簡(jiǎn)述網(wǎng)站建設(shè)優(yōu)劣的評(píng)價(jià)標(biāo)準(zhǔn)外貿(mào)營(yíng)銷(xiāo)網(wǎng)站
controller層設(shè)計(jì)
§ Controller 層邏輯
? MVC架構(gòu)下,我們的web工程結(jié)構(gòu)會(huì)分為三層,自下而上是dao層,service層和controller層。controller層為控制層,主要處理外部請(qǐng)求。調(diào)用service層,一般情況下,controller層不應(yīng)該包含業(yè)務(wù)邏輯,controller的功能應(yīng)該有以下五點(diǎn):
⑴、接收請(qǐng)求并解析參數(shù)
⑵、業(yè)務(wù)邏輯執(zhí)行成功做出響應(yīng)
⑶、異常處理
⑷、轉(zhuǎn)換業(yè)務(wù)對(duì)象
⑸、調(diào)用 Service 接口
§ 普通寫(xiě)法
@RestController
public class TestController {@Autowiredprivate UserService userService;@PostMapping("/test")public Result service(@Validated @RequesBody UserRequestBo requestBo) throws Exception {Result result = new Result();// 參數(shù)校驗(yàn)if (StringUtils.isNotEmpty(requestBo.getId())|| StringUtils.isNotEmpty(requestBo.getType())|| StringUtils.isNotEmpty(requestBo.getName())|| StringUtils.isNotEmpty(requestBo.getAge())) {throw new Exception("必輸項(xiàng)校驗(yàn)失敗");} else {// 調(diào)用service更新user,更新可能拋出異常,要捕獲try {int count = 0;User user = userService.queryUser(requestBo.getId());if (ObjectUtils.isEmpty(user)) {result.setCode("11111111111");result.setMessage("請(qǐng)求失敗");return result;}// 轉(zhuǎn)換業(yè)務(wù)對(duì)象UserDTO userDTO = new UserDTO();BeanUtils.copyProperties(requestBo, userDTO);if ("02".equals(user.getType())) {// 退回修改的更新count = userService.updateUser(userDTO)}else if ("03".equals(user.getType())) {// 已生效狀態(tài),新增一條待復(fù)核count = userService.addUser(userDTO);}// 組裝返回對(duì)象result.setData(count);result.setCode("00000000");result.setMessage("請(qǐng)求成功");} catch (Exception ex) {// 異常處理result.setCode("111111111111");result.setMessage("請(qǐng)求失敗");}}return result;}
}
§ 優(yōu)化思路
1、調(diào)用 Service 層接口
? 一般情況下,controller作為控制層調(diào)用service層接口,不應(yīng)該包含任何業(yè)務(wù)邏輯,所有的業(yè)務(wù)操作,都放在service層實(shí)現(xiàn),把controller層相關(guān)代碼去掉
controller層就變成了:
@RestController
public class TestController {
@Autowired
private UserService userService;@PostMapping("/test")
public Result service(@Validated @RequesBody UserRequestBo requestBo) throws Exception {Result result = new Result();// 參數(shù)校驗(yàn)if (StringUtils.isNotEmpty(requestBo.getId())|| StringUtils.isNotEmpty(requestBo.getType())|| StringUtils.isNotEmpty(requestBo.getName())|| StringUtils.isNotEmpty(requestBo.getAge())) {throw new Exception("必輸項(xiàng)校驗(yàn)失敗");} else {// 調(diào)用service更新user,更新可能拋出異常,要捕獲try {// 轉(zhuǎn)換業(yè)務(wù)對(duì)象UserDTO userDTO = new UserDTO();BeanUtils.copyProperties(requestBo, userDTO);int count = userService.updateUser(userDTO);// 組裝返回對(duì)象result.setData(count);result.setCode("00000000");result.setMessage("請(qǐng)求成功");} catch (Exception ex) {// 異常處理result.setCode("EEEEEEEE");result.setMessage("請(qǐng)求失敗");}}return result;
}
}
2、參數(shù)校驗(yàn)
? 其實(shí)大多數(shù)的參數(shù)校驗(yàn)就是判空或者空字符串,那么我們可以用@NotBlank等注解。在UserRequestBo類(lèi)中name屬性上加上@NotBlank注解
優(yōu)化后如下:
@Data
public class UserRequestBo {@NotBlankprivate String id;@NotBlankprivate String type;@NotBlankprivate String name;@NotBlankprivate String age;
}
controller層就變成了:
@RestController
public class TestController {@Autowiredprivate UserService userService;@PostMapping("/test")public Result service( @Validated @RequesBody UserRequestBo requestBo) throws Exception {Result result = new Result();// 調(diào)用service更新user,更新可能拋出異常,要捕獲try {// 轉(zhuǎn)換業(yè)務(wù)對(duì)象UserDTO userDTO = new UserDTO();BeanUtils.copyProperties(requestBo, userDTO);int count = userService.updateUser(userDTO);// 組裝返回對(duì)象result.setData(count);result.setCode("00000000");result.setMessage("請(qǐng)求成功");} catch (Exception ex) {// 異常處理result.setCode("EEEEEEEE");result.setMessage("請(qǐng)求失敗");}return result;}
}
備注:@NotNull、@NotBlank、@NotEmpty的區(qū)別,也適用于代碼中的校驗(yàn)方法
@NotNull:平常用于基本數(shù)據(jù)的包裝類(lèi)(Integer,Long,Double等等),如果@NotNull 注解被使用在 String 類(lèi)型的數(shù)據(jù)上,則表示該數(shù)據(jù)不能為 Null,但是可以為空字符串(“”),空格字符串(“ ”)等。
@NotEmpty:平常用于 String、Collection集合、Map、數(shù)組等等,@NotEmpty 注解的參數(shù)不能為 Null 或者 長(zhǎng)度為 0,如果用在String類(lèi)型上,則字符串也不能為空字符串(“”), 但是空格字符串(“ ”)不會(huì)被校驗(yàn)住。
@NotBlank:平常用于 String 類(lèi)型的數(shù)據(jù)上,加了@NotBlank 注解的參數(shù)不能為 Null ,不能為空字符串(“”), 也不能會(huì)空格字符串(“ ”),多了一個(gè)trim()得到效果。
3、統(tǒng)一封裝返回對(duì)象
? 代碼中無(wú)論是業(yè)務(wù)成功或者失敗,都需要封裝返回對(duì)象,目前代碼中都是哪里用到就在哪里進(jìn)行封裝
我們可以統(tǒng)一封裝返回對(duì)象
優(yōu)化后如下:
@Data
public class Result<T> {private String code;private String message;private T data;// 請(qǐng)求成功,指定datapublic static <T> Result<T> success(T data) {return new Result<>(ResultEnum.SUCCESS.getCode(), ResultEnum.SUCCESS.getMessage(), data);}// 請(qǐng)求成功,指定data和指定messagepublic static <T> Result<T> success(String message, T data) {return new Result<>(ResultEnum.SUCCESS.getCode(), message, data);}// 請(qǐng)求失敗public static Result<?> failed() {return new Result<>(ResultEnum.COMMON_FAILED.getCode(), ResultEnum.COMMON_FAILED.getMessage(), null);}// 請(qǐng)求失敗,指定messagepublic static Result<?> failed(String message) {return new Result<>(ResultEnum.COMMON_FAILED.getCode(), message, null);}// 請(qǐng)求失敗,指定code和messagepublic static Result<?> failed(String code, String message) {return new Result<>(code, message, null);}
}
controller層就變成了:
@RestController
public class TestController {@Autowiredprivate UserService userService;@PostMapping("/test")public Result service(@Validated @RequesBody UserRequestBo requestBo) throws Exception {// 調(diào)用service更新user,更新可能拋出異常,要捕獲try {// 轉(zhuǎn)換業(yè)務(wù)對(duì)象UserDTO userDTO = new UserDTO();BeanUtils.copyProperties(requestBo, userDTO);int count = userService.updateUser(userDTO);// 組裝返回對(duì)象Result.success(count);} catch (Exception ex) {// 異常處理Result.failed(ex.getMessage());}}
}
4、統(tǒng)一的異常捕獲
? Controller層和service存在大量的try-catch,都是重復(fù)代碼并且看起來(lái)也不優(yōu)雅??梢越ocontroller層的方法加上切面來(lái)統(tǒng)一處理異常。用@ControllerAdvice注解(@RestControllerAdvice也可以),用來(lái)定義controller層的切面,添加@Controller注解的類(lèi)中的方法執(zhí)行都會(huì)進(jìn)入該切面,同時(shí)我們可以使用@ExceptionHandler來(lái)對(duì)不同的異常進(jìn)行捕獲和處理,對(duì)于捕獲的異常,我們可以進(jìn)行日志記錄,并且封裝返回對(duì)象。
優(yōu)化后如下:
// @RestControllerAdvice(basePackages = "com.ruoyi.web.controller.demo.test"), 指定包路徑進(jìn)行切面
// @RestControllerAdvice(basePackageClasses = TestController.class) , 指定Contrller.class進(jìn)行切面
// @RestControllerAdvice 不帶參數(shù)默認(rèn)覆蓋所有添加@Controller注解的類(lèi)
@RestControllerAdvice(basePackageClasses = TestController.class)
public class TestControllerAdvice {@AutowiredHttpServletRequest httpServletRequest;private void logErrorRequest(Exception e){// 組裝日志內(nèi)容String logInfo = String.format("報(bào)錯(cuò)API URL: %S, error = ", httpServletRequest.getRequestURI(), e.getMessage());// 打印日志System.out.println(logInfo);}/*** {@code @RequestBody} 參數(shù)校驗(yàn)不通過(guò)時(shí)拋出的異常處理*/@ExceptionHandler({MethodArgumentNotValidException.class})public Result handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {// 打印日志logErrorRequest(ex);// 組織異常信息,可能存在多個(gè)參數(shù)校驗(yàn)失敗BindingResult bindingResult = ex.getBindingResult();StringBuilder sb = new StringBuilder("校驗(yàn)失敗:");for (FieldError fieldError : bindingResult.getFieldErrors()) {sb.append(fieldError.getField()).append(":").append(fieldError.getDefaultMessage()).append(", ");}return Result.failed(ResultEnum.VALIDATE_FAILED.getCode(), sb.toString());}/*** 業(yè)務(wù)層異常,如果項(xiàng)目中有自定義異常則使用自定義業(yè)務(wù)異常,如果沒(méi)有,可以和其他異常一起處理** @param exception* @return*/@ExceptionHandler(RuntimeException.class)protected Result serviceException(RuntimeException exception) {logErrorRequest(exception);return Result.failed(exception.getMessage());}/*** 其他異常** @param exception* @return*/@ExceptionHandler({HttpClientErrorException.class, IOException.class, Exception.class})protected Result serviceException(Exception exception) {logErrorRequest(exception);return Result.failed(exception.getMessage());}
}
controller層就變成了:
@RestController
public class TestController {@Autowiredprivate UserService userService;@PostMapping("/test")public Result service( @Validated @RequesBody UserRequestBo requestBo) throws Exception {UserDTO userDTO = new UserDTO();BeanUtils.copyProperties(requestBo, userDTO);// 調(diào)用service層接口int count = userService.updateUser(userDTO);//組裝返回對(duì)象return Result.success(count);}
}
5、轉(zhuǎn)換業(yè)務(wù)對(duì)象
? 代碼中可能有很多個(gè)地方轉(zhuǎn)換同一個(gè)業(yè)務(wù)對(duì)象,入?yún)serRequestBo可以轉(zhuǎn)換為userDTO,可以理解為這是UserRequestBo的一個(gè)特性或者能力,我們可以參考充血模式的思想,在UserRequestBo中定義convertToUserDTO方法,我們的目的是轉(zhuǎn)換業(yè)務(wù)對(duì)象,至于使用什么方式轉(zhuǎn)換,調(diào)用方并不關(guān)心,現(xiàn)在使用的BeanUtils.copyProperties(),如果有一天想修改成使用Mapstruct來(lái)進(jìn)行對(duì)象轉(zhuǎn)換,只需要修改UserRequestBo的convertToUserDTO方法即可,不會(huì)涉及到業(yè)務(wù)代碼的修改。
優(yōu)化后代碼:
@Data
public class UserRequestBo {@NotBlankprivate String id;@NotBlankprivate String type;@NotBlankprivate String name;@NotBlankprivate String age;/*** UserRequestBo對(duì)象為UserDTO* */public UserDTO convertToUserDTO(){UserDTO userDTO = new UserDTO();// BeanUtils.copyProperties要求字段名和字段類(lèi)型都要保持一致,如果有不一樣的字段,需要單獨(dú)setBeanUtils.copyProperties(this, userDTO);userDTO.setType(this.getType());return userDTO;}
}
controller層就變成了:
@RestController
public class TestController {@Autowiredprivate UserService userService;@PostMapping("/test")public Result service(@Validated @RequesBody UserRequestBo requestBo) throws Exception {return Result.success(userService.updateUser(requestBo.convertToUserDTO()));}
}
優(yōu)化結(jié)束,打完收工。