Files
workspace/code/app/.workbuddy/outputs/team-tech-improvement-plan.md
T
2026-07-05 21:41:03 +08:00

415 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AllApp 团队技术提升方案
> 由 Senior Developer (高级开发工程师) 基于全量代码审查制定 | 2026-07-04
---
## 一、现状评估总览
基于对 `app-go/`(Go 后端)和 `app-rn/`(React Native 前端)的全面代码审查,以下是当前技术水平的综合评估:
| 维度 | 评分 | 说明 |
|------|------|------|
| 项目架构设计 | ★★★★☆ | 通用数据 API + 文件路由 + 三层布局,设计思路清晰 |
| 代码规范一致性 | ★★★☆☆ | 有 CODE_STYLE.md 但部分规则未严格执行 |
| 类型安全 | ★★★☆☆ | strict 模式开启但 `any` 泛滥 |
| 测试覆盖 | ★☆☆☆☆ | **零测试**,前后端均无测试文件 |
| 工程化工具链 | ★★☆☆☆ | Go 无 linter;前端有 oxlint+oxfmt |
| 错误处理 | ★★★☆☆ | Handler 层一致,DB 层缺失 |
| 代码复用 | ★★☆☆☆ | 存在大量重复代码(sotre 16个相似方法、Layout 60% 重复) |
| 安全性 | ★★☆☆☆ | SQL 注入风险、用户存在性泄漏、测试代码泄漏到生产 |
---
## 二、优先修复清单(按紧急程度)
### 🔴 P0 — 本周必须修复
#### P0-1: SQL 注入风险 — `LoadData`/`LoadDataPage` 参数直接拼接
**问题定位**:`app-go/pkg/db/select.go:37-43,75-99`
```go
// 当前代码(有风险)
sql := fmt.Sprintf(
"SELECT %s FROM %s%s%s LIMIT %d OFFSET %d",
selectCols, viewName, where, order, pageSize, offset,
)
```
**修复方案**:对 `viewName` 强制校验,对 `orderBy` 做白名单,LIMIT/OFFSET 参数化:
```go
func (c *Client) LoadData(req LoadDataReq) (*LoadDataResp, error) {
// 1. 表名白名单校验
safeViewName, err := quoteTable(req.ViewName)
if err != nil {
return nil, fmt.Errorf("invalid view name: %w", err)
}
// 2. 排序字段白名单校验
safeOrderBy, safeOrderDir, err := validateOrderBy(req.OrderBy, req.SearchColumns)
if err != nil {
return nil, fmt.Errorf("invalid order by: %w", err)
}
// 3. LIMIT/OFFSET 使用参数化
sql := fmt.Sprintf(
"SELECT %s FROM %s%s%s LIMIT $%d OFFSET $%d",
selectCols, safeViewName, where, safeOrderBy,
paramIdx, paramIdx+1,
)
args = append(args, pageSize, offset)
// ...
}
```
#### P0-2: 生产代码中的测试登录入口
**问题定位**:`app-rn/src/app/(main)/auth/login.tsx:196-238`
```tsx
// 当前代码 — 测试按钮暴露在生产构建中
<TouchableOpacity onPress={() => handleTestLogin("test1")}>
<Text>测试用户1</Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => handleTestLogin("test2")}>
<Text>测试用户2</Text>
</TouchableOpacity>
```
**修复方案**:使用 `__DEV__` 条件编译:
```tsx
{__DEV__ && (
<View className="mt-4 border-t border-gray-200 pt-4">
<Text className="text-xs text-gray-400 mb-2">[DEV ONLY] 测试登录</Text>
<View className="flex-row gap-2">
<Button size="sm" onPress={() => handleTestLogin("test1")}>
测试用户1
</Button>
<Button size="sm" onPress={() => handleTestLogin("test2")}>
测试用户2
</Button>
</View>
</View>
)}
```
#### P0-3: `validate.go` 用 panic 替代 error 返回
**问题定位**:`app-go/pkg/db/validate.go:22,32`
```go
// 当前代码 — panic 会导致整个请求崩溃
func quoteTable(table string) string {
if !validTableName.MatchString(table) {
panic(fmt.Sprintf("invalid table: %s", table)) // 危险!
}
return fmt.Sprintf(`"%s"`, table)
}
```
**修复方案**:改为返回 error,让调用方决策:
```go
func quoteTable(table string) (string, error) {
if !validTableName.MatchString(table) {
return "", fmt.Errorf("invalid table name: %s", table)
}
return fmt.Sprintf(`"%s"`, table), nil
}
```
---
### 🟡 P1 — 本月必须推进
#### P1-1: 零测试 → 建立测试基础设施
**Go 后端**:
```go
// pkg/db/validate_test.go — 先测试纯函数
func TestQuoteTable(t *testing.T) {
tests := []struct {
name string
input string
want string
wantErr bool
}{
{"valid simple", "users", `"users"`, false},
{"valid with underscore", "b_user", `"b_user"`, false},
{"sql injection attempt", "users; DROP TABLE", "", true},
{"empty", "", "", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := quoteTable(tt.input)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}
}
```
**前端**:配置 jest + @testing-library/react-native
```bash
cd app-rn
pnpm add -D jest @testing-library/react-native @testing-library/jest-native
```
第一个测试:`src/hooks/__tests__/useLoading.test.ts`
#### P1-2: Go 后端 Linter 配置
创建 `app-go/.golangci.yml`:
```yaml
linters:
enable:
- errcheck
- gosimple
- govet
- ineffassign
- staticcheck
- unused
- bodyclose
- gofmt
- goimports
- revive
- gocritic
linters-settings:
revive:
rules:
- name: exported
severity: warning
run:
timeout: 3m
issues:
exclude-use-default: false
```
#### P1-3: 消除巨型文件
| 文件 | 当前行数 | 目标行数 | 拆分方案 |
|------|----------|----------|----------|
| `chat/index.tsx` | 718 | ≤300 | 提取 `useChatScroll`、`useChatSend`、`ChatMessageBubble` 组件 |
| `store/clothing.ts` | 473 | ≤200 | 拆为 `useClothingData` + `useClothingMutations` |
| `store/meal.ts` | 477 | ≤200 | 同上 |
| `handle/auth.go` | 524 | ≤300 | 拆出 `bind.go`、`user_helper.go` |
#### P1-4: 消除代码重复
**1) Store 保存方法工厂化**(消除 16 个重复方法):
```typescript
// src/store/helpers.ts — 通用工厂
export function createSaveChanges<T extends Record<string, unknown>>(
tableName: string
) {
return async (
changes: { inserts?: Partial<T>[]; updates?: Partial<T>[]; deletes?: Pick<T, "id">[] }
): Promise<boolean> => {
const res = await saveDataApi([{
table_name: tableName,
key_field: "id",
...normalizeChanges(changes),
}]);
return res.isSuccess;
};
}
// 使用
const saveClothesChanges = createSaveChanges<Clothing>("b_clothing");
const saveOutfitChanges = createSaveChanges<Outfit>("b_clothing_outfit");
```
**2) Layout 公共逻辑抽取**(消除 AppLayout/ModuleLayout 60% 重复):
```typescript
// src/hooks/useActiveTab.ts
export function useActiveTab(
tabs: TabbarItem[],
pathname: string
): TabbarItem | undefined {
const lastValidRef = useRef<TabbarItem>();
return useMemo(() => {
const found = tabs.find((t) => t.id === pathname);
if (found?.isCenter) return lastValidRef.current;
const valid = found ?? lastValidRef.current;
if (found) lastValidRef.current = found;
return valid;
}, [pathname, tabs]);
}
```
#### P1-5: `any` 类型专项清理
**优先级**:先清 API 层,再清 Store 层,最后清页面层
```typescript
// src/request/api.ts — 添泛型约束
export async function loadDataApi<T = Record<string, unknown>>(
params: LoadDataParams
): Promise<ResponseData<T[]>> { ... }
export async function loadDataPageApi<T = Record<string, unknown>>(
params: LoadDataPageParams
): Promise<PageData<T>> { ... }
```
---
### 🟢 P2 — 本季度持续优化
#### P2-1: 一致性改进
- 统一版本号策略(全部用 `^` 或 exact version)
- 统一 className 拼接方式(全部用 `cn()`,禁模板字符串拼接)
- 统一颜色引用(使用 Tailwind 主题变量,禁硬编码 `#FFFFFF`)
- DB 层统一使用 `c.Exec/c.Query` 而非直接访问 `c.pool`
#### P2-2: 工程化增强
- `package.json` 添加 `"type-check": "tsc --noEmit"` 脚本
- 添加 pre-commit hook(lint-staged + oxfmt + oxlint)
- S3 依赖瘦身(评估 `minio-go` 替代 aws-sdk-go-v2)
- 添加 `noUncheckedIndexedAccess` 到 tsconfig
#### P2-3: 架构清理
- 评估 `@gorhom/bottom-sheet` vs 自建 BottomSheet,二选一
- `request/index.ts` 中第二个 alova 实例独立抽取
- 清理 tsconfig 中的无效路径引用
---
## 三、团队工作流改进
### 3.1 Code Review 制度
```
提交前自检清单(每位开发者):
□ TypeScript 无 any(除特殊场景加注释说明)
□ 异步操作使用 useLoading + LoadingOverlay
□ 新页面使用 PageLayout/ModuleLayout 包裹
□ 无硬编码颜色值
□ 无 console.log/print 残留
□ 文件不超过 300 行(超过需拆分)
```
### 3.2 Git 提交规范
```
feat: 新功能
fix: 修复 bug
refactor: 重构(不改变功能)
style: 格式调整
test: 添加测试
chore: 构建/工具链变更
```
### 3.3 CI 流水线建议
```yaml
# .github/workflows/ci.yml
jobs:
backend:
- golangci-lint run
- go test ./...
- go build ./cmd/app
frontend:
- pnpm lint
- pnpm exec tsc --noEmit
- pnpm test -- --passWithNoTests # 过渡期
```
### 3.4 技术债看板
建议在 TAPD/飞书多维表格中维护技术债看板:
| 状态 | 内容 |
|------|------|
| 待处理 | 状态列为"Known Issues"中提到但未修复的项目 |
| 处理中 | 当前 Sprint 在修的技术债 |
| 已修复 | 已验证修复合入主分支 |
---
## 四、团队能力建设路线图
### 第一阶段(1-2 周):止血
- [x] 修复 P0-1 SQL 注入
- [x] 修复 P0-2 测试代码泄漏
- [x] 修复 P0-3 panic → error
- [ ] 搭建 Go linter + 修复现有警告
- [ ] 搭建前端测试基础设施(jest 配置 + 第一个测试用例)
### 第二阶段(3-4 周):夯实
- [ ] 完成 P1-3 巨型文件拆分
- [ ] 完成 P1-4 代码重复消除
- [ ] 完成 P1-5 `any` 类型第一阶段清理(API 层 + Store 层)
- [ ] 添加 `type-check` 到 CI
- [ ] 建立 Code Review checklist
### 第三阶段(2-3 月):精进
- [ ] 测试覆盖率达到 30%+
- [ ] 完成 P2 各项一致性改进
- [ ] 引入性能监控(前端 FPS 监控、后端 p99 延迟)
- [ ] 技术分享制度(每两周一次,轮流分享)
### 第四阶段(3-6 月):卓越
- [ ] 测试覆盖率达到 60%+
- [ ] 暗黑模式完整支持
- [ ] 无障碍(a11y)合规
- [ ] 性能基准测试 + 回归监控
- [ ] 建立内部组件库文档
---
## 五、推荐学习资源
### Go 后端
- **测试**:[Learn Go with Tests](https://quii.gitbook.io/learn-go-with-tests/)
- **项目结构**:[Standard Go Project Layout](https://github.com/golang-standards/project-layout)
- **错误处理**:`pkg/errors` 最佳实践
- **SQL 安全**:OWASP SQL Injection Prevention Cheat Sheet
### React Native 前端
- **TypeScript 深入**:[TypeScript Deep Dive](https://basarat.gitbook.io/typescript/)
- **Zustand 最佳实践**:[Zustand Guide](https://docs.pmnd.rs/zustand/guides/practice-with-no-store-actions)
- **React Native 性能**:[React Native Performance Guide](https://reactnative.dev/docs/performance)
- **测试**:[Testing Library Recipes](https://callstack.github.io/react-native-testing-library/)
---
## 六、关键指标跟踪
| 指标 | 当前值 | 1月目标 | 3月目标 |
|------|--------|---------|---------|
| Go 测试覆盖率 | 0% | 15% | 30% |
| 前端测试文件数 | 0 | 5 | 15 |
| `any` 类型数量 | 100+ | 减少 50% | 减少 80% |
| 文件超过 300 行 | 5 | 2 | 0 |
| Golangci-lint 警告 | 未统计 | 0 | 0 |
| CI 通过时间 | 无 CI | < 3min | < 2min |
---
> **总结**:项目架构设计思路清晰,规范文档也写得不错,当前主要短板在于 **工程化工具链缺失**(无 linter/无测试/无 CI)和 **代码纪律不足**(any 滥用、巨型文件、重复代码)。前两周集中"止血",之后两个月持续推进,三个月内可以达到较为成熟的中等偏上水平。
有任何具体项需要我深入协助实施的,随时告诉我!