在线办公系统 - Backend
项目介绍
环境配置
MacOS 系统使用 Docker 安装基本环境

1. 安装 Docker
去 Docker 官网https://docs.docker.com/desktop/就能下载到 MacOS 版本的 Docker 安装文件,默认安装即可,无需额外设置。
2. 安装 MySQL
在命令行窗口执行下面的 docker 命令,在线下载 MySQL 镜像文件
docker pull mysql:8.0.23
创建容器的时候,我们需要把 MySQL 容器内的数据目录映射到 MacOS 系统上面。如果 MySQL 容器挂掉了,数据库文件不会丢失。我们新建一个 MySQL 容器,挂载上这个数据目录就又能正常使用 MySQL 了。  MySQL 容器我分配内存空间是 500M,如果将来觉得不够用,删除容器,再创建新容器的时候分配更大的内存。而且只要挂载上那些文件目录,MySQL 的数据就不会丢失。
运行下面的命令,创建 MySQL 容器。-v /root/mysql/data:/var/lib/mysql这个参数设置的是把容器中 MySQL 的数据目录/var/lib/mysql映射到 MacOS 的/root/mysql/data目录上面,当然了 MacOS 的映射目录你也可以改成别的目录地址,通常 MacOS 的root目录不对普通用户开放,你自己创建两个文件夹,把这两个文件夹路径替换到命令中的/root/mysql/data和/root/mysql/config,然后把目录映射到容器上。
docker run -it -d --name mysql -p 3307:3306 \
-m 500m -v /Users/nedonion/ned/mysql/data:/var/lib/mysql \
-v /Users/nedonion/ned/mysql/config:/etc/mysql/conf.d  \
-e MYSQL_ROOT_PASSWORD=abc123456 \
-e TZ=Asia/Shanghai mysql:8.0.23 \
--lower_case_table_names=1
/Users/nedonion/ned/mysql/data替换/root/mysql/data/Users/nedonion/ned/mysql/config替换/root/mysql/config- port number 改为 
3307:3306 
3. 安装 MongoDB
执行下面的命令,下载 MongoDB 的镜像文件
docker pull mongo:4.4.7
因为 MacOS 无法使用 root 目录,故创建/Users/nedonion/ned/mongo/mongod.conf文件,然后在文件中添加如下内容。
net:
   port: 27017
   bindIp: "0.0.0.0"
storage:
   dbPath: "/data/db"
security:
   authorization: enabled
创建容器,为 MongoDB 分配 500M 内存
docker run -it -d --name mongo -p 27017:27017 \
-v /Users/nedonion/ned/mongo:/etc/mongo -m 500m \
-e MONGO_INITDB_ROOT_USERNAME=admin \
-e MONGO_INITDB_ROOT_PASSWORD=abc123456 \
-e TZ=Asia/Shanghai \
mongo:4.4.7 --config /etc/mongo/mongod.conf
4. 安装 Redis
执行命令,在线安装 Redis 镜像
docker pull redis:6.0.10
创建/Users/nedonion/ned/redis/redis.conf文件,然后添加如下内容。
bind 0.0.0.0
protected-mode yes
port 6379
tcp-backlog 511
timeout 0
tcp-keepalive 0
loglevel notice
logfile ""
databases 4
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
dir ./
requirepass abc123456
执行命令,创建 Redis 容器,分配 300M 内存
docker run -it -d --name redis -m 300m  -p 6379:6379 \
-e TZ=Asia/Shanghai \
-v /Users/nedonion/ned/redis:/usr/local/etc/redis redis:6.0.10 \
redis-server /usr/local/etc/redis/redis.conf
5. 安装 RabbitMQ
执行命令,在线安装 RabbitMQ 镜像
docker pull rabbitmq:3.8.9
执行命令,创建 RabbitMQ 容器,分配 300M 内存
docker run -it -d --name mq -m 300m \
-p 4369:4369 -p 5672:5672 -p 15672:15672 -p 25672:25672 \
-e TZ=Asia/Shanghai \
rabbitmq:3.8.9
运行项目
运行 Workflow
配置 application.yml
server:
  jetty:
    threads:
      acceptors: 4
      selectors: 8
  port: 9090
  servlet:
    context-path: /emos-workflow
    multipart:
      max-request-size: 10MB
      max-file-size: 2MB
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/emos?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
      username: root
      password: ...
      initial-size: 2
      max-active: 4
      min-idle: 4
把 emos-workflow.jar 和 application.yml 文件放在相同的目录里面
java -jar -Dfile.encoding=utf-8 emos-workflow.jar --spring.config.location=application.yml
用户管理

3.2 用户登录系统流程
用户登录时序图

上面时序图中,后端项目最终返回给前端的 R 对象中包含两种数据,一个是布尔值代表登陆结果,另一个是该用户的拥有的权限列表。因为在前端需要根据权限判断用户是否可以看到某些栏目和执行某些操作。
用户密码的加密与解密
在 tb_user 数据表中保存的用户信息里面,password 字段的值是经过加密存储的,而且不存在全局密钥。我使用每个用户的 username 作为密钥,对 password 字段加密。
这里采用的是 IBM 开发的 AES 算法。这种对称加密算法在加密和解密数据的时候,使用相同的密钥。也就是说密钥既可以用来加密数据,也可以解密数据。关于 AES 算法的优点我这里不展开说明了,大家可以自己百度查阅资料。MySQL 数据库提供了内置的 DES 加密和解密的函数,我们只需要调用即可。加密的函数叫做AES_ENCRPT(),解密的函数叫做AES_DECRPT(),下面咱们结合具体案例了解这两个函数的用法。
SELECT AES_ENCRYPT("abc123456","HelloWorld");

因为加密后的字节数据在 UTF8 字符集中会出现乱码,所以我把字节数据转换成 16 进制数据(HEX),就不会出现乱码了。
SELECT HEX(AES_ENCRYPT("abc123456","HelloWorld"));

解密的时候,我们需要先把 16 进制数据转换成字节,然后进行解密。
SELECT AES_DECRYPT(UNHEX("F943968B28B2D93E2DC48CD72014FE1A"),"HelloWorld")

