diff --git a/src/main/java/io/jboot/components/restful/DefaultRestfulErrorRender.java b/src/main/java/io/jboot/components/restful/DefaultRestfulErrorRender.java
new file mode 100644
index 0000000000000000000000000000000000000000..1ba7e278ce4abd44c979a88f3ed9db64c20d3604
--- /dev/null
+++ b/src/main/java/io/jboot/components/restful/DefaultRestfulErrorRender.java
@@ -0,0 +1,109 @@
+package io.jboot.components.restful;
+
+import com.jfinal.core.ActionException;
+import com.jfinal.log.Log;
+import com.jfinal.render.Render;
+import com.jfinal.render.RenderException;
+import com.jfinal.render.RenderManager;
+import io.jboot.components.restful.exception.ParameterNullErrorException;
+import io.jboot.components.restful.exception.ParameterParseErrorException;
+import io.jboot.components.restful.exception.RequestMethodErrorException;
+import io.jboot.web.handler.JbootActionHandler;
+
+import java.io.Serializable;
+
+/**
+ * 默认的restful错误响应处理器
+ */
+public class DefaultRestfulErrorRender extends RestfulErrorRender {
+
+ private static final Log log = Log.getLog(JbootActionHandler.class);
+
+ public static class Error implements Serializable {
+ private String errorClass;
+ private int code;
+ private String message;
+
+ public Error(String errorClass, int code, String message) {
+ this.errorClass = errorClass;
+ this.code = code;
+ this.message = message;
+ }
+
+ public String getErrorClass() {
+ return errorClass;
+ }
+
+ public Error setErrorClass(String errorClass) {
+ this.errorClass = errorClass;
+ return this;
+ }
+
+ public int getCode() {
+ return code;
+ }
+
+ public Error setCode(int code) {
+ this.code = code;
+ return this;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public Error setMessage(String message) {
+ this.message = message;
+ return this;
+ }
+ }
+
+ public void render() {
+ log.error("The restful handler intercepted the error", super.getError());
+ Error error = null;
+ if(super.getError() instanceof ParameterNullErrorException
+ || super.getError() instanceof ParameterParseErrorException){
+ //400
+ error = new Error(super.getError().getClass().getName(),
+ HttpStatus.BAD_REQUEST.value(), super.getError().getMessage());
+ } else if(super.getError() instanceof RequestMethodErrorException){
+ error = new Error(super.getError().getClass().getName(),
+ HttpStatus.METHOD_NOT_ALLOWED.value(), super.getError().getMessage());
+ } else if(super.getError() instanceof ActionException){
+ //解析错误代码
+ ActionException actionException = (ActionException)super.getError();
+ int errorCode = actionException.getErrorCode();
+ String msg = "";
+ if (errorCode == 404) {
+ msg = HttpStatus.NOT_FOUND.getReasonPhrase();
+ } else if (errorCode == 400) {
+ msg = HttpStatus.BAD_REQUEST.getReasonPhrase();
+ } else if (errorCode == 401) {
+ msg = HttpStatus.UNAUTHORIZED.getReasonPhrase();
+ } else if (errorCode == 403) {
+ msg = HttpStatus.FORBIDDEN.getReasonPhrase();
+ } else if(errorCode == 405){
+ msg = HttpStatus.METHOD_NOT_ALLOWED.getReasonPhrase();
+ } else {
+ msg = super.getError().getMessage();
+ }
+ error = new Error(super.getError().getClass().getName(),
+ errorCode, msg);
+ } else if(super.getError() instanceof RenderException){
+ //500
+ error = new Error(super.getError().getClass().getName(),
+ HttpStatus.INTERNAL_SERVER_ERROR.value(), super.getError().getMessage());
+ } else {
+ //500
+ error = new Error(super.getError().getClass().getName(),
+ HttpStatus.INTERNAL_SERVER_ERROR.value(), super.getError().getMessage());
+ }
+ Render jsonRender = RenderManager.me().getRenderFactory().getJsonRender(error);
+ jsonRender.setContext(super.request, super.response, super.getAction() == null? "" : super.getAction().getViewPath());
+ super.response.setStatus(error.getCode());
+ jsonRender.render();
+ }
+
+
+
+}
diff --git a/src/main/java/io/jboot/components/restful/HttpStatus.java b/src/main/java/io/jboot/components/restful/HttpStatus.java
new file mode 100644
index 0000000000000000000000000000000000000000..aa54f020457de13e9349a2bb34549b8e6d2ae8c8
--- /dev/null
+++ b/src/main/java/io/jboot/components/restful/HttpStatus.java
@@ -0,0 +1,405 @@
+package io.jboot.components.restful;
+
+public enum HttpStatus {
+
+ /**
+ * {@code 100 Continue}.
+ * @see HTTP/1.1: Semantics and Content, section 6.2.1
+ */
+ CONTINUE(100, "Continue"),
+ /**
+ * {@code 101 Switching Protocols}.
+ * @see HTTP/1.1: Semantics and Content, section 6.2.2
+ */
+ SWITCHING_PROTOCOLS(101, "Switching Protocols"),
+ /**
+ * {@code 102 Processing}.
+ * @see WebDAV
+ */
+ PROCESSING(102, "Processing"),
+ /**
+ * {@code 103 Checkpoint}.
+ * @see A proposal for supporting
+ * resumable POST/PUT HTTP requests in HTTP/1.0
+ */
+ CHECKPOINT(103, "Checkpoint"),
+
+ // 2xx Success
+
+ /**
+ * {@code 200 OK}.
+ * @see HTTP/1.1: Semantics and Content, section 6.3.1
+ */
+ OK(200, "OK"),
+ /**
+ * {@code 201 Created}.
+ * @see HTTP/1.1: Semantics and Content, section 6.3.2
+ */
+ CREATED(201, "Created"),
+ /**
+ * {@code 202 Accepted}.
+ * @see HTTP/1.1: Semantics and Content, section 6.3.3
+ */
+ ACCEPTED(202, "Accepted"),
+ /**
+ * {@code 203 Non-Authoritative Information}.
+ * @see HTTP/1.1: Semantics and Content, section 6.3.4
+ */
+ NON_AUTHORITATIVE_INFORMATION(203, "Non-Authoritative Information"),
+ /**
+ * {@code 204 No Content}.
+ * @see HTTP/1.1: Semantics and Content, section 6.3.5
+ */
+ NO_CONTENT(204, "No Content"),
+ /**
+ * {@code 205 Reset Content}.
+ * @see HTTP/1.1: Semantics and Content, section 6.3.6
+ */
+ RESET_CONTENT(205, "Reset Content"),
+ /**
+ * {@code 206 Partial Content}.
+ * @see HTTP/1.1: Range Requests, section 4.1
+ */
+ PARTIAL_CONTENT(206, "Partial Content"),
+ /**
+ * {@code 207 Multi-Status}.
+ * @see WebDAV
+ */
+ MULTI_STATUS(207, "Multi-Status"),
+ /**
+ * {@code 208 Already Reported}.
+ * @see WebDAV Binding Extensions
+ */
+ ALREADY_REPORTED(208, "Already Reported"),
+ /**
+ * {@code 226 IM Used}.
+ * @see Delta encoding in HTTP
+ */
+ IM_USED(226, "IM Used"),
+
+ // 3xx Redirection
+
+ /**
+ * {@code 300 Multiple Choices}.
+ * @see HTTP/1.1: Semantics and Content, section 6.4.1
+ */
+ MULTIPLE_CHOICES(300, "Multiple Choices"),
+ /**
+ * {@code 301 Moved Permanently}.
+ * @see HTTP/1.1: Semantics and Content, section 6.4.2
+ */
+ MOVED_PERMANENTLY(301, "Moved Permanently"),
+ /**
+ * {@code 302 Found}.
+ * @see HTTP/1.1: Semantics and Content, section 6.4.3
+ */
+ FOUND(302, "Found"),
+ /**
+ * {@code 302 Moved Temporarily}.
+ * @see HTTP/1.0, section 9.3
+ * @deprecated in favor of {@link #FOUND} which will be returned from {@code HttpStatus.valueOf(302)}
+ */
+ @Deprecated
+ MOVED_TEMPORARILY(302, "Moved Temporarily"),
+ /**
+ * {@code 303 See Other}.
+ * @see HTTP/1.1: Semantics and Content, section 6.4.4
+ */
+ SEE_OTHER(303, "See Other"),
+ /**
+ * {@code 304 Not Modified}.
+ * @see HTTP/1.1: Conditional Requests, section 4.1
+ */
+ NOT_MODIFIED(304, "Not Modified"),
+ /**
+ * {@code 305 Use Proxy}.
+ * @see HTTP/1.1: Semantics and Content, section 6.4.5
+ * @deprecated due to security concerns regarding in-band configuration of a proxy
+ */
+ @Deprecated
+ USE_PROXY(305, "Use Proxy"),
+ /**
+ * {@code 307 Temporary Redirect}.
+ * @see HTTP/1.1: Semantics and Content, section 6.4.7
+ */
+ TEMPORARY_REDIRECT(307, "Temporary Redirect"),
+ /**
+ * {@code 308 Permanent Redirect}.
+ * @see RFC 7238
+ */
+ PERMANENT_REDIRECT(308, "Permanent Redirect"),
+
+ // --- 4xx Client Error ---
+
+ /**
+ * {@code 400 Bad Request}.
+ * @see HTTP/1.1: Semantics and Content, section 6.5.1
+ */
+ BAD_REQUEST(400, "Bad Request"),
+ /**
+ * {@code 401 Unauthorized}.
+ * @see HTTP/1.1: Authentication, section 3.1
+ */
+ UNAUTHORIZED(401, "Unauthorized"),
+ /**
+ * {@code 402 Payment Required}.
+ * @see HTTP/1.1: Semantics and Content, section 6.5.2
+ */
+ PAYMENT_REQUIRED(402, "Payment Required"),
+ /**
+ * {@code 403 Forbidden}.
+ * @see HTTP/1.1: Semantics and Content, section 6.5.3
+ */
+ FORBIDDEN(403, "Forbidden"),
+ /**
+ * {@code 404 Not Found}.
+ * @see HTTP/1.1: Semantics and Content, section 6.5.4
+ */
+ NOT_FOUND(404, "Not Found"),
+ /**
+ * {@code 405 Method Not Allowed}.
+ * @see HTTP/1.1: Semantics and Content, section 6.5.5
+ */
+ METHOD_NOT_ALLOWED(405, "Method Not Allowed"),
+ /**
+ * {@code 406 Not Acceptable}.
+ * @see HTTP/1.1: Semantics and Content, section 6.5.6
+ */
+ NOT_ACCEPTABLE(406, "Not Acceptable"),
+ /**
+ * {@code 407 Proxy Authentication Required}.
+ * @see HTTP/1.1: Authentication, section 3.2
+ */
+ PROXY_AUTHENTICATION_REQUIRED(407, "Proxy Authentication Required"),
+ /**
+ * {@code 408 Request Timeout}.
+ * @see HTTP/1.1: Semantics and Content, section 6.5.7
+ */
+ REQUEST_TIMEOUT(408, "Request Timeout"),
+ /**
+ * {@code 409 Conflict}.
+ * @see HTTP/1.1: Semantics and Content, section 6.5.8
+ */
+ CONFLICT(409, "Conflict"),
+ /**
+ * {@code 410 Gone}.
+ * @see
+ * HTTP/1.1: Semantics and Content, section 6.5.9
+ */
+ GONE(410, "Gone"),
+ /**
+ * {@code 411 Length Required}.
+ * @see
+ * HTTP/1.1: Semantics and Content, section 6.5.10
+ */
+ LENGTH_REQUIRED(411, "Length Required"),
+ /**
+ * {@code 412 Precondition failed}.
+ * @see
+ * HTTP/1.1: Conditional Requests, section 4.2
+ */
+ PRECONDITION_FAILED(412, "Precondition Failed"),
+ /**
+ * {@code 413 Payload Too Large}.
+ * @since 4.1
+ * @see
+ * HTTP/1.1: Semantics and Content, section 6.5.11
+ */
+ PAYLOAD_TOO_LARGE(413, "Payload Too Large"),
+ /**
+ * {@code 413 Request Entity Too Large}.
+ * @see HTTP/1.1, section 10.4.14
+ * @deprecated in favor of {@link #PAYLOAD_TOO_LARGE} which will be
+ * returned from {@code HttpStatus.valueOf(413)}
+ */
+ @Deprecated
+ REQUEST_ENTITY_TOO_LARGE(413, "Request Entity Too Large"),
+ /**
+ * {@code 414 URI Too Long}.
+ * @since 4.1
+ * @see
+ * HTTP/1.1: Semantics and Content, section 6.5.12
+ */
+ URI_TOO_LONG(414, "URI Too Long"),
+ /**
+ * {@code 414 Request-URI Too Long}.
+ * @see HTTP/1.1, section 10.4.15
+ * @deprecated in favor of {@link #URI_TOO_LONG} which will be returned from {@code HttpStatus.valueOf(414)}
+ */
+ @Deprecated
+ REQUEST_URI_TOO_LONG(414, "Request-URI Too Long"),
+ /**
+ * {@code 415 Unsupported Media Type}.
+ * @see
+ * HTTP/1.1: Semantics and Content, section 6.5.13
+ */
+ UNSUPPORTED_MEDIA_TYPE(415, "Unsupported Media Type"),
+ /**
+ * {@code 416 Requested Range Not Satisfiable}.
+ * @see HTTP/1.1: Range Requests, section 4.4
+ */
+ REQUESTED_RANGE_NOT_SATISFIABLE(416, "Requested range not satisfiable"),
+ /**
+ * {@code 417 Expectation Failed}.
+ * @see
+ * HTTP/1.1: Semantics and Content, section 6.5.14
+ */
+ EXPECTATION_FAILED(417, "Expectation Failed"),
+ /**
+ * {@code 418 I'm a teapot}.
+ * @see HTCPCP/1.0
+ */
+ I_AM_A_TEAPOT(418, "I'm a teapot"),
+ /**
+ * @deprecated See
+ *
+ * WebDAV Draft Changes
+ */
+ @Deprecated
+ INSUFFICIENT_SPACE_ON_RESOURCE(419, "Insufficient Space On Resource"),
+ /**
+ * @deprecated See
+ *
+ * WebDAV Draft Changes
+ */
+ @Deprecated
+ METHOD_FAILURE(420, "Method Failure"),
+ /**
+ * @deprecated
+ * See
+ * WebDAV Draft Changes
+ */
+ @Deprecated
+ DESTINATION_LOCKED(421, "Destination Locked"),
+ /**
+ * {@code 422 Unprocessable Entity}.
+ * @see WebDAV
+ */
+ UNPROCESSABLE_ENTITY(422, "Unprocessable Entity"),
+ /**
+ * {@code 423 Locked}.
+ * @see WebDAV
+ */
+ LOCKED(423, "Locked"),
+ /**
+ * {@code 424 Failed Dependency}.
+ * @see WebDAV
+ */
+ FAILED_DEPENDENCY(424, "Failed Dependency"),
+ /**
+ * {@code 426 Upgrade Required}.
+ * @see Upgrading to TLS Within HTTP/1.1
+ */
+ UPGRADE_REQUIRED(426, "Upgrade Required"),
+ /**
+ * {@code 428 Precondition Required}.
+ * @see Additional HTTP Status Codes
+ */
+ PRECONDITION_REQUIRED(428, "Precondition Required"),
+ /**
+ * {@code 429 Too Many Requests}.
+ * @see Additional HTTP Status Codes
+ */
+ TOO_MANY_REQUESTS(429, "Too Many Requests"),
+ /**
+ * {@code 431 Request Header Fields Too Large}.
+ * @see Additional HTTP Status Codes
+ */
+ REQUEST_HEADER_FIELDS_TOO_LARGE(431, "Request Header Fields Too Large"),
+ /**
+ * {@code 451 Unavailable For Legal Reasons}.
+ * @see
+ * An HTTP Status Code to Report Legal Obstacles
+ * @since 4.3
+ */
+ UNAVAILABLE_FOR_LEGAL_REASONS(451, "Unavailable For Legal Reasons"),
+
+ // --- 5xx Server Error ---
+
+ /**
+ * {@code 500 Internal Server Error}.
+ * @see HTTP/1.1: Semantics and Content, section 6.6.1
+ */
+ INTERNAL_SERVER_ERROR(500, "Internal Server Error"),
+ /**
+ * {@code 501 Not Implemented}.
+ * @see HTTP/1.1: Semantics and Content, section 6.6.2
+ */
+ NOT_IMPLEMENTED(501, "Not Implemented"),
+ /**
+ * {@code 502 Bad Gateway}.
+ * @see HTTP/1.1: Semantics and Content, section 6.6.3
+ */
+ BAD_GATEWAY(502, "Bad Gateway"),
+ /**
+ * {@code 503 Service Unavailable}.
+ * @see HTTP/1.1: Semantics and Content, section 6.6.4
+ */
+ SERVICE_UNAVAILABLE(503, "Service Unavailable"),
+ /**
+ * {@code 504 Gateway Timeout}.
+ * @see HTTP/1.1: Semantics and Content, section 6.6.5
+ */
+ GATEWAY_TIMEOUT(504, "Gateway Timeout"),
+ /**
+ * {@code 505 HTTP Version Not Supported}.
+ * @see HTTP/1.1: Semantics and Content, section 6.6.6
+ */
+ HTTP_VERSION_NOT_SUPPORTED(505, "HTTP Version not supported"),
+ /**
+ * {@code 506 Variant Also Negotiates}
+ * @see Transparent Content Negotiation
+ */
+ VARIANT_ALSO_NEGOTIATES(506, "Variant Also Negotiates"),
+ /**
+ * {@code 507 Insufficient Storage}
+ * @see WebDAV
+ */
+ INSUFFICIENT_STORAGE(507, "Insufficient Storage"),
+ /**
+ * {@code 508 Loop Detected}
+ * @see WebDAV Binding Extensions
+ */
+ LOOP_DETECTED(508, "Loop Detected"),
+ /**
+ * {@code 509 Bandwidth Limit Exceeded}
+ */
+ BANDWIDTH_LIMIT_EXCEEDED(509, "Bandwidth Limit Exceeded"),
+ /**
+ * {@code 510 Not Extended}
+ * @see HTTP Extension Framework
+ */
+ NOT_EXTENDED(510, "Not Extended"),
+ /**
+ * {@code 511 Network Authentication Required}.
+ * @see Additional HTTP Status Codes
+ */
+ NETWORK_AUTHENTICATION_REQUIRED(511, "Network Authentication Required");
+
+
+ private final int value;
+
+ private final String reasonPhrase;
+
+
+ HttpStatus(int value, String reasonPhrase) {
+ this.value = value;
+ this.reasonPhrase = reasonPhrase;
+ }
+
+
+ /**
+ * Return the integer value of this status code.
+ */
+ public int value() {
+ return this.value;
+ }
+
+ /**
+ * Return the reason phrase of this status code.
+ */
+ public String getReasonPhrase() {
+ return this.reasonPhrase;
+ }
+
+}
diff --git a/src/main/java/io/jboot/components/restful/JbootRestfulManager.java b/src/main/java/io/jboot/components/restful/JbootRestfulManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..ff1fb15bebc4ab6e46d58b62fdd2fb755c888335
--- /dev/null
+++ b/src/main/java/io/jboot/components/restful/JbootRestfulManager.java
@@ -0,0 +1,237 @@
+package io.jboot.components.restful;
+
+import com.jfinal.aop.Interceptor;
+import com.jfinal.aop.InterceptorManager;
+import com.jfinal.config.Routes;
+import com.jfinal.core.Action;
+import com.jfinal.core.Controller;
+import com.jfinal.core.NotAction;
+import io.jboot.components.restful.annotation.DeleteMapping;
+import io.jboot.components.restful.annotation.GetMapping;
+import io.jboot.components.restful.annotation.PostMapping;
+import io.jboot.components.restful.annotation.PutMapping;
+import io.jboot.components.restful.exception.RequestMethodErrorException;
+import io.jboot.utils.StrUtil;
+import io.jboot.web.controller.annotation.RequestMapping;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class JbootRestfulManager {
+
+ public static class Config {
+ private boolean mappingSupperClass;
+ private String baseViewPath;
+ private Interceptor[] routeInterceptors;
+ private List routes;
+
+ public Config() {
+ }
+
+ public Config(boolean mappingSupperClass, String baseViewPath,
+ Interceptor[] routeInterceptors, List routes) {
+ this.mappingSupperClass = mappingSupperClass;
+ this.baseViewPath = baseViewPath;
+ this.routeInterceptors = routeInterceptors;
+ this.routes = routes;
+ }
+
+ public boolean isMappingSupperClass() {
+ return mappingSupperClass;
+ }
+
+ public Config setMappingSupperClass(boolean mappingSupperClass) {
+ this.mappingSupperClass = mappingSupperClass;
+ return this;
+ }
+
+ public String getBaseViewPath() {
+ return baseViewPath;
+ }
+
+ public Config setBaseViewPath(String baseViewPath) {
+ this.baseViewPath = baseViewPath;
+ return this;
+ }
+
+ public Interceptor[] getRouteInterceptors() {
+ return routeInterceptors;
+ }
+
+ public Config setRouteInterceptors(Interceptor[] routeInterceptors) {
+ this.routeInterceptors = routeInterceptors;
+ return this;
+ }
+
+ public List getRoutes() {
+ return routes;
+ }
+
+ public Config setRoutes(List routes) {
+ this.routes = routes;
+ return this;
+ }
+ }
+
+ private static JbootRestfulManager me = new JbootRestfulManager();
+
+ private Map restfulActions = new HashMap<>(2048, 0.5F);
+
+ protected static final String SLASH = "/";
+
+ private RestfulErrorRender restfulErrorRender = new DefaultRestfulErrorRender();
+
+ public static JbootRestfulManager me() {
+ return me;
+ }
+
+ public void init(Config config) {
+ if (config.getRoutes() == null || config.getRoutes().isEmpty()) {
+ return;
+ }
+ InterceptorManager interMan = InterceptorManager.me();
+ Class> dc;
+ // 初始化自定义的restful controller
+ for (Routes.Route route : config.getRoutes()) {
+ Class extends Controller> controllerClass = route.getControllerClass();
+ Interceptor[] controllerInters = interMan.createControllerInterceptor(controllerClass);
+
+ boolean declaredMethods = config.isMappingSupperClass()
+ ? controllerClass.getSuperclass() == Controller.class
+ : true;
+
+ String baseRequestMapping = SLASH;
+ if (controllerClass.getAnnotation(RequestMapping.class) != null) {
+ RequestMapping requestMapping = controllerClass.getAnnotation(RequestMapping.class);
+ if (requestMapping.value().startsWith(SLASH)) {
+ baseRequestMapping = requestMapping.value();
+ } else {
+ baseRequestMapping = baseRequestMapping + requestMapping.value();
+ }
+ }
+
+ Method[] methods = (declaredMethods ? controllerClass.getDeclaredMethods() : controllerClass.getMethods());
+ for (Method method : methods) {
+ if (declaredMethods) {
+ if (!Modifier.isPublic(method.getModifiers()))
+ continue;
+ } else {
+ dc = method.getDeclaringClass();
+ if (dc == Controller.class || dc == Object.class)
+ continue;
+ }
+ //去除mapping
+ if (method.getAnnotation(NotAction.class) != null) {
+ continue;
+ }
+ Interceptor[] actionInters = interMan.buildControllerActionInterceptor(config.getRouteInterceptors(), controllerInters, controllerClass, method);
+
+ String actionKey = baseRequestMapping;
+
+ //GET 判断
+ GetMapping getMapping = method.getAnnotation(GetMapping.class);
+ PostMapping postMapping = method.getAnnotation(PostMapping.class);
+ PutMapping putMapping = method.getAnnotation(PutMapping.class);
+ DeleteMapping deleteMapping = method.getAnnotation(DeleteMapping.class);
+ String requestMethod = "", mappingValue = "";
+ if (getMapping != null) {
+ requestMethod = "GET";
+ if (StrUtil.isNotBlank(getMapping.value())) {
+ mappingValue = getMapping.value();
+ }
+ } else if (postMapping != null) {
+ requestMethod = "POST";
+ if (StrUtil.isNotBlank(postMapping.value())) {
+ mappingValue = postMapping.value();
+ }
+ } else if (putMapping != null) {
+ requestMethod = "PUT";
+ if (StrUtil.isNotBlank(putMapping.value())) {
+ mappingValue = putMapping.value();
+ }
+ } else if (deleteMapping != null) {
+ requestMethod = "DELETE";
+ if (StrUtil.isNotBlank(deleteMapping.value())) {
+ mappingValue = deleteMapping.value();
+ }
+ } else {
+ //默认为get请求
+ requestMethod = "GET";
+ mappingValue = SLASH;
+ }
+ if (StrUtil.isNotBlank(mappingValue)) {
+ if (!actionKey.endsWith(SLASH)) {
+ actionKey = actionKey + SLASH;
+ }
+ if (mappingValue.startsWith(SLASH)) {
+ mappingValue = mappingValue.substring(1);
+ }
+ actionKey = actionKey + mappingValue;
+ } else {
+ if (actionKey.endsWith(SLASH)) {
+ actionKey = actionKey.substring(0, actionKey.length() - 1);
+ }
+ }
+ Action action = new Action(baseRequestMapping, actionKey, controllerClass,
+ method, method.getName(), actionInters, route.getFinalViewPath(config.getBaseViewPath()));
+ String key = requestMethod + ":" + actionKey;
+
+ RestfulAction restfulAction = new RestfulAction(action, actionKey, requestMethod);
+ if (restfulActions.put(key, restfulAction) != null) {
+ //已经存在指定的key
+ throw new RuntimeException(buildMsg(actionKey, controllerClass, method));
+ }
+ }
+ }
+ }
+
+ protected String buildMsg(String actionKey, Class extends Controller> controllerClass, Method method) {
+ StringBuilder sb = new StringBuilder("The action \"")
+ .append(controllerClass.getName()).append(".")
+ .append(method.getName()).append("()\" can not be mapped, ")
+ .append("actionKey \"").append(actionKey).append("\" is already in use.");
+
+ String msg = sb.toString();
+ System.err.println("\nException: " + msg);
+ return msg;
+ }
+
+ public RestfulAction getRestfulAction(String target, String requestMethod) {
+ String actionKey = requestMethod + ":" + target;
+ //先直接获取
+ RestfulAction restfulAction = restfulActions.get(actionKey);
+ if (restfulAction == null) {
+ //路径判断
+ String[] paths = actionKey.split(":")[1].replace(requestMethod, "").split(SLASH);
+ for (String _actionKey : restfulActions.keySet()) {
+ String _requestMethod = _actionKey.split(":")[0];
+ String _target = _actionKey.split(":")[1];
+ System.out.println("---------> target:"+target+",_target:"+_target+",_requestMethod:"+_requestMethod+",requestMethod:" + requestMethod);
+ if( target.equals(_target) && !_requestMethod.equals(requestMethod) ){
+ //请求方法不正确
+ throw new RequestMethodErrorException(_actionKey, _requestMethod, target, requestMethod);
+ }
+ String[] _paths = _actionKey.split(":")[1].replace(requestMethod, "").split(SLASH);
+ if (_actionKey.startsWith(requestMethod) &&
+ _actionKey.contains("{") && _actionKey.contains("}")
+ && paths.length == _paths.length && RestfulUtils.comparePaths(_paths, paths)) {
+ restfulAction = restfulActions.get(_actionKey);
+ break;
+ }
+ }
+ }
+ return restfulAction;
+ }
+
+ public RestfulErrorRender getRestfulErrorRender() {
+ return restfulErrorRender;
+ }
+
+ public JbootRestfulManager setRestfulErrorRender(RestfulErrorRender restfulErrorRender) {
+ this.restfulErrorRender = restfulErrorRender;
+ return this;
+ }
+}
diff --git a/src/main/java/io/jboot/components/restful/ResponseEntity.java b/src/main/java/io/jboot/components/restful/ResponseEntity.java
new file mode 100644
index 0000000000000000000000000000000000000000..90aad64467c540f9f4a3c0f682e5ad41c8e432ed
--- /dev/null
+++ b/src/main/java/io/jboot/components/restful/ResponseEntity.java
@@ -0,0 +1,67 @@
+package io.jboot.components.restful;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class ResponseEntity {
+
+ //响应的数据
+ private T data;
+
+ //自定义响应头部信息
+ private Map headers = new HashMap<>();
+
+ //默认http状态
+ private HttpStatus httpStatus = HttpStatus.OK;
+
+ public T getData() {
+ return data;
+ }
+
+ public ResponseEntity setData(T data) {
+ this.data = data;
+ return this;
+ }
+
+ public ResponseEntity addHeaders(Map headers) {
+ this.headers.putAll(headers);
+ return this;
+ }
+
+ public ResponseEntity addHeader(String key, String value) {
+ this.headers.put(key, value);
+ return this;
+ }
+
+ public Map getHeaders() {
+ return headers;
+ }
+
+ public ResponseEntity setHttpStatus(HttpStatus httpStatus) {
+ this.httpStatus = httpStatus;
+ return this;
+ }
+
+ public HttpStatus getHttpStatus() {
+ return httpStatus;
+ }
+
+ public ResponseEntity() {
+ }
+
+ public ResponseEntity(T data, Map headers, HttpStatus httpStatus) {
+ this.data = data;
+ this.headers = headers;
+ this.httpStatus = httpStatus;
+ }
+
+ public ResponseEntity(Map headers, HttpStatus httpStatus) {
+ this.headers = headers;
+ this.httpStatus = httpStatus;
+ }
+
+ public ResponseEntity(T data) {
+ this.data = data;
+ }
+
+}
diff --git a/src/main/java/io/jboot/components/restful/RestfulAction.java b/src/main/java/io/jboot/components/restful/RestfulAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..98843e3b821e0186807288f63bcdf3d67f97cc08
--- /dev/null
+++ b/src/main/java/io/jboot/components/restful/RestfulAction.java
@@ -0,0 +1,47 @@
+package io.jboot.components.restful;
+
+import com.jfinal.core.Action;
+
+import java.io.Serializable;
+
+public class RestfulAction implements Serializable {
+
+ private Action action;
+
+ private String actionKey;
+
+ private String method;
+
+ public RestfulAction(Action action, String actionKey, String method) {
+ this.action = action;
+ this.actionKey = actionKey;
+ this.method = method;
+ }
+
+ public Action getAction() {
+ return action;
+ }
+
+ public RestfulAction setAction(Action action) {
+ this.action = action;
+ return this;
+ }
+
+ public String getActionKey() {
+ return actionKey;
+ }
+
+ public RestfulAction setActionKey(String actionKey) {
+ this.actionKey = actionKey;
+ return this;
+ }
+
+ public String getMethod() {
+ return method;
+ }
+
+ public RestfulAction setMethod(String method) {
+ this.method = method;
+ return this;
+ }
+}
diff --git a/src/main/java/io/jboot/components/restful/RestfulCallback.java b/src/main/java/io/jboot/components/restful/RestfulCallback.java
new file mode 100644
index 0000000000000000000000000000000000000000..0e088863c7dc91ddf95ba0c94ab417a185534ca6
--- /dev/null
+++ b/src/main/java/io/jboot/components/restful/RestfulCallback.java
@@ -0,0 +1,29 @@
+package io.jboot.components.restful;
+
+import com.jfinal.proxy.Callback;
+
+public class RestfulCallback implements Callback {
+
+ private RestfulAction restfulAction;
+
+ private Object target;
+
+ public RestfulCallback(RestfulAction restfulAction, Object target) {
+ this.restfulAction = restfulAction;
+ this.target = target;
+ }
+
+ public RestfulAction getRestfulAction() {
+ return restfulAction;
+ }
+
+ public Object getTarget() {
+ return target;
+ }
+
+ @Override
+ public Object call(Object[] args) throws Throwable {
+ return restfulAction.getAction().getMethod().invoke(target, args);
+ }
+
+}
diff --git a/src/main/java/io/jboot/components/restful/RestfulErrorRender.java b/src/main/java/io/jboot/components/restful/RestfulErrorRender.java
new file mode 100644
index 0000000000000000000000000000000000000000..d2154da2fc9440b60dd9061ed96e14016f4965dc
--- /dev/null
+++ b/src/main/java/io/jboot/components/restful/RestfulErrorRender.java
@@ -0,0 +1,35 @@
+package io.jboot.components.restful;
+
+import com.jfinal.core.Action;
+import com.jfinal.render.Render;
+
+
+public abstract class RestfulErrorRender extends Render {
+
+ private String target;
+
+ private Action action;
+
+ private Exception error;
+
+ public void init (String target, Action action, Exception error) {
+ this.target = target;
+ this.action = action;
+ this.error = error;
+ }
+
+ protected RestfulErrorRender() {
+ }
+
+ protected String getTarget() {
+ return target;
+ }
+
+ protected Action getAction() {
+ return action;
+ }
+
+ protected Exception getError() {
+ return error;
+ }
+}
diff --git a/src/main/java/io/jboot/components/restful/RestfulHandler.java b/src/main/java/io/jboot/components/restful/RestfulHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..ca18c1b560a6adc329eac03aa6597ca7baf56d24
--- /dev/null
+++ b/src/main/java/io/jboot/components/restful/RestfulHandler.java
@@ -0,0 +1,234 @@
+package io.jboot.components.restful;
+
+import com.jfinal.aop.Invocation;
+import com.jfinal.core.*;
+import com.jfinal.log.Log;
+import com.jfinal.render.Render;
+import com.jfinal.render.RenderException;
+import io.jboot.components.restful.annotation.DownloadResponse;
+import io.jboot.components.restful.annotation.ResponseHeader;
+import io.jboot.components.restful.annotation.ResponseHeaders;
+import io.jboot.components.restful.exception.ParameterNullErrorException;
+import io.jboot.components.restful.exception.ParameterParseErrorException;
+import io.jboot.components.restful.exception.RequestMethodErrorException;
+import io.jboot.utils.ArrayUtil;
+import io.jboot.utils.ClassUtil;
+import io.jboot.utils.StrUtil;
+import io.jboot.web.controller.JbootControllerContext;
+import io.jboot.web.handler.JbootActionHandler;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.File;
+import java.lang.reflect.Method;
+import java.util.Map;
+
+public class RestfulHandler extends JbootActionHandler {
+
+ private static final Log log = Log.getLog(JbootActionHandler.class);
+
+ public Action getAction(String target, String[] urlPara) {
+ return super.getAction(target, urlPara);
+ }
+
+ @Override
+ public void handle(String target, HttpServletRequest request, HttpServletResponse response, boolean[] isHandled) {
+ if (target.indexOf('.') != -1) {
+ return;
+ }
+
+ isHandled[0] = true;
+ String[] urlPara = {null};
+ Action action = getAction(target, urlPara);
+ RestfulAction restfulAction = null;
+ if (action != null && action.getActionKey().equals("/") && action.getMethodName().equals("index")
+ && StrUtil.isNotBlank(target) && !target.equals(action.getActionKey())) {
+ //如果被默认的"/"拦截,并且为index方法,但是请求的url又和actionKey不匹配,则有可能是restful请求
+ try {
+ restfulAction = JbootRestfulManager.me().getRestfulAction(target, request.getMethod());
+ } catch (RequestMethodErrorException e) {
+ handleActionException(target, request, response, action, e);
+ return;
+ }
+ if (restfulAction != null) {
+ action = null;
+ }
+ }
+ //如果无法从内置的action中获取action则尝试从restful管理的action中获取
+ if (action == null) {
+ // 尝试从restful 获取action
+ try {
+ if (restfulAction == null) {
+ restfulAction = JbootRestfulManager.me().getRestfulAction(target, request.getMethod());
+ }
+ } catch (RequestMethodErrorException e) {
+ handleActionException(target, request, response, action, e);
+ return;
+ }
+ if (restfulAction != null) {
+ //restful 风格的请求处理
+ restfulRequest(restfulAction, target, request, response, isHandled);
+ return;
+ }
+ if (log.isWarnEnabled()) {
+ String qs = request.getQueryString();
+ log.warn("404 Action Not Found: " + (qs == null ? target : target + "?" + qs));
+ }
+ renderManager.getRenderFactory().getErrorRender(404).setContext(request, response).render();
+ return;
+ }
+ //在获取到了
+ super.handle(target, request, response, isHandled);
+ }
+
+ private void restfulRequest(RestfulAction restfulAction, String target, HttpServletRequest request, HttpServletResponse response, boolean[] isHandled) {
+ Action action = restfulAction.getAction();
+ Controller controller = null;
+ try {
+ controller = controllerFactory.getController(action.getControllerClass());
+ //注入依赖
+ if (injectDependency) {
+ com.jfinal.aop.Aop.inject(controller);
+ }
+ //绑定controller本身到当前线程
+ JbootControllerContext.hold(controller);
+ //初始化controller
+ CPI._init_(controller, action, request, response, null);
+
+ //解析参数
+ Object[] args = RestfulUtils.parseActionMethodParameters(target, restfulAction.getActionKey(),
+ action.getMethod(), request, controller.getRawData());
+ RestfulCallback restfulCallback = new RestfulCallback(restfulAction, controller);
+
+ Invocation invocation = new Invocation(controller, action.getMethod(), action.getInterceptors(),
+ restfulCallback, args);
+
+ RestfulInvocation fixedInvocation;
+ if (devMode) {
+ if (ActionReporter.isReportAfterInvocation(request)) {
+ fixedInvocation = invokeInvocation(invocation);
+ ActionReporter.report(target, controller, action);
+ } else {
+ ActionReporter.report(target, controller, action);
+ fixedInvocation = invokeInvocation(invocation);
+ }
+ } else {
+ fixedInvocation = invokeInvocation(invocation);
+ }
+
+ Object returnValue = fixedInvocation.getReturnValue();
+
+ // 判断是否带有@DownloadResponse
+ DownloadResponse downloadResponse = action.getMethod().getAnnotation(DownloadResponse.class);
+ if (returnValue == null && downloadResponse == null) {
+ //无返回结果的 restful 请求
+ controller.renderNull();
+ }
+ // 如果标记了@DownloadResponse,并且返回值不为空,会被认为自行处理了下载行为
+ if (downloadResponse != null) {
+ return;
+ }
+ if (returnValue != null) {
+ //初始化返回值
+ initRenderValue(returnValue, controller);
+ }
+
+ Render render = controller.getRender();
+ if (render instanceof ForwardActionRender) {
+ String actionUrl = ((ForwardActionRender) render).getActionUrl();
+ if (target.equals(actionUrl)) {
+ throw new RuntimeException("The forward action url is the same as before.");
+ } else {
+ handle(actionUrl, request, response, isHandled);
+ }
+ return;
+ }
+
+ if (render == null) {
+ render = renderManager.getRenderFactory().getDefaultRender(action.getViewPath() + action.getMethodName());
+ }
+
+ //初始化自定义头部
+ initResponseHeaders(response, fixedInvocation.getMethod());
+ //响应开始
+ render.setContext(request, response, action.getViewPath()).render();
+
+ } catch (RenderException | ParameterNullErrorException | ParameterParseErrorException | ActionException e) {
+ handleActionException(target, request, response, action, e);
+ } catch (Exception e) {
+ String info = ClassUtil.buildMethodString(action.getMethod());
+ if (log.isErrorEnabled()) {
+ String qs = request.getQueryString();
+ String targetInfo = qs == null ? target : target + "?" + qs;
+ log.error(info + " : " + targetInfo, e);
+ }
+ //自定义错误处理
+ handleActionException(target, request, response, action, e);
+ } finally {
+ JbootControllerContext.release();
+ controllerFactory.recycle(controller);
+ }
+ }
+
+ private void handleActionException(String target, HttpServletRequest request, HttpServletResponse response,
+ Action action, Exception e) {
+ RestfulErrorRender restfulErrorRender = JbootRestfulManager.me().getRestfulErrorRender();
+ restfulErrorRender.setContext(request, response, action == null ? "" : action.getViewPath());
+ restfulErrorRender.init(target, action, e);
+ restfulErrorRender.render();
+ }
+
+
+ protected RestfulInvocation invokeInvocation(Invocation inv) {
+ RestfulInvocation fixedInvocation = new RestfulInvocation(inv);
+ fixedInvocation.invoke();
+ return fixedInvocation;
+ }
+
+ protected void initResponseHeaders(HttpServletResponse response, Method actionMethod) {
+ ResponseHeader[] responseHeaders = actionMethod.getAnnotationsByType(ResponseHeader.class);
+ ResponseHeaders responseHeadersList = actionMethod.getAnnotation(ResponseHeaders.class);
+ if (responseHeadersList != null && responseHeadersList.value().length > 0) {
+ if (responseHeaders != null && responseHeaders.length > 0) {
+ responseHeaders = ArrayUtil.concat(responseHeaders, responseHeadersList.value());
+ } else {
+ responseHeaders = responseHeadersList.value();
+ }
+ }
+ if (responseHeaders.length > 0) {
+ for (ResponseHeader header : responseHeaders) {
+ response.setHeader(header.key(), header.value());
+ }
+ }
+ }
+
+ protected void initRenderValue(Object o, Controller controller) {
+ if (o.getClass().equals(String.class)
+ || o.getClass().equals(int.class)
+ || o.getClass().equals(double.class)
+ || o.getClass().equals(byte.class)
+ || o.getClass().equals(long.class)
+ || o.getClass().equals(float.class)
+ || o.getClass().equals(short.class)
+ || o.getClass().equals(char.class)
+ || o.getClass().equals(boolean.class)) {
+ controller.renderText(String.valueOf(o));
+ } else if (o.getClass().equals(File.class)) {
+ controller.renderFile((File) o);
+ } else if (o.getClass().equals(ResponseEntity.class)) {
+ ResponseEntity responseEntity = (ResponseEntity) o;
+ //设置自定义头部信息
+ Map headers = responseEntity.getHeaders();
+
+ headers.forEach((k, v) -> controller.getResponse().setHeader(k, v));
+ //设置http状态代码
+ controller.getResponse().setStatus(responseEntity.getHttpStatus().value());
+ initRenderValue(responseEntity.getData(), controller);
+ } else if (o instanceof Render) { //如果是render类型直接设置render
+ controller.render((Render) o);
+ } else {
+ controller.renderJson(o);
+ }
+ }
+
+}
diff --git a/src/main/java/io/jboot/components/restful/RestfulInvocation.java b/src/main/java/io/jboot/components/restful/RestfulInvocation.java
new file mode 100644
index 0000000000000000000000000000000000000000..6be030eb3639ce77d1cebfc6dc673f2f89697210
--- /dev/null
+++ b/src/main/java/io/jboot/components/restful/RestfulInvocation.java
@@ -0,0 +1,19 @@
+package io.jboot.components.restful;
+
+import com.jfinal.aop.Invocation;
+import com.jfinal.core.Controller;
+import io.jboot.web.fixedinterceptor.FixedInvocation;
+
+public class RestfulInvocation extends FixedInvocation {
+
+ public RestfulInvocation(Invocation invocation) {
+ super(invocation);
+ }
+
+ @Override
+ public Controller getController() {
+ if (getTarget() == null)
+ throw new RuntimeException("This method can only be used for action interception");
+ return (Controller)getTarget();
+ }
+}
diff --git a/src/main/java/io/jboot/components/restful/RestfulUtils.java b/src/main/java/io/jboot/components/restful/RestfulUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..cbb64f62f50cc53bc5034d4131e376afcbf0cad0
--- /dev/null
+++ b/src/main/java/io/jboot/components/restful/RestfulUtils.java
@@ -0,0 +1,201 @@
+package io.jboot.components.restful;
+
+
+import com.jfinal.kit.JsonKit;
+import io.jboot.components.restful.annotation.PathVariable;
+import io.jboot.components.restful.annotation.RequestBody;
+import io.jboot.components.restful.annotation.RequestHeader;
+import io.jboot.components.restful.annotation.RequestParam;
+import io.jboot.components.restful.exception.ParameterNullErrorException;
+import io.jboot.components.restful.exception.ParameterParseErrorException;
+import io.jboot.utils.StrUtil;
+
+import javax.servlet.http.HttpServletRequest;
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+import java.util.HashMap;
+import java.util.Map;
+
+public class RestfulUtils {
+
+ /**
+ * 从url中解析路径参数
+ *
+ * @param url
+ * @param actionKey
+ * @return
+ */
+ public static Map parsePathVariables(String url, String actionKey) {
+ if (actionKey.contains("{") && actionKey.contains("}")) {
+ Map pathVariables = new HashMap<>();
+ String[] paths = url.split("/");
+ String[] _paths = actionKey.split("/");
+ for (int i = 0; i < paths.length; i++) {
+ if (_paths[i].startsWith("{") && _paths[i].endsWith("}")) {
+ String pathKey = _paths[i].substring(1, _paths[i].length() - 1);
+ String value = paths[i];
+ pathVariables.put(pathKey, value);
+ }
+ }
+ return pathVariables;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * 转换请求action请求的参数信息
+ *
+ * @param target
+ * @param actionKey
+ * @param actionMethod
+ * @param request
+ * @param rawData
+ * @return
+ * @throws ParameterNullErrorException
+ * @throws ParameterParseErrorException
+ */
+ public static Object[] parseActionMethodParameters(String target, String actionKey, Method actionMethod, HttpServletRequest request, String rawData)
+ throws ParameterNullErrorException, ParameterParseErrorException {
+ Object[] args = new Object[actionMethod.getParameters().length];
+ for (int i = 0; i < actionMethod.getParameters().length; i++) {
+ Parameter parameter = actionMethod.getParameters()[i];
+ RequestParam requestParam = parameter.getAnnotation(RequestParam.class);
+ RequestBody requestBody = parameter.getAnnotation(RequestBody.class);
+ RequestHeader requestHeader = parameter.getAnnotation(RequestHeader.class);
+ PathVariable pathVariable = parameter.getAnnotation(PathVariable.class);
+ String parameterName = parameter.getName();
+ String values[];
+ if (requestParam != null) {
+ if (StrUtil.isNotBlank(requestParam.value())) {
+ parameterName = requestParam.value();
+ }
+ values = request.getParameterValues(parameterName);
+ parameter.getType();
+ args[i] = parseRequestParamToParameter(values, parameterName, parameter.getType());
+ if (args[i] == null && requestParam.required()) {
+ //要求参数为空,但是却并没有提供参数
+ throw new ParameterNullErrorException(parameterName);
+ }
+ } else if (requestBody != null) {
+ args[i] = parseRequestBodyToParameter(rawData, parameterName, parameter.getType());
+ } else if (requestHeader != null) {
+ if (StrUtil.isNotBlank(requestHeader.value())) {
+ parameterName = requestHeader.value();
+ }
+ String value = request.getHeader(parameterName);
+ args[i] = parseRequestHeaderToParameter(value, parameterName, parameter.getType());
+ if (args[i] == null && requestHeader.required()) {
+ //要求参数为空,但是却并没有提供参数
+ throw new ParameterNullErrorException(parameterName);
+ }
+ } else if (pathVariable != null) {
+ if (StrUtil.isNotBlank(pathVariable.value())) {
+ parameterName = pathVariable.value();
+ }
+ args[i] = parsePathVariableToParameter(target, actionKey, parameterName, parameter.getType());
+ } else {
+ args[i] = null;
+ }
+ }
+ return args;
+ }
+
+ /**
+ * 比对url请求路径
+ *
+ * @param sourcePaths action配置的原路径
+ * @param targetPaths 请求的目标路径
+ * @return
+ */
+ public static boolean comparePaths(String[] sourcePaths, String[] targetPaths) {
+ int matchingCount = 0;
+ for (int i = 0; i < sourcePaths.length; i++) {
+ if (sourcePaths[i].equals(targetPaths[i])
+ || (sourcePaths[i].startsWith("{") && sourcePaths[i].endsWith("}"))) {
+ matchingCount += 1;
+ }
+ }
+ return matchingCount == sourcePaths.length;
+ }
+
+ private static Object parseRequestParamToParameter(String[] value, String name, Class> parameterTypeClass) {
+ if(parameterTypeClass.isArray()){
+ Object [] objects = new Object[value.length];
+ for (int i = 0; i < value.length; i++) {
+ objects[i] = parseCommonValue(value[i], name, parameterTypeClass);
+ }
+ return objects;
+ } else {
+ if(value != null && value.length > 0){
+ return parseCommonValue(value[0], name, parameterTypeClass);
+ }
+ }
+
+ return null;
+ }
+
+ private static Object parseRequestHeaderToParameter(String header, String name, Class> parameterTypeClass) {
+ return parseCommonValue(header, name, parameterTypeClass);
+ }
+
+ private static Object parseRequestBodyToParameter(String body, String name, Class> parameterTypeClass) {
+ //先当作基本数据来转换
+ Object value = parseCommonValue(body, name, parameterTypeClass);
+ if(value == null){
+ value = JsonKit.parse(body, parameterTypeClass);
+ }
+ return value;
+ }
+
+ private static Object parsePathVariableToParameter(String target, String actionKey, String parameterName, Class> parameterTypeClass) {
+ Map pathVariables = parsePathVariables(target, actionKey);
+ String value = pathVariables.get(parameterName);
+ return parseCommonValue(value, parameterName, parameterTypeClass);
+ }
+
+ /**
+ * 转换基本类型参数,目前支持string,int,double,float,boolean,long基本类型数据
+ * @param value
+ * @param name
+ * @param parameterTypeClass
+ * @return
+ */
+ private static Object parseCommonValue(String value, String name, Class> parameterTypeClass) {
+ if (StrUtil.isBlank(value)) {
+ return null;
+ }
+ if (parameterTypeClass.equals(String.class)) {
+ return value;
+ } else if (parameterTypeClass.equals(int.class)) {
+ try {
+ return Integer.valueOf(value);
+ } catch (NumberFormatException e) {
+ throw new ParameterParseErrorException(value, name, parameterTypeClass);
+ }
+ } else if (parameterTypeClass.equals(double.class)) {
+ try {
+ return Double.valueOf(value);
+ } catch (NumberFormatException e) {
+ throw new ParameterParseErrorException(value, name, parameterTypeClass);
+ }
+ } else if (parameterTypeClass.equals(float.class)) {
+ try {
+ return Float.valueOf(value);
+ } catch (NumberFormatException e) {
+ throw new ParameterParseErrorException(value, name, parameterTypeClass);
+ }
+ } else if (parameterTypeClass.equals(boolean.class)) {
+ return Boolean.valueOf(value);
+ } else if (parameterTypeClass.equals(long.class)) {
+ try {
+ return Long.valueOf(value);
+ } catch (NumberFormatException e) {
+ throw new ParameterParseErrorException(value, name, parameterTypeClass);
+ }
+ } else {
+ return null;
+ }
+ }
+
+}
diff --git a/src/main/java/io/jboot/components/restful/annotation/DeleteMapping.java b/src/main/java/io/jboot/components/restful/annotation/DeleteMapping.java
new file mode 100644
index 0000000000000000000000000000000000000000..2483f1c33d112838c96bcee35c4153e040b71a85
--- /dev/null
+++ b/src/main/java/io/jboot/components/restful/annotation/DeleteMapping.java
@@ -0,0 +1,19 @@
+package io.jboot.components.restful.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 删除delete 方法注解
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+@Documented
+public @interface DeleteMapping {
+
+ /**
+ * url mapping
+ * @return
+ */
+ String value() default "";
+
+}
diff --git a/src/main/java/io/jboot/components/restful/annotation/DownloadResponse.java b/src/main/java/io/jboot/components/restful/annotation/DownloadResponse.java
new file mode 100644
index 0000000000000000000000000000000000000000..c4aa83e81af1d5259ab1797017e80dc1dac13178
--- /dev/null
+++ b/src/main/java/io/jboot/components/restful/annotation/DownloadResponse.java
@@ -0,0 +1,12 @@
+package io.jboot.components.restful.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 标注controller action是一个下载响应,并且需要action自行处理response
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+@Documented
+public @interface DownloadResponse {
+}
diff --git a/src/main/java/io/jboot/components/restful/annotation/GetMapping.java b/src/main/java/io/jboot/components/restful/annotation/GetMapping.java
new file mode 100644
index 0000000000000000000000000000000000000000..c571a745e56e34593f2774050318d01b1f63408d
--- /dev/null
+++ b/src/main/java/io/jboot/components/restful/annotation/GetMapping.java
@@ -0,0 +1,16 @@
+package io.jboot.components.restful.annotation;
+
+import java.lang.annotation.*;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+@Documented
+public @interface GetMapping {
+
+ /**
+ * url mapping
+ * @return
+ */
+ String value() default "";
+
+}
diff --git a/src/main/java/io/jboot/components/restful/annotation/PathVariable.java b/src/main/java/io/jboot/components/restful/annotation/PathVariable.java
new file mode 100644
index 0000000000000000000000000000000000000000..68a962491c0c6af12dfd1c47400f6dfc4d3d1453
--- /dev/null
+++ b/src/main/java/io/jboot/components/restful/annotation/PathVariable.java
@@ -0,0 +1,27 @@
+package io.jboot.components.restful.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 路径参数注解
+ * /user/{id}/cards
+ * 支持如下类型参数注入:
+ * string
+ * int
+ * double
+ * float
+ * boolean
+ * long
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.PARAMETER)
+@Documented
+public @interface PathVariable {
+
+ /**
+ * 如果为空则默认为参数名本身
+ * @return
+ */
+ String value() default "";
+
+}
diff --git a/src/main/java/io/jboot/components/restful/annotation/PostMapping.java b/src/main/java/io/jboot/components/restful/annotation/PostMapping.java
new file mode 100644
index 0000000000000000000000000000000000000000..e0f9a45ac7c70aa8900b49f073e2dfc09769207a
--- /dev/null
+++ b/src/main/java/io/jboot/components/restful/annotation/PostMapping.java
@@ -0,0 +1,16 @@
+package io.jboot.components.restful.annotation;
+
+import java.lang.annotation.*;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+@Documented
+public @interface PostMapping {
+
+ /**
+ * url mapping
+ * @return
+ */
+ String value() default "";
+
+}
diff --git a/src/main/java/io/jboot/components/restful/annotation/PutMapping.java b/src/main/java/io/jboot/components/restful/annotation/PutMapping.java
new file mode 100644
index 0000000000000000000000000000000000000000..8d0c66d25a7e9ee3fbec4d27099483db1cccbbaf
--- /dev/null
+++ b/src/main/java/io/jboot/components/restful/annotation/PutMapping.java
@@ -0,0 +1,19 @@
+package io.jboot.components.restful.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * Put 请求方法定义
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+@Documented
+public @interface PutMapping {
+
+ /**
+ * url mapping
+ * @return
+ */
+ String value() default "";
+
+}
diff --git a/src/main/java/io/jboot/components/restful/annotation/RequestBody.java b/src/main/java/io/jboot/components/restful/annotation/RequestBody.java
new file mode 100644
index 0000000000000000000000000000000000000000..73327a66058d243fc484ea12bfc6014c6d7d221e
--- /dev/null
+++ b/src/main/java/io/jboot/components/restful/annotation/RequestBody.java
@@ -0,0 +1,20 @@
+package io.jboot.components.restful.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 请求体参数注解
+ * 支持如下类型参数:
+ * string / string[]
+ * int / int[]
+ * double / double[]
+ * float / float[]
+ * boolean / boolean[]
+ * long / long[]
+ * object / object[]
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.PARAMETER)
+@Documented
+public @interface RequestBody {
+}
diff --git a/src/main/java/io/jboot/components/restful/annotation/RequestHeader.java b/src/main/java/io/jboot/components/restful/annotation/RequestHeader.java
new file mode 100644
index 0000000000000000000000000000000000000000..5a9679701f541fc920d706a69a9368d3546bc821
--- /dev/null
+++ b/src/main/java/io/jboot/components/restful/annotation/RequestHeader.java
@@ -0,0 +1,29 @@
+package io.jboot.components.restful.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 请求头部信息注解
+ * 支持如下类型参数注入:
+ * string
+ * int
+ * double
+ * float
+ * boolean
+ * long
+ *
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.PARAMETER)
+@Documented
+public @interface RequestHeader {
+
+ /**
+ * 如果为空则默认为参数名本身
+ * @return
+ */
+ String value() default "";
+
+ boolean required() default false;
+
+}
diff --git a/src/main/java/io/jboot/components/restful/annotation/RequestParam.java b/src/main/java/io/jboot/components/restful/annotation/RequestParam.java
new file mode 100644
index 0000000000000000000000000000000000000000..e4a16089e93e9727d943267238e0caf26fbb0a2a
--- /dev/null
+++ b/src/main/java/io/jboot/components/restful/annotation/RequestParam.java
@@ -0,0 +1,29 @@
+package io.jboot.components.restful.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 请求参数注解
+ * 支持如下类型参数:
+ * string / string[]
+ * int / int[]
+ * double / double[]
+ * float / float[]
+ * boolean / boolean[]
+ * long / long[]
+ *
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.PARAMETER)
+@Documented
+public @interface RequestParam {
+
+ /**
+ * 如果为空则默认为参数名本身
+ * @return
+ */
+ String value() default "";
+
+ boolean required() default false;
+
+}
diff --git a/src/main/java/io/jboot/components/restful/annotation/ResponseHeader.java b/src/main/java/io/jboot/components/restful/annotation/ResponseHeader.java
new file mode 100644
index 0000000000000000000000000000000000000000..626f22bfd588384c5a563280818c9ff8d305c3c8
--- /dev/null
+++ b/src/main/java/io/jboot/components/restful/annotation/ResponseHeader.java
@@ -0,0 +1,17 @@
+package io.jboot.components.restful.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 自定义响应头
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+@Documented
+public @interface ResponseHeader {
+
+ String key();
+
+ String value();
+
+}
diff --git a/src/main/java/io/jboot/components/restful/annotation/ResponseHeaders.java b/src/main/java/io/jboot/components/restful/annotation/ResponseHeaders.java
new file mode 100644
index 0000000000000000000000000000000000000000..d51f43aebcd50a9d38d1b6fccdd05ad511d187a9
--- /dev/null
+++ b/src/main/java/io/jboot/components/restful/annotation/ResponseHeaders.java
@@ -0,0 +1,12 @@
+package io.jboot.components.restful.annotation;
+
+import java.lang.annotation.*;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+@Documented
+public @interface ResponseHeaders {
+
+ ResponseHeader[] value();
+
+}
diff --git a/src/main/java/io/jboot/components/restful/annotation/RestController.java b/src/main/java/io/jboot/components/restful/annotation/RestController.java
new file mode 100644
index 0000000000000000000000000000000000000000..6650c4768c23ce7e9546f80f014f1e5548fbd27e
--- /dev/null
+++ b/src/main/java/io/jboot/components/restful/annotation/RestController.java
@@ -0,0 +1,12 @@
+package io.jboot.components.restful.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * rest controller 标识
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Documented
+public @interface RestController {
+}
diff --git a/src/main/java/io/jboot/components/restful/exception/ParameterNullErrorException.java b/src/main/java/io/jboot/components/restful/exception/ParameterNullErrorException.java
new file mode 100644
index 0000000000000000000000000000000000000000..081867cccb5c665399abf5116e70affe88ca95b4
--- /dev/null
+++ b/src/main/java/io/jboot/components/restful/exception/ParameterNullErrorException.java
@@ -0,0 +1,24 @@
+package io.jboot.components.restful.exception;
+
+/**
+ * 参数为空错误
+ */
+public class ParameterNullErrorException extends RuntimeException {
+
+ private String parameterName;
+
+
+ public String getParameterName() {
+ return parameterName;
+ }
+
+ public ParameterNullErrorException(String parameterName) {
+ super("Parameter '"+parameterName+"' specifies a forced check, but the value is null");
+ this.parameterName = parameterName;
+ }
+
+ public ParameterNullErrorException(Exception e) {
+ super(e);
+ }
+
+}
diff --git a/src/main/java/io/jboot/components/restful/exception/ParameterParseErrorException.java b/src/main/java/io/jboot/components/restful/exception/ParameterParseErrorException.java
new file mode 100644
index 0000000000000000000000000000000000000000..2a025ab6563b66edd8b7c875b4ab68d67c4aaf07
--- /dev/null
+++ b/src/main/java/io/jboot/components/restful/exception/ParameterParseErrorException.java
@@ -0,0 +1,39 @@
+package io.jboot.components.restful.exception;
+
+/**
+ * 参数类型错误
+ */
+public class ParameterParseErrorException extends RuntimeException {
+
+
+ private String parameterValue;
+
+ private String parameterName;
+
+ private Class> parameterType;
+
+ public String getParameterValue() {
+ return parameterValue;
+ }
+
+ public String getParameterName() {
+ return parameterName;
+ }
+
+ public Class> getParameterType() {
+ return parameterType;
+ }
+
+ public ParameterParseErrorException(String parameterValue, String parameterName, Class> parameterType) {
+ super("Error resolving parameter '" + parameterName + "', unable to match value '"
+ + parameterValue + "' to specified type '" + parameterType.getName() + "'");
+ this.parameterValue = parameterValue;
+ this.parameterName = parameterName;
+ this.parameterType = parameterType;
+ }
+
+ public ParameterParseErrorException(Exception e) {
+ super(e);
+ }
+
+}
diff --git a/src/main/java/io/jboot/components/restful/exception/RequestMethodErrorException.java b/src/main/java/io/jboot/components/restful/exception/RequestMethodErrorException.java
new file mode 100644
index 0000000000000000000000000000000000000000..e72f83c97ea85862d73754f77e24d9396aa66cef
--- /dev/null
+++ b/src/main/java/io/jboot/components/restful/exception/RequestMethodErrorException.java
@@ -0,0 +1,39 @@
+package io.jboot.components.restful.exception;
+
+/**
+ * 请求方法错误
+ */
+public class RequestMethodErrorException extends RuntimeException {
+
+ private String actionKey;
+
+ private String actionMethod;
+
+ private String target;
+
+ private String targetMethod;
+
+ public String getActionKey() {
+ return actionKey;
+ }
+
+ public String getActionMethod() {
+ return actionMethod;
+ }
+
+ public String getTarget() {
+ return target;
+ }
+
+ public String getTargetMethod() {
+ return targetMethod;
+ }
+
+ public RequestMethodErrorException(String actionKey, String actionMethod, String target, String targetMethod) {
+ super("'" + target + "' is specified as a '" + actionMethod + "' request. '" + targetMethod + "' requests are not supported");
+ this.actionKey = actionKey;
+ this.actionMethod = actionMethod;
+ this.target = target;
+ this.targetMethod = targetMethod;
+ }
+}
diff --git a/src/main/java/io/jboot/core/JbootCoreConfig.java b/src/main/java/io/jboot/core/JbootCoreConfig.java
index 35e59b2956aa260b6835ea4baeed358a5ad05c2c..2a50dd26bb1eb0d6dd8dc90794bf72e6255d6c84 100644
--- a/src/main/java/io/jboot/core/JbootCoreConfig.java
+++ b/src/main/java/io/jboot/core/JbootCoreConfig.java
@@ -36,6 +36,9 @@ import io.jboot.app.config.support.nacos.NacosConfigManager;
import io.jboot.components.gateway.JbootGatewayHandler;
import io.jboot.components.gateway.JbootGatewayManager;
import io.jboot.components.limiter.LimiterManager;
+import io.jboot.components.restful.JbootRestfulManager;
+import io.jboot.components.restful.RestfulHandler;
+import io.jboot.components.restful.annotation.RestController;
import io.jboot.components.rpc.JbootrpcManager;
import io.jboot.components.schedule.JbootScheduleManager;
import io.jboot.core.listener.JbootAppListenerManager;
@@ -74,6 +77,9 @@ public class JbootCoreConfig extends JFinalConfig {
private List routeList = new ArrayList<>();
+ private JbootRestfulManager.Config restfulConfig = new JbootRestfulManager.Config();
+
+
public JbootCoreConfig() {
initSystemProperties();
@@ -158,6 +164,8 @@ public class JbootCoreConfig extends JFinalConfig {
routes.setMappingSuperClass(true);
+ List restfulRoutes = new ArrayList<>();
+
List> controllerClassList = ClassScanner.scanSubClass(Controller.class);
if (ArrayUtil.isNotEmpty(controllerClassList)) {
for (Class clazz : controllerClassList) {
@@ -171,6 +179,13 @@ public class JbootCoreConfig extends JFinalConfig {
continue;
}
+ //检查是否是restful类型的controller,如果是则加入restful专门指定的routes
+ RestController restController = clazz.getAnnotation(RestController.class);
+ if(restController != null){
+ restfulRoutes.add(new Routes.Route(value, clazz, value));
+ continue;
+ }
+
String viewPath = AnnotationUtil.get(mapping.viewPath());
if (StrUtil.isNotBlank(viewPath)) {
@@ -192,6 +207,18 @@ public class JbootCoreConfig extends JFinalConfig {
JbootControllerManager.me().setMapping(route.getControllerKey(), route.getControllerClass());
}
+ if( !restfulRoutes.isEmpty() ){
+ //处理restful专属的routes
+ restfulConfig.setRoutes(restfulRoutes)
+ .setBaseViewPath(routes.getBaseViewPath())
+ .setMappingSupperClass(routes.getMappingSuperClass())
+ .setRouteInterceptors(routes.getInterceptors());
+ for (Routes.Route route : restfulRoutes) {
+ JbootControllerManager.me().setMapping(route.getControllerKey(), route.getControllerClass());
+ }
+ routeList.addAll(restfulRoutes);
+ }
+
routeList.addAll(routes.getRouteItemList());
}
@@ -266,6 +293,7 @@ public class JbootCoreConfig extends JFinalConfig {
handlers.add(new JbootGatewayHandler());
handlers.add(new JbootFilterHandler());
handlers.add(new JbootHandler());
+ handlers.setActionHandler(new RestfulHandler());
//若用户自己没配置 ActionHandler,默认使用 JbootActionHandler
if (handlers.getActionHandler() == null) {
@@ -292,6 +320,8 @@ public class JbootCoreConfig extends JFinalConfig {
JbootSeataManager.me().init();
SentinelManager.me().init();
JbootGatewayManager.me().init();
+ JbootRestfulManager.me().init(restfulConfig);
+
JbootAppListenerManager.me().onStart();
}
diff --git a/src/test/java/io/jboot/test/restful/RestfulController.java b/src/test/java/io/jboot/test/restful/RestfulController.java
new file mode 100644
index 0000000000000000000000000000000000000000..08a2367da89812b5c180b9358db61380e94cad13
--- /dev/null
+++ b/src/test/java/io/jboot/test/restful/RestfulController.java
@@ -0,0 +1,117 @@
+package io.jboot.test.restful;
+
+import com.jfinal.aop.Before;
+import com.jfinal.aop.Inject;
+import com.jfinal.aop.Interceptor;
+import com.jfinal.aop.Invocation;
+import com.jfinal.core.NotAction;
+import com.jfinal.kit.JsonKit;
+import com.jfinal.kit.StrKit;
+import io.jboot.components.restful.HttpStatus;
+import io.jboot.components.restful.ResponseEntity;
+import io.jboot.components.restful.annotation.*;
+import io.jboot.web.controller.JbootController;
+import io.jboot.web.controller.annotation.RequestMapping;
+import io.jboot.web.cors.EnableCORS;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+@RestController
+@RequestMapping("/restful")
+public class RestfulController extends JbootController {
+
+ public static class Data implements Serializable {
+ private String id;
+ private String name;
+ private int age;
+
+ public Data(String id, String name, int age) {
+ this.id = id;
+ this.name = name;
+ this.age = age;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public int getAge() {
+ return age;
+ }
+ }
+
+ public static class RestfulInterceptor implements Interceptor {
+
+ public void intercept(Invocation inv) {
+ System.out.println("--------> restful request begin");
+ inv.invoke();
+ System.out.println("--------> restful request end");
+ }
+ }
+
+ @Inject
+ private RestfulService restfulService;
+
+ @NotAction
+ public List initData(){
+ List users = new ArrayList<>();
+ users.add(new RestfulController.Data("1", "tom", 18));
+ users.add(new RestfulController.Data("2", "andy", 29));
+ users.add(new RestfulController.Data("3", "max", 13));
+ return users;
+ }
+
+ //GET /restful
+ @GetMapping
+ @EnableCORS
+ @Before({RestfulInterceptor.class})
+ @ResponseHeaders({@ResponseHeader(key = "d-head-1", value = "a"), @ResponseHeader(key = "d-head-2", value = "b")})
+ public List users(){
+ return initData();
+ }
+
+ // GET /restful/randomKey
+ @GetMapping("/randomKey")
+ public String randomKey(){
+ return restfulService.getRandomKey();
+ }
+
+ // GET /restful/users
+ @GetMapping("/users")
+ public ResponseEntity> entityUsers(){
+ return new ResponseEntity<>(initData()).addHeader("x-token", StrKit.getRandomUUID()).setHttpStatus(HttpStatus.ACCEPTED);
+ }
+
+ // PUT /restful
+ @PutMapping
+ public void create(@RequestBody RestfulController.Data data){
+ System.out.println("get request body data:\n" + JsonKit.toJson(data));
+ }
+
+ // PUT /restful/createList
+ @PutMapping("/createList")
+ public void createUsers(@RequestBody List users){
+ System.out.println("get request body data:\n" + JsonKit.toJson(users));
+ }
+
+ // DELETE /restful/:id
+ @DeleteMapping("/{id}")
+ public void delete(@PathVariable("id") String id){
+ System.out.println("delete by id : " + id );
+ }
+
+ // DELETE /restful/delete
+ @DeleteMapping("/delete")
+ public void deleteByName(@RequestParam(value = "name", required = true) String name,
+ @RequestHeader String token){
+ System.out.println("delete by name : " + name);
+ System.out.println("get token header : " + token);
+ }
+
+}
diff --git a/src/test/java/io/jboot/test/restful/RestfulService.java b/src/test/java/io/jboot/test/restful/RestfulService.java
new file mode 100644
index 0000000000000000000000000000000000000000..aea7b1cdd07935764254fad5991bd84c72317c1d
--- /dev/null
+++ b/src/test/java/io/jboot/test/restful/RestfulService.java
@@ -0,0 +1,5 @@
+package io.jboot.test.restful;
+
+public interface RestfulService {
+ String getRandomKey();
+}
diff --git a/src/test/java/io/jboot/test/restful/RestfulServiceImpl.java b/src/test/java/io/jboot/test/restful/RestfulServiceImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..1c042af367aa2f29755ae80f58ee77737c74ab24
--- /dev/null
+++ b/src/test/java/io/jboot/test/restful/RestfulServiceImpl.java
@@ -0,0 +1,12 @@
+package io.jboot.test.restful;
+
+import com.jfinal.kit.StrKit;
+import io.jboot.aop.annotation.Bean;
+
+@Bean
+public class RestfulServiceImpl implements RestfulService {
+ @Override
+ public String getRandomKey() {
+ return StrKit.getRandomUUID();
+ }
+}