代码调整

dev
truthhun 2 years ago
parent f8f21003b6
commit 45a62a0773

@ -1,40 +0,0 @@
package biz
import (
"context"
v1 "moredoc/api/v1"
"moredoc/model"
"time"
"github.com/golang/protobuf/ptypes/empty"
"go.uber.org/zap"
"google.golang.org/protobuf/types/known/emptypb"
)
type HealthAPIService struct {
dbModel *model.DBModel
logger *zap.Logger
}
func NewHealthAPIService(dbModel *model.DBModel, logger *zap.Logger) (service *HealthAPIService) {
return &HealthAPIService{
dbModel: dbModel,
logger: logger.Named("biz"),
}
}
// Health is health check
func (p *HealthAPIService) Health(ctx context.Context, in *empty.Empty) (out *empty.Empty, err error) {
out = &emptypb.Empty{}
return
}
// Ping ping pong
func (p *HealthAPIService) Ping(ctx context.Context, in *v1.PingRequest) (out *v1.PongReply, err error) {
createdAt := time.Now()
out = &v1.PongReply{
Name: in.Name,
CreatedAt: &createdAt,
}
return
}

@ -11,20 +11,20 @@ import (
type Attachment struct {
Id int64 `form:"id" json:"id,omitempty" gorm:"primaryKey;autoIncrement;column:id;comment:附件 id;"`
Hash string `form:"hash" json:"hash,omitempty" gorm:"column:hash;type:char(32);size:32;default:;index:hash;comment:文件MD5;"`
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) unsigned;default:0;index:user_id;comment:用户 id;"`
TypeId int64 `form:"type_id" json:"type_id,omitempty" gorm:"column:type_id;type:bigint(20) unsigned;default:0;comment:类型数据ID对应与用户头像时则为用户id对应为文档时则为文档ID;"`
Type int `form:"type" json:"type,omitempty" gorm:"column:type;type:smallint(5) unsigned;default:0;comment:附件类型(0 头像1 文档2 文章附件 ...);"`
IsApproved int8 `form:"is_approved" json:"is_approved,omitempty" gorm:"column:is_approved;type:tinyint(3) unsigned;default:1;comment:是否合法;"`
Path string `form:"path" json:"path,omitempty" gorm:"column:path;type:varchar(255);size:255;default:;comment:文件存储路径;"`
Name string `form:"name" json:"name,omitempty" gorm:"column:name;type:varchar(255);size:255;default:;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:文件原名称;"`
Size int64 `form:"size" json:"size,omitempty" gorm:"column:size;type:bigint(20) unsigned;default:0;comment:文件大小;"`
Width int64 `form:"width" json:"width,omitempty" gorm:"column:width;type:bigint(20) unsigned;default:0;comment:宽度;"`
Height int64 `form:"height" json:"height,omitempty" gorm:"column:height;type:bigint(20) unsigned;default:0;comment:高度;"`
Ext string `form:"ext" json:"ext,omitempty" gorm:"column:ext;type:varchar(32);size:32;default:;comment:文件类型,如 .pdf 。统一处理成小写;"`
Ip string `form:"ip" json:"ip,omitempty" gorm:"column:ip;type:varchar(16);size:16;default:;comment:上传文档的用户IP地址;"`
CreatedAt time.Time `form:"created_at" json:"created_at,omitempty" gorm:"column:created_at;type:datetime;default:;comment:创建时间;"`
UpdatedAt time.Time `form:"updated_at" json:"updated_at,omitempty" gorm:"column:updated_at;type:datetime;default:;comment:更新时间;"`
Ext string `form:"ext" json:"ext,omitempty" gorm:"column:ext;type:varchar(32);size:32;comment:文件类型,如 .pdf 。统一处理成小写;"`
Ip string `form:"ip" json:"ip,omitempty" gorm:"column:ip;type:varchar(16);size:16;comment:上传文档的用户IP地址;"`
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:更新时间;"`
}
// 这里是proto文件中的结构体可以根据需要删除或者调整

