├── web-lighter-1.1.1.jar ├── bin ├── web-lighter-1.1.0.jar ├── web-lighter-1.1.0-javadoc.jar └── web-lighter-1.1.0-sources.jar ├── web-lighter-1.1.1-javadoc.jar ├── web-lighter-1.1.1-sources.jar ├── src └── main │ ├── java │ └── com │ │ └── bailey │ │ └── web │ │ └── lighter │ │ ├── annotation │ │ ├── ParamFormat.java │ │ ├── Inject.java │ │ ├── Download.java │ │ ├── ParamFileInfo.java │ │ ├── Param.java │ │ ├── Upload.java │ │ └── Request.java │ │ ├── vo │ │ ├── SearchCriteria.java │ │ ├── Paging.java │ │ ├── ListWithTotal.java │ │ └── Ordering.java │ │ ├── action │ │ ├── ActionLogger.java │ │ ├── ActionException.java │ │ ├── ActionSupport.java │ │ ├── ActionResult.java │ │ ├── RequestHandler.java │ │ └── ActionHelper.java │ │ ├── utils │ │ ├── GsonUtil.java │ │ ├── GsonUTCDateAdapter.java │ │ ├── ContentReader.java │ │ ├── file │ │ │ ├── UploadResult.java │ │ │ ├── DownloadFileInfo.java │ │ │ ├── DownloadUtil.java │ │ │ ├── UploadFileInfo.java │ │ │ └── UploadUtil.java │ │ ├── CharsetConverter.java │ │ ├── DateParser.java │ │ └── ClassHelper.java │ │ ├── ContainerListener.java │ │ ├── servlet │ │ └── DispatcherServlet.java │ │ └── WebLighterConfig.java │ └── resources │ └── web-lighter.xml ├── LICENSE ├── README.md └── pom.xml /web-lighter-1.1.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baileykm/web-lighter/HEAD/web-lighter-1.1.1.jar -------------------------------------------------------------------------------- /bin/web-lighter-1.1.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baileykm/web-lighter/HEAD/bin/web-lighter-1.1.0.jar -------------------------------------------------------------------------------- /web-lighter-1.1.1-javadoc.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baileykm/web-lighter/HEAD/web-lighter-1.1.1-javadoc.jar -------------------------------------------------------------------------------- /web-lighter-1.1.1-sources.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baileykm/web-lighter/HEAD/web-lighter-1.1.1-sources.jar -------------------------------------------------------------------------------- /bin/web-lighter-1.1.0-javadoc.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baileykm/web-lighter/HEAD/bin/web-lighter-1.1.0-javadoc.jar -------------------------------------------------------------------------------- /bin/web-lighter-1.1.0-sources.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baileykm/web-lighter/HEAD/bin/web-lighter-1.1.0-sources.jar -------------------------------------------------------------------------------- /src/main/java/com/bailey/web/lighter/annotation/ParamFormat.java: -------------------------------------------------------------------------------- 1 | package com.bailey.web.lighter.annotation; 2 | 3 | /** 4 | * 支持的上行参数格式 5 | */ 6 | public enum ParamFormat { 7 | json, 8 | text 9 | } 10 | -------------------------------------------------------------------------------- /src/main/resources/web-lighter.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /wl 5 | 6 | 7 | false 8 | -------------------------------------------------------------------------------- /src/main/java/com/bailey/web/lighter/vo/SearchCriteria.java: -------------------------------------------------------------------------------- 1 | package com.bailey.web.lighter.vo; 2 | 3 | import java.util.HashMap; 4 | 5 | /** 6 | * 常用信息VO - 搜索条件 7 | * 8 | * @author Bailey 9 | */ 10 | public class SearchCriteria extends HashMap { 11 | 12 | } -------------------------------------------------------------------------------- /src/main/java/com/bailey/web/lighter/action/ActionLogger.java: -------------------------------------------------------------------------------- 1 | package com.bailey.web.lighter.action; 2 | 3 | import com.bailey.web.lighter.WebLighterConfig; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | public class ActionLogger { 8 | final public static Logger logger = LoggerFactory.getLogger(WebLighterConfig.LIB_NAME + ".Action"); 9 | final public static String RC = System.getProperty("line.separator"); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/bailey/web/lighter/annotation/Inject.java: -------------------------------------------------------------------------------- 1 | package com.bailey.web.lighter.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * 标注当前形参接收需要框架自动实例化并注入的对象, 如: Service 10 | */ 11 | @Target(ElementType.PARAMETER) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | public @interface Inject { 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/bailey/web/lighter/annotation/Download.java: -------------------------------------------------------------------------------- 1 | package com.bailey.web.lighter.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * 标注于Action中的方法上, 说明该方法将处理文件下载请求 10 | */ 11 | @Target(ElementType.METHOD) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | public @interface Download { 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/bailey/web/lighter/vo/Paging.java: -------------------------------------------------------------------------------- 1 | package com.bailey.web.lighter.vo; 2 | 3 | /** 4 | * 常用信息VO - 分页信息 5 | * 6 | * @author Bailey 7 | */ 8 | public class Paging { 9 | private Integer page; 10 | private Integer pageSize; 11 | 12 | public Integer getPage() { 13 | return page; 14 | } 15 | 16 | public void setPage(Integer page) { 17 | this.page = page; 18 | } 19 | 20 | public Integer getPageSize() { 21 | return pageSize; 22 | } 23 | 24 | public void setPageSize(Integer pageSize) { 25 | this.pageSize = pageSize; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/bailey/web/lighter/annotation/ParamFileInfo.java: -------------------------------------------------------------------------------- 1 | package com.bailey.web.lighter.annotation; 2 | 3 | 4 | import com.bailey.web.lighter.utils.file.UploadFileInfo; 5 | 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | 11 | /** 12 | * 标注当前形参为可接收文件信息. 13 | *

参数类型必须为 FileInfo 或 {@code List }. 若参数类型为 FileInfo 则注入成功上传的第1个文件信息

14 | * 15 | * @see UploadFileInfo 16 | */ 17 | 18 | @Target(ElementType.PARAMETER) 19 | @Retention(RetentionPolicy.RUNTIME) 20 | 21 | public @interface ParamFileInfo { 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/bailey/web/lighter/annotation/Param.java: -------------------------------------------------------------------------------- 1 | package com.bailey.web.lighter.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * 标注当前形参可接收前端上行数据. 10 | *

上行数据中参数名应与本注解的 name 一致, 默认值为 data

11 | *

上行数据可是 JSON 格式或 com.google.gson.JsonPrimitive ( Integer, Long, Short, Float, Double, Byte, Boolean, Character )

12 | * @see com.google.gson.JsonPrimitive 13 | */ 14 | @Target(ElementType.PARAMETER) 15 | @Retention(RetentionPolicy.RUNTIME) 16 | public @interface Param { 17 | /** 18 | * 参数名 19 | * @return 参数名 20 | */ 21 | String name() default "data"; 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/bailey/web/lighter/utils/GsonUtil.java: -------------------------------------------------------------------------------- 1 | package com.bailey.web.lighter.utils; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | 6 | import java.sql.Timestamp; 7 | import java.util.Date; 8 | 9 | /** 10 | * Gson 管理工具类 11 | */ 12 | public class GsonUtil { 13 | public static Gson getGsonInstance() { 14 | GsonBuilder gsonBuilder = new GsonBuilder(); 15 | GsonUTCDateAdapter gsonUTCDateAdapter = new GsonUTCDateAdapter(); 16 | gsonBuilder.registerTypeAdapter(Date.class, gsonUTCDateAdapter); 17 | gsonBuilder.registerTypeAdapter(java.sql.Date.class, gsonUTCDateAdapter); 18 | gsonBuilder.registerTypeAdapter(Timestamp.class, gsonUTCDateAdapter); 19 | return gsonBuilder.create(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/bailey/web/lighter/vo/ListWithTotal.java: -------------------------------------------------------------------------------- 1 | package com.bailey.web.lighter.vo; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * @author Bailey 7 | */ 8 | public class ListWithTotal { 9 | private List list; 10 | private Long total; 11 | 12 | public ListWithTotal() { 13 | } 14 | 15 | public ListWithTotal(List list, Long total) { 16 | this.list = list; 17 | this.total = total; 18 | } 19 | 20 | public List getList() { 21 | return list; 22 | } 23 | 24 | public void setList(List list) { 25 | this.list = list; 26 | } 27 | 28 | public Long getTotal() { 29 | return total; 30 | } 31 | 32 | public void setTotal(Long total) { 33 | this.total = total; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/bailey/web/lighter/action/ActionException.java: -------------------------------------------------------------------------------- 1 | package com.bailey.web.lighter.action; 2 | 3 | /** 4 | * Action异常 5 | * 6 | * @author Bailey 7 | */ 8 | public class ActionException extends Exception { 9 | private Throwable rootCause; 10 | 11 | public ActionException() { 12 | super(); 13 | } 14 | 15 | public ActionException(String message) { 16 | super(message); 17 | } 18 | 19 | public ActionException(String message, Throwable rootCause) { 20 | super(message, rootCause); 21 | this.rootCause = rootCause; 22 | } 23 | 24 | public ActionException(Throwable rootCause) { 25 | super(rootCause); 26 | this.rootCause = rootCause; 27 | } 28 | 29 | public Throwable getRootCause() { 30 | return rootCause; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/bailey/web/lighter/utils/GsonUTCDateAdapter.java: -------------------------------------------------------------------------------- 1 | package com.bailey.web.lighter.utils; 2 | 3 | import com.google.gson.*; 4 | 5 | import java.lang.reflect.Type; 6 | import java.text.ParseException; 7 | import java.util.Date; 8 | 9 | /** 10 | * Gson 日期型数据适配器 11 | * 12 | * @author Bailey 13 | */ 14 | public class GsonUTCDateAdapter implements JsonSerializer, JsonDeserializer { 15 | 16 | @Override 17 | public Date deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { 18 | try { 19 | return DateParser.UTC_FORMAT.parse(jsonElement.getAsString()); 20 | } catch (ParseException e) { 21 | throw new JsonParseException(e); 22 | } 23 | } 24 | 25 | @Override 26 | public JsonElement serialize(Date date, Type type, JsonSerializationContext jsonSerializationContext) { 27 | return new JsonPrimitive(DateParser.UTC_FORMAT.format(date)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/bailey/web/lighter/vo/Ordering.java: -------------------------------------------------------------------------------- 1 | package com.bailey.web.lighter.vo; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * 常用信息VO - 排序规则 7 | * 8 | * @author Bailey 9 | */ 10 | 11 | public class Ordering { 12 | private List orders; 13 | 14 | public void setOrders(List orders) { 15 | this.orders = orders; 16 | } 17 | 18 | public List getOrders() { 19 | return orders; 20 | } 21 | 22 | public enum SortOrder { 23 | ASC, DESC 24 | } 25 | 26 | public static class Order { 27 | private String field; 28 | private SortOrder order; 29 | 30 | public String getField() { 31 | return field; 32 | } 33 | 34 | public void setField(String field) { 35 | this.field = field; 36 | } 37 | 38 | public SortOrder getOrder() { 39 | return order; 40 | } 41 | 42 | public void setOrder(SortOrder order) { 43 | this.order = order; 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /src/main/java/com/bailey/web/lighter/annotation/Upload.java: -------------------------------------------------------------------------------- 1 | package com.bailey.web.lighter.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * 标注于Action中的方法上, 说明该方法将处理文件上传请求 10 | */ 11 | @Target(ElementType.METHOD) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | public @interface Upload { 14 | /** 15 | * 文件保存目录, 默认根目录下的upload子目录 16 | * @return 文件保存目录 17 | */ 18 | String uploadDir() default "upload"; 19 | 20 | /** 21 | * 服务器端文件命名规则 22 | *

规则中的星号("*")表示此部分使用UUID替换, 例如: "tmp*" 表示使用 "tmp" + 32位UUID 作为文件名

23 | *

文件扩展名始终与原文件一致

24 | * @return 服务器端文件命名规则 25 | */ 26 | String nameRule() default "*"; 27 | 28 | /** 29 | * 单个文件的最大字节数 30 | * @return 单个文件的最大字节数 31 | */ 32 | int maxFileSize() default 1024 * 1024 * 40; 33 | 34 | /** 35 | * 请求的最大字节数 36 | * @return 请求的最大字节数 37 | */ 38 | int maxRequestSize() default 1024 * 1024 * 50; 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 baileykm 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main/java/com/bailey/web/lighter/action/ActionSupport.java: -------------------------------------------------------------------------------- 1 | package com.bailey.web.lighter.action; 2 | 3 | import javax.servlet.http.HttpServletRequest; 4 | import javax.servlet.http.HttpServletResponse; 5 | import javax.servlet.http.HttpSession; 6 | 7 | /** 8 | * Action 的公共父类, 所示Action应继承此类 9 | * 10 | * @author Bailey 11 | */ 12 | public abstract class ActionSupport { 13 | 14 | private HttpServletRequest request; 15 | private HttpServletResponse response; 16 | 17 | void setRequest(HttpServletRequest request) { 18 | this.request = request; 19 | } 20 | 21 | void setResponse(HttpServletResponse response) { 22 | this.response = response; 23 | } 24 | 25 | /** 26 | * 获得Request对象 27 | * 28 | * @return Request 29 | */ 30 | protected HttpServletRequest getRequest() { 31 | return request; 32 | } 33 | 34 | /** 35 | * 获得Response对象 36 | * 37 | * @return Response 38 | */ 39 | protected HttpServletResponse getResponse() { 40 | return response; 41 | } 42 | 43 | 44 | /** 45 | * 获得Session对象 46 | * 47 | * @return Session 48 | */ 49 | protected HttpSession getSession() { 50 | return request.getSession(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/bailey/web/lighter/ContainerListener.java: -------------------------------------------------------------------------------- 1 | package com.bailey.web.lighter; 2 | 3 | import com.bailey.web.lighter.action.ActionHelper; 4 | import com.bailey.web.lighter.servlet.DispatcherServlet; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import javax.servlet.ServletContext; 8 | import javax.servlet.ServletContextEvent; 9 | import javax.servlet.ServletContextListener; 10 | import javax.servlet.annotation.WebListener; 11 | 12 | /** 13 | * 监听Web容器初始化事件, 进行初始化 14 | * 15 | * @author Bailey 16 | */ 17 | @WebListener 18 | public class ContainerListener implements ServletContextListener { 19 | 20 | @Override 21 | public void contextInitialized(ServletContextEvent sce) { 22 | // 加载配置 23 | WebLighterConfig.loadConfiguration(); 24 | 25 | // 初始化 ActionHelper 26 | try { 27 | ActionHelper.initRequestHandlers(); 28 | } catch (Exception e) { 29 | LoggerFactory.getLogger(WebLighterConfig.LIB_NAME).error("Web-lighter initialization exception!", e); 30 | } 31 | 32 | ServletContext context = sce.getServletContext(); 33 | context.addServlet("wlqDispatcher", DispatcherServlet.class).addMapping(WebLighterConfig.getUrlPrefix() + "/*"); 34 | } 35 | 36 | @Override 37 | public void contextDestroyed(ServletContextEvent sce) { 38 | } 39 | } -------------------------------------------------------------------------------- /src/main/java/com/bailey/web/lighter/annotation/Request.java: -------------------------------------------------------------------------------- 1 | package com.bailey.web.lighter.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * 标注于Action中的方法上, 说明该方法为处理前端请求的方法. 10 | *

参数说明:

11 | *
12 |  *     url          - 可处理的请求的url, 支持通配符和参数, 如: /{param1}/*.action/{param2}
13 |  *                    -- 注意 --
14 |  *                    - web-lighter 使用路径匹配方式拦截前端请求, 默认情况下, 若请求的 url 匹配模式 "/wl/*" 时将被 web-lighter 拦截并处理. 若需要更改拦截匹配模式, 请在配置文件中进行设置.
15 |  *                    - 前端访问路径记得添加路径前缀 ( 默认为"/wl" ), 如: http://localhost:8080/wl/doSomething.action
16 |  *                    - 本注解的 url 参数无需添加路径前缀, 如: /doSomething, 运行时 web-lighter 将会匹配 /wl/doSomething
17 |  *                    - 虽然 url 中支持类似 RESTful Web 风格的参数, 但 web-lighter 暂未完全支持 RESTful Web 的标准方法
18 |  *
19 |  *     format       - 上行数据的格式, 默认 ParamFormat.json, Content-Type = "application/json" 时此参数无效 ( 始终被理解为JSON 格式数据)
20 |  * 
21 | * 22 | * @see ParamFormat 23 | */ 24 | @Target(ElementType.METHOD) 25 | @Retention(RetentionPolicy.RUNTIME) 26 | public @interface Request { 27 | /** 28 | * 可处理的请求的url. 详见 {@link Request} 29 | * @return url 30 | */ 31 | String url(); 32 | 33 | /** 34 | * 上行参数类型. 详见 {@link Request} 35 | * @return 参数类型 36 | */ 37 | ParamFormat format() default ParamFormat.json; 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/bailey/web/lighter/utils/ContentReader.java: -------------------------------------------------------------------------------- 1 | package com.bailey.web.lighter.utils; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | 7 | /** 8 | * 读取数据的工具类 9 | * 10 | * @author Bailey 11 | */ 12 | public class ContentReader { 13 | 14 | final private static int BUFFER_SIZE = 1024 * 8; 15 | 16 | /** 17 | * 从 InputStream 从读取字节流数据 18 | * 19 | * @param inputStream 输入流 20 | * @return 读取到的字节流数据 21 | * @throws IOException IOException 22 | */ 23 | public static ByteArrayOutputStream readFromInputStream(InputStream inputStream) throws IOException { 24 | ByteArrayOutputStream result = new ByteArrayOutputStream(); 25 | byte[] buffer = new byte[BUFFER_SIZE]; 26 | int length; 27 | while ((length = inputStream.read(buffer)) != -1) { 28 | result.write(buffer, 0, length); 29 | } 30 | return result; 31 | } 32 | 33 | /** 34 | * 从 InputStream 从读取字节流数据 35 | * 36 | * @param inputStream 输入流 37 | * @return 读取到的字节流数据 38 | * @throws IOException IOException 39 | */ 40 | public static byte[] readBytes(InputStream inputStream) throws IOException { 41 | return readFromInputStream(inputStream).toByteArray(); 42 | } 43 | 44 | /** 45 | * 从 InputStream 从读取数据形成字符串 46 | * 47 | * @param inputStream 输入流 48 | * @param charsetName 字符集名称 49 | * @return 读取到的字符串数据 50 | * @throws IOException IOException 51 | */ 52 | public static String readString(InputStream inputStream, String charsetName) throws IOException { 53 | return readFromInputStream(inputStream).toString(charsetName); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/bailey/web/lighter/utils/file/UploadResult.java: -------------------------------------------------------------------------------- 1 | package com.bailey.web.lighter.utils.file; 2 | 3 | import java.util.*; 4 | 5 | /** 6 | * 上传结果信息. 包含成功上传的文件信息集合 和 额外的参数集合 7 | * @author Bailey 8 | */ 9 | public class UploadResult { 10 | 11 | private List files = new ArrayList<>(); 12 | 13 | private Map> parameters = new LinkedHashMap<>(); 14 | 15 | public void addFile(String fieldName, String origFileName, long fileSize, String fileType, String serverFileName, String serverFileRelativePath, String serverFileAbsolutePath) { 16 | UploadFileInfo file = new UploadFileInfo(fieldName, origFileName, fileSize, fileType, serverFileName, serverFileRelativePath, serverFileAbsolutePath); 17 | files.add(file); 18 | } 19 | 20 | public void addParameter(String name, String value) { 21 | List valueList = parameters.get(name); 22 | if (valueList == null) { 23 | valueList = new ArrayList<>(); 24 | parameters.put(name, valueList); 25 | } 26 | valueList.add(value); 27 | } 28 | 29 | /** 30 | * 获得成功上传的文件信息 31 | * @return 成功上传的文件信息 32 | */ 33 | public List getFiles() { 34 | return files; 35 | } 36 | 37 | /** 38 | * 获得上传文件时同时携带的参数 39 | * @return 上行参数 40 | */ 41 | public Map getParameters() { 42 | // 把参数转成 Map, 以便和普通的表单提交数据格式统一 (Content-Type : application/x-www-form-urlencoded) 43 | Map rt = new LinkedHashMap<>(); 44 | for (Iterator itr = parameters.keySet().iterator(); itr.hasNext();) { 45 | String name = itr.next(); 46 | List value = parameters.get(name); 47 | rt.put(name, value.toArray(new String[value.size()])); 48 | } 49 | return rt; 50 | } 51 | } -------------------------------------------------------------------------------- /src/main/java/com/bailey/web/lighter/servlet/DispatcherServlet.java: -------------------------------------------------------------------------------- 1 | package com.bailey.web.lighter.servlet; 2 | 3 | import com.bailey.web.lighter.WebLighterConfig; 4 | import com.bailey.web.lighter.action.*; 5 | 6 | import javax.servlet.http.HttpServlet; 7 | import javax.servlet.http.HttpServletRequest; 8 | import javax.servlet.http.HttpServletResponse; 9 | import java.io.IOException; 10 | 11 | /** 12 | * 负责分发请求的主Servlet 13 | * 14 | * @author Bailey 15 | * @see ActionResult 16 | */ 17 | public class DispatcherServlet extends HttpServlet { 18 | @Override 19 | protected void service(HttpServletRequest req, HttpServletResponse resp) { 20 | RequestHandler handler = null; 21 | String requestUrl = null; 22 | String uri = null; 23 | try { 24 | req.setCharacterEncoding("UTF-8"); 25 | resp.setCharacterEncoding("UTF-8"); 26 | resp.setContentType("text/html;charset=UTF-8"); 27 | 28 | // 获得处理当前请求的 RequestHandler 29 | uri = req.getRequestURI(); 30 | requestUrl = uri.substring((req.getContextPath() + WebLighterConfig.getUrlPrefix()).length()); 31 | handler = ActionHelper.getRequestHandler(requestUrl); 32 | if (handler == null) { 33 | resp.sendError(404, "No Action mapped for " + uri); 34 | return; 35 | } 36 | } catch (IOException e) { 37 | ActionLogger.logger.error("Dispatch request error.", e); 38 | throw new RuntimeException(e); 39 | } 40 | 41 | try { 42 | // 执行 HttpServletRequest 处理 43 | new ActionHelper().execute(handler, requestUrl, req, resp); 44 | } catch (ActionException | IOException e) { 45 | ActionLogger.logger.error("Invoke action method error: " + handler.toString(), e); 46 | throw new RuntimeException(e); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/bailey/web/lighter/utils/CharsetConverter.java: -------------------------------------------------------------------------------- 1 | package com.bailey.web.lighter.utils; 2 | 3 | import java.io.UnsupportedEncodingException; 4 | 5 | /** 6 | * 字符集转换工具 7 | * 8 | * @author Bailey 9 | */ 10 | public class CharsetConverter { 11 | /** 12 | * 字符集ISO8859-1 13 | */ 14 | public final static String CHARSET_ISO8859_1 = "ISO8859-1"; 15 | 16 | /** 17 | * 字符集UTF-8 18 | */ 19 | public final static String CHARSET_UTF_8 = "UTF-8"; 20 | 21 | /** 22 | * 字符集GBK 23 | */ 24 | public final static String CHARSET_GBK = "GBK"; 25 | /** 26 | * 字符集GB2312 27 | */ 28 | public final static String CHARSET_GB2312 = "GB2312"; 29 | 30 | 31 | private String from; 32 | private String to; 33 | 34 | /** 35 | * 构建一个字符集转换器实例 36 | * 37 | * @param from 源字符集 38 | * @param to 目标字符集 39 | */ 40 | public CharsetConverter(String from, String to) { 41 | this.from = from; 42 | this.to = to; 43 | } 44 | 45 | /** 46 | * 将字符串从from字符集转成to字符集 47 | * 48 | * @param str 待转换的字符串 49 | * @return 转成目标字符集编码的字符串 50 | * @throws UnsupportedEncodingException 不支持的字符集编码 51 | */ 52 | public String convert(String str) throws UnsupportedEncodingException { 53 | if (str == null) return null; 54 | if (from == null || to == null) return str; 55 | return new String(str.getBytes(from), to); 56 | } 57 | 58 | /** 59 | * 将字符串 str 由 Latin1 (ISO8859-1) 编码转为 UTF-8 60 | *

多数浏览器默认编码为 Latin1 (ISO8859-1), 若浏览器端提交的数据显示为乱码, 常可使用此方法处理

61 | * 62 | * @param str 源字符串 63 | * @return 使用 UTF-8 编码的字符串 64 | * @throws UnsupportedEncodingException 不支持的字符集 65 | */ 66 | public static String latin1ToUTF8(String str) throws UnsupportedEncodingException { 67 | return new String(str.getBytes(CHARSET_ISO8859_1), CHARSET_UTF_8); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/bailey/web/lighter/WebLighterConfig.java: -------------------------------------------------------------------------------- 1 | package com.bailey.web.lighter; 2 | 3 | import javax.xml.bind.JAXBContext; 4 | import javax.xml.bind.Unmarshaller; 5 | import javax.xml.bind.annotation.XmlElement; 6 | import javax.xml.bind.annotation.XmlRootElement; 7 | import java.io.File; 8 | 9 | /** 10 | * web-lighter 的总体配置 11 | * 12 | * @author Bailey 13 | */ 14 | public class WebLighterConfig { 15 | 16 | // 本工具包的名称 17 | public static final String LIB_NAME = "com.bailey.web.lighter"; 18 | 19 | // 配置文件名 20 | private static final String CONFIG_FILE_NAME = "web-lighter.xml"; 21 | 22 | // 配置信息 23 | private static Configuration config = new Configuration(); 24 | 25 | /** 26 | * 配置信息 27 | */ 28 | @XmlRootElement(name = "configuration") 29 | private static class Configuration { 30 | // HTTP请求URL前缀 31 | @XmlElement(name = "urlPrefix") 32 | public String urlPrefix = "/wl"; 33 | } 34 | 35 | /** 36 | * HTTP 请求 URL 前缀 37 | * 38 | * @return URL 前缀 39 | */ 40 | public static String getUrlPrefix() { 41 | return config.urlPrefix; 42 | } 43 | 44 | /** 45 | * 加载配置信息 46 | */ 47 | public static void loadConfiguration() { 48 | File configFile = null; 49 | try { 50 | JAXBContext context = JAXBContext.newInstance(Configuration.class); 51 | Unmarshaller unmarshaller = context.createUnmarshaller(); 52 | String rootPath = Thread.currentThread().getContextClassLoader().getResources("/").nextElement().getPath(); 53 | 54 | configFile = new File(rootPath, CONFIG_FILE_NAME); 55 | 56 | // 找不到配置文件, 直接返回(取默认值) 57 | if (!configFile.exists()) return; 58 | 59 | config = (Configuration) unmarshaller.unmarshal(configFile); 60 | } catch (Exception e) { 61 | System.err.println("[ INFO ] 读取配置文件失败 " + ((configFile == null) ? "" : "[" + configFile.getAbsolutePath()) + "]" + ", 使用默认值"); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/bailey/web/lighter/utils/file/DownloadFileInfo.java: -------------------------------------------------------------------------------- 1 | package com.bailey.web.lighter.utils.file; 2 | 3 | import java.io.*; 4 | import java.nio.file.Files; 5 | import java.nio.file.Path; 6 | import java.nio.file.Paths; 7 | 8 | /** 9 | * 下载文件的信息 10 | * 11 | * @author Bailey 12 | */ 13 | public class DownloadFileInfo { 14 | // 文件数据输入流 15 | private InputStream inputStream; 16 | // 客户端默认保存的文件名 17 | private String clientFileName; 18 | // 文件类别 19 | private String contentType; 20 | 21 | /** 22 | * 从inputStream中读取数据, 供前端下载 23 | * @param inputStream 前端保存时使用的默认文件名 24 | * @param clientFileName 文件名 25 | * @param contentType 文件的contentType, 供前端浏览器识别 26 | */ 27 | public DownloadFileInfo(InputStream inputStream, String clientFileName, String contentType) { 28 | this.inputStream = inputStream; 29 | this.clientFileName = clientFileName; 30 | this.contentType = contentType; 31 | } 32 | 33 | /** 34 | * 从file中读取数据, 供前端下载 35 | * @param file 文件 36 | * @param clientFileName 前端保存时使用的默认文件名 37 | * @throws FileNotFoundException 文件未找到 38 | */ 39 | public DownloadFileInfo(File file, String clientFileName) throws FileNotFoundException { 40 | this(new FileInputStream(file), clientFileName, getContentType(file)); 41 | } 42 | 43 | /** 44 | * 获得文件的 ContentType 45 | * 46 | * @param file 待下载的文件 47 | * @return 文件的ContentType 48 | */ 49 | public static String getContentType(File file) { 50 | String type = null; 51 | try { 52 | Path path = Paths.get(file.getAbsolutePath()); 53 | type = Files.probeContentType(path); 54 | } catch (IOException e) { 55 | } 56 | return type; 57 | } 58 | 59 | public InputStream getInputStream() { 60 | return inputStream; 61 | } 62 | 63 | public String getClientFileName() { 64 | return clientFileName; 65 | } 66 | 67 | public String getContentType() { 68 | return contentType; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/bailey/web/lighter/utils/file/DownloadUtil.java: -------------------------------------------------------------------------------- 1 | package com.bailey.web.lighter.utils.file; 2 | 3 | import nl.bitwalker.useragentutils.Browser; 4 | import nl.bitwalker.useragentutils.UserAgent; 5 | 6 | import javax.servlet.ServletOutputStream; 7 | import javax.servlet.http.HttpServletRequest; 8 | import javax.servlet.http.HttpServletResponse; 9 | import java.io.InputStream; 10 | import java.io.UnsupportedEncodingException; 11 | import java.net.URLEncoder; 12 | 13 | /** 14 | * 文件下载工具类 15 | * 16 | * @author Bailey 17 | */ 18 | public class DownloadUtil { 19 | 20 | private final static int BUFFER_SIZE = 1024 * 1024; 21 | 22 | /** 23 | * 对文件名进行编码. IE 和 其他浏览器使用的默认字符集编码不一致, 为避免前端文件名显示乱码, 须区别处理 24 | * @param req Request对象 25 | * @param fileName 文件名 26 | * @return 经过编码的文件名 27 | * @throws UnsupportedEncodingException 不支持的字符集 28 | */ 29 | private String encodeFileName(HttpServletRequest req, String fileName) throws UnsupportedEncodingException { 30 | UserAgent userAgent = UserAgent.parseUserAgentString(req.getHeader("User-Agent")); 31 | Browser browser = userAgent.getBrowser(); 32 | 33 | if (browser.getName().contains("IE")) { 34 | fileName = URLEncoder.encode(fileName, "UTF-8"); 35 | } else { 36 | fileName = new String(fileName.getBytes(), "ISO-8859-1"); 37 | } 38 | return fileName; 39 | } 40 | 41 | /** 42 | * 开始下载, 将数据写入输出流 43 | * 44 | * @param req Request对象 45 | * @param resp Response对象 46 | * @param fileInfo 文件信息 47 | */ 48 | public void download(HttpServletRequest req, HttpServletResponse resp, DownloadFileInfo fileInfo) { 49 | try { 50 | String fileName = encodeFileName(req, fileInfo.getClientFileName()); 51 | 52 | InputStream fis = fileInfo.getInputStream(); 53 | int length = fis.available(); 54 | ServletOutputStream sos = resp.getOutputStream(); 55 | 56 | resp.setContentLength(length); 57 | resp.setContentType(fileInfo.getContentType()); 58 | resp.setCharacterEncoding("UTF-8"); 59 | resp.addHeader("Content-Disposition", "attachment; filename=" + fileName); 60 | 61 | byte[] buffer = new byte[BUFFER_SIZE]; 62 | int readBytes = -1; 63 | while ((readBytes = fis.read(buffer, 0, BUFFER_SIZE)) != -1) { 64 | sos.write(buffer, 0, readBytes); 65 | } 66 | sos.close(); 67 | fis.close(); 68 | } catch (Exception e) { 69 | throw new RuntimeException(e); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/bailey/web/lighter/utils/file/UploadFileInfo.java: -------------------------------------------------------------------------------- 1 | package com.bailey.web.lighter.utils.file; 2 | 3 | import java.io.File; 4 | 5 | /** 6 | * 上传的文件信息 7 | * 8 | * @author Bailey 9 | */ 10 | public class UploadFileInfo { 11 | // 表单中该文件上传字段的名称 12 | private String fieldName; 13 | // 原文件名 14 | private String origFileName; 15 | // 文件大小 16 | private long fileSize; 17 | // 文件类型 18 | private String fileType; 19 | // 服务器端保存的文件名 20 | private String serverFileName; 21 | // 服务器端保存的文件相对路径(含文件名) 22 | private String serverFileRelativePath; 23 | // 服务器端保存的文件物理路径(含文件名) 24 | private String serverFileAbsolutePath; 25 | 26 | /** 27 | * @param fieldName 字段名 28 | * @param origFileName 原文件名 29 | * @param fileSize 文件字节数 30 | * @param fileType 文件MIME类型 31 | * @param serverFileName 服务器端文件名 32 | * @param serverFileRelativePath 服务器端保存的文件相对路径(含文件名) 33 | * @param serverFileAbsolutePath 服务器端保存的文件物理路径(含文件名) 34 | */ 35 | public UploadFileInfo(String fieldName, String origFileName, long fileSize, String fileType, String serverFileName, String serverFileRelativePath, String serverFileAbsolutePath) { 36 | this.fieldName = fieldName; 37 | this.origFileName = origFileName; 38 | this.fileSize = fileSize; 39 | this.fileType = fileType; 40 | this.serverFileName = serverFileName; 41 | this.serverFileRelativePath = serverFileRelativePath; 42 | this.serverFileAbsolutePath =serverFileAbsolutePath; 43 | } 44 | 45 | /** 46 | * 获得表单中该文件上传字段的名称 47 | * 48 | * @return 字段名 49 | */ 50 | public String getFieldName() { 51 | return fieldName; 52 | } 53 | 54 | /** 55 | * 获得原文件名 56 | * 57 | * @return 原文件名 58 | */ 59 | public String getOrigFileName() { 60 | return origFileName; 61 | } 62 | 63 | /** 64 | * 获得文件字节数 65 | * 66 | * @return 字节数 67 | */ 68 | public long getFileSize() { 69 | return fileSize; 70 | } 71 | 72 | /** 73 | * 获得文件MIME 类型 74 | * 75 | * @return 文件类型 76 | */ 77 | public String getFileType() { 78 | return fileType; 79 | } 80 | 81 | /** 82 | * 获得服务器端存储时使用的文件名 83 | * 84 | * @return 服务器端文件名 85 | */ 86 | public String getServerFileName() { 87 | return serverFileName; 88 | } 89 | 90 | 91 | /** 92 | * 获得服务器端保存的文件相对路径(含文件名) 93 | * 94 | * @return 服务器端保存的文件相对路径(含文件名) 95 | */ 96 | public String getServerFileRelativePath() { 97 | return serverFileRelativePath; 98 | } 99 | /** 100 | * 获得服务器端保存的文件物理路径(含文件名) 101 | * 102 | * @return 服务器端保存的文件物理路径(含文件名) 103 | */ 104 | public String getServerFileAbsolutePath() { 105 | return serverFileAbsolutePath; 106 | } 107 | 108 | @Override 109 | public String toString() { 110 | return "FileInfo{" + "origFileName='" + origFileName + '\'' + ", fileSize=" + fileSize + ", fileType='" + fileType + '\'' + ", serverFileRelativePath='" + serverFileRelativePath + '\'' + '}'; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/main/java/com/bailey/web/lighter/action/ActionResult.java: -------------------------------------------------------------------------------- 1 | package com.bailey.web.lighter.action; 2 | 3 | /** 4 | * 用于封装 Action 处理结果 5 | *

封装的数据包括:

6 | *
  7 |  * code     - 状态码, 正数或 0 表示成功, 默认为0; 负数表示出错
  8 |  * result     - 回传的业务数据
  9 |  * message  - 附加消息, 通常用于保存提示信息, 如: 出错原因
 10 |  * total    - 全部记录数. 通常用于分页查询时返回符合条件的总记录数
 11 |  * 
12 | * 13 | * @author Bailey 14 | */ 15 | public class ActionResult { 16 | final public static int CODE_SUCCESS = 0; 17 | final public static int CODE_FAILURE_DEFAULT = -1; 18 | 19 | private int code; 20 | private Object result; 21 | private String message; 22 | private Long total; 23 | 24 | 25 | public ActionResult(int code, Object result, String message, Long total) { 26 | this.code = code; 27 | this.result = result; 28 | this.message = message; 29 | this.total = total; 30 | } 31 | 32 | public static ActionResult success(int code, Object data, String message, Long total) { 33 | if (code < 0) { 34 | ActionLogger.logger.warn("The code of SUCCESS is: " + code + ". Are you sure?"); 35 | } 36 | return new ActionResult(code, data, message, total); 37 | } 38 | 39 | public static ActionResult success(Object data, String message, Long total) { 40 | return success(CODE_SUCCESS, data, message, total); 41 | } 42 | 43 | public static ActionResult success(Object data, Long total) { 44 | return success(CODE_SUCCESS, data, null, total); 45 | } 46 | 47 | public static ActionResult success(Object data, String message) { 48 | return success(data, message, null); 49 | } 50 | 51 | public static ActionResult success(Object data) { 52 | return success(data, null, null); 53 | } 54 | 55 | public static ActionResult success() { 56 | return success(null); 57 | } 58 | 59 | 60 | public static ActionResult failure(int code, Object data, String message) { 61 | if (!(code < 0)){ 62 | ActionLogger.logger.warn("The code of FAILURE is: " + code + ". Are you sure?"); 63 | } 64 | return new ActionResult(code, data, message, null); 65 | } 66 | 67 | public static ActionResult failure(Object data, String message) { 68 | return failure(CODE_FAILURE_DEFAULT, data, message); 69 | } 70 | 71 | public static ActionResult failure(String message) { 72 | return failure(null, message); 73 | } 74 | 75 | public static ActionResult failure() { 76 | return failure(null, null); 77 | } 78 | 79 | 80 | public int getCode() { 81 | return code; 82 | } 83 | 84 | public void setCode(int code) { 85 | this.code = code; 86 | } 87 | 88 | public Object getResult() { 89 | return result; 90 | } 91 | 92 | public void setResult(Object data) { 93 | this.result = result; 94 | } 95 | 96 | public String getMessage() { 97 | return message; 98 | } 99 | 100 | public void setMessage(String message) { 101 | this.message = message; 102 | } 103 | 104 | public Long getTotal() { 105 | return total; 106 | } 107 | 108 | public void setTotal(Long total) { 109 | this.total = total; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/com/bailey/web/lighter/utils/DateParser.java: -------------------------------------------------------------------------------- 1 | package com.bailey.web.lighter.utils; 2 | 3 | import com.bailey.web.lighter.WebLighterConfig; 4 | import com.bailey.web.lighter.vo.SearchCriteria; 5 | import org.apache.commons.lang3.StringUtils; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.text.DateFormat; 10 | import java.text.ParseException; 11 | import java.text.SimpleDateFormat; 12 | import java.util.Date; 13 | import java.util.Locale; 14 | import java.util.TimeZone; 15 | 16 | /** 17 | * 日期型数据解析工具类 18 | */ 19 | public class DateParser { 20 | 21 | private static final Logger logger = LoggerFactory.getLogger(WebLighterConfig.LIB_NAME + ".utils.DateParser"); 22 | 23 | public static final String UTC_FORMAT_STRING = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; 24 | 25 | public static final DateFormat UTC_FORMAT; 26 | public static final DateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy/MM/dd", Locale.getDefault()); 27 | public static final DateFormat SIMPLE_DATE_TIME_FORMAT = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.getDefault()); 28 | 29 | 30 | static { 31 | UTC_FORMAT = new SimpleDateFormat(UTC_FORMAT_STRING, Locale.getDefault()); 32 | UTC_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC")); 33 | } 34 | 35 | /** 36 | * 将字符串表示的 ISO 日期时间转为 Date 类型 37 | * 38 | * @param dateStr 时间字符串 39 | */ 40 | public static Date parseDate(String dateStr) { 41 | if (dateStr == null) return null; 42 | try { 43 | return UTC_FORMAT.parse(dateStr); 44 | } catch (ParseException e) { 45 | logger.warn("解析时间日期出错", e); 46 | return null; 47 | } 48 | } 49 | 50 | /** 51 | * 将字符串表示的 ISO 日期时间转为 Date 类型 52 | * 53 | * @param dateStrArr 时间字符串数组 54 | */ 55 | public static Date[] parseDate(String... dateStrArr) { 56 | if (dateStrArr == null) return null; 57 | Date[] dates = new Date[dateStrArr.length]; 58 | for (int i = 0; i < dateStrArr.length; i++) { 59 | dates[i] = parseDate(dateStrArr[i]); 60 | } 61 | return dates; 62 | } 63 | 64 | 65 | /** 66 | * 将字符串时段转为日期数组 67 | * 68 | * @param dateRange 形如"2018/11/17 - 2018/12/31" 69 | */ 70 | public static Date[] parseDateRange(String dateRange) throws ParseException { 71 | if (StringUtils.isBlank(dateRange)) return null; 72 | String[] dateStrArr = dateRange.split("-"); 73 | if (dateStrArr.length < 2) throw new ParseException("日期范围格式错误, 应为 yyyy/MM/dd - yyyy/MM/dd", 0); 74 | return new Date[]{dateStrArr[0] == null ? null : SIMPLE_DATE_FORMAT.parse(dateStrArr[0]), dateStrArr[1] == null ? null : SIMPLE_DATE_FORMAT.parse(dateStrArr[1])}; 75 | } 76 | 77 | /** 78 | * 将检索条件中的 ISO 日期时间转为 Date 类型数组. 79 | *

80 | * 默认字段名分别为 dateMin, dateMax 81 | * 82 | * @param criteria 时间字符串数组 83 | */ 84 | public static Date[] parseDate(SearchCriteria criteria) { 85 | return parseDate(criteria, "dateMin", "dateMax"); 86 | } 87 | 88 | /** 89 | * 将检索条件中的 ISO 日期时间转为 Date 类型数组 90 | * 91 | * @param criteria 时间字符串数组 92 | * @param key 字段名 93 | */ 94 | public static Date[] parseDate(SearchCriteria criteria, String... key) { 95 | String[] dateStrArr = new String[key.length]; 96 | for (int i = 0; i < key.length; i++) { 97 | dateStrArr[i] = criteria.get(key[i]); 98 | } 99 | return parseDate(dateStrArr); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/com/bailey/web/lighter/action/RequestHandler.java: -------------------------------------------------------------------------------- 1 | package com.bailey.web.lighter.action; 2 | 3 | import com.bailey.web.lighter.annotation.Request; 4 | 5 | import java.lang.reflect.Method; 6 | import java.util.ArrayList; 7 | import java.util.LinkedHashMap; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.regex.Matcher; 11 | import java.util.regex.Pattern; 12 | 13 | /** 14 | * HttpServletRequest 处理器信息 15 | * 16 | * @author Bailey 17 | */ 18 | public class RequestHandler { 19 | // 可处理的HttpServletRequest的Url模式, 20 | private String urlPattern; 21 | // 用于执行HttpServletRequest处理的Action类 22 | private Class actionClass; 23 | // Action类中用于HttpServletRequest处理的具体方法 24 | private Method method; 25 | 26 | 27 | // 用于提取URL中变量名的Regular Expression Pattern 28 | final private static Pattern paramRegex = Pattern.compile("\\{(\\w+)\\}"); 29 | // 编译完成的urlPattern 30 | private Pattern urlRegex; 31 | // url中的占位参数 32 | private List urlPlaceholderParams; 33 | 34 | RequestHandler(Class actionClass, Method method) { 35 | this.actionClass = actionClass; 36 | this.method = method; 37 | analyzeMethod(); 38 | } 39 | 40 | 41 | String getUrlPattern() { 42 | return urlPattern; 43 | } 44 | 45 | Class getActionClass() { 46 | return actionClass; 47 | } 48 | 49 | Method getMethod() { 50 | return method; 51 | } 52 | 53 | // 替换原始 url-pattern 中的占位符, 形成正则表达式 54 | static String getUlrRegex(String urlPattern) { 55 | String pattern = urlPattern.replaceAll("\\*", "\\\\w+").replaceAll("\\.", "\\\\.").replaceAll("\\{\\w+\\}", "(\\\\w+)"); 56 | pattern = "^" + pattern + "$"; 57 | return pattern; 58 | } 59 | 60 | boolean isMatched(String url) { 61 | return urlRegex.matcher(url).find(); 62 | } 63 | 64 | /** 65 | * 获得URL中占位参数的键值对 66 | *

将值处理为String[] 是为了和表单提交的数据形式一致, 以便后续处理

67 | * 68 | * @param url 请求的url 69 | * @return 参数键值对 70 | * @throws ActionException 需要的参数与实际取得的参数个数不一致 71 | */ 72 | public Map getUrlPlaceholderValues(String url) throws ActionException { 73 | if (urlPlaceholderParams == null) return null; 74 | 75 | // 从URL中提取占位参数值 76 | Matcher matcherUrl = urlRegex.matcher(url); 77 | List values = new ArrayList<>(); 78 | while (matcherUrl.find()) { 79 | for (int i = 1, count = matcherUrl.groupCount(); i <= count; i++) { 80 | values.add(matcherUrl.group(i)); 81 | } 82 | } 83 | 84 | int paramCount = urlPlaceholderParams.size(); 85 | if (paramCount != values.size()) { 86 | throw new ActionException("Mismatch Url parameter-placeholder, expect " + paramCount + ", but got " + values.size()); 87 | } 88 | 89 | Map placeholderValues = new LinkedHashMap<>(); 90 | for (int i = 0; i < paramCount; i++) { 91 | placeholderValues.put(urlPlaceholderParams.get(i), new String[]{values.get(i)}); 92 | } 93 | return placeholderValues; 94 | } 95 | 96 | private void analyzeMethod() { 97 | Request requestAnnotation = method.getAnnotation(Request.class); 98 | urlPattern = requestAnnotation.url(); 99 | 100 | // 预编译匹配URL的正则表达式 101 | urlRegex = Pattern.compile(getUlrRegex(urlPattern)); 102 | 103 | // 分析 url-pattern 中的占位参数信息 104 | Matcher paramMatcher = paramRegex.matcher(urlPattern); 105 | 106 | if (paramMatcher.find()) { 107 | urlPlaceholderParams = new ArrayList<>(); 108 | do { 109 | for (int i = 1, count = paramMatcher.groupCount(); i <= count; i = i + 2) { 110 | urlPlaceholderParams.add(paramMatcher.group(i)); 111 | } 112 | } while (paramMatcher.find()); 113 | } 114 | } 115 | 116 | @Override 117 | public String toString() { 118 | return actionClass.getName() + "." + method.getName() + "()"; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/main/java/com/bailey/web/lighter/utils/file/UploadUtil.java: -------------------------------------------------------------------------------- 1 | package com.bailey.web.lighter.utils.file; 2 | 3 | import com.bailey.web.lighter.action.ActionLogger; 4 | import org.apache.commons.fileupload.FileItem; 5 | import org.apache.commons.fileupload.disk.DiskFileItemFactory; 6 | import org.apache.commons.fileupload.servlet.ServletFileUpload; 7 | 8 | import javax.servlet.http.HttpServletRequest; 9 | import java.io.File; 10 | import java.util.List; 11 | import java.util.UUID; 12 | 13 | /** 14 | * 文件上传工具 15 | * 16 | * @author Bailey 17 | */ 18 | public class UploadUtil { 19 | 20 | // 3MB, 内存中数据超过此阀值时使用临时文件缓冲数据 21 | final private static int MEMORY_THRESHOLD = 1024 * 1024 * 3; 22 | 23 | /** 24 | * 处理文件上传表单数据, 保存文件到磁盘, 提取文件信息及表单字段值并返回 25 | * 26 | * @param request HttpServletRequest请求对象 27 | * @param uploadRelativePath 文件保存路径, 相对路径 28 | * @param serverNameRule 服务器端文件命名规则, 空字符串("")表示使用原文件名. 规则中的星号("*")表示此部分使用UUID替换, 例如: "tmp*" 表示使用 "temp" + 32位UUID 作为文件名. 文件扩展名始终与原文件一致 29 | * @param maxFileSize 允许上传的单个文件最大字节数 30 | * @param maxRequestSize HttpServletRequest最大字节数 31 | * @return 文件上传处理结果 32 | */ 33 | public UploadResult upload(HttpServletRequest request, String uploadRelativePath, String serverNameRule, int maxFileSize, int maxRequestSize) { 34 | 35 | if (!ServletFileUpload.isMultipartContent(request)) { 36 | throw new RuntimeException("File Upload REQUIRE set Content-Type as 'multipart/form-data'"); 37 | } 38 | 39 | // 配置上传参数 40 | DiskFileItemFactory factory = new DiskFileItemFactory(); 41 | // 设置内存临界值 - 超过后将产生临时文件并存储于临时目录中 42 | factory.setSizeThreshold(MEMORY_THRESHOLD); 43 | // 设置临时存储目录 44 | factory.setRepository(new File(System.getProperty("java.io.tmpdir"))); 45 | 46 | ServletFileUpload upload = new ServletFileUpload(factory); 47 | 48 | // 设置最大文件上传值 49 | upload.setFileSizeMax(maxFileSize); 50 | 51 | // 设置最大请求值 (包含文件和表单数据) 52 | upload.setSizeMax(maxRequestSize); 53 | 54 | // 中文处理 55 | upload.setHeaderEncoding("UTF-8"); 56 | 57 | // 构造临时路径来存储上传的文件 58 | // 这个路径相对当前应用的目录 59 | String uploadPath = request.getServletContext().getRealPath("/") + File.separator + uploadRelativePath; 60 | 61 | // 如果目录不存在则创建 62 | File uploadFolder = new File(uploadPath); 63 | if (!uploadFolder.exists()) { 64 | if (!uploadFolder.mkdirs()) throw new RuntimeException("Make folder(s) for upload error: " + uploadPath); 65 | } 66 | 67 | UploadResult result = new UploadResult(); 68 | try { 69 | // 解析请求的内容提取文件数据 70 | List formItems = upload.parseRequest(request); 71 | if (formItems != null && formItems.size() > 0) { 72 | // 迭代处理表单数据 73 | for (FileItem item : formItems) { 74 | String fieldName = item.getFieldName(); 75 | if (!item.isFormField()) { // 文件 76 | if (item.getSize() == 0) continue; 77 | String origFileName = new File(item.getName()).getName(); 78 | 79 | String serverFileName; 80 | if ("".equals(serverNameRule)) { 81 | serverFileName = origFileName; // 传入的服务器端文件名为null, 则使用客户端原文件名保存 82 | } else { 83 | serverFileName = serverNameRule.replaceAll("\\*", UUID.randomUUID().toString().replace("-", "")) 84 | + origFileName.substring(origFileName.lastIndexOf(".", origFileName.length())); 85 | } 86 | 87 | String filePath = uploadPath + File.separator + serverFileName; 88 | File storeFile = new File(filePath); 89 | 90 | item.write(storeFile); // 保存文件到硬盘 91 | result.addFile(fieldName, origFileName, item.getSize(), item.getContentType(), serverFileName, 92 | uploadRelativePath + (uploadRelativePath.endsWith(File.separator)?"":File.separator) + serverFileName 93 | , filePath); 94 | ActionLogger.logger.info("File upload success: " + filePath); 95 | } else { // 普通表单字段 96 | result.addParameter(fieldName, item.getString()); 97 | } 98 | } 99 | } 100 | } catch (Exception e) { 101 | throw new RuntimeException("Upload file ERROR.", e); 102 | } 103 | return result; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | web-lighter 是一个小型 Java Web 应用程序的服务端封装, 曾在多个项目中应用, 对于小型 Web 项目开发而言, 实践证明确实可以省不少事. 因此, 将其共享出来, 若有需要, 拿去用便是. 2 | 3 | ## web-lighter 能做什么? 4 | - 分发 HTTP Request : 接收Http请求并分发给用户自定义的 _Action-Method_ 进行处理, 并将处理结果发回前端 5 | - HTTP Request 参数的自动解析与注入( 支持 text / json ) 6 | - _Action_ 类自动实例化与执行 ( _Action_ 为用户自定义逻辑的封装 ) 7 | - 基于Java注解 ( Annotation ) 的注入配置 8 | - 多文件上传支持 9 | - 文件下载支持 ( 可添加下载鉴权逻辑 ) 10 | 11 | 12 | ## 先来个例子 13 | 14 | 举个简单的例子, 了解一下 web-lighter 的基本用法, 以及它的基本功能. 15 | 16 | > 为了让例子变得尽可能地简单, 其间省略了部分安装与配置 web-lighter 的过程, 如果看完本例仍有兴趣继续尝试使用 web-lighter 请移步查看[Web-lighter 文档](https://baileykm.github.io/2018/06/01/Web-lighter-一个小型的-Java-Web-服务器端封装). 17 | 18 | OK, 开始吧... 19 | 20 | 1. 使用你喜欢的 IDE 创建一个动态 Java Web 项目 (步骤略) 21 | 22 | 2. 将 Web-lighter 的 Jar 文件及其依赖添加到项目构建路径 (支持 Maven) 23 | 24 | 3. 创建一个 Java 类 (`ActionExample.java`), 代码如下: 25 | 26 | ```java 27 | import com.bailey.web.lighter.action.ActionResult; 28 | import com.bailey.web.lighter.action.ActionSupport; 29 | import com.bailey.web.lighter.annotation.Param; 30 | import com.bailey.web.lighter.annotation.Request; 31 | 32 | import java.util.List; 33 | 34 | public class ActionExample extends ActionSupport { // 继承 ActionSupport, 后文称这样的类为 Action 类 35 | 36 | @Request(url = "/doSomeThing") // Request注解标注此方法可以接收的 url, 后文称这样的方法为 Action 方法 (Action-Method) 37 | public ActionResult doSomeThing( 38 | @Param(name = "intParam") Integer id, // 上行参数, 整型 39 | @Param(name = "strParam") String str, // 上行参数, 字符串类型 40 | @Param(name = "voParam") VO vo, // 上行参数, 值对象, 可用于接收前端传来的对象数据 41 | @Param(name = "voArrParam") List voArr // 上行参数, List, 可用于接收前端传来的数组数据 42 | ) { 43 | try { 44 | System.out.println("intParam = " + id); 45 | System.out.println("strParam = " + str); 46 | System.out.println("vo = {id : " + vo.getId() + ", name : " + vo.getName() + "}"); 47 | 48 | for (int i = 0; i < voArr.size(); i++) { 49 | System.out.println("voArr[" + i + "] = {id : " + voArr.get(i).getId() + ", name : " + voArr.get(i).getName() + "}"); 50 | } 51 | 52 | // ... 执行其它业务 53 | 54 | // 成功, 直接将前端传来的voParam回传 55 | return ActionResult.success(vo); 56 | } catch (Exception ex) { 57 | // 抛出异常时向前端返回错误信息 58 | return ActionResult.failure("Something wrong"); 59 | } 60 | } 61 | } 62 | ``` 63 | 64 | 上述代码中涉及的 VO 类可根据实际业务需要定义为一个普通的 *JavaBean* 类, 例如: 65 | 66 | ```java 67 | public class VO { 68 | private Integer id; 69 | private String name; 70 | 71 | public void setId(Integer id) { this.id = id;} 72 | public void getId() { return id;} 73 | public void setName(String name) { this.name = name;} 74 | public void getName() { return name;} 75 | } 76 | ``` 77 | 78 | 4. 创建 HTML 页面 `exmaple.html ` , 引入 *jQuery*, 并嵌入如下 *javascript* 脚本: 79 | 80 | ``` javascript 81 | var params = { 82 | "intParam" : 1, 83 | "strParam" : "This is a string.", 84 | "voParam" : { 85 | "id" : 999, 86 | "name" : "Peter" 87 | }, 88 | "voArrParam": [{ 89 | "id" : 10, 90 | "name" : "John" 91 | },{ 92 | "id" : 20, 93 | "name" : "Joanna" 94 | }] 95 | }; 96 | 97 | $.ajax({ 98 | type : "post", 99 | url : "wl/doSomeThing", // 注意 url 前缀 "wl" 100 | contentType: "application/json", // 注意contentType取值 101 | dataType : 'json', 102 | data : JSON.stringify(params), // 将 js 对象转为 JSON 字符串上行 103 | success : function(data){ 104 | alert(JSON.stringify(data)); // 输出服务器返回的信息 105 | }, 106 | error : function() { 107 | alert('error!'); 108 | } 109 | }); 110 | ``` 111 | 112 | 5. 启动 Web 项目, 在浏览器中打开 `exmaple.html`, 如: 在地址栏中输入 113 | 114 | 6. 运行结果: 115 | 116 | - 服务器控制台输出: 117 | 118 | ``` 119 | intParam = 1 120 | strParam = This is a string. 121 | vo = {id : 999, name : Peter} 122 | voArr[0] = {id : 10, name : John} 123 | voArr[1] = {id : 20, name : Joanna} 124 | ``` 125 | 126 | - 前端浏览器输出: 127 | 128 | ```bash 129 | {"code":0,"result":{"id":999,"name":"Peter"}} 130 | ``` 131 | 132 | ------ 133 | 134 | - 通过上例可以看到 web-lighter 可通过简单地继承 `com.bailey.web.lighter.action.ActionSupport` 并配合必要的注解即可将一个普通的 Java 类转化为可接收并处理前端请求的 *Action* 类. 这也是 web-lighter 的基本功能. 135 | 136 | - web-lighter 可将前端发来的数据作为 Action 方法的形参注入, 以便使用. (若上行数据为 *JSON* 格式则自动进行解析, 并注入) 137 | - web-lighter 可自动将服务器端需要反馈给前端的数据封装后返回前端 (通过在 *Action* 方法中返回 *ActionResult* 的实例 ). 若有必要将自动序列化为 *JSON* 格式字符串. 138 | 139 | ------ 140 | 141 | ## 使用文档 142 | [Web-lighter 文档](https://baileykm.github.io/2018/06/01/Web-lighter-一个小型的-Java-Web-服务器端封装). 143 | -------------------------------------------------------------------------------- /src/main/java/com/bailey/web/lighter/utils/ClassHelper.java: -------------------------------------------------------------------------------- 1 | package com.bailey.web.lighter.utils; 2 | 3 | import com.bailey.web.lighter.WebLighterConfig; 4 | import org.apache.commons.lang3.StringUtils; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.io.File; 8 | import java.io.IOException; 9 | import java.lang.annotation.Annotation; 10 | import java.net.JarURLConnection; 11 | import java.net.URL; 12 | import java.util.ArrayList; 13 | import java.util.Enumeration; 14 | import java.util.List; 15 | import java.util.jar.JarEntry; 16 | 17 | /** 18 | * Class搜索工具类 19 | *

按条件搜索Class, 同时缓存这些Class的信息. 此后可使用如下方法获得满足条件的Class集合

20 | *
 21 |  * - getAllClasses()            : 返回所有类的集合
 22 |  * - getClassesByAnnotation()   : 返回标注了指定注解的类集合
 23 |  * - getSubClasses()            : 返回指定 超类的子类 或 实现了指定接口的类 组成的集合
 24 |  * 
25 | * 26 | * @author Bailey 27 | */ 28 | public class ClassHelper { 29 | private String packageName; 30 | private boolean isRecursive; 31 | private boolean isIncludeJar; 32 | 33 | // 是否已经搜索并缓存过所有类 34 | private boolean isSearched = false; 35 | 36 | // 所有类集合缓存 37 | private List> allClasses = new ArrayList<>(); 38 | 39 | /** 40 | * @param packageName 包名, packageName = "" 时将搜索所有package下的Class 41 | * @param isRecursive 是否深度遍历子包 42 | * @param isIncludeJar 是否遍历JAR包中的文件 43 | */ 44 | public ClassHelper(String packageName, boolean isRecursive, boolean isIncludeJar) { 45 | this.packageName = packageName; 46 | this.isRecursive = isRecursive; 47 | this.isIncludeJar = isIncludeJar; 48 | } 49 | 50 | /** 51 | * 使用默认值构造ClassHelper 52 | *

进行Class搜索时将搜索所有package下的Class, 但忽略jar中的Class.

53 | * 54 | * @see #ClassHelper(String, boolean, boolean) 55 | */ 56 | public ClassHelper() { 57 | this("", true, false); 58 | } 59 | 60 | /** 61 | * 获得符合搜索条件的所有类 62 | * 63 | * @return 符合搜索条件的所有类 64 | */ 65 | public List> getAllClasses() { 66 | if (!isSearched) { 67 | try { 68 | searchClass(); 69 | } catch (Exception e) { 70 | LoggerFactory.getLogger(WebLighterConfig.LIB_NAME).warn("An exception occurred while searching for a RequestHandler, some RequestHandlers may be missed."); 71 | } 72 | } 73 | return allClasses; 74 | } 75 | 76 | /** 77 | * 获得有指定注解的类 78 | * 79 | * @param annotationClass 注解类 80 | * @return 标注了指定注解的类集合 81 | */ 82 | public List> getClassesByAnnotation(Class annotationClass) { 83 | List> classes = new ArrayList<>(); 84 | List> allClasses = getAllClasses(); 85 | for (Class cls : allClasses) { 86 | if (cls.isAnnotationPresent(annotationClass)) { 87 | classes.add(cls); 88 | } 89 | } 90 | return classes; 91 | } 92 | 93 | /** 94 | * 获得指定类的所有子类, 或实现了某一接口的所有子类 95 | * 96 | * @param superClass 超类/接口 97 | * @param 超类/接口 98 | * @return superClass的子类或实现了superClass接口的类 99 | */ 100 | 101 | public List> getSubClasses(Class superClass) { 102 | List> classes = new ArrayList<>(); 103 | List> allClasses = getAllClasses(); 104 | for (Class cls : allClasses) { 105 | if (superClass.isAssignableFrom(cls) && !superClass.equals(cls)) { 106 | classes.add((Class) cls); 107 | } 108 | } 109 | return classes; 110 | } 111 | 112 | /** 113 | * 搜索所有类 114 | */ 115 | private void searchClass() throws IOException { 116 | Enumeration urls = Thread.currentThread().getContextClassLoader().getResources(packageName.replaceAll("\\.", "/")); 117 | while (urls.hasMoreElements()) { 118 | URL url = urls.nextElement(); 119 | if (url != null) { 120 | switch (url.getProtocol()) { 121 | case "jar": // 添加jar中的class 122 | if (!isIncludeJar) continue; 123 | Enumeration jarEntries = ((JarURLConnection) url.openConnection()).getJarFile().entries(); 124 | while (jarEntries.hasMoreElements()) { 125 | String jarEntryName = jarEntries.nextElement().getName(); 126 | if (jarEntryName.endsWith(".class")) { 127 | String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replaceAll("/", "."); 128 | if (isRecursive || packageName.equals(className.substring(0, className.lastIndexOf(".")))) { 129 | addClass(className); 130 | } 131 | } 132 | } 133 | break; 134 | case "file": // 添加src下的class 135 | addClass(url.getPath(), packageName); 136 | break; 137 | } 138 | } 139 | } 140 | } 141 | 142 | /** 143 | * 将指定路径下所有类添加到类集合缓存 allClasses 144 | * 145 | * @param packageName 包名 146 | * @param packagePath 文件夹路径 147 | */ 148 | private void addClass(String packagePath, String packageName) { 149 | // packagePath路径下所有的class文件 或 文件夹 150 | File[] files = new File(packagePath).listFiles((file) -> file.isDirectory() || (file.isFile() && file.getName().endsWith(".class"))); 151 | if (files != null) { 152 | for (File file : files) { 153 | if (file.isFile()) { // 文件 154 | String fileName = file.getName(); 155 | String className = fileName.substring(0, fileName.lastIndexOf(".")); 156 | if (StringUtils.isNotEmpty(packageName)) { 157 | className = packageName + "." + className; 158 | } 159 | addClass(className); 160 | } else if (isRecursive) { // 文件夹 161 | String subPackName = file.getName(); 162 | String subPackagePath = (StringUtils.isEmpty(packagePath)) ? subPackName : (packagePath + "/" + subPackName); 163 | String subPackageName = (StringUtils.isEmpty(packageName)) ? subPackName : (packageName + "." + subPackName); 164 | addClass(subPackagePath, subPackageName); 165 | } 166 | } 167 | } 168 | } 169 | 170 | private void addClass(String className) { 171 | try { 172 | allClasses.add(Class.forName(className)); 173 | } catch (Exception e) { 174 | LoggerFactory.getLogger(WebLighterConfig.LIB_NAME).warn("Can't analyze the class, it has been ignored: " + className); 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.github.baileykm 8 | web-lighter 9 | 1.1.0 10 | web-lighter 11 | Web-lighter is a small server-side package for Java web application. 12 | https://github.com/baileykm/web-lighter 13 | 14 | 15 | utf-8 16 | utf-8 17 | 18 | 19 | 20 | 21 | 22 | javax.servlet 23 | javax.servlet-api 24 | 3.1.0 25 | provided 26 | 27 | 28 | 29 | org.apache.commons 30 | commons-lang3 31 | 3.7 32 | 33 | 34 | 35 | org.slf4j 36 | slf4j-api 37 | 1.7.25 38 | 39 | 40 | 41 | com.google.code.gson 42 | gson 43 | 2.8.4 44 | 45 | 46 | commons-fileupload 47 | commons-fileupload 48 | 1.3.3 49 | 50 | 51 | nl.bitwalker 52 | UserAgentUtils 53 | 1.2.4 54 | 55 | 56 | 57 | 58 | Github Issue 59 | https://github.com/baileykm/web-lighter/issues 60 | 61 | 62 | 63 | 64 | The Apache Software License, Version 2.0 65 | http://www.apache.org/licenses/LICENSE-2.0.txt 66 | 67 | 68 | 69 | 70 | 71 | bailey 72 | mr.bailey@163.com 73 | 74 | 75 | 76 | 77 | scm:git@github.com:baileykm/web-lighter.git 78 | scm:git@github.com:baileykm/web-lighter.git 79 | git@github.com:baileykm/web-lighter.git 80 | 81 | 82 | 83 | 84 | 85 | org.apache.maven.plugins 86 | maven-compiler-plugin 87 | 3.5.1 88 | 89 | 1.8 90 | 1.8 91 | 92 | 93 | 94 | org.apache.maven.plugins 95 | maven-javadoc-plugin 96 | 2.10.4 97 | 98 | true 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | release 107 | 108 | 109 | oss 110 | https://oss.sonatype.org/content/repositories/snapshots/ 111 | 112 | 113 | oss 114 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 115 | 116 | 117 | 118 | 119 | 120 | 121 | org.apache.maven.plugins 122 | maven-source-plugin 123 | 3.0.1 124 | 125 | 126 | package 127 | 128 | jar-no-fork 129 | 130 | 131 | 132 | 133 | 134 | 135 | org.apache.maven.plugins 136 | maven-javadoc-plugin 137 | 2.10.4 138 | 139 | 140 | package 141 | 142 | jar 143 | 144 | 145 | 146 | 147 | 148 | 149 | org.apache.maven.plugins 150 | maven-gpg-plugin 151 | 1.6 152 | 153 | 154 | sign-artifacts 155 | verify 156 | 157 | sign 158 | 159 | 160 | 161 | 162 | 163 | org.sonatype.plugins 164 | nexus-staging-maven-plugin 165 | 1.6.8 166 | true 167 | 168 | ossrh 169 | https://oss.sonatype.org/ 170 | true 171 | 172 | 173 | 174 | 175 | 176 | 177 | -------------------------------------------------------------------------------- /src/main/java/com/bailey/web/lighter/action/ActionHelper.java: -------------------------------------------------------------------------------- 1 | package com.bailey.web.lighter.action; 2 | 3 | 4 | import com.bailey.web.lighter.WebLighterConfig; 5 | import com.bailey.web.lighter.annotation.*; 6 | import com.bailey.web.lighter.utils.ClassHelper; 7 | import com.bailey.web.lighter.utils.GsonUTCDateAdapter; 8 | import com.bailey.web.lighter.utils.GsonUtil; 9 | import com.bailey.web.lighter.utils.file.*; 10 | import com.google.gson.*; 11 | import com.google.gson.reflect.TypeToken; 12 | import org.apache.commons.lang3.ArrayUtils; 13 | import org.apache.commons.lang3.StringUtils; 14 | 15 | import javax.servlet.http.HttpServletRequest; 16 | import javax.servlet.http.HttpServletResponse; 17 | import java.io.IOException; 18 | import java.io.PrintWriter; 19 | import java.lang.reflect.*; 20 | import java.sql.Timestamp; 21 | import java.util.*; 22 | 23 | /** 24 | * Action 处理工具类, 包含如下功能: 25 | *
 26 |  * - 根据 Request 的 URI 找到处理该 Request 的函数
 27 |  * - 实例化处理 Request 的 Action 对象, 并调用相应的处理函数. 调用处理函数前将自动解析上行参数 ( JSON / Text), 并将在调用处理函数时作为参数注入.
 28 |  * - 支持多文件上传 ( 可同时携带数据 )
 29 |  * - 调用 Request 处理函数时自动实例化并注入 Service 之类的对象 ( 使用 @Inject )
 30 |  * 
31 | * 32 | * @author Bailey 33 | * @see Request 34 | * @see Inject 35 | * @see Param 36 | * @see Upload 37 | * @see Download 38 | */ 39 | public class ActionHelper { 40 | final private static List requestHandlers = new ArrayList<>(); 41 | 42 | protected Gson gson; 43 | 44 | public ActionHelper() { 45 | // 配置并创建Gson对象 46 | gson = GsonUtil.getGsonInstance(); 47 | } 48 | 49 | /** 50 | * 初始化requestHandlers 51 | * 遍历所有ActionSupport的子类, 将其信息缓存到requestHandlers, 以便后续使用 52 | * 53 | * @see RequestHandler 54 | */ 55 | public static void initRequestHandlers() { 56 | long start = System.currentTimeMillis(); 57 | // 获得所有ActionSupport的子类 58 | List> actionClasses = new ClassHelper().getSubClasses(ActionSupport.class); 59 | // 临时存储已缓存过的RequestHandler, 用于判定是否多个方法用于处理同一个请求 60 | List urlRegExpress = new ArrayList<>(); 61 | for (Class actionCls : actionClasses) { 62 | Method[] methods = actionCls.getDeclaredMethods(); 63 | for (Method method : methods) { 64 | if (!method.isAnnotationPresent(Request.class)) continue; // 忽略无@Request的方法 65 | String urlPattern = method.getAnnotation(Request.class).url(); 66 | String urlRegex = RequestHandler.getUlrRegex(urlPattern); 67 | if (urlRegExpress.contains(urlRegex)) { 68 | throw new RuntimeException("More than one method is declared as request handler: " + urlPattern); 69 | } else { 70 | urlRegExpress.add(urlRegex); 71 | requestHandlers.add(new RequestHandler(actionCls, method)); 72 | } 73 | } 74 | } 75 | ActionLogger.logger.info("RequestHandler Initialized in " + (System.currentTimeMillis() - start) + "ms. " + requestHandlers.size() + " request(s) mapped."); 76 | if ( ActionLogger.logger.isTraceEnabled()) { 77 | ActionLogger.logger.trace(getRequestHandlersReport()); 78 | } 79 | } 80 | 81 | /** 82 | * 获得用于处理给定uri请求的RequestHandler 83 | * 84 | * @param url 前端发来的请求的URL 85 | * @return 符合条件的 {@link RequestHandler} 86 | */ 87 | public static RequestHandler getRequestHandler(String url) { 88 | for (RequestHandler handler : requestHandlers) { 89 | if (handler.isMatched(url)) return handler; 90 | } 91 | return null; 92 | } 93 | 94 | /** 95 | * 获得请求(Request)与处理方法(Action Method)之间的映射表. 用于调试 96 | * 97 | * @return 映射表 98 | */ 99 | private static String getRequestHandlersReport() { 100 | Map map = new LinkedHashMap<>(); 101 | int maxUrlLength = 0; 102 | int maxMethodNameLength = 0; 103 | int len; 104 | for (RequestHandler handler : requestHandlers) { 105 | String url = handler.getUrlPattern(); 106 | String method = handler.getActionClass().getName() + "." + handler.getMethod().getName() + "()"; 107 | map.put(url, method); 108 | if ((len = url.length()) > maxUrlLength) maxUrlLength = len; 109 | if ((len = method.length()) > maxMethodNameLength) maxMethodNameLength = len; 110 | } 111 | String line = "\n-------------------------------------------------------\n"; 112 | StringBuffer sb = new StringBuffer(); 113 | sb.append(line); 114 | sb.append(" Web-lighter Action Mapping Report "); 115 | sb.append(line); 116 | for (String k : map.keySet()) { 117 | sb.append(WebLighterConfig.getUrlPrefix() + StringUtils.rightPad(k, maxUrlLength, " ") + " ==> " + map.get(k) + "\n"); 118 | } 119 | sb.append(line); 120 | return sb.toString().replace("\n", ActionLogger.RC); 121 | } 122 | 123 | /** 124 | * 执行请求处理函数 125 | * 126 | * @param handler 处理请求的{@link RequestHandler} 127 | * @param url 去除请求前缀(WebLighterConfig.URL_PREFIX)后的url 128 | * @param req HttpServletRequest 129 | * @param resp HttpServletResponse 130 | * @throws ActionException ActionException 131 | * @throws IOException IOException 132 | * @see RequestHandler 133 | */ 134 | public void execute(RequestHandler handler, String url, HttpServletRequest req, HttpServletResponse resp) throws ActionException, IOException { 135 | ActionLogger.logger.trace(WebLighterConfig.getUrlPrefix() + url + " ==> " + handler.getActionClass().getName() + "." + handler.getMethod().getName() + "()"); 136 | 137 | Method method = handler.getMethod(); // 处理请求的Action方法 138 | Request RequestAnnotation = method.getAnnotation(Request.class); 139 | 140 | // 参数数据格式 141 | ParamFormat paramFormat = RequestAnnotation.format(); 142 | 143 | List fileInfos = null; // 上传的文件信息 144 | Map requestParameters = new LinkedHashMap<>(); // 参数集合 145 | 146 | if (method.isAnnotationPresent(Upload.class)) { // 带文件上传的请求 147 | // 接收并保存文件 148 | Upload annotation = method.getAnnotation(Upload.class); 149 | UploadResult resultInfo = new UploadUtil().upload(req, annotation.uploadDir(), annotation.nameRule(), annotation.maxFileSize(), annotation.maxRequestSize()); 150 | requestParameters.putAll(parseParams(resultInfo.getParameters(), paramFormat)); 151 | fileInfos = resultInfo.getFiles(); 152 | } else { // 普通的请求 153 | requestParameters.putAll(parseParams(req, paramFormat)); 154 | } 155 | 156 | // url占位参数 157 | Map urlPlaceholderParams = handler.getUrlPlaceholderValues(url); 158 | 159 | // 检查占位参数是否与其他上行参数冲突 160 | if (urlPlaceholderParams != null) { 161 | for (String urlParamName : urlPlaceholderParams.keySet()) { 162 | for (String paramName : requestParameters.keySet()) { 163 | if (urlParamName.equals(paramName)) { 164 | throw new ActionException("The parameter name \"" + paramName + "\" is conflict with url placeholder parameter. url-pattern: " + handler.getUrlPattern()); 165 | } 166 | } 167 | } 168 | 169 | // 将url占位参数添加到参数集合中 170 | requestParameters.putAll(parseParams(urlPlaceholderParams, ParamFormat.text)); // url 占位参数只可能按字符串处理 171 | } 172 | 173 | // 请求处理结果 174 | ActionResult actionResult = invoke(handler, req, resp, requestParameters, fileInfos); 175 | 176 | if (actionResult == null) { 177 | throw new ActionException("The Action Result Unassigned!!!"); 178 | } 179 | 180 | if (!method.isAnnotationPresent(Download.class) || actionResult.getCode() < 0) { 181 | // 非文件下载请求, 回传结果 182 | PrintWriter writer = resp.getWriter(); 183 | gson.toJson(actionResult, writer); 184 | writer.flush(); 185 | } else { 186 | // 文件下载请求 187 | if (!(actionResult.getResult() instanceof DownloadFileInfo)) { 188 | throw new ActionException("The result property of ActionResult REQUIRE an instance of DownloadFileInfo"); 189 | } 190 | 191 | DownloadFileInfo fileInfo = (DownloadFileInfo) actionResult.getResult(); 192 | new DownloadUtil().download(req, resp, fileInfo); 193 | } 194 | } 195 | 196 | /** 197 | * 按指定的参数格式声明(paramFormat)将参数值封装为JsonElement 198 | * 199 | * @param raw 原始参数 200 | * @param paramFormat 参数格式声明 201 | * @return 封装完成的JsonElement 202 | * @see ParamFormat 203 | */ 204 | private JsonElement parse2Json(String raw, ParamFormat paramFormat) { 205 | try { 206 | switch (paramFormat) { 207 | case json: 208 | return gson.fromJson(raw, JsonElement.class); 209 | case text: 210 | default: 211 | return new JsonPrimitive(raw); 212 | } 213 | } catch (JsonSyntaxException e) { 214 | return new JsonPrimitive(raw); 215 | } 216 | } 217 | 218 | /** 219 | * 将数据转成JSON格式, 以便统一处理 220 | * 221 | * @param rawParams 原始数据 222 | * @param paramFormat 数据格式 223 | * @return JSON格式数据 224 | */ 225 | private Map parseParams(Map rawParams, ParamFormat paramFormat) { 226 | Map params = new LinkedHashMap<>(); 227 | for (String key : rawParams.keySet()) { 228 | String[] values = rawParams.get(key); 229 | JsonElement val = null; 230 | if (!ArrayUtils.isEmpty(values)) { 231 | if (values.length == 1) { 232 | val = parse2Json(values[0], paramFormat); 233 | } else { 234 | JsonArray arr = new JsonArray(); 235 | for (String value : values) { 236 | arr.add(parse2Json(value, paramFormat)); 237 | } 238 | val = arr; 239 | } 240 | } 241 | params.put(key, val); 242 | } 243 | return params; 244 | } 245 | 246 | /** 247 | * 将数据转成JSON格式, 以便统一处理 248 | * 249 | * @param req HttpServletRequest 250 | * @return JSON格式数据 251 | */ 252 | private Map parseParams(HttpServletRequest req, ParamFormat paramFormat) throws IOException, ActionException { 253 | String contentType = req.getContentType(); 254 | if (contentType == null || contentType.contains("application/x-www-form-urlencoded")) { 255 | return parseParams(req.getParameterMap(), paramFormat); 256 | } else if (contentType.contains("application/json")) { 257 | JsonObject jsonObject = gson.fromJson(gson.newJsonReader(req.getReader()), JsonObject.class); 258 | Map params = new LinkedHashMap<>(); 259 | if (jsonObject != null) { 260 | for (String name : jsonObject.keySet()) { 261 | params.put(name, jsonObject.get(name)); 262 | } 263 | } 264 | return params; 265 | } else { 266 | throw new ActionException("The Content-Type MUST be 'application/json' or 'application/x-www-form-urlencoded'"); 267 | } 268 | } 269 | 270 | /** 271 | * 调用请求处理函数, 同时注入相关参数 272 | * 273 | * @param handler 处理请求的 RequestHandler 对象 274 | * @param req Request 对象 275 | * @param resp Response 对象 276 | * @param requestParameters 参数集合, key 为参数名, value 为参数值 277 | * @param fileInfos 上传的文件信息 278 | * @return 封装为 ActionResult 的处理结果 279 | * @throws ActionException Action处理异常 280 | * @see JsonElement 281 | * @see ActionResult 282 | */ 283 | private ActionResult invoke(RequestHandler handler, 284 | HttpServletRequest req, 285 | HttpServletResponse resp, 286 | Map requestParameters, 287 | List fileInfos) throws ActionException { 288 | 289 | // 创建Action实例 290 | ActionSupport action; 291 | // 调用Action中相应的处理方法 292 | try { 293 | action = handler.getActionClass().newInstance(); 294 | action.setRequest(req); 295 | action.setResponse(resp); 296 | } catch (InstantiationException | IllegalAccessException e) { 297 | throw new ActionException("Creating Action instance error: " + handler.getActionClass().getName(), e); 298 | } 299 | 300 | 301 | // 处理请求的Action方法 302 | Method method = handler.getMethod(); 303 | 304 | // 调用方法所需参数 305 | Parameter[] parameters = method.getParameters(); 306 | 307 | // 调用方法需要传入的参数值 308 | Object[] paramValues = new Object[parameters.length]; 309 | 310 | // 依次取得调用请求处理方法所需参数 311 | for (int i = 0; i < parameters.length; i++) { 312 | Parameter param = parameters[i]; 313 | 314 | // 参数带@Inject注解, 需要实例化并注入, 如 Service 315 | if (param.isAnnotationPresent(Inject.class)) { 316 | // 判断是否有默认构造函数, 若有则直接实例化之; 否则尝试使用getInstance()获得实例; 若均失败, 则抛出异常 317 | 318 | Class injectClass = param.getType(); 319 | try { 320 | try { 321 | paramValues[i] = injectClass.newInstance(); // 调用默认构造函数实例化 322 | } catch (InstantiationException | IllegalAccessException e) { 323 | paramValues[i] = injectClass.getDeclaredMethod("getInstance").invoke(null); // 调用 getInstance() 方法, 获得实例 324 | } 325 | } catch (Exception e) { 326 | throw new ActionException("Failed to get instance for @Inject parameter: " + injectClass.getName(), e); 327 | } 328 | continue; 329 | } 330 | 331 | // 参数带@ParamFileInfo注解, 本参数用于接收上传文件信息 332 | if (param.isAnnotationPresent(ParamFileInfo.class)) { 333 | if (param.getType().isAssignableFrom(UploadFileInfo.class)) { // 函数只接收单个文件信息, 取fileInfo中的第0个元素 334 | paramValues[i] = (fileInfos != null && !fileInfos.isEmpty()) ? fileInfos.get(0) : null; 335 | } else { // List, 接收多个文件信息 336 | paramValues[i] = fileInfos; 337 | } 338 | continue; 339 | } 340 | 341 | // 参数带@Param, 本参数用于接收上行参数 342 | if (param.isAnnotationPresent(Param.class)) { // 上行参数 343 | Param paramAnnotation = param.getAnnotation(Param.class); // Param 注解 344 | String paramName = paramAnnotation.name(); // 上行数据中的参数名 345 | JsonElement paramValue = requestParameters.get(paramName); // 上行数据值 346 | Type paramType = param.getParameterizedType(); 347 | Class paramClass = param.getType(); 348 | 349 | if (paramValue == null || paramValue instanceof JsonNull) { // null 350 | paramValues[i] = null; 351 | continue; 352 | } 353 | 354 | try { 355 | // 若要求的参数类型为数组或Collection, 而实际解析得到的参数不是JsonArray则将参数封装为JsonArray 356 | if ((paramClass.isArray() || Collection.class.isAssignableFrom(paramClass)) && !paramValue.isJsonArray()) { 357 | JsonArray arr = new JsonArray(); 358 | arr.add(paramValue); 359 | paramValue = arr; 360 | } else if (String.class.isAssignableFrom(paramClass) && !paramValue.isJsonPrimitive()) { 361 | // 要求的参数类型为String, 而解析时被处理成了VO, 则转为String 362 | paramValue = new JsonPrimitive(gson.toJson(paramValue)); 363 | } 364 | if (paramType instanceof ParameterizedType) { // 泛型参数 365 | ParameterizedType parameterizedType = (ParameterizedType) paramType; 366 | Type rowType = parameterizedType.getRawType(); 367 | Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); 368 | paramValues[i] = gson.fromJson(paramValue, TypeToken.getParameterized(rowType, actualTypeArguments).getType()); 369 | } else { // 非泛型参数 370 | paramValues[i] = gson.fromJson(paramValue, paramType); 371 | } 372 | } catch (Exception e) { 373 | throw new ActionException("Parameter parsing error: " + paramName, e); 374 | } 375 | } 376 | } 377 | 378 | // 调用Action中相应的处理方法 379 | try { 380 | return (ActionResult) method.invoke(action, paramValues); 381 | } catch (IllegalAccessException | InvocationTargetException e) { 382 | throw new ActionException("Error calling method: " + method.getName(), e); 383 | } 384 | } 385 | } 386 | --------------------------------------------------------------------------------