init project

This commit is contained in:
2025-03-28 17:14:57 +08:00
parent 76ca33962e
commit 4d08921f92
357 changed files with 54458 additions and 0 deletions

13
service/enter.go Normal file
View File

@@ -0,0 +1,13 @@
package service
import (
"git.echol.cn/loser/xiecheng_server/service/example"
"git.echol.cn/loser/xiecheng_server/service/system"
)
var ServiceGroupApp = new(ServiceGroup)
type ServiceGroup struct {
SystemServiceGroup system.ServiceGroup
ExampleServiceGroup example.ServiceGroup
}

7
service/example/enter.go Normal file
View File

@@ -0,0 +1,7 @@
package example
type ServiceGroup struct {
CustomerService
FileUploadAndDownloadService
AttachmentCategoryService
}

View File

@@ -0,0 +1,66 @@
package example
import (
"errors"
"git.echol.cn/loser/xiecheng_server/global"
"git.echol.cn/loser/xiecheng_server/model/example"
"gorm.io/gorm"
)
type AttachmentCategoryService struct{}
// AddCategory 创建/更新的分类
func (a *AttachmentCategoryService) AddCategory(req *example.ExaAttachmentCategory) (err error) {
// 检查是否已存在相同名称的分类
if (!errors.Is(global.GVA_DB.Take(&example.ExaAttachmentCategory{}, "name = ? and pid = ?", req.Name, req.Pid).Error, gorm.ErrRecordNotFound)) {
return errors.New("分类名称已存在")
}
if req.ID > 0 {
if err = global.GVA_DB.Model(&example.ExaAttachmentCategory{}).Where("id = ?", req.ID).Updates(&example.ExaAttachmentCategory{
Name: req.Name,
Pid: req.Pid,
}).Error; err != nil {
return err
}
} else {
if err = global.GVA_DB.Create(&example.ExaAttachmentCategory{
Name: req.Name,
Pid: req.Pid,
}).Error; err != nil {
return err
}
}
return nil
}
// DeleteCategory 删除分类
func (a *AttachmentCategoryService) DeleteCategory(id *int) error {
var childCount int64
global.GVA_DB.Model(&example.ExaAttachmentCategory{}).Where("pid = ?", id).Count(&childCount)
if childCount > 0 {
return errors.New("请先删除子级")
}
return global.GVA_DB.Where("id = ?", id).Unscoped().Delete(&example.ExaAttachmentCategory{}).Error
}
// GetCategoryList 分类列表
func (a *AttachmentCategoryService) GetCategoryList() (res []*example.ExaAttachmentCategory, err error) {
var fileLists []example.ExaAttachmentCategory
err = global.GVA_DB.Model(&example.ExaAttachmentCategory{}).Find(&fileLists).Error
if err != nil {
return res, err
}
return a.getChildrenList(fileLists, 0), nil
}
// getChildrenList 子类
func (a *AttachmentCategoryService) getChildrenList(categories []example.ExaAttachmentCategory, parentID uint) []*example.ExaAttachmentCategory {
var tree []*example.ExaAttachmentCategory
for _, category := range categories {
if category.Pid == parentID {
category.Children = a.getChildrenList(categories, category.ID)
tree = append(tree, &category)
}
}
return tree
}

View File

