SpringBoot-校验前后端代码版本是否一致-前后端版本-提示用户刷新页面
1. 需求背景
系统前后端发布后,用户如果未刷新页面,提交的接口数据可能就不符合后端最新的代码逻辑,此时需要告知用户刷新页面。
2. 实现思路
仅在前端需要set Authorization header头的接口中(可以共用shiro.anons配置,不用再梳理api接口)增加一个Api-Version的头,Api-Version的值每次迭代和后端约定即可。
以下场景需要推荐修改Api-Version的值,发布后:
有删减接口
接口的出入参结构发生变化
前端数据处理逻辑的变化
前端样式或提示文案等变化可不修改Api-Version的值,具体问题具体分析。
3. 代码
// WebMvcConfig.java
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
// 注入拦截器
@Autowired
private List<HandlerInterceptor> interceptorList = Collections.emptyList();
@Override
public void addInterceptors(InterceptorRegistry registry) {
for (HandlerInterceptor handlerInterceptor : interceptorList) {
logger.info("add interceptor " + handlerInterceptor.getClass());
registry.addInterceptor(handlerInterceptor);
}
registry.addInterceptor(cleanInterceptor);
registry.addInterceptor(new HandlerInterceptor() {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
///
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
}
});
}
}
// VersionCheckInterceptor.java
package cn.jiangjiesheng.interceptor;
import cn.jiangjiesheng.common.constants.Constants;
import cn.jiangjiesheng.core.utils.IpUtil;
import cn.jiangjiesheng.exception.GnException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.PathMatcher;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* 轻量化实现 校验前后版代码版本是否一致
*/
@Order(Ordered.HIGHEST_PRECEDENCE + 3)
@Component
@Slf4j
public class VersionCheckInterceptor extends AbstractExcludeInterceptor {
@Value("${api.version:}")
private String currentReleaseApiVersion;
@Value("#{'${shiro.anons:}'.split(',')}")
private List<String> shiroAnonUrlList;
@Autowired //注入 AntPathMatcher.java
private PathMatcher pathMatcher;
private final static String HEADER_API_VERSION = "Api-Version";
private final static String AUTHORIZATION = "Authorization";
private final static String ERROR_API_VERSION_TIP_MSG = "当前系统已升级(最新版本%s),请先备份当前页面数据,然后刷新页面重试即可";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
checkCurrentApiVersion(request);
return super.preHandle(request, response, handler);
}
/**
* 校验前后端版本是否一致
* api.version
*
* @param request
*/
private void checkCurrentApiVersion(HttpServletRequest request) {
//未配置当前发行版直接放行
if (StringUtils.isBlank(currentReleaseApiVersion)) {
return;
}
// request.getServletContext().getContextPath(); // /xxxx
// request.getContextPath(); // /xxxx
// request.getServletPath();// /pis/login
String servletPath = request.getServletPath();
if (StringUtils.isBlank(servletPath)) {
return;
}
//TODO 删除
System.err.println("拦截的url:" + servletPath);
//当前请求是否属于 不拦截的url
if (checkIsDoNotCheckVersionUrl(request, servletPath)) {
return;
}
String apiVersion = request.getHeader(HEADER_API_VERSION);
if (StringUtils.isBlank(apiVersion) || !StringUtils.equals(apiVersion, currentReleaseApiVersion)) {
log.warn("当前客户端请求的前端页面未刷新到最新版本,ip: {},ua: {} ", IpUtil.getIpAddress(request), request.getHeader("user-agent"));
throw new GnException(String.format(ERROR_API_VERSION_TIP_MSG, currentReleaseApiVersion));
}
}
/**
* 当前请求是否属于 不拦截的url
*
* @param servletPath
* @return
*/
private boolean checkIsDoNotCheckVersionUrl(HttpServletRequest request, String servletPath) {
List<String> doNotCheckUrlList = getDoNotCheckVersionUrl();
String authorization = request.getHeader(AUTHORIZATION);
if (StringUtils.isBlank(authorization)) {
// 没带authorization头的,一定是匿名的接口,直接返回,提交效率
return true;
}
// 带了authorization头的,也有可能是匿名的接口
// 快速精确匹配一次
if (doNotCheckUrlList.contains(servletPath)) {
return true;
}
//再模糊匹配一次
return doNotCheckUrlList.stream().filter(urlPattern -> urlPattern.contains("*"))
.anyMatch(urlPattern -> pathMatcher.match(urlPattern, servletPath));
}
/**
* 不拦截的url或url pattern,支持 **
*
* @return
*/
private List<String> getDoNotCheckVersionUrl() {
// 支持redis
// 免登陆的接口 + 给第三方调用的 (guest/gateway/*)
// 比如 搜索 guest" /guest /guest/gateway /guest/wvp-alarm-get /bis/guest /bss/guest
//
// 综合: 读取 shiro.anons 配置 即可
// return Lists.newArrayList("/xxxx/pis/login", "/xxxx/bos/dataFlag/*");
return shiroAnonUrlList;
}
}
// application.properties
# 最新迭代的版本
api.version=v20240815
# 匿名访问的url
shiro.anons=/monitor/*,/pis/login,/pis/login/slide,/login,/v1/**,/v2/**,/**/guest/**,/webjars/**,/captcha/**,/swagger**/**,/doc.html,/**/*.html,/**/*.htm,*.ico,/dictExcel/*,/api/**,/doc/**,/dist/**,/test/*,/inspection/*,/hugeScreen/**
正文到此结束
- 本文标签: Spring Boot 前端 Java
- 本文链接: https://code.jiangjiesheng.cn/article/350
- 版权声明: 本文由小江同学原创发布,转载请先联系本站长,谢谢。