@ -11,14 +11,14 @@ import (
type Banner struct {
Id int64 `form:"id" json:"id,omitempty" gorm:"primaryKey;autoIncrement;column:id;comment:;"`
Title string `form:"title" json:"title,omitempty" gorm:"column:title;type:varchar(255);size:255;default:;comment:横幅名称;"`
Path string `form:"path" json:"path,omitempty" gorm:"column:path;type:varchar(255);size:255;default:;comment:横幅地址;"`
Title string `form:"title" json:"title,omitempty" gorm:"column:title;type:varchar(255);size:255;comment:横幅名称;"`
Path string `form:"path" json:"path,omitempty" gorm:"column:path;type:varchar(255);size:255;comment:横幅地址;"`
Sort int `form:"sort" json:"sort,omitempty" gorm:"column:sort;type:int(11);size:11;default:0;comment:排序,值越大越靠前;"`
Status int8 `form:"status" json:"status,omitempty" gorm:"column:status;type:tinyint(4);size:4;default:0;comment:0 正常1禁用;"`
Category int8 `form:"category" json:"category,omitempty" gorm:"column:category;type:tinyint(4);size:4;default:0;comment:0 PC横幅1 H5横幅2 小程序横幅3 APP横幅;"`
CreatedAt time.Time `form:"created_at" json:"created_at,omitempty" gorm:"column:created_at;type:datetime;default:;comment:创建时间;"`
UpdatedAt time.Time `form:"updated_at" json:"updated_at,omitempty" gorm:"column:updated_at;type:datetime;default:;comment:更新时间;"`
Url string `form:"url" json:"url,omitempty" gorm:"column:url;type:varchar(255);size:255;default:;comment:横幅跳转地址;"`
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:更新时间;"`
Url string `form:"url" json:"url,omitempty" gorm:"column:url;type:varchar(255);size:255;comment:横幅跳转地址;"`
}
// 这里是proto文件中的结构体可以根据需要删除或者调整

@ -12,14 +12,14 @@ import (
type Category struct {
Id int `form:"id" json:"id,omitempty" gorm:"primaryKey;autoIncrement;column:id;comment:;"`
ParentId int `form:"parent_id" json:"parent_id,omitempty" gorm:"column:parent_id;type:int(11);size:11;default:0;index:parent_id_title,unique;index:parent_id;comment:上级ID;"`
Title string `form:"title" json:"title,omitempty" gorm:"column:title;type:varchar(64);size:64;default:;index:parent_id_title,unique;comment:分类名称;"`
Cover string `form:"cover" json:"cover,omitempty" gorm:"column:cover;type:varchar(255);size:255;default:;comment:分类封面;"`
Title string `form:"title" json:"title,omitempty" gorm:"column:title;type:varchar(64);size:64;index:parent_id_title,unique;comment:分类名称;"`
Cover string `form:"cover" json:"cover,omitempty" gorm:"column:cover;type:varchar(255);size:255;comment:分类封面;"`
DocCount int `form:"doc_count" json:"doc_count,omitempty" gorm:"column:doc_count;type:int(11);size:11;default:0;comment:文档统计;"`
Sort int `form:"sort" json:"sort,omitempty" gorm:"column:sort;type:int(11);size:11;default:0;comment:排序,值越大越靠前;"`
Alias string `form:"alias" json:"alias,omitempty" gorm:"column:alias;type:varchar(64);size:64;default:;comment:别名,限英文和数字等组成;"`
Alias string `form:"alias" json:"alias,omitempty" gorm:"column:alias;type:varchar(64);size:64;comment:别名,限英文和数字等组成;"`
Status int8 `form:"status" json:"status,omitempty" gorm:"column:status;type:tinyint(1);size:1;default:0;comment:状态0 正常1 禁用;"`
CreatedAt time.Time `form:"created_at" json:"created_at,omitempty" gorm:"column:created_at;type:datetime;default:;comment:创建时间;"`
UpdatedAt time.Time `form:"updated_at" json:"updated_at,omitempty" gorm:"column:updated_at;type:datetime;default:;comment:更新时间;"`
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:更新时间;"`
}
// 这里是proto文件中的结构体可以根据需要删除或者调整

@ -30,16 +30,16 @@ const (
type Config struct {
Id int64 `form:"id" json:"id,omitempty" gorm:"primaryKey;autoIncrement;column:id;comment:;"`
Label string `form:"label" json:"label,omitempty" gorm:"column:label;type:varchar(64);size:64;default:;comment:标签名称;"`
Name string `form:"name" json:"name,omitempty" gorm:"column:name;type:varchar(64);size:64;default:;index:name_category,unique;comment:表单字段名称;"`
Value string `form:"value" json:"value,omitempty" gorm:"column:value;type:text;default:;comment:值;"`
Placeholder int `form:"placeholder" json:"placeholder,omitempty" gorm:"column:placeholder;type:int(11);size:11;default:0;comment:提示信息;"`
InputType int `form:"input_type" json:"input_type,omitempty" gorm:"column:input_type;type:int(11);size:11;default:0;comment:输入类型;"`
Category string `form:"category" json:"category,omitempty" gorm:"column:category;type:varchar(32);size:32;default:;index:name_category,unique;index:category;comment:所属类别;"`
Label string `form:"label" json:"label,omitempty" gorm:"column:label;type:varchar(64);size:64;comment:标签名称;"`
Name string `form:"name" json:"name,omitempty" gorm:"column:name;type:varchar(64);size:64;index:name_category,unique;comment:表单字段名称;"`
Value string `form:"value" json:"value,omitempty" gorm:"column:value;type:text;comment:值;"`
Placeholder string `form:"placeholder" json:"placeholder,omitempty" gorm:"column:placeholder;type:varchar(255);size:255;comment:提示信息;"`
InputType string `form:"input_type" json:"input_type,omitempty" gorm:"column:input_type;type:varchar(32);size:32;default:text;comment:输入类型;"`
Category string `form:"category" json:"category,omitempty" gorm:"column:category;type:varchar(32);size:32;index:name_category,unique;index:category;comment:所属类别;"`
Sort int `form:"sort" json:"sort,omitempty" gorm:"column:sort;type:int(11);size:11;default:0;comment:同一category下的排序;"`
Options string `form:"options" json:"options,omitempty" gorm:"column:options;type:text;default:;comment:针对checkbox等的枚举值;"`
CreatedAt time.Time `form:"created_at" json:"created_at,omitempty" gorm:"column:created_at;type:datetime;default:;comment:创建时间;"`
UpdatedAt time.Time `form:"updated_at" json:"updated_at,omitempty" gorm:"column:updated_at;type:datetime;default:;comment:更新时间;"`
Options string `form:"options" json:"options,omitempty" gorm:"column:options;type:text;comment:针对checkbox等的枚举值;"`
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:更新时间;"`
}
// 这里是proto文件中的结构体可以根据需要删除或者调整
@ -222,6 +222,11 @@ func (m *DBModel) DeleteConfig(ids []interface{}) (err error) {
return
}
const (
ConfigJWTDuration = "duration"
ConfigJWTSecret = "secret"
)
type ConfigJWT struct {
Duration int `json:"duration"` // JWT有效期
Secret string `json:"secret"` // JWT加密密钥
@ -260,6 +265,13 @@ func (m *DBModel) GetConfigOfJWT() (config ConfigJWT) {
return
}
const (
ConfigCaptchaLength = "length"
ConfigCaptchaWidth = "width"
ConfigCaptchaHeight = "height"
ConfigCaptchaType = "type"
)
type ConfigCaptcha struct {
Length int `json:"length"` // 验证码长度
Width int `json:"width"`
@ -300,6 +312,19 @@ func (m *DBModel) GetConfigOfCaptcha() (config ConfigCaptcha) {
return
}
const (
ConfigSystemDomain = "domain"
ConfigSystemTitle = "title"
ConfigSystemDescription = "description"
ConfigSystemKeywords = "keywords"
ConfigSystemLogo = "logo"
ConfigSystemFavicon = "favicon"
ConfigSystemIcp = "icp"
ConfigSystemAnalytics = "analytics"
ConfigSystemTheme = "theme"
ConfigSystemCopyright = "copyright"
)
type ConfigSystem struct {
Domain string `json:"domain"` // 站点域名,不带 HTTPS:// 和 HTTP://
Title string `json:"title"` // 系统名称
@ -337,7 +362,7 @@ func (m *DBModel) GetConfigOfSystem() (config ConfigSystem) {
return
}
var (
const (
ConfigSecurityIsClose = "is_close" // 是否关闭注册
ConfigSecurityEnableRegister = "enable_register" // 是否允许注册
ConfigSecurityEnableCaptchaLogin = "enable_captcha_login" // 是否开启登录验证码

@ -11,16 +11,16 @@ import (
type Document struct {
Id int64 `form:"id" json:"id,omitempty" gorm:"primaryKey;autoIncrement;column:id;comment:;"`
Title string `form:"title" json:"title,omitempty" gorm:"column:title;type:varchar(255);size:255;default:;comment:文档名称;"`
Keywords string `form:"keywords" json:"keywords,omitempty" gorm:"column:keywords;type:varchar(255);size:255;default:;comment:文档关键字;"`
Description string `form:"description" json:"description,omitempty" gorm:"column:description;type:varchar(512);size:512;default:;comment:文档描述;"`
Title string `form:"title" json:"title,omitempty" gorm:"column:title;type:varchar(255);size:255;comment:文档名称;"`
Keywords string `form:"keywords" json:"keywords,omitempty" gorm:"column:keywords;type:varchar(255);size:255;comment:文档关键字;"`
Description string `form:"description" json:"description,omitempty" gorm:"column:description;type:varchar(512);size:512;comment:文档描述;"`
UserId int64 `form:"user_id" json:"user_id,omitempty" gorm:"column:user_id;type:bigint(20);size:20;default:0;index:user_id;comment:文档所属用户ID;"`
Cover string `form:"cover" json:"cover,omitempty" gorm:"column:cover;type:varchar(255);size:255;default:;comment:文档封面;"`
Cover string `form:"cover" json:"cover,omitempty" gorm:"column:cover;type:varchar(255);size:255;comment:文档封面;"`
Width int `form:"width" json:"width,omitempty" gorm:"column:width;type:int(11);size:11;default:0;comment:宽;"`
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;default:;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:收藏人次;"`
@ -30,9 +30,9 @@ type Document struct {
Price int `form:"price" json:"price,omitempty" gorm:"column:price;type:int(11);size:11;default:0;comment:价格0表示免费;"`
Size int64 `form:"size" json:"size,omitempty" gorm:"column:size;type:bigint(20);size:20;default:0;comment:文件大小;"`
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;default:;comment:创建时间;"`
UpdatedAt time.Time `form:"updated_at" json:"updated_at,omitempty" gorm:"column:updated_at;type:datetime;default:;comment:更新时间;"`
DeletedAt time.Time `form:"deleted_at" json:"deleted_at,omitempty" gorm:"column:deleted_at;type:datetime;default:;comment:;"`
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 time.Time `form:"deleted_at" json:"deleted_at,omitempty" gorm:"column:deleted_at;type:datetime;comment:;"`
}
// 这里是proto文件中的结构体可以根据需要删除或者调整

@ -13,8 +13,8 @@ type DocumentCategory struct {
Id int64 `form:"id" json:"id,omitempty" gorm:"primaryKey;autoIncrement;column:id;comment:;"`
DocumentId int64 `form:"document_id" json:"document_id,omitempty" gorm:"column:document_id;type:bigint(20);size:20;default:0;comment:文档ID;"`
CategoryId int64 `form:"category_id" json:"category_id,omitempty" gorm:"column:category_id;type:bigint(20);size:20;default:0;index:category_id;comment:分类ID;"`
CreatedAt time.Time `form:"created_at" json:"created_at,omitempty" gorm:"column:created_at;type:datetime;default:;comment:创建时间;"`
UpdatedAt time.Time `form:"updated_at" json:"updated_at,omitempty" gorm:"column:updated_at;type:datetime;default:;comment:更新时间;"`
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:更新时间;"`
}
// 这里是proto文件中的结构体可以根据需要删除或者调整

@ -14,8 +14,8 @@ type DocumentScore struct {
DocumentId int64 `form:"document_id" json:"document_id,omitempty" gorm:"column:document_id;type:bigint(20);size:20;default:0;comment:文档ID;"`
UserId int64 `form:"user_id" json:"user_id,omitempty" gorm:"column:user_id;type:bigint(20);size:20;default:0;comment:用户ID;"`
Score int `form:"score" json:"score,omitempty" gorm:"column:score;type:int(11);size:11;default:0;comment:文档评分值3位数如500表示5分;"`
CreatedAt time.Time `form:"created_at" json:"created_at,omitempty" gorm:"column:created_at;type:datetime;default:;comment:创建时间;"`
UpdatedAt time.Time `form:"updated_at" json:"updated_at,omitempty" gorm:"column:updated_at;type:datetime;default:;comment:更新时间;"`
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:更新时间;"`
}
// 这里是proto文件中的结构体可以根据需要删除或者调整

@ -13,9 +13,9 @@ type Download 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;index:user_id;comment:下载文档的用户ID;"`
DocumentId int64 `form:"document_id" json:"document_id,omitempty" gorm:"column:document_id;type:bigint(20);size:20;default:0;comment:被下载的文档ID;"`
Ip string `form:"ip" json:"ip,omitempty" gorm:"column:ip;type:varchar(16);size:16;default:;comment:下载文档的用户IP;"`
CreatedAt time.Time `form:"created_at" json:"created_at,omitempty" gorm:"column:created_at;type:datetime;default:;comment:创建时间;"`
UpdatedAt time.Time `form:"updated_at" json:"updated_at,omitempty" gorm:"column:updated_at;type:datetime;default:;comment:更新时间;"`
Ip string `form:"ip" json:"ip,omitempty" gorm:"column:ip;type:varchar(16);size:16;comment:下载文档的用户IP;"`
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:更新时间;"`
}
// 这里是proto文件中的结构体可以根据需要删除或者调整

@ -11,13 +11,13 @@ import (
type Friendlink struct {
Id int `form:"id" json:"id,omitempty" gorm:"primaryKey;autoIncrement;column:id;comment:;"`
Title string `form:"title" json:"title,omitempty" gorm:"column:title;type:varchar(64);size:64;default:;comment:链接名称;"`
Link string `form:"link" json:"link,omitempty" gorm:"column:link;type:varchar(255);size:255;default:;comment:链接地址;"`
Note string `form:"note" json:"note,omitempty" gorm:"column:note;type:text;default:;comment:备注;"`
Title string `form:"title" json:"title,omitempty" gorm:"column:title;type:varchar(64);size:64;comment:链接名称;"`
Link string `form:"link" json:"link,omitempty" gorm:"column:link;type:varchar(255);size:255;comment:链接地址;"`
Note string `form:"note" json:"note,omitempty" gorm:"column:note;type:text;comment:备注;"`
Sort int `form:"sort" json:"sort,omitempty" gorm:"column:sort;type:int(11);size:11;default:0;comment:排序,值越大越靠前;"`
Status int8 `form:"status" json:"status,omitempty" gorm:"column:status;type:tinyint(4);size:4;default:0;comment:状态0 正常1 禁用;"`
CreatedAt time.Time `form:"created_at" json:"created_at,omitempty" gorm:"column:created_at;type:datetime;default:;comment:创建时间;"`
UpdatedAt time.Time `form:"updated_at" json:"updated_at,omitempty" gorm:"column:updated_at;type:datetime;default:;comment:更新时间;"`
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:更新时间;"`
}
// 这里是proto文件中的结构体可以根据需要删除或者调整

@ -4,8 +4,10 @@ import (
"database/sql"
"errors"
"moredoc/conf"
"moredoc/util/captcha"
"strings"
"github.com/gofrs/uuid"
"go.uber.org/zap"
"gorm.io/driver/mysql"
"gorm.io/gorm"
@ -138,7 +140,11 @@ func (m *DBModel) SyncDB() (err error) {
&User{},
}
if err = m.db.AutoMigrate(tableModels...); err != nil {
m.logger.Error("SyncDB", zap.Error(err))
m.logger.Fatal("SyncDB", zap.Error(err))
}
if err = m.initDatabase(); err != nil {
m.logger.Fatal("SyncDB", zap.Error(err))
}
return
}
@ -176,3 +182,92 @@ func (m *DBModel) showTableColumn(tableName string) (columns []TableColumn, err
}
return
}
// initialDatabase 初始化数据库相关数据
func (m *DBModel) initDatabase() (err error) {
// 1. 初始化用户
if err = m.initUser(); err != nil {
m.logger.Error("initialDatabase", zap.Error(err))
return
}
// 2. 初始化配置
if err = m.initConfig(); err != nil {
m.logger.Error("initialDatabase", zap.Error(err))
return
}
return
}
func (m *DBModel) initUser() (err error) {
// 如果不存在任意用户,则初始化一个用户作为管理员
var existUser User
m.db.Select("id").First(&existUser)
if existUser.Id > 0 {
return
}
// 初始化一个用户
user := &User{Username: "admin", Password: "123456"}
err = m.CreateUser(user)
if err != nil {
m.logger.Error("initUser", zap.Error(err))
}
return
}
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: 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: 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: ConfigSystemTheme, Label: "网站主题", Value: "default", Placeholder: "请输入您网站的主题", InputType: "text", Sort: 9, Options: ""},
{Category: ConfigCategorySystem, Name: ConfigSystemCopyright, Label: "网站版权信息", Value: "", Placeholder: "请输入您网站的版权信息", InputType: "text", Sort: 10, Options: ""},
// JWT 配置项
{Category: ConfigCategoryJWT, Name: ConfigJWTDuration, Label: "Token有效期", Value: "365", Placeholder: "用户Token签名有效期单位为天默认365天", InputType: "number", Sort: 11, Options: ""},
{Category: ConfigCategoryJWT, Name: ConfigJWTSecret, Label: "Token密钥", Value: uuid.Must(uuid.NewV4()).String(), Placeholder: "用户Token签名密钥修改之后之前所有的token签名都将失效请慎重修改", InputType: "text", Sort: 12, Options: ""},
// 验证码配置项
{Category: ConfigCategoryCaptcha, Name: ConfigCaptchaHeight, Label: "验证码高度", Value: "60", Placeholder: "请输入验证码高度默认为60", InputType: "number", Sort: 13, Options: ""},
{Category: ConfigCategoryCaptcha, Name: ConfigCaptchaWidth, Label: "验证码宽度", Value: "240", Placeholder: "请输入验证码宽度默认为240", InputType: "number", Sort: 14, Options: ""},
{Category: ConfigCategoryCaptcha, Name: ConfigCaptchaLength, Label: "验证码长度", Value: "5", Placeholder: "请输入验证码长度默认为6", InputType: "number", Sort: 15, Options: ""},
{Category: ConfigCategoryCaptcha, Name: ConfigCaptchaType, Label: "验证码类型", Value: "digit", Placeholder: "请选择验证码类型,默认为数字", InputType: "select", Sort: 16, Options: captcha.CaptchaTypeOptions},
// 安全配置项
{Category: ConfigCategorySecurity, Name: ConfigSecurityIsClose, Label: "是否关闭网站", Value: "false", Placeholder: "请选择是否关闭网站", InputType: "swith", Sort: 17, Options: ""},
{Category: ConfigCategorySecurity, Name: ConfigSecurityEnableRegister, Label: "是否允许注册", Value: "true", Placeholder: "请选择是否允许用户注册", InputType: "swith", Sort: 18, Options: ""},
{Category: ConfigCategorySecurity, Name: ConfigSecurityEnableCaptchaLogin, Label: "是否开启登录验证码", Value: "true", Placeholder: "请选择是否开启登录验证码", InputType: "swith", Sort: 19, Options: ""},
{Category: ConfigCategorySecurity, Name: ConfigSecurityEnableCaptchaRegister, Label: "是否开启注册验证码", Value: "true", Placeholder: "请选择是否开启注册验证码", InputType: "swith", Sort: 20, Options: ""},
{Category: ConfigCategorySecurity, Name: ConfigSecurityEnableCaptchaComment, Label: "是否开启评论验证码", Value: "true", Placeholder: "请选择是否开启评论验证码", InputType: "swith", Sort: 21, Options: ""},
{Category: ConfigCategorySecurity, Name: ConfigSecurityEnableCaptchaFindPassword, Label: "是否开启找回密码验证码", Value: "true", Placeholder: "请选择是否开启找回密码验证码", InputType: "swith", Sort: 22, Options: ""},
{Category: ConfigCategorySecurity, Name: ConfigSecurityEnableCaptchaUpload, Label: "是否开启文档上传验证码", Value: "true", Placeholder: "请选择是否开启文档上传验证码", InputType: "swith", Sort: 23, Options: ""},
}
for _, cfg := range cfgs {
existConfig, _ := m.GetConfigByNameCategory(cfg.Name, cfg.Category, "id")
if existConfig.Id > 0 {
// 更新除了值之外的所有字段
cfg.Id = existConfig.Id
err = m.db.Omit("value").Updates(&cfg).Error
if err != nil {
m.logger.Error("initConfig", zap.Error(err))
return
}
continue
}
err = m.CreateConfig(&cfg)
if err != nil {
m.logger.Error("initConfig", zap.Error(err))
return
}
}
return
}

@ -14,27 +14,27 @@ import (
type User struct {
Id int64 `form:"id" json:"id,omitempty" gorm:"primaryKey;autoIncrement;column:id;comment:用户 id;"`
Username string `form:"username" json:"username,omitempty" gorm:"column:username;type:varchar(64);size:64;default:;index:username,unique;comment:用户名;"`
Password string `form:"password" json:"password,omitempty" gorm:"column:password;type:varchar(128);size:128;default:;comment:密码;"`
Nickname string `form:"nickname" json:"nickname,omitempty" gorm:"column:nickname;type:varchar(64);size:64;default:;comment:用户昵称;"`
Mobile string `form:"mobile" json:"mobile,omitempty" gorm:"column:mobile;type:varchar(20);size:20;default:;index:mobile;comment:手机号;"`
Email string `form:"email" json:"email,omitempty" gorm:"column:email;type:varchar(64);size:64;default:;index:email;comment:联系邮箱;"`
Address string `form:"address" json:"address,omitempty" gorm:"column:address;type:varchar(255);size:255;default:;comment:联系地址;"`
Signature string `form:"signature" json:"signature,omitempty" gorm:"column:signature;type:varchar(255);size:255;default:;comment:签名;"`
LastLoginIp string `form:"last_login_ip" json:"last_login_ip,omitempty" gorm:"column:last_login_ip;type:varchar(16);size:16;default:;comment:最后登录 ip 地址;"`
RegisterIp string `form:"register_ip" json:"register_ip,omitempty" gorm:"column:register_ip;type:varchar(16);size:16;default:;comment:注册ip;"`
Username string `form:"username" json:"username,omitempty" gorm:"column:username;type:varchar(64);size:64;index:username,unique;comment:用户名;"`
Password string `form:"password" json:"password,omitempty" gorm:"column:password;type:varchar(128);size:128;comment:密码;"`
Nickname string `form:"nickname" json:"nickname,omitempty" gorm:"column:nickname;type:varchar(64);size:64;comment:用户昵称;"`
Mobile string `form:"mobile" json:"mobile,omitempty" gorm:"column:mobile;type:varchar(20);size:20;index:mobile;comment:手机号;"`
Email string `form:"email" json:"email,omitempty" gorm:"column:email;type:varchar(64);size:64;index:email;comment:联系邮箱;"`
Address string `form:"address" json:"address,omitempty" gorm:"column:address;type:varchar(255);size:255;comment:联系地址;"`
Signature string `form:"signature" json:"signature,omitempty" gorm:"column:signature;type:varchar(255);size:255;comment:签名;"`
LastLoginIp string `form:"last_login_ip" json:"last_login_ip,omitempty" gorm:"column:last_login_ip;type:varchar(16);size:16;comment:最后登录 ip 地址;"`
RegisterIp string `form:"register_ip" json:"register_ip,omitempty" gorm:"column:register_ip;type:varchar(16);size:16;comment:注册ip;"`
DocCount int `form:"doc_count" json:"doc_count,omitempty" gorm:"column:doc_count;type:int(10) unsigned;default:0;comment:上传的文档数;"`
FollowCount int `form:"follow_count" json:"follow_count,omitempty" gorm:"column:follow_count;type:int(10) unsigned;default:0;comment:关注数;"`
FansCount int `form:"fans_count" json:"fans_count,omitempty" gorm:"column:fans_count;type:int(10) unsigned;default:0;comment:粉丝数;"`
FavoriteCount int `form:"favorite_count" json:"favorite_count,omitempty" gorm:"column:favorite_count;type:int(10) unsigned;default:0;comment:收藏数;"`
CommentCount int `form:"comment_count" json:"comment_count,omitempty" gorm:"column:comment_count;type:int(11);size:11;default:0;comment:评论数;"`
Status int8 `form:"status" json:"status,omitempty" gorm:"column:status;type:tinyint(4);size:4;default:0;index:status;comment:用户状态0正常 1禁用 2审核中 3审核拒绝 4审核忽略;"`
Avatar string `form:"avatar" json:"avatar,omitempty" gorm:"column:avatar;type:varchar(255);size:255;default:;comment:头像;"`
Identity string `form:"identity" json:"identity,omitempty" gorm:"column:identity;type:char(18);size:18;default:;comment:身份证号码;"`
Realname string `form:"realname" json:"realname,omitempty" gorm:"column:realname;type:varchar(20);size:20;default:;comment:身份证姓名;"`
LoginAt time.Time `form:"login_at" json:"login_at,omitempty" gorm:"column:login_at;type:datetime;default:;comment:最后登录时间;"`
CreatedAt time.Time `form:"created_at" json:"created_at,omitempty" gorm:"column:created_at;type:datetime;default:;comment:创建时间;"`
UpdatedAt time.Time `form:"updated_at" json:"updated_at,omitempty" gorm:"column:updated_at;type:datetime;default:;comment:更新时间;"`
Avatar string `form:"avatar" json:"avatar,omitempty" gorm:"column:avatar;type:varchar(255);size:255;comment:头像;"`
Identity string `form:"identity" json:"identity,omitempty" gorm:"column:identity;type:char(18);size:18;comment:身份证号码;"`
Realname string `form:"realname" json:"realname,omitempty" gorm:"column:realname;type:varchar(20);size:20;comment:身份证姓名;"`
LoginAt time.Time `form:"login_at" json:"login_at,omitempty" gorm:"column:login_at;type:datetime;comment:最后登录时间;"`
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:更新时间;"`
}
// 这里是proto文件中的结构体可以根据需要删除或者调整
@ -247,7 +247,7 @@ type UserClaims struct {
// CreateUserJWTToken 生成用户JWT Token
func (m *DBModel) CreateUserJWTToken(userId int64) (string, error) {
jwtCfg := m.GetConfigOfJWT()
expireTime := time.Now().Add(time.Duration(jwtCfg.Duration) * time.Second).Unix()
expireTime := time.Now().Add(time.Duration(jwtCfg.Duration) * 24 * time.Hour).Unix()
claims := UserClaims{
UserId: userId,
UUID: uuid.Must(uuid.NewV4()).String(),

@ -61,14 +61,6 @@ func Run(cfg *conf.Config, logger *zap.Logger) {
endpoint := fmt.Sprintf("localhost:%v", cfg.Port)
healthAPIService := biz.NewHealthAPIService(dbModel, logger)
v1.RegisterHealthAPIServer(grpcServer, healthAPIService)
err = v1.RegisterHealthAPIHandlerFromEndpoint(context.Background(), gwmux, endpoint, dialOpts)
if err != nil {
logger.Fatal("RegisterHealthAPIHandlerFromEndpoint", zap.Error(err))
return
}
// 用户API接口服务
userAPIService := biz.NewUserAPIService(dbModel, logger)
v1.RegisterUserAPIServer(grpcServer, userAPIService)

@ -7,11 +7,12 @@ import (
)
var (
store = base64Captcha.DefaultMemStore
width = 240
height = 60
sourceChinese = strings.Join(strings.Split("欢迎使用由深圳市摩枫网络科技有限公司基于阿帕奇开源协议的魔刀文库系统", ""), ",")
sourceString = "1234567890qwertyuioplkjhgfdsazxcvbnm"
store = base64Captcha.DefaultMemStore
width = 240
height = 60
sourceChinese = strings.Join(strings.Split("欢迎使用由深圳市摩枫网络科技有限公司基于阿帕奇开源协议的魔刀文库系统", ""), ",")
sourceString = "1234567890qwertyuioplkjhgfdsazxcvbnm"
CaptchaTypeOptions = "string:字符串\nmath:算术\nchinese:中文\ndigit:数字\naudio:语音"
)
const (

@ -0,0 +1 @@
package util

@ -0,0 +1,15 @@
module.exports = {
root: true,
env: {
browser: true,
node: true,
},
parserOptions: {
parser: '@babel/eslint-parser',
requireConfigFile: false,
},
extends: ['@nuxtjs', 'plugin:nuxt/recommended', 'prettier'],
plugins: [],
// add your custom rules here
rules: {},
}

90
web/.gitignore vendored

@ -0,0 +1,90 @@
# Created by .ignore support plugin (hsz.mobi)
### Node template
# Logs
/logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# Nuxt generate
dist
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless
# IDE / Editor
.idea
# Service worker
sw.*
# macOS
.DS_Store
# Vim swap files
*.swp

@ -0,0 +1,96 @@
###
# Place your Prettier ignore content here
###
# .gitignore content is duplicated here due to https://github.com/prettier/prettier/issues/8506
# Created by .ignore support plugin (hsz.mobi)
### Node template
# Logs
/logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# Nuxt generate
dist
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless
# IDE / Editor
.idea
# Service worker
sw.*
# macOS
.DS_Store
# Vim swap files
*.swp

@ -0,0 +1,4 @@
{
"semi": false,
"singleQuote": true
}

@ -0,0 +1,68 @@
# web
## Build Setup
```bash
# install dependencies
$ npm install
# serve with hot reload at localhost:3000
$ npm run dev
# build for production and launch server
$ npm run build
$ npm run start
# generate static project
$ npm run generate
```
For detailed explanation on how things work, check out the [documentation](https://nuxtjs.org).
## Special Directories
You can create the following extra directories, some of which have special behaviors. Only `pages` is required; you can delete them if you don't want to use their functionality.
### `assets`
The assets directory contains your uncompiled assets such as Stylus or Sass files, images, or fonts.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/assets).
### `components`
The components directory contains your Vue.js components. Components make up the different parts of your page and can be reused and imported into your pages, layouts and even other components.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/components).
### `layouts`
Layouts are a great help when you want to change the look and feel of your Nuxt app, whether you want to include a sidebar or have distinct layouts for mobile and desktop.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/layouts).
### `pages`
This directory contains your application views and routes. Nuxt will read all the `*.vue` files inside this directory and setup Vue Router automatically.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/get-started/routing).
### `plugins`
The plugins directory contains JavaScript plugins that you want to run before instantiating the root Vue.js Application. This is the place to add Vue plugins and to inject functions or constants. Every time you need to use `Vue.use()`, you should create a file in `plugins/` and add its path to plugins in `nuxt.config.js`.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/plugins).
### `static`
This directory contains your static files. Each file inside this directory is mapped to `/`.
Example: `/static/robots.txt` is mapped as `/robots.txt`.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/static).
### `store`
This directory contains your Vuex store files. Creating a file in this directory automatically activates Vuex.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/store).

@ -0,0 +1,59 @@
import service from '~/utils/request'
export const register = (data) => {
return service({
url: '/api/v1/user/register',
method: 'post',
data,
})
}
export const login = (data) => {
return service({
url: '/api/v1/user/login',
method: 'post',
data,
})
}
export const getUser = (params) => {
return service({
url: '/api/v1/user',
method: 'get',
params,
})
}
export const updateUserPassword = (data) => {
return service({
url: '/api/v1/user/password',
method: 'put',
data,
})
}
export const deleteUser = (params) => {
return service({
url: '/api/v1/user',
method: 'delete',
params,
})
}
export const listUser = (params) => {
return service({
url: '/api/v1/user/list',
method: 'get',
params,
})
}
export const getUserCaptcha = (params) => {
return service({
url: '/api/v1/user/captcha',
method: 'get',
params,
})
}

@ -0,0 +1,64 @@
$bg: #ecf5ff;
body{
margin: 0;padding: 0;
background-color: #fff;
}
.btn-block{
width: 100%;
}
.layout-admin{
height: 100vh;
.el-aside{
height: 100vh;
border-right: 1px solid #e6e6e6;
.el-menu{
border-right: 0;
}
}
.create-project{
padding: 15px;
text-align: center;
border-bottom: 1px solid $bg;
&:hover{
background-color: $bg;
}
}
.el-header{
border-bottom: 1px solid #e6e6e6;
line-height: 60px;
.fold{
padding: 0 15px 0 0;
font-size: 20px;
color: #999;
cursor: pointer;
&:hover{
color: #555
}
}
}
.el-footer{
border-top: 1px solid #e6e6e6;
text-align: center;
line-height: 60px;
height: 60px;
overflow: hidden;
color: #999;
a{
color: #409eff;
}
}
}
.page-login{
.el-card{
width: 400px;
max-width: 100%;
margin: 20vh auto;
}
}
.page-admin-projects{
}

@ -0,0 +1,54 @@
<template>
<div class="com-form-login">
<el-form label-position="top" label-width="80px" :model="user">
<el-form-item label="用户名">
<el-input
v-model="user.username"
placeholder="请输入您的登录用户名"
></el-input>
</el-form-item>
<el-form-item label="密码">
<el-input
v-model="user.password"
placeholder="请输入您的登录密码"
type="password"
@keydown.native.enter="execLogin"
></el-input>
</el-form-item>
<el-form-item>
<el-button
type="primary"
class="btn-block"
icon="el-icon-check"
@click="execLogin"
>立即登录</el-button
>
</el-form-item>
</el-form>
</div>
</template>
<script>
import { mapActions } from 'vuex'
import {getUserCaptcha} from '~/api/user'
export default {
name: 'FormLogin',
data() {
return {
user: {
username: '',
password: '',
},
}
},
async created(){
const res = await getUserCaptcha({type:'login'})
console.log(res)
},
methods: {
...mapActions('user', ['Login']),
async execLogin() {
await this.Login(this.user)
},
},
}
</script>

@ -0,0 +1,100 @@
<template>
<div class="com-form-password">
<el-form
ref="formPassword"
label-position="top"
label-width="80px"
:model="profile"
>
<el-form-item label="用户名">
<el-input
v-model="profile.username"
placeholder="请输入您的登录用户名"
:disabled="true"
></el-input>
</el-form-item>
<el-form-item
label="原密码"
prop="old_password"
:rules="[
{ required: true, trigger: 'blur', message: '请输入您的原密码' },
]"
>
<el-input v-model="profile.old_password" type="password"></el-input>
</el-form-item>
<el-form-item
label="新密码"
prop="new_password"
:rules="[
{ required: true, trigger: 'blur', message: '请输入您的新密码' },
]"
>
<el-input v-model="profile.new_password" type="password"></el-input>
</el-form-item>
<el-form-item
label="确认密码"
prop="repeat_password"
:rules="[
{ required: true, trigger: 'blur', message: '请再次输入您的新密码' },
]"
>
<el-input v-model="profile.repeat_password" type="password"></el-input>
</el-form-item>
<el-form-item>
<el-button
type="primary"
class="btn-block"
icon="el-icon-check"
@click="setPassword"
>修改密码</el-button
>
</el-form-item>
</el-form>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import { setUserPassword } from '~/api/user'
export default {
name: 'FormProfile',
data() {
return {
profile: {
username: '',
old_password: '',
new_password: '',
repeat_password: '',
},
}
},
computed: {
...mapGetters('user', ['user']),
},
created() {
this.profile = {
...this.profile,
username: this.user.username,
}
},
methods: {
setPassword() {
this.$refs.formPassword.validate(async (valid) => {
if (valid) {
if (this.profile.new_password !== this.profile.repeat_password) {
this.$message.error('新密码和确认密码不一致')
return
}
const res = await setUserPassword(this.profile)
if (res.status === 200) {
this.$message.success('密码修改成功')
this.$refs.formPassword.resetFields()
this.$emit('success', res)
} else {
this.$message.error(res.data.message)
}
}
})
},
},
}
</script>

@ -0,0 +1,65 @@
<template>
<div class="com-form-profile">
<el-form label-position="top" label-width="80px" :model="profile">
<el-form-item label="用户名">
<el-input
v-model="profile.username"
placeholder="请输入您的登录用户名"
:disabled="true"
></el-input>
</el-form-item>
<el-form-item
label="真实姓名"
prop="realname"
:rules="[
{ required: true, trigger: 'blur', message: '请输入您的真实姓名' },
]"
>
<el-input v-model="profile.realname"></el-input>
</el-form-item>
<el-form-item label="电子邮箱">
<el-input v-model="profile.email"></el-input>
</el-form-item>
<el-form-item label="联系电话">
<el-input v-model="profile.mobile"></el-input>
</el-form-item>
<el-form-item>
<el-button
type="primary"
class="btn-block"
icon="el-icon-check"
@click="setProfile"
>修改资料</el-button
>
</el-form-item>
</el-form>
</div>
</template>
<script>
import { mapActions, mapGetters } from 'vuex'
export default {
name: 'FormProfile',
data() {
return {
profile: {},
}
},
computed: {
...mapGetters('user', ['user']),
},
created() {
this.profile = {
...this.user,
}
},
methods: {
...mapActions('user', ['setUserProfile']),
async setProfile() {
const res = await this.setUserProfile(this.profile)
if (res.status === 200) {
this.$emit('success', res)
}
},
},
}
</script>

@ -0,0 +1,12 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"~/*": ["./*"],
"@/*": ["./*"],
"~~/*": ["./*"],
"@@/*": ["./*"]
}
},
"exclude": ["node_modules", ".nuxt", "dist"]
}

