Files
user-system/PROJECT_QUALITY_STANDARDS.md
long-agent 765a50b7d4 fix: 生产安全修复 + Go SDK + CAS SSO框架
安全修复:
- CRITICAL: SSO重定向URL注入漏洞 - 修复redirect_uri白名单验证
- HIGH: SSO ClientSecret未验证 - 使用crypto/subtle.ConstantTimeCompare验证
- HIGH: 邮件验证码熵值过低(3字节) - 提升到6字节(48位熵)
- HIGH: 短信验证码熵值过低(4字节) - 提升到6字节
- HIGH: Goroutine使用已取消上下文 - auth_email.go使用独立context+超时
- HIGH: SQL LIKE查询注入风险 - permission/role仓库使用escapeLikePattern

新功能:
- Go SDK: sdk/go/user-management/ 完整SDK实现
- CAS SSO框架: internal/auth/cas.go CAS协议支持

其他:
- L1Cache实例问题修复 - AuthMiddleware共享l1Cache
- 设备指纹XSS防护 - 内存存储替代localStorage
- 响应格式协议中间件
- 导出无界查询修复
2026-04-03 17:38:31 +08:00

9.9 KiB
Raw Blame History

项目质量规范 (Production Quality Standards)

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


一、安全规范 (Security)

1.1 加密与随机数

// ✅ 正确:随机数生成失败时返回错误
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 敏感数据存储

// ✅ 正确:敏感数据使用内存存储
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 认证与授权

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

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

1.4 SQL 注入防护

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

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

1.5 错误信息泄露

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

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

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

2.1 Goroutine 管理

// ✅ 正确:使用 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 并发访问

// ✅ 正确:使用互斥锁保护共享 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 数据库查询

// ✅ 正确:使用 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 导出与批处理

// ✅ 正确:分批处理 + 最大限制
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 响应格式

// ✅ 正确:统一包装响应
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 错误处理

// ✅ 正确:使用标准错误响应
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 错误处理原则

// ✅ 正确:明确处理错误
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 使用

// ✅ 正确:使用请求 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

// ✅ 正确:完整的类型定义
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 单元测试

// ✅ 正确:表驱动测试 + 完整断言
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 集成测试

// ✅ 正确:使用测试数据库,测试后清理
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 无 errorwarning 可接受)
  • 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 验证
  • 参数化查询防注入