【Go】基于GoFiber从零开始搭建一个GoWeb后台管理系统(四)用户管理、部门管理模块

news2024/11/25 10:57:40

第一篇:【Go】基于GoFiber从零开始搭建一个GoWeb后台管理系统(一)搭建项目

第二篇:【Go】基于GoFiber从零开始搭建一个GoWeb后台管理系统(二)日志输出中间件、校验token中间件、配置路由、基础工具函数。

第三篇:【Go】基于GoFiber从零开始搭建一个GoWeb后台管理系统(三)日志管理(登录日志、操作日志)、用户登录模块

碎碎念:最近脖子一直好痛啊,已经持续有一段时间了,从早痛到晚,早上起床就痛,到晚上睡觉都还在痛,导致我经常睡不着觉。之前也会脖子痛,但都是痛一两天就好了。真的坐着痛、躺着痛,普通枕头痛、U型枕痛、不枕枕头也痛。。。大家有什么办法可以缓解一下吗?是要去按摩?

接收参数

gofiber中,接收前端传过来的参数,有以下几种传参方式及接收方式:

  • 用问号拼接在地址后面的参数,我们一般用 c.Query("参数名") 获取;
  • 路径参数(直接拼接在地址后面,比如:/sys/user/getById/1,这里的 1 就是路径参数),用 c.Params("参数名")
  • FormData参数,用 c.FormValue("参数名") 接收;
  • FormData传的文件参数,用 c.MultipartForm() 获取;
  • JSON参数,用 c.BodyParser(结构体指针) 接收,就是将json转为具体的某个结构体。

数据库操作

  • 新增:用 Create() ,新增单条数据和批量新增都是用这个方法:db.Create(&user)、db.Create(&list)
  • 修改:
    • Update() 、UpdateColumn() :更新指定的列,每次只更新一个字段。
    • Updates() 、UpdateColumns():更新整个结构体或map,但是注意这两个方法不会更新零值。
    • Save() :插入或更新,传结构体或map,可以插入、更新零值。根据主键进行更新,如果主键不存在则插入。
  • 删除:Delete() 默认根据主键删除或批量删除,也可以在 Delete() 前面使用 Where() ,指定条件删除。
  • 查询:可以使用 Find() 查询单条数据或多条数据。

Update() 、UpdateColumn() 和 Updates() 、UpdateColumns() 其实是有一点区别的,具体我们可以看看源码:

在这里插入图片描述

tx.Statement.SkipHooks = true 这句代码是关于事务(transaction)的设置。当这个设置被设置为 true 时,GORM 将跳过与该事务相关的所有钩子。

然后更新时,可以使用 Select() 指定要更新的字段,更新或新增时,可以使用 Omit() 忽略指定字段。

具体怎么操作数据库,我们直接看下面的代码。

用户管理

首先是用户管理模块,有这几个接口:

在这里插入图片描述

废话不多说,我们直接上代码

controller层:sys_user.go

package sys

import (
	"fmt"
	"github.com/gofiber/fiber/v2"
	"go-web2/app/common/config"
	"go-web2/app/common/util"
	"go-web2/app/model/sys"
	"path/filepath"
	"strings"
	"time"
)

type UserController struct{}

// 获取当前登录的用户
func (UserController) GetLoginUser(c *fiber.Ctx) error {
	// 获取请求中的 Token
	token := c.Get(config.TokenHeader)
	user := sys.GetLoginUser(token)
	result := map[string]any{}
	result["user"] = user
	result["roles"] = user.RoleId
	result["permissions"] = sys.GetPermsMenuByRoleId(user.RoleId)
	return c.Status(200).JSON(config.Success(result))
}

// 用户列表
func (UserController) GetPage(c *fiber.Ctx) error {
	user := sys.SysUserView{}
	user.UserName = c.Query("code")
	user.RealName = c.Query("name")
	user.AncestorId = c.Query("parentId")
	user.Token = c.Get(config.TokenHeader)
	pageSize := c.QueryInt("pageSize", 10)
	pageNum := c.QueryInt("pageNum", 1)
	return c.Status(200).JSON(config.Success(user.GetPage(pageSize, pageNum)))
}

// 根据id获取用户
func (UserController) GetById(c *fiber.Ctx) error {
	user := sys.SysUser{}
	user.Id = c.Params("id")
	user.Token = c.Get(config.TokenHeader)
	err := user.GetUser()
	if err != nil {
		return c.Status(200).JSON(config.Error("获取用户失败,或没有查看权限"))
	}
	return c.Status(200).JSON(config.Success(user))
}

// 新增用户
func (UserController) Insert(c *fiber.Ctx) error {
	var user sys.SysUser
	if err := c.BodyParser(&user); err != nil {
		return c.Status(200).JSON(config.Error(err.Error()))
	}
	pwd, err := util.GetEncryptedPassword(user.Password)
	if err != nil {
		return c.Status(200).JSON(config.Error("密码加密失败"))
	}
	user.Password = pwd
	user.Token = c.Get(config.TokenHeader)
	err = user.Insert()
	if err != nil {
		return c.Status(200).JSON(config.Error(err.Error()))
	}
	return c.Status(200).JSON(config.Success(nil))
}

