Commit 3f3b8f29 authored by yuguo's avatar yuguo

fix

parent 04584395
...@@ -8,6 +8,7 @@ import ( ...@@ -8,6 +8,7 @@ import (
"strings" "strings"
"internet-hospital/internal/model" "internet-hospital/internal/model"
"internet-hospital/internal/service/notification"
"internet-hospital/pkg/agent" "internet-hospital/pkg/agent"
"internet-hospital/pkg/agent/tools" "internet-hospital/pkg/agent/tools"
"internet-hospital/pkg/database" "internet-hospital/pkg/database"
...@@ -110,7 +111,10 @@ func WireCallbacks() { ...@@ -110,7 +111,10 @@ func WireCallbacks() {
return output.Response, nil return output.Response, nil
}) })
log.Println("[InitTools] AgentCallFn & WorkflowTriggerFn & SkillExecutor 注入完成") // 注入通知回调
tools.NotifyFn = notification.Notify
log.Println("[InitTools] AgentCallFn & WorkflowTriggerFn & NotifyFn & SkillExecutor 注入完成")
} }
// syncToolsToDB 将注册的工具元数据写入 AgentTool 表(不存在则创建,存在则不覆盖) // syncToolsToDB 将注册的工具元数据写入 AgentTool 表(不存在则创建,存在则不覆盖)
......
package admin package admin
import ( import (
"fmt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"internet-hospital/internal/service/doctor" "internet-hospital/internal/service/doctor"
...@@ -144,6 +146,7 @@ func (h *Handler) RegisterRoutes(r *gin.RouterGroup) { ...@@ -144,6 +146,7 @@ func (h *Handler) RegisterRoutes(r *gin.RouterGroup) {
adm.POST("/seed/rbac", h.SeedRBACHandler) adm.POST("/seed/rbac", h.SeedRBACHandler)
adm.POST("/seed/departments", h.SeedDepartmentsHandler) adm.POST("/seed/departments", h.SeedDepartmentsHandler)
adm.POST("/seed/medicines", h.SeedMedicinesHandler) adm.POST("/seed/medicines", h.SeedMedicinesHandler)
adm.POST("/seed/workflows", h.SeedWorkflowsHandler)
// 数据库维护 // 数据库维护
adm.GET("/db/extra-tables", h.ScanExtraTablesHandler) adm.GET("/db/extra-tables", h.ScanExtraTablesHandler)
...@@ -491,3 +494,14 @@ func (h *Handler) SeedMedicinesHandler(c *gin.Context) { ...@@ -491,3 +494,14 @@ func (h *Handler) SeedMedicinesHandler(c *gin.Context) {
response.Success(c, gin.H{"message": "药品种子数据已导入"}) response.Success(c, gin.H{"message": "药品种子数据已导入"})
} }
// SeedWorkflowsHandler 手动导入工作流种子数据
func (h *Handler) SeedWorkflowsHandler(c *gin.Context) {
created, skipped, errs := SeedWorkflows()
response.Success(c, gin.H{
"message": fmt.Sprintf("工作流种子数据导入完成:创建 %d 个,跳过 %d 个,失败 %d 个", created, skipped, len(errs)),
"created": created,
"skipped": skipped,
"errors": errs,
})
}
package admin
import (
"log"
"os"
"internet-hospital/internal/model"
"internet-hospital/pkg/database"
)
// SeedWorkflows 初始化工作流种子数据(幂等,表空时从 scripts/seed_workflows.sql 导入)
func SeedWorkflows() (created int, skipped int, errors []string) {
db := database.GetDB()
if db == nil {
errors = append(errors, "数据库未初始化")
return
}
var count int64
db.Model(&model.WorkflowDefinition{}).Count(&count)
if count > 0 {
skipped = int(count)
return
}
sqlBytes, err := os.ReadFile("scripts/seed_workflows.sql")
if err != nil {
errMsg := "无法读取 scripts/seed_workflows.sql: " + err.Error()
log.Printf("[SeedWorkflows] %s", errMsg)
errors = append(errors, errMsg)
return
}
if err := db.Exec(string(sqlBytes)).Error; err != nil {
errMsg := "执行 SQL 失败: " + err.Error()
log.Printf("[SeedWorkflows] %s", errMsg)
errors = append(errors, errMsg)
return
}
db.Model(&model.WorkflowDefinition{}).Count(&count)
created = int(count)
log.Printf("[SeedWorkflows] 工作流种子数据已导入(%d 个)", created)
return
}
...@@ -52,6 +52,7 @@ type CreateDoctorReq struct { ...@@ -52,6 +52,7 @@ type CreateDoctorReq struct {
Hospital string `json:"hospital" binding:"required"` Hospital string `json:"hospital" binding:"required"`
Introduction string `json:"introduction"` Introduction string `json:"introduction"`
Specialties []string `json:"specialties"` Specialties []string `json:"specialties"`
Price int `json:"price"`
} }
// CreateAdminReq 创建管理员请求 // CreateAdminReq 创建管理员请求
...@@ -438,7 +439,7 @@ func (s *Service) CreateDoctor(ctx context.Context, req *CreateDoctorReq) error ...@@ -438,7 +439,7 @@ func (s *Service) CreateDoctor(ctx context.Context, req *CreateDoctorReq) error
Introduction: req.Introduction, Introduction: req.Introduction,
Specialties: req.Specialties, Specialties: req.Specialties,
Rating: 5.0, Rating: 5.0,
Price: 5000, // 默认50元 Price: req.Price,
Status: "approved", Status: "approved",
} }
if err := s.db.Create(doctor).Error; err != nil { if err := s.db.Create(doctor).Error; err != nil {
......
...@@ -5,13 +5,13 @@ import ( ...@@ -5,13 +5,13 @@ import (
"fmt" "fmt"
"time" "time"
"internet-hospital/internal/model"
"internet-hospital/pkg/agent" "internet-hospital/pkg/agent"
"internet-hospital/pkg/database"
) )
// NotifyFn 通知回调函数,由 WireCallbacks 注入 notification.Notify
var NotifyFn func(userID, title, content, nType, relatedID string)
// SendNotificationTool 向用户发送系统通知(站内信 + WebSocket 推送) // SendNotificationTool 向用户发送系统通知(站内信 + WebSocket 推送)
// 此工具之前在分类 map 中声明但从未注册,此次完整落地
type SendNotificationTool struct{} type SendNotificationTool struct{}
func (t *SendNotificationTool) Name() string { return "send_notification" } func (t *SendNotificationTool) Name() string { return "send_notification" }
...@@ -46,21 +46,15 @@ func (t *SendNotificationTool) Parameters() []agent.ToolParameter { ...@@ -46,21 +46,15 @@ func (t *SendNotificationTool) Parameters() []agent.ToolParameter {
Enum: []string{"reminder", "alert", "info", "followup", "system"}, Enum: []string{"reminder", "alert", "info", "followup", "system"},
}, },
{ {
Name: "priority", Name: "related_id",
Type: "string", Type: "string",
Description: "优先级:normal/high/urgent", Description: "关联业务ID(如问诊ID、处方ID等)",
Required: false, Required: false,
Enum: []string{"normal", "high", "urgent"},
}, },
} }
} }
func (t *SendNotificationTool) Execute(_ context.Context, params map[string]interface{}) (interface{}, error) { func (t *SendNotificationTool) Execute(_ context.Context, params map[string]interface{}) (interface{}, error) {
db := database.GetDB()
if db == nil {
return nil, fmt.Errorf("数据库未初始化")
}
userID, ok := params["user_id"].(string) userID, ok := params["user_id"].(string)
if !ok || userID == "" { if !ok || userID == "" {
return nil, fmt.Errorf("user_id 必填") return nil, fmt.Errorf("user_id 必填")
...@@ -74,28 +68,19 @@ func (t *SendNotificationTool) Execute(_ context.Context, params map[string]inte ...@@ -74,28 +68,19 @@ func (t *SendNotificationTool) Execute(_ context.Context, params map[string]inte
if nType == "" { if nType == "" {
nType = "info" nType = "info"
} }
priority, _ := params["priority"].(string) relatedID, _ := params["related_id"].(string)
if priority == "" {
priority = "normal"
}
// 写入系统日志表作为站内信(复用 SystemLog) if NotifyFn == nil {
log := model.SystemLog{ return nil, fmt.Errorf("通知服务未初始化")
Action: "notification",
Resource: fmt.Sprintf("%s/%s", nType, priority),
Detail: fmt.Sprintf("[%s] %s: %s", nType, title, content),
UserID: userID,
}
if err := db.Create(&log).Error; err != nil {
return nil, fmt.Errorf("写入通知失败: %w", err)
} }
NotifyFn(userID, title, content, nType, relatedID)
return map[string]interface{}{ return map[string]interface{}{
"notification_id": log.ID,
"user_id": userID, "user_id": userID,
"title": title, "title": title,
"type": nType, "type": nType,
"priority": priority, "related_id": relatedID,
"sent_at": time.Now().Format(time.RFC3339), "sent_at": time.Now().Format(time.RFC3339),
}, nil }, nil
} }
...@@ -196,6 +196,20 @@ func (e *Engine) executeNode(ctx context.Context, wf *Workflow, node *Node, exec ...@@ -196,6 +196,20 @@ func (e *Engine) executeNode(ctx context.Context, wf *Workflow, node *Node, exec
execCtx.NodeOutputs[node.ID] = output execCtx.NodeOutputs[node.ID] = output
// 执行下一个节点 // 执行下一个节点
// 条件节点支持真正的分支:NextNodes[0] = true 分支,NextNodes[1] = false 分支
if node.Type == NodeTypeCondition && len(node.NextNodes) >= 2 {
condResult, _ := output.(map[string]interface{})
result, _ := condResult["result"].(bool)
targetIdx := 0
if !result {
targetIdx = 1
}
if next, ok := wf.Nodes[node.NextNodes[targetIdx]]; ok {
if _, err := e.executeNode(ctx, wf, next, execCtx); err != nil {
return nil, err
}
}
} else {
for _, nextID := range node.NextNodes { for _, nextID := range node.NextNodes {
if next, ok := wf.Nodes[nextID]; ok { if next, ok := wf.Nodes[nextID]; ok {
if _, err := e.executeNode(ctx, wf, next, execCtx); err != nil { if _, err := e.executeNode(ctx, wf, next, execCtx); err != nil {
...@@ -203,6 +217,7 @@ func (e *Engine) executeNode(ctx context.Context, wf *Workflow, node *Node, exec ...@@ -203,6 +217,7 @@ func (e *Engine) executeNode(ctx context.Context, wf *Workflow, node *Node, exec
} }
} }
} }
}
return output, nil return output, nil
} }
......
INSERT INTO workflow_definitions (workflow_id, name, description, category, version, definition, status, created_at, updated_at) VALUES
('pre_consult_triage', '预问诊智能分诊', '预问诊完成后根据严重程度分级通知:中度及以上通知医生优先处理', 'pre_consult', 1,
'{"id":"pre_consult_triage","name":"预问诊智能分诊","nodes":{"start":{"id":"start","type":"start","name":"开始","config":{},"next_nodes":["check_severity"]},"check_severity":{"id":"check_severity","type":"code","name":"检查严重程度","config":{"expression":"severity == ''moderate'' || severity == ''severe'' ? true : false"},"next_nodes":["condition_severity"]},"condition_severity":{"id":"condition_severity","type":"condition","name":"是否中度以上","config":{"expression":"check_severity == true"},"next_nodes":["notify_doctor","end"]},"notify_doctor":{"id":"notify_doctor","type":"tool","name":"通知医生","config":{"tool_name":"send_notification","params":{"user_id":"{{doctor_id}}","title":"预问诊分诊提醒","content":"有一位患者完成预问诊,严重程度:{{severity}},建议科室:{{department}},请优先关注","type":"alert","related_id":"{{pre_consult_id}}"}},"next_nodes":["end"]},"end":{"id":"end","type":"end","name":"结束","config":{},"next_nodes":[]}},"edges":[{"id":"e1","source_node":"start","target_node":"check_severity"},{"id":"e2","source_node":"check_severity","target_node":"condition_severity"},{"id":"e3","source_node":"condition_severity","target_node":"notify_doctor","condition":"true"},{"id":"e4","source_node":"condition_severity","target_node":"end","condition":"false"},{"id":"e5","source_node":"notify_doctor","target_node":"end"}]}',
'active', NOW(), NOW()),
('consult_created_notify', '问诊创建通知', '患者创建问诊后自动通知医生', 'consult_created', 1,
'{"id":"consult_created_notify","name":"问诊创建通知","nodes":{"start":{"id":"start","type":"start","name":"开始","config":{},"next_nodes":["notify_doctor"]},"notify_doctor":{"id":"notify_doctor","type":"tool","name":"通知医生","config":{"tool_name":"send_notification","params":{"user_id":"{{doctor_id}}","title":"新问诊请求","content":"您有一条新的{{type}}问诊请求,请及时处理","type":"reminder","related_id":"{{consult_id}}"}},"next_nodes":["end"]},"end":{"id":"end","type":"end","name":"结束","config":{},"next_nodes":[]}},"edges":[{"id":"e1","source_node":"start","target_node":"notify_doctor"},{"id":"e2","source_node":"notify_doctor","target_node":"end"}]}',
'active', NOW(), NOW()),
('consult_ended_followup', '问诊结束随访', '问诊结束后通知患者评价并安排随访提醒', 'consult_ended', 1,
'{"id":"consult_ended_followup","name":"问诊结束随访","nodes":{"start":{"id":"start","type":"start","name":"开始","config":{},"next_nodes":["notify_patient"]},"notify_patient":{"id":"notify_patient","type":"tool","name":"通知患者评价","config":{"tool_name":"send_notification","params":{"user_id":"{{patient_id}}","title":"问诊已结束","content":"您的问诊已结束,请对本次服务进行评价,您的反馈有助于提升服务质量","type":"followup","related_id":"{{consult_id}}"}},"next_nodes":["end"]},"end":{"id":"end","type":"end","name":"结束","config":{},"next_nodes":[]}},"edges":[{"id":"e1","source_node":"start","target_node":"notify_patient"},{"id":"e2","source_node":"notify_patient","target_node":"end"}]}',
'active', NOW(), NOW()),
('prescription_review', '处方创建审核提醒', '处方创建后根据金额决定是否需要药师人工审核', 'prescription_created', 1,
'{"id":"prescription_review","name":"处方创建审核提醒","nodes":{"start":{"id":"start","type":"start","name":"开始","config":{},"next_nodes":["check_amount"]},"check_amount":{"id":"check_amount","type":"code","name":"检查金额","config":{"expression":"total_amount > 200 ? true : false"},"next_nodes":["condition_amount"]},"condition_amount":{"id":"condition_amount","type":"condition","name":"金额是否超200","config":{"expression":"check_amount == true"},"next_nodes":["human_review","notify_patient"]},"human_review":{"id":"human_review","type":"human_review","name":"药师审核","config":{"assignee_role":"pharmacist","title":"高金额处方审核","description":"处方金额超过200元,需要药师复核"},"next_nodes":["notify_patient"]},"notify_patient":{"id":"notify_patient","type":"tool","name":"通知患者","config":{"tool_name":"send_notification","params":{"user_id":"{{patient_id}}","title":"处方已开具","content":"医生已为您开具处方,请查看处方详情并完成支付","type":"reminder","related_id":"{{prescription_id}}"}},"next_nodes":["end"]},"end":{"id":"end","type":"end","name":"结束","config":{},"next_nodes":[]}},"edges":[{"id":"e1","source_node":"start","target_node":"check_amount"},{"id":"e2","source_node":"check_amount","target_node":"condition_amount"},{"id":"e3","source_node":"condition_amount","target_node":"human_review","condition":"true"},{"id":"e4","source_node":"condition_amount","target_node":"notify_patient","condition":"false"},{"id":"e5","source_node":"human_review","target_node":"notify_patient"},{"id":"e6","source_node":"notify_patient","target_node":"end"}]}',
'active', NOW(), NOW()),
('prescription_approved_notify', '处方审核通过通知', '处方审核通过后通知患者取药或支付', 'prescription_approved', 1,
'{"id":"prescription_approved_notify","name":"处方审核通过通知","nodes":{"start":{"id":"start","type":"start","name":"开始","config":{},"next_nodes":["notify_patient"]},"notify_patient":{"id":"notify_patient","type":"tool","name":"通知患者","config":{"tool_name":"send_notification","params":{"user_id":"{{patient_id}}","title":"处方审核通过","content":"您的处方已通过审核,请前往支付并等待配送","type":"reminder","related_id":"{{prescription_id}}"}},"next_nodes":["end"]},"end":{"id":"end","type":"end","name":"结束","config":{},"next_nodes":[]}},"edges":[{"id":"e1","source_node":"start","target_node":"notify_patient"},{"id":"e2","source_node":"notify_patient","target_node":"end"}]}',
'active', NOW(), NOW()),
('payment_completed_process', '支付完成处理', '支付完成后根据订单类型发送不同通知', 'payment_completed', 1,
'{"id":"payment_completed_process","name":"支付完成处理","nodes":{"start":{"id":"start","type":"start","name":"开始","config":{},"next_nodes":["gen_message"]},"gen_message":{"id":"gen_message","type":"code","name":"生成通知内容","config":{"expression":"order_type == ''consult'' ? ''您的问诊订单已支付成功,医生将尽快接诊'' : ''您的处方订单已支付成功,药品将尽快配送''"},"next_nodes":["notify_user"]},"notify_user":{"id":"notify_user","type":"tool","name":"通知用户","config":{"tool_name":"send_notification","params":{"user_id":"{{user_id}}","title":"支付成功","content":"{{gen_message}}","type":"info","related_id":"{{order_id}}"}},"next_nodes":["end"]},"end":{"id":"end","type":"end","name":"结束","config":{},"next_nodes":[]}},"edges":[{"id":"e1","source_node":"start","target_node":"gen_message"},{"id":"e2","source_node":"gen_message","target_node":"notify_user"},{"id":"e3","source_node":"notify_user","target_node":"end"}]}',
'active', NOW(), NOW()),
('renewal_requested_notify', '续方申请通知', '患者提交续方申请后通知相关医生审核', 'renewal_requested', 1,
'{"id":"renewal_requested_notify","name":"续方申请通知","nodes":{"start":{"id":"start","type":"start","name":"开始","config":{},"next_nodes":["notify_patient"]},"notify_patient":{"id":"notify_patient","type":"tool","name":"通知患者","config":{"tool_name":"send_notification","params":{"user_id":"{{patient_id}}","title":"续方申请已提交","content":"您的{{disease_name}}续方申请已提交,医生将尽快审核","type":"reminder","related_id":"{{renewal_id}}"}},"next_nodes":["end"]},"end":{"id":"end","type":"end","name":"结束","config":{},"next_nodes":[]}},"edges":[{"id":"e1","source_node":"start","target_node":"notify_patient"},{"id":"e2","source_node":"notify_patient","target_node":"end"}]}',
'active', NOW(), NOW()),
('health_alert_escalate', '健康预警分级通知', '健康指标异常时根据严重等级分级通知:重度紧急通知,轻中度普通提醒', 'health_alert', 1,
'{"id":"health_alert_escalate","name":"健康预警分级通知","nodes":{"start":{"id":"start","type":"start","name":"开始","config":{},"next_nodes":["check_level"]},"check_level":{"id":"check_level","type":"code","name":"检查预警等级","config":{"expression":"alert_level == ''severe'' ? true : false"},"next_nodes":["condition_level"]},"condition_level":{"id":"condition_level","type":"condition","name":"是否重度预警","config":{"expression":"check_level == true"},"next_nodes":["urgent_notify","normal_notify"]},"urgent_notify":{"id":"urgent_notify","type":"tool","name":"紧急通知","config":{"tool_name":"send_notification","params":{"user_id":"{{patient_id}}","title":"健康指标严重异常","content":"您的{{metric_type}}指标严重异常({{value1}}{{unit}}),请立即就医!","type":"alert","related_id":""}},"next_nodes":["end"]},"normal_notify":{"id":"normal_notify","type":"tool","name":"普通提醒","config":{"tool_name":"send_notification","params":{"user_id":"{{patient_id}}","title":"健康指标异常提醒","content":"您的{{metric_type}}指标异常({{value1}}{{unit}}),建议关注并适时就医","type":"reminder","related_id":""}},"next_nodes":["end"]},"end":{"id":"end","type":"end","name":"结束","config":{},"next_nodes":[]}},"edges":[{"id":"e1","source_node":"start","target_node":"check_level"},{"id":"e2","source_node":"check_level","target_node":"condition_level"},{"id":"e3","source_node":"condition_level","target_node":"urgent_notify","condition":"true"},{"id":"e4","source_node":"condition_level","target_node":"normal_notify","condition":"false"},{"id":"e5","source_node":"urgent_notify","target_node":"end"},{"id":"e6","source_node":"normal_notify","target_node":"end"}]}',
'active', NOW(), NOW())
ON CONFLICT (workflow_id) DO NOTHING;
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment