2.4 商户注册
2.4.4 MapStruct对象转换
2.4.4.2 MapStruct
1、数据传输对象转换的繁琐
每层有自己的数据传输对象,当数据流转到该层又需要将数据转成符合要求的格式,比如:当数据由应用层流转到服务层则需要将数据转成DTO格式,当数据由服务层流向持久层则需要将数据转成Entity格式数据
下边的代码数据由服务层流向持久层:
@Override
public MerchantDTO createMerchant(MerchantDTO merchantDTO) {
Merchant merchant = new Merchant();
//设置审核状态0‐未申请,1‐已申请待审核,2‐审核通过,3‐审核拒绝
merchant.setAuditStatus("0");
//设置手机号
merchant.setMobile(merchantDTO.getMobile());
//...
//保存商户
merchantMapper.insert(merchant);
//将新增商户id返回
merchantDTO.setId(merchant.getId());
return merchantDTO;
}
上边代码的问题是:由merchantDTO转成entity实现过程繁琐。
2、MapStruct解决数据传输对象转换的繁琐
MapStruct是一个代码生成器,它基于约定优于配置的方法大大简化了Java Bean对象之间的映射转换的实现。
MapStruct 使用简单的方法即可完成对象之间的转换,它速度快、类型安全且易于理解。
1)添加依赖
在使用MapStruct的工程中添加MapStruct依赖:
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-jdk8</artifactId>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
2)服务层对象转换
在商户服务实现工程中定义商户对象转换类
定义MerchantConvert转换类,使用@Mapper注解快速实现对象转换
package com.huiminpay.merchant.convert;
import com.huiminpay.merchant.entity.Merchant;
import com.yh.merchant.api.dto.MerchantDTO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface MerchantCovert {
MerchantCovert INSTANCE = Mappers.getMapper(MerchantCovert.class);
MerchantDTO entity2Dto(Merchant merchant);
Merchant dto2Entity(MerchantDTO dto);
}
在MerchantCovert中定义测试方法:
//dto转entity
MerchantDTO merchantDTO = new MerchantDTO();
merchantDTO.setUsername("测试");
merchantDTO.setPassword("111");
Merchant entity = MerchantCovert.INSTANCE.dto2entity(merchantDTO);
//entity转dto
entity.setMobile("123444554");
MerchantDTO merchantDTO1 = MerchantCovert.INSTANCE.entity2dto(entity);
System.out.println(merchantDTO1);
List数据也可以转换:
在MerchantCovert中定义list的方法,如下:
//list之间的转换
List<MerchantDTO> listentity2dto(List<Merchant> list);
测试:
在main方法编写list之间的转换测试
//测试list之间的转换
List<Merchant> list_entity = new ArrayList<>();
list_entity.add(entity);
List<MerchantDTO> erchantDTOS = MerchantCovert.INSTANCE.listentity2dto(list_entity);
System.out.println(merchantDTOS);
3)应用层对象转换
在商户平台应用工程定义商户对象转换类
package com.huiminpay.merchant.convert;
import com.huiminpay.merchant.vo.MerchantRegisterVo;
import com.yh.merchant.api.dto.MerchantDTO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface MerchantRegisterConvert {
MerchantRegisterConvert INSTANCE = Mappers.getMapper(MerchantRegisterConvert.class);
MerchantDTO vo2dto(MerchantRegisterVo vo);
MerchantRegisterVo dto2vo(MerchantDTO dto);
}
2.4.4.3 代码优化
1、优化服务层代码
修改商户服务工程 MerchantServiceImpl 中的 createMerchant 方法:
@Override
public MerchantDTO createMerchant(MerchantDTO merchantDTO) {
Merchant merchant = MerchantCovert.INSTANCE.dto2Entity(merchantDTO);
//设置审核状态0‐未申请,1‐已申请待审核,2‐审核通过,3‐审核拒绝
merchant.setAuditStatus("0");
//保存商户
merchantMapper.insert(merchant);
return MerchantCovert.INSTANCE.entity2Dto(merchant);
}
2、代码应用层代码
修改商户平台应用工程 MerchantController 中的 registerMerchant 方法:
@ApiOperation("注册商户")
@ApiImplicitParam(name = "merchantRegister", value = "注册信息", required = true, dataType =
"MerchantRegisterVo", paramType = "body")
@PostMapping("/merchants/register")
public MerchantRegisterVo registerMerchant(@RequestBody MerchantRegisterVo merchantRegister) {
//校验验证码
smsService.checkVerifiyCode(merchantRegister.getVerifiykey(),
merchantRegister.getVerifiyCode());
//注册商户
MerchantDTO merchantDTO = MerchantRegisterConvert.INSTANCE.vo2dto(merchantRegister);
merchantService.createMerchant(merchantDTO);
return merchantRegister;
}
2.4.4.4 注意MapStruct版本问题
因工程内使用的 Swagger 依赖 MapStruct (其版本低,与lombok同时使用会出现找不到实现类异常),
解决办法如下,可在common工程内引用版本,并排除 swagger 的 MapStruct 依赖
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<exclusions>
<exclusion>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-jdk8</artifactId>
</exclusion>
<exclusion>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-jdk8</artifactId>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
2.4.5 异常处理
2.4.5.1 异常信息格式
系统在交互中难免会有异常发生,前端为了解析异常信息给用户提示,特定义了异常信息的返回格式,如下:
1、返回response状态说明
状态码 | 说明 |
200 | 成功 |
401 | 没有权限 |
500 | 程序错误(需要自定义错误体) |
2、自定义错误体
{
"errCode": "000000",
"errMessage": "错误说明"
}
2.4.5.2 异常处理流程
截至目前系统并没有按照前端要求返回异常信息,测试如下: 注册商户时输入一个错误的验证码,返回信息如下:
{
"timestamp": "2021-09-11T02:46:41.529+0000",
"status": 500,
"error": "Internal Server Error",
"message": "验证码已过期或者不存在",
"path": "/merchant/merchants/register"
}
从上边的返回信息得知,状态码为500符合要求,按前端的规范定义的错误信息要写在 “errMessage” 中,显然不符合要求。
系统规范了异常处理流程,如下:
1、在服务层抛出自定义异常类型及不可预知异常类型。
上图中BusinessException为系统的自定义异常类型,程序中在代码显式抛出该异常,此类异常是程序员可预知的。
另一部分是系统无法预知的异常,如:数据库无法连接,服务器宕机等场景下所抛出的异常,此类异常是程序员无 法预知的异常。
2、应用层接收到服务层抛出异常继续向上抛出,应用层自己也可以抛出自定义异常类型及不可预知异常类型。
3、统一异常处理器捕获到异常进行解析。
判断如果为自定义异常则直接取出错误代码及错误信息,因为程序员在抛出自定义异常时已将错误代码和异常信息指定。
如果为不可预知的异常则统一定义为99999异常代码。
4、统一异常处理器将异常信息格式为前端要求的格式响应给前端。服务端统一将异常信息封装在下边的Json格式中返回:
{
"errCode":"000000",
"errMessage": "错误说明"
}
2.4.5.3 自定义业务异常类
- 在 huiminpay-common 工程的 com.huiminpay.common.domain 包下添加业务异常类 BusinessException:
package com.huiminpay.common.cache.domain;
public class BusinessException extends RuntimeException {
//错误代码
private ErrorCode errorCode;
public BusinessException(ErrorCode errorCode) {
super();
this.errorCode = errorCode;
}
public BusinessException() {
super();
}
public void setErrorCode(ErrorCode errorCode) {
this.errorCode = errorCode;
}
public ErrorCode getErrorCode() {
return errorCode;
}
}
- 定义错误代码
在 common工程专门定义了 ErrorCode 接口及 CommonErrorCode 通用代码。
从资料文件夹的代码目录获取 CommonErrorCode.java 类。
2.4.5.4 自定义业务异常处理器
1、在huiminpay‐common 工程的 com.huiminpay.common.domain 包下添加错误响应包装类 RestErrorResponse:
package com.huiminpay.common.cache.domain;
import io.swagger.annotations.ApiModel;
import lombok.Data;
/**
* @author Administrator
* @version 1.0
**/
@ApiModel(value = "RestErrorResponse", description = "错误响应参数包装")
@Data
public class RestErrorResponse {
private String errCode;
private String errMessage;
public RestErrorResponse(String errCode, String errMessage){
this.errCode = errCode;
this.errMessage= errMessage;
}
}
2、定义全局异常处理器
全局异常处理器使用 ControllerAdvice 注解实现,ControllerAdvice是SpringMVC3.2提供的注解,用ControllerAdvice可以方便实现对Controller面向切面编程,具体用法如下:
1、ControllerAdvice和ExceptionHandler注解实现全局异常处理
2、ControllerAdvice和ModelAttribute注解实现全局数据绑定
3、ControllerAdvice和InitBinder注解实现全局数据预处理
今天学习第一种用法,其它用法有兴趣的可自行查阅相关资料。
ControllerAdvice和ExceptionHandler结合可以捕获Controller抛出的异常,根据异常处理流程,Service和持久层最终都会抛给Controller,所以此方案可以实现全局异常捕获,异常被捕获到即可格式化为前端要的信息格式响应给前端。
在 huiminpay‐merchant‐application 工程的 com.huiminpay.merchant.common.intercept 添加
GlobalExceptionHandler:
package com.huiminpay.merchant.common.intercept;
import com.huiminpay.common.cache.domain.BusinessException;
import com.huiminpay.common.cache.domain.CommonErrorCode;
import com.huiminpay.common.cache.domain.ErrorCode;
import com.huiminpay.common.cache.domain.RestErrorResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 全局异常处理器
* @author Administrator
* @version 1.0
**/
@ControllerAdvice//与@Exceptionhandler配合使用实现全局异常处理
public class GlobalExceptionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);
//捕获Exception异常
@ExceptionHandler(value = Exception.class)
@ResponseBody
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public RestErrorResponse processExcetion( HttpServletRequest request,
HttpServletResponse response,
Exception e){
//解析异常信息
//如果是系统自定义异常,直接取出errCode和errMessage
if(e instanceof BusinessException){
LOGGER.info(e.getMessage(),e);
//解析系统自定义异常信息
BusinessException businessException= (BusinessException) e;
ErrorCode errorCode = businessException.getErrorCode();
//错误代码
int code = errorCode.getCode();
//错误信息
String desc = errorCode.getDesc();
return new RestErrorResponse(String.valueOf(code),desc);
}
LOGGER.error("系统异常:",e);
//统一定义为99999系统未知错误
return new RestErrorResponse(String.valueOf(CommonErrorCode.UNKOWN.getCode()),CommonErrorCode.UNKOWN.getDesc());
}
}
2.4.5.5 抛出自定义异常
按照异常处理流程,应用层抛出自定义异常由异常处理器进行解析。
1、校验验证码接口抛出 BusinessException
修改商户平台应用工程中SmsServicer的verificationMessageCode接口
public void checkVerifyCode(String verifyKey, String verifyCode) throws BusinessException;
接口实现中抛出异常自定义异常类型
@Override
public void checkVerifiyCode(String verifiyKey, String verifiyCode) {
String url = smsUrl + "/verify?name=sms&verificatinotallow=" + verifiyCode + "&verificatinotallow=" + verifiyKey;
Map responseMap = null;
try {
//请求校验验证码
ResponseEntity<Map> exchange = restTemplate.exchange(url, HttpMethod.POST,
HttpEntity.EMPTY, Map.class);
responseMap = exchange.getBody();
log.info("校验验证码,响应内容:{}", JSON.toJSONString(responseMap));
} catch (RestClientException e) {
e.printStackTrace();
log.info(e.getMessage(), e);
throw new BusinessException(CommonErrorCode.E_100102);
}
if (responseMap == null || responseMap.get("result") == null || !(Boolean)responseMap.get("result")) {
throw new BusinessException(CommonErrorCode.E_100102);
}
}
2、测试
请求商户注册,输出一个错误的验证码,返回信息如下
{
"errCode": "100102",
"errMessage": "验证码错误"
}
3、测试不可预知异常
故意在Controller中制造异常,测试是否抛出未知错误异常。 代码如下:
@PostMapping("/merchants/register")
public MerchantRegisterVO registerMerchant(@RequestBody MerchantRegisterVO merchantRegister){
int i=1/0;//故意制造异常
....
}
请商户注册,返回信息如下:
{
"errCode": "999999",
"errMessage": "未知错误"
}
2.4.6 校验商户手机号
2.4.6.1 实现思路
校验商户手机号的唯一性,根据商户的手机号查询商户表,
如果存在记录则说明已有相同的手机号重复,手机号不唯一则抛出自定义异常。
2.4.6.2 完善代码
1、修改商户服务-注册商户接口,添加抛出异常声明
MerchantDTO createMerchant(MerchantDTO merchantDTO) throws BusinessException;
2、修改商户服务-注册商户接口实现方法
if (merchantDTO == null) {
throw new BusinessException(CommonErrorCode.E_100108);
}
//手机号非空校验
if (StringUtils.isEmpty(merchantDTO.getMobile())) {
throw new BusinessException(CommonErrorCode.E_100112);
}
//手机号合法性校验
if (!PhoneUtil.isMatches(merchantDTO.getMobile())) {
throw new BusinessException(CommonErrorCode.E_100109);
}
//联系人非空校验
if (StringUtils.isBlank(merchantDTO.getUsername())) {
throw new BusinessException(CommonErrorCode.E_100110);
}
//密码非空校验
if (StringUtils.isBlank(merchantDTO.getPassword())) {
throw new BusinessException(CommonErrorCode.E_100111);
}
LambdaQueryWrapper<Merchant> lqw = new LambdaQueryWrapper<>();
lqw.eq(Merchant::getMobile,merchantDTO.getMobile());
Integer count = merchantMapper.selectCount(lqw);
if(count>0){
throw new BusinessException(CommonErrorCode.E_100113);
}
3、修改商户应用平台-注册商户接口添加对注册信息的非空校验。
// 1.校验
if (merchantRegister == null) {
throw new BusinessException(CommonErrorCode.E_100108);
}
//手机号非空校验
if (StringUtils.isBlank(merchantRegister.getMobile())) {
throw new BusinessException(CommonErrorCode.E_100112);
}
//校验手机号的合法性
if (!PhoneUtil.isMatches(merchantRegister.getMobile())) {
throw new BusinessException(CommonErrorCode.E_100109);
}
//联系人非空校验
if (StringUtils.isBlank(merchantRegister.getUsername())) {
throw new BusinessException(CommonErrorCode.E_100110);
}
//密码非空校验
if (StringUtils.isBlank(merchantRegister.getPassword())) {
throw new BusinessException(CommonErrorCode.E_100111);
}
//验证码非空校验
if (StringUtils.isBlank(merchantRegister.getVerifiyCode()) ||
StringUtils.isBlank(merchantRegister.getVerifiykey())) {
throw new BusinessException(CommonErrorCode.E_100103);
}