2025-07-11 21:05:58 +08:00
|
|
|
|
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 {
|
2025-07-13 16:36:20 +08:00
|
|
|
|
errmsg := response["errmsg"].(string)
|
|
|
|
|
|
return fmt.Errorf("企业微信API错误: %d - %s", int(errCode), errmsg)
|
2025-07-11 21:05:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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×tamp=%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)
|
|
|
|
|
|
}
|