sys_auto_code.go 15.7 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 157
func (autoCodeService *AutoCodeService) CreateTemp(autoCode system.AutoCodeStruct, ids ...uint) (err error) {
	dataList, fileList, needMkdir, err := autoCodeService.getNeedList(&autoCode)
158 159 160
	if err != nil {
		return err
	}
161
	meta, _ := json.Marshal(autoCode)
162 163 164 165 166 167 168 169
	// 写入文件前,先创建文件夹
	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)
170 171 172
		if err != nil {
			return err
		}
173
		if err = value.template.Execute(f, autoCode); err != nil {
174 175
			return err
		}
176
		_ = f.Close()
177
	}
178

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

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

254
}
255

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

262
func (autoCodeService *AutoCodeService) GetAllTplFile(pathName string, fileList []string) ([]string, error) {
263 264 265
	files, err := ioutil.ReadDir(pathName)
	for _, fi := range files {
		if fi.IsDir() {
266
			fileList, err = autoCodeService.GetAllTplFile(pathName+"/"+fi.Name(), fileList)
267 268 269 270 271 272 273 274 275 276 277
			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.奇淼( 已提交
278

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

285
func (autoCodeService *AutoCodeService) GetTables(dbName string) (err error, TableNames []request.TableReq) {
286
	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.奇淼( 已提交
287 288 289
	return err, TableNames
}

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

295
func (autoCodeService *AutoCodeService) GetDB() (err error, DBNames []request.DBReq) {
Mr.奇淼('s avatar
Mr.奇淼( 已提交
296 297 298 299
	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 已提交
300 301 302
//@author: [piexlmax](https://github.com/piexlmax)
//@function: GetDB
//@description: 获取指定数据库和指定数据表的所有字段名,类型值等
何秀钢 已提交
303 304
//@param: tableName string, dbName string
//@return: err error, Columns []request.ColumnReq
m0_50812349's avatar
m0_50812349 已提交
305

306
func (autoCodeService *AutoCodeService) GetColumn(tableName string, dbName string) (err error, Columns []request.ColumnReq) {
307
	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
308
	return err, Columns
Mr.奇淼('s avatar
Mr.奇淼( 已提交
309
}
m0_50812349's avatar
m0_50812349 已提交
310

311
func (autoCodeService *AutoCodeService) DropTable(tableName string) error {
S
songzhibin97 已提交
312 313 314
	return global.GVA_DB.Exec("DROP TABLE " + tableName).Error
}

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

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

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

367
func (autoCodeService *AutoCodeService) AutoCreateApi(a *system.AutoCodeStruct) (ids []uint, err error) {
Mr.奇淼('s avatar
Mr.奇淼( 已提交
368
	var apiList = []system.SysApi{
369 370 371 372 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
		{
			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 已提交
407

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

423
func (autoCodeService *AutoCodeService) getNeedList(autoCode *system.AutoCodeStruct) (dataList []tplData, fileList []string, needMkdir []string, err error) {
S
songzhibin97 已提交
424 425 426 427 428
	// 去除所有空格
	utils.TrimSpace(autoCode)
	for _, field := range autoCode.Fields {
		utils.TrimSpace(field)
	}
429
	// 获取 basePath 文件夹下所有tpl文件
430
	tplFileList, err := autoCodeService.GetAllTplFile(basePath, nil)
431 432 433 434 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
	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 {
462
				var fileName string
S
songzhibin97 已提交
463 464 465 466
				if origFileName[firstDot:] != ".go" {
					fileName = autoCode.PackageName + origFileName[firstDot:]
				} else {
					fileName = autoCode.HumpPackageName + origFileName[firstDot:]
467 468
				}

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

		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 已提交
483 484 485 486 487 488 489 490 491 492 493 494

// 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
}