代码结构调整

dev
truthhun 2 years ago
parent c4c9090568
commit 115b582525

@ -5,6 +5,8 @@ port="8080"
driver="mysql"
dsn="root:root@tcp(localhost:3306)/moredoc?charset=utf8mb4&loc=Local&parseTime=true"
showSQL=true
prefix="mnt_"
maxOpen=10
maxIdle=10
maxIdle=10
[jwt]
secret="moredoc"
expireDays=365

@ -1,103 +0,0 @@
package biz
import (
"context"
"moredoc/model"
"strings"
"time"
"github.com/gin-gonic/gin"
grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth"
"go.uber.org/zap"
"google.golang.org/grpc"
)
/*
tokenctx便使
ctxctx
*/
type AuthService struct {
dbModel *model.DBModel
logger *zap.Logger
}
func NewAuthService(dbModel *model.DBModel, logger *zap.Logger) (service *UserAPIService) {
return &UserAPIService{dbModel: dbModel, logger: logger.Named("AuthService")}
}
type ContextKey string
func (ck ContextKey) String() string {
return string(ck)
}
const (
CtxKeyUserClaims ContextKey = "user"
)
// const (
// messageInvalidToken = "您的登录令牌已过期,请重新登录"
// )
type ServiceAuthFuncOverride interface {
AuthFuncOverride(ctx context.Context, fullMethodName string) (context.Context, error)
}
func (s *AuthService) AuthUnaryServerInterceptor() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
newCtx, err := s.AuthGRPC(ctx, info)
if err != nil {
return nil, err
}
return handler(newCtx, req)
}
}
// AuthGRPC 验证 gRPC 请求
// 1. 从权限表中查询API如果存在该API则表示该API需要权限才能访问如果不存在则跳过
// 2. 如果用户携带有token则根据token判断是否有效如果有效则获取用户信息放到ctx否则跳过
func (s *AuthService) AuthGRPC(ctx context.Context, info *grpc.UnaryServerInfo) (context.Context, error) {
token, err := grpc_auth.AuthFromMD(ctx, "bearer")
if err != nil {
return ctx, nil
}
claims, err := s.dbModel.CheckUserJWTToken(token)
// token存在但是不正确或者已过期这时需要返回错误前端清除存储的错误登录信息
if err != nil || claims == nil || claims.ExpiresAt < time.Now().Unix() || s.dbModel.IsInvalidToken(claims.UUID) {
// return ctx, status.Error(codes.Unauthenticated, messageInvalidToken)
return ctx, nil
}
newCtx := context.WithValue(ctx, CtxKeyUserClaims, claims)
return newCtx, nil
}
// AuthGin 验证 HTTP 请求
func (s *AuthService) AuthGin() gin.HandlerFunc {
return func(ctx *gin.Context) {
auth := ctx.Request.Header.Get("authorization")
bearer := strings.Split(auth, " ")
if auth == "" || len(bearer) != 2 {
ctx.Next()
return
}
token := bearer[1]
claims, err := s.dbModel.CheckUserJWTToken(token)
if err != nil || claims == nil || claims.ExpiresAt < time.Now().Unix() || s.dbModel.IsInvalidToken(claims.UUID) {
// ctx.JSON(http.StatusUnauthorized, status.Error(codes.Unauthenticated, messageInvalidToken))
// ctx.Abort()
ctx.Next()
return
}
ctx.Set(CtxKeyUserClaims.String(), claims)
ctx.Next()
}
}

