sys_auto_code.go 14.1 KB
Newer Older
1 2 3
package service

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"
8
	"gin-vue-admin/model"
Mr.奇淼('s avatar
Mr.奇淼( 已提交
9
	"gin-vue-admin/model/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 38 39
//@author: [songzhibin97](https://github.com/songzhibin97)
//@function: PreviewTemp
//@description: 预览创建代码
//@param: model.AutoCodeStruct
//@return: map[string]string, error

func PreviewTemp(autoCode model.AutoCodeStruct) (map[string]string, error) {
40
	dataList, _, needMkdir, err := getNeedList(&autoCode)
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
	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.奇淼( 已提交
72
		builder.WriteString("```")
73

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

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

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

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

S
songzhibin97 已提交
104
func CreateTemp(autoCode model.AutoCodeStruct, ids ...uint) (err error) {
105
	dataList, fileList, needMkdir, err := getNeedList(&autoCode)
106 107 108
	if err != nil {
		return err
	}
109
	meta, _ := json.Marshal(autoCode)
110 111 112 113 114 115 116 117
	// 写入文件前,先创建文件夹
	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)
118 119 120
		if err != nil {
			return err
		}
121
		if err = value.template.Execute(f, autoCode); err != nil {
122 123
			return err
		}
124
		_ = f.Close()
125
	}
126

m0_50812349's avatar
m0_50812349 已提交
127
	defer func() { // 移除中间文件
V
v_zhibsong 已提交
128 129 130 131
		if err := os.RemoveAll(autoPath); err != nil {
			return
		}
	}()
132 133 134 135 136 137 138
	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 已提交
139 140 141 142 143
	if autoCode.AutoMoveFile { // 判断是否需要自动转移
		for index, _ := range dataList {
			addAutoMoveFile(&dataList[index])
		}
		for _, value := range dataList { // 移动文件
m0_50812349's avatar
m0_50812349 已提交
144
			if err := utils.FileMove(value.autoCodePath, value.autoMoveFilePath); err != nil {
V
v_zhibsong 已提交
145 146 147
				return err
			}
		}
148 149 150 151 152 153 154 155 156
		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 已提交
157 158 159
		if err != nil {
			return err
		}
160

S
songzhibin97 已提交
161 162 163 164 165 166 167 168 169 170 171 172
		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(";")
			}
		}

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

211
}
212

m0_50812349's avatar
m0_50812349 已提交
213 214 215 216 217 218
//@author: [piexlmax](https://github.com/piexlmax)
//@function: GetAllTplFile
//@description: 获取 pathName 文件夹下所有 tpl 文件
//@param: pathName string, fileList []string
//@return: []string, error

219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234
func GetAllTplFile(pathName string, fileList []string) ([]string, error) {
	files, err := ioutil.ReadDir(pathName)
	for _, fi := range files {
		if fi.IsDir() {
			fileList, err = 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
}
Mr.奇淼('s avatar
Mr.奇淼( 已提交
235

m0_50812349's avatar
m0_50812349 已提交
236 237 238
//@author: [piexlmax](https://github.com/piexlmax)
//@function: GetTables
//@description: 获取数据库的所有表名
何秀钢 已提交
239 240
//@param: dbName string
//@return: err error, TableNames []request.TableReq
m0_50812349's avatar
m0_50812349 已提交
241

Mr.奇淼('s avatar
Mr.奇淼( 已提交
242
func GetTables(dbName string) (err error, TableNames []request.TableReq) {
243
	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.奇淼( 已提交
244 245 246
	return err, TableNames
}

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

Mr.奇淼('s avatar
Mr.奇淼( 已提交
252 253 254 255 256
func GetDB() (err error, DBNames []request.DBReq) {
	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 已提交
257 258 259
//@author: [piexlmax](https://github.com/piexlmax)
//@function: GetDB
//@description: 获取指定数据库和指定数据表的所有字段名,类型值等
何秀钢 已提交
260 261
//@param: tableName string, dbName string
//@return: err error, Columns []request.ColumnReq
m0_50812349's avatar
m0_50812349 已提交
262

263
func GetColumn(tableName string, dbName string) (err error, Columns []request.ColumnReq) {
264
	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
265
	return err, Columns
Mr.奇淼('s avatar
Mr.奇淼( 已提交
266
}
m0_50812349's avatar
m0_50812349 已提交
267

S
songzhibin97 已提交
268 269 270 271
func DropTable(tableName string) error {
	return global.GVA_DB.Exec("DROP TABLE " + tableName).Error
}

m0_50812349's avatar
m0_50812349 已提交
272 273 274 275 276 277
//@author: [SliverHorn](https://github.com/SliverHorn)
//@author: [songzhibin97](https://github.com/songzhibin97)
//@function: addAutoMoveFile
//@description: 生成对应的迁移文件路径
//@param: *tplData
//@return: null
S
songzhibin97 已提交
278

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

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

S
songzhibin97 已提交
324
func AutoCreateApi(a *model.AutoCodeStruct) (ids []uint, err error) {
325 326 327 328 329 330 331 332 333 334 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
	var apiList = []model.SysApi{
		{
			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 已提交
364

365 366
		for _, v := range apiList {
			var api model.SysApi
367
			if errors.Is(tx.Where("path = ? AND method = ?", v.Path, v.Method).First(&api).Error, gorm.ErrRecordNotFound) {
S
songzhibin97 已提交
368
				if err = tx.Create(&v).Error; err != nil { // 遇到错误时回滚事务
369
					return err
S
songzhibin97 已提交
370 371
				} else {
					ids = append(ids, v.ID)
372
				}
373 374 375 376
			}
		}
		return nil
	})
S
songzhibin97 已提交
377
	return ids, err
378
}
379 380

func getNeedList(autoCode *model.AutoCodeStruct) (dataList []tplData, fileList []string, needMkdir []string, err error) {
S
songzhibin97 已提交
381 382 383 384 385
	// 去除所有空格
	utils.TrimSpace(autoCode)
	for _, field := range autoCode.Fields {
		utils.TrimSpace(field)
	}
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 411 412 413 414 415 416 417 418 419
	// 获取 basePath 文件夹下所有tpl文件
	tplFileList, err := GetAllTplFile(basePath, 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})
	}
	// 生成 *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 {
420
				var fileName string
S
songzhibin97 已提交
421 422 423 424
				if origFileName[firstDot:] != ".go" {
					fileName = autoCode.PackageName + origFileName[firstDot:]
				} else {
					fileName = autoCode.HumpPackageName + origFileName[firstDot:]
425 426
				}

427
				dataList[index].autoCodePath = filepath.Join(autoPath, trimBase[:lastSeparator], autoCode.PackageName,
428
					origFileName[:firstDot], fileName)
429 430 431 432 433 434 435 436 437 438 439 440
			}
		}

		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
}