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

设计一个支持多版本的APP的后端服务

  1. 以注册为例子的说明

我们以我们的用户中心的注册为例子我们实现的非常简单就是做一个校验,校验成功之后,把用户注册的数据入库即可。

随着我们产品的迭代注册肯定没有这么简单。比如说我需要填写一个电话号码并且拿到验证码并验证正确了才有资格注册。

但是有一个问题,我们的app升级了版本之后。用户在收到版本升级的消息时,并不一定愿意升级版本。

我们在遇到上述的情况,我们希望更新了新版本的用户使用的是需要经过验证并拿到了验证的标识。在注册的时候需要有验证标识才能做下一步的操作。所以我们可能要对接口进行改造。

第一:我们在接口中通过不同的版本号走不同的逻辑。这个其实也能解决我们遇到的问题。

但是随着版本的升级。我们会遇到更多的需求如果在代码中添加太多的判断肯定会降低接口的响应。也增加了后期维护代码的难度一句话low

第二:我们提供多个不同版本的接口。在请求发起的时候带上app的版本号。我们根据不同的版本号转发到不同的接口。这样我们便于后期代码的维护和重构。同一个接口不至于逻辑过于复杂

  1. 我们在用户中心实现以上的想法

由于我们在各个微服务的中都会需要多升级的版本进行控制。所以把这些通用的工具写到common的工具类中。

在common中添加api版本的包如下图:

 

定义一个自定义注解ApiVersion实现如下(我们把顺便把使用注解的方式以注释的方式写在实现类中):

/**

 * 标识接口版本的注解类

 * @author yusong

 */

/*定义该注解可以作用在的位置  

 * ElementType.METHOD可以在方法上加入注解

 * ElementType.TYPE 在接口、类、枚举、注解可以使用该注解

 */

@Target({ElementType.METHOD,ElementType.TYPE})

/*定义注解的生命周期

 * RetentionPolicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在

 *一般如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解

 */

@Retention(RetentionPolicy.RUNTIME)

@Documented //该注解标明在生成文档的时候是否显示该注解

@Mapping //定义用于mapping映射

public @interface ApiVersion {

/**

 * 标识版本号

 * @return

 */

String value();

}

  1. api版本管理的条件控制的实现

/**

 * api版本管理的条件控制

 * @author yusong

 */

public class ApiVersionCondition implements RequestCondition<ApiVersionCondition>{

//API版本号

private String apiVersion;

//从头信息获取到的版本的字段

private static final String HEADER_VERSION = "version";

/**

 * 构造函数传入版本号

 */

public ApiVersionCondition(String apiVersion) {

this.apiVersion = apiVersion;

}

/**

 * 符合不同筛选条件的进行合并

 */

@Override

public ApiVersionCondition combine(ApiVersionCondition other) {

return new ApiVersionCondition(other.getApiVersion());

}

/**

 * 版本对比用于排序

 */

@Override

public int compareTo(ApiVersionCondition other, HttpServletRequest request) {

return compareTo(other.getApiVersion(),this.getApiVersion())?1:-1;

}

/**

 * 根据request的header版本号进行查找匹配的筛选条件

 */

@Override

public ApiVersionCondition getMatchingCondition(HttpServletRequest request) {

//从头信息中获取版本信息

String version = request.getHeader(HEADER_VERSION);

if(version!=null) {

//向下找到最近最新的一个版本  达到向下兼容的目的

if(compareTo(version, this.apiVersion)) {

return this;

}

}

//客户端未添加app版本的时候 给一个基础的版本

return new ApiVersionCondition("1.0.0");

}

private boolean compareTo(String version1,String version2) {

String[] split1 = version1.split("\\.");

String[] split2 = version2.split("\\.");

for(int i=0;i<split1.length;i++) {

if(Integer.parseInt(split1[i])<Integer.parseInt(split2[i])) {

return false;

}

}

return true;

}

public String getApiVersion() {

return apiVersion;

}

}

  1. 处理mapping映射管理

/**

 * mapping映射管理

 * @author yusong

 */

public class CustomRequestMappingHandlerMapping extends RequestMappingHandlerMapping{

/**

 * 定义api条件控制的类 注解在类上

 */

@Override

protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {

ApiVersion apiVersion = AnnotationUtils.findAnnotation(handlerType, ApiVersion.class);

return createCondition(apiVersion);

}

/**

 * 定义api条件控制的类 注解在方法上

 */

@Override

protected RequestCondition<?> getCustomMethodCondition(Method method){

ApiVersion apiVersion = AnnotationUtils.findAnnotation(method, ApiVersion.class);

return createCondition(apiVersion);

}

private RequestCondition<ApiVersionCondition> createCondition(ApiVersion apiVersion){

return apiVersion == null?null:new ApiVersionCondition(apiVersion.value());

}

}

  1. WebConfig的定义

@Configuration

public class WebConfig extends WebMvcConfigurationSupport{

//定义该值为了其他工程使用该注解生效的时候 扫描该包

public static final String WEB_CONFIG_API_VERSION = "cn.com.leimon.common.tool.util.apiversion";

/**

 * 注册请求的版本请求方法

 */

@Override

public RequestMappingHandlerMapping requestMappingHandlerMapping(

@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,

@Qualifier("mvcConversionService") FormattingConversionService conversionService,

@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {

RequestMappingHandlerMapping handlerMapping = new CustomRequestMappingHandlerMapping();

handlerMapping.setOrder(0);

handlerMapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));

return handlerMapping;

}

}

  1. 让注解生效

我们在用户中心的启动类的注解@ComponentScan中加入common中公用的版本管理。如下:

