当前位置: 首页 > news >正文

北京营销型网站制作优秀简历模板

北京营销型网站制作,优秀简历模板,爱范儿 wordpress,WordPress全球用户量20191、前言 随着互联网的高速发展#xff0c;前端页面的展示、交互体验越来越灵活、炫丽#xff0c;响应体验也要求越来越高#xff0c;后端服务的高并发、高可用、高性能、高扩展等特性的要求也愈加苛刻#xff0c;从而导致前后端研发各自专注于自己擅长的领域深耕细作。 然…1、前言 随着互联网的高速发展前端页面的展示、交互体验越来越灵活、炫丽响应体验也要求越来越高后端服务的高并发、高可用、高性能、高扩展等特性的要求也愈加苛刻从而导致前后端研发各自专注于自己擅长的领域深耕细作。 然而带来的另一个问题前后端的对接界面双方却关注甚少没有任何接口约定规范情况下各自干各自的导致我们在产品项目开发过程中前后端的接口联调对接工作量占比在30%-50%左右甚至会更高。往往前后端接口联调对接及系统间的联调对接都是整个产品项目研发的软肋。 本文的主要初衷就是规范约定先行尽量避免沟通联调产生的不必要的问题让大家身心愉快地专注于各自擅长的领域。 2、为何要分离 目前现有前后端开发模式“后端为主的MVC时代”如下图所示 后端为主的MVC时代 代码可维护性得到明显好转MVC 是个非常好的协作模式从架构层面让开发者懂得什么代码应该写在什么地方。为了让 View 层更简单干脆还可以选择 Velocity、Freemaker 等模板使得模板里写不了 Java 代码。 看起来是功能变弱了但正是这种限制使得前后端分工更清晰。然而依旧并不是那么清晰这个阶段的典型问题是 前端开发重度依赖开发环境开发效率低。 这种架构下前后端协作有两种模式一种是前端写demo写好后让后端去套模板 。淘宝早期包括现在依旧有大量业务线是这种模式。好处很明显demo 可以本地开发很高效。不足是还需要后端套模板有可能套错套完后还需要前端确定来回沟通调整的成本比较大。 另一种协作模式是前端负责浏览器端的所有开发和服务器端的 View 层模板开发支付宝是这种模式。好处是 UI 相关的代码都是前端去写就好后端不用太关注不足就是前端开发重度绑定后端环境环境成为影响前端开发效率的重要因素。 前后端职责依旧纠缠不清。 Velocity 模板还是蛮强大的变量、逻辑、宏等特性依旧可以通过拿到的上下文变量来实现各种业务逻辑。这样只要前端弱势一点往往就会被后端要求在模板层写出不少业务代码。 还有一个很大的灰色地带是 Controller页面路由等功能本应该是前端最关注的但却是由后端来实现。Controller 本身与 Model 往往也会纠缠不清看了让人咬牙的业务代码经常会出现在 Controller 层。这些问题不能全归结于程序员的素养否则 JSP 就够了。 对前端发挥的局限。 性能优化如果只在前端做空间非常有限于是我们经常需要后端合作才能碰撞出火花但由于后端框架限制我们很难使用Comet、Bigpipe等技术方案来优化性能。 总上所述就跟为什麽要代码重构一样 关注点分离 职责分离 对的人做对的事 更好的共建模式 快速的反应变化 3、什么是分离 我们现在要做的前后分离第一阶段“基于 Ajax 带来的 SPA 时代”如图 基于 Ajax 带来的 SPA 时代 这种模式下前后端的分工非常清晰前后端的关键协作点是 Ajax 接口。看起来是如此美妙但回过头来看看的话这与 JSP 时代区别不大。复杂度从服务端的 JSP 里移到了浏览器的 JavaScript浏览器端变得很复杂。类似 Spring MVC这个时代开始出现浏览器端的分层架构 浏览器端的分层架构 对于这一SPA阶段前后端分离有几个重要挑战 前后端接口的约定。 如果后端的接口一塌糊涂如果后端的业务模型不够稳定那么前端开发会很痛苦。这一块在业界有 API Blueprint 等方案来约定和沉淀接口在阿里不少团队也有类似尝试通过接口规则、接口平台等方式来做。有了和后端一起沉淀的接口规则还可以用来模拟数据使得前后端可以在约定接口后实现高效并行开发。 相信这一块会越做越好。 前端开发的复杂度控制。 SPA 应用大多以功能交互型为主JavaScript 代码过十万行很正常。大量 JS 代码的组织与 View 层的绑定等都不是容易的事情。典型的解决方案是业界的 Backbone但 Backbone 做的事还很有限依旧存在大量空白区域需要挑战。 4、如何做分离 4.1 职责分离 职责分离 前后端仅仅通过异步接口(AJAX/JSONP)来编程 前后端都各自有自己的开发流程构建工具测试集合 关注点分离前后端变得相对独立并松耦合 4.2 开发流程 后端编写和维护接口文档在 API 变化时更新接口文档 后端根据接口文档进行接口开发 前端根据接口文档进行开发 Mock平台 开发完成后联调和提交测试 Mock 服务器根据接口文档自动生成 Mock 数据实现了接口文档即API 开发流程 4.3 具体实施 现在已基本完成了接口方面的实施 接口文档服务器可实现接口变更实时同步给前端展示 Mock接口数据平台可实现接口变更实时Mock数据给前端使用 接口规范定义很重要接口定义的好坏直接影响到前端的工作量和实现逻辑具体定义规范见下节 接口文档Mock平台服务器 5. 接口规范V1.0.0 5.1 规范原则 接口返回数据即显示前端仅做渲染逻辑处理 渲染逻辑禁止跨多个接口调用 前端关注交互、渲染逻辑尽量避免业务逻辑处理的出现 请求响应传输数据格式JSONJSON数据尽量简单轻量避免多级JSON的出现 5.2 基本格式 5.2.1 请求基本格式 GET请求、POST请求必须包含key为body的入参所有请求数据包装为JSON格式并存放到入参body中示例如下 GET请求 xxx/login?body{username:admin,password:123456,captcha:scfd,rememberMe:1}POST请求 5.2.2 响应基本格式 {code: 200,data: {message: success} }code : 请求处理状态 200: 请求处理成功 500: 请求处理失败 401: 请求未认证跳转登录页 406: 请求未授权跳转未授权提示页 data.message: 请求处理消息 code200 且 data.messagesuccess: 请求处理成功 code200 且 data.message!success: 请求处理成功, 普通消息提示message内容。 code500: 请求处理失败警告消息提示message内容 5.3 响应实体格式 {code: 200,data: {message: success,entity: {id: 1,name: XXX,code: XXX}} }data.entity: 响应返回的实体数据 5.4 响应列表格式 data.list: 响应返回的列表数据 5.5 响应分页格式 {code: 200,data: {recordCount: 2,message: success,totalCount: 2,pageNo: 1,pageSize: 10,list: \[{id: 1,name: XXX,code: H001},{id: 2,name: XXX,code: H001} \],totalPage: 1} }data.recordCount: 当前页记录数 data.totalCount: 总记录数 data.pageNo: 当前页码 data.pageSize: 每页大小 data.totalPage: 总页数 5.6 特殊内容规范 5.6.1 下拉框、复选框、单选框 由后端接口统一逻辑判定是否选中通过isSelect标示是否选中示例如下 {code: 200,data: {message: success,list: \[{id: 1,name: XXX,code: XXX,isSelect: 1}, {id: 1,name: XXX,code: XXX,isSelect: 0}\]} }禁止下拉框、复选框、单选框判定选中逻辑由前端来处理统一由后端逻辑判定选中返回给前端展示 5.6.2 Boolean类型 关于Boolean类型JSON数据传输中一律使用1/0来标示1为是/True0为否/False 5.6.3 日期类型 关于日期类型JSON数据传输中一律使用字符串具体日期格式因业务而定 6、未来的大前端 目前我们现在用的前后端分离模式属于第一阶段由于使用到的一些技术jquery等对于一些页面展示、数据渲染还是比较复杂不能够很好的达到复用。对于前端还是有很大的工作量。 下一阶段可以在前端工程化方面对技术框架的选择、前端模块化重用方面可多做考量。也就是要迎来“前端为主的 MV* 时代”。大多数的公司也基本都处于这个分离阶段。 最后阶段就是Node 带来的全栈时代完全有前端来控制页面URLController路由等后端的应用就逐步弱化为真正的数据服务业务服务做且仅能做的是提供数据、处理业务逻辑关注高可用、高并发等。 Spring Boot 后端接口规范 一个后端接口大致分为四个部分组成接口地址url、接口请求方式get、post等、请求数据request、响应数据response。虽然说后端接口的编写并没有统一规范要求而且如何构建这几个部分每个公司要求都不同没有什么“一定是最好的”标准但其中最重要的关键点就是看是否规范。 环境说明 因为讲解的重点是后端接口所以需要导入一个spring-boot-starter-web包而lombok作用是简化类前端显示则使用了knife4j具体使用在Spring Boot整合knife4j实现Api文档已写明。另外从springboot-2.3开始校验包被独立成了一个starter组件所以需要引入如下依赖 dependency !--新版框架没有自动引入需要手动引入--groupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-validation/artifactId /dependencydependencygroupIdcom.github.xiaoymin/groupIdartifactIdknife4j-spring-boot-starter/artifactId!--在引用时请在maven中央仓库搜索最新版本号--version2.0.2/version /dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId /dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdoptionaltrue/optional /dependency参数校验 1、介绍 一个接口一般对参数请求数据都会进行安全校验参数校验的重要性自然不必多说那么如何对参数进行校验就有讲究了。一般来说有三种常见的校验方式我们使用了最简洁的第三种方法 业务层校验 Validator BindResult校验 Validator 自动抛出异常 业务层校验无需多说即手动在java的Service层进行数据校验判断。不过这样太繁琐了光校验代码就会有很多 而使用Validator BindingResult已经是非常方便实用的参数校验方式了在实际开发中也有很多项目就是这么做的不过这样还是不太方便因为你每写一个接口都要添加一个BindingResult参数然后再提取错误信息返回给前端简单看一下。 /** * author 公众号码猿技术专栏 */ PostMapping(/addUser) public String addUser(RequestBody Validated User user, BindingResult bindingResult) {// 如果有参数校验失败会将错误信息封装成对象组装在BindingResult里ListObjectError allErrors  bindingResult.getAllErrors();if(!allErrors.isEmpty()){return allErrors.stream().map(o-o.getDefaultMessage()).collect(Collectors.toList()).toString();}// 返回默认的错误信息// return allErrors.get(0).getDefaultMessage();return validationService.addUser(user); }2、Validator 自动抛出异常使用 内置参数校验如下 首先Validator可以非常方便的制定校验规则并自动帮你完成校验。首先在入参里需要校验的字段加上注解,每个注解对应不同的校验规则并可制定校验失败后的信息 Data public class User {NotNull(message  用户id不能为空)private Long id;NotNull(message  用户账号不能为空)Size(min  6, max  11, message  账号长度必须是6-11个字符)private String account;NotNull(message  用户密码不能为空)Size(min  6, max  11, message  密码长度必须是6-16个字符)private String password;NotNull(message  用户邮箱不能为空)Email(message  邮箱格式不正确)private String email; }校验规则和错误提示信息配置完毕后接下来只需要在接口仅需要在校验的参数上加上Valid注解去掉BindingResult后会自动引发异常异常发生了自然而然就不会执行业务逻辑 RestController RequestMapping(user) public class ValidationController {Autowiredprivate ValidationService validationService;PostMapping(/addUser)public String addUser(RequestBody Validated User user) {return validationService.addUser(user);} }现在我们进行测试打开knife4j文档地址当输入的请求数据为空时Validator会将所有的报错信息全部进行返回所以需要与全局异常处理一起使用。 // 使用form data方式调用接口校验异常抛出 BindException // 使用 json 请求体调用接口校验异常抛出 MethodArgumentNotValidException // 单个参数校验异常抛出ConstraintViolationException // 处理 json 请求体调用接口校验失败抛出的异常 ExceptionHandler(MethodArgumentNotValidException.class) public ResultVOString MethodArgumentNotValidException(MethodArgumentNotValidException e) {ListFieldError fieldErrors  e.getBindingResult().getFieldErrors();ListString collect  fieldErrors.stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.toList());return new ResultVO(ResultCode.VALIDATE_FAILED, collect); } // 使用form data方式调用接口校验异常抛出 BindException ExceptionHandler(BindException.class) public ResultVOString BindException(BindException e) {ListFieldError fieldErrors  e.getBindingResult().getFieldErrors();ListString collect  fieldErrors.stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.toList());return new ResultVO(ResultCode.VALIDATE_FAILED, collect); }3、分组校验和递归校验 分组校验有三个步骤 定义一个分组类或接口 在校验注解上添加groups属性指定分组 Controller方法的Validated注解添加分组类 public interface Update extends Default{ } Data public class User {NotNull(message  用户id不能为空,groups  Update.class)private Long id;...... } PostMapping(update) public String update(Validated({Update.class}) User user) {return success; }如果Update不继承DefaultValidated({Update.class})就只会校验属于Update.class分组的参数字段如果继承了会校验了其他默认属于Default.class分组的字段。关注工众号码猿技术专栏回复关键词1111 获取阿里内部java性能调优手册 对于递归校验比如类中类只要在相应属性类上增加Valid注解即可实现对于集合同样适用 4、自定义校验 Spring Validation允许用户自定义校验实现很简单分两步 自定义校验注解 编写校验者类 Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) Retention(RUNTIME) Documented Constraint(validatedBy  {HaveNoBlankValidator.class})// 标明由哪个类执行校验逻辑 public interface HaveNoBlank {// 校验出错时默认返回的消息String message() default 字符串中不能含有空格;Class?[] groups() default { };Class? extends Payload[] payload() default { };/*** 同一个元素上指定多个该注解时使用*/Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })Retention(RUNTIME)Documentedpublic interface List {NotBlank[] value();} } public class HaveNoBlankValidator implements ConstraintValidatorHaveNoBlank, String {Overridepublic boolean isValid(String value, ConstraintValidatorContext context) {// null 不做检验if (value  null) {return true;}// 校验失败return !value.contains( );// 校验成功} }全局异常处理 参数校验失败会自动引发异常我们当然不可能再去手动捕捉异常进行处理。但我们又不想手动捕捉这个异常又要对这个异常进行处理那正好使用SpringBoot全局异常处理来达到一劳永逸的效果 1、基本使用 首先我们需要新建一个类在这个类上加上ControllerAdvice或RestControllerAdvice注解这个类就配置成全局处理类了。 这个根据你的Controller层用的是Controller还是RestController来决定。 然后在类中新建方法在方法上加上ExceptionHandler注解并指定你想处理的异常类型接着在方法内编写对该异常的操作逻辑就完成了对该异常的全局处理我们现在就来演示一下对参数校验失败抛出的MethodArgumentNotValidException全局处理 /** * author 公众号码猿技术专栏 */ RestControllerAdvice ResponseBody public class ExceptionControllerAdvice {ExceptionHandler(MethodArgumentNotValidException.class)ResponseStatus(HttpStatus.BAD_REQUEST)public String MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {// 从异常对象中拿到ObjectError对象ObjectError objectError  e.getBindingResult().getAllErrors().get(0);// 然后提取错误提示信息进行返回return objectError.getDefaultMessage();}/*** 系统异常 预期以外异常*/ExceptionHandler(Exception.class)ResponseStatus(value  HttpStatus.INTERNAL_SERVER_ERROR)public ResultVO? handleUnexpectedServer(Exception ex) {log.error(系统异常, ex);// GlobalMsgEnum.ERROR是我自己定义的枚举类return new ResultVO(GlobalMsgEnum.ERROR);}/*** 所以异常的拦截*/ExceptionHandler(Throwable.class)ResponseStatus(value  HttpStatus.INTERNAL_SERVER_ERROR)public ResultVO? exception(Throwable ex) {log.error(系统异常, ex);return new ResultVO(GlobalMsgEnum.ERROR);} }我们再次进行测试这次返回的就是我们制定的错误提示信息我们通过全局异常处理优雅的实现了我们想要的功能 以后我们再想写接口参数校验就只需要在入参的成员变量上加上Validator校验规则注解然后在参数上加上Valid注解即可完成校验校验失败会自动返回错误提示信息无需任何其他代码 2、自定义异常 在很多情况下我们需要手动抛出异常比如在业务层当有些条件并不符合业务逻辑而使用自定义异常有诸多优点 自定义异常可以携带更多的信息不像这样只能携带一个字符串。 项目开发中经常是很多人负责不同的模块使用自定义异常可以统一了对外异常展示的方式。关注工众号码猿技术专栏回复关键词1111 获取阿里内部java性能调优手册 自定义异常语义更加清晰明了一看就知道是项目中手动抛出的异常。 我们现在就来开始写一个自定义异常 /** * author 公众号码猿技术专栏 */ Getter //只要getter方法无需setter public class APIException extends RuntimeException {private int code;private String msg;public APIException() {this(1001, 接口错误);}public APIException(String msg) {this(1001, msg);}public APIException(int code, String msg) {super(msg);this.code  code;this.msg  msg;} }然后在刚才的全局异常类中加入如下: //自定义的全局异常ExceptionHandler(APIException.class)public String APIExceptionHandler(APIException e) {return e.getMsg();}这样就对异常的处理就比较规范了当然还可以添加对Exception的处理这样无论发生什么异常我们都能屏蔽掉然后响应数据给前端不过建议最后项目上线时这样做能够屏蔽掉错误信息暴露给前端在开发中为了方便调试还是不要这样做。 另外当我们抛出自定义异常的时候全局异常处理只响应了异常中的错误信息msg给前端并没有将错误代码code返回。这还需要配合数据统一响应。 如果在多模块使用全局异常等公共功能抽象成子模块则在需要的子模块中需要将该模块包扫描加入SpringBootApplication(scanBasePackages {com.xxx}) 数据统一响应 统一数据响应是我们自己自定义一个响应体类无论后台是运行正常还是发生异常响应给前端的数据格式是不变的这里我包括了响应信息代码code和响应信息说明msg首先可以设置一个枚举规范响应体中的响应码和响应信息。 /** * author 公众号码猿技术专栏 */ Getter public enum ResultCode {SUCCESS(1000, 操作成功),FAILED(1001, 响应失败),VALIDATE_FAILED(1002, 参数校验失败),ERROR(5000, 未知错误);private int code;private String msg;ResultCode(int code, String msg) {this.code  code;this.msg  msg;} }自定义响应体 package com.csdn.demo1.global;import lombok.Getter; /** * author 公众号码猿技术专栏 */ Getter public class ResultVOT {/*** 状态码比如1000代表响应成功*/private int code;/*** 响应信息用来说明响应情况*/private String msg;/*** 响应的具体数据*/private T data;public ResultVO(T data) {this(ResultCode.SUCCESS, data);}public ResultVO(ResultCode resultCode, T data) {this.code  resultCode.getCode();this.msg  resultCode.getMsg();this.data  data;} }最后需要修改全局异常处理类的返回类型 RestControllerAdvice public class ExceptionControllerAdvice {ExceptionHandler(APIException.class)public ResultVOString APIExceptionHandler(APIException e) {// 注意哦这里传递的响应码枚举return new ResultVO(ResultCode.FAILED, e.getMsg());}ExceptionHandler(MethodArgumentNotValidException.class)public ResultVOString MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {ObjectError objectError  e.getBindingResult().getAllErrors().get(0);// 注意哦这里传递的响应码枚举return new ResultVO(ResultCode.VALIDATE_FAILED, objectError.getDefaultMessage());} }最后在controller层进行接口信息数据的返回 GetMapping(/getUser) public ResultVOUser getUser() {User user  new User();user.setId(1L);user.setAccount(12345678);user.setPassword(12345678);user.setEmail(123qq.com);return new ResultVO(user); }经过测试这样响应码和响应信息只能是枚举规定的那几个就真正做到了响应数据格式、响应码和响应信息规范化、统一化 还有一种全局返回类如下 Data AllArgsConstructor NoArgsConstructor public class Msg {//状态码private int code;//提示信息private String msg;//用户返回给浏览器的数据private MapString,Object data  new HashMap();public static Msg success() {Msg result  new Msg();result.setCode(200);result.setMsg(请求成功);return result;}public static Msg fail() {Msg result  new Msg();result.setCode(400);result.setMsg(请求失败);return result;}public static Msg fail(String msg) {Msg result  new Msg();result.setCode(400);result.setMsg(msg);return result;}public Msg(ReturnResult returnResult){code  returnResult.getCode();msg  returnResult.getMsg();}public Msg add(String key,Object value) {this.getData().put(key, value);return this;} }全局处理响应数据(可选择) 接口返回统一响应体 异常也返回统一响应体其实这样已经很好了但还是有可以优化的地方。要知道一个项目下来定义的接口搞个几百个太正常不过了要是每一个接口返回数据时都要用响应体来包装一下好像有点麻烦有没有办法省去这个包装过程呢 当然是有的还是要用到全局处理。但是为了扩展性就是允许绕过数据统一响应这样就可以提供多方使用我们可以自定义注解利用注解来选择是否进行全局响应包装 首先创建自定义注解作用相当于全局处理类开关 Retention(RetentionPolicy.RUNTIME) Target({ElementType.METHOD}) // 表明该注解只能放在方法上 public interface NotResponseBody { }其次创建一个类并加上注解使其成为全局处理类。然后继承ResponseBodyAdvice接口重写其中的方法即可对我们的controller进行增强操作具体看代码和注释 package com.csdn.demo1.global;import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;RestControllerAdvice(basePackages  {com.scdn.demo1.controller}) // 注意哦这里要加上需要扫描的包 public class ResponseControllerAdvice implements ResponseBodyAdviceObject {Overridepublic boolean supports(MethodParameter returnType, Class? extends HttpMessageConverter? aClass) {// 如果接口返回的类型本身就是ResultVO那就没有必要进行额外的操作返回false// 如果方法上加了我们的自定义注解也没有必要进行额外的操作return !(returnType.getParameterType().equals(ResultVO.class) || returnType.hasMethodAnnotation(NotResponseBody.class));}Overridepublic Object beforeBodyWrite(Object data, MethodParameter returnType, MediaType mediaType, Class? extends HttpMessageConverter? aClass, ServerHttpRequest request, ServerHttpResponse response) {// String类型不能直接包装所以要进行些特别的处理if (returnType.getGenericParameterType().equals(String.class)) {ObjectMapper objectMapper  new ObjectMapper();try {// 将数据包装在ResultVO里后再转换为json字符串响应给前端return objectMapper.writeValueAsString(new ResultVO(data));} catch (JsonProcessingException e) {throw new APIException(返回String类型错误);}}// 将原本的数据包装在ResultVO里return new ResultVO(data);} }重写的这两个方法是用来在controller将数据进行返回前进行增强操作supports方法要返回为true才会执行beforeBodyWrite方法所以如果有些情况不需要进行增强操作可以在supports方法里进行判断。 对返回数据进行真正的操作还是在beforeBodyWrite方法中我们可以直接在该方法里包装数据这样就不需要每个接口都进行数据包装了省去了很多麻烦。此时controller只需这样写就行了 GetMapping(/getUser) //NotResponseBody  //是否绕过数据统一响应开关 public User getUser() {User user  new User();user.setId(1L);user.setAccount(12345678);user.setPassword(12345678);user.setEmail(123qq.com);// 注意哦这里是直接返回的User类型并没有用ResultVO进行包装return user; }接口版本控制 1、简介 在spring boot项目中如果要进行restful接口的版本控制一般有以下几个方向 基于path的版本控制 基于header的版本控制 在spring MVC下url映射到哪个method是由RequestMappingHandlerMapping来控制的那么我们也是通过 RequestMappingHandlerMapping来做版本控制的。 2、Path控制实现 首先定义一个注解 Target({ElementType.METHOD, ElementType.TYPE}) Retention(RetentionPolicy.RUNTIME) public interface ApiVersion {// 默认接口版本号1.0开始这里我只做了两级多级可在正则进行控制String value() default 1.0; }ApiVersionCondition用来控制当前request 指向哪个method public class ApiVersionCondition implements RequestConditionApiVersionCondition {private static final Pattern VERSION_PREFIX_PATTERN  Pattern.compile(v(\\d\\.\\d));private final String version;public ApiVersionCondition(String version) {this.version  version;}Overridepublic ApiVersionCondition combine(ApiVersionCondition other) {// 采用最后定义优先原则则方法上的定义覆盖类上面的定义return new ApiVersionCondition(other.getApiVersion());}Overridepublic ApiVersionCondition getMatchingCondition(HttpServletRequest httpServletRequest) {Matcher m  VERSION_PREFIX_PATTERN.matcher(httpServletRequest.getRequestURI());if (m.find()) {String pathVersion  m.group(1);// 这个方法是精确匹配if (Objects.equals(pathVersion, version)) {return this;}// 该方法是只要大于等于最低接口version即匹配成功需要和compareTo()配合// 举例定义有1.0/1.1接口访问1.2则实际访问的是1.1如果从小开始那么排序反转即可 //            if(Float.parseFloat(pathVersion)Float.parseFloat(version)){ //                return this; //            }}return null;}Overridepublic int compareTo(ApiVersionCondition other, HttpServletRequest request) {return 0;// 优先匹配最新的版本号和getMatchingCondition注释掉的代码同步使用 //        return other.getApiVersion().compareTo(this.version);}public String getApiVersion() {return version;}}PathVersionHandlerMapping用于注入spring用来管理 public class PathVersionHandlerMapping extends RequestMappingHandlerMapping {Overrideprotected boolean isHandler(Class? beanType) {return AnnotatedElementUtils.hasAnnotation(beanType, Controller.class);}Overrideprotected RequestCondition? getCustomTypeCondition(Class? handlerType) {ApiVersion apiVersion  AnnotationUtils.findAnnotation(handlerType,ApiVersion.class);return createCondition(apiVersion);}Overrideprotected RequestCondition? getCustomMethodCondition(Method method) {ApiVersion apiVersion  AnnotationUtils.findAnnotation(method,ApiVersion.class);return createCondition(apiVersion);}private RequestConditionApiVersionConditioncreateCondition(ApiVersion apiVersion) {return apiVersion  null ? null : new ApiVersionCondition(apiVersion.value());} }WebMvcConfiguration配置类让spring来接管 Configuration public class WebMvcConfiguration implements WebMvcRegistrations {Overridepublic RequestMappingHandlerMapping getRequestMappingHandlerMapping() {return new PathVersionHandlerMapping();} }最后controller进行测试默认是v1.0如果方法上有注解以方法上的为准该方法vx.x在路径任意位置出现都可解析 RestController ApiVersion RequestMapping(value  /{version}/test) public class TestController {GetMapping(value  one)public String query(){return test api default;}GetMapping(value  one)ApiVersion(1.1)public String query2(){return test api v1.1;}GetMapping(value  one)ApiVersion(3.1)public String query3(){return test api v3.1;} }3、header控制实现 总体原理与Path类似修改ApiVersionCondition 即可之后访问时在header带上X-VERSION参数即可 public class ApiVersionCondition implements RequestConditionApiVersionCondition {private static final String X_VERSION  X-VERSION;private final String version ;public ApiVersionCondition(String version) {this.version  version;}Overridepublic ApiVersionCondition combine(ApiVersionCondition other) {// 采用最后定义优先原则则方法上的定义覆盖类上面的定义return new ApiVersionCondition(other.getApiVersion());}Overridepublic ApiVersionCondition getMatchingCondition(HttpServletRequest httpServletRequest) {String headerVersion  httpServletRequest.getHeader(X_VERSION);if(Objects.equals(version,headerVersion)){return this;}return null;}Overridepublic int compareTo(ApiVersionCondition apiVersionCondition, HttpServletRequest httpServletRequest) {return 0;}public String getApiVersion() {return version;}}API接口安全 1、简介 APP、前后端分离项目都采用API接口形式与服务器进行数据通信传输的数据被偷窥、被抓包、被伪造时有发生关注工众号码猿技术专栏回复关键词1111 获取阿里内部java性能调优手册那么如何设计一套比较安全的API接口方案至关重要一般的解决方案有以下几点 Token授权认证防止未授权用户获取数据 时间戳超时机制 URL签名防止请求参数被篡改 防重放防止接口被第二次请求防采集 采用HTTPS通信协议防止数据明文传输 2、Token授权认证 因为HTTP协议是无状态的Token的设计方案是用户在客户端使用用户名和密码登录后服务器会给客户端返回一个Token并将Token以键值对的形式存放在缓存一般是Redis中后续客户端对需要授权模块的所有操作都要带上这个Token服务器端接收到请求后进行Token验证如果Token存在说明是授权的请求。 Token生成的设计要求 应用内一定要唯一否则会出现授权混乱A用户看到了B用户的数据 每次生成的Token一定要不一样防止被记录授权永久有效 一般Token对应的是Redis的keyvalue存放的是这个用户相关缓存信息比如用户的id 要设置Token的过期时间过期后需要客户端重新登录获取新的Token如果Token有效期设置较短会反复需要用户登录体验比较差我们一般采用Token过期后客户端静默登录的方式当客户端收到Token过期后客户端用本地保存的用户名和密码在后台静默登录来获取新的Token还有一种是单独出一个刷新Token的接口但是一定要注意刷新机制和安全问题 根据上面的设计方案要求我们很容易得到Tokenmd5(用户ID登录的时间戳服务器端秘钥)这种方式来获得Token因为用户ID是应用内唯一的登录的时间戳保证每次登录的时候都不一样服务器端秘钥是配置在服务器端参与加密的字符串即盐目的是提高Token加密的破解难度注意一定不要泄漏 3、时间戳超时机制 客户端每次请求接口都带上当前时间的时间戳timestamp服务端接收到timestamp后跟当前时间进行比对如果时间差大于一定时间比如1分钟则认为该请求失效。时间戳超时机制是防御DOS攻击的有效手段。 例如http://url/getInfo?id1timetamp1661061696 4、URL签名 写过支付宝或微信支付对接的同学肯定对URL签名不陌生我们只需要将原本发送给server端的明文参数做一下签名然后在server端用相同的算法再做一次签名对比两次签名就可以确保对应明文的参数有没有被中间人篡改过。例如http://url/getInfo?id1timetamp1559396263signe10adc3949ba59abbe56e057f20f883e 签名算法过程 首先对通信的参数按key进行字母排序放入数组中一般请求的接口地址也要参与排序和签名那么需要额外添加urlhttp://url/getInfo这个参数 对排序完的数组键值对用进行连接形成用于加密的参数字符串 在加密的参数字符串前面或者后面加上私钥然后用md5进行加密得到sign然后随着请求接口一起传给服务器。服务器端接收到请求后用同样的算法获得服务器的sign对比客户端的sign是否一致如果一致请求有效 5、防重放 客户端第一次访问时将签名sign存放到服务器的Redis中超时时间设定为跟时间戳的超时时间一致二者时间一致可以保证无论在timestamp限定时间内还是外 URL都只能访问一次如果被非法者截获使用同一个URL再次访问如果发现缓存服务器中已经存在了本次签名则拒绝服务。 如果在缓存中的签名失效的情况下有人使用同一个URL再次访问则会被时间戳超时机制拦截这就是为什么要求sign的超时时间要设定为跟时间戳的超时时间一致。拒绝重复调用机制确保URL被别人截获了也无法使用如抓取数据 方案流程 客户端通过用户名密码登录服务器并获取Token 客户端生成时间戳timestamp并将timestamp作为其中一个参数 客户端将所有的参数包括Token和timestamp按照自己的签名算法进行排序加密得到签名sign 将token、timestamp和sign作为请求时必须携带的参数加在每个请求的URL后边例http://url/request?tokenh40adc3949bafjhbbe56e027f20f583atimetamp1559396263signe10adc3949ba59abbe56e057f20f883e 服务端对token、timestamp和sign进行验证只有在token有效、timestamp未超时、缓存服务器中不存在sign三种情况同时满足本次请求才有效 6、采用HTTPS通信协议 安全套接字层超文本传输协议HTTPS为了数据传输的安全HTTPS在HTTP的基础上加入了SSL协议SSL依靠证书来验证服务器的身份并为客户端和服务器之间的通信加密。 HTTPS也不是绝对安全的比如中间人劫持攻击中间人可以获取到客户端与服务器之间所有的通信内容 总结 自此整个后端接口基本体系就构建完毕了 通过Validator 自动抛出异常来完成了方便的参数校验 通过全局异常处理 自定义异常完成了异常操作的规范 通过数据统一响应完成了响应数据的规范 多个方面组装非常优雅的完成了后端接口的协调让开发人员有更多的经历注重业务逻辑代码轻松构建后端接口 这里再说几点 controller做好try-catch工作及时捕获异常可以再次抛出到全局统一格式返回前端 做好日志系统关键位置一定要有日志 做好全局统一返回类整个项目规范好定义好 controller入参字段可以抽象出一个公共基类在此基础上进行继承扩充 controller层做好入参参数校验 接口安全验证
http://www.w-s-a.com/news/315947/