@@ -0,0 +1,71 @@
package example
import (
"errors"
"git.echol.cn/loser/xiecheng_server/global"
"git.echol.cn/loser/xiecheng_server/model/example"
"gorm.io/gorm"
)
type FileUploadAndDownloadService struct{}
var FileUploadAndDownloadServiceApp = new(FileUploadAndDownloadService)
//@author: [piexlmax](https://github.com/piexlmax)
//@function: FindOrCreateFile
//@description: 上传文件时检测当前文件属性,如果没有文件则创建,有则返回文件的当前切片
//@param: fileMd5 string, fileName string, chunkTotal int
//@return: file model.ExaFile, err error
func (e *FileUploadAndDownloadService) FindOrCreateFile(fileMd5 string, fileName string, chunkTotal int) (file example.ExaFile, err error) {
var cfile example.ExaFile
cfile.FileMd5 = fileMd5
cfile.FileName = fileName
cfile.ChunkTotal = chunkTotal
if errors.Is(global.GVA_DB.Where("file_md5 = ? AND is_finish = ?", fileMd5, true).First(&file).Error, gorm.ErrRecordNotFound) {
err = global.GVA_DB.Where("file_md5 = ? AND file_name = ?", fileMd5, fileName).Preload("ExaFileChunk").FirstOrCreate(&file, cfile).Error
return file, err
}
cfile.IsFinish = true
cfile.FilePath = file.FilePath
err = global.GVA_DB.Create(&cfile).Error
return cfile, err
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: CreateFileChunk
//@description: 创建文件切片记录
//@param: id uint, fileChunkPath string, fileChunkNumber int
//@return: error
func (e *FileUploadAndDownloadService) CreateFileChunk(id uint, fileChunkPath string, fileChunkNumber int) error {
var chunk example.ExaFileChunk
chunk.FileChunkPath = fileChunkPath
chunk.ExaFileID = id
chunk.FileChunkNumber = fileChunkNumber
err := global.GVA_DB.Create(&chunk).Error
return err
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: DeleteFileChunk
//@description: 删除文件切片记录
//@param: fileMd5 string, fileName string, filePath string
//@return: error
func (e *FileUploadAndDownloadService) DeleteFileChunk(fileMd5 string, filePath string) error {
var chunks []example.ExaFileChunk
var file example.ExaFile
err := global.GVA_DB.Where("file_md5 = ?", fileMd5).First(&file).
Updates(map[string]interface{}{
"IsFinish": true,
"file_path": filePath,
}).Error
if err != nil {
return err
}
err = global.GVA_DB.Where("exa_file_id = ?", file.ID).Delete(&chunks).Unscoped().Error
return err
}

View File

@@ -0,0 +1,87 @@
package example
import (
"git.echol.cn/loser/xiecheng_server/global"
"git.echol.cn/loser/xiecheng_server/model/common/request"
"git.echol.cn/loser/xiecheng_server/model/example"
"git.echol.cn/loser/xiecheng_server/model/system"
systemService "git.echol.cn/loser/xiecheng_server/service/system"
)
type CustomerService struct{}
var CustomerServiceApp = new(CustomerService)
//@author: [piexlmax](https://github.com/piexlmax)
//@function: CreateExaCustomer
//@description: 创建客户
//@param: e model.ExaCustomer
//@return: err error
func (exa *CustomerService) CreateExaCustomer(e example.ExaCustomer) (err error) {
err = global.GVA_DB.Create(&e).Error
return err
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: DeleteFileChunk
//@description: 删除客户
//@param: e model.ExaCustomer
//@return: err error
func (exa *CustomerService) DeleteExaCustomer(e example.ExaCustomer) (err error) {
err = global.GVA_DB.Delete(&e).Error
return err
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: UpdateExaCustomer
//@description: 更新客户
//@param: e *model.ExaCustomer
//@return: err error
func (exa *CustomerService) UpdateExaCustomer(e *example.ExaCustomer) (err error) {
err = global.GVA_DB.Save(e).Error
return err
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: GetExaCustomer
//@description: 获取客户信息
//@param: id uint
//@return: customer model.ExaCustomer, err error
func (exa *CustomerService) GetExaCustomer(id uint) (customer example.ExaCustomer, err error) {
err = global.GVA_DB.Where("id = ?", id).First(&customer).Error
return
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: GetCustomerInfoList
//@description: 分页获取客户列表
//@param: sysUserAuthorityID string, info request.PageInfo
//@return: list interface{}, total int64, err error
func (exa *CustomerService) GetCustomerInfoList(sysUserAuthorityID uint, info request.PageInfo) (list interface{}, total int64, err error) {
limit := info.PageSize
offset := info.PageSize * (info.Page - 1)
db := global.GVA_DB.Model(&example.ExaCustomer{})
var a system.SysAuthority
a.AuthorityId = sysUserAuthorityID
auth, err := systemService.AuthorityServiceApp.GetAuthorityInfo(a)
if err != nil {
return
}
var dataId []uint
for _, v := range auth.DataAuthorityId {
dataId = append(dataId, v.AuthorityId)
}
var CustomerList []example.ExaCustomer
err = db.Where("sys_user_authority_id in ?", dataId).Count(&total).Error
if err != nil {
return CustomerList, total, err
} else {
err = db.Limit(limit).Offset(offset).Preload("SysUser").Where("sys_user_authority_id in ?", dataId).Find(&CustomerList).Error
}
return CustomerList, total, err
}

View File

@@ -0,0 +1,123 @@
package example
import (
"errors"
"mime/multipart"
"strings"
"git.echol.cn/loser/xiecheng_server/global"
"git.echol.cn/loser/xiecheng_server/model/example"
"git.echol.cn/loser/xiecheng_server/model/example/request"
"git.echol.cn/loser/xiecheng_server/utils/upload"
)
//@author: [piexlmax](https://github.com/piexlmax)
//@function: Upload
//@description: 创建文件上传记录
//@param: file model.ExaFileUploadAndDownload
//@return: error
func (e *FileUploadAndDownloadService) Upload(file example.ExaFileUploadAndDownload) error {
return global.GVA_DB.Create(&file).Error
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: FindFile
//@description: 查询文件记录
//@param: id uint
//@return: model.ExaFileUploadAndDownload, error
func (e *FileUploadAndDownloadService) FindFile(id uint) (example.ExaFileUploadAndDownload, error) {
var file example.ExaFileUploadAndDownload
err := global.GVA_DB.Where("id = ?", id).First(&file).Error
return file, err
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: DeleteFile
//@description: 删除文件记录
//@param: file model.ExaFileUploadAndDownload
//@return: err error
func (e *FileUploadAndDownloadService) DeleteFile(file example.ExaFileUploadAndDownload) (err error) {
var fileFromDb example.ExaFileUploadAndDownload
fileFromDb, err = e.FindFile(file.ID)
if err != nil {
return
}
oss := upload.NewOss()
if err = oss.DeleteFile(fileFromDb.Key); err != nil {
return errors.New("文件删除失败")
}
err = global.GVA_DB.Where("id = ?", file.ID).Unscoped().Delete(&file).Error
return err
}
// EditFileName 编辑文件名或者备注
func (e *FileUploadAndDownloadService) EditFileName(file example.ExaFileUploadAndDownload) (err error) {
var fileFromDb example.ExaFileUploadAndDownload
return global.GVA_DB.Where("id = ?", file.ID).First(&fileFromDb).Update("name", file.Name).Error
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: GetFileRecordInfoList
//@description: 分页获取数据
//@param: info request.ExaAttachmentCategorySearch
//@return: list interface{}, total int64, err error
func (e *FileUploadAndDownloadService) GetFileRecordInfoList(info request.ExaAttachmentCategorySearch) (list []example.ExaFileUploadAndDownload, total int64, err error) {
limit := info.PageSize
offset := info.PageSize * (info.Page - 1)
db := global.GVA_DB.Model(&example.ExaFileUploadAndDownload{})
if len(info.Keyword) > 0 {
db = db.Where("name LIKE ?", "%"+info.Keyword+"%")
}
if info.ClassId > 0 {
db = db.Where("class_id = ?", info.ClassId)
}
err = db.Count(&total).Error
if err != nil {
return
}
err = db.Limit(limit).Offset(offset).Order("id desc").Find(&list).Error
return list, total, err
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: UploadFile
//@description: 根据配置文件判断是文件上传到本地或者七牛云
//@param: header *multipart.FileHeader, noSave string
//@return: file model.ExaFileUploadAndDownload, err error
func (e *FileUploadAndDownloadService) UploadFile(header *multipart.FileHeader, noSave string, classId int) (file example.ExaFileUploadAndDownload, err error) {
oss := upload.NewOss()
filePath, key, uploadErr := oss.UploadFile(header)
if uploadErr != nil {
return file, uploadErr
}
s := strings.Split(header.Filename, ".")
f := example.ExaFileUploadAndDownload{
Url: filePath,
Name: header.Filename,
ClassId: classId,
Tag: s[len(s)-1],
Key: key,
}
if noSave == "0" {
return f, e.Upload(f)
}
return f, nil
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: ImportURL
//@description: 导入URL
//@param: file model.ExaFileUploadAndDownload
//@return: error
func (e *FileUploadAndDownloadService) ImportURL(file *[]example.ExaFileUploadAndDownload) error {
return global.GVA_DB.Create(&file).Error
}

View File

@@ -0,0 +1,217 @@
package system
import (
"context"
"encoding/json"
"fmt"
"git.echol.cn/loser/xiecheng_server/utils/ast"
"github.com/pkg/errors"
"path"
"path/filepath"
"strconv"
"strings"
"time"
"git.echol.cn/loser/xiecheng_server/global"
common "git.echol.cn/loser/xiecheng_server/model/common/request"
model "git.echol.cn/loser/xiecheng_server/model/system"
request "git.echol.cn/loser/xiecheng_server/model/system/request"
"git.echol.cn/loser/xiecheng_server/utils"
"go.uber.org/zap"
)
var AutocodeHistory = new(autoCodeHistory)
type autoCodeHistory struct{}
// Create 创建代码生成器历史记录
// Author [SliverHorn](https://github.com/SliverHorn)
// Author [songzhibin97](https://github.com/songzhibin97)
func (s *autoCodeHistory) Create(ctx context.Context, info request.SysAutoHistoryCreate) error {
create := info.Create()
err := global.GVA_DB.WithContext(ctx).Create(&create).Error
if err != nil {
return errors.Wrap(err, "创建失败!")
}
return nil
}
// First 根据id获取代码生成器历史的数据
// Author [SliverHorn](https://github.com/SliverHorn)
// Author [songzhibin97](https://github.com/songzhibin97)
func (s *autoCodeHistory) First(ctx context.Context, info common.GetById) (string, error) {
var meta string
err := global.GVA_DB.WithContext(ctx).Model(model.SysAutoCodeHistory{}).Where("id = ?", info.ID).Pluck("request", &meta).Error
if err != nil {
return "", errors.Wrap(err, "获取失败!")
}
return meta, nil
}
// Repeat 检测重复
// Author [SliverHorn](https://github.com/SliverHorn)
// Author [songzhibin97](https://github.com/songzhibin97)
func (s *autoCodeHistory) Repeat(businessDB, structName, abbreviation, Package string) bool {
var count int64
global.GVA_DB.Model(&model.SysAutoCodeHistory{}).Where("business_db = ? and (struct_name = ? OR abbreviation = ?) and package = ? and flag = ?", businessDB, structName, abbreviation, Package, 0).Count(&count).Debug()
return count > 0
}
// RollBack 回滚
// Author [SliverHorn](https://github.com/SliverHorn)
// Author [songzhibin97](https://github.com/songzhibin97)
func (s *autoCodeHistory) RollBack(ctx context.Context, info request.SysAutoHistoryRollBack) error {
var history model.SysAutoCodeHistory
err := global.GVA_DB.Where("id = ?", info.ID).First(&history).Error
if err != nil {
return err
}
if history.ExportTemplateID != 0 {
err = global.GVA_DB.Delete(&model.SysExportTemplate{}, "id = ?", history.ExportTemplateID).Error
if err != nil {
return err
}
}
if info.DeleteApi {
ids := info.ApiIds(history)
err = ApiServiceApp.DeleteApisByIds(ids)
if err != nil {
global.GVA_LOG.Error("ClearTag DeleteApiByIds:", zap.Error(err))
}
} // 清除API表
if info.DeleteMenu {
err = BaseMenuServiceApp.DeleteBaseMenu(int(history.MenuID))
if err != nil {
return errors.Wrap(err, "删除菜单失败!")
}
} // 清除菜单表
if info.DeleteTable {
err = s.DropTable(history.BusinessDB, history.Table)
if err != nil {
return errors.Wrap(err, "删除表失败!")
}
} // 删除表
templates := make(map[string]string, len(history.Templates))
for key, template := range history.Templates {
{
server := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server)
keys := strings.Split(key, "/")
key = filepath.Join(keys...)
key = strings.TrimPrefix(key, server)
} // key
{
web := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.WebRoot())
server := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server)
slices := strings.Split(template, "/")
template = filepath.Join(slices...)
ext := path.Ext(template)
switch ext {
case ".js", ".vue":
template = filepath.Join(web, template)
case ".go":
template = filepath.Join(server, template)
}
} // value
templates[key] = template
}
history.Templates = templates
for key, value := range history.Injections {
var injection ast.Ast
switch key {
case ast.TypePackageApiEnter, ast.TypePackageRouterEnter, ast.TypePackageServiceEnter:
case ast.TypePackageApiModuleEnter, ast.TypePackageRouterModuleEnter, ast.TypePackageServiceModuleEnter:
var entity ast.PackageModuleEnter
_ = json.Unmarshal([]byte(value), &entity)
injection = &entity
case ast.TypePackageInitializeGorm:
var entity ast.PackageInitializeGorm
_ = json.Unmarshal([]byte(value), &entity)
injection = &entity
case ast.TypePackageInitializeRouter:
var entity ast.PackageInitializeRouter
_ = json.Unmarshal([]byte(value), &entity)
injection = &entity
case ast.TypePluginGen:
var entity ast.PluginGen
_ = json.Unmarshal([]byte(value), &entity)
injection = &entity
case ast.TypePluginApiEnter, ast.TypePluginRouterEnter, ast.TypePluginServiceEnter:
var entity ast.PluginEnter
_ = json.Unmarshal([]byte(value), &entity)
injection = &entity
case ast.TypePluginInitializeGorm:
var entity ast.PluginInitializeGorm
_ = json.Unmarshal([]byte(value), &entity)
injection = &entity
case ast.TypePluginInitializeRouter:
var entity ast.PluginInitializeRouter
_ = json.Unmarshal([]byte(value), &entity)
injection = &entity
}
if injection == nil {
continue
}
file, _ := injection.Parse("", nil)
if file != nil {
_ = injection.Rollback(file)
err = injection.Format("", nil, file)
if err != nil {
return err
}
fmt.Printf("[filepath:%s]回滚注入代码成功!\n", key)
}
} // 清除注入代码
removeBasePath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, "rm_file", strconv.FormatInt(int64(time.Now().Nanosecond()), 10))
for _, value := range history.Templates {
if !filepath.IsAbs(value) {
continue
}
removePath := filepath.Join(removeBasePath, strings.TrimPrefix(value, global.GVA_CONFIG.AutoCode.Root))
err = utils.FileMove(value, removePath)
if err != nil {
return errors.Wrapf(err, "[src:%s][dst:%s]文件移动失败!", value, removePath)
}
} // 移动文件
err = global.GVA_DB.WithContext(ctx).Model(&model.SysAutoCodeHistory{}).Where("id = ?", info.ID).Update("flag", 1).Error
if err != nil {
return errors.Wrap(err, "更新失败!")
}
return nil
}
// Delete 删除历史数据
// Author [SliverHorn](https://github.com/SliverHorn)
// Author [songzhibin97](https://github.com/songzhibin97)
func (s *autoCodeHistory) Delete(ctx context.Context, info common.GetById) error {
err := global.GVA_DB.WithContext(ctx).Where("id = ?", info.Uint()).Delete(&model.SysAutoCodeHistory{}).Error
if err != nil {
return errors.Wrap(err, "删除失败!")
}
return nil
}
// GetList 获取系统历史数据
// Author [SliverHorn](https://github.com/SliverHorn)
// Author [songzhibin97](https://github.com/songzhibin97)
func (s *autoCodeHistory) GetList(ctx context.Context, info common.PageInfo) (list []model.SysAutoCodeHistory, total int64, err error) {
var entities []model.SysAutoCodeHistory
db := global.GVA_DB.WithContext(ctx).Model(&model.SysAutoCodeHistory{})
err = db.Count(&total).Error
if err != nil {
return nil, total, err
}
err = db.Scopes(info.Paginate()).Order("updated_at desc").Find(&entities).Error
return entities, total, err
}
// DropTable 获取指定数据库和指定数据表的所有字段名,类型值等
// @author: [piexlmax](https://github.com/piexlmax)
func (s *autoCodeHistory) DropTable(BusinessDb, tableName string) error {
if BusinessDb != "" {
return global.MustGetGlobalDBByDBName(BusinessDb).Exec("DROP TABLE " + tableName).Error
} else {
return global.GVA_DB.Exec("DROP TABLE " + tableName).Error
}
}

View File

@@ -0,0 +1,680 @@
package system
import (
"context"
"fmt"
"go/token"
"os"
"path/filepath"
"strings"
"text/template"
"git.echol.cn/loser/xiecheng_server/global"
common "git.echol.cn/loser/xiecheng_server/model/common/request"
model "git.echol.cn/loser/xiecheng_server/model/system"
"git.echol.cn/loser/xiecheng_server/model/system/request"
"git.echol.cn/loser/xiecheng_server/utils"
"git.echol.cn/loser/xiecheng_server/utils/ast"
"github.com/pkg/errors"
"gorm.io/gorm"
)
var AutoCodePackage = new(autoCodePackage)
type autoCodePackage struct{}
// Create 创建包信息
// @author: [piexlmax](https://github.com/piexlmax)
// @author: [SliverHorn](https://github.com/SliverHorn)
func (s *autoCodePackage) Create(ctx context.Context, info *request.SysAutoCodePackageCreate) error {
switch {
case info.Template == "":
return errors.New("模板不能为空!")
case info.Template == "page":
return errors.New("page为表单生成器!")
case info.PackageName == "":
return errors.New("PackageName不能为空!")
case token.IsKeyword(info.PackageName):
return errors.Errorf("%s为go的关键字!", info.PackageName)
case info.Template == "package":
if info.PackageName == "system" || info.PackageName == "example" {
return errors.New("不能使用已保留的package name")
}
default:
break
}
if !errors.Is(global.GVA_DB.Where("package_name = ? and template = ?", info.PackageName, info.Template).First(&model.SysAutoCodePackage{}).Error, gorm.ErrRecordNotFound) {
return errors.New("存在相同PackageName")
}
create := info.Create()
return global.GVA_DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
err := tx.Create(&create).Error
if err != nil {
return errors.Wrap(err, "创建失败!")
}
code := info.AutoCode()
_, asts, creates, err := s.templates(ctx, create, code, true)
if err != nil {
return err
}
for key, value := range creates { // key 为 模版绝对路径
var files *template.Template
files, err = template.ParseFiles(key)
if err != nil {
return errors.Wrapf(err, "[filepath:%s]读取模版文件失败!", key)
}
err = os.MkdirAll(filepath.Dir(value), os.ModePerm)
if err != nil {
return errors.Wrapf(err, "[filepath:%s]创建文件夹失败!", value)
}
var file *os.File
file, err = os.Create(value)
if err != nil {
return errors.Wrapf(err, "[filepath:%s]创建文件夹失败!", value)
}
err = files.Execute(file, code)
_ = file.Close()
if err != nil {
return errors.Wrapf(err, "[filepath:%s]生成失败!", value)
}
fmt.Printf("[template:%s][filepath:%s]生成成功!\n", key, value)
}
for key, value := range asts {
keys := strings.Split(key, "=>")
if len(keys) == 2 {
switch keys[1] {
case ast.TypePluginInitializeV2, ast.TypePackageApiEnter, ast.TypePackageRouterEnter, ast.TypePackageServiceEnter:
file, _ := value.Parse("", nil)
if file != nil {
err = value.Injection(file)
if err != nil {
return err
}
err = value.Format("", nil, file)
if err != nil {
return err
}
}
fmt.Printf("[type:%s]注入成功!\n", key)
}
}
}
return nil
})
}
// Delete 删除包记录
// @author: [piexlmax](https://github.com/piexlmax)
// @author: [SliverHorn](https://github.com/SliverHorn)
func (s *autoCodePackage) Delete(ctx context.Context, info common.GetById) error {
err := global.GVA_DB.WithContext(ctx).Delete(&model.SysAutoCodePackage{}, info.Uint()).Error
if err != nil {
return errors.Wrap(err, "删除失败!")
}
return nil
}
// All 获取所有包
// @author: [piexlmax](https://github.com/piexlmax)
// @author: [SliverHorn](https://github.com/SliverHorn)
func (s *autoCodePackage) All(ctx context.Context) (entities []model.SysAutoCodePackage, err error) {
server := make([]model.SysAutoCodePackage, 0)
plugin := make([]model.SysAutoCodePackage, 0)
serverPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "service")
pluginPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin")
serverDir, err := os.ReadDir(serverPath)
if err != nil {
return nil, errors.Wrap(err, "读取service文件夹失败!")
}
pluginDir, err := os.ReadDir(pluginPath)
if err != nil {
return nil, errors.Wrap(err, "读取plugin文件夹失败!")
}
for i := 0; i < len(serverDir); i++ {
if serverDir[i].IsDir() {
serverPackage := model.SysAutoCodePackage{
PackageName: serverDir[i].Name(),
Template: "package",
Label: serverDir[i].Name() + "包",
Desc: "系统自动读取" + serverDir[i].Name() + "包",
Module: global.GVA_CONFIG.AutoCode.Module,
}
server = append(server, serverPackage)
}
}
for i := 0; i < len(pluginDir); i++ {
if pluginDir[i].IsDir() {
dirNameMap := map[string]bool{
"api": true,
"config": true,
"initialize": true,
"model": true,
"plugin": true,
"router": true,
"service": true,
}
dir, e := os.ReadDir(filepath.Join(pluginPath, pluginDir[i].Name()))
if e != nil {
return nil, errors.Wrap(err, "读取plugin文件夹失败!")
}
//dir目录需要包含所有的dirNameMap
for k := 0; k < len(dir); k++ {
if dir[k].IsDir() {
if ok := dirNameMap[dir[k].Name()]; ok {
delete(dirNameMap, dir[k].Name())
}
}
}
if len(dirNameMap) != 0 {
continue
}
pluginPackage := model.SysAutoCodePackage{
PackageName: pluginDir[i].Name(),
Template: "plugin",
Label: pluginDir[i].Name() + "插件",
Desc: "系统自动读取" + pluginDir[i].Name() + "插件使用前请确认是否为v2版本插件",
Module: global.GVA_CONFIG.AutoCode.Module,
}
plugin = append(plugin, pluginPackage)
}
}
err = global.GVA_DB.WithContext(ctx).Find(&entities).Error
if err != nil {
return nil, errors.Wrap(err, "获取所有包失败!")
}
entitiesMap := make(map[string]model.SysAutoCodePackage)
for i := 0; i < len(entities); i++ {
entitiesMap[entities[i].PackageName] = entities[i]
}
createEntity := []model.SysAutoCodePackage{}
for i := 0; i < len(server); i++ {
if _, ok := entitiesMap[server[i].PackageName]; !ok {
if server[i].Template == "package" {
createEntity = append(createEntity, server[i])
}
}
}
for i := 0; i < len(plugin); i++ {
if _, ok := entitiesMap[plugin[i].PackageName]; !ok {
if plugin[i].Template == "plugin" {
createEntity = append(createEntity, plugin[i])
}
}
}
if len(createEntity) > 0 {
err = global.GVA_DB.WithContext(ctx).Create(&createEntity).Error
if err != nil {
return nil, errors.Wrap(err, "同步失败!")
}
entities = append(entities, createEntity...)
}
return entities, nil
}
// Templates 获取所有模版文件夹
// @author: [SliverHorn](https://github.com/SliverHorn)
func (s *autoCodePackage) Templates(ctx context.Context) ([]string, error) {
templates := make([]string, 0)
entries, err := os.ReadDir("resource")
if err != nil {
return nil, errors.Wrap(err, "读取模版文件夹失败!")
}
for i := 0; i < len(entries); i++ {
if entries[i].IsDir() {
if entries[i].Name() == "page" {
continue
} // page 为表单生成器
if entries[i].Name() == "function" {
continue
} // function 为函数生成器
if entries[i].Name() == "preview" {
continue
} // preview 为预览代码生成器的代码
templates = append(templates, entries[i].Name())
}
}
return templates, nil
}
func (s *autoCodePackage) templates(ctx context.Context, entity model.SysAutoCodePackage, info request.AutoCode, isPackage bool) (code map[string]string, asts map[string]ast.Ast, creates map[string]string, err error) {
code = make(map[string]string)
asts = make(map[string]ast.Ast)
creates = make(map[string]string)
templateDir := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "resource", entity.Template)
templateDirs, err := os.ReadDir(templateDir)
if err != nil {
return nil, nil, nil, errors.Wrapf(err, "读取模版文件夹[%s]失败!", templateDir)
}
for i := 0; i < len(templateDirs); i++ {
second := filepath.Join(templateDir, templateDirs[i].Name())
switch templateDirs[i].Name() {
case "server":
if !info.GenerateServer && !isPackage {
break
}
var secondDirs []os.DirEntry
secondDirs, err = os.ReadDir(second)
if err != nil {
return nil, nil, nil, errors.Wrapf(err, "读取模版文件夹[%s]失败!", second)
}
for j := 0; j < len(secondDirs); j++ {
if secondDirs[j].Name() == ".DS_Store" {
continue
}
three := filepath.Join(second, secondDirs[j].Name())
if !secondDirs[j].IsDir() {
ext := filepath.Ext(secondDirs[j].Name())
if ext != ".template" && ext != ".tpl" {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版后缀!", three)
}
name := strings.TrimSuffix(secondDirs[j].Name(), ext)
if name == "main.go" || name == "plugin.go" {
pluginInitialize := &ast.PluginInitializeV2{
Type: ast.TypePluginInitializeV2,
Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", entity.PackageName, name),
PluginPath: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "initialize", "plugin_biz_v2.go"),
ImportPath: fmt.Sprintf(`"%s/plugin/%s"`, global.GVA_CONFIG.AutoCode.Module, entity.PackageName),
PackageName: entity.PackageName,
}
asts[pluginInitialize.PluginPath+"=>"+pluginInitialize.Type.String()] = pluginInitialize
creates[three] = pluginInitialize.Path
continue
}
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件!", three)
}
switch secondDirs[j].Name() {
case "api", "router", "service":
var threeDirs []os.DirEntry
threeDirs, err = os.ReadDir(three)
if err != nil {
return nil, nil, nil, errors.Wrapf(err, "读取模版文件夹[%s]失败!", three)
}
for k := 0; k < len(threeDirs); k++ {
if threeDirs[k].Name() == ".DS_Store" {
continue
}
four := filepath.Join(three, threeDirs[k].Name())
if threeDirs[k].IsDir() {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件夹!", four)
}
ext := filepath.Ext(four)
if ext != ".template" && ext != ".tpl" {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版后缀!", four)
}
api := strings.Index(threeDirs[k].Name(), "api")
hasEnter := strings.Index(threeDirs[k].Name(), "enter")
router := strings.Index(threeDirs[k].Name(), "router")
service := strings.Index(threeDirs[k].Name(), "service")
if router == -1 && api == -1 && service == -1 && hasEnter == -1 {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件!", four)
}
if entity.Template == "package" {
create := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, secondDirs[j].Name(), entity.PackageName, info.HumpPackageName+".go")
if api != -1 {
create = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, secondDirs[j].Name(), "v1", entity.PackageName, info.HumpPackageName+".go")
}
if hasEnter != -1 {
isApi := strings.Index(secondDirs[j].Name(), "api")
isRouter := strings.Index(secondDirs[j].Name(), "router")
isService := strings.Index(secondDirs[j].Name(), "service")
if isApi != -1 {
packageApiEnter := &ast.PackageEnter{
Type: ast.TypePackageApiEnter,
Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, secondDirs[j].Name(), "v1", "enter.go"),
ImportPath: fmt.Sprintf(`"%s/%s/%s/%s"`, global.GVA_CONFIG.AutoCode.Module, "api", "v1", entity.PackageName),
StructName: utils.FirstUpper(entity.PackageName) + "ApiGroup",
PackageName: entity.PackageName,
PackageStructName: "ApiGroup",
}
asts[packageApiEnter.Path+"=>"+packageApiEnter.Type.String()] = packageApiEnter
packageApiModuleEnter := &ast.PackageModuleEnter{
Type: ast.TypePackageApiModuleEnter,
Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, secondDirs[j].Name(), "v1", entity.PackageName, "enter.go"),
ImportPath: fmt.Sprintf(`"%s/service"`, global.GVA_CONFIG.AutoCode.Module),
StructName: info.StructName + "Api",
AppName: "ServiceGroupApp",
GroupName: utils.FirstUpper(entity.PackageName) + "ServiceGroup",
ModuleName: info.Abbreviation + "Service",
PackageName: "service",
ServiceName: info.StructName + "Service",
}
asts[packageApiModuleEnter.Path+"=>"+packageApiModuleEnter.Type.String()] = packageApiModuleEnter
creates[four] = packageApiModuleEnter.Path
}
if isRouter != -1 {
packageRouterEnter := &ast.PackageEnter{
Type: ast.TypePackageRouterEnter,
Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, secondDirs[j].Name(), "enter.go"),
ImportPath: fmt.Sprintf(`"%s/%s/%s"`, global.GVA_CONFIG.AutoCode.Module, secondDirs[j].Name(), entity.PackageName),
StructName: utils.FirstUpper(entity.PackageName),
PackageName: entity.PackageName,
PackageStructName: "RouterGroup",
}
asts[packageRouterEnter.Path+"=>"+packageRouterEnter.Type.String()] = packageRouterEnter
packageRouterModuleEnter := &ast.PackageModuleEnter{
Type: ast.TypePackageRouterModuleEnter,
Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, secondDirs[j].Name(), entity.PackageName, "enter.go"),
ImportPath: fmt.Sprintf(`api "%s/api/v1"`, global.GVA_CONFIG.AutoCode.Module),
StructName: info.StructName + "Router",
AppName: "ApiGroupApp",
GroupName: utils.FirstUpper(entity.PackageName) + "ApiGroup",
ModuleName: info.Abbreviation + "Api",
PackageName: "api",
ServiceName: info.StructName + "Api",
}
creates[four] = packageRouterModuleEnter.Path
asts[packageRouterModuleEnter.Path+"=>"+packageRouterModuleEnter.Type.String()] = packageRouterModuleEnter
packageInitializeRouter := &ast.PackageInitializeRouter{
Type: ast.TypePackageInitializeRouter,
Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "initialize", "router_biz.go"),
ImportPath: fmt.Sprintf(`"%s/router"`, global.GVA_CONFIG.AutoCode.Module),
AppName: "RouterGroupApp",
GroupName: utils.FirstUpper(entity.PackageName),
ModuleName: entity.PackageName + "Router",
PackageName: "router",
FunctionName: "Init" + info.StructName + "Router",
LeftRouterGroupName: "privateGroup",
RightRouterGroupName: "publicGroup",
}
asts[packageInitializeRouter.Path+"=>"+packageInitializeRouter.Type.String()] = packageInitializeRouter
}
if isService != -1 {
path := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, secondDirs[j].Name(), strings.TrimSuffix(threeDirs[k].Name(), ext))
importPath := fmt.Sprintf(`"%s/service/%s"`, global.GVA_CONFIG.AutoCode.Module, entity.PackageName)
packageServiceEnter := &ast.PackageEnter{
Type: ast.TypePackageServiceEnter,
Path: path,
ImportPath: importPath,
StructName: utils.FirstUpper(entity.PackageName) + "ServiceGroup",
PackageName: entity.PackageName,
PackageStructName: "ServiceGroup",
}
asts[packageServiceEnter.Path+"=>"+packageServiceEnter.Type.String()] = packageServiceEnter
packageServiceModuleEnter := &ast.PackageModuleEnter{
Type: ast.TypePackageServiceModuleEnter,
Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, secondDirs[j].Name(), entity.PackageName, "enter.go"),
StructName: info.StructName + "Service",
}
asts[packageServiceModuleEnter.Path+"=>"+packageServiceModuleEnter.Type.String()] = packageServiceModuleEnter
creates[four] = packageServiceModuleEnter.Path
}
continue
}
code[four] = create
continue
}
if hasEnter != -1 {
isApi := strings.Index(secondDirs[j].Name(), "api")
isRouter := strings.Index(secondDirs[j].Name(), "router")
isService := strings.Index(secondDirs[j].Name(), "service")
if isRouter != -1 {
pluginRouterEnter := &ast.PluginEnter{
Type: ast.TypePluginRouterEnter,
Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", entity.PackageName, secondDirs[j].Name(), strings.TrimSuffix(threeDirs[k].Name(), ext)),
ImportPath: fmt.Sprintf(`"%s/plugin/%s/api"`, global.GVA_CONFIG.AutoCode.Module, entity.PackageName),
StructName: info.StructName,
StructCamelName: info.Abbreviation,
ModuleName: "api" + info.StructName,
GroupName: "Api",
PackageName: "api",
ServiceName: info.StructName,
}
asts[pluginRouterEnter.Path+"=>"+pluginRouterEnter.Type.String()] = pluginRouterEnter
creates[four] = pluginRouterEnter.Path
}
if isApi != -1 {
pluginApiEnter := &ast.PluginEnter{
Type: ast.TypePluginApiEnter,
Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", entity.PackageName, secondDirs[j].Name(), strings.TrimSuffix(threeDirs[k].Name(), ext)),
ImportPath: fmt.Sprintf(`"%s/plugin/%s/service"`, global.GVA_CONFIG.AutoCode.Module, entity.PackageName),
StructName: info.StructName,
StructCamelName: info.Abbreviation,
ModuleName: "service" + info.StructName,
GroupName: "Service",
PackageName: "service",
ServiceName: info.StructName,
}
asts[pluginApiEnter.Path+"=>"+pluginApiEnter.Type.String()] = pluginApiEnter
creates[four] = pluginApiEnter.Path
}
if isService != -1 {
pluginServiceEnter := &ast.PluginEnter{
Type: ast.TypePluginServiceEnter,
Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", entity.PackageName, secondDirs[j].Name(), strings.TrimSuffix(threeDirs[k].Name(), ext)),
StructName: info.StructName,
StructCamelName: info.Abbreviation,
}
asts[pluginServiceEnter.Path+"=>"+pluginServiceEnter.Type.String()] = pluginServiceEnter
creates[four] = pluginServiceEnter.Path
}
continue
} // enter.go
create := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", entity.PackageName, secondDirs[j].Name(), info.HumpPackageName+".go")
code[four] = create
}
case "gen", "config", "initialize", "plugin", "response":
if entity.Template == "package" {
continue
} // package模板不需要生成gen, config, initialize
var threeDirs []os.DirEntry
threeDirs, err = os.ReadDir(three)
if err != nil {
return nil, nil, nil, errors.Wrapf(err, "读取模版文件夹[%s]失败!", three)
}
for k := 0; k < len(threeDirs); k++ {
if threeDirs[k].Name() == ".DS_Store" {
continue
}
four := filepath.Join(three, threeDirs[k].Name())
if threeDirs[k].IsDir() {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件夹!", four)
}
ext := filepath.Ext(four)
if ext != ".template" && ext != ".tpl" {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版后缀!", four)
}
gen := strings.Index(threeDirs[k].Name(), "gen")
api := strings.Index(threeDirs[k].Name(), "api")
menu := strings.Index(threeDirs[k].Name(), "menu")
viper := strings.Index(threeDirs[k].Name(), "viper")
plugin := strings.Index(threeDirs[k].Name(), "plugin")
config := strings.Index(threeDirs[k].Name(), "config")
router := strings.Index(threeDirs[k].Name(), "router")
hasGorm := strings.Index(threeDirs[k].Name(), "gorm")
response := strings.Index(threeDirs[k].Name(), "response")
if gen != -1 && api != -1 && menu != -1 && viper != -1 && plugin != -1 && config != -1 && router != -1 && hasGorm != -1 && response != -1 {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件!", four)
}
if api != -1 || menu != -1 || viper != -1 || response != -1 || plugin != -1 || config != -1 {
creates[four] = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", entity.PackageName, secondDirs[j].Name(), strings.TrimSuffix(threeDirs[k].Name(), ext))
}
if gen != -1 {
pluginGen := &ast.PluginGen{
Type: ast.TypePluginGen,
Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", entity.PackageName, secondDirs[j].Name(), strings.TrimSuffix(threeDirs[k].Name(), ext)),
ImportPath: fmt.Sprintf(`"%s/plugin/%s/model"`, global.GVA_CONFIG.AutoCode.Module, entity.PackageName),
StructName: info.StructName,
PackageName: "model",
IsNew: true,
}
asts[pluginGen.Path+"=>"+pluginGen.Type.String()] = pluginGen
creates[four] = pluginGen.Path
}
if hasGorm != -1 {
pluginInitializeGorm := &ast.PluginInitializeGorm{
Type: ast.TypePluginInitializeGorm,
Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", entity.PackageName, secondDirs[j].Name(), strings.TrimSuffix(threeDirs[k].Name(), ext)),
ImportPath: fmt.Sprintf(`"%s/plugin/%s/model"`, global.GVA_CONFIG.AutoCode.Module, entity.PackageName),
StructName: info.StructName,
PackageName: "model",
IsNew: true,
}
asts[pluginInitializeGorm.Path+"=>"+pluginInitializeGorm.Type.String()] = pluginInitializeGorm
creates[four] = pluginInitializeGorm.Path
}
if router != -1 {
pluginInitializeRouter := &ast.PluginInitializeRouter{
Type: ast.TypePluginInitializeRouter,
Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", entity.PackageName, secondDirs[j].Name(), strings.TrimSuffix(threeDirs[k].Name(), ext)),
ImportPath: fmt.Sprintf(`"%s/plugin/%s/router"`, global.GVA_CONFIG.AutoCode.Module, entity.PackageName),
AppName: "Router",
GroupName: info.StructName,
PackageName: "router",
FunctionName: "Init",
LeftRouterGroupName: "public",
RightRouterGroupName: "private",
}
asts[pluginInitializeRouter.Path+"=>"+pluginInitializeRouter.Type.String()] = pluginInitializeRouter
creates[four] = pluginInitializeRouter.Path
}
}
case "model":
var threeDirs []os.DirEntry
threeDirs, err = os.ReadDir(three)
if err != nil {
return nil, nil, nil, errors.Wrapf(err, "读取模版文件夹[%s]失败!", three)
}
for k := 0; k < len(threeDirs); k++ {
if threeDirs[k].Name() == ".DS_Store" {
continue
}
four := filepath.Join(three, threeDirs[k].Name())
if threeDirs[k].IsDir() {
var fourDirs []os.DirEntry
fourDirs, err = os.ReadDir(four)
if err != nil {
return nil, nil, nil, errors.Wrapf(err, "读取模版文件夹[%s]失败!", four)
}
for l := 0; l < len(fourDirs); l++ {
if fourDirs[l].Name() == ".DS_Store" {
continue
}
five := filepath.Join(four, fourDirs[l].Name())
if fourDirs[l].IsDir() {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件夹!", five)
}
ext := filepath.Ext(five)
if ext != ".template" && ext != ".tpl" {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版后缀!", five)
}
hasRequest := strings.Index(fourDirs[l].Name(), "request")
if hasRequest == -1 {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件!", five)
}
create := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", entity.PackageName, secondDirs[j].Name(), threeDirs[k].Name(), info.HumpPackageName+".go")
if entity.Template == "package" {
create = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, secondDirs[j].Name(), entity.PackageName, threeDirs[k].Name(), info.HumpPackageName+".go")
}
code[five] = create
}
continue
}
ext := filepath.Ext(threeDirs[k].Name())
if ext != ".template" && ext != ".tpl" {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版后缀!", four)
}
hasModel := strings.Index(threeDirs[k].Name(), "model")
if hasModel == -1 {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件!", four)
}
create := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", entity.PackageName, secondDirs[j].Name(), info.HumpPackageName+".go")
if entity.Template == "package" {
packageInitializeGorm := &ast.PackageInitializeGorm{
Type: ast.TypePackageInitializeGorm,
Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "initialize", "gorm_biz.go"),
ImportPath: fmt.Sprintf(`"%s/model/%s"`, global.GVA_CONFIG.AutoCode.Module, entity.PackageName),
Business: info.BusinessDB,
StructName: info.StructName,
PackageName: entity.PackageName,
IsNew: true,
}
code[four] = packageInitializeGorm.Path
asts[packageInitializeGorm.Path+"=>"+packageInitializeGorm.Type.String()] = packageInitializeGorm
create = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, secondDirs[j].Name(), entity.PackageName, info.HumpPackageName+".go")
}
code[four] = create
}
default:
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件夹!", three)
}
}
case "web":
if !info.GenerateWeb && !isPackage {
break
}
var secondDirs []os.DirEntry
secondDirs, err = os.ReadDir(second)
if err != nil {
return nil, nil, nil, errors.Wrapf(err, "读取模版文件夹[%s]失败!", second)
}
for j := 0; j < len(secondDirs); j++ {
if secondDirs[j].Name() == ".DS_Store" {
continue
}
three := filepath.Join(second, secondDirs[j].Name())
if !secondDirs[j].IsDir() {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件!", three)
}
switch secondDirs[j].Name() {
case "api", "form", "view", "table":
var threeDirs []os.DirEntry
threeDirs, err = os.ReadDir(three)
if err != nil {
return nil, nil, nil, errors.Wrapf(err, "读取模版文件夹[%s]失败!", three)
}
for k := 0; k < len(threeDirs); k++ {
if threeDirs[k].Name() == ".DS_Store" {
continue
}
four := filepath.Join(three, threeDirs[k].Name())
if threeDirs[k].IsDir() {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件夹!", four)
}
ext := filepath.Ext(four)
if ext != ".template" && ext != ".tpl" {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版后缀!", four)
}
api := strings.Index(threeDirs[k].Name(), "api")
form := strings.Index(threeDirs[k].Name(), "form")
view := strings.Index(threeDirs[k].Name(), "view")
table := strings.Index(threeDirs[k].Name(), "table")
if api == -1 && form == -1 && view == -1 && table == -1 {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件!", four)
}
if entity.Template == "package" {
if view != -1 || table != -1 {
formPath := filepath.Join(three, "form.vue"+ext)
value, ok := code[formPath]
if ok {
value = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.WebRoot(), secondDirs[j].Name(), entity.PackageName, info.PackageName, info.PackageName+"Form"+filepath.Ext(strings.TrimSuffix(threeDirs[k].Name(), ext)))
code[formPath] = value
}
}
create := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.WebRoot(), secondDirs[j].Name(), entity.PackageName, info.PackageName, info.PackageName+filepath.Ext(strings.TrimSuffix(threeDirs[k].Name(), ext)))
if api != -1 {
create = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.WebRoot(), secondDirs[j].Name(), entity.PackageName, info.PackageName+filepath.Ext(strings.TrimSuffix(threeDirs[k].Name(), ext)))
}
code[four] = create
continue
}
create := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.WebRoot(), "plugin", entity.PackageName, secondDirs[j].Name(), info.PackageName+filepath.Ext(strings.TrimSuffix(threeDirs[k].Name(), ext)))
code[four] = create
}
default:
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件夹!", three)
}
}
case "readme.txt.tpl", "readme.txt.template":
continue
default:
if templateDirs[i].Name() == ".DS_Store" {
continue
}
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件!", second)
}
}
return code, asts, creates, nil
}

View File

