视觉统一(Zen Minimal × Future Organic 白皮书语言,全站浅色单主题): - 登录页:品牌→由壹点·品牌中心,白纸底+发丝线+柔光光晕,黑按钮 - 作品过稿:深灰头→白皮书,返回链接→返回创意资产库 - 绩效考核:桌面+移动双头部换白皮书,标题改绩效考核,返回改链接 - 组织架构:BoardShell+页面 76 处调色板全量替换(#eef1f4→#fafafa 等) - admin 后台:MaterialsHeader(面包屑治理与设置),Tab 栏墨色高亮 - 深色主题废弃:globals.css 移除媒体查询,tailwind darkMode:'class' 不挂 .dark - 设计文档同步更新:AGENTS.md UI 基线/DESIGN.md/PRODUCT.md/README.md/design-qa.md 个人档案(原个人中心): - API 合并权威人员档案(getEmployeeByUserId → 花名/工号/部门/职位/M-P等级/主管/状态) - 页面白皮书化:权威档案卡(只读 HR 字段)+ 账号区(头像上传)+ 素材/收藏 顶栏平台化: - MaterialsHeader 双模式:首页(五板块导航+全套工具+菜单)/板块(面包屑) - 菜单重排:我的档案/绩效考核/作品过稿看板‖更新记录/提交反馈‖治理与设置‖退出 - 菜单头身份行:花名+职位·部门(权威档案) - 首页排序:01 创意资产库(全员)→ 02 组织管理(管理员)→ 03-05 规划中 - 素材库改名创意资产库,全站同步(索引/导航/卡片/按钮/页脚) - 排行榜换白皮书(白底发丝线+琥珀前三名) 构建修复: - tsconfig 排除 output/(本地审计临时文件不再卡类型检查) - 存储边界门禁豁免 output/(与 .dockerignore 对齐)
38 KiB
CLAUDE.md
本文件为 Claude Code (claude.ai/code) 提供项目代码工作的指导说明。
项目概述
由壹点3D素材 - 一个具有 Apple 风格 UI 设计的现代化 3D 素材共享平台。
技术栈:
- Next.js 14 (App Router) + React 18 + TypeScript
- NextAuth.js 身份认证(凭证登录 + 钉钉 OAuth)
- PostgreSQL + Prisma ORM
- MinIO S3(预览图)+ 本地存储(压缩包)
- Redis(上传会话管理)
- TailwindCSS 样式
开发命令
# 开发环境
npm run dev # 在 :3000 端口启动开发服务器
# 构建与部署
npm run build # 生成 Prisma 客户端 + Next.js 构建
npm start # 启动生产服务器
# 数据库
npm run prisma:generate # 生成 Prisma 客户端
npm run prisma:push # 推送数据库结构
npm run prisma:studio # 打开 Prisma Studio
# 测试
npm test # 运行 Jest 测试
npm run test:watch # 监视模式
npm run test:coverage # 覆盖率报告
# 代码检查
npm run lint # ESLint
网络架构
生产基础设施
物理服务器:
- 生产服务器:
192.168.58.20 - 构建服务器:
192.168.40.206(部署脚本内的固定值;旧文档中的192.168.42.134已不是部署入口) - 香港公网服务器:
103.118.245.6(FRP 入口 + Nginx 反向代理) - 上游报表库:
218.93.9.21(SQL Server 2019,人事与组织架构数据源)
公网访问架构:
- 域名:
sc.jiansun.vip(素材平台)、s3.jiansun.vip(MinIO) - 公网入口:香港服务器 103.118.245.6
- FRP 服务:
frps-jiansun-entry(端口 7002) - 生产服务器通过 FRPC 连接到香港服务器
- Nginx 反向代理:sc.jiansun.vip → 127.0.0.1:2321,s3.jiansun.vip → 127.0.0.1:2323
Docker 网络:
- 网络名称:
youyidian-3d_app-network - 驱动类型:bridge
- 连接的容器:
youyidian-3d-material(生产应用)gongxiangpan-redis(生产 Redis)youyidian-3d-shadow(影子环境应用)
⚠️ 生产实际部署形态(2026-08-14 现场核实,与旧文档不一致,以此为准)
CLAUDE.md 旧版记的容器名/端口映射与线上对不上,实际是蓝绿部署 + 独立 nginx 反代:
- 应用容器采用蓝绿双实例:
youyidian-3d-material-blue/youyidian-3d-material-green,同一时刻一个接流量、另一个待命回退。容器不直接映射宿主机端口(3000/tcp不外露)。 - 流量入口是一个独立的 local-proxy nginx(不是 TrueNAS 系统自带的那个),监听宿主机
4320:- 配置路径:
/mnt/PoolOne/apps/youyidian-3d/local-proxy/youyidian-3d-material/nginx.conf - 通过
upstream里的容器内网 IP(如172.19.0.3:3000)指向当前生效实例 - 切换蓝绿 = 改这个
upstreamIP +nginx -s reload,current-container文件记录当前指向
- 配置路径:
- 应用挂载:
/mnt/PoolOne/apps/youyidian-3d/uploads:/app/uploads(uploads 实际路径,注意不是部署脚本默认的/mnt/PoolOne/youyidian-uploads) - 应用连的数据库是
192.168.58.20:5432/lobechat(宿主机 5432 由gongxiangpan-postgres-1映射)
⚠️ 大文件分片上传 500 的一个基础设施坑(2026-08-14 已修)
现象:视频等大文件分片上传,chunk 0 就返回 500,前端报"上传块 0 失败,已重试多次"或"分片合并文件必须同时提供 uploadId",但应用容器日志里查不到该 chunk 请求——因为请求在 nginx 层就被拦下了。
根因:local-proxy nginx(以 nobody 运行)和 TrueNAS 系统 nginx(以 www-data 运行)共用同一个临时缓冲目录 /var/lib/nginx/body。当系统 nginx 重建该目录并锁成 www-data drwx------ 后,nobody 无法写入,10MB 的分片缓冲失败,nginx 直接回 500。nginx -s reload 或系统 nginx 重启都可能触发。
修复:给 local-proxy 配独立临时目录,不再和系统 nginx 抢。在 nginx.conf 的 http {} 块内加:
client_body_temp_path <proxy_dir>/tmp/client_body;
proxy_temp_path <proxy_dir>/tmp/proxy;
fastcgi_temp_path <proxy_dir>/tmp/fastcgi;
uwsgi_temp_path <proxy_dir>/tmp/uwsgi;
scgi_temp_path <proxy_dir>/tmp/scgi;
并 mkdir -p 这些目录、chown -R nobody:nogroup、chmod 700,再 reload。诊断时先看 local-proxy/.../error.log 有没有 open() "..." failed (13: Permission denied)。
上游报表库(人事与组织架构数据源)
⚠️ 重要认知:花名册不是从钉钉通讯录来的。钉钉在本项目中只负责登录鉴权与消息推送,从未调用过通讯录接口。人事权威源是公司报表库。
连接信息(只读账号,配置在 .env,勿写入仓库):
- Server:
218.93.9.21 - Database:
JSReportReplica - Schema:
rpt(只读) - 环境变量:
REPORT_DB_SERVER/REPORT_DB_DATABASE/REPORT_DB_USER/REPORT_DB_PASSWORD
⚠️ 密码含 # 等特殊字符时,在 .env 中必须加引号,否则 # 之后会被当作注释截断,表现为登录失败。
关键表:
| 表 | 行数量级 | 用途 |
|---|---|---|
rpt.hr_groups |
1075 | 组织架构树。仅 Id / parent_id / name 三列可用,无状态位、无排序、无编码。单一根节点(Id=1),零孤儿节点 |
rpt.hr_employee |
1296 | 钉钉员工表,含 manager_userid、dept_id_list、active |
rpt.hr_employee_register |
1296 | 入职登记,含 sys01-employeeStatus 状态码、sys00-flowerName 花名 |
rpt.hr_flowerName_book |
— | 完整员工档案(含社保、银行卡等敏感信息,谨慎查询) |
rpt.hr_employee_dimission |
4711 | 离职表,含 last_work_day、handover_userid 交接人 |
**员工状态码:2 试用、3 正式、5 已离职。**注意上游还存在中文值(正式/离职/试用)与空值,口径混杂,不可用关键词匹配判断在职。
人员架构与组织同步(2026-08-13 重构)
核心原则
钉钉唯一值是全项目唯一的人员身份判定依据。 姓名、花名、邮箱只能用于人工排错,不得自动建立绑定。
数据模型
| 模型 | 作用 |
|---|---|
UserEmployeeBinding |
素材库账号 ↔ 权威人员档案的正式绑定。活跃绑定双向唯一(部分唯一索引,仅约束 status='ACTIVE') |
OrganizationUnit |
共享组织架构,直接镜像 hr_groups。上游删除的部门置 isActive=false 而非物理删除 |
PerformanceSurveyEmployeeProfile |
事实上的平台级人员主档(表名仍带历史前缀)。新增 employmentState、resignedAt、handoverDingtalkUserId |
users.dingtalkUserId 已加唯一约束,数据库层面杜绝一人多号。
关键文件
| 文件 | 用途 |
|---|---|
lib/employee-directory.ts |
通用人员目录服务。上层统一通过它访问人员数据,不直接读带 PerformanceSurvey 前缀的模型 |
lib/organization-sync.ts |
组织架构同步、在职状态校正、离职联动、每日定时任务 |
lib/material-transfer.ts |
素材正式转移与部门托管 |
在职状态判定
不要用关键词匹配 employmentStatus(上游原样值,口径混杂)。使用规范化的 employmentState:
ACTIVE/RESIGNED/UNKNOWNUNKNOWN视为在职——上游查不到明确状态的人宁可多留,误判导致"人凭空消失"比"多出几个人"难发现- 判定条件统一走
getActivePerformanceSurveyEmployeeProfileWhere()或getActiveEmployeeWhere()
离职联动
上游标记离职后自动执行:归档素材库账号 + 把素材托管给所在部门。
- 默认只处理离职满 30 天者(
RESIGNATION_MIN_DAYS),避开交接期、返聘与上游数据延迟 - 离职日期缺失者暂缓,交人工核实
- 超级管理员账号不由自动流程处理
素材归属与署名分离
这是刻意的设计,不是数据不一致:
Material.userId(归属)+authorName(署名):素材是谁做的,永远记在谁名下Material.custodyDepartmentId(托管部门):人离职后由部门接手维护,不改 userId 与 authorName- 首页卡片显示的作者读的是
user.realName || username,托管不影响显示
定时同步
ensureDailyOrganizationSyncStarted() 在考核调研上下文中懒启动,每 24 小时执行:组织架构 → 在职状态 → 离职联动。
⚠️ 生产容器必须配置 REPORT_DB_* 环境变量,否则同步连不上上游,人员档案会退化成用素材库信息拼的兜底假档案(无工号、无职级、isLeadership=false)。
运维脚本
# 人员架构盘点(纯只读,可重复执行)
npx tsx scripts/diagnose-people-architecture.ts
# 组织架构与在职状态同步(默认预演,--apply 才写入)
npx tsx --env-file=.env scripts/sync-organization.ts
npx tsx --env-file=.env scripts/sync-organization.ts --apply
# 离职人员素材部门托管
npx tsx scripts/custody-archived-user-materials.ts
数据库配置
PostgreSQL (192.168.58.20:5432):
- 数据库用户:
gongxiangpan - 生产数据库:
lobechat(5918 条素材记录)✅ - 影子环境数据库:
lobechat_stage(4051 条素材记录) - 默认数据库:
gongxiangpan(旧系统,仅 4 个表,未使用)
⚠️ 重要提示:生产数据库名称是 lobechat,不是 gongxiangpan(那只是 PostgreSQL 用户名)
Redis 配置
⚠️ 【重要】Redis 连接依赖 Docker 网络配置
应用容器必须连接到 youyidian-3d_app-network 网络才能访问 Redis。如果 Redis 连接失败,会导致:
- 上传分片无法保存,上传到 100% 后循环重传
- 会话无法持久化,用户频繁掉线
正确的部署命令必须包含 --network 参数:
docker run -d --name youyidian-3d-material \
--network youyidian-3d_app-network \ # ⚠️ 必须指定网络!
-p 4320:3000 \
...
诊断 Redis 连接问题:
# 1. 检查容器是否在正确网络
docker network inspect youyidian-3d_app-network | grep youyidian-3d-material
# 2. 检查 Redis 连接
docker exec youyidian-3d-material sh -c "ping -c 2 gongxiangpan-redis"
# 3. 检查容器日志中的 Redis 状态
docker logs youyidian-3d-material 2>&1 | grep -i redis | tail -5
# 4. 手动连接到网络(如果遗漏)
docker network connect youyidian-3d_app-network youyidian-3d-material
docker restart youyidian-3d-material
生产环境 Redis:
- 容器名称:
gongxiangpan-redis - 连接地址:
redis://gongxiangpan-redis:6379(Docker 网络内) - 环境变量:
USE_REDIS=true、REDIS_URL=redis://gongxiangpan-redis:6379 - 会话 TTL:4 小时(14400 秒)
- 内存限制:128MB(maxmemory-policy: allkeys-lru)
- 无需密码
✅ 已验证:生产环境正在使用 Redis 存储上传会话(不是内存 Map 兜底方案)
- 验证方式:在 Redis 中找到 3 个活跃的上传会话
- 数据格式:
{"fileName": "...", "fileSize": N, "totalChunks": N, "chunks": [...], "createdAt": N, "userId": "..."}
开发环境 Redis:
- 连接地址:
redis://localhost:6379 - 兜底方案:内存 Map(Redis 不可用时)
- 启用方式:通过
USE_REDIS=true环境变量
MinIO/S3 存储配置
MinIO 服务器 (192.168.58.20:9000):
- 访问密钥:由服务器环境变量
S3_ACCESS_KEY_ID提供 - 秘密密钥:由服务器环境变量
S3_SECRET_ACCESS_KEY提供 - 存储桶:
gongxiangpan - 文件路径:
由壹点AI/
公网访问域名:
- URL:
https://s3.jiansun.vip/gongxiangpan - 展示缓存前缀:
/gongxiangpan/public-display/ - 预览链接过期时间:7200 秒(2 小时)
- 路径样式:已启用
上传配置
当前设置(截至 2026-01-27):
- 分片大小:50MB(内网/外网统一)
- 并发数:2-3 个并发上传
- 会话 TTL:4 小时(Redis)
- 超时时间:每个分片 5-10 分钟
容器端口映射
| 容器 | 内部端口 | 外部端口 | 用途 |
|---|---|---|---|
| youyidian-3d-material | 3000 | 4320 | 生产应用 |
| youyidian-3d-shadow | 3000 | 5320 | 影子环境应用 |
| gongxiangpan-redis | 6379 | - | 生产 Redis |
密码和密钥管理
⚠️ 安全原则:敏感信息存储在服务器上,不提交到 Git。
SSH 密钥配置
本地密钥位置:C:\Users\54972\.ssh
| 服务器 | IP地址 | SSH别名 | 密钥文件 | 用途 |
|---|---|---|---|---|
| 生产服务器 | 192.168.58.20 | prod |
youyidian_prod_ed25519 |
应用容器、数据库、Redis |
| 构建服务器 | 192.168.42.134 | build |
id_rsa_build_192.168.42.134 |
Docker镜像构建 |
| 香港公网服务器 | 103.118.245.6 | - | - | FRP 入口、Nginx 反向代理 |
| License服务器 | 122.51.149.232 | license-server |
id_ed25519_license_122.51.149.232 |
License服务 |
| 腾讯云服务器 | 45.249.208.118 | tencent |
tencent_server.pem |
腾讯云服务器 |
已废弃服务器:
- 旧镇江服务器 180.97.221.140(已封禁,不再使用)
未识别的密钥:
id_rsa/id_rsa.pub- 默认密钥(保留不动)flnxilicenseserver.pem- 可能是 license 服务器的备用密钥(PEM格式)
连接方式:
# 使用别名(推荐)
ssh prod # 生产服务器
ssh build # 构建服务器
ssh frp # FRP服务器
ssh tencent # 腾讯云服务器
# 使用IP
ssh 192.168.58.20
配置文件:C:\Users\54972\.ssh\config(已配置好 IdentityFile)
查看密码的方式
方式1:通过 SSH 查看环境变量
# 查看生产环境所有环境变量
ssh root@192.168.58.20 "docker exec youyidian-3d-material printenv | grep -E 'DATABASE|S3|REDIS|NEXTAUTH'"
# 查看特定密码
ssh root@192.168.58.20 "docker exec youyidian-3d-material printenv | grep DATABASE_URL"
ssh root@192.168.58.20 "docker exec youyidian-3d-material printenv | grep S3_"
方式2:查看 docker-compose.yml(服务器端)
# 生产服务器上的配置文件路径
ssh root@192.168.58.20 "cat /root/youyidian-3d/docker-compose.yml | grep -A 20 'environment:'"
方式3:查看 .env 文件(如果存在)
ssh root@192.168.58.20 "cat /root/youyidian-3d/.env 2>/dev/null || echo 'No .env file found'"
需要记录的敏感信息位置
| 信息类型 | 存储位置 | 查看命令 |
|---|---|---|
| 数据库密码 | 服务器环境变量 | docker exec youyidian-3d-material printenv | grep DATABASE_URL |
| MinIO/S3 密钥 | 服务器环境变量 | docker exec youyidian-3d-material printenv | grep S3_ |
| NextAuth 密钥 | 服务器环境变量 | docker exec youyidian-3d-material printenv | grep NEXTAUTH |
| 钉钉 App 密钥 | 代码中(已加密) | cat app/api/dingtalk/login/route.ts | grep DINGTALK |
| 管理员 API Key | 服务器环境变量 | docker exec youyidian-3d-material printenv | grep ADMIN_API_KEY |
密码变更记录
当密码变更时,在此处记录:
- 2026-01-27:初始记录,建立密码管理机制
- 2026-05-05:公网接入迁移到香港服务器 103.118.245.6
部署
当前默认使用 Git 增量部署,统一通过 scripts/git-incremental-deploy.ps1 执行:
# 预检
.\scripts\git-incremental-deploy.ps1 -DryRun
# 部署到生产环境
.\scripts\git-incremental-deploy.ps1
# 部署到影子环境
.\scripts\git-incremental-deploy.ps1 -Target shadow
当前部署流程:
- 源码先提交并推送到 Git 仓库
- 构建服务器 (192.168.40.206) 增量拉取指定提交
- 构建服务器构建 Docker 镜像并推送到本地镜像仓库 (192.168.40.206:5000)
- 生产服务器 (192.168.58.20) 拉取指定提交镜像
- 替换正式容器或影子容器
- 检查
/api/health,失败时默认尝试回滚旧镜像
⚠️ 部署前置条件(缺一不可):
脚本由维护人员执行,需要以下环境变量,且必须在内网环境(构建机 192.168.40.206 走公网穿透连不上,表现为 TCP 端口"可达"但 SSH 握手失败——穿透代理对任何连接都返回成功,端口连通性测试是假阳性):
$env:BUILD_SERVER_PASSWORD = '构建机密码' # 脚本用 plink -pw,不走 SSH 密钥
$env:BUILD_SERVER_HOST_KEY = 'SHA256:...' # 已人工核对的构建机指纹
$env:PROD_SERVER_HOST_KEY = 'SHA256:...' # 已人工核对的生产机指纹
已记录的主机指纹(来自 ~/.ssh/known_hosts,生产机已现场验证一致):
- 生产机 192.168.58.20:
SHA256:b0qukGVPuI8j+lykfCIplkcKWh7d4Pq9APzvr4FDnss - 构建机 192.168.40.206:
SHA256:MXW5w48SFSTUaqS+dS1G9hY228hI6SLepjhCmItHL04
正式部署必须显式指定 40 位提交 SHA,脚本不接受"分支最新提交":
.\scripts\git-incremental-deploy.ps1 -Commit '<40位小写提交SHA>'
deploy-remote-build.ps1 属于本地历史脚本名,仅作为识别应急资料使用,不是仓库交付入口,也不再作为默认部署入口。旧的“本机打包源码 -> 上传到构建机”链路不再作为当前标准口径。
详细部署规范见:
docs/Git增量部署方案.mddocs/部署规范.mddocs/部署脚本安全说明.md
架构
目录结构
app/
├── api/ # API 路由
│ ├── auth/[...nextauth]/ # NextAuth 配置
│ ├── materials/ # 素材增删改查、上传、下载
│ │ ├── upload/ # 上传端点(分片、完成、特定类型)
│ │ └── [id]/ # 单个素材操作
│ ├── admin/ # 管理员端点(用户、权限、统计)
│ ├── external/ # 外部 API 集成
│ └── user/ # 用户资料操作
├── [id]/share # 公开分享页面
├── admin/ # 管理员后台页面(素材库管理,旧壳待统一)
├── edit/[id]/ # 素材编辑页面
├── login/ # 登录页面
├── materials/ # 创意资产库(原素材库首页,2026-08-15 迁入)
├── organization/ # 组织管理(人员架构顶层页,2026-08-15 上移)
└── page.tsx # 品牌中心看板首页(登录落点,Zen×Organic 白皮书风)
components/
├── upload/ # 上传组件
│ ├── UploadModal.tsx # 主上传弹窗(通用上传)
│ ├── ProductPackageUploader.tsx # 产品全案
│ ├── TaskPageUploader.tsx # 任务-页面
│ ├── VideoScreenshotUploader.tsx # 任务-视频截图
│ ├── ThreeDOriginalUploader.tsx # 3D原图
│ ├── AIOriginalUploader.tsx # AI原图
│ ├── RealPhotoUploader.tsx # 实拍图
│ └── SpecialBatchUploadUI.tsx # 批量上传界面(音乐、视频、3D、品牌VI等)
├── admin/ # 管理后台组件
├── board/ # 看板壳组件(BoardShell、MaterialsHeader——新视觉语言)
├── home/ # 创意资产库(原首页)组件
└── detail/ # 详情页组件
lib/
├── chunk-upload.ts # 分片上传(IndexedDB 状态 + 重试)
├── chunk-upload-config.ts # 分片上传配置(50MB统一分片)
├── upload-sessions.ts # 内存上传会话 Map(开发环境)
├── upload-sessions-redis.ts # Redis 上传会话(生产环境)
├── task-material-processor.ts # 任务类型素材处理逻辑
├── material-validation.ts # 素材类型验证辅助函数
├── material-type-mapper.ts # 素材类型常量和映射器
├── pdf-processor.ts # PDF 处理(预览图提取)
├── image-processor.ts # 图片压缩和切片
├── video-processor.ts # 视频转码
├── audio-processor.ts # 音频元数据提取
├── psd-processor.ts # PSD 文件处理
├── s3.ts # MinIO S3 客户端
├── auth.ts # NextAuth 配置
├── prisma.ts # Prisma 客户端
└── network.ts # 网络检测和带宽监控
素材类型
平台支持多种素材类型,使用不同的上传流程:
| 类型 | 中文名称 | 上传流程 | 特殊要求 |
|---|---|---|---|
| 3D场景 | 3D Scene | 标准流程 | 渲染器 (CR/V5/FB) |
| 详情模板 | Detail Template | 展示图 + zip | PSD 切片(200KB 分块) |
| 模特 | Model | 仅模特图片 | 无需 zip |
| 标准模型 | Standard Model | 主图 + SKU | SKU 管理 + 白底图元数据 |
| 品牌VI灵感 | Brand VI | 仅 PDF | 自动从 PDF 第1页提取预览 |
| 证书-检测报告 | Certificate Report | 仅 PDF | 支持中文文件名 |
| 任务-详情 | Task-Detail | ProductPackageUploader | 主图/SKU/详情图/PSD |
| 任务-页面 | Task-Page | TaskPageUploader | 预览图 → zip |
| 任务-视频截图 | Task-Video | VideoScreenshotUploader | 截图 → zip |
| 淘系-主图视频 | Video (Main) | 批量上传 | 原名:主图视频 |
| 淘系-逛逛视频 | Video (Browse) | 批量上传 | 原名:种草视频 |
| 抖音-千川素材 | Douyin Qianchuan | 批量上传 | 新增类型 |
| 抖音-账号维护 | Douyin Account | 批量上传 | 新增类型 |
| 音乐/买家秀/小红书 | 特殊类型 | 批量上传 | 特定类型限制 |
视频类型改名说明(2026-04-30):
主图视频→淘系-主图视频种草视频→淘系-逛逛视频- 权限系统支持新旧名称兼容(
lib/video-material-types.ts、lib/department-permission.ts) - 数据库迁移:
prisma/migrations/20260430_200000_rename_video_material_types/migration.sql
身份认证与授权
用户角色:
USER- 普通用户ADMIN- 管理员(旧版,保留兼容性)DEPT_ADMIN- 部门管理员SYSTEM_ADMIN- 系统管理员isSuperAdmin- 超级管理员标记(最多4人)
钉钉集成:
- OAuth 流程:
/api/dingtalk/login - 回调地址:
/dingtalk-callback - 通过
dingtalkUserId关联用户 - 首次登录时创建/更新用户资料
会话管理:
- JWT 策略,30天过期
- Token 包含:
id、role、department、isSuperAdmin、avatar
部门权限:
DepartmentMaterialPermission表控制部门访问权限- 字段:
canView、canDownload、canShare、canDownloadSource - 通过
linkedToDepartment实现权限继承 - 视频类型权限支持新旧名称兼容(
lib/department-permission.ts)
标准模型 SKU 权限(2026-05-05):
- SKU 操作权限改为登录即可(不再限制作者/管理员)
- 白底图上传记录:出图方式(实拍/渲染/AI)、上传人、上传时间
- 白底图元数据字段:
whiteBackgroundSourceType、whiteBackgroundUploadedBy、whiteBackgroundUploadedAt - 历史数据回填:已有白底图默认标记为"渲染"
上传系统架构
分片上传流程(lib/chunk-upload.ts):
- 初始化 - 生成
uploadId,在 Redis/Map 中创建会话 - 分片上传 - 顺序上传分片,支持重试
- 统一分片大小:50MB(内网/外网统一)
- IndexedDB 持久化状态,支持断点续传
- 完成 - 服务端合并分片并处理
上传会话管理:
- 开发环境:
lib/upload-sessions.ts- 内存 Map - 生产环境:
lib/upload-sessions-redis.ts- Redis,4小时 TTL - 已完成会话:1小时 TTL
- 关键函数:
saveUploadSession、getUploadSession、updateChunk、setCompletedSessionTTL
特殊上传流程:
-
标准上传 (
/api/materials/upload)- 预览图 + 可选 zip
- 直接上传或分片上传(如果超过阈值)
-
任务-详情 (
ProductPackageUploader)/api/materials/upload/product-package/init- 上传内容:主图、SKU图、详情图、PSD 文件
lib/task-material-processor.ts处理(6个步骤)
-
任务-页面 (
TaskPageUploader)/api/materials/upload/task-page/init- 预览图 → zip 生成
-
品牌VI(PDF 上传)
lib/pdf-processor.ts提取第1页作为预览lib/pdf-upload-handler.ts处理上传
-
证书-检测报告(PDF 上传)
/api/materials/upload/certificate-report/init/api/materials/upload/certificate-report/complete- 支持中文文件名(使用 execFile 调用 pdftoppm)
- 核心提交成功后,收尾步骤失败不影响整体成功
文件处理流程
图片处理(lib/image-processor.ts):
- 预览压缩:最大 200KB,质量 85
- 详情切片:200KB 分块,带重叠
- 支持水印
- 使用 Sharp 库处理
视频处理(lib/video-processor.ts):
- canonical 源视频(生产机
UPLOAD_DIR/ uploads/NAS)→ 默认 720P H.265(HEVC)主预览 - 生成 10 秒 H.265 预览片段
- 提取封面
- 基于 FFmpeg 转码;同一应用实例最多 1 条任务、FFmpeg 默认 1 线程
- 任务由 PostgreSQL 租约持久化,启动时恢复并每 60 秒重扫;Redis 只用于上传会话等业务,不承担视频队列
- 上传建档后异步经历
pending → processing → done/failed
音频处理(lib/audio-processor.ts):
- 元数据提取(艺术家、专辑、时长)
- 支持格式:MP3、FLAC、M4A、AAC、OGG、WAV
PSD 处理(lib/psd-processor.ts):
- 图层提取
- 使用 ag-psd 生成预览
API 集成
外部 API(lib/external-api.ts):
/api/external/download- 通过 Token 验证下载/api/external/product-assets- 任务系统批量资产下载/api/external/dingtalk-material-match- 钉钉素材匹配接口/api/external/dingtalk-material-options- 钉钉下拉选项接口/api/external/standard-model-sku-white-background- 标准模型 SKU 白底图元数据查询
钉钉接口集成(2026-05-05):
- 支持两种鉴权方式:URL 参数
apiKey或请求头X-API-Key - 钉钉低代码流程推荐使用 URL 参数方式
- 程序系统对接推荐使用请求头方式
- 字段映射:需求类型→素材类型、品牌→店铺、场景类别、产品类别、SPU
- 文档:
docs/钉钉素材库接口对接文档.md
API Key 系统:
ApiKey表用于外部系统身份认证ApiCallLog跟踪所有 API 调用- 通过
x-api-key请求头或ADMIN_API_KEY环境变量验证 - 钉钉接口额外支持 URL 参数
apiKey(lib/external-api.ts中allowQuery: true)
性能优化
分片配置(lib/chunk-upload-config.ts):
- 统一使用 50MB 分片(内网/外网一致)
- 动态超时时间:根据分片大小自动计算(300KB/s 最低速度)
- 并发控制:2-3 个并发(内网3、外网2)
- 请求数减少:1GB 文件从 100 个请求减少到 20 个(80% 减少)
Redis 优化:
- 会话 TTL:4小时(活跃中)、1小时(已完成)
- 连接池,带重试策略
- 离线队列,增强连接弹性
内存监控(lib/memory-monitor.ts):
- 跟踪上传期间的堆使用
- 流式合并,避免 Buffer.concat() 溢出
重要文件位置
| 文件 | 用途 |
|---|---|
prisma/schema.prisma |
数据库结构(所有模型) |
lib/material-validation.ts |
类型特定验证规则 |
lib/material-type-mapper.ts |
素材类型常量和映射器 |
lib/video-material-types.ts |
视频类型定义和新旧兼容 |
lib/department-permission.ts |
部门权限判断(支持视频类型兼容) |
lib/task-material-processor.ts |
任务类型处理流程(6个步骤) |
lib/chunk-upload.ts |
核心分片上传逻辑 |
lib/pdf-processor.ts |
PDF 处理(支持中文文件名) |
lib/external-api.ts |
外部 API 鉴权(支持 URL 参数) |
lib/standard-model-white-background-metadata.ts |
标准模型白底图元数据 |
middleware.ts |
请求拦截和认证检查 |
app/api/materials/upload/route.ts |
旧版上传端点 |
app/api/materials/upload/chunk/route.ts |
分片上传处理 |
app/api/materials/upload/certificate-report/complete/route.ts |
证书报告上传完成 |
components/HeaderTaobaoSearch.tsx |
标题栏淘宝 ID 搜索框 |
components/SkuEditModal.tsx |
SKU 编辑弹窗(白底图上传) |
components/SkuList.tsx |
SKU 列表(白底图元数据显示) |
docs/钉钉素材库接口对接文档.md |
钉钉接口对接文档 |
环境变量
.env 中必需的配置:
DATABASE_URL=postgresql://...
NEXTAUTH_SECRET=...
NEXTAUTH_URL=http://localhost:3000
# S3/MinIO
S3_ENDPOINT=...
S3_BUCKET=...
S3_ACCESS_KEY_ID=...
S3_SECRET_ACCESS_KEY=...
# Redis(生产环境)
REDIS_URL=redis://localhost:6379
# 管理员
ADMIN_API_KEY=...
# 钉钉(可选)
DINGTALK_APP_KEY=...
DINGTALK_APP_SECRET=...
常用模式
API 路由认证:
import { getServerSession } from 'next-auth'
import { authOptions } from '@/lib/auth'
const session = await getServerSession(authOptions)
if (!session?.user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
Prisma 事务:
await prisma.$transaction(async (tx) => {
const material = await tx.material.create({ ... })
await tx.materialAsset.createMany({ ... })
return material
})
错误处理:
import { parseApiError } from '@/lib/error-handler'
try { ... }
catch (error) {
const { message } = parseApiError(error)
return NextResponse.json({ error: message }, { status: 500 })
}
已知问题和注意事项
- 内存溢出 - 在
upload-sessions.ts中使用流式合并,不要用Buffer.concat() - 竞态条件 - 处理过程中避免重复更新
previewUrl - 软删除 - 素材有
deletedAt字段 - 始终过滤掉这些记录 - 任务号格式 - 格式:
TASK+ 12-18位数字 + 可选的-XX后缀 - 预览生成 - 上传先完成建档并进入
pending,预览由后台异步生成;三类产物完整且租约仍有效才是done,失败写入failed - Docker 网络 - 容器必须在
youyidian-3d_app-network上才能访问 Redis - 视频类型权限 - 视频类型改名后需同步更新部门权限表,或依赖代码兼容逻辑
- PDF 中文文件名 - 必须使用
execFile而非exec调用 pdftoppm,避免路径注入 - 部署版本管理 - 不同对话中的改动需合并到同一工作区再部署,避免"回退"现象
- 构建缓存 - 默认保留构建缓存加快构建,只在必要时使用
-AggressiveBuildCacheCleanup - Alpine 镜像源 - 使用清华源,避免官方源在国内网络环境下的连接问题
- 音频/文件访问路由 -
.dockerignore和部署脚本中排除uploads时要精确,避免误伤app/api/uploads路由 - 人员身份判定 - 只认钉钉唯一值。钉钉登录遇到同名账号会拒绝并转人工,不再自动合并——这是有意的摩擦,防止重名变成越权
- 在职状态 - 不要用关键词匹配
employmentStatus,用employmentState。上游有数字码、中文、空值三种口径并存 - 素材署名与归属分离 - 署名和归属对不上通常是正确的(素材转过手,署名保留原作者),不要"修正"成一致
Material.user是级联删除 - 一旦有代码真的删除 User,会连带硬删其全部素材。目前全项目无硬删除调用,靠"没人碰"撑着而非数据库拦着;引入离职下线流程前必须先改掉- 上游连接密码含特殊字符 -
.env中含#的值必须加引号,否则被当注释截断
最近修复的问题(2026-04-30 至 2026-05-05)
- 证书-检测报告中文 PDF 上传失败 - 修复
lib/pdf-processor.ts路径校验,支持中文文件名 - DNZ032912 视频素材"看不见" - 视频类型改名后权限表未同步,已补齐权限并增加代码兼容
- 标准模型 SKU 白底图缺少时间显示 - 新增元数据字段并回填历史数据(2730 条)
- 钉钉接口需要手动输入请求头 - 增加 URL 参数
apiKey支持,适配钉钉低代码流程 - 音频素材播放 404 - 修复
.dockerignore和部署脚本,避免误排除app/api/uploads路由 - 部署后小改动重装基础包 - 优化构建缓存策略,默认保留缓存
- Alpine 镜像源连接失败 - 从官方源改为清华源
测试状态
- Jest 已配置,测试覆盖关键业务逻辑
- 重要测试文件:
lib/__tests__/video-material-types.test.ts- 视频类型兼容测试lib/__tests__/external-api-key.test.ts- 外部 API 鉴权测试lib/__tests__/dingtalk-material-match-route.test.ts- 钉钉匹配接口测试lib/__tests__/dingtalk-material-options-route.test.ts- 钉钉选项接口测试
- 推荐通过影子环境部署进行手动测试
- 使用
/api/admin/system-status进行健康检查
数据库维护记录
2026-04-30:视频类型改名
- 执行迁移:
prisma/migrations/20260430_200000_rename_video_material_types/migration.sql - 手动补齐部门权限:复制 6 条"主图视频"权限到"淘系-主图视频",复制 6 条"种草视频"权限到"淘系-逛逛视频"
2026-05-05:标准模型白底图元数据
- 执行迁移:
prisma/migrations/20260430_add_standard_model_white_background_metadata/migration.sql - 历史数据回填:2730 条 SKU 的
white_background_uploaded_at字段(使用资产最后创建时间) - 历史数据默认值:
white_background_source_type统一回填为"渲染"
2026-08-13:人员架构统一到钉钉唯一值
执行了三个迁移(均已在生产库 lobechat 应用):
20260813_add_user_employee_binding:绑定表 +users.dingtalkUserId唯一约束20260813_add_material_custody_department:素材托管部门20260813_add_organization_units_and_employment_state:组织架构表 + 规范化在职状态
数据操作结果:
- 建立 503 条账号↔人员绑定,覆盖活跃账号的 96.3%(余下 18 个无钉钉 ID,需人工确认)
- 同步 1075 个部门,单根树 5 层,零孤儿节点
- 校正在职状态:1285 在职 / 219 离职 / 214 未知(此前 216 条已离职档案中有 215 条被误判为在职)
- 离职联动:归档 139 个账号(离职满 30 天者),39 人暂缓
- 素材部门托管:3775 条,孤儿素材清零
- 回填 1807 条被误存为邮箱的作者名(全部来自任务-详情入口的历史缺陷)
- 账号治理:处置 1 个"组织上查无此人"的 ADMIN 账号(素材移交后撤权归档)
👤 开发者偏好
⚠️ 沟通偏好
语言:
- 全中文环境:回复、注释、文档、变量命名、思考过程、Git message
- 表达方式:多用自然语言,少用代码堆砌
- 用户背景:非程序员,侧重日常语言解释
回复风格:
- 少代码多文字
- 必要时用少量代码示例
- 优先自然语言说明
🧠 思维方式
方案分析:
- 对比不同方案的优劣
- 用数字和星级量化评价
- 结构化输出,用 emoji 标记章节
问题解决:
- 根因分析:不满足表面修复,深入排查底层原因
- 验证导向:修改后必须验证效果
架构思维:
- 优先考虑稳定性和可扩展性
- 模块化设计,构建稳定基础
- 统一 UI 组件和页面风格
- 抽取通用工具函数和样式
- 减少重复代码,降低维护成本
💼 工作风格
代码原则:
- 不写不重要的文档
- 保持代码简洁,避免过度工程化
- 优先使用现有组件和方案
垃圾文件清理(开发时即时清理,不留尾巴):
- 更新脚本/文档时删除旧版本
- 测试完成后删除临时文件
- 一次性脚本用完即删
- 用 Git 管理版本,不创建备份文件
- 清理无用的会话数据、缓存、临时文件
执行偏好:
- 能自动执行的不让用户手动操作
- 能批量处理的不让用户逐个处理
⚡ 性能意识
内存:
- 避免内存泄漏(事件监听器、定时器、闭包)
- 优化缓存策略(TTL、上限控制)
- 流式处理大文件,避免全量加载
🔒 数据安全
- 绝不删除重要数据目录
- 修改前备份数据
📋 工作习惯
Git:
- 只有明确要求时才创建 commit
- Commit message 使用中文,简洁明了
文件操作:
- 修改前先读取文件内容
- 创建新文件前检查是否已存在
🚨 重要提醒
工作区状态:
- 工作区长期存在大量未提交改动,这是正常状态
- 不同对话中的改动需要合并到同一工作区再部署
- 部署脚本只打包当前磁盘工作区,不会自动合并其它对话的改动
- 高敏感文件(如
components/HeaderTaobaoSearch.tsx)曾多次被"回退",根因是未合并工作区
部署前检查清单:
- 确认所有相关改动都在当前工作区
- 检查
.dockerignore和部署脚本的排除规则 - 验证 Alpine 镜像源配置(清华源)
- 确认构建缓存策略(默认保留)
- 检查 Docker 网络配置(
youyidian-3d_app-network)
数据库操作原则:
- 生产数据库名称是
lobechat,不是gongxiangpan - 涉及权限、素材类型、用户数据的变更需谨慎
- 大批量数据回填前先在影子环境验证
- 保持迁移文件和手动操作的记录
外部接口注意事项:
- 钉钉接口使用 URL 参数
apiKey,不要做成用户填写字段 - API Key 不要在日志中明文输出
- 外部接口调用需记录到
ApiCallLog表