mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-04 15:06:37 +08:00
邮箱
This commit is contained in:
+203
@@ -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}": {
|
"/api/v1/admin/system-configs/{key}": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"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}": {
|
"/f/{id}": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "根据文件 ID 获取并提供已上传的临时或正式文件,若配置了缓存则优先走本地缓存,否则从 S3 等后端存储读取并流式返回",
|
"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": {
|
"system_config.UpdateSystemConfigRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
@@ -3351,6 +3492,9 @@ const docTemplate = `{
|
|||||||
"end_time": {
|
"end_time": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"payload": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"start_time": {
|
"start_time": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@@ -3377,6 +3521,12 @@ const docTemplate = `{
|
|||||||
"name": {
|
"name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"params": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/task.TaskParam"
|
||||||
|
}
|
||||||
|
},
|
||||||
"queue": {
|
"queue": {
|
||||||
"type": "string"
|
"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": {
|
"upload.batchDownloadRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
@@ -3492,6 +3671,9 @@ const docTemplate = `{
|
|||||||
"user.loginRequest": {
|
"user.loginRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"code": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@@ -3503,9 +3685,15 @@ const docTemplate = `{
|
|||||||
"user.registerRequest": {
|
"user.registerRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"code": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"display_name": {
|
"display_name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"email": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"nickname": {
|
"nickname": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@@ -3517,6 +3705,21 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"user.sendEmailCodeRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"email",
|
||||||
|
"scene"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"email": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"scene": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"user.tokenResponse": {
|
"user.tokenResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -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}": {
|
"/api/v1/admin/system-configs/{key}": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"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}": {
|
"/f/{id}": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "根据文件 ID 获取并提供已上传的临时或正式文件,若配置了缓存则优先走本地缓存,否则从 S3 等后端存储读取并流式返回",
|
"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": {
|
"system_config.UpdateSystemConfigRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
@@ -3344,6 +3485,9 @@
|
|||||||
"end_time": {
|
"end_time": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"payload": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"start_time": {
|
"start_time": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@@ -3370,6 +3514,12 @@
|
|||||||
"name": {
|
"name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"params": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/task.TaskParam"
|
||||||
|
}
|
||||||
|
},
|
||||||
"queue": {
|
"queue": {
|
||||||
"type": "string"
|
"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": {
|
"upload.batchDownloadRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
@@ -3485,6 +3664,9 @@
|
|||||||
"user.loginRequest": {
|
"user.loginRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"code": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@@ -3496,9 +3678,15 @@
|
|||||||
"user.registerRequest": {
|
"user.registerRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"code": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"display_name": {
|
"display_name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"email": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"nickname": {
|
"nickname": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@@ -3510,6 +3698,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"user.sendEmailCodeRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"email",
|
||||||
|
"scene"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"email": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"scene": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"user.tokenResponse": {
|
"user.tokenResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -424,6 +424,37 @@ definitions:
|
|||||||
- type
|
- type
|
||||||
- value
|
- value
|
||||||
type: object
|
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:
|
system_config.UpdateSystemConfigRequest:
|
||||||
properties:
|
properties:
|
||||||
description:
|
description:
|
||||||
@@ -439,6 +470,8 @@ definitions:
|
|||||||
properties:
|
properties:
|
||||||
end_time:
|
end_time:
|
||||||
type: string
|
type: string
|
||||||
|
payload:
|
||||||
|
type: string
|
||||||
start_time:
|
start_time:
|
||||||
type: string
|
type: string
|
||||||
task_type:
|
task_type:
|
||||||
@@ -458,6 +491,10 @@ definitions:
|
|||||||
type: integer
|
type: integer
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
|
params:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/task.TaskParam'
|
||||||
|
type: array
|
||||||
queue:
|
queue:
|
||||||
type: string
|
type: string
|
||||||
retryable:
|
retryable:
|
||||||
@@ -468,6 +505,27 @@ definitions:
|
|||||||
type:
|
type:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
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:
|
upload.batchDownloadRequest:
|
||||||
properties:
|
properties:
|
||||||
ids:
|
ids:
|
||||||
@@ -535,6 +593,8 @@ definitions:
|
|||||||
type: object
|
type: object
|
||||||
user.loginRequest:
|
user.loginRequest:
|
||||||
properties:
|
properties:
|
||||||
|
code:
|
||||||
|
type: string
|
||||||
password:
|
password:
|
||||||
type: string
|
type: string
|
||||||
username:
|
username:
|
||||||
@@ -542,8 +602,12 @@ definitions:
|
|||||||
type: object
|
type: object
|
||||||
user.registerRequest:
|
user.registerRequest:
|
||||||
properties:
|
properties:
|
||||||
|
code:
|
||||||
|
type: string
|
||||||
display_name:
|
display_name:
|
||||||
type: string
|
type: string
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
nickname:
|
nickname:
|
||||||
type: string
|
type: string
|
||||||
password:
|
password:
|
||||||
@@ -551,6 +615,16 @@ definitions:
|
|||||||
username:
|
username:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
user.sendEmailCodeRequest:
|
||||||
|
properties:
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
scene:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- email
|
||||||
|
- scene
|
||||||
|
type: object
|
||||||
user.tokenResponse:
|
user.tokenResponse:
|
||||||
properties:
|
properties:
|
||||||
record:
|
record:
|
||||||
@@ -1111,6 +1185,39 @@ paths:
|
|||||||
summary: 更新系统配置
|
summary: 更新系统配置
|
||||||
tags:
|
tags:
|
||||||
- admin
|
- 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:
|
/api/v1/admin/tasks/dispatch:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
@@ -2176,6 +2283,32 @@ paths:
|
|||||||
summary: 用户注册
|
summary: 用户注册
|
||||||
tags:
|
tags:
|
||||||
- user
|
- 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}:
|
/f/{id}:
|
||||||
get:
|
get:
|
||||||
description: 根据文件 ID 获取并提供已上传的临时或正式文件,若配置了缓存则优先走本地缓存,否则从 S3 等后端存储读取并流式返回
|
description: 根据文件 ID 获取并提供已上传的临时或正式文件,若配置了缓存则优先走本地缓存,否则从 S3 等后端存储读取并流式返回
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {toast} from "sonner"
|
|||||||
import {Button} from "@/components/ui/button"
|
import {Button} from "@/components/ui/button"
|
||||||
import {Input} from "@/components/ui/input"
|
import {Input} from "@/components/ui/input"
|
||||||
import {Label} from "@/components/ui/label"
|
import {Label} from "@/components/ui/label"
|
||||||
|
import {Textarea} from "@/components/ui/textarea"
|
||||||
import {Spinner} from "@/components/ui/spinner"
|
import {Spinner} from "@/components/ui/spinner"
|
||||||
import {Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle} from "@/components/ui/dialog"
|
import {Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle} from "@/components/ui/dialog"
|
||||||
import {Calendar as CalendarIcon, Clock, FileText, Info, Layers, Play} from "lucide-react"
|
import {Calendar as CalendarIcon, Clock, FileText, Info, Layers, Play} from "lucide-react"
|
||||||
@@ -140,6 +141,7 @@ export function TaskManager() {
|
|||||||
const [startTime, setStartTime] = useState<Date | undefined>(undefined)
|
const [startTime, setStartTime] = useState<Date | undefined>(undefined)
|
||||||
const [endTime, setEndTime] = useState<Date | undefined>(undefined)
|
const [endTime, setEndTime] = useState<Date | undefined>(undefined)
|
||||||
const [userId, setUserId] = useState("")
|
const [userId, setUserId] = useState("")
|
||||||
|
const [paramValues, setParamValues] = useState<Record<string, string>>({})
|
||||||
|
|
||||||
const fetchTaskTypes = useCallback(async () => {
|
const fetchTaskTypes = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
@@ -158,6 +160,21 @@ export function TaskManager() {
|
|||||||
fetchTaskTypes()
|
fetchTaskTypes()
|
||||||
}, [fetchTaskTypes])
|
}, [fetchTaskTypes])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedTaskType) {
|
||||||
|
const targetTask = taskTypes.find(t => t.type === selectedTaskType)
|
||||||
|
if (targetTask?.params) {
|
||||||
|
const initialValues: Record<string, string> = {}
|
||||||
|
targetTask.params.forEach(p => {
|
||||||
|
initialValues[p.name] = ""
|
||||||
|
})
|
||||||
|
setParamValues(initialValues)
|
||||||
|
} else {
|
||||||
|
setParamValues({})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [selectedTaskType, taskTypes])
|
||||||
|
|
||||||
const handleDispatch = async () => {
|
const handleDispatch = async () => {
|
||||||
if (!selectedTaskType) return
|
if (!selectedTaskType) return
|
||||||
|
|
||||||
@@ -170,7 +187,7 @@ export function TaskManager() {
|
|||||||
task_type: selectedTaskType
|
task_type: selectedTaskType
|
||||||
}
|
}
|
||||||
|
|
||||||
if (targetTask?.type === 'order_sync') {
|
if (targetTask?.supports_time) {
|
||||||
if (startTime) params.start_time = startTime.toISOString()
|
if (startTime) params.start_time = startTime.toISOString()
|
||||||
if (endTime) params.end_time = endTime.toISOString()
|
if (endTime) params.end_time = endTime.toISOString()
|
||||||
}
|
}
|
||||||
@@ -179,6 +196,21 @@ export function TaskManager() {
|
|||||||
if (userId) params.user_id = userId
|
if (userId) params.user_id = userId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle dynamic parameters
|
||||||
|
if (targetTask?.params && targetTask.params.length > 0) {
|
||||||
|
const payloadData: Record<string, string> = {}
|
||||||
|
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)
|
const taskID = await AdminService.dispatchTask(params)
|
||||||
|
|
||||||
toast.success('任务下发成功', {
|
toast.success('任务下发成功', {
|
||||||
@@ -189,6 +221,7 @@ export function TaskManager() {
|
|||||||
setStartTime(undefined)
|
setStartTime(undefined)
|
||||||
setEndTime(undefined)
|
setEndTime(undefined)
|
||||||
setUserId("")
|
setUserId("")
|
||||||
|
setParamValues({})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error('任务下发失败', {
|
toast.error('任务下发失败', {
|
||||||
description: err instanceof Error ? err.message : '未知错误'
|
description: err instanceof Error ? err.message : '未知错误'
|
||||||
@@ -318,42 +351,81 @@ export function TaskManager() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{selectedTaskType === 'order_sync' && (
|
{(() => {
|
||||||
<>
|
const targetTask = getSelectedTaskMeta()
|
||||||
<div className="grid gap-2">
|
if (targetTask?.supports_time) {
|
||||||
<Label>开始时间</Label>
|
return (
|
||||||
<DatePickerWithTime date={startTime} setDate={setStartTime} />
|
<>
|
||||||
<p className="text-xs text-muted-foreground">选择同步的起始时间点</p>
|
<div className="grid gap-2">
|
||||||
</div>
|
<Label>开始时间</Label>
|
||||||
<div className="grid gap-2">
|
<DatePickerWithTime date={startTime} setDate={setStartTime} />
|
||||||
<Label>结束时间</Label>
|
<p className="text-xs text-muted-foreground">选择同步的起始时间点</p>
|
||||||
<DatePickerWithTime date={endTime} setDate={setEndTime} />
|
</div>
|
||||||
<p className="text-xs text-muted-foreground">选择同步的结束时间点</p>
|
<div className="grid gap-2">
|
||||||
</div>
|
<Label>结束时间</Label>
|
||||||
</>
|
<DatePickerWithTime date={endTime} setDate={setEndTime} />
|
||||||
)}
|
<p className="text-xs text-muted-foreground">选择同步的结束时间点</p>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
})()}
|
||||||
|
|
||||||
{selectedTaskType === 'user_gamification' && (
|
{(() => {
|
||||||
<div className="grid gap-2">
|
const targetTask = getSelectedTaskMeta()
|
||||||
<Label htmlFor="user-id">用户 ID</Label>
|
if (!targetTask || !targetTask.params || targetTask.params.length === 0) {
|
||||||
<Input
|
return null
|
||||||
id="user-id"
|
}
|
||||||
type="number"
|
return (
|
||||||
placeholder="e.g. 10086"
|
<div className="space-y-4">
|
||||||
className="font-mono text-xs"
|
{targetTask.params.map((param) => (
|
||||||
value={userId}
|
<div key={param.name} className="grid gap-2">
|
||||||
onChange={(e) => setUserId(e.target.value)}
|
<Label htmlFor={`param-${param.name}`} className="flex items-center gap-1">
|
||||||
/>
|
{param.label}
|
||||||
<p className="text-xs text-muted-foreground">指定需要更新积分数据的用户 ID</p>
|
{param.required && <span className="text-destructive font-bold">*</span>}
|
||||||
</div>
|
</Label>
|
||||||
)}
|
{param.type === 'text' ? (
|
||||||
|
<Textarea
|
||||||
|
id={`param-${param.name}`}
|
||||||
|
placeholder={param.placeholder}
|
||||||
|
className="text-xs min-h-[80px]"
|
||||||
|
value={paramValues[param.name] || ""}
|
||||||
|
onChange={(e) => setParamValues(prev => ({ ...prev, [param.name]: e.target.value }))}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Input
|
||||||
|
id={`param-${param.name}`}
|
||||||
|
type={param.type === 'number' ? 'number' : 'text'}
|
||||||
|
placeholder={param.placeholder}
|
||||||
|
className="text-xs"
|
||||||
|
value={paramValues[param.name] || ""}
|
||||||
|
onChange={(e) => setParamValues(prev => ({ ...prev, [param.name]: e.target.value }))}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{param.description && (
|
||||||
|
<p className="text-[10px] text-muted-foreground">{param.description}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})()}
|
||||||
|
|
||||||
{!['order_sync', 'user_gamification'].includes(selectedTaskType || '') && (
|
{(() => {
|
||||||
<div className="flex items-center gap-2 text-sm text-muted-foreground bg-muted/50 p-3 rounded-md border border-dashed">
|
const targetTask = getSelectedTaskMeta()
|
||||||
<Info className="h-4 w-4" />
|
const hasTime = targetTask?.supports_time
|
||||||
<span>此任务无需额外配置参数,确认后将直接下发。</span>
|
const hasParams = targetTask?.params && targetTask.params.length > 0
|
||||||
</div>
|
if (!hasTime && !hasParams) {
|
||||||
)}
|
return (
|
||||||
|
<div className="flex items-center gap-2 text-sm text-muted-foreground bg-muted/50 p-3 rounded-md border border-dashed">
|
||||||
|
<Info className="h-4 w-4" />
|
||||||
|
<span>此任务无需额外配置参数,确认后将直接下发。</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
})()}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
|
|||||||
@@ -193,6 +193,14 @@ export class AdminService extends BaseService {
|
|||||||
supports_time: item.SupportsTime ?? item.supports_time ?? false,
|
supports_time: item.SupportsTime ?? item.supports_time ?? false,
|
||||||
max_retry: item.MaxRetry ?? item.max_retry ?? 0,
|
max_retry: item.MaxRetry ?? item.max_retry ?? 0,
|
||||||
queue: item.Queue || item.queue || '',
|
queue: item.Queue || item.queue || '',
|
||||||
|
params: (item.Params || item.params || []).map(p => ({
|
||||||
|
name: p.Name || p.name || '',
|
||||||
|
label: p.Label || p.label || '',
|
||||||
|
type: p.Type || p.type || '',
|
||||||
|
required: p.Required ?? p.required ?? false,
|
||||||
|
placeholder: p.Placeholder || p.placeholder || '',
|
||||||
|
description: p.Description || p.description || '',
|
||||||
|
})),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,24 @@ export interface UpdateSystemConfigRequest {
|
|||||||
|
|
||||||
// ==================== 任务管理 ====================
|
// ==================== 任务管理 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任务参数响应
|
||||||
|
*/
|
||||||
|
export interface TaskParamResponse {
|
||||||
|
Name?: string;
|
||||||
|
name?: string;
|
||||||
|
Label?: string;
|
||||||
|
label?: string;
|
||||||
|
Type?: string;
|
||||||
|
type?: string;
|
||||||
|
Required?: boolean;
|
||||||
|
required?: boolean;
|
||||||
|
Placeholder?: string;
|
||||||
|
placeholder?: string;
|
||||||
|
Description?: string;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 任务类型响应
|
* 任务类型响应
|
||||||
*/
|
*/
|
||||||
@@ -64,6 +82,20 @@ export interface TaskTypeResponse {
|
|||||||
max_retry?: number;
|
max_retry?: number;
|
||||||
Queue?: string;
|
Queue?: string;
|
||||||
queue?: string;
|
queue?: string;
|
||||||
|
Params?: TaskParamResponse[];
|
||||||
|
params?: TaskParamResponse[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任务参数定义
|
||||||
|
*/
|
||||||
|
export interface TaskParam {
|
||||||
|
name: string;
|
||||||
|
label: string;
|
||||||
|
type: string;
|
||||||
|
required: boolean;
|
||||||
|
placeholder: string;
|
||||||
|
description: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -84,6 +116,8 @@ export interface TaskMeta {
|
|||||||
max_retry: number;
|
max_retry: number;
|
||||||
/** 队列名称 */
|
/** 队列名称 */
|
||||||
queue: string;
|
queue: string;
|
||||||
|
/** 任务所需的自定义参数定义 */
|
||||||
|
params?: TaskParam[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -98,6 +132,8 @@ export interface DispatchTaskRequest {
|
|||||||
end_time?: string;
|
end_time?: string;
|
||||||
/** 用户 ID(可选,仅部分任务需要) */
|
/** 用户 ID(可选,仅部分任务需要) */
|
||||||
user_id?: string;
|
user_id?: string;
|
||||||
|
/** 任务自定义参数 JSON 字符串 */
|
||||||
|
payload?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TaskExecutionStatus = 'pending' | 'running' | 'succeeded' | 'failed';
|
export type TaskExecutionStatus = 'pending' | 'running' | 'succeeded' | 'failed';
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ limitations under the License.
|
|||||||
package task
|
package task
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -49,6 +50,7 @@ type DispatchTaskRequest struct {
|
|||||||
StartTime *time.Time `json:"start_time"`
|
StartTime *time.Time `json:"start_time"`
|
||||||
EndTime *time.Time `json:"end_time"`
|
EndTime *time.Time `json:"end_time"`
|
||||||
UserID *uint64 `json:"user_id"`
|
UserID *uint64 `json:"user_id"`
|
||||||
|
Payload string `json:"payload"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DispatchTask 下发任务
|
// DispatchTask 下发任务
|
||||||
@@ -78,7 +80,45 @@ func DispatchTask(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
taskID, err := task.DispatchTask(c.Request.Context(), req.TaskType, nil, "manual")
|
var payloadBytes []byte
|
||||||
|
if req.TaskType == task.TaskTypeSendEmail {
|
||||||
|
if strings.TrimSpace(req.Payload) == "" {
|
||||||
|
c.JSON(http.StatusBadRequest, util.Err("任务参数 Payload 不能为空"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var mailPayload struct {
|
||||||
|
To string `json:"to"`
|
||||||
|
Subject string `json:"subject"`
|
||||||
|
Body string `json:"body"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal([]byte(req.Payload), &mailPayload); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, util.Err("无效的 JSON 格式: "+err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mailPayload.To = strings.TrimSpace(mailPayload.To)
|
||||||
|
mailPayload.Subject = strings.TrimSpace(mailPayload.Subject)
|
||||||
|
mailPayload.Body = strings.TrimSpace(mailPayload.Body)
|
||||||
|
|
||||||
|
if mailPayload.To == "" || mailPayload.Subject == "" || mailPayload.Body == "" {
|
||||||
|
c.JSON(http.StatusBadRequest, util.Err("收件人地址 (to)、邮件主题 (subject) 和邮件内容 (body) 不能为空"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
payloadBytes, err = json.Marshal(mailPayload)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, util.Err("构造邮件任务载荷失败"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if req.Payload != "" {
|
||||||
|
payloadBytes = []byte(req.Payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
taskID, err := task.DispatchTask(c.Request.Context(), req.TaskType, payloadBytes, "manual")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, util.Err(fmt.Sprintf("%s: %v", TaskDispatchFailed, err)))
|
c.JSON(http.StatusInternalServerError, util.Err(fmt.Sprintf("%s: %v", TaskDispatchFailed, err)))
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -125,6 +125,59 @@ func TestDispatchTask(t *testing.T) {
|
|||||||
assert.NotEmpty(t, taskID)
|
assert.NotEmpty(t, taskID)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("dispatch send_email task successfully with valid payload", func(t *testing.T) {
|
||||||
|
payload := DispatchTaskRequest{
|
||||||
|
TaskType: task.TaskTypeSendEmail,
|
||||||
|
Payload: `{"to":"receiver@example.com","subject":"Test Subject","body":"Test Body"}`,
|
||||||
|
}
|
||||||
|
body, _ := json.Marshal(payload)
|
||||||
|
req, _ := http.NewRequest("POST", "/api/v1/admin/tasks/dispatch", bytes.NewBuffer(body))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusOK, w.Code, "Body: %s", w.Body.String())
|
||||||
|
|
||||||
|
var resp util.ResponseAny
|
||||||
|
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||||
|
assert.Empty(t, resp.ErrorMsg)
|
||||||
|
assert.NotNil(t, resp.Data)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("dispatch send_email task failure with invalid payload json", func(t *testing.T) {
|
||||||
|
payload := DispatchTaskRequest{
|
||||||
|
TaskType: task.TaskTypeSendEmail,
|
||||||
|
Payload: `{"to":`,
|
||||||
|
}
|
||||||
|
body, _ := json.Marshal(payload)
|
||||||
|
req, _ := http.NewRequest("POST", "/api/v1/admin/tasks/dispatch", bytes.NewBuffer(body))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
|
var resp util.ResponseAny
|
||||||
|
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||||
|
assert.Contains(t, resp.ErrorMsg, "无效的 JSON 格式")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("dispatch send_email task failure with missing fields", func(t *testing.T) {
|
||||||
|
payload := DispatchTaskRequest{
|
||||||
|
TaskType: task.TaskTypeSendEmail,
|
||||||
|
Payload: `{"to":"","subject":"Test","body":"Test"}`,
|
||||||
|
}
|
||||||
|
body, _ := json.Marshal(payload)
|
||||||
|
req, _ := http.NewRequest("POST", "/api/v1/admin/tasks/dispatch", bytes.NewBuffer(body))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
|
var resp util.ResponseAny
|
||||||
|
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||||
|
assert.Contains(t, resp.ErrorMsg, "不能为空")
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("dispatch invalid task type failure", func(t *testing.T) {
|
t.Run("dispatch invalid task type failure", func(t *testing.T) {
|
||||||
payload := DispatchTaskRequest{
|
payload := DispatchTaskRequest{
|
||||||
TaskType: "invalid_task_type",
|
TaskType: "invalid_task_type",
|
||||||
|
|||||||
@@ -31,6 +31,16 @@ const (
|
|||||||
TaskTypeSendEmail = "send_email"
|
TaskTypeSendEmail = "send_email"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TaskParam 任务参数定义
|
||||||
|
type TaskParam struct {
|
||||||
|
Name string `json:"Name"` // 参数键名
|
||||||
|
Label string `json:"Label"` // 显示名称
|
||||||
|
Type string `json:"Type"` // 类型:string, text, number
|
||||||
|
Required bool `json:"Required"` // 是否必填
|
||||||
|
Placeholder string `json:"Placeholder"` // 占位符
|
||||||
|
Description string `json:"Description"` // 描述
|
||||||
|
}
|
||||||
|
|
||||||
// TaskMeta 任务元数据
|
// TaskMeta 任务元数据
|
||||||
type TaskMeta struct {
|
type TaskMeta struct {
|
||||||
Type string
|
Type string
|
||||||
@@ -41,6 +51,7 @@ type TaskMeta struct {
|
|||||||
MaxRetry int
|
MaxRetry int
|
||||||
Queue string
|
Queue string
|
||||||
Retryable bool // 是否支持手动重试
|
Retryable bool // 是否支持手动重试
|
||||||
|
Params []TaskParam
|
||||||
}
|
}
|
||||||
|
|
||||||
// DispatchableTasks 可下发的任务列表
|
// DispatchableTasks 可下发的任务列表
|
||||||
@@ -64,6 +75,32 @@ var DispatchableTasks = []TaskMeta{
|
|||||||
MaxRetry: 3,
|
MaxRetry: 3,
|
||||||
Queue: QueueDefault,
|
Queue: QueueDefault,
|
||||||
Retryable: true,
|
Retryable: true,
|
||||||
|
Params: []TaskParam{
|
||||||
|
{
|
||||||
|
Name: "to",
|
||||||
|
Label: "接收邮箱 (To)",
|
||||||
|
Type: "string",
|
||||||
|
Required: true,
|
||||||
|
Placeholder: "receiver@example.com",
|
||||||
|
Description: "接收邮件的目标邮箱地址",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "subject",
|
||||||
|
Label: "邮件主题 (Subject)",
|
||||||
|
Type: "string",
|
||||||
|
Required: true,
|
||||||
|
Placeholder: "请输入邮件主题",
|
||||||
|
Description: "发送邮件的主题标题",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "body",
|
||||||
|
Label: "邮件内容 (Body)",
|
||||||
|
Type: "text",
|
||||||
|
Required: true,
|
||||||
|
Placeholder: "请输入邮件内容(支持 HTML 格式)",
|
||||||
|
Description: "发送邮件的内容主体",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user