736 lines
19 KiB
Go
736 lines
19 KiB
Go
package book
|
||
|
||
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 sBook struct{}
|
||
|
||
func New() service.IBook {
|
||
return &sBook{}
|
||
}
|
||
|
||
func init() {
|
||
service.RegisterBook(New())
|
||
}
|
||
|
||
// List retrieves a paginated list of books
|
||
func (s *sBook) List(ctx context.Context, in *model.BookListIn) (out *model.BookListOut, err error) {
|
||
out = &model.BookListOut{}
|
||
m := dao.Books.Ctx(ctx)
|
||
if in.Title != "" {
|
||
m = m.Where(dao.Books.Columns().Title+" like ?", "%"+in.Title+"%")
|
||
}
|
||
if in.CategoryId != 0 {
|
||
m = m.Where(dao.Books.Columns().CategoryId, in.CategoryId)
|
||
}
|
||
if in.AuthorId != 0 {
|
||
m = m.Where(dao.Books.Columns().AuthorId, in.AuthorId)
|
||
}
|
||
if in.Status != 0 {
|
||
m = m.Where(dao.Books.Columns().Status, in.Status)
|
||
}
|
||
if in.IsRecommended != 0 {
|
||
m = m.Where(dao.Books.Columns().IsRecommended, in.IsRecommended)
|
||
}
|
||
if in.Sort != "" {
|
||
m = m.Order(in.Sort)
|
||
}
|
||
if err = m.Page(in.Page, in.Size).WithAll().ScanAndCount(&out.List, &out.Total, false); err != nil {
|
||
return
|
||
}
|
||
return
|
||
}
|
||
|
||
func (s *sBook) Create(ctx context.Context, in *model.BookAddIn) (out *model.BookCRUDOut, err error) {
|
||
exist, err := dao.Books.Ctx(ctx).
|
||
Where(dao.Books.Columns().Title, in.Title).
|
||
Exist()
|
||
if err != nil {
|
||
return nil, ecode.Fail.Sub("book_query_failed")
|
||
}
|
||
if exist {
|
||
return nil, ecode.Params.Sub("book_exists")
|
||
}
|
||
|
||
if _, err := dao.Books.Ctx(ctx).Data(do.Books{
|
||
AuthorId: in.AuthorId,
|
||
CategoryId: in.CategoryId,
|
||
Title: in.Title,
|
||
CoverUrl: in.CoverUrl,
|
||
Description: in.Description,
|
||
Status: in.Status,
|
||
Tags: in.Tags,
|
||
IsRecommended: in.IsRecommended,
|
||
}).Insert(); err != nil {
|
||
return nil, ecode.Fail.Sub("book_create_failed")
|
||
}
|
||
|
||
return &model.BookCRUDOut{
|
||
Success: true,
|
||
}, nil
|
||
}
|
||
|
||
func (s *sBook) Update(ctx context.Context, in *model.BookEditIn) (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")
|
||
}
|
||
|
||
exist, err = dao.Books.Ctx(ctx).
|
||
Where(dao.Books.Columns().Title, in.Title).
|
||
Where("id != ?", in.Id).
|
||
Exist()
|
||
if err != nil {
|
||
return nil, ecode.Fail.Sub("book_query_failed")
|
||
}
|
||
if exist {
|
||
return nil, ecode.Params.Sub("book_exists")
|
||
}
|
||
|
||
_, err = dao.Books.Ctx(ctx).
|
||
WherePri(in.Id).
|
||
Data(do.Books{
|
||
AuthorId: in.AuthorId,
|
||
CategoryId: in.CategoryId,
|
||
Title: in.Title,
|
||
CoverUrl: in.CoverUrl,
|
||
Description: in.Description,
|
||
Status: in.Status,
|
||
Tags: in.Tags,
|
||
IsRecommended: in.IsRecommended,
|
||
}).Update()
|
||
if err != nil {
|
||
return nil, ecode.Fail.Sub("book_update_failed")
|
||
}
|
||
|
||
return &model.BookCRUDOut{
|
||
Success: true,
|
||
}, nil
|
||
}
|
||
|
||
func (s *sBook) Delete(ctx context.Context, in *model.BookDelIn) (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.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||
// 1. 删除书籍相关的章节
|
||
_, err := dao.Chapters.Ctx(ctx).TX(tx).
|
||
Where(dao.Chapters.Columns().BookId, in.Id).
|
||
Delete()
|
||
if err != nil {
|
||
return ecode.Fail.Sub("chapter_delete_failed")
|
||
}
|
||
|
||
// 2. 删除书籍相关的用户阅读记录
|
||
_, err = dao.UserReadRecords.Ctx(ctx).TX(tx).
|
||
Where(dao.UserReadRecords.Columns().BookId, in.Id).
|
||
Delete()
|
||
if err != nil {
|
||
return ecode.Fail.Sub("read_record_delete_failed")
|
||
}
|
||
|
||
// 3. 删除书籍相关的用户阅读历史
|
||
_, err = dao.UserReadHistory.Ctx(ctx).TX(tx).
|
||
Where(dao.UserReadHistory.Columns().BookId, in.Id).
|
||
Delete()
|
||
if err != nil {
|
||
return ecode.Fail.Sub("history_delete_failed")
|
||
}
|
||
|
||
// 4. 删除书籍相关的用户书架
|
||
_, err = dao.Bookshelves.Ctx(ctx).TX(tx).
|
||
Where(dao.Bookshelves.Columns().BookId, in.Id).
|
||
Delete()
|
||
if err != nil {
|
||
return ecode.Fail.Sub("bookshelf_delete_failed")
|
||
}
|
||
|
||
// 5. 删除书籍相关的用户评分
|
||
_, err = dao.BookRatings.Ctx(ctx).TX(tx).
|
||
Where(dao.BookRatings.Columns().BookId, in.Id).
|
||
Delete()
|
||
if err != nil {
|
||
return ecode.Fail.Sub("rating_delete_failed")
|
||
}
|
||
|
||
// 6. 删除书籍相关的章节购买记录
|
||
_, err = dao.UserChapterPurchases.Ctx(ctx).TX(tx).
|
||
Where(dao.UserChapterPurchases.Columns().BookId, in.Id).
|
||
Delete()
|
||
if err != nil {
|
||
return ecode.Fail.Sub("purchase_delete_failed")
|
||
}
|
||
|
||
// 7. 最后删除书籍
|
||
_, err = dao.Books.Ctx(ctx).TX(tx).WherePri(in.Id).Delete()
|
||
if err != nil {
|
||
return ecode.Fail.Sub("book_delete_failed")
|
||
}
|
||
|
||
return nil
|
||
})
|
||
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return &model.BookCRUDOut{
|
||
Success: true,
|
||
}, nil
|
||
}
|
||
|
||
// AppList retrieves book list for app
|
||
func (s *sBook) AppList(ctx context.Context, in *model.BookAppListIn) (out *model.BookAppListOut, err error) {
|
||
out = &model.BookAppListOut{}
|
||
|
||
// 构建查询条件,使用WithAll查询关联的作者信息
|
||
m := dao.Books.Ctx(ctx).WithAll()
|
||
|
||
// 书名模糊搜索
|
||
if in.Title != "" {
|
||
m = m.Where(dao.Books.Columns().Title+" like ?", "%"+in.Title+"%")
|
||
}
|
||
|
||
// 分类筛选
|
||
if in.CategoryId != 0 {
|
||
m = m.Where(dao.Books.Columns().CategoryId, in.CategoryId)
|
||
}
|
||
|
||
// 推荐筛选
|
||
if in.IsRecommended {
|
||
m = m.Where(dao.Books.Columns().IsRecommended, 1)
|
||
}
|
||
|
||
// 最新筛选(按创建时间倒序)
|
||
if in.IsLatest == 1 {
|
||
m = m.OrderDesc(dao.Books.Columns().CreatedAt)
|
||
}
|
||
if in.AuthorId != 0 {
|
||
m = m.Where(dao.Books.Columns().AuthorId, in.AuthorId)
|
||
}
|
||
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)
|
||
}
|
||
|
||
// 执行分页查询
|
||
if err = m.Page(in.Page, in.Size).ScanAndCount(&out.List, &out.Total, false); err != nil {
|
||
return nil, ecode.Fail.Sub("book_query_failed")
|
||
}
|
||
|
||
// 用户评分批量查询
|
||
if in.UserId > 0 && len(out.List) > 0 {
|
||
bookIds := make([]int64, 0, len(out.List))
|
||
for _, item := range out.List {
|
||
bookIds = append(bookIds, item.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 out.List {
|
||
if score, ok := ratingMap[out.List[i].Id]; ok {
|
||
out.List[i].HasRated = true
|
||
out.List[i].MyRating = score
|
||
} else {
|
||
out.List[i].HasRated = false
|
||
out.List[i].MyRating = 0
|
||
}
|
||
}
|
||
} else {
|
||
for i := range out.List {
|
||
out.List[i].HasRated = false
|
||
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
|
||
}
|
||
|
||
// AppRate rates a book for app
|
||
func (s *sBook) AppRate(ctx context.Context, in *model.BookAppRateIn) (out *model.BookAppRateOut, err error) {
|
||
out = &model.BookAppRateOut{}
|
||
|
||
// 必须指定用户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")
|
||
}
|
||
|
||
// 验证评分范围(1-10分)
|
||
if in.Rating < 1 || in.Rating > 10 {
|
||
return nil, ecode.Fail.Sub("rating_invalid")
|
||
}
|
||
|
||
// 检查书籍是否存在
|
||
exist, err := dao.Books.Ctx(ctx).Where(dao.Books.Columns().Id, in.BookId).Exist()
|
||
if err != nil {
|
||
return nil, ecode.Fail.Sub("book_query_failed")
|
||
}
|
||
if !exist {
|
||
return nil, ecode.NotFound.Sub("book_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.BookRatings.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||
// 检查是否已经评分过
|
||
exist, err := dao.BookRatings.Ctx(ctx).
|
||
Where(dao.BookRatings.Columns().UserId, in.UserId).
|
||
Where(dao.BookRatings.Columns().BookId, in.BookId).
|
||
Exist()
|
||
if err != nil {
|
||
return ecode.Fail.Sub("rating_query_failed")
|
||
}
|
||
|
||
if exist {
|
||
// 更新现有评分
|
||
_, err = dao.BookRatings.Ctx(ctx).
|
||
Where(dao.BookRatings.Columns().UserId, in.UserId).
|
||
Where(dao.BookRatings.Columns().BookId, in.BookId).
|
||
Data(do.BookRatings{
|
||
Score: in.Rating,
|
||
}).Update()
|
||
if err != nil {
|
||
return ecode.Fail.Sub("rating_update_failed")
|
||
}
|
||
} else {
|
||
// 创建新评分记录
|
||
_, err = dao.BookRatings.Ctx(ctx).Data(do.BookRatings{
|
||
UserId: in.UserId,
|
||
BookId: in.BookId,
|
||
Score: in.Rating,
|
||
}).Insert()
|
||
if err != nil {
|
||
return ecode.Fail.Sub("rating_create_failed")
|
||
}
|
||
}
|
||
|
||
// 重新计算书籍平均评分
|
||
var result struct {
|
||
AvgRating float64 `json:"avg_rating"`
|
||
}
|
||
err = dao.BookRatings.Ctx(ctx).
|
||
Where(dao.BookRatings.Columns().BookId, in.BookId).
|
||
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).
|
||
Where(dao.Books.Columns().Id, in.BookId).
|
||
Data(do.Books{
|
||
Rating: result.AvgRating,
|
||
}).Update()
|
||
if err != nil {
|
||
return ecode.Fail.Sub("book_rating_update_failed")
|
||
}
|
||
|
||
return nil
|
||
}); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
out.Success = true
|
||
return out, nil
|
||
}
|
||
|
||
// AppDetail retrieves book detail for app
|
||
func (s *sBook) AppDetail(ctx context.Context, in *model.BookAppDetailIn) (out *model.BookAppDetailOut, err error) {
|
||
out = &model.BookAppDetailOut{}
|
||
|
||
// 必须指定书籍ID
|
||
if in.Id == 0 {
|
||
return nil, ecode.Fail.Sub("book_id_required")
|
||
}
|
||
|
||
// 构建查询条件
|
||
m := dao.Books.Ctx(ctx)
|
||
m = m.Where(dao.Books.Columns().Id, in.Id)
|
||
|
||
// 执行查询
|
||
if err = m.WithAll().Scan(out); err != nil {
|
||
return nil, ecode.Fail.Sub("book_query_failed")
|
||
}
|
||
|
||
// 检查书籍是否存在
|
||
if out.Id == 0 {
|
||
return nil, ecode.NotFound.Sub("book_not_found")
|
||
}
|
||
|
||
// 如果用户已登录,查询阅读进度
|
||
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"`
|
||
ReadAt string `json:"readAt"`
|
||
}
|
||
err = dao.UserReadHistory.Ctx(ctx).
|
||
Fields("chapter_id, read_at").
|
||
Where(dao.UserReadHistory.Columns().UserId, in.UserId).
|
||
Where(dao.UserReadHistory.Columns().BookId, in.Id).
|
||
OrderDesc(dao.UserReadHistory.Columns().ReadAt).
|
||
Scan(&historyRecord)
|
||
|
||
if err == nil && historyRecord.ChapterId > 0 {
|
||
// 用户读过这本书
|
||
out.HasRead = true
|
||
out.LastChapterId = historyRecord.ChapterId
|
||
out.LastReadAt = historyRecord.ReadAt
|
||
|
||
// 计算阅读进度:总章节数 vs 已读章节数
|
||
totalChapters, err := dao.Chapters.Ctx(ctx).
|
||
Where(dao.Chapters.Columns().BookId, in.Id).
|
||
Count()
|
||
|
||
if err == nil && totalChapters > 0 {
|
||
readChapters, err := dao.UserReadRecords.Ctx(ctx).
|
||
Where(dao.UserReadRecords.Columns().UserId, in.UserId).
|
||
Where(dao.UserReadRecords.Columns().BookId, in.Id).
|
||
WhereGT(dao.UserReadRecords.Columns().ChapterId, 0). // 只统计有章节ID的记录
|
||
Count()
|
||
|
||
if err == nil {
|
||
out.ReadProgress = int(float64(readChapters) / float64(totalChapters) * 100)
|
||
if out.ReadProgress > 100 {
|
||
out.ReadProgress = 100
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return out, nil
|
||
}
|
||
|
||
func (s *sBook) MyList(ctx context.Context, in *model.MyBookListIn) (out *model.MyBookListOut, err error) {
|
||
out = &model.MyBookListOut{}
|
||
|
||
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 (
|
||
ids []int64
|
||
extraMap map[int64]struct {
|
||
Progress int
|
||
LastReadAt string
|
||
}
|
||
total int
|
||
)
|
||
|
||
switch in.Type {
|
||
case 1, 2:
|
||
var bookshelves []struct {
|
||
BookId int64 `json:"bookId"`
|
||
LastReadPercent float64 `json:"lastReadPercent"`
|
||
LastReadAt string `json:"lastReadAt"`
|
||
}
|
||
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")
|
||
}
|
||
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 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 != "" {
|
||
q = q.Order(in.Sort)
|
||
} else {
|
||
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")
|
||
}
|
||
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,
|
||
}
|
||
}
|
||
}
|
||
|
||
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
|
||
}
|
||
|
||
// SetFeatured: 单独修改书籍的精选状态
|
||
func (s *sBook) SetFeatured(ctx context.Context, in *model.BookSetFeaturedIn) (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{
|
||
IsFeatured: in.IsFeatured,
|
||
}).Update()
|
||
if err != nil {
|
||
return nil, ecode.Fail.Sub("book_update_failed")
|
||
}
|
||
return &model.BookCRUDOut{Success: true}, nil
|
||
}
|
||
|
||
// SetRecommended: 单独修改书籍的推荐状态
|
||
func (s *sBook) SetRecommended(ctx context.Context, in *model.BookSetRecommendedIn) (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{
|
||
IsRecommended: in.IsRecommended,
|
||
}).Update()
|
||
if err != nil {
|
||
return nil, ecode.Fail.Sub("book_update_failed")
|
||
}
|
||
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
|
||
}
|