当【添加管理员】时,必须在操作界面上提供一个可以选择【角色】的控件,且添加时必须确定某些角色,从而,新添加的管理员才会因为选择的角色而关联到某些权限,管理员才会具有某些操作权限!
关于选择【角色】,需要将当前系统中的【角色列表】显示在操作界面上!
关于Mapper层(数据访问层 / DAO(Data Access Object)层 / 持久层)
需要执行的SQL语句大致是:
SELECT id, name, description, sort FROM ams_role ORDER BY sort DESC, id
在根包下创建pojo.vo.RoleListItemVO
类:
package cn.tedu.csmall.passport.pojo.vo;
import lombok.Data;
import java.io.Serializable;
@Data
public class RoleListItemVO implements Serializable {
private Long id;
private String name;
private String description;
private Integer sort;
}
在根包下创建mapper.RoleMapper
接口,并添加抽象方法:
package cn.tedu.csmall.passport.mapper;
import cn.tedu.csmall.passport.pojo.vo.RoleListItemVO;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface RoleMapper {
List<RoleListItemVO> list();
}
在src/main/resources
下的mapper
文件夹中粘贴得到RoleMapper.xml
,并在此文件中配置以上抽象方法映射的SQL语句:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.tedu.csmall.passport.mapper.RoleMapper">
<select id="list" resultMap="ListResultMap">
SELECT
<include refid="ListQueryFields" />
FROM
ams_role
ORDER BY
sort DESC, id
</select>
<sql id="ListQueryFields">
<if test="true">
id, name, description, sort
</if>
</sql>
<resultMap id="ListResultMap" type="cn.tedu.csmall.passport.pojo.vo.RoleListItemVO">
<id column="id" property="id" />
<result column="name" property="name" />
<result column="description" property="description" />
<result column="sort" property="sort" />
</resultMap>
</mapper>
在src/test/java
下的根包下创建mapper.RoleMapperTests
测试类,对以上方法进行测试:
package cn.tedu.csmall.passport.mapper;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@Slf4j
@SpringBootTest
public class RoleMapperTests {
@Autowired
RoleMapper mapper;
@Test
void testList() {
List<?> list = mapper.list();
log.debug("查询列表完成,结果集的数量={}", list.size());
for (Object item : list) {
log.debug("{}", item);
}
}
}
关于Service层(业务逻辑层 / 业务层)
在根包下创建service.IRoleService
接口,并在接口中添加抽象方法:
package cn.tedu.csmall.passport.service;
import cn.tedu.csmall.passport.pojo.vo.RoleListItemVO;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Transactional
public interface IRoleService {
List<RoleListItemVO> list();
}
在根包下创建service.impl.RoleServiceImpl
类,实现以上接口,并实现抽象方法:
package cn.tedu.csmall.passport.service.impl;
import cn.tedu.csmall.passport.mapper.RoleMapper;
import cn.tedu.csmall.passport.pojo.vo.RoleListItemVO;
import cn.tedu.csmall.passport.service.IRoleService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Slf4j
@Service
public class RoleServiceImpl implements IRoleService {
@Autowired
private RoleMapper roleMapper;
public RoleServiceImpl() {
log.debug("创建业务对象:RoleServiceImpl");
}
@Override
public List<RoleListItemVO> list() {
log.debug("开始处理【查询角色列表】的业务");
return roleMapper.list();
}
}
在src/test/java
下的根包下创建service.RoleServiceTests
测试类,对以上方法进行测试:
package cn.tedu.csmall.passport.service;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@Slf4j
@SpringBootTest
public class RoleServiceTests {
@Autowired
IRoleService service;
@Test
void testList() {
List<?> list = service.list();
log.debug("查询列表完成,结果集的数量={}", list.size());
for (Object item : list) {
log.debug("{}", item);
}
}
}
关于Controller层(控制器层)
在根包下创建RoleController
,并处理请求:
package cn.tedu.csmall.passport.controller;
import cn.tedu.csmall.passport.pojo.vo.RoleListItemVO;
import cn.tedu.csmall.passport.service.IRoleService;
import cn.tedu.csmall.passport.web.JsonResult;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@Slf4j
@RestController
@RequestMapping("/roles")
@Api(tags = "02. 角色管理模块")
public class RoleController {
@Autowired
private IRoleService roleService;
public RoleController() {
log.info("创建控制器:RoleController");
}
@GetMapping("")
@ApiOperation("查询角色列表")
@ApiOperationSupport(order = 410)
public JsonResult<List<RoleListItemVO>> list() {
log.debug("开始处理【查询角色列表】的请求");
List<RoleListItemVO> list = roleService.list();
return JsonResult.ok(list);
}
}
完成后,启动项目,可以通过Knife4j的调试功能进行测试,注意:需要携带有效的JWT!
另外,如果响应结果时,不希望包含为null
的属性,可以在application.properties
中补充配置:
# 响应结果中,不显示为null的属性
spring.jackson.default-property-inclusion=non_null
在使用qs
将对象转换成FormData格式的字符串时,如果原对象中有数组,默认情况下,数组数据会被转换成如下格式:
roleIds%5B0%5D=1&roleIds%5B1%5D=2
以上代码中,%5B
表示[
,%5D
表示]
。
可以在使用qs
时进行配置,以设置数组数据的格式,例如:
let formData = this.qs.stringify(this.ruleForm, {'arrayFormat': 'repeat'});
当arrayFormat
属性的值为repeat
时,FormData格式为:
roleIds=1&roleIds=2&roleIds=3
当arrayFormat
属性的值为indices
时,FormData格式为:
roleIds%5B0%5D=1&roleIds%5B1%5D=2
当arrayFormat
属性的值为brackets
时,FormData格式为:
roleIds%5B%5D=1&roleIds%5B%5D=2
其实,无论使用以上哪种方式,Spring MVC框架都可以正常接收到数组值!
关于Mapper层
在服务器端,添加管理员时,需要添加管理员与角色的关联,否则,管理员没有关联角色,也就没有权限了!
需要执行的SQL语句大致是(每个管理员可以关联多个角色):
INSERT INTO ams_admin_role (admin_id, role_id) VALUES (?,?), (?,?), ... (?,?);
在根包下创建pojo.entity.AdminRole
实体类:
package cn.tedu.csmall.passport.pojo.entity;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
@Data
public class AdminRole implements Serializable {
private Long id;
private Long adminId;
private Long roleId;
private LocalDateTime gmtCreate;
private LocalDateTime gmtModified;
}
在根包下创建mapper.AdminRoleMapper
接口,并添加抽象方法:
package cn.tedu.csmall.passport.mapper;
import cn.tedu.csmall.passport.pojo.entity.AdminRole;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface AdminRoleMapper {
int insertBatch(List<AdminRole> adminRoleList);
}
在src/main/resources
下的mapper
文件夹中粘贴得到AdminRoleMapper.xml
文件,并配置以上抽象方法映射的SQL语句:
<!-- int insertBatch(List<AdminRole> adminRoleList); -->
<insert id="insertBatch" useGeneratedKeys="true" keyProperty="id">
INSERT INTO ams_admin_role (
admin_id, role_id, gmt_create, gmt_modified
) VALUES
<foreach collection="list" item="adminRole" separator=",">
(#{adminRole.adminId}, #{adminRole.roleId},
#{adminRole.gmtCreate}, #{adminRole.gmtModified})
</foreach>
</insert>
完成后,在src/test/java
下的根包下创建mapper.AdminRoleMapperTests
测试类,对以上功能进行测试:
package cn.tedu.csmall.passport.mapper;
import cn.tedu.csmall.passport.pojo.entity.AdminRole;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Slf4j
@SpringBootTest
public class AdminRoleMapperTests {
@Autowired
AdminRoleMapper mapper;
@Test
void testInsertBatch() {
LocalDateTime now = LocalDateTime.now();
List<AdminRole> adminRoleList = new ArrayList<>();
for (int i = 1; i <= 3; i++) {
AdminRole adminRole = new AdminRole();
adminRole.setAdminId(5L);
adminRole.setRoleId(i + 0L);
adminRole.setGmtCreate(now);
adminRole.setGmtModified(now);
adminRoleList.add(adminRole);
}
int rows = mapper.insertBatch(adminRoleList);
log.debug("批量插入管理员与角色的关联关系数据成功,受影响的行数={}", rows);
}
}
关于Service层
由于此前已经实现了“增加管理员”的业务,只是尚未实现“插入管理员与角色的关联关系”,所以,只需要在原有代码基础上调整即可,并不需要创建新的接口、实现类!
原有代码中,关于“增加管理员”的业务方法的声明是:
void addNew(AdminAddNewDTO adminAddNewDTO);
以上方法中,参数AdminAddNewDTO
中并不包含若干个角色id,所以,需要先补充声明此属性:
@Data
public class AdminAddNewDTO implements Serializable {
// 省略原有代码,以下是新增的属性
/**
* 管理员的角色
*/
private Long[] roleIds;
}
然后,在AdminServiceImpl
中,先自动装配AdminRoleMapper
对象:
@Autowired
private AdminRoleMapper adminRoleMapper;
然后,在原有的addNew()
方法的最后补充:
// 插入管理员与角色的关联关系
log.debug("准备插入管理员与角色的关联关系");
LocalDateTime now = LocalDateTime.now();
Long[] roleIds = adminAddNewDTO.getRoleIds();
List<AdminRole> adminRoleList = new ArrayList<>();
for (int i = 0; i < roleIds.length; i++) {
AdminRole adminRole = new AdminRole();
adminRole.setAdminId(admin.getId());
adminRole.setRoleId(roleIds[i]);
adminRole.setGmtCreate(now);
adminRole.setGmtModified(now);
adminRoleList.add(adminRole);
}
rows = adminRoleMapper.insertBatch(adminRoleList);
if (rows != roleIds.length) {
// 是:抛出ServiceException(ERR_INSERT)
String message = "添加管理员失败,服务器忙,请稍后再尝试![错误代码:2]";
log.warn(message);
throw new ServiceException(ServiceCode.ERR_INSERT, message);
}
log.debug("添加管理员完成!");
完成后,可以通过AdminServiceTests
中原有的测试方法进行测试,也可以在重启项目后,通过前端页面来添加管理员,都会是成功的,此功能开发至此就已经完成!
客户端提交的角色id是Long[] roleIds
,可以通过WHERE id IN (?,?)
作为条件的查询进行检查,需要执行的SQL语句大致是:
SELECT count(*) FROM ams_role WHERE id IN (?,?, ... ?);
在RoleMapper.java
接口中补充抽象方法:
int countByIds(Long[] ids);
在RoleMapper.xml
中配置以上抽象方法映射的SQL语句:
<!-- int countByIds(Long[] ids); -->
<select id="countByIds" resultType="int">
SELECT count(*) FROM ams_role WHERE id IN (
<foreach collection="array" item="id" separator=",">
#{id}
</foreach>
)
</select>
在RoleMapperTests
中编写并执行测试:
@Test
void testCountByIds() {
Long[] ids = {1L,2L,3L,7L,9L};
int count = mapper.countByIds(ids);
log.debug("根据id({})进行统计,统计结果={}", Arrays.toString(ids), count);
}
最后,在AdminServiceImpl
的addNew()
方法,在插入管理员与角色关联之前,补充:
// 需要补充:自动装配RoleMapper对象
// 检查各角色id是否存在
Long[] roleIds = adminAddNewDTO.getRoleIds(); // 从原下方代码中移动到此处
int countByIds = roleMapper.countByIds(roleIds);
if (countByIds != roleIds.length) {
// 是:抛出ServiceException(ERR_BAD_REQUEST)
String message = "添加管理员失败,角色数据错误!";
log.warn(message);
throw new ServiceException(ServiceCode.ERR_BAD_REQUEST, message);
}
当删除管理员时,对应的“管理员与角色的关联”数据也需要一并删除!
关于Mapper层
需要执行的SQL语句大致是:
DELETE FROM ams_admin_role WHERE admin_id=?
在AdminRoleMapper.java
接口中添加抽象方法:
int deleteByAdmin(Long adminId);
在AdminMapper.xml
中配置SQL:
<!-- int deleteByAdmin(Long adminId); -->
<delete id="deleteByAdmin">
DELETE FROM ams_admin_role WHERE admin_id=#{adminId}
</delete>
在AdminMapperTests
中编写并执行测试:
@Test
void testDeleteByAdmin() {
Long adminId = 17L;
int rows = mapper.deleteByAdmin(adminId);
log.debug("根据管理员【id={}】删除关联数据,受影响的行数={}", adminId, rows);
}
关于Service层
需要在现有的“删除管理员”的业务中,补充“从ams_admin_role
表中删除此管理员的关联数据”。
在AdminServiceImpl
类中的deleteById()
方法的最后补充:
// 删除“管理员与角色”的关联数据
rows = adminRoleMapper.deleteByAdmin(id);
// 判断返回值是否小于1
if (rows < 1) {
// 抛出ServiceException,业务状态码:DELETE对应的常量
String message = "删除管理员失败!服务器忙,请稍后再次尝试![错误代码:2]";
log.warn(message);
throw new ServiceException(ServiceCode.ERR_DELETE, message);
}
完成:通过前端页面的操作,实现“启用”、“禁用”管理员账号。