Initial commit

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-11-07 10:47:18 +08:00
co-authored by Claude Opus 4.7
commit 79ac89b458
708 changed files with 151634 additions and 0 deletions
+171
View File
@@ -0,0 +1,171 @@
package vipordermod
import (
"context"
"fmt"
"time"
"videoweb/pub/common/log"
"videoweb/pub/common/srvdef"
"videoweb/pub/mongos"
"videoweb/pub/pubdef"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
var coll *mongos.Collection
func Init(ctx context.Context) {
coll = mongos.Shop.Coll(TN.VipOrder)
initIndex(ctx)
}
func initIndex(ctx context.Context) {
indexes := []mongo.IndexModel{
{Keys: bson.D{{Key: FN.VipOrder.UserId, Value: -1}}},
{Keys: bson.D{{Key: FN.VipOrder.TradeNo, Value: 1}}, Options: options.Index().SetUnique(true)},
{Keys: bson.D{{Key: FN.VipOrder.Status, Value: 1}}},
{Keys: bson.D{{Key: FN.VipOrder.DistrictCode, Value: 1}}},
{Keys: bson.D{{Key: FN.VipOrder.CreatedAt, Value: -1}}},
}
if _, err := coll.CreateIndex(ctx, indexes); err != nil {
panic(fmt.Sprintf("(vipordermod:initIndex) Create Index failed! err:%+v:", err))
}
}
func InsertOneVipOrder(ctx context.Context, doc *VipOrder) *srvdef.SrvErr {
if _, err := coll.InsertOne(ctx, doc); err != nil {
log.Error("vipordermod:InsertOneVipOrder failed", log.Any("doc", doc), log.E(err))
return srvdef.New(srvdef.ErrDbInsertError, "", "")
}
return nil
}
func FindOneVipOrder(ctx context.Context, filter bson.M) (*VipOrder, *srvdef.SrvErr) {
var doc VipOrder
if err := coll.FindOneEx(ctx, &doc, filter); err != nil {
if err != mongo.ErrNoDocuments {
log.Warn("vipordermod:FindOneVipOrder failed", log.E(err), log.Any("filter", filter))
return nil, srvdef.New(srvdef.ErrDbQueryError, "", "")
}
return nil, srvdef.New(srvdef.ErrNoData, "", "")
}
return &doc, nil
}
func FindManyVipOrder(ctx context.Context, filter bson.M, opts ...*options.FindOptions) (VipOrderList, *srvdef.SrvErr) {
var data VipOrderList
if err := coll.Find(ctx, &data, filter, opts...); err != nil {
log.Warn("vipordermod:FindManyVipOrder failed", log.E(err), log.Any("filter", filter))
return nil, srvdef.New(srvdef.ErrDbQueryError, "", "")
}
return data, nil
}
func CountVipOrder(ctx context.Context, filter bson.M) (int64, *srvdef.SrvErr) {
count, err := coll.Count(ctx, filter)
if err != nil {
log.Warn("vipordermod:CountVipOrder failed", log.E(err), log.Any("filter", filter))
return 0, srvdef.New(srvdef.ErrDbQueryError, "", "")
}
return count, nil
}
// StatSyncSuccessOrderList 增量拉取支付成功订单(统计中心同步)
func StatSyncSuccessOrderList(ctx context.Context, successTime time.Time, size int64) (VipOrderList, error) {
opt := (&options.FindOptions{}).SetLimit(size).SetSort(bson.M{FN.VipOrder.FinishedAt: 1})
// 预留足够时间让注册数据先到达统计中心,避免充值记录找不到对应注册用户
endTime := time.Now().Add(-time.Minute * 4)
filter := bson.M{
FN.VipOrder.FinishedAt: bson.M{"$gt": successTime, "$lt": endTime},
FN.VipOrder.Status: pubdef.VipOrderSuccess,
}
list := VipOrderList{}
if err := coll.Find(ctx, &list, filter, opt); err != nil {
return nil, err
}
return list, nil
}
// StatSyncAllOrderList 增量拉取非失败订单(统计中心同步)
func StatSyncAllOrderList(ctx context.Context, createTime time.Time, size int64) (VipOrderList, error) {
opt := (&options.FindOptions{}).SetLimit(size).SetSort(bson.M{FN.VipOrder.CreatedAt: 1})
endTime := time.Now().Add(-time.Minute * 1)
filter := bson.M{
FN.VipOrder.CreatedAt: bson.M{"$gt": createTime, "$lt": endTime},
FN.VipOrder.Status: bson.M{"$ne": pubdef.VipOrderFailed},
}
list := VipOrderList{}
if err := coll.Find(ctx, &list, filter, opt); err != nil {
return nil, err
}
return list, nil
}
// StatSyncVipOrderList 增量拉取会员购买成功订单(统计中心同步)
func StatSyncVipOrderList(ctx context.Context, successTime time.Time, size int64) (VipOrderList, error) {
opt := (&options.FindOptions{}).SetLimit(size).SetSort(bson.M{FN.VipOrder.FinishedAt: 1})
endTime := time.Now().Add(-time.Minute * 1)
filter := bson.M{
FN.VipOrder.FinishedAt: bson.M{"$gt": successTime, "$lt": endTime},
FN.VipOrder.Status: pubdef.VipOrderSuccess,
FN.VipOrder.OrderType: OrderTypeVip,
}
list := VipOrderList{}
if err := coll.Find(ctx, &list, filter, opt); err != nil {
return nil, err
}
return list, nil
}
func EmptyDistrictFilter() bson.M {
return bson.M{
"$or": []bson.M{
{FN.VipOrder.DistrictCode: bson.M{"$exists": false}},
{FN.VipOrder.DistrictCode: ""},
{FN.VipOrder.DistrictCode: nil},
},
}
}
// AggregateVipOrder 聚合查询充值订单
func AggregateVipOrder(ctx context.Context, bind interface{}, pipeline []bson.M) *srvdef.SrvErr {
if err := coll.Aggregate(ctx, bind, pipeline); err != nil {
log.Warn("vipordermod:AggregateVipOrder failed", log.E(err), log.Any("pipeline", pipeline))
return srvdef.New(srvdef.ErrDbQueryError, "", "")
}
return nil
}
func UpdateManyVipOrder(ctx context.Context, filter bson.M, update bson.M) (int64, *srvdef.SrvErr) {
result, err := coll.UpdateMany(ctx, filter, update)
if err != nil {
log.Warn("vipordermod:UpdateManyVipOrder failed", log.E(err), log.Any("filter", filter))
return 0, srvdef.New(srvdef.ErrDbUpdateError, "", "")
}
return result.ModifiedCount, nil
}
// UpdateOrderSuccess 将订单标记为支付成功
func UpdateOrderSuccess(ctx context.Context, tradeNo string, oid string, realAmount int64, paymentAt time.Time) *srvdef.SrvErr {
now := time.Now()
result, err := coll.UpdateOne(ctx,
bson.M{FN.VipOrder.TradeNo: tradeNo},
bson.M{"$set": bson.M{
FN.VipOrder.Status: pubdef.VipOrderSuccess,
FN.VipOrder.Oid: oid,
FN.VipOrder.Amount: realAmount,
FN.VipOrder.FinishedAt: paymentAt,
FN.VipOrder.UpdatedAt: now,
}},
)
if err != nil {
log.Warn("vipordermod:UpdateOrderSuccess failed", log.E(err), log.Any("tradeNo", tradeNo))
return srvdef.New(srvdef.ErrDbUpdateError, "", "")
}
if result.MatchedCount == 0 {
return srvdef.New(srvdef.ErrNoData, "订单不存在", "")
}
return nil
}
+64
View File
@@ -0,0 +1,64 @@
package vipordermod
type TagVipOrder struct {
Id string
OrderType string
UserId string
CardId string
CardName string
CoinCfgId string
Coin string
BaseCoin string
GiftCoin string
Amount string
PayMode string
TradeNo string
Oid string
Status string
PayUrl string
PayUrlMode string
PayChannel string
PayRate string
IPAddr string
SysType string
DistrictCode string
CreatedAt string
UpdatedAt string
FinishedAt string
}
var FN struct {
VipOrder TagVipOrder
}
var TN struct {
VipOrder string
}
func init() {
FN.VipOrder.Id = "_id"
FN.VipOrder.OrderType = "orderType"
FN.VipOrder.UserId = "userId"
FN.VipOrder.CardId = "cardId"
FN.VipOrder.CardName = "cardName"
FN.VipOrder.CoinCfgId = "coinCfgId"
FN.VipOrder.Coin = "coin"
FN.VipOrder.BaseCoin = "baseCoin"
FN.VipOrder.GiftCoin = "giftCoin"
FN.VipOrder.Amount = "amount"
FN.VipOrder.PayMode = "payMode"
FN.VipOrder.TradeNo = "tradeNo"
FN.VipOrder.Oid = "oid"
FN.VipOrder.Status = "status"
FN.VipOrder.PayUrl = "payUrl"
FN.VipOrder.PayUrlMode = "payUrlMode"
FN.VipOrder.PayChannel = "payChannel"
FN.VipOrder.PayRate = "payRate"
FN.VipOrder.IPAddr = "ipAddr"
FN.VipOrder.SysType = "sysType"
FN.VipOrder.DistrictCode = "districtCode"
FN.VipOrder.CreatedAt = "createdAt"
FN.VipOrder.UpdatedAt = "updatedAt"
FN.VipOrder.FinishedAt = "finishedAt"
TN.VipOrder = "vip_order"
}
+130
View File
@@ -0,0 +1,130 @@
package vipordermod
import (
"strings"
"time"
"videoweb/pub/common/useragent"
"videoweb/pub/pubdef"
)
// OrderType 订单类型
type OrderType int8
const (
OrderTypeVip OrderType = 1 // 会员购买
OrderTypeCoin OrderType = 2 // 金币购买
)
// @table: vip_order
type VipOrder struct {
Id int64 `json:"id" bson:"_id"` // 唯一ID
OrderType OrderType `json:"orderType" bson:"orderType"` // 订单类型:1会员 2金币
UserId int64 `json:"userId" bson:"userId"` // 用户ID
// ── VIP 专用 ──
CardId int64 `json:"cardId" bson:"cardId"` // 购买套餐ID(会员畅享)
CardName string `json:"cardName" bson:"cardName"` // 套餐名快照
// ── 金币专用 ──
CoinCfgId int64 `json:"coinCfgId" bson:"coinCfgId"` // 购买套餐ID(金币充值)
Coin int64 `json:"coin" bson:"coin"` // 到账金币数(快照)
BaseCoin int64 `json:"baseCoin" bson:"baseCoin"` // 基础金币数(快照)
GiftCoin int64 `json:"giftCoin" bson:"giftCoin"` // 赠送金币数(快照)
Amount int64 `json:"amount" bson:"amount"` // 支付金额(分)
PayMode string `json:"payMode" bson:"payMode"` // 支付方式:overage/alipay/wechat
TradeNo string `json:"tradeNo" bson:"tradeNo"` // 本地订单号
Oid string `json:"oid" bson:"oid"` // 第三方订单号
Status pubdef.VipOrderStatus `json:"status" bson:"status"` // 订单状态
PayUrl string `json:"payUrl" bson:"payUrl"` // 支付链接
PayUrlMode string `json:"payUrlMode" bson:"payUrlMode"` // 链接打开方式
PayChannel string `json:"payChannel" bson:"payChannel"` // 支付渠道
PayRate string `json:"payRate" bson:"payRate"` // 费率
IPAddr string `json:"ipAddr" bson:"ipAddr"` // 客户端 IP
SysType string `json:"sysType" bson:"sysType"` // ios/android
DistrictCode string `json:"districtCode" bson:"districtCode"` // 下单时用户渠道码
Client *OrderClient `json:"client,omitempty" bson:"client,omitempty"` // 下单时的设备与来源页,支付回调没有浏览器请求
CreatedAt time.Time `json:"createdAt" bson:"createdAt"` // 创建时间
UpdatedAt time.Time `json:"updatedAt" bson:"updatedAt"` // 更新时间
FinishedAt time.Time `json:"finishedAt" bson:"finishedAt"` // 完成时间
}
type VipOrderList []*VipOrder
// OrderClient 保存下单请求里的设备和来源页。支付成功回调没有浏览器请求,用这份快照上报。
type OrderClient struct {
Device string `json:"device" bson:"device"`
DeviceID string `json:"deviceId" bson:"deviceId"`
DeviceFingerprint string `json:"deviceFingerprint" bson:"deviceFingerprint"`
FPVersion string `json:"fpVersion" bson:"fpVersion"`
SID string `json:"sid" bson:"sid"`
AppVersion string `json:"appVersion" bson:"appVersion"`
UserAgent string `json:"userAgent" bson:"userAgent"`
DeviceBrand string `json:"deviceBrand" bson:"deviceBrand"`
DeviceModel string `json:"deviceModel" bson:"deviceModel"`
SystemName string `json:"systemName" bson:"systemName"`
SystemVersion string `json:"systemVersion" bson:"systemVersion"`
SourcePageKey string `json:"sourcePageKey" bson:"sourcePageKey"`
SourcePageName string `json:"sourcePageName" bson:"sourcePageName"`
}
// NewOrderClient 从当次请求复制设备和来源页。
func NewOrderClient(ua useragent.UA, sourcePageKey, sourcePageName string) *OrderClient {
client := &OrderClient{
UserAgent: strings.TrimSpace(ua.UserAgent),
DeviceBrand: strings.TrimSpace(ua.DeviceBrand),
DeviceModel: strings.TrimSpace(ua.DeviceModel),
SystemName: strings.TrimSpace(ua.SystemName),
SystemVersion: strings.TrimSpace(ua.SystemVersion),
SourcePageKey: trimOrderText(sourcePageKey, 64),
SourcePageName: trimOrderText(sourcePageName, 128),
SID: strings.TrimSpace(ua.SessionID),
DeviceID: strings.TrimSpace(ua.DevID),
}
if ua.SDKCommon != nil {
sdk := ua.SDKCommon.Clone()
if device := strings.TrimSpace(sdk.Device); device != "" {
client.Device = device
}
if deviceID := strings.TrimSpace(sdk.DeviceID); deviceID != "" {
client.DeviceID = deviceID
}
client.DeviceFingerprint = strings.TrimSpace(sdk.DeviceFingerprint)
client.FPVersion = strings.TrimSpace(sdk.FPVersion)
if sid := strings.TrimSpace(sdk.SID); sid != "" {
client.SID = sid
}
client.AppVersion = strings.TrimSpace(sdk.AppVersion)
}
return client
}
// UA 还原下单时的设备信息。系统类型仍用订单上的 SysType,给设备类型兜底。
func (c *OrderClient) UA(sysType string) useragent.UA {
ua := useragent.UA{SysType: strings.TrimSpace(sysType)}
if c == nil {
return ua
}
ua.UserAgent = c.UserAgent
ua.DevID = c.DeviceID
ua.DeviceBrand = c.DeviceBrand
ua.DeviceModel = c.DeviceModel
ua.SystemName = c.SystemName
ua.SystemVersion = c.SystemVersion
ua.SessionID = c.SID
ua.SDKCommon = &useragent.SDKCommonFields{
SID: c.SID,
DeviceID: c.DeviceID,
Device: c.Device,
DeviceFingerprint: c.DeviceFingerprint,
FPVersion: c.FPVersion,
AppVersion: c.AppVersion,
}
return ua
}
func trimOrderText(value string, limit int) string {
value = strings.TrimSpace(value)
runes := []rune(value)
if limit <= 0 || len(runes) <= limit {
return value
}
return string(runes[:limit])
}