From 05b19f6c7ea96f16436c73acd75306876dac4159 Mon Sep 17 00:00:00 2001 From: yangyao Date: Thu, 2 Apr 2020 14:43:27 +0800 Subject: [PATCH 1/2] =?UTF-8?q?restful=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../restful/DefaultRestfulErrorRender.java | 109 +++++ .../jboot/components/restful/HttpStatus.java | 405 ++++++++++++++++++ .../restful/JbootRestfulManager.java | 237 ++++++++++ .../components/restful/ResponseEntity.java | 67 +++ .../components/restful/RestfulAction.java | 47 ++ .../components/restful/RestfulCallback.java | 29 ++ .../restful/RestfulErrorRender.java | 35 ++ .../components/restful/RestfulHandler.java | 234 ++++++++++ .../components/restful/RestfulInvocation.java | 19 + .../components/restful/RestfulUtils.java | 201 +++++++++ .../restful/annotation/DeleteMapping.java | 19 + .../restful/annotation/DownloadResponse.java | 12 + .../restful/annotation/GetMapping.java | 16 + .../restful/annotation/PathVariable.java | 27 ++ .../restful/annotation/PostMapping.java | 16 + .../restful/annotation/PutMapping.java | 19 + .../restful/annotation/RequestBody.java | 20 + .../restful/annotation/RequestHeader.java | 29 ++ .../restful/annotation/RequestParam.java | 29 ++ .../restful/annotation/ResponseHeader.java | 17 + .../restful/annotation/ResponseHeaders.java | 12 + .../restful/annotation/RestController.java | 12 + .../ParameterNullErrorException.java | 24 ++ .../ParameterParseErrorException.java | 39 ++ .../RequestMethodErrorException.java | 39 ++ .../jboot/test/restful/RestfulController.java | 117 +++++ .../io/jboot/test/restful/RestfulService.java | 5 + .../test/restful/RestfulServiceImpl.java | 12 + 28 files changed, 1847 insertions(+) create mode 100644 src/main/java/io/jboot/components/restful/DefaultRestfulErrorRender.java create mode 100644 src/main/java/io/jboot/components/restful/HttpStatus.java create mode 100644 src/main/java/io/jboot/components/restful/JbootRestfulManager.java create mode 100644 src/main/java/io/jboot/components/restful/ResponseEntity.java create mode 100644 src/main/java/io/jboot/components/restful/RestfulAction.java create mode 100644 src/main/java/io/jboot/components/restful/RestfulCallback.java create mode 100644 src/main/java/io/jboot/components/restful/RestfulErrorRender.java create mode 100644 src/main/java/io/jboot/components/restful/RestfulHandler.java create mode 100644 src/main/java/io/jboot/components/restful/RestfulInvocation.java create mode 100644 src/main/java/io/jboot/components/restful/RestfulUtils.java create mode 100644 src/main/java/io/jboot/components/restful/annotation/DeleteMapping.java create mode 100644 src/main/java/io/jboot/components/restful/annotation/DownloadResponse.java create mode 100644 src/main/java/io/jboot/components/restful/annotation/GetMapping.java create mode 100644 src/main/java/io/jboot/components/restful/annotation/PathVariable.java create mode 100644 src/main/java/io/jboot/components/restful/annotation/PostMapping.java create mode 100644 src/main/java/io/jboot/components/restful/annotation/PutMapping.java create mode 100644 src/main/java/io/jboot/components/restful/annotation/RequestBody.java create mode 100644 src/main/java/io/jboot/components/restful/annotation/RequestHeader.java create mode 100644 src/main/java/io/jboot/components/restful/annotation/RequestParam.java create mode 100644 src/main/java/io/jboot/components/restful/annotation/ResponseHeader.java create mode 100644 src/main/java/io/jboot/components/restful/annotation/ResponseHeaders.java create mode 100644 src/main/java/io/jboot/components/restful/annotation/RestController.java create mode 100644 src/main/java/io/jboot/components/restful/exception/ParameterNullErrorException.java create mode 100644 src/main/java/io/jboot/components/restful/exception/ParameterParseErrorException.java create mode 100644 src/main/java/io/jboot/components/restful/exception/RequestMethodErrorException.java create mode 100644 src/test/java/io/jboot/test/restful/RestfulController.java create mode 100644 src/test/java/io/jboot/test/restful/RestfulService.java create mode 100644 src/test/java/io/jboot/test/restful/RestfulServiceImpl.java 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 00000000..1ba7e278 --- /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 00000000..aa54f020 --- /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 00000000..ff1fb15b --- /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 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 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 00000000..90aad644 --- /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 00000000..98843e3b --- /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 00000000..0e088863 --- /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 00000000..d2154da2 --- /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 00000000..ca18c1b5 --- /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 00000000..6be030eb --- /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 00000000..cbb64f62 --- /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 00000000..2483f1c3 --- /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 00000000..c4aa83e8 --- /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 00000000..c571a745 --- /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 00000000..68a96249 --- /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 00000000..e0f9a45a --- /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 00000000..8d0c66d2 --- /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 00000000..73327a66 --- /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 00000000..5a967970 --- /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 00000000..e4a16089 --- /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 00000000..626f22bf --- /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 00000000..d51f43ae --- /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 00000000..6650c476 --- /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 00000000..081867cc --- /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 00000000..2a025ab6 --- /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 00000000..e72f83c9 --- /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/test/java/io/jboot/test/restful/RestfulController.java b/src/test/java/io/jboot/test/restful/RestfulController.java new file mode 100644 index 00000000..08a2367d --- /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 00000000..aea7b1cd --- /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 00000000..1c042af3 --- /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(); + } +} -- Gitee From 014f6703426abecb5fe58a833c83d9846d7a1b81 Mon Sep 17 00:00:00 2001 From: yangyao Date: Thu, 2 Apr 2020 14:49:39 +0800 Subject: [PATCH 2/2] =?UTF-8?q?restful=E6=94=AF=E6=8C=81=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E7=9A=84JbootCoreConfig=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/jboot/core/JbootCoreConfig.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/main/java/io/jboot/core/JbootCoreConfig.java b/src/main/java/io/jboot/core/JbootCoreConfig.java index 35e59b29..2a50dd26 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(); } -- Gitee