324 lines
9.8 KiB
Go
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
|
|
}
|