package com.xueyi.common.core.utils.html;
import lombok.Getter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* HTML过滤器,用于去除XSS漏洞隐患。
*
* @author xueyi
*/
public final class HTMLFilter {
/**
* regex flag union representing /si modifiers in php
**/
private static final int REGEX_FLAGS_SI = Pattern.CASE_INSENSITIVE | Pattern.DOTALL;
private static final Pattern P_COMMENTS = Pattern.compile("", Pattern.DOTALL);
private static final Pattern P_COMMENT = Pattern.compile("^!--(.*)--$", REGEX_FLAGS_SI);
private static final Pattern P_TAGS = Pattern.compile("<(.*?)>", Pattern.DOTALL);
private static final Pattern P_END_TAG = Pattern.compile("^/([a-z0-9]+)", REGEX_FLAGS_SI);
private static final Pattern P_START_TAG = Pattern.compile("^([a-z0-9]+)(.*?)(/?)$", REGEX_FLAGS_SI);
private static final Pattern P_QUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)=([\"'])(.*?)\\2", REGEX_FLAGS_SI);
private static final Pattern P_UNQUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)(=)([^\"\\s']+)", REGEX_FLAGS_SI);
private static final Pattern P_PROTOCOL = Pattern.compile("^([^:]+):", REGEX_FLAGS_SI);
private static final Pattern P_ENTITY = Pattern.compile("(\\d+);?");
private static final Pattern P_ENTITY_UNICODE = Pattern.compile("([0-9a-f]+);?");
private static final Pattern P_ENCODE = Pattern.compile("%([0-9a-f]{2});?");
private static final Pattern P_VALID_ENTITIES = Pattern.compile("&([^&;]*)(?=(;|&|$))");
private static final Pattern P_VALID_QUOTES = Pattern.compile("(>|^)([^<]+?)(<|$)", Pattern.DOTALL);
private static final Pattern P_END_ARROW = Pattern.compile("^>");
private static final Pattern P_BODY_TO_END = Pattern.compile("<([^>]*?)(?=<|$)");
private static final Pattern P_XML_CONTENT = Pattern.compile("(^|>)([^<]*?)(?=>)");
private static final Pattern P_STRAY_LEFT_ARROW = Pattern.compile("<([^>]*?)(?=<|$)");
private static final Pattern P_STRAY_RIGHT_ARROW = Pattern.compile("(^|>)([^<]*?)(?=>)");
private static final Pattern P_AMP = Pattern.compile("&");
private static final Pattern P_QUOTE = Pattern.compile("\"");
private static final Pattern P_LEFT_ARROW = Pattern.compile("<");
private static final Pattern P_RIGHT_ARROW = Pattern.compile(">");
private static final Pattern P_BOTH_ARROWS = Pattern.compile("<>");
// @xxx could grow large... maybe use sesat's ReferenceMap
private static final ConcurrentMap<String, Pattern> P_REMOVE_PAIR_BLANKS = new ConcurrentHashMap<>();
private static final ConcurrentMap<String, Pattern> P_REMOVE_SELF_BLANKS = new ConcurrentHashMap<>();
/**
* set of allowed html elements, along with allowed attributes for each element
**/
private final Map<String, List<String>> vAllowed;
/**
* counts of open tags for each (allowable) html element
**/
private final Map<String, Integer> vTagCounts = new HashMap<>();
/**
* html elements which must always be self-closing (e.g. "")
**/
private final String[] vSelfClosingTags;
/**
* html elements which must always have separate opening and closing tags (e.g. "")
**/
private final String[] vNeedClosingTags;
/**
* set of disallowed html elements
**/
private final String[] vDisallowed;
/**
* attributes which should be checked for valid protocols
**/
private final String[] vProtocolAtts;
/**
* allowed protocols
**/
private final String[] vAllowedProtocols;
/**
* tags which should be removed if they contain no content (e.g. "" or "")
**/
private final String[] vRemoveBlanks;
/**
* entities allowed within html markup
**/
private final String[] vAllowedEntities;
/**
* flag determining whether comments are allowed in input String.
*/
private final boolean stripComment;
private final boolean encodeQuotes;
/**
* flag determining whether to try to make tags when presented with "unbalanced" angle brackets (e.g. ""
* becomes " text "). If set to false, unbalanced angle brackets will be html escaped.
*/
@Getter
private final boolean alwaysMakeTags;
/**
* Default constructor.
*/
public HTMLFilter() {
vAllowed = new HashMap<>();
final ArrayList<String> a_atts = new ArrayList<>();
a_atts.add("href");
a_atts.add("target");
vAllowed.put("a", a_atts);
final ArrayList<String> img_atts = new ArrayList<>();
img_atts.add("src");
img_atts.add("width");
img_atts.add("height");
img_atts.add("alt");
vAllowed.put("img", img_atts);
final ArrayList<String> no_atts = new ArrayList<>();
vAllowed.put("b", no_atts);
vAllowed.put("strong", no_atts);
vAllowed.put("i", no_atts);
vAllowed.put("em", no_atts);
vSelfClosingTags = new String[]{"img"};
vNeedClosingTags = new String[]{"a", "b", "strong", "i", "em"};
vDisallowed = new String[]{};
vAllowedProtocols = new String[]{"http", "mailto", "https"}; // no ftp.
vProtocolAtts = new String[]{"src", "href"};
vRemoveBlanks = new String[]{"a", "b", "strong", "i", "em"};
vAllowedEntities = new String[]{"amp", "gt", "lt", "quot"};
stripComment = true;
encodeQuotes = true;
alwaysMakeTags = false;
}
/**
* Map-parameter configurable constructor.
*
* @param conf map containing configuration. keys match field names.
*/
@SuppressWarnings("unchecked")
public HTMLFilter(final Map<String, Object> conf) {
assert conf.containsKey("vAllowed") : "configuration requires vAllowed";
assert conf.containsKey("vSelfClosingTags") : "configuration requires vSelfClosingTags";
assert conf.containsKey("vNeedClosingTags") : "configuration requires vNeedClosingTags";
assert conf.containsKey("vDisallowed") : "configuration requires vDisallowed";
assert conf.containsKey("vAllowedProtocols") : "configuration requires vAllowedProtocols";
assert conf.containsKey("vProtocolAtts") : "configuration requires vProtocolAtts";
assert conf.containsKey("vRemoveBlanks") : "configuration requires vRemoveBlanks";
assert conf.containsKey("vAllowedEntities") : "configuration requires vAllowedEntities";
vAllowed = Collections.unmodifiableMap((HashMap<String, List<String>>) conf.get("vAllowed"));
vSelfClosingTags = (String[]) conf.get("vSelfClosingTags");
vNeedClosingTags = (String[]) conf.get("vNeedClosingTags");
vDisallowed = (String[]) conf.get("vDisallowed");
vAllowedProtocols = (String[]) conf.get("vAllowedProtocols");
vProtocolAtts = (String[]) conf.get("vProtocolAtts");
vRemoveBlanks = (String[]) conf.get("vRemoveBlanks");
vAllowedEntities = (String[]) conf.get("vAllowedEntities");
stripComment = conf.containsKey("stripComment") ? (Boolean) conf.get("stripComment") : true;
encodeQuotes = conf.containsKey("encodeQuotes") ? (Boolean) conf.get("encodeQuotes") : true;
alwaysMakeTags = conf.containsKey("alwaysMakeTags") ? (Boolean) conf.get("alwaysMakeTags") : true;
}
private void reset() {
vTagCounts.clear();
}
// ---------------------------------------------------------------
// my versions of some PHP library functions
public static String chr(final int decimal) {
return String.valueOf((char) decimal);
}
public static String htmlSpecialChars(final String s) {
String result = s;
result = regexReplace(P_AMP, "&", result);
result = regexReplace(P_QUOTE, """, result);
result = regexReplace(P_LEFT_ARROW, "<", result);
result = regexReplace(P_RIGHT_ARROW, ">", result);
return result;
}
// ---------------------------------------------------------------
/**
* given a user submitted input String, filter out any invalid or restricted html.
*
* @param input text (i.e. submitted by a user) than may contain html
* @return "clean" version of input, with only valid, whitelisted html elements allowed
*/
public String filter(final String input) {
reset();
String s = input;
s = escapeComments(s);
s = balanceHTML(s);
s = checkTags(s);
s = processRemoveBlanks(s);
// s = validateEntities(s);
return s;
}
public boolean isStripComments() {
return stripComment;
}
private String escapeComments(final String s) {
final Matcher m = P_COMMENTS.matcher(s);
final StringBuffer buf = new StringBuffer();
if (m.find()) {
final String match = m.group(1); // (.*?)
m.appendReplacement(buf, Matcher.quoteReplacement(""));
}
m.appendTail(buf);
return buf.toString();
}
private String balanceHTML(String s) {
if (alwaysMakeTags) {
//
// try and form html
//
s = regexReplace(P_END_ARROW, "", s);
// 不追加结束标签
s = regexReplace(P_BODY_TO_END, "<$1>", s);
s = regexReplace(P_XML_CONTENT, "$1<$2", s);
} else {
//
// escape stray brackets
//
s = regexReplace(P_STRAY_LEFT_ARROW, "<$1", s);
s = regexReplace(P_STRAY_RIGHT_ARROW, "$1$2><", s);
//
// the last regexp causes '<>' entities to appear
// (we need to do a lookahead assertion so that the last bracket can
// be used in the next pass of the regexp)
//
s = regexReplace(P_BOTH_ARROWS, "", s);
}
return s;
}
private String checkTags(String s) {
Matcher m = P_TAGS.matcher(s);
final StringBuffer buf = new StringBuffer();
while (m.find()) {
String replaceStr = m.group(1);
replaceStr = processTag(replaceStr);
m.appendReplacement(buf, Matcher.quoteReplacement(replaceStr));
}
m.appendTail(buf);
// these get tallied in processTag
// (remember to reset before subsequent calls to filter method)
final StringBuilder sBuilder = new StringBuilder(buf.toString());
for (String key : vTagCounts.keySet()) {
for (int ii = 0; ii < vTagCounts.get(key); ii++) {
sBuilder.append("").append(key).append(">");
}
}
s = sBuilder.toString();
return s;
}
private String processRemoveBlanks(final String s) {
String result = s;
for (String tag : vRemoveBlanks) {
if (!P_REMOVE_PAIR_BLANKS.containsKey(tag)) {
P_REMOVE_PAIR_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?>" + tag + ">"));
}
result = regexReplace(P_REMOVE_PAIR_BLANKS.get(tag), "", result);
if (!P_REMOVE_SELF_BLANKS.containsKey(tag)) {
P_REMOVE_SELF_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?/>"));
}
result = regexReplace(P_REMOVE_SELF_BLANKS.get(tag), "", result);
}
return result;
}
private static String regexReplace(final Pattern regex_pattern, final String replacement, final String s) {
Matcher m = regex_pattern.matcher(s);
return m.replaceAll(replacement);
}
private String processTag(final String s) {
// ending tags
Matcher m = P_END_TAG.matcher(s);
if (m.find()) {
final String name = m.group(1).toLowerCase();
if (allowed(name)) {
if (!inArray(name, vSelfClosingTags)) {
if (vTagCounts.containsKey(name)) {
vTagCounts.put(name, vTagCounts.get(name) - 1);
return "" + name + ">";
}
}
}
}
// starting tags
m = P_START_TAG.matcher(s);
if (m.find()) {
final String name = m.group(1).toLowerCase();
final String body = m.group(2);
String ending = m.group(3);
// debug( "in a starting tag, name='" + name + "'; body='" + body + "'; ending='" + ending + "'" );
if (allowed(name)) {
final StringBuilder params = new StringBuilder();
final Matcher m2 = P_QUOTED_ATTRIBUTES.matcher(body);
final Matcher m3 = P_UNQUOTED_ATTRIBUTES.matcher(body);
final List<String> paramNames = new ArrayList<>();
final List<String> paramValues = new ArrayList<>();
while (m2.find()) {
paramNames.add(m2.group(1)); // ([a-z0-9]+)
paramValues.add(m2.group(3)); // (.*?)
}
while (m3.find()) {
paramNames.add(m3.group(1)); // ([a-z0-9]+)
paramValues.add(m3.group(3)); // ([^\"\\s']+)
}
String paramName, paramValue;
for (int ii = 0; ii < paramNames.size(); ii++) {
paramName = paramNames.get(ii).toLowerCase();
paramValue = paramValues.get(ii);
// debug( "paramName='" + paramName + "'" );
// debug( "paramValue='" + paramValue + "'" );
// debug( "allowed? " + vAllowed.get( name ).contains( paramName ) );
if (allowedAttribute(name, paramName)) {
if (inArray(paramName, vProtocolAtts)) {
paramValue = processParamProtocol(paramValue);
}
params.append(' ').append(paramName).append("=\\\"").append(paramValue).append("\\\"");
}
}
if (inArray(name, vSelfClosingTags)) {
ending = " /";
}
if (inArray(name, vNeedClosingTags)) {
ending = "";
}
if (ending == null || ending.length() < 1) {
if (vTagCounts.containsKey(name)) {
vTagCounts.put(name, vTagCounts.get(name) + 1);
} else {
vTagCounts.put(name, 1);
}
} else {
ending = " /";
}
return "<" + name + params + ending + ">";
} else {
return "";
}
}
// comments
m = P_COMMENT.matcher(s);
if (!stripComment && m.find()) {
return "<" + m.group() + ">";
}
return "";
}
private String processParamProtocol(String s) {
s = decodeEntities(s);
final Matcher m = P_PROTOCOL.matcher(s);
if (m.find()) {
final String protocol = m.group(1);
if (!inArray(protocol, vAllowedProtocols)) {
// bad protocol, turn into local anchor link instead
s = "#" + s.substring(protocol.length() + 1);
if (s.startsWith("#//")) {
s = "#" + s.substring(3);
}
}
}
return s;
}
private String decodeEntities(String s) {
StringBuffer buf = new StringBuffer();
Matcher m = P_ENTITY.matcher(s);
while (m.find()) {
final String match = m.group(1);
final int decimal = Integer.decode(match);
m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal)));
}
m.appendTail(buf);
s = buf.toString();
buf = new StringBuffer();
m = P_ENTITY_UNICODE.matcher(s);
while (m.find()) {
final String match = m.group(1);
final int decimal = Integer.valueOf(match, 16);
m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal)));
}
m.appendTail(buf);
s = buf.toString();
buf = new StringBuffer();
m = P_ENCODE.matcher(s);
while (m.find()) {
final String match = m.group(1);
final int decimal = Integer.valueOf(match, 16);
m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal)));
}
m.appendTail(buf);
s = buf.toString();
s = validateEntities(s);
return s;
}
private String validateEntities(final String s) {
StringBuffer buf = new StringBuffer();
// validate entities throughout the string
Matcher m = P_VALID_ENTITIES.matcher(s);
while (m.find()) {
final String one = m.group(1); // ([^&;]*)
final String two = m.group(2); // (?=(;|&|$))
m.appendReplacement(buf, Matcher.quoteReplacement(checkEntity(one, two)));
}
m.appendTail(buf);
return encodeQuotes(buf.toString());
}
private String encodeQuotes(final String s) {
if (encodeQuotes) {
StringBuffer buf = new StringBuffer();
Matcher m = P_VALID_QUOTES.matcher(s);
while (m.find()) {
final String one = m.group(1); // (>|^)
final String two = m.group(2); // ([^<]+?)
final String three = m.group(3); // (<|$)
// 不替换双引号为",防止json格式无效 regexReplace(P_QUOTE, """, two)
m.appendReplacement(buf, Matcher.quoteReplacement(one + two + three));
}
m.appendTail(buf);
return buf.toString();
} else {
return s;
}
}
private String checkEntity(final String preamble, final String term) {
return ";".equals(term) && isValidEntity(preamble) ? '&' + preamble : "&" + preamble;
}
private boolean isValidEntity(final String entity) {
return inArray(entity, vAllowedEntities);
}
private static boolean inArray(final String s, final String[] array) {
for (String item : array) {
if (item != null && item.equals(s)) {
return true;
}
}
return false;
}
private boolean allowed(final String name) {
return (vAllowed.isEmpty() || vAllowed.containsKey(name)) && !inArray(name, vDisallowed);
}
private boolean allowedAttribute(final String name, final String paramName) {
return allowed(name) && (vAllowed.isEmpty() || vAllowed.get(name).contains(paramName));
}
}
package com.xueyi.common.core.utils.http;
/**
* Http客户端工具类
*
* @author xueyi
*/
public class HttpUtil extends cn.hutool.http.HttpUtil {
}
package com.xueyi.common.core.utils.ip;
import com.xueyi.common.core.utils.core.ArrayUtil;
import com.xueyi.common.core.utils.core.StrUtil;
import com.xueyi.common.core.utils.servlet.ServletUtil;
import jakarta.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* IP工具类
*
* @author xueyi
*/
public class IpUtil {
public final static String REGX_0_255 = "(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]\\d|\\d)";
// 匹配 ip
public final static String REGX_IP = "((" + REGX_0_255 + "\\.){3}" + REGX_0_255 + ")";
public final static String REGX_IP_WILDCARD = "(((\\*\\.){3}\\*)|(" + REGX_0_255 + "(\\.\\*){3})|(" + REGX_0_255 + "\\." + REGX_0_255 + ")(\\.\\*){2}" + "|((" + REGX_0_255 + "\\.){3}\\*))";
// 匹配网段
public final static String REGX_IP_SEG = "(" + REGX_IP + "\\-" + REGX_IP + ")";
/**
* 获取客户端IP
*
* @return IP地址
*/
public static String getIpAddr() {
return getIpAddr(ServletUtil.getRequest());
}
/**
* 获取客户端IP
*
* @param request 请求对象
* @return IP地址
*/
public static String getIpAddr(HttpServletRequest request) {
if (request == null) {
return "unknown";
}
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Forwarded-For");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Real-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : getMultistageReverseProxyIp(ip);
}
/**
* 检查是否为内部IP地址
*
* @param ip IP地址
* @return 结果
*/
public static boolean internalIp(String ip) {
byte[] addr = textToNumericFormatV4(ip);
return internalIp(addr) || "127.0.0.1".equals(ip);
}
/**
* 检查是否为内部IP地址
*
* @param addr byte地址
* @return 结果
*/
private static boolean internalIp(byte[] addr) {
if (ArrayUtil.isEmpty(addr) || addr.length < 2) {
return true;
}
final byte b0 = addr[0];
final byte b1 = addr[1];
// 10.x.x.x/8
final byte SECTION_1 = 0x0A;
// 172.16.x.x/12
final byte SECTION_2 = (byte) 0xAC;
final byte SECTION_3 = (byte) 0x10;
final byte SECTION_4 = (byte) 0x1F;
// 192.168.x.x/16
final byte SECTION_5 = (byte) 0xC0;
final byte SECTION_6 = (byte) 0xA8;
switch (b0) {
case SECTION_1:
return true;
case SECTION_2:
if (b1 >= SECTION_3 && b1 <= SECTION_4) {
return true;
}
case SECTION_5:
if (b1 == SECTION_6) {
return true;
}
default:
return false;
}
}
/**
* 将IPv4地址转换成字节
*
* @param text IPv4地址
* @return byte 字节
*/
public static byte[] textToNumericFormatV4(String text) {
if (text.length() == 0) {
return null;
}
byte[] bytes = new byte[4];
String[] elements = text.split("\\.", -1);
try {
long l;
int i;
switch (elements.length) {
case 1:
l = Long.parseLong(elements[0]);
if ((l < 0L) || (l > 4294967295L)) {
return null;
}
bytes[0] = (byte) (int) (l >> 24 & 0xFF);
bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF);
bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);
bytes[3] = (byte) (int) (l & 0xFF);
break;
case 2:
l = Integer.parseInt(elements[0]);
if ((l < 0L) || (l > 255L)) {
return null;
}
bytes[0] = (byte) (int) (l & 0xFF);
l = Integer.parseInt(elements[1]);
if ((l < 0L) || (l > 16777215L)) {
return null;
}
bytes[1] = (byte) (int) (l >> 16 & 0xFF);
bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);
bytes[3] = (byte) (int) (l & 0xFF);
break;
case 3:
for (i = 0; i < 2; ++i) {
l = Integer.parseInt(elements[i]);
if ((l < 0L) || (l > 255L)) {
return null;
}
bytes[i] = (byte) (int) (l & 0xFF);
}
l = Integer.parseInt(elements[2]);
if ((l < 0L) || (l > 65535L)) {
return null;
}
bytes[2] = (byte) (int) (l >> 8 & 0xFF);
bytes[3] = (byte) (int) (l & 0xFF);
break;
case 4:
for (i = 0; i < 4; ++i) {
l = Integer.parseInt(elements[i]);
if ((l < 0L) || (l > 255L)) {
return null;
}
bytes[i] = (byte) (int) (l & 0xFF);
}
break;
default:
return null;
}
} catch (NumberFormatException e) {
return null;
}
return bytes;
}
/**
* 获取IP地址
*
* @return 本地IP地址
*/
public static String getHostIp() {
try {
return InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException ignored) {
}
return "127.0.0.1";
}
/**
* 获取主机名
*
* @return 本地主机名
*/
public static String getHostName() {
try {
return InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException ignored) {
}
return "未知";
}
/**
* 从多级反向代理中获得第一个非unknown IP地址
*
* @param ip 获得的IP地址
* @return 第一个非unknown IP地址
*/
public static String getMultistageReverseProxyIp(String ip) {
// 多级反向代理检测
if (ip != null && ip.indexOf(StrUtil.COMMA) > 0) {
final String[] ips = ip.trim().split(StrUtil.COMMA);
for (String subIp : ips) {
if (!isUnknown(subIp)) {
ip = subIp;
break;
}
}
}
return StrUtil.sub(ip, 0, 255);
}
/**
* 检测给定字符串是否为未知,多用于检测HTTP请求相关
*
* @param checkString 被检测的字符串
* @return 是否未知
*/
public static boolean isUnknown(String checkString) {
return StrUtil.isBlank(checkString) || StrUtil.UNKNOWN.equalsIgnoreCase(checkString);
}
/**
* 是否为IP
*/
public static boolean isIP(String ip) {
return StrUtil.isNotBlank(ip) && ip.matches(REGX_IP);
}
/**
* 是否为IP,或 *为间隔的通配符地址
*/
public static boolean isIpWildCard(String ip) {
return StrUtil.isNotBlank(ip) && ip.matches(REGX_IP_WILDCARD);
}
/**
* 检测参数是否在ip通配符里
*/
public static boolean ipIsInWildCardNoCheck(String ipWildCard, String ip) {
String[] s1 = ipWildCard.split("\\.");
String[] s2 = ip.split("\\.");
boolean isMatchedSeg = true;
for (int i = 0; i < s1.length && !s1[i].equals("*"); i++) {
if (!s1[i].equals(s2[i])) {
isMatchedSeg = false;
break;
}
}
return isMatchedSeg;
}
/**
* 是否为特定格式如:“10.10.10.1-10.10.10.99”的ip段字符串
*/
public static boolean isIPSegment(String ipSeg) {
return StrUtil.isNotBlank(ipSeg) && ipSeg.matches(REGX_IP_SEG);
}
/**
* 判断ip是否在指定网段中
*/
public static boolean ipIsInNetNoCheck(String iparea, String ip) {
int idx = iparea.indexOf('-');
String[] sips = iparea.substring(0, idx).split("\\.");
String[] sipe = iparea.substring(idx + 1).split("\\.");
String[] sipt = ip.split("\\.");
long ips = 0L, ipe = 0L, ipt = 0L;
for (int i = 0; i < 4; ++i) {
ips = ips << 8 | Integer.parseInt(sips[i]);
ipe = ipe << 8 | Integer.parseInt(sipe[i]);
ipt = ipt << 8 | Integer.parseInt(sipt[i]);
}
if (ips > ipe) {
long t = ips;
ips = ipe;
ipe = t;
}
return ips <= ipt && ipt <= ipe;
}
/**
* 校验ip是否符合过滤串规则
*
* @param filter 过滤IP列表,支持后缀'*'通配,支持网段如:`10.10.10.1-10.10.10.99`
* @param ip 校验IP地址
* @return boolean 结果
*/
public static boolean isMatchedIp(String filter, String ip) {
if (StrUtil.isEmpty(filter) || StrUtil.isEmpty(ip)) {
return false;
}
String[] ips = filter.split(StrUtil.SEMICOLON);
for (String iStr : ips) {
if (isIP(iStr) && iStr.equals(ip)) {
return true;
} else if (isIpWildCard(iStr) && ipIsInWildCardNoCheck(iStr, ip)) {
return true;
} else if (isIPSegment(iStr) && ipIsInNetNoCheck(iStr, ip)) {
return true;
}
}
return false;
}
}
package com.xueyi.common.core.utils.jwt;
import com.xueyi.common.core.constant.basic.SecurityConstants;
import io.jsonwebtoken.Claims;
/**
* Jwt工具类 | 会员端
*
* @author xueyi
*/
public class JwtMemberUtil extends JwtUtil {
/**
* 根据令牌获取平台应用Id
*
* @param token 令牌
* @return 平台应用Id
*/
public static String getApplicationId(String token) {
Claims claims = parseToken(token);
return getApplicationId(claims);
}
/**
* 根据令牌获取平台应用Id
*
* @param claims 身份信息
* @return 平台应用Id
*/
public static String getApplicationId(Claims claims) {
return getValue(claims, SecurityConstants.MemberSecurity.APPLICATION_ID.getCode());
}
/**
* 根据令牌获取应用AppId
*
* @param token 令牌
* @return 应用AppId
*/
public static String getAppId(String token) {
Claims claims = parseToken(token);
return getAppId(claims);
}
/**
* 根据令牌获取应用AppId
*
* @param claims 身份信息
* @return 应用AppId
*/
public static String getAppId(Claims claims) {
return getValue(claims, SecurityConstants.MemberSecurity.APP_ID.getCode());
}
}
package com.xueyi.common.core.utils.jwt;
import com.xueyi.common.core.constant.basic.SecurityConstants;
import io.jsonwebtoken.Claims;
/**
* Jwt工具类 | 平台端
*
* @author xueyi
*/
public class JwtPlatformUtil extends JwtUtil {
/**
* 根据令牌获取平台应用Id
*
* @param token 令牌
* @return 平台应用Id
*/
public static String getAppId(String token) {
Claims claims = parseToken(token);
return getAppId(claims);
}
/**
* 根据令牌获取平台应用Id
*
* @param claims 身份信息
* @return 平台应用Id
*/
public static String getAppId(Claims claims) {
return getValue(claims, SecurityConstants.PlatformSecurity.APP_ID.getCode());
}
}
package com.xueyi.common.core.utils.jwt;
import com.xueyi.common.core.constant.basic.SecurityConstants;
import com.xueyi.common.core.constant.basic.TokenConstants;
import com.xueyi.common.core.utils.core.ConvertUtil;
import com.xueyi.common.core.utils.core.StrUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import java.util.Map;
/**
* Jwt工具类
*
* @author xueyi
*/
public class JwtUtil {
public static String secret = TokenConstants.SECRET;
/**
* 从令牌中获取数据声明
*
* @param token 令牌
* @return 数据声明
*/
public static Claims parseToken(String token) {
return Jwts.parserBuilder().setSigningKey(Keys.hmacShaKeyFor(secret.getBytes())).build().parseClaimsJws(token).getBody();
}
/**
* 从数据声明生成令牌
*
* @param claims 数据声明
* @return 令牌
*/
public static String createToken(Map<String, Object> claims) {
return Jwts.builder().setClaims(claims).signWith(Keys.hmacShaKeyFor(secret.getBytes()), SignatureAlgorithm.HS512).compact();
}
/**
* 根据令牌获取企业Id
*
* @param token 令牌
* @return 企业Id
*/
public static String getEnterpriseId(String token) {
Claims claims = parseToken(token);
return getEnterpriseId(claims);
}
/**
* 根据身份信息获取企业Id
*
* @param claims 身份信息
* @return 企业Id
*/
public static String getEnterpriseId(Claims claims) {
return getValue(claims, SecurityConstants.BaseSecurity.ENTERPRISE_ID.getCode());
}
/**
* 根据令牌获取企业账号
*
* @param token 令牌
* @return 企业账号
*/
public static String getEnterpriseName(String token) {
Claims claims = parseToken(token);
return getEnterpriseName(claims);
}
/**
* 根据身份信息获取企业账号
*
* @param claims 身份信息
* @return 企业账号
*/
public static String getEnterpriseName(Claims claims) {
return getValue(claims, SecurityConstants.BaseSecurity.ENTERPRISE_NAME.getCode());
}
/**
* 根据令牌获取企业类型
*
* @param token 令牌
* @return 企业类型
*/
public static String getIsLessor(String token) {
Claims claims = parseToken(token);
return getIsLessor(claims);
}
/**
* 根据身份信息获取企业类型
*
* @param claims 身份信息
* @return 企业类型
*/
public static String getIsLessor(Claims claims) {
return getValue(claims, SecurityConstants.BaseSecurity.IS_LESSOR.getCode());
}
/**
* 根据令牌获取用户Id
*
* @param token 令牌
* @return 用户Id
*/
public static String getUserId(String token) {
Claims claims = parseToken(token);
return getUserId(claims);
}
/**
* 根据身份信息获取用户Id
*
* @param claims 身份信息
* @return 用户Id
*/
public static String getUserId(Claims claims) {
return getValue(claims, SecurityConstants.BaseSecurity.USER_ID.getCode());
}
/**
* 根据令牌获取用户账号
*
* @param token 令牌
* @return 用户账号
*/
public static String getUserName(String token) {
Claims claims = parseToken(token);
return getUserName(claims);
}
/**
* 根据身份信息获取用户账号
*
* @param claims 身份信息
* @return 用户账号
*/
public static String getUserName(Claims claims) {
return getValue(claims, SecurityConstants.BaseSecurity.USER_NAME.getCode());
}
/**
* 根据令牌获取用户账号
*
* @param token 令牌
* @return 用户账号
*/
public static String getNickName(String token) {
Claims claims = parseToken(token);
return getNickName(claims);
}
/**
* 根据身份信息获取用户账号
*
* @param claims 身份信息
* @return 用户账号
*/
public static String getNickName(Claims claims) {
return getValue(claims, SecurityConstants.BaseSecurity.NICK_NAME.getCode());
}
/**
* 根据令牌获取用户类型
*
* @param token 令牌
* @return 用户类型
*/
public static String getUserType(String token) {
Claims claims = parseToken(token);
return getUserType(claims);
}
/**
* 根据身份信息获取用户类型
*
* @param claims 身份信息
* @return 用户类型
*/
public static String getUserType(Claims claims) {
return getValue(claims, SecurityConstants.BaseSecurity.USER_TYPE.getCode());
}
/**
* 根据令牌获取账户类型
*
* @param token 令牌
* @return 账户类型
*/
public static String getAccountType(String token) {
Claims claims = parseToken(token);
return getAccountType(claims);
}
/**
* 根据令牌获取账户类型
*
* @param claims 身份信息
* @return 账户类型
*/
public static String getAccountType(Claims claims) {
return getValue(claims, SecurityConstants.BaseSecurity.ACCOUNT_TYPE.getCode());
}
/**
* 根据令牌获取租户策略源
*
* @param token 令牌
* @return 租户策略源
*/
public static String getSourceName(String token) {
Claims claims = parseToken(token);
return getSourceName(claims);
}
/**
* 根据令牌获取租户策略源
*
* @param claims 身份信息
* @return 租户策略源
*/
public static String getSourceName(Claims claims) {
return getValue(claims, SecurityConstants.BaseSecurity.SOURCE_NAME.getCode());
}
/**
* 根据令牌获取用户令牌
*
* @param token 令牌
* @return 用户令牌
*/
public static String getUserKey(String token) {
Claims claims = parseToken(token);
return getUserKey(claims);
}
/**
* 根据令牌获取用户令牌
*
* @param claims 身份信息
* @return 用户令牌
*/
public static String getUserKey(Claims claims) {
String accessToken = getValue(claims, SecurityConstants.BaseSecurity.REFRESH_TOKEN.getCode());
if (StrUtil.isNotEmpty(accessToken) && StrUtil.startWith(accessToken, TokenConstants.PREFIX))
return StrUtil.replaceFirst(accessToken, TokenConstants.PREFIX, StrUtil.EMPTY);
return null;
}
/**
* 根据令牌获取访问令牌
*
* @param token 令牌
* @return 访问令牌
*/
public static String getAccessKey(String token) {
Claims claims = parseToken(token);
return getUserKey(claims);
}
/**
* 根据令牌获取访问令牌
*
* @param claims 身份信息
* @return 访问令牌
*/
public static String getAccessKey(Claims claims) {
return getValue(claims, SecurityConstants.BaseSecurity.ACCESS_TOKEN.getCode());
}
/**
* 根据身份信息获取键值
*
* @param claims 身份信息
* @param key 键
* @return 值
*/
public static String getValue(Claims claims, String key) {
return ConvertUtil.toStr(claims.get(key), StrUtil.EMPTY);
}
}
package com.xueyi.common.core.utils.page;
import com.github.pagehelper.PageHelper;
import com.xueyi.common.core.utils.core.ObjectUtil;
import com.xueyi.common.core.utils.sql.SqlUtil;
import com.xueyi.common.core.web.page.PageDomain;
import com.xueyi.common.core.web.page.TableSupport;
/**
* 分页工具类
*
* @author xueyi
*/
public class PageUtil extends PageHelper {
/**
* 设置请求分页数据
*/
public static void startPage() {
PageDomain pageDomain = TableSupport.buildPageRequest();
Integer pageNum = pageDomain.getPageNum();
Integer pageSize = pageDomain.getPageSize();
if (ObjectUtil.isAllNotEmpty(pageNum, pageSize)) {
String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());
Boolean reasonable = pageDomain.getReasonable();
PageHelper.startPage(pageNum, pageSize, orderBy).setReasonable(reasonable);
}
}
/**
* 清理分页的线程变量
*/
public static void clearPage() {
PageHelper.clearPage();
}
}
package com.xueyi.common.core.utils.poi;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Workbook;
/**
* Excel数据格式处理适配器
*
* @author xueyi
*/
public interface ExcelHandlerAdapter {
/**
* 格式化
*
* @param value 单元格数据值
* @param args excel注解args参数组
* @param cell 单元格对象
* @param wb 工作簿对象
* @return 处理后的值
*/
Object format(Object value, String[] args, Cell cell, Workbook wb);
}
package com.xueyi.common.core.utils.poi;
import com.xueyi.common.core.annotation.Excel;
import com.xueyi.common.core.annotation.Excel.ColumnType;
import com.xueyi.common.core.annotation.Excel.Type;
import com.xueyi.common.core.annotation.Excels;
import com.xueyi.common.core.exception.UtilException;
import com.xueyi.common.core.utils.DateUtil;
import com.xueyi.common.core.utils.core.CollUtil;
import com.xueyi.common.core.utils.core.ConvertUtil;
import com.xueyi.common.core.utils.core.ObjectUtil;
import com.xueyi.common.core.utils.core.ReflectUtil;
import com.xueyi.common.core.utils.core.StrUtil;
import com.xueyi.common.core.utils.file.FileTypeUtil;
import com.xueyi.common.core.utils.file.ImageUtil;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.RegExUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.apache.poi.util.IOUtils;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
import org.apache.poi.xssf.usermodel.XSSFDataValidation;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Excel相关处理
*
* @author xueyi
*/
@Slf4j
public class ExcelUtil<T> {
public static final String FORMULA_REGEX_STR = "=|-|\\+|@";
public static final String[] FORMULA_STR = {"=", "-", "+", "@"};
/**
* Excel sheet最大行数,默认65536
*/
public static final int sheetSize = 65536;
/**
* 工作表名称
*/
private String sheetName;
/**
* 导出类型(EXPORT:导出数据;IMPORT:导入模板)
*/
private Type type;
/**
* 工作薄对象
*/
private Workbook wb;
/**
* 工作表对象
*/
private Sheet sheet;
/**
* 样式列表
*/
private Map<String, CellStyle> styles;
/**
* 导入导出数据列表
*/
private List<T> list;
/**
* 注解列表
*/
private List<Object[]> fields;
/**
* 当前行号
*/
private int rownum;
/**
* 标题
*/
private String title;
/**
* 最大高度
*/
private short maxHeight;
/**
* 合并后最后行数
*/
private int subMergedLastRowNum = 0;
/**
* 合并后开始行数
*/
private int subMergedFirstRowNum = 1;
/**
* 对象的子列表方法
*/
private Method subMethod;
/**
* 对象的子列表属性
*/
private List<Field> subFields;
/**
* 统计列表
*/
private Map<Integer, Double> statistics = new HashMap<>();
/**
* 数字格式
*/
private static final DecimalFormat DOUBLE_FORMAT = new DecimalFormat("######0.00");
/**
* 实体对象
*/
public Class<T> clazz;
/**
* 需要排除列属性
*/
public String[] excludeFields;
public ExcelUtil(Class<T> clazz) {
this.clazz = clazz;
}
/**
* 隐藏Excel中列属性
*
* @param fields 列属性名 示例[单个"name"/多个"id","name"]
*/
public void hideColumn(String... fields) {
this.excludeFields = fields;
}
public void init(List<T> list, String sheetName, String title, Type type) {
if (list == null) {
list = new ArrayList<T>();
}
this.list = list;
this.sheetName = sheetName;
this.type = type;
this.title = title;
createExcelField();
createWorkbook();
createTitle();
createSubHead();
}
/**
* 创建excel第一行标题
*/
public void createTitle() {
if (StrUtil.isNotEmpty(title)) {
subMergedFirstRowNum++;
subMergedLastRowNum++;
int titleLastCol = this.fields.size() - 1;
if (isSubList()) {
titleLastCol = titleLastCol + subFields.size() - 1;
}
Row titleRow = sheet.createRow(rownum == 0 ? rownum++ : 0);
titleRow.setHeightInPoints(30);
Cell titleCell = titleRow.createCell(0);
titleCell.setCellStyle(styles.get("title"));
titleCell.setCellValue(title);
sheet.addMergedRegion(new CellRangeAddress(titleRow.getRowNum(), titleRow.getRowNum(), titleRow.getRowNum(), titleLastCol));
}
}
/**
* 创建对象的子列表名称
*/
public void createSubHead() {
if (isSubList()) {
subMergedFirstRowNum++;
subMergedLastRowNum++;
Row subRow = sheet.createRow(rownum);
int excelNum = 0;
for (Object[] objects : fields) {
Excel attr = (Excel) objects[1];
Cell headCell1 = subRow.createCell(excelNum);
headCell1.setCellValue(attr.name());
headCell1.setCellStyle(styles.get(StrUtil.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor())));
excelNum++;
}
int headFirstRow = excelNum - 1;
int headLastRow = headFirstRow + subFields.size() - 1;
if (headLastRow > headFirstRow) {
sheet.addMergedRegion(new CellRangeAddress(rownum, rownum, headFirstRow, headLastRow));
}
rownum++;
}
}
/**
* 对excel表单默认第一个索引名转换成list
*
* @param is 输入流
* @return 转换后集合
*/
public List<T> importExcel(InputStream is) {
List<T> list;
try {
list = importExcel(is, 0);
} catch (Exception e) {
log.error("导入Excel异常{}", e.getMessage());
throw new UtilException(e.getMessage());
} finally {
IOUtils.closeQuietly(is);
}
return list;
}
/**
* 对excel表单默认第一个索引名转换成list
*
* @param is 输入流
* @param titleNum 标题占用行数
* @return 转换后集合
*/
public List<T> importExcel(InputStream is, int titleNum) throws Exception {
return importExcel(StrUtil.EMPTY, is, titleNum);
}
/**
* 对excel表单指定表格索引名转换成list
*
* @param sheetName 表格索引名
* @param titleNum 标题占用行数
* @param is 输入流
* @return 转换后集合
*/
public List<T> importExcel(String sheetName, InputStream is, int titleNum) throws Exception {
this.type = Type.IMPORT;
this.wb = WorkbookFactory.create(is);
List<T> list = new ArrayList<T>();
// 如果指定sheet名,则取指定sheet中的内容 否则默认指向第1个sheet
Sheet sheet = StrUtil.isNotEmpty(sheetName) ? wb.getSheet(sheetName) : wb.getSheetAt(0);
if (sheet == null) {
throw new IOException("文件sheet不存在");
}
// 获取最后一个非空行的行下标,比如总行数为n,则返回的为n-1
int rows = sheet.getLastRowNum();
if (rows > 0) {
// 定义一个map用于存放excel列的序号和field.
Map<String, Integer> cellMap = new HashMap<String, Integer>();
// 获取表头
Row heard = sheet.getRow(titleNum);
for (int i = 0; i < heard.getPhysicalNumberOfCells(); i++) {
Cell cell = heard.getCell(i);
if (ObjectUtil.isNotNull(cell)) {
String value = this.getCellValue(heard, i).toString();
cellMap.put(value, i);
} else {
cellMap.put(null, i);
}
}
// 有数据时才处理 得到类的所有field.
List<Object[]> fields = this.getFields();
Map<Integer, Object[]> fieldsMap = new HashMap<Integer, Object[]>();
for (Object[] objects : fields) {
Excel attr = (Excel) objects[1];
Integer column = cellMap.get(attr.name());
if (column != null) {
fieldsMap.put(column, objects);
}
}
for (int i = titleNum + 1; i <= rows; i++) {
// 从第2行开始取数据,默认第一行是表头.
Row row = sheet.getRow(i);
// 判断当前行是否是空行
if (isRowEmpty(row)) {
continue;
}
T entity = null;
for (Map.Entry<Integer, Object[]> entry : fieldsMap.entrySet()) {
Object val = this.getCellValue(row, entry.getKey());
// 如果不存在实例则新建.
entity = (entity == null ? clazz.newInstance() : entity);
// 从map中得到对应列的field.
Field field = (Field) entry.getValue()[0];
Excel attr = (Excel) entry.getValue()[1];
// 取得类型,并根据对象类型设置值.
Class<?> fieldType = field.getType();
if (String.class == fieldType) {
String s = ConvertUtil.toStr(val);
if (StrUtil.endWith(s, ".0")) {
val = StrUtil.subBefore(s, ".0");
} else {
String dateFormat = field.getAnnotation(Excel.class).dateFormat();
if (StrUtil.isNotEmpty(dateFormat)) {
val = parseDateToStr(dateFormat, val);
} else {
val = ConvertUtil.toStr(val);
}
}
} else if ((Integer.TYPE == fieldType || Integer.class == fieldType) && StrUtil.isNumeric(ConvertUtil.toStr(val))) {
val = ConvertUtil.toInt(val);
} else if ((Long.TYPE == fieldType || Long.class == fieldType) && StrUtil.isNumeric(ConvertUtil.toStr(val))) {
val = ConvertUtil.toLong(val);
} else if (Double.TYPE == fieldType || Double.class == fieldType) {
val = ConvertUtil.toDouble(val);
} else if (Float.TYPE == fieldType || Float.class == fieldType) {
val = ConvertUtil.toFloat(val);
} else if (BigDecimal.class == fieldType) {
val = ConvertUtil.toBigDecimal(val);
} else if (Date.class == fieldType) {
if (val instanceof String) {
val = DateUtil.parseDate(val);
} else if (val instanceof Double) {
val = org.apache.poi.ss.usermodel.DateUtil.getJavaDate((Double) val);
}
} else if (Boolean.TYPE == fieldType || Boolean.class == fieldType) {
val = ConvertUtil.toBool(val, false);
}
if (ObjectUtil.isNotNull(fieldType)) {
String propertyName = field.getName();
if (StrUtil.isNotEmpty(attr.targetAttr())) {
propertyName = field.getName() + "." + attr.targetAttr();
}
if (StrUtil.isNotEmpty(attr.readConverterExp())) {
val = reverseByExp(ConvertUtil.toStr(val), attr.readConverterExp(), attr.separator());
} else if (!attr.handler().equals(ExcelHandlerAdapter.class)) {
val = dataFormatHandlerAdapter(val, attr, null);
}
ReflectUtil.setFieldValue(entity, propertyName, val);
}
}
list.add(entity);
}
}
return list;
}
/**
* 对list数据源将其里面的数据导入到excel表单
*
* @param response 返回数据
* @param list 导出数据集合
* @param sheetName 工作表的名称
*/
public void exportExcel(HttpServletResponse response, List<T> list, String sheetName) {
exportExcel(response, list, sheetName, StrUtil.EMPTY);
}
/**
* 对list数据源将其里面的数据导入到excel表单
*
* @param response 返回数据
* @param list 导出数据集合
* @param sheetName 工作表的名称
* @param title 标题
*/
public void exportExcel(HttpServletResponse response, List<T> list, String sheetName, String title) {
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
this.init(list, sheetName, title, Type.EXPORT);
exportExcel(response);
}
/**
* 对list数据源将其里面的数据导入到excel表单
*
* @param sheetName 工作表的名称
*/
public void importTemplateExcel(HttpServletResponse response, String sheetName) {
importTemplateExcel(response, sheetName, StrUtil.EMPTY);
}
/**
* 对list数据源将其里面的数据导入到excel表单
*
* @param sheetName 工作表的名称
* @param title 标题
*/
public void importTemplateExcel(HttpServletResponse response, String sheetName, String title) {
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
this.init(null, sheetName, title, Type.IMPORT);
exportExcel(response);
}
/**
* 对list数据源将其里面的数据导入到excel表单
*/
public void exportExcel(HttpServletResponse response) {
try {
writeSheet();
wb.write(response.getOutputStream());
} catch (Exception e) {
log.error("导出Excel异常{}", e.getMessage());
} finally {
IOUtils.closeQuietly(wb);
}
}
/**
* 创建写入数据到Sheet
*/
public void writeSheet() {
// 取出一共有多少个sheet.
int sheetNo = Math.max(1, (int) Math.ceil(list.size() * 1.0 / sheetSize));
for (int index = 0; index < sheetNo; index++) {
createSheet(sheetNo, index);
// 产生一行
Row row = sheet.createRow(rownum);
int column = 0;
// 写入各个字段的列头名称
for (Object[] os : fields) {
Field field = (Field) os[0];
Excel excel = (Excel) os[1];
if (Collection.class.isAssignableFrom(field.getType())) {
for (Field subField : subFields) {
Excel subExcel = subField.getAnnotation(Excel.class);
this.createHeadCell(subExcel, row, column++);
}
} else {
this.createHeadCell(excel, row, column++);
}
}
if (Type.EXPORT.equals(type)) {
fillExcelData(index, row);
addStatisticsRow();
}
}
}
/**
* 填充excel数据
*
* @param index 序号
* @param row 单元格行
*/
@SuppressWarnings("unchecked")
public void fillExcelData(int index, Row row) {
int startNo = index * sheetSize;
int endNo = Math.min(startNo + sheetSize, list.size());
int rowNo = (1 + rownum) - startNo;
for (int i = startNo; i < endNo; i++) {
rowNo = isSubList() ? (i > 1 ? rowNo + 1 : rowNo + i) : i + 1 + rownum - startNo;
row = sheet.createRow(rowNo);
// 得到导出对象.
T vo = (T) list.get(i);
Collection<?> subList = null;
if (isSubList()) {
if (isSubListValue(vo)) {
subList = getListCellValue(vo);
subMergedLastRowNum = subMergedLastRowNum + subList.size();
} else {
subMergedFirstRowNum++;
subMergedLastRowNum++;
}
}
int column = 0;
for (Object[] os : fields) {
Field field = (Field) os[0];
Excel excel = (Excel) os[1];
if (Collection.class.isAssignableFrom(field.getType()) && CollUtil.isNotEmpty(subList)) {
boolean subFirst = false;
for (Object obj : subList) {
if (subFirst) {
rowNo++;
row = sheet.createRow(rowNo);
}
List<Field> subFields = FieldUtils.getFieldsListWithAnnotation(obj.getClass(), Excel.class);
int subIndex = 0;
for (Field subField : subFields) {
if (subField.isAnnotationPresent(Excel.class)) {
subField.setAccessible(true);
Excel attr = subField.getAnnotation(Excel.class);
this.addCell(attr, row, (T) obj, subField, column + subIndex);
}
subIndex++;
}
subFirst = true;
}
this.subMergedFirstRowNum = this.subMergedFirstRowNum + subList.size();
} else {
this.addCell(excel, row, vo, field, column++);
}
}
}
}
/**
* 创建表格样式
*
* @param wb 工作薄对象
* @return 样式列表
*/
private Map<String, CellStyle> createStyles(Workbook wb) {
// 写入各条记录,每条记录对应excel表中的一行
Map<String, CellStyle> styles = new HashMap<String, CellStyle>();
CellStyle style = wb.createCellStyle();
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
Font titleFont = wb.createFont();
titleFont.setFontName("Arial");
titleFont.setFontHeightInPoints((short) 16);
titleFont.setBold(true);
style.setFont(titleFont);
styles.put("title", style);
style = wb.createCellStyle();
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
style.setBorderRight(BorderStyle.THIN);
style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
style.setBorderLeft(BorderStyle.THIN);
style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
style.setBorderTop(BorderStyle.THIN);
style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
style.setBorderBottom(BorderStyle.THIN);
style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
Font dataFont = wb.createFont();
dataFont.setFontName("Arial");
dataFont.setFontHeightInPoints((short) 10);
style.setFont(dataFont);
styles.put("data", style);
style = wb.createCellStyle();
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
Font totalFont = wb.createFont();
totalFont.setFontName("Arial");
totalFont.setFontHeightInPoints((short) 10);
style.setFont(totalFont);
styles.put("total", style);
styles.putAll(annotationHeaderStyles(wb, styles));
styles.putAll(annotationDataStyles(wb));
return styles;
}
/**
* 根据Excel注解创建表格头样式
*
* @param wb 工作薄对象
* @return 自定义样式列表
*/
private Map<String, CellStyle> annotationHeaderStyles(Workbook wb, Map<String, CellStyle> styles) {
Map<String, CellStyle> headerStyles = new HashMap<String, CellStyle>();
for (Object[] os : fields) {
Excel excel = (Excel) os[1];
String key = StrUtil.format("header_{}_{}", excel.headerColor(), excel.headerBackgroundColor());
if (!headerStyles.containsKey(key)) {
CellStyle style = wb.createCellStyle();
style.cloneStyleFrom(styles.get("data"));
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
style.setFillForegroundColor(excel.headerBackgroundColor().index);
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
Font headerFont = wb.createFont();
headerFont.setFontName("Arial");
headerFont.setFontHeightInPoints((short) 10);
headerFont.setBold(true);
headerFont.setColor(excel.headerColor().index);
style.setFont(headerFont);
headerStyles.put(key, style);
}
}
return headerStyles;
}
/**
* 根据Excel注解创建表格列样式
*
* @param wb 工作薄对象
* @return 自定义样式列表
*/
private Map<String, CellStyle> annotationDataStyles(Workbook wb) {
Map<String, CellStyle> styles = new HashMap<String, CellStyle>();
for (Object[] os : fields) {
Excel excel = (Excel) os[1];
String key = StrUtil.format("data_{}_{}_{}", excel.align(), excel.color(), excel.backgroundColor());
if (!styles.containsKey(key)) {
CellStyle style = wb.createCellStyle();
style.setAlignment(excel.align());
style.setVerticalAlignment(VerticalAlignment.CENTER);
style.setBorderRight(BorderStyle.THIN);
style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
style.setBorderLeft(BorderStyle.THIN);
style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
style.setBorderTop(BorderStyle.THIN);
style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
style.setBorderBottom(BorderStyle.THIN);
style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
style.setFillForegroundColor(excel.backgroundColor().getIndex());
Font dataFont = wb.createFont();
dataFont.setFontName("Arial");
dataFont.setFontHeightInPoints((short) 10);
dataFont.setColor(excel.color().index);
style.setFont(dataFont);
styles.put(key, style);
}
}
return styles;
}
/**
* 创建单元格
*/
public Cell createHeadCell(Excel attr, Row row, int column) {
// 创建列
Cell cell = row.createCell(column);
// 写入列信息
cell.setCellValue(attr.name());
setDataValidation(attr, row, column);
cell.setCellStyle(styles.get(StrUtil.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor())));
if (isSubList()) {
// 填充默认样式,防止合并单元格样式失效
sheet.setDefaultColumnStyle(column, styles.get(StrUtil.format("data_{}_{}_{}", attr.align(), attr.color(), attr.backgroundColor())));
if (attr.needMerge()) {
sheet.addMergedRegion(new CellRangeAddress(rownum - 1, rownum, column, column));
}
}
return cell;
}
/**
* 设置单元格信息
*
* @param value 单元格值
* @param attr 注解相关
* @param cell 单元格信息
*/
public void setCellVo(Object value, Excel attr, Cell cell) {
if (ColumnType.STRING == attr.cellType()) {
String cellValue = ConvertUtil.toStr(value);
// 对于任何以表达式触发字符 =-+@开头的单元格,直接使用tab字符作为前缀,防止CSV注入。
if (StrUtil.startWithAny(cellValue, FORMULA_STR)) {
cellValue = RegExUtils.replaceFirst(cellValue, FORMULA_REGEX_STR, "\t$0");
}
if (value instanceof Collection && StrUtil.equals(StrUtil.BRACKET, cellValue)) {
cellValue = StrUtil.EMPTY;
}
cell.setCellValue(ObjectUtil.isNull(cellValue) ? attr.defaultValue() : cellValue + attr.suffix());
} else if (ColumnType.NUMERIC == attr.cellType()) {
if (ObjectUtil.isNotNull(value)) {
cell.setCellValue(StrUtil.contains(ConvertUtil.toStr(value), StrUtil.DOT) ? ConvertUtil.toDouble(value) : ConvertUtil.toInt(value));
}
} else if (ColumnType.IMAGE == attr.cellType()) {
ClientAnchor anchor = new XSSFClientAnchor(0, 0, 0, 0, (short) cell.getColumnIndex(), cell.getRow()
.getRowNum(), (short) (cell.getColumnIndex() + 1), cell.getRow().getRowNum() + 1);
String imagePath = ConvertUtil.toStr(value);
if (StrUtil.isNotEmpty(imagePath)) {
byte[] data = ImageUtil.getImage(imagePath);
getDrawingPatriarch(cell.getSheet()).createPicture(anchor,
cell.getSheet().getWorkbook().addPicture(data, getImageType(data)));
}
}
}
/**
* 获取画布
*/
public static Drawing<?> getDrawingPatriarch(Sheet sheet) {
if (sheet.getDrawingPatriarch() == null) {
sheet.createDrawingPatriarch();
}
return sheet.getDrawingPatriarch();
}
/**
* 获取图片类型,设置图片插入类型
*/
public int getImageType(byte[] value) {
String type = FileTypeUtil.getFileExtendName(value);
if ("JPG".equalsIgnoreCase(type)) {
return Workbook.PICTURE_TYPE_JPEG;
} else if ("PNG".equalsIgnoreCase(type)) {
return Workbook.PICTURE_TYPE_PNG;
}
return Workbook.PICTURE_TYPE_JPEG;
}
/**
* 创建表格样式
*/
public void setDataValidation(Excel attr, Row row, int column) {
if (attr.name().contains("注:")) {
sheet.setColumnWidth(column, 6000);
} else {
// 设置列宽
sheet.setColumnWidth(column, (int) ((attr.width() + 0.72) * 256));
}
if (StrUtil.isNotEmpty(attr.prompt()) || attr.combo().length > 0) {
// 提示信息或只能选择不能输入的列内容.
if (attr.combo().length > 15 || StringUtils.join(attr.combo()).length() > 255) {
// 如果下拉数大于15或字符串长度大于255,则使用一个新sheet存储,避免生成的模板下拉值获取不到
setXSSFValidationWithHidden(sheet, attr.combo(), attr.prompt(), 1, 100, column, column);
} else {
// 提示信息或只能选择不能输入的列内容.
setPromptOrValidation(sheet, attr.combo(), attr.prompt(), 1, 100, column, column);
}
}
}
/**
* 添加单元格
*/
public Cell addCell(Excel attr, Row row, T vo, Field field, int column) {
Cell cell = null;
try {
// 设置行高
row.setHeight(maxHeight);
// 根据Excel中设置情况决定是否导出,有些情况需要保持为空,希望用户填写这一列.
if (attr.isExport()) {
// 创建cell
cell = row.createCell(column);
if (isSubListValue(vo) && getListCellValue(vo).size() > 1 && attr.needMerge()) {
CellRangeAddress cellAddress = new CellRangeAddress(subMergedFirstRowNum, subMergedLastRowNum, column, column);
sheet.addMergedRegion(cellAddress);
}
cell.setCellStyle(styles.get(StrUtil.format("data_{}_{}_{}", attr.align(), attr.color(), attr.backgroundColor())));
// 用于读取对象中的属性
Object value = getTargetValue(vo, field, attr);
String dateFormat = attr.dateFormat();
String readConverterExp = attr.readConverterExp();
String separator = attr.separator();
if (StrUtil.isNotEmpty(dateFormat) && ObjectUtil.isNotNull(value)) {
cell.setCellValue(parseDateToStr(dateFormat, value));
} else if (StrUtil.isNotEmpty(readConverterExp) && ObjectUtil.isNotNull(value)) {
cell.setCellValue(convertByExp(ConvertUtil.toStr(value), readConverterExp, separator));
} else if (value instanceof BigDecimal && -1 != attr.scale()) {
cell.setCellValue((((BigDecimal) value).setScale(attr.scale(), attr.roundingMode())).doubleValue());
} else if (!attr.handler().equals(ExcelHandlerAdapter.class)) {
cell.setCellValue(dataFormatHandlerAdapter(value, attr, cell));
} else {
// 设置列类型
setCellVo(value, attr, cell);
}
addStatisticsData(column, ConvertUtil.toStr(value), attr);
}
} catch (Exception e) {
log.error("导出Excel失败{}", e);
}
return cell;
}
/**
* 设置 POI XSSFSheet 单元格提示或选择框
*
* @param sheet 表单
* @param textList 下拉框显示的内容
* @param promptContent 提示内容
* @param firstRow 开始行
* @param endRow 结束行
* @param firstCol 开始列
* @param endCol 结束列
*/
public void setPromptOrValidation(Sheet sheet, String[] textList, String promptContent, int firstRow, int endRow,
int firstCol, int endCol) {
DataValidationHelper helper = sheet.getDataValidationHelper();
DataValidationConstraint constraint = textList.length > 0 ? helper.createExplicitListConstraint(textList) : helper.createCustomConstraint("DD1");
CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol);
DataValidation dataValidation = helper.createValidation(constraint, regions);
if (StrUtil.isNotEmpty(promptContent)) {
// 如果设置了提示信息则鼠标放上去提示
dataValidation.createPromptBox("", promptContent);
dataValidation.setShowPromptBox(true);
}
// 处理Excel兼容性问题
if (dataValidation instanceof XSSFDataValidation) {
dataValidation.setSuppressDropDownArrow(true);
dataValidation.setShowErrorBox(true);
} else {
dataValidation.setSuppressDropDownArrow(false);
}
sheet.addValidationData(dataValidation);
}
/**
* 解析导出值 0=男,1=女,2=未知
*
* @param propertyValue 参数值
* @param converterExp 翻译注解
* @param separator 分隔符
* @return 解析后值
*/
public static String convertByExp(String propertyValue, String converterExp, String separator) {
StringBuilder propertyString = new StringBuilder();
String[] convertSource = converterExp.split(",");
for (String item : convertSource) {
String[] itemArray = item.split("=");
if (StrUtil.containsAny(propertyValue, separator)) {
for (String value : propertyValue.split(separator)) {
if (itemArray[0].equals(value)) {
propertyString.append(itemArray[1]).append(separator);
break;
}
}
} else {
if (itemArray[0].equals(propertyValue)) {
return itemArray[1];
}
}
}
return StrUtil.removeAll(propertyString.toString(), separator);
}
/**
* 反向解析值 男=0,女=1,未知=2
*
* @param propertyValue 参数值
* @param converterExp 翻译注解
* @param separator 分隔符
* @return 解析后值
*/
public static String reverseByExp(String propertyValue, String converterExp, String separator) {
StringBuilder propertyString = new StringBuilder();
String[] convertSource = converterExp.split(",");
for (String item : convertSource) {
String[] itemArray = item.split("=");
if (StrUtil.containsAny(propertyValue, separator)) {
for (String value : propertyValue.split(separator)) {
if (itemArray[1].equals(value)) {
propertyString.append(itemArray[0]).append(separator);
break;
}
}
} else {
if (itemArray[1].equals(propertyValue)) {
return itemArray[0];
}
}
}
return StrUtil.removeAll(propertyString.toString(), separator);
}
/**
* 数据处理器
*
* @param value 数据值
* @param excel 数据注解
* @return
*/
public String dataFormatHandlerAdapter(Object value, Excel excel, Cell cell) {
try {
Object instance = excel.handler().getDeclaredConstructor().newInstance();
Method formatMethod = excel.handler().getMethod("format", Object.class, String[].class, Cell.class, Workbook.class);
value = formatMethod.invoke(instance, value, excel.args(), cell, this.wb);
} catch (Exception e) {
log.error("不能格式化数据{}{} ", excel.handler(), e.getMessage());
}
return ConvertUtil.toStr(value);
}
/**
* 合计统计信息
*/
private void addStatisticsData(Integer index, String text, Excel entity) {
if (entity != null && entity.isStatistics()) {
Double temp = 0D;
if (!statistics.containsKey(index)) {
statistics.put(index, temp);
}
try {
temp = Double.valueOf(text);
} catch (NumberFormatException ignored) {
}
statistics.put(index, statistics.get(index) + temp);
}
}
/**
* 创建统计行
*/
public void addStatisticsRow() {
if (statistics.size() > 0) {
Row row = sheet.createRow(sheet.getLastRowNum() + 1);
Set<Integer> keys = statistics.keySet();
Cell cell = row.createCell(0);
cell.setCellStyle(styles.get("total"));
cell.setCellValue("合计");
for (Integer key : keys) {
cell = row.createCell(key);
cell.setCellStyle(styles.get("total"));
cell.setCellValue(DOUBLE_FORMAT.format(statistics.get(key)));
}
statistics.clear();
}
}
/**
* 获取bean中的属性值
*
* @param vo 实体对象
* @param field 字段
* @param excel 注解
* @return 最终的属性值
*/
private Object getTargetValue(T vo, Field field, Excel excel) throws Exception {
Object o = field.get(vo);
if (StrUtil.isNotEmpty(excel.targetAttr())) {
String target = excel.targetAttr();
if (target.contains(".")) {
String[] targets = target.split("[.]");
for (String name : targets) {
o = getValue(o, name);
}
} else {
o = getValue(o, target);
}
}
return o;
}
/**
* 以类的属性的get方法方法形式获取值
*
* @param o
* @param name
* @return value
* @throws Exception
*/
private Object getValue(Object o, String name) throws Exception {
if (ObjectUtil.isNotNull(o) && StrUtil.isNotEmpty(name)) {
Class<?> clazz = o.getClass();
Field field = clazz.getDeclaredField(name);
field.setAccessible(true);
o = field.get(o);
}
return o;
}
/**
* 得到所有定义字段
*/
private void createExcelField() {
this.fields = getFields();
this.fields = this.fields.stream().sorted(Comparator.comparing(objects -> ((Excel) objects[1]).sort()))
.collect(Collectors.toList());
this.maxHeight = getRowHeight();
}
/**
* 获取字段注解信息
*/
public List<Object[]> getFields() {
List<Object[]> fields = new ArrayList<Object[]>();
List<Field> tempFields = new ArrayList<>();
tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields()));
tempFields.addAll(Arrays.asList(clazz.getDeclaredFields()));
for (Field field : tempFields) {
if (!ArrayUtils.contains(this.excludeFields, field.getName())) {
// 单注解
if (field.isAnnotationPresent(Excel.class)) {
Excel attr = field.getAnnotation(Excel.class);
if (attr != null && (attr.type() == Type.ALL || attr.type() == type)) {
field.setAccessible(true);
fields.add(new Object[]{field, attr});
}
if (Collection.class.isAssignableFrom(field.getType())) {
subMethod = getSubMethod(field.getName(), clazz);
ParameterizedType pt = (ParameterizedType) field.getGenericType();
Class<?> subClass = (Class<?>) pt.getActualTypeArguments()[0];
this.subFields = FieldUtils.getFieldsListWithAnnotation(subClass, Excel.class);
}
}
// 多注解
if (field.isAnnotationPresent(Excels.class)) {
Excels attrs = field.getAnnotation(Excels.class);
Excel[] excels = attrs.value();
for (Excel attr : excels) {
if (attr != null && !ArrayUtils.contains(this.excludeFields, field.getName() + "." + attr.targetAttr())
&& ((attr.type() == Type.ALL || attr.type() == type))) {
field.setAccessible(true);
fields.add(new Object[]{field, attr});
}
}
}
}
}
return fields;
}
/**
* 根据注解获取最大行高
*/
public short getRowHeight() {
double maxHeight = 0;
for (Object[] os : this.fields) {
Excel excel = (Excel) os[1];
maxHeight = Math.max(maxHeight, excel.height());
}
return (short) (maxHeight * 20);
}
/**
* 创建一个工作簿
*/
public void createWorkbook() {
this.wb = new SXSSFWorkbook(500);
this.sheet = wb.createSheet();
wb.setSheetName(0, sheetName);
this.styles = createStyles(wb);
}
/**
* 创建工作表
*
* @param sheetNo sheet数量
* @param index 序号
*/
public void createSheet(int sheetNo, int index) {
// 设置工作表的名称.
if (sheetNo > 1 && index > 0) {
this.sheet = wb.createSheet();
this.createTitle();
wb.setSheetName(index, sheetName + index);
}
}
/**
* 获取单元格值
*
* @param row 获取的行
* @param column 获取单元格列号
* @return 单元格值
*/
public Object getCellValue(Row row, int column) {
if (row == null) {
return row;
}
Object val = "";
try {
Cell cell = row.getCell(column);
if (ObjectUtil.isNotNull(cell)) {
if (cell.getCellType() == CellType.NUMERIC || cell.getCellType() == CellType.FORMULA) {
val = cell.getNumericCellValue();
if (org.apache.poi.ss.usermodel.DateUtil.isCellDateFormatted(cell)) {
val = org.apache.poi.ss.usermodel.DateUtil.getJavaDate((Double) val); // POI Excel 日期格式转换
} else {
if ((Double) val % 1 != 0) {
val = new BigDecimal(val.toString());
} else {
val = new DecimalFormat("0").format(val);
}
}
} else if (cell.getCellType() == CellType.STRING) {
val = cell.getStringCellValue();
} else if (cell.getCellType() == CellType.BOOLEAN) {
val = cell.getBooleanCellValue();
} else if (cell.getCellType() == CellType.ERROR) {
val = cell.getErrorCellValue();
}
}
} catch (Exception e) {
return val;
}
return val;
}
/**
* 判断是否是空行
*
* @param row 判断的行
* @return
*/
private boolean isRowEmpty(Row row) {
if (row == null) {
return true;
}
for (int i = row.getFirstCellNum(); i < row.getLastCellNum(); i++) {
Cell cell = row.getCell(i);
if (cell != null && cell.getCellType() != CellType.BLANK) {
return false;
}
}
return true;
}
/**
* 格式化不同类型的日期对象
*
* @param dateFormat 日期格式
* @param val 被格式化的日期对象
* @return 格式化后的日期字符
*/
public String parseDateToStr(String dateFormat, Object val) {
if (val == null) {
return "";
}
String str;
if (val instanceof Date) {
str = DateUtil.parseDateToStr(dateFormat, (Date) val);
} else if (val instanceof LocalDateTime) {
str = DateUtil.parseDateToStr(dateFormat, DateUtil.toDate((LocalDateTime) val));
} else if (val instanceof LocalDate) {
str = DateUtil.parseDateToStr(dateFormat, DateUtil.toDate((LocalDate) val));
} else {
str = val.toString();
}
return str;
}
/**
* 是否有对象的子列表
*/
public boolean isSubList() {
return CollUtil.isNotEmpty(subFields);
}
/**
* 是否有对象的子列表,集合不为空
*/
public boolean isSubListValue(T vo) {
return CollUtil.isNotEmpty(subFields) && CollUtil.isNotEmpty(getListCellValue(vo));
}
/**
* 获取集合的值
*/
public Collection<?> getListCellValue(Object obj) {
Object value;
try {
value = subMethod.invoke(obj);
} catch (Exception e) {
return new ArrayList<>();
}
return (Collection<?>) value;
}
/**
* 获取对象的子列表方法
*
* @param name 名称
* @param pojoClass 类对象
* @return 子列表方法
*/
public Method getSubMethod(String name, Class<?> pojoClass) {
StringBuilder getMethodName = new StringBuilder("get");
getMethodName.append(name.substring(0, 1).toUpperCase());
getMethodName.append(name.substring(1));
Method method = null;
try {
method = pojoClass.getMethod(getMethodName.toString());
} catch (Exception e) {
log.error("获取对象异常{}", e.getMessage());
}
return method;
}
/**
* 设置某些列的值只能输入预制的数据,显示下拉框(兼容超出一定数量的下拉框).
*
* @param sheet 要设置的sheet.
* @param textList 下拉框显示的内容
* @param promptContent 提示内容
* @param firstRow 开始行
* @param endRow 结束行
* @param firstCol 开始列
* @param endCol 结束列
*/
public void setXSSFValidationWithHidden(Sheet sheet, String[] textList, String promptContent, int firstRow, int endRow, int firstCol, int endCol) {
String hideSheetName = "combo_" + firstCol + "_" + endCol;
Sheet hideSheet = wb.createSheet(hideSheetName); // 用于存储 下拉菜单数据
for (int i = 0; i < textList.length; i++) {
hideSheet.createRow(i).createCell(0).setCellValue(textList[i]);
}
// 创建名称,可被其他单元格引用
Name name = wb.createName();
name.setNameName(hideSheetName + "_data");
name.setRefersToFormula(hideSheetName + "!$A$1:$A$" + textList.length);
DataValidationHelper helper = sheet.getDataValidationHelper();
// 加载下拉列表内容
DataValidationConstraint constraint = helper.createFormulaListConstraint(hideSheetName + "_data");
// 设置数据有效性加载在哪个单元格上,四个参数分别是:起始行、终止行、起始列、终止列
CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol);
// 数据有效性对象
DataValidation dataValidation = helper.createValidation(constraint, regions);
if (StringUtils.isNotEmpty(promptContent)) {
// 如果设置了提示信息则鼠标放上去提示
dataValidation.createPromptBox("", promptContent);
dataValidation.setShowPromptBox(true);
}
// 处理Excel兼容性问题
if (dataValidation instanceof XSSFDataValidation) {
dataValidation.setSuppressDropDownArrow(true);
dataValidation.setShowErrorBox(true);
} else {
dataValidation.setSuppressDropDownArrow(false);
}
sheet.addValidationData(dataValidation);
// 设置hiddenSheet隐藏
wb.setSheetHidden(wb.getSheetIndex(hideSheet), true);
}
}
package com.xueyi.common.core.utils.servlet;
import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.io.IoUtil;
import com.alibaba.fastjson2.JSON;
import com.xueyi.common.core.constant.basic.HttpConstants;
import com.xueyi.common.core.constant.basic.TokenConstants;
import com.xueyi.common.core.utils.core.ArrayUtil;
import com.xueyi.common.core.utils.core.ConvertUtil;
import com.xueyi.common.core.utils.core.StrUtil;
import com.xueyi.common.core.web.result.R;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.LinkedCaseInsensitiveMap;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
/**
* 客户端工具类
*
* @author xueyi
*/
@Slf4j
public class ServletUtil {
/**
* 获取请求token
*/
public static String getToken(ServerHttpRequest request) {
String token = request.getHeaders().getFirst(TokenConstants.SUPPLY_AUTHORIZATION);
if (StrUtil.isBlank(token))
token = request.getHeaders().getFirst(TokenConstants.AUTHENTICATION);
return getToken(token);
}
/**
* 获取请求token
*/
public static String getToken(String token) {
if (StrUtil.isNotEmpty(token) && StrUtil.startWith(token, TokenConstants.PREFIX)) {
token = StrUtil.replaceFirst(token, TokenConstants.PREFIX, StrUtil.EMPTY);
}
return token;
}
/**
* 鉴权失败处理
*/
public static Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String msg) {
log.error("[鉴权异常处理]请求路径:{}", exchange.getRequest().getPath());
return ServletUtil.webFluxResponseWriter(exchange.getResponse(), msg, HttpConstants.Status.UNAUTHORIZED.getCode());
}
/**
* 增加请求头
*/
public static void addHeader(ServerHttpRequest.Builder mutate, String name, Object value) {
if (value == null)
return;
String valueStr = value.toString();
String valueEncode = ServletUtil.urlEncode(valueStr);
mutate.header(name, valueEncode);
}
/**
* 移除请求头
*/
public static void removeHeader(ServerHttpRequest.Builder mutate, String name) {
mutate.headers(httpHeaders -> httpHeaders.remove(name)).build();
}
/**
* 获取String参数
*/
public static String getParameter(String name) {
return getRequest().getParameter(name);
}
/**
* 获取参数Map
*/
public static MultiValueMap<String, String> getParameters(HttpServletRequest request) {
Map<String, String[]> parameterMap = request.getParameterMap();
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(parameterMap.size());
parameterMap.forEach((key, values) -> {
for (String value : values) {
parameters.add(key, value);
}
});
return parameters;
}
/**
* 获取String参数
*/
public static String getParameter(String name, String defaultValue) {
return ConvertUtil.toStr(getRequest().getParameter(name), defaultValue);
}
/**
* 获取Integer参数
*/
public static Integer getParameterToInt(String name) {
return ConvertUtil.toInt(getRequest().getParameter(name));
}
/**
* 获取Integer参数
*/
public static Integer getParameterToInt(String name, Integer defaultValue) {
return ConvertUtil.toInt(getRequest().getParameter(name), defaultValue);
}
/**
* 获取Boolean参数
*/
public static Boolean getParameterToBool(String name) {
return ConvertUtil.toBool(getRequest().getParameter(name));
}
/**
* 获取Boolean参数
*/
public static Boolean getParameterToBool(String name, Boolean defaultValue) {
return ConvertUtil.toBool(getRequest().getParameter(name), defaultValue);
}
/**
* 获得所有请求参数
*
* @param request 请求对象{@link ServletRequest}
* @return Map
*/
public static Map<String, String[]> getParams(ServletRequest request) {
final Map<String, String[]> map = request.getParameterMap();
return Collections.unmodifiableMap(map);
}
/**
* 获得所有请求参数
*
* @param request 请求对象{@link ServletRequest}
* @return Map
*/
public static Map<String, String> getParamMap(ServletRequest request) {
Map<String, String> params = new HashMap<>();
for (Map.Entry<String, String[]> entry : getParams(request).entrySet()) {
params.put(entry.getKey(), ArrayUtil.join(entry.getValue(), StrUtil.COMMA));
}
return params;
}
/**
* 获取request
*/
public static HttpServletRequest getRequest() {
try {
return getRequestAttributes().getRequest();
} catch (Exception e) {
return null;
}
}
/**
* 获取response
*/
public static HttpServletResponse getResponse() {
try {
return getRequestAttributes().getResponse();
} catch (Exception e) {
return null;
}
}
/**
* 获取session
*/
public static HttpSession getSession() {
return getRequest().getSession();
}
public static ServletRequestAttributes getRequestAttributes() {
try {
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
return (ServletRequestAttributes) attributes;
} catch (Exception e) {
return null;
}
}
public static String getHeader(HttpServletRequest request, String name) {
String value = request.getHeader(name);
if (StrUtil.isEmpty(value)) {
return StrUtil.EMPTY;
}
return urlDecode(value);
}
public static Map<String, String> getHeaders(HttpServletRequest request) {
Map<String, String> map = new LinkedCaseInsensitiveMap<>();
Enumeration<String> enumeration = request.getHeaderNames();
if (enumeration != null) {
while (enumeration.hasMoreElements()) {
String key = enumeration.nextElement();
String value = request.getHeader(key);
map.put(key, value);
}
}
return map;
}
/**
* 将字符串渲染到客户端
*
* @param response 渲染对象
* @param string 待渲染的字符串
*/
public static void renderString(HttpServletResponse response, String string) {
try {
response.setStatus(HttpConstants.Status.SUCCESS.getCode());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.getWriter().print(string);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 是否是Ajax异步请求
*
* @param request 请求
*/
public static boolean isAjaxRequest(HttpServletRequest request) {
String accept = request.getHeader("accept");
if (accept != null && accept.contains(MediaType.APPLICATION_JSON_VALUE)) {
return true;
}
String xRequestedWith = request.getHeader("X-Requested-With");
if (xRequestedWith != null && xRequestedWith.contains("XMLHttpRequest")) {
return true;
}
String uri = request.getRequestURI();
if (StrUtil.inStringIgnoreCase(uri, ".json", ".xml")) {
return true;
}
String ajax = request.getParameter("__ajax");
return StrUtil.inStringIgnoreCase(ajax, "json", "xml");
}
/**
* 内容编码
*
* @param str 内容
* @return 编码后的内容
*/
public static String urlEncode(String str) {
try {
return URLEncoder.encode(str, HttpConstants.Character.UTF8.getCode());
} catch (UnsupportedEncodingException e) {
return StrUtil.EMPTY;
}
}
/**
* 内容解码
*
* @param str 内容
* @return 解码后的内容
*/
public static String urlDecode(String str) {
try {
return URLDecoder.decode(str, HttpConstants.Character.UTF8.getCode());
} catch (UnsupportedEncodingException e) {
return StrUtil.EMPTY;
}
}
/**
* 设置webflux模型响应
*
* @param response ServerHttpResponse
* @param value 响应内容
* @return Mono
*/
public static Mono<Void> webFluxResponseWriter(ServerHttpResponse response, Object value) {
return webFluxResponseWriter(response, HttpStatus.OK, value, R.FAIL);
}
/**
* 设置webflux模型响应
*
* @param response ServerHttpResponse
* @param code 响应状态码
* @param value 响应内容
* @return Mono
*/
public static Mono<Void> webFluxResponseWriter(ServerHttpResponse response, Object value, int code) {
return webFluxResponseWriter(response, HttpStatus.OK, value, code);
}
/**
* 设置webflux模型响应
*
* @param response ServerHttpResponse
* @param status http状态码
* @param code 响应状态码
* @param value 响应内容
* @return Mono
*/
public static Mono<Void> webFluxResponseWriter(ServerHttpResponse response, HttpStatus status, Object value, int code) {
return webFluxResponseWriter(response, MediaType.APPLICATION_JSON_VALUE, status, value, code);
}
/**
* 设置webflux模型响应
*
* @param response ServerHttpResponse
* @param contentType content-type
* @param status http状态码
* @param code 响应状态码
* @param value 响应内容
* @return Mono
*/
public static Mono<Void> webFluxResponseWriter(ServerHttpResponse response, String contentType, HttpStatus status, Object value, int code) {
response.setStatusCode(status);
response.getHeaders().add(HttpHeaders.CONTENT_TYPE, contentType);
R<?> result = R.fail(code, value.toString());
DataBuffer dataBuffer = response.bufferFactory().wrap(JSON.toJSONString(result).getBytes());
return response.writeWith(Mono.just(dataBuffer));
}
/**
* 设置servlet响应
*
* @param response HttpServletResponse
* @param value 响应内容
*/
public static void webResponseWriter(HttpServletResponse response, Object value) {
webResponseWriter(response, MediaType.APPLICATION_JSON_VALUE, value);
}
/**
* 设置servlet响应
*
* @param response HttpServletResponse
* @param contentType content-type
* @param value 响应内容
*/
public static void webResponseWriter(HttpServletResponse response, String contentType, Object value) {
response.setContentType(contentType);
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
Writer writer = null;
try {
writer = response.getWriter();
writer.write(JSON.toJSONString(value));
writer.flush();
} catch (IOException var8) {
throw new UtilException(var8);
} finally {
IoUtil.close(writer);
}
}
}
package com.xueyi.common.core.utils.sql;
import com.xueyi.common.core.exception.UtilException;
import com.xueyi.common.core.utils.core.StrUtil;
/**
* sql操作工具类
*
* @author xueyi
*/
public class SqlUtil {
/** 定义常用的 sql关键字 */
public static String SQL_REGEX = "select |insert |delete |update |drop |count |exec |chr |mid |master |truncate |char |and |declare ";
/** 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序) */
public static String SQL_PATTERN = "[a-zA-Z0-9_\\ \\,\\.]+";
/** 限制orderBy最大长度 */
private static final int ORDER_BY_MAX_LENGTH = 500;
/**
* 检查字符,防止注入绕过
*/
public static String escapeOrderBySql(String value) {
if (StrUtil.isNotEmpty(value) && !isValidOrderBySql(value)) {
throw new UtilException("参数不符合规范,不能进行查询");
}
if (StrUtil.length(value) > ORDER_BY_MAX_LENGTH) {
throw new UtilException("参数已超过最大限制,不能进行查询");
}
return value;
}
/**
* 验证 order by 语法是否符合规范
*/
public static boolean isValidOrderBySql(String value) {
return value.matches(SQL_PATTERN);
}
/**
* SQL关键字检查
*/
public static void filterKeyword(String value) {
if (StrUtil.isEmpty(value)) {
return;
}
String[] sqlKeywords = StrUtil.splitToArray(SQL_REGEX, "\\|");
for (String sqlKeyword : sqlKeywords) {
if (StrUtil.indexOfIgnoreCase(value, sqlKeyword) > -1) {
throw new UtilException("参数存在SQL注入风险");
}
}
}
}
package com.xueyi.common.core.utils;
import org.apache.commons.lang3.time.DateFormatUtils;
import java.lang.management.ManagementFactory;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Date;
/**
* 时间工具类
*
* @author xueyi
*/
public class DateUtil extends org.apache.commons.lang3.time.DateUtils {
public static final String YYYY = "yyyy";
public static final String YYYY_MM = "yyyy-MM";
public static final String YYYY_MM_DD = "yyyy-MM-dd";
public static final String YYYYMMDDHHMMSS = "yyyyMMddHHmmss";
public static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
private static final String[] parsePatterns = {
"yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM",
"yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM",
"yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"};
/**
* 获取当前Date型日期
*
* @return Date() 当前日期
*/
public static Date getNowDate() {
return new Date();
}
/**
* 获取当前日期, 默认格式为yyyy-MM-dd
*
* @return String
*/
public static String getDate() {
return dateTimeNow(YYYY_MM_DD);
}
public static String getTime() {
return dateTimeNow(YYYY_MM_DD_HH_MM_SS);
}
public static String dateTimeNow() {
return dateTimeNow(YYYYMMDDHHMMSS);
}
public static String dateTimeNow(final String format) {
return parseDateToStr(format, new Date());
}
public static String dateTime(final Date date) {
return parseDateToStr(YYYY_MM_DD, date);
}
public static String parseDateToStr(final String format, final Date date) {
return new SimpleDateFormat(format).format(date);
}
public static Date dateTime(final String format, final String ts) {
try {
return new SimpleDateFormat(format).parse(ts);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
/**
* 日期路径 即年/月/日 如2018/08/08
*/
public static String datePath() {
Date now = new Date();
return DateFormatUtils.format(now, "yyyy/MM/dd");
}
/**
* 日期路径 即年/月/日 如20180808
*/
public static String dateTime() {
Date now = new Date();
return DateFormatUtils.format(now, "yyyyMMdd");
}
/**
* 日期型字符串转化为日期 格式
*/
public static Date parseDate(Object str) {
if (str == null) {
return null;
}
try {
return parseDate(str.toString(), parsePatterns);
} catch (ParseException e) {
return null;
}
}
/**
* 获取服务器启动时间
*/
public static Date getServerStartDate() {
long time = ManagementFactory.getRuntimeMXBean().getStartTime();
return new Date(time);
}
/**
* 计算时间差
*
* @param startTime 开始时间
* @param endTime 最后时间
* @return 时间差(天/小时/分钟)
*/
public static String timeDistance(Date startTime, Date endTime) {
long nd = 1000 * 24 * 60 * 60;
long nh = 1000 * 60 * 60;
long nm = 1000 * 60;
// long ns = 1000;
// 获得两个时间的毫秒时间差异
long diff = endTime.getTime() - startTime.getTime();
// 计算差多少天
long day = diff / nd;
// 计算差多少小时
long hour = diff % nd / nh;
// 计算差多少分钟
long min = diff % nd % nh / nm;
// 计算差多少秒//输出结果
// long sec = diff % nd % nh % nm / ns;
return day + "天" + hour + "小时" + min + "分钟";
}
/**
* 增加 LocalDateTime ==> Date
*/
public static Date toDate(LocalDateTime temporalAccessor) {
ZonedDateTime zdt = temporalAccessor.atZone(ZoneId.systemDefault());
return Date.from(zdt.toInstant());
}
/**
* 增加 LocalDate ==> Date
*/
public static Date toDate(LocalDate temporalAccessor) {
LocalDateTime localDateTime = LocalDateTime.of(temporalAccessor, LocalTime.of(0, 0, 0));
ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault());
return Date.from(zdt.toInstant());
}
}
package com.xueyi.common.core.utils;
import com.xueyi.common.core.utils.core.StrUtil;
import org.apache.commons.lang3.exception.ExceptionUtils;
import java.io.PrintWriter;
import java.io.StringWriter;
/**
* 错误信息处理类
*
* @author xueyi
*/
public class ExceptionUtil {
/**
* 获取exception的详细错误信息。
*/
public static String getExceptionMessage(Throwable e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw, true));
return sw.toString();
}
public static String getRootErrorMessage(Exception e) {
Throwable root = ExceptionUtils.getRootCause(e);
root = (root == null ? e : root);
if (root == null) {
return StrUtil.EMPTY;
}
String msg = root.getMessage();
if (msg == null) {
return StrUtil.NULL;
}
return StrUtil.nullToEmpty(msg);
}
}
package com.xueyi.common.core.utils;
import com.xueyi.common.core.constant.basic.BaseConstants;
import com.xueyi.common.core.exception.UtilException;
import com.xueyi.common.core.exception.base.BaseException;
import com.xueyi.common.core.utils.core.CollUtil;
import com.xueyi.common.core.utils.core.ObjectUtil;
import com.xueyi.common.core.utils.core.StrUtil;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
/**
* 树结构组装工具类
*
* @author xueyi
*/
public class TreeUtil {
/**
* 构建树结构
* 存在默认参数 详见BaseConstants.Entity
* IdName = ID | FIdName = PARENT_ID | childrenName = CHILDREN | topNode = TOP_NODE | killScattered = false | killNoneChild = true
*
* @param list 组装列表
* @return 树结构列表
*/
public static <T> List<T> buildTree(List<T> list) {
return buildTree(list, BaseConstants.Entity.ID.getCode(),
BaseConstants.Entity.PARENT_ID.getCode(),
BaseConstants.Entity.CHILDREN.getCode(),
BaseConstants.TOP_ID,
false,
true);
}
/**
* 构建树结构
* 存在默认参数 详见BaseConstants.Entity
* IdName = ID | FIdName = PARENT_ID | childrenName = CHILDREN | topNode = TOP_NODE | killNoneChild = true
*
* @param list 组装列表
* @param killScattered 是否移除无法追溯到顶级节点 (true 是 | false 否)
* @return 树结构列表
*/
public static <T> List<T> buildTree(List<T> list, boolean killScattered) {
return buildTree(list, BaseConstants.Entity.ID.getCode(),
BaseConstants.Entity.PARENT_ID.getCode(),
BaseConstants.Entity.CHILDREN.getCode(),
BaseConstants.TOP_ID,
killScattered,
true);
}
/**
* 构建树结构
* 存在默认参数 详见BaseConstants.Entity
* IdName = ID | FIdName = PARENT_ID | childrenName = CHILDREN | topNode = TOP_NODE
*
* @param list 组装列表
* @param killScattered 是否移除无法追溯到顶级节点 (true 是 | false 否)
* @param killNoneChild 是否移除空子节点集合
* @return 树结构列表
*/
public static <T> List<T> buildTree(List<T> list, boolean killScattered, boolean killNoneChild) {
return buildTree(list, BaseConstants.Entity.ID.getCode(),
BaseConstants.Entity.PARENT_ID.getCode(),
BaseConstants.Entity.CHILDREN.getCode(),
BaseConstants.TOP_ID,
killScattered,
killNoneChild);
}
/**
* 构建树结构
* 存在默认参数 详见BaseConstants.Entity
* IdName = ID | FIdName = PARENT_ID | childrenName = CHILDREN | killNoneChild = true
*
* @param list 组装列表
* @param topNode 顶级节点
* @param killScattered 是否移除无法追溯到顶级节点 (true 是 | false 否)
* @return 树结构列表
*/
public static <T> List<T> buildTree(List<T> list, Serializable topNode, boolean killScattered) {
return buildTree(list, BaseConstants.Entity.ID.getCode(),
BaseConstants.Entity.PARENT_ID.getCode(),
BaseConstants.Entity.CHILDREN.getCode(),
topNode,
killScattered,
true);
}
/**
* 构建树结构
*
* @param list 组装列表
* @param IdName Id字段名称
* @param FIdName 父级Id字段名称
* @param childrenName children字段名称
* @param topNode 顶级节点
* @param killScattered 是否移除无法追溯到顶级节点 (true 是 | false 否)
* @param killNoneChild 是否移除空子节点集合
* @return 树结构列表
*/
public static <T> List<T> buildTree(List<T> list, String IdName, String FIdName, String childrenName, Serializable topNode, boolean killScattered, boolean killNoneChild) {
List<T> returnList = new ArrayList<>();
List<Long> tempList = new ArrayList<>();
T top = null;
boolean hasTopNode = false;
try {
if (CollUtil.isNotEmpty(list)) {
int IdKey = 0;
for (T vo : list) {
if (IdKey == 0)
IdKey = checkAttribute(vo, IdName, IdKey);
Field Id = depthRecursive(vo, IdKey).getDeclaredField(IdName);
Id.setAccessible(true);
if (ObjectUtil.equal(Id.get(vo), topNode)) {
hasTopNode = true;
top = vo;
list.remove(vo);
break;
}
}
for (T vo : list) {
Field Id = depthRecursive(vo, IdKey).getDeclaredField(IdName);
Id.setAccessible(true);
tempList.add((Long) Id.get(vo));
}
int FIdKey = 0;
for (T vo : list) {
if (FIdKey == 0)
FIdKey = checkAttribute(vo, FIdName, FIdKey);
Field FId = depthRecursive(vo, FIdKey).getDeclaredField(FIdName);
FId.setAccessible(true);
// 如果是顶级节点, 遍历该父节点的所有子节点
if (!tempList.contains((Long) FId.get(vo))) {
recursionFn(list, vo, IdName, FIdName, childrenName, killNoneChild);
returnList.add(vo);
}
}
}
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new UtilException(e);
}
if (returnList.isEmpty()) {
returnList = list;
}
if (killScattered) {
deleteNoTopNode(returnList, FIdName, topNode);
}
if (hasTopNode && ObjectUtil.isNotNull(top)) {
List<T> topList = new ArrayList<>();
try {
Field children = depthRecursive(top, checkAttribute(top, childrenName, 0)).getDeclaredField(childrenName);
children.setAccessible(true);
if (killNoneChild) {
if (CollUtil.isNotEmpty(returnList))
children.set(top, returnList);
} else {
children.set(top, returnList);
}
topList.add(top);
return topList;
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new UtilException(e);
}
}
return returnList;
}
/**
* 检查是否存在属性
*/
private static <T> int checkAttribute(T t, String fieldName, int key) {
Class<?> clazz = t.getClass();
while (Object.class != clazz) {
key++;
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (StrUtil.equals(fieldName, field.getName())) {
return key;
}
}
clazz = clazz.getSuperclass();
}
throw new BaseException(StrUtil.format("对象不存在属性{}", fieldName));
}
/**
* 递归泛型深度
*/
private static <T> Class<?> depthRecursive(T t, int key) {
Class<?> clazz = t.getClass();
int i = 1;
while (i < key) {
clazz = clazz.getSuperclass();
i++;
}
return clazz;
}
/**
* 递归列表
*/
private static <T> void recursionFn(List<T> list, T t, String IdName, String FIdName, String childrenName, boolean killNoneChild) {
// 得到子节点列表
List<T> childList = getChildList(list, t, IdName, FIdName);
try {
Field children = depthRecursive(t, checkAttribute(t, childrenName, 0)).getDeclaredField(childrenName);
children.setAccessible(true);
if (killNoneChild) {
if (CollUtil.isNotEmpty(childList))
children.set(t, childList);
} else {
children.set(t, childList);
}
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new UtilException(e);
}
for (T tChild : childList) {
if (hasChild(list, tChild, IdName, FIdName)) {
recursionFn(list, tChild, IdName, FIdName, childrenName, killNoneChild);
}
}
}
/**
* 得到子节点列表
*/
private static <T> List<T> getChildList(List<T> list, T t, String IdName, String FIdName) {
List<T> tList = new ArrayList<T>();
Iterator<T> it = list.iterator();
try {
while (it.hasNext()) {
T n = (T) it.next();
Field Id = depthRecursive(t, checkAttribute(t, IdName, 0)).getDeclaredField(IdName);
Id.setAccessible(true);
Field FId = depthRecursive(n, checkAttribute(n, FIdName, 0)).getDeclaredField(FIdName);
FId.setAccessible(true);
if (ObjectUtil.isNotNull((Long) FId.get(n)) && ((Long) FId.get(n)).longValue() == ((Long) Id.get(t)).longValue()) {
tList.add(n);
}
}
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new UtilException(e);
}
return tList;
}
/**
* 判断是否有子节点
*/
private static <T> boolean hasChild(List<T> list, T t, String IdName, String FIdName) {
return CollUtil.isNotEmpty(getChildList(list, t, IdName, FIdName));
}
/**
* 删除无法溯源至顶级节点的值
*/
private static <T> void deleteNoTopNode(List<T> list, String FIdName, Serializable topNode) {
list.removeIf(vo -> {
try {
Field FId = depthRecursive(vo, checkAttribute(vo, FIdName, 0)).getDeclaredField(FIdName);
FId.setAccessible(true);
return !Objects.equals(FId.get(vo), topNode);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new UtilException(e);
}
});
}
/**
* 获取树结构叶子节点集合
* 存在默认参数 详见BaseConstants.Entity
* childrenName = CHILDREN
*
* @param list 组装列表
* @return 叶子节点集合
*/
public static <T> List<T> getLeafNodes(List<T> list) {
return getLeafNodes(list, BaseConstants.Entity.CHILDREN.getCode());
}
/**
* 获取树结构叶子节点集合
* 存在默认参数 详见BaseConstants.Entity
*
* @param list 组装列表
* @param childrenName children字段名称
* @return 叶子节点集合
*/
public static <T> List<T> getLeafNodes(List<T> list, String childrenName) {
List<T> returnList = new ArrayList<>();
try {
if (CollUtil.isNotEmpty(list)) {
T t = list.stream().findFirst().orElse(null);
assert t != null;
int childrenKey = checkAttribute(t, childrenName, 0);
recursionLeafFn(returnList, list, childrenName, childrenKey);
}
} catch (Exception ignored) {
}
return returnList;
}
/**
* 递归列表
*/
private static <T> void recursionLeafFn(List<T> returnList, List<T> list, String childrenName, int childrenKey) {
for (T vo : list) {
try {
Field children = depthRecursive(vo, childrenKey).getDeclaredField(childrenName);
children.setAccessible(true);
List<T> childList = (List<T>) children.get(vo);
if (CollUtil.isEmpty(childList)) {
returnList.add(vo);
} else {
recursionLeafFn(returnList, childList, childrenName, childrenKey);
}
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new UtilException(e);
}
}
}
}
package com.xueyi.common.core.web.entity.base;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.OrderBy;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
import java.time.LocalDateTime;
import static com.baomidou.mybatisplus.annotation.SqlCondition.LIKE;
/**
* Base 基类
*
* @author xueyi
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class BaseEntity extends BasisEntity {
@Serial
private static final long serialVersionUID = 1L;
/** 名称 */
@TableField(condition = LIKE)
protected String name;
/** 状态(0启用 1禁用) */
protected String status;
/** 显示顺序 */
@OrderBy(asc = true, sort = 10)
protected Integer sort;
/** 备注 */
@TableField(condition = LIKE, updateStrategy = FieldStrategy.ALWAYS)
protected String remark;
/** 创建者Id */
@TableField(fill = FieldFill.INSERT, updateStrategy = FieldStrategy.NEVER)
protected Long createBy;
/** 创建时间 */
@OrderBy(sort = 20)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@TableField(insertStrategy = FieldStrategy.NEVER, updateStrategy = FieldStrategy.NEVER)
protected LocalDateTime createTime;
/** 更新者Id */
@TableField(fill = FieldFill.UPDATE, insertStrategy = FieldStrategy.NEVER)
protected Long updateBy;
/** 更新时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@TableField(insertStrategy = FieldStrategy.NEVER, updateStrategy = FieldStrategy.NEVER)
protected LocalDateTime updateTime;
/** 删除标志 */
@JsonIgnore
@TableLogic
@TableField(select = false)
protected Long delFlag;
/** 创建者 */
@TableField(exist = false)
protected String createName;
/** 创建者 */
@TableField(exist = false)
protected String updateName;
}
package com.xueyi.common.core.web.entity.base;
import com.baomidou.mybatisplus.annotation.OrderBy;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.xueyi.common.core.constant.basic.BaseConstants;
import com.xueyi.common.core.utils.core.ObjectUtil;
import com.xueyi.common.core.web.validate.V_E;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Map;
/**
* Basis 基类
*
* @author xueyi
*/
@Data
public class BasisEntity implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** Id */
@TableId
@OrderBy
@NotNull(message = "id不能为空", groups = {V_E.class})
protected Long id;
/** 显示顺序 */
@TableField(exist = false)
protected Integer sort;
/** 数据源名称 */
@JsonIgnore
@TableField(exist = false)
protected String sourceName;
/** 操作类型 */
@JsonIgnore
@TableField(exist = false)
protected BaseConstants.Operate operate;
/** 请求参数 */
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@TableField(exist = false)
protected Map<String, Object> params;
@JsonIgnore
public String getIdStr() {
return ObjectUtil.isNotNull(id) ? id.toString() : null;
}
@JsonIgnore
@SuppressWarnings("all")
public void initId() {
id = null;
}
/**
* 初始化操作类型
*
* @param operate 操作类型
*/
@JsonIgnore
public void initOperate(BaseConstants.Operate operate) {
if (ObjectUtil.isNull(this.operate))
this.operate = operate;
}
}
package com.xueyi.common.core.web.entity.base;
import com.baomidou.mybatisplus.annotation.TableField;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.xueyi.common.core.utils.core.ConvertUtil;
import com.xueyi.common.core.utils.core.NumberUtil;
import com.xueyi.common.core.utils.core.ObjectUtil;
import com.xueyi.common.core.utils.core.StrUtil;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
import java.util.List;
/**
* Tree 基类
*
* @param Dto
* @author xueyi
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class TreeEntity<D> extends BaseEntity {
@Serial
private static final long serialVersionUID = 1L;
/** 父级Id */
protected Long parentId;
/** 父级名称 */
@TableField(exist = false)
protected String parentName;
/** 祖籍列表 */
@JsonIgnore
protected String ancestors;
/** 层级 */
protected Integer level;
/** 存在默认顶级(true存在 false不存在) */
@JsonIgnore
@TableField(exist = false)
protected Boolean defaultNode;
/** 自定义顶级节点名称 */
@JsonIgnore
@TableField(exist = false)
protected String topNodeName;
/** 移除当前及子节点(true是 false否) */
@JsonIgnore
@TableField(exist = false)
protected Boolean exNodes;
/** 子节点集合 */
@TableField(exist = false)
protected List<D> children;
/** 原始祖籍列表 */
@JsonIgnore
@TableField(exist = false)
protected String oldAncestors;
/** 原始层级 */
@JsonIgnore
@TableField(exist = false)
protected Integer oldLevel;
/** 判定是否存在默认顶级 */
@JsonIgnore
public Boolean getDefaultNode() {
return ObjectUtil.isNotNull(this.defaultNode) ? this.defaultNode : Boolean.FALSE;
}
/** 判定是否移除当前及子节点 */
@JsonIgnore
public Boolean getExNodes() {
return ObjectUtil.isNotNull(this.exNodes) ? this.exNodes : Boolean.FALSE;
}
/** 获取当前节点的子节点对应的祖籍 */
@JsonIgnore
public String getChildAncestors() {
return this.ancestors + StrUtil.COMMA + this.id;
}
/** 获取当前节点的子节点对应的原始祖籍 */
@JsonIgnore
public String getOldChildAncestors() {
return this.oldAncestors + StrUtil.COMMA + this.id;
}
/** 计算节点变化的层级数 */
@JsonIgnore
public int getLevelChange() {
return ConvertUtil.toInt(this.level, NumberUtil.Zero) - ConvertUtil.toInt(this.oldLevel, NumberUtil.Zero);
}
}
package com.xueyi.common.core.web.entity.common;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.TableField;
import com.xueyi.common.core.constant.basic.DictConstants;
import com.xueyi.common.core.utils.core.StrUtil;
import com.xueyi.common.core.web.entity.base.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
/**
* Base 混合基类
*
* @author xueyi
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class CBaseEntity extends BaseEntity {
@Serial
private static final long serialVersionUID = 1L;
/** 公共数据(Y是 N否) */
@TableField(updateStrategy = FieldStrategy.NEVER)
protected String isCommon;
/** 校验是否为公共数据 */
public boolean isCommon() {
return StrUtil.equals(DictConstants.DicCommonPrivate.COMMON.getCode(), getIsCommon());
}
/** 校验是否非公共数据 */
public boolean isNotCommon() {
return !isCommon();
}
}
package com.xueyi.common.core.web.entity.common;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.TableField;
import com.xueyi.common.core.constant.basic.DictConstants;
import com.xueyi.common.core.utils.core.StrUtil;
import com.xueyi.common.core.web.entity.base.BasisEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
/**
* Basis 混合基类
*
* @author xueyi
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class CBasisEntity extends BasisEntity {
@Serial
private static final long serialVersionUID = 1L;
/** 公共数据(0是 1否) */
@TableField(updateStrategy = FieldStrategy.NEVER)
protected String isCommon;
/** 校验是否为公共数据 */
public boolean isCommon() {
return StrUtil.equals(DictConstants.DicCommonPrivate.COMMON.getCode(), getIsCommon());
}
/** 校验是否非公共数据 */
public boolean isNotCommon() {
return !isCommon();
}
}
package com.xueyi.common.core.web.entity.common;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.TableField;
import com.xueyi.common.core.constant.basic.DictConstants;
import com.xueyi.common.core.utils.core.StrUtil;
import com.xueyi.common.core.web.entity.base.TreeEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
/**
* Tree 混合基类
*
* @param Dto
* @author xueyi
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class CTreeEntity<D> extends TreeEntity<D> {
@Serial
private static final long serialVersionUID = 1L;
/** 公共数据(Y是 N否) */
@TableField(updateStrategy = FieldStrategy.NEVER)
protected String isCommon;
/** 校验是否为公共数据 */
public boolean isCommon() {
return StrUtil.equals(DictConstants.DicCommonPrivate.COMMON.getCode(), getIsCommon());
}
/** 校验是否非公共数据 */
public boolean isNotCommon() {
return !isCommon();
}
}
package com.xueyi.common.core.web.entity.model;
import com.github.pagehelper.Page;
import com.xueyi.common.core.web.entity.base.BaseEntity;
import java.util.Collection;
import java.util.List;
/**
* Base 基类 对象映射器
*
* @param Query
* @param Dto
* @param Po
* @author xueyi
*/
public interface BaseConverter<Q extends P, D extends P, P extends BaseEntity> {
/**
* 类型转换 | 持久化对象 -> 数据传输对象
*
* @param po 持久化对象
* @return dto
*/
D mapperDto(P po);
/**
* 类型转换 | 持久化对象集合 -> 数据传输对象集合
*
* @param poList 持久化对象集合
* @return dtoList
*/
List<D> mapperDto(Collection<P> poList);
/**
* 类型转换 | 持久化对象集合 -> 数据传输分页对象集合
*
* @param poList 持久化对象集合
* @return dtoList
*/
Page<D> mapperPageDto(Collection<P> poList);
/**
* 类型转换 | 数据传输对象 -> 持久化对象
*
* @param dto 数据传输对象
* @return po
*/
P mapperPo(D dto);
/**
* 类型转换 | 数据传输对象集合 -> 持久化对象集合
*
* @param dtoList 数据传输对象集合
* @return poList
*/
List<P> mapperPo(Collection<D> dtoList);
/**
* 类型转换 | 数据传输对象集合 -> 持久化分页对象集合
*
* @param dtoList 数据传输对象集合
* @return poList
*/
Page<P> mapperPagePo(Collection<D> dtoList);
}
package com.xueyi.common.core.web.entity.model;
import com.xueyi.common.core.web.entity.base.TreeEntity;
import org.mapstruct.Mapping;
import java.util.Collection;
import java.util.List;
/**
* Tree 基类 对象映射器
*
* @param Query
* @param Dto
* @param Po
* @author xueyi
*/
public interface TreeConverter<Q extends P, D extends P, P extends TreeEntity<D>> extends BaseConverter<Q, D, P> {
@Mapping(target = "children", ignore = true)
D mapperDto(P po);
@Mapping(target = "children", ignore = true)
List<D> mapperDto(Collection<P> poList);
@Mapping(target = "children", ignore = true)
P mapperPo(D po);
@Mapping(target = "children", ignore = true)
List<P> mapperPo(Collection<D> poList);
}
tion.TableField;
import com.xueyi.common.core.constant.basic.DictConstants;
import com.xueyi.common.core.utils.core.StrUtil;
import com.xueyi.common.core.web.entity.base.BasisEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
/**
Basis 混合基类
@author xueyi
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class CBasisEntity extends BasisEntity {
@Serial
private static final long serialVersionUID = 1L;
/** 公共数据(0是 1否) */
@TableField(updateStrategy = FieldStrategy.NEVER)
protected String isCommon;
/** 校验是否为公共数据 */
public boolean isCommon() {
return StrUtil.equals(DictConstants.DicCommonPrivate.COMMON.getCode(), getIsCommon());
}
/** 校验是否非公共数据 */
public boolean isNotCommon() {
return !isCommon();
}
}
## Tree 混合基类
~~~java
package com.xueyi.common.core.web.entity.common;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.TableField;
import com.xueyi.common.core.constant.basic.DictConstants;
import com.xueyi.common.core.utils.core.StrUtil;
import com.xueyi.common.core.web.entity.base.TreeEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
/**
* Tree 混合基类
*
* @param Dto
* @author xueyi
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class CTreeEntity extends TreeEntity {
@Serial
private static final long serialVersionUID = 1L;
/** 公共数据(Y是 N否) */
@TableField(updateStrategy = FieldStrategy.NEVER)
protected String isCommon;
/** 校验是否为公共数据 */
public boolean isCommon() {
return StrUtil.equals(DictConstants.DicCommonPrivate.COMMON.getCode(), getIsCommon());
}
/** 校验是否非公共数据 */
public boolean isNotCommon() {
return !isCommon();
}
}
package com.xueyi.common.core.web.entity.model;
import com.github.pagehelper.Page;
import com.xueyi.common.core.web.entity.base.BaseEntity;
import java.util.Collection;
import java.util.List;
/**
* Base 基类 对象映射器
*
* @param Query
* @param Dto
* @param Po
* @author xueyi
*/
public interface BaseConverter<Q extends P, D extends P, P extends BaseEntity> {
/**
* 类型转换 | 持久化对象 -> 数据传输对象
*
* @param po 持久化对象
* @return dto
*/
D mapperDto(P po);
/**
* 类型转换 | 持久化对象集合 -> 数据传输对象集合
*
* @param poList 持久化对象集合
* @return dtoList
*/
List<D> mapperDto(Collection<P> poList);
/**
* 类型转换 | 持久化对象集合 -> 数据传输分页对象集合
*
* @param poList 持久化对象集合
* @return dtoList
*/
Page<D> mapperPageDto(Collection<P> poList);
/**
* 类型转换 | 数据传输对象 -> 持久化对象
*
* @param dto 数据传输对象
* @return po
*/
P mapperPo(D dto);
/**
* 类型转换 | 数据传输对象集合 -> 持久化对象集合
*
* @param dtoList 数据传输对象集合
* @return poList
*/
List<P> mapperPo(Collection<D> dtoList);
/**
* 类型转换 | 数据传输对象集合 -> 持久化分页对象集合
*
* @param dtoList 数据传输对象集合
* @return poList
*/
Page<P> mapperPagePo(Collection<D> dtoList);
}
package com.xueyi.common.core.web.entity.model;
import com.xueyi.common.core.web.entity.base.TreeEntity;
import org.mapstruct.Mapping;
import java.util.Collection;
import java.util.List;
/**
* Tree 基类 对象映射器
*
* @param Query
* @param Dto
* @param Po
* @author xueyi
*/
public interface TreeConverter<Q extends P, D extends P, P extends TreeEntity<D>> extends BaseConverter<Q, D, P> {
@Mapping(target = "children", ignore = true)
D mapperDto(P po);
@Mapping(target = "children", ignore = true)
List<D> mapperDto(Collection<P> poList);
@Mapping(target = "children", ignore = true)
P mapperPo(D po);
@Mapping(target = "children", ignore = true)
List<P> mapperPo(Collection<D> poList);
}