diff --git a/.cursor/rules/frontend-design-spec.mdc b/.cursor/rules/frontend-design-spec.mdc index bbe5f88..e2614eb 100644 --- a/.cursor/rules/frontend-design-spec.mdc +++ b/.cursor/rules/frontend-design-spec.mdc @@ -33,7 +33,7 @@ alwaysApply: true - HandsontablePro 固定列序:操作(可选,内容自适应宽)→ 序号(50px)→ 复选框(可选,60px)→ 数据;`stretch-columns` 只拉宽数据列,勿再传 `action-column-position` / `action-column-width` - 列表页高度:`flex: 1; min-height: 0`,`.list-wrap` 勿写死 min-height - 分页 10/30/50/100,默认 50 -- 新页面 path 与 API 前缀冲突时须配置 nginx SPA 回退(见规范 §十五) +- 生产前后端分域名 + Traefik TLS;前端容器仅静态 SPA(见规范 §十五) ### 权限 - `hasPermission('Module.Resource.Action')` 控制按钮/路由 diff --git a/.dockerignore b/.dockerignore index 4caeb06..5674289 100644 --- a/.dockerignore +++ b/.dockerignore @@ -26,7 +26,5 @@ xhs-agent-desktop .env* !.env.production -# 本地/旧部署用的 nginx,k3s 用 nginx-k3s.conf -nginx-default.conf -nginx-docker.conf +# 本地简易镜像另用 Dockerfile;k3s 构建用 Dockerfile.k3s Dockerfile diff --git a/Dockerfile b/Dockerfile index f7f517b..0112129 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,9 @@ -FROM nginx:1.27-alpine - -# 静态资源 -COPY dist/ /usr/share/nginx/html/ - -# Docker 内只用 HTTP:80,SSL 在宿主机 nginx 或网关层处理 -COPY nginx-docker.conf /etc/nginx/conf.d/default.conf - +# 本地/简易 Docker:仅静态 SPA。HTTPS 由外层网关(如 Traefik)终结。 +# 构建前先执行:pnpm build +FROM docker.m.daocloud.io/library/node:20-alpine +WORKDIR /app +RUN npm config set registry https://registry.npmmirror.com \ + && npm install -g serve@14.2.4 +COPY dist/ ./dist/ EXPOSE 80 +CMD ["serve", "-s", "dist", "-l", "80"] diff --git a/Dockerfile.k3s b/Dockerfile.k3s index baaa055..88fa85e 100644 --- a/Dockerfile.k3s +++ b/Dockerfile.k3s @@ -1,4 +1,5 @@ -# k3s / Kaniko:静态 SPA(方案 A)。API 由浏览器按站点域名直连 +# k3s / Kaniko:静态 SPA(方案 A)。TLS 由 Traefik(websecure) + cert-manager 终结。 +# API 由浏览器按站点域名直连: # test.ideship.com → api.test.ideship.com;其余 → api.ideship.com # FRONT_BUILD_SCRIPT: # prod 默认 build(.env.production → api.ideship.com) @@ -16,7 +17,11 @@ RUN pnpm config set registry https://registry.npmmirror.com \ COPY . . RUN pnpm run ${FRONT_BUILD_SCRIPT} -FROM docker.m.daocloud.io/library/nginx:1.27-alpine -COPY --from=build /app/dist /usr/share/nginx/html/ -COPY nginx-k3s.conf /etc/nginx/conf.d/default.conf +# 仅静态托管 + SPA fallback;不再内置 nginx(网关层 Traefik 负责 HTTPS) +FROM docker.m.daocloud.io/library/node:20-alpine +WORKDIR /app +RUN npm config set registry https://registry.npmmirror.com \ + && npm install -g serve@14.2.4 +COPY --from=build /app/dist ./dist EXPOSE 80 +CMD ["serve", "-s", "dist", "-l", "80"] diff --git a/README.md b/README.md index ee7b479..5a4a972 100644 --- a/README.md +++ b/README.md @@ -98,17 +98,18 @@ npm run lint 1. 安装依赖 `pnpm i` 2. 构建 `pnpm build`(生成 `dist/`) -3. **宿主机 nginx(推荐)**:将 `dist/` 放到 `/usr/share/nginx/html`,使用 `nginx-default.conf`(含 SSL,证书在 `/etc/nginx/ssl/`) -4. **Docker 方式(7962 端口)**:使用 `nginx-docker.conf`(仅 HTTP,不含 SSL) +3. **k3s(推荐)**:`Dockerfile.k3s` 多阶段构建,容器内用 `serve` 提供静态 SPA(`:80`);**HTTPS 由 Traefik `websecure` + cert-manager(Let's Encrypt)终结**,经 Ingress(tls) → Service,不在前端容器内挂证书。 +4. **本地简易 Docker**:先 `pnpm build`,再 `docker build -t web1 .`(`Dockerfile` 拷贝已有 `dist/`) ```bash +# 本地试跑静态容器(HTTP:7962 → 容器 80) docker build -t web1 . docker rm -f web docker run -d -p 7962:80 --name web web1 -docker ps | grep web # 确认 Up 状态 +docker ps | grep web ``` -> 勿把 `nginx-default.conf`(含 SSL)直接打进 Docker,容器内没有 `/etc/nginx/ssl/` 会导致启动失败。 +> 生产勿在前端镜像内配置 SSL;证书由集群 cert-manager 写入 Secret,Traefik Ingress 引用即可。 ## 贡献指南 diff --git a/docs/H5客户中心设计方案.md b/docs/H5客户中心设计方案.md index 26c8762..b804afd 100644 --- a/docs/H5客户中心设计方案.md +++ b/docs/H5客户中心设计方案.md @@ -547,7 +547,7 @@ src/views/h5/customer-center/ - [x] 独立 H5 账号密码登录(`/h5/login`,不跳 PC 登录页) - [x] H5 微信扫码登录:`GET /wechat/mp/login-qrcode` + 长按识别 + 轮询登录 - [x] 独立 H5 充值(`/h5/recharge`,不跳 PC 充值页) -- [x] 中英文与 nginx `/h5` SPA 回退 +- [x] 中英文与 `/h5` SPA 回退(`serve -s` / Traefik) - [ ] 键盘适配与运单上下文在顾问页的完整预填联调 - [x] 手机完整下单(二期):`/h5/place-order` 三步精简下单 + 查价「去下单」草稿深链 diff --git a/docs/前端页面设计规范.md b/docs/前端页面设计规范.md index 0d4108c..0c6752e 100644 --- a/docs/前端页面设计规范.md +++ b/docs/前端页面设计规范.md @@ -555,33 +555,25 @@ const { t } = useI18n(); - [ ] 分页组件置于**列表上方工具栏右侧**(列表右上角),**§7.3**;分页 10/30/50/100,默认 50 - [ ] 操作按钮按权限使用 `hasPermission` - [ ] 路由 meta 配置 `permission` -- [ ] **新增前端页面路径**若落在 `/order/`、`/channel/`、`/system/` 等与 Admin API 同源前缀下,须在 **`nginx-default.conf` / `nginx-docker.conf`** 增加 `^~` 的 `try_files /index.html =200` 规则(**§十五**),避免刷新/直链 404 +- [ ] **部署**:前端与 Admin API **分域名**(如 `ideship.com` / `api.ideship.com`);TLS 走 Traefik + cert-manager(**§十五**) - [ ] 文案通过 i18n,不写死中文 - [ ] 命名符合本规范(文件、路由、变量、i18n key) --- -## 十五、部署与 nginx(SPA 路由) +## 十五、部署与网关(Traefik / SPA) -生产环境 nginx 将 `/order/`、`/channel/`、`/system/` 等前缀**同时**用于 Admin API 反代。浏览器刷新或直接打开前端 URL(如 `/order/batchJobs`)时,若未单独配置 SPA 回退,请求会被转发到后端 API 并返回 **404**。 +生产已弃用站内 nginx。静态资源由前端容器(`serve -s`)提供,**HTTPS** 由 k3s Traefik `websecure` + cert-manager(Let's Encrypt)签发;流量路径: + +`浏览器 → :443 Traefik(websecure) → Ingress(tls) → Service → 前端 Pod(:80)` **约定:** -1. 官网 **`/`** 为 Vue 路由(`src/views/site`);静态资源在 **`/site/`**;**`/9610.html`** 为 `public/9610.html`。客户登录为 **`/login`**,管理端为 **`/admin`**。勿再把 `/login` 重定向到 `/`。 -2. 每新增一个**前端页面路由**,若其 path 与 API 前缀冲突,必须在 `nginx-default.conf` 与 `nginx-docker.conf` 中、**API 正则代理块之前**增加更高优先级的 `^~` 规则,例如: - -```nginx -# 订单前端路由(^~ 须比下方 /order/ API 正则代理更优先) -location ^~ /order/batchJobs { - try_files /index.html =200; -} -``` - -3. 修改后执行 `nginx -t && nginx -s reload`(或重建前端 Docker 镜像使 `nginx-docker.conf` 生效)。 - -4. 本地 Vite 开发不受此限制;**仅在部署环境**需遵守本节前缀规则。`pnpm dev` 时 `/` 即为官网。 - -当前订单模块已配置的前端路径示例:`/order/priceCheck`、`/order/placeOrder`、`/order/myOrders`、`/order/batchJobs`。 +1. **前后端分域名**(方案 A):站点如 `ideship.com` / `test.ideship.com`,API 如 `api.ideship.com` / `api.test.ideship.com`。浏览器直连 API,前端容器不做 Admin 反代,也无 `/__cos__` 反代。 +2. SPA 回退由 `serve -s`(或等价静态服务器)处理;刷新 `/order/batchJobs` 等路径应回 `index.html`,不依赖 nginx `try_files`。 +3. 官网 **`/`** 为 Vue 路由(`src/views/site`);静态资源在 **`/site/`**;**`/9610.html`** 为 `public/9610.html`。客户登录 **`/login`**,管理端 **`/admin`**。 +4. 面单/附件预览:开发环境可用 Vite `/__cos__`;**生产**经鉴权 `file-storage/stream` → blob,勿直开 `https://cos.*`(自定义域证书常不匹配)。 +5. 本地 `pnpm dev` 不受 Ingress 限制;`/` 即为官网。 --- diff --git a/docs/在线客服模块设计方案.md b/docs/在线客服模块设计方案.md index d0e2bc9..163f162 100644 --- a/docs/在线客服模块设计方案.md +++ b/docs/在线客服模块设计方案.md @@ -181,7 +181,7 @@ | `OmsFileUploadHelper` | 聊天图片上传(新 folder:`cs`) | | `NoticeService` | 可选:新消息站内信 | | 数据权限 | 坐席列表/认领范围按客服数据权限过滤 | -| nginx | P1 必须补 WebSocket Upgrade | +| Traefik Ingress | P1 须允许 WebSocket Upgrade(SignalR) | ### 4.3 技术选型 @@ -197,16 +197,8 @@ ### 4.4 服务端部署注意 1. `AddSignalR().AddStackExchangeRedis(...)` 或与现有 FreeRedis 兼容的 backplane 方案(实现阶段选定一种,避免双 Redis 客户端混乱)。 -2. nginx 为 Hub 增加独立 location(建议 `/hubs/`),配置: - -```nginx -proxy_http_version 1.1; -proxy_set_header Upgrade $http_upgrade; -proxy_set_header Connection "upgrade"; -proxy_read_timeout 3600s; -``` - -3. 前端 Docker / `nginx-docker.conf`、`nginx-default.conf` 同步修改;新坐席路由若与 API 前缀冲突,按规范 §十五做 SPA `try_files`。 +2. Traefik / Ingress 对 Hub 路径(建议 `/hubs/` 或 SignalR 约定路径)开启 WebSocket;超时按长连接调大。前后端分域名时,浏览器直连 **api.***,前端静态容器无需反代 Hub。 +3. SPA 回退由前端容器 `serve -s` 处理;见规范 §十五。 --- @@ -610,7 +602,7 @@ src/composables/useCsHub.ts - [ ] 坐席右侧能看到客户与最近订单摘要 - [ ] 结束会话后客户再进线为新会话 - [ ] 双实例部署下消息不丢(Redis backplane) -- [ ] nginx WebSocket 升级验证通过 +- [ ] Traefik / Ingress WebSocket 升级验证通过 - [ ] 权限码控制认领 / 回复 / 结束;客户无法调坐席接口 ### 12.3 非功能 @@ -630,7 +622,7 @@ src/composables/useCsHub.ts | 2 | DDL:`t_cs_conversation` / `t_cs_message` + 仓储 | P1 | | 3 | HTTP 会话/消息/上传 API + 权限码种子 | P1 | | 4 | 坐席 Inbox 静态联调(可先轮询) | P1 | -| 5 | SignalR Hub + 前端 `useCsHub` + nginx | P1 | +| 5 | SignalR Hub + 前端 `useCsHub` + Traefik WS | P1 | | 6 | 分配逻辑 + 在线心跳 + 微信降级 | P1 | | 7 | AI 转人工 + 上下文侧栏 | P1 | | 8 | 未读角标 +(可选)站内信 | P1 | @@ -646,7 +638,7 @@ src/composables/useCsHub.ts | 风险 | 对策 | | --- | --- | | 坐席不用系统、仍只回微信 | P0 微信永远保留;P1 强调「转人工带单号」提升系统内处理价值 | -| SignalR 经 nginx 失败 | 独立 `/hubs/` location + 验收清单;可临时 `SignalREnabled=false` HTTP 轮询 | +| SignalR 经网关失败 | Ingress 允许 Upgrade + 验收清单;可临时 `SignalREnabled=false` HTTP 轮询 | | 多实例串消息 | 强制 Redis backplane,禁止单机假设 | | 客户把客服当即时 IM 闲聊 | 入口文案强调「物流咨询」;系统消息提示工作时间(可配置) | | AI 灌入消息过长 | 限制条数与单条长度;仅转人工时写入 | diff --git a/docs/小红书模块联调与验收.md b/docs/小红书模块联调与验收.md index 5da89af..3883fdb 100644 --- a/docs/小红书模块联调与验收.md +++ b/docs/小红书模块联调与验收.md @@ -46,7 +46,7 @@ pnpm install # 首次 pnpm dev ``` -生产构建:`pnpm build`,Nginx 配置见 `nginx-default.conf`(**仅** SPA 页面路径走 `index.html`,`/xhs/agent/list` 等 API 走 admin 后端,勿整段 `/xhs` 当 SPA)。 +生产构建:`pnpm build`;前后端分域名后,`/xhs/*` 页面由前端 `serve -s` SPA 回退,`/xhs/agent/*` 等 API 走 **api.*** Admin,勿把 API 打到前端域名。 ### 1.4 LIKE AI Agent @@ -158,12 +158,12 @@ Content-Type: application/json - [ ] **F2** 编辑提供商:BaseUrl、Model、ApiSecret 保存成功 - [ ] **F3** 对话发送消息 → 流式/完整回复正常 -### 阶段 G — 生产/nginx(上线前) +### 阶段 G — 生产 / Traefik(上线前) -- [ ] **G1** `nginx -t && nginx -s reload` 已执行 -- [ ] **G2** 刷新 `/xhs/analysis` 等页面返回 SPA,**不** 404 -- [ ] **G3** `POST https://api.nanafa.com/admin/xhs/agent/list`(带 Token)正常 -- [ ] **G4** Agent 使用生产 `--oms-url https://api.nanafa.com/admin` +- [ ] **G1** Ingress(tls) + cert-manager 证书就绪;前端 Service 健康 +- [ ] **G2** 刷新站点域名下 `/xhs/analysis` 等返回 SPA,**不** 404 +- [ ] **G3** `POST https://api.ideship.com/admin/xhs/agent/list`(带 Token)正常 +- [ ] **G4** Agent 使用生产 `--oms-url https://api.ideship.com/admin` --- @@ -232,8 +232,8 @@ curl.exe -s "http://localhost:5281/admin/rbac/auth/login" ... ### 5.5 前端 dev 正常、生产 404 -- Nginx 仅对 **页面路径** 回退 `index.html`(见 `nginx-default.conf` 第 63–66 行) -- API 请求应走 `VITE_API_ADMIN_URL`(如 `https://api.nanafa.com/admin`),不要打到 oms 静态站点的 `/xhs/agent/list` +- 站点域名下页面由 `serve -s` 回退 `index.html` +- API 请求应走 `VITE_API_ADMIN_URL`(如 `https://api.ideship.com/admin`),不要打到前端静态域名的 `/xhs/agent/list` ### 5.6 AI 生成 / 分析失败 diff --git a/docs/小红书模块设计-063001.md b/docs/小红书模块设计-063001.md index f139d17..485c47f 100644 --- a/docs/小红书模块设计-063001.md +++ b/docs/小红书模块设计-063001.md @@ -126,18 +126,12 @@ export default XHS; 'menu.system.xhsAiAssistant': 'AI 助手', ``` -### 2.3 Vite / Nginx SPA 规则 +### 2.3 Vite / SPA 规则 -`/xhs/*` 为纯前端路由,须加入: +`/xhs/*` 为纯前端路由: -- `vite.config.ts` → `frontendRoutes` 增加 `'/xhs'` -- `nginx-default.conf` → 在 API 反代规则之前增加: - -```nginx -location ~ ^/xhs { - try_files $uri /index.html; -} -``` +- `vite.config.ts` → `frontendRoutes` 增加 `'/xhs'`(开发 history fallback) +- 生产由前端容器 `serve -s` 回退 `index.html`;API 走分域名 `api.*`,无需站内 nginx --- diff --git a/docs/微信登录整合方案.md b/docs/微信登录整合方案.md index f30afbf..20c3229 100644 --- a/docs/微信登录整合方案.md +++ b/docs/微信登录整合方案.md @@ -422,17 +422,10 @@ src/SimpleAdmin.Api/ | Token | 与数据库/AppSettings 一致 | | 消息加解密 | **明文**(当前实现) | -### 7.3 Nginx +### 7.3 网关 / 回调 -`nginx-default.conf` 关键规则: - -```nginx -# 微信回调 -location ^~ /api/apiwechat/ { - rewrite ^/api/apiwechat/(.*)$ /admin/apiwechat/$1 break; - proxy_pass https://api.nanafa.com; -} -``` +生产已弃用站内 nginx。微信服务器回调应直连 **API 域名**(如 `https://api.ideship.com/admin/apiwechat/verification`),由 Traefik Ingress 转发到后端 Service;前端静态容器不做 `/api/apiwechat` 反代。 +本地开发仍可由 Vite 代理 `/api/apiwechat` → Admin。 --- diff --git a/nginx-default.conf b/nginx-default.conf deleted file mode 100644 index 6e9d274..0000000 --- a/nginx-default.conf +++ /dev/null @@ -1,489 +0,0 @@ -# ============ 生产环境部署必读(避免「本地可以、线上不行」)============ -# 1. 本文件必须真正生效:确认 oms.nanafa.com 的请求由「本 server 块」处理 -# - 若用 include:确保 include 的是含本 server 的 conf,且无其它 server 抢先匹配 -# - 更新后必须执行:nginx -t && nginx -s reload -# 2. /api、/Track、/rbac、/order 等 Admin 接口均由 nginx 代理到 api.nanafa.com,浏览器只访问 oms 同源 -# 3. HTTPS:证书部署到服务器(勿提交 .key 到 git) -# /etc/nginx/ssl/oms.nanafa.com.pem -# /etc/nginx/ssl/oms.nanafa.com.key -# 4. 官网 `/` 走 Vue SPA;静态资源在 /site/;客户登录 /login;管理端 /admin -# 5. 静态资源使用 /login-assets/,勿使用 /login/ 目录名 -# 6. 微信公众号服务器 URL 请使用 https://api.ideship.com/admin/apiwechat/verification -# (生产三套:admin / itfx / track,均挂在 https://api.ideship.com/{pathbase}) -# ============================================================================ - -# HTTP → HTTPS -server { - listen 80; - server_name oms.nanafa.com; - - return 301 https://$host$request_uri; -} - -server { - listen 443 ssl; - http2 on; - server_name oms.nanafa.com; - - ssl_certificate /etc/nginx/ssl/oms.nanafa.com.pem; - ssl_certificate_key /etc/nginx/ssl/oms.nanafa.com.key; - - ssl_session_timeout 1d; - ssl_session_cache shared:OMS_SSL:10m; - ssl_protocols TLSv1.2 TLSv1.3; - ssl_prefer_server_ciphers off; - - # 启用gzip压缩 - gzip on; - gzip_vary on; - gzip_min_length 1024; - gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json application/xml image/svg+xml; - gzip_comp_level 6; - gzip_disable "msie6"; - - root /usr/share/nginx/html; - - # ============ 官网首页(Vue SPA)============ - location = / { - try_files /index.html =200; - add_header Cache-Control "no-cache, no-store, must-revalidate"; - add_header Pragma "no-cache"; - add_header Expires "0"; - } - - location = /9610.html { - try_files /9610.html =404; - add_header Cache-Control "no-cache, no-store, must-revalidate"; - } - - # 官网仅静态资源在 /site/;首页保持在 / - location = /site { - return 301 /; - } - location = /site/ { - return 301 /; - } - location = /site/index.html { - return 301 /; - } - - location ^~ /site/ { - try_files $uri =404; - expires 7d; - add_header Cache-Control "public"; - access_log off; - } - - location = /favicon.ico { - try_files /favicon.png =404; - expires 7d; - add_header Cache-Control "public"; - access_log off; - } - - location = /favicon.png { - try_files /favicon.png =404; - expires 7d; - add_header Cache-Control "public"; - access_log off; - } - - # ============ 客户 / 管理端登录入口 ============ - location = /login { - try_files /index.html =200; - } - location = /login/ { - try_files /index.html =200; - } - - rewrite ^/admin/?$ /index.html last; - - location = /admin { - try_files /index.html =200; - } - location = /admin/ { - try_files /index.html =200; - } - - # ============ API 前端路由(必须先于 /api/ 代理)============ - location ~ ^/api/(upstreamInterface|upstreamInterfaceConfig|upstreamInterfaceConfigEdit|downstreamInterface(|/newPort)|appList(|/createApplication)|requestLog|callStatistics|apiDebugger|apiTest|openApi)(/|$) { - try_files /index.html =200; - } - - # ============ 小红书前端路由(^~ 须比下方 /xhs/ API 更具体)============ - location ^~ /xhs/startService { - try_files /index.html =200; - } - location ^~ /xhs/creation { - try_files /index.html =200; - } - location ^~ /xhs/publishHistory { - try_files /index.html =200; - } - location ^~ /xhs/skills { - try_files /index.html =200; - } - location ^~ /xhs/humanSettings { - try_files /index.html =200; - } - location ^~ /xhs/agentPair { - try_files /index.html =200; - } - location = /xhs/analysis { - try_files /index.html =200; - } - location = /xhs/analysis/ { - try_files /index.html =200; - } - - # ============ 微信 H5 / 操作页(^~ 须比下方 API 前缀更长或更具体)============ - location ^~ /wechat/register { - try_files /index.html =200; - } - location ^~ /wechat/bind { - try_files /index.html =200; - } - location ^~ /h5 { - try_files /index.html =200; - } - location ^~ /operation/inboundScan { - try_files /index.html =200; - } - location ^~ /operation/outboundScan { - try_files /index.html =200; - } - location ^~ /operation/printLabel { - try_files /index.html =200; - } - location ^~ /operation/parcelArrival { - try_files /index.html =200; - } - location ^~ /operation/fulfillPack { - try_files /index.html =200; - } - - # ============ 订单前端路由(^~ 须比下方 /order/ API 正则代理更优先)============ - location ^~ /order/priceCheck { - try_files /index.html =200; - } - location ^~ /order/placeOrder { - try_files /index.html =200; - } - location ^~ /order/myOrders { - try_files /index.html =200; - } - location ^~ /order/fulfillOrders { - try_files /index.html =200; - } - location ^~ /order/waybillManage { - try_files /index.html =200; - } - location ^~ /order/pickupOrder { - try_files /index.html =200; - } - location ^~ /order/customs9610Order { - try_files /index.html =200; - } - location ^~ /order/batchJobs { - try_files /index.html =200; - } - location ^~ /order/templateMapping { - try_files /index.html =200; - } - location ^~ /order/platformStore { - try_files /index.html =200; - } - location ^~ /order/platformStoreOrder { - try_files /index.html =200; - } - - # ============ 知识库前端路由(^~ 须比下方 /kb/ API 代理更优先)============ - location ^~ /kb/category { - try_files /index.html =200; - } - location ^~ /kb/article { - try_files /index.html =200; - } - - # ============ 仓储前端路由(^~ 须比下方 /warehouse/ API 正则代理更优先)============ - # 页面路径与 API 前缀相同者用精确匹配,避免 /warehouse/sku/list 等接口被误判为 SPA - location = /warehouse/parcel { - try_files /index.html =200; - } - location = /warehouse/inventory { - try_files /index.html =200; - } - location = /warehouse/sku { - try_files /index.html =200; - } - location = /warehouse/binLocation { - try_files /index.html =200; - } - location ^~ /warehouse/inboundNotice { - try_files /index.html =200; - } - - # 采集插件确认页(/Plugin/AddPackage);API 走 /plugin/browser/* - location ^~ /Plugin/ { - try_files /index.html =200; - } - - # ============ 渠道前端路由(^~ 须比下方 /channel/ API 正则代理更优先)============ - location ^~ /channel/salesProducts { try_files /index.html =200; } - location ^~ /channel/serviceChannels { try_files /index.html =200; } - location ^~ /channel/priceArea { try_files /index.html =200; } - location ^~ /channel/blacklist { try_files /index.html =200; } - location ^~ /channel/remoteZones { try_files /index.html =200; } - location ^~ /channel/weightRules { try_files /index.html =200; } - location ^~ /channel/weightRounding { try_files /index.html =200; } - location ^~ /channel/autoTrigger { try_files /index.html =200; } - location ^~ /channel/issueTemplate { try_files /index.html =200; } - location ^~ /channel/labelDesign { try_files /index.html =200; } - location ^~ /channel/guide { try_files /index.html =200; } - - # ============ 系统管理前端路由(^~ 须比下方 /system/ API 正则代理更优先)============ - # 页面路径为 camelCase;API 为 kebab(如 /system/dict/list、/system/batch-email/…),勿用宽泛前缀匹配 dict - location = /system/dict { - try_files /index.html =200; - } - location = /system/warehouseAddress { - try_files /index.html =200; - } - location = /system/announcement { - try_files /index.html =200; - } - location = /system/country { - try_files /index.html =200; - } - location ^~ /system/users { - try_files /index.html =200; - } - location ^~ /system/roles { - try_files /index.html =200; - } - location ^~ /system/menus { - try_files /index.html =200; - } - location ^~ /system/organizations { - try_files /index.html =200; - } - location ^~ /system/batchEmail { - try_files /index.html =200; - } - location ^~ /system/otherConfig { - try_files /index.html =200; - } - location ^~ /system/aiAssistant { - try_files /index.html =200; - } - location ^~ /system/aiSkills { - try_files /index.html =200; - } - location ^~ /system/mcpTools { - try_files /index.html =200; - } - location ^~ /system/dataPerm/ { - try_files /index.html =200; - } - - # ============ 营销前端路由(^~ 须比下方 /marketing/ API 正则代理更优先)============ - location ^~ /marketing/memberLevel { try_files /index.html =200; } - location ^~ /marketing/pointsRule { try_files /index.html =200; } - location ^~ /marketing/pointsFlow { try_files /index.html =200; } - location ^~ /marketing/couponTemplate { try_files /index.html =200; } - location ^~ /marketing/couponGrant { try_files /index.html =200; } - location ^~ /marketing/commissionRule { try_files /index.html =200; } - location ^~ /marketing/commissionDetail { try_files /index.html =200; } - location ^~ /marketing/commissionWithdraw { try_files /index.html =200; } - location ^~ /marketing/referralTree { try_files /index.html =200; } - - # ============ 微信公众号服务器回调(admin API,非 itfx)============ - location ^~ /api/apiwechat/ { - rewrite ^/api/apiwechat/(.*)$ /admin/apiwechat/$1 break; - proxy_pass http://api.nanafa.com; - proxy_http_version 1.1; - proxy_set_header Host api.nanafa.com; - 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_connect_timeout 30s; - proxy_send_timeout 30s; - proxy_read_timeout 30s; - } - - # ============ API 代理(仅真实后端接口,同源请求避免跨域 405)============ - location ~ ^/api/ { - rewrite ^/api/(.*)$ /itfx/$1 break; - proxy_pass http://api.nanafa.com; - proxy_http_version 1.1; - proxy_set_header Host api.nanafa.com; - 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_connect_timeout 30s; - proxy_send_timeout 30s; - proxy_read_timeout 30s; - } - - location ^~ /Track { - rewrite ^/Track(.*)$ /track/Track$1 break; - proxy_pass http://api.nanafa.com; - proxy_http_version 1.1; - proxy_set_header Host api.nanafa.com; - 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_connect_timeout 30s; - proxy_send_timeout 30s; - proxy_read_timeout 30s; - } - - # ============ Admin API 同源反代(/rbac /order /xhs … → api.nanafa.com/admin)============ - location ~ ^/(rbac|order|monitor|customer|recharge|channel|system|warehouse|plugin|marketing|kb)/ { - rewrite ^/(.*)$ /admin/$1 break; - proxy_pass http://api.nanafa.com; - proxy_http_version 1.1; - proxy_set_header Host api.nanafa.com; - 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_connect_timeout 30s; - proxy_send_timeout 30s; - proxy_read_timeout 120s; - } - - location ^~ /wechat/ { - rewrite ^/(.*)$ /admin/$1 break; - proxy_pass http://api.nanafa.com; - proxy_http_version 1.1; - proxy_set_header Host api.nanafa.com; - 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_connect_timeout 30s; - proxy_send_timeout 30s; - proxy_read_timeout 120s; - } - - location ^~ /operation/ { - rewrite ^/(.*)$ /admin/$1 break; - proxy_pass http://api.nanafa.com; - proxy_http_version 1.1; - proxy_set_header Host api.nanafa.com; - 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_connect_timeout 30s; - proxy_send_timeout 30s; - proxy_read_timeout 120s; - } - - location ^~ /xhs/ { - rewrite ^/(.*)$ /admin/$1 break; - proxy_pass http://api.nanafa.com; - proxy_http_version 1.1; - proxy_set_header Host api.nanafa.com; - 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_connect_timeout 30s; - proxy_send_timeout 30s; - proxy_read_timeout 300s; - } - - location ^~ /uploads/ { - rewrite ^/(.*)$ /admin/$1 break; - proxy_pass http://api.nanafa.com; - proxy_http_version 1.1; - proxy_set_header Host api.nanafa.com; - 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_connect_timeout 30s; - proxy_send_timeout 30s; - proxy_read_timeout 120s; - } - - location ^~ /mcp/ { - rewrite ^/(.*)$ /admin/$1 break; - proxy_pass http://api.nanafa.com; - proxy_http_version 1.1; - proxy_set_header Host api.nanafa.com; - 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_connect_timeout 30s; - proxy_send_timeout 30s; - proxy_read_timeout 300s; - } - - location ^~ /ai/ { - rewrite ^/(.*)$ /admin/$1 break; - proxy_pass http://api.nanafa.com; - proxy_http_version 1.1; - proxy_set_header Host api.nanafa.com; - 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_connect_timeout 30s; - proxy_send_timeout 30s; - proxy_read_timeout 300s; - } - - # HTML 不缓存 - location = /index.html { - add_header Cache-Control "no-cache, no-store, must-revalidate"; - add_header Pragma "no-cache"; - add_header Expires "0"; - } - - # 微信 JS 安全域名校验:原样返回 txt,禁止 SPA 回落 index.html - location ~* ^/MP_verify_[A-Za-z0-9]+\.txt$ { - default_type text/plain; - charset off; - add_header Cache-Control "no-store"; - try_files $uri =404; - } - - # ============ 静态资源缓存 ============ - - location ~* \.(?:js|css)$ { - try_files $uri =404; - expires 1y; - add_header Cache-Control "public, immutable"; - access_log off; - } - - location ~* \.(?:jpg|jpeg|png|gif|ico|svg|webp|avif)$ { - try_files $uri =404; - expires 1y; - add_header Cache-Control "public, immutable"; - access_log off; - } - - location ~* \.(?:woff2?|ttf|eot|otf)$ { - try_files $uri =404; - expires 1y; - add_header Cache-Control "public, immutable"; - access_log off; - } - - location ~* \.(?:pdf|zip|tar|gz|br)$ { - try_files $uri =404; - expires 1M; - add_header Cache-Control "public"; - access_log off; - } - - # ============ SPA 前端路由(放在最后)============ - # 不用 try_files $uri $uri/ —— 若磁盘存在同名目录且无 index,会 403 且不会回落 index.html - location / { - index index.html; - try_files $uri /index.html; - } - - error_page 500 502 503 504 /50x.html; - location = /50x.html { - root html; - } -} diff --git a/nginx-docker.conf b/nginx-docker.conf deleted file mode 100644 index 9cf8ed8..0000000 --- a/nginx-docker.conf +++ /dev/null @@ -1,279 +0,0 @@ -# Docker 容器专用:只监听 80,不配置 SSL(7962:80,由外层网关/宿主机 nginx 终结 HTTPS) -server { - listen 80; - server_name _; - - gzip on; - gzip_vary on; - gzip_min_length 1024; - gzip_types text/plain text/css text/xml text/javascript application/javascript application/json application/xml image/svg+xml; - - root /usr/share/nginx/html; - - location = / { - try_files /index.html =200; - add_header Cache-Control "no-cache, no-store, must-revalidate"; - add_header Pragma "no-cache"; - add_header Expires "0"; - } - - location = /9610.html { - try_files /9610.html =404; - add_header Cache-Control "no-cache, no-store, must-revalidate"; - } - - location = /site { - return 301 /; - } - location = /site/ { - return 301 /; - } - location = /site/index.html { - return 301 /; - } - - location ^~ /site/ { - try_files $uri =404; - expires 7d; - add_header Cache-Control "public"; - access_log off; - } - - location = /favicon.ico { - try_files /favicon.png =404; - expires 7d; - add_header Cache-Control "public"; - access_log off; - } - - location = /favicon.png { - try_files /favicon.png =404; - expires 7d; - add_header Cache-Control "public"; - access_log off; - } - - location = /login { - try_files /index.html =200; - } - location = /login/ { - try_files /index.html =200; - } - - rewrite ^/admin/?$ /index.html last; - - location = /admin { - try_files /index.html =200; - } - location = /admin/ { - try_files /index.html =200; - } - - location ~ ^/api/(upstreamInterface|upstreamInterfaceConfig|upstreamInterfaceConfigEdit|downstreamInterface(|/newPort)|appList(|/createApplication)|requestLog|callStatistics|apiDebugger|apiTest|openApi)(/|$) { - try_files /index.html =200; - } - - location ^~ /xhs/startService { try_files /index.html =200; } - location ^~ /xhs/creation { try_files /index.html =200; } - location ^~ /xhs/publishHistory { try_files /index.html =200; } - location ^~ /xhs/skills { try_files /index.html =200; } - location ^~ /xhs/humanSettings { try_files /index.html =200; } - location ^~ /xhs/agentPair { try_files /index.html =200; } - location = /xhs/analysis { try_files /index.html =200; } - location = /xhs/analysis/ { try_files /index.html =200; } - - location ^~ /wechat/register { try_files /index.html =200; } - location ^~ /wechat/bind { try_files /index.html =200; } - location ^~ /h5 { try_files /index.html =200; } - location ^~ /track/query { try_files /index.html =200; } - location ^~ /operation/inboundScan { try_files /index.html =200; } - location ^~ /operation/outboundScan { try_files /index.html =200; } - location ^~ /operation/printLabel { try_files /index.html =200; } - location ^~ /operation/parcelArrival { try_files /index.html =200; } - location ^~ /operation/fulfillPack { try_files /index.html =200; } - - # 订单前端路由(^~ 须比下方 /order/ API 正则代理更优先) - location ^~ /order/priceCheck { try_files /index.html =200; } - location ^~ /order/placeOrder { try_files /index.html =200; } - location ^~ /order/myOrders { try_files /index.html =200; } - location ^~ /order/fulfillOrders { try_files /index.html =200; } - location ^~ /order/waybillManage { try_files /index.html =200; } - location ^~ /order/pickupOrder { try_files /index.html =200; } - location ^~ /order/customs9610Order { try_files /index.html =200; } - location ^~ /order/batchJobs { try_files /index.html =200; } - location ^~ /order/templateMapping { try_files /index.html =200; } - location ^~ /order/platformStore { try_files /index.html =200; } - location ^~ /order/platformStoreOrder { try_files /index.html =200; } - - # 知识库前端路由(^~ 须比下方 /kb/ API 代理更优先) - location ^~ /kb/category { try_files /index.html =200; } - location ^~ /kb/article { try_files /index.html =200; } - - location = /warehouse/parcel { try_files /index.html =200; } - location = /warehouse/inventory { try_files /index.html =200; } - location = /warehouse/sku { try_files /index.html =200; } - location = /warehouse/binLocation { try_files /index.html =200; } - location ^~ /warehouse/inboundNotice { try_files /index.html =200; } - location ^~ /Plugin/ { try_files /index.html =200; } - - location ^~ /channel/salesProducts { try_files /index.html =200; } - location ^~ /channel/serviceChannels { try_files /index.html =200; } - location ^~ /channel/priceArea { try_files /index.html =200; } - location ^~ /channel/blacklist { try_files /index.html =200; } - location ^~ /channel/remoteZones { try_files /index.html =200; } - location ^~ /channel/weightRules { try_files /index.html =200; } - location ^~ /channel/weightRounding { try_files /index.html =200; } - location ^~ /channel/autoTrigger { try_files /index.html =200; } - location ^~ /channel/issueTemplate { try_files /index.html =200; } - location ^~ /channel/labelDesign { try_files /index.html =200; } - - # 营销前端路由(^~ 须比下方 /marketing/ API 正则代理更优先) - location ^~ /marketing/memberLevel { try_files /index.html =200; } - location ^~ /marketing/pointsRule { try_files /index.html =200; } - location ^~ /marketing/pointsFlow { try_files /index.html =200; } - location ^~ /marketing/couponTemplate { try_files /index.html =200; } - location ^~ /marketing/couponGrant { try_files /index.html =200; } - location ^~ /marketing/commissionRule { try_files /index.html =200; } - location ^~ /marketing/commissionDetail { try_files /index.html =200; } - location ^~ /marketing/commissionWithdraw { try_files /index.html =200; } - location ^~ /marketing/referralTree { try_files /index.html =200; } - - # 系统管理前端路由(^~ 须比下方 /system/ API 正则代理更优先) - location = /system/dict { try_files /index.html =200; } - location = /system/warehouseAddress { try_files /index.html =200; } - location = /system/announcement { try_files /index.html =200; } - location = /system/country { try_files /index.html =200; } - location ^~ /system/users { try_files /index.html =200; } - location ^~ /system/roles { try_files /index.html =200; } - location ^~ /system/menus { try_files /index.html =200; } - location ^~ /system/organizations { try_files /index.html =200; } - location ^~ /system/batchEmail { try_files /index.html =200; } - location ^~ /system/otherConfig { try_files /index.html =200; } - location ^~ /system/aiAssistant { try_files /index.html =200; } - location ^~ /system/aiSkills { try_files /index.html =200; } - location ^~ /system/mcpTools { try_files /index.html =200; } - location ^~ /system/dataPerm/ { try_files /index.html =200; } - - location ^~ /api/apiwechat/ { - rewrite ^/api/apiwechat/(.*)$ /admin/apiwechat/$1 break; - proxy_pass http://api.nanafa.com; - proxy_http_version 1.1; - proxy_set_header Host api.nanafa.com; - 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; - } - - location ~ ^/api/ { - rewrite ^/api/(.*)$ /itfx/$1 break; - proxy_pass http://api.nanafa.com; - proxy_http_version 1.1; - proxy_set_header Host api.nanafa.com; - 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; - } - - location ^~ /Track { - rewrite ^/Track(.*)$ /track/Track$1 break; - proxy_pass http://api.nanafa.com; - proxy_http_version 1.1; - proxy_set_header Host api.nanafa.com; - 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; - } - - location ~ ^/(rbac|order|monitor|customer|recharge|channel|system|warehouse|plugin|marketing|kb)/ { - rewrite ^/(.*)$ /admin/$1 break; - proxy_pass http://api.nanafa.com; - proxy_http_version 1.1; - proxy_set_header Host api.nanafa.com; - 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_read_timeout 120s; - } - - location ^~ /wechat/ { - rewrite ^/(.*)$ /admin/$1 break; - proxy_pass http://api.nanafa.com; - proxy_http_version 1.1; - proxy_set_header Host api.nanafa.com; - 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; - } - - location ^~ /operation/ { - rewrite ^/(.*)$ /admin/$1 break; - proxy_pass http://api.nanafa.com; - proxy_http_version 1.1; - proxy_set_header Host api.nanafa.com; - 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; - } - - location ^~ /xhs/ { - rewrite ^/(.*)$ /admin/$1 break; - proxy_pass http://api.nanafa.com; - proxy_http_version 1.1; - proxy_set_header Host api.nanafa.com; - 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_read_timeout 300s; - } - - location ^~ /uploads/ { - rewrite ^/(.*)$ /admin/$1 break; - proxy_pass http://api.nanafa.com; - proxy_http_version 1.1; - proxy_set_header Host api.nanafa.com; - 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_read_timeout 120s; - } - - location ^~ /mcp/ { - rewrite ^/(.*)$ /admin/$1 break; - proxy_pass http://api.nanafa.com; - proxy_http_version 1.1; - proxy_set_header Host api.nanafa.com; - 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_read_timeout 300s; - } - - location ^~ /ai/ { - rewrite ^/(.*)$ /admin/$1 break; - proxy_pass http://api.nanafa.com; - proxy_http_version 1.1; - proxy_set_header Host api.nanafa.com; - 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_read_timeout 300s; - } - - location = /index.html { - add_header Cache-Control "no-cache, no-store, must-revalidate"; - add_header Pragma "no-cache"; - add_header Expires "0"; - } - - location ~* \.(?:js|css)$ { - try_files $uri =404; - expires 1y; - add_header Cache-Control "public, immutable"; - access_log off; - } - - location / { - index index.html; - try_files $uri /index.html; - } -} diff --git a/nginx-k3s.conf b/nginx-k3s.conf deleted file mode 100644 index 5785f83..0000000 --- a/nginx-k3s.conf +++ /dev/null @@ -1,226 +0,0 @@ -# k3s 方案 A:仅静态 SPA。API 由浏览器按域名直连(prod: api.ideship.com / test: api.test.ideship.com)。 -server { - listen 80; - server_name _; - - gzip on; - gzip_vary on; - gzip_min_length 1024; - gzip_types text/plain text/css text/xml text/javascript application/javascript application/json application/xml image/svg+xml; - - root /usr/share/nginx/html; - - location = / { - try_files $uri @spa; - } - - location = /9610.html { - try_files /9610.html =404; - add_header Cache-Control "no-cache, no-store, must-revalidate"; - } - - location = /site { - return 301 /; - } - location = /site/ { - return 301 /; - } - location = /site/index.html { - return 301 /; - } - - location ^~ /site/ { - try_files $uri =404; - expires 7d; - add_header Cache-Control "public"; - access_log off; - } - - location = /favicon.ico { - try_files /favicon.png =404; - expires 7d; - add_header Cache-Control "public"; - access_log off; - } - - location = /favicon.png { - try_files /favicon.png =404; - expires 7d; - add_header Cache-Control "public"; - access_log off; - } - - location = /login { - try_files $uri @spa; - } - location = /login/ { - try_files $uri @spa; - } - - rewrite ^/admin/?$ /index.html last; - - location = /admin { - try_files $uri @spa; - } - location = /admin/ { - try_files $uri @spa; - } - - location ~ ^/api/(upstreamInterface|upstreamInterfaceConfig|upstreamInterfaceConfigEdit|downstreamInterface(|/newPort)|appList(|/createApplication)|requestLog|callStatistics|apiDebugger|apiTest|openApi)(/|$) { - try_files $uri @spa; - } - - location ^~ /xhs/startService { try_files $uri @spa; } - location ^~ /xhs/creation { try_files $uri @spa; } - location ^~ /xhs/publishHistory { try_files $uri @spa; } - location ^~ /xhs/skills { try_files $uri @spa; } - location ^~ /xhs/humanSettings { try_files $uri @spa; } - location ^~ /xhs/agentPair { try_files $uri @spa; } - location = /xhs/analysis { try_files $uri @spa; } - location = /xhs/analysis/ { try_files $uri @spa; } - - location ^~ /wechat/register { try_files $uri @spa; } - location ^~ /wechat/bind { try_files $uri @spa; } - location ^~ /h5 { try_files $uri @spa; } - location ^~ /track/query { try_files $uri @spa; } - location ^~ /operation/inboundScan { try_files $uri @spa; } - location ^~ /operation/outboundScan { try_files $uri @spa; } - location ^~ /operation/printLabel { try_files $uri @spa; } - location ^~ /operation/parcelArrival { try_files $uri @spa; } - location ^~ /operation/fulfillPack { try_files $uri @spa; } - - # 订单前端路由(^~ 须比下方 /order/ API 正则代理更优先) - location ^~ /order/priceCheck { try_files $uri @spa; } - location ^~ /order/placeOrder { try_files $uri @spa; } - location ^~ /order/myOrders { try_files $uri @spa; } - location ^~ /order/fulfillOrders { try_files $uri @spa; } - location ^~ /order/waybillManage { try_files $uri @spa; } - location ^~ /order/pickupOrder { try_files $uri @spa; } - location ^~ /order/customs9610Order { try_files $uri @spa; } - location ^~ /order/batchJobs { try_files $uri @spa; } - location ^~ /order/templateMapping { try_files $uri @spa; } - location ^~ /order/platformStore { try_files $uri @spa; } - location ^~ /order/platformStoreOrder { try_files $uri @spa; } - - # 知识库前端路由(^~ 须比下方 /kb/ API 代理更优先) - location ^~ /kb/category { try_files $uri @spa; } - location ^~ /kb/article { try_files $uri @spa; } - - location = /warehouse/parcel { try_files $uri @spa; } - location = /warehouse/inventory { try_files $uri @spa; } - location = /warehouse/sku { try_files $uri @spa; } - location = /warehouse/binLocation { try_files $uri @spa; } - location ^~ /warehouse/inboundNotice { try_files $uri @spa; } - location ^~ /Plugin/ { try_files $uri @spa; } - - location ^~ /channel/salesProducts { try_files $uri @spa; } - location ^~ /channel/serviceChannels { try_files $uri @spa; } - location ^~ /channel/priceArea { try_files $uri @spa; } - location ^~ /channel/blacklist { try_files $uri @spa; } - location ^~ /channel/remoteZones { try_files $uri @spa; } - location ^~ /channel/weightRules { try_files $uri @spa; } - location ^~ /channel/weightRounding { try_files $uri @spa; } - location ^~ /channel/autoTrigger { try_files $uri @spa; } - location ^~ /channel/issueTemplate { try_files $uri @spa; } - location ^~ /channel/labelDesign { try_files $uri @spa; } - - # 官网线路目录。这条不是前端路由;落到下方 SPA 会返回 index.html,浏览器看不到 JSON。 - # 仅这一条转到对应环境的 Admin API(test → api.test.ideship.com,其余 → api.ideship.com)。 - location = /channel/sales-product/portal/catalog { - set $oms_api_upstream api.ideship.com; - if ($host ~* "(^test\.ideship\.com$|\.test\.ideship\.com$)") { - set $oms_api_upstream api.test.ideship.com; - } - resolver 1.1.1.1 8.8.8.8 ipv6=off valid=300s; - resolver_timeout 5s; - proxy_http_version 1.1; - proxy_ssl_server_name on; - proxy_set_header Host $oms_api_upstream; - 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_pass https://$oms_api_upstream/admin/channel/sales-product/portal/catalog$is_args$args; - proxy_connect_timeout 10s; - proxy_read_timeout 30s; - } - - # 营销前端路由(^~ 须比下方 /marketing/ API 正则代理更优先) - location ^~ /marketing/memberLevel { try_files $uri @spa; } - location ^~ /marketing/pointsRule { try_files $uri @spa; } - location ^~ /marketing/pointsFlow { try_files $uri @spa; } - location ^~ /marketing/couponTemplate { try_files $uri @spa; } - location ^~ /marketing/couponGrant { try_files $uri @spa; } - location ^~ /marketing/commissionRule { try_files $uri @spa; } - location ^~ /marketing/commissionDetail { try_files $uri @spa; } - location ^~ /marketing/commissionWithdraw { try_files $uri @spa; } - location ^~ /marketing/referralTree { try_files $uri @spa; } - - # 面单/截图同源代理:/__cos__// → http:/// - # cos.* 自定义域 https 证书常仍是 *.myqcloud.com;上游继续走 http。 - # COS 签名 q-sign-time 含 ";":前端须编成 %3B,否则查询串被按 ";" 拆参 → Request has expired。 - location ~ ^/__cos__/([^/]+)(/.*)$ { - set $cos_host $1; - set $cos_uri $2; - resolver 1.1.1.1 8.8.8.8 ipv6=off valid=300s; - resolver_timeout 5s; - proxy_http_version 1.1; - proxy_set_header Host $cos_host; - proxy_set_header Authorization ""; - proxy_set_header Cookie ""; - proxy_set_header X-Real-IP ""; - proxy_pass http://$cos_host$cos_uri$is_args$args; - proxy_connect_timeout 10s; - proxy_read_timeout 120s; - proxy_buffering off; - } - - # 系统管理前端路由(^~ 须比下方 /system/ API 正则代理更优先) - location = /system/dict { try_files $uri @spa; } - location = /system/warehouseAddress { try_files $uri @spa; } - location = /system/announcement { try_files $uri @spa; } - location = /system/country { try_files $uri @spa; } - location ^~ /system/users { try_files $uri @spa; } - location ^~ /system/roles { try_files $uri @spa; } - location ^~ /system/menus { try_files $uri @spa; } - location ^~ /system/organizations { try_files $uri @spa; } - location ^~ /system/batchEmail { try_files $uri @spa; } - location ^~ /system/otherConfig { try_files $uri @spa; } - location ^~ /system/aiAssistant { try_files $uri @spa; } - location ^~ /system/aiSkills { try_files $uri @spa; } - location ^~ /system/mcpTools { try_files $uri @spa; } - location ^~ /system/dataPerm/ { try_files $uri @spa; } - - # SPA 回退:try_files $uri @spa 统一带 no-cache,避免部署后浏览器缓存旧 index.html - location @spa { - add_header Cache-Control "no-cache, no-store, must-revalidate" always; - add_header Pragma "no-cache" always; - add_header Expires "0" always; - try_files /index.html =404; - } - - location = /index.html { - add_header Cache-Control "no-cache, no-store, must-revalidate" always; - add_header Pragma "no-cache" always; - add_header Expires "0" always; - try_files /index.html =404; - } - - # 微信 JS 安全域名校验:原样返回 txt,禁止 SPA 回落 index.html - location ~* ^/MP_verify_[A-Za-z0-9]+\.txt$ { - default_type text/plain; - charset off; - add_header Cache-Control "no-store"; - try_files $uri =404; - } - - location ~* \.(?:js|css)$ { - try_files $uri =404; - expires 1y; - add_header Cache-Control "public, immutable"; - access_log off; - } - - location / { - try_files $uri @spa; - } -} diff --git a/src/api/mcp/index.ts b/src/api/mcp/index.ts index 459ce64..b3c72c4 100644 --- a/src/api/mcp/index.ts +++ b/src/api/mcp/index.ts @@ -112,7 +112,7 @@ export async function chatAiAgent(payload: { toolKeys?: string[]; maxToolRounds?: number; }): Promise { - // 走 /ai/agent/chat:线上已有;OMS nginx / Vite 须反代 /ai/(见 nginx-*.conf) + // 走 /ai/agent/chat:开发 Vite 反代 /ai/;生产浏览器直连 api.* 域名 // 后端另注册 /mcp/agent/chat,部署后可再切 const res = await axiosInstance.post(mcpUrl('/ai/agent/chat'), payload); const x = assertBusinessOk(res.data) as any; diff --git a/src/api/oms/common.ts b/src/api/oms/common.ts index 0367c20..139cc52 100644 --- a/src/api/oms/common.ts +++ b/src/api/oms/common.ts @@ -106,8 +106,7 @@ export async function omsPost(path: string, body?: Record) /** * COS 签名参数里的 `q-sign-time=start;end` 含分号。 - * 线上 nginx / 部分网关会把 `;` 当查询分隔符,拆成 `q-sign-time=start&end=…`, - * COS 随即返回 AccessDenied / Request has expired;本地 Vite 代理通常不拆,故本地正常。 + * 部分代理会把 `;` 当查询分隔符拆坏签名;本地 Vite `/__cos__` 转发前编成 `%3B`。 */ export function encodeCosSignedQuery(search: string): string { if (!search) return '' @@ -115,10 +114,11 @@ export function encodeCosSignedQuery(search: string): string { } /** - * COS 绝对地址 → 同源 `/__cos__//...`(开发 Vite / 生产 nginx 反代)。 - * cos.ideship.com 等自定义域名 https 不可用,直链 http 又会在 https 页面触发混合内容。 + * COS 绝对地址 → 同源 `/__cos__//...`(仅开发 Vite 反代)。 + * 生产已弃用站内 nginx,无 `/__cos__`;预览走 file-storage/stream,勿改写为该前缀(会落到 SPA)。 */ function toCosSameOriginProxyUrl(absoluteUrl: string): string | null { + if (!import.meta.env.DEV) return null try { const u = new URL(absoluteUrl) const host = u.hostname.toLowerCase() diff --git a/src/components/PrintTemplate/OrderPrintModal.vue b/src/components/PrintTemplate/OrderPrintModal.vue index c9126ef..35d1c82 100644 --- a/src/components/PrintTemplate/OrderPrintModal.vue +++ b/src/components/PrintTemplate/OrderPrintModal.vue @@ -101,7 +101,7 @@ import { import { useHiprintClientState } from '@/utils/hiprint/hiprintClientState'; import { resolveOmsAssetUrl } from '@/api/oms/common'; import PrintOptionsForm from '@/components/PrintTemplate/PrintOptionsForm.vue'; -import { fetchPrintAssetBytes, fetchPrintPreviewBlob } from '@/utils/printPreview'; +import { fetchPrintAssetBytes, openPrintAssetInNewTab } from '@/utils/printPreview'; import { buildHiprintBlobPrintExtras, HIPRINT_PAPER_PRESET_OPTIONS, @@ -258,34 +258,15 @@ function toAbsoluteUrl(url: string) { return `${window.location.origin}/${resolved}`; } -/** - * 查看面单:经 file-storage/stream(或 /__cos__)拉字节再开 blob。 - * 勿把带 `q-sign-time=a;b` 的 COS 链直接丢给浏览器——线上反代/跳转常把 `;` 拆成 `&`, - * COS 会回 AccessDenied/Request has expired(本地 Vite 不拆,故本地正常)。 - */ +/** 查看面单:与附件预览同一套 stream → blob,避免直开 COS 证书/签名问题 */ async function openItemInNative(item: PrintItem) { if (!item?.url) { Message.warning('当前无可打开的打印链接'); return; } - // 同步开空白页,避免 await 后被 Chrome 拦截弹窗 - const win = window.open('about:blank', '_blank'); - if (!win) { - Message.warning('请允许浏览器弹窗以查看面单'); - return; - } try { - win.document.title = '面单加载中…'; - const blob = await fetchPrintPreviewBlob(item.url); - const blobUrl = URL.createObjectURL(blob); - win.location.replace(blobUrl); - window.setTimeout(() => URL.revokeObjectURL(blobUrl), 60_000); + await openPrintAssetInNewTab(item.url, { loadingTitle: '面单加载中…' }); } catch (e: unknown) { - try { - win.close(); - } catch { - // ignore - } const msg = e instanceof Error ? e.message : String(e || ''); Message.warning(msg || '面单打开失败'); } diff --git a/src/utils/printPreview.ts b/src/utils/printPreview.ts index 373d977..d758a37 100644 --- a/src/utils/printPreview.ts +++ b/src/utils/printPreview.ts @@ -47,14 +47,14 @@ function isCosAbsoluteUrl(raw: string): boolean { } /** - * 把 COS 绝对地址改写为同源 `/__cos__//...` 代理路径。 - * 开发由 Vite 代理;生产由 nginx-k3s `/__cos__/` 反代(避免浏览器直打 COS 撞 CORS)。 + * 把 COS 绝对地址改写为同源 `/__cos__//...`(仅开发 Vite)。 + * 生产无站内 nginx/`__cos__`,返回 null,由 file-storage/stream 拉取。 */ export function toCosProxyUrl(url: string): string | null { + if (!import.meta.env.DEV) return null try { const u = new URL(url) if (!isCosHost(u.host)) return null - // 分号必须 %3B,否则线上反代会拆坏签名时间窗(见 encodeCosSignedQuery) return `${COS_PROXY_PREFIX}/${u.host}${u.pathname}${encodeCosSignedQuery(u.search)}` } catch { return null @@ -307,8 +307,8 @@ async function fetchBlobViaSameOriginPath(path: string): Promise { } /** - * 仅经同源 `/__cos__` 代理拉取 COS(开发 Vite / 生产 nginx)。 - * 禁止浏览器直连 cos.*:私有桶未配 CORS 时必失败。 + * 仅经同源 `/__cos__` 代理拉取 COS(仅开发 Vite)。 + * 禁止浏览器直连 cos.*:私有桶未配 CORS / 自定义域证书常不匹配。 */ async function fetchBlobViaCosProxy(rawUrl: string): Promise { const proxied = toCosProxyUrl(rawUrl) @@ -373,15 +373,6 @@ async function fetchPreviewBlob(rawUrl: string): Promise { const streamed = await fetchViaFileStorageStream(raw, errors) if (streamed) return streamed - // 同源 /__cos__ 代理(生产 nginx;开发上面已试过则跳过) - if (isCos && !import.meta.env.DEV) { - try { - return await fetchBlobViaCosProxy(raw) - } catch (e: unknown) { - errors.push(extractOmsRequestError(e) || 'COS 同源代理下载失败') - } - } - // 3) 同源 /uploads 路径 const sameOriginPath = toSameOriginPath(raw) if (sameOriginPath.startsWith('/')) { @@ -407,8 +398,6 @@ export async function fetchPrintPreviewBlob(rawUrl: string): Promise { export function resolvePrintPreviewPdfSource(rawUrl: string): string | null { const raw = String(rawUrl || '').trim() if (!raw || !isPrintPreviewPdf(raw)) return null - // 生产勿把 /__cos__ 交给前端直链:nginx 未配时会落到 SPA HTML - if (!import.meta.env.DEV) return null return toCosProxyUrl(raw) } @@ -442,3 +431,75 @@ export async function fetchPrintAssetBytes(rawUrl: string): Promise } return new Uint8Array(await blob.arrayBuffer()) } + +/** + * 新标签页预览面单/附件。 + * - 开发 + COS:同步打开 Vite `/__cos__/…`(`;`→`%3B`)。 + * - 生产:无站内反代,一律 file-storage/stream → blob(避免直开 cos 证书告警)。 + */ +export async function openPrintAssetInNewTab( + rawUrl: string, + options?: { loadingTitle?: string }, +): Promise { + const raw = String(rawUrl || '').trim() + if (!raw) { + throw new Error('预览地址为空') + } + + // 仅 DEV 有 /__cos__;生产打开该路径会落到 SPA + if (import.meta.env.DEV && /^https?:\/\//i.test(raw) && isCosAbsoluteUrl(raw)) { + const proxied = toCosProxyUrl(raw) + if (proxied) { + const win = window.open(proxied, '_blank', 'noopener,noreferrer') + if (!win) { + throw new Error('请允许浏览器弹窗以预览文件') + } + return + } + } + + // 同步开页,避免 await 后被当成非用户手势拦截;失败时在页内写错误,不直接 close + const win = window.open('about:blank', '_blank') + if (!win) { + throw new Error('请允许浏览器弹窗以预览文件') + } + const title = options?.loadingTitle || '加载中…' + try { + win.document.open() + win.document.write( + `${escapeHtml(title)}` + + `${escapeHtml(title)}…`, + ) + win.document.close() + } catch { + // 个别环境写 document 失败时仍继续拉 blob + } + + try { + const blob = await fetchPreviewBlob(raw) + const blobUrl = URL.createObjectURL(blob) + win.location.href = blobUrl + window.setTimeout(() => URL.revokeObjectURL(blobUrl), 120_000) + } catch (e) { + const msg = e instanceof Error ? e.message : String(e || '预览失败') + try { + win.document.open() + win.document.write( + `预览失败` + + `${escapeHtml(msg)}`, + ) + win.document.close() + } catch { + // ignore + } + throw e + } +} + +function escapeHtml(text: string): string { + return String(text) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') +} diff --git a/src/views/order/components/OrderAttachmentsTab.vue b/src/views/order/components/OrderAttachmentsTab.vue index c534d3a..a39864c 100644 --- a/src/views/order/components/OrderAttachmentsTab.vue +++ b/src/views/order/components/OrderAttachmentsTab.vue @@ -41,7 +41,7 @@ {{ file.fileName || file.fileUrl }} @@ -53,7 +53,7 @@ {{ $t('order.myOrders.drawer.attachments.preview') }} @@ -91,7 +91,8 @@ import { uploadOrderFiles, type OrderFileItem, } from '@/api/order'; -import { resolveOmsAssetUrl } from '@/api/oms/common'; +import { extractOmsRequestError } from '@/api/oms/common'; +import { openPrintAssetInNewTab } from '@/utils/printPreview'; const FILE_TYPE_DEFS = [ { @@ -166,26 +167,24 @@ const visibleSections = computed(() => { })); }); -/** 与打印弹窗「查看面单」一致:同步打开可访问 URL(勿先 await 再开 blob,Chrome 会当搜索词) */ -function toAbsoluteUrl(path?: string) { - const resolved = resolveOmsAssetUrl(path) || String(path || '').trim(); - if (!resolved) return ''; - if (/^https?:\/\//i.test(resolved) || resolved.startsWith('blob:') || resolved.startsWith('data:')) { - return resolved; - } - if (resolved.startsWith('/')) return `${window.location.origin}${resolved}`; - return `${window.location.origin}/${resolved}`; -} - -function openFile(file: OrderFileItem) { - const url = toAbsoluteUrl(file.fileUrl); - if (!url) { +/** 与打印弹窗「查看面单」一致:开发可走 /__cos__,生产 stream → blob,不直开 cos 域名 */ +async function openFile(file: OrderFileItem) { + const raw = String(file?.fileUrl || '').trim(); + if (!raw) { Message.warning(t('order.myOrders.drawer.attachments.previewFailed')); return; } - const win = window.open(url, '_blank', 'noopener,noreferrer'); - if (!win) { - Message.warning(t('order.myOrders.drawer.attachments.previewBlocked')); + try { + await openPrintAssetInNewTab(raw, { + loadingTitle: t('order.myOrders.drawer.attachments.preview'), + }); + } catch (e: unknown) { + const msg = extractOmsRequestError(e) || (e instanceof Error ? e.message : ''); + if (/弹窗|popup|blocked/i.test(msg)) { + Message.warning(t('order.myOrders.drawer.attachments.previewBlocked')); + return; + } + Message.warning(msg || t('order.myOrders.drawer.attachments.previewFailed')); } } diff --git a/vite.config.ts b/vite.config.ts index 744c327..747ed73 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -276,7 +276,7 @@ export default defineConfig(({ mode }) => { changeOrigin: true, secure: false, }, - // 开发环境面单/截图:同源代理 COS(自定义域名 https 不可用,上游走 http) + // 仅开发:同源代理 COS(生产无 /__cos__,走 file-storage/stream) '/__cos__': { target: 'http://cos.ideship.com', changeOrigin: true, @@ -382,7 +382,7 @@ export default defineConfig(({ mode }) => { changeOrigin: true, secure: false, }, - // MCP 工具 / AI Agent(须与 nginx /mcp/ 反代一致) + // MCP 工具 / AI Agent(开发 Vite 反代;生产直连 api.*) '/mcp': { target: omsAdminTarget, changeOrigin: true,