Conversation
- 提取 bootstrap 包,简化 main.go 入口 - 新增 model/dto.go 统一 DTO 类型 - 修复 pkg/extension 非法导入 internal 的问题 - 消除 init() 副作用,改为显式 Init() 调用 - 拆分 handler/admin、handler/user、service 大文件 - 文件名与功能语义对齐(export/statistics/preset/manage 等) - 合并鉴权守卫到 auth.go,删除多余的 permission.go - 重命名 userCenter→oauth、eventInfo→notify、redis→rate_limit
There was a problem hiding this comment.
Pull request overview
该 PR 以“重构项目”为目标,将原本集中在 main.go、大型 handler/service 文件中的多职责逻辑进行拆分与模块化,推动启动流程收敛、DTO 统一、以及 admin/user 相关能力解耦,以便后续渐进式重构更易推进。
Changes:
- 引入
internal/bootstrap,将时区/Gin 模式/日志/ID/Redis/OSS/数据库/插件/HTTP 引擎初始化从main.go拆分出来。 - 将原本聚合在单文件中的 service/admin 与 handler/user 逻辑拆分为多个职责单一文件,并将部分 DTO 迁移到
internal/model。 - 调整插件管理器加载/执行接口为显式传入插件顺序,并将 Redis/OSS 客户端初始化从
init()改为显式Init()。
Reviewed changes
Copilot reviewed 37 out of 39 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| pkg/extension/manager.go | 插件加载/执行接口改为显式传入插件名列表,减少对全局配置耦合 |
| main.go | 启动流程收敛为 bootstrap 调用,简化入口职责 |
| internal/bootstrap/*.go | 新增启动初始化模块(timezone/gin/logger/id/redis/oss/db/crypto/plugins/http) |
| internal/pkg/redis/config.go | Redis 客户端改为显式 Init() 初始化 |
| internal/pkg/oss/cube.go | OSS 客户端改为显式 Init() 初始化 |
| internal/model/dto.go | 新增 DTO(BaseConfig/QuestionConfig/QuestionList/QuestionSubmit 等) |
| internal/dao/question.go, internal/dao/option.go | DTO 迁移后用类型别名保持向后兼容 |
| internal/service/* | service 拆分(oauth/notify/admin_* 等),提交后触发通知插件 |
| internal/handler/user/* | user handler 从单文件拆分为 survey/submit/stat/oauth/answer/upload 等 |
| internal/handler/admin/* | admin handler 拆分新增 answer/export/preset/statistics,并抽取鉴权辅助函数 |
| .github/skills/refactor-guide/SKILL.md | 新增仓库重构指导文档 |
Comments suppressed due to low confidence (1)
internal/service/rate_limit.go:22
*gin.Contextdoes not implementcontext.Context, but go-redis methods (e.g.RedisClient.Get/Set/Incr) require acontext.Context. Passingchere will not compile. Consider usingctx := c.Request.Context()(or accept acontext.Contextparameter) and pass that to Redis, while keeping the*gin.Contextonly where HTTP-specific data is needed.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| fileName := survey.Title + ".xlsx" | ||
| filePath := "./public/xlsx/" |
There was a problem hiding this comment.
Same filename issue here: survey.Title is used directly for the exported file name, which can allow path traversal or invalid filenames. Please sanitize/normalize the title (or use a safe generated name) before writing to disk and generating the URL.
| options, err := GetOptionsByQuestionID(question.ID) | ||
| if err != nil { | ||
| log.Println("Error fetching options for questionID:", question.ID) | ||
| continue |
There was a problem hiding this comment.
This service uses the standard library log.Println, while the rest of the service layer appears to use structured zap logging (e.g. zap.L().Warn/Error). Using zap here would keep logs consistent and preserve fields (questionID, error) for filtering and observability.
| colName, err := excelize.ColumnNumberToName(j + 3) | ||
| if err != nil { | ||
| return "", errors.New("转换列名失败原因: " + err.Error()) | ||
| } | ||
| if err := f.SetCellValue("Sheet1", colName+strconv.Itoa(i+2), answer); err != nil { | ||
| return "", errors.New("写入数据失败原因: " + err.Error()) | ||
| } |
There was a problem hiding this comment.
This mixes StreamWriter mode (NewStreamWriter/SetRow) with regular cell APIs (f.SetCellValue). In excelize, once a StreamWriter is used for a sheet, writing via the normal APIs can error or produce a corrupted file. Pick one approach: either build the full row and only call streamWriter.SetRow, or drop StreamWriter and use SetCellValue throughout.
| fileName := survey.Title + ".xlsx" | ||
| filePath := "./public/xlsx/" + fileName |
There was a problem hiding this comment.
survey.Title is used directly as part of the filename. Since titles are user-controlled, this can lead to path traversal (e.g. ../...) or invalid filenames on some OSes. Please sanitize/normalize the title into a safe filename (strip path separators, trim length, fall back to survey ID, etc.) before building filePath.
No description provided.