// 修改用户
func (UserController) Update(c *fiber.Ctx) error {
	var user sys.SysUser
	if err := c.BodyParser(&user); err != nil {
		return c.Status(200).JSON(config.Error(err.Error()))
	}
	user.Token = c.Get(config.TokenHeader)
	err := user.Update()
	if err != nil {
		return c.Status(200).JSON(config.Error(err.Error()))
	}
	return c.Status(200).JSON(config.Success(nil))
}

// 删除用户
func (UserController) Delete(c *fiber.Ctx) error {
	var user sys.SysUser
	if err := c.BodyParser(&user); err != nil {
		return c.Status(200).JSON(config.Error(err.Error()))
	}
	user.Token = c.Get(config.TokenHeader)
	ids := strings.Split(user.Id, ",")
	if err := user.Delete(ids); err != nil {
		return c.Status(200).JSON(config.Error("删除用户失败"))
	}
	return c.Status(200).JSON(config.Success(nil))
}

// 修改密码
func (UserController) UpdatePassword(c *fiber.Ctx) error {
	// 正常是需要加解密的(没有前端就暂时不先加密解密)
	//newPassword := util.RSADecrypt(c.FormValue("newPassword"))
	//oldPassword := util.RSADecrypt(c.FormValue("oldPassword"))
	//id := util.RSADecrypt(c.FormValue("id"))
	newPassword := c.FormValue("newPassword")
	oldPassword := c.FormValue("oldPassword")
	id := c.FormValue("id")
	var password sys.Password
	password.Id = id
	password.OldPassword = oldPassword
	password.NewPassword = newPassword
	password.Token = c.Get(config.TokenHeader)
	err := password.UpdatePassword()
	if err != nil {
		return c.Status(200).JSON(config.Error(err.Error()))
	}
	return c.Status(200).JSON(config.Success(nil))
}

// 重置密码
func (UserController) ResetPassword(c *fiber.Ctx) error {
	var user sys.SysUser
	user.Id = c.FormValue("id")
	if user.Id == "" {
		return c.Status(200).JSON(config.Error("用户id不可为空"))
	}
	user.Token = c.Get(config.TokenHeader)
	err := user.ResetPassword()
	if err != nil {
		return c.Status(200).JSON(config.Error(err.Error()))
	}
	return c.Status(200).JSON(config.Success(nil))
}

// 上传头像
func (UserController) Upload(c *fiber.Ctx) error {
	form, err := c.MultipartForm()
	if err != nil {
		fmt.Println(err)
		return c.Status(200).JSON(config.Error("头像上传失败"))
	}
	// 获取新的文件名
	name := form.File["file"][0].Filename                           // 获取文件名
	suffix := name[strings.LastIndex(name, "."):]                   // 文件后缀:.jpg
	fileName := fmt.Sprintf("%d%s", time.Now().UnixMilli(), suffix) // 新文件名
	// 相对路径:/upload/20231208
	relative := filepath.Join(config.FilePath, time.Now().Format("20060102"))
	// 保存文件
	err = util.SaveFile(form, relative, fileName)
	if err != nil {
		return c.Status(200).JSON(config.Error(err.Error()))
	}
	var user sys.SysUser
	user.Token = c.Get(config.TokenHeader)
	relative = filepath.Join(relative, fileName)
	user.Picture = &relative // 数据库保存相对路径
	user.Upload()            // 保存头像到数据库
	return c.Status(200).JSON(config.Success(relative))
}

上面这几个接口,就用到了 c.Query()c.Params()c.FormValue()c.MultipartForm()c.BodyParser() 这几种方式。

model层:sys_user.go

接口有了,那接下来就是获取数据了,数据从数据库来的,所以下面就是来操作数据库了。

package sys

import (
	"errors"
	"fmt"
	"github.com/google/uuid"
	"go-web2/app/common/config"
	"go-web2/app/common/util"
	"strings"
	"time"
)

// 用户信息model,对应数据库的 sys_user 表
type SysUser struct {
	config.BaseModel // 嵌套公共的model,这样就可以使用 BaseModel 的字段了
	SysUserView
	Password string `gorm:"password" json:"password" form:"password"` // 加密密码
}