@ComponentScan(basePackages={"cn.com.leimon.user",WebConfig.WEB_CONFIG_API_VERSION})

  1. 测试

Open接口定义添加同一个路径的的不同实现接口

@ApiOperation(value="用户注册(使用用户名密码的方式)1.0.0版本")

@PostMapping(value = "/register")

public ReturnResult register(

@ApiParam(value = "用户名",required = true)

@RequestParam(required = true)String userName,

@ApiParam(value = "密码",required = true)

@RequestParam(required = true)String password

);

@ApiOperation(value="用户注册(使用用户名密码的方式)")

@PostMapping(value = "/register")

public ReturnResult registertV1(

@ApiParam(value = "用户名",required = true)

@RequestParam(required = true)String userName,

@ApiParam(value = "密码",required = true)

@RequestParam(required = true)String password

);

@ApiOperation(value="用户注册(使用用户名密码的方式)")

@PostMapping(value = "/register")

public ReturnResult registertV2(

@ApiParam(value = "用户名",required = true)

@RequestParam(required = true)String userName,

@ApiParam(value = "密码",required = true)

@RequestParam(required = true)String password

);

我们在实现中加入版本的注解如下:

@Override

@ApiVersion(value = "1.0.0")

public ReturnResult register(String userName,String password) {

System.out.println("==========33333333333333=========================");

/**

 * 1.判断用户名是否在系统中已经存在

 * 2.判断密码是否符合既定的规则

 * 3.入库

 * 4.返回注册状态

 * 5.

 * 6.

 */

//判断用户是否存在

int num = userInfoService.checkRegisterUser(userName);

if(num==1) {//用户已经注册

return new ReturnResult(ResultMessage.USER_IS_REGISTER);

}

if(!StringCheckUtil.isLetterDigit(password)) {

return new ReturnResult(ResultMessage.PASSWORD_ISNOT_RULE);

}

//TODO 入库操作 并返回操作状态

//使用雪花算法计算分布式id

return null;

}

@Override

@ApiVersion(value = "1.0.1")

public ReturnResult registertV1(String userName, String password) {

System.out.println("============111111==========================");

return null;

}

@Override

@ApiVersion(value = "1.0.2")

public ReturnResult registertV2(String userName, String password) {

System.out.println("================22222222222=========================");

return null;

}

我们使用postman测试如下图所示:

 

发送请求时不同的version请求会落到不同的实现。说明功能已经完成了

相关文章:

  • 在Ubuntu/Linux中自动备份MySQL数据库
  • 基于windows WSL安装Docker Desktop,修改默认安装到C盘及默认下载镜像到C盘
  • Kubernetes Pod调度策略
  • 猿创征文 | JavaScript函数柯里化
  • Servlet的注册和生命周期
  • [Latex] \bibitem{} | .bbl 格式参考文献转换与获得
  • cmake和makefile区别和cmake指定编译器(cmake -G)
  • JavaWeb对于Listener的运用详解【利用Session统计在线人数】
  • Windows命令: net与sc的区别
  • 向量数据库是如何检索的?基于 Feder 的 HNSW 可视化实现
  • 架构师的 36 项修炼第11讲:致未来的架构师
  • 基于springboot+vue的商城系统(电商平台)
  • Linux CentOS 8(用户组的管理实验)
  • .net core开源商城系统源码,支持可视化布局小程序
  • ElasticSearch诞生
  • ECMAScript入门(七)--Module语法
  • Idea+maven+scala构建包并在spark on yarn 运行
  • Java IO学习笔记一
  • JavaScript-Array类型
  • Linux后台研发超实用命令总结
  • puppeteer stop redirect 的正确姿势及 net::ERR_FAILED 的解决
  • react-native 安卓真机环境搭建
  • TypeScript迭代器
  • V4L2视频输入框架概述
  • Vue 动态创建 component
  • Web Storage相关
  • yii2权限控制rbac之rule详细讲解
  • 半理解系列--Promise的进化史
  • 区块链将重新定义世界
  • 限制Java线程池运行线程以及等待线程数量的策略
  •  一套莫尔斯电报听写、翻译系统
  • 一些css基础学习笔记
  • 源码之下无秘密 ── 做最好的 Netty 源码分析教程
  • ​力扣解法汇总1802. 有界数组中指定下标处的最大值
  • #HarmonyOS:基础语法
  • #laravel 通过手动安装依赖PHPExcel#
  • #我与Java虚拟机的故事#连载14:挑战高薪面试必看
  • %3cli%3e连接html页面,html+canvas实现屏幕截取
  • (arch)linux 转换文件编码格式
  • (分布式缓存)Redis哨兵
  • (附源码)apringboot计算机专业大学生就业指南 毕业设计061355
  • (附源码)springboot家庭财务分析系统 毕业设计641323
  • (四)汇编语言——简单程序
  • (续)使用Django搭建一个完整的项目(Centos7+Nginx)
  • (一)ClickHouse 中的 `MaterializedMySQL` 数据库引擎的使用方法、设置、特性和限制。
  • (一)SpringBoot3---尚硅谷总结
  • (原創) 如何優化ThinkPad X61開機速度? (NB) (ThinkPad) (X61) (OS) (Windows)
  • .gitignore文件---让git自动忽略指定文件
  • .net core使用RPC方式进行高效的HTTP服务访问
  • .net mvc actionresult 返回字符串_.NET架构师知识普及
  • .net refrector
  • .NET6实现破解Modbus poll点表配置文件
  • .NET开发不可不知、不可不用的辅助类(一)
  • /etc/sudoer文件配置简析
  • @Transactional注解下,循环取序列的值,但得到的值都相同的问题