This commit is contained in:
2026-06-18 21:16:02 +08:00
parent 9685d34187
commit 3a5a0d0028
36 changed files with 1566 additions and 66 deletions

View File

@@ -0,0 +1,152 @@
package api
import (
"context"
"fmt"
"tyapi-server/internal/domains/api/entities"
"tyapi-server/internal/domains/api/repositories"
"tyapi-server/internal/shared/database"
"tyapi-server/internal/shared/interfaces"
"go.uber.org/zap"
"gorm.io/gorm"
)
const QueryWhitelistTable = "query_whitelist_entries"
type GormQueryWhitelistRepository struct {
*database.BaseRepositoryImpl
}
var _ repositories.QueryWhitelistRepository = (*GormQueryWhitelistRepository)(nil)
func NewGormQueryWhitelistRepository(db *gorm.DB, logger *zap.Logger) repositories.QueryWhitelistRepository {
return &GormQueryWhitelistRepository{
BaseRepositoryImpl: database.NewBaseRepositoryImpl(db, logger),
}
}
func (r *GormQueryWhitelistRepository) Create(ctx context.Context, entry *entities.QueryWhitelistEntry) error {
return r.CreateEntity(ctx, entry)
}
func (r *GormQueryWhitelistRepository) Update(ctx context.Context, entry *entities.QueryWhitelistEntry) error {
return r.UpdateEntity(ctx, entry)
}
func (r *GormQueryWhitelistRepository) Delete(ctx context.Context, id string) error {
return r.GetDB(ctx).Where("id = ?", id).Delete(&entities.QueryWhitelistEntry{}).Error
}
func (r *GormQueryWhitelistRepository) FindByID(ctx context.Context, id string) (*entities.QueryWhitelistEntry, error) {
var entry entities.QueryWhitelistEntry
if err := r.GetDB(ctx).Where("id = ?", id).First(&entry).Error; err != nil {
return nil, err
}
return &entry, nil
}
func (r *GormQueryWhitelistRepository) FindEnabledByUserIDsAndIDCardHash(
ctx context.Context,
userIDs []string,
idCardHash string,
) ([]*entities.QueryWhitelistEntry, error) {
if len(userIDs) == 0 || idCardHash == "" {
return nil, nil
}
var entries []*entities.QueryWhitelistEntry
err := r.GetDB(ctx).
Where("user_id IN ? AND id_card_hash = ? AND status = ?", userIDs, idCardHash, entities.QueryWhitelistStatusEnabled).
Find(&entries).Error
if err != nil {
return nil, err
}
return entries, nil
}
func (r *GormQueryWhitelistRepository) FindAllEnabled(ctx context.Context) ([]*entities.QueryWhitelistEntry, error) {
var entries []*entities.QueryWhitelistEntry
err := r.GetDB(ctx).
Where("status = ?", entities.QueryWhitelistStatusEnabled).
Find(&entries).Error
if err != nil {
return nil, err
}
return entries, nil
}
func (r *GormQueryWhitelistRepository) List(
ctx context.Context,
filters map[string]interface{},
options interfaces.ListOptions,
) ([]*entities.QueryWhitelistEntry, int64, error) {
query := r.GetDB(ctx).Model(&entities.QueryWhitelistEntry{})
if userID, ok := filters["user_id"].(string); ok && userID != "" {
query = query.Where("user_id = ?", userID)
}
if status, ok := filters["status"].(string); ok && status != "" {
query = query.Where("status = ?", status)
}
if apiCode, ok := filters["api_code"].(string); ok && apiCode != "" {
query = query.Where("api_codes::text LIKE ?", fmt.Sprintf("%%\"%s\"%%", apiCode))
}
if idCardHash, ok := filters["id_card_hash"].(string); ok && idCardHash != "" {
query = query.Where("id_card_hash = ?", idCardHash)
}
if keyword, ok := filters["keyword"].(string); ok && keyword != "" {
like := "%" + keyword + "%"
query = query.Where("name LIKE ? OR remark LIKE ? OR id_card_masked LIKE ?", like, like, like)
}
var total int64
if err := query.Count(&total).Error; err != nil {
return nil, 0, err
}
page := options.Page
if page < 1 {
page = 1
}
pageSize := options.PageSize
if pageSize < 1 {
pageSize = 20
}
if pageSize > 100 {
pageSize = 100
}
offset := (page - 1) * pageSize
order := "created_at DESC"
if options.Sort != "" {
dir := "ASC"
if options.Order == "desc" {
dir = "DESC"
}
order = fmt.Sprintf("%s %s", options.Sort, dir)
}
var entries []*entities.QueryWhitelistEntry
if err := query.Order(order).Offset(offset).Limit(pageSize).Find(&entries).Error; err != nil {
return nil, 0, err
}
return entries, total, nil
}
func (r *GormQueryWhitelistRepository) ExistsByUserIDCardHashAndName(
ctx context.Context,
userID, idCardHash, name string,
excludeID string,
) (bool, error) {
query := r.GetDB(ctx).Model(&entities.QueryWhitelistEntry{}).
Where("user_id = ? AND id_card_hash = ? AND name = ?", userID, idCardHash, name)
if excludeID != "" {
query = query.Where("id <> ?", excludeID)
}
var count int64
if err := query.Count(&count).Error; err != nil {
return false, err
}
return count > 0, nil
}

