feat: add send many account mode
This commit is contained in:
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"kiroAgent.configureMCP": "Disabled"
|
||||||
|
}
|
||||||
@@ -6,8 +6,6 @@ Message Nest 是一个灵活而强大的消息推送整合平台,旨在简化
|
|||||||
|
|
||||||
如果你有很多消息推送方式,每次都需要调用各种接口去发送消息到各个渠道,或者不同的项目你都需要复制同样的发消息代码,这个项目可以帮你管理各种消息方式,并提供统一的发送api接入。你可以自由组合各种消息渠道,一个api推送到各种渠道,帮你省去接入的繁琐步骤。
|
如果你有很多消息推送方式,每次都需要调用各种接口去发送消息到各个渠道,或者不同的项目你都需要复制同样的发消息代码,这个项目可以帮你管理各种消息方式,并提供统一的发送api接入。你可以自由组合各种消息渠道,一个api推送到各种渠道,帮你省去接入的繁琐步骤。
|
||||||
|
|
||||||
这个项目适合推送固定接受人(或者固定多个接收渠道)的场景,不适合批量群发场景。虽然也能支持群发,但是群发本身也有一定风险,不是维护的主要场景,群发的反馈不会处理。
|
|
||||||
|
|
||||||
演示站点(演示站点的服务器比较烂,见谅) [demo](https://message-nest-demo-site.qwapi.eu.org/)
|
演示站点(演示站点的服务器比较烂,见谅) [demo](https://message-nest-demo-site.qwapi.eu.org/)
|
||||||
|
|
||||||
[](https://hits.sh/github.com/engigu/Message-Push-Nest/)
|
[](https://hits.sh/github.com/engigu/Message-Push-Nest/)
|
||||||
@@ -17,6 +15,7 @@ Message Nest 是一个灵活而强大的消息推送整合平台,旨在简化
|
|||||||
- 🔄 **整合性:** 提供了多种消息推送方式,包括邮件、钉钉、企业微信等,方便你集中管理和定制通知。
|
- 🔄 **整合性:** 提供了多种消息推送方式,包括邮件、钉钉、企业微信等,方便你集中管理和定制通知。
|
||||||
- 🎨 **自定义性:** 可以根据需求定制消息推送策略,满足不同场景的个性化需求。
|
- 🎨 **自定义性:** 可以根据需求定制消息推送策略,满足不同场景的个性化需求。
|
||||||
- 📝 **模板化(⭐推荐):** 支持消息模板功能,通过占位符实现动态内容替换,一次定义多处复用,大幅提高开发效率和维护便利性。
|
- 📝 **模板化(⭐推荐):** 支持消息模板功能,通过占位符实现动态内容替换,一次定义多处复用,大幅提高开发效率和维护便利性。
|
||||||
|
- 📧 **群发模式(🆕):** 支持动态接收者功能,可在 API 调用时指定多个接收账号,实现邮
|
||||||
- 🛠 **开放性:** 易于扩展和集成新的消息通知服务,以适应未来的变化。
|
- 🛠 **开放性:** 易于扩展和集成新的消息通知服务,以适应未来的变化。
|
||||||
|
|
||||||
## 进度 🔨
|
## 进度 🔨
|
||||||
|
|||||||
+51
-8
@@ -51,6 +51,33 @@ POST /api/v1/message/send
|
|||||||
@提醒功能仅在支持的渠道(钉钉、企业微信)中生效。
|
@提醒功能仅在支持的渠道(钉钉、企业微信)中生效。
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
### 动态接收者参数(可选)🆕
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| recipients | array | 否 | 动态接收者列表,如 `["user1@example.com", "user2@example.com"]` |
|
||||||
|
|
||||||
|
::: tip 群发模式
|
||||||
|
动态接收者功能允许在 API 调用时指定接收者列表,实现群发功能。
|
||||||
|
|
||||||
|
**支持的渠道:**
|
||||||
|
- ✅ **邮件** - 支持多个收件人,实现邮件群发
|
||||||
|
- ✅ **微信公众号** - 支持多个 OpenID,实现公众号群发
|
||||||
|
|
||||||
|
**使用条件:**
|
||||||
|
1. 任务实例必须配置为"动态接收者模式"
|
||||||
|
2. 一个任务只能配置一个动态接收实例
|
||||||
|
3. 动态接收实例不能与固定接收实例混合使用
|
||||||
|
|
||||||
|
**配置方式:**
|
||||||
|
在任务编辑页面,添加实例时勾选"动态接收者模式",此时无需配置固定接收者。
|
||||||
|
|
||||||
|
**注意事项:**
|
||||||
|
- 如果任务配置了动态接收实例,`recipients` 参数为必填
|
||||||
|
- 不支持动态接收的渠道会忽略此参数
|
||||||
|
- 建议控制接收者数量,避免触发渠道限流
|
||||||
|
:::
|
||||||
|
|
||||||
## 请求示例
|
## 请求示例
|
||||||
|
|
||||||
### 基本示例(纯文本)
|
### 基本示例(纯文本)
|
||||||
@@ -88,6 +115,22 @@ POST /api/v1/message/send
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 动态接收者示例(群发)🆕
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"token": "a3541c2f0d3e1b4a5c6d7e8f9a0b1c2d3e",
|
||||||
|
"title": "系统维护通知",
|
||||||
|
"text": "系统将于今晚22:00进行维护,预计持续2小时。",
|
||||||
|
"html": "<h2>系统维护通知</h2><p>系统将于今晚<strong>22:00</strong>进行维护,预计持续<strong>2小时</strong>。</p>",
|
||||||
|
"recipients": [
|
||||||
|
"user1@example.com",
|
||||||
|
"user2@example.com",
|
||||||
|
"user3@example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### cURL 示例
|
### cURL 示例
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -258,14 +301,14 @@ V1 API 支持三种消息格式,系统会根据任务实例配置的格式类
|
|||||||
|
|
||||||
## 支持的推送渠道
|
## 支持的推送渠道
|
||||||
|
|
||||||
| 渠道 | 支持格式 | @提醒 | 说明 |
|
| 渠道 | 支持格式 | @提醒 | 动态接收者 | 说明 |
|
||||||
|------|---------|------|------|
|
|------|---------|------|-----------|------|
|
||||||
| **邮件** | Text, HTML | ❌ | SMTP邮件发送,推荐使用 HTML 格式 |
|
| **邮件** | Text, HTML | ❌ | ✅ | SMTP邮件发送,支持群发多个收件人 |
|
||||||
| **钉钉** | Text, Markdown | ✅ | 钉钉机器人,支持 Markdown 富文本 |
|
| **钉钉** | Text, Markdown | ✅ | ❌ | 钉钉机器人,支持 Markdown 富文本 |
|
||||||
| **企业微信** | Text, Markdown | ✅ | 企业微信机器人,支持 Markdown |
|
| **企业微信** | Text, Markdown | ✅ | ❌ | 企业微信机器人,支持 Markdown |
|
||||||
| **微信公众号** | Text | ❌ | 微信测试公众号模板消息 |
|
| **微信公众号** | Text | ❌ | ✅ | 微信测试公众号,支持多个 OpenID 群发 |
|
||||||
| **自定义Webhook** | Text, HTML, Markdown | ❌ | 自定义HTTP请求,格式取决于配置 |
|
| **自定义Webhook** | Text, HTML, Markdown | ❌ | ❌ | 自定义HTTP请求,格式取决于配置 |
|
||||||
| **自托管消息** | Text, HTML, Markdown | ❌ | 站内消息,支持多种格式 |
|
| **自托管消息** | Text, HTML, Markdown | ❌ | ❌ | 站内消息,支持多种格式 |
|
||||||
|
|
||||||
## 使用流程
|
## 使用流程
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ POST /api/v2/message/send
|
|||||||
| token | string | 是 | 加密的模板 Token |
|
| token | string | 是 | 加密的模板 Token |
|
||||||
| title | string | 是 | 消息标题 |
|
| title | string | 是 | 消息标题 |
|
||||||
| placeholders | object | 否 | 占位符键值对 |
|
| placeholders | object | 否 | 占位符键值对 |
|
||||||
|
| recipients | array | 条件必填 | 动态接收者列表(群发模式)🆕 |
|
||||||
|
|
||||||
### 参数说明
|
### 参数说明
|
||||||
|
|
||||||
@@ -59,6 +60,19 @@ POST /api/v2/message/send
|
|||||||
- 如果模板中定义了占位符但未传递,将使用默认值
|
- 如果模板中定义了占位符但未传递,将使用默认值
|
||||||
- 如果既未传递也无默认值,占位符将保持原样
|
- 如果既未传递也无默认值,占位符将保持原样
|
||||||
|
|
||||||
|
#### recipients 🆕
|
||||||
|
|
||||||
|
- 动态接收者列表,用于群发场景
|
||||||
|
- 格式为字符串数组:`["user1@example.com", "user2@example.com"]`
|
||||||
|
- **条件必填**:如果模板配置了动态接收实例,此参数为必填
|
||||||
|
- **支持的渠道**:
|
||||||
|
- ✅ 邮件 - 多个收件人邮箱地址
|
||||||
|
- ✅ 微信公众号 - 多个用户 OpenID
|
||||||
|
- **使用限制**:
|
||||||
|
- 一个模板只能配置一个动态接收实例
|
||||||
|
- 动态接收实例不能与固定接收实例混合使用
|
||||||
|
- 建议控制接收者数量,避免触发渠道限流
|
||||||
|
|
||||||
## 请求示例
|
## 请求示例
|
||||||
|
|
||||||
### 基本示例
|
### 基本示例
|
||||||
@@ -165,6 +179,38 @@ axios.post(url, data)
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 动态接收者示例(群发)🆕
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const axios = require('axios');
|
||||||
|
|
||||||
|
const url = 'http://your-domain/api/v2/message/send';
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
token: 'a3541c2f0d3e1b4a5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b',
|
||||||
|
title: '活动通知',
|
||||||
|
placeholders: {
|
||||||
|
activity_name: '双十二大促',
|
||||||
|
start_time: '2024-12-12 00:00:00',
|
||||||
|
discount: '全场8折'
|
||||||
|
},
|
||||||
|
recipients: [
|
||||||
|
'user1@example.com',
|
||||||
|
'user2@example.com',
|
||||||
|
'user3@example.com'
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
axios.post(url, data)
|
||||||
|
.then(response => {
|
||||||
|
console.log(response.data);
|
||||||
|
console.log(`成功发送给 ${response.data.data.count} 个接收者`);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
### Java 示例
|
### Java 示例
|
||||||
|
|
||||||
```java
|
```java
|
||||||
|
|||||||
+54
-2
@@ -53,14 +53,30 @@
|
|||||||
|
|
||||||
#### 邮件渠道
|
#### 邮件渠道
|
||||||
|
|
||||||
- **收件人邮箱** - 接收邮件的邮箱地址
|
- **固定模式**:
|
||||||
- **消息格式** - Text 或 HTML
|
- 收件人邮箱 - 接收邮件的邮箱地址
|
||||||
|
- 消息格式 - Text 或 HTML
|
||||||
|
- **动态接收者模式(群发)** 🆕:
|
||||||
|
- 勾选"动态接收者模式"
|
||||||
|
- 无需配置固定收件人
|
||||||
|
- 发送时通过 API 的 `recipients` 参数指定多个收件人
|
||||||
|
- 适用于邮件群发场景
|
||||||
|
|
||||||
#### 钉钉/企业微信
|
#### 钉钉/企业微信
|
||||||
|
|
||||||
- **消息格式** - Text 或 Markdown
|
- **消息格式** - Text 或 Markdown
|
||||||
- **@提醒** - 可选配置@手机号或@所有人
|
- **@提醒** - 可选配置@手机号或@所有人
|
||||||
|
|
||||||
|
#### 微信公众号
|
||||||
|
|
||||||
|
- **固定模式**:
|
||||||
|
- 用户 OpenID - 接收消息的用户标识
|
||||||
|
- **动态接收者模式(群发)** 🆕:
|
||||||
|
- 勾选"动态接收者模式"
|
||||||
|
- 无需配置固定 OpenID
|
||||||
|
- 发送时通过 API 的 `recipients` 参数指定多个 OpenID
|
||||||
|
- 适用于公众号群发场景
|
||||||
|
|
||||||
#### 自定义 Webhook
|
#### 自定义 Webhook
|
||||||
|
|
||||||
- 根据 Webhook 要求配置相应参数
|
- 根据 Webhook 要求配置相应参数
|
||||||
@@ -70,6 +86,13 @@
|
|||||||
- 无需额外配置
|
- 无需额外配置
|
||||||
- **消息格式** - Text、HTML 或 Markdown
|
- **消息格式** - Text、HTML 或 Markdown
|
||||||
|
|
||||||
|
::: warning 动态接收者模式限制
|
||||||
|
1. 一个任务只能配置一个动态接收实例
|
||||||
|
2. 动态接收实例不能与固定接收实例混合使用
|
||||||
|
3. 如果配置了动态接收实例,API 调用时 `recipients` 参数为必填
|
||||||
|
4. 建议控制接收者数量,避免触发渠道限流
|
||||||
|
:::
|
||||||
|
|
||||||
## 管理任务
|
## 管理任务
|
||||||
|
|
||||||
### 查看任务列表
|
### 查看任务列表
|
||||||
@@ -190,6 +213,35 @@ curl -X POST http://your-domain/api/v1/message/send \
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 动态接收者功能(群发)🆕
|
||||||
|
|
||||||
|
对于支持的渠道(邮件、微信公众号),可以使用动态接收者实现群发:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"token": "your_task_token",
|
||||||
|
"title": "系统维护通知",
|
||||||
|
"text": "系统将于今晚22:00进行维护",
|
||||||
|
"html": "<h2>系统维护通知</h2><p>系统将于今晚<strong>22:00</strong>进行维护</p>",
|
||||||
|
"recipients": [
|
||||||
|
"user1@example.com",
|
||||||
|
"user2@example.com",
|
||||||
|
"user3@example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**使用场景:**
|
||||||
|
- 邮件群发通知
|
||||||
|
- 微信公众号批量推送
|
||||||
|
- 营销活动通知
|
||||||
|
- 系统公告发布
|
||||||
|
|
||||||
|
**注意事项:**
|
||||||
|
- 任务必须配置为动态接收者模式
|
||||||
|
- 一个任务只能有一个动态接收实例
|
||||||
|
- 建议控制接收者数量,避免触发限流
|
||||||
|
|
||||||
## 工作流程
|
## 工作流程
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
|
|||||||
+43
-2
@@ -169,15 +169,36 @@ Markdown 格式,适用于钉钉、企业微信等支持 Markdown 的渠道:
|
|||||||
不同渠道需要配置不同的参数:
|
不同渠道需要配置不同的参数:
|
||||||
|
|
||||||
**邮件渠道:**
|
**邮件渠道:**
|
||||||
- 收件人邮箱地址
|
- **固定模式**:
|
||||||
- 消息格式:Text 或 HTML
|
- 收件人邮箱地址
|
||||||
|
- 消息格式:Text 或 HTML
|
||||||
|
- **动态接收者模式(群发)** 🆕:
|
||||||
|
- 勾选"动态接收者模式"
|
||||||
|
- 无需配置固定收件人
|
||||||
|
- 发送时通过 API 的 `recipients` 参数指定多个收件人
|
||||||
|
- 适用于邮件群发场景
|
||||||
|
|
||||||
**钉钉/企业微信:**
|
**钉钉/企业微信:**
|
||||||
- 消息格式:Text 或 Markdown
|
- 消息格式:Text 或 Markdown
|
||||||
|
|
||||||
|
**微信公众号:**
|
||||||
|
- **固定模式**:
|
||||||
|
- 用户 OpenID
|
||||||
|
- **动态接收者模式(群发)** 🆕:
|
||||||
|
- 勾选"动态接收者模式"
|
||||||
|
- 无需配置固定 OpenID
|
||||||
|
- 发送时通过 API 的 `recipients` 参数指定多个 OpenID
|
||||||
|
|
||||||
**自定义 Webhook:**
|
**自定义 Webhook:**
|
||||||
- 根据 Webhook 要求配置
|
- 根据 Webhook 要求配置
|
||||||
|
|
||||||
|
::: warning 动态接收者模式限制
|
||||||
|
1. 一个模板只能配置一个动态接收实例
|
||||||
|
2. 动态接收实例不能与固定接收实例混合使用
|
||||||
|
3. 如果配置了动态接收实例,API 调用时 `recipients` 参数为必填
|
||||||
|
4. 建议控制接收者数量,避免触发渠道限流
|
||||||
|
:::
|
||||||
|
|
||||||
### 3. 管理实例
|
### 3. 管理实例
|
||||||
|
|
||||||
- **启用/禁用** - 控制实例是否参与发送
|
- **启用/禁用** - 控制实例是否参与发送
|
||||||
@@ -220,6 +241,26 @@ curl -X POST http://your-domain/api/v2/message/send \
|
|||||||
}'
|
}'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**动态接收者示例(群发)** 🆕:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST http://your-domain/api/v2/message/send \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"token": "encrypted_template_token",
|
||||||
|
"title": "活动通知",
|
||||||
|
"placeholders": {
|
||||||
|
"activity_name": "双十二大促",
|
||||||
|
"discount": "全场8折"
|
||||||
|
},
|
||||||
|
"recipients": [
|
||||||
|
"user1@example.com",
|
||||||
|
"user2@example.com",
|
||||||
|
"user3@example.com"
|
||||||
|
]
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
## 最佳实践
|
## 最佳实践
|
||||||
|
|
||||||
### 1. 占位符命名
|
### 1. 占位符命名
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ module message-nest
|
|||||||
go 1.20
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10
|
||||||
|
github.com/alibabacloud-go/dysmsapi-20170525/v4 v4.1.0
|
||||||
|
github.com/alibabacloud-go/tea v1.2.2
|
||||||
|
github.com/alibabacloud-go/tea-utils/v2 v2.0.6
|
||||||
github.com/engigu/logrus-prefixed-formatter v0.5.3-0.20240113025353-498bcddfa6b5
|
github.com/engigu/logrus-prefixed-formatter v0.5.3-0.20240113025353-498bcddfa6b5
|
||||||
github.com/gin-gonic/gin v1.9.1
|
github.com/gin-gonic/gin v1.9.1
|
||||||
github.com/glebarez/sqlite v1.11.0
|
github.com/glebarez/sqlite v1.11.0
|
||||||
@@ -22,11 +26,19 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect
|
||||||
|
github.com/alibabacloud-go/debug v1.0.1 // indirect
|
||||||
|
github.com/alibabacloud-go/endpoint-util v1.1.0 // indirect
|
||||||
|
github.com/alibabacloud-go/openapi-util v0.1.0 // indirect
|
||||||
|
github.com/alibabacloud-go/tea-utils v1.3.1 // indirect
|
||||||
|
github.com/alibabacloud-go/tea-xml v1.1.3 // indirect
|
||||||
|
github.com/aliyun/credentials-go v1.3.10 // indirect
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d // indirect
|
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d // indirect
|
||||||
github.com/bytedance/sonic v1.10.2 // indirect
|
github.com/bytedance/sonic v1.10.2 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
||||||
github.com/chenzhuoyu/iasm v0.9.1 // indirect
|
github.com/chenzhuoyu/iasm v0.9.1 // indirect
|
||||||
|
github.com/clbanning/mxj/v2 v2.5.5 // indirect
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/fatih/structs v1.1.0 // indirect
|
github.com/fatih/structs v1.1.0 // indirect
|
||||||
@@ -42,7 +54,6 @@ require (
|
|||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
||||||
github.com/kr/pretty v0.1.0 // indirect
|
|
||||||
github.com/leodido/go-urn v1.2.4 // indirect
|
github.com/leodido/go-urn v1.2.4 // indirect
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
@@ -55,11 +66,11 @@ require (
|
|||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||||
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 // indirect
|
|
||||||
github.com/spf13/cast v1.4.1 // indirect
|
github.com/spf13/cast v1.4.1 // indirect
|
||||||
github.com/tidwall/gjson v1.14.1 // indirect
|
github.com/tidwall/gjson v1.14.1 // indirect
|
||||||
github.com/tidwall/match v1.1.1 // indirect
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
github.com/tidwall/pretty v1.2.0 // indirect
|
github.com/tidwall/pretty v1.2.0 // indirect
|
||||||
|
github.com/tjfoc/gmsm v1.4.1 // indirect
|
||||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
@@ -74,8 +85,7 @@ require (
|
|||||||
golang.org/x/text v0.21.0 // indirect
|
golang.org/x/text v0.21.0 // indirect
|
||||||
google.golang.org/protobuf v1.33.0 // indirect
|
google.golang.org/protobuf v1.33.0 // indirect
|
||||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
gopkg.in/ini.v1 v1.47.0 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
modernc.org/libc v1.22.5 // indirect
|
modernc.org/libc v1.22.5 // indirect
|
||||||
modernc.org/mathutil v1.5.0 // indirect
|
modernc.org/mathutil v1.5.0 // indirect
|
||||||
|
|||||||
@@ -1,13 +1,63 @@
|
|||||||
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6 h1:eIf+iGJxdU4U9ypaUfbtOWCsZSbTb8AUHvyPrxu6mAA=
|
||||||
|
github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6/go.mod h1:4EUIoxs/do24zMOGGqYVWgw0s9NtiylnJglOeEB5UJo=
|
||||||
|
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc=
|
||||||
|
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 h1:zE8vH9C7JiZLNJJQ5OwjU9mSi4T9ef9u3BURT6LCLC8=
|
||||||
|
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5/go.mod h1:tWnyE9AjF8J8qqLk645oUmVUnFybApTQWklQmi5tY6g=
|
||||||
|
github.com/alibabacloud-go/darabonba-array v0.1.0 h1:vR8s7b1fWAQIjEjWnuF0JiKsCvclSRTfDzZHTYqfufY=
|
||||||
|
github.com/alibabacloud-go/darabonba-array v0.1.0/go.mod h1:BLKxr0brnggqOJPqT09DFJ8g3fsDshapUD3C3aOEFaI=
|
||||||
|
github.com/alibabacloud-go/darabonba-encode-util v0.0.2 h1:1uJGrbsGEVqWcWxrS9MyC2NG0Ax+GpOM5gtupki31XE=
|
||||||
|
github.com/alibabacloud-go/darabonba-encode-util v0.0.2/go.mod h1:JiW9higWHYXm7F4PKuMgEUETNZasrDM6vqVr/Can7H8=
|
||||||
|
github.com/alibabacloud-go/darabonba-map v0.0.2 h1:qvPnGB4+dJbJIxOOfawxzF3hzMnIpjmafa0qOTp6udc=
|
||||||
|
github.com/alibabacloud-go/darabonba-map v0.0.2/go.mod h1:28AJaX8FOE/ym8OUFWga+MtEzBunJwQGceGQlvaPGPc=
|
||||||
|
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10 h1:GEYkMApgpKEVDn6z12DcH1EGYpDYRB8JxsazM4Rywak=
|
||||||
|
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10/go.mod h1:26a14FGhZVELuz2cc2AolvW4RHmIO3/HRwsdHhaIPDE=
|
||||||
|
github.com/alibabacloud-go/darabonba-signature-util v0.0.7 h1:UzCnKvsjPFzApvODDNEYqBHMFt1w98wC7FOo0InLyxg=
|
||||||
|
github.com/alibabacloud-go/darabonba-signature-util v0.0.7/go.mod h1:oUzCYV2fcCH797xKdL6BDH8ADIHlzrtKVjeRtunBNTQ=
|
||||||
|
github.com/alibabacloud-go/darabonba-string v1.0.2 h1:E714wms5ibdzCqGeYJ9JCFywE5nDyvIXIIQbZVFkkqo=
|
||||||
|
github.com/alibabacloud-go/darabonba-string v1.0.2/go.mod h1:93cTfV3vuPhhEwGGpKKqhVW4jLe7tDpo3LUM0i0g6mA=
|
||||||
|
github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68/go.mod h1:6pb/Qy8c+lqua8cFpEy7g39NRRqOWc3rOwAy8m5Y2BY=
|
||||||
|
github.com/alibabacloud-go/debug v1.0.0/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc=
|
||||||
|
github.com/alibabacloud-go/debug v1.0.1 h1:MsW9SmUtbb1Fnt3ieC6NNZi6aEwrXfDksD4QA6GSbPg=
|
||||||
|
github.com/alibabacloud-go/debug v1.0.1/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc=
|
||||||
|
github.com/alibabacloud-go/dysmsapi-20170525/v4 v4.1.0 h1:DRd/m0TAVzmVoacUv4+C32HrpoehAPxe7Yi8jBwW5HY=
|
||||||
|
github.com/alibabacloud-go/dysmsapi-20170525/v4 v4.1.0/go.mod h1:XA0GIZV/FIFTuasWP6WlGolkSfH/t8J5eqxT2dHAFbE=
|
||||||
|
github.com/alibabacloud-go/endpoint-util v1.1.0 h1:r/4D3VSw888XGaeNpP994zDUaxdgTSHBbVfZlzf6b5Q=
|
||||||
|
github.com/alibabacloud-go/endpoint-util v1.1.0/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE=
|
||||||
|
github.com/alibabacloud-go/openapi-util v0.1.0 h1:0z75cIULkDrdEhkLWgi9tnLe+KhAFE/r5Pb3312/eAY=
|
||||||
|
github.com/alibabacloud-go/openapi-util v0.1.0/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws=
|
||||||
|
github.com/alibabacloud-go/tea v1.1.0/go.mod h1:IkGyUSX4Ba1V+k4pCtJUc6jDpZLFph9QMy2VUPTwukg=
|
||||||
|
github.com/alibabacloud-go/tea v1.1.7/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
|
||||||
|
github.com/alibabacloud-go/tea v1.1.8/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
|
||||||
|
github.com/alibabacloud-go/tea v1.1.11/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
|
||||||
|
github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
|
||||||
|
github.com/alibabacloud-go/tea v1.1.20/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
|
||||||
|
github.com/alibabacloud-go/tea v1.2.2 h1:aTsR6Rl3ANWPfqeQugPglfurloyBJY85eFy7Gc1+8oU=
|
||||||
|
github.com/alibabacloud-go/tea v1.2.2/go.mod h1:CF3vOzEMAG+bR4WOql8gc2G9H3EkH3ZLAQdpmpXMgwk=
|
||||||
|
github.com/alibabacloud-go/tea-utils v1.3.1 h1:iWQeRzRheqCMuiF3+XkfybB3kTgUXkXX+JMrqfLeB2I=
|
||||||
|
github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE=
|
||||||
|
github.com/alibabacloud-go/tea-utils/v2 v2.0.5/go.mod h1:dL6vbUT35E4F4bFTHL845eUloqaerYBYPsdWR2/jhe4=
|
||||||
|
github.com/alibabacloud-go/tea-utils/v2 v2.0.6 h1:ZkmUlhlQbaDC+Eba/GARMPy6hKdCLiSke5RsN5LcyQ0=
|
||||||
|
github.com/alibabacloud-go/tea-utils/v2 v2.0.6/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I=
|
||||||
|
github.com/alibabacloud-go/tea-xml v1.1.3 h1:7LYnm+JbOq2B+T/B0fHC4Ies4/FofC4zHzYtqw7dgt0=
|
||||||
|
github.com/alibabacloud-go/tea-xml v1.1.3/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
|
||||||
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk=
|
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk=
|
||||||
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
||||||
github.com/alicebob/miniredis/v2 v2.30.0 h1:uA3uhDbCxfO9+DI/DuGeAMr9qI+noVWwGPNTFuKID5M=
|
github.com/alicebob/miniredis/v2 v2.30.0 h1:uA3uhDbCxfO9+DI/DuGeAMr9qI+noVWwGPNTFuKID5M=
|
||||||
github.com/alicebob/miniredis/v2 v2.30.0/go.mod h1:84TWKZlxYkfgMucPBf5SOQBYJceZeQRFIaQgNMiCX6Q=
|
github.com/alicebob/miniredis/v2 v2.30.0/go.mod h1:84TWKZlxYkfgMucPBf5SOQBYJceZeQRFIaQgNMiCX6Q=
|
||||||
|
github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw=
|
||||||
|
github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0=
|
||||||
|
github.com/aliyun/credentials-go v1.3.6/go.mod h1:1LxUuX7L5YrZUWzBrRyk0SwSdH4OmPrib8NVePL3fxM=
|
||||||
|
github.com/aliyun/credentials-go v1.3.10 h1:45Xxrae/evfzQL9V10zL3xX31eqgLWEaIdCoPipOEQA=
|
||||||
|
github.com/aliyun/credentials-go v1.3.10/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U=
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d h1:pVrfxiGfwelyab6n21ZBkbkmbevaf+WvMIiR7sr97hw=
|
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d h1:pVrfxiGfwelyab6n21ZBkbkmbevaf+WvMIiR7sr97hw=
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
|
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
|
||||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||||
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
|
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
|
||||||
github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE=
|
github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE=
|
||||||
github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
|
github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
|
||||||
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||||
@@ -20,6 +70,10 @@ github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLI
|
|||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
|
github.com/clbanning/mxj/v2 v2.5.5 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E=
|
||||||
|
github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
|
||||||
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@@ -29,6 +83,9 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
|
|||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/engigu/logrus-prefixed-formatter v0.5.3-0.20240113025353-498bcddfa6b5 h1:XRkGO/dTf/3p1SDYr/BNtDmqwLtI+jssymsCgzvqf5c=
|
github.com/engigu/logrus-prefixed-formatter v0.5.3-0.20240113025353-498bcddfa6b5 h1:XRkGO/dTf/3p1SDYr/BNtDmqwLtI+jssymsCgzvqf5c=
|
||||||
github.com/engigu/logrus-prefixed-formatter v0.5.3-0.20240113025353-498bcddfa6b5/go.mod h1:wpl/H0ZIheoONzhaJIPU5Qe+lmLxPXEdY1PNWjla63E=
|
github.com/engigu/logrus-prefixed-formatter v0.5.3-0.20240113025353-498bcddfa6b5/go.mod h1:wpl/H0ZIheoONzhaJIPU5Qe+lmLxPXEdY1PNWjla63E=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
@@ -64,7 +121,11 @@ github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
|||||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw=
|
github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||||
@@ -73,6 +134,7 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
|
|||||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
@@ -85,8 +147,9 @@ github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbu
|
|||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
|
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 h1:l5lAOZEym3oK3SQ2HBHWsJUfbNBiTXJDeW2QDxw9AQ0=
|
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
|
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
|
||||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
|
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
@@ -95,6 +158,7 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
|
|||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
|
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
@@ -104,8 +168,6 @@ github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02
|
|||||||
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
|
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
@@ -123,9 +185,13 @@ github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyex
|
|||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||||
@@ -147,6 +213,7 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
@@ -163,14 +230,16 @@ github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs
|
|||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY=
|
|
||||||
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
|
github.com/smartystreets/assertions v1.1.0 h1:MkTeG1DMwsrdH7QtLXy5W+fUxWq+vmb6cLmyJ7aRtF0=
|
||||||
|
github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
|
||||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
||||||
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8=
|
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||||
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
|
github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
|
||||||
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
@@ -189,6 +258,9 @@ github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
|||||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
|
github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w=
|
||||||
|
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
|
||||||
|
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
|
||||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||||
@@ -201,7 +273,10 @@ github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs=
|
|||||||
github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
|
github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
|
||||||
github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg=
|
github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg=
|
||||||
github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE=
|
github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE=
|
||||||
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64 h1:5mLPGnFdSsevFRFc9q3yYbBkB6tsm4aCwwQV/j1JQAQ=
|
github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64 h1:5mLPGnFdSsevFRFc9q3yYbBkB6tsm4aCwwQV/j1JQAQ=
|
||||||
github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||||
@@ -211,24 +286,60 @@ golang.org/x/arch v0.6.0 h1:S0JTfE48HbRj80+4tbvZDYsJ3tGv6BUU3XxyZ7CirAc=
|
|||||||
golang.org/x/arch v0.6.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
golang.org/x/arch v0.6.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
|
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||||
|
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||||
|
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||||
|
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||||
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
|
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
|
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||||
|
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||||
|
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
|
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
|
||||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@@ -239,6 +350,7 @@ golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -246,30 +358,64 @@ golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
|
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||||
|
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
||||||
|
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||||
|
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||||
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
||||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||||
|
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
@@ -283,19 +429,21 @@ google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHh
|
|||||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
|
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
|
||||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
|
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
|
||||||
gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY=
|
gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY=
|
||||||
gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0=
|
gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0=
|
||||||
gopkg.in/ini.v1 v1.47.0 h1:XAVsOWcIxjm6JVEbCbSZgSBZIF0BrCzXs4orAQr6uc8=
|
gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/ini.v1 v1.47.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||||
|
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
@@ -306,6 +454,8 @@ gorm.io/driver/mysql v1.5.6/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkD
|
|||||||
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||||
gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s=
|
gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s=
|
||||||
gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||||
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
|
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
|
||||||
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
|
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
|
||||||
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
||||||
|
|||||||
@@ -41,6 +41,12 @@ type InsCustomConfig struct {
|
|||||||
type InsMessageNestConfig struct {
|
type InsMessageNestConfig struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InsAliyunSMSConfig 实例里面的阿里云短信config
|
||||||
|
type InsAliyunSMSConfig struct {
|
||||||
|
PhoneNumber string `json:"phone_number" validate:"required" label:"手机号码"`
|
||||||
|
TemplateCode string `json:"template_code" validate:"required" label:"短信模板CODE"`
|
||||||
|
}
|
||||||
|
|
||||||
// ManyAddTaskIns 批量添加实例
|
// ManyAddTaskIns 批量添加实例
|
||||||
func ManyAddTaskIns(taskIns []SendTasksIns) error {
|
func ManyAddTaskIns(taskIns []SendTasksIns) error {
|
||||||
tx := db.Begin()
|
tx := db.Begin()
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ type SendMessageReq struct {
|
|||||||
AtMobiles []string `json:"at_mobiles" label:"@的手机号列表"`
|
AtMobiles []string `json:"at_mobiles" label:"@的手机号列表"`
|
||||||
AtUserIds []string `json:"at_user_ids" label:"@的用户ID列表"`
|
AtUserIds []string `json:"at_user_ids" label:"@的用户ID列表"`
|
||||||
AtAll bool `json:"at_all" label:"是否@所有人"`
|
AtAll bool `json:"at_all" label:"是否@所有人"`
|
||||||
|
|
||||||
|
// 动态接收者(用于邮箱、微信公众号等支持群发的渠道)
|
||||||
|
Recipients []string `json:"recipients" label:"接收者列表"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DoSendMassage 外部调用发信接口
|
// DoSendMassage 外部调用发信接口
|
||||||
@@ -61,17 +64,18 @@ func DoSendMassage(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
msgService := send_message_service.SendMessageService{
|
msgService := send_message_service.SendMessageService{
|
||||||
SendMode: send_message_service.SendModeTask, // 明确标记为任务模式
|
SendMode: send_message_service.SendModeTask, // 明确标记为任务模式
|
||||||
TaskID: taskID,
|
TaskID: taskID,
|
||||||
Title: req.Title,
|
Title: req.Title,
|
||||||
Text: req.Text,
|
Text: req.Text,
|
||||||
HTML: req.HTML,
|
HTML: req.HTML,
|
||||||
URL: req.URL,
|
URL: req.URL,
|
||||||
MarkDown: req.MarkDown,
|
MarkDown: req.MarkDown,
|
||||||
CallerIp: c.ClientIP(),
|
CallerIp: c.ClientIP(),
|
||||||
AtMobiles: req.AtMobiles,
|
AtMobiles: req.AtMobiles,
|
||||||
AtUserIds: req.AtUserIds,
|
AtUserIds: req.AtUserIds,
|
||||||
AtAll: req.AtAll,
|
AtAll: req.AtAll,
|
||||||
|
Recipients: req.Recipients,
|
||||||
DefaultLogger: logrus.WithFields(logrus.Fields{
|
DefaultLogger: logrus.WithFields(logrus.Fields{
|
||||||
"prefix": "[Send Instance]",
|
"prefix": "[Send Instance]",
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -28,6 +28,20 @@ func (sw *SendTaskInsService) ValidateDiffIns(ins models.SendTasksIns) (string,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "邮箱auth反序列化失败!", empty
|
return "邮箱auth反序列化失败!", empty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查是否为动态接收者模式
|
||||||
|
var configMap map[string]interface{}
|
||||||
|
json.Unmarshal([]byte(ins.Config), &configMap)
|
||||||
|
allowMultiRecip, exists := configMap["allowMultiRecip"].(bool)
|
||||||
|
|
||||||
|
// allowMultiRecip=true为动态模式,to_account可以为空
|
||||||
|
// 历史数据(不存在allowMultiRecip字段)默认为固定模式
|
||||||
|
if exists && allowMultiRecip && emailConfig.ToAccount == "" {
|
||||||
|
// 动态模式,不验证to_account
|
||||||
|
return "", emailConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// 固定模式或有to_account时,进行常规验证
|
||||||
_, Msg := app.CommonPlaygroundValid(emailConfig)
|
_, Msg := app.CommonPlaygroundValid(emailConfig)
|
||||||
return Msg, emailConfig
|
return Msg, emailConfig
|
||||||
}
|
}
|
||||||
@@ -58,6 +72,29 @@ func (sw *SendTaskInsService) ValidateDiffIns(ins models.SendTasksIns) (string,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "微信公众号发送配置反序列化失败!", empty
|
return "微信公众号发送配置反序列化失败!", empty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查是否为动态接收者模式
|
||||||
|
var configMap map[string]interface{}
|
||||||
|
json.Unmarshal([]byte(ins.Config), &configMap)
|
||||||
|
allowMultiRecip, exists := configMap["allowMultiRecip"].(bool)
|
||||||
|
|
||||||
|
// allowMultiRecip=true为动态模式,to_account可以为空
|
||||||
|
// 历史数据(不存在allowMultiRecip字段)默认为固定模式
|
||||||
|
if exists && allowMultiRecip && Config.ToAccount == "" {
|
||||||
|
// 动态模式,不验证to_account
|
||||||
|
return "", Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// 固定模式或有to_account时,进行常规验证
|
||||||
|
_, Msg := app.CommonPlaygroundValid(Config)
|
||||||
|
return Msg, Config
|
||||||
|
}
|
||||||
|
if ins.WayType == "AliyunSMS" {
|
||||||
|
var Config models.InsAliyunSMSConfig
|
||||||
|
err := json.Unmarshal([]byte(ins.Config), &Config)
|
||||||
|
if err != nil {
|
||||||
|
return "阿里云短信配置反序列化失败!", empty
|
||||||
|
}
|
||||||
_, Msg := app.CommonPlaygroundValid(Config)
|
_, Msg := app.CommonPlaygroundValid(Config)
|
||||||
return Msg, Config
|
return Msg, Config
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package send_message_service
|
package send_message_service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"message-nest/models"
|
"message-nest/models"
|
||||||
@@ -48,6 +49,9 @@ type SendMessageService struct {
|
|||||||
AtUserIds []string
|
AtUserIds []string
|
||||||
AtAll bool
|
AtAll bool
|
||||||
|
|
||||||
|
// 动态接收者(用于邮箱、微信公众号等支持群发的渠道)
|
||||||
|
Recipients []string
|
||||||
|
|
||||||
Status int
|
Status int
|
||||||
LogOutput []string
|
LogOutput []string
|
||||||
|
|
||||||
@@ -253,12 +257,35 @@ func (sm *SendMessageService) Send(task models.TaskIns) (string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用 SendUnified 方法(自动格式转换和@功能支持)
|
// 处理动态接收者(邮箱、微信公众号等支持群发的渠道)
|
||||||
res, errMsg := channel.SendUnified(msgObj, ins.SendTasksIns, unifiedContent)
|
isDynamicMode := sm.isDynamicRecipientMode(ins.SendTasksIns)
|
||||||
if res != "" {
|
|
||||||
sm.LogsAndStatusMark(fmt.Sprintf("返回内容:%s\n", res), sm.Status)
|
if isDynamicMode && sm.supportsDynamicRecipient(way.Type) && len(sm.Recipients) > 0 {
|
||||||
|
// 动态接收模式:使用API传入的Recipients列表(群发)
|
||||||
|
sm.LogsAndStatusMark(fmt.Sprintf("动态接收模式,共 %d 个接收者", len(sm.Recipients)), sm.Status)
|
||||||
|
for recipientIdx, recipient := range sm.Recipients {
|
||||||
|
sm.LogsAndStatusMark(fmt.Sprintf(">>> 接收者 %d/%d: %s", recipientIdx+1, len(sm.Recipients), recipient), sm.Status)
|
||||||
|
|
||||||
|
// 临时修改实例配置中的接收者
|
||||||
|
modifiedIns := sm.modifyInsRecipient(ins.SendTasksIns, recipient, way.Type)
|
||||||
|
|
||||||
|
// 使用 SendUnified 方法发送
|
||||||
|
res, errMsg := channel.SendUnified(msgObj, modifiedIns, unifiedContent)
|
||||||
|
if res != "" {
|
||||||
|
sm.LogsAndStatusMark(fmt.Sprintf("返回内容:%s", res), sm.Status)
|
||||||
|
} else {
|
||||||
|
sm.LogsAndStatusMark(sm.TransError(errMsg), errStrIsSuccess(errMsg))
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
sm.LogsAndStatusMark(sm.TransError(errMsg), errStrIsSuccess(errMsg))
|
// 固定接收模式:使用实例配置的to_account
|
||||||
|
sm.LogsAndStatusMark("固定接收模式,使用实例配置的接收者", sm.Status)
|
||||||
|
res, errMsg := channel.SendUnified(msgObj, ins.SendTasksIns, unifiedContent)
|
||||||
|
if res != "" {
|
||||||
|
sm.LogsAndStatusMark(fmt.Sprintf("返回内容:%s\n", res), sm.Status)
|
||||||
|
} else {
|
||||||
|
sm.LogsAndStatusMark(sm.TransError(errMsg), errStrIsSuccess(errMsg))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -366,6 +393,64 @@ func (sm *SendMessageService) BuildTemplateContent(ins models.SendTasksIns) *uni
|
|||||||
return content
|
return content
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// supportsDynamicRecipient 判断渠道是否支持动态接收者
|
||||||
|
func (sm *SendMessageService) supportsDynamicRecipient(wayType string) bool {
|
||||||
|
// 支持动态接收者的渠道类型
|
||||||
|
supportedTypes := map[string]bool{
|
||||||
|
unified.MessageTypeEmail: true,
|
||||||
|
unified.MessageTypeWeChatOFAccount: true,
|
||||||
|
// 可以继续添加其他支持动态接收者的渠道
|
||||||
|
}
|
||||||
|
return supportedTypes[wayType]
|
||||||
|
}
|
||||||
|
|
||||||
|
// isDynamicRecipientMode 判断实例是否配置为动态接收模式
|
||||||
|
func (sm *SendMessageService) isDynamicRecipientMode(ins models.SendTasksIns) bool {
|
||||||
|
// 解析实例配置
|
||||||
|
var config map[string]interface{}
|
||||||
|
if err := json.Unmarshal([]byte(ins.Config), &config); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查allowMultiRecip字段
|
||||||
|
// allowMultiRecip=true: 动态模式
|
||||||
|
// allowMultiRecip=false或不存在: 固定模式
|
||||||
|
if allowMultiRecip, ok := config["allowMultiRecip"]; ok {
|
||||||
|
if allow, ok := allowMultiRecip.(bool); ok {
|
||||||
|
return allow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认为固定模式(兼容历史数据)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// modifyInsRecipient 临时修改实例配置中的接收者
|
||||||
|
func (sm *SendMessageService) modifyInsRecipient(ins models.SendTasksIns, recipient string, wayType string) models.SendTasksIns {
|
||||||
|
// 创建副本
|
||||||
|
modifiedIns := ins
|
||||||
|
|
||||||
|
// 根据渠道类型修改配置
|
||||||
|
var config map[string]interface{}
|
||||||
|
if err := json.Unmarshal([]byte(ins.Config), &config); err != nil {
|
||||||
|
sm.LogsAndStatusMark(fmt.Sprintf("解析实例配置失败: %s", err.Error()), SendFail)
|
||||||
|
return ins
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改接收者字段
|
||||||
|
config["to_account"] = recipient
|
||||||
|
|
||||||
|
// 序列化回JSON
|
||||||
|
modifiedConfigBytes, err := json.Marshal(config)
|
||||||
|
if err != nil {
|
||||||
|
sm.LogsAndStatusMark(fmt.Sprintf("序列化实例配置失败: %s", err.Error()), SendFail)
|
||||||
|
return ins
|
||||||
|
}
|
||||||
|
|
||||||
|
modifiedIns.Config = string(modifiedConfigBytes)
|
||||||
|
return modifiedIns
|
||||||
|
}
|
||||||
|
|
||||||
// GetSendMsg 获取对应消息内容(任务模式使用)
|
// GetSendMsg 获取对应消息内容(任务模式使用)
|
||||||
// 先根据实例设置的类型取,取不到或者取到的是空,则使用text发送
|
// 先根据实例设置的类型取,取不到或者取到的是空,则使用text发送
|
||||||
func (sm *SendMessageService) GetSendMsg(ins models.SendTasksIns) (string, string) {
|
func (sm *SendMessageService) GetSendMsg(ins models.SendTasksIns) (string, string) {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ const (
|
|||||||
MessageTypeCustom = channels.MessageTypeCustom
|
MessageTypeCustom = channels.MessageTypeCustom
|
||||||
MessageTypeWeChatOFAccount = channels.MessageTypeWeChatOFAccount
|
MessageTypeWeChatOFAccount = channels.MessageTypeWeChatOFAccount
|
||||||
MessageTypeMessageNest = channels.MessageTypeMessageNest
|
MessageTypeMessageNest = channels.MessageTypeMessageNest
|
||||||
|
MessageTypeAliyunSMS = channels.MessageTypeAliyunSMS
|
||||||
)
|
)
|
||||||
|
|
||||||
// ChannelRegistry 渠道注册表
|
// ChannelRegistry 渠道注册表
|
||||||
@@ -95,6 +96,7 @@ func GetGlobalChannelRegistry() *ChannelRegistry {
|
|||||||
globalChannelRegistry.Register(channels.NewCustomChannel())
|
globalChannelRegistry.Register(channels.NewCustomChannel())
|
||||||
globalChannelRegistry.Register(channels.NewWeChatOFAccountChannel())
|
globalChannelRegistry.Register(channels.NewWeChatOFAccountChannel())
|
||||||
globalChannelRegistry.Register(channels.NewMessageNestChannel())
|
globalChannelRegistry.Register(channels.NewMessageNestChannel())
|
||||||
|
globalChannelRegistry.Register(channels.NewAliyunSMSChannel())
|
||||||
})
|
})
|
||||||
return globalChannelRegistry
|
return globalChannelRegistry
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
package channels
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"message-nest/models"
|
||||||
|
"message-nest/service/send_ins_service"
|
||||||
|
"message-nest/service/send_way_service"
|
||||||
|
|
||||||
|
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||||
|
dysmsapi "github.com/alibabacloud-go/dysmsapi-20170525/v4/client"
|
||||||
|
util "github.com/alibabacloud-go/tea-utils/v2/service"
|
||||||
|
"github.com/alibabacloud-go/tea/tea"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AliyunSMSChannel struct{ *BaseChannel }
|
||||||
|
|
||||||
|
func NewAliyunSMSChannel() *AliyunSMSChannel {
|
||||||
|
return &AliyunSMSChannel{BaseChannel: NewBaseChannel(MessageTypeAliyunSMS, []string{FormatTypeText})}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AliyunSMSChannel) SendUnified(msgObj interface{}, ins models.SendTasksIns, content *UnifiedMessageContent) (string, string) {
|
||||||
|
auth, ok := msgObj.(send_way_service.WayDetailAliyunSMS)
|
||||||
|
if !ok {
|
||||||
|
return "", "类型转换失败"
|
||||||
|
}
|
||||||
|
|
||||||
|
insService := send_ins_service.SendTaskInsService{}
|
||||||
|
errStr, configInterface := insService.ValidateDiffIns(ins)
|
||||||
|
if errStr != "" {
|
||||||
|
return "", errStr
|
||||||
|
}
|
||||||
|
|
||||||
|
config, ok := configInterface.(models.InsAliyunSMSConfig)
|
||||||
|
if !ok {
|
||||||
|
return "", "阿里云短信config校验失败"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化内容
|
||||||
|
_, formattedContent, err := c.FormatContent(content)
|
||||||
|
if err != nil {
|
||||||
|
return "", err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建阿里云短信客户端
|
||||||
|
client, err := c.createClient(auth.AccessKeyId, auth.AccessKeySecret)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Sprintf("创建阿里云短信客户端失败: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// 准备模板参数
|
||||||
|
templateParam := map[string]interface{}{
|
||||||
|
"content": formattedContent,
|
||||||
|
}
|
||||||
|
if content.Extra != nil {
|
||||||
|
for k, v := range content.Extra {
|
||||||
|
templateParam[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
templateParamJSON, _ := json.Marshal(templateParam)
|
||||||
|
|
||||||
|
// 发送短信
|
||||||
|
sendSmsRequest := &dysmsapi.SendSmsRequest{
|
||||||
|
PhoneNumbers: tea.String(config.PhoneNumber),
|
||||||
|
SignName: tea.String(auth.SignName),
|
||||||
|
TemplateCode: tea.String(config.TemplateCode),
|
||||||
|
TemplateParam: tea.String(string(templateParamJSON)),
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime := &util.RuntimeOptions{}
|
||||||
|
tryErr := func() error {
|
||||||
|
defer func() {
|
||||||
|
if r := tea.Recover(recover()); r != nil {
|
||||||
|
// 处理panic
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
response, _err := client.SendSmsWithOptions(sendSmsRequest, runtime)
|
||||||
|
if _err != nil {
|
||||||
|
return _err
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.Body.Code != nil && *response.Body.Code != "OK" {
|
||||||
|
return fmt.Errorf("发送失败: %s - %s", tea.StringValue(response.Body.Code), tea.StringValue(response.Body.Message))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}()
|
||||||
|
|
||||||
|
if tryErr != nil {
|
||||||
|
return "", tryErr.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// createClient 创建阿里云短信客户端
|
||||||
|
func (c *AliyunSMSChannel) createClient(accessKeyId, accessKeySecret string) (*dysmsapi.Client, error) {
|
||||||
|
config := &openapi.Config{
|
||||||
|
AccessKeyId: tea.String(accessKeyId),
|
||||||
|
AccessKeySecret: tea.String(accessKeySecret),
|
||||||
|
Endpoint: tea.String("dysmsapi.aliyuncs.com"),
|
||||||
|
}
|
||||||
|
return dysmsapi.NewClient(config)
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ const (
|
|||||||
MessageTypeCustom = "Custom"
|
MessageTypeCustom = "Custom"
|
||||||
MessageTypeWeChatOFAccount = "WeChatOFAccount"
|
MessageTypeWeChatOFAccount = "WeChatOFAccount"
|
||||||
MessageTypeMessageNest = "MessageNest"
|
MessageTypeMessageNest = "MessageNest"
|
||||||
|
MessageTypeAliyunSMS = "AliyunSMS"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UnifiedMessageContent 统一的消息内容结构
|
// UnifiedMessageContent 统一的消息内容结构
|
||||||
|
|||||||
@@ -56,6 +56,13 @@ type WeChatOFAccount struct {
|
|||||||
TempID string `json:"tempid" validate:"max=2000" label:"模板消息id"`
|
TempID string `json:"tempid" validate:"max=2000" label:"模板消息id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WayDetailAliyunSMS 阿里云短信渠道明细字段
|
||||||
|
type WayDetailAliyunSMS struct {
|
||||||
|
AccessKeyId string `json:"access_key_id" validate:"required,max=100" label:"AccessKeyId"`
|
||||||
|
AccessKeySecret string `json:"access_key_secret" validate:"required,max=100" label:"AccessKeySecret"`
|
||||||
|
SignName string `json:"sign_name" validate:"required,max=50" label:"短信签名"`
|
||||||
|
}
|
||||||
|
|
||||||
// MessageNest 自托管消息
|
// MessageNest 自托管消息
|
||||||
type MessageNest struct {
|
type MessageNest struct {
|
||||||
}
|
}
|
||||||
@@ -182,6 +189,14 @@ func (sw *SendWay) ValidateDiffWay() (string, interface{}) {
|
|||||||
}
|
}
|
||||||
_, Msg := app.CommonPlaygroundValid(wca)
|
_, Msg := app.CommonPlaygroundValid(wca)
|
||||||
return Msg, wca
|
return Msg, wca
|
||||||
|
} else if sw.Type == "AliyunSMS" {
|
||||||
|
var aliyunSMS WayDetailAliyunSMS
|
||||||
|
err := json.Unmarshal([]byte(sw.Auth), &aliyunSMS)
|
||||||
|
if err != nil {
|
||||||
|
return "阿里云短信auth反序列化失败!", empty
|
||||||
|
}
|
||||||
|
_, Msg := app.CommonPlaygroundValid(aliyunSMS)
|
||||||
|
return Msg, aliyunSMS
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("未知的发信渠道校验: %s", sw.Type), empty
|
return fmt.Sprintf("未知的发信渠道校验: %s", sw.Type), empty
|
||||||
}
|
}
|
||||||
@@ -227,5 +242,9 @@ func (sw *SendWay) TestSendWay(msgObj interface{}) (string, string) {
|
|||||||
if ok {
|
if ok {
|
||||||
return "自托管消息不用测试运行,请直接添加", ""
|
return "自托管消息不用测试运行,请直接添加", ""
|
||||||
}
|
}
|
||||||
|
_, ok = msgObj.(WayDetailAliyunSMS)
|
||||||
|
if ok {
|
||||||
|
return "阿里云短信不用测试运行,请直接添加", ""
|
||||||
|
}
|
||||||
return fmt.Sprintf("未知的发信渠道校验: %s", sw.Type), ""
|
return fmt.Sprintf("未知的发信渠道校验: %s", sw.Type), ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { ref, defineComponent } from 'vue'
|
import { ref, defineComponent, watch, toRef } from 'vue'
|
||||||
import { toast } from 'vue-sonner'
|
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||||
import { Badge } from '@/components/ui/badge'
|
import { Badge } from '@/components/ui/badge'
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { TemplateApiStrGenerate } from '@/util/viewApi'
|
import { TemplateApiStrGenerate } from '@/util/viewApi'
|
||||||
|
import { useInstanceData } from '@/composables/useInstanceData'
|
||||||
|
import { useApiCodeViewer } from '@/composables/useApiCodeViewer'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'TemplateApiViewer',
|
name: 'TemplateApiViewer',
|
||||||
@@ -39,58 +40,67 @@ export default defineComponent({
|
|||||||
emit('update:open', value)
|
emit('update:open', value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 当前选中的标签
|
// 使用实例数据管理 composable
|
||||||
const activeTab = ref('curl')
|
const { hasDynamicRecipientInstance, enabledChannelNames } = useInstanceData(
|
||||||
|
'template',
|
||||||
|
toRef(props, 'templateData'),
|
||||||
|
toRef(props, 'open')
|
||||||
|
)
|
||||||
|
|
||||||
// 代码语言选项
|
// 使用 API 代码查看器 composable
|
||||||
const codeLanguages = [
|
const { activeTab, codeLanguages, copyToClipboard } = useApiCodeViewer()
|
||||||
{ value: 'curl', label: 'cURL', icon: '🌐' },
|
|
||||||
{ value: 'javascript', label: 'JS', icon: '🟨' },
|
// 可选参数选项
|
||||||
{ value: 'python', label: 'Python', icon: '🐍' },
|
const showRecipients = ref(false)
|
||||||
{ value: 'php', label: 'PHP', icon: '🐘' },
|
|
||||||
{ value: 'golang', label: 'Go', icon: '🐹' },
|
// 监听动态接收实例变化,自动勾选
|
||||||
{ value: 'java', label: 'Java', icon: '☕' },
|
watch(hasDynamicRecipientInstance, (newVal) => {
|
||||||
{ value: 'rust', label: 'Rust', icon: '🦀' }
|
if (newVal) {
|
||||||
]
|
showRecipients.value = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听弹窗关闭,重置状态
|
||||||
|
watch(() => props.open, (newVal) => {
|
||||||
|
if (!newVal) {
|
||||||
|
showRecipients.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// 生成API代码示例
|
// 生成API代码示例
|
||||||
const generateApiCode = (language: string) => {
|
const generateApiCode = (language: string) => {
|
||||||
const templateId = props.templateData?.id || 'TEMPLATE_ID'
|
const templateId = props.templateData?.id || 'TEMPLATE_ID'
|
||||||
const placeholders = props.templateData?.placeholders || '[]'
|
const placeholders = props.templateData?.placeholders || '[]'
|
||||||
|
const options = {
|
||||||
|
recipients: showRecipients.value
|
||||||
|
}
|
||||||
|
|
||||||
switch (language) {
|
switch (language) {
|
||||||
case 'curl':
|
case 'curl':
|
||||||
return TemplateApiStrGenerate.getCurlString(templateId, placeholders)
|
return TemplateApiStrGenerate.getCurlString(templateId, placeholders, options)
|
||||||
case 'javascript':
|
case 'javascript':
|
||||||
return TemplateApiStrGenerate.getNodeString(templateId, placeholders)
|
return TemplateApiStrGenerate.getNodeString(templateId, placeholders, options)
|
||||||
case 'python':
|
case 'python':
|
||||||
return TemplateApiStrGenerate.getPythonString(templateId, placeholders)
|
return TemplateApiStrGenerate.getPythonString(templateId, placeholders, options)
|
||||||
case 'php':
|
case 'php':
|
||||||
return TemplateApiStrGenerate.getPHPString(templateId, placeholders)
|
return TemplateApiStrGenerate.getPHPString(templateId, placeholders, options)
|
||||||
case 'golang':
|
case 'golang':
|
||||||
return TemplateApiStrGenerate.getGolangString(templateId, placeholders)
|
return TemplateApiStrGenerate.getGolangString(templateId, placeholders, options)
|
||||||
case 'java':
|
case 'java':
|
||||||
return TemplateApiStrGenerate.getJavaString(templateId, placeholders)
|
return TemplateApiStrGenerate.getJavaString(templateId, placeholders, options)
|
||||||
case 'rust':
|
case 'rust':
|
||||||
return TemplateApiStrGenerate.getRustString(templateId, placeholders)
|
return TemplateApiStrGenerate.getRustString(templateId, placeholders, options)
|
||||||
default:
|
default:
|
||||||
return '// 请选择一种编程语言查看示例代码'
|
return '// 请选择一种编程语言查看示例代码'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 复制代码到剪贴板
|
|
||||||
const copyToClipboard = async (text: string) => {
|
|
||||||
try {
|
|
||||||
await navigator.clipboard.writeText(text)
|
|
||||||
toast.success('复制成功')
|
|
||||||
} catch (err) {
|
|
||||||
toast.error('复制失败')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
handleUpdateOpen,
|
handleUpdateOpen,
|
||||||
activeTab,
|
activeTab,
|
||||||
|
hasDynamicRecipientInstance,
|
||||||
|
enabledChannelNames,
|
||||||
|
showRecipients,
|
||||||
codeLanguages,
|
codeLanguages,
|
||||||
generateApiCode,
|
generateApiCode,
|
||||||
copyToClipboard
|
copyToClipboard
|
||||||
@@ -123,6 +133,45 @@ export default defineComponent({
|
|||||||
<p><strong>可选参数:</strong> 根据模板配置的@提醒字段自动应用</p>
|
<p><strong>可选参数:</strong> 根据模板配置的@提醒字段自动应用</p>
|
||||||
<p class="text-amber-600 dark:text-amber-400"><strong>⚠️ 注意:</strong> V2接口使用加密token,不支持明文ID</p>
|
<p class="text-amber-600 dark:text-amber-400"><strong>⚠️ 注意:</strong> V2接口使用加密token,不支持明文ID</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 已启用的渠道列表 -->
|
||||||
|
<div v-if="enabledChannelNames.length > 0" class="mt-3 pt-3 border-t">
|
||||||
|
<p class="text-xs font-medium text-gray-700 dark:text-gray-300 mb-2">已启用的发送渠道:</p>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<Badge
|
||||||
|
v-for="(name, index) in enabledChannelNames"
|
||||||
|
:key="index"
|
||||||
|
variant="secondary"
|
||||||
|
class="text-xs"
|
||||||
|
>
|
||||||
|
{{ name }}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="mt-3 pt-3 border-t">
|
||||||
|
<p class="text-xs text-amber-600 dark:text-amber-400">⚠️ 该模板暂无启用的发送渠道</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 可选参数 -->
|
||||||
|
<div v-if="hasDynamicRecipientInstance" class="border rounded-lg p-4 bg-gray-50 dark:bg-slate-800/50">
|
||||||
|
<h3 class="font-semibold mb-3">可选参数</h3>
|
||||||
|
<div class="flex flex-wrap gap-4">
|
||||||
|
<label class="flex items-center gap-2 cursor-not-allowed opacity-75">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
v-model="showRecipients"
|
||||||
|
disabled
|
||||||
|
class="rounded cursor-not-allowed"
|
||||||
|
>
|
||||||
|
<span class="text-sm">动态接收者</span>
|
||||||
|
<Badge variant="secondary" class="text-xs">必填</Badge>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-1 text-xs text-gray-500 dark:text-gray-400 mt-3">
|
||||||
|
<p>📧 动态接收者:该模板配置了动态接收实例,发送时必须通过API指定接收者列表(群发模式)</p>
|
||||||
|
<p class="text-amber-600 dark:text-amber-400">⚠️ 此参数已自动勾选且不可取消,因为模板已配置动态接收实例</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 代码示例 -->
|
<!-- 代码示例 -->
|
||||||
|
|||||||
@@ -1,27 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, watch } from 'vue'
|
|
||||||
import { Button } from '@/components/ui/button'
|
|
||||||
import { Badge } from "@/components/ui/badge"
|
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||||
import EmptyTableState from '@/components/ui/EmptyTableState.vue'
|
import InstanceConfig from '@/components/ui/InstanceConfig.vue'
|
||||||
import {
|
|
||||||
Combobox,
|
|
||||||
ComboboxAnchor,
|
|
||||||
ComboboxInput,
|
|
||||||
ComboboxList,
|
|
||||||
ComboboxItem,
|
|
||||||
ComboboxViewport
|
|
||||||
} from '@/components/ui/combobox'
|
|
||||||
import { CheckIcon } from 'lucide-vue-next'
|
|
||||||
import { Input } from '@/components/ui/input'
|
|
||||||
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
|
|
||||||
import { Label } from '@/components/ui/label'
|
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
|
|
||||||
import { Switch } from '@/components/ui/switch'
|
|
||||||
import { CONSTANT } from '@/constant'
|
|
||||||
import { toast } from 'vue-sonner'
|
|
||||||
import { request } from '@/api/api'
|
|
||||||
import { generateBizUniqueID } from '@/util/uuid'
|
|
||||||
|
|
||||||
// 组件props
|
// 组件props
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -37,225 +16,6 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
defineEmits<{
|
defineEmits<{
|
||||||
'update:open': [value: boolean]
|
'update:open': [value: boolean]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
// 前端的页面添加配置
|
|
||||||
const waysConfigMap = CONSTANT.WAYS_DATA
|
|
||||||
|
|
||||||
// 搜索相关状态
|
|
||||||
const searchQuery = ref('')
|
|
||||||
const isSearching = ref(false)
|
|
||||||
const channelName = ref('')
|
|
||||||
|
|
||||||
// 当前显示的选项(搜索结果或所有选项)
|
|
||||||
const displayOptions = ref<Array<{ id: string, name: string, type: string }>>([])
|
|
||||||
|
|
||||||
// 输入框显示值(只在用户搜索时显示搜索内容)
|
|
||||||
const inputDisplayValue = computed({
|
|
||||||
get: () => searchQuery.value,
|
|
||||||
set: (value: string) => {
|
|
||||||
searchQuery.value = value
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 当前选中渠道的配置
|
|
||||||
const currentChannelConfig = computed(() => {
|
|
||||||
// 先根据label找到type
|
|
||||||
let type = displayOptions.value.find(item => item.name === channelName.value)?.type
|
|
||||||
// 再根据type找到配置
|
|
||||||
let rs = waysConfigMap.find(item => item.type === type) || null;
|
|
||||||
|
|
||||||
return rs;
|
|
||||||
})
|
|
||||||
|
|
||||||
// 表单数据
|
|
||||||
const formData = ref<Record<string, any>>({})
|
|
||||||
|
|
||||||
// 监听渠道变化
|
|
||||||
const handlechannelNameChange = () => {
|
|
||||||
// 数据加载后,text/html单选设置默认选中(这里选第一个)
|
|
||||||
if (currentChannelConfig.value?.taskInsRadios.length > 0) {
|
|
||||||
formData.value.templ_type = currentChannelConfig.value?.taskInsRadios[0].subLabel
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加单条实例配置
|
|
||||||
const handleAddSubmit = async () => {
|
|
||||||
// 验证是否选择了渠道
|
|
||||||
if (!channelName.value) {
|
|
||||||
toast.error('请选择发送渠道')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证内容类型
|
|
||||||
const contentType = formData.value.templ_type
|
|
||||||
if (!contentType) {
|
|
||||||
toast.error('请选择消息格式')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证模板对应格式的内容是否为空
|
|
||||||
const templateFieldMap: Record<string, string> = {
|
|
||||||
'text': 'text_template',
|
|
||||||
'html': 'html_template',
|
|
||||||
'markdown': 'markdown_template'
|
|
||||||
}
|
|
||||||
|
|
||||||
const fieldName = templateFieldMap[contentType.toLowerCase()]
|
|
||||||
if (fieldName) {
|
|
||||||
const templateContent = props.templateData?.[fieldName] || ''
|
|
||||||
// 检查是否为空(去除所有空白字符后检查)
|
|
||||||
if (!templateContent.trim()) {
|
|
||||||
toast.error(`模板的 ${contentType} 格式内容为空,无法添加此类型的实例`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 组建表单数据
|
|
||||||
let postData = {
|
|
||||||
"id": generateBizUniqueID('IN'),
|
|
||||||
"enable": 1,
|
|
||||||
"template_id": props.templateData.id,
|
|
||||||
"way_id": displayOptions.value[0]?.id,
|
|
||||||
"way_type": displayOptions.value[0]?.type,
|
|
||||||
"way_name": displayOptions.value[0]?.name,
|
|
||||||
"content_type": formData.value.templ_type,
|
|
||||||
"config": JSON.stringify(formData.value),
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await request.post('/templates/ins/addone', postData)
|
|
||||||
if (response.status === 200 && response.data.code === 200) {
|
|
||||||
toast.success(response.data.msg)
|
|
||||||
// 重新加载实例列表
|
|
||||||
await queryInsListData()
|
|
||||||
// 清空表单
|
|
||||||
channelName.value = ''
|
|
||||||
formData.value = {}
|
|
||||||
} else {
|
|
||||||
toast.error(response.data.msg || '添加实例失败')
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
toast.error(error.response?.data?.msg || '添加实例失败')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 实例表格数据
|
|
||||||
const insTableData = ref<any[]>([])
|
|
||||||
|
|
||||||
// 查询实例列表数据
|
|
||||||
const queryInsListData = async () => {
|
|
||||||
if (!props.templateData?.id) return
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await request.get('/templates/ins/get', {
|
|
||||||
params: { id: props.templateData.id }
|
|
||||||
})
|
|
||||||
if (response.status === 200 && response.data.code === 200) {
|
|
||||||
const insList = response.data.data.ins_list || []
|
|
||||||
insTableData.value = insList
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取实例列表失败', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除实例
|
|
||||||
const handleDeleteIns = async (insId: string) => {
|
|
||||||
try {
|
|
||||||
const response = await request.post('/sendtasks/ins/delete', { id: insId })
|
|
||||||
if (response.status === 200 && response.data.code === 200) {
|
|
||||||
toast.success(response.data.msg)
|
|
||||||
await queryInsListData()
|
|
||||||
} else {
|
|
||||||
toast.error(response.data.msg || '删除失败')
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
toast.error(error.response?.data?.msg || '删除失败')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 切换实例启用状态
|
|
||||||
const handleToggleEnable = async (insId: string, currentStatus: number | string) => {
|
|
||||||
const isEnabled = Number(currentStatus) === 1
|
|
||||||
const newStatus = isEnabled ? 0 : 1
|
|
||||||
|
|
||||||
// 立即更新本地状态,提供即时反馈
|
|
||||||
const insIndex = insTableData.value.findIndex(ins => ins.id === insId)
|
|
||||||
if (insIndex !== -1) {
|
|
||||||
insTableData.value[insIndex].enable = newStatus
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await request.post('/sendtasks/ins/update_enable', {
|
|
||||||
ins_id: insId,
|
|
||||||
status: newStatus
|
|
||||||
})
|
|
||||||
|
|
||||||
if (response.status === 200 && response.data.code === 200) {
|
|
||||||
toast.success(response.data.msg)
|
|
||||||
// 重新加载确保数据同步
|
|
||||||
await queryInsListData()
|
|
||||||
} else {
|
|
||||||
toast.error(response.data.msg || '更新失败')
|
|
||||||
// 失败时恢复原状态
|
|
||||||
if (insIndex !== -1) {
|
|
||||||
insTableData.value[insIndex].enable = currentStatus
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error('状态切换失败:', error)
|
|
||||||
toast.error(error.response?.data?.msg || '更新失败')
|
|
||||||
// 失败时恢复原状态
|
|
||||||
if (insIndex !== -1) {
|
|
||||||
insTableData.value[insIndex].enable = currentStatus
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 防抖定时器
|
|
||||||
let searchTimer: number | null = null
|
|
||||||
|
|
||||||
// 搜索渠道(带防抖)
|
|
||||||
const handleSearch = (query: string) => {
|
|
||||||
// 清除之前的定时器
|
|
||||||
if (searchTimer) {
|
|
||||||
clearTimeout(searchTimer)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!query.trim()) {
|
|
||||||
displayOptions.value = []
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置防抖延迟(500ms)
|
|
||||||
searchTimer = window.setTimeout(async () => {
|
|
||||||
isSearching.value = true
|
|
||||||
try {
|
|
||||||
const response = await request.get('/sendways/list', {
|
|
||||||
params: { name: query }
|
|
||||||
})
|
|
||||||
if (response.status === 200 && response.data.code === 200) {
|
|
||||||
displayOptions.value = response.data.data.lists.map((item: any) => ({
|
|
||||||
id: item.id,
|
|
||||||
name: item.name,
|
|
||||||
type: item.type
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('搜索渠道失败', error)
|
|
||||||
displayOptions.value = []
|
|
||||||
} finally {
|
|
||||||
isSearching.value = false
|
|
||||||
}
|
|
||||||
}, 500)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 监听对话框打开状态,打开时加载实例列表
|
|
||||||
watch(() => props.open, (newVal) => {
|
|
||||||
if (newVal && props.templateData?.id) {
|
|
||||||
queryInsListData()
|
|
||||||
}
|
|
||||||
}, { immediate: true })
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -265,120 +25,12 @@ watch(() => props.open, (newVal) => {
|
|||||||
<DialogTitle>配置发送实例</DialogTitle>
|
<DialogTitle>配置发送实例</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<div class="px-4 pb-4 flex-1 overflow-y-auto">
|
<div class="flex-1 overflow-y-auto">
|
||||||
<div class="space-y-4">
|
<InstanceConfig
|
||||||
|
type="template"
|
||||||
<!-- 模板信息 -->
|
:data="templateData"
|
||||||
<div class="mb-6 p-3 bg-muted rounded-lg space-y-1">
|
:in-dialog="true"
|
||||||
<div class="flex items-baseline gap-2">
|
/>
|
||||||
<span class="text-base font-semibold">{{ templateData?.name }}</span>
|
|
||||||
<Badge variant="outline" class="text-xs">{{ templateData?.id }}</Badge>
|
|
||||||
</div>
|
|
||||||
<div class="text-xs text-muted-foreground">为此模板配置发送实例</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 添加实例表单 -->
|
|
||||||
<div class="space-y-4">
|
|
||||||
<div class="flex items-end gap-2">
|
|
||||||
<div class="flex-1 space-y-2">
|
|
||||||
<Label class="text-sm font-medium">选择发送渠道</Label>
|
|
||||||
<Combobox v-model="channelName" @update:model-value="handlechannelNameChange">
|
|
||||||
<ComboboxAnchor class="w-full">
|
|
||||||
<ComboboxInput v-model="inputDisplayValue" @input="handleSearch(inputDisplayValue)"
|
|
||||||
class="flex h-10 w-full" placeholder="搜索或选择渠道类型进行实例的添加..." />
|
|
||||||
</ComboboxAnchor>
|
|
||||||
<ComboboxList class="w-[var(--reka-combobox-trigger-width)]">
|
|
||||||
<ComboboxViewport>
|
|
||||||
<ComboboxItem v-for="option in displayOptions" :key="option.id" :value="option.name">
|
|
||||||
<div class="flex items-center justify-between w-full">
|
|
||||||
<span>{{ option.name }}</span>
|
|
||||||
<CheckIcon v-if="channelName === option.name" class="h-4 w-4" />
|
|
||||||
</div>
|
|
||||||
</ComboboxItem>
|
|
||||||
<div v-if="isSearching" class="p-2 text-sm text-muted-foreground">搜索中...</div>
|
|
||||||
<div v-if="!isSearching && displayOptions.length === 0 && searchQuery" class="p-2 text-sm text-muted-foreground">
|
|
||||||
未找到匹配的渠道
|
|
||||||
</div>
|
|
||||||
</ComboboxViewport>
|
|
||||||
</ComboboxList>
|
|
||||||
</Combobox>
|
|
||||||
</div>
|
|
||||||
<Button size="sm" variant="outline" @click="handleAddSubmit">添加实例</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 渠道配置表单 -->
|
|
||||||
<div v-if="currentChannelConfig" class="mt-4">
|
|
||||||
<!-- 实例配置输入字段 -->
|
|
||||||
<div v-if="currentChannelConfig.taskInsInputs && currentChannelConfig.taskInsInputs.length > 0" class="mb-2">
|
|
||||||
<Label class="text-sm font-medium mb-1">实例配置</Label>
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
||||||
<div v-for="input in currentChannelConfig.taskInsInputs" :key="input.col" class="space-y-2">
|
|
||||||
<label class="text-xs font-medium text-muted-foreground">{{ input.label || input.desc }}</label>
|
|
||||||
<Input v-model="formData[input.col]" :placeholder="input.desc || `请输入${input.label}`"
|
|
||||||
:type="input.type || 'text'" class="w-full" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 单选框 -->
|
|
||||||
<div v-if="currentChannelConfig.taskInsRadios && currentChannelConfig.taskInsRadios.length > 0" class="mt-4">
|
|
||||||
<Label class="text-sm font-medium mb-2">消息格式</Label>
|
|
||||||
<RadioGroup v-model="formData.templ_type" class="flex gap-4">
|
|
||||||
<div v-for="radio in currentChannelConfig.taskInsRadios" :key="radio.subLabel" class="flex items-center space-x-2">
|
|
||||||
<RadioGroupItem :value="radio.subLabel" :id="radio.subLabel" />
|
|
||||||
<Label :for="radio.subLabel" class="text-sm cursor-pointer">{{ radio.subLabel }}</Label>
|
|
||||||
</div>
|
|
||||||
</RadioGroup>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 关联的实例表 -->
|
|
||||||
<div class="mt-4">
|
|
||||||
<h3 class="text-sm font-medium mb-3">已经关联的实例</h3>
|
|
||||||
<Table>
|
|
||||||
<TableHeader>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead>渠道名称</TableHead>
|
|
||||||
<TableHead>内容类型</TableHead>
|
|
||||||
<TableHead class="text-center">操作</TableHead>
|
|
||||||
</TableRow>
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
<TableRow v-for="ins in insTableData" :key="ins.id">
|
|
||||||
<TableCell>
|
|
||||||
<div class="font-medium">{{ ins.way_name || '未命名' }}</div>
|
|
||||||
<div class="text-xs text-muted-foreground">{{ ins.way_type }}</div>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<Badge variant="secondary">{{ ins.content_type }}</Badge>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell class="text-center">
|
|
||||||
<div class="flex items-center justify-center gap-2">
|
|
||||||
<Switch
|
|
||||||
:model-value="ins.enable === 1"
|
|
||||||
@update:model-value="() => handleToggleEnable(ins.id, ins.enable)"
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="outline"
|
|
||||||
class="text-red-500 border-red-300 hover:bg-red-50 hover:border-red-400 hover:text-red-600 hover:shadow-md transition-all duration-200"
|
|
||||||
@click="handleDeleteIns(ins.id)"
|
|
||||||
>
|
|
||||||
删除
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow v-if="!insTableData || insTableData.length === 0">
|
|
||||||
<TableCell :colspan="3" class="h-24">
|
|
||||||
<EmptyTableState title="暂无实例" description="还没有配置任何实例,请先添加" />
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { ref, defineComponent } from 'vue'
|
import { ref, defineComponent, watch, toRef } from 'vue'
|
||||||
import { toast } from 'vue-sonner'
|
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||||
import { Badge } from '@/components/ui/badge'
|
import { Badge } from '@/components/ui/badge'
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { ApiStrGenerate } from '@/util/viewApi'
|
import { ApiStrGenerate } from '@/util/viewApi'
|
||||||
|
import { useInstanceData } from '@/composables/useInstanceData'
|
||||||
|
import { useApiCodeViewer } from '@/composables/useApiCodeViewer'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'ApiCodeViewer',
|
name: 'ApiCodeViewer',
|
||||||
@@ -39,8 +40,15 @@ export default defineComponent({
|
|||||||
emit('update:open', value)
|
emit('update:open', value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 当前选中的标签
|
// 使用实例数据管理 composable
|
||||||
const activeTab = ref('curl')
|
const { hasDynamicRecipientInstance, enabledChannelNames } = useInstanceData(
|
||||||
|
'task',
|
||||||
|
toRef(props, 'taskData'),
|
||||||
|
toRef(props, 'open')
|
||||||
|
)
|
||||||
|
|
||||||
|
// 使用 API 代码查看器 composable
|
||||||
|
const { activeTab, codeLanguages, copyToClipboard } = useApiCodeViewer()
|
||||||
|
|
||||||
// 可选参数选项
|
// 可选参数选项
|
||||||
const showHtml = ref(false)
|
const showHtml = ref(false)
|
||||||
@@ -49,17 +57,21 @@ export default defineComponent({
|
|||||||
const showAtMobiles = ref(false)
|
const showAtMobiles = ref(false)
|
||||||
const showAtUserIds = ref(false)
|
const showAtUserIds = ref(false)
|
||||||
const showAtAll = ref(false)
|
const showAtAll = ref(false)
|
||||||
|
const showRecipients = ref(false)
|
||||||
// 代码语言选项
|
|
||||||
const codeLanguages = [
|
// 监听动态接收实例变化,自动勾选
|
||||||
{ value: 'curl', label: 'cURL', icon: '🌐' },
|
watch(hasDynamicRecipientInstance, (newVal) => {
|
||||||
{ value: 'javascript', label: 'JS', icon: '🟨' },
|
if (newVal) {
|
||||||
{ value: 'python', label: 'Python', icon: '🐍' },
|
showRecipients.value = true
|
||||||
{ value: 'php', label: 'PHP', icon: '🐘' },
|
}
|
||||||
{ value: 'golang', label: 'Go', icon: '🐹' },
|
})
|
||||||
{ value: 'java', label: 'Java', icon: '☕' },
|
|
||||||
{ value: 'rust', label: 'Rust', icon: '🦀' }
|
// 监听弹窗关闭,重置状态
|
||||||
]
|
watch(() => props.open, (newVal) => {
|
||||||
|
if (!newVal) {
|
||||||
|
showRecipients.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// 生成API代码示例
|
// 生成API代码示例
|
||||||
const generateApiCode = (language: string) => {
|
const generateApiCode = (language: string) => {
|
||||||
@@ -70,7 +82,8 @@ export default defineComponent({
|
|||||||
url: showUrl.value,
|
url: showUrl.value,
|
||||||
at_mobiles: showAtMobiles.value,
|
at_mobiles: showAtMobiles.value,
|
||||||
at_user_ids: showAtUserIds.value,
|
at_user_ids: showAtUserIds.value,
|
||||||
at_all: showAtAll.value
|
at_all: showAtAll.value,
|
||||||
|
recipients: showRecipients.value
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (language) {
|
switch (language) {
|
||||||
@@ -93,25 +106,18 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 复制代码到剪贴板
|
|
||||||
const copyToClipboard = async (text: string) => {
|
|
||||||
try {
|
|
||||||
await navigator.clipboard.writeText(text)
|
|
||||||
toast.success('复制成功')
|
|
||||||
} catch (err) {
|
|
||||||
toast.error('复制失败')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
handleUpdateOpen,
|
handleUpdateOpen,
|
||||||
activeTab,
|
activeTab,
|
||||||
|
hasDynamicRecipientInstance,
|
||||||
|
enabledChannelNames,
|
||||||
showHtml,
|
showHtml,
|
||||||
showMarkdown,
|
showMarkdown,
|
||||||
showUrl,
|
showUrl,
|
||||||
showAtMobiles,
|
showAtMobiles,
|
||||||
showAtUserIds,
|
showAtUserIds,
|
||||||
showAtAll,
|
showAtAll,
|
||||||
|
showRecipients,
|
||||||
codeLanguages,
|
codeLanguages,
|
||||||
generateApiCode,
|
generateApiCode,
|
||||||
copyToClipboard
|
copyToClipboard
|
||||||
@@ -132,16 +138,31 @@ export default defineComponent({
|
|||||||
|
|
||||||
<div class=" space-y-2">
|
<div class=" space-y-2">
|
||||||
<!-- API 信息概览 -->
|
<!-- API 信息概览 -->
|
||||||
|
<div class="border rounded-lg p-4 space-y-2 bg-white dark:bg-slate-900">
|
||||||
<!-- <div class="space-y-3">
|
<div class="flex items-center gap-2">
|
||||||
<div class="border rounded-lg p-4">
|
<Badge variant="default">POST</Badge>
|
||||||
<div class="flex items-center gap-2 mb-2">
|
<code class="text-sm bg-gray-100 dark:bg-slate-800 px-2 py-1 rounded">/api/v1/message/send</code>
|
||||||
<Badge variant="default">POST</Badge>
|
|
||||||
<code class="text-sm">/sendtasks/send</code>
|
|
||||||
</div>
|
|
||||||
<p class="text-sm text-gray-600">发送消息,创建新的消息</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div> -->
|
<p class="text-sm text-gray-600 dark:text-gray-400">发送消息到任务配置的渠道</p>
|
||||||
|
|
||||||
|
<!-- 已启用的渠道列表 -->
|
||||||
|
<div v-if="enabledChannelNames.length > 0" class="mt-3 pt-3 border-t">
|
||||||
|
<p class="text-xs font-medium text-gray-700 dark:text-gray-300 mb-2">已启用的发送渠道:</p>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<Badge
|
||||||
|
v-for="(name, index) in enabledChannelNames"
|
||||||
|
:key="index"
|
||||||
|
variant="secondary"
|
||||||
|
class="text-xs"
|
||||||
|
>
|
||||||
|
{{ name }}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="mt-3 pt-3 border-t">
|
||||||
|
<p class="text-xs text-amber-600 dark:text-amber-400">⚠️ 该任务暂无启用的发送渠道</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 可选参数 -->
|
<!-- 可选参数 -->
|
||||||
<div class="border rounded-lg p-4 space-y-3">
|
<div class="border rounded-lg p-4 space-y-3">
|
||||||
@@ -174,9 +195,23 @@ export default defineComponent({
|
|||||||
<span class="text-sm">@所有人</span>
|
<span class="text-sm">@所有人</span>
|
||||||
<Badge variant="secondary" class="text-xs">新</Badge>
|
<Badge variant="secondary" class="text-xs">新</Badge>
|
||||||
</label>
|
</label>
|
||||||
|
<label
|
||||||
|
v-if="hasDynamicRecipientInstance"
|
||||||
|
class="flex items-center gap-2 cursor-not-allowed opacity-75"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
v-model="showRecipients"
|
||||||
|
disabled
|
||||||
|
class="rounded cursor-not-allowed"
|
||||||
|
>
|
||||||
|
<span class="text-sm">动态接收者</span>
|
||||||
|
<Badge variant="secondary" class="text-xs">必填</Badge>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-1 text-xs text-gray-500 dark:text-gray-400">
|
<div class="space-y-1 text-xs text-gray-500 dark:text-gray-400">
|
||||||
<p>💡 提示:@功能仅钉钉和企业微信支持</p>
|
<p>💡 提示:@功能仅钉钉和企业微信支持</p>
|
||||||
|
<p v-if="hasDynamicRecipientInstance" class="text-amber-600 dark:text-amber-400">📧 动态接收者:该任务配置了动态接收实例,发送时必须通过API指定接收者列表(此参数已自动勾选且不可取消)</p>
|
||||||
<p>📋 发送顺序:实例配置的内容类型优先,若为空则按 <code class="bg-gray-100 dark:bg-gray-800 px-1 rounded">HTML → Markdown → Text</code> 顺序回退</p>
|
<p>📋 发送顺序:实例配置的内容类型优先,若为空则按 <code class="bg-gray-100 dark:bg-gray-800 px-1 rounded">HTML → Markdown → Text</code> 顺序回退</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,27 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, defineProps, withDefaults, onMounted } from 'vue'
|
import { defineProps, withDefaults } from 'vue'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Badge } from "@/components/ui/badge"
|
|
||||||
import EmptyTableState from '@/components/ui/EmptyTableState.vue'
|
|
||||||
import {
|
|
||||||
Combobox,
|
|
||||||
ComboboxAnchor,
|
|
||||||
ComboboxInput,
|
|
||||||
ComboboxList,
|
|
||||||
ComboboxItem,
|
|
||||||
ComboboxViewport
|
|
||||||
} from '@/components/ui/combobox'
|
|
||||||
import { CheckIcon } from 'lucide-vue-next'
|
|
||||||
import { Input } from '@/components/ui/input'
|
import { Input } from '@/components/ui/input'
|
||||||
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
|
|
||||||
import { Label } from '@/components/ui/label'
|
import { Label } from '@/components/ui/label'
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
|
import InstanceConfig from '@/components/ui/InstanceConfig.vue'
|
||||||
import { Switch } from '@/components/ui/switch'
|
|
||||||
import { CONSTANT } from '@/constant'
|
|
||||||
|
|
||||||
import { toast } from 'vue-sonner'
|
import { toast } from 'vue-sonner'
|
||||||
import { request } from '@/api/api'
|
import { request } from '@/api/api'
|
||||||
import { generateBizUniqueID } from '@/util/uuid'
|
|
||||||
|
|
||||||
// 组件props
|
// 组件props
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -33,198 +17,16 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
editData: null
|
editData: null
|
||||||
})
|
})
|
||||||
|
|
||||||
// 前端的页面添加配置
|
// 定义emits
|
||||||
const waysConfigMap = CONSTANT.WAYS_DATA
|
const emit = defineEmits(['update:open', 'save'])
|
||||||
|
|
||||||
// 搜索相关状态
|
|
||||||
const searchQuery = ref('')
|
|
||||||
const isSearching = ref(false)
|
|
||||||
|
|
||||||
|
|
||||||
const channelName = ref('')
|
|
||||||
|
|
||||||
// 当前显示的选项(搜索结果或所有选项)
|
|
||||||
const displayOptions = ref<Array<{ id: string, name: string, type: string }>>([])
|
|
||||||
|
|
||||||
// 输入框显示值(只在用户搜索时显示搜索内容)
|
|
||||||
const inputDisplayValue = computed({
|
|
||||||
get: () => searchQuery.value,
|
|
||||||
set: (value: string) => {
|
|
||||||
searchQuery.value = value
|
|
||||||
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 当前选中任务的配置
|
|
||||||
const currentChannelConfig = computed(() => {
|
|
||||||
// 先根据label找到type
|
|
||||||
let type = displayOptions.value.find(item => item.name === channelName.value)?.type
|
|
||||||
// 再根据type找到配置
|
|
||||||
let rs = waysConfigMap.find(item => item.type === type) || null;
|
|
||||||
|
|
||||||
return rs;
|
|
||||||
})
|
|
||||||
|
|
||||||
// 表单数据
|
|
||||||
const formData = ref<Record<string, any>>({})
|
|
||||||
|
|
||||||
// 监听任务模式变化
|
|
||||||
const handlechannelNameChange = () => {
|
|
||||||
// 数据加载后,text/html单选设置默认选中(这里选第一个)
|
|
||||||
if (currentChannelConfig.value?.taskInsRadios.length > 0) {
|
|
||||||
formData.value.taskInsRadio = currentChannelConfig.value?.taskInsRadios[0].subLabel
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加单条实例配置
|
|
||||||
const handleAddSubmit = async () => {
|
|
||||||
// 组建表单数据
|
|
||||||
let postData = {
|
|
||||||
"id": generateBizUniqueID('IN'),
|
|
||||||
"enable": 1,
|
|
||||||
"task_id": props.editData.id,
|
|
||||||
"way_id": displayOptions.value[0]?.id,
|
|
||||||
"way_type": displayOptions.value[0]?.type,
|
|
||||||
"way_name": displayOptions.value[0]?.name,
|
|
||||||
"content_type": formData.value.taskInsRadio,
|
|
||||||
"config": "{}"
|
|
||||||
};
|
|
||||||
const { taskInsRadio, ...configValue } = formData.value
|
|
||||||
if (configValue) {
|
|
||||||
postData.config = JSON.stringify(configValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
const rsp = await request.post('/sendtasks/ins/addone', postData);
|
|
||||||
if (await rsp.data.code == 200) {
|
|
||||||
// 动态添加表格第一行
|
|
||||||
const newItem = {
|
|
||||||
...postData,
|
|
||||||
created_by: '',
|
|
||||||
modified_by: '',
|
|
||||||
created_on: new Date().toISOString(),
|
|
||||||
modified_on: new Date().toISOString(),
|
|
||||||
extra: ''
|
|
||||||
};
|
|
||||||
insTableData.value.unshift(newItem);
|
|
||||||
toast.success(rsp.data.msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const insTableData = ref<Array<{
|
|
||||||
id: string;
|
|
||||||
created_by: string;
|
|
||||||
modified_by: string;
|
|
||||||
created_on: string;
|
|
||||||
modified_on: string;
|
|
||||||
task_id: string;
|
|
||||||
way_id: string;
|
|
||||||
way_type: string;
|
|
||||||
content_type: string;
|
|
||||||
config: string;
|
|
||||||
extra: string;
|
|
||||||
enable: number;
|
|
||||||
way_name: string;
|
|
||||||
}>>([]);
|
|
||||||
|
|
||||||
const queryInsListData = async () => {
|
|
||||||
let params = { id: props.editData.id };
|
|
||||||
const rsp = await request.get('/sendtasks/ins/gettask', { params: params });
|
|
||||||
insTableData.value = await rsp.data.data.ins_data;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 防抖定时器
|
|
||||||
let searchTimer: number | null = null
|
|
||||||
|
|
||||||
// 搜索处理函数(带防抖)
|
|
||||||
const handleSearch = (query: string) => {
|
|
||||||
// 清除之前的定时器
|
|
||||||
if (searchTimer) {
|
|
||||||
clearTimeout(searchTimer)
|
|
||||||
}
|
|
||||||
if (!query.trim()) {
|
|
||||||
displayOptions.value = []
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 设置防抖延迟(500ms)
|
|
||||||
searchTimer = window.setTimeout(async () => {
|
|
||||||
isSearching.value = true
|
|
||||||
try {
|
|
||||||
const results = await querySearchWayAsync(query)
|
|
||||||
displayOptions.value = results.map((item: any) => ({
|
|
||||||
id: item.id,
|
|
||||||
name: item.name,
|
|
||||||
type: item.type
|
|
||||||
}))
|
|
||||||
} catch (error) {
|
|
||||||
console.error('搜索失败:', error)
|
|
||||||
displayOptions.value = []
|
|
||||||
} finally {
|
|
||||||
isSearching.value = false
|
|
||||||
}
|
|
||||||
}, 500)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const querySearchWayAsync = async (query: string) => {
|
|
||||||
const params = { name: query }
|
|
||||||
const rsp = await request.get('/sendways/list', { params })
|
|
||||||
return rsp.data.data.lists
|
|
||||||
}
|
|
||||||
|
|
||||||
const getWayTypeText = (type: string) => {
|
|
||||||
const wayData = CONSTANT.WAYS_DATA.find(item => item.type === type)
|
|
||||||
return wayData ? wayData.label : type
|
|
||||||
}
|
|
||||||
|
|
||||||
// 切换启用状态
|
|
||||||
const toggleEnable = async (item: any) => {
|
|
||||||
const newStatus = item.enable === 1 ? 0 : 1
|
|
||||||
let postData = { ins_id: item.id, status: newStatus };
|
|
||||||
const rsp = await request.post('/sendtasks/ins/update_enable', postData);
|
|
||||||
if (await rsp.data.code == 200) {
|
|
||||||
toast.success(rsp.data.msg);
|
|
||||||
// 更新本地状态
|
|
||||||
item.enable = newStatus
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleDelete = async (item: any) => {
|
|
||||||
const rsp = await request.post('/sendtasks/ins/delete', { id: item.id });
|
|
||||||
if (rsp.status == 200) {
|
|
||||||
toast.success(rsp.data.msg);
|
|
||||||
insTableData.value = insTableData.value.filter((ins) => ins.id !== item.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleEditTask = async () => {
|
const handleEditTask = async () => {
|
||||||
let postData = { id: props.editData.id, name: props.editData.name };
|
let postData = { id: props.editData.id, name: props.editData.name }
|
||||||
const rsp = await request.post('/sendtasks/edit', postData);
|
const rsp = await request.post('/sendtasks/edit', postData)
|
||||||
if (await rsp.data.code == 200) {
|
if (await rsp.data.code == 200) {
|
||||||
toast.success(rsp.data.msg);
|
toast.success(rsp.data.msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 格式化额外信息列的值
|
|
||||||
const formatInsConfigDisplay = (row: any) => {
|
|
||||||
if (!row.config) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
if (["Email", "WeChatOFAccount"].includes(row.way_type)) {
|
|
||||||
let config = JSON.parse(row.config)
|
|
||||||
let info = `${config.to_account}`
|
|
||||||
return info
|
|
||||||
} else {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 组件挂载时加载实例配置列表
|
|
||||||
onMounted(() => {
|
|
||||||
queryInsListData();
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -236,111 +38,14 @@ onMounted(() => {
|
|||||||
<Button size="sm" variant="outline" class="w-full sm:w-auto sm:ml-auto" @click="handleEditTask">修改</Button>
|
<Button size="sm" variant="outline" class="w-full sm:w-auto sm:ml-auto" @click="handleEditTask">修改</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 实例配置组件 -->
|
||||||
|
<div class="p-4">
|
||||||
<div class="mt-4">
|
<InstanceConfig
|
||||||
<div class="flex items-end gap-2">
|
type="task"
|
||||||
<div class="flex-1 space-y-2">
|
:data="editData"
|
||||||
<Label class="text-sm font-medium">选择发送渠道</Label>
|
/>
|
||||||
<Combobox v-model="channelName" @update:model-value="handlechannelNameChange">
|
|
||||||
<ComboboxAnchor class="w-full">
|
|
||||||
<ComboboxInput v-model="inputDisplayValue" @input="handleSearch(inputDisplayValue)"
|
|
||||||
class="flex h-10 w-full" placeholder="搜索或选择渠道类型进行实例的添加..." />
|
|
||||||
</ComboboxAnchor>
|
|
||||||
<ComboboxList class="w-[var(--reka-combobox-trigger-width)]">
|
|
||||||
<ComboboxViewport>
|
|
||||||
<div v-if="isSearching" class="px-2 py-1.5 text-sm text-muted-foreground">
|
|
||||||
搜索中...
|
|
||||||
</div>
|
|
||||||
<div v-else-if="searchQuery && displayOptions.length === 0"
|
|
||||||
class="px-2 py-1.5 text-sm text-muted-foreground">
|
|
||||||
未找到匹配项
|
|
||||||
</div>
|
|
||||||
<ComboboxItem v-for="option in displayOptions" :key="option.id" :value="option.name"
|
|
||||||
class="relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50">
|
|
||||||
<CheckIcon class="mr-2 h-4 w-4" :class="channelName === option.name ? 'opacity-100' : 'opacity-0'" />
|
|
||||||
{{ option.name }}
|
|
||||||
</ComboboxItem>
|
|
||||||
</ComboboxViewport>
|
|
||||||
</ComboboxList>
|
|
||||||
</Combobox>
|
|
||||||
</div>
|
|
||||||
<Button size="sm" variant="outline" @click="handleAddSubmit">添加实例</Button>
|
|
||||||
</div>
|
|
||||||
<!-- 动态任务配置区域 -->
|
|
||||||
<div v-if="currentChannelConfig" class="mt-4">
|
|
||||||
<!-- 任务指令输入字段 -->
|
|
||||||
<div v-if="currentChannelConfig.taskInsInputs && currentChannelConfig.taskInsInputs.length > 0" class="mb-2">
|
|
||||||
<Label class="text-sm font-medium text-gray-700 mb-1">实例配置</Label>
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
||||||
<div v-for="input in currentChannelConfig.taskInsInputs" :key="input.col" class="space-y-2">
|
|
||||||
<label class="text-xs font-medium text-gray-600">{{ input.label }}</label>
|
|
||||||
<Input v-model="formData[input.col]" :placeholder="input.desc || `请输入${input.desc}`"
|
|
||||||
:type="input.type || 'text'" class="text-sm" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 任务指令单选项 -->
|
|
||||||
<div v-if="currentChannelConfig.taskInsRadios && currentChannelConfig.taskInsRadios.length > 0">
|
|
||||||
<Label class="text-sm font-medium text-gray-700 mb-1">消息格式</Label>
|
|
||||||
<RadioGroup v-model="formData.taskInsRadio" class="flex flex-wrap gap-4">
|
|
||||||
<div v-for="radio in currentChannelConfig.taskInsRadios" :key="radio.value"
|
|
||||||
class="flex items-center space-x-2">
|
|
||||||
<RadioGroupItem :value="radio.subLabel" :id="radio.subLabel" />
|
|
||||||
<Label :for="radio.value"
|
|
||||||
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
|
|
||||||
{{ radio.subLabel }}
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
</RadioGroup>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 关联的实例表 -->
|
|
||||||
<div class="mt-4">
|
|
||||||
<h3 class="text-sm font-medium text-gray-900 mb-3">已经关联的实例</h3>
|
|
||||||
<Table>
|
|
||||||
<TableHeader>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead>渠道名</TableHead>
|
|
||||||
<TableHead>渠道/内容</TableHead>
|
|
||||||
<TableHead class="text-center">操作</TableHead>
|
|
||||||
</TableRow>
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
<TableRow v-for="item in insTableData" :key="item.id">
|
|
||||||
<TableCell>{{ item.way_name }}</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<Badge variant="outline">
|
|
||||||
{{ getWayTypeText(item.way_type) }}
|
|
||||||
</Badge>
|
|
||||||
<Badge variant="outline">
|
|
||||||
{{ item.content_type }}
|
|
||||||
</Badge>
|
|
||||||
<Badge variant="outline" v-if="item.config !== '{}'">
|
|
||||||
{{ formatInsConfigDisplay(item) }}
|
|
||||||
</Badge>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell class="text-center">
|
|
||||||
<div class="flex items-center justify-center gap-2">
|
|
||||||
<Switch :model-value="item.enable === 1" @update:model-value="() => toggleEnable(item)" />
|
|
||||||
<Button size="sm" variant="outline" class="text-red-500 border-red-300 hover:bg-red-50
|
|
||||||
hover:border-red-400 hover:text-red-600 hover:shadow-md
|
|
||||||
transition-all duration-200" @click="() => handleDelete(item)">删除</Button>
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow v-if="!insTableData || insTableData.length === 0">
|
|
||||||
<TableCell :colspan="3" class="h-24">
|
|
||||||
<EmptyTableState title="暂无实例" description="还没有配置任何实例,请先添加" />
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|||||||
@@ -250,20 +250,48 @@ const saveButtonText = computed(() => {
|
|||||||
<!-- Radio Group -->
|
<!-- Radio Group -->
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<label class="text-lg font-medium mb-3 block">渠道类型</label>
|
<label class="text-lg font-medium mb-3 block">渠道类型</label>
|
||||||
<RadioGroup v-model="channelMode" @update:model-value="handleChannelModeChange" class="flex flex-wrap gap-4">
|
<RadioGroup v-model="channelMode" @update:model-value="handleChannelModeChange" class="flex flex-wrap gap-3">
|
||||||
<div v-for="option in channelModeOptions" :key="option.value" class="flex items-center space-x-2">
|
<div v-for="option in channelModeOptions" :key="option.value" class="flex items-center space-x-2">
|
||||||
<RadioGroupItem :value="option.value" :id="option.value" />
|
<RadioGroupItem :value="option.value" :id="option.value" />
|
||||||
<label :for="option.value"
|
<label :for="option.value"
|
||||||
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
|
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 flex items-center gap-1.5">
|
||||||
{{ option.label }}
|
{{ option.label }}
|
||||||
|
<span
|
||||||
|
v-if="waysConfigMap.find(item => item.type === option.value)?.dynamicRecipient?.support"
|
||||||
|
class="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-blue-100 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300"
|
||||||
|
:title="`支持${waysConfigMap.find(item => item.type === option.value)?.dynamicRecipient?.label || '动态接收者'}群发`"
|
||||||
|
>
|
||||||
|
群发
|
||||||
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
|
<p class="text-[11px] text-gray-500 dark:text-gray-400 mt-2.5 leading-relaxed">
|
||||||
|
💡 带"群发"标识的渠道支持动态接收者,可在 API 调用时指定多个接收账号
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<!-- 动态表单 -->
|
<!-- 动态表单 -->
|
||||||
<div v-if="currentChannelConfig" class="mt-6">
|
<div v-if="currentChannelConfig" class="mt-6">
|
||||||
|
<!-- 动态接收者支持提示 -->
|
||||||
|
<div
|
||||||
|
v-if="currentChannelConfig.dynamicRecipient?.support"
|
||||||
|
class="mb-4 p-2.5 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-md"
|
||||||
|
>
|
||||||
|
<div class="flex items-start gap-2">
|
||||||
|
<span class="text-blue-600 dark:text-blue-400 text-sm mt-0.5">📧</span>
|
||||||
|
<div class="flex-1 space-y-1">
|
||||||
|
<p class="text-xs text-blue-800 dark:text-blue-200 font-medium">
|
||||||
|
支持群发模式 - 可在配置实例时启用"动态接收者",通过 API 的 <code class="px-1 py-0.5 bg-blue-100 dark:bg-blue-800 rounded text-[11px]">recipients</code> 参数指定多个{{ currentChannelConfig.dynamicRecipient.label }}
|
||||||
|
</p>
|
||||||
|
<p class="text-[11px] text-blue-600 dark:text-blue-400">
|
||||||
|
适用:邮件群发、公众号批量推送、营销通知等
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 基本配置输入字段 -->
|
<!-- 基本配置输入字段 -->
|
||||||
<div v-if="currentChannelConfig.inputs && currentChannelConfig.inputs.length > 0" class="mb-8">
|
<div v-if="currentChannelConfig.inputs && currentChannelConfig.inputs.length > 0" class="mb-8">
|
||||||
<h4 class="text-base font-medium mb-4 text-gray-800">基本配置</h4>
|
<h4 class="text-base font-medium mb-4 text-gray-800">基本配置</h4>
|
||||||
|
|||||||
@@ -0,0 +1,519 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, watch } from 'vue'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { Badge } from "@/components/ui/badge"
|
||||||
|
import EmptyTableState from '@/components/ui/EmptyTableState.vue'
|
||||||
|
import {
|
||||||
|
Combobox,
|
||||||
|
ComboboxAnchor,
|
||||||
|
ComboboxInput,
|
||||||
|
ComboboxList,
|
||||||
|
ComboboxItem,
|
||||||
|
ComboboxViewport
|
||||||
|
} from '@/components/ui/combobox'
|
||||||
|
import { CheckIcon } from 'lucide-vue-next'
|
||||||
|
import { Input } from '@/components/ui/input'
|
||||||
|
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
|
||||||
|
import { Label } from '@/components/ui/label'
|
||||||
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
|
||||||
|
import { Switch } from '@/components/ui/switch'
|
||||||
|
import { CONSTANT } from '@/constant'
|
||||||
|
import { toast } from 'vue-sonner'
|
||||||
|
import { request } from '@/api/api'
|
||||||
|
import { generateBizUniqueID } from '@/util/uuid'
|
||||||
|
|
||||||
|
// 组件props
|
||||||
|
interface Props {
|
||||||
|
// 实例类型:'template' 或 'task'
|
||||||
|
type: 'template' | 'task'
|
||||||
|
// 关联的数据(模板数据或任务数据)
|
||||||
|
data: any
|
||||||
|
// 是否在对话框中显示(用于模板)
|
||||||
|
inDialog?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
inDialog: false
|
||||||
|
})
|
||||||
|
|
||||||
|
// API 配置映射
|
||||||
|
const apiConfig = computed(() => {
|
||||||
|
if (props.type === 'template') {
|
||||||
|
return {
|
||||||
|
addIns: '/templates/ins/addone',
|
||||||
|
getIns: '/templates/ins/get',
|
||||||
|
deleteIns: '/sendtasks/ins/delete',
|
||||||
|
updateEnable: '/sendtasks/ins/update_enable',
|
||||||
|
idField: 'template_id',
|
||||||
|
nameField: 'name'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
addIns: '/sendtasks/ins/addone',
|
||||||
|
getIns: '/sendtasks/ins/gettask',
|
||||||
|
deleteIns: '/sendtasks/ins/delete',
|
||||||
|
updateEnable: '/sendtasks/ins/update_enable',
|
||||||
|
idField: 'task_id',
|
||||||
|
nameField: 'name'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 前端的页面添加配置
|
||||||
|
const waysConfigMap = CONSTANT.WAYS_DATA
|
||||||
|
|
||||||
|
// 搜索相关状态
|
||||||
|
const searchQuery = ref('')
|
||||||
|
const isSearching = ref(false)
|
||||||
|
const channelName = ref('')
|
||||||
|
|
||||||
|
// 当前显示的选项(搜索结果或所有选项)
|
||||||
|
const displayOptions = ref<Array<{ id: string, name: string, type: string }>>([])
|
||||||
|
|
||||||
|
// 输入框显示值(只在用户搜索时显示搜索内容)
|
||||||
|
const inputDisplayValue = computed({
|
||||||
|
get: () => searchQuery.value,
|
||||||
|
set: (value: string) => {
|
||||||
|
searchQuery.value = value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 当前选中渠道的配置
|
||||||
|
const currentChannelConfig = computed(() => {
|
||||||
|
// 先根据label找到type
|
||||||
|
let type = displayOptions.value.find(item => item.name === channelName.value)?.type
|
||||||
|
// 再根据type找到配置
|
||||||
|
let rs = waysConfigMap.find(item => item.type === type) || null
|
||||||
|
|
||||||
|
return rs
|
||||||
|
})
|
||||||
|
|
||||||
|
// 表单数据
|
||||||
|
const formData = ref<Record<string, any>>({
|
||||||
|
allowMultiRecip: false // 默认false为固定模式,true为动态模式
|
||||||
|
})
|
||||||
|
|
||||||
|
// 是否显示接收者输入框
|
||||||
|
const shouldShowRecipientInput = computed(() => {
|
||||||
|
// 支持动态接收者 且 未勾选(固定模式)时显示输入框
|
||||||
|
return currentChannelConfig.value?.dynamicRecipient?.support && !formData.value.allowMultiRecip
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听渠道变化
|
||||||
|
const handlechannelNameChange = () => {
|
||||||
|
// 数据加载后,text/html单选设置默认选中(这里选第一个)
|
||||||
|
if (currentChannelConfig.value?.taskInsRadios.length > 0) {
|
||||||
|
formData.value.templ_type = currentChannelConfig.value?.taskInsRadios[0].subLabel
|
||||||
|
}
|
||||||
|
// 重置动态接收者设置
|
||||||
|
formData.value.allowMultiRecip = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加单条实例配置
|
||||||
|
const handleAddSubmit = async () => {
|
||||||
|
// 验证是否选择了渠道
|
||||||
|
if (!channelName.value) {
|
||||||
|
toast.error('请选择发送渠道')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查动态接收和固定接收不能混合使用
|
||||||
|
if (insTableData.value.length > 0) {
|
||||||
|
const hasDynamicInstance = insTableData.value.some(ins => {
|
||||||
|
try {
|
||||||
|
const config = JSON.parse(ins.config)
|
||||||
|
return config.allowMultiRecip === true
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const entityName = props.type === 'template' ? '模板' : '任务'
|
||||||
|
|
||||||
|
// 如果要添加动态接收实例,但已有其他实例
|
||||||
|
if (formData.value.allowMultiRecip === true) {
|
||||||
|
if (hasDynamicInstance) {
|
||||||
|
toast.error(`该${entityName}已存在动态接收实例,一个${entityName}只能配置一个动态接收实例`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (insTableData.value.length > 0) {
|
||||||
|
toast.error(`动态接收实例不能与固定接收实例混合使用,请先删除所有固定实例`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果要添加固定接收实例,但已有动态接收实例
|
||||||
|
if (formData.value.allowMultiRecip !== true && hasDynamicInstance) {
|
||||||
|
toast.error(`该${entityName}已配置动态接收实例,不能再添加固定接收实例`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证内容类型
|
||||||
|
const contentType = formData.value.templ_type
|
||||||
|
if (!contentType) {
|
||||||
|
toast.error('请选择消息格式')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 仅模板需要验证对应格式的内容是否为空
|
||||||
|
if (props.type === 'template') {
|
||||||
|
const templateFieldMap: Record<string, string> = {
|
||||||
|
'text': 'text_template',
|
||||||
|
'html': 'html_template',
|
||||||
|
'markdown': 'markdown_template'
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldName = templateFieldMap[contentType.toLowerCase()]
|
||||||
|
if (fieldName) {
|
||||||
|
const templateContent = props.data?.[fieldName] || ''
|
||||||
|
// 检查是否为空(去除所有空白字符后检查)
|
||||||
|
if (!templateContent.trim()) {
|
||||||
|
toast.error(`模板的 ${contentType} 格式内容为空,无法添加此类型的实例`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组建表单数据
|
||||||
|
let postData: Record<string, any> = {
|
||||||
|
"id": generateBizUniqueID('IN'),
|
||||||
|
"enable": 1,
|
||||||
|
[apiConfig.value.idField]: props.data.id,
|
||||||
|
"way_id": displayOptions.value[0]?.id,
|
||||||
|
"way_type": displayOptions.value[0]?.type,
|
||||||
|
"way_name": displayOptions.value[0]?.name,
|
||||||
|
"content_type": formData.value.templ_type,
|
||||||
|
"config": JSON.stringify(formData.value),
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await request.post(apiConfig.value.addIns, postData)
|
||||||
|
if (response.status === 200 && response.data.code === 200) {
|
||||||
|
toast.success(response.data.msg)
|
||||||
|
// 重新加载实例列表
|
||||||
|
await queryInsListData()
|
||||||
|
// 清空表单
|
||||||
|
channelName.value = ''
|
||||||
|
formData.value = { allowMultiRecip: false }
|
||||||
|
} else {
|
||||||
|
toast.error(response.data.msg || '添加实例失败')
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
toast.error(error.response?.data?.msg || '添加实例失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 实例表格数据
|
||||||
|
const insTableData = ref<any[]>([])
|
||||||
|
|
||||||
|
// 格式化额外信息列的值
|
||||||
|
const formatInsConfigDisplay = (row: any) => {
|
||||||
|
if (!row.config) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if (["Email", "WeChatOFAccount"].includes(row.way_type)) {
|
||||||
|
let config = JSON.parse(row.config)
|
||||||
|
// 检查是否为动态接收者模式
|
||||||
|
if (config.allowMultiRecip === true) {
|
||||||
|
return "动态接收"
|
||||||
|
}
|
||||||
|
// 固定模式,显示接收者
|
||||||
|
return config.to_account || ""
|
||||||
|
} else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询实例列表数据
|
||||||
|
const queryInsListData = async () => {
|
||||||
|
if (!props.data?.id) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await request.get(apiConfig.value.getIns, {
|
||||||
|
params: { id: props.data.id }
|
||||||
|
})
|
||||||
|
if (response.status === 200 && response.data.code === 200) {
|
||||||
|
// 模板返回 ins_list,任务返回 ins_data
|
||||||
|
const insList = response.data.data.ins_list || response.data.data.ins_data || []
|
||||||
|
insTableData.value = insList
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取实例列表失败', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除实例
|
||||||
|
const handleDeleteIns = async (insId: string) => {
|
||||||
|
try {
|
||||||
|
const response = await request.post(apiConfig.value.deleteIns, { id: insId })
|
||||||
|
if (response.status === 200 && response.data.code === 200) {
|
||||||
|
toast.success(response.data.msg)
|
||||||
|
await queryInsListData()
|
||||||
|
} else {
|
||||||
|
toast.error(response.data.msg || '删除失败')
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
toast.error(error.response?.data?.msg || '删除失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换实例启用状态
|
||||||
|
const handleToggleEnable = async (insId: string, currentStatus: number | string) => {
|
||||||
|
const isEnabled = Number(currentStatus) === 1
|
||||||
|
const newStatus = isEnabled ? 0 : 1
|
||||||
|
|
||||||
|
// 立即更新本地状态,提供即时反馈
|
||||||
|
const insIndex = insTableData.value.findIndex(ins => ins.id === insId)
|
||||||
|
if (insIndex !== -1) {
|
||||||
|
insTableData.value[insIndex].enable = newStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await request.post(apiConfig.value.updateEnable, {
|
||||||
|
ins_id: insId,
|
||||||
|
status: newStatus
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response.status === 200 && response.data.code === 200) {
|
||||||
|
toast.success(response.data.msg)
|
||||||
|
// 重新加载确保数据同步
|
||||||
|
await queryInsListData()
|
||||||
|
} else {
|
||||||
|
toast.error(response.data.msg || '更新失败')
|
||||||
|
// 失败时恢复原状态
|
||||||
|
if (insIndex !== -1) {
|
||||||
|
insTableData.value[insIndex].enable = currentStatus
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('状态切换失败:', error)
|
||||||
|
toast.error(error.response?.data?.msg || '更新失败')
|
||||||
|
// 失败时恢复原状态
|
||||||
|
if (insIndex !== -1) {
|
||||||
|
insTableData.value[insIndex].enable = currentStatus
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 防抖定时器
|
||||||
|
let searchTimer: number | null = null
|
||||||
|
|
||||||
|
// 搜索渠道(带防抖)
|
||||||
|
const handleSearch = (query: string) => {
|
||||||
|
// 清除之前的定时器
|
||||||
|
if (searchTimer) {
|
||||||
|
clearTimeout(searchTimer)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!query.trim()) {
|
||||||
|
displayOptions.value = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置防抖延迟(500ms)
|
||||||
|
searchTimer = window.setTimeout(async () => {
|
||||||
|
isSearching.value = true
|
||||||
|
try {
|
||||||
|
const response = await request.get('/sendways/list', {
|
||||||
|
params: { name: query }
|
||||||
|
})
|
||||||
|
if (response.status === 200 && response.data.code === 200) {
|
||||||
|
displayOptions.value = response.data.data.lists.map((item: any) => ({
|
||||||
|
id: item.id,
|
||||||
|
name: item.name,
|
||||||
|
type: item.type
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('搜索渠道失败', error)
|
||||||
|
displayOptions.value = []
|
||||||
|
} finally {
|
||||||
|
isSearching.value = false
|
||||||
|
}
|
||||||
|
}, 500)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听数据变化,自动加载实例列表
|
||||||
|
watch(() => props.data?.id, (newVal) => {
|
||||||
|
if (newVal) {
|
||||||
|
queryInsListData()
|
||||||
|
}
|
||||||
|
}, { immediate: true })
|
||||||
|
|
||||||
|
// 暴露方法供父组件调用
|
||||||
|
defineExpose({
|
||||||
|
queryInsListData
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="space-y-4" :class="{ 'px-4 pb-4': inDialog }">
|
||||||
|
<!-- 信息展示区域 -->
|
||||||
|
<div v-if="data" class="p-3 bg-muted rounded-lg space-y-1">
|
||||||
|
<div class="flex items-baseline gap-2">
|
||||||
|
<span class="text-base font-semibold">{{ data[apiConfig.nameField] }}</span>
|
||||||
|
<Badge variant="outline" class="text-xs">{{ data.id }}</Badge>
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-muted-foreground">
|
||||||
|
为此{{ type === 'template' ? '模板' : '任务' }}配置发送实例
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 添加实例表单 -->
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="flex items-end gap-2">
|
||||||
|
<div class="flex-1 space-y-2">
|
||||||
|
<Label class="text-sm font-medium">选择发送渠道</Label>
|
||||||
|
<Combobox v-model="channelName" @update:model-value="handlechannelNameChange">
|
||||||
|
<ComboboxAnchor class="w-full">
|
||||||
|
<ComboboxInput
|
||||||
|
v-model="inputDisplayValue"
|
||||||
|
@input="handleSearch(inputDisplayValue)"
|
||||||
|
class="flex h-10 w-full"
|
||||||
|
placeholder="搜索或选择渠道类型进行实例的添加..."
|
||||||
|
/>
|
||||||
|
</ComboboxAnchor>
|
||||||
|
<ComboboxList class="w-[var(--reka-combobox-trigger-width)]">
|
||||||
|
<ComboboxViewport>
|
||||||
|
<ComboboxItem v-for="option in displayOptions" :key="option.id" :value="option.name">
|
||||||
|
<div class="flex items-center justify-between w-full">
|
||||||
|
<span>{{ option.name }}</span>
|
||||||
|
<CheckIcon v-if="channelName === option.name" class="h-4 w-4" />
|
||||||
|
</div>
|
||||||
|
</ComboboxItem>
|
||||||
|
<div v-if="isSearching" class="p-2 text-sm text-muted-foreground">搜索中...</div>
|
||||||
|
<div v-if="!isSearching && displayOptions.length === 0 && searchQuery" class="p-2 text-sm text-muted-foreground">
|
||||||
|
未找到匹配的渠道
|
||||||
|
</div>
|
||||||
|
</ComboboxViewport>
|
||||||
|
</ComboboxList>
|
||||||
|
</Combobox>
|
||||||
|
</div>
|
||||||
|
<Button size="sm" variant="outline" @click="handleAddSubmit">添加实例</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 渠道配置表单 -->
|
||||||
|
<div v-if="currentChannelConfig" class="mt-4">
|
||||||
|
<!-- 动态接收者勾选框 -->
|
||||||
|
<div v-if="currentChannelConfig.dynamicRecipient?.support" class="mb-4 p-3 border rounded-lg bg-gray-50 dark:bg-gray-800/50">
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<Switch
|
||||||
|
:model-value="formData.allowMultiRecip"
|
||||||
|
@update:model-value="(val: boolean) => formData.allowMultiRecip = val"
|
||||||
|
:id="`allow-multi-${channelName}`"
|
||||||
|
/>
|
||||||
|
<Label :for="`allow-multi-${channelName}`" class="text-sm font-medium cursor-pointer">
|
||||||
|
动态接收者模式
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1 ml-8">
|
||||||
|
{{ formData.allowMultiRecip ? '支持动态接收者,发送时通过API指定接收者列表(群发模式)' : '固定接收者模式,需要在下方配置固定接收者' }}
|
||||||
|
</p>
|
||||||
|
<p v-if="formData.allowMultiRecip" class="text-xs text-orange-500 dark:text-orange-400 mt-1 ml-8 font-medium">
|
||||||
|
⚠️ 注意:一个{{ type === 'template' ? '模板' : '任务' }}只能配置一个动态接收实例,且不能与固定接收实例混合使用
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 接收者输入字段 -->
|
||||||
|
<div v-if="shouldShowRecipientInput" class="mb-2">
|
||||||
|
<Label class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">实例配置</Label>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div class="space-y-2">
|
||||||
|
<label class="text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||||
|
{{ currentChannelConfig.dynamicRecipient.label }}
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
v-model="formData[currentChannelConfig.dynamicRecipient.field]"
|
||||||
|
:placeholder="`请输入${currentChannelConfig.dynamicRecipient.desc}`"
|
||||||
|
type="text"
|
||||||
|
class="text-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 实例配置输入字段(排除动态接收者字段) -->
|
||||||
|
<div v-if="currentChannelConfig.taskInsInputs && currentChannelConfig.taskInsInputs.length > 0" class="mb-2">
|
||||||
|
<Label class="text-sm font-medium mb-1">实例配置</Label>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div
|
||||||
|
v-for="input in currentChannelConfig.taskInsInputs.filter((inp: any) => inp.col !== currentChannelConfig?.dynamicRecipient?.field)"
|
||||||
|
:key="input.col"
|
||||||
|
class="space-y-2"
|
||||||
|
>
|
||||||
|
<label class="text-xs font-medium text-muted-foreground">{{ input.label || input.desc }}</label>
|
||||||
|
<Input
|
||||||
|
v-model="formData[input.col]"
|
||||||
|
:placeholder="input.desc || `请输入${input.label}`"
|
||||||
|
:type="input.type || 'text'"
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 单选框 -->
|
||||||
|
<div v-if="currentChannelConfig.taskInsRadios && currentChannelConfig.taskInsRadios.length > 0" class="mt-4">
|
||||||
|
<Label class="text-sm font-medium mb-2">消息格式</Label>
|
||||||
|
<RadioGroup v-model="formData.templ_type" class="flex gap-4">
|
||||||
|
<div v-for="radio in currentChannelConfig.taskInsRadios" :key="radio.subLabel" class="flex items-center space-x-2">
|
||||||
|
<RadioGroupItem :value="radio.subLabel" :id="radio.subLabel" />
|
||||||
|
<Label :for="radio.subLabel" class="text-sm cursor-pointer">{{ radio.subLabel }}</Label>
|
||||||
|
</div>
|
||||||
|
</RadioGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 关联的实例表 -->
|
||||||
|
<div class="mt-4">
|
||||||
|
<h3 class="text-sm font-medium mb-3">已经关联的实例</h3>
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>渠道名称</TableHead>
|
||||||
|
<TableHead>内容类型</TableHead>
|
||||||
|
<TableHead>接收者</TableHead>
|
||||||
|
<TableHead class="text-center">操作</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
<TableRow v-for="ins in insTableData" :key="ins.id">
|
||||||
|
<TableCell>
|
||||||
|
<div class="font-medium">{{ ins.way_name || '未命名' }}</div>
|
||||||
|
<div class="text-xs text-muted-foreground">{{ ins.way_type }}</div>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Badge variant="secondary">{{ ins.content_type }}</Badge>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<span class="text-sm">{{ formatInsConfigDisplay(ins) }}</span>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell class="text-center">
|
||||||
|
<div class="flex items-center justify-center gap-2">
|
||||||
|
<Switch
|
||||||
|
:model-value="ins.enable === 1"
|
||||||
|
@update:model-value="() => handleToggleEnable(ins.id, ins.enable)"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
class="text-red-500 border-red-300 hover:bg-red-50 hover:border-red-400 hover:text-red-600 hover:shadow-md transition-all duration-200"
|
||||||
|
@click="handleDeleteIns(ins.id)"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow v-if="!insTableData || insTableData.length === 0">
|
||||||
|
<TableCell :colspan="4" class="h-24">
|
||||||
|
<EmptyTableState title="暂无实例" description="还没有配置任何实例,请先添加" />
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { ref } from 'vue'
|
||||||
|
import { toast } from 'vue-sonner'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API 代码查看器公共逻辑 Composable
|
||||||
|
*/
|
||||||
|
export function useApiCodeViewer() {
|
||||||
|
// 当前选中的标签
|
||||||
|
const activeTab = ref('curl')
|
||||||
|
|
||||||
|
// 代码语言选项
|
||||||
|
const codeLanguages = [
|
||||||
|
{ value: 'curl', label: 'cURL', icon: '🌐' },
|
||||||
|
{ value: 'javascript', label: 'JS', icon: '🟨' },
|
||||||
|
{ value: 'python', label: 'Python', icon: '🐍' },
|
||||||
|
{ value: 'php', label: 'PHP', icon: '🐘' },
|
||||||
|
{ value: 'golang', label: 'Go', icon: '🐹' },
|
||||||
|
{ value: 'java', label: 'Java', icon: '☕' },
|
||||||
|
{ value: 'rust', label: 'Rust', icon: '🦀' }
|
||||||
|
]
|
||||||
|
|
||||||
|
// 复制代码到剪贴板
|
||||||
|
const copyToClipboard = async (text: string) => {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(text)
|
||||||
|
toast.success('复制成功')
|
||||||
|
} catch (err) {
|
||||||
|
toast.error('复制失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
activeTab,
|
||||||
|
codeLanguages,
|
||||||
|
copyToClipboard
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import { ref, computed, watch } from 'vue'
|
||||||
|
import { request } from '@/api/api'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实例数据管理 Composable
|
||||||
|
* 用于任务和模板的实例数据加载和处理
|
||||||
|
*/
|
||||||
|
export function useInstanceData(
|
||||||
|
type: 'task' | 'template',
|
||||||
|
dataRef: any,
|
||||||
|
openRef: any
|
||||||
|
) {
|
||||||
|
// 实例数据
|
||||||
|
const instances = ref<any[]>([])
|
||||||
|
|
||||||
|
// 检查是否有支持动态接收者的实例
|
||||||
|
const hasDynamicRecipientInstance = computed(() => {
|
||||||
|
if (instances.value && Array.isArray(instances.value)) {
|
||||||
|
return instances.value.some((ins: any) => {
|
||||||
|
try {
|
||||||
|
const config = typeof ins.config === 'string' ? JSON.parse(ins.config) : ins.config
|
||||||
|
return config?.allowMultiRecip === true
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取已启用的实例渠道名称列表
|
||||||
|
const enabledChannelNames = computed(() => {
|
||||||
|
if (instances.value && Array.isArray(instances.value)) {
|
||||||
|
return instances.value
|
||||||
|
.filter((ins: any) => ins.enable === 1)
|
||||||
|
.map((ins: any) => ins.way_name)
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
})
|
||||||
|
|
||||||
|
// 加载实例数据
|
||||||
|
const loadInstances = async () => {
|
||||||
|
const id = dataRef.value?.id
|
||||||
|
if (!id) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
let response
|
||||||
|
if (type === 'template') {
|
||||||
|
response = await request.get('/templates/ins/get', {
|
||||||
|
params: { id }
|
||||||
|
})
|
||||||
|
instances.value = response.data.data.ins_list || []
|
||||||
|
} else {
|
||||||
|
response = await request.get('/sendtasks/ins/gettask', {
|
||||||
|
params: { id }
|
||||||
|
})
|
||||||
|
instances.value = response.data.data.ins_data || []
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`加载${type === 'template' ? '模板' : '任务'}实例失败:`, err)
|
||||||
|
instances.value = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听弹窗打开状态
|
||||||
|
watch(
|
||||||
|
() => openRef.value,
|
||||||
|
async (newVal: boolean) => {
|
||||||
|
if (newVal) {
|
||||||
|
await loadInstances()
|
||||||
|
} else {
|
||||||
|
instances.value = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
instances,
|
||||||
|
hasDynamicRecipientInstance,
|
||||||
|
enabledChannelNames,
|
||||||
|
loadInstances
|
||||||
|
}
|
||||||
|
}
|
||||||
+37
-3
@@ -20,6 +20,13 @@ const CONSTANT = {
|
|||||||
{
|
{
|
||||||
type: 'Email',
|
type: 'Email',
|
||||||
label: '邮箱',
|
label: '邮箱',
|
||||||
|
// 动态接收者配置
|
||||||
|
dynamicRecipient: {
|
||||||
|
support: true, // 是否支持动态接收者
|
||||||
|
field: 'to_account', // 接收者字段名
|
||||||
|
label: '收件邮箱', // 接收者字段标签
|
||||||
|
desc: '邮箱地址', // 接收者字段描述
|
||||||
|
},
|
||||||
inputs: [
|
inputs: [
|
||||||
{ subLabel: 'smtp服务地址', value: '', col: 'server', desc: "smtp@xyz.com" },
|
{ subLabel: 'smtp服务地址', value: '', col: 'server', desc: "smtp@xyz.com" },
|
||||||
{ subLabel: 'smtp服务端口', value: '', col: 'port', desc: "port" },
|
{ subLabel: 'smtp服务端口', value: '', col: 'port', desc: "port" },
|
||||||
@@ -32,8 +39,7 @@ const CONSTANT = {
|
|||||||
{ subLabel: 'html', content: 'html' },
|
{ subLabel: 'html', content: 'html' },
|
||||||
],
|
],
|
||||||
taskInsInputs: [
|
taskInsInputs: [
|
||||||
{ value: '', col: 'to_account', desc: "目的邮箱账号(发给谁)" },
|
{ value: '', col: 'to_account', desc: '收件邮箱', label: '收件邮箱' },
|
||||||
// { value: '', col: 'title', desc: "邮箱标题" },
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -90,6 +96,13 @@ const CONSTANT = {
|
|||||||
{
|
{
|
||||||
type: 'WeChatOFAccount',
|
type: 'WeChatOFAccount',
|
||||||
label: '微信测试公众号模板',
|
label: '微信测试公众号模板',
|
||||||
|
// 动态接收者配置
|
||||||
|
dynamicRecipient: {
|
||||||
|
support: true, // 是否支持动态接收者
|
||||||
|
field: 'to_account', // 接收者字段名
|
||||||
|
label: '接收者OpenId', // 接收者字段标签
|
||||||
|
desc: 'OpenId', // 接收者字段描述
|
||||||
|
},
|
||||||
inputs: [
|
inputs: [
|
||||||
{ subLabel: 'appID', value: '', col: 'appID', desc: "公众号appid" },
|
{ subLabel: 'appID', value: '', col: 'appID', desc: "公众号appid" },
|
||||||
{ subLabel: 'appsecret', value: '', col: 'appsecret', desc: "公众号appsecret" },
|
{ subLabel: 'appsecret', value: '', col: 'appsecret', desc: "公众号appsecret" },
|
||||||
@@ -103,7 +116,7 @@ const CONSTANT = {
|
|||||||
{ subLabel: 'text', content: 'text' },
|
{ subLabel: 'text', content: 'text' },
|
||||||
],
|
],
|
||||||
taskInsInputs: [
|
taskInsInputs: [
|
||||||
{ value: '', col: 'to_account', desc: "要发送的OpenId(登录微信公众号后台查看)" },
|
{ value: '', col: 'to_account', desc: '接收者OpenId', label: '接收者OpenId' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -121,6 +134,27 @@ const CONSTANT = {
|
|||||||
taskInsInputs: [
|
taskInsInputs: [
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
// 暂时屏蔽阿里云短信入口
|
||||||
|
// {
|
||||||
|
// type: 'AliyunSMS',
|
||||||
|
// label: '阿里云短信',
|
||||||
|
// inputs: [
|
||||||
|
// { subLabel: 'AccessKeyId', value: '', col: 'access_key_id', desc: "阿里云AccessKeyId" },
|
||||||
|
// { subLabel: 'AccessKeySecret', value: '', col: 'access_key_secret', desc: "阿里云AccessKeySecret" },
|
||||||
|
// { subLabel: '短信签名', value: '', col: 'sign_name', desc: "短信签名名称" },
|
||||||
|
// { subLabel: '渠道名', value: '', col: 'name', desc: "想要设置的渠道名字" },
|
||||||
|
// ],
|
||||||
|
// tips: {
|
||||||
|
// text: "阿里云短信说明", desc: "使用阿里云短信服务发送短信,需要在阿里云控制台申请短信签名和模板。<br />AccessKey请在阿里云控制台获取。"
|
||||||
|
// },
|
||||||
|
// taskInsRadios: [
|
||||||
|
// { subLabel: 'text', content: 'text' },
|
||||||
|
// ],
|
||||||
|
// taskInsInputs: [
|
||||||
|
// { value: '', col: 'phone_number', desc: "手机号码(接收短信的手机号)" },
|
||||||
|
// { value: '', col: 'template_code', desc: "短信模板CODE(在阿里云短信控制台获取)" },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
],
|
],
|
||||||
API_VIEW_DATA: [
|
API_VIEW_DATA: [
|
||||||
{ label: "curl", class: "language-shell line-numbers", code: "", func: ApiStrGenerate.getCurlString },
|
{ label: "curl", class: "language-shell line-numbers", code: "", func: ApiStrGenerate.getCurlString },
|
||||||
|
|||||||
+23
-15
@@ -179,6 +179,8 @@ class ApiStrGenerate {
|
|||||||
if (options.at_mobiles) data.at_mobiles = ['13800138000', '13900139000'];
|
if (options.at_mobiles) data.at_mobiles = ['13800138000', '13900139000'];
|
||||||
if (options.at_user_ids) data.at_user_ids = ['zhangsan', 'lisi'];
|
if (options.at_user_ids) data.at_user_ids = ['zhangsan', 'lisi'];
|
||||||
if (options.at_all) data.at_all = true;
|
if (options.at_all) data.at_all = true;
|
||||||
|
// 动态接收者字段(用于邮箱、微信公众号等支持动态接收者的渠道)
|
||||||
|
if (options.recipients) data.recipients = ['user1@example.com', 'user2@example.com'];
|
||||||
return JSON.stringify(data, null, 4);
|
return JSON.stringify(data, null, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,7 +220,7 @@ class ApiStrGenerate {
|
|||||||
// ==================== 模板 API (V2) ====================
|
// ==================== 模板 API (V2) ====================
|
||||||
|
|
||||||
class TemplateApiStrGenerate {
|
class TemplateApiStrGenerate {
|
||||||
static getTemplateDataString(template_id, placeholders_json) {
|
static getTemplateDataString(template_id, placeholders_json, options = {}) {
|
||||||
// 解析占位符配置
|
// 解析占位符配置
|
||||||
let placeholders = {};
|
let placeholders = {};
|
||||||
try {
|
try {
|
||||||
@@ -241,6 +243,12 @@ class TemplateApiStrGenerate {
|
|||||||
title: 'message title',
|
title: 'message title',
|
||||||
placeholders: placeholders
|
placeholders: placeholders
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 添加动态接收者字段(如果需要)
|
||||||
|
if (options.recipients) {
|
||||||
|
data.recipients = ['user1@example.com', 'user2@example.com'];
|
||||||
|
}
|
||||||
|
|
||||||
return JSON.stringify(data, null, 4);
|
return JSON.stringify(data, null, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,32 +256,32 @@ class TemplateApiStrGenerate {
|
|||||||
return `${gethttpOrigin()}/api/v2/message/send`;
|
return `${gethttpOrigin()}/api/v2/message/send`;
|
||||||
}
|
}
|
||||||
|
|
||||||
static getCurlString(template_id, placeholders_json) {
|
static getCurlString(template_id, placeholders_json, options = {}) {
|
||||||
return CodeTemplates.getCurl(this.getApiUrl(), this.getTemplateDataString(template_id, placeholders_json));
|
return CodeTemplates.getCurl(this.getApiUrl(), this.getTemplateDataString(template_id, placeholders_json, options));
|
||||||
}
|
}
|
||||||
|
|
||||||
static getGolangString(template_id, placeholders_json) {
|
static getGolangString(template_id, placeholders_json, options = {}) {
|
||||||
return CodeTemplates.getGolang(this.getApiUrl(), this.getTemplateDataString(template_id, placeholders_json));
|
return CodeTemplates.getGolang(this.getApiUrl(), this.getTemplateDataString(template_id, placeholders_json, options));
|
||||||
}
|
}
|
||||||
|
|
||||||
static getPythonString(template_id, placeholders_json) {
|
static getPythonString(template_id, placeholders_json, options = {}) {
|
||||||
return CodeTemplates.getPython(this.getApiUrl(), this.getTemplateDataString(template_id, placeholders_json));
|
return CodeTemplates.getPython(this.getApiUrl(), this.getTemplateDataString(template_id, placeholders_json, options));
|
||||||
}
|
}
|
||||||
|
|
||||||
static getJavaString(template_id, placeholders_json) {
|
static getJavaString(template_id, placeholders_json, options = {}) {
|
||||||
return CodeTemplates.getJava(this.getApiUrl(), this.getTemplateDataString(template_id, placeholders_json));
|
return CodeTemplates.getJava(this.getApiUrl(), this.getTemplateDataString(template_id, placeholders_json, options));
|
||||||
}
|
}
|
||||||
|
|
||||||
static getRustString(template_id, placeholders_json) {
|
static getRustString(template_id, placeholders_json, options = {}) {
|
||||||
return CodeTemplates.getRust(this.getApiUrl(), this.getTemplateDataString(template_id, placeholders_json));
|
return CodeTemplates.getRust(this.getApiUrl(), this.getTemplateDataString(template_id, placeholders_json, options));
|
||||||
}
|
}
|
||||||
|
|
||||||
static getPHPString(template_id, placeholders_json) {
|
static getPHPString(template_id, placeholders_json, options = {}) {
|
||||||
return CodeTemplates.getPHP(this.getApiUrl(), this.getTemplateDataString(template_id, placeholders_json));
|
return CodeTemplates.getPHP(this.getApiUrl(), this.getTemplateDataString(template_id, placeholders_json, options));
|
||||||
}
|
}
|
||||||
|
|
||||||
static getNodeString(template_id, placeholders_json) {
|
static getNodeString(template_id, placeholders_json, options = {}) {
|
||||||
return CodeTemplates.getNode(this.getApiUrl(), this.getTemplateDataString(template_id, placeholders_json));
|
return CodeTemplates.getNode(this.getApiUrl(), this.getTemplateDataString(template_id, placeholders_json, options));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user