diff --git a/docs/查询白名单公开添加接口对接文档.md b/docs/查询白名单公开添加接口对接文档.md index c3c9bd4..f8dc9ba 100644 --- a/docs/查询白名单公开添加接口对接文档.md +++ b/docs/查询白名单公开添加接口对接文档.md @@ -1,18 +1,18 @@ -# 查询白名单公开添加接口 — 对接文档 +# 查询白名单公开接口 — 对接文档 > 面向**下游调用方**(API 用户 / 合作方),与上游数据源配置无关。 > 平台侧仅在 `config.yaml` 顶部保留**一份** `query_whitelist.public_api` 配置。 --- -## 1. 接口概述 +## 1. 接口一览 -| 项 | 说明 | -|----|------| -| 地址 | `POST /api/v1/query-whitelist/entries` | -| 用途 | 为**当前 access_id 对应用户**添加查询白名单规则 | -| 生效范围 | **仅对该用户生效**(`user_id` 自动绑定,不可创建全局规则) | -| 计费 | 否 | +| 接口 | 方法 | 路径 | 用途 | +|------|------|------|------| +| **创建规则** | POST | `/api/v1/query-whitelist/entries` | 新建一条屏蔽规则;同用户+身份证+姓名已存在则 **拒绝**(`1013`) | +| **追加接口** | POST | `/api/v1/query-whitelist/entries/append` | 向**已有**规则追加 `api_codes`(去重合并),**不新建记录**;规则不存在则 **拒绝**(`1014`) | + +两条接口鉴权、加密、响应格式相同,仅业务语义不同。 命中白名单后,对应 API 调用将返回 `1000 查询为空`,不调用上游、不扣费。 @@ -20,7 +20,7 @@ ## 2. 鉴权(双重 + IP) -### 2.1 请求头 +### 2.1 请求头(两个接口共用) | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| @@ -28,7 +28,7 @@ | `Whitelist-Mgmt-Key` | string | 是 | 平台下发的**独立管理密钥**(与 Access Key 不同) | | `Content-Type` | string | 是 | 固定为 `application/json` | -### 2.2 请求体(加密) +### 2.2 请求体(加密,两个接口共用) 与业务 API 相同,外层仅传 `data` 字段: @@ -44,7 +44,7 @@ 使用目标用户的 **Access Key**(16 进制 Secret Key)加密。Access Key **仅用于本地加密,不要写入明文 JSON**。 -### 2.3 明文业务参数(加密前 JSON) +### 2.3 明文业务参数(加密前 JSON,两个接口字段相同) **无需在明文里传 `key` 或 `access_id`**。身份校验方式与业务 API 相同:请求头携带 `Access-Id`,服务端用该用户 Access Key 解密 `data`,解密成功即证明调用方持有正确密钥。 @@ -61,7 +61,8 @@ |------|------|------|------| | `name` | string | 是 | 姓名;填 `*` 表示只匹配身份证,不校验姓名 | | `id_card` | string | 是 | 18 位身份证号 | -| `api_codes` | string[] | 是 | 生效的产品编码列表;**必须为 JSON 数组**,至少 1 个元素;元素类型为 string;**禁止**传字符串/对象等非数组类型;**禁止**包含通配符 `*`(不可写 `["*"]` 或与其他编码混用) | +| `api_codes` | string[] | 是 | 产品编码列表;**必须为 JSON 数组**,至少 1 个元素;元素为 string;**禁止**非数组类型;**禁止**通配符 `*` | +| `remark` | string | 否 | 备注,最长 500 字符;追加接口传入非空时会覆盖原备注 | > **`api_codes` 约束(公开接口专属)** > @@ -72,7 +73,6 @@ > | 通配全部 `*` | ❌ | `["*"]` | > | 空数组 | ❌ | `[]` | > | 非字符串元素 | ❌ | `[123]`、`[{}]` | -| `remark` | string | 否 | 备注,最长 500 字符 | ### 2.4 IP 白名单 @@ -80,7 +80,61 @@ --- -## 3. 加密算法 +## 3. 两个接口的业务区别 + +规则的唯一键为 **`user_id` + 身份证 + `name`**,每个组合在表里只有**一条**记录,`api_codes` 存在该行的 JSON 数组中。 + +### 3.1 创建接口 `POST /entries` + +用于**首次**为某身份证建立屏蔽规则。 + +| 场景 | 行为 | +|------|------| +| 规则不存在 | 新建 1 条记录 | +| 同用户+身份证+姓名已存在 | 返回 `1013 规则已存在`,**不新建、不合并** | + +示例:第一次传 `api_codes: ["FLXG0V4B"]` → 成功新建。 + +### 3.2 追加接口 `POST /entries/append` + +用于在**已有规则**上补充更多生效接口,适合「先挡一个、后续再加」的场景。 + +| 场景 | 行为 | +|------|------| +| 规则已存在 | **更新原记录**:将本次 `api_codes` 与已有列表**去重合并**(保留原顺序,新编码追加在后),**不新建第二条** | +| 本次编码均已存在 | 仍返回成功(幂等),`api_codes` 不变 | +| 规则不存在 | 返回 `1014 规则不存在,请先调用创建接口` | +| 原规则 `api_codes` 含 `*`(全部接口) | 返回 `1003`,公开接口不支持对「全部接口」规则追加 | + +**追加示例:** + +``` +已有记录:user_id=U1, id_card=350681..., name=*, api_codes=["FLXG0V4B"] + +第二次调用 append: + api_codes: ["JRZQ8A2D"] + +结果(同一条记录被更新): + api_codes: ["FLXG0V4B", "JRZQ8A2D"] ← 合并去重,非新建 +``` + +``` +第三次再 append: + api_codes: ["JRZQ8A2D", "FLXG2E8F"] + +结果: + api_codes: ["FLXG0V4B", "JRZQ8A2D", "FLXG2E8F"] ← JRZQ8A2D 已存在则跳过 +``` + +### 3.3 推荐调用顺序 + +1. 首次屏蔽 → 调 **创建接口**,可一次传齐所有 `api_codes` +2. 后续补接口 → 调 **追加接口**,只传**新增**的编码即可 +3. 若创建时返回 `1013` → 改调 **追加接口** + +--- + +## 4. 加密算法 与业务 API 完全一致(AES-128-CBC + PKCS7 + 随机 IV): @@ -92,7 +146,7 @@ --- -## 4. 响应格式 +## 5. 响应格式 与业务 API 一致: @@ -127,11 +181,11 @@ --- -## 5. 错误码 +## 6. 错误码 | code | message | 说明 | |------|---------|------| -| 0 | 业务成功 | 创建成功 | +| 0 | 业务成功 | 创建或追加成功 | | 1002 | 解密失败 | `data` 解密失败 | | 1003 | 请求参数结构不正确 | 明文参数缺失或格式错误 | | 1004 | 未经授权的IP | IP 不在白名单 | @@ -140,12 +194,15 @@ | 1010 | 缺少管理密钥 | 未传 `Whitelist-Mgmt-Key` | | 1011 | 管理密钥无效 | 管理密钥错误 | | 1012 | 公开接口未启用 | 服务端 `public_api.enabled=false` | -| 1013 | 规则已存在 | 同用户下身份证+姓名重复 | +| 1013 | 规则已存在 | 创建接口:同用户下身份证+姓名重复 | +| 1014 | 规则不存在 | 追加接口:须先调用创建接口 | | 1001 | 接口异常 | 系统错误 | --- -## 6. Node.js 调用示例 +## 7. Node.js 调用示例 + +### 7.1 创建规则 ```javascript const crypto = require('crypto'); @@ -153,7 +210,7 @@ const crypto = require('crypto'); const BASE_URL = 'https://api.tianyuanapi.com'; const ACCESS_ID = process.env.ACCESS_ID; const ACCESS_KEY = process.env.ACCESS_KEY; // 用户 Access Key -const MGMT_KEY = '2R12TmEc1e8P3p69RdIoN5Ykjk%H@4orPy7DZv7MXpGByoEL'; // 平台管理密钥,见文档 §7 +const MGMT_KEY = '2R12TmEc1e8P3p69RdIoN5Ykjk%H@4orPy7DZv7MXpGByoEL'; // 平台管理密钥,见文档 §8 function encrypt(data, keyHex) { const key = Buffer.from(keyHex, 'hex'); @@ -204,9 +261,40 @@ async function addWhitelistEntry() { addWhitelistEntry(); ``` +### 7.2 追加生效接口(去重合并) + +```javascript +async function appendWhitelistApiCodes() { + const payload = { + name: '*', + id_card: '350681198611130611', + api_codes: ['JRZQ8A2D'], // 只传本次要追加的编码 + remark: '', + }; + + const res = await fetch(`${BASE_URL}/api/v1/query-whitelist/entries/append`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Access-Id': ACCESS_ID, + 'Whitelist-Mgmt-Key': MGMT_KEY, + }, + body: JSON.stringify({ data: encrypt(JSON.stringify(payload), ACCESS_KEY) }), + }); + + const json = await res.json(); + console.log('追加响应:', json); + if (json.code === 0 && json.data) { + console.log('合并后规则:', JSON.parse(decrypt(json.data, ACCESS_KEY))); + } +} + +// appendWhitelistApiCodes(); +``` + --- -## 7. 平台配置(仅一份) +## 8. 平台配置(仅一份) `config.yaml` 顶部(`app` 段之后),**不要**写在上游数据源配置块内: @@ -232,7 +320,7 @@ query_whitelist: --- -## 8. 字段说明补充 +## 9. 字段说明补充 ### `name = *` @@ -248,7 +336,7 @@ query_whitelist: --- -## 9. 数据库变更 +## 10. 数据库变更 新增字段 `operation_ip`(记录公开接口添加时的客户端 IP): diff --git a/internal/application/api/errors.go b/internal/application/api/errors.go index ccaceea..8314044 100644 --- a/internal/application/api/errors.go +++ b/internal/application/api/errors.go @@ -29,6 +29,7 @@ var ( ErrInvalidMgmtKey = errors.New("管理密钥无效") ErrPublicAPIDisabled = errors.New("公开接口未启用") ErrWhitelistExists = errors.New("规则已存在") + ErrWhitelistNotFound = errors.New("规则不存在") ) // 错误码映射 - 严格按照用户要求 @@ -58,6 +59,7 @@ var ErrorCodeMap = map[error]int{ ErrInvalidMgmtKey: 1011, ErrPublicAPIDisabled: 1012, ErrWhitelistExists: 1013, + ErrWhitelistNotFound: 1014, } // GetErrorCode 获取错误对应的错误码 diff --git a/internal/application/api/query_whitelist_application_service.go b/internal/application/api/query_whitelist_application_service.go index 69ba03e..736a5f9 100644 --- a/internal/application/api/query_whitelist_application_service.go +++ b/internal/application/api/query_whitelist_application_service.go @@ -20,6 +20,7 @@ import ( type QueryWhitelistApplicationService interface { CreateEntry(ctx context.Context, adminUserID string, req *dto.QueryWhitelistEntryRequest) (*dto.QueryWhitelistEntryResponse, error) CreateEntryPublic(ctx context.Context, headerAccessID, managementKey, clientIP, encryptedData string) (*dto.QueryWhitelistEntryResponse, string, error) + AppendEntryPublic(ctx context.Context, headerAccessID, managementKey, clientIP, encryptedData string) (*dto.QueryWhitelistEntryResponse, string, 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 diff --git a/internal/application/api/query_whitelist_public_service.go b/internal/application/api/query_whitelist_public_service.go index 2756656..462db2f 100644 --- a/internal/application/api/query_whitelist_public_service.go +++ b/internal/application/api/query_whitelist_public_service.go @@ -4,6 +4,8 @@ import ( "context" "crypto/subtle" "encoding/json" + "errors" + "fmt" "strings" "tyapi-server/internal/application/api/dto" @@ -11,9 +13,9 @@ import ( "tyapi-server/internal/domains/api/entities" api_services "tyapi-server/internal/domains/api/services" "tyapi-server/internal/shared/crypto" - "fmt" "go.uber.org/zap" + "gorm.io/gorm" ) const queryWhitelistMgmtKeyHeader = "Whitelist-Mgmt-Key" @@ -23,61 +25,17 @@ func QueryWhitelistMgmtKeyHeader() string { return queryWhitelistMgmtKeyHeader } -// CreateEntryPublic 公开接口:解密业务参数后为当前 access_id 对应用户添加规则(仅对该用户生效) +// CreateEntryPublic 公开接口:新建规则(同用户+身份证+姓名已存在则拒绝) func (s *QueryWhitelistApplicationServiceImpl) CreateEntryPublic( ctx context.Context, headerAccessID, managementKey, clientIP, encryptedData string, ) (*dto.QueryWhitelistEntryResponse, string, error) { - if !s.config.QueryWhitelist.PublicAPI.Enabled { - return nil, "", ErrPublicAPIDisabled - } - if strings.TrimSpace(managementKey) == "" { - return nil, "", ErrMissingMgmtKey - } - if !constantTimeEqual(managementKey, s.config.QueryWhitelist.PublicAPI.ManagementKey) { - return nil, "", ErrInvalidMgmtKey - } - headerAccessID = strings.TrimSpace(headerAccessID) - if headerAccessID == "" { - return nil, "", ErrMissingAccessId - } - - apiUser, err := s.apiUserService.LoadApiUserByAccessId(ctx, headerAccessID) + apiUser, payload, err := s.preparePublicRequest(ctx, headerAccessID, managementKey, clientIP, encryptedData) if err != nil { - s.logger.Warn("公开白名单接口 AccessId 无效", zap.String("access_id", headerAccessID), zap.Error(err)) - return nil, "", ErrInvalidAccessId - } - if apiUser.IsFrozen() { - return nil, "", ErrFrozenAccount + return nil, "", err } - decrypted, err := crypto.AesDecrypt(encryptedData, apiUser.SecretKey) - if err != nil { - s.logger.Warn("公开白名单接口解密失败", zap.String("access_id", headerAccessID), zap.Error(err)) - return nil, "", ErrDecryptFail - } - - var raw map[string]json.RawMessage - if err := json.Unmarshal(decrypted, &raw); err != nil { - return nil, "", ErrRequestParam - } - if err := validatePublicAPICodesField(raw["api_codes"]); err != nil { - return nil, "", MapWhitelistAppError(err) - } - - var payload dto.QueryWhitelistPublicPayload - if err := json.Unmarshal(decrypted, &payload); err != nil { - return nil, "", ErrRequestParam - } - - if !s.config.App.IsDevelopment() && !apiUser.IsWhiteListed(clientIP) { - s.logger.Warn("公开白名单接口 IP 未授权", - zap.String("access_id", headerAccessID), - zap.String("client_ip", clientIP)) - return nil, "", ErrInvalidIP - } - - createdBy := "public_api:" + headerAccessID + createdBy := "public_api:" + strings.TrimSpace(headerAccessID) resp, err := s.createEntry(ctx, createdBy, &dto.QueryWhitelistEntryRequest{ UserID: apiUser.UserId, Name: payload.Name, @@ -88,6 +46,115 @@ func (s *QueryWhitelistApplicationServiceImpl) CreateEntryPublic( return resp, apiUser.SecretKey, MapWhitelistAppError(err) } +// AppendEntryPublic 公开接口:向已有规则追加 api_codes(去重合并,不新建记录) +func (s *QueryWhitelistApplicationServiceImpl) AppendEntryPublic( + ctx context.Context, + headerAccessID, managementKey, clientIP, encryptedData string, +) (*dto.QueryWhitelistEntryResponse, string, error) { + apiUser, payload, err := s.preparePublicRequest(ctx, headerAccessID, managementKey, clientIP, encryptedData) + if err != nil { + return nil, "", err + } + + if err := validateIDCard(payload.IDCard); err != nil { + return nil, "", MapWhitelistAppError(err) + } + if strings.TrimSpace(payload.Name) == "" { + return nil, "", ErrRequestParam + } + + userID := apiUser.UserId + idCardHash := api_services.HashIDCard(payload.IDCard) + name := normalizeWhitelistName(payload.Name) + + entry, err := s.repo.FindByUserIDCardHashAndName(ctx, userID, idCardHash, name) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, "", ErrWhitelistNotFound + } + return nil, "", err + } + + for _, code := range entry.APICodes { + if code == "*" { + return nil, "", ErrRequestParam + } + } + + merged := mergeAPICodes([]string(entry.APICodes), payload.APICodes) + entry.APICodes = entities.APICodeList(merged) + if remark := strings.TrimSpace(payload.Remark); remark != "" { + entry.Remark = remark + } + updatedBy := "public_api_append:" + strings.TrimSpace(headerAccessID) + entry.UpdatedBy = &updatedBy + entry.OperationIP = strings.TrimSpace(clientIP) + + if err := s.repo.Update(ctx, entry); err != nil { + return nil, "", err + } + s.queryWhitelistSvc.InvalidateCache(entry.UserID, idCardHash) + + resp := dto.NewQueryWhitelistEntryResponse(entry) + return &resp, apiUser.SecretKey, nil +} + +func (s *QueryWhitelistApplicationServiceImpl) preparePublicRequest( + ctx context.Context, + headerAccessID, managementKey, clientIP, encryptedData string, +) (*entities.ApiUser, dto.QueryWhitelistPublicPayload, error) { + if !s.config.QueryWhitelist.PublicAPI.Enabled { + return nil, dto.QueryWhitelistPublicPayload{}, ErrPublicAPIDisabled + } + if strings.TrimSpace(managementKey) == "" { + return nil, dto.QueryWhitelistPublicPayload{}, ErrMissingMgmtKey + } + if !constantTimeEqual(managementKey, s.config.QueryWhitelist.PublicAPI.ManagementKey) { + return nil, dto.QueryWhitelistPublicPayload{}, ErrInvalidMgmtKey + } + headerAccessID = strings.TrimSpace(headerAccessID) + if headerAccessID == "" { + return nil, dto.QueryWhitelistPublicPayload{}, ErrMissingAccessId + } + + apiUser, err := s.apiUserService.LoadApiUserByAccessId(ctx, headerAccessID) + if err != nil { + s.logger.Warn("公开白名单接口 AccessId 无效", zap.String("access_id", headerAccessID), zap.Error(err)) + return nil, dto.QueryWhitelistPublicPayload{}, ErrInvalidAccessId + } + if apiUser.IsFrozen() { + return nil, dto.QueryWhitelistPublicPayload{}, ErrFrozenAccount + } + + decrypted, err := crypto.AesDecrypt(encryptedData, apiUser.SecretKey) + if err != nil { + s.logger.Warn("公开白名单接口解密失败", zap.String("access_id", headerAccessID), zap.Error(err)) + return nil, dto.QueryWhitelistPublicPayload{}, ErrDecryptFail + } + + var raw map[string]json.RawMessage + if err := json.Unmarshal(decrypted, &raw); err != nil { + return nil, dto.QueryWhitelistPublicPayload{}, ErrRequestParam + } + if err := validatePublicAPICodesField(raw["api_codes"]); err != nil { + return nil, dto.QueryWhitelistPublicPayload{}, MapWhitelistAppError(err) + } + + var payload dto.QueryWhitelistPublicPayload + if err := json.Unmarshal(decrypted, &payload); err != nil { + return nil, dto.QueryWhitelistPublicPayload{}, ErrRequestParam + } + + if !s.config.App.IsDevelopment() && !apiUser.IsWhiteListed(clientIP) { + s.logger.Warn("公开白名单接口 IP 未授权", + zap.String("access_id", headerAccessID), + zap.String("client_ip", clientIP)) + return nil, dto.QueryWhitelistPublicPayload{}, ErrInvalidIP + } + + return apiUser, payload, nil +} + func (s *QueryWhitelistApplicationServiceImpl) createEntry( ctx context.Context, createdBy string, @@ -126,6 +193,35 @@ func (s *QueryWhitelistApplicationServiceImpl) createEntry( return &resp, nil } +// mergeAPICodes 合并接口编码并去重,保持原有顺序,新增编码追加在后 +func mergeAPICodes(existing, incoming []string) []string { + seen := make(map[string]struct{}, len(existing)+len(incoming)) + result := make([]string, 0, len(existing)+len(incoming)) + for _, code := range existing { + code = strings.TrimSpace(code) + if code == "" { + continue + } + if _, ok := seen[code]; ok { + continue + } + seen[code] = struct{}{} + result = append(result, code) + } + for _, code := range incoming { + code = strings.TrimSpace(code) + if code == "" { + continue + } + if _, ok := seen[code]; ok { + continue + } + seen[code] = struct{}{} + result = append(result, code) + } + return result +} + func constantTimeEqual(a, b string) bool { return subtle.ConstantTimeCompare([]byte(a), []byte(b)) == 1 } @@ -205,6 +301,8 @@ func PublicAPIErrorMessage(err error) string { switch err { case ErrWhitelistExists: return "规则已存在" + case ErrWhitelistNotFound: + return "规则不存在,请先调用创建接口" case ErrRequestParam: return "请求参数结构不正确" default: diff --git a/internal/application/api/query_whitelist_public_service_test.go b/internal/application/api/query_whitelist_public_service_test.go index 84bbc40..6a1b370 100644 --- a/internal/application/api/query_whitelist_public_service_test.go +++ b/internal/application/api/query_whitelist_public_service_test.go @@ -32,3 +32,16 @@ func TestValidatePublicAPICodesField(t *testing.T) { }) } } + +func TestMergeAPICodes(t *testing.T) { + got := mergeAPICodes([]string{"FLXG0V4B", "JRZQ8A2D"}, []string{"JRZQ8A2D", "FLXG2E8F"}) + want := []string{"FLXG0V4B", "JRZQ8A2D", "FLXG2E8F"} + if len(got) != len(want) { + t.Fatalf("mergeAPICodes() = %v, want %v", got, want) + } + for i := range want { + if got[i] != want[i] { + t.Fatalf("mergeAPICodes() = %v, want %v", got, want) + } + } +} diff --git a/internal/domains/api/repositories/query_whitelist_repository.go b/internal/domains/api/repositories/query_whitelist_repository.go index 7df5855..d6ab526 100644 --- a/internal/domains/api/repositories/query_whitelist_repository.go +++ b/internal/domains/api/repositories/query_whitelist_repository.go @@ -16,4 +16,5 @@ type QueryWhitelistRepository interface { FindAllEnabled(ctx context.Context) ([]*entities.QueryWhitelistEntry, error) List(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) ([]*entities.QueryWhitelistEntry, int64, error) ExistsByUserIDCardHashAndName(ctx context.Context, userID, idCardHash, name string, excludeID string) (bool, error) + FindByUserIDCardHashAndName(ctx context.Context, userID, idCardHash, name string) (*entities.QueryWhitelistEntry, error) } diff --git a/internal/domains/api/services/query_whitelist_service_test.go b/internal/domains/api/services/query_whitelist_service_test.go index 2fbcea3..d530c1c 100644 --- a/internal/domains/api/services/query_whitelist_service_test.go +++ b/internal/domains/api/services/query_whitelist_service_test.go @@ -31,6 +31,9 @@ func (m *mockQueryWhitelistRepo) List(ctx context.Context, filters map[string]in func (m *mockQueryWhitelistRepo) ExistsByUserIDCardHashAndName(ctx context.Context, userID, idCardHash, name, excludeID string) (bool, error) { return false, nil } +func (m *mockQueryWhitelistRepo) FindByUserIDCardHashAndName(ctx context.Context, userID, idCardHash, name string) (*entities.QueryWhitelistEntry, error) { + return nil, nil +} func (m *mockQueryWhitelistRepo) FindEnabledByUserIDsAndIDCardHash(ctx context.Context, userIDs []string, idCardHash string) ([]*entities.QueryWhitelistEntry, error) { var result []*entities.QueryWhitelistEntry diff --git a/internal/infrastructure/database/repositories/api/gorm_query_whitelist_repository.go b/internal/infrastructure/database/repositories/api/gorm_query_whitelist_repository.go index ed91fc5..8a71626 100644 --- a/internal/infrastructure/database/repositories/api/gorm_query_whitelist_repository.go +++ b/internal/infrastructure/database/repositories/api/gorm_query_whitelist_repository.go @@ -150,3 +150,17 @@ func (r *GormQueryWhitelistRepository) ExistsByUserIDCardHashAndName( } return count > 0, nil } + +func (r *GormQueryWhitelistRepository) FindByUserIDCardHashAndName( + ctx context.Context, + userID, idCardHash, name string, +) (*entities.QueryWhitelistEntry, error) { + var entry entities.QueryWhitelistEntry + err := r.GetDB(ctx). + Where("user_id = ? AND id_card_hash = ? AND name = ?", userID, idCardHash, name). + First(&entry).Error + if err != nil { + return nil, err + } + return &entry, nil +} diff --git a/internal/infrastructure/http/handlers/public_query_whitelist_handler.go b/internal/infrastructure/http/handlers/public_query_whitelist_handler.go index 5e3464c..4b9b7fc 100644 --- a/internal/infrastructure/http/handlers/public_query_whitelist_handler.go +++ b/internal/infrastructure/http/handlers/public_query_whitelist_handler.go @@ -13,7 +13,7 @@ import ( "go.uber.org/zap" ) -// PublicQueryWhitelistHandler 查询白名单公开添加接口(面向下游调用方) +// PublicQueryWhitelistHandler 查询白名单公开接口(面向下游调用方) type PublicQueryWhitelistHandler struct { appService api_app.QueryWhitelistApplicationService validator interfaces.RequestValidator @@ -34,6 +34,21 @@ func NewPublicQueryWhitelistHandler( // CreateEntry 公开添加查询白名单规则 func (h *PublicQueryWhitelistHandler) CreateEntry(c *gin.Context) { + h.handlePublicEntry(c, func(ctx *gin.Context, accessID, mgmtKey, clientIP, data string) (any, string, error) { + return h.appService.CreateEntryPublic(ctx.Request.Context(), accessID, mgmtKey, clientIP, data) + }) +} + +// AppendEntry 向已有规则追加生效接口(去重合并) +func (h *PublicQueryWhitelistHandler) AppendEntry(c *gin.Context) { + h.handlePublicEntry(c, func(ctx *gin.Context, accessID, mgmtKey, clientIP, data string) (any, string, error) { + return h.appService.AppendEntryPublic(ctx.Request.Context(), accessID, mgmtKey, clientIP, data) + }) +} + +type publicEntryHandler func(*gin.Context, string, string, string, string) (any, string, error) + +func (h *PublicQueryWhitelistHandler) handlePublicEntry(c *gin.Context, fn publicEntryHandler) { accessID := c.GetHeader("Access-Id") if accessID == "" { c.JSON(200, dto.NewErrorResponse(1005, "缺少Access-Id", "")) @@ -53,13 +68,7 @@ func (h *PublicQueryWhitelistHandler) CreateEntry(c *gin.Context) { } transactionID := uuid.New().String() - result, secretKey, err := h.appService.CreateEntryPublic( - c.Request.Context(), - accessID, - mgmtKey, - c.ClientIP(), - req.Data, - ) + result, secretKey, err := fn(c, accessID, mgmtKey, c.ClientIP(), req.Data) if err != nil { code := api_app.GetErrorCode(err) message := api_app.PublicAPIErrorMessage(err) diff --git a/internal/infrastructure/http/routes/public_query_whitelist_routes.go b/internal/infrastructure/http/routes/public_query_whitelist_routes.go index 16b67eb..d843b4e 100644 --- a/internal/infrastructure/http/routes/public_query_whitelist_routes.go +++ b/internal/infrastructure/http/routes/public_query_whitelist_routes.go @@ -31,5 +31,6 @@ func (r *PublicQueryWhitelistRoutes) Register(router *sharedhttp.GinRouter) { group := router.GetEngine().Group("/api/v1/query-whitelist") group.Use(r.domainAuthMiddleware.Handle("")) group.POST("/entries", r.handler.CreateEntry) + group.POST("/entries/append", r.handler.AppendEntry) r.logger.Info("查询白名单公开接口路由注册完成") }