跳到主要内容

Swee-项目技术评审与改进建议

基于对 Swee 全栈代码的深度审查(swee-mobile、swee-admin、swee-server-nest、swee-site、swee-public),从工程质量、架构设计、性能、安全、可维护性等维度进行全面评估。


一、做得好的地方

1.1 VTable 大规模多语言编辑方案

核心亮点

VTable Canvas 渲染方案是整个项目最具工程思考的技术决策,将多语言文案编辑性能从 ~1K 行瓶颈提升到 10K+ 行流畅,是典型的「以技术手段突破业务瓶颈」的优秀案例。

  • V1→V2 技术演进是整个项目最具工程思考的决策——从 EditableProTable(DOM 渲染,~1K 行瓶颈)升级到 VTable(Canvas 渲染,10K+ 行流畅),完美解决了数万行 × 8 语言的文案编辑性能问题
  • 自定义 TextAreaEditorWithValidator 实现单元格级验证 + 三色状态标记(新增/编辑/错误),用户体验好
  • React in Canvas(通过 VTable Group + react 属性注入操作按钮)是一种创新的技术融合
  • 微软翻译器集成 + 字符超长检测,降低运营人员的翻译负担

1.2 NestJS 后端架构规范

  • 模块化设计清晰:Copywriting、File、Setting、Material、Task 五个业务模块职责分明
  • 全局组件完备:AllExceptionsFilter(异常过滤)、ResponseInterceptor(响应格式化)、ValidationPipe(参数验证)、TraceMiddleware(链路追踪)
  • DTO + class-validator 装饰器实现参数校验,每个 DTO 都有 @ApiProperty Swagger 文档标注
  • CacheInterceptor 按路由粒度控制缓存,设计合理

1.3 配置管理的可视化编辑

  • 智能类型推断:根据 JSON 值内容自动识别图片/视频/音频 URL,动态生成对应的预览+上传组件,运营人员无需手动配置列类型
  • JSONEditor 6 种编辑模式(tree/view/form/code/text/preview),覆盖不同复杂度的配置场景
  • 版本回退:支持配置回退到任意历史版本,避免误操作造成线上事故

1.4 埋点事件管理系统

  • 三层嵌套编辑(ModalForm → EditableProTable → 嵌套 EditableProTable)实现复杂度高但用户体验流畅
  • 动态列生成让上报数据查看自适应不同事件结构
  • @swee/codegen 集成实现埋点代码自动生成,减少开发者手动编写工作量

1.5 PWA 配置(swee-mobile)

  • next-pwa 正确配置了 Service Worker 自动注册和跳过等待
  • manifest.json 配置了 standalone 模式、深色主题、应用图标和安装截图
  • 支持多入口(App WebView + PWA 独立模式 + 移动浏览器)

1.6 Zustand 状态管理(swee-mobile)

  • 选择 Zustand 5.0 而非更重的 Redux,适合移动端 PWA 的轻量需求
  • IM 用户信息缓存使用 Zustand Store,避免频繁 API 请求

1.7 素材管理系统

  • 文件夹栈导航(folders[] 数组)实现面包屑式多级目录浏览,交互自然
  • 文件类型智能识别 + 对应图标渲染
  • .txt 文件支持远程获取 → 在线编辑 → Blob 构造 → 上传替换,运营效率高

1.8 财务精确计算

  • BigNumber.js 处理薪资计算,避免浮点精度问题
  • 三维度薪资状态管理(创作奖励/分成状态/分成奖励),状态机设计完整
  • Excel 导出采用 Blob 响应 + 客户端下载

1.9 AI 驱动的运营工具

  • AI 剧情自动生成 + 智能封面生成,useRequest + pollingInterval 实现优雅的异步轮询
  • AI 人设标签系统支撑 AI 伴侣的个性化配置
  • 新闻聚合定时任务(9 大新闻源 + Levenshtein 去重 + 飞书推送)

1.10 其他亮点

  • swee-admin TypeScript 配置了 strict: true
  • 三级权限控制(路由级 → 组件级 → API 级)
  • OSS 直传 + STS 临时授权 + MD5 内容寻址
  • 操作审计日志(前后 JSON 对比 + 操作人/IP/时间)
  • Docker 多阶段构建 + Kustomize K8s 多环境部署
  • Framer Motion 页面转场动画(swee-mobile)
  • Sentry 三端监控配置(Client + Server + Edge,虽然被注释)

二、改进建议

2.1 【紧急】生产环境密钥明文硬编码在代码库中

最高优先级安全风险

这是整个 Swee 项目最严重的安全问题。生产环境密钥已提交到 Git 历史中,即使删除文件内容,历史记录中仍可查看。必须立即轮换所有已泄露的凭证

多个配置文件包含明文凭证并已提交到 Git 仓库。