@ -0,0 +1,235 @@
<template>
<el-container class="layout-admin">
<el-aside :class="isCollapse ? 'layout-aside-collapsed' : ''">
<div class="create-project">
<el-tooltip
v-if="isCollapse"
class="item"
effect="dark"
content="上传文档"
placement="right"
>
<el-button
type="success"
icon="el-icon-plus"
class="btn-block"
@click="showCreateProjectDialog"
></el-button>
</el-tooltip>
<el-button
v-else
type="success"
icon="el-icon-plus"
@click="showCreateProjectDialog"
>上传文档</el-button
>
</div>
<transition
:duration="{ enter: 800, leave: 800 }"
mode="out-in"
name="el-fade-in-linear"
>
<el-menu
:router="true"
:default-openeds="[
'/admin/settings',
'/admin/me',
'/admin/templates',
]"
:collapse="isCollapse"
class="layout-admin-menu"
>
<el-menu-item index="/admin/dashboard">
<i class="el-icon-s-platform"></i>
<span slot="title">面板</span></el-menu-item
>
<el-submenu index="/admin/me">
<template slot="title"
><i class="el-icon-user-solid"></i>
<span slot="title">我的</span></template
>
<el-menu-item index="/admin/projects">
<i class="el-icon-data-analysis"></i>
我的项目</el-menu-item
>
<!-- <el-menu-item index="/admin/shares">我的分享</el-menu-item> -->
<el-menu-item index="/admin/mytemplates"
><i class="el-icon-tickets"></i> 我的模板</el-menu-item
>
<el-menu-item index="/admin/mycharts">
<i class="el-icon-s-grid"></i> 我的模块</el-menu-item
>
</el-submenu>
<el-submenu index="/admin/templates">
<template slot="title"
><i class="el-icon-s-shop"></i>
<span slot="title">应用中心</span></template
>
<el-menu-item index="/admin/templates"
><i class="el-icon-tickets"></i> 模板市场</el-menu-item
>
<el-menu-item index="/admin/charts"
><i class="el-icon-s-grid"></i> 图表模块</el-menu-item
>
</el-submenu>
<el-submenu index="/admin/settings">
<template slot="title"
><i class="el-icon-s-tools"></i>
<span slot="title">系统管理</span></template
>
<el-menu-item index="/admin/settings"
><i class="el-icon-setting"></i> 系统设置</el-menu-item
>
<el-menu-item index="/admin/users"
><i class="el-icon-user"></i> 用户管理</el-menu-item
>
</el-submenu>
</el-menu>
</transition>
</el-aside>
<el-container>
<el-header>
<el-button
v-if="isCollapse"
class="fold"
icon="el-icon-s-unfold"
type="text"
@click="isCollapse = false"
></el-button>
<el-button
v-else
class="fold"
icon="el-icon-s-fold"
type="text"
@click="isCollapse = true"
></el-button>
<span>我的项目</span>
<el-dropdown style="float: right" trigger="click" @command="command">
<el-button>
<i class="el-icon-user" style="margin-right: 15px"> </i>
<span>{{ user.realname }}</span>
</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="profile"> 个人资料 </el-dropdown-item>
<el-dropdown-item command="password"> 修改密码 </el-dropdown-item>
<el-dropdown-item command="logout">退出登录</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</el-header>
<el-main>
<nuxt />
</el-main>
<el-footer>
<span>© 2019-2022</span>
<span>Powered by</span>
<a href="https://mnt.ltd" target="_blank" title="MOREDOC">MOREDOC · 魔刀文库</a>
</el-footer>
</el-container>
<el-dialog title="个人资料" :visible.sync="formProfileVisible" width="30%">
<FormProfile @success="profileSuccess" />
</el-dialog>
<el-dialog title="个人资料" :visible.sync="formPasswordVisible" width="30%">
<FormPassword @success="passwordSuccess" />
</el-dialog>
<el-dialog
title="创建项目"
:visible.sync="createProjectVisible"
width="30%"
>
<form-set-project @success="createProjectSuccess"></form-set-project>
</el-dialog>
</el-container>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
import FormProfile from '~/components/FormProfile.vue'
import FormPassword from '~/components/FormPassword.vue'
export default {
components: {
FormProfile,
FormPassword,
},
middleware: ['auth'],
data() {
return {
createProjectVisible: false,
formProfileVisible: false,
formPasswordVisible: false,
isCollapse: false,
}
},
head() {
return {
title: 'MOREDOC · 魔刀文库',
}
},
computed: {
...mapGetters('user', ['user', 'token']),
},
watch: {
// async $route(to, from) {
// this.activePath = to.path
// this.$refs.mobileMenu.close('mobileMenu')
// },
},
mounted() {
const screenWidth = document.body.clientWidth
if (screenWidth < 1000) {
this.isCollapse = !this.isCollapse
}
},
methods: {
...mapActions('user', ['Logout']),
async createProject() {},
showCreateProjectDialog() {
this.createProjectVisible = true
},
createProjectSuccess(e) {
this.createProjectVisible = false
this.$router.push({
path: '/admin/projects',
query: { _t: new Date().getTime() },
})
},
profileSuccess() {
this.formProfileVisible = false
},
passwordSuccess() {
this.formPasswordVisible = false
},
command(cmd) {
switch (cmd) {
case 'profile':
this.formProfileVisible = true
break
case 'password':
this.formPasswordVisible = true
break
case 'logout':
this.Logout()
this.$router.replace({ path: '/admin/login' })
this.$message.success('退出成功')
break
}
},
},
}
</script>
<style lang="scss">
.layout-aside-collapsed {
width: 60px !important;
overflow: hidden;
.create-project {
padding: 0;
.el-button {
height: 60px;
line-height: 30px;
padding: 0 5px;
border-radius: 0;
span {
display: block;
}
}
}
}
</style>

