开源了,优雅的Controller,应该这样写!

知识图谱_知识图谱 | 知识图谱做用户画像 导读:最近一直在思考怎么用知识图谱做推荐,但是目前能知道的内容都是基于路径的推荐和基于图特征的推荐,所以能不能换一个角度,探究一下用知识图谱怎么做用户画像,因为画像也是推荐系统重要的一部分。现在的知识图谱+推荐怎么做的传统的用户画像怎么做现在的知识图谱+推荐怎么做的?基于路径:可以理解成基于图谱的多路径召回,或者多路径推荐,然后根据路径的转移概率进行排序从而达到推荐的效果,基于路径比较适合简单的推... 阅读详情

平台奖励创作,可能会升级VIP文章,可以移步我的公众号:【编程朝花夕拾】,且可获取首发内容。

01 引言

你的Controller(控制层)还在通过if-else校验参数?还在通过try-catch捕捉异常么?还在因为参数校验不通过,给客户端响应错误信息么…

我们看看优雅的控制层是怎么写的?

02 传统控制层的写法

2.1 参数校验

@RequestMapping("/foo")
public JsonResult login(String cellphone,String name) {
    String cellphone = C2BLoginUtil.getLoginCellphone();
    if (StringUtils.isBlank(cellphone)) {
        return new JsonResult(false,"手机号不能为空!");
    }
    if (StringUtils.isBlank(name)) {
        return new JsonResult(false,"姓名不能为空!");
    }
    
    // ......
 
    return new JsonResult(true);
}

在控制层内实现参数的校验,如果参数较多,就会出现大量的校验,代码就会显的很臃肿。如果多个方法复用参数,每个方法都要去写一遍,造成代码冗余。

2.2 异常的捕捉

@RequestMapping("/foo")
public JsonResult login(String cellphone,String name) {
    try {
        // ...
    } catch (Exception e) {
        log.error("服务调用异常", e);
         return new JsonResult(false, "服务调用异常");
    } 
    
    
    // ......
 
    return new JsonResult(true);
}

每一个方法都需要异常捕获,这也是代码冗余的体现。

03 优雅的写法

统一参数校验, 统一异常捕获。整合控制才能重复的逻辑,减少代码冗余。

3.1 统一参数处理

假如有一个Book 类需要校验,我们需要将参数的校验,统一处理。然后服务端只需要一个注解就可轻松解决,还可以复用。

@Data
public class Book implements Serializable {

    private static final long serialVersionUID = -4746928436252252305L;

    /**
     *  ID
     */
    private Integer bookId;

    /**
     *  名称
     */
    @Length(min = 5, max = 20, message = "书名的长度必须在5~20之间")
    private String bookName;

    /**
     *  定价
     */
    @Min(value = 15, message = "书的价格不能低于15元")
    @Max(value = 100, message = "书的价格不能超过100元")
    private BigDecimal bookPrice;

    /**
     *  发布日期
     */
    @NotNull(message = "publishDate 不能为空")
    private Date publishDate;

    /**
     *  编写完成日期
     */
    @NotNull(message = "finishDateTime 不能为空")
    private LocalDateTime finishDateTime;

    /**
     * script
     */
    private String script;

}

服务端的处理

只需要一个@Valid注解就可完美的解决参数校验的问题。

@RequestMapping("/testFormData")  
public String testFormData(@Valid Book book){
    System.out.println(book);
    return "success";
}

是不是少了好多代码量。

3.2 统一异常捕获

将所有的异常放在一起处理,控制层就不需要处理了,如果需要只需要抛出异常即可。

