Conversation
Signed-off-by: Penryn <15158052130@163.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
feat: 扫码端api
fix: bug修复
feat:big-pan api
feat:用户端重构,基于gbc框架&mygo包
refactor: 结构统一
refactor: route结构统一
refactor: 形式统一
docs: 完善 README 文档
fix:统一代码风格
fix: 修复bug
There was a problem hiding this comment.
Pull request overview
本 PR 引入“毅行/Walk-Server”后端的整体骨架与核心业务模块(用户端、管理员端、Dashboard),并补充了数据层(GORM Gen model/query + repo)、缓存层(Redis cache/lock)、部署与建表脚本,以支持报名、组队、打卡与大盘统计等功能闭环。
Changes:
- 新增 HTTP 路由注册与应用 Boot/Command/Cron 启动框架,接入 Gin + mygo 生态组件(session/jwt/redis/db/log)。
- 新增用户/管理员/Dashboard API 与 repo/cache 层实现,提供报名、组队、打卡、队伍筛选与统计查询等接口。
- 新增数据库建表 SQL、代码生成入口、基础部署(Dockerfile、Loki/Grafana/Alloy)与说明文档。
Reviewed changes
Copilot reviewed 92 out of 97 changed files in this pull request and generated 14 comments.
Show a summary per file
| File | Description |
|---|---|
| register/route.go | 注册 Gin 路由分组与中间件(/admin、/user、/dashboard) |
| register/generate/generate.go | generate 模块 Boot 占位 |
| register/cron.go | Cron 注册占位 |
| register/cmd.go | Cobra 命令注册(server/cron) |
| register/boot.go | 应用 BootList:日志、DB、Redis、JWT、Lock、业务配置加载等 |
| middleware/checkPrem.go | Dashboard 管理员权限校验中间件 |
| middleware/auth.go | 用户 JWT 鉴权中间件封装 |
| middleware/.gitkeep | 目录占位 |
| main.go | 应用入口:启动 HTTP Server + 伴生 cron |
| go.mod | Go module 与依赖声明 |
| docs/dashboard-pr-overview.md | Dashboard 分层/缓存策略说明文档 |
| deploy/sql/wrong_route_records.sql | 新增 wrong_route_records 表 |
| deploy/sql/teams.sql | 新增 teams 表结构 |
| deploy/sql/routes.sql | 新增 routes 表结构 |
| deploy/sql/route_edge.sql | 新增 route_edges 表结构 |
| deploy/sql/points.sql | 新增 points 表结构 |
| deploy/sql/peoples.sql | 新增 peoples 表结构 |
| deploy/sql/checkins.sql | 新增 checkins 表结构 |
| deploy/sql/admins.sql | 新增 admins 表结构 |
| deploy/loki/docker-compose.yaml | Loki + Alloy + Grafana 本地观测栈 |
| deploy/loki/conf/loki-config.yaml | Loki 配置 |
| deploy/loki/conf/alloy-config.alloy | Alloy 采集日志并写入 Loki |
| dao/repo/route.go | 路线统计/点位/路段聚合查询 repo |
| dao/repo/people.go | 人员 repo(含按 OpenID 缓存) |
| dao/repo/admin.go | 管理员 repo(含 session 反查 + 缓存) |
| dao/query/gen.go | GORM Gen 生成的 Query 入口 |
| dao/model/wrong_route_records.gen.go | wrong_route_records model |
| dao/model/teams.gen.go | teams model |
| dao/model/routes.gen.go | routes model |
| dao/model/route_edges.gen.go | route_edges model |
| dao/model/points.gen.go | points model |
| dao/model/peoples.gen.go | peoples model |
| dao/model/checkins.gen.go | checkins model |
| dao/model/admins.gen.go | admins model |
| dao/cache/team/team.go | 队伍缓存 + 筛选缓存 + 分布式锁封装 |
| dao/cache/route/route.go | 路线/统计/点位/路段缓存封装 |
| dao/cache/people/people.go | 人员缓存封装 |
| dao/cache/admin/admin.go | 管理员缓存封装 |
| dao/cache/.gitkeep | 目录占位 |
| cron/.gitkeep | 目录占位 |
| conf/config.example.yaml | 配置模板(db/redis/session/jwt/biz 等) |
| comm/util.go | JWT token 生成、AES 加解密、时间判断工具 |
| comm/password.go | bcrypt hash/verify |
| comm/lock.go | 基于 redsync 的队伍互斥锁 |
| comm/errors.go | comm 包占位 |
| comm/enum.go | 枚举常量与解析/格式化函数 |
| comm/config.go | biz 配置结构体 |
| comm/code.go | 统一错误码定义 |
| cmd/gen/generate.go | GORM Gen 代码生成入口 |
| api/user/wechatLogin.go | 微信登录换取 OpenID 并发 token |
| api/user/userModify.go | 用户可编辑信息更新 |
| api/user/userInfo.go | 用户信息 + 队伍信息查询 |
| api/user/teamUpdate.go | 队长修改队伍信息 |
| api/user/teamLeave.go | 队员退出队伍(事务+并发控制) |
| api/user/teamJoin.go | 加入队伍(事务+并发控制) |
| api/user/teamInfo.go | 获取当前队伍与成员信息 |
| api/user/teamDisband.go | 队长解散队伍 |
| api/user/teamCreate.go | 创建队伍 |
| api/user/teamCommon.go | 队伍通用结构与辅助函数 |
| api/user/registerTeacher.go | 教职工报名 |
| api/user/registerStudent.go | 学生报名 |
| api/user/registerCommon.go | 报名通用逻辑(参数校验+去重) |
| api/user/registerAlumnus.go | 校友报名 |
| api/dashboard/teams/team.go | Dashboard 队伍详情(含缓存) |
| api/dashboard/teams/lost.go | 设置队伍失联状态(含锁/缓存失效) |
| api/dashboard/teams/filter.go | Dashboard 队伍筛选(含缓存/分页游标) |
| api/dashboard/stats/route.go | 单路线详细统计(含缓存) |
| api/dashboard/stats/all.go | 全路线统计聚合(含缓存) |
| api/dashboard/segment.go | 路段人数统计(含缓存) |
| api/dashboard/permission.go | 查询当前管理员权限信息 |
| api/dashboard/overview.go | 总览统计(按校区,含缓存) |
| api/dashboard/checkpoint.go | 点位详情(已到/未到人数,含缓存) |
| api/admin/updateUser.go | 管理员更新人员状态(事务+锁) |
| api/admin/updateTeam.go | 队伍打卡/错路处理/写入记录 |
| api/admin/regroup.go | 重组队伍(事务) |
| api/admin/registerAdmin.go | 管理员注册(测试用途) |
| api/admin/markTeamViolation.go | 标记队伍违规 |
| api/admin/getUserInfoScan.go | 扫码查询人员信息 |
| api/admin/getUserInfo.go | 按 ID 查询人员信息 |
| api/admin/getTeamStatus.go | 获取队伍状态 + 成员状态 |
| api/admin/confirmDestination.go | 终点确认(更新队伍/成员状态) |
| api/admin/bindCode.go | 绑定队伍签到码并推进状态 |
| api/admin/authAdmin.go | 管理员登录(session 写入) |
| README.md | 项目说明、目录结构、启动与生成说明 |
| Dockerfile | 多阶段构建镜像 |
| .gitignore | 忽略配置、日志、生成文件等 |
| .dockerignore | Docker 构建忽略文件 |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| team, err := teamRepo.FindTeamByID(ctx, h.Request.TeamID) | ||
| if err != nil { | ||
| return comm.CodeDatabaseError | ||
| } | ||
| if team == nil { | ||
| return comm.CodeDataNotFound | ||
| } | ||
| if team.Password != h.Request.Password { | ||
| return comm.CodePasswordWrong | ||
| } | ||
| if team.Submit != 0 { | ||
| return comm.CodeTeamSubmitted | ||
| } | ||
| maxTeamSize := 6 |
There was a problem hiding this comment.
这里用 team.Password != h.Request.Password 做明文比较,配合 teams.password 明文存储会带来密码泄露风险。建议改为存储密码哈希并使用安全的 hash compare(bcrypt/argon2),同时考虑对密码尝试做限流以降低暴力破解风险。
| -- Active: 1773747872536@@127.0.0.1@3306@jh_db | ||
| CREATE TABLE `peoples` ( | ||
| `id` bigint unsigned NOT NULL AUTO_INCREMENT, |
There was a problem hiding this comment.
文件顶部的 -- Active: ... 看起来是本地 SQL 客户端生成的连接标记,不属于建表脚本内容,容易污染迁移/回放。建议移除这类本地工具注释,保持 SQL 可重复执行且环境无关。
| // TeamStatus枚举 | ||
| const ( | ||
| TeamStatusNotStart = "notStart" | ||
| TeamStatusInProgress = "inProgress" | ||
| TeamStatusCompleted = "completed" | ||
| TeamStatusWithDrawn = "withDrawn" | ||
| ) |
There was a problem hiding this comment.
TeamStatusWithDrawn 的值是 withDrawn,但 teams.sql 的字段注释里使用的是 withdrawn。如果数据库里已有/将写入 withdrawn(或其他模块按注释实现),会造成状态判断与展示不一致。建议统一团队下撤状态的枚举值(推荐全小写 withdrawn,与 people.walk_status 一致),并同步更新前后端与数据库注释。
| @@ -0,0 +1,10 @@ | |||
| package generate | |||
|
|
|||
| // 用于生成和注册生成模块的启动函数,请不要在此目录下防止自定义的业务代码 | |||
There was a problem hiding this comment.
注释里“请不要在此目录下防止自定义的业务代码”里“防止”应为“放置”,避免歧义。
|
|
||
| ## 配置说明 | ||
|
|
||
| 配置模板见 [config.example.yaml]。 |
There was a problem hiding this comment.
README 中“配置模板见 [config.example.yaml]”的引用路径不正确:仓库内文件在 conf/config.example.yaml,而这里的 Markdown 引用也未定义链接目标,读者点不开/找不到。建议改成明确路径(例如 conf/config.example.yaml)并使用可点击的相对链接。
| 配置模板见 [config.example.yaml]。 | |
| 配置模板见 [`conf/config.example.yaml`](conf/config.example.yaml)。 |
| if err := u.handlePointCheckin(ctx, team, adminID, pointName); err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| if routeEdge == nil || routeEdge.PrevPointName != team.PrevPointName { | ||
| return &routePointCheckinResult{code: &comm.CodePrevPointInvalid}, nil | ||
| } | ||
|
|
There was a problem hiding this comment.
handleRoutePointCheckin 里在校验 routeEdge.PrevPointName 是否与 team.PrevPointName 匹配之前,就先写入了打卡(更新 prev_point_name + 插入 checkin)。当校验失败返回 CodePrevPointInvalid 时,数据库已经产生了副作用,会导致队伍状态/打卡记录被错误推进。建议将前序点位校验放在任何写操作之前,或把校验+写入放在同一个事务里并在校验失败时回滚。
| if err := u.handlePointCheckin(ctx, team, adminID, pointName); err != nil { | |
| return nil, err | |
| } | |
| if routeEdge == nil || routeEdge.PrevPointName != team.PrevPointName { | |
| return &routePointCheckinResult{code: &comm.CodePrevPointInvalid}, nil | |
| } | |
| if routeEdge == nil || routeEdge.PrevPointName != team.PrevPointName { | |
| return &routePointCheckinResult{code: &comm.CodePrevPointInvalid}, nil | |
| } | |
| if err := u.handlePointCheckin(ctx, team, adminID, pointName); err != nil { | |
| return nil, err | |
| } |
| // 转换为累计经过人数:例如原始(0,1)会返回为(1,1)。 | ||
| totalPassed := 0 | ||
| for i := len(r.Response.PointStats) - 1; i >= 0; i-- { | ||
| totalPassed += r.Response.PointStats[i].PassedCount | ||
| r.Response.PointStats[i].PassedCount = totalPassed | ||
| } |
There was a problem hiding this comment.
这里把每个点位的 PassedCount 做了“从终点向前累加”,但上游的 ListRoutePointPassedCounts 统计的是“在该点位打过卡的总人数”(每支队伍会在多个点位产生 checkin)。这样累加会对同一批人重复计数,导致数值严重偏大。建议改为按“队伍当前/最大到达点位序号”来计算每个点位的累计到达人数(例如基于 teams.prev_point_name 或按 checkins 求每队最大 seq_order),而不是对点位打卡人数做累加。
| // 转换为累计经过人数:例如原始(0,1)会返回为(1,1)。 | |
| totalPassed := 0 | |
| for i := len(r.Response.PointStats) - 1; i >= 0; i-- { | |
| totalPassed += r.Response.PointStats[i].PassedCount | |
| r.Response.PointStats[i].PassedCount = totalPassed | |
| } | |
| // 保持每个点位的 PassedCount 为“在该点位打过卡的总人数”,避免在多个点位重复累计同一批人。 |
| func NeedPerm(minPerm string) gin.HandlerFunc { | ||
| minPerm = strings.ToLower(strings.TrimSpace(minPerm)) | ||
| return func(ctx *gin.Context) { | ||
| admin, ok := repo.GetAdminInfo(ctx) | ||
| if !ok { | ||
| return | ||
| } | ||
|
|
||
| currentRank, currentOK := getPermRank(admin.Permission) | ||
| minRank, minOK := getPermRank(minPerm) | ||
|
|
||
| if !currentOK || !minOK { | ||
| reply.Fail(ctx, comm.CodeMiddlewareServiceError) | ||
| return | ||
| } | ||
|
|
||
| if currentRank > minRank { | ||
| reply.Fail(ctx, comm.CodePermissionDenied) | ||
| return |
There was a problem hiding this comment.
NeedPerm 对 minPerm 做了 trim+lowercase,但 admin.Permission 未做任何规范化就用于查表。管理员权限如果包含大小写差异或前后空格(例如注册/导入时写入了 Internal),这里会被当成未知权限并返回中间件错误码。建议对 admin.Permission 同样做 strings.ToLower(strings.TrimSpace(...)) 后再参与 getPermRank 判断。
| adminGroup := r.Group("/admin") | ||
| { | ||
| adminGroup.POST("/register", api.RegisterAdminHandler()) | ||
| adminGroup.POST("/auth", api.AuthAdminHandler()) | ||
|
|
There was a problem hiding this comment.
/admin/register 当前是无需登录即可访问的公开接口,这会允许任意人创建管理员账号(即使代码注释写“测试”,也很容易被误部署到生产)。建议至少在路由层加权限/环境开关(仅 dev/test 暴露),或要求已登录的 super 管理员才能调用。
| team := &model.Team{ | ||
| Name: teamName, | ||
| Num: 1, | ||
| Password: h.Request.Password, | ||
| Slogan: h.Request.Slogan, | ||
| AllowMatch: boolToInt8(h.Request.AllowMatch), | ||
| Captain: openID, | ||
| RouteName: h.Request.RouteName, | ||
| Submit: 0, | ||
| Status: comm.TeamStatusNotStart, | ||
| Code: "", | ||
| IsLost: 0, | ||
| } | ||
| if errTx := txTeamRepo.Create(ctx, team); errTx != nil { |
There was a problem hiding this comment.
队伍加入密码在这里以明文写入 teams.password(并且后续加入时直接做明文比较)。这会导致数据库泄露时直接暴露所有队伍密码,也不利于审计合规。建议像管理员密码一样使用 bcrypt/argon2 等哈希存储,并在校验时用 hash compare;同时避免在日志/响应中回传密码。
No description provided.