完善功能
This commit is contained in:
@ -5,8 +5,12 @@ import (
|
||||
"server/internal/dao"
|
||||
"server/internal/model"
|
||||
"server/internal/model/do"
|
||||
"server/internal/model/entity"
|
||||
"server/internal/service"
|
||||
"server/utility/ecode"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
type sChapter struct{}
|
||||
@ -38,6 +42,7 @@ func (s *sChapter) List(ctx context.Context, in *model.ChapterListIn) (out *mode
|
||||
return
|
||||
}
|
||||
|
||||
// Create creates a new chapter
|
||||
func (s *sChapter) Create(ctx context.Context, in *model.ChapterAddIn) (out *model.ChapterCRUDOut, err error) {
|
||||
if _, err := dao.Chapters.Ctx(ctx).Data(do.Chapters{
|
||||
BookId: in.BookId,
|
||||
@ -53,6 +58,7 @@ func (s *sChapter) Create(ctx context.Context, in *model.ChapterAddIn) (out *mod
|
||||
return &model.ChapterCRUDOut{Success: true}, nil
|
||||
}
|
||||
|
||||
// Update updates an existing chapter
|
||||
func (s *sChapter) Update(ctx context.Context, in *model.ChapterEditIn) (out *model.ChapterCRUDOut, err error) {
|
||||
exist, err := dao.Chapters.Ctx(ctx).
|
||||
WherePri(in.Id).
|
||||
@ -80,6 +86,7 @@ func (s *sChapter) Update(ctx context.Context, in *model.ChapterEditIn) (out *mo
|
||||
return &model.ChapterCRUDOut{Success: true}, nil
|
||||
}
|
||||
|
||||
// Delete deletes a chapter by ID
|
||||
func (s *sChapter) Delete(ctx context.Context, in *model.ChapterDelIn) (out *model.ChapterCRUDOut, err error) {
|
||||
exist, err := dao.Chapters.Ctx(ctx).
|
||||
WherePri(in.Id).
|
||||
@ -90,9 +97,431 @@ func (s *sChapter) Delete(ctx context.Context, in *model.ChapterDelIn) (out *mod
|
||||
if !exist {
|
||||
return nil, ecode.NotFound.Sub("chapter_not_found")
|
||||
}
|
||||
_, err = dao.Chapters.Ctx(ctx).WherePri(in.Id).Delete()
|
||||
|
||||
// 开启事务,删除章节及相关数据
|
||||
err = dao.Chapters.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
// 1. 删除章节相关的用户阅读记录
|
||||
_, err := dao.UserReadRecords.Ctx(ctx).TX(tx).
|
||||
Where(dao.UserReadRecords.Columns().ChapterId, in.Id).
|
||||
Delete()
|
||||
if err != nil {
|
||||
return ecode.Fail.Sub("read_record_delete_failed")
|
||||
}
|
||||
|
||||
// 2. 删除章节相关的购买记录
|
||||
_, err = dao.UserChapterPurchases.Ctx(ctx).TX(tx).
|
||||
Where(dao.UserChapterPurchases.Columns().ChapterId, in.Id).
|
||||
Delete()
|
||||
if err != nil {
|
||||
return ecode.Fail.Sub("purchase_delete_failed")
|
||||
}
|
||||
|
||||
// 3. 最后删除章节
|
||||
_, err = dao.Chapters.Ctx(ctx).TX(tx).WherePri(in.Id).Delete()
|
||||
if err != nil {
|
||||
return ecode.Fail.Sub("chapter_delete_failed")
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, ecode.Fail.Sub("chapter_delete_failed")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &model.ChapterCRUDOut{Success: true}, nil
|
||||
}
|
||||
|
||||
// AppList retrieves chapter list for app without content
|
||||
func (s *sChapter) AppList(ctx context.Context, in *model.ChapterAppListIn) (out *model.ChapterAppListOut, err error) {
|
||||
out = &model.ChapterAppListOut{}
|
||||
|
||||
// 构建查询条件
|
||||
m := dao.Chapters.Ctx(ctx)
|
||||
|
||||
// 必须指定 bookId
|
||||
if in.BookId == 0 {
|
||||
return nil, ecode.Fail.Sub("chapter_book_id_required")
|
||||
}
|
||||
m = m.Where(dao.Chapters.Columns().BookId, in.BookId)
|
||||
|
||||
// 根据 isDesc 字段排序
|
||||
if in.IsDesc {
|
||||
m = m.OrderDesc(dao.Chapters.Columns().Sort)
|
||||
}
|
||||
|
||||
// 执行分页查询,获取列表和总数
|
||||
if err = m.Page(in.Page, in.Size).ScanAndCount(&out.List, &out.Total, false); err != nil {
|
||||
return nil, ecode.Fail.Sub("chapter_query_failed")
|
||||
}
|
||||
|
||||
// 如果指定了用户ID,查询阅读进度
|
||||
if in.UserId > 0 {
|
||||
// 获取所有章节ID
|
||||
chapterIds := make([]int64, 0, len(out.List))
|
||||
for _, item := range out.List {
|
||||
chapterIds = append(chapterIds, item.Id)
|
||||
}
|
||||
|
||||
if len(chapterIds) > 0 {
|
||||
// 查询阅读记录
|
||||
readRecords := make([]struct {
|
||||
ChapterId int64 `json:"chapterId"`
|
||||
ReadAt *gtime.Time `json:"readAt"`
|
||||
}, 0)
|
||||
|
||||
err = dao.UserReadRecords.Ctx(ctx).
|
||||
Fields("chapter_id, read_at").
|
||||
Where(dao.UserReadRecords.Columns().UserId, in.UserId).
|
||||
Where(dao.UserReadRecords.Columns().BookId, in.BookId).
|
||||
WhereIn(dao.UserReadRecords.Columns().ChapterId, chapterIds).
|
||||
Scan(&readRecords)
|
||||
if err != nil {
|
||||
return nil, ecode.Fail.Sub("read_record_query_failed")
|
||||
}
|
||||
|
||||
// 构建阅读记录映射
|
||||
readMap := make(map[int64]*gtime.Time)
|
||||
for _, record := range readRecords {
|
||||
readMap[record.ChapterId] = record.ReadAt
|
||||
}
|
||||
|
||||
// 为每个章节设置阅读进度
|
||||
for i := range out.List {
|
||||
if readAt, exists := readMap[out.List[i].Id]; exists {
|
||||
out.List[i].ReadAt = readAt
|
||||
out.List[i].ReadProgress = 100 // 有阅读记录表示已读
|
||||
} else {
|
||||
out.List[i].ReadProgress = 0 // 未读
|
||||
out.List[i].ReadAt = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// AppDetail retrieves chapter detail for app
|
||||
func (s *sChapter) AppDetail(ctx context.Context, in *model.ChapterAppDetailIn) (out *model.ChapterAppDetailOut, err error) {
|
||||
out = &model.ChapterAppDetailOut{}
|
||||
|
||||
// 构建查询条件
|
||||
m := dao.Chapters.Ctx(ctx)
|
||||
|
||||
// 必须指定章节ID
|
||||
if in.Id == 0 {
|
||||
return nil, ecode.Fail.Sub("chapter_id_required")
|
||||
}
|
||||
m = m.Where(dao.Chapters.Columns().Id, in.Id)
|
||||
|
||||
// 执行查询
|
||||
if err = m.Scan(out); err != nil {
|
||||
return nil, ecode.Fail.Sub("chapter_query_failed")
|
||||
}
|
||||
|
||||
// 检查章节是否存在
|
||||
if out.Id == 0 {
|
||||
return nil, ecode.NotFound.Sub("chapter_not_found")
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// AppPurchase purchases chapter for app
|
||||
func (s *sChapter) AppPurchase(ctx context.Context, in *model.ChapterAppPurchaseIn) (out *model.ChapterAppPurchaseOut, err error) {
|
||||
out = &model.ChapterAppPurchaseOut{}
|
||||
|
||||
// 必须指定章节ID
|
||||
if in.Id == 0 {
|
||||
return nil, ecode.Fail.Sub("chapter_id_required")
|
||||
}
|
||||
|
||||
// 必须指定用户ID
|
||||
if in.UserId == 0 {
|
||||
return nil, ecode.Fail.Sub("user_id_required")
|
||||
}
|
||||
|
||||
// 查询章节信息
|
||||
chapter := &entity.Chapters{}
|
||||
err = dao.Chapters.Ctx(ctx).Where(dao.Chapters.Columns().Id, in.Id).Scan(chapter)
|
||||
if err != nil {
|
||||
return nil, ecode.Fail.Sub("chapter_query_failed")
|
||||
}
|
||||
|
||||
// 检查章节是否存在
|
||||
if chapter.Id == 0 {
|
||||
return nil, ecode.NotFound.Sub("chapter_not_found")
|
||||
}
|
||||
|
||||
// 检查章节是否锁定
|
||||
if chapter.IsLocked == 0 {
|
||||
// 章节免费,直接返回成功
|
||||
out.Success = true
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// 查询用户信息
|
||||
user := &entity.Users{}
|
||||
err = dao.Users.Ctx(ctx).Where(dao.Users.Columns().Id, in.UserId).Scan(user)
|
||||
if err != nil {
|
||||
return nil, ecode.Fail.Sub("user_query_failed")
|
||||
}
|
||||
|
||||
// 检查用户是否存在
|
||||
if user.Id == 0 {
|
||||
return nil, ecode.NotFound.Sub("user_not_found")
|
||||
}
|
||||
|
||||
// 检查用户积分是否足够
|
||||
if user.Points < uint64(chapter.RequiredScore) {
|
||||
return nil, ecode.Fail.Sub("insufficient_points")
|
||||
}
|
||||
|
||||
// 检查是否已经购买过该章节
|
||||
exist, err := dao.UserChapterPurchases.Ctx(ctx).
|
||||
Where(dao.UserChapterPurchases.Columns().UserId, in.UserId).
|
||||
Where(dao.UserChapterPurchases.Columns().ChapterId, in.Id).
|
||||
Exist()
|
||||
if err != nil {
|
||||
return nil, ecode.Fail.Sub("purchase_query_failed")
|
||||
}
|
||||
|
||||
if exist {
|
||||
// 已经购买过,直接返回成功
|
||||
out.Success = true
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// 开启事务处理
|
||||
if err := dao.UserChapterPurchases.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
// 扣除用户积分
|
||||
_, err := dao.Users.Ctx(ctx).TX(tx).Where(dao.Users.Columns().Id, in.UserId).Decrement(dao.Users.Columns().Points, chapter.RequiredScore)
|
||||
if err != nil {
|
||||
return ecode.Fail.Sub("score_deduction_failed")
|
||||
}
|
||||
|
||||
// 记录购买记录
|
||||
purchaseId, err := dao.UserChapterPurchases.Ctx(ctx).TX(tx).Data(do.UserChapterPurchases{
|
||||
UserId: in.UserId,
|
||||
ChapterId: in.Id,
|
||||
BookId: chapter.BookId,
|
||||
PointsUsed: chapter.RequiredScore,
|
||||
PurchaseTime: gtime.Now(),
|
||||
}).InsertAndGetId()
|
||||
if err != nil {
|
||||
return ecode.Fail.Sub("purchase_record_failed")
|
||||
}
|
||||
|
||||
// 记录用户积分日志
|
||||
_, err = dao.UserPointsLogs.Ctx(ctx).TX(tx).Data(do.UserPointsLogs{
|
||||
UserId: in.UserId,
|
||||
ChangeType: 1, // 消费
|
||||
PointsChange: -chapter.RequiredScore,
|
||||
RelatedOrderId: purchaseId,
|
||||
Description: "购买章节:" + chapter.Title,
|
||||
CreatedAt: gtime.Now(),
|
||||
}).Insert()
|
||||
if err != nil {
|
||||
return ecode.Fail.Sub("point_log_failed")
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out.Success = true
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// AppProgress uploads reading progress for app
|
||||
func (s *sChapter) AppProgress(ctx context.Context, in *model.ChapterAppProgressIn) (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")
|
||||
}
|
||||
|
||||
// 必须指定章节ID
|
||||
if in.ChapterId == 0 {
|
||||
return nil, ecode.Fail.Sub("chapter_id_required")
|
||||
}
|
||||
|
||||
// 验证进度范围
|
||||
if in.Progress < 0 || in.Progress > 100 {
|
||||
return nil, ecode.Fail.Sub("progress_invalid")
|
||||
}
|
||||
|
||||
// 检查章节是否存在
|
||||
exist, err := dao.Chapters.Ctx(ctx).Where(dao.Chapters.Columns().Id, in.ChapterId).Exist()
|
||||
if err != nil {
|
||||
return nil, ecode.Fail.Sub("chapter_query_failed")
|
||||
}
|
||||
if !exist {
|
||||
return nil, ecode.NotFound.Sub("chapter_not_found")
|
||||
}
|
||||
|
||||
// 检查用户是否存在
|
||||
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")
|
||||
}
|
||||
|
||||
// 开启事务处理
|
||||
if err := dao.UserReadRecords.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
// 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, in.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, in.ChapterId).
|
||||
Data(do.UserReadRecords{
|
||||
Progress: in.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: in.ChapterId,
|
||||
Progress: in.Progress,
|
||||
ReadAt: gtime.Now(),
|
||||
}).Insert()
|
||||
if err != nil {
|
||||
return ecode.Fail.Sub("read_record_create_failed")
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 更新或创建历史记录
|
||||
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: in.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: in.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).
|
||||
Where(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
|
||||
}
|
||||
}
|
||||
|
||||
// 判断是否为最后一章
|
||||
lastChapter, 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 lastChapter != nil && lastChapter["id"].Int64() == in.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: in.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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user