# 项目质量规范 (Production Quality Standards)

**版本**: 1.0
**更新日期**: 2026-04-03
**适用范围**: D:\project (Go + React/TypeScript)

---

## 一、安全规范 (Security)

### 1.1 加密与随机数

```go
// ✅ 正确：随机数生成失败时返回错误
func generateSecureToken(length int) (string, error) {
    bytes := make([]byte, length)
    if _, err := rand.Read(bytes); err != nil {
        return "", fmt.Errorf("failed to generate secure token: %w", err)
    }
    return base64.URLEncoding.EncodeToString(bytes)[:length], nil
}

// ❌ 禁止：使用不安全 fallback
func generateSecureToken(length int) string {
    // ...
    if _, err := rand.Read(bytes); err != nil {
        // 禁止使用时间戳或 math/rand 作为 fallback
        for i := range bytes {
            bytes[i] = byte(time.Now().UnixNano() % 256)  // 不安全！
        }
    }
    // ...
}
```

### 1.2 敏感数据存储

```typescript
// ✅ 正确：敏感数据使用内存存储
let deviceFingerprintCache: DeviceFingerprint | null = null
export function getDeviceFingerprint(): DeviceFingerprint {
  if (cachedFingerprint) return cachedFingerprint
  cachedFingerprint = buildFingerprint()
  return cachedFingerprint
}

// ❌ 禁止：敏感数据存入 localStorage/sessionStorage
localStorage.setItem('device_id', deviceId)  // XSS 可读取
localStorage.setItem('token', token)        // XSS 可读取
```

### 1.3 认证与授权

```go
// ✅ 正确：所有受保护路由使用中间件
adminRoutes.Use(AuthMiddleware.Required())
adminRoutes.Use(AdminOnly())

// ❌ 禁止：硬编码权限检查
if user.Role != "admin" {
    c.JSON(403, "forbidden")  // 分散的权限检查
}
```

### 1.4 SQL 注入防护

```go
// ✅ 正确：使用参数化查询
db.Where("user_id = ?", userID)
db.Where("name LIKE ?", "%"+EscapeLikeWildcard(name)+"%")

// ❌ 禁止：字符串拼接 SQL
db.Where("user_id = " + userID)  // SQL 注入风险
```

### 1.5 错误信息泄露

```go
// ✅ 正确：分类错误，不返回原始错误
response.Error(c, http.StatusInternalServerError, "服务器内部错误")

// ❌ 禁止：返回原始错误信息给客户端
c.JSON(500, gin.H{"error": err.Error()})  // 可能泄露内部信息
```

---

## 二、并发与性能 (Concurrency & Performance)

### 2.1 Goroutine 管理

```go
// ✅ 正确：使用 context 控制生命周期
go func() {
    select {
    case <-ctx.Done():
        return
    case <-ticker.C:
        cleanup()
    }
}()

// ❌ 禁止：fire-and-forget goroutine
go publishEvent(ctx, event, data)  // 无限制的 goroutine
```

### 2.2 Map 并发访问

```go
// ✅ 正确：使用互斥锁保护共享 map
type SSOManager struct {
    mu      sync.RWMutex
    sessions map[string]*SSOSession
}
func (m *SSOManager) Get(key string) *SSOSession {
    m.mu.RLock()
    defer m.mu.RUnlock()
    return m.sessions[key]
}

// ❌ 禁止：map 并发读写
sessions[key] = session  // concurrent map write
```

### 2.3 数据库查询

```go
// ✅ 正确：使用 JOIN 替代 N+1 查询
func GetUserRolesAndPermissions(ctx, userID) ([]*Role, []*Permission, error) {
    // 单次 JOIN 查询
    rows := db.Raw(`SELECT ... FROM user_roles ur
        JOIN roles r ON ur.role_id = r.id
        LEFT JOIN role_permissions rp ON r.id = rp.role_id
        LEFT JOIN permissions p ON rp.permission_id = p.id
        WHERE ur.user_id = ?`, userID)
}

// ❌ 禁止：循环内单独查询（N+1）
for _, roleID := range roleIDs {
    ancestors := repo.GetAncestorIDs(ctx, roleID)  // 每 role 执行一次查询
}
```

