Files
rootandClaude Opus 5 8679200f41 Initial commit
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 13:57:10 +08:00

384 lines
10 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}