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,540 @@
package ai_changeface_ser
import (
"91porn-server/common"
"91porn-server/common/db"
"91porn-server/common/httputil"
"91porn-server/common/log"
"91porn-server/common/stderr"
"91porn-server/models/commod"
"91porn-server/models/v/aichangefacemod"
"91porn-server/models/v/aichangefacevidmod"
"91porn-server/models/v/aiplazamod"
"91porn-server/models/v/backpackmod"
"91porn-server/models/v/txnmod"
"91porn-server/models/v/walletmod"
"91porn-server/web/proto"
"91porn-server/web/webg"
"fmt"
"strings"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
func List(id *primitive.ObjectID, req *aichangefacemod.ListRequest) ([]aichangefacemod.AiChangeFaceWebResp, int64, bool, error) {
limit := req.Limit()
acfs, count, err := aichangefacemod.WebList(id, req.Uid, req.Status, req.VidID, int(req.Skip()), int(limit+1))
if err != nil {
return nil, 0, false, err
}
hasNext := false
if len(acfs) > int(limit) {
acfs = acfs[:limit]
hasNext = true
}
acfsLen := len(acfs)
acfwrs := make([]aichangefacemod.AiChangeFaceWebResp, acfsLen)
for i, v := range acfs {
acfwrs[i] = aichangefacemod.AiChangeFaceWebResp{
ID: v.ID,
Uid: v.Uid,
Pic: v.Pic,
ModId: v.VidID,
ModUrl: v.ModVideo,
ModTitle: v.ModTitle,
Coin: v.Coin,
IncomeCoin: v.IncomeCoin,
Status: v.Status,
Cover: v.Cover,
Url: v.Url,
Remark: v.Remark,
Operator: v.Operator,
CreatedAt: v.CreatedAt,
UpdatedAt: v.UpdatedAt,
}
}
return acfwrs, count, hasNext, nil
}
func Update(act string, req *aichangefacemod.EditAiChangeFace) stderr.Code {
// 获取Ai记录
data, err := aichangefacemod.FindByID(nil, req.ID)
if err != nil {
return stderr.ErrDbQueryError
}
// 如果已经退款,直接返回,避免重复扣款
if *req.Status == aichangefacemod.StatusRefund && data.Status == aichangefacemod.StatusRefund {
return stderr.Success
}
switch *req.Status {
case aichangefacemod.StatusGenning: // 未完成或完成
if err = aichangefacemod.Update(nil, req.ID, req.Update(act)); err != nil {
return stderr.ErrDbUpdateError
}
return stderr.Success
case aichangefacemod.StatusComplete:
if req.Url == nil || *req.Url == "" {
return stderr.ChangeFaceSuccessWithoutUrl
}
if err = aichangefacemod.Update(nil, req.ID, req.Update(act)); err != nil {
return stderr.ErrDbUpdateError
}
return stderr.Success
case aichangefacemod.StatusRefund: // 退款
if err = webg.VideoDB.Trans(func(t *db.MongoTool) error {
var tl []txnmod.TransactionLog
updateSet, _ := req.Update(act)["$set"].(bson.M)
transitioned, transitionErr := aichangefacemod.TransitionStatus(t, req.ID, data.Status, aichangefacemod.StatusRefund, updateSet)
if transitionErr != nil {
return transitionErr
}
if !transitioned {
return nil
}
// 折扣券返还
if len(data.Discount) > 0 {
if _, err = backpackmod.UnuseManyGoods(t, data.Discount); err != nil {
return err
}
}
if data.Coin > 0 || data.IncomeCoin > 0 {
// 退款进入钱包余额
wallet, err := walletmod.ReturnAmountAndIncome(t, -data.Coin, -data.IncomeCoin, data.Uid)
if err != nil {
log.Error(fmt.Sprintf("Handle AiUnDress Edit walletmod.DebitAmount error:%+v:", err), log.Any("uid", data.Uid))
return err
}
if data.Coin > 0 {
tl = append(tl, txnmod.TransactionLog{
TransNo: primitive.NewObjectID(),
UID: data.Uid,
Amount: data.Coin,
ActualAmount: float64(data.Coin),
TranType: txnmod.AiChangefaceDebitGoldReturn.Key(),
TranTypeInt: int64(txnmod.AiChangefaceDebitGoldReturn),
Desc: fmt.Sprintf("AI换脸金币退返-%d", data.Coin),
RealAmount: walletmod.GetRealAmount(wallet),
})
}
if data.IncomeCoin > 0 {
tl = append(tl, txnmod.TransactionLog{
TransNo: primitive.NewObjectID(),
UID: data.Uid,
Amount: data.IncomeCoin,
ActualAmount: float64(data.IncomeCoin),
TranType: txnmod.AiChangefaceDebitIncomeGoldReturn.Key(),
TranTypeInt: int64(txnmod.AiChangefaceDebitIncomeGoldReturn),
Desc: fmt.Sprintf("AI换收益金币退返-%d", data.IncomeCoin),
RealAmount: walletmod.GetRealAmount(wallet),
})
}
}
if len(tl) > 0 {
txnErr := txnmod.InsertManyTransactionLog(t, tl)
if txnErr != nil {
log.Error(fmt.Sprintf("Handle AiChangeface Generate txnmod.InsertManyTransactionLog error:%+v:", txnErr), log.Any("uid", data.Uid))
return stderr.ErrDbQueryError
}
}
return nil
}); err != nil {
if e, ok := err.(stderr.Code); ok {
return e
}
return stderr.ErrDbQueryError
}
return stderr.Success
default:
return stderr.ErrParamError
}
}
func UpdateNew(act string, req *aichangefacemod.EditAiChangeFace) stderr.Code {
// 获取Ai记录
data, err := aichangefacemod.FindByID(nil, req.ID)
if err != nil {
return stderr.ErrDbQueryError
}
if *req.Status == aichangefacemod.StatusSubmit && data.Status == aichangefacemod.StatusSubmit {
return stderr.Success
}
if *req.Status == aichangefacemod.StatusRefund && data.Status == aichangefacemod.StatusRefund {
return stderr.Success
}
set := bson.M{}
set["status"] = req.Status
if req.Cover != nil {
set["cover"] = strings.TrimSpace(*req.Cover)
}
if req.Url != nil {
set["url"] = strings.TrimSpace(*req.Url)
}
if req.Remark != nil {
set["remark"] = *req.Remark
}
if len(set) == 0 { // 没有做任何修改
return stderr.Success
}
set["operator"] = act // 记录操作员
set["updatedAt"] = time.Now()
switch *req.Status {
case aichangefacemod.StatusGenning: // 未完成或完成
if err = aichangefacemod.Update(nil, req.ID, bson.M{"$set": set}); err != nil {
return stderr.ErrDbUpdateError
}
case aichangefacemod.StatusSubmit:
//提交订单到第三方
notifyURL, notifyErr := webg.AICallbackURL("/api/web/admin/ai/changeface/callback")
if notifyErr != nil {
log.Error("AI视频换脸回调地址配置异常", log.E(notifyErr))
return stderr.Failure
}
httpReq := proto.AiChangeFaceReq{
AppId: int(commod.KFK_APPID),
OrderNumAPP: data.ID.Hex(),
Title: data.ModTitle,
SourceImageUrl: data.Pic[0],
NotifyUrl: notifyURL,
}
url := webg.Conf.URL.AiServer + "/api/changeface/prd/changeFaceByVideo"
//第三方请求地址
var resp proto.AiChangeFaceResp
codeJ, err := httputil.DefaultClientPostJsonWithResp(&resp, url, nil, httpReq)
if err != nil {
log.Error("CreateChangeFace httputil.DefaultClientPostJsonWithResp err", log.E(err))
return stderr.Failure
}
if codeJ != 200 {
log.Error("CreateChangeFace req http code error", log.Any("code", codeJ))
return stderr.Failure
}
if resp.Code == 40000 {
return stderr.ChangeFaceNoModule
}
if err = aichangefacemod.Update(nil, req.ID, bson.M{"$set": set}); err != nil {
return stderr.ErrDbUpdateError
}
case aichangefacemod.StatusRefund: // 退款
if err = webg.VideoDB.Trans(func(t *db.MongoTool) error {
transitioned, transitionErr := aichangefacemod.TransitionStatus(t, req.ID, data.Status, aichangefacemod.StatusRefund, set)
if transitionErr != nil {
return transitionErr
}
if !transitioned {
return nil
}
// 折扣券返还
if len(data.Discount) > 0 {
if _, err = backpackmod.UnuseManyGoods(t, data.Discount); err != nil {
return err
}
}
// 退款
if data.Coin == 0 && data.IncomeCoin == 0 { // 总金额为0则不需要退款
return nil
}
// 退款进入钱包余额
wallet, err := walletmod.ReturnAmountAndIncome(t, -data.Coin, -data.IncomeCoin, data.Uid)
if err != nil {
log.Error(fmt.Sprintf("Handle AiUnDress Edit walletmod.DebitAmount error:%+v:", err), log.Any("uid", data.Uid))
return err
}
// 添加退款记录
totalCoin := data.Coin + data.IncomeCoin
if err = txnmod.InsertTransactionLog(t, &txnmod.TransactionLog{
TransNo: primitive.NewObjectID(),
UID: data.Uid,
Amount: totalCoin,
ActualAmount: float64(totalCoin),
TranType: txnmod.AiChangefaceDebitGoldReturn.Key(),
TranTypeInt: int64(txnmod.AiChangefaceDebitGoldReturn),
Desc: fmt.Sprintf("AI换脸金币退返-%d", totalCoin),
RealAmount: walletmod.GetRealAmount(wallet),
}); err != nil {
log.Error(fmt.Sprintf("Handle AiChangeface Edit txnmod.InsertTransactionLog error:%+v:", err), log.Any("uid", data.Uid))
return err
}
return nil
}); err != nil {
if e, ok := err.(stderr.Code); ok {
return e
}
return stderr.ErrDbQueryError
}
default:
return stderr.ErrParamError
}
return stderr.Success
}
func AddMod(acfv aichangefacevidmod.AddAiChangeFaceVidReq) error {
now := time.Now()
return aichangefacevidmod.AddAiChangeFaceVid(aichangefacevidmod.AiChangeFaceVidMod{
Title: acfv.Title,
SourceURL: acfv.SourceURL,
Status: acfv.Status,
PlayTime: acfv.PlayTime,
Cover: acfv.Cover,
Type: acfv.Type,
NewUrl: acfv.NewUrl,
Coin: acfv.Coin,
VipCoin: acfv.VipCoin,
StyleType: acfv.StyleType,
ModuleType: acfv.ModuleType,
HotMark: acfv.HotMark,
HotValue: acfv.HotValue,
Sort: acfv.Sort,
SceneType: acfv.SceneType,
CreatedAt: now,
UpdatedAt: now,
})
}
func UpdateMod(eacfv aichangefacevidmod.EditAiChangeFaceVid) error {
set := bson.M{}
if eacfv.Title != nil {
set["title"] = *eacfv.Title
}
if eacfv.SourceURL != nil {
set["sourceURL"] = *eacfv.SourceURL
}
if eacfv.Status != nil {
set["status"] = *eacfv.Status
}
if eacfv.NewUrl != nil {
set["newUrl"] = *eacfv.NewUrl
}
if eacfv.PlayTime != nil {
set["playTime"] = *eacfv.PlayTime
}
if eacfv.Cover != nil {
set["cover"] = *eacfv.Cover
}
if eacfv.Type != nil {
set["type"] = *eacfv.Type
}
if eacfv.Coin != nil {
set["coin"] = *eacfv.Coin
}
if eacfv.VipCoin != nil {
set["vipCoin"] = *eacfv.VipCoin
}
if eacfv.ModuleType != nil {
set["moduleType"] = *eacfv.ModuleType
}
if eacfv.HotValue != nil {
set["hotValue"] = *eacfv.HotValue
}
if eacfv.HotMark != nil {
set["hotMark"] = *eacfv.HotMark
}
if eacfv.Sort != nil {
set["sort"] = *eacfv.Sort
}
if eacfv.SceneType != nil {
set["sceneType"] = *eacfv.SceneType
}
if len(set) == 0 {
return nil
}
set["updatedAt"] = time.Now()
return aichangefacevidmod.UpdateAiChangeFaceVid(eacfv.ID, bson.M{"$set": set})
}
func ModList(req *aichangefacevidmod.WebListRequest) (interface{}, stderr.Code) {
var (
data = map[string]interface{}{
"list": []interface{}{},
"count": 0,
}
err error
)
if err != nil {
return nil, stderr.ErrParamError
}
count, err := aichangefacevidmod.CountDocument(req.Filter())
if err != nil {
return nil, stderr.ErrDbQueryError
}
if count == 0 {
return data, stderr.Success
}
list, err := aichangefacevidmod.QueryAllDocument(req.Filter(), req.Options())
if err != nil {
return data, stderr.ErrDbQueryError
}
data["count"] = count
data["list"] = list
return data, stderr.Success
}
func CallBackOrder(req *aichangefacevidmod.CallbackReq) stderr.Code {
//根据订单号更新ai换脸订单
id, _ := primitive.ObjectIDFromHex(req.AppOrderNum)
//查询该笔订单是否存在
aichangeFaceInfo, err := aichangefacemod.FindByID(nil, id)
if aichangeFaceInfo == nil || err != nil {
return stderr.ErrDbQueryError
}
oldOrderTime := time.Date(2025, 9, 15, 0, 0, 0, 0, time.Local)
if aichangeFaceInfo.CreatedAt.Before(oldOrderTime) &&
(aichangeFaceInfo.Status == aichangefacemod.StatusComplete || aichangeFaceInfo.Status == aichangefacemod.StatusRefund) {
// 以前的单子如果处理过了就新下资源和状态
set := bson.M{"url": strings.TrimSpace(req.ImgUrl)}
if req.Msg == "操作成功" {
set["status"] = aichangefacemod.StatusComplete
} else {
set["remark"] = req.Msg
set["status"] = aichangefacemod.StatusRefund
}
_ = aichangefacemod.Update(nil, id, bson.M{"$set": set})
return stderr.Success
}
if aichangeFaceInfo.Status != aichangefacemod.StatusSubmit {
log.Error("AI视频换脸-重复回调或者状态不正确", log.Any("id", aichangeFaceInfo.ID.Hex()), log.Any("status", aichangeFaceInfo.Status))
return stderr.Success
}
var status = aichangefacemod.StatusComplete
//处理金币退款以及后续操作
set := bson.M{}
if req.ImgUrl != "" {
set["url"] = strings.TrimSpace(req.ImgUrl)
}
if req.Msg != "操作成功" {
set["remark"] = req.Msg
status = aichangefacemod.StatusRefund
}
set["status"] = status
set["updatedAt"] = time.Now()
switch status {
case aichangefacemod.StatusComplete:
transitioned, updateErr := aichangefacemod.TransitionStatus(nil, id, aichangefacemod.StatusSubmit, aichangefacemod.StatusComplete, set)
if updateErr != nil {
return stderr.ErrDbUpdateError
}
if !transitioned {
return stderr.Success
}
common.Go(func() {
if aichangeFaceInfo.ShareStatus != 1 {
return
}
// 新增一个分享
alPlaza := aiplazamod.AiPlaza{
Type: 2,
OrderId: aichangeFaceInfo.ID,
Template: aichangeFaceInfo.VidID.Hex(),
Uid: aichangeFaceInfo.Uid,
Title: aichangeFaceInfo.ShareTitle,
OriginalImage: "",
OriginalVideo: aichangeFaceInfo.ModVideo,
OriginalVideoCover: aichangeFaceInfo.ModCover,
OriginContent: "",
GenerateVideo: req.ImgUrl,
GenerateVideoCover: aichangeFaceInfo.Pic[0],
Status: aiplazamod.DefaultStatus,
}
if len(aichangeFaceInfo.Pic) > 0 {
alPlaza.GenerateVideoCover = aichangeFaceInfo.Pic[0]
}
_, err = aiplazamod.Insert(nil, alPlaza)
if err != nil {
log.Error("新增ai广场帖子失败", log.Any("alPlaza", alPlaza), log.E(err))
return
}
})
return stderr.Success
case aichangefacemod.StatusRefund: // 退款
if err = webg.VideoDB.Trans(func(t *db.MongoTool) error {
transitioned, transitionErr := aichangefacemod.TransitionStatus(t, id, aichangefacemod.StatusSubmit, aichangefacemod.StatusRefund, set)
if transitionErr != nil {
return transitionErr
}
if !transitioned {
return nil
}
// 折扣券返还
if len(aichangeFaceInfo.Discount) > 0 {
if _, err = backpackmod.UnuseManyGoods(t, aichangeFaceInfo.Discount); err != nil {
return err
}
}
// 退款
if aichangeFaceInfo.Coin == 0 && aichangeFaceInfo.IncomeCoin == 0 { // 总金额为0则不需要退款
return nil
}
// 退款进入钱包余额
wallet, err := walletmod.ReturnAmountAndIncome(t, -aichangeFaceInfo.Coin, -aichangeFaceInfo.IncomeCoin, aichangeFaceInfo.Uid)
if err != nil {
log.Error(fmt.Sprintf("Handle AiUnDress Edit walletmod.DebitAmount error:%+v:", err), log.Any("uid", aichangeFaceInfo.Uid))
return err
}
// 添加退款记录
totalCoin := aichangeFaceInfo.Coin + aichangeFaceInfo.IncomeCoin
if err = txnmod.InsertTransactionLog(t, &txnmod.TransactionLog{
TransNo: primitive.NewObjectID(),
UID: aichangeFaceInfo.Uid,
Amount: totalCoin,
ActualAmount: float64(totalCoin),
TranType: txnmod.AiChangefaceDebitGoldReturn.Key(),
TranTypeInt: int64(txnmod.AiChangefaceDebitGoldReturn),
Desc: fmt.Sprintf("AI换脸金币退返-%d", totalCoin),
RealAmount: walletmod.GetRealAmount(wallet),
}); err != nil {
log.Error(fmt.Sprintf("Handle AiChangeface Edit txnmod.InsertTransactionLog error:%+v:", err), log.Any("uid", aichangeFaceInfo.Uid))
return err
}
return nil
}); err != nil {
if e, ok := err.(stderr.Code); ok {
return e
}
return stderr.ErrDbQueryError
}
return stderr.Success
default:
return stderr.ErrParamError
}
}
type BatchUpdateReq struct {
IDS []string `json:"ids" bson:"ids"` // AI模版IDS
MID string `json:"mid" bson:"mid"` // 模块ID
}
func (q *BatchUpdateReq) Filter() primitive.M {
filter := bson.M{}
var ids []primitive.ObjectID
for _, id := range q.IDS {
objectID, _ := primitive.ObjectIDFromHex(id)
ids = append(ids, objectID)
}
if len(ids) > 0 {
filter["_id"] = bson.M{"$in": ids}
}
return filter
}
func (q *BatchUpdateReq) UpdateCond() primitive.M {
cond := bson.M{}
cond["mid"] = q.MID
cond["updatedAt"] = time.Now()
return cond
}
func (q *BatchUpdateReq) BatchUpdateMod() error {
return aichangefacevidmod.UpdateManyByCond(q.Filter(), bson.M{"$set": q.UpdateCond()})
}
+41
View File
@@ -0,0 +1,41 @@
package ai_changeface_ser
import "91porn-server/models/v/aiimagetovideomod"
type AllSceneListReq struct {
}
type AllSceneResp struct {
Name string `json:"name" bson:"name"` // 名称
Value int `json:"value" bson:"value"` // 值
}
func (in *AllSceneListReq) All() (data []AllSceneResp, err error) {
var ret []AllSceneResp
ret = append(ret, AllSceneResp{
Name: aiimagetovideomod.SceneTagLiftShirt.String(),
Value: int(aiimagetovideomod.SceneTagLiftShirt),
}, AllSceneResp{
Name: aiimagetovideomod.SceneTagTearClothes.String(),
Value: int(aiimagetovideomod.SceneTagTearClothes),
}, AllSceneResp{
Name: aiimagetovideomod.SceneTagCumOnFace.String(),
Value: int(aiimagetovideomod.SceneTagCumOnFace),
}, AllSceneResp{
Name: aiimagetovideomod.SceneOFace.String(),
Value: int(aiimagetovideomod.SceneOFace),
}, AllSceneResp{
Name: aiimagetovideomod.SceneTagKiss.String(),
Value: int(aiimagetovideomod.SceneTagKiss),
}, AllSceneResp{
Name: aiimagetovideomod.SceneTagSuckBreasts.String(),
Value: int(aiimagetovideomod.SceneTagSuckBreasts),
}, AllSceneResp{
Name: aiimagetovideomod.SceneRevealed.String(),
Value: int(aiimagetovideomod.SceneRevealed),
}, AllSceneResp{
Name: aiimagetovideomod.SceneLargerChest.String(),
Value: int(aiimagetovideomod.SceneLargerChest),
})
return ret, nil
}