package service import ( "context" "database/sql" "encoding/hex" "encoding/json" "strings" "ycc-server/app/main/api/internal/config" "ycc-server/app/main/model" "ycc-server/common/xerr" "ycc-server/pkg/lzkit/crypto" "ycc-server/pkg/lzkit/lzUtils" "github.com/google/uuid" "github.com/pkg/errors" "github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/core/stores/sqlx" ) // WhitelistService 白名单领域服务,集中处理白名单相关的业务逻辑 type WhitelistService struct { config config.Config UserFeatureWhitelistModel model.UserFeatureWhitelistModel WhitelistOrderModel model.WhitelistOrderModel WhitelistOrderItemModel model.WhitelistOrderItemModel QueryModel model.QueryModel FeatureModel model.FeatureModel } // NewWhitelistService 创建白名单服务 func NewWhitelistService( c config.Config, userFeatureWhitelistModel model.UserFeatureWhitelistModel, whitelistOrderModel model.WhitelistOrderModel, whitelistOrderItemModel model.WhitelistOrderItemModel, queryModel model.QueryModel, featureModel model.FeatureModel, ) *WhitelistService { return &WhitelistService{ config: c, UserFeatureWhitelistModel: userFeatureWhitelistModel, WhitelistOrderModel: whitelistOrderModel, WhitelistOrderItemModel: whitelistOrderItemModel, QueryModel: queryModel, FeatureModel: featureModel, } } // EnsureFreeWhitelist 免费下架:如果还没有生效白名单,则创建一条免费白名单记录 func (s *WhitelistService) EnsureFreeWhitelist( ctx context.Context, session sqlx.Session, idCard string, feature *model.Feature, userId string, orderId string, ) error { // 检查是否已存在生效白名单 builder := s.UserFeatureWhitelistModel.SelectBuilder(). Where("id_card = ? AND feature_id = ?", idCard, feature.Id) records, err := s.UserFeatureWhitelistModel.FindAll(ctx, builder, "") if err != nil { return errors.Wrap(err, "查询白名单记录失败") } for _, r := range records { if r.Status == 1 { // 已经下架,直接返回 return nil } } wl := &model.UserFeatureWhitelist{ Id: uuid.NewString(), IdCard: idCard, FeatureId: feature.Id, FeatureApiId: feature.ApiId, UserId: userId, OrderId: lzUtils.StringToNullString(orderId), WhitelistOrderId: lzUtils.StringToNullString(""), Amount: 0, Status: 1, } _, err = s.UserFeatureWhitelistModel.Insert(ctx, session, wl) if err != nil { return errors.Wrap(err, "创建免费白名单记录失败") } return nil } // CreateWhitelistByPaidOrder 根据已支付的白名单订单,创建对应的白名单记录 func (s *WhitelistService) CreateWhitelistByPaidOrder( ctx context.Context, session sqlx.Session, order *model.Order, whitelistOrder *model.WhitelistOrder, ) error { if whitelistOrder.Status != 2 { // 只处理已支付状态 return nil } itemBuilder := s.WhitelistOrderItemModel.SelectBuilder(). Where("order_id = ?", whitelistOrder.Id) items, err := s.WhitelistOrderItemModel.FindAll(ctx, itemBuilder, "") if err != nil { return errors.Wrap(err, "查询白名单订单明细失败") } for _, item := range items { wl := &model.UserFeatureWhitelist{ Id: uuid.NewString(), IdCard: whitelistOrder.IdCard, FeatureId: item.FeatureId, FeatureApiId: item.FeatureApiId, UserId: whitelistOrder.UserId, OrderId: lzUtils.StringToNullString(order.Id), WhitelistOrderId: lzUtils.StringToNullString(whitelistOrder.Id), Amount: item.Price, Status: 1, } if _, err := s.UserFeatureWhitelistModel.Insert(ctx, session, wl); err != nil { return errors.Wrap(err, "创建白名单记录失败") } } return nil } // GetWhitelistedFeatureApisByIdCard 获取某个身份证号已下架的 feature_api_id 集合 func (s *WhitelistService) GetWhitelistedFeatureApisByIdCard( ctx context.Context, idCard string, ) (map[string]bool, error) { result := make(map[string]bool) if s == nil { return result, nil } if idCard == "" { return result, nil } builder := s.UserFeatureWhitelistModel.SelectBuilder(). Where("id_card = ? AND status = ?", idCard, 1) list, err := s.UserFeatureWhitelistModel.FindAll(ctx, builder, "") if err != nil { return nil, errors.Wrap(err, "查询白名单失败") } for _, wl := range list { result[wl.FeatureApiId] = true } return result, nil } // CheckWhitelistExists 检查指定身份证号和模块是否已有生效的白名单记录 func (s *WhitelistService) CheckWhitelistExists( ctx context.Context, idCard string, featureId string, ) (bool, error) { if idCard == "" || featureId == "" { return false, nil } builder := s.UserFeatureWhitelistModel.SelectBuilder(). Where("id_card = ? AND feature_id = ? AND status = ?", idCard, featureId, 1) list, err := s.UserFeatureWhitelistModel.FindAll(ctx, builder, "") if err != nil { return false, errors.Wrap(err, "查询白名单记录失败") } return len(list) > 0, nil } // ProcessOfflineFeature 统一下架处理:处理免费下架或检查付费下架 // 返回:needPay(是否需要支付), amount(金额), whitelistCreated(是否已创建白名单) func (s *WhitelistService) ProcessOfflineFeature( ctx context.Context, session sqlx.Session, idCard string, featureApiId string, userId string, orderId string, ) (needPay bool, amount float64, whitelistCreated bool, err error) { // 1. 提取主模块ID并查询feature信息 mainApiId := s.extractMainApiId(featureApiId) feature, err := s.getFeatureByApiId(ctx, mainApiId) if err != nil { return false, 0, false, err } // 2. 不支持下架的模块:whitelist_price < 0 表示该模块不开放下架功能 if feature.WhitelistPrice < 0 { return false, 0, false, errors.Wrapf(xerr.NewErrMsg("该模块不支持下架"), "") } // 3. 检查是否已有白名单 exists, err := s.CheckWhitelistExists(ctx, idCard, feature.Id) if err != nil { return false, 0, false, err } if exists { // 已有白名单,直接返回成功 return false, 0, true, nil } price := feature.WhitelistPrice // 4. 免费下架:直接创建白名单记录(whitelist_price = 0) if price <= 0 { if err := s.EnsureFreeWhitelist(ctx, session, idCard, feature, userId, orderId); err != nil { return false, 0, false, err } return false, 0, true, nil } // 5. 付费下架:检查是否已有支付成功的订单 paidOrderId, err := s.findPaidWhitelistOrder(ctx, userId, idCard, feature.Id) if err != nil { return false, 0, false, err } // 6. 如果已有支付成功订单,补创建白名单记录 if paidOrderId != "" { if err := s.createWhitelistFromPaidOrder(ctx, session, idCard, feature, userId, orderId, paidOrderId, price); err != nil { return false, 0, false, err } return false, price, true, nil } // 7. 需要支付 return true, price, false, nil } // extractMainApiId 提取主模块ID(去掉下划线后的部分) func (s *WhitelistService) extractMainApiId(featureApiId string) string { if idx := strings.Index(featureApiId, "_"); idx > 0 { return featureApiId[:idx] } return featureApiId } // getFeatureByApiId 根据API ID查询feature信息 func (s *WhitelistService) getFeatureByApiId(ctx context.Context, apiId string) (*model.Feature, error) { feature, err := s.FeatureModel.FindOneByApiId(ctx, apiId) if err != nil { if errors.Is(err, model.ErrNotFound) { return nil, errors.Wrap(err, "模块不存在") } return nil, errors.Wrap(err, "查询模块信息失败") } return feature, nil } // findPaidWhitelistOrder 查找已支付的白名单订单中是否包含指定feature // 返回:paidOrderId(如果找到已支付订单),error func (s *WhitelistService) findPaidWhitelistOrder( ctx context.Context, userId string, idCard string, featureId string, ) (string, error) { orderBuilder := s.WhitelistOrderModel.SelectBuilder(). Where("user_id = ? AND id_card = ? AND status = ?", userId, idCard, 2) // 2表示已支付 orders, err := s.WhitelistOrderModel.FindAll(ctx, orderBuilder, "") if err != nil { return "", errors.Wrap(err, "查询白名单订单失败") } // 查找已支付订单中是否包含该feature for _, order := range orders { itemBuilder := s.WhitelistOrderItemModel.SelectBuilder(). Where("order_id = ? AND feature_id = ?", order.Id, featureId) items, itemErr := s.WhitelistOrderItemModel.FindAll(ctx, itemBuilder, "") if itemErr != nil { return "", errors.Wrap(itemErr, "查询白名单订单明细失败") } if len(items) > 0 { return order.Id, nil } } return "", nil } // createWhitelistFromPaidOrder 根据已支付订单创建白名单记录 func (s *WhitelistService) createWhitelistFromPaidOrder( ctx context.Context, session sqlx.Session, idCard string, feature *model.Feature, userId string, orderId string, paidOrderId string, price float64, ) error { wl := &model.UserFeatureWhitelist{ Id: uuid.NewString(), IdCard: idCard, FeatureId: feature.Id, FeatureApiId: feature.ApiId, UserId: userId, OrderId: lzUtils.StringToNullString(orderId), WhitelistOrderId: lzUtils.StringToNullString(paidOrderId), Amount: price, Status: 1, // 生效 } if _, err := s.UserFeatureWhitelistModel.Insert(ctx, session, wl); err != nil { return errors.Wrap(err, "根据已支付订单创建白名单记录失败") } return nil } // DeleteFeatureFromQueryData 从报告数据中删除指定模块的数据 // queryId: 查询记录ID(Query表的ID) // featureApiId: 要删除的模块API标识 func (s *WhitelistService) DeleteFeatureFromQueryData( ctx context.Context, session sqlx.Session, queryId string, featureApiId string, ) error { // 1. 获取查询记录 queryModel, err := s.getQueryModel(ctx, queryId) if err != nil { return err } if queryModel == nil { // 报告不存在或数据为空,直接返回成功 return nil } // 2. 解密并解析报告数据 mainApiId := s.extractMainApiId(featureApiId) dataArray, key, err := s.decryptQueryData(queryModel) if err != nil { return err } // 3. 清空对应模块的数据(将 data 字段设置为 null) modifiedArray, hasModified := s.clearFeatureData(dataArray, mainApiId) if !hasModified { logx.Infof("删除报告数据:查询记录 %s 中未找到模块 %s 的数据,跳过删除", queryId, featureApiId) return nil } // 4. 重新加密并更新数据库 if err := s.updateQueryData(ctx, session, queryModel, modifiedArray, key); err != nil { return err } logx.Infof("删除报告数据成功:查询记录 %s,模块 %s,已将对应模块的 data 字段设置为 null", queryId, featureApiId) return nil } // getQueryModel 获取查询记录,如果不存在或数据为空则返回nil func (s *WhitelistService) getQueryModel(ctx context.Context, queryId string) (*model.Query, error) { queryModel, err := s.QueryModel.FindOne(ctx, queryId) if err != nil { if errors.Is(err, model.ErrNotFound) { logx.Infof("删除报告数据:查询记录 %s 不存在,跳过删除", queryId) return nil, nil } return nil, errors.Wrap(err, "查询报告记录失败") } // 如果报告数据为空,直接返回 if !queryModel.QueryData.Valid || queryModel.QueryData.String == "" { logx.Infof("删除报告数据:查询记录 %s 对应的报告数据为空,跳过删除", queryId) return nil, nil } return queryModel, nil } // decryptQueryData 解密并解析报告数据 func (s *WhitelistService) decryptQueryData(queryModel *model.Query) ([]map[string]interface{}, []byte, error) { // 获取加密密钥 secretKey := s.config.Encrypt.SecretKey key, decodeErr := hex.DecodeString(secretKey) if decodeErr != nil { return nil, nil, errors.Wrap(decodeErr, "获取AES密钥失败") } // 解密报告数据 decryptedData, decryptErr := crypto.AesDecrypt(queryModel.QueryData.String, key) if decryptErr != nil { return nil, nil, errors.Wrap(decryptErr, "解密报告数据失败") } // 解析JSON数组 var dataArray []map[string]interface{} unmarshalErr := json.Unmarshal(decryptedData, &dataArray) if unmarshalErr != nil { return nil, nil, errors.Wrap(unmarshalErr, "解析报告数据失败") } return dataArray, key, nil } // clearFeatureData 清空指定模块的数据(将 data 字段设置为 null) // 返回修改后的数组和是否进行了修改 func (s *WhitelistService) clearFeatureData(dataArray []map[string]interface{}, mainApiId string) ([]map[string]interface{}, bool) { modifiedArray := make([]map[string]interface{}, 0, len(dataArray)) hasModified := false for _, item := range dataArray { // 深拷贝 item,避免修改原数据 newItem := make(map[string]interface{}) for k, v := range item { newItem[k] = v } apiID, ok := item["apiID"].(string) if !ok { // 如果apiID不存在或类型不对,保留原样 modifiedArray = append(modifiedArray, newItem) continue } // 提取主模块ID进行比较 itemMainApiId := s.extractMainApiId(apiID) // 如果主模块ID匹配,将 data 字段设置为 null if itemMainApiId == mainApiId { newItem["data"] = nil hasModified = true } modifiedArray = append(modifiedArray, newItem) } return modifiedArray, hasModified } // updateQueryData 重新加密并更新数据库 func (s *WhitelistService) updateQueryData( ctx context.Context, session sqlx.Session, queryModel *model.Query, filteredArray []map[string]interface{}, key []byte, ) error { // 重新序列化 filteredBytes, marshalErr := json.Marshal(filteredArray) if marshalErr != nil { return errors.Wrap(marshalErr, "序列化过滤后的报告数据失败") } // 重新加密 encryptedData, encryptErr := crypto.AesEncrypt(filteredBytes, key) if encryptErr != nil { return errors.Wrap(encryptErr, "加密过滤后的报告数据失败") } // 更新数据库 queryModel.QueryData = sql.NullString{ String: encryptedData, Valid: true, } updateErr := s.QueryModel.UpdateWithVersion(ctx, session, queryModel) if updateErr != nil { return errors.Wrap(updateErr, "更新报告数据失败") } return nil } // CheckQueryDataContainsFeature 检查报告数据中是否包含指定的模块 // 返回 true 表示包含该模块,false 表示不包含(已删除) func (s *WhitelistService) CheckQueryDataContainsFeature( ctx context.Context, queryId string, featureApiId string, ) (bool, error) { // 1. 获取查询记录 queryModel, err := s.getQueryModel(ctx, queryId) if err != nil { return false, err } if queryModel == nil { // 报告不存在,认为数据已删除 return false, nil } // 2. 解密并解析报告数据 mainApiId := s.extractMainApiId(featureApiId) dataArray, _, err := s.decryptQueryData(queryModel) if err != nil { return false, err } // 3. 检查数据中是否包含该模块(且 data 不为 null) for _, item := range dataArray { apiID, ok := item["apiID"].(string) if !ok { continue } // 提取主模块ID进行比较 itemMainApiId := s.extractMainApiId(apiID) if itemMainApiId == mainApiId { // 找到了该模块,检查 data 字段 dataValue, exists := item["data"] if !exists || dataValue == nil { // data 字段不存在或为 null,认为数据已删除 return false, nil } // data 字段存在且不为 null,认为数据存在 return true, nil } } // 未找到该模块的数据,说明已删除 return false, nil } // ProcessPaidWhitelistOrder 处理已支付的白名单订单:创建白名单记录并删除报告数据 // order: 支付订单(Order表) // whitelistOrder: 白名单订单(WhitelistOrder表) func (s *WhitelistService) ProcessPaidWhitelistOrder( ctx context.Context, session sqlx.Session, order *model.Order, whitelistOrder *model.WhitelistOrder, ) error { if whitelistOrder.Status != 2 { // 只处理已支付状态 return nil } // 查询订单明细 itemBuilder := s.WhitelistOrderItemModel.SelectBuilder(). Where("order_id = ?", whitelistOrder.Id) items, err := s.WhitelistOrderItemModel.FindAll(ctx, itemBuilder, "") if err != nil { return errors.Wrap(err, "查询白名单订单明细失败") } // 为每个明细创建白名单记录并删除报告数据 for _, item := range items { // 创建白名单记录 wl := &model.UserFeatureWhitelist{ Id: uuid.NewString(), IdCard: whitelistOrder.IdCard, FeatureId: item.FeatureId, FeatureApiId: item.FeatureApiId, UserId: whitelistOrder.UserId, OrderId: lzUtils.StringToNullString(""), // 查询订单ID,如果有的话会在后续步骤中设置 WhitelistOrderId: lzUtils.StringToNullString(whitelistOrder.Id), Amount: item.Price, Status: 1, // 生效 } if _, err := s.UserFeatureWhitelistModel.Insert(ctx, session, wl); err != nil { return errors.Wrap(err, "创建白名单记录失败") } // 尝试删除报告数据 // 注意:由于支付回调时可能不知道具体的查询订单ID,这里先尝试根据 id_card 查找 // 如果找不到对应的报告,就跳过删除步骤(不影响主流程) // 实际的报告数据删除应该在 OfflineFeature 接口中完成(如果提供了 orderId) // 这里暂时不删除,因为无法确定是哪个具体的查询订单 logx.Infof("白名单订单支付成功:订单 %s,模块 %s,已创建白名单记录。如需删除报告数据,请在 OfflineFeature 接口中提供查询订单ID", whitelistOrder.OrderNo, item.FeatureApiId) } return nil }