Merge branch 'feat-cjy-tag'
# Conflicts: # api/cast/cast.pb.go # api/cast/cast.pb.validate.go # api/cast/cast_triple.pb.go
This commit is contained in:
commit
7eba8c6293
9959
api/cast/cast.pb.go
9959
api/cast/cast.pb.go
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go-triple. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-triple v1.0.8
|
||||
// - protoc v6.32.0--rc2
|
||||
// - protoc v3.21.1
|
||||
// source: pb/fiee/cast.proto
|
||||
|
||||
package cast
|
||||
|
||||
BIN
data/话题标签导入模板.xlsx
Normal file
BIN
data/话题标签导入模板.xlsx
Normal file
Binary file not shown.
133
pkg/cron/task.go
133
pkg/cron/task.go
@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"fonchain-fiee/api/aryshare"
|
||||
"fonchain-fiee/api/bundle"
|
||||
"fonchain-fiee/api/cast"
|
||||
"fonchain-fiee/pkg/cache"
|
||||
@ -16,6 +17,7 @@ import (
|
||||
"log"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-redis/redis"
|
||||
@ -40,6 +42,9 @@ func InitTasks() error {
|
||||
if err != nil {
|
||||
log.Printf("添加定时任务失败: %v", err)
|
||||
}
|
||||
|
||||
// 每2分钟执行一次标签观看次数更新任务
|
||||
// err = cm.AddTask("updateCastTagWatchCount", "0 */1 * * * *", UpdateCastTagWatchCountTask)
|
||||
cm.Start()
|
||||
|
||||
// 启动队列消费者
|
||||
@ -573,3 +578,131 @@ func AyrshareMetricsCollectorTask() {
|
||||
func RefreshArtistOrderTask() {
|
||||
_, _ = service.CastProvider.Tools(context.Background(), &cast.ToolsReq{Action: "artistOrderInfo"})
|
||||
}
|
||||
|
||||
// UpdateCastTagWatchCountTask 更新标签观看次数的定时任务(每5分钟执行一次)
|
||||
func UpdateCastTagWatchCountTask() {
|
||||
ctx := context.Background()
|
||||
|
||||
// 计算两天前的00:00:00
|
||||
now := time.Now()
|
||||
twoDaysAgo := now.AddDate(0, 0, -2)
|
||||
createdAtStart := time.Date(twoDaysAgo.Year(), twoDaysAgo.Month(), twoDaysAgo.Day(), 0, 0, 0, 0, twoDaysAgo.Location())
|
||||
createdAtEnd := now
|
||||
|
||||
// 格式化时间字符串:2026-01-01 00:00:00
|
||||
createdAtStartStr := createdAtStart.Format("2006-01-02 15:04:05")
|
||||
createdAtEndStr := createdAtEnd.Format("2006-01-02 15:04:05")
|
||||
|
||||
// 调用 ListCastTags 接口,筛选 IsWatchCountCalled = 2 的数据
|
||||
listReq := &cast.ListCastTagsReq{
|
||||
CreatedAtStart: createdAtStartStr,
|
||||
CreatedAtEnd: createdAtEndStr,
|
||||
IsWatchCountCalled: 2, // 2表示未调用
|
||||
Page: 1,
|
||||
PageSize: 10,
|
||||
}
|
||||
|
||||
listResp, err := service.CastProvider.ListCastTags(ctx, listReq)
|
||||
if err != nil {
|
||||
zap.L().Error("获取标签列表失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
if listResp.Data == nil || len(listResp.Data) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
zap.L().Info("获取到需要更新的标签", zap.Int("count", len(listResp.Data)))
|
||||
|
||||
// 获取有效的 profileKey
|
||||
profileKey, err := serverCast.GetValidProfileKey(ctx, []uint32{1})
|
||||
if err != nil {
|
||||
zap.L().Error("获取有效profileKey失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
// 准备批量更新的数据
|
||||
updateData := make([]*cast.CastTagInfo, 0, len(listResp.Data))
|
||||
|
||||
// 遍历每个标签,调用 RecommendHashtags 接口
|
||||
for _, tag := range listResp.Data {
|
||||
if tag.HashTag == "" {
|
||||
zap.L().Warn("标签HashTag为空,跳过", zap.String("uuid", tag.Uuid))
|
||||
// 即使HashTag为空,也要更新IsWatchCountCalled为1
|
||||
updateData = append(updateData, &cast.CastTagInfo{
|
||||
Uuid: tag.Uuid,
|
||||
WatchCount: 1,
|
||||
IsWatchCountCalled: 1,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
// 调用 RecommendHashtags 接口
|
||||
recommendReq := &aryshare.RecommendHashtagsRequest{
|
||||
Keyword: tag.HashTag,
|
||||
ProfileKey: profileKey,
|
||||
}
|
||||
|
||||
recommendResp, err := service.AyrshareProvider.RecommendHashtags(ctx, recommendReq)
|
||||
if err != nil {
|
||||
zap.L().Error("调用RecommendHashtags接口失败",
|
||||
zap.String("hashTag", tag.HashTag),
|
||||
zap.String("uuid", tag.Uuid),
|
||||
zap.Error(err))
|
||||
// 调用失败时,将WatchCount更新为1,IsWatchCountCalled更新为1
|
||||
updateData = append(updateData, &cast.CastTagInfo{
|
||||
Uuid: tag.Uuid,
|
||||
WatchCount: 1,
|
||||
IsWatchCountCalled: 1,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
// 对比返回结果,查找完全一致的标签
|
||||
var matchedViewCount int64 = 0
|
||||
if recommendResp.Recommendations != nil {
|
||||
for _, recommendation := range recommendResp.Recommendations {
|
||||
// 完全一致匹配(不区分大小写)
|
||||
if strings.EqualFold(recommendation.Name, tag.HashTag) {
|
||||
matchedViewCount = recommendation.ViewCount
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 根据匹配结果更新WatchCount
|
||||
var watchCount int32 = 1
|
||||
if matchedViewCount > 0 {
|
||||
watchCount = int32(matchedViewCount)
|
||||
}
|
||||
|
||||
// 添加到更新列表
|
||||
updateData = append(updateData, &cast.CastTagInfo{
|
||||
Uuid: tag.Uuid,
|
||||
WatchCount: watchCount,
|
||||
IsWatchCountCalled: 1,
|
||||
})
|
||||
|
||||
zap.L().Debug("处理标签完成",
|
||||
zap.String("hashTag", tag.HashTag),
|
||||
zap.String("uuid", tag.Uuid),
|
||||
zap.Int64("matchedViewCount", matchedViewCount),
|
||||
zap.Int32("watchCount", watchCount))
|
||||
}
|
||||
|
||||
// 如果没有需要更新的数据,直接返回
|
||||
if len(updateData) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// 批量更新标签
|
||||
batchUpdateReq := &cast.BatchUpdateCastTagsReq{
|
||||
Data: updateData,
|
||||
}
|
||||
|
||||
_, err = service.CastProvider.BatchUpdateCastTags(ctx, batchUpdateReq)
|
||||
if err != nil {
|
||||
zap.L().Error("批量更新标签失败", zap.Error(err), zap.Int("count", len(updateData)))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@ -81,6 +81,18 @@ func MediaRouter(r *gin.RouterGroup) {
|
||||
prompt.POST("delete", serviceCast.DeletePrompt)
|
||||
}
|
||||
|
||||
tag := auth.Group("tag")
|
||||
{
|
||||
tag.POST("update", serviceCast.UpdateCastTag)
|
||||
tag.POST("list", serviceCast.ListCastTags)
|
||||
tag.POST("import-batch", serviceCast.ImportTagBatch)
|
||||
tag.POST("recalculate-quote-count", serviceCast.RecalculateCastTagQuoteCount)
|
||||
// tag.POST("auto-hashtags", serviceCast.AutoHashtags)
|
||||
// 这两个接口需要关闭ins,通过facebook授权
|
||||
// tag.POST("recommend-hashtags", serviceCast.RecommendHashtags)
|
||||
// tag.POST("search-hashtags", serviceCast.SearchHashtags)
|
||||
}
|
||||
|
||||
//AI 生图
|
||||
aiNoAuth := noAuth.Group("ai")
|
||||
{
|
||||
|
||||
440
pkg/service/cast/tag.go
Normal file
440
pkg/service/cast/tag.go
Normal file
@ -0,0 +1,440 @@
|
||||
package cast
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"fonchain-fiee/api/aryshare"
|
||||
"fonchain-fiee/api/cast"
|
||||
"fonchain-fiee/cmd/config"
|
||||
"fonchain-fiee/pkg/model/login"
|
||||
"fonchain-fiee/pkg/service"
|
||||
"fonchain-fiee/pkg/utils"
|
||||
"math/rand"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/xuri/excelize/v2"
|
||||
"go.uber.org/zap"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
// UpdateCastTag 更新话题标签
|
||||
func UpdateCastTag(ctx *gin.Context) {
|
||||
var req *cast.UpdateCastTagReq
|
||||
var err error
|
||||
if err = ctx.ShouldBind(&req); err != nil {
|
||||
service.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
newCtx := NewCtxWithUserInfo(ctx)
|
||||
resp, err := service.CastProvider.UpdateCastTag(newCtx, req)
|
||||
if err != nil {
|
||||
service.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
service.Success(ctx, resp)
|
||||
return
|
||||
}
|
||||
|
||||
// ListCastTags 获取话题标签列表
|
||||
func ListCastTags(ctx *gin.Context) {
|
||||
var req *cast.ListCastTagsReq
|
||||
var err error
|
||||
if err = ctx.ShouldBind(&req); err != nil {
|
||||
service.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
newCtx := NewCtxWithUserInfo(ctx)
|
||||
resp, err := service.CastProvider.ListCastTags(newCtx, req)
|
||||
if err != nil {
|
||||
service.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
service.Success(ctx, resp)
|
||||
return
|
||||
}
|
||||
|
||||
// ImportTagBatch 批量导入话题标签
|
||||
func ImportTagBatch(ctx *gin.Context) {
|
||||
// 接收form表单的Excel保存到本地进行解析
|
||||
excelFile, err := ctx.FormFile("file")
|
||||
if err != nil {
|
||||
service.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
tempDir := "./runtime/tag"
|
||||
_, err = utils.CheckDirPath(tempDir, true)
|
||||
fileName := time.Now().Format("20060102150405") + "_in_tag.xlsx"
|
||||
excelPath := filepath.Join(tempDir, fileName)
|
||||
if err = ctx.SaveUploadedFile(excelFile, excelPath); err != nil {
|
||||
service.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
// 读取Excel中的数据
|
||||
excelData, err := excelize.OpenFile(excelPath)
|
||||
if err != nil {
|
||||
service.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
defer excelData.Close()
|
||||
// 解析Excel中的数据
|
||||
rows, err := excelData.GetRows("Sheet1")
|
||||
if err != nil {
|
||||
service.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
req := cast.UpdateCastTagBatchReq{
|
||||
Data: make([]*cast.CastTagInfo, 0),
|
||||
}
|
||||
userInfo := login.GetUserInfoFromC(ctx)
|
||||
for line, row := range rows {
|
||||
if line == 0 {
|
||||
continue
|
||||
}
|
||||
temp := cast.CastTagInfo{
|
||||
CreatorUuid: fmt.Sprint(userInfo.ID),
|
||||
CreatorName: userInfo.Name,
|
||||
Source: 1, // 固定来源:人工导入
|
||||
Status: 1, // 固定状态:有效
|
||||
}
|
||||
// 解析Excel列:A-话题标签,B-备注
|
||||
if len(row) > 0 {
|
||||
temp.HashTag = strings.TrimSpace(row[0])
|
||||
}
|
||||
if len(row) > 1 {
|
||||
temp.Remark = strings.TrimSpace(row[1])
|
||||
}
|
||||
zap.L().Info("ImportTagBatch row", zap.Int("line", line), zap.Strings("row", row))
|
||||
// 验证必填字段:话题标签不能为空
|
||||
if utils.CleanString(temp.HashTag) == "" {
|
||||
temp.Remark = "必填项未填"
|
||||
req.Data = append(req.Data, &temp)
|
||||
continue
|
||||
}
|
||||
req.Data = append(req.Data, &temp)
|
||||
}
|
||||
newCtx := NewCtxWithUserInfo(ctx)
|
||||
resp, _err := service.CastProvider.UpdateCastTagBatch(newCtx, &req)
|
||||
if _err != nil {
|
||||
service.Error(ctx, _err)
|
||||
return
|
||||
}
|
||||
// 打开模板 写入resp 返回的数据
|
||||
templatePath := "./data/话题标签导入模板.xlsx"
|
||||
template, err := excelize.OpenFile(templatePath)
|
||||
if err != nil {
|
||||
service.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
defer template.Close()
|
||||
var urlResult string
|
||||
if resp.FailCount != 0 {
|
||||
rowIndex := 2 // 从第2行开始写入(第1行是表头)
|
||||
for _, v := range resp.Data {
|
||||
if v.Success {
|
||||
continue
|
||||
}
|
||||
// 写入失败的数据到Excel模板(只写入话题标签和备注两列)
|
||||
template.SetCellValue("Sheet1", fmt.Sprintf("A%d", rowIndex), v.HashTag)
|
||||
template.SetCellValue("Sheet1", fmt.Sprintf("B%d", rowIndex), v.Remark)
|
||||
rowIndex++
|
||||
}
|
||||
resultFilename := strings.Replace(fileName, "_in_tag.xlsx", "_out_tag.xlsx", -1)
|
||||
resultPath := fmt.Sprintf("./runtime/tag/%s", resultFilename)
|
||||
if err = template.SaveAs(resultPath); err != nil {
|
||||
service.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
urlHost := config.AppConfig.System.FieeHost
|
||||
urlResult = fmt.Sprintf("%s/api/fiee/static/tag/%s", urlHost, resultFilename)
|
||||
}
|
||||
|
||||
service.Success(ctx, map[string]interface{}{
|
||||
"successCount": resp.SuccessCount,
|
||||
"failCount": resp.FailCount,
|
||||
"resultUrl": urlResult,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// RecalculateCastTagQuoteCount 重新计算话题标签引用数量
|
||||
func RecalculateCastTagQuoteCount(ctx *gin.Context) {
|
||||
newCtx := NewCtxWithUserInfo(ctx)
|
||||
// 创建空的请求对象
|
||||
req := &emptypb.Empty{}
|
||||
resp, err := service.CastProvider.RecalculateCastTagQuoteCount(newCtx, req)
|
||||
if err != nil {
|
||||
service.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
service.Success(ctx, resp)
|
||||
return
|
||||
}
|
||||
|
||||
// findNewTags 对比两次标签,找出新增的标签
|
||||
func findNewTags(beforeTags, afterTags []string) []string {
|
||||
// 将 beforeTags 转换为 map,方便查找
|
||||
beforeMap := make(map[string]bool)
|
||||
for _, tag := range beforeTags {
|
||||
cleanTag := strings.TrimSpace(tag)
|
||||
if cleanTag != "" {
|
||||
beforeMap[cleanTag] = true
|
||||
}
|
||||
}
|
||||
|
||||
// 找出 afterTags 中不在 beforeTags 中的标签
|
||||
newTags := make([]string, 0)
|
||||
for _, tag := range afterTags {
|
||||
cleanTag := strings.TrimSpace(tag)
|
||||
if cleanTag != "" && !beforeMap[cleanTag] {
|
||||
newTags = append(newTags, cleanTag)
|
||||
}
|
||||
}
|
||||
|
||||
return newTags
|
||||
}
|
||||
|
||||
func GetValidProfileKey(ctx context.Context, platformIDs []uint32) (string, error) {
|
||||
if len(platformIDs) == 0 {
|
||||
platformIDs = []uint32{1, 2, 3, 5}
|
||||
}
|
||||
profileKeys, err := service.CastProvider.GetArtistAyrShareInfoByPlatformIDs(ctx, &cast.GetArtistAyrShareInfoByPlatformIDsReq{
|
||||
PlatformIDs: platformIDs,
|
||||
Page: 1,
|
||||
PageSize: 20,
|
||||
})
|
||||
if err != nil {
|
||||
zap.L().Error("GetArtistAyrShareInfoByPlatformIDs failed", zap.Error(err))
|
||||
return "", errors.New("获取有效profileKey失败")
|
||||
}
|
||||
if len(profileKeys.Data) == 0 {
|
||||
return "", errors.New("当前没有有效的profileKey")
|
||||
}
|
||||
|
||||
// 过滤出所有非空的 profileKey
|
||||
validProfileKeys := make([]string, 0)
|
||||
for _, item := range profileKeys.Data {
|
||||
if item.ProfileKey != "" {
|
||||
validProfileKeys = append(validProfileKeys, item.ProfileKey)
|
||||
}
|
||||
}
|
||||
|
||||
if len(validProfileKeys) == 0 {
|
||||
return "", errors.New("profileKey为空")
|
||||
}
|
||||
|
||||
// 从有效的 profileKey 中随机选择一个
|
||||
randIndex := rand.Intn(len(validProfileKeys))
|
||||
return validProfileKeys[randIndex], nil
|
||||
}
|
||||
|
||||
// SaveTagsToDatabase 将标签保存到数据库
|
||||
func SaveTagsToDatabase(ctx *gin.Context, tags []string, source uint32) error {
|
||||
if len(tags) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
userInfo := login.GetUserInfoFromC(ctx)
|
||||
newCtx := NewCtxWithUserInfo(ctx)
|
||||
|
||||
// 构建批量导入请求
|
||||
req := cast.UpdateCastTagBatchReq{
|
||||
Data: make([]*cast.CastTagInfo, 0, len(tags)),
|
||||
}
|
||||
|
||||
for _, tag := range tags {
|
||||
tagInfo := &cast.CastTagInfo{
|
||||
HashTag: tag,
|
||||
CreatorUuid: fmt.Sprint(userInfo.ID),
|
||||
CreatorName: userInfo.Name,
|
||||
Source: source, // 4: 自动标签(从内容中自动提取)
|
||||
Status: 1, // 1: 有效
|
||||
}
|
||||
req.Data = append(req.Data, tagInfo)
|
||||
}
|
||||
|
||||
// 调用批量导入接口
|
||||
_, err := service.CastProvider.UpdateCastTagBatch(newCtx, &req)
|
||||
if err != nil {
|
||||
err = errors.New("标签保存到数据库失败")
|
||||
zap.L().Error("SaveTagsToDatabase UpdateCastTagBatch failed", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
zap.L().Info("SaveTagsToDatabase success", zap.Int("tagCount", len(tags)), zap.Strings("tags", tags), zap.Uint32("source", source))
|
||||
return nil
|
||||
}
|
||||
|
||||
func GenerateAutoHashtags(ctx context.Context, post string, max int32, position, language string) (*aryshare.AutoHashtagsResponse, []string, bool, error) {
|
||||
// 验证帖子内容
|
||||
if post == "" {
|
||||
return nil, nil, false, errors.New("帖子内容不能为空")
|
||||
}
|
||||
// post 的长度不能超过1000个字符
|
||||
if len(post) > 1000 {
|
||||
return nil, nil, false, errors.New("自动生成标签的帖子内容不能超过1000个字符")
|
||||
}
|
||||
|
||||
// 提取生成前的标签
|
||||
beforeTags := utils.ExtractTags(post)
|
||||
zap.L().Info("GenerateAutoHashtags beforeTags", zap.Strings("beforeTags", beforeTags))
|
||||
|
||||
// 如果标签数量已经达到或超过5个,不需要生成
|
||||
if len(beforeTags) >= 5 {
|
||||
return nil, nil, false, nil
|
||||
}
|
||||
|
||||
// 设置默认值
|
||||
if position == "" {
|
||||
position = "end"
|
||||
}
|
||||
if language == "" {
|
||||
language = "zh"
|
||||
}
|
||||
|
||||
// 如果 max 为0,则根据现有标签数自动计算,确保总数为5
|
||||
if max == 0 {
|
||||
max = int32(5 - len(beforeTags))
|
||||
}
|
||||
|
||||
// 如果此时 max 小于等于0,则直接返回
|
||||
if max <= 0 {
|
||||
return nil, nil, false, nil
|
||||
}
|
||||
|
||||
profileKey, err := GetValidProfileKey(ctx, []uint32{1, 2, 3, 5})
|
||||
if err != nil {
|
||||
return nil, nil, false, err
|
||||
}
|
||||
|
||||
// 构建请求
|
||||
req := &aryshare.AutoHashtagsRequest{
|
||||
Post: post,
|
||||
Max: max,
|
||||
Position: position,
|
||||
Language: language,
|
||||
ProfileKey: profileKey,
|
||||
}
|
||||
|
||||
// 调用 Ayrshare 的 AutoHashtags 接口
|
||||
resp, err := service.AyrshareProvider.AutoHashtags(ctx, req)
|
||||
if err != nil {
|
||||
zap.L().Error("AutoHashtags failed", zap.Error(err))
|
||||
return nil, nil, false, errors.New("自动生成标签失败")
|
||||
}
|
||||
|
||||
if resp.Post == "" {
|
||||
return nil, nil, false, errors.New("自动生成标签返回的帖子内容为空")
|
||||
}
|
||||
|
||||
// 去掉自动标签返回的帖子内容多余的引号
|
||||
resp.Post = utils.CleanAutoHashtagsQuote(resp.Post)
|
||||
|
||||
// 提取生成后的标签
|
||||
afterTags := utils.ExtractTags(resp.Post)
|
||||
zap.L().Info("GenerateAutoHashtags afterTags", zap.Strings("afterTags", afterTags))
|
||||
|
||||
// 对比两次标签,找出新增的标签
|
||||
newTags := findNewTags(beforeTags, afterTags)
|
||||
|
||||
return resp, newTags, true, nil
|
||||
}
|
||||
|
||||
// AutoHashtags 自动生成标签
|
||||
func AutoHashtags(ctx *gin.Context) {
|
||||
var req *aryshare.AutoHashtagsRequest
|
||||
var err error
|
||||
if err = ctx.ShouldBind(&req); err != nil {
|
||||
service.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
// 调用核心逻辑生成标签
|
||||
resp, newTags, needMore, err := GenerateAutoHashtags(
|
||||
context.Background(),
|
||||
req.Post,
|
||||
req.Max,
|
||||
req.Position,
|
||||
req.Language,
|
||||
)
|
||||
if err != nil {
|
||||
service.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
// 如果标签已满5个,直接返回
|
||||
if !needMore {
|
||||
service.Success(ctx, map[string]interface{}{
|
||||
"message": "当前帖子的标签已经有5个了",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 保存新增的标签到数据库,Source 设置为 4(自动标签)
|
||||
if len(newTags) > 0 {
|
||||
if err = SaveTagsToDatabase(ctx, newTags, 4); err != nil {
|
||||
zap.L().Error("SaveTagsToDatabase failed", zap.Error(err), zap.Strings("newTags", newTags))
|
||||
err = errors.New("标签保存到数据库失败")
|
||||
service.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
service.Success(ctx, resp)
|
||||
return
|
||||
}
|
||||
|
||||
// RecommendHashtags 推荐标签
|
||||
func RecommendHashtags(ctx *gin.Context) {
|
||||
var req *aryshare.RecommendHashtagsRequest
|
||||
var err error
|
||||
if err = ctx.ShouldBind(&req); err != nil {
|
||||
service.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
profileKey, err := GetValidProfileKey(context.Background(), []uint32{1})
|
||||
if err != nil {
|
||||
service.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
req.ProfileKey = profileKey
|
||||
resp, err := service.AyrshareProvider.RecommendHashtags(context.Background(), req)
|
||||
if err != nil {
|
||||
fmt.Println("err", err)
|
||||
zap.L().Error("RecommendHashtags failed", zap.Error(err))
|
||||
err = errors.New("推荐标签失败")
|
||||
service.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
service.Success(ctx, resp)
|
||||
return
|
||||
}
|
||||
|
||||
// SearchHashtags 搜索标签
|
||||
func SearchHashtags(ctx *gin.Context) {
|
||||
var req *aryshare.SearchHashtagsRequest
|
||||
var err error
|
||||
if err = ctx.ShouldBind(&req); err != nil {
|
||||
service.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
profileKey, err := GetValidProfileKey(context.Background(), []uint32{3})
|
||||
if err != nil {
|
||||
service.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
req.ProfileKey = profileKey
|
||||
resp, err := service.AyrshareProvider.SearchHashtags(context.Background(), req)
|
||||
if err != nil {
|
||||
fmt.Println("err", err)
|
||||
err = errors.New("获取热门话题标签失败")
|
||||
zap.L().Error("SearchHashtags failed", zap.Error(err))
|
||||
service.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
service.Success(ctx, resp)
|
||||
return
|
||||
}
|
||||
@ -122,6 +122,14 @@ func UpdateWorkImageCore(ctx *gin.Context, req *cast.UpdateWorkImageReq) (*cast.
|
||||
//if _, err = CheckUserBundleBalance(int32(artistID), modelCast.BalanceTypeImageValue); err != nil {
|
||||
// return nil, err
|
||||
//}
|
||||
|
||||
// 处理内容中的标签:提取、验证并批量导入,以及自动生成标签
|
||||
content, err := processContentAndAutoTags(ctx, req.Content)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 将自动生成标签后的内容更新到请求中
|
||||
req.Content = content
|
||||
newCtx := NewCtxWithUserInfo(ctx)
|
||||
req.Source = 1
|
||||
resp, err := service.CastProvider.UpdateWorkImage(newCtx, req)
|
||||
@ -171,6 +179,100 @@ func UpdateWorkImage(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
func processContentTags(ctx *gin.Context, content string) error {
|
||||
// 如果内容为空,直接返回
|
||||
if content == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 提取标签
|
||||
tags := utils.ExtractTags(content)
|
||||
if len(tags) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 第一步:检查标签格式(验证标签不为空且有效)
|
||||
validTags := make([]string, 0, len(tags))
|
||||
for _, tag := range tags {
|
||||
// 去除空白字符后检查
|
||||
cleanTag := strings.TrimSpace(tag)
|
||||
if cleanTag != "" {
|
||||
validTags = append(validTags, cleanTag)
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有有效标签,直接返回
|
||||
if len(validTags) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 第二步:检查是否有重复的标签
|
||||
tagMap := make(map[string]bool)
|
||||
for _, tag := range validTags {
|
||||
tagLower := strings.ToLower(tag)
|
||||
if tagMap[tagLower] {
|
||||
return errors.New("帖子标签不能重复")
|
||||
}
|
||||
tagMap[tagLower] = true
|
||||
}
|
||||
|
||||
// 第三步:检查标签数量是否超过5个
|
||||
if len(validTags) > 5 {
|
||||
return errors.New("帖子标签数量不能超过5个")
|
||||
}
|
||||
|
||||
fmt.Println("validTags", validTags)
|
||||
|
||||
// 第四步:调用 SaveTagsToDatabase 函数批量导入标签,Source 设置为 3(推荐标签)
|
||||
if err := SaveTagsToDatabase(ctx, validTags, 3); err != nil {
|
||||
zap.L().Error("processContentTags SaveTagsToDatabase failed", zap.Error(err))
|
||||
return errors.New("批量导入标签失败")
|
||||
}
|
||||
|
||||
zap.L().Info("processContentTags success", zap.Int("tagCount", len(validTags)), zap.Strings("tags", validTags))
|
||||
return nil
|
||||
}
|
||||
|
||||
// processContentAndAutoTags 处理内容标签并自动生成标签
|
||||
func processContentAndAutoTags(ctx *gin.Context, content string) (string, error) {
|
||||
// 如果内容为空,直接返回
|
||||
if content == "" {
|
||||
return "", nil
|
||||
}
|
||||
// 处理内容中的标签:提取、验证并批量导入
|
||||
if err := processContentTags(ctx, content); err != nil {
|
||||
return content, err
|
||||
}
|
||||
// 处理完内容标签后,自动生成标签并存入数据库
|
||||
resp, newTags, needMore, err := GenerateAutoHashtags(
|
||||
context.Background(),
|
||||
content,
|
||||
0, // max 为0时自动计算
|
||||
"", // position 使用默认值
|
||||
"", // language 使用默认值
|
||||
)
|
||||
if err != nil {
|
||||
return content, err
|
||||
}
|
||||
|
||||
if resp == nil {
|
||||
return content, nil
|
||||
}
|
||||
|
||||
// 保存新生成的标签到数据库
|
||||
if needMore && len(newTags) > 0 {
|
||||
if saveErr := SaveTagsToDatabase(ctx, newTags, 4); saveErr != nil {
|
||||
zap.L().Error("processContentAndAutoTags SaveTagsToDatabase failed", zap.Error(saveErr))
|
||||
return content, errors.New("自动生成标签保存到数据库失败")
|
||||
}
|
||||
}
|
||||
// 检查一下 resp.Post 是否为空
|
||||
if resp.Post == "" {
|
||||
return content, nil
|
||||
}
|
||||
return resp.Post, nil
|
||||
}
|
||||
|
||||
// UpdateWorkVideoCore 更新作品视频的核心逻辑,可以被其他函数复用
|
||||
func UpdateWorkVideoCore(ctx *gin.Context, req *cast.UpdateWorkVideoReq) (*cast.UpdateWorkVideoResp, error) {
|
||||
var infoResp *accountFiee.UserInfoResponse
|
||||
@ -250,6 +352,15 @@ func UpdateWorkVideoCore(ctx *gin.Context, req *cast.UpdateWorkVideoReq) (*cast.
|
||||
req.ArtistPhone = infoResp.TelNum
|
||||
req.ArtistPhoneAreaCode = infoResp.TelAreaCode
|
||||
req.ArtistSubNum = infoResp.SubNum
|
||||
|
||||
// 处理内容中的标签:提取、验证并批量导入,以及自动生成标签
|
||||
fmt.Println("UpdateWorkVideoCore: req.Content=", req.Content, "req.Title=", req.Title)
|
||||
content, err := processContentAndAutoTags(ctx, req.Content)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 将自动生成标签后的内容更新到请求中
|
||||
req.Content = content
|
||||
newCtx := NewCtxWithUserInfo(ctx)
|
||||
req.Source = 1
|
||||
resp, err := service.CastProvider.UpdateWorkVideo(newCtx, req)
|
||||
@ -1415,6 +1526,15 @@ func ImportWorkBatch(ctx *gin.Context) {
|
||||
req.ImageWorks = append(req.ImageWorks, temp)
|
||||
break
|
||||
}
|
||||
// 处理内容中的标签:提取、验证并批量导入,以及自动生成标签
|
||||
processedContent, err := processContentAndAutoTags(ctx, temp.Content)
|
||||
if err != nil {
|
||||
temp.Remark = fmt.Sprintf("%s", err.Error())
|
||||
req.ImageWorks = append(req.ImageWorks, temp)
|
||||
break
|
||||
}
|
||||
// 将处理后的内容更新到 temp.Content
|
||||
temp.Content = processedContent
|
||||
}
|
||||
}
|
||||
for i := 10; i <= 20; i++ {
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
package utils
|
||||
|
||||
import "strings"
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CleanString 移除所有空白字符
|
||||
func CleanString(s string) string {
|
||||
@ -18,3 +21,33 @@ func TruncateString(s string, maxLen int) string {
|
||||
}
|
||||
return string(runes[:maxLen])
|
||||
}
|
||||
|
||||
// ExtractTags 从文本中提取标签,标签以 # 开头,后面不能直接跟空格
|
||||
func ExtractTags(s string) []string {
|
||||
if len(s) == 0 {
|
||||
return []string{}
|
||||
}
|
||||
re := regexp.MustCompile(`#[^\s#\p{P}]+`)
|
||||
matches := re.FindAllString(s, -1)
|
||||
|
||||
tags := make([]string, 0, len(matches))
|
||||
for _, match := range matches {
|
||||
// 去掉开头的 #,只保留标签内容
|
||||
tag := match[1:]
|
||||
if len(tag) > 0 {
|
||||
tags = append(tags, tag)
|
||||
}
|
||||
}
|
||||
|
||||
return tags
|
||||
}
|
||||
|
||||
// 去掉自动标签里面多余的引号
|
||||
func CleanAutoHashtagsQuote(input string) string {
|
||||
if input == "" {
|
||||
return ""
|
||||
}
|
||||
cleaned := strings.ReplaceAll(input, "\\\"", "")
|
||||
cleaned = strings.ReplaceAll(cleaned, "\"", "")
|
||||
return cleaned
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user