JFinal的ActionKey路由占位符/通配符支持问题

Spring里面支持占位符的路由,比如

/api/v1/admin/prompts/{id}/assignments/{tenant_id}

当我在Controller里面使用

@ActionKey("/api/v1/admin/prompts/*/assignments/*")
或者
@ActionKey("/api/v1/admin/prompts/{id}/assignments/{tenant_id}")


然后访问 http://127.0.0.1:8080/api/v1/admin/prompts/1/assignments/11  

以上两种方法都无法进入到对应的action,直接提示404,

只有 
http://127.0.0.1:8080/api/v1/admin/prompts/*/assignments/*

http://127.0.0.1:8080/api/v1/admin/prompts/{id}/assignments/{tenant_id} 才能正常进入对应的action

请问各位应该怎么处理这样的路由?


当前处理的方式感觉比较丑陋,并且每一个类似的路由都需要手动添加

public void configRoute(Routes me) {
    // 使用 jfinal 4.9.03 新增的路由扫描功能
    me.scan("xx.xxx.");
    me.add(new AdminRoutes());
}
public void configHandler(Handlers me) {
    me.add(new PathVariableHandler());
}
public class AdminRoutes extends Routes {
    @Override
    public void config() {
        // 注册模板路由
        PathVariableHandler.addRoute(
                "/api/v1/admin/prompts/{id}/assign",
                PromptsController.class,
                "assign"
        );
    }
}
public class PathVariableHandler extends Handler {

    // 存储路由模板与正则表达式的映射关系
    private static final Kv routeMap = Kv.create();

    /**
     * 注册模板路由
     *
     * @param template   路由模板,如 "/api/v1/admin/prompts/{id}/assignments/{tenant_id}"
     * @param controller 目标Controller类
     * @param methodName 目标方法名
     */
    public static void addRoute(String template, Class<? extends Controller> controller, String methodName) {
        // 转换模板为正则表达式
        String regex = template.replaceAll("\\{([^}]+)}", "(?<$1>[^/]+)");
        regex = "^" + regex.replace("/", "\\/") + "$";
        Pattern pattern = Pattern.compile(regex);

        // 提取参数名列表
        List<String> paramNames = new ArrayList<>();
        Matcher matcher = Pattern.compile("\\{([^}]+)}").matcher(template);
        while (matcher.find()) {
            paramNames.add(matcher.group(1));
        }

        // 存储路由配置
        routeMap.set(template, new RouteConfig(pattern, paramNames, controller, methodName));
    }

    @Override
    public void handle(String target, HttpServletRequest request, HttpServletResponse response, boolean[] isHandled) {
        for (Object template : routeMap.keySet()) {
            RouteConfig config = routeMap.getAs(template.toString());
            Matcher matcher = config.pattern.matcher(target);
            if (matcher.matches()) {
                // 提取路径参数
                Kv params = Kv.create();
                for (String paramName : config.paramNames) {
                    params.set(paramName, matcher.group(paramName));
                }

                // 将参数存入Request属性
                request.setAttribute("_pathVars", params);

                // 构造ActionKey并转发请求
                String actionKey = AnnotationUtil.getControllerPath(config.controller) + "/" + config.methodName;
                String method = AnnotationUtil.getHttpMethod(config.controller, config.methodName);
                if (method == null || method.equalsIgnoreCase(request.getMethod())) {
                    next.handle(actionKey, request, response, isHandled);
                } else {
                    // HTTP 方法不匹配处理逻辑
                    isHandled[0] = true; // 重要!标记请求已处理
                    Kv error = Kv.create()
                            .set("code", 405)
                            .set("error", "Method Not Allowed")
                            .set("message", String.format(
                                    "Request method '%s' not supported for %s. Required: %s",
                                    request.getMethod(),
                                    request.getRequestURI(),
                                    method
                            ));

                    response.setStatus(405);
                    response.setContentType("application/json");

                    try {
                        response.getWriter().write(JsonKit.toJson(error));
                        response.flushBuffer();
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }
                return;
            }
        }
        next.handle(target, request, response, isHandled);
    }

    // 路由配置内部类
    private static class RouteConfig {
        final Pattern pattern;
        final List<String> paramNames;
        final Class<? extends Controller> controller;
        final String methodName;

        RouteConfig(Pattern pattern, List<String> paramNames, Class<? extends Controller> controller, String methodName) {
            this.pattern = pattern;
            this.paramNames = paramNames;
            this.controller = controller;
            this.methodName = methodName;
        }
    }
}


评论区

杜福忠

2025-05-12 19:22

JF默认不支持这样的路由,里面就是map的get高效key匹配。

扩展支持:
方案1:自定义ActionHandler,里面按需要的业务进行匹配url
https://jfinal.com/doc/2-7
configHandler 》 me.setActionHandler

(推荐)方案2:设置扩展 ActionMapping,按自己业务进行匹配url
https://jfinal.com/doc/2-2
configConstant 》me.setActionMapping 》继承ActionMapping 重写getAction 方法支持自己的业务。 具体的业务实现代码,可以问AI 上面字符串匹配与参数提取方式,会给出正则表达式实现

杜福忠

2025-05-12 19:32

PS:https://jfinal.com/doc/3-4 参数提取后 可使用 - 拼接起来,供Controller的getPara(0),getPara(1) 调用

JFinal

2025-05-12 20:52

一定要支持的话,新增一个 handler ,自己做一下转换,将转换后的 url 对应到相应的 action 即可

本人纯属虚构

2025-05-13 08:57

@杜福忠 感谢支招,但是configConstant阶段,Routes不是为空么,继承ActionMapping需要实现带Routes的有参构造,目前我是在configRoute里 me.add(new AdminRoutes()); AdminRoutes extends Routes,在config里用PathVariableHandler添加路由规则,然后PathVariableHandler extends Handler,在handle里处理跳转到controller对应的action

本人纯属虚构

2025-05-13 09:05

@JFinal 波总,上面贴了代码,感觉比较丑陋,每一条类似的路由都需要手动添加

本人纯属虚构

2025-05-13 09:05

@杜福忠 老铁,上面贴了代码,感觉比较丑陋,每一条类似的路由都需要手动添加

杜福忠

2025-05-13 14:31

@本人纯属虚构 configConstant配置的是钩子 Function func ,是在Config.configJFinal(jfinalConfig); 之后执行的initActionMapping();,你读下源码就知道了。 并且自定义的ActionMapping里面可以区分哪些是需要正则表达式处理的Action,这样可以单独把他们提到一个list中,提前把路由字符串Pattern对象化,list 匹配时效率高。先map get 取Action,没有再去 list里面 Pattern对象for匹配就搞定了,代码少效率高,兼容JF标准写法(如果全是需要正则的路由,可以不要map取值了)

热门反馈

扫码入社