// 用户信息model,用于展示给前端
type SysUserView struct {
	config.BaseModel         // 嵌套公共的model,这样就可以使用 BaseModel 的字段了
	UserName         string  `json:"userName" form:"userName"`         // 用户名称
	RealName         string  `json:"realName" form:"realName"`         // 真实姓名
	DeptId           string  `json:"deptId" form:"deptId"`             // 部门id
	DeptName         string  `json:"deptName" form:"deptName"`         // 部门名称
	AncestorId       string  `json:"ancestorId" form:"ancestorId"`     // 祖级id
	AncestorName     string  `json:"ancestorName" form:"ancestorName"` // 祖级名称
	ChildId          string  `json:"childId" form:"childId"`           // 部门id及子级id
	ChildName        string  `json:"childName" form:"childName"`       // 部门名称及子级名称
	RoleId           string  `json:"roleId" form:"roleId"`             // 角色id
	RoleKey          string  `json:"roleKey" form:"roleKey"`           // 角色代码
	RoleName         string  `json:"roleName" form:"roleName"`         // 角色名称
	Phone            *string `json:"phone" form:"phone"`               // 联系电话 这里用指针,是因为可以传空(这个空不是指空字符串,而是null)
	State            int     `json:"state" form:"state"`               // 状态(1 启用 2 停用)
	Picture          *string `json:"picture" form:"picture"`           // 头像地址
}

// 密码结构体,用于修改密码
type Password struct {
	config.BaseModel        // 嵌套公共的model,这样就可以使用 BaseModel 的字段了
	OldPassword      string `json:"oldPassword" form:"oldPassword"` // 旧密码
	NewPassword      string `json:"newPassword" form:"newPassword"` // 新密码
}

// 新增、更新用户信息时,要忽略的字段
var omit = "dept_name,ancestor_id,ancestor_name,child_id,child_name,role_key,role_name"

// 获取用户管理的表名
func (SysUserView) TableName() string {
	return "sys_user"
}

// 列表
func (e *SysUserView) GetPage(pageSize int, pageNum int) config.PageInfo {
	var list []SysUserView // 查询结果
	var total int64        // 总数
	query := config.DB.Table(e.TableName())
	if e.UserName != "" {
		query.Where("user_name like ?", fmt.Sprintf("%%%s%%", e.UserName))
	}
	if e.RealName != "" {
		query.Where("real_name like ?", fmt.Sprintf("%%%s%%", e.RealName))
	}
	if e.AncestorId != "" {
		ids, _ := GetDeptChild(e.AncestorId) // 获取当前 parentId 的所有子节点包括它本身
		query.Where("FIND_IN_SET(dept_id,?)", ids)
	}
	// 数据过滤
	scope := AppendQueryDataScope(e.Token, "dept_id", "2", false, true)
	if scope != "" {
		query.Where(scope)
	}
	// 关联部门和角色表查询
	offset := (pageNum - 1) * pageSize // 计算跳过的记录数
	query.Debug().Order("sys_user.create_time desc").
		Select("sys_user.*, b.name as dept_name,role_key,role_name").
		Joins("left join sys_dept b on b.id = sys_user.dept_id").
		Joins("left join sys_role c on c.id = sys_user.role_id").
		Offset(offset).Limit(pageSize).Find(&list) // 分页查询,根据offset和limit来查询
	query.Count(&total) // 查询总数用Count,注意查总数不能直接连在Find()后面,需要分开来单独一句才能正确查询到总数。
	return config.PageInfo{list, total}
}

// 详情
func (e *SysUser) GetUser() (err error) {
	query := config.DB.Table(e.TableName())
	sql := `
		SELECT a.*,b.name dept_name,role_key,role_name
		FROM sys_user a
		LEFT JOIN sys_dept b on FIND_IN_SET(b.id,a.dept_id)
		LEFT JOIN sys_role c on a.role_id=c.id
	`
	args := []interface{}{}
	if e.Id != "" {
		sql = sql + "WHERE a.id = ?"
		args = append(args, e.Id)
	}
	if e.UserName != "" {
		sql = sql + "WHERE user_name = ?"
		args = append(args, e.UserName)
	}
	// 数据过滤
	scope := AppendQueryDataScope(e.Token, "dept_id", "2", false, true)
	if scope != "" {
		sql = sql + " AND " + scope
	}
	if err = query.Raw(sql, args...).Find(e).Error; err != nil {
		return
	}
	if e.Id == "" || e.UserName == "" {
		err = errors.New("没有查看权限!")
		return
	}
	return
}

// 新增
func (e *SysUser) Insert() (err error) {
	// 校验新增的用户和当前用户是否是同一部门或子部门
	if !CheckDataScope(e.Token, e.DeptId, false, true) {
		err = errors.New("没有操作权限!")
		return
	}
	// 校验用户名和手机号码
	var count int64
	db := config.DB.Table(e.TableName())
	db.Where("user_name = ?", e.UserName).Count(&count)
	if count > 0 {
		err = errors.New("用户名称已存在!")
		return
	}
	if e.Phone != nil {
		db.Where("phone = ?", e.Phone).Count(&count)
		if count > 0 {
			err = errors.New("手机号码已存在!")
			return
		}
	}
	e.Id = strings.ReplaceAll(uuid.NewString(), "-", "")
	e.CreatorId = GetLoginId(e.Token)
	e.CreateTime = time.Now()
	config.DB.Table(e.TableName()).Omit(omit).Create(e)
	return
}

