五个步骤,助你优雅的写好 Controller 层代码!

这篇具有很好参考价值的文章主要介绍了五个步骤,助你优雅的写好 Controller 层代码!。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

  • Controller 层逻辑

  • 普通写法

  • 优化思路

五个步骤,助你优雅的写好 Controller 层代码!


Controller 层逻辑

MVC架构下,我们的web工程结构会分为三层,自下而上是dao层,service层和controller层。controller层为控制层,主要处理外部请求,调用service层。

一般情况下,controller层不应该包含业务逻辑,controller的功能应该有以下五点:

⑴、接收请求并解析参数

⑵、业务逻辑执行成功做出响应

⑶、异常处理

⑷、转换业务对象

⑸、调用 Service 接口

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro

  • 视频教程:https://doc.iocoder.cn/video/

普通写法

@RestController
public class TestController {

    @Autowired
    private UserService userService;

    @PostMapping("/test")
 public Result service(@Validated  @RequesBody  UserRequestBo requestBo) throws Exception {
        Result result = new Result();
        // 参数校验
        if (StringUtils.isNotEmpty(requestBo.getId())
                || StringUtils.isNotEmpty(requestBo.getType())
                || StringUtils.isNotEmpty(requestBo.getName())
                || StringUtils.isNotEmpty(requestBo.getAge())) {
            throw new Exception("必输项校验失败");
        } else {
            // 调用service更新user,更新可能抛出异常,要捕获
            try {
                int count = 0;
                User user = userService.queryUser(requestBo.getId());
                if (ObjectUtils.isEmpty(user)) {
                    result.setCode("11111111111");
                    result.setMessage("请求失败");
                    return result;
                }
                // 转换业务对象
                UserDTO userDTO = new UserDTO();
                BeanUtils.copyProperties(requestBo, userDTO);
                if ("02".equals(user.getType())) {// 退回修改的更新
                    count = userService.updateUser(userDTO)
                }else if ("03".equals(user.getType())) {// 已生效状态,新增一条待复核
                    count = userService.addUser(userDTO);
                }
                // 组装返回对象
                result.setData(count);
                result.setCode("00000000");
                result.setMessage("请求成功");
            } catch (Exception ex) {
                // 异常处理
                result.setCode("111111111111");
                result.setMessage("请求失败");
            }
        }
        return result;
    }
}

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/yudao-cloud

  • 视频教程:https://doc.iocoder.cn/video/

优化思路

1、调用 Service 层接口

一般情况下,controller作为控制层调用service层接口,不应该包含任何业务逻辑,所有的业务操作,都放在service层实现,把controller层相关代码去掉

controller层就变成了:

@RestController
public class TestController {

@Autowired
private UserService userService;

@PostMapping("/test")
public Result service(@Validated  @RequesBody  UserRequestBo requestBo) throws Exception {
    Result result = new Result();
    // 参数校验
    if (StringUtils.isNotEmpty(requestBo.getId())
            || StringUtils.isNotEmpty(requestBo.getType())
            || StringUtils.isNotEmpty(requestBo.getName())
            || StringUtils.isNotEmpty(requestBo.getAge())) {
        throw new Exception("必输项校验失败");
    } else {
        // 调用service更新user,更新可能抛出异常,要捕获
        try {
         // 转换业务对象
            UserDTO userDTO = new UserDTO();
            BeanUtils.copyProperties(requestBo, userDTO);
            int count = userService.updateUser(userDTO);
            // 组装返回对象
            result.setData(count);
            result.setCode("00000000");
            result.setMessage("请求成功");
        } catch (Exception ex) {
            // 异常处理
            result.setCode("EEEEEEEE");
            result.setMessage("请求失败");
        }
    }
    return result;
}
2、参数校验

其实大多数的参数校验就是判空或者空字符串,那么我们可以用@NotBlank等注解。在UserRequestBo类中name属性上加上@NotBlank注解

优化后如下:

@Data
public class UserRequestBo {

    @NotBlank
    private String id;

    @NotBlank
    private String type;

    @NotBlank
    private String name;