@ -0,0 +1,88 @@
<template>
<div class="__nuxt-error-page">
<div class="error">
<svg
xmlns="http://www.w3.org/2000/svg"
width="90"
height="90"
fill="#DBE1EC"
viewBox="0 0 48 48"
>
<path
d="M22 30h4v4h-4zm0-16h4v12h-4zm1.99-10C12.94 4 4 12.95 4 24s8.94 20 19.99 20S44 35.05 44 24 35.04 4 23.99 4zM24 40c-8.84 0-16-7.16-16-16S15.16 8 24 8s16 7.16 16 16-7.16 16-16 16z"
/>
</svg>
<div class="title">
<h1>{{ error.message }}</h1>
<nuxt-link to="/">
<el-button type="primary"
><i class="el-icon-s-home"></i> 返回首页</el-button
>
</nuxt-link>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'ErrorLayout',
props: ['error'],
head() {
return {
title: this.message,
meta: [
{
name: 'viewport',
content: 'width=device-width,initial-scale=1.0,minimum-scale=1.0',
},
],
}
},
computed: {},
}
</script>
<style>
.__nuxt-error-page {
padding: 1rem;
background: #f7f8fb;
color: #47494e;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
font-family: sans-serif;
font-weight: 100 !important;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
-webkit-font-smoothing: antialiased;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.__nuxt-error-page .error {
max-width: 450px;
}
.__nuxt-error-page .title {
font-size: 1.5rem;
margin-top: 15px;
color: #47494e;
margin-bottom: 8px;
}
.__nuxt-error-page .description {
color: #7f828b;
line-height: 21px;
margin-bottom: 10px;
}
.__nuxt-error-page a {
color: #7f828b !important;
text-decoration: none;
}
.__nuxt-error-page .logo {
position: fixed;
left: 12px;
bottom: 12px;
}
</style>

@ -0,0 +1,17 @@
export default function ({ store, route, redirect }) {
const token = store.getters['user/token']
// 如果已登录,则不允许在访问登录页
if (route.name === 'admin-login' && token) {
redirect('/admin')
}
// 如果未登录,则不允许访问 /admin 前缀的页面
if (
!token &&
route.name !== 'admin-login' &&
route.path.indexOf('/admin') === 0
) {
redirect('/admin/login')
}
}

@ -0,0 +1,72 @@
export default {
// Disable server-side rendering: https://go.nuxtjs.dev/ssr-mode
ssr: false,
// Global page headers: https://go.nuxtjs.dev/config-head
head: {
title: 'web',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: '' },
{ name: 'format-detection', content: 'telephone=no' },
],
link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }],
},
generate: {
dir: '../dist',
},
// Global CSS: https://go.nuxtjs.dev/config-css
css: ['element-ui/lib/theme-chalk/index.css', '~assets/css/app.scss'],
// Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
plugins: ['@/plugins/element-ui'],
// Auto import components: https://go.nuxtjs.dev/config-components
components: true,
// Modules for dev and build (recommended): https://go.nuxtjs.dev/config-modules
buildModules: [
// https://go.nuxtjs.dev/eslint
// '@nuxtjs/eslint-module',
],
// Modules: https://go.nuxtjs.dev/config-modules
modules: [
// https://go.nuxtjs.dev/axios
'@nuxtjs/axios',
// https://go.nuxtjs.dev/pwa
'@nuxtjs/pwa',
],
// Axios module configuration: https://go.nuxtjs.dev/config-axios
axios: {
// Workaround to avoid enforcing hard-coded localhost:3000: https://github.com/nuxt-community/axios-module/issues/308
baseURL: '/',
proxy: true,
},
proxy: {
'/api': {
target: 'http://127.0.0.1:8880', // 目标服务器
changeOrigin: true,
},
'/uploads': {
target: 'http://127.0.0.1:8080', // 目标服务器
changeOrigin: true,
},
},
// PWA module configuration: https://go.nuxtjs.dev/pwa
pwa: {
manifest: {
lang: 'en',
},
},
// Build Configuration: https://go.nuxtjs.dev/config-build
build: {
postcss: null,
transpile: [/^element-ui/],
},
}

