refactor: 后端仓库层收口(A3:考试/部门/学习/证书/档案/公司介绍)
把 A 档剩余对象的 api 裸查询全部收进仓库,api 层裸 store.DB 从 182 降到 99, 剩下的全是 B 档(任务/项目/笔记等尚无仓库的对象)与 C 档(报表聚合查询)。 按对象补齐的仓库方法: - QuestionRepo:List 重写(status 档位改为显式 all/空/具体值)、 ListByIDs(判分不过滤 status)、ListActiveByIDs(下发剔除停用)、 ActivePool(抽题口径,主流程与蓝图共用)、DomainMap(能力雷达反查域) - ExamPaperRepo.List;ExamRecordRepo.ListByUserChronological(趋势图正序) - DepartmentRepo.ListByStatus / CountByName - UserRepo.ListEmployees / CountActiveByDepartment / RenameDepartment - LearningProgressRepo.ListAll;CertificateRepo.ListAll / GetByExamRecord - MediaFileRepo.ListApprovedByBindType 顺带修掉两处隐患: - CertificateRepo.GetByUserAndExam 按不存在的 exam_id 列查,一调即 SQL 报错, 换成按 exam_record_id 的 GetByExamRecord(颁发幂等本来就该按考试记录) - exam.go 与 system.go 各声明了一个 ExamRecordRepo 变量,同一个仓库两份变量 会导致测试覆写时行为分叉,统一为一个 examRecordRepo 考证来源(趋势图正序 vs 列表页倒序)与抽题口径(岗位蓝图/岗位知识映射两条路径) 各自抽成单一出处,避免两处手写漂移。聚合与百分比计算仍留在 handler,未搬进仓库。 验证:tmp 验证程序走真实路由 + 真实 HTTP,对 DB 副本跑 111 项断言全绿 (覆盖停用题仍可判分、错题重练剔除停用题、趋势正序、改名同步 user.department 且 updated_at 仍刷新、未通过的正式考不发证书、公司介绍只出 approved 素材等)。 另对其中 8 条关键语义做了变异测试:逐条注入反向实现,确认断言确实会失败, 并因此发现并修掉验证程序自身一处漏洞(写语句的约束错误只在 rows.Err() 浮出, 原先未检查,导致一条断言实为空断言)。 原始 data/eai_agentplatform.db 全程未触碰,md5 复核一致。 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -13,10 +13,17 @@ import (
|
||||
|
||||
"eai_agentplatform/backend/internal/middleware"
|
||||
"eai_agentplatform/backend/internal/model"
|
||||
"eai_agentplatform/backend/internal/store"
|
||||
"eai_agentplatform/backend/internal/repository"
|
||||
"eai_agentplatform/backend/internal/web"
|
||||
)
|
||||
|
||||
// sourceRepo 知识源仓库(便于测试时覆写),包内共享。
|
||||
var sourceRepo repository.KnowledgeSourceRepo
|
||||
|
||||
func init() {
|
||||
sourceRepo = repository.KnowledgeSourceRepo{}
|
||||
}
|
||||
|
||||
// ============ 扫描 ============
|
||||
|
||||
// KnowledgeScan POST /api/knowledge/scan —— 扫描 knowledge_source 目录建 pending 记录
|
||||
@@ -44,8 +51,7 @@ func KnowledgeScan(c *gin.Context) {
|
||||
continue // 非知识源文档,跳过
|
||||
}
|
||||
|
||||
var existing model.KnowledgeSource
|
||||
if err := store.DB.Where("file_path = ?", e.Name()).First(&existing).Error; err == nil {
|
||||
if existing, found := sourceRepo.GetByFilePath(e.Name()); found {
|
||||
results = append(results, gin.H{"file_path": e.Name(), "status": "skipped", "title": existing.Title})
|
||||
continue
|
||||
}
|
||||
@@ -58,7 +64,7 @@ func KnowledgeScan(c *gin.Context) {
|
||||
AuditStatus: "pending",
|
||||
KnowledgeSpaceKey: ensureKnowledgeSpaceKeyOrDefault(orDefault(fm["knowledge_space_key"], inferKnowledgeSpaceKey(parseTitle(string(data)), fm["domain"], fm["category"]+" "+e.Name()))),
|
||||
}
|
||||
if err := store.DB.Create(&src).Error; err != nil {
|
||||
if !sourceRepo.Insert(&src) {
|
||||
results = append(results, gin.H{"file_path": e.Name(), "status": "error", "title": src.Title})
|
||||
continue
|
||||
}
|
||||
@@ -69,13 +75,6 @@ func KnowledgeScan(c *gin.Context) {
|
||||
|
||||
// KnowledgeAuditList GET /api/knowledge/audit-list?status=&page=&size=
|
||||
func KnowledgeAuditList(c *gin.Context) {
|
||||
q := store.DB.Model(&model.KnowledgeSource{})
|
||||
if s := c.Query("status"); s != "" {
|
||||
q = q.Where("audit_status = ?", s)
|
||||
}
|
||||
if key := sanitizeSpaceKey(c.Query("knowledge_space_key")); key != "" {
|
||||
q = q.Where("knowledge_space_key = ?", key)
|
||||
}
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
size, _ := strconv.Atoi(c.DefaultQuery("size", "20"))
|
||||
if page < 1 {
|
||||
@@ -84,10 +83,8 @@ func KnowledgeAuditList(c *gin.Context) {
|
||||
if size < 1 || size > 100 {
|
||||
size = 20
|
||||
}
|
||||
var total int64
|
||||
q.Count(&total)
|
||||
var items []model.KnowledgeSource
|
||||
q.Order("id ASC").Offset((page - 1) * size).Limit(size).Find(&items)
|
||||
total, items := sourceRepo.ListForAudit(
|
||||
c.Query("status"), sanitizeSpaceKey(c.Query("knowledge_space_key")), page, size)
|
||||
web.OK(c, gin.H{"total": total, "items": items})
|
||||
}
|
||||
|
||||
@@ -98,8 +95,8 @@ func KnowledgeAudit(c *gin.Context) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var src model.KnowledgeSource
|
||||
if err := store.DB.First(&src, id).Error; err != nil {
|
||||
src, found := sourceRepo.GetByID(id)
|
||||
if !found {
|
||||
web.Fail(c, web.NewNotFoundError("知识源不存在"))
|
||||
return
|
||||
}
|
||||
@@ -129,7 +126,7 @@ func KnowledgeAudit(c *gin.Context) {
|
||||
src.AuditAt = &now
|
||||
src.RejectReason = ""
|
||||
src.Ingested = true
|
||||
if err := store.DB.Save(&src).Error; err != nil {
|
||||
if !sourceRepo.Update(&src) {
|
||||
web.Fail(c, web.NewBadRequest("审批失败"))
|
||||
return
|
||||
}
|
||||
@@ -147,7 +144,7 @@ func KnowledgeAudit(c *gin.Context) {
|
||||
src.RejectReason = req.RejectReason
|
||||
src.AuditBy = &auditBy
|
||||
src.AuditAt = &now
|
||||
if err := store.DB.Save(&src).Error; err != nil {
|
||||
if !sourceRepo.Update(&src) {
|
||||
web.Fail(c, web.NewBadRequest("审批失败"))
|
||||
return
|
||||
}
|
||||
@@ -163,8 +160,8 @@ func KnowledgeStatus(c *gin.Context) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var src model.KnowledgeSource
|
||||
if err := store.DB.First(&src, id).Error; err != nil {
|
||||
src, found := sourceRepo.GetByID(id)
|
||||
if !found {
|
||||
web.Fail(c, web.NewNotFoundError("知识源不存在"))
|
||||
return
|
||||
}
|
||||
@@ -302,7 +299,10 @@ func convertAnswer(raw []any) []string {
|
||||
|
||||
// ============ 摄入 ============
|
||||
|
||||
// ingestSource 解析 md → product / knowledge_chunk / question,返回三表写入计数
|
||||
// ingestSource 解析 md → product / knowledge_chunk / question,返回三表计数
|
||||
//
|
||||
// 口径提示:counts 记的是**从 md 解析出的条目数**,不是落库成功数 ——
|
||||
// 单条写入失败不中断、也不扣减计数(沿用原有行为,未改)。
|
||||
func ingestSource(src *model.KnowledgeSource) ([3]int, error) {
|
||||
var counts [3]int
|
||||
full := filepath.Join(Cfg.KnowledgeSourceDir, src.FilePath)
|
||||
@@ -335,12 +335,12 @@ func ingestSource(src *model.KnowledgeSource) ([3]int, error) {
|
||||
ReportRules: kv["report_rules"],
|
||||
Status: "active",
|
||||
}
|
||||
var existing model.Product
|
||||
if err := store.DB.Where("code = ?", p.Code).First(&existing).Error; err == nil {
|
||||
if existing, found := productRepo.GetByCode(p.Code); found {
|
||||
p.ID = existing.ID
|
||||
store.DB.Save(&p)
|
||||
p.CreatedAt = existing.CreatedAt // Save 整字段覆盖,不回填会把创建时间写成零值
|
||||
productRepo.Update(&p)
|
||||
} else {
|
||||
store.DB.Create(&p)
|
||||
productRepo.Insert(&p)
|
||||
}
|
||||
counts[0]++
|
||||
}
|
||||
@@ -351,7 +351,7 @@ func ingestSource(src *model.KnowledgeSource) ([3]int, error) {
|
||||
if text == "" {
|
||||
continue
|
||||
}
|
||||
store.DB.Create(&model.KnowledgeChunk{
|
||||
chunkRepo.Insert(&model.KnowledgeChunk{
|
||||
KnowledgeSourceID: &src.ID,
|
||||
SourceType: "md",
|
||||
SourceID: strconv.FormatUint(uint64(src.ID), 10),
|
||||
@@ -383,7 +383,7 @@ func ingestSource(src *model.KnowledgeSource) ([3]int, error) {
|
||||
Explanation: kv["explanation"],
|
||||
Status: "active",
|
||||
}
|
||||
store.DB.Create(&q)
|
||||
questionRepo.Insert(&q)
|
||||
counts[2]++
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user