# 设计文档：B 端认证系统

**Change ID**: `implement-b-end-auth-system`

---

## 1. 架构概览

### 1.1 系统分层

```
┌─────────────────────────────────────────────────────────────┐
│                      HTTP 层（Fiber）                        │
│  ┌────────────┐  ┌────────────┐  ┌────────────┐            │
│  │ Admin Auth │  │  H5 Auth   │  │  Personal  │            │
│  │  Handler   │  │  Handler   │  │  Customer  │            │
│  └──────┬─────┘  └──────┬─────┘  └──────┬─────┘            │
└─────────┼────────────────┼────────────────┼─────────────────┘
          │                │                │
┌─────────▼────────────────▼────────────────▼─────────────────┐
│                     中间件层（Middleware）                   │
│  ┌────────────┐  ┌────────────┐  ┌────────────┐            │
│  │ Admin Auth │  │  H5 Auth   │  │  Personal  │            │
│  │ Middleware │  │ Middleware │  │    Auth    │            │
│  └──────┬─────┘  └──────┬─────┘  └──────┬─────┘            │
└─────────┼────────────────┼────────────────┼─────────────────┘
          │                │                │
          │                │                │
┌─────────▼────────────────▼────────────────▼─────────────────┐
│                    业务逻辑层（Service）                     │
│  ┌────────────┐  ┌────────────┐  ┌────────────┐            │
│  │ Auth       │  │ Permission │  │  Personal  │            │
│  │ Service    │  │  Service   │  │  Customer  │            │
│  └──────┬─────┘  └──────┬─────┘  └──────┬─────┘            │
└─────────┼────────────────┼────────────────┼─────────────────┘
          │                │                │
┌─────────▼────────────────▼────────────────▼─────────────────┐
│                   数据访问层（Store）                        │
│  ┌────────────┐  ┌────────────┐  ┌────────────┐            │
│  │ Account    │  │   Role     │  │  Personal  │            │
│  │  Store     │  │  Store     │  │  Customer  │            │
│  └──────┬─────┘  └──────┬─────┘  └──────┬─────┘            │
└─────────┼────────────────┼────────────────┼─────────────────┘
          │                │                │
┌─────────▼────────────────▼────────────────▼─────────────────┐
│                     数据存储层                               │
│  ┌──────────────────────┐  ┌──────────────────────┐        │
│  │     PostgreSQL       │  │       Redis          │        │
│  │  (账号、角色、权限)  │  │   (Token、缓存)      │        │
│  └──────────────────────┘  └──────────────────────┘        │
└─────────────────────────────────────────────────────────────┘
```

### 1.2 认证方式对比

| 端口 | 用户类型 | 认证方式 | Token 类型 | 存储方式 |
|------|---------|---------|-----------|----------|
| **Web 后台** | 超级管理员<br>平台用户<br>代理账号 | Bearer Token | Redis Token | Redis |
| **H5 端** | 代理账号<br>企业账号 | Bearer Token | Redis Token | Redis |
| **个人客户端** | 个人客户 | Bearer Token | JWT | 无状态（自签名） |

**设计理由**：
- **B 端（Web + H5）**：使用 Redis Token，支持立即登出和撤销
- **C 端（个人客户）**：使用 JWT，减轻服务器压力，适合高并发场景

---

## 2. 核心模块设计

### 2.1 Token 管理器（TokenManager）

#### 职责
- 生成 access token 和 refresh token
- 验证 token 有效性
- 刷新 access token
- 撤销 token
- 管理用户的所有 token

#### 接口设计

