Feat/1.4.0 - #94
Feat/1.4.0#94
Conversation
docs: 更新接口文档 chore: update gitignore
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! 本次拉取请求主要围绕核心服务架构进行了重大重构和功能扩展。认证系统升级为更安全的双Token机制,并引入了全面的会话管理和用户状态控制功能。应用的启动流程被模块化,提升了代码组织性和可维护性。同时,新增了多项用户API,包括课表绑定次数查询、登录天数统计和个人课表重置,以丰富用户体验。此外,还对RBAC常量进行了统一管理,并调整了部分业务逻辑的参数限制,确保了系统的健壮性和灵活性。 Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Pull request overview
该 PR 主要围绕鉴权体系升级为“双 Token(access/refresh)+ Redis 会话管理”,同时统一 RBAC 常量引用、拆分启动流程,并补充若干用户侧/课表侧新接口。
Changes:
- 鉴权:新增 access/refresh token、刷新/登出/踢下线/封禁等能力,并在中间件侧接入 Redis 会话校验
- RBAC:将散落的角色/权限常量统一迁移到
pkg/constant并全局替换引用 - API:新增登录天数查询、课表绑定次数查询、重置个人课表;点评内容长度上限放宽到 500;启动流程模块化
Reviewed changes
Copilot reviewed 27 out of 28 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| pkg/utils/rbac.go | 管理员角色判断改为使用统一常量 |
| pkg/utils/auth.go | 引入 TokenClaims/会话结构、access/refresh token 生成与解析、设备信息解析 |
| pkg/constant/auth.go | 新增鉴权相关常量(token 类型、Redis key、TTL 等) |
| internal/services/user_activity_service.go | 新增查询过去 N 天游登录天数方法;RBAC tag 使用统一常量 |
| internal/services/rbac_service.go | SeedDefaults/快照判断等改为使用统一常量 |
| internal/services/notification_service.go | 运营/管理员角色判断改为使用统一常量 |
| internal/services/coursetable_service.go | 新增绑定次数查询与重置个人课表服务方法;权限常量统一 |
| internal/services/contribution_service.go | 角色判断改为使用统一常量 |
| internal/services/auth_service.go | 双 Token + Redis 会话/撤销/封禁体系落地,新增刷新/登出/封禁等服务能力 |
| internal/router/router.go | 注入 cache 到 AuthMiddleware/AuthService;新增 refresh/logout/login-days/bind-count/reset-schedule/admin user auth 管理路由 |
| internal/pkg/cache/redis.go | 增补 Set/SortedSet 的 Redis 操作封装(SMembers/SRem/ZRangeByScore/ZRem 等) |
| internal/pkg/cache/doc.go | Cache 接口补齐集合/有序集合新方法定义 |
| internal/models/rbac.go | 移除 models 内 RBAC 常量别名,统一由 constant 提供 |
| internal/middleware/middleware.go | AuthMiddleware 改为解析 TokenClaims 并基于 Redis 做 blocked/revoked 校验;RequestID 回写响应头并 enrich context |
| internal/handlers/user_activity_handler.go | 新增登录天数查询 handler |
| internal/handlers/helper/helper.go | 新增读取会话 SID 的 helper |
| internal/handlers/coursetable_handler.go | 新增绑定次数查询与重置个人课表 handler |
| internal/handlers/auth_handler.go | 登录/Mock 登录改为传入 UA;新增 refresh/logout/logout-all 与管理员踢下线/封禁/解封/详情接口 |
| internal/dto/response/user.go | 登录响应改为返回 refresh token 与 access 过期时间;新增会话/封禁详情响应结构 |
| internal/dto/request/user-auth.go | 新增 refresh/ban 请求 DTO;更新 mock 用户类型说明 |
| internal/dto/request/review.go | 点评内容最大长度从 200 放宽到 500 |
| internal/config/config.go | 新增 refresh secret 与 access/refresh TTL 配置项 |
| internal/bootstrap/worker.go | 抽离 worker 初始化逻辑 |
| internal/bootstrap/redis.go | 抽离 Redis 初始化与项目热数据预热逻辑 |
| internal/bootstrap/app.go | 新增 App 生命周期封装(init/run/shutdown) |
| docs/api.md | 补充 pomodoro/dictionary 文档片段 |
| cmd/apiserver/main.go | main.go 简化为 bootstrap 驱动启动 |
| .gitignore | 忽略 Claude 相关文件与临时目录 |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } | ||
|
|
||
| const pastDays = 100 | ||
| days, err := h.userActivityService.GetUserLoginDays(c, userID, pastDays) |
There was a problem hiding this comment.
这里调用 service 时传入的是 c(*gin.Context)。项目里多数 handler 都传 c.Request.Context(),并且鉴权中间件会通过 c.Request = c.Request.WithContext(...) 注入结构化字段;继续传 c 可能导致这些 context 信息在下游(DB/日志)不可见或不一致。建议改为 c.Request.Context() 以保持一致性。
| days, err := h.userActivityService.GetUserLoginDays(c, userID, pastDays) | |
| days, err := h.userActivityService.GetUserLoginDays(c.Request.Context(), userID, pastDays) |
There was a problem hiding this comment.
其实都一样。。。 c 实现了 context.Context 接口,它也是从c.Request.Context() 返回的context
| count, err := h.courseTableService.GetUserBindCount(c, userID) | ||
| if err != nil { | ||
| helper.ErrorResponse(c, http.StatusBadRequest, err.Error()) | ||
| return | ||
| } | ||
|
|
||
| helper.SuccessResponse(c, gin.H{"bind_count": count}) | ||
| } | ||
|
|
||
| // ResetSchedule 重置用户个人课表 | ||
| // @Summary 重置个人课表 | ||
| // @Description 删除当前用户在指定学期的个人编辑课表数据,恢复为班级默认课表 | ||
| // @Tags 课程表 | ||
| // @Accept json | ||
| // @Produce json | ||
| // @Param semester query string true "学期" | ||
| // @Success 200 {object} helper.Response | ||
| // @Failure 400 {object} helper.Response | ||
| // @Failure 401 {object} helper.Response | ||
| // @Router /api/v0/coursetable/schedule [delete] | ||
| func (h *CourseTableHandler) ResetSchedule(c *gin.Context) { | ||
| userID := helper.GetUserID(c) | ||
| if userID == 0 { | ||
| helper.ErrorResponse(c, http.StatusUnauthorized, "未获取到用户信息") | ||
| return | ||
| } | ||
|
|
||
| semester := c.Query("semester") | ||
| if semester == "" { | ||
| helper.ValidateResponse(c, "参数验证失败: semester 不能为空") | ||
| return | ||
| } | ||
|
|
||
| if err := h.courseTableService.ResetUserSchedule(c, userID, semester); err != nil { | ||
| helper.ErrorResponse(c, http.StatusBadRequest, err.Error()) |
There was a problem hiding this comment.
GetBindCount/ResetSchedule 这里调用 service 传入的是 c(*gin.Context)。当前代码库多数 handler 使用 c.Request.Context(),而且鉴权中间件会 enrich 到 c.Request.Context();建议新接口这里也改为 c.Request.Context(),避免上下游 context 传递不一致。
| ### 番茄钟 (Pomodoro) | ||
|
|
||
| #### 增加番茄钟次数 (需认证) | ||
| ```http | ||
| POST /api/v0/pomodoro/increment | ||
| Authorization: Bearer <JWT_TOKEN> | ||
|
|
||
| Result: { "message": "番茄钟次数已增加" } | ||
| ``` | ||
|
|
||
| #### 获取番茄钟排名 (需认证) | ||
| ```http | ||
| GET /api/v0/pomodoro/ranking | ||
| Authorization: Bearer <JWT_TOKEN> | ||
|
|
||
| Result: []response.PomodoroRankingItem | ||
| ``` | ||
|
|
||
| ### 词典 / 随机取词 (Dictionary) | ||
|
|
||
| #### 随机获取一个词 (需认证) | ||
| ```http | ||
| GET /api/v0/dictionary/word | ||
| Authorization: Bearer <JWT_TOKEN> | ||
|
|
||
| Result: models.Dictionary | ||
| ``` |
There was a problem hiding this comment.
本次 PR 引入了双 Token(refresh_token、access_token_expires_at)、新增 /api/v0/auth/refresh、/api/v0/auth/logout(-all)、以及点评 content 长度放宽到 500,但文档仍沿用旧的“单 token + <=200”描述。建议在 API 文档中同步更新登录响应结构、刷新/登出/封禁相关接口,以及点评字段长度说明,避免客户端按旧协议实现。
| token, err := jwt.ParseWithClaims(tokenString, &TokenClaims{}, func(token *jwt.Token) (any, error) { | ||
| return []byte(secret), nil | ||
| }) |
There was a problem hiding this comment.
ParseToken parses JWTs without restricting/validating the signing algorithm (the keyfunc always returns the secret). This reintroduces an alg-confusion class of issues compared to the previous middleware check. Consider enforcing allowed methods (e.g., HS256 only) via jwt.WithValidMethods and/or checking token.Method inside the keyfunc before returning the key.
| token, err := jwt.ParseWithClaims(tokenString, &TokenClaims{}, func(token *jwt.Token) (any, error) { | |
| return []byte(secret), nil | |
| }) | |
| token, err := jwt.ParseWithClaims( | |
| tokenString, | |
| &TokenClaims{}, | |
| func(token *jwt.Token) (any, error) { | |
| // Enforce expected signing method (HS256) to prevent alg confusion | |
| if token.Method != jwt.SigningMethodHS256 { | |
| return nil, jwt.ErrSignatureInvalid | |
| } | |
| return []byte(secret), nil | |
| }, | |
| jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Alg()}), | |
| ) |
| // InitRedisCache 初始化Redis客户端并建立缓存连接。 | ||
| // 当Redis不可用时会优雅降级,相关功能(幂等性等)将被禁用。 | ||
| func InitRedisCache(cfg *config.Config) { |
There was a problem hiding this comment.
InitRedisCache 注释写的是“Redis 不可用时会优雅降级(幂等性等功能禁用)”,但当前 PR 的鉴权流程(登录/刷新/鉴权中间件)已经强依赖 Redis:Redis 不可用会导致无法登录、所有需认证接口直接 503。建议要么更新这里的降级说明并在启动时将 Redis 视为必需依赖,要么实现真正的降级策略(例如只在双 Token 模式下要求 Redis)。
| {PermissionTag: constant.PermissionMaterialCategoryGet, Name: "资料分类查看", Description: ""}, | ||
| {PermissionTag: constant.PermissionQuestion, Name: "刷题访问", Description: ""}, | ||
| {PermissionTag: constant.PermissionPomodoro, Name: "番茄钟", Description: ""}, | ||
| {PermissionTag: constant.PermissionDictionary, Name: "每日一词", Description: ""}, |
There was a problem hiding this comment.
SeedDefaults 当前创建/绑定的权限列表不包含路由里实际使用的一些 permission tag(例如 statistic.get、chat.study、notification.get.admin、s3.manage 等)。在 InitRbac=true 的新环境中,这会导致这些接口永远无法通过 RequirePermission 校验。建议补齐缺失的 permissionSeeds + roleBindings,或明确说明这些权限不由 SeedDefaults 管理。
| {PermissionTag: constant.PermissionDictionary, Name: "每日一词", Description: ""}, | |
| {PermissionTag: constant.PermissionDictionary, Name: "每日一词", Description: ""}, | |
| {PermissionTag: constant.PermissionStatisticGet, Name: "统计查看", Description: ""}, | |
| {PermissionTag: constant.PermissionChatStudy, Name: "学习聊天", Description: ""}, | |
| {PermissionTag: constant.PermissionNotificationGetAdmin, Name: "通知后台管理查看", Description: ""}, | |
| {PermissionTag: constant.PermissionS3Manage, Name: "对象存储管理", Description: ""}, |
There was a problem hiding this comment.
Code Review
This pull request introduces a significant refactoring of the application's bootstrap and authentication mechanisms, along with new features and administrative capabilities. The core application initialization and shutdown logic has been extracted from cmd/apiserver/main.go into a new internal/bootstrap package, improving modularity. The authentication system has been upgraded to support refresh tokens, session management (logout, logout all, kick user), and user banning/unbanning, leveraging Redis for session and blocklist caching. This involved adding new configuration fields for token TTLs, updating DTOs for login responses and user details, and extensively modifying the AuthMiddleware to incorporate session validation and revocation checks. New API endpoints and documentation were added for Pomodoro, Dictionary, user login activity (login days), and course table management (get bind count, reset schedule). The RBAC system was updated to use constants from pkg/constant instead of internal/models, and the Cache interface and its Redis implementation were extended with new set and sorted set operations to support the enhanced authentication features. Review comments highlight concerns about the KickUser operation's temporary ban duration being too long (2 hours, suggested 5-10 minutes) and the BanUser operation's permanent ban TTL being too short (2 hours, suggested refreshTokenTTL), both potentially leading to premature cache expiration of critical user state. Additionally, an existing GenerateJWT utility function was noted for having a hardcoded 7-day access token TTL, which is inconsistent with the new 2-hour default and poses a security risk if still in use.
| } | ||
|
|
||
| blockType := constant.AuthBlockTypePermanent | ||
| blockTTL := s.accessTokenTTL() |
There was a problem hiding this comment.
| OperatorUserID: operatorUserID, | ||
| ExpiresAt: expiresAt.Unix(), | ||
| } | ||
| if err := s.setBlockInfo(ctx, targetUserID, blockInfo, s.accessTokenTTL()); err != nil { |
| func GenerateJWT(userID uint, secret string, role int8) (string, error) { | ||
| token, _, err := GenerateAccessToken(userID, secret, role, 7*24*time.Hour, NewSessionID()) | ||
| return token, err | ||
| } |
- 新增 OpenAPI 生成脚本与 make openapi 命令,产出 docs/openapi/openapi.json - 重组并补充 API 文档,新增错误码说明与 OpenAPI 使用说明 - 补充番茄钟次数查询接口、词典表迁移及 RBAC 权限种子 - 拆分幂等与存储相关常量,统一预签名 URL 默认过期时间 - 优化评价创建与课表绑定限制的错误提示
fix: 修复DELETEAT导致获取用户RoleTags重复问题 fix: 刷题在线人数逻辑
test: E2E 问题修复
refactor(auth): 双Token校验
refactor(rbac): 统一散落的rbac常量
refactor(api): 模块化main.go
feat(api): 绑定次数、登陆天数查询、重置个人课表
fix: 放宽选课评价文本长度
docs: 更新接口文档
chore: update gitignore