### 2.4 导出与批处理

```go
// ✅ 正确：分批处理 + 最大限制
const MaxExportRecords = 100000
const BatchSize = 5000

for {
    batch, hasMore, err := repo.ListBatch(ctx, cursor, BatchSize)
    if total >= MaxExportRecords {
        break  // 防止 OOM
    }
    // 处理 batch...
}

// ❌ 禁止：无限制加载全表到内存
allRecords := repo.ListAll(ctx)  // 百万级记录 OOM
```

---

## 三、API 设计规范 (API Design)

### 3.1 响应格式

```go
// ✅ 正确：统一包装响应
type APIResponse struct {
    Code    int         `json:"code"`
    Message string      `json:"message"`
    Data    interface{} `json:"data,omitempty"`
}

// 成功响应
response.Success(c, data)           // {code: 0, message: "success", data: {...}}
response.Paginated(c, items, total, page, pageSize)

// ❌ 禁止：裸 JSON 响应
c.JSON(200, gin.H{"users": users})  // 无统一格式
```

### 3.2 错误处理

```go
// ✅ 正确：使用标准错误响应
response.BadRequest(c, "无效的请求参数")
response.Unauthorized(c, "认证已过期，请重新登录")
response.Forbidden(c, "权限不足")
response.NotFound(c, "用户不存在")
response.InternalError(c, "服务器内部错误")

// ❌ 禁止：直接返回错误字符串
c.JSON(400, gin.H{"error": "bad request"})
```

### 3.3 分页参数

```
// ✅ 统一分页格式
GET /users?page=1&page_size=20

// 响应
{
    "code": 0,
    "message": "success",
    "data": {
        "items": [...],
        "total": 100,
        "page": 1,
        "page_size": 20,
        "pages": 5
    }
}
```

---

## 四、代码风格规范 (Code Style)

### 4.1 错误处理原则

```go
// ✅ 正确：明确处理错误
if err != nil {
    if errors.Is(err, sql.ErrNoRows) {
        return nil, ErrNotFound
    }
    return nil, fmt.Errorf("query failed: %w", err)
}

// ❌ 禁止：忽略错误
data, _ := json.Marshal(v)  // 忽略 marshal 错误
```

### 4.2 Context 使用

```go
// ✅ 正确：使用请求 context 或带超时的 context
func HandleRequest(c *gin.Context) {
    ctx := c.Request.Context()
    // 或
    ctx, cancel := context.WithTimeout(c.Request.Context(), 5*time.Second)
    defer cancel()
}

// ❌ 禁止：使用 context.Background()
go func() {
    doSomething(context.Background())  // 生命周期不关联
}()
```

### 4.3 前端 TypeScript

```typescript
// ✅ 正确：完整的类型定义
interface User {
    id: number
    username: string
    email: string
}

// ❌ 禁止：滥用 any
function processData(data: any): any {
    return data  // 类型安全丧失
}

// ✅ 正确：useMemo 缓存 expensive 计算
const columns = useMemo(() => [
    { key: 'name', dataIndex: 'name' },
    // ...
], [dependencies])

// ❌ 禁止：每次渲染重新创建
const columns = [  // 每次渲染创建新数组
    { key: 'name', dataIndex: 'name' },
]
```

---

## 五、测试规范 (Testing)

### 5.1 单元测试

