Initial commit

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 13:57:10 +08:00
co-authored by Claude Opus 5
commit 8679200f41
1897 changed files with 257900 additions and 0 deletions
+284
View File
@@ -0,0 +1,284 @@
package officialwebsiteser
import (
"time"
"91porn-server/common/stderr"
officialwebsitemod "91porn-server/models/v/officialWebsitemod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// ─────────────────────────────────────────
// Service functions
// ─────────────────────────────────────────
func ListAlbum(req *AlbumListReq) (int64, []officialwebsitemod.Album, error) {
filter, err := req.Query()
if err != nil {
return 0, nil, err
}
mod := &officialwebsitemod.Album{}
total, err := mod.Count(filter)
if err != nil {
return 0, nil, err
}
list, err := mod.FindMany(filter, req.FindOptions())
if err != nil {
return 0, nil, err
}
return total, list, nil
}
// resolveAlbumSeoSlug 处理 seoSlug:手填则校验格式并查重(冲突报错);为空则由标题生成雏形并追加后缀去重。
func resolveAlbumSeoSlug(provided, title string, excludeID primitive.ObjectID) (string, error) {
return ResolveSeoSlug(provided, title, func(slug string) (bool, error) {
filter := officialwebsitemod.M{"seoSlug": slug}
if !excludeID.IsZero() {
filter["_id"] = officialwebsitemod.M{"$ne": excludeID}
}
n, err := (&officialwebsitemod.Album{}).Count(filter)
return n > 0, err
})
}
func CreateAlbum(req *ModifyAlbumReq) (officialwebsitemod.Album, error) {
data, err := req.toMod()
if err != nil {
return officialwebsitemod.Album{}, err
}
if data.SeoSlug, err = resolveAlbumSeoSlug(data.SeoSlug, data.Title, primitive.NilObjectID); err != nil {
return officialwebsitemod.Album{}, err
}
if err := data.Create(); err != nil {
return officialwebsitemod.Album{}, err
}
return data, nil
}
func UpdateAlbum(req *ModifyAlbumReq) (resp CountResp, err error) {
if req == nil || req.ID == "" {
return
}
id, parseErr := primitive.ObjectIDFromHex(req.ID)
if parseErr != nil || isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.Album{}
err = mod.FindOne(officialwebsitemod.M{"_id": id})
if err != nil {
return
}
if isNilObjectID(mod.ID) {
err = stderr.ErrParamError
return
}
// 若提交了 seoSlug,先校验/去重(排除自身),回写后由 toM 落库
if req.SeoSlug != nil {
var slug string
if slug, err = resolveAlbumSeoSlug(*req.SeoSlug, mod.Title, id); err != nil {
return
}
*req.SeoSlug = slug
}
var update officialwebsitemod.M
if update, err = req.toM(); err != nil || update == nil {
return
}
resp.Count, err = mod.Update(officialwebsitemod.M{"_id": id}, update)
return
}
func DeleteAlbum(req *DeleteAlbumReq) (data DeleteAlbumResp, err error) {
if req == nil {
err = stderr.CodeEmptyData
return
}
var id primitive.ObjectID
if id, err = primitive.ObjectIDFromHex(req.ID); err != nil {
return
}
if isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.Album{}
data.Count, err = mod.Delete(officialwebsitemod.M{"_id": id})
return
}
// ─────────────────────────────────────────
// Mutation (Modify) types
// ─────────────────────────────────────────
type ModifyAlbumReq struct {
ID string `json:"id" form:"id"`
HeroID *string `json:"heroID" form:"heroID"`
Title *string `json:"title"`
SeoSlug *string `json:"seoSlug"`
Description *string `json:"description"`
Cover *string `json:"cover"`
IsHot *bool `json:"isHot"`
Tags *[]officialwebsitemod.Tag `json:"tags"`
Sort *int64 `json:"sort"`
IsActive *bool `json:"isActive"`
}
// ─────────────────────────────────────────
// Delete types
// ─────────────────────────────────────────
type DeleteAlbumReq struct {
ID string `json:"id" form:"id"`
}
type DeleteAlbumResp struct {
Count int64 `json:"count"`
}
// ─────────────────────────────────────────
// toMod — ModifyAlbumReq → model struct
// ─────────────────────────────────────────
func (req *ModifyAlbumReq) toMod() (officialwebsitemod.Album, error) {
if req == nil {
return officialwebsitemod.Album{}, nil
}
data := officialwebsitemod.Album{
Title: stringOrZero(req.Title),
Description: stringOrZero(req.Description),
Cover: stringOrZero(req.Cover),
IsHot: boolOrZero(req.IsHot),
SortModel: officialwebsitemod.SortModel{
Sort: int64OrZero(req.Sort),
IsActive: boolOrZero(req.IsActive),
},
}
if req.HeroID != nil {
if *req.HeroID != "" {
heroID, err := primitive.ObjectIDFromHex(*req.HeroID)
if err != nil || isNilObjectID(heroID) {
return officialwebsitemod.Album{}, stderr.ErrParamError
}
data.HeroID = heroID
}
}
if req.Tags != nil {
data.Tags = *req.Tags
}
if req.SeoSlug != nil {
data.SeoSlug = *req.SeoSlug
}
return data, nil
}
// ─────────────────────────────────────────
// toM — ModifyAlbumReq → officialwebsitemod.M
// ─────────────────────────────────────────
func (req *ModifyAlbumReq) toM() (officialwebsitemod.M, error) {
if req == nil {
return nil, stderr.CodeEmptyData
}
set := officialwebsitemod.M{}
if req.HeroID != nil {
if *req.HeroID != "" {
heroID, err := primitive.ObjectIDFromHex(*req.HeroID)
if err != nil || isNilObjectID(heroID) {
return nil, stderr.ErrParamError
}
set["heroID"] = heroID
}
}
if req.Title != nil {
set["title"] = *req.Title
}
if req.SeoSlug != nil && *req.SeoSlug != "" {
set["seoSlug"] = *req.SeoSlug
}
if req.Description != nil {
set["description"] = *req.Description
}
if req.Cover != nil {
set["cover"] = *req.Cover
}
if req.IsHot != nil {
set["isHot"] = *req.IsHot
}
if req.Tags != nil {
set["tags"] = *req.Tags
}
if req.Sort != nil {
set["sort"] = *req.Sort
}
if req.IsActive != nil {
set["isActive"] = *req.IsActive
}
if len(set) == 0 {
return nil, nil
}
set["updatedAt"] = time.Now().UTC()
return officialwebsitemod.M{"$set": set}, nil
}
// ─────────────────────────────────────────
// List types
// ─────────────────────────────────────────
type AlbumListReq struct {
PageReq
ID *string `form:"id"`
HeroID *string `form:"heroID"`
Title *string `form:"title"`
Description *string `form:"description"`
Cover *string `form:"cover"`
IsHot *bool `form:"isHot"`
Tags *string `form:"tags"`
Sort *int64 `form:"sort"`
IsActive *bool `form:"isActive"`
CreatedAt *time.Time `form:"createdAt"`
UpdatedAt *time.Time `form:"updatedAt"`
DeletedAt *time.Time `form:"deletedAt"`
}
func (r *AlbumListReq) Query() (officialwebsitemod.M, error) {
filter := officialwebsitemod.M{}
if err := parseObjectIDFilter(filter, "_id", r.ID); err != nil {
return nil, err
}
if err := parseObjectIDFilter(filter, "heroID", r.HeroID); err != nil {
return nil, err
}
if r.Title != nil {
filter["title"] = *r.Title
}
if r.Description != nil {
filter["description"] = *r.Description
}
if r.Cover != nil {
filter["cover"] = *r.Cover
}
if r.IsHot != nil {
filter["isHot"] = *r.IsHot
}
if err := parseJSONFilter(filter, "tags", r.Tags, &[]officialwebsitemod.Tag{}); err != nil {
return nil, err
}
if r.Sort != nil {
filter["sort"] = *r.Sort
}
if r.IsActive != nil {
filter["isActive"] = *r.IsActive
}
if r.CreatedAt != nil {
filter["createdAt"] = *r.CreatedAt
}
if r.UpdatedAt != nil {
filter["updatedAt"] = *r.UpdatedAt
}
if r.DeletedAt != nil {
filter["deletedAt"] = *r.DeletedAt
}
return filter, nil
}
+141
View File
@@ -0,0 +1,141 @@
package officialwebsiteser
import (
"91porn-server/common/log"
officialwebsitemod "91porn-server/models/v/officialWebsitemod"
"github.com/robfig/cron/v3"
)
var seoSlugBackfillCron *cron.Cron
// StartSeoSlugBackfillCron 启动定时任务:扫描官网各表(Video/Hero/Album/News/Photograph)
// 中缺失 seoSlug 的记录,按标题自动生成并回填。
// 启动后异步先跑一次(补存量),之后每小时扫一次(兜底导入/批量遗漏的记录)。
func StartSeoSlugBackfillCron() {
go backfillAllSeoSlug() // 首次异步执行,避免阻塞服务启动
seoSlugBackfillCron = cron.New()
if _, err := seoSlugBackfillCron.AddFunc("@every 1h", backfillAllSeoSlug); err != nil {
log.Error("StartSeoSlugBackfillCron AddFunc fail", log.E(err))
return
}
seoSlugBackfillCron.Start()
}
func backfillAllSeoSlug() {
backfillVideoSeoSlug()
backfillHeroSeoSlug()
backfillAlbumSeoSlug()
backfillNewsSeoSlug()
backfillPhotographSeoSlug()
}
// seoSlugMissingFilter 匹配 seoSlug 字段缺失或为空的记录。
func seoSlugMissingFilter() officialwebsitemod.M {
return officialwebsitemod.M{"$or": []officialwebsitemod.M{
{"seoSlug": officialwebsitemod.M{"$exists": false}},
{"seoSlug": ""},
}}
}
// setSeoSlug 回填单条记录的 seoSlug。
func setSeoSlug(update func(officialwebsitemod.M, officialwebsitemod.M) (int64, error), id interface{}, slug string) {
if _, err := update(officialwebsitemod.M{"_id": id}, officialwebsitemod.M{"$set": officialwebsitemod.M{"seoSlug": slug}}); err != nil {
log.Error("backfill seoSlug update fail", log.E(err), log.Any("id", id))
}
}
func backfillVideoSeoSlug() {
mod := &officialwebsitemod.Video{}
list, err := mod.FindMany(seoSlugMissingFilter())
if err != nil {
log.Error("backfill video seoSlug find fail", log.E(err))
return
}
for i := range list {
if list[i].Title == "" {
continue
}
slug, e := resolveVideoSeoSlug("", list[i].Title, list[i].ID)
if e != nil || slug == "" {
continue
}
setSeoSlug(mod.Update, list[i].ID, slug)
}
}
func backfillHeroSeoSlug() {
mod := &officialwebsitemod.Hero{}
list, err := mod.FindMany(seoSlugMissingFilter())
if err != nil {
log.Error("backfill hero seoSlug find fail", log.E(err))
return
}
for i := range list {
if list[i].Name == "" {
continue
}
slug, e := resolveHeroSeoSlug("", list[i].Name, list[i].ID)
if e != nil || slug == "" {
continue
}
setSeoSlug(mod.Update, list[i].ID, slug)
}
}
func backfillAlbumSeoSlug() {
mod := &officialwebsitemod.Album{}
list, err := mod.FindMany(seoSlugMissingFilter())
if err != nil {
log.Error("backfill album seoSlug find fail", log.E(err))
return
}
for i := range list {
if list[i].Title == "" {
continue
}
slug, e := resolveAlbumSeoSlug("", list[i].Title, list[i].ID)
if e != nil || slug == "" {
continue
}
setSeoSlug(mod.Update, list[i].ID, slug)
}
}
func backfillNewsSeoSlug() {
mod := &officialwebsitemod.News{}
list, err := mod.FindMany(seoSlugMissingFilter())
if err != nil {
log.Error("backfill news seoSlug find fail", log.E(err))
return
}
for i := range list {
if list[i].Title == "" {
continue
}
slug, e := resolveNewsSeoSlug("", list[i].Title, list[i].ID)
if e != nil || slug == "" {
continue
}
setSeoSlug(mod.Update, list[i].ID, slug)
}
}
func backfillPhotographSeoSlug() {
mod := &officialwebsitemod.Photograph{}
list, err := mod.FindMany(seoSlugMissingFilter())
if err != nil {
log.Error("backfill photograph seoSlug find fail", log.E(err))
return
}
for i := range list {
if list[i].Name == "" {
continue
}
slug, e := resolvePhotographSeoSlug("", list[i].Name, list[i].ID)
if e != nil || slug == "" {
continue
}
setSeoSlug(mod.Update, list[i].ID, slug)
}
}
+779
View File
@@ -0,0 +1,779 @@
package officialwebsiteser
import (
"91porn-server/common"
"91porn-server/common/cachev2"
"91porn-server/common/constant/redisconst"
"time"
"91porn-server/common/stderr"
officialwebsitemod "91porn-server/models/v/officialWebsitemod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// ─────────────────────────────────────────
// Service functions
// ─────────────────────────────────────────
func GetBasicData() (data GetBasicDataResp, err error) {
mod := &officialwebsitemod.BasicData{}
if err = mod.FindOne(); err != nil {
return
}
err = data.transfer(mod)
return
}
func CreateBasicData(req *ModifyBasicDataReq) (data officialwebsitemod.BasicData, err error) {
data, err = req.toMod()
if err != nil {
return
}
if err = data.Create(); err != nil {
return officialwebsitemod.BasicData{}, err
}
return
}
func UpdateBasicData(req *ModifyBasicDataReq) (resp UpdateBasicDataResp, err error) {
if req == nil || req.ID == "" {
return
}
id, parseErr := primitive.ObjectIDFromHex(req.ID)
if parseErr != nil || isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.BasicData{}
if err = mod.FindOne(); err != nil {
return
}
var update officialwebsitemod.M
if update, err = req.toM(); err != nil || update == nil {
return
}
resp.Count, err = mod.Update(officialwebsitemod.M{"_id": id}, update)
common.Go(func() {
// 删除缓存
cachev2.Classes().
CacheTime(redisconst.OfficialWebsiteBasicDataCacheExpire).
AutoListKey(redisconst.OfficialWebsiteBasicDataCacheKey).
DeleteCurrent()
})
return
}
func DeleteBasicData(req *DeleteBasicDataReq) (data DeleteBasicDataResp, err error) {
if req == nil {
err = stderr.CodeEmptyData
return
}
var id primitive.ObjectID
if id, err = primitive.ObjectIDFromHex(req.ID); err != nil {
return
}
if isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.BasicData{}
data.Count, err = mod.Delete(officialwebsitemod.M{"_id": id})
return
}
// ─────────────────────────────────────────
// Query (BasicData) types
// ─────────────────────────────────────────
type GetBasicDataReq struct{}
type GetBasicDataResp struct {
ID primitive.ObjectID `json:"id" bson:"_id,omitempty"`
Title string `json:"title" bson:"title"`
Description string `json:"description" bson:"description"`
Banner []GBDBanner `json:"banner" bson:"banner"`
HeroBanner []GBDHeroBanner `json:"heroBanner" bson:"heroBanner"`
Masterpiece []GBDMasterpiece `json:"masterpiece" bson:"masterpiece"`
Business []GBDBusiness `json:"business" bson:"business"`
BannersDescription string `json:"bannersDescription" bson:"bannersDescription"`
BannersDuration int `json:"bannersDuration" bson:"bannersDuration"`
AboutUs GBDAboutUs `json:"aboutUs" bson:"aboutUs"`
FAQ []GBDFAQ `json:"faq" bson:"faq"`
HomePageCMS []GBDCMSData `json:"homePageCMS" bson:"homePageCMS"`
HomePageCMSDescription string `json:"homePageCMSDescription" bson:"homePageCMSDescription"`
RecruitCMS []GBDCMSData `json:"recruitCMS" bson:"recruitCMS"`
Tags []officialwebsitemod.Tag `json:"tags" bson:"tags"`
}
type GBDBanner struct {
Name string `json:"name" bson:"name"`
Description string `json:"description" bson:"description"`
Image string `json:"image" bson:"image"`
PhoneImage string `json:"phoneImage" bson:"phoneImage"`
Thumbnail string `json:"thumbnail" bson:"thumbnail"`
URL string `json:"url" bson:"url"`
}
type GBDHeroBanner struct {
ID primitive.ObjectID `json:"id" bson:"_id,omitempty"`
HeroID string `json:"heroId" bson:"heroId"`
Name string `json:"name" bson:"name"`
Description string `json:"description" bson:"description"`
Image string `json:"image" bson:"image"`
URL string `json:"url" bson:"url"`
Duration int `json:"duration" bson:"duration"`
}
type GBDMasterpiece struct {
ID primitive.ObjectID `json:"id" bson:"_id,omitempty"`
Name string `json:"name" bson:"name"`
Description string `json:"description" bson:"description"`
Cover string `json:"cover" bson:"cover"`
Type string `json:"type" bson:"type"`
}
type GBDBusiness struct {
Name string `json:"name" bson:"name"`
EnglishName string `json:"englishName" bson:"englishName"`
Description string `json:"description" bson:"description"`
Cover string `json:"cover" bson:"cover"`
Detail []GBDBusinessDetail `json:"detail" bson:"detail"`
}
type GBDBusinessDetail struct {
Cover string `json:"cover" bson:"cover"`
Description string `json:"description" bson:"description"`
}
type GBDAboutUs struct {
Description string `json:"description" bson:"description"`
Companies []GBDCompanyInfo `json:"companies" bson:"companies"`
Values string `json:"values" bson:"values"`
ValuesImg string `json:"valuesImg" bson:"valuesImg"`
}
type GBDCompanyInfo struct {
Name string `json:"name" bson:"name"`
Address string `json:"address" bson:"address"`
Description string `json:"description" bson:"description"`
Contact []GBDContactInfo `json:"contact" bson:"contact"`
}
type GBDContactInfo struct {
Type string `json:"type" bson:"type"`
Value string `json:"value" bson:"value"`
}
type GBDFAQ struct {
Question string `json:"question" bson:"question"`
Answer string `json:"answer" bson:"answer"`
}
type GBDCMSData struct {
Description string `json:"description" bson:"description"`
Value string `json:"value" bson:"value"`
}
func (data *GetBasicDataResp) transfer(mod *officialwebsitemod.BasicData) error {
if mod == nil {
return stderr.CodeEmptyData
}
data.ID = mod.ID
data.Title = mod.Title
data.Description = mod.Description
data.Tags = mod.Tags
data.BannersDescription = mod.BannersDescription
data.BannersDuration = mod.BannersDuration
data.HomePageCMSDescription = mod.HomePageCMSDescription
data.AboutUs.Description = mod.AboutUs.Description
data.AboutUs.Values = mod.AboutUs.Values
data.AboutUs.ValuesImg = mod.AboutUs.ValuesImg
data.Banner = make([]GBDBanner, 0, len(mod.Banner))
for _, v := range mod.Banner {
data.Banner = append(data.Banner, GBDBanner{
Name: v.Name, Description: v.Description, Image: v.Image, PhoneImage: v.PhoneImage, Thumbnail: v.Thumbnail, URL: v.URL,
})
}
data.HeroBanner = make([]GBDHeroBanner, 0, len(mod.HeroBanner))
for _, v := range mod.HeroBanner {
data.HeroBanner = append(data.HeroBanner, GBDHeroBanner{
ID: v.ID, HeroID: v.HeroID.Hex(), Name: v.Name, Description: v.Description, Image: v.Image, URL: v.URL, Duration: v.Duration,
})
}
data.Masterpiece = make([]GBDMasterpiece, 0, len(mod.Masterpiece))
for _, v := range mod.Masterpiece {
data.Masterpiece = append(data.Masterpiece, GBDMasterpiece{
ID: v.ID, Name: v.Name, Description: v.Description, Cover: v.Cover, Type: v.Type,
})
}
data.Business = make([]GBDBusiness, 0, len(mod.Business))
for _, v := range mod.Business {
business := GBDBusiness{
Name: v.Name, EnglishName: v.EnglishName, Description: v.Description, Cover: v.Cover,
Detail: make([]GBDBusinessDetail, 0, len(v.Detail)),
}
for _, d := range v.Detail {
business.Detail = append(business.Detail, GBDBusinessDetail{
Cover: d.Cover, Description: d.Description,
})
}
data.Business = append(data.Business, business)
}
data.AboutUs.Companies = make([]GBDCompanyInfo, 0, len(mod.AboutUs.Companies))
for _, v := range mod.AboutUs.Companies {
company := GBDCompanyInfo{
Name: v.Name, Address: v.Address, Description: v.Description,
Contact: make([]GBDContactInfo, 0, len(v.Contact)),
}
for _, c := range v.Contact {
company.Contact = append(company.Contact, GBDContactInfo{Type: c.Type, Value: c.Value})
}
data.AboutUs.Companies = append(data.AboutUs.Companies, company)
}
data.FAQ = make([]GBDFAQ, 0, len(mod.FAQ))
for _, v := range mod.FAQ {
data.FAQ = append(data.FAQ, GBDFAQ{Question: v.Question, Answer: v.Answer})
}
data.HomePageCMS = make([]GBDCMSData, 0, len(mod.HomePageCMS))
for _, v := range mod.HomePageCMS {
data.HomePageCMS = append(data.HomePageCMS, GBDCMSData{Description: v.Description, Value: v.Value})
}
data.RecruitCMS = make([]GBDCMSData, 0, len(mod.RecruitCMS))
for _, v := range mod.RecruitCMS {
data.RecruitCMS = append(data.RecruitCMS, GBDCMSData{Description: v.Description, Value: v.Value})
}
return nil
}
// ─────────────────────────────────────────
// Mutation (Modify) types
// ─────────────────────────────────────────
type ModifyBasicDataReq struct {
ID string `json:"id" form:"id"`
Title *string `json:"title" bson:"title"`
Description *string `json:"description" bson:"description"`
Banner *[]ModifyBanner `json:"banner" bson:"banner"`
HeroBanner *[]ModifyHeroBanner `json:"heroBanner" bson:"heroBanner"`
Masterpiece *[]ModifyMasterpiece `json:"masterpiece" bson:"masterpiece"`
Business *[]ModifyBusiness `json:"business" bson:"business"`
BannersDescription *string `json:"bannersDescription" bson:"bannersDescription"`
BannersDuration *int `json:"bannersDuration" bson:"bannersDuration"`
AboutUs *ModifyAboutUs `json:"aboutUs" bson:"aboutUs"`
FAQ *[]ModifyFAQ `json:"faq" bson:"faq"`
HomePageCMS *[]ModifyCMSData `json:"homePageCMS" bson:"homePageCMS"`
HomePageCMSDescription *string `json:"homePageCMSDescription" bson:"homePageCMSDescription"`
RecruitCMS *[]ModifyCMSData `json:"recruitCMS" bson:"recruitCMS"`
Tags *[]officialwebsitemod.Tag `json:"tags" bson:"tags"`
}
type ModifyBanner struct {
Name *string `json:"name" bson:"name"`
Description *string `json:"description" bson:"description"`
Image *string `json:"image" bson:"image"`
PhoneImage *string `json:"phoneImage" bson:"phoneImage"`
Thumbnail *string `json:"thumbnail" bson:"thumbnail"`
URL *string `json:"url" bson:"url"`
}
type ModifyHeroBanner struct {
ID *primitive.ObjectID `json:"id" bson:"_id,omitempty"`
HeroID *primitive.ObjectID `json:"heroId" bson:"heroId"`
Name *string `json:"name" bson:"name"`
Description *string `json:"description" bson:"description"`
Image *string `json:"image" bson:"image"`
URL *string `json:"url" bson:"url"`
Duration *int `json:"duration" bson:"duration"`
}
type ModifyMasterpiece struct {
ID *primitive.ObjectID `json:"id" bson:"_id,omitempty"`
Name *string `json:"name" bson:"name"`
Description *string `json:"description" bson:"description"`
Cover *string `json:"cover" bson:"cover"`
Type *string `json:"type" bson:"type"`
}
type ModifyBusiness struct {
Name *string `json:"name" bson:"name"`
EnglishName *string `json:"englishName" bson:"englishName"`
Description *string `json:"description" bson:"description"`
Cover *string `json:"cover" bson:"cover"`
Detail *[]ModifyBusinessDetail `json:"detail" bson:"detail"`
}
type ModifyBusinessDetail struct {
Cover *string `json:"cover" bson:"cover"`
Description *string `json:"description" bson:"description"`
}
type ModifyAboutUs struct {
Description *string `json:"description" bson:"description"`
Companies *[]ModifyCompanyInfo `json:"companies" bson:"companies"`
Values *string `json:"values" bson:"values"`
ValuesImg *string `json:"valuesImg" bson:"valuesImg"`
}
type ModifyCompanyInfo struct {
Name *string `json:"name" bson:"name"`
Address *string `json:"address" bson:"address"`
Description *string `json:"description" bson:"description"`
Contact *[]ModifyContactInfo `json:"contact" bson:"contact"`
}
type ModifyContactInfo struct {
Type *string `json:"type" bson:"type"`
Value *string `json:"value" bson:"value"`
}
type ModifyFAQ struct {
Question *string `json:"question" bson:"question"`
Answer *string `json:"answer" bson:"answer"`
}
type ModifyCMSData struct {
Description *string `json:"description" bson:"description"`
Value *string `json:"value" bson:"value"`
}
type UpdateBasicDataResp struct {
Count int64 `json:"count"`
}
// ─────────────────────────────────────────
// Delete types
// ─────────────────────────────────────────
type DeleteBasicDataReq struct {
ID string `json:"id"`
}
type DeleteBasicDataResp struct {
Count int64 `json:"count"`
}
// ─────────────────────────────────────────
// toMod — Modify* → model struct
// ─────────────────────────────────────────
func (v ModifyBanner) toMod() officialwebsitemod.Banner {
return officialwebsitemod.Banner{
Name: stringOrZero(v.Name), Description: stringOrZero(v.Description),
Image: stringOrZero(v.Image), Thumbnail: stringOrZero(v.Thumbnail),
PhoneImage: stringOrZero(v.PhoneImage), URL: stringOrZero(v.URL),
}
}
func (v ModifyHeroBanner) toMod() officialwebsitemod.HeroBanner {
return officialwebsitemod.HeroBanner{
HeroID: objectIDOrZero(v.HeroID), Name: stringOrZero(v.Name), Description: stringOrZero(v.Description),
Image: stringOrZero(v.Image), URL: stringOrZero(v.URL), Duration: intOrZero(v.Duration),
}
}
func (v ModifyMasterpiece) toMod() officialwebsitemod.Masterpiece {
return officialwebsitemod.Masterpiece{
ID: objectIDOrZero(v.ID), Name: stringOrZero(v.Name), Description: stringOrZero(v.Description),
Cover: stringOrZero(v.Cover), Type: stringOrZero(v.Type),
}
}
func (v ModifyBusiness) toMod() officialwebsitemod.Business {
out := officialwebsitemod.Business{
Name: stringOrZero(v.Name), EnglishName: stringOrZero(v.EnglishName),
Description: stringOrZero(v.Description), Cover: stringOrZero(v.Cover),
}
if v.Detail != nil {
out.Detail = make([]officialwebsitemod.BusinessDetail, 0, len(*v.Detail))
for _, d := range *v.Detail {
out.Detail = append(out.Detail, d.toMod())
}
}
return out
}
func (v ModifyBusinessDetail) toMod() officialwebsitemod.BusinessDetail {
return officialwebsitemod.BusinessDetail{
Cover: stringOrZero(v.Cover), Description: stringOrZero(v.Description),
}
}
func (v ModifyContactInfo) toMod() officialwebsitemod.ContactInfo {
return officialwebsitemod.ContactInfo{Type: stringOrZero(v.Type), Value: stringOrZero(v.Value)}
}
func (v ModifyCompanyInfo) toMod() officialwebsitemod.CompanyInfo {
out := officialwebsitemod.CompanyInfo{
Name: stringOrZero(v.Name), Address: stringOrZero(v.Address), Description: stringOrZero(v.Description),
}
if v.Contact != nil {
out.Contact = make([]officialwebsitemod.ContactInfo, 0, len(*v.Contact))
for _, c := range *v.Contact {
out.Contact = append(out.Contact, c.toMod())
}
}
return out
}
func (v *ModifyAboutUs) toMod() officialwebsitemod.AboutUs {
if v == nil {
return officialwebsitemod.AboutUs{}
}
out := officialwebsitemod.AboutUs{
Description: stringOrZero(v.Description), Values: stringOrZero(v.Values), ValuesImg: stringOrZero(v.ValuesImg),
}
if v.Companies != nil {
out.Companies = make([]officialwebsitemod.CompanyInfo, 0, len(*v.Companies))
for _, c := range *v.Companies {
out.Companies = append(out.Companies, c.toMod())
}
}
return out
}
func (v ModifyFAQ) toMod() officialwebsitemod.FAQ {
return officialwebsitemod.FAQ{Question: stringOrZero(v.Question), Answer: stringOrZero(v.Answer)}
}
func (v ModifyCMSData) toMod() officialwebsitemod.CMSData {
return officialwebsitemod.CMSData{Description: stringOrZero(v.Description), Value: stringOrZero(v.Value)}
}
// toMod 将请求结构体转换为完整的 BasicData model
func (req *ModifyBasicDataReq) toMod() (officialwebsitemod.BasicData, error) {
if req == nil {
return officialwebsitemod.BasicData{}, stderr.CodeEmptyData
}
data := officialwebsitemod.BasicData{
Title: stringOrZero(req.Title),
Description: stringOrZero(req.Description),
BannersDescription: stringOrZero(req.BannersDescription),
BannersDuration: intOrZero(req.BannersDuration),
HomePageCMSDescription: stringOrZero(req.HomePageCMSDescription),
}
if req.AboutUs != nil {
data.AboutUs = req.AboutUs.toMod()
}
if req.Banner != nil {
data.Banner = make([]officialwebsitemod.Banner, 0, len(*req.Banner))
for _, b := range *req.Banner {
data.Banner = append(data.Banner, b.toMod())
}
}
if req.HeroBanner != nil {
data.HeroBanner = make([]officialwebsitemod.HeroBanner, 0, len(*req.HeroBanner))
for _, h := range *req.HeroBanner {
data.HeroBanner = append(data.HeroBanner, h.toMod())
}
}
if req.Masterpiece != nil {
data.Masterpiece = make([]officialwebsitemod.Masterpiece, 0, len(*req.Masterpiece))
for _, m := range *req.Masterpiece {
data.Masterpiece = append(data.Masterpiece, m.toMod())
}
}
if req.Business != nil {
data.Business = make([]officialwebsitemod.Business, 0, len(*req.Business))
for _, b := range *req.Business {
data.Business = append(data.Business, b.toMod())
}
}
if req.FAQ != nil {
data.FAQ = make([]officialwebsitemod.FAQ, 0, len(*req.FAQ))
for _, f := range *req.FAQ {
data.FAQ = append(data.FAQ, f.toMod())
}
}
if req.HomePageCMS != nil {
data.HomePageCMS = make([]officialwebsitemod.CMSData, 0, len(*req.HomePageCMS))
for _, h := range *req.HomePageCMS {
data.HomePageCMS = append(data.HomePageCMS, h.toMod())
}
}
if req.RecruitCMS != nil {
data.RecruitCMS = make([]officialwebsitemod.CMSData, 0, len(*req.RecruitCMS))
for _, r := range *req.RecruitCMS {
data.RecruitCMS = append(data.RecruitCMS, r.toMod())
}
}
if req.Tags != nil {
data.Tags = *req.Tags
}
return data, nil
}
// ─────────────────────────────────────────
// toM — Modify* → officialwebsitemod.M (Mongo update doc)
// ─────────────────────────────────────────
func (v ModifyBanner) toM() officialwebsitemod.M {
out := officialwebsitemod.M{}
if v.Name != nil {
out["name"] = *v.Name
}
if v.Description != nil {
out["description"] = *v.Description
}
if v.Image != nil {
out["image"] = *v.Image
}
if v.Thumbnail != nil {
out["thumbnail"] = *v.Thumbnail
}
if v.PhoneImage != nil {
out["phoneImage"] = *v.PhoneImage
}
if v.URL != nil {
out["url"] = *v.URL
}
return out
}
func (v ModifyHeroBanner) toM() officialwebsitemod.M {
out := officialwebsitemod.M{}
if v.ID != nil {
out["_id"] = *v.ID
}
if v.HeroID != nil {
out["heroId"] = *v.HeroID
}
if v.Name != nil {
out["name"] = *v.Name
}
if v.Description != nil {
out["description"] = *v.Description
}
if v.Image != nil {
out["image"] = *v.Image
}
if v.URL != nil {
out["url"] = *v.URL
}
if v.Duration != nil {
out["duration"] = *v.Duration
}
return out
}
func (v ModifyMasterpiece) toM() officialwebsitemod.M {
out := officialwebsitemod.M{}
if v.ID != nil {
out["_id"] = *v.ID
}
if v.Name != nil {
out["name"] = *v.Name
}
if v.Description != nil {
out["description"] = *v.Description
}
if v.Cover != nil {
out["cover"] = *v.Cover
}
if v.Type != nil {
out["type"] = *v.Type
}
return out
}
func (v ModifyBusiness) toM() officialwebsitemod.M {
out := officialwebsitemod.M{}
if v.Name != nil {
out["name"] = *v.Name
}
if v.EnglishName != nil {
out["englishName"] = *v.EnglishName
}
if v.Description != nil {
out["description"] = *v.Description
}
if v.Cover != nil {
out["cover"] = *v.Cover
}
if v.Detail != nil {
detail := make([]officialwebsitemod.M, 0, len(*v.Detail))
for _, d := range *v.Detail {
detail = append(detail, d.toM())
}
out["detail"] = detail
}
return out
}
func (v ModifyBusinessDetail) toM() officialwebsitemod.M {
out := officialwebsitemod.M{}
if v.Cover != nil {
out["cover"] = *v.Cover
}
if v.Description != nil {
out["description"] = *v.Description
}
return out
}
func (v ModifyContactInfo) toM() officialwebsitemod.M {
out := officialwebsitemod.M{}
if v.Type != nil {
out["type"] = *v.Type
}
if v.Value != nil {
out["value"] = *v.Value
}
return out
}
func (v ModifyCompanyInfo) toM() officialwebsitemod.M {
out := officialwebsitemod.M{}
if v.Name != nil {
out["name"] = *v.Name
}
if v.Address != nil {
out["address"] = *v.Address
}
if v.Description != nil {
out["description"] = *v.Description
}
if v.Contact != nil {
contacts := make([]officialwebsitemod.M, 0, len(*v.Contact))
for _, c := range *v.Contact {
contacts = append(contacts, c.toM())
}
out["contact"] = contacts
}
return out
}
func (v *ModifyAboutUs) toM() officialwebsitemod.M {
if v == nil {
return nil
}
out := officialwebsitemod.M{}
if v.Description != nil {
out["description"] = *v.Description
}
if v.Values != nil {
out["values"] = *v.Values
}
if v.ValuesImg != nil {
out["valuesImg"] = *v.ValuesImg
}
if v.Companies != nil {
companies := make([]officialwebsitemod.M, 0, len(*v.Companies))
for _, c := range *v.Companies {
companies = append(companies, c.toM())
}
out["companies"] = companies
}
return out
}
func (v ModifyFAQ) toM() officialwebsitemod.M {
out := officialwebsitemod.M{}
if v.Question != nil {
out["question"] = *v.Question
}
if v.Answer != nil {
out["answer"] = *v.Answer
}
return out
}
func (v ModifyCMSData) toM() officialwebsitemod.M {
out := officialwebsitemod.M{}
if v.Description != nil {
out["description"] = *v.Description
}
if v.Value != nil {
out["value"] = *v.Value
}
return out
}
// toM 将请求中的非 nil 字段构造为 Mongo $set 更新文档
func (req *ModifyBasicDataReq) toM() (officialwebsitemod.M, error) {
if req == nil {
return nil, stderr.CodeEmptyData
}
set := officialwebsitemod.M{}
if req.Title != nil {
set["title"] = *req.Title
}
if req.Description != nil {
set["description"] = *req.Description
}
if req.BannersDescription != nil {
set["bannersDescription"] = *req.BannersDescription
}
if req.BannersDuration != nil {
set["bannersDuration"] = *req.BannersDuration
}
if req.HomePageCMSDescription != nil {
set["homePageCMSDescription"] = *req.HomePageCMSDescription
}
if req.Banner != nil {
banners := make([]officialwebsitemod.M, 0, len(*req.Banner))
for _, b := range *req.Banner {
banners = append(banners, b.toM())
}
set["banner"] = banners
}
if req.HeroBanner != nil {
heroBanners := make([]officialwebsitemod.M, 0, len(*req.HeroBanner))
for _, h := range *req.HeroBanner {
heroBanners = append(heroBanners, h.toM())
}
set["heroBanner"] = heroBanners
}
if req.Masterpiece != nil {
masterpiece := make([]officialwebsitemod.M, 0, len(*req.Masterpiece))
for _, m := range *req.Masterpiece {
masterpiece = append(masterpiece, m.toM())
}
set["masterpiece"] = masterpiece
}
if req.Business != nil {
businesses := make([]officialwebsitemod.M, 0, len(*req.Business))
for _, b := range *req.Business {
businesses = append(businesses, b.toM())
}
set["business"] = businesses
}
if req.AboutUs != nil {
aboutUsM := req.AboutUs.toM()
if len(aboutUsM) > 0 {
set["aboutUs"] = aboutUsM
}
}
if req.FAQ != nil {
faqs := make([]officialwebsitemod.M, 0, len(*req.FAQ))
for _, f := range *req.FAQ {
faqs = append(faqs, f.toM())
}
set["faq"] = faqs
}
if req.HomePageCMS != nil {
cms := make([]officialwebsitemod.M, 0, len(*req.HomePageCMS))
for _, h := range *req.HomePageCMS {
cms = append(cms, h.toM())
}
set["homePageCMS"] = cms
}
if req.RecruitCMS != nil {
rcms := make([]officialwebsitemod.M, 0, len(*req.RecruitCMS))
for _, r := range *req.RecruitCMS {
rcms = append(rcms, r.toM())
}
set["recruitCMS"] = rcms
}
if req.Tags != nil {
set["tags"] = *req.Tags
}
if len(set) == 0 {
return nil, nil
}
set["updatedAt"] = time.Now().UTC()
return officialwebsitemod.M{"$set": set}, nil
}
+209
View File
@@ -0,0 +1,209 @@
package officialwebsiteser
import (
"time"
"91porn-server/common/stderr"
officialwebsitemod "91porn-server/models/v/officialWebsitemod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// ─────────────────────────────────────────
// Service functions
// ─────────────────────────────────────────
func ListBusiness(req *BusinessListReq) (int64, []officialwebsitemod.BusinessData, error) {
filter, err := req.Query()
if err != nil {
return 0, nil, err
}
mod := &officialwebsitemod.BusinessData{}
total, err := mod.Count(filter)
if err != nil {
return 0, nil, err
}
list, err := mod.FindMany(filter, req.FindOptions())
if err != nil {
return 0, nil, err
}
return total, list, nil
}
func CreateBusiness(req *ModifyBusinessReq) (officialwebsitemod.BusinessData, error) {
data := req.toMod()
if err := data.Create(); err != nil {
return officialwebsitemod.BusinessData{}, err
}
return data, nil
}
func UpdateBusiness(req *ModifyBusinessReq) (resp CountResp, err error) {
if req == nil || req.ID == "" {
return
}
id, parseErr := primitive.ObjectIDFromHex(req.ID)
if parseErr != nil || isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.BusinessData{}
if err = mod.FindOne(officialwebsitemod.M{"_id": id}); err != nil {
return
}
var update officialwebsitemod.M
if update, err = req.toM(); err != nil || update == nil {
return
}
resp.Count, err = mod.Update(officialwebsitemod.M{"_id": id}, update)
return
}
func DeleteBusiness(req *DeleteBusinessReq) (data DeleteBusinessResp, err error) {
if req == nil {
err = stderr.CodeEmptyData
return
}
var id primitive.ObjectID
if id, err = primitive.ObjectIDFromHex(req.ID); err != nil {
return
}
if isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.BusinessData{}
data.Count, err = mod.Delete(officialwebsitemod.M{"_id": id})
return
}
// ─────────────────────────────────────────
// Mutation (Modify) types
// ─────────────────────────────────────────
type ModifyBusinessReq struct {
ID string `json:"id" form:"id"`
Title *string `json:"title"`
Description *string `json:"description"`
Type *string `json:"type"`
ServiceOverview *string `json:"serviceOverview"`
CollaborationProcess *string `json:"collaborationProcess"`
CaseStudies *string `json:"caseStudies"`
}
// ─────────────────────────────────────────
// Delete types
// ─────────────────────────────────────────
type DeleteBusinessReq struct {
ID string `json:"id" form:"id"`
}
type DeleteBusinessResp struct {
Count int64 `json:"count"`
}
// ─────────────────────────────────────────
// toMod — ModifyBusinessReq → model struct
// ─────────────────────────────────────────
func (req *ModifyBusinessReq) toMod() officialwebsitemod.BusinessData {
if req == nil {
return officialwebsitemod.BusinessData{}
}
return officialwebsitemod.BusinessData{
Title: stringOrZero(req.Title),
Description: stringOrZero(req.Description),
Type: stringOrZero(req.Type),
ServiceOverview: stringOrZero(req.ServiceOverview),
CollaborationProcess: stringOrZero(req.CollaborationProcess),
CaseStudies: stringOrZero(req.CaseStudies),
}
}
// ─────────────────────────────────────────
// toM — ModifyBusinessReq → officialwebsitemod.M
// ─────────────────────────────────────────
func (req *ModifyBusinessReq) toM() (officialwebsitemod.M, error) {
if req == nil {
return nil, stderr.CodeEmptyData
}
set := officialwebsitemod.M{}
if req.Title != nil {
set["title"] = *req.Title
}
if req.Description != nil {
set["description"] = *req.Description
}
if req.Type != nil {
set["type"] = *req.Type
}
if req.ServiceOverview != nil {
set["serviceOverview"] = *req.ServiceOverview
}
if req.CollaborationProcess != nil {
set["collaborationProcess"] = *req.CollaborationProcess
}
if req.CaseStudies != nil {
set["caseStudies"] = *req.CaseStudies
}
if len(set) == 0 {
return nil, nil
}
set["updatedAt"] = time.Now().UTC()
return officialwebsitemod.M{"$set": set}, nil
}
// ─────────────────────────────────────────
// List types
// ─────────────────────────────────────────
type BusinessListReq struct {
PageReq
ID *string `form:"id"`
Title *string `form:"title"`
Description *string `form:"description"`
Type *string `form:"type"`
ServiceOverview *string `form:"serviceOverview"`
CollaborationProcess *string `form:"collaborationProcess"`
CaseStudies *string `form:"caseStudies"`
CreatedAt *time.Time `form:"createdAt"`
UpdatedAt *time.Time `form:"updatedAt"`
DeletedAt *time.Time `form:"deletedAt"`
}
func (r *BusinessListReq) Query() (officialwebsitemod.M, error) {
filter := officialwebsitemod.M{}
if err := parseObjectIDFilter(filter, "_id", r.ID); err != nil {
return nil, err
}
if r.Title != nil {
filter["title"] = *r.Title
}
if r.Description != nil {
filter["description"] = *r.Description
}
if r.Type != nil {
filter["type"] = *r.Type
}
if r.ServiceOverview != nil {
filter["serviceOverview"] = *r.ServiceOverview
}
if r.CollaborationProcess != nil {
filter["collaborationProcess"] = *r.CollaborationProcess
}
if r.CaseStudies != nil {
filter["caseStudies"] = *r.CaseStudies
}
if r.CreatedAt != nil {
filter["createdAt"] = *r.CreatedAt
}
if r.UpdatedAt != nil {
filter["updatedAt"] = *r.UpdatedAt
}
if r.DeletedAt != nil {
filter["deletedAt"] = *r.DeletedAt
}
return filter, nil
}
+286
View File
@@ -0,0 +1,286 @@
package officialwebsiteser
import (
"encoding/json"
"errors"
"fmt"
"reflect"
"strings"
"time"
officialwebsitemod "91porn-server/models/v/officialWebsitemod"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/options"
)
// ─────────────────────────────────────────
// List Helper Types and Functions
// ─────────────────────────────────────────
// CountResp 通用操作返回
type CountResp struct {
Count int64 `json:"count"`
}
// PageReq 分页请求
type PageReq struct {
Page int64 `form:"page"`
PageSize int64 `form:"pageSize"`
}
func (r *PageReq) FindOptions() *options.FindOptions {
page := r.Page
if page <= 0 {
page = 1
}
pageSize := r.PageSize
if pageSize <= 0 {
pageSize = 20
}
skip := (page - 1) * pageSize
return options.Find().SetSkip(skip).SetLimit(pageSize).SetSort(officialwebsitemod.M{"createdAt": -1})
}
func parseObjectIDFilter(filter officialwebsitemod.M, key string, raw *string) error {
if raw == nil || *raw == "" {
return nil
}
id, err := primitive.ObjectIDFromHex(*raw)
if err != nil {
return fmt.Errorf("invalid %s: %w", key, err)
}
filter[key] = id
return nil
}
func parseJSONFilter(filter officialwebsitemod.M, key string, raw *string, out interface{}) error {
if raw == nil || *raw == "" {
return nil
}
if err := json.Unmarshal([]byte(*raw), out); err != nil {
return fmt.Errorf("invalid %s json: %w", key, err)
}
v := reflect.ValueOf(out)
if v.Kind() == reflect.Ptr {
filter[key] = v.Elem().Interface()
return nil
}
filter[key] = out
return nil
}
// ────────────────────────────────────────
// Update Helper Functions
// ─────────────────────────────────────────
func modelToMap(doc interface{}) (officialwebsitemod.M, error) {
raw, err := bson.Marshal(doc)
if err != nil {
return nil, err
}
data := officialwebsitemod.M{}
if err := bson.Unmarshal(raw, &data); err != nil {
return nil, err
}
delete(data, "_id")
delete(data, "createdAt")
delete(data, "updatedAt")
delete(data, "deletedAt")
return data, nil
}
func modelChanged(oldDoc interface{}, newDoc interface{}) (bool, error) {
oldMap, err := modelToMap(oldDoc)
if err != nil {
return false, err
}
newMap, err := modelToMap(newDoc)
if err != nil {
return false, err
}
return !reflect.DeepEqual(oldMap, newMap), nil
}
func buildModelUpdateDoc(doc interface{}) (officialwebsitemod.M, error) {
data, err := modelToMap(doc)
if err != nil {
return nil, err
}
data["updatedAt"] = time.Now().UTC()
return officialwebsitemod.M{"$set": data}, nil
}
func buildPointerUpdateDoc(oldDoc interface{}, updateDoc interface{}) (officialwebsitemod.M, error) {
oldVal, err := structValueOf(oldDoc, "old doc")
if err != nil {
return nil, err
}
newVal, err := structValueOf(updateDoc, "update doc")
if err != nil {
return nil, err
}
set := officialwebsitemod.M{}
collectPointerChanges(set, oldVal, newVal)
if len(set) == 0 {
return nil, nil
}
set["updatedAt"] = time.Now().UTC()
return officialwebsitemod.M{"$set": set}, nil
}
func structValueOf(doc interface{}, label string) (reflect.Value, error) {
val := reflect.ValueOf(doc)
if val.Kind() == reflect.Ptr {
if val.IsNil() {
return reflect.Value{}, errors.New(label + " is nil")
}
val = val.Elem()
}
if val.Kind() != reflect.Struct {
return reflect.Value{}, errors.New(label + " must be struct")
}
return val, nil
}
func collectPointerChanges(set officialwebsitemod.M, oldVal reflect.Value, newVal reflect.Value) {
newType := newVal.Type()
for i := 0; i < newVal.NumField(); i++ {
addPointerFieldChange(set, oldVal, newVal.Field(i), newType.Field(i))
}
}
func addPointerFieldChange(set officialwebsitemod.M, oldVal reflect.Value, newField reflect.Value, fieldInfo reflect.StructField) {
if fieldInfo.Name == "ID" {
return
}
if !newField.IsValid() || newField.Kind() != reflect.Ptr || newField.IsNil() {
return
}
oldField := findStructFieldByName(oldVal, fieldInfo.Name)
if !oldField.IsValid() {
return
}
newValue := derefValue(newField)
oldValue := derefValue(oldField)
if !newValue.IsValid() || !oldValue.IsValid() {
return
}
if reflect.DeepEqual(oldValue.Interface(), newValue.Interface()) {
return
}
key := bsonFieldName(fieldInfo)
if key == "" {
return
}
set[key] = newValue.Interface()
}
func findStructFieldByName(val reflect.Value, name string) reflect.Value {
if !val.IsValid() {
return reflect.Value{}
}
if val.Kind() == reflect.Ptr {
if val.IsNil() {
return reflect.Value{}
}
val = val.Elem()
}
if val.Kind() != reflect.Struct {
return reflect.Value{}
}
if field := val.FieldByName(name); field.IsValid() {
return field
}
valType := val.Type()
for i := 0; i < val.NumField(); i++ {
fieldType := valType.Field(i)
if !fieldType.Anonymous {
continue
}
field := findStructFieldByName(val.Field(i), name)
if field.IsValid() {
return field
}
}
return reflect.Value{}
}
func derefValue(v reflect.Value) reflect.Value {
for v.IsValid() && v.Kind() == reflect.Ptr {
if v.IsNil() {
return reflect.Value{}
}
v = v.Elem()
}
return v
}
func bsonFieldName(field reflect.StructField) string {
if tag := field.Tag.Get("bson"); tag != "" {
name := strings.Split(tag, ",")[0]
if name != "" && name != "-" {
return name
}
}
if tag := field.Tag.Get("json"); tag != "" {
name := strings.Split(tag, ",")[0]
if name != "" && name != "-" {
return name
}
}
return field.Name
}
// ─────────────────────────────────────────
// Pointer helpers
// ─────────────────────────────────────────
func stringOrZero(v *string) string {
if v == nil {
return ""
}
return *v
}
func intOrZero(v *int) int {
if v == nil {
return 0
}
return *v
}
func objectIDOrZero(v *primitive.ObjectID) primitive.ObjectID {
if v == nil {
return primitive.NilObjectID
}
return *v
}
func int64OrZero(v *int64) int64 {
if v == nil {
return 0
}
return *v
}
func boolOrZero(v *bool) bool {
if v == nil {
return false
}
return *v
}
func isNilObjectID(id primitive.ObjectID) bool {
return id == primitive.NilObjectID
}
+300
View File
@@ -0,0 +1,300 @@
package officialwebsiteser
import (
"time"
"91porn-server/common/stderr"
officialwebsitemod "91porn-server/models/v/officialWebsitemod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// ─────────────────────────────────────────
// Service functions
// ─────────────────────────────────────────
func ListHero(req *HeroListReq) (int64, []officialwebsitemod.Hero, error) {
filter, err := req.Query()
if err != nil {
return 0, nil, err
}
mod := &officialwebsitemod.Hero{}
total, err := mod.Count(filter)
if err != nil {
return 0, nil, err
}
list, err := mod.FindMany(filter, req.FindOptions())
if err != nil {
return 0, nil, err
}
return total, list, nil
}
func CreateHero(req *ModifyHeroReq) (officialwebsitemod.Hero, error) {
data := req.toMod()
// SEO slug:手填优先(校验唯一),为空则由名称生成雏形并去重
var err error
if data.SeoSlug, err = resolveHeroSeoSlug(data.SeoSlug, data.Name, primitive.NilObjectID); err != nil {
return officialwebsitemod.Hero{}, err
}
if err := data.Create(); err != nil {
return officialwebsitemod.Hero{}, err
}
return data, nil
}
// resolveHeroSeoSlug 处理 seoSlug:手填则校验格式并查重(冲突报错);为空则由名称生成雏形并追加后缀去重。
func resolveHeroSeoSlug(provided, name string, excludeID primitive.ObjectID) (string, error) {
return ResolveSeoSlug(provided, name, func(slug string) (bool, error) {
filter := officialwebsitemod.M{"seoSlug": slug}
if !excludeID.IsZero() {
filter["_id"] = officialwebsitemod.M{"$ne": excludeID}
}
n, err := (&officialwebsitemod.Hero{}).Count(filter)
return n > 0, err
})
}
func UpdateHero(req *ModifyHeroReq) (resp CountResp, err error) {
if req == nil || req.ID == "" {
return
}
id, parseErr := primitive.ObjectIDFromHex(req.ID)
if parseErr != nil || isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.Hero{}
if err = mod.FindOne(officialwebsitemod.M{"_id": id}); err != nil {
return
}
// 若提交了 seoSlug,先校验/去重(排除自身),回写后由 toM 落库
if req.SeoSlug != nil {
var slug string
if slug, err = resolveHeroSeoSlug(*req.SeoSlug, mod.Name, id); err != nil {
return
}
*req.SeoSlug = slug
}
var update officialwebsitemod.M
if update, err = req.toM(); err != nil || update == nil {
return
}
resp.Count, err = mod.Update(officialwebsitemod.M{"_id": id}, update)
return
}
func DeleteHero(req *DeleteHeroReq) (data DeleteHeroResp, err error) {
if req == nil {
err = stderr.CodeEmptyData
return
}
var id primitive.ObjectID
if id, err = primitive.ObjectIDFromHex(req.ID); err != nil {
return
}
if isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.Hero{}
data.Count, err = mod.Delete(officialwebsitemod.M{"_id": id})
return
}
// ─────────────────────────────────────────
// Mutation (Modify) types
// ─────────────────────────────────────────
type ModifyHeroReq struct {
ID string `json:"id" form:"id"`
Name *string `json:"name"`
SeoSlug *string `json:"seoSlug"`
Avatar *string `json:"avatar"`
Description *string `json:"description"`
FansCount *int64 `json:"fansCount"`
Height *int64 `json:"height"`
Bust *int64 `json:"bust"`
Waist *int64 `json:"waist"`
Hip *int64 `json:"hip"`
Cover *string `json:"cover"`
Partners *[]primitive.ObjectID `json:"partners"`
Sort *int64 `json:"sort"`
IsActive *bool `json:"isActive"`
}
// ─────────────────────────────────────────
// Delete types
// ─────────────────────────────────────────
type DeleteHeroReq struct {
ID string `json:"id" form:"id"`
}
type DeleteHeroResp struct {
Count int64 `json:"count"`
}
// ─────────────────────────────────────────
// toMod — ModifyHeroReq → model struct
// ─────────────────────────────────────────
func (req *ModifyHeroReq) toMod() officialwebsitemod.Hero {
if req == nil {
return officialwebsitemod.Hero{}
}
data := officialwebsitemod.Hero{
Name: stringOrZero(req.Name),
Avatar: stringOrZero(req.Avatar),
Description: stringOrZero(req.Description),
FansCount: int64OrZero(req.FansCount),
Height: int64OrZero(req.Height),
Bust: int64OrZero(req.Bust),
Waist: int64OrZero(req.Waist),
Hip: int64OrZero(req.Hip),
Cover: stringOrZero(req.Cover),
SortModel: officialwebsitemod.SortModel{
Sort: int64OrZero(req.Sort),
IsActive: boolOrZero(req.IsActive),
},
}
if req.Partners != nil {
data.Partners = *req.Partners
}
if req.SeoSlug != nil {
data.SeoSlug = *req.SeoSlug
}
return data
}
// ─────────────────────────────────────────
// toM — ModifyHeroReq → officialwebsitemod.M
// ─────────────────────────────────────────
func (req *ModifyHeroReq) toM() (officialwebsitemod.M, error) {
if req == nil {
return nil, stderr.CodeEmptyData
}
set := officialwebsitemod.M{}
if req.Name != nil {
set["name"] = *req.Name
}
if req.SeoSlug != nil && *req.SeoSlug != "" {
set["seoSlug"] = *req.SeoSlug
}
if req.Avatar != nil {
set["avatar"] = *req.Avatar
}
if req.Description != nil {
set["description"] = *req.Description
}
if req.FansCount != nil {
set["fansCount"] = *req.FansCount
}
if req.Height != nil {
set["height"] = *req.Height
}
if req.Bust != nil {
set["bust"] = *req.Bust
}
if req.Waist != nil {
set["waist"] = *req.Waist
}
if req.Hip != nil {
set["hip"] = *req.Hip
}
if req.Cover != nil {
set["cover"] = *req.Cover
}
if req.Partners != nil {
set["partners"] = *req.Partners
}
if req.Sort != nil {
set["sort"] = *req.Sort
}
if req.IsActive != nil {
set["isActive"] = *req.IsActive
}
if len(set) == 0 {
return nil, nil
}
set["updatedAt"] = time.Now().UTC()
return officialwebsitemod.M{"$set": set}, nil
}
// ─────────────────────────────────────────
// List types
// ─────────────────────────────────────────
type HeroListReq struct {
PageReq
ID *string `form:"id"`
Name *string `form:"name"`
Avatar *string `form:"avatar"`
Height *int64 `form:"height"`
Bust *int64 `form:"bust"`
Waist *int64 `form:"waist"`
Hip *int64 `form:"hip"`
Description *string `form:"description"`
FansCount *int64 `form:"fansCount"`
Cover *string `form:"cover"`
Partners *string `form:"partners"`
Sort *int64 `form:"sort"`
IsActive *bool `form:"isActive"`
CreatedAt *time.Time `form:"createdAt"`
UpdatedAt *time.Time `form:"updatedAt"`
DeletedAt *time.Time `form:"deletedAt"`
}
func (r *HeroListReq) Query() (officialwebsitemod.M, error) {
filter := officialwebsitemod.M{}
if err := parseObjectIDFilter(filter, "_id", r.ID); err != nil {
return nil, err
}
if r.Name != nil {
filter["name"] = *r.Name
}
if r.Avatar != nil {
filter["avatar"] = *r.Avatar
}
if r.Height != nil {
filter["height"] = *r.Height
}
if r.Bust != nil {
filter["bust"] = *r.Bust
}
if r.Waist != nil {
filter["waist"] = *r.Waist
}
if r.Hip != nil {
filter["hip"] = *r.Hip
}
if r.Description != nil {
filter["description"] = *r.Description
}
if r.FansCount != nil {
filter["fansCount"] = *r.FansCount
}
if r.Cover != nil {
filter["cover"] = *r.Cover
}
if err := parseJSONFilter(filter, "partners", r.Partners, &[]primitive.ObjectID{}); err != nil {
return nil, err
}
if r.Sort != nil {
filter["sort"] = *r.Sort
}
if r.IsActive != nil {
filter["isActive"] = *r.IsActive
}
if r.CreatedAt != nil {
filter["createdAt"] = *r.CreatedAt
}
if r.UpdatedAt != nil {
filter["updatedAt"] = *r.UpdatedAt
}
if r.DeletedAt != nil {
filter["deletedAt"] = *r.DeletedAt
}
return filter, nil
}
+220
View File
@@ -0,0 +1,220 @@
package officialwebsiteser
import (
"time"
"91porn-server/common/stderr"
officialwebsitemod "91porn-server/models/v/officialWebsitemod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// ─────────────────────────────────────────
// Service functions
// ─────────────────────────────────────────
func ListJobList(req *JobListReq) (int64, []officialwebsitemod.Job, error) {
filter, err := req.Query()
if err != nil {
return 0, nil, err
}
mod := &officialwebsitemod.Job{}
total, err := mod.Count(filter)
if err != nil {
return 0, nil, err
}
list, err := mod.FindMany(filter, req.FindOptions())
if err != nil {
return 0, nil, err
}
return total, list, nil
}
func CreateJobList(req *ModifyJobReq) (officialwebsitemod.Job, error) {
data := req.toMod()
if err := data.Create(); err != nil {
return officialwebsitemod.Job{}, err
}
return data, nil
}
func UpdateJobList(req *ModifyJobReq) (resp CountResp, err error) {
if req == nil || req.ID == "" {
return
}
id, parseErr := primitive.ObjectIDFromHex(req.ID)
if parseErr != nil || isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.Job{}
if err = mod.FindOne(officialwebsitemod.M{"_id": id}); err != nil {
return
}
var update officialwebsitemod.M
if update, err = req.toM(); err != nil || update == nil {
return
}
resp.Count, err = mod.Update(officialwebsitemod.M{"_id": id}, update)
return
}
func DeleteJobList(req *DeleteJobReq) (data DeleteJobResp, err error) {
if req == nil {
err = stderr.CodeEmptyData
return
}
var id primitive.ObjectID
if id, err = primitive.ObjectIDFromHex(req.ID); err != nil {
return
}
if isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.Job{}
data.Count, err = mod.Delete(officialwebsitemod.M{"_id": id})
return
}
// ─────────────────────────────────────────
// Mutation (Modify) types
// ─────────────────────────────────────────
type ModifyJobReq struct {
ID string `json:"id" form:"id"`
Title *string `json:"title"`
Description *string `json:"description"`
Requirements *string `json:"requirements"`
Benefits *string `json:"benefits"`
JDUrl *string `json:"jdUrl"`
Sort *int64 `json:"sort"`
IsActive *bool `json:"isActive"`
}
// ─────────────────────────────────────────
// Delete types
// ─────────────────────────────────────────
type DeleteJobReq struct {
ID string `json:"id" form:"id"`
}
type DeleteJobResp struct {
Count int64 `json:"count"`
}
// ─────────────────────────────────────────
// toMod — ModifyJobReq → model struct
// ─────────────────────────────────────────
func (req *ModifyJobReq) toMod() officialwebsitemod.Job {
if req == nil {
return officialwebsitemod.Job{}
}
return officialwebsitemod.Job{
Title: stringOrZero(req.Title),
Description: stringOrZero(req.Description),
Requirements: stringOrZero(req.Requirements),
Benefits: stringOrZero(req.Benefits),
JDUrl: stringOrZero(req.JDUrl),
SortModel: officialwebsitemod.SortModel{
Sort: int64OrZero(req.Sort),
IsActive: boolOrZero(req.IsActive),
},
}
}
// ─────────────────────────────────────────
// toM — ModifyJobReq → officialwebsitemod.M
// ─────────────────────────────────────────
func (req *ModifyJobReq) toM() (officialwebsitemod.M, error) {
if req == nil {
return nil, stderr.CodeEmptyData
}
set := officialwebsitemod.M{}
if req.Title != nil {
set["title"] = *req.Title
}
if req.Description != nil {
set["description"] = *req.Description
}
if req.Requirements != nil {
set["requirements"] = *req.Requirements
}
if req.Benefits != nil {
set["benefits"] = *req.Benefits
}
if req.JDUrl != nil {
set["jdUrl"] = *req.JDUrl
}
if req.Sort != nil {
set["sort"] = *req.Sort
}
if req.IsActive != nil {
set["isActive"] = *req.IsActive
}
if len(set) == 0 {
return nil, nil
}
set["updatedAt"] = time.Now().UTC()
return officialwebsitemod.M{"$set": set}, nil
}
// ─────────────────────────────────────────
// List types
// ─────────────────────────────────────────
type JobListReq struct {
PageReq
ID *string `form:"id"`
Title *string `form:"title"`
Description *string `form:"description"`
Requirements *string `form:"requirements"`
Benefits *string `form:"benefits"`
JDUrl *string `form:"jdUrl"`
Sort *int64 `form:"sort"`
IsActive *bool `form:"isActive"`
CreatedAt *time.Time `form:"createdAt"`
UpdatedAt *time.Time `form:"updatedAt"`
DeletedAt *time.Time `form:"deletedAt"`
}
func (r *JobListReq) Query() (officialwebsitemod.M, error) {
filter := officialwebsitemod.M{}
if err := parseObjectIDFilter(filter, "_id", r.ID); err != nil {
return nil, err
}
if r.Title != nil {
filter["title"] = *r.Title
}
if r.Description != nil {
filter["description"] = *r.Description
}
if r.Requirements != nil {
filter["requirements"] = *r.Requirements
}
if r.Benefits != nil {
filter["benefits"] = *r.Benefits
}
if r.JDUrl != nil {
filter["jdUrl"] = *r.JDUrl
}
if r.Sort != nil {
filter["sort"] = *r.Sort
}
if r.IsActive != nil {
filter["isActive"] = *r.IsActive
}
if r.CreatedAt != nil {
filter["createdAt"] = *r.CreatedAt
}
if r.UpdatedAt != nil {
filter["updatedAt"] = *r.UpdatedAt
}
if r.DeletedAt != nil {
filter["deletedAt"] = *r.DeletedAt
}
return filter, nil
}
+261
View File
@@ -0,0 +1,261 @@
package officialwebsiteser
import (
"time"
"91porn-server/common/stderr"
officialwebsitemod "91porn-server/models/v/officialWebsitemod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// ─────────────────────────────────────────
// Service functions
// ─────────────────────────────────────────
func ListNews(req *NewsListReq) (int64, []officialwebsitemod.News, error) {
filter, err := req.Query()
if err != nil {
return 0, nil, err
}
mod := &officialwebsitemod.News{}
total, err := mod.Count(filter)
if err != nil {
return 0, nil, err
}
list, err := mod.FindMany(filter, req.FindOptions())
if err != nil {
return 0, nil, err
}
return total, list, nil
}
// resolveNewsSeoSlug 处理 seoSlug:手填则校验格式并查重(冲突报错);为空则由标题生成雏形并追加后缀去重。
func resolveNewsSeoSlug(provided, title string, excludeID primitive.ObjectID) (string, error) {
return ResolveSeoSlug(provided, title, func(slug string) (bool, error) {
filter := officialwebsitemod.M{"seoSlug": slug}
if !excludeID.IsZero() {
filter["_id"] = officialwebsitemod.M{"$ne": excludeID}
}
n, err := (&officialwebsitemod.News{}).Count(filter)
return n > 0, err
})
}
func CreateNews(req *ModifyNewsReq) (officialwebsitemod.News, error) {
data := req.toMod()
// SEO slug:手填优先(校验唯一),为空则由标题生成雏形并去重
slug, err := resolveNewsSeoSlug(data.SeoSlug, data.Title, primitive.NilObjectID)
if err != nil {
return officialwebsitemod.News{}, err
}
data.SeoSlug = slug
if err := data.Create(); err != nil {
return officialwebsitemod.News{}, err
}
return data, nil
}
func UpdateNews(req *ModifyNewsReq) (resp CountResp, err error) {
if req == nil || req.ID == "" {
return
}
id, parseErr := primitive.ObjectIDFromHex(req.ID)
if parseErr != nil || isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.News{}
if err = mod.FindOne(officialwebsitemod.M{"_id": id}); err != nil {
return
}
// 若提交了 seoSlug,先校验/去重(排除自身),回写后由 toM 落库
if req.SeoSlug != nil {
var slug string
if slug, err = resolveNewsSeoSlug(*req.SeoSlug, mod.Title, id); err != nil {
return
}
*req.SeoSlug = slug
}
var update officialwebsitemod.M
if update, err = req.toM(); err != nil || update == nil {
return
}
resp.Count, err = mod.Update(officialwebsitemod.M{"_id": id}, update)
return
}
func DeleteNews(req *DeleteNewsReq) (data DeleteNewsResp, err error) {
if req == nil {
err = stderr.CodeEmptyData
return
}
var id primitive.ObjectID
if id, err = primitive.ObjectIDFromHex(req.ID); err != nil {
return
}
if isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.News{}
data.Count, err = mod.Delete(officialwebsitemod.M{"_id": id})
return
}
// ─────────────────────────────────────────
// Mutation (Modify) types
// ─────────────────────────────────────────
type ModifyNewsReq struct {
ID string `json:"id" form:"id"`
Title *string `json:"title"`
SeoSlug *string `json:"seoSlug"`
Description *string `json:"description"`
Cover *string `json:"cover"`
Url *string `json:"url"`
Tags *[]officialwebsitemod.Tag `json:"tags"`
Detail *string `json:"detail"`
Sort *int64 `json:"sort"`
IsActive *bool `json:"isActive"`
}
// ─────────────────────────────────────────
// Delete types
// ─────────────────────────────────────────
type DeleteNewsReq struct {
ID string `json:"id" form:"id"`
}
type DeleteNewsResp struct {
Count int64 `json:"count"`
}
// ─────────────────────────────────────────
// toMod — ModifyNewsReq → model struct
// ─────────────────────────────────────────
func (req *ModifyNewsReq) toMod() officialwebsitemod.News {
if req == nil {
return officialwebsitemod.News{}
}
data := officialwebsitemod.News{
Title: stringOrZero(req.Title),
Description: stringOrZero(req.Description),
Cover: stringOrZero(req.Cover),
Url: stringOrZero(req.Url),
Detail: stringOrZero(req.Detail),
SortModel: officialwebsitemod.SortModel{
Sort: int64OrZero(req.Sort),
IsActive: boolOrZero(req.IsActive),
},
}
if req.Tags != nil {
data.Tags = *req.Tags
}
if req.SeoSlug != nil {
data.SeoSlug = *req.SeoSlug
}
return data
}
// ─────────────────────────────────────────
// toM — ModifyNewsReq → officialwebsitemod.M
// ─────────────────────────────────────────
func (req *ModifyNewsReq) toM() (officialwebsitemod.M, error) {
if req == nil {
return nil, stderr.CodeEmptyData
}
set := officialwebsitemod.M{}
if req.Title != nil {
set["title"] = *req.Title
}
if req.SeoSlug != nil && *req.SeoSlug != "" {
set["seoSlug"] = *req.SeoSlug
}
if req.Description != nil {
set["description"] = *req.Description
}
if req.Cover != nil {
set["cover"] = *req.Cover
}
if req.Url != nil {
set["url"] = *req.Url
}
if req.Tags != nil {
set["tags"] = *req.Tags
}
if req.Detail != nil {
set["detail"] = *req.Detail
}
if req.Sort != nil {
set["sort"] = *req.Sort
}
if req.IsActive != nil {
set["isActive"] = *req.IsActive
}
if len(set) == 0 {
return nil, nil
}
set["updatedAt"] = time.Now().UTC()
return officialwebsitemod.M{"$set": set}, nil
}
// ─────────────────────────────────────────
// List types
// ─────────────────────────────────────────
type NewsListReq struct {
PageReq
ID *string `form:"id"`
Title *string `form:"title"`
Description *string `form:"description"`
Cover *string `form:"cover"`
Url *string `form:"url"`
Tags *string `form:"tags"`
Sort *int64 `form:"sort"`
IsActive *bool `form:"isActive"`
CreatedAt *time.Time `form:"createdAt"`
UpdatedAt *time.Time `form:"updatedAt"`
DeletedAt *time.Time `form:"deletedAt"`
}
func (r *NewsListReq) Query() (officialwebsitemod.M, error) {
filter := officialwebsitemod.M{}
if err := parseObjectIDFilter(filter, "_id", r.ID); err != nil {
return nil, err
}
if r.Title != nil {
filter["title"] = *r.Title
}
if r.Description != nil {
filter["description"] = *r.Description
}
if r.Cover != nil {
filter["cover"] = *r.Cover
}
if r.Url != nil {
filter["url"] = *r.Url
}
if err := parseJSONFilter(filter, "tags", r.Tags, &[]officialwebsitemod.Tag{}); err != nil {
return nil, err
}
if r.Sort != nil {
filter["sort"] = *r.Sort
}
if r.IsActive != nil {
filter["isActive"] = *r.IsActive
}
if r.CreatedAt != nil {
filter["createdAt"] = *r.CreatedAt
}
if r.UpdatedAt != nil {
filter["updatedAt"] = *r.UpdatedAt
}
if r.DeletedAt != nil {
filter["deletedAt"] = *r.DeletedAt
}
return filter, nil
}
+211
View File
@@ -0,0 +1,211 @@
package officialwebsiteser
import (
"time"
"91porn-server/common/stderr"
officialwebsitemod "91porn-server/models/v/officialWebsitemod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// ─────────────────────────────────────────
// Service functions
// ─────────────────────────────────────────
func ListPartner(req *PartnerListReq) (int64, []officialwebsitemod.Partner, error) {
filter, err := req.Query()
if err != nil {
return 0, nil, err
}
mod := &officialwebsitemod.Partner{}
total, err := mod.Count(filter)
if err != nil {
return 0, nil, err
}
list, err := mod.FindMany(filter, req.FindOptions())
if err != nil {
return 0, nil, err
}
return total, list, nil
}
func CreatePartner(req *ModifyPartnerReq) (officialwebsitemod.Partner, error) {
data := req.toMod()
if err := data.Create(); err != nil {
return officialwebsitemod.Partner{}, err
}
return data, nil
}
func UpdatePartner(req *ModifyPartnerReq) (resp CountResp, err error) {
if req == nil || req.ID == "" {
return
}
id, parseErr := primitive.ObjectIDFromHex(req.ID)
if parseErr != nil || isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.Partner{}
if err = mod.FindOne(officialwebsitemod.M{"_id": id}); err != nil {
return
}
var update officialwebsitemod.M
if update, err = req.toM(); err != nil || update == nil {
return
}
resp.Count, err = mod.Update(officialwebsitemod.M{"_id": id}, update)
return
}
func DeletePartner(req *DeletePartnerReq) (data DeletePartnerResp, err error) {
if req == nil {
err = stderr.CodeEmptyData
return
}
var id primitive.ObjectID
if id, err = primitive.ObjectIDFromHex(req.ID); err != nil {
return
}
if isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.Partner{}
data.Count, err = mod.Delete(officialwebsitemod.M{"_id": id})
return
}
// ─────────────────────────────────────────
// Mutation (Modify) types
// ─────────────────────────────────────────
type ModifyPartnerReq struct {
ID string `json:"id" form:"id"`
Name *string `json:"name"`
Description *string `json:"description"`
Logo *string `json:"logo"`
Url *string `json:"url"`
Sort *int64 `json:"sort"`
IsActive *bool `json:"isActive"`
}
// ─────────────────────────────────────────
// Delete types
// ─────────────────────────────────────────
type DeletePartnerReq struct {
ID string `json:"id" form:"id"`
}
type DeletePartnerResp struct {
Count int64 `json:"count"`
}
// ─────────────────────────────────────────
// toMod — ModifyPartnerReq → model struct
// ─────────────────────────────────────────
func (req *ModifyPartnerReq) toMod() officialwebsitemod.Partner {
if req == nil {
return officialwebsitemod.Partner{}
}
return officialwebsitemod.Partner{
Name: stringOrZero(req.Name),
Description: stringOrZero(req.Description),
Logo: stringOrZero(req.Logo),
Url: stringOrZero(req.Url),
SortModel: officialwebsitemod.SortModel{
Sort: int64OrZero(req.Sort),
IsActive: boolOrZero(req.IsActive),
},
}
}
// ─────────────────────────────────────────
// toM — ModifyPartnerReq → officialwebsitemod.M
// ─────────────────────────────────────────
func (req *ModifyPartnerReq) toM() (officialwebsitemod.M, error) {
if req == nil {
return nil, stderr.CodeEmptyData
}
set := officialwebsitemod.M{}
if req.Name != nil {
set["name"] = *req.Name
}
if req.Description != nil {
set["description"] = *req.Description
}
if req.Logo != nil {
set["logo"] = *req.Logo
}
if req.Url != nil {
set["url"] = *req.Url
}
if req.Sort != nil {
set["sort"] = *req.Sort
}
if req.IsActive != nil {
set["isActive"] = *req.IsActive
}
if len(set) == 0 {
return nil, nil
}
set["updatedAt"] = time.Now().UTC()
return officialwebsitemod.M{"$set": set}, nil
}
// ─────────────────────────────────────────
// List types
// ─────────────────────────────────────────
type PartnerListReq struct {
PageReq
ID *string `form:"id"`
Name *string `form:"name"`
Description *string `form:"description"`
Logo *string `form:"logo"`
Url *string `form:"url"`
Sort *int64 `form:"sort"`
IsActive *bool `form:"isActive"`
CreatedAt *time.Time `form:"createdAt"`
UpdatedAt *time.Time `form:"updatedAt"`
DeletedAt *time.Time `form:"deletedAt"`
}
func (r *PartnerListReq) Query() (officialwebsitemod.M, error) {
filter := officialwebsitemod.M{}
if err := parseObjectIDFilter(filter, "_id", r.ID); err != nil {
return nil, err
}
if r.Name != nil {
filter["name"] = *r.Name
}
if r.Description != nil {
filter["description"] = *r.Description
}
if r.Logo != nil {
filter["logo"] = *r.Logo
}
if r.Url != nil {
filter["url"] = *r.Url
}
if r.Sort != nil {
filter["sort"] = *r.Sort
}
if r.IsActive != nil {
filter["isActive"] = *r.IsActive
}
if r.CreatedAt != nil {
filter["createdAt"] = *r.CreatedAt
}
if r.UpdatedAt != nil {
filter["updatedAt"] = *r.UpdatedAt
}
if r.DeletedAt != nil {
filter["deletedAt"] = *r.DeletedAt
}
return filter, nil
}
@@ -0,0 +1,331 @@
package officialwebsiteser
import (
"time"
"91porn-server/common/stderr"
officialwebsitemod "91porn-server/models/v/officialWebsitemod"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// ─────────────────────────────────────────
// Service functions
// ─────────────────────────────────────────
func ListPhotograph(req *ListPhotographReq) (int64, []officialwebsitemod.Photograph, error) {
filter, err := req.Query()
if err != nil {
return 0, nil, err
}
mod := &officialwebsitemod.Photograph{}
total, err := mod.Count(filter)
if err != nil {
return 0, nil, err
}
list, err := mod.FindMany(filter, req.FindOptions())
if err != nil {
return 0, nil, err
}
return total, list, nil
}
// resolvePhotographSeoSlug 处理 seoSlug:手填则校验格式并查重(冲突报错);为空则由标题生成雏形并追加后缀去重。
func resolvePhotographSeoSlug(provided, name string, excludeID primitive.ObjectID) (string, error) {
return ResolveSeoSlug(provided, name, func(slug string) (bool, error) {
filter := officialwebsitemod.M{"seoSlug": slug}
if !excludeID.IsZero() {
filter["_id"] = officialwebsitemod.M{"$ne": excludeID}
}
n, err := (&officialwebsitemod.Photograph{}).Count(filter)
return n > 0, err
})
}
func CreatePhotograph(req *ModifyPhotographReq) (officialwebsitemod.Photograph, error) {
data, err := req.toMod()
if err != nil {
return officialwebsitemod.Photograph{}, err
}
// SEO slug:手填优先(校验唯一),为空则由标题生成雏形并去重
if data.SeoSlug, err = resolvePhotographSeoSlug(data.SeoSlug, data.Name, primitive.NilObjectID); err != nil {
return officialwebsitemod.Photograph{}, err
}
if err := data.Create(); err != nil {
return officialwebsitemod.Photograph{}, err
}
return data, nil
}
func UpdatePhotograph(req *ModifyPhotographReq) (resp CountResp, err error) {
if req == nil || req.ID == "" {
return
}
id, parseErr := primitive.ObjectIDFromHex(req.ID)
if parseErr != nil || isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.Photograph{}
if err = mod.FindOne(officialwebsitemod.M{"_id": id}); err != nil {
return
}
if isNilObjectID(mod.ID) {
err = stderr.ErrParamError
return
}
// 若提交了 seoSlug,先校验/去重(排除自身),回写后由 toM 落库
if req.SeoSlug != nil {
var slug string
if slug, err = resolvePhotographSeoSlug(*req.SeoSlug, mod.Name, id); err != nil {
return
}
*req.SeoSlug = slug
}
var update officialwebsitemod.M
if update, err = req.toM(); err != nil || update == nil {
return
}
resp.Count, err = mod.Update(officialwebsitemod.M{"_id": id}, update)
return
}
func DeletePhotograph(req *DeletePhotographReq) (data DeletePhotographResp, err error) {
if req == nil {
err = stderr.CodeEmptyData
return
}
var id primitive.ObjectID
if id, err = primitive.ObjectIDFromHex(req.ID); err != nil {
return
}
if isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.Photograph{}
data.Count, err = mod.Delete(officialwebsitemod.M{"_id": id})
return
}
func BatchUpdatePhotographOwner(req *BatchUpdatePhotographOwnerReq) (data BatchUpdatePhotographOwnerResp, err error) {
if req == nil {
err = stderr.ErrParamError
return
}
// Check owner is exist
var ownerID primitive.ObjectID
ownerID, err = primitive.ObjectIDFromHex(req.OwnerID)
if err != nil {
return
}
var heroMod = &officialwebsitemod.Hero{}
err = heroMod.FindOne(officialwebsitemod.M{"_id": ownerID})
if err != nil {
return
}
if isNilObjectID(heroMod.ID) {
err = stderr.OfficialWebsiteHeroExist
return
}
var photographIDs []primitive.ObjectID
for i := range req.PhotographIDs {
photographID, parseErr := primitive.ObjectIDFromHex(req.PhotographIDs[i])
err = parseErr
if err != nil {
return
}
photographIDs = append(photographIDs, photographID)
}
var photographMod = &officialwebsitemod.Photograph{}
filter := bson.M{"_id": bson.M{"$in": photographIDs}}
update := bson.M{"$set": bson.M{"heroId": ownerID}}
data.Count, err = photographMod.Update(filter, update)
return
}
// ─────────────────────────────────────────
// Batch (Modify) types
// ─────────────────────────────────────────
type BatchUpdatePhotographOwnerReq struct {
PhotographIDs []string `json:"photograph_ids"`
OwnerID string `json:"owner_id"`
}
type BatchUpdatePhotographOwnerResp struct {
Count int64 `json:"count"`
}
// ─────────────────────────────────────────
// Mutation (Modify) types
// ─────────────────────────────────────────
type ModifyPhotographReq struct {
ID string `json:"id" form:"id"`
HeroID *string `json:"heroID"`
Name *string `json:"name"`
SeoSlug *string `json:"seoSlug"`
Description *string `json:"description"`
Cover *string `json:"cover"`
Photos *[]string `json:"photos"`
Tags *[]officialwebsitemod.Tag `json:"tags"`
Sort *int64 `json:"sort"`
IsActive *bool `json:"isActive"`
}
// ─────────────────────────────────────────
// Delete types
// ─────────────────────────────────────────
type DeletePhotographReq struct {
ID string `json:"id" form:"id"`
}
type DeletePhotographResp struct {
Count int64 `json:"count"`
}
// ─────────────────────────────────────────
// toMod — ModifyPhotographReq → model struct
// ─────────────────────────────────────────
func (req *ModifyPhotographReq) toMod() (data officialwebsitemod.Photograph, err error) {
if req == nil {
return
}
data = officialwebsitemod.Photograph{
Name: stringOrZero(req.Name),
Description: stringOrZero(req.Description),
Cover: stringOrZero(req.Cover),
SortModel: officialwebsitemod.SortModel{
Sort: int64OrZero(req.Sort),
IsActive: boolOrZero(req.IsActive),
},
}
if req.HeroID != nil {
data.HeroID, err = primitive.ObjectIDFromHex(stringOrZero(req.HeroID))
if err != nil {
return
}
}
if req.Photos != nil {
data.Photos = *req.Photos
}
if req.Tags != nil {
data.Tags = *req.Tags
}
if req.SeoSlug != nil {
data.SeoSlug = *req.SeoSlug
}
return
}
// ─────────────────────────────────────────
// toM — ModifyPhotographReq → officialwebsitemod.M
// ─────────────────────────────────────────
func (req *ModifyPhotographReq) toM() (officialwebsitemod.M, error) {
if req == nil {
return nil, stderr.CodeEmptyData
}
set := officialwebsitemod.M{}
if req.HeroID != nil {
if *req.HeroID != "" {
heroID, err := primitive.ObjectIDFromHex(*req.HeroID)
if err != nil || isNilObjectID(heroID) {
return nil, stderr.ErrParamError
}
set["heroId"] = heroID
}
}
if req.Name != nil {
set["name"] = *req.Name
}
if req.SeoSlug != nil && *req.SeoSlug != "" {
set["seoSlug"] = *req.SeoSlug
}
if req.Description != nil {
set["description"] = *req.Description
}
if req.Cover != nil {
set["cover"] = *req.Cover
}
if req.Photos != nil {
set["photos"] = *req.Photos
}
if req.Tags != nil {
set["tags"] = *req.Tags
}
if req.Sort != nil {
set["sort"] = *req.Sort
}
if req.IsActive != nil {
set["isActive"] = *req.IsActive
}
if len(set) == 0 {
return nil, nil
}
set["updatedAt"] = time.Now().UTC()
return officialwebsitemod.M{"$set": set}, nil
}
// ─────────────────────────────────────────
// List types
// ─────────────────────────────────────────
type ListPhotographReq struct {
PageReq
ID *string `form:"id"`
HeroID *string `form:"heroID"`
Name *string `form:"name"`
Description *string `form:"description"`
Cover *string `form:"cover"`
Photos *string `form:"photos"`
Sort *int64 `form:"sort"`
IsActive *bool `form:"isActive"`
CreatedAt *time.Time `form:"createdAt"`
UpdatedAt *time.Time `form:"updatedAt"`
DeletedAt *time.Time `form:"deletedAt"`
}
func (l *ListPhotographReq) Query() (filter officialwebsitemod.M, err error) {
filter = officialwebsitemod.M{}
if err = parseObjectIDFilter(filter, "_id", l.ID); err != nil {
return nil, err
}
if err = parseObjectIDFilter(filter, "heroId", l.HeroID); err != nil {
return nil, err
}
if l.Name != nil {
filter["name"] = *l.Name
}
if l.Description != nil {
filter["description"] = *l.Description
}
if l.Cover != nil {
filter["cover"] = *l.Cover
}
if err = parseJSONFilter(filter, "photos", l.Photos, &[]string{}); err != nil {
return nil, err
}
if l.Sort != nil {
filter["sort"] = *l.Sort
}
if l.IsActive != nil {
filter["isActive"] = *l.IsActive
}
if l.CreatedAt != nil {
filter["createdAt"] = *l.CreatedAt
}
if l.UpdatedAt != nil {
filter["updatedAt"] = *l.UpdatedAt
}
if l.DeletedAt != nil {
filter["deletedAt"] = *l.DeletedAt
}
return filter, nil
}
@@ -0,0 +1,221 @@
package officialwebsiteser
import (
"time"
"91porn-server/common/stderr"
officialwebsitemod "91porn-server/models/v/officialWebsitemod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// ─────────────────────────────────────────
// Service functions
// ─────────────────────────────────────────
func ListRecruitForm(req *RecruitFormListReq) (int64, []officialwebsitemod.RecruitForm, error) {
filter, err := req.Query()
if err != nil {
return 0, nil, err
}
mod := &officialwebsitemod.RecruitForm{}
total, err := mod.Count(filter)
if err != nil {
return 0, nil, err
}
list, err := mod.FindMany(filter, req.FindOptions())
if err != nil {
return 0, nil, err
}
return total, list, nil
}
func CreateRecruitForm(req *ModifyRecruitFormReq) (officialwebsitemod.RecruitForm, error) {
data := req.toMod()
if err := data.Create(); err != nil {
return officialwebsitemod.RecruitForm{}, err
}
return data, nil
}
func UpdateRecruitForm(req *ModifyRecruitFormReq) (resp CountResp, err error) {
if req == nil || req.ID == "" {
return
}
id, parseErr := primitive.ObjectIDFromHex(req.ID)
if parseErr != nil || isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.RecruitForm{}
if err = mod.FindOne(officialwebsitemod.M{"_id": id}); err != nil {
return
}
var update officialwebsitemod.M
if update, err = req.toM(); err != nil || update == nil {
return
}
resp.Count, err = mod.Update(officialwebsitemod.M{"_id": id}, update)
return
}
func DeleteRecruitForm(req *DeleteRecruitFormReq) (data DeleteRecruitFormResp, err error) {
if req == nil {
err = stderr.CodeEmptyData
return
}
var id primitive.ObjectID
if id, err = primitive.ObjectIDFromHex(req.ID); err != nil {
return
}
if isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.RecruitForm{}
data.Count, err = mod.Delete(officialwebsitemod.M{"_id": id})
return
}
// ─────────────────────────────────────────
// Mutation (Modify) types
// ─────────────────────────────────────────
type ModifyRecruitFormReq struct {
ID string `json:"id" form:"id"`
Name *string `json:"name"`
Sex *int `json:"sex"`
Age *int `json:"age"`
Country *string `json:"country"`
Address *string `json:"address"`
Contact *[]officialwebsitemod.ContactInfo `json:"contact"`
Description *string `json:"description"`
}
// ─────────────────────────────────────────
// Delete types
// ─────────────────────────────────────────
type DeleteRecruitFormReq struct {
ID string `json:"id" form:"id"`
}
type DeleteRecruitFormResp struct {
Count int64 `json:"count"`
}
// ─────────────────────────────────────────
// toMod — ModifyRecruitFormReq → model struct
// ─────────────────────────────────────────
func (req *ModifyRecruitFormReq) toMod() officialwebsitemod.RecruitForm {
if req == nil {
return officialwebsitemod.RecruitForm{}
}
data := officialwebsitemod.RecruitForm{
Name: stringOrZero(req.Name),
Sex: intOrZero(req.Sex),
Age: intOrZero(req.Age),
Country: stringOrZero(req.Country),
Address: stringOrZero(req.Address),
Description: stringOrZero(req.Description),
}
if req.Contact != nil {
data.Contact = *req.Contact
}
return data
}
// ─────────────────────────────────────────
// toM — ModifyRecruitFormReq → officialwebsitemod.M
// ─────────────────────────────────────────
func (req *ModifyRecruitFormReq) toM() (officialwebsitemod.M, error) {
if req == nil {
return nil, stderr.CodeEmptyData
}
set := officialwebsitemod.M{}
if req.Name != nil {
set["name"] = *req.Name
}
if req.Sex != nil {
set["sex"] = *req.Sex
}
if req.Age != nil {
set["age"] = *req.Age
}
if req.Country != nil {
set["country"] = *req.Country
}
if req.Address != nil {
set["address"] = *req.Address
}
if req.Contact != nil {
set["contact"] = *req.Contact
}
if req.Description != nil {
set["description"] = *req.Description
}
if len(set) == 0 {
return nil, nil
}
set["updatedAt"] = time.Now().UTC()
return officialwebsitemod.M{"$set": set}, nil
}
// ─────────────────────────────────────────
// List types
// ─────────────────────────────────────────
type RecruitFormListReq struct {
PageReq
ID *string `form:"id"`
Name *string `form:"name"`
Sex *int `form:"sex"`
Age *int `form:"age"`
Country *string `form:"country"`
Address *string `form:"address"`
Contact *string `form:"contact"`
Description *string `form:"description"`
CreatedAt *time.Time `form:"createdAt"`
UpdatedAt *time.Time `form:"updatedAt"`
DeletedAt *time.Time `form:"deletedAt"`
}
func (r *RecruitFormListReq) Query() (officialwebsitemod.M, error) {
filter := officialwebsitemod.M{}
if err := parseObjectIDFilter(filter, "_id", r.ID); err != nil {
return nil, err
}
if r.Name != nil {
filter["name"] = *r.Name
}
if r.Sex != nil {
filter["sex"] = *r.Sex
}
if r.Age != nil {
filter["age"] = *r.Age
}
if r.Country != nil {
filter["country"] = *r.Country
}
if r.Address != nil {
filter["address"] = *r.Address
}
if err := parseJSONFilter(filter, "contact", r.Contact, &[]officialwebsitemod.ContactInfo{}); err != nil {
return nil, err
}
if r.Description != nil {
filter["description"] = *r.Description
}
if r.CreatedAt != nil {
filter["createdAt"] = *r.CreatedAt
}
if r.UpdatedAt != nil {
filter["updatedAt"] = *r.UpdatedAt
}
if r.DeletedAt != nil {
filter["deletedAt"] = *r.DeletedAt
}
return filter, nil
}
+113
View File
@@ -0,0 +1,113 @@
package officialwebsiteser
import (
"regexp"
"strings"
"91porn-server/common/stderr"
"github.com/mozillazg/go-pinyin"
)
var (
seoSlugValidRe = regexp.MustCompile(`^[a-z]+$`) // 纯小写英文字母
seoSlugStripRe = regexp.MustCompile(`[^a-z]`) // 非 a-z 一律剔除
)
// seoSlugMaxLen 自动生成 slug 的最大长度(按拼音音节边界截断,避免长标题生成超长 slug)
const seoSlugMaxLen = 20
// GenerateSeoSlug 从标题生成纯小写字母 slug:中文转拼音、英文保留、其余字符剔除,
// 并按音节边界累加到 seoSlugMaxLen 为止(不从音节中间截断)。
// 例: "优雅Summer 2023" -> "youyasummer"
func GenerateSeoSlug(title string) string {
if title == "" {
return ""
}
args := pinyin.NewArgs()
// 非汉字(英文/数字/符号)原样返回,后续再统一剔除非 a-z
args.Fallback = func(r rune, a pinyin.Args) []string {
return []string{string(r)}
}
rows := pinyin.Pinyin(title, args)
var b strings.Builder
for _, row := range rows {
if len(row) == 0 {
continue
}
// 单个音节先剔除非 a-z、转小写
syl := seoSlugStripRe.ReplaceAllString(strings.ToLower(row[0]), "")
if syl == "" {
continue
}
// 到长度上限则在音节边界停止,避免 slug 过长
if b.Len()+len(syl) > seoSlugMaxLen {
break
}
b.WriteString(syl)
}
return b.String()
}
// ValidateSeoSlug 校验 slug 格式:非空且纯小写英文字母。
func ValidateSeoSlug(s string) bool {
return seoSlugValidRe.MatchString(s)
}
// ResolveSeoSlug 统一处理 seoSlug
// - 手填(provided 非空):校验纯小写字母格式,并查重(冲突报错)
// - 留空:由 title 生成拼音雏形,冲突时追加后缀去重
//
// exists 由各表提供(查各自表是否已占用该 slug)。
func ResolveSeoSlug(provided, title string, exists func(slug string) (bool, error)) (string, error) {
if provided != "" {
if !ValidateSeoSlug(provided) {
return "", stderr.ErrParamError
}
used, err := exists(provided)
if err != nil {
return "", err
}
if used {
return "", stderr.ErrParamError
}
return provided, nil
}
return UniqueSeoSlug(GenerateSeoSlug(title), exists)
}
// UniqueSeoSlug 基于 base 生成同表唯一的 slugexists 判断某 slug 是否已被占用。
// 冲突时依次追加纯小写字母后缀 a、b…z、aa、ab…zz、aaa… 直到命中未占用值;
// base 为空则返回空(走稀疏索引,不参与唯一约束)。
func UniqueSeoSlug(base string, exists func(slug string) (bool, error)) (string, error) {
if base == "" {
return "", nil
}
// n=0 先试 base 原值,之后依次追加 a、b…z、aa… 后缀(双射 26 进制递增)。
// 表内记录有限且各候选互不相同,必能在有限次内命中未占用值。
for n := 0; ; n++ {
candidate := base
if n > 0 {
candidate += seoSlugSuffix(n)
}
used, err := exists(candidate)
if err != nil {
return "", err
}
if !used {
return candidate, nil
}
}
}
// seoSlugSuffix 返回第 n 个纯小写字母后缀(n>=1),按双射 26 进制递增:
// 1->a 2->b … 26->z 27->aa 28->ab … 52->az 53->ba … 702->zz 703->aaa
func seoSlugSuffix(n int) string {
var buf []byte
for n > 0 {
n--
buf = append([]byte{byte('a' + n%26)}, buf...)
n /= 26
}
return string(buf)
}
+202
View File
@@ -0,0 +1,202 @@
package officialwebsiteser
import (
"time"
"91porn-server/common/stderr"
officialwebsitemod "91porn-server/models/v/officialWebsitemod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// ─────────────────────────────────────────
// Service functions
// ─────────────────────────────────────────
func ListTag(req *TagListReq) (int64, []officialwebsitemod.Tag, error) {
filter, err := req.Query()
if err != nil {
return 0, nil, err
}
mod := &officialwebsitemod.Tag{}
total, err := mod.Count(filter)
if err != nil {
return 0, nil, err
}
list, err := mod.FindMany(filter, req.FindOptions())
if err != nil {
return 0, nil, err
}
return total, list, nil
}
func CreateTag(req *ModifyTagReq) (officialwebsitemod.Tag, error) {
data := req.toMod()
if err := data.Create(); err != nil {
return officialwebsitemod.Tag{}, err
}
return data, nil
}
func UpdateTag(req *ModifyTagReq) (resp CountResp, err error) {
if req == nil || req.ID == "" {
return
}
id, parseErr := primitive.ObjectIDFromHex(req.ID)
if parseErr != nil || isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.Tag{}
if err = mod.FindOne(officialwebsitemod.M{"_id": id}); err != nil {
return
}
var update officialwebsitemod.M
if update, err = req.toM(); err != nil || update == nil {
return
}
resp.Count, err = mod.Update(officialwebsitemod.M{"_id": id}, update)
return
}
func DeleteTag(req *DeleteTagReq) (data DeleteTagResp, err error) {
if req == nil {
err = stderr.CodeEmptyData
return
}
var id primitive.ObjectID
if id, err = primitive.ObjectIDFromHex(req.ID); err != nil {
return
}
if isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.Tag{}
data.Count, err = mod.Delete(officialwebsitemod.M{"_id": id})
return
}
// ─────────────────────────────────────────
// Mutation (Modify) types
// ─────────────────────────────────────────
type ModifyTagReq struct {
ID string `json:"id" form:"id"`
Name *string `json:"name"`
Type *string `json:"type"`
IsHot *bool `json:"isHot"`
Sort *int64 `json:"sort"`
IsActive *bool `json:"isActive"`
}
// ─────────────────────────────────────────
// Delete types
// ─────────────────────────────────────────
type DeleteTagReq struct {
ID string `json:"id" form:"id"`
}
type DeleteTagResp struct {
Count int64 `json:"count"`
}
// ─────────────────────────────────────────
// toMod — ModifyTagReq → model struct
// ─────────────────────────────────────────
func (req *ModifyTagReq) toMod() officialwebsitemod.Tag {
if req == nil {
return officialwebsitemod.Tag{}
}
return officialwebsitemod.Tag{
Name: stringOrZero(req.Name),
Type: stringOrZero(req.Type),
IsHot: boolOrZero(req.IsHot),
SortModel: officialwebsitemod.SortModel{
Sort: int64OrZero(req.Sort),
IsActive: boolOrZero(req.IsActive),
},
}
}
// ─────────────────────────────────────────
// toM — ModifyTagReq → officialwebsitemod.M
// ─────────────────────────────────────────
func (req *ModifyTagReq) toM() (officialwebsitemod.M, error) {
if req == nil {
return nil, stderr.CodeEmptyData
}
set := officialwebsitemod.M{}
if req.Name != nil {
set["name"] = *req.Name
}
if req.Type != nil {
set["type"] = *req.Type
}
if req.IsHot != nil {
set["isHot"] = *req.IsHot
}
if req.Sort != nil {
set["sort"] = *req.Sort
}
if req.IsActive != nil {
set["isActive"] = *req.IsActive
}
if len(set) == 0 {
return nil, nil
}
set["updatedAt"] = time.Now().UTC()
return officialwebsitemod.M{"$set": set}, nil
}
// ─────────────────────────────────────────
// List types
// ─────────────────────────────────────────
type TagListReq struct {
PageReq
ID *string `form:"id"`
Name *string `form:"name"`
Type *string `form:"type"`
IsHot *bool `form:"isHot"`
Sort *int64 `form:"sort"`
IsActive *bool `form:"isActive"`
CreatedAt *time.Time `form:"createdAt"`
UpdatedAt *time.Time `form:"updatedAt"`
DeletedAt *time.Time `form:"deletedAt"`
}
func (r *TagListReq) Query() (officialwebsitemod.M, error) {
filter := officialwebsitemod.M{}
if err := parseObjectIDFilter(filter, "_id", r.ID); err != nil {
return nil, err
}
if r.Name != nil {
filter["name"] = *r.Name
}
if r.Type != nil {
filter["type"] = *r.Type
}
if r.IsHot != nil {
filter["isHot"] = *r.IsHot
}
if r.Sort != nil {
filter["sort"] = *r.Sort
}
if r.IsActive != nil {
filter["isActive"] = *r.IsActive
}
if r.CreatedAt != nil {
filter["createdAt"] = *r.CreatedAt
}
if r.UpdatedAt != nil {
filter["updatedAt"] = *r.UpdatedAt
}
if r.DeletedAt != nil {
filter["deletedAt"] = *r.DeletedAt
}
return filter, nil
}
+652
View File
@@ -0,0 +1,652 @@
package officialwebsiteser
import (
"91porn-server/models/v/tagmod"
"91porn-server/models/v/vidmod"
"math/rand/v2"
"time"
"91porn-server/common/stderr"
officialwebsitemod "91porn-server/models/v/officialWebsitemod"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// ─────────────────────────────────────────
// Service functions
// ─────────────────────────────────────────
func CheckVideoExistByVid(vid []primitive.ObjectID) ([]officialwebsitemod.Video, error) {
filter := bson.M{"_id": bson.M{"$in": vid}}
mod := &officialwebsitemod.Video{}
videos, err := mod.FindMany(filter)
return videos, err
}
func ListVideo(req *VideoListReq) (int64, []officialwebsitemod.Video, error) {
filter, err := req.Query()
if err != nil {
return 0, nil, err
}
mod := &officialwebsitemod.Video{}
total, err := mod.Count(filter)
if err != nil {
return 0, nil, err
}
list, err := mod.FindMany(filter, req.FindOptions())
if err != nil {
return 0, nil, err
}
return total, list, nil
}
// resolveVideoSeoSlug 处理 seoSlug:手填则校验格式并查重(冲突报错);为空则由标题生成雏形并追加后缀去重。
func resolveVideoSeoSlug(provided, title string, excludeID primitive.ObjectID) (string, error) {
exists := func(slug string) (bool, error) {
filter := officialwebsitemod.M{"seoSlug": slug}
if !excludeID.IsZero() {
filter["_id"] = officialwebsitemod.M{"$ne": excludeID}
}
n, err := (&officialwebsitemod.Video{}).Count(filter)
return n > 0, err
}
if provided != "" {
if !ValidateSeoSlug(provided) {
return "", stderr.ErrParamError
}
used, err := exists(provided)
if err != nil {
return "", err
}
if used {
return "", stderr.ErrParamError
}
return provided, nil
}
return UniqueSeoSlug(GenerateSeoSlug(title), exists)
}
func CreateVideo(req *ModifyVideoReq) (officialwebsitemod.Video, error) {
data, err := req.toMod()
if err != nil {
return officialwebsitemod.Video{}, err
}
// SEO slug:手填优先(校验唯一),为空则由标题生成雏形并去重
if data.SeoSlug, err = resolveVideoSeoSlug(data.SeoSlug, data.Title, primitive.NilObjectID); err != nil {
return officialwebsitemod.Video{}, err
}
if err := data.Create(); err != nil {
return officialwebsitemod.Video{}, err
}
return data, nil
}
func UpdateVideo(req *ModifyVideoReq) (resp CountResp, err error) {
if req == nil || req.ID == "" {
return
}
id, parseErr := primitive.ObjectIDFromHex(req.ID)
if parseErr != nil || isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.Video{}
if err = mod.FindOne(officialwebsitemod.M{"_id": id}); err != nil {
return
}
// 若提交了 seoSlug,先校验/去重(排除自身),回写后由 toM 落库
if req.SeoSlug != nil {
var slug string
if slug, err = resolveVideoSeoSlug(*req.SeoSlug, mod.Title, id); err != nil {
return
}
*req.SeoSlug = slug
}
var update officialwebsitemod.M
if update, err = req.toM(); err != nil || update == nil {
return
}
resp.Count, err = mod.Update(officialwebsitemod.M{"_id": id}, update)
return
}
func DeleteVideo(req *DeleteVideoReq) (data DeleteVideoResp, err error) {
if req == nil {
err = stderr.CodeEmptyData
return
}
var id primitive.ObjectID
if id, err = primitive.ObjectIDFromHex(req.ID); err != nil {
return
}
if isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.Video{}
data.Count, err = mod.Delete(officialwebsitemod.M{"_id": id})
return
}
func BatchUpdateVideoOwner(req *BatchUpdateVideoOwnerReq) (data BatchUpdateVideoOwnerResp, err error) {
if req == nil {
err = stderr.ErrParamError
return
}
// Check owner is exist
var ownerID primitive.ObjectID
ownerID, err = primitive.ObjectIDFromHex(req.OwnerID)
if err != nil {
return
}
var heroMod = &officialwebsitemod.Hero{}
err = heroMod.FindOne(officialwebsitemod.M{"_id": ownerID})
if err != nil {
return
}
if isNilObjectID(heroMod.ID) {
err = stderr.OfficialWebsiteHeroExist
return
}
var update = bson.M{"$set": bson.M{"heroId": ownerID}}
// Check album is exist, if exist then add into update
if req.AlbumID != "" {
var albumID primitive.ObjectID
albumID, err = primitive.ObjectIDFromHex(req.AlbumID)
if err != nil {
return
}
var album = &officialwebsitemod.Album{}
err = album.FindOne(officialwebsitemod.M{"_id": albumID})
if err != nil {
return
}
update = bson.M{"$set": bson.M{"heroId": ownerID, "albumId": albumID}}
}
var videoIDs []primitive.ObjectID
for i := range req.VideoIDs {
videoID, parseErr := primitive.ObjectIDFromHex(req.VideoIDs[i])
err = parseErr
if err != nil {
return
}
videoIDs = append(videoIDs, videoID)
}
var videoMod = &officialwebsitemod.Video{}
filter := bson.M{"_id": bson.M{"$in": videoIDs}}
data.Count, err = videoMod.UpdateMany(filter, update)
return
}
// ─────────────────────────────────────────
// Batch (Modify) types
// ─────────────────────────────────────────
type BatchUpdateVideoOwnerReq struct {
VideoIDs []string `json:"video_ids"`
OwnerID string `json:"owner_id"`
AlbumID string `json:"album_id"`
}
type BatchUpdateVideoOwnerResp struct {
Count int64 `json:"count"`
}
// ─────────────────────────────────────────
// Mutation (Modify) types
// ─────────────────────────────────────────
type ModifyVideoReq struct {
ID string `json:"id" form:"id"`
AlbumID *string `json:"albumId" form:"albumId"`
HeroID *string `json:"heroId" form:"heroId"`
Title *string `json:"title"`
Description *string `json:"description"`
Cover *string `json:"cover"`
Url *string `json:"url"`
SeoSlug *string `json:"seoSlug"`
Tags *[]officialwebsitemod.Tag `json:"tags"`
IsHot *bool `json:"isHot"`
Sort *int64 `json:"sort"`
IsActive *bool `json:"isActive"`
WatchCount *int64 `json:"watchCount"`
LikeCount *int64 `json:"likeCount"`
CommentCount *int64 `json:"commentCount"`
CollectCount *int64 `json:"collectCount"`
}
// ─────────────────────────────────────────
// Delete types
// ─────────────────────────────────────────
type DeleteVideoReq struct {
ID string `json:"id" form:"id"`
}
type DeleteVideoResp struct {
Count int64 `json:"count"`
}
// ─────────────────────────────────────────
// toMod — ModifyVideoReq → model struct
// ─────────────────────────────────────────
func (req *ModifyVideoReq) toMod() (officialwebsitemod.Video, error) {
if req == nil {
return officialwebsitemod.Video{}, nil
}
data := officialwebsitemod.Video{
Title: stringOrZero(req.Title),
Description: stringOrZero(req.Description),
Cover: stringOrZero(req.Cover),
Url: stringOrZero(req.Url),
IsHot: boolOrZero(req.IsHot),
SortModel: officialwebsitemod.SortModel{
Sort: int64OrZero(req.Sort),
IsActive: boolOrZero(req.IsActive),
},
WatchCount: int64OrZero(req.WatchCount),
LikeCount: int64OrZero(req.LikeCount),
CommentCount: int64OrZero(req.CommentCount),
CollectCount: int64OrZero(req.CollectCount),
}
if req.AlbumID != nil {
if *req.AlbumID != "" {
albumID, err := primitive.ObjectIDFromHex(*req.AlbumID)
if err != nil || isNilObjectID(albumID) {
return officialwebsitemod.Video{}, stderr.ErrParamError
}
data.AlbumID = albumID
}
}
if req.HeroID != nil {
if *req.HeroID != "" {
heroID, err := primitive.ObjectIDFromHex(*req.HeroID)
if err != nil || isNilObjectID(heroID) {
return officialwebsitemod.Video{}, stderr.ErrParamError
}
data.HeroID = heroID
}
}
if req.Tags != nil {
data.Tags = *req.Tags
}
if req.SeoSlug != nil {
data.SeoSlug = *req.SeoSlug
}
return data, nil
}
// ─────────────────────────────────────────
// toM — ModifyVideoReq → officialwebsitemod.M
// ─────────────────────────────────────────
func (req *ModifyVideoReq) toM() (officialwebsitemod.M, error) {
if req == nil {
return nil, stderr.CodeEmptyData
}
set := officialwebsitemod.M{}
if req.AlbumID != nil {
if *req.AlbumID != "" {
albumID, err := primitive.ObjectIDFromHex(*req.AlbumID)
if err != nil || isNilObjectID(albumID) {
return nil, stderr.ErrParamError
}
set["albumId"] = albumID
}
}
if req.HeroID != nil {
if *req.HeroID != "" {
heroID, err := primitive.ObjectIDFromHex(*req.HeroID)
if err != nil || isNilObjectID(heroID) {
return nil, stderr.ErrParamError
}
set["heroId"] = heroID
}
}
if req.Title != nil {
set["title"] = *req.Title
}
if req.Description != nil {
set["description"] = *req.Description
}
if req.Cover != nil {
set["cover"] = *req.Cover
}
if req.Url != nil {
set["url"] = *req.Url
}
if req.Tags != nil {
set["tags"] = *req.Tags
}
if req.SeoSlug != nil && *req.SeoSlug != "" {
set["seoSlug"] = *req.SeoSlug
}
if req.IsHot != nil {
set["isHot"] = *req.IsHot
}
if req.Sort != nil {
set["sort"] = *req.Sort
}
if req.IsActive != nil {
set["isActive"] = *req.IsActive
}
if req.WatchCount != nil {
set["watchCount"] = *req.WatchCount
}
if req.LikeCount != nil {
set["likeCount"] = *req.LikeCount
}
if req.CommentCount != nil {
set["commentCount"] = *req.CommentCount
}
if req.CollectCount != nil {
set["collectCount"] = *req.CollectCount
}
if len(set) == 0 {
return nil, nil
}
set["updatedAt"] = time.Now().UTC()
return officialwebsitemod.M{"$set": set}, nil
}
func BatchImportVideo(req *BatchImportVideoReq) (resp BatchImportVideoResp, err error) {
if req == nil || len(req.VideoIds) == 0 {
err = stderr.ErrParamError
return
}
objIds := make([]primitive.ObjectID, 0, len(req.VideoIds))
videoIDSet := make(map[primitive.ObjectID]struct{}, len(req.VideoIds))
for _, videoId := range req.VideoIds {
var id primitive.ObjectID
id, err = primitive.ObjectIDFromHex(videoId)
if err != nil {
return
}
if _, exists := videoIDSet[id]; exists {
continue
}
videoIDSet[id] = struct{}{}
objIds = append(objIds, id)
}
if len(objIds) == 0 {
return
}
// 1) Filter out videos that already exist in official website video table by `_id`.
var videoMod = &officialwebsitemod.Video{}
officialVideos, findVideoErr := videoMod.FindMany(officialwebsitemod.M{"_id": bson.M{"$in": objIds}})
if findVideoErr != nil {
err = findVideoErr
return
}
existedVideoMap := make(map[primitive.ObjectID]struct{}, len(officialVideos))
for _, v := range officialVideos {
existedVideoMap[v.ID] = struct{}{}
}
needImportVideoIDs := make([]primitive.ObjectID, 0, len(objIds))
for _, id := range objIds {
if _, exists := existedVideoMap[id]; exists {
continue
}
needImportVideoIDs = append(needImportVideoIDs, id)
}
if len(needImportVideoIDs) == 0 {
return
}
mats := []vidmod.Matcher{
(&vidmod.IDInMatch{IDs: needImportVideoIDs}).New(),
}
list, err := vidmod.FindMany(mats...)
if err != nil {
return
}
if len(list) == 0 {
return
}
var insertVideos []officialwebsitemod.Video
allTagIDs := make([]primitive.ObjectID, 0)
allTagIDSet := make(map[primitive.ObjectID]struct{})
for _, video := range list {
insertVideos = append(insertVideos, officialwebsitemod.Video{
ID: video.ID,
Title: video.Title,
Description: video.Content,
Cover: video.Cover,
Url: video.SourceURL,
IsHot: false,
WatchCount: int64(video.FakePlayCount+video.PlayCount) * rand.Int64N(10),
LikeCount: int64(video.FakeLikeCount+video.LikeCount) * rand.Int64N(10),
CommentCount: int64(video.FakeCommentCount+video.CommentCount) * rand.Int64N(10),
CollectCount: int64(video.CollectCount) * rand.Int64N(10),
SortModel: officialwebsitemod.SortModel{
IsActive: true,
},
BaseModel: officialwebsitemod.BaseModel{
CreatedAt: time.Now().UTC(),
UpdatedAt: time.Now().UTC(),
},
})
for _, tag := range video.Tags {
if _, exists := allTagIDSet[tag]; exists {
continue
}
allTagIDSet[tag] = struct{}{}
allTagIDs = append(allTagIDs, tag)
}
}
if len(insertVideos) == 0 {
return
}
// 2) Filter out tags that already exist in official website tag table by `_id`.
var tagMod = &officialwebsitemod.Tag{}
existedTagMap := map[primitive.ObjectID]struct{}{}
existedOfficialTagInfoMap := map[primitive.ObjectID]officialwebsitemod.Tag{}
if len(allTagIDs) > 0 {
officialWebsiteTags, findTagErr := tagMod.FindMany(officialwebsitemod.M{
"_id": bson.M{"$in": allTagIDs},
"type": officialwebsitemod.OfficialWebsiteTagTypeVideo,
})
if findTagErr != nil {
err = findTagErr
return
}
existedTagMap = make(map[primitive.ObjectID]struct{}, len(officialWebsiteTags))
for _, t := range officialWebsiteTags {
existedTagMap[t.ID] = struct{}{}
existedOfficialTagInfoMap[t.ID] = t
}
}
needImportTagIDs := make([]primitive.ObjectID, 0, len(allTagIDs))
for _, tagID := range allTagIDs {
if _, exists := existedTagMap[tagID]; exists {
continue
}
needImportTagIDs = append(needImportTagIDs, tagID)
}
tagsMap, err := tagmod.FindTagsMapByIDS(needImportTagIDs)
if err != nil {
return
}
var insertTags []officialwebsitemod.Tag
for _, tagID := range needImportTagIDs {
tag, ok := tagsMap[tagID]
if !ok {
continue
}
insertTags = append(insertTags, officialwebsitemod.Tag{
ID: tag.ID,
Name: tag.TagName,
Type: officialwebsitemod.OfficialWebsiteTagTypeVideo,
IsHot: len(tag.HotMark) > 0,
SortModel: officialwebsitemod.SortModel{
IsActive: true,
},
BaseModel: officialwebsitemod.BaseModel{
CreatedAt: time.Now().UTC(),
UpdatedAt: time.Now().UTC(),
},
})
}
// Fill video tags from tag info map to keep video docs self-contained.
for i := range insertVideos {
srcVideo := list[i]
videoTags := make([]officialwebsitemod.Tag, 0, len(srcVideo.Tags))
for _, tagID := range srcVideo.Tags {
if oldTag, exists := existedOfficialTagInfoMap[tagID]; exists {
videoTags = append(videoTags, officialwebsitemod.Tag{
ID: oldTag.ID,
Name: oldTag.Name,
Type: officialwebsitemod.OfficialWebsiteTagTypeVideo,
IsHot: oldTag.IsHot,
SortModel: officialwebsitemod.SortModel{
Sort: oldTag.Sort,
IsActive: true,
},
})
continue
}
tag, ok := tagsMap[tagID]
if !ok {
continue
}
videoTags = append(videoTags, officialwebsitemod.Tag{
ID: tag.ID,
Name: tag.TagName,
Type: officialwebsitemod.OfficialWebsiteTagTypeVideo,
IsHot: len(tag.HotMark) > 0,
SortModel: officialwebsitemod.SortModel{
IsActive: true,
},
})
}
insertVideos[i].Tags = videoTags
}
// 3) Insert filtered videos and tags into official website tables separately.
var ids []primitive.ObjectID
ids, err = videoMod.InsertMany(insertVideos)
if err != nil {
return
}
for i := range insertTags {
if createErr := insertTags[i].Create(); createErr != nil {
err = createErr
return
}
}
resp.Count = int64(len(ids))
return
}
// ─────────────────────────────────────────
// Batch Import types
// ─────────────────────────────────────────
type BatchImportVideoReq struct {
VideoIds []string `json:"videoIds"`
}
type BatchImportVideoResp struct {
Count int64 `json:"count"`
}
// ─────────────────────────────────────────
// List types
// ─────────────────────────────────────────
type VideoListReq struct {
PageReq
ID *string `form:"id"`
IDs []string `form:"ids"`
PrimiteveIDs []primitive.ObjectID `form:"primitiveIds"`
AlbumID *string `form:"albumId"`
HeroID *string `form:"heroID"`
Title *string `form:"title"`
Description *string `form:"description"`
Cover *string `form:"cover"`
Url *string `form:"url"`
Tags *string `form:"tags"`
IsHot *bool `form:"isHot"`
Sort *int64 `form:"sort"`
IsActive *bool `form:"isActive"`
CreatedAt *time.Time `form:"createdAt"`
UpdatedAt *time.Time `form:"updatedAt"`
DeletedAt *time.Time `form:"deletedAt"`
}
func (r *VideoListReq) Query() (officialwebsitemod.M, error) {
filter := officialwebsitemod.M{}
if err := parseObjectIDFilter(filter, "_id", r.ID); err != nil {
return nil, err
}
if len(r.IDs) > 0 {
var objIDs []primitive.ObjectID
for _, id := range r.IDs {
objID, err := primitive.ObjectIDFromHex(id)
if err != nil {
return nil, err
}
objIDs = append(objIDs, objID)
}
filter["_id"] = bson.M{"$in": objIDs}
}
if len(r.PrimiteveIDs) > 0 {
filter["_id"] = bson.M{"$in": r.PrimiteveIDs}
}
if err := parseObjectIDFilter(filter, "albumId", r.AlbumID); err != nil {
return nil, err
}
if err := parseObjectIDFilter(filter, "heroId", r.HeroID); err != nil {
return nil, err
}
if r.Title != nil {
filter["title"] = *r.Title
}
if r.Description != nil {
filter["description"] = *r.Description
}
if r.Cover != nil {
filter["cover"] = *r.Cover
}
if r.Url != nil {
filter["url"] = *r.Url
}
if err := parseJSONFilter(filter, "tags", r.Tags, &[]officialwebsitemod.Tag{}); err != nil {
return nil, err
}
if r.IsHot != nil {
filter["isHot"] = *r.IsHot
}
if r.Sort != nil {
filter["sort"] = *r.Sort
}
if r.IsActive != nil {
filter["isActive"] = *r.IsActive
}
if r.CreatedAt != nil {
filter["createdAt"] = *r.CreatedAt
}
if r.UpdatedAt != nil {
filter["updatedAt"] = *r.UpdatedAt
}
if r.DeletedAt != nil {
filter["deletedAt"] = *r.DeletedAt
}
return filter, nil
}