```go
package auth

type TokenManager struct {
	rdb              *redis.Client
	accessTokenTTL   time.Duration  // 24 小时（可配置）
	refreshTokenTTL  time.Duration  // 7 天（可配置）
}

type TokenInfo struct {
	UserID       uint      `json:"user_id"`
	UserType     int       `json:"user_type"`
	ShopID       uint      `json:"shop_id,omitempty"`
	EnterpriseID uint      `json:"enterprise_id,omitempty"`
	Username     string    `json:"username"`
	LoginTime    time.Time `json:"login_time"`
	Device       string    `json:"device"`       // web / h5 / mobile
	IP           string    `json:"ip"`
}

// 生成 token 对
func (m *TokenManager) GenerateTokenPair(ctx context.Context, info *TokenInfo) (accessToken, refreshToken string, err error)

// 验证 access token
func (m *TokenManager) ValidateAccessToken(ctx context.Context, token string) (*TokenInfo, error)

// 验证 refresh token
func (m *TokenManager) ValidateRefreshToken(ctx context.Context, token string) (*TokenInfo, error)

// 刷新 access token
func (m *TokenManager) RefreshAccessToken(ctx context.Context, refreshToken string) (newAccessToken string, err error)

// 撤销单个 token
func (m *TokenManager) RevokeToken(ctx context.Context, token string) error

// 撤销用户的所有 token
func (m *TokenManager) RevokeAllUserTokens(ctx context.Context, userID uint) error
```

#### Redis 存储结构

```
# Access Token
Key:   auth:token:{uuid}
Value: JSON(TokenInfo)
TTL:   24h

# Refresh Token
Key:   auth:refresh:{uuid}
Value: JSON(TokenInfo)
TTL:   7d

# 用户 Token 列表（Set）
Key:   auth:user:{userID}:tokens
Value: Set[access_token_uuid, refresh_token_uuid]
TTL:   7d
```

**示例**：
```
# Access Token
redis> GET auth:token:550e8400-e29b-41d4-a716-446655440000
{
  "user_id": 123,
  "user_type": 2,
  "shop_id": 10,
  "enterprise_id": 0,
  "username": "admin",
  "login_time": "2026-01-15T12:00:00Z",
  "device": "web",
  "ip": "192.168.1.1"
}

# 用户 Token 列表
redis> SMEMBERS auth:user:123:tokens
1) "550e8400-e29b-41d4-a716-446655440000"  # access token
2) "660e8400-e29b-41d4-a716-446655440001"  # refresh token
```

#### Token 生成流程

```
GenerateTokenPair()
  ├─ 1. 生成 access token UUID (uuid.New())
  ├─ 2. 生成 refresh token UUID (uuid.New())
  ├─ 3. 序列化 TokenInfo 为 JSON
  ├─ 4. 存储 access token 到 Redis (TTL: 24h)
  ├─ 5. 存储 refresh token 到 Redis (TTL: 7d)
  ├─ 6. 将两个 token 添加到用户 token 列表（Set）
  └─ 7. 返回 access token 和 refresh token
```

#### Token 验证流程

```
ValidateAccessToken(token)
  ├─ 1. 从 Redis 查询 auth:token:{token}
  ├─ 2. 如果不存在或过期 → 返回 CodeInvalidToken
  ├─ 3. 反序列化 JSON 为 TokenInfo
  ├─ 4. 验证账号状态（可选，需查询数据库）
  └─ 5. 返回 TokenInfo
```

#### Token 刷新流程

```
RefreshAccessToken(refreshToken)
  ├─ 1. 验证 refresh token (ValidateRefreshToken)
  ├─ 2. 撤销旧的 access token (RevokeToken)
  ├─ 3. 生成新的 access token UUID
  ├─ 4. 存储新 token 到 Redis (TTL: 24h)
  ├─ 5. 更新用户 token 列表（删除旧 access token，添加新）
  └─ 6. 返回新 access token
```

#### Token 撤销流程

```
RevokeToken(token)
  ├─ 1. 删除 Redis key: auth:token:{token}
  ├─ 2. 从用户 token 列表中删除该 token
  └─ 3. 返回成功

RevokeAllUserTokens(userID)
  ├─ 1. 获取用户 token 列表 (SMEMBERS auth:user:{userID}:tokens)
  ├─ 2. 批量删除所有 token (DEL auth:token:{uuid} ...)
  ├─ 3. 删除用户 token 列表 (DEL auth:user:{userID}:tokens)
  └─ 4. 返回成功
```

---

### 2.2 认证服务（AuthService）

#### 职责
- 处理登录业务逻辑（验证密码、生成 token）
- 处理登出业务逻辑（撤销 token）
- 处理 token 刷新业务逻辑
- 处理密码修改业务逻辑
- 查询用户权限列表