```go
// ✅ 正确：表驱动测试 + 完整断言
func TestLogin(t *testing.T) {
    tests := []struct {
        name    string
        req     LoginRequest
        wantErr bool
    }{
        {"valid login", LoginRequest{Username: "test", Password: "pass"}, false},
        {"invalid password", LoginRequest{Username: "test", Password: "wrong"}, true},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got, err := Login(tt.req)
            if (err != nil) != tt.wantErr {
                t.Errorf("Login() error = %v, wantErr %v", err, tt.wantErr)
            }
        })
    }
}
```

### 5.2 集成测试

```go
// ✅ 正确：使用测试数据库，测试后清理
func TestUserCRUD(t *testing.T) {
    db := setupTestDB(t)
    defer db.Close()

    repo := NewUserRepository(db)
    user, err := repo.Create(ctx, &User{Username: "test"})
    if err != nil {
        t.Fatalf("failed to create user: %v", err)
    }

    got, err := repo.GetByID(ctx, user.ID)
    if err != nil {
        t.Errorf("GetByID() error = %v", err)
    }
    if got.Username != user.Username {
        t.Errorf("GetByID() = %v, want %v", got.Username, user.Username)
    }
}
```

---

## 六、禁止模式 (Prohibited Patterns)

### 6.1 安全相关

| 禁止模式 | 风险 | 正确做法 |
|---------|------|---------|
| `localStorage.setItem('token', token)` | XSS 读取 | 内存存储或 HttpOnly Cookie |
| `crypto/rand` 失败 fallback 到时间戳 | Token 可预测 | 返回错误 |
| `c.JSON(500, gin.H{"error": err})` | 内部信息泄露 | 统一错误响应 |
| 用户输入拼接 SQL | SQL 注入 | 参数化查询 |
| 重定向未验证 URL | Open Redirect | 白名单验证 |

### 6.2 性能相关

| 禁止模式 | 风险 | 正确做法 |
|---------|------|---------|
| `for { repo.Query() }` 循环内查询 | N+1 | JOIN 批量查询 |
| `ListAll()` 全量加载 | OOM | 分批 + 最大限制 |
| `context.Background()` 在 goroutine | 泄漏 | 带超时的 context |
| 共享 map 无锁保护 | panic | `sync.RWMutex` |

### 6.3 代码质量

| 禁止模式 | 风险 | 正确做法 |
|---------|------|---------|
| `data as SomeType` 类型断言 | 运行时 panic | 类型守卫检查 |
| 魔法数字 | 可读性差 | 定义常量 |
| 重复代码 > 3 处 | 维护性差 | 提取函数/模块 |
| 过长函数 > 100 行 | 可读性差 | 拆分为小函数 |

---

## 七、审查清单 (Review Checklist)

### 提交前必须检查

- [ ] `go vet ./...` 无警告
- [ ] `go build ./...` 编译通过
- [ ] `npm run build` 前端编译通过
- [ ] `npm run lint` 无 error（warning 可接受）
- [ ] 无 `TODO: fixme` 或 ` FIXME` 未处理
- [ ] 无硬编码密码/密钥/Secret
- [ ] 无 `console.log` 生产代码
- [ ] 新增 handler 使用 `response.Success()` 而非裸 `c.JSON`
- [ ] 敏感数据不写入 localStorage/sessionStorage
- [ ] 异步操作有超时控制

### 安全专项检查

- [ ] 新增 API 有权限控制
- [ ] 用户输入有验证
- [ ] SQL 使用参数化查询
- [ ] 错误不泄露内部信息
- [ ] Token 使用 crypto/rand 生成

---

## 八、持续改进

- 每季度进行一次完整的安全审计
- 发现新的反模式及时加入禁止列表
- 定期更新依赖版本（安全补丁）
- 代码覆盖率目标：核心业务 > 80%

---

## 附录：已有安全实践

- ✅ Argon2id 密码哈希
- ✅ JWT JTI 黑名单
- ✅ TOTP 两步验证
- ✅ CSRF Token 保护
- ✅ XSS window guard
- ✅ SSRF URL 验证
- ✅ 参数化查询防注入
