forked from root/91fans_server
对接统计
This commit is contained in:
@@ -23,7 +23,7 @@ type PayTypeParam struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// BillPlatformID 账单中心平台标识
|
// BillPlatformID 账单中心平台标识
|
||||||
const BillPlatformID = "逼哩逼哩"
|
const BillPlatformID = "黄果短剧"
|
||||||
|
|
||||||
// billDevType 账单中心设备类型,非android按ios处理
|
// billDevType 账单中心设备类型,非android按ios处理
|
||||||
func billDevType(devType string) string {
|
func billDevType(devType string) string {
|
||||||
|
|||||||
@@ -0,0 +1,379 @@
|
|||||||
|
package statcenterctl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"91porn-server/common"
|
||||||
|
"91porn-server/common/log"
|
||||||
|
"91porn-server/common/stderr"
|
||||||
|
"91porn-server/models/l/visitlogmod"
|
||||||
|
"91porn-server/models/v/rchgordmod"
|
||||||
|
"91porn-server/models/v/txnmod"
|
||||||
|
"91porn-server/models/v/usermod"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 数据中心同步协议(与哔哩 statcenter 第三版协议保持一致)
|
||||||
|
|
||||||
|
// statAppId 数据中心同步使用的应用ID
|
||||||
|
const statAppId int32 = 2
|
||||||
|
|
||||||
|
type StatJob string
|
||||||
|
|
||||||
|
const (
|
||||||
|
StatJobRegister StatJob = "register" // 用户注册
|
||||||
|
StatJobRecharge StatJob = "recharge" // 用户充值
|
||||||
|
StatJobVisit StatJob = "visit" // 用户访问
|
||||||
|
StatJobVip StatJob = "vip" // VIP
|
||||||
|
StatJobRechargeAll StatJob = "rechargeAll" // 充值订单
|
||||||
|
StatJobRegisterLight StatJob = "registerLight" // 注册轻量(H5注册)
|
||||||
|
)
|
||||||
|
|
||||||
|
type StatcenterSyncReq struct {
|
||||||
|
Job string `json:"job" bson:"job"` // 同步
|
||||||
|
UserId uint64 `json:"userId" bson:"userId"` // 用户(注册)
|
||||||
|
PlatformId string `json:"platformId" bson:"platformId"` // 原始平台Id(访问)
|
||||||
|
SuccessTime time.Time `json:"successTime"` // 成功时间(购买会员、提现、充值)
|
||||||
|
MaxSize int64 `json:"maxSize" bson:"maxSize"` // 最大条数
|
||||||
|
}
|
||||||
|
|
||||||
|
type StatcenterSyncResp struct {
|
||||||
|
Code int `json:"code"` // 200正常 其他异常
|
||||||
|
Msg string `json:"msg"` // 错误消息
|
||||||
|
Job string `json:"job" bson:"job"` // 类型
|
||||||
|
VisitList []UserVisitMsg `json:"visitList"` // 日活记录
|
||||||
|
RegisterList []UserRegisterMsg `json:"registerList"` // 注册记录
|
||||||
|
WithdrawList []UserWithdrawMsg `json:"withdrawList"` // 提现记录
|
||||||
|
RechargeList []UserRechargeMsg `json:"rechargeList"` // 充值记录
|
||||||
|
VipList []UserBuyVIPMsg `json:"buyVipList"` // VIP购买记录
|
||||||
|
GameRecharge []UserRechargeMsg `json:"gameRecharge"` // 游戏充值订单
|
||||||
|
GameWithdraw []UserWithdrawMsg `json:"gameWithdraw"` // 游戏提现订单
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserVisitMsg struct {
|
||||||
|
UserId uint64 `json:"userId" bson:"userId"` // 用户ID
|
||||||
|
PlatformId string `json:"platformId" bson:"platformId"` // 原始平台流水Id
|
||||||
|
AppId int32 `json:"appId" bson:"appId"` // appID
|
||||||
|
SysType string `json:"sysType" bson:"sysType"` // 操作系统类型 安卓 IOS
|
||||||
|
DevType string `json:"devType,omitempty" bson:"devType"` // 设备型号
|
||||||
|
IP string `json:"ip,omitempty" bson:"ip"` // IP
|
||||||
|
Version string `json:"version,omitempty" bson:"version"` // APP版本号
|
||||||
|
DevID string `json:"devID,omitempty" bson:"devID"` // 设备ID
|
||||||
|
VisitAt time.Time `json:"visitAt,omitempty" bson:"visitAt"` // 访问时间
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserRegisterMsg struct {
|
||||||
|
UserId uint64 `json:"userId" bson:"userId"` // 用户ID
|
||||||
|
AppId int32 `json:"appId" bson:"appId"` // appID
|
||||||
|
SysType string `json:"sysType" bson:"sysType"` // 操作系统类型 安卓 IOS
|
||||||
|
PlatformId string `json:"platformId" bson:"platformId"` // 原始平台流水Id
|
||||||
|
DevType string `json:"devType" bson:"devType"` // 设备类型
|
||||||
|
Mobile string `json:"mobile" bson:"mobile"` // 手机号
|
||||||
|
IP string `json:"ip,omitempty" bson:"ip"` // IP
|
||||||
|
Name string `json:"name" bson:"name"` // 名称
|
||||||
|
IsDirect bool `json:"isDirect" bson:"isDirect"` // true:是直推用户
|
||||||
|
DistrictCode string `json:"districtCode" bson:"districtCode" ` // 渠道码
|
||||||
|
PromSeqe string `json:"promSeqe" bson:"promSeqe"` // 全名代理
|
||||||
|
PromCode string `json:"promCode" bson:"promCode"` // 推广序列
|
||||||
|
RegisterTime time.Time `json:"registerTime" bson:"registerTime"` // 注册时间时间
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserRechargeMsg struct {
|
||||||
|
UserId uint64 `json:"userId" bson:"userId"` // 用户ID
|
||||||
|
AppId int32 `json:"appId" bson:"appId"` // appID
|
||||||
|
PlatformId string `json:"platformId" bson:"platformId"` // 原始平台流水Id
|
||||||
|
SysType string `json:"sysType" bson:"sysType"` // 操作系统类型 安卓 IOS
|
||||||
|
DevType string `json:"devType" bson:"devType"` // 设备类型
|
||||||
|
ChannelName string `json:"channelName" bson:"channelName"` // 支付渠道名字
|
||||||
|
CID string `json:"cid" bson:"cid"` // 渠道id
|
||||||
|
Type string `bson:"type" json:"type"` // 充值方式
|
||||||
|
OrderId string `json:"orderId" bson:"orderId"` // 流水id
|
||||||
|
Money int64 `json:"money" bson:"money"` // 充值金额 订单金额
|
||||||
|
PayMoney int64 `json:"payMoney" bson:"payMoney"` // 实际到账金额 用户实际支付金额
|
||||||
|
Status int `json:"status" bson:"status"` // 2付款失败 3付款成功(目前只有成功才发送)
|
||||||
|
Rate string `json:"rate"` // 渠道费率
|
||||||
|
SuccessAt time.Time `json:"successAt" bson:"successAt"` // 成功时间
|
||||||
|
CreatedAt time.Time `json:"createdAt" bson:"createdAt"` // 创建时间
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserWithdrawMsg struct {
|
||||||
|
AppId int32 `json:"appId" bson:"appId"` //appID
|
||||||
|
UID uint64 `json:"uid" bson:"uid"` //用户id
|
||||||
|
PlatformId string `json:"platformId" bson:"platformId"` //原始平台流水Id
|
||||||
|
Name string `json:"name" bson:"name"` //用户名
|
||||||
|
OID string `json:"oid" bson:"oid"` //第三方支付平台id
|
||||||
|
Money int64 `json:"money" bson:"money"` //提现金额
|
||||||
|
PayMoney int64 `json:"payMoney" bson:"payMoney"` //实际到账
|
||||||
|
PayType string `json:"payType" bson:"payType"` //提现方式,alipay,bankcard
|
||||||
|
ActName string `json:"actName" bson:"actName"` //交易账户持有人
|
||||||
|
Act string `json:"act" bson:"act"` //交易账户
|
||||||
|
DeviceType string `json:"deviceType" bson:"deviceType"` //设备类型
|
||||||
|
DevID string `json:"devID" bson:"devID"` //设备id
|
||||||
|
Status int `json:"status" bson:"status"` //5提现成功
|
||||||
|
SuccessAt time.Time `json:"successAt" bson:"successAt"` //回调成功时间
|
||||||
|
UpdatedAt time.Time `json:"updatedAt" bson:"updatedAt"` //刷新时间
|
||||||
|
CreatedAt time.Time `json:"createdAt" bson:"createdAt"` //提现申请时间
|
||||||
|
IsDirect bool `json:"isDirect" bson:"isDirect"` //true:是直推用户
|
||||||
|
DistrictCode string `json:"districtCode" bson:"districtCode"` //渠道码
|
||||||
|
PromCode string `json:"promCode" bson:"promCode"` //用户推广序列
|
||||||
|
}
|
||||||
|
|
||||||
|
type BuyVIPType int64
|
||||||
|
|
||||||
|
const (
|
||||||
|
BuyVIPTypeMoney BuyVIPType = 0 + iota // 访问
|
||||||
|
BuyVIPTypeGold // 金币
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserBuyVIPMsg struct {
|
||||||
|
AppId int32 `json:"appId" bson:"appId"` //appID
|
||||||
|
UID uint64 `json:"uid" bson:"uid"` //用户id
|
||||||
|
PlatformId string `json:"platformId" bson:"platformId"` //原始平台流水Id
|
||||||
|
Name string `json:"name" bson:"name"` //用户名
|
||||||
|
Money int64 `json:"money" bson:"money"` //提现金额
|
||||||
|
Amount int64 `json:"amount" bson:"amount"` //金币
|
||||||
|
BuyVIPType BuyVIPType `json:"buyVIPType" bson:"buyVIPType"` //支付方式
|
||||||
|
OID string `json:"oid" bson:"oid"` //第三方支付平台id
|
||||||
|
DeviceType string `json:"deviceType" bson:"deviceType"` //设备类型
|
||||||
|
Status int `json:"status" bson:"status"` //1未审核 5提现成功
|
||||||
|
SuccessAt time.Time `json:"successTime"` // 成功时间(购买会员、提现、充值)
|
||||||
|
IsDirect bool `json:"isDirect" bson:"isDirect"` //true:是直推用户
|
||||||
|
DistrictCode string `json:"districtCode" bson:"districtCode"` //渠道码
|
||||||
|
PromCode string `json:"promCode" bson:"promCode"` //用户推广序列
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatcenterSyncList doc
|
||||||
|
// @Tags 数据通过
|
||||||
|
// @Summary 拉去数据同步信息
|
||||||
|
// @Description 返回用户收益详情
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param param body StatcenterSyncReq true "参数"
|
||||||
|
// @Success 200 {object} StatcenterSyncResp "成功"
|
||||||
|
// @Success 400 {string} string "失败"
|
||||||
|
// @Router /api/web/statcenter/sync [POST]
|
||||||
|
func StatcenterSyncList(ctx *gin.Context) {
|
||||||
|
var request StatcenterSyncReq
|
||||||
|
if err := ctx.ShouldBind(&request); err != nil {
|
||||||
|
common.ServeJSON(ctx, stderr.ErrParamError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
resp StatcenterSyncResp
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
resp.Job = request.Job
|
||||||
|
resp.Code = 200
|
||||||
|
log.Info("StatcenterSyncList", log.Any("job", request.Job))
|
||||||
|
|
||||||
|
switch StatJob(request.Job) {
|
||||||
|
case StatJobVisit:
|
||||||
|
resp.VisitList, err = syncUserVisitList(request)
|
||||||
|
case StatJobRegister:
|
||||||
|
resp.RegisterList, err = syncUserRegisterList(request)
|
||||||
|
case StatJobRegisterLight:
|
||||||
|
resp.RegisterList, err = syncH5UserRegisterList(request)
|
||||||
|
case StatJobRecharge:
|
||||||
|
resp.RechargeList, err = syncUserRechargeList(request)
|
||||||
|
case StatJobRechargeAll:
|
||||||
|
resp.RechargeList, err = syncUserAllRechargeList(request)
|
||||||
|
case StatJobVip:
|
||||||
|
resp.VipList, err = syncUserVipList(request)
|
||||||
|
default:
|
||||||
|
resp.Code = 501
|
||||||
|
resp.Msg = "job is error"
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
resp.Code = 502
|
||||||
|
resp.Msg = err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusOK, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// syncUserVisitList 日活数据同步
|
||||||
|
func syncUserVisitList(request StatcenterSyncReq) ([]UserVisitMsg, error) {
|
||||||
|
visitList, err := visitlogmod.AccessSyncById(request.PlatformId, request.MaxSize)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("syncUserVisitList visitlogmod.AccessSyncById", log.Any("request", request), log.E(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
list := make([]UserVisitMsg, 0, len(visitList))
|
||||||
|
for _, v := range visitList {
|
||||||
|
list = append(list, UserVisitMsg{
|
||||||
|
UserId: v.UID,
|
||||||
|
AppId: statAppId,
|
||||||
|
SysType: v.SysType,
|
||||||
|
DevType: v.SysType,
|
||||||
|
IP: v.IP,
|
||||||
|
Version: v.Ver,
|
||||||
|
DevID: v.DevID,
|
||||||
|
VisitAt: v.CreatedAt,
|
||||||
|
PlatformId: v.ID.Hex(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// syncUserRegisterList 注册数据同步
|
||||||
|
func syncUserRegisterList(request StatcenterSyncReq) ([]UserRegisterMsg, error) {
|
||||||
|
filter := bson.M{
|
||||||
|
"createdAt": bson.M{"$gt": request.SuccessTime},
|
||||||
|
"userType": bson.M{"$in": []usermod.UserType{usermod.UserUnkown, usermod.UserLoufengAgent}},
|
||||||
|
}
|
||||||
|
opts := options.Find().SetLimit(request.MaxSize).SetSort(bson.D{{Key: "uid", Value: 1}})
|
||||||
|
userList, _, err := usermod.FetchList(filter, opts)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("syncUserRegisterList usermod.FetchList", log.Any("request", request), log.E(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
list := make([]UserRegisterMsg, 0, len(userList))
|
||||||
|
for _, u := range userList {
|
||||||
|
list = append(list, toUserRegisterMsg(u, u.CreatedAt))
|
||||||
|
}
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// syncH5UserRegisterList H5注册数据同步(按绑定手机时间)
|
||||||
|
func syncH5UserRegisterList(request StatcenterSyncReq) ([]UserRegisterMsg, error) {
|
||||||
|
userList, err := usermod.StatcenterSyncBindUserList(request.SuccessTime, request.MaxSize)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("syncH5UserRegisterList usermod.StatcenterSyncBindUserList", log.Any("request", request), log.E(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
list := make([]UserRegisterMsg, 0, len(userList))
|
||||||
|
for _, u := range userList {
|
||||||
|
if strings.Contains(u.DevType, "aosp:") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !isH5SysType(u.SysType) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var registerTime time.Time
|
||||||
|
if u.MobileBindAt != nil {
|
||||||
|
registerTime = *u.MobileBindAt
|
||||||
|
}
|
||||||
|
list = append(list, toUserRegisterMsg(&u, registerTime))
|
||||||
|
}
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toUserRegisterMsg(u *usermod.User, registerTime time.Time) UserRegisterMsg {
|
||||||
|
return UserRegisterMsg{
|
||||||
|
UserId: u.UID,
|
||||||
|
AppId: statAppId,
|
||||||
|
SysType: u.SysType,
|
||||||
|
DevType: u.DevType,
|
||||||
|
Mobile: u.Mobile,
|
||||||
|
Name: u.Name,
|
||||||
|
IP: u.RegisterIP,
|
||||||
|
IsDirect: u.IsDirect,
|
||||||
|
DistrictCode: u.DistrictCode,
|
||||||
|
PromSeqe: u.PromSeqe,
|
||||||
|
PromCode: u.PromCode,
|
||||||
|
RegisterTime: registerTime,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// isH5SysType 是否H5端
|
||||||
|
func isH5SysType(sysType string) bool {
|
||||||
|
s := strings.ToLower(sysType)
|
||||||
|
switch s {
|
||||||
|
case "light", "tg", "web":
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return strings.Contains(s, "h5")
|
||||||
|
}
|
||||||
|
|
||||||
|
// syncUserRechargeList 成功充值订单同步
|
||||||
|
func syncUserRechargeList(request StatcenterSyncReq) ([]UserRechargeMsg, error) {
|
||||||
|
orders, err := rchgordmod.StatCenterSyncRecharge(request.SuccessTime, request.MaxSize)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("syncUserRechargeList rchgordmod.StatCenterSyncRecharge", log.Any("request", request), log.E(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
list := make([]UserRechargeMsg, 0, len(orders))
|
||||||
|
for _, o := range orders {
|
||||||
|
msg := toUserRechargeMsg(o)
|
||||||
|
msg.SuccessAt = o.SuccessAt
|
||||||
|
list = append(list, msg)
|
||||||
|
}
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// syncUserAllRechargeList 全部充值订单同步(不含失败订单)
|
||||||
|
func syncUserAllRechargeList(request StatcenterSyncReq) ([]UserRechargeMsg, error) {
|
||||||
|
orders, err := rchgordmod.StatCenterSyncOrder(request.SuccessTime, request.MaxSize)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("syncUserAllRechargeList rchgordmod.StatCenterSyncOrder", log.Any("request", request), log.E(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
list := make([]UserRechargeMsg, 0, len(orders))
|
||||||
|
for _, o := range orders {
|
||||||
|
if o.Status == rchgordmod.FAILURE {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
msg := toUserRechargeMsg(o)
|
||||||
|
msg.CreatedAt = o.CreatedAt
|
||||||
|
list = append(list, msg)
|
||||||
|
}
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toUserRechargeMsg(o rchgordmod.RechargeOrder) UserRechargeMsg {
|
||||||
|
return UserRechargeMsg{
|
||||||
|
UserId: o.UID,
|
||||||
|
AppId: statAppId,
|
||||||
|
SysType: o.DevType,
|
||||||
|
DevType: o.DevType,
|
||||||
|
PlatformId: o.ID.Hex(),
|
||||||
|
ChannelName: o.Channel,
|
||||||
|
CID: o.OID,
|
||||||
|
Type: o.RechargeType,
|
||||||
|
OrderId: o.ID.Hex(),
|
||||||
|
Money: o.Money,
|
||||||
|
PayMoney: o.PayMoney,
|
||||||
|
Status: o.Status,
|
||||||
|
Rate: o.Rate,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// syncUserVipList 充值购买会员卡同步
|
||||||
|
func syncUserVipList(request StatcenterSyncReq) ([]UserBuyVIPMsg, error) {
|
||||||
|
endTime := time.Now().Add(-time.Minute * 2)
|
||||||
|
filter := bson.M{
|
||||||
|
"tranTypeInt": int64(txnmod.BuyVIP),
|
||||||
|
"createdAt": bson.M{"$gt": request.SuccessTime, "$lt": endTime},
|
||||||
|
}
|
||||||
|
opts := options.Find().SetLimit(request.MaxSize).SetSort(bson.D{{Key: "createdAt", Value: 1}})
|
||||||
|
_, logs, err := txnmod.FindTransactionLogs(filter, opts)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("syncUserVipList txnmod.FindTransactionLogs", log.Any("request", request), log.E(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
list := make([]UserBuyVIPMsg, 0, len(logs))
|
||||||
|
for _, l := range logs {
|
||||||
|
money := l.Amount
|
||||||
|
if money < 0 {
|
||||||
|
money = -money
|
||||||
|
}
|
||||||
|
list = append(list, UserBuyVIPMsg{
|
||||||
|
UID: l.UID,
|
||||||
|
AppId: statAppId,
|
||||||
|
PlatformId: l.ID.Hex(),
|
||||||
|
Money: money * 10, // 流水金额为会员卡价格(角),转换为分
|
||||||
|
BuyVIPType: BuyVIPTypeMoney,
|
||||||
|
OID: l.TransNo.Hex(),
|
||||||
|
DeviceType: l.SysType,
|
||||||
|
Status: 3,
|
||||||
|
SuccessAt: l.CreatedAt,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
@@ -189,4 +189,9 @@ func registerRouter(router *gin.Engine) {
|
|||||||
{
|
{
|
||||||
syncRouter(syncDataRouter)
|
syncRouter(syncDataRouter)
|
||||||
}
|
}
|
||||||
|
//数据中心同步接口,不走后台登录鉴权
|
||||||
|
statcenterGroup := router.Group("/api/web/statcenter")
|
||||||
|
{
|
||||||
|
statcenterRouter(statcenterGroup)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,11 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// statcenterRouter 数据中心拉取同步数据(协议与哔哩 /api/web/statcenter/sync 一致)
|
||||||
|
func statcenterRouter(router *gin.RouterGroup) {
|
||||||
|
router.POST("/sync", statcenterctl.StatcenterSyncList)
|
||||||
|
}
|
||||||
|
|
||||||
// syncRouter 数据同步
|
// syncRouter 数据同步
|
||||||
func syncRouter(router *gin.RouterGroup) {
|
func syncRouter(router *gin.RouterGroup) {
|
||||||
routerGroup := router.Group("/router")
|
routerGroup := router.Group("/router")
|
||||||
|
|||||||
Reference in New Issue
Block a user