您好!欢迎来到爱源码

爱源码

热门搜索: 抖音快手短视频下载   

【项目练习】SpringBoot三招组合拳手把手教你打出优雅的背景界面。 {源代码交易}

  • 时间:2022-09-06 04:23 编辑: 来源: 阅读:283
  • 扫一扫,手机访问
摘要:【项目练习】SpringBoot三招组合拳手把手教你打出优雅的背景界面。 {源代码交易}
项目驱动学习,实践检验的真知前言一个后台接口大致分为四个部分:接口地址(url),接口请求方式(get,post等。),请求数据(request)和响应数据(response) 如何构建这些部分因公司而异,没有“最佳”的标准。但是一个优秀的后端接口和一个糟糕的后端接口是有很大区别的,最重要的关键点就是看能不能标准化!本文一步一步地展示了如何构建一个优秀的后端接口系统。系统建好以后,就标准化了,新建一个后端接口会非常容易。 文末我贴出了项目演示的github地址,并克隆运行,每个优化记录我都提交了代码,大家可以清楚的看到项目的改进过程!这里需要的包是SpringBoot配置项目,本文的重点是后台接口,所以只需要导入一个spring-boot-starter-web包: 参数验证一个接口一般会对参数(请求数据)进行安全验证,所以参数验证的重要性不用多说,所以如何验证参数很重要。 业务层验证首先我们来看看最常见的做法,参数验证是在业务层进行的:公共字符串adduser(user user){ if(user = = null | | user . getid()= = null | | user . get account()= = null | | user . get password()= = null | | user . getemail()= = null){ return“对象或对象字段不能为空”;} if(stringitils . isempty(user . get account())| | | stringitils . isempty(user . get password())| | stringitils . isempty(user . getemail()){ return“不能输入空字符串”;} if (user.getAccount()。length()& lt;6 || user.getAccount()。length()>;1) {return "账户长度必须为6-11个字符";} if (user.getPassword()。length()& lt;6 || user.getPassword()。length()>;16) {return“密码长度必须为6-16个字符”;}如果(!pattern . matches("[A-ZA-Z0-9 _-]+@[A-ZA-Z0-9 _-]+(\ \。[A-ZA-Z0-9 _-]+)+$ ",user . getemail())}//参数验证后在这里写业务逻辑返回“成功”;}这个当然没有问题,格式也很整齐,一目了然,但是太繁琐了,还没有业务操作。仅仅为了一个参数检查就有这么多行代码是不够优雅的。 我们用Spring Validator和Hibernate Validator改进一下,方便参数验证!这两组验证器依赖包已经包含在前面提到的web依赖包中,所以可以直接使用。 验证器+验证器的BindResult可以非常方便的制定验证规则,帮助你自动完成验证。 首先,向条目中要验证的字段添加注释。每个评论对应不同的验证规则,验证失败后可以公式化信息:@ data public class user { @ not null(message = " customer id不能为空")private Long id@NotNull(message = "客户账户不能为空")@Size(min = 6,max = 11,message = "账户长度必须为6-11个字符")私有字符串账户;@NotNull(message = "客户密码不能为空")@Size(min = 6,max = 11,message = "密码长度必须为6-16个字符")私有字符串密码;@NotNull(message = "客户邮箱不能为空")@Email(message = "邮件格式不正确")私有字符串Email;}验证规则和错误警告信息配置完成后,在界面上需要验证的参数添加@Valid注释,添加BindResult参数,就可以方便的完成验证:@ rest controller @ request mapping(" user ")public class user controller { @ autowired private userservice userservice;@ postmapping ("/adduser ")公共字符串adduser(@ request body @ valid user user,binding result binding result){//如果有任何参数检查失败,则将错误信息封装到一个对象中,组装到binding result for(object error error:binding result . getallerrors()){ return error . getdefaultmessage();} return userService.addUser(用户);}}这样,当请求数据传递到接口时,验证器会自动完成验证,验证结果会封装在BindingResult中。如果有错误消息,我们会直接返回给前台,业务逻辑代码根本不会执行。 此时不再需要业务层的校验码:公共字符串adduser(user user){//直接写业务逻辑返回“成功”;}现在可以看一下参数验证效果了。 我们故意向该接口传递一个不符合验证规则的参数。我们先给接口传一个错误数据,然后故意传不符合验证条件的密码字段:{"account": "12345678 "," image": "123 @ qq.com "," ID": 0," password": "123"}然后再看。不难看出,使用Validator有以下优点:简化代码,前面业务层的一大段验证码被省略了。 简单易用,所以很多验证规则都可以轻松实现,比如邮件格式验证。在自己写一长串正则表达式之前,很容易出错,可以用Validator直接做一个注释。 (验证规则评论比较多,大家自己看懂。)降低耦合度,使用Validator可以让业务层只关注业务逻辑,摆脱基本的参数验证逻辑。 使用Validator+ BindingResult是一种非常方便实用的参数检查方式,实际开发中很多项目都是这么做的。但是还是不方便,因为每次写接口都要添加一个BindingResult参数,然后提取错误信息返回前台。 这就有点麻烦了,而且有大量的重复代码(虽然这个重复代码可以封装成一个方法)。 我们可以去掉BindingResult步骤吗?当然可以!validator+自动抛出异常。我们完全可以去掉BindingResult这一步:@ post mapping("/adduser ")public string adduser(@ request body @ valid user){ return userservice . adduser(user);}去掉之后会怎么样?我们直接实验吧,或者像以前一样只给接口传一个不符合校验规则的参数。 这时我们可以观察控制台,发现界面抛出了一个MethodNumentNotValueException异常:image居然达到了想要的效果。如果参数验证失败,则不会执行下一个业务逻辑。如果BindingResult被移除,异常会自动抛出,业务逻辑不会自然执行。 也就是说,我们完全没有必要添加相关的BindingResult相关操作。 然而,事情还没有结束。引发了异常,但是我们没有编写代码来返回错误消息。如果参数验证失败,前台会回复什么数据?我们来看看刚才发生异常后接口响应的数据:image,没错,它直接将错误对象的全部信息响应到前台!这个很难受,但是处理这个问题也很简单,就是我们接下来要讲的全局异常解决方案!如果全局异常解析参数检查失败,将自动引发异常。当然,我们不能手动捕捉异常来解决它,或者我们不妨使用前面的BindingResult方法。 你不想手动捕捉这个异常,但是又要解决,那就用SpringBoot全局异常解决方案,达到一劳永逸的效果!基本上,首先我们需要创建一个新的类,给这个类添加@ControllerAdvice或者@RestControllerAdvice注释,这个类就会被配置为全局解决方案类。 (这取决于你的控制器层用的是@Controller还是@RestController)。然后,在该类中创建一个新方法,向该方法添加@ExceptionHandler注释,并指定要解决的异常类型。然后,在方法中写出异常的运算逻辑,然后完成异常的全局求解!现在来演示一下参数验证失败时抛出的MethodargumentNotValidity异常的全局解决方案:@ restcontrolleradvice public Class Exception controller device { @ Exception Handler(method argument ntnotvalidexception . Class)public String MethodArgumentNotValidException Handler(methodargumentnotvalid Exception e){//Get object error object error object error = e . getbinding result()。GetAllerrors()。从异常对象中获取(0);//然后提取错误提醒信息返回returnobjecterror . getdefaultmessage();}}}我们来看看这次验证失败后的响应数据:图片没错,这次我们返回了自己做的错误警告信息!我们通过全局异常解决优雅地实现了期望的功能!如果以后要写接口参数验证,只需要给参与的成员变量加上验证器验证规则注释,然后给参数加上@Valid注释就可以完成验证了。如果验证失败,我们将自动返回一个错误警告消息,没有任何其他代码!当然,自己设置异常全局解不可能只解决一个异常,其目的也不仅仅是优化一个参数验证方法。 在实际开发中,如何解决异常其实是一件很麻烦的事情。 传统的异常解析一般有以下麻烦:是否尝试...捕捉或抛出异常,是在控制器层解决还是在服务层解决,是在dao层通过什么都不做还是返回特定数据来解决。如果返回什么数据就返回什么数据,我们无法提前捕捉到所有的异常。如果有一个未被捕获的异常,我们该怎么办?所有这些问题都可以通过全局异常解决来处理,也称为统一异常解决。全球统一解决方案代表什么?代表规范!有了规范,很多问题都会迎刃而解!大家都已经知道全局异常解析的基本用法了。接下来,我们将进一步规范项目中的异常解决方案:自行设置异常。 在很多情况下,我们需要手动抛出异常。比如在业务层,当某些条件不符合业务逻辑时,我可以手动抛出异常来触发事务回滚。 手动抛出异常最简单的方法是抛出new RuntimeException(“异常信息”),但最好使用自己的设置:设置自己的异常可以携带更多信息,不像这样只能携带一个字符串。 很多人在项目的开发中往往负责不同的模块,自己设置异常可以统一外部异常呈现的方式。 设置自己的异常语义更清晰,一看就知道是项目中手动抛出的异常。 现在开始写一个自设置异常:@Getter //只有Getter方法,没有setter公共类API异常扩展运行时异常{ private intcode私有字符串msgpublic exception(){ this(1001,“接口错误”);} public API exception(String msg){ this(1001,msg);} public APIException(int code,String msg){ super(msg);this.code = codethis.msg = msg}}}记得刚才在全局异常解析类中添加了我们自己设置异常的解决方案:@ exception handler(API exception . class)公共字符串API exception handler(API exception e){ return e . getmsg();}这样,异常的解决方案就标准化了。当然也可以加入异常的解决方案,这样无论发生什么异常,我们都可以屏蔽掉,然后把数据回复到前台。但是建议在项目最终上线的时候这样做,可以屏蔽掉错误信息,暴露到前台。为了方便开发中的调试,最好不要这样做。 现在全局异常解析和自定义异常都已经完成了,不知道大家有没有发现一个问题,就是当我们抛出自定义异常时,全局异常解析只向前台回复异常中的错误消息msg,而不返回错误代码。 这就引出了我们接下来要讲的:数据的统一响应。现在我们已经标准化了参数验证和异常解决,但是还没有标准化响应数据!比如我想获取一个分页信息数据,成功获取自然会返回数据列表。如果我获取不成功,后端会响应异常信息,也就是一个字符串,说明前端开发者根本不知道后端数据会是什么样子!所以统一响应数据是前后规范中必须的!设置统一响应体和统一数据响应的第一步是自己设置一个响应体类。无论后端运行正常还是异常,响应前端的数据格式都是不变的!那么如何定义响应者呢?可以参考我们自己的异常类设置,或者一个响应信息代码和响应信息描述msg: @ getter公共类resultvo < T & gt{/* * *状态码,如1000为成功响应*/ private int码;/* * *响应信息,用于解释响应*/privatestringmg;/* * *响应的具体数据*/私有T数据;public result VO(T data){ this(1000,“成功”,数据);} public ResultVO(int code,String msg,T data){ this . code = code;this.msg = msgthis.data = data}}然后我们修改全局异常解析的返回值:@ exception handler(API exception . class)public result VO < String & gt;API exception handler(API exception one){//注意这里的返回类型是设置响应体返回新结果vo < & gt(e.getCode(),“响应失败”,e . getmsg());} @ exception handler(methoargumentnotvalidexception . class)public result VO & lt;字符串& gtmethodgargumentnotvaliexception handler(methodgargumentnotvaliexception e){ ObjectError ObjectError = e . getbinding result()。getAllErrors()。get(0);//注意这里的返回类型是设置响应体返回新结果VO < & gt(1001,“参数验证失败”,object error . getdefaultmessage());}我们来看看如果此时发生异常,会有哪些数据响应前台:imageOK。这个异常信息反应很好。状态码、响应描述、错误提醒数据全部返回前台,所有异常都将返回相同的格式!此处修复了异常。当我们到达接口时,不要忘记修改返回类型。让我们添加一个新的接口来看看效果:@ get mapping("/getuser ")public resultvo < User & gt;getUser(){ User User = new User();user . setid(1L);user . set account(" 12345678 ");user . set password(" 12345678 ");user . set email(" 123 @ QQ . com ");返回新结果& lt& gt(用户);}我们来看看响应返回正确的情况下的效果:图片。这样无论是正确响应还是发生异常,响应数据的格式都是统一的,非常标准!数据格式标准化了,但是响应代码code和响应信息msg还没有标准化!大家发现不管是正确响应还是异常响应,响应代码和响应信息都可以随心所欲的设置。如果10个开发人员为同一类型的响应编写10个不同的响应代码,那么这个统一响应体的格式规范就没有意义了!因此,有必要对响应代码和响应信息进行标准化。 响应代码的枚举使用枚举来标准化响应体中的响应代码和响应信息是绝对合适的。我们现在创建一个响应代码枚举类:@ getter公共枚举结果代码{success (1000,“操作成功”)、FAILED(1001,“响应失败”)、VALIDATE_FAILED(1002,“参数验证失败”)、private int代码;私有字符串msgResultCode(int code,String msg){ this . code = code;this.msg = msg}}然后修改响应体的构造方法,使其只能接受响应代码枚举来设置响应代码和响应信息:public result VO(tdata){ this(result code。成功,数据);} public result VO(result code result code,T data){ this . code = result code . get code();this . msg = result code . getmsg();this.data = data}然后同时修改全局异常解析的响应代码设置方式:@ exception handler(API exception . class)public result VO < String & gt;API exception handler(API exception one){//注意,这里传递的响应代码枚举返回新的结果vo < & gt(结果代码。失败,e . getmsg());} @ exception handler(methoargumentnotvalidexception . class)public result VO & lt;字符串& gtmethodgargumentnotvaliexception handler(methodgargumentnotvaliexception e){ ObjectError ObjectError = e . getbinding result()。getAllErrors()。get(0);//注意,这里传递的响应代码枚举返回新的结果vo < & gt(结果代码。VALIDATE_FAILED,object error . getdefaultmessage());}这样,响应代码和响应信息就只能是枚举中指定的那些,从而真正规范和统一了响应数据格式、响应代码和响应信息!解决了全局响应数据接口返回统一响应体+异常也返回统一响应体的问题。其实这已经很好了,但是还有优化的空间。 要知道一个项目定义几百个接口是很正常的。如果每个接口在返回数据时都要用响应体包装,那就有点麻烦了。有什么办法可以省去这个包装过程?当然有滴滴,但还是要全球解决。 首先,创建一个类,并对其进行注释,使其成为一个全局解决方案类。 然后继承ResponseBodyAdvice接口并重写方法,就可以增强我们的控制器了。参见代码和注释:@ restControllerAdvice(base packages = { " com . rude crab . demo . controller " })//注意,添加包公共类响应控制器实现响应体建议< Object & gt{ @ Override public boolean supports(method parameter returnType,Class & lt?扩展HttpMessageConverter & lt?& gt& gtAClass) {//如果接口本身返回的类型是ResultVO,则不需要额外操作,返回false return!returnType.getParameterType()。equals(result VO . class);} @ Override public Object before body write(Object data,MethodParameter returnType,MediaType mediaType,Class & lt?扩展HttpMessageConverter & lt?& gt& gtAclass,server http request request,server http response response){//string类型不能直接包装,因此,如果(返回类型,要做一些特殊的解决方案。getgenericparametertype()。equals(string . class)){ object mapper object mapper = new object mapper();Try {//将数据包加载到ResultVO后,转换成json字符串,响应前台returnobjectmapper . writevalueasstring(new result VO 真正对返回数据的操作还是在beforeBodyWrite方法中,我们可以直接在这个方法中对数据进行打包,这样就不需要在每个接口上打包数据包,省去了很多麻烦。 我们现在可以去掉接口的数据包装器来看看效果:@ get mapping("/getuser ")public user getuser(){ user user = new user();user . setid(1L);user . set account(" 12345678 ");user . set password(" 12345678 ");user . set email(" 123 @ QQ . com ");//注意,这里是直接返回的用户类型,没有用ResultVO包装返回用户;}然后我们来看响应数据:image成功打包数据!注意:在beforeBodyWrite方法中,包数据不能直接转换字符串类型数据,所以需要特殊的解决方案。这里不谈太多细节,有兴趣的可以深入了解。 总结自此,构建了整个后端接口的基础体系,通过验证器+自动异常抛出完成了便捷的参数验证,通过全局异常求解+自行设置异常完成了异常操作的规范,通过数据统一响应完成了响应数据的规范。多方面的组装非常优雅的完成了后端接口的协调,让开发者有更多的经验,关注业务逻辑代码。轻松构建后端接口。还是那句话,项目系统怎么搭建,后端接口怎么写,没有绝对统一的标准。并不是说遵循这篇文章就是最好的。你想做什么都可以。你可以根据自己的想法对本文的每一个环节进行编码。我只是提供了一个想法!最后,把这个项目的github地址放在这里,克隆可以直接在本地运行,而且我对每一条优化记录都做了代码提交,所以你可以清楚的看到项目的改进过程。如果对你有帮助,请在github上点个星,我会继续升级很多【项目实践】!根据这篇文章,我还写了一篇后续。也可以阅读:【项目实践】如何在统一后台接口规范的同时优雅地扩展规范。


  • 全部评论(0)
资讯详情页最新发布上方横幅
最新发布的资讯信息
【技术支持|常见问题】1556原创ng8文章搜索页面不齐(2024-05-01 14:43)
【技术支持|常见问题】1502企业站群-多域名跳转-多模板切换(2024-04-09 12:19)
【技术支持|常见问题】1126完美滑屏版视频只能显示10个(2024-03-29 13:37)
【技术支持|常见问题】响应式自适应代码(2024-03-24 14:23)
【技术支持|常见问题】1126完美滑屏版百度未授权使用地图api怎么办(2024-03-15 07:21)
【技术支持|常见问题】如何集成阿里通信短信接口(2024-02-19 21:48)
【技术支持|常见问题】算命网微信支付宝产品名称年份在哪修改?风水姻缘合婚配对_公司起名占卜八字算命算财运查吉凶源码(2024-01-07 12:27)
【域名/主机/服务器|】帝国CMS安装(2023-08-20 11:31)
【技术支持|常见问题】通过HTTPs测试Mozilla DNS {免费源码}(2022-11-04 10:37)
【技术支持|常见问题】别告诉我你没看过邰方这两则有思想的创意广告! (2022-11-04 10:37)

联系我们
Q Q:375457086
Q Q:526665408
电话:0755-84666665
微信:15999668636
联系客服
企业客服1 企业客服2 联系客服
86-755-84666665
手机版
手机版
扫一扫进手机版
返回顶部