Initial commit

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 13:57:10 +08:00
co-authored by Claude Opus 5
commit 8679200f41
1897 changed files with 257900 additions and 0 deletions
@@ -0,0 +1,384 @@
package ai_changeface_ser
import (
"91porn-server/app/appg"
"91porn-server/app/service/aiautoser"
"91porn-server/common"
"91porn-server/common/aiService"
"91porn-server/common/constant/redisconst"
"91porn-server/common/db"
"91porn-server/common/log"
"91porn-server/common/stderr"
"91porn-server/middleware/ua"
"91porn-server/models/commod"
"91porn-server/models/v/aichangefacemod"
"91porn-server/models/v/aichangefacevidmod"
"91porn-server/models/v/backpackmod"
"91porn-server/models/v/txnmod"
"91porn-server/models/v/usermod"
"91porn-server/models/v/walletmod"
"encoding/json"
"errors"
"fmt"
"time"
"go.mongodb.org/mongo-driver/bson/primitive"
)
func List(uid uint64, status *aichangefacemod.AiChangeFaceStatus, skip, limit int) ([]aichangefacemod.AiChangeFace, bool, error) {
acfs, err := aichangefacemod.ListByUid(uid, status, skip, limit+1)
if err != nil {
return nil, false, err
}
hasNext := false
if len(acfs) > limit {
acfs = acfs[:limit]
hasNext = true
}
return acfs, hasNext, nil
}
func Generate(uid uint64, pics []string, vidModId primitive.ObjectID, discount []primitive.ObjectID, shareTitle string, shareStatus int, ua ua.UA, ip string) stderr.Code {
vidMod, err := aiService.NewAiService(
aiService.AppId(int(commod.KFK_APPID)),
aiService.Url(appg.Conf.URL.AiServer),
aiService.Redis(appg.Redis),
).GetTemplate(vidModId)
if vidMod.ID.IsZero() {
return stderr.Failure
}
var discountValue int64
now := time.Now()
var dsId []primitive.ObjectID
for _, d := range discount {
dsId = append(dsId, d)
bp, err := backpackmod.GetByID(nil, d)
if err != nil {
return stderr.ErrDbQueryError
}
if bp == nil { // 抵扣券不存在
return stderr.Failure
}
if bp.GoodsType != backpackmod.AiChangeFaceDiscount { // 抵扣券类型不对
return stderr.Failure
}
if bp.Status == backpackmod.Used { // 抵扣券已被使用
return stderr.Failure
}
if bp.ExpiredTime.Before(now) { // 抵扣券已过期
return stderr.Failure
}
discountValue += bp.GoodsValue
}
originPrice := vidMod.Coin
user, err := usermod.FindUserByUID(uid)
if err != nil {
return stderr.ErrDbQueryError
}
if user.IsVIP(time.Now()) {
originPrice = vidMod.VipCoin
}
price := int64(originPrice) - discountValue
if price < 0 {
price = 0
}
// 获取钱包信息
wlt, err := walletmod.GetWallet(uid)
if err != nil {
return stderr.ErrDbQueryError
}
if wlt == nil || wlt.Amount+wlt.Income < price {
return stderr.InsufficientBalance
}
// 扣除的普通金币和收益金币
debitAmt, debitIncome := TotalDebit(price, wlt)
// 获取用户是否是复购
isRepurchase, err := txnmod.CheckRepurchaseByTransTypes(uid, []txnmod.TransType{
txnmod.AiChangefaceDebitGold,
txnmod.AiChangefaceDebitInComeGold,
})
if err != nil {
log.Error("txnmod.CheckRepurchaseByTransTypes fail", log.E(err))
return stderr.ErrDbQueryError
}
var orderId primitive.ObjectID
var orderCreatedAt time.Time
if err = appg.VideoDB.Trans(func(tool *db.MongoTool) error {
var tl []txnmod.TransactionLog
if debitAmt > 0 || debitIncome > 0 {
// 扣除钱包余额
wallet, err := walletmod.DebitAmountAndIncome(tool, debitAmt, debitIncome, uid)
if err != nil {
log.Error(fmt.Sprintf("Handle AiChangeface Generate walletmod.DebitAmount error:%+v:", err), log.Any("uid", uid))
if err.Error() == "no enough balance" {
return stderr.InsufficientBalance
}
return stderr.ErrDbUpdateError
}
if debitAmt > 0 {
tl = append(tl, txnmod.TransactionLog{
TransNo: primitive.NewObjectID(),
UID: uid,
Amount: -debitAmt,
ActualAmount: -float64(debitAmt),
TranType: txnmod.AiChangefaceDebitGold.Key(),
TranTypeInt: int64(txnmod.AiChangefaceDebitGold),
Desc: fmt.Sprintf("生成AI换脸金币-%d", debitAmt),
RealAmount: walletmod.GetRealAmount(wallet),
SysType: user.SysType,
IsRepurchase: isRepurchase,
})
}
if debitIncome > 0 {
tl = append(tl, txnmod.TransactionLog{
TransNo: primitive.NewObjectID(),
UID: uid,
Amount: -debitIncome,
ActualAmount: -float64(debitIncome),
TranType: txnmod.AiChangefaceDebitGold.Key(),
TranTypeInt: int64(txnmod.AiChangefaceDebitGold),
Desc: fmt.Sprintf("生成AI换脸收益金币-%d", debitIncome),
RealAmount: walletmod.GetRealAmount(wallet),
SysType: user.SysType,
IsRepurchase: isRepurchase,
})
}
}
if len(tl) > 0 {
txnErr := txnmod.InsertManyTransactionLog(tool, tl)
if txnErr != nil {
log.Error(fmt.Sprintf("Handle AiChangeface Generate txnmod.InsertManyTransactionLog error:%+v:", txnErr), log.Any("uid", uid))
return stderr.ErrDbQueryError
}
}
// 抵扣券使用
if len(dsId) > 0 {
modified, err := backpackmod.UseManyGoods(tool, dsId)
if err != nil {
return stderr.ErrDbUpdateError
}
if modified != int64(len(dsId)) {
return stderr.Failure
}
}
// 新增AI脱衣记录
orderId = primitive.NewObjectID()
orderCreatedAt = time.Now()
if err = aichangefacemod.AddAiChangeFace(tool, orderId, orderCreatedAt, uid, pics, vidMod, debitAmt, debitIncome, discount, shareTitle, shareStatus); err != nil {
log.Error(fmt.Sprintf("Handle AiChangeface Generate InsertOnes error:%+v:", err), log.Any("uid", uid))
return stderr.ErrDbInsertError
}
return nil
}); err != nil {
if e, ok := err.(stderr.Code); ok {
return e
}
return stderr.ErrDbQueryError
}
common.Go(func() {
if err := aiautoser.SubmitVideo(orderId); err != nil {
log.Error("AI视频换脸自动处理失败", log.Any("id", orderId.Hex()), log.E(err))
}
})
return stderr.Success
}
func TotalDebit(coins int64, w *walletmod.Wallet) (amtDebit, income int64) {
var (
deAmt int64
deIncome int64
)
if w.Amount >= coins {
deAmt = coins
}
if w.Amount < coins && w.Amount+w.Income >= coins {
deAmt = w.Amount
deIncome = coins - w.Amount
}
return deAmt, deIncome
}
// ModList 获取模版列表
func ModList(uid uint64) (data *aichangefacevidmod.AppResponse, err error) {
var ret aichangefacevidmod.AppResponse
key := redisconst.AIModCache
str, err := appg.Redis.Get(key)
if err != nil {
log.Warn(fmt.Sprintf("用户ID:%d;缓存获取AI模版列表信息异常:%v", uid, err))
}
if str != nil {
if err = json.Unmarshal([]byte(*str), &ret); err == nil {
return &ret, nil
}
log.Warn(fmt.Sprintf("用户ID:%d;解析缓存数据异常:%v", uid, err))
}
mods, err := aichangefacevidmod.GetAllMods()
if err != nil {
return &ret, err
}
if len(mods) > 0 {
for _, m := range mods {
if m.ModuleType == aichangefacevidmod.AIUndress {
ret.AiUndressMod = append(ret.AiUndressMod, aichangefacevidmod.AiUndressMod{
ID: m.ID,
Cover: m.Cover,
})
}
if m.ModuleType == aichangefacevidmod.AIImgToVideo {
ret.AiImgToVideoMod = append(ret.AiImgToVideoMod, aichangefacevidmod.AiImgToVideoMod{
ID: m.ID,
Title: m.Title,
SceneType: m.SceneType,
Cover: m.Cover,
NewUrl: m.NewUrl,
})
}
if m.ModuleType == aichangefacevidmod.AITextToImage {
ret.AiTextToImgMod = append(ret.AiTextToImgMod, aichangefacevidmod.AiTextToImgMod{
ID: m.ID,
Cover: m.Cover,
Title: m.Title,
StyleType: m.HotValue,
})
}
//if m.ModuleType == aichangefacevidmod.AIVideoChangeFace {
// ret.AiChangeFaceVideoMod = append(ret.AiChangeFaceVideoMod, aichangefacevidmod.AiChangeMod{
// ID: m.ID,
// Title: m.Title,
// SourceURL: m.SourceURL,
// Cover: m.Cover,
// PlayTime: m.PlayTime,
// Type: m.Type,
// HotMark: m.HotMark,
// HotValue: m.HotValue,
// Coin: m.Coin,
// VipCoin: m.VipCoin,
// })
//}
//if m.ModuleType == aichangefacevidmod.AIImgChangeFace {
// ret.AiChangeFaceMod = append(ret.AiChangeFaceMod, aichangefacevidmod.AiChangeFaceMod{
// Title: m.Title,
// ID: m.ID,
// Cover: m.Cover,
// HotMark: m.HotMark,
// HotValue: m.HotValue,
// Coin: m.Coin,
// VipCoin: m.VipCoin,
// })
//}
}
}
common.Go(func() {
d, err := json.Marshal(ret)
if err != nil {
return
}
if err = appg.Redis.Set(key, d, 5*time.Minute); err != nil {
log.Warn(fmt.Sprintf("用户ID:%d;保存缓存数据异常:%v", uid, err))
}
})
return &ret, nil
}
type ModListV2Req struct {
Type int `json:"type" form:"type"` // 0-图片换脸 1-视频换脸
CategoryId string `json:"categoryId" form:"categoryId"` // 模版分类id,如果为空,则默认第一个模版分类
}
type ModListV2Resp struct {
CategoryList []*aiService.Category `json:"categoryList"`
TemplateList []*aiService.Template `json:"templateList"`
CategoryId string `json:"categoryId"` // 当前返回的分类id下的模版
}
// ModListV2 获取模版列表
func ModListV2(req ModListV2Req) (resp ModListV2Resp, err error) {
ai := aiService.NewAiService(
aiService.AppId(int(commod.KFK_APPID)),
aiService.Url(appg.Conf.URL.AiServer),
aiService.Redis(appg.Redis),
)
resp.CategoryList, resp.CategoryId, resp.TemplateList, err = ai.GetTemplateList(req.Type, req.CategoryId)
if err != nil {
return
}
return resp, nil
}
type ModInfoReq struct {
Id string `json:"id" form:"id"`
Type int `json:"type" form:"type"` // 1-ai图片换脸 2-ai视频换脸 3-ai脱衣 4-ai图生视频 5-ai绘画
}
type ModInfoResp struct {
AiUndressMod aichangefacevidmod.AiUndressMod `json:"aiUndressMod" bson:"aiUndressMod"` // AI脱衣模版
AiTextToImgMod aichangefacevidmod.AiTextToImgMod `json:"aiTextToImgMod" bson:"aiTextToImgMod"` // AI绘图模型
AiImgToVideoMod aichangefacevidmod.AiImgToVideoMod `json:"aiImgToVideoMod" bson:"aiImgToVideoMod"` // AI图生视频模型
AiChangeFaceMod aiService.Template `json:"aiChangeFaceMod" bson:"aiChangeFaceMod"` // Ai视频/图片换脸模型
}
func (p *ModInfoReq) GetInfo() (resp ModInfoResp, err error) {
id, _ := primitive.ObjectIDFromHex(p.Id)
if id.IsZero() {
return resp, errors.New("id无效")
}
if p.Type > 2 {
// 从本地的表里获取
m, err := aichangefacevidmod.GetModById(id)
if err != nil {
return resp, err
}
if m.ModuleType == aichangefacevidmod.AIUndress {
resp.AiUndressMod = aichangefacevidmod.AiUndressMod{
ID: m.ID,
Cover: m.Cover,
}
}
if m.ModuleType == aichangefacevidmod.AIImgToVideo {
resp.AiImgToVideoMod = aichangefacevidmod.AiImgToVideoMod{
ID: m.ID,
Cover: m.Cover,
NewUrl: m.NewUrl,
}
}
if m.ModuleType == aichangefacevidmod.AITextToImage {
resp.AiTextToImgMod = aichangefacevidmod.AiTextToImgMod{
ID: m.ID,
Cover: m.Cover,
Title: m.Title,
StyleType: m.HotValue,
}
}
return resp, nil
}
// 获取视频/图片换脸模版
ai := aiService.NewAiService(
aiService.AppId(int(commod.KFK_APPID)),
aiService.Url(appg.Conf.URL.AiServer),
aiService.Redis(appg.Redis),
)
template, err := ai.GetTemplate(id)
if err != nil {
log.Error("ai.GetTemplate fail", log.Any("id", id), log.E(err))
return
}
resp.AiChangeFaceMod = template
return
}