feat(knowledge): 饲养阶段风险提示接口与种子数据(#13)
This commit is contained in:
@@ -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("未知的饲养阶段")
|
||||
}
|
||||
Reference in New Issue
Block a user