文件结构调整,支持插件自动化 (#1824)

* Update index.vue

多个详情页之间切换tab,页面没有重新渲染

* feature:新增同步API功能

* feature: 同步表数据

* feature:新增同步API功能

* feature: 增加V2插件注册

* feature:给Enter的依赖结构增加单独的New 方便引用

* feature: 调整关联属性的选择模式

* feature: 增加component组件和name的映射插件,防止keepalive的懒加载失效。

* update: PluginInitializeRouter  && 修复TypePluginInitializeMenu ast 类型错误

* update: 测试文件的astType 类型错误

* feature: 文件变更自动同步componentName.json。

* feature: 文件变更自动同步componentName.json。

* feat: UI美化

* feat: 自动化页面顺序调整

* feature:修改404页面

* update: PluginInitializeMenu

* update: Plugin template

* fixed systemApi 重复声明

* api.vue:update:修改API分组为下拉列表

* update: import添加注释

* update: plugin_enter_test.go 增加测试用例

* update: ast 预览文件路径

* update: config Autocode 新增Module字段以及如果为空的情况下自动获取运行目录下的go.mod文件

* update: auto_code_package.go 完善调用ast工具类的封装使用

* update: auto_code_template.go Create方法和修正SysAutoCodeHistory

* feat:调整自动化package为模板,增加初始化配置信息,调整页面信息。

* update: ast PreviewPath MkdirAll

* update: ast type错误, PluginEnter and PackageModuleEnter add TemplatePath模版路径

* update: autoCodePackage and autoCodeTemplate bug修正

* update: PackageInitializeRouter 传入两个路由组

* update: PackageModuleEnter 处理空变量时与type冲突注入

* update: Package 模版更新

* update: utils/ast 优化统一

* update: 注入内容修复错误

* fix: 修复注释错误

* update: plugin 模版 完成

* feature: 文件watch功能只在development下开启

* update: viper.go.template 因为viper不区分配置的key的大小写所以用package

* update: ast 测试代码规范化

* update: package 删除api和router多余导包

* update: plugin template

* update: auto_code_package 问题修复

* update: ast 测试插件的预览功能

* update: gorm_biz 更新注册方式

* update: go.mod tidy

* remove: plugin template gen main.go.template

* update: ast 重构, 分离读取和写入步骤支持

* update: AutoCodePackageApi 传入参数错误修复

* rename: sys_autocode_history.go => sys_auto_code_history.go

* update: 预览无需落盘, 创建落盘,抽离公共参数

* update: api.go.tpl 导包位置fmt 和package js位置存放错误

* update: 测试用例修复 and PackageInitializeGorm 重构

* update: ast 新增相对路径, 代码生成器历史回滚功能

* update: ast 工具类回滚失败修复以及测试文件

* update: 代码生成器历史 回滚问题修复

* update: 代码生成器模版忽略.DS_Store

* featute: 自动化GORM结构的注入和剔除

* feature: 插件模板调整

* feature: 增加公告插件示例,调整代码模板。

* feature: 自动注册插件V2。

---------

Co-authored-by: zayn <972858472@qq.com>
Co-authored-by: SliverHorn <sliver_horn@qq.com>
Co-authored-by: krank <emosick@qq.com>
Co-authored-by: cjk <wlicjk@126.com>
Co-authored-by: piexlMax(奇淼 <qimiaojiangjizhao@gmail.com>
Co-authored-by: maxwell <zhong.maxwell@gmail.com>
This commit is contained in:
PiexlMax(奇淼
2024-07-21 11:33:25 +08:00
committed by GitHub
parent 6b3a9024d3
commit 6e4dc10c49
193 changed files with 9410 additions and 2904 deletions

View File

@@ -0,0 +1,217 @@
package system
import (
"context"
"encoding/json"
"fmt"
"github.com/flipped-aurora/gin-vue-admin/server/utils/ast"
"github.com/pkg/errors"
"path"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/flipped-aurora/gin-vue-admin/server/global"
common "github.com/flipped-aurora/gin-vue-admin/server/model/common/request"
model "github.com/flipped-aurora/gin-vue-admin/server/model/system"
request "github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
"github.com/flipped-aurora/gin-vue-admin/server/utils"
"go.uber.org/zap"
)
var AutocodeHistory = new(autoCodeHistory)
type autoCodeHistory struct{}
// Create 创建代码生成器历史记录
// Author [SliverHorn](https://github.com/SliverHorn)
// Author [songzhibin97](https://github.com/songzhibin97)
func (s *autoCodeHistory) Create(ctx context.Context, info request.SysAutoHistoryCreate) error {
create := info.Create()
err := global.GVA_DB.WithContext(ctx).Create(&create).Error
if err != nil {
return errors.Wrap(err, "创建失败!")
}
return nil
}
// First 根据id获取代码生成器历史的数据
// Author [SliverHorn](https://github.com/SliverHorn)
// Author [songzhibin97](https://github.com/songzhibin97)
func (s *autoCodeHistory) First(ctx context.Context, info common.GetById) (string, error) {
var meta string
err := global.GVA_DB.WithContext(ctx).Model(model.SysAutoCodeHistory{}).Where("id = ?", info.ID).Pluck("request", &meta).Error
if err != nil {
return "", errors.Wrap(err, "获取失败!")
}
return meta, nil
}
// Repeat 检测重复
// Author [SliverHorn](https://github.com/SliverHorn)
// Author [songzhibin97](https://github.com/songzhibin97)
func (s *autoCodeHistory) Repeat(businessDB, structName, Package string) bool {
var count int64
global.GVA_DB.Model(&model.SysAutoCodeHistory{}).Where("business_db = ? and struct_name = ? and package = ? and flag = 0", businessDB, structName, Package).Count(&count)
return count > 0
}
// RollBack 回滚
// Author [SliverHorn](https://github.com/SliverHorn)
// Author [songzhibin97](https://github.com/songzhibin97)
func (s *autoCodeHistory) RollBack(ctx context.Context, info request.SysAutoHistoryRollBack) error {
var history model.SysAutoCodeHistory
err := global.GVA_DB.Where("id = ?", info.ID).First(&history).Error
if err != nil {
return err
}
if info.DeleteApi {
ids := info.ApiIds(history)
err = ApiServiceApp.DeleteApisByIds(ids)
if err != nil {
global.GVA_LOG.Error("ClearTag DeleteApiByIds:", zap.Error(err))
}
} // 清除API表
if info.DeleteMenu {
err = BaseMenuServiceApp.DeleteBaseMenu(int(history.MenuID))
if err != nil {
return errors.Wrap(err, "删除菜单失败!")
}
} // 清除菜单表
if info.DeleteTable {
err = s.DropTable(history.BusinessDB, history.Table)
if err != nil {
return errors.Wrap(err, "删除表失败!")
}
} // 删除表
templates := make(map[string]string, len(history.Templates))
for key, template := range history.Templates {
{
server := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server)
keys := strings.Split(key, "/")
key = filepath.Join(keys...)
key = strings.TrimPrefix(key, server)
} // key
{
web := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.WebRoot())
server := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server)
slices := strings.Split(template, "/")
template = filepath.Join(slices...)
ext := path.Ext(template)
switch ext {
case ".js", ".vue":
template = filepath.Join(web, template)
case ".go":
template = filepath.Join(server, template)
}
} // value
templates[key] = template
}
history.Templates = templates
for key, value := range history.Injections {
var injection ast.Ast
switch key {
case ast.TypePackageApiEnter, ast.TypePackageRouterEnter, ast.TypePackageServiceEnter:
var entity ast.PackageEnter
_ = json.Unmarshal([]byte(value), &entity)
injection = &entity
case ast.TypePackageApiModuleEnter, ast.TypePackageRouterModuleEnter, ast.TypePackageServiceModuleEnter:
var entity ast.PackageModuleEnter
_ = json.Unmarshal([]byte(value), &entity)
injection = &entity
case ast.TypePackageInitializeGorm:
var entity ast.PackageInitializeGorm
_ = json.Unmarshal([]byte(value), &entity)
injection = &entity
case ast.TypePackageInitializeRouter:
var entity ast.PackageInitializeRouter
_ = json.Unmarshal([]byte(value), &entity)
injection = &entity
case ast.TypePluginGen:
var entity ast.PluginGen
_ = json.Unmarshal([]byte(value), &entity)
injection = &entity
case ast.TypePluginApiEnter, ast.TypePluginRouterEnter, ast.TypePluginServiceEnter:
var entity ast.PluginEnter
_ = json.Unmarshal([]byte(value), &entity)
injection = &entity
case ast.TypePluginInitializeV2:
var entity ast.PluginInitializeV2
_ = json.Unmarshal([]byte(value), &entity)
injection = &entity
case ast.TypePluginInitializeGorm:
var entity ast.PluginInitializeGorm
_ = json.Unmarshal([]byte(value), &entity)
injection = &entity
case ast.TypePluginInitializeRouter:
var entity ast.PluginInitializeRouter
_ = json.Unmarshal([]byte(value), &entity)
injection = &entity
}
if injection == nil {
continue
}
file, _ := injection.Parse("", nil)
if file != nil {
_ = injection.Rollback(file)
err = injection.Format("", nil, file)
if err != nil {
return err
}
fmt.Printf("[filepath:%s]回滚注入代码成功!\n", key)
}
} // 清除注入代码
removeBasePath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, "rm_file", strconv.FormatInt(int64(time.Now().Nanosecond()), 10))
for _, value := range history.Templates {
if !filepath.IsAbs(value) {
continue
}
removePath := filepath.Join(removeBasePath, strings.TrimPrefix(value, global.GVA_CONFIG.AutoCode.Root))
err = utils.FileMove(value, removePath)
if err != nil {
return errors.Wrapf(err, "[src:%s][dst:%s]文件移动失败!", value, removePath)
}
} // 移动文件
err = global.GVA_DB.WithContext(ctx).Model(&model.SysAutoCodeHistory{}).Where("id = ?", info.ID).Update("flag", 1).Error
if err != nil {
return errors.Wrap(err, "更新失败!")
}
return nil
}
// Delete 删除历史数据
// Author [SliverHorn](https://github.com/SliverHorn)
// Author [songzhibin97](https://github.com/songzhibin97)
func (s *autoCodeHistory) Delete(ctx context.Context, info common.GetById) error {
err := global.GVA_DB.WithContext(ctx).Where("id = ?", info.Uint()).Delete(&model.SysAutoCodeHistory{}).Error
if err != nil {
return errors.Wrap(err, "删除失败!")
}
return nil
}
// GetList 获取系统历史数据
// Author [SliverHorn](https://github.com/SliverHorn)
// Author [songzhibin97](https://github.com/songzhibin97)
func (s *autoCodeHistory) GetList(ctx context.Context, info common.PageInfo) (list []model.SysAutoCodeHistory, total int64, err error) {
var entities []model.SysAutoCodeHistory
db := global.GVA_DB.WithContext(ctx).Model(&model.SysAutoCodeHistory{})
err = db.Count(&total).Error
if err != nil {
return nil, total, err
}
err = db.Scopes(info.Paginate()).Order("updated_at desc").Find(&entities).Error
return entities, total, err
}
// DropTable 获取指定数据库和指定数据表的所有字段名,类型值等
// @author: [piexlmax](https://github.com/piexlmax)
func (s *autoCodeHistory) DropTable(BusinessDb, tableName string) error {
if BusinessDb != "" {
return global.MustGetGlobalDBByDBName(BusinessDb).Exec("DROP TABLE " + tableName).Error
} else {
return global.GVA_DB.Exec("DROP TABLE " + tableName).Error
}
}

View File

