在登录时,可能出现:
BadCredentialsExceptionBadCredentialsExceptionDisabledException在访问时,可能出现:
AccessDeniedException以上异常都可以由统一处理异常的机制进行处理,则先在ServiceCode中添加对应的业务状态码:
- /**
- * 未授权的访问
- */
- Integer ERR_UNAUTHORIZED = 40100;
- /**
- * 未授权的访问:账号禁用
- */
- Integer ERR_UNAUTHORIZED_DISABLED = 40101;
- /**
- * 禁止访问,通常是已登录,但无权限
- */
- Integer ERR_FORBIDDEN = 40300;
然后,在统一处理异常的类中,添加对相关异常的处理:
- @ExceptionHandler
- public JsonResult
handleBadCredentialsException(BadCredentialsException e) { - String message = "登录失败,用户名或密码错误!";
- log.debug("处理BadCredentialsException:{}", message);
- return JsonResult.fail(ServiceCode.ERR_UNAUTHORIZED, message);
- }
-
- @ExceptionHandler
- public JsonResult
handleDisabledException(DisabledException e) { - String message = "登录失败,此账号已禁用!";
- log.debug("处理DisabledException:{}", message);
- return JsonResult.fail(ServiceCode.ERR_UNAUTHORIZED_DISABLED, message);
- }
-
- @ExceptionHandler
- public JsonResult
handleAccessDeniedException(AccessDeniedException e) { - String message = "访问失败,当前登录的账号无此权限!";
- log.debug("处理AccessDeniedException:{}", message);
- return JsonResult.fail(ServiceCode.ERR_FORBIDDEN, message);
- }
另外,在解析JWT的过程中,也可能出现异常,由于解析JWT是在过滤器中进行的,如果出现异常,不会被统一处理异常的机制获取得到(因为过滤器执行的时间点太早),所以,只能在过滤器中自行处理异常,例如:
- // 尝试解析JWT
- Claims claims = null;
- try {
- claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();
- } catch (MalformedJwtException e) {
- log.warn("解析JWT失败:{}:{}", e.getClass().getName(), e.getMessage());
- JsonResult
jsonResult = JsonResult.fail( - ServiceCode.ERR_JWT_PARSE, "无法获取到有效的登录信息,请重新登录!");
- String jsonResultString = JSON.toJSONString(jsonResult);
- PrintWriter writer = response.getWriter();
- writer.println(jsonResultString);
- writer.close();
- return;
- } catch (SignatureException e) {
- log.warn("解析JWT失败:{}:{}", e.getClass().getName(), e.getMessage());
- JsonResult
jsonResult = JsonResult.fail( - ServiceCode.ERR_JWT_PARSE, "无法获取到有效的登录信息,请重新登录!");
- String jsonResultString = JSON.toJSONString(jsonResult);
- PrintWriter writer = response.getWriter();
- writer.println(jsonResultString);
- writer.close();
- return;
- } catch (ExpiredJwtException e) {
- log.warn("解析JWT失败:{}:{}", e.getClass().getName(), e.getMessage());
- JsonResult
jsonResult = JsonResult.fail( - ServiceCode.ERR_JWT_EXPIRED, "登录信息已过期,请重新登录!");
- String jsonResultString = JSON.toJSONString(jsonResult);
- PrintWriter writer = response.getWriter();
- writer.println(jsonResultString);
- writer.close();
- return;
- } catch (Throwable e) {
- log.warn("解析JWT失败:{}:{}", e.getClass().getName(), e.getMessage());
- JsonResult
jsonResult = JsonResult.fail( - ServiceCode.ERR_JWT_PARSE, "无法获取到有效的登录信息,请重新登录!");
- String jsonResultString = JSON.toJSONString(jsonResult);
- PrintWriter writer = response.getWriter();
- writer.println(jsonResultString);
- writer.close();
- return;
- }
目前,当执行添加管理员时,并没有处理管理员的“角色”,以至于这些新添加的管理员没有权限!所以,添加管理员时,必须为管理员分配角色!
需要执行的任务:
AdminAddNewDTO,增加Long[]属性,用于表示客户端选择的若干种角色的id,使得Controller能接收客户端在“添加管理员”时选择的若干种角色首先,实现Mapper层,需要执行的SQL语句大致是:
insert into ams_admin_role (admin_id, role_id) values (?,?), (?,?) ... (?,?);
在根包下创建mapper.AdminRoleMapper接口(如果不存在,则创建,如果已存在,不需要重复创建),并在接口中添加抽象方法:
int insertBatch(AdminRole[] adminRoleList);
在AdminRoleMapper.xml中配置以上抽象方法映射的SQL语句:
- <insert id="insertBatch" useGeneratedKeys="true" keyProperty="id">
- INSERT INTO ams_admin_role
- (admin_id, role_id, gmt_create, gmt_modified)
- VALUES
- <foreach collection="array" item="adminRole" separator=",">
- (#{adminRole.adminId}, #{adminRole.roleId},
- #{adminRole.gmtCreate}, #{adminRole.gmtModified})
- foreach>
- insert>
完成后,在AdminRoleMapperTests中编写并执行测试:
- @Test
- public void testInsertBatch() {
- Long adminId = 5L;
- Long[] roleIds = {2L, 3L, 4L};
- LocalDateTime now = LocalDateTime.now();
-
- AdminRole[] adminRoleList = new AdminRole[roleIds.length];
- for (int i = 0; i < roleIds.length; i++) {
- AdminRole adminRole = new AdminRole();
- adminRole.setAdminId(adminId);
- adminRole.setRoleId(roleIds[i]);
- adminRole.setGmtCreate(now);
- adminRole.setGmtModified(now);
- adminRoleList[i] = adminRole;
- }
-
- int rows = mapper.insertBatch(adminRoleList);
- log.debug("批量插入管理员与角色的关联数据成功,受影响的行数={}", rows);
- }
测试通过后,在AdminAddNewDTO类中补充private Long[] roleIds;属性:
- /**
- * 管理员关联到的若干个角色的id
- */
- @ApiModelProperty("管理员关联到的若干个角色的id")
- private Long[] roleIds;
然后,在AdminServiceImpl中,先自动装配AdminRoleMapper对象:
- @Autowired
- private AdminRoleMapper adminRoleMapper;
并在“添加管理员”的业务的最后,补充向管理员与角色关联的表中插入数据:
- // 向管理员与角色关联的表中插入数据
- log.debug("准备向管理员与角色关联的表中插入数据");
- Long adminId = admin.getId();
- Long[] roleIds = adminAddNewDTO.getRoleIds();
- LocalDateTime now = LocalDateTime.now();
-
- AdminRole[] adminRoleList = new AdminRole[roleIds.length];
- for (int i = 0; i < roleIds.length; i++) {
- AdminRole adminRole = new AdminRole();
- adminRole.setAdminId(adminId);
- adminRole.setRoleId(roleIds[i]);
- adminRole.setGmtCreate(now);
- adminRole.setGmtModified(now);
- adminRoleList[i] = adminRole;
- }
- rows = adminRoleMapper.insertBatch(adminRoleList);
- // 判断受影响的行数是否小于1(可能插入多条数据,所以,大于或等于1的值均视为正确)
- if (rows < 1) {
- // 是:日志,抛出ServiceException
- String message = "添加管理员失败,服务器忙,请稍后再次尝试![错误代码:2]";
- log.warn(message);
- throw new ServiceException(ServiceCode.ERR_INSERT, message);
- }
完成后,修改原有的AdminServiceTests中添加管理员的测试方法,为测试数据补充Long[] roleIds属性的值,再进行测试:
- @Test
- void testAddNew() {
- AdminAddNewDTO adminAddNewDTO = new AdminAddNewDTO();
- adminAddNewDTO.setUsername("test-admin-109");
- adminAddNewDTO.setPassword("123456");
-
- Long[] roleIds = {2L, 4L}; // 新增代码
- adminAddNewDTO.setRoleIds(roleIds); // 新增代码
-
- try {
- service.addNew(adminAddNewDTO);
- log.debug("添加管理员成功!");
- } catch (ServiceException e) {
- log.debug(e.getMessage());
- }
- }
事务:Transaction,是数据库中可以保障多次写(增删改)操作要么全部成功,要么全部失败的机制。
在基于Spring JDBC的数据库编程(包括使用Mybatis框架实现数据库编程)中,在处理业务的方法上添加@Transactional注解,即可保证此业务方法是事务性的。
关于事务的相关概念:
在Spring JDBC的事务管理中,其实现大概是:
- 开启事务
- try {
- 执行业务方法
- 提交事务
- } catch (RuntimeException e) {
- 回滚事务
- }
可以看到,当执行业务方法时,如果出现了RuntimeException(含其子孙类异常),都会回滚事务,这是Spring JDBC事务管理的默认处理方式!
在使用@Transactional注解时,可以通过配置rollbackFor及相关属性,来指定回滚的异常类型,还可以通过配置noRollbackFor及相关属性,来指定不执行回滚的异常类型。
关于@Transactional注解,可以添加在:
如果将此注解添加在“接口”或“接口的实现类”上,则表示对应的所有方法都是事务性的!
如果“接口”或“接口的实现类”上添加了此注解,并配置了注解的某属性,同时,“接口的抽象方法”或“实现类的实现方法上”也添加了此注解,也配置了注解的同样的属性,却是不同的值,则以方法上的配置值为准!
通常,推荐将注解添加在接口上,或接口中的抽象方法上!
本质上,Spring JDBC是通过接口代理模式来实现的事务管理,如果将@Transactional注解添加在业务实现类中的自定义方法上(未在接口中声明的方法),会是错误的!
通常,如果某个业务涉及2次或以上次数的写(增删改)操作,就必须使其是事务性的!另外,如果某个业务中涉及多次查询,使用@Transactional可以使得这些查询共用同一个数据库连接对象,可提高查询效率。
另外:建议学习“事务的ACID特性”、“事务的传播”、“事务的隔离”。