完成文档手册功能

dev
truthhun 1 year ago
parent 7062c0c68b
commit 74b2423754

@ -0,0 +1,72 @@
syntax="proto3";
import "google/protobuf/timestamp.proto";
import "gogoproto/gogo.proto";
// import "validate/validate.proto";
import "google/api/annotations.proto";
import "google/protobuf/empty.proto";
package api.v1;
option go_package = "moredoc/api/v1;v1";
option java_multiple_files = true;
option java_package = "api.v1";
message Favorite{
int64 id = 1;
int64 user_id = 2;
int64 document_id = 3;
string title = 6;
google.protobuf.Timestamp created_at = 4 [ (gogoproto.stdtime) = true ];
google.protobuf.Timestamp updated_at = 5 [ (gogoproto.stdtime) = true ];
}
message DeleteFavoriteRequest {
repeated int64 id = 1;
}
message ListFavoriteRequest {
int64 page = 1;
int64 size = 2;
int64 user_id = 3;
}
message ListFavoriteReply {
int64 total = 1;
repeated Favorite favorite = 2;
}
message GetFavoriteRequest{
int64 document_id = 1;
}
service FavoriteAPI{
//
rpc CreateFavorite (Favorite) returns (Favorite){
option (google.api.http) = {
post: '/api/v1/favorite',
body: '*',
};
}
//
rpc DeleteFavorite (DeleteFavoriteRequest) returns (google.protobuf.Empty){
option (google.api.http) = {
delete: '/api/v1/favorite',
};
}
// id
rpc GetFavorite (GetFavoriteRequest) returns (Favorite){
option (google.api.http) = {
get: '/api/v1/favorite',
};
}
//
rpc ListFavorite (ListFavoriteRequest) returns (ListFavoriteReply){
option (google.api.http) = {
get: '/api/v1/favorite/list',
};
}
}

@ -0,0 +1,116 @@
package biz
import (
"context"
pb "moredoc/api/v1"
"moredoc/middleware/auth"
"moredoc/model"
"moredoc/util"
"go.uber.org/zap"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
)
type FavoriteAPIService struct {
pb.UnimplementedFavoriteAPIServer
dbModel *model.DBModel
logger *zap.Logger
}
func NewFavoriteAPIService(dbModel *model.DBModel, logger *zap.Logger) (service *FavoriteAPIService) {
return &FavoriteAPIService{dbModel: dbModel, logger: logger.Named("FavoriteAPIService")}
}
func (s *FavoriteAPIService) checkLogin(ctx context.Context) (*auth.UserClaims, error) {
return checkGRPCLogin(s.dbModel, ctx)
}
func (s *FavoriteAPIService) CreateFavorite(ctx context.Context, req *pb.Favorite) (*pb.Favorite, error) {
userClaims, err := s.checkLogin(ctx)
if err != nil {
return nil, err
}
favorite := &model.Favorite{
UserId: userClaims.UserId,
DocumentId: req.DocumentId,
}
exsit, _ := s.dbModel.GetUserFavorite(favorite.UserId, favorite.DocumentId)
if exsit.Id > 0 {
return nil, status.Errorf(codes.AlreadyExists, "您已经收藏过了")
}
err = s.dbModel.CreateFavorite(favorite)
if err != nil {
return nil, status.Errorf(codes.Internal, "收藏失败:"+err.Error())
}
pbFavorite := &pb.Favorite{}
util.CopyStruct(favorite, pbFavorite)
return pbFavorite, nil
}
func (s *FavoriteAPIService) DeleteFavorite(ctx context.Context, req *pb.DeleteFavoriteRequest) (*emptypb.Empty, error) {
userClaims, err := s.checkLogin(ctx)
if err != nil {
return nil, err
}
err = s.dbModel.DeleteFavorite(userClaims.UserId, req.Id)
if err != nil {
return nil, status.Errorf(codes.Internal, "删除失败:"+err.Error())
}
return &emptypb.Empty{}, nil
}
func (s *FavoriteAPIService) GetFavorite(ctx context.Context, req *pb.GetFavoriteRequest) (*pb.Favorite, error) {
userClaims, err := s.checkLogin(ctx)
if err != nil {
return nil, err
}
favorite, _ := s.dbModel.GetUserFavorite(userClaims.UserId, req.DocumentId)
pbFavorite := &pb.Favorite{}
if favorite.Id > 0 {
util.CopyStruct(&favorite, pbFavorite)
}
return pbFavorite, nil
}
// 获取用户自身的收藏列表
func (s *FavoriteAPIService) ListFavorite(ctx context.Context, req *pb.ListFavoriteRequest) (*pb.ListFavoriteReply, error) {
articleStatus := []int{model.DocumentStatusConverted, model.DocumentStatusConverting, model.DocumentStatusPending, model.DocumentStatusFailed}
userId := req.UserId
userClaims, err := s.checkLogin(ctx)
if err == nil {
if req.UserId == userClaims.UserId {
articleStatus = []int{} // 如果是获取自己的收藏列表,可以获取所有状态的文章
}
userId = userClaims.UserId
}
favorites, total, err := s.dbModel.GetFavoriteList(&model.OptionGetFavoriteList{
Page: int(req.Page),
Size: int(req.Size_),
WithCount: true,
QueryIn: map[string][]interface{}{
"user_id": {userId},
},
}, articleStatus...)
if err != nil {
return nil, status.Errorf(codes.Internal, "获取失败:"+err.Error())
}
resp := &pb.ListFavoriteReply{
Total: total,
Favorite: favorites,
}
return resp, nil
}

