package com.lnwazg.httpkit.handler.route; import java.io.File; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.ImmutableTriple; import org.apache.commons.lang3.tuple.MutablePair; import com.google.gson.JsonArray; import com.lnwazg.httpkit.CommonResponse; import com.lnwazg.httpkit.ControllerPathMethodMapper; import com.lnwazg.httpkit.HttpResponseCode; import com.lnwazg.httpkit.anno.IgnoreRoute; import com.lnwazg.httpkit.controller.BaseController; import com.lnwazg.httpkit.exception.RoutingException; import com.lnwazg.httpkit.handler.HttpHandler; import com.lnwazg.httpkit.io.HttpReader; import com.lnwazg.httpkit.io.IOInfo; import com.lnwazg.httpkit.page.RenderPage; import com.lnwazg.httpkit.server.HttpServer; import com.lnwazg.httpkit.util.RenderUtils; import com.lnwazg.kit.anno.Anno; import com.lnwazg.kit.controllerpattern.Controller; import com.lnwazg.kit.controllerpattern.RequestMapping; import com.lnwazg.kit.executor.ExecMgr; import com.lnwazg.kit.gson.GsonKit; import com.lnwazg.kit.http.url.URIEncoderDecoder; import com.lnwazg.kit.http.url.UriParamUtils; import com.lnwazg.kit.log.Logs; import com.lnwazg.kit.map.Maps; import com.lnwazg.kit.reflect.ClassKit; import com.lnwazg.kit.singleton.B; import com.lnwazg.kit.str.UrlKit; /** * 路由对象<br> * 路由的优先级从高到低顺序排列如下:<br> * 1. 自定义的Controller<br> * 2. freeMarker ftl文件解析<br> * 3. 文件浏览器解析<br> */ public class Router implements HttpHandler { /** * 控制器的路由表<br> * key为控制器类的方法名+前缀的contextPath<br> * value为对应的处理器类以及相应的方法的包装—->Route对象 */ private final Map<String, ControllerPathMethodMapper> controllerRoutesMap = new HashMap<>(); /** * 正则匹配的路由附表<br> * key: ^/card/users/\w+/topics/\w+.do$ <br> * value: /card/users/{userId}/topics/{topicId}.do ^/card/users/(\w+)/topics/(\w+).do$ [userId,topicId] */ private final Map<String, RegexMapDetail> regexUrlToDetailMap = new HashMap<>(); /** * 文档文件夹目录路由表<br> * key为起始路径,用作key匹配的时候用,例如:/root/maven/<br> * value为对应的File对象,例如:new File("D:\\maven") */ private final Map<String, File> docDirectoryRoutesMap = new HashMap<>(); /** * Freemarker的路由表<br> * key为起始路径,用作key匹配的时候用,例如:/root/maven/<br> * value为具体的资源目录,例如:static/<br> * 为何此处value一定是一个相对路径?因为要兼容当资源全部打包成jar包之后,依然可以可靠服务的情况!<br> */ private final Map<String, String> freemarkerRoutesMap = new HashMap<>(); /** * Controller类和对象的一对一对应表 */ private Map<Class<? extends BaseController>, BaseController> controllerClassObjectMap = new HashMap<>(); private HttpServer httpServer; public void setHttpServer(HttpServer httpServer) { this.httpServer = httpServer; } @Override public void accept(IOInfo ioInfo) { HttpReader reader = ioInfo.getReader(); try { String uri = reader.getUri(); //找到路径 /root/base/index?fff=4343&bbb=6666 if (StringUtils.isNotEmpty(uri)) { //多斜杠兼容 //例如将: http://127.0.0.1:8080/////__info__ 自动翻译成 http://127.0.0.1:8080/__info__ ///root/base/////index?fff=4343&bbb=6666 => /root/base/index?fff=4343&bbb=6666 uri = UrlKit.cleanUri(uri); //此处的处理含有几个优先级,因此可能会有些许性能的损失!但是无妨,因为性能损失换来了系统服务弹性的提升! //1.优先从routesMap中查找 //找到那个路由处理器对象,即可获得要调用的类对象以及方法,还有调用参数 uri = UriParamUtils.removeParams(uri);//首先先做去除参数的操作 ///root/base/index?fff=4343&bbb=6666 => /root/base/index ImmutablePair<ControllerPathMethodMapper, Map<String, String>> pair = findFromControllerMap(uri); if (pair != null) { //2.routesMap中找到数据了,此处开始核心的业务调用 ControllerPathMethodMapper controllerPathMethodMapper = pair.getLeft(); Map<String, String> extraMap = pair.getRight(); //额外的参数,是从url中的正则匹配所提取到的 //类似如下的url: /card/users/{userId}/topics/{topicId}.do 可以提取到: userId=155,topicId=200 if (Maps.isNotEmpty(extraMap)) { //将这些额外的url正则参数也放入到参数表中 ioInfo.appendExtraRequestParamMap(extraMap); } controllerPathMethodMapper.invokeControllerMethod(ioInfo); } else { //插入的逻辑:RPC服务相关的逻辑 //判断RPC服务开关:如果开启了RPC service服务,那么尝试匹配RPC请求uri: /root/__httpRpc__/{interfaceName} //如果是"/root/__httpRpc__/"这样的开头,则根据末端的interfaceName,找到当初根据interfaceName注册的interfaceImpl, //从头中获取uniqueMethodName,然后到interfaceImpl中匹配该uniqueMethodName,获得真正的method对象 //从体中获取params json,根据真正的method对象拿到paramClass[],然后依次将json还原成对应的参数Object[] //最后调用该interfaceImpl的method,传入还原出的args[],得到响应对象。将响应结果转换为json,返回给客户端即可。 if (ioInfo.getHttpServer().isEnableRpc() && matchRpcServiceUri(uri, ioInfo)) { String serviceName = getRpcServiceName(uri, ioInfo); Logs.i("RPC call serviceName=" + serviceName); if (rpcInstanceMap.containsKey(serviceName)) { //准备调用 Object interfaceImpl = rpcInstanceMap.get(serviceName); String uniqueUnfullMethodName = ioInfo.getReader().getHeader("uniqueMethodName"); String reqJson = ioInfo.getReader().getPayloadBody(); Method method = ClassKit.getMethodByUniqueUnFullMethodName(interfaceImpl, uniqueUnfullMethodName); Logs.d("find service from rpcInstanceMap, uniqueMethodName=" + uniqueUnfullMethodName + " reqJson=" + reqJson + " method=" + (method == null ? "null" : method.getName())); if (method != null) { Class<?>[] paramClazzArray = method.getParameterTypes(); Object retObj = null; if (paramClazzArray.length > 0) { Object[] args = new Object[paramClazzArray.length]; JsonArray jsonArray = GsonKit.parseString2JsonArray(reqJson); for (int i = 0; i < args.length; i++) { String paramJsonI = jsonArray.get(i).toString(); args[i] = GsonKit.parseString2Object(paramJsonI, paramClazzArray[i]); } retObj = method.invoke(interfaceImpl, args); } else { retObj = method.invoke(interfaceImpl); } String retStr = GsonKit.parseObject2String(retObj); responseAsJson(ioInfo, retStr, HttpResponseCode.OK); } else { Logs.e("method is undefined:" + uniqueUnfullMethodName); responseAsJson(ioInfo, "method is undefined:" + uniqueUnfullMethodName, HttpResponseCode.SERVICE_UNAVAILABLE); } } else { Logs.e("Service not found: " + serviceName); responseAsJson(ioInfo, "Service not found: " + serviceName, HttpResponseCode.SERVICE_UNAVAILABLE); } } //3.查找不到,则从docRoutesMap中查找 例如 /root/games/1.doc?aaa=123 //key: /root/games value: File //key: /root/list //4.如果是list请求,列出根目录的文件服务器列表 else if (matchDirRootListDrives(uri, ioInfo)) { listDirRootDrives(ioInfo); } //如果是__info__请求,那么列出当前网站的元数据信息页 else if (matchMetaInfoRequestPath(uri, ioInfo)) { describeWebsiteMetadata(ioInfo); } else { //5.首先尝试从ftl映射目录中查找资源 //key: /root/web/page/index.ftl ImmutableTriple<String, String, String> baseAndSubPath = findResourcePathFromFreemarkerRouteMap(uri); if (baseAndSubPath != null) { //6.ftl网站映射处理 processFtl(ioInfo, baseAndSubPath.getLeft(), baseAndSubPath.getMiddle(), baseAndSubPath.getRight(), uri); } else { //7.从文件服务器中(文档目录映射表中)查找 //key: /root/games //value: File File file = findFileFromDocRouteMap(uri); if (file != null && file.exists()) { //7.开启一个线程,处理这个文件请求 processFile(ioInfo, file, uri); } else { //8.最终啥都没匹配到 throw new RoutingException("Unable to find route, uri: " + uri); } } } } } } catch (InvocationTargetException | IllegalAccessException e) { throw new RuntimeException("Unable to invoke route action.", e); } } /** * 描述网站元信息 * @author nan.li * @param ioInfo */ private void describeWebsiteMetadata(IOInfo ioInfo) { ExecMgr.cachedExec.execute(() -> { try { RenderUtils.renderMsg(ioInfo, HttpResponseCode.OK, GsonKit.gson.toJson(ioInfo.getHttpServer().getHttpServiceSummary()), "json"); } catch (Exception e) { e.printStackTrace(); } }); } private void responseAsJson(IOInfo ioInfo, String msg, HttpResponseCode code) { ExecMgr.cachedExec.execute(() -> { try { RenderUtils.renderMsg(ioInfo, code, msg, "json"); } catch (Exception e) { e.printStackTrace(); } }); } /** * 若符合元数据描述页面<br> * 这个页面一般是秘密的、私有的、不对外开放的,但是便于内部开发使用的 * @author nan.li * @param uri * @param ioInfo * @return */ private boolean matchMetaInfoRequestPath(String uri, IOInfo ioInfo) { if (uri.equals(String.format("%s/__info__", ioInfo.getHttpServer().getBasePath()))) { return true; } return false; } private void listDirRootDrives(IOInfo ioInfo) { ExecMgr.cachedExec.execute(() -> { try { RenderPage.showDirectory(ioInfo, docDirectoryRoutesMap); } catch (Exception e) { e.printStackTrace(); } }); } /** * 如果是文件,就以文件的方式进行处理;否则,按照文件夹的方式进行处理 * @author [email protected] * @param reader * @param writer * @param f * @param uri */ private void processFile(IOInfo ioInfo, File f, String uri) { ExecMgr.cachedExec.execute(() -> { try { if (f.isDirectory()) { //如果是文件夹的话,那么一定要将结尾加上斜杠 if (!uri.endsWith("/")) { //发送重定向 RenderUtils.sendRedirect(ioInfo, uri + "/"); } else { RenderPage.showDirectory(ioInfo, f, uri); } } else { RenderUtils.renderFile(ioInfo, HttpResponseCode.OK, f); } } catch (Exception e) { e.printStackTrace(); } }); } /** * 渲染ftl文件 * @author nan.li * @param reader * @param writer * @param basePath /root/web/ * @param resourcePath static/ * @param subPath page/index.ftl * @param uri */ private void processFtl(IOInfo ioInfo, String basePath, String resourcePath, String subPath, String uri) { ExecMgr.cachedExec.execute(() -> { //判断资源是否存在 if (existResource(resourcePath, subPath)) { String fileName = getFileNameFromSubPath(subPath); if (StringUtils.isNotEmpty(fileName) && FilenameUtils.getExtension(fileName).toLowerCase().equals("ftl")) { RenderUtils.renderFtl(ioInfo, HttpResponseCode.OK, basePath, resourcePath, subPath); } else { RenderUtils.renderResource(ioInfo, HttpResponseCode.OK, resourcePath, subPath, fileName); } } else { CommonResponse.notFound().accept(ioInfo); } }); } /** * 获取文件名 * @author nan.li * @param subPath page/index.ftl * @return */ private String getFileNameFromSubPath(String subPath) { int index = subPath.lastIndexOf("/"); if (index != -1) { return subPath.substring(index + 1); } return null; } /** * 判断某个资源是否存在 * @author nan.li * @param resourcePath static/ * @param subPath page/index.ftl * @return */ private boolean existResource(String resourcePath, String subPath) { String path = String.format("%s%s", resourcePath, subPath); return Router.class.getClassLoader().getResourceAsStream(path) != null; } /** * 符合列出驱动器列表的uri<br> * 例如如下:<br> * http://127.0.0.1/list<br> * http://127.0.0.1/index * @author nan.li * @param uri * @param ioInfo * @return */ private boolean matchDirRootListDrives(String uri, IOInfo ioInfo) { if (uri.equals(String.format("%s/list", ioInfo.getHttpServer().getBasePath())) || uri.equals(String.format("%s/index", ioInfo.getHttpServer().getBasePath()))) { return true; } return false; } /** * 是否符合RPC调用的URI * @author nan.li * @param uri * @param ioInfo * @return */ private boolean matchRpcServiceUri(String uri, IOInfo ioInfo) { ///root/__httpRpc__/{interfaceName} return uri.startsWith(String.format("%s/__httpRpc__/", ioInfo.getHttpServer().getBasePath())); } private String getRpcServiceName(String uri, IOInfo ioInfo) { ///root/__httpRpc__/{interfaceName} return uri.substring(String.format("%s/__httpRpc__/", ioInfo.getHttpServer().getBasePath()).length()); } private ImmutableTriple<String, String, String> findResourcePathFromFreemarkerRouteMap(String uri) { //2.查找不到,则从docRoutesMap中查找 例如 //uri: /root/web/page/index.ftl //key: /root/web //value: static/ for (String key : freemarkerRoutesMap.keySet()) { if (uri.startsWith(key + "/")) { //uri: /root/web/page/index.ftl //key: /root/web //value: static/ //key: /root/web/ String subPath = StringUtils.removeStart(uri, key + "/"); //subPath: page/index.ftl //uri解码,即可完美支持中文 subPath = URIEncoderDecoder.decode(subPath); return new ImmutableTriple<String, String, String>(key + "/", freemarkerRoutesMap.get(key), subPath); } } return null; } /** * 从文档路由表中查找匹配的路由器 * @author [email protected] * @param uri * @return */ private File findFileFromDocRouteMap(String uri) { //2.查找不到,则从docRoutesMap中查找 例如 //uri: /root/games/1.doc?aaa=123 //key: /root/games //value: File for (String key : docDirectoryRoutesMap.keySet()) { //uri: /root/games //uri: /root/games/ if (uri.equals(key) || uri.equals(key + "/")) { //刚好相等,那么直接将那个文件夹返回即可 return docDirectoryRoutesMap.get(key); } //uri: /root/games/1.doc?aaa=123 else if (uri.startsWith(key + "/")) { //先去除参数 // uri = UriUtils.removeParams(uri); //uri: /root/games/1.doc //key: /root/games String subPath = StringUtils.removeStart(uri, key); //subPath: /1.doc //uri解码,即可完美支持中文 subPath = URIEncoderDecoder.decode(subPath); return new File(docDirectoryRoutesMap.get(key), subPath); } } return null; } /** * 将方法放入到路由表中 * @author nan.li * @param path * @param controllerPathMethodMapper * @param controllerMethodAnno */ private void putControllerRoutesMap(String path, ControllerPathMethodMapper controllerPathMethodMapper, String controllerMethodAnno, HttpServer httpServer) { Logs.i(String.format("Adding route -> %s", controllerPathMethodMapper)); httpServer.getHttpServiceSummary().trackServicePath(controllerPathMethodMapper.getRoutingPath(), controllerMethodAnno); if (controllerRoutesMap.containsKey(path)) { System.err.println("警告:存在重复的path: " + path + ", 将会仅保留最后的那个映射方法!"); } controllerRoutesMap.put(path, controllerPathMethodMapper); } /** * 正则匹配的路由附表 * @author nan.li * @param urlRegex * @param regexMapDetail * @param controllerMethodAnno */ private void putToRegexMap(String urlRegex, RegexMapDetail regexMapDetail, String controllerMethodAnno) { Logs.i(String.format("Adding REGEX route %s -> %s", urlRegex, regexMapDetail)); httpServer.getHttpServiceSummary().trackServicePath(regexMapDetail.getMethodMapping(), controllerMethodAnno); if (regexUrlToDetailMap.containsKey(urlRegex)) { System.err.println("警告:存在重复的urlRegex: " + urlRegex + ", 将会仅保留最后的那个映射!"); } regexUrlToDetailMap.put(urlRegex, regexMapDetail); } /** * 查找指定uri所能匹配到的Route对象 * @author nan.li * @param uri * @return */ public ImmutablePair<ControllerPathMethodMapper, Map<String, String>> findFromControllerMap(String uri) { // Logs.d("findFromControllerMap() uri: " + uri); //uri: /card/cache.do // Pattern pattern = Pattern.compile("^\\/\\w+\\/\\w+$"); // /card/users/{userId}/topics/{topicId}.do //uri: /card/users/155/topics/200.do //uri: ^/card/users/\\w+/topics/\\w+.do$ // ^/card/users/(\\w+)/topics/(\\w+).do$ ControllerPathMethodMapper controllerPathMethodMapper = controllerRoutesMap.get(uri); if (controllerPathMethodMapper != null) { //直接就匹配到了 return new ImmutablePair<ControllerPathMethodMapper, Map<String, String>>(controllerPathMethodMapper, null); } else { //还要尝试用正则附表去匹配 for (String regexStr : regexUrlToDetailMap.keySet()) { //尝试正则匹配 //regexStr ^/card/users/\w+/topics/\w+.do$ //uri /card/users/155/topics/200.do if (Pattern.compile(regexStr).matcher(uri).matches()) { //匹配成功! // Logs.d("uri: " + uri + " 匹配成功!"); RegexMapDetail regexMapDetail = regexUrlToDetailMap.get(regexStr); //接下来就是提取参数,构建paramMap List<String> paramValueList = new ArrayList<>(); Pattern pat = Pattern.compile(regexMapDetail.getExtractRegex());//^/card/users/(\w+)/topics/(\w+).do$ Matcher mat = pat.matcher(uri); while (mat.find()) { for (int i = 1; i <= mat.groupCount(); i++) { String find = mat.group(i); paramValueList.add(find);//[155,200] } } //额外的参数表 //这些额外参数是通过url中的正则匹配出来的 Map<String, String> paramMap = new HashMap<>(); for (int i = 0; i < regexMapDetail.getParamNameList().size(); i++) { //userId 155 //topicId 200 paramMap.put(regexMapDetail.getParamNameList().get(i), paramValueList.get(i)); } // /card/users/{userId}/topics/{topicId}.do controllerPathMethodMapper = controllerRoutesMap.get(regexMapDetail.getMethodMapping()); return new ImmutablePair<ControllerPathMethodMapper, Map<String, String>>(controllerPathMethodMapper, paramMap); } } } return null; } public boolean matchPath2(String key) { Pattern pattern = Pattern.compile("^/card/users/\\w+/topics/\\w+.do$"); Matcher matcher = pattern.matcher(key); return matcher.matches(); } public boolean matchPath(String key) { // Pattern pattern = Pattern.compile("^\\/\\w+\\/\\w+$"); Pattern pattern = Pattern.compile("^/\\w+/\\w+$"); Matcher matcher = pattern.matcher(key); return matcher.matches(); } public MutablePair<String, String> resolvePath(String key) { MutablePair<String, String> retPair = new MutablePair<>(); List<String> ret = new ArrayList<>(); Pattern pat = Pattern.compile("^/(\\w+)/(\\w+)$"); Matcher mat = pat.matcher(key); while (mat.find()) { for (int i = 1; i <= mat.groupCount(); i++) { String find = mat.group(i); ret.add(find); } } if (ret.size() == 2) { retPair.setLeft(ret.get(0)); retPair.setRight(ret.get(1)); return retPair; } return null; } /** * 文档路由表 * @author [email protected] * @param basePath * @param file */ private void putDocumentRootMap(String basePath, File file) { Logs.i(String.format("Adding DOCUMENT route -> %s --> %s", basePath, file.getPath())); httpServer.getHttpServiceSummary().trackServicePath(basePath, "http文件服务器"); if (docDirectoryRoutesMap.containsKey(basePath)) { System.err.println("警告:存在重复的document root path: " + basePath + ", 将会仅保留最后的那个映射方法!"); } docDirectoryRoutesMap.put(basePath, file); } private void putFreemarkerRoot(String basePath, String resourcePath) { Logs.i(String.format("Adding FreeMarker route -> %s --> %s", basePath, resourcePath)); httpServer.getHttpServiceSummary().trackServicePath(basePath, "freemarker页面"); if (freemarkerRoutesMap.containsKey(basePath)) { System.err.println("警告:存在重复的freemarker root path: " + basePath + ", 将会仅保留最后的那个映射方法!"); } freemarkerRoutesMap.put(basePath, resourcePath); } /** * 将控制器加入路由表 * @author nan.li * @param c * @param router */ public void addControllerRoutes(Class<BaseController> c, Router router) { if (!controllerClassObjectMap.containsKey(c)) { controllerClassObjectMap.put(c, B.g(c)); } addControllerRoutes(c, controllerClassObjectMap.get(c), router); } /** * 将控制器加入路由表 * @author nan.li * @param controllerClazz * @param controllerProxy 其实是生成的动态代理类 * @param router */ public void addControllerRoutes(Class<BaseController> controllerClazz, BaseController controllerProxy, Router router) { if (controllerProxy == null || router == null) { throw new IllegalArgumentException(); } Class<?> c = (Class<?>)controllerClazz; String basePath = httpServer.getBasePath();//基础路径 if (c.isAnnotationPresent(Controller.class)) { //只有加了@Controller注解的类才会路由 Controller bp = c.getAnnotation(Controller.class); String bpValue = bp.value(); if (StringUtils.isNotEmpty(bpValue) && !bpValue.equals("/")) { basePath = basePath + bpValue; } String finalBasePath = basePath; do { //对所有的声明的方法都进行映射 Arrays.stream(c.getDeclaredMethods()).forEach(method -> { //只有当该方法没有被忽略时,才会对其路由 if (!method.isAnnotationPresent(IgnoreRoute.class)) { //方法名拼接上斜杠,就是路径 String path = ""; //取出Controller方法上面的注释,便于元数据页面进行描述 String controllerMethodAnno = null; if (method.isAnnotationPresent(Anno.class)) { controllerMethodAnno = method.getAnnotation(Anno.class).value(); } //方法映射 String methodMapping = method.getName(); //如果上面有注解,那么就映射到具体的注解所指定的路径上去 if (method.isAnnotationPresent(RequestMapping.class)) { String annoValue = method.getAnnotation(RequestMapping.class).value(); if (StringUtils.isNotEmpty(annoValue)) { // /cache if (annoValue.startsWith("/")) { //@RequestMapping(value = "/cache") //截取到斜杠后面的内容 annoValue = annoValue.substring(1);// cache } if (StringUtils.isNotEmpty(annoValue)) { methodMapping = annoValue; } } } // /users/{userId}/topics/{topicId} // /cache // 可能是走RESTFUL模糊匹配uri参数的,也可能不走 //是否是正则匹配的url boolean regexMapping = methodMapping.indexOf("{") != -1; //controller的结尾是否需要以.do结尾 if (StringUtils.isNotEmpty(httpServer.getControllerSuffix())) { //有后缀,则拼接上后缀 path = String.format("%s/%s.%s", finalBasePath, methodMapping, httpServer.getControllerSuffix()).trim(); } else { //无后缀,则直接是裸方法名 path = String.format("%s/%s", finalBasePath, methodMapping).trim(); } //如何是正则表达式的话,那么还要借用另一个map多做一层关联 if (regexMapping) { //path = /card/users/{userId}/topics/{topicId}.do //将正则的内容放入到另一个表里面 //原有的methodMapping /card/users/{userId}/topics/{topicId}.do //期望匹配到的实际链接 /card/users/155/topics/200.do //参数表: [userId, topicId] //匹配正则 ^/card/users/\\w+/topics/\\w+.do$ 用于进行url参数数据匹配 //提取正则 ^/card/users/(\\w+)/topics/(\\w+).do$ 用户进行url参数提取,获得到 155 200这两个参数,对应于 [userId, topicId] 参数名列表 // Logs.d("regexMapping path: " + path); //用于提取出参数表 Pattern pat = Pattern.compile("\\{(\\w+)\\}"); Matcher mat = pat.matcher(path); List<String> paramNameList = new ArrayList<>();//[userId,topicId] while (mat.find()) { for (int i = 1; i <= mat.groupCount(); i++) { String paramName = mat.group(i); paramNameList.add(paramName); } } // Logs.d("paramNameList: " + paramNameList); //path = /card/users/{userId}/topics/{topicId}.do String urlRegex = String.format("^%s$", path.replaceAll("\\{\\w+\\}", "\\\\w+"));// ^/card/users/\w+/topics/\w+.do$ String extractRegex = String.format("^%s$", path.replaceAll("\\{\\w+\\}", "(\\\\w+)")); //^/card/users/(\w+)/topics/(\w+).do$ // Logs.d("urlRegex: " + urlRegex); // Logs.d("extractRegex: " + extractRegex); router.putToRegexMap(urlRegex, new RegexMapDetail(path, extractRegex, paramNameList), controllerMethodAnno); } else { //构建路由对象 ControllerPathMethodMapper controllerPathMethodMapper = new ControllerPathMethodMapper(path, method, controllerProxy); //将其放入路由表 router.putControllerRoutesMap(path, controllerPathMethodMapper, controllerMethodAnno, httpServer); } } }); } while ((c = c.getSuperclass()) != BaseController.class); } else { //因为没加@Controller注解,所以不会对其路由 } } public static void main(String[] args) { System.out.println("/card/users/{userId}/topics/{topicId}.do".replaceAll("\\{\\w+\\}", "")); //card/users//topics/.do Pattern pat = Pattern.compile("\\{(\\w+)\\}"); Matcher mat = pat.matcher("/card/users/{userId}/topics/{topicId}.do"); while (mat.find()) { for (int i = 0; i <= mat.groupCount(); i++) { String find = mat.group(i); System.out.println(find); } } // {userId} // userId // {topicId} // topicId } /** * 往路由表中增加某个文档根目录的路由 * @author [email protected] * @param basePath * @param file * @param router */ public void addDocumentRootRoutes(String docBasePath, File file, Router router) { String basePath = httpServer.getBasePath(); if (!docBasePath.startsWith("/")) { docBasePath = String.format("/%s", docBasePath); } basePath = String.format("%s%s", basePath, docBasePath); router.putDocumentRootMap(basePath, file); } /** * 增加freemarker的服务根目录映射 * @author nan.li * @param docBasePath * @param resourcePath * @param router */ public void addFreemarkerRootRoutes(String docBasePath, String resourcePath, Router router) { String basePath = httpServer.getBasePath(); if (!docBasePath.startsWith("/")) { docBasePath = String.format("/%s", docBasePath); } if (!resourcePath.endsWith("/")) { resourcePath = String.format("%s/", resourcePath); } basePath = String.format("%s%s", basePath, docBasePath); router.putFreemarkerRoot(basePath, resourcePath); if (!httpServer.isInitFreemarkerRoot()) { httpServer.setFkBasePath(basePath + "/"); httpServer.setFkResourcePath(resourcePath); httpServer.setInitFreemarkerRoot(true); } } /** * RPC实例表 */ private final Map<String, Object> rpcInstanceMap = new HashMap<>(); /** * 注册RPC的实例对象 * @author nan.li * @param clazz */ public void registerRpcImpl(Class<?> clazz) { String interfaceName = clazz.getInterfaces()[0].getSimpleName(); Logs.i("注册RPC Service, serverName=" + interfaceName); rpcInstanceMap.put(interfaceName, ClassKit.newInstance(clazz)); } }