View File

@@ -0,0 +1,149 @@
package handlers
import (
"strconv"
"strings"
api_app "tyapi-server/internal/application/api"
"tyapi-server/internal/application/api/dto"
"tyapi-server/internal/shared/interfaces"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
type AdminQueryWhitelistHandler struct {
appService api_app.QueryWhitelistApplicationService
responseBuilder interfaces.ResponseBuilder
validator interfaces.RequestValidator
logger *zap.Logger
}
func NewAdminQueryWhitelistHandler(
appService api_app.QueryWhitelistApplicationService,
responseBuilder interfaces.ResponseBuilder,
validator interfaces.RequestValidator,
logger *zap.Logger,
) *AdminQueryWhitelistHandler {
return &AdminQueryWhitelistHandler{
appService: appService,
responseBuilder: responseBuilder,
validator: validator,
logger: logger,
}
}
func (h *AdminQueryWhitelistHandler) ListEntries(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
filters := map[string]interface{}{}
if userID := strings.TrimSpace(c.Query("user_id")); userID != "" {
filters["user_id"] = userID
}
if status := strings.TrimSpace(c.Query("status")); status != "" {
filters["status"] = status
}
if apiCode := strings.TrimSpace(c.Query("api_code")); apiCode != "" {
filters["api_code"] = apiCode
}
if idCard := strings.TrimSpace(c.Query("id_card")); idCard != "" {
filters["id_card"] = idCard
}
if keyword := strings.TrimSpace(c.Query("keyword")); keyword != "" {
filters["keyword"] = keyword
}
result, err := h.appService.ListEntries(c.Request.Context(), filters, interfaces.ListOptions{
Page: page,
PageSize: pageSize,
})
if err != nil {
h.logger.Error("获取查询白名单列表失败", zap.Error(err))
h.responseBuilder.InternalError(c, "获取查询白名单列表失败")
return
}
h.responseBuilder.Success(c, result, "获取查询白名单列表成功")
}
func (h *AdminQueryWhitelistHandler) GetEntry(c *gin.Context) {
id := c.Param("id")
result, err := h.appService.GetEntry(c.Request.Context(), id)
if err != nil {
h.logger.Error("获取查询白名单详情失败", zap.Error(err))
h.responseBuilder.BadRequest(c, err.Error())
return
}
h.responseBuilder.Success(c, result, "获取查询白名单详情成功")
}
func (h *AdminQueryWhitelistHandler) CreateEntry(c *gin.Context) {
var req dto.QueryWhitelistEntryRequest
if err := h.validator.BindAndValidate(c, &req); err != nil {
h.responseBuilder.BadRequest(c, "参数校验失败")
return
}
adminUserID := c.GetString("user_id")
result, err := h.appService.CreateEntry(c.Request.Context(), adminUserID, &req)
if err != nil {
h.logger.Error("创建查询白名单失败", zap.Error(err))
h.responseBuilder.BadRequest(c, err.Error())
return
}
h.responseBuilder.Success(c, result, "创建查询白名单成功")
}
func (h *AdminQueryWhitelistHandler) UpdateEntry(c *gin.Context) {
id := c.Param("id")
var req dto.QueryWhitelistEntryUpdateRequest
if err := h.validator.BindAndValidate(c, &req); err != nil {
h.responseBuilder.BadRequest(c, "参数校验失败")
return
}
adminUserID := c.GetString("user_id")
result, err := h.appService.UpdateEntry(c.Request.Context(), adminUserID, id, &req)
if err != nil {
h.logger.Error("更新查询白名单失败", zap.Error(err))
h.responseBuilder.BadRequest(c, err.Error())
return
}
h.responseBuilder.Success(c, result, "更新查询白名单成功")
}
func (h *AdminQueryWhitelistHandler) UpdateEntryStatus(c *gin.Context) {
id := c.Param("id")
var req dto.QueryWhitelistStatusRequest
if err := h.validator.BindAndValidate(c, &req); err != nil {
h.responseBuilder.BadRequest(c, "参数校验失败")
return
}
adminUserID := c.GetString("user_id")
result, err := h.appService.UpdateEntryStatus(c.Request.Context(), adminUserID, id, req.Status)
if err != nil {
h.logger.Error("更新查询白名单状态失败", zap.Error(err))
h.responseBuilder.BadRequest(c, err.Error())
return
}
h.responseBuilder.Success(c, result, "更新查询白名单状态成功")
}
func (h *AdminQueryWhitelistHandler) DeleteEntry(c *gin.Context) {
id := c.Param("id")
if err := h.appService.DeleteEntry(c.Request.Context(), id); err != nil {
h.logger.Error("删除查询白名单失败", zap.Error(err))
h.responseBuilder.BadRequest(c, err.Error())
return
}
h.responseBuilder.Success(c, nil, "删除查询白名单成功")
}
func (h *AdminQueryWhitelistHandler) ImportLegacyEntries(c *gin.Context) {
adminUserID := c.GetString("user_id")
result, err := h.appService.ImportLegacyEntries(c.Request.Context(), adminUserID)
if err != nil {
h.logger.Error("导入历史硬编码白名单失败", zap.Error(err))
h.responseBuilder.BadRequest(c, err.Error())
return
}
h.responseBuilder.Success(c, result, "导入历史硬编码白名单成功")
}

View File

@@ -76,7 +76,7 @@ type decodedSendCodeData struct {
// @Router /api/v1/users/send-code [post]
func (h *UserHandler) SendCode(c *gin.Context) {
var cmd commands.SendCodeCommand
// 绑定请求包含data字段和可选的captchaVerifyParam字段
if err := c.ShouldBindJSON(&cmd); err != nil {
h.response.BadRequest(c, "请求参数格式错误必须提供data字段")
@@ -113,7 +113,7 @@ func (h *UserHandler) SendCode(c *gin.Context) {
zap.String("scene", decodedData.Scene),
zap.String("client_ip", c.ClientIP()),
zap.Error(err))
// 根据错误类型返回不同的用户友好消息(不暴露技术细节)
userMessage := h.getSignatureErrorMessage(err)
h.response.BadRequest(c, userMessage)
@@ -123,11 +123,11 @@ func (h *UserHandler) SendCode(c *gin.Context) {
// 构建SendCodeCommand用于调用应用服务
serviceCmd := &commands.SendCodeCommand{
Phone: decodedData.Phone,
Scene: decodedData.Scene,
Timestamp: decodedData.Timestamp,
Nonce: decodedData.Nonce,
Signature: decodedData.Signature,
Phone: decodedData.Phone,
Scene: decodedData.Scene,
Timestamp: decodedData.Timestamp,
Nonce: decodedData.Nonce,
Signature: decodedData.Signature,
CaptchaVerifyParam: cmd.CaptchaVerifyParam,
}
@@ -183,7 +183,7 @@ func (h *UserHandler) verifyDecodedSignature(ctx context.Context, data *decodedS
// getSignatureErrorMessage 根据错误类型返回用户友好的错误消息(不暴露技术细节)
func (h *UserHandler) getSignatureErrorMessage(err error) string {
errMsg := err.Error()
// 根据错误消息内容判断错误类型,返回通用的用户友好消息
if strings.Contains(errMsg, "请求已被使用") || strings.Contains(errMsg, "重复提交") {
// 重放攻击:返回通用消息,不暴露具体原因
@@ -197,12 +197,11 @@ func (h *UserHandler) getSignatureErrorMessage(err error) string {
// 签名错误:返回通用消息
return "请求验证失败,请重新操作"
}
// 其他错误:返回通用消息
return "请求验证失败,请重新操作"
}
// Register 用户注册
// @Summary 用户注册
// @Description 使用手机号、密码和验证码进行用户注册,需要确认密码

View File

@@ -0,0 +1,37 @@
package routes
import (
"tyapi-server/internal/infrastructure/http/handlers"
sharedhttp "tyapi-server/internal/shared/http"
"tyapi-server/internal/shared/middleware"
)
type AdminQueryWhitelistRoutes struct {
handler *handlers.AdminQueryWhitelistHandler
admin *middleware.AdminAuthMiddleware
}
func NewAdminQueryWhitelistRoutes(
handler *handlers.AdminQueryWhitelistHandler,
admin *middleware.AdminAuthMiddleware,
) *AdminQueryWhitelistRoutes {
return &AdminQueryWhitelistRoutes{
handler: handler,
admin: admin,
}
}
func (r *AdminQueryWhitelistRoutes) Register(router *sharedhttp.GinRouter) {
engine := router.GetEngine()
group := engine.Group("/api/v1/admin/query-whitelist")
group.Use(r.admin.Handle())
{
group.GET("/entries", r.handler.ListEntries)
group.GET("/entries/:id", r.handler.GetEntry)
group.POST("/entries", r.handler.CreateEntry)
group.PUT("/entries/:id", r.handler.UpdateEntry)
group.PATCH("/entries/:id/status", r.handler.UpdateEntryStatus)
group.DELETE("/entries/:id", r.handler.DeleteEntry)
group.POST("/entries/import-legacy", r.handler.ImportLegacyEntries)
}
}