refactor: 后端仓库层更名为数据访问层(internal/repository → internal/dal)

「仓库层」是 repository 的直译,中文里与「代码仓库 / git 仓库」同词,
而这一层做的事就是数据访问。名字改成它实际在做的事。

改名口径(纯机械替换,无逻辑改动):
- 包:internal/repository → internal/dal(package repository → package dal)
- 类型:XxxRepo → XxxDAO(TaskRecordDAO / SpecialistDAO / PositionDAO …)
- 变量:xxxRepo → xxxDAO
- import 路径、包限定符、日志前缀 [repository] → [dal] 同步
- 注释里的「仓库层」→「数据访问层」;core.go 包注释补上 DAL/DAO 全称

命名规范补登(AR09 是命名问题的最高依据,改了名就得回去登记):
- AR09 §3.1 术语表新增「数据访问层 dal / DAO」一行
- AR09 §5.6 缩写表新增 DAO / dal —— 原文是「只有下表内的缩写允许使用」,
  不登记就是自己破自己的规矩
- PROJECT_STATE.md 新增 D27 记录本次更名决策

验证:全部在 db 副本上做,生产库 data/eai_agentplatform.db 未触碰。
- 等价性对照:拿 HEAD 源码 + 仅改名 造出第二棵树,两棵树各自起
  httptest 服务跑同一份探针(60 个 GET + 13 个写/回读,覆盖专员/技能/应用/
  任务/交付物/项目/岗位/考试/知识/积分/管理端只读等),逐端点比对响应体:
  73 项里 52 项字节完全一致、21 项仅运行期时间戳不同、内容差异 0。
- 探针非空:往改名后的树注入「SpecialistDAO.List 限 3 条」变异,
  /api/specialists 立刻被抓出 —— 证明上面那个 0 不是没测到。
- 暂存区自洽:把索引整个导出成源码树,go build / go vet / go test ./... 全绿。
- gofmt:因 import 排序变化而错位的 19 个文件已修;另 2 个文件(skill_definition.go、
  seed.go)的格式问题是工作区里别人的在制品带来的,未替其改动。

