[Go] Build a GoWeb backend management system from scratch based on GoFiber (4) User management and department management modules

The first article: [Go] Build a GoWeb backend management system from scratch based on GoFiber (1) Building the project

Part Two: [Go] Build a GoWeb backend management system from scratch based on GoFiber (2) Log output middleware, verification token middleware, configuration routing, and basic tool functions.

Part 3: [Go] Build a GoWeb backend management system from scratch based on GoFiber (3) Log management (login log, operation log), user login module

Random thoughts: My neck has been hurting recently, and it has been going on for a while. It hurts from morning to night. It hurts when I get up in the morning, and it still hurts when I go to bed at night, which makes me often unable to sleep. I would have pain in my neck before, but it would go away within a day or two. It really hurts when sitting and lying down. It hurts on ordinary pillows, U-shaped pillows, and even without pillows. . . Is there anything you can do to alleviate this? Going for a massage?

Receive parameters

In gofiber, to receive parameters from the front end, there are the following parameter transmission and reception methods:

  • For parameters spliced ​​after the address with a question mark, we generally use c.Query("参数名")to obtain them;
  • Path parameters (directly spliced ​​after the address, for example: /sys/user/getById/1, 1 here is the path parameter), use c.Params("参数名");
  • FormData parameter, received c.FormValue("参数名")with;
  • The file parameters passed by FormData can be c.MultipartForm()obtained with ;
  • JSON parameters, used c.BodyParser(结构体指针)to receive, is to convert json into a specific structure.

Database operations

  • Add: Create()Use this method to add a single piece of data or add a batch of data: db.Create(&user)、db.Create(&list).
  • Revise:
    • Update() 、UpdateColumn(): Update the specified column and only update one field at a time.
    • Updates() 、UpdateColumns(): Update the entire structure or map, but note that these two methods will not update zero values.
    • Save(): Insert or update, pass structure or map, zero value can be inserted or updated. Update based on primary key, insert if primary key does not exist.
  • Delete: Delete()Delete based on primary key or batch delete by default. It can also Delete()be used in front Where()to specify deletion conditions.
  • Query: You can use Find()to query a single piece of data or multiple pieces of data.

There is actually a slight difference between Update(), UpdateColumn() and Updates(), UpdateColumns(). For details, we can look at the source code:

Insert image description here

tx.Statement.SkipHooks = trueThis code is about transaction settings. When this setting is set to true, GORM will skip all hooks related to this transaction.

Then when updating, you can use Select()to specify the fields to be updated. When updating or adding, you can use to Omit()ignore the specified fields.

Specifically how to operate the database, let's look directly at the code below.

User Management

The first is the user management module, which has these interfaces:

Insert image description here

Without further ado, let’s get straight to the code

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

The above interfaces use methods such as c.Query(), c.Params(), c.FormValue(), c.MultipartForm()and .c.BodyParser()

model层:sys_user.go

Now that the interface is in place, the next step is to obtain the data. The data comes from the database, so the next step is to operate the database.

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
}

Through the methods and functions of the controller and model layers above, it is not difficult to see that many of my functions are bound to structures. The structure bound to the controller layer is UserController, and the model layer is also bound to the corresponding structure. Functions like this that are bound to a specific data type are called methods.

Insert image description here

Bind the function to a data type so that the function only belongs to this data type. We know that in go, function names cannot be repeated in the same package, but we can bind different data types to functions with the same name. This saves you a lot of trouble with naming.

If a function is specifically used to handle the business of a certain structure, you can bind the corresponding structure to the function (you can also not bind it, but you need to pay attention when naming the function).

Department management

Next is the department management module, which has the following interfaces:

Insert image description here

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
}

at last

In fact, there is nothing special to note about the above code, it is all business code. For novices, what needs to be paid attention to may be that when querying data, especially when splicing query conditions using native SQL, splicing should be done in the form of placeholders (that is, represented by "?"), and those query conditions should not be used. Plus sign or fmt.Sprintf()to splice, otherwise it is easy to have SQL injection problems.

There is also paging query. When you need to query the total number, the Count() method needs to be written in a separate sentence, otherwise the query will not be the total number, but the number of each page.


Okay, the above is the entire content of this article. When I finish updating all the articles on this project, I will release the address of the complete code. Welcome everyone to like and support. Finally, you can follow me to not get lost~

Guess you like

Origin blog.csdn.net/weixin_43165220/article/details/134941513