f
This commit is contained in:
@@ -237,7 +237,7 @@ development:
|
|||||||
|
|
||||||
# 企业微信配置
|
# 企业微信配置
|
||||||
wechat_work:
|
wechat_work:
|
||||||
webhook_url: ""
|
webhook_url: "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=649bf737-28ca-4f30-ad5f-cfb65b2af113"
|
||||||
secret: ""
|
secret: ""
|
||||||
|
|
||||||
# ===========================================
|
# ===========================================
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ import (
|
|||||||
"tyapi-server/internal/domains/certification/services"
|
"tyapi-server/internal/domains/certification/services"
|
||||||
finance_service "tyapi-server/internal/domains/finance/services"
|
finance_service "tyapi-server/internal/domains/finance/services"
|
||||||
user_entities "tyapi-server/internal/domains/user/entities"
|
user_entities "tyapi-server/internal/domains/user/entities"
|
||||||
user_service "tyapi-server/internal/domains/user/services"
|
user_service "tyapi-server/internal/domains/user/services"
|
||||||
|
"tyapi-server/internal/config"
|
||||||
|
"tyapi-server/internal/infrastructure/external/notification"
|
||||||
"tyapi-server/internal/infrastructure/external/storage"
|
"tyapi-server/internal/infrastructure/external/storage"
|
||||||
"tyapi-server/internal/shared/database"
|
"tyapi-server/internal/shared/database"
|
||||||
"tyapi-server/internal/shared/esign"
|
"tyapi-server/internal/shared/esign"
|
||||||
@@ -47,7 +49,8 @@ type CertificationApplicationServiceImpl struct {
|
|||||||
enterpriseInfoSubmitRecordRepo repositories.EnterpriseInfoSubmitRecordRepository
|
enterpriseInfoSubmitRecordRepo repositories.EnterpriseInfoSubmitRecordRepository
|
||||||
txManager *database.TransactionManager
|
txManager *database.TransactionManager
|
||||||
|
|
||||||
logger *zap.Logger
|
wechatWorkService *notification.WeChatWorkService
|
||||||
|
logger *zap.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCertificationApplicationService 创建认证应用服务
|
// NewCertificationApplicationService 创建认证应用服务
|
||||||
@@ -67,7 +70,12 @@ func NewCertificationApplicationService(
|
|||||||
ocrService sharedOCR.OCRService,
|
ocrService sharedOCR.OCRService,
|
||||||
txManager *database.TransactionManager,
|
txManager *database.TransactionManager,
|
||||||
logger *zap.Logger,
|
logger *zap.Logger,
|
||||||
|
cfg *config.Config,
|
||||||
) CertificationApplicationService {
|
) CertificationApplicationService {
|
||||||
|
var wechatSvc *notification.WeChatWorkService
|
||||||
|
if cfg != nil && cfg.WechatWork.WebhookURL != "" {
|
||||||
|
wechatSvc = notification.NewWeChatWorkService(cfg.WechatWork.WebhookURL, cfg.WechatWork.Secret, logger)
|
||||||
|
}
|
||||||
return &CertificationApplicationServiceImpl{
|
return &CertificationApplicationServiceImpl{
|
||||||
aggregateService: aggregateService,
|
aggregateService: aggregateService,
|
||||||
userAggregateService: userAggregateService,
|
userAggregateService: userAggregateService,
|
||||||
@@ -83,6 +91,7 @@ func NewCertificationApplicationService(
|
|||||||
enterpriseInfoSubmitRecordService: enterpriseInfoSubmitRecordService,
|
enterpriseInfoSubmitRecordService: enterpriseInfoSubmitRecordService,
|
||||||
ocrService: ocrService,
|
ocrService: ocrService,
|
||||||
txManager: txManager,
|
txManager: txManager,
|
||||||
|
wechatWorkService: wechatSvc,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1104,6 +1113,35 @@ func (s *CertificationApplicationServiceImpl) completeUserActivationWithoutContr
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 企业认证成功企业微信通知(仅展示企业名称和联系手机)
|
||||||
|
if s.wechatWorkService != nil {
|
||||||
|
user, err := s.userAggregateService.GetUserWithEnterpriseInfo(ctx, cert.UserID)
|
||||||
|
if err == nil {
|
||||||
|
companyName := "未知企业"
|
||||||
|
phone := ""
|
||||||
|
if user.EnterpriseInfo != nil {
|
||||||
|
companyName = user.EnterpriseInfo.CompanyName
|
||||||
|
if user.EnterpriseInfo.LegalPersonPhone != "" {
|
||||||
|
phone = user.EnterpriseInfo.LegalPersonPhone
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if user.Phone != "" && phone == "" {
|
||||||
|
phone = user.Phone
|
||||||
|
}
|
||||||
|
content := fmt.Sprintf(
|
||||||
|
"### 【天远API】企业认证成功\n"+
|
||||||
|
"> 企业名称:%s\n"+
|
||||||
|
"> 联系手机:%s\n"+
|
||||||
|
"> 完成时间:%s\n"+
|
||||||
|
"\n该企业已完成认证,请相关同事同步更新内部系统。",
|
||||||
|
companyName,
|
||||||
|
phone,
|
||||||
|
time.Now().Format("2006-01-02 15:04:05"),
|
||||||
|
)
|
||||||
|
_ = s.wechatWorkService.SendMarkdownMessage(ctx, content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
finance_services "tyapi-server/internal/domains/finance/services"
|
finance_services "tyapi-server/internal/domains/finance/services"
|
||||||
product_repositories "tyapi-server/internal/domains/product/repositories"
|
product_repositories "tyapi-server/internal/domains/product/repositories"
|
||||||
user_repositories "tyapi-server/internal/domains/user/repositories"
|
user_repositories "tyapi-server/internal/domains/user/repositories"
|
||||||
|
"tyapi-server/internal/infrastructure/external/notification"
|
||||||
"tyapi-server/internal/shared/component_report"
|
"tyapi-server/internal/shared/component_report"
|
||||||
"tyapi-server/internal/shared/database"
|
"tyapi-server/internal/shared/database"
|
||||||
"tyapi-server/internal/shared/export"
|
"tyapi-server/internal/shared/export"
|
||||||
@@ -43,6 +44,7 @@ type FinanceApplicationServiceImpl struct {
|
|||||||
exportManager *export.ExportManager
|
exportManager *export.ExportManager
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
config *config.Config
|
config *config.Config
|
||||||
|
wechatWorkService *notification.WeChatWorkService
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFinanceApplicationService 创建财务应用服务
|
// NewFinanceApplicationService 创建财务应用服务
|
||||||
@@ -63,6 +65,11 @@ func NewFinanceApplicationService(
|
|||||||
config *config.Config,
|
config *config.Config,
|
||||||
exportManager *export.ExportManager,
|
exportManager *export.ExportManager,
|
||||||
) FinanceApplicationService {
|
) FinanceApplicationService {
|
||||||
|
var wechatSvc *notification.WeChatWorkService
|
||||||
|
if config != nil && config.WechatWork.WebhookURL != "" {
|
||||||
|
wechatSvc = notification.NewWeChatWorkService(config.WechatWork.WebhookURL, config.WechatWork.Secret, logger)
|
||||||
|
}
|
||||||
|
|
||||||
return &FinanceApplicationServiceImpl{
|
return &FinanceApplicationServiceImpl{
|
||||||
aliPayClient: aliPayClient,
|
aliPayClient: aliPayClient,
|
||||||
wechatPayService: wechatPayService,
|
wechatPayService: wechatPayService,
|
||||||
@@ -79,9 +86,46 @@ func NewFinanceApplicationService(
|
|||||||
exportManager: exportManager,
|
exportManager: exportManager,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
config: config,
|
config: config,
|
||||||
|
wechatWorkService: wechatSvc,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getUserContactInfo 获取企业名称和联系手机号(尽量用企业信息里的手机号,退化到用户登录手机号)
|
||||||
|
func (s *FinanceApplicationServiceImpl) getUserContactInfo(ctx context.Context, userID string) (companyName, phone string) {
|
||||||
|
companyName = "未知企业"
|
||||||
|
phone = ""
|
||||||
|
|
||||||
|
if userID == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := s.userRepo.GetByIDWithEnterpriseInfo(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Warn("获取用户企业信息失败,使用默认企业名称",
|
||||||
|
zap.String("user_id", userID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 登录手机号
|
||||||
|
if user.Phone != "" {
|
||||||
|
phone = user.Phone
|
||||||
|
}
|
||||||
|
|
||||||
|
// 企业名称和企业手机号
|
||||||
|
if user.EnterpriseInfo != nil {
|
||||||
|
if user.EnterpriseInfo.CompanyName != "" {
|
||||||
|
companyName = user.EnterpriseInfo.CompanyName
|
||||||
|
}
|
||||||
|
if user.EnterpriseInfo.LegalPersonPhone != "" {
|
||||||
|
phone = user.EnterpriseInfo.LegalPersonPhone
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (s *FinanceApplicationServiceImpl) CreateWallet(ctx context.Context, cmd *commands.CreateWalletCommand) (*responses.WalletResponse, error) {
|
func (s *FinanceApplicationServiceImpl) CreateWallet(ctx context.Context, cmd *commands.CreateWalletCommand) (*responses.WalletResponse, error) {
|
||||||
// 调用钱包聚合服务创建钱包
|
// 调用钱包聚合服务创建钱包
|
||||||
wallet, err := s.walletService.CreateWallet(ctx, cmd.UserID)
|
wallet, err := s.walletService.CreateWallet(ctx, cmd.UserID)
|
||||||
@@ -936,6 +980,33 @@ func (s *FinanceApplicationServiceImpl) processAlipayPaymentSuccess(ctx context.
|
|||||||
zap.String("amount", amount.String()),
|
zap.String("amount", amount.String()),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 充值成功企业微信通知(仅充值订单,且忽略发送错误)
|
||||||
|
if s.wechatWorkService != nil {
|
||||||
|
// 再次获取充值记录,拿到用户ID
|
||||||
|
rechargeRecord, err := s.rechargeRecordRepo.GetByID(ctx, alipayOrder.RechargeID)
|
||||||
|
if err == nil {
|
||||||
|
companyName, phone := s.getUserContactInfo(ctx, rechargeRecord.UserID)
|
||||||
|
content := fmt.Sprintf(
|
||||||
|
"### 【天远API】用户充值成功通知\n"+
|
||||||
|
"> 企业名称:%s\n"+
|
||||||
|
"> 联系手机:%s\n"+
|
||||||
|
"> 充值渠道:支付宝\n"+
|
||||||
|
"> 充值金额:%s 元\n"+
|
||||||
|
"> 时间:%s\n",
|
||||||
|
companyName,
|
||||||
|
phone,
|
||||||
|
amount.String(),
|
||||||
|
time.Now().Format("2006-01-02 15:04:05"),
|
||||||
|
)
|
||||||
|
_ = s.wechatWorkService.SendMarkdownMessage(ctx, content)
|
||||||
|
} else {
|
||||||
|
s.logger.Warn("获取充值记录失败,跳过企业微信充值通知",
|
||||||
|
zap.String("out_trade_no", outTradeNo),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1681,6 +1752,24 @@ func (s *FinanceApplicationServiceImpl) processWechatPaymentSuccess(ctx context.
|
|||||||
zap.String("user_id", rechargeRecord.UserID),
|
zap.String("user_id", rechargeRecord.UserID),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 微信充值成功企业微信通知(忽略发送错误)
|
||||||
|
if s.wechatWorkService != nil {
|
||||||
|
companyName, phone := s.getUserContactInfo(ctx, rechargeRecord.UserID)
|
||||||
|
content := fmt.Sprintf(
|
||||||
|
"### 【天远API】用户充值成功通知\n"+
|
||||||
|
"> 企业名称:%s\n"+
|
||||||
|
"> 联系手机:%s\n"+
|
||||||
|
"> 充值渠道:微信\n"+
|
||||||
|
"> 充值金额:%s 元\n"+
|
||||||
|
"> 时间:%s\n",
|
||||||
|
companyName,
|
||||||
|
phone,
|
||||||
|
amount.String(),
|
||||||
|
time.Now().Format("2006-01-02 15:04:05"),
|
||||||
|
)
|
||||||
|
_ = s.wechatWorkService.SendMarkdownMessage(ctx, content)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,12 +7,14 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"tyapi-server/internal/application/finance/dto"
|
"tyapi-server/internal/application/finance/dto"
|
||||||
|
"tyapi-server/internal/config"
|
||||||
"tyapi-server/internal/domains/finance/entities"
|
"tyapi-server/internal/domains/finance/entities"
|
||||||
finance_repo "tyapi-server/internal/domains/finance/repositories"
|
finance_repo "tyapi-server/internal/domains/finance/repositories"
|
||||||
"tyapi-server/internal/domains/finance/services"
|
"tyapi-server/internal/domains/finance/services"
|
||||||
"tyapi-server/internal/domains/finance/value_objects"
|
"tyapi-server/internal/domains/finance/value_objects"
|
||||||
user_repo "tyapi-server/internal/domains/user/repositories"
|
user_repo "tyapi-server/internal/domains/user/repositories"
|
||||||
user_service "tyapi-server/internal/domains/user/services"
|
user_service "tyapi-server/internal/domains/user/services"
|
||||||
|
"tyapi-server/internal/infrastructure/external/notification"
|
||||||
"tyapi-server/internal/infrastructure/external/storage"
|
"tyapi-server/internal/infrastructure/external/storage"
|
||||||
|
|
||||||
"github.com/shopspring/decimal"
|
"github.com/shopspring/decimal"
|
||||||
@@ -59,8 +61,9 @@ type InvoiceApplicationServiceImpl struct {
|
|||||||
userAggregateService user_service.UserAggregateService
|
userAggregateService user_service.UserAggregateService
|
||||||
|
|
||||||
// 外部服务依赖
|
// 外部服务依赖
|
||||||
storageService *storage.QiNiuStorageService
|
storageService *storage.QiNiuStorageService
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
|
wechatWorkServer *notification.WeChatWorkService
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewInvoiceApplicationService 创建发票应用服务
|
// NewInvoiceApplicationService 创建发票应用服务
|
||||||
@@ -76,7 +79,13 @@ func NewInvoiceApplicationService(
|
|||||||
userInvoiceInfoService services.UserInvoiceInfoService,
|
userInvoiceInfoService services.UserInvoiceInfoService,
|
||||||
storageService *storage.QiNiuStorageService,
|
storageService *storage.QiNiuStorageService,
|
||||||
logger *zap.Logger,
|
logger *zap.Logger,
|
||||||
|
cfg *config.Config,
|
||||||
) InvoiceApplicationService {
|
) InvoiceApplicationService {
|
||||||
|
var wechatSvc *notification.WeChatWorkService
|
||||||
|
if cfg != nil && cfg.WechatWork.WebhookURL != "" {
|
||||||
|
wechatSvc = notification.NewWeChatWorkService(cfg.WechatWork.WebhookURL, cfg.WechatWork.Secret, logger)
|
||||||
|
}
|
||||||
|
|
||||||
return &InvoiceApplicationServiceImpl{
|
return &InvoiceApplicationServiceImpl{
|
||||||
invoiceRepo: invoiceRepo,
|
invoiceRepo: invoiceRepo,
|
||||||
userInvoiceInfoRepo: userInvoiceInfoRepo,
|
userInvoiceInfoRepo: userInvoiceInfoRepo,
|
||||||
@@ -89,6 +98,7 @@ func NewInvoiceApplicationService(
|
|||||||
userInvoiceInfoService: userInvoiceInfoService,
|
userInvoiceInfoService: userInvoiceInfoService,
|
||||||
storageService: storageService,
|
storageService: storageService,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
wechatWorkServer: wechatSvc,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,7 +185,7 @@ func (s *InvoiceApplicationServiceImpl) ApplyInvoice(ctx context.Context, userID
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 10. 构建响应DTO
|
// 10. 构建响应DTO
|
||||||
return &dto.InvoiceApplicationResponse{
|
resp := &dto.InvoiceApplicationResponse{
|
||||||
ID: application.ID,
|
ID: application.ID,
|
||||||
UserID: application.UserID,
|
UserID: application.UserID,
|
||||||
InvoiceType: application.InvoiceType,
|
InvoiceType: application.InvoiceType,
|
||||||
@@ -183,7 +193,33 @@ func (s *InvoiceApplicationServiceImpl) ApplyInvoice(ctx context.Context, userID
|
|||||||
Status: application.Status,
|
Status: application.Status,
|
||||||
InvoiceInfo: invoiceInfo,
|
InvoiceInfo: invoiceInfo,
|
||||||
CreatedAt: application.CreatedAt,
|
CreatedAt: application.CreatedAt,
|
||||||
}, nil
|
}
|
||||||
|
|
||||||
|
// 11. 企业微信通知(忽略发送错误),只使用企业名称和联系电话
|
||||||
|
if s.wechatWorkServer != nil {
|
||||||
|
companyName := userWithEnterprise.EnterpriseInfo.CompanyName
|
||||||
|
phone := user.Phone
|
||||||
|
if userWithEnterprise.EnterpriseInfo.LegalPersonPhone != "" {
|
||||||
|
phone = userWithEnterprise.EnterpriseInfo.LegalPersonPhone
|
||||||
|
}
|
||||||
|
|
||||||
|
content := fmt.Sprintf(
|
||||||
|
"### 【天远API】用户申请开发票\n"+
|
||||||
|
"> 企业名称:%s\n"+
|
||||||
|
"> 联系手机:%s\n"+
|
||||||
|
"> 申请开票金额:%s 元\n"+
|
||||||
|
"> 发票类型:%s\n"+
|
||||||
|
"> 申请时间:%s\n",
|
||||||
|
companyName,
|
||||||
|
phone,
|
||||||
|
application.Amount.String(),
|
||||||
|
string(application.InvoiceType),
|
||||||
|
time.Now().Format("2006-01-02 15:04:05"),
|
||||||
|
)
|
||||||
|
_ = s.wechatWorkServer.SendMarkdownMessage(ctx, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserInvoiceInfo 获取用户发票信息
|
// GetUserInvoiceInfo 获取用户发票信息
|
||||||
|
|||||||
@@ -885,6 +885,7 @@ func NewContainer() *Container {
|
|||||||
ocrService sharedOCR.OCRService,
|
ocrService sharedOCR.OCRService,
|
||||||
txManager *shared_database.TransactionManager,
|
txManager *shared_database.TransactionManager,
|
||||||
logger *zap.Logger,
|
logger *zap.Logger,
|
||||||
|
cfg *config.Config,
|
||||||
) certification.CertificationApplicationService {
|
) certification.CertificationApplicationService {
|
||||||
return certification.NewCertificationApplicationService(
|
return certification.NewCertificationApplicationService(
|
||||||
aggregateService,
|
aggregateService,
|
||||||
@@ -902,6 +903,7 @@ func NewContainer() *Container {
|
|||||||
ocrService,
|
ocrService,
|
||||||
txManager,
|
txManager,
|
||||||
logger,
|
logger,
|
||||||
|
cfg,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
fx.As(new(certification.CertificationApplicationService)),
|
fx.As(new(certification.CertificationApplicationService)),
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package services
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/shopspring/decimal"
|
"github.com/shopspring/decimal"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
@@ -11,6 +12,7 @@ import (
|
|||||||
"tyapi-server/internal/domains/api/entities"
|
"tyapi-server/internal/domains/api/entities"
|
||||||
api_repositories "tyapi-server/internal/domains/api/repositories"
|
api_repositories "tyapi-server/internal/domains/api/repositories"
|
||||||
user_repositories "tyapi-server/internal/domains/user/repositories"
|
user_repositories "tyapi-server/internal/domains/user/repositories"
|
||||||
|
"tyapi-server/internal/infrastructure/external/notification"
|
||||||
"tyapi-server/internal/infrastructure/external/sms"
|
"tyapi-server/internal/infrastructure/external/sms"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -27,6 +29,7 @@ type BalanceAlertServiceImpl struct {
|
|||||||
smsService *sms.AliSMSService
|
smsService *sms.AliSMSService
|
||||||
config *config.Config
|
config *config.Config
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
|
wechatWorkService *notification.WeChatWorkService
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBalanceAlertService 创建余额预警服务
|
// NewBalanceAlertService 创建余额预警服务
|
||||||
@@ -38,6 +41,10 @@ func NewBalanceAlertService(
|
|||||||
config *config.Config,
|
config *config.Config,
|
||||||
logger *zap.Logger,
|
logger *zap.Logger,
|
||||||
) BalanceAlertService {
|
) BalanceAlertService {
|
||||||
|
var wechatSvc *notification.WeChatWorkService
|
||||||
|
if config != nil && config.WechatWork.WebhookURL != "" {
|
||||||
|
wechatSvc = notification.NewWeChatWorkService(config.WechatWork.WebhookURL, config.WechatWork.Secret, logger)
|
||||||
|
}
|
||||||
return &BalanceAlertServiceImpl{
|
return &BalanceAlertServiceImpl{
|
||||||
apiUserRepo: apiUserRepo,
|
apiUserRepo: apiUserRepo,
|
||||||
userRepo: userRepo,
|
userRepo: userRepo,
|
||||||
@@ -45,6 +52,7 @@ func NewBalanceAlertService(
|
|||||||
smsService: smsService,
|
smsService: smsService,
|
||||||
config: config,
|
config: config,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
wechatWorkService: wechatSvc,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,7 +162,27 @@ func (s *BalanceAlertServiceImpl) sendArrearsAlert(ctx context.Context, apiUser
|
|||||||
zap.Float64("balance", balance),
|
zap.Float64("balance", balance),
|
||||||
zap.String("enterprise_name", enterpriseName))
|
zap.String("enterprise_name", enterpriseName))
|
||||||
|
|
||||||
return s.smsService.SendBalanceAlert(ctx, apiUser.AlertPhone, balance, 0, "arrears", enterpriseName)
|
if err := s.smsService.SendBalanceAlert(ctx, apiUser.AlertPhone, balance, 0, "arrears", enterpriseName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 企业微信欠费告警通知(仅展示企业名称和联系手机)
|
||||||
|
if s.wechatWorkService != nil {
|
||||||
|
content := fmt.Sprintf(
|
||||||
|
"### 【天远API】用户余额欠费告警\n"+
|
||||||
|
"<font color=\"warning\">该企业已发生欠费,请及时联系并处理。</font>\n"+
|
||||||
|
"> 企业名称:%s\n"+
|
||||||
|
"> 联系手机:%s\n"+
|
||||||
|
"> 当前余额:%.2f 元\n"+
|
||||||
|
"> 时间:%s\n",
|
||||||
|
enterpriseName,
|
||||||
|
apiUser.AlertPhone,
|
||||||
|
balance,
|
||||||
|
time.Now().Format("2006-01-02 15:04:05"),
|
||||||
|
)
|
||||||
|
_ = s.wechatWorkService.SendMarkdownMessage(ctx, content)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// sendLowBalanceAlert 发送低余额预警
|
// sendLowBalanceAlert 发送低余额预警
|
||||||
@@ -182,5 +210,27 @@ func (s *BalanceAlertServiceImpl) sendLowBalanceAlert(ctx context.Context, apiUs
|
|||||||
zap.Float64("threshold", apiUser.BalanceAlertThreshold),
|
zap.Float64("threshold", apiUser.BalanceAlertThreshold),
|
||||||
zap.String("enterprise_name", enterpriseName))
|
zap.String("enterprise_name", enterpriseName))
|
||||||
|
|
||||||
return s.smsService.SendBalanceAlert(ctx, apiUser.AlertPhone, balance, apiUser.BalanceAlertThreshold, "low_balance", enterpriseName)
|
if err := s.smsService.SendBalanceAlert(ctx, apiUser.AlertPhone, balance, apiUser.BalanceAlertThreshold, "low_balance", enterpriseName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 企业微信余额预警通知(仅展示企业名称和联系手机)
|
||||||
|
if s.wechatWorkService != nil {
|
||||||
|
content := fmt.Sprintf(
|
||||||
|
"### 【天远API】用户余额预警\n"+
|
||||||
|
"<font color=\"warning\">用户余额已低于预警阈值,请及时跟进。</font>\n"+
|
||||||
|
"> 企业名称:%s\n"+
|
||||||
|
"> 联系手机:%s\n"+
|
||||||
|
"> 当前余额:%.2f 元\n"+
|
||||||
|
"> 预警阈值:%.2f 元\n"+
|
||||||
|
"> 时间:%s\n",
|
||||||
|
enterpriseName,
|
||||||
|
apiUser.AlertPhone,
|
||||||
|
balance,
|
||||||
|
apiUser.BalanceAlertThreshold,
|
||||||
|
time.Now().Format("2006-01-02 15:04:05"),
|
||||||
|
)
|
||||||
|
_ = s.wechatWorkService.SendMarkdownMessage(ctx, content)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ func (s *WeChatWorkService) sendNewApplicationNotification(ctx context.Context,
|
|||||||
applicantName := data["applicant_name"].(string)
|
applicantName := data["applicant_name"].(string)
|
||||||
applicationID := data["application_id"].(string)
|
applicationID := data["application_id"].(string)
|
||||||
|
|
||||||
content := fmt.Sprintf(`## 🆕 新的企业认证申请
|
content := fmt.Sprintf(`## 【天远API】🆕 新的企业认证申请
|
||||||
|
|
||||||
**企业名称**: %s
|
**企业名称**: %s
|
||||||
**申请人**: %s
|
**申请人**: %s
|
||||||
@@ -183,7 +183,7 @@ func (s *WeChatWorkService) sendOCRSuccessNotification(ctx context.Context, data
|
|||||||
confidence := data["confidence"].(float64)
|
confidence := data["confidence"].(float64)
|
||||||
applicationID := data["application_id"].(string)
|
applicationID := data["application_id"].(string)
|
||||||
|
|
||||||
content := fmt.Sprintf(`## ✅ OCR识别成功
|
content := fmt.Sprintf(`## 【天远API】✅ OCR识别成功
|
||||||
|
|
||||||
**企业名称**: %s
|
**企业名称**: %s
|
||||||
**识别置信度**: %.2f%%
|
**识别置信度**: %.2f%%
|
||||||
@@ -204,7 +204,7 @@ func (s *WeChatWorkService) sendOCRFailedNotification(ctx context.Context, data
|
|||||||
applicationID := data["application_id"].(string)
|
applicationID := data["application_id"].(string)
|
||||||
errorMsg := data["error_message"].(string)
|
errorMsg := data["error_message"].(string)
|
||||||
|
|
||||||
content := fmt.Sprintf(`## ❌ OCR识别失败
|
content := fmt.Sprintf(`## 【天远API】❌ OCR识别失败
|
||||||
|
|
||||||
**申请ID**: %s
|
**申请ID**: %s
|
||||||
**错误信息**: %s
|
**错误信息**: %s
|
||||||
@@ -224,7 +224,7 @@ func (s *WeChatWorkService) sendFaceVerifySuccessNotification(ctx context.Contex
|
|||||||
applicationID := data["application_id"].(string)
|
applicationID := data["application_id"].(string)
|
||||||
confidence := data["confidence"].(float64)
|
confidence := data["confidence"].(float64)
|
||||||
|
|
||||||
content := fmt.Sprintf(`## ✅ 人脸识别成功
|
content := fmt.Sprintf(`## 【天远API】✅ 人脸识别成功
|
||||||
|
|
||||||
**申请人**: %s
|
**申请人**: %s
|
||||||
**申请ID**: %s
|
**申请ID**: %s
|
||||||
@@ -246,7 +246,7 @@ func (s *WeChatWorkService) sendFaceVerifyFailedNotification(ctx context.Context
|
|||||||
applicationID := data["application_id"].(string)
|
applicationID := data["application_id"].(string)
|
||||||
errorMsg := data["error_message"].(string)
|
errorMsg := data["error_message"].(string)
|
||||||
|
|
||||||
content := fmt.Sprintf(`## ❌ 人脸识别失败
|
content := fmt.Sprintf(`## 【天远API】❌ 人脸识别失败
|
||||||
|
|
||||||
**申请人**: %s
|
**申请人**: %s
|
||||||
**申请ID**: %s
|
**申请ID**: %s
|
||||||
@@ -269,7 +269,7 @@ func (s *WeChatWorkService) sendAdminApprovedNotification(ctx context.Context, d
|
|||||||
adminName := data["admin_name"].(string)
|
adminName := data["admin_name"].(string)
|
||||||
comment := data["comment"].(string)
|
comment := data["comment"].(string)
|
||||||
|
|
||||||
content := fmt.Sprintf(`## ✅ 管理员审核通过
|
content := fmt.Sprintf(`## 【天远API】✅ 管理员审核通过
|
||||||
|
|
||||||
**企业名称**: %s
|
**企业名称**: %s
|
||||||
**申请ID**: %s
|
**申请ID**: %s
|
||||||
@@ -294,7 +294,7 @@ func (s *WeChatWorkService) sendAdminRejectedNotification(ctx context.Context, d
|
|||||||
adminName := data["admin_name"].(string)
|
adminName := data["admin_name"].(string)
|
||||||
reason := data["reason"].(string)
|
reason := data["reason"].(string)
|
||||||
|
|
||||||
content := fmt.Sprintf(`## ❌ 管理员审核拒绝
|
content := fmt.Sprintf(`## 【天远API】❌ 管理员审核拒绝
|
||||||
|
|
||||||
**企业名称**: %s
|
**企业名称**: %s
|
||||||
**申请ID**: %s
|
**申请ID**: %s
|
||||||
@@ -318,7 +318,7 @@ func (s *WeChatWorkService) sendContractSignedNotification(ctx context.Context,
|
|||||||
applicationID := data["application_id"].(string)
|
applicationID := data["application_id"].(string)
|
||||||
signerName := data["signer_name"].(string)
|
signerName := data["signer_name"].(string)
|
||||||
|
|
||||||
content := fmt.Sprintf(`## 📝 电子合同已签署
|
content := fmt.Sprintf(`## 【天远API】📝 电子合同已签署
|
||||||
|
|
||||||
**企业名称**: %s
|
**企业名称**: %s
|
||||||
**申请ID**: %s
|
**申请ID**: %s
|
||||||
@@ -340,7 +340,7 @@ func (s *WeChatWorkService) sendCertificationCompletedNotification(ctx context.C
|
|||||||
applicationID := data["application_id"].(string)
|
applicationID := data["application_id"].(string)
|
||||||
walletAddress := data["wallet_address"].(string)
|
walletAddress := data["wallet_address"].(string)
|
||||||
|
|
||||||
content := fmt.Sprintf(`## 🎉 企业认证完成
|
content := fmt.Sprintf(`## 【天远API】🎉 企业认证完成
|
||||||
|
|
||||||
**企业名称**: %s
|
**企业名称**: %s
|
||||||
**申请ID**: %s
|
**申请ID**: %s
|
||||||
@@ -475,7 +475,7 @@ func (s *WeChatWorkService) SendSystemAlert(ctx context.Context, level, title, m
|
|||||||
icon = "📢"
|
icon = "📢"
|
||||||
}
|
}
|
||||||
|
|
||||||
content := fmt.Sprintf(`## %s 系统告警
|
content := fmt.Sprintf(`## 【天远API】%s 系统告警
|
||||||
|
|
||||||
**级别**: %s
|
**级别**: %s
|
||||||
**标题**: %s
|
**标题**: %s
|
||||||
@@ -496,7 +496,7 @@ func (s *WeChatWorkService) SendSystemAlert(ctx context.Context, level, title, m
|
|||||||
func (s *WeChatWorkService) SendDailyReport(ctx context.Context, reportData map[string]interface{}) error {
|
func (s *WeChatWorkService) SendDailyReport(ctx context.Context, reportData map[string]interface{}) error {
|
||||||
s.logger.Info("发送每日报告")
|
s.logger.Info("发送每日报告")
|
||||||
|
|
||||||
content := fmt.Sprintf(`## 📊 企业认证系统每日报告
|
content := fmt.Sprintf(`## 【天远API】📊 企业认证系统每日报告
|
||||||
|
|
||||||
**报告日期**: %s
|
**报告日期**: %s
|
||||||
|
|
||||||
|
|||||||
148
internal/infrastructure/external/notification/wechat_work_service_test.go
vendored
Normal file
148
internal/infrastructure/external/notification/wechat_work_service_test.go
vendored
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
package notification_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
"tyapi-server/internal/infrastructure/external/notification"
|
||||||
|
)
|
||||||
|
|
||||||
|
// newTestWeChatWorkService 创建用于测试的企业微信服务实例
|
||||||
|
// 默认使用环境变量 WECOM_WEBHOOK,若未设置则使用项目配置中的 webhook。
|
||||||
|
func newTestWeChatWorkService(t *testing.T) *notification.WeChatWorkService {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
webhook := os.Getenv("WECOM_WEBHOOK")
|
||||||
|
if webhook == "" {
|
||||||
|
// 使用你提供的 webhook 地址
|
||||||
|
webhook = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=649bf737-28ca-4f30-ad5f-cfb65b2af113"
|
||||||
|
}
|
||||||
|
|
||||||
|
logger, _ := zap.NewDevelopment()
|
||||||
|
return notification.NewWeChatWorkService(webhook, "", logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestWeChatWork_SendAllBusinessNotifications
|
||||||
|
// 手动运行该用例,将依次向企业微信群推送 5 种业务场景的通知:
|
||||||
|
// 1. 用户充值成功
|
||||||
|
// 2. 用户申请开发票
|
||||||
|
// 3. 用户企业认证成功
|
||||||
|
// 4. 用户余额低于阈值
|
||||||
|
// 5. 用户余额欠费
|
||||||
|
//
|
||||||
|
// 注意:
|
||||||
|
// - 通知中只使用企业名称和手机号码,不展示用户ID
|
||||||
|
// - 默认使用示例企业名称和手机号,实际使用时请根据需要修改
|
||||||
|
func TestWeChatWork_SendAllBusinessNotifications(t *testing.T) {
|
||||||
|
svc := newTestWeChatWorkService(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// 示例企业信息(实际可按需修改)
|
||||||
|
enterpriseName := "测试企业有限公司"
|
||||||
|
phone := "13800000000"
|
||||||
|
|
||||||
|
now := time.Now().Format("2006-01-02 15:04:05")
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
content string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "recharge_success",
|
||||||
|
content: fmt.Sprintf(
|
||||||
|
"### 【天远API】用户充值成功通知\n"+
|
||||||
|
"> 企业名称:%s\n"+
|
||||||
|
"> 联系手机:%s\n"+
|
||||||
|
"> 充值金额:%s 元\n"+
|
||||||
|
"> 入账总额:%s 元(含赠送)\n"+
|
||||||
|
"> 时间:%s\n",
|
||||||
|
enterpriseName,
|
||||||
|
phone,
|
||||||
|
"1000.00",
|
||||||
|
"1050.00",
|
||||||
|
now,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invoice_applied",
|
||||||
|
content: fmt.Sprintf(
|
||||||
|
"### 【天远API】用户申请开发票\n"+
|
||||||
|
"> 企业名称:%s\n"+
|
||||||
|
"> 联系手机:%s\n"+
|
||||||
|
"> 申请开票金额:%s 元\n"+
|
||||||
|
"> 发票类型:%s\n"+
|
||||||
|
"> 申请时间:%s\n"+
|
||||||
|
"\n请财务尽快审核并开具发票。",
|
||||||
|
enterpriseName,
|
||||||
|
phone,
|
||||||
|
"500.00",
|
||||||
|
"增值税专用发票",
|
||||||
|
now,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "certification_completed",
|
||||||
|
content: fmt.Sprintf(
|
||||||
|
"### 【天远API】企业认证成功\n"+
|
||||||
|
"> 企业名称:%s\n"+
|
||||||
|
"> 联系手机:%s\n"+
|
||||||
|
"> 完成时间:%s\n"+
|
||||||
|
"\n该企业已完成认证,请相关同事同步更新内部系统并关注后续接入情况。",
|
||||||
|
enterpriseName,
|
||||||
|
phone,
|
||||||
|
now,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "low_balance_alert",
|
||||||
|
content: fmt.Sprintf(
|
||||||
|
"### 【天远API】用户余额预警\n"+
|
||||||
|
"<font color=\"warning\">用户余额已低于预警阈值,请及时跟进。</font>\n"+
|
||||||
|
"> 企业名称:%s\n"+
|
||||||
|
"> 联系手机:%s\n"+
|
||||||
|
"> 当前余额:%s 元\n"+
|
||||||
|
"> 预警阈值:%s 元\n"+
|
||||||
|
"> 时间:%s\n",
|
||||||
|
enterpriseName,
|
||||||
|
phone,
|
||||||
|
"180.00",
|
||||||
|
"200.00",
|
||||||
|
now,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "arrears_alert",
|
||||||
|
content: fmt.Sprintf(
|
||||||
|
"### 【天远API】用户余额欠费告警\n"+
|
||||||
|
"<font color=\"warning\">该企业已发生欠费,请及时联系并处理。</font>\n"+
|
||||||
|
"> 企业名称:%s\n"+
|
||||||
|
"> 联系手机:%s\n"+
|
||||||
|
"> 当前余额:%s 元\n"+
|
||||||
|
"> 欠费金额:%s 元\n"+
|
||||||
|
"> 时间:%s\n",
|
||||||
|
enterpriseName,
|
||||||
|
phone,
|
||||||
|
"-50.00",
|
||||||
|
"50.00",
|
||||||
|
now,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
if err := svc.SendMarkdownMessage(ctx, tc.content); err != nil {
|
||||||
|
t.Fatalf("发送场景[%s]通知失败: %v", tc.name, err)
|
||||||
|
}
|
||||||
|
// 简单间隔,避免瞬时发送过多消息
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user