You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
144 lines
5.7 KiB
144 lines
5.7 KiB
package user
|
|
|
|
import (
|
|
"time"
|
|
"yunyan/comm"
|
|
"yunyan/lego/sys/log"
|
|
"yunyan/lego/sys/mysql"
|
|
"yunyan/pb"
|
|
)
|
|
|
|
// @Summary 使用资源请求
|
|
// @Description 上报一次用量(翻译秒数 / 会议秒数 / AI 对话字数)。服务端记原始统计,并按后台换算系数折算成算力做记账。
|
|
// @Tags User
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Param user body pb.UserUsagesReq true "使用资源请求"
|
|
// @Success 200 {object} comm.HttpResult{data=pb.UserUsagesResp} "成功返回"
|
|
// @Router /api/home/user_usages [post]
|
|
//
|
|
// 2026-09 起本接口**不再扣三个额度桶、不再因余额不足拒绝**——功能准入只看 VIP(见 comm.ResolveVip),
|
|
// 用量统一折算成算力(见 comm.ApplyComputeUsage):先扣设备赠送、再扣用户余额;
|
|
// 算力闸门(COMPUTE_GATE)默认关,余额不够也放行、差额记为超额。
|
|
//
|
|
// ⚠️ 客户端只上报**原始量**(秒 / 字符数),不上报算力:换算系数在后台可改,客户端自己算的话
|
|
// 改一次系数就要发一次版。客户端拿到的算力是本接口**返回**的。
|
|
func (this *apiComp) Usages(session comm.IUserSession, req *pb.UserUsagesReq) (resp *pb.UserUsagesResp, errdata *pb.ErrorData) {
|
|
var (
|
|
user *pb.DBUser
|
|
statistics *pb.DBUserStatistics
|
|
userlog *pb.DBUserUseLog
|
|
kind comm.ComputeUsageKind
|
|
raw int64
|
|
err error
|
|
)
|
|
user, err = this.module.model.findforid(session.GetUserId())
|
|
if err != nil {
|
|
errdata = &pb.ErrorData{Code: pb.ErrorCode_DBError, Message: err.Error()}
|
|
return
|
|
}
|
|
statistics, err = this.module.model.getStatistics(session.GetUserId())
|
|
if err != nil && err != mysql.ErrNoDocuments {
|
|
errdata = &pb.ErrorData{Code: pb.ErrorCode_DBError, Message: err.Error()}
|
|
return
|
|
}
|
|
userlog = &pb.DBUserUseLog{
|
|
Uid: session.GetUserId(),
|
|
Logtype: pb.UserLogType_UserConsume,
|
|
Ts: time.Now().Unix(),
|
|
}
|
|
// ── 原始统计照记(现有字段不动,看板与结算还在用) ──
|
|
switch req.Usagetype {
|
|
case pb.UsageType_Trade:
|
|
kind = comm.ComputeUsageTranslate
|
|
if v, ok := req.Usages["TRANSLATION_MODE_simultaneous"]; ok && v > 0 {
|
|
statistics.Trademodel1Num += 1
|
|
statistics.Trademodel1Time += v
|
|
raw += v
|
|
}
|
|
if v, ok := req.Usages["TRANSLATION_MODE_faceToFace"]; ok && v > 0 {
|
|
statistics.Trademodel2Num += 1
|
|
statistics.Trademodel2Time += v
|
|
raw += v
|
|
}
|
|
if v, ok := req.Usages["TRANSLATION_MODE_call"]; ok && v > 0 {
|
|
statistics.Trademodel3Num += 1
|
|
statistics.Trademodel3Time += v
|
|
raw += v
|
|
}
|
|
if v, ok := req.Usages["TRANSLATION_MODE_audioVideo"]; ok && v > 0 {
|
|
statistics.Trademodel4Num += 1
|
|
statistics.Trademodel4Time += v
|
|
raw += v
|
|
}
|
|
// 原实现把翻译秒数写进了 Addagentintegral(AI 那一列),这里改回正确的列。
|
|
userlog.Addtradesecond = -raw
|
|
case pb.UsageType_Meet:
|
|
kind = comm.ComputeUsageMeeting
|
|
if v, ok := req.Usages["MEETING_ASR_AUDIO_SECONDS"]; ok && v > 0 {
|
|
statistics.Meetnum += 1
|
|
statistics.Meettime += v
|
|
raw = v
|
|
userlog.Addmeetsecond = -v
|
|
}
|
|
case pb.UsageType_AI:
|
|
kind = comm.ComputeUsageAIChat
|
|
statistics.Aichatnum += 1
|
|
// 这两列名字里带 token,但存的是**字符数**(Unicode 码点),与大模型自己的 tokenizer 无关。
|
|
// 字段早就有、一直没人写;老客户端不传字数时 raw=0 → 本次不扣算力、只记一次。
|
|
statistics.Aichatuptoken += req.Upchars
|
|
statistics.Aichatdowntoken += req.Downchars
|
|
raw = req.Upchars + req.Downchars
|
|
userlog.Addagentintegral = -1
|
|
default:
|
|
errdata = &pb.ErrorData{Code: pb.ErrorCode_ReqParameterError, Message: "未知的用量类型"}
|
|
return
|
|
}
|
|
|
|
// ── 算力记账 ──
|
|
res, err := comm.ApplyComputeUsage(session.GetUserId(), kind, raw, userlog, statistics)
|
|
if err != nil {
|
|
this.module.Error("Usages: 算力记账失败",
|
|
log.Field{Key: "uid", Value: session.GetUserId()},
|
|
log.Field{Key: "kind", Value: int(kind)},
|
|
log.Field{Key: "raw", Value: raw},
|
|
log.Field{Key: "err", Value: err.Error()})
|
|
errdata = &pb.ErrorData{Code: pb.ErrorCode_DBError, Message: err.Error()}
|
|
return
|
|
}
|
|
if res.Rejected {
|
|
errdata = &pb.ErrorData{Code: pb.ErrorCode_ComputeNotEnough, Message: "算力不足"}
|
|
return
|
|
}
|
|
if err = this.module.model.updateStatistics(statistics); err != nil {
|
|
errdata = &pb.ErrorData{Code: pb.ErrorCode_DBError, Message: err.Error()}
|
|
return
|
|
}
|
|
if err = this.module.model.addIntegralLog(userlog); err != nil {
|
|
errdata = &pb.ErrorData{Code: pb.ErrorCode_DBError, Message: err.Error()}
|
|
return
|
|
}
|
|
// 资源消耗埋点(口径不变)
|
|
if this.module.analyze != nil {
|
|
switch req.Usagetype {
|
|
case pb.UsageType_Trade:
|
|
this.module.analyze.Report(&comm.StatEvent{Type: comm.StatEventTranslate, ProductId: user.Lastbindproductid, ChannelId: user.Lastbindchannelid, Uid: session.GetUserId(), Second: raw})
|
|
case pb.UsageType_Meet:
|
|
this.module.analyze.Report(&comm.StatEvent{Type: comm.StatEventMeeting, ProductId: user.Lastbindproductid, ChannelId: user.Lastbindchannelid, Uid: session.GetUserId(), Second: raw})
|
|
case pb.UsageType_AI:
|
|
this.module.analyze.Report(&comm.StatEvent{Type: comm.StatEventAiChat, ProductId: user.Lastbindproductid, ChannelId: user.Lastbindchannelid, Uid: session.GetUserId()})
|
|
}
|
|
}
|
|
// 兼容:老客户端按 usages[usagetype] 读「剩余量」。三桶已停用,这里回算力剩余合计而不是 0——
|
|
// 老包若在本地拿剩余量做闸门,回 0 会把它锁死。单位对不上是过渡期的已知代价。
|
|
left := res.DeviceLeft + res.UserLeft
|
|
resp = &pb.UserUsagesResp{
|
|
Usages: map[int32]int64{int32(req.Usagetype): left},
|
|
Compute: res.Compute,
|
|
Computedeviceleft: res.DeviceLeft,
|
|
Computeuserleft: res.UserLeft,
|
|
Over: res.Over,
|
|
}
|
|
return
|
|
}
|
|
|