@RestControllerAdvice
public class GlobalExceptionHandler 

    public static String LOG_TEMPLATE = "请求路径[url]={},[%s] 异常信息:";
    public static String REQUEST_PARAMS_EX_TEMPLATE = "参数[%s]:%s";
    public static String REQUEST_PARAMS_MISS_TEMPLATE = "请求参数缺失,缺失的参数:%s";

    @Autowired(required = false)
    private GlobalExceptionResponseResolver globalExceptionResponseResolver;

    /**
     * 校验参数绑定异常
     *
     * @author ws
     * @date 2024/1/29 11:13
     * @param e
     */
    @ExceptionHandler(BindException.class)
    public Object bindExceptionHandler(HttpServletRequest request, BindException e) {
        log.error(String.format(LOG_TEMPLATE, "bindExceptionHandler"), request.getRequestURL(), e);
        return doGenerateResult(e, getErrorMsgDefault(e.getBindingResult().getFieldErrors(), e.getMessage()));
    }

    /**
     * 方法参数校验异常处理
     *
     * @author ws
     * @date 2024/1/29 13:25
     * @param e
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Object methodArgumentNotValidExceptionHandler(HttpServletRequest request, MethodArgumentNotValidException e) {
        log.error(String.format(LOG_TEMPLATE, "methodArgumentNotValidExceptionHandler"), request.getRequestURL(), e);
        return doGenerateResult(e, getErrorMsgDefault(e.getBindingResult().getFieldErrors(), e.getMessage()));
    }

    /**
     * 参数校验配合@Requestparam的异常
     *
     * @author ws
     * @date 2024/3/7 16:29
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public Object constraintViolationExceptionExceptionHandler(HttpServletRequest request, ConstraintViolationException e) {
        log.error(String.format(LOG_TEMPLATE, "constraintViolationExceptionExceptionHandler"), request.getRequestURL(), e);
        return doGenerateResult(e, getConstraintViolationExceptionMsg(e));
    }

    /**
     * 请求参数缺失异常
     *
     * @author ws
     * @date 2024/1/29 13:33
     * @param e
     */
    @ExceptionHandler(MissingServletRequestParameterException.class)
    public Object missingServletRequestParameterExceptionHandler(HttpServletRequest request, MissingServletRequestParameterException e) {
        log.error(String.format(LOG_TEMPLATE, "missingServletRequestParameterExceptionHandler"), request.getRequestURL(), e);
        return doGenerateResult(e, String.format(REQUEST_PARAMS_MISS_TEMPLATE, e.getParameterName()));
    }

    /**
     * 重复的请求拦截异常
     *
     * @author ws
     * @date 2024/1/30 16:51
     * @param request
     * @param e
     */
    @ExceptionHandler(WebConfigException.class)
    public Object requestLimitingInterceptorExceptionHandler(HttpServletRequest request, WebConfigException e) {
        log.error(String.format(LOG_TEMPLATE, "requestLimitingInterceptorExceptionHandler"), request.getRequestURL(), e);
        return doGenerateResult(e, e.getMessage());
    }

    /**
     * 兜底的异常处理
     *
     * @author ws
     * @date 2024/1/29 13:35
     * @param e
     */
    @ExceptionHandler(Exception.class)
    public Object exceptionHandler(HttpServletRequest request, Exception e) {
        log.error(String.format(LOG_TEMPLATE, "exceptionHandler"), request.getRequestURL(), e);
        return doGenerateResult(e, e.getMessage());
    }

    /**
     * 处理ConstraintViolationException的参数
     *
     * @author ws
     * @date 2024/3/7 16:34
     */
    private String getConstraintViolationExceptionMsg(ConstraintViolationException e) {
        Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
        if (CollectionUtils.isEmpty(violations)) {
            return e.getMessage();
        }

        return violations.stream().filter(Objects::nonNull)
                .map(cv -> String.format(REQUEST_PARAMS_EX_TEMPLATE, cv.getPropertyPath(), cv.getMessage()))
                .collect(Collectors.joining("\n"));
    }

    /**
     * 处理公用的参数校验异常的信息
     *
     * @author ws
     * @date 2024/1/31 10:45
     */
    private String getErrorMsgDefault(List<FieldError> fieldErrors, String detaultMsg) {
        String errorMsg = null;
        if (!CollectionUtils.isEmpty(fieldErrors)) {
            List<String> msgs = new ArrayList<>(fieldErrors.size());
            fieldErrors.forEach(error -> msgs.add(showErrorMsg(error)));
            errorMsg = String.join("\n", msgs);
        }
        return Optional.ofNullable(errorMsg).orElse(detaultMsg);
    }

    private String showErrorMsg(FieldError fieldError) {
        return String.format(REQUEST_PARAMS_EX_TEMPLATE, fieldError.getField(), fieldError.getDefaultMessage());
    }

    /**
     * 处理通用结果
     *
     * @author ws
     * @date 2024/1/31 10:43
     */
    private Object doGenerateResult(Exception e, String errorMsg) {
        disposeExceptionTrace(errorMsg);
        if (globalExceptionResponseResolver != null) {
            globalExceptionResponseResolver.pushExceptionNotice(e, errorMsg);
            return globalExceptionResponseResolver.resolveExceptionResponse(e, errorMsg);
        }
        return JsonResult.ofFail(errorMsg);
    }
}