@ -0,0 +1,167 @@
package model
import (
"fmt"
pb "moredoc/api/v1"
"time"
"go.uber.org/zap"
"gorm.io/gorm"
)
type Favorite struct {
Id int64 `form:"id" json:"id,omitempty" gorm:"primaryKey;autoIncrement;column:id;comment:自增主键;"`
UserId int64 `form:"user_id" json:"user_id,omitempty" gorm:"column:user_id;type:bigint(20);size:20;default:0;comment:用户id;index:idx_user_id;index:idx_user_document,unique"`
DocumentId int64 `form:"document_id" json:"document_id,omitempty" gorm:"column:document_id;type:bigint(20);size:20;default:0;comment:;index:idx_user_document,unique"`
CreatedAt time.Time `form:"created_at" json:"created_at,omitempty" gorm:"column:created_at;type:datetime;comment:;"`
UpdatedAt time.Time `form:"updated_at" json:"updated_at,omitempty" gorm:"column:updated_at;type:datetime;comment:;"`
}
func (Favorite) TableName() string {
return tablePrefix + "favorite"
}
// CreateFavorite 创建Favorite
func (m *DBModel) CreateFavorite(favorite *Favorite) (err error) {
tx := m.db.Begin()
defer func() {
if err != nil {
tx.Rollback()
} else {
tx.Commit()
}
}()
// 添加收藏
err = tx.Create(favorite).Error
if err != nil {
m.logger.Error("CreateFavorite", zap.Error(err))
return
}
// 文档收藏数量增加
err = tx.Model(&Document{}).Where("id = ?", favorite.DocumentId).Update("favorite_count", gorm.Expr("favorite_count + ?", 1)).Error
if err != nil {
m.logger.Error("CreateFavorite", zap.Error(err))
return
}
// 用户收藏数量增加
err = tx.Model(&User{}).Where("id = ?", favorite.UserId).Update("favorite_count", gorm.Expr("favorite_count + ?", 1)).Error
if err != nil {
m.logger.Error("CreateFavorite", zap.Error(err))
return
}
return
}
func (m *DBModel) GetUserFavorite(userId int64, DocumentId int64) (favorite Favorite, err error) {
err = m.db.Where("user_id = ? and document_id = ?", userId, DocumentId).First(&favorite).Error
return
}
// GetFavorite 根据id获取Favorite
func (m *DBModel) GetFavorite(id int64, fields ...string) (favorite Favorite, err error) {
db := m.db
fields = m.FilterValidFields(Favorite{}.TableName(), fields...)
if len(fields) > 0 {
db = db.Select(fields)
}
err = db.Where("id = ?", id).First(&favorite).Error
return
}
type OptionGetFavoriteList struct {
Page int
Size int
WithCount bool
SelectFields []string // 查询字段
QueryIn map[string][]interface{} // map[field][]{value1,value2,...}
}
// GetFavoriteList 获取Favorite列表
func (m *DBModel) GetFavoriteList(opt *OptionGetFavoriteList, documentStatus ...int) (favoriteList []*pb.Favorite, total int64, err error) {
tableFavorite := Favorite{}.TableName() + " f"
db := m.db.
Table(tableFavorite).
Joins(
fmt.Sprintf("left join %s a on f.document_id = a.id", Document{}.TableName()),
)
db = m.generateQueryIn(db, tableFavorite, opt.QueryIn)
if len(documentStatus) > 0 {
db = db.Where("a.status in (?)", documentStatus)
}
if opt.WithCount {
err = db.Count(&total).Error
if err != nil {
m.logger.Error("GetFavoriteList", zap.Error(err))
return
}
}
opt.SelectFields = m.FilterValidFields(tableFavorite, opt.SelectFields...)
if len(opt.SelectFields) > 0 {
db = db.Select(opt.SelectFields)
}
db = db.Order("id desc").Offset((opt.Page - 1) * opt.Size).Limit(opt.Size)
err = db.Select("f.*, a.title").Find(&favoriteList).Error
if err != nil && err != gorm.ErrRecordNotFound {
m.logger.Error("GetFavoriteList", zap.Error(err))
}
return
}
// DeleteFavorite 取消收藏
func (m *DBModel) DeleteFavorite(userId int64, ids []int64) (err error) {
if len(ids) == 0 || userId == 0 {
return
}
var favorites []Favorite
m.db.Where("id in (?) and user_id = ?", ids, userId).Select("id", "user_id", "document_id").Find(&favorites)
if len(favorites) == 0 {
return
}
tx := m.db.Begin()
defer func() {
if err != nil {
tx.Rollback()
} else {
tx.Commit()
}
}()
// 删除收藏
document := &Document{}
user := &User{}
for _, favorite := range favorites {
err = tx.Delete(&favorite).Error
if err != nil {
m.logger.Error("DeleteFavorite", zap.Error(err))
return
}
// 文档收藏数量减少
err = tx.Model(document).Where("id = ?", favorite.DocumentId).Update("favorite_count", gorm.Expr("favorite_count - ?", 1)).Error
if err != nil {
m.logger.Error("DeleteFavorite", zap.Error(err))
return
}
// 用户收藏数量减少
err = tx.Model(user).Where("id = ?", favorite.UserId).Update("favorite_count", gorm.Expr("favorite_count - ?", 1)).Error
if err != nil {
m.logger.Error("DeleteFavorite", zap.Error(err))
return
}
}
return
}

