修复漏洞,重构初始化功能,优化媒体库 (#1024)
* 媒体库增加 普通上传、压缩上传按钮,方便媒体库直接上传图片 * 增加数据类型切换后的的校验,避免使用错误的查询条件和字典条件。 * refactor: 重构初始化逻辑 * 媒体库功能丰富 * 修复注入漏洞和路径穿越 * 修复自动化接口获取数据库表失败后未能终止的bug * 微调媒体库样式 Co-authored-by: bypanghu <bypanghu@163.com> Co-authored-by: tesun <36953434+tesun@users.noreply.github.com> Co-authored-by: pnck <hio131@gmail.com> Co-authored-by: task <121913992@qq.com>
This commit is contained in:
@@ -1,65 +1,136 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
adapter "github.com/casbin/gorm-adapter/v3"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/example"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
|
||||
"gorm.io/gorm"
|
||||
"sort"
|
||||
)
|
||||
|
||||
const (
|
||||
Mysql = "mysql"
|
||||
Pgsql = "pgsql"
|
||||
InitSuccess = "\n[%v] --> 初始数据成功!\n"
|
||||
InitDataExist = "\n[%v] --> %v 的初始数据已存在!\n"
|
||||
InitDataFailed = "\n[%v] --> %v 初始数据失败! \nerr: %+v\n"
|
||||
InitDataSuccess = "\n[%v] --> %v 初始数据成功!\n"
|
||||
)
|
||||
|
||||
const (
|
||||
InitOrderSystem = 10
|
||||
InitOrderInternal = 1000
|
||||
InitOrderExternal = 100000
|
||||
)
|
||||
|
||||
var (
|
||||
ErrMissingDBContext = errors.New("missing db in context")
|
||||
ErrMissingDependentContext = errors.New("missing dependent value in context")
|
||||
ErrDBTypeMismatch = errors.New("db type mismatch")
|
||||
)
|
||||
|
||||
// SubInitializer 提供 source/*/init() 使用的接口,每个 initializer 完成一个初始化过程
|
||||
type SubInitializer interface {
|
||||
InitializerName() string // 不一定代表单独一个表,所以改成了更宽泛的语义
|
||||
MigrateTable(ctx context.Context) (next context.Context, err error)
|
||||
InitializeData(ctx context.Context) (next context.Context, err error)
|
||||
TableCreated(ctx context.Context) bool
|
||||
DataInserted(ctx context.Context) bool
|
||||
}
|
||||
|
||||
// TypedDBInitHandler 执行传入的 initializer
|
||||
type TypedDBInitHandler interface {
|
||||
EnsureDB(ctx context.Context, conf *request.InitDB) (context.Context, error) // 建库,失败属于 fatal error,因此让它 panic
|
||||
WriteConfig(ctx context.Context) error // 回写配置
|
||||
InitTables(ctx context.Context, inits initSlice) error // 建表 handler
|
||||
InitData(ctx context.Context, inits initSlice) error // 建数据 handler
|
||||
}
|
||||
|
||||
// orderedInitializer 组合一个顺序字段,以供排序
|
||||
type orderedInitializer struct {
|
||||
order int
|
||||
SubInitializer
|
||||
}
|
||||
|
||||
// initSlice 供 initializer 排序依赖时使用
|
||||
type initSlice []*orderedInitializer
|
||||
|
||||
var (
|
||||
initializers initSlice
|
||||
cache map[string]*orderedInitializer
|
||||
)
|
||||
|
||||
// RegisterInit 注册要执行的初始化过程,会在 InitDB() 时调用
|
||||
func RegisterInit(order int, i SubInitializer) {
|
||||
if initializers == nil {
|
||||
initializers = initSlice{}
|
||||
}
|
||||
if cache == nil {
|
||||
cache = map[string]*orderedInitializer{}
|
||||
}
|
||||
name := i.InitializerName()
|
||||
if _, existed := cache[name]; existed {
|
||||
panic(fmt.Sprintf("Name conflict on %s", name))
|
||||
}
|
||||
ni := orderedInitializer{order, i}
|
||||
initializers = append(initializers, &ni)
|
||||
cache[name] = &ni
|
||||
}
|
||||
|
||||
/* ---- * service * ---- */
|
||||
|
||||
type InitDBService struct{}
|
||||
|
||||
// InitDB 创建数据库并初始化 总入口
|
||||
// Author [piexlmax](https://github.com/piexlmax)
|
||||
// Author [SliverHorn](https://github.com/SliverHorn)
|
||||
// Author [songzhibin97](https://github.com/songzhibin97)
|
||||
func (initDBService *InitDBService) InitDB(conf request.InitDB) error {
|
||||
func (initDBService *InitDBService) InitDB(conf request.InitDB) (err error) {
|
||||
ctx := context.TODO()
|
||||
if len(initializers) == 0 {
|
||||
return errors.New("无可用初始化过程,请检查初始化是否已执行完成")
|
||||
}
|
||||
sort.Sort(&initializers) // 保证有依赖的 initializer 排在后面执行
|
||||
// Note: 若 initializer 只有单一依赖,可以写为 B=A+1, C=A+1; 由于 BC 之间没有依赖关系,所以谁先谁后并不影响初始化
|
||||
// 若存在多个依赖,可以写为 C=A+B, D=A+B+C, E=A+1;
|
||||
// C必然>A|B,因此在AB之后执行,D必然>A|B|C,因此在ABC后执行,而E只依赖A,顺序与CD无关,因此E与CD哪个先执行并不影响
|
||||
var initHandler TypedDBInitHandler
|
||||
switch conf.DBType {
|
||||
case "mysql":
|
||||
return initDBService.initMysqlDB(conf)
|
||||
initHandler = NewMysqlInitHandler()
|
||||
ctx = context.WithValue(ctx, "dbtype", "mysql")
|
||||
case "pgsql":
|
||||
return initDBService.initPgsqlDB(conf)
|
||||
initHandler = NewPgsqlInitHandler()
|
||||
ctx = context.WithValue(ctx, "dbtype", "pgsql")
|
||||
default:
|
||||
return initDBService.initMysqlDB(conf)
|
||||
initHandler = NewMysqlInitHandler()
|
||||
ctx = context.WithValue(ctx, "dbtype", "mysql")
|
||||
}
|
||||
ctx, err = initHandler.EnsureDB(ctx, &conf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
db := ctx.Value("db").(*gorm.DB)
|
||||
global.GVA_DB = db
|
||||
|
||||
if err = initHandler.WriteConfig(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = initHandler.InitTables(ctx, initializers); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = initHandler.InitData(ctx, initializers); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
initializers = initSlice{}
|
||||
cache = map[string]*orderedInitializer{}
|
||||
return nil
|
||||
}
|
||||
|
||||
// initTables 初始化表
|
||||
// Author [SliverHorn](https://github.com/SliverHorn)
|
||||
func (initDBService *InitDBService) initTables() error {
|
||||
return global.GVA_DB.AutoMigrate(
|
||||
system.SysApi{},
|
||||
system.SysUser{},
|
||||
system.SysBaseMenu{},
|
||||
system.SysAuthority{},
|
||||
system.JwtBlacklist{},
|
||||
system.SysDictionary{},
|
||||
system.SysAutoCodeHistory{},
|
||||
system.SysOperationRecord{},
|
||||
system.SysDictionaryDetail{},
|
||||
system.SysBaseMenuParameter{},
|
||||
system.SysBaseMenuBtn{},
|
||||
system.SysAuthorityBtn{},
|
||||
system.SysAutoCode{},
|
||||
|
||||
adapter.CasbinRule{},
|
||||
|
||||
example.ExaFile{},
|
||||
example.ExaCustomer{},
|
||||
example.ExaFileChunk{},
|
||||
example.ExaFileUploadAndDownload{},
|
||||
)
|
||||
}
|
||||
|
||||
// createDatabase 创建数据库(mysql)
|
||||
// Author [SliverHorn](https://github.com/SliverHorn)
|
||||
// Author: [songzhibin97](https://github.com/songzhibin97)
|
||||
|
||||
func (initDBService *InitDBService) createDatabase(dsn string, driver string, createSql string) error {
|
||||
// createDatabase 创建数据库( EnsureDB() 中调用 )
|
||||
func createDatabase(dsn string, driver string, createSql string) error {
|
||||
db, err := sql.Open(driver, dsn)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -76,3 +147,35 @@ func (initDBService *InitDBService) createDatabase(dsn string, driver string, cr
|
||||
_, err = db.Exec(createSql)
|
||||
return err
|
||||
}
|
||||
|
||||
// createTables 创建表(默认 dbInitHandler.initTables 行为)
|
||||
func createTables(ctx context.Context, inits initSlice) error {
|
||||
next, cancel := context.WithCancel(ctx)
|
||||
defer func(c func()) { c() }(cancel)
|
||||
for _, init := range inits {
|
||||
if init.TableCreated(next) {
|
||||
continue
|
||||
}
|
||||
if n, err := init.MigrateTable(next); err != nil {
|
||||
return err
|
||||
} else {
|
||||
next = n
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/* -- sortable interface -- */
|
||||
|
||||
func (a initSlice) Len() int {
|
||||
return len(a)
|
||||
}
|
||||
|
||||
func (a initSlice) Less(i, j int) bool {
|
||||
return a[i].order < a[j].order
|
||||
}
|
||||
|
||||
func (a initSlice) Swap(i, j int) {
|
||||
a[i], a[j] = a[j], a[i]
|
||||
}
|
||||
|
Reference in New Issue
Block a user