Compare commits

...

40 Commits

Author SHA1 Message Date
c5970da195 f 2026-03-10 19:15:54 +08:00
1bcb4a9c2e f 2026-03-10 19:12:35 +08:00
8877cf9691 f 2026-03-10 19:07:29 +08:00
f63e6df9f9 f 2026-03-10 19:03:42 +08:00
a00fe12141 f 2026-03-10 17:56:31 +08:00
6b80182986 f 2026-03-10 17:49:53 +08:00
bf4c114ee2 Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-03-10 17:28:22 +08:00
4cd3954574 f 2026-03-10 17:28:04 +08:00
Mrx
d7a5589873 f 2026-03-09 11:31:40 +08:00
Mrx
b0ec75d1af f 2026-03-06 16:39:00 +08:00
Mrx
57d18be972 f 2026-03-06 16:28:25 +08:00
Mrx
3d8775b6dc f 2026-03-06 15:20:27 +08:00
Mrx
f40950f890 f 2026-03-06 15:12:58 +08:00
Mrx
ba21a8f965 Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-03-06 10:56:37 +08:00
Mrx
7d2716da7a f 2026-03-06 10:56:36 +08:00
9a7bda9527 f 2026-03-05 19:03:18 +08:00
abdae033f0 f 2026-03-05 18:44:17 +08:00
96abacd392 f 2026-03-05 18:41:00 +08:00
4e6c93413e Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-03-05 17:44:59 +08:00
9c8dbd458f f 2026-03-05 17:44:50 +08:00
Mrx
9e9cee02f5 Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-03-05 16:24:56 +08:00
Mrx
360bd579ce f 2026-03-05 16:24:53 +08:00
db889ccba0 f 2026-03-05 14:17:48 +08:00
25a4961328 f 2026-03-05 12:26:02 +08:00
Mrx
578e68a76b f 2026-03-05 11:05:01 +08:00
Mrx
019e47896d f 2026-03-05 10:57:06 +08:00
Mrx
c0898e6829 f 2026-03-05 10:54:16 +08:00
Mrx
4ee6e891cd f 2026-03-04 14:10:30 +08:00
Mrx
44b5f6b145 f 2026-03-04 13:20:44 +08:00
Mrx
677b7362cf f 2026-03-04 13:19:55 +08:00
Mrx
02dbc02fe8 f 2026-03-04 12:59:45 +08:00
Mrx
374143995e f 2026-03-04 12:41:08 +08:00
Mrx
7a957a6b87 f 2026-03-04 12:39:01 +08:00
Mrx
c885d562ee f 2026-03-02 19:21:23 +08:00
Mrx
9f36cd8b63 f 2026-03-02 12:21:25 +08:00
Mrx
4122f874fc f 2026-03-02 12:16:27 +08:00
Mrx
9a32387b21 f 2026-03-02 11:56:47 +08:00
Mrx
7bf9150cfc f 2026-03-02 11:39:53 +08:00
Mrx
fecd5a38fd Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-03-02 11:38:30 +08:00
Mrx
2636d9dff6 f 2026-03-02 11:38:19 +08:00
37 changed files with 4891 additions and 441 deletions

View File

@@ -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

View File

@@ -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权限运行

View File

@@ -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{},

View File