// 修改
func (e *SysUser) Update() (err error) {
	// 校验修改的用户和当前用户是否是同一部门或子部门
	if !CheckDataScope(e.Token, e.DeptId, false, true) {
		err = errors.New("没有操作权限!")
		return
	}
	var byId SysUser
	config.DB.Table(e.TableName()).Where("id = ?", e.Id).Find(&byId)
	if byId.DeptId != e.DeptId && !CheckDataScope(e.Token, byId.DeptId, false, true) {
		err = errors.New("没有操作权限!")
		return
	}
	// 校验用户名和手机号码
	var count int64
	db := config.DB.Table(e.TableName())
	db.Where("user_name = ? and id <> ?", e.UserName, e.Id).Count(&count)
	if count > 0 {
		err = errors.New("用户名称已存在!")
		return
	}
	if e.Phone != nil {
		db.Where("phone = ? and id <> ?", e.Phone, e.Id).Count(&count)
		if count > 0 {
			err = errors.New("手机号码已存在!")
			return
		}
	}
	config.DB.Table(e.TableName()).Omit(omit).Model(&SysUser{}).Where("id = ?", e.Id).Updates(e)
	return
}

// 删除
func (e *SysUser) Delete(ids []string) (err error) {
	// 先查询要删除的用户
	scope := GetDataScope(e.Token, false, true)
	if scope != "" {
		var list []SysUser
		config.DB.Table(e.TableName()).Where("id in (?)", ids).Find(&list)
		split := strings.Split(scope, ",")
		for _, user := range list {
			if !util.IsContain(split, user.DeptId) {
				err = errors.New("没有操作权限!")
				return
			}
		}
	}
	config.DB.Table(e.TableName()).Delete(&SysUser{}, ids)
	return
}

// 修改密码
func (e *Password) UpdatePassword() (err error) {
	if e.NewPassword == "" || e.OldPassword == "" || e.Id == "" {
		err = errors.New("数据解密失败")
		return
	}
	var user SysUser
	config.DB.Table(user.TableName()).Where("id = ?", e.Id).Find(&user)
	if !CheckDataScope(e.Token, user.DeptId, false, true) {
		err = errors.New("没有操作权限!")
		return
	}
	if e.NewPassword == e.OldPassword {
		err = errors.New("新密码不可于旧密码相同")
		return
	}
	if e.NewPassword == config.InitPassword {
		err = errors.New("新密码不可于初始密码相同")
		return
	}
	// 正则表达式我去你大爷!!!不校验了,我校验你大爷!!!从别的项目复制过来的正则,为什么你就死活校验不上!!
	// 同样的正则,我一模一样复制到校验工具去验证都可以匹配上,就你不行,我去你大爷,不校验了,校验你爹!!!
	/*reg := "^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[_#?!@$%^&*-]).{8,}$"
	m, _ := regexp.MatchString(reg, e.NewPassword)
	if !m {
		err = errors.New("密码长度大于7,且必须由数字、大小写字母、特殊字符组成")
		return
	}*/
	b := util.AuthenticatePassword(e.OldPassword, user.Password)
	if !b {
		err = errors.New("旧密码错误")
		return
	}
	newPassword, err := util.GetEncryptedPassword(e.NewPassword)
	if err = config.DB.Table(user.TableName()).Where("id = ?", e.Id).Update("password", newPassword).Error; err != nil {
		err = errors.New("密码修改失败")
		return
	}
	return
}

// 重置密码为初始密码
func (e *SysUser) ResetPassword() (err error) {
	var user SysUser
	config.DB.Table(e.TableName()).Where("id = ?", e.Id).Find(&user)
	if !CheckDataScope(e.Token, user.DeptId, false, true) {
		err = errors.New("没有操作权限!")
		return
	}
	password, err := util.GetEncryptedPassword(config.InitPassword)
	if err = config.DB.Table(e.TableName()).Where("id = ?", e.Id).Update("password", password).Error; err != nil {
		err = errors.New("密码重置失败")
		return
	}
	return
}

// 上传头像
func (e *SysUser) Upload() {
	id := GetLoginId(e.Token)
	config.DB.Table(e.TableName()).Where("id = ?", id).Update("picture", e.Picture)
}

// 根据部门id校验是否存在用户
func CheckDeptExistUser(deptId string) bool {
	var count int64
	query := config.DB.Table(SysUserView{}.TableName())
	query.Where("dept_id = ?", deptId).Count(&count)
	return count > 0
}

// 根据角色id校验是否存在用户
func CheckRoleExistUser(roleId string) bool {
	var count int64
	query := config.DB.Table(SysUserView{}.TableName())
	query.Where("role_id = ?", roleId).Count(&count)
	return count > 0
}

通过上面controller、model层的方法、函数,不难看出,我很多函数都绑定了结构体,controller层绑定的结构体是 UserController ,model层也绑定了对应的结构体。像这种绑定了具体的数据类型的函数我们称为方法。

在这里插入图片描述