#### 接口设计

```go
package auth

type Service struct {
	accountStore      AccountStore
	roleStore         RoleStore
	permissionStore   PermissionStore
	tokenManager      *auth.TokenManager
	logger            *zap.Logger
}

// 登录
func (s *Service) Login(ctx context.Context, req *LoginRequest, clientIP string) (*LoginResponse, error)

// 登出
func (s *Service) Logout(ctx context.Context, token string) error

// 刷新 token
func (s *Service) RefreshToken(ctx context.Context, refreshToken string) (newAccessToken string, error)

// 获取当前用户信息和权限
func (s *Service) GetCurrentUser(ctx context.Context, userID uint) (*model.Account, []string, error)

// 修改密码
func (s *Service) ChangePassword(ctx context.Context, userID uint, oldPassword, newPassword string) error
```

#### 登录流程设计

```
Login(username, password, device, clientIP)
  ├─ 1. 根据用户名查询账号 (accountStore.GetByUsername)
  │    ├─ 如果不存在 → 返回 CodeInvalidCredentials ("用户名或密码错误")
  │    └─ 获取账号信息（包含密码哈希、状态）
  │
  ├─ 2. 验证密码 (bcrypt.CompareHashAndPassword)
  │    ├─ 如果错误 → 返回 CodeInvalidCredentials
  │    └─ 密码正确
  │
  ├─ 3. 检查账号状态
  │    ├─ status = 0 → 返回 CodeAccountDisabled ("账号已禁用")
  │    └─ status = 1 → 继续
  │
  ├─ 4. 构造 TokenInfo
  │    ├─ UserID = account.ID
  │    ├─ UserType = account.UserType
  │    ├─ ShopID = account.ShopID
  │    ├─ EnterpriseID = account.EnterpriseID
  │    ├─ Username = account.Username
  │    ├─ LoginTime = time.Now()
  │    ├─ Device = device
  │    └─ IP = clientIP
  │
  ├─ 5. 生成 token 对 (tokenManager.GenerateTokenPair)
  │    ├─ 生成 access token (UUID)
  │    ├─ 生成 refresh token (UUID)
  │    └─ 存储到 Redis
  │
  ├─ 6. 查询用户权限列表 (permissionService.GetUserPermissions)
  │    ├─ 查询用户的所有角色 (accountRoleStore.GetByAccountID)
  │    ├─ 查询角色的所有权限 (rolePermissionStore.GetByRoleIDs)
  │    └─ 返回权限编码列表 (["user:create", "user:update", ...])
  │
  ├─ 7. 构造响应
  │    ├─ AccessToken = access token
  │    ├─ RefreshToken = refresh token
  │    ├─ User = account（隐藏密码字段）
  │    └─ Permissions = 权限列表
  │
  └─ 8. 返回 LoginResponse
```

**安全考虑**：
- ✅ 密码错误和用户名不存在返回相同错误消息，防止用户枚举攻击
- ✅ 密码使用 bcrypt 哈希，成本因子 = 10
- ✅ Token 使用 UUID v4（不可预测）
- ✅ 登录时记录 IP 和设备信息

#### 登出流程设计

```
Logout(token)
  ├─ 1. 验证 token (tokenManager.ValidateAccessToken)
  │    ├─ 如果无效 → 返回 CodeInvalidToken
  │    └─ 获取 TokenInfo
  │
  ├─ 2. 撤销 access token (tokenManager.RevokeToken)
  │    └─ 删除 Redis key: auth:token:{token}
  │
  ├─ 3. 撤销 refresh token（可选）
  │    ├─ 从用户 token 列表获取对应的 refresh token
  │    └─ 删除 Redis key: auth:refresh:{refreshToken}
  │
  └─ 4. 返回成功
```

**设计选择**：
- ❓ **是否同时撤销 refresh token**？
  - **方案 A**：只撤销 access token，保留 refresh token（允许继续刷新）
  - **方案 B**：同时撤销 access token 和 refresh token（完全登出）
  - **推荐**：方案 B（安全性优先，符合用户预期）

#### 密码修改流程设计

