Files
aggregate_translate_server/app/admin/service/tm_member.go
hucan 8ae43bfba9
Some checks failed
Build / build (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
build / Build (push) Has been cancelled
GitHub Actions Mirror / mirror_to_gitee (push) Has been cancelled
GitHub Actions Mirror / mirror_to_gitlab (push) Has been cancelled
Issue Close Require / issue-close-require (push) Has been cancelled
Issue Check Inactive / issue-check-inactive (push) Has been cancelled
1
2025-06-29 00:36:30 +08:00

681 lines
19 KiB
Go
Raw 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 service
import (
"errors"
"fmt"
"strconv"
"strings"
"time"
"github.com/bytedance/sonic"
"github.com/go-admin-team/go-admin-core/sdk/service"
"github.com/go-redis/redis/v8"
"github.com/jinzhu/copier"
"gorm.io/gorm"
"go-admin/app/admin/models"
"go-admin/app/admin/service/dto"
"go-admin/common/actions"
cDto "go-admin/common/dto"
rediskey "go-admin/common/redis_key"
"go-admin/utils/redishelper"
"go-admin/utils/utility"
)
type TmMember struct {
service.Service
}
func (e TmMember) GetUserPlatforms(userId int, resp *[]dto.TmMemberPlatformFrontedResp) error {
var datas []models.TmMemberPlatform
var memberAccount models.TmMember
if err := e.Orm.Model(&memberAccount).
Find(&memberAccount).Error; err != nil {
return err
}
err := e.Orm.Model(&models.TmMemberPlatform{}).
Where(" member_id=?", memberAccount.Id).
Find(&datas).Error
if err != nil {
return err
}
platformService := TmPlatform{Service: e.Service}
for _, item := range datas {
dataItem := dto.TmMemberPlatformFrontedResp{}
copier.Copy(&dataItem, &item)
platform, _ := platformService.GetByKey(item.PlatformKey)
dataItem.ApiKey = memberAccount.ApiKey
dataItem.Name = platform.ShowName
dataItem.Price = int(platform.Price.IntPart())
dataItem.RemainChars, _ = e.GetRemainCount(item.PlatformKey, dataItem.ApiKey)
*resp = append(*resp, dataItem)
}
return nil
}
// GetPage 获取TmMember列表
func (e *TmMember) GetPage(c *dto.TmMemberGetPageReq, p *actions.DataPermission, list *[]dto.TmMemberResp, count *int64) error {
var err error
var data models.TmMember
var datas []models.TmMember
err = e.Orm.Model(&data).
Joins("join sys_user on sys_user.user_id = tm_member.user_id ").
Scopes(
cDto.MakeCondition(c.GetNeedSearch()),
cDto.Paginate(c.GetPageSize(), c.GetPageIndex()),
actions.Permission(data.TableName(), p),
).
Select("tm_member.id,tm_member.api_key,tm_member.total_chars,tm_member.remain_chars,tm_member.status,tm_member.created_at,sys_user.status as userStatus,sys_user.nick_name,sys_user.user_id").
Find(&datas).Limit(-1).Offset(-1).
Count(count).Error
if err != nil {
e.Log.Errorf("TmMemberService GetPage error:%s \r\n", err)
return err
}
userIds := []int{}
for _, item := range datas {
if !utility.ContainsInt(userIds, item.UserId) {
userIds = append(userIds, item.UserId)
}
}
platformService := TmPlatform{Service: e.Service}
userService := SysUser{Service: e.Service}
users, _ := userService.GetByIds(userIds)
activeList, _ := platformService.GetActiveList()
for _, item := range datas {
dataItem := dto.TmMemberResp{}
copier.Copy(&dataItem, &item)
// count, _ := e.GetRemainCount(,dataItem.ApiKey)
dataItem.ApiKey = utility.DesensitizeGeneric(dataItem.ApiKey, 2, 2, '*')
// dataItem.RemainChars = count
for _, user := range users {
if user.UserId == item.UserId {
dataItem.UserStatus, _ = strconv.Atoi(user.Status)
}
}
for _, platform := range activeList {
platformItem := dto.TmMemberPlatformResp{}
platformItem.Name = platform.Name
platformItem.RemainChars, _ = e.GetRemainCount(platform.Code, item.ApiKey)
dataItem.Platforms = append(dataItem.Platforms, platformItem)
}
*list = append(*list, dataItem)
}
return nil
}
// Get 获取TmMember对象
func (e *TmMember) Get(d *dto.TmMemberGetReq, p *actions.DataPermission, model *models.TmMember) error {
var data models.TmMember
err := e.Orm.Model(&data).
Scopes(
actions.Permission(data.TableName(), p),
).
First(model, d.GetId()).Error
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
err = errors.New("查看对象不存在或无权查看")
e.Log.Errorf("Service GetTmMember error:%s \r\n", err)
return err
}
if err != nil {
e.Log.Errorf("db error:%s", err)
return err
}
return nil
}
// Insert 创建TmMember对象
func (e *TmMember) Insert(c *dto.TmMemberInsertReq, data *models.TmMember) error {
var err error
c.Generate(data)
apiKey, err := utility.GenerateBase62Key(32)
if err != nil {
return errors.New("生成apiKey失败")
}
data.ApiKey = apiKey
data.RemainChars = 10000
data.TotalChars = 10000
err = e.Orm.Create(data).Error
if err != nil {
e.Log.Errorf("TmMemberService Insert error:%s \r\n", err)
return err
}
return nil
}
// SaveCache 保存缓存
func (e *TmMember) SaveCache(data *models.TmMember) error {
key := fmt.Sprintf(rediskey.TM_MEMBER_BY_KEY, data.ApiKey)
val, err := sonic.MarshalString(data)
if err != nil {
return err
}
redishelper.DefaultRedis.SetString(key, val)
return nil
}
// SaveAllCache 保存所有缓存
func (e *TmMember) SaveAllCache() error {
var list []models.TmMember
if err := e.Orm.Model(&models.TmMember{}).
Joins("join sys_user on sys_user.user_id = tm_member.user_id").
Find(&list).Error; err != nil {
return err
}
for _, item := range list {
key := fmt.Sprintf(rediskey.TM_MEMBER_BY_KEY, item.ApiKey)
// remainKey := fmt.Sprintf(rediskey.TM_MEMBER_REMAIN_COUNT, item.ApiKey)
val, err := sonic.MarshalString(item)
if err != nil {
return err
}
redishelper.DefaultRedis.SetString(key, val)
// redishelper.DefaultRedis.SetString(remainKey, strconv.Itoa(item.RemainChars))
}
return nil
}
// Update 修改TmMember对象
func (e *TmMember) Update(c *dto.TmMemberUpdateReq, p *actions.DataPermission) error {
var err error
var data = models.TmMember{}
e.Orm.Scopes(
actions.Permission(data.TableName(), p),
).First(&data, c.GetId())
c.Generate(&data)
db := e.Orm.Save(&data)
if err = db.Error; err != nil {
e.Log.Errorf("TmMemberService Save error:%s \r\n", err)
return err
}
if db.RowsAffected == 0 {
return errors.New("无权更新该数据")
}
return nil
}
// Remove 删除TmMember
func (e *TmMember) Remove(d *dto.TmMemberDeleteReq, p *actions.DataPermission) error {
var data models.TmMember
db := e.Orm.Model(&data).
Scopes(
actions.Permission(data.TableName(), p),
).Delete(&data, d.GetId())
if err := db.Error; err != nil {
e.Log.Errorf("Service RemoveTmMember error:%s \r\n", err)
return err
}
if db.RowsAffected == 0 {
return errors.New("无权删除该数据")
}
return nil
}
func (e *TmMember) RemoveByUserIds(userIds []int) error {
data := make([]models.TmMember, 0)
if err := e.Orm.Model(&data).Where("user_id in ?", userIds).Find(&data).Error; err != nil {
return err
}
if err := e.Orm.Model(models.TmMember{}).Delete(&models.TmMember{}, "user_id in ?", userIds).Error; err != nil {
return err
}
for _, item := range data {
key := fmt.Sprintf(rediskey.TM_MEMBER_BY_KEY, item.ApiKey)
remainKey := fmt.Sprintf(rediskey.TM_MEMBER_REMAIN_COUNT, item.ApiKey, "*")
scanKeys, _ := redishelper.DefaultRedis.ScanKeys(remainKey)
redishelper.DefaultRedis.DeleteString(key)
for _, s := range scanKeys {
redishelper.DefaultRedis.DeleteString(s)
}
}
return nil
}
// SyncMemberRemain 同步剩余字符
func (e *TmMember) SyncMemberRemain() error {
scanKeys, err := redishelper.DefaultRedis.ScanKeys(fmt.Sprintf("%s*", rediskey.TM_MEMBER_REMAIN_COUNT_PURE))
if err != nil {
return err
}
members := e.getCacheMembers()
datas := make([]models.TmMemberPlatform, 0)
for _, key := range scanKeys {
items := strings.Split(key, ":")
apiKey := items[len(items)-2]
platform := items[len(items)-1]
val, err := redishelper.DefaultRedis.GetString(key)
remainCount, err1 := strconv.Atoi(val)
if err != nil || err1 != nil {
e.Log.Errorf("TmMemberService SyncMemberRemain GetString error:%s \r\n err1:%s \r\n", err, err1)
continue
}
if member := members[apiKey]; member.Id > 0 {
item := models.TmMemberPlatform{}
item.Id = member.Id
item.RemainingCharacter = remainCount
item.PlatformKey = platform
datas = append(datas, item)
}
}
arrayData := utility.SplitSlice(datas, 1000)
for _, dataBatch := range arrayData {
// 遍历当前批次的所有记录,为每条记录单独执行 UPDATE
for _, record := range dataBatch {
stmt := `
UPDATE tm_member_platform
SET
remaining_character = ?,
updated_at = NOW()
WHERE platform_key = ? AND member_id = ?;
`
args := []interface{}{
record.RemainingCharacter,
record.PlatformKey,
record.Id,
}
// 执行单个 UPDATE 语句
if err := e.Orm.Exec(stmt, args...).Error; err != nil {
// 记录错误,但继续处理批次中的其他记录
e.Log.Errorf("TmMemberService SyncMemberRemain single Exec for PlatformKey %s, MemberID %d error: %s \r\n", record.PlatformKey, record.Id, err)
}
}
}
return nil
}
// SyncMemberDailyUsage 同步每日使用字符
func (e *TmMember) SyncMemberDailyUsage() error {
key := fmt.Sprintf("%s:%s*", rediskey.TM_MEMBER_DAILY_COUNT_PURE, time.Now().AddDate(0, 0, -1).Format("20060102"))
todayKey := fmt.Sprintf("%s:%s*", rediskey.TM_MEMBER_DAILY_COUNT_PURE, time.Now().Format("20060102"))
dailys := make([]models.TmMemberDailyUsage, 0)
members := e.getCacheMembers()
keys, err := redishelper.DefaultRedis.ScanKeys(key)
if err != nil {
e.Log.Errorf("TmMemberService SyncMemberDailyUsage ScanKeys error:%s \r\n", err)
return err
}
todayKeys, err := redishelper.DefaultRedis.ScanKeys(todayKey)
if err != nil {
e.Log.Errorf("TmMemberService SyncMemberDailyUsage ScanKeys error:%s \r\n", err)
return err
}
e.loadSyncData(keys, &members, &dailys)
e.loadSyncData(todayKeys, &members, &dailys)
arrayData := utility.SplitSlice(dailys, 1000)
for _, dataBatch := range arrayData {
// 构建批量插入 SQL
var (
valueStrings []string // 存储 ?, ?, ?, ? 这样的占位符字符串
valueArgs []interface{} // 存储所有参数值
)
// 循环当前批次的数据,构建 SQL 片段和参数
for _, record := range dataBatch {
// 为每一条记录添加 VALUES (?, ?, ?, ?) 部分
valueStrings = append(valueStrings, "(?, ?,?, ?)")
// 按照 SQL 语句中字段的顺序添加参数
valueArgs = append(valueArgs, record.MemberId)
valueArgs = append(valueArgs, record.PlatformId)
valueArgs = append(valueArgs, record.Date) // time.Time 会被 SQL 驱动正确处理为 DATE 类型
valueArgs = append(valueArgs, record.UseChars)
}
// 拼接完整的 SQL 语句
// 核心的 INSERT ... ON DUPLICATE KEY UPDATE 部分
stmt := fmt.Sprintf(`
INSERT INTO tm_member_daily_usage (member_id,platform_id, date, use_chars)
VALUES %s
ON DUPLICATE KEY UPDATE use_chars = VALUES(use_chars), updated_at = NOW();
`, strings.Join(valueStrings, ",")) // 用逗号连接所有的 VALUES 部分
// 执行批量 SQL
if execErr := e.Orm.Exec(stmt, valueArgs...).Error; execErr != nil {
e.Log.Errorf("TmMemberService SyncMemberDailyUsage Exec error:%s \r\n", execErr)
}
}
return nil
}
// 获取缓存memebr
func (e *TmMember) getCacheMembers() map[string]models.TmMember {
memberVals, _ := redishelper.DefaultRedis.GetAllKeysAndValues(fmt.Sprintf(rediskey.TM_MEMBER_BY_KEY, "*"))
members := map[string]models.TmMember{}
for _, val := range memberVals {
var member models.TmMember
err := sonic.UnmarshalString(val, &member)
if err != nil {
e.Log.Errorf("TmMemberService SyncMemberDailyUsage UnmarshalString error:%s \r\n", err)
continue
}
members[member.ApiKey] = member
}
return members
}
// 加载同步数据
func (e *TmMember) loadSyncData(keys []string, members *map[string]models.TmMember, dailys *[]models.TmMemberDailyUsage) {
platformService := TmPlatform{Service: e.Service}
for _, key := range keys {
items := strings.Split(key, ":")
apiKey := items[len(items)-2]
platform := items[len(items)-1]
if apiKey == "" {
continue
}
platformEntity, _ := platformService.GetByKey(platform)
if platformEntity.Id <= 0 {
e.Log.Error("TmMemberService SyncMemberDailyUsage platform not found", platform)
continue
}
keyVal, _ := redishelper.DefaultRedis.GetString(key)
count, err := strconv.Atoi(keyVal)
if err != nil {
e.Log.Errorf("TmMemberService SyncMemberDailyUsage GetString error:%s \r\n", err)
continue
}
if member := (*members)[apiKey]; member.Id > 0 {
date, err := time.Parse("20060102", items[1])
if err != nil {
e.Log.Errorf("同步每日使用字符 日期转换失败 Parse error:%s \r\n", err)
continue
}
*dailys = append(*dailys, models.TmMemberDailyUsage{
MemberId: (*members)[apiKey].Id,
UseChars: count,
Date: date,
PlatformId: platformEntity.Id,
})
}
}
}
// func (e *TmMember) GetMyApiKey(userId int) (dto.TranslateUserInfoResp, error) {
// var data models.TmMember
// resp := dto.TranslateUserInfoResp{}
// if err := e.Orm.Model(&data).Where("user_id = ?", userId).First(&data).Error; err != nil {
// e.Log.Errorf("TmMemberService GetMyApiKey error:%s \r\n", err)
// return resp, nil
// }
// var err error
// resp.UserApiKey = data.ApiKey
// resp.RemainChars, err = e.GetRemainCount(data.ApiKey)
// if err != nil {
// e.Log.Errorf("转换类型失败,error:%v", err)
// }
// return resp, nil
// }
// GetTranslateStatistic 获取翻译统计
func (e *TmMember) GetTranslateStatistic(userId int, list *[]dto.TranslateStatisticResp) error {
endDate := time.Now().Format("2006-01-02")
startDate := time.Now().AddDate(0, 0, -14).Format("2006-01-02")
var datas []models.TmMemberDailyUsage
if err := e.Orm.Model(&models.TmMemberDailyUsage{}).
Joins("join tm_member on tm_member.id = tm_member_daily_usage.member_id").
Where("tm_member.user_id = ? and date >= ? and date <= ?", userId, startDate, endDate).Find(&datas).Error; err != nil {
e.Log.Errorf("个人翻译统计 获取统计数据失败 error:%s \r\n", err)
return err
}
usageMap := make(map[string]int)
for _, usage := range datas {
// 如果同一天有多条记录(例如来自不同平台),则累加其字符数
usageMap[usage.Date.Format("2006-01-02")] += int(usage.UseChars)
}
resultList := make([]dto.TranslateStatisticResp, 0, 15)
currentDate := time.Now().AddDate(0, 0, -14) // 从15天前的日期开始
for i := 0; i < 15; i++ {
dateStr := currentDate.Format("2006-01-02")
// 从 map 中获取当天的总字符数,如果 map 中没有则默认为0
totalChars := usageMap[dateStr]
// 将数据添加到结果列表中
resultList = append(resultList, dto.TranslateStatisticResp{
TotalCount: totalChars,
Date: dateStr,
})
// 移动到下一天
currentDate = currentDate.AddDate(0, 0, 1)
}
// 将生成的列表赋值给传入的指针,以便调用者获取结果
*list = resultList
return nil
}
// 字符数充值
func (e *TmMember) Recharge(req *dto.TmMemberRechargeReq, p *actions.DataPermission, userId int) error {
data := models.TmMember{}
if err := e.GetById(req.Id, &data); err != nil {
return err
}
record := models.TmRechargeLog{}
record.UserId = data.UserId
record.MemberId = data.Id
record.CreateBy = userId
record.CreatedAt = time.Now()
record.TotalChars = req.TotalChars * 10000
if err := e.IncrBy(req.PlatformCode, data.ApiKey, record.TotalChars); err != nil {
e.Log.Errorf("TmMemberService Recharge IncrBy error:%s \r\n", err)
return errors.New("充值失败")
}
if err := e.Orm.Model(record).Create(&record).Error; err != nil {
if err2 := e.DecrBy(req.PlatformCode, data.ApiKey, record.TotalChars); err2 != nil {
e.Log.Errorf("充值失败 缓存重置失败 error:%s \r\n", err2)
}
e.Log.Errorf("TmMemberService Recharge Create error:%s \r\n", err)
return errors.New("充值失败")
}
return nil
}
// 获取可用字符数
func (e *TmMember) GetRemainCount(platformKey, apiKey string) (int, error) {
key := fmt.Sprintf(rediskey.TM_MEMBER_REMAIN_COUNT, apiKey, platformKey)
val, err := redishelper.DefaultRedis.GetString(key)
result := 0
if err != nil && !errors.Is(err, redis.Nil) {
return 0, err
}
if val != "" {
result, err = strconv.Atoi(val)
if err != nil {
return 0, err
}
}
if result == 0 {
var data models.TmMember
if err := e.Orm.Model(&data).Where("api_key = ?", apiKey).First(&data).Error; err != nil {
return 0, err
}
result = data.RemainChars
redishelper.DefaultRedis.SetString(key, strconv.Itoa(result))
}
return result, nil
}
// 增加字符
func (e *TmMember) IncrBy(platformKey, apiKey string, totalChars int) error {
remainCountKey := fmt.Sprintf(rediskey.TM_MEMBER_REMAIN_COUNT, apiKey, platformKey)
return redishelper.DefaultRedis.IncrBy(remainCountKey, int64(totalChars)).Err()
}
// 扣除字符
func (e *TmMember) DecrBy(platformKey, apiKey string, totalChars int) error {
remainCountKey := fmt.Sprintf(rediskey.TM_MEMBER_REMAIN_COUNT, apiKey, platformKey)
return redishelper.DefaultRedis.DecrBy(remainCountKey, int64(totalChars)).Err()
}
// 根据id获取数据
func (e *TmMember) GetById(id int, data *models.TmMember) error {
if err := e.Orm.Model(data).Where("id = ?", id).First(data).Error; err != nil {
return err
}
return nil
}
// 修改翻译用户状态
func (e TmMember) ChangeStatus(req *dto.TmMemberChangeStatusReq, p *actions.DataPermission) error {
var err error
var data = models.TmMember{}
e.Orm.Scopes(
actions.Permission(data.TableName(), p),
).Where("id = ?", req.Id).First(&data)
db := e.Orm.Model(data).Update("status", req.Status)
if err = db.Error; err != nil {
e.Log.Errorf("TmMemberService Save error:%s \r\n", err)
return err
}
if db.RowsAffected == 0 {
return errors.New("无权更新该数据")
}
data.Status = req.Status
val, _ := sonic.MarshalString(data)
if val != "" {
key := fmt.Sprintf(rediskey.TM_MEMBER_BY_KEY, data.ApiKey)
redishelper.DefaultRedis.SetString(key, val)
}
return nil
}
// 根据api key获取数据
func (e *TmMember) GetByKey(apiKey string) (models.TmMember, error) {
key := fmt.Sprintf(rediskey.TM_MEMBER_BY_KEY, apiKey)
val, _ := redishelper.DefaultRedis.GetString(key)
result := models.TmMember{}
if val != "" {
sonic.UnmarshalString(val, &result)
}
if result.Id == 0 {
if err := e.Orm.Model(&result).Where("api_key = ?", apiKey).First(&result).Error; err != nil {
return result, err
}
}
return result, nil
}
// 同步插入数据
func (e *TmMember) SyncInsert(req *dto.TmMemberSyncInsertReq, entity *models.TmMember) error {
platformService := TmPlatform{Service: e.Service}
activePlatforms, _ := platformService.GetActiveList()
TmMemberPlatforms := make([]models.TmMemberPlatform, 0)
copier.Copy(entity, req)
apiKey, err := utility.GenerateBase62Key(32)
if err != nil {
return errors.New("生成apiKey失败")
}
entity.ApiKey = apiKey
entity.CreatedAt = time.Now()
entity.Status = 1
if err := e.Orm.Save(entity).Error; err != nil {
return err
}
for _, platform := range activePlatforms {
item := models.TmMemberPlatform{
MemberId: entity.Id,
PlatformId: platform.Id,
PlatformKey: platform.Code,
Status: 1,
TotalCharacter: 10000,
RemainingCharacter: 10000,
}
TmMemberPlatforms = append(TmMemberPlatforms, item)
}
if err := e.Orm.Save(&TmMemberPlatforms).Error; err != nil {
return err
}
for _, platform := range TmMemberPlatforms {
e.IncrBy(platform.PlatformKey, entity.ApiKey, platform.RemainingCharacter)
}
return nil
}