@@ -0,0 +1,579 @@
package system
import (
"context"
"fmt"
"github.com/flipped-aurora/gin-vue-admin/server/global"
common "github.com/flipped-aurora/gin-vue-admin/server/model/common/request"
model "github.com/flipped-aurora/gin-vue-admin/server/model/system"
"github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
"github.com/flipped-aurora/gin-vue-admin/server/utils/ast"
"github.com/pkg/errors"
"go/token"
"gorm.io/gorm"
"os"
"path/filepath"
"strings"
"text/template"
)
var AutoCodePackage = new(autoCodePackage)
type autoCodePackage struct{}
// Create 创建包信息
// @author: [piexlmax](https://github.com/piexlmax)
// @author: [SliverHorn](https://github.com/SliverHorn)
func (s *autoCodePackage) Create(ctx context.Context, info *request.SysAutoCodePackageCreate) error {
switch {
case info.Template == "":
return errors.New("模板不能为空!")
case info.Template == "page":
return errors.New("page为表单生成器!")
case info.PackageName == "":
return errors.New("PackageName不能为空!")
case token.IsKeyword(info.PackageName):
return errors.Errorf("%s为go的关键字!", info.PackageName)
case info.Template == "package":
if info.PackageName == "system" || info.PackageName == "example" {
return errors.New("不能使用已保留的package name")
}
default:
break
}
if !errors.Is(global.GVA_DB.Where("package_name = ? and template = ?", info.PackageName, info.Template).First(&model.SysAutoCodePackage{}).Error, gorm.ErrRecordNotFound) {
return errors.New("存在相同PackageName")
}
create := info.Create()
return global.GVA_DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
err := tx.Create(&create).Error
if err != nil {
return errors.Wrap(err, "创建失败!")
}
code := info.AutoCode()
_, asts, creates, err := s.templates(ctx, create, code)
if err != nil {
return err
}
for key, value := range creates { // key 为 模版绝对路径
var files *template.Template
files, err = template.ParseFiles(key)
if err != nil {
return errors.Wrapf(err, "[filepath:%s]读取模版文件失败!", key)
}
err = os.MkdirAll(filepath.Dir(value), os.ModePerm)
if err != nil {
return errors.Wrapf(err, "[filepath:%s]创建文件夹失败!", value)
}
var file *os.File
file, err = os.Create(value)
if err != nil {
return errors.Wrapf(err, "[filepath:%s]创建文件夹失败!", value)
}
err = files.Execute(file, code)
_ = file.Close()
if err != nil {
return errors.Wrapf(err, "[filepath:%s]生成失败!", value)
}
fmt.Printf("[template:%s][filepath:%s]生成成功!\n", key, value)
}
for key, value := range asts {
keys := strings.Split(key, "=>")
if len(keys) == 2 {
if keys[1] == ast.TypePluginInitializeV2 {
file, _ := value.Parse("", nil)
if file != nil {
err = value.Injection(file)
if err != nil {
return err
}
err = value.Format("", nil, file)
if err != nil {
return err
}
}
fmt.Printf("[type:%s]注入成功!\n", key)
}
}
}
return nil
})
}
// Delete 删除包记录
// @author: [piexlmax](https://github.com/piexlmax)
// @author: [SliverHorn](https://github.com/SliverHorn)
func (s *autoCodePackage) Delete(ctx context.Context, info common.GetById) error {
err := global.GVA_DB.WithContext(ctx).Delete(&model.SysAutoCodePackage{}, info.Uint()).Error
if err != nil {
return errors.Wrap(err, "删除失败!")
}
return nil
}
// All 获取所有包
// @author: [piexlmax](https://github.com/piexlmax)
// @author: [SliverHorn](https://github.com/SliverHorn)
func (s *autoCodePackage) All(ctx context.Context) (entities []model.SysAutoCodePackage, err error) {
err = global.GVA_DB.WithContext(ctx).Find(&entities).Error
if err != nil {
return nil, errors.Wrap(err, "获取所有包失败!")
}
return entities, nil
}
// Templates 获取所有模版文件夹
// @author: [SliverHorn](https://github.com/SliverHorn)
func (s *autoCodePackage) Templates(ctx context.Context) ([]string, error) {
templates := make([]string, 0)
entries, err := os.ReadDir("resource")
if err != nil {
return nil, errors.Wrap(err, "读取模版文件夹失败!")
}
for i := 0; i < len(entries); i++ {
if entries[i].IsDir() {
if entries[i].Name() == "page" {
continue
} // page 为表单生成器
if entries[i].Name() == "preview" {
continue
} // preview 为预览代码生成器的代码
templates = append(templates, entries[i].Name())
}
}
return templates, nil
}
func (s *autoCodePackage) templates(ctx context.Context, entity model.SysAutoCodePackage, info request.AutoCode) (code map[string]string, asts map[string]ast.Ast, creates map[string]string, err error) {
code = make(map[string]string)
asts = make(map[string]ast.Ast)
creates = make(map[string]string)
templateDir := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "resource", entity.Template)
templateDirs, err := os.ReadDir(templateDir)
if err != nil {
return nil, nil, nil, errors.Wrapf(err, "读取模版文件夹[%s]失败!", templateDir)
}
for i := 0; i < len(templateDirs); i++ {
second := filepath.Join(templateDir, templateDirs[i].Name())
switch templateDirs[i].Name() {
case "server":
var secondDirs []os.DirEntry
secondDirs, err = os.ReadDir(second)
if err != nil {
return nil, nil, nil, errors.Wrapf(err, "读取模版文件夹[%s]失败!", second)
}
for j := 0; j < len(secondDirs); j++ {
if secondDirs[j].Name() == ".DS_Store" {
continue
}
three := filepath.Join(second, secondDirs[j].Name())
if !secondDirs[j].IsDir() {
ext := filepath.Ext(secondDirs[j].Name())
if ext != ".template" && ext != ".tpl" {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版后缀!", three)
}
name := strings.TrimSuffix(secondDirs[j].Name(), ext)
if name == "main.go" || name == "plugin.go" {
pluginInitialize := &ast.PluginInitializeV2{
Type: ast.TypePluginInitializeV2,
Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", entity.PackageName, name),
PluginPath: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "initialize", "plugin_biz_v2.go"),
ImportPath: fmt.Sprintf(`"%s/plugin/%s"`, global.GVA_CONFIG.AutoCode.Module, entity.PackageName),
PackageName: entity.PackageName,
}
asts[pluginInitialize.PluginPath+"=>"+pluginInitialize.Type.String()] = pluginInitialize
creates[three] = pluginInitialize.Path
continue
}
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件!", three)
}
switch secondDirs[j].Name() {
case "api", "router", "service":
var threeDirs []os.DirEntry
threeDirs, err = os.ReadDir(three)
if err != nil {
return nil, nil, nil, errors.Wrapf(err, "读取模版文件夹[%s]失败!", three)
}
for k := 0; k < len(threeDirs); k++ {
if threeDirs[k].Name() == ".DS_Store" {
continue
}
four := filepath.Join(three, threeDirs[k].Name())
if threeDirs[k].IsDir() {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件夹!", four)
}
ext := filepath.Ext(four)
if ext != ".template" && ext != ".tpl" {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版后缀!", four)
}
api := strings.Index(threeDirs[k].Name(), "api")
hasEnter := strings.Index(threeDirs[k].Name(), "enter")
router := strings.Index(threeDirs[k].Name(), "router")
service := strings.Index(threeDirs[k].Name(), "service")
if router == -1 && api == -1 && service == -1 && hasEnter == -1 {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件!", four)
}
if entity.Template == "package" {
create := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, secondDirs[j].Name(), entity.PackageName, info.HumpPackageName+".go")
if api != -1 {
create = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, secondDirs[j].Name(), "v1", entity.PackageName, info.HumpPackageName+".go")
}
if hasEnter != -1 {
isApi := strings.Index(secondDirs[j].Name(), "api")
isRouter := strings.Index(secondDirs[j].Name(), "router")
isService := strings.Index(secondDirs[j].Name(), "service")
if isApi != -1 {
packageApiEnter := &ast.PackageEnter{
Type: ast.TypePackageApiEnter,
Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, secondDirs[j].Name(), "v1", "enter.go"),
ImportPath: fmt.Sprintf(`"%s/%s/%s/%s"`, global.GVA_CONFIG.AutoCode.Module, "api", "v1", entity.PackageName),
StructName: info.PackageT + "ApiGroup",
PackageName: entity.PackageName,
PackageStructName: "ApiGroup",
}
asts[packageApiEnter.Path+"=>"+packageApiEnter.Type.String()] = packageApiEnter
packageApiModuleEnter := &ast.PackageModuleEnter{
Type: ast.TypePackageApiModuleEnter,
Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, secondDirs[j].Name(), "v1", entity.PackageName, "enter.go"),
ImportPath: fmt.Sprintf(`"%s/service"`, global.GVA_CONFIG.AutoCode.Module),
StructName: info.StructName + "Api",
AppName: "ServiceGroupApp",
GroupName: info.PackageT + "ServiceGroup",
ModuleName: info.Abbreviation + "Service",
PackageName: "service",
ServiceName: info.StructName + "Service",
}
asts[packageApiModuleEnter.Path+"=>"+packageApiModuleEnter.Type.String()] = packageApiModuleEnter
creates[four] = packageApiModuleEnter.Path
}
if isRouter != -1 {
packageRouterEnter := &ast.PackageEnter{
Type: ast.TypePackageRouterEnter,
Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, secondDirs[j].Name(), "enter.go"),
ImportPath: fmt.Sprintf(`"%s/%s/%s"`, global.GVA_CONFIG.AutoCode.Module, secondDirs[j].Name(), entity.PackageName),
StructName: info.PackageT,
PackageName: entity.PackageName,
PackageStructName: "RouterGroup",
}
asts[packageRouterEnter.Path+"=>"+packageRouterEnter.Type.String()] = packageRouterEnter
packageRouterModuleEnter := &ast.PackageModuleEnter{
Type: ast.TypePackageRouterModuleEnter,
Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, secondDirs[j].Name(), entity.PackageName, "enter.go"),
ImportPath: fmt.Sprintf(`api "%s/api/v1"`, global.GVA_CONFIG.AutoCode.Module),
StructName: info.StructName + "Router",
AppName: "ApiGroupApp",
GroupName: info.PackageT + "ApiGroup",
ModuleName: info.Abbreviation + "Api",
PackageName: "api",
ServiceName: info.StructName + "Api",
}
creates[four] = packageRouterModuleEnter.Path
asts[packageRouterModuleEnter.Path+"=>"+packageRouterModuleEnter.Type.String()] = packageRouterModuleEnter
packageInitializeRouter := &ast.PackageInitializeRouter{
Type: ast.TypePackageInitializeRouter,
Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "initialize", "router_biz.go"),
ImportPath: fmt.Sprintf(`"%s/router"`, global.GVA_CONFIG.AutoCode.Module),
AppName: "RouterGroupApp",
GroupName: info.PackageT,
ModuleName: entity.PackageName + "Router",
PackageName: "router",
FunctionName: "Init" + info.StructName + "Router",
LeftRouterGroupName: "privateGroup",
RightRouterGroupName: "publicGroup",
}
asts[packageInitializeRouter.Path+"=>"+packageInitializeRouter.Type.String()] = packageInitializeRouter
}
if isService != -1 {
path := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, secondDirs[j].Name(), strings.TrimSuffix(threeDirs[k].Name(), ext))
importPath := fmt.Sprintf(`"%s/service/%s"`, global.GVA_CONFIG.AutoCode.Module, entity.PackageName)
packageServiceEnter := &ast.PackageEnter{
Type: ast.TypePackageServiceEnter,
Path: path,
ImportPath: importPath,
StructName: info.PackageT + "ServiceGroup",
PackageName: entity.PackageName,
PackageStructName: "ServiceGroup",
}
asts[packageServiceEnter.Path+"=>"+packageServiceEnter.Type.String()] = packageServiceEnter
packageServiceModuleEnter := &ast.PackageModuleEnter{
Type: ast.TypePackageServiceModuleEnter,
Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, secondDirs[j].Name(), entity.PackageName, "enter.go"),
StructName: info.StructName + "Service",
}
asts[packageServiceModuleEnter.Path+"=>"+packageServiceModuleEnter.Type.String()] = packageServiceModuleEnter
creates[four] = packageServiceModuleEnter.Path
}
continue
}
code[four] = create
continue
}
if hasEnter != -1 {
isApi := strings.Index(secondDirs[j].Name(), "api")
isRouter := strings.Index(secondDirs[j].Name(), "router")
isService := strings.Index(secondDirs[j].Name(), "service")
if isRouter != -1 {
pluginRouterEnter := &ast.PluginEnter{
Type: ast.TypePluginRouterEnter,
Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", entity.PackageName, secondDirs[j].Name(), strings.TrimSuffix(threeDirs[k].Name(), ext)),
ImportPath: fmt.Sprintf(`"%s/plugin/%s/api"`, global.GVA_CONFIG.AutoCode.Module, entity.PackageName),
StructName: info.StructName,
StructCamelName: info.Abbreviation,
ModuleName: "api" + info.StructName,
GroupName: "Api",
PackageName: "api",
ServiceName: info.StructName,
}
asts[pluginRouterEnter.Path+"=>"+pluginRouterEnter.Type.String()] = pluginRouterEnter
creates[four] = pluginRouterEnter.Path
}
if isApi != -1 {
pluginApiEnter := &ast.PluginEnter{
Type: ast.TypePluginApiEnter,
Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", entity.PackageName, secondDirs[j].Name(), strings.TrimSuffix(threeDirs[k].Name(), ext)),
ImportPath: fmt.Sprintf(`"%s/plugin/%s/service"`, global.GVA_CONFIG.AutoCode.Module, entity.PackageName),
StructName: info.StructName,
StructCamelName: info.Abbreviation,
ModuleName: "service" + info.StructName,
GroupName: "Service",
PackageName: "service",
ServiceName: info.StructName,
}
asts[pluginApiEnter.Path+"=>"+pluginApiEnter.Type.String()] = pluginApiEnter
creates[four] = pluginApiEnter.Path
}
if isService != -1 {
pluginServiceEnter := &ast.PluginEnter{
Type: ast.TypePluginServiceEnter,
Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", entity.PackageName, secondDirs[j].Name(), strings.TrimSuffix(threeDirs[k].Name(), ext)),
StructName: info.StructName,
StructCamelName: info.Abbreviation,
}
asts[pluginServiceEnter.Path+"=>"+pluginServiceEnter.Type.String()] = pluginServiceEnter
creates[four] = pluginServiceEnter.Path
}
continue
} // enter.go
create := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", entity.PackageName, secondDirs[j].Name(), info.HumpPackageName+".go")
code[four] = create
}
case "gen", "config", "initialize", "plugin", "response":
if entity.Template == "package" {
continue
} // package模板不需要生成gen, config, initialize
var threeDirs []os.DirEntry
threeDirs, err = os.ReadDir(three)
if err != nil {
return nil, nil, nil, errors.Wrapf(err, "读取模版文件夹[%s]失败!", three)
}
for k := 0; k < len(threeDirs); k++ {
if threeDirs[k].Name() == ".DS_Store" {
continue
}
four := filepath.Join(three, threeDirs[k].Name())
if threeDirs[k].IsDir() {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件夹!", four)
}
ext := filepath.Ext(four)
if ext != ".template" && ext != ".tpl" {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版后缀!", four)
}
gen := strings.Index(threeDirs[k].Name(), "gen")
api := strings.Index(threeDirs[k].Name(), "api")
menu := strings.Index(threeDirs[k].Name(), "menu")
viper := strings.Index(threeDirs[k].Name(), "viper")
plugin := strings.Index(threeDirs[k].Name(), "plugin")
config := strings.Index(threeDirs[k].Name(), "config")
router := strings.Index(threeDirs[k].Name(), "router")
hasGorm := strings.Index(threeDirs[k].Name(), "gorm")
response := strings.Index(threeDirs[k].Name(), "response")
if gen != -1 && api != -1 && menu != -1 && viper != -1 && plugin != -1 && config != -1 && router != -1 && hasGorm != -1 && response != -1 {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件!", four)
}
if api != -1 || menu != -1 || viper != -1 || response != -1 || plugin != -1 || config != -1 {
creates[four] = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", entity.PackageName, secondDirs[j].Name(), strings.TrimSuffix(threeDirs[k].Name(), ext))
}
if gen != -1 {
pluginGen := &ast.PluginGen{
Type: ast.TypePluginGen,
Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", entity.PackageName, secondDirs[j].Name(), strings.TrimSuffix(threeDirs[k].Name(), ext)),
ImportPath: fmt.Sprintf(`"%s/plugin/%s/model"`, global.GVA_CONFIG.AutoCode.Module, entity.PackageName),
StructName: info.StructName,
PackageName: "model",
IsNew: true,
}
asts[pluginGen.Path+"=>"+pluginGen.Type.String()] = pluginGen
creates[four] = pluginGen.Path
}
if hasGorm != -1 {
pluginInitializeGorm := &ast.PluginInitializeGorm{
Type: ast.TypePluginInitializeGorm,
Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", entity.PackageName, secondDirs[j].Name(), strings.TrimSuffix(threeDirs[k].Name(), ext)),
ImportPath: fmt.Sprintf(`"%s/plugin/%s/model"`, global.GVA_CONFIG.AutoCode.Module, entity.PackageName),
StructName: info.StructName,
PackageName: "model",
IsNew: true,
}
asts[pluginInitializeGorm.Path+"=>"+pluginInitializeGorm.Type.String()] = pluginInitializeGorm
creates[four] = pluginInitializeGorm.Path
}
if router != -1 {
pluginInitializeRouter := &ast.PluginInitializeRouter{
Type: ast.TypePluginInitializeRouter,
Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", entity.PackageName, secondDirs[j].Name(), strings.TrimSuffix(threeDirs[k].Name(), ext)),
ImportPath: fmt.Sprintf(`"%s/plugin/%s/router"`, global.GVA_CONFIG.AutoCode.Module, entity.PackageName),
AppName: "Router",
GroupName: info.StructName,
PackageName: "router",
FunctionName: "Init",
LeftRouterGroupName: "public",
RightRouterGroupName: "private",
}
asts[pluginInitializeRouter.Path+"=>"+pluginInitializeRouter.Type.String()] = pluginInitializeRouter
creates[four] = pluginInitializeRouter.Path
}
}
case "model":
var threeDirs []os.DirEntry
threeDirs, err = os.ReadDir(three)
if err != nil {
return nil, nil, nil, errors.Wrapf(err, "读取模版文件夹[%s]失败!", three)
}
for k := 0; k < len(threeDirs); k++ {
if threeDirs[k].Name() == ".DS_Store" {
continue
}
four := filepath.Join(three, threeDirs[k].Name())
if threeDirs[k].IsDir() {
var fourDirs []os.DirEntry
fourDirs, err = os.ReadDir(four)
if err != nil {
return nil, nil, nil, errors.Wrapf(err, "读取模版文件夹[%s]失败!", four)
}
for l := 0; l < len(fourDirs); l++ {
if fourDirs[l].Name() == ".DS_Store" {
continue
}
five := filepath.Join(four, fourDirs[l].Name())
if fourDirs[l].IsDir() {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件夹!", five)
}
ext := filepath.Ext(five)
if ext != ".template" && ext != ".tpl" {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版后缀!", five)
}
hasRequest := strings.Index(fourDirs[l].Name(), "request")
if hasRequest == -1 {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件!", five)
}
create := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", entity.PackageName, secondDirs[j].Name(), threeDirs[k].Name(), info.HumpPackageName+".go")
if entity.Template == "package" {
create = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, secondDirs[j].Name(), entity.PackageName, threeDirs[k].Name(), info.HumpPackageName+".go")
}
code[five] = create
}
continue
}
ext := filepath.Ext(threeDirs[k].Name())
if ext != ".template" && ext != ".tpl" {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版后缀!", four)
}
hasModel := strings.Index(threeDirs[k].Name(), "model")
if hasModel == -1 {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件!", four)
}
create := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", entity.PackageName, secondDirs[j].Name(), info.HumpPackageName+".go")
if entity.Template == "package" {
packageInitializeGorm := &ast.PackageInitializeGorm{
Type: ast.TypePackageInitializeGorm,
Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "initialize", "gorm_biz.go"),
ImportPath: fmt.Sprintf(`"%s/model/%s"`, global.GVA_CONFIG.AutoCode.Module, entity.PackageName),
Business: info.BusinessDB,
StructName: info.StructName,
PackageName: entity.PackageName,
IsNew: true,
}
code[four] = packageInitializeGorm.Path
asts[packageInitializeGorm.Path+"=>"+packageInitializeGorm.Type.String()] = packageInitializeGorm
create = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, secondDirs[j].Name(), entity.PackageName, info.HumpPackageName+".go")
}
code[four] = create
}
default:
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件夹!", three)
}
}
case "web":
var secondDirs []os.DirEntry
secondDirs, err = os.ReadDir(second)
if err != nil {
return nil, nil, nil, errors.Wrapf(err, "读取模版文件夹[%s]失败!", second)
}
for j := 0; j < len(secondDirs); j++ {
if secondDirs[j].Name() == ".DS_Store" {
continue
}
three := filepath.Join(second, secondDirs[j].Name())
if !secondDirs[j].IsDir() {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件!", three)
}
switch secondDirs[j].Name() {
case "api", "form", "view", "table":
var threeDirs []os.DirEntry
threeDirs, err = os.ReadDir(three)
if err != nil {
return nil, nil, nil, errors.Wrapf(err, "读取模版文件夹[%s]失败!", three)
}
for k := 0; k < len(threeDirs); k++ {
if threeDirs[k].Name() == ".DS_Store" {
continue
}
four := filepath.Join(three, threeDirs[k].Name())
if threeDirs[k].IsDir() {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件夹!", four)
}
ext := filepath.Ext(four)
if ext != ".template" && ext != ".tpl" {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版后缀!", four)
}
api := strings.Index(threeDirs[k].Name(), "api")
form := strings.Index(threeDirs[k].Name(), "form")
view := strings.Index(threeDirs[k].Name(), "view")
table := strings.Index(threeDirs[k].Name(), "table")
if api == -1 && form == -1 && view == -1 && table == -1 {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件!", four)
}
if entity.Template == "package" {
if view != -1 || table != -1 {
formPath := filepath.Join(three, "form.vue"+ext)
value, ok := code[formPath]
if ok {
value = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.WebRoot(), secondDirs[j].Name(), entity.PackageName, info.PackageName, info.Abbreviation+"Form"+filepath.Ext(strings.TrimSuffix(threeDirs[k].Name(), ext)))
code[formPath] = value
}
}
create := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.WebRoot(), secondDirs[j].Name(), entity.PackageName, info.PackageName, info.Abbreviation+filepath.Ext(strings.TrimSuffix(threeDirs[k].Name(), ext)))
if api != -1 {
create = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.WebRoot(), secondDirs[j].Name(), entity.PackageName, info.Abbreviation+filepath.Ext(strings.TrimSuffix(threeDirs[k].Name(), ext)))
}
code[four] = create
continue
}
create := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.WebRoot(), "plugin", entity.PackageName, secondDirs[j].Name(), info.Abbreviation+filepath.Ext(strings.TrimSuffix(threeDirs[k].Name(), ext)))
code[four] = create
}
default:
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件夹!", three)
}
}
case "readme.txt.tpl", "readme.txt.template":
continue
default:
if templateDirs[i].Name() == ".DS_Store" {
continue
}
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件!", second)
}
}
return code, asts, creates, nil
}