相关文章:

  • 电商网站建设免费在线优化网站
  • 厦门网站建设咨询挣钱最快的小游戏
  • 郑州网站网络营销莱芜雪野湖别墅
  • 安装iis8 添加网站河南省建设执业资格中心网站
  • 个人网站电商怎么做广州市营销型网站建设
  • 空间站做网站什么版本wordpress 勾子
  • win7网站服务器制作软件网站浏览图片怎么做的
  • 网站制作平台公司嵌入式软件开发环境
  • 网站服务器镜像微商做网站网站
  • 十大旅游电子商务网站网上定做衣服
  • 怎样进行网站备案上海发布公众号app
  • 网站后台模板论坛网站优化招商
  • 个人网站设计作品能用VUE做网站
  • 网站建设预付阿里云域名备案查询
  • 苏州本地网站免费咨询医生的软件
  • 个人网站做废品回收福建网站开发招聘
  • wordpress网站备案学设计常用的网站
  • 网站建设的频道是什么网站用什么开发软件做
  • 电子商务网站建设与规划总结外链查询网站
  • 西安网站品牌建设做网站需要的东西
  • 网站外围网站怎么做移动端网站开发项目
  • 做网站只做前端可以用吗知更鸟免费 wordpress
  • html5 微信网站主流开发技术标准网站搭建费用
  • 加强统计局网站的建设和管理广州微信网站建设价格
  • 华宁网站建设设计公司 网站
  • 简历网站免费怎么查在哪个网站做的备案
  • 响应式网站 价格网站用哪些系统做的比较好用
  • 高端网站案例360做的网站
  • 瑞安地区建设网站公众号开发者工具是干嘛的
  • 请解释网站开发的主要流程.wordpress主体上传