add
This commit is contained in:
@@ -96,6 +96,7 @@ type ApiApplicationServiceImpl struct {
|
||||
subscriptionService *product_services.ProductSubscriptionService
|
||||
balanceAlertService finance_services.BalanceAlertService
|
||||
subordinateRepo subordinate_repositories.SubordinateRepository
|
||||
queryWhitelistSvc services.QueryWhitelistService
|
||||
}
|
||||
|
||||
func NewApiApplicationService(
|
||||
@@ -116,6 +117,7 @@ func NewApiApplicationService(
|
||||
exportManager *export.ExportManager,
|
||||
balanceAlertService finance_services.BalanceAlertService,
|
||||
subordinateRepo subordinate_repositories.SubordinateRepository,
|
||||
queryWhitelistSvc services.QueryWhitelistService,
|
||||
) ApiApplicationService {
|
||||
service := &ApiApplicationServiceImpl{
|
||||
apiCallService: apiCallService,
|
||||
@@ -135,6 +137,7 @@ func NewApiApplicationService(
|
||||
subscriptionService: subscriptionService,
|
||||
balanceAlertService: balanceAlertService,
|
||||
subordinateRepo: subordinateRepo,
|
||||
queryWhitelistSvc: queryWhitelistSvc,
|
||||
}
|
||||
|
||||
return service
|
||||
@@ -367,6 +370,12 @@ func extractParentAccessID(params map[string]interface{}) (string, bool) {
|
||||
|
||||
// callExternalApi 同步调用外部API
|
||||
func (s *ApiApplicationServiceImpl) callExternalApi(ctx context.Context, cmd *commands.ApiCallCommand, validation *dto.ApiCallValidationResult) (string, error) {
|
||||
// 查询白名单拦截:命中则返回「查询为空」,不调用上游、不扣费
|
||||
if s.queryWhitelistSvc != nil &&
|
||||
s.queryWhitelistSvc.ShouldReturnEmpty(ctx, validation.GetUserID(), cmd.ApiName, validation.RequestParams) {
|
||||
return "", ErrQueryEmpty
|
||||
}
|
||||
|
||||
// 创建CallContext
|
||||
callContext := &processors.CallContext{
|
||||
ContractCode: validation.ContractCode,
|
||||
|
||||
71
internal/application/api/dto/query_whitelist_dto.go
Normal file
71
internal/application/api/dto/query_whitelist_dto.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"tyapi-server/internal/domains/api/entities"
|
||||
)
|
||||
|
||||
type QueryWhitelistEntryRequest struct {
|
||||
UserID string `json:"user_id" validate:"required"`
|
||||
Name string `json:"name" validate:"required,min=1,max=100"`
|
||||
IDCard string `json:"id_card" validate:"required"`
|
||||
APICodes []string `json:"api_codes" validate:"required,min=1,dive,required"`
|
||||
Remark string `json:"remark" validate:"max=500"`
|
||||
}
|
||||
|
||||
type QueryWhitelistEntryUpdateRequest struct {
|
||||
Name string `json:"name" validate:"omitempty,min=1,max=100"`
|
||||
IDCard string `json:"id_card" validate:"omitempty"`
|
||||
APICodes []string `json:"api_codes" validate:"omitempty,min=1,dive,required"`
|
||||
Remark string `json:"remark" validate:"max=500"`
|
||||
}
|
||||
|
||||
type QueryWhitelistStatusRequest struct {
|
||||
Status string `json:"status" validate:"required,oneof=enabled disabled"`
|
||||
}
|
||||
|
||||
type QueryWhitelistEntryResponse struct {
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
IsGlobal bool `json:"is_global"`
|
||||
Name string `json:"name"`
|
||||
IDCardMasked string `json:"id_card_masked"`
|
||||
APICodes []string `json:"api_codes"`
|
||||
Status string `json:"status"`
|
||||
Remark string `json:"remark"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type QueryWhitelistListResponse struct {
|
||||
Items []QueryWhitelistEntryResponse `json:"items"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
Size int `json:"page_size"`
|
||||
}
|
||||
|
||||
type QueryWhitelistImportLegacyResponse struct {
|
||||
Imported int `json:"imported"`
|
||||
Skipped int `json:"skipped"`
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
func NewQueryWhitelistEntryResponse(entry *entities.QueryWhitelistEntry) QueryWhitelistEntryResponse {
|
||||
apiCodes := []string(entry.APICodes)
|
||||
if apiCodes == nil {
|
||||
apiCodes = []string{}
|
||||
}
|
||||
return QueryWhitelistEntryResponse{
|
||||
ID: entry.ID,
|
||||
UserID: entry.UserID,
|
||||
IsGlobal: entry.IsGlobal(),
|
||||
Name: entry.Name,
|
||||
IDCardMasked: entry.IDCardMasked,
|
||||
APICodes: apiCodes,
|
||||
Status: entry.Status,
|
||||
Remark: entry.Remark,
|
||||
CreatedAt: entry.CreatedAt,
|
||||
UpdatedAt: entry.UpdatedAt,
|
||||
}
|
||||
}
|
||||
309
internal/application/api/query_whitelist_application_service.go
Normal file
309
internal/application/api/query_whitelist_application_service.go
Normal file
@@ -0,0 +1,309 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"tyapi-server/internal/application/api/dto"
|
||||
"tyapi-server/internal/domains/api/entities"
|
||||
"tyapi-server/internal/domains/api/repositories"
|
||||
api_services "tyapi-server/internal/domains/api/services"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type QueryWhitelistApplicationService interface {
|
||||
CreateEntry(ctx context.Context, adminUserID string, req *dto.QueryWhitelistEntryRequest) (*dto.QueryWhitelistEntryResponse, error)
|
||||
UpdateEntry(ctx context.Context, adminUserID, id string, req *dto.QueryWhitelistEntryUpdateRequest) (*dto.QueryWhitelistEntryResponse, error)
|
||||
UpdateEntryStatus(ctx context.Context, adminUserID, id, status string) (*dto.QueryWhitelistEntryResponse, error)
|
||||
DeleteEntry(ctx context.Context, id string) error
|
||||
GetEntry(ctx context.Context, id string) (*dto.QueryWhitelistEntryResponse, error)
|
||||
ListEntries(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*dto.QueryWhitelistListResponse, error)
|
||||
ImportLegacyEntries(ctx context.Context, adminUserID string) (*dto.QueryWhitelistImportLegacyResponse, error)
|
||||
}
|
||||
|
||||
type QueryWhitelistApplicationServiceImpl struct {
|
||||
repo repositories.QueryWhitelistRepository
|
||||
queryWhitelistSvc api_services.QueryWhitelistService
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
func NewQueryWhitelistApplicationService(
|
||||
repo repositories.QueryWhitelistRepository,
|
||||
queryWhitelistSvc api_services.QueryWhitelistService,
|
||||
logger *zap.Logger,
|
||||
) QueryWhitelistApplicationService {
|
||||
return &QueryWhitelistApplicationServiceImpl{
|
||||
repo: repo,
|
||||
queryWhitelistSvc: queryWhitelistSvc,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *QueryWhitelistApplicationServiceImpl) CreateEntry(
|
||||
ctx context.Context,
|
||||
adminUserID string,
|
||||
req *dto.QueryWhitelistEntryRequest,
|
||||
) (*dto.QueryWhitelistEntryResponse, error) {
|
||||
if err := validateQueryWhitelistRequest(req.UserID, req.Name, req.IDCard, req.APICodes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
idCardHash := api_services.HashIDCard(req.IDCard)
|
||||
exists, err := s.repo.ExistsByUserIDCardHashAndName(ctx, req.UserID, idCardHash, strings.TrimSpace(req.Name), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if exists {
|
||||
return nil, fmt.Errorf("该用户下已存在相同的身份证与姓名规则")
|
||||
}
|
||||
|
||||
entry := &entities.QueryWhitelistEntry{
|
||||
UserID: strings.TrimSpace(req.UserID),
|
||||
Name: normalizeWhitelistName(req.Name),
|
||||
IDCardHash: idCardHash,
|
||||
IDCardMasked: api_services.MaskIDCard(req.IDCard),
|
||||
APICodes: entities.APICodeList(req.APICodes),
|
||||
Status: entities.QueryWhitelistStatusEnabled,
|
||||
Remark: strings.TrimSpace(req.Remark),
|
||||
CreatedBy: &adminUserID,
|
||||
}
|
||||
if err := s.repo.Create(ctx, entry); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.queryWhitelistSvc.InvalidateCache(entry.UserID, idCardHash)
|
||||
resp := dto.NewQueryWhitelistEntryResponse(entry)
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
func (s *QueryWhitelistApplicationServiceImpl) UpdateEntry(
|
||||
ctx context.Context,
|
||||
adminUserID, id string,
|
||||
req *dto.QueryWhitelistEntryUpdateRequest,
|
||||
) (*dto.QueryWhitelistEntryResponse, error) {
|
||||
entry, err := s.repo.FindByID(ctx, id)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fmt.Errorf("规则不存在")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
oldHash := entry.IDCardHash
|
||||
|
||||
if req.Name != "" {
|
||||
entry.Name = normalizeWhitelistName(req.Name)
|
||||
}
|
||||
if req.IDCard != "" {
|
||||
if err := validateIDCard(req.IDCard); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
entry.IDCardHash = api_services.HashIDCard(req.IDCard)
|
||||
entry.IDCardMasked = api_services.MaskIDCard(req.IDCard)
|
||||
}
|
||||
if len(req.APICodes) > 0 {
|
||||
if err := validateAPICodes(req.APICodes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
entry.APICodes = entities.APICodeList(req.APICodes)
|
||||
}
|
||||
if req.Remark != "" || req.Remark == "" {
|
||||
entry.Remark = strings.TrimSpace(req.Remark)
|
||||
}
|
||||
|
||||
exists, err := s.repo.ExistsByUserIDCardHashAndName(ctx, entry.UserID, entry.IDCardHash, entry.Name, entry.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if exists {
|
||||
return nil, fmt.Errorf("该用户下已存在相同的身份证与姓名规则")
|
||||
}
|
||||
|
||||
entry.UpdatedBy = &adminUserID
|
||||
if err := s.repo.Update(ctx, entry); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.queryWhitelistSvc.InvalidateCache(entry.UserID, oldHash)
|
||||
s.queryWhitelistSvc.InvalidateCache(entry.UserID, entry.IDCardHash)
|
||||
resp := dto.NewQueryWhitelistEntryResponse(entry)
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
func (s *QueryWhitelistApplicationServiceImpl) UpdateEntryStatus(
|
||||
ctx context.Context,
|
||||
adminUserID, id, status string,
|
||||
) (*dto.QueryWhitelistEntryResponse, error) {
|
||||
entry, err := s.repo.FindByID(ctx, id)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fmt.Errorf("规则不存在")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
entry.Status = status
|
||||
entry.UpdatedBy = &adminUserID
|
||||
if err := s.repo.Update(ctx, entry); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.queryWhitelistSvc.InvalidateCache(entry.UserID, entry.IDCardHash)
|
||||
resp := dto.NewQueryWhitelistEntryResponse(entry)
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
func (s *QueryWhitelistApplicationServiceImpl) DeleteEntry(ctx context.Context, id string) error {
|
||||
entry, err := s.repo.FindByID(ctx, id)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fmt.Errorf("规则不存在")
|
||||
}
|
||||
return err
|
||||
}
|
||||
if err := s.repo.Delete(ctx, id); err != nil {
|
||||
return err
|
||||
}
|
||||
s.queryWhitelistSvc.InvalidateCache(entry.UserID, entry.IDCardHash)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *QueryWhitelistApplicationServiceImpl) GetEntry(ctx context.Context, id string) (*dto.QueryWhitelistEntryResponse, error) {
|
||||
entry, err := s.repo.FindByID(ctx, id)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fmt.Errorf("规则不存在")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
resp := dto.NewQueryWhitelistEntryResponse(entry)
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
func (s *QueryWhitelistApplicationServiceImpl) ListEntries(
|
||||
ctx context.Context,
|
||||
filters map[string]interface{},
|
||||
options interfaces.ListOptions,
|
||||
) (*dto.QueryWhitelistListResponse, error) {
|
||||
if idCard, ok := filters["id_card"].(string); ok && idCard != "" {
|
||||
filters["id_card_hash"] = api_services.HashIDCard(idCard)
|
||||
delete(filters, "id_card")
|
||||
}
|
||||
entries, total, err := s.repo.List(ctx, filters, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items := make([]dto.QueryWhitelistEntryResponse, 0, len(entries))
|
||||
for _, entry := range entries {
|
||||
items = append(items, dto.NewQueryWhitelistEntryResponse(entry))
|
||||
}
|
||||
page := options.Page
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
size := options.PageSize
|
||||
if size < 1 {
|
||||
size = 20
|
||||
}
|
||||
return &dto.QueryWhitelistListResponse{
|
||||
Items: items,
|
||||
Total: total,
|
||||
Page: page,
|
||||
Size: size,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *QueryWhitelistApplicationServiceImpl) ImportLegacyEntries(
|
||||
ctx context.Context,
|
||||
adminUserID string,
|
||||
) (*dto.QueryWhitelistImportLegacyResponse, error) {
|
||||
imported := 0
|
||||
skipped := 0
|
||||
for _, idCard := range LegacyHardcodedIDCards {
|
||||
hash := api_services.HashIDCard(idCard)
|
||||
exists, err := s.repo.ExistsByUserIDCardHashAndName(
|
||||
ctx,
|
||||
entities.QueryWhitelistGlobalUserID,
|
||||
hash,
|
||||
entities.QueryWhitelistWildcardName,
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if exists {
|
||||
skipped++
|
||||
continue
|
||||
}
|
||||
entry := &entities.QueryWhitelistEntry{
|
||||
UserID: entities.QueryWhitelistGlobalUserID,
|
||||
Name: entities.QueryWhitelistWildcardName,
|
||||
IDCardHash: hash,
|
||||
IDCardMasked: api_services.MaskIDCard(idCard),
|
||||
APICodes: entities.APICodeList{"*"},
|
||||
Status: entities.QueryWhitelistStatusEnabled,
|
||||
Remark: "自硬编码迁移-全局",
|
||||
CreatedBy: &adminUserID,
|
||||
}
|
||||
if err := s.repo.Create(ctx, entry); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.queryWhitelistSvc.InvalidateCache(entities.QueryWhitelistGlobalUserID, hash)
|
||||
imported++
|
||||
}
|
||||
return &dto.QueryWhitelistImportLegacyResponse{
|
||||
Imported: imported,
|
||||
Skipped: skipped,
|
||||
Total: len(LegacyHardcodedIDCards),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func validateQueryWhitelistRequest(userID, name, idCard string, apiCodes []string) error {
|
||||
userID = strings.TrimSpace(userID)
|
||||
if userID == "" {
|
||||
return fmt.Errorf("user_id 不能为空")
|
||||
}
|
||||
if strings.TrimSpace(name) == "" {
|
||||
return fmt.Errorf("name 不能为空")
|
||||
}
|
||||
if err := validateIDCard(idCard); err != nil {
|
||||
return err
|
||||
}
|
||||
return validateAPICodes(apiCodes)
|
||||
}
|
||||
|
||||
func validateIDCard(idCard string) error {
|
||||
idCard = api_services.NormalizeIDCard(idCard)
|
||||
if len(idCard) != 18 {
|
||||
return fmt.Errorf("身份证号格式不正确")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateAPICodes(apiCodes []string) error {
|
||||
if len(apiCodes) == 0 {
|
||||
return fmt.Errorf("api_codes 不能为空")
|
||||
}
|
||||
hasWildcard := false
|
||||
for _, code := range apiCodes {
|
||||
code = strings.TrimSpace(code)
|
||||
if code == "" {
|
||||
return fmt.Errorf("api_codes 不能包含空值")
|
||||
}
|
||||
if code == "*" {
|
||||
hasWildcard = true
|
||||
}
|
||||
}
|
||||
if hasWildcard && len(apiCodes) > 1 {
|
||||
return fmt.Errorf("api_codes 包含 * 时不能与其他编码混用")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalizeWhitelistName(name string) string {
|
||||
name = strings.TrimSpace(name)
|
||||
if name == entities.QueryWhitelistWildcardName {
|
||||
return entities.QueryWhitelistWildcardName
|
||||
}
|
||||
return name
|
||||
}
|
||||
22
internal/application/api/query_whitelist_legacy.go
Normal file
22
internal/application/api/query_whitelist_legacy.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package api
|
||||
|
||||
// LegacyHardcodedIDCards 原 processor 硬编码的身份证号,导入为全局规则(user_id=*,name=*)
|
||||
var LegacyHardcodedIDCards = []string{
|
||||
"350681198611130611",
|
||||
"622301200006250550",
|
||||
"320682198910134998",
|
||||
"640102198708020925",
|
||||
"420624197310234034",
|
||||
"350104198501184416",
|
||||
"410521198606018056",
|
||||
"410482198504029333",
|
||||
"370982199012037272",
|
||||
"431027198810290730",
|
||||
"362502199510298017",
|
||||
"340826199008250378",
|
||||
"321027198304072129",
|
||||
"420116198907031413",
|
||||
"13032319930128263X",
|
||||
"350681198412013041",
|
||||
"33072619741031111X",
|
||||
}
|
||||
Reference in New Issue
Block a user