sys_auto_code.go 14.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"
Mr.奇淼('s avatar
Mr.奇淼( 已提交
7
	"gin-vue-admin/global"
Mr.奇淼('s avatar
Mr.奇淼( 已提交
8 9
	"gin-vue-admin/model/system"
	"gin-vue-admin/model/system/request"
10
	"gin-vue-admin/utils"
11
	"io/ioutil"
12
	"os"
m0_50812349's avatar
m0_50812349 已提交
13
	"path/filepath"
S
songzhibin97 已提交
14
	"strconv"
15
	"strings"
16
	"text/template"
17 18

	"gorm.io/gorm"
19 20
)

21 22 23 24 25
const (
	autoPath = "autoCode/"
	basePath = "resource/template"
)

26
type tplData struct {
m0_50812349's avatar
m0_50812349 已提交
27 28 29 30
	template         *template.Template
	locationPath     string
	autoCodePath     string
	autoMoveFilePath string
31 32
}

33 34 35 36 37
type AutoCodeService struct {
}

var AutoCodeServiceApp = new(AutoCodeService)

38 39 40 41 42 43
//@author: [songzhibin97](https://github.com/songzhibin97)
//@function: PreviewTemp
//@description: 预览创建代码
//@param: model.AutoCodeStruct
//@return: map[string]string, error

44 45
func (autoCodeService *AutoCodeService) PreviewTemp(autoCode system.AutoCodeStruct) (map[string]string, error) {
	dataList, _, needMkdir, err := autoCodeService.getNeedList(&autoCode)
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 73 74 75 76
	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.奇淼( 已提交
77
		builder.WriteString("```")
78

Mr.奇淼('s avatar
Mr.奇淼( 已提交
79 80 81 82
		if ext != "" && strings.Contains(ext, ".") {
			builder.WriteString(strings.Replace(ext, ".", "", -1))
		}
		builder.WriteString("\n\n")
83 84 85 86 87
		data, err := ioutil.ReadAll(f)
		if err != nil {
			return nil, err
		}
		builder.Write(data)
Mr.奇淼('s avatar
Mr.奇淼( 已提交
88
		builder.WriteString("\n\n```")
89

90
		pathArr := strings.Split(value.autoCodePath, string(os.PathSeparator))
Mr.奇淼('s avatar
Mr.奇淼( 已提交
91
		ret[pathArr[1]+"-"+pathArr[3]] = builder.String()
92 93 94 95 96 97 98 99 100 101 102
		_ = f.Close()

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

m0_50812349's avatar
m0_50812349 已提交
103 104 105 106
//@author: [piexlmax](https://github.com/piexlmax)
//@function: CreateTemp
//@description: 创建代码
//@param: model.AutoCodeStruct
何秀钢 已提交
107
//@return: err error
Mr.奇淼('s avatar
Mr.奇淼( 已提交
108

109 110
func (autoCodeService *AutoCodeService) CreateTemp(autoCode system.AutoCodeStruct, ids ...uint) (err error) {
	dataList, fileList, needMkdir, err := autoCodeService.getNeedList(&autoCode)
111 112 113
	if err != nil {
		return err
	}
114
	meta, _ := json.Marshal(autoCode)
115 116 117 118 119 120 121 122
	// 写入文件前,先创建文件夹
	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)
123 124 125
		if err != nil {
			return err
		}
126
		if err = value.template.Execute(f, autoCode); err != nil {
127 128
			return err
		}
129
		_ = f.Close()
130
	}
131

m0_50812349's avatar
m0_50812349 已提交
132
	defer func() { // 移除中间文件
V
v_zhibsong 已提交
133 134 135 136
		if err := os.RemoveAll(autoPath); err != nil {
			return
		}
	}()
137 138 139 140 141 142 143
	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 已提交
144
	if autoCode.AutoMoveFile { // 判断是否需要自动转移
145 146
		for index := range dataList {
			autoCodeService.addAutoMoveFile(&dataList[index])
m0_50812349's avatar
m0_50812349 已提交
147 148
		}
		for _, value := range dataList { // 移动文件
m0_50812349's avatar
m0_50812349 已提交
149
			if err := utils.FileMove(value.autoCodePath, value.autoMoveFilePath); err != nil {
V
v_zhibsong 已提交
150 151 152
				return err
			}
		}
153 154 155 156 157 158 159 160 161
		initializeGormFilePath := filepath.Join(global.GVA_CONFIG.AutoCode.Root,
			global.GVA_CONFIG.AutoCode.Server, global.GVA_CONFIG.AutoCode.SInitialize, "gorm.go")
		initializeRouterFilePath := filepath.Join(global.GVA_CONFIG.AutoCode.Root,
			global.GVA_CONFIG.AutoCode.Server, global.GVA_CONFIG.AutoCode.SInitialize, "router.go")
		err = utils.AutoInjectionCode(initializeGormFilePath, "MysqlTables", "model."+autoCode.StructName+"{},")
		if err != nil {
			return err
		}
		err = utils.AutoInjectionCode(initializeRouterFilePath, "Routers", "router.Init"+autoCode.StructName+"Router(PrivateGroup)")
S
songzhibin97 已提交
162 163 164
		if err != nil {
			return err
		}
165

S
songzhibin97 已提交
166 167 168 169 170 171 172 173 174 175 176 177
		injectionCodeMeta.WriteString(fmt.Sprintf("%s@%s@%s", initializeGormFilePath, "MysqlTables", "model."+autoCode.StructName+"{},"))
		injectionCodeMeta.WriteString(";")
		injectionCodeMeta.WriteString(fmt.Sprintf("%s@%s@%s", initializeRouterFilePath, "Routers", "router.Init"+autoCode.StructName+"Router(PrivateGroup)"))

		// 保存生成信息
		for _, data := range dataList {
			if len(data.autoMoveFilePath) != 0 {
				bf.WriteString(data.autoMoveFilePath)
				bf.WriteString(";")
			}
		}

178 179 180 181
		if global.GVA_CONFIG.AutoCode.TransferRestart {
			go func() {
				_ = utils.Reload()
			}()
S
songzhibin97 已提交
182
		}
183
		//return errors.New("创建代码成功并移动文件成功")
m0_50812349's avatar
m0_50812349 已提交
184
	} else { // 打包
185
		if err = utils.ZipFiles("./ginvueadmin.zip", fileList, ".", "."); err != nil {
V
v_zhibsong 已提交
186 187
			return err
		}
188
	}
189 190
	if autoCode.AutoMoveFile || autoCode.AutoCreateApiToSql {
		if autoCode.TableName != "" {
191
			err = AutoCodeHistoryServiceApp.CreateAutoCodeHistory(
192
				string(meta),
193 194
				autoCode.StructName,
				autoCode.Description,
195 196 197 198 199 200
				bf.String(),
				injectionCodeMeta.String(),
				autoCode.TableName,
				idBf.String(),
			)
		} else {
201
			err = AutoCodeHistoryServiceApp.CreateAutoCodeHistory(
202
				string(meta),
203 204
				autoCode.StructName,
				autoCode.Description,
205 206 207 208 209 210
				bf.String(),
				injectionCodeMeta.String(),
				autoCode.StructName,
				idBf.String(),
			)
		}
211 212 213 214 215 216 217
	}
	if err != nil {
		return err
	}
	if autoCode.AutoMoveFile {
		return errors.New("创建代码成功并移动文件成功")
	}
218
	return nil
219

220
}
221

m0_50812349's avatar
m0_50812349 已提交
222 223 224 225 226 227
//@author: [piexlmax](https://github.com/piexlmax)
//@function: GetAllTplFile
//@description: 获取 pathName 文件夹下所有 tpl 文件
//@param: pathName string, fileList []string
//@return: []string, error

228
func (autoCodeService *AutoCodeService) GetAllTplFile(pathName string, fileList []string) ([]string, error) {
229 230 231
	files, err := ioutil.ReadDir(pathName)
	for _, fi := range files {
		if fi.IsDir() {
232
			fileList, err = autoCodeService.GetAllTplFile(pathName+"/"+fi.Name(), fileList)
233 234 235 236 237 238 239 240 241 242 243
			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.奇淼( 已提交
244

m0_50812349's avatar
m0_50812349 已提交
245 246 247
//@author: [piexlmax](https://github.com/piexlmax)
//@function: GetTables
//@description: 获取数据库的所有表名
何秀钢 已提交
248 249
//@param: dbName string
//@return: err error, TableNames []request.TableReq
m0_50812349's avatar
m0_50812349 已提交
250

251
func (autoCodeService *AutoCodeService) GetTables(dbName string) (err error, TableNames []request.TableReq) {
252
	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.奇淼( 已提交
253 254 255
	return err, TableNames
}

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

261
func (autoCodeService *AutoCodeService) GetDB() (err error, DBNames []request.DBReq) {
Mr.奇淼('s avatar
Mr.奇淼( 已提交
262 263 264 265
	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 已提交
266 267 268
//@author: [piexlmax](https://github.com/piexlmax)
//@function: GetDB
//@description: 获取指定数据库和指定数据表的所有字段名,类型值等
何秀钢 已提交
269 270
//@param: tableName string, dbName string
//@return: err error, Columns []request.ColumnReq
m0_50812349's avatar
m0_50812349 已提交
271

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

277
func (autoCodeService *AutoCodeService) DropTable(tableName string) error {
S
songzhibin97 已提交
278 279 280
	return global.GVA_DB.Exec("DROP TABLE " + tableName).Error
}

m0_50812349's avatar
m0_50812349 已提交
281 282 283 284 285 286
//@author: [SliverHorn](https://github.com/SliverHorn)
//@author: [songzhibin97](https://github.com/songzhibin97)
//@function: addAutoMoveFile
//@description: 生成对应的迁移文件路径
//@param: *tplData
//@return: null
S
songzhibin97 已提交
287

288
func (autoCodeService *AutoCodeService) addAutoMoveFile(data *tplData) {
S
songzhibin97 已提交
289
	base := filepath.Base(data.autoCodePath)
290 291 292 293 294 295 296
	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 已提交
297 298
			data.autoMoveFilePath = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server,
				global.GVA_CONFIG.AutoCode.SRouter, base)
299
		} else if strings.Contains(fileSlice[n-2], "api") {
S
songzhibin97 已提交
300 301
			data.autoMoveFilePath = filepath.Join(global.GVA_CONFIG.AutoCode.Root,
				global.GVA_CONFIG.AutoCode.Server, global.GVA_CONFIG.AutoCode.SApi, base)
302
		} else if strings.Contains(fileSlice[n-2], "service") {
S
songzhibin97 已提交
303 304
			data.autoMoveFilePath = filepath.Join(global.GVA_CONFIG.AutoCode.Root,
				global.GVA_CONFIG.AutoCode.Server, global.GVA_CONFIG.AutoCode.SService, base)
305
		} else if strings.Contains(fileSlice[n-2], "model") {
S
songzhibin97 已提交
306 307
			data.autoMoveFilePath = filepath.Join(global.GVA_CONFIG.AutoCode.Root,
				global.GVA_CONFIG.AutoCode.Server, global.GVA_CONFIG.AutoCode.SModel, base)
308
		} else if strings.Contains(fileSlice[n-2], "request") {
S
songzhibin97 已提交
309 310
			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 已提交
311
		}
312 313
	} else if strings.Contains(fileSlice[1], "web") {
		if strings.Contains(fileSlice[n-1], "js") {
S
songzhibin97 已提交
314 315
			data.autoMoveFilePath = filepath.Join(global.GVA_CONFIG.AutoCode.Root,
				global.GVA_CONFIG.AutoCode.Web, global.GVA_CONFIG.AutoCode.WApi, base)
316
		} else if strings.Contains(fileSlice[n-2], "form") {
S
songzhibin97 已提交
317
			data.autoMoveFilePath = filepath.Join(global.GVA_CONFIG.AutoCode.Root,
S
songzhibin97 已提交
318
				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")
319
		} else if strings.Contains(fileSlice[n-2], "table") {
S
songzhibin97 已提交
320
			data.autoMoveFilePath = filepath.Join(global.GVA_CONFIG.AutoCode.Root,
S
songzhibin97 已提交
321
				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 已提交
322 323 324
		}
	}
}
325 326 327 328 329 330

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

333
func (autoCodeService *AutoCodeService) AutoCreateApi(a *system.AutoCodeStruct) (ids []uint, err error) {
Mr.奇淼('s avatar
Mr.奇淼( 已提交
334
	var apiList = []system.SysApi{
335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372
		{
			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 已提交
373

374
		for _, v := range apiList {
Mr.奇淼('s avatar
Mr.奇淼( 已提交
375
			var api system.SysApi
376
			if errors.Is(tx.Where("path = ? AND method = ?", v.Path, v.Method).First(&api).Error, gorm.ErrRecordNotFound) {
S
songzhibin97 已提交
377
				if err = tx.Create(&v).Error; err != nil { // 遇到错误时回滚事务
378
					return err
S
songzhibin97 已提交
379 380
				} else {
					ids = append(ids, v.ID)
381
				}
382 383 384 385
			}
		}
		return nil
	})
S
songzhibin97 已提交
386
	return ids, err
387
}
388

389
func (autoCodeService *AutoCodeService) getNeedList(autoCode *system.AutoCodeStruct) (dataList []tplData, fileList []string, needMkdir []string, err error) {
S
songzhibin97 已提交
390 391 392 393 394
	// 去除所有空格
	utils.TrimSpace(autoCode)
	for _, field := range autoCode.Fields {
		utils.TrimSpace(field)
	}
395
	// 获取 basePath 文件夹下所有tpl文件
396
	tplFileList, err := autoCodeService.GetAllTplFile(basePath, nil)
397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428
	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
	autoPath := "autoCode/"
	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 {
429
				var fileName string
S
songzhibin97 已提交
430 431 432 433
				if origFileName[firstDot:] != ".go" {
					fileName = autoCode.PackageName + origFileName[firstDot:]
				} else {
					fileName = autoCode.HumpPackageName + origFileName[firstDot:]
434 435
				}

436
				dataList[index].autoCodePath = filepath.Join(autoPath, trimBase[:lastSeparator], autoCode.PackageName,
437
					origFileName[:firstDot], fileName)
438 439 440 441 442 443 444 445 446 447 448 449
			}
		}

		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
}