分类禁用了就不可选

dev
truthhun 2 years ago
parent 580d3bc6c1
commit e792d0aa7f

@ -0,0 +1,107 @@
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 Document {
int64 id = 1;
string title = 2;
string keywords = 3;
string description = 4;
int64 user_id = 5;
string cover = 6;
int32 width = 7;
int32 height = 8;
int32 preview = 9;
int32 pages = 10;
string uuid = 11;
int32 download_count = 12;
int32 view_count = 13;
int32 favorite_count = 14;
int32 comment_count = 15;
int32 score = 16;
int32 score_count = 17;
int32 price = 18;
int64 size = 19;
int32 status = 20;
google.protobuf.Timestamp created_at = 21 [ (gogoproto.stdtime) = true ];
google.protobuf.Timestamp updated_at = 22 [ (gogoproto.stdtime) = true ];
google.protobuf.Timestamp deleted_at = 23 [ (gogoproto.stdtime) = true ];
int64 deleted_user_id = 24;
}
message DeleteDocumentRequest { repeated int64 id = 1; }
message GetDocumentRequest { int64 id = 1; }
message ListDocumentRequest {
int64 page = 1;
int64 size = 2;
string wd = 3;
repeated string field = 4;
string order = 5;
repeated int64 category_id = 6;
repeated int64 user_id = 7;
}
message ListDocumentReply {
int64 total = 1;
repeated Document document = 2;
}
message DocumentItem {
string title = 1; //
string keywords = 2; //
string description = 3; //
string filepath = 4; //
int64 attachment_id = 5; // ID
int32 price = 6; //
}
message CreateDocumentRequest {
repeated int64 category_id = 1;
repeated DocumentItem document = 2;
}
service DocumentAPI {
rpc CreateDocument(CreateDocumentRequest) returns (google.protobuf.Empty) {
option (google.api.http) = {
post : '/api/v1/document',
body : '*',
};
}
rpc UpdateDocument(Document) returns (google.protobuf.Empty) {
option (google.api.http) = {
put : '/api/v1/document',
body : '*',
};
}
rpc DeleteDocument(DeleteDocumentRequest) returns (google.protobuf.Empty) {
option (google.api.http) = {
delete : '/api/v1/document',
};
}
rpc GetDocument(GetDocumentRequest) returns (Document) {
option (google.api.http) = {
get : '/api/v1/document',
};
}
rpc ListDocument(ListDocumentRequest) returns (ListDocumentReply) {
option (google.api.http) = {
get : '/api/v1/document/list',
};
}
}

@ -167,13 +167,51 @@ func (s *AttachmentAPIService) ListAttachment(ctx context.Context, req *pb.ListA
}
}
}
s.logger.Debug("ListAttachment", zap.Any("pbAttachments", pbAttachments), zap.Int64("total", total), zap.Any("attachments", attachments))
return &pb.ListAttachmentReply{Total: total, Attachment: pbAttachments}, nil
}
// 上传文档
// UploadDocument 上传文档
func (s *AttachmentAPIService) UploadDocument(ctx *gin.Context) {
userCliams, statusCodes, err := s.checkGinPermission(ctx)
if err != nil {
ctx.JSON(statusCodes, ginResponse{Code: statusCodes, Message: err.Error(), Error: err.Error()})
return
}
form, err := ctx.MultipartForm()
if err != nil {
ctx.JSON(http.StatusBadRequest, ginResponse{Code: http.StatusBadRequest, Message: err.Error(), Error: err.Error()})
return
}
var attachments []*model.Attachment
name := "file"
fileheaders := form.File[name]
for _, fileheader := range fileheaders {
ext := strings.ToLower(filepath.Ext(fileheader.Filename))
if !filetil.IsDocument(ext) {
ctx.JSON(http.StatusBadRequest, ginResponse{Code: http.StatusBadRequest, Message: "不支持的文件类型", Error: "不支持的文件类型"})
return
}
attachment, err := s.saveFile(ctx, fileheader)
if err != nil {
os.Remove("." + attachment.Path)
ctx.JSON(http.StatusInternalServerError, ginResponse{Code: http.StatusInternalServerError, Message: err.Error(), Error: err.Error()})
return
}
attachment.UserId = userCliams.UserId
attachment.Type = model.AttachmentTypeDocument
attachments = append(attachments, attachment)
}
err = s.dbModel.CreateAttachments(attachments)
if err != nil {
ctx.JSON(http.StatusInternalServerError, ginResponse{Code: http.StatusInternalServerError, Message: err.Error(), Error: err.Error()})
return
}
ctx.JSON(http.StatusOK, ginResponse{Code: http.StatusOK, Message: "ok", Data: attachments})
}
// UploadAvatar 上传头像