文件泄露内容
swee-server-nest/src/config/production.config.ts生产数据库密码、Lark App Secret
swee-server-nest/src/config/common.config.ts阿里云 OSS AccessKeyId + AccessKeySecret、微软翻译 API Key、Lark 测试 App Secret、爬虫服务密码
swee-server-nest/src/config/test.config.ts测试数据库密码

风险:任何有代码读取权限的人都可以直接访问生产数据库、操作阿里云 OSS 存储桶、调用微软翻译 API。

建议

第一步:立即轮换所有已泄露的凭证(因为密钥已提交到 Git 历史,即使现在删除文件内容,历史记录中仍可查看)

第二步:将密钥从源码迁移到环境变量

当前的问题根源在于:production.config.tscommon.config.tstest.config.ts 这些 .ts 文件是被 Git 追踪的源码文件,密钥写在里面就等于提交到了仓库。正确做法是将密钥读取改为环境变量:

swee-server-nest/src/config/production.config.ts
// ❌ 当前做法 — 密钥硬编码在 .ts 源码中,会被 Git 追踪
export default {
database: {
password: 'xxx_real_password_xxx',
},
};

// ✅ 正确做法 — 从环境变量读取,源码中不包含任何真实密钥
export default {
database: {
password: process.env.DB_PASSWORD,
},
};

使用 @nestjs/config 模块的 ConfigModule.forRoot() 会自动加载项目根目录下的 .env 文件到 process.env

第三步:.env 文件本身绝对不能提交到 Git

关键区别:

  • .env — 包含真实密钥的文件,必须加入 .gitignore,不提交到 Git
  • .env.example — 只包含占位符的模板文件,提交到 Git,供团队成员参考
.gitignore
# .gitignore 中添加
.env
.env.local
.env.production
.env.example
# .env.example(提交到 Git,作为模板)
DB_PASSWORD=<请填写数据库密码>
OSS_ACCESS_KEY_ID=<请填写阿里云 AccessKeyId>
OSS_ACCESS_KEY_SECRET=<请填写阿里云 AccessKeySecret>
MS_TRANSLATOR_KEY=<请填写微软翻译 API Key>
LARK_APP_SECRET=<请填写飞书 App Secret>

第四步:不同环境的密钥注入方式

环境注入方式说明
本地开发复制 .env.example.env,填入真实值.env 已被 .gitignore 忽略,不会提交
CI/CD 流水线GitHub Secrets / GitLab CI Variables在流水线配置中设置环境变量,构建时自动注入
K8s 生产环境kubectl create secret 或挂载 ConfigMap通过 K8s Secrets 注入 Pod 的环境变量
更高安全要求阿里云 KMS / HashiCorp Vault运行时动态拉取密钥,密钥自动轮换

第五步:防止未来密钥泄露

  • 安装 gitleaksgit-secrets 作为 pre-commit hook,在提交时自动扫描是否包含密钥
  • 如需彻底清除 Git 历史中的密钥,可使用 git filter-branchBFG Repo-Cleaner

2.2 【紧急】生产日志泄露用户 Access Token

现状

文件行号问题
auth-admin.guard.ts24console.log(`accessToken: ${accessToken}`)
auth-user.guard.ts18console.log(`accessToken: ${accessToken}`)
response.interceptor.ts30每个请求的 authorization header 都被记录

风险:用户的 access token 被记录到服务器日志中,日志系统泄露等同于 session 劫持。