服务端的处理

理论上服务端已经不需要处理异常了。但是如果需要校验方法内部的异常,只需要抛出异常即可。

@RequestMapping("/testEx")
public String testEx() {
    Assert.notNull(obj, "obj 不能为空");
    return "success";
}

这里如果obj 如果为空,就会抛异常,异常会被统一处理。

3.3 统一Web的日期处理

Form表单的如果传递日期格式,需要控制层处理。如果日期的格式都不相同,处理起来就会麻烦。我们可以使用统一处理日期格式,兼容所有的场景。

@ControllerAdvice
public class GlobalWebHandler {

    /**
     * 处理web的data数据,这里主要处理form表单的日期
     *
     * @author ws
     * @date 2024/1/29 14:13
     * @param binder
     */
    @InitBinder
    public void globalWebDataBinder(WebDataBinder binder){
        DateFormatter dateFormatter = new DateFormatter(DateFormatConstant.STANDARD_DATE_TIME);
        dateFormatter.setFallbackPatterns(DateFormatConstant.FORMAT_PATTERNS);
        binder.addCustomFormatter(dateFormatter, Date.class);

        //兼容jdk8 LocalDateTime
        binder.addCustomFormatter(new LocalDateTimeFormatter(), LocalDateTime.class);
    }
}

该方法兼容常用的日志格式以及LocalDateTime, 只要传递对应的字符串,就可以正常解析。支持的格式:

04 开源项目

控制层的优雅写法,技术文章中多次被各个大佬讲过,但是都只是教你如何处理,却没有现成的工具封装。为了能够更好的使用优雅的写法,我自己从总结了常用的类型、以及以及处理方案并开源,欢迎大家使用。

只要引入对应的依赖,就可体验这种写法,不需要繁琐的配置,开箱即用。

GitHub地址:https://github.com/simonking-ws/webconfig-springboot

Gitee地址: https://gitee.com/simonkingws/webconfig-springboot

该项目不仅仅处理了通用的参数,还增加常用的拦截器、重复提交、链路追踪、在线运维等方法:

Howland电流源电路:从理论到实践的深度解析与优化策略 本文深度解析了Howland电流源电路的工作原理,从经典四电阻架构的“虚短”“虚断”分析,到其实现宽带恒流输出的理论优势。重点剖析了实践中因电阻精度、运放参数及PCB布局导致的性能瓶颈,并提供了引入第五电阻、优化运放选型等关键优化策略,旨在将输出阻抗从kΩ级提升至MΩ级,以满足精密测量与传感器驱动等应用需求。 阅读详情

相关推荐

单细胞实战之亚细胞分群从T/NK至CD4+T细胞——从入门到进阶(中级篇1)

亚细胞分群从T/NK至CD4+T细胞

zfyyzhys的博客 6389

记录一下全局异常 GlobalExceptionHandler 不生效问题

一、正常加载时的情况 加载正常时,在idea 中可以发现是有绿色的小图标的,然而我们的并没有这个图标,表示这个类没有被spring的bend管理到 二、使用排除法找问题 1、当发现全局异常没有执行, 首先打断点看,发现方法在static 内抛出的 throw new ErrorException 于是把代码 throw 放到最外层的入口处, 发现不是static的问题, (排除) 2、然后看了整个项目是否还存在另外一个全局异常类, 结果并没有(排除) 3、于是猜想是不是springboot的版本问题

