Compare commits
2 Commits
f00cee7410
...
1bfeac0504
| Author | SHA1 | Date | |
|---|---|---|---|
| 1bfeac0504 | |||
| 00a3f0f1e9 |
@@ -102,7 +102,6 @@ func (a *Application) Run() error {
|
|||||||
|
|
||||||
// RunMigrations 运行数据库迁移
|
// RunMigrations 运行数据库迁移
|
||||||
func (a *Application) RunMigrations() error {
|
func (a *Application) RunMigrations() error {
|
||||||
return nil
|
|
||||||
a.logger.Info("Running database migrations...")
|
a.logger.Info("Running database migrations...")
|
||||||
|
|
||||||
// 创建数据库连接
|
// 创建数据库连接
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ type CreateProductCommand struct {
|
|||||||
Content string `json:"content" binding:"omitempty,max=5000" comment:"产品内容"`
|
Content string `json:"content" binding:"omitempty,max=5000" comment:"产品内容"`
|
||||||
CategoryID string `json:"category_id" binding:"required,uuid" comment:"产品分类ID"`
|
CategoryID string `json:"category_id" binding:"required,uuid" comment:"产品分类ID"`
|
||||||
Price float64 `json:"price" binding:"price,min=0" comment:"产品价格"`
|
Price float64 `json:"price" binding:"price,min=0" comment:"产品价格"`
|
||||||
|
CostPrice float64 `json:"cost_price" binding:"omitempty,min=0" comment:"成本价"`
|
||||||
|
Remark string `json:"remark" binding:"omitempty,max=1000" comment:"备注"`
|
||||||
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||||
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
||||||
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
||||||
@@ -27,6 +29,8 @@ type UpdateProductCommand struct {
|
|||||||
Content string `json:"content" binding:"omitempty,max=5000" comment:"产品内容"`
|
Content string `json:"content" binding:"omitempty,max=5000" comment:"产品内容"`
|
||||||
CategoryID string `json:"category_id" binding:"required,uuid" comment:"产品分类ID"`
|
CategoryID string `json:"category_id" binding:"required,uuid" comment:"产品分类ID"`
|
||||||
Price float64 `json:"price" binding:"price,min=0" comment:"产品价格"`
|
Price float64 `json:"price" binding:"price,min=0" comment:"产品价格"`
|
||||||
|
CostPrice float64 `json:"cost_price" binding:"omitempty,min=0" comment:"成本价"`
|
||||||
|
Remark string `json:"remark" binding:"omitempty,max=1000" comment:"备注"`
|
||||||
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||||
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
||||||
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ type UpdateSubscriptionPriceCommand struct {
|
|||||||
|
|
||||||
// BatchUpdateSubscriptionPricesCommand 批量更新订阅价格命令
|
// BatchUpdateSubscriptionPricesCommand 批量更新订阅价格命令
|
||||||
type BatchUpdateSubscriptionPricesCommand struct {
|
type BatchUpdateSubscriptionPricesCommand struct {
|
||||||
UserID string `json:"user_id" binding:"required,uuid" comment:"用户ID"`
|
UserID string `json:"user_id" binding:"required,uuid" comment:"用户ID"`
|
||||||
Discount float64 `json:"discount" binding:"required,min=0.1,max=10" comment:"折扣比例(0.1-10折)"`
|
AdjustmentType string `json:"adjustment_type" binding:"required,oneof=discount cost_multiple" comment:"调整方式(discount:按售价折扣,cost_multiple:按成本价倍数)"`
|
||||||
Scope string `json:"scope" binding:"required,oneof=undiscounted all" comment:"改价范围(undiscounted:仅未打折,all:所有)"`
|
Discount float64 `json:"discount,omitempty" binding:"omitempty,min=0.1,max=10" comment:"折扣比例(0.1-10折)"`
|
||||||
|
CostMultiple float64 `json:"cost_multiple,omitempty" binding:"omitempty,min=0.1" comment:"成本价倍数"`
|
||||||
|
Scope string `json:"scope" binding:"required,oneof=undiscounted all" comment:"改价范围(undiscounted:仅未打折,all:所有)"`
|
||||||
}
|
}
|
||||||
@@ -70,6 +70,12 @@ type ProductSimpleResponse struct {
|
|||||||
IsSubscribed *bool `json:"is_subscribed,omitempty" comment:"当前用户是否已订阅"`
|
IsSubscribed *bool `json:"is_subscribed,omitempty" comment:"当前用户是否已订阅"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ProductSimpleAdminResponse 管理员产品简单信息响应(包含成本价)
|
||||||
|
type ProductSimpleAdminResponse struct {
|
||||||
|
ProductSimpleResponse
|
||||||
|
CostPrice float64 `json:"cost_price" comment:"成本价"`
|
||||||
|
}
|
||||||
|
|
||||||
// ProductStatsResponse 产品统计响应
|
// ProductStatsResponse 产品统计响应
|
||||||
type ProductStatsResponse struct {
|
type ProductStatsResponse struct {
|
||||||
TotalProducts int64 `json:"total_products" comment:"产品总数"`
|
TotalProducts int64 `json:"total_products" comment:"产品总数"`
|
||||||
@@ -88,6 +94,8 @@ type ProductAdminInfoResponse struct {
|
|||||||
Content string `json:"content" comment:"产品内容"`
|
Content string `json:"content" comment:"产品内容"`
|
||||||
CategoryID string `json:"category_id" comment:"产品分类ID"`
|
CategoryID string `json:"category_id" comment:"产品分类ID"`
|
||||||
Price float64 `json:"price" comment:"产品价格"`
|
Price float64 `json:"price" comment:"产品价格"`
|
||||||
|
CostPrice float64 `json:"cost_price" comment:"成本价"`
|
||||||
|
Remark string `json:"remark" comment:"备注"`
|
||||||
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||||
IsVisible bool `json:"is_visible" comment:"是否可见"`
|
IsVisible bool `json:"is_visible" comment:"是否可见"`
|
||||||
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ type SubscriptionInfoResponse struct {
|
|||||||
// 关联信息
|
// 关联信息
|
||||||
User *UserSimpleResponse `json:"user,omitempty" comment:"用户信息"`
|
User *UserSimpleResponse `json:"user,omitempty" comment:"用户信息"`
|
||||||
Product *ProductSimpleResponse `json:"product,omitempty" comment:"产品信息"`
|
Product *ProductSimpleResponse `json:"product,omitempty" comment:"产品信息"`
|
||||||
|
// 管理员端使用,包含成本价的产品信息
|
||||||
|
ProductAdmin *ProductSimpleAdminResponse `json:"product_admin,omitempty" comment:"产品信息(管理员端,包含成本价)"`
|
||||||
|
|
||||||
CreatedAt time.Time `json:"created_at" comment:"创建时间"`
|
CreatedAt time.Time `json:"created_at" comment:"创建时间"`
|
||||||
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"`
|
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"`
|
||||||
|
|||||||
@@ -60,6 +60,8 @@ func (s *ProductApplicationServiceImpl) CreateProduct(ctx context.Context, cmd *
|
|||||||
Content: cmd.Content,
|
Content: cmd.Content,
|
||||||
CategoryID: cmd.CategoryID,
|
CategoryID: cmd.CategoryID,
|
||||||
Price: decimal.NewFromFloat(cmd.Price),
|
Price: decimal.NewFromFloat(cmd.Price),
|
||||||
|
CostPrice: decimal.NewFromFloat(cmd.CostPrice),
|
||||||
|
Remark: cmd.Remark,
|
||||||
IsEnabled: cmd.IsEnabled,
|
IsEnabled: cmd.IsEnabled,
|
||||||
IsVisible: cmd.IsVisible,
|
IsVisible: cmd.IsVisible,
|
||||||
IsPackage: cmd.IsPackage,
|
IsPackage: cmd.IsPackage,
|
||||||
@@ -89,6 +91,8 @@ func (s *ProductApplicationServiceImpl) UpdateProduct(ctx context.Context, cmd *
|
|||||||
existingProduct.Content = cmd.Content
|
existingProduct.Content = cmd.Content
|
||||||
existingProduct.CategoryID = cmd.CategoryID
|
existingProduct.CategoryID = cmd.CategoryID
|
||||||
existingProduct.Price = decimal.NewFromFloat(cmd.Price)
|
existingProduct.Price = decimal.NewFromFloat(cmd.Price)
|
||||||
|
existingProduct.CostPrice = decimal.NewFromFloat(cmd.CostPrice)
|
||||||
|
existingProduct.Remark = cmd.Remark
|
||||||
existingProduct.IsEnabled = cmd.IsEnabled
|
existingProduct.IsEnabled = cmd.IsEnabled
|
||||||
existingProduct.IsVisible = cmd.IsVisible
|
existingProduct.IsVisible = cmd.IsVisible
|
||||||
existingProduct.IsPackage = cmd.IsPackage
|
existingProduct.IsPackage = cmd.IsPackage
|
||||||
@@ -528,6 +532,8 @@ func (s *ProductApplicationServiceImpl) convertToProductAdminInfoResponse(produc
|
|||||||
Content: product.Content,
|
Content: product.Content,
|
||||||
CategoryID: product.CategoryID,
|
CategoryID: product.CategoryID,
|
||||||
Price: product.Price.InexactFloat64(),
|
Price: product.Price.InexactFloat64(),
|
||||||
|
CostPrice: product.CostPrice.InexactFloat64(),
|
||||||
|
Remark: product.Remark,
|
||||||
IsEnabled: product.IsEnabled,
|
IsEnabled: product.IsEnabled,
|
||||||
IsVisible: product.IsVisible, // 管理员可以看到可见状态
|
IsVisible: product.IsVisible, // 管理员可以看到可见状态
|
||||||
IsPackage: product.IsPackage,
|
IsPackage: product.IsPackage,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package product
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/shopspring/decimal"
|
"github.com/shopspring/decimal"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
@@ -45,6 +46,22 @@ func (s *SubscriptionApplicationServiceImpl) UpdateSubscriptionPrice(ctx context
|
|||||||
// BatchUpdateSubscriptionPrices 一键改价
|
// BatchUpdateSubscriptionPrices 一键改价
|
||||||
// 业务流程:1. 获取用户所有订阅 2. 根据范围筛选 3. 批量更新价格
|
// 业务流程:1. 获取用户所有订阅 2. 根据范围筛选 3. 批量更新价格
|
||||||
func (s *SubscriptionApplicationServiceImpl) BatchUpdateSubscriptionPrices(ctx context.Context, cmd *commands.BatchUpdateSubscriptionPricesCommand) error {
|
func (s *SubscriptionApplicationServiceImpl) BatchUpdateSubscriptionPrices(ctx context.Context, cmd *commands.BatchUpdateSubscriptionPricesCommand) error {
|
||||||
|
// 记录请求参数
|
||||||
|
s.logger.Info("开始批量更新订阅价格",
|
||||||
|
zap.String("user_id", cmd.UserID),
|
||||||
|
zap.String("adjustment_type", cmd.AdjustmentType),
|
||||||
|
zap.Float64("discount", cmd.Discount),
|
||||||
|
zap.Float64("cost_multiple", cmd.CostMultiple),
|
||||||
|
zap.String("scope", cmd.Scope))
|
||||||
|
|
||||||
|
// 验证调整方式对应的参数
|
||||||
|
if cmd.AdjustmentType == "discount" && cmd.Discount <= 0 {
|
||||||
|
return fmt.Errorf("按售价折扣调整时,折扣比例必须大于0")
|
||||||
|
}
|
||||||
|
if cmd.AdjustmentType == "cost_multiple" && cmd.CostMultiple <= 0 {
|
||||||
|
return fmt.Errorf("按成本价倍数调整时,倍数必须大于0")
|
||||||
|
}
|
||||||
|
|
||||||
subscriptions, _, err := s.productSubscriptionService.ListSubscriptions(ctx, &repoQueries.ListSubscriptionsQuery{
|
subscriptions, _, err := s.productSubscriptionService.ListSubscriptions(ctx, &repoQueries.ListSubscriptionsQuery{
|
||||||
UserID: cmd.UserID,
|
UserID: cmd.UserID,
|
||||||
Page: 1,
|
Page: 1,
|
||||||
@@ -54,6 +71,9 @@ func (s *SubscriptionApplicationServiceImpl) BatchUpdateSubscriptionPrices(ctx c
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.logger.Info("获取到订阅列表",
|
||||||
|
zap.Int("total_subscriptions", len(subscriptions)))
|
||||||
|
|
||||||
// 根据范围筛选订阅
|
// 根据范围筛选订阅
|
||||||
var targetSubscriptions []*entities.Subscription
|
var targetSubscriptions []*entities.Subscription
|
||||||
for _, sub := range subscriptions {
|
for _, sub := range subscriptions {
|
||||||
@@ -69,24 +89,64 @@ func (s *SubscriptionApplicationServiceImpl) BatchUpdateSubscriptionPrices(ctx c
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 批量更新价格
|
// 批量更新价格
|
||||||
|
updatedCount := 0
|
||||||
|
skippedCount := 0
|
||||||
for _, sub := range targetSubscriptions {
|
for _, sub := range targetSubscriptions {
|
||||||
if sub.Product != nil {
|
if sub.Product == nil {
|
||||||
// 计算折扣后的价格
|
skippedCount++
|
||||||
discountRatio := cmd.Discount / 10
|
continue
|
||||||
newPrice := sub.Product.Price.Mul(decimal.NewFromFloat(discountRatio))
|
}
|
||||||
// 四舍五入到2位小数
|
|
||||||
newPrice = newPrice.Round(2)
|
|
||||||
|
|
||||||
err := s.productSubscriptionService.UpdateSubscriptionPrice(ctx, sub.ID, newPrice.InexactFloat64())
|
var newPrice decimal.Decimal
|
||||||
if err != nil {
|
|
||||||
s.logger.Error("批量更新订阅价格失败",
|
if cmd.AdjustmentType == "discount" {
|
||||||
|
// 按售价折扣调整
|
||||||
|
discountRatio := cmd.Discount / 10
|
||||||
|
newPrice = sub.Product.Price.Mul(decimal.NewFromFloat(discountRatio))
|
||||||
|
} else if cmd.AdjustmentType == "cost_multiple" {
|
||||||
|
// 按成本价倍数调整
|
||||||
|
// 检查成本价是否有效(必须大于0)
|
||||||
|
// 使用严格检查:成本价必须大于0
|
||||||
|
if !sub.Product.CostPrice.GreaterThan(decimal.Zero) {
|
||||||
|
// 跳过没有成本价或成本价为0的产品
|
||||||
|
skippedCount++
|
||||||
|
s.logger.Info("跳过未设置成本价或成本价为0的订阅",
|
||||||
zap.String("subscription_id", sub.ID),
|
zap.String("subscription_id", sub.ID),
|
||||||
zap.Error(err))
|
zap.String("product_id", sub.ProductID),
|
||||||
// 继续处理其他订阅,不中断整个流程
|
zap.String("product_name", sub.Product.Name),
|
||||||
|
zap.String("cost_price", sub.Product.CostPrice.String()))
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
// 计算成本价倍数后的价格
|
||||||
|
newPrice = sub.Product.CostPrice.Mul(decimal.NewFromFloat(cmd.CostMultiple))
|
||||||
|
} else {
|
||||||
|
s.logger.Warn("未知的调整方式",
|
||||||
|
zap.String("adjustment_type", cmd.AdjustmentType),
|
||||||
|
zap.String("subscription_id", sub.ID))
|
||||||
|
skippedCount++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 四舍五入到2位小数
|
||||||
|
newPrice = newPrice.Round(2)
|
||||||
|
|
||||||
|
err := s.productSubscriptionService.UpdateSubscriptionPrice(ctx, sub.ID, newPrice.InexactFloat64())
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("批量更新订阅价格失败",
|
||||||
|
zap.String("subscription_id", sub.ID),
|
||||||
|
zap.Error(err))
|
||||||
|
skippedCount++
|
||||||
|
// 继续处理其他订阅,不中断整个流程
|
||||||
|
} else {
|
||||||
|
updatedCount++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.logger.Info("批量更新订阅价格完成",
|
||||||
|
zap.Int("total", len(targetSubscriptions)),
|
||||||
|
zap.Int("updated", updatedCount),
|
||||||
|
zap.Int("skipped", skippedCount))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,7 +189,7 @@ func (s *SubscriptionApplicationServiceImpl) ListSubscriptions(ctx context.Conte
|
|||||||
}
|
}
|
||||||
items := make([]responses.SubscriptionInfoResponse, len(subscriptions))
|
items := make([]responses.SubscriptionInfoResponse, len(subscriptions))
|
||||||
for i := range subscriptions {
|
for i := range subscriptions {
|
||||||
resp := s.convertToSubscriptionInfoResponse(subscriptions[i])
|
resp := s.convertToSubscriptionInfoResponseForAdmin(subscriptions[i])
|
||||||
if resp != nil {
|
if resp != nil {
|
||||||
items[i] = *resp // 解引用指针
|
items[i] = *resp // 解引用指针
|
||||||
}
|
}
|
||||||
@@ -300,6 +360,65 @@ func (s *SubscriptionApplicationServiceImpl) convertToProductSimpleResponse(prod
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// convertToSubscriptionInfoResponseForAdmin 转换为订阅信息响应(管理员端,包含成本价)
|
||||||
|
func (s *SubscriptionApplicationServiceImpl) convertToSubscriptionInfoResponseForAdmin(subscription *entities.Subscription) *responses.SubscriptionInfoResponse {
|
||||||
|
// 查询用户信息
|
||||||
|
var userInfo *responses.UserSimpleResponse
|
||||||
|
if subscription.UserID != "" {
|
||||||
|
user, err := s.userRepo.GetByIDWithEnterpriseInfo(context.Background(), subscription.UserID)
|
||||||
|
if err == nil {
|
||||||
|
companyName := "未知公司"
|
||||||
|
if user.EnterpriseInfo != nil {
|
||||||
|
companyName = user.EnterpriseInfo.CompanyName
|
||||||
|
}
|
||||||
|
userInfo = &responses.UserSimpleResponse{
|
||||||
|
ID: user.ID,
|
||||||
|
CompanyName: companyName,
|
||||||
|
Phone: user.Phone,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var productAdminResponse *responses.ProductSimpleAdminResponse
|
||||||
|
if subscription.Product != nil {
|
||||||
|
productAdminResponse = s.convertToProductSimpleAdminResponse(subscription.Product)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &responses.SubscriptionInfoResponse{
|
||||||
|
ID: subscription.ID,
|
||||||
|
UserID: subscription.UserID,
|
||||||
|
ProductID: subscription.ProductID,
|
||||||
|
Price: subscription.Price.InexactFloat64(),
|
||||||
|
User: userInfo,
|
||||||
|
ProductAdmin: productAdminResponse,
|
||||||
|
APIUsed: subscription.APIUsed,
|
||||||
|
CreatedAt: subscription.CreatedAt,
|
||||||
|
UpdatedAt: subscription.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertToProductSimpleAdminResponse 转换为管理员产品简单信息响应(包含成本价)
|
||||||
|
func (s *SubscriptionApplicationServiceImpl) convertToProductSimpleAdminResponse(product *entities.Product) *responses.ProductSimpleAdminResponse {
|
||||||
|
var categoryResponse *responses.CategorySimpleResponse
|
||||||
|
if product.Category != nil {
|
||||||
|
categoryResponse = s.convertToCategorySimpleResponse(product.Category)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &responses.ProductSimpleAdminResponse{
|
||||||
|
ProductSimpleResponse: responses.ProductSimpleResponse{
|
||||||
|
ID: product.ID,
|
||||||
|
OldID: product.OldID,
|
||||||
|
Name: product.Name,
|
||||||
|
Code: product.Code,
|
||||||
|
Description: product.Description,
|
||||||
|
Price: product.Price.InexactFloat64(),
|
||||||
|
Category: categoryResponse,
|
||||||
|
IsPackage: product.IsPackage,
|
||||||
|
},
|
||||||
|
CostPrice: product.CostPrice.InexactFloat64(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// convertToCategorySimpleResponse 转换为分类简单信息响应
|
// convertToCategorySimpleResponse 转换为分类简单信息响应
|
||||||
func (s *SubscriptionApplicationServiceImpl) convertToCategorySimpleResponse(category *entities.ProductCategory) *responses.CategorySimpleResponse {
|
func (s *SubscriptionApplicationServiceImpl) convertToCategorySimpleResponse(category *entities.ProductCategory) *responses.CategorySimpleResponse {
|
||||||
if category == nil {
|
if category == nil {
|
||||||
|
|||||||
@@ -452,6 +452,37 @@ type QCXG9P1CReq struct {
|
|||||||
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type QCXG6B4EReq struct {
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type JRZQ2F8AReq struct {
|
||||||
|
Name string `json:"name" validate:"required,min=1,validName"`
|
||||||
|
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||||
|
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||||
|
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type JRZQ1E7BReq struct {
|
||||||
|
Name string `json:"name" validate:"required,min=1,validName"`
|
||||||
|
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||||
|
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||||
|
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||||
|
}
|
||||||
|
|
||||||
type JRZQ9E2AReq struct {
|
type JRZQ9E2AReq 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"`
|
||||||
|
|||||||
@@ -123,6 +123,8 @@ func registerAllProcessors(combService *comb.CombService) {
|
|||||||
"JRZQ8B3C": jrzq.ProcessJRZQ8B3CRequest,
|
"JRZQ8B3C": jrzq.ProcessJRZQ8B3CRequest,
|
||||||
"JRZQ9D4E": jrzq.ProcessJRZQ9D4ERequest,
|
"JRZQ9D4E": jrzq.ProcessJRZQ9D4ERequest,
|
||||||
"JRZQ0L85": jrzq.ProcessJRZQ0L85Request,
|
"JRZQ0L85": jrzq.ProcessJRZQ0L85Request,
|
||||||
|
"JRZQ2F8A": jrzq.ProcessJRZQ2F8ARequest,
|
||||||
|
"JRZQ1E7B": jrzq.ProcessJRZQ1E7BRequest,
|
||||||
|
|
||||||
// QYGL系列处理器
|
// QYGL系列处理器
|
||||||
"QYGL8261": qygl.ProcessQYGL8261Request,
|
"QYGL8261": qygl.ProcessQYGL8261Request,
|
||||||
@@ -141,6 +143,7 @@ func registerAllProcessors(combService *comb.CombService) {
|
|||||||
"QYGL4B2E": qygl.ProcessQYGL4B2ERequest, // 税收违法
|
"QYGL4B2E": qygl.ProcessQYGL4B2ERequest, // 税收违法
|
||||||
"COMENT01": qygl.ProcessCOMENT01Request, // 企业风险报告
|
"COMENT01": qygl.ProcessCOMENT01Request, // 企业风险报告
|
||||||
"QYGL5F6A": qygl.ProcessQYGL5F6ARequest, // 企业相关查询
|
"QYGL5F6A": qygl.ProcessQYGL5F6ARequest, // 企业相关查询
|
||||||
|
"QYGL2B5C": qygl.ProcessQYGL2B5CRequest, // 企业联系人实际经营地址
|
||||||
|
|
||||||
// YYSY系列处理器
|
// YYSY系列处理器
|
||||||
"YYSYD50F": yysy.ProcessYYSYD50FRequest,
|
"YYSYD50F": yysy.ProcessYYSYD50FRequest,
|
||||||
@@ -190,6 +193,8 @@ func registerAllProcessors(combService *comb.CombService) {
|
|||||||
// QCXG系列处理器
|
// QCXG系列处理器
|
||||||
"QCXG7A2B": qcxg.ProcessQCXG7A2BRequest,
|
"QCXG7A2B": qcxg.ProcessQCXG7A2BRequest,
|
||||||
"QCXG9P1C": qcxg.ProcessQCXG9P1CRequest,
|
"QCXG9P1C": qcxg.ProcessQCXG9P1CRequest,
|
||||||
|
"QCXG8A3D": qcxg.ProcessQCXG8A3DRequest,
|
||||||
|
"QCXG6B4E": qcxg.ProcessQCXG6B4ERequest,
|
||||||
|
|
||||||
// DWBG系列处理器 - 多维报告
|
// DWBG系列处理器 - 多维报告
|
||||||
"DWBG6A2C": dwbg.ProcessDWBG6A2CRequest,
|
"DWBG6A2C": dwbg.ProcessDWBG6A2CRequest,
|
||||||
|
|||||||
@@ -172,6 +172,11 @@ func (s *FormConfigServiceImpl) getDTOStruct(ctx context.Context, apiCode string
|
|||||||
"IVYZ8I9J": &dto.IVYZ8I9JReq{},
|
"IVYZ8I9J": &dto.IVYZ8I9JReq{},
|
||||||
"JRZQ0L85": &dto.JRZQ0L85Req{},
|
"JRZQ0L85": &dto.JRZQ0L85Req{},
|
||||||
"COMBHZY2": &dto.COMBHZY2Req{},
|
"COMBHZY2": &dto.COMBHZY2Req{},
|
||||||
|
"QCXG8A3D": &dto.QCXG8A3DReq{},
|
||||||
|
"QCXG6B4E": &dto.QCXG6B4EReq{},
|
||||||
|
"QYGL2B5C": &dto.QYGL2B5CReq{},
|
||||||
|
"JRZQ2F8A": &dto.JRZQ2F8AReq{},
|
||||||
|
"JRZQ1E7B": &dto.JRZQ1E7BReq{},
|
||||||
}
|
}
|
||||||
|
|
||||||
// 优先返回已配置的DTO
|
// 优先返回已配置的DTO
|
||||||
@@ -271,6 +276,8 @@ func (s *FormConfigServiceImpl) parseValidationRules(validateTag string) string
|
|||||||
frontendRules = append(frontendRules, "姓名格式")
|
frontendRules = append(frontendRules, "姓名格式")
|
||||||
case rule == "validUSCI":
|
case rule == "validUSCI":
|
||||||
frontendRules = append(frontendRules, "统一社会信用代码格式")
|
frontendRules = append(frontendRules, "统一社会信用代码格式")
|
||||||
|
case rule == "validEnterpriseName" || rule == "enterprise_name":
|
||||||
|
frontendRules = append(frontendRules, "企业名称格式")
|
||||||
case rule == "validBankCard":
|
case rule == "validBankCard":
|
||||||
frontendRules = append(frontendRules, "银行卡号格式")
|
frontendRules = append(frontendRules, "银行卡号格式")
|
||||||
case rule == "validDate":
|
case rule == "validDate":
|
||||||
@@ -357,6 +364,9 @@ func (s *FormConfigServiceImpl) generateFieldLabel(jsonTag string) string {
|
|||||||
"page_size": "每页数量",
|
"page_size": "每页数量",
|
||||||
"use_scenario": "使用场景",
|
"use_scenario": "使用场景",
|
||||||
"auth_authorize_file_code": "授权文件编码",
|
"auth_authorize_file_code": "授权文件编码",
|
||||||
|
"plate_no": "车牌号",
|
||||||
|
"plate_type": "号牌类型",
|
||||||
|
"vin_code": "车辆识别代号VIN码",
|
||||||
}
|
}
|
||||||
|
|
||||||
if label, exists := labelMap[jsonTag]; exists {
|
if label, exists := labelMap[jsonTag]; exists {
|
||||||
@@ -394,6 +404,9 @@ func (s *FormConfigServiceImpl) generateExampleValue(fieldType reflect.Type, jso
|
|||||||
"page_size": "10",
|
"page_size": "10",
|
||||||
"use_scenario": "1",
|
"use_scenario": "1",
|
||||||
"auth_authorize_file_code": "AUTH123456",
|
"auth_authorize_file_code": "AUTH123456",
|
||||||
|
"plate_no": "京A12345",
|
||||||
|
"plate_type": "01",
|
||||||
|
"vin_code": "LSGBF53M8DS123456",
|
||||||
}
|
}
|
||||||
|
|
||||||
if example, exists := exampleMap[jsonTag]; exists {
|
if example, exists := exampleMap[jsonTag]; exists {
|
||||||
@@ -440,6 +453,9 @@ func (s *FormConfigServiceImpl) generatePlaceholder(jsonTag string, fieldType st
|
|||||||
"page_size": "请输入每页数量(1-100)",
|
"page_size": "请输入每页数量(1-100)",
|
||||||
"use_scenario": "请选择使用场景",
|
"use_scenario": "请选择使用场景",
|
||||||
"auth_authorize_file_code": "请输入授权文件编码",
|
"auth_authorize_file_code": "请输入授权文件编码",
|
||||||
|
"plate_no": "请输入车牌号",
|
||||||
|
"plate_type": "请选择号牌类型(01或02)",
|
||||||
|
"vin_code": "请输入17位车辆识别代号VIN码",
|
||||||
}
|
}
|
||||||
|
|
||||||
if placeholder, exists := placeholderMap[jsonTag]; exists {
|
if placeholder, exists := placeholderMap[jsonTag]; exists {
|
||||||
@@ -488,6 +504,9 @@ func (s *FormConfigServiceImpl) generateDescription(jsonTag string, validation s
|
|||||||
"page_size": "请输入每页数量,范围1-100",
|
"page_size": "请输入每页数量,范围1-100",
|
||||||
"use_scenario": "使用场景:1-信贷审核;2-保险评估;3-招聘背景调查;4-其他业务场景;99-其他",
|
"use_scenario": "使用场景:1-信贷审核;2-保险评估;3-招聘背景调查;4-其他业务场景;99-其他",
|
||||||
"auth_authorize_file_code": "请输入授权文件编码",
|
"auth_authorize_file_code": "请输入授权文件编码",
|
||||||
|
"plate_no": "请输入车牌号",
|
||||||
|
"plate_type": "号牌类型:01-小型汽车;02-大型汽车(可选)",
|
||||||
|
"vin_code": "请输入17位车辆识别代号VIN码(Vehicle Identification Number)",
|
||||||
}
|
}
|
||||||
|
|
||||||
if desc, exists := descMap[jsonTag]; exists {
|
if desc, exists := descMap[jsonTag]; exists {
|
||||||
|
|||||||
@@ -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/zhicha"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessJRZQ1E7BRequest JRZQ1E7B API处理方法 - 消费交易特征
|
||||||
|
func ProcessJRZQ1E7BRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.JRZQ1E7BReq
|
||||||
|
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, "ZCI034", 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/zhicha"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessJRZQ2F8ARequest JRZQ2F8A API处理方法 - 探针A
|
||||||
|
func ProcessJRZQ2F8ARequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.JRZQ2F8AReq
|
||||||
|
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, "ZCI009", 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,46 @@
|
|||||||
|
package qcxg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/zhicha"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessQCXG6B4ERequest QCXG6B4E API处理方法 - 车辆出险记录查验
|
||||||
|
func ProcessQCXG6B4ERequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.QCXG6B4EReq
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
reqData := map[string]interface{}{
|
||||||
|
"vin": paramsDto.VINCode,
|
||||||
|
"authorized": paramsDto.Authorized,
|
||||||
|
}
|
||||||
|
|
||||||
|
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI049", 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,50 @@
|
|||||||
|
package qcxg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/zhicha"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessQCXG8A3DRequest QCXG8A3D API处理方法 - 车辆七项信息核验
|
||||||
|
func ProcessQCXG8A3DRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.QCXG8A3DReq
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
reqData := map[string]interface{}{
|
||||||
|
"plate": paramsDto.PlateNo,
|
||||||
|
"authorized": paramsDto.Authorized,
|
||||||
|
}
|
||||||
|
// 如果传了车牌类型,则添加到请求数据中
|
||||||
|
if paramsDto.PlateType != "" {
|
||||||
|
reqData["vehType"] = paramsDto.PlateType
|
||||||
|
}
|
||||||
|
|
||||||
|
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI048", 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 qygl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/zhicha"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessQYGL2B5CRequest QYGL2B5C API处理方法 - 企业联系人实际经营地址
|
||||||
|
func ProcessQYGL2B5CRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.QYGL2B5CReq
|
||||||
|
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 两选一校验:EntName 和 EntCode 至少传一个
|
||||||
|
var keyword string
|
||||||
|
if paramsDto.EntName != "" && paramsDto.EntCode != "" {
|
||||||
|
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, errors.New("企业名称和企业统一信用代码只能传其中一个"))
|
||||||
|
}
|
||||||
|
if paramsDto.EntName == "" && paramsDto.EntCode == "" {
|
||||||
|
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, errors.New("必须提供企业名称或企业统一信用代码中的其中一个"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确定使用哪个值作为 keyword
|
||||||
|
if paramsDto.EntName != "" {
|
||||||
|
keyword = paramsDto.EntName
|
||||||
|
} else {
|
||||||
|
keyword = paramsDto.EntCode
|
||||||
|
}
|
||||||
|
|
||||||
|
reqData := map[string]interface{}{
|
||||||
|
"keyword": keyword,
|
||||||
|
"authorized": paramsDto.Authorized,
|
||||||
|
}
|
||||||
|
|
||||||
|
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI050", 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
|
||||||
|
}
|
||||||
|
|
||||||
@@ -18,6 +18,8 @@ type Product struct {
|
|||||||
Content string `gorm:"type:text" comment:"产品内容"`
|
Content string `gorm:"type:text" comment:"产品内容"`
|
||||||
CategoryID string `gorm:"type:varchar(36);not null" comment:"产品分类ID"`
|
CategoryID string `gorm:"type:varchar(36);not null" comment:"产品分类ID"`
|
||||||
Price decimal.Decimal `gorm:"type:decimal(10,2);not null;default:0" comment:"产品价格"`
|
Price decimal.Decimal `gorm:"type:decimal(10,2);not null;default:0" comment:"产品价格"`
|
||||||
|
CostPrice decimal.Decimal `gorm:"type:decimal(10,2);default:0" comment:"成本价"`
|
||||||
|
Remark string `gorm:"type:text" comment:"备注"`
|
||||||
IsEnabled bool `gorm:"default:true" comment:"是否启用"`
|
IsEnabled bool `gorm:"default:true" comment:"是否启用"`
|
||||||
IsVisible bool `gorm:"default:true" comment:"是否展示"`
|
IsVisible bool `gorm:"default:true" comment:"是否展示"`
|
||||||
IsPackage bool `gorm:"default:false" comment:"是否组合包"`
|
IsPackage bool `gorm:"default:false" comment:"是否组合包"`
|
||||||
|
|||||||
@@ -290,6 +290,10 @@ func (s *ProductManagementService) ValidateProduct(product *entities.Product) er
|
|||||||
return errors.New("产品价格不能为负数")
|
return errors.New("产品价格不能为负数")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if product.CostPrice.IsNegative() {
|
||||||
|
return errors.New("成本价不能为负数")
|
||||||
|
}
|
||||||
|
|
||||||
// 验证分类是否存在
|
// 验证分类是否存在
|
||||||
if product.CategoryID != "" {
|
if product.CategoryID != "" {
|
||||||
category, err := s.categoryRepo.GetByID(context.Background(), product.CategoryID)
|
category, err := s.categoryRepo.GetByID(context.Background(), product.CategoryID)
|
||||||
|
|||||||
@@ -172,10 +172,10 @@ func (r *GormSubscriptionRepository) ListSubscriptions(ctx context.Context, quer
|
|||||||
dbQuery = dbQuery.Offset(offset).Limit(query.PageSize)
|
dbQuery = dbQuery.Offset(offset).Limit(query.PageSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 预加载Product的id、name、code、price、is_package字段,并同时预加载ProductCategory的id、name、code字段
|
// 预加载Product的id、name、code、price、cost_price、is_package字段,并同时预加载ProductCategory的id、name、code字段
|
||||||
if err := dbQuery.
|
if err := dbQuery.
|
||||||
Preload("Product", func(db *gorm.DB) *gorm.DB {
|
Preload("Product", func(db *gorm.DB) *gorm.DB {
|
||||||
return db.Select("id", "name", "code", "price", "is_package", "category_id").
|
return db.Select("id", "name", "code", "price", "cost_price", "is_package", "category_id").
|
||||||
Preload("Category", func(db2 *gorm.DB) *gorm.DB {
|
Preload("Category", func(db2 *gorm.DB) *gorm.DB {
|
||||||
return db2.Select("id", "name", "code")
|
return db2.Select("id", "name", "code")
|
||||||
})
|
})
|
||||||
|
|||||||
603
scripts/analyze_processors.go
Normal file
603
scripts/analyze_processors.go
Normal file
@@ -0,0 +1,603 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user