```
ChangePassword(userID, oldPassword, newPassword)
  ├─ 1. 查询账号 (accountStore.GetByID)
  │    ├─ 如果不存在 → 返回 CodeNotFound
  │    └─ 获取账号信息（包含密码哈希）
  │
  ├─ 2. 验证旧密码 (bcrypt.CompareHashAndPassword)
  │    ├─ 如果错误 → 返回 CodeInvalidOldPassword
  │    └─ 密码正确
  │
  ├─ 3. 验证新密码格式
  │    ├─ 长度 8-32 位
  │    ├─ 包含字母和数字
  │    └─ 如果不符合 → 返回 CodeInvalidPassword
  │
  ├─ 4. 哈希新密码 (bcrypt.GenerateFromPassword)
  │    └─ cost = 10
  │
  ├─ 5. 更新数据库 (accountStore.UpdatePassword)
  │    └─ 更新 password 字段
  │
  ├─ 6. 撤销所有旧 token (tokenManager.RevokeAllUserTokens)
  │    ├─ 删除用户的所有 access token
  │    ├─ 删除用户的所有 refresh token
  │    └─ 强制用户重新登录
  │
  └─ 7. 返回成功
```

**安全考虑**：
- ✅ 修改密码后立即撤销所有旧 token，防止密码泄露后被利用
- ✅ 需要验证旧密码，防止未授权修改
- ✅ 新密码复杂度要求（后续可加强：特殊字符、大小写等）

---

### 2.3 认证中间件（Auth Middleware）

#### 职责
- 从请求中提取 token
- 验证 token 有效性
- 检查用户类型权限
- 将用户信息注入 context

#### 设计架构

**复用现有中间件**：`pkg/middleware/auth.go` 的 `Auth()` 函数

```go
// 通用认证中间件（已存在）
func Auth(config AuthConfig) fiber.Handler
```

**配置方式**：
```go
// 后台认证中间件
adminAuthMiddleware := middleware.Auth(middleware.AuthConfig{
	TokenValidator: adminTokenValidator,  // 自定义验证函数
	SkipPaths: []string{"/api/admin/login", "/api/admin/refresh-token"},
})

// H5 认证中间件
h5AuthMiddleware := middleware.Auth(middleware.AuthConfig{
	TokenValidator: h5TokenValidator,  // 自定义验证函数
	SkipPaths: []string{"/api/h5/login", "/api/h5/refresh-token"},
})
```

#### Token 验证器设计

**后台 Token 验证器**：
```go
adminTokenValidator := func(token string) (*middleware.UserContextInfo, error) {
	// 1. 验证 token
	tokenInfo, err := tokenManager.ValidateAccessToken(ctx, token)
	if err != nil {
		return nil, err
	}
	
	// 2. 检查用户类型（后台只允许平台用户和代理账号）
	allowedTypes := []int{
		constants.UserTypeSuperAdmin,  // 超级管理员
		constants.UserTypePlatform,    // 平台用户
		constants.UserTypeAgent,       // 代理账号
	}
	if !contains(allowedTypes, tokenInfo.UserType) {
		return nil, errors.New(errors.CodeForbidden, "无权访问后台")
	}
	
	// 3. 返回用户上下文信息
	return &middleware.UserContextInfo{
		UserID:       tokenInfo.UserID,
		UserType:     tokenInfo.UserType,
		ShopID:       tokenInfo.ShopID,
		EnterpriseID: tokenInfo.EnterpriseID,
	}, nil
}
```

**H5 Token 验证器**：
```go
h5TokenValidator := func(token string) (*middleware.UserContextInfo, error) {
	// 1. 验证 token
	tokenInfo, err := tokenManager.ValidateAccessToken(ctx, token)
	if err != nil {
		return nil, err
	}
	
	// 2. 检查用户类型（H5 只允许代理账号和企业账号）
	allowedTypes := []int{
		constants.UserTypeAgent,       // 代理账号
		constants.UserTypeEnterprise,  // 企业账号
	}
	if !contains(allowedTypes, tokenInfo.UserType) {
		return nil, errors.New(errors.CodeForbidden, "无权访问 H5 端")
	}
	
	// 3. 返回用户上下文信息
	return &middleware.UserContextInfo{
		UserID:       tokenInfo.UserID,
		UserType:     tokenInfo.UserType,
		ShopID:       tokenInfo.ShopID,
		EnterpriseID: tokenInfo.EnterpriseID,
	}, nil
}
```

