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 }