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

12 KiB
Raw Blame History

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

// 当前代码(有风险)
sql := fmt.Sprintf(
    "SELECT %s FROM %s%s%s LIMIT %d OFFSET %d",
    selectCols, viewName, where, order, pageSize, offset,
)

修复方案:对 viewName 强制校验,对 orderBy 做白名单,LIMIT/OFFSET 参数化:

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

// 当前代码 — 测试按钮暴露在生产构建中
<TouchableOpacity onPress={() => handleTestLogin("test1")}>
  <Text>测试用户1</Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => handleTestLogin("test2")}>
  <Text>测试用户2</Text>
</TouchableOpacity>

修复方案:使用 __DEV__ 条件编译:

{__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

// 当前代码 — panic 会导致整个请求崩溃
func quoteTable(table string) string {
    if !validTableName.MatchString(table) {
        panic(fmt.Sprintf("invalid table: %s", table))  // 危险!
    }
    return fmt.Sprintf(`"%s"`, table)
}

修复方案:改为返回 error,让调用方决策:

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 后端:

// 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

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:

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 个重复方法):

// 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% 重复):

// 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 层,最后清页面层

// 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 流水线建议

# .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 周):止血

  • 修复 P0-1 SQL 注入
  • 修复 P0-2 测试代码泄漏
  • 修复 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 后端

React Native 前端


六、关键指标跟踪

指标 当前值 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 滥用、巨型文件、重复代码)。前两周集中"止血",之后两个月持续推进,三个月内可以达到较为成熟的中等偏上水平。

有任何具体项需要我深入协助实施的,随时告诉我!