将函数绑定数据类型,这样子就可以让函数只属于这个数据类型了。我们知道,在go中,同一个包下函数名是不可以重复的,但我们给那些重名的函数绑定不同数据类型,就可以了。这样可以省去好多命名的烦恼。

假如某个函数是专门用来处理某个结构体的业务时,那就可以给这个函数绑定对应的结构体(不绑定也可以,就是函数命名的时候需要注意)。

部门管理

接下来是部门管理模块,有以下几个接口:

在这里插入图片描述

controller层:sys_dept.go

package sys

import (
	"github.com/gofiber/fiber/v2"
	"go-web2/app/common/config"
	"go-web2/app/model/sys"
)

type DeptController struct{}

// 部门树列表
func (DeptController) GetList(c *fiber.Ctx) error {
	dept := sys.SysDept{}
	dept.Token = c.Get(config.TokenHeader)
	return c.Status(200).JSON(config.Success(dept.GetListTree()))
}

// 根据id获取部门
func (DeptController) GetById(c *fiber.Ctx) error {
	dept := sys.SysDept{}
	dept.Id = c.Params("id")
	dept.Token = c.Get(config.TokenHeader)
	err := dept.GetById()
	if err != nil {
		return c.Status(200).JSON(config.Error(err.Error()))
	}
	return c.Status(200).JSON(config.Success(dept))
}

// 新增部门
func (DeptController) Insert(c *fiber.Ctx) error {
	var dept sys.SysDept
	if err := c.BodyParser(&dept); err != nil {
		//log.Error(err.Error())
		return c.Status(200).JSON(config.Error(err.Error()))
	}
	dept.Token = c.Get(config.TokenHeader)
	err := dept.Insert()
	if err != nil {
		return c.Status(200).JSON(config.Error(err.Error()))
	}
	return c.Status(200).JSON(config.Success(nil))
}

// 修改部门
func (DeptController) Update(c *fiber.Ctx) error {
	var dept sys.SysDept
	if err := c.BodyParser(&dept); err != nil {
		//log.Error(err.Error())
		return c.Status(200).JSON(config.Error(err.Error()))
	}
	dept.Token = c.Get(config.TokenHeader)
	err := dept.Update()
	if err != nil {
		return c.Status(200).JSON(config.Error(err.Error()))
	}
	return c.Status(200).JSON(config.Success(nil))
}

// 删除部门
func (DeptController) Delete(c *fiber.Ctx) error {
	var dept sys.SysDept
	dept.Id = c.Params("id")
	dept.Token = c.Get(config.TokenHeader)
	if err := dept.Delete(); err != nil {
		return c.Status(200).JSON(config.Error(err.Error()))
	}
	return c.Status(200).JSON(config.Success(nil))
}

// 部门下拉树列表
func (DeptController) GetSelectList(c *fiber.Ctx) error {
	dept := sys.SysDept{}
	dept.Token = c.Get(config.TokenHeader)
	return c.Status(200).JSON(config.Success(dept.GetListTree()))
}

model层:sys_dept.go

package sys

import (
	"github.com/google/uuid"
	"github.com/pkg/errors"
	"go-web2/app/common/config"
	"strings"
	"time"
)

// 部门管理
type SysDept struct {
	config.BaseModel
	Name     string    `json:"name" form:"name"`         // 名称
	ParentId string    `json:"parentId" form:"parentId"` // 上级部门id
	Level    int       `json:"level" form:"level"`       // 层级(1 根目录 2 单位 3 部门 4 小组)
	Sort     int       `json:"sort" form:"sort"`         // 序号
	Children []SysDept `gorm:"-" json:"children"`        // 子级数据
}

// 获取表名
func (SysDept) TableName() string {
	return "sys_dept"
}

// 递归所有子节点
func (e *SysDept) ChildList() []SysDept {
	// 获取所以子节点包括它本身
	sql := `
		WITH RECURSIVE temp_dept AS (
            SELECT id,name,parent_id FROM sys_dept WHERE id = ?
            UNION ALL
            SELECT d.id,d.name,d.parent_id FROM sys_dept d
            JOIN temp_dept sd ON sd.id = d.parent_id
        )
        SELECT * FROM temp_dept
	`
	var childList []SysDept
	config.DB.Raw(sql, e.ParentId).Find(&childList)
	return childList
}

// 根据parentId得到所有子节点的id和name(包括它本身),以逗号分隔
func GetDeptChild(parentId string) (string, string) {
	dept := SysDept{}
	dept.ParentId = parentId
	list := dept.ChildList()
	if len(list) > 0 {
		idList := []string{}
		nameList := []string{}
		for _, t := range list {
			idList = append(idList, t.Id)
			nameList = append(nameList, t.Name)
		}
		return strings.Join(idList, ","), strings.Join(nameList, ",")
	}
	return "", ""
}

