From f136c9abdb9be269b61ee371c8f85da0ee22eaf9 Mon Sep 17 00:00:00 2001 From: ryan Date: Mon, 8 Jun 2026 14:57:19 +0800 Subject: [PATCH] =?UTF-8?q?=E9=82=AE=E7=AE=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/docs.go | 203 +++++++++++++++++++ docs/swagger.json | 203 +++++++++++++++++++ docs/swagger.yaml | 133 ++++++++++++ frontend/components/common/admin/tasks.tsx | 142 +++++++++---- frontend/lib/services/admin/admin.service.ts | 8 + frontend/lib/services/admin/types.ts | 36 ++++ internal/apps/admin/task/routers.go | 42 +++- internal/apps/admin/task/routers_test.go | 53 +++++ internal/task/constants.go | 37 ++++ 9 files changed, 821 insertions(+), 36 deletions(-) diff --git a/docs/docs.go b/docs/docs.go index bd8ac336..84431935 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -640,6 +640,63 @@ const docTemplate = `{ } } }, + "/api/v1/admin/system-configs/smtp/test": { + "post": { + "security": [ + { + "SessionCookie": [] + } + ], + "description": "使用传入的配置进行 SMTP 邮件发送测试,支持使用 ****** 占位符使用保存的数据库密码", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "测试 SMTP 邮件发送", + "parameters": [ + { + "description": "测试请求参数", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system_config.TestSMTPRequest" + } + } + ], + "responses": { + "200": { + "description": "测试执行完毕", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseAny" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/system_config.TestSMTPResponse" + } + } + } + ] + } + }, + "400": { + "description": "参数错误", + "schema": { + "$ref": "#/definitions/util.ResponseAny" + } + } + } + } + }, "/api/v1/admin/system-configs/{key}": { "get": { "security": [ @@ -2647,6 +2704,46 @@ const docTemplate = `{ } } }, + "/api/v1/user/send-email-code": { + "post": { + "description": "向指定邮箱发送验证码(用于注册场景)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "发送邮箱验证码", + "parameters": [ + { + "description": "发送验证码请求参数", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/user.sendEmailCodeRequest" + } + } + ], + "responses": { + "200": { + "description": "发送成功", + "schema": { + "$ref": "#/definitions/util.ResponseAny" + } + }, + "400": { + "description": "参数错误", + "schema": { + "$ref": "#/definitions/util.ResponseAny" + } + } + } + } + }, "/f/{id}": { "get": { "description": "根据文件 ID 获取并提供已上传的临时或正式文件,若配置了缓存则优先走本地缓存,否则从 S3 等后端存储读取并流式返回", @@ -3326,6 +3423,50 @@ const docTemplate = `{ } } }, + "system_config.TestSMTPRequest": { + "type": "object", + "required": [ + "smtp_host", + "smtp_password", + "smtp_port", + "smtp_username", + "to" + ], + "properties": { + "smtp_host": { + "type": "string", + "maxLength": 255 + }, + "smtp_password": { + "type": "string", + "maxLength": 255 + }, + "smtp_port": { + "type": "integer" + }, + "smtp_username": { + "type": "string", + "maxLength": 255 + }, + "to": { + "type": "string" + } + } + }, + "system_config.TestSMTPResponse": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "log": { + "type": "string" + }, + "success": { + "type": "boolean" + } + } + }, "system_config.UpdateSystemConfigRequest": { "type": "object", "required": [ @@ -3351,6 +3492,9 @@ const docTemplate = `{ "end_time": { "type": "string" }, + "payload": { + "type": "string" + }, "start_time": { "type": "string" }, @@ -3377,6 +3521,12 @@ const docTemplate = `{ "name": { "type": "string" }, + "params": { + "type": "array", + "items": { + "$ref": "#/definitions/task.TaskParam" + } + }, "queue": { "type": "string" }, @@ -3392,6 +3542,35 @@ const docTemplate = `{ } } }, + "task.TaskParam": { + "type": "object", + "properties": { + "Description": { + "description": "描述", + "type": "string" + }, + "Label": { + "description": "显示名称", + "type": "string" + }, + "Name": { + "description": "参数键名", + "type": "string" + }, + "Placeholder": { + "description": "占位符", + "type": "string" + }, + "Required": { + "description": "是否必填", + "type": "boolean" + }, + "Type": { + "description": "类型:string, text, number", + "type": "string" + } + } + }, "upload.batchDownloadRequest": { "type": "object", "required": [ @@ -3492,6 +3671,9 @@ const docTemplate = `{ "user.loginRequest": { "type": "object", "properties": { + "code": { + "type": "string" + }, "password": { "type": "string" }, @@ -3503,9 +3685,15 @@ const docTemplate = `{ "user.registerRequest": { "type": "object", "properties": { + "code": { + "type": "string" + }, "display_name": { "type": "string" }, + "email": { + "type": "string" + }, "nickname": { "type": "string" }, @@ -3517,6 +3705,21 @@ const docTemplate = `{ } } }, + "user.sendEmailCodeRequest": { + "type": "object", + "required": [ + "email", + "scene" + ], + "properties": { + "email": { + "type": "string" + }, + "scene": { + "type": "string" + } + } + }, "user.tokenResponse": { "type": "object", "properties": { diff --git a/docs/swagger.json b/docs/swagger.json index a133d46b..9e0a4d38 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -633,6 +633,63 @@ } } }, + "/api/v1/admin/system-configs/smtp/test": { + "post": { + "security": [ + { + "SessionCookie": [] + } + ], + "description": "使用传入的配置进行 SMTP 邮件发送测试,支持使用 ****** 占位符使用保存的数据库密码", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "测试 SMTP 邮件发送", + "parameters": [ + { + "description": "测试请求参数", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system_config.TestSMTPRequest" + } + } + ], + "responses": { + "200": { + "description": "测试执行完毕", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseAny" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/system_config.TestSMTPResponse" + } + } + } + ] + } + }, + "400": { + "description": "参数错误", + "schema": { + "$ref": "#/definitions/util.ResponseAny" + } + } + } + } + }, "/api/v1/admin/system-configs/{key}": { "get": { "security": [ @@ -2640,6 +2697,46 @@ } } }, + "/api/v1/user/send-email-code": { + "post": { + "description": "向指定邮箱发送验证码(用于注册场景)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "发送邮箱验证码", + "parameters": [ + { + "description": "发送验证码请求参数", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/user.sendEmailCodeRequest" + } + } + ], + "responses": { + "200": { + "description": "发送成功", + "schema": { + "$ref": "#/definitions/util.ResponseAny" + } + }, + "400": { + "description": "参数错误", + "schema": { + "$ref": "#/definitions/util.ResponseAny" + } + } + } + } + }, "/f/{id}": { "get": { "description": "根据文件 ID 获取并提供已上传的临时或正式文件,若配置了缓存则优先走本地缓存,否则从 S3 等后端存储读取并流式返回", @@ -3319,6 +3416,50 @@ } } }, + "system_config.TestSMTPRequest": { + "type": "object", + "required": [ + "smtp_host", + "smtp_password", + "smtp_port", + "smtp_username", + "to" + ], + "properties": { + "smtp_host": { + "type": "string", + "maxLength": 255 + }, + "smtp_password": { + "type": "string", + "maxLength": 255 + }, + "smtp_port": { + "type": "integer" + }, + "smtp_username": { + "type": "string", + "maxLength": 255 + }, + "to": { + "type": "string" + } + } + }, + "system_config.TestSMTPResponse": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "log": { + "type": "string" + }, + "success": { + "type": "boolean" + } + } + }, "system_config.UpdateSystemConfigRequest": { "type": "object", "required": [ @@ -3344,6 +3485,9 @@ "end_time": { "type": "string" }, + "payload": { + "type": "string" + }, "start_time": { "type": "string" }, @@ -3370,6 +3514,12 @@ "name": { "type": "string" }, + "params": { + "type": "array", + "items": { + "$ref": "#/definitions/task.TaskParam" + } + }, "queue": { "type": "string" }, @@ -3385,6 +3535,35 @@ } } }, + "task.TaskParam": { + "type": "object", + "properties": { + "Description": { + "description": "描述", + "type": "string" + }, + "Label": { + "description": "显示名称", + "type": "string" + }, + "Name": { + "description": "参数键名", + "type": "string" + }, + "Placeholder": { + "description": "占位符", + "type": "string" + }, + "Required": { + "description": "是否必填", + "type": "boolean" + }, + "Type": { + "description": "类型:string, text, number", + "type": "string" + } + } + }, "upload.batchDownloadRequest": { "type": "object", "required": [ @@ -3485,6 +3664,9 @@ "user.loginRequest": { "type": "object", "properties": { + "code": { + "type": "string" + }, "password": { "type": "string" }, @@ -3496,9 +3678,15 @@ "user.registerRequest": { "type": "object", "properties": { + "code": { + "type": "string" + }, "display_name": { "type": "string" }, + "email": { + "type": "string" + }, "nickname": { "type": "string" }, @@ -3510,6 +3698,21 @@ } } }, + "user.sendEmailCodeRequest": { + "type": "object", + "required": [ + "email", + "scene" + ], + "properties": { + "email": { + "type": "string" + }, + "scene": { + "type": "string" + } + } + }, "user.tokenResponse": { "type": "object", "properties": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index a4fa55c0..134f6459 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -424,6 +424,37 @@ definitions: - type - value type: object + system_config.TestSMTPRequest: + properties: + smtp_host: + maxLength: 255 + type: string + smtp_password: + maxLength: 255 + type: string + smtp_port: + type: integer + smtp_username: + maxLength: 255 + type: string + to: + type: string + required: + - smtp_host + - smtp_password + - smtp_port + - smtp_username + - to + type: object + system_config.TestSMTPResponse: + properties: + error: + type: string + log: + type: string + success: + type: boolean + type: object system_config.UpdateSystemConfigRequest: properties: description: @@ -439,6 +470,8 @@ definitions: properties: end_time: type: string + payload: + type: string start_time: type: string task_type: @@ -458,6 +491,10 @@ definitions: type: integer name: type: string + params: + items: + $ref: '#/definitions/task.TaskParam' + type: array queue: type: string retryable: @@ -468,6 +505,27 @@ definitions: type: type: string type: object + task.TaskParam: + properties: + Description: + description: 描述 + type: string + Label: + description: 显示名称 + type: string + Name: + description: 参数键名 + type: string + Placeholder: + description: 占位符 + type: string + Required: + description: 是否必填 + type: boolean + Type: + description: 类型:string, text, number + type: string + type: object upload.batchDownloadRequest: properties: ids: @@ -535,6 +593,8 @@ definitions: type: object user.loginRequest: properties: + code: + type: string password: type: string username: @@ -542,8 +602,12 @@ definitions: type: object user.registerRequest: properties: + code: + type: string display_name: type: string + email: + type: string nickname: type: string password: @@ -551,6 +615,16 @@ definitions: username: type: string type: object + user.sendEmailCodeRequest: + properties: + email: + type: string + scene: + type: string + required: + - email + - scene + type: object user.tokenResponse: properties: record: @@ -1111,6 +1185,39 @@ paths: summary: 更新系统配置 tags: - admin + /api/v1/admin/system-configs/smtp/test: + post: + consumes: + - application/json + description: 使用传入的配置进行 SMTP 邮件发送测试,支持使用 ****** 占位符使用保存的数据库密码 + parameters: + - description: 测试请求参数 + in: body + name: request + required: true + schema: + $ref: '#/definitions/system_config.TestSMTPRequest' + produces: + - application/json + responses: + "200": + description: 测试执行完毕 + schema: + allOf: + - $ref: '#/definitions/util.ResponseAny' + - properties: + data: + $ref: '#/definitions/system_config.TestSMTPResponse' + type: object + "400": + description: 参数错误 + schema: + $ref: '#/definitions/util.ResponseAny' + security: + - SessionCookie: [] + summary: 测试 SMTP 邮件发送 + tags: + - admin /api/v1/admin/tasks/dispatch: post: consumes: @@ -2176,6 +2283,32 @@ paths: summary: 用户注册 tags: - user + /api/v1/user/send-email-code: + post: + consumes: + - application/json + description: 向指定邮箱发送验证码(用于注册场景) + parameters: + - description: 发送验证码请求参数 + in: body + name: request + required: true + schema: + $ref: '#/definitions/user.sendEmailCodeRequest' + produces: + - application/json + responses: + "200": + description: 发送成功 + schema: + $ref: '#/definitions/util.ResponseAny' + "400": + description: 参数错误 + schema: + $ref: '#/definitions/util.ResponseAny' + summary: 发送邮箱验证码 + tags: + - user /f/{id}: get: description: 根据文件 ID 获取并提供已上传的临时或正式文件,若配置了缓存则优先走本地缓存,否则从 S3 等后端存储读取并流式返回 diff --git a/frontend/components/common/admin/tasks.tsx b/frontend/components/common/admin/tasks.tsx index d5aefb51..641b17cc 100644 --- a/frontend/components/common/admin/tasks.tsx +++ b/frontend/components/common/admin/tasks.tsx @@ -6,6 +6,7 @@ import {toast} from "sonner" import {Button} from "@/components/ui/button" import {Input} from "@/components/ui/input" import {Label} from "@/components/ui/label" +import {Textarea} from "@/components/ui/textarea" import {Spinner} from "@/components/ui/spinner" import {Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle} from "@/components/ui/dialog" import {Calendar as CalendarIcon, Clock, FileText, Info, Layers, Play} from "lucide-react" @@ -140,6 +141,7 @@ export function TaskManager() { const [startTime, setStartTime] = useState(undefined) const [endTime, setEndTime] = useState(undefined) const [userId, setUserId] = useState("") + const [paramValues, setParamValues] = useState>({}) const fetchTaskTypes = useCallback(async () => { try { @@ -158,6 +160,21 @@ export function TaskManager() { fetchTaskTypes() }, [fetchTaskTypes]) + useEffect(() => { + if (selectedTaskType) { + const targetTask = taskTypes.find(t => t.type === selectedTaskType) + if (targetTask?.params) { + const initialValues: Record = {} + targetTask.params.forEach(p => { + initialValues[p.name] = "" + }) + setParamValues(initialValues) + } else { + setParamValues({}) + } + } + }, [selectedTaskType, taskTypes]) + const handleDispatch = async () => { if (!selectedTaskType) return @@ -170,7 +187,7 @@ export function TaskManager() { task_type: selectedTaskType } - if (targetTask?.type === 'order_sync') { + if (targetTask?.supports_time) { if (startTime) params.start_time = startTime.toISOString() if (endTime) params.end_time = endTime.toISOString() } @@ -179,6 +196,21 @@ export function TaskManager() { if (userId) params.user_id = userId } + // Handle dynamic parameters + if (targetTask?.params && targetTask.params.length > 0) { + const payloadData: Record = {} + for (const param of targetTask.params) { + const val = (paramValues[param.name] || "").trim() + if (param.required && !val) { + toast.error(`${param.label}不能为空`) + setDispatching(false) + return + } + payloadData[param.name] = val + } + params.payload = JSON.stringify(payloadData) + } + const taskID = await AdminService.dispatchTask(params) toast.success('任务下发成功', { @@ -189,6 +221,7 @@ export function TaskManager() { setStartTime(undefined) setEndTime(undefined) setUserId("") + setParamValues({}) } catch (err) { toast.error('任务下发失败', { description: err instanceof Error ? err.message : '未知错误' @@ -318,42 +351,81 @@ export function TaskManager() { - {selectedTaskType === 'order_sync' && ( - <> -
- - -

选择同步的起始时间点

-
-
- - -

选择同步的结束时间点

-
- - )} + {(() => { + const targetTask = getSelectedTaskMeta() + if (targetTask?.supports_time) { + return ( + <> +
+ + +

选择同步的起始时间点

+
+
+ + +

选择同步的结束时间点

+
+ + ) + } + return null + })()} - {selectedTaskType === 'user_gamification' && ( -
- - setUserId(e.target.value)} - /> -

指定需要更新积分数据的用户 ID

-
- )} + {(() => { + const targetTask = getSelectedTaskMeta() + if (!targetTask || !targetTask.params || targetTask.params.length === 0) { + return null + } + return ( +
+ {targetTask.params.map((param) => ( +
+ + {param.type === 'text' ? ( +