View File

@@ -0,0 +1,105 @@
package system
import (
"context"
model "github.com/flipped-aurora/gin-vue-admin/server/model/system"
"github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
"reflect"
"testing"
)
func Test_autoCodePackage_Create(t *testing.T) {
type args struct {
ctx context.Context
info *request.SysAutoCodePackageCreate
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "测试 package",
args: args{
ctx: context.Background(),
info: &request.SysAutoCodePackageCreate{
Template: "package",
PackageName: "gva",
},
},
wantErr: false,
},
{
name: "测试 plugin",
args: args{
ctx: context.Background(),
info: &request.SysAutoCodePackageCreate{
Template: "plugin",
PackageName: "gva",
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := &autoCodePackage{}
if err := a.Create(tt.args.ctx, tt.args.info); (err != nil) != tt.wantErr {
t.Errorf("Create() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func Test_autoCodePackage_templates(t *testing.T) {
type args struct {
ctx context.Context
entity model.SysAutoCodePackage
info request.AutoCode
}
tests := []struct {
name string
args args
wantCode map[string]string
wantEnter map[string]map[string]string
wantErr bool
}{
{
name: "测试1",
args: args{
ctx: context.Background(),
entity: model.SysAutoCodePackage{
Desc: "描述",
Label: "展示名",
Template: "plugin",
PackageName: "preview",
},
info: request.AutoCode{
Abbreviation: "user",
HumpPackageName: "user",
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &autoCodePackage{}
gotCode, gotEnter, gotCreates, err := s.templates(tt.args.ctx, tt.args.entity, tt.args.info)
if (err != nil) != tt.wantErr {
t.Errorf("templates() error = %v, wantErr %v", err, tt.wantErr)
return
}
for key, value := range gotCode {
t.Logf("\n")
t.Logf(key)
t.Logf(value)
t.Logf("\n")
}
t.Log(gotCreates)
if !reflect.DeepEqual(gotEnter, tt.wantEnter) {
t.Errorf("templates() gotEnter = %v, want %v", gotEnter, tt.wantEnter)
}
})
}
}

View File

@@ -0,0 +1,174 @@
package system
import (
"context"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/utils"
"github.com/mholt/archiver/v4"
cp "github.com/otiai10/copy"
"github.com/pkg/errors"
"go.uber.org/zap"
"io"
"mime/multipart"
"os"
"path/filepath"
"strings"
)
var AutoCodePlugin = new(autoCodePlugin)
type autoCodePlugin struct{}
// Install 插件安装
func (s *autoCodePlugin) Install(file *multipart.FileHeader) (web, server int, err error) {
const GVAPLUGPINATH = "./gva-plug-temp/"
defer os.RemoveAll(GVAPLUGPINATH)
_, err = os.Stat(GVAPLUGPINATH)
if os.IsNotExist(err) {
os.Mkdir(GVAPLUGPINATH, os.ModePerm)
}
src, err := file.Open()
if err != nil {
return -1, -1, err
}
defer src.Close()
out, err := os.Create(GVAPLUGPINATH + file.Filename)
if err != nil {
return -1, -1, err
}
defer out.Close()
_, err = io.Copy(out, src)
paths, err := utils.Unzip(GVAPLUGPINATH+file.Filename, GVAPLUGPINATH)
paths = filterFile(paths)
var webIndex = -1
var serverIndex = -1
webPlugin := ""
serverPlugin := ""
for i := range paths {
paths[i] = filepath.ToSlash(paths[i])
pathArr := strings.Split(paths[i], "/")
ln := len(pathArr)
if ln < 4 {
continue
}
if pathArr[2]+"/"+pathArr[3] == `server/plugin` && len(serverPlugin) == 0 {
serverPlugin = filepath.Join(pathArr[0], pathArr[1], pathArr[2], pathArr[3])
}
if pathArr[2]+"/"+pathArr[3] == `web/plugin` && len(webPlugin) == 0 {
webPlugin = filepath.Join(pathArr[0], pathArr[1], pathArr[2], pathArr[3])
}
}
if len(serverPlugin) == 0 && len(webPlugin) == 0 {
zap.L().Error("非标准插件,请按照文档自动迁移使用")
return webIndex, serverIndex, errors.New("非标准插件,请按照文档自动迁移使用")
}
if len(serverPlugin) != 0 {
err = installation(serverPlugin, global.GVA_CONFIG.AutoCode.Server, global.GVA_CONFIG.AutoCode.Server)
if err != nil {
return webIndex, serverIndex, err
}
}
if len(webPlugin) != 0 {
err = installation(webPlugin, global.GVA_CONFIG.AutoCode.Server, global.GVA_CONFIG.AutoCode.Web)
if err != nil {
return webIndex, serverIndex, err
}
}
return 1, 1, err
}
func installation(path string, formPath string, toPath string) error {
arr := strings.Split(filepath.ToSlash(path), "/")
ln := len(arr)
if ln < 3 {
return errors.New("arr")
}
name := arr[ln-3]
var form = filepath.ToSlash(global.GVA_CONFIG.AutoCode.Root + formPath + "/" + path)
var to = filepath.ToSlash(global.GVA_CONFIG.AutoCode.Root + toPath + "/plugin/")
_, err := os.Stat(to + name)
if err == nil {
zap.L().Error("autoPath 已存在同名插件,请自行手动安装", zap.String("to", to))
return errors.New(toPath + "已存在同名插件,请自行手动安装")
}
return cp.Copy(form, to, cp.Options{Skip: skipMacSpecialDocument})
}
func filterFile(paths []string) []string {
np := make([]string, 0, len(paths))
for _, path := range paths {
if ok, _ := skipMacSpecialDocument(path); ok {
continue
}
np = append(np, path)
}
return np
}
func skipMacSpecialDocument(src string) (bool, error) {
if strings.Contains(src, ".DS_Store") || strings.Contains(src, "__MACOSX") {
return true, nil
}
return false, nil
}
func (s *autoCodePlugin) PubPlug(plugName string) (zipPath string, err error) {
if plugName == "" {
return "", errors.New("插件名称不能为空")
}
// 防止路径穿越
plugName = filepath.Clean(plugName)
webPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Web, "plugin", plugName)
serverPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", plugName)
// 创建一个新的zip文件
// 判断目录是否存在
_, err = os.Stat(webPath)
if err != nil {
return "", errors.New("web路径不存在")
}
_, err = os.Stat(serverPath)
if err != nil {
return "", errors.New("server路径不存在")
}
fileName := plugName + ".zip"
// 创建一个新的zip文件
files, err := archiver.FilesFromDisk(nil, map[string]string{
webPath: plugName + "/web/plugin/" + plugName,
serverPath: plugName + "/server/plugin/" + plugName,
})
// create the output file we'll write to
out, err := os.Create(fileName)
if err != nil {
return
}
defer out.Close()
// we can use the CompressedArchive type to gzip a tarball
// (compression is not required; you could use Tar directly)
format := archiver.CompressedArchive{
Archival: archiver.Zip{},
}
// create the archive
err = format.Archive(context.Background(), out, files)
if err != nil {
return
}
return filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, fileName), nil
}

View File

@@ -0,0 +1,171 @@
package system
import (
"context"
"encoding/json"
"fmt"
"github.com/flipped-aurora/gin-vue-admin/server/global"
model "github.com/flipped-aurora/gin-vue-admin/server/model/system"
"github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
"github.com/flipped-aurora/gin-vue-admin/server/utils/ast"
"github.com/pkg/errors"
"gorm.io/gorm"
"os"
"path/filepath"
"strings"
"text/template"
)
var AutoCodeTemplate = new(autoCodeTemplate)
type autoCodeTemplate struct{}
// Create 创建生成自动化代码
func (s *autoCodeTemplate) Create(ctx context.Context, info request.AutoCode) error {
history := info.History()
var autoPkg model.SysAutoCodePackage
err := global.GVA_DB.WithContext(ctx).Where("package_name = ?", info.Package).First(&autoPkg).Error
if err != nil {
return errors.Wrap(err, "查询包失败!")
}
// 增加判断: 重复创建struct
if AutocodeHistory.Repeat(info.BusinessDB, info.StructName, info.Package) {
return errors.New("已经创建过此数据结构,请勿重复创建!")
}
// 自动创建api
if info.AutoCreateApiToSql {
apis := info.Apis()
err := global.GVA_DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
for _, v := range apis {
var api model.SysApi
err := tx.Where("path = ? AND method = ?", v.Path, v.Method).First(&api).Error
if err == nil {
return errors.New("存在相同的API请关闭自动创建API功能")
}
if errors.Is(err, gorm.ErrRecordNotFound) {
if err = tx.Create(&v).Error; err != nil { // 遇到错误时回滚事务
return err
}
history.ApiIDs = append(history.ApiIDs, v.ID)
}
}
return nil
})
if err != nil {
return err
}
}
// 自动创建menu
if info.AutoCreateMenuToSql {
var entity model.SysBaseMenu
err := global.GVA_DB.WithContext(ctx).First(&entity, "name = ?", info.Abbreviation).Error
if err == nil {
return errors.New("存在相同的菜单路由,请关闭自动创建菜单功能")
}
entity = info.Menu(autoPkg.Template)
err = global.GVA_DB.WithContext(ctx).Create(&entity).Error
if err != nil {
return errors.Wrap(err, "创建菜单失败!")
}
history.MenuID = entity.ID
}
generate, templates, injections, err := s.generate(ctx, info, autoPkg)
if err != nil {
return err
}
for key, builder := range generate {
err = os.MkdirAll(filepath.Dir(key), os.ModePerm)
if err != nil {
return errors.Wrapf(err, "[filepath:%s]创建文件夹失败!", key)
}
err = os.WriteFile(key, []byte(builder.String()), 0666)
if err != nil {
return errors.Wrapf(err, "[filepath:%s]写入文件失败!", key)
}
}
// 创建历史记录
history.Templates = templates
history.Injections = make(map[string]string, len(injections))
for key, value := range injections {
bytes, _ := json.Marshal(value)
history.Injections[key] = string(bytes)
}
err = AutocodeHistory.Create(ctx, history)
if err != nil {
return err
}
return nil
}
// Preview 预览自动化代码
func (s *autoCodeTemplate) Preview(ctx context.Context, info request.AutoCode) (map[string]string, error) {
var entity model.SysAutoCodePackage
err := global.GVA_DB.WithContext(ctx).Where("package_name = ?", info.Package).First(&entity).Error
if err != nil {
return nil, errors.Wrap(err, "查询包失败!")
}
codes := make(map[string]strings.Builder)
preview := make(map[string]string)
codes, _, _, err = s.generate(ctx, info, entity)
if err != nil {
return nil, err
}
for key, writer := range codes {
if len(key) > len(global.GVA_CONFIG.AutoCode.Root) {
key, _ = filepath.Rel(global.GVA_CONFIG.AutoCode.Root, key)
}
var builder strings.Builder
builder.WriteString("```\n\n")
builder.WriteString(writer.String())
builder.WriteString("\n\n```")
preview[key] = builder.String()
}
return preview, nil
}
func (s *autoCodeTemplate) generate(ctx context.Context, info request.AutoCode, entity model.SysAutoCodePackage) (map[string]strings.Builder, map[string]string, map[string]ast.Ast, error) {
templates, asts, _, err := AutoCodePackage.templates(ctx, entity, info)
if err != nil {
return nil, nil, nil, err
}
code := make(map[string]strings.Builder)
for key, create := range templates {
var files *template.Template
files, err = template.ParseFiles(key)
if err != nil {
return nil, nil, nil, errors.Wrapf(err, "[filpath:%s]读取模版文件失败!", key)
}
var builder strings.Builder
err = files.Execute(&builder, info)
if err != nil {
return nil, nil, nil, errors.Wrapf(err, "[filpath:%s]生成文件失败!", create)
}
code[create] = builder
} // 生成文件
injections := make(map[string]ast.Ast, len(asts))
if info.AutoMigrate {
for key, value := range asts {
keys := strings.Split(key, "=>")
if len(keys) == 2 {
var builder strings.Builder
parse, _ := value.Parse("", &builder)
if parse != nil {
_ = value.Injection(parse)
err = value.Format("", &builder, parse)
if err != nil {
return nil, nil, nil, err
}
code[keys[0]] = builder
injections[keys[1]] = value
fmt.Println(keys[0], "注入成功!")
}
}
}
} // 注入代码
return code, templates, injections, nil
}

View File

@@ -0,0 +1,84 @@
package system
import (
"context"
"encoding/json"
"github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
"reflect"
"testing"
)
func Test_autoCodeTemplate_Create(t *testing.T) {
type args struct {
ctx context.Context
info request.AutoCode
}
tests := []struct {
name string
args args
wantErr bool
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &autoCodeTemplate{}
if err := s.Create(tt.args.ctx, tt.args.info); (err != nil) != tt.wantErr {
t.Errorf("Create() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func Test_autoCodeTemplate_Preview(t *testing.T) {
type args struct {
ctx context.Context
info request.AutoCode
}
tests := []struct {
name string
args args
want map[string]string
wantErr bool
}{
{
name: "测试 package",
args: args{
ctx: context.Background(),
info: request.AutoCode{},
},
wantErr: false,
},
{
name: "测试 plugin",
args: args{
ctx: context.Background(),
info: request.AutoCode{},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
testJson := `{"structName":"SysUser","tableName":"sys_users","packageName":"sysUsers","package":"gva","abbreviation":"sysUsers","description":"sysUsers表","businessDB":"","autoCreateApiToSql":true,"autoCreateMenuToSql":true,"autoMigrate":true,"gvaModel":true,"autoCreateResource":false,"fields":[{"fieldName":"Uuid","fieldDesc":"用户UUID","fieldType":"string","dataType":"varchar","fieldJson":"uuid","primaryKey":false,"dataTypeLong":"191","columnName":"uuid","comment":"用户UUID","require":false,"errorText":"","clearable":true,"fieldSearchType":"","fieldIndexType":"","dictType":"","front":true,"dataSource":{"association":1,"table":"","label":"","value":""}},{"fieldName":"Username","fieldDesc":"用户登录名","fieldType":"string","dataType":"varchar","fieldJson":"username","primaryKey":false,"dataTypeLong":"191","columnName":"username","comment":"用户登录名","require":false,"errorText":"","clearable":true,"fieldSearchType":"","fieldIndexType":"","dictType":"","front":true,"dataSource":{"association":1,"table":"","label":"","value":""}},{"fieldName":"Password","fieldDesc":"用户登录密码","fieldType":"string","dataType":"varchar","fieldJson":"password","primaryKey":false,"dataTypeLong":"191","columnName":"password","comment":"用户登录密码","require":false,"errorText":"","clearable":true,"fieldSearchType":"","fieldIndexType":"","dictType":"","front":true,"dataSource":{"association":1,"table":"","label":"","value":""}},{"fieldName":"NickName","fieldDesc":"用户昵称","fieldType":"string","dataType":"varchar","fieldJson":"nickName","primaryKey":false,"dataTypeLong":"191","columnName":"nick_name","comment":"用户昵称","require":false,"errorText":"","clearable":true,"fieldSearchType":"","fieldIndexType":"","dictType":"","front":true,"dataSource":{"association":1,"table":"","label":"","value":""}},{"fieldName":"SideMode","fieldDesc":"用户侧边主题","fieldType":"string","dataType":"varchar","fieldJson":"sideMode","primaryKey":false,"dataTypeLong":"191","columnName":"side_mode","comment":"用户侧边主题","require":false,"errorText":"","clearable":true,"fieldSearchType":"","fieldIndexType":"","dictType":"","front":true,"dataSource":{"association":1,"table":"","label":"","value":""}},{"fieldName":"HeaderImg","fieldDesc":"用户头像","fieldType":"string","dataType":"varchar","fieldJson":"headerImg","primaryKey":false,"dataTypeLong":"191","columnName":"header_img","comment":"用户头像","require":false,"errorText":"","clearable":true,"fieldSearchType":"","fieldIndexType":"","dictType":"","front":true,"dataSource":{"association":1,"table":"","label":"","value":""}},{"fieldName":"BaseColor","fieldDesc":"基础颜色","fieldType":"string","dataType":"varchar","fieldJson":"baseColor","primaryKey":false,"dataTypeLong":"191","columnName":"base_color","comment":"基础颜色","require":false,"errorText":"","clearable":true,"fieldSearchType":"","fieldIndexType":"","dictType":"","front":true,"dataSource":{"association":1,"table":"","label":"","value":""}},{"fieldName":"AuthorityId","fieldDesc":"用户角色ID","fieldType":"int","dataType":"bigint","fieldJson":"authorityId","primaryKey":false,"dataTypeLong":"20","columnName":"authority_id","comment":"用户角色ID","require":false,"errorText":"","clearable":true,"fieldSearchType":"","fieldIndexType":"","dictType":"","front":true,"dataSource":{"association":1,"table":"","label":"","value":""}},{"fieldName":"Phone","fieldDesc":"用户手机号","fieldType":"string","dataType":"varchar","fieldJson":"phone","primaryKey":false,"dataTypeLong":"191","columnName":"phone","comment":"用户手机号","require":false,"errorText":"","clearable":true,"fieldSearchType":"","fieldIndexType":"","dictType":"","front":true,"dataSource":{"association":1,"table":"","label":"","value":""}},{"fieldName":"Email","fieldDesc":"用户邮箱","fieldType":"string","dataType":"varchar","fieldJson":"email","primaryKey":false,"dataTypeLong":"191","columnName":"email","comment":"用户邮箱","require":false,"errorText":"","clearable":true,"fieldSearchType":"","fieldIndexType":"","dictType":"","front":true,"dataSource":{"association":1,"table":"","label":"","value":""}},{"fieldName":"Enable","fieldDesc":"用户是否被冻结 1正常 2冻结","fieldType":"int","dataType":"bigint","fieldJson":"enable","primaryKey":false,"dataTypeLong":"19","columnName":"enable","comment":"用户是否被冻结 1正常 2冻结","require":false,"errorText":"","clearable":true,"fieldSearchType":"","fieldIndexType":"","dictType":"","front":true,"dataSource":{"association":1,"table":"","label":"","value":""}}],"humpPackageName":"sys_users"}`
err := json.Unmarshal([]byte(testJson), &tt.args.info)
if err != nil {
t.Error(err)
return
}
err = tt.args.info.Pretreatment()
if err != nil {
t.Error(err)
return
}
got, err := AutoCodeTemplate.Preview(tt.args.ctx, tt.args.info)
if (err != nil) != tt.wantErr {
t.Errorf("Preview() error = %+v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Preview() got = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -12,9 +12,13 @@ type ServiceGroup struct {
AuthorityService
DictionaryService
SystemConfigService
AutoCodeHistoryService
OperationRecordService
DictionaryDetailService
AuthorityBtnService
SysExportTemplateService
AutoCodePlugin autoCodePlugin
AutoCodePackage autoCodePackage
AutoCodeHistory autoCodeHistory
AutoCodeTemplate autoCodeTemplate
}

View File

@@ -12,6 +12,8 @@ import (
type JwtService struct{}
var JwtServiceApp = new(JwtService)
//@author: [piexlmax](https://github.com/piexlmax)
//@function: JsonInBlacklist
//@description: 拉黑jwt

View File

@@ -215,7 +215,7 @@ func (apiService *ApiService) GetAPIInfoList(api system.SysApi, info request.Pag
//@return: apis []model.SysApi, err error
func (apiService *ApiService) GetAllApis() (apis []system.SysApi, err error) {
err = global.GVA_DB.Find(&apis).Error
err = global.GVA_DB.Order("id desc").Find(&apis).Error
return
}

View File

@@ -11,6 +11,8 @@ import (
type AuthorityBtnService struct{}
var AuthorityBtnServiceApp = new(AuthorityBtnService)
func (a *AuthorityBtnService) GetAuthorityBtn(req request.SysAuthorityBtnReq) (res response.SysAuthorityBtnRes, err error) {
var authorityBtn []system.SysAuthorityBtn
err = global.GVA_DB.Find(&authorityBtn, "authority_id = ? and sys_menu_id = ?", req.AuthorityId, req.MenuID).Error

View File

@@ -1,956 +0,0 @@
package system
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"mime/multipart"
"os"
"path/filepath"
"strconv"
"strings"
"text/template"
"github.com/mholt/archiver/v4"
ast2 "github.com/flipped-aurora/gin-vue-admin/server/utils/ast"
"github.com/flipped-aurora/gin-vue-admin/server/resource/autocode_template/subcontract"
cp "github.com/otiai10/copy"
"go.uber.org/zap"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
"github.com/flipped-aurora/gin-vue-admin/server/utils"
"gorm.io/gorm"
)
const (
autoPath = "autocode_template/"
autocodePath = "resource/autocode_template"
plugServerPath = "resource/plug_template/server"
plugWebPath = "resource/plug_template/web"
packageService = "service/%s/enter.go"
packageServiceName = "service"
packageRouter = "router/%s/enter.go"
packageRouterName = "router"
packageAPI = "api/v1/%s/enter.go"
packageAPIName = "api/v1"
)
type autoPackage struct {
path string
temp string
name string
}
var (
packageInjectionMap map[string]astInjectionMeta
injectionPaths []injectionMeta
)
func Init(Package string) {
injectionPaths = []injectionMeta{
{
path: filepath.Join(global.GVA_CONFIG.AutoCode.Root,
global.GVA_CONFIG.AutoCode.Server, fmt.Sprintf(global.GVA_CONFIG.AutoCode.SApi, Package), "enter.go"),
funcName: "ApiGroup",
structNameF: "%sApi",
},
{
path: filepath.Join(global.GVA_CONFIG.AutoCode.Root,
global.GVA_CONFIG.AutoCode.Server, fmt.Sprintf(global.GVA_CONFIG.AutoCode.SRouter, Package), "enter.go"),
funcName: "RouterGroup",
structNameF: "%sRouter",
},
{
path: filepath.Join(global.GVA_CONFIG.AutoCode.Root,
global.GVA_CONFIG.AutoCode.Server, fmt.Sprintf(global.GVA_CONFIG.AutoCode.SService, Package), "enter.go"),
funcName: "ServiceGroup",
structNameF: "%sService",
},
}
packageInjectionMap = map[string]astInjectionMeta{
packageServiceName: {
path: filepath.Join(global.GVA_CONFIG.AutoCode.Root,
global.GVA_CONFIG.AutoCode.Server, "service", "enter.go"),
importCodeF: "github.com/flipped-aurora/gin-vue-admin/server/%s/%s",
packageNameF: "%s",
groupName: "ServiceGroup",
structNameF: "%sServiceGroup",
},
packageRouterName: {
path: filepath.Join(global.GVA_CONFIG.AutoCode.Root,
global.GVA_CONFIG.AutoCode.Server, "router", "enter.go"),
importCodeF: "github.com/flipped-aurora/gin-vue-admin/server/%s/%s",
packageNameF: "%s",
groupName: "RouterGroup",
structNameF: "%s",
},
packageAPIName: {
path: filepath.Join(global.GVA_CONFIG.AutoCode.Root,
global.GVA_CONFIG.AutoCode.Server, "api/v1", "enter.go"),
importCodeF: "github.com/flipped-aurora/gin-vue-admin/server/%s/%s",
packageNameF: "%s",
groupName: "ApiGroup",
structNameF: "%sApiGroup",
},
}
}
type injectionMeta struct {
path string
funcName string
structNameF string // 带格式化的
}
type astInjectionMeta struct {
path string
importCodeF string
structNameF string
packageNameF string
groupName string
}
type tplData struct {
template *template.Template
autoPackage string
locationPath string
autoCodePath string
autoMoveFilePath string
}
type AutoCodeService struct{}
var AutoCodeServiceApp = new(AutoCodeService)
// @author: [songzhibin97](https://github.com/songzhibin97)
// @function: PreviewTemp
// @description: 预览创建代码
// @param: model.AutoCodeStruct
// @return: map[string]string, error
func (autoCodeService *AutoCodeService) PreviewTemp(autoCode system.AutoCodeStruct) (map[string]string, error) {
fmtField(&autoCode)
dataList, _, needMkdir, err := autoCodeService.getNeedList(&autoCode)
if err != nil {
return nil, err
}
// 写入文件前,先创建文件夹
if err = utils.CreateDir(needMkdir...); err != nil {
return nil, err
}
// 创建map
ret := make(map[string]string)
// 生成map
for _, value := range dataList {
ext := ""
if ext = filepath.Ext(value.autoCodePath); ext == ".txt" {
continue
}
f, err := os.OpenFile(value.autoCodePath, os.O_CREATE|os.O_WRONLY, 0o755)
if err != nil {
return nil, err
}
if err = value.template.Execute(f, autoCode); err != nil {
return nil, err
}
_ = f.Close()
f, err = os.OpenFile(value.autoCodePath, os.O_CREATE|os.O_RDONLY, 0o755)
if err != nil {
return nil, err
}
builder := strings.Builder{}
builder.WriteString("```")
if ext != "" && strings.Contains(ext, ".") {
builder.WriteString(strings.Replace(ext, ".", "", -1))
}
builder.WriteString("\n\n")
data, err := io.ReadAll(f)
if err != nil {
return nil, err
}
builder.Write(data)
builder.WriteString("\n\n```")
pathArr := strings.Split(value.autoCodePath, string(os.PathSeparator))
ret[pathArr[1]+"-"+pathArr[3]] = builder.String()
_ = f.Close()
}
defer func() { // 移除中间文件
if err := os.RemoveAll(autoPath); err != nil {
return
}
}()
return ret, nil
}
func makeDictTypes(autoCode *system.AutoCodeStruct) {
DictTypeM := make(map[string]string)
for _, v := range autoCode.Fields {
if v.DictType != "" {
DictTypeM[v.DictType] = ""
}
}
for k := range DictTypeM {
autoCode.DictTypes = append(autoCode.DictTypes, k)
}
}
// @author: [piexlmax](https://github.com/piexlmax)
// @function: CreateTemp
// @description: 创建代码
// @param: model.AutoCodeStruct
// @return: err error
func (autoCodeService *AutoCodeService) CreateTemp(autoCode system.AutoCodeStruct, menuID uint, ids ...uint) (err error) {
fmtField(&autoCode)
// 增加判断: 重复创建struct
if AutoCodeHistoryServiceApp.Repeat(autoCode.BusinessDB, autoCode.StructName, autoCode.Package) {
return RepeatErr
}
dataList, _, needMkdir, err := autoCodeService.getNeedList(&autoCode)
if err != nil {
return err
}
meta, _ := json.Marshal(autoCode)
// 增加判断Package不为空
if autoCode.Package == "" {
return errors.New("Package为空\n")
}
// 写入文件前,先创建文件夹
if err = utils.CreateDir(needMkdir...); err != nil {
return err
}
// 生成文件
for _, value := range dataList {
f, err := os.OpenFile(value.autoCodePath, os.O_CREATE|os.O_WRONLY, 0o755)
if err != nil {
return err
}
if err = value.template.Execute(f, autoCode); err != nil {
return err
}
_ = f.Close()
}
defer func() { // 移除中间文件
if err := os.RemoveAll(autoPath); err != nil {
return
}
}()
bf := strings.Builder{}
idBf := strings.Builder{}
injectionCodeMeta := strings.Builder{}
for _, id := range ids {
idBf.WriteString(strconv.Itoa(int(id)))
idBf.WriteString(";")
}
Init(autoCode.Package)
for index := range dataList {
autoCodeService.addAutoMoveFile(&dataList[index])
}
// 判断目标文件是否都可以移动
for _, value := range dataList {
if utils.FileExist(value.autoMoveFilePath) {
return errors.New(fmt.Sprintf("目标文件已存在:%s\n", value.autoMoveFilePath))
}
}
for _, value := range dataList { // 移动文件
if err := utils.FileMove(value.autoCodePath, value.autoMoveFilePath); err != nil {
return err
}
}
{
if autoCode.AutoMigrate {
// 在gorm.go 注入 自动迁移
path := filepath.Join(global.GVA_CONFIG.AutoCode.Root,
global.GVA_CONFIG.AutoCode.Server, global.GVA_CONFIG.AutoCode.SInitialize, "gorm_biz.go")
varDB := utils.MaheHump(autoCode.BusinessDB)
ast2.AddRegisterTablesAst(path, "bizModel", autoCode.Package, varDB, autoCode.BusinessDB, autoCode.StructName)
}
}
{
// router.go 注入 自动迁移
path := filepath.Join(global.GVA_CONFIG.AutoCode.Root,
global.GVA_CONFIG.AutoCode.Server, global.GVA_CONFIG.AutoCode.SInitialize, "router_biz.go")
ast2.AddRouterCode(path, "initBizRouter", autoCode.Package, autoCode.StructName)
}
// 给各个enter进行注入
err = injectionCode(autoCode.StructName, &injectionCodeMeta)
if err != nil {
return
}
// 保存生成信息
for _, data := range dataList {
if len(data.autoMoveFilePath) != 0 {
bf.WriteString(data.autoMoveFilePath)
bf.WriteString(";")
}
}
if autoCode.TableName != "" {
err = AutoCodeHistoryServiceApp.CreateAutoCodeHistory(
string(meta),
autoCode.StructName,
autoCode.Description,
bf.String(),
injectionCodeMeta.String(),
autoCode.TableName,
idBf.String(),
autoCode.Package,
autoCode.BusinessDB,
menuID,
)
} else {
err = AutoCodeHistoryServiceApp.CreateAutoCodeHistory(
string(meta),
autoCode.StructName,
autoCode.Description,
bf.String(),
injectionCodeMeta.String(),
autoCode.StructName,
idBf.String(),
autoCode.Package,
autoCode.BusinessDB,
menuID,
)
}
if err != nil {
return err
}
return nil
}
// @author: [piexlmax](https://github.com/piexlmax)
// @function: GetAllTplFile
// @description: 获取 pathName 文件夹下所有 tpl 文件
// @param: pathName string, fileList []string
// @return: []string, error
func (autoCodeService *AutoCodeService) GetAllTplFile(pathName string, fileList []string) ([]string, error) {
files, err := os.ReadDir(pathName)
for _, fi := range files {
if fi.IsDir() {
fileList, err = autoCodeService.GetAllTplFile(pathName+"/"+fi.Name(), fileList)
if err != nil {
return nil, err
}
} else {
if strings.HasSuffix(fi.Name(), ".tpl") {
fileList = append(fileList, pathName+"/"+fi.Name())
}
}
}
return fileList, err
}
// @author: [piexlmax](https://github.com/piexlmax)
// @function: GetDB
// @description: 获取指定数据库和指定数据表的所有字段名,类型值等
// @param: tableName string, dbName string
// @return: err error, Columns []request.ColumnReq
func (autoCodeService *AutoCodeService) DropTable(BusinessDb, tableName string) error {
if BusinessDb != "" {
return global.MustGetGlobalDBByDBName(BusinessDb).Exec("DROP TABLE " + tableName).Error
} else {
return global.GVA_DB.Exec("DROP TABLE " + tableName).Error
}
}
// @author: [SliverHorn](https://github.com/SliverHorn)
// @author: [songzhibin97](https://github.com/songzhibin97)
// @function: addAutoMoveFile
// @description: 生成对应的迁移文件路径
// @param: *tplData
// @return: null
func (autoCodeService *AutoCodeService) addAutoMoveFile(data *tplData) {
base := filepath.Base(data.autoCodePath)
fileSlice := strings.Split(data.autoCodePath, string(os.PathSeparator))
n := len(fileSlice)
if n <= 2 {
return
}
if strings.Contains(fileSlice[1], "server") {
if strings.Contains(fileSlice[n-2], "router") {
data.autoMoveFilePath = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server,
fmt.Sprintf(global.GVA_CONFIG.AutoCode.SRouter, data.autoPackage), base)
} else if strings.Contains(fileSlice[n-2], "api") {
data.autoMoveFilePath = filepath.Join(global.GVA_CONFIG.AutoCode.Root,
global.GVA_CONFIG.AutoCode.Server, fmt.Sprintf(global.GVA_CONFIG.AutoCode.SApi, data.autoPackage), base)
} else if strings.Contains(fileSlice[n-2], "service") {
data.autoMoveFilePath = filepath.Join(global.GVA_CONFIG.AutoCode.Root,
global.GVA_CONFIG.AutoCode.Server, fmt.Sprintf(global.GVA_CONFIG.AutoCode.SService, data.autoPackage), base)
} else if strings.Contains(fileSlice[n-2], "model") {
data.autoMoveFilePath = filepath.Join(global.GVA_CONFIG.AutoCode.Root,
global.GVA_CONFIG.AutoCode.Server, fmt.Sprintf(global.GVA_CONFIG.AutoCode.SModel, data.autoPackage), base)
} else if strings.Contains(fileSlice[n-2], "request") {
data.autoMoveFilePath = filepath.Join(global.GVA_CONFIG.AutoCode.Root,
global.GVA_CONFIG.AutoCode.Server, fmt.Sprintf(global.GVA_CONFIG.AutoCode.SRequest, data.autoPackage), base)
}
} else if strings.Contains(fileSlice[1], "web") {
if strings.Contains(fileSlice[n-1], "js") {
data.autoMoveFilePath = filepath.Join(global.GVA_CONFIG.AutoCode.Root,
global.GVA_CONFIG.AutoCode.Web, global.GVA_CONFIG.AutoCode.WApi, data.autoPackage, base)
} else if strings.Contains(fileSlice[n-2], "form") {
data.autoMoveFilePath = filepath.Join(global.GVA_CONFIG.AutoCode.Root,
global.GVA_CONFIG.AutoCode.Web, global.GVA_CONFIG.AutoCode.WForm, data.autoPackage, filepath.Base(filepath.Dir(filepath.Dir(data.autoCodePath))), strings.TrimSuffix(base, filepath.Ext(base))+"Form.vue")
} else if strings.Contains(fileSlice[n-2], "table") {
data.autoMoveFilePath = filepath.Join(global.GVA_CONFIG.AutoCode.Root,
global.GVA_CONFIG.AutoCode.Web, global.GVA_CONFIG.AutoCode.WTable, data.autoPackage, filepath.Base(filepath.Dir(filepath.Dir(data.autoCodePath))), base)
}
}
}
// @author: [piexlmax](https://github.com/piexlmax)
// @author: [SliverHorn](https://github.com/SliverHorn)
// @function: CreateApi
// @description: 自动创建api数据,
// @param: a *model.AutoCodeStruct
// @return: err error
func (autoCodeService *AutoCodeService) AutoCreateApi(a *system.AutoCodeStruct) (ids []uint, err error) {
apiList := []system.SysApi{
{
Path: "/" + a.Abbreviation + "/" + "create" + a.StructName,
Description: "新增" + a.Description,
ApiGroup: a.Description,
Method: "POST",
},
{
Path: "/" + a.Abbreviation + "/" + "delete" + a.StructName,
Description: "删除" + a.Description,
ApiGroup: a.Description,
Method: "DELETE",
},
{
Path: "/" + a.Abbreviation + "/" + "delete" + a.StructName + "ByIds",
Description: "批量删除" + a.Description,
ApiGroup: a.Description,
Method: "DELETE",
},
{
Path: "/" + a.Abbreviation + "/" + "update" + a.StructName,
Description: "更新" + a.Description,
ApiGroup: a.Description,
Method: "PUT",
},
{
Path: "/" + a.Abbreviation + "/" + "find" + a.StructName,
Description: "根据ID获取" + a.Description,
ApiGroup: a.Description,
Method: "GET",
},
{
Path: "/" + a.Abbreviation + "/" + "get" + a.StructName + "List",
Description: "获取" + a.Description + "列表",
ApiGroup: a.Description,
Method: "GET",
},
}
err = global.GVA_DB.Transaction(func(tx *gorm.DB) error {
for _, v := range apiList {
var api system.SysApi
findErr := tx.Where("path = ? AND method = ?", v.Path, v.Method).First(&api).Error
if findErr == nil {
return errors.New("存在相同的API请关闭自动创建API功能")
}
if errors.Is(findErr, gorm.ErrRecordNotFound) {
if err = tx.Create(&v).Error; err != nil { // 遇到错误时回滚事务
return err
} else {
ids = append(ids, v.ID)
}
}
}
return nil
})
return ids, err
}
func (autoCodeService *AutoCodeService) AutoCreateMenu(a *system.AutoCodeStruct) (id uint, err error) {
var menu system.SysBaseMenu
err = global.GVA_DB.First(&menu, "name = ?", a.Abbreviation).Error
if err == nil {
return 0, errors.New("存在相同的菜单路由,请关闭自动创建菜单功能")
}
menu.ParentId = 0
menu.Name = a.Abbreviation
menu.Path = a.Abbreviation
menu.Meta.Title = a.Description
menu.Component = fmt.Sprintf("view/%s/%s/%s.vue", a.Package, a.PackageName, a.PackageName)
err = global.GVA_DB.Create(&menu).Error
return menu.ID, err
}
func (autoCodeService *AutoCodeService) getNeedList(autoCode *system.AutoCodeStruct) (dataList []tplData, fileList []string, needMkdir []string, err error) {
// 去除所有空格
utils.TrimSpace(autoCode)
for _, field := range autoCode.Fields {
utils.TrimSpace(field)
}
// 获取 basePath 文件夹下所有tpl文件
tplFileList, err := autoCodeService.GetAllTplFile(autocodePath, nil)
if err != nil {
return nil, nil, nil, err
}
dataList = make([]tplData, 0, len(tplFileList))
fileList = make([]string, 0, len(tplFileList))
needMkdir = make([]string, 0, len(tplFileList)) // 当文件夹下存在多个tpl文件时改为map更合理
// 根据文件路径生成 tplData 结构体,待填充数据
for _, value := range tplFileList {
dataList = append(dataList, tplData{locationPath: value, autoPackage: autoCode.Package})
}
// 生成 *Template, 填充 template 字段
for index, value := range dataList {
dataList[index].template, err = template.ParseFiles(value.locationPath)
if err != nil {
return nil, nil, nil, err
}
}
// 生成文件路径,填充 autoCodePath 字段readme.txt.tpl不符合规则需要特殊处理
// resource/template/web/api.js.tpl -> autoCode/web/autoCode.PackageName/api/autoCode.PackageName.js
// resource/template/readme.txt.tpl -> autoCode/readme.txt
for index, value := range dataList {
trimBase := strings.TrimPrefix(value.locationPath, autocodePath+"/")
if trimBase == "readme.txt.tpl" {
dataList[index].autoCodePath = autoPath + "readme.txt"
continue
}
if lastSeparator := strings.LastIndex(trimBase, "/"); lastSeparator != -1 {
origFileName := strings.TrimSuffix(trimBase[lastSeparator+1:], ".tpl")
firstDot := strings.Index(origFileName, ".")
if firstDot != -1 {
var fileName string
if origFileName[firstDot:] != ".go" {
fileName = autoCode.PackageName + origFileName[firstDot:]
} else {
fileName = autoCode.HumpPackageName + origFileName[firstDot:]
}
dataList[index].autoCodePath = filepath.Join(autoPath, trimBase[:lastSeparator], autoCode.PackageName,
origFileName[:firstDot], fileName)
}
}
if lastSeparator := strings.LastIndex(dataList[index].autoCodePath, string(os.PathSeparator)); lastSeparator != -1 {
needMkdir = append(needMkdir, dataList[index].autoCodePath[:lastSeparator])
}
}
for _, value := range dataList {
fileList = append(fileList, value.autoCodePath)
}
return dataList, fileList, needMkdir, err
}
// injectionCode 封装代码注入
func injectionCode(structName string, bf *strings.Builder) error {
for _, meta := range injectionPaths {
code := fmt.Sprintf(meta.structNameF, structName)
ast2.ImportForAutoEnter(meta.path, meta.funcName, code)
bf.WriteString(fmt.Sprintf("%s@%s@%s;", meta.path, meta.funcName, code))
}
return nil
}
func (autoCodeService *AutoCodeService) CreateAutoCode(s *system.SysAutoCode) error {
if s.PackageName == "autocode" || s.PackageName == "system" || s.PackageName == "example" || s.PackageName == "" {
return errors.New("不能使用已保留的package name")
}
if !errors.Is(global.GVA_DB.Where("package_name = ?", s.PackageName).First(&system.SysAutoCode{}).Error, gorm.ErrRecordNotFound) {
return errors.New("存在相同PackageName")
}
if e := autoCodeService.CreatePackageTemp(s.PackageName); e != nil {
return e
}
return global.GVA_DB.Create(&s).Error
}
func (autoCodeService *AutoCodeService) GetPackage() (pkgList []system.SysAutoCode, err error) {
err = global.GVA_DB.Find(&pkgList).Error
return pkgList, err
}
func (autoCodeService *AutoCodeService) DelPackage(a system.SysAutoCode) error {
return global.GVA_DB.Delete(&a).Error
}
func (autoCodeService *AutoCodeService) CreatePackageTemp(packageName string) error {
Init(packageName)
pendingTemp := []autoPackage{{
path: packageService,
name: packageServiceName,
temp: string(subcontract.Server),
}, {
path: packageRouter,
name: packageRouterName,
temp: string(subcontract.Router),
}, {
path: packageAPI,
name: packageAPIName,
temp: string(subcontract.API),
}}
webTemp := []string{
filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Web, global.GVA_CONFIG.AutoCode.WApi),
filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Web, global.GVA_CONFIG.AutoCode.WForm),
}
for _, s := range webTemp {
err := os.MkdirAll(filepath.Join(s, packageName), 0755)
if err != nil {
return err
}
}
for i, s := range pendingTemp {
pendingTemp[i].path = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, filepath.Clean(fmt.Sprintf(s.path, packageName)))
}
// 选择模板
for _, s := range pendingTemp {
err := os.MkdirAll(filepath.Dir(s.path), 0755)
if err != nil {
return err
}
f, err := os.Create(s.path)
if err != nil {
return err
}
defer f.Close()
temp, err := template.New("").Parse(s.temp)
if err != nil {
return err
}
err = temp.Execute(f, struct {
PackageName string `json:"package_name"`
}{packageName})
if err != nil {
return err
}
}
// 创建完成后在对应的位置插入结构代码
for _, v := range pendingTemp {
meta := packageInjectionMap[v.name]
if err := ast2.ImportReference(meta.path, fmt.Sprintf(meta.importCodeF, v.name, packageName), fmt.Sprintf(meta.structNameF, utils.FirstUpper(packageName)), fmt.Sprintf(meta.packageNameF, packageName), meta.groupName); err != nil {
return err
}
}
return nil
}
// CreatePlug 自动创建插件模板
func (autoCodeService *AutoCodeService) CreatePlug(plug system.AutoPlugReq) error {
// 检查列表参数是否有效
plug.CheckList()
err := autoCodeService.createPluginServer(plug)
if err != nil {
return err
}
err = autoCodeService.createPluginWeb(plug)
if err != nil {
return err
}
return nil
}
func (autoCodeService *AutoCodeService) createPluginServer(plug system.AutoPlugReq) error {
tplFileList, _ := autoCodeService.GetAllTplFile(plugServerPath, nil)
for _, tpl := range tplFileList {
temp, err := template.ParseFiles(tpl)
if err != nil {
zap.L().Error("parse err", zap.String("tpl", tpl), zap.Error(err))
return err
}
pathArr := strings.SplitAfter(tpl, "/")
if strings.Index(pathArr[3], "tpl") < 0 {
dirPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, fmt.Sprintf(global.GVA_CONFIG.AutoCode.SPlug, plug.Snake+"/"+pathArr[3]))
os.MkdirAll(dirPath, 0755)
}
file := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, fmt.Sprintf(global.GVA_CONFIG.AutoCode.SPlug, plug.Snake+"/"+tpl[len(plugServerPath):len(tpl)-4]))
f, err := os.OpenFile(file, os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
zap.L().Error("open file", zap.String("tpl", tpl), zap.Error(err), zap.Any("plug", plug))
return err
}
defer f.Close()
err = temp.Execute(f, plug)
if err != nil {
zap.L().Error("exec err", zap.String("tpl", tpl), zap.Error(err), zap.Any("plug", plug))
return err
}
}
return nil
}
func (autoCodeService *AutoCodeService) createPluginWeb(plug system.AutoPlugReq) error {
tplFileList, _ := autoCodeService.GetAllTplFile(plugWebPath, nil)
for _, tpl := range tplFileList {
temp, err := template.ParseFiles(tpl)
if err != nil {
zap.L().Error("parse err", zap.String("tpl", tpl), zap.Error(err))
return err
}
pathArr := strings.SplitAfter(tpl, "/")
if strings.Index(pathArr[3], "tpl") < 0 {
dirPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Web, fmt.Sprintf(global.GVA_CONFIG.AutoCode.SPlug, plug.Snake+"/"+pathArr[3]))
os.MkdirAll(dirPath, 0755)
}
file := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Web, fmt.Sprintf(global.GVA_CONFIG.AutoCode.SPlug, plug.Snake+"/"+tpl[len(plugWebPath):len(tpl)-4]))
f, err := os.OpenFile(file, os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
zap.L().Error("open file", zap.String("tpl", tpl), zap.Error(err), zap.Any("plug", plug))
return err
}
defer f.Close()
err = temp.Execute(f, plug)
if err != nil {
zap.L().Error("exec err", zap.String("tpl", tpl), zap.Error(err), zap.Any("plug", plug))
return err
}
}
return nil
}
func (autoCodeService *AutoCodeService) InstallPlugin(file *multipart.FileHeader) (web, server int, err error) {
const GVAPLUGPINATH = "./gva-plug-temp/"
defer os.RemoveAll(GVAPLUGPINATH)
_, err = os.Stat(GVAPLUGPINATH)
if os.IsNotExist(err) {
os.Mkdir(GVAPLUGPINATH, os.ModePerm)
}
src, err := file.Open()
if err != nil {
return -1, -1, err
}
defer src.Close()
out, err := os.Create(GVAPLUGPINATH + file.Filename)
if err != nil {
return -1, -1, err
}
defer out.Close()
_, err = io.Copy(out, src)
paths, err := utils.Unzip(GVAPLUGPINATH+file.Filename, GVAPLUGPINATH)
paths = filterFile(paths)
var webIndex = -1
var serverIndex = -1
webPlugin := ""
serverPlugin := ""
for i := range paths {
paths[i] = filepath.ToSlash(paths[i])
pathArr := strings.Split(paths[i], "/")
ln := len(pathArr)
if ln < 4 {
continue
}
if pathArr[2]+"/"+pathArr[3] == `server/plugin` && len(serverPlugin) == 0 {
serverPlugin = filepath.Join(pathArr[0], pathArr[1], pathArr[2], pathArr[3])
}
if pathArr[2]+"/"+pathArr[3] == `web/plugin` && len(webPlugin) == 0 {
webPlugin = filepath.Join(pathArr[0], pathArr[1], pathArr[2], pathArr[3])
}
}
if len(serverPlugin) == 0 && len(webPlugin) == 0 {
zap.L().Error("非标准插件,请按照文档自动迁移使用")
return webIndex, serverIndex, errors.New("非标准插件,请按照文档自动迁移使用")
}
if len(serverPlugin) != 0 {
err = installation(serverPlugin, global.GVA_CONFIG.AutoCode.Server, global.GVA_CONFIG.AutoCode.Server)
if err != nil {
return webIndex, serverIndex, err
}
}
if len(webPlugin) != 0 {
err = installation(webPlugin, global.GVA_CONFIG.AutoCode.Server, global.GVA_CONFIG.AutoCode.Web)
if err != nil {
return webIndex, serverIndex, err
}
}
return 1, 1, err
}
func installation(path string, formPath string, toPath string) error {
arr := strings.Split(filepath.ToSlash(path), "/")
ln := len(arr)
if ln < 3 {
return errors.New("arr")
}
name := arr[ln-3]
var form = filepath.ToSlash(global.GVA_CONFIG.AutoCode.Root + formPath + "/" + path)
var to = filepath.ToSlash(global.GVA_CONFIG.AutoCode.Root + toPath + "/plugin/")
_, err := os.Stat(to + name)
if err == nil {
zap.L().Error("autoPath 已存在同名插件,请自行手动安装", zap.String("to", to))
return errors.New(toPath + "已存在同名插件,请自行手动安装")
}
return cp.Copy(form, to, cp.Options{Skip: skipMacSpecialDocument})
}
func filterFile(paths []string) []string {
np := make([]string, 0, len(paths))
for _, path := range paths {
if ok, _ := skipMacSpecialDocument(path); ok {
continue
}
np = append(np, path)
}
return np
}
func skipMacSpecialDocument(src string) (bool, error) {
if strings.Contains(src, ".DS_Store") || strings.Contains(src, "__MACOSX") {
return true, nil
}
return false, nil
}
func (autoCodeService *AutoCodeService) PubPlug(plugName string) (zipPath string, err error) {
if plugName == "" {
return "", errors.New("插件名称不能为空")
}
// 防止路径穿越
plugName = filepath.Clean(plugName)
webPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Web, "plugin", plugName)
serverPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", plugName)
// 创建一个新的zip文件
// 判断目录是否存在
_, err = os.Stat(webPath)
if err != nil {
return "", errors.New("web路径不存在")
}
_, err = os.Stat(serverPath)
if err != nil {
return "", errors.New("server路径不存在")
}
fileName := plugName + ".zip"
// 创建一个新的zip文件
files, err := archiver.FilesFromDisk(nil, map[string]string{
webPath: plugName + "/web/plugin/" + plugName,
serverPath: plugName + "/server/plugin/" + plugName,
})
// create the output file we'll write to
out, err := os.Create(fileName)
if err != nil {
return
}
defer out.Close()
// we can use the CompressedArchive type to gzip a tarball
// (compression is not required; you could use Tar directly)
format := archiver.CompressedArchive{
Archival: archiver.Zip{},
}
// create the archive
err = format.Archive(context.Background(), out, files)
if err != nil {
return
}
return filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, fileName), nil
}
func fmtField(autoCode *system.AutoCodeStruct) {
makeDictTypes(autoCode)
autoCode.DataSourceMap = make(map[string]*system.DataSource)
for i := range autoCode.Fields {
if autoCode.Fields[i].Front {
autoCode.FrontFields = append(autoCode.FrontFields, autoCode.Fields[i])
}
if autoCode.Fields[i].FieldType == "time.Time" {
autoCode.HasTimer = true
if autoCode.Fields[i].FieldSearchType != "" {
autoCode.HasSearchTimer = true
}
}
if autoCode.Fields[i].Sort {
autoCode.NeedSort = true
}
if autoCode.Fields[i].FieldType == "picture" {
autoCode.HasPic = true
}
if autoCode.Fields[i].FieldType == "video" {
autoCode.HasPic = true
}
if autoCode.Fields[i].FieldType == "richtext" {
autoCode.HasRichText = true
}
if autoCode.Fields[i].FieldType == "pictures" {
autoCode.HasPic = true
autoCode.NeedJSON = true
}
if autoCode.Fields[i].FieldType == "json" {
autoCode.NeedJSON = true
}
if autoCode.Fields[i].FieldType == "array" {
autoCode.NeedJSON = true
}
if autoCode.Fields[i].FieldType == "file" {
autoCode.HasFile = true
autoCode.NeedJSON = true
}
if autoCode.Fields[i].DataSource != nil &&
autoCode.Fields[i].DataSource.Table != "" &&
autoCode.Fields[i].DataSource.Label != "" &&
autoCode.Fields[i].DataSource.Value != "" {
autoCode.HasDataSource = true
autoCode.DataSourceMap[autoCode.Fields[i].FieldJson] = autoCode.Fields[i].DataSource
autoCode.Fields[i].CheckDataSource = true
}
if autoCode.GvaModel {
autoCode.PrimaryField = &system.Field{
FieldName: "ID",
FieldType: "uint",
FieldDesc: "ID",
FieldJson: "ID",
DataTypeLong: "20",
Comment: "主键ID",
ColumnName: "id",
}
}
if !autoCode.GvaModel && autoCode.PrimaryField == nil && autoCode.Fields[i].PrimaryKey {
autoCode.PrimaryField = autoCode.Fields[i]
}
}
}

View File

@@ -5,6 +5,8 @@ import (
"github.com/flipped-aurora/gin-vue-admin/server/model/system/response"
)
type AutoCodeService struct{}
type Database interface {
GetDB(businessDB string) (data []response.Db, err error)
GetTables(businessDB string, dbName string) (data []response.Table, err error)

View File

@@ -1,165 +0,0 @@
package system
import (
"errors"
"fmt"
"path/filepath"
"strconv"
"strings"
"time"
systemReq "github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
"github.com/flipped-aurora/gin-vue-admin/server/utils/ast"
"github.com/flipped-aurora/gin-vue-admin/server/model/system/response"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/common/request"
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
"github.com/flipped-aurora/gin-vue-admin/server/utils"
"go.uber.org/zap"
)
var RepeatErr = errors.New("重复创建")
type AutoCodeHistoryService struct{}
var AutoCodeHistoryServiceApp = new(AutoCodeHistoryService)
// CreateAutoCodeHistory 创建代码生成器历史记录
// RouterPath : RouterPath@RouterString;RouterPath2@RouterString2
// Author [SliverHorn](https://github.com/SliverHorn)
// Author [songzhibin97](https://github.com/songzhibin97)
func (autoCodeHistoryService *AutoCodeHistoryService) CreateAutoCodeHistory(meta, structName, structCNName, autoCodePath string, injectionMeta string, tableName string, apiIds string, Package string, BusinessDB string, menuID uint) error {
return global.GVA_DB.Create(&system.SysAutoCodeHistory{
Package: Package,
RequestMeta: meta,
AutoCodePath: autoCodePath,
InjectionMeta: injectionMeta,
StructName: structName,
StructCNName: structCNName,
TableName: tableName,
ApiIDs: apiIds,
BusinessDB: BusinessDB,
MenuID: menuID,
}).Error
}
// First 根据id获取代码生成器历史的数据
// Author [SliverHorn](https://github.com/SliverHorn)
// Author [songzhibin97](https://github.com/songzhibin97)
func (autoCodeHistoryService *AutoCodeHistoryService) First(info *request.GetById) (string, error) {
var meta string
return meta, global.GVA_DB.Model(system.SysAutoCodeHistory{}).Select("request_meta").Where("id = ?", info.Uint()).First(&meta).Error
}
// Repeat 检测重复
// Author [SliverHorn](https://github.com/SliverHorn)
// Author [songzhibin97](https://github.com/songzhibin97)
func (autoCodeHistoryService *AutoCodeHistoryService) Repeat(businessDB, structName, Package string) bool {
var count int64
global.GVA_DB.Model(&system.SysAutoCodeHistory{}).Where("business_db = ? and struct_name = ? and package = ? and flag = 0", businessDB, structName, Package).Count(&count)
return count > 0
}
// RollBack 回滚
// Author [SliverHorn](https://github.com/SliverHorn)
// Author [songzhibin97](https://github.com/songzhibin97)
func (autoCodeHistoryService *AutoCodeHistoryService) RollBack(info *systemReq.RollBack) error {
md := system.SysAutoCodeHistory{}
if err := global.GVA_DB.Where("id = ?", info.ID).First(&md).Error; err != nil {
return err
}
// 清除API表
var err error
if info.DeleteApi {
ids := request.IdsReq{}
idsStr := strings.Split(md.ApiIDs, ";")
for i := range idsStr[0 : len(idsStr)-1] {
id, err := strconv.Atoi(idsStr[i])
if err != nil {
return err
}
ids.Ids = append(ids.Ids, id)
}
err = ApiServiceApp.DeleteApisByIds(ids)
if err != nil {
global.GVA_LOG.Error("ClearTag DeleteApiByIds:", zap.Error(err))
}
}
// 清除菜单表
if info.DeleteMenu {
err = BaseMenuServiceApp.DeleteBaseMenu(int(md.MenuID))
if err != nil {
global.GVA_LOG.Error("ClearTag DeleteBaseMenu:", zap.Error(err))
}
}
// 删除表
if info.DeleteTable {
if err = AutoCodeServiceApp.DropTable(md.BusinessDB, md.TableName); err != nil {
global.GVA_LOG.Error("ClearTag DropTable:", zap.Error(err))
}
}
// 删除文件
for _, path := range strings.Split(md.AutoCodePath, ";") {
// 增加安全判断补丁:
_path, err := filepath.Abs(path)
if err != nil || _path != path {
continue
}
// 迁移
nPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root,
"rm_file", time.Now().Format("20060102"), filepath.Base(filepath.Dir(filepath.Dir(path))), filepath.Base(filepath.Dir(path)), filepath.Base(path))
// 判断目标文件是否存在
for utils.FileExist(nPath) {
fmt.Println("文件已存在:", nPath)
nPath += fmt.Sprintf("_%d", time.Now().Nanosecond())
}
err = utils.FileMove(path, nPath)
if err != nil {
global.GVA_LOG.Error("file move err ", zap.Error(err))
}
//_ = utils.DeLFile(path)
}
// 清除注入
for _, v := range strings.Split(md.InjectionMeta, ";") {
// RouterPath@functionName@RouterString
meta := strings.Split(v, "@")
if len(meta) == 3 {
_ = utils.AutoClearCode(meta[0], meta[2])
}
}
ast.RollBackAst(md.Package, md.StructName)
md.Flag = 1
return global.GVA_DB.Save(&md).Error
}
// Delete 删除历史数据
// Author [SliverHorn](https://github.com/SliverHorn)
// Author [songzhibin97](https://github.com/songzhibin97)
func (autoCodeHistoryService *AutoCodeHistoryService) Delete(info *request.GetById) error {
return global.GVA_DB.Where("id = ?", info.Uint()).Delete(&system.SysAutoCodeHistory{}).Error
}
// GetList 获取系统历史数据
// Author [SliverHorn](https://github.com/SliverHorn)
// Author [songzhibin97](https://github.com/songzhibin97)
func (autoCodeHistoryService *AutoCodeHistoryService) GetList(info request.PageInfo) (list []response.AutoCodeHistory, total int64, err error) {
limit := info.PageSize
offset := info.PageSize * (info.Page - 1)
db := global.GVA_DB.Model(&system.SysAutoCodeHistory{})
var entities []response.AutoCodeHistory
err = db.Count(&total).Error
if err != nil {
return nil, total, err
}
err = db.Limit(limit).Offset(offset).Order("updated_at desc").Find(&entities).Error
return entities, total, err
}

View File

@@ -16,6 +16,8 @@ import (
type DictionaryService struct{}
var DictionaryServiceApp = new(DictionaryService)
func (dictionaryService *DictionaryService) CreateSysDictionary(sysDictionary system.SysDictionary) (err error) {
if (!errors.Is(global.GVA_DB.First(&system.SysDictionary{}, "type = ?", sysDictionary.Type).Error, gorm.ErrRecordNotFound)) {
return errors.New("存在相同的type不允许创建")

View File

@@ -14,6 +14,8 @@ import (
type DictionaryDetailService struct{}
var DictionaryDetailServiceApp = new(DictionaryDetailService)
func (dictionaryDetailService *DictionaryDetailService) CreateSysDictionaryDetail(sysDictionaryDetail system.SysDictionaryDetail) (err error) {
err = global.GVA_DB.Create(&sysDictionaryDetail).Error
return err

View File

@@ -21,6 +21,8 @@ import (
type SysExportTemplateService struct {
}
var SysExportTemplateServiceApp = new(SysExportTemplateService)
// CreateSysExportTemplate 创建导出模板记录
// Author [piexlmax](https://github.com/piexlmax)
func (sysExportTemplateService *SysExportTemplateService) CreateSysExportTemplate(sysExportTemplate *system.SysExportTemplate) (err error) {

View File

@@ -3,15 +3,16 @@ package system
import (
"context"
"errors"
"github.com/flipped-aurora/gin-vue-admin/server/config"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
"github.com/flipped-aurora/gin-vue-admin/server/utils"
"github.com/glebarez/sqlite"
"github.com/gofrs/uuid/v5"
"github.com/gookit/color"
"gorm.io/gorm"
"path/filepath"
"github.com/flipped-aurora/gin-vue-admin/server/config"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
"github.com/flipped-aurora/gin-vue-admin/server/utils"
)
type SqliteInitHandler struct{}
@@ -24,7 +25,7 @@ func NewSqliteInitHandler() *SqliteInitHandler {
func (h SqliteInitHandler) WriteConfig(ctx context.Context) error {
c, ok := ctx.Value("config").(config.Sqlite)
if !ok {
return errors.New("mysql config invalid")
return errors.New("sqlite config invalid")
}
global.GVA_CONFIG.System.DbType = "sqlite"
global.GVA_CONFIG.Sqlite = c

View File

@@ -15,6 +15,8 @@ import (
type OperationRecordService struct{}
var OperationRecordServiceApp = new(OperationRecordService)
func (operationRecordService *OperationRecordService) CreateSysOperationRecord(sysOperationRecord system.SysOperationRecord) (err error) {
err = global.GVA_DB.Create(&sysOperationRecord).Error
return err

View File

@@ -15,6 +15,8 @@ import (
type SystemConfigService struct{}
var SystemConfigServiceApp = new(SystemConfigService)
func (systemConfigService *SystemConfigService) GetSystemConfig() (conf config.Server, err error) {
return global.GVA_CONFIG, nil
}

View File

@@ -21,6 +21,8 @@ import (
type UserService struct{}
var UserServiceApp = new(UserService)
func (userService *UserService) Register(u system.SysUser) (userInter system.SysUser, err error) {
var user system.SysUser
if !errors.Is(global.GVA_DB.Where("username = ?", u.Username).First(&user).Error, gorm.ErrRecordNotFound) { // 判断用户名是否注册