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

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
}