完善功能

This commit is contained in:
2025-07-16 15:16:40 +08:00
parent b2871ec0d2
commit f68a5b360b
123 changed files with 4643 additions and 931 deletions

View File

@ -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
}