@ -170,6 +170,7 @@ func (m *DBModel) SyncDB() (err error) {
&GroupPermission{},
&Logout{},
&Article{},
&Favorite{},
}
if err = m.db.AutoMigrate(tableModels...); err != nil {
m.logger.Fatal("SyncDB", zap.Error(err))

@ -112,5 +112,14 @@ func RegisterGRPCService(dbModel *model.DBModel, logger *zap.Logger, endpoint st
return
}
// 收藏服务
favoriteAPIService := biz.NewFavoriteAPIService(dbModel, logger)
v1.RegisterFavoriteAPIServer(grpcServer, favoriteAPIService)
err = v1.RegisterFavoriteAPIHandlerFromEndpoint(context.Background(), gwmux, endpoint, dialOpts)
if err != nil {
logger.Error("RegisterFavoriteAPIHandlerFromEndpoint", zap.Error(err))
return
}
return
}

@ -0,0 +1,35 @@
import service from '~/utils/request'
export const createFavorite = (data) => {
return service({
url: '/api/v1/favorite',
method: 'post',
data,
})
}
export const deleteFavorite = (params) => {
return service({
url: '/api/v1/favorite',
method: 'delete',
params,
})
}
export const getFavorite = (params) => {
return service({
url: '/api/v1/favorite',
method: 'get',
params,
})
}
export const listFavorite = (params) => {
return service({
url: '/api/v1/favorite/list',
method: 'get',
params,
})
}

