Compare commits

...

2 Commits

Author SHA1 Message Date
11fe48809e Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2025-11-26 18:23:13 +08:00
785818f73d 18278715334@163.com 2025-11-26 18:23:02 +08:00
44 changed files with 533 additions and 2565 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -58,7 +58,8 @@ services:
depends_on: depends_on:
redis: redis:
condition: service_healthy condition: service_healthy
restart: unless-stopped restart:
unless-stopped
# Jaeger 链路追踪 # Jaeger 链路追踪
jaeger: jaeger:

View File

@@ -965,7 +965,7 @@ func (s *ProductApplicationServiceImpl) getDTOMap() map[string]interface{} {
"JRZQ0A03": &dto.JRZQ0A03Req{}, "JRZQ0A03": &dto.JRZQ0A03Req{},
"JRZQ4AA8": &dto.JRZQ4AA8Req{}, "JRZQ4AA8": &dto.JRZQ4AA8Req{},
"JRZQ8203": &dto.JRZQ8203Req{}, "JRZQ8203": &dto.JRZQ8203Req{},
"JRZQDBCE": &dto.JRZQDBCEReq{}, "JRZQDBCE": &dto.JRZQDCBEReq{},
"QYGL2ACD": &dto.QYGL2ACDReq{}, "QYGL2ACD": &dto.QYGL2ACDReq{},
"QYGL6F2D": &dto.QYGL6F2DReq{}, "QYGL6F2D": &dto.QYGL6F2DReq{},
"QYGL45BD": &dto.QYGL45BDReq{}, "QYGL45BD": &dto.QYGL45BDReq{},

View File

@@ -94,7 +94,7 @@ type JRZQ8203Req struct {
IDCard string `json:"id_card" validate:"required,validIDCard"` IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"` Name string `json:"name" validate:"required,min=1,validName"`
} }
type JRZQDBCEReq struct { type JRZQDCBEReq struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"` MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
IDCard string `json:"id_card" validate:"required,validIDCard"` IDCard string `json:"id_card" validate:"required,validIDCard"`
BankCard string `json:"bank_card" validate:"required,validBankCard"` BankCard string `json:"bank_card" validate:"required,validBankCard"`
@@ -311,6 +311,12 @@ type IVYZ9D2EReq struct {
UseScenario string `json:"use_scenario" validate:"required,oneof=1 2 3 4 99"` 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 行为数据查询请求参数 // DWBG7F3AReq 行为数据查询请求参数
type DWBG7F3AReq struct { type DWBG7F3AReq struct {
Name string `json:"name" validate:"required,min=1,validName"` 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"` 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 { type YYSY8B1CReq struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"` MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
} }
@@ -394,6 +409,12 @@ type JRZQ3C7BReq struct {
Name string `json:"name" validate:"required,min=1,validName"` Name string `json:"name" validate:"required,min=1,validName"`
Authorized string `json:"authorized" validate:"required,oneof=0 1"` Authorized string `json:"authorized" validate:"required,oneof=0 1"`
} }
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 { type JRZQ8A2DReq struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"` MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
@@ -404,9 +425,32 @@ type JRZQ8A2DReq struct {
// YYSY8F3AReq 行为数据查询请求参数 // YYSY8F3AReq 行为数据查询请求参数
type YYSY8F3AReq struct { 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"` Name string `json:"name" validate:"required,min=1,validName"`
IDCard string `json:"id_card" validate:"required,validIDCard"` IDCard string `json:"id_card" validate:"required,validIDCard"`
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"` 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 { type JRZQ5E9FReq struct {

View File

@@ -125,7 +125,9 @@ func registerAllProcessors(combService *comb.CombService) {
"JRZQ0L85": jrzq.ProcessJRZQ0L85Request, "JRZQ0L85": jrzq.ProcessJRZQ0L85Request,
"JRZQ2F8A": jrzq.ProcessJRZQ2F8ARequest, "JRZQ2F8A": jrzq.ProcessJRZQ2F8ARequest,
"JRZQ1E7B": jrzq.ProcessJRZQ1E7BRequest, "JRZQ1E7B": jrzq.ProcessJRZQ1E7BRequest,
"JRZQ3C9R": jrzq.ProcessJRZQ3C9RRequest,
"JRZQ0B6Y": jrzq.ProcessJRZQ0B6YRequest,
"JRZQ9A1W": jrzq.ProcessJRZQ9A1WRequest,
// QYGL系列处理器 // QYGL系列处理器
"QYGL8261": qygl.ProcessQYGL8261Request, "QYGL8261": qygl.ProcessQYGL8261Request,
"QYGL2ACD": qygl.ProcessQYGL2ACDRequest, "QYGL2ACD": qygl.ProcessQYGL2ACDRequest,
@@ -144,6 +146,7 @@ func registerAllProcessors(combService *comb.CombService) {
"COMENT01": qygl.ProcessCOMENT01Request, // 企业风险报告 "COMENT01": qygl.ProcessCOMENT01Request, // 企业风险报告
"QYGL5F6A": qygl.ProcessQYGL5F6ARequest, // 企业相关查询 "QYGL5F6A": qygl.ProcessQYGL5F6ARequest, // 企业相关查询
"QYGL2B5C": qygl.ProcessQYGL2B5CRequest, // 企业联系人实际经营地址 "QYGL2B5C": qygl.ProcessQYGL2B5CRequest, // 企业联系人实际经营地址
"QYGL6S1B": qygl.ProcessQYGL6S1BRequest, //董监高司法综合信息核验
// YYSY系列处理器 // YYSY系列处理器
"YYSYD50F": yysy.ProcessYYSYD50FRequest, "YYSYD50F": yysy.ProcessYYSYD50FRequest,
@@ -162,6 +165,8 @@ func registerAllProcessors(combService *comb.CombService) {
"YYSY8C2D": yysy.ProcessYYSY8C2DRequest, "YYSY8C2D": yysy.ProcessYYSY8C2DRequest,
"YYSY7D3E": yysy.ProcessYYSY7D3ERequest, "YYSY7D3E": yysy.ProcessYYSY7D3ERequest,
"YYSY9E4A": yysy.ProcessYYSY9E4ARequest, "YYSY9E4A": yysy.ProcessYYSY9E4ARequest,
"YYSY9F1B": yysy.ProcessYYSY9F1BYequest,
"YYSY6F2B": yysy.ProcessYYSY6F2BRequest,
// IVYZ系列处理器 // IVYZ系列处理器
"IVYZ0B03": ivyz.ProcessIVYZ0B03Request, "IVYZ0B03": ivyz.ProcessIVYZ0B03Request,
@@ -186,6 +191,7 @@ func registerAllProcessors(combService *comb.CombService) {
"IVYZ6G7H": ivyz.ProcessIVYZ6G7HRequest, "IVYZ6G7H": ivyz.ProcessIVYZ6G7HRequest,
"IVYZ8I9J": ivyz.ProcessIVYZ8I9JRequest, "IVYZ8I9J": ivyz.ProcessIVYZ8I9JRequest,
"IVYZ9K2L": ivyz.ProcessIVYZ9K2LRequest, "IVYZ9K2L": ivyz.ProcessIVYZ9K2LRequest,
"IVYZ2C1P": ivyz.ProcessIVYZ2C1PRequest,
// COMB系列处理器 - 只注册有自定义逻辑的组合包 // COMB系列处理器 - 只注册有自定义逻辑的组合包
"COMB86PM": comb.ProcessCOMB86PMRequest, // 有自定义逻辑重命名ApiCode "COMB86PM": comb.ProcessCOMB86PMRequest, // 有自定义逻辑重命名ApiCode

View File

@@ -96,7 +96,7 @@ func (s *FormConfigServiceImpl) getDTOStruct(ctx context.Context, apiCode string
"JRZQ0A03": &dto.JRZQ0A03Req{}, "JRZQ0A03": &dto.JRZQ0A03Req{},
"JRZQ4AA8": &dto.JRZQ4AA8Req{}, "JRZQ4AA8": &dto.JRZQ4AA8Req{},
"JRZQ8203": &dto.JRZQ8203Req{}, "JRZQ8203": &dto.JRZQ8203Req{},
"JRZQDBCE": &dto.JRZQDBCEReq{}, "JRZQDCBE": &dto.JRZQDCBEReq{},
"QYGL2ACD": &dto.QYGL2ACDReq{}, "QYGL2ACD": &dto.QYGL2ACDReq{},
"QYGL6F2D": &dto.QYGL6F2DReq{}, "QYGL6F2D": &dto.QYGL6F2DReq{},
"QYGL45BD": &dto.QYGL45BDReq{}, "QYGL45BD": &dto.QYGL45BDReq{},
@@ -178,6 +178,13 @@ func (s *FormConfigServiceImpl) getDTOStruct(ctx context.Context, apiCode string
"QYGL2B5C": &dto.QYGL2B5CReq{}, "QYGL2B5C": &dto.QYGL2B5CReq{},
"JRZQ2F8A": &dto.JRZQ2F8AReq{}, "JRZQ2F8A": &dto.JRZQ2F8AReq{},
"JRZQ1E7B": &dto.JRZQ1E7BReq{}, "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 // 优先返回已配置的DTO

View File

@@ -13,6 +13,7 @@ import (
// ProcessDWBG8B4DRequest DWBG8B4D API处理方法 - 谛听多维报告 // ProcessDWBG8B4DRequest DWBG8B4D API处理方法 - 谛听多维报告
func ProcessDWBG8B4DRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) { func ProcessDWBG8B4DRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.DWBG8B4DReq var paramsDto dto.DWBG8B4DReq
if err := json.Unmarshal(params, &paramsDto); err != nil { if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err) return nil, errors.Join(processors.ErrSystem, err)
} }

View File

@@ -0,0 +1,56 @@
package ivyz
import (
"context"
"encoding/json"
"errors"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/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, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
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
}

View File

@@ -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, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
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
}

View File

@@ -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, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
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
}

View File

@@ -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, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
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
}

View File

@@ -12,7 +12,7 @@ import (
// ProcessJRZQDCBERequest JRZQDCBE API处理方法 // ProcessJRZQDCBERequest JRZQDCBE API处理方法
func ProcessJRZQDCBERequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) { 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, &paramsDto); err != nil { if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err) return nil, errors.Join(processors.ErrSystem, err)
} }

View File

@@ -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, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
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
}

View File

@@ -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, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
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
}

View File

@@ -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, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
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
}

View File

@@ -56,6 +56,7 @@ type TianYanChaResponse struct {
Reason string `json:"reason"` Reason string `json:"reason"`
Result interface{} `json:"result"` Result interface{} `json:"result"`
} }
// NewTianYanChaService 创建天眼查服务实例 // NewTianYanChaService 创建天眼查服务实例
func NewTianYanChaService(baseURL, token string, timeout time.Duration) *TianYanChaService { func NewTianYanChaService(baseURL, token string, timeout time.Duration) *TianYanChaService {
if timeout == 0 { if timeout == 0 {
@@ -118,8 +119,7 @@ func (t *TianYanChaService) CallAPI(ctx context.Context, apiCode string, params
isTimeout = true isTimeout = true
} else if netErr, ok := err.(interface{ Timeout() bool }); ok && netErr.Timeout() { } else if netErr, ok := err.(interface{ Timeout() bool }); ok && netErr.Timeout() {
isTimeout = true isTimeout = true
} else if errStr := err.Error(); } else if errStr := err.Error(); errStr == "context deadline exceeded" ||
errStr == "context deadline exceeded" ||
errStr == "timeout" || errStr == "timeout" ||
errStr == "Client.Timeout exceeded" || errStr == "Client.Timeout exceeded" ||
errStr == "net/http: request canceled" { errStr == "net/http: request canceled" {

View File

@@ -131,8 +131,7 @@ func (y *YushanService) CallAPI(ctx context.Context, code string, params map[str
isTimeout = true isTimeout = true
} else if netErr, ok := err.(interface{ Timeout() bool }); ok && netErr.Timeout() { } else if netErr, ok := err.(interface{ Timeout() bool }); ok && netErr.Timeout() {
isTimeout = true isTimeout = true
} else if errStr := err.Error(); } else if errStr := err.Error(); errStr == "context deadline exceeded" ||
errStr == "context deadline exceeded" ||
errStr == "timeout" || errStr == "timeout" ||
errStr == "Client.Timeout exceeded" || errStr == "Client.Timeout exceeded" ||
errStr == "net/http: request canceled" { errStr == "net/http: request canceled" {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 产品编号 产品名称 成本价
2 COMB298Y 人事背调组合包
3 COMB86PM 海之源科技定制组合包
4 COMB92KS 小微企业哈密定制
5 COMBHZY2 海之源定制大数据组合报告
6 COMBQN10 全能个人大数据报告(标准版)
7 COMBQN11 全能婚恋风险报告(标准版)
8 COMBQN12 全能入职背调报告(标准版)
9 COMBQN13 全能小微企业报告(标准版)
10 COMBQN14 全能家政风险报告(标准版)
11 COMBQN15 全能消金报告(标准版)
12 COMBTY11 天远个人风险报告(专业版)
13 COMBTY12 天远婚恋风险报告(专业版)
14 COMBTY13 天远入职背调报告(专业版)
15 COMBTY14 天远老板企业报告(专业版)
16 COMBTY15 天远家政风险报告(专业版)
17 COMBTY16 天远贷前风险报告(专业版)
18 COMENT01 企业风险报告(专业版)
19 IVYZ4E8B 单人婚姻状态C
20 JRZQ8A2D 特殊名单验证B
21 QYGL23T7 企业法人四要素高级版
22 QYGL3F8E 人企关系加强版
23 YYSY9E4A 手机号码归属地

View File

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

View File

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

View File

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

View File

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

View File

@@ -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,内部处理,,
1 产品编号 产品名称 分类 价格 数据源 数据源编号 成本价
2 DWBG6A2C 司南报告服务 多维报告 10.8 安徽智查 ZCI102 2.4
3 DWBG8B4D 谛听多维报告 多维报告 10.8 安徽智查 ZCI103 2.1
4 FLXG2E8F 司法核验报告 风险管控 5 安徽智查 ZCI101 1.2
5 FLXG5A3B 个人司法涉诉B 风险管控 2.2 安徽智查 ZCI006 0.42
6 FLXG8B4D 涉赌涉诈风险评估 风险管控 1.8 安徽智查 ZCI027 0.3
7 FLXG9C1D 法院信息详情高级版 风险管控 1.5 安徽智查 ZCI007 0.23
8 FLXGDEA8 公安不良人员名单 风险管控 2 安徽智查 ZCI028 0.45
9 FLXGDEA9 公安不良人员名单(加强版) 风险管控 2.5 安徽智查 ZCI005 0.45
10 IVYZ2A8B 身份二要素认证 身份验证 0.25 安徽智查 ZCI001 0.05
11 IVYZ5E3F 单人婚姻状态B 身份验证 2.5 安徽智查 ZCI029 0.55
12 IVYZ7C9D 人脸识别验证 身份验证 0.3 安徽智查 ZCI013 0.3
13 IVYZ7F3A 学历信息查询B 身份验证 4.5 安徽智查 ZCI035 2.3
14 JRZQ09J8 收入评估(社保评级) 金融验证 3.5 安徽智查 ZCI031 0.83
15 JRZQ1D09 3C租赁申请意向 金融验证 3 安徽智查 ZCI020 0.23
16 JRZQ3C7B 借贷意向验证B 金融验证 2.5 安徽智查 ZCI017 0.33
17 JRZQ4B6C 探针C风险评估 金融验证 2 安徽智查 ZCI023 0.5
18 JRZQ5E9F 借选指数评估 金融验证 3 安徽智查 ZCI021 0.38
19 JRZQ7F1A 全景雷达 金融验证 3.5 安徽智查 ZCI008 0.6
20 JRZQ8A2D 特殊名单验证B 金融验证 2 安徽智查 ZCI018 0
21 QCXG9P1C 名下车辆详版 汽车相关 3.8 安徽智查 ZCI051 1.6
22 YYSY3E7F 空号检测服务 运营商验证 0.2 安徽智查 ZCI010 0.055
23 YYSY4F2E 运营商三要素验证(详版) 运营商验证 0.35 安徽智查 ZCI002 0.16
24 YYSY6D9A 全网手机号状态验证 运营商验证 0.6 安徽智查 ZCI030 0.035
25 YYSY8B1C 手机在网时长B 运营商验证 0.3 安徽智查 ZCI003 0.1
26 YYSY9E4A 手机号码归属地 运营商验证 0.3 安徽智查 ZCI026 0
27 FLXG0687 反赌反诈 风险管控 1.8 羽山数据 RIS031 0.3
28 FLXGBC21 手机号码特别风险 风险管控 2 羽山数据 MOB032 0.1
29 QCXG7A2B 名下车辆 汽车相关 2 羽山数据 CAR061 1.6
30 FLXG0V3B 个人不良核验(标准版) 风险管控 3 西部数据 G34BJ03 0.8
31 FLXG0V4B 个人司法涉诉 风险管控 2.5 西部数据 G22SC01 0.5
32 FLXG162A 团伙欺诈评估 风险管控 2.5 西部数据 G32BJ05 0.7
33 FLXG3D56 特殊名单验证 金融验证 2.5 西部数据 G26BJ05 0.2
34 FLXG54F5 手机号码风险 风险管控 3 西部数据 G03HZ01 0.55
35 FLXG5876 易诉人识别 风险管控 2 西部数据 G03XM02 0.6
36 FLXG5B2E 自然人限高信息 风险管控 2 西部数据 G36SC01 0.5
37 FLXG75FE 涉网风险 风险管控 2 西部数据 FLXG75FE 0.4
38 FLXG8A3F 自然人失信信息 风险管控 2 西部数据 G37SC01 0.5
39 FLXG9687 电诈风险预警 风险管控 1 西部数据 G31BJ05 0.4
40 FLXG970F 风险人员核验 风险管控 2 西部数据 WEST00028 0.35
41 FLXGC9D1 黑灰产等级 风险管控 3 西部数据 G30BJ05 0.2
42 FLXGCA3D 个人综合涉诉 风险管控 2.5 西部数据 G22BJ03 0.5
43 FLXGDEC7 个人不良核验 风险管控 3 西部数据 G23BJ03 0.8
44 IVYZ0B03 二要素验证(姓名、手机号) 身份验证 0.3 西部数据 G17BJ02 0.29
45 IVYZ1C9D 身份验证 西部数据 G38SC02
46 IVYZ2125 活体+人像核验组件 身份验证 0.3 西部数据 IVYZ2125 0.3
47 IVYZ385E 自然人生存状态标识 身份验证 1.5 西部数据 WEST00020 0.3
48 IVYZ4E8B 单人婚姻状态C 身份验证 2.5 西部数据 G09GZ02
49 IVYZ5733 单人婚姻状态A 身份验证 2.5 西部数据 G09GZ02 1
50 IVYZ7F2A 双人婚姻状态B 身份验证 2.5 西部数据 G10GZ02 0.6
51 IVYZ81NC 单人婚姻查询(登记时间版) 身份验证 4.5 西部数据 G09XM02 1
52 IVYZ9363 双人婚姻状态A 身份验证 2.5 西部数据 G10XM02 1
53 IVYZ9A2B 学历信息查询A 身份验证 5 西部数据 G11BJ06 3
54 IVYZADEE 身份证三要素比对 身份验证 0.3 西部数据 IVYZADEE 0.2
55 IVYZGZ08 身份验证 西部数据 G08SC02
56 JRZQ0A03 借贷意向验证 金融验证 2.5 西部数据 G27BJ05 0.6
57 JRZQ4AA8 偿债压力指数 金融验证 3 西部数据 G29BJ05 0.6
58 JRZQ8203 借贷行为验证 金融验证 3 西部数据 G28BJ05 1
59 JRZQDCBE 银行卡四要素验证 金融验证 0.4 西部数据 G20GZ01 0.3
60 QYGL2ACD 企业三要素核验 企业相关 0.2 西部数据 WEST00022 0.1
61 QYGL45BD 企业法人四要素核验 企业相关 0.3 西部数据 WEST00021 0.25
62 QYGL6F2D 人企关联 企业相关 3 西部数据 G05XM02 0.9
63 QYGL8261 企业综合涉诉 企业相关 2.5 西部数据 Q03BJ03 0.5
64 QYGL8271 企业司法涉诉(详版) 企业相关 2.5 西部数据 Q03SC01 0.5
65 QYGLB4C0 股东人企关系精准版 企业相关 3 西部数据 G05HZ01 0.6
66 YYSY09CD 运营商三要素验证(简版) 运营商验证 0.3 西部数据 G16BJ02 0.3
67 YYSY4B21 手机在网状态 运营商验证 0.5 西部数据 G25BJ02 0.055
68 YYSY4B37 手机在网时长A 运营商验证 0.3 西部数据 G02BJ02 0.2
69 YYSY6F2E 运营商三要素核验(高级版) 运营商验证 0.4 西部数据 G15BJ02 0.35
70 YYSYD50F 二要素核验(手机号、身份证号) 运营商验证 0.35 西部数据 G18BJ02 0.29
71 YYSYF7DB 手机二次卡 运营商验证 0.3 西部数据 G19BJ02 0.2
72 DWBG7F3A 多头借贷行业风险版 金融验证 2.5 四川星维 CDJ-1101695406546284544 0.45
73 FLXG7E8F 个人司法涉诉查询 风险管控 2 四川星维 CDJ-1101695378264092672 0.36
74 IVYZ3A7F 学历信息查询(学校名称版) 身份验证 5 四川星维 CDJ-1104648854749245440 3
75 IVYZ6G7H 单人婚姻状态(补证版) 身份验证 3.5 四川星维 CDJ-1104646268587536384 0.7
76 IVYZ8I9J 互联网行为推测 身份验证 1.8 四川星维 CDJ-1074522823015198720 0.6
77 IVYZ9D2E 身份验证 四川星维 CDJ-1104648845446279168 2.2
78 JRZQ0L85 个人信用分 金融验证 1.5 四川星维 CDJ-1101695364016041984 0.38
79 JRZQ6F2A 借贷意向验证A 金融验证 2 四川星维 CDJ-1101695369065984000 0.25
80 JRZQ8B3C 个人消费能力等级 金融验证 3 四川星维 CDJ-1101695392528920576 0.34
81 JRZQ9D4E 多头借贷小时级 金融验证 2.5 四川星维 CDJ-1118085532960616448 0.4
82 JRZQ9E2A 多头借贷风险信息查询 金融验证 3 四川星维 CDJ-1068350101688086528 0.6
83 QYGL5F6A 名下企业关联 企业相关 2.8 四川星维 CDJ-1101695397213958144 0.44
84 YYSY7D3E 携号转网查询 运营商验证 0.3 四川星维 CDJ-1100244706893164544 0.02
85 YYSY8C2D 运营商三要素(新详版) 运营商验证 0.35 四川星维 CDJ-1100244702166183936 0.19
86 YYSY8F3A 运营商验证 四川星维 CDJ-1100244697766359040 0.14
87 YYSY9A1B 运营商三要素验证(简版) 运营商验证 0.3 四川星维 CDJ-1100244697766359040 0.14
88 QYGL4B2E 企业相关 天眼查 TaxContravention
89 QYGL5A3C 对外投资历史 企业相关 0.5 天眼查 InvestHistory 0.1
90 QYGL7C1A 经营异常 企业相关 0.5 天眼查 AbnormalInfo 0.15
91 QYGL7D9A 企业相关 天眼查 OwnTax
92 QYGL8B4D 融资历史 企业相关 0.5 天眼查 FinancingHistory 0.1
93 QYGL9E2F 行政处罚 企业相关 0.5 天眼查 PunishmentInfo 0.15
94 QYGL23T7 企业法人四要素高级版 企业相关 0.3 阿里云 check
95 YYSYBE08 二要素核验(姓名、身份证号) 运营商验证 0.25 阿里云 check 0.03
96 IVYZ3P9M 学历信息查询(实时版) 身份验证 5 木子数据 PC0041 1.72
97 COMENT01 企业风险报告(专业版) 组合包 30 内部处理
98 QYGL3F8E 人企关系加强版 企业相关 10.8 内部处理

BIN
worker Normal file

Binary file not shown.