Files
tyapi-server/internal/shared/notification/wechat_work_service.go
2025-07-11 21:05:58 +08:00

516 lines
14 KiB
Go
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package notification
import (
"bytes"
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"time"
"go.uber.org/zap"
)
// WeChatWorkService 企业微信通知服务
type WeChatWorkService struct {
webhookURL string
secret string
timeout time.Duration
logger *zap.Logger
}
// WechatWorkConfig 企业微信配置
type WechatWorkConfig struct {
WebhookURL string `yaml:"webhook_url"`
Timeout time.Duration `yaml:"timeout"`
}
// WechatWorkMessage 企业微信消息
type WechatWorkMessage struct {
MsgType string `json:"msgtype"`
Text *WechatWorkText `json:"text,omitempty"`
Markdown *WechatWorkMarkdown `json:"markdown,omitempty"`
}
// WechatWorkText 文本消息
type WechatWorkText struct {
Content string `json:"content"`
MentionedList []string `json:"mentioned_list,omitempty"`
MentionedMobileList []string `json:"mentioned_mobile_list,omitempty"`
}
// WechatWorkMarkdown Markdown消息
type WechatWorkMarkdown struct {
Content string `json:"content"`
}
// NewWeChatWorkService 创建企业微信通知服务
func NewWeChatWorkService(webhookURL, secret string, logger *zap.Logger) *WeChatWorkService {
return &WeChatWorkService{
webhookURL: webhookURL,
secret: secret,
timeout: 30 * time.Second,
logger: logger,
}
}
// SendTextMessage 发送文本消息
func (s *WeChatWorkService) SendTextMessage(ctx context.Context, content string, mentionedList []string, mentionedMobileList []string) error {
s.logger.Info("发送企业微信文本消息",
zap.String("content", content),
zap.Strings("mentioned_list", mentionedList),
)
message := map[string]interface{}{
"msgtype": "text",
"text": map[string]interface{}{
"content": content,
"mentioned_list": mentionedList,
"mentioned_mobile_list": mentionedMobileList,
},
}
return s.sendMessage(ctx, message)
}
// SendMarkdownMessage 发送Markdown消息
func (s *WeChatWorkService) SendMarkdownMessage(ctx context.Context, content string) error {
s.logger.Info("发送企业微信Markdown消息", zap.String("content", content))
message := map[string]interface{}{
"msgtype": "markdown",
"markdown": map[string]interface{}{
"content": content,
},
}
return s.sendMessage(ctx, message)
}
// SendCardMessage 发送卡片消息
func (s *WeChatWorkService) SendCardMessage(ctx context.Context, title, description, url string, btnText string) error {
s.logger.Info("发送企业微信卡片消息",
zap.String("title", title),
zap.String("description", description),
)
message := map[string]interface{}{
"msgtype": "template_card",
"template_card": map[string]interface{}{
"card_type": "text_notice",
"source": map[string]interface{}{
"icon_url": "https://example.com/icon.png",
"desc": "企业认证系统",
},
"main_title": map[string]interface{}{
"title": title,
},
"horizontal_content_list": []map[string]interface{}{
{
"keyname": "描述",
"value": description,
},
},
"jump_list": []map[string]interface{}{
{
"type": "1",
"title": btnText,
"url": url,
},
},
},
}
return s.sendMessage(ctx, message)
}
// SendCertificationNotification 发送认证相关通知
func (s *WeChatWorkService) SendCertificationNotification(ctx context.Context, notificationType string, data map[string]interface{}) error {
s.logger.Info("发送认证通知", zap.String("type", notificationType))
switch notificationType {
case "new_application":
return s.sendNewApplicationNotification(ctx, data)
case "ocr_success":
return s.sendOCRSuccessNotification(ctx, data)
case "ocr_failed":
return s.sendOCRFailedNotification(ctx, data)
case "face_verify_success":
return s.sendFaceVerifySuccessNotification(ctx, data)
case "face_verify_failed":
return s.sendFaceVerifyFailedNotification(ctx, data)
case "admin_approved":
return s.sendAdminApprovedNotification(ctx, data)
case "admin_rejected":
return s.sendAdminRejectedNotification(ctx, data)
case "contract_signed":
return s.sendContractSignedNotification(ctx, data)
case "certification_completed":
return s.sendCertificationCompletedNotification(ctx, data)
default:
return fmt.Errorf("不支持的通知类型: %s", notificationType)
}
}
// sendNewApplicationNotification 发送新申请通知
func (s *WeChatWorkService) sendNewApplicationNotification(ctx context.Context, data map[string]interface{}) error {
companyName := data["company_name"].(string)
applicantName := data["applicant_name"].(string)
applicationID := data["application_id"].(string)
content := fmt.Sprintf(`## 🆕 新的企业认证申请
**企业名称**: %s
**申请人**: %s
**申请ID**: %s
**申请时间**: %s
请管理员及时审核处理。`,
companyName,
applicantName,
applicationID,
time.Now().Format("2006-01-02 15:04:05"))
return s.SendMarkdownMessage(ctx, content)
}
// sendOCRSuccessNotification 发送OCR识别成功通知
func (s *WeChatWorkService) sendOCRSuccessNotification(ctx context.Context, data map[string]interface{}) error {
companyName := data["company_name"].(string)
confidence := data["confidence"].(float64)
applicationID := data["application_id"].(string)
content := fmt.Sprintf(`## ✅ OCR识别成功
**企业名称**: %s
**识别置信度**: %.2f%%
**申请ID**: %s
**识别时间**: %s
营业执照信息已自动提取,请用户确认信息。`,
companyName,
confidence*100,
applicationID,
time.Now().Format("2006-01-02 15:04:05"))
return s.SendMarkdownMessage(ctx, content)
}
// sendOCRFailedNotification 发送OCR识别失败通知
func (s *WeChatWorkService) sendOCRFailedNotification(ctx context.Context, data map[string]interface{}) error {
applicationID := data["application_id"].(string)
errorMsg := data["error_message"].(string)
content := fmt.Sprintf(`## ❌ OCR识别失败
**申请ID**: %s
**错误信息**: %s
**失败时间**: %s
请检查营业执照图片质量或联系技术支持。`,
applicationID,
errorMsg,
time.Now().Format("2006-01-02 15:04:05"))
return s.SendMarkdownMessage(ctx, content)
}
// sendFaceVerifySuccessNotification 发送人脸识别成功通知
func (s *WeChatWorkService) sendFaceVerifySuccessNotification(ctx context.Context, data map[string]interface{}) error {
applicantName := data["applicant_name"].(string)
applicationID := data["application_id"].(string)
confidence := data["confidence"].(float64)
content := fmt.Sprintf(`## ✅ 人脸识别成功
**申请人**: %s
**申请ID**: %s
**识别置信度**: %.2f%%
**识别时间**: %s
身份验证通过,可以进行下一步操作。`,
applicantName,
applicationID,
confidence*100,
time.Now().Format("2006-01-02 15:04:05"))
return s.SendMarkdownMessage(ctx, content)
}
// sendFaceVerifyFailedNotification 发送人脸识别失败通知
func (s *WeChatWorkService) sendFaceVerifyFailedNotification(ctx context.Context, data map[string]interface{}) error {
applicantName := data["applicant_name"].(string)
applicationID := data["application_id"].(string)
errorMsg := data["error_message"].(string)
content := fmt.Sprintf(`## ❌ 人脸识别失败
**申请人**: %s
**申请ID**: %s
**错误信息**: %s
**失败时间**: %s
请重新进行人脸识别或联系技术支持。`,
applicantName,
applicationID,
errorMsg,
time.Now().Format("2006-01-02 15:04:05"))
return s.SendMarkdownMessage(ctx, content)
}
// sendAdminApprovedNotification 发送管理员审核通过通知
func (s *WeChatWorkService) sendAdminApprovedNotification(ctx context.Context, data map[string]interface{}) error {
companyName := data["company_name"].(string)
applicationID := data["application_id"].(string)
adminName := data["admin_name"].(string)
comment := data["comment"].(string)
content := fmt.Sprintf(`## ✅ 管理员审核通过
**企业名称**: %s
**申请ID**: %s
**审核人**: %s
**审核意见**: %s
**审核时间**: %s
认证申请已通过审核,请用户签署电子合同。`,
companyName,
applicationID,
adminName,
comment,
time.Now().Format("2006-01-02 15:04:05"))
return s.SendMarkdownMessage(ctx, content)
}
// sendAdminRejectedNotification 发送管理员审核拒绝通知
func (s *WeChatWorkService) sendAdminRejectedNotification(ctx context.Context, data map[string]interface{}) error {
companyName := data["company_name"].(string)
applicationID := data["application_id"].(string)
adminName := data["admin_name"].(string)
reason := data["reason"].(string)
content := fmt.Sprintf(`## ❌ 管理员审核拒绝
**企业名称**: %s
**申请ID**: %s
**审核人**: %s
**拒绝原因**: %s
**审核时间**: %s
认证申请被拒绝,请根据反馈意见重新提交。`,
companyName,
applicationID,
adminName,
reason,
time.Now().Format("2006-01-02 15:04:05"))
return s.SendMarkdownMessage(ctx, content)
}
// sendContractSignedNotification 发送合同签署通知
func (s *WeChatWorkService) sendContractSignedNotification(ctx context.Context, data map[string]interface{}) error {
companyName := data["company_name"].(string)
applicationID := data["application_id"].(string)
signerName := data["signer_name"].(string)
content := fmt.Sprintf(`## 📝 电子合同已签署
**企业名称**: %s
**申请ID**: %s
**签署人**: %s
**签署时间**: %s
电子合同签署完成系统将自动生成钱包和Access Key。`,
companyName,
applicationID,
signerName,
time.Now().Format("2006-01-02 15:04:05"))
return s.SendMarkdownMessage(ctx, content)
}
// sendCertificationCompletedNotification 发送认证完成通知
func (s *WeChatWorkService) sendCertificationCompletedNotification(ctx context.Context, data map[string]interface{}) error {
companyName := data["company_name"].(string)
applicationID := data["application_id"].(string)
walletAddress := data["wallet_address"].(string)
content := fmt.Sprintf(`## 🎉 企业认证完成
**企业名称**: %s
**申请ID**: %s
**钱包地址**: %s
**完成时间**: %s
恭喜企业认证流程已完成钱包和Access Key已生成。`,
companyName,
applicationID,
walletAddress,
time.Now().Format("2006-01-02 15:04:05"))
return s.SendMarkdownMessage(ctx, content)
}
// sendMessage 发送消息到企业微信
func (s *WeChatWorkService) sendMessage(ctx context.Context, message map[string]interface{}) error {
// 生成签名URL
signedURL := s.generateSignedURL()
// 序列化消息
messageBytes, err := json.Marshal(message)
if err != nil {
return fmt.Errorf("序列化消息失败: %w", err)
}
// 创建HTTP客户端
client := &http.Client{
Timeout: s.timeout,
}
// 创建请求
req, err := http.NewRequestWithContext(ctx, "POST", signedURL, bytes.NewBuffer(messageBytes))
if err != nil {
return fmt.Errorf("创建请求失败: %w", err)
}
// 设置请求头
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", "tyapi-server/1.0")
// 发送请求
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("发送请求失败: %w", err)
}
defer resp.Body.Close()
// 检查响应状态
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("请求失败,状态码: %d", resp.StatusCode)
}
// 解析响应
var response map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
return fmt.Errorf("解析响应失败: %w", err)
}
// 检查错误码
if errCode, ok := response["errcode"].(float64); ok && errCode != 0 {
errMsg := response["errmsg"].(string)
return fmt.Errorf("企业微信API错误: %d - %s", int(errCode), errMsg)
}
s.logger.Info("企业微信消息发送成功", zap.Any("response", response))
return nil
}
// generateSignedURL 生成带签名的URL
func (s *WeChatWorkService) generateSignedURL() string {
if s.secret == "" {
return s.webhookURL
}
// 生成时间戳
timestamp := time.Now().Unix()
// 生成随机字符串(这里简化处理,实际应该使用随机字符串)
nonce := fmt.Sprintf("%d", timestamp)
// 构建签名字符串
signStr := fmt.Sprintf("%d\n%s", timestamp, s.secret)
// 计算签名
h := hmac.New(sha256.New, []byte(s.secret))
h.Write([]byte(signStr))
signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
// 构建签名URL
return fmt.Sprintf("%s&timestamp=%d&nonce=%s&sign=%s",
s.webhookURL, timestamp, nonce, signature)
}
// SendSystemAlert 发送系统告警
func (s *WeChatWorkService) SendSystemAlert(ctx context.Context, level, title, message string) error {
s.logger.Info("发送系统告警",
zap.String("level", level),
zap.String("title", title),
)
// 根据告警级别选择图标
var icon string
switch level {
case "info":
icon = ""
case "warning":
icon = "⚠️"
case "error":
icon = "🚨"
case "critical":
icon = "💥"
default:
icon = "📢"
}
content := fmt.Sprintf(`## %s 系统告警
**级别**: %s
**标题**: %s
**消息**: %s
**时间**: %s
请相关人员及时处理。`,
icon,
level,
title,
message,
time.Now().Format("2006-01-02 15:04:05"))
return s.SendMarkdownMessage(ctx, content)
}
// SendDailyReport 发送每日报告
func (s *WeChatWorkService) SendDailyReport(ctx context.Context, reportData map[string]interface{}) error {
s.logger.Info("发送每日报告")
content := fmt.Sprintf(`## 📊 企业认证系统每日报告
**报告日期**: %s
### 统计数据
- **新增申请**: %d
- **OCR识别成功**: %d
- **OCR识别失败**: %d
- **人脸识别成功**: %d
- **人脸识别失败**: %d
- **审核通过**: %d
- **审核拒绝**: %d
- **认证完成**: %d
### 系统状态
- **系统运行时间**: %s
- **API调用次数**: %d
- **错误次数**: %d
祝您工作愉快!`,
time.Now().Format("2006-01-02"),
reportData["new_applications"],
reportData["ocr_success"],
reportData["ocr_failed"],
reportData["face_verify_success"],
reportData["face_verify_failed"],
reportData["admin_approved"],
reportData["admin_rejected"],
reportData["certification_completed"],
reportData["uptime"],
reportData["api_calls"],
reportData["errors"])
return s.SendMarkdownMessage(ctx, content)
}