com.github.yiuman.citrus.support.utils.WebUtils Maven / Gradle / Ivy
package com.github.yiuman.citrus.support.utils;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
import com.github.yiuman.citrus.support.crud.view.TableView;
import com.github.yiuman.citrus.support.http.JsonServletRequestWrapper;
import com.github.yiuman.citrus.support.model.Header;
import com.github.yiuman.citrus.support.model.Page;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.util.*;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.support.WebRequestDataBinder;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.support.AbstractMultipartHttpServletRequest;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.beans.PropertyEditorSupport;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.Predicate;
/**
* Web相关操作工具
*
* @author yiuman
* @date 2020/4/4
*/
@Slf4j
public final class WebUtils {
private static final String APPLICATION_VND_MS_EXCEL = "application/vnd.ms-excel";
/**
* 忽略实体没有的字段
*/
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.configure(JsonParser.Feature.AUTO_CLOSE_SOURCE, true)
.setSerializationInclusion(JsonInclude.Include.ALWAYS);
private WebUtils() {
}
public static ServletRequestAttributes getServletRequestAttributes() {
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes());
}
public static HttpServletRequest getRequest() {
return getServletRequestAttributes().getRequest();
}
public static String getRequestParam(String name) {
return getRequest().getParameter(name);
}
public static HttpServletResponse getResponse() {
return getServletRequestAttributes().getResponse();
}
public static Object getAttribute(String name, int scope) {
return getServletRequestAttributes().getAttribute(name, scope);
}
/**
* 类型绑定及参数校验
*
* @param entityClass 当前绑定数据的类型
* @param request 当前请求
* @param 绑定数据的类型
* @return 已经绑定好的数据
* @throws Exception 反射异常、参数校验异常
*/
public static T bindDataAndValidate(Class entityClass, HttpServletRequest request) throws Exception {
//构造认证模式实体
final T supportEntity = WebUtils.requestDataBind(entityClass, request);
//校验实体参数
ValidateUtils.defaultValidateEntity(supportEntity);
return supportEntity;
}
/**
* 类型绑定请求数据
*
* @param objectClass 绑定的数据类型Class
* @param request 当前请求
* @param force 是否强制生成
* @return 已经处理好的绑定数据
* @throws Exception 反射异常
*/
public static T requestDataBind(Class objectClass, HttpServletRequest request, boolean force) throws Exception {
if (objectClass == null || request == null) {
return null;
}
//若是get请求或者是form-data则先检验下有没参数
boolean hasParameterRequest = request instanceof AbstractMultipartHttpServletRequest || request.getMethod().equals(HttpMethod.GET.name());
if (!force && hasParameterRequest && CollectionUtils.isEmpty(request.getParameterMap())) {
return null;
}
//构造实体
T t = BeanUtils.instantiateClass(objectClass);
requestDataBind(t, request);
return t;
}
/**
* 类型绑定请求数据
*
* @param objectClass 绑定的数据类型Class
* @param request 当前请求
* @param 当前绑定实例Class的类型
* @return 已经处理好的绑定数据
* @throws Exception 反射异常
*/
public static T requestDataBind(Class objectClass, HttpServletRequest request) throws Exception {
return requestDataBind(objectClass, request, false);
}
/**
* 实例绑定请求数据
*
* @param object 传入的绑定实例
* @param request 当前请求
* @param 当前绑定实例类型
* @throws Exception 反射异常
*/
@SuppressWarnings("unchecked")
public static void requestDataBind(T object, HttpServletRequest request) throws Exception {
if (request instanceof AbstractMultipartHttpServletRequest || request.getMethod().equals(HttpMethod.GET.name())) {
WebRequestDataBinder dataBinder = new WebRequestDataBinder(object);
dataBinder.registerCustomEditor(LocalDate.class, new LocalDateEditor());
dataBinder.bind(new ServletWebRequest(request));
} else {
try {
if (!(request instanceof JsonServletRequestWrapper)) {
request = new JsonServletRequestWrapper(request);
}
if (object.getClass().isArray()) {
BeanUtils.copyProperties(object, ((JsonServletRequestWrapper) request).getArray());
} else {
T realT = (T) OBJECT_MAPPER.readValue(request.getInputStream(), object.getClass());
BeanUtils.copyProperties(realT, object);
}
} catch (MismatchedInputException ex) {
log.info("请求数据转换异常", ex);
}
}
}
/**
* 获取cookie值
*
* @param request 当前请求
* @param name cookie参数名
* @return cookie值
*/
public static String getCookie(HttpServletRequest request, String name) {
Optional optionalCookie = Arrays.stream(request.getCookies()).filter(cookie -> name.equals(cookie.getName())).findFirst();
return optionalCookie.map(Cookie::getValue).orElse(null);
}
/**
* 获取用户真实IP地址,不使用request.getRemoteAddr();的原因是有可能用户使用了代理软件方式避免真实IP地址,
*
* 可是,如果通过了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP值,究竟哪个才是真正的用户端的真实IP呢?
* 答案是取X-Forwarded-For中第一个非unknown的有效IP字符串。
*
* 如:X-Forwarded-For:192.168.1.110, 192.168.1.120, 192.168.1.130,
* 192.168.1.100
*
* 用户真实IP为: 192.168.1.110
*
* @param request 请求
* @return IPADDRESS ip地址
*/
public static String getIpAddress(HttpServletRequest request) {
String ip = request.getHeader(IpHeaders.X_FORWARDED_FOR);
final Predicate ipPredicate = ip1 -> ip1 == null || ip1.length() == 0 || IpHeaders.UNKNOWN.equalsIgnoreCase(ip1);
if (ipPredicate.test(ip)) {
ip = request.getHeader(IpHeaders.PROXY_CLIENT_IP);
}
if (ipPredicate.test(ip)) {
ip = request.getHeader(IpHeaders.WL_PROXY_CLIENT_IP);
}
if (ipPredicate.test(ip)) {
ip = request.getHeader(IpHeaders.HTTP_CLIENT_IP);
}
if (ipPredicate.test(ip)) {
ip = request.getHeader(IpHeaders.HTTP_X_FORWARDED_FOR);
}
if (ipPredicate.test(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
public static void exportJson(HttpServletResponse response, Object data, String name) throws IOException {
addExportFilenameHeaders(response, name, "json");
response.getWriter().write(OBJECT_MAPPER.writeValueAsString(data));
}
/**
* 导出excel文档
*
* @param response 当前响应
* @param clazz 类型
* @param data 数据
* @param name 导出文件名
* @param 导出定义类的类型
* @throws IOException IO异常
*/
public static void exportExcel(HttpServletResponse response, Class clazz, List data, String name) throws IOException {
addExportFilenameHeaders(response, name);
response.setContentType(APPLICATION_VND_MS_EXCEL);
EasyExcel.write(response.getOutputStream(), clazz)
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
.sheet("sheet1").doWrite(data);
}
/**
* 动态列导出Excel
*
* @param response 响应
* @param headers 表头
* @param data 数据
* @param name 文件 名
* @throws IOException IO异常
*/
public static void exportExcel(HttpServletResponse response, List> headers, List> data, String name) throws IOException {
addExportFilenameHeaders(response, name + ".xls");
response.setContentType(APPLICATION_VND_MS_EXCEL);
EasyExcel.write(response.getOutputStream())
.registerConverter(new LocalDateExportConvertor())
.head(headers)
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
.sheet("sheet1")
.doWrite(data);
}
/**
* 动态列导出Excel
*
* @param response 响应
* @param page 分页对象
* @param name 文件 名
* @throws IOException IO异常
*/
public static void exportExcel(HttpServletResponse response, Page> page, String name) throws Exception {
addExportFilenameHeaders(response, name + ".xls");
response.setContentType(APPLICATION_VND_MS_EXCEL);
List> records = page.getRecords();
List pageHeaders = null;
Object view = page.getView();
if (view instanceof TableView) {
TableView tableView = (TableView) view;
pageHeaders = tableView.getHeaders();
}
List> data = new ArrayList<>(records.size());
Map> recordExtend = page.getRecordExtend();
List> headers = new ArrayList<>(pageHeaders.size());
pageHeaders.forEach(header -> headers.add(Collections.singletonList(header.getText())));
//这里记录字段与表头的对应关系,方便后边操作,遍历一次后之后不需要重新取
final Class> recordClass = records.get(0).getClass();
Field recordKeyField = ReflectionUtils.findField(recordClass, page.getItemKey());
recordKeyField.setAccessible(true);
final Map fieldMap = new HashMap<>(256);
List finalPageHeaders = pageHeaders;
records.forEach(record -> {
List