#### 中间件执行流程

```
HTTP 请求
  │
  ├─ 1. Auth 中间件（pkg/middleware/auth.go）
  │    ├─ 检查路径是否在 SkipPaths 中
  │    │   ├─ 是 → 跳过认证，执行下一个中间件
  │    │   └─ 否 → 继续认证流程
  │    │
  │    ├─ 提取 token（从 Authorization header）
  │    │   ├─ 如果缺失 → 返回 CodeMissingToken (401)
  │    │   └─ 提取 "Bearer {token}"
  │    │
  │    ├─ 调用 TokenValidator 函数
  │    │   ├─ 验证 token（查询 Redis）
  │    │   ├─ 检查用户类型权限
  │    │   └─ 返回 UserContextInfo
  │    │
  │    ├─ 将用户信息注入 context
  │    │   ├─ c.Locals(ContextKeyUserID, userInfo.UserID)
  │    │   ├─ c.Locals(ContextKeyUserType, userInfo.UserType)
  │    │   ├─ c.Locals(ContextKeyShopID, userInfo.ShopID)
  │    │   ├─ c.Locals(ContextKeyEnterpriseID, userInfo.EnterpriseID)
  │    │   └─ c.SetUserContext(ctx)  // 用于 GORM 数据权限过滤
  │    │
  │    └─ 执行下一个中间件 (c.Next())
  │
  ├─ 2. Permission 中间件（可选，pkg/middleware/permission.go）
  │    ├─ 从 context 获取 userID
  │    ├─ 检查权限码（如 "user:create"）
  │    └─ 如果无权限 → 返回 CodeForbidden (403)
  │
  └─ 3. 业务处理器（Handler）
       ├─ 从 context 获取用户信息
       └─ 执行业务逻辑
```

---

### 2.4 路由设计

#### 后台路由（/api/admin）

```go
// 公开路由（无需认证）
public := api.Group("/admin")
public.Post("/login", authHandler.Login)                      // 登录
public.Post("/refresh-token", authHandler.RefreshToken)       // 刷新 token

// 受保护路由（需要认证）
protected := api.Group("/admin")
protected.Use(adminAuthMiddleware)                            // 应用认证中间件
protected.Post("/logout", authHandler.Logout)                 // 登出
protected.Get("/me", authHandler.GetMe)                       // 获取当前用户
protected.Put("/password", authHandler.ChangePassword)        // 修改密码

// 其他受保护路由（业务模块）
protected.Get("/accounts", accountHandler.List)               // 账号管理
protected.Get("/roles", roleHandler.List)                     // 角色管理
protected.Get("/permissions", permissionHandler.List)         // 权限管理
// ...
```

#### H5 路由（/api/h5）

```go
// 公开路由（无需认证）
public := api.Group("/h5")
public.Post("/login", authHandler.Login)                      // 登录
public.Post("/refresh-token", authHandler.RefreshToken)       // 刷新 token

// 受保护路由（需要认证）
protected := api.Group("/h5")
protected.Use(h5AuthMiddleware)                               // 应用认证中间件
protected.Post("/logout", authHandler.Logout)                 // 登出
protected.Get("/me", authHandler.GetMe)                       // 获取当前用户
protected.Put("/password", authHandler.ChangePassword)        // 修改密码

// H5 业务路由
protected.Get("/shops", shopHandler.List)                     // 店铺列表
protected.Get("/enterprises", enterpriseHandler.List)         // 企业列表
// ...
```

#### 个人客户路由（/api/c）

```go
// 公开路由（无需认证）
public := api.Group("/c/v1")
public.Post("/login/send-code", personalCustomerHandler.SendCode)  // 发送验证码
public.Post("/login", personalCustomerHandler.Login)               // 登录

// 受保护路由（需要认证）
protected := api.Group("/c/v1")
protected.Use(personalAuthMiddleware)                              // 应用个人客户认证中间件
protected.Get("/profile", personalCustomerHandler.GetProfile)      // 获取个人资料
protected.Put("/profile", personalCustomerHandler.UpdateProfile)   // 更新个人资料
// ...
```

