完善功能
This commit is contained in:
268
internal/logic/author/author.go
Normal file
268
internal/logic/author/author.go
Normal file
@ -0,0 +1,268 @@
|
||||
package author
|
||||
|
||||
import (
|
||||
"context"
|
||||
"server/internal/dao"
|
||||
"server/internal/model"
|
||||
"server/internal/model/do"
|
||||
"server/internal/service"
|
||||
"server/utility/ecode"
|
||||
"server/utility/encrypt"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
)
|
||||
|
||||
type sAuthor struct{}
|
||||
|
||||
func New() service.IAuthor {
|
||||
return &sAuthor{}
|
||||
}
|
||||
func init() {
|
||||
service.RegisterAuthor(New())
|
||||
}
|
||||
|
||||
// List retrieves a paginated list of authors
|
||||
func (s *sAuthor) List(ctx context.Context, in *model.AuthorListIn) (out *model.AuthorListOut, err error) {
|
||||
out = &model.AuthorListOut{}
|
||||
m := dao.Authors.Ctx(ctx)
|
||||
if in.PenName != "" {
|
||||
m = m.Where(dao.Authors.Columns().PenName+" like ?", "%"+in.PenName+"%")
|
||||
}
|
||||
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 {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Create adds a new author
|
||||
func (s *sAuthor) Create(ctx context.Context, in *model.AuthorAddIn) (out *model.AuthorCRUDOut, err error) {
|
||||
// 开启事务,确保用户和作者同时写入
|
||||
err = dao.Authors.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
// 检查该 userId 是否已存在作者
|
||||
exist, err := dao.Authors.Ctx(ctx).TX(tx).
|
||||
Where(dao.Authors.Columns().UserId, in.UserId).
|
||||
Exist()
|
||||
if err != nil {
|
||||
return ecode.Fail.Sub("author_query_failed")
|
||||
}
|
||||
if exist {
|
||||
return ecode.Params.Sub("author_user_exists")
|
||||
}
|
||||
|
||||
// 检查用户是否存在
|
||||
userExist, err := dao.Users.Ctx(ctx).TX(tx).
|
||||
Where(dao.Users.Columns().Id, in.UserId).
|
||||
Exist()
|
||||
if err != nil {
|
||||
return ecode.Fail.Sub("author_query_failed")
|
||||
}
|
||||
if !userExist {
|
||||
// 不存在则创建用户,用户名用笔名,密码默认 Aa123456
|
||||
hash, err := encrypt.EncryptPassword("Aa123456")
|
||||
if err != nil {
|
||||
return ecode.Fail.Sub("password_encryption_failed")
|
||||
}
|
||||
result, err := dao.Users.Ctx(ctx).TX(tx).Data(do.Users{
|
||||
Username: in.PenName,
|
||||
PasswordHash: hash,
|
||||
}).InsertAndGetId()
|
||||
if err != nil {
|
||||
return ecode.Fail.Sub("author_create_failed")
|
||||
}
|
||||
in.UserId = result
|
||||
}
|
||||
|
||||
// 创建作者
|
||||
_, err = dao.Authors.Ctx(ctx).TX(tx).Data(do.Authors{
|
||||
UserId: in.UserId,
|
||||
PenName: in.PenName,
|
||||
Bio: in.Bio,
|
||||
Status: in.Status,
|
||||
}).Insert()
|
||||
if err != nil {
|
||||
return ecode.Fail.Sub("author_create_failed")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &model.AuthorCRUDOut{Success: true}, nil
|
||||
}
|
||||
|
||||
// Update edits an author
|
||||
func (s *sAuthor) Update(ctx context.Context, in *model.AuthorEditIn) (out *model.AuthorCRUDOut, err error) {
|
||||
exist, err := dao.Authors.Ctx(ctx).
|
||||
WherePri(in.Id).
|
||||
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.Id).
|
||||
Data(do.Authors{
|
||||
PenName: in.PenName,
|
||||
Bio: in.Bio,
|
||||
Status: in.Status,
|
||||
}).Update()
|
||||
if err != nil {
|
||||
return nil, ecode.Fail.Sub("author_update_failed")
|
||||
}
|
||||
return &model.AuthorCRUDOut{Success: true}, nil
|
||||
}
|
||||
|
||||
// Delete removes an author by id
|
||||
func (s *sAuthor) Delete(ctx context.Context, in *model.AuthorDelIn) (out *model.AuthorCRUDOut, err error) {
|
||||
exist, err := dao.Authors.Ctx(ctx).
|
||||
WherePri(in.Id).
|
||||
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.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
// 1. 删除作者相关的用户关注记录
|
||||
_, err := dao.UserFollowAuthors.Ctx(ctx).TX(tx).
|
||||
Where(dao.UserFollowAuthors.Columns().AuthorId, in.Id).
|
||||
Delete()
|
||||
if err != nil {
|
||||
return ecode.Fail.Sub("follow_author_delete_failed")
|
||||
}
|
||||
|
||||
// 2. 删除作者相关的书籍(这里需要递归删除书籍相关的所有数据)
|
||||
// 先查询该作者的所有书籍ID
|
||||
var bookIds []int64
|
||||
err = dao.Books.Ctx(ctx).TX(tx).
|
||||
Where(dao.Books.Columns().AuthorId, in.Id).
|
||||
Fields("id").
|
||||
Scan(&bookIds)
|
||||
if err != nil {
|
||||
return ecode.Fail.Sub("book_query_failed")
|
||||
}
|
||||
|
||||
if len(bookIds) > 0 {
|
||||
// 删除书籍相关的章节
|
||||
_, err = dao.Chapters.Ctx(ctx).TX(tx).
|
||||
WhereIn(dao.Chapters.Columns().BookId, bookIds).
|
||||
Delete()
|
||||
if err != nil {
|
||||
return ecode.Fail.Sub("chapter_delete_failed")
|
||||
}
|
||||
|
||||
// 删除书籍相关的用户阅读记录
|
||||
_, err = dao.UserReadRecords.Ctx(ctx).TX(tx).
|
||||
WhereIn(dao.UserReadRecords.Columns().BookId, bookIds).
|
||||
Delete()
|
||||
if err != nil {
|
||||
return ecode.Fail.Sub("read_record_delete_failed")
|
||||
}
|
||||
|
||||
// 删除书籍相关的用户阅读历史
|
||||
_, err = dao.UserReadHistory.Ctx(ctx).TX(tx).
|
||||
WhereIn(dao.UserReadHistory.Columns().BookId, bookIds).
|
||||
Delete()
|
||||
if err != nil {
|
||||
return ecode.Fail.Sub("history_delete_failed")
|
||||
}
|
||||
|
||||
// 删除书籍相关的用户书架
|
||||
_, err = dao.Bookshelves.Ctx(ctx).TX(tx).
|
||||
WhereIn(dao.Bookshelves.Columns().BookId, bookIds).
|
||||
Delete()
|
||||
if err != nil {
|
||||
return ecode.Fail.Sub("bookshelf_delete_failed")
|
||||
}
|
||||
|
||||
// 删除书籍相关的用户评分
|
||||
_, err = dao.BookRatings.Ctx(ctx).TX(tx).
|
||||
WhereIn(dao.BookRatings.Columns().BookId, bookIds).
|
||||
Delete()
|
||||
if err != nil {
|
||||
return ecode.Fail.Sub("rating_delete_failed")
|
||||
}
|
||||
|
||||
// 删除书籍相关的章节购买记录
|
||||
_, err = dao.UserChapterPurchases.Ctx(ctx).TX(tx).
|
||||
WhereIn(dao.UserChapterPurchases.Columns().BookId, bookIds).
|
||||
Delete()
|
||||
if err != nil {
|
||||
return ecode.Fail.Sub("purchase_delete_failed")
|
||||
}
|
||||
|
||||
// 最后删除书籍
|
||||
_, err = dao.Books.Ctx(ctx).TX(tx).
|
||||
Where(dao.Books.Columns().AuthorId, in.Id).
|
||||
Delete()
|
||||
if err != nil {
|
||||
return ecode.Fail.Sub("book_delete_failed")
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 最后删除作者
|
||||
_, err = dao.Authors.Ctx(ctx).TX(tx).WherePri(in.Id).Delete()
|
||||
if err != nil {
|
||||
return ecode.Fail.Sub("author_delete_failed")
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &model.AuthorCRUDOut{Success: true}, nil
|
||||
}
|
||||
|
||||
// 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).
|
||||
Exist()
|
||||
if err != nil {
|
||||
return nil, ecode.Fail.Sub("author_query_failed")
|
||||
}
|
||||
if exist {
|
||||
return nil, ecode.Params.Sub("author_user_exists")
|
||||
}
|
||||
if _, err := dao.Authors.Ctx(ctx).Data(do.Authors{
|
||||
UserId: userId,
|
||||
PenName: in.PenName,
|
||||
Bio: in.Bio,
|
||||
Status: 1, // 默认正常
|
||||
}).Insert(); err != nil {
|
||||
return nil, ecode.Fail.Sub("author_create_failed")
|
||||
}
|
||||
return &model.AuthorApplyOut{Success: true}, nil
|
||||
}
|
||||
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()
|
||||
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 {
|
||||
return nil, ecode.Fail.Sub("author_query_failed")
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
@ -7,6 +7,8 @@ import (
|
||||
"server/internal/model/do"
|
||||
"server/internal/service"
|
||||
"server/utility/ecode"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
)
|
||||
|
||||
type sBook struct{}
|
||||
@ -38,7 +40,10 @@ func (s *sBook) List(ctx context.Context, in *model.BookListIn) (out *model.Book
|
||||
if in.IsRecommended != 0 {
|
||||
m = m.Where(dao.Books.Columns().IsRecommended, in.IsRecommended)
|
||||
}
|
||||
if err = m.Page(in.Page, in.Size).ScanAndCount(&out.List, &out.Total, false); err != nil {
|
||||
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
|
||||
@ -127,12 +132,633 @@ func (s *sBook) Delete(ctx context.Context, in *model.BookDelIn) (out *model.Boo
|
||||
return nil, ecode.NotFound.Sub("book_not_found")
|
||||
}
|
||||
|
||||
_, err = dao.Books.Ctx(ctx).WherePri(in.Id).Delete()
|
||||
// 开启事务,删除书籍及相关数据
|
||||
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, ecode.Fail.Sub("book_delete_failed")
|
||||
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.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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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).TX(tx).
|
||||
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).TX(tx).
|
||||
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).TX(tx).Data(do.BookRatings{
|
||||
UserId: in.UserId,
|
||||
BookId: in.BookId,
|
||||
Score: in.Rating,
|
||||
}).Insert()
|
||||
if err != nil {
|
||||
return ecode.Fail.Sub("rating_create_failed")
|
||||
}
|
||||
}
|
||||
|
||||
// 重新计算书籍平均评分
|
||||
var avgRating float64
|
||||
err = dao.BookRatings.Ctx(ctx).TX(tx).
|
||||
Where(dao.BookRatings.Columns().BookId, in.BookId).
|
||||
Fields("AVG(score) as avg_rating").
|
||||
Scan(&avgRating)
|
||||
if err != nil {
|
||||
return ecode.Fail.Sub("rating_calculation_failed")
|
||||
}
|
||||
|
||||
// 更新书籍的平均评分
|
||||
_, err = dao.Books.Ctx(ctx).TX(tx).
|
||||
Where(dao.Books.Columns().Id, in.BookId).
|
||||
Data(do.Books{
|
||||
Rating: 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.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 {
|
||||
// 查询用户对该书籍的历史记录
|
||||
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).
|
||||
Where(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{}
|
||||
|
||||
// 验证用户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
|
||||
|
||||
switch in.Type {
|
||||
case 1: // 正在读
|
||||
// 查询书架表中read_status=1的记录
|
||||
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)
|
||||
}
|
||||
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 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表
|
||||
var histories []struct {
|
||||
BookId int64 `json:"bookId"`
|
||||
ChapterId int64 `json:"chapterId"`
|
||||
ReadAt string `json:"readAt"`
|
||||
}
|
||||
|
||||
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)
|
||||
} 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)
|
||||
}
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
// 返回空列表
|
||||
}
|
||||
|
||||
out = &model.MyBookListOut{
|
||||
Total: total,
|
||||
List: list,
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
57
internal/logic/bookshelve/bookshelve.go
Normal file
57
internal/logic/bookshelve/bookshelve.go
Normal file
@ -0,0 +1,57 @@
|
||||
package bookshelve
|
||||
|
||||
import (
|
||||
"context"
|
||||
"server/internal/dao"
|
||||
"server/internal/model"
|
||||
"server/internal/model/do"
|
||||
"server/internal/service"
|
||||
"server/utility/ecode"
|
||||
)
|
||||
|
||||
type sBookshelve struct{}
|
||||
|
||||
func New() service.IBookshelve {
|
||||
return &sBookshelve{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterBookshelve(New())
|
||||
}
|
||||
|
||||
// Add 添加书架
|
||||
func (s *sBookshelve) Add(ctx context.Context, in *model.BookshelveAddIn) (out *model.BookshelveCRUDOut, err error) {
|
||||
exist, err := dao.Bookshelves.Ctx(ctx).
|
||||
Where(dao.Bookshelves.Columns().UserId, in.UserId).
|
||||
Where(dao.Bookshelves.Columns().BookId, in.BookId).
|
||||
Exist()
|
||||
if err != nil {
|
||||
return nil, ecode.Fail.Sub("bookshelve_query_failed")
|
||||
}
|
||||
if exist {
|
||||
return nil, ecode.Params.Sub("bookshelve_exists")
|
||||
}
|
||||
if _, err := dao.Bookshelves.Ctx(ctx).Data(do.Bookshelves{
|
||||
UserId: in.UserId,
|
||||
BookId: in.BookId,
|
||||
ReadStatus: 1, // 默认为正在读
|
||||
}).Insert(); err != nil {
|
||||
return nil, ecode.Fail.Sub("bookshelve_create_failed")
|
||||
}
|
||||
return &model.BookshelveCRUDOut{Success: true}, nil
|
||||
}
|
||||
|
||||
// Delete 批量删除书架
|
||||
func (s *sBookshelve) Delete(ctx context.Context, in *model.BookshelveDelIn) (out *model.BookshelveCRUDOut, err error) {
|
||||
if len(in.BookIds) == 0 {
|
||||
return nil, ecode.Params.Sub("bookshelve_bookids_empty")
|
||||
}
|
||||
_, err = dao.Bookshelves.Ctx(ctx).
|
||||
Where(dao.Bookshelves.Columns().UserId, in.UserId).
|
||||
WhereIn(dao.Bookshelves.Columns().BookId, in.BookIds).
|
||||
Delete()
|
||||
if err != nil {
|
||||
return nil, ecode.Fail.Sub("bookshelve_delete_failed")
|
||||
}
|
||||
return &model.BookshelveCRUDOut{Success: true}, nil
|
||||
}
|
||||
@ -7,6 +7,8 @@ import (
|
||||
"server/internal/model/do"
|
||||
"server/internal/service"
|
||||
"server/utility/ecode"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
)
|
||||
|
||||
type sCategory struct {
|
||||
@ -24,8 +26,8 @@ func init() {
|
||||
func (s *sCategory) List(ctx context.Context, in *model.CategoryListIn) (out *model.CategoryListOut, err error) {
|
||||
out = &model.CategoryListOut{}
|
||||
m := dao.Categories.Ctx(ctx)
|
||||
if in.Type != 0 {
|
||||
m = m.Where(dao.Categories.Columns().Type, in.Type)
|
||||
if in.Channel != 0 {
|
||||
m = m.Where(dao.Categories.Columns().Channel, in.Channel)
|
||||
}
|
||||
if err = m.Page(in.Page, in.Size).ScanAndCount(&out.List, &out.Total, false); err != nil {
|
||||
return
|
||||
@ -34,9 +36,6 @@ func (s *sCategory) List(ctx context.Context, in *model.CategoryListIn) (out *mo
|
||||
}
|
||||
|
||||
func (s *sCategory) Create(ctx context.Context, in *model.CategoryAddIn) (out *model.CategoryCRUDOut, err error) {
|
||||
if in.Type != 1 && in.Type != 2 {
|
||||
return nil, ecode.Params.Sub("category_type_invalid")
|
||||
}
|
||||
exist, err := dao.Categories.Ctx(ctx).
|
||||
Where(dao.Categories.Columns().Name, in.Name).
|
||||
Exist()
|
||||
@ -48,8 +47,8 @@ func (s *sCategory) Create(ctx context.Context, in *model.CategoryAddIn) (out *m
|
||||
}
|
||||
|
||||
if _, err := dao.Categories.Ctx(ctx).Data(do.Categories{
|
||||
Name: in.Name,
|
||||
Type: in.Type,
|
||||
Name: in.Name,
|
||||
Channel: in.Channel,
|
||||
}).Insert(); err != nil {
|
||||
return nil, ecode.Fail.Sub("category_create_failed")
|
||||
}
|
||||
@ -60,9 +59,6 @@ func (s *sCategory) Create(ctx context.Context, in *model.CategoryAddIn) (out *m
|
||||
}
|
||||
|
||||
func (s *sCategory) Update(ctx context.Context, in *model.CategoryEditIn) (out *model.CategoryCRUDOut, err error) {
|
||||
if in.Type != 1 && in.Type != 2 {
|
||||
return nil, ecode.Params.Sub("category_type_invalid")
|
||||
}
|
||||
exist, err := dao.Categories.Ctx(ctx).
|
||||
WherePri(in.Id).
|
||||
Exist()
|
||||
@ -88,8 +84,8 @@ func (s *sCategory) Update(ctx context.Context, in *model.CategoryEditIn) (out *
|
||||
_, err = dao.Categories.Ctx(ctx).
|
||||
WherePri(in.Id).
|
||||
Data(do.Categories{
|
||||
Name: in.Name,
|
||||
Type: in.Type,
|
||||
Name: in.Name,
|
||||
Channel: in.Channel,
|
||||
}).Update()
|
||||
if err != nil {
|
||||
return nil, ecode.Fail.Sub("category_update_failed")
|
||||
@ -111,10 +107,31 @@ func (s *sCategory) Delete(ctx context.Context, in *model.CategoryDelIn) (out *m
|
||||
return nil, ecode.NotFound.Sub("category_not_found")
|
||||
}
|
||||
|
||||
// Soft delete category
|
||||
_, err = dao.Categories.Ctx(ctx).WherePri(in.Id).Delete()
|
||||
// 开启事务,检查是否有书籍使用了这个分类
|
||||
err = dao.Categories.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
// 检查是否有书籍使用了这个分类
|
||||
bookCount, err := dao.Books.Ctx(ctx).TX(tx).
|
||||
Where(dao.Books.Columns().CategoryId, in.Id).
|
||||
Count()
|
||||
if err != nil {
|
||||
return ecode.Fail.Sub("book_query_failed")
|
||||
}
|
||||
|
||||
if bookCount > 0 {
|
||||
return ecode.Fail.Sub("category_in_use")
|
||||
}
|
||||
|
||||
// 删除分类
|
||||
_, err = dao.Categories.Ctx(ctx).TX(tx).WherePri(in.Id).Delete()
|
||||
if err != nil {
|
||||
return ecode.Fail.Sub("category_delete_failed")
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, ecode.Fail.Sub("category_delete_failed")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &model.CategoryCRUDOut{
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -28,7 +28,7 @@ func (s *sFeedback) List(ctx context.Context, in *model.FeedbackListIn) (out *mo
|
||||
if in.Status != 0 {
|
||||
m = m.Where(dao.Feedbacks.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
|
||||
|
||||
@ -6,11 +6,13 @@ package logic
|
||||
|
||||
import (
|
||||
_ "server/internal/logic/admin"
|
||||
_ "server/internal/logic/author"
|
||||
_ "server/internal/logic/book"
|
||||
_ "server/internal/logic/bookshelve"
|
||||
_ "server/internal/logic/category"
|
||||
_ "server/internal/logic/chapter"
|
||||
_ "server/internal/logic/feedback"
|
||||
_ "server/internal/logic/read_record"
|
||||
_ "server/internal/logic/tag"
|
||||
_ "server/internal/logic/user"
|
||||
_ "server/internal/logic/user_follow_author"
|
||||
_ "server/internal/logic/user_read_record"
|
||||
)
|
||||
|
||||
@ -1,68 +0,0 @@
|
||||
package read_record
|
||||
|
||||
import (
|
||||
"context"
|
||||
"server/internal/dao"
|
||||
"server/internal/model"
|
||||
"server/internal/model/do"
|
||||
"server/internal/service"
|
||||
"server/utility/ecode"
|
||||
)
|
||||
|
||||
type sReadRecord struct{}
|
||||
|
||||
func New() service.IReadRecord {
|
||||
return &sReadRecord{}
|
||||
}
|
||||
func init() {
|
||||
service.RegisterReadRecord(New())
|
||||
}
|
||||
|
||||
// List retrieves a paginated list of read records
|
||||
func (s *sReadRecord) List(ctx context.Context, in *model.ReadRecordListIn) (out *model.ReadRecordListOut, err error) {
|
||||
out = &model.ReadRecordListOut{}
|
||||
m := dao.ReadRecords.Ctx(ctx)
|
||||
if in.UserId != 0 {
|
||||
m = m.Where(dao.ReadRecords.Columns().UserId, in.UserId)
|
||||
}
|
||||
if in.BookId != 0 {
|
||||
m = m.Where(dao.ReadRecords.Columns().BookId, in.BookId)
|
||||
}
|
||||
if in.ChapterId != 0 {
|
||||
m = m.Where(dao.ReadRecords.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 read record
|
||||
func (s *sReadRecord) Create(ctx context.Context, in *model.ReadRecordAddIn) (out *model.ReadRecordCRUDOut, err error) {
|
||||
if _, err := dao.ReadRecords.Ctx(ctx).Data(do.ReadRecords{
|
||||
UserId: in.UserId,
|
||||
BookId: in.BookId,
|
||||
ChapterId: in.ChapterId,
|
||||
}).Insert(); err != nil {
|
||||
return nil, ecode.Fail.Sub("read_record_create_failed")
|
||||
}
|
||||
return &model.ReadRecordCRUDOut{Success: true}, nil
|
||||
}
|
||||
|
||||
// Delete removes a read record by id
|
||||
func (s *sReadRecord) Delete(ctx context.Context, in *model.ReadRecordDelIn) (out *model.ReadRecordCRUDOut, err error) {
|
||||
exist, err := dao.ReadRecords.Ctx(ctx).
|
||||
WherePri(in.Id).
|
||||
Exist()
|
||||
if err != nil {
|
||||
return nil, ecode.Fail.Sub("read_record_query_failed")
|
||||
}
|
||||
if !exist {
|
||||
return nil, ecode.NotFound.Sub("read_record_not_found")
|
||||
}
|
||||
_, err = dao.ReadRecords.Ctx(ctx).WherePri(in.Id).Delete()
|
||||
if err != nil {
|
||||
return nil, ecode.Fail.Sub("read_record_delete_failed")
|
||||
}
|
||||
return &model.ReadRecordCRUDOut{Success: true}, nil
|
||||
}
|
||||
@ -1,119 +0,0 @@
|
||||
package tag
|
||||
|
||||
import (
|
||||
"context"
|
||||
"server/internal/dao"
|
||||
"server/internal/model"
|
||||
"server/internal/model/do"
|
||||
"server/internal/service"
|
||||
"server/utility/ecode"
|
||||
)
|
||||
|
||||
type sTag struct{}
|
||||
|
||||
func New() service.ITag {
|
||||
return &sTag{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterTag(New())
|
||||
}
|
||||
|
||||
// List retrieves a paginated list of tags
|
||||
func (s *sTag) List(ctx context.Context, in *model.TagListIn) (out *model.TagListOut, err error) {
|
||||
out = &model.TagListOut{}
|
||||
m := dao.Tags.Ctx(ctx)
|
||||
if in.Name != "" {
|
||||
m = m.Where(dao.Tags.Columns().Name+" like ?", "%"+in.Name+"%")
|
||||
}
|
||||
if in.Type != 0 {
|
||||
m = m.Where(dao.Tags.Columns().Type, in.Type)
|
||||
}
|
||||
if err = m.Page(in.Page, in.Size).ScanAndCount(&out.List, &out.Total, false); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sTag) Create(ctx context.Context, in *model.TagAddIn) (out *model.TagCRUDOut, err error) {
|
||||
exist, err := dao.Tags.Ctx(ctx).
|
||||
Where(dao.Tags.Columns().Name, in.Name).
|
||||
Where(dao.Tags.Columns().Type, in.Type).
|
||||
Exist()
|
||||
if err != nil {
|
||||
return nil, ecode.Fail.Sub("tag_query_failed")
|
||||
}
|
||||
if exist {
|
||||
return nil, ecode.Params.Sub("tag_exists")
|
||||
}
|
||||
|
||||
if _, err := dao.Tags.Ctx(ctx).Data(do.Tags{
|
||||
Name: in.Name,
|
||||
Type: in.Type,
|
||||
}).Insert(); err != nil {
|
||||
return nil, ecode.Fail.Sub("tag_create_failed")
|
||||
}
|
||||
|
||||
return &model.TagCRUDOut{
|
||||
Success: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *sTag) Update(ctx context.Context, in *model.TagEditIn) (out *model.TagCRUDOut, err error) {
|
||||
exist, err := dao.Tags.Ctx(ctx).
|
||||
WherePri(in.Id).
|
||||
Exist()
|
||||
if err != nil {
|
||||
return nil, ecode.Fail.Sub("tag_query_failed")
|
||||
}
|
||||
if !exist {
|
||||
return nil, ecode.NotFound.Sub("tag_not_found")
|
||||
}
|
||||
|
||||
exist, err = dao.Tags.Ctx(ctx).
|
||||
Where(dao.Tags.Columns().Name, in.Name).
|
||||
Where(dao.Tags.Columns().Type, in.Type).
|
||||
Where("id != ?", in.Id).
|
||||
Exist()
|
||||
if err != nil {
|
||||
return nil, ecode.Fail.Sub("tag_query_failed")
|
||||
}
|
||||
if exist {
|
||||
return nil, ecode.Params.Sub("tag_exists")
|
||||
}
|
||||
|
||||
_, err = dao.Tags.Ctx(ctx).
|
||||
WherePri(in.Id).
|
||||
Data(do.Tags{
|
||||
Name: in.Name,
|
||||
Type: in.Type,
|
||||
}).Update()
|
||||
if err != nil {
|
||||
return nil, ecode.Fail.Sub("tag_update_failed")
|
||||
}
|
||||
|
||||
return &model.TagCRUDOut{
|
||||
Success: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *sTag) Delete(ctx context.Context, in *model.TagDelIn) (out *model.TagCRUDOut, err error) {
|
||||
exist, err := dao.Tags.Ctx(ctx).
|
||||
WherePri(in.Id).
|
||||
Exist()
|
||||
if err != nil {
|
||||
return nil, ecode.Fail.Sub("tag_query_failed")
|
||||
}
|
||||
if !exist {
|
||||
return nil, ecode.NotFound.Sub("tag_not_found")
|
||||
}
|
||||
|
||||
_, err = dao.Tags.Ctx(ctx).WherePri(in.Id).Delete()
|
||||
if err != nil {
|
||||
return nil, ecode.Fail.Sub("tag_delete_failed")
|
||||
}
|
||||
|
||||
return &model.TagCRUDOut{
|
||||
Success: true,
|
||||
}, nil
|
||||
}
|
||||
@ -11,6 +11,8 @@ import (
|
||||
"server/utility/encrypt"
|
||||
"server/utility/jwt"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
)
|
||||
|
||||
type sUser struct{}
|
||||
@ -37,7 +39,13 @@ func (s *sUser) Login(ctx context.Context, in *model.UserLoginIn) (out *model.Us
|
||||
if !encrypt.ComparePassword(entityUser.PasswordHash, in.Password) {
|
||||
return nil, ecode.Password // 密码不正确
|
||||
}
|
||||
token, err := jwt.GenerateToken(&jwt.TokenIn{UserId: entityUser.Id, Role: "user"})
|
||||
// 判断是否为作者
|
||||
author, _ := dao.Authors.Ctx(ctx).Where(do.Authors{UserId: entityUser.Id, Status: 1}).One()
|
||||
role := "user"
|
||||
if author != nil && !author.IsEmpty() {
|
||||
role = "author"
|
||||
}
|
||||
token, err := jwt.GenerateToken(&jwt.TokenIn{UserId: entityUser.Id, Role: role})
|
||||
if err != nil {
|
||||
return nil, ecode.Fail.Sub("token_generation_failed")
|
||||
}
|
||||
@ -92,7 +100,111 @@ func (s *sUser) Info(ctx context.Context, in *model.UserInfoIn) (out *model.User
|
||||
}
|
||||
|
||||
func (s *sUser) Delete(ctx context.Context, in *model.UserDeleteIn) (out *model.UserDeleteOut, err error) {
|
||||
// FIXME
|
||||
// 查询用户信息
|
||||
user, err := dao.Users.Ctx(ctx).Where(do.Users{Id: in.UserId}).One()
|
||||
if err != nil {
|
||||
return nil, ecode.Fail.Sub("database_query_failed")
|
||||
}
|
||||
if user == nil {
|
||||
return nil, ecode.Auth.Sub("user_not_found")
|
||||
}
|
||||
|
||||
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.Sub("password_incorrect")
|
||||
}
|
||||
|
||||
// 开启事务,删除用户及相关数据
|
||||
err = dao.Users.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
// 1. 删除用户阅读记录
|
||||
_, err := dao.UserReadRecords.Ctx(ctx).TX(tx).
|
||||
Where(dao.UserReadRecords.Columns().UserId, in.UserId).
|
||||
Delete()
|
||||
if err != nil {
|
||||
return ecode.Fail.Sub("read_record_delete_failed")
|
||||
}
|
||||
|
||||
// 2. 删除用户阅读历史
|
||||
_, err = dao.UserReadHistory.Ctx(ctx).TX(tx).
|
||||
Where(dao.UserReadHistory.Columns().UserId, in.UserId).
|
||||
Delete()
|
||||
if err != nil {
|
||||
return ecode.Fail.Sub("history_delete_failed")
|
||||
}
|
||||
|
||||
// 3. 删除用户书架
|
||||
_, err = dao.Bookshelves.Ctx(ctx).TX(tx).
|
||||
Where(dao.Bookshelves.Columns().UserId, in.UserId).
|
||||
Delete()
|
||||
if err != nil {
|
||||
return ecode.Fail.Sub("bookshelf_delete_failed")
|
||||
}
|
||||
|
||||
// 4. 删除用户关注作者记录
|
||||
_, err = dao.UserFollowAuthors.Ctx(ctx).TX(tx).
|
||||
Where(dao.UserFollowAuthors.Columns().UserId, in.UserId).
|
||||
Delete()
|
||||
if err != nil {
|
||||
return ecode.Fail.Sub("follow_author_delete_failed")
|
||||
}
|
||||
|
||||
// 5. 删除用户评分记录
|
||||
_, err = dao.BookRatings.Ctx(ctx).TX(tx).
|
||||
Where(dao.BookRatings.Columns().UserId, in.UserId).
|
||||
Delete()
|
||||
if err != nil {
|
||||
return ecode.Fail.Sub("rating_delete_failed")
|
||||
}
|
||||
|
||||
// 6. 删除用户章节购买记录
|
||||
_, err = dao.UserChapterPurchases.Ctx(ctx).TX(tx).
|
||||
Where(dao.UserChapterPurchases.Columns().UserId, in.UserId).
|
||||
Delete()
|
||||
if err != nil {
|
||||
return ecode.Fail.Sub("purchase_delete_failed")
|
||||
}
|
||||
|
||||
// 7. 删除用户积分日志
|
||||
_, err = dao.UserPointsLogs.Ctx(ctx).TX(tx).
|
||||
Where(dao.UserPointsLogs.Columns().UserId, in.UserId).
|
||||
Delete()
|
||||
if err != nil {
|
||||
return ecode.Fail.Sub("points_log_delete_failed")
|
||||
}
|
||||
|
||||
// 8. 删除用户反馈
|
||||
_, err = dao.Feedbacks.Ctx(ctx).TX(tx).
|
||||
Where(dao.Feedbacks.Columns().UserId, in.UserId).
|
||||
Delete()
|
||||
if err != nil {
|
||||
return ecode.Fail.Sub("feedback_delete_failed")
|
||||
}
|
||||
|
||||
// 9. 删除作者信息(如果用户是作者)
|
||||
_, err = dao.Authors.Ctx(ctx).TX(tx).
|
||||
Where(dao.Authors.Columns().UserId, in.UserId).
|
||||
Delete()
|
||||
if err != nil {
|
||||
return ecode.Fail.Sub("author_delete_failed")
|
||||
}
|
||||
|
||||
// 10. 最后删除用户(软删除)
|
||||
_, err = dao.Users.Ctx(ctx).TX(tx).Where(do.Users{Id: in.UserId}).Delete()
|
||||
if err != nil {
|
||||
return ecode.Fail.Sub("user_delete_failed")
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &model.UserDeleteOut{Success: true}, nil
|
||||
}
|
||||
|
||||
@ -5,11 +5,19 @@ import (
|
||||
"server/internal/dao"
|
||||
"server/internal/model"
|
||||
"server/internal/model/do"
|
||||
"server/internal/service"
|
||||
"server/utility/ecode"
|
||||
)
|
||||
|
||||
type sUserFollowAuthor struct{}
|
||||
|
||||
func New() service.IUserFollowAuthor {
|
||||
return &sUserFollowAuthor{}
|
||||
}
|
||||
func init() {
|
||||
service.RegisterUserFollowAuthor(New())
|
||||
}
|
||||
|
||||
// List retrieves a paginated list of user follow authors
|
||||
func (s *sUserFollowAuthor) List(ctx context.Context, in *model.UserFollowAuthorListIn) (out *model.UserFollowAuthorListOut, err error) {
|
||||
out = &model.UserFollowAuthorListOut{}
|
||||
@ -64,3 +72,27 @@ func (s *sUserFollowAuthor) Delete(ctx context.Context, in *model.UserFollowAuth
|
||||
}
|
||||
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)
|
||||
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 &model.UserFollowAuthorCRUDOut{Success: true}, nil
|
||||
}
|
||||
|
||||
97
internal/logic/user_read_record/user_read_record.go
Normal file
97
internal/logic/user_read_record/user_read_record.go
Normal file
@ -0,0 +1,97 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user