package ai_mate_ser import ( "91porn-server/app/appg" "91porn-server/common" aimate "91porn-server/common/aiMate" "91porn-server/common/constant/redisconst" "91porn-server/common/db" "91porn-server/common/log" "91porn-server/common/redis" "91porn-server/common/stderr" "91porn-server/middleware/ua" "91porn-server/models/commod" "91porn-server/models/v/currencymod" "91porn-server/models/v/txnmod" "91porn-server/models/v/usermod" "91porn-server/models/v/walletmod" "errors" "fmt" "time" "github.com/go-redsync/redsync/v4" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" ) func GetApiUrl() string { var apiUrl string if appg.Conf.URL.AIMateH5 != "" { if appg.Conf.Base.Env == "prod" { apiUrl = fmt.Sprintf("%s/?aId=%d", appg.Conf.URL.AIMateH5, commod.KFK_APPID) } else { apiUrl = fmt.Sprintf("%s/?aId=%d&devTest=1", appg.Conf.URL.AIMateH5, commod.KFK_APPID) } } return apiUrl } type LoginResp struct { URL string `json:"url"` } type loginDependencies struct { findUser func(uint64) (*usermod.User, error) updateUser func(uint64, usermod.UserSelector) (*usermod.User, error) getWallet func(uint64) (*walletmod.Wallet, error) getRemoteBalance func(uint64, string) (float64, error) getAccessURL func(uint64, string, string, float64) (string, error) newUUID func() string } func defaultLoginDependencies() loginDependencies { return loginDependencies{ findUser: usermod.FindUserByUID, updateUser: usermod.Update, getWallet: walletmod.GetWallet, getRemoteBalance: aimate.GetUserAiBalance, getAccessURL: aimate.GetUserAccessAiUrl, newUUID: common.UUID, } } // Login 获取当前用户的 AI 女友第三方登录地址。 func Login(uid uint64) (LoginResp, error) { if appg.Redis == nil { return LoginResp{}, errors.New("redis is unavailable") } lock := redis.BuildLock( appg.Redis, redisconst.GetAiMateLoginLockKey(uid), redsync.WithExpiry(30*time.Second), redsync.WithTries(1), ) if err := lock.Lock(); err != nil { return LoginResp{}, fmt.Errorf("acquire ai mate login lock: %w", err) } defer func() { if _, err := lock.Unlock(); err != nil { log.Warn("ai_mate_ser Login unlock failed", log.Any("uid", uid), log.E(err)) } }() return login(uid, defaultLoginDependencies()) } func login(uid uint64, deps loginDependencies) (LoginResp, error) { user, err := deps.findUser(uid) if err != nil { return LoginResp{}, err } if user == nil || user.ID.IsZero() { return LoginResp{}, errors.New("ai mate login user not found") } wallet, err := deps.getWallet(uid) if err != nil { return LoginResp{}, err } localBalance := float64(0) if wallet != nil { localBalance = wallet.AiMateBalance } aiMateUID := user.AiMateUid remoteBalance := float64(0) if aiMateUID == "" { aiMateUID = deps.newUUID() if aiMateUID == "" { return LoginResp{}, errors.New("generate ai mate uid failed") } if _, err = deps.updateUser(uid, usermod.UserSelector{AiMateUid: &aiMateUID}); err != nil { return LoginResp{}, err } } else { remoteBalance, err = deps.getRemoteBalance(uid, aiMateUID) if err != nil { return LoginResp{}, err } } url, err := deps.getAccessURL(uid, aiMateUID, user.Name, balanceTopUp(localBalance, remoteBalance)) if err != nil { return LoginResp{}, err } if url == "" { return LoginResp{}, errors.New("ai mate login url is empty") } return LoginResp{URL: url}, nil } func balanceTopUp(localBalance, remoteBalance float64) float64 { if localBalance <= remoteBalance || localBalance <= 0 { return 0 } return localBalance - remoteBalance } // GetCurrencyList 获取AI伴侣币列表 func GetCurrencyList() (code stderr.Code, data interface{}) { code = stderr.Success cs := make([]currencymod.CurrencyApp, 0) //查询 currencys, err := currencymod.List(commod.AiMateCoin) if err != nil { log.Error("ai_mate_ser GetCurrencyList currencymod.List err", log.E(err)) code = stderr.ErrDbQueryError return } if len(currencys) > 0 { for _, c := range currencys { cs = append(cs, currencymod.CurrencyApp{ ID: c.ID, Name: c.Name, Type: c.Type, Coins: c.Coins, Price: c.Price, CouponDesc: c.CouponDesc, }) } } data = cs return } // ExchangeReq 兑换请求参数 type ExchangeReq struct { Id primitive.ObjectID `json:"id" binding:"required"` } // Exchange 获取AI伴侣币兑换 func Exchange(uid uint64, req ExchangeReq, ua ua.UA, ip string) (code stderr.Code) { code = stderr.Success //查询用户信息 user, err := usermod.FindUserByUID(uid) if err != nil { log.Error("ai_mate_ser Exchange usermod.FindUserByUID err", log.Any("uid", uid), log.E(err)) code = stderr.ErrDbQueryError return } if user == nil || user.ID.IsZero() { log.Error("ai_mate_ser Exchange user not exist", log.Any("uid", uid)) code = stderr.UserIsNotExists return } //查询货币是否存在 currency, err := currencymod.Get(req.Id) if err != nil { log.Error("ai_mate_ser Exchange currencymod.List err", log.E(err)) code = stderr.ErrDbQueryError return } if currency.ID.IsZero() { log.Error("ai_mate_ser Exchange currency not exist", log.Any("id", req.Id.Hex())) code = stderr.CodeEmptyData return } if !currency.IsActive { log.Error("ai_mate_ser Exchange currency isActive is false", log.Any("id", req.Id.Hex())) code = stderr.ErrDataInvalid return } if currency.Type != commod.AiMateCoin { log.Error("ai_mate_ser Exchange currency type not commod.AiMateCoin", log.Any("id", req.Id.Hex())) code = stderr.ExchangeCodeInvalid return } //查询用户钱包 wallet, err := walletmod.GetWallet(uid) if err != nil { code = stderr.ErrDbQueryError log.Error("ai_mate_ser Exchange walletmod.GetWallet err:", log.E(err), log.Any("uid", uid)) return } //需要支付的金额 price := currency.Price if wallet == nil || wallet.Amount < price { code = stderr.InsufficientBalance log.Error("ai_mate_ser Exchange user Insufficient balance", log.Any("uid", uid), log.Any("id", currency.ID.Hex())) return } //兑换业务 if err = appg.VideoDB.Trans(func(t *db.MongoTool) error { //扣除金币余额,增加AI伴侣余额 cAmount := -price cAiMateBalance := float64(currency.Coins) p := walletmod.CreditPlan{ Amount: &cAmount, AiMateBalance: &cAiMateBalance, } wallet, err := walletmod.Credit(t, p, uid) if err != nil { //扣钱 return err } //新增交易日志 txnLog := &txnmod.TransactionLog{ ID: primitive.NewObjectID(), UID: uid, Amount: -price, ActualAmount: float64(-price), TranType: txnmod.AiMateCurrencyExchange.Key(), TranTypeInt: int64(txnmod.AiMateCurrencyExchange), TransNo: currency.ID, Desc: "AI伴侣币购买-" + currency.Name, DiscDoc: user.DiscDoc, SysType: user.SysType, RealAmount: walletmod.GetRealAmount(wallet), AiMatePoint: cAiMateBalance, RealAiMatePoint: wallet.AiMateBalance, CreatedAt: time.Now(), } if err = txnmod.InsertTransactionLog(t, txnLog); err != nil { return err } return nil }); err != nil { log.Warn("ai_mate_ser Exchange Transaction fail", log.Any("uid", uid), log.Any("id", currency.ID.Hex()), log.E(err)) code = stderr.BuyFailed return } return } type BalanceResp struct { Balance float64 `json:"balance" bson:"balance"` // 余额 LastOrderId string `json:"lastOrderId" bson:"lastOrderId"` } // GetNewBalance 获取AI伴侣余余额 func GetNewBalance(uid uint64) (BalanceResp, error) { var ret BalanceResp // 查询用户信息 user, err := usermod.FindUserByUID(uid) if err != nil { log.Error("ai_mate_ser GetBalance usermod.FindUserByUID err:", log.E(err), log.Any("uid", uid)) return ret, err } if user == nil || user.ID.IsZero() { log.Error("ai_mate_ser GetBalance user not exist", log.Any("uid", uid)) return ret, errors.New("ai_mate_ser GetBalance user not exist") } // 查询用户钱包ai伴侣余额 wallet, err := walletmod.GetWallet(uid) if err != nil { log.Error("ai_mate_ser GetBalance walletmod.GetWallet err:", log.E(err), log.Any("uid", uid)) return ret, err } if wallet != nil && wallet.AiMateBalance > 0 { ret.Balance = wallet.AiMateBalance ret.LastOrderId = wallet.LastAiMateRecordId } return ret, nil } type SyncInfoRes struct { UID uint64 `json:"uid" bson:"uid"` // 用户id TotalTokens int `json:"totalTokens" bson:"totalTokens"` // 总共使用的 token 数量 Amount float64 `json:"amount" bson:"amount"` // 花费积分 OrderID string `json:"orderId" bson:"orderId"` // 订单ID } func (q *SyncInfoRes) Sync() error { // 同步用户信息 userInfo, err := usermod.FindUserByUIDForNoCache(q.UID) if err != nil { return err } if userInfo == nil || userInfo.ID.IsZero() { log.Warn(fmt.Sprintf("user is null")) return nil } // 获取 cond := bson.M{"tranTypeInt": txnmod.AiMateChat, "uid": q.UID, "productID": q.OrderID} transactionLog, err := txnmod.FindByCond(cond) if err != nil { return err } // 重复上传 if !transactionLog.ID.IsZero() { return nil } // 获取余额 wallet, err := walletmod.GetWallet(q.UID) if err != nil { return err } if wallet == nil || wallet.ID.IsZero() { return errors.New("wallet is null") } amt := -q.Amount // 获取用户是否是复购 isRepurchase, err := txnmod.CheckRepurchaseByTransTypes(q.UID, []txnmod.TransType{ txnmod.AiMateChat, }) if err != nil { log.Error("txnmod.CheckRepurchaseByTransTypes fail", log.E(err)) return err } // 新增日志流水记录 if err = appg.VideoDB.Trans(func(t *db.MongoTool) error { if q.Amount > 0 { // 扣除余额 w, err := walletmod.CreditAiMate(t, amt, q.UID, q.OrderID) if err != nil { return err } po := int64(-q.Amount) // 添加流水 if err = txnmod.InsertTransactionLog(t, &txnmod.TransactionLog{ TransNo: primitive.NewObjectID(), UID: userInfo.UID, TranType: txnmod.AiMateChat.Key(), TranTypeInt: int64(txnmod.AiMateChat), Desc: fmt.Sprintf("AI女友聊天-花费:%.2f积分", q.Amount), DiscDoc: userInfo.DiscDoc, SysType: userInfo.SysType, Integral: po, ProductID: &q.OrderID, AiMatePoint: -q.Amount, RealAiMatePoint: w.AiMateBalance, IsRepurchase: isRepurchase, }); err != nil { return err } } return nil }); err != nil { log.Error(fmt.Sprintf("uid:%v,ai_mate sync trans err:%v", q.UID, err)) return err } return nil }