@ -5,6 +5,7 @@ import (
"time"
pb "moredoc/api/v1"
"moredoc/middleware/auth"
"moredoc/model"
"moredoc/util"
"moredoc/util/validate"
@ -23,10 +24,11 @@ type UserAPIService struct {
pb.UnimplementedUserAPIServer
dbModel *model.DBModel
logger *zap.Logger
auth *auth.Auth
}
func NewUserAPIService(dbModel *model.DBModel, logger *zap.Logger) (service *UserAPIService) {
return &UserAPIService{dbModel: dbModel, logger: logger.Named("UserAPIService")}
func NewUserAPIService(dbModel *model.DBModel, logger *zap.Logger, auth *auth.Auth) (service *UserAPIService) {
return &UserAPIService{dbModel: dbModel, logger: logger.Named("UserAPIService"), auth: auth}
}
func (s *UserAPIService) getValidFieldMap() map[string]string {
@ -34,7 +36,6 @@ func (s *UserAPIService) getValidFieldMap() map[string]string {
}
// Register 用户注册
// TODO: 1. 如果系统启用了注册,判断是否需要管理员审核
func (s *UserAPIService) Register(ctx context.Context, req *pb.RegisterAndLoginRequest) (*emptypb.Empty, error) {
err := validate.ValidateStruct(req, s.getValidFieldMap())
if err != nil {
@ -51,7 +52,7 @@ func (s *UserAPIService) Register(ctx context.Context, req *pb.RegisterAndLoginR
return nil, status.Errorf(codes.InvalidArgument, "系统未开放注册")
}
if !cfg.IsClose {
if cfg.IsClose {
return nil, status.Errorf(codes.InvalidArgument, "网站已关闭,占时不允许注册")
}
@ -83,7 +84,6 @@ func (s *UserAPIService) Register(ctx context.Context, req *pb.RegisterAndLoginR
}
// Login 用户登录
// TODO: 1. 判断是否启用了验证码,如果启用了验证码,则需要进行验证码验证
func (s *UserAPIService) Login(ctx context.Context, req *pb.RegisterAndLoginRequest) (*pb.LoginReply, error) {
errValidate := validate.ValidateStruct(req, s.getValidFieldMap())
@ -105,7 +105,7 @@ func (s *UserAPIService) Login(ctx context.Context, req *pb.RegisterAndLoginRequ
return nil, status.Errorf(codes.InvalidArgument, "用户名或密码错误")
}
token, err := s.dbModel.CreateUserJWTToken(user.Id)
token, err := s.auth.CreateJWTToken(user.Id)
if err != nil {
return nil, status.Errorf(codes.Internal, err.Error())
}

@ -105,9 +105,7 @@ func initConfig() {
initLogger(cfg.Level)
if cfg.Database.Prefix == "" {
cfg.Database.Prefix = "nd_"
}
cfg.Database.Prefix = "mnt_"
logger.Info("config", zap.Any("config", cfg))
}

@ -5,6 +5,7 @@ type Config struct {
Level string //
Port int // listent port
Database Database
JWT JWT
}
type Database struct {
@ -14,3 +15,8 @@ type Database struct {
MaxOpen int
Prefix string // table prefix, default is nd_
}
type JWT struct {
Secret string
ExpireDays int64
}

@ -0,0 +1,123 @@
package auth
import (
"context"
"moredoc/conf"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/gofrs/uuid"
"github.com/golang-jwt/jwt"
grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth"
"google.golang.org/grpc"
)
type Auth struct {
jwt *conf.JWT
}
type UserClaims struct {
UserId int64
UUID string
jwt.StandardClaims
}
func NewAuth(jwt *conf.JWT) *Auth {
return &Auth{
jwt: jwt,
}
}
type ContextKey string
func (ck ContextKey) String() string {
return string(ck)
}
const (
CtxKeyUserClaims ContextKey = "user"
CtxKeyFullMethod ContextKey = "fullMethod"
)
func (p *Auth) AuthUnaryServerInterceptor() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
newCtx, err := p.AuthGRPC(ctx, info)
if err != nil {
return nil, err
}
return handler(newCtx, req)
}
}
func (p *Auth) AuthGRPC(ctx context.Context, info *grpc.UnaryServerInfo) (context.Context, error) {
ctx = context.WithValue(ctx, CtxKeyFullMethod, info.FullMethod)
token, err := grpc_auth.AuthFromMD(ctx, "bearer")
if err != nil {
return ctx, nil
}
claims, err := p.CheckJWTToken(token)
if err != nil || claims == nil || claims.ExpiresAt < time.Now().Unix() {
return ctx, nil
}
newCtx := context.WithValue(ctx, CtxKeyUserClaims, claims)
return newCtx, nil
}
func (p *Auth) AuthGin() gin.HandlerFunc {
return func(ctx *gin.Context) {
auth := ctx.Request.Header.Get("authorization")
bearer := strings.Split(auth, " ")
if auth == "" || len(bearer) != 2 {
ctx.Next()
return
}
token := bearer[1]
claims, err := p.CheckJWTToken(token)
if err != nil || claims == nil || claims.ExpiresAt < time.Now().Unix() {
ctx.Next()
return
}
ctx.Set(CtxKeyUserClaims.String(), claims)
ctx.Next()
}
}
// CreateUserJWTToken 生成用户JWT Token
func (p *Auth) CreateJWTToken(userId int64) (string, error) {
expireTime := time.Now().Add(time.Duration(p.jwt.ExpireDays) * 24 * time.Hour).Unix()
claims := UserClaims{
UserId: userId,
UUID: uuid.Must(uuid.NewV4()).String(),
StandardClaims: jwt.StandardClaims{
ExpiresAt: expireTime,
Issuer: "moredoc",
IssuedAt: time.Now().Unix(),
},
}
token, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte(p.jwt.Secret))
return token, err
}
// CheckUserJWTToken 验证用户JWT token
func (p *Auth) CheckJWTToken(token string) (*UserClaims, error) {
tokenClaims, err := jwt.ParseWithClaims(token, &UserClaims{}, func(token *jwt.Token) (interface{}, error) {
return []byte(p.jwt.Secret), nil
})
if err != nil {
return nil, err
}
if tokenClaims != nil {
if claims, ok := tokenClaims.Claims.(*UserClaims); ok && tokenClaims.Valid {
return claims, nil
}
}
return nil, err
}

@ -7,7 +7,6 @@ import (
"strings"
"time"
"github.com/gofrs/uuid"
jsoniter "github.com/json-iterator/go"
"go.uber.org/zap"
"gorm.io/gorm"
@ -224,49 +223,6 @@ 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加密密钥
}
// GetConfigJWT 获取JWT配置
func (m *DBModel) GetConfigOfJWT() (config ConfigJWT) {
var configs []Config
err := m.db.Where("category = ?", ConfigCategoryJWT).Find(&configs).Error
if err != nil && err != gorm.ErrRecordNotFound {
m.logger.Error("GetConfigJWT", zap.Error(err))
}
var data = make(map[string]interface{})
for _, cfg := range configs {
switch cfg.Name {
case "secret":
value := cfg.Value
if value == "" {
value = "moredoc"
}
data[cfg.Name] = value
case "duration":
value, _ := strconv.Atoi(cfg.Value)
if value <= 0 {
value = 3600 * 24 * 7
}
data[cfg.Name] = value
}
}
bytes, _ := json.Marshal(data)
json.Unmarshal(bytes, &config)
return
}
const (
ConfigCaptchaLength = "length"
ConfigCaptchaWidth = "width"
@ -427,10 +383,6 @@ func (m *DBModel) initConfig() (err error) {
{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: ""},

@ -31,7 +31,6 @@ func (GroupPermission) TableName() string {
}
// CreateGroupPermission 创建GroupPermission
// TODO: 创建成功之后,注意相关表统计字段数值的增减
func (m *DBModel) CreateGroupPermission(groupPermission *GroupPermission) (err error) {
err = m.db.Create(groupPermission).Error
if err != nil {
@ -182,7 +181,6 @@ func (m *DBModel) GetGroupPermissionList(opt OptionGetGroupPermissionList) (grou
}
// DeleteGroupPermission 删除数据
// TODO: 删除数据之后,存在 group_permission_id 的关联表,需要删除对应数据,同时相关表的统计数值,也要随着减少
func (m *DBModel) DeleteGroupPermission(ids []interface{}) (err error) {
err = m.db.Where("id in (?)", ids).Delete(&GroupPermission{}).Error
if err != nil {
@ -190,39 +188,3 @@ func (m *DBModel) DeleteGroupPermission(ids []interface{}) (err error) {
}
return
}
// CheckPermissionByUserId 根据用户ID检查用户是否有权限
func (m *DBModel) CheckPermissionByUserId(permissionIdentifier string, userId int64) (yes bool) {
var (
userGroups []UserGroup
groupId []int64
)
m.db.Where("user_id = ?", userId).Find(&userGroups)
for _, ug := range userGroups {
groupId = append(groupId, ug.GroupId)
}
return m.CheckPermissionByGroupId(permissionIdentifier, groupId)
}
// CheckPermissionByGroupId 根据用户所属用户组ID检查用户是否有权限
func (m *DBModel) CheckPermissionByGroupId(permissionIdentifier string, groupId []int64) (yes bool) {
if len(groupId) == 0 {
return
}
permission, _ := m.GetPermissionByIdentifier(permissionIdentifier, "id")
if permission.Id == 0 {
return
}
var groupPermission GroupPermission
err := m.db.Where("group_id in (?) and permission_id = ?", groupId, permission.Id).First(&groupPermission).Error
if err != nil {
m.logger.Error("CheckPermissionByGroupId", zap.Error(err))
}
// 如果有权限返回true
return groupPermission.Id > 0
}

@ -36,19 +36,21 @@ type DBModel struct {
tableFieldsMap map[string]map[string]struct{}
validToken sync.Map // map[tokenUUID]struct{} 有效的token uuid
invalidToken sync.Map // map[tokenUUID]struct{} 存在未过期但无效token比如读者退出登录后的token
jwt conf.JWT
}
func NewDBModel(cfg *conf.Database, lg *zap.Logger) (m *DBModel, err error) {
func NewDBModel(cfg *conf.Config, lg *zap.Logger) (m *DBModel, err error) {
if lg == nil {
err = errors.New("logger cant be nil")
return
}
tablePrefix = cfg.Prefix
tablePrefix = cfg.Database.Prefix
m = &DBModel{
logger: lg.Named("model"),
tablePrefix: cfg.Prefix,
tablePrefix: cfg.Database.Prefix,
jwt: cfg.JWT,
tableFields: make(map[string][]string),
tableFieldsMap: make(map[string]map[string]struct{}),
}
@ -59,21 +61,21 @@ func NewDBModel(cfg *conf.Database, lg *zap.Logger) (m *DBModel, err error) {
)
sqlLogLevel := logger.Info
if !cfg.ShowSQL {
if !cfg.Database.ShowSQL {
sqlLogLevel = logger.Silent
}
db, err = gorm.Open(mysql.New(mysql.Config{
DSN: cfg.DSN, // DSN data source name
DefaultStringSize: 255, // string 类型字段的默认长度
DisableDatetimePrecision: true, // 禁用 datetime 精度MySQL 5.6 之前的数据库不支持
DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
DontSupportRenameColumn: true, // 用 `change` 重命名列MySQL 8 之前的数据库和 MariaDB 不支持重命名列
SkipInitializeWithVersion: false, // 根据当前 MySQL 版本自动配置
DSN: cfg.Database.DSN, // DSN data source name
DefaultStringSize: 255, // string 类型字段的默认长度
DisableDatetimePrecision: true, // 禁用 datetime 精度MySQL 5.6 之前的数据库不支持
DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
DontSupportRenameColumn: true, // 用 `change` 重命名列MySQL 8 之前的数据库和 MariaDB 不支持重命名列
SkipInitializeWithVersion: false, // 根据当前 MySQL 版本自动配置
}), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
TablePrefix: cfg.Prefix, // 表名前缀,`User`表为`t_users`
SingularTable: true, // 使用单数表名,启用该选项后,`User` 表将是`user`
TablePrefix: cfg.Database.Prefix, // 表名前缀,`User`表为`t_users`
SingularTable: true, // 使用单数表名,启用该选项后,`User` 表将是`user`
},
Logger: logger.Default.LogMode(sqlLogLevel),
})
@ -88,12 +90,12 @@ func NewDBModel(cfg *conf.Database, lg *zap.Logger) (m *DBModel, err error) {
return
}
if cfg.MaxIdle > 0 {
sqlDB.SetMaxIdleConns(cfg.MaxIdle)
if cfg.Database.MaxIdle > 0 {
sqlDB.SetMaxIdleConns(cfg.Database.MaxIdle)
}
if cfg.MaxOpen > 0 {
sqlDB.SetMaxIdleConns(cfg.MaxOpen)
if cfg.Database.MaxOpen > 0 {
sqlDB.SetMaxIdleConns(cfg.Database.MaxOpen)
}
m.db = db
@ -143,6 +145,7 @@ func (m *DBModel) SyncDB() (err error) {
&UserGroup{},
&Permission{},
&GroupPermission{},
&Logout{},
}
if err = m.db.AutoMigrate(tableModels...); err != nil {
m.logger.Fatal("SyncDB", zap.Error(err))
@ -190,10 +193,10 @@ func (m *DBModel) showTableColumn(tableName string) (columns []TableColumn, err
// initialDatabase 初始化数据库相关数据
func (m *DBModel) initDatabase() (err error) {
if err = m.initPermission(); err != nil {
m.logger.Error("initialDatabase", zap.Error(err))
return
}
// if err = m.initPermission(); err != nil {
// m.logger.Error("initialDatabase", zap.Error(err))
// return
// }
// 初始化用户组及其权限
if err = m.initGroupAndPermission(); err != nil {

@ -11,10 +11,10 @@ import (
type Permission struct {
Id int64 `form:"id" json:"id,omitempty" gorm:"primaryKey;autoIncrement;column:id;comment:;"`
Category string `form:"category" json:"category,omitempty" gorm:"column:category;type:varchar(64);size:64;index:category;comment:权限类别组;"`
Title string `form:"title" json:"title,omitempty" gorm:"column:title;type:varchar(255);size:255;comment:权限中文名称;"`
Method string `form:"method" json:"method,omitempty" gorm:"column:method;type:varchar(16);size:16;index:method_path,unique;comment:请求方法grpc为空;"`
Path string `form:"path" json:"path,omitempty" gorm:"column:path;type:varchar(128);size:128;index:method_path,unique;comment:API路径;"`
Title string `form:"title" json:"title,omitempty" gorm:"column:title;type:varchar(255);size:255;comment:中文名称;"`
Description string `form:"description" json:"description,omitempty" gorm:"column:description;type:varchar(255);size:255;comment:权限描述;"`
Identifier string `form:"identifier" json:"identifier,omitempty" gorm:"column:identifier;type:varchar(64);size:64;index:identifier,unique;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:更新时间;"`
}
@ -34,161 +34,7 @@ func (Permission) TableName() string {
// = 0;
//}
// PermissionCategoryXXX 基本按照数据表来定义
const (
PermissionCategoryAttachment = "attachment" // 附件管理,包括上传等
PermissionCategoryBanner = "banner" // 管理横幅
PermissionCategoryCategory = "category" // 管理文档分类
PermissionCategoryConfig = "config" // 管理系统配置:开启验证码,是否允许上传等
PermissionCategoryDocument = "document" // 管理文档
PermissionCategoryFriendlink = "friendlink" // 管理友情链接
PermissionCategoryGroup = "group" // 管理用户组:创建、修改、删除、查看等
PermissionCategoryGroupPermission = "groupPermission" // 权限设置
PermissionCategoryUser = "user" // 管理用户:创建、修改、删除、查看、修改密码、禁用、变更分组等
)
const (
// PermissionCategoryAttachment = "attachment" // 附件管理,包括上传等
PermissionIdentifierAttachmentList = "attachmentList" // 查看附件列表
PermissionIdentifierAttachmentDelete = "attachmentDelete" // 删除单个附件
PermissionIdentifierAttachmentBatchDelete = "attachmentBatchDelete" // 批量删除附件
PermissionIdentifierAttachmentDisable = "attachmentDisable" // 禁止附件,禁止后无法访问,用于控制非法附件
// PermissionCategoryBanner = "banner" // 管理横幅
PermissionIdentifierBannerList = "bannerList" // 查看横幅列表
PermissionIdentifierBannerCreate = "bannerCreate" // 创建横幅
PermissionIdentifierBannerUpdate = "bannerUpdate" // 更新横幅
PermissionIdentifierBannerDelete = "bannerDelete" // 删除横幅
PermissionIdentifierBannerBatchDelete = "bannerBatchDelete" // 批量删除横幅
// PermissionCategoryCategory = "category" // 管理文档分类
PermissionIdentifierCategoryList = "categoryList" // 查看文档分类列表
PermissionIdentifierCategoryCreate = "categoryCreate" // 创建分类
PermissionIdentifierCategoryUpdate = "categoryUpdate" // 更新分类
PermissionIdentifierCategoryDelete = "categoryDelete" // 删除分类
PermissionIdentifierCategoryBatchDelete = "categoryBatchDelete" // 批量删除分类
// PermissionCategoryConfig = "config" // 管理系统配置:开启验证码,是否允许上传等。不允许删除配置
PermissionIdentifierConfigList = "configList" // 查看系统配置列表
PermissionIdentifierConfigUpdate = "configUpdate" // 更新系统配置
// PermissionCategoryDocument = "document" // 管理文档
PermissionIdentifierDocumentList = "documentList" // 查看文档列表
PermissionIdentifierDocumentCreate = "documentCreate" // 创建文档
PermissionIdentifierDocumentBatchCreate = "documentBatchCreate" // 批量创建文档
PermissionIdentifierDocumentUpdate = "documentUpdate" // 更新文档
PermissionIdentifierDocumentDelete = "documentDelete" // 删除文档
PermissionIdentifierDocumentBatchDelete = "documentBatchDelete" // 批量删除文档
// PermissionCategoryFriendlink = "friendlink" // 管理友情链接
PermissionIdentifierFriendlinkList = "friendlinkList" // 查看友情链接列表
PermissionIdentifierFriendlinkCreate = "friendlinkCreate" // 创建友情链接
PermissionIdentifierFriendlinkUpdate = "friendlinkUpdate" // 更新友情链接
PermissionIdentifierFriendlinkDelete = "friendlinkDelete" // 删除友情链接
PermissionIdentifierFriendlinkBatchDelete = "friendlinkBatchDelete" // 批量删除友情链接
// PermissionCategoryGroup = "group" // 管理用户组:创建、修改、删除、查看等
PermissionIdentifierGroupList = "groupList" // 查看用户组列表
PermissionIdentifierGroupCreate = "groupCreate" // 创建用户组
PermissionIdentifierGroupUpdate = "groupUpdate" // 更新用户组
PermissionIdentifierGroupDelete = "groupDelete" // 删除用户组
PermissionIdentifierGroupBatchDelete = "groupBatchDelete" // 批量删除用户组
// PermissionCategoryGroupPermission = "groupPermission" // 权限设置
PermissionIdentifierGroupPermissionList = "groupPermissionList" // 查看用户组权限列表
PermissionIdentifierGroupPermissionUpdate = "groupPermissionUpdate" // 更新用户组权限
// PermissionCategoryUser = "user" // 管理用户:创建、修改、删除、查看、修改密码、禁用、变更分组等
PermissionIdentifierUserCreate = "userCreate" // 创建用户
PermissionIdentifierUserUpdate = "userUpdate" // 修改用户
PermissionIdentifierUserDelete = "userDelete" // 删除用户
PermissionIdentifierUserBatchDelete = "userBatchDelete" // 删除用户
PermissionIdentifierUserList = "userList" // 查看用户
PermissionIdentifierUserChangePassword = "userChangePassword" // 修改用户密码
PermissionIdentifierUserDisable = "userDisable" // 禁用用户
PermissionIdentifierUserChangeGroup = "userChangeGroup" // 变更用户分组
)
func (m *DBModel) initPermission() (err error) {
permissions := []Permission{
{Id: 1, Category: PermissionCategoryAttachment, Identifier: PermissionIdentifierAttachmentList, Title: "附件管理"},
{Id: 2, Category: PermissionCategoryAttachment, Identifier: PermissionIdentifierAttachmentDelete, Title: "删除附件"},
{Id: 3, Category: PermissionCategoryAttachment, Identifier: PermissionIdentifierAttachmentBatchDelete, Title: "批量删除附件"},
{Id: 4, Category: PermissionCategoryAttachment, Identifier: PermissionIdentifierAttachmentDisable, Title: "禁用附件"},
{Id: 42, Category: PermissionCategoryAttachment, Identifier: PermissionIdentifierAttachmentDisable, Title: "禁用附件"},
{Id: 5, Category: PermissionCategoryBanner, Identifier: PermissionIdentifierBannerList, Title: "横幅管理"},
{Id: 6, Category: PermissionCategoryBanner, Identifier: PermissionIdentifierBannerCreate, Title: "创建横幅"},
{Id: 7, Category: PermissionCategoryBanner, Identifier: PermissionIdentifierBannerUpdate, Title: "更新横幅"},
{Id: 8, Category: PermissionCategoryBanner, Identifier: PermissionIdentifierBannerDelete, Title: "删除横幅"},
{Id: 9, Category: PermissionCategoryBanner, Identifier: PermissionIdentifierBannerBatchDelete, Title: "批量删除横幅"},
{Id: 10, Category: PermissionCategoryCategory, Identifier: PermissionIdentifierCategoryList, Title: "分类管理"},
{Id: 11, Category: PermissionCategoryCategory, Identifier: PermissionIdentifierCategoryCreate, Title: "创建分类"},
{Id: 12, Category: PermissionCategoryCategory, Identifier: PermissionIdentifierCategoryUpdate, Title: "更新分类"},
{Id: 13, Category: PermissionCategoryCategory, Identifier: PermissionIdentifierCategoryDelete, Title: "删除分类"},
{Id: 14, Category: PermissionCategoryCategory, Identifier: PermissionIdentifierCategoryBatchDelete, Title: "批量删除分类"},
{Id: 15, Category: PermissionCategoryConfig, Identifier: PermissionIdentifierConfigList, Title: "系统配置管理"},
{Id: 16, Category: PermissionCategoryConfig, Identifier: PermissionIdentifierConfigUpdate, Title: "更新系统配置"},
{Id: 17, Category: PermissionCategoryDocument, Identifier: PermissionIdentifierDocumentList, Title: "文档管理"},
{Id: 18, Category: PermissionCategoryDocument, Identifier: PermissionIdentifierDocumentCreate, Title: "创建文档"},
{Id: 19, Category: PermissionCategoryDocument, Identifier: PermissionIdentifierDocumentBatchCreate, Title: "批量创建文档"},
{Id: 20, Category: PermissionCategoryDocument, Identifier: PermissionIdentifierDocumentUpdate, Title: "更新文档"},
{Id: 21, Category: PermissionCategoryDocument, Identifier: PermissionIdentifierDocumentDelete, Title: "删除文档"},
{Id: 22, Category: PermissionCategoryDocument, Identifier: PermissionIdentifierDocumentBatchDelete, Title: "批量删除文档"},
{Id: 23, Category: PermissionCategoryGroup, Identifier: PermissionIdentifierGroupList, Title: "用户组管理"},
{Id: 24, Category: PermissionCategoryGroup, Identifier: PermissionIdentifierGroupCreate, Title: "创建用户组"},
{Id: 25, Category: PermissionCategoryGroup, Identifier: PermissionIdentifierGroupUpdate, Title: "更新用户组"},
{Id: 26, Category: PermissionCategoryGroup, Identifier: PermissionIdentifierGroupDelete, Title: "删除用户组"},
{Id: 27, Category: PermissionCategoryGroup, Identifier: PermissionIdentifierGroupBatchDelete, Title: "批量删除用户组"},
{Id: 28, Category: PermissionCategoryGroupPermission, Identifier: PermissionIdentifierGroupPermissionList, Title: "权限管理"},
{Id: 29, Category: PermissionCategoryGroupPermission, Identifier: PermissionIdentifierGroupPermissionUpdate, Title: "设置权限"},
{Id: 30, Category: PermissionCategoryFriendlink, Identifier: PermissionIdentifierFriendlinkList, Title: "友链管理"},
{Id: 31, Category: PermissionCategoryFriendlink, Identifier: PermissionIdentifierFriendlinkCreate, Title: "创建友链"},
{Id: 32, Category: PermissionCategoryFriendlink, Identifier: PermissionIdentifierFriendlinkUpdate, Title: "更新友链"},
{Id: 33, Category: PermissionCategoryFriendlink, Identifier: PermissionIdentifierFriendlinkDelete, Title: "删除友链"},
{Id: 34, Category: PermissionCategoryFriendlink, Identifier: PermissionIdentifierFriendlinkBatchDelete, Title: "批量删除友链"},
{Id: 35, Category: PermissionCategoryUser, Identifier: PermissionIdentifierUserCreate, Title: "创建用户"},
{Id: 36, Category: PermissionCategoryUser, Identifier: PermissionIdentifierUserUpdate, Title: "更新用户"},
{Id: 37, Category: PermissionCategoryUser, Identifier: PermissionIdentifierUserDelete, Title: "删除用户"},
{Id: 38, Category: PermissionCategoryUser, Identifier: PermissionIdentifierUserBatchDelete, Title: "批量删除用户"},
{Id: 39, Category: PermissionCategoryUser, Identifier: PermissionIdentifierUserList, Title: "用户管理"},
{Id: 40, Category: PermissionCategoryUser, Identifier: PermissionIdentifierUserChangePassword, Title: "修改用户密码"},
{Id: 41, Category: PermissionCategoryUser, Identifier: PermissionIdentifierUserChangeGroup, Title: "变更用户分组"},
}
sess := m.db.Begin()
defer func() {
if err != nil {
sess.Rollback()
} else {
sess.Commit()
}
}()
err = sess.Where("id > ?", 0).Delete(&Permission{}).Error
if err != nil {
m.logger.Error("delete permission error", zap.Error(err))
return
}
err = sess.Create(&permissions).Error
if err != nil {
m.logger.Error("create permission error", zap.Error(err))
return
}
return
}
// CreatePermission 创建Permission
// TODO: 创建成功之后,注意相关表统计字段数值的增减
func (m *DBModel) CreatePermission(permission *Permission) (err error) {
err = m.db.Create(permission).Error
if err != nil {
@ -227,8 +73,8 @@ func (m *DBModel) GetPermission(id interface{}, fields ...string) (permission Pe
return
}
// GetPermissionByIdentifier(identifier string, fields ...string) 根据唯一索引获取Permission
func (m *DBModel) GetPermissionByIdentifier(identifier string, fields ...string) (permission Permission, err error) {
// GetPermissionByMethodPath
func (m *DBModel) GetPermissionByMethodPath(method, path string, createIfNotExist bool, fields ...string) (permission Permission, err error) {
db := m.db
fields = m.FilterValidFields(Permission{}.TableName(), fields...)
@ -236,16 +82,89 @@ func (m *DBModel) GetPermissionByIdentifier(identifier string, fields ...string)
db = db.Select(fields)
}
db = db.Where("identifier = ?", identifier)
db = db.Where("path = ?", path)
if method != "" {
db = db.Where("method = ?", method)
} else {
db = db.Where("(method IS NULL or method = '')")
}
err = db.First(&permission).Error
if err != nil && err != gorm.ErrRecordNotFound {
m.logger.Error("GetPermissionByIdentifier", zap.Error(err))
return
}
if permission.Id > 0 {
return
}
if createIfNotExist {
permission.Method = method
permission.Path = path
err = m.CreatePermission(&permission)
if err != nil {
m.logger.Error("GetPermissionByIdentifier", zap.Error(err))
return
}
}
return
}
// DeletePermission 删除数据
func (m *DBModel) DeletePermission(ids []interface{}) (err error) {
err = m.db.Where("id in (?)", ids).Delete(&Permission{}).Error
if err != nil {
m.logger.Error("DeletePermission", zap.Error(err))
}
return
}
// CheckPermissionByUserId 根据用户ID检查用户是否有权限
func (m *DBModel) CheckPermissionByUserId(userId int64, method, path string) (yes bool) {
var (
userGroups []UserGroup
groupId []int64
)
// NOTE: ID为1的用户拥有所有权限可以理解为类似linux的root用户
if userId == 1 {
return true
}
if userId > 0 {
m.db.Where("user_id = ?", userId).Find(&userGroups)
for _, ug := range userGroups {
groupId = append(groupId, ug.GroupId)
}
}
return m.CheckPermissionByGroupId(groupId, method, path)
}
// CheckPermissionByGroupId 根据用户所属用户组ID检查用户是否有权限
func (m *DBModel) CheckPermissionByGroupId(groupId []int64, method, path string) (yes bool) {
fields := []string{"id", "type", "method", "path"}
permission, err := m.GetPermissionByMethodPath(method, path, true, fields...)
if err != nil {
m.logger.Error("CheckPermissionByGroupId", zap.Error(err))
}
if permission.Id == 0 { // 权限控制表里面不存在的记录,默认允许访问
return true
}
// 校验当前登录了的用户所属用户组,是否有权限
var groupPermission GroupPermission
err = m.db.Where("group_id in (?) and permission_id = ?", groupId, permission.Id).First(&groupPermission).Error
if err != nil {
m.logger.Error("CheckPermissionByGroupId", zap.Error(err))
}
// 如果有权限返回true
return groupPermission.Id > 0
}
type OptionGetPermissionList struct {
Page int
Size int
@ -335,13 +254,3 @@ func (m *DBModel) GetPermissionList(opt OptionGetPermissionList) (permissionList
}
return
}
// DeletePermission 删除数据
// TODO: 删除数据之后,存在 permission_id 的关联表,需要删除对应数据,同时相关表的统计数值,也要随着减少
func (m *DBModel) DeletePermission(ids []interface{}) (err error) {
err = m.db.Where("id in (?)", ids).Delete(&Permission{}).Error
if err != nil {
m.logger.Error("DeletePermission", zap.Error(err))
}
return
}

@ -6,8 +6,6 @@ import (
"time"
"github.com/alexandrevicenzi/unchained"
"github.com/gofrs/uuid"
"github.com/golang-jwt/jwt"
"go.uber.org/zap"
"gorm.io/gorm"
)
@ -266,48 +264,6 @@ func (m *DBModel) DeleteUser(ids []interface{}) (err error) {
return
}
type UserClaims struct {
UserId int64
UUID string
jwt.StandardClaims
}
// CreateUserJWTToken 生成用户JWT Token
func (m *DBModel) CreateUserJWTToken(userId int64) (string, error) {
jwtCfg := m.GetConfigOfJWT()
expireTime := time.Now().Add(time.Duration(jwtCfg.Duration) * 24 * time.Hour).Unix()
claims := UserClaims{
UserId: userId,
UUID: uuid.Must(uuid.NewV4()).String(),
StandardClaims: jwt.StandardClaims{
ExpiresAt: expireTime,
Issuer: "moredoc",
IssuedAt: time.Now().Unix(),
},
}
token, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte(jwtCfg.Secret))
return token, err
}
// CheckUserJWTToken 验证用户JWT token
func (m *DBModel) CheckUserJWTToken(token string) (*UserClaims, error) {
jwtCfg := m.GetConfigOfJWT()
tokenClaims, err := jwt.ParseWithClaims(token, &UserClaims{}, func(token *jwt.Token) (interface{}, error) {
return []byte(jwtCfg.Secret), nil
})
if err != nil {
return nil, err
}
if tokenClaims != nil {
if claims, ok := tokenClaims.Claims.(*UserClaims); ok && tokenClaims.Valid {
return claims, nil
}
}
return nil, err
}
func (m *DBModel) initUser() (err error) {
// 如果不存在任意用户,则初始化一个用户作为管理员
var existUser User

@ -9,6 +9,7 @@ import (
v1 "moredoc/api/v1"
"moredoc/biz"
"moredoc/conf"
"moredoc/middleware/auth"
"moredoc/middleware/jsonpb"
"moredoc/model"
@ -28,6 +29,7 @@ import (
// Run start server
func Run(cfg *conf.Config, logger *zap.Logger) {
size := 100 * 1024 * 1024 // 100MB
dialOpts := []grpc.DialOption{
grpc.WithInsecure(),
@ -35,9 +37,11 @@ func Run(cfg *conf.Config, logger *zap.Logger) {
grpc.WithDefaultCallOptions(grpc.UseCompressor("gzip")),
}
auth := auth.NewAuth(&cfg.JWT)
grpcServer := grpc.NewServer(
grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
// jwtil.UnaryServerInterceptor(jwtil.TokenAuthInterceptor),
auth.AuthUnaryServerInterceptor(),
grpc_recovery.UnaryServerInterceptor(),
)),
)
@ -49,20 +53,20 @@ func Run(cfg *conf.Config, logger *zap.Logger) {
),
)
dbModel, err := model.NewDBModel(&cfg.Database, logger)
dbModel, err := model.NewDBModel(cfg, logger)
if err != nil {
logger.Fatal("NewDBModel", zap.Error(err))
return
}
endpoint := fmt.Sprintf("localhost:%v", cfg.Port)
// =========================================================================
// 【start】 在这里注册您的API服务模块
// =========================================================================
endpoint := fmt.Sprintf("localhost:%v", cfg.Port)
// 用户API接口服务
userAPIService := biz.NewUserAPIService(dbModel, logger)
userAPIService := biz.NewUserAPIService(dbModel, logger, auth)
v1.RegisterUserAPIServer(grpcServer, userAPIService)
err = v1.RegisterUserAPIHandlerFromEndpoint(context.Background(), gwmux, endpoint, dialOpts)
if err != nil {

@ -10,7 +10,7 @@ import (
func SyncDB(cfg *conf.Config, logger *zap.Logger) {
lg := logger.Named("syncdb")
lg.Info("start syncdb")
dbModel, err := model.NewDBModel(&cfg.Database, logger)
dbModel, err := model.NewDBModel(cfg, logger)
if err != nil {
lg.Fatal("NewDBModel", zap.Error(err))
return

Loading…
Cancel
Save