建议

  • 立即移除 guard 中的 token 日志
  • response.interceptor.ts 中将 authorization 字段脱敏(仅记录前 8 位 + ***
  • console.log 替换为 NestJS 内置 Logger,按级别过滤

2.3 【紧急】CORS 配置接受所有来源

现状main.ts 第 9 行 cors: true 允许任意域名发起跨域请求。

风险:攻击者可以从任意域名发起请求,结合 Cookie 认证可导致 CSRF 攻击。

建议

swee-server-nest/src/main.ts
app.enableCors({
origin: ['https://admin.swee.com', 'https://swee.com', 'https://m.swee.com'],
credentials: true,
});

2.4 【紧急】缺少 HTTP 安全头(Helmet)

现状:后端未使用 helmet 中间件,缺少 X-Content-Type-OptionsX-Frame-OptionsContent-Security-Policy 等安全响应头。

建议pnpm add helmet 并在 main.tsapp.use(helmet())

2.5 【紧急】硬编码 Token 用于 API 鉴权

现状copywriting.service.ts 第 507 行使用固定字符串 '639290EC-B91E-EB21-3B45-3A1B19A0DAB0' 做下载接口鉴权,而非正规的认证守卫。

建议:使用 @UseGuards(AuthAdminGuard) 替代硬编码 token。

2.6 【紧急】超级管理员通过 userId === 1 硬编码判断

现状setting.service.ts 第 223 行 userId === 1 可以绕过所有权限过滤。

风险:如果用户 ID 1 被意外分配或被攻击者利用,将获得超级管理员权限。

建议:使用 RBAC 角色系统判断超级管理员权限,而非硬编码 ID。

2.7 【紧急】缺少速率限制(Rate Limiting)

现状:后端完全没有 ThrottlerModule 或任何速率限制机制。

风险:暴力破解登录、恶意爬取数据、DDoS 攻击无任何防护。

建议

swee-server-nest/src/app.module.ts
import { ThrottlerModule } from '@nestjs/throttler';

ThrottlerModule.forRoot([{
ttl: 60000, // 1 分钟窗口
limit: 100, // 最多 100 次请求
}])

2.8 【严重】swee-mobile 完全没有 ErrorBoundary

现状:整个 swee-mobile 应用没有任何 ErrorBoundary,_app.tsx 中没有顶层错误边界。一个组件的渲染错误会导致整个 PWA 白屏崩溃。

建议

  • _app.tsx 最外层添加全局 ErrorBoundary
  • 为 AI 聊天、IM 通讯等核心页面添加局部 ErrorBoundary
  • ErrorBoundary 的 componentDidCatch 中上报 Sentry

2.9 【严重】Sentry 监控被完全注释掉(swee-mobile)

现状next.config.jswithSentryConfig 被完全注释(第 42-84 行),虽然 sentry.client.config.tssentry.server.config.tssentry.edge.config.ts 配置文件存在,但实际未生效。

风险:生产环境完全没有错误追踪,用户遇到 Bug 时团队无法感知。

建议:恢复 Sentry 配置并确保 source map 正确上传。

2.10 【严重】测试覆盖几乎为零

团队层面的共性短板

零测试覆盖在 Swee 和 RYZZ 两个项目中同时存在,这不是个别开发者的问题,而是团队缺少测试文化和 CI 门禁。建议从团队规范层面推动测试覆盖率要求。

现状

  • swee-mobile:0 个测试文件
  • swee-admin:0 个测试文件
  • swee-server-nest:4 个测试文件,但 E2E 测试测的是不存在的默认端点(GET /Hello World!),核心业务模块(copywriting、setting、material、task)完全无测试

建议

  • 优先为 copywriting.service.ts(文案管理核心)和 task.service.ts(新闻聚合定时任务)添加单元测试
  • 为 swee-mobile 的 AI 聊天流程添加集成测试
  • 修复无意义的 E2E 测试,改为测试实际 API 端点
  • CI 中添加测试覆盖率门槛

2.11 【严重】错误信息泄露给客户端

现状all-exceptions.filter.ts 第 46 行将所有 Error.message 直接返回客户端,可能泄露数据库结构、文件路径等内部信息。

建议:生产环境只返回通用错误消息(如"服务器内部错误"),详细信息仅记录到日志。

2.12 【严重】所有业务错误 HTTP 状态码统一为 200

现状common.error.ts 第 65 行 super({ code, message }, HttpStatus.OK),所有错误(包括未授权、资源不存在、参数错误)都返回 HTTP 200。

风险

  • 监控工具无法通过 HTTP 状态码识别错误
  • 前端无法利用 Axios 的 HTTP 状态码拦截器做统一错误处理
  • CDN/负载均衡器无法正确统计错误率

建议:按错误类型返回正确的 HTTP 状态码(400/401/403/404/500)。

2.13 【重要】大量生产代码中的 console.log

现状

swee-server-nest(后端生产运行时):

  • task.service.ts 中有 10+ 处 console.log
  • requestLib.ts 中有 5 处调试日志
  • 存在名为 aaa() 的无意义调试方法(console.log(2222, ...)

swee-mobile(前端):

  • views/Ai/Channels/index.tsx 中有 console.log(111111, ...)console.log(333333, ...)
  • EasemobProvider.tsx 中有 console.log('easemob connected')

建议

  • 后端:将 console.log 替换为 NestJS Logger,生产环境只输出 warn/error 级别
  • 前端:清理数字标记的调试日志,使用 logger 模块统一管理
  • 删除 aaa() 方法等调试残留代码

2.14 【重要】数据库实体缺少索引

现状:所有 TypeORM 实体均未使用 @Index() 装饰器:

  • copywriting.entity.tskey 字段被频繁查询但无索引
  • collection.entity.tsname 字段用于 Like 查询无索引
  • setting.entity.tsname 字段用于查找无索引

风险:随着数据量增长,查询性能会显著下降。

建议:为频繁查询和筛选的字段添加索引:

copywriting.entity.ts
@Index()
@Column()
key: string;

2.15 【重要】N+1 查询问题

现状copywriting.service.ts 第 332 行 getCopywritingListByCollections 对每个 collectionId 发起独立的数据库查询。

建议:使用 In() 操作符一次查询所有:

copywriting.service.ts
this.copywritingRepository.find({
where: { collection: { id: In(collectionIds) } }
})

2.16 【重要】无数据库迁移机制

现状

  • 开发环境使用 synchronize: true 自动同步 schema
  • 但整个项目没有任何迁移文件
  • 测试/生产环境的 schema 变更完全依赖手动操作

风险:数据库 schema 变更无版本控制,多人开发时容易冲突,生产环境回滚困难。

建议

  • 配置 TypeORM CLI,使用 migration:generatemigration:run 管理 schema 变更
  • 关闭所有环境的 synchronize
  • 在 CI 中添加迁移验证步骤

2.17 【重要】swee-server-nest TypeScript 未启用 strict 模式

现状tsconfig.json 中无 strict: true,允许大量隐式 anytask.service.ts 等文件中存在大量 any 类型。

建议:启用 strict: true,逐步修复类型错误。可以先添加 strictNullChecksnoImplicitAny

2.18 【重要】swee-mobile 关闭了 catch 变量类型检查

现状tsconfig.json 中设置 useUnknownInCatchVariables: false,使 catch 变量回退为 any 类型。

建议:移除该覆盖,让 catch 变量为 unknown,强制安全访问。

2.19 【重要】Next.js 图片优化被禁用(swee-mobile)

现状next.config.jsimages: { unoptimized: true } 关闭了所有图片优化,且多处使用原生 <img> 标签。

建议:开启 Next.js 图片优化,配置 CDN 域名白名单,将高频展示图片迁移到 next/image

2.20 【重要】React Strict Mode 被注释(swee-mobile)

现状next.config.js 第 8 行 // reactStrictMode: true 被注释掉。

建议:启用 Strict Mode,帮助在开发期间发现副作用和过时 API 使用。

2.21 【中等】缺少 React.memo 和 useCallback(swee-mobile)

现状:整个 swee-mobile 中完全没有使用 React.memouseCallback 使用极少。虽然部分场景使用了 ahooks 的 useMemoizedFn,但组件本身未记忆化。

风险:父组件重渲染导致所有子组件无条件重渲染,在移动端可能造成卡顿。

建议

  • 为纯展示组件(如 AiChannel、BenefitItem 等)添加 React.memo
  • 为传递给子组件的回调函数使用 useCallbackuseMemoizedFn

2.22 【中等】自定义滚动加载替代成熟方案

现状Home.tsxSearch/index.tsx 注释掉了 ahooks 的 useInfiniteScroll,改用手写的 scroll 事件监听,引入了重复代码和精度问题。

建议:恢复使用 useInfiniteScroll,或统一封装一个 useScrollLoad Hook。

2.23 【中等】EasemobContext 存在空实现

现状EasemobProvider.tsxfetchUserInfoByIdInStore 的默认值返回空 Map/空对象,组件在 Provider 外使用时会静默返回空数据而非报错。

建议:Context 默认值应抛出错误(如 throw new Error('useEasemob must be used within EasemobProvider'))。

2.24 【中等】Zustand Store 设计不足

现状

  • userInfoMap 使用 new Map() 作为 state,无法与 DevTools 协作(Map 不可 JSON 序列化)
  • TODO 注释遗留,userInfoMap 未做持久化
  • 整个应用只有一个 Store

建议

  • Map 替换为普通对象 Record<string, UserInfo>
  • 使用 zustand/middlewarepersist 中间件实现持久化
  • 按业务域拆分为多个 Store(如 useIMStoreuseUserStore

2.25 【中等】未完成功能和占位内容残留

现状

  • Chat/Dialog.tsx 中有 Array.from({ length: 8 }) 生成的占位按钮("About")和占位文字("111"、"Encounter")
  • Index/components/Inbox.tsx 中硬编码了 conversationId1882255696775991297)和未翻译的英文("Real girl live broadcast room")
  • Home.tsx 中有 Toast.show(`${item.title} clicked`) 的调试代码

建议:上线前清理所有占位内容和调试代码。

2.26 【中等】国际化硬编码字符串

现状

  • BenefitItem/index.tsx 第 65 行硬编码 'Real People' 未使用 t() 包裹
  • Chat/Dialog.tsx 中多处字符串("Please enter a message"、"About")未翻译
  • Ai/Channels/index.tsx 使用了 t('No data'),但翻译文件中缺少该键

建议

  • 所有用户可见文案使用 t() 包裹
  • 建立翻译键完整性检查脚本,检测遗漏和多余的键

2.27 【中等】列表渲染使用数组下标作为 key

现状:多个列表组件使用 key={index}(Home.tsx、Channels/index.tsx、Search/index.tsx)。

风险:列表项增删时导致 React 不必要的重渲染或状态错乱。

建议:使用稳定的业务 ID 作为 key。

2.28 【中等】大量注释代码

现状

  • swee-mobile/Home.tsx 第 82-114 行:大块注释掉的 useInfiniteScroll 旧实现
  • swee-mobile/Search/index.tsx 第 33-94 行:同样的大块注释代码
  • swee-server-nest/task.service.ts 第 940-964 行:大块注释代码
  • swee-server-nest/main.ts 第 11、30-31 行:注释残留

建议:使用 Git 历史追溯而非代码注释保留旧逻辑,清理所有注释代码。

2.29 【中等】超大文件需要拆分

文件行数问题
swee-admin/views/Content/List/index.tsx1578单文件逻辑过多
swee-admin/views/Content/components/ContentCreateModalForm.tsx1537表单与 UI 耦合过深
swee-server-nest/module/task/task.service.ts969职责过多(新闻聚合 + 翻译 + 飞书推送 + 调试方法)
swee-admin/views/Material/Library.tsx804素材管理单文件实现
swee-admin/views/Operation/Payroll.tsx705

建议

  • task.service.ts 拆分为 NewsScraperServiceNewsTranslationServiceLarkNotificationService
  • ContentCreateModalForm.tsx 按表单区域拆分为子组件
  • Library.tsx 将文件操作、上传、预览逻辑提取为独立组件

2.30 【中等】内存缓存无 Redis

现状CacheModule.register() 使用默认的内存缓存(无 store 配置),进程重启后缓存丢失,多实例部署时无法共享。

建议:接入 Redis 作为缓存后端:

swee-server-nest/src/app.module.ts
CacheModule.register({
store: redisStore,
host: process.env.REDIS_HOST,
});

2.31 【中等】PWA 质量问题

现状

  • manifest.json 所有图标 purpose"any",缺少 "maskable" 变体
  • next-pwa 使用默认缓存策略,未自定义 API 和图片的缓存规则

建议

  • 添加 maskable 图标(Android 设备显示效果更好)
  • 自定义 Service Worker 缓存策略(API → NetworkFirst,图片 → CacheFirst)

2.32 【低等】Docker 安全问题

现状

  • swee-server-nest Dockerfile 以 root 用户运行容器
  • 两个 Dockerfile 中都有遗留的调试命令(RUN ls -aRUN cat package.json
  • swee-mobile Dockerfile 先切换到 nextjs 用户,又切回 root 安装 pm2
  • 两个 Dockerfile 均未配置 HEALTHCHECK

建议

  • 后端 Dockerfile 创建非 root 用户运行应用
  • 移除调试命令
  • 在 installer 阶段安装 pm2,避免 runner 阶段切回 root
  • 添加 HEALTHCHECK 指令或 K8s livenessProbe/readinessProbe

2.33 【低等】API 文件中文拼音命名

现状:swee-server-nest 的 src/apis/user/ 下大量文件使用中文拼音命名(如 guanlihoutaiyonghujiekou.tsmima.tsdenglu.ts)。

建议:在 OpenAPI 代码生成脚本中配置英文语义命名。

2.34 【低等】catch 块中 error 类型不安全

现状task.service.ts 多处 console.error(`... ${error.message}`),但 catch 变量可能不是 Error 实例。

建议:统一使用类型安全的 error 处理:

类型安全的错误处理
catch (error) {
const message = error instanceof Error ? error.message : String(error);
}

2.35 【低等】DTO 参数缺少类型注解

现状copywriting.service.ts 第 141 行 getCollectionDetail(getCollectionDetailReq) 无类型注解。

建议:为所有 Service 方法参数添加 DTO 类型注解。


三、架构设计层面分析

以上的改进建议偏向代码质量细节,本节从更高的技术方案和架构设计视角进行评审。

3.1 架构亮点

3.1.1 NestJS 管道式请求处理链

架构设计

NestJS 的管道式架构是企业级后端的标准实践。每个中间件/拦截器/守卫职责单一、可独立测试、可声明式组合,新增 API 自动继承完整的处理链路。

后端实现了一套完整的、可组合的请求处理管道,每个环节职责单一:

请求 → TraceMiddleware → AuthGuard → ValidationPipe → Handler → ResponseInterceptor → AllExceptionsFilter → 响应
(链路追踪ID) (权限校验) (参数校验) (业务处理) (响应格式统一) (异常兜底)

设计亮点

  • TraceMiddleware 在管道最前端为每个请求生成 traceId,贯穿整个请求链路,便于日志追踪和问题定位
  • ResponseInterceptor 统一包装成 { code, data, msg, traceId, date, path } 格式,前端无需处理格式差异
  • AllExceptionsFilter 兜底所有未处理的异常,保证即使代码抛出未预期的错误,客户端也能收到结构化的错误响应
  • 各环节通过 NestJS 装饰器声明式组合,新增 API 自动享有完整的管道保护

3.1.2 声明式权限控制架构

自定义 @Auth() 装饰器将权限控制从业务逻辑中完全剥离:

copywriting-collection-admin.controller.ts
// 一行装饰器 = 用户类型校验 + 权限粒度校验 + 用户信息注入
@Auth({ type: 'admin', permissionId: 'system:copywriting:collection' })
export class CopywritingCollectionAdminController { }

设计亮点

  • 支持 adminuser 两种用户类型,Guard 内部根据 type 走不同的校验逻辑
  • permissionId 支持细粒度权限控制(如 system:copywriting:collection vs system:copywriting:detail
  • 验证通过后将用户信息自动注入到 request.adminUserInfo,Service 层可直接使用
  • Controller 级别声明权限,整个 Controller 的所有方法自动受保护,不会遗漏

与 swee-admin 的前端权限形成三级权限体系

路由级权限 → 前端 access 插件过滤菜单和路由
组件级权限 → useAccess() 控制按钮/操作的显隐
API 级权限 → @Auth() 装饰器在后端最终拦截

3.1.3 分层配置合并策略

多环境配置采用「通用基础 + 环境覆盖」的深度合并模式:

common.config.ts(通用配置:OSS 区域、端口、API URL 模板...)
↓ mergeDeep()
development.config.ts / test.config.ts / production.config.ts

最终运行时配置(TConfig 类型约束)

设计亮点

  • common.config.ts 放置跨环境不变的配置,避免三个环境文件重复定义
  • mergeDeep() 深层合并而非浅层覆盖,嵌套对象的部分字段可以被单独覆盖
  • 导出 TConfig 类型,Service 中通过 config.get('oss.accessKeyId', { infer: true }) 获取配置项时有完整的类型提示和检查
  • 通过 NEST_ENV 环境变量切换配置,Docker 构建时注入

3.1.4 OpenAPI 全链路自动化

从 Swagger 文档到前端可用的 TypeScript API 客户端,实现了全链路自动化:

NestJS DTO + @ApiProperty()        前端 openapi 脚本
↓ ↓
Swagger JSON(/api-docs) → @umijs/openapi codegen
↓ ↓
自动生成 API 文档 自动生成 TypeScript API 函数 + 类型定义

设计亮点

  • 后端 DTO 是唯一的数据源,前端 API 类型与后端严格一致
  • 修改后端 DTO → 重新执行 pnpm openapi → 前端类型自动更新 → TypeScript 编译报错指出需要适配的地方
  • swee-mobile 同时对接 6 个 Java 微服务(user、recommend、live、model、interact、im),全部通过 codegen 生成
  • 减少了大量手写 API 调用代码和类型定义的工作

3.1.5 TypeORM Entity 的软删除 + 审计设计

所有数据实体统一实现了软删除和时间戳审计:

common/base.entity.ts
@CreateDateColumn() createdAt: Date;   // 自动记录创建时间
@UpdateDateColumn() updatedAt: Date; // 自动记录更新时间
@DeleteDateColumn() deletedAt: Date; // 软删除标记(非空 = 已删除)

配合操作日志实体(LogEntity)记录操作人、操作 IP、操作类型、变更前后的 JSON 对比,形成完整的数据审计链。

3.1.6 Monorepo + Turbo Prune 的按需构建

与 RYZZ 共享同一套 Monorepo 基础设施,Swee 的每个应用(swee-mobile、swee-admin、swee-server-nest)都可以独立构建:

Docker 构建时按需裁剪
turbo prune swee-server-nest --docker
# 只提取后端及其依赖的共享包,不包含前端应用代码
# 最终 Docker 镜像不包含任何前端代码

实现了「一个仓库管理所有应用,但每个应用独立部署」的最佳实践。

3.2 架构改进建议

3.2.1 REST API 设计不规范 — 偏离 RESTful 原则

架构债务

所有操作统一使用 POST 方法将导致 HTTP 缓存机制完全失效、监控工具无法区分读写操作、API 可发现性差。这是一项需要规划迁移的架构债务。

现状:API 设计以「动作 + 资源」命名,且读操作也使用 POST 方法:

POST /copywriting/collection/admin/add_collection      → 创建
POST /copywriting/collection/admin/delete_collection → 删除
POST /copywriting/collection/admin/update_collection → 更新
POST /copywriting/collection/admin/get_collection_list → 查询(也是 POST)
POST /copywriting/item/open/get_copywriting_list_by_collections → 查询

架构问题

  • 所有操作都用 POST,HTTP 方法的语义(GET/POST/PUT/DELETE)完全丢失
  • CDN 和浏览器无法对 POST 请求做缓存(GET 可以)
  • 与 HTTP 缓存头(Cache-Control、ETag)不兼容
  • 动词命名(add/delete/get)与 HTTP 方法语义重复
  • 不利于 API 的版本化和文档生成的标准化

建议的 RESTful 方案

POST   /api/v1/admin/copywriting/collections          → 创建集合
DELETE /api/v1/admin/copywriting/collections/:id → 删除集合
PATCH /api/v1/admin/copywriting/collections/:id → 更新集合
GET /api/v1/admin/copywriting/collections → 查询集合列表
GET /api/v1/admin/copywriting/collections/:id → 查询单个集合
GET /api/v1/open/copywriting/items?collectionIds=a,b → 公开查询
  • 资源用名词复数(collectionsitems
  • HTTP 方法表示操作类型
  • 路径参数表示资源 ID
  • 查询参数用于筛选和分页
  • /api/v1/ 前缀支持版本管理

3.2.2 认证架构每请求远程校验 — 性能瓶颈

现状:每个需要认证的 API 请求,AuthGuard 都会调用外部 Java 服务来验证 Token 并获取用户信息:

客户端请求 → NestJS AuthGuard → 调用 Java getUserInfo() API → 返回用户信息 → 继续处理

架构问题

  • 每个 API 请求额外增加一次网络往返(通常 10-50ms)
  • 当外部 Java 服务慢或不可用时,所有 NestJS API 都会超时或失败
  • 高并发时对 Java 用户服务产生放大效应(NestJS 的每个请求 = Java 的一个额外请求)
  • 无法做到 NestJS 独立运行(强依赖外部服务)

建议方案

  1. 本地 JWT 验证:Java 服务签发 JWT Token,NestJS 本地验证签名即可,无需每次远程调用
  2. 用户信息缓存:验证 Token 后,将用户信息缓存(Redis 或内存,TTL 5 分钟),相同 Token 的后续请求直接命中缓存
  3. 权限列表缓存:用户的 pemimList 短期缓存,减少对 Java 服务的调用频率
auth-admin.guard.ts — 改进后的认证流程
async canActivate(context: ExecutionContext): Promise<boolean> {
const token = extractToken(request);
// 1. 本地验证 JWT 签名和过期时间(无网络请求)
const payload = this.jwtService.verify(token);
// 2. 从缓存获取用户信息(命中率 > 90%)
let userInfo = await this.cache.get(`user:${payload.userId}`);
if (!userInfo) {
userInfo = await getUserInfoFromJava(token);
await this.cache.set(`user:${payload.userId}`, userInfo, 300);
}
request.adminUserInfo = userInfo;
return true;
}

3.2.3 缺乏事件驱动架构 — 同步耦合过重

现状:所有业务流程都是同步、阻塞的 HTTP 请求-响应模式:

  • 新闻聚合任务(9 个新闻源抓取 + 翻译 + 去重 + 飞书推送)在一个同步方法中串行执行
  • 文案批量导入 + 翻译也是同步处理
  • OSS 上传 + CDN 刷新是同步等待

架构问题

  • 长耗时操作(新闻抓取、批量翻译)阻塞 HTTP 连接,容易超时
  • 外部服务失败(微软翻译 API、飞书 API)直接导致整个操作失败,无重试
  • 无法做到「接受请求 → 异步处理 → 通知完成」的异步模式
  • 单实例部署时,定时任务正常;多实例部署时,每个实例都会执行一遍定时任务

建议方案

  1. 引入消息队列(Redis Queue / BullMQ):
    API 接收请求 → 发送消息到队列 → 立即返回任务 ID
    Worker 消费消息 → 执行耗时操作 → 更新任务状态
    前端轮询任务状态 或 WebSocket 推送完成通知
  2. 外部服务调用加熔断器@nestjs/terminus + opossum):
    • 微软翻译 API 连续失败 5 次 → 熔断 30 秒 → 降级返回原文
    • 飞书推送失败 → 重试 3 次 → 记录到失败队列后续补推
  3. 定时任务加分布式锁(Redis SETNX):确保多实例只有一个执行

3.2.4 无 API 版本管理 — 前后端耦合

现状:所有 API 路径没有版本号前缀,如 /copywriting/collection/admin/add_collection

架构问题

  • API 破坏性变更(字段重命名、删除)会立即影响所有前端客户端
  • 无法同时支持新旧版本的前端(如 PWA 的 Service Worker 可能缓存了旧版代码)
  • 移动端 App(如果有原生壳)更新不及时,需要兼容旧 API

建议方案: NestJS 支持全局路由前缀和版本管理:

swee-server-nest/src/main.ts
app.setGlobalPrefix('api');
app.enableVersioning({
type: VersioningType.URI,
defaultVersion: '1',
});

// controller 中使用版本号
@Controller({ path: 'copywriting', version: '1' })
// → /api/v1/copywriting/...

3.2.5 前后端数据模型耦合 — Entity 直接暴露

现状:部分 Service 方法直接返回 TypeORM Entity 对象给 Controller,Entity 的所有字段(包括 deletedAt、关联对象)直接序列化返回给前端。

架构问题

  • 数据库字段变更直接影响 API 响应格式
  • deletedAt 等内部字段泄露给前端
  • 关联关系的懒加载/急加载行为不可预测
  • 无法对不同端返回不同粒度的数据

建议方案: 严格使用 DTO 作为响应模型,Service 层内部使用 Entity,输出时转换为 DTO:

copywriting-collection-admin.controller.ts
// Service 返回 Entity
const entity = await this.repository.findOne(id);

// Controller 转换为 DTO(可以用 class-transformer 的 plainToInstance)
return plainToInstance(CopywritingCollectionDto, entity, {
excludeExtraneousValues: true,
});

3.2.6 无健康检查和优雅关机

现状

  • 后端无 /health/ready 端点
  • Dockerfile 无 HEALTHCHECK 指令
  • 无优雅关机处理(收到 SIGTERM 后立即退出,进行中的请求被中断)

架构影响

  • K8s 无法判断 Pod 是否健康,滚动更新时可能将流量路由到未就绪的 Pod
  • 数据库连接、OSS 客户端在进程退出时未正确关闭
  • 进行中的文件上传、数据库事务被中断

建议方案

health.controller.ts + main.ts
// 1. 健康检查
@Controller('health')
export class HealthController {
@Get()
check() {
return { status: 'ok', timestamp: Date.now() };
}

@Get('ready')
async ready() {
// 检查数据库连接、Redis 连接等
await this.dataSource.query('SELECT 1');
return { status: 'ready' };
}
}

// 2. 优雅关机
app.enableShutdownHooks();
// NestJS 会自动在 SIGTERM 时等待进行中的请求完成后再退出

3.2.7 swee-mobile 缺乏统一的数据获取策略

现状:swee-mobile 的数据获取方式分散且不统一:

  • 部分使用 ahooks 的 useRequest
  • 部分使用自定义的 scroll 事件监听做分页
  • 注释掉了 useInfiniteScroll 改用手写方案
  • 无全局的请求缓存和去重机制

架构问题

  • 相同数据多个组件各自请求,无共享缓存
  • 页面切换后再返回,数据重新加载无过渡
  • 无 Optimistic Update 支持
  • 错误处理各自为政,没有统一的重试和降级

建议方案: 引入 SWR 或 React Query 作为统一的数据获取层:

  • 自动缓存和去重(相同 key 的并发请求只发一次)
  • stale-while-revalidate 策略(先展示缓存数据,后台静默刷新)
  • 内置无限滚动支持(useInfiniteQuery
  • 全局错误处理和重试策略
  • 与 Zustand 互补(React Query 管理服务端状态,Zustand 管理客户端状态)

四、改进优先级矩阵

优先级改进项预估工作量影响范围
P0轮换所有泄露的密钥(6 个密钥/密码)2 小时安全
P0移除 guard 中的 token 日志0.5 小时安全
P0配置 CORS 白名单0.5 小时安全
P0添加 Helmet 安全头0.5 小时安全
P0添加速率限制1 小时安全
P0移除硬编码鉴权 Token0.5 小时安全
P1添加 ErrorBoundary(swee-mobile)1 天用户体验
P1恢复 Sentry 监控0.5 天可观测性
P1错误信息不暴露给客户端0.5 天安全
P1HTTP 状态码规范化1 天API 设计
P1清理生产 console.log + 调试残留1 天代码质量
P2添加数据库索引0.5 天性能
P2修复 N+1 查询0.5 天性能
P2建立数据库迁移机制2-3 天可维护性
P2启用后端 TypeScript strict3-5 天类型安全
P2添加核心模块单元测试2-3 周稳定性
P2开启 Next.js 图片优化1-2 天性能
P2清理占位内容和调试代码1 天代码质量
P3接入 Redis 缓存1-2 天可扩展性
P3拆分超大文件3-5 天可维护性
P3PWA 质量优化1-2 天用户体验
P3React.memo / useCallback 优化2-3 天性能
P3Docker 安全加固1 天安全
P3Zustand Store 重构1-2 天可维护性
P4国际化完整性检查1 天多语言支持
P4API 文件英文重命名1 天可读性
P4启用 React Strict Mode0.1 天开发质量

五、总结

Swee 项目在业务功能实现方面展现了很强的工程能力——VTable 大规模文案编辑、配置可视化编辑器的智能类型推断、三层嵌套的埋点事件编辑、AI 驱动的剧情生成等都是高质量的实现。NestJS 后端的模块化设计和 Monorepo 架构也相当规范。

安全问题是最大的红线——6 个生产环境密钥明文硬编码在代码库中,加上 CORS 全开、无速率限制、Token 日志泄露,组成了一个严重的安全风险面。这些问题应该立即修复。

质量保障方面,测试覆盖接近于零、Sentry 被注释、无 ErrorBoundary 是三个突出的短板。性能方面,数据库缺少索引、N+1 查询、图片优化被禁用等问题需要关注。

与 RYZZ 项目的对比

  • 相同点:零测试覆盖、无 ErrorBoundary、Sentry 被注释、图片优化被禁用、React Strict Mode 被注释——说明这些是团队层面的习惯性短板,需要从团队规范层面解决
  • Swee 更严重:密钥明文泄露(RYZZ 仅有 Sentry Token 泄露,Swee 有 6 个密钥/密码泄露)、CORS 全开、无速率限制
  • RYZZ 更严重:WebSocket 重连策略不健壮(Swee 无 WS)、SEO 严重不足(Swee 的 site 作为官网对 SEO 要求较低)

一句话总结:业务功能实现出色,安全加固是当务之急,质量保障体系需要从团队层面建立。