first commit
这个提交包含在:
+27
@@ -0,0 +1,27 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
.gradle
|
||||
build
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
FROM denoland/deno:latest
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN deno install
|
||||
|
||||
CMD ["deno", "run", "start"]
|
||||
|
||||
EXPOSE 8080
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"imports": {
|
||||
"@fastify/view": "npm:@fastify/view@^10.0.1",
|
||||
"@std/path": "jsr:@std/path@^1.0.6",
|
||||
"fastify": "npm:fastify@^5.0.0",
|
||||
"handlebars": "npm:handlebars@^4.7.8",
|
||||
"mime": "npm:mime@^4.0.4"
|
||||
},
|
||||
"tasks": {
|
||||
"start": "deno run --allow-net --allow-read --allow-sys --allow-env main.ts"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,348 @@
|
||||
{
|
||||
"version": "4",
|
||||
"specifiers": {
|
||||
"jsr:@std/path@^1.0.6": "1.0.6",
|
||||
"npm:@fastify/view@^10.0.1": "10.0.1",
|
||||
"npm:fastify@5": "5.0.0",
|
||||
"npm:handlebars@^4.7.8": "4.7.8",
|
||||
"npm:mime@^4.0.4": "4.0.4"
|
||||
},
|
||||
"jsr": {
|
||||
"@std/path@1.0.6": {
|
||||
"integrity": "ab2c55f902b380cf28e0eec501b4906e4c1960d13f00e11cfbcd21de15f18fed"
|
||||
}
|
||||
},
|
||||
"npm": {
|
||||
"@fastify/ajv-compiler@4.0.1_ajv@8.17.1": {
|
||||
"integrity": "sha512-DxrBdgsjNLP0YM6W5Hd6/Fmj43S8zMKiFJYgi+Ri3htTGAowPVG/tG1wpnWLMjufEnehRivUCKZ1pLDIoZdTuw==",
|
||||
"dependencies": [
|
||||
"ajv",
|
||||
"ajv-formats",
|
||||
"fast-uri@3.0.2"
|
||||
]
|
||||
},
|
||||
"@fastify/error@4.0.0": {
|
||||
"integrity": "sha512-OO/SA8As24JtT1usTUTKgGH7uLvhfwZPwlptRi2Dp5P4KKmJI3gvsZ8MIHnNwDs4sLf/aai5LzTyl66xr7qMxA=="
|
||||
},
|
||||
"@fastify/fast-json-stringify-compiler@5.0.1": {
|
||||
"integrity": "sha512-f2d3JExJgFE3UbdFcpPwqNUEoHWmt8pAKf8f+9YuLESdefA0WgqxeT6DrGL4Yrf/9ihXNSKOqpjEmurV405meA==",
|
||||
"dependencies": [
|
||||
"fast-json-stringify"
|
||||
]
|
||||
},
|
||||
"@fastify/merge-json-schemas@0.1.1": {
|
||||
"integrity": "sha512-fERDVz7topgNjtXsJTTW1JKLy0rhuLRcquYqNR9rF7OcVpCa2OVW49ZPDIhaRRCaUuvVxI+N416xUoF76HNSXA==",
|
||||
"dependencies": [
|
||||
"fast-deep-equal"
|
||||
]
|
||||
},
|
||||
"@fastify/view@10.0.1": {
|
||||
"integrity": "sha512-rXtBN0oVDmoRZAS7lelrCIahf+qFtlMOOas8VPdA7JvrJ9ChcF7e36pIUPU0Vbs3KmHxESUb7XatavUZEe/k5Q==",
|
||||
"dependencies": [
|
||||
"fastify-plugin",
|
||||
"toad-cache"
|
||||
]
|
||||
},
|
||||
"abort-controller@3.0.0": {
|
||||
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
|
||||
"dependencies": [
|
||||
"event-target-shim"
|
||||
]
|
||||
},
|
||||
"abstract-logging@2.0.1": {
|
||||
"integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA=="
|
||||
},
|
||||
"ajv-formats@3.0.1_ajv@8.17.1": {
|
||||
"integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
|
||||
"dependencies": [
|
||||
"ajv"
|
||||
]
|
||||
},
|
||||
"ajv@8.17.1": {
|
||||
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
||||
"dependencies": [
|
||||
"fast-deep-equal",
|
||||
"fast-uri@3.0.2",
|
||||
"json-schema-traverse",
|
||||
"require-from-string"
|
||||
]
|
||||
},
|
||||
"atomic-sleep@1.0.0": {
|
||||
"integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="
|
||||
},
|
||||
"avvio@9.0.0": {
|
||||
"integrity": "sha512-UbYrOXgE/I+knFG+3kJr9AgC7uNo8DG+FGGODpH9Bj1O1kL/QDjBXnTem9leD3VdQKtaHjV3O85DQ7hHh4IIHw==",
|
||||
"dependencies": [
|
||||
"@fastify/error",
|
||||
"fastq"
|
||||
]
|
||||
},
|
||||
"base64-js@1.5.1": {
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
|
||||
},
|
||||
"buffer@6.0.3": {
|
||||
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
|
||||
"dependencies": [
|
||||
"base64-js",
|
||||
"ieee754"
|
||||
]
|
||||
},
|
||||
"cookie@0.7.2": {
|
||||
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="
|
||||
},
|
||||
"event-target-shim@5.0.1": {
|
||||
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="
|
||||
},
|
||||
"events@3.3.0": {
|
||||
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="
|
||||
},
|
||||
"fast-decode-uri-component@1.0.1": {
|
||||
"integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="
|
||||
},
|
||||
"fast-deep-equal@3.1.3": {
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||
},
|
||||
"fast-json-stringify@6.0.0_ajv@8.17.1": {
|
||||
"integrity": "sha512-FGMKZwniMTgZh7zQp9b6XnBVxUmKVahQLQeRQHqwYmPDqDhcEKZ3BaQsxelFFI5PY7nN71OEeiL47/zUWcYe1A==",
|
||||
"dependencies": [
|
||||
"@fastify/merge-json-schemas",
|
||||
"ajv",
|
||||
"ajv-formats",
|
||||
"fast-deep-equal",
|
||||
"fast-uri@2.4.0",
|
||||
"json-schema-ref-resolver",
|
||||
"rfdc"
|
||||
]
|
||||
},
|
||||
"fast-querystring@1.1.2": {
|
||||
"integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==",
|
||||
"dependencies": [
|
||||
"fast-decode-uri-component"
|
||||
]
|
||||
},
|
||||
"fast-redact@3.5.0": {
|
||||
"integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A=="
|
||||
},
|
||||
"fast-uri@2.4.0": {
|
||||
"integrity": "sha512-ypuAmmMKInk5q7XcepxlnUWDLWv4GFtaJqAzWKqn62IpQ3pejtr5dTVbt3vwqVaMKmkNR55sTT+CqUKIaT21BA=="
|
||||
},
|
||||
"fast-uri@3.0.2": {
|
||||
"integrity": "sha512-GR6f0hD7XXyNJa25Tb9BuIdN0tdr+0BMi6/CJPH3wJO1JjNG3n/VsSw38AwRdKZABm8lGbPfakLRkYzx2V9row=="
|
||||
},
|
||||
"fastify-plugin@5.0.1": {
|
||||
"integrity": "sha512-HCxs+YnRaWzCl+cWRYFnHmeRFyR5GVnJTAaCJQiYzQSDwK9MgJdyAsuL3nh0EWRCYMgQ5MeziymvmAhUHYHDUQ=="
|
||||
},
|
||||
"fastify@5.0.0": {
|
||||
"integrity": "sha512-Qe4dU+zGOzg7vXjw4EvcuyIbNnMwTmcuOhlOrOJsgwzvjEZmsM/IeHulgJk+r46STjdJS/ZJbxO8N70ODXDMEQ==",
|
||||
"dependencies": [
|
||||
"@fastify/ajv-compiler",
|
||||
"@fastify/error",
|
||||
"@fastify/fast-json-stringify-compiler",
|
||||
"abstract-logging",
|
||||
"avvio",
|
||||
"fast-json-stringify",
|
||||
"find-my-way",
|
||||
"light-my-request",
|
||||
"pino",
|
||||
"process-warning",
|
||||
"proxy-addr",
|
||||
"rfdc",
|
||||
"secure-json-parse",
|
||||
"semver",
|
||||
"toad-cache"
|
||||
]
|
||||
},
|
||||
"fastq@1.17.1": {
|
||||
"integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
|
||||
"dependencies": [
|
||||
"reusify"
|
||||
]
|
||||
},
|
||||
"find-my-way@9.1.0": {
|
||||
"integrity": "sha512-Y5jIsuYR4BwWDYYQ2A/RWWE6gD8a0FMgtU+HOq1WKku+Cwdz8M1v8wcAmRXXM1/iqtoqg06v+LjAxMYbCjViMw==",
|
||||
"dependencies": [
|
||||
"fast-deep-equal",
|
||||
"fast-querystring",
|
||||
"safe-regex2"
|
||||
]
|
||||
},
|
||||
"forwarded@0.2.0": {
|
||||
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="
|
||||
},
|
||||
"handlebars@4.7.8": {
|
||||
"integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==",
|
||||
"dependencies": [
|
||||
"minimist",
|
||||
"neo-async",
|
||||
"source-map",
|
||||
"uglify-js",
|
||||
"wordwrap"
|
||||
]
|
||||
},
|
||||
"ieee754@1.2.1": {
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
|
||||
},
|
||||
"ipaddr.js@1.9.1": {
|
||||
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
|
||||
},
|
||||
"json-schema-ref-resolver@1.0.1": {
|
||||
"integrity": "sha512-EJAj1pgHc1hxF6vo2Z3s69fMjO1INq6eGHXZ8Z6wCQeldCuwxGK9Sxf4/cScGn3FZubCVUehfWtcDM/PLteCQw==",
|
||||
"dependencies": [
|
||||
"fast-deep-equal"
|
||||
]
|
||||
},
|
||||
"json-schema-traverse@1.0.0": {
|
||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
|
||||
},
|
||||
"light-my-request@6.1.0": {
|
||||
"integrity": "sha512-+NFuhlOGoEwxeQfJ/pobkVFxcnKyDtiX847hLjuB/IzBxIl3q4VJeFI8uRCgb3AlTWL1lgOr+u5+8QdUcr33ng==",
|
||||
"dependencies": [
|
||||
"cookie",
|
||||
"process-warning",
|
||||
"set-cookie-parser"
|
||||
]
|
||||
},
|
||||
"mime@4.0.4": {
|
||||
"integrity": "sha512-v8yqInVjhXyqP6+Kw4fV3ZzeMRqEW6FotRsKXjRS5VMTNIuXsdRoAvklpoRgSqXm6o9VNH4/C0mgedko9DdLsQ=="
|
||||
},
|
||||
"minimist@1.2.8": {
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="
|
||||
},
|
||||
"neo-async@2.6.2": {
|
||||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
|
||||
},
|
||||
"on-exit-leak-free@2.1.2": {
|
||||
"integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="
|
||||
},
|
||||
"pino-abstract-transport@1.2.0": {
|
||||
"integrity": "sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==",
|
||||
"dependencies": [
|
||||
"readable-stream",
|
||||
"split2"
|
||||
]
|
||||
},
|
||||
"pino-std-serializers@7.0.0": {
|
||||
"integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA=="
|
||||
},
|
||||
"pino@9.4.0": {
|
||||
"integrity": "sha512-nbkQb5+9YPhQRz/BeQmrWpEknAaqjpAqRK8NwJpmrX/JHu7JuZC5G1CeAwJDJfGes4h+YihC6in3Q2nGb+Y09w==",
|
||||
"dependencies": [
|
||||
"atomic-sleep",
|
||||
"fast-redact",
|
||||
"on-exit-leak-free",
|
||||
"pino-abstract-transport",
|
||||
"pino-std-serializers",
|
||||
"process-warning",
|
||||
"quick-format-unescaped",
|
||||
"real-require",
|
||||
"safe-stable-stringify",
|
||||
"sonic-boom",
|
||||
"thread-stream"
|
||||
]
|
||||
},
|
||||
"process-warning@4.0.0": {
|
||||
"integrity": "sha512-/MyYDxttz7DfGMMHiysAsFE4qF+pQYAA8ziO/3NcRVrQ5fSk+Mns4QZA/oRPFzvcqNoVJXQNWNAsdwBXLUkQKw=="
|
||||
},
|
||||
"process@0.11.10": {
|
||||
"integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="
|
||||
},
|
||||
"proxy-addr@2.0.7": {
|
||||
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
|
||||
"dependencies": [
|
||||
"forwarded",
|
||||
"ipaddr.js"
|
||||
]
|
||||
},
|
||||
"quick-format-unescaped@4.0.4": {
|
||||
"integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="
|
||||
},
|
||||
"readable-stream@4.5.2": {
|
||||
"integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==",
|
||||
"dependencies": [
|
||||
"abort-controller",
|
||||
"buffer",
|
||||
"events",
|
||||
"process",
|
||||
"string_decoder"
|
||||
]
|
||||
},
|
||||
"real-require@0.2.0": {
|
||||
"integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="
|
||||
},
|
||||
"require-from-string@2.0.2": {
|
||||
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="
|
||||
},
|
||||
"ret@0.5.0": {
|
||||
"integrity": "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw=="
|
||||
},
|
||||
"reusify@1.0.4": {
|
||||
"integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw=="
|
||||
},
|
||||
"rfdc@1.4.1": {
|
||||
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="
|
||||
},
|
||||
"safe-buffer@5.2.1": {
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
|
||||
},
|
||||
"safe-regex2@4.0.0": {
|
||||
"integrity": "sha512-Hvjfv25jPDVr3U+4LDzBuZPPOymELG3PYcSk5hcevooo1yxxamQL/bHs/GrEPGmMoMEwRrHVGiCA1pXi97B8Ew==",
|
||||
"dependencies": [
|
||||
"ret"
|
||||
]
|
||||
},
|
||||
"safe-stable-stringify@2.5.0": {
|
||||
"integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="
|
||||
},
|
||||
"secure-json-parse@2.7.0": {
|
||||
"integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw=="
|
||||
},
|
||||
"semver@7.6.3": {
|
||||
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="
|
||||
},
|
||||
"set-cookie-parser@2.7.0": {
|
||||
"integrity": "sha512-lXLOiqpkUumhRdFF3k1osNXCy9akgx/dyPZ5p8qAg9seJzXr5ZrlqZuWIMuY6ejOsVLE6flJ5/h3lsn57fQ/PQ=="
|
||||
},
|
||||
"sonic-boom@4.1.0": {
|
||||
"integrity": "sha512-NGipjjRicyJJ03rPiZCJYjwlsuP2d1/5QUviozRXC7S3WdVWNK5e3Ojieb9CCyfhq2UC+3+SRd9nG3I2lPRvUw==",
|
||||
"dependencies": [
|
||||
"atomic-sleep"
|
||||
]
|
||||
},
|
||||
"source-map@0.6.1": {
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
|
||||
},
|
||||
"split2@4.2.0": {
|
||||
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="
|
||||
},
|
||||
"string_decoder@1.3.0": {
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||
"dependencies": [
|
||||
"safe-buffer"
|
||||
]
|
||||
},
|
||||
"thread-stream@3.1.0": {
|
||||
"integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==",
|
||||
"dependencies": [
|
||||
"real-require"
|
||||
]
|
||||
},
|
||||
"toad-cache@3.7.0": {
|
||||
"integrity": "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw=="
|
||||
},
|
||||
"uglify-js@3.19.3": {
|
||||
"integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ=="
|
||||
},
|
||||
"wordwrap@1.0.0": {
|
||||
"integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q=="
|
||||
}
|
||||
},
|
||||
"workspace": {
|
||||
"dependencies": [
|
||||
"jsr:@std/path@^1.0.6",
|
||||
"npm:@fastify/view@^10.0.1",
|
||||
"npm:fastify@5",
|
||||
"npm:handlebars@^4.7.8",
|
||||
"npm:mime@^4.0.4"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import * as path from "@std/path";
|
||||
|
||||
import fastify from 'fastify';
|
||||
import fastifyView from "@fastify/view"
|
||||
import handlebars from "handlebars";
|
||||
import mime from 'mime/lite';
|
||||
|
||||
const server = fastify();
|
||||
|
||||
const __dirname = new URL('.', import.meta.url).pathname;
|
||||
|
||||
server.register(fastifyView, { engine: { handlebars }, root: path.resolve(__dirname, 'views') });
|
||||
|
||||
enum Headers {
|
||||
FormatHeader = 'x-format',
|
||||
CodeHeader = 'x-code',
|
||||
OriginalURI = 'x-original-uri',
|
||||
ContentType = 'content-type',
|
||||
Namespace = "x-namespace",
|
||||
IngressName = "x-ingress-name",
|
||||
ServiceName = "x-service-name",
|
||||
ServicePort = "x-service-port",
|
||||
RequestId = "x-request-id",
|
||||
}
|
||||
|
||||
const CodeMessageMap: Record<number, string> = {
|
||||
403: '禁止访问此内容: 抱歉,由于安全问题您被禁止访问此内容。如果您认为这是个错误,请联系我们获取帮助。',
|
||||
404: '内容未找到: 您正在寻找的内容可能已被删除、名称已更改或暂时不可用。',
|
||||
500: '服务器错误: 请稍后再试,或者返回主页。如果问题持续存在,请联系我们,我们会尽快修复。'
|
||||
};
|
||||
|
||||
// 默认处理函数
|
||||
server.all('/', (request, reply) => {
|
||||
const headers = request.headers;
|
||||
|
||||
// 获取OriginalURI的文件后缀
|
||||
const originalURI: string = headers[Headers.OriginalURI] || '';
|
||||
const urlExtWithDot = path.extname(originalURI);
|
||||
|
||||
// 获取FormatHeader对应的文件后缀
|
||||
const format: string = headers[Headers.FormatHeader] || '';
|
||||
// 排除format存在多个的情况 目前只支持一个
|
||||
const firstFormat = format.split(',')[0];
|
||||
const formatExt = mime.getExtension(firstFormat);
|
||||
const formatExtWithDot = formatExt ? `.${formatExt}` : '';
|
||||
|
||||
const ext = formatExtWithDot || urlExtWithDot || '.html';
|
||||
|
||||
// 获取header中的其他内容
|
||||
const requestId: string = headers[Headers.RequestId] || '';
|
||||
|
||||
// 获取CodeHeader对应的状态码
|
||||
const code: number = Number(headers[Headers.CodeHeader] || 404);
|
||||
|
||||
// 获取兼容code 比如没有403的code,就使用404 没有502的code,就使用500 取第一位相同的但是最小的code
|
||||
let compatibleCode = code;
|
||||
|
||||
const isCodeExist = Reflect.has(CodeMessageMap, code);
|
||||
|
||||
if (!isCodeExist) {
|
||||
const codeList = Object.keys(CodeMessageMap).map(Number).sort((a, b) => a - b);
|
||||
compatibleCode = Number(codeList.find((c) => String(c).startsWith(String(code).charAt(0))) || 404)
|
||||
}
|
||||
|
||||
const message = CodeMessageMap[compatibleCode];
|
||||
|
||||
const data = { code, requestId, message };
|
||||
|
||||
console.log(`[${new Date().toISOString()}] code:${code} url:'${originalURI}' ext:'${ext}' requestId:'${requestId}'`);
|
||||
|
||||
// 根据后缀渲染不同的内容
|
||||
if (ext === '.html' || ext === '.htm') {
|
||||
const [title, content] = message.split(':');
|
||||
|
||||
const htmlContent = content.trim().replace(/。(?=.+)/, '。<br>');
|
||||
|
||||
reply.code(code).view('error.hbs', { ...data, title, content: htmlContent });
|
||||
} else if (ext === '.json') {
|
||||
reply.code(code).send(data);
|
||||
} else {
|
||||
reply.code(code).send(message);
|
||||
}
|
||||
});
|
||||
|
||||
// 提供健康检查接口
|
||||
server.get('/healthz', (_request, reply) => reply.send('OK'));
|
||||
|
||||
server.listen({ port: 8080, host: '0.0.0.0'}, (_err, address) => {
|
||||
if (_err) throw _err
|
||||
|
||||
console.log(`Server listening at ${address}`);
|
||||
});
|
||||
+246
@@ -0,0 +1,246 @@
|
||||
<!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>{{ code }} {{ title }}</title>
|
||||
<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: 188px;
|
||||
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 {
|
||||
margin-top: 35px
|
||||
}
|
||||
|
||||
.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
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 480px) {
|
||||
.error .error-code {
|
||||
height: 122px;
|
||||
line-height: 122px
|
||||
}
|
||||
|
||||
.error .error-code h1 {
|
||||
font-size: 122px
|
||||
}
|
||||
|
||||
.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>{{ code }}</h1>
|
||||
</div>
|
||||
<h2>{{ title }}</h2>
|
||||
<p>{{{ content }}}</p>
|
||||
<p>{{ requestId }}</p>
|
||||
<a href="/">回到首页</a>
|
||||
<a href="mailto:admin@dev.cm">联系我们</a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
在新议题中引用
屏蔽一个用户