@@ -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{

View File

@@ -346,12 +346,12 @@ func (s *UserApplicationServiceImpl) ListUsers(ctx context.Context, query *queri
contractItems := make([]*responses.ContractInfoItem, 0, len(contracts)) contractItems := make([]*responses.ContractInfoItem, 0, len(contracts))
for _, contract := range contracts { for _, contract := range contracts {
contractItems = append(contractItems, &responses.ContractInfoItem{ contractItems = append(contractItems, &responses.ContractInfoItem{
ID: contract.ID, ID: contract.ID,
ContractName: contract.ContractName, ContractName: contract.ContractName,
ContractType: string(contract.ContractType), ContractType: string(contract.ContractType),
ContractTypeName: contract.GetContractTypeName(), ContractTypeName: contract.GetContractTypeName(),
ContractFileURL: contract.ContractFileURL, ContractFileURL: contract.ContractFileURL,
CreatedAt: contract.CreatedAt, CreatedAt: contract.CreatedAt,
}) })
} }
item.EnterpriseInfo.Contracts = contractItems item.EnterpriseInfo.Contracts = contractItems
@@ -418,12 +418,12 @@ func (s *UserApplicationServiceImpl) GetUserDetail(ctx context.Context, userID s
contractItems := make([]*responses.ContractInfoItem, 0, len(contracts)) contractItems := make([]*responses.ContractInfoItem, 0, len(contracts))
for _, contract := range contracts { for _, contract := range contracts {
contractItems = append(contractItems, &responses.ContractInfoItem{ contractItems = append(contractItems, &responses.ContractInfoItem{
ID: contract.ID, ID: contract.ID,
ContractName: contract.ContractName, ContractName: contract.ContractName,
ContractType: string(contract.ContractType), ContractType: string(contract.ContractType),
ContractTypeName: contract.GetContractTypeName(), ContractTypeName: contract.GetContractTypeName(),
ContractFileURL: contract.ContractFileURL, ContractFileURL: contract.ContractFileURL,
CreatedAt: contract.CreatedAt, CreatedAt: contract.CreatedAt,
}) })
} }
item.EnterpriseInfo.Contracts = contractItems item.EnterpriseInfo.Contracts = contractItems

View File

@@ -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()

View File

@@ -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"`

View File

@@ -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

View 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"
}

View File

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

View File

@@ -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,26 +69,76 @@ 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)
return &ApiRequestService{ return &ApiRequestService{
westDexService: westDexService, westDexService: westDexService,
muziService: muziService, muziService: muziService,
yushanService: yushanService, yushanService: yushanService,
tianYanChaService: tianYanChaService, tianYanChaService: tianYanChaService,
alicloudService: alicloudService, alicloudService: alicloudService,
validator: validator, validator: validator,
processorDeps: processorDeps, processorDeps: processorDeps,
combService: combService, combService: combService,
config: cfg, config: cfg,
reportRepo: reportRepo,
} }
} }
@@ -145,10 +198,12 @@ func registerAllProcessors(combService *comb.CombService) {
"JRZQ1W4X": jrzq.ProcessJRZQ1W4XRequest, "JRZQ1W4X": jrzq.ProcessJRZQ1W4XRequest,
"JRZQ3P01": jrzq.ProcessJRZQ3P01Request, "JRZQ3P01": jrzq.ProcessJRZQ3P01Request,
"JRZQ3AG6": jrzq.ProcessJRZQ3AG6Request, "JRZQ3AG6": jrzq.ProcessJRZQ3AG6Request,
"JRZQO6L7": jrzq.ProcessJRZQO6L7Request, // 全国自然人经济特征评分模型v3 简版 "JRZQO6L7": jrzq.ProcessJRZQO6L7Request, // 全国自然人经济特征评分模型v3 简版
"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

View File

@@ -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{}, //人车关系核验ETC10093 月更 "QCXG3M7Z": &dto.QCXG3M7ZReq{}, //人车关系核验ETC10093 月更
"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": "查询原因ID1-授信审批2-贷中管理3-贷后管理4-异议处理5-担保查询6-租赁资质审查7-融资租赁审批8-借贷撮合查询9-保险审批10-资质审核11-风控审核12-企业背调", "query_reason_id": "查询原因ID1-授信审批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 锐·舒适版",

View File

@@ -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,
} }
} }

View File

