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

news2024/12/23 17:18:22
  • 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()));
    }
}

优化结束,打完收工。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/676280.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

6.23黄金是否会跌破1900?多单被套怎么办?

近期有哪些消息面影响黄金走势&#xff1f;今日黄金多空该如何研判&#xff1f; ​黄金消息面解析&#xff1a;周四&#xff08;6月22日&#xff09;美市尾盘&#xff0c;现货黄金收报1910美元/盎司&#xff0c;下跌20美元或0.1%&#xff0c;日内最高触及1934.95美元/盎司&…

C++ 面向对象(1)——类 对象

C 在 C 语言的基础上增加了面向对象编程&#xff0c;C 支持面向对象程序设计。类是 C 的核心特性&#xff0c;通常被称为用户定义的类型。 类用于指定对象的形式&#xff0c;是一种用户自定义的数据类型&#xff0c;它是一种封装了数据和函数的组合。类中的数据称为成员变量&a…

Studio One6中文版多少钱?有哪些新功能

Studio One6中文版现在有三个版本&#xff0c;免费版&#xff0c;Artist&#xff0c;Pro版本。下载后是免费版&#xff0c;免费版没有时间限制&#xff0c;但是功能受限。三个版本都支持win/mac系统&#xff0c;而且同时支持5台设备使用&#xff0c;还可以换机使用。 三个版本…

Spring Cloud Day2 Nacos配置管理、Feign远程调用与Gateway服务网关

SpringCloud实用篇02 0.学习目标 1.Nacos配置管理 Nacos除了可以做注册中心&#xff0c;同样可以做配置管理来使用。 1.1.统一配置管理 当微服务部署的实例越来越多&#xff0c;达到数十、数百时&#xff0c;逐个修改微服务配置就会让人抓狂&#xff0c;而且很容易出错。我…

关闭 MAC 的 Microsoft AutoUpdate 自动更新

不是我说&#xff0c;这玩意儿看着是真不爽&#xff01;&#xff01;而且每天都要弹出来搞事情&#xff01;&#xff01;&#xff01; 我宣布&#xff1a;今天就要永久关闭 MAC 的 Microsoft AutoUpdate 自动更新&#xff01;&#xff01; 像我一样的朋友请举手&#xff01;&am…

Linux学习[17]bash学习深入3---万用字符特殊符号---数据流重导向

文章目录 前言1. 万用字符2. 特殊字符3. 数据流重导向3.1标准输出3.2 标准输入 总结 前言 这篇博客是对之前在查找的时候涉及到的一些通配符(bash里面就是万用字符)的整理。这个为后面管线相关打一个基础。 1. 万用字符 这里整理了一个表格&#xff0c;后面配上相关实例。 符…

定制化你的应用外观:gradio的自定义主题功能

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

Axure教程——多项选择器

本文介绍利用Axure里的中继器和动态面板制作一个多选下拉列表 一、效果 预览地址:https://frh0rc.axshare.com 二、功能 1、点击下拉框可以弹出选项&#xff0c;点击选项可以选中选项2、用户可以取消选中 三、制作 1、制作下拉框 拖入一个矩形组件&#xff0c;命名为“下拉框…

C++——指针空值

在良好的C/C编程习惯中&#xff0c;声明一个变量时最好给该变量一个合适的初始值&#xff0c;否则可能会出现不可预料的错误&#xff0c;比如未初始化的指针。如果一个指针没有合法的指向&#xff0c;我们基本都是按照如下方式对其进行初始化&#xff1a; void TestPtr() {int*…

python---案例分析(2)

例5: 使用python生成一个二维码 结果就会显示一个二维码!拿出手机扫描二维码就可以看到make中填写的内容! 例6: 操作excel 使用python计算平均分的情况 首先在自己的pycharm上安装xlrd 必须是上述版本的 安装成功版本后,import一下即可使用 以下是计算100班的平均分 例6: …

后端开发通用

1、前后端开发 项目基于前后端分离的架构进行开发&#xff0c;前后端分离架构总体上包括前端和服务端&#xff0c;通常是多人协作开发 对于后端java工程师 把精力放在设计模式&#xff0c;springspringmvc&#xff0c;linux&#xff0c;mysql事务隔离与锁机制&#xff0c;mongo…

typescript找不到模块‘vue‘ ‘vue-router‘

import { createRouter, createWebHashHistory, createWebHistory } from vue-router 提示&#xff1a;找不到模块“vue-router”。你的意思是要将 "moduleResolution" 选项设置为 "node"&#xff0c;还是要将别名添加到 "paths" 选项中?ts(27…

Python基础篇(五):函数的定义和调用

Python基础篇(四)&#xff1a;基本数据类型的学习和示例 函数的定义和调用 前言1. Python 函数示例2. 自定义函数2.1 函数语法2.2 函数示例2.3 函数调用 3.内置函数3.1 数学函数3.2 类型转换函数3.3 序列操作函数3.4 输入输出函数3.5 文件操作函数3.6 迭代函数3.7 集合操作函数…

C#期末考试总结:

考点1&#xff1a;内插字符串$(使用方法&#xff1a;$"........{变量名}....."&#xff0c;作用&#xff1a;可读性增强&#xff0c;本身个也是一个字符串&#xff0c;可以作为一个变量赋值&#xff0c;有利于字符串的生成 考点2&#xff1a;强类型语言 考点3&…

【Unity之IMGUI】—自定义常用控件的封装(即拿即用)

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;Uni…

为什么有了HTTP,还需要WebSocket协议?

目录 WebSocket是什么&#xff1f; WebSocket怎样建立连接&#xff1f; WebSocket的实际用途 WebSocket 与 HTTP 的选择 HTTP是基于TCP协议的&#xff0c;同一时间里&#xff0c;客户端和服务器只能有一方主动发数据&#xff0c;是半双工通信。 通常&#xff0c;打开某个网…

基于Java海鲜自助餐厅系统设计实现(源码+lw+部署文档+讲解等)

博主介绍&#xff1a; ✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战 ✌ &#x1f345; 文末获取源码联系 &#x1f345; &#x1f447;&#x1f3fb; 精…

【Syncfusion系列】开篇简介

背景 准备开个新坑&#xff0c;分享下Syncfusion&#xff08;UI框架&#xff09;的使用&#xff0c;之前用的handycontry&#xff0c;目前是两者结合起来用。Syncfusion真的是非常的强大&#xff0c;我必须记录一下。 Syncfusion 简介 Syncfusion 是一家软件公司&#xff0c;专…

华为手机内存不足?别焦虑,这里有迁移数据的超实用技巧!

1、usb线缆连接电脑和手机。 2、使用C:\Program Files (x86)\HiSuite\HiSuite.exe工具导出照片和视频。 步骤1&#xff1a;下载和安装HiSuite.exe工具 前往华为官网下载并安装HiSuite.exe工具。安装完成后&#xff0c;通过USB线将你的华为手机连接到电脑上。 步骤2&#xff…

centos 安装elasticsearch8.7.0, 并设置密码访问

访问下载网站 Elasticsearch 8.7.0 | Elastic 进入centos , 创建目录es mkdir /opt/es cd /opt/es wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-8.7.0-linux-x86_64.tar.gz 3、解压目录 es870 tar -zxvf elasticsearch-8.7.0-linux-x86_64.tar…