// 递归所有父节点,获取获取祖级列表(不包括它本身)
func (e *SysDept) GetAncestor() (string, string) {
	sql := `
		WITH RECURSIVE temp_dept(id,name,parent_id,level) AS (
			SELECT id,name,parent_id,level
			FROM sys_dept
			WHERE id = ?
			UNION ALL
			SELECT d.id,d.name,d.parent_id,d.level
			FROM sys_dept d
			JOIN temp_dept c ON d.id = c.parent_id
		)
		SELECT id,name,parent_id,level
		FROM temp_dept
		WHERE id != ?
		ORDER BY level,parent_id
	`
	var ancestorList []SysDept
	config.DB.Raw(sql, e.Id, e.Id).Find(&ancestorList)
	idList := []string{}
	nameList := []string{}
	for _, t := range ancestorList {
		idList = append(idList, t.Id)
		nameList = append(nameList, t.Name)
	}
	return strings.Join(idList, ","), strings.Join(nameList, ",")
}

// 树形列表
func (e *SysDept) GetListTree() []SysDept {
	var list []SysDept // 查询结果
	sql := ""
	args := []interface{}{}
	loginUser := GetLoginUser(e.Token)
	e.Id = loginUser.DeptId
	if e.Id != "" {
		// 这里用于递归 e.Id 的父级根节点,因为转为树结构时,是从根节点开始递归的(不包括e.Id本身)
		sql = `WITH RECURSIVE temp_dept(id,parent_id,name,level,sort) AS (
                SELECT id,parent_id,name,level,sort
                FROM sys_dept
                WHERE id = ?
                UNION ALL
                SELECT d.id,d.parent_id,d.name,d.level,d.sort
                FROM sys_dept d
                JOIN temp_dept c ON d.id = c.parent_id
            )
            SELECT id,parent_id,name,level,sort
            FROM temp_dept
            WHERE id != ?
            UNION ALL`
		args = append(args, e.Id, e.Id)
	}
	sql += ` SELECT id,parent_id,name,level,sort FROM sys_dept`
	// 设置数据范围查询
	scope := AppendQueryDataScope(e.Token, "id", "2", false, true)
	if scope != "" {
		sql = sql + " WHERE " + scope
	}
	config.DB.Table(e.TableName()).Debug().Order("`level`,parent_id,sort asc").Raw(sql, args...).Find(&list)
	return buildDeptTree(list, "ROOT")
}

// 获取详情
func (e *SysDept) GetById() (err error) {
	if !CheckDataScope(e.Token, e.Id, false, true) {
		err = errors.New("没有操作权限!")
		return
	}
	config.DB.Table(e.TableName()).Where("id = ?", e.Id).Find(e)
	return
}

// 新增
func (e *SysDept) Insert() (err error) {
	// 新增部门时,只允许新增子部门(也就是只允许给当前用户所在部门新增子部门)
	if !CheckDataScope(e.Token, e.ParentId, false, true) {
		err = errors.New("没有操作权限!")
		return
	}
	// 校验用户名和手机号码
	var count int64
	query := config.DB.Table(e.TableName())
	query.Where("name = ? and parent_id = ?", e.Name, e.ParentId).Count(&count)
	if count > 0 {
		err = errors.New("名称已存在!")
		return
	}
	err = e.getLevel()
	if err != nil {
		return err
	}
	e.Id = strings.ReplaceAll(uuid.NewString(), "-", "")
	e.CreatorId = GetLoginId(e.Token)
	e.CreateTime = time.Now()
	config.DB.Table(e.TableName()).Create(e)
	// 新增成功,更新数据权限缓存
	exists, _ := config.RedisConn.Exists(config.DATA_SCOPE + e.ParentId).Result()
	if exists > 0 {
		childId := config.RedisConn.HGet(config.DATA_SCOPE+e.ParentId, "childId").Val()
		childName := config.RedisConn.HGet(config.DATA_SCOPE+e.ParentId, "childName").Val()
		config.RedisConn.HSet(config.DATA_SCOPE+e.ParentId, "childId", childId+","+e.Id)
		config.RedisConn.HSet(config.DATA_SCOPE+e.ParentId, "childName", childName+","+e.Name)
	}
	return
}

// 修改
func (e *SysDept) Update() (err error) {
	// 修改部门时,只允许修改当前部门和子部门数据
	if !CheckDataScope(e.Token, e.Id, false, true) {
		err = errors.New("没有操作权限!")
		return
	}
	// 校验用户名和手机号码
	var count int64
	query := config.DB.Table(e.TableName())
	query.Where("name = ? and parent_id = ? and id <> ?", e.Name, e.ParentId, e.Id).Count(&count)
	if count > 0 {
		err = errors.New("名称已存在!")
		return
	}
	err = e.getLevel()
	if err != nil {
		return err
	}
	config.DB.Table(e.TableName()).Model(&SysDept{}).Where("id = ?", e.Id).Updates(e)
	return
}