14409
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -0,0 +1,44 @@
{
"name": "web",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "nuxt",
"build": "nuxt build",
"start": "nuxt start",
"generate": "nuxt generate",
"lint:js": "eslint --ext \".js,.vue\" --ignore-path .gitignore .",
"lint:prettier": "prettier --check .",
"lint": "npm run lint:js && npm run lint:prettier",
"lintfix": "prettier --write --list-different . && npm run lint:js -- --fix"
},
"dependencies": {
"@nuxtjs/axios": "^5.13.6",
"@nuxtjs/pwa": "^3.3.5",
"core-js": "^3.19.3",
"element-ui": "^2.15.6",
"hotkeys-js": "^3.10.0",
"nuxt": "^2.15.8",
"vue": "^2.6.14",
"vue-marquee-text-component": "^1.2.0",
"vue-server-renderer": "^2.6.14",
"vue-template-compiler": "^2.6.14",
"vuex-persist": "^3.1.3",
"webpack": "^4.46.0"
},
"devDependencies": {
"@babel/eslint-parser": "^7.16.3",
"@nuxtjs/eslint-config": "^8.0.0",
"@nuxtjs/eslint-module": "^3.0.2",
"eslint": "^8.4.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-nuxt": "^3.1.0",
"eslint-plugin-vue": "^8.2.0",
"prettier": "^2.5.1",
"sass": "^1.32.11",
"sass-loader": "^10.1.1",
"stylelint": "^13.12.0",
"stylelint-config-prettier": "^8.0.2",
"stylelint-config-standard": "^21.0.0"
}
}

