diff --git a/api/activity/activity.go b/api/activity/activity.go new file mode 100644 index 0000000..c4f12bf --- /dev/null +++ b/api/activity/activity.go @@ -0,0 +1,27 @@ +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package activity + +import ( + "context" + + "server/api/activity/v1" +) + +type IActivityV1 interface { + RuleList(ctx context.Context, req *v1.RuleListReq) (res *v1.RuleListRes, err error) + RuleAdd(ctx context.Context, req *v1.RuleAddReq) (res *v1.RuleAddRes, err error) + RuleEdit(ctx context.Context, req *v1.RuleEditReq) (res *v1.RuleEditRes, err error) + RuleDel(ctx context.Context, req *v1.RuleDelReq) (res *v1.RuleDelRes, err error) + RuleSetStatus(ctx context.Context, req *v1.RuleSetStatusReq) (res *v1.RuleSetStatusRes, err error) + ItemList(ctx context.Context, req *v1.ItemListReq) (res *v1.ItemListRes, err error) + ItemAdd(ctx context.Context, req *v1.ItemAddReq) (res *v1.ItemAddRes, err error) + ItemEdit(ctx context.Context, req *v1.ItemEditReq) (res *v1.ItemEditRes, err error) + ItemDel(ctx context.Context, req *v1.ItemDelReq) (res *v1.ItemDelRes, err error) + ItemGet(ctx context.Context, req *v1.ItemGetReq) (res *v1.ItemGetRes, err error) + ItemSetStatus(ctx context.Context, req *v1.ItemSetStatusReq) (res *v1.ItemSetStatusRes, err error) + SignInList(ctx context.Context, req *v1.SignInListReq) (res *v1.SignInListRes, err error) + SignIn(ctx context.Context, req *v1.SignInReq) (res *v1.SignInRes, err error) +} diff --git a/api/activity/v1/activity.go b/api/activity/v1/activity.go new file mode 100644 index 0000000..7990724 --- /dev/null +++ b/api/activity/v1/activity.go @@ -0,0 +1,143 @@ +package v1 + +import ( + "server/internal/model" + + "github.com/gogf/gf/v2/frame/g" +) + +type RuleListReq struct { + g.Meta `path:"/activity" tags:"Backend/Activity" method:"get" summary:"获取签到奖励规则列表"` + Page int `json:"page" dc:"页码"` + Size int `json:"size" dc:"每页数量"` + Status int `json:"status" dc:"状态"` + RuleName string `json:"ruleName" dc:"规则名称"` +} +type RuleListRes struct { + Total int `json:"total" dc:"总数"` + List []model.SignInRewardRule `json:"list" dc:"规则列表"` +} + +type RuleAddReq struct { + g.Meta `path:"/activity" tags:"Backend/Activity" method:"post" summary:"新增签到奖励规则"` + RuleName string `json:"ruleName" dc:"规则名称" v:"required"` + CycleDays int `json:"cycleDays" dc:"周期天数" v:"required"` + StartDate string `json:"startDate" dc:"开始日期" v:"required"` + EndDate string `json:"endDate" dc:"结束日期" v:"required"` + Status int `json:"status" dc:"状态" v:"required"` +} +type RuleAddRes struct { + Success bool `json:"success" dc:"是否成功"` +} + +type RuleEditReq struct { + g.Meta `path:"/activity/{id}" tags:"Backend/Activity" method:"put" summary:"编辑签到奖励规则"` + Id int64 `json:"id" dc:"规则ID" v:"required"` + RuleName string `json:"ruleName" dc:"规则名称" v:"required"` + CycleDays int `json:"cycleDays" dc:"周期天数" v:"required"` + StartDate string `json:"startDate" dc:"开始日期" v:"required"` + EndDate string `json:"endDate" dc:"结束日期" v:"required"` + Status int `json:"status" dc:"状态" v:"required"` +} +type RuleEditRes struct { + Success bool `json:"success" dc:"是否成功"` +} + +type RuleDelReq struct { + g.Meta `path:"/activity/{id}" tags:"Backend/Activity" method:"delete" summary:"删除签到奖励规则"` + Id int64 `json:"id" dc:"规则ID" v:"required"` +} +type RuleDelRes struct { + Success bool `json:"success" dc:"是否成功"` +} + +type RuleSetStatusReq struct { + g.Meta `path:"/activity/{id}/status" tags:"Backend/Activity" method:"patch" summary:"设置签到奖励规则状态"` + Id int64 `json:"id" dc:"规则ID" v:"required"` + Status int `json:"status" dc:"状态" v:"required"` +} +type RuleSetStatusRes struct { + Success bool `json:"success" dc:"是否成功"` +} + +// 签到奖励明细列表 +type ItemListReq struct { + g.Meta `path:"/activity/items" tags:"Backend/Activity" method:"get" summary:"获取签到奖励明细列表"` + RuleId int64 `json:"ruleId" dc:"规则ID" v:"required"` +} +type ItemListRes struct { + List []model.SignInRewardDetail `json:"list" dc:"明细列表"` +} + +// 新增签到奖励明细 +type ItemAddReq struct { + g.Meta `path:"/activity/item" tags:"Backend/Activity" method:"post" summary:"新增签到奖励明细"` + RuleId int64 `json:"ruleId" dc:"规则ID" v:"required"` + DayNumber int `json:"dayNumber" dc:"签到天数" v:"required"` + RewardType int `json:"rewardType" dc:"奖励类型" v:"required"` + Quantity int `json:"quantity" dc:"奖励数量" v:"required"` + Status int `json:"status" dc:"状态" v:"required"` +} +type ItemAddRes struct { + Id int64 `json:"id" dc:"明细ID"` + Success bool `json:"success" dc:"是否成功"` +} + +// 编辑签到奖励明细 +type ItemEditReq struct { + g.Meta `path:"/activity/item/{id}" tags:"Backend/Activity" method:"put" summary:"编辑签到奖励明细"` + Id int64 `json:"id" dc:"明细ID" v:"required"` + RuleId int64 `json:"ruleId" dc:"规则ID" v:"required"` + DayNumber int `json:"dayNumber" dc:"签到天数" v:"required"` + RewardType int `json:"rewardType" dc:"奖励类型" v:"required"` + Quantity int `json:"quantity" dc:"奖励数量" v:"required"` + Status int `json:"status" dc:"状态" v:"required"` +} +type ItemEditRes struct { + Success bool `json:"success" dc:"是否成功"` +} + +// 删除签到奖励明细 +type ItemDelReq struct { + g.Meta `path:"/activity/item/{id}" tags:"Backend/Activity" method:"delete" summary:"删除签到奖励明细"` + Id int64 `json:"id" dc:"明细ID" v:"required"` +} +type ItemDelRes struct { + Success bool `json:"success" dc:"是否成功"` +} + +// 查询单个签到奖励明细 +type ItemGetReq struct { + g.Meta `path:"/activity/item/{id}" tags:"Backend/Activity" method:"get" summary:"获取单个签到奖励明细"` + Id int64 `json:"id" dc:"明细ID" v:"required"` +} +type ItemGetRes struct { + model.SignInRewardDetail +} + +// 设置签到奖励明细状态 +type ItemSetStatusReq struct { + g.Meta `path:"/activity/item/{id}/status" tags:"Backend/Activity" method:"patch" summary:"设置签到奖励明细状态"` + Id int64 `json:"id" dc:"明细ID" v:"required"` + Status int `json:"status" dc:"状态" v:"required"` +} +type ItemSetStatusRes struct { + Success bool `json:"success" dc:"是否成功"` +} + +type SignInListReq struct { + g.Meta `path:"/activity/sign" tags:"APP/Activity" method:"get" summary:"用户签到任务列表"` +} +type SignInListRes struct { + List []model.SignInListItem `json:"list" dc:"签到列表"` +} + +// 用户签到 +type SignInReq struct { + g.Meta `path:"/activity/sign" tags:"APP/Activity" method:"post" summary:"用户签到"` + RuleId int64 `json:"ruleId" dc:"规则ID" v:"required"` + RewardDetailId int64 `json:"rewardDetailId" dc:"奖励明细ID" v:"required"` +} +type SignInRes struct { + Success bool `json:"success" dc:"是否成功"` +} diff --git a/api/admin/v1/admin.go b/api/admin/v1/admin.go index 68b0a42..b1d49da 100644 --- a/api/admin/v1/admin.go +++ b/api/admin/v1/admin.go @@ -9,8 +9,9 @@ type InfoReq struct { } type InfoRes struct { g.Meta `mime:"application/json"` - AdminId int64 `json:"adminId"` + Id int64 `json:"id"` Username string `json:"username"` + Role string `json:"role"` } type EditPassReq struct { diff --git a/api/ads/ads.go b/api/ads/ads.go new file mode 100644 index 0000000..3149f54 --- /dev/null +++ b/api/ads/ads.go @@ -0,0 +1,15 @@ +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package ads + +import ( + "context" + + "server/api/ads/v1" +) + +type IAdsV1 interface { + Upload(ctx context.Context, req *v1.UploadReq) (res *v1.UploadRes, err error) +} diff --git a/api/ads/v1/ads.go b/api/ads/v1/ads.go new file mode 100644 index 0000000..f62a9e9 --- /dev/null +++ b/api/ads/v1/ads.go @@ -0,0 +1,16 @@ +package v1 + +import "github.com/gogf/gf/v2/frame/g" + +// UploadReq 广告数据上传请求 +type UploadReq struct { + g.Meta `path:"/ads/upload" tags:"APP/Ads" method:"post" summary:"广告数据上传"` + NodeUid string `json:"node_uid" dc:"节点ID" v:"required"` + DeviceCode string `json:"device_code" dc:"设备编号" v:"required"` + Data string `json:"data" dc:"广告数据" v:"required"` +} + +// UploadRes 广告数据上传响应 +type UploadRes struct { + Success bool `json:"success" dc:"是否成功"` +} diff --git a/api/auth/auth.go b/api/auth/auth.go index 64d3473..8e0587c 100644 --- a/api/auth/auth.go +++ b/api/auth/auth.go @@ -16,4 +16,5 @@ type IAuthV1 interface { UserRegister(ctx context.Context, req *v1.UserRegisterReq) (res *v1.UserRegisterRes, err error) UserEditPass(ctx context.Context, req *v1.UserEditPassReq) (res *v1.UserEditPassRes, err error) UserCode(ctx context.Context, req *v1.UserCodeReq) (res *v1.UserCodeRes, err error) + AuthorLogin(ctx context.Context, req *v1.AuthorLoginReq) (res *v1.AuthorLoginRes, err error) } diff --git a/api/auth/v1/auth.go b/api/auth/v1/auth.go index a4123d8..f54d891 100644 --- a/api/auth/v1/auth.go +++ b/api/auth/v1/auth.go @@ -3,7 +3,7 @@ package v1 import "github.com/gogf/gf/v2/frame/g" type AdminLoginReq struct { - g.Meta `path:"/admin/login" tags:"Admin" method:"post" summary:"管理员登录"` + g.Meta `path:"/admin/login" tags:"Backend/Admin" method:"post" summary:"管理员登录"` Username string `json:"username" v:"required" dc:"用户名"` Password string `json:"password" v:"required" dc:"密码"` } @@ -45,3 +45,12 @@ type UserCodeReq struct { type UserCodeRes struct { Success bool `json:"success" dc:"是否成功"` } + +type AuthorLoginReq struct { + g.Meta `path:"/author/login" tags:"APP/Author" method:"post" summary:"作者登录"` + Email string `json:"email" v:"required" dc:"邮箱"` + Password string `json:"password" v:"required" dc:"密码"` +} +type AuthorLoginRes struct { + Token string `json:"token" dc:"token"` +} diff --git a/api/author/author.go b/api/author/author.go index f53f444..76b2450 100644 --- a/api/author/author.go +++ b/api/author/author.go @@ -18,4 +18,7 @@ type IAuthorV1 interface { Follow(ctx context.Context, req *v1.FollowReq) (res *v1.FollowRes, err error) Unfollow(ctx context.Context, req *v1.UnfollowReq) (res *v1.UnfollowRes, err error) Detail(ctx context.Context, req *v1.DetailReq) (res *v1.DetailRes, err error) + AuthorInfo(ctx context.Context, req *v1.AuthorInfoReq) (res *v1.AuthorInfoRes, err error) + Apply(ctx context.Context, req *v1.ApplyReq) (res *v1.ApplyRes, err error) + Review(ctx context.Context, req *v1.ReviewReq) (res *v1.ReviewRes, err error) } diff --git a/api/author/v1/author.go b/api/author/v1/author.go index bb8bf48..df60d94 100644 --- a/api/author/v1/author.go +++ b/api/author/v1/author.go @@ -1,12 +1,13 @@ package v1 import ( - "github.com/gogf/gf/v2/frame/g" "server/internal/model" + + "github.com/gogf/gf/v2/frame/g" ) type ListReq struct { - g.Meta `path:"/author" tags:"Backend/Admin" method:"get" summary:"获取作者列表"` + g.Meta `path:"/author" tags:"Backend/Author" method:"get" summary:"获取作者列表"` Page int `json:"page" dc:"页码"` Size int `json:"size" dc:"每页数量"` PenName string `json:"penName" dc:"笔名(模糊搜索)"` @@ -18,7 +19,7 @@ type ListRes struct { } type AddReq struct { - g.Meta `path:"/author" tags:"Backend/Admin" method:"post" summary:"新增作者"` + g.Meta `path:"/author" tags:"Backend/Author" method:"post" summary:"新增作者"` UserId int64 `json:"userId" dc:"用户ID" v:"required"` PenName string `json:"penName" dc:"笔名" v:"required"` Bio string `json:"bio" dc:"作者简介"` @@ -29,7 +30,7 @@ type AddRes struct { } type EditReq struct { - g.Meta `path:"/author" tags:"Backend/Admin" method:"put" summary:"编辑作者"` + g.Meta `path:"/author" tags:"Backend/Author" method:"put" summary:"编辑作者"` Id int64 `json:"id" dc:"作者ID" v:"required"` PenName string `json:"penName" dc:"笔名" v:"required"` Bio string `json:"bio" dc:"作者简介"` @@ -40,7 +41,7 @@ type EditRes struct { } type DelReq struct { - g.Meta `path:"/author" tags:"Backend/Admin" method:"delete" summary:"删除作者"` + g.Meta `path:"/author" tags:"Backend/Author" method:"delete" summary:"删除作者"` Id int64 `json:"id" dc:"作者ID" v:"required"` } type DelRes struct { @@ -50,7 +51,7 @@ type DelRes struct { // 关注作者 // ============================= type FollowReq struct { - g.Meta `path:"/author/follow" tags:"APP" method:"post" summary:"关注作者"` + g.Meta `path:"/author/follow" tags:"APP/Author" method:"post" summary:"关注作者"` AuthorId int64 `json:"authorId" dc:"作者ID" v:"required"` } type FollowRes struct { @@ -60,16 +61,48 @@ type FollowRes struct { // 取消关注作者 // ============================= type UnfollowReq struct { - g.Meta `path:"/author/unfollow" tags:"APP" method:"post" summary:"取消关注作者"` + g.Meta `path:"/author/unfollow" tags:"APP/Author" method:"post" summary:"取消关注作者"` AuthorId int64 `json:"authorId" dc:"作者ID" v:"required"` } type UnfollowRes struct { Success bool `json:"success" dc:"是否成功"` } type DetailReq struct { - g.Meta `path:"/author/detail" tags:"APP" method:"get" summary:"作者详情"` + g.Meta `path:"/author/detail" tags:"APP/Author" method:"get" summary:"作者详情"` AuthorId int64 `json:"authorId" dc:"作者ID" v:"required"` } type DetailRes struct { - Author *model.AuthorDetailOut `json:"author" dc:"作者信息"` + *model.AuthorDetailOut +} + +type AuthorInfoReq struct { + g.Meta `path:"/author/info" tags:"Backend/Author" method:"get" summary:"作者基础信息"` +} +type AuthorInfoRes struct { + Id int64 `json:"id"` + PenName string `json:"penName"` + Role string `json:"role"` +} + +type ApplyReq struct { + g.Meta `path:"/author/apply" tags:"APP/Author" method:"post" summary:"申请作者"` + Bio string `json:"bio" dc:"作者简介"` + PenName string `json:"penName" dc:"笔名"` +} + +type ApplyRes struct { + g.Meta `path:"/author/apply" tags:"APP/Author" method:"post" summary:"申请作者"` + Success bool `json:"success" dc:"是否成功"` + Msg string `json:"msg" dc:"消息"` +} + +// 审核作者 +// ============================= +type ReviewReq struct { + g.Meta `path:"/author/review" tags:"Backend/Author" method:"post" summary:"审核作者申请"` + AuthorId int64 `json:"authorId" dc:"作者ID" v:"required"` + Status int `json:"status" dc:"审核状态:1通过,3拒绝" v:"required|in:1,3"` +} +type ReviewRes struct { + Success bool `json:"success" dc:"是否成功"` } diff --git a/api/book/book.go b/api/book/book.go index f74b6b7..32e9010 100644 --- a/api/book/book.go +++ b/api/book/book.go @@ -17,10 +17,13 @@ type IBookV1 interface { Del(ctx context.Context, req *v1.DelReq) (res *v1.DelRes, err error) ShelfAdd(ctx context.Context, req *v1.ShelfAddReq) (res *v1.ShelfAddRes, err error) ShelfRemove(ctx context.Context, req *v1.ShelfRemoveReq) (res *v1.ShelfRemoveRes, err error) + HistoryRemove(ctx context.Context, req *v1.HistoryRemoveReq) (res *v1.HistoryRemoveRes, err error) AppList(ctx context.Context, req *v1.AppListReq) (res *v1.AppListRes, err error) AppDetail(ctx context.Context, req *v1.AppDetailReq) (res *v1.AppDetailRes, err error) AppRate(ctx context.Context, req *v1.AppRateReq) (res *v1.AppRateRes, err error) MyList(ctx context.Context, req *v1.MyListReq) (res *v1.MyListRes, err error) BookSetFeatured(ctx context.Context, req *v1.BookSetFeaturedReq) (res *v1.BookSetFeaturedRes, err error) BookSetRecommended(ctx context.Context, req *v1.BookSetRecommendedReq) (res *v1.BookSetRecommendedRes, err error) + BookSetHot(ctx context.Context, req *v1.BookSetHotReq) (res *v1.BookSetHotRes, err error) + BookCoverImage(ctx context.Context, req *v1.BookCoverImageReq) (res *v1.BookCoverImageRes, err error) } diff --git a/api/book/v1/book.go b/api/book/v1/book.go index 14ab8f4..7eb7838 100644 --- a/api/book/v1/book.go +++ b/api/book/v1/book.go @@ -3,11 +3,13 @@ package v1 import ( "server/internal/model" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/frame/g" ) type ListReq struct { - g.Meta `path:"/book" tags:"Backend/Author" method:"get" summary:"获取小说列表"` + g.Meta `path:"/book" tags:"Backend/Book" method:"get" summary:"获取小说列表"` Page int `json:"page" dc:"页码"` Size int `json:"size" dc:"每页数量"` Title string `json:"title" dc:"书名模糊搜索"` @@ -23,7 +25,7 @@ type ListRes struct { } type AddReq struct { - g.Meta `path:"/book" tags:"Backend/Author" method:"post" summary:"新增小说"` + g.Meta `path:"/book" tags:"Backend/Book" method:"post" summary:"新增小说"` AuthorId int64 `json:"authorId" dc:"作者ID" v:"required"` CategoryId int64 `json:"categoryId" dc:"分类ID" v:"required"` Title string `json:"title" dc:"书名" v:"required"` @@ -40,7 +42,7 @@ type AddRes struct { } type EditReq struct { - g.Meta `path:"/book" tags:"Backend/Author" method:"put" summary:"编辑小说"` + g.Meta `path:"/book" tags:"Backend/Book" method:"put" summary:"编辑小说"` Id int64 `json:"id" dc:"书籍ID" v:"required"` AuthorId int64 `json:"authorId" dc:"作者ID" v:"required"` CategoryId int64 `json:"categoryId" dc:"分类ID" v:"required"` @@ -58,7 +60,7 @@ type EditRes struct { } type DelReq struct { - g.Meta `path:"/book" tags:"Backend/Author" method:"delete" summary:"删除小说"` + g.Meta `path:"/book" tags:"Backend/Book" method:"delete" summary:"删除小说"` Id int64 `json:"id" dc:"书籍ID" v:"required"` } type DelRes struct { @@ -67,7 +69,7 @@ type DelRes struct { // 加入书架 type ShelfAddReq struct { - g.Meta `path:"/book/shelf/add" tags:"APP" method:"post" summary:"加入书架"` + g.Meta `path:"/book/shelf/add" tags:"APP/Book" method:"post" summary:"加入书架"` BookId int64 `json:"bookId" dc:"小说ID" v:"required"` } type ShelfAddRes struct { @@ -76,21 +78,30 @@ type ShelfAddRes struct { // 移除书架 type ShelfRemoveReq struct { - g.Meta `path:"/book/shelf/remove" tags:"APP" method:"post" summary:"移除书架(支持批量)"` + g.Meta `path:"/book/shelf/remove" tags:"APP/Book" method:"post" summary:"移除书架(支持批量)"` BookIds []int64 `json:"bookIds" dc:"小说ID列表" v:"required"` } type ShelfRemoveRes struct { Success bool `json:"success" dc:"是否成功"` } +type HistoryRemoveReq struct { + g.Meta `path:"/book/history/remove" tags:"APP/Book" method:"post" summary:"移除阅读记录(支持批量)"` + BookIds []int64 `json:"bookIds" dc:"小说ID列表" v:"required"` +} +type HistoryRemoveRes struct { + Success bool `json:"success" dc:"是否成功"` +} + // App 获取书籍列表 type AppListReq struct { - g.Meta `path:"/book/app/list" tags:"APP" method:"get" summary:"App获取书籍列表"` + g.Meta `path:"/book/app/list" tags:"APP/Book" method:"get" summary:"App获取书籍列表"` Page int `json:"page" dc:"页码"` Size int `json:"size" dc:"每页数量"` IsRecommended bool `json:"isRecommended" dc:"是否推荐"` IsFeatured bool `json:"isFeatured" dc:"是否精选"` IsLatest int `json:"isLatest" dc:"是否最新"` + IsHot bool `json:"isHot" dc:"是否最热"` CategoryId int64 `json:"categoryId" dc:"分类ID"` Title string `json:"title" dc:"书名模糊搜索"` AuthorId int `json:"authorId" dc:"作者ID"` @@ -104,34 +115,42 @@ type AppListRes struct { // App 获取书籍详情 type AppDetailReq struct { - g.Meta `path:"/book/app/detail" tags:"APP" method:"get" summary:"App获取书籍详情"` + g.Meta `path:"/book/app/detail" tags:"APP/Book" method:"get" summary:"App获取书籍详情"` Id int64 `json:"id" dc:"书籍ID" v:"required"` } type AppDetailRes struct { - Id int64 `json:"id" dc:"书籍ID"` - AuthorId int64 `json:"authorId" dc:"作者ID"` - CategoryId int64 `json:"categoryId" dc:"分类ID"` - Title string `json:"title" dc:"书名"` - CoverUrl string `json:"coverUrl" dc:"封面图"` - Description string `json:"description" dc:"简介"` - Status int `json:"status" dc:"状态"` - Tags string `json:"tags" dc:"标签"` - IsRecommended int `json:"isRecommended" dc:"是否推荐"` - IsFeatured int `json:"isFeatured" dc:"是否精选"` - Language string `json:"language" dc:"语言"` - Rating float64 `json:"rating" dc:"评分"` - CurrentReaders int64 `json:"currentReaders" dc:"在读人数"` - CreatedAt string `json:"createdAt" dc:"创建时间"` - UpdatedAt string `json:"updatedAt" dc:"更新时间"` - HasRead bool `json:"hasRead" dc:"是否读过"` - ReadProgress int `json:"readProgress" dc:"阅读进度百分比"` - LastChapterId int64 `json:"lastChapterId" dc:"最近阅读章节ID"` - LastReadAt string `json:"lastReadAt" dc:"最近阅读时间"` + Id int64 `json:"id" dc:"书籍ID"` + CoverUrl string `json:"coverUrl" dc:"封面图"` + Rating float64 `json:"rating" dc:"评分"` + Title string `json:"title" dc:"标题"` + Description string `json:"description" dc:"简介"` + AuthorId int64 `json:"authorId" dc:"作者ID"` + Author model.AppAuthor `json:"author" dc:"作者信息"` + IsFeatured int `json:"isFeatured" dc:"是否精选"` + Language string `json:"language" dc:"语言"` + CategoryId int64 `json:"categoryId" dc:"分类ID"` + Category model.Category `json:"category" dc:"分类信息"` + Status int `json:"status" dc:"状态"` + WordsCount int `json:"wordsCount" dc:"字数"` + ChaptersCount int `json:"chaptersCount" dc:"章节数"` + ReadCount int64 `json:"readCount" dc:"阅读人数"` + CurrentReaders int64 `json:"currentReaders" dc:"在读人数"` + Tags string `json:"tags" dc:"标签"` + IsRecommended int `json:"isRecommended" dc:"是否推荐"` + HasRated bool `json:"hasRated" dc:"当前用户是否已评分"` + MyRating float64 `json:"myRating" dc:"当前用户评分(未评分为0)"` + HasRead bool `json:"hasRead" dc:"是否读过"` + ReadProgress int `json:"readProgress" dc:"阅读进度百分比"` + LastChapterId int64 `json:"lastChapterId" dc:"最近阅读章节ID"` + LastReadAt string `json:"lastReadAt" dc:"最近阅读时间"` + IsInBookshelf bool `json:"isInBookshelf" dc:"当前用户是否已加入书架"` + CreatedAt string `json:"createdAt" dc:"创建时间"` + UpdatedAt string `json:"updatedAt" dc:"更新时间"` } // App 用户评分 type AppRateReq struct { - g.Meta `path:"/book/app/rate" tags:"APP" method:"post" summary:"App用户评分"` + g.Meta `path:"/book/app/rate" tags:"APP/Book" method:"post" summary:"App用户评分"` BookId int64 `json:"bookId" dc:"书籍ID" v:"required"` Rating float64 `json:"rating" dc:"评分(1-10分)" v:"required"` } @@ -152,7 +171,7 @@ type BookAppItem struct { // 我的书籍列表 // ============================= type MyListReq struct { - g.Meta `path:"/book/app/my-books" tags:"APP" method:"get" summary:"获取我的书籍列表"` + g.Meta `path:"/book/app/my-books" tags:"APP/Book" method:"get" summary:"获取我的书籍列表"` Type int `json:"type" dc:"类型:1-正在读 2-已读完 3-历史记录" v:"required"` Page int `json:"page" dc:"页码"` Size int `json:"size" dc:"每页数量"` @@ -183,3 +202,20 @@ type BookSetRecommendedReq struct { type BookSetRecommendedRes struct { Success bool `json:"success" dc:"是否成功"` } + +type BookSetHotReq struct { + g.Meta `path:"/book/set-hot" tags:"Backend/Book" method:"post" summary:"设置书籍热门状态"` + Id int64 `json:"id" dc:"书籍ID" v:"required"` + IsHot int `json:"isHot" dc:"是否热门" v:"required"` +} +type BookSetHotRes struct { + Success bool `json:"success" dc:"是否成功"` +} + +type BookCoverImageReq struct { + g.Meta `path:"/book/coverImage" tags:"Backend/Book" method:"post" summary:"上传封面图"` + File *ghttp.UploadFile `json:"file" v:"required#请上传文件"` +} +type BookCoverImageRes struct { + ImageUrl string `json:"imageUrl" dc:"图片地址"` +} diff --git a/api/category/v1/category.go b/api/category/v1/category.go index a72c951..1d1ef86 100644 --- a/api/category/v1/category.go +++ b/api/category/v1/category.go @@ -7,7 +7,7 @@ import ( ) type ListReq struct { - g.Meta `path:"/category" tags:"APP" method:"get" summary:"获取分类列表"` + g.Meta `path:"/category" tags:"Backend-APP/Category" method:"get" summary:"获取分类列表"` Page int `json:"page" dc:"页码"` Size int `json:"size" dc:"每页数量"` Name string `json:"name" dc:"分类名称(模糊搜索)"` @@ -19,7 +19,7 @@ type ListRes struct { } type AddReq struct { - g.Meta `path:"/category" tags:"Backend/Admin" method:"post" summary:"新增分类"` + g.Meta `path:"/category" tags:"Backend/Category" method:"post" summary:"新增分类"` Name string `json:"name" dc:"分类名称" v:"required"` Channel int `json:"channel" dc:"频道类型:1=男频,2=女频" v:"required"` } @@ -28,7 +28,7 @@ type AddRes struct { } type EditReq struct { - g.Meta `path:"/category" tags:"Backend/Admin" method:"put" summary:"编辑分类"` + g.Meta `path:"/category" tags:"Backend/Category" method:"put" summary:"编辑分类"` Id int64 `json:"id" dc:"分类ID" v:"required"` Name string `json:"name" dc:"分类名称" v:"required"` Channel int `json:"channel" dc:"频道类型:1=男频,2=女频" v:"required"` @@ -38,7 +38,7 @@ type EditRes struct { } type DelReq struct { - g.Meta `path:"/category" tags:"Backend/Admin" method:"delete" summary:"删除分类"` + g.Meta `path:"/category" tags:"Backend/Category" method:"delete" summary:"删除分类"` Id int64 `json:"id" dc:"分类ID" v:"required"` } type DelRes struct { diff --git a/api/chapter/v1/chapter.go b/api/chapter/v1/chapter.go index 8851760..be37dcc 100644 --- a/api/chapter/v1/chapter.go +++ b/api/chapter/v1/chapter.go @@ -7,7 +7,7 @@ import ( ) type ListReq struct { - g.Meta `path:"/chapter" tags:"Backend/Author" method:"get" summary:"获取章节列表"` + g.Meta `path:"/chapter" tags:"Backend/Chapter" method:"get" summary:"获取章节列表"` Page int `json:"page" dc:"页码"` Size int `json:"size" dc:"每页数量"` BookId int64 `json:"bookId" dc:"小说ID"` @@ -20,7 +20,7 @@ type ListRes struct { } type AddReq struct { - g.Meta `path:"/chapter" tags:"Backend/Author" method:"post" summary:"新增章节"` + g.Meta `path:"/chapter" tags:"Backend/Chapter" method:"post" summary:"新增章节"` BookId int64 `json:"bookId" dc:"小说ID" v:"required"` Title string `json:"title" dc:"章节标题" v:"required"` Content string `json:"content" dc:"章节内容" v:"required"` @@ -34,7 +34,7 @@ type AddRes struct { } type EditReq struct { - g.Meta `path:"/chapter" tags:"Backend/Author" method:"put" summary:"编辑章节"` + g.Meta `path:"/chapter" tags:"Backend/Chapter" method:"put" summary:"编辑章节"` Id int64 `json:"id" dc:"章节ID" v:"required"` BookId int64 `json:"bookId" dc:"小说ID" v:"required"` Title string `json:"title" dc:"章节标题" v:"required"` @@ -49,7 +49,7 @@ type EditRes struct { } type DelReq struct { - g.Meta `path:"/chapter" tags:"Backend/Author" method:"delete" summary:"删除章节"` + g.Meta `path:"/chapter" tags:"Backend/Chapter" method:"delete" summary:"删除章节"` Id int64 `json:"id" dc:"章节ID" v:"required"` } type DelRes struct { @@ -57,7 +57,7 @@ type DelRes struct { } type AppListReq struct { - g.Meta `path:"/chapter/app/list" tags:"APP" method:"get" summary:"App获取章节列表"` + g.Meta `path:"/chapter/app/list" tags:"APP/Chapter" method:"get" summary:"App获取章节列表"` BookId int64 `json:"bookId" dc:"书籍ID" v:"required"` IsDesc bool `json:"isDesc" dc:"是否逆序排列"` Page int `json:"page" dc:"页码"` @@ -69,7 +69,7 @@ type AppListRes struct { } type AppDetailReq struct { - g.Meta `path:"/chapter/app/detail" tags:"APP" method:"get" summary:"App获取章节详情"` + g.Meta `path:"/chapter/app/detail" tags:"APP/Chapter" method:"get" summary:"App获取章节详情"` Id int64 `json:"id" dc:"章节ID" v:"required"` } type AppDetailRes struct { @@ -85,7 +85,7 @@ type AppDetailRes struct { } type AppPurchaseReq struct { - g.Meta `path:"/chapter/app/purchase" tags:"APP" method:"post" summary:"App购买章节"` + g.Meta `path:"/chapter/app/purchase" tags:"APP/Chapter" method:"post" summary:"App购买章节"` Id int64 `json:"id" dc:"章节ID" v:"required"` } type AppPurchaseRes struct { @@ -93,11 +93,11 @@ type AppPurchaseRes struct { } type AppProgressReq struct { - g.Meta `path:"/chapter/app/progress" tags:"APP" method:"post" summary:"App上传阅读进度"` - BookId int64 `json:"bookId" dc:"书籍ID" v:"required"` - ChapterId int64 `json:"chapterId" dc:"章节ID" v:"required"` - Progress int `json:"progress" dc:"阅读进度百分比(0-100)"` + g.Meta `path:"/chapter/app/progress" tags:"APP/Chapter" method:"post" summary:"App上传阅读进度"` + BookId int64 `json:"bookId" dc:"书籍ID" v:"required"` + Chapters []model.ChapterProgressItem `json:"chapters" dc:"章节进度列表" v:"required"` } + type AppProgressRes struct { Success bool `json:"success" dc:"是否成功"` } diff --git a/api/feedback/v1/feedback.go b/api/feedback/v1/feedback.go index 8912861..167d7ac 100644 --- a/api/feedback/v1/feedback.go +++ b/api/feedback/v1/feedback.go @@ -7,7 +7,7 @@ import ( ) type ListReq struct { - g.Meta `path:"/feedback" tags:"Backend/Admin" method:"get" summary:"获取反馈列表"` + g.Meta `path:"/feedback" tags:"Backend/Feedback" method:"get" summary:"获取反馈列表"` Page int `json:"page" dc:"页码"` Size int `json:"size" dc:"每页数量"` UserId int64 `json:"userId" dc:"用户ID"` @@ -19,7 +19,7 @@ type ListRes struct { } type AddReq struct { - g.Meta `path:"/feedback" tags:"APP" method:"post" summary:"新增反馈"` + g.Meta `path:"/feedback" tags:"APP/Feedback" method:"post" summary:"新增反馈"` Content string `json:"content" dc:"反馈内容" v:"required"` } type AddRes struct { diff --git a/api/recommend/recommend.go b/api/recommend/recommend.go new file mode 100644 index 0000000..b03f6ea --- /dev/null +++ b/api/recommend/recommend.go @@ -0,0 +1,22 @@ +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package recommend + +import ( + "context" + + "server/api/recommend/v1" +) + +type IRecommendV1 interface { + List(ctx context.Context, req *v1.ListReq) (res *v1.ListRes, err error) + Add(ctx context.Context, req *v1.AddReq) (res *v1.AddRes, err error) + Edit(ctx context.Context, req *v1.EditReq) (res *v1.EditRes, err error) + Del(ctx context.Context, req *v1.DelReq) (res *v1.DelRes, err error) + SetStatus(ctx context.Context, req *v1.SetStatusReq) (res *v1.SetStatusRes, err error) + SortOrder(ctx context.Context, req *v1.SortOrderReq) (res *v1.SortOrderRes, err error) + AppList(ctx context.Context, req *v1.AppListReq) (res *v1.AppListRes, err error) + UploadCover(ctx context.Context, req *v1.UploadCoverReq) (res *v1.UploadCoverRes, err error) +} diff --git a/api/recommend/v1/recommend.go b/api/recommend/v1/recommend.go new file mode 100644 index 0000000..50c765a --- /dev/null +++ b/api/recommend/v1/recommend.go @@ -0,0 +1,93 @@ +package v1 + +import ( + "server/internal/model" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" +) + +type ListReq struct { + g.Meta `path:"/recommend" tags:"Backend/Recommend" method:"get" summary:"获取推荐列表"` + Page int `json:"page" dc:"页码"` + Size int `json:"size" dc:"每页数量"` + Type int `json:"type" dc:"推荐类型"` + Status int `json:"status" dc:"状态"` + BookId int64 `json:"bookId" dc:"书籍ID"` +} +type ListRes struct { + Total int `json:"total" dc:"总数"` + List []model.BookRecommendation `json:"list" dc:"推荐列表"` +} + +type AddReq struct { + g.Meta `path:"/recommend" tags:"Backend/Recommend" method:"post" summary:"新增推荐"` + BookId int64 `json:"bookId" dc:"书籍ID" v:"required"` + Type int `json:"type" dc:"推荐类型" v:"required"` + CoverUrl string `json:"coverUrl" dc:"封面图" v:"required"` + SortOrder int `json:"sortOrder" dc:"排序" v:"required"` + Status int `json:"status" dc:"状态" v:"required"` +} +type AddRes struct { + Success bool `json:"success" dc:"是否成功"` +} + +type EditReq struct { + g.Meta `path:"/recommend" tags:"Backend/Recommend" method:"put" summary:"编辑推荐"` + Id int64 `json:"id" dc:"推荐ID" v:"required"` + BookId int64 `json:"bookId" dc:"书籍ID" v:"required"` + Type int `json:"type" dc:"推荐类型" v:"required"` + CoverUrl string `json:"coverUrl" dc:"封面图" v:"required"` + SortOrder int `json:"sortOrder" dc:"排序" v:"required"` + Status int `json:"status" dc:"状态" v:"required"` +} +type EditRes struct { + Success bool `json:"success" dc:"是否成功"` +} + +type DelReq struct { + g.Meta `path:"/recommend" tags:"Backend/Recommend" method:"delete" summary:"删除推荐"` + Id int64 `json:"id" dc:"推荐ID" v:"required"` +} +type DelRes struct { + Success bool `json:"success" dc:"是否成功"` +} + +type SetStatusReq struct { + g.Meta `path:"/recommend/set-status" tags:"Backend/Recommend" method:"post" summary:"设置推荐状态"` + Id int64 `json:"id" dc:"推荐ID" v:"required"` + Status int `json:"status" dc:"状态" v:"required"` +} +type SetStatusRes struct { + Success bool `json:"success" dc:"是否成功"` +} + +type SortOrderReq struct { + g.Meta `path:"/recommend/sort-order" tags:"Backend/Recommend" method:"post" summary:"设置推荐排序"` + Id int64 `json:"id" dc:"推荐ID" v:"required"` + SortOrder int `json:"sortOrder" dc:"排序" v:"required"` +} +type SortOrderRes struct { + Success bool `json:"success" dc:"是否成功"` +} + +type AppListReq struct { + g.Meta `path:"/recommend/app/list" tags:"APP/Recommend" method:"get" summary:"App获取推荐列表"` + Type int `json:"type" dc:"推荐类型"` + Status int `json:"status" dc:"状态"` + Page int `json:"page" dc:"页码"` + Size int `json:"size" dc:"每页数量"` +} + +type AppListRes struct { + Total int `json:"total" dc:"总数"` + List []model.RecommendAppItem `json:"list" dc:"推荐列表"` +} + +type UploadCoverReq struct { + g.Meta `path:"/recommend/upload-cover" tags:"Backend/Recommend" method:"post" summary:"上传推荐封面图"` + File *ghttp.UploadFile `json:"file" type:"file" dc:"图片文件"` +} +type UploadCoverRes struct { + Url string `json:"url" dc:"图片访问地址"` +} diff --git a/api/system/system.go b/api/system/system.go new file mode 100644 index 0000000..89c3a10 --- /dev/null +++ b/api/system/system.go @@ -0,0 +1,16 @@ +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package system + +import ( + "context" + + "server/api/system/v1" +) + +type ISystemV1 interface { + SystemSave(ctx context.Context, req *v1.SystemSaveReq) (res *v1.SystemSaveRes, err error) + SystemVersion(ctx context.Context, req *v1.SystemVersionReq) (res *v1.SystemVersionRes, err error) +} diff --git a/api/system/v1/system.go b/api/system/v1/system.go new file mode 100644 index 0000000..d7fc7d7 --- /dev/null +++ b/api/system/v1/system.go @@ -0,0 +1,19 @@ +package v1 + +import "github.com/gogf/gf/v2/frame/g" + +type SystemSaveReq struct { + g.Meta `path:"/system/save" method:"post" tags:"Backend/System" sm:"(管理员)保存系统设置"` + Key string + Value string +} +type SystemSaveRes struct { +} +type SystemVersionReq struct { + g.Meta `path:"/system/version" method:"get" tags:"APP/System" sm:"(访客、用户、管理员)获取版本信息"` +} + +type SystemVersionRes struct { + Ios map[string]string `json:"ios"` + Android map[string]string `json:"android"` +} diff --git a/api/task/task.go b/api/task/task.go new file mode 100644 index 0000000..4eaa7ce --- /dev/null +++ b/api/task/task.go @@ -0,0 +1,19 @@ +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package task + +import ( + "context" + + "server/api/task/v1" +) + +type ITaskV1 interface { + List(ctx context.Context, req *v1.ListReq) (res *v1.ListRes, err error) + Add(ctx context.Context, req *v1.AddReq) (res *v1.AddRes, err error) + Edit(ctx context.Context, req *v1.EditReq) (res *v1.EditRes, err error) + Del(ctx context.Context, req *v1.DelReq) (res *v1.DelRes, err error) + AppList(ctx context.Context, req *v1.AppListReq) (res *v1.AppListRes, err error) +} diff --git a/api/task/v1/task.go b/api/task/v1/task.go new file mode 100644 index 0000000..6d21c2e --- /dev/null +++ b/api/task/v1/task.go @@ -0,0 +1,61 @@ +package v1 + +import ( + "server/internal/model" + + "github.com/gogf/gf/v2/frame/g" +) + +// ================== 任务接口 ================== +type ListReq struct { + g.Meta `path:"/task" tags:"Backend/Task" method:"get" summary:"任务列表"` + Page int `json:"page"` + Size int `json:"size"` + Title string `json:"title" dc:"任务标题(模糊搜索)"` + Status int `json:"status" dc:"状态:1启用,2禁用"` +} +type ListRes struct { + Total int `json:"total"` + List []model.Task `json:"list"` +} + +type AddReq struct { + g.Meta `path:"/task" tags:"Backend/Task" method:"post" summary:"新增任务"` + TaskType uint `json:"taskType"` + Title string `json:"title"` + Description string `json:"description"` + RewardPoints uint `json:"rewardPoints"` + Status int `json:"status"` +} +type AddRes struct { + Success bool `json:"success"` +} + +type EditReq struct { + g.Meta `path:"/task" tags:"Backend/Task" method:"put" summary:"编辑任务"` + Id int64 `json:"id"` + TaskType uint `json:"taskType"` + Title string `json:"title"` + Description string `json:"description"` + RewardPoints uint `json:"rewardPoints"` + Status int `json:"status"` +} +type EditRes struct { + Success bool `json:"success"` +} + +type DelReq struct { + g.Meta `path:"/task" tags:"Backend/Task" method:"delete" summary:"删除任务"` + Id int64 `json:"id"` +} +type DelRes struct { + Success bool `json:"success"` +} + +// ================== App端任务接口 ================== +type AppListReq struct { + g.Meta `path:"/task/appList" tags:"APP/Task" method:"get" summary:"App端任务列表"` +} +type AppListRes struct { + List []model.TaskSimpleItem `json:"list"` +} diff --git a/api/user/v1/user.go b/api/user/v1/user.go index 51a2927..15b8dd2 100644 --- a/api/user/v1/user.go +++ b/api/user/v1/user.go @@ -5,19 +5,24 @@ import ( ) type InfoReq struct { - g.Meta `path:"/user/info" tags:"APP" method:"get" summary:"获取用户信息"` + g.Meta `path:"/user/info" tags:"APP/User" method:"get" summary:"获取用户信息"` } type InfoRes struct { - g.Meta `mime:"application/json"` - UserId int64 `json:"userId"` - Username string `json:"username"` // 用户名 - Avatar string `json:"avatar"` // 头像 URL - Email string `json:"email"` // 邮箱 - Points uint64 `json:"points"` + g.Meta `mime:"application/json"` + Id int64 `json:"id" dc:"用户ID"` + Username string `json:"username" dc:"用户名"` + Avatar string `json:"avatar" dc:"头像URL"` + Email string `json:"email" dc:"邮箱"` + Points uint64 `json:"points" dc:"积分数量"` + BackgroundUrl string `json:"backgroundUrl" dc:"背景图片URL"` + AttentionCount int `json:"attentionCount" dc:"关注数量"` + Role string `json:"role" dc:"用户角色"` + IsAuthor bool `json:"isAuthor" dc:"是否是作者"` + AuthorStatus int `json:"authorStatus" dc:"作者申请状态:0=未申请,1=审核中,2=已通过,3=已拒绝"` } type DeleteReq struct { - g.Meta `path:"/user/delete" tags:"APP" method:"post" summary:"删除用户"` + g.Meta `path:"/user/delete" tags:"APP/User" method:"post" summary:"删除用户"` Password string `json:"password" v:"required" dc:"密码"` } @@ -26,7 +31,7 @@ type DeleteRes struct { } type LogoutReq struct { - g.Meta `path:"/user/logout" tags:"APP" method:"post" summary:"登出"` + g.Meta `path:"/user/logout" tags:"APP/User" method:"post" summary:"登出"` } type LogoutRes struct { Success bool `json:"success" dc:"是否成功"` diff --git a/hack/config.yaml b/hack/config.yaml index d033627..f078483 100644 --- a/hack/config.yaml +++ b/hack/config.yaml @@ -4,6 +4,6 @@ gfcli: gen: dao: - - link: "mysql:root:MSms0427@tcp(127.0.0.1:3306)/novel" + - link: "mysql:root:MSms0427@tcp(127.0.0.1:3306)/novel2" descriptionTag: true tablesEx: "casbin_rule" \ No newline at end of file diff --git a/internal/cmd/cmd.go b/internal/cmd/cmd.go index cc02040..b359b3b 100644 --- a/internal/cmd/cmd.go +++ b/internal/cmd/cmd.go @@ -2,6 +2,7 @@ package cmd import ( "context" + "server/internal/controller/activity" "server/internal/controller/admin" "server/internal/controller/auth" "server/internal/controller/author" @@ -9,6 +10,9 @@ import ( "server/internal/controller/category" "server/internal/controller/chapter" "server/internal/controller/feedback" + "server/internal/controller/recommend" + "server/internal/controller/system" + "server/internal/controller/task" "server/internal/controller/user" "server/internal/middleware" @@ -42,6 +46,10 @@ var ( chapter.NewV1(), feedback.NewV1(), user.NewV1(), + recommend.NewV1(), + activity.NewV1(), + task.NewV1(), + system.NewV1(), ) }) }) diff --git a/internal/consts/ads.go b/internal/consts/ads.go new file mode 100644 index 0000000..720af47 --- /dev/null +++ b/internal/consts/ads.go @@ -0,0 +1,38 @@ +package consts + +// AdState 广告状态枚举 +type AdState int + +const ( + StateFetchFailed AdState = iota + 1 // 拉取失败 + StateFetchSuccess // 拉取成功 + StateDisplayFailed // 显示失败 + StateDisplaySuccess // 显示成功 + StateNotWatched // 未观看完成 + StateWatched // 观看完成 + StateNotClicked // 未点击 + StateClicked // 已点击 + StateNotDownloaded // 未下载 + StateDownloaded // 已下载 +) + +// GetStateDescription 获取状态描述 +func GetStateDescription(state AdState) string { + descriptions := map[AdState]string{ + StateFetchFailed: "拉取失败", + StateFetchSuccess: "拉取成功", + StateDisplayFailed: "显示失败", + StateDisplaySuccess: "显示成功", + StateNotWatched: "未观看完成", + StateWatched: "观看完成", + StateNotClicked: "未点击", + StateClicked: "已点击", + StateNotDownloaded: "未下载", + StateDownloaded: "已下载", + } + + if desc, exists := descriptions[state]; exists { + return desc + } + return "未知状态" +} diff --git a/internal/controller/activity/activity.go b/internal/controller/activity/activity.go new file mode 100644 index 0000000..83449de --- /dev/null +++ b/internal/controller/activity/activity.go @@ -0,0 +1,5 @@ +// ================================================================================= +// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. +// ================================================================================= + +package activity diff --git a/internal/controller/activity/activity_new.go b/internal/controller/activity/activity_new.go new file mode 100644 index 0000000..cd3baba --- /dev/null +++ b/internal/controller/activity/activity_new.go @@ -0,0 +1,15 @@ +// ================================================================================= +// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. +// ================================================================================= + +package activity + +import ( + "server/api/activity" +) + +type ControllerV1 struct{} + +func NewV1() activity.IActivityV1 { + return &ControllerV1{} +} diff --git a/internal/controller/activity/activity_v1_item_add.go b/internal/controller/activity/activity_v1_item_add.go new file mode 100644 index 0000000..411c35e --- /dev/null +++ b/internal/controller/activity/activity_v1_item_add.go @@ -0,0 +1,23 @@ +package activity + +import ( + "context" + + v1 "server/api/activity/v1" + "server/internal/model" + "server/internal/service" +) + +func (c *ControllerV1) ItemAdd(ctx context.Context, req *v1.ItemAddReq) (res *v1.ItemAddRes, err error) { + id, err := service.SignInRewardDetails().Create(ctx, &model.SignInRewardDetail{ + RuleId: req.RuleId, + DayNumber: req.DayNumber, + RewardType: req.RewardType, + Quantity: req.Quantity, + Status: req.Status, + }) + if err != nil { + return nil, err + } + return &v1.ItemAddRes{Id: id, Success: true}, nil +} diff --git a/internal/controller/activity/activity_v1_item_del.go b/internal/controller/activity/activity_v1_item_del.go new file mode 100644 index 0000000..cdc66ff --- /dev/null +++ b/internal/controller/activity/activity_v1_item_del.go @@ -0,0 +1,17 @@ +package activity + +import ( + "context" + + v1 "server/api/activity/v1" + "server/internal/model" + "server/internal/service" +) + +func (c *ControllerV1) ItemDel(ctx context.Context, req *v1.ItemDelReq) (res *v1.ItemDelRes, err error) { + out, err := service.SignInRewardDetails().Delete(ctx, &model.SignInRewardDetailDeleteIn{Id: req.Id}) + if err != nil { + return nil, err + } + return &v1.ItemDelRes{Success: out.Success}, nil +} diff --git a/internal/controller/activity/activity_v1_item_edit.go b/internal/controller/activity/activity_v1_item_edit.go new file mode 100644 index 0000000..87fd324 --- /dev/null +++ b/internal/controller/activity/activity_v1_item_edit.go @@ -0,0 +1,24 @@ +package activity + +import ( + "context" + + v1 "server/api/activity/v1" + "server/internal/model" + "server/internal/service" +) + +func (c *ControllerV1) ItemEdit(ctx context.Context, req *v1.ItemEditReq) (res *v1.ItemEditRes, err error) { + err = service.SignInRewardDetails().Update(ctx, &model.SignInRewardDetail{ + Id: req.Id, + RuleId: req.RuleId, + DayNumber: req.DayNumber, + RewardType: req.RewardType, + Quantity: req.Quantity, + Status: req.Status, + }) + if err != nil { + return nil, err + } + return &v1.ItemEditRes{Success: true}, nil +} diff --git a/internal/controller/activity/activity_v1_item_get.go b/internal/controller/activity/activity_v1_item_get.go new file mode 100644 index 0000000..48a114f --- /dev/null +++ b/internal/controller/activity/activity_v1_item_get.go @@ -0,0 +1,17 @@ +package activity + +import ( + "context" + + v1 "server/api/activity/v1" + "server/internal/model" + "server/internal/service" +) + +func (c *ControllerV1) ItemGet(ctx context.Context, req *v1.ItemGetReq) (res *v1.ItemGetRes, err error) { + out, err := service.SignInRewardDetails().Get(ctx, &model.SignInRewardDetailGetIn{Id: req.Id}) + if err != nil { + return nil, err + } + return &v1.ItemGetRes{SignInRewardDetail: out.SignInRewardDetail}, nil +} diff --git a/internal/controller/activity/activity_v1_item_list.go b/internal/controller/activity/activity_v1_item_list.go new file mode 100644 index 0000000..31ca9cb --- /dev/null +++ b/internal/controller/activity/activity_v1_item_list.go @@ -0,0 +1,20 @@ +package activity + +import ( + "context" + v1 "server/api/activity/v1" + "server/internal/model" + "server/internal/service" +) + +func (c *ControllerV1) ItemList(ctx context.Context, req *v1.ItemListReq) (res *v1.ItemListRes, err error) { + out, err := service.SignInRewardDetails().List(ctx, &model.SignInRewardDetailListIn{ + RuleId: req.RuleId, + }) + if err != nil { + return nil, err + } + return &v1.ItemListRes{ + List: out.List, + }, nil +} diff --git a/internal/controller/activity/activity_v1_item_set_status.go b/internal/controller/activity/activity_v1_item_set_status.go new file mode 100644 index 0000000..05ed86a --- /dev/null +++ b/internal/controller/activity/activity_v1_item_set_status.go @@ -0,0 +1,19 @@ +package activity + +import ( + "context" + v1 "server/api/activity/v1" + "server/internal/model" + "server/internal/service" +) + +func (c *ControllerV1) ItemSetStatus(ctx context.Context, req *v1.ItemSetStatusReq) (res *v1.ItemSetStatusRes, err error) { + out, err := service.SignInRewardDetails().SetStatus(ctx, &model.SignInRewardDetailSetStatusIn{ + Id: req.Id, + Status: req.Status, + }) + if err != nil { + return nil, err + } + return &v1.ItemSetStatusRes{Success: out.Success}, nil +} diff --git a/internal/controller/activity/activity_v1_rule_add.go b/internal/controller/activity/activity_v1_rule_add.go new file mode 100644 index 0000000..5bf2cbe --- /dev/null +++ b/internal/controller/activity/activity_v1_rule_add.go @@ -0,0 +1,23 @@ +package activity + +import ( + "context" + "server/internal/model" + "server/internal/service" + + v1 "server/api/activity/v1" +) + +func (c *ControllerV1) RuleAdd(ctx context.Context, req *v1.RuleAddReq) (res *v1.RuleAddRes, err error) { + out, err := service.SignInRewardRules().Create(ctx, &model.SignInRewardRulesCreateIn{ + RuleName: req.RuleName, + StartDate: req.StartDate, + EndDate: req.EndDate, + CycleDays: req.CycleDays, + Status: req.Status, + }) + if err != nil { + return nil, err + } + return &v1.RuleAddRes{Success: out.Success}, nil +} diff --git a/internal/controller/activity/activity_v1_rule_del.go b/internal/controller/activity/activity_v1_rule_del.go new file mode 100644 index 0000000..a5fc144 --- /dev/null +++ b/internal/controller/activity/activity_v1_rule_del.go @@ -0,0 +1,19 @@ +package activity + +import ( + "context" + "server/internal/model" + "server/internal/service" + + "server/api/activity/v1" +) + +func (c *ControllerV1) RuleDel(ctx context.Context, req *v1.RuleDelReq) (res *v1.RuleDelRes, err error) { + out, err := service.SignInRewardRules().Delete(ctx, &model.SignInRewardRulesDeleteIn{ + Id: req.Id, + }) + if err != nil { + return nil, err + } + return &v1.RuleDelRes{Success: out.Success}, nil +} diff --git a/internal/controller/activity/activity_v1_rule_edit.go b/internal/controller/activity/activity_v1_rule_edit.go new file mode 100644 index 0000000..d3bc807 --- /dev/null +++ b/internal/controller/activity/activity_v1_rule_edit.go @@ -0,0 +1,24 @@ +package activity + +import ( + "context" + "server/internal/model" + "server/internal/service" + + "server/api/activity/v1" +) + +func (c *ControllerV1) RuleEdit(ctx context.Context, req *v1.RuleEditReq) (res *v1.RuleEditRes, err error) { + out, err := service.SignInRewardRules().Update(ctx, &model.SignInRewardRulesUpdateIn{ + Id: req.Id, + RuleName: req.RuleName, + CycleDays: req.CycleDays, + StartDate: req.StartDate, + EndDate: req.EndDate, + Status: req.Status, + }) + if err != nil { + return nil, err + } + return &v1.RuleEditRes{Success: out.Success}, nil +} diff --git a/internal/controller/activity/activity_v1_rule_list.go b/internal/controller/activity/activity_v1_rule_list.go new file mode 100644 index 0000000..8669bd1 --- /dev/null +++ b/internal/controller/activity/activity_v1_rule_list.go @@ -0,0 +1,25 @@ +package activity + +import ( + "context" + "server/internal/model" + "server/internal/service" + + "server/api/activity/v1" +) + +func (c *ControllerV1) RuleList(ctx context.Context, req *v1.RuleListReq) (res *v1.RuleListRes, err error) { + out, err := service.SignInRewardRules().List(ctx, &model.SignInRewardRulesListIn{ + Page: req.Page, + RuleName: req.RuleName, + Size: req.Size, + Status: req.Status, + }) + if err != nil { + return nil, err + } + return &v1.RuleListRes{ + Total: out.Total, + List: out.List, + }, nil +} diff --git a/internal/controller/activity/activity_v1_rule_set_status.go b/internal/controller/activity/activity_v1_rule_set_status.go new file mode 100644 index 0000000..65789fe --- /dev/null +++ b/internal/controller/activity/activity_v1_rule_set_status.go @@ -0,0 +1,20 @@ +package activity + +import ( + "context" + "server/internal/model" + "server/internal/service" + + "server/api/activity/v1" +) + +func (c *ControllerV1) RuleSetStatus(ctx context.Context, req *v1.RuleSetStatusReq) (res *v1.RuleSetStatusRes, err error) { + out, err := service.SignInRewardRules().SetStatus(ctx, &model.SignInRewardRulesSetStatusIn{ + Id: req.Id, + Status: req.Status, + }) + if err != nil { + return nil, err + } + return &v1.RuleSetStatusRes{Success: out.Success}, nil +} diff --git a/internal/controller/activity/activity_v1_sign_in.go b/internal/controller/activity/activity_v1_sign_in.go new file mode 100644 index 0000000..b73ab17 --- /dev/null +++ b/internal/controller/activity/activity_v1_sign_in.go @@ -0,0 +1,26 @@ +package activity + +import ( + "context" + v1 "server/api/activity/v1" + "server/internal/model" + "server/internal/service" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gtime" +) + +func (c *ControllerV1) SignIn(ctx context.Context, req *v1.SignInReq) (res *v1.SignInRes, err error) { + userId := g.RequestFromCtx(ctx).GetCtxVar("id").Int64() + in := &model.UserSignInLogSignIn{ + UserId: userId, + RuleId: req.RuleId, + RewardDetailId: req.RewardDetailId, + SignInDate: gtime.Now(), // 或根据 req.SignInDate 解析 + } + out, err := service.UserSignInLogs().Sign(ctx, in) + if err != nil { + return nil, err + } + return &v1.SignInRes{Success: out.Success}, nil +} diff --git a/internal/controller/activity/activity_v1_sign_in_list.go b/internal/controller/activity/activity_v1_sign_in_list.go new file mode 100644 index 0000000..adb76d3 --- /dev/null +++ b/internal/controller/activity/activity_v1_sign_in_list.go @@ -0,0 +1,24 @@ +package activity + +import ( + "context" + "github.com/gogf/gf/v2/frame/g" + "server/internal/model" + "server/internal/service" + + "server/api/activity/v1" +) + +func (c *ControllerV1) SignInList(ctx context.Context, req *v1.SignInListReq) (res *v1.SignInListRes, err error) { + userId := g.RequestFromCtx(ctx).GetCtxVar("id").Int64() + out, err := service.SignInRewardRules().SignInList(ctx, &model.SignInListIn{ + UserId: userId, + }) + if err != nil { + return nil, err + } + res = &v1.SignInListRes{ + List: out.List, + } + return &v1.SignInListRes{List: out.List}, nil +} diff --git a/internal/controller/admin/admin_v1_info.go b/internal/controller/admin/admin_v1_info.go index 443aad8..d0cb402 100644 --- a/internal/controller/admin/admin_v1_info.go +++ b/internal/controller/admin/admin_v1_info.go @@ -2,11 +2,12 @@ package admin import ( "context" - "github.com/gogf/gf/v2/frame/g" "server/internal/model" "server/internal/service" - "server/api/admin/v1" + "github.com/gogf/gf/v2/frame/g" + + v1 "server/api/admin/v1" ) func (c *ControllerV1) Info(ctx context.Context, req *v1.InfoReq) (res *v1.InfoRes, err error) { @@ -17,7 +18,8 @@ func (c *ControllerV1) Info(ctx context.Context, req *v1.InfoReq) (res *v1.InfoR return nil, err } return &v1.InfoRes{ - AdminId: out.AdminId, + Id: out.Id, Username: out.Username, + Role: out.Role, }, nil } diff --git a/internal/controller/ads/ads.go b/internal/controller/ads/ads.go new file mode 100644 index 0000000..56c56c1 --- /dev/null +++ b/internal/controller/ads/ads.go @@ -0,0 +1,5 @@ +// ================================================================================= +// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. +// ================================================================================= + +package ads diff --git a/internal/controller/ads/ads_new.go b/internal/controller/ads/ads_new.go new file mode 100644 index 0000000..2cc8157 --- /dev/null +++ b/internal/controller/ads/ads_new.go @@ -0,0 +1,15 @@ +// ================================================================================= +// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. +// ================================================================================= + +package ads + +import ( + "server/api/ads" +) + +type ControllerV1 struct{} + +func NewV1() ads.IAdsV1 { + return &ControllerV1{} +} diff --git a/internal/controller/ads/ads_v1_upload.go b/internal/controller/ads/ads_v1_upload.go new file mode 100644 index 0000000..6788a79 --- /dev/null +++ b/internal/controller/ads/ads_v1_upload.go @@ -0,0 +1,27 @@ +package ads + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" + + v1 "server/api/ads/v1" + "server/internal/model" + "server/internal/service" +) + +func (c *ControllerV1) Upload(ctx context.Context, req *v1.UploadReq) (res *v1.UploadRes, err error) { + userId := g.RequestFromCtx(ctx).GetCtxVar("id").Int64() + out, err := service.Ads().Upload(ctx, &model.AdsUploadIn{ + UserId: userId, + NodeUid: req.NodeUid, + DeviceCode: req.DeviceCode, + Data: req.Data, + }) + if err != nil { + return nil, err + } + return &v1.UploadRes{ + Success: out.Success, + }, nil +} \ No newline at end of file diff --git a/internal/controller/auth/auth_v1_author_login.go b/internal/controller/auth/auth_v1_author_login.go new file mode 100644 index 0000000..419ab63 --- /dev/null +++ b/internal/controller/auth/auth_v1_author_login.go @@ -0,0 +1,17 @@ +package auth + +import ( + "context" + + v1 "server/api/auth/v1" + "server/internal/model" + "server/internal/service" +) + +func (c *ControllerV1) AuthorLogin(ctx context.Context, req *v1.AuthorLoginReq) (res *v1.AuthorLoginRes, err error) { + out, err := service.User().AuthorLogin(ctx, &model.UserLoginIn{Email: req.Email, Password: req.Password}) + if err != nil { + return nil, err + } + return &v1.AuthorLoginRes{Token: out.Token}, nil +} diff --git a/internal/controller/auth/auth_v1_user_login.go b/internal/controller/auth/auth_v1_user_login.go index 70327ae..1e07174 100644 --- a/internal/controller/auth/auth_v1_user_login.go +++ b/internal/controller/auth/auth_v1_user_login.go @@ -5,7 +5,7 @@ import ( "server/internal/model" "server/internal/service" - "server/api/auth/v1" + v1 "server/api/auth/v1" ) func (c *ControllerV1) UserLogin(ctx context.Context, req *v1.UserLoginReq) (res *v1.UserLoginRes, err error) { diff --git a/internal/controller/author/author_v1_apply.go b/internal/controller/author/author_v1_apply.go new file mode 100644 index 0000000..2fc7c31 --- /dev/null +++ b/internal/controller/author/author_v1_apply.go @@ -0,0 +1,23 @@ +package author + +import ( + "context" + "github.com/gogf/gf/v2/frame/g" + "server/internal/model" + "server/internal/service" + + "server/api/author/v1" +) + +func (c *ControllerV1) Apply(ctx context.Context, req *v1.ApplyReq) (res *v1.ApplyRes, err error) { + userId := g.RequestFromCtx(ctx).GetCtxVar("id").Int64() + out, err := service.Author().Apply(ctx, &model.AuthorApplyIn{ + UserId: userId, + PenName: req.PenName, + Bio: req.Bio, + }) + if err != nil { + return nil, err + } + return &v1.ApplyRes{Success: out.Success, Msg: "Please wait patiently for the management review"}, nil +} diff --git a/internal/controller/author/author_v1_author_info.go b/internal/controller/author/author_v1_author_info.go new file mode 100644 index 0000000..1a6b5df --- /dev/null +++ b/internal/controller/author/author_v1_author_info.go @@ -0,0 +1,23 @@ +package author + +import ( + "context" + "github.com/gogf/gf/v2/frame/g" + "server/internal/model" + "server/internal/service" + + "server/api/author/v1" +) + +func (c *ControllerV1) AuthorInfo(ctx context.Context, req *v1.AuthorInfoReq) (res *v1.AuthorInfoRes, err error) { + userId := g.RequestFromCtx(ctx).GetCtxVar("id").Int64() + out, err := service.Author().AuthorInfo(ctx, &model.AuthorInfoIn{UserId: userId}) + if err != nil { + return nil, err + } + return &v1.AuthorInfoRes{ + Id: out.Id, + PenName: out.PenName, + Role: out.Role, + }, nil +} diff --git a/internal/controller/author/author_v1_detail.go b/internal/controller/author/author_v1_detail.go index fa67603..303cd20 100644 --- a/internal/controller/author/author_v1_detail.go +++ b/internal/controller/author/author_v1_detail.go @@ -13,5 +13,5 @@ func (c *ControllerV1) Detail(ctx context.Context, req *v1.DetailReq) (res *v1.D if err != nil { return nil, err } - return &v1.DetailRes{Author: out}, nil + return &v1.DetailRes{out}, nil } diff --git a/internal/controller/author/author_v1_review.go b/internal/controller/author/author_v1_review.go new file mode 100644 index 0000000..e9fbcf5 --- /dev/null +++ b/internal/controller/author/author_v1_review.go @@ -0,0 +1,21 @@ +package author + +import ( + "context" + + "server/internal/model" + "server/internal/service" + + v1 "server/api/author/v1" +) + +func (c *ControllerV1) Review(ctx context.Context, req *v1.ReviewReq) (res *v1.ReviewRes, err error) { + out, err := service.Author().Review(ctx, &model.AuthorReviewIn{ + AuthorId: req.AuthorId, + Status: req.Status, + }) + if err != nil { + return nil, err + } + return &v1.ReviewRes{Success: out.Success}, nil +} diff --git a/internal/controller/book/book_v1_app_detail.go b/internal/controller/book/book_v1_app_detail.go index 1dc79aa..7500729 100644 --- a/internal/controller/book/book_v1_app_detail.go +++ b/internal/controller/book/book_v1_app_detail.go @@ -23,22 +23,29 @@ func (c *ControllerV1) AppDetail(ctx context.Context, req *v1.AppDetailReq) (res return &v1.AppDetailRes{ Id: out.Id, - AuthorId: out.AuthorId, - CategoryId: out.CategoryId, - Title: out.Title, CoverUrl: out.CoverUrl, + Rating: out.Rating, + Title: out.Title, Description: out.Description, + AuthorId: out.AuthorId, + Author: out.Author, + CategoryId: out.CategoryId, + Category: out.Category, Status: out.Status, + WordsCount: out.WordsCount, + ChaptersCount: out.ChaptersCount, + ReadCount: out.ReadCount, + CurrentReaders: out.CurrentReaders, Tags: out.Tags, IsRecommended: out.IsRecommended, - Rating: out.Rating, - CurrentReaders: out.CurrentReaders, CreatedAt: out.CreatedAt.String(), UpdatedAt: out.UpdatedAt.String(), - // 添加阅读进度信息 - HasRead: out.HasRead, - ReadProgress: out.ReadProgress, - LastChapterId: out.LastChapterId, - LastReadAt: out.LastReadAt, + HasRated: out.HasRated, + MyRating: out.MyRating, + HasRead: out.HasRead, + ReadProgress: out.ReadProgress, + LastChapterId: out.LastChapterId, + LastReadAt: out.LastReadAt, + IsInBookshelf: out.IsInBookshelf, }, nil } diff --git a/internal/controller/book/book_v1_app_list.go b/internal/controller/book/book_v1_app_list.go index 9c4a5e4..d30c62d 100644 --- a/internal/controller/book/book_v1_app_list.go +++ b/internal/controller/book/book_v1_app_list.go @@ -17,6 +17,7 @@ func (c *ControllerV1) AppList(ctx context.Context, req *v1.AppListReq) (res *v1 IsRecommended: req.IsRecommended, IsFeatured: req.IsFeatured, IsLatest: req.IsLatest, + IsHot: req.IsHot, CategoryId: req.CategoryId, Title: req.Title, AuthorId: req.AuthorId, diff --git a/internal/controller/book/book_v1_book_cover_image.go b/internal/controller/book/book_v1_book_cover_image.go new file mode 100644 index 0000000..af35418 --- /dev/null +++ b/internal/controller/book/book_v1_book_cover_image.go @@ -0,0 +1,24 @@ +package book + +import ( + "context" + "server/internal/model" + "server/internal/service" + + v1 "server/api/book/v1" + + "server/utility/ecode" + + "github.com/gogf/gf/v2/errors/gerror" +) + +func (c *ControllerV1) BookCoverImage(ctx context.Context, req *v1.BookCoverImageReq) (res *v1.BookCoverImageRes, err error) { + if req.File == nil { + return nil, gerror.NewCode(ecode.Fail.Sub("image_file_required").Code()) + } + image, err := service.Upload().UploadImage(ctx, &model.UploadImageIn{File: req.File, Type: "book"}) + if err != nil { + return nil, err + } + return &v1.BookCoverImageRes{ImageUrl: image.ImageUrl}, nil +} diff --git a/internal/controller/book/book_v1_book_set_featured.go b/internal/controller/book/book_v1_book_set_featured.go index fdf41b2..8029adc 100644 --- a/internal/controller/book/book_v1_book_set_featured.go +++ b/internal/controller/book/book_v1_book_set_featured.go @@ -3,12 +3,15 @@ package book import ( "context" - "github.com/gogf/gf/v2/errors/gcode" - "github.com/gogf/gf/v2/errors/gerror" - "server/api/book/v1" + "server/internal/model" + "server/internal/service" ) func (c *ControllerV1) BookSetFeatured(ctx context.Context, req *v1.BookSetFeaturedReq) (res *v1.BookSetFeaturedRes, err error) { - return nil, gerror.NewCode(gcode.CodeNotImplemented) + out, err := service.Book().SetFeatured(ctx, &model.BookSetFeaturedIn{Id: req.Id, IsFeatured: req.IsFeatured}) + if err != nil { + return nil, err + } + return &v1.BookSetFeaturedRes{Success: out.Success}, nil } diff --git a/internal/controller/book/book_v1_book_set_hot.go b/internal/controller/book/book_v1_book_set_hot.go new file mode 100644 index 0000000..fbd52e9 --- /dev/null +++ b/internal/controller/book/book_v1_book_set_hot.go @@ -0,0 +1,22 @@ +package book + +import ( + "context" + + v1 "server/api/book/v1" + "server/internal/model" + "server/internal/service" +) + +func (c *ControllerV1) BookSetHot(ctx context.Context, req *v1.BookSetHotReq) (res *v1.BookSetHotRes, err error) { + out, err := service.Book().SetHot(ctx, &model.BookSetHotIn{ + Id: req.Id, + IsHot: req.IsHot, + }) + if err != nil { + return nil, err + } + return &v1.BookSetHotRes{ + Success: out.Success, + }, nil +} diff --git a/internal/controller/book/book_v1_book_set_recommended.go b/internal/controller/book/book_v1_book_set_recommended.go index e02499b..a5100a1 100644 --- a/internal/controller/book/book_v1_book_set_recommended.go +++ b/internal/controller/book/book_v1_book_set_recommended.go @@ -2,13 +2,16 @@ package book import ( "context" - - "github.com/gogf/gf/v2/errors/gcode" - "github.com/gogf/gf/v2/errors/gerror" + "server/internal/model" + "server/internal/service" "server/api/book/v1" ) func (c *ControllerV1) BookSetRecommended(ctx context.Context, req *v1.BookSetRecommendedReq) (res *v1.BookSetRecommendedRes, err error) { - return nil, gerror.NewCode(gcode.CodeNotImplemented) + out, err := service.Book().SetRecommended(ctx, &model.BookSetRecommendedIn{Id: req.Id, IsRecommended: req.IsRecommended}) + if err != nil { + return nil, err + } + return &v1.BookSetRecommendedRes{Success: out.Success}, nil } diff --git a/internal/controller/book/book_v1_history_remove.go b/internal/controller/book/book_v1_history_remove.go new file mode 100644 index 0000000..6c9cb57 --- /dev/null +++ b/internal/controller/book/book_v1_history_remove.go @@ -0,0 +1,21 @@ +package book + +import ( + "context" + "github.com/gogf/gf/v2/frame/g" + "server/internal/model" + "server/internal/service" + + "server/api/book/v1" +) + +func (c *ControllerV1) HistoryRemove(ctx context.Context, req *v1.HistoryRemoveReq) (res *v1.HistoryRemoveRes, err error) { + userId := g.RequestFromCtx(ctx).GetCtxVar("id").Int64() + out, err := service.UserReadHistory().Remove(ctx, &model.UserReadHistoryDelIn{BookIds: req.BookIds, UserId: userId}) + if err != nil { + return nil, err + } + return &v1.HistoryRemoveRes{ + Success: out.Success, + }, nil +} diff --git a/internal/controller/chapter/chapter_v1_app_progress.go b/internal/controller/chapter/chapter_v1_app_progress.go index fbee63b..df498c5 100644 --- a/internal/controller/chapter/chapter_v1_app_progress.go +++ b/internal/controller/chapter/chapter_v1_app_progress.go @@ -11,11 +11,10 @@ import ( func (c *ControllerV1) AppProgress(ctx context.Context, req *v1.AppProgressReq) (res *v1.AppProgressRes, err error) { userId := g.RequestFromCtx(ctx).GetCtxVar("id").Int64() - out, err := service.Chapter().AppProgress(ctx, &model.ChapterAppProgressIn{ - BookId: req.BookId, - ChapterId: req.ChapterId, - Progress: req.Progress, - UserId: userId, + out, err := service.Chapter().AppBatchProgress(ctx, &model.ChapterAppBatchProgressIn{ + BookId: req.BookId, + Chapters: req.Chapters, + UserId: userId, }) if err != nil { return nil, err diff --git a/internal/controller/recommend/recommend.go b/internal/controller/recommend/recommend.go new file mode 100644 index 0000000..f74e016 --- /dev/null +++ b/internal/controller/recommend/recommend.go @@ -0,0 +1,5 @@ +// ================================================================================= +// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. +// ================================================================================= + +package recommend diff --git a/internal/controller/recommend/recommend_new.go b/internal/controller/recommend/recommend_new.go new file mode 100644 index 0000000..5a739e8 --- /dev/null +++ b/internal/controller/recommend/recommend_new.go @@ -0,0 +1,15 @@ +// ================================================================================= +// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. +// ================================================================================= + +package recommend + +import ( + "server/api/recommend" +) + +type ControllerV1 struct{} + +func NewV1() recommend.IRecommendV1 { + return &ControllerV1{} +} diff --git a/internal/controller/recommend/recommend_v1_add.go b/internal/controller/recommend/recommend_v1_add.go new file mode 100644 index 0000000..e2c82e8 --- /dev/null +++ b/internal/controller/recommend/recommend_v1_add.go @@ -0,0 +1,23 @@ +package recommend + +import ( + "context" + "server/internal/model" + "server/internal/service" + + v1 "server/api/recommend/v1" +) + +func (c *ControllerV1) Add(ctx context.Context, req *v1.AddReq) (res *v1.AddRes, err error) { + out, err := service.BookRecommendations().Create(ctx, &model.BookRecommendationsCreateIn{ + BookId: req.BookId, + Type: req.Type, + CoverUrl: req.CoverUrl, + SortOrder: req.SortOrder, + Status: req.Status, + }) + if err != nil { + return nil, err + } + return &v1.AddRes{Success: out.Success}, nil +} diff --git a/internal/controller/recommend/recommend_v1_app_list.go b/internal/controller/recommend/recommend_v1_app_list.go new file mode 100644 index 0000000..7d2ceec --- /dev/null +++ b/internal/controller/recommend/recommend_v1_app_list.go @@ -0,0 +1,35 @@ +package recommend + +import ( + "context" + "server/internal/model" + "server/internal/service" + + v1 "server/api/recommend/v1" +) + +func (c *ControllerV1) AppList(ctx context.Context, req *v1.AppListReq) (res *v1.AppListRes, err error) { + out, err := service.BookRecommendations().AppList(ctx, &model.BookRecommendationsListIn{ + Page: req.Page, + Size: req.Size, + Type: req.Type, + Status: req.Status, + }) + if err != nil { + return nil, err + } + // 转换为 RecommendAppItem + list := make([]model.RecommendAppItem, 0, len(out.List)) + for _, item := range out.List { + list = append(list, model.RecommendAppItem{ + Id: item.Id, + BookId: item.BookId, + CoverUrl: item.CoverUrl, + SortOrder: item.SortOrder, + }) + } + return &v1.AppListRes{ + Total: out.Total, + List: list, + }, nil +} diff --git a/internal/controller/recommend/recommend_v1_del.go b/internal/controller/recommend/recommend_v1_del.go new file mode 100644 index 0000000..323e81e --- /dev/null +++ b/internal/controller/recommend/recommend_v1_del.go @@ -0,0 +1,19 @@ +package recommend + +import ( + "context" + "server/internal/model" + "server/internal/service" + + v1 "server/api/recommend/v1" +) + +func (c *ControllerV1) Del(ctx context.Context, req *v1.DelReq) (res *v1.DelRes, err error) { + out, err := service.BookRecommendations().Delete(ctx, &model.BookRecommendationsDeleteIn{ + Id: req.Id, + }) + if err != nil { + return nil, err + } + return &v1.DelRes{Success: out.Success}, nil +} diff --git a/internal/controller/recommend/recommend_v1_edit.go b/internal/controller/recommend/recommend_v1_edit.go new file mode 100644 index 0000000..3140153 --- /dev/null +++ b/internal/controller/recommend/recommend_v1_edit.go @@ -0,0 +1,24 @@ +package recommend + +import ( + "context" + "server/internal/model" + "server/internal/service" + + v1 "server/api/recommend/v1" +) + +func (c *ControllerV1) Edit(ctx context.Context, req *v1.EditReq) (res *v1.EditRes, err error) { + out, err := service.BookRecommendations().Update(ctx, &model.BookRecommendationsUpdateIn{ + Id: req.Id, + BookId: req.BookId, + Type: req.Type, + CoverUrl: req.CoverUrl, + SortOrder: req.SortOrder, + Status: req.Status, + }) + if err != nil { + return nil, err + } + return &v1.EditRes{Success: out.Success}, nil +} diff --git a/internal/controller/recommend/recommend_v1_list.go b/internal/controller/recommend/recommend_v1_list.go new file mode 100644 index 0000000..b6160bf --- /dev/null +++ b/internal/controller/recommend/recommend_v1_list.go @@ -0,0 +1,26 @@ +package recommend + +import ( + "context" + "server/internal/model" + "server/internal/service" + + v1 "server/api/recommend/v1" +) + +func (c *ControllerV1) List(ctx context.Context, req *v1.ListReq) (res *v1.ListRes, err error) { + out, err := service.BookRecommendations().List(ctx, &model.BookRecommendationsListIn{ + Page: req.Page, + Size: req.Size, + Type: req.Type, + Status: req.Status, + BookId: req.BookId, + }) + if err != nil { + return nil, err + } + return &v1.ListRes{ + Total: out.Total, + List: out.List, + }, nil +} diff --git a/internal/controller/recommend/recommend_v1_set_status.go b/internal/controller/recommend/recommend_v1_set_status.go new file mode 100644 index 0000000..a94e2a7 --- /dev/null +++ b/internal/controller/recommend/recommend_v1_set_status.go @@ -0,0 +1,20 @@ +package recommend + +import ( + "context" + "server/internal/model" + "server/internal/service" + + v1 "server/api/recommend/v1" +) + +func (c *ControllerV1) SetStatus(ctx context.Context, req *v1.SetStatusReq) (res *v1.SetStatusRes, err error) { + out, err := service.BookRecommendations().SetStatus(ctx, &model.BookRecommendationsSetStatusIn{ + Id: req.Id, + Status: req.Status, + }) + if err != nil { + return nil, err + } + return &v1.SetStatusRes{Success: out.Success}, nil +} diff --git a/internal/controller/recommend/recommend_v1_sort_order.go b/internal/controller/recommend/recommend_v1_sort_order.go new file mode 100644 index 0000000..c5fc656 --- /dev/null +++ b/internal/controller/recommend/recommend_v1_sort_order.go @@ -0,0 +1,20 @@ +package recommend + +import ( + "context" + "server/internal/model" + "server/internal/service" + + v1 "server/api/recommend/v1" +) + +func (c *ControllerV1) SortOrder(ctx context.Context, req *v1.SortOrderReq) (res *v1.SortOrderRes, err error) { + out, err := service.BookRecommendations().SortOrder(ctx, &model.BookRecommendationsSortOrderIn{ + Id: req.Id, + SortOrder: req.SortOrder, + }) + if err != nil { + return nil, err + } + return &v1.SortOrderRes{Success: out.Success}, nil +} diff --git a/internal/controller/recommend/recommend_v1_upload_cover.go b/internal/controller/recommend/recommend_v1_upload_cover.go new file mode 100644 index 0000000..2a4559e --- /dev/null +++ b/internal/controller/recommend/recommend_v1_upload_cover.go @@ -0,0 +1,16 @@ +package recommend + +import ( + "context" + "server/internal/service" + + v1 "server/api/recommend/v1" +) + +func (c *ControllerV1) UploadCover(ctx context.Context, req *v1.UploadCoverReq) (res *v1.UploadCoverRes, err error) { + url, err := service.BookRecommendations().UploadCover(ctx, req.File) + if err != nil { + return nil, err + } + return &v1.UploadCoverRes{Url: url}, nil +} diff --git a/internal/controller/system/system.go b/internal/controller/system/system.go new file mode 100644 index 0000000..24bd259 --- /dev/null +++ b/internal/controller/system/system.go @@ -0,0 +1,5 @@ +// ================================================================================= +// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. +// ================================================================================= + +package system diff --git a/internal/controller/system/system_new.go b/internal/controller/system/system_new.go new file mode 100644 index 0000000..f17640a --- /dev/null +++ b/internal/controller/system/system_new.go @@ -0,0 +1,15 @@ +// ================================================================================= +// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. +// ================================================================================= + +package system + +import ( + "server/api/system" +) + +type ControllerV1 struct{} + +func NewV1() system.ISystemV1 { + return &ControllerV1{} +} diff --git a/internal/controller/system/system_v1_system_save.go b/internal/controller/system/system_v1_system_save.go new file mode 100644 index 0000000..eff8711 --- /dev/null +++ b/internal/controller/system/system_v1_system_save.go @@ -0,0 +1,16 @@ +package system + +import ( + "context" + "server/internal/model" + "server/internal/service" + + "server/api/system/v1" +) + +func (c *ControllerV1) SystemSave(ctx context.Context, req *v1.SystemSaveReq) (res *v1.SystemSaveRes, err error) { + return nil, service.System().Save(ctx, &model.SystemSaveInput{ + Key: req.Key, + Value: req.Value, + }) +} diff --git a/internal/controller/system/system_v1_system_version.go b/internal/controller/system/system_v1_system_version.go new file mode 100644 index 0000000..a1b9b10 --- /dev/null +++ b/internal/controller/system/system_v1_system_version.go @@ -0,0 +1,16 @@ +package system + +import ( + "context" + "server/internal/service" + + "server/api/system/v1" +) + +func (c *ControllerV1) SystemVersion(ctx context.Context, req *v1.SystemVersionReq) (res *v1.SystemVersionRes, err error) { + version, err := service.System().Version(ctx) + if err != nil { + return nil, err + } + return &v1.SystemVersionRes{Ios: version.Ios, Android: version.Android}, nil +} diff --git a/internal/controller/task/task.go b/internal/controller/task/task.go new file mode 100644 index 0000000..4399401 --- /dev/null +++ b/internal/controller/task/task.go @@ -0,0 +1,5 @@ +// ================================================================================= +// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. +// ================================================================================= + +package task diff --git a/internal/controller/task/task_new.go b/internal/controller/task/task_new.go new file mode 100644 index 0000000..be0124d --- /dev/null +++ b/internal/controller/task/task_new.go @@ -0,0 +1,15 @@ +// ================================================================================= +// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. +// ================================================================================= + +package task + +import ( + "server/api/task" +) + +type ControllerV1 struct{} + +func NewV1() task.ITaskV1 { + return &ControllerV1{} +} diff --git a/internal/controller/task/task_v1_add.go b/internal/controller/task/task_v1_add.go new file mode 100644 index 0000000..13b3303 --- /dev/null +++ b/internal/controller/task/task_v1_add.go @@ -0,0 +1,23 @@ +package task + +import ( + "context" + + v1 "server/api/task/v1" + "server/internal/model" + "server/internal/service" +) + +func (c *ControllerV1) Add(ctx context.Context, req *v1.AddReq) (res *v1.AddRes, err error) { + out, err := service.Task().Add(ctx, &model.TaskAddIn{ + TaskType: req.TaskType, + Title: req.Title, + Description: req.Description, + RewardPoints: req.RewardPoints, + Status: req.Status, + }) + if err != nil { + return nil, err + } + return &v1.AddRes{Success: out.Success}, nil +} diff --git a/internal/controller/task/task_v1_app_list.go b/internal/controller/task/task_v1_app_list.go new file mode 100644 index 0000000..69f894f --- /dev/null +++ b/internal/controller/task/task_v1_app_list.go @@ -0,0 +1,19 @@ +package task + +import ( + "context" + + v1 "server/api/task/v1" + "server/internal/model" + "server/internal/service" +) + +func (c *ControllerV1) AppList(ctx context.Context, req *v1.AppListReq) (res *v1.AppListRes, err error) { + out, err := service.Task().AppList(ctx, &model.TaskAppListIn{}) + if err != nil { + return nil, err + } + return &v1.AppListRes{ + List: out.List, + }, nil +} diff --git a/internal/controller/task/task_v1_del.go b/internal/controller/task/task_v1_del.go new file mode 100644 index 0000000..f868c67 --- /dev/null +++ b/internal/controller/task/task_v1_del.go @@ -0,0 +1,17 @@ +package task + +import ( + "context" + + v1 "server/api/task/v1" + "server/internal/model" + "server/internal/service" +) + +func (c *ControllerV1) Del(ctx context.Context, req *v1.DelReq) (res *v1.DelRes, err error) { + out, err := service.Task().Delete(ctx, &model.TaskDelIn{Id: req.Id}) + if err != nil { + return nil, err + } + return &v1.DelRes{Success: out.Success}, nil +} diff --git a/internal/controller/task/task_v1_edit.go b/internal/controller/task/task_v1_edit.go new file mode 100644 index 0000000..fb8f43f --- /dev/null +++ b/internal/controller/task/task_v1_edit.go @@ -0,0 +1,24 @@ +package task + +import ( + "context" + + v1 "server/api/task/v1" + "server/internal/model" + "server/internal/service" +) + +func (c *ControllerV1) Edit(ctx context.Context, req *v1.EditReq) (res *v1.EditRes, err error) { + out, err := service.Task().Edit(ctx, &model.TaskEditIn{ + Id: req.Id, + TaskType: req.TaskType, + Title: req.Title, + Description: req.Description, + RewardPoints: req.RewardPoints, + Status: req.Status, + }) + if err != nil { + return nil, err + } + return &v1.EditRes{Success: out.Success}, nil +} diff --git a/internal/controller/task/task_v1_list.go b/internal/controller/task/task_v1_list.go new file mode 100644 index 0000000..caf16de --- /dev/null +++ b/internal/controller/task/task_v1_list.go @@ -0,0 +1,25 @@ +package task + +import ( + "context" + + v1 "server/api/task/v1" + "server/internal/model" + "server/internal/service" +) + +func (c *ControllerV1) List(ctx context.Context, req *v1.ListReq) (res *v1.ListRes, err error) { + out, err := service.Task().List(ctx, &model.TaskListIn{ + Page: req.Page, + Size: req.Size, + Title: req.Title, + Status: req.Status, + }) + if err != nil { + return nil, err + } + return &v1.ListRes{ + Total: out.Total, + List: out.List, + }, nil +} diff --git a/internal/controller/user/user_v1_info.go b/internal/controller/user/user_v1_info.go index 9c8a07b..b5517d2 100644 --- a/internal/controller/user/user_v1_info.go +++ b/internal/controller/user/user_v1_info.go @@ -23,10 +23,14 @@ func (c *ControllerV1) Info(ctx context.Context, req *v1.InfoReq) (res *v1.InfoR } return &v1.InfoRes{ - UserId: out.UserId, - Username: out.Username, - Avatar: out.Avatar, - Email: out.Email, - Points: out.Points, + Id: out.Id, + Username: out.Username, + Avatar: out.Avatar, + Email: out.Email, + Points: out.Points, + BackgroundUrl: out.BackgroundUrl, + AttentionCount: out.AttentionCount, + IsAuthor: out.IsAuthor, + AuthorStatus: out.AuthorStatus, }, nil } diff --git a/internal/controller/user/user_v1_logout.go b/internal/controller/user/user_v1_logout.go index 5ee99aa..f68ec1e 100644 --- a/internal/controller/user/user_v1_logout.go +++ b/internal/controller/user/user_v1_logout.go @@ -2,13 +2,15 @@ package user import ( "context" - - "github.com/gogf/gf/v2/errors/gcode" - "github.com/gogf/gf/v2/errors/gerror" + "fmt" + "github.com/gogf/gf/v2/frame/g" "server/api/user/v1" ) func (c *ControllerV1) Logout(ctx context.Context, req *v1.LogoutReq) (res *v1.LogoutRes, err error) { - return nil, gerror.NewCode(gcode.CodeNotImplemented) + g.Redis().Do(ctx, "SETEX", fmt.Sprintf("blacklist:%s", g.RequestFromCtx(ctx).GetCtxVar("jti").String()), "1") + return &v1.LogoutRes{ + Success: true, + }, nil } diff --git a/internal/dao/ad_event_logs.go b/internal/dao/ad_event_logs.go new file mode 100644 index 0000000..bed1126 --- /dev/null +++ b/internal/dao/ad_event_logs.go @@ -0,0 +1,27 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "server/internal/dao/internal" +) + +// internalAdEventLogsDao is an internal type for wrapping the internal DAO implementation. +type internalAdEventLogsDao = *internal.AdEventLogsDao + +// adEventLogsDao is the data access object for the table ad_event_logs. +// You can define custom methods on it to extend its functionality as needed. +type adEventLogsDao struct { + internalAdEventLogsDao +} + +var ( + // AdEventLogs is a globally accessible object for table ad_event_logs operations. + AdEventLogs = adEventLogsDao{ + internal.NewAdEventLogsDao(), + } +) + +// Add your custom methods and functionality below. diff --git a/internal/dao/ad_event_transitions.go b/internal/dao/ad_event_transitions.go new file mode 100644 index 0000000..0d06e06 --- /dev/null +++ b/internal/dao/ad_event_transitions.go @@ -0,0 +1,27 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "server/internal/dao/internal" +) + +// internalAdEventTransitionsDao is an internal type for wrapping the internal DAO implementation. +type internalAdEventTransitionsDao = *internal.AdEventTransitionsDao + +// adEventTransitionsDao is the data access object for the table ad_event_transitions. +// You can define custom methods on it to extend its functionality as needed. +type adEventTransitionsDao struct { + internalAdEventTransitionsDao +} + +var ( + // AdEventTransitions is a globally accessible object for table ad_event_transitions operations. + AdEventTransitions = adEventTransitionsDao{ + internal.NewAdEventTransitionsDao(), + } +) + +// Add your custom methods and functionality below. diff --git a/internal/dao/book_recommendations.go b/internal/dao/book_recommendations.go new file mode 100644 index 0000000..32a94fb --- /dev/null +++ b/internal/dao/book_recommendations.go @@ -0,0 +1,27 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "server/internal/dao/internal" +) + +// internalBookRecommendationsDao is an internal type for wrapping the internal DAO implementation. +type internalBookRecommendationsDao = *internal.BookRecommendationsDao + +// bookRecommendationsDao is the data access object for the table book_recommendations. +// You can define custom methods on it to extend its functionality as needed. +type bookRecommendationsDao struct { + internalBookRecommendationsDao +} + +var ( + // BookRecommendations is a globally accessible object for table book_recommendations operations. + BookRecommendations = bookRecommendationsDao{ + internal.NewBookRecommendationsDao(), + } +) + +// Add your custom methods and functionality below. diff --git a/internal/dao/internal/ad_event_logs.go b/internal/dao/internal/ad_event_logs.go new file mode 100644 index 0000000..ad90efb --- /dev/null +++ b/internal/dao/internal/ad_event_logs.go @@ -0,0 +1,97 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// AdEventLogsDao is the data access object for the table ad_event_logs. +type AdEventLogsDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns AdEventLogsColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// AdEventLogsColumns defines and stores column names for the table ad_event_logs. +type AdEventLogsColumns struct { + Id string // 广告事件ID + UserId string // 用户ID + AdsPlatId string // 平台ID:1-META,2-ADMOB + AdsCategoryId string // 广告类型:1-横幅,2-插页,3-激励插页,4-激励,5-原生,6-开屏 + AppPackage string // App包名 + Status string // 广告状态:1-拉取失败,2-拉取成功,3-显示失败,4-显示成功,5-未观看完成,6-观看完成,7-未点击,8-已点击,9-未下载,10-已下载 + StatusDesc string // 状态描述 + CreatedAt string // 创建时间 + UpdatedAt string // 更新时间 + DeletedAt string // 软删除时间戳 +} + +// adEventLogsColumns holds the columns for the table ad_event_logs. +var adEventLogsColumns = AdEventLogsColumns{ + Id: "id", + UserId: "user_id", + AdsPlatId: "ads_plat_id", + AdsCategoryId: "ads_category_id", + AppPackage: "app_package", + Status: "status", + StatusDesc: "status_desc", + CreatedAt: "created_at", + UpdatedAt: "updated_at", + DeletedAt: "deleted_at", +} + +// NewAdEventLogsDao creates and returns a new DAO object for table data access. +func NewAdEventLogsDao(handlers ...gdb.ModelHandler) *AdEventLogsDao { + return &AdEventLogsDao{ + group: "default", + table: "ad_event_logs", + columns: adEventLogsColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *AdEventLogsDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *AdEventLogsDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *AdEventLogsDao) Columns() AdEventLogsColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *AdEventLogsDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *AdEventLogsDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *AdEventLogsDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/internal/dao/internal/ad_event_transitions.go b/internal/dao/internal/ad_event_transitions.go new file mode 100644 index 0000000..df669e3 --- /dev/null +++ b/internal/dao/internal/ad_event_transitions.go @@ -0,0 +1,89 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// AdEventTransitionsDao is the data access object for the table ad_event_transitions. +type AdEventTransitionsDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns AdEventTransitionsColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// AdEventTransitionsColumns defines and stores column names for the table ad_event_transitions. +type AdEventTransitionsColumns struct { + Id string // 状态流转记录ID + EventId string // 所属广告事件ID,关联ad_event_logs.id + FromStatus string // 原状态(首次记录为空) + ToStatus string // 目标状态 + CreatedAt string // 状态变更时间 + DeletedAt string // 软删除时间戳 +} + +// adEventTransitionsColumns holds the columns for the table ad_event_transitions. +var adEventTransitionsColumns = AdEventTransitionsColumns{ + Id: "id", + EventId: "event_id", + FromStatus: "from_status", + ToStatus: "to_status", + CreatedAt: "created_at", + DeletedAt: "deleted_at", +} + +// NewAdEventTransitionsDao creates and returns a new DAO object for table data access. +func NewAdEventTransitionsDao(handlers ...gdb.ModelHandler) *AdEventTransitionsDao { + return &AdEventTransitionsDao{ + group: "default", + table: "ad_event_transitions", + columns: adEventTransitionsColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *AdEventTransitionsDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *AdEventTransitionsDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *AdEventTransitionsDao) Columns() AdEventTransitionsColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *AdEventTransitionsDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *AdEventTransitionsDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *AdEventTransitionsDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/internal/dao/internal/admins.go b/internal/dao/internal/admins.go index 36ac685..7e53449 100644 --- a/internal/dao/internal/admins.go +++ b/internal/dao/internal/admins.go @@ -13,9 +13,10 @@ import ( // AdminsDao is the data access object for the table admins. type AdminsDao struct { - table string // table is the underlying table name of the DAO. - group string // group is the database configuration group name of the current DAO. - columns AdminsColumns // columns contains all the column names of Table for convenient usage. + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns AdminsColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. } // AdminsColumns defines and stores column names for the table admins. @@ -39,11 +40,12 @@ var adminsColumns = AdminsColumns{ } // NewAdminsDao creates and returns a new DAO object for table data access. -func NewAdminsDao() *AdminsDao { +func NewAdminsDao(handlers ...gdb.ModelHandler) *AdminsDao { return &AdminsDao{ - group: "default", - table: "admins", - columns: adminsColumns, + group: "default", + table: "admins", + columns: adminsColumns, + handlers: handlers, } } @@ -69,7 +71,11 @@ func (dao *AdminsDao) Group() string { // Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. func (dao *AdminsDao) Ctx(ctx context.Context) *gdb.Model { - return dao.DB().Model(dao.table).Safe().Ctx(ctx) + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) } // Transaction wraps the transaction logic using function f. diff --git a/internal/dao/internal/authors.go b/internal/dao/internal/authors.go index b55e089..6af65ff 100644 --- a/internal/dao/internal/authors.go +++ b/internal/dao/internal/authors.go @@ -13,41 +13,45 @@ import ( // AuthorsDao is the data access object for the table authors. type AuthorsDao struct { - table string // table is the underlying table name of the DAO. - group string // group is the database configuration group name of the current DAO. - columns AuthorsColumns // columns contains all the column names of Table for convenient usage. + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns AuthorsColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. } // AuthorsColumns defines and stores column names for the table authors. type AuthorsColumns struct { - Id string // 作者ID - UserId string // 用户ID - PenName string // 笔名 - Bio string // 作者简介 - Status string // 状态:1=正常,2=禁用 - CreatedAt string // 创建时间 - UpdatedAt string // 更新时间 - DeletedAt string // 软删除时间戳 + Id string // 作者ID + UserId string // 用户ID + PenName string // 笔名 + Bio string // 作者简介 + FollowerCount string // 粉丝数量 + Status string // 状态:1=正常,2=待审核, 3=未通过 + CreatedAt string // 创建时间 + UpdatedAt string // 更新时间 + DeletedAt string // 软删除时间戳 } // authorsColumns holds the columns for the table authors. var authorsColumns = AuthorsColumns{ - Id: "id", - UserId: "user_id", - PenName: "pen_name", - Bio: "bio", - Status: "status", - CreatedAt: "created_at", - UpdatedAt: "updated_at", - DeletedAt: "deleted_at", + Id: "id", + UserId: "user_id", + PenName: "pen_name", + Bio: "bio", + FollowerCount: "follower_count", + Status: "status", + CreatedAt: "created_at", + UpdatedAt: "updated_at", + DeletedAt: "deleted_at", } // NewAuthorsDao creates and returns a new DAO object for table data access. -func NewAuthorsDao() *AuthorsDao { +func NewAuthorsDao(handlers ...gdb.ModelHandler) *AuthorsDao { return &AuthorsDao{ - group: "default", - table: "authors", - columns: authorsColumns, + group: "default", + table: "authors", + columns: authorsColumns, + handlers: handlers, } } @@ -73,7 +77,11 @@ func (dao *AuthorsDao) Group() string { // Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. func (dao *AuthorsDao) Ctx(ctx context.Context) *gdb.Model { - return dao.DB().Model(dao.table).Safe().Ctx(ctx) + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) } // Transaction wraps the transaction logic using function f. diff --git a/internal/dao/internal/book_ratings.go b/internal/dao/internal/book_ratings.go index 40ca7a0..05b9735 100644 --- a/internal/dao/internal/book_ratings.go +++ b/internal/dao/internal/book_ratings.go @@ -13,9 +13,10 @@ import ( // BookRatingsDao is the data access object for the table book_ratings. type BookRatingsDao struct { - table string // table is the underlying table name of the DAO. - group string // group is the database configuration group name of the current DAO. - columns BookRatingsColumns // columns contains all the column names of Table for convenient usage. + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns BookRatingsColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. } // BookRatingsColumns defines and stores column names for the table book_ratings. @@ -39,11 +40,12 @@ var bookRatingsColumns = BookRatingsColumns{ } // NewBookRatingsDao creates and returns a new DAO object for table data access. -func NewBookRatingsDao() *BookRatingsDao { +func NewBookRatingsDao(handlers ...gdb.ModelHandler) *BookRatingsDao { return &BookRatingsDao{ - group: "default", - table: "book_ratings", - columns: bookRatingsColumns, + group: "default", + table: "book_ratings", + columns: bookRatingsColumns, + handlers: handlers, } } @@ -69,7 +71,11 @@ func (dao *BookRatingsDao) Group() string { // Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. func (dao *BookRatingsDao) Ctx(ctx context.Context) *gdb.Model { - return dao.DB().Model(dao.table).Safe().Ctx(ctx) + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) } // Transaction wraps the transaction logic using function f. diff --git a/internal/dao/internal/book_recommendations.go b/internal/dao/internal/book_recommendations.go new file mode 100644 index 0000000..9d98a74 --- /dev/null +++ b/internal/dao/internal/book_recommendations.go @@ -0,0 +1,95 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// BookRecommendationsDao is the data access object for the table book_recommendations. +type BookRecommendationsDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns BookRecommendationsColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// BookRecommendationsColumns defines and stores column names for the table book_recommendations. +type BookRecommendationsColumns struct { + Id string // 主键 + BookId string // 书籍ID,关联 books 表 + Type string // 推荐类型:1=首页Banner,2=编辑推荐,3=分类推荐等 + CoverUrl string // 推荐封面图(横图) + SortOrder string // 展示排序,越小越靠前 + Status string // 是否启用:1=启用,0=禁用 + CreatedAt string // 创建时间 + UpdatedAt string // 更新时间 + DeletedAt string // 软删除时间戳 +} + +// bookRecommendationsColumns holds the columns for the table book_recommendations. +var bookRecommendationsColumns = BookRecommendationsColumns{ + Id: "id", + BookId: "book_id", + Type: "type", + CoverUrl: "cover_url", + SortOrder: "sort_order", + Status: "status", + CreatedAt: "created_at", + UpdatedAt: "updated_at", + DeletedAt: "deleted_at", +} + +// NewBookRecommendationsDao creates and returns a new DAO object for table data access. +func NewBookRecommendationsDao(handlers ...gdb.ModelHandler) *BookRecommendationsDao { + return &BookRecommendationsDao{ + group: "default", + table: "book_recommendations", + columns: bookRecommendationsColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *BookRecommendationsDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *BookRecommendationsDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *BookRecommendationsDao) Columns() BookRecommendationsColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *BookRecommendationsDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *BookRecommendationsDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *BookRecommendationsDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/internal/dao/internal/books.go b/internal/dao/internal/books.go index 1b5fca3..07e9aac 100644 --- a/internal/dao/internal/books.go +++ b/internal/dao/internal/books.go @@ -13,9 +13,10 @@ import ( // BooksDao is the data access object for the table books. type BooksDao struct { - table string // table is the underlying table name of the DAO. - group string // group is the database configuration group name of the current DAO. - columns BooksColumns // columns contains all the column names of Table for convenient usage. + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns BooksColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. } // BooksColumns defines and stores column names for the table books. @@ -38,6 +39,7 @@ type BooksColumns struct { DeletedAt string // 软删除时间戳 IsRecommended string // 是否推荐:0=否,1=是 IsFeatured string // 是否精选:0=否,1=是 + IsHot string // 是否热门:0=否,1=是 Language string // 语言,如 zh=中文,en=英文,jp=日文 } @@ -61,15 +63,17 @@ var booksColumns = BooksColumns{ DeletedAt: "deleted_at", IsRecommended: "is_recommended", IsFeatured: "is_featured", + IsHot: "is_hot", Language: "language", } // NewBooksDao creates and returns a new DAO object for table data access. -func NewBooksDao() *BooksDao { +func NewBooksDao(handlers ...gdb.ModelHandler) *BooksDao { return &BooksDao{ - group: "default", - table: "books", - columns: booksColumns, + group: "default", + table: "books", + columns: booksColumns, + handlers: handlers, } } @@ -95,7 +99,11 @@ func (dao *BooksDao) Group() string { // Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. func (dao *BooksDao) Ctx(ctx context.Context) *gdb.Model { - return dao.DB().Model(dao.table).Safe().Ctx(ctx) + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) } // Transaction wraps the transaction logic using function f. diff --git a/internal/dao/internal/bookshelves.go b/internal/dao/internal/bookshelves.go index 6b2b23b..cf4dec8 100644 --- a/internal/dao/internal/bookshelves.go +++ b/internal/dao/internal/bookshelves.go @@ -13,9 +13,10 @@ import ( // BookshelvesDao is the data access object for the table bookshelves. type BookshelvesDao struct { - table string // table is the underlying table name of the DAO. - group string // group is the database configuration group name of the current DAO. - columns BookshelvesColumns // columns contains all the column names of Table for convenient usage. + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns BookshelvesColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. } // BookshelvesColumns defines and stores column names for the table bookshelves. @@ -43,11 +44,12 @@ var bookshelvesColumns = BookshelvesColumns{ } // NewBookshelvesDao creates and returns a new DAO object for table data access. -func NewBookshelvesDao() *BookshelvesDao { +func NewBookshelvesDao(handlers ...gdb.ModelHandler) *BookshelvesDao { return &BookshelvesDao{ - group: "default", - table: "bookshelves", - columns: bookshelvesColumns, + group: "default", + table: "bookshelves", + columns: bookshelvesColumns, + handlers: handlers, } } @@ -73,7 +75,11 @@ func (dao *BookshelvesDao) Group() string { // Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. func (dao *BookshelvesDao) Ctx(ctx context.Context) *gdb.Model { - return dao.DB().Model(dao.table).Safe().Ctx(ctx) + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) } // Transaction wraps the transaction logic using function f. diff --git a/internal/dao/internal/categories.go b/internal/dao/internal/categories.go index 70acac7..e3d397a 100644 --- a/internal/dao/internal/categories.go +++ b/internal/dao/internal/categories.go @@ -13,9 +13,10 @@ import ( // CategoriesDao is the data access object for the table categories. type CategoriesDao struct { - table string // table is the underlying table name of the DAO. - group string // group is the database configuration group name of the current DAO. - columns CategoriesColumns // columns contains all the column names of Table for convenient usage. + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns CategoriesColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. } // CategoriesColumns defines and stores column names for the table categories. @@ -39,11 +40,12 @@ var categoriesColumns = CategoriesColumns{ } // NewCategoriesDao creates and returns a new DAO object for table data access. -func NewCategoriesDao() *CategoriesDao { +func NewCategoriesDao(handlers ...gdb.ModelHandler) *CategoriesDao { return &CategoriesDao{ - group: "default", - table: "categories", - columns: categoriesColumns, + group: "default", + table: "categories", + columns: categoriesColumns, + handlers: handlers, } } @@ -69,7 +71,11 @@ func (dao *CategoriesDao) Group() string { // Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. func (dao *CategoriesDao) Ctx(ctx context.Context) *gdb.Model { - return dao.DB().Model(dao.table).Safe().Ctx(ctx) + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) } // Transaction wraps the transaction logic using function f. diff --git a/internal/dao/internal/chapters.go b/internal/dao/internal/chapters.go index 32541cb..ba23a09 100644 --- a/internal/dao/internal/chapters.go +++ b/internal/dao/internal/chapters.go @@ -13,9 +13,10 @@ import ( // ChaptersDao is the data access object for the table chapters. type ChaptersDao struct { - table string // table is the underlying table name of the DAO. - group string // group is the database configuration group name of the current DAO. - columns ChaptersColumns // columns contains all the column names of Table for convenient usage. + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns ChaptersColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. } // ChaptersColumns defines and stores column names for the table chapters. @@ -49,11 +50,12 @@ var chaptersColumns = ChaptersColumns{ } // NewChaptersDao creates and returns a new DAO object for table data access. -func NewChaptersDao() *ChaptersDao { +func NewChaptersDao(handlers ...gdb.ModelHandler) *ChaptersDao { return &ChaptersDao{ - group: "default", - table: "chapters", - columns: chaptersColumns, + group: "default", + table: "chapters", + columns: chaptersColumns, + handlers: handlers, } } @@ -79,7 +81,11 @@ func (dao *ChaptersDao) Group() string { // Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. func (dao *ChaptersDao) Ctx(ctx context.Context) *gdb.Model { - return dao.DB().Model(dao.table).Safe().Ctx(ctx) + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) } // Transaction wraps the transaction logic using function f. diff --git a/internal/dao/internal/feedbacks.go b/internal/dao/internal/feedbacks.go index e0390be..d557bb2 100644 --- a/internal/dao/internal/feedbacks.go +++ b/internal/dao/internal/feedbacks.go @@ -13,9 +13,10 @@ import ( // FeedbacksDao is the data access object for the table feedbacks. type FeedbacksDao struct { - table string // table is the underlying table name of the DAO. - group string // group is the database configuration group name of the current DAO. - columns FeedbacksColumns // columns contains all the column names of Table for convenient usage. + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns FeedbacksColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. } // FeedbacksColumns defines and stores column names for the table feedbacks. @@ -39,11 +40,12 @@ var feedbacksColumns = FeedbacksColumns{ } // NewFeedbacksDao creates and returns a new DAO object for table data access. -func NewFeedbacksDao() *FeedbacksDao { +func NewFeedbacksDao(handlers ...gdb.ModelHandler) *FeedbacksDao { return &FeedbacksDao{ - group: "default", - table: "feedbacks", - columns: feedbacksColumns, + group: "default", + table: "feedbacks", + columns: feedbacksColumns, + handlers: handlers, } } @@ -69,7 +71,11 @@ func (dao *FeedbacksDao) Group() string { // Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. func (dao *FeedbacksDao) Ctx(ctx context.Context) *gdb.Model { - return dao.DB().Model(dao.table).Safe().Ctx(ctx) + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) } // Transaction wraps the transaction logic using function f. diff --git a/internal/dao/internal/sign_in_reward_details.go b/internal/dao/internal/sign_in_reward_details.go new file mode 100644 index 0000000..9171705 --- /dev/null +++ b/internal/dao/internal/sign_in_reward_details.go @@ -0,0 +1,95 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// SignInRewardDetailsDao is the data access object for the table sign_in_reward_details. +type SignInRewardDetailsDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns SignInRewardDetailsColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// SignInRewardDetailsColumns defines and stores column names for the table sign_in_reward_details. +type SignInRewardDetailsColumns struct { + Id string // 主键 + RuleId string // 规则ID,关联 sign_in_reward_rules 表 + DayNumber string // 签到天数(1到cycle_days) + RewardType string // 奖励类型:1=积分 + Quantity string // 奖励数量,如积分数量或礼包数量 + Status string // 记录状态:1=启用,0=禁用 + CreatedAt string // 创建时间 + UpdatedAt string // 更新时间 + DeletedAt string // 软删除时间戳 +} + +// signInRewardDetailsColumns holds the columns for the table sign_in_reward_details. +var signInRewardDetailsColumns = SignInRewardDetailsColumns{ + Id: "id", + RuleId: "rule_id", + DayNumber: "day_number", + RewardType: "reward_type", + Quantity: "quantity", + Status: "status", + CreatedAt: "created_at", + UpdatedAt: "updated_at", + DeletedAt: "deleted_at", +} + +// NewSignInRewardDetailsDao creates and returns a new DAO object for table data access. +func NewSignInRewardDetailsDao(handlers ...gdb.ModelHandler) *SignInRewardDetailsDao { + return &SignInRewardDetailsDao{ + group: "default", + table: "sign_in_reward_details", + columns: signInRewardDetailsColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *SignInRewardDetailsDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *SignInRewardDetailsDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *SignInRewardDetailsDao) Columns() SignInRewardDetailsColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *SignInRewardDetailsDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *SignInRewardDetailsDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *SignInRewardDetailsDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/internal/dao/internal/sign_in_reward_rules.go b/internal/dao/internal/sign_in_reward_rules.go new file mode 100644 index 0000000..e45cebb --- /dev/null +++ b/internal/dao/internal/sign_in_reward_rules.go @@ -0,0 +1,95 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// SignInRewardRulesDao is the data access object for the table sign_in_reward_rules. +type SignInRewardRulesDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns SignInRewardRulesColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// SignInRewardRulesColumns defines and stores column names for the table sign_in_reward_rules. +type SignInRewardRulesColumns struct { + Id string // 主键 + RuleName string // 规则名称,如“7天签到活动” + CycleDays string // 奖励周期天数,如7天 + StartDate string // 活动开始日期 + EndDate string // 活动结束日期 + Status string // 规则状态:1=启用,0=禁用 + CreatedAt string // 创建时间 + UpdatedAt string // 更新时间 + DeletedAt string // 软删除时间戳 +} + +// signInRewardRulesColumns holds the columns for the table sign_in_reward_rules. +var signInRewardRulesColumns = SignInRewardRulesColumns{ + Id: "id", + RuleName: "rule_name", + CycleDays: "cycle_days", + StartDate: "start_date", + EndDate: "end_date", + Status: "status", + CreatedAt: "created_at", + UpdatedAt: "updated_at", + DeletedAt: "deleted_at", +} + +// NewSignInRewardRulesDao creates and returns a new DAO object for table data access. +func NewSignInRewardRulesDao(handlers ...gdb.ModelHandler) *SignInRewardRulesDao { + return &SignInRewardRulesDao{ + group: "default", + table: "sign_in_reward_rules", + columns: signInRewardRulesColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *SignInRewardRulesDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *SignInRewardRulesDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *SignInRewardRulesDao) Columns() SignInRewardRulesColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *SignInRewardRulesDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *SignInRewardRulesDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *SignInRewardRulesDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/internal/dao/internal/system.go b/internal/dao/internal/system.go new file mode 100644 index 0000000..baab4e3 --- /dev/null +++ b/internal/dao/internal/system.go @@ -0,0 +1,81 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// SystemDao is the data access object for the table system. +type SystemDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns SystemColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// SystemColumns defines and stores column names for the table system. +type SystemColumns struct { + Key string // + Value string // +} + +// systemColumns holds the columns for the table system. +var systemColumns = SystemColumns{ + Key: "key", + Value: "value", +} + +// NewSystemDao creates and returns a new DAO object for table data access. +func NewSystemDao(handlers ...gdb.ModelHandler) *SystemDao { + return &SystemDao{ + group: "default", + table: "system", + columns: systemColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *SystemDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *SystemDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *SystemDao) Columns() SystemColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *SystemDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *SystemDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *SystemDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/internal/dao/internal/task_logs.go b/internal/dao/internal/task_logs.go new file mode 100644 index 0000000..76326ab --- /dev/null +++ b/internal/dao/internal/task_logs.go @@ -0,0 +1,93 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// TaskLogsDao is the data access object for the table task_logs. +type TaskLogsDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns TaskLogsColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// TaskLogsColumns defines and stores column names for the table task_logs. +type TaskLogsColumns struct { + Id string // 任务日志ID + TaskId string // 任务ID,关联 tasks.id + UserId string // 用户ID,关联 users.id + RewardPoints string // 本次任务获得的积分 + ActionTime string // 操作时间(如完成时间) + Status string // 日志状态:1=有效,2=无效 + Extra string // 扩展信息,例如来源、IP 等 + CreatedAt string // 创建时间 +} + +// taskLogsColumns holds the columns for the table task_logs. +var taskLogsColumns = TaskLogsColumns{ + Id: "id", + TaskId: "task_id", + UserId: "user_id", + RewardPoints: "reward_points", + ActionTime: "action_time", + Status: "status", + Extra: "extra", + CreatedAt: "created_at", +} + +// NewTaskLogsDao creates and returns a new DAO object for table data access. +func NewTaskLogsDao(handlers ...gdb.ModelHandler) *TaskLogsDao { + return &TaskLogsDao{ + group: "default", + table: "task_logs", + columns: taskLogsColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *TaskLogsDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *TaskLogsDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *TaskLogsDao) Columns() TaskLogsColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *TaskLogsDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *TaskLogsDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *TaskLogsDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/internal/dao/internal/task_types.go b/internal/dao/internal/task_types.go new file mode 100644 index 0000000..c98f7eb --- /dev/null +++ b/internal/dao/internal/task_types.go @@ -0,0 +1,83 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// TaskTypesDao is the data access object for the table task_types. +type TaskTypesDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns TaskTypesColumns // columns contains all the column names of Table for convenient usage. +} + +// TaskTypesColumns defines and stores column names for the table task_types. +type TaskTypesColumns struct { + Id string // 任务类型ID + Name string // 任务类型名称,例如:广告、写一本书、首次登录 + Description string // 任务类型描述 + Status string // 状态:1=启用,2=禁用 + CreatedAt string // 创建时间 + UpdatedAt string // 更新时间 +} + +// taskTypesColumns holds the columns for the table task_types. +var taskTypesColumns = TaskTypesColumns{ + Id: "id", + Name: "name", + Description: "description", + Status: "status", + CreatedAt: "created_at", + UpdatedAt: "updated_at", +} + +// NewTaskTypesDao creates and returns a new DAO object for table data access. +func NewTaskTypesDao() *TaskTypesDao { + return &TaskTypesDao{ + group: "default", + table: "task_types", + columns: taskTypesColumns, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *TaskTypesDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *TaskTypesDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *TaskTypesDao) Columns() TaskTypesColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *TaskTypesDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *TaskTypesDao) Ctx(ctx context.Context) *gdb.Model { + return dao.DB().Model(dao.table).Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *TaskTypesDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/internal/dao/internal/tasks.go b/internal/dao/internal/tasks.go new file mode 100644 index 0000000..dd27a23 --- /dev/null +++ b/internal/dao/internal/tasks.go @@ -0,0 +1,95 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// TasksDao is the data access object for the table tasks. +type TasksDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns TasksColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// TasksColumns defines and stores column names for the table tasks. +type TasksColumns struct { + Id string // 任务ID + TaskType string // 任务类型:1=首次登录,2=广告,3=发布书籍 + Title string // 任务标题 + Description string // 任务描述 + RewardPoints string // 完成任务奖励的积分数 + Status string // 状态:1=启用,2=禁用 + CreatedAt string // 创建时间 + UpdatedAt string // 更新时间 + DeletedAt string // 软删除时间戳 +} + +// tasksColumns holds the columns for the table tasks. +var tasksColumns = TasksColumns{ + Id: "id", + TaskType: "task_type", + Title: "title", + Description: "description", + RewardPoints: "reward_points", + Status: "status", + CreatedAt: "created_at", + UpdatedAt: "updated_at", + DeletedAt: "deleted_at", +} + +// NewTasksDao creates and returns a new DAO object for table data access. +func NewTasksDao(handlers ...gdb.ModelHandler) *TasksDao { + return &TasksDao{ + group: "default", + table: "tasks", + columns: tasksColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *TasksDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *TasksDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *TasksDao) Columns() TasksColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *TasksDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *TasksDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *TasksDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/internal/dao/internal/user_chapter_purchases.go b/internal/dao/internal/user_chapter_purchases.go index 9ee4f34..ca8a267 100644 --- a/internal/dao/internal/user_chapter_purchases.go +++ b/internal/dao/internal/user_chapter_purchases.go @@ -13,9 +13,10 @@ import ( // UserChapterPurchasesDao is the data access object for the table user_chapter_purchases. type UserChapterPurchasesDao struct { - table string // table is the underlying table name of the DAO. - group string // group is the database configuration group name of the current DAO. - columns UserChapterPurchasesColumns // columns contains all the column names of Table for convenient usage. + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns UserChapterPurchasesColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. } // UserChapterPurchasesColumns defines and stores column names for the table user_chapter_purchases. @@ -39,11 +40,12 @@ var userChapterPurchasesColumns = UserChapterPurchasesColumns{ } // NewUserChapterPurchasesDao creates and returns a new DAO object for table data access. -func NewUserChapterPurchasesDao() *UserChapterPurchasesDao { +func NewUserChapterPurchasesDao(handlers ...gdb.ModelHandler) *UserChapterPurchasesDao { return &UserChapterPurchasesDao{ - group: "default", - table: "user_chapter_purchases", - columns: userChapterPurchasesColumns, + group: "default", + table: "user_chapter_purchases", + columns: userChapterPurchasesColumns, + handlers: handlers, } } @@ -69,7 +71,11 @@ func (dao *UserChapterPurchasesDao) Group() string { // Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. func (dao *UserChapterPurchasesDao) Ctx(ctx context.Context) *gdb.Model { - return dao.DB().Model(dao.table).Safe().Ctx(ctx) + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) } // Transaction wraps the transaction logic using function f. diff --git a/internal/dao/internal/user_follow_authors.go b/internal/dao/internal/user_follow_authors.go index 477804e..5b3ac8b 100644 --- a/internal/dao/internal/user_follow_authors.go +++ b/internal/dao/internal/user_follow_authors.go @@ -13,9 +13,10 @@ import ( // UserFollowAuthorsDao is the data access object for the table user_follow_authors. type UserFollowAuthorsDao struct { - table string // table is the underlying table name of the DAO. - group string // group is the database configuration group name of the current DAO. - columns UserFollowAuthorsColumns // columns contains all the column names of Table for convenient usage. + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns UserFollowAuthorsColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. } // UserFollowAuthorsColumns defines and stores column names for the table user_follow_authors. @@ -35,11 +36,12 @@ var userFollowAuthorsColumns = UserFollowAuthorsColumns{ } // NewUserFollowAuthorsDao creates and returns a new DAO object for table data access. -func NewUserFollowAuthorsDao() *UserFollowAuthorsDao { +func NewUserFollowAuthorsDao(handlers ...gdb.ModelHandler) *UserFollowAuthorsDao { return &UserFollowAuthorsDao{ - group: "default", - table: "user_follow_authors", - columns: userFollowAuthorsColumns, + group: "default", + table: "user_follow_authors", + columns: userFollowAuthorsColumns, + handlers: handlers, } } @@ -65,7 +67,11 @@ func (dao *UserFollowAuthorsDao) Group() string { // Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. func (dao *UserFollowAuthorsDao) Ctx(ctx context.Context) *gdb.Model { - return dao.DB().Model(dao.table).Safe().Ctx(ctx) + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) } // Transaction wraps the transaction logic using function f. diff --git a/internal/dao/internal/user_points_logs.go b/internal/dao/internal/user_points_logs.go index fa6b50e..82a84ab 100644 --- a/internal/dao/internal/user_points_logs.go +++ b/internal/dao/internal/user_points_logs.go @@ -13,9 +13,10 @@ import ( // UserPointsLogsDao is the data access object for the table user_points_logs. type UserPointsLogsDao struct { - table string // table is the underlying table name of the DAO. - group string // group is the database configuration group name of the current DAO. - columns UserPointsLogsColumns // columns contains all the column names of Table for convenient usage. + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns UserPointsLogsColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. } // UserPointsLogsColumns defines and stores column names for the table user_points_logs. @@ -41,11 +42,12 @@ var userPointsLogsColumns = UserPointsLogsColumns{ } // NewUserPointsLogsDao creates and returns a new DAO object for table data access. -func NewUserPointsLogsDao() *UserPointsLogsDao { +func NewUserPointsLogsDao(handlers ...gdb.ModelHandler) *UserPointsLogsDao { return &UserPointsLogsDao{ - group: "default", - table: "user_points_logs", - columns: userPointsLogsColumns, + group: "default", + table: "user_points_logs", + columns: userPointsLogsColumns, + handlers: handlers, } } @@ -71,7 +73,11 @@ func (dao *UserPointsLogsDao) Group() string { // Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. func (dao *UserPointsLogsDao) Ctx(ctx context.Context) *gdb.Model { - return dao.DB().Model(dao.table).Safe().Ctx(ctx) + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) } // Transaction wraps the transaction logic using function f. diff --git a/internal/dao/internal/user_read_history.go b/internal/dao/internal/user_read_history.go index c4fc68d..6df1c2f 100644 --- a/internal/dao/internal/user_read_history.go +++ b/internal/dao/internal/user_read_history.go @@ -13,9 +13,10 @@ import ( // UserReadHistoryDao is the data access object for the table user_read_history. type UserReadHistoryDao struct { - table string // table is the underlying table name of the DAO. - group string // group is the database configuration group name of the current DAO. - columns UserReadHistoryColumns // columns contains all the column names of Table for convenient usage. + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns UserReadHistoryColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. } // UserReadHistoryColumns defines and stores column names for the table user_read_history. @@ -37,11 +38,12 @@ var userReadHistoryColumns = UserReadHistoryColumns{ } // NewUserReadHistoryDao creates and returns a new DAO object for table data access. -func NewUserReadHistoryDao() *UserReadHistoryDao { +func NewUserReadHistoryDao(handlers ...gdb.ModelHandler) *UserReadHistoryDao { return &UserReadHistoryDao{ - group: "default", - table: "user_read_history", - columns: userReadHistoryColumns, + group: "default", + table: "user_read_history", + columns: userReadHistoryColumns, + handlers: handlers, } } @@ -67,7 +69,11 @@ func (dao *UserReadHistoryDao) Group() string { // Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. func (dao *UserReadHistoryDao) Ctx(ctx context.Context) *gdb.Model { - return dao.DB().Model(dao.table).Safe().Ctx(ctx) + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) } // Transaction wraps the transaction logic using function f. diff --git a/internal/dao/internal/user_read_records.go b/internal/dao/internal/user_read_records.go index 80d9532..d02c611 100644 --- a/internal/dao/internal/user_read_records.go +++ b/internal/dao/internal/user_read_records.go @@ -13,9 +13,10 @@ import ( // UserReadRecordsDao is the data access object for the table user_read_records. type UserReadRecordsDao struct { - table string // table is the underlying table name of the DAO. - group string // group is the database configuration group name of the current DAO. - columns UserReadRecordsColumns // columns contains all the column names of Table for convenient usage. + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns UserReadRecordsColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. } // UserReadRecordsColumns defines and stores column names for the table user_read_records. @@ -43,11 +44,12 @@ var userReadRecordsColumns = UserReadRecordsColumns{ } // NewUserReadRecordsDao creates and returns a new DAO object for table data access. -func NewUserReadRecordsDao() *UserReadRecordsDao { +func NewUserReadRecordsDao(handlers ...gdb.ModelHandler) *UserReadRecordsDao { return &UserReadRecordsDao{ - group: "default", - table: "user_read_records", - columns: userReadRecordsColumns, + group: "default", + table: "user_read_records", + columns: userReadRecordsColumns, + handlers: handlers, } } @@ -73,7 +75,11 @@ func (dao *UserReadRecordsDao) Group() string { // Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. func (dao *UserReadRecordsDao) Ctx(ctx context.Context) *gdb.Model { - return dao.DB().Model(dao.table).Safe().Ctx(ctx) + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) } // Transaction wraps the transaction logic using function f. diff --git a/internal/dao/internal/user_sign_in_logs.go b/internal/dao/internal/user_sign_in_logs.go new file mode 100644 index 0000000..bb5edab --- /dev/null +++ b/internal/dao/internal/user_sign_in_logs.go @@ -0,0 +1,97 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// UserSignInLogsDao is the data access object for the table user_sign_in_logs. +type UserSignInLogsDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns UserSignInLogsColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// UserSignInLogsColumns defines and stores column names for the table user_sign_in_logs. +type UserSignInLogsColumns struct { + Id string // 主键 + UserId string // 用户ID,关联 users 表 + RuleId string // 规则ID,关联 sign_in_reward_rules 表 + RewardDetailId string // 奖励详情ID,关联 sign_in_reward_details 表 + SignInDate string // 签到日期 + Quantity string // 奖励数量,如积分数量或礼包数量 + Status string // 记录状态:1=有效,0=无效 + CreatedAt string // 创建时间 + UpdatedAt string // 更新时间 + DeletedAt string // 软删除时间戳 +} + +// userSignInLogsColumns holds the columns for the table user_sign_in_logs. +var userSignInLogsColumns = UserSignInLogsColumns{ + Id: "id", + UserId: "user_id", + RuleId: "rule_id", + RewardDetailId: "reward_detail_id", + SignInDate: "sign_in_date", + Quantity: "quantity", + Status: "status", + CreatedAt: "created_at", + UpdatedAt: "updated_at", + DeletedAt: "deleted_at", +} + +// NewUserSignInLogsDao creates and returns a new DAO object for table data access. +func NewUserSignInLogsDao(handlers ...gdb.ModelHandler) *UserSignInLogsDao { + return &UserSignInLogsDao{ + group: "default", + table: "user_sign_in_logs", + columns: userSignInLogsColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *UserSignInLogsDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *UserSignInLogsDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *UserSignInLogsDao) Columns() UserSignInLogsColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *UserSignInLogsDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *UserSignInLogsDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *UserSignInLogsDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/internal/dao/internal/users.go b/internal/dao/internal/users.go index 41ab041..a1d9126 100644 --- a/internal/dao/internal/users.go +++ b/internal/dao/internal/users.go @@ -13,43 +13,49 @@ import ( // UsersDao is the data access object for the table users. type UsersDao struct { - table string // table is the underlying table name of the DAO. - group string // group is the database configuration group name of the current DAO. - columns UsersColumns // columns contains all the column names of Table for convenient usage. + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns UsersColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. } // UsersColumns defines and stores column names for the table users. type UsersColumns struct { - Id string // 用户ID - Username string // 用户名 - PasswordHash string // 密码哈希 - Avatar string // 头像URL - Email string // 邮箱 - Points string // 积分 - CreatedAt string // 注册时间 - UpdatedAt string // 更新时间 - DeletedAt string // 软删除时间戳 + Id string // 用户ID + Username string // 用户名 + PasswordHash string // 密码哈希 + Avatar string // 头像URL + Email string // 邮箱 + Points string // 积分 + CreatedAt string // 注册时间 + UpdatedAt string // 更新时间 + DeletedAt string // 软删除时间戳 + BackgroundUrl string // 作者背景图 + AttentionCount string // 关注他人的数量 attention count } // usersColumns holds the columns for the table users. var usersColumns = UsersColumns{ - Id: "id", - Username: "username", - PasswordHash: "password_hash", - Avatar: "avatar", - Email: "email", - Points: "points", - CreatedAt: "created_at", - UpdatedAt: "updated_at", - DeletedAt: "deleted_at", + Id: "id", + Username: "username", + PasswordHash: "password_hash", + Avatar: "avatar", + Email: "email", + Points: "points", + CreatedAt: "created_at", + UpdatedAt: "updated_at", + DeletedAt: "deleted_at", + BackgroundUrl: "background_url", + AttentionCount: "attention_count", } // NewUsersDao creates and returns a new DAO object for table data access. -func NewUsersDao() *UsersDao { +func NewUsersDao(handlers ...gdb.ModelHandler) *UsersDao { return &UsersDao{ - group: "default", - table: "users", - columns: usersColumns, + group: "default", + table: "users", + columns: usersColumns, + handlers: handlers, } } @@ -75,7 +81,11 @@ func (dao *UsersDao) Group() string { // Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. func (dao *UsersDao) Ctx(ctx context.Context) *gdb.Model { - return dao.DB().Model(dao.table).Safe().Ctx(ctx) + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) } // Transaction wraps the transaction logic using function f. diff --git a/internal/dao/sign_in_reward_details.go b/internal/dao/sign_in_reward_details.go new file mode 100644 index 0000000..ab49fd1 --- /dev/null +++ b/internal/dao/sign_in_reward_details.go @@ -0,0 +1,27 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "server/internal/dao/internal" +) + +// internalSignInRewardDetailsDao is an internal type for wrapping the internal DAO implementation. +type internalSignInRewardDetailsDao = *internal.SignInRewardDetailsDao + +// signInRewardDetailsDao is the data access object for the table sign_in_reward_details. +// You can define custom methods on it to extend its functionality as needed. +type signInRewardDetailsDao struct { + internalSignInRewardDetailsDao +} + +var ( + // SignInRewardDetails is a globally accessible object for table sign_in_reward_details operations. + SignInRewardDetails = signInRewardDetailsDao{ + internal.NewSignInRewardDetailsDao(), + } +) + +// Add your custom methods and functionality below. diff --git a/internal/dao/sign_in_reward_rules.go b/internal/dao/sign_in_reward_rules.go new file mode 100644 index 0000000..a9b63cf --- /dev/null +++ b/internal/dao/sign_in_reward_rules.go @@ -0,0 +1,27 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "server/internal/dao/internal" +) + +// internalSignInRewardRulesDao is an internal type for wrapping the internal DAO implementation. +type internalSignInRewardRulesDao = *internal.SignInRewardRulesDao + +// signInRewardRulesDao is the data access object for the table sign_in_reward_rules. +// You can define custom methods on it to extend its functionality as needed. +type signInRewardRulesDao struct { + internalSignInRewardRulesDao +} + +var ( + // SignInRewardRules is a globally accessible object for table sign_in_reward_rules operations. + SignInRewardRules = signInRewardRulesDao{ + internal.NewSignInRewardRulesDao(), + } +) + +// Add your custom methods and functionality below. diff --git a/internal/dao/system.go b/internal/dao/system.go new file mode 100644 index 0000000..7c6305b --- /dev/null +++ b/internal/dao/system.go @@ -0,0 +1,27 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "server/internal/dao/internal" +) + +// internalSystemDao is an internal type for wrapping the internal DAO implementation. +type internalSystemDao = *internal.SystemDao + +// systemDao is the data access object for the table system. +// You can define custom methods on it to extend its functionality as needed. +type systemDao struct { + internalSystemDao +} + +var ( + // System is a globally accessible object for table system operations. + System = systemDao{ + internal.NewSystemDao(), + } +) + +// Add your custom methods and functionality below. diff --git a/internal/dao/task_logs.go b/internal/dao/task_logs.go new file mode 100644 index 0000000..3a873e8 --- /dev/null +++ b/internal/dao/task_logs.go @@ -0,0 +1,27 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "server/internal/dao/internal" +) + +// internalTaskLogsDao is an internal type for wrapping the internal DAO implementation. +type internalTaskLogsDao = *internal.TaskLogsDao + +// taskLogsDao is the data access object for the table task_logs. +// You can define custom methods on it to extend its functionality as needed. +type taskLogsDao struct { + internalTaskLogsDao +} + +var ( + // TaskLogs is a globally accessible object for table task_logs operations. + TaskLogs = taskLogsDao{ + internal.NewTaskLogsDao(), + } +) + +// Add your custom methods and functionality below. diff --git a/internal/dao/task_types.go b/internal/dao/task_types.go new file mode 100644 index 0000000..4aaa6d8 --- /dev/null +++ b/internal/dao/task_types.go @@ -0,0 +1,27 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "server/internal/dao/internal" +) + +// internalTaskTypesDao is an internal type for wrapping the internal DAO implementation. +type internalTaskTypesDao = *internal.TaskTypesDao + +// taskTypesDao is the data access object for the table task_types. +// You can define custom methods on it to extend its functionality as needed. +type taskTypesDao struct { + internalTaskTypesDao +} + +var ( + // TaskTypes is a globally accessible object for table task_types operations. + TaskTypes = taskTypesDao{ + internal.NewTaskTypesDao(), + } +) + +// Add your custom methods and functionality below. diff --git a/internal/dao/tasks.go b/internal/dao/tasks.go new file mode 100644 index 0000000..e3e2e27 --- /dev/null +++ b/internal/dao/tasks.go @@ -0,0 +1,27 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "server/internal/dao/internal" +) + +// internalTasksDao is an internal type for wrapping the internal DAO implementation. +type internalTasksDao = *internal.TasksDao + +// tasksDao is the data access object for the table tasks. +// You can define custom methods on it to extend its functionality as needed. +type tasksDao struct { + internalTasksDao +} + +var ( + // Tasks is a globally accessible object for table tasks operations. + Tasks = tasksDao{ + internal.NewTasksDao(), + } +) + +// Add your custom methods and functionality below. diff --git a/internal/dao/user_sign_in_logs.go b/internal/dao/user_sign_in_logs.go new file mode 100644 index 0000000..1169f2e --- /dev/null +++ b/internal/dao/user_sign_in_logs.go @@ -0,0 +1,27 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "server/internal/dao/internal" +) + +// internalUserSignInLogsDao is an internal type for wrapping the internal DAO implementation. +type internalUserSignInLogsDao = *internal.UserSignInLogsDao + +// userSignInLogsDao is the data access object for the table user_sign_in_logs. +// You can define custom methods on it to extend its functionality as needed. +type userSignInLogsDao struct { + internalUserSignInLogsDao +} + +var ( + // UserSignInLogs is a globally accessible object for table user_sign_in_logs operations. + UserSignInLogs = userSignInLogsDao{ + internal.NewUserSignInLogsDao(), + } +) + +// Add your custom methods and functionality below. diff --git a/internal/logic/ad_event_logs/ad_event_logs.go b/internal/logic/ad_event_logs/ad_event_logs.go new file mode 100644 index 0000000..3ef4efb --- /dev/null +++ b/internal/logic/ad_event_logs/ad_event_logs.go @@ -0,0 +1,273 @@ +package ads + +import ( + "context" + "fmt" + "server/internal/consts" + "server/internal/dao" + "server/internal/model" + "server/internal/model/do" + "server/internal/service" + "server/utility/ecode" + "server/utility/encrypt" + "server/utility/mqtt" + "server/utility/state_machine" + "time" + + "github.com/gogf/gf/v2/os/glog" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +type sAds struct { + stateMachine *state_machine.AdStateMachine +} + +func New() service.IAds { + return &sAds{ + stateMachine: state_machine.NewAdStateMachine(), + } +} + +func init() { + service.RegisterAds(New()) +} + +// Upload 广告数据上传 - 处理用户广告状态流转 +func (s *sAds) Upload(ctx context.Context, in *model.AdsUploadIn) (out *model.AdsUploadOut, err error) { + // 解密广告数据 + adsData, err := encrypt.DecryptAdsData(in.Data) + if err != nil { + return nil, ecode.Fail.Sub("data_decrypt_failed") + } + + // 验证数据 + if adsData.AdsPlatId <= 0 || adsData.AdsCategoryId <= 0 || adsData.AppPackage == "" { + return nil, ecode.Params.Sub("invalid_ads_data") + } + + // 使用事务同时操作多个表 + err = dao.AdEventLogs.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + // 1. 查找用户该广告的最新记录,确保状态流转是在同一条广告记录上进行的 + var fromState consts.AdState + var adEventId int64 // 广告事件ID,用于关联状态流转记录 + var isNewLifecycle bool // 是否是新的生命周期 + + // 当前状态 + toState := consts.AdState(adsData.Status) + + // 拉取成功和拉取失败总是新的广告生命周期的开始 + if toState == consts.StateFetchSuccess || toState == consts.StateFetchFailed { + isNewLifecycle = true + fromState = 0 + glog.Infof(ctx, "Starting new ad lifecycle with fetch state for user %d, app %s, state: %s", + in.UserId, adsData.AppPackage, consts.GetStateDescription(toState)) + } else { + // 查询条件:同一用户、同一平台、同一广告类型、同一APP包名 + latestLog, err := dao.AdEventLogs.Ctx(ctx).TX(tx). + Where(do.AdEventLogs{ + UserId: in.UserId, + AdsPlatId: adsData.AdsPlatId, + AdsCategoryId: adsData.AdsCategoryId, + AppPackage: adsData.AppPackage, + }). + Order("created_at DESC"). + One() + + if err == nil && !latestLog.IsEmpty() { + fromState = consts.AdState(latestLog["status"].Int()) + adEventId = latestLog["id"].Int64() + + // 检查是否是终止状态,如果是终止状态,则需要创建新的广告记录 + if s.stateMachine.IsTerminalState(fromState) { + // 如果是终止状态,则表示这是一个新的广告生命周期 + isNewLifecycle = true + fromState = 0 // 重置状态,表示新的生命周期开始 + glog.Infof(ctx, "Starting new ad lifecycle after terminal state for user %d, app %s", + in.UserId, adsData.AppPackage) + } else { + glog.Infof(ctx, "Continuing ad lifecycle for user %d, app %s, from state %d to %d", + in.UserId, adsData.AppPackage, fromState, adsData.Status) + } + } else { + // 没有找到记录,这是第一次上传该广告数据 + isNewLifecycle = true + glog.Infof(ctx, "First ad upload for user %d, app %s", in.UserId, adsData.AppPackage) + } + } + + // 2. 使用状态机验证状态转换 + flowID := generateFlowID(in.UserId, adsData.AppPackage) + + // 如果不是新的生命周期,则验证状态转换 + if !isNewLifecycle { + err = s.stateMachine.Transition(ctx, flowID, in.UserId, fromState, toState, "用户操作") + if err != nil { + glog.Warningf(ctx, "Invalid state transition: %s -> %s for user %d, app %s", + consts.GetStateDescription(fromState), consts.GetStateDescription(toState), + in.UserId, adsData.AppPackage) + return err + } + } + + // 3. 插入或更新广告事件日志 + var eventId int64 + if isNewLifecycle { + // 新的生命周期,创建新记录 + eventId, err = dao.AdEventLogs.Ctx(ctx).TX(tx).Data(do.AdEventLogs{ + UserId: in.UserId, + AdsPlatId: adsData.AdsPlatId, + AdsCategoryId: adsData.AdsCategoryId, + AppPackage: adsData.AppPackage, + Status: adsData.Status, + StatusDesc: consts.GetStateDescription(toState), + }).InsertAndGetId() + if err != nil { + return ecode.Fail.Sub("database_save_failed") + } + } else { + // 继续现有生命周期,更新状态 + _, err = dao.AdEventLogs.Ctx(ctx).TX(tx). + Where("id", adEventId). + Data(do.AdEventLogs{ + Status: adsData.Status, + StatusDesc: consts.GetStateDescription(toState), + }). + Update() + if err != nil { + return ecode.Fail.Sub("database_update_failed") + } + eventId = adEventId + } + + // 4. 插入状态流转记录 + var fromStatus interface{} + if fromState != 0 { + fromStatus = int(fromState) + } + + _, err = dao.AdEventTransitions.Ctx(ctx).TX(tx).Data(do.AdEventTransitions{ + EventId: eventId, + FromStatus: fromStatus, + ToStatus: adsData.Status, + }).Insert() + if err != nil { + return ecode.Fail.Sub("transition_save_failed") + } + + // 5. 如果状态是观看完成(StateWatched),则给用户增加积分 + if toState == consts.StateWatched { + // 查询任务表中状态为1(启用)且任务类型为2(广告)的任务 + taskInfo, err := dao.Tasks.Ctx(ctx).TX(tx). + Fields("id, reward_points"). + Where("task_type", 2). + Where("status", 1). + One() + + if err != nil { + glog.Errorf(ctx, "Failed to query ad task: %v", err) + return ecode.Fail.Sub("task_query_failed") + } + + if !taskInfo.IsEmpty() { + taskId := taskInfo["id"].Int64() + rewardPoints := taskInfo["reward_points"].Uint() + + if rewardPoints > 0 { + // 更新用户积分 + _, err = dao.Users.Ctx(ctx).TX(tx). + Where("id", in.UserId). + Increment("points", rewardPoints) + + if err != nil { + glog.Errorf(ctx, "Failed to update user points: %v", err) + return ecode.Fail.Sub("user_points_update_failed") + } + + // 记录积分日志 + _, err = dao.UserPointsLogs.Ctx(ctx).TX(tx).Data(do.UserPointsLogs{ + UserId: in.UserId, + ChangeType: 2, // 2=收入(earn) + PointsChange: int(rewardPoints), + RelatedOrderId: eventId, + Description: "观看广告奖励", + }).Insert() + + if err != nil { + glog.Errorf(ctx, "Failed to create user points log: %v", err) + return ecode.Fail.Sub("user_points_log_create_failed") + } + + // 记录任务日志 + _, err = dao.TaskLogs.Ctx(ctx).TX(tx).Data(do.TaskLogs{ + TaskId: taskId, + UserId: in.UserId, + RewardPoints: rewardPoints, + }).Insert() + + if err != nil { + glog.Errorf(ctx, "Failed to create task log: %v", err) + return ecode.Fail.Sub("task_log_create_failed") + } + + glog.Infof(ctx, "User %d earned %d points for watching ad", in.UserId, rewardPoints) + } + } else { + glog.Warningf(ctx, "No active ad task found") + } + } + + return nil + }) + + if err != nil { + return nil, err + } + + // 构建MQTT消息 + mqttMessage := map[string]interface{}{ + "type": "ads", + "node_uid": in.NodeUid, + "device_code": in.DeviceCode, + "data": map[string]interface{}{ + "ads_plat_id": adsData.AdsPlatId, + "ads_category_id": adsData.AdsCategoryId, + "app_package": adsData.AppPackage, + "status": adsData.Status, + "timestamp": time.Now().Unix(), + }, + } + + // 发送到MQTT + s.publishToMQTT(ctx, mqttMessage) + + return &model.AdsUploadOut{Success: true}, nil +} + +// generateFlowID 生成流转ID +func generateFlowID(userID int64, appPackage string) string { + return fmt.Sprintf("flow_%d_%s_%d", userID, appPackage, time.Now().Unix()) +} + +// publishToMQTT 发送消息到MQTT +func (s *sAds) publishToMQTT(ctx context.Context, message map[string]interface{}) { + mqttClient := mqtt.GetMQTTClient("amazon_sqs") + if mqttClient == nil { + glog.Errorf(ctx, "MQTT client not initialized") + return + } + + queueName := g.Cfg().MustGet(ctx, "sqs.ads").String() + if queueName == "" { + glog.Errorf(ctx, "MQTT queue name not configured") + return + } + + err := mqttClient.Publish(ctx, queueName, message) + if err != nil { + glog.Errorf(ctx, "Failed to publish message to MQTT: %v", err) + } else { + glog.Infof(ctx, "Message published to MQTT: %v", message) + } +} diff --git a/internal/logic/admin/admin.go b/internal/logic/admin/admin.go index 6ea13eb..f605b8d 100644 --- a/internal/logic/admin/admin.go +++ b/internal/logic/admin/admin.go @@ -62,8 +62,9 @@ func (s *sAdmin) Info(ctx context.Context, in *model.AdminInfoIn) (out *model.Ad return nil, ecode.Fail.Sub("admin_query_failed") } return &model.AdminInfoOut{ - AdminId: admin.Id, + Id: admin.Id, Username: admin.Username, + Role: consts.AdminRoleCode, }, nil } diff --git a/internal/logic/author/author.go b/internal/logic/author/author.go index 7d4b18d..1da5f0f 100644 --- a/internal/logic/author/author.go +++ b/internal/logic/author/author.go @@ -31,7 +31,7 @@ func (s *sAuthor) List(ctx context.Context, in *model.AuthorListIn) (out *model. if in.Status != 0 { m = m.Where(dao.Authors.Columns().Status, in.Status) } - if err = m.Page(in.Page, in.Size).ScanAndCount(&out.List, &out.Total, false); err != nil { + if err = m.Page(in.Page, in.Size).WithAll().ScanAndCount(&out.List, &out.Total, false); err != nil { return } return @@ -226,13 +226,8 @@ func (s *sAuthor) Delete(ctx context.Context, in *model.AuthorDelIn) (out *model // Apply 允许用户申请成为作者 func (s *sAuthor) Apply(ctx context.Context, in *model.AuthorApplyIn) (out *model.AuthorApplyOut, err error) { - userIdVal := ctx.Value("id") - userId, ok := userIdVal.(int64) - if !ok || userId == 0 { - return nil, ecode.Fail.Sub("user_id_invalid") - } exist, err := dao.Authors.Ctx(ctx). - Where(dao.Authors.Columns().UserId, userId). + Where(dao.Authors.Columns().UserId, in.UserId). Exist() if err != nil { return nil, ecode.Fail.Sub("author_query_failed") @@ -241,10 +236,10 @@ func (s *sAuthor) Apply(ctx context.Context, in *model.AuthorApplyIn) (out *mode return nil, ecode.Params.Sub("author_user_exists") } if _, err := dao.Authors.Ctx(ctx).Data(do.Authors{ - UserId: userId, + UserId: in.UserId, PenName: in.PenName, Bio: in.Bio, - Status: 1, // 默认正常 + Status: 2, // 默认禁用 }).Insert(); err != nil { return nil, ecode.Fail.Sub("author_create_failed") } @@ -252,17 +247,74 @@ func (s *sAuthor) Apply(ctx context.Context, in *model.AuthorApplyIn) (out *mode } func (s *sAuthor) Detail(ctx context.Context, in *model.AuthorDetailIn) (out *model.AuthorDetailOut, err error) { out = &model.AuthorDetailOut{} - exist, err := dao.Authors.Ctx(ctx). - WherePri(in.AuthorId). - Exist() + err = dao.Authors.Ctx(ctx).WherePri(in.AuthorId).WithAll().Scan(&out) + if err != nil { + return nil, ecode.Fail.Sub("author_query_failed") + } + if out == nil { + return nil, ecode.NotFound.Sub("author_not_found") + } + userId := ctx.Value("id") + if userId != nil { + exist, err := dao.UserFollowAuthors.Ctx(ctx). + Where(dao.UserFollowAuthors.Columns().UserId, userId). + Where(dao.UserFollowAuthors.Columns().AuthorId, in.AuthorId). + Exist() + if err != nil { + return nil, ecode.Fail.Sub("user_follow_author_query_failed") + } + out.IsFollowed = exist + } + // 查询作者作品数量 + out.WorksCount, err = dao.Books.Ctx(ctx). + Where(dao.Books.Columns().AuthorId, in.AuthorId). + Count() + if err != nil { + return nil, ecode.Fail.Sub("author_book_count_failed") + } + return out, nil +} + +// AuthorInfo 获取作者信息 +func (s *sAuthor) AuthorInfo(ctx context.Context, in *model.AuthorInfoIn) (out *model.AuthorInfoOut, err error) { + exist, err := dao.Authors.Ctx(ctx).Where(dao.Authors.Columns().UserId, in.UserId).Exist() if err != nil { return nil, ecode.Fail.Sub("author_query_failed") } if !exist { return nil, ecode.NotFound.Sub("author_not_found") } - if err = dao.Authors.Ctx(ctx).WherePri(in.AuthorId).WithAll().Scan(&out); err != nil { + + var author struct { + Id int64 `json:"id"` + PenName string `json:"penName"` + } + err = dao.Authors.Ctx(ctx).Where(dao.Authors.Columns().UserId, in.UserId).Fields("id, pen_name").Scan(&author) + if err != nil { return nil, ecode.Fail.Sub("author_query_failed") } - return out, nil + + return &model.AuthorInfoOut{ + Id: author.Id, + PenName: author.PenName, + Role: "author", + }, nil +} + +// 审核作者申请(通过/拒绝) +func (s *sAuthor) Review(ctx context.Context, in *model.AuthorReviewIn) (out *model.AuthorReviewOut, err error) { + exist, err := dao.Authors.Ctx(ctx).WherePri(in.AuthorId).Exist() + if err != nil { + return nil, ecode.Fail.Sub("author_query_failed") + } + if !exist { + return nil, ecode.NotFound.Sub("author_not_found") + } + _, err = dao.Authors.Ctx(ctx).WherePri(in.AuthorId).Data(do.Authors{ + Status: in.Status, + }).Update() + if err != nil { + return nil, ecode.Fail.Sub("author_review_failed") + } + return &model.AuthorReviewOut{Success: true}, nil } diff --git a/internal/logic/book/book.go b/internal/logic/book/book.go index e646f55..c527205 100644 --- a/internal/logic/book/book.go +++ b/internal/logic/book/book.go @@ -232,6 +232,9 @@ func (s *sBook) AppList(ctx context.Context, in *model.BookAppListIn) (out *mode if in.IsFeatured { m = m.Where(dao.Books.Columns().IsFeatured, 1) } + if in.IsHot { + m = m.Where(dao.Books.Columns().IsHot, 1) + } if in.Sort != "" { m = m.Order(in.Sort) } @@ -278,6 +281,30 @@ func (s *sBook) AppList(ctx context.Context, in *model.BookAppListIn) (out *mode out.List[i].MyRating = 0 } } + + // 查询用户书架 + type shelfRow struct { + BookId int64 `json:"bookId"` + } + shelves := make([]shelfRow, 0) + err = dao.Bookshelves.Ctx(ctx). + Fields("book_id"). + Where(dao.Bookshelves.Columns().UserId, in.UserId). + WhereIn(dao.Bookshelves.Columns().BookId, bookIds). + Scan(&shelves) + if err == nil && len(shelves) > 0 { + shelfMap := make(map[int64]bool, len(shelves)) + for _, s := range shelves { + shelfMap[s.BookId] = true + } + for i := range out.List { + out.List[i].IsInBookshelf = shelfMap[out.List[i].Id] + } + } else { + for i := range out.List { + out.List[i].IsInBookshelf = false + } + } } return out, nil @@ -323,7 +350,7 @@ func (s *sBook) AppRate(ctx context.Context, in *model.BookAppRateIn) (out *mode // 开启事务处理评分 if err := dao.BookRatings.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { // 检查是否已经评分过 - exist, err := dao.BookRatings.Ctx(ctx).TX(tx). + exist, err := dao.BookRatings.Ctx(ctx). Where(dao.BookRatings.Columns().UserId, in.UserId). Where(dao.BookRatings.Columns().BookId, in.BookId). Exist() @@ -333,7 +360,7 @@ func (s *sBook) AppRate(ctx context.Context, in *model.BookAppRateIn) (out *mode if exist { // 更新现有评分 - _, err = dao.BookRatings.Ctx(ctx).TX(tx). + _, err = dao.BookRatings.Ctx(ctx). Where(dao.BookRatings.Columns().UserId, in.UserId). Where(dao.BookRatings.Columns().BookId, in.BookId). Data(do.BookRatings{ @@ -344,7 +371,7 @@ func (s *sBook) AppRate(ctx context.Context, in *model.BookAppRateIn) (out *mode } } else { // 创建新评分记录 - _, err = dao.BookRatings.Ctx(ctx).TX(tx).Data(do.BookRatings{ + _, err = dao.BookRatings.Ctx(ctx).Data(do.BookRatings{ UserId: in.UserId, BookId: in.BookId, Score: in.Rating, @@ -355,20 +382,22 @@ func (s *sBook) AppRate(ctx context.Context, in *model.BookAppRateIn) (out *mode } // 重新计算书籍平均评分 - var avgRating float64 - err = dao.BookRatings.Ctx(ctx).TX(tx). + var result struct { + AvgRating float64 `json:"avg_rating"` + } + err = dao.BookRatings.Ctx(ctx). Where(dao.BookRatings.Columns().BookId, in.BookId). - Fields("AVG(score) as avg_rating"). - Scan(&avgRating) + Fields("COALESCE(AVG(" + dao.BookRatings.Columns().Score + "), 0) as avg_rating"). + Scan(&result) if err != nil { return ecode.Fail.Sub("rating_calculation_failed") } // 更新书籍的平均评分 - _, err = dao.Books.Ctx(ctx).TX(tx). + _, err = dao.Books.Ctx(ctx). Where(dao.Books.Columns().Id, in.BookId). Data(do.Books{ - Rating: avgRating, + Rating: result.AvgRating, }).Update() if err != nil { return ecode.Fail.Sub("book_rating_update_failed") @@ -397,7 +426,7 @@ func (s *sBook) AppDetail(ctx context.Context, in *model.BookAppDetailIn) (out * m = m.Where(dao.Books.Columns().Id, in.Id) // 执行查询 - if err = m.Scan(out); err != nil { + if err = m.WithAll().Scan(out); err != nil { return nil, ecode.Fail.Sub("book_query_failed") } @@ -408,6 +437,29 @@ func (s *sBook) AppDetail(ctx context.Context, in *model.BookAppDetailIn) (out * // 如果用户已登录,查询阅读进度 if in.UserId > 0 { + // 查询用户是否已将本书加入书架 + count, err := dao.Bookshelves.Ctx(ctx). + Where(dao.Bookshelves.Columns().UserId, in.UserId). + Where(dao.Bookshelves.Columns().BookId, in.Id). + Count() + if err == nil { + out.IsInBookshelf = count > 0 + } + + // 查询用户是否已评分 + var ratingRecord struct { + Score float64 `json:"score"` + } + err = dao.BookRatings.Ctx(ctx). + Fields("score"). + Where(dao.BookRatings.Columns().UserId, in.UserId). + Where(dao.BookRatings.Columns().BookId, in.Id). + Scan(&ratingRecord) + if err == nil && ratingRecord.Score > 0 { + out.HasRated = true + out.MyRating = ratingRecord.Score + } + // 查询用户对该书籍的历史记录 var historyRecord struct { ChapterId int64 `json:"chapterId"` @@ -435,7 +487,7 @@ func (s *sBook) AppDetail(ctx context.Context, in *model.BookAppDetailIn) (out * readChapters, err := dao.UserReadRecords.Ctx(ctx). Where(dao.UserReadRecords.Columns().UserId, in.UserId). Where(dao.UserReadRecords.Columns().BookId, in.Id). - Where(dao.UserReadRecords.Columns().ChapterId, ">", 0). // 只统计有章节ID的记录 + WhereGT(dao.UserReadRecords.Columns().ChapterId, 0). // 只统计有章节ID的记录 Count() if err == nil { @@ -454,274 +506,174 @@ func (s *sBook) AppDetail(ctx context.Context, in *model.BookAppDetailIn) (out * func (s *sBook) MyList(ctx context.Context, in *model.MyBookListIn) (out *model.MyBookListOut, err error) { out = &model.MyBookListOut{} - // 验证用户ID if in.UserId == 0 { return nil, ecode.Fail.Sub("user_id_required") } - - // 验证类型参数 if in.Type < 1 || in.Type > 3 { return nil, ecode.Fail.Sub("type_invalid") } - var list []model.MyBookItem - var total int + var ( + ids []int64 + extraMap map[int64]struct { + Progress int + LastReadAt string + } + total int + ) switch in.Type { - case 1: // 正在读 - // 查询书架表中read_status=1的记录 + case 1, 2: var bookshelves []struct { BookId int64 `json:"bookId"` LastReadPercent float64 `json:"lastReadPercent"` LastReadAt string `json:"lastReadAt"` } - - if in.Sort != "" { - err = dao.Bookshelves.Ctx(ctx). - Fields("book_id, last_read_percent, last_read_at"). - Where(dao.Bookshelves.Columns().UserId, in.UserId). - Where(dao.Bookshelves.Columns().ReadStatus, 1). - Order(in.Sort). - Page(in.Page, in.Size). - ScanAndCount(&bookshelves, &total, false) - } else { - err = dao.Bookshelves.Ctx(ctx). - Fields("book_id, last_read_percent, last_read_at"). - Where(dao.Bookshelves.Columns().UserId, in.UserId). - Where(dao.Bookshelves.Columns().ReadStatus, 1). - Page(in.Page, in.Size). - ScanAndCount(&bookshelves, &total, false) + readStatus := 1 + if in.Type == 2 { + readStatus = 2 } + q := dao.Bookshelves.Ctx(ctx). + Fields("book_id, last_read_percent, last_read_at"). + Where(dao.Bookshelves.Columns().UserId, in.UserId). + Where(dao.Bookshelves.Columns().ReadStatus, readStatus) + if in.Sort != "" { + q = q.Order(in.Sort) + } else { + q = q + } + err = q.Page(in.Page, in.Size).ScanAndCount(&bookshelves, &total, false) if err != nil { return nil, ecode.Fail.Sub("bookshelf_query_failed") } - - // 获取书籍详细信息 - if len(bookshelves) > 0 { - bookIds := make([]int64, 0, len(bookshelves)) - for _, bs := range bookshelves { - bookIds = append(bookIds, bs.BookId) - } - - var books []model.Book - err = dao.Books.Ctx(ctx). - WhereIn(dao.Books.Columns().Id, bookIds). - Scan(&books) - if err != nil { - return nil, ecode.Fail.Sub("book_query_failed") - } - - // 构建书籍ID到书架信息的映射 - bookshelfMap := make(map[int64]struct { - LastReadPercent float64 - LastReadAt string - }) - for _, bs := range bookshelves { - bookshelfMap[bs.BookId] = struct { - LastReadPercent float64 - LastReadAt string - }{ - LastReadPercent: bs.LastReadPercent, - LastReadAt: bs.LastReadAt, - } - } - - // 构建返回列表 - for _, book := range books { - bsInfo := bookshelfMap[book.Id] - list = append(list, model.MyBookItem{ - Id: book.Id, - Title: book.Title, - CoverUrl: book.CoverUrl, - Description: book.Description, - Progress: int(bsInfo.LastReadPercent), - IsInShelf: true, - LastReadAt: bsInfo.LastReadAt, - Status: book.Status, - AuthorId: book.AuthorId, - CategoryId: book.CategoryId, - }) + ids = make([]int64, 0, len(bookshelves)) + extraMap = make(map[int64]struct { + Progress int + LastReadAt string + }, len(bookshelves)) + for _, bs := range bookshelves { + ids = append(ids, bs.BookId) + extraMap[bs.BookId] = struct { + Progress int + LastReadAt string + }{ + Progress: int(bs.LastReadPercent), + LastReadAt: bs.LastReadAt, } } - - case 2: // 已读完 - // 查询书架表中read_status=2的记录 - var bookshelves []struct { - BookId int64 `json:"bookId"` - LastReadPercent float64 `json:"lastReadPercent"` - LastReadAt string `json:"lastReadAt"` - } - - if in.Sort != "" { - err = dao.Bookshelves.Ctx(ctx). - Fields("book_id, last_read_percent, last_read_at"). - Where(dao.Bookshelves.Columns().UserId, in.UserId). - Where(dao.Bookshelves.Columns().ReadStatus, 2). - Order(in.Sort). - Page(in.Page, in.Size). - ScanAndCount(&bookshelves, &total, false) - } else { - err = dao.Bookshelves.Ctx(ctx). - Fields("book_id, last_read_percent, last_read_at"). - Where(dao.Bookshelves.Columns().UserId, in.UserId). - Where(dao.Bookshelves.Columns().ReadStatus, 2). - Page(in.Page, in.Size). - ScanAndCount(&bookshelves, &total, false) - } - if err != nil { - return nil, ecode.Fail.Sub("bookshelf_query_failed") - } - - // 获取书籍详细信息 - if len(bookshelves) > 0 { - bookIds := make([]int64, 0, len(bookshelves)) - for _, bs := range bookshelves { - bookIds = append(bookIds, bs.BookId) - } - - var books []model.Book - err = dao.Books.Ctx(ctx). - WhereIn(dao.Books.Columns().Id, bookIds). - Scan(&books) - if err != nil { - return nil, ecode.Fail.Sub("book_query_failed") - } - - // 构建书籍ID到书架信息的映射 - bookshelfMap := make(map[int64]struct { - LastReadPercent float64 - LastReadAt string - }) - for _, bs := range bookshelves { - bookshelfMap[bs.BookId] = struct { - LastReadPercent float64 - LastReadAt string - }{ - LastReadPercent: bs.LastReadPercent, - LastReadAt: bs.LastReadAt, - } - } - - // 构建返回列表 - for _, book := range books { - bsInfo := bookshelfMap[book.Id] - list = append(list, model.MyBookItem{ - Id: book.Id, - Title: book.Title, - CoverUrl: book.CoverUrl, - Description: book.Description, - Progress: int(bsInfo.LastReadPercent), - IsInShelf: true, - LastReadAt: bsInfo.LastReadAt, - Status: book.Status, - AuthorId: book.AuthorId, - CategoryId: book.CategoryId, - }) - } - } - - case 3: // 历史记录 - // 查询user_read_history表 + case 3: var histories []struct { BookId int64 `json:"bookId"` ChapterId int64 `json:"chapterId"` ReadAt string `json:"readAt"` } - + q := dao.UserReadHistory.Ctx(ctx). + Fields("book_id, chapter_id, read_at"). + Where(dao.UserReadHistory.Columns().UserId, in.UserId) if in.Sort != "" { - err = dao.UserReadHistory.Ctx(ctx). - Fields("book_id, chapter_id, read_at"). - Where(dao.UserReadHistory.Columns().UserId, in.UserId). - Order(in.Sort). - Page(in.Page, in.Size). - ScanAndCount(&histories, &total, false) + q = q.Order(in.Sort) } else { - err = dao.UserReadHistory.Ctx(ctx). - Fields("book_id, chapter_id, read_at"). - Where(dao.UserReadHistory.Columns().UserId, in.UserId). - OrderDesc(dao.UserReadHistory.Columns().ReadAt). - Page(in.Page, in.Size). - ScanAndCount(&histories, &total, false) + q = q.OrderDesc(dao.UserReadHistory.Columns().ReadAt) } + err = q.Page(in.Page, in.Size).ScanAndCount(&histories, &total, false) if err != nil { return nil, ecode.Fail.Sub("history_query_failed") } - - // 获取书籍详细信息 - if len(histories) > 0 { - bookIds := make([]int64, 0, len(histories)) - for _, history := range histories { - bookIds = append(bookIds, history.BookId) - } - - var books []model.Book - err = dao.Books.Ctx(ctx). - WhereIn(dao.Books.Columns().Id, bookIds). - Scan(&books) - if err != nil { - return nil, ecode.Fail.Sub("book_query_failed") - } - - // 构建书籍ID到历史信息的映射 - historyMap := make(map[int64]struct { - ChapterId int64 - ReadAt string - }) - for _, history := range histories { - historyMap[history.BookId] = struct { - ChapterId int64 - ReadAt string - }{ - ChapterId: history.ChapterId, - ReadAt: history.ReadAt, - } - } - - // 检查是否在书架中 - var bookshelves []struct { - BookId int64 `json:"bookId"` - } - err = dao.Bookshelves.Ctx(ctx). - Fields("book_id"). - Where(dao.Bookshelves.Columns().UserId, in.UserId). - WhereIn(dao.Bookshelves.Columns().BookId, bookIds). - Scan(&bookshelves) - if err != nil { - return nil, ecode.Fail.Sub("bookshelf_query_failed") - } - - // 构建书架ID集合 - shelfBookIds := make(map[int64]bool) - for _, bs := range bookshelves { - shelfBookIds[bs.BookId] = true - } - - // 构建返回列表 - for _, book := range books { - historyInfo := historyMap[book.Id] - list = append(list, model.MyBookItem{ - Id: book.Id, - Title: book.Title, - CoverUrl: book.CoverUrl, - Description: book.Description, - Progress: 0, // 历史记录不显示进度 - IsInShelf: shelfBookIds[book.Id], - LastReadAt: historyInfo.ReadAt, - Status: book.Status, - AuthorId: book.AuthorId, - CategoryId: book.CategoryId, - }) + ids = make([]int64, 0, len(histories)) + extraMap = make(map[int64]struct { + Progress int + LastReadAt string + }, len(histories)) + for _, h := range histories { + ids = append(ids, h.BookId) + extraMap[h.BookId] = struct { + Progress int + LastReadAt string + }{ + Progress: 0, + LastReadAt: h.ReadAt, } } - - default: - // 返回空列表 } - out = &model.MyBookListOut{ - Total: total, - List: list, + var books []model.MyBookItem + if len(ids) > 0 { + err = dao.Books.Ctx(ctx).WhereIn(dao.Books.Columns().Id, ids).Scan(&books) + if err != nil { + return nil, ecode.Fail.Sub("book_query_failed") + } } + + // type3 需要查书架 + shelfBookIds := map[int64]bool{} + if in.Type == 3 && len(ids) > 0 { + var bookshelves []struct{ BookId int64 } + err = dao.Bookshelves.Ctx(ctx). + Fields("book_id"). + Where(dao.Bookshelves.Columns().UserId, in.UserId). + WhereIn(dao.Bookshelves.Columns().BookId, ids). + Scan(&bookshelves) + if err != nil { + return nil, ecode.Fail.Sub("bookshelf_query_failed") + } + for _, bs := range bookshelves { + shelfBookIds[bs.BookId] = true + } + } + + for i := range books { + if info, ok := extraMap[books[i].Id]; ok { + books[i].Progress = info.Progress + books[i].LastReadAt = info.LastReadAt + } + if in.Type == 1 || in.Type == 2 { + books[i].IsInShelf = true + } else if in.Type == 3 { + books[i].IsInShelf = shelfBookIds[books[i].Id] + } + } + + // 查询用户评分信息 + if len(books) > 0 { + bookIds := make([]int64, 0, len(books)) + for _, book := range books { + bookIds = append(bookIds, book.Id) + } + + type ratingRow struct { + BookId int64 `json:"bookId"` + Score float64 `json:"score"` + } + ratings := make([]ratingRow, 0) + err = dao.BookRatings.Ctx(ctx). + Fields("book_id, score"). + Where(dao.BookRatings.Columns().UserId, in.UserId). + WhereIn(dao.BookRatings.Columns().BookId, bookIds). + Scan(&ratings) + if err == nil && len(ratings) > 0 { + ratingMap := make(map[int64]float64, len(ratings)) + for _, r := range ratings { + ratingMap[r.BookId] = r.Score + } + for i := range books { + if score, ok := ratingMap[books[i].Id]; ok { + books[i].HasRated = true + books[i].MyRating = score + } else { + books[i].HasRated = false + books[i].MyRating = 0 + } + } + } else { + for i := range books { + books[i].HasRated = false + books[i].MyRating = 0 + } + } + } + + out.Total = total + out.List = books return } @@ -762,3 +714,22 @@ func (s *sBook) SetRecommended(ctx context.Context, in *model.BookSetRecommended } return &model.BookCRUDOut{Success: true}, nil } + +// SetHot: 单独修改书籍的热门状态 +func (s *sBook) SetHot(ctx context.Context, in *model.BookSetHotIn) (out *model.BookCRUDOut, err error) { + // 检查书籍是否存在 + exist, err := dao.Books.Ctx(ctx).WherePri(in.Id).Exist() + if err != nil { + return nil, ecode.Fail.Sub("book_query_failed") + } + if !exist { + return nil, ecode.NotFound.Sub("book_not_found") + } + _, err = dao.Books.Ctx(ctx).WherePri(in.Id).Data(do.Books{ + IsHot: in.IsHot, + }).Update() + if err != nil { + return nil, ecode.Fail.Sub("book_update_failed") + } + return &model.BookCRUDOut{Success: true}, nil +} diff --git a/internal/logic/book_recommendations/book_recommendations.go b/internal/logic/book_recommendations/book_recommendations.go new file mode 100644 index 0000000..3eac7bc --- /dev/null +++ b/internal/logic/book_recommendations/book_recommendations.go @@ -0,0 +1,226 @@ +package book_recommendations + +import ( + "context" + "server/internal/dao" + "server/internal/model" + "server/internal/model/do" + "server/internal/service" + "server/utility/ecode" + "server/utility/oss" + + "github.com/gogf/gf/v2/net/ghttp" +) + +type sBookRecommendations struct{} + +func New() service.IBookRecommendations { + return &sBookRecommendations{} +} + +func init() { + service.RegisterBookRecommendations(New()) +} + +// List 获取推荐列表 +func (s *sBookRecommendations) List(ctx context.Context, in *model.BookRecommendationsListIn) (out *model.BookRecommendationsListOut, err error) { + out = &model.BookRecommendationsListOut{} + m := dao.BookRecommendations.Ctx(ctx) + if in.Type != 0 { + m = m.Where(dao.BookRecommendations.Columns().Type, in.Type) + } + if in.Status != 0 { + m = m.Where(dao.BookRecommendations.Columns().Status, in.Status) + } + if in.BookId != 0 { + m = m.Where(dao.BookRecommendations.Columns().BookId, in.BookId) + } + m = m.Order(dao.BookRecommendations.Columns().SortOrder) + if err = m.Page(in.Page, in.Size).WithAll().ScanAndCount(&out.List, &out.Total, false); err != nil { + return nil, ecode.Fail.Sub("book_recommendation_query_failed") + } + return out, nil +} + +// AppList 获取APP端推荐列表 +func (s *sBookRecommendations) AppList(ctx context.Context, in *model.BookRecommendationsListIn) (out *model.BookRecommendationsAppListOut, err error) { + out = &model.BookRecommendationsAppListOut{} + m := dao.BookRecommendations.Ctx(ctx) + // 直接筛选状态为1的记录 + m = m.Where(dao.BookRecommendations.Columns().Status, 1) + // 根据排序字段排序 + m = m.Order(dao.BookRecommendations.Columns().SortOrder) + var list []model.RecommendAppItem + if err = m.Page(in.Page, in.Size).ScanAndCount(&list, &out.Total, false); err != nil { + return nil, ecode.Fail.Sub("book_recommendation_query_failed") + } + out.List = list + return out, nil +} + +// Create 新增推荐 +func (s *sBookRecommendations) Create(ctx context.Context, in *model.BookRecommendationsCreateIn) (out *model.BookRecommendationsCRUDOut, err error) { + // 检查同类型同书籍是否已存在(未软删除) + exist, err := dao.BookRecommendations.Ctx(ctx). + Where(dao.BookRecommendations.Columns().BookId, in.BookId). + Where(dao.BookRecommendations.Columns().Type, in.Type). + Exist() + if err != nil { + return nil, ecode.Fail.Sub("book_recommendation_query_failed") + } + if exist { + return nil, ecode.Params.Sub("book_recommendation_exists") + } + // 插入数据 + _, err = dao.BookRecommendations.Ctx(ctx).Data(do.BookRecommendations{ + BookId: in.BookId, + Type: in.Type, + CoverUrl: in.CoverUrl, + SortOrder: in.SortOrder, + Status: in.Status, + }).Insert() + if err != nil { + return nil, ecode.Fail.Sub("book_recommendation_create_failed") + } + return &model.BookRecommendationsCRUDOut{Success: true}, nil +} + +// Update 编辑推荐 +func (s *sBookRecommendations) Update(ctx context.Context, in *model.BookRecommendationsUpdateIn) (out *model.BookRecommendationsCRUDOut, err error) { + // 检查是否存在 + exist, err := dao.BookRecommendations.Ctx(ctx). + Where(dao.BookRecommendations.Columns().Id, in.Id). + Exist() + if err != nil { + return nil, ecode.Fail.Sub("book_recommendation_query_failed") + } + if !exist { + return nil, ecode.NotFound.Sub("book_recommendation_not_found") + } + // 检查同类型同书籍是否有重复(排除自己) + repeat, err := dao.BookRecommendations.Ctx(ctx). + Where(dao.BookRecommendations.Columns().BookId, in.BookId). + Where(dao.BookRecommendations.Columns().Type, in.Type). + WhereNot(dao.BookRecommendations.Columns().Id, in.Id). + Exist() + if err != nil { + return nil, ecode.Fail.Sub("book_recommendation_query_failed") + } + if repeat { + return nil, ecode.Params.Sub("book_recommendation_exists") + } + _, err = dao.BookRecommendations.Ctx(ctx). + Where(dao.BookRecommendations.Columns().Id, in.Id). + Data(do.BookRecommendations{ + BookId: in.BookId, + Type: in.Type, + CoverUrl: in.CoverUrl, + SortOrder: in.SortOrder, + Status: in.Status, + }).Update() + if err != nil { + return nil, ecode.Fail.Sub("book_recommendation_update_failed") + } + return &model.BookRecommendationsCRUDOut{Success: true}, nil +} + +// Delete 删除推荐 +func (s *sBookRecommendations) Delete(ctx context.Context, in *model.BookRecommendationsDeleteIn) (out *model.BookRecommendationsCRUDOut, err error) { + // 检查是否存在 + exist, err := dao.BookRecommendations.Ctx(ctx). + Where(dao.BookRecommendations.Columns().Id, in.Id). + Exist() + if err != nil { + return nil, ecode.Fail.Sub("book_recommendation_query_failed") + } + if !exist { + return nil, ecode.NotFound.Sub("book_recommendation_not_found") + } + // 直接调用 Delete + _, err = dao.BookRecommendations.Ctx(ctx). + Where(dao.BookRecommendations.Columns().Id, in.Id). + Delete() + if err != nil { + return nil, ecode.Fail.Sub("book_recommendation_delete_failed") + } + return &model.BookRecommendationsCRUDOut{Success: true}, nil +} + +// SetStatus 启用/禁用推荐 +func (s *sBookRecommendations) SetStatus(ctx context.Context, in *model.BookRecommendationsSetStatusIn) (out *model.BookRecommendationsCRUDOut, err error) { + // 检查是否存在 + exist, err := dao.BookRecommendations.Ctx(ctx). + Where(dao.BookRecommendations.Columns().Id, in.Id). + Exist() + if err != nil { + return nil, ecode.Fail.Sub("book_recommendation_query_failed") + } + if !exist { + return nil, ecode.NotFound.Sub("book_recommendation_not_found") + } + _, err = dao.BookRecommendations.Ctx(ctx). + Where(dao.BookRecommendations.Columns().Id, in.Id). + Data(do.BookRecommendations{ + Status: in.Status, + }).Update() + if err != nil { + return nil, ecode.Fail.Sub("book_recommendation_update_failed") + } + return &model.BookRecommendationsCRUDOut{Success: true}, nil +} + +// SortOrder 设置排序 +func (s *sBookRecommendations) SortOrder(ctx context.Context, in *model.BookRecommendationsSortOrderIn) (out *model.BookRecommendationsCRUDOut, err error) { + // 检查是否存在 + exist, err := dao.BookRecommendations.Ctx(ctx). + Where(dao.BookRecommendations.Columns().Id, in.Id). + Exist() + if err != nil { + return nil, ecode.Fail.Sub("book_recommendation_query_failed") + } + if !exist { + return nil, ecode.NotFound.Sub("book_recommendation_not_found") + } + _, err = dao.BookRecommendations.Ctx(ctx). + Where(dao.BookRecommendations.Columns().Id, in.Id). + Data(do.BookRecommendations{ + SortOrder: in.SortOrder, + }).Update() + if err != nil { + return nil, ecode.Fail.Sub("book_recommendation_update_failed") + } + return &model.BookRecommendationsCRUDOut{Success: true}, nil +} + +// UploadCover 上传推荐封面图 +func (s *sBookRecommendations) UploadCover(ctx context.Context, file *ghttp.UploadFile) (url string, err error) { + if file == nil { + return "", ecode.Params.Sub("image_file_required") + } + // 校验文件类型(只允许图片) + contentType := file.Header.Get("Content-Type") + if contentType == "" || contentType[:6] != "image/" { + return "", ecode.Params.Sub("image_type_invalid") + } + allowedTypes := map[string]bool{ + "image/jpeg": true, + "image/png": true, + "image/gif": true, + "image/webp": true, + } + if !allowedTypes[contentType] { + return "", ecode.Params.Sub("image_format_invalid") + } + if file.Size > 1*1024*1024 { + return "", ecode.Params.Sub("image_size_exceeded") + } + client := oss.GetOSSClient("amazon_s3") + if client == nil { + return "", ecode.Fail.Sub("server_error") + } + url, err = client.Upload(file, "recommend") + if err != nil { + return "", ecode.Fail.Sub("image_upload_failed") + } + return url, nil +} diff --git a/internal/logic/bookshelve/bookshelve.go b/internal/logic/bookshelve/bookshelve.go index 3424710..b6a98a4 100644 --- a/internal/logic/bookshelve/bookshelve.go +++ b/internal/logic/bookshelve/bookshelve.go @@ -55,3 +55,4 @@ func (s *sBookshelve) Delete(ctx context.Context, in *model.BookshelveDelIn) (ou } return &model.BookshelveCRUDOut{Success: true}, nil } + diff --git a/internal/logic/chapter/chapter.go b/internal/logic/chapter/chapter.go index ceccf96..c268cde 100644 --- a/internal/logic/chapter/chapter.go +++ b/internal/logic/chapter/chapter.go @@ -180,13 +180,34 @@ func (s *sChapter) AppList(ctx context.Context, in *model.ChapterAppListIn) (out return nil, ecode.Fail.Sub("read_record_query_failed") } + // 查询购买记录 + purchaseRecords := make([]struct { + ChapterId int64 `json:"chapterId"` + }, 0) + + err = dao.UserChapterPurchases.Ctx(ctx). + Fields("chapter_id"). + Where(dao.UserChapterPurchases.Columns().UserId, in.UserId). + Where(dao.UserChapterPurchases.Columns().BookId, in.BookId). + WhereIn(dao.UserChapterPurchases.Columns().ChapterId, chapterIds). + Scan(&purchaseRecords) + if err != nil { + return nil, ecode.Fail.Sub("purchase_record_query_failed") + } + // 构建阅读记录映射 readMap := make(map[int64]*gtime.Time) for _, record := range readRecords { readMap[record.ChapterId] = record.ReadAt } - // 为每个章节设置阅读进度 + // 构建购买记录映射 + purchaseMap := make(map[int64]bool) + for _, record := range purchaseRecords { + purchaseMap[record.ChapterId] = true + } + + // 为每个章节设置阅读进度和购买状态 for i := range out.List { if readAt, exists := readMap[out.List[i].Id]; exists { out.List[i].ReadAt = readAt @@ -195,6 +216,19 @@ func (s *sChapter) AppList(ctx context.Context, in *model.ChapterAppListIn) (out out.List[i].ReadProgress = 0 // 未读 out.List[i].ReadAt = nil } + + // 设置购买状态 + if out.List[i].IsLocked == 0 { + // 免费章节,直接设置为已购买 + out.List[i].IsPurchased = true + } else { + // 付费章节,根据购买记录设置 + if purchaseMap[out.List[i].Id] { + out.List[i].IsPurchased = true + } else { + out.List[i].IsPurchased = false + } + } } } } @@ -473,7 +507,7 @@ func (s *sChapter) AppProgress(ctx context.Context, in *model.ChapterAppProgress readChapters, err = dao.UserReadRecords.Ctx(ctx).TX(tx). Where(dao.UserReadRecords.Columns().UserId, in.UserId). Where(dao.UserReadRecords.Columns().BookId, in.BookId). - Where(dao.UserReadRecords.Columns().ChapterId, ">", 0). + WhereGT(dao.UserReadRecords.Columns().ChapterId, 0). Count() if err != nil { return ecode.Fail.Sub("read_chapter_count_failed") @@ -525,3 +559,211 @@ func (s *sChapter) AppProgress(ctx context.Context, in *model.ChapterAppProgress out.Success = true return out, nil } + +// AppBatchProgress uploads batch reading progress for app +func (s *sChapter) AppBatchProgress(ctx context.Context, in *model.ChapterAppBatchProgressIn) (out *model.ChapterAppProgressOut, err error) { + out = &model.ChapterAppProgressOut{} + + // 必须指定用户ID + if in.UserId == 0 { + return nil, ecode.Fail.Sub("user_id_required") + } + + // 必须指定书籍ID + if in.BookId == 0 { + return nil, ecode.Fail.Sub("book_id_required") + } + + // 必须指定章节列表 + if len(in.Chapters) == 0 { + return nil, ecode.Fail.Sub("chapters_required") + } + + // 检查用户是否存在 + exist, err := dao.Users.Ctx(ctx).Where(dao.Users.Columns().Id, in.UserId).Exist() + if err != nil { + return nil, ecode.Fail.Sub("user_query_failed") + } + if !exist { + return nil, ecode.NotFound.Sub("user_not_found") + } + + // 验证所有章节进度 + for _, chapter := range in.Chapters { + if chapter.ChapterId == 0 { + return nil, ecode.Fail.Sub("chapter_id_required") + } + if chapter.Progress < 0 || chapter.Progress > 100 { + return nil, ecode.Fail.Sub("progress_invalid") + } + } + + // 检查所有章节是否存在 + chapterIds := make([]int64, 0) + for _, chapter := range in.Chapters { + chapterIds = append(chapterIds, chapter.ChapterId) + } + + exist, err = dao.Chapters.Ctx(ctx). + WhereIn(dao.Chapters.Columns().Id, chapterIds). + Where(dao.Chapters.Columns().BookId, in.BookId). + Exist() + if err != nil { + return nil, ecode.Fail.Sub("chapter_query_failed") + } + if !exist { + return nil, ecode.NotFound.Sub("chapter_not_found") + } + + // 开启事务处理 + if err := dao.UserReadRecords.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + // 批量处理每个章节的进度 + for _, chapter := range in.Chapters { + // 1. 更新或创建阅读记录 + exist, err := dao.UserReadRecords.Ctx(ctx).TX(tx). + Where(dao.UserReadRecords.Columns().UserId, in.UserId). + Where(dao.UserReadRecords.Columns().BookId, in.BookId). + Where(dao.UserReadRecords.Columns().ChapterId, chapter.ChapterId). + Exist() + if err != nil { + return ecode.Fail.Sub("read_record_query_failed") + } + + if exist { + // 更新现有记录 + _, err = dao.UserReadRecords.Ctx(ctx).TX(tx). + Where(dao.UserReadRecords.Columns().UserId, in.UserId). + Where(dao.UserReadRecords.Columns().BookId, in.BookId). + Where(dao.UserReadRecords.Columns().ChapterId, chapter.ChapterId). + Data(do.UserReadRecords{ + Progress: chapter.Progress, + ReadAt: gtime.Now(), + }).Update() + if err != nil { + return ecode.Fail.Sub("read_record_update_failed") + } + } else { + // 创建新记录 + _, err = dao.UserReadRecords.Ctx(ctx).TX(tx).Data(do.UserReadRecords{ + UserId: in.UserId, + BookId: in.BookId, + ChapterId: chapter.ChapterId, + Progress: chapter.Progress, + ReadAt: gtime.Now(), + }).Insert() + if err != nil { + return ecode.Fail.Sub("read_record_create_failed") + } + } + } + + // 2. 更新或创建历史记录(使用最后一个章节作为当前阅读章节) + lastChapter := in.Chapters[len(in.Chapters)-1] + exist, err = dao.UserReadHistory.Ctx(ctx).TX(tx). + Where(dao.UserReadHistory.Columns().UserId, in.UserId). + Where(dao.UserReadHistory.Columns().BookId, in.BookId). + Exist() + if err != nil { + return ecode.Fail.Sub("history_query_failed") + } + + if exist { + // 更新现有历史记录 + _, err = dao.UserReadHistory.Ctx(ctx).TX(tx). + Where(dao.UserReadHistory.Columns().UserId, in.UserId). + Where(dao.UserReadHistory.Columns().BookId, in.BookId). + Data(do.UserReadHistory{ + ChapterId: lastChapter.ChapterId, + ReadAt: gtime.Now(), + }).Update() + if err != nil { + return ecode.Fail.Sub("history_update_failed") + } + } else { + // 创建新历史记录 + _, err = dao.UserReadHistory.Ctx(ctx).TX(tx).Data(do.UserReadHistory{ + UserId: in.UserId, + BookId: in.BookId, + ChapterId: lastChapter.ChapterId, + ReadAt: gtime.Now(), + }).Insert() + if err != nil { + return ecode.Fail.Sub("history_create_failed") + } + } + + // 3. 更新书架记录(如果存在) + exist, err = dao.Bookshelves.Ctx(ctx).TX(tx). + Where(dao.Bookshelves.Columns().UserId, in.UserId). + Where(dao.Bookshelves.Columns().BookId, in.BookId). + Exist() + if err != nil { + return ecode.Fail.Sub("bookshelf_query_failed") + } + + if exist { + // 计算阅读进度百分比 + totalChapters, err := dao.Chapters.Ctx(ctx).TX(tx). + Where(dao.Chapters.Columns().BookId, in.BookId). + Count() + if err != nil { + return ecode.Fail.Sub("chapter_count_failed") + } + + var readChapters int + if totalChapters > 0 { + readChapters, err = dao.UserReadRecords.Ctx(ctx).TX(tx). + Where(dao.UserReadRecords.Columns().UserId, in.UserId). + Where(dao.UserReadRecords.Columns().BookId, in.BookId). + WhereGT(dao.UserReadRecords.Columns().ChapterId, 0). + Count() + if err != nil { + return ecode.Fail.Sub("read_chapter_count_failed") + } + } + + readPercent := 0.0 + if totalChapters > 0 { + readPercent = float64(readChapters) / float64(totalChapters) * 100 + if readPercent > 100 { + readPercent = 100 + } + } + + // 判断是否为最后一章 + lastChapterInfo, err := dao.Chapters.Ctx(ctx).TX(tx). + Where(dao.Chapters.Columns().BookId, in.BookId). + OrderDesc(dao.Chapters.Columns().Sort). + One() + if err != nil { + return ecode.Fail.Sub("chapter_query_failed") + } + + readStatus := 1 // 默认为正在读 + if lastChapterInfo != nil && lastChapterInfo["id"].Int64() == lastChapter.ChapterId { + readStatus = 2 // 如果是最后一章,标记为已读完 + } + + // 更新书架记录 + _, err = dao.Bookshelves.Ctx(ctx).TX(tx). + Where(dao.Bookshelves.Columns().UserId, in.UserId). + Where(dao.Bookshelves.Columns().BookId, in.BookId). + Data(do.Bookshelves{ + LastReadChapterId: lastChapter.ChapterId, + LastReadPercent: readPercent, + LastReadAt: gtime.Now(), + ReadStatus: readStatus, + }).Update() + if err != nil { + return ecode.Fail.Sub("bookshelf_update_failed") + } + } + + return nil + }); err != nil { + return nil, err + } + + out.Success = true + return out, nil +} diff --git a/internal/logic/logic.go b/internal/logic/logic.go index fac6965..5069c3d 100644 --- a/internal/logic/logic.go +++ b/internal/logic/logic.go @@ -5,14 +5,22 @@ package logic import ( + _ "server/internal/logic/ad_event_logs" _ "server/internal/logic/admin" _ "server/internal/logic/author" _ "server/internal/logic/book" + _ "server/internal/logic/book_recommendations" _ "server/internal/logic/bookshelve" _ "server/internal/logic/category" _ "server/internal/logic/chapter" _ "server/internal/logic/feedback" + _ "server/internal/logic/sign_in_reward_details" + _ "server/internal/logic/sign_in_reward_rules" + _ "server/internal/logic/system" + _ "server/internal/logic/task" + _ "server/internal/logic/upload" _ "server/internal/logic/user" _ "server/internal/logic/user_follow_author" - _ "server/internal/logic/user_read_record" + _ "server/internal/logic/user_read_history" + _ "server/internal/logic/user_sign_in_logs" ) diff --git a/internal/logic/sign_in_reward_details/sign_in_reward_details.go b/internal/logic/sign_in_reward_details/sign_in_reward_details.go new file mode 100644 index 0000000..c9d4810 --- /dev/null +++ b/internal/logic/sign_in_reward_details/sign_in_reward_details.go @@ -0,0 +1,93 @@ +package sign_in_reward_details + +import ( + "context" + "server/internal/dao" + "server/internal/model" + "server/internal/model/do" + "server/internal/service" + "server/utility/ecode" +) + +type sSignInRewardDetails struct{} + +func init() { + service.RegisterSignInRewardDetails(New()) +} + +func New() *sSignInRewardDetails { + return &sSignInRewardDetails{} +} + +// Create 新增签到奖励明细 +func (s *sSignInRewardDetails) Create(ctx context.Context, in *model.SignInRewardDetail) (int64, error) { + id, err := dao.SignInRewardDetails.Ctx(ctx).Data(do.SignInRewardDetails{ + RuleId: in.RuleId, + DayNumber: in.DayNumber, + RewardType: in.RewardType, + Quantity: in.Quantity, + Status: in.Status, + }).InsertAndGetId() + if err != nil { + return 0, ecode.New(10001, "sign_in_reward_detail_create_failed") + } + return id, nil +} + +// Update 编辑签到奖励明细 +func (s *sSignInRewardDetails) Update(ctx context.Context, in *model.SignInRewardDetail) error { + _, err := dao.SignInRewardDetails.Ctx(ctx).Data(do.SignInRewardDetails{ + RuleId: in.RuleId, + DayNumber: in.DayNumber, + RewardType: in.RewardType, + Quantity: in.Quantity, + Status: in.Status, + }).Where(do.SignInRewardDetails{Id: in.Id}).Update() + if err != nil { + return ecode.New(10002, "sign_in_reward_detail_update_failed") + } + return nil +} + +// Delete 删除签到奖励明细 +func (s *sSignInRewardDetails) Delete(ctx context.Context, in *model.SignInRewardDetailDeleteIn) (*model.SignInRewardDetailDeleteOut, error) { + _, err := dao.SignInRewardDetails.Ctx(ctx).Where(do.SignInRewardDetails{Id: in.Id}).Delete() + if err != nil { + return &model.SignInRewardDetailDeleteOut{Success: false}, ecode.New(10003, "sign_in_reward_detail_delete_failed") + } + return &model.SignInRewardDetailDeleteOut{Success: true}, nil +} + +// Get 查询单个签到奖励明细 +func (s *sSignInRewardDetails) Get(ctx context.Context, in *model.SignInRewardDetailGetIn) (*model.SignInRewardDetailGetOut, error) { + var detail model.SignInRewardDetail + err := dao.SignInRewardDetails.Ctx(ctx).Where(do.SignInRewardDetails{Id: in.Id}).Scan(&detail) + if err != nil { + return nil, ecode.New(10004, "sign_in_reward_detail_query_failed") + } + if detail.Id == 0 { + return nil, ecode.New(10006, "sign_in_reward_detail_not_found") + } + return &model.SignInRewardDetailGetOut{SignInRewardDetail: detail}, nil +} + +// List 根据 ruleId 查询签到奖励明细列表 +func (s *sSignInRewardDetails) List(ctx context.Context, in *model.SignInRewardDetailListIn) (*model.SignInRewardDetailListOut, error) { + var list []model.SignInRewardDetail + err := dao.SignInRewardDetails.Ctx(ctx).Where(do.SignInRewardDetails{RuleId: in.RuleId}).Order("day_number asc").Scan(&list) + if err != nil { + return nil, ecode.New(10005, "sign_in_reward_detail_query_failed") + } + return &model.SignInRewardDetailListOut{List: list}, nil +} + +// SetStatus 设置签到奖励明细状态 +func (s *sSignInRewardDetails) SetStatus(ctx context.Context, in *model.SignInRewardDetailSetStatusIn) (*model.SignInRewardDetailSetStatusOut, error) { + _, err := dao.SignInRewardDetails.Ctx(ctx).Data(do.SignInRewardDetails{ + Status: in.Status, + }).Where(do.SignInRewardDetails{Id: in.Id}).Update() + if err != nil { + return &model.SignInRewardDetailSetStatusOut{Success: false}, ecode.New(10007, "sign_in_reward_detail_update_failed") + } + return &model.SignInRewardDetailSetStatusOut{Success: true}, nil +} diff --git a/internal/logic/sign_in_reward_rules/sign_in_reward_rules.go b/internal/logic/sign_in_reward_rules/sign_in_reward_rules.go new file mode 100644 index 0000000..5bf2809 --- /dev/null +++ b/internal/logic/sign_in_reward_rules/sign_in_reward_rules.go @@ -0,0 +1,207 @@ +package sign_in_reward_rules + +import ( + "context" + "server/internal/dao" + "server/internal/model" + "server/internal/model/do" + "server/internal/service" + "server/utility/ecode" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/os/gtime" +) + +type sSignInRewardRules struct{} + +func New() service.ISignInRewardRules { + return &sSignInRewardRules{} +} + +func init() { + service.RegisterSignInRewardRules(New()) +} + +// List 获取签到奖励规则列表 +func (s *sSignInRewardRules) List(ctx context.Context, in *model.SignInRewardRulesListIn) (out *model.SignInRewardRulesListOut, err error) { + out = &model.SignInRewardRulesListOut{} + m := dao.SignInRewardRules.Ctx(ctx) + if in.Status != 0 { + m = m.Where(dao.SignInRewardRules.Columns().Status, in.Status) + } + if in.RuleName != "" { + m = m.WhereLike(dao.SignInRewardRules.Columns().RuleName, "%"+in.RuleName+"%") + } + m = m.OrderDesc(dao.SignInRewardRules.Columns().CreatedAt) + if err = m.Page(in.Page, in.Size).WithAll().ScanAndCount(&out.List, &out.Total, false); err != nil { + return nil, ecode.Fail.Sub("sign_in_reward_rule_query_failed") + } + return out, nil +} + +// Create 新增签到奖励规则 +func (s *sSignInRewardRules) Create(ctx context.Context, in *model.SignInRewardRulesCreateIn) (out *model.SignInRewardRulesCRUDOut, err error) { + // 检查同名未删除规则是否已存在 + exist, err := dao.SignInRewardRules.Ctx(ctx). + Where(dao.SignInRewardRules.Columns().RuleName, in.RuleName). + WhereNull(dao.SignInRewardRules.Columns().DeletedAt). + Exist() + if err != nil { + return nil, ecode.Fail.Sub("sign_in_reward_rule_query_failed") + } + if exist { + return nil, ecode.Params.Sub("sign_in_reward_rule_exists") + } + // 开启事务 + err = dao.SignInRewardRules.Ctx(ctx).Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + result, err := dao.SignInRewardRules.Ctx(ctx).TX(tx).Data(do.SignInRewardRules{ + RuleName: in.RuleName, + CycleDays: in.CycleDays, + StartDate: gtime.NewFromStr(in.StartDate), + EndDate: gtime.NewFromStr(in.EndDate), + Status: in.Status, + }).InsertAndGetId() + if err != nil { + return ecode.Fail.Sub("sign_in_reward_rule_create_failed") + } + // 自动插入明细 + var details []do.SignInRewardDetails + for i := 1; i <= in.CycleDays; i++ { + details = append(details, do.SignInRewardDetails{ + RuleId: result, + DayNumber: i, + RewardType: 1, // 默认积分 + Quantity: 0, + Status: 0, // 默认禁用 + }) + } + if len(details) > 0 { + _, err = dao.SignInRewardDetails.Ctx(ctx).TX(tx).Data(details).Insert() + if err != nil { + return ecode.Fail.Sub("sign_in_reward_rule_create_failed") + } + } + return nil + }) + if err != nil { + return nil, err + } + return &model.SignInRewardRulesCRUDOut{Success: true}, nil +} + +// Update 编辑签到奖励规则 +func (s *sSignInRewardRules) Update(ctx context.Context, in *model.SignInRewardRulesUpdateIn) (out *model.SignInRewardRulesCRUDOut, err error) { + // 检查是否存在 + exist, err := dao.SignInRewardRules.Ctx(ctx). + Where(dao.SignInRewardRules.Columns().Id, in.Id). + WhereNull(dao.SignInRewardRules.Columns().DeletedAt). + Exist() + if err != nil { + return nil, ecode.Fail.Sub("sign_in_reward_rule_query_failed") + } + if !exist { + return nil, ecode.NotFound.Sub("sign_in_reward_rule_not_found") + } + // 检查同名未删除规则是否有重复(排除自己) + repeat, err := dao.SignInRewardRules.Ctx(ctx). + Where(dao.SignInRewardRules.Columns().RuleName, in.RuleName). + WhereNull(dao.SignInRewardRules.Columns().DeletedAt). + WhereNot(dao.SignInRewardRules.Columns().Id, in.Id). + Exist() + if err != nil { + return nil, ecode.Fail.Sub("sign_in_reward_rule_query_failed") + } + if repeat { + return nil, ecode.Params.Sub("sign_in_reward_rule_exists") + } + _, err = dao.SignInRewardRules.Ctx(ctx). + Where(dao.SignInRewardRules.Columns().Id, in.Id). + Data(do.SignInRewardRules{ + RuleName: in.RuleName, + CycleDays: in.CycleDays, + StartDate: gtime.NewFromStr(in.StartDate), + EndDate: gtime.NewFromStr(in.EndDate), + Status: in.Status, + }).Update() + if err != nil { + return nil, ecode.Fail.Sub("sign_in_reward_rule_update_failed") + } + return &model.SignInRewardRulesCRUDOut{Success: true}, nil +} + +// Delete 删除签到奖励规则 +func (s *sSignInRewardRules) Delete(ctx context.Context, in *model.SignInRewardRulesDeleteIn) (out *model.SignInRewardRulesCRUDOut, err error) { + // 检查是否存在 + exist, err := dao.SignInRewardRules.Ctx(ctx). + Where(dao.SignInRewardRules.Columns().Id, in.Id). + WhereNull(dao.SignInRewardRules.Columns().DeletedAt). + Exist() + if err != nil { + return nil, ecode.Fail.Sub("sign_in_reward_rule_query_failed") + } + if !exist { + return nil, ecode.NotFound.Sub("sign_in_reward_rule_not_found") + } + _, err = dao.SignInRewardRules.Ctx(ctx). + Where(dao.SignInRewardRules.Columns().Id, in.Id). + Delete() + if err != nil { + return nil, ecode.Fail.Sub("sign_in_reward_rule_delete_failed") + } + return &model.SignInRewardRulesCRUDOut{Success: true}, nil +} + +// SetStatus 设置签到奖励规则状态 +func (s *sSignInRewardRules) SetStatus(ctx context.Context, in *model.SignInRewardRulesSetStatusIn) (out *model.SignInRewardRulesCRUDOut, err error) { + // 检查是否存在 + exist, err := dao.SignInRewardRules.Ctx(ctx). + Where(dao.SignInRewardRules.Columns().Id, in.Id). + WhereNull(dao.SignInRewardRules.Columns().DeletedAt). + Exist() + if err != nil { + return nil, ecode.Fail.Sub("sign_in_reward_rule_query_failed") + } + if !exist { + return nil, ecode.NotFound.Sub("sign_in_reward_rule_not_found") + } + _, err = dao.SignInRewardRules.Ctx(ctx). + Where(dao.SignInRewardRules.Columns().Id, in.Id). + Data(do.SignInRewardRules{ + Status: in.Status, + }).Update() + if err != nil { + return nil, ecode.Fail.Sub("sign_in_reward_rule_update_failed") + } + return &model.SignInRewardRulesCRUDOut{Success: true}, nil +} + +func (s *sSignInRewardRules) SignInList(ctx context.Context, in *model.SignInListIn) (out *model.SignInListOut, err error) { + // 查询活动(只取一条) + out = &model.SignInListOut{} + err = dao.SignInRewardRules.Ctx(ctx).Where(do.SignInRewardRules{Status: 1}).OrderDesc("created_at").WithAll().Limit(1).Scan(&out) + if err != nil { + return nil, err + } + + // 查询用户在该活动下的签到记录 + if in.UserId == 0 { + return + } + userSignLogs, err := dao.UserSignInLogs.Ctx(ctx).Where(do.UserSignInLogs{UserId: in.UserId, RuleId: out.Id}).Fields(dao.UserSignInLogs.Columns().RewardDetailId).Array() + if err != nil { + return nil, err + } + claimedMap := make(map[int64]bool, 0) + for _, log := range userSignLogs { + claimedMap[log.Int64()] = true + } + + // 填充 IsClaimed + for i := range out.List { + if claimedMap[out.List[i].Id] { + out.List[i].IsClaimed = true + } + } + + return out, nil +} diff --git a/internal/logic/system/system.go b/internal/logic/system/system.go new file mode 100644 index 0000000..7bd28ee --- /dev/null +++ b/internal/logic/system/system.go @@ -0,0 +1,50 @@ +package system + +import ( + "context" + "encoding/json" + "github.com/gogf/gf/v2/frame/g" + "server/internal/dao" + "server/internal/model" + "server/internal/service" +) + +type sSystem struct { +} + +func init() { + service.RegisterSystem(New()) +} +func New() service.ISystem { + return &sSystem{} +} +func (*sSystem) Unique(ctx context.Context, in *model.SystemUniqueInput) (*model.SystemOutput, error) { + orm := dao.System.Ctx(ctx) + if in.Key != "" { + orm = orm.Where("`key`=?", in.Key) + } + if in.Lock { + orm = orm.LockUpdate() + } + out := (*model.SystemOutput)(nil) + err := orm.Scan(&out) + return out, err +} +func (s *sSystem) Version(ctx context.Context) (*model.SystemVersionOutput, error) { + system, err := s.Unique(ctx, &model.SystemUniqueInput{ + Key: "version", + }) + if err != nil { + return nil, err + } + var out *model.SystemVersionOutput + err = json.Unmarshal([]byte(system.Value), &out) + return out, err +} + +func (s *sSystem) Save(ctx context.Context, in *model.SystemSaveInput) (err error) { + _, err = dao.System.Ctx(ctx).Update(g.Map{ + "value": in.Value, + }, g.Map{"key": in.Key}) + return +} diff --git a/internal/logic/task/task.go b/internal/logic/task/task.go new file mode 100644 index 0000000..994718c --- /dev/null +++ b/internal/logic/task/task.go @@ -0,0 +1,100 @@ +package task + +import ( + "context" + "server/internal/dao" + "server/internal/model" + "server/internal/model/do" + "server/internal/service" + "server/utility/ecode" +) + +type sTask struct { +} + +func New() service.ITask { + return &sTask{} +} +func init() { + service.RegisterTask(New()) +} +func (s *sTask) List(ctx context.Context, in *model.TaskListIn) (out *model.TaskListOut, err error) { + out = &model.TaskListOut{} + m := dao.Tasks.Ctx(ctx) + if in.Title != "" { + m = m.WhereLike("title", "%"+in.Title+"%") + } + if in.Status != 0 { + m = m.Where("status", in.Status) + } + if err = m.Page(in.Page, in.Size).OrderDesc("id").ScanAndCount(&out.List, &out.Total, false); err != nil { + return nil, ecode.Fail.Sub("task_query_failed") + } + return out, nil +} + +func (s *sTask) Add(ctx context.Context, in *model.TaskAddIn) (out *model.TaskCRUDOut, err error) { + _, err = dao.Tasks.Ctx(ctx).Data(do.Tasks{ + TaskType: in.TaskType, + Title: in.Title, + Description: in.Description, + RewardPoints: in.RewardPoints, + Status: in.Status, + }).Insert() + if err != nil { + return nil, ecode.Fail.Sub("task_add_failed") + } + return &model.TaskCRUDOut{Success: true}, nil +} + +func (s *sTask) Edit(ctx context.Context, in *model.TaskEditIn) (out *model.TaskCRUDOut, err error) { + exist, err := dao.Tasks.Ctx(ctx).WherePri(in.Id).Exist() + if err != nil { + return nil, ecode.Fail.Sub("task_query_failed") + } + if !exist { + return nil, ecode.NotFound.Sub("task_not_found") + } + _, err = dao.Tasks.Ctx(ctx).WherePri(in.Id).Data(do.Tasks{ + TaskType: in.TaskType, + Title: in.Title, + Description: in.Description, + RewardPoints: in.RewardPoints, + Status: in.Status, + }).Update() + if err != nil { + return nil, ecode.Fail.Sub("task_edit_failed") + } + return &model.TaskCRUDOut{Success: true}, nil +} + +func (s *sTask) Delete(ctx context.Context, in *model.TaskDelIn) (out *model.TaskCRUDOut, err error) { + exist, err := dao.Tasks.Ctx(ctx).WherePri(in.Id).Exist() + if err != nil { + return nil, ecode.Fail.Sub("task_query_failed") + } + if !exist { + return nil, ecode.NotFound.Sub("task_not_found") + } + _, err = dao.Tasks.Ctx(ctx).WherePri(in.Id).Delete() + if err != nil { + return nil, ecode.Fail.Sub("task_delete_failed") + } + return &model.TaskCRUDOut{Success: true}, nil +} + +// AppList 获取任务列表(只返回任务类型和奖励积分) +func (s *sTask) AppList(ctx context.Context, in *model.TaskAppListIn) (out *model.TaskSimpleListOut, err error) { + out = &model.TaskSimpleListOut{} + + // 只查询任务类型和奖励积分字段 + if err = dao.Tasks.Ctx(ctx). + Fields("task_type, reward_points"). + Where("status", 1). + OrderDesc("id"). + Scan(&out.List); err != nil { + return nil, ecode.Fail.Sub("task_query_failed") + } + + return out, nil +} diff --git a/internal/logic/upload/upload.go b/internal/logic/upload/upload.go new file mode 100644 index 0000000..d6c6bb4 --- /dev/null +++ b/internal/logic/upload/upload.go @@ -0,0 +1,66 @@ +package upload + +import ( + "context" + "net/http" + "server/internal/model" + "server/internal/service" + "server/utility/ecode" + "server/utility/oss" + + "github.com/gogf/gf/v2/errors/gerror" +) + +type sUpload struct{} + +func New() service.IUpload { + return &sUpload{} +} +func init() { + service.RegisterUpload(New()) +} +func (s *sUpload) UploadImage(ctx context.Context, in *model.UploadImageIn) (*model.UploadImageOut, error) { + if in.File == nil { + return nil, gerror.NewCode(ecode.Fail.Sub("image_file_required").Code()) + } + // 校验文件类型(只允许图片) + contentType := in.File.Header.Get("Content-Type") + if contentType == "" { + f, err := in.File.Open() + if err != nil { + return nil, gerror.NewCode(ecode.Fail.Sub("image_read_failed").Code()) + } + defer f.Close() + buf := make([]byte, 512) + _, err = f.Read(buf) + if err != nil { + return nil, gerror.NewCode(ecode.Fail.Sub("image_read_failed").Code()) + } + contentType = http.DetectContentType(buf) + } + if len(contentType) < 6 || contentType[:6] != "image/" { + return nil, gerror.NewCode(ecode.Fail.Sub("image_type_invalid").Code()) + } + allowedTypes := map[string]bool{ + "image/jpeg": true, + "image/png": true, + "image/gif": true, + "image/webp": true, + } + if !allowedTypes[contentType] { + return nil, gerror.NewCode(ecode.Fail.Sub("image_format_invalid").Code()) + } + // 校验文件大小(不超过1MB) + if in.File.Size > 1*1024*1024 { + return nil, gerror.NewCode(ecode.Fail.Sub("image_size_exceeded").Code()) + } + client := oss.GetOSSClient("amazon_s3") + if client == nil { + return nil, gerror.NewCode(ecode.Fail.Code()) + } + url, err := client.Upload(in.File, in.Type) + if err != nil { + return nil, err + } + return &model.UploadImageOut{ImageUrl: url}, nil +} diff --git a/internal/logic/user/user.go b/internal/logic/user/user.go index 4401364..846c57d 100644 --- a/internal/logic/user/user.go +++ b/internal/logic/user/user.go @@ -90,12 +90,26 @@ func (s *sUser) Info(ctx context.Context, in *model.UserInfoIn) (out *model.User if err = user.Struct(&entityUser); err != nil { return nil, ecode.Fail.Sub("data_conversion_failed") } + // 查询作者信息 + var authorStatus int + isAuthor := false + author, err := dao.Authors.Ctx(ctx).Where(do.Authors{UserId: entityUser.Id}).One() + if err == nil && !author.IsEmpty() { + authorStatus = author[dao.Authors.Columns().Status].Int() + if authorStatus == 1 { + isAuthor = true + } + } return &model.UserInfoOut{ - UserId: entityUser.Id, - Username: entityUser.Username, - Email: entityUser.Email, - Avatar: entityUser.Avatar, - Points: entityUser.Points, // 如有积分表可补充 + Id: entityUser.Id, + Username: entityUser.Username, + Email: entityUser.Email, + Avatar: entityUser.Avatar, + Points: entityUser.Points, // 如有积分表可补充 + BackgroundUrl: entityUser.BackgroundUrl, + AttentionCount: entityUser.AttentionCount, + IsAuthor: isAuthor, + AuthorStatus: authorStatus, }, nil } @@ -246,3 +260,33 @@ func (s *sUser) EditPass(ctx context.Context, in *model.UserEditPassIn) (out *mo Success: true, }, nil } + +func (s *sUser) AuthorLogin(ctx context.Context, in *model.UserLoginIn) (out *model.UserLoginOut, err error) { + user, err := dao.Users.Ctx(ctx).Where(do.Users{Email: in.Email}).One() + if err != nil { + return nil, ecode.Fail.Sub("database_query_failed") + } + if user == nil { + return nil, ecode.Auth // 账户名或密码不正确 + } + var entityUser entity.Users + if err = user.Struct(&entityUser); err != nil { + return nil, ecode.Fail.Sub("data_conversion_failed") + } + if !encrypt.ComparePassword(entityUser.PasswordHash, in.Password) { + return nil, ecode.Password // 密码不正确 + } + // 验证是否为作者 + author, err := dao.Authors.Ctx(ctx).Where(do.Authors{UserId: entityUser.Id, Status: 1}).One() + if err != nil { + return nil, ecode.Fail.Sub("author_query_failed") + } + if author == nil || author.IsEmpty() { + return nil, ecode.Auth.Sub("not_author") // 不是作者 + } + token, err := jwt.GenerateToken(&jwt.TokenIn{UserId: entityUser.Id, Role: "author"}) + if err != nil { + return nil, ecode.Fail.Sub("token_generation_failed") + } + return &model.UserLoginOut{Token: token}, nil +} diff --git a/internal/logic/user_follow_author/user_follow_author.go b/internal/logic/user_follow_author/user_follow_author.go index c67407e..ae59b82 100644 --- a/internal/logic/user_follow_author/user_follow_author.go +++ b/internal/logic/user_follow_author/user_follow_author.go @@ -7,6 +7,8 @@ import ( "server/internal/model/do" "server/internal/service" "server/utility/ecode" + + "github.com/gogf/gf/v2/database/gdb" ) type sUserFollowAuthor struct{} @@ -36,63 +38,82 @@ func (s *sUserFollowAuthor) List(ctx context.Context, in *model.UserFollowAuthor // Create adds a new user follow author func (s *sUserFollowAuthor) Create(ctx context.Context, in *model.UserFollowAuthorAddIn) (out *model.UserFollowAuthorCRUDOut, err error) { - exist, err := dao.UserFollowAuthors.Ctx(ctx). - Where(dao.UserFollowAuthors.Columns().UserId, in.UserId). - Where(dao.UserFollowAuthors.Columns().AuthorId, in.AuthorId). - Exist() - if err != nil { - return nil, ecode.Fail.Sub("user_follow_author_query_failed") - } - if exist { - return nil, ecode.Params.Sub("user_follow_author_exists") - } - if _, err := dao.UserFollowAuthors.Ctx(ctx).Data(do.UserFollowAuthors{ - UserId: in.UserId, - AuthorId: in.AuthorId, - }).Insert(); err != nil { - return nil, ecode.Fail.Sub("user_follow_author_create_failed") - } - return &model.UserFollowAuthorCRUDOut{Success: true}, nil -} + err = dao.UserFollowAuthors.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + exist, err := dao.UserFollowAuthors.Ctx(ctx).TX(tx). + Where(dao.UserFollowAuthors.Columns().UserId, in.UserId). + Where(dao.UserFollowAuthors.Columns().AuthorId, in.AuthorId). + Exist() + if err != nil { + return ecode.Fail.Sub("user_follow_author_query_failed") + } + if exist { + return ecode.Params.Sub("user_follow_author_exists") + } + if _, err := dao.UserFollowAuthors.Ctx(ctx).TX(tx).Data(do.UserFollowAuthors{ + UserId: in.UserId, + AuthorId: in.AuthorId, + }).Insert(); err != nil { + return ecode.Fail.Sub("user_follow_author_create_failed") + } -// Delete removes a user follow author by id -func (s *sUserFollowAuthor) Delete(ctx context.Context, in *model.UserFollowAuthorDelIn) (out *model.UserFollowAuthorCRUDOut, err error) { - exist, err := dao.UserFollowAuthors.Ctx(ctx). - WherePri(in.Id). - Exist() + // 作者被关注数 +1 + if _, err := dao.Authors.Ctx(ctx).TX(tx). + Where(dao.Authors.Columns().Id, in.AuthorId). + Increment(dao.Authors.Columns().FollowerCount, 1); err != nil { + return ecode.Fail.Sub("author_follow_count_update_failed") + } + // 用户关注数 +1 + if _, err := dao.Users.Ctx(ctx).TX(tx). + Where(dao.Users.Columns().Id, in.UserId). + Increment(dao.Users.Columns().AttentionCount, 1); err != nil { + return ecode.Fail.Sub("user_attention_count_update_failed") + } + return nil + }) if err != nil { - return nil, ecode.Fail.Sub("user_follow_author_query_failed") - } - if !exist { - return nil, ecode.NotFound.Sub("user_follow_author_not_found") - } - _, err = dao.UserFollowAuthors.Ctx(ctx).WherePri(in.Id).Delete() - if err != nil { - return nil, ecode.Fail.Sub("user_follow_author_delete_failed") + return nil, err } + return &model.UserFollowAuthorCRUDOut{Success: true}, nil } // Unfollow removes a user follow author by userId and authorId func (s *sUserFollowAuthor) Unfollow(ctx context.Context, userId int64, authorId int64) (out *model.UserFollowAuthorCRUDOut, err error) { - if userId == 0 || authorId == 0 { - return nil, ecode.Params.Sub("user_id_or_author_id_invalid") - } - // 查找关注记录 - var record struct{ Id int64 } - err = dao.UserFollowAuthors.Ctx(ctx). - Where(dao.UserFollowAuthors.Columns().UserId, userId). - Where(dao.UserFollowAuthors.Columns().AuthorId, authorId). - Fields("id").Scan(&record) + err = dao.UserFollowAuthors.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + if userId == 0 || authorId == 0 { + return ecode.Params.Sub("user_id_or_author_id_invalid") + } + // 查找关注记录 + result, err := dao.UserFollowAuthors.Ctx(ctx).TX(tx). + Where(dao.UserFollowAuthors.Columns().UserId, userId). + Where(dao.UserFollowAuthors.Columns().AuthorId, authorId). + Delete() + if err != nil { + return ecode.Fail.Sub("user_follow_author_delete_failed") + } + affected, err := result.RowsAffected() + if err != nil { + return ecode.Fail.Sub("user_follow_author_delete_failed") + } + if affected == 0 { + return ecode.NotFound.Sub("user_follow_author_not_found") + } + // 作者被关注数 -1 + if _, err := dao.Authors.Ctx(ctx).TX(tx). + Where(dao.Authors.Columns().Id, authorId). + Decrement(dao.Authors.Columns().FollowerCount, 1); err != nil { + return ecode.Fail.Sub("author_follow_count_update_failed") + } + // 用户关注数 -1 + if _, err := dao.Users.Ctx(ctx).TX(tx). + Where(dao.Users.Columns().Id, userId). + Decrement(dao.Users.Columns().AttentionCount, 1); err != nil { + return ecode.Fail.Sub("user_attention_count_update_failed") + } + return nil + }) if err != nil { - return nil, ecode.Fail.Sub("user_follow_author_query_failed") - } - if record.Id == 0 { - return nil, ecode.NotFound.Sub("user_follow_author_not_found") - } - _, err = dao.UserFollowAuthors.Ctx(ctx).WherePri(record.Id).Delete() - if err != nil { - return nil, ecode.Fail.Sub("user_follow_author_delete_failed") + return nil, err } return &model.UserFollowAuthorCRUDOut{Success: true}, nil } diff --git a/internal/logic/user_read_history/user_read_history.go b/internal/logic/user_read_history/user_read_history.go new file mode 100644 index 0000000..d51a3e4 --- /dev/null +++ b/internal/logic/user_read_history/user_read_history.go @@ -0,0 +1,36 @@ +package userreadhistory + +import ( + "context" + "server/internal/dao" + "server/internal/model" + "server/internal/service" + "server/utility/ecode" +) + +type sUserReadHistory struct{} + +func New() service.IUserReadHistory { + return &sUserReadHistory{} +} + +func init() { + service.RegisterUserReadHistory(New()) +} + +func (s *sUserReadHistory) Remove(ctx context.Context, in *model.UserReadHistoryDelIn) (out *model.UserReadHistoryCRUDOut, err error) { + // 参数校验 + if in.UserId == 0 || len(in.BookIds) == 0 { + return nil, ecode.Params.Sub("user_read_history_remove_param_invalid") + } + + _, err = dao.UserReadHistory.Ctx(ctx). + Where(dao.UserReadHistory.Columns().UserId, in.UserId). + WhereIn(dao.UserReadHistory.Columns().BookId, in.BookIds). + Delete() + if err != nil { + return nil, ecode.Fail.Sub("user_read_history_remove_failed") + } + + return &model.UserReadHistoryCRUDOut{Success: true}, nil +} diff --git a/internal/logic/user_read_record/user_read_record.go b/internal/logic/user_read_record/user_read_record.go deleted file mode 100644 index c65de66..0000000 --- a/internal/logic/user_read_record/user_read_record.go +++ /dev/null @@ -1,97 +0,0 @@ -package user_read_record - -import ( - "context" - "server/internal/dao" - "server/internal/model" - "server/internal/model/do" - "server/internal/service" - "server/utility/ecode" - - "github.com/gogf/gf/v2/os/gtime" -) - -type sUserReadRecord struct{} - -func New() service.IUserReadRecord { - return &sUserReadRecord{} -} -func init() { - service.RegisterUserReadRecord(New()) -} - -// List retrieves a paginated list of user read records -func (s *sUserReadRecord) List(ctx context.Context, in *model.UserReadRecordListIn) (out *model.UserReadRecordListOut, err error) { - out = &model.UserReadRecordListOut{} - m := dao.UserReadRecords.Ctx(ctx) - if in.UserId != 0 { - m = m.Where(dao.UserReadRecords.Columns().UserId, in.UserId) - } - if in.BookId != 0 { - m = m.Where(dao.UserReadRecords.Columns().BookId, in.BookId) - } - if in.ChapterId != 0 { - m = m.Where(dao.UserReadRecords.Columns().ChapterId, in.ChapterId) - } - if err = m.Page(in.Page, in.Size).ScanAndCount(&out.List, &out.Total, false); err != nil { - return - } - return -} - -// Create adds a new user read record -func (s *sUserReadRecord) Create(ctx context.Context, in *model.UserReadRecordAddIn) (out *model.UserReadRecordCRUDOut, err error) { - // 检查是否已存在相同的阅读记录 - exist, err := dao.UserReadRecords.Ctx(ctx). - Where(dao.UserReadRecords.Columns().UserId, in.UserId). - Where(dao.UserReadRecords.Columns().BookId, in.BookId). - Where(dao.UserReadRecords.Columns().ChapterId, in.ChapterId). - Exist() - - if err != nil { - return nil, ecode.Fail.Sub("read_record_query_failed") - } - - if exist { - // 如果记录已存在,更新进度和时间 - _, err = dao.UserReadRecords.Ctx(ctx). - Where(dao.UserReadRecords.Columns().UserId, in.UserId). - Where(dao.UserReadRecords.Columns().BookId, in.BookId). - Where(dao.UserReadRecords.Columns().ChapterId, in.ChapterId). - Data(do.UserReadRecords{ - Progress: in.Progress, - ReadAt: gtime.Now(), - }).Update() - } else { - // 创建新记录 - _, err = dao.UserReadRecords.Ctx(ctx).Data(do.UserReadRecords{ - UserId: in.UserId, - BookId: in.BookId, - ChapterId: in.ChapterId, - Progress: in.Progress, - ReadAt: gtime.Now(), - }).Insert() - } - - if err != nil { - return nil, ecode.Fail.Sub("read_record_create_failed") - } - return &model.UserReadRecordCRUDOut{Success: true}, nil -} - -// Delete removes user read records by userId and bookIds -func (s *sUserReadRecord) Delete(ctx context.Context, in *model.UserReadRecordDelIn) (out *model.UserReadRecordCRUDOut, err error) { - if len(in.BookIds) == 0 { - return nil, ecode.Params.Sub("bookshelve_bookids_empty") - } - - // 批量删除指定用户的指定书籍历史记录 - _, err = dao.UserReadRecords.Ctx(ctx). - Where(dao.UserReadRecords.Columns().UserId, in.UserId). - WhereIn(dao.UserReadRecords.Columns().BookId, in.BookIds). - Delete() - if err != nil { - return nil, ecode.Fail.Sub("read_record_delete_failed") - } - return &model.UserReadRecordCRUDOut{Success: true}, nil -} diff --git a/internal/logic/user_sign_in_logs/user_sign_in_logs.go b/internal/logic/user_sign_in_logs/user_sign_in_logs.go new file mode 100644 index 0000000..3ce0cd6 --- /dev/null +++ b/internal/logic/user_sign_in_logs/user_sign_in_logs.go @@ -0,0 +1,86 @@ +package user_sign_in_logs + +import ( + "context" + "server/internal/dao" + "server/internal/model" + "server/internal/model/do" + "server/internal/service" + "server/utility/ecode" + + "github.com/gogf/gf/v2/database/gdb" +) + +type sUserSignInLogs struct{} + +func New() service.IUserSignInLogs { + return &sUserSignInLogs{} +} +func init() { + service.RegisterUserSignInLogs(New()) +} + +// Sign 用户签到,只允许每日唯一签到 +func (s *sUserSignInLogs) Sign(ctx context.Context, in *model.UserSignInLogSignIn) (*model.UserSignInLogSignOut, error) { + // 事务前查询奖励明细 + detail, err := dao.SignInRewardDetails.Ctx(ctx).Where(do.SignInRewardDetails{Id: in.RewardDetailId}).One() + if err != nil { + return nil, ecode.New(10014, "sign_in_reward_detail_query_failed") + } + if detail == nil || detail.IsEmpty() { + return nil, ecode.New(10015, "sign_in_reward_detail_not_found") + } + quantity := detail["quantity"].Int() + + var success bool + err = dao.UserSignInLogs.Ctx(ctx).Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + // 检查是否已签到 + count, err := dao.UserSignInLogs.Ctx(ctx).TX(tx).Where(do.UserSignInLogs{ + UserId: in.UserId, + RuleId: in.RuleId, + SignInDate: in.SignInDate, + DeletedAt: nil, + }).Count() + if err != nil { + return ecode.New(10010, "user_sign_in_log_query_failed") + } + if count > 0 { + success = false + return nil + } + // 插入签到记录 + result, err := dao.UserSignInLogs.Ctx(ctx).TX(tx).Data(do.UserSignInLogs{ + UserId: in.UserId, + RuleId: in.RuleId, + RewardDetailId: in.RewardDetailId, + SignInDate: in.SignInDate, + Quantity: quantity, + Status: 1, + }).InsertAndGetId() + if err != nil { + return ecode.New(10011, "user_sign_in_log_create_failed") + } + // 插入积分日志 + _, err = dao.UserPointsLogs.Ctx(ctx).TX(tx).Data(do.UserPointsLogs{ + UserId: in.UserId, + ChangeType: 2, // 2=收入(earn) + PointsChange: quantity, + RelatedOrderId: result, + Description: "签到奖励", + }).Insert() + if err != nil { + return ecode.New(10012, "user_points_log_create_failed") + } + // 更新用户积分 + _, err = dao.Users.Ctx(ctx).TX(tx).Where(do.Users{Id: in.UserId}).Increment("points", quantity) + if err != nil { + return ecode.New(10013, "user_points_update_failed") + } + success = true + return nil + }) + if err != nil { + return nil, err + } + return &model.UserSignInLogSignOut{Success: success}, nil +} diff --git a/internal/model/admin.go b/internal/model/admin.go index 9c15b71..9408016 100644 --- a/internal/model/admin.go +++ b/internal/model/admin.go @@ -16,8 +16,9 @@ type AdminInfoIn struct { } type AdminInfoOut struct { - AdminId int64 // 管理员ID + Id int64 // 管理员ID Username string // 用户名 + Role string // 角色类型 } type AdminEditPassIn struct { diff --git a/internal/model/ads.go b/internal/model/ads.go new file mode 100644 index 0000000..f46b97f --- /dev/null +++ b/internal/model/ads.go @@ -0,0 +1,49 @@ +package model + +import ( + "server/internal/consts" + + "github.com/gogf/gf/v2/os/gtime" +) + +// AdsUploadIn 广告数据上传输入参数 +type AdsUploadIn struct { + UserId int64 + NodeUid string + DeviceCode string + Data string +} + +// AdsUploadOut 广告数据上传输出参数 +type AdsUploadOut struct { + Success bool `json:"success" dc:"是否成功"` +} + +// AdsData 广告数据结构 +type AdsData struct { + AdsPlatId int `json:"ads_plat_id"` // 平台ID:1-META,2-ADMOB + AdsCategoryId int `json:"ads_category_id"` // 广告分类:1-横幅广告,2-插页式广告,3-激励插页式广告,4-激励广告,5-原生广告,6-开屏广告 + AppPackage string `json:"app_package"` // APP包名 + Status consts.AdState `json:"status"` // 状态:1-拉取失败,2-拉取成功,3-显示失败,4-显示成功,5-未观看完成,6-观看完成,7-未点击,8-已点击,9-未下载,10-已下载 +} + +// GetAdLifecycleIn 获取广告生命周期输入参数 +type GetAdLifecycleIn struct { + UserId int64 + AdsPlatId int + AdsCategoryId int + AppPackage string +} + +// GetAdLifecycleOut 获取广告生命周期输出参数 +type GetAdLifecycleOut struct { + Records []*AdLifecycleRecord `json:"records" dc:"广告生命周期记录"` +} + +// AdLifecycleRecord 广告生命周期记录 +type AdLifecycleRecord struct { + EventId int64 `json:"event_id" dc:"事件ID"` + Status consts.AdState `json:"status" dc:"状态"` + StatusDesc string `json:"status_desc" dc:"状态描述"` + CreatedAt *gtime.Time `json:"created_at" dc:"创建时间"` +} diff --git a/internal/model/author.go b/internal/model/author.go index 6dcbe74..18c1546 100644 --- a/internal/model/author.go +++ b/internal/model/author.go @@ -18,7 +18,7 @@ type AuthorListIn struct { } type AuthorListOut struct { Total int - List []Author + List []AuthorListItem } type AuthorAddIn struct { @@ -41,6 +41,7 @@ type AuthorCRUDOut struct { } type AuthorApplyIn struct { + UserId int64 PenName string // 笔名 Bio string // 作者简介 } @@ -51,10 +52,50 @@ type AuthorDetailIn struct { AuthorId int64 } type AuthorDetailOut struct { - g.Meta `orm:"table:authors"` - Id int64 `json:"id" orm:"id"` - UserId int64 `json:"userId" orm:"user_id"` - PenName string `json:"penName" orm:"pen_name"` - Bio string `json:"bio" orm:"bio"` - User User `json:"user" orm:"with:id = user_id"` + g.Meta `orm:"table:authors"` + Id int64 `json:"id" orm:"id"` // 作者ID author id + UserId int64 `json:"userId" orm:"user_id"` // 用户ID user id + PenName string `json:"penName" orm:"pen_name"` // 笔名 pen name + Bio string `json:"bio" orm:"bio"` // 作者简介 author bio + FollowerCount int `json:"followerCount" orm:"follower_count"` // 粉丝数量 follower count + User User `json:"user" orm:"with:id = user_id"` // 用户信息 user info + WorksCount int `json:"worksCount" orm:"-"` // 作品数量 works count + IsFollowed bool `json:"isFollowed" orm:"-"` // 是否已关注 is followed +} + +type AuthorListItem struct { + g.Meta `orm:"table:authors"` + Id int64 `json:"id"` + UserId int64 `json:"userId"` + User User `json:"user" orm:"with:id = user_id"` + PenName string `json:"penName"` + Bio string `json:"bio"` + FollowerCount int `json:"followerCount"` + Status int `json:"status"` +} + +type AuthorInfoIn struct { + UserId int64 // 用户ID +} + +type AuthorInfoOut struct { + Id int64 `json:"id"` // 作者ID + PenName string `json:"penName"` // 笔名 + Role string `json:"role"` // 角色 +} + +// 审核作者申请入参 +// AuthorReviewIn 用于管理员审核作者申请 +// Status: 1=通过,3=拒绝 +// Remark 可选,记录审核意见 +type AuthorReviewIn struct { + AuthorId int64 // 作者ID + Status int // 审核状态 1=通过,2=拒绝 + Remark string // 审核备注(可选) +} + +// 审核作者申请出参 +// AuthorReviewOut 返回审核是否成功 +type AuthorReviewOut struct { + Success bool } diff --git a/internal/model/book.go b/internal/model/book.go index 3e45b8f..9421aa4 100644 --- a/internal/model/book.go +++ b/internal/model/book.go @@ -25,8 +25,16 @@ type Book struct { Tags string `json:"tags" orm:"tags"` IsRecommended int `json:"isRecommended" orm:"is_recommended"` IsFeatured int `json:"isFeatured" orm:"is_featured"` + IsHot int `json:"isHot" orm:"is_hot"` Language string `json:"language" orm:"language"` } +type SimpleBook struct { + g.Meta `orm:"table:books"` + Id int64 `json:"id" orm:"id"` + AuthorId int64 `json:"authorId" orm:"author_id"` + Author Author `json:"author" orm:"with:id = author_id"` + Title string `json:"title" orm:"title"` +} // App作者信息结构体 type AppAuthor struct { @@ -112,10 +120,12 @@ type BookAppItem struct { CurrentReaders int64 `json:"currentReaders" dc:"在读人数" orm:"current_readers"` Tags string `json:"tags" dc:"标签" orm:"tags"` IsRecommended int `json:"isRecommended" dc:"是否推荐" orm:"is_recommended"` + IsHot int `json:"isHot" dc:"是否热门" orm:"is_hot"` CreatedAt *gtime.Time `json:"createdAt" dc:"创建时间" orm:"created_at"` UpdatedAt *gtime.Time `json:"updatedAt" dc:"更新时间" orm:"updated_at"` HasRated bool `json:"hasRated" dc:"当前用户是否已评分"` MyRating float64 `json:"myRating" dc:"当前用户评分(未评分为0)"` + IsInBookshelf bool `json:"isInBookshelf" dc:"当前用户是否已加入书架"` } // App 书籍列表查询输入参数 @@ -129,6 +139,7 @@ type BookAppListIn struct { UserId int64 `json:"userId" dc:"用户ID"` AuthorId int `json:"authorId" dc:"作者ID"` IsFeatured bool `json:"isFeatured" dc:"是否精选"` + IsHot bool `json:"isHot" dc:"是否热门"` Language string `json:"language" dc:"语言"` Sort string `json:"sort" dc:"排序字段"` } @@ -147,25 +158,34 @@ type BookAppDetailIn struct { // App 书籍详情输出结构体 type BookAppDetailOut struct { - Id int64 `json:"id" dc:"书籍ID"` - AuthorId int64 `json:"authorId" dc:"作者ID"` - CategoryId int64 `json:"categoryId" dc:"分类ID"` - Title string `json:"title" dc:"书名"` - CoverUrl string `json:"coverUrl" dc:"封面图"` - Description string `json:"description" dc:"简介"` - Status int `json:"status" dc:"状态"` - Tags string `json:"tags" dc:"标签"` - IsRecommended int `json:"isRecommended" dc:"是否推荐"` - IsFeatured int `json:"isFeatured" dc:"是否精选"` - Language string `json:"language" dc:"语言"` - Rating float64 `json:"rating" dc:"评分"` - CurrentReaders int64 `json:"currentReaders" dc:"在读人数"` - CreatedAt *gtime.Time `json:"createdAt" dc:"创建时间"` - UpdatedAt *gtime.Time `json:"updatedAt" dc:"更新时间"` - HasRead bool `json:"hasRead" dc:"是否读过"` - ReadProgress int `json:"readProgress" dc:"阅读进度百分比"` - LastChapterId int64 `json:"lastChapterId" dc:"最近阅读章节ID"` - LastReadAt string `json:"lastReadAt" dc:"最近阅读时间"` + Id int64 `json:"id" dc:"书籍ID" orm:"id"` + CoverUrl string `json:"coverUrl" dc:"封面图" orm:"cover_url"` + Rating float64 `json:"rating" dc:"评分" orm:"rating"` + Title string `json:"title" dc:"标题" orm:"title"` + Description string `json:"description" dc:"简介" orm:"description"` + AuthorId int64 `json:"authorId" dc:"作者ID" orm:"author_id"` + Author AppAuthor `json:"author" dc:"作者信息" orm:"with:id = author_id"` + IsFeatured int `json:"isFeatured" dc:"是否精选" orm:"is_featured"` + Language string `json:"language" dc:"语言" orm:"language"` + CategoryId int64 `json:"categoryId" dc:"分类ID" orm:"category_id"` + Category Category `json:"category" dc:"分类信息" orm:"with:id = category_id"` + Status int `json:"status" dc:"状态" orm:"status"` + WordsCount int `json:"wordsCount" dc:"字数" orm:"words_count"` + ChaptersCount int `json:"chaptersCount" dc:"章节数" orm:"chapters_count"` + ReadCount int64 `json:"readCount" dc:"阅读人数" orm:"read_count"` + CurrentReaders int64 `json:"currentReaders" dc:"在读人数" orm:"current_readers"` + Tags string `json:"tags" dc:"标签" orm:"tags"` + IsRecommended int `json:"isRecommended" dc:"是否推荐" orm:"is_recommended"` + IsHot int `json:"isHot" dc:"是否热门" orm:"is_hot"` + CreatedAt *gtime.Time `json:"createdAt" dc:"创建时间" orm:"created_at"` + UpdatedAt *gtime.Time `json:"updatedAt" dc:"更新时间" orm:"updated_at"` + HasRated bool `json:"hasRated" dc:"当前用户是否已评分"` + MyRating float64 `json:"myRating" dc:"当前用户评分(未评分为0)"` + HasRead bool `json:"hasRead" dc:"是否读过"` + ReadProgress int `json:"readProgress" dc:"阅读进度百分比"` + LastChapterId int64 `json:"lastChapterId" dc:"最近阅读章节ID"` + LastReadAt string `json:"lastReadAt" dc:"最近阅读时间"` + IsInBookshelf bool `json:"isInBookshelf" dc:"当前用户是否已加入书架"` } // App 用户评分输入参数 @@ -207,16 +227,20 @@ type BookRatingCRUDOut struct { // 我的书籍列表项 // ============================= type MyBookItem struct { - Id int64 `json:"id"` - Title string `json:"title"` - CoverUrl string `json:"coverUrl"` - Description string `json:"description"` - Progress int `json:"progress" dc:"阅读进度百分比"` - IsInShelf bool `json:"isInShelf" dc:"是否在书架"` - LastReadAt string `json:"lastReadAt" dc:"最近阅读时间"` - Status int `json:"status" dc:"书籍状态"` - AuthorId int64 `json:"authorId"` - CategoryId int64 `json:"categoryId"` + g.Meta `orm:"table:books"` + Id int64 `json:"id"` + Title string `json:"title"` + CoverUrl string `json:"coverUrl"` + Description string `json:"description"` + Progress int `json:"progress" dc:"阅读进度百分比"` + IsInShelf bool `json:"isInShelf" dc:"是否在书架"` + LastReadAt string `json:"lastReadAt" dc:"最近阅读时间"` + Status int `json:"status" dc:"书籍状态"` + AuthorId int64 `json:"authorId"` + CategoryId int64 `json:"categoryId"` + Rating float64 `json:"rating" dc:"评分"` + HasRated bool `json:"hasRated" dc:"当前用户是否已评分"` + MyRating float64 `json:"myRating" dc:"当前用户评分(未评分为0)"` } // 我的书籍列表查询参数 @@ -248,3 +272,8 @@ type BookSetRecommendedIn struct { Id int64 `json:"id" dc:"书籍ID"` IsRecommended int `json:"isRecommended" dc:"是否推荐"` } + +type BookSetHotIn struct { + Id int64 `json:"id" dc:"书籍ID"` + IsHot int `json:"isHot" dc:"是否热门"` +} diff --git a/internal/model/book_recommendations.go b/internal/model/book_recommendations.go new file mode 100644 index 0000000..0907298 --- /dev/null +++ b/internal/model/book_recommendations.go @@ -0,0 +1,76 @@ +package model + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gtime" +) + +type BookRecommendation struct { + g.Meta `orm:"table:book_recommendations"` + Id int64 `json:"id" orm:"id"` + BookId int64 `json:"bookId" orm:"book_id"` + Book SimpleBook `json:"book" orm:"with:id=book_id"` + Type int `json:"type" orm:"type"` // 推荐类型:1=首页Banner,2=编辑推荐,3=分类推荐等 + CoverUrl string `json:"coverUrl" orm:"cover_url"` + SortOrder int `json:"sortOrder" orm:"sort_order"` + Status int `json:"status" orm:"status"` // 1=启用,0=禁用 + CreatedAt *gtime.Time `json:"createdAt" orm:"created_at"` + UpdatedAt *gtime.Time `json:"updatedAt" orm:"updated_at"` + DeletedAt *gtime.Time `json:"deletedAt" orm:"deleted_at"` +} + +type BookRecommendationsListIn struct { + Page int `json:"page"` + Size int `json:"size"` + Type int `json:"type"` + Status int `json:"status"` + BookId int64 `json:"bookId"` +} +type BookRecommendationsListOut struct { + Total int `json:"total"` + List []BookRecommendation `json:"list"` +} + +type BookRecommendationsCreateIn struct { + BookId int64 `json:"bookId"` + Type int `json:"type"` + CoverUrl string `json:"coverUrl"` + SortOrder int `json:"sortOrder"` + Status int `json:"status"` +} +type BookRecommendationsUpdateIn struct { + Id int64 `json:"id"` + BookId int64 `json:"bookId"` + Type int `json:"type"` + CoverUrl string `json:"coverUrl"` + SortOrder int `json:"sortOrder"` + Status int `json:"status"` +} +type BookRecommendationsDeleteIn struct { + Id int64 `json:"id"` +} +type BookRecommendationsCRUDOut struct { + Success bool `json:"success"` +} +type BookRecommendationsSetStatusIn struct { + Id int64 `json:"id"` + Status int `json:"status"` +} +type BookRecommendationsSortOrderIn struct { + Id int64 `json:"id"` + SortOrder int `json:"sortOrder"` +} + +// App 推荐简要结构体 +// 只用于 AppList 返回 +type RecommendAppItem struct { + Id int64 `json:"id" dc:"推荐ID"` + BookId int64 `json:"bookId" dc:"书籍ID"` + CoverUrl string `json:"coverUrl" dc:"推荐封面图"` + SortOrder int `json:"sortOrder" dc:"顺序"` +} + +type BookRecommendationsAppListOut struct { + Total int `json:"total"` + List []RecommendAppItem `json:"list"` +} diff --git a/internal/model/chapter.go b/internal/model/chapter.go index de10771..910d7a5 100644 --- a/internal/model/chapter.go +++ b/internal/model/chapter.go @@ -77,6 +77,7 @@ type ChapterAppItem struct { UpdatedAt *gtime.Time `json:"updatedAt" orm:"updated_at" dc:"更新时间"` ReadProgress int `json:"readProgress" dc:"阅读进度百分比"` ReadAt *gtime.Time `json:"readAt" dc:"最后阅读时间"` + IsPurchased bool `json:"isPurchased" dc:"用户是否已购买该章节"` } type ChapterAppListOut struct { @@ -121,6 +122,19 @@ type ChapterAppProgressIn struct { UserId int64 `json:"userId" dc:"用户ID"` } +// App 批量上传阅读进度输入参数 +type ChapterAppBatchProgressIn struct { + BookId int64 `json:"bookId" dc:"书籍ID"` + Chapters []ChapterProgressItem `json:"chapters" dc:"章节进度列表"` + UserId int64 `json:"userId" dc:"用户ID"` +} + +// 章节进度项 +type ChapterProgressItem struct { + ChapterId int64 `json:"chapterId" dc:"章节ID"` + Progress int `json:"progress" dc:"阅读进度百分比"` +} + // App 上传阅读进度输出结构体 type ChapterAppProgressOut struct { Success bool `json:"success" dc:"是否成功"` diff --git a/internal/model/do/ad_event_logs.go b/internal/model/do/ad_event_logs.go new file mode 100644 index 0000000..23e70fa --- /dev/null +++ b/internal/model/do/ad_event_logs.go @@ -0,0 +1,25 @@ +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package do + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gtime" +) + +// AdEventLogs is the golang structure of table ad_event_logs for DAO operations like Where/Data. +type AdEventLogs struct { + g.Meta `orm:"table:ad_event_logs, do:true"` + Id interface{} // 广告事件ID + UserId interface{} // 用户ID + AdsPlatId interface{} // 平台ID:1-META,2-ADMOB + AdsCategoryId interface{} // 广告类型:1-横幅,2-插页,3-激励插页,4-激励,5-原生,6-开屏 + AppPackage interface{} // App包名 + Status interface{} // 广告状态:1-拉取失败,2-拉取成功,3-显示失败,4-显示成功,5-未观看完成,6-观看完成,7-未点击,8-已点击,9-未下载,10-已下载 + StatusDesc interface{} // 状态描述 + CreatedAt *gtime.Time // 创建时间 + UpdatedAt *gtime.Time // 更新时间 + DeletedAt *gtime.Time // 软删除时间戳 +} diff --git a/internal/model/do/ad_event_transitions.go b/internal/model/do/ad_event_transitions.go new file mode 100644 index 0000000..99c6787 --- /dev/null +++ b/internal/model/do/ad_event_transitions.go @@ -0,0 +1,21 @@ +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package do + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gtime" +) + +// AdEventTransitions is the golang structure of table ad_event_transitions for DAO operations like Where/Data. +type AdEventTransitions struct { + g.Meta `orm:"table:ad_event_transitions, do:true"` + Id interface{} // 状态流转记录ID + EventId interface{} // 所属广告事件ID,关联ad_event_logs.id + FromStatus interface{} // 原状态(首次记录为空) + ToStatus interface{} // 目标状态 + CreatedAt *gtime.Time // 状态变更时间 + DeletedAt *gtime.Time // 软删除时间戳 +} diff --git a/internal/model/do/authors.go b/internal/model/do/authors.go index 7600116..dad92c0 100644 --- a/internal/model/do/authors.go +++ b/internal/model/do/authors.go @@ -11,13 +11,14 @@ import ( // Authors is the golang structure of table authors for DAO operations like Where/Data. type Authors struct { - g.Meta `orm:"table:authors, do:true"` - Id interface{} // 作者ID - UserId interface{} // 用户ID - PenName interface{} // 笔名 - Bio interface{} // 作者简介 - Status interface{} // 状态:1=正常,2=禁用 - CreatedAt *gtime.Time // 创建时间 - UpdatedAt *gtime.Time // 更新时间 - DeletedAt *gtime.Time // 软删除时间戳 + g.Meta `orm:"table:authors, do:true"` + Id interface{} // 作者ID + UserId interface{} // 用户ID + PenName interface{} // 笔名 + Bio interface{} // 作者简介 + FollowerCount interface{} // 粉丝数量 + Status interface{} // 状态:1=正常,2=待审核, 3=未通过 + CreatedAt *gtime.Time // 创建时间 + UpdatedAt *gtime.Time // 更新时间 + DeletedAt *gtime.Time // 软删除时间戳 } diff --git a/internal/model/do/book_recommendations.go b/internal/model/do/book_recommendations.go new file mode 100644 index 0000000..d46c057 --- /dev/null +++ b/internal/model/do/book_recommendations.go @@ -0,0 +1,24 @@ +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package do + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gtime" +) + +// BookRecommendations is the golang structure of table book_recommendations for DAO operations like Where/Data. +type BookRecommendations struct { + g.Meta `orm:"table:book_recommendations, do:true"` + Id interface{} // 主键 + BookId interface{} // 书籍ID,关联 books 表 + Type interface{} // 推荐类型:1=首页Banner,2=编辑推荐,3=分类推荐等 + CoverUrl interface{} // 推荐封面图(横图) + SortOrder interface{} // 展示排序,越小越靠前 + Status interface{} // 是否启用:1=启用,0=禁用 + CreatedAt *gtime.Time // 创建时间 + UpdatedAt *gtime.Time // 更新时间 + DeletedAt *gtime.Time // 软删除时间戳 +} diff --git a/internal/model/do/books.go b/internal/model/do/books.go index 3974243..b406f3d 100644 --- a/internal/model/do/books.go +++ b/internal/model/do/books.go @@ -30,5 +30,6 @@ type Books struct { DeletedAt *gtime.Time // 软删除时间戳 IsRecommended interface{} // 是否推荐:0=否,1=是 IsFeatured interface{} // 是否精选:0=否,1=是 + IsHot interface{} // 是否热门:0=否,1=是 Language interface{} // 语言,如 zh=中文,en=英文,jp=日文 } diff --git a/internal/model/do/sign_in_reward_details.go b/internal/model/do/sign_in_reward_details.go new file mode 100644 index 0000000..fba20e2 --- /dev/null +++ b/internal/model/do/sign_in_reward_details.go @@ -0,0 +1,24 @@ +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package do + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gtime" +) + +// SignInRewardDetails is the golang structure of table sign_in_reward_details for DAO operations like Where/Data. +type SignInRewardDetails struct { + g.Meta `orm:"table:sign_in_reward_details, do:true"` + Id interface{} // 主键 + RuleId interface{} // 规则ID,关联 sign_in_reward_rules 表 + DayNumber interface{} // 签到天数(1到cycle_days) + RewardType interface{} // 奖励类型:1=积分 + Quantity interface{} // 奖励数量,如积分数量或礼包数量 + Status interface{} // 记录状态:1=启用,0=禁用 + CreatedAt *gtime.Time // 创建时间 + UpdatedAt *gtime.Time // 更新时间 + DeletedAt *gtime.Time // 软删除时间戳 +} diff --git a/internal/model/do/sign_in_reward_rules.go b/internal/model/do/sign_in_reward_rules.go new file mode 100644 index 0000000..bfa0984 --- /dev/null +++ b/internal/model/do/sign_in_reward_rules.go @@ -0,0 +1,24 @@ +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package do + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gtime" +) + +// SignInRewardRules is the golang structure of table sign_in_reward_rules for DAO operations like Where/Data. +type SignInRewardRules struct { + g.Meta `orm:"table:sign_in_reward_rules, do:true"` + Id interface{} // 主键 + RuleName interface{} // 规则名称,如“7天签到活动” + CycleDays interface{} // 奖励周期天数,如7天 + StartDate *gtime.Time // 活动开始日期 + EndDate *gtime.Time // 活动结束日期 + Status interface{} // 规则状态:1=启用,0=禁用 + CreatedAt *gtime.Time // 创建时间 + UpdatedAt *gtime.Time // 更新时间 + DeletedAt *gtime.Time // 软删除时间戳 +} diff --git a/internal/model/do/system.go b/internal/model/do/system.go new file mode 100644 index 0000000..5dfa734 --- /dev/null +++ b/internal/model/do/system.go @@ -0,0 +1,16 @@ +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package do + +import ( + "github.com/gogf/gf/v2/frame/g" +) + +// System is the golang structure of table system for DAO operations like Where/Data. +type System struct { + g.Meta `orm:"table:system, do:true"` + Key interface{} // + Value interface{} // +} diff --git a/internal/model/do/task_logs.go b/internal/model/do/task_logs.go new file mode 100644 index 0000000..032db98 --- /dev/null +++ b/internal/model/do/task_logs.go @@ -0,0 +1,23 @@ +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package do + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gtime" +) + +// TaskLogs is the golang structure of table task_logs for DAO operations like Where/Data. +type TaskLogs struct { + g.Meta `orm:"table:task_logs, do:true"` + Id interface{} // 任务日志ID + TaskId interface{} // 任务ID,关联 tasks.id + UserId interface{} // 用户ID,关联 users.id + RewardPoints interface{} // 本次任务获得的积分 + ActionTime *gtime.Time // 操作时间(如完成时间) + Status interface{} // 日志状态:1=有效,2=无效 + Extra interface{} // 扩展信息,例如来源、IP 等 + CreatedAt *gtime.Time // 创建时间 +} diff --git a/internal/model/do/task_types.go b/internal/model/do/task_types.go new file mode 100644 index 0000000..222dd0d --- /dev/null +++ b/internal/model/do/task_types.go @@ -0,0 +1,21 @@ +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package do + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gtime" +) + +// TaskTypes is the golang structure of table task_types for DAO operations like Where/Data. +type TaskTypes struct { + g.Meta `orm:"table:task_types, do:true"` + Id interface{} // 任务类型ID + Name interface{} // 任务类型名称,例如:广告、写一本书、首次登录 + Description interface{} // 任务类型描述 + Status interface{} // 状态:1=启用,2=禁用 + CreatedAt *gtime.Time // 创建时间 + UpdatedAt *gtime.Time // 更新时间 +} diff --git a/internal/model/do/tasks.go b/internal/model/do/tasks.go new file mode 100644 index 0000000..10532d6 --- /dev/null +++ b/internal/model/do/tasks.go @@ -0,0 +1,24 @@ +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package do + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gtime" +) + +// Tasks is the golang structure of table tasks for DAO operations like Where/Data. +type Tasks struct { + g.Meta `orm:"table:tasks, do:true"` + Id interface{} // 任务ID + TaskType interface{} // 任务类型:1=首次登录,2=广告,3=发布书籍 + Title interface{} // 任务标题 + Description interface{} // 任务描述 + RewardPoints interface{} // 完成任务奖励的积分数 + Status interface{} // 状态:1=启用,2=禁用 + CreatedAt *gtime.Time // 创建时间 + UpdatedAt *gtime.Time // 更新时间 + DeletedAt *gtime.Time // 软删除时间戳 +} diff --git a/internal/model/do/user_sign_in_logs.go b/internal/model/do/user_sign_in_logs.go new file mode 100644 index 0000000..85ecf68 --- /dev/null +++ b/internal/model/do/user_sign_in_logs.go @@ -0,0 +1,25 @@ +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package do + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gtime" +) + +// UserSignInLogs is the golang structure of table user_sign_in_logs for DAO operations like Where/Data. +type UserSignInLogs struct { + g.Meta `orm:"table:user_sign_in_logs, do:true"` + Id interface{} // 主键 + UserId interface{} // 用户ID,关联 users 表 + RuleId interface{} // 规则ID,关联 sign_in_reward_rules 表 + RewardDetailId interface{} // 奖励详情ID,关联 sign_in_reward_details 表 + SignInDate *gtime.Time // 签到日期 + Quantity interface{} // 奖励数量,如积分数量或礼包数量 + Status interface{} // 记录状态:1=有效,0=无效 + CreatedAt *gtime.Time // 创建时间 + UpdatedAt *gtime.Time // 更新时间 + DeletedAt *gtime.Time // 软删除时间戳 +} diff --git a/internal/model/do/users.go b/internal/model/do/users.go index 8157e77..4a24a70 100644 --- a/internal/model/do/users.go +++ b/internal/model/do/users.go @@ -11,14 +11,16 @@ import ( // Users is the golang structure of table users for DAO operations like Where/Data. type Users struct { - g.Meta `orm:"table:users, do:true"` - Id interface{} // 用户ID - Username interface{} // 用户名 - PasswordHash interface{} // 密码哈希 - Avatar interface{} // 头像URL - Email interface{} // 邮箱 - Points interface{} // 积分 - CreatedAt *gtime.Time // 注册时间 - UpdatedAt *gtime.Time // 更新时间 - DeletedAt *gtime.Time // 软删除时间戳 + g.Meta `orm:"table:users, do:true"` + Id interface{} // 用户ID + Username interface{} // 用户名 + PasswordHash interface{} // 密码哈希 + Avatar interface{} // 头像URL + Email interface{} // 邮箱 + Points interface{} // 积分 + CreatedAt *gtime.Time // 注册时间 + UpdatedAt *gtime.Time // 更新时间 + DeletedAt *gtime.Time // 软删除时间戳 + BackgroundUrl interface{} // 作者背景图 + AttentionCount interface{} // 关注他人的数量 attention count } diff --git a/internal/model/entity/ad_event_logs.go b/internal/model/entity/ad_event_logs.go new file mode 100644 index 0000000..f9b7bd1 --- /dev/null +++ b/internal/model/entity/ad_event_logs.go @@ -0,0 +1,23 @@ +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package entity + +import ( + "github.com/gogf/gf/v2/os/gtime" +) + +// AdEventLogs is the golang structure for table ad_event_logs. +type AdEventLogs struct { + Id int64 `json:"id" orm:"id" description:"广告事件ID"` // 广告事件ID + UserId int64 `json:"userId" orm:"user_id" description:"用户ID"` // 用户ID + AdsPlatId int `json:"adsPlatId" orm:"ads_plat_id" description:"平台ID:1-META,2-ADMOB"` // 平台ID:1-META,2-ADMOB + AdsCategoryId int `json:"adsCategoryId" orm:"ads_category_id" description:"广告类型:1-横幅,2-插页,3-激励插页,4-激励,5-原生,6-开屏"` // 广告类型:1-横幅,2-插页,3-激励插页,4-激励,5-原生,6-开屏 + AppPackage string `json:"appPackage" orm:"app_package" description:"App包名"` // App包名 + Status int `json:"status" orm:"status" description:"广告状态:1-拉取失败,2-拉取成功,3-显示失败,4-显示成功,5-未观看完成,6-观看完成,7-未点击,8-已点击,9-未下载,10-已下载"` // 广告状态:1-拉取失败,2-拉取成功,3-显示失败,4-显示成功,5-未观看完成,6-观看完成,7-未点击,8-已点击,9-未下载,10-已下载 + StatusDesc string `json:"statusDesc" orm:"status_desc" description:"状态描述"` // 状态描述 + CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:"创建时间"` // 创建时间 + UpdatedAt *gtime.Time `json:"updatedAt" orm:"updated_at" description:"更新时间"` // 更新时间 + DeletedAt *gtime.Time `json:"deletedAt" orm:"deleted_at" description:"软删除时间戳"` // 软删除时间戳 +} diff --git a/internal/model/entity/ad_event_transitions.go b/internal/model/entity/ad_event_transitions.go new file mode 100644 index 0000000..7a6ee40 --- /dev/null +++ b/internal/model/entity/ad_event_transitions.go @@ -0,0 +1,19 @@ +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package entity + +import ( + "github.com/gogf/gf/v2/os/gtime" +) + +// AdEventTransitions is the golang structure for table ad_event_transitions. +type AdEventTransitions struct { + Id int64 `json:"id" orm:"id" description:"状态流转记录ID"` // 状态流转记录ID + EventId int64 `json:"eventId" orm:"event_id" description:"所属广告事件ID,关联ad_event_logs.id"` // 所属广告事件ID,关联ad_event_logs.id + FromStatus int `json:"fromStatus" orm:"from_status" description:"原状态(首次记录为空)"` // 原状态(首次记录为空) + ToStatus int `json:"toStatus" orm:"to_status" description:"目标状态"` // 目标状态 + CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:"状态变更时间"` // 状态变更时间 + DeletedAt *gtime.Time `json:"deletedAt" orm:"deleted_at" description:"软删除时间戳"` // 软删除时间戳 +} diff --git a/internal/model/entity/authors.go b/internal/model/entity/authors.go index 9bb9ba8..c26dcef 100644 --- a/internal/model/entity/authors.go +++ b/internal/model/entity/authors.go @@ -10,12 +10,13 @@ import ( // Authors is the golang structure for table authors. type Authors struct { - Id int64 `json:"id" orm:"id" description:"作者ID"` // 作者ID - UserId int64 `json:"userId" orm:"user_id" description:"用户ID"` // 用户ID - PenName string `json:"penName" orm:"pen_name" description:"笔名"` // 笔名 - Bio string `json:"bio" orm:"bio" description:"作者简介"` // 作者简介 - Status int `json:"status" orm:"status" description:"状态:1=正常,2=禁用"` // 状态:1=正常,2=禁用 - CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:"创建时间"` // 创建时间 - UpdatedAt *gtime.Time `json:"updatedAt" orm:"updated_at" description:"更新时间"` // 更新时间 - DeletedAt *gtime.Time `json:"deletedAt" orm:"deleted_at" description:"软删除时间戳"` // 软删除时间戳 + Id int64 `json:"id" orm:"id" description:"作者ID"` // 作者ID + UserId int64 `json:"userId" orm:"user_id" description:"用户ID"` // 用户ID + PenName string `json:"penName" orm:"pen_name" description:"笔名"` // 笔名 + Bio string `json:"bio" orm:"bio" description:"作者简介"` // 作者简介 + FollowerCount uint `json:"followerCount" orm:"follower_count" description:"粉丝数量"` // 粉丝数量 + Status int `json:"status" orm:"status" description:"状态:1=正常,2=待审核, 3=未通过"` // 状态:1=正常,2=待审核, 3=未通过 + CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:"创建时间"` // 创建时间 + UpdatedAt *gtime.Time `json:"updatedAt" orm:"updated_at" description:"更新时间"` // 更新时间 + DeletedAt *gtime.Time `json:"deletedAt" orm:"deleted_at" description:"软删除时间戳"` // 软删除时间戳 } diff --git a/internal/model/entity/book_recommendations.go b/internal/model/entity/book_recommendations.go new file mode 100644 index 0000000..6dd66d1 --- /dev/null +++ b/internal/model/entity/book_recommendations.go @@ -0,0 +1,22 @@ +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package entity + +import ( + "github.com/gogf/gf/v2/os/gtime" +) + +// BookRecommendations is the golang structure for table book_recommendations. +type BookRecommendations struct { + Id int64 `json:"id" orm:"id" description:"主键"` // 主键 + BookId int64 `json:"bookId" orm:"book_id" description:"书籍ID,关联 books 表"` // 书籍ID,关联 books 表 + Type int `json:"type" orm:"type" description:"推荐类型:1=首页Banner,2=编辑推荐,3=分类推荐等"` // 推荐类型:1=首页Banner,2=编辑推荐,3=分类推荐等 + CoverUrl string `json:"coverUrl" orm:"cover_url" description:"推荐封面图(横图)"` // 推荐封面图(横图) + SortOrder int `json:"sortOrder" orm:"sort_order" description:"展示排序,越小越靠前"` // 展示排序,越小越靠前 + Status int `json:"status" orm:"status" description:"是否启用:1=启用,0=禁用"` // 是否启用:1=启用,0=禁用 + CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:"创建时间"` // 创建时间 + UpdatedAt *gtime.Time `json:"updatedAt" orm:"updated_at" description:"更新时间"` // 更新时间 + DeletedAt *gtime.Time `json:"deletedAt" orm:"deleted_at" description:"软删除时间戳"` // 软删除时间戳 +} diff --git a/internal/model/entity/books.go b/internal/model/entity/books.go index 190c63d..ee2a153 100644 --- a/internal/model/entity/books.go +++ b/internal/model/entity/books.go @@ -28,5 +28,6 @@ type Books struct { DeletedAt *gtime.Time `json:"deletedAt" orm:"deleted_at" description:"软删除时间戳"` // 软删除时间戳 IsRecommended int `json:"isRecommended" orm:"is_recommended" description:"是否推荐:0=否,1=是"` // 是否推荐:0=否,1=是 IsFeatured int `json:"isFeatured" orm:"is_featured" description:"是否精选:0=否,1=是"` // 是否精选:0=否,1=是 + IsHot int `json:"isHot" orm:"is_hot" description:"是否热门:0=否,1=是"` // 是否热门:0=否,1=是 Language string `json:"language" orm:"language" description:"语言,如 zh=中文,en=英文,jp=日文"` // 语言,如 zh=中文,en=英文,jp=日文 } diff --git a/internal/model/entity/sign_in_reward_details.go b/internal/model/entity/sign_in_reward_details.go new file mode 100644 index 0000000..e5ab20a --- /dev/null +++ b/internal/model/entity/sign_in_reward_details.go @@ -0,0 +1,22 @@ +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package entity + +import ( + "github.com/gogf/gf/v2/os/gtime" +) + +// SignInRewardDetails is the golang structure for table sign_in_reward_details. +type SignInRewardDetails struct { + Id int64 `json:"id" orm:"id" description:"主键"` // 主键 + RuleId int64 `json:"ruleId" orm:"rule_id" description:"规则ID,关联 sign_in_reward_rules 表"` // 规则ID,关联 sign_in_reward_rules 表 + DayNumber int `json:"dayNumber" orm:"day_number" description:"签到天数(1到cycle_days)"` // 签到天数(1到cycle_days) + RewardType int `json:"rewardType" orm:"reward_type" description:"奖励类型:1=积分"` // 奖励类型:1=积分 + Quantity int `json:"quantity" orm:"quantity" description:"奖励数量,如积分数量或礼包数量"` // 奖励数量,如积分数量或礼包数量 + Status int `json:"status" orm:"status" description:"记录状态:1=启用,0=禁用"` // 记录状态:1=启用,0=禁用 + CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:"创建时间"` // 创建时间 + UpdatedAt *gtime.Time `json:"updatedAt" orm:"updated_at" description:"更新时间"` // 更新时间 + DeletedAt *gtime.Time `json:"deletedAt" orm:"deleted_at" description:"软删除时间戳"` // 软删除时间戳 +} diff --git a/internal/model/entity/sign_in_reward_rules.go b/internal/model/entity/sign_in_reward_rules.go new file mode 100644 index 0000000..8ee9dc3 --- /dev/null +++ b/internal/model/entity/sign_in_reward_rules.go @@ -0,0 +1,22 @@ +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package entity + +import ( + "github.com/gogf/gf/v2/os/gtime" +) + +// SignInRewardRules is the golang structure for table sign_in_reward_rules. +type SignInRewardRules struct { + Id int64 `json:"id" orm:"id" description:"主键"` // 主键 + RuleName string `json:"ruleName" orm:"rule_name" description:"规则名称,如“7天签到活动”"` // 规则名称,如“7天签到活动” + CycleDays int `json:"cycleDays" orm:"cycle_days" description:"奖励周期天数,如7天"` // 奖励周期天数,如7天 + StartDate *gtime.Time `json:"startDate" orm:"start_date" description:"活动开始日期"` // 活动开始日期 + EndDate *gtime.Time `json:"endDate" orm:"end_date" description:"活动结束日期"` // 活动结束日期 + Status int `json:"status" orm:"status" description:"规则状态:1=启用,0=禁用"` // 规则状态:1=启用,0=禁用 + CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:"创建时间"` // 创建时间 + UpdatedAt *gtime.Time `json:"updatedAt" orm:"updated_at" description:"更新时间"` // 更新时间 + DeletedAt *gtime.Time `json:"deletedAt" orm:"deleted_at" description:"软删除时间戳"` // 软删除时间戳 +} diff --git a/internal/model/entity/system.go b/internal/model/entity/system.go new file mode 100644 index 0000000..15a702f --- /dev/null +++ b/internal/model/entity/system.go @@ -0,0 +1,11 @@ +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package entity + +// System is the golang structure for table system. +type System struct { + Key string `json:"key" orm:"key" description:""` // + Value string `json:"value" orm:"value" description:""` // +} diff --git a/internal/model/entity/task_logs.go b/internal/model/entity/task_logs.go new file mode 100644 index 0000000..0030a60 --- /dev/null +++ b/internal/model/entity/task_logs.go @@ -0,0 +1,21 @@ +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package entity + +import ( + "github.com/gogf/gf/v2/os/gtime" +) + +// TaskLogs is the golang structure for table task_logs. +type TaskLogs struct { + Id int64 `json:"id" orm:"id" description:"任务日志ID"` // 任务日志ID + TaskId int64 `json:"taskId" orm:"task_id" description:"任务ID,关联 tasks.id"` // 任务ID,关联 tasks.id + UserId int64 `json:"userId" orm:"user_id" description:"用户ID,关联 users.id"` // 用户ID,关联 users.id + RewardPoints int `json:"rewardPoints" orm:"reward_points" description:"本次任务获得的积分"` // 本次任务获得的积分 + ActionTime *gtime.Time `json:"actionTime" orm:"action_time" description:"操作时间(如完成时间)"` // 操作时间(如完成时间) + Status int `json:"status" orm:"status" description:"日志状态:1=有效,2=无效"` // 日志状态:1=有效,2=无效 + Extra string `json:"extra" orm:"extra" description:"扩展信息,例如来源、IP 等"` // 扩展信息,例如来源、IP 等 + CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:"创建时间"` // 创建时间 +} diff --git a/internal/model/entity/task_types.go b/internal/model/entity/task_types.go new file mode 100644 index 0000000..5b1aa1c --- /dev/null +++ b/internal/model/entity/task_types.go @@ -0,0 +1,19 @@ +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package entity + +import ( + "github.com/gogf/gf/v2/os/gtime" +) + +// TaskTypes is the golang structure for table task_types. +type TaskTypes struct { + Id uint `json:"id" orm:"id" description:"任务类型ID"` // 任务类型ID + Name string `json:"name" orm:"name" description:"任务类型名称,例如:广告、写一本书、首次登录"` // 任务类型名称,例如:广告、写一本书、首次登录 + Description string `json:"description" orm:"description" description:"任务类型描述"` // 任务类型描述 + Status int `json:"status" orm:"status" description:"状态:1=启用,2=禁用"` // 状态:1=启用,2=禁用 + CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:"创建时间"` // 创建时间 + UpdatedAt *gtime.Time `json:"updatedAt" orm:"updated_at" description:"更新时间"` // 更新时间 +} diff --git a/internal/model/entity/tasks.go b/internal/model/entity/tasks.go new file mode 100644 index 0000000..33d9eab --- /dev/null +++ b/internal/model/entity/tasks.go @@ -0,0 +1,22 @@ +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package entity + +import ( + "github.com/gogf/gf/v2/os/gtime" +) + +// Tasks is the golang structure for table tasks. +type Tasks struct { + Id int64 `json:"id" orm:"id" description:"任务ID"` // 任务ID + TaskType int `json:"taskType" orm:"task_type" description:"任务类型:1=首次登录,2=广告,3=发布书籍"` // 任务类型:1=首次登录,2=广告,3=发布书籍 + Title string `json:"title" orm:"title" description:"任务标题"` // 任务标题 + Description string `json:"description" orm:"description" description:"任务描述"` // 任务描述 + RewardPoints uint `json:"rewardPoints" orm:"reward_points" description:"完成任务奖励的积分数"` // 完成任务奖励的积分数 + Status int `json:"status" orm:"status" description:"状态:1=启用,2=禁用"` // 状态:1=启用,2=禁用 + CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:"创建时间"` // 创建时间 + UpdatedAt *gtime.Time `json:"updatedAt" orm:"updated_at" description:"更新时间"` // 更新时间 + DeletedAt *gtime.Time `json:"deletedAt" orm:"deleted_at" description:"软删除时间戳"` // 软删除时间戳 +} diff --git a/internal/model/entity/user_sign_in_logs.go b/internal/model/entity/user_sign_in_logs.go new file mode 100644 index 0000000..c7ba5a9 --- /dev/null +++ b/internal/model/entity/user_sign_in_logs.go @@ -0,0 +1,23 @@ +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package entity + +import ( + "github.com/gogf/gf/v2/os/gtime" +) + +// UserSignInLogs is the golang structure for table user_sign_in_logs. +type UserSignInLogs struct { + Id int64 `json:"id" orm:"id" description:"主键"` // 主键 + UserId int64 `json:"userId" orm:"user_id" description:"用户ID,关联 users 表"` // 用户ID,关联 users 表 + RuleId int64 `json:"ruleId" orm:"rule_id" description:"规则ID,关联 sign_in_reward_rules 表"` // 规则ID,关联 sign_in_reward_rules 表 + RewardDetailId int64 `json:"rewardDetailId" orm:"reward_detail_id" description:"奖励详情ID,关联 sign_in_reward_details 表"` // 奖励详情ID,关联 sign_in_reward_details 表 + SignInDate *gtime.Time `json:"signInDate" orm:"sign_in_date" description:"签到日期"` // 签到日期 + Quantity int `json:"quantity" orm:"quantity" description:"奖励数量,如积分数量或礼包数量"` // 奖励数量,如积分数量或礼包数量 + Status int `json:"status" orm:"status" description:"记录状态:1=有效,0=无效"` // 记录状态:1=有效,0=无效 + CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:"创建时间"` // 创建时间 + UpdatedAt *gtime.Time `json:"updatedAt" orm:"updated_at" description:"更新时间"` // 更新时间 + DeletedAt *gtime.Time `json:"deletedAt" orm:"deleted_at" description:"软删除时间戳"` // 软删除时间戳 +} diff --git a/internal/model/entity/users.go b/internal/model/entity/users.go index 4b95d1b..52884f3 100644 --- a/internal/model/entity/users.go +++ b/internal/model/entity/users.go @@ -10,13 +10,15 @@ import ( // Users is the golang structure for table users. type Users struct { - Id int64 `json:"id" orm:"id" description:"用户ID"` // 用户ID - Username string `json:"username" orm:"username" description:"用户名"` // 用户名 - PasswordHash string `json:"passwordHash" orm:"password_hash" description:"密码哈希"` // 密码哈希 - Avatar string `json:"avatar" orm:"avatar" description:"头像URL"` // 头像URL - Email string `json:"email" orm:"email" description:"邮箱"` // 邮箱 - Points uint64 `json:"points" orm:"points" description:"积分"` // 积分 - CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:"注册时间"` // 注册时间 - UpdatedAt *gtime.Time `json:"updatedAt" orm:"updated_at" description:"更新时间"` // 更新时间 - DeletedAt *gtime.Time `json:"deletedAt" orm:"deleted_at" description:"软删除时间戳"` // 软删除时间戳 + Id int64 `json:"id" orm:"id" description:"用户ID"` // 用户ID + Username string `json:"username" orm:"username" description:"用户名"` // 用户名 + PasswordHash string `json:"passwordHash" orm:"password_hash" description:"密码哈希"` // 密码哈希 + Avatar string `json:"avatar" orm:"avatar" description:"头像URL"` // 头像URL + Email string `json:"email" orm:"email" description:"邮箱"` // 邮箱 + Points uint64 `json:"points" orm:"points" description:"积分"` // 积分 + CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:"注册时间"` // 注册时间 + UpdatedAt *gtime.Time `json:"updatedAt" orm:"updated_at" description:"更新时间"` // 更新时间 + DeletedAt *gtime.Time `json:"deletedAt" orm:"deleted_at" description:"软删除时间戳"` // 软删除时间戳 + BackgroundUrl string `json:"backgroundUrl" orm:"background_url" description:"作者背景图"` // 作者背景图 + AttentionCount int `json:"attentionCount" orm:"attention_count" description:"关注他人的数量 attention count"` // 关注他人的数量 attention count } diff --git a/internal/model/sign_in_reward_details.go b/internal/model/sign_in_reward_details.go new file mode 100644 index 0000000..1cfb4fa --- /dev/null +++ b/internal/model/sign_in_reward_details.go @@ -0,0 +1,78 @@ +package model + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gtime" +) + +type SignInRewardDetail struct { + g.Meta `orm:"table:sign_in_reward_details"` + Id int64 `json:"id" orm:"id"` + RuleId int64 `json:"ruleId" orm:"rule_id"` + DayNumber int `json:"dayNumber" orm:"day_number"` + RewardType int `json:"rewardType" orm:"reward_type"` + Quantity int `json:"quantity" orm:"quantity"` + Status int `json:"status" orm:"status"` + CreatedAt *gtime.Time `json:"createdAt" orm:"created_at"` + UpdatedAt *gtime.Time `json:"updatedAt" orm:"updated_at"` + DeletedAt *gtime.Time `json:"deletedAt" orm:"deleted_at"` +} + +type SimpleReward struct { + g.Meta `orm:"table:sign_in_reward_details"` + Id int64 `json:"id" orm:"id"` + RuleId int64 `json:"ruleId" orm:"rule_id"` + DayNumber int `json:"dayNumber" orm:"day_number"` + RewardType int `json:"rewardType" orm:"reward_type"` + Quantity int `json:"quantity" orm:"quantity"` +} + +// 查询单个明细入参 +type SignInRewardDetailGetIn struct { + Id int64 `json:"id" dc:"明细ID"` +} + +// 查询单个明细出参 +type SignInRewardDetailGetOut struct { + SignInRewardDetail +} + +// 查询明细列表入参 +type SignInRewardDetailListIn struct { + RuleId int64 `json:"ruleId" dc:"规则ID"` +} + +// 查询明细列表出参 +type SignInRewardDetailListOut struct { + List []SignInRewardDetail `json:"list"` +} + +// 删除明细入参 +type SignInRewardDetailDeleteIn struct { + Id int64 `json:"id" dc:"明细ID"` +} + +// 删除明细出参 +type SignInRewardDetailDeleteOut struct { + Success bool `json:"success"` +} + +// 设置明细状态入参 +type SignInRewardDetailSetStatusIn struct { + Id int64 `json:"id" dc:"明细ID"` + Status int `json:"status" dc:"状态"` +} + +// 设置明细状态出参 +type SignInRewardDetailSetStatusOut struct { + Success bool `json:"success"` +} + +type SignInListItem struct { + g.Meta `orm:"table:sign_in_reward_details"` + Id int64 `json:"id" orm:"id"` + RuleId int64 `json:"ruleId" orm:"rule_id"` + DayNumber int `json:"dayNumber" orm:"day_number"` + Quantity int `json:"quantity" orm:"quantity"` + IsClaimed bool `json:"isClaimed" orm:"-"` +} diff --git a/internal/model/sign_in_reward_rules.go b/internal/model/sign_in_reward_rules.go new file mode 100644 index 0000000..49ae450 --- /dev/null +++ b/internal/model/sign_in_reward_rules.go @@ -0,0 +1,66 @@ +package model + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gtime" +) + +type SignInRewardRule struct { + g.Meta `orm:"table:sign_in_reward_rules"` + Id int64 `json:"id" orm:"id"` + RuleName string `json:"ruleName" orm:"rule_name"` + CycleDays int `json:"cycleDays" orm:"cycle_days"` + StartDate string `json:"startDate" orm:"start_date"` + EndDate string `json:"endDate" orm:"end_date"` + Status int `json:"status" orm:"status"` + CreatedAt *gtime.Time `json:"createdAt" orm:"created_at"` + UpdatedAt *gtime.Time `json:"updatedAt" orm:"updated_at"` + DeletedAt *gtime.Time `json:"deletedAt" orm:"deleted_at"` +} + +type SignInRewardRulesListIn struct { + Page int `json:"page"` + Size int `json:"size"` + Status int `json:"status"` + RuleName string `json:"ruleName"` +} +type SignInRewardRulesListOut struct { + Total int `json:"total"` + List []SignInRewardRule `json:"list"` +} + +type SignInRewardRulesCreateIn struct { + RuleName string `json:"ruleName"` + CycleDays int `json:"cycleDays"` + StartDate string `json:"startDate"` + EndDate string `json:"endDate"` + Status int `json:"status"` +} +type SignInRewardRulesUpdateIn struct { + Id int64 `json:"id"` + RuleName string `json:"ruleName"` + CycleDays int `json:"cycleDays"` + StartDate string `json:"startDate"` + EndDate string `json:"endDate"` + Status int `json:"status"` +} +type SignInRewardRulesDeleteIn struct { + Id int64 `json:"id"` +} +type SignInRewardRulesCRUDOut struct { + Success bool `json:"success"` +} +type SignInRewardRulesSetStatusIn struct { + Id int64 `json:"id"` + Status int `json:"status"` +} + +type SignInListIn struct { + UserId int64 `json:"userId"` +} + +type SignInListOut struct { + g.Meta `orm:"table:sign_in_reward_rules"` + Id int64 `json:"id" orm:"id"` + List []SignInListItem `json:"list" orm:"with:rule_id = id"` +} diff --git a/internal/model/system.go b/internal/model/system.go new file mode 100644 index 0000000..1f5d862 --- /dev/null +++ b/internal/model/system.go @@ -0,0 +1,25 @@ +package model + +import "server/internal/model/entity" + +type SystemOutput struct { + entity.System +} + +type SystemUniqueInput struct { + Key string + Lock +} +type Lock bool +type SystemAdsOutput struct { + Index string `json:"index"` +} + +type SystemVersionOutput struct { + Ios map[string]string `json:"ios"` + Android map[string]string `json:"android"` +} +type SystemSaveInput struct { + Key string + Value string +} diff --git a/internal/model/task.go b/internal/model/task.go new file mode 100644 index 0000000..91e3f24 --- /dev/null +++ b/internal/model/task.go @@ -0,0 +1,101 @@ +package model + +import ( + "github.com/gogf/gf/v2/os/gtime" +) + +type Task struct { + Id int64 `json:"id" orm:"id"` + TaskType uint `json:"taskType" orm:"task_type"` + Title string `json:"title" orm:"title"` + Description string `json:"description" orm:"description"` + RewardPoints uint `json:"rewardPoints" orm:"reward_points"` + Limit uint `json:"limit" orm:"limit"` + LimitType int `json:"limitType" orm:"limit_type"` + Status int `json:"status" orm:"status"` + StartTime *gtime.Time `json:"startTime" orm:"start_time"` + EndTime *gtime.Time `json:"endTime" orm:"end_time"` + CreatedAt *gtime.Time `json:"createdAt" orm:"created_at"` + UpdatedAt *gtime.Time `json:"updatedAt" orm:"updated_at"` + DeletedAt *gtime.Time `json:"deletedAt" orm:"deleted_at"` +} + +type TaskListIn struct { + Page int + Size int + Title string // 可选,按标题模糊搜索 + Status int // 可选,按状态筛选 +} +type TaskListOut struct { + Total int + List []Task +} + +type TaskAddIn struct { + TaskType uint + Title string + Description string + RewardPoints uint + Limit uint + LimitType int + Status int + StartTime *gtime.Time + EndTime *gtime.Time +} +type TaskEditIn struct { + Id int64 + TaskType uint + Title string + Description string + RewardPoints uint + Limit uint + LimitType int + Status int + StartTime *gtime.Time + EndTime *gtime.Time +} +type TaskDelIn struct { + Id int64 +} +type TaskCRUDOut struct { + Success bool +} + +// App端任务列表项,包含用户完成情况 +// CompletedCount: 用户已完成次数 +// IsCompleted: 用户今日/周期内是否已完成 +// LastActionTime: 最近一次完成时间 +type TaskAppListItem struct { + Id int64 `json:"id" orm:"id" dc:"任务ID"` + TaskType uint `json:"taskType" orm:"task_type" dc:"任务类型:1=首次登录,2=广告,3=发布书籍"` + Title string `json:"title" orm:"title" dc:"任务标题"` + Description string `json:"description" orm:"description" dc:"任务描述"` + RewardPoints uint `json:"rewardPoints" orm:"reward_points" dc:"完成任务奖励的积分数"` + DailyLimit uint `json:"dailyLimit" orm:"daily_limit" dc:"每天可参与次数,NULL 表示不限"` + LimitType int `json:"limitType" orm:"limit_type" dc:"限制类型:1=不限,2=每日,3=每周,4=仅一次"` + Status int `json:"status" orm:"status" dc:"状态:1=启用,2=禁用"` + StartTime *gtime.Time `json:"startTime" orm:"start_time" dc:"任务开始时间"` + EndTime *gtime.Time `json:"endTime" orm:"end_time" dc:"任务结束时间"` + CompletedCount int `json:"completedCount" orm:"-" dc:"用户已完成次数"` + IsCompleted bool `json:"isCompleted" orm:"-" dc:"是否已完成"` + LastActionTime *gtime.Time `json:"lastActionTime" orm:"-" dc:"最近完成时间"` +} + +type TaskAppListOut struct { + List []TaskAppListItem `json:"list"` +} + +// App端任务列表查询参数 +type TaskAppListIn struct { + UserId int64 `json:"userId" dc:"用户ID"` +} + +// 简化的任务列表项,只包含任务类型和奖励积分 +type TaskSimpleItem struct { + TaskType uint `json:"taskType" orm:"task_type" dc:"任务类型:1=首次登录,2=广告,3=发布书籍"` + RewardPoints uint `json:"rewardPoints" orm:"reward_points" dc:"完成任务奖励的积分数"` +} + +type TaskSimpleListOut struct { + List []TaskSimpleItem `json:"list"` +} diff --git a/internal/model/upload.go b/internal/model/upload.go index 8b53790..fd95671 100644 --- a/internal/model/upload.go +++ b/internal/model/upload.go @@ -1 +1,16 @@ package model + +import ( + "github.com/gogf/gf/v2/net/ghttp" +) + +// 上传图片输入结构体 +// File: 上传的文件,Type: 图片类型或业务类型 +type UploadImageIn struct { + File *ghttp.UploadFile `json:"file" dc:"上传的文件"` + Type string `json:"type" dc:"图片类型或业务类型"` +} + +type UploadImageOut struct { + ImageUrl string +} diff --git a/internal/model/user.go b/internal/model/user.go index b8acdcd..d025434 100644 --- a/internal/model/user.go +++ b/internal/model/user.go @@ -3,11 +3,13 @@ package model import "github.com/gogf/gf/v2/frame/g" type User struct { - g.Meta `orm:"table:users"` - Id int64 `json:"id" orm:"id"` - Email string `json:"email" orm:"email"` - Username string `json:"username" orm:"username"` - Avatar string `json:"avatar" orm:"avatar"` + g.Meta `orm:"table:users"` + Id int64 `json:"id" orm:"id"` + Email string `json:"email" orm:"email"` + Username string `json:"username" orm:"username"` + Avatar string `json:"avatar" orm:"avatar"` + AttentionCount int `json:"attentionCount" orm:"attention_count"` + BackGroundUrl string `json:"backgroundUrl" orm:"background_url"` } type AppUser struct { g.Meta `orm:"table:users"` @@ -38,11 +40,15 @@ type UserInfoIn struct { } type UserInfoOut struct { - UserId int64 // 用户ID - Username string // 用户名 - Email string // 邮箱 - Points uint64 // 积分 - Avatar string // 头像 + Id int64 // 用户ID + Username string // 用户名 + Email string // 邮箱 + Points uint64 // 积分 + Avatar string // 头像 + BackgroundUrl string // 背景图片 + AttentionCount int // 关注数 + IsAuthor bool // 是否是作者 + AuthorStatus int // 作者申请状态(0无,1通过,2禁用,3拒绝,2/3为未通过) } type UserDeleteIn struct { diff --git a/internal/model/user_chapter_purchases.go b/internal/model/user_chapter_purchases.go new file mode 100644 index 0000000..53fad8f --- /dev/null +++ b/internal/model/user_chapter_purchases.go @@ -0,0 +1,12 @@ +package model + +import "github.com/gogf/gf/v2/os/gtime" + +type UserChapterPurchase struct { + Id int64 `json:"id" orm:"id" description:"购买记录ID"` + UserId int64 `json:"userId" orm:"user_id" description:"用户ID"` + BookId int64 `json:"bookId" orm:"book_id" description:"小说ID"` + ChapterId int64 `json:"chapterId" orm:"chapter_id" description:"章节ID"` + PointsUsed int `json:"pointsUsed" orm:"points_used" description:"消耗积分数"` + PurchaseTime *gtime.Time `json:"purchaseTime" orm:"purchase_time" description:"购买时间"` +} diff --git a/internal/model/user_points_logs.go b/internal/model/user_points_logs.go new file mode 100644 index 0000000..74ce765 --- /dev/null +++ b/internal/model/user_points_logs.go @@ -0,0 +1,13 @@ +package model + +import "github.com/gogf/gf/v2/os/gtime" + +type UserPointsLog struct { + Id int64 `json:"id" orm:"id" description:"积分流水ID"` + UserId int64 `json:"userId" orm:"user_id" description:"用户ID"` + ChangeType int `json:"changeType" orm:"change_type" description:"变动类型,1=消费(spend), 2=收入(earn)"` + PointsChange int `json:"pointsChange" orm:"points_change" description:"积分变化数,正数增加,负数减少"` + RelatedOrderId int64 `json:"relatedOrderId" orm:"related_order_id" description:"关联ID"` + Description string `json:"description" orm:"description" description:"变动说明"` + CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:"变动时间"` +} diff --git a/internal/model/user_sign_in_logs.go b/internal/model/user_sign_in_logs.go new file mode 100644 index 0000000..500b747 --- /dev/null +++ b/internal/model/user_sign_in_logs.go @@ -0,0 +1,14 @@ +package model + +import "github.com/gogf/gf/v2/os/gtime" + +type UserSignInLogSignIn struct { + UserId int64 `json:"userId"` + RuleId int64 `json:"ruleId"` + RewardDetailId int64 `json:"rewardDetailId"` + SignInDate *gtime.Time `json:"signInDate"` +} + +type UserSignInLogSignOut struct { + Success bool `json:"success"` +} diff --git a/internal/packed/packed.go b/internal/packed/packed.go index 22336da..c0430e5 100644 --- a/internal/packed/packed.go +++ b/internal/packed/packed.go @@ -1,8 +1,11 @@ package packed import ( + _ "server/utility/i18n" _ "server/utility/myCasbin" + _ "server/utility/oss/amazon_s3" + _ "github.com/gogf/gf/contrib/drivers/mysql/v2" _ "github.com/gogf/gf/contrib/nosql/redis/v2" "github.com/gogf/gf/frame/g" diff --git a/internal/service/ad_event_logs.go b/internal/service/ad_event_logs.go new file mode 100644 index 0000000..811af60 --- /dev/null +++ b/internal/service/ad_event_logs.go @@ -0,0 +1,33 @@ +// ================================================================================ +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// You can delete these comments if you wish manually maintain this interface file. +// ================================================================================ + +package service + +import ( + "context" + "server/internal/model" +) + +type ( + IAds interface { + // Upload 广告数据上传 - 处理用户广告状态流转 + Upload(ctx context.Context, in *model.AdsUploadIn) (out *model.AdsUploadOut, err error) + } +) + +var ( + localAds IAds +) + +func Ads() IAds { + if localAds == nil { + panic("implement not found for interface IAds, forgot register?") + } + return localAds +} + +func RegisterAds(i IAds) { + localAds = i +} diff --git a/internal/service/author.go b/internal/service/author.go index d4cccfd..0cdf903 100644 --- a/internal/service/author.go +++ b/internal/service/author.go @@ -23,6 +23,10 @@ type ( // Apply 允许用户申请成为作者 Apply(ctx context.Context, in *model.AuthorApplyIn) (out *model.AuthorApplyOut, err error) Detail(ctx context.Context, in *model.AuthorDetailIn) (out *model.AuthorDetailOut, err error) + // AuthorInfo 获取作者信息 + AuthorInfo(ctx context.Context, in *model.AuthorInfoIn) (out *model.AuthorInfoOut, err error) + // 审核作者申请(通过/拒绝) + Review(ctx context.Context, in *model.AuthorReviewIn) (out *model.AuthorReviewOut, err error) } ) diff --git a/internal/service/book.go b/internal/service/book.go index 8cdbe18..d378fec 100644 --- a/internal/service/book.go +++ b/internal/service/book.go @@ -28,6 +28,8 @@ type ( SetFeatured(ctx context.Context, in *model.BookSetFeaturedIn) (out *model.BookCRUDOut, err error) // SetRecommended: 单独修改书籍的推荐状态 SetRecommended(ctx context.Context, in *model.BookSetRecommendedIn) (out *model.BookCRUDOut, err error) + // SetHot: 单独修改书籍的热门状态 + SetHot(ctx context.Context, in *model.BookSetHotIn) (out *model.BookCRUDOut, err error) } ) diff --git a/internal/service/book_recommendations.go b/internal/service/book_recommendations.go new file mode 100644 index 0000000..9fd80eb --- /dev/null +++ b/internal/service/book_recommendations.go @@ -0,0 +1,49 @@ +// ================================================================================ +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// You can delete these comments if you wish manually maintain this interface file. +// ================================================================================ + +package service + +import ( + "context" + "server/internal/model" + + "github.com/gogf/gf/v2/net/ghttp" +) + +type ( + IBookRecommendations interface { + // List 获取推荐列表 + List(ctx context.Context, in *model.BookRecommendationsListIn) (out *model.BookRecommendationsListOut, err error) + // AppList 获取APP端推荐列表 + AppList(ctx context.Context, in *model.BookRecommendationsListIn) (out *model.BookRecommendationsAppListOut, err error) + // Create 新增推荐 + Create(ctx context.Context, in *model.BookRecommendationsCreateIn) (out *model.BookRecommendationsCRUDOut, err error) + // Update 编辑推荐 + Update(ctx context.Context, in *model.BookRecommendationsUpdateIn) (out *model.BookRecommendationsCRUDOut, err error) + // Delete 删除推荐 + Delete(ctx context.Context, in *model.BookRecommendationsDeleteIn) (out *model.BookRecommendationsCRUDOut, err error) + // SetStatus 启用/禁用推荐 + SetStatus(ctx context.Context, in *model.BookRecommendationsSetStatusIn) (out *model.BookRecommendationsCRUDOut, err error) + // SortOrder 设置排序 + SortOrder(ctx context.Context, in *model.BookRecommendationsSortOrderIn) (out *model.BookRecommendationsCRUDOut, err error) + // UploadCover 上传推荐封面图 + UploadCover(ctx context.Context, file *ghttp.UploadFile) (url string, err error) + } +) + +var ( + localBookRecommendations IBookRecommendations +) + +func BookRecommendations() IBookRecommendations { + if localBookRecommendations == nil { + panic("implement not found for interface IBookRecommendations, forgot register?") + } + return localBookRecommendations +} + +func RegisterBookRecommendations(i IBookRecommendations) { + localBookRecommendations = i +} diff --git a/internal/service/chapter.go b/internal/service/chapter.go index 58c8eae..10f1ba6 100644 --- a/internal/service/chapter.go +++ b/internal/service/chapter.go @@ -28,6 +28,8 @@ type ( AppPurchase(ctx context.Context, in *model.ChapterAppPurchaseIn) (out *model.ChapterAppPurchaseOut, err error) // AppProgress uploads reading progress for app AppProgress(ctx context.Context, in *model.ChapterAppProgressIn) (out *model.ChapterAppProgressOut, err error) + // AppBatchProgress uploads batch reading progress for app + AppBatchProgress(ctx context.Context, in *model.ChapterAppBatchProgressIn) (out *model.ChapterAppProgressOut, err error) } ) diff --git a/internal/service/sign_in_reward_details.go b/internal/service/sign_in_reward_details.go new file mode 100644 index 0000000..19a4836 --- /dev/null +++ b/internal/service/sign_in_reward_details.go @@ -0,0 +1,43 @@ +// ================================================================================ +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// You can delete these comments if you wish manually maintain this interface file. +// ================================================================================ + +package service + +import ( + "context" + "server/internal/model" +) + +type ( + ISignInRewardDetails interface { + // Create 新增签到奖励明细 + Create(ctx context.Context, in *model.SignInRewardDetail) (int64, error) + // Update 编辑签到奖励明细 + Update(ctx context.Context, in *model.SignInRewardDetail) error + // Delete 删除签到奖励明细 + Delete(ctx context.Context, in *model.SignInRewardDetailDeleteIn) (*model.SignInRewardDetailDeleteOut, error) + // Get 查询单个签到奖励明细 + Get(ctx context.Context, in *model.SignInRewardDetailGetIn) (*model.SignInRewardDetailGetOut, error) + // List 根据 ruleId 查询签到奖励明细列表 + List(ctx context.Context, in *model.SignInRewardDetailListIn) (*model.SignInRewardDetailListOut, error) + // SetStatus 设置签到奖励明细状态 + SetStatus(ctx context.Context, in *model.SignInRewardDetailSetStatusIn) (*model.SignInRewardDetailSetStatusOut, error) + } +) + +var ( + localSignInRewardDetails ISignInRewardDetails +) + +func SignInRewardDetails() ISignInRewardDetails { + if localSignInRewardDetails == nil { + panic("implement not found for interface ISignInRewardDetails, forgot register?") + } + return localSignInRewardDetails +} + +func RegisterSignInRewardDetails(i ISignInRewardDetails) { + localSignInRewardDetails = i +} diff --git a/internal/service/sign_in_reward_rules.go b/internal/service/sign_in_reward_rules.go new file mode 100644 index 0000000..bb4af9f --- /dev/null +++ b/internal/service/sign_in_reward_rules.go @@ -0,0 +1,42 @@ +// ================================================================================ +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// You can delete these comments if you wish manually maintain this interface file. +// ================================================================================ + +package service + +import ( + "context" + "server/internal/model" +) + +type ( + ISignInRewardRules interface { + // List 获取签到奖励规则列表 + List(ctx context.Context, in *model.SignInRewardRulesListIn) (out *model.SignInRewardRulesListOut, err error) + // Create 新增签到奖励规则 + Create(ctx context.Context, in *model.SignInRewardRulesCreateIn) (out *model.SignInRewardRulesCRUDOut, err error) + // Update 编辑签到奖励规则 + Update(ctx context.Context, in *model.SignInRewardRulesUpdateIn) (out *model.SignInRewardRulesCRUDOut, err error) + // Delete 删除签到奖励规则 + Delete(ctx context.Context, in *model.SignInRewardRulesDeleteIn) (out *model.SignInRewardRulesCRUDOut, err error) + // SetStatus 设置签到奖励规则状态 + SetStatus(ctx context.Context, in *model.SignInRewardRulesSetStatusIn) (out *model.SignInRewardRulesCRUDOut, err error) + SignInList(ctx context.Context, in *model.SignInListIn) (out *model.SignInListOut, err error) + } +) + +var ( + localSignInRewardRules ISignInRewardRules +) + +func SignInRewardRules() ISignInRewardRules { + if localSignInRewardRules == nil { + panic("implement not found for interface ISignInRewardRules, forgot register?") + } + return localSignInRewardRules +} + +func RegisterSignInRewardRules(i ISignInRewardRules) { + localSignInRewardRules = i +} diff --git a/internal/service/system.go b/internal/service/system.go new file mode 100644 index 0000000..38d90c4 --- /dev/null +++ b/internal/service/system.go @@ -0,0 +1,34 @@ +// ================================================================================ +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// You can delete these comments if you wish manually maintain this interface file. +// ================================================================================ + +package service + +import ( + "context" + "server/internal/model" +) + +type ( + ISystem interface { + Unique(ctx context.Context, in *model.SystemUniqueInput) (*model.SystemOutput, error) + Version(ctx context.Context) (*model.SystemVersionOutput, error) + Save(ctx context.Context, in *model.SystemSaveInput) (err error) + } +) + +var ( + localSystem ISystem +) + +func System() ISystem { + if localSystem == nil { + panic("implement not found for interface ISystem, forgot register?") + } + return localSystem +} + +func RegisterSystem(i ISystem) { + localSystem = i +} diff --git a/internal/service/task.go b/internal/service/task.go new file mode 100644 index 0000000..2f55d23 --- /dev/null +++ b/internal/service/task.go @@ -0,0 +1,37 @@ +// ================================================================================ +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// You can delete these comments if you wish manually maintain this interface file. +// ================================================================================ + +package service + +import ( + "context" + "server/internal/model" +) + +type ( + ITask interface { + List(ctx context.Context, in *model.TaskListIn) (out *model.TaskListOut, err error) + Add(ctx context.Context, in *model.TaskAddIn) (out *model.TaskCRUDOut, err error) + Edit(ctx context.Context, in *model.TaskEditIn) (out *model.TaskCRUDOut, err error) + Delete(ctx context.Context, in *model.TaskDelIn) (out *model.TaskCRUDOut, err error) + // AppList 获取任务列表(只返回任务类型和奖励积分) + AppList(ctx context.Context, in *model.TaskAppListIn) (out *model.TaskSimpleListOut, err error) + } +) + +var ( + localTask ITask +) + +func Task() ITask { + if localTask == nil { + panic("implement not found for interface ITask, forgot register?") + } + return localTask +} + +func RegisterTask(i ITask) { + localTask = i +} diff --git a/internal/service/upload.go b/internal/service/upload.go new file mode 100644 index 0000000..2f58836 --- /dev/null +++ b/internal/service/upload.go @@ -0,0 +1,32 @@ +// ================================================================================ +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// You can delete these comments if you wish manually maintain this interface file. +// ================================================================================ + +package service + +import ( + "context" + "server/internal/model" +) + +type ( + IUpload interface { + UploadImage(ctx context.Context, in *model.UploadImageIn) (*model.UploadImageOut, error) + } +) + +var ( + localUpload IUpload +) + +func Upload() IUpload { + if localUpload == nil { + panic("implement not found for interface IUpload, forgot register?") + } + return localUpload +} + +func RegisterUpload(i IUpload) { + localUpload = i +} diff --git a/internal/service/user.go b/internal/service/user.go index 4f0a5f9..e92c9da 100644 --- a/internal/service/user.go +++ b/internal/service/user.go @@ -18,6 +18,7 @@ type ( Delete(ctx context.Context, in *model.UserDeleteIn) (out *model.UserDeleteOut, err error) Code(ctx context.Context, in *model.UserCodeIn) (out *model.UserCodeOut, err error) EditPass(ctx context.Context, in *model.UserEditPassIn) (out *model.UserEditPassOut, err error) + AuthorLogin(ctx context.Context, in *model.UserLoginIn) (out *model.UserLoginOut, err error) } ) diff --git a/internal/service/user_follow_author.go b/internal/service/user_follow_author.go index ff2d574..adcd0ec 100644 --- a/internal/service/user_follow_author.go +++ b/internal/service/user_follow_author.go @@ -16,8 +16,6 @@ type ( List(ctx context.Context, in *model.UserFollowAuthorListIn) (out *model.UserFollowAuthorListOut, err error) // Create adds a new user follow author Create(ctx context.Context, in *model.UserFollowAuthorAddIn) (out *model.UserFollowAuthorCRUDOut, err error) - // Delete removes a user follow author by id - Delete(ctx context.Context, in *model.UserFollowAuthorDelIn) (out *model.UserFollowAuthorCRUDOut, err error) // Unfollow removes a user follow author by userId and authorId Unfollow(ctx context.Context, userId int64, authorId int64) (out *model.UserFollowAuthorCRUDOut, err error) } diff --git a/internal/service/user_read_history.go b/internal/service/user_read_history.go index 1bad384..9c73a8e 100644 --- a/internal/service/user_read_history.go +++ b/internal/service/user_read_history.go @@ -12,10 +12,7 @@ import ( type ( IUserReadHistory interface { - // Add 添加历史记录 - Add(ctx context.Context, in *model.UserReadHistoryAddIn) (out *model.UserReadHistoryCRUDOut, err error) - // Delete 批量删除历史记录 - Delete(ctx context.Context, in *model.UserReadHistoryDelIn) (out *model.UserReadHistoryCRUDOut, err error) + Remove(ctx context.Context, in *model.UserReadHistoryDelIn) (out *model.UserReadHistoryCRUDOut, err error) } ) diff --git a/internal/service/user_read_record.go b/internal/service/user_read_record.go deleted file mode 100644 index ba5f3e9..0000000 --- a/internal/service/user_read_record.go +++ /dev/null @@ -1,37 +0,0 @@ -// ================================================================================ -// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. -// You can delete these comments if you wish manually maintain this interface file. -// ================================================================================ - -package service - -import ( - "context" - "server/internal/model" -) - -type ( - IUserReadRecord interface { - // List retrieves a paginated list of user read records - List(ctx context.Context, in *model.UserReadRecordListIn) (out *model.UserReadRecordListOut, err error) - // Create adds a new user read record - Create(ctx context.Context, in *model.UserReadRecordAddIn) (out *model.UserReadRecordCRUDOut, err error) - // Delete removes user read records by userId and bookIds - Delete(ctx context.Context, in *model.UserReadRecordDelIn) (out *model.UserReadRecordCRUDOut, err error) - } -) - -var ( - localUserReadRecord IUserReadRecord -) - -func UserReadRecord() IUserReadRecord { - if localUserReadRecord == nil { - panic("implement not found for interface IUserReadRecord, forgot register?") - } - return localUserReadRecord -} - -func RegisterUserReadRecord(i IUserReadRecord) { - localUserReadRecord = i -} diff --git a/internal/service/user_sign_in_logs.go b/internal/service/user_sign_in_logs.go new file mode 100644 index 0000000..5df69a6 --- /dev/null +++ b/internal/service/user_sign_in_logs.go @@ -0,0 +1,33 @@ +// ================================================================================ +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// You can delete these comments if you wish manually maintain this interface file. +// ================================================================================ + +package service + +import ( + "context" + "server/internal/model" +) + +type ( + IUserSignInLogs interface { + // Sign 用户签到,只允许每日唯一签到 + Sign(ctx context.Context, in *model.UserSignInLogSignIn) (*model.UserSignInLogSignOut, error) + } +) + +var ( + localUserSignInLogs IUserSignInLogs +) + +func UserSignInLogs() IUserSignInLogs { + if localUserSignInLogs == nil { + panic("implement not found for interface IUserSignInLogs, forgot register?") + } + return localUserSignInLogs +} + +func RegisterUserSignInLogs(i IUserSignInLogs) { + localUserSignInLogs = i +} diff --git a/manifest/config/config.yaml b/manifest/config/config.yaml index 425e03b..4483484 100644 --- a/manifest/config/config.yaml +++ b/manifest/config/config.yaml @@ -15,7 +15,7 @@ database: level: "all" stdout: true default: - link: "mysql:root:MSms0427@tcp(127.0.0.1:3306)/novel?loc=Local&charset=utf8mb4" + link: "mysql:root:MSms0427@tcp(127.0.0.1:3306)/novel2?loc=Local&charset=utf8mb4" debug: true # Redis configuration. @@ -29,23 +29,11 @@ casbin: #OSS配置 oss: - aliyun: - bucket: "arenaxx" - key: "LTAI4FhJV8uQvgSzviJ5tgcP" - secret: "Zc5E5CO4fqsXTIFsq4SHIaaXO4beJm" - endpoint: "oss-us-east-1.aliyuncs.com" - -# Email -email: - username: "1016848185@qq.com" - password: "zugifvgkwvmibehf" - host: "smtp.qq.com" - port: 465 - -saas: - link: "http://62.151.176.239:8002" - iosAppId: "26" - androidAppId: "27" + s3: + bucket: "iosnovel" + key: "AKIA2OAJT2TMKO6YTRA3" + secret: "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" + region: "us-east-1" # AWS aws: diff --git a/novel.sql b/novel.sql index 6c476ca..bd1278b 100644 --- a/novel.sql +++ b/novel.sql @@ -11,7 +11,7 @@ Target Server Version : 80041 (8.0.41) File Encoding : 65001 - Date: 16/07/2025 09:37:05 + Date: 23/07/2025 11:36:45 */ SET NAMES utf8mb4; @@ -33,13 +33,6 @@ CREATE TABLE `admins` ( KEY `idx_email_deleted` (`deleted_at`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='系统管理员表'; --- ---------------------------- --- Records of admins --- ---------------------------- -BEGIN; -INSERT INTO `admins` (`id`, `username`, `password_hash`, `created_at`, `updated_at`, `deleted_at`) VALUES (1, 'admin', '$2a$10$cin1jkkEiKg0GW2blAWEb.1V8QeUCHV350yZ6sTbmIC/4fdbFqnTW', '2025-07-09 13:40:54', '2025-07-09 13:40:54', NULL); -COMMIT; - -- ---------------------------- -- Table structure for authors -- ---------------------------- @@ -49,6 +42,7 @@ CREATE TABLE `authors` ( `user_id` bigint NOT NULL COMMENT '用户ID', `pen_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '笔名', `bio` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT '作者简介', + `follower_count` int unsigned NOT NULL DEFAULT '0' COMMENT '粉丝数量', `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态:1=正常,2=禁用', `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updated_at` timestamp NULL DEFAULT NULL COMMENT '更新时间', @@ -59,18 +53,6 @@ CREATE TABLE `authors` ( CONSTRAINT `fk_authors_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='作者表'; --- ---------------------------- --- Records of authors --- ---------------------------- -BEGIN; -INSERT INTO `authors` (`id`, `user_id`, `pen_name`, `bio`, `status`, `created_at`, `updated_at`, `deleted_at`) VALUES (1, 6, 'MoonScribe', 'A fantasy writer crafting magical worlds and epic quests.', 1, '2023-01-11 09:30:00', '2025-07-10 14:00:00', NULL); -INSERT INTO `authors` (`id`, `user_id`, `pen_name`, `bio`, `status`, `created_at`, `updated_at`, `deleted_at`) VALUES (2, 7, 'NebulaTale', 'Sci-fi author exploring interstellar adventures and cosmic mysteries.', 1, '2023-03-16 12:00:00', '2025-06-25 10:15:00', NULL); -INSERT INTO `authors` (`id`, `user_id`, `pen_name`, `bio`, `status`, `created_at`, `updated_at`, `deleted_at`) VALUES (3, 8, 'MistWalker', 'Mystery novelist weaving intricate plots in shadowy settings.', 1, '2023-05-21 14:00:00', '2025-05-10 16:20:00', NULL); -INSERT INTO `authors` (`id`, `user_id`, `pen_name`, `bio`, `status`, `created_at`, `updated_at`, `deleted_at`) VALUES (4, 9, 'LegendWeaver', 'Epic storyteller inspired by ancient myths and heroic sagas.', 1, '2023-07-26 10:30:00', '2025-07-01 12:30:00', NULL); -INSERT INTO `authors` (`id`, `user_id`, `pen_name`, `bio`, `status`, `created_at`, `updated_at`, `deleted_at`) VALUES (5, 10, 'StarBloom', 'Romantic writer penning heartfelt stories under the stars.', 1, '2023-09-11 15:30:00', NULL, NULL); -INSERT INTO `authors` (`id`, `user_id`, `pen_name`, `bio`, `status`, `created_at`, `updated_at`, `deleted_at`) VALUES (6, 11, 'NightShade', 'Thriller and horror author delving into the darkest corners of the mind.', 2, '2023-11-06 09:00:00', '2025-06-15 09:00:00', '2025-07-05 11:00:00'); -COMMIT; - -- ---------------------------- -- Table structure for book_ratings -- ---------------------------- @@ -90,10 +72,24 @@ CREATE TABLE `book_ratings` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='小说评分与评论表'; -- ---------------------------- --- Records of book_ratings +-- Table structure for book_recommendations -- ---------------------------- -BEGIN; -COMMIT; +DROP TABLE IF EXISTS `book_recommendations`; +CREATE TABLE `book_recommendations` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `book_id` bigint NOT NULL COMMENT '书籍ID,关联 books 表', + `type` tinyint NOT NULL DEFAULT '1' COMMENT '推荐类型:1=首页Banner,2=编辑推荐,3=分类推荐等', + `cover_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '推荐封面图(横图)', + `sort_order` int DEFAULT '0' COMMENT '展示排序,越小越靠前', + `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否启用:1=启用,0=禁用', + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` timestamp NULL DEFAULT NULL COMMENT '更新时间', + `deleted_at` timestamp NULL DEFAULT NULL COMMENT '软删除时间戳', + PRIMARY KEY (`id`), + KEY `idx_book_id_deleted` (`book_id`,`deleted_at`), + KEY `idx_type_sort_deleted` (`type`,`sort_order`,`deleted_at`), + CONSTRAINT `fk_book_recommendations_book_id` FOREIGN KEY (`book_id`) REFERENCES `books` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='书籍推荐配置表'; -- ---------------------------- -- Table structure for books @@ -125,20 +121,7 @@ CREATE TABLE `books` ( KEY `idx_status` (`status`), CONSTRAINT `fk_books_author_id` FOREIGN KEY (`author_id`) REFERENCES `authors` (`id`), CONSTRAINT `fk_books_category_id` FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=41 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='小说表'; - --- ---------------------------- --- Records of books --- ---------------------------- -BEGIN; -INSERT INTO `books` (`id`, `author_id`, `category_id`, `title`, `cover_url`, `description`, `status`, `words_count`, `chapters_count`, `rating`, `read_count`, `current_readers`, `tags`, `created_at`, `updated_at`, `deleted_at`, `is_recommended`, `is_featured`, `language`) VALUES (1, 1, 1, 'Echoes of the Lost Realm', 'https://example.com/covers/echoes_realm.jpg', 'A young sorcerer uncovers secrets of a forgotten kingdom.', 1, 160000, 48, 8.60, 13000, 0, 'fantasy,magic,adventure', '2024-02-15 10:00:00', '2025-07-10 12:00:00', NULL, 1, 0, 'en'); -INSERT INTO `books` (`id`, `author_id`, `category_id`, `title`, `cover_url`, `description`, `status`, `words_count`, `chapters_count`, `rating`, `read_count`, `current_readers`, `tags`, `created_at`, `updated_at`, `deleted_at`, `is_recommended`, `is_featured`, `language`) VALUES (2, 2, 2, 'Void Wanderers', 'https://example.com/covers/void_wanderers.jpg', 'A crew of explorers navigates uncharted galaxies.', 2, 210000, 62, 9.00, 22000, 0, 'sci-fi,space,exploration', '2023-09-10 11:00:00', '2025-06-20 13:30:00', NULL, 1, 0, 'en'); -INSERT INTO `books` (`id`, `author_id`, `category_id`, `title`, `cover_url`, `description`, `status`, `words_count`, `chapters_count`, `rating`, `read_count`, `current_readers`, `tags`, `created_at`, `updated_at`, `deleted_at`, `is_recommended`, `is_featured`, `language`) VALUES (3, 3, 3, 'Shadows of Doubt', 'https://example.com/covers/shadows_doubt.jpg', 'A detective solves a murder in a city shrouded in secrets.', 1, 100000, 32, 8.20, 7000, 0, 'mystery,detective,crime', '2024-05-25 14:00:00', '2025-07-05 09:45:00', NULL, 0, 0, 'en'); -INSERT INTO `books` (`id`, `author_id`, `category_id`, `title`, `cover_url`, `description`, `status`, `words_count`, `chapters_count`, `rating`, `read_count`, `current_readers`, `tags`, `created_at`, `updated_at`, `deleted_at`, `is_recommended`, `is_featured`, `language`) VALUES (4, 4, 4, 'The Eternal Saga', 'https://example.com/covers/eternal_saga.jpg', 'Heroes battle to fulfill an ancient prophecy.', 2, 340000, 85, 9.40, 28000, 0, 'epic,fantasy,mythology', '2023-12-01 12:00:00', '2025-07-01 11:15:00', NULL, 1, 0, 'en'); -INSERT INTO `books` (`id`, `author_id`, `category_id`, `title`, `cover_url`, `description`, `status`, `words_count`, `chapters_count`, `rating`, `read_count`, `current_readers`, `tags`, `created_at`, `updated_at`, `deleted_at`, `is_recommended`, `is_featured`, `language`) VALUES (5, 5, 5, 'Love Beyond the Stars', 'https://example.com/covers/love_stars.jpg', 'A romance blossoms during an interstellar journey.', 1, 115000, 38, 8.50, 11000, 0, 'romance,sci-fi,drama', '2024-07-15 13:00:00', '2025-07-11 02:00:00', NULL, 0, 0, 'en'); -INSERT INTO `books` (`id`, `author_id`, `category_id`, `title`, `cover_url`, `description`, `status`, `words_count`, `chapters_count`, `rating`, `read_count`, `current_readers`, `tags`, `created_at`, `updated_at`, `deleted_at`, `is_recommended`, `is_featured`, `language`) VALUES (6, 6, 7, 'Curse of the Forgotten', NULL, 'A haunted village hides a terrifying secret.', 3, 65000, 18, 6.90, 2500, 0, 'horror,supernatural,mystery', '2024-03-10 16:00:00', '2025-07-11 02:00:00', '2025-07-05 10:00:00', 0, 0, 'en'); -INSERT INTO `books` (`id`, `author_id`, `category_id`, `title`, `cover_url`, `description`, `status`, `words_count`, `chapters_count`, `rating`, `read_count`, `current_readers`, `tags`, `created_at`, `updated_at`, `deleted_at`, `is_recommended`, `is_featured`, `language`) VALUES (7, 2, 6, 'Pulse of Danger', 'https://example.com/covers/pulse_danger.jpg', 'A rogue agent uncovers a global conspiracy.', 1, 140000, 40, 8.70, 9500, 0, 'thriller,action,conspiracy', '2024-04-20 09:30:00', '2025-06-30 15:00:00', NULL, 1, 0, 'en'); -COMMIT; +) ENGINE=InnoDB AUTO_INCREMENT=91 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='小说表'; -- ---------------------------- -- Table structure for bookshelves @@ -161,12 +144,6 @@ CREATE TABLE `bookshelves` ( CONSTRAINT `fk_bookshelves_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户书架表'; --- ---------------------------- --- Records of bookshelves --- ---------------------------- -BEGIN; -COMMIT; - -- ---------------------------- -- Table structure for casbin_rule -- ---------------------------- @@ -181,55 +158,7 @@ CREATE TABLE `casbin_rule` ( `v4` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `v5` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', PRIMARY KEY (`id`) USING BTREE -) ENGINE=InnoDB AUTO_INCREMENT=43 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='路由权限表'; - --- ---------------------------- --- Records of casbin_rule --- ---------------------------- -BEGIN; -INSERT INTO `casbin_rule` (`id`, `p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES (1, 'g', 'user', 'guest', '', '', '', ''); -INSERT INTO `casbin_rule` (`id`, `p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES (2, 'g', 'author', 'user', '', '', '', ''); -INSERT INTO `casbin_rule` (`id`, `p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES (3, 'g', 'admin', 'author', '', '', '', ''); -INSERT INTO `casbin_rule` (`id`, `p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES (4, 'p', 'guest', '/book/app/list', 'GET', 'App获取书籍列表', '', ''); -INSERT INTO `casbin_rule` (`id`, `p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES (5, 'p', 'guest', '/book/app/detail', 'GET', 'App获取书籍详情', '', ''); -INSERT INTO `casbin_rule` (`id`, `p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES (6, 'p', 'guest', '/chapter/app/list', 'GET', 'App获取章节列表', '', ''); -INSERT INTO `casbin_rule` (`id`, `p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES (7, 'p', 'guest', '/chapter/app/detail', 'GET', 'App获取章节详情', '', ''); -INSERT INTO `casbin_rule` (`id`, `p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES (8, 'p', 'guest', '/category', 'GET', '获取分类列表', '', ''); -INSERT INTO `casbin_rule` (`id`, `p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES (9, 'p', 'user', '/book/shelf/add', 'POST', '加入书架', '', ''); -INSERT INTO `casbin_rule` (`id`, `p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES (10, 'p', 'user', '/book/shelf/remove', 'POST', '移除书架', '', ''); -INSERT INTO `casbin_rule` (`id`, `p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES (11, 'p', 'user', '/book/history/add', 'POST', '添加历史记录', '', ''); -INSERT INTO `casbin_rule` (`id`, `p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES (12, 'p', 'user', '/book/history/remove', 'POST', '删除历史记录', '', ''); -INSERT INTO `casbin_rule` (`id`, `p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES (13, 'p', 'user', '/book/app/rate', 'POST', 'App用户评分', '', ''); -INSERT INTO `casbin_rule` (`id`, `p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES (14, 'p', 'user', '/chapter/app/purchase', 'POST', 'App购买章节', '', ''); -INSERT INTO `casbin_rule` (`id`, `p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES (15, 'p', 'user', '/chapter/app/progress', 'POST', 'App上传阅读进度', '', ''); -INSERT INTO `casbin_rule` (`id`, `p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES (16, 'p', 'user', '/feedback', 'POST', '新增反馈', '', ''); -INSERT INTO `casbin_rule` (`id`, `p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES (17, 'p', 'user', '/user/info', 'GET', '获取用户信息', '', ''); -INSERT INTO `casbin_rule` (`id`, `p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES (18, 'p', 'user', '/user/delete', 'POST', '删除用户', '', ''); -INSERT INTO `casbin_rule` (`id`, `p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES (19, 'p', 'user', '/user/logout', 'POST', '用户登出', '', ''); -INSERT INTO `casbin_rule` (`id`, `p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES (20, 'p', 'user', '/author/follow', 'POST', '关注作者', '', ''); -INSERT INTO `casbin_rule` (`id`, `p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES (21, 'p', 'user', '/author/unfollow', 'POST', '取消关注作者', '', ''); -INSERT INTO `casbin_rule` (`id`, `p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES (22, 'p', 'author', '/book', 'GET', '获取图书列表', '', ''); -INSERT INTO `casbin_rule` (`id`, `p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES (23, 'p', 'author', '/book', 'POST', '新增图书', '', ''); -INSERT INTO `casbin_rule` (`id`, `p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES (24, 'p', 'author', '/book', 'PUT', '编辑图书', '', ''); -INSERT INTO `casbin_rule` (`id`, `p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES (25, 'p', 'author', '/book', 'DELETE', '删除图书', '', ''); -INSERT INTO `casbin_rule` (`id`, `p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES (26, 'p', 'author', '/chapter', 'GET', '获取章节列表', '', ''); -INSERT INTO `casbin_rule` (`id`, `p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES (27, 'p', 'author', '/chapter', 'POST', '创建章节', '', ''); -INSERT INTO `casbin_rule` (`id`, `p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES (28, 'p', 'author', '/chapter', 'PUT', '更新章节', '', ''); -INSERT INTO `casbin_rule` (`id`, `p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES (29, 'p', 'author', '/chapter', 'DELETE', '删除章节', '', ''); -INSERT INTO `casbin_rule` (`id`, `p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES (30, 'p', 'admin', '/author', 'GET', '获取作者列表', '', ''); -INSERT INTO `casbin_rule` (`id`, `p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES (31, 'p', 'admin', '/author', 'POST', '创建作者', '', ''); -INSERT INTO `casbin_rule` (`id`, `p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES (32, 'p', 'admin', '/author', 'PUT', '更新作者', '', ''); -INSERT INTO `casbin_rule` (`id`, `p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES (33, 'p', 'admin', '/author', 'DELETE', '删除作者', '', ''); -INSERT INTO `casbin_rule` (`id`, `p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES (34, 'p', 'admin', '/feedback', 'GET', '获取反馈列表', '', ''); -INSERT INTO `casbin_rule` (`id`, `p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES (35, 'p', 'admin', '/category', 'POST', '创建分类', '', ''); -INSERT INTO `casbin_rule` (`id`, `p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES (36, 'p', 'admin', '/category', 'PUT', '更新分类', '', ''); -INSERT INTO `casbin_rule` (`id`, `p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES (37, 'p', 'admin', '/category', 'DELETE', '删除分类', '', ''); -INSERT INTO `casbin_rule` (`id`, `p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES (38, 'p', 'admin', '/admin/info', 'GET', '获取管理员用户信息', '', ''); -INSERT INTO `casbin_rule` (`id`, `p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES (39, 'p', 'admin', '/admin/editPass', 'POST', '管理员修改密码', '', ''); -INSERT INTO `casbin_rule` (`id`, `p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES (40, 'p', 'user', '/book/app/my-books', 'GET', '获取我的书籍列表', '', ''); -INSERT INTO `casbin_rule` (`id`, `p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES (41, 'p', 'admin', '/book/set-featured', 'POST', '设置书籍精选状态', '', ''); -INSERT INTO `casbin_rule` (`id`, `p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES (42, 'p', 'admin', '/book/set-recommended', 'POST', '设置书籍推荐状态', '', ''); -COMMIT; +) ENGINE=InnoDB AUTO_INCREMENT=66 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='路由权限表'; -- ---------------------------- -- Table structure for categories @@ -246,24 +175,6 @@ CREATE TABLE `categories` ( UNIQUE KEY `uk_name` (`name`) ) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='小说分类表'; --- ---------------------------- --- Records of categories --- ---------------------------- -BEGIN; -INSERT INTO `categories` (`id`, `name`, `created_at`, `updated_at`, `deleted_at`, `channel`) VALUES (1, 'Fantasy', '2023-01-10 09:00:00', '2025-07-10 15:30:00', NULL, 1); -INSERT INTO `categories` (`id`, `name`, `created_at`, `updated_at`, `deleted_at`, `channel`) VALUES (2, 'Science Fiction', '2023-02-15 10:30:00', '2025-06-20 12:00:00', NULL, 1); -INSERT INTO `categories` (`id`, `name`, `created_at`, `updated_at`, `deleted_at`, `channel`) VALUES (3, 'Mystery', '2023-03-20 14:00:00', '2025-05-15 09:45:00', NULL, 1); -INSERT INTO `categories` (`id`, `name`, `created_at`, `updated_at`, `deleted_at`, `channel`) VALUES (4, 'Epic', '2023-04-05 11:15:00', '2025-07-01 16:20:00', NULL, 1); -INSERT INTO `categories` (`id`, `name`, `created_at`, `updated_at`, `deleted_at`, `channel`) VALUES (5, 'Romance', '2023-05-12 08:45:00', NULL, NULL, 1); -INSERT INTO `categories` (`id`, `name`, `created_at`, `updated_at`, `deleted_at`, `channel`) VALUES (6, 'Thriller', '2023-06-18 13:20:00', '2025-04-10 10:10:00', NULL, 1); -INSERT INTO `categories` (`id`, `name`, `created_at`, `updated_at`, `deleted_at`, `channel`) VALUES (8, 'Modern Romance', '2025-07-15 21:00:00', NULL, NULL, 2); -INSERT INTO `categories` (`id`, `name`, `created_at`, `updated_at`, `deleted_at`, `channel`) VALUES (9, 'Historical Romance', '2025-07-15 21:00:00', NULL, NULL, 2); -INSERT INTO `categories` (`id`, `name`, `created_at`, `updated_at`, `deleted_at`, `channel`) VALUES (10, 'School Life', '2025-07-15 21:00:00', NULL, NULL, 2); -INSERT INTO `categories` (`id`, `name`, `created_at`, `updated_at`, `deleted_at`, `channel`) VALUES (11, 'CEO Romance', '2025-07-15 21:00:00', NULL, NULL, 2); -INSERT INTO `categories` (`id`, `name`, `created_at`, `updated_at`, `deleted_at`, `channel`) VALUES (12, 'Time Travel', '2025-07-15 21:00:00', NULL, NULL, 2); -INSERT INTO `categories` (`id`, `name`, `created_at`, `updated_at`, `deleted_at`, `channel`) VALUES (13, 'Fantasy Romance', '2025-07-15 21:00:00', NULL, NULL, 2); -COMMIT; - -- ---------------------------- -- Table structure for chapters -- ---------------------------- @@ -285,33 +196,6 @@ CREATE TABLE `chapters` ( CONSTRAINT `fk_chapters_book_id` FOREIGN KEY (`book_id`) REFERENCES `books` (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='章节表'; --- ---------------------------- --- Records of chapters --- ---------------------------- -BEGIN; -INSERT INTO `chapters` (`id`, `book_id`, `title`, `content`, `word_count`, `sort`, `is_locked`, `required_score`, `created_at`, `updated_at`, `deleted_at`) VALUES (1, 1, 'The Fated Dawn', 'Aelar discovers a shimmering crystal in the ruins, pulsing with ancient magic...', 3700, 1, 0, 0, '2024-02-15 10:10:00', '2025-07-11 20:23:43', NULL); -INSERT INTO `chapters` (`id`, `book_id`, `title`, `content`, `word_count`, `sort`, `is_locked`, `required_score`, `created_at`, `updated_at`, `deleted_at`) VALUES (2, 1, 'The Oracle’s Vision', 'Aelar seeks guidance from a reclusive seer in the enchanted forest...', 4000, 2, 0, 0, '2024-03-05 11:45:00', '2024-03-05 11:45:00', NULL); -INSERT INTO `chapters` (`id`, `book_id`, `title`, `content`, `word_count`, `sort`, `is_locked`, `required_score`, `created_at`, `updated_at`, `deleted_at`) VALUES (3, 1, 'The Veiled Kingdom', 'Aelar battles a mystical guardian to enter the Lost Realm...', 4200, 3, 1, 70, '2025-07-10 12:00:00', '2025-07-10 12:00:00', NULL); -INSERT INTO `chapters` (`id`, `book_id`, `title`, `content`, `word_count`, `sort`, `is_locked`, `required_score`, `created_at`, `updated_at`, `deleted_at`) VALUES (4, 2, 'The Cosmic Leap', 'The Starfarer crew prepares for a daring jump into uncharted space...', 4400, 1, 0, 0, '2023-09-10 11:10:00', '2023-09-10 11:10:00', NULL); -INSERT INTO `chapters` (`id`, `book_id`, `title`, `content`, `word_count`, `sort`, `is_locked`, `required_score`, `created_at`, `updated_at`, `deleted_at`) VALUES (5, 2, 'Anomaly Detected', 'A mysterious signal causes chaos aboard the Starfarer...', 4700, 2, 0, 0, '2023-10-25 12:30:00', '2023-10-25 12:30:00', NULL); -INSERT INTO `chapters` (`id`, `book_id`, `title`, `content`, `word_count`, `sort`, `is_locked`, `required_score`, `created_at`, `updated_at`, `deleted_at`) VALUES (6, 2, 'The Starborn Legacy', 'The crew uncovers an alien artifact that holds the key to their mission...', 5000, 3, 1, 130, '2025-06-20 13:30:00', '2025-06-20 13:30:00', NULL); -INSERT INTO `chapters` (`id`, `book_id`, `title`, `content`, `word_count`, `sort`, `is_locked`, `required_score`, `created_at`, `updated_at`, `deleted_at`) VALUES (7, 3, 'The Cryptic Note', 'Detective Lila finds a hidden message that changes the case...', 3400, 1, 0, 0, '2024-05-25 14:10:00', '2024-05-25 14:10:00', NULL); -INSERT INTO `chapters` (`id`, `book_id`, `title`, `content`, `word_count`, `sort`, `is_locked`, `required_score`, `created_at`, `updated_at`, `deleted_at`) VALUES (8, 3, 'Suspect’s Secrets', 'Lila interrogates a key witness with a dark past...', 3600, 2, 1, 50, '2024-06-20 15:15:00', '2024-06-20 15:15:00', NULL); -INSERT INTO `chapters` (`id`, `book_id`, `title`, `content`, `word_count`, `sort`, `is_locked`, `required_score`, `created_at`, `updated_at`, `deleted_at`) VALUES (9, 3, 'The Shadow’s Trail', 'A late-night pursuit leads Lila to a shocking discovery...', 3800, 3, 1, 70, '2025-07-05 09:45:00', '2025-07-05 09:45:00', NULL); -INSERT INTO `chapters` (`id`, `book_id`, `title`, `content`, `word_count`, `sort`, `is_locked`, `required_score`, `created_at`, `updated_at`, `deleted_at`) VALUES (10, 4, 'The Oracle’s Call', 'Kael is summoned to lead the fight against an ancient evil...', 5200, 1, 0, 0, '2023-12-01 12:10:00', '2023-12-01 12:10:00', NULL); -INSERT INTO `chapters` (`id`, `book_id`, `title`, `content`, `word_count`, `sort`, `is_locked`, `required_score`, `created_at`, `updated_at`, `deleted_at`) VALUES (11, 4, 'The Great Clash', 'Kael’s forces face the dark army in a battle for the ages...', 5400, 2, 0, 0, '2024-01-25 13:15:00', '2024-01-25 13:15:00', NULL); -INSERT INTO `chapters` (`id`, `book_id`, `title`, `content`, `word_count`, `sort`, `is_locked`, `required_score`, `created_at`, `updated_at`, `deleted_at`) VALUES (12, 4, 'The Prophecy’s End', 'Kael’s final stand determines the fate of the world...', 5700, 3, 1, 200, '2025-07-01 11:15:00', '2025-07-01 11:15:00', NULL); -INSERT INTO `chapters` (`id`, `book_id`, `title`, `content`, `word_count`, `sort`, `is_locked`, `required_score`, `created_at`, `updated_at`, `deleted_at`) VALUES (13, 5, 'First Glance', 'Elara and Kian meet aboard the starship, their connection instant...', 3200, 1, 0, 0, '2024-07-15 13:10:00', '2024-07-15 13:10:00', NULL); -INSERT INTO `chapters` (`id`, `book_id`, `title`, `content`, `word_count`, `sort`, `is_locked`, `required_score`, `created_at`, `updated_at`, `deleted_at`) VALUES (14, 5, 'Under the Stars', 'A quiet evening reveals the depth of Elara and Kian’s feelings...', 3400, 2, 1, 60, '2024-08-30 14:15:00', '2024-08-30 14:15:00', NULL); -INSERT INTO `chapters` (`id`, `book_id`, `title`, `content`, `word_count`, `sort`, `is_locked`, `required_score`, `created_at`, `updated_at`, `deleted_at`) VALUES (15, 5, 'Tempest of Love', 'A galactic storm threatens to tear Elara and Kian apart...', 3700, 3, 1, 80, '2025-07-11 02:00:00', '2025-07-11 02:00:00', NULL); -INSERT INTO `chapters` (`id`, `book_id`, `title`, `content`, `word_count`, `sort`, `is_locked`, `required_score`, `created_at`, `updated_at`, `deleted_at`) VALUES (16, 6, 'The Fog Descends', 'A chilling mist engulfs the village, hiding unspeakable horrors...', 2900, 1, 0, 0, '2024-03-10 16:15:00', '2024-03-10 16:15:00', '2025-07-05 10:00:00'); -INSERT INTO `chapters` (`id`, `book_id`, `title`, `content`, `word_count`, `sort`, `is_locked`, `required_score`, `created_at`, `updated_at`, `deleted_at`) VALUES (17, 6, 'The Haunted Relic', 'A cursed artifact in the village square awakens dark forces...', 3100, 2, 0, 0, '2024-04-05 17:00:00', '2024-04-05 17:00:00', '2025-07-05 10:00:00'); -INSERT INTO `chapters` (`id`, `book_id`, `title`, `content`, `word_count`, `sort`, `is_locked`, `required_score`, `created_at`, `updated_at`, `deleted_at`) VALUES (18, 6, 'Whispers in the Dark', 'The villagers hear eerie voices as the curse tightens its grip...', 3300, 3, 0, 0, '2024-04-20 17:30:00', '2024-04-20 17:30:00', '2025-07-05 10:00:00'); -INSERT INTO `chapters` (`id`, `book_id`, `title`, `content`, `word_count`, `sort`, `is_locked`, `required_score`, `created_at`, `updated_at`, `deleted_at`) VALUES (19, 7, 'The Covert Operation', 'Agent Jace infiltrates a secret facility with a deadly agenda...', 3700, 1, 0, 0, '2024-04-20 09:45:00', '2024-04-20 09:45:00', NULL); -INSERT INTO `chapters` (`id`, `book_id`, `title`, `content`, `word_count`, `sort`, `is_locked`, `required_score`, `created_at`, `updated_at`, `deleted_at`) VALUES (20, 7, 'The Hidden Truth', 'Jace uncovers evidence of a global conspiracy...', 3900, 2, 1, 80, '2024-05-20 10:30:00', '2024-05-20 10:30:00', NULL); -INSERT INTO `chapters` (`id`, `book_id`, `title`, `content`, `word_count`, `sort`, `is_locked`, `required_score`, `created_at`, `updated_at`, `deleted_at`) VALUES (21, 7, 'The Final Countdown', 'Jace races to stop a catastrophic event...', 4100, 3, 1, 90, '2025-06-30 15:00:00', '2025-06-30 15:00:00', NULL); -COMMIT; - -- ---------------------------- -- Table structure for feedbacks -- ---------------------------- @@ -329,15 +213,43 @@ CREATE TABLE `feedbacks` ( ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户反馈表'; -- ---------------------------- --- Records of feedbacks +-- Table structure for sign_in_reward_details -- ---------------------------- -BEGIN; -INSERT INTO `feedbacks` (`id`, `user_id`, `content`, `status`, `created_at`, `updated_at`) VALUES (1, 1, 'App crashes during photo upload.', 1, '2025-07-01 10:15:23', NULL); -INSERT INTO `feedbacks` (`id`, `user_id`, `content`, `status`, `created_at`, `updated_at`) VALUES (2, 2, 'Please add dark mode support.', 2, '2025-07-02 14:30:45', '2025-07-05 09:20:10'); -INSERT INTO `feedbacks` (`id`, `user_id`, `content`, `status`, `created_at`, `updated_at`) VALUES (3, 3, 'Slow login process needs optimization.', 3, '2025-07-03 08:45:12', '2025-07-06 16:00:00'); -INSERT INTO `feedbacks` (`id`, `user_id`, `content`, `status`, `created_at`, `updated_at`) VALUES (4, 4, 'More filter options would be great.', 1, '2025-07-04 19:22:33', NULL); -INSERT INTO `feedbacks` (`id`, `user_id`, `content`, `status`, `created_at`, `updated_at`) VALUES (5, 5, 'Payment section has a bug.', 2, '2025-07-05 11:10:50', '2025-07-07 13:45:22'); -COMMIT; +DROP TABLE IF EXISTS `sign_in_reward_details`; +CREATE TABLE `sign_in_reward_details` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `rule_id` bigint NOT NULL COMMENT '规则ID,关联 sign_in_reward_rules 表', + `day_number` int NOT NULL COMMENT '签到天数(1到cycle_days)', + `reward_type` tinyint NOT NULL DEFAULT '1' COMMENT '奖励类型:1=积分', + `quantity` int NOT NULL COMMENT '奖励数量,如积分数量或礼包数量', + `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '记录状态:1=启用,0=禁用', + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` timestamp NULL DEFAULT NULL COMMENT '更新时间', + `deleted_at` timestamp NULL DEFAULT NULL COMMENT '软删除时间戳', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_rule_id_day_number_deleted` (`rule_id`,`day_number`,`deleted_at`) COMMENT '确保规则下每天唯一奖励', + KEY `idx_rule_id_status_deleted` (`rule_id`,`status`,`deleted_at`) COMMENT '规则ID和状态查询索引', + CONSTRAINT `fk_sign_in_reward_details_rule_id` FOREIGN KEY (`rule_id`) REFERENCES `sign_in_reward_rules` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='签到奖励详情表'; + +-- ---------------------------- +-- Table structure for sign_in_reward_rules +-- ---------------------------- +DROP TABLE IF EXISTS `sign_in_reward_rules`; +CREATE TABLE `sign_in_reward_rules` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `rule_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '规则名称,如“7天签到活动”', + `cycle_days` int NOT NULL COMMENT '奖励周期天数,如7天', + `start_date` date NOT NULL COMMENT '活动开始日期', + `end_date` date NOT NULL COMMENT '活动结束日期', + `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '规则状态:1=启用,0=禁用', + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` timestamp NULL DEFAULT NULL COMMENT '更新时间', + `deleted_at` timestamp NULL DEFAULT NULL COMMENT '软删除时间戳', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_rule_name_deleted` (`rule_name`,`deleted_at`) COMMENT '确保规则名称唯一', + KEY `idx_status_start_end_deleted` (`status`,`start_date`,`end_date`,`deleted_at`) COMMENT '规则状态和活动时间查询索引' +) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='签到奖励规则表'; -- ---------------------------- -- Table structure for user_chapter_purchases @@ -359,12 +271,6 @@ CREATE TABLE `user_chapter_purchases` ( CONSTRAINT `fk_user_chapter_purchases_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户章节购买记录表'; --- ---------------------------- --- Records of user_chapter_purchases --- ---------------------------- -BEGIN; -COMMIT; - -- ---------------------------- -- Table structure for user_follow_authors -- ---------------------------- @@ -382,12 +288,6 @@ CREATE TABLE `user_follow_authors` ( CONSTRAINT `fk_user_follow_authors_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户关注作者表'; --- ---------------------------- --- Records of user_follow_authors --- ---------------------------- -BEGIN; -COMMIT; - -- ---------------------------- -- Table structure for user_points_logs -- ---------------------------- @@ -406,12 +306,6 @@ CREATE TABLE `user_points_logs` ( CONSTRAINT `fk_user_points_logs_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户积分流水表'; --- ---------------------------- --- Records of user_points_logs --- ---------------------------- -BEGIN; -COMMIT; - -- ---------------------------- -- Table structure for user_read_history -- ---------------------------- @@ -432,12 +326,6 @@ CREATE TABLE `user_read_history` ( CONSTRAINT `fk_user_read_history_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户阅读历史记录表'; --- ---------------------------- --- Records of user_read_history --- ---------------------------- -BEGIN; -COMMIT; - -- ---------------------------- -- Table structure for user_read_records -- ---------------------------- @@ -459,10 +347,29 @@ CREATE TABLE `user_read_records` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户阅读记录表'; -- ---------------------------- --- Records of user_read_records +-- Table structure for user_sign_in_logs -- ---------------------------- -BEGIN; -COMMIT; +DROP TABLE IF EXISTS `user_sign_in_logs`; +CREATE TABLE `user_sign_in_logs` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `user_id` bigint NOT NULL COMMENT '用户ID,关联 users 表', + `rule_id` bigint NOT NULL COMMENT '规则ID,关联 sign_in_reward_rules 表', + `reward_detail_id` bigint NOT NULL COMMENT '奖励详情ID,关联 sign_in_reward_details 表', + `sign_in_date` date NOT NULL COMMENT '签到日期', + `quantity` int NOT NULL COMMENT '奖励数量,如积分数量或礼包数量', + `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '记录状态:1=有效,0=无效', + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` timestamp NULL DEFAULT NULL COMMENT '更新时间', + `deleted_at` timestamp NULL DEFAULT NULL COMMENT '软删除时间戳', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_user_rule_sign_in_date_deleted` (`user_id`,`rule_id`,`sign_in_date`,`deleted_at`) COMMENT '确保用户在规则下每日唯一签到', + KEY `idx_user_id_rule_id_deleted` (`user_id`,`rule_id`,`deleted_at`) COMMENT '用户ID和规则ID查询索引', + KEY `fk_user_sign_in_logs_rule_id` (`rule_id`), + KEY `fk_user_sign_in_logs_reward_detail_id` (`reward_detail_id`), + CONSTRAINT `fk_user_sign_in_logs_reward_detail_id` FOREIGN KEY (`reward_detail_id`) REFERENCES `sign_in_reward_details` (`id`) ON DELETE CASCADE, + CONSTRAINT `fk_user_sign_in_logs_rule_id` FOREIGN KEY (`rule_id`) REFERENCES `sign_in_reward_rules` (`id`) ON DELETE CASCADE, + CONSTRAINT `fk_user_sign_in_logs_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户签到记录表'; -- ---------------------------- -- Table structure for users @@ -478,26 +385,11 @@ CREATE TABLE `users` ( `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '注册时间', `updated_at` timestamp NULL DEFAULT NULL COMMENT '更新时间', `deleted_at` timestamp NULL DEFAULT NULL COMMENT '软删除时间戳', + `background_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '作者背景图', + `attention_count` int DEFAULT '0' COMMENT '关注他人的数量 attention count', PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`), KEY `idx_email_deleted` (`email`,`deleted_at`) ) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户表'; --- ---------------------------- --- Records of users --- ---------------------------- -BEGIN; -INSERT INTO `users` (`id`, `username`, `password_hash`, `avatar`, `email`, `points`, `created_at`, `updated_at`, `deleted_at`) VALUES (1, 'john_doe', '$2a$10$cin1jkkEiKg0GW2blAWEb.1V8QeUCHV350yZ6sTbmIC/4fdbFqnTW', 'https://example.com/avatars/john.jpg', 'john.doe@example.com', 150, '2025-06-01 09:00:00', '2025-07-05 14:20:30', NULL); -INSERT INTO `users` (`id`, `username`, `password_hash`, `avatar`, `email`, `points`, `created_at`, `updated_at`, `deleted_at`) VALUES (2, 'jane_smith', '$2a$10$cin1jkkEiKg0GW2blAWEb.1V8QeUCHV350yZ6sTbmIC/4fdbFqnTW', 'https://example.com/avatars/jane.jpg', 'jane.smith@example.com', 320, '2025-06-02 12:15:45', '2025-07-06 10:10:10', NULL); -INSERT INTO `users` (`id`, `username`, `password_hash`, `avatar`, `email`, `points`, `created_at`, `updated_at`, `deleted_at`) VALUES (3, 'alice_wong', '$2a$10$cin1jkkEiKg0GW2blAWEb.1V8QeUCHV350yZ6sTbmIC/4fdbFqnTW', NULL, 'alice.wong@example.com', 50, '2025-06-03 15:30:22', NULL, NULL); -INSERT INTO `users` (`id`, `username`, `password_hash`, `avatar`, `email`, `points`, `created_at`, `updated_at`, `deleted_at`) VALUES (4, 'bob_lee', '$2a$10$cin1jkkEiKg0GW2blAWEb.1V8QeUCHV350yZ6sTbmIC/4fdbFqnTW', 'https://example.com/avatars/bob.jpg', 'bob.lee@example.com', 200, '2025-06-04 08:45:00', '2025-07-07 16:00:00', NULL); -INSERT INTO `users` (`id`, `username`, `password_hash`, `avatar`, `email`, `points`, `created_at`, `updated_at`, `deleted_at`) VALUES (5, 'emma_brown', '$2a$10$cin1jkkEiKg0GW2blAWEb.1V8QeUCHV350yZ6sTbmIC/4fdbFqnTW', NULL, 'emma.brown@example.com', 0, '2025-06-05 11:20:15', NULL, NULL); -INSERT INTO `users` (`id`, `username`, `password_hash`, `avatar`, `email`, `points`, `created_at`, `updated_at`, `deleted_at`) VALUES (6, 'MoonScribe', '$2a$10$cin1jkkEiKg0GW2blAWEb.1V8QeUCHV350yZ6sTbmIC/4fdbFqnTW', 'https://example.com/avatars/moonscribe.jpg', 'moonscribe@example.com', 1800, '2023-01-10 09:00:00', '2025-07-10 14:00:00', NULL); -INSERT INTO `users` (`id`, `username`, `password_hash`, `avatar`, `email`, `points`, `created_at`, `updated_at`, `deleted_at`) VALUES (7, 'NebulaTale', '$2a$10$cin1jkkEiKg0GW2blAWEb.1V8QeUCHV350yZ6sTbmIC/4fdbFqnTW', 'https://example.com/avatars/nebulatale.jpg', 'nebulatale@example.com', 2500, '2023-03-15 11:30:00', '2025-06-25 10:15:00', NULL); -INSERT INTO `users` (`id`, `username`, `password_hash`, `avatar`, `email`, `points`, `created_at`, `updated_at`, `deleted_at`) VALUES (8, 'MistWalker', '$2a$10$cin1jkkEiKg0GW2blAWEb.1V8QeUCHV350yZ6sTbmIC/4fdbFqnTW', NULL, 'mistwalker@example.com', 900, '2023-05-20 13:45:00', '2025-05-10 16:20:00', NULL); -INSERT INTO `users` (`id`, `username`, `password_hash`, `avatar`, `email`, `points`, `created_at`, `updated_at`, `deleted_at`) VALUES (9, 'LegendWeaver', '$2a$10$cin1jkkEiKg0GW2blAWEb.1V8QeUCHV350yZ6sTbmIC/4fdbFqnTW', 'https://example.com/avatars/legendweaver.jpg', 'legendweaver@example.com', 3200, '2023-07-25 10:00:00', '2025-07-01 12:30:00', NULL); -INSERT INTO `users` (`id`, `username`, `password_hash`, `avatar`, `email`, `points`, `created_at`, `updated_at`, `deleted_at`) VALUES (10, 'StarBloom', '$2a$10$cin1jkkEiKg0GW2blAWEb.1V8QeUCHV350yZ6sTbmIC/4fdbFqnTW', 'https://example.com/avatars/starbloom.jpg', 'starbloom@example.com', 1400, '2023-09-10 15:00:00', NULL, NULL); -INSERT INTO `users` (`id`, `username`, `password_hash`, `avatar`, `email`, `points`, `created_at`, `updated_at`, `deleted_at`) VALUES (11, 'NightShade', '$2a$10$cin1jkkEiKg0GW2blAWEb.1V8QeUCHV350yZ6sTbmIC/4fdbFqnTW', NULL, 'nightshade@example.com', 700, '2023-11-05 08:30:00', '2025-06-15 09:00:00', '2025-07-05 11:00:00'); -COMMIT; - SET FOREIGN_KEY_CHECKS = 1; diff --git a/noveltest b/noveltest index 4fd67be..772dbaa 100755 Binary files a/noveltest and b/noveltest differ diff --git a/utility/encrypt/aes.go b/utility/encrypt/aes.go new file mode 100644 index 0000000..f62ebf6 --- /dev/null +++ b/utility/encrypt/aes.go @@ -0,0 +1,80 @@ +package encrypt + +import ( + "crypto/aes" + "crypto/cipher" + "encoding/base64" + "encoding/json" + "fmt" + "server/internal/model" +) + +// 内部常量,外部无法访问 +const ( + aesKey = "K8mN2pQ9rS5vX1zA" // 16字节密钥 + aesIV = "H7jL4oP8qR6uW0yZ" // 16字节初始化向量 +) + +// DecryptAdsData 解密广告数据并反序列化 +func DecryptAdsData(encryptedData string) (*model.AdsData, error) { + // 解码base64数据 + ciphertext, err := base64.StdEncoding.DecodeString(encryptedData) + if err != nil { + return nil, fmt.Errorf("base64 decode failed: %v", err) + } + + // 创建AES cipher + block, err := aes.NewCipher([]byte(aesKey)) + if err != nil { + return nil, fmt.Errorf("aes new cipher failed: %v", err) + } + + // 检查数据长度 + if len(ciphertext) < aes.BlockSize { + return nil, fmt.Errorf("ciphertext too short") + } + + // 创建CBC模式 + mode := cipher.NewCBCDecrypter(block, []byte(aesIV)) + + // 解密 + plaintext := make([]byte, len(ciphertext)) + mode.CryptBlocks(plaintext, ciphertext) + + // 去除PKCS7填充 + plaintext, err = pkcs7Unpad(plaintext) + if err != nil { + return nil, fmt.Errorf("pkcs7 unpad failed: %v", err) + } + + // 反序列化JSON + var adsData model.AdsData + err = json.Unmarshal(plaintext, &adsData) + if err != nil { + return nil, fmt.Errorf("json unmarshal failed: %v", err) + } + + return &adsData, nil +} + +// pkcs7Unpad 去除PKCS7填充 +func pkcs7Unpad(data []byte) ([]byte, error) { + length := len(data) + if length == 0 { + return nil, fmt.Errorf("invalid padding") + } + + padding := int(data[length-1]) + if padding > length { + return nil, fmt.Errorf("invalid padding") + } + + // 验证填充 + for i := length - padding; i < length; i++ { + if data[i] != byte(padding) { + return nil, fmt.Errorf("invalid padding") + } + } + + return data[:length-padding], nil +} diff --git a/utility/encrypt/password_test.go b/utility/encrypt/password_test.go new file mode 100644 index 0000000..b5a7148 --- /dev/null +++ b/utility/encrypt/password_test.go @@ -0,0 +1,52 @@ +package encrypt + +import "testing" + +func TestEncryptPassword(t *testing.T) { + type args struct { + password string + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + // TODO: Add test cases. + {"case1", args{"Yh243480917"}, "$2a$10$Q6yGw6W./eG0M9uKO1KFUeTs9FEndsPzHL0iTgvf4y/cJ9L3Rnqb.", false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := EncryptPassword(tt.args.password) + if (err != nil) != tt.wantErr { + t.Errorf("EncryptPassword() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("EncryptPassword() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestComparePassword(t *testing.T) { + type args struct { + hashedPassword string + password string + } + tests := []struct { + name string + args args + want bool + }{ + // TODO: Add test cases. + {"case1", args{"$2a$10$lKkwK05oskB.YPnXcGH2pupQxoK.02GDdGxWpstxc1keiWVFekhJ6", "Y"}, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := ComparePassword(tt.args.hashedPassword, tt.args.password); got != tt.want { + t.Errorf("ComparePassword() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/utility/i18n/i18n.go b/utility/i18n/i18n.go index f29dab3..a3a162d 100644 --- a/utility/i18n/i18n.go +++ b/utility/i18n/i18n.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "strings" + "sync" "github.com/gogf/gf/v2/frame/g" ) @@ -111,13 +112,20 @@ var languageMap = map[string]map[string]string{ "user_follow_author_create_failed": "关注作者失败", "user_follow_author_not_found": "关注记录不存在", "user_follow_author_delete_failed": "取消关注失败", + "user_follow_author_update_failed": "关注作者更新失败", // 作者相关 - "author_query_failed": "作者查询失败", - "author_user_exists": "该用户已绑定作者", - "author_create_failed": "作者创建失败", - "author_not_found": "作者不存在", - "author_update_failed": "作者更新失败", - "author_delete_failed": "作者删除失败", + "author_query_failed": "作者查询失败", + "author_user_exists": "该用户已绑定作者", + "author_create_failed": "作者创建失败", + "author_not_found": "作者不存在", + "author_update_failed": "作者更新失败", + "author_delete_failed": "作者删除失败", + "author_info_failed": "获取作者信息失败", + "not_author": "当前用户不是作者", + "author_id_required": "作者ID不能为空", + "author_review_failed": "作者审核失败", + "author_review_status_invalid": "审核状态无效", + "author_review_remark_too_long": "审核备注过长", // 书架相关 "bookshelve_query_failed": "书架查询失败", "bookshelve_exists": "该书已在书架中", @@ -142,6 +150,60 @@ var languageMap = map[string]map[string]string{ "bookshelf_update_failed": "书架更新失败", "chapter_count_failed": "章节统计失败", "read_chapter_count_failed": "已读章节统计失败", + // 图片上传相关 + "image_file_required": "图片文件不能为空", + "image_type_invalid": "只允许上传图片文件", + "image_format_invalid": "仅支持 jpg、png、gif、webp 格式的图片", + "image_size_exceeded": "图片大小不能超过1MB", + "image_read_failed": "无法读取图片内容", + "image_upload_failed": "图片上传失败", + // 推荐相关 + "book_recommendation_query_failed": "推荐查询失败", + "book_recommendation_exists": "该类型下该书籍已存在推荐", + "book_recommendation_create_failed": "推荐创建失败", + "book_recommendation_not_found": "推荐不存在", + "book_recommendation_update_failed": "推荐更新失败", + "book_recommendation_delete_failed": "推荐删除失败", + // 签到奖励规则相关 + "sign_in_reward_rule_query_failed": "签到奖励规则查询失败", + "sign_in_reward_rule_exists": "规则名称已存在", + "sign_in_reward_rule_create_failed": "签到奖励规则创建失败", + "sign_in_reward_rule_not_found": "签到奖励规则不存在", + "sign_in_reward_rule_update_failed": "签到奖励规则更新失败", + "sign_in_reward_rule_delete_failed": "签到奖励规则删除失败", + // 签到奖励明细相关 + "sign_in_reward_detail_query_failed": "签到奖励明细查询失败", + "sign_in_reward_detail_exists": "该规则下该天奖励已存在", + "sign_in_reward_detail_create_failed": "签到奖励明细创建失败", + "sign_in_reward_detail_not_found": "签到奖励明细不存在", + "sign_in_reward_detail_update_failed": "签到奖励明细更新失败", + "sign_in_reward_detail_delete_failed": "签到奖励明细删除失败", + // 签到日志相关 + "user_sign_in_log_query_failed": "签到日志查询失败", + "user_sign_in_log_create_failed": "签到日志创建失败", + "user_points_log_create_failed": "积分日志创建失败", + "user_points_update_failed": "用户积分更新失败", + // 用户阅读历史相关 + "user_read_history_query_failed": "历史记录查询失败", + "user_read_history_update_failed": "历史记录更新失败", + "user_read_history_create_failed": "历史记录创建失败", + "user_read_history_delete_failed": "历史记录删除失败", + "user_read_history_not_found": "历史记录不存在", + "user_id_or_book_id_or_chapter_id_invalid": "用户ID、书籍ID或章节ID无效", + "user_id_or_book_ids_invalid": "用户ID或书籍ID列表无效", + // 任务相关 + "task_query_failed": "任务查询失败", + "task_add_failed": "任务添加失败", + "task_edit_failed": "任务编辑失败", + "task_delete_failed": "任务删除失败", + "task_not_found": "任务不存在", + "task_log_query_failed": "任务日志查询失败", + // 任务类型相关 + "task_type_query_failed": "任务类型查询失败", + "task_type_add_failed": "任务类型添加失败", + "task_type_edit_failed": "任务类型编辑失败", + "task_type_delete_failed": "任务类型删除失败", + "task_type_not_found": "任务类型不存在", }, "en-US": { "hello": "Hello World!", @@ -239,12 +301,20 @@ var languageMap = map[string]map[string]string{ "user_follow_author_not_found": "Follow record not found", "user_follow_author_delete_failed": "Unfollow failed", // Author related - "author_query_failed": "Author query failed", - "author_user_exists": "User already has an author profile", - "author_create_failed": "Author creation failed", - "author_not_found": "Author not found", - "author_update_failed": "Author update failed", - "author_delete_failed": "Author deletion failed", + "author_query_failed": "Author query failed", + "author_user_exists": "User already has an author profile", + "author_create_failed": "Author creation failed", + "author_not_found": "Author not found", + "author_update_failed": "Author update failed", + "author_delete_failed": "Author deletion failed", + "author_info_failed": "Failed to get author info", + "not_author": "Current user is not an author", + "author_id_required": "Author ID cannot be empty", + "author_review_failed": "Author review failed", + "author_review_status_invalid": "Invalid review status", + "author_review_remark_too_long": "Review remark is too long", + "user_follow_author_update_failed": "Author update failed", + // Bookshelve related "bookshelve_query_failed": "Bookshelf query failed", "bookshelve_exists": "Book already in bookshelf", @@ -269,11 +339,92 @@ var languageMap = map[string]map[string]string{ "bookshelf_update_failed": "Bookshelf update failed", "chapter_count_failed": "Chapter count failed", "read_chapter_count_failed": "Read chapter count failed", + // 图片上传相关 + "image_file_required": "Image file is required", + "image_type_invalid": "Only image files are allowed", + "image_format_invalid": "Only jpg, png, gif, webp formats are supported", + "image_size_exceeded": "Image size cannot exceed 1MB", + "image_read_failed": "Failed to read image file", + "image_upload_failed": "Image upload failed", + // Recommendation related + "book_recommendation_query_failed": "Recommendation query failed", + "book_recommendation_exists": "The book already exists in this recommendation type", + "book_recommendation_create_failed": "Recommendation creation failed", + "book_recommendation_not_found": "Recommendation not found", + "book_recommendation_update_failed": "Recommendation update failed", + "book_recommendation_delete_failed": "Recommendation deletion failed", + // 签到奖励规则相关 + "sign_in_reward_rule_query_failed": "Sign-in reward rule query failed", + "sign_in_reward_rule_exists": "Rule name already exists", + "sign_in_reward_rule_create_failed": "Sign-in reward rule creation failed", + "sign_in_reward_rule_not_found": "Sign-in reward rule not found", + "sign_in_reward_rule_update_failed": "Sign-in reward rule update failed", + "sign_in_reward_rule_delete_failed": "Sign-in reward rule deletion failed", + // Sign-in reward detail related + "sign_in_reward_detail_query_failed": "Sign-in reward detail query failed", + "sign_in_reward_detail_exists": "Reward for this day already exists under the rule", + "sign_in_reward_detail_create_failed": "Sign-in reward detail creation failed", + "sign_in_reward_detail_not_found": "Sign-in reward detail not found", + "sign_in_reward_detail_update_failed": "Sign-in reward detail update failed", + "sign_in_reward_detail_delete_failed": "Sign-in reward detail deletion failed", + // Sign-in log related + "user_sign_in_log_query_failed": "Sign-in log query failed", + "user_sign_in_log_create_failed": "Sign-in log creation failed", + "user_points_log_create_failed": "Points log creation failed", + "user_points_update_failed": "User points update failed", + // User read history related + "user_read_history_query_failed": "Read history query failed", + "user_read_history_update_failed": "Read history update failed", + "user_read_history_create_failed": "Read history creation failed", + "user_read_history_delete_failed": "Read history deletion failed", + "user_read_history_not_found": "Read history not found", + "user_id_or_book_id_or_chapter_id_invalid": "User ID, Book ID or Chapter ID is invalid", + "user_id_or_book_ids_invalid": "User ID or Book IDs is invalid", + // Task related + "task_query_failed": "Task query failed", + "task_add_failed": "Task add failed", + "task_edit_failed": "Task edit failed", + "task_delete_failed": "Task delete failed", + "task_not_found": "Task not found", + "task_log_query_failed": "Task log query failed", + // TaskType related + "task_type_query_failed": "Task type query failed", + "task_type_add_failed": "Task type add failed", + "task_type_edit_failed": "Task type edit failed", + "task_type_delete_failed": "Task type delete failed", + "task_type_not_found": "Task type not found", }, } +// I18n 单例结构体 +type I18n struct { + languageMap map[string]map[string]string + mu sync.RWMutex +} + +var ( + instance *I18n + once sync.Once +) + +// GetInstance 获取单例实例 +func GetInstance() *I18n { + once.Do(func() { + instance = &I18n{ + languageMap: languageMap, + } + }) + return instance +} + +// init 初始化函数 +func init() { + // 确保单例实例被创建 + GetInstance() +} + // GetLanguage 从请求头或查询参数获取语言设置 -func GetLanguage(ctx context.Context) string { +func (i *I18n) GetLanguage(ctx context.Context) string { // 优先从请求头获取 if r := g.RequestFromCtx(ctx); r != nil { // 从 Accept-Language 头获取 @@ -302,9 +453,12 @@ func GetLanguage(ctx context.Context) string { } // T 翻译消息 -func T(ctx context.Context, key string) string { - lang := GetLanguage(ctx) - if messages, exists := languageMap[lang]; exists { +func (i *I18n) T(ctx context.Context, key string) string { + lang := i.GetLanguage(ctx) + i.mu.RLock() + defer i.mu.RUnlock() + + if messages, exists := i.languageMap[lang]; exists { if message, exists := messages[key]; exists { return message } @@ -312,7 +466,7 @@ func T(ctx context.Context, key string) string { // 如果当前语言没有找到,尝试默认语言 if lang != DefaultLanguage { - if messages, exists := languageMap[DefaultLanguage]; exists { + if messages, exists := i.languageMap[DefaultLanguage]; exists { if message, exists := messages[key]; exists { return message } @@ -324,14 +478,27 @@ func T(ctx context.Context, key string) string { } // Tf 翻译消息并格式化 -func Tf(ctx context.Context, key string, args ...interface{}) string { - message := T(ctx, key) +func (i *I18n) Tf(ctx context.Context, key string, args ...interface{}) string { + message := i.T(ctx, key) if len(args) > 0 { message = fmt.Sprintf(message, args...) } return message } +// 为了保持向后兼容,提供全局函数 +func GetLanguage(ctx context.Context) string { + return GetInstance().GetLanguage(ctx) +} + +func T(ctx context.Context, key string) string { + return GetInstance().T(ctx, key) +} + +func Tf(ctx context.Context, key string, args ...interface{}) string { + return GetInstance().Tf(ctx, key, args...) +} + // isSupportedLanguage 检查是否为支持的语言 func isSupportedLanguage(lang string) bool { for _, supported := range SupportedLanguages { diff --git a/utility/jwt/jwt.go b/utility/jwt/jwt.go index 69915be..5e5aed8 100644 --- a/utility/jwt/jwt.go +++ b/utility/jwt/jwt.go @@ -1,7 +1,9 @@ package jwt import ( + "context" "errors" + "github.com/gogf/gf/v2/frame/g" "server/utility/ecode" "strings" "time" @@ -70,9 +72,23 @@ func ParseToken(tokenString string) (*TokenOut, error) { return nil, ecode.InvalidOperation.Sub("invalid_token") } + blacklist, err := isBlacklist(claims.JTI) + if err != nil { + return nil, ecode.Fail.Sub("token_parse_failed") + } + if blacklist { + return nil, ecode.InvalidOperation.Sub("token_expired") + } return &TokenOut{ UserId: claims.UserId, Role: claims.Role, JTI: claims.JTI, }, nil } +func isBlacklist(uuid string) (bool, error) { + exitst, err := g.Redis().Exists(context.Background(), "blacklist:"+uuid) + if err != nil { + return false, err + } + return exitst > 0, nil +} diff --git a/utility/myCasbin/casbin.go b/utility/myCasbin/casbin.go index e70de13..d55351a 100644 --- a/utility/myCasbin/casbin.go +++ b/utility/myCasbin/casbin.go @@ -46,8 +46,11 @@ func init() { enforcer.AddPolicy("guest", "/chapter/app/list", "GET", "App获取章节列表") enforcer.AddPolicy("guest", "/chapter/app/detail", "GET", "App获取章节详情") enforcer.AddPolicy("guest", "/category", "GET", "获取分类列表") + enforcer.AddPolicy("guest", "/recommend/app/list", "GET", "App获取推荐列表") + enforcer.AddPolicy("guest", "/activity/sign", "GET", "用户签到任务列表") + enforcer.AddPolicy("guest", "/task/appList", "GET", "App端任务列表") + enforcer.AddPolicy("guest", "/system/version", "GET", "获取系统版本信息") } - // user { // book @@ -67,6 +70,9 @@ func init() { // author follow/unfollow enforcer.AddPolicy("user", "/author/follow", "POST", "关注作者") enforcer.AddPolicy("user", "/author/unfollow", "POST", "取消关注作者") + enforcer.AddPolicy("user", "/author/detail", "GET", "获取作者详情") + enforcer.AddPolicy("user", "/activity/sign", "POST", "用户签到") + enforcer.AddPolicy("user", "/author/apply", "POST", "申请成为作者") } // author { @@ -75,17 +81,22 @@ func init() { enforcer.AddPolicy("author", "/book", "POST", "新增图书") enforcer.AddPolicy("author", "/book", "PUT", "编辑图书") enforcer.AddPolicy("author", "/book", "DELETE", "删除图书") + enforcer.AddPolicy("author", "/book/coverImage", "POST", "上传图书封面图") + // chapter enforcer.AddPolicy("author", "/chapter", "GET", "获取章节列表") enforcer.AddPolicy("author", "/chapter", "POST", "创建章节") enforcer.AddPolicy("author", "/chapter", "PUT", "更新章节") enforcer.AddPolicy("author", "/chapter", "DELETE", "删除章节") + + enforcer.AddPolicy("author", "/author/info", "GET", "获取作者基础信息") } // admin { // book enforcer.AddPolicy("admin", "/book/set-featured", "POST", "设置书籍精选状态") enforcer.AddPolicy("admin", "/book/set-recommended", "POST", "设置书籍推荐状态") + enforcer.AddPolicy("admin", "/book/set-hot", "POST", "设置书籍最热状态") // author enforcer.AddPolicy("admin", "/author", "GET", "获取作者列表") enforcer.AddPolicy("admin", "/author", "POST", "创建作者") @@ -100,6 +111,37 @@ func init() { // admin enforcer.AddPolicy("admin", "/admin/info", "GET", "获取管理员用户信息") enforcer.AddPolicy("admin", "/admin/editPass", "POST", "管理员修改密码") + enforcer.AddPolicy("admin", "/author/review", "POST", "审核作者申请") + enforcer.AddPolicy("admin", "/task", "GET", "获取任务列表") + enforcer.AddPolicy("admin", "/task", "POST", "新增任务") + enforcer.AddPolicy("admin", "/task", "PUT", "编辑任务") + enforcer.AddPolicy("admin", "/task", "DELETE", "删除任务") + enforcer.AddPolicy("admin", "/system/save", "POST", "获取系统版本信息") + + } + // recommend + { + enforcer.AddPolicy("admin", "/recommend", "GET", "获取推荐列表") + enforcer.AddPolicy("admin", "/recommend", "POST", "新增推荐") + enforcer.AddPolicy("admin", "/recommend", "PUT", "编辑推荐") + enforcer.AddPolicy("admin", "/recommend", "DELETE", "删除推荐") + enforcer.AddPolicy("admin", "/recommend/set-status", "POST", "设置推荐状态") + enforcer.AddPolicy("admin", "/recommend/sort-order", "POST", "设置推荐排序") + enforcer.AddPolicy("admin", "/recommend/info", "GET", "获取推荐详情") + } + // activity 签到奖励规则相关接口 + { + enforcer.AddPolicy("admin", "/activity", "GET", "获取签到奖励规则全信息") + enforcer.AddPolicy("admin", "/activity", "POST", "新增签到奖励规则全信息") + enforcer.AddPolicy("admin", "/activity/*", "PUT", "编辑签到奖励规则全信息") + enforcer.AddPolicy("admin", "/activity/*", "DELETE", "删除签到奖励规则全信息") + enforcer.AddPolicy("admin", "/activity/*/status", "PATCH", "设置签到奖励规则状态全信息") + enforcer.AddPolicy("admin", "/activity/items", "GET", "获取签到奖励明细列表") + enforcer.AddPolicy("admin", "/activity/item", "POST", "新增签到奖励明细") + enforcer.AddPolicy("admin", "/activity/item/*", "PUT", "编辑签到奖励明细") + enforcer.AddPolicy("admin", "/activity/item/*", "DELETE", "删除签到奖励明细") + enforcer.AddPolicy("admin", "/activity/item/*", "GET", "获取单个签到奖励明细") + enforcer.AddPolicy("admin", "/activity/item/*/status", "PATCH", "设置签到奖励明细状态") } instance = &myCasbin{Enforcer: enforcer} diff --git a/utility/oss/amazon_s3/amazon-s3.go b/utility/oss/amazon_s3/amazon-s3.go index 52b9c62..8c0c80a 100644 --- a/utility/oss/amazon_s3/amazon-s3.go +++ b/utility/oss/amazon_s3/amazon-s3.go @@ -2,6 +2,9 @@ package amazons3 import ( "bytes" + "context" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" "server/utility/oss" "github.com/aws/aws-sdk-go/aws" @@ -9,65 +12,42 @@ import ( "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/s3/s3manager" "github.com/gogf/gf/crypto/gmd5" - "github.com/gogf/gf/errors/gerror" - "github.com/gogf/gf/frame/g" - "github.com/gogf/gf/net/ghttp" "github.com/gogf/gf/os/gfile" "github.com/gogf/gf/os/glog" "github.com/gogf/gf/os/gtime" "github.com/gogf/gf/util/grand" ) -type AmazonS3Client struct{} - -func (c *AmazonS3Client) initSess() (*session.Session, error) { - key := g.Config().GetString("aws.s3.key") - secret := g.Config().GetString("aws.s3.secret") - region := g.Config().GetString("aws.s3.region") - if len(key) == 0 || len(secret) == 0 { - return nil, gerror.New("aws s3 配置错误") - } - config := &aws.Config{ - Region: aws.String(region), - Credentials: credentials.NewStaticCredentials(key, secret, ""), - } - sess := session.Must(session.NewSession(config)) - return sess, nil +type AmazonS3Client struct { + key string + secret string + region string + bucket string + sess *session.Session } -func (c *AmazonS3Client) Upload(file interface{}) (string, error) { - uploadFile, ok := file.(*ghttp.UploadFile) - if !ok { - return "", gerror.New("参数类型错误") - } - bucket := g.Config().GetString("aws.s3.bucket") - if len(bucket) == 0 { - glog.Warning("bucket为空:", bucket) - return "", gerror.New("aws s3 配置错误") - } - sess, err := c.initSess() - if err != nil { - return "", err - } - uploader := s3manager.NewUploader(sess) - f, err := uploadFile.Open() +var amazonS3Client *AmazonS3Client + +func (c *AmazonS3Client) Upload(file *ghttp.UploadFile, folder string) (string, error) { + uploader := s3manager.NewUploader(c.sess) + f, err := file.Open() if err != nil { return "", err } defer f.Close() - body := make([]byte, uploadFile.Size) + body := make([]byte, file.Size) _, err = f.Read(body) if err != nil { return "", err } - key, err := gmd5.Encrypt(uploadFile.Filename + gtime.Datetime() + grand.Digits(6)) - ext := gfile.Ext(uploadFile.Filename) + key, err := gmd5.Encrypt(file.Filename + gtime.Datetime() + grand.Digits(6)) + ext := gfile.Ext(file.Filename) if err != nil { return "", err } r, err := uploader.Upload(&s3manager.UploadInput{ - Bucket: aws.String(bucket), - Key: aws.String(key + ext), + Bucket: aws.String(c.bucket), + Key: aws.String(folder + "/" + key + ext), Body: bytes.NewReader(body), }) if err != nil { @@ -77,16 +57,7 @@ func (c *AmazonS3Client) Upload(file interface{}) (string, error) { } func (c *AmazonS3Client) UploadLocalFile(path, name string) (string, error) { - bucket := g.Config().GetString("aws.s3.bucket") - if len(bucket) == 0 { - glog.Warning("bucket为空:", bucket) - return "", gerror.New("aws s3 配置错误") - } - sess, err := c.initSess() - if err != nil { - return "", err - } - uploader := s3manager.NewUploader(sess) + uploader := s3manager.NewUploader(c.sess) f, err := gfile.Open(path) if err != nil { return "", err @@ -98,7 +69,7 @@ func (c *AmazonS3Client) UploadLocalFile(path, name string) (string, error) { return "", err } r, err := uploader.Upload(&s3manager.UploadInput{ - Bucket: aws.String(bucket), + Bucket: aws.String(c.bucket), Key: aws.String(name), Body: bytes.NewReader(body), }) @@ -110,5 +81,26 @@ func (c *AmazonS3Client) UploadLocalFile(path, name string) (string, error) { } func init() { - oss.RegisterClient("amazon_s3", &AmazonS3Client{}) + ctx := context.Background() + key := g.Config().MustGet(ctx, "oss.s3.key").String() + secret := g.Config().MustGet(ctx, "oss.s3.secret").String() + region := g.Config().MustGet(ctx, "oss.s3.region").String() + bucket := g.Config().MustGet(ctx, "oss.s3.bucket").String() + + if key == "" || secret == "" || region == "" || bucket == "" { + glog.Error("请配置 OSS 配置") + } + config := &aws.Config{ + Region: aws.String(region), + Credentials: credentials.NewStaticCredentials(key, secret, ""), + } + sess := session.Must(session.NewSession(config)) + amazonS3Client := AmazonS3Client{ + bucket: bucket, + key: key, + region: region, + secret: secret, + sess: sess, + } + oss.RegisterClient("amazon_s3", &amazonS3Client) } diff --git a/utility/oss/oss.go b/utility/oss/oss.go index 46a9397..66e9b29 100644 --- a/utility/oss/oss.go +++ b/utility/oss/oss.go @@ -1,7 +1,9 @@ package oss +import "github.com/gogf/gf/v2/net/ghttp" + type OSSClient interface { - Upload(file interface{}) (string, error) + Upload(file *ghttp.UploadFile, folder string) (string, error) UploadLocalFile(path, name string) (string, error) } diff --git a/utility/state_machine/ads_state_machine.go b/utility/state_machine/ads_state_machine.go new file mode 100644 index 0000000..97fe7ee --- /dev/null +++ b/utility/state_machine/ads_state_machine.go @@ -0,0 +1,115 @@ +package state_machine + +import ( + "context" + "sync" + + "github.com/gogf/gf/v2/os/glog" + + "server/internal/consts" + "server/utility/ecode" +) + +// AdStateMachine 广告状态机 +type AdStateMachine struct { + mu sync.RWMutex // 读写锁,保证并发安全 + // 状态流转规则 + transitions map[consts.AdState][]consts.AdState + // 终止状态 + terminalStates map[consts.AdState]bool +} + +// NewAdStateMachine 创建新的广告状态机 +func NewAdStateMachine() *AdStateMachine { + sm := &AdStateMachine{ + transitions: make(map[consts.AdState][]consts.AdState), + terminalStates: make(map[consts.AdState]bool), + } + + // 初始化状态流转规则 + sm.initTransitions() + return sm +} + +// initTransitions 初始化状态流转规则 +func (sm *AdStateMachine) initTransitions() { + // 定义状态流转规则 + sm.transitions = map[consts.AdState][]consts.AdState{ + consts.StateFetchSuccess: {consts.StateDisplayFailed, consts.StateDisplaySuccess}, + consts.StateDisplaySuccess: {consts.StateNotWatched, consts.StateWatched}, + consts.StateWatched: {consts.StateNotClicked, consts.StateClicked}, + consts.StateClicked: {consts.StateNotDownloaded, consts.StateDownloaded}, + consts.StateNotDownloaded: {consts.StateDownloaded}, + } + + // 定义终止状态 + sm.terminalStates = map[consts.AdState]bool{ + consts.StateFetchFailed: true, + consts.StateDisplayFailed: true, + consts.StateNotWatched: true, + consts.StateNotClicked: true, + consts.StateDownloaded: true, + } +} + +// CanTransition 检查是否可以转换到目标状态 +func (sm *AdStateMachine) CanTransition(fromState, toState consts.AdState) bool { + sm.mu.RLock() + defer sm.mu.RUnlock() + + // 检查是否为终止状态 + if sm.terminalStates[fromState] { + return false + } + + // 检查允许的转换 + allowedStates, exists := sm.transitions[fromState] + if !exists { + return false + } + + for _, allowed := range allowedStates { + if allowed == toState { + return true + } + } + + return false +} + +// Transition 执行状态转换 +func (sm *AdStateMachine) Transition(ctx context.Context, flowID string, userID int64, fromState, toState consts.AdState, reason string) error { + sm.mu.Lock() + defer sm.mu.Unlock() + + // 验证状态转换 + if !sm.CanTransition(fromState, toState) { + glog.Warningf(ctx, "Invalid state transition: %s -> %s, FlowID: %s, UserID: %d, Reason: %s", + consts.GetStateDescription(fromState), consts.GetStateDescription(toState), flowID, userID, reason) + return ecode.Params.Sub("invalid_state_transition") + } + + // 记录状态转换日志 + glog.Infof(ctx, "State transition: %s -> %s, FlowID: %s, UserID: %d, Reason: %s", + consts.GetStateDescription(fromState), consts.GetStateDescription(toState), flowID, userID, reason) + + return nil +} + +// IsTerminalState 检查是否为终止状态 +func (sm *AdStateMachine) IsTerminalState(state consts.AdState) bool { + sm.mu.RLock() + defer sm.mu.RUnlock() + return sm.terminalStates[state] +} + +// GetAvailableTransitions 获取当前状态可用的转换 +func (sm *AdStateMachine) GetAvailableTransitions(currentState consts.AdState) []consts.AdState { + sm.mu.RLock() + defer sm.mu.RUnlock() + + if transitions, exists := sm.transitions[currentState]; exists { + return transitions + } + return []consts.AdState{} +} diff --git a/utility/state_machine/ads_state_machine_test.go b/utility/state_machine/ads_state_machine_test.go new file mode 100644 index 0000000..ee5f894 --- /dev/null +++ b/utility/state_machine/ads_state_machine_test.go @@ -0,0 +1,190 @@ +// Package state_machine 提供广告状态机的单元测试 +package state_machine + +import ( + "server/internal/consts" + "testing" +) + +// TestNewAdStateMachine 测试创建新的广告状态机 +func TestNewAdStateMachine(t *testing.T) { + sm := NewAdStateMachine() + if sm == nil { + t.Fatal("期望创建非空的状态机,但得到了nil") + } + if sm.transitions == nil { + t.Error("期望transitions非空,但得到了nil") + } + if sm.terminalStates == nil { + t.Error("期望terminalStates非空,但得到了nil") + } +} + +// TestAdStateMachine_CanTransition 测试状态转换验证功能 +func TestAdStateMachine_CanTransition(t *testing.T) { + sm := NewAdStateMachine() + + // 测试有效转换 + if !sm.CanTransition(consts.StateFetchSuccess, consts.StateDisplaySuccess) { + t.Error("期望从拉取成功到显示成功的转换是有效的") + } + + // 测试无效转换 + if sm.CanTransition(consts.StateFetchSuccess, consts.StateNotWatched) { + t.Error("期望从拉取成功到未观看完成的转换是无效的") + } + + // 测试从终止状态转换 + if sm.CanTransition(consts.StateFetchFailed, consts.StateDisplaySuccess) { + t.Error("期望从拉取失败终止状态无法转换到其他状态") + } +} + +// TestAdStateMachine_IsTerminalState 测试终止状态检查功能 +func TestAdStateMachine_IsTerminalState(t *testing.T) { + sm := NewAdStateMachine() + + // 测试终止状态 + if !sm.IsTerminalState(consts.StateDisplayFailed) { + t.Error("期望显示失败状态是终止状态") + } + + // 测试非终止状态 + if sm.IsTerminalState(consts.StateFetchSuccess) { + t.Error("期望拉取成功状态不是终止状态") + } +} + +// TestAdStateMachine_GetAvailableTransitions 测试获取可用转换状态功能 +func TestAdStateMachine_GetAvailableTransitions(t *testing.T) { + sm := NewAdStateMachine() + + // 测试有可用转换的状态 + transitions := sm.GetAvailableTransitions(consts.StateFetchSuccess) + if len(transitions) == 0 { + t.Error("期望拉取成功状态有可用的转换状态") + } + + // 测试终止状态 + transitions = sm.GetAvailableTransitions(consts.StateDisplayFailed) + if len(transitions) != 0 { + t.Error("期望显示失败终止状态没有可用的转换状态") + } + + // 测试不存在的状态 + transitions = sm.GetAvailableTransitions(999) // 不存在的状态 + if len(transitions) != 0 { + t.Error("期望不存在的状态没有可用的转换状态") + } +} + +// TestAdStateMachine_StateTransitionFlow 测试状态流转路径 +func TestAdStateMachine_StateTransitionFlow(t *testing.T) { + sm := NewAdStateMachine() + + // 测试路径1:拉取失败是终止状态 + if !sm.IsTerminalState(consts.StateFetchFailed) { + t.Error("期望拉取失败是终止状态") + } + + // 测试路径2:拉取成功 -> 显示失败 -> 终止 + if !sm.CanTransition(consts.StateFetchSuccess, consts.StateDisplayFailed) { + t.Error("期望从拉取成功到显示失败的转换是有效的") + } + if !sm.IsTerminalState(consts.StateDisplayFailed) { + t.Error("期望显示失败是终止状态") + } + + // 测试路径3:拉取成功 -> 显示成功 -> 未观看完成 -> 终止 + if !sm.CanTransition(consts.StateFetchSuccess, consts.StateDisplaySuccess) { + t.Error("期望从拉取成功到显示成功的转换是有效的") + } + if !sm.CanTransition(consts.StateDisplaySuccess, consts.StateNotWatched) { + t.Error("期望从显示成功到未观看完成的转换是有效的") + } + if !sm.IsTerminalState(consts.StateNotWatched) { + t.Error("期望未观看完成是终止状态") + } + + // 测试路径4:拉取成功 -> 显示成功 -> 观看完成 -> 未点击 -> 终止 + if !sm.CanTransition(consts.StateDisplaySuccess, consts.StateWatched) { + t.Error("期望从显示成功到观看完成的转换是有效的") + } + if !sm.CanTransition(consts.StateWatched, consts.StateNotClicked) { + t.Error("期望从观看完成到未点击的转换是有效的") + } + if !sm.IsTerminalState(consts.StateNotClicked) { + t.Error("期望未点击是终止状态") + } + + // 测试路径5:拉取成功 -> 显示成功 -> 观看完成 -> 已点击 -> 未下载 -> 已下载 + if !sm.CanTransition(consts.StateWatched, consts.StateClicked) { + t.Error("期望从观看完成到已点击的转换是有效的") + } + if !sm.CanTransition(consts.StateClicked, consts.StateNotDownloaded) { + t.Error("期望从已点击到未下载的转换是有效的") + } + if sm.IsTerminalState(consts.StateNotDownloaded) { + t.Error("期望未下载不是终止状态") + } + if !sm.CanTransition(consts.StateNotDownloaded, consts.StateDownloaded) { + t.Error("期望从未下载到已下载的转换是有效的") + } + + // 测试路径6:拉取成功 -> 显示成功 -> 观看完成 -> 已点击 -> 已下载 -> 终止 + if !sm.CanTransition(consts.StateClicked, consts.StateDownloaded) { + t.Error("期望从已点击到已下载的转换是有效的") + } + if !sm.IsTerminalState(consts.StateDownloaded) { + t.Error("期望已下载是终止状态") + } +} + +// TestAdStateMachine_InvalidTransitions 测试无效的状态转换 +func TestAdStateMachine_InvalidTransitions(t *testing.T) { + sm := NewAdStateMachine() + + // 测试不允许的跳跃转换 + if sm.CanTransition(consts.StateFetchSuccess, consts.StateWatched) { + t.Error("不应该允许从拉取成功直接转换到观看完成") + } + if sm.CanTransition(consts.StateDisplaySuccess, consts.StateClicked) { + t.Error("不应该允许从显示成功直接转换到已点击") + } + if sm.CanTransition(consts.StateWatched, consts.StateDownloaded) { + t.Error("不应该允许从观看完成直接转换到已下载") + } + + // 测试反向转换 + if sm.CanTransition(consts.StateDisplaySuccess, consts.StateFetchSuccess) { + t.Error("不应该允许从显示成功转换回拉取成功") + } + if sm.CanTransition(consts.StateWatched, consts.StateDisplaySuccess) { + t.Error("不应该允许从观看完成转换回显示成功") + } + if sm.CanTransition(consts.StateClicked, consts.StateWatched) { + t.Error("不应该允许从已点击转换回观看完成") + } + if sm.CanTransition(consts.StateDownloaded, consts.StateClicked) { + t.Error("不应该允许从已下载转换回已点击") + } +} + +// TestAdStateMachine_TransitionFromNotDownloaded 测试从未下载状态的转换 +func TestAdStateMachine_TransitionFromNotDownloaded(t *testing.T) { + sm := NewAdStateMachine() + + // 测试从未下载到已下载的转换 + if !sm.CanTransition(consts.StateNotDownloaded, consts.StateDownloaded) { + t.Error("期望从未下载到已下载的转换是有效的") + } + + // 确认未下载状态的可用转换只有已下载 + transitions := sm.GetAvailableTransitions(consts.StateNotDownloaded) + if len(transitions) != 1 { + t.Errorf("期望未下载状态有1个可用转换,但得到了%d个", len(transitions)) + } + if len(transitions) > 0 && transitions[0] != consts.StateDownloaded { + t.Errorf("期望未下载状态的可用转换是已下载,但得到了%d", transitions[0]) + } +}