合并 internal → main: 退款/提现/激活/CI 全面优化 #4
+23
-1
@@ -141,6 +141,23 @@ type (
|
||||
List []WithdrawalLog `json:"list"`
|
||||
Total int64 `json:"total"`
|
||||
}
|
||||
QueryCommissionReturnLogRequest {
|
||||
Page int `form:"page"`
|
||||
Size int `form:"size"`
|
||||
}
|
||||
CommissionReturnLog {
|
||||
Id int64 `json:"id"`
|
||||
UserId int64 `json:"user_id"`
|
||||
Amount int64 `json:"amount"`
|
||||
EventType uint16 `json:"event_type"`
|
||||
Content string `json:"content"`
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
UpdatedAt int64 `json:"updated_at"`
|
||||
}
|
||||
QueryCommissionReturnLogResponse {
|
||||
List []CommissionReturnLog `json:"list"`
|
||||
Total int64 `json:"total"`
|
||||
}
|
||||
GetDeviceOnlineStatsResponse {
|
||||
WeeklyStats []WeeklyStat `json:"weekly_stats"`
|
||||
ConnectionRecords ConnectionRecords `json:"connection_records"`
|
||||
@@ -384,10 +401,14 @@ service ppanel {
|
||||
@handler CancelWithdrawal
|
||||
post /withdrawal_cancel (CancelWithdrawalRequest) returns (WithdrawalLog)
|
||||
|
||||
@doc "Query Withdrawal Log"
|
||||
@doc "Query Withdrawal Log (biz_type=commission_refund deprecated, use /commission_return_log)"
|
||||
@handler QueryWithdrawalLog
|
||||
get /withdrawal_log (QueryWithdrawalLogListRequest) returns (QueryWithdrawalLogListResponse)
|
||||
|
||||
@doc "Query Commission Return Log"
|
||||
@handler QueryCommissionReturnLog
|
||||
get /commission_return_log (QueryCommissionReturnLogRequest) returns (QueryCommissionReturnLogResponse)
|
||||
|
||||
@doc "Device Online Statistics"
|
||||
@handler DeviceOnlineStatistics
|
||||
get /device_online_statistics returns (GetDeviceOnlineStatsResponse)
|
||||
@@ -447,3 +468,4 @@ service ppanel {
|
||||
@handler DeviceWsConnect
|
||||
get /device_ws_connect
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/perfect-panel/server/internal/model/log"
|
||||
"github.com/perfect-panel/server/internal/model/user"
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
"github.com/perfect-panel/server/internal/types"
|
||||
"github.com/perfect-panel/server/pkg/constant"
|
||||
"github.com/perfect-panel/server/pkg/logger"
|
||||
"github.com/perfect-panel/server/pkg/xerr"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type commissionReturnLogRecord struct {
|
||||
LogID int64
|
||||
UserID int64
|
||||
Amount int64
|
||||
EventType uint16
|
||||
Content string
|
||||
CreatedAt int64
|
||||
UpdatedAt int64
|
||||
}
|
||||
|
||||
type QueryCommissionReturnLogLogic struct {
|
||||
logger.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
// NewQueryCommissionReturnLogLogic Query Commission Return Log
|
||||
func NewQueryCommissionReturnLogLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryCommissionReturnLogLogic {
|
||||
return &QueryCommissionReturnLogLogic{
|
||||
Logger: logger.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *QueryCommissionReturnLogLogic) QueryCommissionReturnLog(req *types.QueryCommissionReturnLogRequest) (*types.QueryCommissionReturnLogResponse, error) {
|
||||
u, ok := l.ctx.Value(constant.CtxKeyUser).(*user.User)
|
||||
if !ok {
|
||||
l.Error("current user is not found in context")
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access")
|
||||
}
|
||||
|
||||
page, size := normalizePagination(req.Page, req.Size)
|
||||
list, total, err := l.queryCommissionReturnLogRecords(u.Id, page, size)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
respList := make([]types.CommissionReturnLog, 0, len(list))
|
||||
for _, item := range list {
|
||||
respList = append(respList, types.CommissionReturnLog{
|
||||
Id: item.LogID,
|
||||
UserId: item.UserID,
|
||||
Amount: item.Amount,
|
||||
EventType: item.EventType,
|
||||
Content: item.Content,
|
||||
CreatedAt: item.CreatedAt,
|
||||
UpdatedAt: item.UpdatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
return &types.QueryCommissionReturnLogResponse{
|
||||
List: respList,
|
||||
Total: total,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func normalizePagination(page, size int) (int, int) {
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
if size <= 0 {
|
||||
size = 10
|
||||
}
|
||||
return page, size
|
||||
}
|
||||
|
||||
func (l *QueryCommissionReturnLogLogic) queryCommissionReturnLogRecords(userID int64, page, size int) ([]commissionReturnLogRecord, int64, error) {
|
||||
query := l.svcCtx.DB.WithContext(l.ctx).
|
||||
Model(&log.SystemLog{}).
|
||||
Where("`type` = ? AND object_id = ? AND (`content` LIKE ? OR `content` LIKE ? OR `content` LIKE ?)",
|
||||
log.TypeCommission.Uint8(),
|
||||
userID,
|
||||
"%\"type\":333%",
|
||||
"%\"type\":337%",
|
||||
"%\"type\":338%",
|
||||
)
|
||||
|
||||
var total int64
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "count commission return logs failed: %v", err)
|
||||
}
|
||||
|
||||
var rows []log.SystemLog
|
||||
if err := query.Order("id DESC").Limit(size).Offset((page - 1) * size).Find(&rows).Error; err != nil {
|
||||
return nil, 0, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query commission return logs failed: %v", err)
|
||||
}
|
||||
|
||||
list := make([]commissionReturnLogRecord, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
var content log.Commission
|
||||
if err := content.Unmarshal([]byte(row.Content)); err != nil {
|
||||
l.Errorw("unmarshal commission return log content failed",
|
||||
logger.Field("log_id", row.Id),
|
||||
logger.Field("error", err.Error()),
|
||||
)
|
||||
continue
|
||||
}
|
||||
if !isCommissionReturnEventType(content.Type) {
|
||||
continue
|
||||
}
|
||||
|
||||
timestamp := content.Timestamp
|
||||
if timestamp == 0 {
|
||||
timestamp = row.CreatedAt.UnixMilli()
|
||||
}
|
||||
list = append(list, commissionReturnLogRecord{
|
||||
LogID: row.Id,
|
||||
UserID: row.ObjectID,
|
||||
Amount: content.Amount,
|
||||
EventType: content.Type,
|
||||
Content: row.Content,
|
||||
CreatedAt: timestamp,
|
||||
UpdatedAt: timestamp,
|
||||
})
|
||||
}
|
||||
|
||||
return list, total, nil
|
||||
}
|
||||
|
||||
func isCommissionReturnEventType(eventType uint16) bool {
|
||||
switch eventType {
|
||||
case log.CommissionTypeRefund, log.CommissionTypeWithdrawReject, log.CommissionTypeWithdrawCancel:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package user
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/perfect-panel/server/internal/model/log"
|
||||
"github.com/perfect-panel/server/internal/model/user"
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
"github.com/perfect-panel/server/internal/types"
|
||||
@@ -40,14 +39,7 @@ func (l *QueryWithdrawalLogLogic) QueryWithdrawalLog(req *types.QueryWithdrawalL
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access")
|
||||
}
|
||||
|
||||
page := req.Page
|
||||
size := req.Size
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
if size <= 0 {
|
||||
size = 10
|
||||
}
|
||||
page, size := normalizePagination(req.Page, req.Size)
|
||||
|
||||
switch req.BizType {
|
||||
case "", withdrawalLogBizTypeWithdrawal:
|
||||
@@ -97,46 +89,22 @@ func (l *QueryWithdrawalLogLogic) queryWithdrawalLogs(userID int64, page, size i
|
||||
}
|
||||
|
||||
func (l *QueryWithdrawalLogLogic) queryCommissionRefundLogs(userID int64, page, size int) (*types.QueryWithdrawalLogListResponse, error) {
|
||||
query := l.svcCtx.DB.WithContext(l.ctx).
|
||||
Model(&log.SystemLog{}).
|
||||
Where("`type` = ? AND object_id = ? AND `content` LIKE ?", log.TypeCommission.Uint8(), userID, "%\"type\":333%")
|
||||
|
||||
var total int64
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "count commission refund logs failed: %v", err)
|
||||
}
|
||||
|
||||
var rows []log.SystemLog
|
||||
if err := query.Order("id DESC").Limit(size).Offset((page - 1) * size).Find(&rows).Error; err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query commission refund logs failed: %v", err)
|
||||
queryLogic := NewQueryCommissionReturnLogLogic(l.ctx, l.svcCtx)
|
||||
rows, total, err := queryLogic.queryCommissionReturnLogRecords(userID, page, size)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
list := make([]types.WithdrawalLog, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
var content log.Commission
|
||||
if err := content.Unmarshal([]byte(row.Content)); err != nil {
|
||||
l.Errorw("unmarshal commission refund log content failed",
|
||||
logger.Field("log_id", row.Id),
|
||||
logger.Field("error", err.Error()),
|
||||
)
|
||||
continue
|
||||
}
|
||||
if content.Type != log.CommissionTypeRefund {
|
||||
continue
|
||||
}
|
||||
|
||||
timestamp := content.Timestamp
|
||||
if timestamp == 0 {
|
||||
timestamp = row.CreatedAt.UnixMilli()
|
||||
}
|
||||
list = append(list, types.WithdrawalLog{
|
||||
Id: row.Id,
|
||||
Id: row.LogID,
|
||||
BizType: withdrawalLogBizTypeCommissionRefund,
|
||||
UserId: row.ObjectID,
|
||||
Amount: content.Amount,
|
||||
UserId: row.UserID,
|
||||
Amount: row.Amount,
|
||||
Content: row.Content,
|
||||
CreatedAt: timestamp,
|
||||
UpdatedAt: timestamp,
|
||||
CreatedAt: row.CreatedAt,
|
||||
UpdatedAt: row.UpdatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
@@ -58,26 +59,56 @@ func TestQueryWithdrawalLog_WithWithdrawalBizType(t *testing.T) {
|
||||
if got.Id != 1001 || got.UserId != userID || got.Amount != 3000 || got.CreatedAt != createdAt.UnixMilli() || got.UpdatedAt != updatedAt.UnixMilli() {
|
||||
t.Fatalf("withdrawal item = %+v", got)
|
||||
}
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Fatalf("unmet sql expectations: %v", err)
|
||||
}
|
||||
assertQueryWithdrawalLogExpectations(t, mock)
|
||||
}
|
||||
|
||||
func TestQueryWithdrawalLog_WithCommissionRefundBizType(t *testing.T) {
|
||||
func TestQueryCommissionReturnLog_HappyPathIncludes333337338(t *testing.T) {
|
||||
const userID = int64(42)
|
||||
content := `{"type":333,"amount":2500,"order_no":"ORDER-1","timestamp":1700000000123}`
|
||||
createdAt := time.Unix(1700000000, 0)
|
||||
|
||||
db, mock, cleanup := newQueryWithdrawalLogTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
mock.ExpectQuery("SELECT count(*) FROM `system_logs` WHERE `type` = ? AND object_id = ? AND `content` LIKE ?").
|
||||
WithArgs(logmodel.TypeCommission.Uint8(), userID, "%\"type\":333%").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
|
||||
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%", 10).
|
||||
expectCommissionReturnQueries(mock, userID, 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(), userID, "%\"type\":333%", "%\"type\":337%", "%\"type\":338%", 10).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "type", "date", "object_id", "content", "created_at"}).
|
||||
AddRow(int64(2001), logmodel.TypeCommission.Uint8(), "2023-11-14", userID, content, createdAt))
|
||||
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))
|
||||
|
||||
logic := NewQueryCommissionReturnLogLogic(newTestQueryCtx(userID), &svc.ServiceContext{DB: db})
|
||||
resp, err := logic.QueryCommissionReturnLog(&types.QueryCommissionReturnLogRequest{Page: 1, Size: 10})
|
||||
if err != nil {
|
||||
t.Fatalf("QueryCommissionReturnLog unexpected error: %v", err)
|
||||
}
|
||||
if resp.Total != 3 || len(resp.List) != 3 {
|
||||
t.Fatalf("QueryCommissionReturnLog response = %+v, want three logs", resp)
|
||||
}
|
||||
|
||||
eventTypes := []uint16{resp.List[0].EventType, resp.List[1].EventType, resp.List[2].EventType}
|
||||
wantTypes := []uint16{logmodel.CommissionTypeWithdrawCancel, logmodel.CommissionTypeWithdrawReject, logmodel.CommissionTypeRefund}
|
||||
if fmt.Sprint(eventTypes) != fmt.Sprint(wantTypes) {
|
||||
t.Fatalf("event types = %v, want %v", eventTypes, wantTypes)
|
||||
}
|
||||
|
||||
assertQueryWithdrawalLogExpectations(t, mock)
|
||||
}
|
||||
|
||||
func TestQueryWithdrawalLog_WithCommissionRefundBizTypeIncludes333337338(t *testing.T) {
|
||||
const userID = int64(42)
|
||||
createdAt := time.Unix(1700000000, 0)
|
||||
|
||||
db, mock, cleanup := newQueryWithdrawalLogTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
expectCommissionReturnQueries(mock, userID, 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(), userID, "%\"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", 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))
|
||||
|
||||
logic := newTestQueryWithdrawalLogLogic(t, db, userID)
|
||||
resp, err := logic.QueryWithdrawalLog(&types.QueryWithdrawalLogListRequest{
|
||||
@@ -88,22 +119,72 @@ func TestQueryWithdrawalLog_WithCommissionRefundBizType(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("QueryWithdrawalLog unexpected error: %v", err)
|
||||
}
|
||||
if resp.Total != 1 || len(resp.List) != 1 {
|
||||
t.Fatalf("QueryWithdrawalLog response = %+v, want one commission refund", resp)
|
||||
if resp.Total != 3 || len(resp.List) != 3 {
|
||||
t.Fatalf("QueryWithdrawalLog response = %+v, want three commission returns", resp)
|
||||
}
|
||||
got := resp.List[0]
|
||||
if got.BizType != withdrawalLogBizTypeCommissionRefund {
|
||||
t.Fatalf("BizType = %q, want %q", got.BizType, withdrawalLogBizTypeCommissionRefund)
|
||||
for _, item := range resp.List {
|
||||
if item.BizType != withdrawalLogBizTypeCommissionRefund {
|
||||
t.Fatalf("BizType = %q, want %q", item.BizType, withdrawalLogBizTypeCommissionRefund)
|
||||
}
|
||||
if item.Status != 0 || item.Reason != "" || item.Method != 0 || item.Account != "" || item.QrCodeUrl != "" {
|
||||
t.Fatalf("withdrawal-only fields should keep zero values, got %+v", item)
|
||||
}
|
||||
}
|
||||
if got.Id != 2001 || got.UserId != userID || got.Amount != 2500 || got.Content != content || got.CreatedAt != 1700000000123 || got.UpdatedAt != 1700000000123 {
|
||||
t.Fatalf("commission refund item = %+v", got)
|
||||
assertQueryWithdrawalLogExpectations(t, mock)
|
||||
}
|
||||
|
||||
func TestQueryCommissionReturnLog_SkipsInvalidJSONAndLogsWarn(t *testing.T) {
|
||||
const userID = int64(42)
|
||||
createdAt := time.Unix(1700000000, 0)
|
||||
|
||||
db, mock, cleanup := newQueryWithdrawalLogTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
expectCommissionReturnQueries(mock, userID, 2)
|
||||
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).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "type", "date", "object_id", "content", "created_at"}).
|
||||
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`, createdAt))
|
||||
|
||||
var buf bytes.Buffer
|
||||
restoreLogger := captureTestLogs(&buf)
|
||||
defer restoreLogger()
|
||||
|
||||
logic := NewQueryCommissionReturnLogLogic(newTestQueryCtx(userID), &svc.ServiceContext{DB: db})
|
||||
resp, err := logic.QueryCommissionReturnLog(&types.QueryCommissionReturnLogRequest{Page: 1, Size: 10})
|
||||
if err != nil {
|
||||
t.Fatalf("QueryCommissionReturnLog unexpected error: %v", err)
|
||||
}
|
||||
if got.Status != 0 || got.Method != 0 || got.Account != "" || got.QrCodeUrl != "" {
|
||||
t.Fatalf("commission refund withdrawal-only fields = %+v, want zero values", got)
|
||||
if resp.Total != 2 || len(resp.List) != 1 {
|
||||
t.Fatalf("QueryCommissionReturnLog response = %+v, want total=2 and one valid row", resp)
|
||||
}
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Fatalf("unmet sql expectations: %v", err)
|
||||
if !strings.Contains(buf.String(), "unmarshal commission return log content failed") {
|
||||
t.Fatalf("expected warn log, got %q", buf.String())
|
||||
}
|
||||
assertQueryWithdrawalLogExpectations(t, mock)
|
||||
}
|
||||
|
||||
func TestQueryCommissionReturnLog_FiltersOtherUsersByObjectID(t *testing.T) {
|
||||
const userID = int64(42)
|
||||
|
||||
db, mock, cleanup := newQueryWithdrawalLogTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
expectCommissionReturnQueries(mock, userID, 0)
|
||||
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).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "type", "date", "object_id", "content", "created_at"}))
|
||||
|
||||
logic := NewQueryCommissionReturnLogLogic(newTestQueryCtx(userID), &svc.ServiceContext{DB: db})
|
||||
resp, err := logic.QueryCommissionReturnLog(&types.QueryCommissionReturnLogRequest{Page: 1, Size: 10})
|
||||
if err != nil {
|
||||
t.Fatalf("QueryCommissionReturnLog unexpected error: %v", err)
|
||||
}
|
||||
if resp.Total != 0 || len(resp.List) != 0 {
|
||||
t.Fatalf("QueryCommissionReturnLog response = %+v, want no rows for filtered user", resp)
|
||||
}
|
||||
assertQueryWithdrawalLogExpectations(t, mock)
|
||||
}
|
||||
|
||||
func TestQueryWithdrawalLog_RejectsInvalidBizType(t *testing.T) {
|
||||
@@ -119,9 +200,7 @@ func TestQueryWithdrawalLog_RejectsInvalidBizType(t *testing.T) {
|
||||
if !isQueryWithdrawalLogErrCode(err, xerr.InvalidParams) {
|
||||
t.Fatalf("QueryWithdrawalLog err = %v, want InvalidParams", err)
|
||||
}
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Fatalf("unmet sql expectations: %v", err)
|
||||
}
|
||||
assertQueryWithdrawalLogExpectations(t, mock)
|
||||
}
|
||||
|
||||
func newQueryWithdrawalLogTestDB(t *testing.T) (*gorm.DB, sqlmock.Sqlmock, func()) {
|
||||
@@ -150,7 +229,7 @@ func newQueryWithdrawalLogTestDB(t *testing.T) (*gorm.DB, sqlmock.Sqlmock, func(
|
||||
|
||||
func newTestQueryWithdrawalLogLogic(t *testing.T, db *gorm.DB, userID int64) *QueryWithdrawalLogLogic {
|
||||
t.Helper()
|
||||
ctx := context.WithValue(context.Background(), constant.CtxKeyUser, &usermodel.User{Id: userID})
|
||||
ctx := newTestQueryCtx(userID)
|
||||
return &QueryWithdrawalLogLogic{
|
||||
Logger: logger.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
@@ -160,6 +239,37 @@ func newTestQueryWithdrawalLogLogic(t *testing.T, db *gorm.DB, userID int64) *Qu
|
||||
}
|
||||
}
|
||||
|
||||
func newTestQueryCtx(userID int64) context.Context {
|
||||
return context.WithValue(context.Background(), constant.CtxKeyUser, &usermodel.User{Id: userID})
|
||||
}
|
||||
|
||||
func expectCommissionReturnQueries(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))
|
||||
}
|
||||
|
||||
func assertQueryWithdrawalLogExpectations(t *testing.T, mock sqlmock.Sqlmock) {
|
||||
t.Helper()
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Fatalf("unmet sql expectations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func captureTestLogs(buf *bytes.Buffer) func() {
|
||||
prevWriter := logger.Reset()
|
||||
prevLevel := logger.InfoLevel
|
||||
logger.SetLevel(logger.DebugLevel)
|
||||
logger.SetWriter(logger.NewWriter(buf))
|
||||
return func() {
|
||||
logger.Reset()
|
||||
logger.SetLevel(prevLevel)
|
||||
if prevWriter != nil {
|
||||
logger.SetWriter(prevWriter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func queryWithdrawalLogErrCodeOf(err error) uint32 {
|
||||
if err == nil {
|
||||
return 0
|
||||
|
||||
@@ -309,6 +309,16 @@ type CommissionLog struct {
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
}
|
||||
|
||||
type CommissionReturnLog struct {
|
||||
Id int64 `json:"id"`
|
||||
UserId int64 `json:"user_id"`
|
||||
Amount int64 `json:"amount"`
|
||||
EventType uint16 `json:"event_type"`
|
||||
Content string `json:"content"`
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
UpdatedAt int64 `json:"updated_at"`
|
||||
}
|
||||
|
||||
type CommissionWithdrawRequest struct {
|
||||
Amount int64 `json:"amount"`
|
||||
Content string `json:"content"`
|
||||
@@ -2376,6 +2386,16 @@ type QueryAnnouncementResponse struct {
|
||||
List []Announcement `json:"announcements"`
|
||||
}
|
||||
|
||||
type QueryCommissionReturnLogRequest struct {
|
||||
Page int `form:"page"`
|
||||
Size int `form:"size"`
|
||||
}
|
||||
|
||||
type QueryCommissionReturnLogResponse struct {
|
||||
List []CommissionReturnLog `json:"list"`
|
||||
Total int64 `json:"total"`
|
||||
}
|
||||
|
||||
type QueryDocumentDetailRequest struct {
|
||||
Id int64 `form:"id" validate:"required"`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user