sys_auto_code.go 15.8 KB
Newer Older
1
package system
2 3

import (
4
	"encoding/json"
m0_50812349's avatar
m0_50812349 已提交
5
	"errors"
S
songzhibin97 已提交
6
	"fmt"
7
	"io/ioutil"
8
	"os"
m0_50812349's avatar
m0_50812349 已提交
9
	"path/filepath"
S
songzhibin97 已提交
10
	"strconv"
11
	"strings"
12
	"text/template"
13

14 15 16 17
	"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/model/system/request"
	"github.com/flipped-aurora/gin-vue-admin/server/utils"
S
songzhibin97 已提交
18

19
	"gorm.io/gorm"
20 21
)

22
const (
23
	autoPath = "autocode_template/"
24 25 26
	basePath = "resource/template"
)

S
songzhibin97 已提交
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
var injectionPaths []injectionMeta

func Init() {
	if len(injectionPaths) != 0 {
		return
	}
	injectionPaths = []injectionMeta{
		{
			path: filepath.Join(global.GVA_CONFIG.AutoCode.Root,
				global.GVA_CONFIG.AutoCode.Server, global.GVA_CONFIG.AutoCode.SInitialize, "gorm.go"),
			funcName:    "MysqlTables",
			structNameF: "autocode.%s{},",
		},
		{
			path: filepath.Join(global.GVA_CONFIG.AutoCode.Root,
				global.GVA_CONFIG.AutoCode.Server, global.GVA_CONFIG.AutoCode.SInitialize, "router.go"),
			funcName:    "Routers",
			structNameF: "autocodeRouter.Init%sRouter(PrivateGroup)",
		},
		{
			path: filepath.Join(global.GVA_CONFIG.AutoCode.Root,
				global.GVA_CONFIG.AutoCode.Server, global.GVA_CONFIG.AutoCode.SApi, "enter.go"),
			funcName:    "ApiGroup",
			structNameF: "%sApi",
		},
		{
			path: filepath.Join(global.GVA_CONFIG.AutoCode.Root,
				global.GVA_CONFIG.AutoCode.Server, global.GVA_CONFIG.AutoCode.SRouter, "enter.go"),
			funcName:    "RouterGroup",
			structNameF: "%sRouter",
		},
		{
			path: filepath.Join(global.GVA_CONFIG.AutoCode.Root,
				global.GVA_CONFIG.AutoCode.Server, global.GVA_CONFIG.AutoCode.SService, "enter.go"),
			funcName:    "ServiceGroup",
			structNameF: "%sService",
		},
	}
}

type injectionMeta struct {
	path        string
	funcName    string
	structNameF string // 带格式化的
}

73
type tplData struct {
m0_50812349's avatar
m0_50812349 已提交
74 75 76 77
	template         *template.Template
	locationPath     string
	autoCodePath     string
	autoMoveFilePath string
78 79
}

80 81 82 83 84
type AutoCodeService struct {
}

var AutoCodeServiceApp = new(AutoCodeService)

85 86 87 88 89 90
//@author: [songzhibin97](https://github.com/songzhibin97)
//@function: PreviewTemp
//@description: 预览创建代码
//@param: model.AutoCodeStruct
//@return: map[string]string, error

91 92
func (autoCodeService *AutoCodeService) PreviewTemp(autoCode system.AutoCodeStruct) (map[string]string, error) {
	dataList, _, needMkdir, err := autoCodeService.getNeedList(&autoCode)
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
	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, 0755)
		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, 0755)
		if err != nil {
			return nil, err
		}
		builder := strings.Builder{}