@ -0,0 +1,41 @@
package biz
import (
"context"
pb "moredoc/api/v1"
"moredoc/model"
"go.uber.org/zap"
"google.golang.org/protobuf/types/known/emptypb"
)
type DocumentAPIService struct {
pb.UnimplementedDocumentAPIServer
dbModel *model.DBModel
logger *zap.Logger
}
func NewDocumentAPIService(dbModel *model.DBModel, logger *zap.Logger) (service *DocumentAPIService) {
return &DocumentAPIService{dbModel: dbModel, logger: logger.Named("DocumentAPIService")}
}
func (s *DocumentAPIService) CreateDocument(ctx context.Context, req *pb.CreateDocumentRequest) (*emptypb.Empty, error) {
return &emptypb.Empty{}, nil
}
func (s *DocumentAPIService) UpdateDocument(ctx context.Context, req *pb.Document) (*emptypb.Empty, error) {
return &emptypb.Empty{}, nil
}
func (s *DocumentAPIService) DeleteDocument(ctx context.Context, req *pb.DeleteDocumentRequest) (*emptypb.Empty, error) {
return &emptypb.Empty{}, nil
}
func (s *DocumentAPIService) GetDocument(ctx context.Context, req *pb.GetDocumentRequest) (*pb.Document, error) {
return &pb.Document{}, nil
}
func (s *DocumentAPIService) ListDocument(ctx context.Context, req *pb.ListDocumentRequest) (*pb.ListDocumentReply, error) {
return &pb.ListDocumentReply{}, nil
}

