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 都有
@ApiPropertySwagger 文档标注 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.ts、common.config.ts、test.config.ts 这些 .ts 文件是被 Git 追踪的源码文件,密钥写在里面就等于提交到了仓库。正确做法是将密钥读取改为环境变量:
// ❌ 当前做法 — 密钥硬编码在 .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 中添加
.env
.env.local
.env.production
# .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 | 运行时动态拉取密钥,密钥自动轮换 |
第五步:防止未来密钥泄露
- 安装
gitleaks或git-secrets作为 pre-commit hook,在提交时自动扫描是否包含密钥 - 如需彻底清除 Git 历史中的密钥,可使用
git filter-branch或BFG Repo-Cleaner
2.2 【紧急】生产日志泄露用户 Access Token
现状:
| 文件 | 行号 | 问题 |
|---|---|---|
auth-admin.guard.ts | 24 | console.log(`accessToken: ${accessToken}`) |
auth-user.guard.ts | 18 | console.log(`accessToken: ${accessToken}`) |
response.interceptor.ts | 30 | 每个请求的 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 攻击。
建议:
app.enableCors({
origin: ['https://admin.swee.com', 'https://swee.com', 'https://m.swee.com'],
credentials: true,
});
2.4 【紧急】缺少 HTTP 安全头(Helmet)
现状:后端未使用 helmet 中间件,缺少 X-Content-Type-Options、X-Frame-Options、Content-Security-Policy 等安全响应头。
建议:pnpm add helmet 并在 main.ts 中 app.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 攻击无任何防护。
建议:
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.js 中 withSentryConfig 被完全注释(第 42-84 行),虽然 sentry.client.config.ts、sentry.server.config.ts、sentry.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.logrequestLib.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替换为 NestJSLogger,生产环境只输出 warn/error 级别 - 前端:清理数字标记的调试日志,使用 logger 模块统一管理
- 删除
aaa()方法等调试残留代码
2.14 【重要】数据库实体缺少索引
现状:所有 TypeORM 实体均未使用 @Index() 装饰器:
copywriting.entity.ts的key字段被频繁查询但无索引collection.entity.ts的name字段用于 Like 查询无索引setting.entity.ts的name字段用于查找无索引
风险:随着数据量增长,查询性能会显著下降。
建议:为频繁查询和筛选的字段添加索引:
@Index()
@Column()
key: string;
2.15 【重要】N+1 查询问题
现状:copywriting.service.ts 第 332 行 getCopywritingListByCollections 对每个 collectionId 发起独立的数据库查询。
建议:使用 In() 操作符一次查询所有:
this.copywritingRepository.find({
where: { collection: { id: In(collectionIds) } }
})
2.16 【重要】无数据库迁移机制
现状:
- 开发环境使用
synchronize: true自动同步 schema - 但整个项目没有任何迁移文件
- 测试/生产环境的 schema 变更完全依赖手动操作
风险:数据库 schema 变更无版本控制,多人开发时容易冲突,生产环境回滚困难。
建议:
- 配置 TypeORM CLI,使用
migration:generate和migration:run管理 schema 变更 - 关闭所有环境的
synchronize - 在 CI 中添加迁移验证步骤
2.17 【重要】swee-server-nest TypeScript 未启用 strict 模式
现状:tsconfig.json 中无 strict: true,允许大量隐式 any。task.service.ts 等文件中存在大量 any 类型。
建议:启用 strict: true,逐步修复类型错误。可以先添加 strictNullChecks 和 noImplicitAny。
2.18 【重要】swee-mobile 关闭了 catch 变量类型检查
现状:tsconfig.json 中设置 useUnknownInCatchVariables: false,使 catch 变量回退为 any 类型。
建议:移除该覆盖,让 catch 变量为 unknown,强制安全访问。
2.19 【重要】Next.js 图片优化被禁用(swee-mobile)
现状:next.config.js 中 images: { 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.memo,useCallback 使用极少。虽然部分场景使用了 ahooks 的 useMemoizedFn,但组件本身未记忆化。
风险:父组件重渲染导致所有子组件无条件重渲染,在移动端可能造成卡顿。
建议:
- 为纯展示组件(如 AiChannel、BenefitItem 等)添加
React.memo - 为传递给子组件的回调函数使用
useCallback或useMemoizedFn
2.22 【中等】自定义滚动加载替代成熟方案
现状:Home.tsx 和 Search/index.tsx 注释掉了 ahooks 的 useInfiniteScroll,改用手写的 scroll 事件监听,引入了重复代码和精度问题。
建议:恢复使用 useInfiniteScroll,或统一封装一个 useScrollLoad Hook。
2.23 【中等】EasemobContext 存在空实现
现状:EasemobProvider.tsx 中 fetchUserInfoByIdInStore 的默认值返回空 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/middleware的persist中间件实现持久化 - 按业务域拆分为多个 Store(如
useIMStore、useUserStore)
2.25 【中等】未完成功能和占位内容残留
现状:
Chat/Dialog.tsx中有Array.from({ length: 8 })生成的占位按钮("About")和占位文字("111"、"Encounter")Index/components/Inbox.tsx中硬编码了conversationId(1882255696775991297)和未翻译的英文("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.tsx | 1578 | 单文件逻辑过多 |
swee-admin/views/Content/components/ContentCreateModalForm.tsx | 1537 | 表单与 UI 耦合过深 |
swee-server-nest/module/task/task.service.ts | 969 | 职责过多(新闻聚合 + 翻译 + 飞书推送 + 调试方法) |
swee-admin/views/Material/Library.tsx | 804 | 素材管理单文件实现 |
swee-admin/views/Operation/Payroll.tsx | 705 |
建议:
task.service.ts拆分为NewsScraperService、NewsTranslationService、LarkNotificationServiceContentCreateModalForm.tsx按表单区域拆分为子组件Library.tsx将文件操作、上传、预览逻辑提取为独立组件
2.30 【中等】内存缓存无 Redis
现状:CacheModule.register() 使用默认的内存缓存(无 store 配置),进程重启后缓存丢失,多实例部署时无法共享。
建议:接入 Redis 作为缓存后端:
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-nestDockerfile 以 root 用户运行容器- 两个 Dockerfile 中都有遗留的调试命令(
RUN ls -a、RUN cat package.json) swee-mobileDockerfile 先切换到 nextjs 用户,又切回 root 安装 pm2- 两个 Dockerfile 均未配置
HEALTHCHECK
建议:
- 后端 Dockerfile 创建非 root 用户运行应用
- 移除调试命令
- 在 installer 阶段安装 pm2,避免 runner 阶段切回 root
- 添加
HEALTHCHECK指令或 K8slivenessProbe/readinessProbe
2.33 【低等】API 文件中文拼音命名
现状:swee-server-nest 的 src/apis/user/ 下大量文件使用中文拼音命名(如 guanlihoutaiyonghujiekou.ts、mima.ts、denglu.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() 装饰器将权限控制从业务逻辑中完全剥离:
// 一行装饰器 = 用户类型校验 + 权限粒度校验 + 用户信息注入
@Auth({ type: 'admin', permissionId: 'system:copywriting:collection' })
export class CopywritingCollectionAdminController { }
设计亮点:
- 支持
admin和user两种用户类型,Guard 内部根据 type 走不同的校验逻辑 permissionId支持细粒度权限控制(如system:copywriting:collectionvssystem: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 的软删除 + 审计设计
所有数据实体统一实现了软删除和时间戳审计:
@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)都可以独立构建:
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 → 公开查询
- 资源用名词复数(
collections、items) - 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 独立运行(强依赖外部服务)
建议方案:
- 本地 JWT 验证:Java 服务签发 JWT Token,NestJS 本地验证签名即可,无需每次远程调用
- 用户信息缓存:验证 Token 后,将用户信息缓存(Redis 或内存,TTL 5 分钟),相同 Token 的后续请求直接命中缓存
- 权限列表缓存:用户的
pemimList短期缓存,减少对 Java 服务的调用频率
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)直接导致整个操作失败,无重试
- 无法做到「接受请求 → 异步处理 → 通知完成」的异步模式
- 单实例部署时,定时任务正常;多实例部署时,每个实例都会执行一遍定时任务
建议方案:
- 引入消息队列(Redis Queue / BullMQ):
API 接收请求 → 发送消息到队列 → 立即返回任务 ID
Worker 消费消息 → 执行耗时操作 → 更新任务状态
前端轮询任务状态 或 WebSocket 推送完成通知 - 外部服务调用加熔断器(
@nestjs/terminus+opossum):- 微软翻译 API 连续失败 5 次 → 熔断 30 秒 → 降级返回原文
- 飞书推送失败 → 重试 3 次 → 记录到失败队列后续补推
- 定时任务加分布式锁(Redis
SETNX):确保多实例只有一个执行
3.2.4 无 API 版本管理 — 前后端耦合
现状:所有 API 路径没有版本号前缀,如 /copywriting/collection/admin/add_collection。
架构问题:
- API 破坏性变更(字段重命名、删除)会立即影响所有前端客户端
- 无法同时支持新旧版本的前端(如 PWA 的 Service Worker 可能缓存了旧版代码)
- 移动端 App(如果有原生壳)更新不及时,需要兼容旧 API
建议方案: NestJS 支持全局路由前缀和版本管理:
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:
// 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 客户端在进程退出时未正确关闭
- 进行中的文件上传、数据库事务被中断
建议方案:
// 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 | 移除硬编码鉴权 Token | 0.5 小时 | 安全 |
| P1 | 添加 ErrorBoundary(swee-mobile) | 1 天 | 用户体验 |
| P1 | 恢复 Sentry 监控 | 0.5 天 | 可观测性 |
| P1 | 错误信息不暴露给客户端 | 0.5 天 | 安全 |
| P1 | HTTP 状态码规范化 | 1 天 | API 设计 |
| P1 | 清理生产 console.log + 调试残留 | 1 天 | 代码质量 |
| P2 | 添加数据库索引 | 0.5 天 | 性能 |
| P2 | 修复 N+1 查询 | 0.5 天 | 性能 |
| P2 | 建立数据库迁移机制 | 2-3 天 | 可维护性 |
| P2 | 启用后端 TypeScript strict | 3-5 天 | 类型安全 |
| P2 | 添加核心模块单元测试 | 2-3 周 | 稳定性 |
| P2 | 开启 Next.js 图片优化 | 1-2 天 | 性能 |
| P2 | 清理占位内容和调试代码 | 1 天 | 代码质量 |
| P3 | 接入 Redis 缓存 | 1-2 天 | 可扩展性 |
| P3 | 拆分超大文件 | 3-5 天 | 可维护性 |
| P3 | PWA 质量优化 | 1-2 天 | 用户体验 |
| P3 | React.memo / useCallback 优化 | 2-3 天 | 性能 |
| P3 | Docker 安全加固 | 1 天 | 安全 |
| P3 | Zustand Store 重构 | 1-2 天 | 可维护性 |
| P4 | 国际化完整性检查 | 1 天 | 多语言支持 |
| P4 | API 文件英文重命名 | 1 天 | 可读性 |
| P4 | 启用 React Strict Mode | 0.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 要求较低)
一句话总结:业务功能实现出色,安全加固是当务之急,质量保障体系需要从团队层面建立。