csmall-passport(Day15)

文嘉禧
2023-12-01

1. 显示角色列表

当【添加管理员】时,必须在操作界面上提供一个可以选择【角色】的控件,且添加时必须确定某些角色,从而,新添加的管理员才会因为选择的角色而关联到某些权限,管理员才会具有某些操作权限!

关于选择【角色】,需要将当前系统中的【角色列表】显示在操作界面上!

关于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

2. 关于客户端提交数组格式的数据

在使用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框架都可以正常接收到数组值!

3. 调整:添加管理员时关联角色

关于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中原有的测试方法进行测试,也可以在重启项目后,通过前端页面来添加管理员,都会是成功的,此功能开发至此就已经完成!

4. 修复:增加管理员时,检查角色id是否存在

客户端提交的角色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);
}

最后,在AdminServiceImpladdNew()方法,在插入管理员与角色关联之前,补充:

// 需要补充:自动装配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);
}

4. 修复:删除管理员时,需要删除关联数据

当删除管理员时,对应的“管理员与角色的关联”数据也需要一并删除!

关于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);
}

作业

完成:通过前端页面的操作,实现“启用”、“禁用”管理员账号。

 类似资料: