package com.component.msg.redis.util;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy;
import org.springframework.stereotype.Component;
import javax.persistence.Column;
import javax.persistence.Query;
import javax.persistence.Table;
import javax.persistence.Transient;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.FileSystem;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @description 将所有mysql对应的entity中所有属性转为mysql对应字段,并入redis缓存,用于简化sql查询代码
*/
@Component
public class SqlHelper implements ApplicationRunner {
@Autowired
private RedisUtil redisUtil;
private static final String BASIC_PACKAGES = "com.modules.system.domain";
public static final String REDIS_MYSQL_ENTITY_KEY = "mysql_entity";
private final Pattern underLinePattern = Pattern.compile("_(\\w)");
public static final List<String> defaultIgnoreFieldList;
public static final Map<String, String> defaultAppendFieldMap;
private static Map<String, JSONObject> localMap = new HashMap<>();
private static final ThreadLocal<SimpleDateFormat> sdf = ThreadLocal.withInitial(() ->
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
static {
defaultIgnoreFieldList = new ArrayList<String>() {{
add("createDate");
add("updateDate");
add("createBy");
add("updateBy");
}};
defaultAppendFieldMap = new TreeMap<String, String>() {{
put("creatTime", "create_date");
put("updateTime", "update_date");
}};
}
@Override
public void run(ApplicationArguments args) throws Exception {
redisUtil.del(REDIS_MYSQL_ENTITY_KEY);
scanMysqlEntity(args);
}
//扫描mysql实体类属性,入redis缓存
private void scanMysqlEntity(ApplicationArguments args) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
List<String> list = new ArrayList<>();
for (String packagePath : BASIC_PACKAGES.split(",")) {
list.addAll(getClazzName(packagePath, true));
}
try {
for (String clazz : list) {
getFieldsByClass(cl.loadClass(clazz));
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
/*try {
for (String packagePath : BASIC_PACKAGES.replace(".", "/").split(",")) {
Enumeration<URL> resources = cl.getResources(packagePath);
while (resources.hasMoreElements()) {
URL url = resources.nextElement();
File[] files = new File(url.getPath()).listFiles();
if (files != null) {
for (File classFile : files) {
cacheEntityFields(packagePath, classFile);
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}*/
}
private void cacheEntityFields(String packagePath, File classFile) throws ClassNotFoundException {
// 如果是java类文件 去掉后面的.class 只留下类名
String pureClassName = classFile.getName().substring(0, classFile.getName().lastIndexOf(".class"));
String path = packagePath.replace("/", ".") + "." + pureClassName;
//使用appclassloader,不使用restartclassloader
Class<?> clazz = ClassLoader.getSystemClassLoader().loadClass(path);
getFieldsByClass(clazz);
}
private JSONObject getFieldsByClass(Class clazz) {
String clazzName = clazz.getName();
if (localMap.containsKey(clazzName)) {
return localMap.get(clazzName);
}
if (!clazz.isAnnotationPresent(Table.class)) return null;
Field[] fields = clazz.getDeclaredFields();
//使用有序的tree
JSONObject columnMap = new JSONObject(16, true);
for (Field field : fields) {
if (!field.isAccessible()) field.setAccessible(true);
if (field.isAnnotationPresent(Transient.class)
|| defaultIgnoreFieldList.contains(field.getName()))
continue;
columnMap.put(field.getName(), getColumnName(field));
}
if (defaultAppendFieldMap.size() > 0) {
for (String key : defaultAppendFieldMap.keySet()) {
columnMap.put(key, defaultAppendFieldMap.get(key));
}
}
localMap.put(clazz.getName(), columnMap);
redisUtil.hashPut(REDIS_MYSQL_ENTITY_KEY, clazz.getName(), columnMap.toString());
return columnMap;
}
private String getColumnName(Field field) {
String columnName = null;
if (field.isAnnotationPresent(Column.class)) {
Column column = field.getAnnotation(Column.class);
columnName = column.name();
}
if (columnName != null && columnName.length() > 0)
return columnName;
return getDbColumnNameByFieldName(field.getName());
}
private String getDbColumnNameByFieldName(String fieldName) {
return apply(fieldName);
}
private String apply(String name) {
//参考 SpringPhysicalNamingStrategy
if (name == null) {
return null;
}
StringBuilder builder = new StringBuilder(name.replace('.', '_'));
for (int i = 1; i < builder.length() - 1; i++) {
if (isUnderscoreRequired(builder.charAt(i - 1), builder.charAt(i),
builder.charAt(i + 1))) {
builder.insert(i++, '_');
}
}
return builder.toString().toLowerCase(Locale.ROOT);
}
private static final String CLASS_SUFFIX = ".class";
private static final String CLASS_FILE_PREFIX = File.separator + "classes" + File.separator;
private static final String PACKAGE_SEPARATOR = ".";
public static List<String> getClazzName(String packageName, boolean showChildPackageFlag) {
List<String> result = new ArrayList<>();
String suffixPath = packageName.replaceAll("\\.", "/");
ClassLoader loader = Thread.currentThread().getContextClassLoader();
try {
Enumeration<URL> urls = loader.getResources(suffixPath);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
if (url != null) {
String protocol = url.getProtocol();
if ("file".equals(protocol)) {
String path = url.getPath();
result.addAll(getAllClassNameByFile(new File(path), showChildPackageFlag));
} else if ("jar".equals(protocol)) {
JarFile jarFile = null;
try {
jarFile = ((JarURLConnection) url.openConnection()).getJarFile();
} catch (Exception e) {
e.printStackTrace();
}
if (jarFile != null) {
result.addAll(getAllClassNameByJar(jarFile, packageName, showChildPackageFlag));
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
private static List<String> getAllClassNameByFile(File file, boolean flag) {
List<String> result = new ArrayList<>();
if (!file.exists()) {
return result;
}
if (file.isFile()) {
String path = file.getPath();
// 注意:这里替换文件分割符要用replace。因为replaceAll里面的参数是正则表达式,而windows环境中File.separator="\\"的,因此会有问题
if (path.endsWith(CLASS_SUFFIX)) {
path = path.replace(CLASS_SUFFIX, "");
String clazzName = path.substring(path.indexOf(CLASS_FILE_PREFIX) + CLASS_FILE_PREFIX.length())
.replace(File.separator, PACKAGE_SEPARATOR);
if (-1 == clazzName.indexOf("$")) {
result.add(clazzName);
}
}
return result;
} else {
File[] listFiles = file.listFiles();
if (listFiles != null && listFiles.length > 0) {
for (File f : listFiles) {
if (flag) {
result.addAll(getAllClassNameByFile(f, flag));
} else {
if (f.isFile()) {
String path = f.getPath();
if (path.endsWith(CLASS_SUFFIX)) {
path = path.replace(CLASS_SUFFIX, "");
// 从"/classes/"后面开始截取
String clazzName = path.substring(path.indexOf(CLASS_FILE_PREFIX) + CLASS_FILE_PREFIX.length())
.replace(File.separator, PACKAGE_SEPARATOR);
if (-1 == clazzName.indexOf("$")) {
result.add(clazzName);
}
}
}
}
}
}
return result;
}
}
private static List<String> getAllClassNameByJar(JarFile jarFile, String packageName, boolean flag) {
List<String> result = new ArrayList<>();
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry jarEntry = entries.nextElement();
String name = jarEntry.getName();
// 判断是不是class文件
if (name.endsWith(CLASS_SUFFIX)) {
name = name.replace(CLASS_SUFFIX, "").replace("/", ".");
if (flag) {
// 如果要子包的文件,那么就只要开头相同且不是内部类就ok
if (name.startsWith(packageName) && -1 == name.indexOf("$")) {
result.add(name);
}
} else {
// 如果不要子包的文件,那么就必须保证最后一个"."之前的字符串和包名一样且不是内部类
if (packageName.equals(name.substring(0, name.lastIndexOf("."))) && -1 == name.indexOf("$")) {
result.add(name);
}
}
}
}
return result;
}
private boolean isUnderscoreRequired(char before, char current, char after) {
return Character.isLowerCase(before) && Character.isUpperCase(current)
&& Character.isLowerCase(after);
}
/**
* 把app_id转换成appId
*
* @param value 要转换的下划线字符串
* @return 驼峰体字符串
*/
private String underLineToCamel(final String value) {
StringBuffer sb = new StringBuffer();
Matcher matcher = underLinePattern.matcher(value);
while (matcher.find()) {
matcher.appendReplacement(sb, matcher.group(1).toUpperCase());
}
matcher.appendTail(sb);
return sb.toString();
}
public JSONObject getEntityColumns(Class clazz) {
return getFieldsByClass(clazz);
/*Object value = redisUtil.hashGet(REDIS_MYSQL_ENTITY_KEY, clazz.getName());
return JSONUtil.parseObj(value, false, true);*/
}
public static String getFieldNamesByClazz(JSONObject entityColumns, String... ignoreFields) {
Set<String> fieldNames = entityColumns.keySet();
List<String> ignoreFieldList = null;
if (ignoreFields != null && ignoreFields.length > 0) {
ignoreFieldList = new ArrayList<>(Arrays.asList(ignoreFields));
}
List<String> list = new ArrayList<>();
for (String fieldName : fieldNames) {
if (ignoreFieldList == null || !ignoreFieldList.contains(fieldName)) {
list.add(fieldName);
}
}
return CollUtil.join(list, ",");
}
public static Query query(Query query, List<Object> paramList) {
if (paramList.size() > 0) {
int index = 0;
for (Object param : paramList) {
query.setParameter(++index, param);
}
}
return query;
}
public static String castToSql(JSONObject fields, String tableAlias, String... ignoreFields) {
List<String> ignoreFieldList = null;
if (ignoreFields != null && ignoreFields.length > 0) {
ignoreFieldList = new ArrayList<>(Arrays.asList(ignoreFields));
}
StringBuilder sql = new StringBuilder();
if (tableAlias != null && tableAlias.length() > 0) {
tableAlias = tableAlias.trim() + ".";
} else {
tableAlias = "";
}
for (String fieldName : fields.keySet()) {
if (ignoreFieldList == null || !ignoreFieldList.contains(fieldName)) {
String columnName = fields.getStr(fieldName);
sql.append(tableAlias).append(columnName);
if (!columnName.equals(fieldName)) {
sql.append(" as ").append(fieldName).append(",");
} else {
sql.append(",");
}
}
}
if (sql.length() > 0) {
sql.setLength(sql.length() - 1);
}
return sql.toString();
}
public static JSONArray objToArray(List<Object[]> list, String columns) {
JSONArray result = new JSONArray();
if (list != null && columns != null) {
String[] columnArr = columns.split(",");
int columnLen = columnArr.length;
for (Object[] row : list) {
JSONObject job = new JSONObject();
for (int index = 0; index < columnLen; index++) {
job.put(columnArr[index], row[index] == null ? "" : row[index]);
}
result.add(job);
}
}
return result;
}
public static JSONArray formatTimeDefault(JSONArray jsonArray) {
return formatTime(jsonArray, "creatTime", "updateTime");
}
public static JSONArray formatTime(JSONArray jsonArray, String... formatFields) {
if (jsonArray != null && formatFields != null && formatFields.length > 0) {
Iterator<Object> iterator = jsonArray.iterator();
while (iterator.hasNext()) {
JSONObject jsonObject = (JSONObject) iterator.next();
for (String formatField : formatFields) {
Date date = jsonObject.getDate(formatField);
if (date != null) {
jsonObject.put(formatField, sdf.get().format(date));
}
}
}
}
return jsonArray;
}
}