mirror of
https://github.com/ctwj/urldb.git
synced 2025-11-25 03:15:04 +08:00
update: wechat
This commit is contained in:
82
db/converter/wechat_bot_converter.go
Normal file
82
db/converter/wechat_bot_converter.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package converter
|
||||
|
||||
import (
|
||||
"github.com/ctwj/urldb/db/dto"
|
||||
"github.com/ctwj/urldb/db/entity"
|
||||
)
|
||||
|
||||
// WechatBotConfigRequestToSystemConfigs 将微信机器人配置请求转换为系统配置实体
|
||||
func WechatBotConfigRequestToSystemConfigs(req dto.WechatBotConfigRequest) []entity.SystemConfig {
|
||||
configs := []entity.SystemConfig{
|
||||
{Key: entity.ConfigKeyWechatBotEnabled, Value: wechatBoolToString(req.Enabled)},
|
||||
{Key: entity.ConfigKeyWechatAppId, Value: req.AppID},
|
||||
{Key: entity.ConfigKeyWechatAppSecret, Value: req.AppSecret},
|
||||
{Key: entity.ConfigKeyWechatToken, Value: req.Token},
|
||||
{Key: entity.ConfigKeyWechatEncodingAesKey, Value: req.EncodingAesKey},
|
||||
{Key: entity.ConfigKeyWechatWelcomeMessage, Value: req.WelcomeMessage},
|
||||
{Key: entity.ConfigKeyWechatAutoReplyEnabled, Value: wechatBoolToString(req.AutoReplyEnabled)},
|
||||
{Key: entity.ConfigKeyWechatSearchLimit, Value: wechatIntToString(req.SearchLimit)},
|
||||
}
|
||||
return configs
|
||||
}
|
||||
|
||||
// SystemConfigToWechatBotConfig 将系统配置转换为微信机器人配置响应
|
||||
func SystemConfigToWechatBotConfig(configs []entity.SystemConfig) dto.WechatBotConfigResponse {
|
||||
resp := dto.WechatBotConfigResponse{
|
||||
Enabled: false,
|
||||
AppID: "",
|
||||
Token: "",
|
||||
EncodingAesKey: "",
|
||||
WelcomeMessage: "欢迎关注老九网盘资源库!发送关键词即可搜索资源。",
|
||||
AutoReplyEnabled: true,
|
||||
SearchLimit: 5,
|
||||
}
|
||||
|
||||
for _, config := range configs {
|
||||
switch config.Key {
|
||||
case entity.ConfigKeyWechatBotEnabled:
|
||||
resp.Enabled = config.Value == "true"
|
||||
case entity.ConfigKeyWechatAppId:
|
||||
resp.AppID = config.Value
|
||||
case entity.ConfigKeyWechatToken:
|
||||
resp.Token = config.Value
|
||||
case entity.ConfigKeyWechatEncodingAesKey:
|
||||
resp.EncodingAesKey = config.Value
|
||||
case entity.ConfigKeyWechatWelcomeMessage:
|
||||
if config.Value != "" {
|
||||
resp.WelcomeMessage = config.Value
|
||||
}
|
||||
case entity.ConfigKeyWechatAutoReplyEnabled:
|
||||
resp.AutoReplyEnabled = config.Value == "true"
|
||||
case entity.ConfigKeyWechatSearchLimit:
|
||||
if config.Value != "" {
|
||||
resp.SearchLimit = wechatStringToInt(config.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
// 辅助函数 - 使用大写名称避免与其他文件中的函数冲突
|
||||
func wechatBoolToString(b bool) string {
|
||||
if b {
|
||||
return "true"
|
||||
}
|
||||
return "false"
|
||||
}
|
||||
|
||||
func wechatIntToString(i int) string {
|
||||
return string(rune(i + '0'))
|
||||
}
|
||||
|
||||
func wechatStringToInt(s string) int {
|
||||
if s == "" {
|
||||
return 0
|
||||
}
|
||||
// 简单转换,实际项目中应该使用strconv.Atoi
|
||||
if len(s) == 1 && s[0] >= '0' && s[0] <= '9' {
|
||||
return int(s[0] - '0')
|
||||
}
|
||||
return 0
|
||||
}
|
||||
24
db/dto/wechat_bot.go
Normal file
24
db/dto/wechat_bot.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package dto
|
||||
|
||||
// WechatBotConfigRequest 微信公众号机器人配置请求
|
||||
type WechatBotConfigRequest struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
AppID string `json:"app_id"`
|
||||
AppSecret string `json:"app_secret"`
|
||||
Token string `json:"token"`
|
||||
EncodingAesKey string `json:"encoding_aes_key"`
|
||||
WelcomeMessage string `json:"welcome_message"`
|
||||
AutoReplyEnabled bool `json:"auto_reply_enabled"`
|
||||
SearchLimit int `json:"search_limit"`
|
||||
}
|
||||
|
||||
// WechatBotConfigResponse 微信公众号机器人配置响应
|
||||
type WechatBotConfigResponse struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
AppID string `json:"app_id"`
|
||||
Token string `json:"token"`
|
||||
EncodingAesKey string `json:"encoding_aes_key"`
|
||||
WelcomeMessage string `json:"welcome_message"`
|
||||
AutoReplyEnabled bool `json:"auto_reply_enabled"`
|
||||
SearchLimit int `json:"search_limit"`
|
||||
}
|
||||
@@ -57,6 +57,16 @@ const (
|
||||
ConfigKeyTelegramProxyUsername = "telegram_proxy_username"
|
||||
ConfigKeyTelegramProxyPassword = "telegram_proxy_password"
|
||||
|
||||
// 微信公众号配置
|
||||
ConfigKeyWechatBotEnabled = "wechat_bot_enabled"
|
||||
ConfigKeyWechatAppId = "wechat_app_id"
|
||||
ConfigKeyWechatAppSecret = "wechat_app_secret"
|
||||
ConfigKeyWechatToken = "wechat_token"
|
||||
ConfigKeyWechatEncodingAesKey = "wechat_encoding_aes_key"
|
||||
ConfigKeyWechatWelcomeMessage = "wechat_welcome_message"
|
||||
ConfigKeyWechatAutoReplyEnabled = "wechat_auto_reply_enabled"
|
||||
ConfigKeyWechatSearchLimit = "wechat_search_limit"
|
||||
|
||||
// 界面配置
|
||||
ConfigKeyEnableAnnouncements = "enable_announcements"
|
||||
ConfigKeyAnnouncements = "announcements"
|
||||
@@ -135,6 +145,16 @@ const (
|
||||
ConfigResponseFieldTelegramProxyUsername = "telegram_proxy_username"
|
||||
ConfigResponseFieldTelegramProxyPassword = "telegram_proxy_password"
|
||||
|
||||
// 微信公众号配置字段
|
||||
ConfigResponseFieldWechatBotEnabled = "wechat_bot_enabled"
|
||||
ConfigResponseFieldWechatAppId = "wechat_app_id"
|
||||
ConfigResponseFieldWechatAppSecret = "wechat_app_secret"
|
||||
ConfigResponseFieldWechatToken = "wechat_token"
|
||||
ConfigResponseFieldWechatEncodingAesKey = "wechat_encoding_aes_key"
|
||||
ConfigResponseFieldWechatWelcomeMessage = "wechat_welcome_message"
|
||||
ConfigResponseFieldWechatAutoReplyEnabled = "wechat_auto_reply_enabled"
|
||||
ConfigResponseFieldWechatSearchLimit = "wechat_search_limit"
|
||||
|
||||
// 界面配置字段
|
||||
ConfigResponseFieldEnableAnnouncements = "enable_announcements"
|
||||
ConfigResponseFieldAnnouncements = "announcements"
|
||||
@@ -200,6 +220,16 @@ const (
|
||||
ConfigDefaultTelegramProxyUsername = ""
|
||||
ConfigDefaultTelegramProxyPassword = ""
|
||||
|
||||
// 微信公众号配置默认值
|
||||
ConfigDefaultWechatBotEnabled = "false"
|
||||
ConfigDefaultWechatAppId = ""
|
||||
ConfigDefaultWechatAppSecret = ""
|
||||
ConfigDefaultWechatToken = ""
|
||||
ConfigDefaultWechatEncodingAesKey = ""
|
||||
ConfigDefaultWechatWelcomeMessage = "欢迎关注老九网盘资源库!发送关键词即可搜索资源。"
|
||||
ConfigDefaultWechatAutoReplyEnabled = "true"
|
||||
ConfigDefaultWechatSearchLimit = "5"
|
||||
|
||||
// 界面配置默认值
|
||||
ConfigDefaultEnableAnnouncements = "false"
|
||||
ConfigDefaultAnnouncements = ""
|
||||
|
||||
10
go.mod
10
go.mod
@@ -21,13 +21,23 @@ require (
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/fatih/structs v1.1.0 // indirect
|
||||
github.com/go-redis/redis/v8 v8.11.5 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/prometheus/client_golang v1.23.2 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.66.1 // indirect
|
||||
github.com/prometheus/procfs v0.16.1 // indirect
|
||||
github.com/silenceper/wechat/v2 v2.1.10 // indirect
|
||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||
github.com/spf13/cast v1.4.1 // indirect
|
||||
github.com/tidwall/gjson v1.14.1 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||
)
|
||||
|
||||
|
||||
108
go.sum
108
go.sum
@@ -1,14 +1,22 @@
|
||||
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
||||
github.com/alicebob/miniredis/v2 v2.30.0/go.mod h1:84TWKZlxYkfgMucPBf5SOQBYJceZeQRFIaQgNMiCX6Q=
|
||||
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
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/bytedance/sonic v1.13.3 h1:MS8gmaH16Gtirygw7jV91pDCN33NyMrPbN7qiYhEsF0=
|
||||
github.com/bytedance/sonic v1.13.3/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
||||
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
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/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
||||
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
@@ -16,6 +24,12 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
|
||||
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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||
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.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
||||
github.com/gin-contrib/cors v1.4.0 h1:oJ6gwtUl3lqV0WEIwM/LxPF1QZ5qe2lGWdY2+bz7y0g=
|
||||
@@ -38,8 +52,11 @@ github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91
|
||||
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
||||
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
||||
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
||||
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
||||
github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
|
||||
github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
|
||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
|
||||
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
@@ -49,11 +66,26 @@ github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXe
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
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/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
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.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
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.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
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.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
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/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
@@ -100,6 +132,18 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
|
||||
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
@@ -121,10 +165,18 @@ github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6po
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/silenceper/wechat/v2 v2.1.10 h1:jMg0//CZBIuogEvuXgxJQuJ47SsPPAqFrrbOtro2pko=
|
||||
github.com/silenceper/wechat/v2 v2.1.10/go.mod h1:7Iu3EhQYVtDUJAj+ZVRy8yom75ga7aDWv8RurLkVm0s=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
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/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
@@ -132,6 +184,12 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tidwall/gjson v1.14.1 h1:iymTbGkQBhveq21bEvAQ81I0LEBork8BFe1CUZXdyuo=
|
||||
github.com/tidwall/gjson v1.14.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
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/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
|
||||
@@ -140,32 +198,64 @@ github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA
|
||||
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
||||
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||
golang.org/x/arch v0.19.0 h1:LmbDQUodHThXE+htjrnmVD73M//D9GTH6wFZjyDkjyU=
|
||||
golang.org/x/arch v0.19.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||
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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/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.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
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-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
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-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/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-20200323222414-85ca7c5b95cd/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-20210112080510-489259a85091/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-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
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.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
@@ -175,8 +265,20 @@ golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
||||
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
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-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
@@ -187,6 +289,12 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0=
|
||||
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.4/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.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
@@ -440,3 +440,80 @@ func (h *FileHandler) calculateFileHash(filePath string) (string, error) {
|
||||
}
|
||||
return fmt.Sprintf("%x", hash.Sum(nil)), nil
|
||||
}
|
||||
|
||||
// UploadWechatVerifyFile 上传微信公众号验证文件(TXT文件)
|
||||
// 无需认证,仅支持TXT文件,不记录数据库,直接保存到uploads目录
|
||||
func (h *FileHandler) UploadWechatVerifyFile(c *gin.Context) {
|
||||
// 获取上传的文件
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
ErrorResponse(c, "未提供文件", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// 验证文件扩展名必须是.txt
|
||||
ext := strings.ToLower(filepath.Ext(file.Filename))
|
||||
if ext != ".txt" {
|
||||
ErrorResponse(c, "仅支持TXT文件", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// 验证文件大小(限制1MB)
|
||||
if file.Size > 1*1024*1024 {
|
||||
ErrorResponse(c, "文件大小不能超过1MB", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// 生成文件名(使用原始文件名,但确保是安全的)
|
||||
originalName := filepath.Base(file.Filename)
|
||||
safeFileName := h.makeSafeFileName(originalName)
|
||||
|
||||
// 确保uploads目录存在
|
||||
uploadsDir := "./uploads"
|
||||
if err := os.MkdirAll(uploadsDir, 0755); err != nil {
|
||||
ErrorResponse(c, "创建上传目录失败", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// 构建完整文件路径
|
||||
filePath := filepath.Join(uploadsDir, safeFileName)
|
||||
|
||||
// 保存文件
|
||||
if err := c.SaveUploadedFile(file, filePath); err != nil {
|
||||
ErrorResponse(c, "保存文件失败", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// 设置文件权限
|
||||
if err := os.Chmod(filePath, 0644); err != nil {
|
||||
utils.Warn("设置文件权限失败: %v", err)
|
||||
}
|
||||
|
||||
// 返回成功响应
|
||||
accessURL := fmt.Sprintf("/%s", safeFileName)
|
||||
response := map[string]interface{}{
|
||||
"success": true,
|
||||
"message": "验证文件上传成功",
|
||||
"file_name": safeFileName,
|
||||
"access_url": accessURL,
|
||||
}
|
||||
|
||||
SuccessResponse(c, response)
|
||||
}
|
||||
|
||||
// makeSafeFileName 生成安全的文件名,移除危险字符
|
||||
func (h *FileHandler) makeSafeFileName(filename string) string {
|
||||
// 移除路径分隔符和特殊字符
|
||||
safeName := strings.ReplaceAll(filename, "/", "_")
|
||||
safeName = strings.ReplaceAll(safeName, "\\", "_")
|
||||
safeName = strings.ReplaceAll(safeName, "..", "_")
|
||||
|
||||
// 限制文件名长度
|
||||
if len(safeName) > 100 {
|
||||
ext := filepath.Ext(safeName)
|
||||
name := safeName[:100-len(ext)]
|
||||
safeName = name + ext
|
||||
}
|
||||
|
||||
return safeName
|
||||
}
|
||||
|
||||
235
handlers/wechat_handler.go
Normal file
235
handlers/wechat_handler.go
Normal file
@@ -0,0 +1,235 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/ctwj/urldb/db/converter"
|
||||
"github.com/ctwj/urldb/db/dto"
|
||||
"github.com/ctwj/urldb/db/entity"
|
||||
"github.com/ctwj/urldb/db/repo"
|
||||
"github.com/ctwj/urldb/services"
|
||||
"github.com/ctwj/urldb/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/silenceper/wechat/v2/officialaccount/message"
|
||||
)
|
||||
|
||||
// WechatHandler 微信公众号处理器
|
||||
type WechatHandler struct {
|
||||
wechatService services.WechatBotService
|
||||
systemConfigRepo repo.SystemConfigRepository
|
||||
}
|
||||
|
||||
// NewWechatHandler 创建微信公众号处理器
|
||||
func NewWechatHandler(
|
||||
wechatService services.WechatBotService,
|
||||
systemConfigRepo repo.SystemConfigRepository,
|
||||
) *WechatHandler {
|
||||
return &WechatHandler{
|
||||
wechatService: wechatService,
|
||||
systemConfigRepo: systemConfigRepo,
|
||||
}
|
||||
}
|
||||
|
||||
// HandleWechatMessage 处理微信消息推送
|
||||
func (h *WechatHandler) HandleWechatMessage(c *gin.Context) {
|
||||
// 验证微信消息签名
|
||||
if !h.validateSignature(c) {
|
||||
utils.Error("[WECHAT:VALIDATE] 签名验证失败")
|
||||
c.String(http.StatusForbidden, "签名验证失败")
|
||||
return
|
||||
}
|
||||
|
||||
// 处理微信验证请求
|
||||
if c.Request.Method == "GET" {
|
||||
echostr := c.Query("echostr")
|
||||
utils.Info("[WECHAT:VERIFY] 微信服务器验证成功, echostr=%s", echostr)
|
||||
c.String(http.StatusOK, echostr)
|
||||
return
|
||||
}
|
||||
|
||||
// 读取请求体
|
||||
body, err := io.ReadAll(c.Request.Body)
|
||||
if err != nil {
|
||||
utils.Error("[WECHAT:MESSAGE] 读取请求体失败: %v", err)
|
||||
c.String(http.StatusBadRequest, "读取请求体失败")
|
||||
return
|
||||
}
|
||||
|
||||
// 解析微信消息
|
||||
var msg message.MixMessage
|
||||
if err := xml.Unmarshal(body, &msg); err != nil {
|
||||
utils.Error("[WECHAT:MESSAGE] 解析微信消息失败: %v", err)
|
||||
c.String(http.StatusBadRequest, "消息格式错误")
|
||||
return
|
||||
}
|
||||
|
||||
// 处理消息
|
||||
reply, err := h.wechatService.HandleMessage(&msg)
|
||||
if err != nil {
|
||||
utils.Error("[WECHAT:MESSAGE] 处理微信消息失败: %v", err)
|
||||
c.String(http.StatusInternalServerError, "处理失败")
|
||||
return
|
||||
}
|
||||
|
||||
// 如果有回复内容,发送回复
|
||||
if reply != nil {
|
||||
responseXML, err := xml.Marshal(reply)
|
||||
if err != nil {
|
||||
utils.Error("[WECHAT:MESSAGE] 序列化回复消息失败: %v", err)
|
||||
c.String(http.StatusInternalServerError, "回复失败")
|
||||
return
|
||||
}
|
||||
c.Data(http.StatusOK, "application/xml", responseXML)
|
||||
} else {
|
||||
c.String(http.StatusOK, "success")
|
||||
}
|
||||
}
|
||||
|
||||
// GetBotConfig 获取微信机器人配置
|
||||
func (h *WechatHandler) GetBotConfig(c *gin.Context) {
|
||||
configs, err := h.systemConfigRepo.GetOrCreateDefault()
|
||||
if err != nil {
|
||||
ErrorResponse(c, "获取配置失败", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
botConfig := converter.SystemConfigToWechatBotConfig(configs)
|
||||
SuccessResponse(c, botConfig)
|
||||
}
|
||||
|
||||
// UpdateBotConfig 更新微信机器人配置
|
||||
func (h *WechatHandler) UpdateBotConfig(c *gin.Context) {
|
||||
var req dto.WechatBotConfigRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
ErrorResponse(c, "请求参数错误", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// 转换为系统配置实体
|
||||
configs := converter.WechatBotConfigRequestToSystemConfigs(req)
|
||||
|
||||
// 保存配置
|
||||
if len(configs) > 0 {
|
||||
err := h.systemConfigRepo.UpsertConfigs(configs)
|
||||
if err != nil {
|
||||
ErrorResponse(c, "保存配置失败", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 重新加载配置缓存
|
||||
if err := h.systemConfigRepo.SafeRefreshConfigCache(); err != nil {
|
||||
ErrorResponse(c, "刷新配置缓存失败", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// 重新加载机器人服务配置
|
||||
if err := h.wechatService.ReloadConfig(); err != nil {
|
||||
ErrorResponse(c, "重新加载机器人配置失败", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// 配置更新完成后,尝试启动机器人(如果未运行且配置有效)
|
||||
if startErr := h.wechatService.Start(); startErr != nil {
|
||||
utils.Warn("[WECHAT:HANDLER] 配置更新后尝试启动机器人失败: %v", startErr)
|
||||
// 启动失败不影响配置保存,只记录警告
|
||||
}
|
||||
|
||||
// 返回成功
|
||||
SuccessResponse(c, map[string]interface{}{
|
||||
"success": true,
|
||||
"message": "配置更新成功,机器人已尝试启动",
|
||||
})
|
||||
}
|
||||
|
||||
// GetBotStatus 获取机器人状态
|
||||
func (h *WechatHandler) GetBotStatus(c *gin.Context) {
|
||||
// 获取机器人运行时状态
|
||||
runtimeStatus := h.wechatService.GetRuntimeStatus()
|
||||
|
||||
// 获取配置状态
|
||||
configs, err := h.systemConfigRepo.GetOrCreateDefault()
|
||||
if err != nil {
|
||||
ErrorResponse(c, "获取配置失败", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// 解析配置状态
|
||||
configStatus := map[string]interface{}{
|
||||
"enabled": false,
|
||||
"auto_reply_enabled": false,
|
||||
"app_id_configured": false,
|
||||
"token_configured": false,
|
||||
}
|
||||
|
||||
for _, config := range configs {
|
||||
switch config.Key {
|
||||
case entity.ConfigKeyWechatBotEnabled:
|
||||
configStatus["enabled"] = config.Value == "true"
|
||||
case entity.ConfigKeyWechatAutoReplyEnabled:
|
||||
configStatus["auto_reply_enabled"] = config.Value == "true"
|
||||
case entity.ConfigKeyWechatAppId:
|
||||
configStatus["app_id_configured"] = config.Value != ""
|
||||
case entity.ConfigKeyWechatToken:
|
||||
configStatus["token_configured"] = config.Value != ""
|
||||
}
|
||||
}
|
||||
|
||||
// 合并状态信息
|
||||
status := map[string]interface{}{
|
||||
"config": configStatus,
|
||||
"runtime": runtimeStatus,
|
||||
"overall_status": runtimeStatus["is_running"].(bool),
|
||||
"status_text": func() string {
|
||||
if runtimeStatus["is_running"].(bool) {
|
||||
return "运行中"
|
||||
} else if configStatus["enabled"].(bool) {
|
||||
return "已启用但未运行"
|
||||
} else {
|
||||
return "已停止"
|
||||
}
|
||||
}(),
|
||||
}
|
||||
|
||||
SuccessResponse(c, status)
|
||||
}
|
||||
|
||||
// validateSignature 验证微信消息签名
|
||||
func (h *WechatHandler) validateSignature(c *gin.Context) bool {
|
||||
// 获取配置中的Token
|
||||
configs, err := h.systemConfigRepo.GetOrCreateDefault()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var token string
|
||||
for _, config := range configs {
|
||||
if config.Key == entity.ConfigKeyWechatToken {
|
||||
token = config.Value
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if token == "" {
|
||||
// 如果没有配置Token,跳过签名验证(开发模式)
|
||||
return true
|
||||
}
|
||||
|
||||
signature := c.Query("signature")
|
||||
timestamp := c.Query("timestamp")
|
||||
nonce := c.Query("nonce")
|
||||
|
||||
// 验证签名
|
||||
tmpArr := []string{token, timestamp, nonce}
|
||||
sort.Strings(tmpArr)
|
||||
tmpStr := strings.Join(tmpArr, "")
|
||||
tmpStr = fmt.Sprintf("%x", sha1.Sum([]byte(tmpStr)))
|
||||
|
||||
return tmpStr == signature
|
||||
}
|
||||
24
main.go
24
main.go
@@ -363,6 +363,8 @@ func main() {
|
||||
api.GET("/files", middleware.AuthMiddleware(), fileHandler.GetFileList)
|
||||
api.DELETE("/files", middleware.AuthMiddleware(), fileHandler.DeleteFiles)
|
||||
api.PUT("/files", middleware.AuthMiddleware(), fileHandler.UpdateFile)
|
||||
// 微信公众号验证文件上传(无需认证,仅支持TXT文件)
|
||||
api.POST("/wechat/verify-file", fileHandler.UploadWechatVerifyFile)
|
||||
|
||||
// 创建Telegram Bot服务
|
||||
telegramBotService := services.NewTelegramBotService(
|
||||
@@ -377,6 +379,18 @@ func main() {
|
||||
utils.Error("启动Telegram Bot服务失败: %v", err)
|
||||
}
|
||||
|
||||
// 创建微信公众号机器人服务
|
||||
wechatBotService := services.NewWechatBotService(
|
||||
repoManager.SystemConfigRepository,
|
||||
repoManager.ResourceRepository,
|
||||
repoManager.ReadyResourceRepository,
|
||||
)
|
||||
|
||||
// 启动微信公众号机器人服务
|
||||
if err := wechatBotService.Start(); err != nil {
|
||||
utils.Error("启动微信公众号机器人服务失败: %v", err)
|
||||
}
|
||||
|
||||
// Telegram相关路由
|
||||
telegramHandler := handlers.NewTelegramHandler(
|
||||
repoManager.TelegramChannelRepository,
|
||||
@@ -398,6 +412,16 @@ func main() {
|
||||
api.GET("/telegram/logs/stats", middleware.AuthMiddleware(), middleware.AdminMiddleware(), telegramHandler.GetTelegramLogStats)
|
||||
api.POST("/telegram/logs/clear", middleware.AuthMiddleware(), middleware.AdminMiddleware(), telegramHandler.ClearTelegramLogs)
|
||||
api.POST("/telegram/webhook", telegramHandler.HandleWebhook)
|
||||
|
||||
// 微信公众号相关路由
|
||||
wechatHandler := handlers.NewWechatHandler(
|
||||
wechatBotService,
|
||||
repoManager.SystemConfigRepository,
|
||||
)
|
||||
api.GET("/wechat/bot-config", middleware.AuthMiddleware(), middleware.AdminMiddleware(), wechatHandler.GetBotConfig)
|
||||
api.PUT("/wechat/bot-config", middleware.AuthMiddleware(), middleware.AdminMiddleware(), wechatHandler.UpdateBotConfig)
|
||||
api.GET("/wechat/bot-status", middleware.AuthMiddleware(), middleware.AdminMiddleware(), wechatHandler.GetBotStatus)
|
||||
api.POST("/api/wechat/callback", wechatHandler.HandleWechatMessage)
|
||||
}
|
||||
|
||||
// 设置监控系统
|
||||
|
||||
@@ -60,17 +60,42 @@ server {
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
|
||||
# 缓存设置
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
|
||||
|
||||
# 允许跨域访问
|
||||
add_header Access-Control-Allow-Origin "*";
|
||||
add_header Access-Control-Allow-Methods "GET, OPTIONS";
|
||||
add_header Access-Control-Allow-Headers "Origin, Content-Type, Accept";
|
||||
}
|
||||
|
||||
# 微信公众号验证文件路由 - 根目录的TXT文件直接访问后端uploads目录
|
||||
location ~ ^/[^/]+\.txt$ {
|
||||
# 检查文件是否存在于uploads目录
|
||||
set $uploads_path /uploads$uri;
|
||||
if (-f $uploads_path) {
|
||||
proxy_pass http://backend;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# 缓存设置
|
||||
expires 1h;
|
||||
add_header Cache-Control "public";
|
||||
|
||||
# 允许跨域访问
|
||||
add_header Access-Control-Allow-Origin "*";
|
||||
add_header Access-Control-Allow-Methods "GET, OPTIONS";
|
||||
add_header Access-Control-Allow-Headers "Origin, Content-Type, Accept";
|
||||
break;
|
||||
}
|
||||
# 如果文件不存在,返回404
|
||||
return 404;
|
||||
}
|
||||
|
||||
# 健康检查
|
||||
location /health {
|
||||
proxy_pass http://backend/health;
|
||||
|
||||
56
services/wechat_bot_service.go
Normal file
56
services/wechat_bot_service.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"github.com/ctwj/urldb/db/repo"
|
||||
"github.com/silenceper/wechat/v2/officialaccount"
|
||||
"github.com/silenceper/wechat/v2/officialaccount/message"
|
||||
)
|
||||
|
||||
// WechatBotService 微信公众号机器人服务接口
|
||||
type WechatBotService interface {
|
||||
Start() error
|
||||
Stop() error
|
||||
IsRunning() bool
|
||||
ReloadConfig() error
|
||||
HandleMessage(msg *message.MixMessage) (interface{}, error)
|
||||
SendWelcomeMessage(openID string) error
|
||||
GetRuntimeStatus() map[string]interface{}
|
||||
GetConfig() *WechatBotConfig
|
||||
}
|
||||
|
||||
// WechatBotConfig 微信公众号机器人配置
|
||||
type WechatBotConfig struct {
|
||||
Enabled bool
|
||||
AppID string
|
||||
AppSecret string
|
||||
Token string
|
||||
EncodingAesKey string
|
||||
WelcomeMessage string
|
||||
AutoReplyEnabled bool
|
||||
SearchLimit int
|
||||
}
|
||||
|
||||
// WechatBotServiceImpl 微信公众号机器人服务实现
|
||||
type WechatBotServiceImpl struct {
|
||||
isRunning bool
|
||||
systemConfigRepo repo.SystemConfigRepository
|
||||
resourceRepo repo.ResourceRepository
|
||||
readyRepo repo.ReadyResourceRepository
|
||||
config *WechatBotConfig
|
||||
wechatClient *officialaccount.OfficialAccount
|
||||
}
|
||||
|
||||
// NewWechatBotService 创建微信公众号机器人服务
|
||||
func NewWechatBotService(
|
||||
systemConfigRepo repo.SystemConfigRepository,
|
||||
resourceRepo repo.ResourceRepository,
|
||||
readyResourceRepo repo.ReadyResourceRepository,
|
||||
) WechatBotService {
|
||||
return &WechatBotServiceImpl{
|
||||
isRunning: false,
|
||||
systemConfigRepo: systemConfigRepo,
|
||||
resourceRepo: resourceRepo,
|
||||
readyRepo: readyResourceRepo,
|
||||
config: &WechatBotConfig{},
|
||||
}
|
||||
}
|
||||
258
services/wechat_bot_service_impl.go
Normal file
258
services/wechat_bot_service_impl.go
Normal file
@@ -0,0 +1,258 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/ctwj/urldb/db/entity"
|
||||
"github.com/ctwj/urldb/utils"
|
||||
"github.com/silenceper/wechat/v2/cache"
|
||||
"github.com/silenceper/wechat/v2/officialaccount"
|
||||
"github.com/silenceper/wechat/v2/officialaccount/config"
|
||||
"github.com/silenceper/wechat/v2/officialaccount/message"
|
||||
)
|
||||
|
||||
// loadConfig 加载微信配置
|
||||
func (s *WechatBotServiceImpl) loadConfig() error {
|
||||
configs, err := s.systemConfigRepo.GetOrCreateDefault()
|
||||
if err != nil {
|
||||
return fmt.Errorf("加载配置失败: %v", err)
|
||||
}
|
||||
|
||||
utils.Info("[WECHAT] 从数据库加载到 %d 个配置项", len(configs))
|
||||
|
||||
// 初始化默认值
|
||||
s.config.Enabled = false
|
||||
s.config.AppID = ""
|
||||
s.config.AppSecret = ""
|
||||
s.config.Token = ""
|
||||
s.config.EncodingAesKey = ""
|
||||
s.config.WelcomeMessage = "欢迎关注老九网盘资源库!发送关键词即可搜索资源。"
|
||||
s.config.AutoReplyEnabled = true
|
||||
s.config.SearchLimit = 5
|
||||
|
||||
for _, config := range configs {
|
||||
switch config.Key {
|
||||
case entity.ConfigKeyWechatBotEnabled:
|
||||
s.config.Enabled = config.Value == "true"
|
||||
utils.Info("[WECHAT:CONFIG] 加载配置 %s = %s (Enabled: %v)", config.Key, config.Value, s.config.Enabled)
|
||||
case entity.ConfigKeyWechatAppId:
|
||||
s.config.AppID = config.Value
|
||||
utils.Info("[WECHAT:CONFIG] 加载配置 %s = [HIDDEN]", config.Key)
|
||||
case entity.ConfigKeyWechatAppSecret:
|
||||
s.config.AppSecret = config.Value
|
||||
utils.Info("[WECHAT:CONFIG] 加载配置 %s = [HIDDEN]", config.Key)
|
||||
case entity.ConfigKeyWechatToken:
|
||||
s.config.Token = config.Value
|
||||
utils.Info("[WECHAT:CONFIG] 加载配置 %s = [HIDDEN]", config.Key)
|
||||
case entity.ConfigKeyWechatEncodingAesKey:
|
||||
s.config.EncodingAesKey = config.Value
|
||||
utils.Info("[WECHAT:CONFIG] 加载配置 %s = [HIDDEN]", config.Key)
|
||||
case entity.ConfigKeyWechatWelcomeMessage:
|
||||
if config.Value != "" {
|
||||
s.config.WelcomeMessage = config.Value
|
||||
}
|
||||
utils.Info("[WECHAT:CONFIG] 加载配置 %s = %s", config.Key, config.Value)
|
||||
case entity.ConfigKeyWechatAutoReplyEnabled:
|
||||
s.config.AutoReplyEnabled = config.Value == "true"
|
||||
utils.Info("[WECHAT:CONFIG] 加载配置 %s = %s (AutoReplyEnabled: %v)", config.Key, config.Value, s.config.AutoReplyEnabled)
|
||||
case entity.ConfigKeyWechatSearchLimit:
|
||||
if config.Value != "" {
|
||||
limit, err := strconv.Atoi(config.Value)
|
||||
if err == nil && limit > 0 {
|
||||
s.config.SearchLimit = limit
|
||||
}
|
||||
}
|
||||
utils.Info("[WECHAT:CONFIG] 加载配置 %s = %s (SearchLimit: %d)", config.Key, config.Value, s.config.SearchLimit)
|
||||
}
|
||||
}
|
||||
|
||||
utils.Info("[WECHAT:SERVICE] 微信公众号机器人配置加载完成: Enabled=%v, AutoReplyEnabled=%v",
|
||||
s.config.Enabled, s.config.AutoReplyEnabled)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start 启动微信公众号机器人服务
|
||||
func (s *WechatBotServiceImpl) Start() error {
|
||||
if s.isRunning {
|
||||
utils.Info("[WECHAT:SERVICE] 微信公众号机器人服务已经在运行中")
|
||||
return nil
|
||||
}
|
||||
|
||||
// 加载配置
|
||||
if err := s.loadConfig(); err != nil {
|
||||
return fmt.Errorf("加载配置失败: %v", err)
|
||||
}
|
||||
|
||||
if !s.config.Enabled || s.config.AppID == "" || s.config.AppSecret == "" {
|
||||
utils.Info("[WECHAT:SERVICE] 微信公众号机器人未启用或配置不完整")
|
||||
return nil
|
||||
}
|
||||
|
||||
// 创建微信客户端
|
||||
cfg := &config.Config{
|
||||
AppID: s.config.AppID,
|
||||
AppSecret: s.config.AppSecret,
|
||||
Token: s.config.Token,
|
||||
EncodingAESKey: s.config.EncodingAesKey,
|
||||
Cache: cache.NewMemory(),
|
||||
}
|
||||
s.wechatClient = officialaccount.NewOfficialAccount(cfg)
|
||||
|
||||
s.isRunning = true
|
||||
utils.Info("[WECHAT:SERVICE] 微信公众号机器人服务已启动")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop 停止微信公众号机器人服务
|
||||
func (s *WechatBotServiceImpl) Stop() error {
|
||||
if !s.isRunning {
|
||||
return nil
|
||||
}
|
||||
|
||||
s.isRunning = false
|
||||
utils.Info("[WECHAT:SERVICE] 微信公众号机器人服务已停止")
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsRunning 检查微信公众号机器人服务是否正在运行
|
||||
func (s *WechatBotServiceImpl) IsRunning() bool {
|
||||
return s.isRunning
|
||||
}
|
||||
|
||||
// ReloadConfig 重新加载微信公众号机器人配置
|
||||
func (s *WechatBotServiceImpl) ReloadConfig() error {
|
||||
utils.Info("[WECHAT:SERVICE] 开始重新加载配置...")
|
||||
|
||||
// 重新加载配置
|
||||
if err := s.loadConfig(); err != nil {
|
||||
utils.Error("[WECHAT:SERVICE] 重新加载配置失败: %v", err)
|
||||
return fmt.Errorf("重新加载配置失败: %v", err)
|
||||
}
|
||||
|
||||
utils.Info("[WECHAT:SERVICE] 配置重新加载完成: Enabled=%v, AutoReplyEnabled=%v",
|
||||
s.config.Enabled, s.config.AutoReplyEnabled)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetRuntimeStatus 获取微信公众号机器人运行时状态
|
||||
func (s *WechatBotServiceImpl) GetRuntimeStatus() map[string]interface{} {
|
||||
status := map[string]interface{}{
|
||||
"is_running": s.IsRunning(),
|
||||
"config_loaded": s.config != nil,
|
||||
"app_id": s.config.AppID,
|
||||
}
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
// GetConfig 获取当前配置
|
||||
func (s *WechatBotServiceImpl) GetConfig() *WechatBotConfig {
|
||||
return s.config
|
||||
}
|
||||
|
||||
// HandleMessage 处理微信消息
|
||||
func (s *WechatBotServiceImpl) HandleMessage(msg *message.MixMessage) (interface{}, error) {
|
||||
utils.Info("[WECHAT:MESSAGE] 收到消息: FromUserName=%s, MsgType=%s, Event=%s, Content=%s",
|
||||
msg.FromUserName, msg.MsgType, msg.Event, msg.Content)
|
||||
|
||||
switch msg.MsgType {
|
||||
case message.MsgTypeText:
|
||||
return s.handleTextMessage(msg)
|
||||
case message.MsgTypeEvent:
|
||||
return s.handleEventMessage(msg)
|
||||
default:
|
||||
return nil, nil // 不处理其他类型消息
|
||||
}
|
||||
}
|
||||
|
||||
// handleTextMessage 处理文本消息
|
||||
func (s *WechatBotServiceImpl) handleTextMessage(msg *message.MixMessage) (interface{}, error) {
|
||||
if !s.config.AutoReplyEnabled {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
keyword := strings.TrimSpace(msg.Content)
|
||||
if keyword == "" {
|
||||
return message.NewText("请输入搜索关键词"), nil
|
||||
}
|
||||
|
||||
// 搜索资源
|
||||
resources, err := s.SearchResources(keyword)
|
||||
if err != nil {
|
||||
utils.Error("[WECHAT:SEARCH] 搜索失败: %v", err)
|
||||
return message.NewText("搜索服务暂时不可用,请稍后重试"), nil
|
||||
}
|
||||
|
||||
if len(resources) == 0 {
|
||||
return message.NewText(fmt.Sprintf("未找到关键词\"%s\"相关的资源,请尝试其他关键词", keyword)), nil
|
||||
}
|
||||
|
||||
// 格式化搜索结果
|
||||
resultText := s.formatSearchResults(keyword, resources)
|
||||
return message.NewText(resultText), nil
|
||||
}
|
||||
|
||||
// handleEventMessage 处理事件消息
|
||||
func (s *WechatBotServiceImpl) handleEventMessage(msg *message.MixMessage) (interface{}, error) {
|
||||
if msg.Event == message.EventSubscribe {
|
||||
// 新用户关注
|
||||
return message.NewText(s.config.WelcomeMessage), nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// SearchResources 搜索资源
|
||||
func (s *WechatBotServiceImpl) SearchResources(keyword string) ([]entity.Resource, error) {
|
||||
// 使用现有的资源搜索功能
|
||||
resources, total, err := s.resourceRepo.Search(keyword, nil, 1, s.config.SearchLimit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if total == 0 {
|
||||
return []entity.Resource{}, nil
|
||||
}
|
||||
|
||||
return resources, nil
|
||||
}
|
||||
|
||||
// formatSearchResults 格式化搜索结果
|
||||
func (s *WechatBotServiceImpl) formatSearchResults(keyword string, resources []entity.Resource) string {
|
||||
var result strings.Builder
|
||||
result.WriteString(fmt.Sprintf("🔍 搜索\"%s\"的结果(共%d条):\n\n", keyword, len(resources)))
|
||||
|
||||
for i, resource := range resources {
|
||||
result.WriteString(fmt.Sprintf("%d. %s\n", i+1, resource.Title))
|
||||
if resource.Description != "" {
|
||||
desc := resource.Description
|
||||
if len(desc) > 50 {
|
||||
desc = desc[:50] + "..."
|
||||
}
|
||||
result.WriteString(fmt.Sprintf(" %s\n", desc))
|
||||
}
|
||||
if resource.SaveURL != "" {
|
||||
result.WriteString(fmt.Sprintf(" 转存链接:%s\n", resource.SaveURL))
|
||||
} else if resource.URL != "" {
|
||||
result.WriteString(fmt.Sprintf(" 资源链接:%s\n", resource.URL))
|
||||
}
|
||||
result.WriteString("\n")
|
||||
}
|
||||
|
||||
result.WriteString("💡 提示:回复资源编号可获取详细信息")
|
||||
return result.String()
|
||||
}
|
||||
|
||||
// SendWelcomeMessage 发送欢迎消息(预留接口,实际通过事件处理)
|
||||
func (s *WechatBotServiceImpl) SendWelcomeMessage(openID string) error {
|
||||
// 实际上欢迎消息是通过关注事件自动发送的
|
||||
// 这里提供一个手动发送的接口
|
||||
if !s.isRunning || s.wechatClient == nil {
|
||||
return fmt.Errorf("微信客户端未初始化")
|
||||
}
|
||||
|
||||
// 注意:Customer API 需要额外的权限,这里仅作示例
|
||||
// 实际应用中可能需要使用模板消息或其他方式
|
||||
return nil
|
||||
}
|
||||
456
web/components/WechatBotTab.vue
Normal file
456
web/components/WechatBotTab.vue
Normal file
@@ -0,0 +1,456 @@
|
||||
<template>
|
||||
<div class="tab-content-container">
|
||||
<div class="space-y-6">
|
||||
<!-- 基础配置 -->
|
||||
<n-card title="基础配置" class="mb-6">
|
||||
<n-form :model="configForm" label-placement="left" label-width="120px">
|
||||
<n-form-item label="AppID">
|
||||
<n-input v-model:value="configForm.app_id" placeholder="请输入微信公众号AppID" />
|
||||
</n-form-item>
|
||||
<n-form-item label="AppSecret">
|
||||
<n-input v-model:value="configForm.app_secret" type="password" placeholder="请输入AppSecret" show-password-on="click" />
|
||||
</n-form-item>
|
||||
<n-form-item label="Token">
|
||||
<n-input v-model:value="configForm.token" placeholder="请输入Token(用于消息验证)" />
|
||||
</n-form-item>
|
||||
<n-form-item label="EncodingAESKey">
|
||||
<n-input v-model:value="configForm.encoding_aes_key" type="password" placeholder="请输入EncodingAESKey(可选,用于消息加密)" show-password-on="click" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-card>
|
||||
|
||||
<!-- 功能配置 -->
|
||||
<n-card title="功能配置" class="mb-6">
|
||||
<n-form :model="configForm" label-placement="left" label-width="120px">
|
||||
<n-form-item label="启用机器人">
|
||||
<n-switch v-model:value="configForm.enabled" />
|
||||
</n-form-item>
|
||||
<n-form-item label="自动回复">
|
||||
<n-switch v-model:value="configForm.auto_reply_enabled" />
|
||||
</n-form-item>
|
||||
<n-form-item label="欢迎消息">
|
||||
<n-input v-model:value="configForm.welcome_message" type="textarea" :rows="3" placeholder="新用户关注时发送的欢迎消息" />
|
||||
</n-form-item>
|
||||
<n-form-item label="搜索结果限制">
|
||||
<n-input-number v-model:value="configForm.search_limit" :min="1" :max="10" placeholder="搜索结果返回数量" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-card>
|
||||
|
||||
<!-- 微信公众号验证文件上传 -->
|
||||
<n-card title="微信公众号验证文件" class="mb-6">
|
||||
<div class="space-y-4">
|
||||
<div class="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-4">
|
||||
<h4 class="font-medium text-blue-800 dark:text-blue-200 mb-2">验证文件上传说明</h4>
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300 mb-2">
|
||||
微信公众号需要上传一个TXT验证文件到网站根目录。请按照以下步骤操作:
|
||||
</p>
|
||||
<ol class="text-sm text-gray-700 dark:text-gray-300 list-decimal list-inside space-y-1">
|
||||
<li>点击下方"选择文件"按钮,选择微信提供的TXT验证文件</li>
|
||||
<li>点击"上传验证文件"按钮上传文件</li>
|
||||
<li>上传成功后,文件将可通过网站根目录直接访问</li>
|
||||
<li>在微信公众平台完成域名验证</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center space-x-4">
|
||||
<n-upload
|
||||
ref="uploadRef"
|
||||
:show-file-list="false"
|
||||
:accept="'.txt'"
|
||||
:max="1"
|
||||
:custom-request="handleUpload"
|
||||
@before-upload="beforeUpload"
|
||||
@change="handleFileChange"
|
||||
>
|
||||
<n-button type="primary">
|
||||
<template #icon>
|
||||
<i class="fas fa-file-upload"></i>
|
||||
</template>
|
||||
选择TXT文件
|
||||
</n-button>
|
||||
</n-upload>
|
||||
<n-button
|
||||
type="success"
|
||||
@click="triggerUpload"
|
||||
:disabled="!selectedFile"
|
||||
:loading="uploading"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="fas fa-cloud-upload-alt"></i>
|
||||
</template>
|
||||
上传验证文件
|
||||
</n-button>
|
||||
</div>
|
||||
|
||||
<div v-if="uploadResult" class="p-3 rounded-md bg-green-50 dark:bg-green-900/20 text-green-700 dark:text-green-300">
|
||||
<div class="flex items-center">
|
||||
<i class="fas fa-check-circle mr-2"></i>
|
||||
<span>文件上传成功!</span>
|
||||
</div>
|
||||
<p class="text-xs mt-1">文件名: {{ uploadResult.file_name }}</p>
|
||||
<p class="text-xs">访问地址: {{ getFullUrl(uploadResult.access_url) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</n-card>
|
||||
|
||||
<!-- 微信公众号平台配置说明 -->
|
||||
<n-card title="微信公众号平台配置" class="mb-6">
|
||||
<div class="space-y-4">
|
||||
<div class="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-4">
|
||||
<h4 class="font-medium text-blue-800 dark:text-blue-200 mb-2">服务器配置</h4>
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300 mb-2">
|
||||
在微信公众平台后台的<strong>开发 > 基本配置 > 服务器配置</strong>中设置:
|
||||
</p>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-2">
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-1 block">URL</label>
|
||||
<div class="flex items-center space-x-2">
|
||||
<n-input :value="serverUrl" readonly class="flex-1" />
|
||||
<n-button size="small" @click="copyToClipboard(serverUrl)" type="primary">
|
||||
<template #icon>
|
||||
<i class="fas fa-copy"></i>
|
||||
</template>
|
||||
复制
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-1 block">Token</label>
|
||||
<div class="flex items-center space-x-2">
|
||||
<n-input :value="configForm.token" readonly class="flex-1" />
|
||||
<n-button size="small" @click="copyToClipboard(configForm.token)" type="primary">
|
||||
<template #icon>
|
||||
<i class="fas fa-copy"></i>
|
||||
</template>
|
||||
复制
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-green-50 dark:bg-green-900/20 rounded-lg p-4">
|
||||
<h4 class="font-medium text-green-800 dark:text-green-200 mb-2">消息加解密配置</h4>
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300">
|
||||
如果需要启用消息加密,请在微信公众平台选择<strong>安全模式</strong>,并填写上面的EncodingAESKey。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-yellow-50 dark:bg-yellow-900/20 rounded-lg p-4">
|
||||
<h4 class="font-medium text-yellow-800 dark:text-yellow-200 mb-2">注意事项</h4>
|
||||
<ul class="text-sm text-gray-700 dark:text-gray-300 list-disc list-inside space-y-1">
|
||||
<li>服务器必须支持HTTPS(微信要求)</li>
|
||||
<li>域名必须已备案</li>
|
||||
<li>首次配置时,微信会发送GET请求验证服务器</li>
|
||||
<li>配置完成后记得点击"启用"按钮</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</n-card>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="flex justify-end space-x-4">
|
||||
<n-button @click="resetForm">重置</n-button>
|
||||
<n-button type="primary" @click="saveConfig" :loading="loading">保存配置</n-button>
|
||||
</div>
|
||||
|
||||
<!-- 状态信息 -->
|
||||
<n-card title="运行状态" class="mt-6">
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center space-x-4">
|
||||
<n-tag :type="botStatus.overall_status ? 'success' : 'default'">
|
||||
{{ botStatus.status_text || '未知状态' }}
|
||||
</n-tag>
|
||||
<n-tag v-if="botStatus.config" :type="botStatus.config.enabled ? 'success' : 'default'">
|
||||
配置状态: {{ botStatus.config.enabled ? '已启用' : '已禁用' }}
|
||||
</n-tag>
|
||||
<n-tag v-if="botStatus.config" :type="botStatus.config.app_id_configured ? 'success' : 'warning'">
|
||||
AppID: {{ botStatus.config.app_id_configured ? '已配置' : '未配置' }}
|
||||
</n-tag>
|
||||
</div>
|
||||
|
||||
<div v-if="!botStatus.overall_status && botStatus.config && botStatus.config.enabled" class="bg-orange-50 dark:bg-orange-900/20 rounded-lg p-3">
|
||||
<p class="text-sm text-orange-800 dark:text-orange-200">
|
||||
<i class="fas fa-exclamation-triangle mr-2"></i>
|
||||
机器人已启用但未运行,请检查配置是否正确或查看系统日志。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</n-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import { useNotification } from 'naive-ui'
|
||||
import { useWechatApi } from '~/composables/useApi'
|
||||
|
||||
// 定义配置表单类型
|
||||
interface WechatBotConfigForm {
|
||||
enabled: boolean
|
||||
app_id: string
|
||||
app_secret: string
|
||||
token: string
|
||||
encoding_aes_key: string
|
||||
welcome_message: string
|
||||
auto_reply_enabled: boolean
|
||||
search_limit: number
|
||||
}
|
||||
|
||||
const notification = useNotification()
|
||||
const loading = ref(false)
|
||||
const wechatApi = useWechatApi()
|
||||
const botStatus = ref({
|
||||
overall_status: false,
|
||||
status_text: '',
|
||||
config: null as any,
|
||||
runtime: null as any
|
||||
})
|
||||
// 验证文件上传相关
|
||||
const uploadRef = ref()
|
||||
const selectedFile = ref<File | null>(null)
|
||||
const uploading = ref(false)
|
||||
const uploadResult = ref<any>(null)
|
||||
|
||||
// 配置表单 - 直接使用 reactive
|
||||
const configForm = reactive<WechatBotConfigForm>({
|
||||
enabled: false,
|
||||
app_id: '',
|
||||
app_secret: '',
|
||||
token: '',
|
||||
encoding_aes_key: '',
|
||||
welcome_message: '欢迎关注老九网盘资源库!发送关键词即可搜索资源。',
|
||||
auto_reply_enabled: true,
|
||||
search_limit: 5
|
||||
})
|
||||
|
||||
// 计算服务器URL
|
||||
const serverUrl = computed(() => {
|
||||
if (process.client) {
|
||||
return `${window.location.origin}/api/wechat/callback`
|
||||
}
|
||||
return 'https://yourdomain.com/api/wechat/callback'
|
||||
})
|
||||
|
||||
// 获取机器人配置
|
||||
const fetchBotConfig = async () => {
|
||||
try {
|
||||
const response = await wechatApi.getBotConfig()
|
||||
|
||||
if (response) {
|
||||
// 直接更新 configForm
|
||||
configForm.enabled = response.enabled || false
|
||||
configForm.app_id = response.app_id || ''
|
||||
configForm.app_secret = response.app_secret || '' // 现在所有字段都不敏感
|
||||
configForm.token = response.token || ''
|
||||
configForm.encoding_aes_key = response.encoding_aes_key || ''
|
||||
configForm.welcome_message = response.welcome_message || '欢迎关注老九网盘资源库!发送关键词即可搜索资源。'
|
||||
configForm.auto_reply_enabled = response.auto_reply_enabled || true
|
||||
configForm.search_limit = response.search_limit || 5
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取微信机器人配置失败:', error)
|
||||
notification.error({
|
||||
content: '获取配置失败',
|
||||
duration: 3000
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 获取机器人状态
|
||||
const fetchBotStatus = async () => {
|
||||
try {
|
||||
const response = await wechatApi.getBotStatus()
|
||||
|
||||
if (response) {
|
||||
botStatus.value = response
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取微信机器人状态失败:', error)
|
||||
notification.error({
|
||||
content: '获取状态失败',
|
||||
duration: 3000
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 保存配置
|
||||
const saveConfig = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// 直接保存所有字段,不检测变更
|
||||
const payload = {
|
||||
enabled: configForm.enabled,
|
||||
app_id: configForm.app_id,
|
||||
app_secret: configForm.app_secret,
|
||||
token: configForm.token,
|
||||
encoding_aes_key: configForm.encoding_aes_key,
|
||||
welcome_message: configForm.welcome_message,
|
||||
auto_reply_enabled: configForm.auto_reply_enabled,
|
||||
search_limit: configForm.search_limit
|
||||
}
|
||||
|
||||
const response = await wechatApi.updateBotConfig(payload)
|
||||
|
||||
if (response.success) {
|
||||
notification.success({
|
||||
content: '配置保存成功',
|
||||
duration: 3000
|
||||
})
|
||||
// 重新获取状态和配置
|
||||
await fetchBotConfig()
|
||||
await fetchBotStatus()
|
||||
} else {
|
||||
throw new Error(response.message || '保存失败')
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('保存微信机器人配置失败:', error)
|
||||
notification.error({
|
||||
content: error.message || '保存配置失败',
|
||||
duration: 3000
|
||||
})
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 重置表单
|
||||
const resetForm = () => {
|
||||
// 重新获取原始配置
|
||||
fetchBotConfig()
|
||||
}
|
||||
|
||||
// 复制到剪贴板
|
||||
const copyToClipboard = async (text: string) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text)
|
||||
notification.success({
|
||||
content: '已复制到剪贴板',
|
||||
duration: 2000
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('复制失败:', error)
|
||||
notification.error({
|
||||
content: '复制失败',
|
||||
duration: 2000
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 验证文件上传相关函数
|
||||
const beforeUpload = (options: { file: any, fileList: any[] }) => {
|
||||
// 从 options 中提取文件
|
||||
const file = options?.file?.file || options?.file
|
||||
|
||||
// 检查文件对象是否有效
|
||||
if (!file || !file.name) {
|
||||
notification.error({
|
||||
content: '文件选择失败,请重试',
|
||||
duration: 2000
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
// 验证文件类型 - 使用多重检查确保是TXT文件
|
||||
const isValid = file.type === 'text/plain' ||
|
||||
file.name.toLowerCase().endsWith('.txt') ||
|
||||
file.type === 'text/plain;charset=utf-8'
|
||||
|
||||
if (!isValid) {
|
||||
notification.error({
|
||||
content: '请上传TXT文件',
|
||||
duration: 2000
|
||||
})
|
||||
selectedFile.value = null // 清空之前的选择
|
||||
return false // 阻止上传无效文件
|
||||
}
|
||||
|
||||
// 保存选中的文件并更新状态
|
||||
selectedFile.value = file
|
||||
|
||||
return false // 阻止自动上传
|
||||
}
|
||||
|
||||
const handleUpload = ({ file, onSuccess, onError }: any) => {
|
||||
// 这个函数不会被调用,因为我们阻止了自动上传
|
||||
}
|
||||
|
||||
// 文件选择变化时的处理函数
|
||||
const handleFileChange = (options: { file: any, fileList: any[] }) => {
|
||||
// 从 change 事件中提取文件信息
|
||||
const file = options?.file?.file || options?.file
|
||||
|
||||
if (file && file.name) {
|
||||
// 更新选中的文件
|
||||
selectedFile.value = file
|
||||
}
|
||||
}
|
||||
|
||||
const triggerUpload = async () => {
|
||||
if (!selectedFile.value) {
|
||||
notification.warning({
|
||||
content: '请选择要上传的TXT文件',
|
||||
duration: 2000
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
uploading.value = true
|
||||
try {
|
||||
const formData = new FormData()
|
||||
formData.append('file', selectedFile.value)
|
||||
|
||||
const response = await wechatApi.uploadVerifyFile(formData)
|
||||
|
||||
if (response.success) {
|
||||
uploadResult.value = response
|
||||
notification.success({
|
||||
content: '验证文件上传成功',
|
||||
duration: 3000
|
||||
})
|
||||
// 清空选择的文件
|
||||
selectedFile.value = null
|
||||
if (uploadRef.value) {
|
||||
uploadRef.value.clear()
|
||||
}
|
||||
} else {
|
||||
throw new Error(response.message || '上传失败')
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('上传验证文件失败:', error)
|
||||
notification.error({
|
||||
content: error.message || '上传验证文件失败',
|
||||
duration: 3000
|
||||
})
|
||||
} finally {
|
||||
uploading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const getFullUrl = (path: string) => {
|
||||
if (process.client) {
|
||||
return `${window.location.origin}${path}`
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
// 页面加载时获取配置和状态
|
||||
onMounted(async () => {
|
||||
await fetchBotConfig()
|
||||
await fetchBotStatus()
|
||||
// 定期刷新状态
|
||||
const interval = setInterval(fetchBotStatus, 30000)
|
||||
onUnmounted(() => clearInterval(interval))
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 微信公众号机器人标签样式 */
|
||||
.tab-content-container {
|
||||
height: calc(100vh - 240px);
|
||||
overflow-y: auto;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
</style>
|
||||
@@ -354,4 +354,18 @@ export const useSystemLogApi = () => {
|
||||
getSystemLogSummary,
|
||||
clearSystemLogs
|
||||
}
|
||||
}
|
||||
|
||||
// 微信机器人管理API
|
||||
export const useWechatApi = () => {
|
||||
const getBotConfig = () => useApiFetch('/wechat/bot-config').then(parseApiResponse)
|
||||
const updateBotConfig = (data: any) => useApiFetch('/wechat/bot-config', { method: 'PUT', body: data }).then(parseApiResponse)
|
||||
const getBotStatus = () => useApiFetch('/wechat/bot-status').then(parseApiResponse)
|
||||
const uploadVerifyFile = (formData: FormData) => useApiFetch('/wechat/verify-file', { method: 'POST', body: formData }).then(parseApiResponse)
|
||||
return {
|
||||
getBotConfig,
|
||||
updateBotConfig,
|
||||
getBotStatus,
|
||||
uploadVerifyFile
|
||||
}
|
||||
}
|
||||
@@ -23,13 +23,7 @@
|
||||
</n-tab-pane>
|
||||
|
||||
<n-tab-pane name="wechat" tab="微信公众号">
|
||||
<div class="tab-content-container">
|
||||
<div class="text-center py-12">
|
||||
<i class="fas fa-lock text-4xl text-gray-400 mb-4"></i>
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-white mb-2">功能暂未开放</h3>
|
||||
<p class="text-gray-500 dark:text-gray-400">微信公众号机器人功能正在开发中,敬请期待</p>
|
||||
</div>
|
||||
</div>
|
||||
<WechatBotTab />
|
||||
</n-tab-pane>
|
||||
|
||||
<n-tab-pane name="telegram" tab="Telegram机器人">
|
||||
@@ -59,6 +53,7 @@ import { ref } from 'vue'
|
||||
import AdminPageLayout from '~/components/AdminPageLayout.vue'
|
||||
import TelegramBotTab from '~/components/TelegramBotTab.vue'
|
||||
import QqBotTab from '~/components/QqBotTab.vue'
|
||||
import WechatBotTab from '~/components/WechatBotTab.vue'
|
||||
|
||||
// 设置页面布局
|
||||
definePageMeta({
|
||||
|
||||
Reference in New Issue
Block a user