修复(#38): commission_refund 收窄到只查 type=333(移除 337/338 提现退佣混入)

Closes HIF-38

owner 业务定义:commission_refund 这个分类只应返回「下级退单导致用户拿到的佣金被扣回」记录。

改动:
- queryWithdrawalLogLogic.go: ?biz_type=commission_refund 分支 SQL 过滤从 IN(333,337,338) 改为 = 333
- 同步更新 logic/handler 单测

不在范围:
- /commission_return_log(新推荐接口)继续返回 333/337/338 — 前端暂未切,owner 决定不动
- 337/338 不另外开 UI 入口 — 提现记录 status 字段(rejected/cancelled)已表达
This commit is contained in:
2026-06-12 01:51:48 -07:00
committed by GitHub
parent f11097ab83
commit cfb253d96f
4 changed files with 73 additions and 32 deletions
@@ -2,6 +2,7 @@ package user
import ( import (
"context" "context"
"database/sql/driver"
"fmt" "fmt"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
@@ -25,7 +26,7 @@ func TestCommissionReturnLogHandler_HTTPResponse(t *testing.T) {
db, mock, cleanup := newCommissionReturnHandlerTestDB(t) db, mock, cleanup := newCommissionReturnHandlerTestDB(t)
defer cleanup() defer cleanup()
expectCommissionReturnHTTPQueries(mock, 42, 3) expectCommissionReturnHTTPQueries(mock, 42, 3, logmodel.CommissionTypeRefund, logmodel.CommissionTypeWithdrawReject, logmodel.CommissionTypeWithdrawCancel)
mock.ExpectQuery("SELECT * FROM `system_logs` WHERE `type` = ? AND object_id = ? AND (`content` LIKE ? OR `content` LIKE ? OR `content` LIKE ?) ORDER BY id DESC LIMIT ?"). mock.ExpectQuery("SELECT * FROM `system_logs` WHERE `type` = ? AND object_id = ? AND (`content` LIKE ? OR `content` LIKE ? OR `content` LIKE ?) ORDER BY id DESC LIMIT ?").
WithArgs(logmodel.TypeCommission.Uint8(), int64(42), "%\"type\":333%", "%\"type\":337%", "%\"type\":338%", 10). WithArgs(logmodel.TypeCommission.Uint8(), int64(42), "%\"type\":333%", "%\"type\":337%", "%\"type\":338%", 10).
WillReturnRows(sqlmock.NewRows([]string{"id", "type", "date", "object_id", "content", "created_at"}). WillReturnRows(sqlmock.NewRows([]string{"id", "type", "date", "object_id", "content", "created_at"}).
@@ -62,12 +63,10 @@ func TestWithdrawalLogHandler_CommissionRefundHTTPResponse(t *testing.T) {
db, mock, cleanup := newCommissionReturnHandlerTestDB(t) db, mock, cleanup := newCommissionReturnHandlerTestDB(t)
defer cleanup() defer cleanup()
expectCommissionReturnHTTPQueries(mock, 42, 3) expectCommissionReturnHTTPQueries(mock, 42, 1, logmodel.CommissionTypeRefund)
mock.ExpectQuery("SELECT * FROM `system_logs` WHERE `type` = ? AND object_id = ? AND (`content` LIKE ? OR `content` LIKE ? OR `content` LIKE ?) ORDER BY id DESC LIMIT ?"). mock.ExpectQuery("SELECT * FROM `system_logs` WHERE `type` = ? AND object_id = ? AND (`content` LIKE ?) ORDER BY id DESC LIMIT ?").
WithArgs(logmodel.TypeCommission.Uint8(), int64(42), "%\"type\":333%", "%\"type\":337%", "%\"type\":338%", 10). WithArgs(logmodel.TypeCommission.Uint8(), int64(42), "%\"type\":333%", 10).
WillReturnRows(sqlmock.NewRows([]string{"id", "type", "date", "object_id", "content", "created_at"}). WillReturnRows(sqlmock.NewRows([]string{"id", "type", "date", "object_id", "content", "created_at"}).
AddRow(int64(2003), logmodel.TypeCommission.Uint8(), "2023-11-14", int64(42), `{"type":338,"amount":1500,"order_no":"ORDER-3","timestamp":1700000003123}`, time.Unix(1700000000, 0)).
AddRow(int64(2002), logmodel.TypeCommission.Uint8(), "2023-11-14", int64(42), `{"type":337,"amount":2000,"order_no":"ORDER-2","timestamp":1700000002123}`, time.Unix(1700000000, 0)).
AddRow(int64(2001), logmodel.TypeCommission.Uint8(), "2023-11-14", int64(42), `{"type":333,"amount":2500,"order_no":"ORDER-1","timestamp":1700000001123}`, time.Unix(1700000000, 0))) AddRow(int64(2001), logmodel.TypeCommission.Uint8(), "2023-11-14", int64(42), `{"type":333,"amount":2500,"order_no":"ORDER-1","timestamp":1700000001123}`, time.Unix(1700000000, 0)))
router := gin.New() router := gin.New()
@@ -85,7 +84,7 @@ func TestWithdrawalLogHandler_CommissionRefundHTTPResponse(t *testing.T) {
body := strings.TrimSpace(rec.Body.String()) body := strings.TrimSpace(rec.Body.String())
t.Logf("withdrawal_log commission_refund response: %s", body) t.Logf("withdrawal_log commission_refund response: %s", body)
if !strings.Contains(body, `"biz_type":"commission_refund"`) || !strings.Contains(body, `"amount":1500`) || !strings.Contains(body, `"amount":2000`) || !strings.Contains(body, `"amount":2500`) { if !strings.Contains(body, `"biz_type":"commission_refund"`) || !strings.Contains(body, `"amount":2500`) || strings.Contains(body, `"amount":1500`) || strings.Contains(body, `"amount":2000`) {
t.Fatalf("response body = %s", body) t.Fatalf("response body = %s", body)
} }
if err := mock.ExpectationsWereMet(); err != nil { if err := mock.ExpectationsWereMet(); err != nil {
@@ -125,8 +124,18 @@ func injectTestUser(userID int64) gin.HandlerFunc {
} }
} }
func expectCommissionReturnHTTPQueries(mock sqlmock.Sqlmock, userID int64, total int64) { func expectCommissionReturnHTTPQueries(mock sqlmock.Sqlmock, userID int64, total int64, eventTypes ...uint16) {
mock.ExpectQuery("SELECT count(*) FROM `system_logs` WHERE `type` = ? AND object_id = ? AND (`content` LIKE ? OR `content` LIKE ? OR `content` LIKE ?)"). query := "SELECT count(*) FROM `system_logs` WHERE `type` = ? AND object_id = ?"
WithArgs(logmodel.TypeCommission.Uint8(), userID, "%\"type\":333%", "%\"type\":337%", "%\"type\":338%"). args := []driver.Value{logmodel.TypeCommission.Uint8(), userID}
if len(eventTypes) > 0 {
clauses := make([]string, 0, len(eventTypes))
for _, eventType := range eventTypes {
clauses = append(clauses, "`content` LIKE ?")
args = append(args, fmt.Sprintf("%%\"type\":%d%%", eventType))
}
query += " AND (" + strings.Join(clauses, " OR ") + ")"
}
mock.ExpectQuery(query).
WithArgs(args...).
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(total)) WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(total))
} }
@@ -2,6 +2,8 @@ package user
import ( import (
"context" "context"
"strconv"
"strings"
"github.com/perfect-panel/server/internal/model/log" "github.com/perfect-panel/server/internal/model/log"
"github.com/perfect-panel/server/internal/model/user" "github.com/perfect-panel/server/internal/model/user"
@@ -81,15 +83,28 @@ func normalizePagination(page, size int) (int, int) {
} }
func (l *QueryCommissionReturnLogLogic) queryCommissionReturnLogRecords(userID int64, page, size int) ([]commissionReturnLogRecord, int64, error) { func (l *QueryCommissionReturnLogLogic) queryCommissionReturnLogRecords(userID int64, page, size int) ([]commissionReturnLogRecord, int64, error) {
return l.queryCommissionReturnLogRecordsByEventTypes(userID, page, size,
log.CommissionTypeRefund,
log.CommissionTypeWithdrawReject,
log.CommissionTypeWithdrawCancel,
)
}
func (l *QueryCommissionReturnLogLogic) queryCommissionReturnLogRecordsByEventTypes(userID int64, page, size int, eventTypes ...uint16) ([]commissionReturnLogRecord, int64, error) {
if len(eventTypes) == 0 {
return []commissionReturnLogRecord{}, 0, nil
}
likeClauses := make([]string, 0, len(eventTypes))
args := make([]interface{}, 0, len(eventTypes)+2)
args = append(args, log.TypeCommission.Uint8(), userID)
for _, eventType := range eventTypes {
likeClauses = append(likeClauses, "`content` LIKE ?")
args = append(args, commissionReturnLogEventTypePattern(eventType))
}
query := l.svcCtx.DB.WithContext(l.ctx). query := l.svcCtx.DB.WithContext(l.ctx).
Model(&log.SystemLog{}). Model(&log.SystemLog{}).
Where("`type` = ? AND object_id = ? AND (`content` LIKE ? OR `content` LIKE ? OR `content` LIKE ?)", Where("`type` = ? AND object_id = ? AND ("+strings.Join(likeClauses, " OR ")+")", args...)
log.TypeCommission.Uint8(),
userID,
"%\"type\":333%",
"%\"type\":337%",
"%\"type\":338%",
)
var total int64 var total int64
if err := query.Count(&total).Error; err != nil { if err := query.Count(&total).Error; err != nil {
@@ -133,6 +148,10 @@ func (l *QueryCommissionReturnLogLogic) queryCommissionReturnLogRecords(userID i
return list, total, nil return list, total, nil
} }
func commissionReturnLogEventTypePattern(eventType uint16) string {
return "%\"type\":" + strconv.FormatUint(uint64(eventType), 10) + "%"
}
func isCommissionReturnEventType(eventType uint16) bool { func isCommissionReturnEventType(eventType uint16) bool {
switch eventType { switch eventType {
case log.CommissionTypeRefund, log.CommissionTypeWithdrawReject, log.CommissionTypeWithdrawCancel: case log.CommissionTypeRefund, log.CommissionTypeWithdrawReject, log.CommissionTypeWithdrawCancel:
@@ -3,6 +3,7 @@ package user
import ( import (
"context" "context"
"github.com/perfect-panel/server/internal/model/log"
"github.com/perfect-panel/server/internal/model/user" "github.com/perfect-panel/server/internal/model/user"
"github.com/perfect-panel/server/internal/svc" "github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types" "github.com/perfect-panel/server/internal/types"
@@ -90,7 +91,7 @@ func (l *QueryWithdrawalLogLogic) queryWithdrawalLogs(userID int64, page, size i
func (l *QueryWithdrawalLogLogic) queryCommissionRefundLogs(userID int64, page, size int) (*types.QueryWithdrawalLogListResponse, error) { func (l *QueryWithdrawalLogLogic) queryCommissionRefundLogs(userID int64, page, size int) (*types.QueryWithdrawalLogListResponse, error) {
queryLogic := NewQueryCommissionReturnLogLogic(l.ctx, l.svcCtx) queryLogic := NewQueryCommissionReturnLogLogic(l.ctx, l.svcCtx)
rows, total, err := queryLogic.queryCommissionReturnLogRecords(userID, page, size) rows, total, err := queryLogic.queryCommissionReturnLogRecordsByEventTypes(userID, page, size, log.CommissionTypeRefund)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -3,6 +3,7 @@ package user
import ( import (
"bytes" "bytes"
"context" "context"
"database/sql/driver"
"fmt" "fmt"
"strings" "strings"
"testing" "testing"
@@ -69,7 +70,7 @@ func TestQueryCommissionReturnLog_HappyPathIncludes333337338(t *testing.T) {
db, mock, cleanup := newQueryWithdrawalLogTestDB(t) db, mock, cleanup := newQueryWithdrawalLogTestDB(t)
defer cleanup() defer cleanup()
expectCommissionReturnQueries(mock, userID, 3) expectCommissionReturnQueries(mock, userID, 3, logmodel.CommissionTypeRefund, logmodel.CommissionTypeWithdrawReject, logmodel.CommissionTypeWithdrawCancel)
mock.ExpectQuery("SELECT * FROM `system_logs` WHERE `type` = ? AND object_id = ? AND (`content` LIKE ? OR `content` LIKE ? OR `content` LIKE ?) ORDER BY id DESC LIMIT ?"). mock.ExpectQuery("SELECT * FROM `system_logs` WHERE `type` = ? AND object_id = ? AND (`content` LIKE ? OR `content` LIKE ? OR `content` LIKE ?) ORDER BY id DESC LIMIT ?").
WithArgs(logmodel.TypeCommission.Uint8(), userID, "%\"type\":333%", "%\"type\":337%", "%\"type\":338%", 10). WithArgs(logmodel.TypeCommission.Uint8(), userID, "%\"type\":333%", "%\"type\":337%", "%\"type\":338%", 10).
WillReturnRows(sqlmock.NewRows([]string{"id", "type", "date", "object_id", "content", "created_at"}). WillReturnRows(sqlmock.NewRows([]string{"id", "type", "date", "object_id", "content", "created_at"}).
@@ -95,19 +96,17 @@ func TestQueryCommissionReturnLog_HappyPathIncludes333337338(t *testing.T) {
assertQueryWithdrawalLogExpectations(t, mock) assertQueryWithdrawalLogExpectations(t, mock)
} }
func TestQueryWithdrawalLog_WithCommissionRefundBizTypeIncludes333337338(t *testing.T) { func TestQueryWithdrawalLog_WithCommissionRefundBizTypeOnlyIncludes333(t *testing.T) {
const userID = int64(42) const userID = int64(42)
createdAt := time.Unix(1700000000, 0) createdAt := time.Unix(1700000000, 0)
db, mock, cleanup := newQueryWithdrawalLogTestDB(t) db, mock, cleanup := newQueryWithdrawalLogTestDB(t)
defer cleanup() defer cleanup()
expectCommissionReturnQueries(mock, userID, 3) expectCommissionReturnQueries(mock, userID, 1, logmodel.CommissionTypeRefund)
mock.ExpectQuery("SELECT * FROM `system_logs` WHERE `type` = ? AND object_id = ? AND (`content` LIKE ? OR `content` LIKE ? OR `content` LIKE ?) ORDER BY id DESC LIMIT ?"). mock.ExpectQuery("SELECT * FROM `system_logs` WHERE `type` = ? AND object_id = ? AND (`content` LIKE ?) ORDER BY id DESC LIMIT ?").
WithArgs(logmodel.TypeCommission.Uint8(), userID, "%\"type\":333%", "%\"type\":337%", "%\"type\":338%", 10). WithArgs(logmodel.TypeCommission.Uint8(), userID, "%\"type\":333%", 10).
WillReturnRows(sqlmock.NewRows([]string{"id", "type", "date", "object_id", "content", "created_at"}). WillReturnRows(sqlmock.NewRows([]string{"id", "type", "date", "object_id", "content", "created_at"}).
AddRow(int64(2003), logmodel.TypeCommission.Uint8(), "2023-11-14", userID, `{"type":338,"amount":1500,"order_no":"ORDER-3","timestamp":1700000003123}`, createdAt).
AddRow(int64(2002), logmodel.TypeCommission.Uint8(), "2023-11-14", userID, `{"type":337,"amount":2000,"order_no":"ORDER-2","timestamp":1700000002123}`, createdAt).
AddRow(int64(2001), logmodel.TypeCommission.Uint8(), "2023-11-14", userID, `{"type":333,"amount":2500,"order_no":"ORDER-1","timestamp":1700000001123}`, createdAt)) AddRow(int64(2001), logmodel.TypeCommission.Uint8(), "2023-11-14", userID, `{"type":333,"amount":2500,"order_no":"ORDER-1","timestamp":1700000001123}`, createdAt))
logic := newTestQueryWithdrawalLogLogic(t, db, userID) logic := newTestQueryWithdrawalLogLogic(t, db, userID)
@@ -119,8 +118,8 @@ func TestQueryWithdrawalLog_WithCommissionRefundBizTypeIncludes333337338(t *test
if err != nil { if err != nil {
t.Fatalf("QueryWithdrawalLog unexpected error: %v", err) t.Fatalf("QueryWithdrawalLog unexpected error: %v", err)
} }
if resp.Total != 3 || len(resp.List) != 3 { if resp.Total != 1 || len(resp.List) != 1 {
t.Fatalf("QueryWithdrawalLog response = %+v, want three commission returns", resp) t.Fatalf("QueryWithdrawalLog response = %+v, want one commission refund", resp)
} }
for _, item := range resp.List { for _, item := range resp.List {
if item.BizType != withdrawalLogBizTypeCommissionRefund { if item.BizType != withdrawalLogBizTypeCommissionRefund {
@@ -130,6 +129,9 @@ func TestQueryWithdrawalLog_WithCommissionRefundBizTypeIncludes333337338(t *test
t.Fatalf("withdrawal-only fields should keep zero values, got %+v", item) t.Fatalf("withdrawal-only fields should keep zero values, got %+v", item)
} }
} }
if resp.List[0].Id != 2001 {
t.Fatalf("commission refund item id = %d, want 2001", resp.List[0].Id)
}
assertQueryWithdrawalLogExpectations(t, mock) assertQueryWithdrawalLogExpectations(t, mock)
} }
@@ -140,7 +142,7 @@ func TestQueryCommissionReturnLog_SkipsInvalidJSONAndLogsWarn(t *testing.T) {
db, mock, cleanup := newQueryWithdrawalLogTestDB(t) db, mock, cleanup := newQueryWithdrawalLogTestDB(t)
defer cleanup() defer cleanup()
expectCommissionReturnQueries(mock, userID, 2) expectCommissionReturnQueries(mock, userID, 2, logmodel.CommissionTypeRefund, logmodel.CommissionTypeWithdrawReject, logmodel.CommissionTypeWithdrawCancel)
mock.ExpectQuery("SELECT * FROM `system_logs` WHERE `type` = ? AND object_id = ? AND (`content` LIKE ? OR `content` LIKE ? OR `content` LIKE ?) ORDER BY id DESC LIMIT ?"). mock.ExpectQuery("SELECT * FROM `system_logs` WHERE `type` = ? AND object_id = ? AND (`content` LIKE ? OR `content` LIKE ? OR `content` LIKE ?) ORDER BY id DESC LIMIT ?").
WithArgs(logmodel.TypeCommission.Uint8(), userID, "%\"type\":333%", "%\"type\":337%", "%\"type\":338%", 10). WithArgs(logmodel.TypeCommission.Uint8(), userID, "%\"type\":333%", "%\"type\":337%", "%\"type\":338%", 10).
WillReturnRows(sqlmock.NewRows([]string{"id", "type", "date", "object_id", "content", "created_at"}). WillReturnRows(sqlmock.NewRows([]string{"id", "type", "date", "object_id", "content", "created_at"}).
@@ -171,7 +173,7 @@ func TestQueryCommissionReturnLog_FiltersOtherUsersByObjectID(t *testing.T) {
db, mock, cleanup := newQueryWithdrawalLogTestDB(t) db, mock, cleanup := newQueryWithdrawalLogTestDB(t)
defer cleanup() defer cleanup()
expectCommissionReturnQueries(mock, userID, 0) expectCommissionReturnQueries(mock, userID, 0, logmodel.CommissionTypeRefund, logmodel.CommissionTypeWithdrawReject, logmodel.CommissionTypeWithdrawCancel)
mock.ExpectQuery("SELECT * FROM `system_logs` WHERE `type` = ? AND object_id = ? AND (`content` LIKE ? OR `content` LIKE ? OR `content` LIKE ?) ORDER BY id DESC LIMIT ?"). mock.ExpectQuery("SELECT * FROM `system_logs` WHERE `type` = ? AND object_id = ? AND (`content` LIKE ? OR `content` LIKE ? OR `content` LIKE ?) ORDER BY id DESC LIMIT ?").
WithArgs(logmodel.TypeCommission.Uint8(), userID, "%\"type\":333%", "%\"type\":337%", "%\"type\":338%", 10). WithArgs(logmodel.TypeCommission.Uint8(), userID, "%\"type\":333%", "%\"type\":337%", "%\"type\":338%", 10).
WillReturnRows(sqlmock.NewRows([]string{"id", "type", "date", "object_id", "content", "created_at"})) WillReturnRows(sqlmock.NewRows([]string{"id", "type", "date", "object_id", "content", "created_at"}))
@@ -243,9 +245,19 @@ func newTestQueryCtx(userID int64) context.Context {
return context.WithValue(context.Background(), constant.CtxKeyUser, &usermodel.User{Id: userID}) return context.WithValue(context.Background(), constant.CtxKeyUser, &usermodel.User{Id: userID})
} }
func expectCommissionReturnQueries(mock sqlmock.Sqlmock, userID int64, total int64) { func expectCommissionReturnQueries(mock sqlmock.Sqlmock, userID int64, total int64, eventTypes ...uint16) {
mock.ExpectQuery("SELECT count(*) FROM `system_logs` WHERE `type` = ? AND object_id = ? AND (`content` LIKE ? OR `content` LIKE ? OR `content` LIKE ?)"). query := "SELECT count(*) FROM `system_logs` WHERE `type` = ? AND object_id = ?"
WithArgs(logmodel.TypeCommission.Uint8(), userID, "%\"type\":333%", "%\"type\":337%", "%\"type\":338%"). args := []driver.Value{logmodel.TypeCommission.Uint8(), userID}
if len(eventTypes) > 0 {
clauses := make([]string, 0, len(eventTypes))
for _, eventType := range eventTypes {
clauses = append(clauses, "`content` LIKE ?")
args = append(args, fmt.Sprintf("%%\"type\":%d%%", eventType))
}
query += " AND (" + strings.Join(clauses, " OR ") + ")"
}
mock.ExpectQuery(query).
WithArgs(args...).
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(total)) WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(total))
} }