    @NotBlank
    private String age;
}

controller层就变成了:

@RestController
public class TestController {

    @Autowired
    private UserService userService;

    @PostMapping("/test")
    public Result service( @Validated  @RequesBody  UserRequestBo requestBo) throws Exception {
        Result result = new Result();
        // 调用service更新user,更新可能抛出异常,要捕获
        try {
         // 转换业务对象
            UserDTO userDTO = new UserDTO();
            BeanUtils.copyProperties(requestBo, userDTO);
            int count = userService.updateUser(userDTO);
            // 组装返回对象
            result.setData(count);
            result.setCode("00000000");
            result.setMessage("请求成功");
        } catch (Exception ex) {
            // 异常处理
            result.setCode("EEEEEEEE");
            result.setMessage("请求失败");
        }
        return result;
    }
}

备注:@NotNull@NotBlank@NotEmpty的区别,也适用于代码中的校验方法

  • @NotNull: 平常用于基本数据的包装类(Integer,Long,Double等等),如果@NotNull 注解被使用在 String 类型的数据上,则表示该数据不能为 Null,但是可以为空字符串(“”),空格字符串(“ ”)等。

  • @NotEmpty: 平常用于 String、Collection集合、Map、数组等等,@NotEmpty 注解的参数不能为 Null 或者 长度为 0,如果用在String类型上,则字符串也不能为空字符串(“”), 但是空格字符串(“ ”)不会被校验住。

  • @NotBlank: 平常用于 String 类型的数据上,加了@NotBlank 注解的参数不能为 Null ,不能为空字符串(“”), 也不能会空格字符串(“ ”),多了一个trim()得到效果。

3、统一封装返回对象

代码中无论是业务成功或者失败,都需要封装返回对象,目前代码中都是哪里用到就在哪里进行封装

我们可以统一封装返回对象

优化后如下:

@Data
public class Result<T> {

    private String code;

    private String message;

    private T data;

 // 请求成功,指定data
    public static <T> Result<T> success(T data) {
        return new Result<>(ResultEnum.SUCCESS.getCode(), ResultEnum.SUCCESS.getMessage(), data);
    }
    
 // 请求成功,指定data和指定message
    public static <T> Result<T> success(String message, T data) {
        return new Result<>(ResultEnum.SUCCESS.getCode(), message, data);
    }
    
 // 请求失败
    public static Result<?> failed() {
        return new Result<>(ResultEnum.COMMON_FAILED.getCode(), ResultEnum.COMMON_FAILED.getMessage(), null);
    }
    
 // 请求失败,指定message
    public static Result<?> failed(String message) {
        return new Result<>(ResultEnum.COMMON_FAILED.getCode(), message, null);
    }
    
    // 请求失败,指定code和message
    public static Result<?> failed(String code, String message) {
        return new Result<>(code, message, null);
    }
}

controller层就变成了:

@RestController
public class TestController {

    @Autowired
    private UserService userService;

    @PostMapping("/test")
    public Result service(@Validated  @RequesBody  UserRequestBo requestBo) throws Exception {
        // 调用service更新user,更新可能抛出异常,要捕获
        try {
         // 转换业务对象
            UserDTO userDTO = new UserDTO();
            BeanUtils.copyProperties(requestBo, userDTO);
            int count = userService.updateUser(userDTO);
            // 组装返回对象
            Result.success(count);
        } catch (Exception ex) {
            // 异常处理
            Result.failed(ex.getMessage());
        }
    }
} 
4、统一的异常捕获

Controller层和service存在大量的try-catch,都是重复代码并且看起来也不优雅。可以给controller层的方法加上切面来统一处理异常。

@ControllerAdvice注解(@RestControllerAdvice也可以),用来定义controller层的切面,添加@Controller注解的类中的方法执行都会进入该切面,同时我们可以使用@ExceptionHandler来对不同的异常进行捕获和处理,对于捕获的异常,我们可以进行日志记录,并且封装返回对象。

优化后如下:

// @RestControllerAdvice(basePackages = "com.ruoyi.web.controller.demo.test"), 指定包路径进行切面
// @RestControllerAdvice(basePackageClasses = TestController.class) , 指定Contrller.class进行切面
// @RestControllerAdvice 不带参数默认覆盖所有添加@Controller注解的类
@RestControllerAdvice(basePackageClasses = TestController.class)
public class TestControllerAdvice {