未纳入本次提交:工作区里正在进行中的「文生语音技能 + 技能展示色/交互卡」
(tts_handlers.go、text_to_speech/manifest.go、skillCatalog.js 等),
以及 router.go / skill_definition.go / seed.go 三个文件里属于该在制品的改动 ——
这三个文件只把「改名那一版」放进索引,工作区原样保留。

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
eaiadmin
2026-09-19 09:19:21 +08:00
co-authored by Claude Code
parent 1875e7dd4c
commit e8aedd50d2
70 changed files with 738 additions and 733 deletions
@@ -14,25 +14,25 @@ import (
"github.com/golang-jwt/jwt/v5"
"eai_agentplatform/backend/internal/auth"
"eai_agentplatform/backend/internal/dal"
"eai_agentplatform/backend/internal/middleware"
"eai_agentplatform/backend/internal/model"
"eai_agentplatform/backend/internal/repository"
"eai_agentplatform/backend/internal/web"
)
// 考试域仓库,包内共享(知识源摄入也会写题目,见 knowledge.go)。
var (
mistakeRepo repository.MistakeRecordRepo
questionRepo repository.QuestionRepo
paperRepo repository.ExamPaperRepo
examRecordRepo repository.ExamRecordRepo
mistakeDAO dal.MistakeRecordDAO
questionDAO dal.QuestionDAO
paperDAO dal.ExamPaperDAO
examRecordDAO dal.ExamRecordDAO
)
func init() {
mistakeRepo = repository.MistakeRecordRepo{}
questionRepo = repository.QuestionRepo{}
paperRepo = repository.ExamPaperRepo{}
examRecordRepo = repository.ExamRecordRepo{}
mistakeDAO = dal.MistakeRecordDAO{}
questionDAO = dal.QuestionDAO{}
paperDAO = dal.ExamPaperDAO{}
examRecordDAO = dal.ExamRecordDAO{}
}
// Option 题目选项
@@ -84,7 +84,7 @@ func questionView(q model.Question) gin.H {
// ListQuestions GET /api/exam/questions?domain=&status=
func ListQuestions(c *gin.Context) {
// 管理端题库:status 缺省要看到全部(含已停用),按约定传 "all"
items := questionRepo.List(c.Query("domain"), orDefault(c.Query("status"), "all"))
items := questionDAO.List(c.Query("domain"), orDefault(c.Query("status"), "all"))
out := make([]gin.H, 0, len(items))
for _, it := range items {
out = append(out, questionView(it))
@@ -119,7 +119,7 @@ func CreateQuestion(c *gin.Context) {
Explanation: req.Explanation,
Status: "active",
}
if !questionRepo.Insert(&q) {
if !questionDAO.Insert(&q) {
web.Fail(c, web.NewBadRequest("创建题目失败"))
return
}
@@ -132,7 +132,7 @@ func UpdateQuestion(c *gin.Context) {
if !ok {
return
}
q, found := questionRepo.GetByID(id)
q, found := questionDAO.GetByID(id)
if !found {
web.Fail(c, web.NewNotFoundError("题目不存在"))
return
@@ -159,7 +159,7 @@ func UpdateQuestion(c *gin.Context) {
q.Options = string(optsJSON)
q.Answer = string(ansJSON)
q.Explanation = req.Explanation
if !questionRepo.Update(&q) {
if !questionDAO.Update(&q) {
web.Fail(c, web.NewBadRequest("更新题目失败"))
return
}
@@ -172,11 +172,11 @@ func DeleteQuestion(c *gin.Context) {
if !ok {
return
}
if _, found := questionRepo.GetByID(id); !found {
if _, found := questionDAO.GetByID(id); !found {
web.Fail(c, web.NewNotFoundError("题目不存在"))
return
}
if !questionRepo.Delete(id) {
if !questionDAO.Delete(id) {
web.Fail(c, web.NewBadRequest("停用题目失败"))
return
}
@@ -187,7 +187,7 @@ func DeleteQuestion(c *gin.Context) {
// ListPapers GET /api/exam/papers
func ListPapers(c *gin.Context) {
web.OK(c, paperRepo.List())
web.OK(c, paperDAO.List())
}
// CreatePaper POST /api/exam/papers (admin)
@@ -208,7 +208,7 @@ func CreatePaper(c *gin.Context) {
return
}
p.Status = "active"
if !paperRepo.Insert(&p) {
if !paperDAO.Insert(&p) {
web.Fail(c, web.NewBadRequest("创建考试失败"))
return
}
@@ -221,7 +221,7 @@ func UpdatePaper(c *gin.Context) {
if !ok {
return
}
p, found := paperRepo.GetByID(id)
p, found := paperDAO.GetByID(id)
if !found {
web.Fail(c, web.NewNotFoundError("考试配置不存在"))
return
@@ -253,7 +253,7 @@ func UpdatePaper(c *gin.Context) {
if req.Status != "" {
p.Status = req.Status
}
if !paperRepo.Update(&p) {
if !paperDAO.Update(&p) {
web.Fail(c, web.NewBadRequest("更新考试失败"))
return
}
@@ -266,11 +266,11 @@ func DeletePaper(c *gin.Context) {
if !ok {
return
}
if _, found := paperRepo.GetByID(id); !found {
if _, found := paperDAO.GetByID(id); !found {
web.Fail(c, web.NewNotFoundError("考试配置不存在"))
return
}
if !paperRepo.Delete(id) {
if !paperDAO.Delete(id) {
web.Fail(c, web.NewBadRequest("停用考试失败"))
return
}
@@ -282,7 +282,7 @@ func DeletePaper(c *gin.Context) {
// ExamList GET /api/exam/list —— 我的考试列表(含完成状态)
func ExamList(c *gin.Context) {
u := middleware.CurrentUser(c)
items := paperRepo.GetActive()
items := paperDAO.GetActive()
type row struct {
ID uint `json:"id"`
@@ -298,7 +298,7 @@ func ExamList(c *gin.Context) {
out := make([]row, 0, len(items))
for _, p := range items {
st := "available"
if p.Type == "formal" && u != nil && examRecordRepo.HasTaken(u.ID, p.ID) {
if p.Type == "formal" && u != nil && examRecordDAO.HasTaken(u.ID, p.ID) {
st = "completed"
}
out = append(out, row{
@@ -318,7 +318,7 @@ func ExamCover(c *gin.Context) {
return
}
id := uint(id64)
p, found := paperRepo.GetByID(id)
p, found := paperDAO.GetByID(id)
if !found || p.Status != "active" {
web.Fail(c, web.NewNotFoundError("考试不存在或已停用"))
return
@@ -351,18 +351,18 @@ func pickQuestions(p model.ExamPaper) ([]model.Question, error) {
var courseIDs []uint
if p.PositionID != nil {
// 岗位考试:优先蓝图,其次岗位知识映射
if bps := positionRepo.Blueprints(*p.PositionID); len(bps) > 0 {
if bps := positionDAO.Blueprints(*p.PositionID); len(bps) > 0 {
return pickQuestionsByBlueprint(p, bps)
}
// 岗位考试:圈定岗位应学范围
domains, courseIDs = positionScope(positionRepo.Knowledge(*p.PositionID))
domains, courseIDs = positionScope(positionDAO.Knowledge(*p.PositionID))
} else {
// 原有逻辑:按 exam_paper.domain 抽题
domains = splitDomains(p.Domain)
}
var qs []model.Question
if !questionRepo.ActivePool(domains, courseIDs).Order("id ASC").Find(&qs) {
if !questionDAO.ActivePool(domains, courseIDs).Order("id ASC").Find(&qs) {
return nil, fmt.Errorf("查询题库失败")
}
if len(qs) < p.QuestionCount {
@@ -377,8 +377,8 @@ func pickQuestions(p model.ExamPaper) ([]model.Question, error) {
// pickQuestionsByBlueprint 按岗位考试蓝图逐条抽题:先在岗位应学范围内,再按 (domain, type) 细分抽样。
func pickQuestionsByBlueprint(p model.ExamPaper, bps []model.PositionExamBlueprint) ([]model.Question, error) {
// 圈定岗位应学范围(与 P0 岗位知识映射抽题一致)
domains, courseIDs := positionScope(positionRepo.Knowledge(*p.PositionID))
base := questionRepo.ActivePool(domains, courseIDs)
domains, courseIDs := positionScope(positionDAO.Knowledge(*p.PositionID))
base := questionDAO.ActivePool(domains, courseIDs)
out := make([]model.Question, 0, p.QuestionCount)
used := map[uint]bool{}
@@ -456,12 +456,12 @@ func validatePaperPosition(c *gin.Context, positionID *uint) bool {
if positionID == nil {
return true
}
pos, found := positionRepo.GetByID(*positionID)
pos, found := positionDAO.GetByID(*positionID)
if !found || pos.Status != "active" {
web.Fail(c, web.NewBadRequest("关联岗位不存在或已停用"))
return false
}
if positionRepo.CountKnowledge(*positionID) == 0 {
if positionDAO.CountKnowledge(*positionID) == 0 {
web.Fail(c, web.NewBadRequest("岗位考试必须先配置岗位知识映射"))
return false
}
@@ -473,7 +473,7 @@ func validatePaperBlueprintCount(c *gin.Context, p model.ExamPaper) bool {
if p.PositionID == nil {
return true
}
bps := positionRepo.Blueprints(*p.PositionID)
bps := positionDAO.Blueprints(*p.PositionID)
if len(bps) == 0 {
return true
}
@@ -509,12 +509,12 @@ func ExamStart(c *gin.Context) {
web.Fail(c, web.NewBadRequest("paper_id 必填"))
return
}
p, found := paperRepo.GetByID(req.PaperID)
p, found := paperDAO.GetByID(req.PaperID)
if !found || p.Status != "active" {
web.Fail(c, web.NewNotFoundError("考试不存在或已停用"))
return
}
if p.Type == "formal" && u != nil && examRecordRepo.HasTaken(u.ID, p.ID) {
if p.Type == "formal" && u != nil && examRecordDAO.HasTaken(u.ID, p.ID) {
web.Fail(c, web.NewConflictError("已参加过该正式考试"))
return
}
@@ -676,7 +676,7 @@ func ExamSubmit(c *gin.Context) {
web.Fail(c, web.NewBadRequest("练习会话无题目"))
return
}
qs := questionRepo.ListByIDs(ids)
qs := questionDAO.ListByIDs(ids)
qmap := make(map[uint]model.Question, len(qs))
for _, q := range qs {
qmap[q.ID] = q
@@ -755,14 +755,14 @@ func ExamSubmit(c *gin.Context) {
return
}
p, found := paperRepo.GetByID(uint(pid))
p, found := paperDAO.GetByID(uint(pid))
if !found {
web.Fail(c, web.NewNotFoundError("考试不存在"))
return
}
// 正式考不可重复交卷
if stype == "formal" && u != nil && examRecordRepo.HasTaken(u.ID, p.ID) {
if stype == "formal" && u != nil && examRecordDAO.HasTaken(u.ID, p.ID) {
web.Fail(c, web.NewConflictError("已参加过该正式考试"))
return
}
@@ -772,7 +772,7 @@ func ExamSubmit(c *gin.Context) {
web.Fail(c, web.NewBadRequest("考试会话无题目"))
return
}
questions := questionRepo.ListByIDs(ids)
questions := questionDAO.ListByIDs(ids)
qmap := make(map[uint]model.Question, len(questions))
for _, q := range questions {
qmap[q.ID] = q
@@ -867,7 +867,7 @@ func ExamSubmit(c *gin.Context) {
CorrectCount: correctCount, WrongCount: wrongCount,
DetailJSON: string(detailJSON), SubmittedAt: time.Now(),
}
examRecordRepo.Insert(&rec)
examRecordDAO.Insert(&rec)
if passed {
awardPoints(u.ID, "formal_pass", ptFormalPass, "paper", p.ID)
issueCertificate(u, rec)
@@ -897,7 +897,7 @@ func splitIDs(s string) []uint {
// ExamRecordList GET /api/exam/record?page=&size= —— 我的考试记录
func ExamRecordList(c *gin.Context) {
u := middleware.CurrentUser(c)
items := examRecordRepo.ListByUser(u.ID)
items := examRecordDAO.ListByUser(u.ID)
if items == nil {
items = []model.ExamRecord{}
}
@@ -911,7 +911,7 @@ func ExamRecordDetail(c *gin.Context) {
if !ok {
return
}
rec, found := examRecordRepo.GetByID(id)
rec, found := examRecordDAO.GetByID(id)
if !found {
web.Fail(c, web.NewNotFoundError("考试记录不存在"))
return
@@ -947,7 +947,7 @@ func mistakePayload(userID, questionID uint, source string, q model.Question, us
// recordMistake 答错入本:按 (user_id, question_id, source) 去重,再次答错则更新并重置为未掌握。
func recordMistake(userID, questionID uint, q model.Question, userAns any, correct []string, source string) {
mistakeRepo.RecordWrong(mistakePayload(userID, questionID, source, q, userAns, correct))
mistakeDAO.RecordWrong(mistakePayload(userID, questionID, source, q, userAns, correct))
}
// mistakeView 错题出参(answer 反序列化,便于前端直接展示)
@@ -967,7 +967,7 @@ type mistakeView struct {
// MyMistakes GET /api/exam/mistakes —— 我的错题本
func MyMistakes(c *gin.Context) {
u := middleware.CurrentUser(c)
items := mistakeRepo.ListByUser(u.ID)
items := mistakeDAO.ListByUser(u.ID)
out := make([]mistakeView, 0, len(items))
for _, it := range items {
var userAns any
@@ -994,7 +994,7 @@ func ResolveMistake(c *gin.Context) {
if !ok {
return
}
rec, found := mistakeRepo.GetByID(id)
rec, found := mistakeDAO.GetByID(id)
if !found {
web.Fail(c, web.NewNotFoundError("错题记录不存在"))
return
@@ -1013,7 +1013,7 @@ func ResolveMistake(c *gin.Context) {
}
wasResolved := rec.Resolved
rec.Resolved = target
if !mistakeRepo.Update(&rec) {
if !mistakeDAO.Update(&rec) {
web.Fail(c, web.NewBadRequest("更新错题状态失败"))
return
}
@@ -1037,7 +1037,7 @@ func MistakePractice(c *gin.Context) {
}
_ = c.ShouldBindJSON(&req)
recs := mistakeRepo.ListForPractice(u.ID, req.Source, req.OnlyUnresolved)
recs := mistakeDAO.ListForPractice(u.ID, req.Source, req.OnlyUnresolved)
if len(recs) == 0 {
web.Fail(c, web.NewBadRequest("暂无可重练的错题"))
return
@@ -1050,7 +1050,7 @@ func MistakePractice(c *gin.Context) {
ids = dedupeUints(ids)
// 剔除已停用/删除的题目,仅下发可作答题目
questions := questionRepo.ListActiveByIDs(ids)
questions := questionDAO.ListActiveByIDs(ids)
if len(questions) == 0 {
web.Fail(c, web.NewBadRequest("错题对应题目已失效,暂无可重练题目"))
return
@@ -1101,11 +1101,11 @@ func MistakePractice(c *gin.Context) {
// touchMistakeOnPractice 错题重练判分后同步错题状态:答对置为已掌握(加积分),答错重置为未掌握。
func touchMistakeOnPractice(userID, questionID uint, q model.Question, userAns any, correct []string, resolved bool) {
payload := mistakePayload(userID, questionID, "", q, userAns, correct)
flipped, touched := mistakeRepo.TouchOnPractice(payload, resolved)
flipped, touched := mistakeDAO.TouchOnPractice(payload, resolved)
if !touched {
// 防御性兜底:理论上重练题目均来自错题本,这里创建一条
payload.Source = "re_practice"
mistakeRepo.RecordWrong(payload)
mistakeDAO.RecordWrong(payload)
return
}
// 仅在「未掌握 → 已掌握」时加分,避免反复重练刷分