Mr.奇淼('s avatar
Mr.奇淼( 已提交
124
		builder.WriteString("```")
125

Mr.奇淼('s avatar
Mr.奇淼( 已提交
126 127 128 129
		if ext != "" && strings.Contains(ext, ".") {
			builder.WriteString(strings.Replace(ext, ".", "", -1))
		}
		builder.WriteString("\n\n")
130 131 132 133 134
		data, err := ioutil.ReadAll(f)
		if err != nil {
			return nil, err
		}
		builder.Write(data)
Mr.奇淼('s avatar
Mr.奇淼( 已提交
135
		builder.WriteString("\n\n```")
136

137
		pathArr := strings.Split(value.autoCodePath, string(os.PathSeparator))
Mr.奇淼('s avatar
Mr.奇淼( 已提交
138
		ret[pathArr[1]+"-"+pathArr[3]] = builder.String()
139 140 141 142 143 144 145 146 147 148 149
		_ = f.Close()

	}
	defer func() { // 移除中间文件
		if err := os.RemoveAll(autoPath); err != nil {
			return
		}
	}()
	return ret, nil
}

m0_50812349's avatar
m0_50812349 已提交
150 151 152 153
//@author: [piexlmax](https://github.com/piexlmax)
//@function: CreateTemp
//@description: 创建代码
//@param: model.AutoCodeStruct
何秀钢 已提交
154
//@return: err error
Mr.奇淼('s avatar
Mr.奇淼( 已提交
155

156
func (autoCodeService *AutoCodeService) CreateTemp(autoCode system.AutoCodeStruct, ids ...uint) (err error) {
S
songzhibin97 已提交
157 158 159 160
	// 增加判断: 重复创建struct
	if autoCode.AutoMoveFile && AutoCodeHistoryServiceApp.Repeat(autoCode.StructName) {
		return RepeatErr
	}
161
	dataList, fileList, needMkdir, err := autoCodeService.getNeedList(&autoCode)
162 163 164
	if err != nil {
		return err
	}
165
	meta, _ := json.Marshal(autoCode)
166 167 168 169 170 171 172 173
	// 写入文件前,先创建文件夹
	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, 0755)
174 175 176
		if err != nil {
			return err
		}
177
		if err = value.template.Execute(f, autoCode); err != nil {
178 179
			return err
		}
180
		_ = f.Close()
181
	}
182

m0_50812349's avatar
m0_50812349 已提交
183
	defer func() { // 移除中间文件
V
v_zhibsong 已提交
184 185 186 187
		if err := os.RemoveAll(autoPath); err != nil {
			return
		}
	}()
188 189 190 191 192 193 194
	bf := strings.Builder{}
	idBf := strings.Builder{}
	injectionCodeMeta := strings.Builder{}
	for _, id := range ids {
		idBf.WriteString(strconv.Itoa(int(id)))
		idBf.WriteString(";")
	}
m0_50812349's avatar
m0_50812349 已提交
195
	if autoCode.AutoMoveFile { // 判断是否需要自动转移
S
songzhibin97 已提交
196
		Init()
197 198
		for index := range dataList {
			autoCodeService.addAutoMoveFile(&dataList[index])
m0_50812349's avatar
m0_50812349 已提交
199 200
		}
		for _, value := range dataList { // 移动文件
m0_50812349's avatar
m0_50812349 已提交
201
			if err := utils.FileMove(value.autoCodePath, value.autoMoveFilePath); err != nil {
V
v_zhibsong 已提交
202 203 204
				return err
			}
		}
S
songzhibin97 已提交
205
		err = injectionCode(autoCode.StructName, &injectionCodeMeta)
206
		if err != nil {
S
songzhibin97 已提交
207
			return
208
		}
S
songzhibin97 已提交
209 210 211 212 213 214 215 216
		// 保存生成信息
		for _, data := range dataList {
			if len(data.autoMoveFilePath) != 0 {
				bf.WriteString(data.autoMoveFilePath)
				bf.WriteString(";")
			}
		}

217
		if global.GVA_CONFIG.AutoCode.TransferRestart {
S
songzhibin97 已提交
218 219 220
			go func() {
				_ = utils.Reload()
			}()
S
songzhibin97 已提交
221
		}
m0_50812349's avatar
m0_50812349 已提交
222
	} else { // 打包
223
		if err = utils.ZipFiles("./ginvueadmin.zip", fileList, ".", "."); err != nil {
V
v_zhibsong 已提交
224 225
			return err
		}
226
	}
227 228
	if autoCode.AutoMoveFile || autoCode.AutoCreateApiToSql {
		if autoCode.TableName != "" {
229
			err = AutoCodeHistoryServiceApp.CreateAutoCodeHistory(
230
				string(meta),
231 232
				autoCode.StructName,
				autoCode.Description,
233 234 235 236 237 238
				bf.String(),
				injectionCodeMeta.String(),
				autoCode.TableName,
				idBf.String(),
			)
		} else {
239
			err = AutoCodeHistoryServiceApp.CreateAutoCodeHistory(
240
				string(meta),
241 242
				autoCode.StructName,
				autoCode.Description,
243 244 245 246 247 248
				bf.String(),
				injectionCodeMeta.String(),
				autoCode.StructName,
				idBf.String(),
			)
		}
249 250 251 252 253
	}
	if err != nil {
		return err
	}
	if autoCode.AutoMoveFile {
254
		return system.AutoMoveErr
255
	}
256
	return nil
257

258
}
259

m0_50812349's avatar
m0_50812349 已提交
260 261 262 263 264 265
//@author: [piexlmax](https://github.com/piexlmax)
//@function: GetAllTplFile
//@description: 获取 pathName 文件夹下所有 tpl 文件
//@param: pathName string, fileList []string
//@return: []string, error

266
func (autoCodeService *AutoCodeService) GetAllTplFile(pathName string, fileList []string) ([]string, error) {
267 268 269
	files, err := ioutil.ReadDir(pathName)
	for _, fi := range files {
		if fi.IsDir() {
270
			fileList, err = autoCodeService.GetAllTplFile(pathName+"/"+fi.Name(), fileList)
271 272 273 274 275 276 277 278 279 280 281
			if err != nil {
				return nil, err
			}
		} else {
			if strings.HasSuffix(fi.Name(), ".tpl") {
				fileList = append(fileList, pathName+"/"+fi.Name())
			}
		}
	}
	return fileList, err
}
Mr.奇淼('s avatar
Mr.奇淼( 已提交
282

m0_50812349's avatar
m0_50812349 已提交
283 284 285
//@author: [piexlmax](https://github.com/piexlmax)
//@function: GetTables
//@description: 获取数据库的所有表名
何秀钢 已提交
286 287
//@param: dbName string
//@return: err error, TableNames []request.TableReq
m0_50812349's avatar
m0_50812349 已提交
288

289
func (autoCodeService *AutoCodeService) GetTables(dbName string) (err error, TableNames []request.TableReq) {
290
	err = global.GVA_DB.Raw("select table_name as table_name from information_schema.tables where table_schema = ?", dbName).Scan(&TableNames).Error
Mr.奇淼('s avatar
Mr.奇淼( 已提交
291 292 293
	return err, TableNames
}

m0_50812349's avatar
m0_50812349 已提交
294 295 296
//@author: [piexlmax](https://github.com/piexlmax)
//@function: GetDB
//@description: 获取数据库的所有数据库名
何秀钢 已提交
297
//@return: err error, DBNames []request.DBReq
m0_50812349's avatar
m0_50812349 已提交
298

299
func (autoCodeService *AutoCodeService) GetDB() (err error, DBNames []request.DBReq) {
Mr.奇淼('s avatar
Mr.奇淼( 已提交
300 301 302 303
	err = global.GVA_DB.Raw("SELECT SCHEMA_NAME AS `database` FROM INFORMATION_SCHEMA.SCHEMATA;").Scan(&DBNames).Error
	return err, DBNames
}

m0_50812349's avatar
m0_50812349 已提交
304 305 306
//@author: [piexlmax](https://github.com/piexlmax)
//@function: GetDB
//@description: 获取指定数据库和指定数据表的所有字段名,类型值等
何秀钢 已提交
307 308
//@param: tableName string, dbName string
//@return: err error, Columns []request.ColumnReq
m0_50812349's avatar
m0_50812349 已提交
309

310
func (autoCodeService *AutoCodeService) GetColumn(tableName string, dbName string) (err error, Columns []request.ColumnReq) {
311
	err = global.GVA_DB.Raw("SELECT COLUMN_NAME column_name,DATA_TYPE data_type,CASE DATA_TYPE WHEN 'longtext' THEN c.CHARACTER_MAXIMUM_LENGTH WHEN 'varchar' THEN c.CHARACTER_MAXIMUM_LENGTH WHEN 'double' THEN CONCAT_WS( ',', c.NUMERIC_PRECISION, c.NUMERIC_SCALE ) WHEN 'decimal' THEN CONCAT_WS( ',', c.NUMERIC_PRECISION, c.NUMERIC_SCALE ) WHEN 'int' THEN c.NUMERIC_PRECISION WHEN 'bigint' THEN c.NUMERIC_PRECISION ELSE '' END AS data_type_long,COLUMN_COMMENT column_comment FROM INFORMATION_SCHEMA.COLUMNS c WHERE table_name = ? AND table_schema = ?", tableName, dbName).Scan(&Columns).Error
312
	return err, Columns
Mr.奇淼('s avatar
Mr.奇淼( 已提交
313
}
m0_50812349's avatar
m0_50812349 已提交
314

315
func (autoCodeService *AutoCodeService) DropTable(tableName string) error {
S
songzhibin97 已提交
316 317 318
	return global.GVA_DB.Exec("DROP TABLE " + tableName).Error
}

m0_50812349's avatar
m0_50812349 已提交
319 320 321 322 323 324
//@author: [SliverHorn](https://github.com/SliverHorn)
//@author: [songzhibin97](https://github.com/songzhibin97)
//@function: addAutoMoveFile
//@description: 生成对应的迁移文件路径
//@param: *tplData
//@return: null
S
songzhibin97 已提交
325

326
func (autoCodeService *AutoCodeService) addAutoMoveFile(data *tplData) {
S
songzhibin97 已提交
327
	base := filepath.Base(data.autoCodePath)
328 329 330 331 332 333 334
	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") {
S
songzhibin97 已提交
335 336
			data.autoMoveFilePath = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server,
				global.GVA_CONFIG.AutoCode.SRouter, base)
337
		} else if strings.Contains(fileSlice[n-2], "api") {
S
songzhibin97 已提交
338 339
			data.autoMoveFilePath = filepath.Join(global.GVA_CONFIG.AutoCode.Root,
				global.GVA_CONFIG.AutoCode.Server, global.GVA_CONFIG.AutoCode.SApi, base)
340
		} else if strings.Contains(fileSlice[n-2], "service") {
S
songzhibin97 已提交
341 342
			data.autoMoveFilePath = filepath.Join(global.GVA_CONFIG.AutoCode.Root,
				global.GVA_CONFIG.AutoCode.Server, global.GVA_CONFIG.AutoCode.SService, base)
343
		} else if strings.Contains(fileSlice[n-2], "model") {
S
songzhibin97 已提交
344 345
			data.autoMoveFilePath = filepath.Join(global.GVA_CONFIG.AutoCode.Root,
				global.GVA_CONFIG.AutoCode.Server, global.GVA_CONFIG.AutoCode.SModel, base)
346
		} else if strings.Contains(fileSlice[n-2], "request") {
S
songzhibin97 已提交
347 348
			data.autoMoveFilePath = filepath.Join(global.GVA_CONFIG.AutoCode.Root,
				global.GVA_CONFIG.AutoCode.Server, global.GVA_CONFIG.AutoCode.SRequest, base)
m0_50812349's avatar
m0_50812349 已提交
349
		}
350 351
	} else if strings.Contains(fileSlice[1], "web") {
		if strings.Contains(fileSlice[n-1], "js") {
S
songzhibin97 已提交
352 353
			data.autoMoveFilePath = filepath.Join(global.GVA_CONFIG.AutoCode.Root,
				global.GVA_CONFIG.AutoCode.Web, global.GVA_CONFIG.AutoCode.WApi, base)
354
		} else if strings.Contains(fileSlice[n-2], "form") {
S
songzhibin97 已提交
355
			data.autoMoveFilePath = filepath.Join(global.GVA_CONFIG.AutoCode.Root,
S
songzhibin97 已提交
356
				global.GVA_CONFIG.AutoCode.Web, global.GVA_CONFIG.AutoCode.WForm, filepath.Base(filepath.Dir(filepath.Dir(data.autoCodePath))), strings.TrimSuffix(base, filepath.Ext(base))+"Form.vue")
357
		} else if strings.Contains(fileSlice[n-2], "table") {
S
songzhibin97 已提交
358
			data.autoMoveFilePath = filepath.Join(global.GVA_CONFIG.AutoCode.Root,
S
songzhibin97 已提交
359
				global.GVA_CONFIG.AutoCode.Web, global.GVA_CONFIG.AutoCode.WTable, filepath.Base(filepath.Dir(filepath.Dir(data.autoCodePath))), base)
m0_50812349's avatar
m0_50812349 已提交
360 361 362
		}
	}
}
363 364 365 366 367 368

//@author: [piexlmax](https://github.com/piexlmax)
//@author: [SliverHorn](https://github.com/SliverHorn)
//@function: CreateApi
//@description: 自动创建api数据,
//@param: a *model.AutoCodeStruct
何秀钢 已提交
369
//@return: err error
370

371
func (autoCodeService *AutoCodeService) AutoCreateApi(a *system.AutoCodeStruct) (ids []uint, err error) {
Mr.奇淼('s avatar
Mr.奇淼( 已提交
372
	var apiList = []system.SysApi{
373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410
		{
			Path:        "/" + a.Abbreviation + "/" + "create" + a.StructName,
			Description: "新增" + a.Description,
			ApiGroup:    a.Abbreviation,
			Method:      "POST",
		},
		{
			Path:        "/" + a.Abbreviation + "/" + "delete" + a.StructName,
			Description: "删除" + a.Description,
			ApiGroup:    a.Abbreviation,
			Method:      "DELETE",
		},
		{
			Path:        "/" + a.Abbreviation + "/" + "delete" + a.StructName + "ByIds",
			Description: "批量删除" + a.Description,
			ApiGroup:    a.Abbreviation,
			Method:      "DELETE",
		},
		{
			Path:        "/" + a.Abbreviation + "/" + "update" + a.StructName,
			Description: "更新" + a.Description,
			ApiGroup:    a.Abbreviation,
			Method:      "PUT",
		},
		{
			Path:        "/" + a.Abbreviation + "/" + "find" + a.StructName,
			Description: "根据ID获取" + a.Description,
			ApiGroup:    a.Abbreviation,
			Method:      "GET",
		},
		{
			Path:        "/" + a.Abbreviation + "/" + "get" + a.StructName + "List",
			Description: "获取" + a.Description + "列表",
			ApiGroup:    a.Abbreviation,
			Method:      "GET",
		},
	}
	err = global.GVA_DB.Transaction(func(tx *gorm.DB) error {
S
songzhibin97 已提交
411

412
		for _, v := range apiList {
Mr.奇淼('s avatar
Mr.奇淼( 已提交
413
			var api system.SysApi
414
			if errors.Is(tx.Where("path = ? AND method = ?", v.Path, v.Method).First(&api).Error, gorm.ErrRecordNotFound) {
S
songzhibin97 已提交
415
				if err = tx.Create(&v).Error; err != nil { // 遇到错误时回滚事务
416
					return err
S
songzhibin97 已提交
417 418
				} else {
					ids = append(ids, v.ID)
419
				}
420 421 422 423
			}
		}
		return nil
	})
S
songzhibin97 已提交
424
	return ids, err
425
}
426

427
func (autoCodeService *AutoCodeService) getNeedList(autoCode *system.AutoCodeStruct) (dataList []tplData, fileList []string, needMkdir []string, err error) {
S
songzhibin97 已提交
428 429 430 431 432
	// 去除所有空格
	utils.TrimSpace(autoCode)
	for _, field := range autoCode.Fields {
		utils.TrimSpace(field)
	}
433
	// 获取 basePath 文件夹下所有tpl文件
434
	tplFileList, err := autoCodeService.GetAllTplFile(basePath, nil)
435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465
	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})
	}
	// 生成 *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, basePath+"/")
		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 {
466
				var fileName string
S
songzhibin97 已提交
467 468 469 470
				if origFileName[firstDot:] != ".go" {
					fileName = autoCode.PackageName + origFileName[firstDot:]
				} else {
					fileName = autoCode.HumpPackageName + origFileName[firstDot:]
471 472
				}

473
				dataList[index].autoCodePath = filepath.Join(autoPath, trimBase[:lastSeparator], autoCode.PackageName,
474
					origFileName[:firstDot], fileName)
475 476 477 478 479 480 481 482 483 484 485 486
			}
		}

		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
}
S
songzhibin97 已提交
487 488 489 490 491 492 493 494 495 496 497 498

// injectionCode 封装代码注入
func injectionCode(structName string, bf *strings.Builder) error {
	for _, meta := range injectionPaths {
		code := fmt.Sprintf(meta.structNameF, structName)
		if err := utils.AutoInjectionCode(meta.path, meta.funcName, code); err != nil {
			return err
		}
		bf.WriteString(fmt.Sprintf("%s@%s@%s;", meta.path, meta.funcName, code))
	}
	return nil
}