ws - 兮的博客空间 1万+

光伏发电系统模拟及其发电预测开源python工具pvlib

本文介绍光伏发电系统模拟及其发电预测开源python工具pvlib,以及举例介绍获取辐照度方法、光伏发电预测思路。

肖永威的专栏 1万+

@ControllerAdvice + @ExceptionHandler 全局处理 Controller 层异常

@ControllerAdvice + @ExceptionHandler 全局处理 Controller 层异常...

kinginblue的专栏 9万+

Android 导出aar 用于Unity 百度语音唤醒

AndroidStudio版本下载地址Android Studio Arctic Fox Unity版本2021.3.26f1c1 新建AndroidStudio工程 新建module 这里注意一下,网上很多教程说要注意包名要和unity一样,但是测试下来,不一样也可以的 在新建的module里添加这两个jar包,并点击右键add as library 导入jniLibs 右键创建这几个package 代码就安装这个大佬的写参考代码 需要注意的的是在WakeupListener脚本里U

weixin_38777563的博客 633

Titans:谷歌新型神经记忆架构,突破 Transformer 长序列处理的瓶颈

Titans 是谷歌推出的新型神经网络架构,通过神经长期记忆模块突破 Transformer 在处理长序列数据时的瓶颈,支持并行计算,显著提升训练效率。

士多啤梨先生の博客 991

Controller 层代码就该这么写,简洁又优雅!

Controller 层代码就该这么写,简洁又优雅!

欢迎朋友,我是一名十多年开发经验的新手,希望多多指教。 1337

谷歌账号国家或地区设置修改全攻略:条件、流程与避坑指南

谷歌账号的国家或地区设置与付款资料、账号历史行为及登录环境强绑定,修改时需满足特定条件。其原理在于地区作为索引键,关联内容库、货币结算和风控策略。正确切换可解决内容推荐错乱、付款失败等问题,适用于搬家、订阅区域限定服务等场景。操作前需在目标地区创建付款资料、清空余额并取消订阅,且一年仅能修改一次。本文详解从准备到验证的完整流程,并揭示官方文档未提及的实操陷阱,助你安全完成地区切换。

weixin_42521558的博客 2324

【禁止血压飙升】阿里大佬写的 Controller 太优雅了!

转自:https://juejin.cn/post/7357172505961578511见过几千行代码的 controller吗?我见过。见过全是 try catch 的 controller 吗,我见过。见过全是字段校验的 controller 吗,我见过。见过全是业务代码的 controller 吗?不好意思,我们公司很多业务写在 controller 的。不优雅的 controller@R...

HollisChuang's Blog 276

IOPaint模型部署教程

IOPaint是一款功能全面且强大的AI图像处理工具,它不仅免费开源,还由SOTA(State-of-the-Art,即最先进)AI模型驱动,为图像编辑和修复带来了前所未有的便利和高效。以下是对IOPaint的详细介绍:

SJJS_1的博客 1542

sap资产post记录查询时候 报错 如图

SAP资产过账查询报错AA653解决方案摘要 错误原因:系统无法计算会计年度期间的最后一天,导致资产折旧/过账失败。常见原因包括:1)未创建2025年期间版本;2)期间日期映射不完整;3)依赖日历但未生成2025年日历。 解决方法: 通过OB37维护2025年12/13个完整期间 补充缺失期间或修正结束日期 使用SCAL生成2025年工厂日历 验证步骤: 检查T009R表2025年记录完整性 测试AFAB折旧运行 重新查询资产过账 提示:建议财务团队统一维护跨年度期间数据,并在测试系统验证变更。

金牌架构师 834

如何优雅的写 Controller 层代码?

