mirror of
https://gitee.com/orangeform/orange-admin.git
synced 2026-01-17 10:36:31 +08:00
created
This commit is contained in:
25
orange-admin-service/common/common-biz/pom.xml
Normal file
25
orange-admin-service/common/common-biz/pom.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.orange.admin</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>common-biz</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<name>common-biz</name>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.orange.admin</groupId>
|
||||
<artifactId>common-core</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.orange.admin.common.biz.advice;
|
||||
|
||||
import org.springframework.beans.propertyeditors.CustomDateEditor;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.InitBinder;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Controller的环绕拦截类。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
@ControllerAdvice
|
||||
public class MyControllerAdvice {
|
||||
|
||||
/**
|
||||
* 转换前端传入的日期变量参数为指定格式。
|
||||
*
|
||||
* @param binder 数据绑定参数。
|
||||
*/
|
||||
@InitBinder
|
||||
public void initBinder(WebDataBinder binder) {
|
||||
binder.registerCustomEditor(Date.class,
|
||||
new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"), false));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.orange.admin.common.biz.advice;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import com.orange.admin.common.core.constant.ErrorCodeEnum;
|
||||
import com.orange.admin.common.core.exception.RedisCacheAccessException;
|
||||
import com.orange.admin.common.core.object.ResponseResult;
|
||||
import org.apache.ibatis.exceptions.PersistenceException;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.dao.PermissionDeniedDataAccessException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* 业务层的异常处理类,这里只是给出最通用的Exception的捕捉,今后可以根据业务需要,
|
||||
* 用不同的函数,处理不同类型的异常。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
@Slf4j
|
||||
@RestControllerAdvice
|
||||
public class MyExceptionHandler {
|
||||
|
||||
@ExceptionHandler(value = Exception.class)
|
||||
public ResponseResult<?> exceptionHandle(Exception ex, HttpServletRequest request) {
|
||||
log.error("Unhandled exception from URL [" + request.getRequestURI() + "]", ex);
|
||||
return ResponseResult.error(ErrorCodeEnum.UNHANDLED_EXCEPTION);
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = DuplicateKeyException.class)
|
||||
public ResponseResult<?> duplicateKeyExceptionHandle(Exception ex, HttpServletRequest request) {
|
||||
log.error("DuplicateKeyException exception from URL [" + request.getRequestURI() + "]", ex);
|
||||
return ResponseResult.error(ErrorCodeEnum.DUPLICATED_UNIQUE_KEY);
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = DataAccessException.class)
|
||||
public ResponseResult<?> dataAccessExceptionHandle(Exception ex, HttpServletRequest request) {
|
||||
log.error("DataAccessException exception from URL [" + request.getRequestURI() + "]", ex);
|
||||
if (ex.getCause() instanceof PersistenceException
|
||||
&& ex.getCause().getCause() instanceof PermissionDeniedDataAccessException) {
|
||||
return ResponseResult.error(ErrorCodeEnum.DATA_PERM_ACCESS_FAILED);
|
||||
}
|
||||
return ResponseResult.error(ErrorCodeEnum.DATA_ACCESS_FAILED);
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = RedisCacheAccessException.class)
|
||||
public ResponseResult<?> redisCacheAccessExceptionHandle(Exception ex, HttpServletRequest request) {
|
||||
log.error("RedisCacheAccessException exception from URL [" + request.getRequestURI() + "]", ex);
|
||||
if (ex.getCause() instanceof TimeoutException) {
|
||||
return ResponseResult.error(ErrorCodeEnum.REDIS_CACHE_ACCESS_TIMEOUT);
|
||||
}
|
||||
return ResponseResult.error(ErrorCodeEnum.REDIS_CACHE_ACCESS_STATE_ERROR);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.orange.admin.common.biz.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 应用程序自定义的通用属性配置文件。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
@Data
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "application.common-biz")
|
||||
public class CommonBizConfig {
|
||||
|
||||
/**
|
||||
* Snowflake计算主键Id时所需的WorkNode参数值。
|
||||
*/
|
||||
private Integer snowflakeWorkNode;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.orange.admin.common.biz.config;
|
||||
|
||||
import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||
import com.alibaba.fastjson.support.config.FastJsonConfig;
|
||||
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
|
||||
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* DTA 对象数据格式转换器,这里集成了alibaba的fastjson,作为消息格式转换工具
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
@Configuration
|
||||
public class FastjsonConfig {
|
||||
|
||||
@Bean
|
||||
public HttpMessageConverters fastJsonHttpMessageConverters() {
|
||||
FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
|
||||
FastJsonConfig fastJsonConfig = new FastJsonConfig();
|
||||
fastJsonConfig.setSerializerFeatures(
|
||||
SerializerFeature.PrettyFormat, SerializerFeature.DisableCircularReferenceDetect);
|
||||
fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
fastConverter.setFastJsonConfig(fastJsonConfig);
|
||||
return new HttpMessageConverters(fastConverter);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.orange.admin.common.biz.config;
|
||||
|
||||
import org.apache.tomcat.util.descriptor.web.SecurityCollection;
|
||||
import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
|
||||
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* tomcat配置对象。当前配置禁用了PUT和DELETE方法,防止渗透攻击。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
@Configuration
|
||||
public class TomcatConfig {
|
||||
|
||||
@Bean
|
||||
public TomcatServletWebServerFactory servletContainer() {
|
||||
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
|
||||
factory.addContextCustomizers(context -> {
|
||||
SecurityConstraint securityConstraint = new SecurityConstraint();
|
||||
securityConstraint.setUserConstraint("CONFIDENTIAL");
|
||||
SecurityCollection collection = new SecurityCollection();
|
||||
collection.addPattern("/*");
|
||||
collection.addMethod("HEAD");
|
||||
collection.addMethod("PUT");
|
||||
collection.addMethod("PATCH");
|
||||
collection.addMethod("DELETE");
|
||||
collection.addMethod("TRACE");
|
||||
collection.addMethod("COPY");
|
||||
collection.addMethod("SEARCH");
|
||||
collection.addMethod("PROPFIND");
|
||||
securityConstraint.addCollection(collection);
|
||||
context.addConstraint(securityConstraint);
|
||||
});
|
||||
return factory;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.orange.admin.common.biz.constant;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public final class Subject {
|
||||
|
||||
/**
|
||||
* 语文。
|
||||
*/
|
||||
public static final int CHINESE = 0;
|
||||
/**
|
||||
* 数学。
|
||||
*/
|
||||
public static final int MATH = 1;
|
||||
/**
|
||||
* 英语。
|
||||
*/
|
||||
public static final int ENGLISH = 2;
|
||||
|
||||
public static final Map<Object, String> DICT_MAP = new HashMap<>(3);
|
||||
static {
|
||||
DICT_MAP.put(CHINESE, "语文");
|
||||
DICT_MAP.put(MATH, "数学");
|
||||
DICT_MAP.put(ENGLISH, "英语");
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断参数是否为当前常量字典的合法值。
|
||||
*
|
||||
* @param value 待验证的参数值。
|
||||
* @return 合法返回true,否则false。
|
||||
*/
|
||||
public static boolean isValid(int value) {
|
||||
return DICT_MAP.containsKey(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 私有构造函数,明确标识该常量类的作用。
|
||||
*/
|
||||
private Subject() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.orange.admin.common.biz.constant;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public final class YesNo {
|
||||
|
||||
/**
|
||||
* 是。
|
||||
*/
|
||||
public static final int YES = 1;
|
||||
/**
|
||||
* 否。
|
||||
*/
|
||||
public static final int NO = 0;
|
||||
|
||||
public static final Map<Object, String> DICT_MAP = new HashMap<>(2);
|
||||
static {
|
||||
DICT_MAP.put(YES, "是");
|
||||
DICT_MAP.put(NO, "否");
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断参数是否为当前常量字典的合法值。
|
||||
*
|
||||
* @param value 待验证的参数值。
|
||||
* @return 合法返回true,否则false。
|
||||
*/
|
||||
public static boolean isValid(int value) {
|
||||
return DICT_MAP.containsKey(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 私有构造函数,明确标识该常量类的作用。
|
||||
*/
|
||||
private YesNo() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.orange.admin.common.biz.interceptor;
|
||||
|
||||
import com.orange.admin.common.core.object.GlobalThreadLocal;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* 服务访问日志的拦截器,主要完成记录接口调用时长。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
@Slf4j
|
||||
public class AccessInterceptor implements HandlerInterceptor {
|
||||
|
||||
private static final ThreadLocal<Long> STARTTIME_THREADLOCAL = new ThreadLocal<>();
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
|
||||
throws Exception {
|
||||
STARTTIME_THREADLOCAL.set(System.currentTimeMillis());
|
||||
// 每次进入Controller接口之前,均主动打开数据权限验证。
|
||||
// 可以避免该Servlet线程在处理之前的请求时异常退出,从而导致该状态数据没有被正常清除。
|
||||
GlobalThreadLocal.setDataPerm(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
|
||||
ModelAndView modelAndView) throws Exception {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
|
||||
throws Exception {
|
||||
long startTime = STARTTIME_THREADLOCAL.get();
|
||||
long elapse = System.currentTimeMillis() - startTime;
|
||||
String urlPath = request.getRequestURI();
|
||||
String controllerMethod = "Unknown";
|
||||
if (handler instanceof HandlerMethod) {
|
||||
HandlerMethod hm = (HandlerMethod) handler;
|
||||
String controllerName = hm.getBean().getClass().getName();
|
||||
// 这里将cglib织入的部分去掉,提升日志的可读性
|
||||
int index = controllerName.indexOf("$$");
|
||||
if (index > 0) {
|
||||
controllerName = controllerName.substring(0, index);
|
||||
}
|
||||
controllerMethod = controllerName + "." + hm.getMethod().getName();
|
||||
}
|
||||
log.info("access: {} -- elapse {} ms -- {}.", controllerMethod, elapse, urlPath);
|
||||
STARTTIME_THREADLOCAL.remove();
|
||||
GlobalThreadLocal.clearDataPerm();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
package com.orange.admin.common.biz.interceptor;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.orange.admin.common.core.annotation.MyRequestBody;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.web.bind.support.WebDataBinderFactory;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* MyRequestBody解析器
|
||||
* 解决的问题:
|
||||
* 1、单个字符串等包装类型都要写一个对象才可以用@RequestBody接收;
|
||||
* 2、多个对象需要封装到一个对象里才可以用@RequestBody接收。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
public class MyRequestArgumentResolver implements HandlerMethodArgumentResolver {
|
||||
|
||||
private static final String JSONBODY_ATTRIBUTE = "MY_REQUEST_BODY_ATTRIBUTE_XX";
|
||||
|
||||
private static Set<Class<?>> classSet = new HashSet<>();
|
||||
|
||||
static {
|
||||
classSet.add(Integer.class);
|
||||
classSet.add(Long.class);
|
||||
classSet.add(Short.class);
|
||||
classSet.add(Float.class);
|
||||
classSet.add(Double.class);
|
||||
classSet.add(Boolean.class);
|
||||
classSet.add(Byte.class);
|
||||
classSet.add(Character.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置支持的方法参数类型
|
||||
*
|
||||
* @param parameter 方法参数
|
||||
* @return 支持的类型
|
||||
*/
|
||||
@Override
|
||||
public boolean supportsParameter(@NonNull MethodParameter parameter) {
|
||||
return parameter.hasParameterAnnotation(MyRequestBody.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数解析,利用fastjson
|
||||
* 注意:非基本类型返回null会报空指针异常,要通过反射或者JSON工具类创建一个空对象
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object resolveArgument(
|
||||
@NonNull MethodParameter parameter,
|
||||
ModelAndViewContainer mavContainer,
|
||||
@NonNull NativeWebRequest webRequest,
|
||||
WebDataBinderFactory binderFactory) throws Exception {
|
||||
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
|
||||
String contentType = servletRequest.getContentType();
|
||||
if (!HttpMethod.POST.name().equals(servletRequest.getMethod())) {
|
||||
throw new IllegalArgumentException("Only POST method can be applied @MyRequestBody annotation!");
|
||||
}
|
||||
if (!StringUtils.containsIgnoreCase(contentType, "application/json")) {
|
||||
throw new IllegalArgumentException(
|
||||
"Only application/json Content-Type can be applied @MyRequestBody annotation!");
|
||||
}
|
||||
// 根据@MyRequestBody注解value作为json解析的key
|
||||
MyRequestBody parameterAnnotation = parameter.getParameterAnnotation(MyRequestBody.class);
|
||||
JSONObject jsonObject = getRequestBody(webRequest);
|
||||
if (jsonObject == null) {
|
||||
if (parameterAnnotation.required()) {
|
||||
throw new IllegalArgumentException("Request Body is EMPTY!");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
String key = parameterAnnotation.value();
|
||||
if (StringUtils.isBlank(key)) {
|
||||
key = parameter.getParameterName();
|
||||
}
|
||||
Object value = jsonObject.get(key);
|
||||
if (value == null) {
|
||||
if (parameterAnnotation.required()) {
|
||||
throw new IllegalArgumentException(String.format("Required parameter %s is not present!", key));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
// 获取参数类型。
|
||||
Class<?> parameterType = parameter.getParameterType();
|
||||
//基本类型
|
||||
if (parameterType.isPrimitive()) {
|
||||
return parsePrimitive(parameterType.getName(), value);
|
||||
}
|
||||
// 基本类型包装类
|
||||
if (isBasicDataTypes(parameterType)) {
|
||||
return parseBasicTypeWrapper(parameterType, value);
|
||||
// 字符串类型
|
||||
} else if (parameterType == String.class) {
|
||||
return value.toString();
|
||||
}
|
||||
if (value instanceof JSONArray) {
|
||||
Object o;
|
||||
if (!parameterType.equals(List.class)) {
|
||||
o = parameterType.newInstance();
|
||||
parameterType = (Class<?>) ((ParameterizedType)
|
||||
parameterType.getGenericSuperclass()).getActualTypeArguments()[0];
|
||||
} else {
|
||||
parameterType = parameterAnnotation.elementType();
|
||||
if (parameterType.equals(Class.class)) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("List Type parameter %s MUST have elementType!", key));
|
||||
}
|
||||
o = new LinkedList<>();
|
||||
}
|
||||
if (!(o instanceof List)) {
|
||||
throw new IllegalArgumentException(String.format("Required parameter %s is List!", key));
|
||||
}
|
||||
((List<Object>) o).addAll(((JSONArray) value).toJavaList(parameterType));
|
||||
return o;
|
||||
}
|
||||
// 其他复杂对象
|
||||
return JSONObject.toJavaObject((JSONObject) value, parameterType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 基本类型解析
|
||||
*/
|
||||
private Object parsePrimitive(String parameterTypeName, Object value) {
|
||||
final String booleanTypeName = "boolean";
|
||||
if (booleanTypeName.equals(parameterTypeName)) {
|
||||
return Boolean.valueOf(value.toString());
|
||||
}
|
||||
final String intTypeName = "int";
|
||||
if (intTypeName.equals(parameterTypeName)) {
|
||||
return Integer.valueOf(value.toString());
|
||||
}
|
||||
final String charTypeName = "char";
|
||||
if (charTypeName.equals(parameterTypeName)) {
|
||||
return value.toString().charAt(0);
|
||||
}
|
||||
final String shortTypeName = "short";
|
||||
if (shortTypeName.equals(parameterTypeName)) {
|
||||
return Short.valueOf(value.toString());
|
||||
}
|
||||
final String longTypeName = "long";
|
||||
if (longTypeName.equals(parameterTypeName)) {
|
||||
return Long.valueOf(value.toString());
|
||||
}
|
||||
final String floatTypeName = "float";
|
||||
if (floatTypeName.equals(parameterTypeName)) {
|
||||
return Float.valueOf(value.toString());
|
||||
}
|
||||
final String doubleTypeName = "double";
|
||||
if (doubleTypeName.equals(parameterTypeName)) {
|
||||
return Double.valueOf(value.toString());
|
||||
}
|
||||
final String byteTypeName = "byte";
|
||||
if (byteTypeName.equals(parameterTypeName)) {
|
||||
return Byte.valueOf(value.toString());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 基本类型包装类解析
|
||||
*/
|
||||
private Object parseBasicTypeWrapper(Class<?> parameterType, Object value) {
|
||||
if (Number.class.isAssignableFrom(parameterType)) {
|
||||
if (value instanceof String) {
|
||||
return Convert.convert(parameterType, value);
|
||||
}
|
||||
Number number = (Number) value;
|
||||
if (parameterType == Integer.class) {
|
||||
return number.intValue();
|
||||
} else if (parameterType == Short.class) {
|
||||
return number.shortValue();
|
||||
} else if (parameterType == Long.class) {
|
||||
return number.longValue();
|
||||
} else if (parameterType == Float.class) {
|
||||
return number.floatValue();
|
||||
} else if (parameterType == Double.class) {
|
||||
return number.doubleValue();
|
||||
} else if (parameterType == Byte.class) {
|
||||
return number.byteValue();
|
||||
}
|
||||
} else if (parameterType == Boolean.class) {
|
||||
return value.toString();
|
||||
} else if (parameterType == Character.class) {
|
||||
return value.toString().charAt(0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为基本数据类型包装类
|
||||
*/
|
||||
private boolean isBasicDataTypes(Class<?> clazz) {
|
||||
return classSet.contains(clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求体JSON字符串
|
||||
*/
|
||||
private JSONObject getRequestBody(NativeWebRequest webRequest) {
|
||||
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
|
||||
// 有就直接获取
|
||||
JSONObject jsonObject = (JSONObject) webRequest.getAttribute(JSONBODY_ATTRIBUTE, NativeWebRequest.SCOPE_REQUEST);
|
||||
// 没有就从请求中读取
|
||||
if (jsonObject == null) {
|
||||
try {
|
||||
String jsonBody = IOUtils.toString(servletRequest.getReader());
|
||||
jsonObject = JSON.parseObject(jsonBody);
|
||||
if (jsonObject != null) {
|
||||
webRequest.setAttribute(JSONBODY_ATTRIBUTE, jsonObject, NativeWebRequest.SCOPE_REQUEST);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return jsonObject;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.orange.admin.common.biz.listener;
|
||||
|
||||
import com.orange.admin.common.core.base.service.BaseDictService;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 应用程序启动后的事件监听对象。主要负责加载Model之间的字典关联和一对一关联所对应的Service结构关系。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
@Component
|
||||
public class LoadCachedDataListener implements ApplicationListener<ApplicationReadyEvent> {
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
|
||||
Map<String, BaseDictService> serviceMap =
|
||||
applicationReadyEvent.getApplicationContext().getBeansOfType(BaseDictService.class);
|
||||
for (Map.Entry<String, BaseDictService> e : serviceMap.entrySet()) {
|
||||
e.getValue().loadCachedData();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.orange.admin.common.biz.listener;
|
||||
|
||||
import com.orange.admin.common.core.base.service.BaseService;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 应用程序启动后的事件监听对象。主要负责加载Model之间的字典关联和一对一关联所对应的Service结构关系。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
@Component
|
||||
public class LoadServiceRelationListener implements ApplicationListener<ApplicationReadyEvent> {
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
|
||||
Map<String, BaseService> serviceMap =
|
||||
applicationReadyEvent.getApplicationContext().getBeansOfType(BaseService.class);
|
||||
for (Map.Entry<String, BaseService> e : serviceMap.entrySet()) {
|
||||
e.getValue().loadRelationStruct();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.orange.admin.common.biz.util;
|
||||
|
||||
import cn.hutool.core.lang.Snowflake;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import com.orange.admin.common.biz.config.CommonBizConfig;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
/**
|
||||
* 全局共享的snowflake计算工具类。
|
||||
* 基于Hutool中的对应工具类实现,同时读取系统的配置参数并初始化该对象。如果今后
|
||||
* 升级为基于分布式Id生成服务的实现时,仅需修改内部实现,外部业务方法不会受到任何影响。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
@Component
|
||||
public class BasicIdGenerator {
|
||||
|
||||
@Autowired
|
||||
private CommonBizConfig config;
|
||||
|
||||
private Snowflake snowflake;
|
||||
|
||||
@PostConstruct
|
||||
private void init() {
|
||||
snowflake = IdUtil.createSnowflake(config.getSnowflakeWorkNode(), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取基于Snowflake算法的数值型Id。
|
||||
* 由于底层实现为synchronized方法,因此计算过程串行化,且线程安全。
|
||||
*
|
||||
* @return 计算后的全局唯一Id。
|
||||
*/
|
||||
public long nextLongId() {
|
||||
return this.snowflake.nextId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取基于Snowflake算法的字符串Id。
|
||||
* 由于底层实现为synchronized方法,因此计算过程串行化,且线程安全。
|
||||
*
|
||||
* @return 计算后的全局唯一Id。
|
||||
*/
|
||||
public String nextStringId() {
|
||||
return this.snowflake.nextIdStr();
|
||||
}
|
||||
}
|
||||
107
orange-admin-service/common/common-core/pom.xml
Normal file
107
orange-admin-service/common/common-core/pom.xml
Normal file
@@ -0,0 +1,107 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.orange.admin</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>common-core</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<name>common-core</name>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
<!-- 常用工具 -->
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-codec</groupId>
|
||||
<artifactId>commons-codec</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-fileupload</groupId>
|
||||
<artifactId>commons-fileupload</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>joda-time</groupId>
|
||||
<artifactId>joda-time</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-collections4</artifactId>
|
||||
<version>${commons-collections4.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-csv</artifactId>
|
||||
<version>${common-csv.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>${hutool.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt</artifactId>
|
||||
<version>${jjwt.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
<version>${fastjson.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
<artifactId>caffeine</artifactId>
|
||||
<version>${caffeine.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.jimmyshi</groupId>
|
||||
<artifactId>bean-query</artifactId>
|
||||
<version>${bean.query.version}</version>
|
||||
</dependency>
|
||||
<!-- poi相关工具包 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi-ooxml</artifactId>
|
||||
<version>${poi-ooxml.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 数据库访问层相关工具 -->
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>druid-spring-boot-starter</artifactId>
|
||||
<version>${druid.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>tk.mybatis</groupId>
|
||||
<artifactId>mapper-spring-boot-starter</artifactId>
|
||||
<version>${mybatis-mapper.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.pagehelper</groupId>
|
||||
<artifactId>pagehelper-spring-boot-starter</artifactId>
|
||||
<version>${pagehelper.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.orange.admin.common.core.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 主要用于标记逻辑删除字段。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
@Target({ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface DeletedFlagColumn {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.orange.admin.common.core.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 主要用于标记数据权限中基于DeptId进行过滤的字段。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
@Target({ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface DeptFilterColumn {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.orange.admin.common.core.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 用于注解DAO层Mapper对象的数据权限规则。
|
||||
* 由于框架使用了tk.mapper,所以并非所有的Mapper接口均在当前Mapper对象中定义,有一部分被tk.mapper封装,如selectAll等。
|
||||
* 如果需要排除tk.mapper中的方法,可以直接使用tk.mapper基类所声明的方法名称即可。
|
||||
* 另外,比较特殊的场景是,因为tk.mapper是通用框架,所以同样的selectAll方法,可以获取不同的数据集合,因此在service中如果
|
||||
* 出现两个不同的方法调用Mapper的selectAll方法,但是一个需要参与过滤,另外一个不需要参与,那么就需要修改当前类的Mapper方法,
|
||||
* 将其中一个方法重新定义一个具体的接口方法,并重新设定其是否参与数据过滤。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
@Target({ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface EnableDataPerm {
|
||||
|
||||
/**
|
||||
* 排除的方法名称数组。如果为空,所有的方法均会被Mybaits拦截注入权限过滤条件。
|
||||
*
|
||||
* @return 被排序的方法名称数据。
|
||||
*/
|
||||
String[] excluseMethodName() default {};
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.orange.admin.common.core.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 主要用于标记更新字段。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
@Target({ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface JobUpdateTimeColumn {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.orange.admin.common.core.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 主要用于标记Service所依赖的数据源类型。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface MyDataSource {
|
||||
|
||||
/**
|
||||
* 标注的数据源类型
|
||||
* @return 当前标注的数据源类型。
|
||||
*/
|
||||
int value();
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.orange.admin.common.core.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 标记Controller中的方法参数,参数解析器会根据该注解将请求中的JSON数据,映射到参数中的绑定字段。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
@Target(ElementType.PARAMETER)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface MyRequestBody {
|
||||
|
||||
/**
|
||||
* 是否必须出现的参数。
|
||||
*/
|
||||
boolean required() default false;
|
||||
/**
|
||||
* 解析时用到的JSON的key。
|
||||
*/
|
||||
String value() default "";
|
||||
/**
|
||||
* 集合元素的ClassType。只有在接口参数为List<E>的时候,需要把E的class传入。
|
||||
* 缺省值Class.class表示没有设置。
|
||||
*/
|
||||
Class<?> elementType() default Class.class;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.orange.admin.common.core.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 主要用于标记无需Token验证的接口
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface NoAuthInterface {
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.orange.admin.common.core.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 标识Model和常量字典之间的关联关系。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
@Target({ElementType.FIELD, ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface RelationConstDict {
|
||||
|
||||
/**
|
||||
* 当前对象的关联Id字段名称。
|
||||
*
|
||||
* @return 当前对象的关联Id字段名称。
|
||||
*/
|
||||
String masterIdField();
|
||||
|
||||
/**
|
||||
* 被关联的常量字典的Class对象。
|
||||
*
|
||||
* @return 关联的常量字典的Class对象。
|
||||
*/
|
||||
Class<?> constantDictClass();
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.orange.admin.common.core.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 标识Model之间的字典关联关系。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
@Target({ElementType.FIELD, ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface RelationDict {
|
||||
|
||||
/**
|
||||
* 当前对象的关联Id字段名称。
|
||||
*
|
||||
* @return 当前对象的关联Id字段名称。
|
||||
*/
|
||||
String masterIdField();
|
||||
|
||||
/**
|
||||
* 被关联Model对象的Class对象。
|
||||
*
|
||||
* @return 被关联Model对象的Class对象。
|
||||
*/
|
||||
Class<?> slaveModelClass();
|
||||
|
||||
/**
|
||||
* 被关联Model对象的关联Id字段名称。
|
||||
*
|
||||
* @return 被关联Model对象的关联Id字段名称。
|
||||
*/
|
||||
String slaveIdField();
|
||||
|
||||
/**
|
||||
* 被关联Model对象的关联Name字段名称。
|
||||
*
|
||||
* @return 被关联Model对象的关联Name字段名称。
|
||||
*/
|
||||
String slaveNameField();
|
||||
|
||||
/**
|
||||
* 被关联的本地Service对象名称。
|
||||
*
|
||||
* @return 被关联的本地Service对象名称。
|
||||
*/
|
||||
String slaveServiceName();
|
||||
|
||||
/**
|
||||
* 在同一个实体对象中,如果有一对一关联和字典关联,都是基于相同的主表字段,并关联到
|
||||
* 相同关联表的同一关联字段时,可以在字典关联的注解中引用被一对一注解标准的对象属性。
|
||||
* 从而在数据整合时,当前字典的数据可以直接取自"equalOneToOneRelationField"指定
|
||||
* 的字段,从而避免一次没必要的数据库查询操作,提升了加载显示的效率。
|
||||
*
|
||||
* @return 与该字典字段引用关系完全相同的一对一关联属性名称。
|
||||
*/
|
||||
String equalOneToOneRelationField() default "";
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.orange.admin.common.core.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 主要用于多对多的Model关系。标注通过从表关联字段或者关联表关联字段计算主表虚拟字段的规则。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
@Target({ElementType.FIELD, ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface RelationManyToManyAggregation {
|
||||
|
||||
/**
|
||||
* 当前对象的关联Id字段名称。
|
||||
*
|
||||
* @return 当前对象的关联Id字段名称。
|
||||
*/
|
||||
String masterIdField();
|
||||
|
||||
/**
|
||||
* 被关联的本地Service对象名称。
|
||||
*
|
||||
* @return 被关联的本地Service对象名称。
|
||||
*/
|
||||
String slaveServiceName();
|
||||
|
||||
/**
|
||||
* 多对多从表Model对象的Class对象。
|
||||
*
|
||||
* @return 被关联Model对象的Class对象。
|
||||
*/
|
||||
Class<?> slaveModelClass();
|
||||
|
||||
/**
|
||||
* 多对多从表Model对象的关联Id字段名称。
|
||||
*
|
||||
* @return 被关联Model对象的关联Id字段名称。
|
||||
*/
|
||||
String slaveIdField();
|
||||
|
||||
/**
|
||||
* 多对多关联表Model对象的Class对象。
|
||||
*
|
||||
* @return 被关联Model对象的Class对象。
|
||||
*/
|
||||
Class<?> relationModelClass();
|
||||
|
||||
/**
|
||||
* 多对多关联表Model对象中与主表关联的Id字段名称。
|
||||
*
|
||||
* @return 被关联Model对象的关联Id字段名称。
|
||||
*/
|
||||
String relationMasterIdField();
|
||||
|
||||
/**
|
||||
* 多对多关联表Model对象中与从表关联的Id字段名称。
|
||||
*
|
||||
* @return 被关联Model对象的关联Id字段名称。
|
||||
*/
|
||||
String relationSlaveIdField();
|
||||
|
||||
/**
|
||||
* 聚合计算所在的Model。
|
||||
*
|
||||
* @return 聚合计算所在Model的Class。
|
||||
*/
|
||||
Class<?> aggregationModelClass();
|
||||
|
||||
/**
|
||||
* 聚合类型。具体数值参考AggregationType对象。
|
||||
*
|
||||
* @return 聚合类型。
|
||||
*/
|
||||
int aggregationType();
|
||||
|
||||
/**
|
||||
* 聚合计算所在Model的字段名称。
|
||||
*
|
||||
* @return 聚合计算所在Model的字段名称。
|
||||
*/
|
||||
String aggregationField();
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.orange.admin.common.core.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 主要用于一对多的Model关系。标注通过从表关联字段计算主表虚拟字段的规则。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
@Target({ElementType.FIELD, ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface RelationOneToManyAggregation {
|
||||
|
||||
/**
|
||||
* 当前对象的关联Id字段名称。
|
||||
*
|
||||
* @return 当前对象的关联Id字段名称。
|
||||
*/
|
||||
String masterIdField();
|
||||
|
||||
/**
|
||||
* 被关联的本地Service对象名称。
|
||||
*
|
||||
* @return 被关联的本地Service对象名称。
|
||||
*/
|
||||
String slaveServiceName();
|
||||
|
||||
/**
|
||||
* 被关联Model对象的Class对象。
|
||||
*
|
||||
* @return 被关联Model对象的Class对象。
|
||||
*/
|
||||
Class<?> slaveModelClass();
|
||||
|
||||
/**
|
||||
* 被关联Model对象的关联Id字段名称。
|
||||
*
|
||||
* @return 被关联Model对象的关联Id字段名称。
|
||||
*/
|
||||
String slaveIdField();
|
||||
|
||||
/**
|
||||
* 被关联Model对象中参与计算的聚合类型。具体数值参考AggregationType对象。
|
||||
*
|
||||
* @return 被关联Model对象中参与计算的聚合类型。
|
||||
*/
|
||||
int aggregationType();
|
||||
|
||||
/**
|
||||
* 被关联Model对象中参与聚合计算的字段名称。
|
||||
*
|
||||
* @return 被关联Model对象中参与计算字段的名称。
|
||||
*/
|
||||
String aggregationField();
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.orange.admin.common.core.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 标识Model之间的一对一关联关系。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
@Target({ElementType.FIELD, ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface RelationOneToOne {
|
||||
|
||||
/**
|
||||
* 当前对象的关联Id字段名称。
|
||||
*
|
||||
* @return 当前对象的关联Id字段名称。
|
||||
*/
|
||||
String masterIdField();
|
||||
|
||||
/**
|
||||
* 被关联Model对象的Class对象。
|
||||
*
|
||||
* @return 被关联Model对象的Class对象。
|
||||
*/
|
||||
Class<?> slaveModelClass();
|
||||
|
||||
/**
|
||||
* 被关联Model对象的关联Id字段名称。
|
||||
*
|
||||
* @return 被关联Model对象的关联Id字段名称。
|
||||
*/
|
||||
String slaveIdField();
|
||||
|
||||
/**
|
||||
* 被关联的本地Service对象名称。
|
||||
*
|
||||
* @return 被关联的本地Service对象名称。
|
||||
*/
|
||||
String slaveServiceName();
|
||||
|
||||
/**
|
||||
* 在一对一关联时,是否加载从表的字典关联。
|
||||
*
|
||||
* @return 是否加载从表的字典关联。true关联,false则只返回从表自身数据。
|
||||
*/
|
||||
boolean loadSlaveDict() default true;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.orange.admin.common.core.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 主要用于标记数据权限中基于UserId进行过滤的字段。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
@Target({ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface UserFilterColumn {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.orange.admin.common.core.base.dao;
|
||||
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import tk.mybatis.mapper.additional.insert.InsertListMapper;
|
||||
import tk.mybatis.mapper.annotation.RegisterMapper;
|
||||
import tk.mybatis.mapper.common.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 数据访问对象的基类。
|
||||
*
|
||||
* @param <M> 主Model实体对象。
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
@RegisterMapper
|
||||
public interface BaseDaoMapper<M> extends Mapper<M>, InsertListMapper<M> {
|
||||
|
||||
/**
|
||||
* 根据指定的表名、显示字段列表、过滤条件字符串和分组字段,返回聚合计算后的查询结果。
|
||||
*
|
||||
* @param selectTable 表名称。
|
||||
* @param selectFields 返回字段列表,逗号分隔。
|
||||
* @param whereClause SQL常量形式的条件从句。
|
||||
* @param groupBy 分组字段列表,逗号分隔。
|
||||
* @return 对象可选字段Map列表。
|
||||
*/
|
||||
@Select("<script>"
|
||||
+ "SELECT ${selectFields} FROM ${selectTable}"
|
||||
+ "<where>"
|
||||
+ " <if test=\"whereClause != null and whereClause != ''\">"
|
||||
+ " AND ${whereClause}"
|
||||
+ " </if>"
|
||||
+ "</where>"
|
||||
+ "<if test=\"groupBy != null and groupBy != ''\">"
|
||||
+ " GROUP BY ${groupBy}"
|
||||
+ "</if>"
|
||||
+ "</script>")
|
||||
<T> List<Map<String, Object>> getGroupedListByCondition(
|
||||
@Param("selectTable") String selectTable,
|
||||
@Param("selectFields") String selectFields,
|
||||
@Param("whereClause") String whereClause,
|
||||
@Param("groupBy") String groupBy);
|
||||
|
||||
/**
|
||||
* 根据指定的表名、显示字段列表、过滤条件字符串和排序字符串,返回查询结果。
|
||||
*
|
||||
* @param selectTable 表名称。
|
||||
* @param selectFields 选择的字段列表。
|
||||
* @param whereClause 过滤字符串。
|
||||
* @param orderBy 排序字符串。
|
||||
* @return 查询结果。
|
||||
*/
|
||||
@Select("<script>"
|
||||
+ "SELECT ${selectFields} FROM ${selectTable}"
|
||||
+ "<where>"
|
||||
+ " <if test=\"whereClause != null and whereClause != ''\">"
|
||||
+ " AND ${whereClause}"
|
||||
+ " </if>"
|
||||
+ "</where>"
|
||||
+ "<if test=\"orderBy != null and orderBy != ''\">"
|
||||
+ " ORDER BY ${orderBy}"
|
||||
+ "</if>"
|
||||
+ "</script>")
|
||||
<T> List<Map<String, Object>> getListByCondition(
|
||||
@Param("selectTable") String selectTable,
|
||||
@Param("selectFields") String selectFields,
|
||||
@Param("whereClause") String whereClause,
|
||||
@Param("orderBy") String orderBy);
|
||||
|
||||
/**
|
||||
* 用指定过滤条件,计算记录数量。
|
||||
*
|
||||
* @param selectTable 表名称。
|
||||
* @param whereClause 过滤字符串。
|
||||
* @return 返回过滤后的数据数量。
|
||||
*/
|
||||
@Select("<script>"
|
||||
+ "SELECT COUNT(1) FROM ${selectTable}"
|
||||
+ "<where>"
|
||||
+ " <if test=\"whereClause != null and whereClause != ''\">"
|
||||
+ " AND ${whereClause}"
|
||||
+ " </if>"
|
||||
+ "</where>"
|
||||
+ "</script>")
|
||||
int getCountByCondition(@Param("selectTable") String selectTable, @Param("whereClause") String whereClause);
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.orange.admin.common.core.base.mapper;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Model对象到Domain类型对象的相互转换。实现类通常声明在Model实体类中。
|
||||
*
|
||||
* @param <D> Domain域对象类型。
|
||||
* @param <M> Model实体对象类型。
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
public interface BaseModelMapper<D, M> {
|
||||
|
||||
/**
|
||||
* 转换Model实体对象到Domain域对象。
|
||||
*
|
||||
* @param model Model实体对象。
|
||||
* @return Domain域对象。
|
||||
*/
|
||||
D fromModel(M model);
|
||||
|
||||
/**
|
||||
* 转换Model实体对象列表到Domain域对象列表。
|
||||
*
|
||||
* @param modelList Model实体对象列表。
|
||||
* @return Domain域对象列表。
|
||||
*/
|
||||
List<D> fromModelList(List<M> modelList);
|
||||
|
||||
/**
|
||||
* 转换Domain域对象到Model实体对象。
|
||||
*
|
||||
* @param domain Domain域对象。
|
||||
* @return Model实体对象。
|
||||
*/
|
||||
M toModel(D domain);
|
||||
|
||||
/**
|
||||
* 转换Domain域对象列表到Model实体对象列表。
|
||||
*
|
||||
* @param domainList Domain域对象列表。
|
||||
* @return Model实体对象列表。
|
||||
*/
|
||||
List<M> toModelList(List<D> domainList);
|
||||
|
||||
/**
|
||||
* 转换bean到map
|
||||
*
|
||||
* @param bean bean对象。
|
||||
* @param ignoreNullValue 值为null的字段是否转换到Map。
|
||||
* @param <T> bean类型。
|
||||
* @return 转换后的map对象。
|
||||
*/
|
||||
default <T> Map<String, Object> beanToMap(T bean, boolean ignoreNullValue) {
|
||||
return BeanUtil.beanToMap(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换map到bean。
|
||||
*
|
||||
* @param map map对象。
|
||||
* @param beanClazz bean的Class对象。
|
||||
* @param <T> bean类型。
|
||||
* @return 转换后的bean对象。
|
||||
*/
|
||||
default <T> T mapToBean(Map<String, Object> map, Class<T> beanClazz) {
|
||||
return BeanUtil.mapToBean(map, beanClazz, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对于Map字段到Map字段的映射场景,MapStruct会根据方法签名自动选择该函数
|
||||
* 作为对象copy的函数。由于该函数是直接返回的,因此没有对象copy,效率更高。
|
||||
* 如果没有该函数,MapStruct会生成如下代码:
|
||||
* Map<String, Object> map = courseDto.getTeacherIdDictMap();
|
||||
* if ( map != null ) {
|
||||
* course.setTeacherIdDictMap( new HashMap<String, Object>( map ) );
|
||||
* }
|
||||
*
|
||||
* @param map map对象。
|
||||
* @return 直接返回的map。
|
||||
*/
|
||||
default Map<String, Object> mapToMap(Map<String, Object> map) {
|
||||
return map;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.orange.admin.common.core.base.mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 哑元占位对象。Model实体对象和Domain域对象相同的场景下使用。
|
||||
* 由于没有实际的数据转换,因此同时保证了代码统一和执行效率。
|
||||
*
|
||||
* @param <M> 数据类型。
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
public class DummyModelMapper<M> implements BaseModelMapper<M, M> {
|
||||
|
||||
/**
|
||||
* 不转换直接返回。
|
||||
*
|
||||
* @param model Model实体对象。
|
||||
* @return Domain域对象。
|
||||
*/
|
||||
@Override
|
||||
public M fromModel(M model) {
|
||||
return model;
|
||||
}
|
||||
|
||||
/**
|
||||
* 不转换直接返回。
|
||||
*
|
||||
* @param modelList Model实体对象列表。
|
||||
* @return Domain域对象列表。
|
||||
*/
|
||||
@Override
|
||||
public List<M> fromModelList(List<M> modelList) {
|
||||
return modelList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 不转换直接返回。
|
||||
*
|
||||
* @param domain Domain域对象。
|
||||
* @return Model实体对象。
|
||||
*/
|
||||
@Override
|
||||
public M toModel(M domain) {
|
||||
return domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* 不转换直接返回。
|
||||
*
|
||||
* @param domainList Domain域对象列表。
|
||||
* @return Model实体对象列表。
|
||||
*/
|
||||
@Override
|
||||
public List<M> toModelList(List<M> domainList) {
|
||||
return domainList;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package com.orange.admin.common.core.base.service;
|
||||
|
||||
import com.orange.admin.common.core.cache.DictionaryCache;
|
||||
import com.orange.admin.common.core.constant.GlobalDeletedFlag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import tk.mybatis.mapper.entity.Example;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 带有缓存功能的字典Service基类,需要留意的是,由于缓存基于Key/Value方式存储,
|
||||
* 目前仅支持基于主键字段的缓存查找,其他条件的查找仍然从数据源获取。
|
||||
*
|
||||
* @param <M> Model实体对象的类型。
|
||||
* @param <K> Model对象主键的类型。
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class BaseDictService<M, K> extends BaseService<M, K> {
|
||||
|
||||
/**
|
||||
* 缓存池对象。
|
||||
*/
|
||||
protected DictionaryCache<K, M> dictionaryCache;
|
||||
|
||||
/**
|
||||
* 构造函数使用缺省缓存池对象。
|
||||
*/
|
||||
public BaseDictService() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否在服务启动的时候加载。子类可以重载该方法,并在需要的时候手工调用loadCachedData加载数据。
|
||||
*
|
||||
* @return true表示启动即可加载数据,false需要手动调用loadCachedData进行加载。
|
||||
*/
|
||||
public boolean loadOnStartup() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在系统启动时,加载全部数据到内存,缓存的key只能为映射表的主键。
|
||||
*/
|
||||
public void loadCachedData() {
|
||||
if (loadOnStartup()) {
|
||||
reloadCachedData();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新加载数据库中所有当前表数据到系统内存。
|
||||
*/
|
||||
public void reloadCachedData() {
|
||||
List<M> allList = super.getAllList();
|
||||
dictionaryCache.reload(allList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 直接从缓存池中获取主键Id关联的数据。
|
||||
*
|
||||
* @param id 主键Id。
|
||||
* @return 主键关联的数据,不存在返回null。
|
||||
*/
|
||||
@Override
|
||||
public M getById(K id) {
|
||||
return dictionaryCache.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 直接从缓存池中获取所有数据。
|
||||
*
|
||||
* @return 返回所有数据。
|
||||
*/
|
||||
@Override
|
||||
public List<M> getAllList() {
|
||||
return dictionaryCache.getAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 直接从缓存池中返回符合主键 in (idValues) 条件的所有数据。
|
||||
*
|
||||
* @param idValues 主键值列表。
|
||||
* @return 检索后的数据列表。
|
||||
*/
|
||||
@Override
|
||||
public List<M> getInList(Set<K> idValues) {
|
||||
return dictionaryCache.getInList(idValues);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回符合 inFilterField in (inFilterValues) 条件的所有数据。蜀国property是主键,则从缓存中读取。
|
||||
*
|
||||
* @param inFilterField 参与(In-list)过滤的Java字段。
|
||||
* @param inFilterValues 参与(In-list)过滤的Java字段值集合。
|
||||
* @return 检索后的数据列表。
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> List<M> getInList(String inFilterField, Set<T> inFilterValues) {
|
||||
if (inFilterField.equals(this.idFieldName)) {
|
||||
return this.getInList((Set<K>) inFilterValues);
|
||||
}
|
||||
return this.getInList(inFilterField, inFilterValues, (String) null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断参数值列表中的所有数据,是否全部存在。另外,keyName字段在数据表中必须是唯一键值,否则返回结果会出现误判。
|
||||
*
|
||||
* @param inFilterField 待校验的数据字段,这里使用Java对象中的属性,如courseId,而不是数据字段名course_id。
|
||||
* @param inFilterValues 数据值集合。
|
||||
* @return 全部存在返回true,否则false。
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> boolean existUniqueKeyList(String inFilterField, Set<T> inFilterValues) {
|
||||
if (CollectionUtils.isEmpty(inFilterValues)) {
|
||||
return false;
|
||||
}
|
||||
if (inFilterField.equals(this.idFieldName)) {
|
||||
List<M> dataList = dictionaryCache.getInList((Set<K>) inFilterValues);
|
||||
return dataList.size() == inFilterValues.size();
|
||||
}
|
||||
Example e = this.makeDefaultInListExample(inFilterField, inFilterValues, null);
|
||||
if (deletedFlagFieldName != null) {
|
||||
e.and().andEqualTo(deletedFlagFieldName, GlobalDeletedFlag.NORMAL);
|
||||
}
|
||||
return mapper().selectCountByExample(e) == inFilterValues.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存中的数据数量。
|
||||
*
|
||||
* @return 缓存中的数据总量。
|
||||
*/
|
||||
public int getCachedCount() {
|
||||
return dictionaryCache.getCount();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,87 @@
|
||||
package com.orange.admin.common.core.cache;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 主要用于完整缓存字典表数据的接口对象。
|
||||
*
|
||||
* @param <K> 字典表主键类型。
|
||||
* @param <V> 字典表对象类型。
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
public interface DictionaryCache<K, V> {
|
||||
|
||||
/**
|
||||
* 按照数据插入的顺序返回全部字典对象的列表。
|
||||
*
|
||||
* @return 全部字段数据列表。
|
||||
*/
|
||||
List<V> getAll();
|
||||
|
||||
/**
|
||||
* 获取缓存中与键列表对应的对象列表。
|
||||
*
|
||||
* @param keys 主键集合。
|
||||
* @return 对象列表。
|
||||
*/
|
||||
List<V> getInList(Set<K> keys);
|
||||
|
||||
/**
|
||||
* 将参数List中的数据保存到缓存中,同时保证getAll返回的数据列表,与参数列表中数据项的顺序保持一致。
|
||||
*
|
||||
* @param dataList 待缓存的数据列表。
|
||||
*/
|
||||
void putAll(List<V> dataList);
|
||||
|
||||
/**
|
||||
* 重新加载,先清空原有数据,在执行putAll的操作。
|
||||
*
|
||||
* @param dataList 待缓存的数据列表。
|
||||
*/
|
||||
void reload(List<V> dataList);
|
||||
|
||||
/**
|
||||
* 从缓存中获取指定的数据。
|
||||
*
|
||||
* @param key 数据的key。
|
||||
* @return 获取到的数据,如果没有返回null。
|
||||
*/
|
||||
V get(K key);
|
||||
|
||||
/**
|
||||
* 将数据存入缓存。
|
||||
*
|
||||
* @param key 通常为字典数据的主键。
|
||||
* @param object 字典数据对象。
|
||||
*/
|
||||
void put(K key, V object);
|
||||
|
||||
/**
|
||||
* 获取缓存中数据条目的数量。
|
||||
*
|
||||
* @return 返回缓存的数据数量。
|
||||
*/
|
||||
int getCount();
|
||||
|
||||
/**
|
||||
* 删除缓存中指定的键。
|
||||
*
|
||||
* @param key 待删除数据的主键。
|
||||
* @return 返回被删除的对象,如果主键不存在,返回null。
|
||||
*/
|
||||
V invalidate(K key);
|
||||
|
||||
/**
|
||||
* 删除缓存中,参数列表中包含的键。
|
||||
*
|
||||
* @param keys 待删除数据的主键集合。
|
||||
*/
|
||||
void invalidateSet(Set<K> keys);
|
||||
|
||||
/**
|
||||
* 清空缓存
|
||||
*/
|
||||
void invalidateAll();
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package com.orange.admin.common.core.cache;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* 字典数据内存缓存对象。
|
||||
*
|
||||
* @param <K> 字典表主键类型。
|
||||
* @param <V> 字典表对象类型。
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
public class MapDictionaryCache<K, V> implements DictionaryCache<K, V> {
|
||||
|
||||
/**
|
||||
* 存储字典数据的Map。
|
||||
*/
|
||||
protected LinkedHashMap<K, V> dataMap = new LinkedHashMap<>();
|
||||
/**
|
||||
* 获取字典主键数据的函数对象。
|
||||
*/
|
||||
protected Function<V, K> idGetter;
|
||||
|
||||
/**
|
||||
* 当前对象的构造器函数。
|
||||
*
|
||||
* @param idGetter 获取当前类主键字段值的函数对象。
|
||||
* @param <K> 字典主键类型。
|
||||
* @param <V> 字典对象类型
|
||||
* @return 实例化后的字典内存缓存对象。
|
||||
*/
|
||||
public static <K, V> MapDictionaryCache<K, V> create(Function<V, K> idGetter) {
|
||||
if (idGetter == null) {
|
||||
throw new IllegalArgumentException("IdGetter can't be NULL.");
|
||||
}
|
||||
return new MapDictionaryCache<>(idGetter);
|
||||
}
|
||||
|
||||
public MapDictionaryCache(Function<V, K> idGetter) {
|
||||
this.idGetter = idGetter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized List<V> getAll() {
|
||||
List<V> resultList = new LinkedList<>();
|
||||
for (Map.Entry<K, V> entry : dataMap.entrySet()) {
|
||||
resultList.add(entry.getValue());
|
||||
}
|
||||
return resultList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized List<V> getInList(Set<K> keys) {
|
||||
List<V> resultList = new LinkedList<>();
|
||||
keys.forEach(key -> {
|
||||
V object = dataMap.get(key);
|
||||
if (object != null) {
|
||||
resultList.add(object);
|
||||
}
|
||||
});
|
||||
return resultList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void putAll(List<V> dataList) {
|
||||
if (dataList == null) {
|
||||
return;
|
||||
}
|
||||
dataList.forEach(dataObj -> {
|
||||
K id = idGetter.apply(dataObj);
|
||||
dataMap.put(id, dataObj);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void reload(List<V> dataList) {
|
||||
this.invalidateAll();
|
||||
this.putAll(dataList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized V get(K id) {
|
||||
return id == null ? null : dataMap.get(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void put(K id, V object) {
|
||||
dataMap.put(id, object);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int getCount() {
|
||||
return dataMap.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized V invalidate(K id) {
|
||||
return id == null ? null : dataMap.remove(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void invalidateSet(Set<K> keys) {
|
||||
keys.forEach(id -> {
|
||||
if (id != null) {
|
||||
dataMap.remove(id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void invalidateAll() {
|
||||
dataMap.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.orange.admin.common.core.cache;
|
||||
|
||||
import com.google.common.collect.LinkedHashMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* 树形字典数据内存缓存对象。
|
||||
*
|
||||
* @param <K> 字典表主键类型。
|
||||
* @param <V> 字典表对象类型。
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
public class MapTreeDictionaryCache<K, V> extends MapDictionaryCache<K, V> {
|
||||
|
||||
private final Multimap<K, V> allTreeMap = LinkedHashMultimap.create();
|
||||
/**
|
||||
* 获取字典父主键数据的函数对象。
|
||||
*/
|
||||
protected Function<V, K> parentIdGetter;
|
||||
|
||||
/**
|
||||
* 当前对象的构造器函数。
|
||||
*
|
||||
* @param idGetter 获取当前类主键字段值的函数对象。
|
||||
* @param parentIdGetter 获取当前类父主键字段值的函数对象。
|
||||
* @param <K> 字典主键类型。
|
||||
* @param <V> 字典对象类型
|
||||
* @return 实例化后的树形字典内存缓存对象。
|
||||
*/
|
||||
public static <K, V> MapTreeDictionaryCache<K, V> create(Function<V, K> idGetter, Function<V, K> parentIdGetter) {
|
||||
if (idGetter == null) {
|
||||
throw new IllegalArgumentException("IdGetter can't be NULL.");
|
||||
}
|
||||
if (parentIdGetter == null) {
|
||||
throw new IllegalArgumentException("ParentIdGetter can't be NULL.");
|
||||
}
|
||||
return new MapTreeDictionaryCache<>(idGetter, parentIdGetter);
|
||||
}
|
||||
|
||||
public MapTreeDictionaryCache(Function<V, K> idGetter, Function<V, K> parentIdGetter) {
|
||||
super(idGetter);
|
||||
this.parentIdGetter = parentIdGetter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取该父主键的子数据列表。
|
||||
*
|
||||
* @param parentId 父主键Id。
|
||||
* @return 子数据列表。
|
||||
*/
|
||||
public synchronized List<V> getListByParentId(K parentId) {
|
||||
return new LinkedList<>(allTreeMap.get(parentId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void putAll(List<V> dataList) {
|
||||
if (dataList == null) {
|
||||
return;
|
||||
}
|
||||
super.putAll(dataList);
|
||||
dataList.forEach(data -> {
|
||||
K parentId = parentIdGetter.apply(data);
|
||||
allTreeMap.remove(parentId, data);
|
||||
allTreeMap.put(parentId, data);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void put(K id, V data) {
|
||||
super.put(id, data);
|
||||
K parentId = parentIdGetter.apply(data);
|
||||
allTreeMap.remove(parentId, data);
|
||||
allTreeMap.put(parentId, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized V invalidate(K id) {
|
||||
V v = super.invalidate(id);
|
||||
if (v != null) {
|
||||
K parentId = parentIdGetter.apply(v);
|
||||
allTreeMap.remove(parentId, v);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void invalidateSet(Set<K> keys) {
|
||||
keys.forEach(id -> {
|
||||
if (id != null) {
|
||||
V data = dataMap.remove(id);
|
||||
if (data != null) {
|
||||
K parentId = parentIdGetter.apply(data);
|
||||
allTreeMap.remove(parentId, data);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void invalidateAll() {
|
||||
super.invalidateAll();
|
||||
allTreeMap.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.orange.admin.common.core.constant;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 聚合计算的常量类型对象。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
public final class AggregationType {
|
||||
|
||||
/**
|
||||
* sum 计数
|
||||
*/
|
||||
public final static int SUM = 0;
|
||||
/**
|
||||
* count 汇总
|
||||
*/
|
||||
public final static int COUNT = 1;
|
||||
/**
|
||||
* average 平均值
|
||||
*/
|
||||
public final static int AVG = 2;
|
||||
/**
|
||||
* min 最小值
|
||||
*/
|
||||
public final static int MIN = 3;
|
||||
/**
|
||||
* max 最大值
|
||||
*/
|
||||
public final static int MAX = 4;
|
||||
|
||||
public static final Map<Object, String> DICT_MAP = new HashMap<>(5);
|
||||
static {
|
||||
DICT_MAP.put(0, "累计总和");
|
||||
DICT_MAP.put(1, "数量总和");
|
||||
DICT_MAP.put(2, "平均值");
|
||||
DICT_MAP.put(3, "最小值");
|
||||
DICT_MAP.put(4, "最大值");
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断参数是否为当前常量字典的合法值。
|
||||
*
|
||||
* @param value 待验证的参数值。
|
||||
* @return 合法返回true,否则false。
|
||||
*/
|
||||
public static boolean isValid(Integer value) {
|
||||
return DICT_MAP.containsKey(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取与SQL对应的聚合函数字符串名称。
|
||||
*
|
||||
* @return 聚合函数名称。
|
||||
*/
|
||||
public static String getAggregationFunction(Integer aggregationType) {
|
||||
switch (aggregationType) {
|
||||
case COUNT:
|
||||
return "COUNT";
|
||||
case AVG:
|
||||
return "AVG";
|
||||
case SUM:
|
||||
return "SUM";
|
||||
case MAX:
|
||||
return "MAX";
|
||||
case MIN:
|
||||
return "MIN";
|
||||
default:
|
||||
throw new IllegalArgumentException("无效的聚合类型!");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.orange.admin.common.core.constant;
|
||||
|
||||
/**
|
||||
* 应用程序的常量声明对象。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
public interface ApplicationConstant {
|
||||
|
||||
/**
|
||||
* 图片文件上传的父目录。
|
||||
*/
|
||||
String UPLOAD_IMAGE_PARENT_PATH = "/image";
|
||||
/**
|
||||
* 附件文件上传的父目录。
|
||||
*/
|
||||
String UPLOAD_ATTACHMENT_PARENT_PATH = "/attachment";
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.orange.admin.common.core.constant;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 数据权限规则类型常量类。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
public final class DataPermRuleType {
|
||||
|
||||
/**
|
||||
* 查看全部。
|
||||
*/
|
||||
public static final int TYPE_ALL = 0;
|
||||
|
||||
/**
|
||||
* 仅查看当前用户
|
||||
*/
|
||||
public static final int TYPE_USER_ONLY = 1;
|
||||
|
||||
/**
|
||||
* 仅查看当前部门
|
||||
*/
|
||||
public static final int TYPE_DEPT_ONLY = 2;
|
||||
|
||||
/**
|
||||
* 自定义部门列表
|
||||
*/
|
||||
public static final int TYPE_CUSTOM_DETP_LIST = 5;
|
||||
|
||||
public static final Map<Object, String> DICT_MAP = new HashMap<>(4);
|
||||
static {
|
||||
DICT_MAP.put(0, "查看全部");
|
||||
DICT_MAP.put(1, "仅查看当前用户");
|
||||
DICT_MAP.put(2, "仅查看所在部门");
|
||||
DICT_MAP.put(5, "自定义部门列表");
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断参数是否为当前常量字典的合法取值范围。
|
||||
*
|
||||
* @param value 待验证的参数值。
|
||||
* @return 合法返回true,否则false。
|
||||
*/
|
||||
public static boolean isValid(int value) {
|
||||
return DICT_MAP.containsKey(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 私有构造函数,明确标识该常量类的作用。
|
||||
*/
|
||||
private DataPermRuleType() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.orange.admin.common.core.constant;
|
||||
|
||||
/**
|
||||
* 返回应答中的错误代码和错误信息。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
public enum ErrorCodeEnum {
|
||||
|
||||
NO_ERROR("没有错误"),
|
||||
UNHANDLED_EXCEPTION("未处理的异常!"),
|
||||
|
||||
ARGUMENT_NULL_EXIST("数据验证失败,接口调用参数存在空值,请核对!"),
|
||||
ARGUMENT_PK_ID_NULL("数据验证失败,接口调用主键Id参数为空,请核对!"),
|
||||
INVALID_ARGUMENT_FORMAT("数据验证失败,不合法的参数格式,请核对!"),
|
||||
INVALID_STATUS_ARGUMENT("数据验证失败,无效的状态参数值,请核对!"),
|
||||
INVALID_UPLOAD_FILE_ARGUMENT("数据验证失败,上传文件参数错误,请核对!"),
|
||||
INVALID_UPLOAD_FILE_IOERROR("上传文件写入失败,请联系管理员!"),
|
||||
UNAUTHORIZED_LOGIN("当前用户尚未登录或登录已超时,请重新登录!"),
|
||||
UNAUTHORIZED_USER_PERMISSION("权限验证失败,当前用户不能访问该接口,请核对!"),
|
||||
NO_ACCESS_PERMISSION("当前用户没有访问权限,请核对!"),
|
||||
NO_OPERATION_PERMISSION("当前用户没有操作权限,请核对!"),
|
||||
|
||||
PASSWORD_ERR("密码错误,请重试!"),
|
||||
INVALID_USERNAME_PASSWORD("用户名或密码错误,请重试!"),
|
||||
INVALID_USER_STATUS("用户状态错误,请刷新后重试!"),
|
||||
|
||||
HAS_CHILDREN_DATA("数据验证失败,子数据存在,请刷新后重试!"),
|
||||
DATA_VALIDATAED_FAILED("数据验证失败,请核对!"),
|
||||
UPLOAD_FILE_FAILED("文件上传失败,请联系管理员!"),
|
||||
DATA_SAVE_FAILED("数据保存失败,请联系管理员!"),
|
||||
DATA_ACCESS_FAILED("数据访问失败,请联系管理员!"),
|
||||
DATA_PERM_ACCESS_FAILED("数据访问失败,您没有该页面的数据访问权限!"),
|
||||
DUPLICATED_UNIQUE_KEY("数据保存失败,存在重复数据,请核对!"),
|
||||
DATA_NOT_EXIST("数据不存在,请刷新后重试!"),
|
||||
DATA_PARENT_LEVEL_ID_NOT_EXIST("数据验证失败,父级别关联Id不存在,请刷新后重试!"),
|
||||
DATA_PARENT_ID_NOT_EXIST("数据验证失败,ParentId不存在,请核对!"),
|
||||
INVALID_RELATED_RECORD_ID("数据验证失败,关联数据并不存在,请刷新后重试!"),
|
||||
INVALID_DATA_FIELD("数据验证失败,无效的数据字段!"),
|
||||
SERVER_INTERNAL_ERROR("服务器内部错误,请联系管理员!"),
|
||||
REDIS_CACHE_ACCESS_TIMEOUT("Redis缓存数据访问超时,请刷新后重试!"),
|
||||
REDIS_CACHE_ACCESS_STATE_ERROR("Redis缓存数据访问状态错误,请刷新后重试!");
|
||||
|
||||
// 下面的枚举值为特定枚举值,即开发者可以根据自己的项目需求定义更多的非通用枚举值
|
||||
|
||||
ErrorCodeEnum(String errorMessage) {
|
||||
this.errorMessage = errorMessage;
|
||||
}
|
||||
|
||||
private String errorMessage;
|
||||
|
||||
public String getErrorMessage() {
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
public void setErrorMessage(String errorMessage) {
|
||||
this.errorMessage = errorMessage;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.orange.admin.common.core.constant;
|
||||
|
||||
/**
|
||||
* 数据记录逻辑删除标记常量。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
public interface GlobalDeletedFlag {
|
||||
|
||||
/**
|
||||
* 表示数据表记录已经删除
|
||||
*/
|
||||
int DELETED = -1;
|
||||
/**
|
||||
* 数据记录正常
|
||||
*/
|
||||
int NORMAL = 1;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.orange.admin.common.core.exception;
|
||||
|
||||
/**
|
||||
* 数据验证失败的自定义异常。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
public class DataValidationException extends RuntimeException {
|
||||
|
||||
public DataValidationException() {
|
||||
|
||||
}
|
||||
|
||||
public DataValidationException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.orange.admin.common.core.exception;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 无效的实体对象字段的自定义异常。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class InvalidDataFieldException extends RuntimeException {
|
||||
|
||||
private String modelName;
|
||||
private String fieldName;
|
||||
|
||||
public InvalidDataFieldException(String modelName, String fieldName) {
|
||||
super("Invalid FieldName [" + fieldName + "] in Model Class [" + modelName + "].");
|
||||
this.modelName = modelName;
|
||||
this.fieldName = fieldName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.orange.admin.common.core.exception;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 无效的实体对象的自定义异常。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class InvalidDataModelException extends RuntimeException {
|
||||
|
||||
private String modelName;
|
||||
|
||||
public InvalidDataModelException(String modelName) {
|
||||
super("Invalid Model Class [" + modelName + "].");
|
||||
this.modelName = modelName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.orange.admin.common.core.exception;
|
||||
|
||||
/**
|
||||
* 没有数据被修改的自定义异常。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
public class NoDataAffectException extends RuntimeException {
|
||||
|
||||
public NoDataAffectException() {
|
||||
|
||||
}
|
||||
|
||||
public NoDataAffectException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.orange.admin.common.core.exception;
|
||||
|
||||
/**
|
||||
* 没有数据访问权限的自定义异常。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
public class NoDataPermException extends RuntimeException {
|
||||
|
||||
public NoDataPermException() {
|
||||
|
||||
}
|
||||
|
||||
public NoDataPermException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.orange.admin.common.core.exception;
|
||||
|
||||
/**
|
||||
* Redis缓存访问失败。比如:获取分布式数据锁超时、等待线程中断等。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
public class RedisCacheAccessException extends RuntimeException {
|
||||
|
||||
public RedisCacheAccessException(String msg, Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.orange.admin.common.core.object;
|
||||
|
||||
import cn.hutool.core.util.BooleanUtil;
|
||||
|
||||
/**
|
||||
* 线程本地化数据管理的工具类。可根据需求自行添加更多的线程本地化变量及其操作方法。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
public class GlobalThreadLocal {
|
||||
|
||||
/**
|
||||
* 存储数据权限过滤是否启用的线程本地化对象。
|
||||
*/
|
||||
private static final ThreadLocal<Boolean> DATA_PERM_ENABLE = ThreadLocal.withInitial(() -> Boolean.TRUE);
|
||||
|
||||
/**
|
||||
* 设置数据权限过滤是否打开。如果打开,当前Servlet线程所执行的SQL操作,均会进行数据权限过滤。
|
||||
*
|
||||
* @param enable 打开为true,否则false。
|
||||
* @return 返回之前的状态,便于恢复。
|
||||
*/
|
||||
public static boolean setDataPerm(boolean enable) {
|
||||
boolean oldValue = DATA_PERM_ENABLE.get();
|
||||
DATA_PERM_ENABLE.set(enable);
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前Servlet线程所执行的SQL操作,是否进行数据权限过滤。
|
||||
*
|
||||
* @return true 进行数据权限过滤,否则false。
|
||||
*/
|
||||
public static boolean enabledDataPerm() {
|
||||
return BooleanUtil.isTrue(DATA_PERM_ENABLE.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空该存储数据,主动释放线程本地化存储资源。
|
||||
*/
|
||||
public static void clearDataPerm() {
|
||||
DATA_PERM_ENABLE.remove();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.orange.admin.common.core.object;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Mybatis Mapper.xml中所需的分组条件对象。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class MyGroupCriteria {
|
||||
|
||||
/**
|
||||
* GROUP BY 从句后面的参数。
|
||||
*/
|
||||
private String groupBy;
|
||||
/**
|
||||
* SELECT 从句后面的分组显示字段。
|
||||
*/
|
||||
private String groupSelect;
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package com.orange.admin.common.core.object;
|
||||
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import com.orange.admin.common.core.util.MyModelUtil;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 查询分组参数请求对象。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Slf4j
|
||||
@Data
|
||||
public class MyGroupParam extends ArrayList<MyGroupParam.GroupInfo> {
|
||||
|
||||
/**
|
||||
* SQL语句的SELECT LIST中,分组字段的返回字段名称列表。
|
||||
*/
|
||||
private List<String> selectGroupFieldList;
|
||||
|
||||
/**
|
||||
* 分组参数解析后构建的SQL语句中所需的分组数据,如GROUP BY的字段列表和SELECT LIST中的分组字段显示列表。
|
||||
*/
|
||||
private MyGroupCriteria groupCriteria;
|
||||
/**
|
||||
* 基于分组参数对象中的数据,构建SQL中select list和group by从句可以直接使用的分组对象。
|
||||
*
|
||||
* @param groupParam 分组参数对象。
|
||||
* @param modelClazz 查询表对应的主对象的Class。
|
||||
* @return SQL中所需的GROUP对象。详见MyGroupCriteria类定义。
|
||||
*/
|
||||
public static MyGroupParam buildGroupBy(MyGroupParam groupParam, Class<?> modelClazz) {
|
||||
if (groupParam == null) {
|
||||
return null;
|
||||
}
|
||||
if (modelClazz == null) {
|
||||
throw new IllegalArgumentException("modelClazz Argument can't be NULL");
|
||||
}
|
||||
groupParam.selectGroupFieldList = new LinkedList<>();
|
||||
StringBuilder groupByBuilder = new StringBuilder(128);
|
||||
StringBuilder groupSelectBuilder = new StringBuilder(128);
|
||||
int i = 0;
|
||||
for (GroupInfo groupInfo : groupParam) {
|
||||
String modelName, fieldName, tableName, columnName;
|
||||
String[] stringArray = StringUtils.split(groupInfo.fieldName,'.');
|
||||
if (stringArray.length == 1) {
|
||||
modelName = modelClazz.getSimpleName();
|
||||
fieldName = groupInfo.fieldName;
|
||||
tableName = MyModelUtil.mapToTableName(modelClazz);
|
||||
columnName = MyModelUtil.mapToColumnName(fieldName, modelClazz);
|
||||
} else {
|
||||
Field field = ReflectUtil.getField(modelClazz, stringArray[0]);
|
||||
if (field == null) {
|
||||
log.error("Relation Field [{}] doesn't exist in Class [{}]!",
|
||||
stringArray[0], modelClazz.getSimpleName());
|
||||
return null;
|
||||
}
|
||||
Class<?> fieldClazz = field.getType();
|
||||
modelName = fieldClazz.getSimpleName();
|
||||
fieldName = stringArray[1];
|
||||
tableName = MyModelUtil.mapToTableName(fieldClazz);
|
||||
columnName = MyModelUtil.mapToColumnName(fieldName, fieldClazz);
|
||||
}
|
||||
if (StringUtils.isBlank(tableName)) {
|
||||
log.error("ModelName [{}] can't be mapped to Table!", modelName);
|
||||
return null;
|
||||
}
|
||||
if (StringUtils.isBlank(columnName)) {
|
||||
log.error("FieldName [{}] in Class [{}`] can't be mapped to Column!", fieldName, modelName);
|
||||
return null;
|
||||
}
|
||||
if (StringUtils.isNotBlank(groupInfo.dateAggregateBy)) {
|
||||
groupByBuilder.append("DATE_FORMAT(").append(tableName).append(".").append(columnName);
|
||||
groupSelectBuilder.append("DATE_FORMAT(").append(tableName).append(".").append(columnName);
|
||||
if ("day".equals(groupInfo.dateAggregateBy)) {
|
||||
groupByBuilder.append(", '%Y-%m-%d')");
|
||||
groupSelectBuilder.append(", '%Y-%m-%d')");
|
||||
} else if ("month".equals(groupInfo.dateAggregateBy)) {
|
||||
groupByBuilder.append(", '%Y-%m-01')");
|
||||
groupSelectBuilder.append(", '%Y-%m-01')");
|
||||
} else if ("year".equals(groupInfo.dateAggregateBy)) {
|
||||
groupByBuilder.append(", '%Y-01-01')");
|
||||
groupSelectBuilder.append(", '%Y-01-01')");
|
||||
} else {
|
||||
throw new IllegalArgumentException("Illegal DATE_FORMAT for GROUP ID list.");
|
||||
}
|
||||
if (StringUtils.isNotBlank(groupInfo.aliasName)) {
|
||||
groupSelectBuilder.append(" ").append(groupInfo.aliasName);
|
||||
} else {
|
||||
groupSelectBuilder.append(" ").append(columnName);
|
||||
}
|
||||
} else {
|
||||
groupByBuilder.append(tableName).append(".").append(columnName);
|
||||
groupSelectBuilder.append(tableName).append(".").append(columnName);
|
||||
if (StringUtils.isNotBlank(groupInfo.aliasName)) {
|
||||
groupSelectBuilder.append(" ").append(groupInfo.aliasName);
|
||||
}
|
||||
}
|
||||
String aliasName = StringUtils.isBlank(groupInfo.aliasName) ? fieldName : groupInfo.aliasName;
|
||||
// selectGroupFieldList中的元素,目前只是被export操作使用。会根据集合中的元素名称匹配导出表头。
|
||||
groupParam.selectGroupFieldList.add(aliasName);
|
||||
if (++i < groupParam.size()) {
|
||||
groupByBuilder.append(", ");
|
||||
groupSelectBuilder.append(", ");
|
||||
}
|
||||
}
|
||||
groupParam.groupCriteria = new MyGroupCriteria(groupByBuilder.toString(), groupSelectBuilder.toString());
|
||||
return groupParam;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class GroupInfo {
|
||||
/**
|
||||
* Java对象的字段名。目前主要包含三种格式:
|
||||
* 1. 简单的属性名称,如userId,将会直接映射到与其关联的数据库字段。表名为当前ModelClazz所对应的表名。
|
||||
* 映射结果或为 my_main_table.user_id
|
||||
* 2. 一对一关联表属性,如user.userId,这里将先获取user属性的对象类型并映射到对应的表名,后面的userId为
|
||||
* user所在实体的属性。映射结果或为:my_sys_user.user_id
|
||||
*/
|
||||
private String fieldName;
|
||||
/**
|
||||
* SQL语句的Select List中,分组字段的别名。如果别名为NULL,直接取fieldName。
|
||||
*/
|
||||
private String aliasName;
|
||||
/**
|
||||
* 如果该值不为NULL,则会对分组字段进行DATE_FORMAT函数的计算,并根据具体的值,将日期数据截取到指定的位。
|
||||
* day: 表示按照天聚合,将会截取到天。DATE_FORMAT(columnName, '%Y-%m-%d')
|
||||
* month: 表示按照月聚合,将会截取到月。DATE_FORMAT(columnName, '%Y-%m-01')
|
||||
* year: 表示按照年聚合,将会截取到年。DATE_FORMAT(columnName, '%Y-01-01')
|
||||
*/
|
||||
private String dateAggregateBy;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
package com.orange.admin.common.core.object;
|
||||
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import com.orange.admin.common.core.util.MyModelUtil;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Controller参数中的排序请求对象。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Slf4j
|
||||
@Data
|
||||
public class MyOrderParam extends ArrayList<MyOrderParam.OrderInfo> {
|
||||
|
||||
/**
|
||||
* 基于排序对象中的JSON数据,构建SQL中order by从句可以直接使用的排序字符串。
|
||||
*
|
||||
* @param orderParam 排序参数对象。
|
||||
* @param modelClazz 查询主表对应的主对象的Class。
|
||||
* @return SQL中order by从句可以直接使用的排序字符串。
|
||||
*/
|
||||
public static String buildOrderBy(MyOrderParam orderParam, Class<?> modelClazz) {
|
||||
if (orderParam == null) {
|
||||
return null;
|
||||
}
|
||||
String exceptionMessage;
|
||||
if (modelClazz == null) {
|
||||
throw new IllegalArgumentException("modelClazz Argument can't be NULL");
|
||||
}
|
||||
int i = 0;
|
||||
StringBuilder orderBy = new StringBuilder(128);
|
||||
for (OrderInfo orderInfo : orderParam) {
|
||||
String modelName, fieldName = orderInfo.fieldName, tableName, columnName;
|
||||
int pos = fieldName.indexOf("DictMap.");
|
||||
if (pos != -1) {
|
||||
fieldName = fieldName.substring(0, pos);
|
||||
}
|
||||
String[] stringArray = StringUtils.split(fieldName, '.');
|
||||
if (stringArray.length == 1) {
|
||||
modelName = modelClazz.getSimpleName();
|
||||
tableName = MyModelUtil.mapToTableName(modelClazz);
|
||||
columnName = MyModelUtil.mapToColumnName(fieldName, modelClazz);
|
||||
} else {
|
||||
Field field = ReflectUtil.getField(modelClazz, stringArray[0]);
|
||||
if (field == null) {
|
||||
exceptionMessage = String.format(
|
||||
"Relation Field [%s] doesn't exist in Class [%s]!", stringArray[0], modelClazz.getSimpleName());
|
||||
log.error(exceptionMessage);
|
||||
throw new IllegalArgumentException(exceptionMessage);
|
||||
}
|
||||
Class<?> fieldClazz = field.getType();
|
||||
modelName = fieldClazz.getSimpleName();
|
||||
fieldName = stringArray[1];
|
||||
tableName = MyModelUtil.mapToTableName(fieldClazz);
|
||||
columnName = MyModelUtil.mapToColumnName(fieldName, fieldClazz);
|
||||
}
|
||||
if (StringUtils.isBlank(tableName)) {
|
||||
exceptionMessage = String.format("ModelName [%s] can't be mapped to Table!", modelName);
|
||||
log.error(exceptionMessage);
|
||||
throw new IllegalArgumentException(exceptionMessage);
|
||||
}
|
||||
if (StringUtils.isBlank(columnName)) {
|
||||
exceptionMessage = String.format(
|
||||
"FieldName [%s] in Class [%s] can't be mapped to Column!", fieldName, modelName);
|
||||
log.error(exceptionMessage);
|
||||
throw new IllegalArgumentException(exceptionMessage);
|
||||
}
|
||||
if (StringUtils.isNotBlank(orderInfo.dateAggregateBy)) {
|
||||
orderBy.append("DATE_FORMAT(").append(tableName).append(".").append(columnName);
|
||||
if ("day".equals(orderInfo.dateAggregateBy)) {
|
||||
orderBy.append(", '%Y-%m-%d')");
|
||||
} else if ("month".equals(orderInfo.dateAggregateBy)) {
|
||||
orderBy.append(", '%Y-%m-01')");
|
||||
} else if ("year".equals(orderInfo.dateAggregateBy)) {
|
||||
orderBy.append(", '%Y-01-01')");
|
||||
} else {
|
||||
throw new IllegalArgumentException("Illegal DATE_FORMAT for GROUP ID list.");
|
||||
}
|
||||
} else {
|
||||
orderBy.append(tableName).append(".").append(columnName);
|
||||
}
|
||||
if (orderInfo.asc != null && !orderInfo.asc) {
|
||||
orderBy.append(" DESC");
|
||||
}
|
||||
if (++i < orderParam.size()) {
|
||||
orderBy.append(", ");
|
||||
}
|
||||
}
|
||||
return orderBy.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 在排序列表中,可能存在基于指定表字段的排序,该函数将获取指定表的所有排序字段。
|
||||
* 返回有的字符串,可直接用于SQL中的ORDER BY从句。
|
||||
*
|
||||
* @param orderParam 排序参数对象。
|
||||
* @param modelClazz 查询主表对应的主对象的Class。
|
||||
* @param relationModelName 与关联表对应的Model的名称,如my_course_paper表应对的Java对象CoursePaper。
|
||||
* 如果该值为null或空字符串,则获取所有主表的排序字段。
|
||||
* @return 返回的是表字段,而非Java对象的属性,多个字段之间逗号分隔。
|
||||
*/
|
||||
public static String getOrderClauseByModelName(
|
||||
MyOrderParam orderParam, Class<?> modelClazz, String relationModelName) {
|
||||
if (orderParam == null) {
|
||||
return null;
|
||||
}
|
||||
if (modelClazz == null) {
|
||||
throw new IllegalArgumentException("modelClazz Argument can't be NULL");
|
||||
}
|
||||
String exceptionMessage;
|
||||
List<String> fieldNameList = new LinkedList<>();
|
||||
String prefix = null;
|
||||
if (StringUtils.isNotBlank(relationModelName)) {
|
||||
prefix = relationModelName + ".";
|
||||
}
|
||||
for (OrderInfo orderInfo : orderParam) {
|
||||
boolean found = false;
|
||||
String modelName = null, fieldName = orderInfo.fieldName, tableName = null, columnName = null;
|
||||
int pos = fieldName.indexOf("DictMap.");
|
||||
if (pos != -1) {
|
||||
fieldName = fieldName.substring(0, pos);
|
||||
}
|
||||
if (prefix != null) {
|
||||
if (fieldName.startsWith(prefix)) {
|
||||
Field field = ReflectUtil.getField(modelClazz, relationModelName);
|
||||
if (field == null) {
|
||||
exceptionMessage = String.format(
|
||||
"Relation Field [%s] doesn't exist in Class [%s]!", relationModelName, modelClazz.getSimpleName());
|
||||
log.error(exceptionMessage);
|
||||
throw new IllegalArgumentException(exceptionMessage);
|
||||
}
|
||||
Class<?> fieldClazz = field.getType();
|
||||
modelName = fieldClazz.getSimpleName();
|
||||
fieldName = StringUtils.removeStart(fieldName, prefix);
|
||||
tableName = MyModelUtil.mapToTableName(fieldClazz);
|
||||
columnName = MyModelUtil.mapToColumnName(fieldName, fieldClazz);
|
||||
found = true;
|
||||
}
|
||||
} else {
|
||||
if (!fieldName.contains(".")) {
|
||||
modelName = modelClazz.getSimpleName();
|
||||
tableName = MyModelUtil.mapToTableName(modelClazz);
|
||||
columnName = MyModelUtil.mapToColumnName(fieldName, modelClazz);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
if (StringUtils.isBlank(tableName)) {
|
||||
exceptionMessage = String.format("ModelName [%s] can't be mapped to Table!", modelName);
|
||||
log.error(exceptionMessage);
|
||||
throw new IllegalArgumentException(exceptionMessage);
|
||||
}
|
||||
if (StringUtils.isBlank(columnName)) {
|
||||
exceptionMessage = String.format(
|
||||
"FieldName [%s] in Class [%s] can't be mapped to Column!", fieldName, modelName);
|
||||
log.error(exceptionMessage);
|
||||
throw new IllegalArgumentException(exceptionMessage);
|
||||
}
|
||||
StringBuilder orderBy = new StringBuilder(128);
|
||||
orderBy.append(tableName).append(".").append(columnName);
|
||||
if (orderInfo.asc != null && !orderInfo.asc) {
|
||||
orderBy.append(" DESC");
|
||||
}
|
||||
fieldNameList.add(orderBy.toString());
|
||||
}
|
||||
}
|
||||
return StringUtils.join(fieldNameList, ", ");
|
||||
}
|
||||
|
||||
/**
|
||||
* 在排序列表中,可能存在基于指定表字段的排序,该函数将删除指定表的所有排序字段。
|
||||
*
|
||||
* @param orderParam 排序参数对象。
|
||||
* @param modelClazz 查询主表对应的主对象的Class。
|
||||
* @param relationModelName 与关联表对应的Model的名称,如my_course_paper表应对的Java对象CoursePaper。
|
||||
* 如果该值为null或空字符串,则获取所有主表的排序字段。
|
||||
*/
|
||||
public static void removeOrderClauseByModelName(
|
||||
MyOrderParam orderParam, Class<?> modelClazz, String relationModelName) {
|
||||
if (orderParam == null) {
|
||||
return;
|
||||
}
|
||||
if (modelClazz == null) {
|
||||
throw new IllegalArgumentException("modelClazz Argument can't be NULL");
|
||||
}
|
||||
List<Integer> fieldIndexList = new LinkedList<>();
|
||||
String prefix = null;
|
||||
if (StringUtils.isNotBlank(relationModelName)) {
|
||||
prefix = relationModelName + ".";
|
||||
}
|
||||
int i = 0;
|
||||
for (OrderInfo orderInfo : orderParam) {
|
||||
String fieldName = orderInfo.fieldName;
|
||||
int pos = fieldName.indexOf("DictMap.");
|
||||
if (pos != -1) {
|
||||
fieldName = fieldName.substring(0, pos);
|
||||
}
|
||||
if (prefix != null) {
|
||||
if (fieldName.startsWith(prefix)) {
|
||||
fieldIndexList.add(i);
|
||||
}
|
||||
} else {
|
||||
if (!fieldName.contains(".")) {
|
||||
fieldIndexList.add(i);
|
||||
}
|
||||
}
|
||||
++i;
|
||||
}
|
||||
for (int index : fieldIndexList) {
|
||||
orderParam.remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class OrderInfo {
|
||||
/**
|
||||
* Java对象的字段名。目前主要包含三种格式:
|
||||
* 1. 简单的属性名称,如userId,将会直接映射到与其关联的数据库字段。表名为当前ModelClazz所对应的表名。
|
||||
* 映射结果或为 my_main_table.user_id
|
||||
* 2. 字典属性名称,如userIdDictMap.id,由于仅仅支持字典中Id数据的排序,所以直接截取DictMap之前的字符串userId作为排序属性。
|
||||
* 表名为当前ModelClazz所对应的表名。映射结果或为 my_main_table.user_id
|
||||
* 3. 一对一关联表属性,如user.userId,这里将先获取user属性的对象类型并映射到对应的表名,后面的userId为
|
||||
* user所在实体的属性。映射结果或为:my_sys_user.user_id
|
||||
*/
|
||||
private String fieldName;
|
||||
/**
|
||||
* 排序方向。true为升序,否则降序。
|
||||
*/
|
||||
private Boolean asc = true;
|
||||
/**
|
||||
* 如果该值不为NULL,则会对日期型排序字段进行DATE_FORMAT函数的计算,并根据具体的值,将日期数据截取到指定的位。
|
||||
* day: 表示按照天聚合,将会截取到天。DATE_FORMAT(columnName, '%Y-%m-%d')
|
||||
* month: 表示按照月聚合,将会截取到月。DATE_FORMAT(columnName, '%Y-%m-01')
|
||||
* year: 表示按照年聚合,将会截取到年。DATE_FORMAT(columnName, '%Y-01-01')
|
||||
*/
|
||||
private String dateAggregateBy;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.orange.admin.common.core.object;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* Controller参数中的分页请求对象
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
@Getter
|
||||
public class MyPageParam {
|
||||
|
||||
public static final int DEFAULT_PAGE_NUM = 1;
|
||||
public static final int DEFAULT_PAGE_SIZE = 10;
|
||||
|
||||
/**
|
||||
* 分页号码,从1开始计数。
|
||||
*/
|
||||
private Integer pageNum;
|
||||
|
||||
/**
|
||||
* 每页大小。
|
||||
*/
|
||||
private Integer pageSize;
|
||||
|
||||
/**
|
||||
* 设置当前分页页号。
|
||||
*
|
||||
* @param pageNum 页号,如果传入非法值,则使用缺省值。
|
||||
*/
|
||||
public void setPageNum(Integer pageNum) {
|
||||
if (pageNum == null) {
|
||||
return;
|
||||
}
|
||||
if (pageNum <= 0) {
|
||||
pageNum = DEFAULT_PAGE_NUM;
|
||||
}
|
||||
this.pageNum = pageNum;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置分页的大小。
|
||||
*
|
||||
* @param pageSize 分页大小,如果传入非法值,则使用缺省值。
|
||||
*/
|
||||
public void setPageSize(Integer pageSize) {
|
||||
if (pageSize == null) {
|
||||
return;
|
||||
}
|
||||
if (pageSize <= 0 || pageSize > 100) {
|
||||
pageSize = DEFAULT_PAGE_SIZE;
|
||||
}
|
||||
this.pageSize = pageSize;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
package com.orange.admin.common.core.object;
|
||||
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import com.alibaba.fastjson.annotation.JSONField;
|
||||
import com.orange.admin.common.core.exception.InvalidDataFieldException;
|
||||
import com.orange.admin.common.core.exception.InvalidDataModelException;
|
||||
import com.orange.admin.common.core.util.MyModelUtil;
|
||||
import lombok.*;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Where中的条件语句。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
@Slf4j
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class MyWhereCriteria {
|
||||
|
||||
/**
|
||||
* 等于
|
||||
*/
|
||||
public static final int OPERATOR_EQUAL = 0;
|
||||
|
||||
/**
|
||||
* 不等于
|
||||
*/
|
||||
public static final int OPERATOR_NOT_EQUAL = 1;
|
||||
|
||||
/**
|
||||
* 大于等于
|
||||
*/
|
||||
public static final int OPERATOR_GE = 2;
|
||||
|
||||
/**
|
||||
* 大于
|
||||
*/
|
||||
public static final int OPERATOR_GT = 3;
|
||||
|
||||
/**
|
||||
* 小于等于
|
||||
*/
|
||||
public static final int OPERATOR_LE = 4;
|
||||
|
||||
/**
|
||||
* 小于
|
||||
*/
|
||||
public static final int OPERATOR_LT = 5;
|
||||
|
||||
/**
|
||||
* LIKE
|
||||
*/
|
||||
public static final int OPERATOR_LIKE = 6;
|
||||
|
||||
/**
|
||||
* NOT NULL
|
||||
*/
|
||||
public static final int OPERATOR_NOT_NULL = 7;
|
||||
|
||||
/**
|
||||
* IS NULL
|
||||
*/
|
||||
public static final int OPERATOR_IS_NULL = 8;
|
||||
|
||||
/**
|
||||
* IN
|
||||
*/
|
||||
public static final int OPERATOR_IN = 9;
|
||||
|
||||
/**
|
||||
* 参与过滤的实体对象的Class。
|
||||
*/
|
||||
@JSONField(serialize = false)
|
||||
private Class<?> modelClazz;
|
||||
|
||||
/**
|
||||
* Java属性名称。
|
||||
*/
|
||||
private String fieldName;
|
||||
|
||||
/**
|
||||
* 操作符类型,取值范围见上面的常量值。
|
||||
*/
|
||||
private Integer operatorType;
|
||||
|
||||
/**
|
||||
* 条件数据值。
|
||||
*/
|
||||
private Object value;
|
||||
|
||||
/**
|
||||
* 设置条件值。
|
||||
*
|
||||
* @param fieldName 条件所属的实体对象的字段名。
|
||||
* @param operatorType 条件操作符。具体值可参考当前对象的静态变量。
|
||||
* @param value 条件过滤值。
|
||||
* @return 验证结果对象,如果有错误将会返回具体的错误信息。
|
||||
*/
|
||||
public VerifyResult setCriteria(String fieldName, Integer operatorType, Object value) {
|
||||
this.operatorType = operatorType;
|
||||
this.fieldName = fieldName;
|
||||
this.value = value;
|
||||
return doVerify();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置条件值。
|
||||
*
|
||||
* @param modelClazz 数据表对应实体对象的Class.
|
||||
* @param fieldName 条件所属的实体对象的字段名。
|
||||
* @param operatorType 条件操作符。具体值可参考当前对象的静态变量。
|
||||
* @param value 条件过滤值。
|
||||
* @return 验证结果对象,如果有错误将会返回具体的错误信息。
|
||||
*/
|
||||
public VerifyResult setCriteria(Class<?> modelClazz, String fieldName, Integer operatorType, Object value) {
|
||||
this.modelClazz = modelClazz;
|
||||
this.operatorType = operatorType;
|
||||
this.fieldName = fieldName;
|
||||
this.value = value;
|
||||
return doVerify();
|
||||
}
|
||||
|
||||
/**
|
||||
* 在执行该函数之前,该对象的所有数据均已经赋值完毕。
|
||||
* 该函数主要验证操作符字段和条件值字段对应关系的合法性。
|
||||
*
|
||||
* @return 验证结果对象,如果有错误将会返回具体的错误信息。
|
||||
*/
|
||||
public VerifyResult doVerify() {
|
||||
if (fieldName == null) {
|
||||
return VerifyResult.error("过滤字段名称 [fieldName] 不能为空!");
|
||||
}
|
||||
if (modelClazz != null) {
|
||||
if (ReflectUtil.getField(modelClazz, fieldName) == null) {
|
||||
return VerifyResult.error(
|
||||
"过滤字段 [" + fieldName + "] 在实体对象 [" + modelClazz.getSimpleName() + "] 中并不存在!");
|
||||
}
|
||||
}
|
||||
if (!checkOperatorType()) {
|
||||
return VerifyResult.error("无效的操作符类型 [" + operatorType + "]!");
|
||||
}
|
||||
// 其他操作符必须包含value值
|
||||
if (operatorType != OPERATOR_IS_NULL && operatorType != OPERATOR_NOT_NULL) {
|
||||
if (value == null) {
|
||||
String operatorString = this.getOperatorString();
|
||||
return VerifyResult.error("操作符 [" + operatorString + "] 的条件值不能为空!");
|
||||
}
|
||||
}
|
||||
if (this.operatorType == OPERATOR_IN) {
|
||||
if (!(value instanceof Collection)) {
|
||||
return VerifyResult.error("操作符 [IN] 的条件值必须为集合对象!");
|
||||
}
|
||||
if (CollectionUtils.isEmpty((Collection<?>) value)) {
|
||||
return VerifyResult.error("操作符 [IN] 的条件值不能为空!");
|
||||
}
|
||||
}
|
||||
return VerifyResult.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断操作符类型是否合法。
|
||||
*
|
||||
* @return 合法返回true,否则false。
|
||||
*/
|
||||
public boolean checkOperatorType() {
|
||||
return operatorType != null
|
||||
&& (operatorType >= OPERATOR_EQUAL && operatorType <= OPERATOR_IN);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取操作符的字符串形式。
|
||||
*
|
||||
* @return 操作符的字符串。
|
||||
*/
|
||||
public String getOperatorString() {
|
||||
switch (operatorType) {
|
||||
case OPERATOR_EQUAL:
|
||||
return " = ";
|
||||
case OPERATOR_NOT_EQUAL:
|
||||
return " != ";
|
||||
case OPERATOR_GE:
|
||||
return " >= ";
|
||||
case OPERATOR_GT:
|
||||
return " > ";
|
||||
case OPERATOR_LE:
|
||||
return " <= ";
|
||||
case OPERATOR_LT:
|
||||
return " < ";
|
||||
case OPERATOR_LIKE:
|
||||
return " LIKE ";
|
||||
case OPERATOR_NOT_NULL:
|
||||
return " IS NOT NULL ";
|
||||
case OPERATOR_IS_NULL:
|
||||
return " IS NULL ";
|
||||
case OPERATOR_IN:
|
||||
return " IN ";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取组装后的SQL Where从句,如 table_name.column_name = 'value'。
|
||||
* 与查询数据表对应的实体对象Class为当前对象的modelClazz字段。
|
||||
*
|
||||
* @exception InvalidDataFieldException selectFieldList中存在非法实体字段时,抛出该异常。
|
||||
* @return 组装后的SQL条件从句。
|
||||
*/
|
||||
public String getCriteriaString() {
|
||||
return getCriteriaString(this.modelClazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取组装后的SQL Where从句,如 table_name.column_name = 'value'。
|
||||
*
|
||||
* @param modelClazz 与查询数据表对应的实体对象的Class。
|
||||
* @exception InvalidDataFieldException selectFieldList中存在非法实体字段时,抛出该异常。
|
||||
* @exception InvalidDataModelException 参数modelClazz没有对应的table,抛出该异常。
|
||||
* @return 组装后的SQL条件从句。
|
||||
*/
|
||||
public String getCriteriaString(Class<?> modelClazz) {
|
||||
if (modelClazz == null) {
|
||||
throw new IllegalArgumentException("ModelClazz argument can't be NULL.");
|
||||
}
|
||||
Tuple2<String, Integer> fieldInfo = MyModelUtil.mapToColumnInfo(fieldName, modelClazz);
|
||||
if (fieldInfo == null) {
|
||||
throw new InvalidDataFieldException(modelClazz.getSimpleName(), fieldName);
|
||||
}
|
||||
String tableName = MyModelUtil.mapToTableName(modelClazz);
|
||||
if (tableName == null) {
|
||||
throw new InvalidDataModelException(modelClazz.getSimpleName());
|
||||
}
|
||||
StringBuilder sb = new StringBuilder(64);
|
||||
sb.append(tableName).append(".").append(fieldInfo.getFirst()).append(getOperatorString());
|
||||
if (operatorType == OPERATOR_IN) {
|
||||
Collection<?> filterValues = (Collection<?>) value;
|
||||
sb.append("(");
|
||||
int i = 0;
|
||||
for (Object filterValue : filterValues) {
|
||||
if (fieldInfo.getSecond().equals(MyModelUtil.NUMERIC_FIELD_TYPE)) {
|
||||
sb.append(filterValue);
|
||||
} else {
|
||||
sb.append("'").append(filterValue).append("'");
|
||||
}
|
||||
if (i++ != filterValues.size() - 1) {
|
||||
sb.append(", ");
|
||||
}
|
||||
}
|
||||
sb.append(")");
|
||||
} else {
|
||||
if (value != null) {
|
||||
if (fieldInfo.getSecond().equals(MyModelUtil.NUMERIC_FIELD_TYPE)) {
|
||||
sb.append(value);
|
||||
} else {
|
||||
sb.append("'").append(value).append("'");
|
||||
}
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取组装后的SQL Where从句。如 table_name.column_name = 'value'。
|
||||
*
|
||||
* @param criteriaList 条件列表,所有条件直接目前仅支持 AND 的关系。
|
||||
* @exception InvalidDataFieldException selectFieldList中存在非法实体字段时,抛出该异常。
|
||||
* @return 组装后的SQL条件从句。
|
||||
*/
|
||||
public static String getCriteriaString(List<MyWhereCriteria> criteriaList) {
|
||||
return getCriteriaString(criteriaList, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取组装后的SQL Where从句。如 table_name.column_name = 'value'。
|
||||
*
|
||||
* @param criteriaList 条件列表,所有条件直接目前仅支持 AND 的关系。
|
||||
* @param modelClazz 与数据表对应的实体对象的Class。
|
||||
* 如果不为NULL实体对象Class使用该值,否则使用每个MyWhereCriteria自身的modelClazz。
|
||||
* @exception InvalidDataFieldException selectFieldList中存在非法实体字段时,抛出该异常。
|
||||
* @return 组装后的SQL条件从句。
|
||||
*/
|
||||
public static String getCriteriaString(List<MyWhereCriteria> criteriaList, Class<?> modelClazz) {
|
||||
if (CollectionUtils.isEmpty(criteriaList)) {
|
||||
return null;
|
||||
}
|
||||
StringBuilder sb = new StringBuilder(256);
|
||||
int i = 0;
|
||||
for (MyWhereCriteria whereCriteria : criteriaList) {
|
||||
Class<?> clazz = modelClazz;
|
||||
if (clazz == null) {
|
||||
clazz = whereCriteria.modelClazz;
|
||||
}
|
||||
if (i++ != 0) {
|
||||
sb.append(" AND ");
|
||||
}
|
||||
String criteriaString = whereCriteria.getCriteriaString(clazz);
|
||||
sb.append(criteriaString);
|
||||
}
|
||||
return sb.length() == 0 ? null : sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
package com.orange.admin.common.core.object;
|
||||
|
||||
import com.orange.admin.common.core.constant.ErrorCodeEnum;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 接口返回对象。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
@Data
|
||||
public class ResponseResult<T> {
|
||||
|
||||
/**
|
||||
* 是否成功标记。
|
||||
*/
|
||||
private boolean success = true;
|
||||
/**
|
||||
* 错误码。
|
||||
*/
|
||||
private String errorCode = "NO-ERROR";
|
||||
/**
|
||||
* 错误信息描述。
|
||||
*/
|
||||
private String errorMessage = "NO-MESSAGE";
|
||||
/**
|
||||
* 实际数据。
|
||||
*/
|
||||
private T data = null;
|
||||
|
||||
/**
|
||||
* 根据参数errorCodeEnum的枚举值,判断创建成功对象还是错误对象。
|
||||
* 如果返回错误对象,errorCode 和 errorMessage 分别取自于参数 errorCodeEnum 的 name() 和 getErrorMessage()。
|
||||
*
|
||||
* @param errorCodeEnum - 错误码枚举
|
||||
* @return 返回创建的ResponseResult实例对象
|
||||
*/
|
||||
public static <T> ResponseResult<T> create(ErrorCodeEnum errorCodeEnum) {
|
||||
return create(errorCodeEnum, errorCodeEnum.getErrorMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据参数errorCodeEnum的枚举值,判断创建成功对象还是错误对象。
|
||||
* 如果返回错误对象,errorCode 和 errorMessage 分别取自于参数 errorCodeEnum 的 name() 和参数 errorMessage。
|
||||
*
|
||||
* @param errorCodeEnum - 错误码枚举。
|
||||
* @param errorMessage - 如果该参数为null,错误信息取自errorCodeEnum参数内置的errorMessage,否则使用当前参数。
|
||||
* @return 返回创建的ResponseResult实例对象
|
||||
*/
|
||||
public static <T> ResponseResult<T> create(ErrorCodeEnum errorCodeEnum, String errorMessage) {
|
||||
return errorCodeEnum == ErrorCodeEnum.NO_ERROR ? success()
|
||||
: error(errorCodeEnum.name(), errorMessage != null ? errorMessage : errorCodeEnum.getErrorMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据参数errorCodeEnum的枚举值,判断创建成功对象还是错误对象。
|
||||
* 如果返回错误对象,errorCode 和 errorMessage 分别取自于参数 errorCodeEnum 的 name() 和参数 errorMessage。
|
||||
* 如果返回的是成功对象,dataObject数据对象将被连同返回。
|
||||
*
|
||||
* @param errorCodeEnum - 错误码枚举。
|
||||
* @param errorMessage - 如果该参数为null,错误信息取自errorCodeEnum参数内置的errorMessage,否则使用当前参数。
|
||||
* @param data - 返回的数据对象。
|
||||
* @return 返回创建的ResponseResult实例对象
|
||||
*/
|
||||
public static <T> ResponseResult<T> create(ErrorCodeEnum errorCodeEnum, String errorMessage, T data) {
|
||||
return errorCodeEnum == ErrorCodeEnum.NO_ERROR ? success(data) : create(errorCodeEnum, errorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据参数errorCode是否为空,判断创建成功对象还是错误对象。
|
||||
* 如果返回错误对象,errorCode 和 errorMessage 分别取自于参数 errorCode 和参数 errorMessage。
|
||||
*
|
||||
* @param errorCode - 自定义的错误码
|
||||
* @param errorMessage - 自定义的错误信息
|
||||
* @return 返回创建的ResponseResult实例对象
|
||||
*/
|
||||
public static <T> ResponseResult<T> create(String errorCode, String errorMessage) {
|
||||
return errorCode == null ? success() : error(errorCode, errorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建成功对象。
|
||||
* 如果需要绑定返回数据,可以在实例化后调用setDataObject方法。
|
||||
*
|
||||
* @return 返回创建的ResponseResult实例对象
|
||||
*/
|
||||
public static <T> ResponseResult<T> success() {
|
||||
return success(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建带有返回数据的成功对象。
|
||||
*
|
||||
* @param data - 返回的数据对象
|
||||
* @return 返回创建的ResponseResult实例对象
|
||||
*/
|
||||
public static <T> ResponseResult<T> success(T data) {
|
||||
ResponseResult<T> resp = new ResponseResult<>();
|
||||
resp.data = data;
|
||||
return resp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建错误对象。
|
||||
* 如果返回错误对象,errorCode 和 errorMessage 分别取自于参数 errorCodeEnum 的 name() 和 getErrorMessage()。
|
||||
*
|
||||
* @param errorCodeEnum - 错误码枚举
|
||||
* @return 返回创建的ResponseResult实例对象
|
||||
*/
|
||||
public static <T> ResponseResult<T> error(ErrorCodeEnum errorCodeEnum) {
|
||||
return error(errorCodeEnum.name(), errorCodeEnum.getErrorMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建错误对象。
|
||||
* 如果返回错误对象,errorCode 和 errorMessage 分别取自于参数 errorCodeEnum 的 name() 和参数 errorMessage。
|
||||
*
|
||||
* @param errorCodeEnum - 错误码枚举
|
||||
* @param errorMessage - 自定义的错误信息
|
||||
* @return 返回创建的ResponseResult实例对象
|
||||
*/
|
||||
public static <T> ResponseResult<T> error(ErrorCodeEnum errorCodeEnum, String errorMessage) {
|
||||
return error(errorCodeEnum.name(), errorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建错误对象。
|
||||
* 如果返回错误对象,errorCode 和 errorMessage 分别取自于参数 errorCode 和参数 errorMessage。
|
||||
*
|
||||
* @param errorCode - 自定义的错误码
|
||||
* @param errorMessage - 自定义的错误信息
|
||||
* @return 返回创建的ResponseResult实例对象
|
||||
*/
|
||||
public static <T> ResponseResult<T> error(String errorCode, String errorMessage) {
|
||||
return new ResponseResult<>(false, errorCode, errorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否成功。
|
||||
*
|
||||
* @return true成功,否则false。
|
||||
*/
|
||||
public boolean isSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
private ResponseResult() {
|
||||
|
||||
}
|
||||
|
||||
private ResponseResult(boolean success, String errorCode, String errorMessage) {
|
||||
this.success = success;
|
||||
this.errorCode = errorCode;
|
||||
this.errorMessage = errorMessage;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.orange.admin.common.core.object;
|
||||
|
||||
import com.orange.admin.common.core.util.ContextUtil;
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* 基于Jwt,用于前后端传递的令牌对象。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
@Data
|
||||
@ToString
|
||||
public class TokenData {
|
||||
|
||||
/**
|
||||
* 在HTTP Request对象中的属性键。
|
||||
*/
|
||||
public static final String REQUEST_ATTRIBUTE_NAME = "tokenData";
|
||||
/**
|
||||
* 用户Id。
|
||||
*/
|
||||
private Long userId;
|
||||
/**
|
||||
* 用户所在部门Id。
|
||||
*/
|
||||
private Long deptId;
|
||||
/**
|
||||
* 是否为超级管理员。
|
||||
*/
|
||||
private Boolean isAdmin;
|
||||
/**
|
||||
* 用户显示名称。
|
||||
*/
|
||||
private String showName;
|
||||
/**
|
||||
* 标识不同登录的会话Id。
|
||||
*/
|
||||
private String sessionId;
|
||||
|
||||
/**
|
||||
* 将令牌对象添加到Http请求对象。
|
||||
*
|
||||
* @param tokenData 令牌对象。
|
||||
*/
|
||||
public static void addToRequest(TokenData tokenData) {
|
||||
HttpServletRequest request = ContextUtil.getHttpRequest();
|
||||
request.setAttribute(TokenData.REQUEST_ATTRIBUTE_NAME, tokenData);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Http Request对象中获取令牌对象。
|
||||
*
|
||||
* @return 令牌对象。
|
||||
*/
|
||||
public static TokenData takeFromRequest() {
|
||||
HttpServletRequest request = ContextUtil.getHttpRequest();
|
||||
return (TokenData) request.getAttribute(REQUEST_ATTRIBUTE_NAME);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.orange.admin.common.core.object;
|
||||
|
||||
/**
|
||||
* 二元组对象。主要用于可以一次返回多个结果的场景,同时还能避免强制转换。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
public class Tuple2<T1, T2> {
|
||||
|
||||
/**
|
||||
* 第一个变量。
|
||||
*/
|
||||
private final T1 first;
|
||||
/**
|
||||
* 第二个变量。
|
||||
*/
|
||||
private final T2 second;
|
||||
|
||||
/**
|
||||
* 构造函数。
|
||||
*
|
||||
* @param first 第一个变量。
|
||||
* @param second 第二个变量。
|
||||
*/
|
||||
public Tuple2(T1 first, T2 second) {
|
||||
this.first = first;
|
||||
this.second = second;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取第一个变量。
|
||||
*
|
||||
* @return 返回第一个变量。
|
||||
*/
|
||||
public T1 getFirst() {
|
||||
return first;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取第二个变量。
|
||||
*
|
||||
* @return 返回第二个变量。
|
||||
*/
|
||||
public T2 getSecond() {
|
||||
return second;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.orange.admin.common.core.object;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 接口数据验证结果对象。主要是Service类使用。
|
||||
* 同时为了提升效率,减少查询次数,可以根据具体的需求,将部分验证关联对象存入data字段,以供Controller使用。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
@Data
|
||||
public class VerifyResult {
|
||||
|
||||
/**
|
||||
* 是否成功标记。
|
||||
*/
|
||||
private boolean success = true;
|
||||
/**
|
||||
* 错误信息描述。
|
||||
*/
|
||||
private String errorMessage = null;
|
||||
/**
|
||||
* 在验证同时,仍然需要附加的关联数据对象。
|
||||
*/
|
||||
private JSONObject data;
|
||||
|
||||
/**
|
||||
* 创建验证结果对象。
|
||||
*
|
||||
* @param errorMessage 错误描述信息。
|
||||
* @return 如果参数为空,表示成功,否则返回代码错误信息的错误对象实例。
|
||||
*/
|
||||
public static VerifyResult create(String errorMessage) {
|
||||
return errorMessage == null ? ok() : error(errorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建验证结果对象。
|
||||
*
|
||||
* @param errorMessage 错误描述信息。
|
||||
* @param data 附带的数据对象。
|
||||
* @return 如果参数为空,表示成功,否则返回代码错误信息的错误对象实例。
|
||||
*/
|
||||
public static VerifyResult create(String errorMessage, JSONObject data) {
|
||||
return errorMessage == null ? ok(data) : error(errorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建表示验证成功的对象实例。
|
||||
*
|
||||
* @return 验证成功对象实例。
|
||||
*/
|
||||
public static VerifyResult ok() {
|
||||
return new VerifyResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建表示验证成功的对象实例。
|
||||
*
|
||||
* @param data 附带的数据对象。
|
||||
* @return 验证成功对象实例。
|
||||
*/
|
||||
public static VerifyResult ok(JSONObject data) {
|
||||
VerifyResult result = new VerifyResult();
|
||||
result.data = data;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建表示验证失败的对象实例。
|
||||
*
|
||||
* @param errorMessage 错误描述。
|
||||
* @return 验证失败对象实例。
|
||||
*/
|
||||
public static VerifyResult error(String errorMessage) {
|
||||
VerifyResult verifyResult = new VerifyResult();
|
||||
verifyResult.success = false;
|
||||
verifyResult.errorMessage = errorMessage;
|
||||
return verifyResult;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.orange.admin.common.core.util;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Spring 系统启动应用感知对象,主要用于获取Spring Bean的上下文对象,后续的代码中可以直接查找系统中加载的Bean对象。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
@Component
|
||||
public class ApplicationContextHolder implements ApplicationContextAware {
|
||||
|
||||
private static ApplicationContext applicationContext;
|
||||
|
||||
/**
|
||||
* Spring 启动的过程中会自动调用,并将应用上下文对象赋值进来。
|
||||
*
|
||||
* @param applicationContext 应用上下文对象,可通过该对象查找Spring中已经加载的Bean。
|
||||
* @throws BeansException Bean处理相关的异常。
|
||||
*/
|
||||
@Override
|
||||
public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
|
||||
ApplicationContextHolder.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取应用上下文对象。
|
||||
*
|
||||
* @return 应用上下文。
|
||||
*/
|
||||
public static ApplicationContext getApplicationContext() {
|
||||
assertApplicationContext();
|
||||
return applicationContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据BeanName,获取Bean对象。
|
||||
*
|
||||
* @param beanName Bean名称。
|
||||
* @param <T> 返回的Bean类型。
|
||||
* @return Bean对象。
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T getBean(String beanName) {
|
||||
assertApplicationContext();
|
||||
return (T) applicationContext.getBean(beanName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据Bean的ClassType,获取Bean对象。
|
||||
*
|
||||
* @param beanType Bean的Class类型。。
|
||||
* @param <T> 返回的Bean类型。
|
||||
* @return Bean对象。
|
||||
*/
|
||||
public static <T> T getBean(Class<T> beanType) {
|
||||
assertApplicationContext();
|
||||
return applicationContext.getBean(beanType);
|
||||
}
|
||||
|
||||
private static void assertApplicationContext() {
|
||||
if (ApplicationContextHolder.applicationContext == null) {
|
||||
throw new RuntimeException("applicaitonContext属性为null,请检查是否注入了ApplicationContextHolder!");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.orange.admin.common.core.util;
|
||||
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* 获取Servlet HttpRequest和HttpResponse的工具类。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
public class ContextUtil {
|
||||
|
||||
/**
|
||||
* 判断当前是否处于HttpServletRequest上下文环境。
|
||||
*
|
||||
* @return 是返回true,否则false。
|
||||
*/
|
||||
public static boolean hasRequestContext() {
|
||||
return RequestContextHolder.getRequestAttributes() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Servlet请求上下文的HttpRequest对象。
|
||||
*
|
||||
* @return 请求上下文中的HttpRequest对象。
|
||||
*/
|
||||
public static HttpServletRequest getHttpRequest() {
|
||||
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Servlet请求上下文的HttpResponse对象。
|
||||
*
|
||||
* @return 请求上下文中的HttpResponse对象。
|
||||
*/
|
||||
public static HttpServletResponse getHttpResponse() {
|
||||
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.orange.admin.common.core.util;
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.poi.excel.ExcelUtil;
|
||||
import cn.hutool.poi.excel.ExcelWriter;
|
||||
import cn.jimmyshi.beanquery.BeanQuery;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.csv.CSVFormat;
|
||||
import org.apache.commons.csv.CSVPrinter;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 导出工具类,目前支持xlsx和csv两种类型。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
public class ExportUtil {
|
||||
|
||||
/**
|
||||
* 数据导出。目前仅支持xlsx和csv。
|
||||
*
|
||||
* @param dataList 导出数据列表。
|
||||
* @param selectFieldMap 导出的数据字段,key为对象字段名称,value为中文标题名称。
|
||||
* @param filename 导出文件名。
|
||||
* @param <T> 数据对象类型。
|
||||
* @throws IOException 文件操作失败。
|
||||
*/
|
||||
public static <T> void doExport(
|
||||
Collection<T> dataList, Map<String, String> selectFieldMap, String filename) throws IOException {
|
||||
if (CollectionUtils.isEmpty(dataList)) {
|
||||
return;
|
||||
}
|
||||
StringBuilder sb = new StringBuilder(128);
|
||||
for (Map.Entry<String, String> e : selectFieldMap.entrySet()) {
|
||||
sb.append(e.getKey()).append(" as ").append(e.getValue()).append(", ");
|
||||
}
|
||||
// 去掉末尾的逗号
|
||||
String selectFieldString = sb.substring(0, sb.length() - 2);
|
||||
// 写出数据到xcel格式的输出流
|
||||
List<Map<String, Object>> resultList = BeanQuery.select(selectFieldString).executeFrom(dataList);
|
||||
// 构建HTTP输出流参数
|
||||
HttpServletResponse response = ContextUtil.getHttpResponse();
|
||||
response.setHeader("content-type", "application/octet-stream");
|
||||
response.setContentType("application/octet-stream");
|
||||
response.setHeader("Content-Disposition", "attachment;filename=" + filename);
|
||||
if ("xlsx".equals(FilenameUtils.getExtension(filename))) {
|
||||
ServletOutputStream out = response.getOutputStream();
|
||||
ExcelWriter writer = ExcelUtil.getWriter(true);
|
||||
writer.setRowHeight(-1, 30);
|
||||
writer.setColumnWidth(-1, 30);
|
||||
writer.setColumnWidth(1, 20);
|
||||
writer.write(resultList);
|
||||
writer.flush(out);
|
||||
writer.close();
|
||||
IoUtil.close(out);
|
||||
} else if ("csv".equals(FilenameUtils.getExtension(filename))) {
|
||||
Collection<String> headerList = selectFieldMap.values();
|
||||
String[] headerArray = new String[headerList.size()];
|
||||
headerList.toArray(headerArray);
|
||||
CSVFormat format = CSVFormat.DEFAULT.withHeader(headerArray);
|
||||
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
try (Writer out = response.getWriter(); CSVPrinter printer = new CSVPrinter(out, format)) {
|
||||
for (Map<String, Object> o : resultList) {
|
||||
for (Map.Entry<String, Object> entry : o.entrySet()) {
|
||||
printer.print(entry.getValue());
|
||||
}
|
||||
printer.println();
|
||||
}
|
||||
printer.flush();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
throw new RuntimeException("不支持的导出文件类型!");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
package com.orange.admin.common.core.util;
|
||||
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.poi.excel.ExcelReader;
|
||||
import cn.hutool.poi.excel.ExcelUtil;
|
||||
import cn.hutool.poi.excel.sax.Excel07SaxReader;
|
||||
import org.apache.commons.csv.CSVFormat;
|
||||
import org.apache.commons.csv.CSVParser;
|
||||
import org.apache.commons.csv.CSVRecord;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 导入工具类,目前支持xlsx和csv两种类型。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
public class ImportUtil {
|
||||
|
||||
/**
|
||||
* 同步导入方式。
|
||||
*
|
||||
* @param filename 导入文件名。
|
||||
* @return 导入数据列表。
|
||||
*/
|
||||
public static List<Map<String, Object>> doImport(String filename) {
|
||||
if ("xlsx".equals(FilenameUtils.getExtension(filename))) {
|
||||
try (ExcelReader reader = ExcelUtil.getReader(filename)) {
|
||||
return reader.readAll();
|
||||
}
|
||||
} else if ("csv".equals(FilenameUtils.getExtension(filename))) {
|
||||
List<Map<String, Object>> resultList = new LinkedList<>();
|
||||
try (FileReader reader = new FileReader(filename)) {
|
||||
CSVParser parser = CSVFormat.DEFAULT.withFirstRecordAsHeader().parse(reader);
|
||||
Map<String, Integer> headerMap = parser.getHeaderMap();
|
||||
for (CSVRecord record : parser) {
|
||||
Map<String, Object> rowMap = new LinkedHashMap<>();
|
||||
for (final Map.Entry<String, Integer> header : headerMap.entrySet()) {
|
||||
int col = header.getValue();
|
||||
if (col < record.size()) {
|
||||
rowMap.put(header.getKey(), record.get(col));
|
||||
}
|
||||
}
|
||||
resultList.add(rowMap);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return resultList;
|
||||
}
|
||||
throw new RuntimeException("不支持的导入文件类型!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步导入方式,即SAX导入方式。
|
||||
*
|
||||
* @param filename 导入文件名。
|
||||
* @param importer 异步导入处理接口。
|
||||
* @throws IOException 文件处理异常。
|
||||
*/
|
||||
public static <T> void doImport(String filename, BaseImporter<T> importer) throws IOException {
|
||||
if ("xlsx".equals(FilenameUtils.getExtension(filename))) {
|
||||
Excel07SaxReader reader = new MyExcel07SaxReader<>(importer);
|
||||
try (InputStream in = new FileInputStream(filename)) {
|
||||
reader.read(in, 0);
|
||||
}
|
||||
} else if ("csv".equals(FilenameUtils.getExtension(filename))) {
|
||||
try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
|
||||
int rowIndex = 0;
|
||||
do {
|
||||
String rowData = reader.readLine();
|
||||
if (StringUtils.isBlank(rowData)) {
|
||||
importer.doImport(-1, null);
|
||||
break;
|
||||
}
|
||||
String[] dataArray = StringUtils.split(rowData, ",");
|
||||
importer.doImport(rowIndex++, Arrays.asList(dataArray));
|
||||
if (importer.doInterrupt()) {
|
||||
break;
|
||||
}
|
||||
} while (true);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("不支持的导入文件类型!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步导入抽象类。
|
||||
*
|
||||
* @param <T> 导入数据对象类型。
|
||||
*/
|
||||
public static abstract class BaseImporter<T> {
|
||||
private Class<T> beanType;
|
||||
private List<T> batchRowList = new LinkedList<>();
|
||||
private int batchSize;
|
||||
private Field[] fieldArray = null;
|
||||
private Map<String, String> headerColumnMap;
|
||||
|
||||
public BaseImporter(int batchSize, Class<T> beanType, Map<String, String> headerColumnMap) {
|
||||
if (batchSize <= 0) {
|
||||
batchSize = 100;
|
||||
}
|
||||
this.batchSize = batchSize;
|
||||
this.beanType = beanType;
|
||||
this.headerColumnMap = headerColumnMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入操作执行函数。
|
||||
*
|
||||
* @param rowIndex 当前行号。
|
||||
* @param row 当前行数据列表对象。
|
||||
*/
|
||||
public void doImport(int rowIndex, List<Object> row) {
|
||||
if (row == null) {
|
||||
doProcess(batchRowList);
|
||||
doFinish();
|
||||
batchRowList.clear();
|
||||
return;
|
||||
}
|
||||
if (rowIndex <= 0) {
|
||||
fieldArray = new Field[row.size()];
|
||||
List<String> headerList = row.stream().map(Object::toString).collect(Collectors.toList());
|
||||
List<String> columnList = new ArrayList<>(row.size());
|
||||
for (String headerName : headerList) {
|
||||
String columnName = headerColumnMap.get(headerName);
|
||||
if (columnName != null) {
|
||||
columnList.add(columnName);
|
||||
}
|
||||
}
|
||||
columnList.stream()
|
||||
.map(columnName -> ReflectUtil.getField(beanType, columnName))
|
||||
.collect(Collectors.toList())
|
||||
.toArray(fieldArray);
|
||||
return;
|
||||
}
|
||||
T data;
|
||||
try {
|
||||
data = beanType.newInstance();
|
||||
for (int i = 0; i < row.size(); i++) {
|
||||
Object value = row.get(i);
|
||||
Field field = fieldArray[i];
|
||||
if (field != null) {
|
||||
ReflectUtil.setFieldValue(data, field, value);
|
||||
}
|
||||
}
|
||||
batchRowList.add(data);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (rowIndex % batchSize == 0) {
|
||||
doProcess(batchRowList);
|
||||
batchRowList.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据处理进行中回调模板函数。
|
||||
*
|
||||
* @param batchRowList 一批数据行。
|
||||
*/
|
||||
public abstract void doProcess(List<T> batchRowList);
|
||||
|
||||
/**
|
||||
* 数据处理完毕回调模板函数。
|
||||
*/
|
||||
public abstract void doFinish();
|
||||
|
||||
/**
|
||||
* 数据处理终端标记模板函数。
|
||||
* @return 是否中断。true则中断后面的处理。
|
||||
*/
|
||||
public abstract boolean doInterrupt();
|
||||
}
|
||||
|
||||
static class MyExcel07SaxReader<T> extends Excel07SaxReader {
|
||||
private BaseImporter<T> importer;
|
||||
|
||||
MyExcel07SaxReader(BaseImporter<T> importer) {
|
||||
super((sheetIndex, rowIndex, rowList) -> importer.doImport(rowIndex, rowList));
|
||||
this.importer = importer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endElement(String uri, String localName, String qName) {
|
||||
super.endElement(uri, localName, qName);
|
||||
if (importer.doInterrupt()) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endDocument() {
|
||||
importer.doImport(-1, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.orange.admin.common.core.util;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Ip工具类。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
@Slf4j
|
||||
public class IpUtil {
|
||||
|
||||
private static final String UNKNOWN = "unknown";
|
||||
|
||||
/**
|
||||
* 通过Servlet的HttpRequest对象获取Ip地址。
|
||||
*
|
||||
* @param request HttpRequest对象。
|
||||
* @return 本次请求的Ip地址。
|
||||
*/
|
||||
public static String getRemoteIpAddress(HttpServletRequest request) {
|
||||
String ip = null;
|
||||
//X-Forwarded-For:Squid 服务代理
|
||||
String ipAddresses = request.getHeader("X-Forwarded-For");
|
||||
if (StringUtils.isBlank(ipAddresses) || UNKNOWN.equalsIgnoreCase(ipAddresses)) {
|
||||
//Proxy-Client-IP:apache 服务代理
|
||||
ipAddresses = request.getHeader("Proxy-Client-IP");
|
||||
}
|
||||
if (StringUtils.isBlank(ipAddresses) || UNKNOWN.equalsIgnoreCase(ipAddresses)) {
|
||||
//WL-Proxy-Client-IP:weblogic 服务代理
|
||||
ipAddresses = request.getHeader("WL-Proxy-Client-IP");
|
||||
}
|
||||
if (StringUtils.isBlank(ipAddresses) || UNKNOWN.equalsIgnoreCase(ipAddresses)) {
|
||||
//HTTP_CLIENT_IP:有些代理服务器
|
||||
ipAddresses = request.getHeader("HTTP_CLIENT_IP");
|
||||
}
|
||||
if (StringUtils.isBlank(ipAddresses) || UNKNOWN.equalsIgnoreCase(ipAddresses)) {
|
||||
//X-Real-IP:nginx服务代理
|
||||
ipAddresses = request.getHeader("X-Real-IP");
|
||||
}
|
||||
//有些网络通过多层代理,那么获取到的ip就会有多个,一般都是通过逗号(,)分割开来,并且第一个ip为客户端的真实IP
|
||||
if (StringUtils.isNotBlank(ipAddresses)) {
|
||||
ip = ipAddresses.split(",")[0];
|
||||
}
|
||||
//还是不能获取到,最后再通过request.getRemoteAddr();获取
|
||||
if (StringUtils.isBlank(ipAddresses) || UNKNOWN.equalsIgnoreCase(ipAddresses)) {
|
||||
ip = request.getRemoteAddr();
|
||||
}
|
||||
return ip;
|
||||
}
|
||||
|
||||
public static String getFirstLocalIpAddress() {
|
||||
String ip;
|
||||
try {
|
||||
List<String> ipList = getHostAddress();
|
||||
// default the first
|
||||
ip = (!ipList.isEmpty()) ? ipList.get(0) : "";
|
||||
} catch (Exception ex) {
|
||||
ip = "";
|
||||
log.error("Failed to call ", ex);
|
||||
}
|
||||
return ip;
|
||||
}
|
||||
|
||||
private static List<String> getHostAddress() throws SocketException {
|
||||
List<String> ipList = new ArrayList<>(5);
|
||||
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
|
||||
while (interfaces.hasMoreElements()) {
|
||||
NetworkInterface ni = interfaces.nextElement();
|
||||
Enumeration<InetAddress> allAddress = ni.getInetAddresses();
|
||||
while (allAddress.hasMoreElements()) {
|
||||
InetAddress address = allAddress.nextElement();
|
||||
if (address.isLoopbackAddress()) {
|
||||
// skip the loopback addr
|
||||
continue;
|
||||
}
|
||||
if (address instanceof Inet6Address) {
|
||||
// skip the IPv6 addr
|
||||
continue;
|
||||
}
|
||||
String hostAddress = address.getHostAddress();
|
||||
ipList.add(hostAddress);
|
||||
}
|
||||
}
|
||||
return ipList;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package com.orange.admin.common.core.util;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 基于JWT的Token生成工具类
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
public class JwtUtil {
|
||||
|
||||
private static final String TOKEN_PREFIX = "Bearer:";
|
||||
private static final String CLAIM_KEY_CREATEDTIME = "CreatedTime";
|
||||
|
||||
/**
|
||||
* Token缺省过期时间是30分钟
|
||||
*/
|
||||
private static final Long TOKEN_EXPIRATION = 1800000L;
|
||||
/**
|
||||
* 缺省情况下,Token会每5分钟被刷新一次
|
||||
*/
|
||||
private static final Long REFRESH_TOKEN_INTERVAL = 300000L;
|
||||
|
||||
/**
|
||||
* 生成加密后的JWT令牌,生成的结果中包含令牌前缀,如"Bearer "
|
||||
*
|
||||
* @param claims 令牌中携带的数据
|
||||
* @param expirationMillisecond 过期的毫秒数
|
||||
* @return 生成后的令牌信息
|
||||
*/
|
||||
public static String generateToken(Map<String, Object> claims, long expirationMillisecond, String signingKey) {
|
||||
// 自动添加token的创建时间
|
||||
long createTime = System.currentTimeMillis();
|
||||
claims.put(CLAIM_KEY_CREATEDTIME, createTime);
|
||||
String token = Jwts.builder()
|
||||
.setClaims(claims)
|
||||
.setExpiration(new Date(createTime + expirationMillisecond))
|
||||
.signWith(SignatureAlgorithm.HS512, signingKey)
|
||||
.compact();
|
||||
return TOKEN_PREFIX + token;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成加密后的JWT令牌,生成的结果中包含令牌前缀,如"Bearer "
|
||||
*
|
||||
* @param claims 令牌中携带的数据
|
||||
* @return 生成后的令牌信息
|
||||
*/
|
||||
public static String generateToken(Map<String, Object> claims, String signingKey) {
|
||||
return generateToken(claims, TOKEN_EXPIRATION, signingKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取token中的数据对象
|
||||
*
|
||||
* @param token 令牌信息(需要包含令牌前缀,如"Bearer:")
|
||||
* @return 令牌中的数据对象,解析视频返回null。
|
||||
*/
|
||||
public static Claims parseToken(String token, String signingKey) {
|
||||
if (token == null || !token.startsWith(TOKEN_PREFIX)) {
|
||||
return null;
|
||||
}
|
||||
String tokenKey = token.substring(TOKEN_PREFIX.length());
|
||||
Claims claims = null;
|
||||
try {
|
||||
claims = Jwts.parser().setSigningKey(signingKey).parseClaimsJws(tokenKey).getBody();
|
||||
} catch (Exception e) {
|
||||
System.out.println("Token Expired");
|
||||
}
|
||||
return claims;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断令牌是否过期
|
||||
*
|
||||
* @param claims 令牌解密后的Map对象。
|
||||
* @return true 过期,否则false。
|
||||
*/
|
||||
public static boolean isNullOrExpired(Claims claims) {
|
||||
return claims == null || claims.getExpiration().before(new Date());
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断解密后的Token payload是否需要被强制刷新,如果需要,则调用generateToken方法重新生成Token。
|
||||
*
|
||||
* @param claims Token解密后payload数据
|
||||
* @return true 需要刷新,否则false
|
||||
*/
|
||||
public static boolean needToRefresh(Claims claims) {
|
||||
if (claims == null) {
|
||||
return false;
|
||||
}
|
||||
Long createTime = (Long) claims.get(CLAIM_KEY_CREATEDTIME);
|
||||
return createTime == null || System.currentTimeMillis() - createTime > REFRESH_TOKEN_INTERVAL;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package com.orange.admin.common.core.util;
|
||||
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.Validation;
|
||||
import javax.validation.Validator;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 脚手架中常用的基本工具方法集合,一般而言工程内部使用的方法。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
public class MyCommonUtil {
|
||||
|
||||
private static Validator validator;
|
||||
|
||||
static {
|
||||
validator = Validation.buildDefaultValidatorFactory().getValidator();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建uuid。
|
||||
*
|
||||
* @return 返回uuid。
|
||||
*/
|
||||
public static String generateUuid() {
|
||||
return UUID.randomUUID().toString().replaceAll("-", "");
|
||||
}
|
||||
|
||||
/**
|
||||
* 对用户密码进行加盐后加密。
|
||||
*
|
||||
* @param password 明文密码。
|
||||
* @return 加密后的密码。
|
||||
*/
|
||||
public static String encrptedPassword(String password, String passwordSalt) {
|
||||
return DigestUtils.md5Hex(password + passwordSalt);
|
||||
}
|
||||
|
||||
/**
|
||||
* 这个方法一般用于Controller对于入口参数的基本验证。
|
||||
* 对于字符串,如果为空字符串,也将视为Blank,同时返回true。
|
||||
*
|
||||
* @param objs 一组参数。
|
||||
* @return 返回是否存在null或空字符串的参数。
|
||||
*/
|
||||
public static boolean existBlankArgument(Object...objs) {
|
||||
for (Object obj : objs) {
|
||||
if (MyCommonUtil.isBlankOrNull(obj)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 结果和 existBlankArgument 相反。
|
||||
*
|
||||
* @param objs 一组参数。
|
||||
* @return 返回是否存在null或空字符串的参数。
|
||||
*/
|
||||
public static boolean existNotBlankArgument(Object...objs) {
|
||||
for (Object obj : objs) {
|
||||
if (!MyCommonUtil.isBlankOrNull(obj)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证参数是否为空。
|
||||
*
|
||||
* @param obj 待判断的参数。
|
||||
* @return 空或者null返回true,否则false。
|
||||
*/
|
||||
public static boolean isBlankOrNull(Object obj) {
|
||||
if (obj instanceof Collection) {
|
||||
return CollectionUtils.isEmpty((Collection<?>) obj);
|
||||
}
|
||||
return obj == null || (obj instanceof CharSequence && StringUtils.isBlank((CharSequence) obj));
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证参数是否为非空。
|
||||
*
|
||||
* @param obj 待判断的参数。
|
||||
* @return 空或者null返回false,否则true。
|
||||
*/
|
||||
public static boolean isNotBlankOrNull(Object obj) {
|
||||
return !isBlankOrNull(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断模型对象是否通过校验,没有通过返回具体的校验错误信息。
|
||||
*
|
||||
* @param model 带校验的model。
|
||||
* @param groups Validate绑定的校验组。
|
||||
* @return 没有错误返回null,否则返回具体的错误信息。
|
||||
*/
|
||||
public static <T> String getModelValidationError(T model, Class<?>...groups) {
|
||||
Set<ConstraintViolation<T>> constraintViolations = validator.validate(model, groups);
|
||||
if (!constraintViolations.isEmpty()) {
|
||||
Iterator<ConstraintViolation<T>> it = constraintViolations.iterator();
|
||||
ConstraintViolation<T> constraint = it.next();
|
||||
return constraint.getMessage();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 拼接参数中的字符串列表,用指定分隔符进行分割,同时每个字符串对象用单引号括起来。
|
||||
*
|
||||
* @param dataList 字符串集合。
|
||||
* @param separator 分隔符。
|
||||
* @return 拼接后的字符串。
|
||||
*/
|
||||
public static String joinString(Collection<String> dataList, final char separator) {
|
||||
int index = 0;
|
||||
StringBuilder sb = new StringBuilder(128);
|
||||
for (String deptId : dataList) {
|
||||
sb.append("'").append(deptId).append("'");
|
||||
if (index++ != dataList.size() - 1) {
|
||||
sb.append(separator);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
package com.orange.admin.common.core.util;
|
||||
|
||||
import com.orange.admin.common.core.object.Tuple2;
|
||||
import org.apache.commons.lang3.time.DateUtils;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Period;
|
||||
import org.joda.time.format.DateTimeFormat;
|
||||
import org.joda.time.format.DateTimeFormatter;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
||||
import static org.joda.time.PeriodType.days;
|
||||
|
||||
/**
|
||||
* 日期工具类,主要封装了部分joda-time中的方法,让很多代码一行完成,同时统一了日期到字符串的pattern格式。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
public class MyDateUtil {
|
||||
|
||||
/**
|
||||
* 统一的日期pattern,今后可以根据自己的需求去修改。
|
||||
*/
|
||||
public static final String COMMON_DATE_FORMAT = "yyyy-MM-dd";
|
||||
/**
|
||||
* 统一的日期时间pattern,今后可以根据自己的需求去修改。
|
||||
*/
|
||||
public static final String COMMON_DATETIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
|
||||
/**
|
||||
* 缺省日期格式化器,提前获取提升运行时效率。
|
||||
*/
|
||||
private static final DateTimeFormatter DATE_PARSE_FORMATTER =
|
||||
DateTimeFormat.forPattern(MyDateUtil.COMMON_DATE_FORMAT);
|
||||
/**
|
||||
* 缺省日期时间格式化器,提前获取提升运行时效率。
|
||||
*/
|
||||
private static final DateTimeFormatter DATETIME_PARSE_FORMATTER =
|
||||
DateTimeFormat.forPattern(MyDateUtil.COMMON_DATETIME_FORMAT);
|
||||
|
||||
/**
|
||||
* 获取一天的开始时间的字符串格式,如2019-08-03 00:00:00.000。
|
||||
*
|
||||
* @param dateTime 待格式化的日期时间对象。
|
||||
* @return 格式化后的字符串。
|
||||
*/
|
||||
public static String getBeginTimeOfDay(DateTime dateTime) {
|
||||
return dateTime.withTimeAtStartOfDay().toString(COMMON_DATETIME_FORMAT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一天的结束时间的字符串格式,如2019-08-03 23:59:59.999。
|
||||
*
|
||||
* @param dateTime 待格式化的日期时间对象。
|
||||
* @return 格式化后的字符串。
|
||||
*/
|
||||
public static String getEndTimeOfDay(DateTime dateTime) {
|
||||
return dateTime.withTime(23, 59, 59, 999).toString(COMMON_DATETIME_FORMAT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一天中的开始时间和结束时间的字符串格式,如2019-08-03 00:00:00.000 和 2019-08-03 23:59:59.999。
|
||||
*
|
||||
* @param dateTime 待格式化的日期时间对象。
|
||||
* @return 包含格式后字符串的二元组对象。
|
||||
*/
|
||||
public static Tuple2<String, String> getDateTimeRangeOfDay(DateTime dateTime) {
|
||||
return new Tuple2<>(getBeginTimeOfDay(dateTime), getEndTimeOfDay(dateTime));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本月第一天的日期格式。如2019-08-01。
|
||||
*
|
||||
* @param dateTime 待格式化的日期对象。
|
||||
* @return 格式化后的字符串。
|
||||
*/
|
||||
public static String getBeginDateOfMonth(DateTime dateTime) {
|
||||
return dateTime.withDayOfMonth(1).toString(COMMON_DATE_FORMAT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本月第一天的日期格式。如2019-08-01。
|
||||
*
|
||||
* @param dateString 待格式化的日期字符串对象。
|
||||
* @return 格式化后的字符串。
|
||||
*/
|
||||
public static String getBeginDateOfMonth(String dateString) {
|
||||
DateTime dateTime = toDate(dateString);
|
||||
return dateTime.withDayOfMonth(1).toString(COMMON_DATE_FORMAT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算指定日期距离今天相差的天数。
|
||||
*
|
||||
* @param dateTime 待格式化的日期时间对象。
|
||||
* @return 相差天数。
|
||||
*/
|
||||
public static int getDayDiffToNow(DateTime dateTime) {
|
||||
return new Period(dateTime, new DateTime(), days()).getDays();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将日期对象格式化为缺省的字符串格式。
|
||||
*
|
||||
* @param dateTime 待格式化的日期对象。
|
||||
* @return 格式化后的字符串。
|
||||
*/
|
||||
public static String toDateString(DateTime dateTime) {
|
||||
return dateTime.toString(COMMON_DATE_FORMAT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将日期时间对象格式化为缺省的字符串格式。
|
||||
*
|
||||
* @param dateTime 待格式化的日期对象。
|
||||
* @return 格式化后的字符串。
|
||||
*/
|
||||
public static String toDateTimeString(DateTime dateTime) {
|
||||
return dateTime.toString(COMMON_DATETIME_FORMAT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将缺省格式的日期字符串解析为日期对象。
|
||||
*
|
||||
* @param dateString 待解析的字符串。
|
||||
* @return 解析后的日期对象。
|
||||
*/
|
||||
public static DateTime toDate(String dateString) {
|
||||
return DATE_PARSE_FORMATTER.parseDateTime(dateString);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将缺省格式的日期字符串解析为日期对象。
|
||||
*
|
||||
* @param dateTimeString 待解析的字符串。
|
||||
* @return 解析后的日期对象。
|
||||
*/
|
||||
public static DateTime toDateTime(String dateTimeString) {
|
||||
return DATETIME_PARSE_FORMATTER.parseDateTime(dateTimeString);
|
||||
}
|
||||
|
||||
/**
|
||||
* 截取时间到天。如2019-10-03 01:20:30 转换为 2019-10-03 00:00:00。
|
||||
* 由于没有字符串的中间转换,因此效率更高。
|
||||
*
|
||||
* @param date 待截取日期对象。
|
||||
* @return 转换后日期对象。
|
||||
*/
|
||||
public static Date truncateToDay(Date date) {
|
||||
return DateUtils.truncate(date, Calendar.DAY_OF_MONTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* 截取时间到月。如2019-10-03 01:20:30 转换为 2019-10-01 00:00:00。
|
||||
* 由于没有字符串的中间转换,因此效率更高。
|
||||
*
|
||||
* @param date 待截取日期对象。
|
||||
* @return 转换后日期对象。
|
||||
*/
|
||||
public static Date truncateToMonth(Date date) {
|
||||
return DateUtils.truncate(date, Calendar.MONTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* 截取时间到年。如2019-10-03 01:20:30 转换为 2019-01-01 00:00:00。
|
||||
* 由于没有字符串的中间转换,因此效率更高。
|
||||
*
|
||||
* @param date 待截取日期对象。
|
||||
* @return 转换后日期对象。
|
||||
*/
|
||||
public static Date truncateToYear(Date date) {
|
||||
return DateUtils.truncate(date, Calendar.YEAR);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,409 @@
|
||||
package com.orange.admin.common.core.util;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import com.orange.admin.common.core.annotation.RelationDict;
|
||||
import com.orange.admin.common.core.annotation.RelationOneToOne;
|
||||
import com.orange.admin.common.core.object.Tuple2;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import tk.mybatis.mapper.entity.Example;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.Transient;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 负责Model数据操作、类型转换和关系关联等行为的工具类。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
@Slf4j
|
||||
public class MyModelUtil {
|
||||
|
||||
/**
|
||||
* 数值型字段。
|
||||
*/
|
||||
public static final Integer NUMERIC_FIELD_TYPE = 0;
|
||||
/**
|
||||
* 字符型字段。
|
||||
*/
|
||||
public static final Integer STRING_FIELD_TYPE = 1;
|
||||
/**
|
||||
* 日期型字段。
|
||||
*/
|
||||
public static final Integer DATE_FIELD_TYPE = 2;
|
||||
/**
|
||||
* mapToColumnName和mapToColumnInfo使用的缓存。
|
||||
*/
|
||||
private static Map<String, Tuple2<String, Integer>> cachedColumnInfoMap = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 映射Model对象的字段反射对象,获取与该字段对应的数据库列名称。
|
||||
*
|
||||
* @param field 字段反射对象。
|
||||
* @param modelClazz Model对象的Class类。
|
||||
* @return 该字段所对应的数据表列名称。
|
||||
*/
|
||||
public static String mapToColumnName(Field field, Class<?> modelClazz) {
|
||||
return mapToColumnName(field.getName(), modelClazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* 映射Model对象的字段名称,获取与该字段对应的数据库列名称。
|
||||
*
|
||||
* @param fieldName 字段名称。
|
||||
* @param modelClazz Model对象的Class类。
|
||||
* @return 该字段所对应的数据表列名称。
|
||||
*/
|
||||
public static String mapToColumnName(String fieldName, Class<?> modelClazz) {
|
||||
Tuple2<String, Integer> columnInfo = mapToColumnInfo(fieldName, modelClazz);
|
||||
return columnInfo == null ? null : columnInfo.getFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* 映射Model对象的字段名称,获取与该字段对应的数据库列名称和字段类型。
|
||||
*
|
||||
* @param fieldName 字段名称。
|
||||
* @param modelClazz Model对象的Class类。
|
||||
* @return 该字段所对应的数据表列名称和Java字段类型。
|
||||
*/
|
||||
public static Tuple2<String, Integer> mapToColumnInfo(String fieldName, Class<?> modelClazz) {
|
||||
if (StringUtils.isBlank(fieldName)) {
|
||||
return null;
|
||||
}
|
||||
StringBuilder sb = new StringBuilder(128);
|
||||
sb.append(modelClazz.getName()).append("-#-").append(fieldName);
|
||||
Tuple2<String, Integer> columnInfo = cachedColumnInfoMap.get(sb.toString());
|
||||
if (columnInfo == null) {
|
||||
Field field = ReflectUtil.getField(modelClazz, fieldName);
|
||||
if (field == null) {
|
||||
return null;
|
||||
}
|
||||
Column c = field.getAnnotation(Column.class);
|
||||
String typeName = field.getType().getSimpleName();
|
||||
String columnName = c == null ? fieldName : c.name();
|
||||
// 这里缺省情况下都是按照整型去处理,因为他覆盖太多的类型了。
|
||||
// 如Integer/Long/Double/BigDecimal,可根据实际情况完善和扩充。
|
||||
Integer type = NUMERIC_FIELD_TYPE;
|
||||
if ("String".equals(typeName)) {
|
||||
type = STRING_FIELD_TYPE;
|
||||
} else if ("Date".equals(typeName)) {
|
||||
type = DATE_FIELD_TYPE;
|
||||
}
|
||||
columnInfo = new Tuple2<>(columnName, type);
|
||||
cachedColumnInfoMap.put(sb.toString(), columnInfo);
|
||||
}
|
||||
return columnInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 映射Model主对象的Class名称,到Model所对应的表名称。
|
||||
*
|
||||
* @param modelClazz Model主对象的Class。
|
||||
* @return Model对象对应的数据表名称。
|
||||
*/
|
||||
public static String mapToTableName(Class<?> modelClazz) {
|
||||
Table t = modelClazz.getAnnotation(Table.class);
|
||||
return t == null ? null : t.name();
|
||||
}
|
||||
|
||||
/**
|
||||
* 在当前Service的主Model类型中,根据thisRelationField字段的RelationDict注解参数,将被关联对象thatModel中的数据,
|
||||
* 关联到thisModel对象的thisRelationField字段中。
|
||||
*
|
||||
* @param thisClazz 主对象的Class对象。
|
||||
* @param thisModel 主对象。
|
||||
* @param thatModel 字典关联对象。
|
||||
* @param thisRelationField 关联对象中保存被关联对象的字段名称。
|
||||
* @param <T> 主表对象类型。
|
||||
* @param <R> 从表对象类型。
|
||||
*/
|
||||
public static <T, R> void makeDictRelation(
|
||||
Class<T> thisClazz, T thisModel, R thatModel, String thisRelationField) {
|
||||
if (thatModel == null || thisModel == null) {
|
||||
return;
|
||||
}
|
||||
// 这里不做任何空值判断,从而让配置错误在调试期间即可抛出
|
||||
Field thisTargetField = ReflectUtil.getField(thisClazz, thisRelationField);
|
||||
RelationDict r = thisTargetField.getAnnotation(RelationDict.class);
|
||||
Class<?> thatClass = r.slaveModelClass();
|
||||
Field slaveIdField = ReflectUtil.getField(thatClass, r.slaveIdField());
|
||||
Field slaveNameField = ReflectUtil.getField(thatClass, r.slaveNameField());
|
||||
Map<String, Object> m = new HashMap<>(2);
|
||||
m.put("id", ReflectUtil.getFieldValue(thatModel, slaveIdField));
|
||||
m.put("name", ReflectUtil.getFieldValue(thatModel, slaveNameField));
|
||||
ReflectUtil.setFieldValue(thisModel, thisTargetField, m);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在当前Service的主Model类型中,根据thisRelationField字段的RelationDict注解参数,将被关联对象集合thatModelList中的数据,
|
||||
* 逐个关联到thisModelList每一个元素的thisRelationField字段中。
|
||||
*
|
||||
* @param thisClazz 主对象的Class对象。
|
||||
* @param thisModelList 主对象列表。
|
||||
* @param thatModelList 字典关联对象列表集合。
|
||||
* @param thisRelationField 关联对象中保存被关联对象的字段名称。
|
||||
* @param <T> 主表对象类型。
|
||||
* @param <R> 从表对象类型。
|
||||
*/
|
||||
public static <T, R> void makeDictRelation(
|
||||
Class<T> thisClazz, List<? extends T> thisModelList, List<R> thatModelList, String thisRelationField) {
|
||||
if (CollectionUtils.isEmpty(thatModelList)
|
||||
|| CollectionUtils.isEmpty(thisModelList)) {
|
||||
return;
|
||||
}
|
||||
// 这里不做任何空值判断,从而让配置错误在调试期间即可抛出
|
||||
Field thisTargetField = ReflectUtil.getField(thisClazz, thisRelationField);
|
||||
RelationDict r = thisTargetField.getAnnotation(RelationDict.class);
|
||||
Field masterIdField = ReflectUtil.getField(thisClazz, r.masterIdField());
|
||||
Class<?> thatClass = r.slaveModelClass();
|
||||
Field slaveIdField = ReflectUtil.getField(thatClass, r.slaveIdField());
|
||||
Field slaveNameField = ReflectUtil.getField(thatClass, r.slaveNameField());
|
||||
Map<Object, R> thatMap = new HashMap<>(20);
|
||||
thatModelList.forEach(thatModel -> {
|
||||
Object id = ReflectUtil.getFieldValue(thatModel, slaveIdField);
|
||||
thatMap.put(id, thatModel);
|
||||
});
|
||||
thisModelList.forEach(thisModel -> {
|
||||
if (thisModel != null) {
|
||||
Object id = ReflectUtil.getFieldValue(thisModel, masterIdField);
|
||||
R thatModel = thatMap.get(id);
|
||||
if (thatModel != null) {
|
||||
Map<String, Object> m = new HashMap<>(4);
|
||||
m.put("id", id);
|
||||
m.put("name", ReflectUtil.getFieldValue(thatModel, slaveNameField));
|
||||
ReflectUtil.setFieldValue(thisModel, thisTargetField, m);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 在当前Service的主Model类型中,根据thisRelationField字段的RelationDict注解参数,将被关联对象集合thatModelMap中的数据,
|
||||
* 逐个关联到thisModelList每一个元素的thisRelationField字段中。
|
||||
* 该函数之所以使用Map,主要出于性能优化考虑,在连续使用thatModelMap进行关联时,有效的避免了从多次从List转换到Map的过程。
|
||||
*
|
||||
* @param thisClazz 主对象的Class对象。
|
||||
* @param thisModelList 主对象列表。
|
||||
* @param thatMadelMap 字典关联对象映射集合。
|
||||
* @param thisRelationField 关联对象中保存被关联对象的字段名称。
|
||||
* @param <T> 主表对象类型。
|
||||
* @param <R> 从表对象类型。
|
||||
*/
|
||||
public static <T, R> void makeDictRelation(
|
||||
Class<T> thisClazz, List<? extends T> thisModelList, Map<Object, R> thatMadelMap, String thisRelationField) {
|
||||
if (MapUtils.isEmpty(thatMadelMap)
|
||||
|| CollectionUtils.isEmpty(thisModelList)) {
|
||||
return;
|
||||
}
|
||||
// 这里不做任何空值判断,从而让配置错误在调试期间即可抛出
|
||||
Field thisTargetField = ReflectUtil.getField(thisClazz, thisRelationField);
|
||||
RelationDict r = thisTargetField.getAnnotation(RelationDict.class);
|
||||
Field masterIdField = ReflectUtil.getField(thisClazz, r.masterIdField());
|
||||
Class<?> thatClass = r.slaveModelClass();
|
||||
Field slaveIdField = ReflectUtil.getField(thatClass, r.slaveIdField());
|
||||
Field slaveNameField = ReflectUtil.getField(thatClass, r.slaveNameField());
|
||||
thisModelList.forEach(thisModel -> {
|
||||
if (thisModel != null) {
|
||||
Object id = ReflectUtil.getFieldValue(thisModel, masterIdField);
|
||||
R thatModel = thatMadelMap.get(id);
|
||||
if (thatModel != null) {
|
||||
Map<String, Object> m = new HashMap<>(4);
|
||||
m.put("id", id);
|
||||
m.put("name", ReflectUtil.getFieldValue(thatModel, slaveNameField));
|
||||
ReflectUtil.setFieldValue(thisModel, thisTargetField, m);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 在当前Service的主Model类型中,根据thisRelationField字段的RelationOneToOne注解参数,将被关联对象列表thatModelList中的数据,
|
||||
* 逐个关联到thisModelList每一个元素的thisRelationField字段中。
|
||||
*
|
||||
* @param thisClazz 主对象的Class对象。
|
||||
* @param thisModelList 主对象列表。
|
||||
* @param thatModelList 一对一关联对象列表。
|
||||
* @param thisRelationField 关联对象中保存被关联对象的字段名称。
|
||||
* @param <T> 主表对象类型。
|
||||
* @param <R> 从表对象类型。
|
||||
*/
|
||||
public static <T, R> void makeOneToOneRelation(
|
||||
Class<T> thisClazz, List<? extends T> thisModelList, List<R> thatModelList, String thisRelationField) {
|
||||
if (CollectionUtils.isEmpty(thatModelList)
|
||||
|| CollectionUtils.isEmpty(thisModelList)) {
|
||||
return;
|
||||
}
|
||||
// 这里不做任何空值判断,从而让配置错误在调试期间即可抛出
|
||||
Field thisTargetField = ReflectUtil.getField(thisClazz, thisRelationField);
|
||||
RelationOneToOne r = thisTargetField.getAnnotation(RelationOneToOne.class);
|
||||
Field masterIdField = ReflectUtil.getField(thisClazz, r.masterIdField());
|
||||
Class<?> thatClass = r.slaveModelClass();
|
||||
Field slaveIdField = ReflectUtil.getField(thatClass, r.slaveIdField());
|
||||
Map<Object, R> thatMap = new HashMap<>(20);
|
||||
thatModelList.forEach(thatModel -> {
|
||||
Object id = ReflectUtil.getFieldValue(thatModel, slaveIdField);
|
||||
thatMap.put(id, thatModel);
|
||||
});
|
||||
// 判断放在循环的外部,提升一点儿效率。
|
||||
if (thisTargetField.getType().equals(Map.class)) {
|
||||
thisModelList.forEach(thisModel -> {
|
||||
Object id = ReflectUtil.getFieldValue(thisModel, masterIdField);
|
||||
R thatModel = thatMap.get(id);
|
||||
if (thatModel != null) {
|
||||
ReflectUtil.setFieldValue(thisModel, thisTargetField, BeanUtil.beanToMap(thatModel));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
thisModelList.forEach(thisModel -> {
|
||||
Object id = ReflectUtil.getFieldValue(thisModel, masterIdField);
|
||||
R thatModel = thatMap.get(id);
|
||||
if (thatModel != null) {
|
||||
ReflectUtil.setFieldValue(thisModel, thisTargetField, thatModel);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据主对象和关联对象各自的关联Id函数,将主对象列表和关联对象列表中的数据关联到一起,并将关联对象
|
||||
* 设置到主对象的指定关联字段中。
|
||||
* NOTE: 用于主对象关联字段中,没有包含RelationOneToOne注解的场景。
|
||||
*
|
||||
* @param thisClazz 主对象的Class对象。
|
||||
* @param thisModelList 主对象列表。
|
||||
* @param thisIdGetterFunc 主对象Id的Getter函数。
|
||||
* @param thatModelList 关联对象列表。
|
||||
* @param thatIdGetterFunc 关联对象Id的Getter函数。
|
||||
* @param thisRelationField 主对象中保存被关联对象的字段名称。
|
||||
* @param <T> 主表对象类型。
|
||||
* @param <R> 从表对象类型。
|
||||
*/
|
||||
public static <T, R> void makeOneToOneRelation(
|
||||
Class<T> thisClazz,
|
||||
List<? extends T> thisModelList,
|
||||
Function<T, Object> thisIdGetterFunc,
|
||||
List<R> thatModelList,
|
||||
Function<R, Object> thatIdGetterFunc,
|
||||
String thisRelationField) {
|
||||
makeOneToOneRelation(thisClazz, thisModelList,
|
||||
thisIdGetterFunc, thatModelList, thatIdGetterFunc, thisRelationField, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据主对象和关联对象各自的关联Id函数,将主对象列表和关联对象列表中的数据关联到一起,并将关联对象
|
||||
* 设置到主对象的指定关联字段中。
|
||||
* NOTE: 用于主对象关联字段中,没有包含RelationOneToOne注解的场景。
|
||||
*
|
||||
* @param thisClazz 主对象的Class对象。
|
||||
* @param thisModelList 主对象列表。
|
||||
* @param thisIdGetterFunc 主对象Id的Getter函数。
|
||||
* @param thatModelList 关联对象列表。
|
||||
* @param thatIdGetterFunc 关联对象Id的Getter函数。
|
||||
* @param thisRelationField 主对象中保存被关联对象的字段名称。
|
||||
* @param orderByThatList 如果为true,则按照ThatModelList的顺序输出。同时thisModelList被排序后的新列表替换。
|
||||
* @param <T> 主表对象类型。
|
||||
* @param <R> 从表对象类型。
|
||||
*/
|
||||
public static <T, R> void makeOneToOneRelation(
|
||||
Class<T> thisClazz,
|
||||
List<? extends T> thisModelList,
|
||||
Function<T, Object> thisIdGetterFunc,
|
||||
List<R> thatModelList,
|
||||
Function<R, Object> thatIdGetterFunc,
|
||||
String thisRelationField,
|
||||
boolean orderByThatList) {
|
||||
Field thisTargetField = ReflectUtil.getField(thisClazz, thisRelationField);
|
||||
if (orderByThatList) {
|
||||
List<T> newThisModelList = new LinkedList<>();
|
||||
Map<Object, ? extends T> thisModelMap =
|
||||
thisModelList.stream().collect(Collectors.toMap(thisIdGetterFunc, c -> c));
|
||||
if (thisTargetField.getType().equals(Map.class)) {
|
||||
thatModelList.forEach(thatModel -> {
|
||||
Object thatId = thatIdGetterFunc.apply(thatModel);
|
||||
T thisModel = thisModelMap.get(thatId);
|
||||
if (thisModel != null) {
|
||||
ReflectUtil.setFieldValue(thisModel, thisTargetField, BeanUtil.beanToMap(thatModel));
|
||||
newThisModelList.add(thisModel);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
thatModelList.forEach(thatModel -> {
|
||||
Object thatId = thatIdGetterFunc.apply(thatModel);
|
||||
T thisModel = thisModelMap.get(thatId);
|
||||
if (thisModel != null) {
|
||||
ReflectUtil.setFieldValue(thisModel, thisTargetField, thatModel);
|
||||
newThisModelList.add(thisModel);
|
||||
}
|
||||
});
|
||||
}
|
||||
thisModelList = newThisModelList;
|
||||
} else {
|
||||
Map<Object, R> thatMadelMap =
|
||||
thatModelList.stream().collect(Collectors.toMap(thatIdGetterFunc, c -> c));
|
||||
if (thisTargetField.getType().equals(Map.class)) {
|
||||
thisModelList.forEach(thisModel -> {
|
||||
Object thisId = thisIdGetterFunc.apply(thisModel);
|
||||
R thatModel = thatMadelMap.get(thisId);
|
||||
if (thatModel != null) {
|
||||
ReflectUtil.setFieldValue(thisModel, thisTargetField, BeanUtil.beanToMap(thatModel));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
thisModelList.forEach(thisModel -> {
|
||||
Object thisId = thisIdGetterFunc.apply(thisModel);
|
||||
R thatModel = thatMadelMap.get(thisId);
|
||||
if (thatModel != null) {
|
||||
ReflectUtil.setFieldValue(thisModel, thisTargetField, thatModel);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换过滤对象到与其等效的Example对象。
|
||||
*
|
||||
* @param filterModel 过滤对象。
|
||||
* @param modelClass 过滤对象的Class对象。
|
||||
* @param <T> 过滤对象类型。
|
||||
* @return 转换后的Example对象。
|
||||
*/
|
||||
public static <T> Example convertFilterModelToExample(T filterModel, Class<T> modelClass) {
|
||||
if (filterModel == null) {
|
||||
return null;
|
||||
}
|
||||
Example e = new Example(modelClass);
|
||||
Example.Criteria c = e.createCriteria();
|
||||
Field[] fields = ReflectUtil.getFields(modelClass);
|
||||
for (Field field : fields) {
|
||||
if (field.getAnnotation(Transient.class) != null) {
|
||||
continue;
|
||||
}
|
||||
int modifiers = field.getModifiers();
|
||||
// transient类型的字段不能作为查询条件
|
||||
if ((modifiers & 128) == 0) {
|
||||
field.setAccessible(true);
|
||||
try {
|
||||
Object o = field.get(filterModel);
|
||||
if (o != null) {
|
||||
c.andEqualTo(field.getName(), field.get(filterModel));
|
||||
}
|
||||
} catch (IllegalAccessException ex) {
|
||||
log.error("Failed to call reflection code.", ex);
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
return e;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.orange.admin.common.core.util;
|
||||
|
||||
import cn.jimmyshi.beanquery.BeanQuery;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.github.pagehelper.Page;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 生成带有分页信息的数据列表
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
public class MyPageUtil {
|
||||
|
||||
/**
|
||||
* 用户构建带有分页信息的数据列表。
|
||||
*
|
||||
* @param dataList 数据列表,该参数必须是调用PageHelper.startPage之后,立即执行mybatis查询操作的结果集。
|
||||
* @param includeFields 结果集中需要返回到前端的字段,多个字段之间逗号分隔。
|
||||
* @return 返回只是包含includeFields字段的数据列表,以及结果集TotalCount。
|
||||
*/
|
||||
public static <T> JSONObject makeResponseData(List<T> dataList, String includeFields) {
|
||||
JSONObject pageData = new JSONObject();
|
||||
pageData.put("dataList", BeanQuery.select(includeFields).from(dataList).execute());
|
||||
if (dataList instanceof Page) {
|
||||
pageData.put("totalCount", ((Page<?>)dataList).getTotal());
|
||||
}
|
||||
return pageData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户构建带有分页信息的数据列表。
|
||||
*
|
||||
* @param dataList 数据列表,该参数必须是调用PageHelper.startPage之后,立即执行mybatis查询操作的结果集。
|
||||
* @return 返回结果集和TotalCount。
|
||||
*/
|
||||
public static <T> JSONObject makeResponseData(List<T> dataList) {
|
||||
JSONObject pageData = new JSONObject();
|
||||
pageData.put("dataList", dataList);
|
||||
if (dataList instanceof Page) {
|
||||
pageData.put("totalCount", ((Page<?>)dataList).getTotal());
|
||||
}
|
||||
return pageData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户构建带有分页信息的数据列表。
|
||||
*
|
||||
* @param dataList 数据列表,该参数必须是调用PageHelper.startPage之后,立即执行mybatis查询操作的结果集。
|
||||
* @param totalCount 总数量。
|
||||
* @return 返回结果集和TotalCount。
|
||||
*/
|
||||
public static <T> JSONObject makeResponseData(List<T> dataList, Long totalCount) {
|
||||
JSONObject pageData = new JSONObject();
|
||||
pageData.put("dataList", dataList);
|
||||
if (totalCount != null) {
|
||||
pageData.put("totalCount", totalCount);
|
||||
}
|
||||
return pageData;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.orange.admin.common.core.util;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* 将列表结构组建为树结构的工具类。
|
||||
*
|
||||
* @param <T> 对象类型。
|
||||
* @param <K> 节点之间关联键的类型。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
@Data
|
||||
public class TreeNode<T, K> {
|
||||
|
||||
private K id;
|
||||
private K parentId;
|
||||
private T data;
|
||||
private List<TreeNode<T, K>> childList = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 将列表结构组建为树结构的工具方法。
|
||||
*
|
||||
* @param dataList 数据列表结构。
|
||||
* @param idFunc 获取关联id的函数对象。
|
||||
* @param parentIdFunc 获取关联ParentId的函数对象。
|
||||
* @param root 根节点。
|
||||
* @param <T> 数据对象类型。
|
||||
* @param <K> 节点之间关联键的类型。
|
||||
* @return 源数据对象的树结构存储。
|
||||
*/
|
||||
public static <T, K> List<TreeNode<T, K>> build(
|
||||
List<T> dataList, Function<T, K> idFunc, Function<T, K> parentIdFunc, K root) {
|
||||
List<TreeNode<T, K>> treeNodeList = new ArrayList<>();
|
||||
for (T data : dataList) {
|
||||
if (parentIdFunc.apply(data).equals(idFunc.apply(data))) {
|
||||
continue;
|
||||
}
|
||||
TreeNode<T, K> dataNode = new TreeNode<>();
|
||||
dataNode.setId(idFunc.apply(data));
|
||||
dataNode.setParentId(parentIdFunc.apply(data));
|
||||
dataNode.setData(data);
|
||||
treeNodeList.add(dataNode);
|
||||
}
|
||||
return root == null ? toBuildTreeWithoutRoot(treeNodeList) : toBuildTree(treeNodeList, root);
|
||||
}
|
||||
|
||||
private static <T, K> List<TreeNode<T, K>> toBuildTreeWithoutRoot(List<TreeNode<T, K>> treeNodes) {
|
||||
Map<K, TreeNode<T, K>> treeNodeMap = new HashMap<>(treeNodes.size());
|
||||
for (TreeNode<T, K> treeNode : treeNodes) {
|
||||
treeNodeMap.put(treeNode.id, treeNode);
|
||||
}
|
||||
List<TreeNode<T, K>> treeNodeList = new ArrayList<>();
|
||||
for (TreeNode<T, K> treeNode : treeNodes) {
|
||||
TreeNode<T, K> parentNode = treeNodeMap.get(treeNode.getParentId());
|
||||
if (parentNode == null) {
|
||||
treeNodeList.add(treeNode);
|
||||
} else {
|
||||
parentNode.add(treeNode);
|
||||
}
|
||||
}
|
||||
return treeNodeList;
|
||||
}
|
||||
|
||||
private static <T, K> List<TreeNode<T, K>> toBuildTree(List<TreeNode<T, K>> treeNodes, K root) {
|
||||
List<TreeNode<T, K>> treeNodeList = new ArrayList<>();
|
||||
for (TreeNode<T, K> treeNode : treeNodes) {
|
||||
if (root.equals(treeNode.getParentId())) {
|
||||
treeNodeList.add(treeNode);
|
||||
}
|
||||
for (TreeNode<T, K> it : treeNodes) {
|
||||
if (it.getParentId() == treeNode.getId()) {
|
||||
if (treeNode.getChildList() == null) {
|
||||
treeNode.setChildList(new ArrayList<>());
|
||||
}
|
||||
treeNode.add(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
return treeNodeList;
|
||||
}
|
||||
|
||||
private void add(TreeNode<T, K> node) {
|
||||
childList.add(node);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
package com.orange.admin.common.core.util;
|
||||
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.orange.admin.common.core.constant.ApplicationConstant;
|
||||
import com.orange.admin.common.core.constant.ErrorCodeEnum;
|
||||
import com.orange.admin.common.core.object.ResponseResult;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 上传或下载附件文件的工具类。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
@Slf4j
|
||||
public class UpDownloadUtil {
|
||||
|
||||
/**
|
||||
* 执行下载操作,并将读取的文件数据直接写入到HttpServletResponse应答对象。
|
||||
*
|
||||
* @param rootBaseDir 文件下载的根目录。
|
||||
* @param modelName 所在数据表的实体对象名。
|
||||
* @param fieldName 关联字段的实体对象属性名。
|
||||
* @param fileName 文件名。
|
||||
* @param asImage 是否为图片对象。图片是无需权限验证的,因此和附件存放在不同的子目录。
|
||||
* @param response Http 应答对象。
|
||||
*/
|
||||
public static void doDownload(
|
||||
String rootBaseDir,
|
||||
String modelName,
|
||||
String fieldName,
|
||||
String fileName,
|
||||
Boolean asImage,
|
||||
HttpServletResponse response) {
|
||||
StringBuilder uploadPathBuilder = new StringBuilder(128);
|
||||
uploadPathBuilder.append(rootBaseDir);
|
||||
if (asImage) {
|
||||
uploadPathBuilder.append(ApplicationConstant.UPLOAD_IMAGE_PARENT_PATH);
|
||||
} else {
|
||||
uploadPathBuilder.append(ApplicationConstant.UPLOAD_ATTACHMENT_PARENT_PATH);
|
||||
}
|
||||
uploadPathBuilder.append("/").append(modelName).append("/").append(fieldName).append("/").append(fileName);
|
||||
File file = new File(uploadPathBuilder.toString());
|
||||
if (!file.exists()) {
|
||||
log.warn("Download file [" + uploadPathBuilder.toString() + "] failed, no file found!");
|
||||
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
response.setHeader("content-type", "application/octet-stream");
|
||||
response.setContentType("application/octet-stream");
|
||||
response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
|
||||
byte[] buff = new byte[2048];
|
||||
try (OutputStream os = response.getOutputStream()) {
|
||||
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
|
||||
int i = bis.read(buff);
|
||||
while (i != -1) {
|
||||
os.write(buff, 0, buff.length);
|
||||
os.flush();
|
||||
i = bis.read(buff);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行文件上传操作,并将与该文件下载对应的Url直接写入到HttpServletResponse应答对象,返回给前端。
|
||||
*
|
||||
* @param rootBaseDir 存放上传文件的根目录。
|
||||
* @param modelName 所在数据表的实体对象名。
|
||||
* @param fieldName 关联字段的实体对象属性名。
|
||||
* @param uploadFile Http请求中上传的文件对象。
|
||||
* @param asImage 是否为图片对象。图片是无需权限验证的,因此和附件存放在不同的子目录。
|
||||
* @param response Http 应答对象。
|
||||
* @throws IOException 文件操作错误。
|
||||
*/
|
||||
public static void doUpload(
|
||||
String rootBaseDir,
|
||||
String modelName,
|
||||
String fieldName,
|
||||
Boolean asImage,
|
||||
MultipartFile uploadFile,
|
||||
HttpServletResponse response) throws IOException {
|
||||
PrintWriter out = response.getWriter();
|
||||
response.setContentType("application/json; charset=utf-8");
|
||||
StringBuilder uploadPathBuilder = new StringBuilder(128);
|
||||
uploadPathBuilder.append(rootBaseDir);
|
||||
if (asImage) {
|
||||
uploadPathBuilder.append(ApplicationConstant.UPLOAD_IMAGE_PARENT_PATH);
|
||||
} else {
|
||||
uploadPathBuilder.append(ApplicationConstant.UPLOAD_ATTACHMENT_PARENT_PATH);
|
||||
}
|
||||
uploadPathBuilder.append("/").append(modelName).append("/").append(fieldName).append("/");
|
||||
// 根据请求上传的uri构建下载uri,只是将末尾的/upload改为/download即可。
|
||||
HttpServletRequest request = ContextUtil.getHttpRequest();
|
||||
String uri = request.getRequestURI();
|
||||
uri = StringUtils.removeEnd(uri, "/");
|
||||
uri = StringUtils.removeEnd(uri, "/upload");
|
||||
String downloadUri = uri + "/download";
|
||||
StringBuilder filenameBuilder = new StringBuilder(64);
|
||||
filenameBuilder.append(MyCommonUtil.generateUuid())
|
||||
.append(".").append(FilenameUtils.getExtension(uploadFile.getOriginalFilename()));
|
||||
UploadFileInfo fileInfo = new UploadFileInfo();
|
||||
fileInfo.downloadUri = downloadUri;
|
||||
fileInfo.filename = filenameBuilder.toString();
|
||||
try {
|
||||
byte[] bytes = uploadFile.getBytes();
|
||||
Path path = Paths.get(uploadPathBuilder.toString() + filenameBuilder.toString());
|
||||
//如果没有files文件夹,则创建
|
||||
if (!Files.isWritable(path)) {
|
||||
Files.createDirectories(Paths.get(uploadPathBuilder.toString()));
|
||||
}
|
||||
//文件写入指定路径
|
||||
Files.write(path, bytes);
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to write uploaded file [" + uploadFile.getOriginalFilename() + " ].", e);
|
||||
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
|
||||
out.print(JSONObject.toJSONString(ResponseResult.error(ErrorCodeEnum.INVALID_UPLOAD_FILE_IOERROR)));
|
||||
return;
|
||||
}
|
||||
out.print(JSONObject.toJSONString(ResponseResult.success(fileInfo)));
|
||||
out.flush();
|
||||
out.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断filename参数指定的文件名,是否被包含在fileInfoJson参数中。
|
||||
*
|
||||
* @param fileInfoJson 内部类UploadFileInfo的JSONArray数组。
|
||||
* @param filename 被包含的文件名。
|
||||
* @return 存在返回true,否则false。
|
||||
*/
|
||||
public static boolean containFile(String fileInfoJson, String filename) {
|
||||
if (StringUtils.isAnyBlank(fileInfoJson, filename)) {
|
||||
return false;
|
||||
}
|
||||
List<UploadFileInfo> fileInfoList = JSONArray.parseArray(fileInfoJson, UploadFileInfo.class);
|
||||
if (CollectionUtils.isNotEmpty(fileInfoList)) {
|
||||
for (UploadFileInfo fileInfo : fileInfoList) {
|
||||
if (StringUtils.equals(filename, fileInfo.filename)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Data
|
||||
static class UploadFileInfo {
|
||||
private String downloadUri;
|
||||
private String filename;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.orange.admin.common.core.validator;
|
||||
|
||||
/**
|
||||
* 数据增加的验证分组。通常用于数据新增场景。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
public interface AddGroup {
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.orange.admin.common.core.validator;
|
||||
|
||||
import javax.validation.Constraint;
|
||||
import javax.validation.Payload;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 定义在Model对象中,标注字段值引用自指定的常量字典,和ConstDictRefValidator对象配合完成数据验证。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
@Target({ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Constraint(validatedBy = ConstDictValidator.class)
|
||||
public @interface ConstDictRef {
|
||||
|
||||
/**
|
||||
* 引用的常量字典对象,该对象必须包含isValid的静态方法。
|
||||
*
|
||||
* @return 最大长度。
|
||||
*/
|
||||
Class<?> constDictClass();
|
||||
|
||||
/**
|
||||
* 超过边界后的错误消息提示。
|
||||
*
|
||||
* @return 错误提示。
|
||||
*/
|
||||
String message() default "无效的字典引用值!";
|
||||
|
||||
/**
|
||||
* 验证分组。
|
||||
*
|
||||
* @return 验证分组。
|
||||
*/
|
||||
Class<?>[] groups() default {};
|
||||
|
||||
/**
|
||||
* 载荷对象类型。
|
||||
*
|
||||
* @return 载荷对象。
|
||||
*/
|
||||
Class<? extends Payload>[] payload() default {};
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.orange.admin.common.core.validator;
|
||||
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
|
||||
import javax.validation.ConstraintValidator;
|
||||
import javax.validation.ConstraintValidatorContext;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* 数据字段自定义验证,用于验证Model中字符串字段的最大长度和最小长度。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
public class ConstDictValidator implements ConstraintValidator<ConstDictRef, Object> {
|
||||
|
||||
private ConstDictRef constDictRef;
|
||||
|
||||
@Override
|
||||
public void initialize(ConstDictRef constDictRef) {
|
||||
this.constDictRef = constDictRef;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(Object s, ConstraintValidatorContext constraintValidatorContext) {
|
||||
if (s == null) {
|
||||
return true;
|
||||
}
|
||||
Method method =
|
||||
ReflectUtil.getMethodByName(constDictRef.constDictClass(), "isValid");
|
||||
return ReflectUtil.invokeStatic(method, s);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.orange.admin.common.core.validator;
|
||||
|
||||
import javax.validation.Constraint;
|
||||
import javax.validation.Payload;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 定义在Model或Dto对象中,UTF-8编码的字符串字段长度的上限和下限,和TextLengthValidator对象配合完成数据验证。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
@Target({ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Constraint(validatedBy = TextLengthValidator.class)
|
||||
public @interface TextLength {
|
||||
|
||||
/**
|
||||
* 字符串字段的最小长度。
|
||||
*
|
||||
* @return 最小长度。
|
||||
*/
|
||||
int min() default 0;
|
||||
|
||||
/**
|
||||
* 字符串字段的最大长度。
|
||||
*
|
||||
* @return 最大长度。
|
||||
*/
|
||||
int max() default Integer.MAX_VALUE;
|
||||
|
||||
/**
|
||||
* 超过边界后的错误消息提示。
|
||||
*
|
||||
* @return 错误提示。
|
||||
*/
|
||||
String message() default "字段长度超过最大字节数!";
|
||||
|
||||
/**
|
||||
* 验证分组。
|
||||
*
|
||||
* @return 验证分组。
|
||||
*/
|
||||
Class<?>[] groups() default { };
|
||||
|
||||
/**
|
||||
* 载荷对象类型。
|
||||
*
|
||||
* @return 载荷对象。
|
||||
*/
|
||||
Class<? extends Payload>[] payload() default { };
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.orange.admin.common.core.validator;
|
||||
|
||||
import org.apache.commons.lang3.CharUtils;
|
||||
|
||||
import javax.validation.ConstraintValidator;
|
||||
import javax.validation.ConstraintValidatorContext;
|
||||
|
||||
/**
|
||||
* 数据字段自定义验证,用于验证Model中UTF-8编码的字符串字段的最大长度和最小长度。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
public class TextLengthValidator implements ConstraintValidator<TextLength, String> {
|
||||
|
||||
private TextLength textLength;
|
||||
|
||||
@Override
|
||||
public void initialize(TextLength textLength) {
|
||||
this.textLength = textLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
|
||||
if (s == null) {
|
||||
return true;
|
||||
}
|
||||
int length = 0;
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char c = s.charAt(i);
|
||||
if (CharUtils.isAscii(c)) {
|
||||
++length;
|
||||
} else {
|
||||
length += 2;
|
||||
}
|
||||
}
|
||||
return length >= textLength.min() && length <= textLength.max();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.orange.admin.common.core.validator;
|
||||
|
||||
/**
|
||||
* 数据修改的验证分组。通常用于数据更新的场景。
|
||||
*
|
||||
* @author Stephen.Liu
|
||||
* @date 2020-04-11
|
||||
*/
|
||||
public interface UpdateGroup {
|
||||
|
||||
}
|
||||
19
orange-admin-service/common/pom.xml
Normal file
19
orange-admin-service/common/pom.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.orange.admin</groupId>
|
||||
<artifactId>OrangeAdmin</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>common</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
<module>common-core</module>
|
||||
<module>common-biz</module>
|
||||
</modules>
|
||||
</project>
|
||||
Reference in New Issue
Block a user