@@ -0,0 +1,105 @@
package system
import (
"context"
model "git.echol.cn/loser/xiecheng_server/model/system"
"git.echol.cn/loser/xiecheng_server/model/system/request"
"reflect"
"testing"
)
func Test_autoCodePackage_Create(t *testing.T) {
type args struct {
ctx context.Context
info *request.SysAutoCodePackageCreate
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "测试 package",
args: args{
ctx: context.Background(),
info: &request.SysAutoCodePackageCreate{
Template: "package",
PackageName: "gva",
},
},
wantErr: false,
},
{
name: "测试 plugin",
args: args{
ctx: context.Background(),
info: &request.SysAutoCodePackageCreate{
Template: "plugin",
PackageName: "gva",
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := &autoCodePackage{}
if err := a.Create(tt.args.ctx, tt.args.info); (err != nil) != tt.wantErr {
t.Errorf("Create() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func Test_autoCodePackage_templates(t *testing.T) {
type args struct {
ctx context.Context
entity model.SysAutoCodePackage
info request.AutoCode
}
tests := []struct {
name string
args args
wantCode map[string]string
wantEnter map[string]map[string]string
wantErr bool
}{
{
name: "测试1",
args: args{
ctx: context.Background(),
entity: model.SysAutoCodePackage{
Desc: "描述",
Label: "展示名",
Template: "plugin",
PackageName: "preview",
},
info: request.AutoCode{
Abbreviation: "user",
HumpPackageName: "user",
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &autoCodePackage{}
gotCode, gotEnter, gotCreates, err := s.templates(tt.args.ctx, tt.args.entity, tt.args.info)
if (err != nil) != tt.wantErr {
t.Errorf("templates() error = %v, wantErr %v", err, tt.wantErr)
return
}
for key, value := range gotCode {
t.Logf("\n")
t.Logf(key)
t.Logf(value)
t.Logf("\n")
}
t.Log(gotCreates)
if !reflect.DeepEqual(gotEnter, tt.wantEnter) {
t.Errorf("templates() gotEnter = %v, want %v", gotEnter, tt.wantEnter)
}
})
}
}

View File

@@ -0,0 +1,249 @@
package system
import (
"bytes"
"context"
"fmt"
"git.echol.cn/loser/xiecheng_server/global"
"git.echol.cn/loser/xiecheng_server/model/system"
"git.echol.cn/loser/xiecheng_server/model/system/request"
"git.echol.cn/loser/xiecheng_server/utils"
"git.echol.cn/loser/xiecheng_server/utils/ast"
"github.com/mholt/archiver/v4"
cp "github.com/otiai10/copy"
"github.com/pkg/errors"
"go.uber.org/zap"
"go/parser"
"go/printer"
"go/token"
"io"
"mime/multipart"
"os"
"path/filepath"
"strings"
)
var AutoCodePlugin = new(autoCodePlugin)
type autoCodePlugin struct{}
// Install 插件安装
func (s *autoCodePlugin) Install(file *multipart.FileHeader) (web, server int, err error) {
const GVAPLUGPINATH = "./gva-plug-temp/"
defer os.RemoveAll(GVAPLUGPINATH)
_, err = os.Stat(GVAPLUGPINATH)
if os.IsNotExist(err) {
os.Mkdir(GVAPLUGPINATH, os.ModePerm)
}
src, err := file.Open()
if err != nil {
return -1, -1, err
}
defer src.Close()
out, err := os.Create(GVAPLUGPINATH + file.Filename)
if err != nil {
return -1, -1, err
}
defer out.Close()
_, err = io.Copy(out, src)
paths, err := utils.Unzip(GVAPLUGPINATH+file.Filename, GVAPLUGPINATH)
paths = filterFile(paths)
var webIndex = -1
var serverIndex = -1
webPlugin := ""
serverPlugin := ""
for i := range paths {
paths[i] = filepath.ToSlash(paths[i])
pathArr := strings.Split(paths[i], "/")
ln := len(pathArr)
if ln < 4 {
continue
}
if pathArr[2]+"/"+pathArr[3] == `server/plugin` && len(serverPlugin) == 0 {
serverPlugin = filepath.Join(pathArr[0], pathArr[1], pathArr[2], pathArr[3])
}
if pathArr[2]+"/"+pathArr[3] == `web/plugin` && len(webPlugin) == 0 {
webPlugin = filepath.Join(pathArr[0], pathArr[1], pathArr[2], pathArr[3])
}
}
if len(serverPlugin) == 0 && len(webPlugin) == 0 {
zap.L().Error("非标准插件,请按照文档自动迁移使用")
return webIndex, serverIndex, errors.New("非标准插件,请按照文档自动迁移使用")
}
if len(serverPlugin) != 0 {
err = installation(serverPlugin, global.GVA_CONFIG.AutoCode.Server, global.GVA_CONFIG.AutoCode.Server)
if err != nil {
return webIndex, serverIndex, err
}
}
if len(webPlugin) != 0 {
err = installation(webPlugin, global.GVA_CONFIG.AutoCode.Server, global.GVA_CONFIG.AutoCode.Web)
if err != nil {
return webIndex, serverIndex, err
}
}
return 1, 1, err
}
func installation(path string, formPath string, toPath string) error {
arr := strings.Split(filepath.ToSlash(path), "/")
ln := len(arr)
if ln < 3 {
return errors.New("arr")
}
name := arr[ln-3]
var form = filepath.Join(global.GVA_CONFIG.AutoCode.Root, formPath, path)
var to = filepath.Join(global.GVA_CONFIG.AutoCode.Root, toPath, "plugin")
_, err := os.Stat(to + name)
if err == nil {
zap.L().Error("autoPath 已存在同名插件,请自行手动安装", zap.String("to", to))
return errors.New(toPath + "已存在同名插件,请自行手动安装")
}
return cp.Copy(form, to, cp.Options{Skip: skipMacSpecialDocument})
}
func filterFile(paths []string) []string {
np := make([]string, 0, len(paths))
for _, path := range paths {
if ok, _ := skipMacSpecialDocument(nil, path, ""); ok {
continue
}
np = append(np, path)
}
return np
}
func skipMacSpecialDocument(_ os.FileInfo, src, _ string) (bool, error) {
if strings.Contains(src, ".DS_Store") || strings.Contains(src, "__MACOSX") {
return true, nil
}
return false, nil
}
func (s *autoCodePlugin) PubPlug(plugName string) (zipPath string, err error) {
if plugName == "" {
return "", errors.New("插件名称不能为空")
}
// 防止路径穿越
plugName = filepath.Clean(plugName)
webPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Web, "plugin", plugName)
serverPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", plugName)
// 创建一个新的zip文件
// 判断目录是否存在
_, err = os.Stat(webPath)
if err != nil {
return "", errors.New("web路径不存在")
}
_, err = os.Stat(serverPath)
if err != nil {
return "", errors.New("server路径不存在")
}
fileName := plugName + ".zip"
// 创建一个新的zip文件
files, err := archiver.FilesFromDisk(nil, map[string]string{
webPath: plugName + "/web/plugin/" + plugName,
serverPath: plugName + "/server/plugin/" + plugName,
})
// create the output file we'll write to
out, err := os.Create(fileName)
if err != nil {
return
}
defer out.Close()
// we can use the CompressedArchive type to gzip a tarball
// (compression is not required; you could use Tar directly)
format := archiver.Archive{
Archival: archiver.Zip{},
}
// create the archive
err = format.Archive(context.Background(), out, files)
if err != nil {
return
}
return filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, fileName), nil
}
func (s *autoCodePlugin) InitMenu(menuInfo request.InitMenu) (err error) {
menuPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", menuInfo.PlugName, "initialize", "menu.go")
src, err := os.ReadFile(menuPath)
if err != nil {
fmt.Println(err)
}
fileSet := token.NewFileSet()
astFile, err := parser.ParseFile(fileSet, "", src, 0)
arrayAst := ast.FindArray(astFile, "model", "SysBaseMenu")
var menus []system.SysBaseMenu
parentMenu := []system.SysBaseMenu{
{
ParentId: 0,
Path: menuInfo.PlugName + "Menu",
Name: menuInfo.PlugName + "Menu",
Hidden: false,
Component: "view/routerHolder.vue",
Sort: 0,
Meta: system.Meta{
Title: menuInfo.ParentMenu,
Icon: "school",
},
},
}
err = global.GVA_DB.Find(&menus, "id in (?)", menuInfo.Menus).Error
if err != nil {
return err
}
menus = append(parentMenu, menus...)
menuExpr := ast.CreateMenuStructAst(menus)
arrayAst.Elts = *menuExpr
var out []byte
bf := bytes.NewBuffer(out)
printer.Fprint(bf, fileSet, astFile)
os.WriteFile(menuPath, bf.Bytes(), 0666)
return nil
}
func (s *autoCodePlugin) InitAPI(apiInfo request.InitApi) (err error) {
apiPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", apiInfo.PlugName, "initialize", "api.go")
src, err := os.ReadFile(apiPath)
if err != nil {
fmt.Println(err)
}
fileSet := token.NewFileSet()
astFile, err := parser.ParseFile(fileSet, "", src, 0)
arrayAst := ast.FindArray(astFile, "model", "SysApi")
var apis []system.SysApi
err = global.GVA_DB.Find(&apis, "id in (?)", apiInfo.APIs).Error
if err != nil {
return err
}
apisExpr := ast.CreateApiStructAst(apis)
arrayAst.Elts = *apisExpr
var out []byte
bf := bytes.NewBuffer(out)
printer.Fprint(bf, fileSet, astFile)
os.WriteFile(apiPath, bf.Bytes(), 0666)
return nil
}

View File

@@ -0,0 +1,452 @@
package system
import (
"context"
"encoding/json"
"fmt"
"go/ast"
"go/format"
"go/parser"
"go/token"
"os"
"path/filepath"
"strings"
"text/template"
"git.echol.cn/loser/xiecheng_server/global"
model "git.echol.cn/loser/xiecheng_server/model/system"
"git.echol.cn/loser/xiecheng_server/model/system/request"
utilsAst "git.echol.cn/loser/xiecheng_server/utils/ast"
"github.com/pkg/errors"
"gorm.io/gorm"
)
var AutoCodeTemplate = new(autoCodeTemplate)
type autoCodeTemplate struct{}
func (s *autoCodeTemplate) checkPackage(Pkg string, template string) (err error) {
switch template {
case "package":
apiEnter := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "api", "v1", Pkg, "enter.go")
_, err = os.Stat(apiEnter)
if err != nil {
return fmt.Errorf("package结构异常,缺少api/v1/%s/enter.go", Pkg)
}
serviceEnter := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "service", Pkg, "enter.go")
_, err = os.Stat(serviceEnter)
if err != nil {
return fmt.Errorf("package结构异常,缺少service/%s/enter.go", Pkg)
}
routerEnter := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "router", Pkg, "enter.go")
_, err = os.Stat(routerEnter)
if err != nil {
return fmt.Errorf("package结构异常,缺少router/%s/enter.go", Pkg)
}
case "plugin":
pluginEnter := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", Pkg, "plugin.go")
_, err = os.Stat(pluginEnter)
if err != nil {
return fmt.Errorf("plugin结构异常,缺少plugin/%s/plugin.go", Pkg)
}
}
return nil
}
// Create 创建生成自动化代码
func (s *autoCodeTemplate) Create(ctx context.Context, info request.AutoCode) error {
history := info.History()
var autoPkg model.SysAutoCodePackage
err := global.GVA_DB.WithContext(ctx).Where("package_name = ?", info.Package).First(&autoPkg).Error
if err != nil {
return errors.Wrap(err, "查询包失败!")
}
err = s.checkPackage(info.Package, autoPkg.Template)
if err != nil {
return err
}
// 增加判断: 重复创建struct 或者重复的简称
if AutocodeHistory.Repeat(info.BusinessDB, info.StructName, info.Abbreviation, info.Package) {
return errors.New("已经创建过此数据结构,请勿重复创建!")
}
generate, templates, injections, err := s.generate(ctx, info, autoPkg)
if err != nil {
return err
}
for key, builder := range generate {
err = os.MkdirAll(filepath.Dir(key), os.ModePerm)
if err != nil {
return errors.Wrapf(err, "[filepath:%s]创建文件夹失败!", key)
}
err = os.WriteFile(key, []byte(builder.String()), 0666)
if err != nil {
return errors.Wrapf(err, "[filepath:%s]写入文件失败!", key)
}
}
// 自动创建api
if info.AutoCreateApiToSql && !info.OnlyTemplate {
apis := info.Apis()
err := global.GVA_DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
for _, v := range apis {
var api model.SysApi
var id uint
err := tx.Where("path = ? AND method = ?", v.Path, v.Method).First(&api).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
if err = tx.Create(&v).Error; err != nil { // 遇到错误时回滚事务
return err
}
id = v.ID
} else {
id = api.ID
}
history.ApiIDs = append(history.ApiIDs, id)
}
return nil
})
if err != nil {
return err
}
}
// 自动创建menu
if info.AutoCreateMenuToSql {
var entity model.SysBaseMenu
var id uint
err := global.GVA_DB.WithContext(ctx).First(&entity, "name = ?", info.Abbreviation).Error
if err == nil {
id = entity.ID
} else {
entity = info.Menu(autoPkg.Template)
if info.AutoCreateBtnAuth && !info.OnlyTemplate {
entity.MenuBtn = []model.SysBaseMenuBtn{
{SysBaseMenuID: entity.ID, Name: "add", Desc: "新增"},
{SysBaseMenuID: entity.ID, Name: "batchDelete", Desc: "批量删除"},
{SysBaseMenuID: entity.ID, Name: "delete", Desc: "删除"},
{SysBaseMenuID: entity.ID, Name: "edit", Desc: "编辑"},
{SysBaseMenuID: entity.ID, Name: "info", Desc: "详情"},
}
if info.HasExcel {
excelBtn := []model.SysBaseMenuBtn{
{SysBaseMenuID: entity.ID, Name: "exportTemplate", Desc: "导出模板"},
{SysBaseMenuID: entity.ID, Name: "exportExcel", Desc: "导出Excel"},
{SysBaseMenuID: entity.ID, Name: "importExcel", Desc: "导入Excel"},
}
entity.MenuBtn = append(entity.MenuBtn, excelBtn...)
}
}
err = global.GVA_DB.WithContext(ctx).Create(&entity).Error
id = entity.ID
if err != nil {
return errors.Wrap(err, "创建菜单失败!")
}
}
history.MenuID = id
}
if info.HasExcel {
dbName := info.BusinessDB
name := info.Package + "_" + info.StructName
tableName := info.TableName
fieldsMap := make(map[string]string, len(info.Fields))
for _, field := range info.Fields {
if field.Excel {
fieldsMap[field.ColumnName] = field.FieldDesc
}
}
templateInfo, _ := json.Marshal(fieldsMap)
sysExportTemplate := model.SysExportTemplate{
DBName: dbName,
Name: name,
TableName: tableName,
TemplateID: name,
TemplateInfo: string(templateInfo),
}
err = SysExportTemplateServiceApp.CreateSysExportTemplate(&sysExportTemplate)
if err != nil {
return err
}
history.ExportTemplateID = sysExportTemplate.ID
}
// 创建历史记录
history.Templates = templates
history.Injections = make(map[string]string, len(injections))
for key, value := range injections {
bytes, _ := json.Marshal(value)
history.Injections[key] = string(bytes)
}
err = AutocodeHistory.Create(ctx, history)
if err != nil {
return err
}
return nil
}
// Preview 预览自动化代码
func (s *autoCodeTemplate) Preview(ctx context.Context, info request.AutoCode) (map[string]string, error) {
var entity model.SysAutoCodePackage
err := global.GVA_DB.WithContext(ctx).Where("package_name = ?", info.Package).First(&entity).Error
if err != nil {
return nil, errors.Wrap(err, "查询包失败!")
}
// 增加判断: 重复创建struct 或者重复的简称
if AutocodeHistory.Repeat(info.BusinessDB, info.StructName, info.Abbreviation, info.Package) && !info.IsAdd {
return nil, errors.New("已经创建过此数据结构或重复简称,请勿重复创建!")
}
preview := make(map[string]string)
codes, _, _, err := s.generate(ctx, info, entity)
if err != nil {
return nil, err
}
for key, writer := range codes {
if len(key) > len(global.GVA_CONFIG.AutoCode.Root) {
key, _ = filepath.Rel(global.GVA_CONFIG.AutoCode.Root, key)
}
// 获取key的后缀 取消.
suffix := filepath.Ext(key)[1:]
var builder strings.Builder
builder.WriteString("```" + suffix + "\n\n")
builder.WriteString(writer.String())
builder.WriteString("\n\n```")
preview[key] = builder.String()
}
return preview, nil
}
func (s *autoCodeTemplate) generate(ctx context.Context, info request.AutoCode, entity model.SysAutoCodePackage) (map[string]strings.Builder, map[string]string, map[string]utilsAst.Ast, error) {
templates, asts, _, err := AutoCodePackage.templates(ctx, entity, info, false)
if err != nil {
return nil, nil, nil, err
}
code := make(map[string]strings.Builder)
for key, create := range templates {
var files *template.Template
files, err = template.ParseFiles(key)
if err != nil {
return nil, nil, nil, errors.Wrapf(err, "[filpath:%s]读取模版文件失败!", key)
}
var builder strings.Builder
err = files.Execute(&builder, info)
if err != nil {
return nil, nil, nil, errors.Wrapf(err, "[filpath:%s]生成文件失败!", create)
}
code[create] = builder
} // 生成文件
injections := make(map[string]utilsAst.Ast, len(asts))
for key, value := range asts {
keys := strings.Split(key, "=>")
if len(keys) == 2 {
if keys[1] == utilsAst.TypePluginInitializeV2 {
continue
}
if info.OnlyTemplate {
if keys[1] == utilsAst.TypePackageInitializeGorm || keys[1] == utilsAst.TypePluginInitializeGorm {
continue
}
}
if !info.AutoMigrate {
if keys[1] == utilsAst.TypePackageInitializeGorm || keys[1] == utilsAst.TypePluginInitializeGorm {
continue
}
}
var builder strings.Builder
parse, _ := value.Parse("", &builder)
if parse != nil {
_ = value.Injection(parse)
err = value.Format("", &builder, parse)
if err != nil {
return nil, nil, nil, err
}
code[keys[0]] = builder
injections[keys[1]] = value
fmt.Println(keys[0], "注入成功!")
}
}
}
// 注入代码
return code, templates, injections, nil
}
func (s *autoCodeTemplate) AddFunc(info request.AutoFunc) error {
autoPkg := model.SysAutoCodePackage{}
err := global.GVA_DB.First(&autoPkg, "package_name = ?", info.Package).Error
if err != nil {
return err
}
if autoPkg.Template != "package" {
info.IsPlugin = true
}
err = s.addTemplateToFile("api.go", info)
if err != nil {
return err
}
err = s.addTemplateToFile("server.go", info)
if err != nil {
return err
}
err = s.addTemplateToFile("api.js", info)
if err != nil {
return err
}
return s.addTemplateToAst("router", info)
}
func (s *autoCodeTemplate) GetApiAndServer(info request.AutoFunc) (map[string]string, error) {
autoPkg := model.SysAutoCodePackage{}
err := global.GVA_DB.First(&autoPkg, "package_name = ?", info.Package).Error
if err != nil {
return nil, err
}
if autoPkg.Template != "package" {
info.IsPlugin = true
}
apiStr, err := s.getTemplateStr("api.go", info)
if err != nil {
return nil, err
}
serverStr, err := s.getTemplateStr("server.go", info)
if err != nil {
return nil, err
}
jsStr, err := s.getTemplateStr("api.js", info)
if err != nil {
return nil, err
}
return map[string]string{"api": apiStr, "server": serverStr, "js": jsStr}, nil
}
func (s *autoCodeTemplate) getTemplateStr(t string, info request.AutoFunc) (string, error) {
tempPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "resource", "function", t+".tpl")
files, err := template.ParseFiles(tempPath)
if err != nil {
return "", errors.Wrapf(err, "[filepath:%s]读取模版文件失败!", tempPath)
}
var builder strings.Builder
err = files.Execute(&builder, info)
if err != nil {
fmt.Println(err.Error())
return "", errors.Wrapf(err, "[filpath:%s]生成文件失败!", tempPath)
}
return builder.String(), nil
}
func (s *autoCodeTemplate) addTemplateToAst(t string, info request.AutoFunc) error {
tPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "router", info.Package, info.HumpPackageName+".go")
funcName := fmt.Sprintf("Init%sRouter", info.StructName)
routerStr := "RouterWithoutAuth"
if info.IsAuth {
routerStr = "Router"
}
stmtStr := fmt.Sprintf("%s%s.%s(\"%s\", %sApi.%s)", info.Abbreviation, routerStr, info.Method, info.Router, info.Abbreviation, info.FuncName)
if info.IsPlugin {
tPath = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", info.Package, "router", info.HumpPackageName+".go")
stmtStr = fmt.Sprintf("group.%s(\"%s\", api%s.%s)", info.Method, info.Router, info.StructName, info.FuncName)
funcName = "Init"
}
src, err := os.ReadFile(tPath)
if err != nil {
return err
}
fileSet := token.NewFileSet()
astFile, err := parser.ParseFile(fileSet, "", src, 0)
if err != nil {
return err
}
funcDecl := utilsAst.FindFunction(astFile, funcName)
stmtNode := utilsAst.CreateStmt(stmtStr)
if info.IsAuth {
for i := 0; i < len(funcDecl.Body.List); i++ {
st := funcDecl.Body.List[i]
// 使用类型断言来检查stmt是否是一个块语句
if blockStmt, ok := st.(*ast.BlockStmt); ok {
// 如果是,插入代码 跳出
blockStmt.List = append(blockStmt.List, stmtNode)
break
}
}
} else {
for i := len(funcDecl.Body.List) - 1; i >= 0; i-- {
st := funcDecl.Body.List[i]
// 使用类型断言来检查stmt是否是一个块语句
if blockStmt, ok := st.(*ast.BlockStmt); ok {
// 如果是,插入代码 跳出
blockStmt.List = append(blockStmt.List, stmtNode)
break
}
}
}
// 创建一个新的文件
f, err := os.Create(tPath)
if err != nil {
return err
}
defer f.Close()
if err := format.Node(f, fileSet, astFile); err != nil {
return err
}
return err
}
func (s *autoCodeTemplate) addTemplateToFile(t string, info request.AutoFunc) error {
getTemplateStr, err := s.getTemplateStr(t, info)
if err != nil {
return err
}
var target string
switch t {
case "api.go":
if info.IsAi && info.ApiFunc != "" {
getTemplateStr = info.ApiFunc
}
target = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "api", "v1", info.Package, info.HumpPackageName+".go")
case "server.go":
if info.IsAi && info.ServerFunc != "" {
getTemplateStr = info.ServerFunc
}
target = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "service", info.Package, info.HumpPackageName+".go")
case "api.js":
if info.IsAi && info.JsFunc != "" {
getTemplateStr = info.JsFunc
}
target = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Web, "api", info.Package, info.PackageName+".js")
}
if info.IsPlugin {
switch t {
case "api.go":
target = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", info.Package, "api", info.HumpPackageName+".go")
case "server.go":
target = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", info.Package, "service", info.HumpPackageName+".go")
case "api.js":
target = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Web, "plugin", info.Package, "api", info.PackageName+".js")
}
}
// 打开文件,如果不存在则返回错误
file, err := os.OpenFile(target, os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
return err
}
defer file.Close()
// 写入内容
_, err = fmt.Fprintln(file, getTemplateStr)
if err != nil {
fmt.Printf("写入文件失败: %s\n", err.Error())
return err
}
return nil
}

View File

