案例的实现思路是: 将需要转发的路由抽象成一个默认的模版bean,如果医院有定制的转发逻辑就在默认转发模版bean的基础上扩展实现(也可以不扩展,单独再写一个类),在医院的配置中配置模版key(这个key就是模版bean的名称),这样灵活些,并且在filter中增加了根据医院配置获取bean的方法
1.TransferRegex
public interface TransferRegex {
/**
* 必须包含数字及字母
*/
String numberAndLetterRegex = "^[a-z0-9A-Z]+$";
}
2. AbstractTransfer 黑白名单抽象类
public abstract class AbstractTransfer {
private String[] urlMatch = new String[] { "" };
private String[] whiteUrlMatch = new String[] { "" };
public void setUrlMatch(String... urlMatch) {
this.urlMatch = urlMatch;
}
public String[] getUrlMatch() {
return this.urlMatch;
}
public String[] getWhiteUrlMatch() {
return whiteUrlMatch;
}
public void setWhiteUrlMatch(String... whiteUrlMatch) {
this.whiteUrlMatch = whiteUrlMatch;
}
}
3. TransferHandlerAbstract转发抽象处理器
public abstract class TransferHandlerAbstract extends AbstractTransfer implements InitializingBean {
public final Log logger = LogFactory.getLog(getClass());
public final String prefix = "/redirect";
/**
* 中转请求
* @param req /
*/
public void transferRequest(String prefix,HttpServletRequest req, HttpServletResponse resp) {
// 中转
doTransferRequest(prefix,url(req), req, resp);
}
public void transferRequest(String url, HttpServletResponse resp) {
// 中转
doTransferRequest(url, resp);
}
public void transferRequest(String url) {
// 中转
doTransferRequest(url);
}
protected String url(HttpServletRequest req) {
return req.getRequestURI();
}
protected void doTransferRequest(String url) {
// to-do
}
protected void doTransferRequest(String url, HttpServletResponse resp) {
// to-do
}
protected void doTransferRequest(String prefix,String url, HttpServletRequest req, HttpServletResponse resp) {
// to-do
}
@Override
public void afterPropertiesSet() throws Exception {
}
}
4.filter实现及注册
1.TransferFilter
@Slf4j
public class TransferFilter extends OncePerRequestFilter implements TransferRegex {
/**
* 中转处理器
*/
private final Map<String, TransferHandlerAbstract> transferHandlerMap;
/**
* 路径匹配器
*/
private final PathMatcher pathMatcher = new AntPathMatcher();
/**
* redisson
*/
private final RedissonClient redissonClient;
private final String prefix;
/**
* 验证器
* @param request /
* @param response /
* @param filterChain /
* @throws ServletException /
* @throws IOException /
*/
@SneakyThrows
@Override
protected void doFilterInternal(@NotNull HttpServletRequest request,
@NotNull HttpServletResponse response,
@NotNull FilterChain filterChain) throws ServletException,
IOException {
// todo: 配置化(根据医院), 获取redis医院配置,从中获取
// 因为根据平台走的,所以用户token中获取不到serviceAppid,后期再用户token中获取平台id,双重验证
// 1. 平台配置了以平台为准 2.平台没配置,获取请求头中的serviceAppid进行二次验证
String serviceAppid = request.getHeader("serviceAppid");
if (StringUtils.isNotBlank(serviceAppid)) {
RMap<String, String> map = redissonClient.getMap(Hospital.HOSPITAL_CONFIGURE_KEY);
if (null != map && !map.isEmpty()) {
String s = map.get(serviceAppid);
if (StringUtils.isBlank(s)) {
filterChain.doFilter(request, response);
}
ListHospital listHospital = JSON.parseObject(s, ListHospital.class);
String transferTemplateKey = listHospital.getTransferTemplate();
if (StringUtils.isNotBlank(transferTemplateKey)
&& transferHandlerMap.containsKey(transferTemplateKey)) {
log.info("transferTemplateKey:{}", transferTemplateKey);
String path = request.getRequestURI();
TransferHandlerAbstract transferHandler = transferHandlerMap
.get(transferTemplateKey);
String[] whiteUrlMatch = transferHandler.getWhiteUrlMatch();
boolean hasWhiteUrl = getStrategyForPath(whiteUrlMatch,path);
if (!hasWhiteUrl) {
String[] urlMatch = transferHandler.getUrlMatch();
boolean b = getStrategyForPath(urlMatch,path);
if (b) {
// 执行中转方法
transferHandler.transferRequest(prefix,request, response);
return;
}
}
}
}
}
filterChain.doFilter(request, response);
}
/**
* 找到匹配的url
* @param paths 所有match url
* @param path 需要匹配的url
* @return
*/
public Boolean getStrategyForPath(String[] paths, String path) {
List<String> matchingPatterns = new ArrayList<>();
for (String pattern : paths) {
if (pathMatcher.match(pattern, path)) {
matchingPatterns.add(pattern);
}
}
if (!matchingPatterns.isEmpty()) {
Comparator<String> comparator = pathMatcher.getPatternComparator(path);
matchingPatterns.sort(comparator);
String s = matchingPatterns.get(0);
return StringUtils.isNotBlank(s);
}
return false;
}
public TransferFilter(Map<String, TransferHandlerAbstract> transferHandlerMap,
RedissonClient redissonClient,String prefix) {
this.transferHandlerMap = transferHandlerMap;
this.redissonClient = redissonClient;
this.prefix = prefix;
}
}
2. TransferAutoConfiguration 注册filter
如果是多模块,可以将除了注册bean之外的其他类放到common,哪儿个项目用到就在哪儿个项目注册filter
@Slf4j
@Configuration
@RequiredArgsConstructor
public class TransferAutoConfiguration {
private final Map<String, TransferHandlerAbstract> transferHandlerMap;
private final RedissonClient redissonClient;
@Bean
public FilterRegistrationBean<TransferFilter> TransferFilterFilterRegistrationBean() {
log.info("TransferFilter registration ...");
FilterRegistrationBean<TransferFilter> bean = new FilterRegistrationBean<>();
bean.setFilter(new TransferFilter(transferHandlerMap, redissonClient,PLATFORM_API_PATH));
bean.setOrder(1);
// 默认全部
bean.setUrlPatterns(Collections.singletonList("/*"));
return bean;
}
}
5.具体的转发模版
AllTransferHandler 默认路经中转模版
@Slf4j
@Component
public class AllTransferHandler extends TransferHandlerAbstract {
@Override
public void afterPropertiesSet() {
logger.info("AllTransferHandler afterPropertiesSet...");
// 设置匹配match
setUrlMatch(
HOSPITAL_API_PATH + "/appointment/createAppointment",
HOSPITAL_API_PATH + "/appointment/selectByPage",
HOSPITAL_API_PATH + "/appointment/cancelAppointment/{id}",
HOSPITAL_API_PATH + "/appointmentRecord/selectByPage",
HOSPITAL_API_PATH + "/appointmentRecord/selectByPage",
HOSPITAL_API_PATH + "/appointmentRecord/selectById/**",
HOSPITAL_API_PATH + "/meal/list",
HOSPITAL_API_PATH + "/meal/details",
HOSPITAL_API_PATH + "/meal/itemText/**",
HOSPITAL_API_PATH + "/mealType/list",
HOSPITAL_API_PATH + "/item/list",
HOSPITAL_API_PATH + "/mealItem/list",
HOSPITAL_API_PATH + "/exclusive/check",
HOSPITAL_API_PATH + "/itemType/list",
HOSPITAL_API_PATH + "/mealSchedule/list",
HOSPITAL_API_PATH + "/scheduleSource/list",
HOSPITAL_API_PATH + "/team/appoint",
HOSPITAL_API_PATH + "/team/reschedule",
HOSPITAL_API_PATH + "/team/{name}/{idCard}/{serviceAppid}/{isOverTeam}/{teamAppointId}",
HOSPITAL_API_PATH + "/team/getMealDeploy/{idCard}/{teamId}",
HOSPITAL_API_PATH + "/report/appoint_list",
HOSPITAL_API_PATH + "/report/detail.connect",
HOSPITAL_API_PATH + "/appointment/get_code/{id}",
// 支付
HOSPITAL_API_PATH + "/pay/doPay",
HOSPITAL_API_PATH + "/pay/callBack",
HOSPITAL_API_PATH + "/pay/checkPay",
HOSPITAL_API_PATH + "/pay/refund",
// 问卷
HOSPITAL_API_PATH + "/examAnswer/save",
HOSPITAL_API_PATH + "/examAnswer/info",
HOSPITAL_API_PATH + "/examAnswer/exam",
// 平台
PLATFORM_API_PATH + "/hospital/list",
PLATFORM_API_PATH + "/hospital/{hosId}",
// article
HOSPITAL_API_PATH + "/article/list",
HOSPITAL_API_PATH + "/article/id"
);
// 白名单
setWhiteUrlMatch(PLATFORM_API_PATH + "/hospital/position_conversion");
}
/**
* 构造
*/
public AllTransferHandler() {
}
/**
* 中转请求
* @param prefix 请求前缀地址
* @param url 请求地址
* @param req /
* @param resp /
*/
@SneakyThrows
@Override
public void doTransferRequest(String prefix,String url, HttpServletRequest req, HttpServletResponse resp) {
String uri = prefix + url.replaceFirst(prefix, this.prefix);
log.info("doTransferRequest -> url:{}", url);
// 使用转发不使用重定向(重定向设置参数太过麻烦)
req.getRequestDispatcher(uri).forward(req, resp);
// resp.sendRedirect(uri);
}
}
6. 案例
因为项目需要适配另一个标准的中间件时间紧迫,并且前台不能变所以就有了此方法,执行流程是:请求(/appointment) -> filter(匹配路由)-> 转发其他路由上(/redirect/appointment)
@RestController
@RequestMapping(HOSPITAL_API_PATH + "/redirect")
@Slf4j
@AllArgsConstructor
public class RedirectRest {
@SneakyThrows
@GetMapping("/appointment/{id}")
@Operation(summary = "预约", description = "预约")
@Override
public ApiRestResult<Object> cancelAppoint(@Parameter(name = "id", description = "预约id") @PathVariable("id") String id,
@Validated CancelAppointVo cancelAppointVo
) {
// urlSuffix 可以医院配置中,缓存的redis
Object ogj;
if (cancelAppointVo.getAppointType().equals(IsTeam.CHECK.getCode())) {
ogj = exec( cancelAppointVo.setId(id), "/appointment", "123456");
}
return ApiRestResult.ok(ogj);
}
}