@ -0,0 +1,52 @@
<template>
<div class="page page-notfound">
<div class="notfound">
<img src="/static/images/404.png" alt="404" />
<h3 class="mgt-15px">Page Not Found</h3>
<nuxt-link to="/">
<el-button type="primary"
><i class="el-icon-s-home"></i> 返回首页</el-button
>
</nuxt-link>
</div>
</div>
</template>
<script>
export default {
data() {
return {}
},
head() {
return {
title: '404 - Not Found - MOREDOC · 魔刀文库',
meta: [
{
hid: 'keywords',
name: 'keywords',
content: '404,Not Found',
},
{
hid: 'description',
name: 'description',
content: '内容不存在',
},
],
}
},
watch: {},
async created() {},
mounted() {},
methods: {},
}
</script>
<style>
.page-notfound {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
text-align: center;
padding-top: 200px;
}
</style>

@ -0,0 +1,16 @@
<template>
<div>
<h1>Welcome to MOREDOC!</h1>
</div>
</template>
<script>
export default {
layout: 'admin',
head() {
return {
title: `面板 - MOREDOC · 魔刀文库`,
}
},
}
</script>

@ -0,0 +1,13 @@
<template>
<div></div>
</template>
<script>
export default {
name: 'AdminIndex',
layout: 'admin',
created() {
this.$router.push('/admin/dashboard')
},
}
</script>

