From c3e5fa20033e0367937c7deea6cef55fdf588331 Mon Sep 17 00:00:00 2001 From: engigu Date: Sun, 12 Oct 2025 14:35:49 +0800 Subject: [PATCH] feat: add cronmessage sendNow --- routers/api/v1/cron_msg.go | 37 ++++++++++++++++ routers/router.go | 1 + service/cron_msg_service/cron_msg.go | 26 ++++++++++++ service/cron_msg_service/startup_cron_msg.go | 42 +++++++++++++++++++ .../pages/cronMessages/AddCronMessages.vue | 39 +++++++++++++++++ .../pages/cronMessages/CronMessageForm.vue | 14 ++++++- .../pages/cronMessages/EditCronMessages.vue | 39 +++++++++++++++++ 7 files changed, 197 insertions(+), 1 deletion(-) diff --git a/routers/api/v1/cron_msg.go b/routers/api/v1/cron_msg.go index b702aa8..11d0639 100644 --- a/routers/api/v1/cron_msg.go +++ b/routers/api/v1/cron_msg.go @@ -161,3 +161,40 @@ func EditCronMsgTask(c *gin.Context) { cron_msg_service.UpdateCronMsgToCronServer(msg) appG.CResponse(http.StatusOK, "编辑定时消息成功!", nil) } + +type SendNowCronMsgReq struct { + TaskID string `json:"task_id" validate:"required,max=100,min=1" label:"关联的任务id"` + Title string `json:"title" validate:"required,max=100,min=1" label:"消息标题"` + Content string `json:"content" validate:"required,max=10000,min=1" label:"消息内容"` + Url string `json:"url" validate:"" label:"消息详情地址"` +} + +// SendNowCronMsg 立即发送定时消息 +func SendNowCronMsg(c *gin.Context) { + var ( + appG = app.Gin{C: c} + req SendNowCronMsgReq + ) + + errCode, errMsg := app.BindJsonAndPlayValid(c, &req) + if errCode != e.SUCCESS { + appG.CResponse(errCode, errMsg, nil) + return + } + + CronMsgService := cron_msg_service.CronMsgService{ + TaskID: req.TaskID, + Title: req.Title, + Content: req.Content, + Url: req.Url, + } + + // 调用立即发送服务 + err := CronMsgService.SendNowByParams(c.ClientIP()) + if err != nil { + appG.CResponse(http.StatusBadRequest, err.Error(), nil) + return + } + + appG.CResponse(http.StatusOK, "发送成功!", nil) +} diff --git a/routers/router.go b/routers/router.go index 12e7b69..66d3790 100644 --- a/routers/router.go +++ b/routers/router.go @@ -93,6 +93,7 @@ func InitRouter(f embed.FS) *gin.Engine { apiV1.GET("/cronmessages/list", v1.GetCronMsgList) apiV1.POST("/cronmessages/delete", v1.DeleteCronMsgTask) apiV1.POST("/cronmessages/edit", v1.EditCronMsgTask) + apiV1.POST("/cronmessages/sendnow", v1.SendNowCronMsg) // hostedMessage apiV1.GET("/hostedmessages/list", v1.GetHostMessageList) diff --git a/service/cron_msg_service/cron_msg.go b/service/cron_msg_service/cron_msg.go index c3b479b..b3df93b 100644 --- a/service/cron_msg_service/cron_msg.go +++ b/service/cron_msg_service/cron_msg.go @@ -85,3 +85,29 @@ func GetCronNextTime(cronExpr string) string { nextTime := schedule.Next(time.Now()).Format("2006-01-02 15:04:05") return nextTime } + +// SendNow 立即发送定时消息(根据定时消息ID) +func (st *CronMsgService) SendNow(callerIP string) error { + // 获取定时消息详情 + msg, err := st.GetByID() + if err != nil { + return err + } + + // 调用发送服务 + return SendCronMessage(msg, callerIP) +} + +// SendNowByParams 立即发送定时消息(根据传入的参数) +func (st *CronMsgService) SendNowByParams(callerIP string) error { + // 直接使用传入的参数构造消息对象 + msg := models.CronMessages{ + TaskID: st.TaskID, + Title: st.Title, + Content: st.Content, + Url: st.Url, + } + + // 调用发送服务 + return SendCronMessage(msg, callerIP) +} diff --git a/service/cron_msg_service/startup_cron_msg.go b/service/cron_msg_service/startup_cron_msg.go index 9bb3183..fc88a4f 100644 --- a/service/cron_msg_service/startup_cron_msg.go +++ b/service/cron_msg_service/startup_cron_msg.go @@ -2,6 +2,7 @@ package cron_msg_service import ( "fmt" + "strings" "github.com/sirupsen/logrus" "message-nest/models" "message-nest/pkg/constant" @@ -106,3 +107,44 @@ func RemoveCronMsgToCronServer(msg models.CronMessages) { func StartUpMsgCronTask() { MsgCronTask{}.Register() } + +// SendCronMessage 发送定时消息(用于立即发送) +func SendCronMessage(msg models.CronMessages, callerIP string) error { + // 查询关联的发信任务 + task, err := models.GetTaskByID(msg.TaskID) + if err != nil { + return fmt.Errorf("发信任务不存在: %s", msg.TaskID) + } + + // 创建发送服务 + sender := send_message_service.SendMessageService{ + TaskID: task.ID, + Title: msg.Title, + Text: msg.Content, + URL: msg.Url, + CallerIp: callerIP, + DefaultLogger: logrus.WithFields(logrus.Fields{ + "prefix": "[Manual Send Cron Message]", + }), + } + + // 预检查 + taskData, err := sender.SendPreCheck() + if err != nil { + errMsg := err.Error() + // 如果是没有关联实例的错误,返回更友好的提示 + if strings.Contains(errMsg, "没有关联任何实例") { + return fmt.Errorf("该发信任务尚未配置发送实例,请先在【发信任务】页面为任务 [%s] 添加至少一个发送实例", task.Name) + } + return fmt.Errorf("发送预检查失败: %s", errMsg) + } + + // 发送消息 + _, err = sender.Send(taskData) + if err != nil { + return fmt.Errorf("发送失败: %s", err.Error()) + } + + logrus.Infof("立即发送定时消息成功,消息id: %s,消息名: %s", msg.ID, msg.Name) + return nil +} diff --git a/web/src/components/pages/cronMessages/AddCronMessages.vue b/web/src/components/pages/cronMessages/AddCronMessages.vue index a3281d4..c89a3cf 100644 --- a/web/src/components/pages/cronMessages/AddCronMessages.vue +++ b/web/src/components/pages/cronMessages/AddCronMessages.vue @@ -69,6 +69,44 @@ const handleCancel = () => { emit('cancel') emit('update:open', false) } + +// 立即发送(新增模式也支持,可以在创建前测试发送效果) +const handleSendNow = async () => { + // 验证必填字段 + if (!formData.task_id) { + toast.error('请先选择关联的发信任务') + return + } + if (!formData.title) { + toast.error('请先填写消息标题') + return + } + if (!formData.content) { + toast.error('请先填写消息内容') + return + } + + loading.value = true + try { + const postData = { + task_id: formData.task_id, + title: formData.title, + content: formData.content, + url: formData.url + } + + const rsp = await request.post('/cronmessages/sendnow', postData) + if (rsp.data.code === 200) { + toast.success(rsp.data.msg) + } else { + toast.error(rsp.data.msg) + } + } catch (error) { + toast.error('发送失败,请稍后重试') + } finally { + loading.value = false + } +} diff --git a/web/src/components/pages/cronMessages/CronMessageForm.vue b/web/src/components/pages/cronMessages/CronMessageForm.vue index d62b3d3..e33ced8 100644 --- a/web/src/components/pages/cronMessages/CronMessageForm.vue +++ b/web/src/components/pages/cronMessages/CronMessageForm.vue @@ -8,7 +8,7 @@ import { Textarea } from '@/components/ui/textarea' import { request } from '@/api/api' interface CronMessageFormData { - id?: number + id?: string name: string cron_expression: string title: string @@ -27,6 +27,7 @@ interface Emits { (e: 'update:modelValue', value: CronMessageFormData): void (e: 'submit'): void (e: 'cancel'): void + (e: 'sendNow'): void } const props = withDefaults(defineProps(), { @@ -92,6 +93,11 @@ const handleCancel = () => { emit('cancel') } +// 立即发送 +const handleSendNow = () => { + emit('sendNow') +} + // 组件挂载时加载数据 loadAvailableTasks() @@ -117,6 +123,9 @@ loadAvailableTasks() +