@ -10,7 +10,8 @@ import (
// TODO: 附件管理需要有一个定时任务定时根据type和type_id清理无效的附件数据同时删除无效的文件
const (
AttachmentTypeAvatar = iota // 用户头像
AttachmentTypeUnknown = iota // 未知
AttachmentTypeAvatar // 用户头像
AttachmentTypeDocument // 文档
AttachmentTypeArticle // 文章
AttachmentTypeComment // 评论
@ -32,7 +33,7 @@ type Attachment struct {
Hash string `form:"hash" json:"hash,omitempty" gorm:"column:hash;type:char(32);size:32;index:hash;comment:文件MD5;"`
UserId int64 `form:"user_id" json:"user_id,omitempty" gorm:"column:user_id;type:bigint(20);default:0;index:user_id;comment:用户 id;"`
TypeId int64 `form:"type_id" json:"type_id,omitempty" gorm:"column:type_id;type:bigint(20);default:0;comment:类型数据ID对应与用户头像时则为用户id对应为文档时则为文档ID;"`
Type int `form:"type" json:"type,omitempty" gorm:"column:type;type:smallint(5);default:0;comment:附件类型(0 头像1 文档2 文章附件 ...);"`
Type int `form:"type" json:"type,omitempty" gorm:"column:type;type:smallint(5);default:0;comment:附件类型(0 位置1 头像2 文档3 文章附件 ...);"`
Enable bool `form:"enable" json:"enable,omitempty" gorm:"column:enable;type:tinyint(3);default:1;comment:是否合法;"`
Path string `form:"path" json:"path,omitempty" gorm:"column:path;type:varchar(255);size:255;comment:文件存储路径;"`
Name string `form:"name" json:"name,omitempty" gorm:"column:name;type:varchar(255);size:255;comment:文件原名称;"`
@ -60,6 +61,16 @@ func (m *DBModel) CreateAttachment(attachment *Attachment) (err error) {
return
}
// CreateAttachment 创建Attachment
func (m *DBModel) CreateAttachments(attachments []*Attachment) (err error) {
err = m.db.Create(attachments).Error
if err != nil {
m.logger.Error("CreateAttachment", zap.Error(err))
return
}
return
}
// GetAttachmentTypeName 获取附件类型名称
func (m *DBModel) GetAttachmentTypeName(typ int) string {
name, _ := attachmentTypeName[typ]

@ -307,13 +307,13 @@ func (m *DBModel) initConfig() (err error) {
cfgs := []Config{
// 系统配置项
{Category: ConfigCategorySystem, Name: ConfigSystemTitle, Label: "网站名称", Value: "MOREDOC · 魔刀文库", Placeholder: "请输入您网站的名称", InputType: "text", Sort: 1, Options: ""},
{Category: ConfigCategorySystem, Name: ConfigSystemDescription, Label: "网站描述", Value: "MOREDOC · 魔刀文库", Placeholder: "请输入您网站的描述", InputType: "text", Sort: 2, Options: ""},
{Category: ConfigCategorySystem, Name: ConfigSystemDescription, Label: "网站描述", Value: "MOREDOC · 魔刀文库", Placeholder: "请输入您网站的描述", InputType: "textarea", Sort: 2, Options: ""},
{Category: ConfigCategorySystem, Name: ConfigSystemKeywords, Label: "网站关键字", Value: "MOREDOC · 魔刀文库", Placeholder: "请输入您网站的关键字", InputType: "text", Sort: 3, Options: ""},
{Category: ConfigCategorySystem, Name: ConfigSystemLogo, Label: "网站Logo", Value: "", Placeholder: "请输入您网站的Logo", InputType: "text", Sort: 4, Options: ""},
{Category: ConfigCategorySystem, Name: ConfigSystemFavicon, Label: "网站Favicon", Value: "", Placeholder: "请输入您网站的Favicon", InputType: "text", Sort: 5, Options: ""},
{Category: ConfigCategorySystem, Name: ConfigSystemLogo, Label: "网站Logo", Value: "", Placeholder: "请输入您网站的Logo路径", InputType: "text", Sort: 4, Options: ""},
{Category: ConfigCategorySystem, Name: ConfigSystemFavicon, Label: "网站Favicon", Value: "", Placeholder: "请输入您网站的Favicon路径", InputType: "text", Sort: 5, Options: ""},
{Category: ConfigCategorySystem, Name: ConfigSystemIcp, Label: "网站备案号", Value: "", Placeholder: "请输入您网站的备案号", InputType: "text", Sort: 6, Options: ""},
{Category: ConfigCategorySystem, Name: ConfigSystemDomain, Label: "网站域名", Value: "", Placeholder: "请输入您网站的域名", InputType: "textarea", Sort: 7, Options: ""},
{Category: ConfigCategorySystem, Name: ConfigSystemAnalytics, Label: "网站统计代码", Value: "", Placeholder: "请输入您网站的统计代码", InputType: "text", Sort: 8, Options: ""},
{Category: ConfigCategorySystem, Name: ConfigSystemDomain, Label: "网站域名", Value: "", Placeholder: "请输入您网站的域名访问地址,如 https://moredoc.mnt.ltd", InputType: "text", Sort: 7, Options: ""},
{Category: ConfigCategorySystem, Name: ConfigSystemAnalytics, Label: "网站统计代码", Value: "", Placeholder: "请输入您网站的统计代码", InputType: "textarea", Sort: 8, Options: ""},
{Category: ConfigCategorySystem, Name: ConfigSystemTheme, Label: "网站主题", Value: "default", Placeholder: "请输入您网站的主题", InputType: "text", Sort: 9, Options: ""},
{Category: ConfigCategorySystem, Name: ConfigSystemCopyright, Label: "网站版权信息", Value: "", Placeholder: "请输入您网站的版权信息", InputType: "text", Sort: 10, Options: ""},

@ -20,7 +20,7 @@ type Document struct {
Height int `form:"height" json:"height,omitempty" gorm:"column:height;type:int(11);size:11;default:0;comment:高;"`
Preview int `form:"preview" json:"preview,omitempty" gorm:"column:preview;type:int(11);size:11;default:0;comment:允许预览页数;"`
Pages int `form:"pages" json:"pages,omitempty" gorm:"column:pages;type:int(11);size:11;default:0;comment:文档页数;"`
Uuid string `form:"uuid" json:"uuid,omitempty" gorm:"column:uuid;type:varchar(36);size:36;comment:文档UUID用于隐藏文档真实路径;"`
UUID string `form:"uuid" json:"uuid,omitempty" gorm:"column:uuid;type:varchar(36);size:36;comment:文档UUID用于隐藏文档真实路径;"`
DownloadCount int `form:"download_count" json:"download_count,omitempty" gorm:"column:download_count;type:int(11);size:11;default:0;comment:下载人次;"`
ViewCount int `form:"view_count" json:"view_count,omitempty" gorm:"column:view_count;type:int(11);size:11;default:0;comment:浏览人次;"`
FavoriteCount int `form:"favorite_count" json:"favorite_count,omitempty" gorm:"column:favorite_count;type:int(11);size:11;default:0;comment:收藏人次;"`
@ -32,35 +32,10 @@ type Document struct {
Status int `form:"status" json:"status,omitempty" gorm:"column:status;type:smallint(6);size:6;default:0;index:status;comment:文档状态0 待转换1 转换中2 转换完成3 转换失败4 禁用;"`
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:更新时间;"`
DeletedAt *gorm.DeletedAt
DeletedUserId int64 `form:"deleted_user_id" json:"deleted_user_id,omitempty" gorm:"column:deleted_user_id;type:bigint(20);size:20;default:0;comment:删除用户ID;"`
}
// 这里是proto文件中的结构体可以根据需要删除或者调整
//message Document {
// int64 id = 1;
// string title = 2;
// string keywords = 3;
// string description = 4;
// int64 user_id = 5;
// string cover = 6;
// int32 width = 7;
// int32 height = 8;
// int32 preview = 9;
// int32 pages = 10;
// string uuid = 11;
// int32 download_count = 12;
// int32 view_count = 13;
// int32 favorite_count = 14;
// int32 comment_count = 15;
// int32 score = 16;
// int32 score_count = 17;
// int32 price = 18;
// int64 size = 19;
// int32 status = 20;
// google.protobuf.Timestamp created_at = 21 [ (gogoproto.stdtime) = true ];
// google.protobuf.Timestamp updated_at = 22 [ (gogoproto.stdtime) = true ];
// google.protobuf.Timestamp deleted_at = 23 [ (gogoproto.stdtime) = true ];
//}
func (Document) TableName() string {
return tablePrefix + "document"
}

@ -86,5 +86,14 @@ func RegisterGRPCService(dbModel *model.DBModel, logger *zap.Logger, endpoint st
return
}
// 注册文档服务
documentAPIService := biz.NewDocumentAPIService(dbModel, logger)
v1.RegisterDocumentAPIServer(grpcServer, documentAPIService)
err = v1.RegisterDocumentAPIHandlerFromEndpoint(context.Background(), gwmux, endpoint, dialOpts)
if err != nil {
logger.Error("RegisterDocumentAPIHandlerFromEndpoint", zap.Error(err))
return
}
return
}

@ -22,6 +22,27 @@ var imagesExt = map[string]struct{}{
// ".webp": {},
}
var documentExt = map[string]struct{}{
// word
".doc": {}, ".docx": {}, ".rtf": {}, ".wps": {}, ".odt": {},
// PPT
".ppt": {}, ".pptx": {}, ".pps": {}, ".ppsx": {}, ".dps": {}, ".odp": {}, ".pot": {},
// XLS
".xls": {}, ".xlsx": {}, ".et": {}, ".ods": {},
// 其他
".epub": {}, ".umd": {}, ".chm": {}, ".mobi": {},
// TXT
".txt": {},
// PDF
".pdf": {},
}
// IsDocument 是否是文档
func IsDocument(ext string) bool {
_, ok := documentExt[ext]
return ok
}
// IsImage 判断文件是否是图片
func IsImage(ext string) bool {
_, ok := imagesExt[ext]

@ -0,0 +1,43 @@
import service from '~/utils/request'
export const createDocument = (data) => {
return service({
url: '/api/v1/document',
method: 'post',
data,
})
}
export const updateDocument = (data) => {
return service({
url: '/api/v1/document',
method: 'put',
data,
})
}
export const deleteDocument = (params) => {
return service({
url: '/api/v1/document',
method: 'delete',
params,
})
}
export const getDocument = (params) => {
return service({
url: '/api/v1/document',
method: 'get',
params,
})
}
export const listDocument = (params) => {
return service({
url: '/api/v1/document/list',
method: 'get',
params,
})
}

@ -103,7 +103,6 @@ export const user = {
// TODO: 剔除。这里只是开发的时候暂时用到
allowPages.push(
'/admin/document',
'/admin/document/category',
'/admin/document/list',
'/admin/document/recycle'
)

@ -7,12 +7,13 @@ export const userStatusOptions = [
]
export const attachmentTypeOptions = [
{ label: '头像', value: 0 },
{ label: '文档', value: 1 },
{ label: '文章', value: 2 },
{ label: '评论', value: 3 },
{ label: '横幅', value: 4 },
{ label: '分类封面', value: 5 },
{ label: '未知', value: 0 },
{ label: '头像', value: 1 },
{ label: '文档', value: 2 },
{ label: '文章', value: 3 },
{ label: '评论', value: 4 },
{ label: '横幅', value: 5 },
{ label: '分类封面', value: 6 },
]
// 0 网站横幅1 小程序横幅2 APP横幅

@ -7,6 +7,12 @@ const cumstomPermissionMap = {
children: [],
pages: ['/admin/document', '/admin/document/list'],
},
'api.v1.CategoryAPI': {
label: '分类管理',
path: 'ListCategory',
children: [],
pages: ['/admin/document', '/admin/document/category'],
},
'api.v1.UserAPI': {
label: '用户管理',
path: 'ListUser',

@ -113,16 +113,18 @@ export function formatBytes(bytes, decimals = 2) {
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`
}
// 分类转树形结构
// categoryToTrees 分类转树形结构
export function categoryToTrees(categories) {
const result = []
const map = {}
categories.forEach((item) => {
item.disabled = !item.enable
map[item.id] = item
})
categories.forEach((item) => {
const parent = map[item.parent_id]
if (parent) {
if (parent.disabled) item.disabled = true
;(parent.children || (parent.children = [])).push(item)
} else {
result.push(item)

Loading…
Cancel
Save