🎉 更新系统版本
This commit is contained in:
@@ -331,3 +331,82 @@ func (authorityService *AuthorityService) GetParentAuthorityID(authorityID uint)
|
||||
}
|
||||
return *authority.ParentId, nil
|
||||
}
|
||||
|
||||
// GetUserIdsByAuthorityId 获取拥有指定角色的所有用户ID
|
||||
func (authorityService *AuthorityService) GetUserIdsByAuthorityId(authorityId uint) (userIds []uint, err error) {
|
||||
var records []system.SysUserAuthority
|
||||
err = global.GVA_DB.Where("sys_authority_authority_id = ?", authorityId).Find(&records).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, r := range records {
|
||||
userIds = append(userIds, r.SysUserId)
|
||||
}
|
||||
return userIds, nil
|
||||
}
|
||||
|
||||
// SetRoleUsers 全量覆盖某角色关联的用户列表
|
||||
// 入参:角色ID + 目标用户ID列表,保存时将该角色的关联关系完全替换为传入列表
|
||||
func (authorityService *AuthorityService) SetRoleUsers(authorityId uint, userIds []uint) error {
|
||||
return global.GVA_DB.Transaction(func(tx *gorm.DB) error {
|
||||
// 1. 查出当前拥有该角色的所有用户ID
|
||||
var existingRecords []system.SysUserAuthority
|
||||
if err := tx.Where("sys_authority_authority_id = ?", authorityId).Find(&existingRecords).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
currentSet := make(map[uint]struct{})
|
||||
for _, r := range existingRecords {
|
||||
currentSet[r.SysUserId] = struct{}{}
|
||||
}
|
||||
|
||||
targetSet := make(map[uint]struct{})
|
||||
for _, id := range userIds {
|
||||
targetSet[id] = struct{}{}
|
||||
}
|
||||
|
||||
// 2. 删除该角色所有已有的用户关联
|
||||
if err := tx.Delete(&system.SysUserAuthority{}, "sys_authority_authority_id = ?", authorityId).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 3. 对被移除的用户:若该角色是其主角色,则将主角色切换为其剩余的其他角色
|
||||
for userId := range currentSet {
|
||||
if _, ok := targetSet[userId]; ok {
|
||||
continue // 仍在目标列表中,不处理
|
||||
}
|
||||
var user system.SysUser
|
||||
if err := tx.First(&user, "id = ?", userId).Error; err != nil {
|
||||
continue
|
||||
}
|
||||
if user.AuthorityId == authorityId {
|
||||
// 从剩余关联(已删除当前角色后)中找另一个角色作为主角色
|
||||
var another system.SysUserAuthority
|
||||
if err := tx.Where("sys_user_id = ?", userId).First(&another).Error; err != nil {
|
||||
// 没有其他角色,主角色保持不变,不做处理
|
||||
continue
|
||||
}
|
||||
if err := tx.Model(&system.SysUser{}).Where("id = ?", userId).
|
||||
Update("authority_id", another.SysAuthorityAuthorityId).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 批量插入新的关联记录
|
||||
if len(userIds) > 0 {
|
||||
newRecords := make([]system.SysUserAuthority, 0, len(userIds))
|
||||
for _, userId := range userIds {
|
||||
newRecords = append(newRecords, system.SysUserAuthority{
|
||||
SysUserId: userId,
|
||||
SysAuthorityAuthorityId: authorityId,
|
||||
})
|
||||
}
|
||||
if err := tx.Create(&newRecords).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -171,3 +171,45 @@ func (casbinService *CasbinService) FreshCasbin() (err error) {
|
||||
err = e.LoadPolicy()
|
||||
return err
|
||||
}
|
||||
|
||||
// GetAuthoritiesByApi 获取拥有指定API权限的所有角色ID
|
||||
func (casbinService *CasbinService) GetAuthoritiesByApi(path, method string) (authorityIds []uint, err error) {
|
||||
var rules []gormadapter.CasbinRule
|
||||
err = global.GVA_DB.Where("ptype = 'p' AND v1 = ? AND v2 = ?", path, method).Find(&rules).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, r := range rules {
|
||||
id, e := strconv.Atoi(r.V0)
|
||||
if e == nil {
|
||||
authorityIds = append(authorityIds, uint(id))
|
||||
}
|
||||
}
|
||||
return authorityIds, nil
|
||||
}
|
||||
|
||||
// SetApiAuthorities 全量覆盖某API关联的角色列表
|
||||
func (casbinService *CasbinService) SetApiAuthorities(path, method string, authorityIds []uint) error {
|
||||
return global.GVA_DB.Transaction(func(tx *gorm.DB) error {
|
||||
// 1. 删除该API所有已有的角色关联
|
||||
if err := tx.Where("ptype = 'p' AND v1 = ? AND v2 = ?", path, method).Delete(&gormadapter.CasbinRule{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
// 2. 批量插入新的关联记录
|
||||
if len(authorityIds) > 0 {
|
||||
newRules := make([]gormadapter.CasbinRule, 0, len(authorityIds))
|
||||
for _, authorityId := range authorityIds {
|
||||
newRules = append(newRules, gormadapter.CasbinRule{
|
||||
Ptype: "p",
|
||||
V0: strconv.Itoa(int(authorityId)),
|
||||
V1: path,
|
||||
V2: method,
|
||||
})
|
||||
}
|
||||
if err := tx.Create(&newRules).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -315,6 +315,65 @@ func (menuService *MenuService) GetMenuAuthority(info *request.GetAuthorityId) (
|
||||
return menus, err
|
||||
}
|
||||
|
||||
// GetAuthoritiesByMenuId 获取拥有指定菜单的所有角色ID
|
||||
func (menuService *MenuService) GetAuthoritiesByMenuId(menuId uint) (authorityIds []uint, err error) {
|
||||
var records []system.SysAuthorityMenu
|
||||
err = global.GVA_DB.Where("sys_base_menu_id = ?", menuId).Find(&records).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, r := range records {
|
||||
id, e := strconv.Atoi(r.AuthorityId)
|
||||
if e == nil {
|
||||
authorityIds = append(authorityIds, uint(id))
|
||||
}
|
||||
}
|
||||
return authorityIds, nil
|
||||
}
|
||||
|
||||
// GetDefaultRouterAuthorityIds 获取将指定菜单设为首页的角色ID列表
|
||||
func (menuService *MenuService) GetDefaultRouterAuthorityIds(menuId uint) (authorityIds []uint, err error) {
|
||||
var menu system.SysBaseMenu
|
||||
err = global.GVA_DB.First(&menu, menuId).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var authorities []system.SysAuthority
|
||||
err = global.GVA_DB.Where("default_router = ?", menu.Name).Find(&authorities).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, auth := range authorities {
|
||||
authorityIds = append(authorityIds, auth.AuthorityId)
|
||||
}
|
||||
return authorityIds, nil
|
||||
}
|
||||
|
||||
// SetMenuAuthorities 全量覆盖某菜单关联的角色列表
|
||||
func (menuService *MenuService) SetMenuAuthorities(menuId uint, authorityIds []uint) error {
|
||||
return global.GVA_DB.Transaction(func(tx *gorm.DB) error {
|
||||
// 1. 删除该菜单所有已有的角色关联
|
||||
if err := tx.Where("sys_base_menu_id = ?", menuId).Delete(&system.SysAuthorityMenu{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
// 2. 批量插入新的关联记录
|
||||
if len(authorityIds) > 0 {
|
||||
menuIdStr := strconv.Itoa(int(menuId))
|
||||
newRecords := make([]system.SysAuthorityMenu, 0, len(authorityIds))
|
||||
for _, authorityId := range authorityIds {
|
||||
newRecords = append(newRecords, system.SysAuthorityMenu{
|
||||
MenuId: menuIdStr,
|
||||
AuthorityId: strconv.Itoa(int(authorityId)),
|
||||
})
|
||||
}
|
||||
if err := tx.Create(&newRecords).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// UserAuthorityDefaultRouter 用户角色默认路由检查
|
||||
//
|
||||
// Author [SliverHorn](https://github.com/SliverHorn)
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
@@ -158,6 +163,107 @@ func (s *SkillsService) Save(_ context.Context, req request.SkillSaveRequest) er
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SkillsService) Delete(_ context.Context, req request.SkillDeleteRequest) error {
|
||||
if strings.TrimSpace(req.Tool) == "" {
|
||||
return errors.New("工具类型不能为空")
|
||||
}
|
||||
if !isSafeName(req.Skill) {
|
||||
return errors.New("技能名称不合法")
|
||||
}
|
||||
skillDir, err := s.skillDir(req.Tool, req.Skill)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
info, err := os.Stat(skillDir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return errors.New("技能不存在")
|
||||
}
|
||||
return err
|
||||
}
|
||||
if !info.IsDir() {
|
||||
return errors.New("技能目录异常")
|
||||
}
|
||||
return os.RemoveAll(skillDir)
|
||||
}
|
||||
|
||||
func (s *SkillsService) Package(_ context.Context, req request.SkillPackageRequest) (string, []byte, error) {
|
||||
if strings.TrimSpace(req.Tool) == "" {
|
||||
return "", nil, errors.New("工具类型不能为空")
|
||||
}
|
||||
if !isSafeName(req.Skill) {
|
||||
return "", nil, errors.New("技能名称不合法")
|
||||
}
|
||||
|
||||
skillDir, err := s.skillDir(req.Tool, req.Skill)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
info, err := os.Stat(skillDir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return "", nil, errors.New("技能不存在")
|
||||
}
|
||||
return "", nil, err
|
||||
}
|
||||
if !info.IsDir() {
|
||||
return "", nil, errors.New("技能目录异常")
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
zw := zip.NewWriter(buf)
|
||||
|
||||
walkErr := filepath.WalkDir(skillDir, func(path string, d fs.DirEntry, walkErr error) error {
|
||||
if walkErr != nil {
|
||||
return walkErr
|
||||
}
|
||||
rel, err := filepath.Rel(skillDir, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rel == "." {
|
||||
return nil
|
||||
}
|
||||
zipName := filepath.ToSlash(rel)
|
||||
if d.IsDir() {
|
||||
_, err = zw.Create(strings.TrimSuffix(zipName, "/") + "/")
|
||||
return err
|
||||
}
|
||||
|
||||
fileInfo, err := d.Info()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
header, err := zip.FileInfoHeader(fileInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
header.Name = zipName
|
||||
header.Method = zip.Deflate
|
||||
|
||||
writer, err := zw.CreateHeader(header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
content, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = writer.Write(content)
|
||||
return err
|
||||
})
|
||||
if walkErr != nil {
|
||||
_ = zw.Close()
|
||||
return "", nil, walkErr
|
||||
}
|
||||
|
||||
if err = zw.Close(); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
return req.Skill + ".zip", buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (s *SkillsService) CreateScript(_ context.Context, req request.SkillScriptCreateRequest) (string, string, error) {
|
||||
if !isSafeName(req.Skill) {
|
||||
return "", "", errors.New("技能名称不合法")
|
||||
@@ -279,6 +385,136 @@ func (s *SkillsService) SaveGlobalConstraint(_ context.Context, req request.Skil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SkillsService) DownloadOnlineSkill(_ context.Context, req request.DownloadOnlineSkillReq) error {
|
||||
skillsDir, err := s.toolSkillsDir(req.Tool)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body, err := json.Marshal(map[string]interface{}{
|
||||
"plugin_id": req.ID,
|
||||
"version": req.Version,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("构建下载请求失败: %w", err)
|
||||
}
|
||||
|
||||
downloadReq, err := http.NewRequest(http.MethodPost, "https://plugin.gin-vue-admin.com/api/shopPlugin/downloadSkill", bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return fmt.Errorf("构建下载请求失败: %w", err)
|
||||
}
|
||||
downloadReq.Header.Set("Content-Type", "application/json")
|
||||
|
||||
downloadResp, err := http.DefaultClient.Do(downloadReq)
|
||||
if err != nil {
|
||||
return fmt.Errorf("下载技能失败: %w", err)
|
||||
}
|
||||
defer downloadResp.Body.Close()
|
||||
|
||||
if downloadResp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("下载技能失败, HTTP状态码: %d", downloadResp.StatusCode)
|
||||
}
|
||||
|
||||
metaBody, err := io.ReadAll(downloadResp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("读取下载结果失败: %w", err)
|
||||
}
|
||||
|
||||
var meta struct {
|
||||
Data struct {
|
||||
URL string `json:"url"`
|
||||
} `json:"data"`
|
||||
}
|
||||
if err = json.Unmarshal(metaBody, &meta); err != nil {
|
||||
return fmt.Errorf("解析下载结果失败: %w", err)
|
||||
}
|
||||
|
||||
realDownloadURL := strings.TrimSpace(meta.Data.URL)
|
||||
if realDownloadURL == "" {
|
||||
return errors.New("下载结果缺少 url")
|
||||
}
|
||||
|
||||
zipResp, err := http.Get(realDownloadURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("下载压缩包失败: %w", err)
|
||||
}
|
||||
defer zipResp.Body.Close()
|
||||
|
||||
if zipResp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("下载压缩包失败, HTTP状态码: %d", zipResp.StatusCode)
|
||||
}
|
||||
|
||||
tmpFile, err := os.CreateTemp("", "gva-skill-*.zip")
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建临时文件失败: %w", err)
|
||||
}
|
||||
tmpPath := tmpFile.Name()
|
||||
defer os.Remove(tmpPath)
|
||||
|
||||
if _, err = io.Copy(tmpFile, zipResp.Body); err != nil {
|
||||
tmpFile.Close()
|
||||
return fmt.Errorf("保存技能包失败: %w", err)
|
||||
}
|
||||
tmpFile.Close()
|
||||
|
||||
if err = extractZipToDir(tmpPath, skillsDir); err != nil {
|
||||
return fmt.Errorf("解压技能包失败: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func extractZipToDir(zipPath, destDir string) error {
|
||||
r, err := zip.OpenReader(zipPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
for _, f := range r.File {
|
||||
name := filepath.FromSlash(f.Name)
|
||||
if strings.Contains(name, "..") {
|
||||
continue
|
||||
}
|
||||
|
||||
target := filepath.Join(destDir, name)
|
||||
if !strings.HasPrefix(filepath.Clean(target), filepath.Clean(destDir)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if f.FileInfo().IsDir() {
|
||||
if err := os.MkdirAll(target, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(target), os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rc, err := f.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out, err := os.OpenFile(target, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
|
||||
if err != nil {
|
||||
rc.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = io.Copy(out, rc)
|
||||
rc.Close()
|
||||
out.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SkillsService) toolSkillsDir(tool string) (string, error) {
|
||||
toolDir, ok := skillToolDirs[tool]
|
||||
if !ok {
|
||||
|
||||
@@ -109,7 +109,25 @@ func (userService *UserService) GetUserInfoList(info systemReq.GetUserList) (lis
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = db.Limit(limit).Offset(offset).Preload("Authorities").Preload("Authority").Find(&userList).Error
|
||||
|
||||
orderStr := "id desc"
|
||||
if info.OrderKey != "" {
|
||||
allowedOrders := map[string]bool{
|
||||
"id": true,
|
||||
"username": true,
|
||||
"nick_name": true,
|
||||
"phone": true,
|
||||
"email": true,
|
||||
}
|
||||
if allowedOrders[info.OrderKey] {
|
||||
orderStr = info.OrderKey
|
||||
if info.Desc {
|
||||
orderStr = info.OrderKey + " desc"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = db.Limit(limit).Offset(offset).Order(orderStr).Preload("Authorities").Preload("Authority").Find(&userList).Error
|
||||
return userList, total, err
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user