sys_auto_code.go 16.0 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
		}
S
songzhibin97 已提交
200 201 202 203 204 205
		// 判断目标文件是否都可以移动
		for _, value := range dataList {
			if utils.FileExist(value.autoMoveFilePath) {
				return errors.New(fmt.Sprintf("目标文件已存在:%s\n", value.autoMoveFilePath))
			}
		}
m0_50812349's avatar
m0_50812349 已提交
206
		for _, value := range dataList { // 移动文件
m0_50812349's avatar
m0_50812349 已提交
207
			if err := utils.FileMove(value.autoCodePath, value.autoMoveFilePath); err != nil {
V
v_zhibsong 已提交
208 209 210
				return err
			}
		}
S
songzhibin97 已提交
211
		err = injectionCode(autoCode.StructName, &injectionCodeMeta)
212
		if err != nil {
S
songzhibin97 已提交
213
			return
214
		}
S
songzhibin97 已提交
215 216 217 218 219 220 221 222
		// 保存生成信息
		for _, data := range dataList {
			if len(data.autoMoveFilePath) != 0 {
				bf.WriteString(data.autoMoveFilePath)
				bf.WriteString(";")
			}
		}

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

264
}
265

m0_50812349's avatar
m0_50812349 已提交
266 267 268 269 270 271
//@author: [piexlmax](https://github.com/piexlmax)
//@function: GetAllTplFile
//@description: 获取 pathName 文件夹下所有 tpl 文件
//@param: pathName string, fileList []string
//@return: []string, error

272
func (autoCodeService *AutoCodeService) GetAllTplFile(pathName string, fileList []string) ([]string, error) {
273 274 275
	files, err := ioutil.ReadDir(pathName)
	for _, fi := range files {
		if fi.IsDir() {
276
			fileList, err = autoCodeService.GetAllTplFile(pathName+"/"+fi.Name(), fileList)
277 278 279 280 281 282 283 284 285 286 287
			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.奇淼( 已提交
288

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

295
func (autoCodeService *AutoCodeService) GetTables(dbName string) (err error, TableNames []request.TableReq) {
296
	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.奇淼( 已提交
297 298 299
	return err, TableNames
}

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

305
func (autoCodeService *AutoCodeService) GetDB() (err error, DBNames []request.DBReq) {
Mr.奇淼('s avatar
Mr.奇淼( 已提交
306 307 308 309
	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 已提交
310 311 312
//@author: [piexlmax](https://github.com/piexlmax)
//@function: GetDB
//@description: 获取指定数据库和指定数据表的所有字段名,类型值等
何秀钢 已提交
313 314
//@param: tableName string, dbName string
//@return: err error, Columns []request.ColumnReq
m0_50812349's avatar
m0_50812349 已提交
315

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

321
func (autoCodeService *AutoCodeService) DropTable(tableName string) error {
S
songzhibin97 已提交
322 323 324
	return global.GVA_DB.Exec("DROP TABLE " + tableName).Error
}

m0_50812349's avatar
m0_50812349 已提交
325 326 327 328 329 330
//@author: [SliverHorn](https://github.com/SliverHorn)
//@author: [songzhibin97](https://github.com/songzhibin97)
//@function: addAutoMoveFile
//@description: 生成对应的迁移文件路径
//@param: *tplData
//@return: null
S
songzhibin97 已提交
331

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

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

377
func (autoCodeService *AutoCodeService) AutoCreateApi(a *system.AutoCodeStruct) (ids []uint, err error) {
Mr.奇淼('s avatar
Mr.奇淼( 已提交
378
	var apiList = []system.SysApi{
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 411 412 413 414 415 416
		{
			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 已提交
417

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

433
func (autoCodeService *AutoCodeService) getNeedList(autoCode *system.AutoCodeStruct) (dataList []tplData, fileList []string, needMkdir []string, err error) {
S
songzhibin97 已提交
434 435 436 437 438
	// 去除所有空格
	utils.TrimSpace(autoCode)
	for _, field := range autoCode.Fields {
		utils.TrimSpace(field)
	}
439
	// 获取 basePath 文件夹下所有tpl文件
440
	tplFileList, err := autoCodeService.GetAllTplFile(basePath, nil)
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 466 467 468 469 470 471
	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 {
472
				var fileName string
S
songzhibin97 已提交
473 474 475 476
				if origFileName[firstDot:] != ".go" {
					fileName = autoCode.PackageName + origFileName[firstDot:]
				} else {
					fileName = autoCode.HumpPackageName + origFileName[firstDot:]
477 478
				}

479
				dataList[index].autoCodePath = filepath.Join(autoPath, trimBase[:lastSeparator], autoCode.PackageName,
480
					origFileName[:firstDot], fileName)
481 482 483 484 485 486 487 488 489 490 491 492
			}
		}

		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 已提交
493 494 495 496 497 498 499 500 501 502 503 504

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