注册超级管理员账号
insert into tb_user (username, password, role, root, status)
values ('admin', 'E15F671BE4D965A790689456ED3B4B90', '[0]', 1, 1)
3.3 用户登录
Persistence Layer
在TbUserDao.xml文件中添加用于登陆的 SQL 语句,原理并不复杂。根据登陆页面提交的用户名和密钥,到数据库中查找是否有匹配的数据。因为数据库中的密码是加密过的,所以我们要把登陆的密码加密之后,再跟数据库中的记录做比对。
<select id="login" parameterType="HashMap" resultType="Integer">
    SELECT id
    FROM tb_user
    WHERE username = #{username}
    AND password = HEX(AES_ENCRYPT(#{password}, #{username}))
    LIMIT 1;
</select>
大家请注意,SELECT 子句中返回的是主键 id 值。如果没查询到匹配的用户记录,数据库会返回 null 值,所以 resultType 属 性值必须是 Integer,而不能是 int,因为 int 无法保存 null 值。
在TbUserDao.java接口中定义抽象的 Dao 方法,与 SQL 语句对应。
@Mapper
public interface TbUserDao {
    ……
    public Integer login(HashMap param);
}
Business Layer
在UserService.java接口中声明抽象的的登陆方法。
public interface UserService {
    ……
    public Integer login(HashMap param);
}
在UserServiceImpl.java中实现登陆方法,就是把查询到用户 ID 返回。
@Service
public class UserServiceImpl implements UserService {
    ……
    @Override
    public Integer login(HashMap param) {
        Integer userId = userDao.login(param);
        return userId;
    }
}
Web Layer
我们首先需要创建一个 Form 类保存 HTTP 请求提交的数据,使用 Form 类的好处是可以为变量设置注解,自动完成后端验证。在com.example.emos.api.controller.form包中创建LoginForm.java类。
@Data
@Schema(description = "登陆表单类")
public class LoginForm {
    @NotBlank(message = "username不能为空")
    @Pattern(regexp = "^[a-zA-Z0-9]{5,20}$", message = "username内容不正确")
    @Schema(description = "用户名")
    private String username;
    @NotBlank(message = "password不能为空")
    @Pattern(regexp = "^[a-zA-Z0-9]{6,20}$", message = "password内容不正确")
    @Schema(description = "密码")
    private String password;
}
在UserController.java类中定义 Web 方法用于处理登陆请求。
@RestController
@RequestMapping("/user")
@Tag(name = "UserController", description = "用户Web接口")
public class UserController {
    ……
    @PostMapping("/login")
    @Operation(summary = "登陆系统")
    public R login(@Valid @RequestBody LoginForm form) {
        HashMap param = JSONUtil.parse(form).toBean(HashMap.class);
        Integer userId = userService.login(param);
        R r = R.ok().put("result", userId != null ? true : false);
        if (userId != null) {
            StpUtil.setLoginId(userId);
            Set<String> permissions = userService.searchUserPermissions(userId);
            /*
             * 因为新版的Chrome浏览器不支持前端Ajax的withCredentials,
             * 导致Ajax无法提交Cookie,所以我们要取出生成的Token返回给前端,
             * 让前端保存在Storage中,然后每次在Ajax的Header上提交Token
             */
            String token=StpUtil.getTokenInfo().getTokenValue();
            r.put("permissions",permissions).put("token",token);
        }
        return r;
    }
}
Swagger 测试
用 Swagger 测试的时候,打开浏览器输入 http://localhost:8090/emos-api/swagger-ui.html,然后找到 Web 方法执行测试,这种做法做简单。
Sa-Token
Sa-Token 框架要求我们用户登陆成功之后,必须在 Web 方法中执行 StpUtil.setLoginId(userId),Sa-Token 框架会根据 UserId 自动生成 Token 令牌,缓存到 Redis 里,然后把 Token 以 Cookie 的形式存储到浏览器上面。

浏览器再提交什么请求,都会带上 Cookie(含有 Token),这样 Sa-Token 拦截请求,检查 Cookie 是否含有合法的 Token(与 Redis 缓存的 Token 比对),就能判断出用户是否登陆了系统。如果没登陆系统,就跳转到登陆画面。

3.5 修改密码和退出登录
Persistence Layer
在TbUserDao.xml文件中声明修改密码的 SQL 语句。由于退出系统不需要操作数据库,所以不需要用到持久层。
<update id="updatePassword" parameterType="HashMap">
  UPDATE tb_user
  SET password = HEX(AES_ENCRYPT(#{newPassword}, username))
  WHERE id = #{userId}
  AND password = HEX(AES_ENCRYPT(#{password}, username))
</update>
在TbUserDao.java接口中定义抽象方法。
public interface TbUserDao {
    ……
    public int updatePassword(HashMap param);
}
Business Layer
在UserService.java接口中声明抽象方法。
public interface UserService {
    ……
    public int updatePassword(HashMap param);
}
在UserServiceImpl.java中实现抽象方法。
public class UserServiceImpl implements UserService {
    ……
    @Override
    public int updatePassword(HashMap param) {
        int rows = userDao.updatePassword(param);
        return rows;
    }
}
Web Layer
创建UpdatePasswordForm.java类,用于保存前端提交的数据。
@Schema(description = "修改密码表单")
@Data
public class UpdatePasswordForm {
    @NotBlank(message = "password不能为空")
    @Pattern(regexp = "^[a-zA-Z0-9]{6,20}$",message = "password内容不正确")
    @Schema(description = "密码")
    String password;
    @NotBlank(message = "newPassword")
    @Pattern(regexp = "^[a-zA-Z0-9]{6,20}$",message = "newPassword内容不正确")
    @Schema(description = "密码")
    private String newPassword
}
在UserController.java中声明退出系统和修改密码的两个 Web 方法。
public class UserController {
    ……
    @GetMapping("/logout")
    @Operation(summary = "退出系统")
    public R logout() {
        StpUtil.logout();
        return R.ok();
    }
    @PostMapping("/updatePassword")
    @SaCheckLogin
    @Operation(summary = "修改密码")
    public R updatePassword(@Valid @RequestBody UpdatePasswordForm form) {
        int userId = StpUtil.getLoginIdAsInt();
        HashMap param = new HashMap() {{
            put("userId", userId);
            put("password", form.getPassword());
            put("newPassword", form.getNewPassword());
        }};
        int rows = userService.updatePassword(param);
        return R.ok().put("rows", rows);
    }
}
3.7 查询用户分页数据
Persistence Layer
因为查询条件是动态组合的,所以我们在持久层里面编写 SQL 语句的时候,要判断某个数据不为空,就把它添加到 WHERE 子句里面充当查询条件。因为是分页查询,所以我们要在 TbUserDao.xml 文件中编写两条 SQL 语句,一个是用来查询总记录数,另一个用来查询分页记录。
<select id="searchUserByPage" parameterType="HashMap" resultType="HashMap">
    SELECT
        DISTINCT u.id,
        u.name,
        u.sex,
        u.tel,
        u.email,
        d.dept_name AS dept,
        u.hiredate,
        u.root,
        u.status,
        ( SELECT GROUP_CONCAT( role_name separator "," ) FROM tb_role WHERE JSON_CONTAINS ( u.role, CONVERT ( id, CHAR ) ) ) AS roles
    FROM tb_user u
    JOIN tb_role r ON JSON_CONTAINS ( u.role, CONVERT ( r.id, CHAR ) )
    LEFT JOIN tb_dept d ON u.dept_id = d.id
    WHERE 1=1
    <if test="name!=null">
        AND u.name LIKE "%${name}%"
    </if>
    <if test="sex!=null">
        AND u.sex=#{sex}
    </if>
    <if test="role!=null">
        AND r.role_name=#{role}
    </if>
    <if test="deptId!=null">
        AND d.id=#{deptId}
    </if>
    <if test="status!=null">
        AND u.status=#{status}
    </if>
    LIMIT #{start}, #{length}
</select>
<select id="searchUserCount" parameterType="HashMap" resultType="long">
    SELECT
         COUNT(DISTINCT u.id)
    FROM tb_user u
    JOIN tb_role r ON JSON_CONTAINS ( u.role, CONVERT ( r.id, CHAR ) )
    WHERE 1=1
    <if test="name!=null">
        AND u.name LIKE "%${name}%"
    </if>
    <if test="sex!=null">
        AND u.sex=#{sex}
    </if>
    <if test="role!=null">
        AND r.role_name=#{role}
    </if>
    <if test="deptId!=null">
        AND u.dept_id=#{deptId}
    </if>
    <if test="status!=null">
        AND u.status=#{status}
    </if>
</select>
Business Layer
在UserService.java接口中声明抽象方法。
public interface UserService {
    ……
    public PageUtils searchUserByPage(HashMap param);
}
在UserServiceImpl.java类中实现抽象方法。
public class UserServiceImpl implements UserService {
    ……
    @Override
    public PageUtils searchUserByPage(HashMap param) {
        ArrayList<HashMap> list = userDao.searchUserByPage(param);
        long count = userDao.searchUserCount(param);
        int start = (Integer) param.get("start");
        int length = (Integer) param.get("length");
        PageUtils pageUtils = new PageUtils(list, count, start, length);
        return pageUtils;
    }
}
Web Layer
创建SearchUserByPageForm.java类,保存 Ajax 提交的数据。
@Data
@Schema(description = "查询用户分页记录表单")
public class SearchUserByPageForm {
    @NotNull(message = "page不能为空")
    @Min(value = 1, message = "page不能小于1")
    @Schema(description = "页数")
    private Integer page;
    @NotNull(message = "length不能为空")
    @Range(min = 10, max = 50, message = "length必须在10~50之间")
    @Schema(description = "每页记录数")
    private Integer length;
    @Pattern(regexp = "^[\\u4e00-\\u9fa5]{1,10}$", message = "name内容不正确")
    @Schema(description = "姓名")
    private String name;
    @Pattern(regexp = "^男$|^女$", message = "sex内容不正确")
    @Schema(description = "性别")
    private String sex;
    @Pattern(regexp = "^[a-zA-Z0-9\\u4e00-\\u9fa5]{2,10}$", message = "role内容不正确")
    @Schema(description = "角色")
    private String role;
    @Min(value = 1, message = "dept不能小于1")
    private Integer deptId;
    @Min(value = 1, message = "status不能小于1")
    private Integer status;
}
在UserController.java类中定义 Web 方法。
@RestController
@RequestMapping("/user")
@Tag(name = "UserController", description = "用户Web接口")
public class UserController {
    ……
    @PostMapping("/searchUserByPage")
    @Operation(summary = "查询用户分页记录")
    @SaCheckPermission(value = {"ROOT", "USER:SELECT"}, mode = SaMode.OR)
    public R searchUserByPage(@Valid @RequestBody SearchUserByPageForm form) {
        int page = form.getPage();
        int length = form.getLength();
        int start = (page - 1) * length;
        HashMap param = JSONUtil.parse(form).toBean(HashMap.class);
        param.put("start", start);
        PageUtils pageUtils = userService.searchUserByPage(param);
        return R.ok().put("page", pageUtils);
    }
}
3.9 添加新用户
实现 CRUD 操作中的添加新用户。添加新用户的时候,弹窗页面要求我们录入新用户的基本信息,这些信息都要被写入到用户表中。

Persistence Layer
持久层的 SQL 语句写起来要做很多的判断,为什么呢?这要先看一下tb_user数据表的字段。有大量的字段都是允许空值的。

在TbUserDao.xml文件中声明 INSERT 语句,用于插入用户记录。
<insert id="insert" parameterType="com.example.emos.api.db.pojo.TbUser">
    INSERT INTO tb_user
    SET
    <if test="username!=null">
        username = #{username},
    </if>
    <if test="password!=null">
        password = HEX(AES_ENCRYPT(#{password},#{username})),
    </if>
    <if test="openId!=null">
        open_id = #{openId},
    </if>
    <if test="nickname!=null">
        nickname = #{nickname},
    </if>
    <if test="photo!=null">
        photo = #{photo},
    </if>
    <if test="name!=null">
        name = #{name},
    </if>
    <if test="sex!=null">
        sex = #{sex},
    </if>
    <if test="tel!=null">
        tel = #{tel},
    </if>
    <if test="email!=null">
        email=#{email},
    </if>
    <if test="hiredate!=null">
        hiredate = #{hiredate},
    </if>
    role = #{role},
    <if test="root!=null">
        root = #{root},
    </if>
    <if test="deptId!=null">
        dept_id = #{deptId},
    </if>
    status = #{status},
    create_time = #{createTime}
</insert>
在TbUserDao.java中定义抽象的 DAO 方法。
public interface TbUserDao {
    ……
    public int insert(TbUser user);
}
Business Layer
在UserService.java接口中,定义抽象的方法。
public interface UserService {
    ……
    public int insert(TbUser user);
}
在UserServiceImpl.java类中实现抽象方法。
public class UserServiceImpl implements UserService{
    ……
	@Override
    public int insert(TbUser user) {
        int rows = userDao.insert(user);
        return rows;
    }
}
Web Layer
创建 InsertUserForm.java 类,用于保存 Ajax 提交的数据。
@Schema(description = "添加用户表单")
@Data
public class InsertUserForm {
    @NotBlank(message = "username不能为空")
    @Pattern(regexp = "^[a-zA-Z0-9]{5,20}$", message = "username内容不正确")
    @Schema(description = "用户名")
    private String username;
    @NotBlank(message = "password不能为空")
    @Pattern(regexp = "^[a-zA-Z0-9]{6,20}$", message = "password内容不正确")
    @Schema(description = "密码")
    private String password;
    @NotBlank(message = "name不能为空")
    @Pattern(regexp = "^[\\u4e00-\\u9fa5]{2,10}$", message = "name内容不正确")
    @Schema(description = "姓名")
    private String name;
    @NotBlank(message = "sex不能为空")
    @Pattern(regexp = "^男$|^女$", message = "sex内容不正确")
    @Schema(description = "性别")
    private String sex;
    @NotBlank(message = "tel不能为空")
    @Pattern(regexp = "^1\\d{10}$", message = "tel内容不正确")
    @Schema(description = "电话")
    private String tel;
    @NotBlank(message = "email内容不正确")
    @Email(message = "email内容不正确")
    @Schema(description = "邮箱")
    private String email;
    @NotBlank(message = "hiredate不能为空")
    @Pattern(regexp = "^((((1[6-9]|[2-9]\\d)\\d{2})-(0?[13578]|1[02])-(0?[1-9]|[12]\\d|3[01]))|(((1[6-9]|[2-9]\\d)\\d{2})-(0?[13456789]|1[012])-(0?[1-9]|[12]\\d|30))|(((1[6-9]|[2-9]\\d)\\d{2})-0?2-(0?[1-9]|1\\d|2[0-8]))|(((1[6-9]|[2-9]\\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00))-0?2-29-))$", message = "hiredate内容不正确")
    @Schema(description = "入职日期")
    private String hiredate;
    @NotEmpty(message = "role不能为空")
    @Schema(description = "角色")
    private Integer[] role;
    @NotNull(message = "deptId不能为空")
    @Min(value = 1, message = "deptId不能小于1")
    @Schema(description = "部门")
    private Integer deptId;
}
在 UserController.java 类中,声明 Web 方法。
public class UserController {
    ……
    @PostMapping("/insert")
    @SaCheckPermission(value = {"ROOT", "USER:INSERT"}, mode = SaMode.OR)
    @Operation(summary = "添加用  户")
    public R insert(@Valid @RequestBody InsertUserForm form) {
        TbUser user = JSONUtil.parse(form).toBean(TbUser.class);
        user.setStatus((byte) 1);
        user.setRole(JSONUtil.parseArray(form.getRole()).toString());
        user.setCreateTime(new Date());
        int rows = userService.insert(user);
        return R.ok().put("rows", rows);
    }
}
3.11 修改用户信息
Persistence Layer
因为我们无法判断用户在弹窗页面中修改了用户信息的哪部分,所以我们编写 UPDATE 语句需要判断提交的数据只要不为空,就更新到数据表中。
在 TbUserDao.xml 文件中,声明 UPDATE 语句。
<update id="update" parameterType="HashMap">
    UPDATE tb_user
    SET
    <if test="username!=null and password!=null">
        username = #{username},
        password = HEX(AES_ENCRYPT(#{password},#{username})),
    </if>
    <if test="name!=null">
        name = #{name},
    </if>
    <if test="sex!=null">
        sex = #{sex},
    </if>
    <if test="tel!=null">
        tel = #{tel},
    </if>
    <if test="email!=null">
        email = #{email},
    </if>
    <if test="hiredate!=null">
        hiredate = #{hiredate},
    </if>
    <if test="role!=null">
        role = #{role},
    </if>
    <if test="root!=null">
        root = #{root},
    </if>
    <if test="deptId!=null">
        dept_id = #{deptId},
    </if>
    <if test="status!=null">
        status = #{status},
    </if>
    id=id
    WHERE id=#{userId}
</update>
在 TbUserDao.java 接口中,定义抽象方法。
@Mapper
public interface TbUserDao {
    ……
    public int update(HashMap param);
}
Business Layer
在 UserService.java 接口中,声明抽象方法。
public interface UserService {
    ……
    public int update(HashMap param);
}
在 UserServiceImpl.java 类中,实现抽象方法。
public class UserServiceImpl implements UserService {
    ……
    @Override
    public int update(HashMap param) {
        int rows = userDao.update(param);
        return rows;
    }
}
Web Layer
创建 UpdateUserForm.java 类,用于保存 Ajax 提交的数据。
@Schema(description = "修改用户信息表单")
@Data
public class UpdateUserForm {
    @NotNull(message = "userId不能为空")
    @Min(value = 1, message = "userId不能小于1")
    @Schema(description = "用户ID")
    private Integer userId;
    @NotBlank(message = "username不能为空")
    @Pattern(regexp = "^[a-zA-Z0-9]{5,20}$", message = "username内容不正确")
    @Schema(description = "用户名")
    private String username;
    @NotBlank(message = "password不能为空")
    @Pattern(regexp = "^[a-zA-Z0-9]{6,20}$", message = "password内容不正确")
    @Schema(description = "密码")
    private String password;
    @NotBlank(message = "name不能为空")
    @Pattern(regexp = "^[\\u4e00-\\u9fa5]{2,10}$", message = "name内容不正确")
    @Schema(description = "姓名")
    private String name;
    @NotBlank(message = "sex不能为空")
    @Pattern(regexp = "^男$|^女$", message = "sex内容不正确")
    @Schema(description = "性别")
    private String sex;
    @NotBlank(message = "tel不能为空")
    @Pattern(regexp = "^1\\d{10}$", message = "tel内容不正确")
    @Schema(description = "电话")
    private String tel;
    @NotBlank(message = "email不能为空")
    @Email(message = "email内容不正确")
    @Schema(description = "邮箱")
    private String email;
    @NotBlank(message = "hiredate日期不能为空")
    @Pattern(regexp = "^((((1[6-9]|[2-9]\\d)\\d{2})-(0?[13578]|1[02])-(0?[1-9]|[12]\\d|3[01]))|(((1[6-9]|[2-9]\\d)\\d{2})-(0?[13456789]|1[012])-(0?[1-9]|[12]\\d|30))|(((1[6-9]|[2-9]\\d)\\d{2})-0?2-(0?[1-9]|1\\d|2[0-8]))|(((1[6-9]|[2-9]\\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00))-0?2-29-))$", message = "hiredate内容不正确")
    @Schema(description = "入职日期")
    private String hiredate;
    @NotEmpty(message = "role不能为空")
    @Schema(description = "角色")
    private Integer[] role;
    @NotNull(message = "deptId不能为空")
    @Min(value = 1, message = "deptId不能小于1")
    @Schema(description = "部门")
    private Integer deptId;
}
在 UserController.java 类中,定义 Web 方法。
public class UserController {
    ……
    @PostMapping("/update")
    @SaCheckPermission(value = {"ROOT", "USER:UPDATE"}, mode = SaMode.OR)
    @Operation(summary = "修改用户")
    public R update(@Valid @RequestBody UpdateUserForm form) {
        HashMap param = JSONUtil.parse(form).toBean(HashMap.class);
        param.replace("role", JSONUtil.parseArray(form.getRole()).toString());
        int rows = userService.update(param);
        if (rows == 1) {
            //修改资料后,把该用户踢下线
            StpUtil.logoutByLoginId(form.getUserId());
        }
        return R.ok().put("rows", rows);
    }
}
3.13 删除用户
在用户管理页面,有两个可以删除用户记录的按钮。一个按钮是用来删除某行记录的,另一个按钮是用来删除选中的多条用户记录。因此说我们设计后端程序的时候,无论删除一条还是多条用户记录都能适配。
Persistence Layer
在 TbUserDao.xml 文件中,定义 SQL 语句,仅限于删除非超级管理员的员工记录。
<delete id="deleteUserByIds">
    DELETE FROM tb_user
    WHERE id IN
    <foreach collection="array" open="(" separator="," close=")" item="one">
        #{one}
    </foreach>
        AND root=FALSE
</delete>
在 TbUserDao.java 接口中,声明 DAO 方法。
public interface TbUserDao {
    ……
    public int deleteUserByIds(Integer[] ids);
}
Business Layer
在 UserService.java 接口中,定义抽象方法。
public interface UserService {
    ……
    public int deleteUserByIds(Integer[] ids);
}
在 UserServiceImpl.java 类中,实现抽象方法。
public class UserServiceImpl implements UserService {
    ……
    @Override
    public int deleteUserByIds(Integer[] ids) {
        int rows = userDao.deleteUserByIds(ids);
        return rows;
    }
}
Web Layer
创建 DeleteUserByIdsForm.java 类,保存 Ajax 提交的数据。
@Schema(description = "删除用户表单")
@Data
public class DeleteUserByIdsForm {
    @NotEmpty(message = "ids不能为空")
    @Schema(description = "用户ID")
    private Integer[] ids;
}
在 UserController.java 类中,定义 Web 方法。
public class UserController {
    ……
    @PostMapping("/deleteUserByIds")
    @SaCheckPermission(value = {"ROOT", "USER:DELETE"}, mode = SaMode.OR)
    @Operation(summary = "删除用户")
    public R deleteUserByIds(@Valid @RequestBody DeleteUserByIdsForm form) {
        Integer userId = StpUtil.getLoginIdAsInt();
        if (ArrayUtil.contains(form.getIds(), userId)) {
            return R.error("您不能删除自己的帐户");
        }
        int rows = userService.deleteUserByIds(form.getIds());
        if (rows > 0) {
            //把被删除的用户踢下线
            for (Integer id : form.getIds()) {
                StpUtil.logoutByLoginId(id);
            }
        }
        return R.ok().put("rows", rows);
    }
}
角色管理

4.2 查询角色分页数据
角色管理栏目的截图如下。大家看到了,在 Emos 系统里面存在内置的角色,这些角色是不能删除的,甚至选中删除也不行。超级管理员帐户就更加严格了,既不能删除也不能修改。这是因为超级管理员角色已经拥有最高权限了,没有必要再为它设置权限。

Persistence Layer
在 TbRoleDao.xml 文件中,定义 SQL 用于查询角色分页数据。看上面的截图可知,在角色管理页面上,只有按照角色名字模糊查询。所以在 SQL 语句中,WHERE 子句里面只有一个查询条件。由于在页面表格中要显示每个角色拥有的权限数量,而且 tb_role 表的 permissions 字段是 JSON 数组格式,所以我们统计数组的元素数量,就是该角色拥有的权限数量。恰好 JSON_LENGTH()函数能获取 JSON 数组的长度,所以我就用在 SQL 语句中了。
<select id="searchRoleByPage" parameterType="HashMap" resultType="HashMap">
    SELECT
        r.id,
        r.role_name AS roleName,
        COUNT( u.id ) AS users,
        JSON_LENGTH ( r.permissions ) AS permissions,
        r.`desc`,
        r.systemic
    FROM tb_role r
    LEFT JOIN tb_user u ON JSON_CONTAINS ( u.role, CONVERT ( r.id, CHAR ) )
    WHERE 1=1
    <if test="roleName!=null">
        AND r.role_name LIKE '%${roleName}%'
    </if>
    GROUP BY r.id
    ORDER BY r.id
    LIMIT #{start}, #{length}
</select>
<select id="searchRoleCount" parameterType="HashMap" resultType="long">
    SELECT COUNT(*) FROM (
        SELECT r.id
        FROM tb_role r
        LEFT JOIN tb_user u ON JSON_CONTAINS ( u.role, CONVERT ( r.id, CHAR ) )
        WHERE 1=1
        <if test="roleName!=null">
            AND r.role_name LIKE '%${roleName}%'
        </if>
        GROUP BY r.id
    ) AS temp
</select>
在 TbRoleDao.java 接口中,定义 DAO 方法。
public interface TbRoleDao {
    ……
    public ArrayList<HashMap> searchRoleByPage(HashMap param);
    public long searchRoleCount(HashMap param);
}
Business Layer
在 RoleService.java 接口中,定义抽象方法。
public interface RoleService {
    ……
    public PageUtils searchRoleByPage(HashMap param);
}
在 RoleServiceImpl.java 类中,实现抽象方法。
public class RoleServiceImpl implements RoleService {
    ……
    @Override
    public PageUtils searchRoleByPage(HashMap param) {
        ArrayList<HashMap> list = roleDao.searchRoleByPage(param);
        long count = roleDao.searchRoleCount(param);
        int start = (Integer) param.get("start");
        int length = (Integer) param.get("length");
        PageUtils pageUtils = new PageUtils(list, count, start, length);
        return pageUtils;
    }
}
Web Layer
创建 SearchRoleByPageForm.java 类,用于封装 Ajax 提交的数据
@Data
@Schema(description = "查询角色分页表单")
public class SearchRoleByPageForm {
    @Pattern(regexp = "^[0-9a-zA-Z\\u4e00-\\u9fa5]{1,10}$", message = "roleName内容不正确")
    @Schema(description = "角色名称")
    private String roleName;
    @NotNull(message = "page不能为空")
    @Min(value = 1, message = "page不能小于1")
    @Schema(description = "页数")
    private Integer page;
    @NotNull(message = "length不能为空")
    @Range(min = 10, max = 50, message = "length必须在10~50之间")
    @Schema(description = "每页记录数")
    private Integer length;
}
在 RoleController.java 类中,定义 Web 方法。
public class RoleController {
    ……
    @PostMapping("/searchRoleByPage")
    @Operation(summary = "查询角色分页数据")
    @SaCheckPermission(value = {"ROOT", "ROLE:SELECT"}, mode = SaMode.OR)
    public R searchRoleByPage(@Valid @RequestBody SearchRoleByPageForm form) {
        int page = form.getPage();
        int length = form.getLength();
        int start = (page - 1) * length;
        HashMap param = JSONUtil.parse(form).toBean(HashMap.class);
        param.put("start", start);
        PageUtils pageUtils = roleService.searchRoleByPage(param);
        return R.ok().put("page", pageUtils);
    }
}
4.4 添加新角色
添加新角色是在弹窗页面里面填写角色的信息,其中设置角色关联的权限,用到了 ElementUI 组件库的穿梭框。我们选中的权限,最终是以数组的形式保存到前端页面,提交到后端 Web 方法也是数组格式的权限 ID 值。

Persistence Layer
在 TbRoleDao.xml 文件中,定义插入记录的 INSERT 语句。
<insert id="insert" parameterType="com.example.emos.api.db.pojo.TbRole">
    INSERT INTO tb_role
    SET role_name=#{roleName},
    permissions=#{permissions}
    <if test="desc!=null">
        ,`desc`=#{desc}
    </if>
</insert>
在 TbRoleDao.java 接口中,声明抽象 DAO 方法。
public interface TbRoleDao {
    ……
    public int insert(TbRole role);
}
Business Layer
在 RoleService.java 接口中,定义抽象方法。
public interface RoleService{
    ……
    public int insert(TbRole role);
}
在 RoleServiceImpl.java 类中,实现抽象方法。
public class RoleServiceImpl implements RoleService {
    ……
    @Override
    public int insert(TbRole role) {
        int rows = roleDao.insert(role);
        return rows;
    }
}
Web Layer
创建 InsertRoleForm.java 类,用于封装 Ajax 提交的数据。
@Data
@Schema(description = "添加角色表单")
public class InsertRoleForm {
    @NotBlank(message = "roleName不能为空")
    @Schema(description = "角色名称")
    private String roleName;
    @NotEmpty(message = "permissions不能为空")
    @Schema(description = "权限")
    private Integer[] permissions;
    @Length(max = 20,message = "desc不能超过20个字符")
    @Schema(description = "备注")
    private String desc;
}
在 RoleController.java 类中,定义 Web 方法。利用 Swagger 先登录系统,然后测试 Web 方法。
public class RoleController {
    ……
    @PostMapping("/insert")
    @Operation(summary = "添加角色")
    @SaCheckPermission(value = {"ROOT", "ROLE:INSERT"}, mode = SaMode.OR)
    public R insert(@Valid @RequestBody InsertRoleForm form) {
        TbRole role = new TbRole();
        role.setRoleName(form.getRoleName());
        role.setPermissions(JSONUtil.parseArray(form.getPermissions()).toString());
        role.setDesc(form.getDesc());
        int rows = roleService.insert(role);
        return R.ok().put("rows", rows);
    }
}
4.6 修改角色信息

Persistence Layer
在 TbRoleDao.xml 文件中,定义 SQL 语句。searchUserIdByRoleId 这个 SQL 语句用来查询某个角色关联的用户,因为要把用户踢下线,所以要把这些用户查询出来。另外,因为超级管理员的 id=0,所以 update 这个 SQL 语句的 WHERE id=#{id} AND id!=0 这句话的意思是不能修改超级管理员记录。
<select id="searchUserIdByRoleId" parameterType="int" resultType="int">
    SELECT u.id
    FROM tb_role r
    JOIN tb_user u ON JSON_CONTAINS ( u.role, CONVERT ( r.id, CHAR ) )
    WHERE r.id=#{id}
</select>
<update id="update" parameterType="com.example.emos.api.db.pojo.TbRole">
    UPDATE tb_role
    SET role_name=#{roleName},
        `desc`=#{desc},
        permissions=#{permissions}
    WHERE id=#{id} AND id!=0
</update>
在 TbRoleDao.java 接口中,定义抽象 DAO 方法。
public interface TbRoleDao {
    ……
    public ArrayList<Integer> searchUserIdByRoleId(int roleId);
    public int update(TbRole role);
}
Business Layer
在 RoleService.java 接口中,定义抽象方法。
public interface RoleService {
    ……
    public ArrayList<Integer> searchUserIdByRoleId(int roleId);
    public int update(TbRole role);
}
在 RoleServiceImpl.java 类中,实现抽象方法。
public class RoleServiceImpl implements RoleService {
    ……
    @Override
    public ArrayList<Integer> searchUserIdByRoleId(int roleId) {
        ArrayList<Integer> list = roleDao.searchUserIdByRoleId(roleId);
        return list;
    }
    @Override
    public int update(TbRole role) {
        int rows = roleDao.update(role);
        return rows;
    }
}
Web Layer
创建 UpdateRoleForm.java 类,封装 Ajax 提交的数据。
@Schema(description = "更新角色表单")
@Data
public class UpdateRoleForm {
    @NotNull(message = "id不能为空")
    @Schema(description = "角色ID")
    private Integer id;
    @NotBlank(message = "roleName不能为空")
    @Pattern(regexp = "^[a-zA-Z0-9\\u4e00-\\u9fa5]{2,10}", message = "roleName内容不正确")
    @Schema(description = "角色名称")
    private String roleName;
    @NotEmpty(message = "permissions不能为空")
    @Schema(description = "权限")
    private Integer[] permissions;
    @Length(max = 20, message = "desc不能超过20个字符")
    @Schema(description = "简介")
    private String desc;
    @NotNull(message = "changed不能为空")
    @Schema(description = "权限是否改动了")
    private Boolean changed;
}
在 RoleController.java 类中,定义 Web 方法,然后就可以用 Swagger 测试这个 Web 方法了
public class RoleController {
    ……
    @PostMapping("/update")
    @Operation(summary = "更新角色")
    @SaCheckPermission(value = {"ROOT", "ROLE:UPDATE"}, mode = SaMode.OR)
    public R update(@Valid @RequestBody UpdateRoleForm form) {
        TbRole role = new TbRole();
        role.setId(form.getId());
        role.setRoleName(form.getRoleName());
        role.setPermissions(JSONUtil.parseArray(form.getPermissions()).toString());
        role.setDesc(form.getDesc());
        int rows = roleService.update(role);
        //如果用户修改成功,并且用户修改了该角色的关联权限
        if (rows == 1 && form.getChanged()) {
            //把该角色关联的用户踢下线
            ArrayList<Integer> list = roleService.searchUserIdByRoleId(form.getId());
            list.forEach(userId -> {
                StpUtil.logoutByLoginId(userId);
            });
        }
        return R.ok().put("rows", rows);
    }
}
4.8 删除非内置角色
Persistence Layer
在TbRoleDao.xml文件中,定义两个 SQL 语句。
<select id="searchCanDelete" resultType="boolean">
    SELECT IF( SUM( temp.users ) > 0, FALSE, TRUE ) AS result FROM (
        SELECT COUNT( u.id ) AS users
        FROM tb_role r
        JOIN tb_user u ON JSON_CONTAINS ( u.role, CONVERT ( r.id, CHAR ) )
        WHERE r.id IN
        <foreach collection="array" open="(" separator="," close=")" item="one">
            #{one}
        </foreach>
        GROUP BY r.id
    ) temp
</select>
<delete id="deleteRoleByIds">
    DELETE FROM tb_role
    WHERE id IN
    <foreach collection="array" open="(" separator="," close=")" item="one">
        #{one}
    </foreach>
    AND systemic=FALSE
</delete>
在 TbRoleDao.java 接口中,定义 DAO 方法。
public interface TbRoleDao {
    … …
    public boolean searchCanDelete(Integer[] ids);
    public int deleteRoleByIds(Integer[] ids);
}
Business Layer
在 RoleService.java 接口中,定义抽象方法。
public interface RoleService {
    ……
    public int deleteRoleByIds(Integer[] ids);
}
在 RoleServiceImpl.java 类中,实现抽象方法,其中就有判定角色是否能删除。
public class RoleServiceImpl implements RoleService {
    ……
    @Override
    public int deleteRoleByIds(Integer[] ids) {
        if (!roleDao.searchCanDelete(ids)) {
            throw new EmosException("无法删除关联用户的角色");
        }
        int rows = roleDao.deleteRoleByIds(ids);
        return rows;
    }
}
Web Layer
创建 DeleteRoleByIdsForm.java 类,封装 Ajax 提交的数据。
@Data
@Schema(description = "删除角色表单")
public class DeleteRoleByIdsForm {
    @NotEmpty(message = "ids不能为空")
    @Schema(description = "角色ID")
    private Integer[] ids;
}
在 RoleController.java 类中,声明 Web 方法,然后大家可以用 Swagger 测试一下。
public class RoleController {
    ……
    @PostMapping("/deleteRoleByIds")
    @Operation(summary = "删除角色记录")
    @SaCheckPermission(value = {"ROOT", "ROLE:DELETE"}, mode = SaMode.OR)
    public R deleteRoleByIds(@Valid @RequestBody DeleteRoleByIdsForm form) {
        int rows = roleService.deleteRoleByIds(form.getIds());
        return R.ok().put("rows", rows);
    }
}
部门管理

5.2 查询部门分页数据
Persistence Layer
在 TbDeptDao.xml 文件中,定义 SQL 用于查询部门分页数据。看上面的截图可知,在部门管理页面上,只有按照部门名字模糊查询。所以在 SQL 语句中,WHERE 子句里面只有一个查询条件。由于在页面表格中要显示每个部门拥有的员工数量,所以用了 COUNT()汇总函数。
<select id="searchDeptByPage" parameterType="HashMap" resultType="HashMap">
    SELECT d.id,
    d.dept_name AS deptName,
    d.tel,
    d.email,
    d.desc,
    COUNT(u.id) AS emps
    FROM tb_dept d LEFT JOIN tb_user u ON u.dept_id=d.id AND u.status=1
    WHERE 1=1
    <if test="deptName!=null">
        AND d.dept_name LIKE '%${deptName}%'
    </if>
    GROUP BY d.id
    LIMIT #{start}, #{length}
</select>
<select id="searchDeptCount" parameterType="HashMap" resultType="long">
    SELECT COUNT(*) FROM (
    SELECT d.id
    FROM tb_dept d LEFT JOIN tb_user u ON u.dept_id=d.id AND u.status=1
    WHERE 1=1
    <if test="deptName!=null">
        AND d.dept_name LIKE '%${deptName}%'
    </if>
    GROUP BY d.id
    ) AS temp
</select>
在 TbDeptDao.java 接口中,声明 DAO 方法。
public interface TbDeptDao {
    ……
    public ArrayList<HashMap> searchDeptByPage(HashMap param);
    public long searchDeptCount(HashMap param);
}
Business Layer
在 DeptService.java 接口中,声明抽象方法。
public interface DeptService {
    ……
    public PageUtils searchDeptByPage(HashMap param);
}
在 DeptServiceImpl.java 类中,实现  抽象方法。
public class DeptServiceImpl implements DeptService {
    ……
    @Override
    public PageUtils searchDeptByPage(HashMap param) {
        ArrayList<HashMap> list = deptDao.searchDeptByPage(param);
        long count = deptDao.searchDeptCount(param);
        int start = (Integer) param.get("start");
        int length = (Integer) param.get("length");
        PageUtils pageUtils = new PageUtils(list, count, start, length);
        return pageUtils;
    }
}
Web Layer
创建 SearchDeptByPageForm.java 类,保存 Ajax 提交的数据。
@Data
@Schema(description = "查询部门分页表单")
public class SearchDeptByPageForm {
    @Pattern(regexp = "^[0-9a-zA-Z\\u4e00-\\u9fa5]{1,10}$", message = "deptName内容不正确")
    @Schema(description = "部门名称")
    private String deptName;
    @NotNull(message = "page不能为空")
    @Min(value = 1, message = "page不能小于1")
    @Schema(description = "页数")
    private Integer page;
    @NotNull(message = "length不能为空")
    @Range(min = 10, max = 50, message = "length必须为10~50之间")
    @Schema(description = "每页记录数")
    private Integer length;
}
在 DeptController.java 类中,定义 Web 方法,然后大家就可以在 Swagger 页面测试 Web 方法了。
public class DeptController {
	……
    @PostMapping("/searchDeptByPage")
    @Operation(summary = "查询部门分页数据")
    @SaCheckPermission(value = {"ROOT", "DEPT:SELECT"}, mode = SaMode.OR)
    public R searchDeptByPage(@Valid @RequestBody SearchDeptByPageForm form) {
        int page = form.getPage();
        int length = form.getLength();
        int start = (page - 1) * length;
        HashMap param = JSONUtil.parse(form).toBean(HashMap.class);
        param.put("start", start);
        PageUtils pageUtils = deptService.searchDeptByPage(param);
        return R.ok().put("page", pageUtils);
    }
}
5.4 添加新部门
Persistence Layer
在 TbDeptDao.xml 文件中,定义 SQL 语句
<insert id="insert" parameterType="com.example.emos.api.db.pojo.TbDept">
    INSERT INTO tb_dept
    SET dept_name=#{deptName}
    <if test="tel!=null">
        ,tel=#{tel}
    </if>
    <if test="email!=null">
        ,email=#{email}
    </if>
    <if test="desc!=null">
        ,`desc`=#{desc}
    </if>
</insert>
在 TbDeptDao.java 接口中,声明抽象 DAO 方法。
public interface TbDeptDao {
    ……
    public int insert(TbDept dept);
}
Business Layer
在 DeptService.java 接口中,定义抽象方法。
public interface DeptService {
    ……
    public int insert(TbDept dept);
}
在 DeptServiceImpl.java 类中,实现抽象方法。
public class DeptServiceImpl implements DeptService {
    ……
    @Override
    public int insert(TbDept dept) {
        int rows = deptDao.insert(dept);
        return rows;
    }
}
Web Layer
创建 InsertDeptForm.java 类,用于封装 Ajax 提交的数据。
@Data
@Schema(description = "添加部门表单")
public class InsertDeptForm {
    @NotBlank(message = "deptName不能为空")
    @Schema(description = "部门名称")
    private String deptName;
    @Pattern(regexp = "^1\\d{10}$|^(0\\d{2,3}\\-){0,1}[1-9]\\d{6,7}$",message = "tel内容错误")
    @Schema(description = "电话")
    private String tel;
    @Email(message = "email不正确")
    @Schema(description = "邮箱")
    private String email;
    @Length(max = 20,message = "desc不能超过20个字符")
    @Schema(description = "备注")
    private String desc;
}
在 DeptController.java 类中,定义 Web 方法。利用 Swagger 先登录系统,然后测试 Web 方法。
public class DeptController {
    ……
    @PostMapping("/insert")
    @Operation(summary = "添加部门")
    @SaCheckPermission(value = {"ROOT", "DEPT:INSERT"}, mode = SaMode.OR)
    public R insert(@Valid @RequestBody InsertDeptForm form) {
        TbDept dept = JSONUtil.parse(form).toBean(TbDept.class);
        int rows = deptService.insert(dept);
        return R.ok().put("rows", rows);
    }
}
5.6 修改部门信息
Persistence Layer
在 TbDeptDao.xml 文件中,定义 SQL 语句。
<update id="update" parameterType="com.example.emos.api.db.pojo.TbDept">
    UPDATE tb_dept
    SET dept_name=#{deptName},
        tel=#{tel},
        email=#{email},
        `desc`=#{desc}
    WHERE id=#{id}
</update>
在 TbDeptDao.java 接口中,定义抽象 DAO 方法。
public interface TbDeptDao {
    ……
    public int update(TbDept dept);
}
Business Layer
在 DeptService.java 接口中,定义抽象方法。
public interface DeptService {
    ……
    public int update(TbDept dept);
}
在 DeptServiceImpl.java 类中,实现抽象方法。
public class DeptServiceImpl implements DeptService {
    ……
    @Override
    public int update(TbDept dept) {
        int rows=deptDao.update(dept);
        return rows;
    }
}
Web Layer
创建 UpdateDeptForm.java 类,封装 Ajax 提交的数据。
@Schema(description = "更新部门表单")
@Data
public class UpdateDeptForm {
    @NotNull(message = "id不能为空")
    @Schema(description = "部门ID")
    private Integer id;
    @NotBlank(message = "deptName不能为空")
    @Schema(description = "部门名称")
    private String deptName;
    @Pattern(regexp = "^1\\d{10}$|^(0\\d{2,3}\\-){0,1}[1-9]\\d{6,7}$",message = "tel内容不正确")
    @Schema(description = "电话")
    private String tel;
    @Email(message = "email内容不正确")
    @Schema(description = "邮箱")
    private String email;
    @Schema(description = "备注")
    @Length(max = 20,message = "desc不能超过20个字符")
    private String desc;
}
在 DeptController.java 类中,定义 Web 方法,然后就可以用 Swagger 测试这个 Web 方法了。
public class DeptController {
    ……
    @PostMapping("/update")
    @Operation(summary = "更新部门")
    @SaCheckPermission(value = {"ROOT", "DEPT:UPDATE"}, mode = SaMode.OR)
    public R update(@Valid @RequestBody UpdateDeptForm form) {
        TbDept dept = new TbDept();
        dept.setId(form.getId());
        dept.setDeptName(form.getDeptName());
        dept.setTel(form.getTel());
        dept.setEmail(form.getEmail());
        dept.setDesc(form.getDesc());
        int rows = deptService.update(dept);
        return R.ok().put("rows", rows);
    }
}
5.8 删除无用户的部门
Persistence Layer
在 TbDeptDao.xml 文件中,定义两个 SQL 语句。searchCanDelete 这个语句是查询要删除的  部门是否存在关联的用户,这个 SQL 语句的语法跟角色管理模块类似,没什么可讲的。
<select id="searchCanDelete" resultType="boolean">
    SELECT IF( SUM( temp.users ) > 0, FALSE, TRUE ) AS result FROM (
    SELECT COUNT( u.id ) AS users
    FROM tb_dept d
    JOIN tb_user u ON d.id=u.dept_id
    WHERE d.id IN
    <foreach collection="array" open="(" separator="," close=")" item="one">
        #{one}
    </foreach>
    GROUP BY d.id
    ) temp
</select>
<delete id="deleteDeptByIds">
    DELETE FROM tb_dept
    WHERE id IN
    <foreach collection="array" open="(" separator="," close=")" item="one">
        #{one}
    </foreach>
</delete>
在 TbDeptDao.java 接口中,定义 DAO 方法。
public interface TbDeptDao {
    ……
    public boolean searchCanDelete(Integer[] ids);
    public int deleteDeptByIds(Integer[] ids);
}
Business Layer
在 DeptService.java 接口中,定义抽象方法。
public interface DeptService {
    ……
    public int deleteDeptByIds(Integer[] ids);
}
在 DeptServiceImpl.java 类中,实现抽象方法,其中就有判定部门是否能删除。
public class DeptServiceImpl implements DeptService {
    ……
    @Override
    public int deleteDeptByIds(Integer[] ids) {
        if (!deptDao.searchCanDelete(ids)) {
            throw new EmosException("无法删除关联用户的部门");
        }
        int rows = deptDao.deleteDeptByIds(ids);
        return rows;
    }
}
Web Layer
创建 DeleteDeptByIdsForm.java 类,封装 Ajax 提交的数据。
@Data
@Schema(description = "删除部门表单")
public class DeleteDeptByIdsForm {
    @NotEmpty(message = "ids不能为空")
    @Schema(description = "部门ID")
    private Integer[] ids;
}
在 DeptController.java 类中,声明 Web 方法,然后大家可以用 Swagger 测试一下。
public class DeptController {
    ……
    @PostMapping("/deleteDeptByIds")
    @Operation(summary = "删除部门记录")
    @SaCheckPermission(value = {"ROOT", "DEPT:DELETE"}, mode = SaMode.OR)
    public R deleteDeptByIds(@Valid @RequestBody DeleteDeptByIdsForm form) {
        int rows = deptService.deleteDeptByIds(form.getIds());
        return R.ok().put("rows", rows);
    }
}
会议室管理
会议室模块是为线下会议服务的,只有在 Emos 系统中创建线下会议室记录,然后用户 才能选中会议室申请线下会议。
6.2 查询会议室分页数据
我们去 TbMeetingRoomDao.xml 文件中,定义 SQL 语句。
<select id="searchMeetingRoomByPage" parameterType="HashMap" resultType="HashMap">
    SELECT mr.id,
           mr.`name`,
           mr.max,
           mr.`desc`,
           mr.`status`
    FROM tb_meeting_room mr
    LEFT JOIN tb_meeting m ON m.type=2 AND mr.`name`=m.place
    <if test="name!=null">
        WHERE mr.name LIKE '%${name}%'
    </if>
    GROUP BY mr.id
    <if test="canDelete==false">
        HAVING COUNT(m.id) > 0
    </if>
    <if test="canDelete==true">
        HAVING COUNT(m.id) = 0
    </if>
    LIMIT #{start}, #{length}
</select>
<select id="searchMeetingRoomCount" parameterType="HashMap" resultType="long">
    SELECT COUNT(*) FROM (
        SELECT mr.id
        FROM tb_meeting_room mr
        LEFT JOIN tb_meeting m ON m.type=2 AND mr.`name`=m.place
        <if test="name!=null">
            WHERE mr.name LIKE '%${name}%'
        </if>
        GROUP BY mr.id
        <if test="canDelete==false">
            HAVING COUNT(m.id) > 0
        </if>
        <if test="canDelete==true">
            HAVING COUNT(m.id) = 0
        </if>
    ) AS temp
</select>
在 TbMeetingRooDao.java 中,声明 DAO 方法。
public interface TbMeetingRoomDao {
    ……
    public ArrayList<HashMap> searchMeetingRoomByPage(HashMap param);
    public long searchMeetingRoomCount(HashMap param);
}
Business Layer
在 MeetingRoomService.java 接口中,声明抽象方法。
public interface MeetingRoomService {
    ……
    public PageUtils searchMeetingRoomByPage(HashMap param);
}
在 DeptServiceImpl.java 类中,实现抽象方法。
public class MeetingRoomServiceImpl implements MeetingRoomService {
    ……
    @Override
    public PageUtils searchMeetingRoomByPage(HashMap param) {
        ArrayList<HashMap> list = meetingRoomDao.searchMeetingRoomByPage(param);
        long count = meetingRoomDao.searchMeetingRoomCount(param);
        int start = (Integer) param.get("start");
        int length = (Integer) param.get("length");
        PageUtils pageUtils = new PageUtils(list, count, start, length);
        return pageUtils;
    }
}
Web Layer
创建 SearchMeetingRoomByPageForm.java 类,保存 Ajax 提交的数据。
@Data
@Schema(description = "查询会议室  分页表单")
public class SearchMeetingRoomByPageForm {
    @Pattern(regexp = "^[0-9a-zA-Z\\u4e00-\\u9fa5]{1,20}$", message = "name内容不正确")
    @Schema(description = "会议室名称")
    private String name;
    @Schema(description = "是否可以删除")
    private Boolean canDelete;
    @NotNull(message = "page不能为空")
    @Min(value = 1, message = "page不能小于1")
    @Schema(description = "页数")
    private Integer page;
    @NotNull(message = "length不能为空")
    @Range(min = 10, max = 50, message = "length必须在10~50之间")
    @Schema(description = "每页记录数")
    private Integer length;
}
在 MeetingRoomController.java 类中,定义 Web 方法,然后大家就可以在 Swagger 页面测试 Web 方法了。
public class MeetingRoomController {
    ……
    @PostMapping("/searchMeetingRoomByPage")
    @Operation(summary = "查询会议室分页数据")
    @SaCheckLogin
    public R searchMeetingRoomByPage(@Valid @RequestBody SearchMeetingRoomByPageForm form) {
        int page = form.getPage();
        int length = form.getLength();
        int start = (page - 1) * length;
        HashMap param = JSONUtil.parse(form).toBean(HashMap.class);
        param.put("start", start);
        PageUtils pageUtils = meetingRoomService.searchMeetingRoomByPage(param);
        return R.ok().put("page", pageUtils);
    }
}
6.4 添加新会议室
Persistence Layer
在 TbMeetingRoomDao.xml 文件中,定义 SQL 语句。
<insert id="insert" parameterType="com.example.emos.api.db.pojo.TbMeetingRoom">
    INSERT INTO tb_meeting_room
    SET name=#{name},
    max=#{max}
    <if test="desc!=null">
        ,`desc`=#{desc}
    </if>
    <if test="status!=null">
        ,status=#{status}
    </if>
</insert>
在 TbMeetingRoomDao.java 接口中,声明抽象 DAO 方法。
public interface TbMeetingRoomDao {
    ……
    public int insert(TbMeetingRoom meetingRoom);
}
Business Layer
在 MeetingRoomService.java 接口中,定义抽象方法。
public interface MeetingRoomService {
    ……
    public int insert(TbMeetingRoom meetingRoom);
}
在 MeetingRoomServiceImpl.java 类中,实现抽象方法。
public class MeetingRoomServiceImpl implements MeetingRoomService {
    ……
    @Override
    public int insert(TbMeetingRoom meetingRoom) {
        int rows = meetingRoomDao.insert(meetingRoom);
        return rows;
    }
}
Web Layer
创建 InsertMeetingRoomForm.java 类,用于封装 Ajax 提交的数据。
@Data
@Schema(description = "添加会议室表单")
public class InsertMeetingRoomForm {
    @NotBlank(message = "name不能为空")
    @Pattern(regexp = "^[a-zA-Z0-9\\u4e00-\\u9fa5]{2,20}$",message = "name内容不正确")
    @Schema(description = "会议室名称")
    private String name;
    @NotNull(message = "max不能为空")
    @Range(min = 1, max = 99999,message = "max必须在1~99999之间")
    @Schema(description = "人数上限")
    private String max;
    @Length(max = 20,message = "desc不能超过20个字符")
    @Schema(description = "备注")
    private String desc;
    @Schema(description = "状态")
    private Boolean status;
}
在 MeetingRoomController.java 类中,定义 Web 方法。利用 Swagger 先登录系统,然后测试 Web 方法。
public class MeetingRoomController {
    ……
    @PostMapping("/insert")
    @Operation(summary = "添加会议室")
    @SaCheckPermission(value = {"ROOT", "MEETING_ROOM:INSERT"}, mode = SaMode.OR)
    public R insert(@Valid @RequestBody InsertMeetingRoomForm form) {
        TbMeetingRoom meetingRoom = JSONUtil.parse(form).toBean(TbMeetingRoom.class);
        int rows = meetingRoomService.insert(meetingRoom);
        return R.ok().put("rows", rows);
    }
}