    @Autowired
    HttpServletRequest httpServletRequest;

    private void logErrorRequest(Exception e){
        // 组装日志内容
        String logInfo = String.format("报错API URL: %S, error = ", httpServletRequest.getRequestURI(), e.getMessage());
        // 打印日志
        System.out.println(logInfo);
    }

    /**
     * {@code @RequestBody} 参数校验不通过时抛出的异常处理
     */
    @ExceptionHandler({MethodArgumentNotValidException.class})
    public Result handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
        // 打印日志
        logErrorRequest(ex);
        // 组织异常信息,可能存在多个参数校验失败
        BindingResult bindingResult = ex.getBindingResult();
        StringBuilder sb = new StringBuilder("校验失败:");
        for (FieldError fieldError : bindingResult.getFieldErrors()) {
       sb.append(fieldError.getField()).append(":").append(fieldError.getDefaultMessage()).append(", ");
        }
        return Result.failed(ResultEnum.VALIDATE_FAILED.getCode(), sb.toString());
    }

    /**
     * 业务层异常,如果项目中有自定义异常则使用自定义业务异常,如果没有,可以和其他异常一起处理
     *
     * @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 {

    @Autowired
    private UserService userService;

    @PostMapping("/test")
    public Result service( @Validated  @RequesBody  UserRequestBo requestBo) throws Exception {
        UserDTO userDTO = new UserDTO();
        BeanUtils.copyProperties(requestBo, userDTO);
        // 调用service层接口
        int count = userService.updateUser(userDTO);
        //组装返回对象
        return Result.success(count);
    }
}
5、转换业务对象

代码中可能有很多个地方转换同一个业务对象,入参UserRequestBo可以转换为userDTO,可以理解为这是UserRequestBo的一个特性或者能力,我们可以参考充血模式的思想,在UserRequestBo中定义convertToUserDTO方法,我们的目的是转换业务对象,至于使用什么方式转换,调用方并不关心,现在使用的BeanUtils.copyProperties(),如果有一天想修改成使用Mapstruct来进行对象转换,只需要修改UserRequestBoconvertToUserDTO方法即可,不会涉及到业务代码的修改。

优化后代码:

@Data
public class UserRequestBo {

    @NotBlank
    private String id;

    @NotBlank
    private String type;

    @NotBlank
    private String name;

    @NotBlank
    private String age;

    /**
     * UserRequestBo对象为UserDTO
     * */
    public UserDTO convertToUserDTO(){
        UserDTO userDTO = new UserDTO();
        // BeanUtils.copyProperties要求字段名和字段类型都要保持一致,如果有不一样的字段,需要单独set
        BeanUtils.copyProperties(this, userDTO);
        userDTO.setType(this.getType());
        return userDTO;
    }
}

controller层就变成了:

@RestController
public class TestController {

    @Autowired
    private UserService userService;

    @PostMapping("/test")
    public Result service(@Validated  @RequesBody  UserRequestBo requestBo) throws Exception {
        return Result.success(userService.updateUser(requestBo.convertToUserDTO()));
    }
}

优化结束,打完收工。

 文章来源地址https://www.toymoban.com/news/detail-496840.html

