diff --git a/docs/团队对接-TOS与短信登录.md b/docs/团队对接-TOS与短信登录.md new file mode 100644 index 0000000..1df55aa --- /dev/null +++ b/docs/团队对接-TOS与短信登录.md @@ -0,0 +1,247 @@ +# 团队对接 · TOS 资源桶 + 阿里云短信登录 + +> 这是给后端 / 运维同学的对接清单。把每一项的「需要回填」补齐后,原文回给前端(Claude)就能直接接入。 +> 文件里所有变量名都跟 `.env.example` 对齐,回填时直接写值即可。 + +--- + +## A. 火山引擎 TOS · 静态资源 + +### A.1 目标 + +把 36 位艺人的立绘 / 氛围图 / 三视图 / 表演视频 + Hero PV 全部走 TOS + CDN 引用。 +当前本地 ~1GB 资源已 `.gitignore`,**不入 git**。 + +### A.2 桶配置建议 + +| 项 | 建议值 | 备注 | +|---|---|---| +| 桶名 | `cyber-star` | 名字仅用于服务端 SDK,前端只看域名 | +| 区域 | `cn-beijing` 或就近 | 跟用户群体所在区域一致 | +| 读权限 | **公共读 (Public Read)** | 投票网站资源全部对外可访问,不需要签名 URL | +| 写权限 | 私有 | 仅服务端 / 运维通过 AK/SK 写入 | +| CDN 加速 | **强烈建议开启** | 火山 TOS 可直接挂火山 CDN,全国边缘加速;不开 CDN 时直接走桶域名也行,只是速度差一些 | +| 防盗链 | 可选 | 起步阶段不限制;后期可加 Referer 白名单 `*.airlabs.art` | +| HTTPS | **必须** | 网站走 HTTPS,资源也必须是 HTTPS,否则浏览器拦截 | + +### A.3 桶内目录结构(请保持一致) + +``` +cyber-star/ +├── portraits/ +│ ├── 001.png # 立绘(卡片用,半身) +│ ├── 001-2.png # 氛围图 2 +│ ├── 001-3.png # 氛围图 3 +│ ├── 001-view.png # 三视图(详情页备用,可暂不上传) +│ ├── 002.png +│ ├── ... +│ └── 036-3.png +└── videos/ + ├── hero-pv.mp4 # 首页 Hero 背景 PV + └── artists/ + ├── 001.mp4 + ├── 002.mp4 + ├── 004.mp4 # 003 / 010 / 017 / 027 暂时缺,先不传 + └── ... +``` + +> 路径与本地 `public/portraits/` `public/videos/` 完全对齐。前端代码里只需要把 `/portraits/...` 替换为 `${TOS_DOMAIN}/portraits/...`。 + +### A.4 前端要的信息(**必填**) + +```yaml +# ── 公共域名(前端代码引用资源用,必须公网 HTTPS 可达)── +NEXT_PUBLIC_TOS_DOMAIN: "" # 例:https://cyber-star.tos-cn-beijing.volces.com + # 或挂 CDN 后:https://cdn.cyber-star.airlabs.art + +# ── 是否启用公共读 ── +PUBLIC_READ: "" # yes / no(若 no 请说明签名策略) + +# ── CDN 是否启用 + 加速域名 ── +CDN_ENABLED: "" # yes / no +CDN_DOMAIN: "" # 启用 CDN 时填这个,前端用这个域名而不是桶原域名 +``` + +### A.5 服务端要的信息(**仅当后端要写桶 / 头像直传时需要**,前端暂时不需要) + +> ⚠️ AK/SK 是高敏感信息,**不要发到聊天里**。请用密钥管理工具同步给运维 / CI 环境变量,或者通过加密渠道(飞书加密文件 / 1Password)给。下面只是占位提醒。 + +```yaml +TOS_ENDPOINT: "" # tos-cn-beijing.volces.com +TOS_REGION: "" # cn-beijing +TOS_BUCKET: "" # cyber-star +TOS_ACCESS_KEY: "" # 走密钥管理,不写明文 +TOS_SECRET_KEY: "" # 走密钥管理,不写明文 +``` + +### A.6 上传分工 + +- [ ] **前端**(Claude)负责压缩。把 `public/portraits/` 和 `public/videos/` 用 sharp + ffmpeg 批量转换,输出到 `assets-compressed/`,保持 A.3 的目录结构。压缩目标: + - 立绘 PNG → WebP,目标单张 ≤ 800KB(卡片清晰度足够) + - 三视图 PNG → WebP,目标单张 ≤ 1.5MB + - Hero PV mp4 → H.264 1080p,目标 ≤ 15MB + - 单人 solo mp4 → H.264 1080p,目标单条 ≤ 3MB +- [ ] **运维 / 后端** 拿到 `assets-compressed/` 后用 `tosutil` 整目录上传到桶根: + ```bash + tosutil cp -r -f assets-compressed/* tos://cyber-star/ + ``` +- [ ] **前端**(Claude)回填 `NEXT_PUBLIC_TOS_DOMAIN` 进 `.env`,封装 `tosUrl(path)` 工具函数,把代码里所有 `/portraits/...` `/videos/...` 改成走该函数。 + +### A.7 验收 + +- [ ] 浏览器直接打开 `${NEXT_PUBLIC_TOS_DOMAIN}/portraits/001.png` 能看到图 +- [ ] 同样地址在 https 下不报 mixed-content +- [ ] 网站首页加载,Network 面板看到立绘从 TOS 域名下载,状态 200 + +--- + +## B. 阿里云短信服务 · 手机号 OTP 登录 + +### B.1 目标 + +`/api/auth/send-otp` 现在只在控制台打 log,需要接真实短信通道,给用户发 6 位验证码完成手机号登录。 +全国大陆手机号,5 分钟内有效,单号码 60 秒限频(前端已做)。 + +### B.2 阿里云控制台需要申请的东西 + +按这个顺序在阿里云控制台 → **短信服务** 申请: + +#### B.2.1 短信签名 + +- **签名内容**:`Cyber Star` 或 `虚拟偶像出道企划` 或公司主体相关 +- **签名类型**:通用 +- **使用场景**:验证码(推荐)/ 网站会员注册 +- **审核材料**:网站备案截图 / 公司营业执照(看运营那边怎么对接) +- **审核时长**:1-2 工作日 + +#### B.2.2 短信模板 + +- **模板类型**:验证码 +- **模板内容**(必须严格使用 `${code}` 占位符): + ``` + 您的验证码是 ${code},5 分钟内有效,请勿泄露。 + ``` +- **审核时长**:1-2 工作日 +- 申请通过后会拿到一个 `SMS_xxxxxxxx` 的模板 Code + +#### B.2.3 RAM 子账号 + AccessKey + +- 在阿里云 RAM 创建子账号 `cyber-star-sms`,授予 `AliyunDysmsFullAccess` 权限 +- 生成 AccessKey ID + Secret(一次性显示,存好) + +### B.3 前端要的信息(**必填**) + +```yaml +# ── 短信签名与模板(公开信息,可以聊天里给)── +SMS_SIGN_NAME: "" # 例:Cyber Star +SMS_TEMPLATE_CODE: "" # 例:SMS_298765432 + +# ── 接入信息(敏感,走密钥管理)── +SMS_ACCESS_KEY: "" # 阿里云 RAM 子账号 AK +SMS_SECRET_KEY: "" # 阿里云 RAM 子账号 SK +SMS_REGION: "" # 默认 cn-hangzhou,国内一般不变 +``` + +### B.4 上下游依赖(**必填,缺一个都跑不起来**) + +#### B.4.1 Redis + +```yaml +REDIS_URL: "" # redis://default:PASSWORD@host:port + # 用途:① OTP 验证码 5min TTL 存储 ② 限流计数(手机号 60s/IP 5min 等) +``` + +如果还没有 Redis 实例,可以先用火山 Redis(同地域低延迟)或自建。**最低规格够用**,QPS 不大。 + +#### B.4.2 MySQL + +```yaml +DATABASE_URL: "" # mysql://user:pwd@host:port/db?charset=utf8mb4 + # 用途:用户表 / 投票记录 / 应援关系 / 每日额度 +``` + +- 火山 RDS for MySQL 8 或自建均可 +- 第一次部署需要跑: + ```bash + npx prisma migrate deploy + npx prisma db seed # 灌 36 位艺人 + ActivityConfig(活动开关 / 起止时间 / 每日额度) + ``` + +#### B.4.3 NextAuth + +```yaml +AUTH_SECRET: "" # openssl rand -base64 32 生成 +AUTH_URL: "" # 例:https://cyber-star.airlabs.art(生产域名,不含末尾斜杠) +``` + +### B.5 后端要做的代码改动(仅 1 处,~10 行) + +`src/app/api/auth/send-otp/route.ts` 第 41-53 行现在长这样: + +```ts +const redis = getRedis(); +if (redis) { + await redis.set(`sms:otp:${phone}`, code, "EX", 300); +} + +// TODO[团队]: 接入真实短信服务(阿里云 / 火山引擎 SMS) +if (process.env.NODE_ENV !== "production") { + console.log(`[dev-otp] 发送给 ${phone}: ${code}`); +} +``` + +要改成调阿里云 SDK 真实发短信。**这一步前端(Claude)可以代写**,需要后端确认接 SDK 用 `@alicloud/dysmsapi20170525` 还是 `@alicloud/pop-core`,或者后端自己写也行。 + +### B.6 验收 + +- [ ] 真实手机号点「获取验证码」,30 秒内收到短信,内容包含 6 位数字 +- [ ] 验证码填回去能登录成功,导航栏显示头像 +- [ ] 60 秒内对同一手机号再次请求,接口返回 429(限流生效) +- [ ] OTP 5 分钟后失效 + +--- + +## C. 回填模板(**直接复制这一块改值,整段返回给前端**) + +```yaml +# ───── A. TOS 资源桶 ───── +NEXT_PUBLIC_TOS_DOMAIN: "" +PUBLIC_READ: "" +CDN_ENABLED: "" +CDN_DOMAIN: "" + +# ───── B. 阿里云短信 ───── +SMS_SIGN_NAME: "" +SMS_TEMPLATE_CODE: "" +SMS_REGION: "" + +# ───── B.4 基础依赖 ───── +REDIS_URL: "" +DATABASE_URL: "" +AUTH_SECRET: "" +AUTH_URL: "" + +# ───── 备注(其他需要前端知道的事情)───── +# 例如:访问令牌怎么续期、桶有没有 Referer 白名单、SMS 模板审核状态等 +NOTES: | + +``` + +**敏感信息(AK/SK)请单独走密钥管理或加密渠道,不要写在这个文件里。** + +--- + +## D. 时间线建议 + +``` +Day 0 (今天) ─ 后端 / 运维拿到这份文档,开始走流程 +Day 1 ─ 阿里云提交签名 / 模板审核(等 1-2 天) + ─ 创建 TOS 桶 + CDN 域名 + ─ 部署 MySQL / Redis 实例 +Day 1-3 ─ 前端这边:本地压缩 1GB 资源 → 输出到 assets-compressed/ + ─ 前端代码适配 NEXT_PUBLIC_TOS_DOMAIN 占位 +Day 2-3 ─ 短信审核通过 → 回填模板 + AK + ─ 资源上传到 TOS +Day 3 ─ 联调:真实手机号收验证码,登录,投票,资源走 CDN +Day 4 ─ 上线 +```