@ -0,0 +1,23 @@
<template>
<div class="page-login">
<el-card shadow="never">
<div slot="header" class="clearfix">
<span>用户登录</span>
</div>
<form-login></form-login>
</el-card>
</div>
</template>
<script>
export default {
//
name: 'IndexPage',
middleware: ['auth'],
head() {
return {
title: `用户登录 - MOREDOC · 魔刀文库`,
}
},
}
</script>

@ -0,0 +1,15 @@
<template>
<div>系统设置配置系统相关信息</div>
</template>
<script>
export default {
name: 'AdminIndex',
layout: 'admin',
head() {
return {
title: `系统设置 - MOREDOC · 魔刀文库`,
}
},
}
</script>

@ -0,0 +1,14 @@
<template>
<div>模板市场可以选购模板</div>
</template>
<script>
export default {
layout: 'admin',
head() {
return {
title: `模板市场 - MOREDOC · 魔刀文库`,
}
},
}
</script>

@ -0,0 +1,15 @@
<template>
<div>用户管理启用禁用和创建用户并给予用户创建图表的数量配额等</div>
</template>
<script>
export default {
name: 'AdminIndex',
layout: 'admin',
head() {
return {
title: `用户管理 - MOREDOC · 魔刀文库`,
}
},
}
</script>

