20260705214103
This commit is contained in:
1 parent
3556d5e8c1
commit
ae99f50f6d
27 files changed
+2945
-3292
No files matched your search
@@ -0,0 +1,23 @@
|
||||
# 2026-07-04 工作日志
|
||||
|
||||
## 团队技术提升方案制定
|
||||
- 对 `app-go/`(Go 后端)和 `app-rn/`(React Native 前端)进行了全量代码审查
|
||||
- 发现 15 个后端问题和 20+ 前端问题
|
||||
- 输出了完整的团队技术提升方案(`outputs/team-tech-improvement-plan.md`)
|
||||
|
||||
### 关键发现
|
||||
**严重(P0)**:
|
||||
- SQL 注入风险:LoadData/LoadDataPage 表名/排序字段直接拼接
|
||||
- 生产代码中测试登录入口未隐藏
|
||||
- validate.go 用 panic 替代 error 返回
|
||||
|
||||
**重要(P1)**:
|
||||
- 零测试覆盖(前后端均无)
|
||||
- Go 端无 linter 配置
|
||||
- chat/index.tsx (718行)、clothing.ts (473行)、meal.ts (477行) 三巨型文件
|
||||
- store 中 16 个 saveXxxChanges 方法完全重复
|
||||
- 前后端大量 any 类型逃逸
|
||||
|
||||
**改进项(P2)**:
|
||||
- 版本号策略不统一、className 拼接风格混用、硬编码颜色值
|
||||
- S3 依赖可瘦身、@gorhom/bottom-sheet 与自建 BottomSheet 二选一
|
||||
@@ -0,0 +1,414 @@
|
||||
# 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 滥用、巨型文件、重复代码)。前两周集中"止血",之后两个月持续推进,三个月内可以达到较为成熟的中等偏上水平。
|
||||
|
||||
有任何具体项需要我深入协助实施的,随时告诉我!
|
||||
Reference in new issue
Block a user