**路由层级关系**：
```
/api
├── /admin         (后台，adminAuthMiddleware)
│   ├── /login     (公开)
│   ├── /logout    (受保护)
│   └── ...
├── /h5            (H5 端，h5AuthMiddleware)
│   ├── /login     (公开)
│   ├── /logout    (受保护)
│   └── ...
└── /c/v1          (个人客户，personalAuthMiddleware)
    ├── /login     (公开)
    ├── /profile   (受保护)
    └── ...
```

---

## 3. 数据模型设计

### 3.1 DTO 设计

#### 登录请求（LoginRequest）

```go
type LoginRequest struct {
	Username string `json:"username" validate:"required" description:"用户名或手机号"`
	Password string `json:"password" validate:"required" description:"密码"`
	Device   string `json:"device" validate:"omitempty,oneof=web h5 mobile" description:"设备类型"`
}
```

#### 登录响应（LoginResponse）

```go
type LoginResponse struct {
	AccessToken  string   `json:"access_token" description:"访问令牌"`
	RefreshToken string   `json:"refresh_token" description:"刷新令牌"`
	ExpiresIn    int64    `json:"expires_in" description:"访问令牌过期时间（秒）"`
	User         UserInfo `json:"user" description:"用户信息"`
	Permissions  []string `json:"permissions" description:"权限列表"`
}

type UserInfo struct {
	ID           uint   `json:"id"`
	Username     string `json:"username"`
	Phone        string `json:"phone"`
	UserType     int    `json:"user_type"`
	UserTypeName string `json:"user_type_name"`  // "超级管理员" / "平台用户" / ...
	ShopID       uint   `json:"shop_id,omitempty"`
	ShopName     string `json:"shop_name,omitempty"`
	EnterpriseID uint   `json:"enterprise_id,omitempty"`
	EnterpriseName string `json:"enterprise_name,omitempty"`
}
```

#### 刷新 Token 请求（RefreshTokenRequest）

```go
type RefreshTokenRequest struct {
	RefreshToken string `json:"refresh_token" validate:"required" description:"刷新令牌"`
}
```

#### 刷新 Token 响应（RefreshTokenResponse）

```go
type RefreshTokenResponse struct {
	AccessToken string `json:"access_token" description:"新的访问令牌"`
	ExpiresIn   int64  `json:"expires_in" description:"过期时间（秒）"`
}
```

#### 修改密码请求（ChangePasswordRequest）

```go
type ChangePasswordRequest struct {
	OldPassword string `json:"old_password" validate:"required" description:"旧密码"`
	NewPassword string `json:"new_password" validate:"required,min=8,max=32" description:"新密码（8-32位）"`
}
```

### 3.2 统一响应格式

所有 API 响应使用 `pkg/response` 的统一格式：

```json
{
  "code": 0,
  "msg": "成功",
  "data": {
    "access_token": "550e8400-e29b-41d4-a716-446655440000",
    "refresh_token": "660e8400-e29b-41d4-a716-446655440001",
    "expires_in": 86400,
    "user": {
      "id": 123,
      "username": "admin",
      "phone": "13800000000",
      "user_type": 2,
      "user_type_name": "平台用户"
    },
    "permissions": ["user:create", "user:update", "user:delete"]
  },
  "timestamp": "2026-01-15T12:00:00Z"
}
```

**错误响应**：
```json
{
  "code": 1010,
  "msg": "用户名或密码错误",
  "data": null,
  "timestamp": "2026-01-15T12:00:00Z"
}
```

---

## 4. 安全设计

### 4.1 密码安全

| 机制 | 实现方式 | 说明 |
|------|---------|------|
| **密码哈希** | bcrypt (cost=10) | 慢哈希算法,防暴力破解 |
| **密码复杂度** | 8-32 位，字母+数字 | Validator 验证 |
| **密码存储** | 不返回给客户端 | `json:"-"` 标签 |
| **密码传输** | HTTPS 加密 | 生产环境强制 HTTPS |

