Files
web_hgcg_server/web/service/srv_homeconfig/home_config.go
T
2026-10-27 16:44:55 +08:00

324 lines
9.8 KiB
Go

package srv_homeconfig
import (
"context"
"regexp"
"strings"
"time"
"videoweb/pub/common/playletcache"
"videoweb/pub/common/response"
"videoweb/pub/common/srvdef"
"videoweb/pub/models/homeconfigmod"
"videoweb/pub/models/translationcategorymod"
"videoweb/pub/pubdef"
"videoweb/pub/seo/sitemapdirty"
"videoweb/web/api/proto"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
)
const (
defaultHomeConfigPageSize int64 = 10
maxHomeConfigPageSize int64 = 100
)
var homeConfigWebProjection = bson.D{
{Key: homeconfigmod.FN.HomeConfig.Id, Value: 1},
{Key: homeconfigmod.FN.HomeConfig.Name, Value: 1},
{Key: homeconfigmod.FN.HomeConfig.Slug, Value: 1},
{Key: homeconfigmod.FN.HomeConfig.MoreCategoryId, Value: 1},
{Key: homeconfigmod.FN.HomeConfig.Desc, Value: 1},
{Key: homeconfigmod.FN.HomeConfig.Type, Value: 1},
{Key: homeconfigmod.FN.HomeConfig.Rank, Value: 1},
{Key: homeconfigmod.FN.HomeConfig.SortType, Value: 1},
{Key: homeconfigmod.FN.HomeConfig.Status, Value: 1},
{Key: homeconfigmod.FN.HomeConfig.SeoInfo.Title, Value: 1},
{Key: homeconfigmod.FN.HomeConfig.SeoInfo.Description, Value: 1},
{Key: homeconfigmod.FN.HomeConfig.SeoInfo.Keyworld, Value: 1},
{Key: homeconfigmod.FN.HomeConfig.CreatedAt, Value: 1},
{Key: homeconfigmod.FN.HomeConfig.UpdatedAt, Value: 1},
}
// ListHomeConfig 分页查询首页配置列表
func ListHomeConfig(ctx context.Context, req *proto.HomeConfigListReq) (*proto.HomeConfigListPageResp, *srvdef.SrvErr) {
if req == nil {
return nil, srvdef.New(srvdef.ErrParamError, "", "")
}
filter := bson.M{}
if req.Id != nil && *req.Id > 0 {
filter[homeconfigmod.FN.HomeConfig.Id] = *req.Id
}
if req.Type != nil {
if !validHomeConfigType(*req.Type) {
return nil, srvdef.New(srvdef.ErrParamError, "类型无效", "")
}
filter[homeconfigmod.FN.HomeConfig.Type] = *req.Type
}
if req.SortType != nil {
if !validHomeConfigSortType(*req.SortType) {
return nil, srvdef.New(srvdef.ErrParamError, "排序字段无效", "")
}
filter[homeconfigmod.FN.HomeConfig.SortType] = *req.SortType
}
if req.Status != nil {
filter[homeconfigmod.FN.HomeConfig.Status] = *req.Status
}
if name := strings.TrimSpace(req.Name); name != "" {
filter[homeconfigmod.FN.HomeConfig.Name] = bson.M{"$regex": regexp.QuoteMeta(name)}
}
if slug := strings.TrimSpace(req.Slug); slug != "" {
filter[homeconfigmod.FN.HomeConfig.Slug] = slug
}
pageNum, pageSize := normalizeHomeConfigPage(req.PageNum, req.PageSize)
totalCount, err := homeconfigmod.CountHomeConfig(ctx, filter)
if err != nil {
return nil, err
}
resp := &proto.HomeConfigListPageResp{
BasePageResp: response.BasePageResp{
TotalCount: totalCount,
PageSize: int(pageSize),
PageNum: int(pageNum),
List: make([]*proto.HomeConfigListResp, 0),
},
}
if totalCount == 0 {
return resp, nil
}
list, err := homeconfigmod.FindManyHomeConfig(ctx, filter, options.Find().
SetProjection(homeConfigWebProjection).
SetSort(bson.D{
{Key: homeconfigmod.FN.HomeConfig.Rank, Value: -1},
{Key: homeconfigmod.FN.HomeConfig.UpdatedAt, Value: -1},
{Key: homeconfigmod.FN.HomeConfig.Id, Value: -1},
}).
SetSkip((pageNum-1)*pageSize).
SetLimit(pageSize))
if err != nil {
return nil, err
}
items := make([]*proto.HomeConfigListResp, 0, len(list))
for _, item := range list {
if item == nil {
continue
}
items = append(items, &proto.HomeConfigListResp{
Id: item.Id,
MoreCategoryId: item.MoreCategoryId,
Name: item.Name,
Slug: item.Slug,
Desc: item.Desc,
Type: item.Type,
Rank: item.Rank,
SortType: item.SortType,
Status: item.Status,
SeoInfo: normalizeHomeConfigSeoValue(item.SeoInfo),
CreatedAt: item.CreatedAt,
UpdatedAt: item.UpdatedAt,
})
}
resp.List = items
return resp, nil
}
// AddHomeConfig 新增首页配置
func AddHomeConfig(ctx context.Context, req *proto.HomeConfigAddReq) (*proto.HomeConfigAddResp, *srvdef.SrvErr) {
if req == nil {
return nil, srvdef.New(srvdef.ErrParamError, "", "")
}
name := strings.TrimSpace(req.Name)
if name == "" {
return nil, srvdef.New(srvdef.ErrParamError, "名称不能为空", "")
}
if !validHomeConfigType(req.Type) {
return nil, srvdef.New(srvdef.ErrParamError, "类型无效", "")
}
if !validHomeConfigSortType(req.SortType) {
return nil, srvdef.New(srvdef.ErrParamError, "排序字段无效", "")
}
if req.Status == nil {
return nil, srvdef.New(srvdef.ErrParamError, "状态不能为空", "")
}
if err := validateHomeMoreCategory(ctx, req.MoreCategoryId); err != nil {
return nil, err
}
doc := &homeconfigmod.HomeConfig{
Name: name,
MoreCategoryId: req.MoreCategoryId,
Slug: strings.TrimSpace(req.Slug),
Desc: strings.TrimSpace(req.Desc),
Type: req.Type,
Rank: req.Rank,
SortType: req.SortType,
Status: *req.Status,
SeoInfo: normalizeHomeConfigSeoValue(req.SeoInfo),
}
if err := ensureHomeConfigSlug(ctx, doc, 0); err != nil {
return nil, err
}
if err := homeconfigmod.InsertOneHomeConfig(ctx, doc); err != nil {
return nil, err
}
clearHomeConfigCache()
sitemapdirty.Mark(ctx)
return &proto.HomeConfigAddResp{Id: doc.Id}, nil
}
// UpdateHomeConfig 更新首页配置
func UpdateHomeConfig(ctx context.Context, req *proto.HomeConfigUpdateReq) *srvdef.SrvErr {
if req == nil || req.Id <= 0 {
return srvdef.New(srvdef.ErrParamError, "首页配置 ID无效", "")
}
info, err := homeconfigmod.FindOneHomeConfig(ctx, bson.M{homeconfigmod.FN.HomeConfig.Id: req.Id})
if err != nil {
return err
}
setDoc := bson.M{homeconfigmod.FN.HomeConfig.UpdatedAt: time.Now()}
if req.MoreCategoryId != nil {
if err := validateHomeMoreCategory(ctx, *req.MoreCategoryId); err != nil {
return err
}
setDoc[homeconfigmod.FN.HomeConfig.MoreCategoryId] = *req.MoreCategoryId
}
if req.Name != nil {
name := strings.TrimSpace(*req.Name)
if name == "" {
return srvdef.New(srvdef.ErrParamError, "名称不能为空", "")
}
setDoc[homeconfigmod.FN.HomeConfig.Name] = name
}
if req.Desc != nil {
setDoc[homeconfigmod.FN.HomeConfig.Desc] = strings.TrimSpace(*req.Desc)
}
if req.Type != nil {
if !validHomeConfigType(*req.Type) {
return srvdef.New(srvdef.ErrParamError, "类型无效", "")
}
setDoc[homeconfigmod.FN.HomeConfig.Type] = *req.Type
}
if req.Rank != nil {
setDoc[homeconfigmod.FN.HomeConfig.Rank] = *req.Rank
}
if req.SortType != nil {
if !validHomeConfigSortType(*req.SortType) {
return srvdef.New(srvdef.ErrParamError, "排序字段无效", "")
}
setDoc[homeconfigmod.FN.HomeConfig.SortType] = *req.SortType
}
if req.Status != nil {
setDoc[homeconfigmod.FN.HomeConfig.Status] = *req.Status
}
if req.SeoInfo != nil {
setDoc["seoInfo"] = normalizeHomeConfigSeoValue(*req.SeoInfo)
}
if serr := prepareHomeConfigSlugOnUpdate(ctx, req.Id, info, req, setDoc); serr != nil {
return serr
}
if len(setDoc) <= 1 {
return srvdef.New(srvdef.ErrParamError, "没有可更新字段", "")
}
if err := homeconfigmod.UpdateOneHomeConfig(ctx,
bson.M{homeconfigmod.FN.HomeConfig.Id: req.Id},
bson.M{"$set": setDoc},
); err != nil {
return err
}
clearHomeConfigCache()
sitemapdirty.Mark(ctx)
return nil
}
// DelHomeConfig 删除首页配置
func DelHomeConfig(ctx context.Context, req *proto.HomeConfigDelReq) *srvdef.SrvErr {
if req == nil || req.Id <= 0 {
return srvdef.New(srvdef.ErrParamError, "首页配置 ID无效", "")
}
count, err := homeconfigmod.DelOneHomeConfig(ctx, bson.M{homeconfigmod.FN.HomeConfig.Id: req.Id})
if err != nil {
return err
}
if count == 0 {
return srvdef.New(srvdef.ErrNoData, "", "")
}
clearHomeConfigCache()
sitemapdirty.Mark(ctx)
return nil
}
func validHomeConfigType(typ int32) bool {
return typ == homeconfigmod.TypePlaylet || typ == homeconfigmod.TypeTheme
}
func validateHomeMoreCategory(ctx context.Context, id int64) *srvdef.SrvErr {
if id < 0 {
return srvdef.New(srvdef.ErrParamError, "更多跳转目标ID无效", "")
}
if id == 0 {
return nil
}
fields := translationcategorymod.FN.TranslationCategory
category, err := translationcategorymod.FindOneTranslationCategory(ctx, bson.M{
fields.Id: id,
fields.Status: true,
fields.HomeMoreEnabled: true,
fields.CategoryType: pubdef.TranslationTypeUrl,
})
if err != nil {
if err.Code == srvdef.ErrNoData {
return srvdef.New(srvdef.ErrParamError, "更多跳转目标不可用", "")
}
return err
}
if strings.TrimSpace(category.UrlValue) == "" {
return srvdef.New(srvdef.ErrParamError, "更多跳转目标路径为空", "")
}
return nil
}
func validHomeConfigSortType(sortType int32) bool {
return sortType == homeconfigmod.SortTypeRecommend ||
sortType == homeconfigmod.SortTypeHot ||
sortType == homeconfigmod.SortTypeLatest ||
sortType == homeconfigmod.SortTypePreview ||
sortType == homeconfigmod.SortTypeCategory
}
func clearHomeConfigCache() {
playletcache.ClearHomeCache()
}
// normalizeHomeConfigPage 规范分页参数,限制单页数量避免大分页拖慢查询
func normalizeHomeConfigPage(pageNum, pageSize int64) (int64, int64) {
if pageNum <= 0 {
pageNum = 1
}
if pageSize <= 0 {
pageSize = defaultHomeConfigPageSize
}
if pageSize > maxHomeConfigPageSize {
pageSize = maxHomeConfigPageSize
}
return pageNum, pageSize
}
// normalizeHomeConfigSeoValue 规范化 SEO 字段,避免 keywords 为 null
func normalizeHomeConfigSeoValue(seo pubdef.SeoValue) pubdef.SeoValue {
seo.Title = strings.TrimSpace(seo.Title)
seo.Description = strings.TrimSpace(seo.Description)
if seo.Keyworld == nil {
seo.Keyworld = make([]string, 0)
return seo
}
words := make([]string, 0, len(seo.Keyworld))
for _, word := range seo.Keyworld {
word = strings.TrimSpace(word)
if word == "" {
continue
}
words = append(words, word)
}
seo.Keyworld = words
return seo
}