新功能(#26): 拆分退款日志查询接口

拆分用户中心退款日志查询:

- 新增 GET /v1/public/user/commission_return_log(333/337/338)
- 旧 GET /v1/public/user/withdrawal_log?biz_type=commission_refund 复用新逻辑做兼容
- 默认 withdrawal_log 行为不变(仍查 withdrawals 表)
- 单测覆盖 333/337/338 happy path、坏 JSON 跳过、object_id 隔离、handler 级 HTTP 响应

父 issue: HIF-25
子 issue: HIF-26
This commit is contained in:
2026-06-11 22:02:22 -07:00
committed by GitHub
parent 3e6318dcdf
commit f11097ab83
9 changed files with 494 additions and 70 deletions
@@ -0,0 +1,26 @@
package user
import (
"github.com/gin-gonic/gin"
"github.com/perfect-panel/server/internal/logic/public/user"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/result"
)
// Query Commission Return Log
func QueryCommissionReturnLogHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) {
var req types.QueryCommissionReturnLogRequest
_ = c.ShouldBind(&req)
validateErr := svcCtx.Validate(&req)
if validateErr != nil {
result.ParamErrorResult(c, validateErr)
return
}
l := user.NewQueryCommissionReturnLogLogic(c.Request.Context(), svcCtx)
resp, err := l.QueryCommissionReturnLog(&req)
result.HttpResult(c, resp, err)
}
}
@@ -0,0 +1,132 @@
package user
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"github.com/DATA-DOG/go-sqlmock"
"github.com/gin-gonic/gin"
logmodel "github.com/perfect-panel/server/internal/model/log"
usermodel "github.com/perfect-panel/server/internal/model/user"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/pkg/constant"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func TestCommissionReturnLogHandler_HTTPResponse(t *testing.T) {
gin.SetMode(gin.TestMode)
db, mock, cleanup := newCommissionReturnHandlerTestDB(t)
defer cleanup()
expectCommissionReturnHTTPQueries(mock, 42, 3)
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).
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)))
router := gin.New()
svcCtx := &svc.ServiceContext{DB: db}
router.Use(injectTestUser(42))
router.GET("/v1/public/user/commission_return_log", QueryCommissionReturnLogHandler(svcCtx))
req := httptest.NewRequest(http.MethodGet, "/v1/public/user/commission_return_log?page=1&size=10", nil)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want 200; body=%s", rec.Code, rec.Body.String())
}
body := strings.TrimSpace(rec.Body.String())
t.Logf("commission_return_log response: %s", body)
if !strings.Contains(body, `"event_type":338`) || !strings.Contains(body, `"event_type":337`) || !strings.Contains(body, `"event_type":333`) {
t.Fatalf("response body = %s", body)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatalf("unmet sql expectations: %v", err)
}
}
func TestWithdrawalLogHandler_CommissionRefundHTTPResponse(t *testing.T) {
gin.SetMode(gin.TestMode)
db, mock, cleanup := newCommissionReturnHandlerTestDB(t)
defer cleanup()
expectCommissionReturnHTTPQueries(mock, 42, 3)
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).
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)))
router := gin.New()
svcCtx := &svc.ServiceContext{DB: db}
router.Use(injectTestUser(42))
router.GET("/v1/public/user/withdrawal_log", QueryWithdrawalLogHandler(svcCtx))
req := httptest.NewRequest(http.MethodGet, "/v1/public/user/withdrawal_log?page=1&size=10&biz_type=commission_refund", nil)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want 200; body=%s", rec.Code, rec.Body.String())
}
body := strings.TrimSpace(rec.Body.String())
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`) {
t.Fatalf("response body = %s", body)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatalf("unmet sql expectations: %v", err)
}
}
func newCommissionReturnHandlerTestDB(t *testing.T) (*gorm.DB, sqlmock.Sqlmock, func()) {
t.Helper()
sqlDB, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherFunc(func(expectedSQL, actualSQL string) error {
if strings.Contains(actualSQL, expectedSQL) {
return nil
}
return fmt.Errorf("actual sql %q does not contain %q", actualSQL, expectedSQL)
})))
if err != nil {
t.Fatalf("create sqlmock: %v", err)
}
db, err := gorm.Open(mysql.New(mysql.Config{Conn: sqlDB, SkipInitializeWithVersion: true}), &gorm.Config{})
if err != nil {
_ = sqlDB.Close()
t.Fatalf("open gorm db: %v", err)
}
return db, mock, func() {
_ = sqlDB.Close()
}
}
func injectTestUser(userID int64) gin.HandlerFunc {
return func(c *gin.Context) {
ctx := context.WithValue(c.Request.Context(), constant.CtxKeyUser, &usermodel.User{Id: userID})
c.Request = c.Request.WithContext(ctx)
c.Next()
}
}
func expectCommissionReturnHTTPQueries(mock sqlmock.Sqlmock, userID int64, total int64) {
mock.ExpectQuery("SELECT count(*) FROM `system_logs` WHERE `type` = ? AND object_id = ? AND (`content` LIKE ? OR `content` LIKE ? OR `content` LIKE ?)").
WithArgs(logmodel.TypeCommission.Uint8(), userID, "%\"type\":333%", "%\"type\":337%", "%\"type\":338%").
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(total))
}
@@ -8,7 +8,7 @@ import (
"github.com/perfect-panel/server/pkg/result"
)
// Query Withdrawal Log
// Query Withdrawal Log (biz_type=commission_refund deprecated, use /commission_return_log)
func QueryWithdrawalLogHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) {
var req types.QueryWithdrawalLogListRequest
+3
View File
@@ -1180,6 +1180,9 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
// Verify Email
publicUserGroupRouter.POST("/verify_email", publicUser.VerifyEmailHandler(serverCtx))
// Query Commission Return Log
publicUserGroupRouter.GET("/commission_return_log", publicUser.QueryCommissionReturnLogHandler(serverCtx))
// Query Withdrawal Log
publicUserGroupRouter.GET("/withdrawal_log", publicUser.QueryWithdrawalLogHandler(serverCtx))
}