币商充值
This commit is contained in:
parent
f8a8d4d65f
commit
a2ef52c8d9
@ -19,6 +19,7 @@ import (
|
|||||||
"chatapp3-golang/internal/service/invite"
|
"chatapp3-golang/internal/service/invite"
|
||||||
"chatapp3-golang/internal/service/lingxian"
|
"chatapp3-golang/internal/service/lingxian"
|
||||||
"chatapp3-golang/internal/service/luckygift"
|
"chatapp3-golang/internal/service/luckygift"
|
||||||
|
"chatapp3-golang/internal/service/rechargeagency"
|
||||||
"chatapp3-golang/internal/service/rechargereward"
|
"chatapp3-golang/internal/service/rechargereward"
|
||||||
"chatapp3-golang/internal/service/registerreward"
|
"chatapp3-golang/internal/service/registerreward"
|
||||||
"chatapp3-golang/internal/service/roomturnoverreward"
|
"chatapp3-golang/internal/service/roomturnoverreward"
|
||||||
@ -41,6 +42,7 @@ func main() {
|
|||||||
lingxianService := lingxian.NewService(app.Config, app.Repository.DB, app.Repository.Redis)
|
lingxianService := lingxian.NewService(app.Config, app.Repository.DB, app.Repository.Redis)
|
||||||
gameOpenService := gameopen.NewGameOpenService(app.Config, app.Repository.DB, &app.Gateways)
|
gameOpenService := gameopen.NewGameOpenService(app.Config, app.Repository.DB, &app.Gateways)
|
||||||
luckyGiftService := luckygift.NewLuckyGiftService(app.Config, app.Repository.DB, app.Repository.Redis, app.Gateways.Wallet)
|
luckyGiftService := luckygift.NewLuckyGiftService(app.Config, app.Repository.DB, app.Repository.Redis, app.Gateways.Wallet)
|
||||||
|
rechargeAgencyService := rechargeagency.NewService(app.Config, app.Repository.DB, &app.Gateways)
|
||||||
registerRewardService := registerreward.NewRegisterRewardService(app.Config, app.Repository.DB, app.Repository.Redis, &app.Gateways)
|
registerRewardService := registerreward.NewRegisterRewardService(app.Config, app.Repository.DB, app.Repository.Redis, &app.Gateways)
|
||||||
rechargeRewardService := rechargereward.NewService(app.Config, app.Repository.DB, &app.Gateways)
|
rechargeRewardService := rechargereward.NewService(app.Config, app.Repository.DB, &app.Gateways)
|
||||||
signInRewardService := signinreward.NewSignInRewardService(app.Config, app.Repository.DB, app.Repository.Redis, &app.Gateways)
|
signInRewardService := signinreward.NewSignInRewardService(app.Config, app.Repository.DB, app.Repository.Redis, &app.Gateways)
|
||||||
@ -67,6 +69,7 @@ func main() {
|
|||||||
GameProviders: gameProviders,
|
GameProviders: gameProviders,
|
||||||
LuckyGift: luckyGiftService,
|
LuckyGift: luckyGiftService,
|
||||||
HostCenter: hostCenterService,
|
HostCenter: hostCenterService,
|
||||||
|
RechargeAgency: rechargeAgencyService,
|
||||||
RechargeReward: rechargeRewardService,
|
RechargeReward: rechargeRewardService,
|
||||||
RegisterReward: registerRewardService,
|
RegisterReward: registerRewardService,
|
||||||
RoomTurnoverReward: roomTurnoverRewardService,
|
RoomTurnoverReward: roomTurnoverRewardService,
|
||||||
|
|||||||
@ -19,6 +19,7 @@ type Gateways struct {
|
|||||||
Room *RoomGateway
|
Room *RoomGateway
|
||||||
Team *TeamGateway
|
Team *TeamGateway
|
||||||
Wallet *WalletGateway
|
Wallet *WalletGateway
|
||||||
|
Freight *FreightGateway
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGateways 基于共享 HTTP 客户端创建所有 Java 网关。
|
// NewGateways 基于共享 HTTP 客户端创建所有 Java 网关。
|
||||||
@ -36,6 +37,7 @@ func NewGateways(cfg config.Config) Gateways {
|
|||||||
Room: &RoomGateway{client: client},
|
Room: &RoomGateway{client: client},
|
||||||
Team: &TeamGateway{client: client},
|
Team: &TeamGateway{client: client},
|
||||||
Wallet: &WalletGateway{client: client},
|
Wallet: &WalletGateway{client: client},
|
||||||
|
Freight: &FreightGateway{client: client},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,6 +186,11 @@ func (g *Gateways) ChangeGoldBalance(ctx context.Context, cmd GoldReceiptCommand
|
|||||||
return g.Wallet.ChangeGoldBalance(ctx, cmd)
|
return g.Wallet.ChangeGoldBalance(ctx, cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PageFreightSellers 透传到 H5 币商列表接口。
|
||||||
|
func (g *Gateways) PageFreightSellers(ctx context.Context, authorization string, sysOrigin string, cursor int, limit int) (FreightSellerPage, error) {
|
||||||
|
return g.Freight.PageFreightSellers(ctx, authorization, sysOrigin, cursor, limit)
|
||||||
|
}
|
||||||
|
|
||||||
// AuthGateway 负责认证相关接口。
|
// AuthGateway 负责认证相关接口。
|
||||||
type AuthGateway struct {
|
type AuthGateway struct {
|
||||||
client *Client
|
client *Client
|
||||||
@ -383,3 +390,13 @@ func (g *WalletGateway) ExistsGoldEvent(ctx context.Context, eventID string) (bo
|
|||||||
func (g *WalletGateway) ChangeGoldBalance(ctx context.Context, cmd GoldReceiptCommand) error {
|
func (g *WalletGateway) ChangeGoldBalance(ctx context.Context, cmd GoldReceiptCommand) error {
|
||||||
return g.client.ChangeGoldBalance(ctx, cmd)
|
return g.client.ChangeGoldBalance(ctx, cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FreightGateway 负责货运代理/币商相关接口。
|
||||||
|
type FreightGateway struct {
|
||||||
|
client *Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// PageFreightSellers 查询 H5 可见币商列表。
|
||||||
|
func (g *FreightGateway) PageFreightSellers(ctx context.Context, authorization string, sysOrigin string, cursor int, limit int) (FreightSellerPage, error) {
|
||||||
|
return g.client.PageFreightSellers(ctx, authorization, sysOrigin, cursor, limit)
|
||||||
|
}
|
||||||
|
|||||||
@ -2,10 +2,10 @@ package integration
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"chatapp3-golang/internal/config"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -13,6 +13,8 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"chatapp3-golang/internal/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
@ -213,6 +215,57 @@ type UserTotalRecharge struct {
|
|||||||
UpdateTime string `json:"updateTime"`
|
UpdateTime string `json:"updateTime"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FreightSellerPage struct {
|
||||||
|
Records []FreightSellerRecord `json:"records"`
|
||||||
|
Total int64 `json:"total"`
|
||||||
|
Current int `json:"current"`
|
||||||
|
Size int `json:"size"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FreightSellerRecord struct {
|
||||||
|
ID Int64Value `json:"id"`
|
||||||
|
SysOrigin string `json:"sysOrigin"`
|
||||||
|
UserID Int64Value `json:"userId"`
|
||||||
|
EarnPoints DecimalString `json:"earnPoints"`
|
||||||
|
ConsumptionPoints DecimalString `json:"consumptionPoints"`
|
||||||
|
Balance DecimalString `json:"balance"`
|
||||||
|
Close bool `json:"close"`
|
||||||
|
Display bool `json:"display"`
|
||||||
|
Dealer bool `json:"dealer"`
|
||||||
|
SuperDealer bool `json:"superDealer"`
|
||||||
|
SupportedCountries string `json:"supportedCountries"`
|
||||||
|
ContactInfo string `json:"contactInfo"`
|
||||||
|
H5Display bool `json:"h5Display"`
|
||||||
|
SellerQuantity int `json:"sellerQuantity"`
|
||||||
|
RealSellerQuantity int64 `json:"realSellerQuantity"`
|
||||||
|
BecomeDays int `json:"becomeDays"`
|
||||||
|
TransactionCount int64 `json:"transactionCount"`
|
||||||
|
IsFollow bool `json:"isFollow"`
|
||||||
|
UserBaseInfo FreightSellerProfile `json:"userBaseInfo"`
|
||||||
|
NationalFlag []string `json:"nationalFlag"`
|
||||||
|
OwnNationalFlag string `json:"ownNationalFlag"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FreightSellerProfile struct {
|
||||||
|
ID Int64Value `json:"id"`
|
||||||
|
Account string `json:"account"`
|
||||||
|
UserAvatar string `json:"userAvatar"`
|
||||||
|
UserNickname string `json:"userNickname"`
|
||||||
|
CountryCode string `json:"countryCode"`
|
||||||
|
CountryName string `json:"countryName"`
|
||||||
|
RegionCode string `json:"regionCode"`
|
||||||
|
OriginSys string `json:"originSys"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type freightSellerPageResponse struct {
|
||||||
|
Status *bool `json:"status"`
|
||||||
|
ErrorMsg string `json:"errorMsg"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Body *FreightSellerPage `json:"body"`
|
||||||
|
Data *FreightSellerPage `json:"data"`
|
||||||
|
FreightSellerPage
|
||||||
|
}
|
||||||
|
|
||||||
type resultResponse[T any] struct {
|
type resultResponse[T any] struct {
|
||||||
Code int `json:"code"`
|
Code int `json:"code"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
@ -577,6 +630,42 @@ func (c *Client) GetThisMonthTotalPersonalRecharge(ctx context.Context, userID i
|
|||||||
return resp.Body, nil
|
return resp.Body, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) PageFreightSellers(ctx context.Context, authorization string, sysOrigin string, cursor int, limit int) (FreightSellerPage, error) {
|
||||||
|
values := url.Values{}
|
||||||
|
values.Set("sysOrigin", strings.TrimSpace(sysOrigin))
|
||||||
|
values.Set("dealer", "true")
|
||||||
|
values.Set("cursor", strconv.Itoa(cursor))
|
||||||
|
values.Set("limit", strconv.Itoa(limit))
|
||||||
|
|
||||||
|
endpoint := strings.TrimRight(c.cfg.Java.OtherBaseURL, "/") + "/user/freight-balance/page?" + values.Encode()
|
||||||
|
headers := javaAppHeadersFromAuthorization(authorization)
|
||||||
|
if strings.TrimSpace(authorization) != "" {
|
||||||
|
headers.Set("Authorization", strings.TrimSpace(authorization))
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp freightSellerPageResponse
|
||||||
|
if err := c.getJSON(ctx, endpoint, headers, &resp); err != nil {
|
||||||
|
return FreightSellerPage{}, err
|
||||||
|
}
|
||||||
|
if resp.Status != nil && !*resp.Status {
|
||||||
|
message := strings.TrimSpace(resp.ErrorMsg)
|
||||||
|
if message == "" {
|
||||||
|
message = strings.TrimSpace(resp.Message)
|
||||||
|
}
|
||||||
|
if message == "" {
|
||||||
|
message = "freight seller page failed"
|
||||||
|
}
|
||||||
|
return FreightSellerPage{}, errors.New(message)
|
||||||
|
}
|
||||||
|
if resp.Body != nil {
|
||||||
|
return *resp.Body, nil
|
||||||
|
}
|
||||||
|
if resp.Data != nil {
|
||||||
|
return *resp.Data, nil
|
||||||
|
}
|
||||||
|
return resp.FreightSellerPage, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) GetUserProfile(ctx context.Context, userID int64) (UserProfile, error) {
|
func (c *Client) GetUserProfile(ctx context.Context, userID int64) (UserProfile, error) {
|
||||||
endpoint := c.cfg.Java.OtherBaseURL + "/user-profile/client/getByUserId?userId=" + url.QueryEscape(strconv.FormatInt(userID, 10))
|
endpoint := c.cfg.Java.OtherBaseURL + "/user-profile/client/getByUserId?userId=" + url.QueryEscape(strconv.FormatInt(userID, 10))
|
||||||
var resp resultResponse[UserProfile]
|
var resp resultResponse[UserProfile]
|
||||||
|
|||||||
17
internal/model/recharge_agency_models.go
Normal file
17
internal/model/recharge_agency_models.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// RechargeAgencySellerInfo stores H5-only profile fields for recharge agency sellers.
|
||||||
|
type RechargeAgencySellerInfo struct {
|
||||||
|
ID int64 `gorm:"column:id;primaryKey;autoIncrement"`
|
||||||
|
SysOrigin string `gorm:"column:sys_origin;size:32;uniqueIndex:uk_recharge_agency_seller_info_user,priority:1"`
|
||||||
|
UserID int64 `gorm:"column:user_id;uniqueIndex:uk_recharge_agency_seller_info_user,priority:2"`
|
||||||
|
Whatsapp string `gorm:"column:whatsapp;size:64"`
|
||||||
|
CreateTime time.Time `gorm:"column:create_time"`
|
||||||
|
UpdateTime time.Time `gorm:"column:update_time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (RechargeAgencySellerInfo) TableName() string {
|
||||||
|
return "recharge_agency_seller_info"
|
||||||
|
}
|
||||||
59
internal/router/recharge_agency_routes.go
Normal file
59
internal/router/recharge_agency_routes.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"chatapp3-golang/internal/service/rechargeagency"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// registerRechargeAgencyRoutes 注册充值代理 H5 币商名单路由。
|
||||||
|
func registerRechargeAgencyRoutes(engine *gin.Engine, javaClient authGateway, service *rechargeagency.Service) {
|
||||||
|
if service == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
handler := func(c *gin.Context) {
|
||||||
|
cursor, _ := strconv.Atoi(strings.TrimSpace(c.DefaultQuery("cursor", "1")))
|
||||||
|
limit, _ := strconv.Atoi(strings.TrimSpace(c.DefaultQuery("limit", "20")))
|
||||||
|
|
||||||
|
resp, err := service.PageSellers(c.Request.Context(), mustAuthUser(c), cursor, limit)
|
||||||
|
if err != nil {
|
||||||
|
writeError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeOK(c, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
listGroup := engine.Group("/app/h5/recharge-agency-list")
|
||||||
|
listGroup.Use(authMiddleware(javaClient))
|
||||||
|
listGroup.GET("/sellers", handler)
|
||||||
|
|
||||||
|
agencyGroup := engine.Group("/app/h5/recharge-agency")
|
||||||
|
agencyGroup.Use(authMiddleware(javaClient))
|
||||||
|
agencyGroup.GET("/sellers", handler)
|
||||||
|
agencyGroup.GET("/profile", func(c *gin.Context) {
|
||||||
|
resp, err := service.GetProfile(c.Request.Context(), mustAuthUser(c))
|
||||||
|
if err != nil {
|
||||||
|
writeError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeOK(c, resp)
|
||||||
|
})
|
||||||
|
agencyGroup.POST("/profile/whatsapp", func(c *gin.Context) {
|
||||||
|
var req rechargeagency.UpdateWhatsappRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
writeError(c, rechargeagency.NewAppError(http.StatusBadRequest, "bad_request", err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := service.UpdateWhatsapp(c.Request.Context(), mustAuthUser(c), req)
|
||||||
|
if err != nil {
|
||||||
|
writeError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeOK(c, resp)
|
||||||
|
})
|
||||||
|
}
|
||||||
161
internal/router/recharge_agency_routes_test.go
Normal file
161
internal/router/recharge_agency_routes_test.go
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"chatapp3-golang/internal/config"
|
||||||
|
"chatapp3-golang/internal/integration"
|
||||||
|
"chatapp3-golang/internal/model"
|
||||||
|
"chatapp3-golang/internal/service/rechargeagency"
|
||||||
|
|
||||||
|
"gorm.io/driver/sqlite"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type rechargeAgencyRouteAuth struct{}
|
||||||
|
|
||||||
|
func (rechargeAgencyRouteAuth) AuthenticateToken(context.Context, string) (integration.UserCredential, error) {
|
||||||
|
return integration.UserCredential{
|
||||||
|
UserID: integration.Int64Value(7),
|
||||||
|
SysOrigin: "LIKEI",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rechargeAgencyRouteAuth) AuthenticateConsoleToken(context.Context, string) (integration.ConsoleAccount, error) {
|
||||||
|
return integration.ConsoleAccount{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type rechargeAgencyRouteJava struct {
|
||||||
|
authorization string
|
||||||
|
sysOrigin string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *rechargeAgencyRouteJava) PageFreightSellers(_ context.Context, authorization string, sysOrigin string, cursor int, limit int) (integration.FreightSellerPage, error) {
|
||||||
|
f.authorization = authorization
|
||||||
|
f.sysOrigin = sysOrigin
|
||||||
|
return integration.FreightSellerPage{
|
||||||
|
Total: 1,
|
||||||
|
Current: cursor,
|
||||||
|
Size: limit,
|
||||||
|
Records: []integration.FreightSellerRecord{
|
||||||
|
{
|
||||||
|
ID: integration.Int64Value(1),
|
||||||
|
SysOrigin: sysOrigin,
|
||||||
|
UserID: integration.Int64Value(99),
|
||||||
|
Balance: integration.DecimalString("10"),
|
||||||
|
UserBaseInfo: integration.FreightSellerProfile{
|
||||||
|
Account: "u99",
|
||||||
|
UserNickname: "Nina",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRechargeAgencySellersRoute(t *testing.T) {
|
||||||
|
java := &rechargeAgencyRouteJava{}
|
||||||
|
db, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=shared"), &gorm.Config{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("open sqlite: %v", err)
|
||||||
|
}
|
||||||
|
if err := db.AutoMigrate(&model.RechargeAgencySellerInfo{}); err != nil {
|
||||||
|
t.Fatalf("auto migrate: %v", err)
|
||||||
|
}
|
||||||
|
if err := db.Create(&model.RechargeAgencySellerInfo{
|
||||||
|
SysOrigin: "LIKEI",
|
||||||
|
UserID: 99,
|
||||||
|
Whatsapp: "+8613800138000",
|
||||||
|
}).Error; err != nil {
|
||||||
|
t.Fatalf("seed whatsapp: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
service := rechargeagency.NewService(config.Config{}, db, java)
|
||||||
|
engine := NewRouter(config.Config{}, nil, rechargeAgencyRouteAuth{}, Services{
|
||||||
|
RechargeAgency: service,
|
||||||
|
})
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/app/h5/recharge-agency-list/sellers?cursor=2&limit=1", nil)
|
||||||
|
req.Header.Set("Authorization", "Bearer token")
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
|
||||||
|
engine.ServeHTTP(rec, req)
|
||||||
|
|
||||||
|
if rec.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status = %d body = %s", rec.Code, rec.Body.String())
|
||||||
|
}
|
||||||
|
if java.authorization != "Bearer token" || java.sysOrigin != "LIKEI" {
|
||||||
|
t.Fatalf("gateway authorization/sysOrigin = %q/%q", java.authorization, java.sysOrigin)
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload struct {
|
||||||
|
Body struct {
|
||||||
|
Total int64 `json:"total"`
|
||||||
|
Records []struct {
|
||||||
|
UserID string `json:"userId"`
|
||||||
|
UserNickname string `json:"userNickname"`
|
||||||
|
Whatsapp string `json:"whatsapp"`
|
||||||
|
} `json:"records"`
|
||||||
|
} `json:"body"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil {
|
||||||
|
t.Fatalf("decode response: %v", err)
|
||||||
|
}
|
||||||
|
if payload.Body.Total != 1 || len(payload.Body.Records) != 1 {
|
||||||
|
t.Fatalf("payload body mismatch: %+v", payload.Body)
|
||||||
|
}
|
||||||
|
if payload.Body.Records[0].UserID != "99" || payload.Body.Records[0].UserNickname != "Nina" {
|
||||||
|
t.Fatalf("record mismatch: %+v", payload.Body.Records[0])
|
||||||
|
}
|
||||||
|
if payload.Body.Records[0].Whatsapp != "+8613800138000" {
|
||||||
|
t.Fatalf("whatsapp mismatch: %+v", payload.Body.Records[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRechargeAgencyProfileRoutes(t *testing.T) {
|
||||||
|
db, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=shared"), &gorm.Config{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("open sqlite: %v", err)
|
||||||
|
}
|
||||||
|
if err := db.AutoMigrate(&model.RechargeAgencySellerInfo{}); err != nil {
|
||||||
|
t.Fatalf("auto migrate: %v", err)
|
||||||
|
}
|
||||||
|
service := rechargeagency.NewService(config.Config{}, db, nil)
|
||||||
|
engine := NewRouter(config.Config{}, nil, rechargeAgencyRouteAuth{}, Services{
|
||||||
|
RechargeAgency: service,
|
||||||
|
})
|
||||||
|
|
||||||
|
postReq := httptest.NewRequest(http.MethodPost, "/app/h5/recharge-agency/profile/whatsapp", bytes.NewBufferString(`{"whatsapp":"+8613800138000"}`))
|
||||||
|
postReq.Header.Set("Authorization", "Bearer token")
|
||||||
|
postReq.Header.Set("Content-Type", "application/json")
|
||||||
|
postRec := httptest.NewRecorder()
|
||||||
|
engine.ServeHTTP(postRec, postReq)
|
||||||
|
if postRec.Code != http.StatusOK {
|
||||||
|
t.Fatalf("post status = %d body = %s", postRec.Code, postRec.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
getReq := httptest.NewRequest(http.MethodGet, "/app/h5/recharge-agency/profile", nil)
|
||||||
|
getReq.Header.Set("Authorization", "Bearer token")
|
||||||
|
getRec := httptest.NewRecorder()
|
||||||
|
engine.ServeHTTP(getRec, getReq)
|
||||||
|
if getRec.Code != http.StatusOK {
|
||||||
|
t.Fatalf("get status = %d body = %s", getRec.Code, getRec.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload struct {
|
||||||
|
Body struct {
|
||||||
|
UserID string `json:"userId"`
|
||||||
|
Whatsapp string `json:"whatsapp"`
|
||||||
|
} `json:"body"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(getRec.Body.Bytes(), &payload); err != nil {
|
||||||
|
t.Fatalf("decode get response: %v", err)
|
||||||
|
}
|
||||||
|
if payload.Body.UserID != "7" || payload.Body.Whatsapp != "+8613800138000" {
|
||||||
|
t.Fatalf("profile body mismatch: %+v", payload.Body)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -13,6 +13,7 @@ import (
|
|||||||
"chatapp3-golang/internal/service/invite"
|
"chatapp3-golang/internal/service/invite"
|
||||||
"chatapp3-golang/internal/service/lingxian"
|
"chatapp3-golang/internal/service/lingxian"
|
||||||
"chatapp3-golang/internal/service/luckygift"
|
"chatapp3-golang/internal/service/luckygift"
|
||||||
|
"chatapp3-golang/internal/service/rechargeagency"
|
||||||
"chatapp3-golang/internal/service/rechargereward"
|
"chatapp3-golang/internal/service/rechargereward"
|
||||||
"chatapp3-golang/internal/service/registerreward"
|
"chatapp3-golang/internal/service/registerreward"
|
||||||
"chatapp3-golang/internal/service/roomturnoverreward"
|
"chatapp3-golang/internal/service/roomturnoverreward"
|
||||||
@ -32,6 +33,7 @@ type Services struct {
|
|||||||
GameProviders *gameprovider.Registry
|
GameProviders *gameprovider.Registry
|
||||||
LuckyGift *luckygift.LuckyGiftService
|
LuckyGift *luckygift.LuckyGiftService
|
||||||
HostCenter *hostcenter.Service
|
HostCenter *hostcenter.Service
|
||||||
|
RechargeAgency *rechargeagency.Service
|
||||||
RechargeReward *rechargereward.Service
|
RechargeReward *rechargereward.Service
|
||||||
RegisterReward *registerreward.RegisterRewardService
|
RegisterReward *registerreward.RegisterRewardService
|
||||||
RoomTurnoverReward *roomturnoverreward.Service
|
RoomTurnoverReward *roomturnoverreward.Service
|
||||||
@ -52,6 +54,7 @@ func NewRouter(
|
|||||||
|
|
||||||
registerHealthRoutes(engine, cfg, repository)
|
registerHealthRoutes(engine, cfg, repository)
|
||||||
registerInviteRoutes(engine, javaClient, services.Invite)
|
registerInviteRoutes(engine, javaClient, services.Invite)
|
||||||
|
registerRechargeAgencyRoutes(engine, javaClient, services.RechargeAgency)
|
||||||
registerRechargeRewardRoutes(engine, javaClient, services.RechargeReward)
|
registerRechargeRewardRoutes(engine, javaClient, services.RechargeReward)
|
||||||
registerRegisterRewardRoutes(engine, javaClient, services.RegisterReward)
|
registerRegisterRewardRoutes(engine, javaClient, services.RegisterReward)
|
||||||
registerSignInRewardRoutes(engine, javaClient, services.SignInReward)
|
registerSignInRewardRoutes(engine, javaClient, services.SignInReward)
|
||||||
|
|||||||
118
internal/service/rechargeagency/list.go
Normal file
118
internal/service/rechargeagency/list.go
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
package rechargeagency
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"chatapp3-golang/internal/integration"
|
||||||
|
"chatapp3-golang/internal/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PageSellers 返回当前用户可见的 H5 币商名单。
|
||||||
|
func (s *Service) PageSellers(ctx context.Context, user AuthUser, cursor int, limit int) (*SellerListResponse, error) {
|
||||||
|
if s.java == nil {
|
||||||
|
return nil, NewAppError(http.StatusServiceUnavailable, "java_gateway_unavailable", "java gateway is unavailable")
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(user.Authorization) == "" {
|
||||||
|
return nil, NewAppError(http.StatusUnauthorized, "missing_authorization", "authorization is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
sysOrigin := normalizeSysOrigin(user.SysOrigin)
|
||||||
|
if sysOrigin == "" {
|
||||||
|
return nil, NewAppError(http.StatusUnauthorized, "invalid_sys_origin", "authenticated sysOrigin is required")
|
||||||
|
}
|
||||||
|
cursor, limit = normalizePage(cursor, limit)
|
||||||
|
|
||||||
|
page, err := s.java.PageFreightSellers(ctx, user.Authorization, sysOrigin, cursor, limit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, NewAppError(http.StatusBadGateway, "freight_seller_list_failed", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
records := make([]SellerView, 0, len(page.Records))
|
||||||
|
for _, record := range page.Records {
|
||||||
|
records = append(records, buildSellerView(record))
|
||||||
|
}
|
||||||
|
if s.db != nil {
|
||||||
|
whatsapps, err := s.loadSellerWhatsapps(ctx, sysOrigin, collectSellerUserIDs(records))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for i := range records {
|
||||||
|
if whatsapp := strings.TrimSpace(whatsapps[records[i].UserID]); whatsapp != "" {
|
||||||
|
records[i].Whatsapp = whatsapp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &SellerListResponse{
|
||||||
|
SysOrigin: sysOrigin,
|
||||||
|
Cursor: cursor,
|
||||||
|
Limit: limit,
|
||||||
|
Total: page.Total,
|
||||||
|
Records: records,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildSellerView(record integration.FreightSellerRecord) SellerView {
|
||||||
|
profile := record.UserBaseInfo
|
||||||
|
return SellerView{
|
||||||
|
ID: int64(record.ID),
|
||||||
|
SysOrigin: strings.TrimSpace(record.SysOrigin),
|
||||||
|
UserID: int64(record.UserID),
|
||||||
|
Account: firstNonBlank(profile.Account, formatInt64(int64(record.UserID))),
|
||||||
|
UserAvatar: strings.TrimSpace(profile.UserAvatar),
|
||||||
|
UserNickname: firstNonBlank(profile.UserNickname, profile.Account, formatInt64(int64(record.UserID))),
|
||||||
|
CountryCode: strings.TrimSpace(profile.CountryCode),
|
||||||
|
CountryName: strings.TrimSpace(profile.CountryName),
|
||||||
|
RegionCode: strings.TrimSpace(profile.RegionCode),
|
||||||
|
Balance: normalizeDecimalString(string(record.Balance)),
|
||||||
|
ContactInfo: strings.TrimSpace(record.ContactInfo),
|
||||||
|
SupportedCountries: strings.TrimSpace(record.SupportedCountries),
|
||||||
|
NationalFlag: normalizeFlags(record.NationalFlag),
|
||||||
|
OwnNationalFlag: strings.TrimSpace(record.OwnNationalFlag),
|
||||||
|
BecomeDays: record.BecomeDays,
|
||||||
|
TransactionCount: record.TransactionCount,
|
||||||
|
IsFollow: record.IsFollow,
|
||||||
|
SuperDealer: record.SuperDealer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectSellerUserIDs(records []SellerView) []int64 {
|
||||||
|
seen := make(map[int64]struct{}, len(records))
|
||||||
|
userIDs := make([]int64, 0, len(records))
|
||||||
|
for _, record := range records {
|
||||||
|
if record.UserID <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := seen[record.UserID]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[record.UserID] = struct{}{}
|
||||||
|
userIDs = append(userIDs, record.UserID)
|
||||||
|
}
|
||||||
|
return userIDs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) loadSellerWhatsapps(ctx context.Context, sysOrigin string, userIDs []int64) (map[int64]string, error) {
|
||||||
|
whatsapps := make(map[int64]string, len(userIDs))
|
||||||
|
if len(userIDs) == 0 {
|
||||||
|
return whatsapps, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var rows []model.RechargeAgencySellerInfo
|
||||||
|
err := s.db.WithContext(ctx).
|
||||||
|
Select("user_id", "whatsapp").
|
||||||
|
Where("sys_origin = ? AND user_id IN ?", sysOrigin, userIDs).
|
||||||
|
Find(&rows).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, NewAppError(http.StatusInternalServerError, "seller_whatsapp_query_failed", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, row := range rows {
|
||||||
|
if whatsapp := strings.TrimSpace(row.Whatsapp); whatsapp != "" {
|
||||||
|
whatsapps[row.UserID] = whatsapp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return whatsapps, nil
|
||||||
|
}
|
||||||
119
internal/service/rechargeagency/list_test.go
Normal file
119
internal/service/rechargeagency/list_test.go
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
package rechargeagency
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"chatapp3-golang/internal/config"
|
||||||
|
"chatapp3-golang/internal/integration"
|
||||||
|
"chatapp3-golang/internal/model"
|
||||||
|
|
||||||
|
"gorm.io/driver/sqlite"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fakeJavaGateway struct {
|
||||||
|
gotAuthorization string
|
||||||
|
gotSysOrigin string
|
||||||
|
gotCursor int
|
||||||
|
gotLimit int
|
||||||
|
page integration.FreightSellerPage
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeJavaGateway) PageFreightSellers(_ context.Context, authorization string, sysOrigin string, cursor int, limit int) (integration.FreightSellerPage, error) {
|
||||||
|
f.gotAuthorization = authorization
|
||||||
|
f.gotSysOrigin = sysOrigin
|
||||||
|
f.gotCursor = cursor
|
||||||
|
f.gotLimit = limit
|
||||||
|
return f.page, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPageSellersUsesAuthenticatedUserAndMapsRecords(t *testing.T) {
|
||||||
|
db, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=shared"), &gorm.Config{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("open sqlite: %v", err)
|
||||||
|
}
|
||||||
|
if err := db.AutoMigrate(&model.RechargeAgencySellerInfo{}); err != nil {
|
||||||
|
t.Fatalf("auto migrate: %v", err)
|
||||||
|
}
|
||||||
|
if err := db.Create(&model.RechargeAgencySellerInfo{
|
||||||
|
SysOrigin: "LIKEI",
|
||||||
|
UserID: 99,
|
||||||
|
Whatsapp: "+8613800138000",
|
||||||
|
CreateTime: time.Now(),
|
||||||
|
UpdateTime: time.Now(),
|
||||||
|
}).Error; err != nil {
|
||||||
|
t.Fatalf("seed whatsapp: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
java := &fakeJavaGateway{
|
||||||
|
page: integration.FreightSellerPage{
|
||||||
|
Total: 1,
|
||||||
|
Current: 1,
|
||||||
|
Size: 20,
|
||||||
|
Records: []integration.FreightSellerRecord{
|
||||||
|
{
|
||||||
|
ID: integration.Int64Value(11),
|
||||||
|
SysOrigin: "LIKEI",
|
||||||
|
UserID: integration.Int64Value(99),
|
||||||
|
Balance: integration.DecimalString("123.45"),
|
||||||
|
SuperDealer: true,
|
||||||
|
NationalFlag: []string{"", "https://flag.example/ae.png"},
|
||||||
|
OwnNationalFlag: "https://flag.example/own.png",
|
||||||
|
BecomeDays: 7,
|
||||||
|
TransactionCount: 12,
|
||||||
|
UserBaseInfo: integration.FreightSellerProfile{
|
||||||
|
Account: "u99",
|
||||||
|
UserNickname: "Nina",
|
||||||
|
UserAvatar: "https://avatar.example/u99.png",
|
||||||
|
CountryCode: "AE",
|
||||||
|
CountryName: "United Arab Emirates",
|
||||||
|
RegionCode: "MENA",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
service := NewService(config.Config{}, db, java)
|
||||||
|
|
||||||
|
resp, err := service.PageSellers(context.Background(), AuthUser{
|
||||||
|
SysOrigin: "likei",
|
||||||
|
Authorization: "Bearer token",
|
||||||
|
}, 0, 200)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("PageSellers() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if java.gotAuthorization != "Bearer token" || java.gotSysOrigin != "LIKEI" {
|
||||||
|
t.Fatalf("gateway auth/sysOrigin = %q/%q", java.gotAuthorization, java.gotSysOrigin)
|
||||||
|
}
|
||||||
|
if java.gotCursor != 1 || java.gotLimit != 100 {
|
||||||
|
t.Fatalf("gateway cursor/limit = %d/%d", java.gotCursor, java.gotLimit)
|
||||||
|
}
|
||||||
|
if resp.Total != 1 || len(resp.Records) != 1 {
|
||||||
|
t.Fatalf("response total/records = %d/%d", resp.Total, len(resp.Records))
|
||||||
|
}
|
||||||
|
record := resp.Records[0]
|
||||||
|
if record.ID != 11 || record.UserID != 99 || record.Account != "u99" || record.UserNickname != "Nina" {
|
||||||
|
t.Fatalf("mapped record identity mismatch: %+v", record)
|
||||||
|
}
|
||||||
|
if record.Balance != "123.45" || !record.SuperDealer || record.TransactionCount != 12 {
|
||||||
|
t.Fatalf("mapped record stats mismatch: %+v", record)
|
||||||
|
}
|
||||||
|
if record.Whatsapp != "+8613800138000" {
|
||||||
|
t.Fatalf("mapped whatsapp = %q", record.Whatsapp)
|
||||||
|
}
|
||||||
|
if len(record.NationalFlag) != 1 || record.NationalFlag[0] != "https://flag.example/ae.png" {
|
||||||
|
t.Fatalf("mapped flags mismatch: %+v", record.NationalFlag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPageSellersRequiresAuthorization(t *testing.T) {
|
||||||
|
service := NewService(config.Config{}, nil, &fakeJavaGateway{})
|
||||||
|
|
||||||
|
_, err := service.PageSellers(context.Background(), AuthUser{SysOrigin: "LIKEI"}, 1, 20)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("PageSellers() error is nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
60
internal/service/rechargeagency/normalize.go
Normal file
60
internal/service/rechargeagency/normalize.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package rechargeagency
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func normalizeSysOrigin(value string) string {
|
||||||
|
return strings.ToUpper(strings.TrimSpace(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizePage(cursor int, limit int) (int, int) {
|
||||||
|
if cursor <= 0 {
|
||||||
|
cursor = 1
|
||||||
|
}
|
||||||
|
if limit <= 0 {
|
||||||
|
limit = 20
|
||||||
|
}
|
||||||
|
if limit > 100 {
|
||||||
|
limit = 100
|
||||||
|
}
|
||||||
|
return cursor, limit
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeDecimalString(value string) string {
|
||||||
|
normalized := strings.TrimSpace(value)
|
||||||
|
if normalized == "" {
|
||||||
|
return "0"
|
||||||
|
}
|
||||||
|
return normalized
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeFlags(flags []string) []string {
|
||||||
|
if len(flags) == 0 {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
result := make([]string, 0, len(flags))
|
||||||
|
for _, flag := range flags {
|
||||||
|
if normalized := strings.TrimSpace(flag); normalized != "" {
|
||||||
|
result = append(result, normalized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func firstNonBlank(values ...string) string {
|
||||||
|
for _, value := range values {
|
||||||
|
if normalized := strings.TrimSpace(value); normalized != "" {
|
||||||
|
return normalized
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatInt64(value int64) string {
|
||||||
|
if value <= 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return strconv.FormatInt(value, 10)
|
||||||
|
}
|
||||||
101
internal/service/rechargeagency/profile.go
Normal file
101
internal/service/rechargeagency/profile.go
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
package rechargeagency
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"chatapp3-golang/internal/model"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetProfile returns the current recharge agency seller extension profile.
|
||||||
|
func (s *Service) GetProfile(ctx context.Context, user AuthUser) (*SellerProfileResponse, error) {
|
||||||
|
sysOrigin, userID, err := normalizeSellerProfileUser(user)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if s.db == nil {
|
||||||
|
return nil, NewAppError(http.StatusServiceUnavailable, "database_unavailable", "database is unavailable")
|
||||||
|
}
|
||||||
|
|
||||||
|
var row model.RechargeAgencySellerInfo
|
||||||
|
err = s.db.WithContext(ctx).
|
||||||
|
Where("sys_origin = ? AND user_id = ?", sysOrigin, userID).
|
||||||
|
First(&row).Error
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return &SellerProfileResponse{
|
||||||
|
SysOrigin: sysOrigin,
|
||||||
|
UserID: userID,
|
||||||
|
Whatsapp: "",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, NewAppError(http.StatusInternalServerError, "seller_profile_query_failed", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return &SellerProfileResponse{
|
||||||
|
SysOrigin: sysOrigin,
|
||||||
|
UserID: userID,
|
||||||
|
Whatsapp: strings.TrimSpace(row.Whatsapp),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateWhatsapp stores the current recharge agency seller WhatsApp contact.
|
||||||
|
func (s *Service) UpdateWhatsapp(ctx context.Context, user AuthUser, req UpdateWhatsappRequest) (*SellerProfileResponse, error) {
|
||||||
|
sysOrigin, userID, err := normalizeSellerProfileUser(user)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if s.db == nil {
|
||||||
|
return nil, NewAppError(http.StatusServiceUnavailable, "database_unavailable", "database is unavailable")
|
||||||
|
}
|
||||||
|
|
||||||
|
whatsapp := strings.TrimSpace(req.Whatsapp)
|
||||||
|
if whatsapp == "" {
|
||||||
|
return nil, NewAppError(http.StatusBadRequest, "whatsapp_required", "whatsapp is required")
|
||||||
|
}
|
||||||
|
if len([]rune(whatsapp)) > 64 {
|
||||||
|
return nil, NewAppError(http.StatusBadRequest, "whatsapp_too_long", "whatsapp cannot exceed 64 characters")
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
row := model.RechargeAgencySellerInfo{
|
||||||
|
SysOrigin: sysOrigin,
|
||||||
|
UserID: userID,
|
||||||
|
Whatsapp: whatsapp,
|
||||||
|
CreateTime: now,
|
||||||
|
UpdateTime: now,
|
||||||
|
}
|
||||||
|
err = s.db.WithContext(ctx).Clauses(clause.OnConflict{
|
||||||
|
Columns: []clause.Column{{Name: "sys_origin"}, {Name: "user_id"}},
|
||||||
|
DoUpdates: clause.Assignments(map[string]any{
|
||||||
|
"whatsapp": whatsapp,
|
||||||
|
"update_time": now,
|
||||||
|
}),
|
||||||
|
}).Create(&row).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, NewAppError(http.StatusInternalServerError, "seller_profile_save_failed", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return &SellerProfileResponse{
|
||||||
|
SysOrigin: sysOrigin,
|
||||||
|
UserID: userID,
|
||||||
|
Whatsapp: whatsapp,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeSellerProfileUser(user AuthUser) (string, int64, error) {
|
||||||
|
sysOrigin := normalizeSysOrigin(user.SysOrigin)
|
||||||
|
if sysOrigin == "" {
|
||||||
|
return "", 0, NewAppError(http.StatusUnauthorized, "invalid_sys_origin", "authenticated sysOrigin is required")
|
||||||
|
}
|
||||||
|
if user.UserID <= 0 {
|
||||||
|
return "", 0, NewAppError(http.StatusUnauthorized, "invalid_user", "authenticated user is required")
|
||||||
|
}
|
||||||
|
return sysOrigin, user.UserID, nil
|
||||||
|
}
|
||||||
80
internal/service/rechargeagency/profile_test.go
Normal file
80
internal/service/rechargeagency/profile_test.go
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
package rechargeagency
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"chatapp3-golang/internal/config"
|
||||||
|
"chatapp3-golang/internal/model"
|
||||||
|
|
||||||
|
"gorm.io/driver/sqlite"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newRechargeAgencyProfileTestService(t *testing.T) *Service {
|
||||||
|
t.Helper()
|
||||||
|
db, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=shared"), &gorm.Config{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("open sqlite: %v", err)
|
||||||
|
}
|
||||||
|
if err := db.AutoMigrate(&model.RechargeAgencySellerInfo{}); err != nil {
|
||||||
|
t.Fatalf("auto migrate: %v", err)
|
||||||
|
}
|
||||||
|
return NewService(config.Config{}, db, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSellerProfileWhatsappUpsert(t *testing.T) {
|
||||||
|
service := newRechargeAgencyProfileTestService(t)
|
||||||
|
user := AuthUser{
|
||||||
|
UserID: 2042274349343506434,
|
||||||
|
SysOrigin: "likei",
|
||||||
|
}
|
||||||
|
|
||||||
|
empty, err := service.GetProfile(context.Background(), user)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetProfile() error = %v", err)
|
||||||
|
}
|
||||||
|
if empty.SysOrigin != "LIKEI" || empty.UserID != user.UserID || empty.Whatsapp != "" {
|
||||||
|
t.Fatalf("empty profile = %+v", empty)
|
||||||
|
}
|
||||||
|
|
||||||
|
saved, err := service.UpdateWhatsapp(context.Background(), user, UpdateWhatsappRequest{Whatsapp: " +8613800138000 "})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("UpdateWhatsapp() error = %v", err)
|
||||||
|
}
|
||||||
|
if saved.Whatsapp != "+8613800138000" {
|
||||||
|
t.Fatalf("saved whatsapp = %q", saved.Whatsapp)
|
||||||
|
}
|
||||||
|
|
||||||
|
updated, err := service.UpdateWhatsapp(context.Background(), user, UpdateWhatsappRequest{Whatsapp: "+8613900139000"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("second UpdateWhatsapp() error = %v", err)
|
||||||
|
}
|
||||||
|
if updated.Whatsapp != "+8613900139000" {
|
||||||
|
t.Fatalf("updated whatsapp = %q", updated.Whatsapp)
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := service.GetProfile(context.Background(), user)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetProfile() after save error = %v", err)
|
||||||
|
}
|
||||||
|
if got.Whatsapp != "+8613900139000" {
|
||||||
|
t.Fatalf("got whatsapp = %q", got.Whatsapp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateWhatsappValidatesInput(t *testing.T) {
|
||||||
|
service := newRechargeAgencyProfileTestService(t)
|
||||||
|
user := AuthUser{
|
||||||
|
UserID: 7,
|
||||||
|
SysOrigin: "LIKEI",
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := service.UpdateWhatsapp(context.Background(), user, UpdateWhatsappRequest{}); err == nil {
|
||||||
|
t.Fatal("UpdateWhatsapp() empty error is nil")
|
||||||
|
}
|
||||||
|
if _, err := service.UpdateWhatsapp(context.Background(), user, UpdateWhatsappRequest{Whatsapp: strings.Repeat("1", 65)}); err == nil {
|
||||||
|
t.Fatal("UpdateWhatsapp() long error is nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
75
internal/service/rechargeagency/types.go
Normal file
75
internal/service/rechargeagency/types.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package rechargeagency
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"chatapp3-golang/internal/common"
|
||||||
|
"chatapp3-golang/internal/config"
|
||||||
|
"chatapp3-golang/internal/integration"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AppError = common.AppError
|
||||||
|
type AuthUser = common.AuthUser
|
||||||
|
|
||||||
|
var NewAppError = common.NewAppError
|
||||||
|
|
||||||
|
type javaGateway interface {
|
||||||
|
PageFreightSellers(ctx context.Context, authorization string, sysOrigin string, cursor int, limit int) (integration.FreightSellerPage, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type rechargeAgencyDB interface {
|
||||||
|
WithContext(ctx context.Context) *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// Service 负责充值代理 H5 币商名单数据。
|
||||||
|
type Service struct {
|
||||||
|
cfg config.Config
|
||||||
|
db rechargeAgencyDB
|
||||||
|
java javaGateway
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewService(cfg config.Config, db rechargeAgencyDB, java javaGateway) *Service {
|
||||||
|
return &Service{cfg: cfg, db: db, java: java}
|
||||||
|
}
|
||||||
|
|
||||||
|
type SellerListResponse struct {
|
||||||
|
SysOrigin string `json:"sysOrigin"`
|
||||||
|
Cursor int `json:"cursor"`
|
||||||
|
Limit int `json:"limit"`
|
||||||
|
Total int64 `json:"total"`
|
||||||
|
Records []SellerView `json:"records"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SellerView struct {
|
||||||
|
ID int64 `json:"id,string"`
|
||||||
|
SysOrigin string `json:"sysOrigin"`
|
||||||
|
UserID int64 `json:"userId,string"`
|
||||||
|
Account string `json:"account"`
|
||||||
|
UserAvatar string `json:"userAvatar"`
|
||||||
|
UserNickname string `json:"userNickname"`
|
||||||
|
CountryCode string `json:"countryCode"`
|
||||||
|
CountryName string `json:"countryName"`
|
||||||
|
RegionCode string `json:"regionCode"`
|
||||||
|
Balance string `json:"balance"`
|
||||||
|
ContactInfo string `json:"contactInfo,omitempty"`
|
||||||
|
Whatsapp string `json:"whatsapp,omitempty"`
|
||||||
|
SupportedCountries string `json:"supportedCountries,omitempty"`
|
||||||
|
NationalFlag []string `json:"nationalFlag"`
|
||||||
|
OwnNationalFlag string `json:"ownNationalFlag,omitempty"`
|
||||||
|
BecomeDays int `json:"becomeDays"`
|
||||||
|
TransactionCount int64 `json:"transactionCount"`
|
||||||
|
IsFollow bool `json:"isFollow"`
|
||||||
|
SuperDealer bool `json:"superDealer"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SellerProfileResponse struct {
|
||||||
|
SysOrigin string `json:"sysOrigin"`
|
||||||
|
UserID int64 `json:"userId,string"`
|
||||||
|
Whatsapp string `json:"whatsapp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateWhatsappRequest struct {
|
||||||
|
Whatsapp string `json:"whatsapp"`
|
||||||
|
}
|
||||||
10
migrations/021_recharge_agency_seller_info.sql
Normal file
10
migrations/021_recharge_agency_seller_info.sql
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS `recharge_agency_seller_info` (
|
||||||
|
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||||
|
`sys_origin` varchar(32) NOT NULL COMMENT '来源系统',
|
||||||
|
`user_id` bigint NOT NULL COMMENT '币商用户ID',
|
||||||
|
`whatsapp` varchar(64) NOT NULL DEFAULT '' COMMENT 'WhatsApp',
|
||||||
|
`create_time` datetime(3) DEFAULT NULL COMMENT '创建时间',
|
||||||
|
`update_time` datetime(3) DEFAULT NULL COMMENT '更新时间',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_recharge_agency_seller_info_user` (`sys_origin`, `user_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='充值代理币商扩展资料';
|
||||||
Loading…
x
Reference in New Issue
Block a user