@@ -109,7 +109,7 @@ func collectAPIData(ctx context.Context, params dto.DWBG8B4DReq, deps *processor
{ {
apiCode: "FLXG8B4D", apiCode: "FLXG8B4D",
params: map[string]interface{}{ params: map[string]interface{}{
"mobile_no": params.MobileNo, "mobile_no": params.MobileNo,
"authorized": "1", "authorized": "1",
}, },
}, },
@@ -117,9 +117,9 @@ func collectAPIData(ctx context.Context, params dto.DWBG8B4DReq, deps *processor
{ {
apiCode: "JRZQ8A2D", apiCode: "JRZQ8A2D",
params: map[string]interface{}{ params: map[string]interface{}{
"mobile_no": params.MobileNo, "mobile_no": params.MobileNo,
"id_card": params.IDCard, "id_card": params.IDCard,
"name": params.Name, "name": params.Name,
"authorized": "1", "authorized": "1",
}, },
}, },
@@ -127,9 +127,9 @@ func collectAPIData(ctx context.Context, params dto.DWBG8B4DReq, deps *processor
{ {
apiCode: "JRZQ1D09", apiCode: "JRZQ1D09",
params: map[string]interface{}{ params: map[string]interface{}{
"mobile_no": params.MobileNo, "mobile_no": params.MobileNo,
"id_card": params.IDCard, "id_card": params.IDCard,
"name": params.Name, "name": params.Name,
"authorized": "1", "authorized": "1",
}, },
}, },
@@ -155,9 +155,9 @@ func collectAPIData(ctx context.Context, params dto.DWBG8B4DReq, deps *processor
{ {
apiCode: "JRZQ5E9F", apiCode: "JRZQ5E9F",
params: map[string]interface{}{ params: map[string]interface{}{
"mobile_no": params.MobileNo, "mobile_no": params.MobileNo,
"id_card": params.IDCard, "id_card": params.IDCard,
"name": params.Name, "name": params.Name,
"authorized": "1", "authorized": "1",
}, },
}, },
@@ -165,8 +165,8 @@ func collectAPIData(ctx context.Context, params dto.DWBG8B4DReq, deps *processor
{ {
apiCode: "FLXGDEA9", apiCode: "FLXGDEA9",
params: map[string]interface{}{ params: map[string]interface{}{
"id_card": params.IDCard, "id_card": params.IDCard,
"name": params.Name, "name": params.Name,
"authorized": "1", "authorized": "1",
}, },
}, },
@@ -211,7 +211,7 @@ func collectAPIData(ctx context.Context, params dto.DWBG8B4DReq, deps *processor
}() }()
paramsBytes, err := json.Marshal(ac.params) paramsBytes, err := json.Marshal(ac.params)
if err != nil { if err != nil {
log.Warn("序列化API参数失败", log.Warn("序列化API参数失败",
zap.String("api_code", ac.apiCode), zap.String("api_code", ac.apiCode),
zap.Error(err), zap.Error(err),
@@ -266,7 +266,7 @@ func callProcessor(ctx context.Context, apiCode string, params []byte, deps *pro
return nil, fmt.Errorf("未找到处理器: %s", apiCode) return nil, fmt.Errorf("未找到处理器: %s", apiCode)
} }
respBytes, err := processor(ctx, params, deps) respBytes, err := processor(ctx, params, deps)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var data interface{} var data interface{}
@@ -387,7 +387,7 @@ func buildBaseInfo(apiData map[string]interface{}, params dto.DWBG8B4DReq, log *
// 从运营商三要素获取户籍所在地address字段 // 从运营商三要素获取户籍所在地address字段
if address, ok := yysyMap["address"].(string); ok && address != "" { if address, ok := yysyMap["address"].(string); ok && address != "" {
baseInfo["location"] = address baseInfo["location"] = address
} else { } else {
// 如果address不存在尝试从身份证号获取 // 如果address不存在尝试从身份证号获取
if location := getLocationFromIDCard(params.IDCard); location != "" { if location := getLocationFromIDCard(params.IDCard); location != "" {
baseInfo["location"] = location baseInfo["location"] = location
@@ -1152,21 +1152,21 @@ func buildRiskWarning(apiData map[string]interface{}, log *zap.Logger) map[strin
// 计算总风险点数量排除Counts字段和level字段 // 计算总风险点数量排除Counts字段和level字段
totalRiskCounts := 0 totalRiskCounts := 0
excludeFields := map[string]bool{ excludeFields := map[string]bool{
"totalRiskCounts": true, "totalRiskCounts": true,
"level": true, "level": true,
"sfhyfxRiskCounts": true, "sfhyfxRiskCounts": true,
"sfhyfxRiskHighCounts": true, "sfhyfxRiskHighCounts": true,
"sfhyfxRiskMiddleCounts": true, "sfhyfxRiskMiddleCounts": true,
"gazdyrhyRiskCounts": true, "gazdyrhyRiskCounts": true,
"gazdyrhyRiskHighCounts": true, "gazdyrhyRiskHighCounts": true,
"gazdyrhyRiskMiddleCounts": true, "gazdyrhyRiskMiddleCounts": true,
"yqfxRiskCounts": true, "yqfxRiskCounts": true,
"yqfxRiskHighCounts": true, "yqfxRiskHighCounts": true,
"yqfxRiskMiddleCounts": true, "yqfxRiskMiddleCounts": true,
"zlfxpgRiskCounts": true, "zlfxpgRiskCounts": true,
"zlfxpgRiskHighCounts": true, "zlfxpgRiskHighCounts": true,
"zlfxpgRiskMiddleCounts": true, "zlfxpgRiskMiddleCounts": true,
"jdpgRiskCounts": true, "jdpgRiskCounts": true,
"jdpgRiskHighCounts": true, "jdpgRiskHighCounts": true,
"jdpgRiskMiddleCounts": true, "jdpgRiskMiddleCounts": true,
"idCardRiskCounts": true, "idCardRiskCounts": true,
@@ -1901,10 +1901,10 @@ func buildOverdueRiskProduct(apiData map[string]interface{}, log *zap.Logger) ma
overdueRiskProduct["overdueLast30Days"] == "逾期" overdueRiskProduct["overdueLast30Days"] == "逾期"
if hasOverdue || hasRecentOverdue { if hasOverdue || hasRecentOverdue {
overdueRiskProduct["lyjlhyFlag"] = 1 // 高风险 overdueRiskProduct["lyjlhyFlag"] = 1 // 高风险
overdueRiskProduct["dkzhktjFlag"] = 1 // 高风险 overdueRiskProduct["dkzhktjFlag"] = 1 // 高风险
} else { } else {
overdueRiskProduct["lyjlhyFlag"] = 2 // 低风险 overdueRiskProduct["lyjlhyFlag"] = 2 // 低风险
overdueRiskProduct["dkzhktjFlag"] = 2 // 低风险 overdueRiskProduct["dkzhktjFlag"] = 2 // 低风险
} }
} }
@@ -2919,11 +2919,11 @@ func buildOrganLoanPerformances(variableValue map[string]interface{}) []interfac
// 时间周期映射 // 时间周期映射
periodMap := map[string]string{ periodMap := map[string]string{
"last7Day": "d7", "last7Day": "d7",
"last15Day": "d15", "last15Day": "d15",
"last1Month": "m1", "last1Month": "m1",
"last3Month": "m3", "last3Month": "m3",
"last6Month": "m6", "last6Month": "m6",
"last12Month": "m12", "last12Month": "m12",
} }
@@ -2983,11 +2983,11 @@ func buildCustomerLoanPerformances(variableValue map[string]interface{}) []inter
} }
periodMap := map[string]string{ periodMap := map[string]string{
"last7Day": "d7", "last7Day": "d7",
"last15Day": "d15", "last15Day": "d15",
"last1Month": "m1", "last1Month": "m1",
"last3Month": "m3", "last3Month": "m3",
"last6Month": "m6", "last6Month": "m6",
"last12Month": "m12", "last12Month": "m12",
} }
@@ -3043,11 +3043,11 @@ func buildBusinessLoanPerformances(variableValue map[string]interface{}) []inter
} }
periodMap := map[string]string{ periodMap := map[string]string{
"last7Day": "d7", "last7Day": "d7",
"last15Day": "d15", "last15Day": "d15",
"last1Month": "m1", "last1Month": "m1",
"last3Month": "m3", "last3Month": "m3",
"last6Month": "m6", "last6Month": "m6",
"last12Month": "m12", "last12Month": "m12",
} }
@@ -3102,11 +3102,11 @@ func buildTimeLoanPerformances(variableValue map[string]interface{}) []interface
} }
periodMap := map[string]string{ periodMap := map[string]string{
"last7Day": "d7", "last7Day": "d7",
"last15Day": "d15", "last15Day": "d15",
"last1Month": "m1", "last1Month": "m1",
"last3Month": "m3", "last3Month": "m3",
"last6Month": "m6", "last6Month": "m6",
"last12Month": "m12", "last12Month": "m12",
} }
@@ -3782,12 +3782,12 @@ func buildLeasingRiskAssessment(apiData map[string]interface{}, log *zap.Logger)
// 时间周期映射3Days->d3, 7Days->d7, 14Days->d14, Month->m1, 3Months->m3, 6Months->m6, 12Months->m12 // 时间周期映射3Days->d3, 7Days->d7, 14Days->d14, Month->m1, 3Months->m3, 6Months->m6, 12Months->m12
periodMap := map[string]string{ periodMap := map[string]string{
"3Days": "d3", "3Days": "d3",
"7Days": "d7", "7Days": "d7",
"14Days": "d14", "14Days": "d14",
"Month": "m1", "Month": "m1",
"3Months": "m3", "3Months": "m3",
"6Months": "m6", "6Months": "m6",
"12Months": "m12", "12Months": "m12",
} }
@@ -4011,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]
@@ -4170,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 ""
}}, }},
@@ -4505,7 +4507,7 @@ func getCreditScore(apiData map[string]interface{}) int {
return 900 return 900
} }
parsed, err := strconv.ParseFloat(v, 64) parsed, err := strconv.ParseFloat(v, 64)
if err != nil { if err != nil {
// 解析失败返回900默认信用较好 // 解析失败返回900默认信用较好
return 900 return 900
} }

View File

@@ -1,219 +0,0 @@
# 谛听多维报告DWBG8B4D风险标识判断标准
本文档汇总 `dwbg8b4d_processor.go` 中所有风险标识(*Flag / riskFlag的判定规则与数据来源便于查阅和评审。
---
## 一、elementVerificationDetail要素核验产品内风险标识
### 1. sjsysFlag手机三要素简版风险标识
| 取值 | 含义 | 判断条件 |
|------|--------|----------|
| 0 | 未查得 | 无 YYSYH6D2 数据或无法解析 |
| 1 | 高风险 | YYSYH6D2.result ≠ "0"(身份证+手机号+姓名 不一致) |
| 2 | 低风险 | YYSYH6D2.result == "0"(三要素一致) |
**数据源**YYSYH6D2手机三要素简版
---
### 2. sfzeysFlag身份证二要素风险标识
| 取值 | 含义 | 判断条件 |
|------|--------|----------|
| 0 | 未查得 | 无 IVYZ9K7F 数据或 status/desc/result 无法解析 |
| 1 | 高风险 | 核验结果不为「一致」 |
| 2 | 低风险 | 核验结果为「一致」 |
**数据源**IVYZ9K7F身份证二要素。status 来自 data.status、desc 或 result0=一致非0=不一致)。
---
### 3. onlineRiskFlag手机在网时长风险标识
| 取值 | 含义 | 判断条件 |
|------|--------|----------|
| 0 | 未查得 | 无 YYSY8B1C 数据或 inTime 为空 |
| 1 | 高风险 | inTime == "0" 或 "3"(在网时长极短) |
| 2 | 低风险 | inTime 为其他非空值 |
**数据源**YYSY8B1C手机在网时长
---
### 4. phoneVailRiskFlag手机信息验证风险标识
| 取值 | 含义 | 判断条件 |
|------|--------|----------|
| 0 | 未查得 | 无 YYSYE7V5 数据或 status 无法解析 |
| 1 | 高风险 | status == 1不在网 |
| 2 | 低风险 | status == 0在网/实号) |
**数据源**YYSYE7V5手机在网状态等
---
### 5. belongRiskFlag身份证号与手机号归属地风险标识
| 取值 | 含义 | 判断条件 |
|------|--------|----------|
| 0 | 未查得 | 无 YYSY9E4A 或 YYSYH6D2或户籍/归属地任一方为空 |
| 1 | 高风险 | 户籍所在地与号码归属地**不一致**(经智能比较) |
| 2 | 低风险 | 户籍所在地与号码归属地**一致** |
**数据源**YYSY9E4A号码归属地、YYSYH6D2address 作户籍)。使用 `compareLocation` 做省/市归一化后比较。
---
### 6. highRiskFlag公安重点人员核验风险标识
| 取值 | 含义 | 判断条件 |
|------|--------|----------|
| 0 | 无风险 | keyPersonCheckList 五项fontFlag、jingJiFontFlag、fangAiFlag、zhongDianFlag、sheJiaoTongFlag**均为 0** |
| 1 | 高风险 | **任一项** fontFlag / jingJiFontFlag / fangAiFlag / zhongDianFlag 为 1涉刑/经侦/妨害/重点/涉交通以外) |
| 2 | 低风险 | **仅** sheJiaoTongFlag 为 1仅涉交通命中其余为 0 |
**数据源**FLXGDEA9 的 level 解析后填充 keyPersonCheckListA→fontFlag, B→jingJiFontFlag, C→fangAiFlag, D→zhongDianFlag, E→sheJiaoTongFlag。**不看是否有数据**,仅根据五项当前值判定。
---
## 二、overdueRiskProduct逾期风险产品内风险标识
### 7. lyjlhyFlag履约/借贷逾期类风险标识)
| 取值 | 含义 | 判断条件 |
|------|--------|----------|
| 0 | 未查得 | 无 JRZQ5E9F 数据(默认) |
| 1 | 高风险 | 当前存在未结清逾期 **或** 近 7/14/30 天有逾期hasUnsettledOverdue=="逾期" 或 overdueLast7Days/14Days/30Days 任一为「逾期」) |
| 2 | 低风险 | 有 JRZQ5E9F 数据且无上述逾期情况 |
**数据源**JRZQ5E9F借选指数评估字段xyp_cpl0044、xyp_cpl0029、xyp_cpl0030、xyp_cpl0031 等。
---
### 8. dkzhktjFlag贷款综合情况风险标识
**lyjlhyFlag** 使用同一套逻辑,同时赋值:有逾期或近期逾期则为 1否则为 2。
**数据源**JRZQ5E9F。
---
### 9. tsmdyzFlag特殊名单验证风险标识
| 取值 | 含义 | 判断条件 |
|------|--------|----------|
| 0 | 未查得 | 无 JRZQ8A2D 数据(默认) |
| 1 | 高风险 | specialListVerification 列表**非空**(身份证或手机号任一侧命中:法院失信人/被执行人、银行/非银中风险/一般风险/高风险等任一) |
| 2 | 低风险 | 有 JRZQ8A2D 数据且 specialListVerification 为空 |
**数据源**JRZQ8A2D特殊名单验证 B。命中规则id/cell 下对应字段值为 "0" 表示命中(如 court_bad、court_executed、bank_lost 等)。
---
## 三、multCourtInfo司法风险核验产品内风险标识
### 10. legalCasesFlag涉案公告案件风险标识
| 取值 | 含义 | 判断条件 |
|------|--------|----------|
| 0 | 无 | 无 FLXG7E8F 或 lawsuitStat 下无民事/刑事/行政/保全/破产案件 |
| 1 | 高风险 | legalCases 列表**非空**(民事、刑事、行政、保全、破产任一类 cases 非空) |
**数据源**FLXG7E8F个人司法涉诉查询→ judicial_data.lawsuitStat → civil/criminal/administrative/preservation/bankrupt.cases。
---
### 11. executionCasesFlag执行案件风险标识
| 取值 | 含义 | 判断条件 |
|------|--------|----------|
| 0 | 无 | 无 FLXG7E8F 或 implement.cases 为空 |
| 1 | 高风险 | executionCases 列表**非空** |
**数据源**FLXG7E8F → judicial_data.lawsuitStat.implement.cases。
---
### 12. disinCasesFlag失信案件风险标识
| 取值 | 含义 | 判断条件 |
|------|--------|----------|
| 0 | 无 | 无 FLXG7E8F 或 breachCaseList 为空 |
| 1 | 高风险 | disinCases 列表**非空** |
**数据源**FLXG7E8F → judicial_data.breachCaseList。
---
### 13. limitCasesFlag限高案件风险标识
| 取值 | 含义 | 判断条件 |
|------|--------|----------|
| 0 | 无 | 无 FLXG7E8F 或 consumptionRestrictionList 为空 |
| 1 | 高风险 | limitCases 列表**非空** |
**数据源**FLXG7E8F → judicial_data.consumptionRestrictionList。
---
## 四、loanEvaluationVerificationDetail借贷评估产品内风险标识
### 14. riskFlag借贷评估风险标识
| 取值 | 含义 | 判断条件 |
|------|--------|----------|
| 0 | 未查得 | 无 JRZQ6F2A 或 risk_screen_v2.variables[0].variableValue 无法解析 |
| 1 | 高风险 | checkLoanRisk 为 true**任一时间段**d7/d15/m1/m3银行或非银申请次数 **≥ 10**als_{period}_id_bank_allnum / als_{period}_id_nbank_allnum |
| 2 | 低风险 | 有数据且 checkLoanRisk 为 false |
**数据源**JRZQ6F2A借贷意向验证 A→ risk_screen_v2.variables[0].variableValue。
---
## 五、leasingRiskAssessment租赁风险评估产品内风险标识
### 15. riskFlag租赁风险评估标识
| 取值 | 含义 | 判断条件 |
|------|--------|----------|
| 0 | 无风险 | 近 12 月m12**总次数为 0**(身份证维度 alc_m12_id_allnum 与手机号维度 alc_m12_cell_allnum 取**较大值**后为 0 |
| 1 | 高风险 | 近 12 月总次数 **> 10** |
| 2 | 低风险 | 近 12 月总次数 **110**(含 10 |
**数据源**JRZQ1D093C 租赁申请意向)。**仅用近 12 月Last12 / m12**totalCount = max(idCount, cellCount)。
---
## 六、riskWarning规则风险提示内各项命中规则摘要
以下为「命中即置 1」的规则未命中或未查得均为 0。不改变上述各产品内 *Flag 的取值含义。
| 类别 | 字段示例 | 命中条件(简要) |
|----------------|----------|------------------|
| 要素核查 | idCardTwoElementMismatch | IVYZ9K7F 二要素结果非「一致」 |
| | phoneThreeElementMismatch | YYSYH6D2.result ≠ "0" |
| 运营商/在网 | shortPhoneDuration | YYSY8B1C.inTime == "0" |
| | shortPhoneDurationSlight | YYSY8B1C.inTime == "3" |
| | noPhoneDuration | YYSYE7V5.status==1 且 desc 含「风险」 |
| 归属地 | idCardPhoneProvinceMismatch | 户籍与号码归属地智能比较不一致 |
| 公安重点人员 | hasCriminalRecord / isEconomyFront / isDisrupSocial / isKeyPerson / isTrafficRelated | FLXGDEA9.level 解析后 A/B/C/D/E 前缀 |
| 涉赌涉诈 | isAntiFraudInfo | FLXG8B4D 中 moneyLaundering/deceiver/gamblerPlayer/gamblerBanker 任一项非空且非"0" |
| 逾期/名单 | hitHighRiskBankLastTwoYears / hitHighRiskNonBankLastTwoYears / hitCurrentOverdue | JRZQ8A2D.id 下 bank_lost/nbank_lost/bank_overdue/nbank_overdue 等字段值为 "0"(命中) |
| 司法 | hitCivilCase / hitCriminalRisk / hitExecutionCase / hitAdministrativeCase / hitPreservationReview / hitBankruptcyAndLiquidation | FLXG7E8F 对应案件类型 **cases 数组非空**(不依 count |
| 借贷意向 | frequentApplicationRecent | 近 d7/d15/m1 银行或非银申请次数 **≥ 10**JRZQ6F2A |
| | frequentBankApplications / moreFrequentBankApplications | 近 m6+m12 银行申请总次数 **≥ 20** / **≥ 15** |
| | frequentNonBankApplications / moreFrequentNonBankApplications | 近 m6+m12 非银申请总次数 **≥ 20** / **≥ 15** |
| 租赁申请 | veryFrequentRentalApplications / frequentRentalApplications | JRZQ1D09 近 m3+m6+m12每期取 id/cell 较大值再累加)总次数 **≥ 20** / **≥ 15** |
| 偿债压力 | highDebtPressure | JRZQ5E9F 当前逾期金额或当前逾期机构数非空且非 "0"/"1" |
---
## 七、风险取值约定(通用)
- **0**:未查得 / 无风险 / 无命中(依字段语义)
- **1**:高风险 / 命中
- **2**:低风险(仅部分 *Flag 使用,如 sjsysFlag、sfzeysFlag、onlineRiskFlag、phoneVailRiskFlag、belongRiskFlag、highRiskFlag、lyjlhyFlag、dkzhktjFlag、tsmdyzFlag、loanEvaluation riskFlag、leasingRiskAssessment riskFlag
文档版本:根据当前 `dwbg8b4d_processor.go` 逻辑整理,若有代码变更请同步更新本文档。

View File

@@ -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, &paramsDto); 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
}

View File

@@ -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, &paramsDto); 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
}

View File

@@ -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, &paramsDto); 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
}

View File

@@ -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, &paramsDto); 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
}

View File

@@ -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)

View File

@@ -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, &paramsDto); 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
}

View File

@@ -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}
}()
}
// 企业全量信息核验V2QYGLUY3S
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)
// 企业司法涉诉V2QYGL5S1I
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

View File

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

View File

@@ -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, &paramsDto); 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
}

View File

@@ -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, &paramsDto); 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
}

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -20,7 +20,7 @@ import (
) )
const ( const (
UsersTable = "users" UsersTable = "users"
UserCacheTTL = 30 * 60 // 30分钟 UserCacheTTL = 30 * 60 // 30分钟
) )

View File

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

View File

@@ -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

View File

@@ -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
} }

View 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,
})
}

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

View File

@@ -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

File diff suppressed because it is too large Load Diff