docker化项目
> 还删除无关的public/vite.svg文件
Change-Id: If26b139f8a984aec1518c0233b52d2a6a25a5bb7
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..4ab90f9
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,11 @@
+# 排除构建工具生成的文件
+node_modules/ # Node.js 依赖
+target/ # Maven/Java 构建输出
+dist/ # 前端构建产物
+build/ # 通用构建目录
+
+# 排除临时文件
+*.log # 日志文件
+*.swp # Vim 交换文件
+.idea/ # IntelliJ IDEA 配置
+.vscode/ # VS Code 配置
\ No newline at end of file
diff --git a/50x.html b/50x.html
new file mode 100644
index 0000000..0d5573e
--- /dev/null
+++ b/50x.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<html lang="en">
+ <head>
+ <meta charset="UTF-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <title>创驿~ 链接用户和创作者的驿站</title>
+ </head>
+ <body>
+ 服务器崩溃了 哭/(ㄒoㄒ)/~~
+ </body>
+</html>
diff --git a/Dokerfile b/Dokerfile
new file mode 100644
index 0000000..c7ea0a4
--- /dev/null
+++ b/Dokerfile
@@ -0,0 +1,22 @@
+# 构建阶段
+FROM FROM docker.1ms.run/node:23-alpine AS builder
+WORKDIR /src
+
+# 安装 pnpm
+RUN npm install -g pnpm
+
+# 复制依赖文件并安装
+COPY package.json pnpm-lock.yaml ./
+RUN pnpm install --frozen-lockfile
+
+# 复制源码并构建
+COPY . .
+RUN pnpm build
+
+# 生产环境
+FROM docker.1ms.run/nginx:1.25-alpine
+COPY --from=builder /dist /usr/share/nginx/html
+COPY 50x.html /usr/share/nginx/html
+COPY nginx.conf /etc/nginx/conf.d/default.conf
+EXPOSE 80
+CMD ["nginx", "-g", "daemon off;"]
\ No newline at end of file
diff --git a/nginx.conf b/nginx.conf
new file mode 100644
index 0000000..51c2b08
--- /dev/null
+++ b/nginx.conf
@@ -0,0 +1,52 @@
+server {
+ listen 80;
+ server_name team1.10813352.xyz;
+
+ # 前端静态文件配置
+ root /usr/share/nginx/html;
+ index index.html;
+
+ # 解决 React Router 单页应用路由问题
+ location / {
+ try_files $uri $uri/ /index.html;
+
+ # 缓存策略
+ expires 7d; # 静态资源缓存7天
+ add_header Cache-Control "public";
+
+ # Gzip 压缩
+ gzip on;
+ gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
+ gzip_min_length 1024;
+ gzip_comp_level 6;
+ }
+
+ # API 请求代理到后端
+ location /api/ {
+ proxy_pass http://group1-backend:8080/; # 后端服务地址
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+
+ # 长连接支持
+ proxy_http_version 1.1;
+ proxy_set_header Connection "";
+
+ # 超时设置
+ proxy_connect_timeout 60s;
+ proxy_send_timeout 60s;
+ proxy_read_timeout 60s;
+ }
+
+ # 错误页面配置
+ error_page 404 /index.html;
+ error_page 500 502 503 504 /50x.html;
+
+ # 安全头设置
+ add_header X-Frame-Options "SAMEORIGIN" always;
+ add_header X-XSS-Protection "1; mode=block" always;
+ add_header X-Content-Type-Options "nosniff" always;
+ add_header Referrer-Policy "no-referrer-when-downgrade" always;
+ add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:" always;
+}
\ No newline at end of file
diff --git a/package.json b/package.json
index 6591ac1..6f5a635 100644
--- a/package.json
+++ b/package.json
@@ -14,6 +14,7 @@
"@ant-design/v5-patch-for-react-19": "^1.0.3",
"@reduxjs/toolkit": "^2.8.2",
"antd": "^5.25.2",
+ "axios": "^1.9.0",
"less": "^4.3.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index d818e04..e3fae56 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -20,6 +20,9 @@
antd:
specifier: ^5.25.2
version: 5.25.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ axios:
+ specifier: ^1.9.0
+ version: 1.9.0
less:
specifier: ^4.3.0
version: 4.3.0
@@ -781,6 +784,12 @@
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
engines: {node: '>=12'}
+ asynckit@0.4.0:
+ resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
+
+ axios@1.9.0:
+ resolution: {integrity: sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==}
+
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
@@ -803,6 +812,10 @@
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
engines: {node: '>=8'}
+ call-bind-apply-helpers@1.0.2:
+ resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
+ engines: {node: '>= 0.4'}
+
callsites@3.1.0:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'}
@@ -832,6 +845,10 @@
color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+ combined-stream@1.0.8:
+ resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
+ engines: {node: '>= 0.8'}
+
compute-scroll-into-view@3.1.1:
resolution: {integrity: sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==}
@@ -877,6 +894,14 @@
deep-is@0.1.4:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
+ delayed-stream@1.0.0:
+ resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
+ engines: {node: '>=0.4.0'}
+
+ dunder-proto@1.0.1:
+ resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
+ engines: {node: '>= 0.4'}
+
electron-to-chromium@1.5.155:
resolution: {integrity: sha512-ps5KcGGmwL8VaeJlvlDlu4fORQpv3+GIcF5I3f9tUKUlJ/wsysh6HU8P5L1XWRYeXfA0oJd4PyM8ds8zTFf6Ng==}
@@ -884,9 +909,25 @@
resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==}
hasBin: true
+ es-define-property@1.0.1:
+ resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
+ engines: {node: '>= 0.4'}
+
+ es-errors@1.3.0:
+ resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
+ engines: {node: '>= 0.4'}
+
es-module-lexer@1.7.0:
resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
+ es-object-atoms@1.1.1:
+ resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
+ engines: {node: '>= 0.4'}
+
+ es-set-tostringtag@2.1.0:
+ resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
+ engines: {node: '>= 0.4'}
+
esbuild@0.25.4:
resolution: {integrity: sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==}
engines: {node: '>=18'}
@@ -1003,15 +1044,39 @@
flatted@3.3.3:
resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
+ follow-redirects@1.15.9:
+ resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==}
+ engines: {node: '>=4.0'}
+ peerDependencies:
+ debug: '*'
+ peerDependenciesMeta:
+ debug:
+ optional: true
+
+ form-data@4.0.2:
+ resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==}
+ engines: {node: '>= 6'}
+
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
+ function-bind@1.1.2:
+ resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+
gensync@1.0.0-beta.2:
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
engines: {node: '>=6.9.0'}
+ get-intrinsic@1.3.0:
+ resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
+ engines: {node: '>= 0.4'}
+
+ get-proto@1.0.1:
+ resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
+ engines: {node: '>= 0.4'}
+
glob-parent@5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
@@ -1032,6 +1097,10 @@
resolution: {integrity: sha512-aibexHNbb/jiUSObBgpHLj+sIuUmJnYcgXBlrfsiDZ9rt4aF2TFRbyLgZ2iFQuVZ1K5Mx3FVkbKRSgKrbK3K2g==}
engines: {node: '>=18'}
+ gopd@1.2.0:
+ resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
+ engines: {node: '>= 0.4'}
+
graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
@@ -1042,6 +1111,18 @@
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
+ has-symbols@1.1.0:
+ resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
+ engines: {node: '>= 0.4'}
+
+ has-tostringtag@1.0.2:
+ resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
+ engines: {node: '>= 0.4'}
+
+ hasown@2.0.2:
+ resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
+ engines: {node: '>= 0.4'}
+
iconv-lite@0.6.3:
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
engines: {node: '>=0.10.0'}
@@ -1149,6 +1230,10 @@
resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==}
engines: {node: '>=6'}
+ math-intrinsics@1.1.0:
+ resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
+ engines: {node: '>= 0.4'}
+
merge2@1.4.1:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'}
@@ -1157,6 +1242,14 @@
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
engines: {node: '>=8.6'}
+ mime-db@1.52.0:
+ resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
+ engines: {node: '>= 0.6'}
+
+ mime-types@2.1.35:
+ resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
+ engines: {node: '>= 0.6'}
+
mime@1.6.0:
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
engines: {node: '>=4'}
@@ -1246,6 +1339,9 @@
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'}
+ proxy-from-env@1.1.0:
+ resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
+
prr@1.0.1:
resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==}
@@ -2532,6 +2628,16 @@
assertion-error@2.0.1: {}
+ asynckit@0.4.0: {}
+
+ axios@1.9.0:
+ dependencies:
+ follow-redirects: 1.15.9
+ form-data: 4.0.2
+ proxy-from-env: 1.1.0
+ transitivePeerDependencies:
+ - debug
+
balanced-match@1.0.2: {}
brace-expansion@1.1.11:
@@ -2556,6 +2662,11 @@
cac@6.7.14: {}
+ call-bind-apply-helpers@1.0.2:
+ dependencies:
+ es-errors: 1.3.0
+ function-bind: 1.1.2
+
callsites@3.1.0: {}
caniuse-lite@1.0.30001718: {}
@@ -2583,6 +2694,10 @@
color-name@1.1.4: {}
+ combined-stream@1.0.8:
+ dependencies:
+ delayed-stream: 1.0.0
+
compute-scroll-into-view@3.1.1: {}
concat-map@0.0.1: {}
@@ -2617,6 +2732,14 @@
deep-is@0.1.4: {}
+ delayed-stream@1.0.0: {}
+
+ dunder-proto@1.0.1:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ es-errors: 1.3.0
+ gopd: 1.2.0
+
electron-to-chromium@1.5.155: {}
errno@0.1.8:
@@ -2624,8 +2747,23 @@
prr: 1.0.1
optional: true
+ es-define-property@1.0.1: {}
+
+ es-errors@1.3.0: {}
+
es-module-lexer@1.7.0: {}
+ es-object-atoms@1.1.1:
+ dependencies:
+ es-errors: 1.3.0
+
+ es-set-tostringtag@2.1.0:
+ dependencies:
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ has-tostringtag: 1.0.2
+ hasown: 2.0.2
+
esbuild@0.25.4:
optionalDependencies:
'@esbuild/aix-ppc64': 0.25.4
@@ -2781,11 +2919,40 @@
flatted@3.3.3: {}
+ follow-redirects@1.15.9: {}
+
+ form-data@4.0.2:
+ dependencies:
+ asynckit: 0.4.0
+ combined-stream: 1.0.8
+ es-set-tostringtag: 2.1.0
+ mime-types: 2.1.35
+
fsevents@2.3.3:
optional: true
+ function-bind@1.1.2: {}
+
gensync@1.0.0-beta.2: {}
+ get-intrinsic@1.3.0:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ es-define-property: 1.0.1
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.1
+ function-bind: 1.1.2
+ get-proto: 1.0.1
+ gopd: 1.2.0
+ has-symbols: 1.1.0
+ hasown: 2.0.2
+ math-intrinsics: 1.1.0
+
+ get-proto@1.0.1:
+ dependencies:
+ dunder-proto: 1.0.1
+ es-object-atoms: 1.1.1
+
glob-parent@5.1.2:
dependencies:
is-glob: 4.0.3
@@ -2800,6 +2967,8 @@
globals@16.1.0: {}
+ gopd@1.2.0: {}
+
graceful-fs@4.2.11:
optional: true
@@ -2807,6 +2976,16 @@
has-flag@4.0.0: {}
+ has-symbols@1.1.0: {}
+
+ has-tostringtag@1.0.2:
+ dependencies:
+ has-symbols: 1.1.0
+
+ hasown@2.0.2:
+ dependencies:
+ function-bind: 1.1.2
+
iconv-lite@0.6.3:
dependencies:
safer-buffer: 2.1.2
@@ -2905,6 +3084,8 @@
semver: 5.7.2
optional: true
+ math-intrinsics@1.1.0: {}
+
merge2@1.4.1: {}
micromatch@4.0.8:
@@ -2912,6 +3093,12 @@
braces: 3.0.3
picomatch: 2.3.1
+ mime-db@1.52.0: {}
+
+ mime-types@2.1.35:
+ dependencies:
+ mime-db: 1.52.0
+
mime@1.6.0:
optional: true
@@ -2985,6 +3172,8 @@
prelude-ls@1.2.1: {}
+ proxy-from-env@1.1.0: {}
+
prr@1.0.1:
optional: true
diff --git a/public/vite.svg b/public/vite.svg
deleted file mode 100644
index e7b8dfb..0000000
--- a/public/vite.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
\ No newline at end of file
diff --git a/src/api/client.ts b/src/api/client.ts
deleted file mode 100644
index 623ee78..0000000
--- a/src/api/client.ts
+++ /dev/null
@@ -1,148 +0,0 @@
-type RequestOptions = {
- method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
- headers?: Record<string, string>;
- body?: unknown;
- queryParams?: Record<string, string | number>;
-};
-
-type Interceptor = {
- request?: (config: RequestInit) => RequestInit;
- requestError?: (error: unknown) => Promise<never>; // 错误拦截器应始终抛出
- response?: (response: Response) => Response | Promise<Response>;
- // 错误拦截器必须抛出或返回与请求相同类型的结果
- responseError?: <T>(error: unknown) => Promise<T>;
-};
-
-export class HttpClient {
- private static instance: HttpClient;
- private baseUrl: string;
- private interceptors: Interceptor[] = [];
-
- private constructor(baseUrl: string) {
- this.baseUrl = baseUrl;
- }
-
- public static getInstance(baseUrl: string): HttpClient {
- if (!HttpClient.instance) {
- HttpClient.instance = new HttpClient(baseUrl);
- }
- return HttpClient.instance;
- }
-
- // 添加拦截器
- addInterceptor(interceptor: Interceptor) {
- this.interceptors.push(interceptor);
- return this; // 支持链式调用
- }
-
- async request<T>(endpoint: string, options: RequestOptions = {}): Promise<T> {
- const {
- method = 'GET',
- headers = {},
- body,
- queryParams = {}
- } = options;
-
- const url = new URL(`${this.baseUrl}${endpoint}`);
- Object.entries(queryParams).forEach(([key, value]) => {
- url.searchParams.append(key, String(value));
- });
-
- let config: RequestInit = {
- method,
- headers: {
- 'Content-Type': 'application/json',
- ...headers
- }
- };
-
- if (body) {
- config.body = JSON.stringify(body);
- }
-
- // 请求拦截器处理
- for (const interceptor of this.interceptors) {
- if (interceptor.request) {
- config = interceptor.request(config);
- }
- }
-
- try {
- let response = await fetch(url.toString(), config);
-
- // 响应拦截器处理
- for (const interceptor of this.interceptors) {
- if (interceptor.response) {
- response = await interceptor.response(response);
- }
- }
-
- if (!response.ok) {
- const errorData = await response.json();
- throw new Error(`HTTP error! Status: ${response.status}, Message: ${errorData.message || 'Unknown error'}`);
- }
-
- return await response.json();
- } catch (error) {
- // 改进的错误拦截器处理
- let lastError = error;
-
- for (const interceptor of this.interceptors) {
- if (interceptor.responseError) {
- try {
- // 错误拦截器必须返回Promise<T>或抛出
- return await interceptor.responseError<T>(lastError);
- } catch (e) {
- // 保存最新错误并继续下一个拦截器
- lastError = e;
- console.error('Interceptor error:', e);
- }
- }
- }
-
- // 如果所有拦截器都未能处理错误,则抛出最后一个错误
- throw lastError;
- }
- }
- get<T>(endpoint: string, queryParams?: Record<string, string | number>) {
- return this.request<T>(endpoint, { queryParams });
- }
-
- post<T>(endpoint: string, body?: unknown, headers?: Record<string, string>) {
- return this.request<T>(endpoint, { method: 'POST', body, headers });
- }
-
- put<T>(endpoint: string, body?: unknown, headers?: Record<string, string>) {
- return this.request<T>(endpoint, { method: 'PUT', body, headers });
- }
-
- delete<T>(endpoint: string, headers?: Record<string, string>) {
- return this.request<T>(endpoint, { method: 'DELETE', headers });
- }
-}
-
-const client = HttpClient.getInstance('http://localhost:8080/');
-// 添加Token注入拦截器
-client.addInterceptor({
- request: (config) => {
- const token = localStorage.getItem('token'); // 从存储中获取token
- if (token) {
- config.headers = {
- ...config.headers,
- 'Authorization': `Bearer ${token}`
- };
- }
- return config;
- },
- responseError: async (error) => {
- if (error instanceof Error && error.message.includes('401')) {
- console.log('Token expired, redirecting to login...');
- // 示例:重定向到登录页
- window.location.href = '/login';
- // 必须返回Promise<never>或抛出
- return new Promise(() => {}); // 永远不解决的Promise
- }
- throw error;
- }
-});
-export default client as HttpClient;
\ No newline at end of file
diff --git a/src/api/interceptors.ts b/src/api/interceptors.ts
new file mode 100644
index 0000000..3b31740
--- /dev/null
+++ b/src/api/interceptors.ts
@@ -0,0 +1,20 @@
+import axios from "axios";
+import { useNavigate } from "react-router";
+
+// 为所有auth外请求添加token头
+axios.interceptors.request.use((config) => {
+ const requestUrl = config.url;
+ if (requestUrl?.includes("/auth/")) {
+ config.url = requestUrl.replace("/auth/","/");
+ } else {
+ const token = localStorage.getItem('token');
+ if (!token) {
+ const navigate = useNavigate();
+ navigate("/login")
+ }
+ config.headers['Authorization'] = `Bearer ${token}`;
+ }
+ return config;
+}, (error) => {
+ return error;
+} );
\ No newline at end of file
diff --git a/src/feature/auth/Register.tsx b/src/feature/auth/Register.tsx
index 5b90c59..197d265 100644
--- a/src/feature/auth/Register.tsx
+++ b/src/feature/auth/Register.tsx
@@ -15,9 +15,20 @@
function Register() {
const [countdown, setCountdown] = useState(0);
const [form] = Form.useForm<FormValues>();
- const [emailStatues] = useState(false);
+ const emailValue = Form.useWatch('email', form)
+
+ //
+ function isValidEmail(email: string): boolean {
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+
+ return emailRegex.test(email);
+ }
const sendVerificationCode = () => {
+ if (!isValidEmail(emailValue)) {
+ form.validateFields(['email'])
+ return;
+ }
setCountdown(60)
}
@@ -76,8 +87,9 @@
style={{ flex: 1 }}
/>
<Button
- disabled={countdown > 0 || !emailStatues}
+ disabled={countdown > 0}
onClick={sendVerificationCode}
+ color="primary"
style={{ width: 120 }}
>
{countdown > 0 ? `${countdown}s后重试` : '发送验证码'}
diff --git a/src/routes.ts b/src/routes.ts
index 1356b1e..e0dd020 100644
--- a/src/routes.ts
+++ b/src/routes.ts
@@ -19,7 +19,7 @@
{ path: "register", Component: Register },
{ path: "forget", Component: Forget },
],
- }
+ },
]
}]);
\ No newline at end of file