// 删除
func (e *SysDept) Delete() (err error) {
	// 修改部门时,只允许修改当前部门和子部门数据
	if !CheckDataScope(e.Token, e.Id, false, true) {
		err = errors.New("没有操作权限!")
		return
	}
	// 1、校验是否存在下级
	var count int64
	query := config.DB.Table(e.TableName())
	query.Where("parent_id = ?", e.Id).Count(&count)
	if count > 0 {
		err = errors.New("存在下级,不允许删除")
		return
	}
	// 2、校验是否存在用户
	if CheckDeptExistUser(e.Id) {
		err = errors.New("该组织存在用户,不允许删除")
		return
	}
	if err = config.DB.Table(e.TableName()).Delete(e).Error; err != nil {
		return
	}
	return
}

// 新增或修改部门时,根据父级的level获取当前部门的level
func (e *SysDept) getLevel() (err error) {
	if e.ParentId == "ROOT" {
		e.Level = 1
	} else {
		var parent SysDept
		parent.Id = e.ParentId
		parent.GetById()
		if parent.Name == "" {
			err = errors.New("上级不存在!")
			return
		}
		e.Level = parent.Level + 1
	}
	return
}

// 构建树结构
func (e *SysDept) BuildTree(list []SysDept, parentId string) []SysDept {
	var tree []SysDept
	for _, item := range list {
		if item.ParentId == parentId {
			children := e.BuildTree(list, item.Id)
			if len(children) > 0 {
				item.Children = children
			}
			tree = append(tree, item)
		}
	}
	return tree
}

最后

其实上面的代码都没有什么特别需要注意的地方,都是一些业务代码来的。对新手来说,需要注意的地方,可能就是查询数据的时候,特别是用原生sql拼接查询条件时,要用占位符的形式(也就是用 “?” 表示)拼接,那些查询条件不要用加号或者是fmt.Sprintf() 去拼接,不然容易有sql注入的问题。

还有就是分页查询,需要查询总数时,Count() 方法需要分开单独一句写,不然查的就不是总数,而是每一页的数量。


好啦,以上就是本篇文章的全部内容辣,等我更完这个项目的全部文章,我会放出完整代码的地址,欢迎大家多多点赞支持下,最后可以关注我不迷路~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1321316.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

向华为学习:基于BLM模型的战略规划研讨会实操的详细说明,含研讨表单(四)

2023年只剩下不到10天了&#xff0c;如何科学、系统地制定2024年的公司战略&#xff1f;如果您还没有找到好的方法&#xff0c;或者对过去的方法不是很满意&#xff0c;或者想探索习方法&#xff0c;不妨来看看华为和许多标杆企业在用的——基于BLM模型来组织战略规划。 前面三…

SpringBoot Whitelabel Error Page 报错--【已解决】

springboot 报错信息如下 这个报错页面就是个404 &#xff0c;代表你访问的url 没有对应的的requestmapping 其实没啥影响的一个问题&#xff0c;但是看到Error 就是不爽&#xff0c;改了他丫的 解决方法如下 一、调整application.properties配置【治标不治本】 server.err…

pytorch——豆瓣读书评价分析

任务目标 基于给定数据集&#xff0c;采用三层bp神经网络方法&#xff0c;编写程序并构建分类模型&#xff0c;通过给定特征实现预测的书籍评分的模型。 选取数据 在各项指标中&#xff0c;我认为书籍的评分和出版社、评论数量还有作者相关&#xff0c;和其他属性的关系并大。…

泛型深入理解

泛型的概述 泛型&#xff1a;是JDK5中引入的特性&#xff0c;可以在编译阶段约束操作的数据类型&#xff0c;并进行检查。 泛型的格式&#xff1a;<数据类型>; 注意&#xff1a;泛型只能支持引用数据类型。 集合体系的全部接口和实现类都是支持泛型的使用的。 泛型的…

鹿目标检测数据集VOC+YOLO格式3400+张

鹿是一种优美、高贵的动物&#xff0c;常见于欧亚大陆和北美洲的森林、草原和山地。它们属于哺乳动物&#xff0c;是偶蹄目牛科的动物&#xff0c;通常被称为鹿科动物。鹿的身体修长&#xff0c;腿长而纤细&#xff0c;四肢支撑着一副优美的身躯&#xff0c;让人不禁为之倾倒。…

基于JAVA+SpringBoot的线上智能问诊就医平台

✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、项目背景介绍&#xff1a; 智慧医药系统&#xf…

【设计模式--行为型--访问者模式】

设计模式--行为型--访问者模式 访问者模式定义结构案例优缺点使用场景扩展分派动态分派静态分派双分派 访问者模式 定义 封装一些作用于某种数据结构中的各元素的操作&#xff0c;它可以在不改变这个数据结构的前提下定义作用于这些元素的新操作。 结构 抽象访问者角色&…

故障排除: Vcenter Root user password expires in 0 days.

故障排除: Vcenter Root user password expires in 0 days. 1. 故障现象 登录Vsphere Client显示报错信息如下: Vcenter Root user password expires in 0 days.2. 故障排除 2.1 打开Vcenter ssh 访问VMware vCenter Server 管理,即5480端口,比如VMware vSphere地址是https…

RAG(检索增强生成)技术