### 4.2 Token 安全

| 机制 | 实现方式 | 说明 |
|------|---------|------|
| **Token 生成** | UUID v4 | 不可预测，128 位随机 |
| **Token 过期** | 24 小时（access）<br>7 天（refresh） | 配置化 |
| **Token 撤销** | Redis 删除 key | 支持立即登出 |
| **Token 绑定** | 记录 IP、设备 | 便于审计（后续可加强验证） |

### 4.3 防暴力破解

| 机制 | 实现方式 | 说明 |
|------|---------|------|
| **限流** | 集成 `pkg/middleware/ratelimit.go` | 同一 IP 每分钟最多 10 次登录尝试 |
| **错误消息** | 统一返回"用户名或密码错误" | 防止用户枚举攻击 |
| **账号锁定** | 后续迭代 | 5 次失败锁定 15 分钟 |

### 4.4 HTTPS 强制

**生产环境**：
- 配置 Fiber HTTPS
- 使用 Let's Encrypt 自动签发证书
- 重定向 HTTP → HTTPS

**开发环境**：
- 允许 HTTP
- 使用自签名证书测试

---

## 5. 性能优化

### 5.1 Redis 连接池

```go
redis.Options{
	Addr:         "localhost:6379",
	PoolSize:     100,           // 连接池大小
	MinIdleConns: 10,            // 最小空闲连接
	MaxRetries:   3,             // 重试次数
	DialTimeout:  5 * time.Second,
	ReadTimeout:  3 * time.Second,
	WriteTimeout: 3 * time.Second,
}
```

### 5.2 Token 验证缓存

**优化策略**：
- ✅ Redis 查询已经很快（< 5ms）
- ❌ 不再添加本地缓存（避免分布式一致性问题）
- ✅ 使用 Redis Pipeline 批量操作（撤销多个 token）

### 5.3 权限查询优化

**问题**：每次登录都查询用户权限，涉及多表 JOIN

**优化方案**：
1. 查询用户的所有角色（`account_role` 表）
2. 批量查询角色的权限（`role_permission` 表，使用 `IN` 查询）
3. 去重权限编码
4. 缓存到 Redis（可选，5 分钟 TTL）

**代码示例**：
```go
// 1. 查询用户角色
roleIDs, err := accountRoleStore.GetRoleIDsByAccountID(ctx, userID)

// 2. 批量查询权限
permissions, err := permissionStore.GetByRoleIDs(ctx, roleIDs)

// 3. 提取权限编码
permCodes := make([]string, 0, len(permissions))
for _, perm := range permissions {
	permCodes = append(permCodes, perm.PermCode)
}

return permCodes, nil
```

---

## 6. 错误处理

### 6.1 错误码扩展

```go
// pkg/errors/codes.go

// 认证相关错误码
CodeMissingToken       = 1002  // 缺失认证令牌
CodeInvalidToken       = 1003  // 无效或过期的令牌
CodeUnauthorized       = 1004  // 未授权
CodeForbidden          = 1005  // 禁止访问

// 登录相关错误码（新增）
CodeInvalidCredentials = 1010  // 用户名或密码错误
CodeAccountDisabled    = 1011  // 账号已禁用
CodeAccountLocked      = 1012  // 账号已锁定
CodePasswordExpired    = 1013  // 密码已过期
CodeInvalidOldPassword = 1014  // 旧密码错误
CodeInvalidPassword    = 1015  // 密码格式不正确（已存在）
CodePasswordTooWeak    = 1016  // 密码强度不足（已存在）
```

### 6.2 错误处理流程

```
业务层错误
  │
  ├─ 返回 AppError（errors.New(code, message)）
  │
  ↓
Handler 层接收错误
  │
  ├─ 直接返回 error（由全局 ErrorHandler 处理）
  │
  ↓
全局 ErrorHandler
  │
  ├─ 提取错误码和消息
  ├─ 生成统一 JSON 响应
  ├─ 设置 HTTP 状态码
  ├─ 记录日志
  └─ 返回给客户端
```

---

## 7. 监控和日志

### 7.1 日志记录