到了这里,关于五个步骤,助你优雅的写好 Controller 层代码!的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Ubuntu忘记密码(五个小步骤)

    可能用到的操作: 按键/鼠标操作 作用 进入虚拟机屏幕[点击] 鼠标焦点在虚拟机中,接下来的操作都在虚拟机中响应 退出虚拟机屏幕[ctrl+alt] 将鼠标焦点从虚拟机中移除,回到主屏幕 步骤一 :重启虚拟机,注意在 启动界面出现时按一下键盘上的[e]即可 。默认选择[Ubuntu],在

    2024年02月05日
    浏览(36)
  • ADC前端电路的五个设计步骤

    现代通信系统和测试设备常常需要尽快地将模拟信号数字化,以便在数字域中完成信号处理。但是,为模数转换器(ADC)设计变压器前端电路很有挑战性,特别是在高中频(IF)的系统中。本文总结了5个设计步骤,以帮助开发出的ADC前端。这5个步骤包括:1. 了解系统和设计要求;

    2024年02月05日
    浏览(31)
  • 多图详解:不停机分库分表五个步骤

    分库分表确实可以解决单表数据量大这个问题,但是并非首选。因为分库分表至少引入了三个必须解决的突出问题。 第一是分库分表方案本身具有的复杂性。第二是本地事务失效问题,原本在同一个数据库中可以保证强一致性业务逻辑,分库之后事务失效。第三是难以聚合查

    2024年02月05日
    浏览(20)
  • HTML图片设置成为页面背景 ( 五个小步骤)

    1 在body里面使用, 一般放在网页的开头编写 2 找到图片存放路径( 可以是jpg也可以是gif图片格式) 3 设置图片不重复出现 4 使图片位置固定 5 使背景 比例 达到窗口的100% 6 代码展示

    2024年02月11日
    浏览(45)
  • 建立人力资源运营团队的五个步骤

    作为小企业主,设置人力资源运营可能不是您的首要任务。但是,随着您扩大运营规模和员工人数,您可能会遇到合规性和员工敬业度问题,从而阻碍您的业务增长。组建一个团队来照顾您的人力资源运营和员工可以让您专注于改进您的产品和满足客户需求。  如果您是希望

    2023年04月18日
    浏览(27)
  • 如何将写好的Python代码,封装运行?

    要把Python代码封装成可执行的程序可以通过以下步骤完成: 首先将代码保存为.py文件 然后在代码中添加适当的命令行参数解析器(如argparse),使得代码可以通过命令行接受输入参数 之后再在代码的开头添加#!/usr/bin/env python,这将允许脚本在Unix/Linux/Mac系统中以可执行文件的

    2024年02月08日
    浏览(34)
  • linux网关设置方法是什么?五个步骤帮你搞定

    linux网关设置方法是什么? 网关是一种充当转换重任的计算机系统或设备。那么linux下网关如何设置呢?一些对电脑操作系统不熟悉的朋友可能不是很了解,因此下面小编就给大家分享一下设置linux网关的方法,五个步骤就可以解决 设置linux网关的方法: 步骤一:使用xshell登

    2024年02月06日
    浏览(32)
  • 网络钓鱼:工作场所保护电子邮件安全的五个步骤

    尽管工作场所的聊天和即时通讯应用越来越多,但对许多人来说电子邮件仍继续在内部和外部业务通信中占主导地位。 不幸的是,电子邮件还是网络攻击的最常见切入点,攻击者会将恶意软件和漏洞传播到网络,并泄漏登录凭据和敏感数据。 电子邮件安全攻击态势 SophosLab

    2024年01月16日
    浏览(86)
  • c# OpenCvSharp 目标检测五个步骤(又学会了)(七)

    目标检测通过下面5个步骤得出结果 读取图像 Cv2.ImRead(); 使用模板匹配函数 cv2.matchTemplate(); 获取匹配结果的最大值和最小值的位置 Cv2.MinMaxLoc(); 绘制矩形框标记匹配结果 显示匹配结果 图例(上个扑克牌玩一下)  看结果 确实匹配到一个爱心,那么爱心只能一个,不甘心再来

    2024年02月20日
    浏览(26)
  • 真实对比kimi、通义千问、文心一言的写代码能力,到底谁强?

    🤖AI改变生活:最近都在说月之暗面的kimi的各项能力吊打国内其他大模型,今天我们真实感受下 kimi、通义千问、文心一言的根据需求写代码的能力。 测评结果让人震惊! 我们先看一下热捧的月之暗面的kimi模型。 第一次运行有错误,很正常,我们继续把错误发给kimi,让他

    2024年04月13日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包