351 lines
14 KiB
Markdown
351 lines
14 KiB
Markdown
|
|
# 用户登录
|
|||
|
|
|
|||
|
|
<cite>
|
|||
|
|
**本文引用的文件**
|
|||
|
|
- [auth_handler.go](file://internal/handler/auth_handler.go)
|
|||
|
|
- [routes.go](file://internal/handler/routes.go)
|
|||
|
|
- [jwt.go](file://pkg/auth/jwt.go)
|
|||
|
|
- [password.go](file://pkg/auth/password.go)
|
|||
|
|
- [user_service.go](file://internal/service/user_service.go)
|
|||
|
|
- [common.go](file://internal/types/common.go)
|
|||
|
|
- [response.go](file://internal/model/response.go)
|
|||
|
|
- [user.go](file://internal/model/user.go)
|
|||
|
|
- [token.go](file://internal/model/token.go)
|
|||
|
|
- [token_service.go](file://internal/service/token_service.go)
|
|||
|
|
- [auth.go](file://internal/middleware/auth.go)
|
|||
|
|
</cite>
|
|||
|
|
|
|||
|
|
## 目录
|
|||
|
|
1. [简介](#简介)
|
|||
|
|
2. [项目结构](#项目结构)
|
|||
|
|
3. [核心组件](#核心组件)
|
|||
|
|
4. [架构总览](#架构总览)
|
|||
|
|
5. [详细组件分析](#详细组件分析)
|
|||
|
|
6. [依赖关系分析](#依赖关系分析)
|
|||
|
|
7. [性能考量](#性能考量)
|
|||
|
|
8. [故障排查指南](#故障排查指南)
|
|||
|
|
9. [结论](#结论)
|
|||
|
|
10. [附录](#附录)
|
|||
|
|
|
|||
|
|
## 简介
|
|||
|
|
本文件面向“用户登录”API,围绕 /api/v1/auth/login 端点进行完整说明,覆盖:
|
|||
|
|
- HTTP 方法与路由
|
|||
|
|
- 请求体结构(用户名或邮箱登录)
|
|||
|
|
- 响应格式(包含JWT token与用户信息)
|
|||
|
|
- 错误码与错误处理
|
|||
|
|
- JWT令牌生成流程(访问令牌与声明)
|
|||
|
|
- 登录成功/失败后的审计日志记录(IP、User-Agent)
|
|||
|
|
- 安全建议(防暴力破解、错误提示模糊化)
|
|||
|
|
|
|||
|
|
## 项目结构
|
|||
|
|
该登录流程涉及以下模块协作:
|
|||
|
|
- 路由层:注册 /api/v1/auth/login 路由
|
|||
|
|
- 控制器层:处理登录请求、参数校验、调用服务层
|
|||
|
|
- 服务层:用户查找、密码校验、令牌生成、登录日志记录
|
|||
|
|
- 认证与密码:JWT服务、bcrypt密码加解密
|
|||
|
|
- 数据模型:用户、登录日志、令牌
|
|||
|
|
- 中间件:JWT认证中间件用于后续受保护接口
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
graph TB
|
|||
|
|
Client["客户端"] --> Routes["路由注册<br/>/api/v1/auth/login"]
|
|||
|
|
Routes --> Handler["控制器 Login()<br/>参数绑定/错误处理"]
|
|||
|
|
Handler --> Service["服务层 LoginUser()<br/>查找用户/校验密码/生成JWT"]
|
|||
|
|
Service --> JWT["JWT服务 GenerateToken()<br/>签发访问令牌"]
|
|||
|
|
Service --> ModelUser["用户模型 User"]
|
|||
|
|
Service --> ModelLog["登录日志 UserLoginLog"]
|
|||
|
|
Handler --> Resp["统一响应 Response/ErrorResponse"]
|
|||
|
|
Client --> Middleware["认证中间件 AuthMiddleware()<br/>Bearer Token 校验"]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
图表来源
|
|||
|
|
- [routes.go](file://internal/handler/routes.go#L16-L25)
|
|||
|
|
- [auth_handler.go](file://internal/handler/auth_handler.go#L86-L147)
|
|||
|
|
- [user_service.go](file://internal/service/user_service.go#L70-L122)
|
|||
|
|
- [jwt.go](file://pkg/auth/jwt.go#L32-L53)
|
|||
|
|
- [user.go](file://internal/model/user.go#L1-L21)
|
|||
|
|
- [user.go](file://internal/model/user.go#L52-L71)
|
|||
|
|
- [auth.go](file://internal/middleware/auth.go#L12-L56)
|
|||
|
|
|
|||
|
|
章节来源
|
|||
|
|
- [routes.go](file://internal/handler/routes.go#L16-L25)
|
|||
|
|
- [auth_handler.go](file://internal/handler/auth_handler.go#L86-L147)
|
|||
|
|
|
|||
|
|
## 核心组件
|
|||
|
|
- 路由与控制器
|
|||
|
|
- /api/v1/auth/login 由控制器 Login 处理,负责参数绑定、IP/User-Agent采集、调用服务层并返回统一响应。
|
|||
|
|
- 服务层
|
|||
|
|
- LoginUser 支持用户名或邮箱登录;校验用户状态与密码;生成JWT;更新最后登录时间;记录成功/失败日志。
|
|||
|
|
- 认证与密码
|
|||
|
|
- JWT服务提供 GenerateToken/ValidateToken;密码使用 bcrypt 进行哈希与校验。
|
|||
|
|
- 数据模型
|
|||
|
|
- User:用户信息;UserLoginLog:登录审计日志;Token:令牌模型(用于后续刷新/失效等场景)。
|
|||
|
|
- 统一响应
|
|||
|
|
- Response/ErrorResponse 提供统一的状态码与消息封装。
|
|||
|
|
|
|||
|
|
章节来源
|
|||
|
|
- [auth_handler.go](file://internal/handler/auth_handler.go#L86-L147)
|
|||
|
|
- [user_service.go](file://internal/service/user_service.go#L70-L122)
|
|||
|
|
- [jwt.go](file://pkg/auth/jwt.go#L32-L53)
|
|||
|
|
- [password.go](file://pkg/auth/password.go#L7-L21)
|
|||
|
|
- [user.go](file://internal/model/user.go#L1-L21)
|
|||
|
|
- [user.go](file://internal/model/user.go#L52-L71)
|
|||
|
|
- [token.go](file://internal/model/token.go#L1-L15)
|
|||
|
|
- [response.go](file://internal/model/response.go#L20-L53)
|
|||
|
|
|
|||
|
|
## 架构总览
|
|||
|
|
登录端到端序列图(POST /api/v1/auth/login):
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
sequenceDiagram
|
|||
|
|
participant C as "客户端"
|
|||
|
|
participant R as "路由"
|
|||
|
|
participant H as "控制器 Login()"
|
|||
|
|
participant S as "服务层 LoginUser()"
|
|||
|
|
participant J as "JWT服务"
|
|||
|
|
participant U as "用户模型"
|
|||
|
|
participant L as "登录日志"
|
|||
|
|
C->>R : "POST /api/v1/auth/login"
|
|||
|
|
R->>H : "进入 Login 控制器"
|
|||
|
|
H->>H : "ShouldBindJSON 绑定请求体"
|
|||
|
|
H->>S : "LoginUser(jwtService, username, password, ip, ua)"
|
|||
|
|
S->>U : "根据用户名或邮箱查找用户"
|
|||
|
|
S->>S : "校验用户状态"
|
|||
|
|
S->>S : "bcrypt 校验密码"
|
|||
|
|
S->>J : "GenerateToken(userID, username, role)"
|
|||
|
|
J-->>S : "返回JWT字符串"
|
|||
|
|
S->>U : "更新 last_login_at"
|
|||
|
|
S->>L : "logSuccessLogin/logFailedLogin"
|
|||
|
|
S-->>H : "返回 user, token"
|
|||
|
|
H-->>C : "200 成功响应或401失败响应"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
图表来源
|
|||
|
|
- [auth_handler.go](file://internal/handler/auth_handler.go#L86-L147)
|
|||
|
|
- [user_service.go](file://internal/service/user_service.go#L70-L122)
|
|||
|
|
- [jwt.go](file://pkg/auth/jwt.go#L32-L53)
|
|||
|
|
- [user.go](file://internal/model/user.go#L1-L21)
|
|||
|
|
- [user.go](file://internal/model/user.go#L52-L71)
|
|||
|
|
|
|||
|
|
## 详细组件分析
|
|||
|
|
|
|||
|
|
### 接口定义与请求/响应
|
|||
|
|
- HTTP 方法与路径
|
|||
|
|
- 方法:POST
|
|||
|
|
- 路径:/api/v1/auth/login
|
|||
|
|
- 请求体结构
|
|||
|
|
- 字段:username(支持用户名或邮箱)、password(6-128字符)
|
|||
|
|
- 参数校验:必填、长度约束
|
|||
|
|
- 响应格式
|
|||
|
|
- 成功:统一响应,data 包含 token 与 userInfo
|
|||
|
|
- 失败:统一错误响应,包含 code 与 message
|
|||
|
|
- 错误码
|
|||
|
|
- 400:请求参数错误
|
|||
|
|
- 401:未授权(用户名/邮箱或密码错误、账号被禁用等)
|
|||
|
|
|
|||
|
|
章节来源
|
|||
|
|
- [auth_handler.go](file://internal/handler/auth_handler.go#L86-L147)
|
|||
|
|
- [common.go](file://internal/types/common.go#L27-L31)
|
|||
|
|
- [response.go](file://internal/model/response.go#L20-L53)
|
|||
|
|
|
|||
|
|
### 请求体结构与参数校验
|
|||
|
|
- 请求体字段
|
|||
|
|
- username:支持用户名或邮箱
|
|||
|
|
- password:6-128字符
|
|||
|
|
- 参数校验
|
|||
|
|
- Gin 绑定时会触发结构体 tag 校验(必填、长度范围)
|
|||
|
|
- 实际行为
|
|||
|
|
- 控制器层在绑定失败时直接返回400
|
|||
|
|
|
|||
|
|
章节来源
|
|||
|
|
- [common.go](file://internal/types/common.go#L27-L31)
|
|||
|
|
- [auth_handler.go](file://internal/handler/auth_handler.go#L101-L109)
|
|||
|
|
|
|||
|
|
### 登录流程与JWT生成
|
|||
|
|
- 登录流程要点
|
|||
|
|
- 识别登录方式:包含@视为邮箱,否则为用户名
|
|||
|
|
- 校验用户状态(仅状态为正常才允许登录)
|
|||
|
|
- 使用 bcrypt 校验密码
|
|||
|
|
- 生成JWT访问令牌(包含用户ID、用户名、角色等声明)
|
|||
|
|
- 更新最后登录时间
|
|||
|
|
- 记录登录日志(成功/失败均记录IP与User-Agent)
|
|||
|
|
- JWT声明内容
|
|||
|
|
- 包含 user_id、username、role
|
|||
|
|
- 默认包含过期时间、签发时间、生效时间、签发者等注册声明
|
|||
|
|
- 令牌有效期
|
|||
|
|
- 由JWT服务配置的过期小时数决定
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
flowchart TD
|
|||
|
|
Start(["进入 LoginUser"]) --> Detect["判断登录方式<br/>用户名或邮箱"]
|
|||
|
|
Detect --> FindUser["查找用户"]
|
|||
|
|
FindUser --> Status{"用户状态正常?"}
|
|||
|
|
Status -- 否 --> LogFail["记录失败日志<br/>原因:账号被禁用"]
|
|||
|
|
LogFail --> ReturnErr["返回错误"]
|
|||
|
|
Status -- 是 --> CheckPwd["bcrypt 校验密码"]
|
|||
|
|
CheckPwd --> PwdOK{"密码正确?"}
|
|||
|
|
PwdOK -- 否 --> LogFail2["记录失败日志<br/>原因:密码错误"]
|
|||
|
|
LogFail2 --> ReturnErr
|
|||
|
|
PwdOK -- 是 --> GenToken["GenerateToken(userID, username, role)"]
|
|||
|
|
GenToken --> UpdateTime["更新 last_login_at"]
|
|||
|
|
UpdateTime --> LogSuccess["记录成功日志"]
|
|||
|
|
LogSuccess --> Done(["返回 user, token"])
|
|||
|
|
ReturnErr --> Done
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
图表来源
|
|||
|
|
- [user_service.go](file://internal/service/user_service.go#L70-L122)
|
|||
|
|
- [jwt.go](file://pkg/auth/jwt.go#L32-L53)
|
|||
|
|
- [password.go](file://pkg/auth/password.go#L16-L21)
|
|||
|
|
- [user.go](file://internal/model/user.go#L1-L21)
|
|||
|
|
- [user.go](file://internal/model/user.go#L52-L71)
|
|||
|
|
|
|||
|
|
章节来源
|
|||
|
|
- [user_service.go](file://internal/service/user_service.go#L70-L122)
|
|||
|
|
- [jwt.go](file://pkg/auth/jwt.go#L32-L53)
|
|||
|
|
- [password.go](file://pkg/auth/password.go#L16-L21)
|
|||
|
|
|
|||
|
|
### 响应格式与错误码
|
|||
|
|
- 成功响应
|
|||
|
|
- data:包含 token 与 userInfo(id、username、email、avatar、points、role、status、last_login_at、created_at、updated_at)
|
|||
|
|
- 失败响应
|
|||
|
|
- 400:请求参数错误
|
|||
|
|
- 401:未授权(用户名/邮箱或密码错误、账号被禁用)
|
|||
|
|
- 统一响应封装
|
|||
|
|
- Response/ErrorResponse 提供 code、message、data/error 字段
|
|||
|
|
|
|||
|
|
章节来源
|
|||
|
|
- [auth_handler.go](file://internal/handler/auth_handler.go#L131-L147)
|
|||
|
|
- [common.go](file://internal/types/common.go#L107-L126)
|
|||
|
|
- [response.go](file://internal/model/response.go#L20-L53)
|
|||
|
|
|
|||
|
|
### 登录日志记录(IP、User-Agent)
|
|||
|
|
- 记录内容
|
|||
|
|
- 成功/失败均记录:用户ID、IP地址、User-Agent、登录方式(PASSWORD)、是否成功、失败原因
|
|||
|
|
- 记录时机
|
|||
|
|
- 成功:登录成功后记录
|
|||
|
|
- 失败:用户不存在、账号禁用、密码错误等情况下记录
|
|||
|
|
- 存储位置
|
|||
|
|
- 写入 user_login_logs 表
|
|||
|
|
|
|||
|
|
章节来源
|
|||
|
|
- [auth_handler.go](file://internal/handler/auth_handler.go#L111-L129)
|
|||
|
|
- [user_service.go](file://internal/service/user_service.go#L203-L226)
|
|||
|
|
- [user.go](file://internal/model/user.go#L52-L71)
|
|||
|
|
|
|||
|
|
### 安全考虑与建议
|
|||
|
|
- 防暴力破解
|
|||
|
|
- 建议在网关或应用层增加速率限制(例如基于IP或用户名的限流),在失败时延长冷却时间或临时封禁
|
|||
|
|
- 可结合验证码机制(当前登录接口未强制验证码,但注册/重置密码有验证码流程)
|
|||
|
|
- 错误提示模糊化
|
|||
|
|
- 当前服务层对“用户不存在/密码错误/账号禁用”均返回统一的“用户名/邮箱或密码错误”,避免泄露具体原因
|
|||
|
|
- 令牌管理
|
|||
|
|
- 当前登录仅返回JWT访问令牌;若需刷新令牌,可参考后续令牌服务(刷新/失效等)能力
|
|||
|
|
- 传输安全
|
|||
|
|
- 建议仅在HTTPS下提供登录接口,防止凭据被窃听
|
|||
|
|
|
|||
|
|
章节来源
|
|||
|
|
- [user_service.go](file://internal/service/user_service.go#L88-L103)
|
|||
|
|
- [auth_handler.go](file://internal/handler/auth_handler.go#L116-L129)
|
|||
|
|
|
|||
|
|
## 依赖关系分析
|
|||
|
|
- 控制器依赖
|
|||
|
|
- 控制器 Login 依赖:JWT服务、日志、Redis(注册流程)、服务层 LoginUser
|
|||
|
|
- 服务层依赖
|
|||
|
|
- LoginUser 依赖:用户仓库(查找用户)、JWT服务(生成令牌)、密码工具(bcrypt)、登录日志仓库(写入日志)
|
|||
|
|
- 认证依赖
|
|||
|
|
- JWT服务依赖:golang-jwt库;密码工具依赖:bcrypt
|
|||
|
|
- 模型依赖
|
|||
|
|
- User、UserLoginLog、Token 模型用于数据持久化与审计
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
graph LR
|
|||
|
|
H["控制器 Login()"] --> S["服务层 LoginUser()"]
|
|||
|
|
H --> J["JWT服务 GenerateToken()"]
|
|||
|
|
S --> U["User 模型"]
|
|||
|
|
S --> L["UserLoginLog 模型"]
|
|||
|
|
S --> P["bcrypt 密码校验"]
|
|||
|
|
H --> R["路由 RegisterRoutes()"]
|
|||
|
|
M["认证中间件 AuthMiddleware()"] --> J
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
图表来源
|
|||
|
|
- [auth_handler.go](file://internal/handler/auth_handler.go#L86-L147)
|
|||
|
|
- [user_service.go](file://internal/service/user_service.go#L70-L122)
|
|||
|
|
- [jwt.go](file://pkg/auth/jwt.go#L32-L53)
|
|||
|
|
- [password.go](file://pkg/auth/password.go#L16-L21)
|
|||
|
|
- [user.go](file://internal/model/user.go#L1-L21)
|
|||
|
|
- [user.go](file://internal/model/user.go#L52-L71)
|
|||
|
|
- [routes.go](file://internal/handler/routes.go#L16-L25)
|
|||
|
|
- [auth.go](file://internal/middleware/auth.go#L12-L56)
|
|||
|
|
|
|||
|
|
章节来源
|
|||
|
|
- [auth_handler.go](file://internal/handler/auth_handler.go#L86-L147)
|
|||
|
|
- [user_service.go](file://internal/service/user_service.go#L70-L122)
|
|||
|
|
- [jwt.go](file://pkg/auth/jwt.go#L32-L53)
|
|||
|
|
- [password.go](file://pkg/auth/password.go#L16-L21)
|
|||
|
|
- [user.go](file://internal/model/user.go#L1-L21)
|
|||
|
|
- [user.go](file://internal/model/user.go#L52-L71)
|
|||
|
|
- [routes.go](file://internal/handler/routes.go#L16-L25)
|
|||
|
|
- [auth.go](file://internal/middleware/auth.go#L12-L56)
|
|||
|
|
|
|||
|
|
## 性能考量
|
|||
|
|
- 登录路径涉及数据库查询与密码校验,建议:
|
|||
|
|
- 对用户表建立合适的索引(username、email)
|
|||
|
|
- 密码校验使用 bcrypt,默认成本适中,可根据硬件能力调整
|
|||
|
|
- 登录日志写入采用异步或批量策略(当前为同步写入,注意高并发下的I/O影响)
|
|||
|
|
- JWT生成与校验为CPU密集度较低的操作,主要瓶颈在数据库与密码校验
|
|||
|
|
|
|||
|
|
## 故障排查指南
|
|||
|
|
- 常见问题与定位
|
|||
|
|
- 400 参数错误:检查请求体字段是否缺失或长度不符合要求
|
|||
|
|
- 401 未授权:核对用户名/邮箱是否存在、账号状态是否正常、密码是否正确
|
|||
|
|
- 登录日志未记录:确认日志写入是否成功,检查数据库连接与表结构
|
|||
|
|
- 日志与审计
|
|||
|
|
- 成功/失败日志均包含 IP 与 User-Agent,便于追踪来源设备与来源网络
|
|||
|
|
- 令牌相关
|
|||
|
|
- 若后续引入刷新令牌,可参考令牌服务中的刷新/失效逻辑
|
|||
|
|
|
|||
|
|
章节来源
|
|||
|
|
- [auth_handler.go](file://internal/handler/auth_handler.go#L101-L129)
|
|||
|
|
- [user_service.go](file://internal/service/user_service.go#L203-L226)
|
|||
|
|
- [response.go](file://internal/model/response.go#L20-L53)
|
|||
|
|
|
|||
|
|
## 结论
|
|||
|
|
/api/v1/auth/login 提供了完整的用户名或邮箱登录能力,具备参数校验、密码校验、JWT签发与登录日志记录。当前实现聚焦于登录流程本身,后续可在速率限制、验证码、刷新令牌等方面进一步增强安全性与可用性。
|
|||
|
|
|
|||
|
|
## 附录
|
|||
|
|
|
|||
|
|
### API定义与示例
|
|||
|
|
|
|||
|
|
- 端点
|
|||
|
|
- 方法:POST
|
|||
|
|
- 路径:/api/v1/auth/login
|
|||
|
|
- 请求体
|
|||
|
|
- username:字符串,必填,支持用户名或邮箱
|
|||
|
|
- password:字符串,必填,长度6-128
|
|||
|
|
- 成功响应
|
|||
|
|
- code:200
|
|||
|
|
- data:包含 token 与 userInfo
|
|||
|
|
- 示例(结构示意)
|
|||
|
|
- data: { token: "...", userInfo: { id, username, email, avatar, points, role, status, last_login_at, created_at, updated_at } }
|
|||
|
|
- 失败响应
|
|||
|
|
- 400:请求参数错误
|
|||
|
|
- 401:未授权(用户名/邮箱或密码错误、账号被禁用)
|
|||
|
|
|
|||
|
|
章节来源
|
|||
|
|
- [auth_handler.go](file://internal/handler/auth_handler.go#L86-L147)
|
|||
|
|
- [common.go](file://internal/types/common.go#L27-L31)
|
|||
|
|
- [common.go](file://internal/types/common.go#L107-L126)
|
|||
|
|
- [response.go](file://internal/model/response.go#L20-L53)
|
|||
|
|
|
|||
|
|
### JWT声明与令牌生命周期
|
|||
|
|
- 声明内容
|
|||
|
|
- user_id、username、role
|
|||
|
|
- 过期时间、签发时间、生效时间、签发者等注册声明
|
|||
|
|
- 令牌类型
|
|||
|
|
- 当前登录返回访问令牌;刷新/失效等能力可参考令牌服务
|
|||
|
|
|
|||
|
|
章节来源
|
|||
|
|
- [jwt.go](file://pkg/auth/jwt.go#L24-L53)
|
|||
|
|
- [token_service.go](file://internal/service/token_service.go#L151-L238)
|