+ ⚠️ 请确保所选任务已配置至少一个发送实例,否则无法发送 +

@@ -152,6 +161,9 @@ loadAvailableTasks() + diff --git a/web/src/components/pages/cronMessages/EditCronMessages.vue b/web/src/components/pages/cronMessages/EditCronMessages.vue index 0e51fd2..8bf2509 100644 --- a/web/src/components/pages/cronMessages/EditCronMessages.vue +++ b/web/src/components/pages/cronMessages/EditCronMessages.vue @@ -89,6 +89,44 @@ const handleCancel = () => { emit('update:open', false) } +// 立即发送 +const handleSendNow = async () => { + // 验证必填字段 + if (!formData.task_id) { + toast.error('请先选择关联的发信任务') + return + } + if (!formData.title) { + toast.error('请先填写消息标题') + return + } + if (!formData.content) { + toast.error('请先填写消息内容') + return + } + + loading.value = true + try { + const postData = { + task_id: formData.task_id, + title: formData.title, + content: formData.content, + url: formData.url + } + + const rsp = await request.post('/cronmessages/sendnow', postData) + if (rsp.data.code === 200) { + toast.success(rsp.data.msg) + } else { + toast.error(rsp.data.msg) + } + } catch (error) { + toast.error('发送失败,请稍后重试') + } finally { + loading.value = false + } +} + // 监听 cronMessage 变化,更新表单数据 watch( () => props.cronMessage, @@ -114,6 +152,7 @@ watch( :loading="loading" @submit="handleSubmit" @cancel="handleCancel" + @send-now="handleSendNow" />