Files
eaiadminandClaude Code e8aedd50d2 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>
2026-09-19 09:19:21 +08:00

157 lines
3.9 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package api
import (
"encoding/json"
"github.com/gin-gonic/gin"
"eai_agentplatform/backend/internal/middleware"
"eai_agentplatform/backend/internal/model"
"eai_agentplatform/backend/internal/store"
"eai_agentplatform/backend/internal/web"
)
// 本文件用到的仓库:questionDAO/examRecordDAO 声明在 exam.go,learningDAO 声明在 learning.go。
// domainLabels 能力雷达维度(正式考试按域聚合的掌握度)。
var domainLabels = map[string]string{
"company": "公司认知",
"product": "产品知识",
"sales": "销售能力",
}
// domainOrder 雷达图固定排序。
var domainOrder = []string{"company", "product", "sales"}
// detailWrap 正式考试答题明细(与 ExamSubmit 持久化的 detail_json 结构对齐)。
type detailWrap struct {
Questions []struct {
QuestionID uint `json:"question_id"`
IsCorrect bool `json:"is_correct"`
} `json:"questions"`
}
// MyProfile GET /api/my/profile —— 学员学习档案:能力雷达 + 学习统计 + 成绩趋势 + 积分。
func MyProfile(c *gin.Context) {
u := middleware.CurrentUser(c)
if u == nil {
web.Fail(c, web.NewAuthError("未登录"))
return
}
// 学习进度(公司/产品/课程浏览)
lps := learningDAO.ListByUser(u.ID)
companyViewed := false
productViewed, courseViewed := 0, 0
for _, lp := range lps {
switch lp.ItemType {
case "company":
companyViewed = true
case "product":
productViewed++
case "course":
courseViewed++
}
}
// 错题统计
mistakeTotal := mistakeDAO.CountByUser(u.ID)
mistakeResolved := mistakeDAO.CountResolvedByUser(u.ID)
// 自测次数(自测不落 exam_record,改由积分流水统计)
var selfTestCount int64
store.DB.Model(&model.PointEvent{}).Where("user_id = ? AND event_type = ?", u.ID, "self_test").Count(&selfTestCount)
// 正式考试记录 + 按域能力聚合(趋势图要按时间正序)
recs := examRecordDAO.ListByUserChronological(u.ID)
// 题目 → 域映射(用于从答题明细反推各域掌握度)
qDomain := questionDAO.DomainMap()
type domainAgg struct {
Correct int `json:"correct"`
Total int `json:"total"`
}
perDomain := map[string]*domainAgg{}
formalPassed := 0
trend := make([]gin.H, 0, len(recs))
for _, r := range recs {
if r.Passed {
formalPassed++
}
trend = append(trend, gin.H{
"exam_name": r.ExamName,
"score": r.Score,
"total_score": r.TotalScore,
"passed": r.Passed,
"submitted_at": r.SubmittedAt,
})
var dw detailWrap
if err := json.Unmarshal([]byte(r.DetailJSON), &dw); err != nil {
continue
}
for _, qd := range dw.Questions {
domain := qDomain[qd.QuestionID]
if domain == "" {
continue
}
a := perDomain[domain]
if a == nil {
a = &domainAgg{}
perDomain[domain] = a
}
a.Total++
if qd.IsCorrect {
a.Correct++
}
}
}
domains := make([]gin.H, 0, len(domainOrder))
for _, d := range domainOrder {
a := perDomain[d]
if a == nil {
a = &domainAgg{}
}
mastery := 0.0
if a.Total > 0 {
mastery = round1(float64(a.Correct) * 100 / float64(a.Total))
}
domains = append(domains, gin.H{
"domain": d,
"label": domainLabels[d],
"correct": a.Correct,
"total": a.Total,
"mastery": mastery,
})
}
// 薄弱点:有作答记录中掌握度最低的域
weakDomain := ""
weakMastery := 100.0
for _, d := range domains {
if d["total"].(int) > 0 && d["mastery"].(float64) < weakMastery {
weakMastery = d["mastery"].(float64)
weakDomain = d["domain"].(string)
}
}
web.OK(c, gin.H{
"learning_points": u.LearningPoints,
"stats": gin.H{
"company_viewed": companyViewed,
"product_viewed": productViewed,
"course_viewed": courseViewed,
"self_test_count": selfTestCount,
"formal_count": len(recs),
"formal_passed": formalPassed,
"mistake_count": mistakeTotal,
"mistake_resolved": mistakeResolved,
},
"domains": domains,
"weak_domain": weakDomain,
"recent_scores": trend,
})
}