**登录成功**：
```go
logger.Info("用户登录成功",
	zap.Uint("user_id", userID),
	zap.String("username", username),
	zap.String("device", device),
	zap.String("ip", clientIP),
)
```

**登录失败**：
```go
logger.Warn("用户登录失败",
	zap.String("username", username),
	zap.String("reason", "密码错误"),
	zap.String("ip", clientIP),
)
```

**Token 验证失败**：
```go
logger.Warn("Token 验证失败",
	zap.String("token", token[:10]+"..."),  // 只记录前 10 位
	zap.String("reason", "已过期"),
	zap.String("ip", clientIP),
)
```

### 7.2 监控指标

**关键指标**：
- 登录成功率
- 登录失败率（按原因分类）
- Token 验证耗时（P50、P95、P99）
- Redis 连接错误次数
- 并发登录数

**告警规则**：
- 登录失败率 > 30%（可能是暴力破解）
- Token 验证耗时 P95 > 10ms
- Redis 连接错误次数 > 10 次/分钟

---

## 8. 测试策略

### 8.1 单元测试

**覆盖模块**：
- Token 管理器（`pkg/auth/token_test.go`）
- 认证服务（`internal/service/auth/service_test.go`）

**测试方法**：
- 使用 Mock 对象（`github.com/stretchr/testify/mock`）
- Mock `AccountStore`、`Redis`
- 覆盖率目标：≥ 90%

### 8.2 集成测试

**覆盖接口**：
- 后台登录、登出、刷新 token
- H5 登录、登出、刷新 token
- 认证中间件行为

**测试环境**：
- 使用 `testcontainers` 启动真实 PostgreSQL 和 Redis
- 测试完整的请求-响应流程
- 验证 Redis 数据存储正确

### 8.3 性能测试

**测试场景**：
- Token 验证性能（目标：< 5ms）
- 登录性能（目标：< 200ms）
- 并发登录（1000 并发）

**工具**：
- Go Benchmark（`go test -bench`）
- Apache Bench（`ab`）
- Vegeta（负载测试）

---

## 9. 部署和运维

### 9.1 环境配置

**开发环境**（`configs/config.dev.yaml`）：
```yaml
jwt:
  secret_key: "dev-secret-key-32-characters-long"
  access_token_ttl: 24h
  refresh_token_ttl: 168h  # 7 days

redis:
  address: "localhost:6379"
  password: ""
  db: 0
```

**生产环境**（`configs/config.prod.yaml`）：
```yaml
jwt:
  secret_key: "${JWT_SECRET_KEY}"  # 从环境变量读取
  access_token_ttl: 24h
  refresh_token_ttl: 168h

redis:
  address: "${REDIS_ADDR}"
  password: "${REDIS_PASSWORD}"
  db: 0
```

### 9.2 Redis 高可用

**生产环境推荐**：
- 使用 Redis 哨兵模式（Sentinel）或集群模式（Cluster）
- 配置主从复制
- 定期备份（RDB + AOF）

**配置示例**：
```yaml
redis:
  mode: sentinel  # sentinel / cluster / standalone
  master_name: "mymaster"
  sentinel_addrs:
    - "sentinel1:26379"
    - "sentinel2:26379"
    - "sentinel3:26379"
```

### 9.3 健康检查

**API 健康检查**：
```
GET /health
```

**响应**：
```json
{
  "status": "ok",
  "redis": "connected",
  "postgres": "connected"
}
```

---

## 10. 后续优化方向

1. **Token Rotation**：刷新 token 时同时更新 refresh token
2. **设备指纹**：绑定 token 到设备，防止 token 被盗用
3. **IP 白名单**：限制特定 IP 访问
4. **账号锁定策略**：登录失败 5 次锁定 15 分钟
5. **两步验证（2FA）**：短信验证码、TOTP
6. **单点登录（SSO）**：统一登录入口
7. **审计日志**：记录登录、权限变更等操作
8. **密码策略**：强制定期修改、密码历史记录
9. **OAuth 第三方登录**：微信企业登录、钉钉登录
10. **实时踢人**：管理员强制下线用户

---

**文档状态**: 待审批  
**创建时间**: 2026-01-15  
**最后更新**: 2026-01-15
