Compare commits
2 Commits
c10fb27b93
...
11fe48809e
| Author | SHA1 | Date | |
|---|---|---|---|
| 11fe48809e | |||
| 785818f73d |
BIN
cmd/api/__debug_bin.exe320353000
Normal file
BIN
cmd/api/__debug_bin.exe320353000
Normal file
Binary file not shown.
BIN
cmd/worker/__debug_bin.exe1068760645
Normal file
BIN
cmd/worker/__debug_bin.exe1068760645
Normal file
Binary file not shown.
BIN
cmd/worker/__debug_bin.exe1835124629
Normal file
BIN
cmd/worker/__debug_bin.exe1835124629
Normal file
Binary file not shown.
BIN
cmd/worker/__debug_bin.exe4056734935
Normal file
BIN
cmd/worker/__debug_bin.exe4056734935
Normal file
Binary file not shown.
BIN
cmd/worker/__debug_bin.exe438186156
Normal file
BIN
cmd/worker/__debug_bin.exe438186156
Normal file
Binary file not shown.
@@ -58,7 +58,8 @@ services:
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
restart:
|
||||
unless-stopped
|
||||
|
||||
# Jaeger 链路追踪
|
||||
jaeger:
|
||||
|
||||
@@ -46,7 +46,7 @@ type ApiApplicationService interface {
|
||||
|
||||
// 管理端API调用记录
|
||||
GetAdminApiCalls(ctx context.Context, filters map[string]interface{}, options shared_interfaces.ListOptions) (*dto.ApiCallListResponse, error)
|
||||
|
||||
|
||||
// 导出功能
|
||||
ExportAdminApiCalls(ctx context.Context, filters map[string]interface{}, format string) ([]byte, error)
|
||||
|
||||
@@ -442,7 +442,7 @@ func (s *ApiApplicationServiceImpl) asyncRecordFailure(ctx context.Context, apiC
|
||||
zap.String("transaction_id", apiCall.TransactionId),
|
||||
zap.String("error_type", errorType),
|
||||
zap.String("error_msg", errorMsg))
|
||||
|
||||
|
||||
// 可选:如果需要统计失败请求,可以在这里添加计数器
|
||||
// s.failureCounter.Inc()
|
||||
}
|
||||
@@ -774,7 +774,7 @@ func (s *ApiApplicationServiceImpl) ExportAdminApiCalls(ctx context.Context, fil
|
||||
const batchSize = 1000 // 每批处理1000条记录
|
||||
var allCalls []*entities.ApiCall
|
||||
var productNameMap map[string]string
|
||||
|
||||
|
||||
// 分批获取数据
|
||||
page := 1
|
||||
for {
|
||||
@@ -819,7 +819,7 @@ func (s *ApiApplicationServiceImpl) ExportAdminApiCalls(ctx context.Context, fil
|
||||
// 准备导出数据
|
||||
headers := []string{"企业名称", "产品名称", "交易ID", "客户端IP", "状态", "开始时间", "结束时间"}
|
||||
columnWidths := []float64{30, 20, 40, 15, 10, 20, 20}
|
||||
|
||||
|
||||
data := make([][]interface{}, len(allCalls))
|
||||
for i, call := range allCalls {
|
||||
// 从映射中获取企业名称
|
||||
@@ -1206,8 +1206,8 @@ func (s *ApiApplicationServiceImpl) GetUserBalanceAlertSettings(ctx context.Cont
|
||||
// 获取API用户信息
|
||||
apiUser, err := s.apiUserService.LoadApiUserByUserId(ctx, userID)
|
||||
if err != nil {
|
||||
s.logger.Error("获取API用户信息失败",
|
||||
zap.String("user_id", userID),
|
||||
s.logger.Error("获取API用户信息失败",
|
||||
zap.String("user_id", userID),
|
||||
zap.Error(err))
|
||||
return nil, fmt.Errorf("获取API用户信息失败: %w", err)
|
||||
}
|
||||
@@ -1218,9 +1218,9 @@ func (s *ApiApplicationServiceImpl) GetUserBalanceAlertSettings(ctx context.Cont
|
||||
|
||||
// 返回预警设置
|
||||
settings := map[string]interface{}{
|
||||
"enabled": apiUser.BalanceAlertEnabled,
|
||||
"threshold": apiUser.BalanceAlertThreshold,
|
||||
"alert_phone": apiUser.AlertPhone,
|
||||
"enabled": apiUser.BalanceAlertEnabled,
|
||||
"threshold": apiUser.BalanceAlertThreshold,
|
||||
"alert_phone": apiUser.AlertPhone,
|
||||
}
|
||||
|
||||
return settings, nil
|
||||
@@ -1231,8 +1231,8 @@ func (s *ApiApplicationServiceImpl) UpdateUserBalanceAlertSettings(ctx context.C
|
||||
// 获取API用户信息
|
||||
apiUser, err := s.apiUserService.LoadApiUserByUserId(ctx, userID)
|
||||
if err != nil {
|
||||
s.logger.Error("获取API用户信息失败",
|
||||
zap.String("user_id", userID),
|
||||
s.logger.Error("获取API用户信息失败",
|
||||
zap.String("user_id", userID),
|
||||
zap.Error(err))
|
||||
return fmt.Errorf("获取API用户信息失败: %w", err)
|
||||
}
|
||||
@@ -1243,16 +1243,16 @@ func (s *ApiApplicationServiceImpl) UpdateUserBalanceAlertSettings(ctx context.C
|
||||
|
||||
// 更新预警设置
|
||||
if err := apiUser.UpdateBalanceAlertSettings(enabled, threshold, alertPhone); err != nil {
|
||||
s.logger.Error("更新预警设置失败",
|
||||
zap.String("user_id", userID),
|
||||
s.logger.Error("更新预警设置失败",
|
||||
zap.String("user_id", userID),
|
||||
zap.Error(err))
|
||||
return fmt.Errorf("更新预警设置失败: %w", err)
|
||||
}
|
||||
|
||||
// 保存到数据库
|
||||
if err := s.apiUserService.SaveApiUser(ctx, apiUser); err != nil {
|
||||
s.logger.Error("保存API用户信息失败",
|
||||
zap.String("user_id", userID),
|
||||
s.logger.Error("保存API用户信息失败",
|
||||
zap.String("user_id", userID),
|
||||
zap.Error(err))
|
||||
return fmt.Errorf("保存API用户信息失败: %w", err)
|
||||
}
|
||||
@@ -1271,8 +1271,8 @@ func (s *ApiApplicationServiceImpl) TestBalanceAlertSms(ctx context.Context, use
|
||||
// 获取用户信息以获取企业名称
|
||||
user, err := s.userRepo.GetByID(ctx, userID)
|
||||
if err != nil {
|
||||
s.logger.Error("获取用户信息失败",
|
||||
zap.String("user_id", userID),
|
||||
s.logger.Error("获取用户信息失败",
|
||||
zap.String("user_id", userID),
|
||||
zap.Error(err))
|
||||
return fmt.Errorf("获取用户信息失败: %w", err)
|
||||
}
|
||||
@@ -1285,8 +1285,8 @@ func (s *ApiApplicationServiceImpl) TestBalanceAlertSms(ctx context.Context, use
|
||||
|
||||
// 调用短信服务发送测试短信
|
||||
if err := s.balanceAlertService.CheckAndSendAlert(ctx, userID, decimal.NewFromFloat(balance)); err != nil {
|
||||
s.logger.Error("发送测试预警短信失败",
|
||||
zap.String("user_id", userID),
|
||||
s.logger.Error("发送测试预警短信失败",
|
||||
zap.String("user_id", userID),
|
||||
zap.String("phone", phone),
|
||||
zap.Float64("balance", balance),
|
||||
zap.String("alert_type", alertType),
|
||||
|
||||
@@ -965,7 +965,7 @@ func (s *ProductApplicationServiceImpl) getDTOMap() map[string]interface{} {
|
||||
"JRZQ0A03": &dto.JRZQ0A03Req{},
|
||||
"JRZQ4AA8": &dto.JRZQ4AA8Req{},
|
||||
"JRZQ8203": &dto.JRZQ8203Req{},
|
||||
"JRZQDBCE": &dto.JRZQDBCEReq{},
|
||||
"JRZQDBCE": &dto.JRZQDCBEReq{},
|
||||
"QYGL2ACD": &dto.QYGL2ACDReq{},
|
||||
"QYGL6F2D": &dto.QYGL6F2DReq{},
|
||||
"QYGL45BD": &dto.QYGL45BDReq{},
|
||||
|
||||
@@ -94,7 +94,7 @@ type JRZQ8203Req struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
}
|
||||
type JRZQDBCEReq struct {
|
||||
type JRZQDCBEReq struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
BankCard string `json:"bank_card" validate:"required,validBankCard"`
|
||||
@@ -300,8 +300,8 @@ type IVYZ3A7FReq struct {
|
||||
}
|
||||
|
||||
type IVYZ9K2LReq struct {
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
PhotoData string `json:"photo_data" validate:"required,validBase64Image"`
|
||||
}
|
||||
|
||||
@@ -311,6 +311,12 @@ type IVYZ9D2EReq struct {
|
||||
UseScenario string `json:"use_scenario" validate:"required,oneof=1 2 3 4 99"`
|
||||
}
|
||||
|
||||
type IVYZ2C1PReq struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||
}
|
||||
|
||||
// DWBG7F3AReq 行为数据查询请求参数
|
||||
type DWBG7F3AReq struct {
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
@@ -354,6 +360,15 @@ type YYSY4F2EReq struct {
|
||||
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||
}
|
||||
|
||||
type YYSY9F1BReq struct {
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
Phone string `json:"phone" validate:"required,min=11,max=11,validMobileNo"`
|
||||
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||
}
|
||||
type YYSY6F2BReq struct {
|
||||
Phone string `json:"phone" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
type YYSY8B1CReq struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
@@ -394,6 +409,12 @@ type JRZQ3C7BReq struct {
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||
}
|
||||
type JRZQ3C9RReq struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||
}
|
||||
|
||||
type JRZQ8A2DReq struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
@@ -404,9 +425,32 @@ type JRZQ8A2DReq struct {
|
||||
|
||||
// YYSY8F3AReq 行为数据查询请求参数
|
||||
type YYSY8F3AReq struct {
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
IDCard string `json:"cardNo" validate:"required,validIDCard"`
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
CardId string `json:"cardId" validate:"required,validIDCard"`
|
||||
}
|
||||
|
||||
// 銀行卡黑名單
|
||||
type JRZQ0B6YReq struct {
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
BankCard string `json:"bank_card" validate:"omitempty,validBankCard"`
|
||||
}
|
||||
|
||||
// 银行卡鉴权
|
||||
type JRZQ9A1WReq struct {
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
BankCard string `json:"bank_card" validate:"omitempty,validBankCard"`
|
||||
}
|
||||
|
||||
// 企业管理董监高司法综合信息核验
|
||||
type QYGL6S1BReq struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||
}
|
||||
|
||||
type JRZQ5E9FReq struct {
|
||||
@@ -460,20 +504,20 @@ type QCXG9P1CReq struct {
|
||||
}
|
||||
|
||||
type QCXG8A3DReq struct {
|
||||
PlateNo string `json:"plate_no" validate:"required"`
|
||||
PlateType string `json:"plate_type" validate:"omitempty,oneof=01 02"`
|
||||
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||
PlateNo string `json:"plate_no" validate:"required"`
|
||||
PlateType string `json:"plate_type" validate:"omitempty,oneof=01 02"`
|
||||
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||
}
|
||||
|
||||
type QCXG6B4EReq struct {
|
||||
VINCode string `json:"vin_code" validate:"required"`
|
||||
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||
VINCode string `json:"vin_code" validate:"required"`
|
||||
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||
}
|
||||
|
||||
type QYGL2B5CReq struct {
|
||||
EntName string `json:"ent_name" validate:"omitempty,min=1,validEnterpriseName"`
|
||||
EntCode string `json:"ent_code" validate:"omitempty,validUSCI"`
|
||||
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||
EntName string `json:"ent_name" validate:"omitempty,min=1,validEnterpriseName"`
|
||||
EntCode string `json:"ent_code" validate:"omitempty,validUSCI"`
|
||||
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||
}
|
||||
|
||||
type JRZQ2F8AReq struct {
|
||||
|
||||
@@ -125,7 +125,9 @@ func registerAllProcessors(combService *comb.CombService) {
|
||||
"JRZQ0L85": jrzq.ProcessJRZQ0L85Request,
|
||||
"JRZQ2F8A": jrzq.ProcessJRZQ2F8ARequest,
|
||||
"JRZQ1E7B": jrzq.ProcessJRZQ1E7BRequest,
|
||||
|
||||
"JRZQ3C9R": jrzq.ProcessJRZQ3C9RRequest,
|
||||
"JRZQ0B6Y": jrzq.ProcessJRZQ0B6YRequest,
|
||||
"JRZQ9A1W": jrzq.ProcessJRZQ9A1WRequest,
|
||||
// QYGL系列处理器
|
||||
"QYGL8261": qygl.ProcessQYGL8261Request,
|
||||
"QYGL2ACD": qygl.ProcessQYGL2ACDRequest,
|
||||
@@ -144,6 +146,7 @@ func registerAllProcessors(combService *comb.CombService) {
|
||||
"COMENT01": qygl.ProcessCOMENT01Request, // 企业风险报告
|
||||
"QYGL5F6A": qygl.ProcessQYGL5F6ARequest, // 企业相关查询
|
||||
"QYGL2B5C": qygl.ProcessQYGL2B5CRequest, // 企业联系人实际经营地址
|
||||
"QYGL6S1B": qygl.ProcessQYGL6S1BRequest, //董监高司法综合信息核验
|
||||
|
||||
// YYSY系列处理器
|
||||
"YYSYD50F": yysy.ProcessYYSYD50FRequest,
|
||||
@@ -162,6 +165,8 @@ func registerAllProcessors(combService *comb.CombService) {
|
||||
"YYSY8C2D": yysy.ProcessYYSY8C2DRequest,
|
||||
"YYSY7D3E": yysy.ProcessYYSY7D3ERequest,
|
||||
"YYSY9E4A": yysy.ProcessYYSY9E4ARequest,
|
||||
"YYSY9F1B": yysy.ProcessYYSY9F1BYequest,
|
||||
"YYSY6F2B": yysy.ProcessYYSY6F2BRequest,
|
||||
|
||||
// IVYZ系列处理器
|
||||
"IVYZ0B03": ivyz.ProcessIVYZ0B03Request,
|
||||
@@ -186,6 +191,7 @@ func registerAllProcessors(combService *comb.CombService) {
|
||||
"IVYZ6G7H": ivyz.ProcessIVYZ6G7HRequest,
|
||||
"IVYZ8I9J": ivyz.ProcessIVYZ8I9JRequest,
|
||||
"IVYZ9K2L": ivyz.ProcessIVYZ9K2LRequest,
|
||||
"IVYZ2C1P": ivyz.ProcessIVYZ2C1PRequest,
|
||||
|
||||
// COMB系列处理器 - 只注册有自定义逻辑的组合包
|
||||
"COMB86PM": comb.ProcessCOMB86PMRequest, // 有自定义逻辑:重命名ApiCode
|
||||
|
||||
@@ -96,7 +96,7 @@ func (s *FormConfigServiceImpl) getDTOStruct(ctx context.Context, apiCode string
|
||||
"JRZQ0A03": &dto.JRZQ0A03Req{},
|
||||
"JRZQ4AA8": &dto.JRZQ4AA8Req{},
|
||||
"JRZQ8203": &dto.JRZQ8203Req{},
|
||||
"JRZQDBCE": &dto.JRZQDBCEReq{},
|
||||
"JRZQDCBE": &dto.JRZQDCBEReq{},
|
||||
"QYGL2ACD": &dto.QYGL2ACDReq{},
|
||||
"QYGL6F2D": &dto.QYGL6F2DReq{},
|
||||
"QYGL45BD": &dto.QYGL45BDReq{},
|
||||
@@ -178,6 +178,13 @@ func (s *FormConfigServiceImpl) getDTOStruct(ctx context.Context, apiCode string
|
||||
"QYGL2B5C": &dto.QYGL2B5CReq{},
|
||||
"JRZQ2F8A": &dto.JRZQ2F8AReq{},
|
||||
"JRZQ1E7B": &dto.JRZQ1E7BReq{},
|
||||
"JRZQ3C9R": &dto.JRZQ3C9RReq{},
|
||||
"IVYZ2C1P": &dto.IVYZ2C1PReq{},
|
||||
"YYSY9F1B": &dto.YYSY9F1BReq{},
|
||||
"YYSY6F2B": &dto.YYSY6F2BReq{},
|
||||
"QYGL6S1B": &dto.QYGL6S1BReq{},
|
||||
"JRZQ0B6Y": &dto.JRZQ0B6YReq{},
|
||||
"JRZQ9A1W": &dto.JRZQ9A1WReq{},
|
||||
}
|
||||
|
||||
// 优先返回已配置的DTO
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
// ProcessDWBG8B4DRequest DWBG8B4D API处理方法 - 谛听多维报告
|
||||
func ProcessDWBG8B4DRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.DWBG8B4DReq
|
||||
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
@@ -33,8 +33,8 @@ func ProcessFLXG0V3Bequest(ctx context.Context, params []byte, deps *processors.
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"id_card": encryptedIDCard,
|
||||
"name": encryptedName,
|
||||
"id_card": encryptedIDCard,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -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/zhicha"
|
||||
)
|
||||
|
||||
// ProcessIVYZ2C1PRequest IVYZ2C1P API处理方法 - 风控黑名单
|
||||
func ProcessIVYZ2C1PRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.IVYZ2C1PReq
|
||||
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)
|
||||
}
|
||||
|
||||
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"idCard": encryptedIDCard,
|
||||
"authorized": paramsDto.Authorized,
|
||||
}
|
||||
|
||||
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI037", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, zhicha.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 将响应数据转换为JSON字节
|
||||
respBytes, err := json.Marshal(respData)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
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/westdex"
|
||||
)
|
||||
|
||||
// ProcessJRZQ0B6YRequest JRZQ0B6Y 银行卡黑名单查询V1API处理方法
|
||||
func ProcessJRZQ0B6YRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.JRZQ0B6YReq
|
||||
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)
|
||||
}
|
||||
|
||||
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedMobileNo, err := deps.WestDexService.Encrypt(paramsDto.MobileNo)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedBankCard, err := deps.WestDexService.Encrypt(paramsDto.BankCard)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"cardId": encryptedBankCard,
|
||||
"cardNo": encryptedIDCard,
|
||||
"phone": encryptedMobileNo,
|
||||
},
|
||||
}
|
||||
|
||||
respBytes, err := deps.YushanService.CallAPI(ctx, "FIN019", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, westdex.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -37,9 +37,9 @@ func ProcessJRZQ3C7BRequest(ctx context.Context, params []byte, deps *processors
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"idCard": encryptedIDCard,
|
||||
"phone": encryptedMobileNo,
|
||||
"name": encryptedName,
|
||||
"idCard": encryptedIDCard,
|
||||
"phone": encryptedMobileNo,
|
||||
"authorized": paramsDto.Authorized,
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
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/zhicha"
|
||||
)
|
||||
|
||||
// ProcessJRZQ3c9RRequest JRZQ3c9R API处理方法 - 支付行为指数
|
||||
func ProcessJRZQ3C9RRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.JRZQ3C9RReq
|
||||
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)
|
||||
}
|
||||
|
||||
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedMobileNo, err := deps.ZhichaService.Encrypt(paramsDto.MobileNo)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"idCard": encryptedIDCard,
|
||||
"phone": encryptedMobileNo,
|
||||
"authorized": paramsDto.Authorized,
|
||||
}
|
||||
|
||||
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI036", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, zhicha.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 将响应数据转换为JSON字节
|
||||
respBytes, err := json.Marshal(respData)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -37,9 +37,9 @@ func ProcessJRZQ4B6CRequest(ctx context.Context, params []byte, deps *processors
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"idCard": encryptedIDCard,
|
||||
"phone": encryptedMobileNo,
|
||||
"name": encryptedName,
|
||||
"idCard": encryptedIDCard,
|
||||
"phone": encryptedMobileNo,
|
||||
"authorized": paramsDto.Authorized,
|
||||
}
|
||||
|
||||
|
||||
@@ -54,4 +54,4 @@ func ProcessJRZQ8203Request(ctx context.Context, params []byte, deps *processors
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
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/westdex"
|
||||
)
|
||||
|
||||
// ProcessJRZQ9A1WRequest JRZQ9A1W 银行卡鉴权V1API处理方法
|
||||
func ProcessJRZQ9A1WRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.JRZQ9A1WReq
|
||||
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)
|
||||
}
|
||||
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedMobileNo, err := deps.WestDexService.Encrypt(paramsDto.MobileNo)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedBankCard, err := deps.WestDexService.Encrypt(paramsDto.BankCard)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"cardId": encryptedBankCard,
|
||||
"cardNo": encryptedIDCard,
|
||||
"phone": encryptedMobileNo,
|
||||
},
|
||||
}
|
||||
|
||||
respBytes, err := deps.YushanService.CallAPI(ctx, "PCB145", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, westdex.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -24,8 +24,8 @@ func ProcessJRZQ9E2ARequest(ctx context.Context, params []byte, deps *processors
|
||||
// 构建请求数据,将项目规范的字段名转换为 XingweiService 需要的字段名
|
||||
reqData := map[string]interface{}{
|
||||
"phoneNumber": paramsDto.MobileNo,
|
||||
"idCardNum": paramsDto.IDCard,
|
||||
"name": paramsDto.Name,
|
||||
"idCardNum": paramsDto.IDCard,
|
||||
"name": paramsDto.Name,
|
||||
"authAuthorizeFileCode": paramsDto.AuthAuthorizeFileCode,
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
// ProcessJRZQDCBERequest JRZQDCBE API处理方法
|
||||
func ProcessJRZQDCBERequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.JRZQDBCEReq
|
||||
var paramsDto dto.JRZQDCBEReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
@@ -43,10 +43,10 @@ func ProcessJRZQDCBERequest(ctx context.Context, params []byte, deps *processors
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"idcard": encryptedIDCard,
|
||||
"mobile": encryptedMobileNo,
|
||||
"acc_no": encryptedBankCard,
|
||||
"name": encryptedName,
|
||||
"idcard": encryptedIDCard,
|
||||
"mobile": encryptedMobileNo,
|
||||
"acc_no": encryptedBankCard,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -60,4 +60,4 @@ func ProcessJRZQDCBERequest(ctx context.Context, params []byte, deps *processors
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package qygl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
)
|
||||
|
||||
// ProcessQYGL6S1BRequest QYGL6S1B API处理方法 - 董监高司法综合信息核验
|
||||
|
||||
func ProcessQYGL6S1BRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.QYGL6S1BReq
|
||||
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)
|
||||
}
|
||||
|
||||
encryptedPhone, err := deps.ZhichaService.Encrypt(paramsDto.IDCard)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
// 构建API调用参数
|
||||
apiParams := map[string]string{
|
||||
"idCard": encryptedPhone,
|
||||
"authorized": paramsDto.Authorized,
|
||||
}
|
||||
|
||||
// 调用天眼查API -
|
||||
response, err := deps.TianYanChaService.CallAPI(ctx, "ZCI043", apiParams)
|
||||
if err != nil {
|
||||
return nil, convertTianYanChaError(err)
|
||||
}
|
||||
|
||||
// 检查天眼查API调用是否成功
|
||||
if !response.Success {
|
||||
return nil, errors.Join(processors.ErrDatasource, errors.New(response.Message))
|
||||
}
|
||||
|
||||
// 返回天眼查响应数据
|
||||
respBytes, err := json.Marshal(response.Data)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
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/zhicha"
|
||||
)
|
||||
|
||||
// ProcessYYSY6F2BRequestYYSY 6F2B API处理方法 - 手机消费区间验证
|
||||
func ProcessYYSY6F2BRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.YYSY6F2BReq
|
||||
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)
|
||||
}
|
||||
|
||||
encryptedPhone, err := deps.ZhichaService.Encrypt(paramsDto.Phone)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"phone": encryptedPhone,
|
||||
}
|
||||
|
||||
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI041", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, zhicha.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 将响应数据转换为JSON字节
|
||||
respBytes, err := json.Marshal(respData)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
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/zhicha"
|
||||
)
|
||||
|
||||
// ProcessYYSY9F1BYequest YYSY9F1B API处理方法 - 手机二要素验证
|
||||
|
||||
func ProcessYYSY9F1BYequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.YYSY9F1BReq
|
||||
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)
|
||||
}
|
||||
|
||||
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedPhone, err := deps.ZhichaService.Encrypt(paramsDto.Phone)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"phone": encryptedPhone,
|
||||
"authorized": paramsDto.Authorized,
|
||||
}
|
||||
|
||||
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI040", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, zhicha.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 将响应数据转换为JSON字节
|
||||
respBytes, err := json.Marshal(respData)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -21,13 +21,13 @@ var (
|
||||
|
||||
// APIEndpoints 天眼查 API 端点映射
|
||||
var APIEndpoints = map[string]string{
|
||||
"VerifyThreeElements": "/open/ic/verify/2.0", // 企业三要素验证
|
||||
"InvestHistory": "/open/hi/invest/2.0", // 对外投资历史
|
||||
"FinancingHistory": "/open/cd/findHistoryRongzi/2.0", // 融资历史
|
||||
"PunishmentInfo": "/open/mr/punishmentInfo/3.0", // 行政处罚
|
||||
"AbnormalInfo": "/open/mr/abnormal/2.0", // 经营异常
|
||||
"OwnTax": "/open/mr/ownTax", // 欠税公告
|
||||
"TaxContravention": "/open/mr/taxContravention", // 税收违法
|
||||
"VerifyThreeElements": "/open/ic/verify/2.0", // 企业三要素验证
|
||||
"InvestHistory": "/open/hi/invest/2.0", // 对外投资历史
|
||||
"FinancingHistory": "/open/cd/findHistoryRongzi/2.0", // 融资历史
|
||||
"PunishmentInfo": "/open/mr/punishmentInfo/3.0", // 行政处罚
|
||||
"AbnormalInfo": "/open/mr/abnormal/2.0", // 经营异常
|
||||
"OwnTax": "/open/mr/ownTax", // 欠税公告
|
||||
"TaxContravention": "/open/mr/taxContravention", // 税收违法
|
||||
}
|
||||
|
||||
// TianYanChaConfig 天眼查配置
|
||||
@@ -56,6 +56,7 @@ type TianYanChaResponse struct {
|
||||
Reason string `json:"reason"`
|
||||
Result interface{} `json:"result"`
|
||||
}
|
||||
|
||||
// NewTianYanChaService 创建天眼查服务实例
|
||||
func NewTianYanChaService(baseURL, token string, timeout time.Duration) *TianYanChaService {
|
||||
if timeout == 0 {
|
||||
@@ -118,14 +119,13 @@ func (t *TianYanChaService) CallAPI(ctx context.Context, apiCode string, params
|
||||
isTimeout = true
|
||||
} else if netErr, ok := err.(interface{ Timeout() bool }); ok && netErr.Timeout() {
|
||||
isTimeout = true
|
||||
} else if errStr := err.Error();
|
||||
errStr == "context deadline exceeded" ||
|
||||
errStr == "timeout" ||
|
||||
errStr == "Client.Timeout exceeded" ||
|
||||
errStr == "net/http: request canceled" {
|
||||
} else if errStr := err.Error(); errStr == "context deadline exceeded" ||
|
||||
errStr == "timeout" ||
|
||||
errStr == "Client.Timeout exceeded" ||
|
||||
errStr == "net/http: request canceled" {
|
||||
isTimeout = true
|
||||
}
|
||||
|
||||
|
||||
if isTimeout {
|
||||
return nil, errors.Join(ErrDatasource, fmt.Errorf("API请求超时: %v", err))
|
||||
}
|
||||
@@ -156,7 +156,7 @@ func (t *TianYanChaService) CallAPI(ctx context.Context, apiCode string, params
|
||||
if tianYanChaResp.ErrorCode == 300000 {
|
||||
return nil, errors.Join(ErrNotFound, fmt.Errorf("天眼查查询为空: %s", tianYanChaResp.Reason))
|
||||
}
|
||||
|
||||
|
||||
return &APIResponse{
|
||||
Success: false,
|
||||
Code: tianYanChaResp.ErrorCode,
|
||||
|
||||
@@ -55,7 +55,7 @@ func NewYushanService(url, apiKey, acctID string, logger *external_logger.Extern
|
||||
func (y *YushanService) CallAPI(ctx context.Context, code string, params map[string]interface{}) (respBytes []byte, err error) {
|
||||
startTime := time.Now()
|
||||
requestID := y.generateRequestID()
|
||||
|
||||
|
||||
// 从ctx中获取transactionId
|
||||
var transactionID string
|
||||
if ctxTransactionID, ok := ctx.Value("transaction_id").(string); ok {
|
||||
@@ -131,14 +131,13 @@ func (y *YushanService) CallAPI(ctx context.Context, code string, params map[str
|
||||
isTimeout = true
|
||||
} else if netErr, ok := err.(interface{ Timeout() bool }); ok && netErr.Timeout() {
|
||||
isTimeout = true
|
||||
} else if errStr := err.Error();
|
||||
errStr == "context deadline exceeded" ||
|
||||
errStr == "timeout" ||
|
||||
errStr == "Client.Timeout exceeded" ||
|
||||
errStr == "net/http: request canceled" {
|
||||
} else if errStr := err.Error(); errStr == "context deadline exceeded" ||
|
||||
errStr == "timeout" ||
|
||||
errStr == "Client.Timeout exceeded" ||
|
||||
errStr == "net/http: request canceled" {
|
||||
isTimeout = true
|
||||
}
|
||||
|
||||
|
||||
if isTimeout {
|
||||
err = errors.Join(ErrDatasource, fmt.Errorf("API请求超时: %v", err))
|
||||
} else {
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
# 管理员Token生成脚本使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
`generate_admin_token_simple.go` 是一个用于生成管理员JWT token的脚本,方便管理员登录用户账号进行查看和调试。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 支持通过环境变量自定义用户信息
|
||||
- 自动加载项目配置文件
|
||||
- 生成标准的JWT token
|
||||
- 提供详细的使用说明和示例
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 直接运行
|
||||
|
||||
```bash
|
||||
cd tyapi-server-gin
|
||||
go run scripts/generate_admin_token_simple.go
|
||||
```
|
||||
|
||||
### 固定用户信息
|
||||
|
||||
脚本中已预设了以下用户信息:
|
||||
- 用户ID: `admin_user_001`
|
||||
- 手机号: `13800138000`
|
||||
- 邮箱: `admin@example.com`
|
||||
- 用户类型: `admin`
|
||||
|
||||
如需修改用户信息,请直接编辑脚本中的相应变量。
|
||||
|
||||
## 输出示例
|
||||
|
||||
```
|
||||
=== 管理员Token生成成功 ===
|
||||
用户ID: admin_user_001
|
||||
手机号: 13800138000
|
||||
邮箱: admin@example.com
|
||||
用户类型: admin
|
||||
过期时间: 24h0m0s
|
||||
|
||||
=== Token ===
|
||||
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
|
||||
=== 使用说明 ===
|
||||
在API请求的Authorization头部中使用:
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
|
||||
=== 示例curl命令 ===
|
||||
curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." http://localhost:8080/api/v1/user/profile
|
||||
```
|
||||
|
||||
## 使用场景
|
||||
|
||||
1. **管理员调试**: 管理员需要以特定用户身份登录系统进行调试
|
||||
2. **用户问题排查**: 管理员需要查看特定用户的数据和操作
|
||||
3. **API测试**: 开发人员需要测试不同用户类型的API权限
|
||||
4. **系统维护**: 在系统维护时需要以特定用户身份执行操作
|
||||
|
||||
## 安全注意事项
|
||||
|
||||
⚠️ **重要提醒**:
|
||||
- 此脚本仅用于开发和调试环境
|
||||
- 生产环境中请勿使用此脚本
|
||||
- 生成的token具有完整的用户权限,请妥善保管
|
||||
- 使用完毕后请及时删除或过期token
|
||||
|
||||
## 编译为可执行文件
|
||||
|
||||
如果需要频繁使用,可以编译为可执行文件:
|
||||
|
||||
```bash
|
||||
# 编译
|
||||
go build -o bin/generate-admin-token scripts/generate_admin_token_simple.go
|
||||
|
||||
# 使用
|
||||
./bin/generate-admin-token
|
||||
```
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 1. 配置文件找不到
|
||||
确保在项目根目录下运行脚本,或者确保 `config.yaml` 文件存在。
|
||||
|
||||
### 2. 权限错误
|
||||
确保脚本有读取配置文件的权限。
|
||||
|
||||
### 3. 依赖问题
|
||||
确保所有Go依赖都已正确安装:
|
||||
```bash
|
||||
go mod tidy
|
||||
```
|
||||
@@ -1,72 +0,0 @@
|
||||
# Token生成器使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
为了方便管理员登录用户账号进行查看和调试,我们创建了一个简单的JWT token生成脚本。
|
||||
|
||||
## 文件说明
|
||||
|
||||
- `generate_admin_token_simple.go` - 生成管理员token的脚本
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 生成管理员Token
|
||||
|
||||
```bash
|
||||
cd tyapi-server-gin
|
||||
go run scripts/generate_admin_token_simple.go
|
||||
```
|
||||
|
||||
### 预设用户信息
|
||||
|
||||
脚本中已预设了以下管理员信息:
|
||||
- 用户ID: `admin_user_001`
|
||||
- 手机号: `13800138000`
|
||||
- 邮箱: `admin@example.com`
|
||||
- 用户类型: `admin`
|
||||
|
||||
### 输出示例
|
||||
|
||||
```
|
||||
=== 管理员Token生成成功 ===
|
||||
用户ID: admin_user_001
|
||||
手机号: 13800138000
|
||||
邮箱: admin@example.com
|
||||
用户类型: admin
|
||||
过期时间: 168h0m0s
|
||||
|
||||
=== Token ===
|
||||
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
|
||||
=== 使用说明 ===
|
||||
在API请求的Authorization头部中使用:
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
|
||||
=== 示例curl命令 ===
|
||||
curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." http://localhost:8080/api/v1/user/profile
|
||||
```
|
||||
|
||||
## 使用场景
|
||||
|
||||
1. **管理员调试**: 管理员需要以特定用户身份登录系统进行调试
|
||||
2. **用户问题排查**: 管理员需要查看特定用户的数据和操作
|
||||
3. **API测试**: 开发人员需要测试不同用户类型的API权限
|
||||
|
||||
## 安全提醒
|
||||
|
||||
⚠️ **重要提醒**:
|
||||
- 此脚本仅用于开发和调试环境
|
||||
- 生产环境中请勿使用此脚本
|
||||
- 生成的token具有完整的用户权限,请妥善保管
|
||||
|
||||
## 修改用户信息
|
||||
|
||||
如需修改用户信息,请直接编辑 `generate_admin_token_simple.go` 文件中的相应变量:
|
||||
|
||||
```go
|
||||
// 固定的用户信息
|
||||
userID := "admin_user_001"
|
||||
phone := "13800138000"
|
||||
email := "admin@example.com"
|
||||
userType := "admin"
|
||||
```
|
||||
@@ -1,603 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
"gorm.io/gorm/schema"
|
||||
)
|
||||
|
||||
// ProcessorInfo 处理器信息
|
||||
type ProcessorInfo struct {
|
||||
ProductCode string // 产品编号(从文件名提取)
|
||||
ProductName string // 产品名称(从数据库查询)
|
||||
Category string // 分类(从数据库查询)
|
||||
Price string // 价格(从数据库查询)
|
||||
DataSource string // 数据源(根据service确定)
|
||||
DataSourceCode string // 数据源编号(CallAPI的第一个参数)
|
||||
CostPrice string // 成本价(留空)
|
||||
}
|
||||
|
||||
// Product 产品实体(简化版)
|
||||
type Product struct {
|
||||
Code string `gorm:"column:code"`
|
||||
Name string `gorm:"column:name"`
|
||||
CategoryID string `gorm:"column:category_id"`
|
||||
Category *ProductCategory `gorm:"foreignKey:CategoryID"`
|
||||
Price decimal.Decimal `gorm:"column:price"`
|
||||
}
|
||||
|
||||
// ProductCategory 产品分类实体(简化版)
|
||||
type ProductCategory struct {
|
||||
ID string `gorm:"column:id"`
|
||||
Name string `gorm:"column:name"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
// 获取处理器目录
|
||||
processorsDir := filepath.Join("internal", "domains", "api", "services", "processors")
|
||||
if len(os.Args) > 1 {
|
||||
processorsDir = os.Args[1]
|
||||
}
|
||||
|
||||
// 扫描所有处理器文件
|
||||
processors, err := scanProcessors(processorsDir)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "扫描处理器失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// 连接数据库
|
||||
db, err := connectDB()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "连接数据库失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// 检查数据库产品数量和处理器数量
|
||||
ctx := context.Background()
|
||||
checkProductAndProcessorCounts(ctx, db, processors)
|
||||
|
||||
// 查询产品信息并填充
|
||||
fmt.Println("正在查询数据库...")
|
||||
successCount := 0
|
||||
for i := range processors {
|
||||
product, err := queryProduct(ctx, db, processors[i].ProductCode)
|
||||
if err == nil && product != nil && product.Name != "" {
|
||||
processors[i].ProductName = product.Name
|
||||
if product.Category != nil && product.Category.Name != "" {
|
||||
processors[i].Category = product.Category.Name
|
||||
}
|
||||
// 格式化价格,保留两位小数
|
||||
if !product.Price.IsZero() {
|
||||
processors[i].Price = product.Price.String()
|
||||
}
|
||||
successCount++
|
||||
} else {
|
||||
// 如果查询失败,保留空值(不输出错误,避免日志过多)
|
||||
processors[i].ProductName = ""
|
||||
processors[i].Category = ""
|
||||
processors[i].Price = ""
|
||||
}
|
||||
}
|
||||
fmt.Printf("成功查询到 %d/%d 个产品的信息\n", successCount, len(processors))
|
||||
|
||||
// 按数据源排序
|
||||
sortProcessorsByDataSource(processors)
|
||||
|
||||
// 输出表格
|
||||
printTable(processors)
|
||||
|
||||
// 同时输出到文件
|
||||
writeToFiles(processors)
|
||||
}
|
||||
|
||||
// sortProcessorsByDataSource 按数据源排序
|
||||
func sortProcessorsByDataSource(processors []ProcessorInfo) {
|
||||
// 定义数据源的排序优先级
|
||||
dataSourceOrder := map[string]int{
|
||||
"安徽智查": 1,
|
||||
"羽山数据": 2,
|
||||
"西部数据": 3,
|
||||
"四川星维": 4,
|
||||
"天眼查": 5,
|
||||
"阿里云": 6,
|
||||
"木子数据": 7,
|
||||
"内部处理": 8,
|
||||
"未知": 9,
|
||||
}
|
||||
|
||||
sort.Slice(processors, func(i, j int) bool {
|
||||
orderI, existsI := dataSourceOrder[processors[i].DataSource]
|
||||
orderJ, existsJ := dataSourceOrder[processors[j].DataSource]
|
||||
|
||||
// 如果数据源不存在,放在最后
|
||||
if !existsI {
|
||||
orderI = 999
|
||||
}
|
||||
if !existsJ {
|
||||
orderJ = 999
|
||||
}
|
||||
|
||||
// 首先按数据源排序
|
||||
if orderI != orderJ {
|
||||
return orderI < orderJ
|
||||
}
|
||||
|
||||
// 如果数据源相同,按产品编号排序
|
||||
return processors[i].ProductCode < processors[j].ProductCode
|
||||
})
|
||||
}
|
||||
|
||||
// writeToFiles 将表格写入文件
|
||||
func writeToFiles(processors []ProcessorInfo) {
|
||||
// 写入 CSV 文件
|
||||
csvFile, err := os.Create("processors_table.csv")
|
||||
if err == nil {
|
||||
defer csvFile.Close()
|
||||
csvFile.WriteString("\xEF\xBB\xBF") // UTF-8 BOM
|
||||
csvFile.WriteString("产品编号,产品名称,分类,价格,数据源,数据源编号,成本价\n")
|
||||
for _, p := range processors {
|
||||
productName := strings.ReplaceAll(p.ProductName, ",", ",")
|
||||
category := strings.ReplaceAll(p.Category, ",", ",")
|
||||
csvFile.WriteString(fmt.Sprintf("%s,%s,%s,%s,%s,%s,%s\n",
|
||||
p.ProductCode,
|
||||
productName,
|
||||
category,
|
||||
p.Price,
|
||||
p.DataSource,
|
||||
p.DataSourceCode,
|
||||
p.CostPrice))
|
||||
}
|
||||
fmt.Println("\n✅ CSV 文件已保存到: processors_table.csv")
|
||||
}
|
||||
|
||||
// 写入 Markdown 文件
|
||||
mdFile, err := os.Create("processors_table.md")
|
||||
if err == nil {
|
||||
defer mdFile.Close()
|
||||
mdFile.WriteString("# 处理器产品信息表\n\n")
|
||||
mdFile.WriteString("| 产品编号 | 产品名称 | 分类 | 价格 | 数据源 | 数据源编号 | 成本价 |\n")
|
||||
mdFile.WriteString("|---------|---------|------|------|--------|-----------|--------|\n")
|
||||
for _, p := range processors {
|
||||
productName := p.ProductName
|
||||
if productName == "" {
|
||||
productName = "-"
|
||||
}
|
||||
category := p.Category
|
||||
if category == "" {
|
||||
category = "-"
|
||||
}
|
||||
price := p.Price
|
||||
if price == "" {
|
||||
price = "-"
|
||||
}
|
||||
dataSource := p.DataSource
|
||||
if dataSource == "" {
|
||||
dataSource = "-"
|
||||
}
|
||||
dataSourceCode := p.DataSourceCode
|
||||
if dataSourceCode == "" {
|
||||
dataSourceCode = "-"
|
||||
}
|
||||
costPrice := p.CostPrice
|
||||
if costPrice == "" {
|
||||
costPrice = "-"
|
||||
}
|
||||
mdFile.WriteString(fmt.Sprintf("| %s | %s | %s | %s | %s | %s | %s |\n",
|
||||
p.ProductCode,
|
||||
productName,
|
||||
category,
|
||||
price,
|
||||
dataSource,
|
||||
dataSourceCode,
|
||||
costPrice))
|
||||
}
|
||||
fmt.Println("✅ Markdown 文件已保存到: processors_table.md")
|
||||
}
|
||||
}
|
||||
|
||||
// scanProcessors 扫描处理器文件
|
||||
func scanProcessors(dir string) ([]ProcessorInfo, error) {
|
||||
var processors []ProcessorInfo
|
||||
|
||||
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 跳过非 .go 文件
|
||||
if !strings.HasSuffix(path, "_processor.go") {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 跳过 comb 和 test 目录
|
||||
if strings.Contains(path, string(filepath.Separator)+"comb"+string(filepath.Separator)) {
|
||||
return nil
|
||||
}
|
||||
if strings.Contains(path, string(filepath.Separator)+"test"+string(filepath.Separator)) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 读取文件内容
|
||||
content, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 提取产品编号(从文件名)
|
||||
filename := filepath.Base(path)
|
||||
productCode := extractProductCode(filename)
|
||||
|
||||
// 解析文件内容,提取数据源和数据源编号
|
||||
dataSource, dataSourceCode := extractDataSourceInfo(string(content))
|
||||
|
||||
processors = append(processors, ProcessorInfo{
|
||||
ProductCode: productCode,
|
||||
ProductName: "",
|
||||
Category: "",
|
||||
Price: "",
|
||||
DataSource: dataSource,
|
||||
DataSourceCode: dataSourceCode,
|
||||
CostPrice: "",
|
||||
})
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return processors, err
|
||||
}
|
||||
|
||||
// extractProductCode 从文件名提取产品编号
|
||||
func extractProductCode(filename string) string {
|
||||
// 例如: dwbg6a2c_processor.go -> DWBG6A2C
|
||||
name := strings.TrimSuffix(filename, "_processor.go")
|
||||
return strings.ToUpper(name)
|
||||
}
|
||||
|
||||
// extractDataSourceInfo 从代码中提取数据源和数据源编号
|
||||
func extractDataSourceInfo(content string) (string, string) {
|
||||
// 先尝试匹配特殊的 CallAPI 方法(如 G05HZ01CallAPI)
|
||||
specialCallAPIPattern := regexp.MustCompile(`deps\.(\w+Service)\.(\w+CallAPI)\([^,]+,\s*"([^"]+)"`)
|
||||
specialMatches := specialCallAPIPattern.FindAllStringSubmatch(content, -1)
|
||||
if len(specialMatches) > 0 {
|
||||
serviceName := specialMatches[0][1]
|
||||
dataSourceCode := specialMatches[0][3]
|
||||
return getDataSourceName(serviceName), dataSourceCode
|
||||
}
|
||||
|
||||
// 先尝试匹配 AlicloudService 的特殊情况(第一个参数是字符串路径,没有 ctx)
|
||||
// 匹配: deps.AlicloudService.CallAPI("path", ...)
|
||||
alicloudPattern := regexp.MustCompile(`deps\.AlicloudService\.CallAPI\(\s*"([^"]+)"`)
|
||||
alicloudMatches := alicloudPattern.FindAllStringSubmatch(content, -1)
|
||||
if len(alicloudMatches) > 0 {
|
||||
path := alicloudMatches[0][1]
|
||||
parts := strings.Split(path, "/")
|
||||
dataSourceCode := path
|
||||
if len(parts) > 0 {
|
||||
dataSourceCode = parts[len(parts)-1]
|
||||
}
|
||||
return "阿里云", dataSourceCode
|
||||
}
|
||||
|
||||
// 匹配普通的 CallAPI 调用
|
||||
// 匹配模式: deps.ServiceName.CallAPI(ctx, "CODE", ...) 或 deps.ServiceName.CallAPI(ctx, projectID, ...)
|
||||
callAPIPattern := regexp.MustCompile(`deps\.(\w+Service)\.CallAPI\([^,]*,\s*([^,)]+)`)
|
||||
matches := callAPIPattern.FindAllStringSubmatch(content, -1)
|
||||
|
||||
if len(matches) == 0 {
|
||||
// 检查是否有直接的 HTTP 请求(如 COMENT01)
|
||||
if strings.Contains(content, "http.NewRequest") || strings.Contains(content, "http.Client") {
|
||||
return "内部处理", ""
|
||||
}
|
||||
// 检查是否调用了其他处理器(如 QYGL3F8E)
|
||||
if strings.Contains(content, "Process") && strings.Contains(content, "Request") {
|
||||
return "内部处理", ""
|
||||
}
|
||||
return "未知", ""
|
||||
}
|
||||
|
||||
// 取第一个匹配的服务
|
||||
serviceName := matches[0][1]
|
||||
codeOrVar := strings.TrimSpace(matches[0][2])
|
||||
|
||||
var dataSourceCode string
|
||||
|
||||
// 如果是字符串字面量(带引号),直接提取
|
||||
if strings.HasPrefix(codeOrVar, `"`) && strings.HasSuffix(codeOrVar, `"`) {
|
||||
dataSourceCode = strings.Trim(codeOrVar, `"`)
|
||||
// 对于 AlicloudService,提取路径的最后部分作为数据源编号
|
||||
if serviceName == "AlicloudService" {
|
||||
parts := strings.Split(dataSourceCode, "/")
|
||||
if len(parts) > 0 {
|
||||
dataSourceCode = parts[len(parts)-1]
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 如果是变量(如 projectID),尝试查找变量定义
|
||||
// 对于 XingweiService,查找 projectID 变量
|
||||
if strings.Contains(codeOrVar, "projectID") || codeOrVar == "projectID" {
|
||||
projectIDPattern := regexp.MustCompile(`projectID\s*:=\s*"([^"]+)"`)
|
||||
projectIDMatches := projectIDPattern.FindAllStringSubmatch(content, -1)
|
||||
if len(projectIDMatches) > 0 {
|
||||
// 取最后一个匹配的 projectID(通常是最接近 CallAPI 的那个)
|
||||
dataSourceCode = projectIDMatches[len(projectIDMatches)-1][1]
|
||||
} else {
|
||||
dataSourceCode = "变量未找到"
|
||||
}
|
||||
} else {
|
||||
// 尝试查找变量定义(通用模式)
|
||||
varPattern := regexp.MustCompile(regexp.QuoteMeta(codeOrVar) + `\s*:=\s*"([^"]+)"`)
|
||||
varMatches := varPattern.FindAllStringSubmatch(content, -1)
|
||||
if len(varMatches) > 0 {
|
||||
dataSourceCode = varMatches[len(varMatches)-1][1]
|
||||
} else {
|
||||
dataSourceCode = codeOrVar
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 根据服务名确定数据源
|
||||
dataSource := getDataSourceName(serviceName)
|
||||
|
||||
return dataSource, dataSourceCode
|
||||
}
|
||||
|
||||
// getDataSourceName 根据服务名获取数据源名称
|
||||
func getDataSourceName(serviceName string) string {
|
||||
switch serviceName {
|
||||
case "ZhichaService":
|
||||
return "安徽智查"
|
||||
case "YushanService":
|
||||
return "羽山数据"
|
||||
case "WestDexService":
|
||||
return "西部数据"
|
||||
case "XingweiService":
|
||||
return "四川星维"
|
||||
case "TianYanChaService":
|
||||
return "天眼查"
|
||||
case "AlicloudService":
|
||||
return "阿里云"
|
||||
case "MuziService":
|
||||
return "木子数据"
|
||||
default:
|
||||
return "未知"
|
||||
}
|
||||
}
|
||||
|
||||
// connectDB 连接数据库
|
||||
func connectDB() (*gorm.DB, error) {
|
||||
// 数据库连接配置
|
||||
dsn := "host=1.117.67.95 user=tyapi_user password=Pg9mX4kL8nW2rT5y dbname=tyapi port=25010 sslmode=disable TimeZone=Asia/Shanghai"
|
||||
|
||||
// 配置GORM,使用单数表名(与项目配置一致)
|
||||
gormConfig := &gorm.Config{
|
||||
NamingStrategy: schema.NamingStrategy{
|
||||
SingularTable: true, // 使用单数表名
|
||||
},
|
||||
Logger: logger.Default.LogMode(logger.Silent), // 禁用日志输出,减少噪音
|
||||
}
|
||||
|
||||
db, err := gorm.Open(postgres.Open(dsn), gormConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("连接数据库失败: %w", err)
|
||||
}
|
||||
|
||||
// 测试连接
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取数据库实例失败: %w", err)
|
||||
}
|
||||
|
||||
if err := sqlDB.Ping(); err != nil {
|
||||
return nil, fmt.Errorf("数据库连接测试失败: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("数据库连接成功")
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// checkProductAndProcessorCounts 检查数据库产品数量和处理器数量
|
||||
func checkProductAndProcessorCounts(ctx context.Context, db *gorm.DB, processors []ProcessorInfo) {
|
||||
// 统计处理器数量(排除 comb)
|
||||
processorCount := len(processors)
|
||||
fmt.Printf("\n=== 统计信息 ===\n")
|
||||
fmt.Printf("处理器数量(排除 comb): %d\n", processorCount)
|
||||
|
||||
// 统计数据库中的产品数量(排除组合包)
|
||||
var productCount int64
|
||||
// 先尝试单数表名
|
||||
err := db.WithContext(ctx).
|
||||
Table("product").
|
||||
Where("is_package = ? AND deleted_at IS NULL", false).
|
||||
Count(&productCount).Error
|
||||
|
||||
// 如果单数表名查询失败,尝试复数表名
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "does not exist") {
|
||||
err = db.WithContext(ctx).
|
||||
Table("products").
|
||||
Where("is_package = ? AND deleted_at IS NULL", false).
|
||||
Count(&productCount).Error
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
fmt.Printf("数据库产品数量(排除组合包): %d\n", productCount)
|
||||
} else {
|
||||
fmt.Printf("数据库产品数量查询失败: %v\n", err)
|
||||
}
|
||||
|
||||
// 统计有匹配产品的处理器数量
|
||||
matchedCount := 0
|
||||
for _, p := range processors {
|
||||
var count int64
|
||||
err := db.WithContext(ctx).
|
||||
Table("product").
|
||||
Where("code = ? AND deleted_at IS NULL", p.ProductCode).
|
||||
Count(&count).Error
|
||||
|
||||
if err != nil && strings.Contains(err.Error(), "does not exist") {
|
||||
err = db.WithContext(ctx).
|
||||
Table("products").
|
||||
Where("code = ? AND deleted_at IS NULL", p.ProductCode).
|
||||
Count(&count).Error
|
||||
}
|
||||
|
||||
if err == nil && count > 0 {
|
||||
matchedCount++
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("有匹配产品的处理器数量: %d/%d\n", matchedCount, processorCount)
|
||||
fmt.Printf("无匹配产品的处理器数量: %d/%d\n", processorCount-matchedCount, processorCount)
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// queryProduct 查询产品信息
|
||||
func queryProduct(ctx context.Context, db *gorm.DB, code string) (*Product, error) {
|
||||
var product Product
|
||||
var err error
|
||||
|
||||
// 尝试不同的表名查询(先尝试单数表名,因为用户确认表名是 product)
|
||||
tableNames := []string{"product", "products"}
|
||||
for _, tableName := range tableNames {
|
||||
err = db.WithContext(ctx).
|
||||
Table(tableName).
|
||||
Select("code, name, category_id, price").
|
||||
Where("code = ? AND deleted_at IS NULL", code).
|
||||
First(&product).Error
|
||||
|
||||
if err == nil {
|
||||
// 查询成功,查询分类信息
|
||||
if product.CategoryID != "" {
|
||||
var category ProductCategory
|
||||
// 尝试不同的分类表名(先尝试单数表名)
|
||||
categoryTableNames := []string{"product_category", "product_categories"}
|
||||
for _, catTableName := range categoryTableNames {
|
||||
err = db.WithContext(ctx).
|
||||
Table(catTableName).
|
||||
Select("id, name").
|
||||
Where("id = ? AND deleted_at IS NULL", product.CategoryID).
|
||||
First(&category).Error
|
||||
if err == nil {
|
||||
product.Category = &category
|
||||
break
|
||||
}
|
||||
// 如果是表不存在的错误,继续尝试下一个表名
|
||||
if err != nil && strings.Contains(err.Error(), "does not exist") {
|
||||
continue
|
||||
}
|
||||
// 如果是记录不存在的错误,也继续尝试(可能是表名不对)
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
continue
|
||||
}
|
||||
}
|
||||
// 即使分类查询失败,也返回产品信息(分类可以为空)
|
||||
}
|
||||
return &product, nil
|
||||
}
|
||||
|
||||
// 检查错误类型
|
||||
errStr := err.Error()
|
||||
// 如果是表不存在的错误,继续尝试下一个表名
|
||||
if strings.Contains(errStr, "does not exist") {
|
||||
continue
|
||||
}
|
||||
// 如果是记录不存在的错误,也继续尝试(可能是表名不对)
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
continue
|
||||
}
|
||||
// 其他错误直接返回
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 所有表名都查询失败
|
||||
return nil, gorm.ErrRecordNotFound
|
||||
}
|
||||
|
||||
// printTable 打印表格
|
||||
func printTable(processors []ProcessorInfo) {
|
||||
// 打印表头
|
||||
fmt.Printf("%-15s %-30s %-20s %-15s %-15s %-20s %-15s\n",
|
||||
"产品编号", "产品名称", "分类", "价格", "数据源", "数据源编号", "成本价")
|
||||
fmt.Println(strings.Repeat("-", 130))
|
||||
|
||||
// 打印数据
|
||||
for _, p := range processors {
|
||||
fmt.Printf("%-15s %-30s %-20s %-15s %-15s %-20s %-15s\n",
|
||||
p.ProductCode,
|
||||
p.ProductName,
|
||||
p.Category,
|
||||
p.Price,
|
||||
p.DataSource,
|
||||
p.DataSourceCode,
|
||||
p.CostPrice)
|
||||
}
|
||||
|
||||
// 打印 CSV 格式
|
||||
fmt.Println("\n=== CSV 格式 ===")
|
||||
fmt.Println("产品编号,产品名称,分类,价格,数据源,数据源编号,成本价")
|
||||
for _, p := range processors {
|
||||
// 转义 CSV 中的特殊字符
|
||||
productName := strings.ReplaceAll(p.ProductName, ",", ",")
|
||||
category := strings.ReplaceAll(p.Category, ",", ",")
|
||||
fmt.Printf("%s,%s,%s,%s,%s,%s,%s\n",
|
||||
p.ProductCode,
|
||||
productName,
|
||||
category,
|
||||
p.Price,
|
||||
p.DataSource,
|
||||
p.DataSourceCode,
|
||||
p.CostPrice)
|
||||
}
|
||||
|
||||
// 打印 Markdown 表格格式
|
||||
fmt.Println("\n=== Markdown 表格格式 ===")
|
||||
fmt.Println("| 产品编号 | 产品名称 | 分类 | 价格 | 数据源 | 数据源编号 | 成本价 |")
|
||||
fmt.Println("|---------|---------|------|------|--------|-----------|--------|")
|
||||
for _, p := range processors {
|
||||
productName := p.ProductName
|
||||
if productName == "" {
|
||||
productName = "-"
|
||||
}
|
||||
category := p.Category
|
||||
if category == "" {
|
||||
category = "-"
|
||||
}
|
||||
price := p.Price
|
||||
if price == "" {
|
||||
price = "-"
|
||||
}
|
||||
dataSource := p.DataSource
|
||||
if dataSource == "" {
|
||||
dataSource = "-"
|
||||
}
|
||||
dataSourceCode := p.DataSourceCode
|
||||
if dataSourceCode == "" {
|
||||
dataSourceCode = "-"
|
||||
}
|
||||
costPrice := p.CostPrice
|
||||
if costPrice == "" {
|
||||
costPrice = "-"
|
||||
}
|
||||
fmt.Printf("| %s | %s | %s | %s | %s | %s | %s |\n",
|
||||
p.ProductCode,
|
||||
productName,
|
||||
category,
|
||||
price,
|
||||
dataSource,
|
||||
dataSourceCode,
|
||||
costPrice)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,255 +0,0 @@
|
||||
# TYAPI 生产环境部署脚本 (PowerShell版本)
|
||||
# 使用方法: .\scripts\deploy.ps1 [版本号]
|
||||
|
||||
param(
|
||||
[string]$Version = "latest"
|
||||
)
|
||||
|
||||
# 配置
|
||||
$REGISTRY_URL = "docker-registry.tianyuanapi.com"
|
||||
$IMAGE_NAME = "tyapi-server"
|
||||
$APP_VERSION = $Version
|
||||
$BUILD_TIME = (Get-Date -Format "yyyy-MM-ddTHH:mm:ssZ")
|
||||
|
||||
try {
|
||||
$GIT_COMMIT = git rev-parse --short HEAD 2>$null
|
||||
if (-not $GIT_COMMIT) { $GIT_COMMIT = "dev" }
|
||||
}
|
||||
catch {
|
||||
$GIT_COMMIT = "dev"
|
||||
}
|
||||
|
||||
# 颜色输出函数
|
||||
function Write-Info($message) {
|
||||
Write-Host "[INFO] $message" -ForegroundColor Blue
|
||||
}
|
||||
|
||||
function Write-Success($message) {
|
||||
Write-Host "[SUCCESS] $message" -ForegroundColor Green
|
||||
}
|
||||
|
||||
function Write-Warning($message) {
|
||||
Write-Host "[WARNING] $message" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
function Write-Error($message) {
|
||||
Write-Host "[ERROR] $message" -ForegroundColor Red
|
||||
}
|
||||
|
||||
# 检查必要工具
|
||||
function Test-Requirements {
|
||||
Write-Info "检查部署环境..."
|
||||
|
||||
if (-not (Get-Command docker -ErrorAction SilentlyContinue)) {
|
||||
Write-Error "Docker 未安装或不在 PATH 中"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if (-not (Get-Command docker-compose -ErrorAction SilentlyContinue)) {
|
||||
Write-Error "docker-compose 未安装或不在 PATH 中"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if (-not (Get-Command git -ErrorAction SilentlyContinue)) {
|
||||
Write-Warning "Git 未安装,将使用默认提交哈希"
|
||||
}
|
||||
|
||||
Write-Success "环境检查通过"
|
||||
}
|
||||
|
||||
# 构建 Docker 镜像
|
||||
function Build-Image {
|
||||
Write-Info "开始构建 Docker 镜像..."
|
||||
|
||||
docker build `
|
||||
--build-arg VERSION="$APP_VERSION" `
|
||||
--build-arg COMMIT="$GIT_COMMIT" `
|
||||
--build-arg BUILD_TIME="$BUILD_TIME" `
|
||||
-t "$REGISTRY_URL/$IMAGE_NAME`:$APP_VERSION" `
|
||||
-t "$REGISTRY_URL/$IMAGE_NAME`:latest" `
|
||||
.
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error "Docker 镜像构建失败"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Success "Docker 镜像构建完成"
|
||||
}
|
||||
|
||||
# 推送镜像到私有仓库
|
||||
function Push-Image {
|
||||
Write-Info "推送镜像到私有仓库..."
|
||||
|
||||
# 推送版本标签
|
||||
docker push "$REGISTRY_URL/$IMAGE_NAME`:$APP_VERSION"
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Success "已推送版本标签: $APP_VERSION"
|
||||
}
|
||||
else {
|
||||
Write-Error "推送版本标签失败"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# 推送latest标签
|
||||
docker push "$REGISTRY_URL/$IMAGE_NAME`:latest"
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Success "已推送latest标签"
|
||||
}
|
||||
else {
|
||||
Write-Error "推送latest标签失败"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# 准备生产环境配置
|
||||
function Test-Config {
|
||||
Write-Info "准备生产环境配置..."
|
||||
|
||||
# 检查.env文件是否存在
|
||||
if (-not (Test-Path ".env")) {
|
||||
if (Test-Path ".env.production") {
|
||||
Write-Warning ".env文件不存在,正在复制模板..."
|
||||
Copy-Item ".env.production" ".env"
|
||||
Write-Warning "请编辑 .env 文件并设置正确的配置值"
|
||||
exit 1
|
||||
}
|
||||
else {
|
||||
Write-Error "配置文件 .env 和 .env.production 都不存在"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# 验证关键配置
|
||||
$envContent = Get-Content ".env" -Raw
|
||||
if (-not ($envContent -match "^DB_PASSWORD=" -and -not ($envContent -match "your_secure_database_password_here"))) {
|
||||
Write-Error "请在 .env 文件中设置安全的数据库密码"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if (-not ($envContent -match "^JWT_SECRET=" -and -not ($envContent -match "your_super_secure_jwt_secret"))) {
|
||||
Write-Error "请在 .env 文件中设置安全的JWT密钥"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Success "配置检查通过"
|
||||
}
|
||||
|
||||
# 部署到生产环境
|
||||
function Start-Deploy {
|
||||
Write-Info "开始部署到生产环境..."
|
||||
|
||||
# 设置版本环境变量
|
||||
$env:APP_VERSION = $APP_VERSION
|
||||
|
||||
# 停止现有服务
|
||||
Write-Info "停止现有服务..."
|
||||
docker-compose -f docker-compose.prod.yml down --remove-orphans
|
||||
|
||||
# 清理未使用的镜像
|
||||
Write-Info "清理未使用的Docker资源..."
|
||||
docker image prune -f
|
||||
|
||||
# 拉取最新镜像
|
||||
Write-Info "拉取最新镜像..."
|
||||
docker-compose -f docker-compose.prod.yml pull
|
||||
|
||||
# 启动服务
|
||||
Write-Info "启动生产环境服务..."
|
||||
docker-compose -f docker-compose.prod.yml up -d
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error "服务启动失败"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# 等待服务启动
|
||||
Write-Info "等待服务启动..."
|
||||
Start-Sleep -Seconds 30
|
||||
|
||||
# 检查服务状态
|
||||
Write-Info "检查服务状态..."
|
||||
docker-compose -f docker-compose.prod.yml ps
|
||||
|
||||
# 健康检查
|
||||
Write-Info "执行健康检查..."
|
||||
$maxAttempts = 10
|
||||
$attempt = 0
|
||||
|
||||
while ($attempt -lt $maxAttempts) {
|
||||
try {
|
||||
$response = Invoke-WebRequest -Uri "http://localhost:8080/health" -TimeoutSec 5 -ErrorAction Stop
|
||||
if ($response.StatusCode -eq 200) {
|
||||
Write-Success "应用健康检查通过"
|
||||
break
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$attempt++
|
||||
Write-Info "健康检查失败,重试 $attempt/$maxAttempts..."
|
||||
Start-Sleep -Seconds 10
|
||||
}
|
||||
}
|
||||
|
||||
if ($attempt -eq $maxAttempts) {
|
||||
Write-Error "应用健康检查失败,请检查日志"
|
||||
docker-compose -f docker-compose.prod.yml logs tyapi-app
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Success "部署完成!"
|
||||
}
|
||||
|
||||
# 显示部署信息
|
||||
function Show-Info {
|
||||
Write-Info "部署信息:"
|
||||
Write-Host " 版本: $APP_VERSION"
|
||||
Write-Host " 提交: $GIT_COMMIT"
|
||||
Write-Host " 构建时间: $BUILD_TIME"
|
||||
Write-Host " 镜像: $REGISTRY_URL/$IMAGE_NAME`:$APP_VERSION"
|
||||
Write-Host ""
|
||||
Write-Host "🌐 服务访问地址:"
|
||||
Write-Host " 📱 API服务: http://localhost:8080"
|
||||
Write-Host " 📚 API文档: http://localhost:8080/swagger/index.html"
|
||||
Write-Host " 💚 健康检查: http://localhost:8080/health"
|
||||
Write-Host ""
|
||||
Write-Host "📊 监控和追踪:"
|
||||
Write-Host " 📈 Grafana仪表盘: http://localhost:3000"
|
||||
Write-Host " 🔍 Prometheus监控: http://localhost:9090"
|
||||
Write-Host " 🔗 Jaeger链路追踪: http://localhost:16686"
|
||||
Write-Host ""
|
||||
Write-Host "🛠 管理工具:"
|
||||
Write-Host " 🗄️ pgAdmin数据库: http://localhost:5050"
|
||||
Write-Host " 📦 MinIO对象存储: http://localhost:9000"
|
||||
Write-Host " 🎛️ MinIO控制台: http://localhost:9001"
|
||||
Write-Host ""
|
||||
Write-Host "🔧 管理命令:"
|
||||
Write-Host " 查看日志: docker-compose -f docker-compose.prod.yml logs -f"
|
||||
Write-Host " 停止服务: docker-compose -f docker-compose.prod.yml down"
|
||||
Write-Host " 查看状态: docker-compose -f docker-compose.prod.yml ps"
|
||||
Write-Host " 重启应用: docker-compose -f docker-compose.prod.yml restart tyapi-app"
|
||||
}
|
||||
|
||||
# 主函数
|
||||
function Main {
|
||||
Write-Info "开始 TYAPI 生产环境部署..."
|
||||
Write-Info "版本: $APP_VERSION"
|
||||
|
||||
Test-Requirements
|
||||
Test-Config
|
||||
Build-Image
|
||||
Push-Image
|
||||
Start-Deploy
|
||||
Show-Info
|
||||
|
||||
Write-Success "🎉 部署成功!"
|
||||
}
|
||||
|
||||
# 运行主函数
|
||||
try {
|
||||
Main
|
||||
}
|
||||
catch {
|
||||
Write-Error "部署过程中发生错误: $($_.Exception.Message)"
|
||||
exit 1
|
||||
}
|
||||
@@ -1,221 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# TYAPI 生产环境部署脚本
|
||||
# 使用方法: ./scripts/deploy.sh [version]
|
||||
|
||||
set -e
|
||||
|
||||
# 配置
|
||||
REGISTRY_URL="docker-registry.tianyuanapi.com"
|
||||
IMAGE_NAME="tyapi-server"
|
||||
APP_VERSION=${1:-latest}
|
||||
BUILD_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
GIT_COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo 'dev')
|
||||
|
||||
# 颜色输出
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 日志函数
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# 检查必要工具
|
||||
check_requirements() {
|
||||
log_info "检查部署环境..."
|
||||
|
||||
if ! command -v docker &> /dev/null; then
|
||||
log_error "Docker 未安装或不在 PATH 中"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v docker-compose &> /dev/null; then
|
||||
log_error "docker-compose 未安装或不在 PATH 中"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v git &> /dev/null; then
|
||||
log_warning "Git 未安装,将使用默认提交哈希"
|
||||
fi
|
||||
|
||||
log_success "环境检查通过"
|
||||
}
|
||||
|
||||
# 构建 Docker 镜像
|
||||
build_image() {
|
||||
log_info "开始构建 Docker 镜像..."
|
||||
|
||||
docker build \
|
||||
--build-arg VERSION="$APP_VERSION" \
|
||||
--build-arg COMMIT="$GIT_COMMIT" \
|
||||
--build-arg BUILD_TIME="$BUILD_TIME" \
|
||||
-t "$REGISTRY_URL/$IMAGE_NAME:$APP_VERSION" \
|
||||
-t "$REGISTRY_URL/$IMAGE_NAME:latest" \
|
||||
.
|
||||
|
||||
log_success "Docker 镜像构建完成"
|
||||
}
|
||||
|
||||
# 推送镜像到私有仓库
|
||||
push_image() {
|
||||
log_info "推送镜像到私有仓库..."
|
||||
|
||||
# 推送版本标签
|
||||
docker push "$REGISTRY_URL/$IMAGE_NAME:$APP_VERSION"
|
||||
log_success "已推送版本标签: $APP_VERSION"
|
||||
|
||||
# 推送latest标签
|
||||
docker push "$REGISTRY_URL/$IMAGE_NAME:latest"
|
||||
log_success "已推送latest标签"
|
||||
}
|
||||
|
||||
# 准备生产环境配置
|
||||
prepare_config() {
|
||||
log_info "准备生产环境配置..."
|
||||
|
||||
# 检查.env文件是否存在
|
||||
if [ ! -f ".env" ]; then
|
||||
if [ -f ".env.production" ]; then
|
||||
log_warning ".env文件不存在,正在复制模板..."
|
||||
cp .env.production .env
|
||||
log_warning "请编辑 .env 文件并设置正确的配置值"
|
||||
exit 1
|
||||
else
|
||||
log_error "配置文件 .env 和 .env.production 都不存在"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# 验证关键配置
|
||||
if ! grep -q "^DB_PASSWORD=" .env || grep -q "your_secure_database_password_here" .env; then
|
||||
log_error "请在 .env 文件中设置安全的数据库密码"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! grep -q "^JWT_SECRET=" .env || grep -q "your_super_secure_jwt_secret" .env; then
|
||||
log_error "请在 .env 文件中设置安全的JWT密钥"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_success "配置检查通过"
|
||||
}
|
||||
|
||||
# 部署到生产环境
|
||||
deploy() {
|
||||
log_info "开始部署到生产环境..."
|
||||
|
||||
# 设置版本环境变量
|
||||
export APP_VERSION="$APP_VERSION"
|
||||
|
||||
# 停止现有服务
|
||||
log_info "停止现有服务..."
|
||||
docker-compose -f docker-compose.prod.yml down --remove-orphans
|
||||
|
||||
# 清理未使用的镜像
|
||||
log_info "清理未使用的Docker资源..."
|
||||
docker image prune -f
|
||||
|
||||
# 拉取最新镜像
|
||||
log_info "拉取最新镜像..."
|
||||
docker-compose -f docker-compose.prod.yml pull
|
||||
|
||||
# 启动服务
|
||||
log_info "启动生产环境服务..."
|
||||
docker-compose -f docker-compose.prod.yml up -d
|
||||
|
||||
# 等待服务启动
|
||||
log_info "等待服务启动..."
|
||||
sleep 30
|
||||
|
||||
# 检查服务状态
|
||||
log_info "检查服务状态..."
|
||||
docker-compose -f docker-compose.prod.yml ps
|
||||
|
||||
# 健康检查
|
||||
log_info "执行健康检查..."
|
||||
max_attempts=10
|
||||
attempt=0
|
||||
|
||||
while [ $attempt -lt $max_attempts ]; do
|
||||
if curl -f http://localhost:8080/health > /dev/null 2>&1; then
|
||||
log_success "应用健康检查通过"
|
||||
break
|
||||
else
|
||||
attempt=$((attempt + 1))
|
||||
log_info "健康检查失败,重试 $attempt/$max_attempts..."
|
||||
sleep 10
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $attempt -eq $max_attempts ]; then
|
||||
log_error "应用健康检查失败,请检查日志"
|
||||
docker-compose -f docker-compose.prod.yml logs tyapi-app
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_success "部署完成!"
|
||||
}
|
||||
|
||||
# 显示部署信息
|
||||
show_info() {
|
||||
log_info "部署信息:"
|
||||
echo " 版本: $APP_VERSION"
|
||||
echo " 提交: $GIT_COMMIT"
|
||||
echo " 构建时间: $BUILD_TIME"
|
||||
echo " 镜像: $REGISTRY_URL/$IMAGE_NAME:$APP_VERSION"
|
||||
echo ""
|
||||
echo "🌐 服务访问地址:"
|
||||
echo " 📱 API服务: http://localhost:8080"
|
||||
echo " 📚 API文档: http://localhost:8080/swagger/index.html"
|
||||
echo " 💚 健康检查: http://localhost:8080/health"
|
||||
echo ""
|
||||
echo "📊 监控和追踪:"
|
||||
echo " 📈 Grafana仪表盘: http://localhost:3000"
|
||||
echo " 🔍 Prometheus监控: http://localhost:9090"
|
||||
echo " 🔗 Jaeger链路追踪: http://localhost:16686"
|
||||
echo ""
|
||||
echo "🛠 管理工具:"
|
||||
echo " 🗄️ pgAdmin数据库: http://localhost:5050"
|
||||
echo " 📦 MinIO对象存储: http://localhost:9000"
|
||||
echo " 🎛️ MinIO控制台: http://localhost:9001"
|
||||
echo ""
|
||||
echo "🔧 管理命令:"
|
||||
echo " 查看日志: docker-compose -f docker-compose.prod.yml logs -f"
|
||||
echo " 停止服务: docker-compose -f docker-compose.prod.yml down"
|
||||
echo " 查看状态: docker-compose -f docker-compose.prod.yml ps"
|
||||
echo " 重启应用: docker-compose -f docker-compose.prod.yml restart tyapi-app"
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
log_info "开始 TYAPI 生产环境部署..."
|
||||
log_info "版本: $APP_VERSION"
|
||||
|
||||
check_requirements
|
||||
prepare_config
|
||||
build_image
|
||||
push_image
|
||||
deploy
|
||||
show_info
|
||||
|
||||
log_success "🎉 部署成功!"
|
||||
}
|
||||
|
||||
# 运行主函数
|
||||
main "$@"
|
||||
@@ -1,162 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
"gorm.io/gorm/schema"
|
||||
)
|
||||
|
||||
// Product 产品实体(简化版)
|
||||
type Product struct {
|
||||
Code string `gorm:"column:code"`
|
||||
Name string `gorm:"column:name"`
|
||||
CostPrice *float64 `gorm:"column:cost_price"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
// 连接数据库
|
||||
db, err := connectDB()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "连接数据库失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
fmt.Println("正在查询数据库中未设置成本价的产品...")
|
||||
fmt.Println()
|
||||
|
||||
// 查询没有设置成本价的产品(cost_price IS NULL 或 cost_price = 0)
|
||||
var products []Product
|
||||
err = db.WithContext(ctx).
|
||||
Table("product").
|
||||
Select("code, name, cost_price").
|
||||
Where("deleted_at IS NULL AND (cost_price IS NULL OR cost_price = 0)").
|
||||
Order("code ASC").
|
||||
Find(&products).Error
|
||||
|
||||
// 如果单数表名查询失败,尝试复数表名
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "does not exist") {
|
||||
err = db.WithContext(ctx).
|
||||
Table("products").
|
||||
Select("code, name, cost_price").
|
||||
Where("deleted_at IS NULL AND (cost_price IS NULL OR cost_price = 0)").
|
||||
Order("code ASC").
|
||||
Find(&products).Error
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "查询失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
totalCount := len(products)
|
||||
fmt.Printf("找到 %d 个未设置成本价的产品\n\n", totalCount)
|
||||
|
||||
if totalCount == 0 {
|
||||
fmt.Println("所有产品都已设置成本价!")
|
||||
return
|
||||
}
|
||||
|
||||
// 打印到控制台
|
||||
fmt.Println("=== 未设置成本价的产品列表 ===")
|
||||
fmt.Printf("%-20s %-50s %-15s\n", "产品编号", "产品名称", "成本价")
|
||||
fmt.Println(strings.Repeat("-", 85))
|
||||
|
||||
for _, p := range products {
|
||||
costPriceStr := "-"
|
||||
if p.CostPrice != nil && *p.CostPrice != 0 {
|
||||
costPriceStr = fmt.Sprintf("%.2f", *p.CostPrice)
|
||||
}
|
||||
productName := p.Name
|
||||
if productName == "" {
|
||||
productName = "-"
|
||||
}
|
||||
fmt.Printf("%-20s %-50s %-15s\n", p.Code, productName, costPriceStr)
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
|
||||
// 保存到 CSV 文件
|
||||
csvFile, err := os.Create("missing_cost_price.csv")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "创建 CSV 文件失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
defer csvFile.Close()
|
||||
|
||||
// 写入 UTF-8 BOM
|
||||
csvFile.WriteString("\xEF\xBB\xBF")
|
||||
|
||||
writer := csv.NewWriter(csvFile)
|
||||
defer writer.Flush()
|
||||
|
||||
// 写入表头
|
||||
headers := []string{"产品编号", "产品名称", "成本价"}
|
||||
if err := writer.Write(headers); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "写入 CSV 表头失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 写入数据
|
||||
for _, p := range products {
|
||||
costPriceStr := ""
|
||||
if p.CostPrice != nil && *p.CostPrice != 0 {
|
||||
costPriceStr = fmt.Sprintf("%.2f", *p.CostPrice)
|
||||
}
|
||||
productName := p.Name
|
||||
if productName == "" {
|
||||
productName = "-"
|
||||
}
|
||||
record := []string{p.Code, productName, costPriceStr}
|
||||
if err := writer.Write(record); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "写入 CSV 数据失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("✅ 结果已保存到: missing_cost_price.csv\n")
|
||||
fmt.Printf("📊 总计: %d 个产品未设置成本价\n", totalCount)
|
||||
}
|
||||
|
||||
// connectDB 连接数据库
|
||||
func connectDB() (*gorm.DB, error) {
|
||||
// 数据库连接配置
|
||||
dsn := "host=1.117.67.95 user=tyapi_user password=Pg9mX4kL8nW2rT5y dbname=tyapi port=25010 sslmode=disable TimeZone=Asia/Shanghai"
|
||||
|
||||
// 配置GORM,使用单数表名(与项目配置一致)
|
||||
gormConfig := &gorm.Config{
|
||||
NamingStrategy: schema.NamingStrategy{
|
||||
SingularTable: true, // 使用单数表名
|
||||
},
|
||||
Logger: logger.Default.LogMode(logger.Info), // 显示 SQL 日志
|
||||
}
|
||||
|
||||
db, err := gorm.Open(postgres.Open(dsn), gormConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("连接数据库失败: %w", err)
|
||||
}
|
||||
|
||||
// 测试连接
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取数据库实例失败: %w", err)
|
||||
}
|
||||
|
||||
if err := sqlDB.Ping(); err != nil {
|
||||
return nil, fmt.Errorf("数据库连接测试失败: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("数据库连接成功")
|
||||
return db, nil
|
||||
}
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"tyapi-server/internal/config"
|
||||
"tyapi-server/internal/shared/middleware"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 固定的用户信息
|
||||
userID := "93b2fb79-4def-4ad2-8004-08943fcf2fa9"
|
||||
phone := "17537188270"
|
||||
email := ""
|
||||
userType := "user"
|
||||
|
||||
// 加载配置文件
|
||||
cfg, err := config.LoadConfig()
|
||||
if err != nil {
|
||||
log.Fatalf("加载配置文件失败: %v", err)
|
||||
}
|
||||
|
||||
// 创建logger
|
||||
logger, err := zap.NewProduction()
|
||||
if err != nil {
|
||||
log.Fatalf("创建logger失败: %v", err)
|
||||
}
|
||||
defer logger.Sync()
|
||||
|
||||
// 创建JWT认证中间件
|
||||
jwtAuth := middleware.NewJWTAuthMiddleware(cfg, logger)
|
||||
|
||||
// 生成token
|
||||
token, err := jwtAuth.GenerateToken(userID, phone, email, userType)
|
||||
if err != nil {
|
||||
log.Fatalf("生成token失败: %v", err)
|
||||
}
|
||||
|
||||
// 输出结果
|
||||
fmt.Println("=== 管理员Token生成成功 ===")
|
||||
fmt.Printf("用户ID: %s\n", userID)
|
||||
fmt.Printf("手机号: %s\n", phone)
|
||||
fmt.Printf("邮箱: %s\n", email)
|
||||
fmt.Printf("用户类型: %s\n", userType)
|
||||
fmt.Printf("过期时间: %s\n", cfg.JWT.ExpiresIn.String())
|
||||
fmt.Println("\n=== Token ===")
|
||||
fmt.Println(token)
|
||||
fmt.Println("\n=== 使用说明 ===")
|
||||
fmt.Println("在API请求的Authorization头部中使用:")
|
||||
fmt.Printf("Authorization: Bearer %s\n", token)
|
||||
fmt.Println("\n=== 示例curl命令 ===")
|
||||
fmt.Printf("curl -H \"Authorization: Bearer %s\" http://localhost:8080/api/v1/user/profile\n", token)
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
-- TYAPI Server Database Initialization Script
|
||||
-- This script runs when PostgreSQL container starts for the first time
|
||||
|
||||
-- Create development database if it doesn't exist
|
||||
-- Note: tyapi_dev is already created by POSTGRES_DB environment variable
|
||||
|
||||
-- Create test database for running tests
|
||||
-- Note: Skip database creation in init script, handle in application if needed
|
||||
|
||||
-- Create production database (for reference)
|
||||
-- CREATE DATABASE tyapi_prod;
|
||||
|
||||
-- Connect to development database
|
||||
\c tyapi_dev;
|
||||
|
||||
-- Enable necessary extensions
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS "pg_trgm";
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS "btree_gin";
|
||||
|
||||
-- Create schemas for better organization
|
||||
CREATE SCHEMA IF NOT EXISTS public;
|
||||
|
||||
CREATE SCHEMA IF NOT EXISTS logs;
|
||||
|
||||
CREATE SCHEMA IF NOT EXISTS metrics;
|
||||
|
||||
-- Set search path
|
||||
SET search_path TO public, logs, metrics;
|
||||
|
||||
-- Test database setup will be handled by application migrations
|
||||
-- when needed, since we don't create it in this init script
|
||||
|
||||
-- Continue with development database setup
|
||||
-- (already connected to tyapi_dev)
|
||||
|
||||
-- Create application-specific roles (optional)
|
||||
-- CREATE ROLE tyapi_app WITH LOGIN PASSWORD 'app_password';
|
||||
-- CREATE ROLE tyapi_readonly WITH LOGIN PASSWORD 'readonly_password';
|
||||
|
||||
-- Grant permissions
|
||||
-- GRANT CONNECT ON DATABASE tyapi_dev TO tyapi_app;
|
||||
-- GRANT USAGE ON SCHEMA public TO tyapi_app;
|
||||
-- GRANT CREATE ON SCHEMA public TO tyapi_app;
|
||||
|
||||
-- Initial seed data can be added here
|
||||
-- This will be replaced by proper migrations in the application
|
||||
|
||||
-- Log the initialization
|
||||
-- Note: pg_stat_statements extension may not be available, skip this insert
|
||||
|
||||
-- Create a simple health check function
|
||||
CREATE OR REPLACE FUNCTION health_check()
|
||||
RETURNS json AS $$
|
||||
BEGIN
|
||||
RETURN json_build_object(
|
||||
'status', 'healthy',
|
||||
'database', current_database(),
|
||||
'timestamp', now(),
|
||||
'version', version()
|
||||
);
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
@@ -1,154 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 日志管理脚本
|
||||
# 用于清理旧日志文件和查看日志统计信息
|
||||
|
||||
LOG_DIR="./logs"
|
||||
RETENTION_DAYS=30
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 打印带颜色的消息
|
||||
print_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
print_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# 显示帮助信息
|
||||
show_help() {
|
||||
echo "日志管理脚本"
|
||||
echo ""
|
||||
echo "用法: $0 [命令]"
|
||||
echo ""
|
||||
echo "命令:"
|
||||
echo " clean - 清理超过 $RETENTION_DAYS 天的旧日志文件"
|
||||
echo " stats - 显示日志统计信息"
|
||||
echo " size - 显示日志目录大小"
|
||||
echo " list - 列出所有日志文件"
|
||||
echo " help - 显示此帮助信息"
|
||||
echo ""
|
||||
echo "示例:"
|
||||
echo " $0 clean # 清理旧日志"
|
||||
echo " $0 stats # 查看统计信息"
|
||||
}
|
||||
|
||||
# 清理旧日志文件
|
||||
clean_old_logs() {
|
||||
print_info "开始清理超过 $RETENTION_DAYS 天的旧日志文件..."
|
||||
|
||||
if [ ! -d "$LOG_DIR" ]; then
|
||||
print_error "日志目录 $LOG_DIR 不存在"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 查找并删除超过指定天数的日志文件
|
||||
find "$LOG_DIR" -name "*.log*" -type f -mtime +$RETENTION_DAYS -exec rm -f {} \;
|
||||
|
||||
# 删除空的日期目录
|
||||
find "$LOG_DIR" -type d -empty -delete
|
||||
|
||||
print_success "旧日志文件清理完成"
|
||||
}
|
||||
|
||||
# 显示日志统计信息
|
||||
show_stats() {
|
||||
print_info "日志统计信息:"
|
||||
echo ""
|
||||
|
||||
if [ ! -d "$LOG_DIR" ]; then
|
||||
print_error "日志目录 $LOG_DIR 不存在"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 总文件数
|
||||
total_files=$(find "$LOG_DIR" -name "*.log*" -type f | wc -l)
|
||||
echo "总日志文件数: $total_files"
|
||||
|
||||
# 总大小
|
||||
total_size=$(du -sh "$LOG_DIR" 2>/dev/null | cut -f1)
|
||||
echo "日志目录总大小: $total_size"
|
||||
|
||||
# 按日期统计
|
||||
echo ""
|
||||
echo "按日期统计:"
|
||||
for date_dir in "$LOG_DIR"/*/; do
|
||||
if [ -d "$date_dir" ]; then
|
||||
date_name=$(basename "$date_dir")
|
||||
file_count=$(find "$date_dir" -name "*.log*" -type f | wc -l)
|
||||
dir_size=$(du -sh "$date_dir" 2>/dev/null | cut -f1)
|
||||
echo " $date_name: $file_count 个文件, $dir_size"
|
||||
fi
|
||||
done
|
||||
|
||||
# 最近修改的文件
|
||||
echo ""
|
||||
echo "最近修改的日志文件:"
|
||||
find "$LOG_DIR" -name "*.log*" -type f -exec ls -lh {} \; | head -5
|
||||
}
|
||||
|
||||
# 显示日志目录大小
|
||||
show_size() {
|
||||
if [ ! -d "$LOG_DIR" ]; then
|
||||
print_error "日志目录 $LOG_DIR 不存在"
|
||||
return 1
|
||||
fi
|
||||
|
||||
total_size=$(du -sh "$LOG_DIR" 2>/dev/null | cut -f1)
|
||||
print_info "日志目录大小: $total_size"
|
||||
}
|
||||
|
||||
# 列出所有日志文件
|
||||
list_logs() {
|
||||
if [ ! -d "$LOG_DIR" ]; then
|
||||
print_error "日志目录 $LOG_DIR 不存在"
|
||||
return 1
|
||||
fi
|
||||
|
||||
print_info "所有日志文件:"
|
||||
find "$LOG_DIR" -name "*.log*" -type f -exec ls -lh {} \;
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
case "$1" in
|
||||
"clean")
|
||||
clean_old_logs
|
||||
;;
|
||||
"stats")
|
||||
show_stats
|
||||
;;
|
||||
"size")
|
||||
show_size
|
||||
;;
|
||||
"list")
|
||||
list_logs
|
||||
;;
|
||||
"help"|"-h"|"--help"|"")
|
||||
show_help
|
||||
;;
|
||||
*)
|
||||
print_error "未知命令: $1"
|
||||
show_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# 执行主函数
|
||||
main "$@"
|
||||
@@ -1,372 +0,0 @@
|
||||
# Repository 迁移指南
|
||||
|
||||
## 从传统模式迁移到 BaseRepositoryImpl 模式
|
||||
|
||||
### 步骤 1:修改结构体定义
|
||||
|
||||
**之前:**
|
||||
```go
|
||||
type GormExampleRepository struct {
|
||||
db *gorm.DB
|
||||
logger *zap.Logger
|
||||
}
|
||||
```
|
||||
|
||||
**之后:**
|
||||
```go
|
||||
type GormExampleRepository struct {
|
||||
*database.BaseRepositoryImpl // 嵌入基础Repository实现
|
||||
}
|
||||
```
|
||||
|
||||
### 步骤 2:修改构造函数
|
||||
|
||||
**之前:**
|
||||
```go
|
||||
func NewGormExampleRepository(db *gorm.DB, logger *zap.Logger) ExampleRepository {
|
||||
return &GormExampleRepository{
|
||||
db: db,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**之后:**
|
||||
```go
|
||||
func NewGormExampleRepository(db *gorm.DB, logger *zap.Logger) ExampleRepository {
|
||||
return &GormExampleRepository{
|
||||
BaseRepositoryImpl: database.NewBaseRepositoryImpl(db, logger),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 步骤 3:删除 getDB 方法
|
||||
|
||||
**删除这样的代码:**
|
||||
```go
|
||||
func (r *GormExampleRepository) getDB(ctx context.Context) *gorm.DB {
|
||||
if tx, ok := database.GetTx(ctx); ok {
|
||||
return tx
|
||||
}
|
||||
return r.db
|
||||
}
|
||||
```
|
||||
|
||||
### 步骤 4:实现 Repository[T] 接口方法
|
||||
|
||||
#### 基础 CRUD 操作
|
||||
|
||||
**Create - 之前:**
|
||||
```go
|
||||
func (r *GormExampleRepository) Create(ctx context.Context, entity Entity) (Entity, error) {
|
||||
r.logger.Info("创建实体", zap.String("id", entity.ID))
|
||||
err := r.getDB(ctx).WithContext(ctx).Create(&entity).Error
|
||||
return entity, err
|
||||
}
|
||||
```
|
||||
|
||||
**Create - 之后:**
|
||||
```go
|
||||
func (r *GormExampleRepository) Create(ctx context.Context, entity Entity) (Entity, error) {
|
||||
r.GetLogger().Info("创建实体", zap.String("id", entity.ID))
|
||||
err := r.BaseRepositoryImpl.Create(ctx, &entity)
|
||||
return entity, err
|
||||
}
|
||||
```
|
||||
|
||||
**GetByID - 之前:**
|
||||
```go
|
||||
func (r *GormExampleRepository) GetByID(ctx context.Context, id string) (Entity, error) {
|
||||
var entity Entity
|
||||
err := r.getDB(ctx).WithContext(ctx).Where("id = ?", id).First(&entity).Error
|
||||
return entity, err
|
||||
}
|
||||
```
|
||||
|
||||
**GetByID - 之后:**
|
||||
```go
|
||||
func (r *GormExampleRepository) GetByID(ctx context.Context, id string) (Entity, error) {
|
||||
var entity Entity
|
||||
err := r.BaseRepositoryImpl.GetByID(ctx, id, &entity)
|
||||
return entity, err
|
||||
}
|
||||
```
|
||||
|
||||
**Update - 之前:**
|
||||
```go
|
||||
func (r *GormExampleRepository) Update(ctx context.Context, entity Entity) error {
|
||||
r.logger.Info("更新实体", zap.String("id", entity.ID))
|
||||
return r.getDB(ctx).WithContext(ctx).Save(&entity).Error
|
||||
}
|
||||
```
|
||||
|
||||
**Update - 之后:**
|
||||
```go
|
||||
func (r *GormExampleRepository) Update(ctx context.Context, entity Entity) error {
|
||||
r.GetLogger().Info("更新实体", zap.String("id", entity.ID))
|
||||
return r.BaseRepositoryImpl.Update(ctx, &entity)
|
||||
}
|
||||
```
|
||||
|
||||
#### 批量操作
|
||||
|
||||
**CreateBatch:**
|
||||
```go
|
||||
func (r *GormExampleRepository) CreateBatch(ctx context.Context, entities []Entity) error {
|
||||
r.GetLogger().Info("批量创建实体", zap.Int("count", len(entities)))
|
||||
return r.BaseRepositoryImpl.CreateBatch(ctx, &entities)
|
||||
}
|
||||
```
|
||||
|
||||
**GetByIDs:**
|
||||
```go
|
||||
func (r *GormExampleRepository) GetByIDs(ctx context.Context, ids []string) ([]Entity, error) {
|
||||
var entities []Entity
|
||||
err := r.BaseRepositoryImpl.GetByIDs(ctx, ids, &entities)
|
||||
return entities, err
|
||||
}
|
||||
```
|
||||
|
||||
**UpdateBatch:**
|
||||
```go
|
||||
func (r *GormExampleRepository) UpdateBatch(ctx context.Context, entities []Entity) error {
|
||||
r.GetLogger().Info("批量更新实体", zap.Int("count", len(entities)))
|
||||
return r.BaseRepositoryImpl.UpdateBatch(ctx, &entities)
|
||||
}
|
||||
```
|
||||
|
||||
**DeleteBatch:**
|
||||
```go
|
||||
func (r *GormExampleRepository) DeleteBatch(ctx context.Context, ids []string) error {
|
||||
r.GetLogger().Info("批量删除实体", zap.Strings("ids", ids))
|
||||
return r.BaseRepositoryImpl.DeleteBatch(ctx, ids, &Entity{})
|
||||
}
|
||||
```
|
||||
|
||||
### 步骤 5:实现 BaseRepository 接口方法
|
||||
|
||||
#### 基础操作
|
||||
|
||||
**Delete:**
|
||||
```go
|
||||
func (r *GormExampleRepository) Delete(ctx context.Context, id string) error {
|
||||
r.GetLogger().Info("删除实体", zap.String("id", id))
|
||||
return r.BaseRepositoryImpl.Delete(ctx, id, &Entity{})
|
||||
}
|
||||
```
|
||||
|
||||
**Exists:**
|
||||
```go
|
||||
func (r *GormExampleRepository) Exists(ctx context.Context, id string) (bool, error) {
|
||||
return r.BaseRepositoryImpl.Exists(ctx, id, &Entity{})
|
||||
}
|
||||
```
|
||||
|
||||
**Count:**
|
||||
```go
|
||||
func (r *GormExampleRepository) Count(ctx context.Context, options interfaces.CountOptions) (int64, error) {
|
||||
// 如果需要自定义搜索逻辑
|
||||
if options.Search != "" {
|
||||
return r.CountWhere(ctx, &Entity{}, "name LIKE ? OR description LIKE ?",
|
||||
"%"+options.Search+"%", "%"+options.Search+"%")
|
||||
}
|
||||
return r.BaseRepositoryImpl.Count(ctx, &Entity{}, options)
|
||||
}
|
||||
```
|
||||
|
||||
**SoftDelete:**
|
||||
```go
|
||||
func (r *GormExampleRepository) SoftDelete(ctx context.Context, id string) error {
|
||||
r.GetLogger().Info("软删除实体", zap.String("id", id))
|
||||
return r.BaseRepositoryImpl.SoftDelete(ctx, id, &Entity{})
|
||||
}
|
||||
```
|
||||
|
||||
**Restore:**
|
||||
```go
|
||||
func (r *GormExampleRepository) Restore(ctx context.Context, id string) error {
|
||||
r.GetLogger().Info("恢复实体", zap.String("id", id))
|
||||
return r.BaseRepositoryImpl.Restore(ctx, id, &Entity{})
|
||||
}
|
||||
```
|
||||
|
||||
#### 列表查询
|
||||
|
||||
**List:**
|
||||
```go
|
||||
func (r *GormExampleRepository) List(ctx context.Context, options interfaces.ListOptions) ([]Entity, error) {
|
||||
var entities []Entity
|
||||
|
||||
// 如果需要自定义搜索逻辑
|
||||
if options.Search != "" {
|
||||
query := r.GetDB(ctx).Model(&Entity{})
|
||||
|
||||
// 应用筛选条件
|
||||
if options.Filters != nil {
|
||||
for key, value := range options.Filters {
|
||||
query = query.Where(key+" = ?", value)
|
||||
}
|
||||
}
|
||||
|
||||
// 自定义搜索逻辑
|
||||
query = query.Where("name LIKE ? OR description LIKE ?",
|
||||
"%"+options.Search+"%", "%"+options.Search+"%")
|
||||
|
||||
// 应用预加载
|
||||
for _, include := range options.Include {
|
||||
query = query.Preload(include)
|
||||
}
|
||||
|
||||
// 应用排序
|
||||
if options.Sort != "" {
|
||||
order := "ASC"
|
||||
if options.Order == "desc" || options.Order == "DESC" {
|
||||
order = "DESC"
|
||||
}
|
||||
query = query.Order(options.Sort + " " + order)
|
||||
} else {
|
||||
query = query.Order("created_at DESC")
|
||||
}
|
||||
|
||||
// 应用分页
|
||||
if options.Page > 0 && options.PageSize > 0 {
|
||||
offset := (options.Page - 1) * options.PageSize
|
||||
query = query.Offset(offset).Limit(options.PageSize)
|
||||
}
|
||||
|
||||
return entities, query.Find(&entities).Error
|
||||
}
|
||||
|
||||
// 使用基础实现
|
||||
err := r.BaseRepositoryImpl.List(ctx, &Entity{}, &entities, options)
|
||||
return entities, err
|
||||
}
|
||||
```
|
||||
|
||||
### 步骤 6:业务方法使用辅助方法
|
||||
|
||||
**使用 FindOne:**
|
||||
```go
|
||||
func (r *GormExampleRepository) GetByCode(ctx context.Context, code string) (*Entity, error) {
|
||||
var entity Entity
|
||||
err := r.FindOne(ctx, &entity, "code = ?", code)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &entity, nil
|
||||
}
|
||||
```
|
||||
|
||||
**使用 FindWhere:**
|
||||
```go
|
||||
func (r *GormExampleRepository) GetByStatus(ctx context.Context, status string) ([]Entity, error) {
|
||||
var entities []Entity
|
||||
err := r.FindWhere(ctx, &entities, "status = ?", status)
|
||||
return entities, err
|
||||
}
|
||||
```
|
||||
|
||||
**使用 CountWhere:**
|
||||
```go
|
||||
func (r *GormExampleRepository) CountByStatus(ctx context.Context, status string) (int64, error) {
|
||||
return r.CountWhere(ctx, &Entity{}, "status = ?", status)
|
||||
}
|
||||
```
|
||||
|
||||
**使用 ExistsWhere:**
|
||||
```go
|
||||
func (r *GormExampleRepository) ExistsByCode(ctx context.Context, code string) (bool, error) {
|
||||
return r.ExistsWhere(ctx, &Entity{}, "code = ?", code)
|
||||
}
|
||||
```
|
||||
|
||||
### 步骤 7:复杂查询仍使用 GetDB
|
||||
|
||||
对于复杂查询,继续使用 `r.GetDB(ctx)` 来获取数据库连接:
|
||||
|
||||
```go
|
||||
func (r *GormExampleRepository) ComplexQuery(ctx context.Context, params QueryParams) ([]Entity, error) {
|
||||
var entities []Entity
|
||||
query := r.GetDB(ctx).Model(&Entity{})
|
||||
|
||||
// 添加复杂的查询逻辑
|
||||
if params.Status != "" {
|
||||
query = query.Where("status = ?", params.Status)
|
||||
}
|
||||
|
||||
if params.DateRange != nil {
|
||||
query = query.Where("created_at BETWEEN ? AND ?", params.DateRange.Start, params.DateRange.End)
|
||||
}
|
||||
|
||||
// 连接查询
|
||||
query = query.Joins("LEFT JOIN related_table ON entities.related_id = related_table.id").
|
||||
Where("related_table.active = ?", true)
|
||||
|
||||
return entities, query.Find(&entities).Error
|
||||
}
|
||||
```
|
||||
|
||||
## BaseRepositoryImpl 提供的方法
|
||||
|
||||
### 核心方法
|
||||
- `GetDB(ctx)` - 获取数据库连接(自动支持事务)
|
||||
- `GetLogger()` - 获取日志记录器
|
||||
- `WithTx(tx)` - 创建事务版本的Repository
|
||||
|
||||
### 基础 CRUD
|
||||
- `Create(ctx, entity)` - 创建实体
|
||||
- `GetByID(ctx, id, entity)` - 根据ID获取
|
||||
- `Update(ctx, entity)` - 更新实体
|
||||
- `Delete(ctx, id, entity)` - 删除实体
|
||||
- `Exists(ctx, id, entity)` - 检查存在
|
||||
|
||||
### 批量操作
|
||||
- `CreateBatch(ctx, entities)` - 批量创建
|
||||
- `GetByIDs(ctx, ids, entities)` - 批量获取
|
||||
- `UpdateBatch(ctx, entities)` - 批量更新
|
||||
- `DeleteBatch(ctx, ids, entity)` - 批量删除
|
||||
|
||||
### 查询方法
|
||||
- `List(ctx, entity, entities, options)` - 列表查询
|
||||
- `Count(ctx, entity, options)` - 计数查询
|
||||
- `FindWhere(ctx, entities, condition, args...)` - 条件查询
|
||||
- `FindOne(ctx, entity, condition, args...)` - 单个查询
|
||||
- `CountWhere(ctx, entity, condition, args...)` - 条件计数
|
||||
- `ExistsWhere(ctx, entity, condition, args...)` - 条件存在
|
||||
|
||||
### 软删除
|
||||
- `SoftDelete(ctx, id, entity)` - 软删除
|
||||
- `Restore(ctx, id, entity)` - 恢复
|
||||
|
||||
### 事务辅助
|
||||
- `ExecuteInTransaction(ctx, fn)` - 执行事务
|
||||
- `IsInTransaction(ctx)` - 检查事务状态
|
||||
|
||||
## 优势
|
||||
|
||||
1. **统一事务处理**:所有Repository自动支持事务
|
||||
2. **减少代码重复**:移除重复的getDB方法和基础CRUD实现
|
||||
3. **提高可维护性**:统一的事务逻辑在一个地方维护
|
||||
4. **类型安全**:编译时检查,减少运行时错误
|
||||
5. **更清晰的职责**:Repository专注于业务逻辑,基础功能由BaseRepository提供
|
||||
6. **完整的接口支持**:自动实现Repository[T]和BaseRepository的所有方法
|
||||
|
||||
## 迁移检查清单
|
||||
|
||||
- [ ] 修改结构体定义,嵌入 BaseRepositoryImpl
|
||||
- [ ] 更新构造函数
|
||||
- [ ] 删除 getDB 方法
|
||||
- [ ] 实现 Repository[T] 接口的所有方法
|
||||
- [ ] 实现 BaseRepository 接口的所有方法
|
||||
- [ ] 使用辅助方法替换重复的查询逻辑
|
||||
- [ ] 为特殊需求重写搜索逻辑
|
||||
- [ ] 运行测试确保功能正常
|
||||
- [ ] 更新相关文档
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **方法参数**:BaseRepositoryImpl的方法使用`interface{}`,需要传递指针
|
||||
2. **搜索逻辑**:默认搜索逻辑可能不适合所有实体,需要重写
|
||||
3. **预加载**:使用ListOptions的Include字段或直接调用GetDB()
|
||||
4. **事务支持**:所有方法自动支持事务,无需额外处理
|
||||
5. **日志记录**:使用GetLogger()而不是直接访问logger字段
|
||||
@@ -1,23 +0,0 @@
|
||||
产品编号,产品名称,成本价
|
||||
COMB298Y,人事背调组合包,
|
||||
COMB86PM,海之源科技定制组合包,
|
||||
COMB92KS,小微企业哈密定制,
|
||||
COMBHZY2,海之源定制大数据组合报告,
|
||||
COMBQN10,全能个人大数据报告(标准版),
|
||||
COMBQN11,全能婚恋风险报告(标准版),
|
||||
COMBQN12,全能入职背调报告(标准版),
|
||||
COMBQN13,全能小微企业报告(标准版),
|
||||
COMBQN14,全能家政风险报告(标准版),
|
||||
COMBQN15,全能消金报告(标准版),
|
||||
COMBTY11,天远个人风险报告(专业版),
|
||||
COMBTY12,天远婚恋风险报告(专业版),
|
||||
COMBTY13,天远入职背调报告(专业版),
|
||||
COMBTY14,天远老板企业报告(专业版),
|
||||
COMBTY15,天远家政风险报告(专业版),
|
||||
COMBTY16,天远贷前风险报告(专业版),
|
||||
COMENT01,企业风险报告(专业版),
|
||||
IVYZ4E8B,单人婚姻状态C,
|
||||
JRZQ8A2D,特殊名单验证B,
|
||||
QYGL23T7,企业法人四要素高级版,
|
||||
QYGL3F8E,人企关系加强版,
|
||||
YYSY9E4A,手机号码归属地,
|
||||
|
@@ -1,13 +0,0 @@
|
||||
-- 设置时区为北京时间
|
||||
ALTER SYSTEM SET timezone = 'Asia/Shanghai';
|
||||
|
||||
ALTER SYSTEM SET log_timezone = 'Asia/Shanghai';
|
||||
|
||||
-- 重新加载配置
|
||||
SELECT pg_reload_conf ();
|
||||
|
||||
-- 验证时区设置
|
||||
SELECT name, setting
|
||||
FROM pg_settings
|
||||
WHERE
|
||||
name IN ('timezone', 'log_timezone');
|
||||
@@ -1,67 +0,0 @@
|
||||
@echo off
|
||||
REM 缓存测试脚本 (Windows版本)
|
||||
REM 使用方法: scripts\test_cache.bat [base_url]
|
||||
REM 默认base_url: http://localhost:8080
|
||||
|
||||
set BASE_URL=%1
|
||||
if "%BASE_URL%"=="" set BASE_URL=http://localhost:8080
|
||||
set API_BASE=%BASE_URL%/api/cache-test
|
||||
|
||||
echo 🧪 开始缓存系统测试...
|
||||
echo 📍 测试地址: %BASE_URL%
|
||||
echo.
|
||||
|
||||
REM 测试函数
|
||||
:test_endpoint
|
||||
set method=%1
|
||||
set endpoint=%2
|
||||
set description=%3
|
||||
|
||||
echo 🔍 测试 %description%...
|
||||
|
||||
if "%method%"=="GET" (
|
||||
curl -s "%API_BASE%%endpoint%"
|
||||
) else if "%method%"=="POST" (
|
||||
curl -s -X POST "%API_BASE%%endpoint%"
|
||||
) else if "%method%"=="DELETE" (
|
||||
curl -s -X DELETE "%API_BASE%%endpoint%"
|
||||
)
|
||||
|
||||
if %errorlevel% equ 0 (
|
||||
echo ✅ 成功
|
||||
) else (
|
||||
echo ❌ 失败
|
||||
)
|
||||
echo.
|
||||
goto :eof
|
||||
|
||||
REM 1. 测试缓存统计
|
||||
call :test_endpoint GET /stats "缓存统计"
|
||||
|
||||
REM 2. 测试基础缓存操作
|
||||
call :test_endpoint GET /test "基础缓存操作"
|
||||
|
||||
REM 3. 测试缓存键查询
|
||||
call :test_endpoint GET /keys/gorm_cache:* "缓存键查询"
|
||||
|
||||
REM 4. 测试性能测试
|
||||
call :test_endpoint POST /performance "缓存性能测试"
|
||||
|
||||
REM 5. 测试表缓存调试
|
||||
call :test_endpoint GET /table/users "用户表缓存调试"
|
||||
|
||||
echo 🎉 缓存测试完成!
|
||||
echo.
|
||||
echo 📋 测试结果说明:
|
||||
echo - 如果所有测试都返回200状态码,说明缓存系统正常工作
|
||||
echo - 如果某些测试失败,请检查应用是否正在运行
|
||||
echo - 查看应用日志获取更详细的调试信息
|
||||
echo.
|
||||
echo 🔧 手动测试命令:
|
||||
echo curl %API_BASE%/stats
|
||||
echo curl %API_BASE%/test
|
||||
echo curl %API_BASE%/keys/gorm_cache:*
|
||||
echo curl -X POST %API_BASE%/performance
|
||||
echo curl %API_BASE%/table/users
|
||||
|
||||
pause
|
||||
@@ -1,79 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 缓存测试脚本
|
||||
# 使用方法: ./scripts/test_cache.sh [base_url]
|
||||
# 默认base_url: http://localhost:8080
|
||||
|
||||
BASE_URL=${1:-http://localhost:8080}
|
||||
API_BASE="$BASE_URL/api/cache-test"
|
||||
|
||||
echo "🧪 开始缓存系统测试..."
|
||||
echo "📍 测试地址: $BASE_URL"
|
||||
echo ""
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 测试函数
|
||||
test_endpoint() {
|
||||
local method=$1
|
||||
local endpoint=$2
|
||||
local description=$3
|
||||
|
||||
echo -n "🔍 测试 $description... "
|
||||
|
||||
if [ "$method" = "GET" ]; then
|
||||
response=$(curl -s -w "%{http_code}" "$API_BASE$endpoint")
|
||||
elif [ "$method" = "POST" ]; then
|
||||
response=$(curl -s -w "%{http_code}" -X POST "$API_BASE$endpoint")
|
||||
elif [ "$method" = "DELETE" ]; then
|
||||
response=$(curl -s -w "%{http_code}" -X DELETE "$API_BASE$endpoint")
|
||||
fi
|
||||
|
||||
# 提取HTTP状态码
|
||||
http_code="${response: -3}"
|
||||
# 提取响应体
|
||||
body="${response%???}"
|
||||
|
||||
if [ "$http_code" = "200" ]; then
|
||||
echo -e "${GREEN}✅ 成功${NC}"
|
||||
echo " 响应: $body" | head -c 100
|
||||
echo "..."
|
||||
else
|
||||
echo -e "${RED}❌ 失败 (HTTP $http_code)${NC}"
|
||||
echo " 错误: $body"
|
||||
fi
|
||||
echo ""
|
||||
}
|
||||
|
||||
# 1. 测试缓存统计
|
||||
test_endpoint "GET" "/stats" "缓存统计"
|
||||
|
||||
# 2. 测试基础缓存操作
|
||||
test_endpoint "GET" "/test" "基础缓存操作"
|
||||
|
||||
# 3. 测试缓存键查询
|
||||
test_endpoint "GET" "/keys/gorm_cache:*" "缓存键查询"
|
||||
|
||||
# 4. 测试性能测试
|
||||
test_endpoint "POST" "/performance" "缓存性能测试"
|
||||
|
||||
# 5. 测试表缓存调试
|
||||
test_endpoint "GET" "/table/users" "用户表缓存调试"
|
||||
|
||||
echo "🎉 缓存测试完成!"
|
||||
echo ""
|
||||
echo "📋 测试结果说明:"
|
||||
echo " - 如果所有测试都返回200状态码,说明缓存系统正常工作"
|
||||
echo " - 如果某些测试失败,请检查应用是否正在运行"
|
||||
echo " - 查看应用日志获取更详细的调试信息"
|
||||
echo ""
|
||||
echo "🔧 手动测试命令:"
|
||||
echo " curl $API_BASE/stats"
|
||||
echo " curl $API_BASE/test"
|
||||
echo " curl $API_BASE/keys/gorm_cache:*"
|
||||
echo " curl -X POST $API_BASE/performance"
|
||||
echo " curl $API_BASE/table/users"
|
||||
@@ -1,160 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
"gorm.io/gorm/schema"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 连接数据库
|
||||
db, err := connectDB()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "连接数据库失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// 读取 CSV 文件
|
||||
csvFile, err := os.Open("成本价.csv")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "打开 CSV 文件失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer csvFile.Close()
|
||||
|
||||
reader := csv.NewReader(csvFile)
|
||||
reader.LazyQuotes = true
|
||||
reader.TrimLeadingSpace = true
|
||||
|
||||
// 读取所有记录
|
||||
records, err := reader.ReadAll()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "读取 CSV 文件失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(records) < 2 {
|
||||
fmt.Fprintf(os.Stderr, "CSV 文件数据不足(需要至少包含表头和数据行)\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
successCount := 0
|
||||
failCount := 0
|
||||
skipCount := 0
|
||||
|
||||
fmt.Printf("开始更新成本价...\n")
|
||||
fmt.Printf("共 %d 条记录(包含表头)\n\n", len(records))
|
||||
|
||||
// 从第二行开始处理(跳过表头)
|
||||
for i := 1; i < len(records); i++ {
|
||||
record := records[i]
|
||||
if len(record) < 7 {
|
||||
fmt.Printf("第 %d 行数据列数不足,跳过\n", i+1)
|
||||
skipCount++
|
||||
continue
|
||||
}
|
||||
|
||||
productCode := strings.TrimSpace(record[0])
|
||||
costPriceStr := strings.TrimSpace(record[6]) // 成本价在第7列(索引6)
|
||||
|
||||
// 跳过产品编号为空的行
|
||||
if productCode == "" {
|
||||
fmt.Printf("第 %d 行产品编号为空,跳过\n", i+1)
|
||||
skipCount++
|
||||
continue
|
||||
}
|
||||
|
||||
// 如果成本价为空,跳过(不更新)
|
||||
if costPriceStr == "" {
|
||||
fmt.Printf("产品 %s: 成本价为空,跳过\n", productCode)
|
||||
skipCount++
|
||||
continue
|
||||
}
|
||||
|
||||
// 解析成本价为浮点数
|
||||
costPrice, err := strconv.ParseFloat(costPriceStr, 64)
|
||||
if err != nil {
|
||||
fmt.Printf("产品 %s: 成本价格式错误 (%s),跳过: %v\n", productCode, costPriceStr, err)
|
||||
skipCount++
|
||||
continue
|
||||
}
|
||||
|
||||
// 更新数据库
|
||||
result := db.WithContext(ctx).
|
||||
Table("product").
|
||||
Where("code = ? AND deleted_at IS NULL", productCode).
|
||||
Update("cost_price", costPrice)
|
||||
|
||||
if result.Error != nil {
|
||||
// 如果单数表名失败,尝试复数表名
|
||||
if strings.Contains(result.Error.Error(), "does not exist") {
|
||||
result = db.WithContext(ctx).
|
||||
Table("products").
|
||||
Where("code = ? AND deleted_at IS NULL", productCode).
|
||||
Update("cost_price", costPrice)
|
||||
}
|
||||
|
||||
if result.Error != nil {
|
||||
fmt.Printf("产品 %s: 更新失败 - %v\n", productCode, result.Error)
|
||||
failCount++
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if result.RowsAffected == 0 {
|
||||
fmt.Printf("产品 %s: 未找到匹配的记录\n", productCode)
|
||||
failCount++
|
||||
} else {
|
||||
fmt.Printf("产品 %s: 成功更新成本价为 %.2f (影响 %d 行)\n", productCode, costPrice, result.RowsAffected)
|
||||
successCount++
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("\n=== 更新完成 ===\n")
|
||||
fmt.Printf("成功更新: %d 条\n", successCount)
|
||||
fmt.Printf("更新失败: %d 条\n", failCount)
|
||||
fmt.Printf("跳过记录: %d 条\n", skipCount)
|
||||
fmt.Printf("总计处理: %d 条\n", len(records)-1)
|
||||
}
|
||||
|
||||
// connectDB 连接数据库
|
||||
func connectDB() (*gorm.DB, error) {
|
||||
// 数据库连接配置
|
||||
dsn := "host=1.117.67.95 user=tyapi_user password=Pg9mX4kL8nW2rT5y dbname=tyapi port=25010 sslmode=disable TimeZone=Asia/Shanghai"
|
||||
|
||||
// 配置GORM,使用单数表名(与项目配置一致)
|
||||
gormConfig := &gorm.Config{
|
||||
NamingStrategy: schema.NamingStrategy{
|
||||
SingularTable: true, // 使用单数表名
|
||||
},
|
||||
Logger: logger.Default.LogMode(logger.Info), // 显示 SQL 日志
|
||||
}
|
||||
|
||||
db, err := gorm.Open(postgres.Open(dsn), gormConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("连接数据库失败: %w", err)
|
||||
}
|
||||
|
||||
// 测试连接
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取数据库实例失败: %w", err)
|
||||
}
|
||||
|
||||
if err := sqlDB.Ping(); err != nil {
|
||||
return nil, fmt.Errorf("数据库连接测试失败: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("数据库连接成功")
|
||||
return db, nil
|
||||
}
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
产品编号,产品名称,分类,价格,数据源,数据源编号,成本价
|
||||
DWBG6A2C,司南报告服务,多维报告,10.8,安徽智查,ZCI102,2.4
|
||||
DWBG8B4D,谛听多维报告,多维报告,10.8,安徽智查,ZCI103,2.1
|
||||
FLXG2E8F,司法核验报告,风险管控,5,安徽智查,ZCI101,1.2
|
||||
FLXG5A3B,个人司法涉诉B,风险管控,2.2,安徽智查,ZCI006,0.42
|
||||
FLXG8B4D,涉赌涉诈风险评估,风险管控,1.8,安徽智查,ZCI027,0.3
|
||||
FLXG9C1D,法院信息详情高级版,风险管控,1.5,安徽智查,ZCI007,0.23
|
||||
FLXGDEA8,公安不良人员名单,风险管控,2,安徽智查,ZCI028,0.45
|
||||
FLXGDEA9,公安不良人员名单(加强版),风险管控,2.5,安徽智查,ZCI005,0.45
|
||||
IVYZ2A8B,身份二要素认证,身份验证,0.25,安徽智查,ZCI001,0.05
|
||||
IVYZ5E3F,单人婚姻状态B,身份验证,2.5,安徽智查,ZCI029,0.55
|
||||
IVYZ7C9D,人脸识别验证,身份验证,0.3,安徽智查,ZCI013,0.3
|
||||
IVYZ7F3A,学历信息查询B,身份验证,4.5,安徽智查,ZCI035,2.3
|
||||
JRZQ09J8,收入评估(社保评级),金融验证,3.5,安徽智查,ZCI031,0.83
|
||||
JRZQ1D09,3C租赁申请意向,金融验证,3,安徽智查,ZCI020,0.23
|
||||
JRZQ3C7B,借贷意向验证B,金融验证,2.5,安徽智查,ZCI017,0.33
|
||||
JRZQ4B6C,探针C风险评估,金融验证,2,安徽智查,ZCI023,0.5
|
||||
JRZQ5E9F,借选指数评估,金融验证,3,安徽智查,ZCI021,0.38
|
||||
JRZQ7F1A,全景雷达,金融验证,3.5,安徽智查,ZCI008,0.6
|
||||
JRZQ8A2D,特殊名单验证B,金融验证,2,安徽智查,ZCI018,0
|
||||
QCXG9P1C,名下车辆详版,汽车相关,3.8,安徽智查,ZCI051,1.6
|
||||
YYSY3E7F,空号检测服务,运营商验证,0.2,安徽智查,ZCI010,0.055
|
||||
YYSY4F2E,运营商三要素验证(详版),运营商验证,0.35,安徽智查,ZCI002,0.16
|
||||
YYSY6D9A,全网手机号状态验证,运营商验证,0.6,安徽智查,ZCI030,0.035
|
||||
YYSY8B1C,手机在网时长B,运营商验证,0.3,安徽智查,ZCI003,0.1
|
||||
YYSY9E4A,手机号码归属地,运营商验证,0.3,安徽智查,ZCI026,0
|
||||
FLXG0687,反赌反诈,风险管控,1.8,羽山数据,RIS031,0.3
|
||||
FLXGBC21,手机号码特别风险,风险管控,2,羽山数据,MOB032,0.1
|
||||
QCXG7A2B,名下车辆,汽车相关,2,羽山数据,CAR061,1.6
|
||||
FLXG0V3B,个人不良核验(标准版),风险管控,3,西部数据,G34BJ03,0.8
|
||||
FLXG0V4B,个人司法涉诉,风险管控,2.5,西部数据,G22SC01,0.5
|
||||
FLXG162A,团伙欺诈评估,风险管控,2.5,西部数据,G32BJ05,0.7
|
||||
FLXG3D56,特殊名单验证,金融验证,2.5,西部数据,G26BJ05,0.2
|
||||
FLXG54F5,手机号码风险,风险管控,3,西部数据,G03HZ01,0.55
|
||||
FLXG5876,易诉人识别,风险管控,2,西部数据,G03XM02,0.6
|
||||
FLXG5B2E,自然人限高信息,风险管控,2,西部数据,G36SC01,0.5
|
||||
FLXG75FE,涉网风险,风险管控,2,西部数据,FLXG75FE,0.4
|
||||
FLXG8A3F,自然人失信信息,风险管控,2,西部数据,G37SC01,0.5
|
||||
FLXG9687,电诈风险预警,风险管控,1,西部数据,G31BJ05,0.4
|
||||
FLXG970F,风险人员核验,风险管控,2,西部数据,WEST00028,0.35
|
||||
FLXGC9D1,黑灰产等级,风险管控,3,西部数据,G30BJ05,0.2
|
||||
FLXGCA3D,个人综合涉诉,风险管控,2.5,西部数据,G22BJ03,0.5
|
||||
FLXGDEC7,个人不良核验,风险管控,3,西部数据,G23BJ03,0.8
|
||||
IVYZ0B03,二要素验证(姓名、手机号),身份验证,0.3,西部数据,G17BJ02,0.29
|
||||
IVYZ1C9D,,身份验证,,西部数据,G38SC02,
|
||||
IVYZ2125,活体+人像核验组件,身份验证,0.3,西部数据,IVYZ2125,0.3
|
||||
IVYZ385E,自然人生存状态标识,身份验证,1.5,西部数据,WEST00020,0.3
|
||||
IVYZ4E8B,单人婚姻状态C,身份验证,2.5,西部数据,G09GZ02,
|
||||
IVYZ5733,单人婚姻状态A,身份验证,2.5,西部数据,G09GZ02,1
|
||||
IVYZ7F2A,双人婚姻状态B,身份验证,2.5,西部数据,G10GZ02,0.6
|
||||
IVYZ81NC,单人婚姻查询(登记时间版),身份验证,4.5,西部数据,G09XM02,1
|
||||
IVYZ9363,双人婚姻状态A,身份验证,2.5,西部数据,G10XM02,1
|
||||
IVYZ9A2B,学历信息查询A,身份验证,5,西部数据,G11BJ06,3
|
||||
IVYZADEE,身份证三要素比对,身份验证,0.3,西部数据,IVYZADEE,0.2
|
||||
IVYZGZ08,,身份验证,,西部数据,G08SC02,
|
||||
JRZQ0A03,借贷意向验证,金融验证,2.5,西部数据,G27BJ05,0.6
|
||||
JRZQ4AA8,偿债压力指数,金融验证,3,西部数据,G29BJ05,0.6
|
||||
JRZQ8203,借贷行为验证,金融验证,3,西部数据,G28BJ05,1
|
||||
JRZQDCBE,银行卡四要素验证,金融验证,0.4,西部数据,G20GZ01,0.3
|
||||
QYGL2ACD,企业三要素核验,企业相关,0.2,西部数据,WEST00022,0.1
|
||||
QYGL45BD,企业法人四要素核验,企业相关,0.3,西部数据,WEST00021,0.25
|
||||
QYGL6F2D,人企关联,企业相关,3,西部数据,G05XM02,0.9
|
||||
QYGL8261,企业综合涉诉,企业相关,2.5,西部数据,Q03BJ03,0.5
|
||||
QYGL8271,企业司法涉诉(详版),企业相关,2.5,西部数据,Q03SC01,0.5
|
||||
QYGLB4C0,股东人企关系精准版,企业相关,3,西部数据,G05HZ01,0.6
|
||||
YYSY09CD,运营商三要素验证(简版),运营商验证,0.3,西部数据,G16BJ02,0.3
|
||||
YYSY4B21,手机在网状态,运营商验证,0.5,西部数据,G25BJ02,0.055
|
||||
YYSY4B37,手机在网时长A,运营商验证,0.3,西部数据,G02BJ02,0.2
|
||||
YYSY6F2E,运营商三要素核验(高级版),运营商验证,0.4,西部数据,G15BJ02,0.35
|
||||
YYSYD50F,二要素核验(手机号、身份证号),运营商验证,0.35,西部数据,G18BJ02,0.29
|
||||
YYSYF7DB,手机二次卡,运营商验证,0.3,西部数据,G19BJ02,0.2
|
||||
DWBG7F3A,多头借贷行业风险版,金融验证,2.5,四川星维,CDJ-1101695406546284544,0.45
|
||||
FLXG7E8F,个人司法涉诉查询,风险管控,2,四川星维,CDJ-1101695378264092672,0.36
|
||||
IVYZ3A7F,学历信息查询(学校名称版),身份验证,5,四川星维,CDJ-1104648854749245440,3
|
||||
IVYZ6G7H,单人婚姻状态(补证版),身份验证,3.5,四川星维,CDJ-1104646268587536384,0.7
|
||||
IVYZ8I9J,互联网行为推测,身份验证,1.8,四川星维,CDJ-1074522823015198720,0.6
|
||||
IVYZ9D2E,,身份验证,,四川星维,CDJ-1104648845446279168,2.2
|
||||
JRZQ0L85,个人信用分,金融验证,1.5,四川星维,CDJ-1101695364016041984,0.38
|
||||
JRZQ6F2A,借贷意向验证A,金融验证,2,四川星维,CDJ-1101695369065984000,0.25
|
||||
JRZQ8B3C,个人消费能力等级,金融验证,3,四川星维,CDJ-1101695392528920576,0.34
|
||||
JRZQ9D4E,多头借贷小时级,金融验证,2.5,四川星维,CDJ-1118085532960616448,0.4
|
||||
JRZQ9E2A,多头借贷风险信息查询,金融验证,3,四川星维,CDJ-1068350101688086528,0.6
|
||||
QYGL5F6A,名下企业关联,企业相关,2.8,四川星维,CDJ-1101695397213958144,0.44
|
||||
YYSY7D3E,携号转网查询,运营商验证,0.3,四川星维,CDJ-1100244706893164544,0.02
|
||||
YYSY8C2D,运营商三要素(新详版),运营商验证,0.35,四川星维,CDJ-1100244702166183936,0.19
|
||||
YYSY8F3A,,运营商验证,,四川星维,CDJ-1100244697766359040,0.14
|
||||
YYSY9A1B,运营商三要素验证(简版),运营商验证,0.3,四川星维,CDJ-1100244697766359040,0.14
|
||||
QYGL4B2E,,企业相关,,天眼查,TaxContravention,
|
||||
QYGL5A3C,对外投资历史,企业相关,0.5,天眼查,InvestHistory,0.1
|
||||
QYGL7C1A,经营异常,企业相关,0.5,天眼查,AbnormalInfo,0.15
|
||||
QYGL7D9A,,企业相关,,天眼查,OwnTax,
|
||||
QYGL8B4D,融资历史,企业相关,0.5,天眼查,FinancingHistory,0.1
|
||||
QYGL9E2F,行政处罚,企业相关,0.5,天眼查,PunishmentInfo,0.15
|
||||
QYGL23T7,企业法人四要素高级版,企业相关,0.3,阿里云,check,
|
||||
YYSYBE08,二要素核验(姓名、身份证号),运营商验证,0.25,阿里云,check,0.03
|
||||
IVYZ3P9M,学历信息查询(实时版),身份验证,5,木子数据,PC0041,1.72
|
||||
COMENT01,企业风险报告(专业版),组合包,30,内部处理,,
|
||||
QYGL3F8E,人企关系加强版,企业相关,10.8,内部处理,,
|
||||
|
Reference in New Issue
Block a user