diff --git a/db/converter/wechat_bot_converter.go b/db/converter/wechat_bot_converter.go new file mode 100644 index 0000000..c409a4f --- /dev/null +++ b/db/converter/wechat_bot_converter.go @@ -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 +} \ No newline at end of file diff --git a/db/dto/wechat_bot.go b/db/dto/wechat_bot.go new file mode 100644 index 0000000..c6679fe --- /dev/null +++ b/db/dto/wechat_bot.go @@ -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"` +} \ No newline at end of file diff --git a/db/entity/system_config_constants.go b/db/entity/system_config_constants.go index f4bfe47..e84d0c5 100644 --- a/db/entity/system_config_constants.go +++ b/db/entity/system_config_constants.go @@ -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 = "" diff --git a/go.mod b/go.mod index f107f09..e02358c 100644 --- a/go.mod +++ b/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 ) diff --git a/go.sum b/go.sum index f0dfd3a..34c498f 100644 --- a/go.sum +++ b/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= diff --git a/handlers/file_handler.go b/handlers/file_handler.go index 3309164..d84cdbb 100644 --- a/handlers/file_handler.go +++ b/handlers/file_handler.go @@ -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 +} diff --git a/handlers/wechat_handler.go b/handlers/wechat_handler.go new file mode 100644 index 0000000..c4e5613 --- /dev/null +++ b/handlers/wechat_handler.go @@ -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 +} \ No newline at end of file diff --git a/main.go b/main.go index 5165e98..c78fe62 100644 --- a/main.go +++ b/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) } // 设置监控系统 diff --git a/nginx/conf.d/default.conf b/nginx/conf.d/default.conf index 6d86745..87f1b0d 100644 --- a/nginx/conf.d/default.conf +++ b/nginx/conf.d/default.conf @@ -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; diff --git a/services/wechat_bot_service.go b/services/wechat_bot_service.go new file mode 100644 index 0000000..b595b29 --- /dev/null +++ b/services/wechat_bot_service.go @@ -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{}, + } +} \ No newline at end of file diff --git a/services/wechat_bot_service_impl.go b/services/wechat_bot_service_impl.go new file mode 100644 index 0000000..c6c9896 --- /dev/null +++ b/services/wechat_bot_service_impl.go @@ -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 +} \ No newline at end of file diff --git a/web/components/WechatBotTab.vue b/web/components/WechatBotTab.vue new file mode 100644 index 0000000..d06dc2f --- /dev/null +++ b/web/components/WechatBotTab.vue @@ -0,0 +1,456 @@ + + + + + \ No newline at end of file diff --git a/web/composables/useApi.ts b/web/composables/useApi.ts index d8d5b84..d1c9399 100644 --- a/web/composables/useApi.ts +++ b/web/composables/useApi.ts @@ -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 + } } \ No newline at end of file diff --git a/web/pages/admin/bot.vue b/web/pages/admin/bot.vue index c4eada5..196065f 100644 --- a/web/pages/admin/bot.vue +++ b/web/pages/admin/bot.vue @@ -23,13 +23,7 @@ -
-
- -

功能暂未开放

-

微信公众号机器人功能正在开发中,敬请期待

-
-
+
@@ -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({