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..e850ec54c3134c640cdceaf14f7b31d6fc729f6e --- /dev/null +++ b/src/main/java/io/jboot/components/restful/JbootRestfulManager.java @@ -0,0 +1,229 @@ +package io.jboot.components.restful; + +import com.jfinal.aop.Interceptor; +import com.jfinal.aop.InterceptorManager; +import com.jfinal.config.Routes; +import com.jfinal.core.ActionException; +import com.jfinal.core.Controller; +import com.jfinal.core.NotAction; +import com.jfinal.render.RenderManager; +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.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 RenderManager renderManager = RenderManager.me(); + + 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 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); + } + } + RestfulAction action = new RestfulAction(baseRequestMapping, actionKey, controllerClass, + method, method.getName(), actionInters, route.getFinalViewPath(config.getBaseViewPath()), requestMethod); + String key = requestMethod + ":" + actionKey; + if (restfulActions.put(key, action) != null) { + //已经存在指定的key + throw new RuntimeException(buildMsg(actionKey, controllerClass, method)); + } + } + } + } + + protected String buildMsg(String actionKey, Class 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 = target.split(SLASH); + for(Map.Entry entry : restfulActions.entrySet()){ + String _requestMethod = entry.getValue().getRequestMethod(); + String _target = entry.getValue().getActionKey(); + if (target.equals(_target) && !_requestMethod.equals(requestMethod)) { + //请求方法不正确 + throw new ActionException(HttpStatus.METHOD_NOT_ALLOWED.value(), + renderManager.getRenderFactory().getErrorRender(HttpStatus.METHOD_NOT_ALLOWED.value()), + "'" + target + "' is specified as a '" + _requestMethod + "' request. '" + requestMethod + "' requests are not supported"); + } + String[] _paths = entry.getValue().getActionKey().split(SLASH); + if (entry.getValue().getActionKey().startsWith(requestMethod) && + entry.getValue().getActionKey().contains("{") && entry.getValue().getActionKey().contains("}") + && paths.length == _paths.length && RestfulUtils.comparePaths(_paths, paths)) { + restfulAction = entry.getValue(); + break; + } + } + } + return restfulAction; + } +} 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..bae89f315534fa76f33ac8b8f344df76abd4d84c --- /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 Object data; + + //自定义响应头部信息 + private Map headers = new HashMap<>(); + + //默认http状态 + private HttpStatus httpStatus = HttpStatus.OK; + + public Object getData() { + return data; + } + + public ResponseEntity setData(Object 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(Object 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(Object 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..1178f8c8f4903c5d817cc6cb71256ea320c5c997 --- /dev/null +++ b/src/main/java/io/jboot/components/restful/RestfulAction.java @@ -0,0 +1,24 @@ +package io.jboot.components.restful; + +import com.jfinal.aop.Interceptor; +import com.jfinal.core.Action; +import com.jfinal.core.Controller; + +import java.lang.reflect.Method; + +public class RestfulAction extends Action { + + private String requestMethod; + + public String getRequestMethod() { + return requestMethod; + } + + public RestfulAction(String controllerKey, String actionKey, Class controllerClass, + Method method, String methodName, Interceptor[] interceptors, String viewPath, String requestMethod) { + super(controllerKey, actionKey, controllerClass, method, methodName, interceptors, viewPath); + this.requestMethod = requestMethod; + } + + +} 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..00b6bd66467df0df9b381a7060f6adb5c082677d --- /dev/null +++ b/src/main/java/io/jboot/components/restful/RestfulCallback.java @@ -0,0 +1,21 @@ +package io.jboot.components.restful; + +import com.jfinal.core.Action; +import com.jfinal.proxy.Callback; + +public class RestfulCallback implements Callback { + + private Action action; + private Object target; + + public RestfulCallback(Action restfulAction, Object target) { + this.action = restfulAction; + this.target = target; + } + + @Override + public Object call(Object[] args) throws Throwable { + return action.getMethod().invoke(target, args); + } + +} 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..d6b44370953b0c48bb3f4f9ec47753aca885bf33 --- /dev/null +++ b/src/main/java/io/jboot/components/restful/RestfulHandler.java @@ -0,0 +1,64 @@ +package io.jboot.components.restful; + +import com.jfinal.aop.Invocation; +import com.jfinal.core.Action; +import com.jfinal.core.Controller; +import com.jfinal.log.Log; +import io.jboot.components.restful.annotation.ResponseHeader; +import io.jboot.components.restful.annotation.ResponseHeaders; +import io.jboot.utils.ArrayUtil; +import io.jboot.utils.StrUtil; +import io.jboot.web.handler.JbootActionHandler; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class RestfulHandler extends JbootActionHandler { + + private static final Log log = Log.getLog(JbootActionHandler.class); + + @Override + public Action getAction(String target, String[] urlPara, HttpServletRequest request) { + Action action = super.getAction(target, urlPara); + if(action != null){ + if (action.getActionKey().equals("/") && action.getMethodName().equals("index") + && StrUtil.isNotBlank(target) && !target.equals(action.getActionKey())) { + action = JbootRestfulManager.me().getRestfulAction(target, request.getMethod()); + } + } + if (action == null) { + action = JbootRestfulManager.me().getRestfulAction(target, request.getMethod()); + } + return action; + } + + @Override + public Invocation getInvocation(Action action, Controller controller) { + if (action instanceof RestfulAction) { + Object[] args = RestfulUtils.parseActionMethodParameters(action.getActionKey(), action.getActionKey(), + action.getMethod(), controller.getRequest(), controller.getRawData()); + return new RestfulInvocation(action, controller, args); + } else { + return super.getInvocation(action, controller); + } + } + + @Override + public void setResponse(HttpServletResponse response, Action action) { + ResponseHeader[] responseHeaders = action.getMethod().getAnnotationsByType(ResponseHeader.class); + ResponseHeaders responseHeadersList = action.getMethod().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()); + } + } + } + +} 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..99e16b587c126eb86e8e500b9a255e4f0a81538b --- /dev/null +++ b/src/main/java/io/jboot/components/restful/RestfulInvocation.java @@ -0,0 +1,56 @@ +package io.jboot.components.restful; + +import com.jfinal.aop.Invocation; +import com.jfinal.core.Action; +import com.jfinal.core.Controller; + +public class RestfulInvocation extends Invocation { + + private Action action; + + public RestfulInvocation(Action action, Controller controller, Object[] args) { + super(controller, action.getMethod(), action.getInterceptors(), new RestfulCallback(action, controller), args); + this.action = action; + } + + + @Override + public Controller getController() { + return super.getTarget(); + } + + /** + * Return the action key. + * actionKey = controllerKey + methodName + */ + @Override + public String getActionKey() { + return action.getActionKey(); + } + + /** + * Return the controller key. + */ + @Override + public String getControllerKey() { + return action.getControllerKey(); + } + + /** + * Return view path of this controller. + */ + @Override + public String getViewPath() { + return action.getViewPath(); + } + + /** + * return true if it is action invocation. + */ + @Override + public boolean isActionInvocation() { + return action != null; + } + + +} 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..e9190698a8c46b25f20cf31637c07e484f099718 --- /dev/null +++ b/src/main/java/io/jboot/components/restful/RestfulUtils.java @@ -0,0 +1,228 @@ +package io.jboot.components.restful; + + +import com.jfinal.core.ActionException; +import com.jfinal.kit.JsonKit; +import com.jfinal.render.RenderManager; +import io.jboot.components.restful.annotation.*; +import io.jboot.utils.StrUtil; + +import javax.servlet.http.HttpServletRequest; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.math.BigDecimal; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.TimeZone; + +public class RestfulUtils { + + private static final RenderManager renderManager = RenderManager.me(); + + /** + * 从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 ActionException + */ + public static Object[] parseActionMethodParameters(String target, String actionKey, Method actionMethod, HttpServletRequest request, String rawData) + throws ActionException { + 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(), parameter); + if (args[i] == null && requestParam.required()) { + //要求参数不为空,但是却并没有提供参数 + throw genBindError("Parameter '" + parameterName + "' specifies a forced check, but the value is null"); + } + } else if (requestBody != null) { + args[i] = parseRequestBodyToParameter(rawData, parameterName, parameter.getType(), parameter); + } else if (requestHeader != null) { + if (StrUtil.isNotBlank(requestHeader.value())) { + parameterName = requestHeader.value(); + } + String value = request.getHeader(parameterName); + args[i] = parseRequestHeaderToParameter(value, parameterName, parameter.getType(), parameter); + if (args[i] == null && requestHeader.required()) { + //要求参数不为空,但是却并没有提供参数 + throw genBindError("Parameter '" + parameterName + "' specifies a forced check, but the value is null"); + } + } else if (pathVariable != null) { + if (StrUtil.isNotBlank(pathVariable.value())) { + parameterName = pathVariable.value(); + } + args[i] = parsePathVariableToParameter(target, actionKey, parameterName, parameter.getType(), parameter); + } 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, Parameter parameter) { + if (parameterTypeClass.isArray()) { + Object[] objects = new Object[value.length]; + for (int i = 0; i < value.length; i++) { + objects[i] = parseCommonValue(value[i], name, parameterTypeClass, parameter); + } + return objects; + } else { + if (value != null && value.length > 0) { + return parseCommonValue(value[0], name, parameterTypeClass, parameter); + } + } + + return null; + } + + private static Object parseRequestHeaderToParameter(String header, String name, Class parameterTypeClass, Parameter parameter) { + return parseCommonValue(header, name, parameterTypeClass, parameter); + } + + private static Object parseRequestBodyToParameter(String body, String name, Class parameterTypeClass, Parameter parameter) { + //先当作基本数据来转换 + Object value = parseCommonValue(body, name, parameterTypeClass, parameter); + if (value == null) { + value = JsonKit.parse(body, parameterTypeClass); + } + return value; + } + + private static Object parsePathVariableToParameter(String target, String actionKey, String parameterName, Class parameterTypeClass, Parameter parameter) { + Map pathVariables = parsePathVariables(target, actionKey); + String value = pathVariables.get(parameterName); + return parseCommonValue(value, parameterName, parameterTypeClass, parameter); + } + + /** + * 转换基本类型参数,目前支持string,int,double,float,boolean,long基本类型数据 + * + * @param value + * @param name + * @param parameterTypeClass + * @param parameter + * @return + */ + private static Object parseCommonValue(String value, String name, Class parameterTypeClass, Parameter parameter) { + if (StrUtil.isBlank(value)) { + return null; + } + if (parameterTypeClass.equals(String.class)) { + return value; + } else if (parameterTypeClass.equals(int.class) + || parameterTypeClass.equals(double.class) + || parameterTypeClass.equals(float.class) + || parameterTypeClass.equals(long.class) + || parameterTypeClass.equals(BigDecimal.class) + || parameterTypeClass.equals(short.class)) { + try { + if (parameterTypeClass.equals(int.class)) { + return Integer.valueOf(value); + } else if (parameterTypeClass.equals(double.class)) { + return Double.valueOf(value); + } else if (parameterTypeClass.equals(float.class)) { + return Float.valueOf(value); + } else if (parameterTypeClass.equals(long.class)) { + return Long.valueOf(value); + } else if (parameterTypeClass.equals(BigDecimal.class)) { + return new BigDecimal(value); + } else if (parameterTypeClass.equals(short.class)) { + return Short.valueOf(value); + } else { + return null; + } + } catch (NumberFormatException e) { + throw genBindError("Error resolving parameter '" + name + "', unable to match value '" + + value + "' to specified type '" + parameterTypeClass.getName() + "'"); + } + } else if (parameterTypeClass.equals(boolean.class)) { + return Boolean.valueOf(value); + } else if (parameterTypeClass.equals(Date.class)) { + DateFormat dateFormat = parameter.getAnnotation(DateFormat.class); + SimpleDateFormat simpleDateFormat; + if (dateFormat != null) { + simpleDateFormat = new SimpleDateFormat(dateFormat.value()); + simpleDateFormat.setTimeZone(TimeZone.getTimeZone(dateFormat.timeZone())); + } else { + simpleDateFormat = new SimpleDateFormat(); + } + try { + return simpleDateFormat.parse(value); + } catch (ParseException e) { + throw genBindError("Error resolving parameter '" + name + "', unable to match value '" + + value + "' to specified type '" + parameterTypeClass.getName() + "'"); + } + } else { + return null; + } + } + + private static ActionException genBindError(String message) { + return new ActionException(HttpStatus.BAD_REQUEST.value(), + renderManager.getRenderFactory().getErrorRender(HttpStatus.BAD_REQUEST.value()), message); + } + +} diff --git a/src/main/java/io/jboot/components/restful/annotation/DateFormat.java b/src/main/java/io/jboot/components/restful/annotation/DateFormat.java new file mode 100644 index 0000000000000000000000000000000000000000..b8939ecfff34a25e12be585e93135ed49c935337 --- /dev/null +++ b/src/main/java/io/jboot/components/restful/annotation/DateFormat.java @@ -0,0 +1,19 @@ +package io.jboot.components.restful.annotation; + +import java.lang.annotation.*; + +/** + * + * 日期注解,可以结合@PathVarible,@RequestParam,@RequestHeader一起使用 + * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PARAMETER) +@Documented +public @interface DateFormat { + + String value() default "yyyy-MM-dd HH:mm:ss"; + + String timeZone() default "GMT+8"; + +} 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/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..4d0c04c4ce581d4d85ebec6d52b40257539a33a8 --- /dev/null +++ b/src/main/java/io/jboot/components/restful/annotation/PathVariable.java @@ -0,0 +1,30 @@ +package io.jboot.components.restful.annotation; + +import java.lang.annotation.*; + +/** + * 路径参数注解 + * /user/{id}/cards + * 支持如下类型参数注入: + * string + * int + * double + * float + * boolean + * long + * bigDecimal + * date + * short + */ +@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..ae2d882638315234b73cb6a8c588de6c09f681cb --- /dev/null +++ b/src/main/java/io/jboot/components/restful/annotation/RequestBody.java @@ -0,0 +1,23 @@ +package io.jboot.components.restful.annotation; + +import java.lang.annotation.*; + +/** + * 请求体参数注解 + * 支持如下类型参数: + * string + * int + * double + * float + * boolean + * long + * object + * bigDecimal + * date + * short + */ +@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..9a9c361c8e7aee0adf3a94bc374881e8950df4f3 --- /dev/null +++ b/src/main/java/io/jboot/components/restful/annotation/RequestHeader.java @@ -0,0 +1,31 @@ +package io.jboot.components.restful.annotation; + +import java.lang.annotation.*; + +/** + * 请求头部信息注解 + * 支持如下类型参数注入: + * string + * int + * double + * float + * boolean + * long + * bigDecimal + * date + * short + */ +@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..906af5649de62d0167ff47dd0cedb2c6878d0fe3 --- /dev/null +++ b/src/main/java/io/jboot/components/restful/annotation/RequestParam.java @@ -0,0 +1,31 @@ +package io.jboot.components.restful.annotation; + +import java.lang.annotation.*; + +/** + * 请求参数注解 + * 支持如下类型参数: + * string / string[] + * int / int[] + * double / double[] + * float / float[] + * boolean / boolean[] + * long / long[] + * bigDecimal / bigDecimal[] + * date / date[] + * short / short[] + */ +@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/core/JbootCoreConfig.java b/src/main/java/io/jboot/core/JbootCoreConfig.java index 204d9dde207df9622055cf4a7559930275754175..e5b573b9d778bfc1c089f877b2d3001a1a139bd5 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; @@ -73,7 +76,9 @@ import java.util.Properties; public class JbootCoreConfig extends JFinalConfig { private List routeList = new ArrayList<>(); + private List restfulRoutes = new ArrayList<>(); + private JbootRestfulManager.Config restfulConfig = new JbootRestfulManager.Config(); public JbootCoreConfig() { @@ -173,6 +178,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)) { @@ -194,6 +206,19 @@ 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()); } @@ -271,7 +296,11 @@ public class JbootCoreConfig extends JFinalConfig { //若用户自己没配置 ActionHandler,默认使用 JbootActionHandler if (handlers.getActionHandler() == null) { - handlers.setActionHandler(new JbootActionHandler()); + if (!restfulRoutes.isEmpty()) { + handlers.setActionHandler(new RestfulHandler()); + } else { + handlers.setActionHandler(new JbootActionHandler()); + } } } @@ -294,6 +323,7 @@ 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/main/java/io/jboot/web/render/JbootReturnValueRender.java b/src/main/java/io/jboot/web/render/JbootReturnValueRender.java index d95d9635279663c0dc3e90a97ec0d2f3f4a978af..6061a7ff5dfe1c2bd82e6510e05b80131388335d 100644 --- a/src/main/java/io/jboot/web/render/JbootReturnValueRender.java +++ b/src/main/java/io/jboot/web/render/JbootReturnValueRender.java @@ -18,6 +18,7 @@ package io.jboot.web.render; import com.jfinal.core.Action; import com.jfinal.kit.JsonKit; import com.jfinal.render.*; +import io.jboot.components.restful.ResponseEntity; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -26,6 +27,7 @@ import java.math.BigDecimal; import java.math.BigInteger; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.Map; /** * @author Michael Yang 杨福海 (fuhai999@gmail.com) @@ -58,8 +60,14 @@ public class JbootReturnValueRender extends Render { this.render = new TextRender((String) value); } else if (this.value instanceof Date) { this.render = new TextRender(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format((Date) value)); + } else if (this.value instanceof Render) { + this.render = (Render) this.value; } else { - this.render = new JsonRender(JsonKit.toJson(value)); + if (this.value instanceof ResponseEntity) { + this.render = new JsonRender(((ResponseEntity) this.value).getData()); + } else { + this.render = new JsonRender(JsonKit.toJson(this.value)); + } } } @@ -67,17 +75,25 @@ public class JbootReturnValueRender extends Render { @Override public Render setContext(HttpServletRequest request, HttpServletResponse response) { render.setContext(request, response); + super.setContext(request, response); return this; } @Override public Render setContext(HttpServletRequest request, HttpServletResponse response, String viewPath) { render.setContext(request, response, viewPath); + super.setContext(request, response, viewPath); return this; } @Override public void render() { + if (this.value instanceof ResponseEntity) { + ResponseEntity responseEntity = (ResponseEntity) this.value; + Map headers = responseEntity.getHeaders(); + headers.forEach((k, v) -> response.setHeader(k, v)); + response.setStatus(responseEntity.getHttpStatus().value()); + } this.render.render(); } 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..ab1fc96c94f832c0fffff8fe4e6cfba175ab0dd0 --- /dev/null +++ b/src/test/java/io/jboot/test/restful/RestfulController.java @@ -0,0 +1,140 @@ +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.math.BigDecimal; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +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); + } + + @GetMapping("/get-date") + public Date getDate(){ + return new Date(); + } + + @PutMapping("/input-date") + public void inputDate(@RequestParam @DateFormat Date date){ + System.out.println("input date:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date)); + renderNull(); + } + + @PutMapping("/input-big-decimal") + public void inputBigDecimal(@RequestParam BigDecimal num){ + System.out.println("input bigDecimal:"+num.toString()); + renderNull(); + } + + +} 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(); + } +}