把 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>
75 lines
1.9 KiB
Go
75 lines
1.9 KiB
Go
package api
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"eai_agentplatform/backend/internal/middleware"
|
|
"eai_agentplatform/backend/internal/model"
|
|
"eai_agentplatform/backend/internal/repository"
|
|
"eai_agentplatform/backend/internal/web"
|
|
)
|
|
|
|
// certRepo 证书仓库(便于测试时覆写),包内共享。
|
|
var certRepo repository.CertificateRepo
|
|
|
|
func init() {
|
|
certRepo = repository.CertificateRepo{}
|
|
}
|
|
|
|
// issueCertificate 正式考试通过后颁发证书(幂等:同一 exam_record 只发一张)。
|
|
func issueCertificate(u *model.User, rec model.ExamRecord) {
|
|
if u == nil || !rec.Passed {
|
|
return
|
|
}
|
|
if _, found := certRepo.GetByExamRecord(rec.ID); found {
|
|
return
|
|
}
|
|
certNo := fmt.Sprintf("BST-%06d", rec.ID)
|
|
certRepo.Insert(&model.Certificate{
|
|
UserID: u.ID, ExamRecordID: rec.ID, UserName: u.FullName,
|
|
ExamName: rec.ExamName, Score: rec.Score, TotalScore: rec.TotalScore, PassScore: rec.PassScore,
|
|
CertNo: certNo, IssuedAt: time.Now(),
|
|
})
|
|
}
|
|
|
|
// MyCertificates GET /api/exam/certificates —— 我的证书列表
|
|
func MyCertificates(c *gin.Context) {
|
|
u := middleware.CurrentUser(c)
|
|
if u == nil {
|
|
web.Fail(c, web.NewAuthError("未登录"))
|
|
return
|
|
}
|
|
items := certRepo.ListByUser(u.ID)
|
|
if items == nil {
|
|
items = []model.Certificate{}
|
|
}
|
|
web.OK(c, items)
|
|
}
|
|
|
|
// CertificateDetail GET /api/exam/certificates/{id} —— 证书详情(本人或管理员可看,用于打印留存)
|
|
func CertificateDetail(c *gin.Context) {
|
|
u := middleware.CurrentUser(c)
|
|
id, ok := parseID(c, "id")
|
|
if !ok {
|
|
return
|
|
}
|
|
cert, found := certRepo.GetByID(id)
|
|
if !found {
|
|
web.Fail(c, web.NewNotFoundError("证书不存在"))
|
|
return
|
|
}
|
|
if u == nil || (u.Role != "admin" && cert.UserID != u.ID) {
|
|
web.Fail(c, web.NewForbiddenError("无权查看他人证书"))
|
|
return
|
|
}
|
|
web.OK(c, cert)
|
|
}
|
|
|
|
// AdminCertificates GET /api/system/certificates —— 全员证书(管理员)
|
|
func AdminCertificates(c *gin.Context) {
|
|
web.OK(c, certRepo.ListAll())
|
|
}
|