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×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) }