公共组件
This commit is contained in:
parent
72ec2bfcc9
commit
35060cc1fc
@ -176,38 +176,12 @@ func (h *Handler) resolveGrantUserID(ctx context.Context, keyword string) (int64
|
||||
if keyword == "" {
|
||||
return 0, false, true
|
||||
}
|
||||
if h.userDB == nil {
|
||||
return 0, false, true
|
||||
}
|
||||
// keyword 支持真实 user_id、短 ID 和昵称模糊匹配;排序优先精确 ID,避免昵称碰撞查到错误用户。
|
||||
appCode := appctx.FromContext(ctx)
|
||||
nowMs := time.Now().UnixMilli()
|
||||
matchSQL, matchArgs := shared.UserIdentityExactSQL("u", "u.user_id", keyword, nowMs)
|
||||
orderSQL, orderArgs := shared.UserIdentityExactOrderSQL("u", "u.user_id", keyword, nowMs)
|
||||
args := append([]any{appCode}, matchArgs...)
|
||||
args = append(args, orderArgs...)
|
||||
rows, err := h.userDB.QueryContext(ctx, `
|
||||
SELECT u.user_id
|
||||
FROM users u
|
||||
WHERE u.app_code = ? AND `+matchSQL+`
|
||||
ORDER BY
|
||||
`+orderSQL+`,
|
||||
u.user_id DESC
|
||||
LIMIT 1`,
|
||||
args...,
|
||||
)
|
||||
// 累计充值奖励按 activity-service 的内部 user_id 过滤;keyword 的长 ID、短号、靓号、昵称优先级由 shared 统一维护。
|
||||
userID, matched, err := shared.ResolveExactUserID(ctx, h.userDB, appctx.FromContext(ctx), keyword, time.Now().UnixMilli())
|
||||
if err != nil {
|
||||
return 0, false, false
|
||||
}
|
||||
defer rows.Close()
|
||||
if !rows.Next() {
|
||||
return 0, false, rows.Err() == nil
|
||||
}
|
||||
var userID int64
|
||||
if err := rows.Scan(&userID); err != nil {
|
||||
return 0, false, false
|
||||
}
|
||||
return userID, true, rows.Err() == nil
|
||||
return userID, matched, true
|
||||
}
|
||||
|
||||
func (h *Handler) fillGrantUsers(ctx context.Context, grants []grantDTO) error {
|
||||
|
||||
@ -181,37 +181,12 @@ func (h *Handler) resolveClaimUserID(ctx context.Context, keyword string) (int64
|
||||
if keyword == "" {
|
||||
return 0, false, true
|
||||
}
|
||||
if h.userDB == nil {
|
||||
return 0, false, true
|
||||
}
|
||||
appCode := appctx.FromContext(ctx)
|
||||
nowMs := time.Now().UnixMilli()
|
||||
matchSQL, matchArgs := shared.UserIdentityExactSQL("u", "u.user_id", keyword, nowMs)
|
||||
orderSQL, orderArgs := shared.UserIdentityExactOrderSQL("u", "u.user_id", keyword, nowMs)
|
||||
args := append([]any{appCode}, matchArgs...)
|
||||
args = append(args, orderArgs...)
|
||||
rows, err := h.userDB.QueryContext(ctx, `
|
||||
SELECT u.user_id
|
||||
FROM users u
|
||||
WHERE u.app_code = ? AND `+matchSQL+`
|
||||
ORDER BY
|
||||
`+orderSQL+`,
|
||||
u.user_id DESC
|
||||
LIMIT 1`,
|
||||
args...,
|
||||
)
|
||||
// 奖励领取记录由 activity-service 按内部 user_id 过滤;后台关键词先统一解析,避免各活动模块复制短号/靓号 SQL。
|
||||
userID, matched, err := shared.ResolveExactUserID(ctx, h.userDB, appctx.FromContext(ctx), keyword, time.Now().UnixMilli())
|
||||
if err != nil {
|
||||
return 0, false, false
|
||||
}
|
||||
defer rows.Close()
|
||||
if !rows.Next() {
|
||||
return 0, false, rows.Err() == nil
|
||||
}
|
||||
var userID int64
|
||||
if err := rows.Scan(&userID); err != nil {
|
||||
return 0, false, false
|
||||
}
|
||||
return userID, true, rows.Err() == nil
|
||||
return userID, matched, true
|
||||
}
|
||||
|
||||
func (h *Handler) fillClaimUsers(ctx context.Context, claims []claimDTO) error {
|
||||
|
||||
@ -237,37 +237,12 @@ func (h *Handler) resolveUserID(ctx context.Context, keyword string) (int64, boo
|
||||
if keyword == "" {
|
||||
return 0, false, true
|
||||
}
|
||||
if h.userDB == nil {
|
||||
return 0, false, true
|
||||
}
|
||||
appCode := appctx.FromContext(ctx)
|
||||
nowMs := time.Now().UnixMilli()
|
||||
matchSQL, matchArgs := shared.UserIdentityExactSQL("u", "u.user_id", keyword, nowMs)
|
||||
orderSQL, orderArgs := shared.UserIdentityExactOrderSQL("u", "u.user_id", keyword, nowMs)
|
||||
args := append([]any{appCode}, matchArgs...)
|
||||
args = append(args, orderArgs...)
|
||||
rows, err := h.userDB.QueryContext(ctx, `
|
||||
SELECT u.user_id
|
||||
FROM users u
|
||||
WHERE u.app_code = ? AND `+matchSQL+`
|
||||
ORDER BY
|
||||
`+orderSQL+`,
|
||||
u.user_id DESC
|
||||
LIMIT 1`,
|
||||
args...,
|
||||
)
|
||||
// 邀请奖励汇总页只把内部 user_id 交给 activity-service;展示 ID 的兼容规则集中在 shared 解析器里。
|
||||
userID, matched, err := shared.ResolveExactUserID(ctx, h.userDB, appctx.FromContext(ctx), keyword, time.Now().UnixMilli())
|
||||
if err != nil {
|
||||
return 0, false, false
|
||||
}
|
||||
defer rows.Close()
|
||||
if !rows.Next() {
|
||||
return 0, false, rows.Err() == nil
|
||||
}
|
||||
var userID int64
|
||||
if err := rows.Scan(&userID); err != nil {
|
||||
return 0, false, false
|
||||
}
|
||||
return userID, true, rows.Err() == nil
|
||||
return userID, matched, true
|
||||
}
|
||||
|
||||
func (h *Handler) fillUsers(ctx context.Context, claims []claimDTO) error {
|
||||
|
||||
@ -7,6 +7,7 @@ import (
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"hyapp-admin-server/internal/appctx"
|
||||
"hyapp-admin-server/internal/integration/walletclient"
|
||||
@ -37,11 +38,19 @@ func New(wallet walletclient.Client, userDB *sql.DB, audit shared.OperationLogge
|
||||
func (h *Handler) ListRechargeBills(c *gin.Context) {
|
||||
options := shared.ListOptions(c)
|
||||
appCode := appctx.FromContext(c.Request.Context())
|
||||
userID, ok := h.resolveRechargeBillUserFilter(c, appCode, "user_id 参数不正确", "user_id", "userId")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
sellerUserID, ok := h.resolveRechargeBillUserFilter(c, appCode, "seller_user_id 参数不正确", "seller_user_id", "sellerUserId")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
resp, err := h.wallet.ListRechargeBills(c.Request.Context(), &walletv1.ListRechargeBillsRequest{
|
||||
RequestId: middleware.CurrentRequestID(c),
|
||||
AppCode: appCode,
|
||||
UserId: queryInt64(c, "user_id", "userId"),
|
||||
SellerUserId: queryInt64(c, "seller_user_id", "sellerUserId"),
|
||||
UserId: userID,
|
||||
SellerUserId: sellerUserID,
|
||||
RegionId: queryInt64(c, "region_id", "regionId"),
|
||||
RechargeType: strings.TrimSpace(firstQuery(c, "recharge_type", "rechargeType")),
|
||||
Status: options.Status,
|
||||
@ -68,6 +77,24 @@ func (h *Handler) ListRechargeBills(c *gin.Context) {
|
||||
response.OK(c, response.Page{Items: items, Page: options.Page, PageSize: options.PageSize, Total: resp.GetTotal()})
|
||||
}
|
||||
|
||||
func (h *Handler) resolveRechargeBillUserFilter(c *gin.Context, appCode string, invalidMessage string, keys ...string) (int64, bool) {
|
||||
keyword := strings.TrimSpace(firstQuery(c, keys...))
|
||||
if keyword == "" {
|
||||
return 0, true
|
||||
}
|
||||
// 充值账单事实在 wallet-service,筛选字段只能传内部 user_id;后台先把长 ID、默认短号、当前展示号和 active 靓号解析成同一个稳定 ID。
|
||||
userID, _, err := shared.ResolveExactUserIDOrNumericFallback(c.Request.Context(), h.userDB, appCode, keyword, time.Now().UnixMilli())
|
||||
if err != nil {
|
||||
if errors.Is(err, shared.ErrInvalidUserIdentityFilter) {
|
||||
response.BadRequest(c, invalidMessage)
|
||||
return 0, false
|
||||
}
|
||||
response.ServerError(c, "查询账单用户信息失败")
|
||||
return 0, false
|
||||
}
|
||||
return userID, true
|
||||
}
|
||||
|
||||
// fillRechargeBillUsers 将用户资料写回当前页账单 DTO;缺失资料会在 DTO 层保留长 ID,避免影响账单事实展示。
|
||||
func (h *Handler) fillRechargeBillUsers(ctx context.Context, appCode string, items []rechargeBillDTO) error {
|
||||
userIDs := collectRechargeBillUserIDs(items)
|
||||
|
||||
@ -13,9 +13,42 @@ import (
|
||||
"hyapp-admin-server/internal/middleware"
|
||||
walletv1 "hyapp.local/api/proto/wallet/v1"
|
||||
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func TestListRechargeBillsResolvesDisplayIDsBeforeWalletFilter(t *testing.T) {
|
||||
db, sqlMock, err := sqlmock.New()
|
||||
if err != nil {
|
||||
t.Fatalf("create sql mock failed: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
wallet := &mockPaymentWallet{rechargeBillsResp: &walletv1.ListRechargeBillsResponse{}}
|
||||
router := newPaymentHandlerTestRouter(New(wallet, db, nil))
|
||||
userID := int64(327702592329093120)
|
||||
sellerUserID := int64(328453424662192128)
|
||||
expectUserIdentityLookup(sqlMock, "111", userID)
|
||||
expectUserIdentityLookup(sqlMock, "167864", sellerUserID)
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "/admin/payment/recharge-bills?user_id=111&seller_user_id=167864", nil)
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(recorder, request)
|
||||
|
||||
if recorder.Code != http.StatusOK {
|
||||
t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String())
|
||||
}
|
||||
if wallet.lastRechargeBills == nil ||
|
||||
wallet.lastRechargeBills.GetUserId() != userID ||
|
||||
wallet.lastRechargeBills.GetSellerUserId() != sellerUserID {
|
||||
t.Fatalf("recharge bill user filters should be resolved before wallet call: %+v", wallet.lastRechargeBills)
|
||||
}
|
||||
if err := sqlMock.ExpectationsWereMet(); err != nil {
|
||||
t.Fatalf("sql expectations mismatch: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListThirdPartyPaymentChannelsForwardsIncludeDisabledMethods(t *testing.T) {
|
||||
wallet := &mockPaymentWallet{thirdPartyChannelsResp: &walletv1.ListThirdPartyPaymentChannelsResponse{
|
||||
Channels: []*walletv1.ThirdPartyPaymentChannel{{
|
||||
@ -330,6 +363,7 @@ func newPaymentHandlerTestRouter(handler *Handler) *gin.Engine {
|
||||
c.Set(middleware.ContextUsername, "tester")
|
||||
c.Next()
|
||||
})
|
||||
router.GET("/admin/payment/recharge-bills", handler.ListRechargeBills)
|
||||
router.GET("/admin/payment/third-party-channels", handler.ListThirdPartyPaymentChannels)
|
||||
router.GET("/admin/payment/temporary-links", handler.ListTemporaryPaymentLinks)
|
||||
router.GET("/admin/payment/temporary-links/:order_id", handler.GetTemporaryPaymentLink)
|
||||
@ -345,9 +379,30 @@ type adminPaymentTestResponse struct {
|
||||
Data map[string]any `json:"data"`
|
||||
}
|
||||
|
||||
func expectUserIdentityLookup(sqlMock sqlmock.Sqlmock, keyword string, userID int64) {
|
||||
sqlMock.ExpectQuery(`(?s)SELECT u\.user_id.*FROM users u.*LIMIT 1`).
|
||||
WithArgs(
|
||||
"lalu",
|
||||
keyword,
|
||||
keyword,
|
||||
keyword,
|
||||
sqlmock.AnyArg(),
|
||||
keyword,
|
||||
"%"+keyword+"%",
|
||||
keyword,
|
||||
keyword,
|
||||
keyword,
|
||||
sqlmock.AnyArg(),
|
||||
keyword,
|
||||
).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"user_id"}).AddRow(userID))
|
||||
}
|
||||
|
||||
type mockPaymentWallet struct {
|
||||
walletclient.Client
|
||||
|
||||
lastRechargeBills *walletv1.ListRechargeBillsRequest
|
||||
rechargeBillsResp *walletv1.ListRechargeBillsResponse
|
||||
lastThirdPartyChannels *walletv1.ListThirdPartyPaymentChannelsRequest
|
||||
thirdPartyChannelsResp *walletv1.ListThirdPartyPaymentChannelsResponse
|
||||
lastTemporaryOrders *walletv1.ListTemporaryRechargeOrdersRequest
|
||||
@ -360,6 +415,14 @@ type mockPaymentWallet struct {
|
||||
lastCreateProduct *walletv1.CreateRechargeProductRequest
|
||||
}
|
||||
|
||||
func (m *mockPaymentWallet) ListRechargeBills(_ context.Context, req *walletv1.ListRechargeBillsRequest) (*walletv1.ListRechargeBillsResponse, error) {
|
||||
m.lastRechargeBills = req
|
||||
if m.rechargeBillsResp != nil {
|
||||
return m.rechargeBillsResp, nil
|
||||
}
|
||||
return &walletv1.ListRechargeBillsResponse{}, nil
|
||||
}
|
||||
|
||||
func (m *mockPaymentWallet) ListThirdPartyPaymentChannels(_ context.Context, req *walletv1.ListThirdPartyPaymentChannelsRequest) (*walletv1.ListThirdPartyPaymentChannelsResponse, error) {
|
||||
m.lastThirdPartyChannels = req
|
||||
if m.thirdPartyChannelsResp != nil {
|
||||
|
||||
@ -187,37 +187,12 @@ func (h *Handler) resolveClaimUserID(ctx context.Context, keyword string) (int64
|
||||
if keyword == "" {
|
||||
return 0, false, true
|
||||
}
|
||||
if h.userDB == nil {
|
||||
return 0, false, true
|
||||
}
|
||||
appCode := appctx.FromContext(ctx)
|
||||
nowMs := time.Now().UnixMilli()
|
||||
matchSQL, matchArgs := shared.UserIdentityExactSQL("u", "u.user_id", keyword, nowMs)
|
||||
orderSQL, orderArgs := shared.UserIdentityExactOrderSQL("u", "u.user_id", keyword, nowMs)
|
||||
args := append([]any{appCode}, matchArgs...)
|
||||
args = append(args, orderArgs...)
|
||||
rows, err := h.userDB.QueryContext(ctx, `
|
||||
SELECT u.user_id
|
||||
FROM users u
|
||||
WHERE u.app_code = ? AND `+matchSQL+`
|
||||
ORDER BY
|
||||
`+orderSQL+`,
|
||||
u.user_id DESC
|
||||
LIMIT 1`,
|
||||
args...,
|
||||
)
|
||||
// 注册奖励领取记录的 owner 是 activity-service;列表关键词先解析成内部 user_id,再保持下游分页和 total 同源。
|
||||
userID, matched, err := shared.ResolveExactUserID(ctx, h.userDB, appctx.FromContext(ctx), keyword, time.Now().UnixMilli())
|
||||
if err != nil {
|
||||
return 0, false, false
|
||||
}
|
||||
defer rows.Close()
|
||||
if !rows.Next() {
|
||||
return 0, false, rows.Err() == nil
|
||||
}
|
||||
var userID int64
|
||||
if err := rows.Scan(&userID); err != nil {
|
||||
return 0, false, false
|
||||
}
|
||||
return userID, true, rows.Err() == nil
|
||||
return userID, matched, true
|
||||
}
|
||||
|
||||
func (h *Handler) fillClaimUsers(ctx context.Context, claims []claimDTO) error {
|
||||
|
||||
@ -3,6 +3,7 @@ package resource
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
@ -664,14 +665,17 @@ func (h *Handler) LookupResourceGrantTarget(c *gin.Context) {
|
||||
|
||||
func (h *Handler) ListResourceGrants(c *gin.Context) {
|
||||
options := shared.ListOptions(c)
|
||||
targetUserID, _, badRequest, err := h.resolveResourceGrantListTargetUserID(
|
||||
appCode := appctx.FromContext(c.Request.Context())
|
||||
targetUserID, _, err := shared.ResolveExactUserIDOrNumericFallback(
|
||||
c.Request.Context(),
|
||||
appctx.FromContext(c.Request.Context()),
|
||||
h.userDB,
|
||||
appCode,
|
||||
firstQuery(c, "target_user_id", "targetUserId"),
|
||||
time.Now().UnixMilli(),
|
||||
)
|
||||
if err != nil {
|
||||
if badRequest {
|
||||
response.BadRequest(c, err.Error())
|
||||
if errors.Is(err, shared.ErrInvalidUserIdentityFilter) {
|
||||
response.BadRequest(c, "target_user_id 参数不正确")
|
||||
return
|
||||
}
|
||||
response.ServerError(c, "查询赠送用户失败")
|
||||
@ -679,7 +683,7 @@ func (h *Handler) ListResourceGrants(c *gin.Context) {
|
||||
}
|
||||
resp, err := h.wallet.ListResourceGrants(c.Request.Context(), &walletv1.ListResourceGrantsRequest{
|
||||
RequestId: middleware.CurrentRequestID(c),
|
||||
AppCode: appctx.FromContext(c.Request.Context()),
|
||||
AppCode: appCode,
|
||||
TargetUserId: targetUserID,
|
||||
Status: options.Status,
|
||||
Page: int32(options.Page),
|
||||
@ -735,18 +739,16 @@ func (h *Handler) ListResourceShopPurchaseOrders(c *gin.Context) {
|
||||
}
|
||||
userKeyword := strings.TrimSpace(firstQuery(c, "user_keyword", "userKeyword", "user"))
|
||||
if userID == 0 && userKeyword != "" {
|
||||
resolvedUserID, filtered, err := h.resolveResourceShopPurchaseUserID(c.Request.Context(), appCode, userKeyword)
|
||||
resolvedUserID, matched, err := h.resolveResourceShopPurchaseUserID(c.Request.Context(), appCode, userKeyword)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
response.OK(c, response.Page{Items: []resourceShopPurchaseOrderDTO{}, Page: options.Page, PageSize: options.PageSize, Total: 0})
|
||||
return
|
||||
}
|
||||
response.ServerError(c, "查询购买用户失败")
|
||||
return
|
||||
}
|
||||
if filtered {
|
||||
userID = resolvedUserID
|
||||
if !matched {
|
||||
response.OK(c, response.Page{Items: []resourceShopPurchaseOrderDTO{}, Page: options.Page, PageSize: options.PageSize, Total: 0})
|
||||
return
|
||||
}
|
||||
userID = resolvedUserID
|
||||
}
|
||||
ctx, cancel := h.walletRequestContext(c)
|
||||
defer cancel()
|
||||
|
||||
@ -2,9 +2,6 @@ package resource
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -117,32 +114,6 @@ func applyGrantTargetUsers(grants []grantDTO, users map[int64]grantUserDTO) {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) resolveResourceGrantListTargetUserID(ctx context.Context, appCode string, raw string) (int64, bool, bool, error) {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return 0, false, false, nil
|
||||
}
|
||||
// 资源赠送记录的最终 owner 是 wallet-service,wallet 只接受内部 user_id 过滤;
|
||||
// 后台列表输入框展示的是“用户信息”,运营会输入默认短号或靓号,所以这里先按 user-service 身份投影解析。
|
||||
// 解析不到时保留旧版数字 target_user_id 行为,避免用户资料缺失时历史赠送记录完全查不到。
|
||||
fallbackUserID, parseErr := strconv.ParseInt(raw, 10, 64)
|
||||
if h != nil && h.userDB != nil {
|
||||
resolvedUserID, filtered, err := h.resolveResourceShopPurchaseUserID(ctx, appCode, raw)
|
||||
switch {
|
||||
case err == nil && filtered:
|
||||
return resolvedUserID, true, false, nil
|
||||
case err == sql.ErrNoRows:
|
||||
// 没有用户身份投影时继续尝试按内部 user_id 过滤,兼容历史孤儿赠送记录。
|
||||
case err != nil:
|
||||
return 0, true, false, err
|
||||
}
|
||||
}
|
||||
if parseErr != nil || fallbackUserID < 0 {
|
||||
return 0, true, true, fmt.Errorf("target_user_id 参数不正确")
|
||||
}
|
||||
return fallbackUserID, true, false, nil
|
||||
}
|
||||
|
||||
func (h *Handler) enrichResourceShopPurchaseUsers(ctx context.Context, orders []resourceShopPurchaseOrderDTO) error {
|
||||
ids := collectResourceShopPurchaseUserIDs(orders)
|
||||
if len(ids) == 0 {
|
||||
@ -191,29 +162,11 @@ func (h *Handler) resolveResourceShopPurchaseUserID(ctx context.Context, appCode
|
||||
if keyword == "" {
|
||||
return 0, false, nil
|
||||
}
|
||||
if h == nil || h.userDB == nil {
|
||||
return 0, false, fmt.Errorf("user mysql is not configured")
|
||||
if h == nil {
|
||||
return 0, false, nil
|
||||
}
|
||||
nowMs := time.Now().UnixMilli()
|
||||
matchSQL, matchArgs := shared.UserIdentityExactSQL("u", "u.user_id", keyword, nowMs)
|
||||
orderSQL, orderArgs := shared.UserIdentityExactOrderSQL("u", "u.user_id", keyword, nowMs)
|
||||
args := append([]any{appCode}, matchArgs...)
|
||||
args = append(args, orderArgs...)
|
||||
row := h.userDB.QueryRowContext(ctx, `
|
||||
SELECT u.user_id
|
||||
FROM users u
|
||||
WHERE u.app_code = ? AND `+matchSQL+`
|
||||
ORDER BY
|
||||
`+orderSQL+`,
|
||||
u.user_id DESC
|
||||
LIMIT 1`,
|
||||
args...,
|
||||
)
|
||||
var userID int64
|
||||
if err := row.Scan(&userID); err != nil {
|
||||
return 0, true, err
|
||||
}
|
||||
return userID, true, nil
|
||||
// 资源商店购买记录同样由 wallet-service 按内部 user_id 过滤;后台输入允许短号、靓号和昵称,先解析再下传。
|
||||
return shared.ResolveExactUserID(ctx, h.userDB, appCode, keyword, time.Now().UnixMilli())
|
||||
}
|
||||
|
||||
func (h *Handler) enrichGrantOperators(ctx context.Context, grants []grantDTO) error {
|
||||
|
||||
@ -202,41 +202,16 @@ func (h *Handler) resolveOwnerUserID(ctx context.Context, keyword string) (int64
|
||||
return 0, false, true
|
||||
}
|
||||
if h.userDB == nil {
|
||||
// userDB 缺失时只允许长 ID 精确筛选,避免把短号或昵称误当成 settlement.owner_user_id 查询。
|
||||
if parsed, err := strconv.ParseInt(keyword, 10, 64); err == nil && parsed > 0 {
|
||||
return parsed, true, true
|
||||
}
|
||||
return 0, false, true
|
||||
// userDB 缺失时只允许正整数长 ID 精确筛选,避免把短号或昵称误当成 settlement.owner_user_id 查询。
|
||||
userID, matched, err := shared.ResolveExactUserIDOrNumericFallback(ctx, nil, appctx.FromContext(ctx), keyword, time.Now().UnixMilli())
|
||||
return userID, matched, err == nil
|
||||
}
|
||||
appCode := appctx.FromContext(ctx)
|
||||
nowMs := time.Now().UnixMilli()
|
||||
matchSQL, matchArgs := shared.UserIdentityExactSQL("u", "u.user_id", keyword, nowMs)
|
||||
orderSQL, orderArgs := shared.UserIdentityExactOrderSQL("u", "u.user_id", keyword, nowMs)
|
||||
args := append([]any{appCode}, matchArgs...)
|
||||
args = append(args, orderArgs...)
|
||||
// 表头筛选先把运营输入解析成唯一房主 user_id,再交给 activity-service 的 owner_user_id 条件,保证分页 total 和列表数据同源。
|
||||
rows, err := h.userDB.QueryContext(ctx, `
|
||||
SELECT u.user_id
|
||||
FROM users u
|
||||
WHERE u.app_code = ? AND `+matchSQL+`
|
||||
ORDER BY
|
||||
`+orderSQL+`,
|
||||
u.user_id DESC
|
||||
LIMIT 1`,
|
||||
args...,
|
||||
)
|
||||
userID, matched, err := shared.ResolveExactUserID(ctx, h.userDB, appctx.FromContext(ctx), keyword, time.Now().UnixMilli())
|
||||
if err != nil {
|
||||
return 0, false, false
|
||||
}
|
||||
defer rows.Close()
|
||||
if !rows.Next() {
|
||||
return 0, false, rows.Err() == nil
|
||||
}
|
||||
var userID int64
|
||||
if err := rows.Scan(&userID); err != nil {
|
||||
return 0, false, false
|
||||
}
|
||||
return userID, true, rows.Err() == nil
|
||||
return userID, matched, true
|
||||
}
|
||||
|
||||
func (h *Handler) fillOwnerUsers(ctx context.Context, settlements []settlementDTO) error {
|
||||
|
||||
@ -163,37 +163,12 @@ func (h *Handler) resolveClaimUserID(ctx context.Context, keyword string) (int64
|
||||
if keyword == "" {
|
||||
return 0, false, true
|
||||
}
|
||||
if h.userDB == nil {
|
||||
return 0, false, true
|
||||
}
|
||||
appCode := appctx.FromContext(ctx)
|
||||
nowMs := time.Now().UnixMilli()
|
||||
matchSQL, matchArgs := shared.UserIdentityExactSQL("u", "u.user_id", keyword, nowMs)
|
||||
orderSQL, orderArgs := shared.UserIdentityExactOrderSQL("u", "u.user_id", keyword, nowMs)
|
||||
args := append([]any{appCode}, matchArgs...)
|
||||
args = append(args, orderArgs...)
|
||||
rows, err := h.userDB.QueryContext(ctx, `
|
||||
SELECT u.user_id
|
||||
FROM users u
|
||||
WHERE u.app_code = ? AND `+matchSQL+`
|
||||
ORDER BY
|
||||
`+orderSQL+`,
|
||||
u.user_id DESC
|
||||
LIMIT 1`,
|
||||
args...,
|
||||
)
|
||||
// 签到领取记录按 activity-service 的内部 user_id 查;短号、靓号和昵称先在 admin 侧解析,避免下游误用展示 ID。
|
||||
userID, matched, err := shared.ResolveExactUserID(ctx, h.userDB, appctx.FromContext(ctx), keyword, time.Now().UnixMilli())
|
||||
if err != nil {
|
||||
return 0, false, false
|
||||
}
|
||||
defer rows.Close()
|
||||
if !rows.Next() {
|
||||
return 0, false, rows.Err() == nil
|
||||
}
|
||||
var userID int64
|
||||
if err := rows.Scan(&userID); err != nil {
|
||||
return 0, false, false
|
||||
}
|
||||
return userID, true, rows.Err() == nil
|
||||
return userID, matched, true
|
||||
}
|
||||
|
||||
func (h *Handler) fillClaimUsers(ctx context.Context, claims []claimDTO) error {
|
||||
|
||||
@ -0,0 +1,63 @@
|
||||
package shared
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var ErrInvalidUserIdentityFilter = errors.New("invalid user identity filter")
|
||||
|
||||
// ResolveExactUserID 把后台输入框里的用户身份解析成 owner service 使用的内部 user_id。
|
||||
// 查询条件复用统一身份 SQL:精确长 ID、当前展示号、默认短号、active 靓号优先,昵称只做最后的模糊兜底。
|
||||
func ResolveExactUserID(ctx context.Context, db *sql.DB, appCode string, keyword string, nowMs int64) (int64, bool, error) {
|
||||
keyword = strings.TrimSpace(keyword)
|
||||
if keyword == "" || db == nil {
|
||||
return 0, false, nil
|
||||
}
|
||||
matchSQL, matchArgs := UserIdentityExactSQL("u", "u.user_id", keyword, nowMs)
|
||||
orderSQL, orderArgs := UserIdentityExactOrderSQL("u", "u.user_id", keyword, nowMs)
|
||||
args := append([]any{appCode}, matchArgs...)
|
||||
args = append(args, orderArgs...)
|
||||
row := db.QueryRowContext(ctx, `
|
||||
SELECT u.user_id
|
||||
FROM users u
|
||||
WHERE u.app_code = ? AND `+matchSQL+`
|
||||
ORDER BY
|
||||
`+orderSQL+`,
|
||||
u.user_id DESC
|
||||
LIMIT 1`,
|
||||
args...,
|
||||
)
|
||||
var userID int64
|
||||
if err := row.Scan(&userID); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return 0, false, nil
|
||||
}
|
||||
return 0, false, err
|
||||
}
|
||||
return userID, true, nil
|
||||
}
|
||||
|
||||
// ResolveExactUserIDOrNumericFallback 用于列表 owner 在 wallet/activity 等下游服务的场景。
|
||||
// 下游只认内部 user_id,后台先按短号/靓号解析;解析不到时只接受正整数长 ID,兼容用户资料缺失的历史事实记录。
|
||||
func ResolveExactUserIDOrNumericFallback(ctx context.Context, db *sql.DB, appCode string, keyword string, nowMs int64) (int64, bool, error) {
|
||||
keyword = strings.TrimSpace(keyword)
|
||||
if keyword == "" {
|
||||
return 0, false, nil
|
||||
}
|
||||
if db != nil {
|
||||
userID, matched, err := ResolveExactUserID(ctx, db, appCode, keyword, nowMs)
|
||||
if err != nil || matched {
|
||||
return userID, matched, err
|
||||
}
|
||||
}
|
||||
userID, err := strconv.ParseInt(keyword, 10, 64)
|
||||
if err != nil || userID <= 0 {
|
||||
return 0, true, fmt.Errorf("%w: %s", ErrInvalidUserIdentityFilter, keyword)
|
||||
}
|
||||
return userID, true, nil
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user