点击上方“芋道源码”,选择“设为星标”管她前浪,还是后浪?能浪的浪,才是好浪!每天 10:33更新文章,每天掉亿点点头发...源码精品专栏原创 | Java 2021超神之路,很肝~中文详细注释的开源项目RPC 框架 Dubbo 源码解析网络应用框架 Netty 源码解析消息中间件 RocketMQ 源码解析数据库中间件 Sharding-JDBC 和 MyCAT ...

芋艿V 311

深度解读 ABAP CDS View 中的 @Analytics.dataCategory: #FACT 注解

注解在 ABAP CDS View 中具有重要意义。它不仅定义了当前视图在数据模型中的角色,还为后续的分析和报告奠定了基础。在实际开发中,通过合理使用这一注解,可以显著提升数据模型的可用性和系统性能。无论是简单的 KPI 展示还是复杂的多维分析,这一注解都能发挥关键作用。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 787

Controller层代码就该这么写,简洁又优雅!

????这是一个或许对你有用的社群????一对一交流/面试小册/简历优化/求职解惑,欢迎加入「芋道快速开发平台」知识星球。下面是星球提供的部分资料:《项目实战(视频)》:从书中学,往事中“练”《互联网高频面试题》:面朝简历学习,春暖花开《架构 x 系统设计》:摧枯拉朽,掌控面试高频场景题《精进 Java 学习指南》:系统学习,互联网主流技术栈《必读 Java 源码专栏》:知其然,知其所以然????这是一个或许...

weixin_44421461的博客 292

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

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

点击上方“Java基基”,选择“设为星标”做积极的人,而不是积极废人!每天14:00更新文章,每天掉亿点点头发...源码精品专栏原创 | Java 2021超神之路,很肝~中文详细注释的开源项目RPC 框架 Dubbo 源码解析网络应用框架 Netty 源码解析消息中间件 RocketMQ 源码解析数据库中间件 Sharding-JDBC 和 MyCAT 源码解析作业调度中间件 Elast...

weixin_44421461的博客 499

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

Controller层代码这么写,简洁又优雅!

点击上方“芋道源码”,选择“设为星标”管她前浪,还是后浪?能浪的浪,才是好浪!每天 10:33更新文章,每天掉亿点点头发...源码精品专栏原创 | Java 2021超神之路,很肝~中文详细注释的开源项目RPC 框架 Dubbo 源码解析网络应用框架 Netty 源码解析消息中间件 RocketMQ 源码解析数据库中间件 Sharding-JDBC 和 MyCAT ...

芋艿V 169

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

从阿里跳槽来的工程师,写个Controller都这么优雅!

往期热门文章:一份充电大全~开源了! Java Bean 转 Map 的巨坑,注意了!!!文章来源:https://juejin.cn/post/7123091045071454238目录一个优秀的Controller层逻辑从现状看问题改造 Controller 层逻辑总结一个优秀的Controller层逻辑说到 Controller,相信大家都不陌生,它可以很方便地对外提供数据接口。它的定位,...

95

看看人家那后端API接口写得,那叫一个优雅!

点击上方“Java基基”,选择“设为星标”做积极的人,而不是积极废人!源码精品专栏原创 | Java 2020超神之路,很肝~中文详细注释的开源项目RPC 框架 Dubbo 源码解析...

weixin_44421461的博客 254

舒服!老板连夜夸我Controller写得优雅

???? 这是一个或许对你有用的社群???? 一对一交流/面试小册/简历优化/求职解惑,欢迎加入「芋道快速开发平台」知识星球。下面是星球提供的部分资料:《项目实战(视频)》:从书中学,往事上“练”《互联网高频面试题》:面朝简历学习,春暖花开《架构 x 系统设计》:摧枯拉朽,掌控面试高频场景题《精进 Java 学习指南》:系统学习,互联网主流技术栈《必读 Java 源码专栏》:知其然,知其所以然????这是一个或许...

芋艿V 107
上一篇: 爽呀,JDK17里面可以使用的神仙语法(三)
下一篇: 从零到高手:掌握XPath的5个神技,让你数据抓取快人10倍
智_永无止境
博客等级 码龄10年 803粉丝 404原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

智_永无止境

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值