feat(cdn): cdn功能提交
这个提交包含在:
@@ -28,29 +28,3 @@ spec:
|
|||||||
name: halo
|
name: halo
|
||||||
port:
|
port:
|
||||||
number: 80
|
number: 80
|
||||||
|
|
||||||
---
|
|
||||||
apiVersion: networking.k8s.io/v1
|
|
||||||
kind: Ingress
|
|
||||||
metadata:
|
|
||||||
name: halo-static-cdn
|
|
||||||
namespace: apps
|
|
||||||
annotations:
|
|
||||||
nginx.ingress.kubernetes.io/use-regex: "true"
|
|
||||||
nginx.ingress.kubernetes.io/rewrite-target: /$2
|
|
||||||
# 添加允许跨域请求
|
|
||||||
nginx.ingress.kubernetes.io/enable-cors: "true"
|
|
||||||
nginx.ingress.kubernetes.io/cors-allow-origin: "https://dev.cm, https://*.dev.cm, https://fillcode.cm, https://*.fillcode.cm"
|
|
||||||
spec:
|
|
||||||
ingressClassName: nginx
|
|
||||||
rules:
|
|
||||||
- host: cdn.fillcode.com
|
|
||||||
http:
|
|
||||||
paths:
|
|
||||||
- path: /dev-cm(/|$)(.*)
|
|
||||||
pathType: ImplementationSpecific
|
|
||||||
backend:
|
|
||||||
service:
|
|
||||||
name: halo
|
|
||||||
port:
|
|
||||||
number: 80
|
|
||||||
|
|||||||
@@ -1,301 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta content="width=device-width,initial-scale=1,user-scalable=no,viewport-fit=cover" name="viewport">
|
|
||||||
<title>出于安全原因 请完成验证</title>
|
|
||||||
<script src="{{captcha_frontend_js}}" async defer></script>
|
|
||||||
<style>
|
|
||||||
* {
|
|
||||||
-webkit-box-sizing: border-box;
|
|
||||||
box-sizing: border-box
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
padding: 0;
|
|
||||||
margin: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
#error {
|
|
||||||
position: relative;
|
|
||||||
height: 100vh
|
|
||||||
}
|
|
||||||
|
|
||||||
#error .error {
|
|
||||||
position: absolute;
|
|
||||||
left: 50%;
|
|
||||||
top: 50%;
|
|
||||||
-webkit-transform: translate(-50%, -50%);
|
|
||||||
-ms-transform: translate(-50%, -50%);
|
|
||||||
transform: translate(-50%, -50%)
|
|
||||||
}
|
|
||||||
|
|
||||||
#error .error-bg {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
overflow: hidden
|
|
||||||
}
|
|
||||||
|
|
||||||
#error .error-bg > div {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
width: 1px;
|
|
||||||
background-color: #eee
|
|
||||||
}
|
|
||||||
|
|
||||||
#error .error-bg > div:nth-child(1) {
|
|
||||||
left: 20%
|
|
||||||
}
|
|
||||||
|
|
||||||
#error .error-bg > div:nth-child(2) {
|
|
||||||
left: 40%
|
|
||||||
}
|
|
||||||
|
|
||||||
#error .error-bg > div:nth-child(3) {
|
|
||||||
left: 60%
|
|
||||||
}
|
|
||||||
|
|
||||||
#error .error-bg > div:nth-child(4) {
|
|
||||||
left: 80%
|
|
||||||
}
|
|
||||||
|
|
||||||
#error .error-bg > div:after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: -.5px;
|
|
||||||
-webkit-transform: translateY(-160px);
|
|
||||||
-ms-transform: translateY(-160px);
|
|
||||||
transform: translateY(-160px);
|
|
||||||
height: 160px;
|
|
||||||
width: 2px;
|
|
||||||
background-color: #1cfafe
|
|
||||||
}
|
|
||||||
|
|
||||||
@-webkit-keyframes drop {
|
|
||||||
90% {
|
|
||||||
height: 20px
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
height: 160px;
|
|
||||||
-webkit-transform: translateY(calc(100vh + 160px));
|
|
||||||
transform: translateY(calc(100vh + 160px))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes drop {
|
|
||||||
90% {
|
|
||||||
height: 20px
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
height: 160px;
|
|
||||||
-webkit-transform: translateY(calc(100vh + 160px));
|
|
||||||
transform: translateY(calc(100vh + 160px))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#error .error-bg > div:nth-child(1):after {
|
|
||||||
-webkit-animation: drop 3s infinite linear;
|
|
||||||
animation: drop 3s infinite linear;
|
|
||||||
-webkit-animation-delay: .2s;
|
|
||||||
animation-delay: .2s
|
|
||||||
}
|
|
||||||
|
|
||||||
#error .error-bg > div:nth-child(2):after {
|
|
||||||
-webkit-animation: drop 2s infinite linear;
|
|
||||||
animation: drop 2s infinite linear;
|
|
||||||
-webkit-animation-delay: .7s;
|
|
||||||
animation-delay: .7s
|
|
||||||
}
|
|
||||||
|
|
||||||
#error .error-bg > div:nth-child(3):after {
|
|
||||||
-webkit-animation: drop 3s infinite linear;
|
|
||||||
animation: drop 3s infinite linear;
|
|
||||||
-webkit-animation-delay: .9s;
|
|
||||||
animation-delay: .9s
|
|
||||||
}
|
|
||||||
|
|
||||||
#error .error-bg > div:nth-child(4):after {
|
|
||||||
-webkit-animation: drop 2s infinite linear;
|
|
||||||
animation: drop 2s infinite linear;
|
|
||||||
-webkit-animation-delay: 1.2s;
|
|
||||||
animation-delay: 1.2s
|
|
||||||
}
|
|
||||||
|
|
||||||
.error {
|
|
||||||
max-width: 520px;
|
|
||||||
width: 100%;
|
|
||||||
padding: 20px;
|
|
||||||
text-align: center
|
|
||||||
}
|
|
||||||
|
|
||||||
.error .error-code {
|
|
||||||
height: 210px;
|
|
||||||
line-height: 210px
|
|
||||||
}
|
|
||||||
|
|
||||||
.error .error-code h1 {
|
|
||||||
font-family: oswald, sans-serif;
|
|
||||||
font-size: 80px;
|
|
||||||
font-weight: 700;
|
|
||||||
margin: 0;
|
|
||||||
text-shadow: 4px 4px 0 #1cfafe
|
|
||||||
}
|
|
||||||
|
|
||||||
.error h2 {
|
|
||||||
font-family: oswald, sans-serif;
|
|
||||||
font-size: 42px;
|
|
||||||
font-weight: 700;
|
|
||||||
margin: 0;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 1.6px
|
|
||||||
}
|
|
||||||
|
|
||||||
.error p {
|
|
||||||
font-family: lato, sans-serif;
|
|
||||||
color: #000;
|
|
||||||
font-weight: 400;
|
|
||||||
margin-top: 20px;
|
|
||||||
margin-bottom: 25px
|
|
||||||
}
|
|
||||||
|
|
||||||
.error a {
|
|
||||||
font-family: lato, sans-serif;
|
|
||||||
padding: 10px 30px;
|
|
||||||
display: inline-block;
|
|
||||||
color: #000;
|
|
||||||
font-weight: 400;
|
|
||||||
text-transform: uppercase;
|
|
||||||
-webkit-box-shadow: 0 0 0 2px #000, 2px 2px 0 2px #1cfafe;
|
|
||||||
box-shadow: 0 0 0 2px #000, 2px 2px 0 2px #1cfafe;
|
|
||||||
text-decoration: none;
|
|
||||||
-webkit-transition: .2s all;
|
|
||||||
transition: .2s all
|
|
||||||
}
|
|
||||||
|
|
||||||
.error a:not(:first-of-type) {
|
|
||||||
margin-left: 20px
|
|
||||||
}
|
|
||||||
|
|
||||||
.error a:hover {
|
|
||||||
background-color: #1cfafe;
|
|
||||||
-webkit-box-shadow: 0 0 0 0 #000, 0 0 0 2px #1cfafe;
|
|
||||||
box-shadow: 0 0 0 0 #000, 0 0 0 2px #1cfafe
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-social > a {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
line-height: 40px;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0 5px
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-social > a:hover {
|
|
||||||
background-color: #1cfafe;
|
|
||||||
-webkit-box-shadow: 0 0 0 0 #000, 0 0 0 2px #1cfafe;
|
|
||||||
box-shadow: 0 0 0 0 #000, 0 0 0 2px #1cfafe
|
|
||||||
}
|
|
||||||
|
|
||||||
#captcha-form {
|
|
||||||
position: relative;
|
|
||||||
width: 300px;
|
|
||||||
height: 65px;
|
|
||||||
overflow: hidden;
|
|
||||||
margin: 0 auto 30px;
|
|
||||||
background-color: #fff;
|
|
||||||
-webkit-box-shadow: 0 0 0 2px #000, 2px 2px 0 2px #1cfafe;
|
|
||||||
box-shadow: 0 0 0 2px #000, 2px 2px 0 2px #1cfafe;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
z-index: 1;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
|
||||||
height: 65px;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading:has(+ *) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading::before {
|
|
||||||
content: "";
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
border: 2px solid #000;
|
|
||||||
border-right-color: #1cfafe;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
#captcha {
|
|
||||||
position: relative;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
from {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media only screen and (max-width: 480px) {
|
|
||||||
.error .error-code {
|
|
||||||
height: 122px;
|
|
||||||
line-height: 122px
|
|
||||||
}
|
|
||||||
|
|
||||||
.error .error-code h1 {
|
|
||||||
font-size: 60px
|
|
||||||
}
|
|
||||||
|
|
||||||
.error h2 {
|
|
||||||
font-size: 26px
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="error">
|
|
||||||
<div class="error-bg">
|
|
||||||
<div></div>
|
|
||||||
<div></div>
|
|
||||||
<div></div>
|
|
||||||
<div></div>
|
|
||||||
</div>
|
|
||||||
<div class="error">
|
|
||||||
<div class="error-code">
|
|
||||||
<h1>FillCode</h1>
|
|
||||||
</div>
|
|
||||||
<h2>请完成验证</h2>
|
|
||||||
<p>请完成下面验证, 页面将会自动跳转到访问页面。</p>
|
|
||||||
<form id="captcha-form" method="POST">
|
|
||||||
<div id="captcha" class="{{captcha_frontend_key}}" data-sitekey="{{captcha_site_key}}" data-callback="captchaCallback" data-size="flexible"></div>
|
|
||||||
<div class="loading">验证码加载中, 请稍等...</div>
|
|
||||||
</form>
|
|
||||||
<a href="mailto:admin@dev.cm">联系我们</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
function captchaCallback() {
|
|
||||||
setTimeout(() => document.querySelector('#captcha-form').submit(), 500)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
apiVersion: cert-manager.io/v1
|
|
||||||
kind: Certificate
|
|
||||||
metadata:
|
|
||||||
name: cdn-crt
|
|
||||||
namespace: infra-net
|
|
||||||
spec:
|
|
||||||
secretName: cdn-crt
|
|
||||||
issuerRef:
|
|
||||||
name: dnspod
|
|
||||||
kind: ClusterIssuer
|
|
||||||
group: cert-manager.io
|
|
||||||
dnsNames:
|
|
||||||
- "fillcode.com"
|
|
||||||
- "*.fillcode.com"
|
|
||||||
- "sinceai.com"
|
|
||||||
- "*.sinceai.com"
|
|
||||||
- "dev.cm"
|
|
||||||
- "*.dev.cm"
|
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 更新 ConfigMap 中的静态文件
|
||||||
|
cat > configmap-static.yaml << 'EOF'
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: static
|
||||||
|
namespace: infra-net
|
||||||
|
data:
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# 直接遍历 static 目录并追加到文件
|
||||||
|
for file in static/*; do
|
||||||
|
filename=$(basename "$file")
|
||||||
|
echo " $filename: |" >> configmap-static.yaml
|
||||||
|
sed 's/^/ /' "$file" >> configmap-static.yaml
|
||||||
|
echo "" >> configmap-static.yaml
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "ConfigMap updated successfully!"
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: ConfigMap
|
kind: ConfigMap
|
||||||
metadata:
|
metadata:
|
||||||
name: custom-html
|
name: static
|
||||||
namespace: infra-net
|
namespace: infra-net
|
||||||
data:
|
data:
|
||||||
captcha.html: |
|
captcha.html: |
|
||||||
@@ -17,17 +17,17 @@ data:
|
|||||||
-webkit-box-sizing: border-box;
|
-webkit-box-sizing: border-box;
|
||||||
box-sizing: border-box
|
box-sizing: border-box
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0
|
margin: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
#error {
|
#error {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 100vh
|
height: 100vh
|
||||||
}
|
}
|
||||||
|
|
||||||
#error .error {
|
#error .error {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
@@ -36,7 +36,7 @@ data:
|
|||||||
-ms-transform: translate(-50%, -50%);
|
-ms-transform: translate(-50%, -50%);
|
||||||
transform: translate(-50%, -50%)
|
transform: translate(-50%, -50%)
|
||||||
}
|
}
|
||||||
|
|
||||||
#error .error-bg {
|
#error .error-bg {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
@@ -45,7 +45,7 @@ data:
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
overflow: hidden
|
overflow: hidden
|
||||||
}
|
}
|
||||||
|
|
||||||
#error .error-bg > div {
|
#error .error-bg > div {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
@@ -53,23 +53,23 @@ data:
|
|||||||
width: 1px;
|
width: 1px;
|
||||||
background-color: #eee
|
background-color: #eee
|
||||||
}
|
}
|
||||||
|
|
||||||
#error .error-bg > div:nth-child(1) {
|
#error .error-bg > div:nth-child(1) {
|
||||||
left: 20%
|
left: 20%
|
||||||
}
|
}
|
||||||
|
|
||||||
#error .error-bg > div:nth-child(2) {
|
#error .error-bg > div:nth-child(2) {
|
||||||
left: 40%
|
left: 40%
|
||||||
}
|
}
|
||||||
|
|
||||||
#error .error-bg > div:nth-child(3) {
|
#error .error-bg > div:nth-child(3) {
|
||||||
left: 60%
|
left: 60%
|
||||||
}
|
}
|
||||||
|
|
||||||
#error .error-bg > div:nth-child(4) {
|
#error .error-bg > div:nth-child(4) {
|
||||||
left: 80%
|
left: 80%
|
||||||
}
|
}
|
||||||
|
|
||||||
#error .error-bg > div:after {
|
#error .error-bg > div:after {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -82,71 +82,71 @@ data:
|
|||||||
width: 2px;
|
width: 2px;
|
||||||
background-color: #1cfafe
|
background-color: #1cfafe
|
||||||
}
|
}
|
||||||
|
|
||||||
@-webkit-keyframes drop {
|
@-webkit-keyframes drop {
|
||||||
90% {
|
90% {
|
||||||
height: 20px
|
height: 20px
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
height: 160px;
|
height: 160px;
|
||||||
-webkit-transform: translateY(calc(100vh + 160px));
|
-webkit-transform: translateY(calc(100vh + 160px));
|
||||||
transform: translateY(calc(100vh + 160px))
|
transform: translateY(calc(100vh + 160px))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes drop {
|
@keyframes drop {
|
||||||
90% {
|
90% {
|
||||||
height: 20px
|
height: 20px
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
height: 160px;
|
height: 160px;
|
||||||
-webkit-transform: translateY(calc(100vh + 160px));
|
-webkit-transform: translateY(calc(100vh + 160px));
|
||||||
transform: translateY(calc(100vh + 160px))
|
transform: translateY(calc(100vh + 160px))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#error .error-bg > div:nth-child(1):after {
|
#error .error-bg > div:nth-child(1):after {
|
||||||
-webkit-animation: drop 3s infinite linear;
|
-webkit-animation: drop 3s infinite linear;
|
||||||
animation: drop 3s infinite linear;
|
animation: drop 3s infinite linear;
|
||||||
-webkit-animation-delay: .2s;
|
-webkit-animation-delay: .2s;
|
||||||
animation-delay: .2s
|
animation-delay: .2s
|
||||||
}
|
}
|
||||||
|
|
||||||
#error .error-bg > div:nth-child(2):after {
|
#error .error-bg > div:nth-child(2):after {
|
||||||
-webkit-animation: drop 2s infinite linear;
|
-webkit-animation: drop 2s infinite linear;
|
||||||
animation: drop 2s infinite linear;
|
animation: drop 2s infinite linear;
|
||||||
-webkit-animation-delay: .7s;
|
-webkit-animation-delay: .7s;
|
||||||
animation-delay: .7s
|
animation-delay: .7s
|
||||||
}
|
}
|
||||||
|
|
||||||
#error .error-bg > div:nth-child(3):after {
|
#error .error-bg > div:nth-child(3):after {
|
||||||
-webkit-animation: drop 3s infinite linear;
|
-webkit-animation: drop 3s infinite linear;
|
||||||
animation: drop 3s infinite linear;
|
animation: drop 3s infinite linear;
|
||||||
-webkit-animation-delay: .9s;
|
-webkit-animation-delay: .9s;
|
||||||
animation-delay: .9s
|
animation-delay: .9s
|
||||||
}
|
}
|
||||||
|
|
||||||
#error .error-bg > div:nth-child(4):after {
|
#error .error-bg > div:nth-child(4):after {
|
||||||
-webkit-animation: drop 2s infinite linear;
|
-webkit-animation: drop 2s infinite linear;
|
||||||
animation: drop 2s infinite linear;
|
animation: drop 2s infinite linear;
|
||||||
-webkit-animation-delay: 1.2s;
|
-webkit-animation-delay: 1.2s;
|
||||||
animation-delay: 1.2s
|
animation-delay: 1.2s
|
||||||
}
|
}
|
||||||
|
|
||||||
.error {
|
.error {
|
||||||
max-width: 520px;
|
max-width: 520px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
text-align: center
|
text-align: center
|
||||||
}
|
}
|
||||||
|
|
||||||
.error .error-code {
|
.error .error-code {
|
||||||
height: 210px;
|
height: 210px;
|
||||||
line-height: 210px
|
line-height: 210px
|
||||||
}
|
}
|
||||||
|
|
||||||
.error .error-code h1 {
|
.error .error-code h1 {
|
||||||
font-family: oswald, sans-serif;
|
font-family: oswald, sans-serif;
|
||||||
font-size: 80px;
|
font-size: 80px;
|
||||||
@@ -154,7 +154,7 @@ data:
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
text-shadow: 4px 4px 0 #1cfafe
|
text-shadow: 4px 4px 0 #1cfafe
|
||||||
}
|
}
|
||||||
|
|
||||||
.error h2 {
|
.error h2 {
|
||||||
font-family: oswald, sans-serif;
|
font-family: oswald, sans-serif;
|
||||||
font-size: 42px;
|
font-size: 42px;
|
||||||
@@ -163,7 +163,7 @@ data:
|
|||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 1.6px
|
letter-spacing: 1.6px
|
||||||
}
|
}
|
||||||
|
|
||||||
.error p {
|
.error p {
|
||||||
font-family: lato, sans-serif;
|
font-family: lato, sans-serif;
|
||||||
color: #000;
|
color: #000;
|
||||||
@@ -171,7 +171,7 @@ data:
|
|||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
margin-bottom: 25px
|
margin-bottom: 25px
|
||||||
}
|
}
|
||||||
|
|
||||||
.error a {
|
.error a {
|
||||||
font-family: lato, sans-serif;
|
font-family: lato, sans-serif;
|
||||||
padding: 10px 30px;
|
padding: 10px 30px;
|
||||||
@@ -185,17 +185,17 @@ data:
|
|||||||
-webkit-transition: .2s all;
|
-webkit-transition: .2s all;
|
||||||
transition: .2s all
|
transition: .2s all
|
||||||
}
|
}
|
||||||
|
|
||||||
.error a:not(:first-of-type) {
|
.error a:not(:first-of-type) {
|
||||||
margin-left: 20px
|
margin-left: 20px
|
||||||
}
|
}
|
||||||
|
|
||||||
.error a:hover {
|
.error a:hover {
|
||||||
background-color: #1cfafe;
|
background-color: #1cfafe;
|
||||||
-webkit-box-shadow: 0 0 0 0 #000, 0 0 0 2px #1cfafe;
|
-webkit-box-shadow: 0 0 0 0 #000, 0 0 0 2px #1cfafe;
|
||||||
box-shadow: 0 0 0 0 #000, 0 0 0 2px #1cfafe
|
box-shadow: 0 0 0 0 #000, 0 0 0 2px #1cfafe
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-social > a {
|
.error-social > a {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
@@ -203,13 +203,13 @@ data:
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0 5px
|
margin: 0 5px
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-social > a:hover {
|
.error-social > a:hover {
|
||||||
background-color: #1cfafe;
|
background-color: #1cfafe;
|
||||||
-webkit-box-shadow: 0 0 0 0 #000, 0 0 0 2px #1cfafe;
|
-webkit-box-shadow: 0 0 0 0 #000, 0 0 0 2px #1cfafe;
|
||||||
box-shadow: 0 0 0 0 #000, 0 0 0 2px #1cfafe
|
box-shadow: 0 0 0 0 #000, 0 0 0 2px #1cfafe
|
||||||
}
|
}
|
||||||
|
|
||||||
#captcha-form {
|
#captcha-form {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
@@ -220,7 +220,7 @@ data:
|
|||||||
-webkit-box-shadow: 0 0 0 2px #000, 2px 2px 0 2px #1cfafe;
|
-webkit-box-shadow: 0 0 0 2px #000, 2px 2px 0 2px #1cfafe;
|
||||||
box-shadow: 0 0 0 2px #000, 2px 2px 0 2px #1cfafe;
|
box-shadow: 0 0 0 2px #000, 2px 2px 0 2px #1cfafe;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading {
|
.loading {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
@@ -233,11 +233,11 @@ data:
|
|||||||
height: 65px;
|
height: 65px;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading:has(+ *) {
|
.loading:has(+ *) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading::before {
|
.loading::before {
|
||||||
content: "";
|
content: "";
|
||||||
width: 20px;
|
width: 20px;
|
||||||
@@ -247,12 +247,12 @@ data:
|
|||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
animation: spin 1s linear infinite;
|
animation: spin 1s linear infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
#captcha {
|
#captcha {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes spin {
|
@keyframes spin {
|
||||||
from {
|
from {
|
||||||
transform: rotate(0deg);
|
transform: rotate(0deg);
|
||||||
@@ -261,17 +261,17 @@ data:
|
|||||||
transform: rotate(360deg);
|
transform: rotate(360deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 480px) {
|
@media only screen and (max-width: 480px) {
|
||||||
.error .error-code {
|
.error .error-code {
|
||||||
height: 122px;
|
height: 122px;
|
||||||
line-height: 122px
|
line-height: 122px
|
||||||
}
|
}
|
||||||
|
|
||||||
.error .error-code h1 {
|
.error .error-code h1 {
|
||||||
font-size: 60px
|
font-size: 60px
|
||||||
}
|
}
|
||||||
|
|
||||||
.error h2 {
|
.error h2 {
|
||||||
font-size: 26px
|
font-size: 26px
|
||||||
}
|
}
|
||||||
@@ -293,7 +293,8 @@ data:
|
|||||||
<h2>请完成验证</h2>
|
<h2>请完成验证</h2>
|
||||||
<p>请完成下面验证, 页面将会自动跳转到访问页面。</p>
|
<p>请完成下面验证, 页面将会自动跳转到访问页面。</p>
|
||||||
<form id="captcha-form" method="POST">
|
<form id="captcha-form" method="POST">
|
||||||
<div id="captcha" class="{{captcha_frontend_key}}" data-sitekey="{{captcha_site_key}}" data-callback="captchaCallback" data-size="flexible"></div>
|
<div id="captcha" class="{{captcha_frontend_key}}" data-sitekey="{{captcha_site_key}}"
|
||||||
|
data-callback="captchaCallback" data-size="flexible"></div>
|
||||||
<div class="loading">验证码加载中, 请稍等...</div>
|
<div class="loading">验证码加载中, 请稍等...</div>
|
||||||
</form>
|
</form>
|
||||||
<a href="mailto:admin@dev.cm">联系我们</a>
|
<a href="mailto:admin@dev.cm">联系我们</a>
|
||||||
@@ -307,5 +308,163 @@ data:
|
|||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
pwa-cdn.js: |
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
// 配置
|
||||||
|
const pwaCdnConfig = {
|
||||||
|
cdnUrl: 'https://cdn.fillcode.com/',
|
||||||
|
serviceWorkerUrl: '/__static/sw-cdn.js',
|
||||||
|
staticRegex: /\.(js|css|png|jpg|jpeg|gif|svg|webp|woff|woff2|ttf|ico)$/,
|
||||||
|
debug: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PWA 初始化函数
|
||||||
|
*/
|
||||||
|
async function initializePWA() {
|
||||||
|
// 检查支持
|
||||||
|
if (!('serviceWorker' in navigator)) return console.log('PWA-CDN: Service Worker not supported')
|
||||||
|
|
||||||
|
let registration;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 注册Service Worker - 使用相对路径
|
||||||
|
registration = await navigator.serviceWorker.register(pwaCdnConfig.serviceWorkerUrl, {scope: '/'})
|
||||||
|
|
||||||
|
console.log('PWA-CDN: Service Worker registered')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('PWA-CDN: Failed to register Service Worker:', error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送初始配置
|
||||||
|
const sendConfig = () => {
|
||||||
|
registration.active.postMessage({type: 'CONFIG', config: pwaCdnConfig})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果注册失败,直接返回错误
|
||||||
|
if(!registration) return console.error('PWA-CDN: Service Worker registration failed, cannot send config')
|
||||||
|
|
||||||
|
// 更新配置函数
|
||||||
|
window.updatePWACDNConfig = (newConfig) => {
|
||||||
|
Object.assign(pwaCdnConfig, newConfig)
|
||||||
|
sendConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待Service Worker激活后发送配置
|
||||||
|
if (registration.active) sendConfig()
|
||||||
|
|
||||||
|
// 监听Service Worker更新事件
|
||||||
|
registration.addEventListener('updatefound', () => {
|
||||||
|
const newWorker = registration.installing
|
||||||
|
|
||||||
|
newWorker.addEventListener('statechange', () => {
|
||||||
|
if (newWorker.state === 'activated') sendConfig()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动 PWA-CDN
|
||||||
|
* */
|
||||||
|
initializePWA().catch(console.error)
|
||||||
|
|
||||||
|
sw-cdn.js: |
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
// Service Worker 配置 - 默认值
|
||||||
|
let config = {
|
||||||
|
cdnUrl: 'https://cdn.fillcode.com/',
|
||||||
|
serviceWorkerUrl: '/__static/sw-cdn.js',
|
||||||
|
staticRegex: /\.(js|css|png|jpg|jpeg|gif|svg|webp|woff|woff2|ttf|ico)$/,
|
||||||
|
debug: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听配置更新消息
|
||||||
|
self.addEventListener('message', e => {
|
||||||
|
if (e.data.type !== 'CONFIG') return
|
||||||
|
|
||||||
|
config = e.data.config
|
||||||
|
|
||||||
|
if (config.debug) console.log('PWA-CDN: Config updated', config)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 拦截网络请求
|
||||||
|
self.addEventListener('fetch', e => {
|
||||||
|
const url = new URL(e.request.url)
|
||||||
|
|
||||||
|
// 如果请求不是GET方法,直接返回
|
||||||
|
if (e.request.method !== 'GET') return
|
||||||
|
|
||||||
|
// 如果请求的域名不是当前页面的域名
|
||||||
|
if (url.origin !== self.location.origin) return
|
||||||
|
|
||||||
|
// 过滤__static路径下的请求
|
||||||
|
if (url.pathname.startsWith('/__static/')) return
|
||||||
|
|
||||||
|
// 如果请求的路径不匹配静态资源正则表达式,直接返回
|
||||||
|
if (!config.staticRegex.test(url.pathname)) return
|
||||||
|
|
||||||
|
// 判断是否是强制需要同源请求
|
||||||
|
const requiresSameOrigin = ['worker', 'sharedworker', 'serviceworker'].includes(e.request.destination)
|
||||||
|
|
||||||
|
// 如果是强制需要同源请求的资源类型,直接返回
|
||||||
|
if (requiresSameOrigin) return
|
||||||
|
|
||||||
|
// 开始处理静态资源请求
|
||||||
|
e.respondWith(handleStaticResource(e.request, url))
|
||||||
|
})
|
||||||
|
|
||||||
|
// 处理静态资源请求
|
||||||
|
async function handleStaticResource(request, url) {
|
||||||
|
// 生成CDN子路径
|
||||||
|
const hostname = self.location.hostname
|
||||||
|
const cdnPath = hostname.replace(/\./g, '-')
|
||||||
|
|
||||||
|
const targetUrl = config.cdnUrl + cdnPath + url.pathname + url.search
|
||||||
|
|
||||||
|
if (config.debug) console.log('PWA-CDN:', url.href, '->', targetUrl)
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 创建新请求,保留原始缓存策略
|
||||||
|
const newRequest = new Request(targetUrl, {...request, mode: 'cors'})
|
||||||
|
|
||||||
|
// 请求目标域名,浏览器会自动处理缓存
|
||||||
|
const response = await fetch(newRequest)
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error('Target domain response not ok: ' + response.status)
|
||||||
|
|
||||||
|
// 修复重定向问题
|
||||||
|
if([301, 302, 307, 308].includes(response.status)) {
|
||||||
|
const location = response.headers.get('location')
|
||||||
|
|
||||||
|
// 如果重定向的地址不是CDN地址,或者已经包含了CDN子路径,则返回原始响应
|
||||||
|
if(!location?.startsWith('/') || location.startsWith(`/${cdnPath}`)) return response
|
||||||
|
|
||||||
|
// 否则,重定向到新的CDN地址
|
||||||
|
const redirectUrl = new URL(location, config.cdnUrl + cdnPath)
|
||||||
|
|
||||||
|
if (config.debug) console.log('PWA-CDN: Redirecting to', redirectUrl.href)
|
||||||
|
return Response.redirect(redirectUrl.href, response.status)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
} catch (error) {
|
||||||
|
if (config.debug) console.warn('PWA-CDN: Fallback to original request for', url.href, error)
|
||||||
|
|
||||||
|
// 失败时回退到原始请求
|
||||||
|
return fetch(request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Service Worker 生命周期
|
||||||
|
self.addEventListener('install', () => {
|
||||||
|
if (config.debug) console.log('PWA-CDN: Service Worker installing')
|
||||||
|
self.skipWaiting().catch(console.error)
|
||||||
|
})
|
||||||
|
|
||||||
|
self.addEventListener('activate', () => {
|
||||||
|
if (config.debug) console.log('PWA-CDN: Service Worker activated')
|
||||||
|
self.clients.claim().catch(console.error)
|
||||||
|
})
|
||||||
|
|
||||||
@@ -23,6 +23,12 @@ spec:
|
|||||||
hostNetwork: true
|
hostNetwork: true
|
||||||
hostPort:
|
hostPort:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
# 添加 DNS 配置
|
||||||
|
dnsPolicy: "None"
|
||||||
|
dnsConfig:
|
||||||
|
nameservers:
|
||||||
|
- "169.254.20.10"
|
||||||
|
- "10.43.0.10"
|
||||||
service:
|
service:
|
||||||
enabled: false
|
enabled: false
|
||||||
publishService:
|
publishService:
|
||||||
@@ -52,6 +58,12 @@ spec:
|
|||||||
server-snippet: |
|
server-snippet: |
|
||||||
# dns配置 配置在http块下会出现重复配置 所以配置在server块下
|
# dns配置 配置在http块下会出现重复配置 所以配置在server块下
|
||||||
resolver 169.254.20.10 10.43.0.10 ipv6=off;
|
resolver 169.254.20.10 10.43.0.10 ipv6=off;
|
||||||
|
# 代理全局静态资源 可提供serviceWorker的支持
|
||||||
|
location ^~ /__static/ {
|
||||||
|
proxy_pass http://ingress-nginx-defaultbackend.infra-net.svc.cluster.local/static/;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
add_header Service-Worker-Allowed "/";
|
||||||
|
}
|
||||||
# 启用geoip2模块
|
# 启用geoip2模块
|
||||||
use-geoip: "false"
|
use-geoip: "false"
|
||||||
use-geoip2: "true"
|
use-geoip2: "true"
|
||||||
@@ -107,7 +119,7 @@ spec:
|
|||||||
plugins: "crowdsec"
|
plugins: "crowdsec"
|
||||||
lua-shared-dicts: "crowdsec_cache: 50m"
|
lua-shared-dicts: "crowdsec_cache: 50m"
|
||||||
# 启用geoip2模块
|
# 启用geoip2模块
|
||||||
maxmindLicenseKey: "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"
|
maxmindLicenseKey: "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"
|
||||||
extraArgs:
|
extraArgs:
|
||||||
default-ssl-certificate: "infra-net/dev-cm-crt"
|
default-ssl-certificate: "infra-net/dev-cm-crt"
|
||||||
# crowdsec插件配置
|
# crowdsec插件配置
|
||||||
@@ -137,7 +149,7 @@ spec:
|
|||||||
- name: SECRET_KEY
|
- name: SECRET_KEY
|
||||||
value: "0x4AAAAAAAxJ2dwFOaNg5ae3c6wYTmWH0bU"
|
value: "0x4AAAAAAAxJ2dwFOaNg5ae3c6wYTmWH0bU"
|
||||||
- name: CAPTCHA_TEMPLATE_PATH
|
- name: CAPTCHA_TEMPLATE_PATH
|
||||||
value: /etc/nginx/lua/plugins/crowdsec/templates/captcha.html
|
value: /etc/nginx/static/captcha.html
|
||||||
command: ['sh', '-c', "bash /docker_start.sh; mkdir -p /lua_plugins/crowdsec/; cp -R /crowdsec/* /lua_plugins/crowdsec/"]
|
command: ['sh', '-c', "bash /docker_start.sh; mkdir -p /lua_plugins/crowdsec/; cp -R /crowdsec/* /lua_plugins/crowdsec/"]
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- name: crowdsec-bouncer-plugin
|
- name: crowdsec-bouncer-plugin
|
||||||
@@ -145,26 +157,33 @@ spec:
|
|||||||
extraVolumes:
|
extraVolumes:
|
||||||
- name: crowdsec-bouncer-plugin
|
- name: crowdsec-bouncer-plugin
|
||||||
emptyDir: {}
|
emptyDir: {}
|
||||||
- name: custom-html
|
- name: static
|
||||||
configMap:
|
configMap:
|
||||||
name: custom-html
|
name: static
|
||||||
extraVolumeMounts:
|
extraVolumeMounts:
|
||||||
- name: crowdsec-bouncer-plugin
|
- name: crowdsec-bouncer-plugin
|
||||||
mountPath: /etc/nginx/lua/plugins/crowdsec
|
mountPath: /etc/nginx/lua/plugins/crowdsec
|
||||||
subPath: crowdsec
|
subPath: crowdsec
|
||||||
- name: custom-html
|
- name: static
|
||||||
mountPath: /etc/nginx/lua/plugins/crowdsec/templates
|
mountPath: /etc/nginx/static
|
||||||
defaultBackend:
|
defaultBackend:
|
||||||
enabled: true
|
enabled: true
|
||||||
image:
|
image:
|
||||||
registry: docker.io
|
registry: docker.io
|
||||||
image: devcm/default-backend
|
image: devcm/default-backend
|
||||||
tag: v0.1.0
|
tag: v0.2.0
|
||||||
autoscaling:
|
autoscaling:
|
||||||
enabled: true
|
enabled: true
|
||||||
minReplicas: 1
|
minReplicas: 1
|
||||||
maxReplicas: 3
|
maxReplicas: 3
|
||||||
targetCPUUtilizationPercentage: 80
|
targetCPUUtilizationPercentage: 80
|
||||||
|
extraVolumes:
|
||||||
|
- name: static
|
||||||
|
configMap:
|
||||||
|
name: static
|
||||||
|
extraVolumeMounts:
|
||||||
|
- name: static
|
||||||
|
mountPath: /app/static
|
||||||
affinity:
|
affinity:
|
||||||
nodeAffinity:
|
nodeAffinity:
|
||||||
requiredDuringSchedulingIgnoredDuringExecution:
|
requiredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
---
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: cdn
|
||||||
|
namespace: infra-net
|
||||||
|
annotations:
|
||||||
|
nginx.ingress.kubernetes.io/use-regex: "true"
|
||||||
|
nginx.ingress.kubernetes.io/rewrite-target: /$2
|
||||||
|
# 添加允许跨域请求
|
||||||
|
nginx.ingress.kubernetes.io/enable-cors: "true"
|
||||||
|
nginx.ingress.kubernetes.io/cors-allow-origin: "https://dev.cm, https://*.dev.cm, https://fillcode.cm, https://*.fillcode.cm"
|
||||||
|
spec:
|
||||||
|
ingressClassName: nginx
|
||||||
|
rules:
|
||||||
|
- host: cdn.fillcode.com
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /dev-cm(/|$)(.*)
|
||||||
|
pathType: ImplementationSpecific
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: cdn-halo
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
- path: /git-dev-cm(/|$)(.*)
|
||||||
|
pathType: ImplementationSpecific
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: cdn-gitea-http
|
||||||
|
port:
|
||||||
|
number: 3000
|
||||||
|
- path: /monitor-dev-cm(/|$)(.*)
|
||||||
|
pathType: ImplementationSpecific
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: cdn-prometheus-grafana
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: cdn-halo
|
||||||
|
namespace: infra-net
|
||||||
|
spec:
|
||||||
|
type: ExternalName
|
||||||
|
externalName: halo.apps.svc.cluster.local
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: cdn-gitea-http
|
||||||
|
namespace: infra-net
|
||||||
|
spec:
|
||||||
|
type: ExternalName
|
||||||
|
externalName: gitea-http.infra-devops.svc.cluster.local
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: cdn-prometheus-grafana
|
||||||
|
namespace: infra-net
|
||||||
|
spec:
|
||||||
|
type: ExternalName
|
||||||
|
externalName: prometheus-grafana.infra-monitor.svc.cluster.local
|
||||||
@@ -0,0 +1,302 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta content="width=device-width,initial-scale=1,user-scalable=no,viewport-fit=cover" name="viewport">
|
||||||
|
<title>出于安全原因 请完成验证</title>
|
||||||
|
<script src="{{captcha_frontend_js}}" async defer></script>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
box-sizing: border-box
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
#error {
|
||||||
|
position: relative;
|
||||||
|
height: 100vh
|
||||||
|
}
|
||||||
|
|
||||||
|
#error .error {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
-webkit-transform: translate(-50%, -50%);
|
||||||
|
-ms-transform: translate(-50%, -50%);
|
||||||
|
transform: translate(-50%, -50%)
|
||||||
|
}
|
||||||
|
|
||||||
|
#error .error-bg {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
overflow: hidden
|
||||||
|
}
|
||||||
|
|
||||||
|
#error .error-bg > div {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 1px;
|
||||||
|
background-color: #eee
|
||||||
|
}
|
||||||
|
|
||||||
|
#error .error-bg > div:nth-child(1) {
|
||||||
|
left: 20%
|
||||||
|
}
|
||||||
|
|
||||||
|
#error .error-bg > div:nth-child(2) {
|
||||||
|
left: 40%
|
||||||
|
}
|
||||||
|
|
||||||
|
#error .error-bg > div:nth-child(3) {
|
||||||
|
left: 60%
|
||||||
|
}
|
||||||
|
|
||||||
|
#error .error-bg > div:nth-child(4) {
|
||||||
|
left: 80%
|
||||||
|
}
|
||||||
|
|
||||||
|
#error .error-bg > div:after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -.5px;
|
||||||
|
-webkit-transform: translateY(-160px);
|
||||||
|
-ms-transform: translateY(-160px);
|
||||||
|
transform: translateY(-160px);
|
||||||
|
height: 160px;
|
||||||
|
width: 2px;
|
||||||
|
background-color: #1cfafe
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes drop {
|
||||||
|
90% {
|
||||||
|
height: 20px
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
height: 160px;
|
||||||
|
-webkit-transform: translateY(calc(100vh + 160px));
|
||||||
|
transform: translateY(calc(100vh + 160px))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes drop {
|
||||||
|
90% {
|
||||||
|
height: 20px
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
height: 160px;
|
||||||
|
-webkit-transform: translateY(calc(100vh + 160px));
|
||||||
|
transform: translateY(calc(100vh + 160px))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#error .error-bg > div:nth-child(1):after {
|
||||||
|
-webkit-animation: drop 3s infinite linear;
|
||||||
|
animation: drop 3s infinite linear;
|
||||||
|
-webkit-animation-delay: .2s;
|
||||||
|
animation-delay: .2s
|
||||||
|
}
|
||||||
|
|
||||||
|
#error .error-bg > div:nth-child(2):after {
|
||||||
|
-webkit-animation: drop 2s infinite linear;
|
||||||
|
animation: drop 2s infinite linear;
|
||||||
|
-webkit-animation-delay: .7s;
|
||||||
|
animation-delay: .7s
|
||||||
|
}
|
||||||
|
|
||||||
|
#error .error-bg > div:nth-child(3):after {
|
||||||
|
-webkit-animation: drop 3s infinite linear;
|
||||||
|
animation: drop 3s infinite linear;
|
||||||
|
-webkit-animation-delay: .9s;
|
||||||
|
animation-delay: .9s
|
||||||
|
}
|
||||||
|
|
||||||
|
#error .error-bg > div:nth-child(4):after {
|
||||||
|
-webkit-animation: drop 2s infinite linear;
|
||||||
|
animation: drop 2s infinite linear;
|
||||||
|
-webkit-animation-delay: 1.2s;
|
||||||
|
animation-delay: 1.2s
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
max-width: 520px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center
|
||||||
|
}
|
||||||
|
|
||||||
|
.error .error-code {
|
||||||
|
height: 210px;
|
||||||
|
line-height: 210px
|
||||||
|
}
|
||||||
|
|
||||||
|
.error .error-code h1 {
|
||||||
|
font-family: oswald, sans-serif;
|
||||||
|
font-size: 80px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin: 0;
|
||||||
|
text-shadow: 4px 4px 0 #1cfafe
|
||||||
|
}
|
||||||
|
|
||||||
|
.error h2 {
|
||||||
|
font-family: oswald, sans-serif;
|
||||||
|
font-size: 42px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin: 0;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1.6px
|
||||||
|
}
|
||||||
|
|
||||||
|
.error p {
|
||||||
|
font-family: lato, sans-serif;
|
||||||
|
color: #000;
|
||||||
|
font-weight: 400;
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-bottom: 25px
|
||||||
|
}
|
||||||
|
|
||||||
|
.error a {
|
||||||
|
font-family: lato, sans-serif;
|
||||||
|
padding: 10px 30px;
|
||||||
|
display: inline-block;
|
||||||
|
color: #000;
|
||||||
|
font-weight: 400;
|
||||||
|
text-transform: uppercase;
|
||||||
|
-webkit-box-shadow: 0 0 0 2px #000, 2px 2px 0 2px #1cfafe;
|
||||||
|
box-shadow: 0 0 0 2px #000, 2px 2px 0 2px #1cfafe;
|
||||||
|
text-decoration: none;
|
||||||
|
-webkit-transition: .2s all;
|
||||||
|
transition: .2s all
|
||||||
|
}
|
||||||
|
|
||||||
|
.error a:not(:first-of-type) {
|
||||||
|
margin-left: 20px
|
||||||
|
}
|
||||||
|
|
||||||
|
.error a:hover {
|
||||||
|
background-color: #1cfafe;
|
||||||
|
-webkit-box-shadow: 0 0 0 0 #000, 0 0 0 2px #1cfafe;
|
||||||
|
box-shadow: 0 0 0 0 #000, 0 0 0 2px #1cfafe
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-social > a {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
line-height: 40px;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0 5px
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-social > a:hover {
|
||||||
|
background-color: #1cfafe;
|
||||||
|
-webkit-box-shadow: 0 0 0 0 #000, 0 0 0 2px #1cfafe;
|
||||||
|
box-shadow: 0 0 0 0 #000, 0 0 0 2px #1cfafe
|
||||||
|
}
|
||||||
|
|
||||||
|
#captcha-form {
|
||||||
|
position: relative;
|
||||||
|
width: 300px;
|
||||||
|
height: 65px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 0 auto 30px;
|
||||||
|
background-color: #fff;
|
||||||
|
-webkit-box-shadow: 0 0 0 2px #000, 2px 2px 0 2px #1cfafe;
|
||||||
|
box-shadow: 0 0 0 2px #000, 2px 2px 0 2px #1cfafe;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 65px;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading:has(+ *) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading::before {
|
||||||
|
content: "";
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border: 2px solid #000;
|
||||||
|
border-right-color: #1cfafe;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
#captcha {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 480px) {
|
||||||
|
.error .error-code {
|
||||||
|
height: 122px;
|
||||||
|
line-height: 122px
|
||||||
|
}
|
||||||
|
|
||||||
|
.error .error-code h1 {
|
||||||
|
font-size: 60px
|
||||||
|
}
|
||||||
|
|
||||||
|
.error h2 {
|
||||||
|
font-size: 26px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="error">
|
||||||
|
<div class="error-bg">
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
<div class="error">
|
||||||
|
<div class="error-code">
|
||||||
|
<h1>FillCode</h1>
|
||||||
|
</div>
|
||||||
|
<h2>请完成验证</h2>
|
||||||
|
<p>请完成下面验证, 页面将会自动跳转到访问页面。</p>
|
||||||
|
<form id="captcha-form" method="POST">
|
||||||
|
<div id="captcha" class="{{captcha_frontend_key}}" data-sitekey="{{captcha_site_key}}"
|
||||||
|
data-callback="captchaCallback" data-size="flexible"></div>
|
||||||
|
<div class="loading">验证码加载中, 请稍等...</div>
|
||||||
|
</form>
|
||||||
|
<a href="mailto:admin@dev.cm">联系我们</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
function captchaCallback() {
|
||||||
|
setTimeout(() => document.querySelector('#captcha-form').submit(), 500)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
// 配置
|
||||||
|
const pwaCdnConfig = {
|
||||||
|
cdnUrl: 'https://cdn.fillcode.com/',
|
||||||
|
serviceWorkerUrl: '/__static/sw-cdn.js',
|
||||||
|
staticRegex: /\.(js|css|png|jpg|jpeg|gif|svg|webp|woff|woff2|ttf|ico)$/,
|
||||||
|
debug: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PWA 初始化函数
|
||||||
|
*/
|
||||||
|
async function initializePWA() {
|
||||||
|
// 检查支持
|
||||||
|
if (!('serviceWorker' in navigator)) return console.log('PWA-CDN: Service Worker not supported')
|
||||||
|
|
||||||
|
let registration;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 注册Service Worker - 使用相对路径
|
||||||
|
registration = await navigator.serviceWorker.register(pwaCdnConfig.serviceWorkerUrl, {scope: '/'})
|
||||||
|
|
||||||
|
console.log('PWA-CDN: Service Worker registered')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('PWA-CDN: Failed to register Service Worker:', error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送初始配置
|
||||||
|
const sendConfig = () => {
|
||||||
|
registration.active.postMessage({type: 'CONFIG', config: pwaCdnConfig})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果注册失败,直接返回错误
|
||||||
|
if(!registration) return console.error('PWA-CDN: Service Worker registration failed, cannot send config')
|
||||||
|
|
||||||
|
// 更新配置函数
|
||||||
|
window.updatePWACDNConfig = (newConfig) => {
|
||||||
|
Object.assign(pwaCdnConfig, newConfig)
|
||||||
|
sendConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待Service Worker激活后发送配置
|
||||||
|
if (registration.active) sendConfig()
|
||||||
|
|
||||||
|
// 监听Service Worker更新事件
|
||||||
|
registration.addEventListener('updatefound', () => {
|
||||||
|
const newWorker = registration.installing
|
||||||
|
|
||||||
|
newWorker.addEventListener('statechange', () => {
|
||||||
|
if (newWorker.state === 'activated') sendConfig()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动 PWA-CDN
|
||||||
|
* */
|
||||||
|
initializePWA().catch(console.error)
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
// Service Worker 配置 - 默认值
|
||||||
|
let config = {
|
||||||
|
cdnUrl: 'https://cdn.fillcode.com/',
|
||||||
|
serviceWorkerUrl: '/__static/sw-cdn.js',
|
||||||
|
staticRegex: /\.(js|css|png|jpg|jpeg|gif|svg|webp|woff|woff2|ttf|ico)$/,
|
||||||
|
debug: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听配置更新消息
|
||||||
|
self.addEventListener('message', e => {
|
||||||
|
if (e.data.type !== 'CONFIG') return
|
||||||
|
|
||||||
|
config = e.data.config
|
||||||
|
|
||||||
|
if (config.debug) console.log('PWA-CDN: Config updated', config)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 拦截网络请求
|
||||||
|
self.addEventListener('fetch', e => {
|
||||||
|
const url = new URL(e.request.url)
|
||||||
|
|
||||||
|
// 如果请求不是GET方法,直接返回
|
||||||
|
if (e.request.method !== 'GET') return
|
||||||
|
|
||||||
|
// 如果请求的域名不是当前页面的域名
|
||||||
|
if (url.origin !== self.location.origin) return
|
||||||
|
|
||||||
|
// 过滤__static路径下的请求
|
||||||
|
if (url.pathname.startsWith('/__static/')) return
|
||||||
|
|
||||||
|
// 如果请求的路径不匹配静态资源正则表达式,直接返回
|
||||||
|
if (!config.staticRegex.test(url.pathname)) return
|
||||||
|
|
||||||
|
// 判断是否是强制需要同源请求
|
||||||
|
const requiresSameOrigin = ['worker', 'sharedworker', 'serviceworker'].includes(e.request.destination)
|
||||||
|
|
||||||
|
// 如果是强制需要同源请求的资源类型,直接返回
|
||||||
|
if (requiresSameOrigin) return
|
||||||
|
|
||||||
|
// 开始处理静态资源请求
|
||||||
|
e.respondWith(handleStaticResource(e.request, url))
|
||||||
|
})
|
||||||
|
|
||||||
|
// 处理静态资源请求
|
||||||
|
async function handleStaticResource(request, url) {
|
||||||
|
// 生成CDN子路径
|
||||||
|
const hostname = self.location.hostname
|
||||||
|
const cdnPath = hostname.replace(/\./g, '-')
|
||||||
|
|
||||||
|
const targetUrl = config.cdnUrl + cdnPath + url.pathname + url.search
|
||||||
|
|
||||||
|
if (config.debug) console.log('PWA-CDN:', url.href, '->', targetUrl)
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 创建新请求,保留原始缓存策略
|
||||||
|
const newRequest = new Request(targetUrl, {...request, mode: 'cors'})
|
||||||
|
|
||||||
|
// 请求目标域名,浏览器会自动处理缓存
|
||||||
|
const response = await fetch(newRequest)
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error('Target domain response not ok: ' + response.status)
|
||||||
|
|
||||||
|
// 修复重定向问题
|
||||||
|
if([301, 302, 307, 308].includes(response.status)) {
|
||||||
|
const location = response.headers.get('location')
|
||||||
|
|
||||||
|
// 如果重定向的地址不是CDN地址,或者已经包含了CDN子路径,则返回原始响应
|
||||||
|
if(!location?.startsWith('/') || location.startsWith(`/${cdnPath}`)) return response
|
||||||
|
|
||||||
|
// 否则,重定向到新的CDN地址
|
||||||
|
const redirectUrl = new URL(location, config.cdnUrl + cdnPath)
|
||||||
|
|
||||||
|
if (config.debug) console.log('PWA-CDN: Redirecting to', redirectUrl.href)
|
||||||
|
return Response.redirect(redirectUrl.href, response.status)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
} catch (error) {
|
||||||
|
if (config.debug) console.warn('PWA-CDN: Fallback to original request for', url.href, error)
|
||||||
|
|
||||||
|
// 失败时回退到原始请求
|
||||||
|
return fetch(request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Service Worker 生命周期
|
||||||
|
self.addEventListener('install', () => {
|
||||||
|
if (config.debug) console.log('PWA-CDN: Service Worker installing')
|
||||||
|
self.skipWaiting().catch(console.error)
|
||||||
|
})
|
||||||
|
|
||||||
|
self.addEventListener('activate', () => {
|
||||||
|
if (config.debug) console.log('PWA-CDN: Service Worker activated')
|
||||||
|
self.clients.claim().catch(console.error)
|
||||||
|
})
|
||||||
在新议题中引用
屏蔽一个用户