@ -0,0 +1,24 @@
<template>
<div class="welcome" style="padding: 100px 0; text-align: center">
<h1>Welcome to MoreDoc</h1>
<div style="font-size: 1.3em">
MoreDoc · 魔刀文库 深圳市摩枫网络科技(<a href="https://mnt.ltd" target="_blank">https://mnt.ltd</a>) golangTXTPDFEPUBoffice线dochub
</div>
</div>
</template>
<script>
export default {
name: 'IndexPage',
data() {
return {}
},
head() {
return {
title: 'MOREDOC · 魔刀文库,数据可视化解决方案,让数据开口说话',
}
},
async created() {},
methods: {},
}
</script>

@ -0,0 +1,17 @@
import Vue from 'vue'
import Element from 'element-ui'
import hotkeys from 'hotkeys-js'
// import locale from 'element-ui/lib/locale/lang/en'
// Vue.use(Element, { locale })
import zhLocale from 'element-ui/lib/locale/lang/zh-CN'
Vue.use(Element, { zhLocale })
// 以便光标在输入框时快捷键同样有效
hotkeys.filter = (e) => {
return true
}
// Vue2 引入快捷键
Vue.prototype.$hotkeys = hotkeys

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

@ -0,0 +1,10 @@
# STORE
**This directory is not required, you can delete it if you don't want to use it.**
This directory contains your Vuex Store files.
Vuex Store option is implemented in the Nuxt.js framework.
Creating a file in this directory automatically activates the option in the framework.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/vuex-store).

@ -0,0 +1,21 @@
import Vue from 'vue'
import Vuex from 'vuex'
import VuexPersistence from 'vuex-persist'
import { user } from '~/store/module/user'
Vue.use(Vuex)
const vuexLocal = new VuexPersistence({
storage: window.localStorage,
modules: ['user'],
})
const store = () =>
new Vuex.Store({
modules: {
user,
},
plugins: [vuexLocal.plugin],
})
export default store

@ -0,0 +1,84 @@
import { Message } from 'element-ui'
import { login, getUser, setUserProfile } from '~/api/user'
export const user = {
namespaced: true,
state: {
user: {
username: '',
realname: '',
email: '',
mobile: '',
avatar: '',
status: false,
limit: 0,
},
token: '',
},
mutations: {
setUser(state, user) {
state.user = user
},
setToken(state, token) {
state.token = token
},
logout(state) {
state.user = {}
state.token = ''
localStorage.clear()
},
},
actions: {
// 获取用户信息
async GetUser({ commit }) {
const res = await getUser()
if (res.status === 200) {
commit('setUser', res.data.data.user)
}
return res
},
async setUserProfile({ commit }, profile) {
const res = await setUserProfile(profile)
if (res.status === 200) {
commit('setUser', res.data.data)
Message({
type: 'success',
message: '修改成功',
})
} else {
Message({
type: 'error',
message: res.data.message || '修改失败',
})
}
return res
},
async Login({ commit }, loginInfo) {
const res = await login(loginInfo)
if (res.status === 200) {
commit('setUser', res.data.data.user)
commit('setToken', res.data.data.token)
Message({
type: 'success',
message: '登录成功',
})
location.reload()
} else {
Message({
type: 'error',
message: res.data.message || '登录失败',
})
}
},
Logout({ commit }) {
commit('logout')
},
},
getters: {
user(state) {
return state.user
},
token(state) {
return state.token
},
},
}

@ -0,0 +1,42 @@
import axios from 'axios' // 引入axios
import store from '~/store/index'
const service = axios.create({
timeout: 30000,
headers: {
'Content-Type': 'application/json',
},
})
// http request 拦截器
service.interceptors.request.use(
(config) => {
const token = store().getters['user/token'] || ''
if (token) config.headers.authorization = `Bearer ${token}`
return config
},
(error) => {
return Promise.reject(error)
}
)
// http response 拦截器
service.interceptors.response.use(
(response) => {
return response
},
(error) => {
if (error.response.status === 401) {
store().commit('user/logout')
}
// let message = error.response.data.message || error.response.statusText
// Message({
// showClose: true,
// message: message,
// type: 'error',
// })
return error.response
}
)
export default service

@ -0,0 +1,102 @@
// 对Date的扩展将 Date 转化为指定格式的String
// 月(M)、日(d)、小时(h)、分(m)、秒(s)、季度(q) 可以用 1-2 个占位符,
// 年(y)可以用 1-4 个占位符,毫秒(S)只能用 1 个占位符(是 1-3 位的数字)
// (new Date()).Format("yyyy-MM-dd hh:mm:ss.S") ==> 2006-07-02 08:09:04.423
// (new Date()).Format("yyyy-M-d h:m:s.S") ==> 2006-7-2 8:9:4.18
// eslint-disable-next-line no-extend-native
Date.prototype.Format = function (fmt) {
const o = {
'M+': this.getMonth() + 1, // 月份
'd+': this.getDate(), // 日
'h+': this.getHours(), // 小时
'm+': this.getMinutes(), // 分
's+': this.getSeconds(), // 秒
'q+': Math.floor((this.getMonth() + 3) / 3), // 季度
S: this.getMilliseconds(), // 毫秒
}
if (/(y+)/.test(fmt)) {
fmt = fmt.replace(
RegExp.$1,
(this.getFullYear() + '').substr(4 - RegExp.$1.length)
)
}
for (const k in o) {
if (new RegExp('(' + k + ')').test(fmt)) {
fmt = fmt.replace(
RegExp.$1,
RegExp.$1.length === 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length)
)
}
}
return fmt
}
export function formatTimeToStr(times, pattern) {
let d = new Date(times).Format('yyyy-MM-dd hh:mm:ss')
if (pattern) {
d = new Date(times).Format(pattern)
}
return d.toLocaleString()
}
export function formatDatetime(time) {
if (typeof time === 'string' && time !== '') {
const date = new Date(time)
return formatTimeToStr(date, 'yyyy-MM-dd hh:mm:ss')
}
return '-'
}
export function formatDate(time) {
if (typeof time === 'string' && time !== '') {
const date = new Date(time)
return formatTimeToStr(date, 'yyyy-MM-dd')
}
return '-'
}
export function formatOnlyMonthDate(time) {
if (typeof time === 'string' && time !== '') {
const date = new Date(time)
return formatTimeToStr(date, 'MM-dd')
}
return '-'
}
export function formatYear(time) {
if (typeof time === 'string' && time !== '') {
const date = new Date(time)
return formatTimeToStr(date, 'yyyy')
}
return '-'
}
export function formatRelativeTime(time) {
if (!(typeof time === 'string' && time !== '')) {
return '刚刚'
}
const timestamp = parseInt(new Date(time).getTime() / 1000)
const now = parseInt(new Date().getTime() / 1000)
const diff = now - timestamp
const minute = 60
const hour = minute * 60
const day = hour * 24
const month = day * 30
const monthC = diff / month
const dayC = diff / day
const hourC = diff / hour
const minC = diff / minute
if (monthC > 12) {
return parseInt(monthC / 12) + ' 年前'
} else if (monthC >= 1) {
return parseInt(monthC) + ' 月前'
} else if (dayC >= 1) {
return parseInt(dayC) + ' 天前'
} else if (hourC >= 1) {
return parseInt(hourC) + ' 小时前'
} else if (minC >= 1) {
return parseInt(minC) + ' 分钟前'
}
return '刚刚'
}
Loading…
Cancel
Save