@@ -0,0 +1,84 @@
package system
import (
"context"
"encoding/json"
"git.echol.cn/loser/xiecheng_server/model/system/request"
"reflect"
"testing"
)
func Test_autoCodeTemplate_Create(t *testing.T) {
type args struct {
ctx context.Context
info request.AutoCode
}
tests := []struct {
name string
args args
wantErr bool
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &autoCodeTemplate{}
if err := s.Create(tt.args.ctx, tt.args.info); (err != nil) != tt.wantErr {
t.Errorf("Create() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func Test_autoCodeTemplate_Preview(t *testing.T) {
type args struct {
ctx context.Context
info request.AutoCode
}
tests := []struct {
name string
args args
want map[string]string
wantErr bool
}{
{
name: "测试 package",
args: args{
ctx: context.Background(),
info: request.AutoCode{},
},
wantErr: false,
},
{
name: "测试 plugin",
args: args{
ctx: context.Background(),
info: request.AutoCode{},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
testJson := `{"structName":"SysUser","tableName":"sys_users","packageName":"sysUsers","package":"gva","abbreviation":"sysUsers","description":"sysUsers表","businessDB":"","autoCreateApiToSql":true,"autoCreateMenuToSql":true,"autoMigrate":true,"gvaModel":true,"autoCreateResource":false,"fields":[{"fieldName":"Uuid","fieldDesc":"用户UUID","fieldType":"string","dataType":"varchar","fieldJson":"uuid","primaryKey":false,"dataTypeLong":"191","columnName":"uuid","comment":"用户UUID","require":false,"errorText":"","clearable":true,"fieldSearchType":"","fieldIndexType":"","dictType":"","front":true,"dataSource":{"association":1,"table":"","label":"","value":""}},{"fieldName":"Username","fieldDesc":"用户登录名","fieldType":"string","dataType":"varchar","fieldJson":"username","primaryKey":false,"dataTypeLong":"191","columnName":"username","comment":"用户登录名","require":false,"errorText":"","clearable":true,"fieldSearchType":"","fieldIndexType":"","dictType":"","front":true,"dataSource":{"association":1,"table":"","label":"","value":""}},{"fieldName":"Password","fieldDesc":"用户登录密码","fieldType":"string","dataType":"varchar","fieldJson":"password","primaryKey":false,"dataTypeLong":"191","columnName":"password","comment":"用户登录密码","require":false,"errorText":"","clearable":true,"fieldSearchType":"","fieldIndexType":"","dictType":"","front":true,"dataSource":{"association":1,"table":"","label":"","value":""}},{"fieldName":"NickName","fieldDesc":"用户昵称","fieldType":"string","dataType":"varchar","fieldJson":"nickName","primaryKey":false,"dataTypeLong":"191","columnName":"nick_name","comment":"用户昵称","require":false,"errorText":"","clearable":true,"fieldSearchType":"","fieldIndexType":"","dictType":"","front":true,"dataSource":{"association":1,"table":"","label":"","value":""}},{"fieldName":"SideMode","fieldDesc":"用户侧边主题","fieldType":"string","dataType":"varchar","fieldJson":"sideMode","primaryKey":false,"dataTypeLong":"191","columnName":"side_mode","comment":"用户侧边主题","require":false,"errorText":"","clearable":true,"fieldSearchType":"","fieldIndexType":"","dictType":"","front":true,"dataSource":{"association":1,"table":"","label":"","value":""}},{"fieldName":"HeaderImg","fieldDesc":"用户头像","fieldType":"string","dataType":"varchar","fieldJson":"headerImg","primaryKey":false,"dataTypeLong":"191","columnName":"header_img","comment":"用户头像","require":false,"errorText":"","clearable":true,"fieldSearchType":"","fieldIndexType":"","dictType":"","front":true,"dataSource":{"association":1,"table":"","label":"","value":""}},{"fieldName":"BaseColor","fieldDesc":"基础颜色","fieldType":"string","dataType":"varchar","fieldJson":"baseColor","primaryKey":false,"dataTypeLong":"191","columnName":"base_color","comment":"基础颜色","require":false,"errorText":"","clearable":true,"fieldSearchType":"","fieldIndexType":"","dictType":"","front":true,"dataSource":{"association":1,"table":"","label":"","value":""}},{"fieldName":"AuthorityId","fieldDesc":"用户角色ID","fieldType":"int","dataType":"bigint","fieldJson":"authorityId","primaryKey":false,"dataTypeLong":"20","columnName":"authority_id","comment":"用户角色ID","require":false,"errorText":"","clearable":true,"fieldSearchType":"","fieldIndexType":"","dictType":"","front":true,"dataSource":{"association":1,"table":"","label":"","value":""}},{"fieldName":"Phone","fieldDesc":"用户手机号","fieldType":"string","dataType":"varchar","fieldJson":"phone","primaryKey":false,"dataTypeLong":"191","columnName":"phone","comment":"用户手机号","require":false,"errorText":"","clearable":true,"fieldSearchType":"","fieldIndexType":"","dictType":"","front":true,"dataSource":{"association":1,"table":"","label":"","value":""}},{"fieldName":"Email","fieldDesc":"用户邮箱","fieldType":"string","dataType":"varchar","fieldJson":"email","primaryKey":false,"dataTypeLong":"191","columnName":"email","comment":"用户邮箱","require":false,"errorText":"","clearable":true,"fieldSearchType":"","fieldIndexType":"","dictType":"","front":true,"dataSource":{"association":1,"table":"","label":"","value":""}},{"fieldName":"Enable","fieldDesc":"用户是否被冻结 1正常 2冻结","fieldType":"int","dataType":"bigint","fieldJson":"enable","primaryKey":false,"dataTypeLong":"19","columnName":"enable","comment":"用户是否被冻结 1正常 2冻结","require":false,"errorText":"","clearable":true,"fieldSearchType":"","fieldIndexType":"","dictType":"","front":true,"dataSource":{"association":1,"table":"","label":"","value":""}}],"humpPackageName":"sys_users"}`
err := json.Unmarshal([]byte(testJson), &tt.args.info)
if err != nil {
t.Error(err)
return
}
err = tt.args.info.Pretreatment()
if err != nil {
t.Error(err)
return
}
got, err := AutoCodeTemplate.Preview(tt.args.ctx, tt.args.info)
if (err != nil) != tt.wantErr {
t.Errorf("Preview() error = %+v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Preview() got = %v, want %v", got, tt.want)
}
})
}
}

24
service/system/enter.go Normal file
View File

@@ -0,0 +1,24 @@
package system
type ServiceGroup struct {
JwtService
ApiService
MenuService
UserService
CasbinService
InitDBService
AutoCodeService
BaseMenuService
AuthorityService
DictionaryService
SystemConfigService
OperationRecordService
DictionaryDetailService
AuthorityBtnService
SysExportTemplateService
SysParamsService
AutoCodePlugin autoCodePlugin
AutoCodePackage autoCodePackage
AutoCodeHistory autoCodeHistory
AutoCodeTemplate autoCodeTemplate
}

View File

@@ -0,0 +1,84 @@
package system
import (
"context"
"go.uber.org/zap"
"git.echol.cn/loser/xiecheng_server/global"
"git.echol.cn/loser/xiecheng_server/model/system"
"git.echol.cn/loser/xiecheng_server/utils"
)
type JwtService struct{}
var JwtServiceApp = new(JwtService)
//@author: [piexlmax](https://github.com/piexlmax)
//@function: JsonInBlacklist
//@description: 拉黑jwt
//@param: jwtList model.JwtBlacklist
//@return: err error
func (jwtService *JwtService) JsonInBlacklist(jwtList system.JwtBlacklist) (err error) {
err = global.GVA_DB.Create(&jwtList).Error
if err != nil {
return
}
global.BlackCache.SetDefault(jwtList.Jwt, struct{}{})
return
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: IsBlacklist
//@description: 判断JWT是否在黑名单内部
//@param: jwt string
//@return: bool
func (jwtService *JwtService) IsBlacklist(jwt string) bool {
_, ok := global.BlackCache.Get(jwt)
return ok
// err := global.GVA_DB.Where("jwt = ?", jwt).First(&system.JwtBlacklist{}).Error
// isNotFound := errors.Is(err, gorm.ErrRecordNotFound)
// return !isNotFound
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: GetRedisJWT
//@description: 从redis取jwt
//@param: userName string
//@return: redisJWT string, err error
func (jwtService *JwtService) GetRedisJWT(userName string) (redisJWT string, err error) {
redisJWT, err = global.GVA_REDIS.Get(context.Background(), userName).Result()
return redisJWT, err
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: SetRedisJWT
//@description: jwt存入redis并设置过期时间
//@param: jwt string, userName string
//@return: err error
func (jwtService *JwtService) SetRedisJWT(jwt string, userName string) (err error) {
// 此处过期时间等于jwt过期时间
dr, err := utils.ParseDuration(global.GVA_CONFIG.JWT.ExpiresTime)
if err != nil {
return err
}
timer := dr
err = global.GVA_REDIS.Set(context.Background(), userName, jwt, timer).Err()
return err
}
func LoadAll() {
var data []string
err := global.GVA_DB.Model(&system.JwtBlacklist{}).Select("jwt").Find(&data).Error
if err != nil {
global.GVA_LOG.Error("加载数据库jwt黑名单失败!", zap.Error(err))
return
}
for i := 0; i < len(data); i++ {
global.BlackCache.SetDefault(data[i], struct{}{})
} // jwt黑名单 加入 BlackCache 中
}

326
service/system/sys_api.go Normal file
View File

@@ -0,0 +1,326 @@
package system
import (
"errors"
"fmt"
"strings"
"git.echol.cn/loser/xiecheng_server/global"
"git.echol.cn/loser/xiecheng_server/model/common/request"
"git.echol.cn/loser/xiecheng_server/model/system"
systemRes "git.echol.cn/loser/xiecheng_server/model/system/response"
"gorm.io/gorm"
)
//@author: [piexlmax](https://github.com/piexlmax)
//@function: CreateApi
//@description: 新增基础api
//@param: api model.SysApi
//@return: err error
type ApiService struct{}
var ApiServiceApp = new(ApiService)
func (apiService *ApiService) CreateApi(api system.SysApi) (err error) {
if !errors.Is(global.GVA_DB.Where("path = ? AND method = ?", api.Path, api.Method).First(&system.SysApi{}).Error, gorm.ErrRecordNotFound) {
return errors.New("存在相同api")
}
return global.GVA_DB.Create(&api).Error
}
func (apiService *ApiService) GetApiGroups() (groups []string, groupApiMap map[string]string, err error) {
var apis []system.SysApi
err = global.GVA_DB.Find(&apis).Error
if err != nil {
return
}
groupApiMap = make(map[string]string, 0)
for i := range apis {
pathArr := strings.Split(apis[i].Path, "/")
newGroup := true
for i2 := range groups {
if groups[i2] == apis[i].ApiGroup {
newGroup = false
}
}
if newGroup {
groups = append(groups, apis[i].ApiGroup)
}
groupApiMap[pathArr[1]] = apis[i].ApiGroup
}
return
}
func (apiService *ApiService) SyncApi() (newApis, deleteApis, ignoreApis []system.SysApi, err error) {
newApis = make([]system.SysApi, 0)
deleteApis = make([]system.SysApi, 0)
ignoreApis = make([]system.SysApi, 0)
var apis []system.SysApi
err = global.GVA_DB.Find(&apis).Error
if err != nil {
return
}
var ignores []system.SysIgnoreApi
err = global.GVA_DB.Find(&ignores).Error
if err != nil {
return
}
for i := range ignores {
ignoreApis = append(ignoreApis, system.SysApi{
Path: ignores[i].Path,
Description: "",
ApiGroup: "",
Method: ignores[i].Method,
})
}
var cacheApis []system.SysApi
for i := range global.GVA_ROUTERS {
ignoresFlag := false
for j := range ignores {
if ignores[j].Path == global.GVA_ROUTERS[i].Path && ignores[j].Method == global.GVA_ROUTERS[i].Method {
ignoresFlag = true
}
}
if !ignoresFlag {
cacheApis = append(cacheApis, system.SysApi{
Path: global.GVA_ROUTERS[i].Path,
Method: global.GVA_ROUTERS[i].Method,
})
}
}
//对比数据库中的api和内存中的api如果数据库中的api不存在于内存中则把api放入删除数组如果内存中的api不存在于数据库中则把api放入新增数组
for i := range cacheApis {
var flag bool
// 如果存在于内存不存在于api数组中
for j := range apis {
if cacheApis[i].Path == apis[j].Path && cacheApis[i].Method == apis[j].Method {
flag = true
}
}
if !flag {
newApis = append(newApis, system.SysApi{
Path: cacheApis[i].Path,
Description: "",
ApiGroup: "",
Method: cacheApis[i].Method,
})
}
}
for i := range apis {
var flag bool
// 如果存在于api数组不存在于内存
for j := range cacheApis {
if cacheApis[j].Path == apis[i].Path && cacheApis[j].Method == apis[i].Method {
flag = true
}
}
if !flag {
deleteApis = append(deleteApis, apis[i])
}
}
return
}
func (apiService *ApiService) IgnoreApi(ignoreApi system.SysIgnoreApi) (err error) {
if ignoreApi.Flag {
return global.GVA_DB.Create(&ignoreApi).Error
}
return global.GVA_DB.Unscoped().Delete(&ignoreApi, "path = ? AND method = ?", ignoreApi.Path, ignoreApi.Method).Error
}
func (apiService *ApiService) EnterSyncApi(syncApis systemRes.SysSyncApis) (err error) {
return global.GVA_DB.Transaction(func(tx *gorm.DB) error {
var txErr error
if len(syncApis.NewApis) > 0 {
txErr = tx.Create(&syncApis.NewApis).Error
if txErr != nil {
return txErr
}
}
for i := range syncApis.DeleteApis {
CasbinServiceApp.ClearCasbin(1, syncApis.DeleteApis[i].Path, syncApis.DeleteApis[i].Method)
txErr = tx.Delete(&system.SysApi{}, "path = ? AND method = ?", syncApis.DeleteApis[i].Path, syncApis.DeleteApis[i].Method).Error
if txErr != nil {
return txErr
}
}
return nil
})
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: DeleteApi
//@description: 删除基础api
//@param: api model.SysApi
//@return: err error
func (apiService *ApiService) DeleteApi(api system.SysApi) (err error) {
var entity system.SysApi
err = global.GVA_DB.First(&entity, "id = ?", api.ID).Error // 根据id查询api记录
if errors.Is(err, gorm.ErrRecordNotFound) { // api记录不存在
return err
}
err = global.GVA_DB.Delete(&entity).Error
if err != nil {
return err
}
CasbinServiceApp.ClearCasbin(1, entity.Path, entity.Method)
return nil
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: GetAPIInfoList
//@description: 分页获取数据,
//@param: api model.SysApi, info request.PageInfo, order string, desc bool
//@return: list interface{}, total int64, err error
func (apiService *ApiService) GetAPIInfoList(api system.SysApi, info request.PageInfo, order string, desc bool) (list interface{}, total int64, err error) {
limit := info.PageSize
offset := info.PageSize * (info.Page - 1)
db := global.GVA_DB.Model(&system.SysApi{})
var apiList []system.SysApi
if api.Path != "" {
db = db.Where("path LIKE ?", "%"+api.Path+"%")
}
if api.Description != "" {
db = db.Where("description LIKE ?", "%"+api.Description+"%")
}
if api.Method != "" {
db = db.Where("method = ?", api.Method)
}
if api.ApiGroup != "" {
db = db.Where("api_group = ?", api.ApiGroup)
}
err = db.Count(&total).Error
if err != nil {
return apiList, total, err
}
db = db.Limit(limit).Offset(offset)
OrderStr := "id desc"
if order != "" {
orderMap := make(map[string]bool, 5)
orderMap["id"] = true
orderMap["path"] = true
orderMap["api_group"] = true
orderMap["description"] = true
orderMap["method"] = true
if !orderMap[order] {
err = fmt.Errorf("非法的排序字段: %v", order)
return apiList, total, err
}
OrderStr = order
if desc {
OrderStr = order + " desc"
}
}
err = db.Order(OrderStr).Find(&apiList).Error
return apiList, total, err
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: GetAllApis
//@description: 获取所有的api
//@return: apis []model.SysApi, err error
func (apiService *ApiService) GetAllApis(authorityID uint) (apis []system.SysApi, err error) {
parentAuthorityID, err := AuthorityServiceApp.GetParentAuthorityID(authorityID)
if err != nil {
return nil, err
}
err = global.GVA_DB.Order("id desc").Find(&apis).Error
if parentAuthorityID == 0 || !global.GVA_CONFIG.System.UseStrictAuth {
return
}
paths := CasbinServiceApp.GetPolicyPathByAuthorityId(authorityID)
// 挑选 apis里面的path和method也在paths里面的api
var authApis []system.SysApi
for i := range apis {
for j := range paths {
if paths[j].Path == apis[i].Path && paths[j].Method == apis[i].Method {
authApis = append(authApis, apis[i])
}
}
}
return authApis, err
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: GetApiById
//@description: 根据id获取api
//@param: id float64
//@return: api model.SysApi, err error
func (apiService *ApiService) GetApiById(id int) (api system.SysApi, err error) {
err = global.GVA_DB.First(&api, "id = ?", id).Error
return
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: UpdateApi
//@description: 根据id更新api
//@param: api model.SysApi
//@return: err error
func (apiService *ApiService) UpdateApi(api system.SysApi) (err error) {
var oldA system.SysApi
err = global.GVA_DB.First(&oldA, "id = ?", api.ID).Error
if oldA.Path != api.Path || oldA.Method != api.Method {
var duplicateApi system.SysApi
if ferr := global.GVA_DB.First(&duplicateApi, "path = ? AND method = ?", api.Path, api.Method).Error; ferr != nil {
if !errors.Is(ferr, gorm.ErrRecordNotFound) {
return ferr
}
} else {
if duplicateApi.ID != api.ID {
return errors.New("存在相同api路径")
}
}
}
if err != nil {
return err
}
err = CasbinServiceApp.UpdateCasbinApi(oldA.Path, api.Path, oldA.Method, api.Method)
if err != nil {
return err
}
return global.GVA_DB.Save(&api).Error
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: DeleteApisByIds
//@description: 删除选中API
//@param: apis []model.SysApi
//@return: err error
func (apiService *ApiService) DeleteApisByIds(ids request.IdsReq) (err error) {
return global.GVA_DB.Transaction(func(tx *gorm.DB) error {
var apis []system.SysApi
err = tx.Find(&apis, "id in ?", ids.Ids).Error
if err != nil {
return err
}
err = tx.Delete(&[]system.SysApi{}, "id in ?", ids.Ids).Error
if err != nil {
return err
}
for _, sysApi := range apis {
CasbinServiceApp.ClearCasbin(1, sysApi.Path, sysApi.Method)
}
return err
})
}

View File

@@ -0,0 +1,330 @@
package system
import (
"errors"
"strconv"
systemReq "git.echol.cn/loser/xiecheng_server/model/system/request"
"git.echol.cn/loser/xiecheng_server/global"
"git.echol.cn/loser/xiecheng_server/model/common/request"
"git.echol.cn/loser/xiecheng_server/model/system"
"git.echol.cn/loser/xiecheng_server/model/system/response"
"gorm.io/gorm"
)
var ErrRoleExistence = errors.New("存在相同角色id")
//@author: [piexlmax](https://github.com/piexlmax)
//@function: CreateAuthority
//@description: 创建一个角色
//@param: auth model.SysAuthority
//@return: authority system.SysAuthority, err error
type AuthorityService struct{}
var AuthorityServiceApp = new(AuthorityService)
func (authorityService *AuthorityService) CreateAuthority(auth system.SysAuthority) (authority system.SysAuthority, err error) {
if err = global.GVA_DB.Where("authority_id = ?", auth.AuthorityId).First(&system.SysAuthority{}).Error; !errors.Is(err, gorm.ErrRecordNotFound) {
return auth, ErrRoleExistence
}
e := global.GVA_DB.Transaction(func(tx *gorm.DB) error {
if err = tx.Create(&auth).Error; err != nil {
return err
}
auth.SysBaseMenus = systemReq.DefaultMenu()
if err = tx.Model(&auth).Association("SysBaseMenus").Replace(&auth.SysBaseMenus); err != nil {
return err
}
casbinInfos := systemReq.DefaultCasbin()
authorityId := strconv.Itoa(int(auth.AuthorityId))
rules := [][]string{}
for _, v := range casbinInfos {
rules = append(rules, []string{authorityId, v.Path, v.Method})
}
return CasbinServiceApp.AddPolicies(tx, rules)
})
return auth, e
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: CopyAuthority
//@description: 复制一个角色
//@param: copyInfo response.SysAuthorityCopyResponse
//@return: authority system.SysAuthority, err error
func (authorityService *AuthorityService) CopyAuthority(adminAuthorityID uint, copyInfo response.SysAuthorityCopyResponse) (authority system.SysAuthority, err error) {
var authorityBox system.SysAuthority
if !errors.Is(global.GVA_DB.Where("authority_id = ?", copyInfo.Authority.AuthorityId).First(&authorityBox).Error, gorm.ErrRecordNotFound) {
return authority, ErrRoleExistence
}
copyInfo.Authority.Children = []system.SysAuthority{}
menus, err := MenuServiceApp.GetMenuAuthority(&request.GetAuthorityId{AuthorityId: copyInfo.OldAuthorityId})
if err != nil {
return
}
var baseMenu []system.SysBaseMenu
for _, v := range menus {
intNum := v.MenuId
v.SysBaseMenu.ID = uint(intNum)
baseMenu = append(baseMenu, v.SysBaseMenu)
}
copyInfo.Authority.SysBaseMenus = baseMenu
err = global.GVA_DB.Create(&copyInfo.Authority).Error
if err != nil {
return
}
var btns []system.SysAuthorityBtn
err = global.GVA_DB.Find(&btns, "authority_id = ?", copyInfo.OldAuthorityId).Error
if err != nil {
return
}
if len(btns) > 0 {
for i := range btns {
btns[i].AuthorityId = copyInfo.Authority.AuthorityId
}
err = global.GVA_DB.Create(&btns).Error
if err != nil {
return
}
}
paths := CasbinServiceApp.GetPolicyPathByAuthorityId(copyInfo.OldAuthorityId)
err = CasbinServiceApp.UpdateCasbin(adminAuthorityID, copyInfo.Authority.AuthorityId, paths)
if err != nil {
_ = authorityService.DeleteAuthority(&copyInfo.Authority)
}
return copyInfo.Authority, err
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: UpdateAuthority
//@description: 更改一个角色
//@param: auth model.SysAuthority
//@return: authority system.SysAuthority, err error
func (authorityService *AuthorityService) UpdateAuthority(auth system.SysAuthority) (authority system.SysAuthority, err error) {
var oldAuthority system.SysAuthority
err = global.GVA_DB.Where("authority_id = ?", auth.AuthorityId).First(&oldAuthority).Error
if err != nil {
global.GVA_LOG.Debug(err.Error())
return system.SysAuthority{}, errors.New("查询角色数据失败")
}
err = global.GVA_DB.Model(&oldAuthority).Updates(&auth).Error
return auth, err
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: DeleteAuthority
//@description: 删除角色
//@param: auth *model.SysAuthority
//@return: err error
func (authorityService *AuthorityService) DeleteAuthority(auth *system.SysAuthority) error {
if errors.Is(global.GVA_DB.Debug().Preload("Users").First(&auth).Error, gorm.ErrRecordNotFound) {
return errors.New("该角色不存在")
}
if len(auth.Users) != 0 {
return errors.New("此角色有用户正在使用禁止删除")
}
if !errors.Is(global.GVA_DB.Where("authority_id = ?", auth.AuthorityId).First(&system.SysUser{}).Error, gorm.ErrRecordNotFound) {
return errors.New("此角色有用户正在使用禁止删除")
}
if !errors.Is(global.GVA_DB.Where("parent_id = ?", auth.AuthorityId).First(&system.SysAuthority{}).Error, gorm.ErrRecordNotFound) {
return errors.New("此角色存在子角色不允许删除")
}
return global.GVA_DB.Transaction(func(tx *gorm.DB) error {
var err error
if err = tx.Preload("SysBaseMenus").Preload("DataAuthorityId").Where("authority_id = ?", auth.AuthorityId).First(auth).Unscoped().Delete(auth).Error; err != nil {
return err
}
if len(auth.SysBaseMenus) > 0 {
if err = tx.Model(auth).Association("SysBaseMenus").Delete(auth.SysBaseMenus); err != nil {
return err
}
// err = db.Association("SysBaseMenus").Delete(&auth)
}
if len(auth.DataAuthorityId) > 0 {
if err = tx.Model(auth).Association("DataAuthorityId").Delete(auth.DataAuthorityId); err != nil {
return err
}
}
if err = tx.Delete(&system.SysUserAuthority{}, "sys_authority_authority_id = ?", auth.AuthorityId).Error; err != nil {
return err
}
if err = tx.Where("authority_id = ?", auth.AuthorityId).Delete(&[]system.SysAuthorityBtn{}).Error; err != nil {
return err
}
authorityId := strconv.Itoa(int(auth.AuthorityId))
if err = CasbinServiceApp.RemoveFilteredPolicy(tx, authorityId); err != nil {
return err
}
return nil
})
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: GetAuthorityInfoList
//@description: 分页获取数据
//@param: info request.PageInfo
//@return: list interface{}, total int64, err error
func (authorityService *AuthorityService) GetAuthorityInfoList(authorityID uint) (list []system.SysAuthority, err error) {
var authority system.SysAuthority
err = global.GVA_DB.Where("authority_id = ?", authorityID).First(&authority).Error
if err != nil {
return nil, err
}
var authorities []system.SysAuthority
db := global.GVA_DB.Model(&system.SysAuthority{})
if global.GVA_CONFIG.System.UseStrictAuth {
// 当开启了严格树形结构后
if *authority.ParentId == 0 {
// 只有顶级角色可以修改自己的权限和以下权限
err = db.Preload("DataAuthorityId").Where("authority_id = ?", authorityID).Find(&authorities).Error
} else {
// 非顶级角色只能修改以下权限
err = db.Debug().Preload("DataAuthorityId").Where("parent_id = ?", authorityID).Find(&authorities).Error
}
} else {
err = db.Preload("DataAuthorityId").Where("parent_id = ?", "0").Find(&authorities).Error
}
for k := range authorities {
err = authorityService.findChildrenAuthority(&authorities[k])
}
return authorities, err
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: GetAuthorityInfoList
//@description: 分页获取数据
//@param: info request.PageInfo
//@return: list interface{}, total int64, err error
func (authorityService *AuthorityService) GetStructAuthorityList(authorityID uint) (list []uint, err error) {
var auth system.SysAuthority
_ = global.GVA_DB.First(&auth, "authority_id = ?", authorityID).Error
var authorities []system.SysAuthority
err = global.GVA_DB.Preload("DataAuthorityId").Where("parent_id = ?", authorityID).Find(&authorities).Error
if len(authorities) > 0 {
for k := range authorities {
list = append(list, authorities[k].AuthorityId)
childrenList, err := authorityService.GetStructAuthorityList(authorities[k].AuthorityId)
if err == nil {
list = append(list, childrenList...)
}
}
}
if *auth.ParentId == 0 {
list = append(list, authorityID)
}
return list, err
}
func (authorityService *AuthorityService) CheckAuthorityIDAuth(authorityID, targetID uint) (err error) {
if !global.GVA_CONFIG.System.UseStrictAuth {
return nil
}
authIDS, err := authorityService.GetStructAuthorityList(authorityID)
if err != nil {
return err
}
hasAuth := false
for _, v := range authIDS {
if v == targetID {
hasAuth = true
break
}
}
if !hasAuth {
return errors.New("您提交的角色ID不合法")
}
return nil
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: GetAuthorityInfo
//@description: 获取所有角色信息
//@param: auth model.SysAuthority
//@return: sa system.SysAuthority, err error
func (authorityService *AuthorityService) GetAuthorityInfo(auth system.SysAuthority) (sa system.SysAuthority, err error) {
err = global.GVA_DB.Preload("DataAuthorityId").Where("authority_id = ?", auth.AuthorityId).First(&sa).Error
return sa, err
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: SetDataAuthority
//@description: 设置角色资源权限
//@param: auth model.SysAuthority
//@return: error
func (authorityService *AuthorityService) SetDataAuthority(adminAuthorityID uint, auth system.SysAuthority) error {
var checkIDs []uint
checkIDs = append(checkIDs, auth.AuthorityId)
for i := range auth.DataAuthorityId {
checkIDs = append(checkIDs, auth.DataAuthorityId[i].AuthorityId)
}
for i := range checkIDs {
err := authorityService.CheckAuthorityIDAuth(adminAuthorityID, checkIDs[i])
if err != nil {
return err
}
}
var s system.SysAuthority
global.GVA_DB.Preload("DataAuthorityId").First(&s, "authority_id = ?", auth.AuthorityId)
err := global.GVA_DB.Model(&s).Association("DataAuthorityId").Replace(&auth.DataAuthorityId)
return err
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: SetMenuAuthority
//@description: 菜单与角色绑定
//@param: auth *model.SysAuthority
//@return: error
func (authorityService *AuthorityService) SetMenuAuthority(auth *system.SysAuthority) error {
var s system.SysAuthority
global.GVA_DB.Preload("SysBaseMenus").First(&s, "authority_id = ?", auth.AuthorityId)
err := global.GVA_DB.Model(&s).Association("SysBaseMenus").Replace(&auth.SysBaseMenus)
return err
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: findChildrenAuthority
//@description: 查询子角色
//@param: authority *model.SysAuthority
//@return: err error
func (authorityService *AuthorityService) findChildrenAuthority(authority *system.SysAuthority) (err error) {
err = global.GVA_DB.Preload("DataAuthorityId").Where("parent_id = ?", authority.AuthorityId).Find(&authority.Children).Error
if len(authority.Children) > 0 {
for k := range authority.Children {
err = authorityService.findChildrenAuthority(&authority.Children[k])
}
}
return err
}
func (authorityService *AuthorityService) GetParentAuthorityID(authorityID uint) (parentID uint, err error) {
var authority system.SysAuthority
err = global.GVA_DB.Where("authority_id = ?", authorityID).First(&authority).Error
return *authority.ParentId, err
}

View File

@@ -0,0 +1,60 @@
package system
import (
"errors"
"git.echol.cn/loser/xiecheng_server/global"
"git.echol.cn/loser/xiecheng_server/model/system"
"git.echol.cn/loser/xiecheng_server/model/system/request"
"git.echol.cn/loser/xiecheng_server/model/system/response"
"gorm.io/gorm"
)
type AuthorityBtnService struct{}
var AuthorityBtnServiceApp = new(AuthorityBtnService)
func (a *AuthorityBtnService) GetAuthorityBtn(req request.SysAuthorityBtnReq) (res response.SysAuthorityBtnRes, err error) {
var authorityBtn []system.SysAuthorityBtn
err = global.GVA_DB.Find(&authorityBtn, "authority_id = ? and sys_menu_id = ?", req.AuthorityId, req.MenuID).Error
if err != nil {
return
}
var selected []uint
for _, v := range authorityBtn {
selected = append(selected, v.SysBaseMenuBtnID)
}
res.Selected = selected
return res, err
}
func (a *AuthorityBtnService) SetAuthorityBtn(req request.SysAuthorityBtnReq) (err error) {
return global.GVA_DB.Transaction(func(tx *gorm.DB) error {
var authorityBtn []system.SysAuthorityBtn
err = tx.Delete(&[]system.SysAuthorityBtn{}, "authority_id = ? and sys_menu_id = ?", req.AuthorityId, req.MenuID).Error
if err != nil {
return err
}
for _, v := range req.Selected {
authorityBtn = append(authorityBtn, system.SysAuthorityBtn{
AuthorityId: req.AuthorityId,
SysMenuID: req.MenuID,
SysBaseMenuBtnID: v,
})
}
if len(authorityBtn) > 0 {
err = tx.Create(&authorityBtn).Error
}
if err != nil {
return err
}
return err
})
}
func (a *AuthorityBtnService) CanRemoveAuthorityBtn(ID string) (err error) {
fErr := global.GVA_DB.First(&system.SysAuthorityBtn{}, "sys_base_menu_btn_id = ?", ID).Error
if errors.Is(fErr, gorm.ErrRecordNotFound) {
return nil
}
return errors.New("此按钮正在被使用无法删除")
}

View File

@@ -0,0 +1,55 @@
package system
import (
"git.echol.cn/loser/xiecheng_server/global"
"git.echol.cn/loser/xiecheng_server/model/system/response"
)
type AutoCodeService struct{}
type Database interface {
GetDB(businessDB string) (data []response.Db, err error)
GetTables(businessDB string, dbName string) (data []response.Table, err error)
GetColumn(businessDB string, tableName string, dbName string) (data []response.Column, err error)
}
func (autoCodeService *AutoCodeService) Database(businessDB string) Database {
if businessDB == "" {
switch global.GVA_CONFIG.System.DbType {
case "mysql":
return AutoCodeMysql
case "pgsql":
return AutoCodePgsql
case "mssql":
return AutoCodeMssql
case "oracle":
return AutoCodeOracle
case "sqlite":
return AutoCodeSqlite
default:
return AutoCodeMysql
}
} else {
for _, info := range global.GVA_CONFIG.DBList {
if info.AliasName == businessDB {
switch info.Type {
case "mysql":
return AutoCodeMysql
case "mssql":
return AutoCodeMssql
case "pgsql":
return AutoCodePgsql
case "oracle":
return AutoCodeOracle
case "sqlite":
return AutoCodeSqlite
default:
return AutoCodeMysql
}
}
}
return AutoCodeMysql
}
}

View File

@@ -0,0 +1,83 @@
package system
import (
"fmt"
"git.echol.cn/loser/xiecheng_server/global"
"git.echol.cn/loser/xiecheng_server/model/system/response"
)
var AutoCodeMssql = new(autoCodeMssql)
type autoCodeMssql struct{}
// GetDB 获取数据库的所有数据库名
// Author [piexlmax](https://github.com/piexlmax)
// Author [SliverHorn](https://github.com/SliverHorn)
func (s *autoCodeMssql) GetDB(businessDB string) (data []response.Db, err error) {
var entities []response.Db
sql := "select name AS 'database' from sys.databases;"
if businessDB == "" {
err = global.GVA_DB.Raw(sql).Scan(&entities).Error
} else {
err = global.GVA_DBList[businessDB].Raw(sql).Scan(&entities).Error
}
return entities, err
}
// GetTables 获取数据库的所有表名
// Author [piexlmax](https://github.com/piexlmax)
// Author [SliverHorn](https://github.com/SliverHorn)
func (s *autoCodeMssql) GetTables(businessDB string, dbName string) (data []response.Table, err error) {
var entities []response.Table
sql := fmt.Sprintf(`select name as 'table_name' from %s.DBO.sysobjects where xtype='U'`, dbName)
if businessDB == "" {
err = global.GVA_DB.Raw(sql).Scan(&entities).Error
} else {
err = global.GVA_DBList[businessDB].Raw(sql).Scan(&entities).Error
}
return entities, err
}
// GetColumn 获取指定数据库和指定数据表的所有字段名,类型值等
// Author [piexlmax](https://github.com/piexlmax)
// Author [SliverHorn](https://github.com/SliverHorn)
func (s *autoCodeMssql) GetColumn(businessDB string, tableName string, dbName string) (data []response.Column, err error) {
var entities []response.Column
sql := fmt.Sprintf(`
SELECT
sc.name AS column_name,
st.name AS data_type,
sc.max_length AS data_type_long,
CASE
WHEN pk.object_id IS NOT NULL THEN 1
ELSE 0
END AS primary_key,
sc.column_id
FROM
%s.sys.columns sc
JOIN
sys.types st ON sc.user_type_id=st.user_type_id
LEFT JOIN
%s.sys.objects so ON so.name='%s' AND so.type='U'
LEFT JOIN
%s.sys.indexes si ON si.object_id = so.object_id AND si.is_primary_key = 1
LEFT JOIN
%s.sys.index_columns sic ON sic.object_id = si.object_id AND sic.index_id = si.index_id AND sic.column_id = sc.column_id
LEFT JOIN
%s.sys.key_constraints pk ON pk.object_id = si.object_id
WHERE
st.is_user_defined=0 AND sc.object_id = so.object_id
ORDER BY
sc.column_id
`, dbName, dbName, tableName, dbName, dbName, dbName)
if businessDB == "" {
err = global.GVA_DB.Raw(sql).Scan(&entities).Error
} else {
err = global.GVA_DBList[businessDB].Raw(sql).Scan(&entities).Error
}
return entities, err
}

View File

@@ -0,0 +1,83 @@
package system
import (
"git.echol.cn/loser/xiecheng_server/global"
"git.echol.cn/loser/xiecheng_server/model/system/response"
)
var AutoCodeMysql = new(autoCodeMysql)
type autoCodeMysql struct{}
// GetDB 获取数据库的所有数据库名
// Author [piexlmax](https://github.com/piexlmax)
// Author [SliverHorn](https://github.com/SliverHorn)
func (s *autoCodeMysql) GetDB(businessDB string) (data []response.Db, err error) {
var entities []response.Db
sql := "SELECT SCHEMA_NAME AS `database` FROM INFORMATION_SCHEMA.SCHEMATA;"
if businessDB == "" {
err = global.GVA_DB.Raw(sql).Scan(&entities).Error
} else {
err = global.GVA_DBList[businessDB].Raw(sql).Scan(&entities).Error
}
return entities, err
}
// GetTables 获取数据库的所有表名
// Author [piexlmax](https://github.com/piexlmax)
// Author [SliverHorn](https://github.com/SliverHorn)
func (s *autoCodeMysql) GetTables(businessDB string, dbName string) (data []response.Table, err error) {
var entities []response.Table
sql := `select table_name as table_name from information_schema.tables where table_schema = ?`
if businessDB == "" {
err = global.GVA_DB.Raw(sql, dbName).Scan(&entities).Error
} else {
err = global.GVA_DBList[businessDB].Raw(sql, dbName).Scan(&entities).Error
}
return entities, err
}
// GetColumn 获取指定数据库和指定数据表的所有字段名,类型值等
// Author [piexlmax](https://github.com/piexlmax)
// Author [SliverHorn](https://github.com/SliverHorn)
func (s *autoCodeMysql) GetColumn(businessDB string, tableName string, dbName string) (data []response.Column, err error) {
var entities []response.Column
sql := `
SELECT
c.COLUMN_NAME column_name,
c.DATA_TYPE data_type,
CASE c.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,
c.COLUMN_COMMENT column_comment,
CASE WHEN kcu.COLUMN_NAME IS NOT NULL THEN 1 ELSE 0 END AS primary_key,
c.ORDINAL_POSITION
FROM
INFORMATION_SCHEMA.COLUMNS c
LEFT JOIN
INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
ON
c.TABLE_SCHEMA = kcu.TABLE_SCHEMA
AND c.TABLE_NAME = kcu.TABLE_NAME
AND c.COLUMN_NAME = kcu.COLUMN_NAME
AND kcu.CONSTRAINT_NAME = 'PRIMARY'
WHERE
c.TABLE_NAME = ?
AND c.TABLE_SCHEMA = ?
ORDER BY
c.ORDINAL_POSITION;`
if businessDB == "" {
err = global.GVA_DB.Raw(sql, tableName, dbName).Scan(&entities).Error
} else {
err = global.GVA_DBList[businessDB].Raw(sql, tableName, dbName).Scan(&entities).Error
}
return entities, err
}

View File

@@ -0,0 +1,72 @@
package system
import (
"git.echol.cn/loser/xiecheng_server/global"
"git.echol.cn/loser/xiecheng_server/model/system/response"
)
var AutoCodeOracle = new(autoCodeOracle)
type autoCodeOracle struct{}
// GetDB 获取数据库的所有数据库名
// Author [piexlmax](https://github.com/piexlmax)
// Author [SliverHorn](https://github.com/SliverHorn)
func (s *autoCodeOracle) GetDB(businessDB string) (data []response.Db, err error) {
var entities []response.Db
sql := `SELECT lower(username) AS "database" FROM all_users`
err = global.GVA_DBList[businessDB].Raw(sql).Scan(&entities).Error
return entities, err
}
// GetTables 获取数据库的所有表名
// Author [piexlmax](https://github.com/piexlmax)
// Author [SliverHorn](https://github.com/SliverHorn)
func (s *autoCodeOracle) GetTables(businessDB string, dbName string) (data []response.Table, err error) {
var entities []response.Table
sql := `select lower(table_name) as "table_name" from all_tables where lower(owner) = ?`
err = global.GVA_DBList[businessDB].Raw(sql, dbName).Scan(&entities).Error
return entities, err
}
// GetColumn 获取指定数据库和指定数据表的所有字段名,类型值等
// Author [piexlmax](https://github.com/piexlmax)
// Author [SliverHorn](https://github.com/SliverHorn)
func (s *autoCodeOracle) GetColumn(businessDB string, tableName string, dbName string) (data []response.Column, err error) {
var entities []response.Column
sql := `
SELECT
lower(a.COLUMN_NAME) as "column_name",
(CASE WHEN a.DATA_TYPE = 'NUMBER' AND a.DATA_SCALE=0 THEN 'int' else lower(a.DATA_TYPE) end) as "data_type",
(CASE WHEN a.DATA_TYPE = 'NUMBER' THEN a.DATA_PRECISION else a.DATA_LENGTH end) as "data_type_long",
b.COMMENTS as "column_comment",
(CASE WHEN pk.COLUMN_NAME IS NOT NULL THEN 1 ELSE 0 END) as "primary_key",
a.COLUMN_ID
FROM
all_tab_columns a
JOIN
all_col_comments b ON a.OWNER = b.OWNER AND a.TABLE_NAME = b.TABLE_NAME AND a.COLUMN_NAME = b.COLUMN_NAME
LEFT JOIN
(
SELECT
acc.OWNER,
acc.TABLE_NAME,
acc.COLUMN_NAME
FROM
all_cons_columns acc
JOIN
all_constraints ac ON acc.OWNER = ac.OWNER AND acc.CONSTRAINT_NAME = ac.CONSTRAINT_NAME
WHERE
ac.CONSTRAINT_TYPE = 'P'
) pk ON a.OWNER = pk.OWNER AND a.TABLE_NAME = pk.TABLE_NAME AND a.COLUMN_NAME = pk.COLUMN_NAME
WHERE
lower(a.table_name) = ?
AND lower(a.OWNER) = ?
ORDER BY
a.COLUMN_ID;
`
err = global.GVA_DBList[businessDB].Raw(sql, tableName, dbName).Scan(&entities).Error
return entities, err
}

View File

@@ -0,0 +1,135 @@
package system
import (
"git.echol.cn/loser/xiecheng_server/global"
"git.echol.cn/loser/xiecheng_server/model/system/response"
)
var AutoCodePgsql = new(autoCodePgsql)
type autoCodePgsql struct{}
// GetDB 获取数据库的所有数据库名
// Author [piexlmax](https://github.com/piexlmax)
// Author [SliverHorn](https://github.com/SliverHorn)
func (a *autoCodePgsql) GetDB(businessDB string) (data []response.Db, err error) {
var entities []response.Db
sql := `SELECT datname as database FROM pg_database WHERE datistemplate = false`
if businessDB == "" {
err = global.GVA_DB.Raw(sql).Scan(&entities).Error
} else {
err = global.GVA_DBList[businessDB].Raw(sql).Scan(&entities).Error
}
return entities, err
}
// GetTables 获取数据库的所有表名
// Author [piexlmax](https://github.com/piexlmax)
// Author [SliverHorn](https://github.com/SliverHorn)
func (a *autoCodePgsql) GetTables(businessDB string, dbName string) (data []response.Table, err error) {
var entities []response.Table
sql := `select table_name as table_name from information_schema.tables where table_catalog = ? and table_schema = ?`
db := global.GVA_DB
if businessDB != "" {
db = global.GVA_DBList[businessDB]
}
err = db.Raw(sql, dbName, "public").Scan(&entities).Error
return entities, err
}
// GetColumn 获取指定数据库和指定数据表的所有字段名,类型值等
// Author [piexlmax](https://github.com/piexlmax)
// Author [SliverHorn](https://github.com/SliverHorn)
func (a *autoCodePgsql) GetColumn(businessDB string, tableName string, dbName string) (data []response.Column, err error) {
// todo 数据获取不全, 待完善sql
sql := `
SELECT
psc.COLUMN_NAME AS COLUMN_NAME,
psc.udt_name AS data_type,
CASE
psc.udt_name
WHEN 'text' THEN
concat_ws ( '', '', psc.CHARACTER_MAXIMUM_LENGTH )
WHEN 'varchar' THEN
concat_ws ( '', '', psc.CHARACTER_MAXIMUM_LENGTH )
WHEN 'smallint' THEN
concat_ws ( ',', psc.NUMERIC_PRECISION, psc.NUMERIC_SCALE )
WHEN 'decimal' THEN
concat_ws ( ',', psc.NUMERIC_PRECISION, psc.NUMERIC_SCALE )
WHEN 'integer' THEN
concat_ws ( '', '', psc.NUMERIC_PRECISION )
WHEN 'int4' THEN
concat_ws ( '', '', psc.NUMERIC_PRECISION )
WHEN 'int8' THEN
concat_ws ( '', '', psc.NUMERIC_PRECISION )
WHEN 'bigint' THEN
concat_ws ( '', '', psc.NUMERIC_PRECISION )
WHEN 'timestamp' THEN
concat_ws ( '', '', psc.datetime_precision )
ELSE ''
END AS data_type_long,
(
SELECT
pd.description
FROM
pg_description pd
WHERE
(pd.objoid,pd.objsubid) in (
SELECT pa.attrelid,pa.attnum
FROM
pg_attribute pa
WHERE pa.attrelid = ( SELECT oid FROM pg_class pc WHERE
pc.relname = psc.table_name
)
and attname = psc.column_name
)
) AS column_comment,
(
SELECT
COUNT(*)
FROM
pg_constraint
WHERE
contype = 'p'
AND conrelid = (
SELECT
oid
FROM
pg_class
WHERE
relname = psc.table_name
)
AND conkey::int[] @> ARRAY[(
SELECT
attnum::integer
FROM
pg_attribute
WHERE
attrelid = conrelid
AND attname = psc.column_name
)]
) > 0 AS primary_key,
psc.ordinal_position
FROM
INFORMATION_SCHEMA.COLUMNS psc
WHERE
table_catalog = ?
AND table_schema = 'public'
AND TABLE_NAME = ?
ORDER BY
psc.ordinal_position;
`
var entities []response.Column
//sql = strings.ReplaceAll(sql, "@table_catalog", dbName)
//sql = strings.ReplaceAll(sql, "@table_name", tableName)
db := global.GVA_DB
if businessDB != "" {
db = global.GVA_DBList[businessDB]
}
err = db.Raw(sql, dbName, tableName).Scan(&entities).Error
return entities, err
}

View File

@@ -0,0 +1,84 @@
package system
import (
"fmt"
"git.echol.cn/loser/xiecheng_server/global"
"git.echol.cn/loser/xiecheng_server/model/system/response"
"path/filepath"
"strings"
)
var AutoCodeSqlite = new(autoCodeSqlite)
type autoCodeSqlite struct{}
// GetDB 获取数据库的所有数据库名
// Author [piexlmax](https://github.com/piexlmax)
// Author [SliverHorn](https://github.com/SliverHorn)
func (a *autoCodeSqlite) GetDB(businessDB string) (data []response.Db, err error) {
var entities []response.Db
sql := "PRAGMA database_list;"
var databaseList []struct {
File string `gorm:"column:file"`
}
if businessDB == "" {
err = global.GVA_DB.Raw(sql).Find(&databaseList).Error
} else {
err = global.GVA_DBList[businessDB].Raw(sql).Find(&databaseList).Error
}
for _, database := range databaseList {
if database.File != "" {
fileName := filepath.Base(database.File)
fileExt := filepath.Ext(fileName)
fileNameWithoutExt := strings.TrimSuffix(fileName, fileExt)
entities = append(entities, response.Db{fileNameWithoutExt})
}
}
// entities = append(entities, response.Db{global.GVA_CONFIG.Sqlite.Dbname})
return entities, err
}
// GetTables 获取数据库的所有表名
// Author [piexlmax](https://github.com/piexlmax)
// Author [SliverHorn](https://github.com/SliverHorn)
func (a *autoCodeSqlite) GetTables(businessDB string, dbName string) (data []response.Table, err error) {
var entities []response.Table
sql := `SELECT name FROM sqlite_master WHERE type='table'`
tabelNames := []string{}
if businessDB == "" {
err = global.GVA_DB.Raw(sql).Find(&tabelNames).Error
} else {
err = global.GVA_DBList[businessDB].Raw(sql).Find(&tabelNames).Error
}
for _, tabelName := range tabelNames {
entities = append(entities, response.Table{tabelName})
}
return entities, err
}
// GetColumn 获取指定数据表的所有字段名,类型值等
// Author [piexlmax](https://github.com/piexlmax)
// Author [SliverHorn](https://github.com/SliverHorn)
func (a *autoCodeSqlite) GetColumn(businessDB string, tableName string, dbName string) (data []response.Column, err error) {
var entities []response.Column
sql := fmt.Sprintf("PRAGMA table_info(%s);", tableName)
var columnInfos []struct {
Name string `gorm:"column:name"`
Type string `gorm:"column:type"`
Pk int `gorm:"column:pk"`
}
if businessDB == "" {
err = global.GVA_DB.Raw(sql).Scan(&columnInfos).Error
} else {
err = global.GVA_DBList[businessDB].Raw(sql).Scan(&columnInfos).Error
}
for _, columnInfo := range columnInfos {
entities = append(entities, response.Column{
ColumnName: columnInfo.Name,
DataType: columnInfo.Type,
PrimaryKey: columnInfo.Pk == 1,
})
}
return entities, err
}

View File

@@ -0,0 +1,146 @@
package system
import (
"errors"
"git.echol.cn/loser/xiecheng_server/global"
"git.echol.cn/loser/xiecheng_server/model/system"
"gorm.io/gorm"
)
type BaseMenuService struct{}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: DeleteBaseMenu
//@description: 删除基础路由
//@param: id float64
//@return: err error
var BaseMenuServiceApp = new(BaseMenuService)
func (baseMenuService *BaseMenuService) DeleteBaseMenu(id int) (err error) {
err = global.GVA_DB.First(&system.SysBaseMenu{}, "parent_id = ?", id).Error
if err == nil {
return errors.New("此菜单存在子菜单不可删除")
}
var menu system.SysBaseMenu
err = global.GVA_DB.First(&menu, id).Error
if err != nil {
return errors.New("记录不存在")
}
err = global.GVA_DB.First(&system.SysAuthority{}, "default_router = ?", menu.Name).Error
if err == nil {
return errors.New("此菜单有角色正在作为首页,不可删除")
}
return global.GVA_DB.Transaction(func(tx *gorm.DB) error {
err = tx.Delete(&system.SysBaseMenu{}, "id = ?", id).Error
if err != nil {
return err
}
err = tx.Delete(&system.SysBaseMenuParameter{}, "sys_base_menu_id = ?", id).Error
if err != nil {
return err
}
err = tx.Delete(&system.SysBaseMenuBtn{}, "sys_base_menu_id = ?", id).Error
if err != nil {
return err
}
err = tx.Delete(&system.SysAuthorityBtn{}, "sys_menu_id = ?", id).Error
if err != nil {
return err
}
err = tx.Delete(&system.SysAuthorityMenu{}, "sys_base_menu_id = ?", id).Error
if err != nil {
return err
}
return nil
})
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: UpdateBaseMenu
//@description: 更新路由
//@param: menu model.SysBaseMenu
//@return: err error
func (baseMenuService *BaseMenuService) UpdateBaseMenu(menu system.SysBaseMenu) (err error) {
var oldMenu system.SysBaseMenu
upDateMap := make(map[string]interface{})
upDateMap["keep_alive"] = menu.KeepAlive
upDateMap["close_tab"] = menu.CloseTab
upDateMap["default_menu"] = menu.DefaultMenu
upDateMap["parent_id"] = menu.ParentId
upDateMap["path"] = menu.Path
upDateMap["name"] = menu.Name
upDateMap["hidden"] = menu.Hidden
upDateMap["component"] = menu.Component
upDateMap["title"] = menu.Title
upDateMap["active_name"] = menu.ActiveName
upDateMap["icon"] = menu.Icon
upDateMap["sort"] = menu.Sort
err = global.GVA_DB.Transaction(func(tx *gorm.DB) error {
tx.Where("id = ?", menu.ID).Find(&oldMenu)
if oldMenu.Name != menu.Name {
if !errors.Is(tx.Where("id <> ? AND name = ?", menu.ID, menu.Name).First(&system.SysBaseMenu{}).Error, gorm.ErrRecordNotFound) {
global.GVA_LOG.Debug("存在相同name修改失败")
return errors.New("存在相同name修改失败")
}
}
txErr := tx.Unscoped().Delete(&system.SysBaseMenuParameter{}, "sys_base_menu_id = ?", menu.ID).Error
if txErr != nil {
global.GVA_LOG.Debug(txErr.Error())
return txErr
}
txErr = tx.Unscoped().Delete(&system.SysBaseMenuBtn{}, "sys_base_menu_id = ?", menu.ID).Error
if txErr != nil {
global.GVA_LOG.Debug(txErr.Error())
return txErr
}
if len(menu.Parameters) > 0 {
for k := range menu.Parameters {
menu.Parameters[k].SysBaseMenuID = menu.ID
}
txErr = tx.Create(&menu.Parameters).Error
if txErr != nil {
global.GVA_LOG.Debug(txErr.Error())
return txErr
}
}
if len(menu.MenuBtn) > 0 {
for k := range menu.MenuBtn {
menu.MenuBtn[k].SysBaseMenuID = menu.ID
}
txErr = tx.Create(&menu.MenuBtn).Error
if txErr != nil {
global.GVA_LOG.Debug(txErr.Error())
return txErr
}
}
txErr = tx.Model(&oldMenu).Updates(upDateMap).Error
if txErr != nil {
global.GVA_LOG.Debug(txErr.Error())
return txErr
}
return nil
})
return err
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: GetBaseMenuById
//@description: 返回当前选中menu
//@param: id float64
//@return: menu system.SysBaseMenu, err error
func (baseMenuService *BaseMenuService) GetBaseMenuById(id int) (menu system.SysBaseMenu, err error) {
err = global.GVA_DB.Preload("MenuBtn").Preload("Parameters").Where("id = ?", id).First(&menu).Error
return
}

View File

@@ -0,0 +1,221 @@
package system
import (
"errors"
"strconv"
"sync"
"gorm.io/gorm"
"git.echol.cn/loser/xiecheng_server/global"
"git.echol.cn/loser/xiecheng_server/model/system/request"
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/model"
gormadapter "github.com/casbin/gorm-adapter/v3"
_ "github.com/go-sql-driver/mysql"
"go.uber.org/zap"
)
//@author: [piexlmax](https://github.com/piexlmax)
//@function: UpdateCasbin
//@description: 更新casbin权限
//@param: authorityId string, casbinInfos []request.CasbinInfo
//@return: error
type CasbinService struct{}
var CasbinServiceApp = new(CasbinService)
func (casbinService *CasbinService) UpdateCasbin(adminAuthorityID, AuthorityID uint, casbinInfos []request.CasbinInfo) error {
err := AuthorityServiceApp.CheckAuthorityIDAuth(adminAuthorityID, AuthorityID)
if err != nil {
return err
}
if global.GVA_CONFIG.System.UseStrictAuth {
apis, e := ApiServiceApp.GetAllApis(adminAuthorityID)
if e != nil {
return e
}
for i := range casbinInfos {
hasApi := false
for j := range apis {
if apis[j].Path == casbinInfos[i].Path && apis[j].Method == casbinInfos[i].Method {
hasApi = true
break
}
}
if !hasApi {
return errors.New("存在api不在权限列表中")
}
}
}
authorityId := strconv.Itoa(int(AuthorityID))
casbinService.ClearCasbin(0, authorityId)
rules := [][]string{}
//做权限去重处理
deduplicateMap := make(map[string]bool)
for _, v := range casbinInfos {
key := authorityId + v.Path + v.Method
if _, ok := deduplicateMap[key]; !ok {
deduplicateMap[key] = true
rules = append(rules, []string{authorityId, v.Path, v.Method})
}
}
if len(rules) == 0 {
return nil
} // 设置空权限无需调用 AddPolicies 方法
e := casbinService.Casbin()
success, _ := e.AddPolicies(rules)
if !success {
return errors.New("存在相同api,添加失败,请联系管理员")
}
return nil
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: UpdateCasbinApi
//@description: API更新随动
//@param: oldPath string, newPath string, oldMethod string, newMethod string
//@return: error
func (casbinService *CasbinService) UpdateCasbinApi(oldPath string, newPath string, oldMethod string, newMethod string) error {
err := global.GVA_DB.Model(&gormadapter.CasbinRule{}).Where("v1 = ? AND v2 = ?", oldPath, oldMethod).Updates(map[string]interface{}{
"v1": newPath,
"v2": newMethod,
}).Error
if err != nil {
return err
}
e := casbinService.Casbin()
return e.LoadPolicy()
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: GetPolicyPathByAuthorityId
//@description: 获取权限列表
//@param: authorityId string
//@return: pathMaps []request.CasbinInfo
func (casbinService *CasbinService) GetPolicyPathByAuthorityId(AuthorityID uint) (pathMaps []request.CasbinInfo) {
e := casbinService.Casbin()
authorityId := strconv.Itoa(int(AuthorityID))
list, _ := e.GetFilteredPolicy(0, authorityId)
for _, v := range list {
pathMaps = append(pathMaps, request.CasbinInfo{
Path: v[1],
Method: v[2],
})
}
return pathMaps
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: ClearCasbin
//@description: 清除匹配的权限
//@param: v int, p ...string
//@return: bool
func (casbinService *CasbinService) ClearCasbin(v int, p ...string) bool {
e := casbinService.Casbin()
success, _ := e.RemoveFilteredPolicy(v, p...)
return success
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: RemoveFilteredPolicy
//@description: 使用数据库方法清理筛选的politicy 此方法需要调用FreshCasbin方法才可以在系统中即刻生效
//@param: db *gorm.DB, authorityId string
//@return: error
func (casbinService *CasbinService) RemoveFilteredPolicy(db *gorm.DB, authorityId string) error {
return db.Delete(&gormadapter.CasbinRule{}, "v0 = ?", authorityId).Error
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: SyncPolicy
//@description: 同步目前数据库的policy 此方法需要调用FreshCasbin方法才可以在系统中即刻生效
//@param: db *gorm.DB, authorityId string, rules [][]string
//@return: error
func (casbinService *CasbinService) SyncPolicy(db *gorm.DB, authorityId string, rules [][]string) error {
err := casbinService.RemoveFilteredPolicy(db, authorityId)
if err != nil {
return err
}
return casbinService.AddPolicies(db, rules)
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: AddPolicies
//@description: 添加匹配的权限
//@param: v int, p ...string
//@return: bool
func (casbinService *CasbinService) AddPolicies(db *gorm.DB, rules [][]string) error {
var casbinRules []gormadapter.CasbinRule
for i := range rules {
casbinRules = append(casbinRules, gormadapter.CasbinRule{
Ptype: "p",
V0: rules[i][0],
V1: rules[i][1],
V2: rules[i][2],
})
}
return db.Create(&casbinRules).Error
}
func (casbinService *CasbinService) FreshCasbin() (err error) {
e := casbinService.Casbin()
err = e.LoadPolicy()
return err
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: Casbin
//@description: 持久化到数据库 引入自定义规则
//@return: *casbin.Enforcer
var (
syncedCachedEnforcer *casbin.SyncedCachedEnforcer
once sync.Once
)
func (casbinService *CasbinService) Casbin() *casbin.SyncedCachedEnforcer {
once.Do(func() {
a, err := gormadapter.NewAdapterByDB(global.GVA_DB)
if err != nil {
zap.L().Error("适配数据库失败请检查casbin表是否为InnoDB引擎!", zap.Error(err))
return
}
text := `
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && keyMatch2(r.obj,p.obj) && r.act == p.act
`
m, err := model.NewModelFromString(text)
if err != nil {
zap.L().Error("字符串加载模型失败!", zap.Error(err))
return
}
syncedCachedEnforcer, _ = casbin.NewSyncedCachedEnforcer(m, a)
syncedCachedEnforcer.SetExpireTime(60 * 60)
_ = syncedCachedEnforcer.LoadPolicy()
})
return syncedCachedEnforcer
}

View File

@@ -0,0 +1,112 @@
package system
import (
"errors"
"git.echol.cn/loser/xiecheng_server/global"
"git.echol.cn/loser/xiecheng_server/model/system"
"gorm.io/gorm"
)
//@author: [piexlmax](https://github.com/piexlmax)
//@function: CreateSysDictionary
//@description: 创建字典数据
//@param: sysDictionary model.SysDictionary
//@return: err error
type DictionaryService struct{}
var DictionaryServiceApp = new(DictionaryService)
func (dictionaryService *DictionaryService) CreateSysDictionary(sysDictionary system.SysDictionary) (err error) {
if (!errors.Is(global.GVA_DB.First(&system.SysDictionary{}, "type = ?", sysDictionary.Type).Error, gorm.ErrRecordNotFound)) {
return errors.New("存在相同的type不允许创建")
}
err = global.GVA_DB.Create(&sysDictionary).Error
return err
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: DeleteSysDictionary
//@description: 删除字典数据
//@param: sysDictionary model.SysDictionary
//@return: err error
func (dictionaryService *DictionaryService) DeleteSysDictionary(sysDictionary system.SysDictionary) (err error) {
err = global.GVA_DB.Where("id = ?", sysDictionary.ID).Preload("SysDictionaryDetails").First(&sysDictionary).Error
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
return errors.New("请不要搞事")
}
if err != nil {
return err
}
err = global.GVA_DB.Delete(&sysDictionary).Error
if err != nil {
return err
}
if sysDictionary.SysDictionaryDetails != nil {
return global.GVA_DB.Where("sys_dictionary_id=?", sysDictionary.ID).Delete(sysDictionary.SysDictionaryDetails).Error
}
return
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: UpdateSysDictionary
//@description: 更新字典数据
//@param: sysDictionary *model.SysDictionary
//@return: err error
func (dictionaryService *DictionaryService) UpdateSysDictionary(sysDictionary *system.SysDictionary) (err error) {
var dict system.SysDictionary
sysDictionaryMap := map[string]interface{}{
"Name": sysDictionary.Name,
"Type": sysDictionary.Type,
"Status": sysDictionary.Status,
"Desc": sysDictionary.Desc,
}
err = global.GVA_DB.Where("id = ?", sysDictionary.ID).First(&dict).Error
if err != nil {
global.GVA_LOG.Debug(err.Error())
return errors.New("查询字典数据失败")
}
if dict.Type != sysDictionary.Type {
if !errors.Is(global.GVA_DB.First(&system.SysDictionary{}, "type = ?", sysDictionary.Type).Error, gorm.ErrRecordNotFound) {
return errors.New("存在相同的type不允许创建")
}
}
err = global.GVA_DB.Model(&dict).Updates(sysDictionaryMap).Error
return err
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: GetSysDictionary
//@description: 根据id或者type获取字典单条数据
//@param: Type string, Id uint
//@return: err error, sysDictionary model.SysDictionary
func (dictionaryService *DictionaryService) GetSysDictionary(Type string, Id uint, status *bool) (sysDictionary system.SysDictionary, err error) {
var flag = false
if status == nil {
flag = true
} else {
flag = *status
}
err = global.GVA_DB.Where("(type = ? OR id = ?) and status = ?", Type, Id, flag).Preload("SysDictionaryDetails", func(db *gorm.DB) *gorm.DB {
return db.Where("status = ?", true).Order("sort")
}).First(&sysDictionary).Error
return
}
//@author: [piexlmax](https://github.com/piexlmax)
//@author: [SliverHorn](https://github.com/SliverHorn)
//@function: GetSysDictionaryInfoList
//@description: 分页获取字典列表
//@param: info request.SysDictionarySearch
//@return: err error, list interface{}, total int64
func (dictionaryService *DictionaryService) GetSysDictionaryInfoList() (list interface{}, err error) {
var sysDictionarys []system.SysDictionary
err = global.GVA_DB.Find(&sysDictionarys).Error
return sysDictionarys, err
}

View File

@@ -0,0 +1,118 @@
package system
import (
"git.echol.cn/loser/xiecheng_server/global"
"git.echol.cn/loser/xiecheng_server/model/system"
"git.echol.cn/loser/xiecheng_server/model/system/request"
)
//@author: [piexlmax](https://github.com/piexlmax)
//@function: CreateSysDictionaryDetail
//@description: 创建字典详情数据
//@param: sysDictionaryDetail model.SysDictionaryDetail
//@return: err error
type DictionaryDetailService struct{}
var DictionaryDetailServiceApp = new(DictionaryDetailService)
func (dictionaryDetailService *DictionaryDetailService) CreateSysDictionaryDetail(sysDictionaryDetail system.SysDictionaryDetail) (err error) {
err = global.GVA_DB.Create(&sysDictionaryDetail).Error
return err
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: DeleteSysDictionaryDetail
//@description: 删除字典详情数据
//@param: sysDictionaryDetail model.SysDictionaryDetail
//@return: err error
func (dictionaryDetailService *DictionaryDetailService) DeleteSysDictionaryDetail(sysDictionaryDetail system.SysDictionaryDetail) (err error) {
err = global.GVA_DB.Delete(&sysDictionaryDetail).Error
return err
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: UpdateSysDictionaryDetail
//@description: 更新字典详情数据
//@param: sysDictionaryDetail *model.SysDictionaryDetail
//@return: err error
func (dictionaryDetailService *DictionaryDetailService) UpdateSysDictionaryDetail(sysDictionaryDetail *system.SysDictionaryDetail) (err error) {
err = global.GVA_DB.Save(sysDictionaryDetail).Error
return err
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: GetSysDictionaryDetail
//@description: 根据id获取字典详情单条数据
//@param: id uint
//@return: sysDictionaryDetail system.SysDictionaryDetail, err error
func (dictionaryDetailService *DictionaryDetailService) GetSysDictionaryDetail(id uint) (sysDictionaryDetail system.SysDictionaryDetail, err error) {
err = global.GVA_DB.Where("id = ?", id).First(&sysDictionaryDetail).Error
return
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: GetSysDictionaryDetailInfoList
//@description: 分页获取字典详情列表
//@param: info request.SysDictionaryDetailSearch
//@return: list interface{}, total int64, err error
func (dictionaryDetailService *DictionaryDetailService) GetSysDictionaryDetailInfoList(info request.SysDictionaryDetailSearch) (list interface{}, total int64, err error) {
limit := info.PageSize
offset := info.PageSize * (info.Page - 1)
// 创建db
db := global.GVA_DB.Model(&system.SysDictionaryDetail{})
var sysDictionaryDetails []system.SysDictionaryDetail
// 如果有条件搜索 下方会自动创建搜索语句
if info.Label != "" {
db = db.Where("label LIKE ?", "%"+info.Label+"%")
}
if info.Value != "" {
db = db.Where("value = ?", info.Value)
}
if info.Status != nil {
db = db.Where("status = ?", info.Status)
}
if info.SysDictionaryID != 0 {
db = db.Where("sys_dictionary_id = ?", info.SysDictionaryID)
}
err = db.Count(&total).Error
if err != nil {
return
}
err = db.Limit(limit).Offset(offset).Order("sort").Find(&sysDictionaryDetails).Error
return sysDictionaryDetails, total, err
}
// 按照字典id获取字典全部内容的方法
func (dictionaryDetailService *DictionaryDetailService) GetDictionaryList(dictionaryID uint) (list []system.SysDictionaryDetail, err error) {
var sysDictionaryDetails []system.SysDictionaryDetail
err = global.GVA_DB.Find(&sysDictionaryDetails, "sys_dictionary_id = ?", dictionaryID).Error
return sysDictionaryDetails, err
}
// 按照字典type获取字典全部内容的方法
func (dictionaryDetailService *DictionaryDetailService) GetDictionaryListByType(t string) (list []system.SysDictionaryDetail, err error) {
var sysDictionaryDetails []system.SysDictionaryDetail
db := global.GVA_DB.Model(&system.SysDictionaryDetail{}).Joins("JOIN sys_dictionaries ON sys_dictionaries.id = sys_dictionary_details.sys_dictionary_id")
err = db.Debug().Find(&sysDictionaryDetails, "type = ?", t).Error
return sysDictionaryDetails, err
}
// 按照字典id+字典内容value获取单条字典内容
func (dictionaryDetailService *DictionaryDetailService) GetDictionaryInfoByValue(dictionaryID uint, value string) (detail system.SysDictionaryDetail, err error) {
var sysDictionaryDetail system.SysDictionaryDetail
err = global.GVA_DB.First(&sysDictionaryDetail, "sys_dictionary_id = ? and value = ?", dictionaryID, value).Error
return sysDictionaryDetail, err
}
// 按照字典type+字典内容value获取单条字典内容
func (dictionaryDetailService *DictionaryDetailService) GetDictionaryInfoByTypeValue(t string, value string) (detail system.SysDictionaryDetail, err error) {
var sysDictionaryDetails system.SysDictionaryDetail
db := global.GVA_DB.Model(&system.SysDictionaryDetail{}).Joins("JOIN sys_dictionaries ON sys_dictionaries.id = sys_dictionary_details.sys_dictionary_id")
err = db.First(&sysDictionaryDetails, "sys_dictionaries.type = ? and sys_dictionary_details.value = ?", t, value).Error
return sysDictionaryDetails, err
}

View File

@@ -0,0 +1,424 @@
package system
import (
"bytes"
"encoding/json"
"fmt"
"mime/multipart"
"net/url"
"strconv"
"strings"
"time"
"git.echol.cn/loser/xiecheng_server/global"
"git.echol.cn/loser/xiecheng_server/model/common/request"
"git.echol.cn/loser/xiecheng_server/model/system"
systemReq "git.echol.cn/loser/xiecheng_server/model/system/request"
"git.echol.cn/loser/xiecheng_server/utils"
"github.com/xuri/excelize/v2"
"gorm.io/gorm"
)
type SysExportTemplateService struct {
}
var SysExportTemplateServiceApp = new(SysExportTemplateService)
// CreateSysExportTemplate 创建导出模板记录
// Author [piexlmax](https://github.com/piexlmax)
func (sysExportTemplateService *SysExportTemplateService) CreateSysExportTemplate(sysExportTemplate *system.SysExportTemplate) (err error) {
err = global.GVA_DB.Create(sysExportTemplate).Error
return err
}
// DeleteSysExportTemplate 删除导出模板记录
// Author [piexlmax](https://github.com/piexlmax)
func (sysExportTemplateService *SysExportTemplateService) DeleteSysExportTemplate(sysExportTemplate system.SysExportTemplate) (err error) {
err = global.GVA_DB.Delete(&sysExportTemplate).Error
return err
}
// DeleteSysExportTemplateByIds 批量删除导出模板记录
// Author [piexlmax](https://github.com/piexlmax)
func (sysExportTemplateService *SysExportTemplateService) DeleteSysExportTemplateByIds(ids request.IdsReq) (err error) {
err = global.GVA_DB.Delete(&[]system.SysExportTemplate{}, "id in ?", ids.Ids).Error
return err
}
// UpdateSysExportTemplate 更新导出模板记录
// Author [piexlmax](https://github.com/piexlmax)
func (sysExportTemplateService *SysExportTemplateService) UpdateSysExportTemplate(sysExportTemplate system.SysExportTemplate) (err error) {
return global.GVA_DB.Transaction(func(tx *gorm.DB) error {
conditions := sysExportTemplate.Conditions
e := tx.Delete(&[]system.Condition{}, "template_id = ?", sysExportTemplate.TemplateID).Error
if e != nil {
return e
}
sysExportTemplate.Conditions = nil
joins := sysExportTemplate.JoinTemplate
e = tx.Delete(&[]system.JoinTemplate{}, "template_id = ?", sysExportTemplate.TemplateID).Error
if e != nil {
return e
}
sysExportTemplate.JoinTemplate = nil
e = tx.Updates(&sysExportTemplate).Error
if e != nil {
return e
}
if len(conditions) > 0 {
for i := range conditions {
conditions[i].ID = 0
}
e = tx.Create(&conditions).Error
}
if len(joins) > 0 {
for i := range joins {
joins[i].ID = 0
}
e = tx.Create(&joins).Error
}
return e
})
}
// GetSysExportTemplate 根据id获取导出模板记录
// Author [piexlmax](https://github.com/piexlmax)
func (sysExportTemplateService *SysExportTemplateService) GetSysExportTemplate(id uint) (sysExportTemplate system.SysExportTemplate, err error) {
err = global.GVA_DB.Where("id = ?", id).Preload("JoinTemplate").Preload("Conditions").First(&sysExportTemplate).Error
return
}
// GetSysExportTemplateInfoList 分页获取导出模板记录
// Author [piexlmax](https://github.com/piexlmax)
func (sysExportTemplateService *SysExportTemplateService) GetSysExportTemplateInfoList(info systemReq.SysExportTemplateSearch) (list []system.SysExportTemplate, total int64, err error) {
limit := info.PageSize
offset := info.PageSize * (info.Page - 1)
// 创建db
db := global.GVA_DB.Model(&system.SysExportTemplate{})
var sysExportTemplates []system.SysExportTemplate
// 如果有条件搜索 下方会自动创建搜索语句
if info.StartCreatedAt != nil && info.EndCreatedAt != nil {
db = db.Where("created_at BETWEEN ? AND ?", info.StartCreatedAt, info.EndCreatedAt)
}
if info.Name != "" {
db = db.Where("name LIKE ?", "%"+info.Name+"%")
}
if info.TableName != "" {
db = db.Where("table_name = ?", info.TableName)
}
if info.TemplateID != "" {
db = db.Where("template_id = ?", info.TemplateID)
}
err = db.Count(&total).Error
if err != nil {
return
}
if limit != 0 {
db = db.Limit(limit).Offset(offset)
}
err = db.Find(&sysExportTemplates).Error
return sysExportTemplates, total, err
}
// ExportExcel 导出Excel
// Author [piexlmax](https://github.com/piexlmax)
func (sysExportTemplateService *SysExportTemplateService) ExportExcel(templateID string, values url.Values) (file *bytes.Buffer, name string, err error) {
var template system.SysExportTemplate
err = global.GVA_DB.Preload("Conditions").Preload("JoinTemplate").First(&template, "template_id = ?", templateID).Error
if err != nil {
return nil, "", err
}
f := excelize.NewFile()
defer func() {
if err := f.Close(); err != nil {
fmt.Println(err)
}
}()
// Create a new sheet.
index, err := f.NewSheet("Sheet1")
if err != nil {
fmt.Println(err)
return
}
var templateInfoMap = make(map[string]string)
columns, err := utils.GetJSONKeys(template.TemplateInfo)
if err != nil {
return nil, "", err
}
err = json.Unmarshal([]byte(template.TemplateInfo), &templateInfoMap)
if err != nil {
return nil, "", err
}
var tableTitle []string
var selectKeyFmt []string
for _, key := range columns {
selectKeyFmt = append(selectKeyFmt, key)
tableTitle = append(tableTitle, templateInfoMap[key])
}
selects := strings.Join(selectKeyFmt, ", ")
var tableMap []map[string]interface{}
db := global.GVA_DB
if template.DBName != "" {
db = global.MustGetGlobalDBByDBName(template.DBName)
}
if len(template.JoinTemplate) > 0 {
for _, join := range template.JoinTemplate {
db = db.Joins(join.JOINS + " " + join.Table + " ON " + join.ON)
}
}
db = db.Select(selects).Table(template.TableName)
if len(template.Conditions) > 0 {
for _, condition := range template.Conditions {
sql := fmt.Sprintf("%s %s ?", condition.Column, condition.Operator)
value := values.Get(condition.From)
if value != "" {
if condition.Operator == "LIKE" {
value = "%" + value + "%"
}
db = db.Where(sql, value)
}
}
}
// 通过参数传入limit
limit := values.Get("limit")
if limit != "" {
l, e := strconv.Atoi(limit)
if e == nil {
db = db.Limit(l)
}
}
// 模板的默认limit
if limit == "" && template.Limit != nil && *template.Limit != 0 {
db = db.Limit(*template.Limit)
}
// 通过参数传入offset
offset := values.Get("offset")
if offset != "" {
o, e := strconv.Atoi(offset)
if e == nil {
db = db.Offset(o)
}
}
// 获取当前表的所有字段
table := template.TableName
orderColumns, err := db.Migrator().ColumnTypes(table)
if err != nil {
return nil, "", err
}
// 创建一个 map 来存储字段名
fields := make(map[string]bool)
for _, column := range orderColumns {
fields[column.Name()] = true
}
// 通过参数传入order
order := values.Get("order")
if order == "" && template.Order != "" {
// 如果没有order入参这里会使用模板的默认排序
order = template.Order
}
if order != "" {
checkOrderArr := strings.Split(order, " ")
orderStr := ""
// 检查请求的排序字段是否在字段列表中
if _, ok := fields[checkOrderArr[0]]; !ok {
return nil, "", fmt.Errorf("order by %s is not in the fields", order)
}
orderStr = checkOrderArr[0]
if len(checkOrderArr) > 1 {
if checkOrderArr[1] != "asc" && checkOrderArr[1] != "desc" {
return nil, "", fmt.Errorf("order by %s is not secure", order)
}
orderStr = orderStr + " " + checkOrderArr[1]
}
db = db.Order(orderStr)
}
err = db.Debug().Find(&tableMap).Error
if err != nil {
return nil, "", err
}
var rows [][]string
rows = append(rows, tableTitle)
for _, exTable := range tableMap {
var row []string
for _, column := range columns {
column = strings.ReplaceAll(column, "\"", "")
column = strings.ReplaceAll(column, "`", "")
if len(template.JoinTemplate) > 0 {
columnAs := strings.Split(column, " as ")
if len(columnAs) > 1 {
column = strings.TrimSpace(strings.Split(column, " as ")[1])
} else {
columnArr := strings.Split(column, ".")
if len(columnArr) > 1 {
column = strings.Split(column, ".")[1]
}
}
}
// 需要对时间类型特殊处理
if t, ok := exTable[column].(time.Time); ok {
row = append(row, t.Format("2006-01-02 15:04:05"))
} else {
row = append(row, fmt.Sprintf("%v", exTable[column]))
}
}
rows = append(rows, row)
}
for i, row := range rows {
for j, colCell := range row {
sErr := f.SetCellValue("Sheet1", fmt.Sprintf("%s%d", getColumnName(j+1), i+1), colCell)
if sErr != nil {
return nil, "", sErr
}
}
}
f.SetActiveSheet(index)
file, err = f.WriteToBuffer()
if err != nil {
return nil, "", err
}
return file, template.Name, nil
}
// ExportTemplate 导出Excel模板
// Author [piexlmax](https://github.com/piexlmax)
func (sysExportTemplateService *SysExportTemplateService) ExportTemplate(templateID string) (file *bytes.Buffer, name string, err error) {
var template system.SysExportTemplate
err = global.GVA_DB.First(&template, "template_id = ?", templateID).Error
if err != nil {
return nil, "", err
}
f := excelize.NewFile()
defer func() {
if err := f.Close(); err != nil {
fmt.Println(err)
}
}()
// Create a new sheet.
index, err := f.NewSheet("Sheet1")
if err != nil {
fmt.Println(err)
return
}
var templateInfoMap = make(map[string]string)
columns, err := utils.GetJSONKeys(template.TemplateInfo)
err = json.Unmarshal([]byte(template.TemplateInfo), &templateInfoMap)
if err != nil {
return nil, "", err
}
var tableTitle []string
for _, key := range columns {
tableTitle = append(tableTitle, templateInfoMap[key])
}
for i := range tableTitle {
fErr := f.SetCellValue("Sheet1", fmt.Sprintf("%s%d", getColumnName(i+1), 1), tableTitle[i])
if fErr != nil {
return nil, "", fErr
}
}
f.SetActiveSheet(index)
file, err = f.WriteToBuffer()
if err != nil {
return nil, "", err
}
return file, template.Name, nil
}
// ImportExcel 导入Excel
// Author [piexlmax](https://github.com/piexlmax)
func (sysExportTemplateService *SysExportTemplateService) ImportExcel(templateID string, file *multipart.FileHeader) (err error) {
var template system.SysExportTemplate
err = global.GVA_DB.First(&template, "template_id = ?", templateID).Error
if err != nil {
return err
}
src, err := file.Open()
if err != nil {
return err
}
defer src.Close()
f, err := excelize.OpenReader(src)
if err != nil {
return err
}
rows, err := f.GetRows("Sheet1")
if err != nil {
return err
}
var templateInfoMap = make(map[string]string)
err = json.Unmarshal([]byte(template.TemplateInfo), &templateInfoMap)
if err != nil {
return err
}
var titleKeyMap = make(map[string]string)
for key, title := range templateInfoMap {
titleKeyMap[title] = key
}
db := global.GVA_DB
if template.DBName != "" {
db = global.MustGetGlobalDBByDBName(template.DBName)
}
return db.Transaction(func(tx *gorm.DB) error {
excelTitle := rows[0]
values := rows[1:]
items := make([]map[string]interface{}, 0, len(values))
for _, row := range values {
var item = make(map[string]interface{})
for ii, value := range row {
key := titleKeyMap[excelTitle[ii]]
item[key] = value
}
needCreated := tx.Migrator().HasColumn(template.TableName, "created_at")
needUpdated := tx.Migrator().HasColumn(template.TableName, "updated_at")
if item["created_at"] == nil && needCreated {
item["created_at"] = time.Now()
}
if item["updated_at"] == nil && needUpdated {
item["updated_at"] = time.Now()
}
items = append(items, item)
}
cErr := tx.Table(template.TableName).CreateInBatches(&items, 1000).Error
return cErr
})
}
func getColumnName(n int) string {
columnName := ""
for n > 0 {
n--
columnName = string(rune('A'+n%26)) + columnName
n /= 26
}
return columnName
}

View File

@@ -0,0 +1,189 @@
package system
import (
"context"
"database/sql"
"errors"
"fmt"
"git.echol.cn/loser/xiecheng_server/global"
"git.echol.cn/loser/xiecheng_server/model/system/request"
"gorm.io/gorm"
"sort"
)
const (
Mysql = "mysql"
Pgsql = "pgsql"
Sqlite = "sqlite"
Mssql = "mssql"
InitSuccess = "\n[%v] --> 初始数据成功!\n"
InitDataExist = "\n[%v] --> %v 的初始数据已存在!\n"
InitDataFailed = "\n[%v] --> %v 初始数据失败! \nerr: %+v\n"
InitDataSuccess = "\n[%v] --> %v 初始数据成功!\n"
)
const (
InitOrderSystem = 10
InitOrderInternal = 1000
InitOrderExternal = 100000
)
var (
ErrMissingDBContext = errors.New("missing db in context")
ErrMissingDependentContext = errors.New("missing dependent value in context")
ErrDBTypeMismatch = errors.New("db type mismatch")
)
// SubInitializer 提供 source/*/init() 使用的接口,每个 initializer 完成一个初始化过程
type SubInitializer interface {
InitializerName() string // 不一定代表单独一个表,所以改成了更宽泛的语义
MigrateTable(ctx context.Context) (next context.Context, err error)
InitializeData(ctx context.Context) (next context.Context, err error)
TableCreated(ctx context.Context) bool
DataInserted(ctx context.Context) bool
}
// TypedDBInitHandler 执行传入的 initializer
type TypedDBInitHandler interface {
EnsureDB(ctx context.Context, conf *request.InitDB) (context.Context, error) // 建库,失败属于 fatal error因此让它 panic
WriteConfig(ctx context.Context) error // 回写配置
InitTables(ctx context.Context, inits initSlice) error // 建表 handler
InitData(ctx context.Context, inits initSlice) error // 建数据 handler
}
// orderedInitializer 组合一个顺序字段,以供排序
type orderedInitializer struct {
order int
SubInitializer
}
// initSlice 供 initializer 排序依赖时使用
type initSlice []*orderedInitializer
var (
initializers initSlice
cache map[string]*orderedInitializer
)
// RegisterInit 注册要执行的初始化过程,会在 InitDB() 时调用
func RegisterInit(order int, i SubInitializer) {
if initializers == nil {
initializers = initSlice{}
}
if cache == nil {
cache = map[string]*orderedInitializer{}
}
name := i.InitializerName()
if _, existed := cache[name]; existed {
panic(fmt.Sprintf("Name conflict on %s", name))
}
ni := orderedInitializer{order, i}
initializers = append(initializers, &ni)
cache[name] = &ni
}
/* ---- * service * ---- */
type InitDBService struct{}
// InitDB 创建数据库并初始化 总入口
func (initDBService *InitDBService) InitDB(conf request.InitDB) (err error) {
ctx := context.TODO()
ctx = context.WithValue(ctx, "adminPassword", conf.AdminPassword)
if len(initializers) == 0 {
return errors.New("无可用初始化过程,请检查初始化是否已执行完成")
}
sort.Sort(&initializers) // 保证有依赖的 initializer 排在后面执行
// Note: 若 initializer 只有单一依赖,可以写为 B=A+1, C=A+1; 由于 BC 之间没有依赖关系,所以谁先谁后并不影响初始化
// 若存在多个依赖,可以写为 C=A+B, D=A+B+C, E=A+1;
// C必然>A|B因此在AB之后执行D必然>A|B|C因此在ABC后执行而E只依赖A顺序与CD无关因此E与CD哪个先执行并不影响
var initHandler TypedDBInitHandler
switch conf.DBType {
case "mysql":
initHandler = NewMysqlInitHandler()
ctx = context.WithValue(ctx, "dbtype", "mysql")
case "pgsql":
initHandler = NewPgsqlInitHandler()
ctx = context.WithValue(ctx, "dbtype", "pgsql")
case "sqlite":
initHandler = NewSqliteInitHandler()
ctx = context.WithValue(ctx, "dbtype", "sqlite")
case "mssql":
initHandler = NewMssqlInitHandler()
ctx = context.WithValue(ctx, "dbtype", "mssql")
default:
initHandler = NewMysqlInitHandler()
ctx = context.WithValue(ctx, "dbtype", "mysql")
}
ctx, err = initHandler.EnsureDB(ctx, &conf)
if err != nil {
return err
}
db := ctx.Value("db").(*gorm.DB)
global.GVA_DB = db
if err = initHandler.InitTables(ctx, initializers); err != nil {
return err
}
if err = initHandler.InitData(ctx, initializers); err != nil {
return err
}
if err = initHandler.WriteConfig(ctx); err != nil {
return err
}
initializers = initSlice{}
cache = map[string]*orderedInitializer{}
return nil
}
// createDatabase 创建数据库( EnsureDB() 中调用
func createDatabase(dsn string, driver string, createSql string) error {
db, err := sql.Open(driver, dsn)
if err != nil {
return err
}
defer func(db *sql.DB) {
err = db.Close()
if err != nil {
fmt.Println(err)
}
}(db)
if err = db.Ping(); err != nil {
return err
}
_, err = db.Exec(createSql)
return err
}
// createTables 创建表(默认 dbInitHandler.initTables 行为)
func createTables(ctx context.Context, inits initSlice) error {
next, cancel := context.WithCancel(ctx)
defer func(c func()) { c() }(cancel)
for _, init := range inits {
if init.TableCreated(next) {
continue
}
if n, err := init.MigrateTable(next); err != nil {
return err
} else {
next = n
}
}
return nil
}
/* -- sortable interface -- */
func (a initSlice) Len() int {
return len(a)
}
func (a initSlice) Less(i, j int) bool {
return a[i].order < a[j].order
}
func (a initSlice) Swap(i, j int) {
a[i], a[j] = a[j], a[i]
}

View File

@@ -0,0 +1,92 @@
package system
import (
"context"
"errors"
"git.echol.cn/loser/xiecheng_server/config"
"git.echol.cn/loser/xiecheng_server/global"
"git.echol.cn/loser/xiecheng_server/model/system/request"
"git.echol.cn/loser/xiecheng_server/utils"
"github.com/google/uuid"
"github.com/gookit/color"
"gorm.io/driver/sqlserver"
"gorm.io/gorm"
"path/filepath"
)
type MssqlInitHandler struct{}
func NewMssqlInitHandler() *MssqlInitHandler {
return &MssqlInitHandler{}
}
// WriteConfig mssql回写配置
func (h MssqlInitHandler) WriteConfig(ctx context.Context) error {
c, ok := ctx.Value("config").(config.Mssql)
if !ok {
return errors.New("mssql config invalid")
}
global.GVA_CONFIG.System.DbType = "mssql"
global.GVA_CONFIG.Mssql = c
global.GVA_CONFIG.JWT.SigningKey = uuid.New().String()
cs := utils.StructToMap(global.GVA_CONFIG)
for k, v := range cs {
global.GVA_VP.Set(k, v)
}
global.GVA_ACTIVE_DBNAME = &c.Dbname
return global.GVA_VP.WriteConfig()
}
// EnsureDB 创建数据库并初始化 mssql
func (h MssqlInitHandler) EnsureDB(ctx context.Context, conf *request.InitDB) (next context.Context, err error) {
if s, ok := ctx.Value("dbtype").(string); !ok || s != "mssql" {
return ctx, ErrDBTypeMismatch
}
c := conf.ToMssqlConfig()
next = context.WithValue(ctx, "config", c)
if c.Dbname == "" {
return ctx, nil
} // 如果没有数据库名, 则跳出初始化数据
dsn := conf.MssqlEmptyDsn()
mssqlConfig := sqlserver.Config{
DSN: dsn, // DSN data source name
DefaultStringSize: 191, // string 类型字段的默认长度
}
var db *gorm.DB
if db, err = gorm.Open(sqlserver.New(mssqlConfig), &gorm.Config{DisableForeignKeyConstraintWhenMigrating: true}); err != nil {
return nil, err
}
global.GVA_CONFIG.AutoCode.Root, _ = filepath.Abs("..")
next = context.WithValue(next, "db", db)
return next, err
}
func (h MssqlInitHandler) InitTables(ctx context.Context, inits initSlice) error {
return createTables(ctx, inits)
}
func (h MssqlInitHandler) InitData(ctx context.Context, inits initSlice) error {
next, cancel := context.WithCancel(ctx)
defer func(c func()) { c() }(cancel)
for _, init := range inits {
if init.DataInserted(next) {
color.Info.Printf(InitDataExist, Mssql, init.InitializerName())
continue
}
if n, err := init.InitializeData(next); err != nil {
color.Info.Printf(InitDataFailed, Mssql, init.InitializerName(), err)
return err
} else {
next = n
color.Info.Printf(InitDataSuccess, Mssql, init.InitializerName())
}
}
color.Info.Printf(InitSuccess, Mssql)
return nil
}

View File

@@ -0,0 +1,97 @@
package system
import (
"context"
"errors"
"fmt"
"path/filepath"
"git.echol.cn/loser/xiecheng_server/config"
"github.com/gookit/color"
"git.echol.cn/loser/xiecheng_server/utils"
"git.echol.cn/loser/xiecheng_server/global"
"git.echol.cn/loser/xiecheng_server/model/system/request"
"github.com/google/uuid"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type MysqlInitHandler struct{}
func NewMysqlInitHandler() *MysqlInitHandler {
return &MysqlInitHandler{}
}
// WriteConfig mysql回写配置
func (h MysqlInitHandler) WriteConfig(ctx context.Context) error {
c, ok := ctx.Value("config").(config.Mysql)
if !ok {
return errors.New("mysql config invalid")
}
global.GVA_CONFIG.System.DbType = "mysql"
global.GVA_CONFIG.Mysql = c
global.GVA_CONFIG.JWT.SigningKey = uuid.New().String()
cs := utils.StructToMap(global.GVA_CONFIG)
for k, v := range cs {
global.GVA_VP.Set(k, v)
}
global.GVA_ACTIVE_DBNAME = &c.Dbname
return global.GVA_VP.WriteConfig()
}
// EnsureDB 创建数据库并初始化 mysql
func (h MysqlInitHandler) EnsureDB(ctx context.Context, conf *request.InitDB) (next context.Context, err error) {
if s, ok := ctx.Value("dbtype").(string); !ok || s != "mysql" {
return ctx, ErrDBTypeMismatch
}
c := conf.ToMysqlConfig()
next = context.WithValue(ctx, "config", c)
if c.Dbname == "" {
return ctx, nil
} // 如果没有数据库名, 则跳出初始化数据
dsn := conf.MysqlEmptyDsn()
createSql := fmt.Sprintf("CREATE DATABASE IF NOT EXISTS `%s` DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci;", c.Dbname)
if err = createDatabase(dsn, "mysql", createSql); err != nil {
return nil, err
} // 创建数据库
var db *gorm.DB
if db, err = gorm.Open(mysql.New(mysql.Config{
DSN: c.Dsn(), // DSN data source name
DefaultStringSize: 191, // string 类型字段的默认长度
SkipInitializeWithVersion: true, // 根据版本自动配置
}), &gorm.Config{DisableForeignKeyConstraintWhenMigrating: true}); err != nil {
return ctx, err
}
global.GVA_CONFIG.AutoCode.Root, _ = filepath.Abs("..")
next = context.WithValue(next, "db", db)
return next, err
}
func (h MysqlInitHandler) InitTables(ctx context.Context, inits initSlice) error {
return createTables(ctx, inits)
}
func (h MysqlInitHandler) InitData(ctx context.Context, inits initSlice) error {
next, cancel := context.WithCancel(ctx)
defer func(c func()) { c() }(cancel)
for _, init := range inits {
if init.DataInserted(next) {
color.Info.Printf(InitDataExist, Mysql, init.InitializerName())
continue
}
if n, err := init.InitializeData(next); err != nil {
color.Info.Printf(InitDataFailed, Mysql, init.InitializerName(), err)
return err
} else {
next = n
color.Info.Printf(InitDataSuccess, Mysql, init.InitializerName())
}
}
color.Info.Printf(InitSuccess, Mysql)
return nil
}

View File

@@ -0,0 +1,101 @@
package system
import (
"context"
"errors"
"fmt"
"path/filepath"
"git.echol.cn/loser/xiecheng_server/config"
"github.com/gookit/color"
"git.echol.cn/loser/xiecheng_server/utils"
"git.echol.cn/loser/xiecheng_server/global"
"git.echol.cn/loser/xiecheng_server/model/system/request"
"github.com/google/uuid"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
type PgsqlInitHandler struct{}
func NewPgsqlInitHandler() *PgsqlInitHandler {
return &PgsqlInitHandler{}
}
// WriteConfig pgsql 回写配置
func (h PgsqlInitHandler) WriteConfig(ctx context.Context) error {
c, ok := ctx.Value("config").(config.Pgsql)
if !ok {
return errors.New("postgresql config invalid")
}
global.GVA_CONFIG.System.DbType = "pgsql"
global.GVA_CONFIG.Pgsql = c
global.GVA_CONFIG.JWT.SigningKey = uuid.New().String()
cs := utils.StructToMap(global.GVA_CONFIG)
for k, v := range cs {
global.GVA_VP.Set(k, v)
}
global.GVA_ACTIVE_DBNAME = &c.Dbname
return global.GVA_VP.WriteConfig()
}
// EnsureDB 创建数据库并初始化 pg
func (h PgsqlInitHandler) EnsureDB(ctx context.Context, conf *request.InitDB) (next context.Context, err error) {
if s, ok := ctx.Value("dbtype").(string); !ok || s != "pgsql" {
return ctx, ErrDBTypeMismatch
}
c := conf.ToPgsqlConfig()
next = context.WithValue(ctx, "config", c)
if c.Dbname == "" {
return ctx, nil
} // 如果没有数据库名, 则跳出初始化数据
dsn := conf.PgsqlEmptyDsn()
var createSql string
if conf.Template != "" {
createSql = fmt.Sprintf("CREATE DATABASE %s WITH TEMPLATE %s;", c.Dbname, conf.Template)
} else {
createSql = fmt.Sprintf("CREATE DATABASE %s;", c.Dbname)
}
if err = createDatabase(dsn, "pgx", createSql); err != nil {
return nil, err
} // 创建数据库
var db *gorm.DB
if db, err = gorm.Open(postgres.New(postgres.Config{
DSN: c.Dsn(), // DSN data source name
PreferSimpleProtocol: false,
}), &gorm.Config{DisableForeignKeyConstraintWhenMigrating: true}); err != nil {
return ctx, err
}
global.GVA_CONFIG.AutoCode.Root, _ = filepath.Abs("..")
next = context.WithValue(next, "db", db)
return next, err
}
func (h PgsqlInitHandler) InitTables(ctx context.Context, inits initSlice) error {
return createTables(ctx, inits)
}
func (h PgsqlInitHandler) InitData(ctx context.Context, inits initSlice) error {
next, cancel := context.WithCancel(ctx)
defer func(c func()) { c() }(cancel)
for i := 0; i < len(inits); i++ {
if inits[i].DataInserted(next) {
color.Info.Printf(InitDataExist, Pgsql, inits[i].InitializerName())
continue
}
if n, err := inits[i].InitializeData(next); err != nil {
color.Info.Printf(InitDataFailed, Pgsql, inits[i].InitializerName(), err)
return err
} else {
next = n
color.Info.Printf(InitDataSuccess, Pgsql, inits[i].InitializerName())
}
}
color.Info.Printf(InitSuccess, Pgsql)
return nil
}

View File

@@ -0,0 +1,88 @@
package system
import (
"context"
"errors"
"github.com/glebarez/sqlite"
"github.com/google/uuid"
"github.com/gookit/color"
"gorm.io/gorm"
"path/filepath"
"git.echol.cn/loser/xiecheng_server/config"
"git.echol.cn/loser/xiecheng_server/global"
"git.echol.cn/loser/xiecheng_server/model/system/request"
"git.echol.cn/loser/xiecheng_server/utils"
)
type SqliteInitHandler struct{}
func NewSqliteInitHandler() *SqliteInitHandler {
return &SqliteInitHandler{}
}
// WriteConfig mysql回写配置
func (h SqliteInitHandler) WriteConfig(ctx context.Context) error {
c, ok := ctx.Value("config").(config.Sqlite)
if !ok {
return errors.New("sqlite config invalid")
}
global.GVA_CONFIG.System.DbType = "sqlite"
global.GVA_CONFIG.Sqlite = c
global.GVA_CONFIG.JWT.SigningKey = uuid.New().String()
cs := utils.StructToMap(global.GVA_CONFIG)
for k, v := range cs {
global.GVA_VP.Set(k, v)
}
global.GVA_ACTIVE_DBNAME = &c.Dbname
return global.GVA_VP.WriteConfig()
}
// EnsureDB 创建数据库并初始化 sqlite
func (h SqliteInitHandler) EnsureDB(ctx context.Context, conf *request.InitDB) (next context.Context, err error) {
if s, ok := ctx.Value("dbtype").(string); !ok || s != "sqlite" {
return ctx, ErrDBTypeMismatch
}
c := conf.ToSqliteConfig()
next = context.WithValue(ctx, "config", c)
if c.Dbname == "" {
return ctx, nil
} // 如果没有数据库名, 则跳出初始化数据
dsn := conf.SqliteEmptyDsn()
var db *gorm.DB
if db, err = gorm.Open(sqlite.Open(dsn), &gorm.Config{
DisableForeignKeyConstraintWhenMigrating: true,
}); err != nil {
return ctx, err
}
global.GVA_CONFIG.AutoCode.Root, _ = filepath.Abs("..")
next = context.WithValue(next, "db", db)
return next, err
}
func (h SqliteInitHandler) InitTables(ctx context.Context, inits initSlice) error {
return createTables(ctx, inits)
}
func (h SqliteInitHandler) InitData(ctx context.Context, inits initSlice) error {
next, cancel := context.WithCancel(ctx)
defer func(c func()) { c() }(cancel)
for _, init := range inits {
if init.DataInserted(next) {
color.Info.Printf(InitDataExist, Sqlite, init.InitializerName())
continue
}
if n, err := init.InitializeData(next); err != nil {
color.Info.Printf(InitDataFailed, Sqlite, init.InitializerName(), err)
return err
} else {
next = n
color.Info.Printf(InitDataSuccess, Sqlite, init.InitializerName())
}
}
color.Info.Printf(InitSuccess, Sqlite)
return nil
}

289
service/system/sys_menu.go Normal file
View File

@@ -0,0 +1,289 @@
package system
import (
"errors"
"git.echol.cn/loser/xiecheng_server/global"
"git.echol.cn/loser/xiecheng_server/model/common/request"
"git.echol.cn/loser/xiecheng_server/model/system"
"gorm.io/gorm"
"strconv"
)
//@author: [piexlmax](https://github.com/piexlmax)
//@function: getMenuTreeMap
//@description: 获取路由总树map
//@param: authorityId string
//@return: treeMap map[string][]system.SysMenu, err error
type MenuService struct{}
var MenuServiceApp = new(MenuService)
func (menuService *MenuService) getMenuTreeMap(authorityId uint) (treeMap map[uint][]system.SysMenu, err error) {
var allMenus []system.SysMenu
var baseMenu []system.SysBaseMenu
var btns []system.SysAuthorityBtn
treeMap = make(map[uint][]system.SysMenu)
var SysAuthorityMenus []system.SysAuthorityMenu
err = global.GVA_DB.Where("sys_authority_authority_id = ?", authorityId).Find(&SysAuthorityMenus).Error
if err != nil {
return
}
var MenuIds []string
for i := range SysAuthorityMenus {
MenuIds = append(MenuIds, SysAuthorityMenus[i].MenuId)
}
err = global.GVA_DB.Where("id in (?)", MenuIds).Order("sort").Preload("Parameters").Find(&baseMenu).Error
if err != nil {
return
}
for i := range baseMenu {
allMenus = append(allMenus, system.SysMenu{
SysBaseMenu: baseMenu[i],
AuthorityId: authorityId,
MenuId: baseMenu[i].ID,
Parameters: baseMenu[i].Parameters,
})
}
err = global.GVA_DB.Where("authority_id = ?", authorityId).Preload("SysBaseMenuBtn").Find(&btns).Error
if err != nil {
return
}
var btnMap = make(map[uint]map[string]uint)
for _, v := range btns {
if btnMap[v.SysMenuID] == nil {
btnMap[v.SysMenuID] = make(map[string]uint)
}
btnMap[v.SysMenuID][v.SysBaseMenuBtn.Name] = authorityId
}
for _, v := range allMenus {
v.Btns = btnMap[v.SysBaseMenu.ID]
treeMap[v.ParentId] = append(treeMap[v.ParentId], v)
}
return treeMap, err
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: GetMenuTree
//@description: 获取动态菜单树
//@param: authorityId string
//@return: menus []system.SysMenu, err error
func (menuService *MenuService) GetMenuTree(authorityId uint) (menus []system.SysMenu, err error) {
menuTree, err := menuService.getMenuTreeMap(authorityId)
menus = menuTree[0]
for i := 0; i < len(menus); i++ {
err = menuService.getChildrenList(&menus[i], menuTree)
}
return menus, err
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: getChildrenList
//@description: 获取子菜单
//@param: menu *model.SysMenu, treeMap map[string][]model.SysMenu
//@return: err error
func (menuService *MenuService) getChildrenList(menu *system.SysMenu, treeMap map[uint][]system.SysMenu) (err error) {
menu.Children = treeMap[menu.MenuId]
for i := 0; i < len(menu.Children); i++ {
err = menuService.getChildrenList(&menu.Children[i], treeMap)
}
return err
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: GetInfoList
//@description: 获取路由分页
//@return: list interface{}, total int64,err error
func (menuService *MenuService) GetInfoList(authorityID uint) (list interface{}, err error) {
var menuList []system.SysBaseMenu
treeMap, err := menuService.getBaseMenuTreeMap(authorityID)
menuList = treeMap[0]
for i := 0; i < len(menuList); i++ {
err = menuService.getBaseChildrenList(&menuList[i], treeMap)
}
return menuList, err
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: getBaseChildrenList
//@description: 获取菜单的子菜单
//@param: menu *model.SysBaseMenu, treeMap map[string][]model.SysBaseMenu
//@return: err error
func (menuService *MenuService) getBaseChildrenList(menu *system.SysBaseMenu, treeMap map[uint][]system.SysBaseMenu) (err error) {
menu.Children = treeMap[menu.ID]
for i := 0; i < len(menu.Children); i++ {
err = menuService.getBaseChildrenList(&menu.Children[i], treeMap)
}
return err
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: AddBaseMenu
//@description: 添加基础路由
//@param: menu model.SysBaseMenu
//@return: error
func (menuService *MenuService) AddBaseMenu(menu system.SysBaseMenu) error {
if !errors.Is(global.GVA_DB.Where("name = ?", menu.Name).First(&system.SysBaseMenu{}).Error, gorm.ErrRecordNotFound) {
return errors.New("存在重复name请修改name")
}
return global.GVA_DB.Create(&menu).Error
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: getBaseMenuTreeMap
//@description: 获取路由总树map
//@return: treeMap map[string][]system.SysBaseMenu, err error
func (menuService *MenuService) getBaseMenuTreeMap(authorityID uint) (treeMap map[uint][]system.SysBaseMenu, err error) {
parentAuthorityID, err := AuthorityServiceApp.GetParentAuthorityID(authorityID)
if err != nil {
return nil, err
}
var allMenus []system.SysBaseMenu
treeMap = make(map[uint][]system.SysBaseMenu)
db := global.GVA_DB.Order("sort").Preload("MenuBtn").Preload("Parameters")
// 当开启了严格的树角色并且父角色不为0时需要进行菜单筛选
if global.GVA_CONFIG.System.UseStrictAuth && parentAuthorityID != 0 {
var authorityMenus []system.SysAuthorityMenu
err = global.GVA_DB.Where("sys_authority_authority_id = ?", authorityID).Find(&authorityMenus).Error
if err != nil {
return nil, err
}
var menuIds []string
for i := range authorityMenus {
menuIds = append(menuIds, authorityMenus[i].MenuId)
}
db = db.Where("id in (?)", menuIds)
}
err = db.Find(&allMenus).Error
for _, v := range allMenus {
treeMap[v.ParentId] = append(treeMap[v.ParentId], v)
}
return treeMap, err
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: GetBaseMenuTree
//@description: 获取基础路由树
//@return: menus []system.SysBaseMenu, err error
func (menuService *MenuService) GetBaseMenuTree(authorityID uint) (menus []system.SysBaseMenu, err error) {
treeMap, err := menuService.getBaseMenuTreeMap(authorityID)
menus = treeMap[0]
for i := 0; i < len(menus); i++ {
err = menuService.getBaseChildrenList(&menus[i], treeMap)
}
return menus, err
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: AddMenuAuthority
//@description: 为角色增加menu树
//@param: menus []model.SysBaseMenu, authorityId string
//@return: err error
func (menuService *MenuService) AddMenuAuthority(menus []system.SysBaseMenu, adminAuthorityID, authorityId uint) (err error) {
var auth system.SysAuthority
auth.AuthorityId = authorityId
auth.SysBaseMenus = menus
err = AuthorityServiceApp.CheckAuthorityIDAuth(adminAuthorityID, authorityId)
if err != nil {
return err
}
var authority system.SysAuthority
_ = global.GVA_DB.First(&authority, "authority_id = ?", adminAuthorityID).Error
var menuIds []string
// 当开启了严格的树角色并且父角色不为0时需要进行菜单筛选
if global.GVA_CONFIG.System.UseStrictAuth && *authority.ParentId != 0 {
var authorityMenus []system.SysAuthorityMenu
err = global.GVA_DB.Where("sys_authority_authority_id = ?", adminAuthorityID).Find(&authorityMenus).Error
if err != nil {
return err
}
for i := range authorityMenus {
menuIds = append(menuIds, authorityMenus[i].MenuId)
}
for i := range menus {
hasMenu := false
for j := range menuIds {
idStr := strconv.Itoa(int(menus[i].ID))
if idStr == menuIds[j] {
hasMenu = true
}
}
if !hasMenu {
return errors.New("添加失败,请勿跨级操作")
}
}
}
err = AuthorityServiceApp.SetMenuAuthority(&auth)
return err
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: GetMenuAuthority
//@description: 查看当前角色树
//@param: info *request.GetAuthorityId
//@return: menus []system.SysMenu, err error
func (menuService *MenuService) GetMenuAuthority(info *request.GetAuthorityId) (menus []system.SysMenu, err error) {
var baseMenu []system.SysBaseMenu
var SysAuthorityMenus []system.SysAuthorityMenu
err = global.GVA_DB.Where("sys_authority_authority_id = ?", info.AuthorityId).Find(&SysAuthorityMenus).Error
if err != nil {
return
}
var MenuIds []string
for i := range SysAuthorityMenus {
MenuIds = append(MenuIds, SysAuthorityMenus[i].MenuId)
}
err = global.GVA_DB.Where("id in (?) ", MenuIds).Order("sort").Find(&baseMenu).Error
for i := range baseMenu {
menus = append(menus, system.SysMenu{
SysBaseMenu: baseMenu[i],
AuthorityId: info.AuthorityId,
MenuId: baseMenu[i].ID,
Parameters: baseMenu[i].Parameters,
})
}
return menus, err
}
// UserAuthorityDefaultRouter 用户角色默认路由检查
//
// Author [SliverHorn](https://github.com/SliverHorn)
func (menuService *MenuService) UserAuthorityDefaultRouter(user *system.SysUser) {
var menuIds []string
err := global.GVA_DB.Model(&system.SysAuthorityMenu{}).Where("sys_authority_authority_id = ?", user.AuthorityId).Pluck("sys_base_menu_id", &menuIds).Error
if err != nil {
return
}
var am system.SysBaseMenu
err = global.GVA_DB.First(&am, "name = ? and id in (?)", user.Authority.DefaultRouter, menuIds).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
user.Authority.DefaultRouter = "404"
}
}

View File

@@ -0,0 +1,88 @@
package system
import (
"git.echol.cn/loser/xiecheng_server/global"
"git.echol.cn/loser/xiecheng_server/model/common/request"
"git.echol.cn/loser/xiecheng_server/model/system"
systemReq "git.echol.cn/loser/xiecheng_server/model/system/request"
)
//@author: [granty1](https://github.com/granty1)
//@function: CreateSysOperationRecord
//@description: 创建记录
//@param: sysOperationRecord model.SysOperationRecord
//@return: err error
type OperationRecordService struct{}
var OperationRecordServiceApp = new(OperationRecordService)
func (operationRecordService *OperationRecordService) CreateSysOperationRecord(sysOperationRecord system.SysOperationRecord) (err error) {
err = global.GVA_DB.Create(&sysOperationRecord).Error
return err
}
//@author: [granty1](https://github.com/granty1)
//@author: [piexlmax](https://github.com/piexlmax)
//@function: DeleteSysOperationRecordByIds
//@description: 批量删除记录
//@param: ids request.IdsReq
//@return: err error
func (operationRecordService *OperationRecordService) DeleteSysOperationRecordByIds(ids request.IdsReq) (err error) {
err = global.GVA_DB.Delete(&[]system.SysOperationRecord{}, "id in (?)", ids.Ids).Error
return err
}
//@author: [granty1](https://github.com/granty1)
//@function: DeleteSysOperationRecord
//@description: 删除操作记录
//@param: sysOperationRecord model.SysOperationRecord
//@return: err error
func (operationRecordService *OperationRecordService) DeleteSysOperationRecord(sysOperationRecord system.SysOperationRecord) (err error) {
err = global.GVA_DB.Delete(&sysOperationRecord).Error
return err
}
//@author: [granty1](https://github.com/granty1)
//@function: GetSysOperationRecord
//@description: 根据id获取单条操作记录
//@param: id uint
//@return: sysOperationRecord system.SysOperationRecord, err error
func (operationRecordService *OperationRecordService) GetSysOperationRecord(id uint) (sysOperationRecord system.SysOperationRecord, err error) {
err = global.GVA_DB.Where("id = ?", id).First(&sysOperationRecord).Error
return
}
//@author: [granty1](https://github.com/granty1)
//@author: [piexlmax](https://github.com/piexlmax)
//@function: GetSysOperationRecordInfoList
//@description: 分页获取操作记录列表
//@param: info systemReq.SysOperationRecordSearch
//@return: list interface{}, total int64, err error
func (operationRecordService *OperationRecordService) GetSysOperationRecordInfoList(info systemReq.SysOperationRecordSearch) (list interface{}, total int64, err error) {
limit := info.PageSize
offset := info.PageSize * (info.Page - 1)
// 创建db
db := global.GVA_DB.Model(&system.SysOperationRecord{})
var sysOperationRecords []system.SysOperationRecord
// 如果有条件搜索 下方会自动创建搜索语句
if info.Method != "" {
db = db.Where("method = ?", info.Method)
}
if info.Path != "" {
db = db.Where("path LIKE ?", "%"+info.Path+"%")
}
if info.Status != 0 {
db = db.Where("status = ?", info.Status)
}
err = db.Count(&total).Error
if err != nil {
return
}
err = db.Order("id desc").Limit(limit).Offset(offset).Preload("User").Find(&sysOperationRecords).Error
return sysOperationRecords, total, err
}

View File

@@ -0,0 +1,82 @@
package system
import (
"git.echol.cn/loser/xiecheng_server/global"
"git.echol.cn/loser/xiecheng_server/model/system"
systemReq "git.echol.cn/loser/xiecheng_server/model/system/request"
)
type SysParamsService struct{}
// CreateSysParams 创建参数记录
// Author [Mr.奇淼](https://github.com/pixelmaxQm)
func (sysParamsService *SysParamsService) CreateSysParams(sysParams *system.SysParams) (err error) {
err = global.GVA_DB.Create(sysParams).Error
return err
}
// DeleteSysParams 删除参数记录
// Author [Mr.奇淼](https://github.com/pixelmaxQm)
func (sysParamsService *SysParamsService) DeleteSysParams(ID string) (err error) {
err = global.GVA_DB.Delete(&system.SysParams{}, "id = ?", ID).Error
return err
}
// DeleteSysParamsByIds 批量删除参数记录
// Author [Mr.奇淼](https://github.com/pixelmaxQm)
func (sysParamsService *SysParamsService) DeleteSysParamsByIds(IDs []string) (err error) {
err = global.GVA_DB.Delete(&[]system.SysParams{}, "id in ?", IDs).Error
return err
}
// UpdateSysParams 更新参数记录
// Author [Mr.奇淼](https://github.com/pixelmaxQm)
func (sysParamsService *SysParamsService) UpdateSysParams(sysParams system.SysParams) (err error) {
err = global.GVA_DB.Model(&system.SysParams{}).Where("id = ?", sysParams.ID).Updates(&sysParams).Error
return err
}
// GetSysParams 根据ID获取参数记录
// Author [Mr.奇淼](https://github.com/pixelmaxQm)
func (sysParamsService *SysParamsService) GetSysParams(ID string) (sysParams system.SysParams, err error) {
err = global.GVA_DB.Where("id = ?", ID).First(&sysParams).Error
return
}
// GetSysParamsInfoList 分页获取参数记录
// Author [Mr.奇淼](https://github.com/pixelmaxQm)
func (sysParamsService *SysParamsService) GetSysParamsInfoList(info systemReq.SysParamsSearch) (list []system.SysParams, total int64, err error) {
limit := info.PageSize
offset := info.PageSize * (info.Page - 1)
// 创建db
db := global.GVA_DB.Model(&system.SysParams{})
var sysParamss []system.SysParams
// 如果有条件搜索 下方会自动创建搜索语句
if info.StartCreatedAt != nil && info.EndCreatedAt != nil {
db = db.Where("created_at BETWEEN ? AND ?", info.StartCreatedAt, info.EndCreatedAt)
}
if info.Name != "" {
db = db.Where("name LIKE ?", "%"+info.Name+"%")
}
if info.Key != "" {
db = db.Where("key LIKE ?", "%"+info.Key+"%")
}
err = db.Count(&total).Error
if err != nil {
return
}
if limit != 0 {
db = db.Limit(limit).Offset(offset)
}
err = db.Find(&sysParamss).Error
return sysParamss, total, err
}
// GetSysParam 根据key获取参数value
// Author [Mr.奇淼](https://github.com/pixelmaxQm)
func (sysParamsService *SysParamsService) GetSysParam(key string) (param system.SysParams, err error) {
err = global.GVA_DB.Where(system.SysParams{Key: key}).First(&param).Error
return
}

View File

@@ -0,0 +1,62 @@
package system
import (
"git.echol.cn/loser/xiecheng_server/config"
"git.echol.cn/loser/xiecheng_server/global"
"git.echol.cn/loser/xiecheng_server/model/system"
"git.echol.cn/loser/xiecheng_server/utils"
"go.uber.org/zap"
)
//@author: [piexlmax](https://github.com/piexlmax)
//@function: GetSystemConfig
//@description: 读取配置文件
//@return: conf config.Server, err error
type SystemConfigService struct{}
var SystemConfigServiceApp = new(SystemConfigService)
func (systemConfigService *SystemConfigService) GetSystemConfig() (conf config.Server, err error) {
return global.GVA_CONFIG, nil
}
// @description set system config,
//@author: [piexlmax](https://github.com/piexlmax)
//@function: SetSystemConfig
//@description: 设置配置文件
//@param: system model.System
//@return: err error
func (systemConfigService *SystemConfigService) SetSystemConfig(system system.System) (err error) {
cs := utils.StructToMap(system.Config)
for k, v := range cs {
global.GVA_VP.Set(k, v)
}
err = global.GVA_VP.WriteConfig()
return err
}
//@author: [SliverHorn](https://github.com/SliverHorn)
//@function: GetServerInfo
//@description: 获取服务器信息
//@return: server *utils.Server, err error
func (systemConfigService *SystemConfigService) GetServerInfo() (server *utils.Server, err error) {
var s utils.Server
s.Os = utils.InitOS()
if s.Cpu, err = utils.InitCPU(); err != nil {
global.GVA_LOG.Error("func utils.InitCPU() Failed", zap.String("err", err.Error()))
return &s, err
}
if s.Ram, err = utils.InitRAM(); err != nil {
global.GVA_LOG.Error("func utils.InitRAM() Failed", zap.String("err", err.Error()))
return &s, err
}
if s.Disk, err = utils.InitDisk(); err != nil {
global.GVA_LOG.Error("func utils.InitDisk() Failed", zap.String("err", err.Error()))
return &s, err
}
return &s, nil
}

317
service/system/sys_user.go Normal file
View File

@@ -0,0 +1,317 @@
package system
import (
"errors"
"fmt"
"git.echol.cn/loser/xiecheng_server/model/common"
systemReq "git.echol.cn/loser/xiecheng_server/model/system/request"
"time"
"git.echol.cn/loser/xiecheng_server/global"
"git.echol.cn/loser/xiecheng_server/model/system"
"git.echol.cn/loser/xiecheng_server/utils"
"github.com/google/uuid"
"gorm.io/gorm"
)
//@author: [piexlmax](https://github.com/piexlmax)
//@function: Register
//@description: 用户注册
//@param: u model.SysUser
//@return: userInter system.SysUser, err error
type UserService struct{}
var UserServiceApp = new(UserService)
func (userService *UserService) Register(u system.SysUser) (userInter system.SysUser, err error) {
var user system.SysUser
if !errors.Is(global.GVA_DB.Where("username = ?", u.Username).First(&user).Error, gorm.ErrRecordNotFound) { // 判断用户名是否注册
return userInter, errors.New("用户名已注册")
}
// 否则 附加uuid 密码hash加密 注册
u.Password = utils.BcryptHash(u.Password)
u.UUID = uuid.New()
err = global.GVA_DB.Create(&u).Error
return u, err
}
//@author: [piexlmax](https://github.com/piexlmax)
//@author: [SliverHorn](https://github.com/SliverHorn)
//@function: Login
//@description: 用户登录
//@param: u *model.SysUser
//@return: err error, userInter *model.SysUser
func (userService *UserService) Login(u *system.SysUser) (userInter *system.SysUser, err error) {
if nil == global.GVA_DB {
return nil, fmt.Errorf("db not init")
}
var user system.SysUser
err = global.GVA_DB.Where("username = ?", u.Username).Preload("Authorities").Preload("Authority").First(&user).Error
if err == nil {
if ok := utils.BcryptCheck(u.Password, user.Password); !ok {
return nil, errors.New("密码错误")
}
MenuServiceApp.UserAuthorityDefaultRouter(&user)
}
return &user, err
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: ChangePassword
//@description: 修改用户密码
//@param: u *model.SysUser, newPassword string
//@return: userInter *model.SysUser,err error
func (userService *UserService) ChangePassword(u *system.SysUser, newPassword string) (userInter *system.SysUser, err error) {
var user system.SysUser
if err = global.GVA_DB.Where("id = ?", u.ID).First(&user).Error; err != nil {
return nil, err
}
if ok := utils.BcryptCheck(u.Password, user.Password); !ok {
return nil, errors.New("原密码错误")
}
user.Password = utils.BcryptHash(newPassword)
err = global.GVA_DB.Save(&user).Error
return &user, err
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: GetUserInfoList
//@description: 分页获取数据
//@param: info request.PageInfo
//@return: err error, list interface{}, total int64
func (userService *UserService) GetUserInfoList(info systemReq.GetUserList) (list interface{}, total int64, err error) {
limit := info.PageSize
offset := info.PageSize * (info.Page - 1)
db := global.GVA_DB.Model(&system.SysUser{})
var userList []system.SysUser
if info.NickName != "" {
db = db.Where("nick_name LIKE ?", "%"+info.NickName+"%")
}
if info.Phone != "" {
db = db.Where("phone LIKE ?", "%"+info.Phone+"%")
}
if info.Username != "" {
db = db.Where("username LIKE ?", "%"+info.Username+"%")
}
if info.Email != "" {
db = db.Where("email LIKE ?", "%"+info.Email+"%")
}
err = db.Count(&total).Error
if err != nil {
return
}
err = db.Limit(limit).Offset(offset).Preload("Authorities").Preload("Authority").Find(&userList).Error
return userList, total, err
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: SetUserAuthority
//@description: 设置一个用户的权限
//@param: uuid uuid.UUID, authorityId string
//@return: err error
func (userService *UserService) SetUserAuthority(id uint, authorityId uint) (err error) {
assignErr := global.GVA_DB.Where("sys_user_id = ? AND sys_authority_authority_id = ?", id, authorityId).First(&system.SysUserAuthority{}).Error
if errors.Is(assignErr, gorm.ErrRecordNotFound) {
return errors.New("该用户无此角色")
}
var authority system.SysAuthority
err = global.GVA_DB.Where("authority_id = ?", authorityId).First(&authority).Error
if err != nil {
return err
}
var authorityMenu []system.SysAuthorityMenu
var authorityMenuIDs []string
err = global.GVA_DB.Where("sys_authority_authority_id = ?", authorityId).Find(&authorityMenu).Error
if err != nil {
return err
}
for i := range authorityMenu {
authorityMenuIDs = append(authorityMenuIDs, authorityMenu[i].MenuId)
}
var authorityMenus []system.SysBaseMenu
err = global.GVA_DB.Preload("Parameters").Where("id in (?)", authorityMenuIDs).Find(&authorityMenus).Error
if err != nil {
return err
}
hasMenu := false
for i := range authorityMenus {
if authorityMenus[i].Name == authority.DefaultRouter {
hasMenu = true
break
}
}
if !hasMenu {
return errors.New("找不到默认路由,无法切换本角色")
}
err = global.GVA_DB.Model(&system.SysUser{}).Where("id = ?", id).Update("authority_id", authorityId).Error
return err
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: SetUserAuthorities
//@description: 设置一个用户的权限
//@param: id uint, authorityIds []string
//@return: err error
func (userService *UserService) SetUserAuthorities(adminAuthorityID, id uint, authorityIds []uint) (err error) {
return global.GVA_DB.Transaction(func(tx *gorm.DB) error {
var user system.SysUser
TxErr := tx.Where("id = ?", id).First(&user).Error
if TxErr != nil {
global.GVA_LOG.Debug(TxErr.Error())
return errors.New("查询用户数据失败")
}
TxErr = tx.Delete(&[]system.SysUserAuthority{}, "sys_user_id = ?", id).Error
if TxErr != nil {
return TxErr
}
var useAuthority []system.SysUserAuthority
for _, v := range authorityIds {
e := AuthorityServiceApp.CheckAuthorityIDAuth(adminAuthorityID, v)
if e != nil {
return e
}
useAuthority = append(useAuthority, system.SysUserAuthority{
SysUserId: id, SysAuthorityAuthorityId: v,
})
}
TxErr = tx.Create(&useAuthority).Error
if TxErr != nil {
return TxErr
}
TxErr = tx.Model(&user).Update("authority_id", authorityIds[0]).Error
if TxErr != nil {
return TxErr
}
// 返回 nil 提交事务
return nil
})
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: DeleteUser
//@description: 删除用户
//@param: id float64
//@return: err error
func (userService *UserService) DeleteUser(id int) (err error) {
return global.GVA_DB.Transaction(func(tx *gorm.DB) error {
if err := tx.Where("id = ?", id).Delete(&system.SysUser{}).Error; err != nil {
return err
}
if err := tx.Delete(&[]system.SysUserAuthority{}, "sys_user_id = ?", id).Error; err != nil {
return err
}
return nil
})
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: SetUserInfo
//@description: 设置用户信息
//@param: reqUser model.SysUser
//@return: err error, user model.SysUser
func (userService *UserService) SetUserInfo(req system.SysUser) error {
return global.GVA_DB.Model(&system.SysUser{}).
Select("updated_at", "nick_name", "header_img", "phone", "email", "enable").
Where("id=?", req.ID).
Updates(map[string]interface{}{
"updated_at": time.Now(),
"nick_name": req.NickName,
"header_img": req.HeaderImg,
"phone": req.Phone,
"email": req.Email,
"enable": req.Enable,
}).Error
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: SetSelfInfo
//@description: 设置用户信息
//@param: reqUser model.SysUser
//@return: err error, user model.SysUser
func (userService *UserService) SetSelfInfo(req system.SysUser) error {
return global.GVA_DB.Model(&system.SysUser{}).
Where("id=?", req.ID).
Updates(req).Error
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: SetSelfSetting
//@description: 设置用户配置
//@param: req datatypes.JSON, uid uint
//@return: err error
func (userService *UserService) SetSelfSetting(req common.JSONMap, uid uint) error {
return global.GVA_DB.Model(&system.SysUser{}).Where("id = ?", uid).Update("origin_setting", req).Error
}
//@author: [piexlmax](https://github.com/piexlmax)
//@author: [SliverHorn](https://github.com/SliverHorn)
//@function: GetUserInfo
//@description: 获取用户信息
//@param: uuid uuid.UUID
//@return: err error, user system.SysUser
func (userService *UserService) GetUserInfo(uuid uuid.UUID) (user system.SysUser, err error) {
var reqUser system.SysUser
err = global.GVA_DB.Preload("Authorities").Preload("Authority").First(&reqUser, "uuid = ?", uuid).Error
if err != nil {
return reqUser, err
}
MenuServiceApp.UserAuthorityDefaultRouter(&reqUser)
return reqUser, err
}
//@author: [SliverHorn](https://github.com/SliverHorn)
//@function: FindUserById
//@description: 通过id获取用户信息
//@param: id int
//@return: err error, user *model.SysUser
func (userService *UserService) FindUserById(id int) (user *system.SysUser, err error) {
var u system.SysUser
err = global.GVA_DB.Where("id = ?", id).First(&u).Error
return &u, err
}
//@author: [SliverHorn](https://github.com/SliverHorn)
//@function: FindUserByUuid
//@description: 通过uuid获取用户信息
//@param: uuid string
//@return: err error, user *model.SysUser
func (userService *UserService) FindUserByUuid(uuid string) (user *system.SysUser, err error) {
var u system.SysUser
if err = global.GVA_DB.Where("uuid = ?", uuid).First(&u).Error; err != nil {
return &u, errors.New("用户不存在")
}
return &u, nil
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: ResetPassword
//@description: 修改用户密码
//@param: ID uint
//@return: err error
func (userService *UserService) ResetPassword(ID uint) (err error) {
err = global.GVA_DB.Model(&system.SysUser{}).Where("id = ?", ID).Update("password", utils.BcryptHash("123456")).Error
return err
}