Compare commits
45 Commits
bc6dce21ee
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| c5970da195 | |||
| 1bcb4a9c2e | |||
| 8877cf9691 | |||
| f63e6df9f9 | |||
| a00fe12141 | |||
| 6b80182986 | |||
| bf4c114ee2 | |||
| 4cd3954574 | |||
| d7a5589873 | |||
| b0ec75d1af | |||
| 57d18be972 | |||
| 3d8775b6dc | |||
| f40950f890 | |||
| ba21a8f965 | |||
| 7d2716da7a | |||
| 9a7bda9527 | |||
| abdae033f0 | |||
| 96abacd392 | |||
| 4e6c93413e | |||
| 9c8dbd458f | |||
| 9e9cee02f5 | |||
| 360bd579ce | |||
| db889ccba0 | |||
| 25a4961328 | |||
| 578e68a76b | |||
| 019e47896d | |||
| c0898e6829 | |||
| 4ee6e891cd | |||
| 44b5f6b145 | |||
| 677b7362cf | |||
| 02dbc02fe8 | |||
| 374143995e | |||
| 7a957a6b87 | |||
| c885d562ee | |||
| 9f36cd8b63 | |||
| 4122f874fc | |||
| 9a32387b21 | |||
| 7bf9150cfc | |||
| fecd5a38fd | |||
| 2636d9dff6 | |||
| 927b08b871 | |||
| dedd4a60a4 | |||
| a54a19e439 | |||
| 6dd392f673 | |||
| 8d5da9d88e |
@@ -53,9 +53,8 @@ COPY --from=builder /app/tyapi-server .
|
|||||||
COPY config.yaml .
|
COPY config.yaml .
|
||||||
COPY configs/ ./configs/
|
COPY configs/ ./configs/
|
||||||
|
|
||||||
# 复制资源文件(直接从构建上下文复制,与配置文件一致)
|
# 复制资源文件(报告模板、PDF、组件等)
|
||||||
COPY resources/etc ./resources/etc
|
COPY resources ./resources
|
||||||
COPY resources/pdf ./resources/pdf
|
|
||||||
|
|
||||||
# 暴露端口
|
# 暴露端口
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|||||||
@@ -276,6 +276,8 @@ wallet:
|
|||||||
default_credit_limit: 50.00
|
default_credit_limit: 50.00
|
||||||
min_amount: "100.00" # 生产环境最低充值金额
|
min_amount: "100.00" # 生产环境最低充值金额
|
||||||
max_amount: "100000.00" # 单次最高充值金额
|
max_amount: "100000.00" # 单次最高充值金额
|
||||||
|
recharge_bonus_enabled: true # 是否启用充值赠送,设为 false 时仅展示商务洽谈提示
|
||||||
|
api_store_recharge_tip: "" # 关闭赠送时展示的提示文案,为空则使用默认文案
|
||||||
# 支付宝充值赠送配置
|
# 支付宝充值赠送配置
|
||||||
alipay_recharge_bonus:
|
alipay_recharge_bonus:
|
||||||
- recharge_amount: 1000.00 # 充值1000元
|
- recharge_amount: 1000.00 # 充值1000元
|
||||||
|
|||||||
@@ -108,6 +108,8 @@ wallet:
|
|||||||
default_credit_limit: 0.01
|
default_credit_limit: 0.01
|
||||||
min_amount: "0.01" # 生产环境最低充值金额
|
min_amount: "0.01" # 生产环境最低充值金额
|
||||||
max_amount: "100000.00" # 单次最高充值金额
|
max_amount: "100000.00" # 单次最高充值金额
|
||||||
|
recharge_bonus_enabled: false # 开发环境可设为 true 测试赠送
|
||||||
|
api_store_recharge_tip: "尊敬的客户,若您的充值金额较大或有批量调价需求,为获取专属商务优惠方案,请直接联系我司商务团队进行洽谈。感谢您的支持!"
|
||||||
# 支付宝充值赠送配置
|
# 支付宝充值赠送配置
|
||||||
alipay_recharge_bonus:
|
alipay_recharge_bonus:
|
||||||
- recharge_amount: 0.01 # 充值1000元
|
- recharge_amount: 0.01 # 充值1000元
|
||||||
|
|||||||
@@ -109,7 +109,9 @@ wallet:
|
|||||||
default_credit_limit: 50.00
|
default_credit_limit: 50.00
|
||||||
min_amount: "100.00" # 生产环境最低充值金额
|
min_amount: "100.00" # 生产环境最低充值金额
|
||||||
max_amount: "100000.00" # 单次最高充值金额
|
max_amount: "100000.00" # 单次最高充值金额
|
||||||
# 支付宝充值赠送配置
|
recharge_bonus_enabled: false # 暂不赠送,展示商务洽谈提示
|
||||||
|
api_store_recharge_tip: "尊敬的客户,若您的充值金额较大或有批量调价需求,为获取专属商务优惠方案,请直接联系我司商务团队进行洽谈。感谢您的支持!"
|
||||||
|
# 支付宝充值赠送配置(recharge_bonus_enabled 为 true 时生效)
|
||||||
alipay_recharge_bonus:
|
alipay_recharge_bonus:
|
||||||
- recharge_amount: 1000.00 # 充值1000元
|
- recharge_amount: 1000.00 # 充值1000元
|
||||||
bonus_amount: 50.00 # 赠送50元
|
bonus_amount: 50.00 # 赠送50元
|
||||||
|
|||||||
@@ -89,7 +89,8 @@ services:
|
|||||||
- "25000:8080"
|
- "25000:8080"
|
||||||
volumes:
|
volumes:
|
||||||
- ./logs:/app/logs
|
- ./logs:/app/logs
|
||||||
- ./resources/Pure_Component:/app/resources/Pure_Component
|
# 挂载完整 resources 目录(包含 qiye.html、Pure_Component、pdf 等)
|
||||||
|
- ./resources:/app/resources
|
||||||
# 持久化PDF缓存目录,确保生成的PDF在容器重启后仍然存在
|
# 持久化PDF缓存目录,确保生成的PDF在容器重启后仍然存在
|
||||||
- ./storage/pdfg-cache:/app/storage/pdfg-cache
|
- ./storage/pdfg-cache:/app/storage/pdfg-cache
|
||||||
# user: "1001:1001" # 注释掉,使用root权限运行
|
# user: "1001:1001" # 注释掉,使用root权限运行
|
||||||
|
|||||||
@@ -260,6 +260,7 @@ func (a *Application) autoMigrate(db *gorm.DB) error {
|
|||||||
// api
|
// api
|
||||||
&apiEntities.ApiUser{},
|
&apiEntities.ApiUser{},
|
||||||
&apiEntities.ApiCall{},
|
&apiEntities.ApiCall{},
|
||||||
|
&apiEntities.Report{},
|
||||||
|
|
||||||
// 任务域
|
// 任务域
|
||||||
&taskEntities.AsyncTask{},
|
&taskEntities.AsyncTask{},
|
||||||
|
|||||||
@@ -609,8 +609,6 @@ func (s *ApiApplicationServiceImpl) GetUserApiCalls(ctx context.Context, userID
|
|||||||
// 转换为响应DTO
|
// 转换为响应DTO
|
||||||
var items []dto.ApiCallRecordResponse
|
var items []dto.ApiCallRecordResponse
|
||||||
for _, call := range calls {
|
for _, call := range calls {
|
||||||
// 出于安全考虑,不再在数据库中存储或解密真实请求参数
|
|
||||||
// 这里只保留数据库中的原始占位值(通常为空字符串)
|
|
||||||
requestParamsStr := call.RequestParams
|
requestParamsStr := call.RequestParams
|
||||||
|
|
||||||
item := dto.ApiCallRecordResponse{
|
item := dto.ApiCallRecordResponse{
|
||||||
|
|||||||
@@ -110,6 +110,8 @@ type AlipayRechargeOrderResponse struct {
|
|||||||
type RechargeConfigResponse struct {
|
type RechargeConfigResponse struct {
|
||||||
MinAmount string `json:"min_amount"` // 最低充值金额
|
MinAmount string `json:"min_amount"` // 最低充值金额
|
||||||
MaxAmount string `json:"max_amount"` // 最高充值金额
|
MaxAmount string `json:"max_amount"` // 最高充值金额
|
||||||
|
RechargeBonusEnabled bool `json:"recharge_bonus_enabled"` // 是否启用充值赠送
|
||||||
|
ApiStoreRechargeTip string `json:"api_store_recharge_tip"` // API 商店充值提示(大额/批量联系商务)
|
||||||
AlipayRechargeBonus []AlipayRechargeBonusRuleResponse `json:"alipay_recharge_bonus"`
|
AlipayRechargeBonus []AlipayRechargeBonusRuleResponse `json:"alipay_recharge_bonus"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1337,16 +1337,25 @@ func (s *FinanceApplicationServiceImpl) GetAdminRechargeRecords(ctx context.Cont
|
|||||||
|
|
||||||
// GetRechargeConfig 获取充值配置
|
// GetRechargeConfig 获取充值配置
|
||||||
func (s *FinanceApplicationServiceImpl) GetRechargeConfig(ctx context.Context) (*responses.RechargeConfigResponse, error) {
|
func (s *FinanceApplicationServiceImpl) GetRechargeConfig(ctx context.Context) (*responses.RechargeConfigResponse, error) {
|
||||||
bonus := make([]responses.AlipayRechargeBonusRuleResponse, 0, len(s.config.Wallet.AliPayRechargeBonus))
|
bonus := make([]responses.AlipayRechargeBonusRuleResponse, 0)
|
||||||
|
if s.config.Wallet.RechargeBonusEnabled && len(s.config.Wallet.AliPayRechargeBonus) > 0 {
|
||||||
|
bonus = make([]responses.AlipayRechargeBonusRuleResponse, 0, len(s.config.Wallet.AliPayRechargeBonus))
|
||||||
for _, rule := range s.config.Wallet.AliPayRechargeBonus {
|
for _, rule := range s.config.Wallet.AliPayRechargeBonus {
|
||||||
bonus = append(bonus, responses.AlipayRechargeBonusRuleResponse{
|
bonus = append(bonus, responses.AlipayRechargeBonusRuleResponse{
|
||||||
RechargeAmount: rule.RechargeAmount,
|
RechargeAmount: rule.RechargeAmount,
|
||||||
BonusAmount: rule.BonusAmount,
|
BonusAmount: rule.BonusAmount,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
tip := s.config.Wallet.ApiStoreRechargeTip
|
||||||
|
if tip == "" && !s.config.Wallet.RechargeBonusEnabled {
|
||||||
|
tip = "尊敬的客户,若您的充值金额较大或有批量调价需求,为获取专属商务优惠方案,请直接联系我司商务团队进行洽谈。感谢您的支持!"
|
||||||
|
}
|
||||||
return &responses.RechargeConfigResponse{
|
return &responses.RechargeConfigResponse{
|
||||||
MinAmount: s.config.Wallet.MinAmount,
|
MinAmount: s.config.Wallet.MinAmount,
|
||||||
MaxAmount: s.config.Wallet.MaxAmount,
|
MaxAmount: s.config.Wallet.MaxAmount,
|
||||||
|
RechargeBonusEnabled: s.config.Wallet.RechargeBonusEnabled,
|
||||||
|
ApiStoreRechargeTip: tip,
|
||||||
AlipayRechargeBonus: bonus,
|
AlipayRechargeBonus: bonus,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -1651,9 +1660,9 @@ func (s *FinanceApplicationServiceImpl) processWechatPaymentSuccess(ctx context.
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算充值赠送金额(复用支付宝的赠送逻辑)
|
// 计算充值赠送金额(复用支付宝的赠送逻辑,受 recharge_bonus_enabled 开关控制)
|
||||||
bonusAmount := decimal.Zero
|
bonusAmount := decimal.Zero
|
||||||
if len(s.config.Wallet.AliPayRechargeBonus) > 0 {
|
if s.config.Wallet.RechargeBonusEnabled && len(s.config.Wallet.AliPayRechargeBonus) > 0 {
|
||||||
for i := len(s.config.Wallet.AliPayRechargeBonus) - 1; i >= 0; i-- {
|
for i := len(s.config.Wallet.AliPayRechargeBonus) - 1; i >= 0; i-- {
|
||||||
rule := s.config.Wallet.AliPayRechargeBonus[i]
|
rule := s.config.Wallet.AliPayRechargeBonus[i]
|
||||||
if amount.GreaterThanOrEqual(decimal.NewFromFloat(rule.RechargeAmount)) {
|
if amount.GreaterThanOrEqual(decimal.NewFromFloat(rule.RechargeAmount)) {
|
||||||
|
|||||||
@@ -334,6 +334,8 @@ type WalletConfig struct {
|
|||||||
DefaultCreditLimit float64 `mapstructure:"default_credit_limit"`
|
DefaultCreditLimit float64 `mapstructure:"default_credit_limit"`
|
||||||
MinAmount string `mapstructure:"min_amount"` // 最低充值金额
|
MinAmount string `mapstructure:"min_amount"` // 最低充值金额
|
||||||
MaxAmount string `mapstructure:"max_amount"` // 最高充值金额
|
MaxAmount string `mapstructure:"max_amount"` // 最高充值金额
|
||||||
|
RechargeBonusEnabled bool `mapstructure:"recharge_bonus_enabled"` // 是否启用充值赠送,关闭后仅展示商务洽谈提示
|
||||||
|
ApiStoreRechargeTip string `mapstructure:"api_store_recharge_tip"` // API 商店充值提示文案(大额/批量需求联系商务)
|
||||||
AliPayRechargeBonus []AliPayRechargeBonusRule `mapstructure:"alipay_recharge_bonus"`
|
AliPayRechargeBonus []AliPayRechargeBonusRule `mapstructure:"alipay_recharge_bonus"`
|
||||||
BalanceAlert BalanceAlertConfig `mapstructure:"balance_alert"`
|
BalanceAlert BalanceAlertConfig `mapstructure:"balance_alert"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -660,6 +660,10 @@ func NewContainer() *Container {
|
|||||||
api_repo.NewGormApiCallRepository,
|
api_repo.NewGormApiCallRepository,
|
||||||
fx.As(new(domain_api_repo.ApiCallRepository)),
|
fx.As(new(domain_api_repo.ApiCallRepository)),
|
||||||
),
|
),
|
||||||
|
fx.Annotate(
|
||||||
|
api_repo.NewGormReportRepository,
|
||||||
|
fx.As(new(domain_api_repo.ReportRepository)),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// 统计域仓储层
|
// 统计域仓储层
|
||||||
@@ -769,7 +773,8 @@ func NewContainer() *Container {
|
|||||||
api_services.NewApiUserAggregateService,
|
api_services.NewApiUserAggregateService,
|
||||||
),
|
),
|
||||||
api_services.NewApiCallAggregateService,
|
api_services.NewApiCallAggregateService,
|
||||||
api_services.NewApiRequestService,
|
// 使用带仓储注入的构造函数,支持企业报告记录持久化
|
||||||
|
api_services.NewApiRequestServiceWithRepos,
|
||||||
api_services.NewFormConfigService,
|
api_services.NewFormConfigService,
|
||||||
),
|
),
|
||||||
|
|
||||||
@@ -1284,6 +1289,8 @@ func NewContainer() *Container {
|
|||||||
) *handlers.ComponentReportOrderHandler {
|
) *handlers.ComponentReportOrderHandler {
|
||||||
return handlers.NewComponentReportOrderHandler(componentReportOrderService, purchaseOrderRepo, config, logger)
|
return handlers.NewComponentReportOrderHandler(componentReportOrderService, purchaseOrderRepo, config, logger)
|
||||||
},
|
},
|
||||||
|
// 企业全景报告页面处理器
|
||||||
|
handlers.NewQYGLReportHandler,
|
||||||
// UI组件HTTP处理器
|
// UI组件HTTP处理器
|
||||||
func(
|
func(
|
||||||
uiComponentAppService product.UIComponentApplicationService,
|
uiComponentAppService product.UIComponentApplicationService,
|
||||||
@@ -1330,6 +1337,8 @@ func NewContainer() *Container {
|
|||||||
routes.NewStatisticsRoutes,
|
routes.NewStatisticsRoutes,
|
||||||
// PDFG路由
|
// PDFG路由
|
||||||
routes.NewPDFGRoutes,
|
routes.NewPDFGRoutes,
|
||||||
|
// 企业报告页面路由
|
||||||
|
routes.NewQYGLReportRoutes,
|
||||||
),
|
),
|
||||||
|
|
||||||
// 应用生命周期
|
// 应用生命周期
|
||||||
@@ -1445,6 +1454,7 @@ func RegisterRoutes(
|
|||||||
apiRoutes *routes.ApiRoutes,
|
apiRoutes *routes.ApiRoutes,
|
||||||
statisticsRoutes *routes.StatisticsRoutes,
|
statisticsRoutes *routes.StatisticsRoutes,
|
||||||
pdfgRoutes *routes.PDFGRoutes,
|
pdfgRoutes *routes.PDFGRoutes,
|
||||||
|
qyglReportRoutes *routes.QYGLReportRoutes,
|
||||||
jwtAuth *middleware.JWTAuthMiddleware,
|
jwtAuth *middleware.JWTAuthMiddleware,
|
||||||
adminAuth *middleware.AdminAuthMiddleware,
|
adminAuth *middleware.AdminAuthMiddleware,
|
||||||
cfg *config.Config,
|
cfg *config.Config,
|
||||||
@@ -1470,6 +1480,7 @@ func RegisterRoutes(
|
|||||||
announcementRoutes.Register(router)
|
announcementRoutes.Register(router)
|
||||||
statisticsRoutes.Register(router)
|
statisticsRoutes.Register(router)
|
||||||
pdfgRoutes.Register(router)
|
pdfgRoutes.Register(router)
|
||||||
|
qyglReportRoutes.Register(router)
|
||||||
|
|
||||||
// 打印注册的路由信息
|
// 打印注册的路由信息
|
||||||
router.PrintRoutes()
|
router.PrintRoutes()
|
||||||
|
|||||||
@@ -113,6 +113,22 @@ type QYGL2ACDReq struct {
|
|||||||
LegalPerson string `json:"legal_person" validate:"required,min=1,validName"`
|
LegalPerson string `json:"legal_person" validate:"required,min=1,validName"`
|
||||||
EntCode string `json:"ent_code" validate:"required,validUSCI"`
|
EntCode string `json:"ent_code" validate:"required,validUSCI"`
|
||||||
}
|
}
|
||||||
|
type QYGLUY3SReq struct {
|
||||||
|
EntName string `json:"ent_name" validate:"omitempty,min=1,validEnterpriseName"`
|
||||||
|
EntRegno string `json:"ent_reg_no" validate:"omitempty"`
|
||||||
|
EntCode string `json:"ent_code" validate:"omitempty,validUSCI"`
|
||||||
|
}
|
||||||
|
type QYGLJ1U9Req struct {
|
||||||
|
EntName string `json:"ent_name" validate:"required,min=1,validEnterpriseName"`
|
||||||
|
EntCode string `json:"ent_code" validate:"required,validUSCI"`
|
||||||
|
}
|
||||||
|
type JRZQOCRYReq struct {
|
||||||
|
PhotoData string `json:"photo_data" validate:"required,validBase64Image"`
|
||||||
|
}
|
||||||
|
type JRZQOCREReq struct {
|
||||||
|
PhotoData string `json:"photo_data" validate:"omitempty,validBase64Image"`
|
||||||
|
ImageUrl string `json:"image_url" validate:"omitempty,url"`
|
||||||
|
}
|
||||||
|
|
||||||
type YYSYK9R4Req struct {
|
type YYSYK9R4Req struct {
|
||||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||||
@@ -120,6 +136,10 @@ type YYSYK9R4Req struct {
|
|||||||
Name string `json:"name" validate:"required,min=1,validName"`
|
Name string `json:"name" validate:"required,min=1,validName"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type YYSY35TAReq struct {
|
||||||
|
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||||
|
}
|
||||||
|
|
||||||
type QCXG9F5CReq struct {
|
type QCXG9F5CReq struct {
|
||||||
PlateNo string `json:"plate_no" validate:"required"`
|
PlateNo string `json:"plate_no" validate:"required"`
|
||||||
}
|
}
|
||||||
@@ -403,7 +423,7 @@ type COMENT01Req struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type JRZQ09J8Req struct {
|
type JRZQ09J8Req struct {
|
||||||
MobileNo string `json:"mobile_no" validate:"omitempty,min=11,max=11,validMobileNo"`
|
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||||
Name string `json:"name" validate:"required,min=1,validName"`
|
Name string `json:"name" validate:"required,min=1,validName"`
|
||||||
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||||
@@ -584,10 +604,18 @@ type YYSY6F2BReq struct {
|
|||||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IVYZOCR1Req struct {
|
||||||
|
PhotoData string `json:"photo_data" validate:"omitempty,validBase64Image"`
|
||||||
|
ImageUrl string `json:"image_url" validate:"omitempty,url"`
|
||||||
|
}
|
||||||
type YYSY8B1CReq struct {
|
type YYSY8B1CReq struct {
|
||||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type QYGLJ0Q1Req struct {
|
||||||
|
EntName string `json:"ent_name" validate:"omitempty,min=1,validEnterpriseName"`
|
||||||
|
EntCode string `json:"ent_code" validate:"omitempty,validUSCI"`
|
||||||
|
}
|
||||||
type YYSY6D9AReq struct {
|
type YYSY6D9AReq struct {
|
||||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ func NewApiCall(accessId, requestParams, clientIp string) (*ApiCall, error) {
|
|||||||
AccessId: accessId,
|
AccessId: accessId,
|
||||||
TransactionId: GenerateTransactionID(),
|
TransactionId: GenerateTransactionID(),
|
||||||
ClientIp: clientIp,
|
ClientIp: clientIp,
|
||||||
RequestParams: "",
|
RequestParams: requestParams,
|
||||||
Status: ApiCallStatusPending,
|
Status: ApiCallStatusPending,
|
||||||
StartAt: time.Now(),
|
StartAt: time.Now(),
|
||||||
}, nil
|
}, nil
|
||||||
|
|||||||
35
internal/domains/api/entities/enterprise_report.go
Normal file
35
internal/domains/api/entities/enterprise_report.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// Report 报告记录实体
|
||||||
|
// 用于持久化存储各类报告(企业报告等),通过编号和类型区分
|
||||||
|
type Report struct {
|
||||||
|
// 报告编号,直接使用业务生成的 reportId 作为主键
|
||||||
|
ReportID string `gorm:"primaryKey;type:varchar(64)" json:"report_id"`
|
||||||
|
|
||||||
|
// 报告类型,例如 enterprise(企业报告)、personal 等
|
||||||
|
Type string `gorm:"type:varchar(32);not null;index" json:"type"`
|
||||||
|
|
||||||
|
// 调用来源API编码,例如 QYGLJ1U9
|
||||||
|
ApiCode string `gorm:"type:varchar(32);not null;index" json:"api_code"`
|
||||||
|
|
||||||
|
// 企业名称和统一社会信用代码,便于后续检索
|
||||||
|
EntName string `gorm:"type:varchar(255);index" json:"ent_name"`
|
||||||
|
EntCode string `gorm:"type:varchar(64);index" json:"ent_code"`
|
||||||
|
|
||||||
|
// 原始请求参数(JSON字符串),用于审计和排错
|
||||||
|
RequestParams string `gorm:"type:text" json:"request_params"`
|
||||||
|
|
||||||
|
// 报告完整JSON内容
|
||||||
|
ReportData string `gorm:"type:text" json:"report_data"`
|
||||||
|
|
||||||
|
// 创建时间
|
||||||
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName 指定数据库表名
|
||||||
|
func (Report) TableName() string {
|
||||||
|
return "reports"
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package repositories
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/entities"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReportRepository 报告记录仓储接口
|
||||||
|
type ReportRepository interface {
|
||||||
|
// Create 创建报告记录
|
||||||
|
Create(ctx context.Context, report *entities.Report) error
|
||||||
|
|
||||||
|
// FindByReportID 根据报告编号查询记录
|
||||||
|
FindByReportID(ctx context.Context, reportID string) (*entities.Report, error)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
"tyapi-server/internal/application/api/commands"
|
"tyapi-server/internal/application/api/commands"
|
||||||
"tyapi-server/internal/config"
|
"tyapi-server/internal/config"
|
||||||
|
api_repositories "tyapi-server/internal/domains/api/repositories"
|
||||||
"tyapi-server/internal/domains/api/services/processors"
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
"tyapi-server/internal/domains/api/services/processors/comb"
|
"tyapi-server/internal/domains/api/services/processors/comb"
|
||||||
"tyapi-server/internal/domains/api/services/processors/dwbg"
|
"tyapi-server/internal/domains/api/services/processors/dwbg"
|
||||||
@@ -50,6 +51,8 @@ type ApiRequestService struct {
|
|||||||
processorDeps *processors.ProcessorDependencies
|
processorDeps *processors.ProcessorDependencies
|
||||||
combService *comb.CombService
|
combService *comb.CombService
|
||||||
config *config.Config
|
config *config.Config
|
||||||
|
|
||||||
|
reportRepo api_repositories.ReportRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewApiRequestService(
|
func NewApiRequestService(
|
||||||
@@ -66,12 +69,61 @@ func NewApiRequestService(
|
|||||||
validator interfaces.RequestValidator,
|
validator interfaces.RequestValidator,
|
||||||
productManagementService *services.ProductManagementService,
|
productManagementService *services.ProductManagementService,
|
||||||
cfg *config.Config,
|
cfg *config.Config,
|
||||||
|
) *ApiRequestService {
|
||||||
|
return NewApiRequestServiceWithRepos(
|
||||||
|
westDexService,
|
||||||
|
shujubaoService,
|
||||||
|
muziService,
|
||||||
|
yushanService,
|
||||||
|
tianYanChaService,
|
||||||
|
alicloudService,
|
||||||
|
zhichaService,
|
||||||
|
xingweiService,
|
||||||
|
jiguangService,
|
||||||
|
shumaiService,
|
||||||
|
validator,
|
||||||
|
productManagementService,
|
||||||
|
cfg,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewApiRequestServiceWithRepos 带自定义仓储的构造函数,便于扩展(例如企业报告记录)
|
||||||
|
func NewApiRequestServiceWithRepos(
|
||||||
|
westDexService *westdex.WestDexService,
|
||||||
|
shujubaoService *shujubao.ShujubaoService,
|
||||||
|
muziService *muzi.MuziService,
|
||||||
|
yushanService *yushan.YushanService,
|
||||||
|
tianYanChaService *tianyancha.TianYanChaService,
|
||||||
|
alicloudService *alicloud.AlicloudService,
|
||||||
|
zhichaService *zhicha.ZhichaService,
|
||||||
|
xingweiService *xingwei.XingweiService,
|
||||||
|
jiguangService *jiguang.JiguangService,
|
||||||
|
shumaiService *shumai.ShumaiService,
|
||||||
|
validator interfaces.RequestValidator,
|
||||||
|
productManagementService *services.ProductManagementService,
|
||||||
|
cfg *config.Config,
|
||||||
|
reportRepo api_repositories.ReportRepository,
|
||||||
) *ApiRequestService {
|
) *ApiRequestService {
|
||||||
// 创建组合包服务
|
// 创建组合包服务
|
||||||
combService := comb.NewCombService(productManagementService)
|
combService := comb.NewCombService(productManagementService)
|
||||||
|
|
||||||
// 创建处理器依赖容器
|
// 创建处理器依赖容器
|
||||||
processorDeps := processors.NewProcessorDependencies(westDexService, shujubaoService, muziService, yushanService, tianYanChaService, alicloudService, zhichaService, xingweiService, jiguangService, shumaiService, validator, combService)
|
processorDeps := processors.NewProcessorDependencies(
|
||||||
|
westDexService,
|
||||||
|
shujubaoService,
|
||||||
|
muziService,
|
||||||
|
yushanService,
|
||||||
|
tianYanChaService,
|
||||||
|
alicloudService,
|
||||||
|
zhichaService,
|
||||||
|
xingweiService,
|
||||||
|
jiguangService,
|
||||||
|
shumaiService,
|
||||||
|
validator,
|
||||||
|
combService,
|
||||||
|
reportRepo,
|
||||||
|
)
|
||||||
|
|
||||||
// 统一注册所有处理器
|
// 统一注册所有处理器
|
||||||
registerAllProcessors(combService)
|
registerAllProcessors(combService)
|
||||||
@@ -86,6 +138,7 @@ func NewApiRequestService(
|
|||||||
processorDeps: processorDeps,
|
processorDeps: processorDeps,
|
||||||
combService: combService,
|
combService: combService,
|
||||||
config: cfg,
|
config: cfg,
|
||||||
|
reportRepo: reportRepo,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,6 +202,8 @@ func registerAllProcessors(combService *comb.CombService) {
|
|||||||
"JRZQO7L1": jrzq.ProcessJRZQO7L1Request, // 全国自然人经济特征评分模型v4 详版
|
"JRZQO7L1": jrzq.ProcessJRZQO7L1Request, // 全国自然人经济特征评分模型v4 详版
|
||||||
"JRZQS7G0": jrzq.ProcessJRZQS7G0Request, // 社保综合评分V1
|
"JRZQS7G0": jrzq.ProcessJRZQS7G0Request, // 社保综合评分V1
|
||||||
"JRZQ1P5G": jrzq.ProcessJRZQ1P5GRequest, // 全国自然人借贷压力指数查询(2)
|
"JRZQ1P5G": jrzq.ProcessJRZQ1P5GRequest, // 全国自然人借贷压力指数查询(2)
|
||||||
|
"JRZQOCRE": jrzq.ProcessJRZQOCREERequest, // 银行卡OCR数卖
|
||||||
|
"JRZQOCRY": jrzq.ProcessJRZQOCRYERequest, // 银行卡OCR数据宝
|
||||||
|
|
||||||
// QYGL系列处理器
|
// QYGL系列处理器
|
||||||
"QYGL8261": qygl.ProcessQYGL8261Request,
|
"QYGL8261": qygl.ProcessQYGL8261Request,
|
||||||
@@ -178,6 +233,10 @@ func registerAllProcessors(combService *comb.CombService) {
|
|||||||
"QYGLNIO8": qygl.ProcessQYGLNIO8Request, //企业基本信息
|
"QYGLNIO8": qygl.ProcessQYGLNIO8Request, //企业基本信息
|
||||||
"QYGLP0HT": qygl.ProcessQYGLP0HTRequest, //股权穿透
|
"QYGLP0HT": qygl.ProcessQYGLP0HTRequest, //股权穿透
|
||||||
"QYGL5S1I": qygl.ProcessQYGL5S1IRequest, //企业司法涉诉I
|
"QYGL5S1I": qygl.ProcessQYGL5S1IRequest, //企业司法涉诉I
|
||||||
|
"QYGLJ1U9": qygl.ProcessQYGLJ1U9Request, //企业全景报告(聚合 QYGLUY3S/QYGLJ0Q1/QYGL5S1I)
|
||||||
|
"QYGLJ0Q1": qygl.ProcessQYGLJ0Q1Request, //企业股权结构全景查询
|
||||||
|
"QYGLUY3S": qygl.ProcessQYGLUY3SRequest, //企业经营状态全景查询
|
||||||
|
"YYSY35TA": yysy.ProcessYYSY35TARequest, //运营商归属地数卖
|
||||||
|
|
||||||
// YYSY系列处理器
|
// YYSY系列处理器
|
||||||
"YYSYD50F": yysy.ProcessYYSYD50FRequest,
|
"YYSYD50F": yysy.ProcessYYSYD50FRequest,
|
||||||
@@ -248,6 +307,8 @@ func registerAllProcessors(combService *comb.CombService) {
|
|||||||
"IVYZN2P8": ivyz.ProcessIVYZN2P8Request, //身份证实名认证政务版
|
"IVYZN2P8": ivyz.ProcessIVYZN2P8Request, //身份证实名认证政务版
|
||||||
"IVYZX5QZ": ivyz.ProcessIVYZX5QZRequest, //活体检测
|
"IVYZX5QZ": ivyz.ProcessIVYZX5QZRequest, //活体检测
|
||||||
"IVYZX5Q2": ivyz.ProcessIVYZX5Q2Request, //活体识别步骤二
|
"IVYZX5Q2": ivyz.ProcessIVYZX5Q2Request, //活体识别步骤二
|
||||||
|
"IVYZOCR1": ivyz.ProcessIVYZOCR1Request, //身份证OCR
|
||||||
|
"IVYZOCR2": ivyz.ProcessIVYZOCR2Request, //身份证OCR2数卖
|
||||||
|
|
||||||
// COMB系列处理器 - 只注册有自定义逻辑的组合包
|
// COMB系列处理器 - 只注册有自定义逻辑的组合包
|
||||||
"COMB86PM": comb.ProcessCOMB86PMRequest, // 有自定义逻辑:重命名ApiCode
|
"COMB86PM": comb.ProcessCOMB86PMRequest, // 有自定义逻辑:重命名ApiCode
|
||||||
|
|||||||
@@ -176,6 +176,7 @@ func (s *FormConfigServiceImpl) getDTOStruct(ctx context.Context, apiCode string
|
|||||||
"QCXG8A3D": &dto.QCXG8A3DReq{},
|
"QCXG8A3D": &dto.QCXG8A3DReq{},
|
||||||
"QCXG6B4E": &dto.QCXG6B4EReq{},
|
"QCXG6B4E": &dto.QCXG6B4EReq{},
|
||||||
"QYGL2B5C": &dto.QYGL2B5CReq{},
|
"QYGL2B5C": &dto.QYGL2B5CReq{},
|
||||||
|
"QYGLJ1U9": &dto.QYGLJ1U9Req{},
|
||||||
"JRZQ2F8A": &dto.JRZQ2F8AReq{},
|
"JRZQ2F8A": &dto.JRZQ2F8AReq{},
|
||||||
"JRZQ1E7B": &dto.JRZQ1E7BReq{},
|
"JRZQ1E7B": &dto.JRZQ1E7BReq{},
|
||||||
"JRZQ3C9R": &dto.JRZQ3C9RReq{},
|
"JRZQ3C9R": &dto.JRZQ3C9RReq{},
|
||||||
@@ -255,6 +256,13 @@ func (s *FormConfigServiceImpl) getDTOStruct(ctx context.Context, apiCode string
|
|||||||
"YYSYK9R4": &dto.YYSYK9R4Req{}, //全网手机三要素验证1979周更新版
|
"YYSYK9R4": &dto.YYSYK9R4Req{}, //全网手机三要素验证1979周更新版
|
||||||
"QCXG3M7Z": &dto.QCXG3M7ZReq{}, //人车关系核验(ETC)10093 月更
|
"QCXG3M7Z": &dto.QCXG3M7ZReq{}, //人车关系核验(ETC)10093 月更
|
||||||
"JRZQ1P5G": &dto.JRZQ1P5GReq{}, //全国自然人借贷压力指数查询(2)
|
"JRZQ1P5G": &dto.JRZQ1P5GReq{}, //全国自然人借贷压力指数查询(2)
|
||||||
|
"IVYZOCR1": &dto.IVYZOCR1Req{}, //身份证OCR
|
||||||
|
"IVYZOCR2": &dto.IVYZOCR1Req{}, //身份证OCR2数卖
|
||||||
|
"QYGLJ0Q1": &dto.QYGLJ0Q1Req{}, //企业股权结构全景查询
|
||||||
|
"QYGLUY3S": &dto.QYGLUY3SReq{}, //企业全量信息核验V2 可用
|
||||||
|
"JRZQOCRE": &dto.JRZQOCREReq{}, //银行卡OCR数卖
|
||||||
|
"JRZQOCRY": &dto.JRZQOCRYReq{}, //银行卡OCR数据宝
|
||||||
|
"YYSY35TA": &dto.YYSY35TAReq{}, //运营商归属地数卖
|
||||||
}
|
}
|
||||||
|
|
||||||
// 优先返回已配置的DTO
|
// 优先返回已配置的DTO
|
||||||
@@ -438,6 +446,7 @@ func (s *FormConfigServiceImpl) generateFieldLabel(jsonTag string) string {
|
|||||||
"ent_name": "企业名称",
|
"ent_name": "企业名称",
|
||||||
"legal_person": "法人姓名",
|
"legal_person": "法人姓名",
|
||||||
"ent_code": "企业代码",
|
"ent_code": "企业代码",
|
||||||
|
"ent_reg_no": "企业注册号",
|
||||||
"auth_date": "授权日期",
|
"auth_date": "授权日期",
|
||||||
"date_range": "日期范围",
|
"date_range": "日期范围",
|
||||||
"time_range": "时间范围",
|
"time_range": "时间范围",
|
||||||
@@ -459,7 +468,7 @@ func (s *FormConfigServiceImpl) generateFieldLabel(jsonTag string) string {
|
|||||||
"plate_type": "号牌类型",
|
"plate_type": "号牌类型",
|
||||||
"vin_code": "车辆识别代号VIN码",
|
"vin_code": "车辆识别代号VIN码",
|
||||||
"return_type": "返回类型",
|
"return_type": "返回类型",
|
||||||
"photo_data": "人脸图片",
|
"photo_data": "入参图片base64编码",
|
||||||
"owner_type": "企业主类型",
|
"owner_type": "企业主类型",
|
||||||
"type": "查询类型",
|
"type": "查询类型",
|
||||||
"query_reason_id": "查询原因ID",
|
"query_reason_id": "查询原因ID",
|
||||||
@@ -471,7 +480,7 @@ func (s *FormConfigServiceImpl) generateFieldLabel(jsonTag string) string {
|
|||||||
"notice_model": "车辆型号",
|
"notice_model": "车辆型号",
|
||||||
"vlphoto_data": "行驶证图片",
|
"vlphoto_data": "行驶证图片",
|
||||||
"carplate_type": "车辆号牌类型",
|
"carplate_type": "车辆号牌类型",
|
||||||
"image_url": "行驶证图片地址",
|
"image_url": "入参图片地址",
|
||||||
"reg_url": "车辆登记证图片地址",
|
"reg_url": "车辆登记证图片地址",
|
||||||
"token": "token采集及获取结果时所使用的凭证,有效期2个小时,在此时效内,应用侧可以发起采集请求(重复的采集所触发的结果会被忽略)和结果查询",
|
"token": "token采集及获取结果时所使用的凭证,有效期2个小时,在此时效内,应用侧可以发起采集请求(重复的采集所触发的结果会被忽略)和结果查询",
|
||||||
"vehicle_name": "车型名称",
|
"vehicle_name": "车型名称",
|
||||||
@@ -500,6 +509,7 @@ func (s *FormConfigServiceImpl) generateExampleValue(fieldType reflect.Type, jso
|
|||||||
"ent_name": "示例企业有限公司",
|
"ent_name": "示例企业有限公司",
|
||||||
"legal_person": "王五",
|
"legal_person": "王五",
|
||||||
"ent_code": "91110000123456789X",
|
"ent_code": "91110000123456789X",
|
||||||
|
"ent_reg_no": "110000000123456",
|
||||||
"auth_date": "20240101-20241231",
|
"auth_date": "20240101-20241231",
|
||||||
"date_range": "20240101-20241231",
|
"date_range": "20240101-20241231",
|
||||||
"time_range": "09:00-18:00",
|
"time_range": "09:00-18:00",
|
||||||
@@ -571,6 +581,7 @@ func (s *FormConfigServiceImpl) generatePlaceholder(jsonTag string, fieldType st
|
|||||||
"ent_name": "请输入企业全称",
|
"ent_name": "请输入企业全称",
|
||||||
"legal_person": "请输入法人真实姓名",
|
"legal_person": "请输入法人真实姓名",
|
||||||
"ent_code": "请输入统一社会信用代码",
|
"ent_code": "请输入统一社会信用代码",
|
||||||
|
"ent_reg_no": "请输入企业注册号(统一社会信用代码)",
|
||||||
"auth_date": "请输入授权日期范围(YYYYMMDD-YYYYMMDD)",
|
"auth_date": "请输入授权日期范围(YYYYMMDD-YYYYMMDD)",
|
||||||
"date_range": "请输入日期范围(YYYYMMDD-YYYYMMDD)",
|
"date_range": "请输入日期范围(YYYYMMDD-YYYYMMDD)",
|
||||||
"time_range": "请输入时间范围(HH:MM-HH:MM)",
|
"time_range": "请输入时间范围(HH:MM-HH:MM)",
|
||||||
@@ -592,7 +603,7 @@ func (s *FormConfigServiceImpl) generatePlaceholder(jsonTag string, fieldType st
|
|||||||
"plate_type": "请选择号牌类型(01或02)",
|
"plate_type": "请选择号牌类型(01或02)",
|
||||||
"vin_code": "请输入17位车辆识别代号VIN码",
|
"vin_code": "请输入17位车辆识别代号VIN码",
|
||||||
"return_type": "请选择返回类型",
|
"return_type": "请选择返回类型",
|
||||||
"photo_data": "请输入base64编码的人脸图片(支持JPG、BMP、PNG格式)",
|
"photo_data": "请输入base64编码的入参图片(支持JPG、BMP、PNG格式)",
|
||||||
"ownerType": "请选择企业主类型",
|
"ownerType": "请选择企业主类型",
|
||||||
"type": "请选择查询类型",
|
"type": "请选择查询类型",
|
||||||
"query_reason_id": "请选择查询原因ID",
|
"query_reason_id": "请选择查询原因ID",
|
||||||
@@ -644,6 +655,7 @@ func (s *FormConfigServiceImpl) generateDescription(jsonTag string, validation s
|
|||||||
"ent_name": "请输入企业全称",
|
"ent_name": "请输入企业全称",
|
||||||
"legal_person": "请输入法人真实姓名",
|
"legal_person": "请输入法人真实姓名",
|
||||||
"ent_code": "请输入统一社会信用代码",
|
"ent_code": "请输入统一社会信用代码",
|
||||||
|
"ent_reg_no": "请输入企业注册号(统一社会信用代码)",
|
||||||
"auth_date": "请输入授权日期范围,格式:YYYYMMDD-YYYYMMDD,且日期范围必须包括今天",
|
"auth_date": "请输入授权日期范围,格式:YYYYMMDD-YYYYMMDD,且日期范围必须包括今天",
|
||||||
"date_range": "请输入日期范围,格式:YYYYMMDD-YYYYMMDD",
|
"date_range": "请输入日期范围,格式:YYYYMMDD-YYYYMMDD",
|
||||||
"time_range": "请输入时间范围,格式:HH:MM-HH:MM",
|
"time_range": "请输入时间范围,格式:HH:MM-HH:MM",
|
||||||
@@ -665,7 +677,7 @@ func (s *FormConfigServiceImpl) generateDescription(jsonTag string, validation s
|
|||||||
"plate_type": "号牌类型:01-小型汽车;02-大型汽车(可选)",
|
"plate_type": "号牌类型:01-小型汽车;02-大型汽车(可选)",
|
||||||
"vin_code": "请输入17位车辆识别代号VIN码(Vehicle Identification Number)",
|
"vin_code": "请输入17位车辆识别代号VIN码(Vehicle Identification Number)",
|
||||||
"return_type": "返回类型:1-专业和学校名称数据返回编码形式(默认);2-专业和学校名称数据返回中文名称",
|
"return_type": "返回类型:1-专业和学校名称数据返回编码形式(默认);2-专业和学校名称数据返回中文名称",
|
||||||
"photo_data": "人脸图片(必填):base64编码的图片数据,仅支持JPG、BMP、PNG三种格式",
|
"photo_data": "入参图片:base64编码的图片数据,仅支持JPG、BMP、PNG三种格式",
|
||||||
"owner_type": "企业主类型编码:1-法定代表人;2-主要人员;3-自然人股东;4-法定代表人及自然人股东;5-其他",
|
"owner_type": "企业主类型编码:1-法定代表人;2-主要人员;3-自然人股东;4-法定代表人及自然人股东;5-其他",
|
||||||
"type": "查询类型:per-人员,ent-企业 ",
|
"type": "查询类型:per-人员,ent-企业 ",
|
||||||
"query_reason_id": "查询原因ID:1-授信审批;2-贷中管理;3-贷后管理;4-异议处理;5-担保查询;6-租赁资质审查;7-融资租赁审批;8-借贷撮合查询;9-保险审批;10-资质审核;11-风控审核;12-企业背调",
|
"query_reason_id": "查询原因ID:1-授信审批;2-贷中管理;3-贷后管理;4-异议处理;5-担保查询;6-租赁资质审查;7-融资租赁审批;8-借贷撮合查询;9-保险审批;10-资质审核;11-风控审核;12-企业背调",
|
||||||
@@ -677,7 +689,7 @@ func (s *FormConfigServiceImpl) generateDescription(jsonTag string, validation s
|
|||||||
"notice_model": "车辆型号",
|
"notice_model": "车辆型号",
|
||||||
"vlphoto_data": "行驶证图片:base64编码的图片数据,仅支持JPG、BMP、PNG三种格式",
|
"vlphoto_data": "行驶证图片:base64编码的图片数据,仅支持JPG、BMP、PNG三种格式",
|
||||||
"carplate_type": "车辆号牌类型:01-大型汽车;02-小型汽车;03-使馆汽车;04-领馆汽车;05-境外汽车;06-外籍汽车;07-普通摩托车;08-轻便摩托车;09-使馆摩托车;10-领馆摩托车;11-境外摩托车;12-外籍摩托车;13-低速车;14-拖拉机;15-挂车;16-教练汽车;17-教练摩托车;20-临时入境汽车;21-临时入境摩托车;22-临时行驶车;23-警用汽车;24-警用摩托;51-新能源大型车;52-新能源小型车",
|
"carplate_type": "车辆号牌类型:01-大型汽车;02-小型汽车;03-使馆汽车;04-领馆汽车;05-境外汽车;06-外籍汽车;07-普通摩托车;08-轻便摩托车;09-使馆摩托车;10-领馆摩托车;11-境外摩托车;12-外籍摩托车;13-低速车;14-拖拉机;15-挂车;16-教练汽车;17-教练摩托车;20-临时入境汽车;21-临时入境摩托车;22-临时行驶车;23-警用汽车;24-警用摩托;51-新能源大型车;52-新能源小型车",
|
||||||
"image_url": "行驶证图片地址(必填):请提供行驶证的图片URL地址",
|
"image_url": "入参图片url地址",
|
||||||
"reg_url": "车辆登记证图片地址(非必填):请提供车辆登记证的图片URL地址",
|
"reg_url": "车辆登记证图片地址(非必填):请提供车辆登记证的图片URL地址",
|
||||||
"token": "token采集及获取结果时所使用的凭证,有效期2个小时,在此时效内,应用侧可以发起采集请求(重复的采集所触发的结果会被忽略)和结果查询",
|
"token": "token采集及获取结果时所使用的凭证,有效期2个小时,在此时效内,应用侧可以发起采集请求(重复的采集所触发的结果会被忽略)和结果查询",
|
||||||
"vehicle_name": "车型名称,示例:凌派 2020款 锐·混动 1.5L 锐·舒适版",
|
"vehicle_name": "车型名称,示例:凌派 2020款 锐·混动 1.5L 锐·舒适版",
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ package processors
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"tyapi-server/internal/application/api/commands"
|
"tyapi-server/internal/application/api/commands"
|
||||||
|
"tyapi-server/internal/domains/api/repositories"
|
||||||
"tyapi-server/internal/infrastructure/external/alicloud"
|
"tyapi-server/internal/infrastructure/external/alicloud"
|
||||||
"tyapi-server/internal/infrastructure/external/jiguang"
|
"tyapi-server/internal/infrastructure/external/jiguang"
|
||||||
"tyapi-server/internal/infrastructure/external/muzi"
|
"tyapi-server/internal/infrastructure/external/muzi"
|
||||||
@@ -42,6 +44,9 @@ type ProcessorDependencies struct {
|
|||||||
CombService CombServiceInterface // Changed to interface to break import cycle
|
CombService CombServiceInterface // Changed to interface to break import cycle
|
||||||
Options *commands.ApiCallOptions // 添加Options支持
|
Options *commands.ApiCallOptions // 添加Options支持
|
||||||
CallContext *CallContext // 添加CallApi调用上下文
|
CallContext *CallContext // 添加CallApi调用上下文
|
||||||
|
|
||||||
|
// 企业报告记录仓储,用于持久化 QYGLJ1U9 生成的企业报告
|
||||||
|
ReportRepo repositories.ReportRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewProcessorDependencies 创建处理器依赖容器
|
// NewProcessorDependencies 创建处理器依赖容器
|
||||||
@@ -58,6 +63,7 @@ func NewProcessorDependencies(
|
|||||||
shumaiService *shumai.ShumaiService,
|
shumaiService *shumai.ShumaiService,
|
||||||
validator interfaces.RequestValidator,
|
validator interfaces.RequestValidator,
|
||||||
combService CombServiceInterface, // Changed to interface
|
combService CombServiceInterface, // Changed to interface
|
||||||
|
reportRepo repositories.ReportRepository,
|
||||||
) *ProcessorDependencies {
|
) *ProcessorDependencies {
|
||||||
return &ProcessorDependencies{
|
return &ProcessorDependencies{
|
||||||
WestDexService: westDexService,
|
WestDexService: westDexService,
|
||||||
@@ -74,6 +80,7 @@ func NewProcessorDependencies(
|
|||||||
CombService: combService,
|
CombService: combService,
|
||||||
Options: nil, // 初始化为nil,在调用时设置
|
Options: nil, // 初始化为nil,在调用时设置
|
||||||
CallContext: nil, // 初始化为nil,在调用时设置
|
CallContext: nil, // 初始化为nil,在调用时设置
|
||||||
|
ReportRepo: reportRepo,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1524,7 +1524,7 @@ func buildElementVerificationDetail(apiData map[string]interface{}, log *zap.Log
|
|||||||
}
|
}
|
||||||
detail["belongRisks"] = belongRisks
|
detail["belongRisks"] = belongRisks
|
||||||
|
|
||||||
// 公安重点人员核验产品(highRiskFlag 和 keyPersonCheckList)
|
// 公安重点人员核验产品(highRiskFlag 由 keyPersonCheckList 五项是否命中决定)
|
||||||
flxgdea9Data := getMapValue(apiData, "FLXGDEA9")
|
flxgdea9Data := getMapValue(apiData, "FLXGDEA9")
|
||||||
keyPersonCheckList := make(map[string]interface{})
|
keyPersonCheckList := make(map[string]interface{})
|
||||||
keyPersonCheckList["num"] = "1"
|
keyPersonCheckList["num"] = "1"
|
||||||
@@ -1538,7 +1538,6 @@ func buildElementVerificationDetail(apiData map[string]interface{}, log *zap.Log
|
|||||||
if flxgdea9Map, ok := flxgdea9Data.(map[string]interface{}); ok {
|
if flxgdea9Map, ok := flxgdea9Data.(map[string]interface{}); ok {
|
||||||
level, ok := flxgdea9Map["level"].(string)
|
level, ok := flxgdea9Map["level"].(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
// 尝试从其他可能的字段获取
|
|
||||||
if levelVal, exists := flxgdea9Map["level"]; exists {
|
if levelVal, exists := flxgdea9Map["level"]; exists {
|
||||||
if levelStr, ok := levelVal.(string); ok {
|
if levelStr, ok := levelVal.(string); ok {
|
||||||
level = levelStr
|
level = levelStr
|
||||||
@@ -1548,19 +1547,14 @@ func buildElementVerificationDetail(apiData map[string]interface{}, log *zap.Log
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 仅根据 level 解析并填充 keyPersonCheckList 五项
|
||||||
if level != "" && level != "0" {
|
if level != "" && level != "0" {
|
||||||
// 有风险,设置highRiskFlag为1(高风险)
|
|
||||||
detail["highRiskFlag"] = 1
|
|
||||||
|
|
||||||
// 解析level字段,填充keyPersonCheckList
|
|
||||||
levelParts := strings.Split(level, ",")
|
levelParts := strings.Split(level, ",")
|
||||||
for _, part := range levelParts {
|
for _, part := range levelParts {
|
||||||
part = strings.TrimSpace(part)
|
part = strings.TrimSpace(part)
|
||||||
if part == "" || part == "0" {
|
if part == "" || part == "0" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据level代码判断风险类型
|
|
||||||
if strings.HasPrefix(part, "A") {
|
if strings.HasPrefix(part, "A") {
|
||||||
keyPersonCheckList["fontFlag"] = 1
|
keyPersonCheckList["fontFlag"] = 1
|
||||||
}
|
}
|
||||||
@@ -1577,14 +1571,27 @@ func buildElementVerificationDetail(apiData map[string]interface{}, log *zap.Log
|
|||||||
keyPersonCheckList["sheJiaoTongFlag"] = 1
|
keyPersonCheckList["sheJiaoTongFlag"] = 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// 无风险,设置highRiskFlag为2(低风险)
|
|
||||||
detail["highRiskFlag"] = 2
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
detail["keyPersonCheckList"] = keyPersonCheckList
|
detail["keyPersonCheckList"] = keyPersonCheckList
|
||||||
|
|
||||||
|
// 仅根据 keyPersonCheckList 五项判定 highRiskFlag(不看是否有数据):
|
||||||
|
// 五项均未命中 → 0 无风险;仅 sheJiaoTongFlag 命中 → 2 低风险;其他任一项命中 → 1 高风险
|
||||||
|
otherHit := keyPersonFlagEq1(keyPersonCheckList, "fontFlag") ||
|
||||||
|
keyPersonFlagEq1(keyPersonCheckList, "jingJiFontFlag") ||
|
||||||
|
keyPersonFlagEq1(keyPersonCheckList, "fangAiFlag") ||
|
||||||
|
keyPersonFlagEq1(keyPersonCheckList, "zhongDianFlag")
|
||||||
|
sheJiaoHit := keyPersonFlagEq1(keyPersonCheckList, "sheJiaoTongFlag")
|
||||||
|
if otherHit {
|
||||||
|
detail["highRiskFlag"] = 1 // 高风险
|
||||||
|
} else if sheJiaoHit {
|
||||||
|
detail["highRiskFlag"] = 2 // 低风险:仅涉交通命中
|
||||||
|
} else {
|
||||||
|
detail["highRiskFlag"] = 0 // 无风险:五项均未命中
|
||||||
|
}
|
||||||
|
|
||||||
// 设置默认值
|
// 设置默认值
|
||||||
if _, exists := detail["sjsysFlag"]; !exists {
|
if _, exists := detail["sjsysFlag"]; !exists {
|
||||||
detail["sjsysFlag"] = 0
|
detail["sjsysFlag"] = 0
|
||||||
@@ -3862,40 +3869,73 @@ func buildLeasingRiskAssessment(apiData map[string]interface{}, log *zap.Logger)
|
|||||||
assessment[fieldNameNight] = fmt.Sprintf("%s/%s", idNightAllnum, cellNightAllnum)
|
assessment[fieldNameNight] = fmt.Sprintf("%s/%s", idNightAllnum, cellNightAllnum)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 判断风险标识:如果有申请记录,设置为高风险
|
// 仅使用近12月(Last12,对应 m12)的次数作为判断依据,
|
||||||
hasApplication := false
|
// totalCount 取身份证维度和手机号维度中的较大值,而不是两者相加
|
||||||
for _, apiPeriod := range periodMap {
|
totalCount := 0
|
||||||
fieldName := fmt.Sprintf("alc_%s_id_allnum", apiPeriod)
|
|
||||||
// 尝试多种类型:string、int、float64
|
idKey12 := "alc_m12_id_allnum"
|
||||||
if allnum, ok := jrzq1d09Map[fieldName].(string); ok && allnum != "" && allnum != "0" {
|
cellKey12 := "alc_m12_cell_allnum"
|
||||||
hasApplication = true
|
|
||||||
break
|
idCount := 0
|
||||||
} else if allnum, ok := jrzq1d09Map[fieldName].(int); ok && allnum > 0 {
|
cellCount := 0
|
||||||
hasApplication = true
|
|
||||||
break
|
// 身份证维度(近12月)
|
||||||
} else if allnum, ok := jrzq1d09Map[fieldName].(float64); ok && allnum > 0 {
|
if v, ok := jrzq1d09Map[idKey12]; ok {
|
||||||
hasApplication = true
|
switch vv := v.(type) {
|
||||||
break
|
case string:
|
||||||
}
|
if vv != "" && vv != "0" {
|
||||||
// 也检查cell字段
|
if parsed, err := strconv.Atoi(vv); err == nil {
|
||||||
fieldNameCell := fmt.Sprintf("alc_%s_cell_allnum", apiPeriod)
|
idCount = parsed
|
||||||
if allnum, ok := jrzq1d09Map[fieldNameCell].(string); ok && allnum != "" && allnum != "0" {
|
|
||||||
hasApplication = true
|
|
||||||
break
|
|
||||||
} else if allnum, ok := jrzq1d09Map[fieldNameCell].(int); ok && allnum > 0 {
|
|
||||||
hasApplication = true
|
|
||||||
break
|
|
||||||
} else if allnum, ok := jrzq1d09Map[fieldNameCell].(float64); ok && allnum > 0 {
|
|
||||||
hasApplication = true
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 风险标识:如果有申请记录,风险较高;如果没有申请记录,风险较低
|
case float64:
|
||||||
// 注意:riskFlag的值:0=未查得,1=低风险,2=高风险
|
if vv > 0 {
|
||||||
if hasApplication {
|
idCount = int(vv)
|
||||||
assessment["riskFlag"] = 2 // 有申请记录,风险较高
|
}
|
||||||
|
case int:
|
||||||
|
if vv > 0 {
|
||||||
|
idCount = vv
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 手机号维度(近12月)
|
||||||
|
if v, ok := jrzq1d09Map[cellKey12]; ok {
|
||||||
|
switch vv := v.(type) {
|
||||||
|
case string:
|
||||||
|
if vv != "" && vv != "0" {
|
||||||
|
if parsed, err := strconv.Atoi(vv); err == nil {
|
||||||
|
cellCount = parsed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
if vv > 0 {
|
||||||
|
cellCount = int(vv)
|
||||||
|
}
|
||||||
|
case int:
|
||||||
|
if vv > 0 {
|
||||||
|
cellCount = vv
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用身份证和手机号两个维度中的较大值作为近12月总次数
|
||||||
|
if idCount >= cellCount {
|
||||||
|
totalCount = idCount
|
||||||
} else {
|
} else {
|
||||||
assessment["riskFlag"] = 1 // 无申请记录,风险较低
|
totalCount = cellCount
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据近12月总申请次数设置风险标识:
|
||||||
|
// - totalCount == 0 -> 0 无风险 / 未查得
|
||||||
|
// - 0 < totalCount <= 10 -> 2 低风险(有少量租赁申请)
|
||||||
|
// - totalCount > 10 -> 1 高风险(租赁申请很多)
|
||||||
|
if totalCount == 0 {
|
||||||
|
assessment["riskFlag"] = 0
|
||||||
|
} else if totalCount <= 10 {
|
||||||
|
assessment["riskFlag"] = 2
|
||||||
|
} else {
|
||||||
|
assessment["riskFlag"] = 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3912,6 +3952,22 @@ func getMapValue(data map[string]interface{}, key string) interface{} {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// keyPersonFlagEq1 判断 keyPersonCheckList 中某标识是否为 1(支持 int/float64)
|
||||||
|
func keyPersonFlagEq1(m map[string]interface{}, key string) bool {
|
||||||
|
v, ok := m[key]
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
switch vv := v.(type) {
|
||||||
|
case int:
|
||||||
|
return vv == 1
|
||||||
|
case float64:
|
||||||
|
return vv == 1
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func maskName(name string) string {
|
func maskName(name string) string {
|
||||||
if name == "" {
|
if name == "" {
|
||||||
return ""
|
return ""
|
||||||
@@ -3955,8 +4011,10 @@ func calculateAgeAndSex(idCard string) (int, string) {
|
|||||||
return 0, ""
|
return 0, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
// 计算年龄(简化处理,使用当前年份)
|
// 计算年龄(简化处理,使用当前年份)
|
||||||
age := 2024 - year
|
age := now.Year() - year
|
||||||
|
|
||||||
// 提取性别(第17位,奇数为男,偶数为女)
|
// 提取性别(第17位,奇数为男,偶数为女)
|
||||||
sexCode := idCard[16:17]
|
sexCode := idCard[16:17]
|
||||||
@@ -4114,7 +4172,7 @@ func extractProvinceFromAddress(address string) string {
|
|||||||
// 处理"XX省"格式(最后处理,因为可能与其他模式冲突)
|
// 处理"XX省"格式(最后处理,因为可能与其他模式冲突)
|
||||||
{"省", func(addr string) string {
|
{"省", func(addr string) string {
|
||||||
if idx := strings.Index(addr, "省"); idx > 0 {
|
if idx := strings.Index(addr, "省"); idx > 0 {
|
||||||
return addr[:idx+1]
|
return addr[:idx+len("省")]
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}},
|
}},
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package ivyz
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/shujubao"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessIVYZOCR1Request IVYZOCR1 身份证OCR API 处理方法(使用数据宝服务示例)
|
||||||
|
func ProcessIVYZOCR1Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.IVYZOCR1Req
|
||||||
|
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建数据宝入参:姓名、身份证、手机号、银行卡号(sign 外的业务参数可按需 AES 加密后作为 bodyData)
|
||||||
|
reqParams := map[string]interface{}{
|
||||||
|
"key": "8782f2a32463f75b53096323461df735",
|
||||||
|
"imageId": paramsDto.PhotoData,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 最终请求 URL = https://api.chinadatapay.com/communication + 拼接接口地址值,如 personal/197
|
||||||
|
apiPath := "/trade/user/1985"
|
||||||
|
data, err := deps.ShujubaoService.CallAPI(ctx, apiPath, reqParams)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, shujubao.ErrDatasource) {
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, err)
|
||||||
|
}
|
||||||
|
if errors.Is(err, shujubao.ErrQueryEmpty) {
|
||||||
|
return nil, errors.Join(processors.ErrNotFound, err)
|
||||||
|
}
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
respBytes, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package ivyz
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/shumai"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessIVYZOCR2Request IVYZOCR2 OCR识别API处理方法数卖
|
||||||
|
func ProcessIVYZOCR2Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.IVYZOCR1Req
|
||||||
|
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if paramsDto.PhotoData == "" && paramsDto.ImageUrl == "" {
|
||||||
|
return nil, errors.Join(processors.ErrInvalidParam, errors.New("photo_data or image_url is required"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2选1:有值的用对应 key,空则用另一个
|
||||||
|
reqFormData := make(map[string]interface{})
|
||||||
|
if paramsDto.PhotoData != "" {
|
||||||
|
reqFormData["image"] = paramsDto.PhotoData
|
||||||
|
} else {
|
||||||
|
reqFormData["url"] = paramsDto.ImageUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
// 以表单方式调用数脉 API;参数在 CallAPIForm 内转为 application/x-www-form-urlencoded
|
||||||
|
apiPath := "/v4/idcard/ocr" // 接口路径,根据数脉文档填写(如 v4/xxx)
|
||||||
|
respBytes, err := deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, shumai.ErrNotFound) {
|
||||||
|
// 查无记录情况
|
||||||
|
return nil, errors.Join(processors.ErrNotFound, err)
|
||||||
|
} else if errors.Is(err, shumai.ErrDatasource) {
|
||||||
|
// 数据源错误
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, err)
|
||||||
|
} else if errors.Is(err, shumai.ErrSystem) {
|
||||||
|
// 系统错误
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
} else {
|
||||||
|
// 其他未知错误
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package jrzq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/shumai"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessJRZQOCREERequest JRZQOCRE 银行卡OCR API 数卖服务示例
|
||||||
|
func ProcessJRZQOCREERequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.JRZQOCREReq
|
||||||
|
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if paramsDto.PhotoData == "" && paramsDto.ImageUrl == "" {
|
||||||
|
return nil, errors.Join(processors.ErrInvalidParam, errors.New("photo_data or image_url is required"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2选1:有值的用对应 key,空则用另一个
|
||||||
|
reqFormData := make(map[string]interface{})
|
||||||
|
if paramsDto.PhotoData != "" {
|
||||||
|
reqFormData["image"] = paramsDto.PhotoData
|
||||||
|
} else {
|
||||||
|
reqFormData["url"] = paramsDto.ImageUrl
|
||||||
|
}
|
||||||
|
// 以表单方式调用数脉 API;参数在 CallAPIForm 内转为 application/x-www-form-urlencoded
|
||||||
|
apiPath := "/v2/bankcard/ocr" // 接口路径,根据数脉文档填写(如 v4/xxx)
|
||||||
|
respBytes, err := deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, shumai.ErrDatasource) {
|
||||||
|
// 数据源错误
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, err)
|
||||||
|
} else if errors.Is(err, shumai.ErrSystem) {
|
||||||
|
// 系统错误
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
} else {
|
||||||
|
// 其他未知错误
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package jrzq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/shujubao"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessJRZQOCRYERequest JRZQOCRY 银行卡OCR API 处理方法(使用数据宝服务示例)
|
||||||
|
func ProcessJRZQOCRYERequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.JRZQOCRYReq
|
||||||
|
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建数据宝入参:姓名、身份证、手机号、银行卡号(sign 外的业务参数可按需 AES 加密后作为 bodyData)
|
||||||
|
reqParams := map[string]interface{}{
|
||||||
|
"key": "3ee8e7a7a71870db2c0bf98e7e6b8b5c",
|
||||||
|
"imageId": paramsDto.PhotoData,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 最终请求 URL = https://api.chinadatapay.com/communication + 拼接接口地址值,如 personal/197
|
||||||
|
apiPath := "/trade/user/1986"
|
||||||
|
data, err := deps.ShujubaoService.CallAPI(ctx, apiPath, reqParams)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, shujubao.ErrDatasource) {
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, err)
|
||||||
|
}
|
||||||
|
if errors.Is(err, shujubao.ErrQueryEmpty) {
|
||||||
|
return nil, errors.Join(processors.ErrNotFound, err)
|
||||||
|
}
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
respBytes, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
@@ -21,20 +21,23 @@ func ProcessQYGL5S1IRequest(ctx context.Context, params []byte, deps *processors
|
|||||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
}
|
}
|
||||||
if paramsDto.EntCode == "" && paramsDto.EntName == "" {
|
|
||||||
return nil, errors.Join(processors.ErrInvalidParam, errors.New("必须提供企业统一信用代码或企业名称中的一个"))
|
encryptedEntName, err := deps.ZhichaService.Encrypt(paramsDto.EntName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
encryptedEntCode, err := deps.ZhichaService.Encrypt(paramsDto.EntCode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 优先使用企业名称,否则使用统一信用代码
|
// 按企业名称时传 enterpriseNo(加密名),按统一信用代码时传 enterpriseName(加密代码)
|
||||||
var enterpriseNo, enterpriseName string
|
reqData := map[string]interface{}{}
|
||||||
if paramsDto.EntName != "" {
|
if paramsDto.EntName != "" {
|
||||||
enterpriseName = paramsDto.EntName
|
reqData["enterpriseName"] = encryptedEntName
|
||||||
} else {
|
|
||||||
enterpriseNo = paramsDto.EntCode
|
|
||||||
}
|
}
|
||||||
reqData := map[string]interface{}{
|
if paramsDto.EntCode != "" {
|
||||||
"enterpriseNo": enterpriseNo,
|
reqData["enterpriseNo"] = encryptedEntCode
|
||||||
"enterpriseName": enterpriseName,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI088", reqData)
|
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI088", reqData)
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package qygl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/shujubao"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessQYGLJ0Q1Request QYGLJ0Q1 企业股权结构全景查询 API 处理方法(使用数据宝服务示例)
|
||||||
|
func ProcessQYGLJ0Q1Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.QYGLJ0Q1Req
|
||||||
|
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 二选一:企业名称(entName) 与 统一社会信用代码(creditCode) 必须且仅能传其一
|
||||||
|
hasEntName := paramsDto.EntName != ""
|
||||||
|
hasEntCode := paramsDto.EntCode != ""
|
||||||
|
if hasEntName == hasEntCode { // 两个都填或两个都未填
|
||||||
|
return nil, errors.Join(processors.ErrInvalidParam, errors.New("ent_name 与 ent_code 二选一,必须且仅能传其中一个"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建数据宝入参(sign 外的业务参数可按需 AES 加密后作为 bodyData)
|
||||||
|
reqParams := map[string]interface{}{
|
||||||
|
"key": "adac456f7b4ced764b606c8b07fed4d3",
|
||||||
|
}
|
||||||
|
if hasEntName {
|
||||||
|
reqParams["entName"] = paramsDto.EntName
|
||||||
|
} else {
|
||||||
|
reqParams["creditCode"] = paramsDto.EntCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// 最终请求 URL = https://api.chinadatapay.com/communication + 拼接接口地址值,如 personal/197
|
||||||
|
apiPath := "/communication/personal/10216"
|
||||||
|
data, err := deps.ShujubaoService.CallAPI(ctx, apiPath, reqParams)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, shujubao.ErrDatasource) {
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, err)
|
||||||
|
}
|
||||||
|
if errors.Is(err, shujubao.ErrQueryEmpty) {
|
||||||
|
return nil, errors.Join(processors.ErrNotFound, err)
|
||||||
|
}
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析响应中的 JSON 字符串(使用 qyglb4c0 中的 RecursiveParse)
|
||||||
|
parsedResp, err := RecursiveParse(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
respBytes, err := json.Marshal(parsedResp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
package qygl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/entities"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessQYGLJ1U9Request 企业全景报告处理器:并发调用企业全量(QYGLUY3S)、股权全景(QYGLJ0Q1)、司法涉诉(QYGL5S1I),
|
||||||
|
// 然后复用 qyglj1u9_processor_build.go 中的 buildReport / map* 逻辑生成企业报告结构
|
||||||
|
func ProcessQYGLJ1U9Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
// 复用 QYGLUY3S 的入参结构:企业名称/注册号/统一社会信用代码
|
||||||
|
var p dto.QYGLJ1U9Req
|
||||||
|
if err := json.Unmarshal(params, &p); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
if err := deps.Validator.ValidateStruct(p); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 并发调用三个已有处理器
|
||||||
|
type apiResult struct {
|
||||||
|
key string
|
||||||
|
data map[string]interface{}
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
resultsCh := make(chan apiResult, 3)
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
call := func(key string, req interface{}, fn func(context.Context, []byte, *processors.ProcessorDependencies) ([]byte, error)) {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
b, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
resultsCh <- apiResult{key: key, err: err}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := fn(ctx, b, deps)
|
||||||
|
if err != nil {
|
||||||
|
resultsCh <- apiResult{key: key, err: err}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var m map[string]interface{}
|
||||||
|
if err := json.Unmarshal(resp, &m); err != nil {
|
||||||
|
resultsCh <- apiResult{key: key, err: err}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resultsCh <- apiResult{key: key, data: m}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 企业全量信息核验V2(QYGLUY3S)
|
||||||
|
call("jiguangFull", map[string]interface{}{
|
||||||
|
"ent_name": p.EntName,
|
||||||
|
"ent_code": p.EntCode,
|
||||||
|
}, ProcessQYGLUY3SRequest)
|
||||||
|
|
||||||
|
// 企业股权结构全景(QYGLJ0Q1)
|
||||||
|
call("equityPanorama", map[string]interface{}{
|
||||||
|
"ent_name": p.EntName,
|
||||||
|
}, ProcessQYGLJ0Q1Request)
|
||||||
|
|
||||||
|
// 企业司法涉诉V2(QYGL5S1I)
|
||||||
|
call("judicialCertFull", map[string]interface{}{
|
||||||
|
"ent_name": p.EntName,
|
||||||
|
"ent_code": p.EntCode,
|
||||||
|
}, ProcessQYGL5S1IRequest)
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
close(resultsCh)
|
||||||
|
|
||||||
|
var jiguang, judicial, equity map[string]interface{}
|
||||||
|
for r := range resultsCh {
|
||||||
|
if r.err != nil {
|
||||||
|
// 任一关键数据源异常,则返回系统错误(也可以根据需求做降级)
|
||||||
|
return nil, errors.Join(processors.ErrSystem, fmt.Errorf("%s 调用失败: %w", r.key, r.err))
|
||||||
|
}
|
||||||
|
switch r.key {
|
||||||
|
case "jiguangFull":
|
||||||
|
jiguang = r.data
|
||||||
|
case "judicialCertFull":
|
||||||
|
judicial = r.data
|
||||||
|
case "equityPanorama":
|
||||||
|
equity = r.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if jiguang == nil {
|
||||||
|
jiguang = map[string]interface{}{}
|
||||||
|
}
|
||||||
|
if judicial == nil {
|
||||||
|
judicial = map[string]interface{}{}
|
||||||
|
}
|
||||||
|
if equity == nil {
|
||||||
|
equity = map[string]interface{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复用构建逻辑生成企业报告结构
|
||||||
|
report := buildReport(jiguang, judicial, equity)
|
||||||
|
|
||||||
|
// 为报告生成唯一编号并缓存,供后续通过编号查看
|
||||||
|
reportID := saveQYGLReport(report)
|
||||||
|
report["reportId"] = reportID
|
||||||
|
|
||||||
|
// 持久化企业报告记录到数据库(忽略持久化失败,不影响接口主流程)
|
||||||
|
if deps.ReportRepo != nil {
|
||||||
|
reqJSON, _ := json.Marshal(p)
|
||||||
|
reportJSON, _ := json.Marshal(report)
|
||||||
|
_ = deps.ReportRepo.Create(ctx, &entities.Report{
|
||||||
|
ReportID: reportID,
|
||||||
|
Type: "enterprise",
|
||||||
|
ApiCode: "QYGLJ1U9",
|
||||||
|
EntName: p.EntName,
|
||||||
|
EntCode: p.EntCode,
|
||||||
|
RequestParams: string(reqJSON),
|
||||||
|
ReportData: string(reportJSON),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 为报告补充前端查看链接,供调用方直接跳转到企业报告页面(通过编号访问)
|
||||||
|
report["reportUrl"] = buildQYGLReportURLByID(reportID)
|
||||||
|
|
||||||
|
out, err := json.Marshal(report)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 内存中的企业报告缓存(简单实现,进程重启后清空)
|
||||||
|
var qyglReportStore = struct {
|
||||||
|
sync.RWMutex
|
||||||
|
data map[string]map[string]interface{}
|
||||||
|
}{
|
||||||
|
data: make(map[string]map[string]interface{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
// saveQYGLReport 保存报告并返回生成的编号
|
||||||
|
func saveQYGLReport(report map[string]interface{}) string {
|
||||||
|
id := generateQYGLReportID()
|
||||||
|
qyglReportStore.Lock()
|
||||||
|
qyglReportStore.data[id] = report
|
||||||
|
qyglReportStore.Unlock()
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetQYGLReport 根据编号获取报告(供页面渲染使用)
|
||||||
|
func GetQYGLReport(id string) (map[string]interface{}, bool) {
|
||||||
|
qyglReportStore.RLock()
|
||||||
|
defer qyglReportStore.RUnlock()
|
||||||
|
r, ok := qyglReportStore.data[id]
|
||||||
|
return r, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateQYGLReportID 生成短编号
|
||||||
|
func generateQYGLReportID() string {
|
||||||
|
b := make([]byte, 8)
|
||||||
|
if _, err := rand.Read(b); err == nil {
|
||||||
|
return hex.EncodeToString(b)
|
||||||
|
}
|
||||||
|
// 随机数失败时退化为时间戳
|
||||||
|
return fmt.Sprintf("%d", time.Now().UnixNano())
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildQYGLReportURLByID 构造企业报告前端查看链接(通过编号查看)
|
||||||
|
func buildQYGLReportURLByID(id string) string {
|
||||||
|
return "https://api.tianyuanapi.com/reports/qygl/" + url.PathEscape(id)
|
||||||
|
}
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,35 @@
|
|||||||
|
package qygl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
sharedvalidator "tyapi-server/internal/shared/validator"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestQYGLJ1U9Req_ValidateParams 仅验证 QYGLJ1U9 入参的校验规则(特别是 validUSCI)。
|
||||||
|
func TestQYGLJ1U9Req_ValidateParams(t *testing.T) {
|
||||||
|
// 使用全局业务校验器
|
||||||
|
bv := sharedvalidator.NewBusinessValidator()
|
||||||
|
|
||||||
|
t.Run("invalid_usci_should_fail", func(t *testing.T) {
|
||||||
|
req := dto.QYGLJ1U9Req{
|
||||||
|
EntName: "测试企业有限公司",
|
||||||
|
EntCode: "123", // 明显不符合 validUSCI
|
||||||
|
}
|
||||||
|
if err := bv.ValidateStruct(req); err == nil {
|
||||||
|
t.Fatalf("expected validation error for invalid ent_code, got nil")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid_usci_should_pass", func(t *testing.T) {
|
||||||
|
req := dto.QYGLJ1U9Req{
|
||||||
|
EntName: "杭州娃哈哈集团有限公司",
|
||||||
|
EntCode: "91330000142916567N", // 符合 validUSCI 正则的示例
|
||||||
|
}
|
||||||
|
if err := bv.ValidateStruct(req); err != nil {
|
||||||
|
t.Fatalf("expected no validation error for valid ent_code, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package qygl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/shujubao"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessQYGLUY3SRequest QYGLUY3S 企业全量信息核验V2 可用 API 处理方法(使用数据宝服务示例)
|
||||||
|
func ProcessQYGLUY3SRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.QYGLUY3SReq
|
||||||
|
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建数据宝入参:姓名、身份证、手机号、银行卡号(sign 外的业务参数可按需 AES 加密后作为 bodyData)
|
||||||
|
reqParams := map[string]interface{}{
|
||||||
|
"key": "5131227a847c06c111f624a22ebacc06",
|
||||||
|
"entName": paramsDto.EntName,
|
||||||
|
"regno": paramsDto.EntRegno,
|
||||||
|
"creditcode": paramsDto.EntCode,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 最终请求 URL = https://api.chinadatapay.com/communication + 拼接接口地址值,如 personal/197
|
||||||
|
apiPath := "/communication/personal/10195"
|
||||||
|
data, err := deps.ShujubaoService.CallAPI(ctx, apiPath, reqParams)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, shujubao.ErrDatasource) {
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, err)
|
||||||
|
}
|
||||||
|
if errors.Is(err, shujubao.ErrQueryEmpty) {
|
||||||
|
return nil, errors.Join(processors.ErrNotFound, err)
|
||||||
|
}
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析响应中的 JSON 字符串(使用 qyglb4c0 中的 RecursiveParse)
|
||||||
|
parsedResp, err := RecursiveParse(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
respBytes, err := json.Marshal(parsedResp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package yysy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/shumai"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessYYSY35TARequest YYSY35TA API 运营商归属地数卖处理方法数脉
|
||||||
|
func ProcessYYSY35TARequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.YYSY35TAReq
|
||||||
|
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
reqFormData := map[string]interface{}{
|
||||||
|
"mobile": paramsDto.MobileNo,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 以表单方式调用数脉 API;参数在 CallAPIForm 内转为 application/x-www-form-urlencoded
|
||||||
|
apiPath := "/v4/phone/number" // 接口路径,根据数脉文档填写(如 v4/xxx)
|
||||||
|
respBytes, err := deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, shumai.ErrNotFound) {
|
||||||
|
// 查无记录情况
|
||||||
|
return nil, errors.Join(processors.ErrNotFound, err)
|
||||||
|
} else if errors.Is(err, shumai.ErrDatasource) {
|
||||||
|
// 数据源错误
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, err)
|
||||||
|
} else if errors.Is(err, shumai.ErrSystem) {
|
||||||
|
// 系统错误
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
} else {
|
||||||
|
// 其他未知错误
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
@@ -39,6 +39,14 @@ func ProcessYYSY9E4ARequest(ctx context.Context, params []byte, deps *processors
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 兼容上游有时返回 JSON 字符串的情况:如果是字符串则尝试再反序列化一次
|
||||||
|
if str, ok := respData.(string); ok && str != "" {
|
||||||
|
var parsed interface{}
|
||||||
|
if err := json.Unmarshal([]byte(str), &parsed); err == nil {
|
||||||
|
respData = parsed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 将响应数据转换为JSON字节
|
// 将响应数据转换为JSON字节
|
||||||
respBytes, err := json.Marshal(respData)
|
respBytes, err := json.Marshal(respData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ import (
|
|||||||
"tyapi-server/internal/shared/interfaces"
|
"tyapi-server/internal/shared/interfaces"
|
||||||
)
|
)
|
||||||
|
|
||||||
// calculateAlipayRechargeBonus 计算支付宝充值赠送金额
|
// calculateAlipayRechargeBonus 计算支付宝充值赠送金额(受 recharge_bonus_enabled 开关控制)
|
||||||
func calculateAlipayRechargeBonus(rechargeAmount decimal.Decimal, walletConfig *config.WalletConfig) decimal.Decimal {
|
func calculateAlipayRechargeBonus(rechargeAmount decimal.Decimal, walletConfig *config.WalletConfig) decimal.Decimal {
|
||||||
if walletConfig == nil || len(walletConfig.AliPayRechargeBonus) == 0 {
|
if walletConfig == nil || !walletConfig.RechargeBonusEnabled || len(walletConfig.AliPayRechargeBonus) == 0 {
|
||||||
return decimal.Zero
|
return decimal.Zero
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestCalculateAlipayRechargeBonus(t *testing.T) {
|
func TestCalculateAlipayRechargeBonus(t *testing.T) {
|
||||||
// 创建测试配置
|
// 创建测试配置(开启赠送)
|
||||||
walletConfig := &config.WalletConfig{
|
walletConfig := &config.WalletConfig{
|
||||||
|
RechargeBonusEnabled: true,
|
||||||
AliPayRechargeBonus: []config.AliPayRechargeBonusRule{
|
AliPayRechargeBonus: []config.AliPayRechargeBonusRule{
|
||||||
{RechargeAmount: 1000.00, BonusAmount: 50.00}, // 充1000送50
|
{RechargeAmount: 1000.00, BonusAmount: 50.00}, // 充1000送50
|
||||||
{RechargeAmount: 5000.00, BonusAmount: 300.00}, // 充5000送300
|
{RechargeAmount: 5000.00, BonusAmount: 300.00}, // 充5000送300
|
||||||
@@ -74,6 +75,7 @@ func TestCalculateAlipayRechargeBonus(t *testing.T) {
|
|||||||
func TestCalculateAlipayRechargeBonus_EmptyConfig(t *testing.T) {
|
func TestCalculateAlipayRechargeBonus_EmptyConfig(t *testing.T) {
|
||||||
// 测试空配置
|
// 测试空配置
|
||||||
walletConfig := &config.WalletConfig{
|
walletConfig := &config.WalletConfig{
|
||||||
|
RechargeBonusEnabled: true,
|
||||||
AliPayRechargeBonus: []config.AliPayRechargeBonusRule{},
|
AliPayRechargeBonus: []config.AliPayRechargeBonusRule{},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,4 +87,17 @@ func TestCalculateAlipayRechargeBonus_EmptyConfig(t *testing.T) {
|
|||||||
assert.True(t, bonus.Equal(decimal.Zero), "nil配置应该返回零赠送金额")
|
assert.True(t, bonus.Equal(decimal.Zero), "nil配置应该返回零赠送金额")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCalculateAlipayRechargeBonus_Disabled(t *testing.T) {
|
||||||
|
// 关闭赠送时,任意金额均不赠送
|
||||||
|
walletConfig := &config.WalletConfig{
|
||||||
|
RechargeBonusEnabled: false,
|
||||||
|
AliPayRechargeBonus: []config.AliPayRechargeBonusRule{
|
||||||
|
{RechargeAmount: 1000.00, BonusAmount: 50.00},
|
||||||
|
{RechargeAmount: 10000.00, BonusAmount: 800.00},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
bonus := calculateAlipayRechargeBonus(decimal.NewFromFloat(10000.00), walletConfig)
|
||||||
|
assert.True(t, bonus.Equal(decimal.Zero), "关闭赠送时应返回零")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/entities"
|
||||||
|
"tyapi-server/internal/domains/api/repositories"
|
||||||
|
"tyapi-server/internal/shared/database"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ReportsTable = "reports"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GormReportRepository 报告记录 GORM 仓储实现
|
||||||
|
type GormReportRepository struct {
|
||||||
|
*database.BaseRepositoryImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ repositories.ReportRepository = (*GormReportRepository)(nil)
|
||||||
|
|
||||||
|
// NewGormReportRepository 创建报告记录仓储实现
|
||||||
|
func NewGormReportRepository(db *gorm.DB, logger *zap.Logger) repositories.ReportRepository {
|
||||||
|
return &GormReportRepository{
|
||||||
|
BaseRepositoryImpl: database.NewBaseRepositoryImpl(db, logger),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create 创建报告记录
|
||||||
|
func (r *GormReportRepository) Create(ctx context.Context, report *entities.Report) error {
|
||||||
|
return r.CreateEntity(ctx, report)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindByReportID 根据报告编号查询记录
|
||||||
|
func (r *GormReportRepository) FindByReportID(ctx context.Context, reportID string) (*entities.Report, error) {
|
||||||
|
var report entities.Report
|
||||||
|
if err := r.FindOneByField(ctx, &report, "report_id", reportID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &report, nil
|
||||||
|
}
|
||||||
|
|
||||||
@@ -17,12 +17,50 @@ import (
|
|||||||
"tyapi-server/internal/shared/external_logger"
|
"tyapi-server/internal/shared/external_logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// 错误日志中单条入参值的最大长度,避免 base64 等长内容打满日志
|
||||||
|
maxLogParamValueLen = 300
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrDatasource = errors.New("数据源异常")
|
ErrDatasource = errors.New("数据源异常")
|
||||||
ErrSystem = errors.New("系统异常")
|
ErrSystem = errors.New("系统异常")
|
||||||
ErrQueryEmpty = errors.New("查询为空")
|
ErrQueryEmpty = errors.New("查询为空")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// truncateForLog 将字符串截断到指定长度,用于错误日志,避免 base64 等过长内容
|
||||||
|
func truncateForLog(s string, maxLen int) string {
|
||||||
|
if maxLen <= 0 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
if len(s) <= maxLen {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return s[:maxLen] + "...[truncated, total " + strconv.Itoa(len(s)) + " chars]"
|
||||||
|
}
|
||||||
|
|
||||||
|
// paramsForLog 返回适合写入错误日志的入参副本(长字符串会被截断)
|
||||||
|
func paramsForLog(params map[string]interface{}) map[string]interface{} {
|
||||||
|
if params == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := make(map[string]interface{}, len(params))
|
||||||
|
for k, v := range params {
|
||||||
|
if v == nil {
|
||||||
|
out[k] = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch val := v.(type) {
|
||||||
|
case string:
|
||||||
|
out[k] = truncateForLog(val, maxLogParamValueLen)
|
||||||
|
default:
|
||||||
|
s := fmt.Sprint(v)
|
||||||
|
out[k] = truncateForLog(s, maxLogParamValueLen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
// ShujubaoResp 数据宝 API 通用响应(按实际文档调整)
|
// ShujubaoResp 数据宝 API 通用响应(按实际文档调整)
|
||||||
type ShujubaoResp struct {
|
type ShujubaoResp struct {
|
||||||
Code string `json:"code"`
|
Code string `json:"code"`
|
||||||
@@ -187,7 +225,7 @@ func (s *ShujubaoService) CallAPI(ctx context.Context, apiPath string, params ma
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.Join(ErrSystem, err)
|
err = errors.Join(ErrSystem, err)
|
||||||
if s.logger != nil {
|
if s.logger != nil {
|
||||||
s.logger.LogError(requestID, transactionID, apiPath, err, params)
|
s.logger.LogError(requestID, transactionID, apiPath, err, paramsForLog(params))
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -216,7 +254,7 @@ func (s *ShujubaoService) CallAPI(ctx context.Context, apiPath string, params ma
|
|||||||
err = errors.Join(ErrSystem, err)
|
err = errors.Join(ErrSystem, err)
|
||||||
}
|
}
|
||||||
if s.logger != nil {
|
if s.logger != nil {
|
||||||
s.logger.LogError(requestID, transactionID, apiPath, err, params)
|
s.logger.LogError(requestID, transactionID, apiPath, err, paramsForLog(params))
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -226,7 +264,7 @@ func (s *ShujubaoService) CallAPI(ctx context.Context, apiPath string, params ma
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.Join(ErrSystem, err)
|
err = errors.Join(ErrSystem, err)
|
||||||
if s.logger != nil {
|
if s.logger != nil {
|
||||||
s.logger.LogError(requestID, transactionID, apiPath, err, params)
|
s.logger.LogError(requestID, transactionID, apiPath, err, paramsForLog(params))
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -239,7 +277,7 @@ func (s *ShujubaoService) CallAPI(ctx context.Context, apiPath string, params ma
|
|||||||
if response.StatusCode != http.StatusOK {
|
if response.StatusCode != http.StatusOK {
|
||||||
err = errors.Join(ErrDatasource, fmt.Errorf("HTTP状态码 %d", response.StatusCode))
|
err = errors.Join(ErrDatasource, fmt.Errorf("HTTP状态码 %d", response.StatusCode))
|
||||||
if s.logger != nil {
|
if s.logger != nil {
|
||||||
s.logger.LogError(requestID, transactionID, apiPath, err, params)
|
s.logger.LogError(requestID, transactionID, apiPath, err, paramsForLog(params))
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -248,7 +286,7 @@ func (s *ShujubaoService) CallAPI(ctx context.Context, apiPath string, params ma
|
|||||||
if err := json.Unmarshal(respBody, &shujubaoResp); err != nil {
|
if err := json.Unmarshal(respBody, &shujubaoResp); err != nil {
|
||||||
err = errors.Join(ErrSystem, fmt.Errorf("响应解析失败: %w", err))
|
err = errors.Join(ErrSystem, fmt.Errorf("响应解析失败: %w", err))
|
||||||
if s.logger != nil {
|
if s.logger != nil {
|
||||||
s.logger.LogError(requestID, transactionID, apiPath, err, params)
|
s.logger.LogError(requestID, transactionID, apiPath, err, paramsForLog(params))
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -261,7 +299,7 @@ func (s *ShujubaoService) CallAPI(ctx context.Context, apiPath string, params ma
|
|||||||
if code != "10000" {
|
if code != "10000" {
|
||||||
shujubaoErr := NewShujubaoErrorFromCode(code, shujubaoResp.Message)
|
shujubaoErr := NewShujubaoErrorFromCode(code, shujubaoResp.Message)
|
||||||
if s.logger != nil {
|
if s.logger != nil {
|
||||||
s.logger.LogError(requestID, transactionID, apiPath, shujubaoErr, params)
|
s.logger.LogError(requestID, transactionID, apiPath, shujubaoErr, paramsForLog(params))
|
||||||
}
|
}
|
||||||
return nil, errors.Join(ErrDatasource, shujubaoErr)
|
return nil, errors.Join(ErrDatasource, shujubaoErr)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,12 +16,52 @@ import (
|
|||||||
"tyapi-server/internal/shared/external_logger"
|
"tyapi-server/internal/shared/external_logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// 错误日志中单条入参值的最大长度,避免 base64 等长内容打满日志
|
||||||
|
maxLogParamValueLen = 300
|
||||||
|
// 错误日志中 response_body 的最大长度
|
||||||
|
maxLogResponseBodyLen = 500
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrDatasource = errors.New("数据源异常")
|
ErrDatasource = errors.New("数据源异常")
|
||||||
ErrSystem = errors.New("系统异常")
|
ErrSystem = errors.New("系统异常")
|
||||||
ErrNotFound = errors.New("查询为空")
|
ErrNotFound = errors.New("查询为空")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// truncateForLog 将字符串截断到指定长度,用于错误日志,避免 base64 等过长内容
|
||||||
|
func truncateForLog(s string, maxLen int) string {
|
||||||
|
if maxLen <= 0 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
if len(s) <= maxLen {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return s[:maxLen] + "...[truncated, total " + strconv.Itoa(len(s)) + " chars]"
|
||||||
|
}
|
||||||
|
|
||||||
|
// requestParamsForLog 返回适合写入错误日志的入参副本(长字符串会被截断)
|
||||||
|
func requestParamsForLog(reqFormData map[string]interface{}) map[string]interface{} {
|
||||||
|
if reqFormData == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := make(map[string]interface{}, len(reqFormData))
|
||||||
|
for k, v := range reqFormData {
|
||||||
|
if v == nil {
|
||||||
|
out[k] = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch val := v.(type) {
|
||||||
|
case string:
|
||||||
|
out[k] = truncateForLog(val, maxLogParamValueLen)
|
||||||
|
default:
|
||||||
|
s := fmt.Sprint(v)
|
||||||
|
out[k] = truncateForLog(s, maxLogParamValueLen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
// ShumaiResponse 数脉 API 通用响应(占位,按实际文档调整)
|
// ShumaiResponse 数脉 API 通用响应(占位,按实际文档调整)
|
||||||
type ShumaiResponse struct {
|
type ShumaiResponse struct {
|
||||||
Code int `json:"code"` // 状态码
|
Code int `json:"code"` // 状态码
|
||||||
@@ -188,7 +228,7 @@ func (s *ShumaiService) CallAPIForm(ctx context.Context, apiPath string, reqForm
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.Join(ErrSystem, err)
|
err = errors.Join(ErrSystem, err)
|
||||||
if s.logger != nil {
|
if s.logger != nil {
|
||||||
s.logger.LogError(requestID, transactionID, apiPath, err, map[string]interface{}{"request_params": reqFormData})
|
s.logger.LogError(requestID, transactionID, apiPath, err, map[string]interface{}{"request_params": requestParamsForLog(reqFormData)})
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -216,7 +256,7 @@ func (s *ShumaiService) CallAPIForm(ctx context.Context, apiPath string, reqForm
|
|||||||
err = errors.Join(ErrSystem, err)
|
err = errors.Join(ErrSystem, err)
|
||||||
}
|
}
|
||||||
if s.logger != nil {
|
if s.logger != nil {
|
||||||
s.logger.LogError(requestID, transactionID, apiPath, err, map[string]interface{}{"request_params": reqFormData})
|
s.logger.LogError(requestID, transactionID, apiPath, err, map[string]interface{}{"request_params": requestParamsForLog(reqFormData)})
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -227,7 +267,7 @@ func (s *ShumaiService) CallAPIForm(ctx context.Context, apiPath string, reqForm
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.Join(ErrSystem, err)
|
err = errors.Join(ErrSystem, err)
|
||||||
if s.logger != nil {
|
if s.logger != nil {
|
||||||
s.logger.LogError(requestID, transactionID, apiPath, err, map[string]interface{}{"request_params": reqFormData})
|
s.logger.LogError(requestID, transactionID, apiPath, err, map[string]interface{}{"request_params": requestParamsForLog(reqFormData)})
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -236,8 +276,8 @@ func (s *ShumaiService) CallAPIForm(ctx context.Context, apiPath string, reqForm
|
|||||||
err = errors.Join(ErrDatasource, fmt.Errorf("HTTP %d", resp.StatusCode))
|
err = errors.Join(ErrDatasource, fmt.Errorf("HTTP %d", resp.StatusCode))
|
||||||
if s.logger != nil {
|
if s.logger != nil {
|
||||||
errorPayload := map[string]interface{}{
|
errorPayload := map[string]interface{}{
|
||||||
"request_params": reqFormData,
|
"request_params": requestParamsForLog(reqFormData),
|
||||||
"response_body": string(raw),
|
"response_body": truncateForLog(string(raw), maxLogResponseBodyLen),
|
||||||
}
|
}
|
||||||
s.logger.LogError(requestID, transactionID, apiPath, err, errorPayload)
|
s.logger.LogError(requestID, transactionID, apiPath, err, errorPayload)
|
||||||
}
|
}
|
||||||
@@ -253,8 +293,8 @@ func (s *ShumaiService) CallAPIForm(ctx context.Context, apiPath string, reqForm
|
|||||||
parseErr := errors.Join(ErrSystem, fmt.Errorf("响应解析失败: %w", err))
|
parseErr := errors.Join(ErrSystem, fmt.Errorf("响应解析失败: %w", err))
|
||||||
if s.logger != nil {
|
if s.logger != nil {
|
||||||
s.logger.LogError(requestID, transactionID, apiPath, parseErr, map[string]interface{}{
|
s.logger.LogError(requestID, transactionID, apiPath, parseErr, map[string]interface{}{
|
||||||
"request_params": reqFormData,
|
"request_params": requestParamsForLog(reqFormData),
|
||||||
"response_body": string(raw),
|
"response_body": truncateForLog(string(raw), maxLogResponseBodyLen),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return nil, parseErr
|
return nil, parseErr
|
||||||
@@ -273,8 +313,8 @@ func (s *ShumaiService) CallAPIForm(ctx context.Context, apiPath string, reqForm
|
|||||||
}
|
}
|
||||||
if s.logger != nil {
|
if s.logger != nil {
|
||||||
s.logger.LogError(requestID, transactionID, apiPath, shumaiErr, map[string]interface{}{
|
s.logger.LogError(requestID, transactionID, apiPath, shumaiErr, map[string]interface{}{
|
||||||
"request_params": reqFormData,
|
"request_params": requestParamsForLog(reqFormData),
|
||||||
"response_body": string(raw),
|
"response_body": truncateForLog(string(raw), maxLogResponseBodyLen),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if shumaiErr.IsNoRecord() {
|
if shumaiErr.IsNoRecord() {
|
||||||
@@ -292,8 +332,8 @@ func (s *ShumaiService) CallAPIForm(ctx context.Context, apiPath string, reqForm
|
|||||||
marshalErr := errors.Join(ErrSystem, fmt.Errorf("data 序列化失败: %w", err))
|
marshalErr := errors.Join(ErrSystem, fmt.Errorf("data 序列化失败: %w", err))
|
||||||
if s.logger != nil {
|
if s.logger != nil {
|
||||||
s.logger.LogError(requestID, transactionID, apiPath, marshalErr, map[string]interface{}{
|
s.logger.LogError(requestID, transactionID, apiPath, marshalErr, map[string]interface{}{
|
||||||
"request_params": reqFormData,
|
"request_params": requestParamsForLog(reqFormData),
|
||||||
"response_body": string(raw),
|
"response_body": truncateForLog(string(raw), maxLogResponseBodyLen),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return nil, marshalErr
|
return nil, marshalErr
|
||||||
|
|||||||
@@ -210,8 +210,13 @@ func (z *ZhichaService) CallAPI(ctx context.Context, proID string, params map[st
|
|||||||
return nil, ErrDatasource
|
return nil, ErrDatasource
|
||||||
}
|
}
|
||||||
|
|
||||||
// 201 表示查询为空,返回空对象
|
// 201 表示查询为空,兼容其它情况如果data也为空,则返回空对象
|
||||||
if zhichaResp.Code == "201" {
|
if zhichaResp.Code == "201" {
|
||||||
|
// 先做类型断言
|
||||||
|
dataMap, ok := zhichaResp.Data.(map[string]interface{})
|
||||||
|
if ok && len(dataMap) > 0 {
|
||||||
|
return dataMap, nil
|
||||||
|
}
|
||||||
return map[string]interface{}{}, nil
|
return map[string]interface{}{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -84,20 +84,25 @@ func TestEncryptWithInvalidKey(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDecryptWithInvalidData(t *testing.T) {
|
func TestDecryptWithInvalidData(t *testing.T) {
|
||||||
key := "1234567890abcdef1234567890abcdef"
|
key := "af4ca0098e6a202a5c08c413ebd9fd62"
|
||||||
|
|
||||||
// 测试无效的加密数据
|
// 测试无效的加密数据
|
||||||
invalidData := []string{
|
invalidData := []string{
|
||||||
"", // 空数据
|
"", // 空数据
|
||||||
"invalid_base64", // 无效的Base64
|
"invalid_base64", // 无效的Base64
|
||||||
"dGVzdA==", // 有效的Base64但不是AES加密数据
|
"dGVzdA==", // 有效的Base64但不是AES加密数据
|
||||||
|
"i96w+SDjwENjuvsokMFbLw==",
|
||||||
|
"oaihmICgEcszWMk0gXoB12E/ygF4g78x0/sC3/KHnBk=",
|
||||||
|
"5bx+WvXvdNRVVOp9UuNFHg==",
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, data := range invalidData {
|
for _, data := range invalidData {
|
||||||
_, err := Decrypt(data, key)
|
decrypted, err := Decrypt(data, key)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("使用无效数据 %s 应该返回错误", data)
|
t.Errorf("使用无效数据 %s 应该返回错误", data)
|
||||||
}
|
}
|
||||||
|
fmt.Println("data: ", data)
|
||||||
|
fmt.Println("decrypted: ", decrypted)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
137
internal/infrastructure/http/handlers/qygl_report_handler.go
Normal file
137
internal/infrastructure/http/handlers/qygl_report_handler.go
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
"tyapi-server/internal/application/api/commands"
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
api_repositories "tyapi-server/internal/domains/api/repositories"
|
||||||
|
api_services "tyapi-server/internal/domains/api/services"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors/qygl"
|
||||||
|
)
|
||||||
|
|
||||||
|
// QYGLReportHandler 企业全景报告页面渲染处理器
|
||||||
|
// 使用 QYGLJ1U9 聚合接口生成企业报告数据,并通过模板引擎渲染 qiye.html
|
||||||
|
type QYGLReportHandler struct {
|
||||||
|
apiRequestService *api_services.ApiRequestService
|
||||||
|
logger *zap.Logger
|
||||||
|
|
||||||
|
reportRepo api_repositories.ReportRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewQYGLReportHandler 创建企业报告页面处理器
|
||||||
|
func NewQYGLReportHandler(
|
||||||
|
apiRequestService *api_services.ApiRequestService,
|
||||||
|
logger *zap.Logger,
|
||||||
|
reportRepo api_repositories.ReportRepository,
|
||||||
|
) *QYGLReportHandler {
|
||||||
|
return &QYGLReportHandler{
|
||||||
|
apiRequestService: apiRequestService,
|
||||||
|
logger: logger,
|
||||||
|
reportRepo: reportRepo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetQYGLReportPage 企业全景报告页面
|
||||||
|
// GET /reports/qygl?ent_code=xxx&ent_name=yyy&ent_reg_no=zzz
|
||||||
|
func (h *QYGLReportHandler) GetQYGLReportPage(c *gin.Context) {
|
||||||
|
// 读取查询参数
|
||||||
|
entCode := c.Query("ent_code")
|
||||||
|
entName := c.Query("ent_name")
|
||||||
|
entRegNo := c.Query("ent_reg_no")
|
||||||
|
|
||||||
|
// 组装 QYGLUY3S 入参
|
||||||
|
req := dto.QYGLUY3SReq{
|
||||||
|
EntName: entName,
|
||||||
|
EntRegno: entRegNo,
|
||||||
|
EntCode: entCode,
|
||||||
|
}
|
||||||
|
|
||||||
|
params, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("序列化企业全景报告入参失败", zap.Error(err))
|
||||||
|
c.String(http.StatusInternalServerError, "生成企业报告失败,请稍后重试")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通过 ApiRequestService 调用 QYGLJ1U9 聚合处理器
|
||||||
|
options := &commands.ApiCallOptions{}
|
||||||
|
callCtx := &processors.CallContext{}
|
||||||
|
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
respBytes, err := h.apiRequestService.PreprocessRequestApi(ctx, "QYGLJ1U9", params, options, callCtx)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("调用企业全景报告处理器失败", zap.Error(err))
|
||||||
|
c.String(http.StatusInternalServerError, "生成企业报告失败,请稍后重试")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var report map[string]interface{}
|
||||||
|
if err := json.Unmarshal(respBytes, &report); err != nil {
|
||||||
|
h.logger.Error("解析企业全景报告结果失败", zap.Error(err))
|
||||||
|
c.String(http.StatusInternalServerError, "生成企业报告失败,请稍后重试")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
reportJSONBytes, err := json.Marshal(report)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("序列化企业全景报告结果失败", zap.Error(err))
|
||||||
|
c.String(http.StatusInternalServerError, "生成企业报告失败,请稍后重试")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 template.JS 避免在脚本中被转义,直接作为 JS 对象字面量注入
|
||||||
|
reportJSON := template.JS(reportJSONBytes)
|
||||||
|
|
||||||
|
c.HTML(http.StatusOK, "qiye.html", gin.H{
|
||||||
|
"ReportJSON": reportJSON,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetQYGLReportPageByID 通过编号查看企业全景报告页面
|
||||||
|
// GET /reports/qygl/:id
|
||||||
|
func (h *QYGLReportHandler) GetQYGLReportPageByID(c *gin.Context) {
|
||||||
|
id := c.Param("id")
|
||||||
|
if id == "" {
|
||||||
|
c.String(http.StatusBadRequest, "报告编号不能为空")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 优先从数据库中查询报告记录
|
||||||
|
if h.reportRepo != nil {
|
||||||
|
if entity, err := h.reportRepo.FindByReportID(c.Request.Context(), id); err == nil && entity != nil {
|
||||||
|
reportJSON := template.JS(entity.ReportData)
|
||||||
|
c.HTML(http.StatusOK, "qiye.html", gin.H{
|
||||||
|
"ReportJSON": reportJSON,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回退到进程内存缓存(兼容老的访问方式)
|
||||||
|
report, ok := qygl.GetQYGLReport(id)
|
||||||
|
if !ok {
|
||||||
|
c.String(http.StatusNotFound, "报告不存在或已过期")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
reportJSONBytes, err := json.Marshal(report)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("序列化企业全景报告结果失败", zap.Error(err))
|
||||||
|
c.String(http.StatusInternalServerError, "生成企业报告失败,请稍后再试")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
reportJSON := template.JS(reportJSONBytes)
|
||||||
|
|
||||||
|
c.HTML(http.StatusOK, "qiye.html", gin.H{
|
||||||
|
"ReportJSON": reportJSON,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
32
internal/infrastructure/http/routes/qygl_report_routes.go
Normal file
32
internal/infrastructure/http/routes/qygl_report_routes.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"tyapi-server/internal/infrastructure/http/handlers"
|
||||||
|
sharedhttp "tyapi-server/internal/shared/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// QYGLReportRoutes 企业报告页面路由注册器
|
||||||
|
type QYGLReportRoutes struct {
|
||||||
|
handler *handlers.QYGLReportHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewQYGLReportRoutes 创建企业报告页面路由注册器
|
||||||
|
func NewQYGLReportRoutes(
|
||||||
|
handler *handlers.QYGLReportHandler,
|
||||||
|
) *QYGLReportRoutes {
|
||||||
|
return &QYGLReportRoutes{
|
||||||
|
handler: handler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register 注册企业报告页面路由
|
||||||
|
func (r *QYGLReportRoutes) Register(router *sharedhttp.GinRouter) {
|
||||||
|
engine := router.GetEngine()
|
||||||
|
|
||||||
|
// 企业全景报告页面(实时生成)
|
||||||
|
engine.GET("/reports/qygl", r.handler.GetQYGLReportPage)
|
||||||
|
|
||||||
|
// 企业全景报告页面(通过编号查看)
|
||||||
|
engine.GET("/reports/qygl/:id", r.handler.GetQYGLReportPageByID)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -37,6 +38,18 @@ func NewGinRouter(cfg *config.Config, logger *zap.Logger) *GinRouter {
|
|||||||
// 创建Gin引擎
|
// 创建Gin引擎
|
||||||
engine := gin.New()
|
engine := gin.New()
|
||||||
|
|
||||||
|
// 加载HTML模板(企业报告等页面)
|
||||||
|
// 为避免生产环境文件不存在导致panic,这里先检查文件是否存在
|
||||||
|
const reportTemplatePath = "resources/qiye.html"
|
||||||
|
if _, err := os.Stat(reportTemplatePath); err == nil {
|
||||||
|
engine.LoadHTMLFiles(reportTemplatePath)
|
||||||
|
logger.Info("已加载企业报告模板文件", zap.String("template", reportTemplatePath))
|
||||||
|
} else {
|
||||||
|
logger.Warn("未找到企业报告模板文件,将跳过模板加载(请确认部署时包含 resources/qiye.html)",
|
||||||
|
zap.String("template", reportTemplatePath),
|
||||||
|
zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
return &GinRouter{
|
return &GinRouter{
|
||||||
engine: engine,
|
engine: engine,
|
||||||
config: cfg,
|
config: cfg,
|
||||||
|
|||||||
Binary file not shown.
2186
resources/qiye.html
Normal file
2186
resources/qiye.html
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user