@ -74,7 +74,7 @@
<div class="doc-page-more text-center">
<div>下载文档到电脑方便使用</div>
<el-button type="primary" icon="el-icon-download">
下载文档</el-button
下载文档({{ formatBytes(document.size) }})</el-button
>
<div v-if="document.preview - pages.length > 0">
还有 {{ document.preview - pages.length }} 页可预览
@ -86,10 +86,16 @@
<div>
<div class="share-info">
本文档由
<nuxt-link to="/" class="el-link el-link--primary"
>皇虫</nuxt-link
<nuxt-link
:to="`/user/${document.user_id}`"
class="el-link el-link--primary"
>{{ document.user.username || '匿名用户' }}</nuxt-link
>
<span class="text-muted">2020-01-17 09:47:50</span> 上传分享
<span class="text-muted">{{
formatDatetime(document.created_at)
}}</span>
上传分享
</div>
<div class="btn-actions">
<el-button type="primary" plain icon="el-icon-warning-outline"
@ -99,12 +105,23 @@
type="primary"
icon="el-icon-download"
class="float-right"
>下载文档(12.23MB)</el-button
>下载文档({{ formatBytes(document.size) }})</el-button
>
<el-button
v-if="favorite.id > 0"
type="primary"
plain
class="float-right"
icon="el-icon-star-on"
@click="deleteFavorite"
>取消收藏</el-button
>
<el-button
v-else
type="primary"
class="float-right"
icon="el-icon-star-off"
@click="createFavorite"
>收藏</el-button
>
</div>
@ -130,8 +147,17 @@
<el-tooltip content="全屏阅读">
<el-button icon="el-icon-full-screen"></el-button>
</el-tooltip>
<el-tooltip content="收藏/取消收藏文档">
<el-button icon="el-icon-star-off"></el-button>
<el-tooltip :content="favorite.id > 0 ? '取消收藏' : '收藏文档'">
<el-button
v-if="favorite.id > 0"
icon="el-icon-star-on"
@click="deleteFavorite"
></el-button>
<el-button
v-else
icon="el-icon-star-off"
@click="createFavorite"
></el-button>
</el-tooltip>
<el-tooltip content="缩小">
<el-button icon="el-icon-zoom-out"></el-button>
@ -163,7 +189,7 @@
>{{ document.price || 0 }} 个魔豆</el-button
>
<el-button type="primary" icon="el-icon-download"
>下载文档(12.23MB)</el-button
>下载文档({{ formatBytes(document.size) }})</el-button
>
</el-button-group>
</el-col>
@ -182,6 +208,8 @@
import { mapGetters } from 'vuex'
import DocumentSimpleList from '~/components/DocumentSimpleList.vue'
import { getDocument } from '~/api/document'
import { getFavorite, createFavorite, deleteFavorite } from '~/api/favorite'
import { formatDatetime, formatBytes } from '~/utils/utils'
export default {
name: 'PageDocument',
components: { DocumentSimpleList },
@ -207,6 +235,9 @@ export default {
pageWidth: 0,
currentPage: 1,
breadcrumbs: [],
favorite: {
id: 0,
},
}
},
head() {
@ -218,7 +249,7 @@ export default {
...mapGetters('category', ['categoryMap']),
},
created() {
this.getDocument()
Promise.all([this.getDocument(), this.getFavorite()])
},
mounted() {
window.addEventListener('scroll', this.handleScroll)
@ -227,6 +258,8 @@ export default {
window.removeEventListener('scroll', this.handleScroll)
},
methods: {
formatDatetime,
formatBytes,
async getDocument() {
const res = await getDocument({
id: this.documentId,
@ -285,17 +318,17 @@ export default {
},
prevPage() {
if (this.currentPage > 1) {
this.currentPage--
this.scrollToPage(this.currentPage)
const currentPage = this.currentPage - 1
this.scrollToPage(currentPage)
}
},
nextPage() {
if (this.currentPage < this.document.preview) {
this.currentPage++
if (this.currentPage > this.pages.length) {
const currentPage = this.currentPage + 1
if (currentPage > this.pages.length) {
this.continueRead()
}
this.scrollToPage(this.currentPage)
this.scrollToPage(currentPage)
}
},
scrollToPage(page) {
@ -308,6 +341,36 @@ export default {
behavior: 'smooth',
})
},
async getFavorite() {
const res = await getFavorite({
document_id: this.documentId,
})
if (res.status === 200) {
this.favorite = res.data || { id: 0 }
}
},
//
async deleteFavorite() {
const res = await deleteFavorite({ id: this.favorite.id })
if (res.status === 200) {
this.$message.success('取消收藏成功')
this.favorite = { id: 0 }
} else {
this.$message.error(res.data.message)
}
},
//
async createFavorite() {
const res = await createFavorite({
document_id: this.documentId,
})
if (res.status === 200) {
this.$message.success('收藏成功')
this.favorite = res.data
} else {
this.$message.error(res.data.message)
}
},
continueRead() {
let end = this.pages.length + 5
if (end > this.document.preview) {

Loading…
Cancel
Save