feat(knowledge): 饲养阶段风险提示接口与种子数据(#13)

This commit is contained in:
weijuesen
2026-08-12 14:44:41 +08:00
parent 9c8388d220
commit 6d9cfc7aa9
3 changed files with 169 additions and 0 deletions
+36
View File
@@ -28,9 +28,45 @@ func RegisterKnowledgeRoutes(rg *gin.RouterGroup, db *gorm.DB, s3 *service.S3Ser
rg.PATCH("/knowledge/articles/:id", write, updateArticle(db))
rg.DELETE("/knowledge/articles/:id", write, deleteArticle(db))
rg.GET("/knowledge/stage-hints", read, listStageHints(db))
registerKnowledgeImageRoute(rg, db, s3, imageBucket)
}
// listStageHints 饲养阶段风险提示(?stage= 过滤;GET 天然幂等)
func listStageHints(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
hints, err := model.StageHintsByStage(c.Query("stage"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
resp := make([]gin.H, 0, len(hints))
for _, h := range hints {
diseases := make([]gin.H, 0, len(h.Diseases))
for _, dh := range h.Diseases {
id := ""
var d model.Disease
if db.Where("name = ?", dh.Name).First(&d).Error == nil {
id = d.ID
}
diseases = append(diseases, gin.H{
"id": id,
"name": dh.Name,
"reason": dh.Reason,
})
}
resp = append(resp, gin.H{
"stage": h.Stage,
"label": h.Label,
"summary": h.Summary,
"diseases": diseases,
})
}
c.JSON(http.StatusOK, resp)
}
}
// listDiseases 蚕病百科列表(支持 category/keyword/enabled 过滤)
func listDiseases(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
@@ -0,0 +1,66 @@
package model
import "testing"
func TestSeedStageHintsKeysValid(t *testing.T) {
valid := map[string]bool{"young": true, "grown": true, "late5": true, "pupa": true}
seen := map[string]bool{}
for _, h := range SeedStageHints {
if !valid[h.Stage] {
t.Errorf("非法阶段 key: %s", h.Stage)
}
if seen[h.Stage] {
t.Errorf("阶段 key 重复: %s", h.Stage)
}
seen[h.Stage] = true
if h.Label == "" || h.Summary == "" {
t.Errorf("阶段 %s 缺少 label/summary", h.Stage)
}
if len(h.Diseases) == 0 {
t.Errorf("阶段 %s 缺少病种", h.Stage)
}
}
}
func TestSeedStageHintsDiseasesExist(t *testing.T) {
names := map[string]bool{}
for _, d := range SeedDiseases {
names[d.Name] = true
}
for _, h := range SeedStageHints {
for _, dh := range h.Diseases {
if !names[dh.Name] {
t.Errorf("阶段 %s 引用了不存在的病种: %s", h.Stage, dh.Name)
}
if dh.Reason == "" {
t.Errorf("阶段 %s 病种 %s 缺少原因", h.Stage, dh.Name)
}
}
}
}
func TestStageHintsByStageEmptyReturnsAll(t *testing.T) {
hints, err := StageHintsByStage("")
if err != nil {
t.Fatalf("空阶段不应报错: %v", err)
}
if len(hints) != len(SeedStageHints) {
t.Errorf("空阶段应返回全部 %d 条,实际 %d", len(SeedStageHints), len(hints))
}
}
func TestStageHintsByStageYoung(t *testing.T) {
hints, err := StageHintsByStage("young")
if err != nil {
t.Fatalf("young 不应报错: %v", err)
}
if len(hints) != 1 || hints[0].Stage != "young" {
t.Errorf("young 应返回 1 条且 key=young,实际 %+v", hints)
}
}
func TestStageHintsByStageUnknown(t *testing.T) {
if _, err := StageHintsByStage("unknown"); err == nil {
t.Error("未知阶段应报错")
}
}
@@ -0,0 +1,67 @@
package model
import "errors"
// StageDiseaseHint 阶段风险提示中的病种条目
type StageDiseaseHint struct {
Name string `json:"name"`
Reason string `json:"reason"`
}
// StageHint 饲养阶段风险提示
type StageHint struct {
Stage string `json:"stage"`
Label string `json:"label"`
Summary string `json:"summary"`
Diseases []StageDiseaseHint `json:"diseases"`
}
// SeedStageHints 饲养阶段风险种子数据(内容来源:规格书附录 11.1 高发阶段 + 3.2.3 蚕病环境风险规则)
var SeedStageHints = []StageHint{
{
Stage: "young",
Label: "小蚕期(1-3龄)",
Summary: "小蚕期抗病力较弱,重点防真菌感染与蚕座卫生问题",
Diseases: []StageDiseaseHint{
{Name: "其他真菌病(绿僵病/黄僵病/曲霉病)", Reason: "曲霉病多发于小蚕期,注意蚕座卫生与湿度控制"},
},
},
{
Stage: "grown",
Label: "大蚕期(4-5龄)",
Summary: "群体密度大、食桑量大,注意密度、通风与桑叶卫生",
Diseases: []StageDiseaseHint{
{Name: "软化病", Reason: "5 龄集中暴发,控制蚕头密度、加强通风、避免桑叶潮湿"},
{Name: "核型多角体病", Reason: "5 龄后期至蛹期高发,春蚕期较多发"},
},
},
{
Stage: "late5",
Label: "5龄后期",
Summary: "临近上蔟,注意温湿度稳定,防止应激诱发急性发病",
Diseases: []StageDiseaseHint{
{Name: "核型多角体病", Reason: "温度突变(>30℃ 或 <20℃)易诱发潜伏感染转急性发病"},
},
},
{
Stage: "pupa",
Label: "蛹期",
Summary: "蛹期发病影响制种,注意温湿度稳定",
Diseases: []StageDiseaseHint{
{Name: "核型多角体病", Reason: "5 龄后期至蛹期高发,注意温湿度稳定"},
},
},
}
// StageHintsByStage 按阶段 key 过滤风险提示;stage 为空返回全部
func StageHintsByStage(stage string) ([]StageHint, error) {
if stage == "" {
return SeedStageHints, nil
}
for _, h := range SeedStageHints {
if h.Stage == stage {
return []StageHint{h}, nil
}
}
return nil, errors.New("未知的饲养阶段")
}