1.基本概念 检索增强 LLM ( Retrieval Augmented LLM )&#xff0c;简单来说&#xff0c;就是给 LLM 提供外部数据库&#xff0c;对于用户问题 ( Query )&#xff0c;通过一些信息检索 ( Information Retrieval, IR ) 的技术&#xff0c;先从外部数据库中检索出和用户问题相关…

【个人版】SpringBoot下Spring-Security自定义落地篇【四】

SpringBoot Spring-Security 背景&#xff1a; 上篇文章在源码读取的基础上&#xff0c;根据自身代码习惯及需求&#xff0c;总结了一个自定义简单落地版本。后来在看到松哥写的博文&#xff08;不太爱看官网&#xff09;&#xff0c;发现还有新的变种模式&#xff0c;虽然整…

机器学习---决策树

介绍 决策树和随机森林都是非线性有监督的分类模型。 决策树是一种树形结构,树内部每个节点表示一个属性上的测试,每个分支代表一个测试输出,每个叶子节点代表一个分类类别。通过训练数据构建决策树,可以对未知数据进行分类, 随机森林是由多个决策树组成,随机森林中每…

【参天引擎】华为参天引擎内核架构源码架构,多线程服务,数据节点管理,多节点间元数据管理

cantian引擎源码结构 ​专栏内容&#xff1a; 参天引擎内核架构 本专栏一起来聊聊参天引擎内核架构&#xff0c;以及如何实现多机的数据库节点的多读多写&#xff0c;与传统主备&#xff0c;MPP的区别&#xff0c;技术难点的分析&#xff0c;数据元数据同步&#xff0c;多主节点…

极智AI | 算子融合、矩阵分块 一图看懂大模型优化技术FlashAttention

欢迎关注我的公众号 [极智视界],获取我的更多经验分享 大家好,我是极智视界,本文来介绍一下 算子融合、矩阵分块 一图看懂大模型优化技术FlashAttention。 邀您加入我的知识星球「极智视界」,星球内有超多好玩的项目实战源码下载,链接:https://t.zsxq.com/0aiNxERDq 没错…

RabbitMQ入门指南(二):架构和管理控制台的使用

专栏导航 RabbitMQ入门指南 从零开始了解大数据 目录 专栏导航 前言 一、RabbitMQ架构 二、RabbitMQ管理控制台的使用 1.Exchange交换机 2.Queue队列 3.绑定Exchange交换机和Queue队列 4.发送消息 5.数据隔离 总结 前言 RabbitMQ是一个高效、可靠的开源消息队列系统…

HTML_CSS的基本选择器的使用及其作用范围和优先级

目录 ✨CSS的使用&#xff1a;行内样式内部样式外部样式 ✨CSS基本选择器&#xff1a;id选择器class选择器标签选择器 ✨优先级&#xff1a;选择器的优先级样式表的优先级 ✨CSS的使用&#xff1a; 根据定义CSS的位置不同&#xff0c;分为行内样式、内部样式和外部样式 行内样…

java配置+J_IDEA配置+git配置+maven配置+基本语句

当前目录文件夹dir 进入文件夹cd 返回上一级cd.. 创建文件夹&#xff1a;mkdir 文件名删除文件夹&#xff1a;rd 文件夹名&#xff0c; 目录不为空不能直接删 rd /s 带子文件夹一起删 清屏cls 切换d盘才能进入 下载git地址&#xff1a; Git - Downloading Package (g…

隐私计算介绍

这里只对隐私计算做一些概念性的浅显介绍&#xff0c;作为入门了解即可 目录 隐私计算概述隐私计算概念隐私计算背景国外各个国家和地区纷纷出台了围绕数据使用和保护的公共政策国内近年来也出台了数据安全、隐私和使用相关的政策法规 隐私计算技术发展 隐私计算技术安全多方计…

JDBC 数据库连接池

目录 一、什么是数据库连接池二、为什么需要数据库连接池&#xff1f;三、JDBC 数据库连接池的实现四、C3P0的使用1、加入c3p0 jar包2、配置xml文件3、c3p0-config.xml模板4、C3P0的使用 五、Druid的使用1、加入Druid jar包2、定义配置文件:3、Druid连接池的使用 六、HikariCP的…

【最新】2023年30米分辨率土地利用遥感监测数据

改革开放以来&#xff0c;中国经济的快速发展对土地利用模式产生了深刻的影响。同时&#xff0c;中国又具有复杂的自然环境背景和广阔的陆地面积&#xff0c;其土地利用变化不仅对国家发展&#xff0c;也对全球环境变化产生了深刻的影响。为了恢复和重建我国土地利用变化的现代…

硬件基础-二极管

3.二极管 正偏时是多数载流子载流导电&#xff0c;反偏时是少数载流子载流导电。所以&#xff0c;正偏电流大&#xff0c;反偏电流小&#xff0c;PN 结显示出单向电性。多数载流子正向通过 PN 结时就需要克服内电场的作用&#xff0c;需要约 0.7 伏的外加电压&#xff0c;这是…