init
This commit is contained in:
@@ -0,0 +1,316 @@
|
||||
package com.bc.exam.aspect;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.bc.exam.core.annon.Dict;
|
||||
import com.bc.exam.core.api.ApiRest;
|
||||
import com.bc.exam.core.utils.Reflections;
|
||||
import com.bc.exam.modules.system.service.SysDictService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 数据字典AOP类,处理数据字典值
|
||||
*
|
||||
* @author chenhaodong
|
||||
*/
|
||||
@Aspect
|
||||
//@Component
|
||||
@Slf4j
|
||||
public class DictAspect {
|
||||
|
||||
@Autowired
|
||||
private SysDictService sysDictService;
|
||||
|
||||
/**
|
||||
* 切入Controller执行
|
||||
* @param pjp
|
||||
* @return
|
||||
* @throws Throwable
|
||||
*/
|
||||
@Around("execution(public * com.bc.exam..*.*Controller.*(..))")
|
||||
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
|
||||
return this.translate(pjp);
|
||||
}
|
||||
|
||||
/**
|
||||
* 进行翻译并返回,调用前必须实现:BaseDictService
|
||||
*
|
||||
* @param pjp
|
||||
* @return
|
||||
* @throws Throwable
|
||||
*/
|
||||
public Object translate(ProceedingJoinPoint pjp) throws Throwable {
|
||||
// 处理字典
|
||||
return this.parseAllDictText(pjp.proceed());
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换全部数据字典
|
||||
*
|
||||
* @param result
|
||||
*/
|
||||
private Object parseAllDictText(Object result) {
|
||||
|
||||
// 非ApiRest类型不处理
|
||||
if (result instanceof ApiRest) {
|
||||
parseFullDictText(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 转换所有类型的数据字典、包含子列表
|
||||
*
|
||||
* @param result
|
||||
*/
|
||||
private void parseFullDictText(Object result) {
|
||||
|
||||
try {
|
||||
|
||||
Object rest = ((ApiRest) result).getData();
|
||||
|
||||
// 不处理普通数据类型
|
||||
if (rest == null || this.isBaseType(rest.getClass())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 分页的
|
||||
if (rest instanceof IPage) {
|
||||
List<Object> items = new ArrayList<>(16);
|
||||
for (Object record : ((IPage) rest).getRecords()) {
|
||||
Object item = this.parseObject(record);
|
||||
items.add(item);
|
||||
}
|
||||
((IPage) rest).setRecords(items);
|
||||
return;
|
||||
}
|
||||
|
||||
// 数据列表的
|
||||
if (rest instanceof List) {
|
||||
List<Object> items = new ArrayList<>();
|
||||
for (Object record : ((List) rest)) {
|
||||
Object item = this.parseObject(record);
|
||||
items.add(item);
|
||||
}
|
||||
// 重新回写值
|
||||
((ApiRest) result).setData(items);
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理单对象
|
||||
Object item = this.parseObject(((ApiRest) result).getData());
|
||||
((ApiRest) result).setData(item);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理数据字典值
|
||||
*
|
||||
* @param record
|
||||
* @return
|
||||
*/
|
||||
public Object parseObject(Object record) {
|
||||
|
||||
if (record == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 不处理普通数据类型
|
||||
if (this.isBaseType(record.getClass())) {
|
||||
return record;
|
||||
}
|
||||
|
||||
// 转换JSON字符
|
||||
String json = JSON.toJSONString(record);
|
||||
JSONObject item = JSONObject.parseObject(json);
|
||||
|
||||
for (Field field : Reflections.getAllFields(record)) {
|
||||
|
||||
// 如果是List类型
|
||||
if (List.class.isAssignableFrom(field.getType())) {
|
||||
try {
|
||||
List list = this.processList(field, item.getObject(field.getName(), List.class));
|
||||
item.put(field.getName(), list);
|
||||
continue;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// 处理普通字段
|
||||
if (field.getAnnotation(Dict.class) != null) {
|
||||
String code = field.getAnnotation(Dict.class).dicCode();
|
||||
String text = field.getAnnotation(Dict.class).dicText();
|
||||
String table = field.getAnnotation(Dict.class).dictTable();
|
||||
String key = String.valueOf(item.get(field.getName()));
|
||||
|
||||
//翻译字典值对应的txt
|
||||
String textValue = this.translateDictValue(code, text, table, key);
|
||||
if (StringUtils.isEmpty(textValue)) {
|
||||
textValue = "";
|
||||
}
|
||||
item.put(field.getName() + "_dictText", textValue);
|
||||
continue;
|
||||
}
|
||||
|
||||
//日期格式转换
|
||||
if (field.getType().getName().equals("java.util.Date") && item.get(field.getName()) != null) {
|
||||
|
||||
// 获取注解
|
||||
JsonFormat ann = field.getAnnotation(JsonFormat.class);
|
||||
// 格式化方式
|
||||
SimpleDateFormat fmt;
|
||||
|
||||
// 使用注解指定的
|
||||
if (ann != null && !StringUtils.isEmpty(ann.pattern())) {
|
||||
fmt = new SimpleDateFormat(ann.pattern());
|
||||
} else {
|
||||
// 默认时间样式
|
||||
fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
}
|
||||
item.put(field.getName(), fmt.format(new Date((Long) item.get(field.getName()))));
|
||||
continue;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得类型为List的值
|
||||
*
|
||||
* @param field
|
||||
* @return
|
||||
*/
|
||||
private List<Object> processList(Field field, List list) {
|
||||
|
||||
// 空判断
|
||||
if (list == null || list.size() == 0) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
// 获得List属性的真实类
|
||||
Type genericType = field.getGenericType();
|
||||
Class<?> actualType = null;
|
||||
if (genericType instanceof ParameterizedType) {
|
||||
// 尝试获取数据类型
|
||||
ParameterizedType pt = (ParameterizedType) genericType;
|
||||
try {
|
||||
actualType = (Class) pt.getActualTypeArguments()[0];
|
||||
}catch (Exception e){
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
// 常规列表无需处理
|
||||
if (isBaseType(actualType)) {
|
||||
return list;
|
||||
}
|
||||
|
||||
// 返回列表
|
||||
List<Object> result = new ArrayList<>(16);
|
||||
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
// 创建实例-->赋值-->字典处理
|
||||
Object data = list.get(i);
|
||||
try {
|
||||
data = JSON.parseObject(JSON.toJSONString(data), actualType);
|
||||
}catch (Exception e){
|
||||
// 转换出错不处理
|
||||
}
|
||||
|
||||
// 处理后的数据
|
||||
Object pds = this.parseObject(data);
|
||||
result.add(pds);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 翻译实现
|
||||
*
|
||||
* @param code
|
||||
* @param text
|
||||
* @param table
|
||||
* @param key
|
||||
* @return
|
||||
*/
|
||||
private String translateDictValue(String code, String text, String table, String key) {
|
||||
if (StringUtils.isEmpty(key)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
// 翻译值
|
||||
String dictText = null;
|
||||
if (!StringUtils.isEmpty(table)) {
|
||||
dictText = sysDictService.findDict(table, text, code, key.trim());
|
||||
}
|
||||
|
||||
if (!StringUtils.isEmpty(dictText)) {
|
||||
return dictText;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否基本类型
|
||||
*
|
||||
* @param clazz
|
||||
* @return
|
||||
*/
|
||||
private boolean isBaseType(Class clazz) {
|
||||
|
||||
|
||||
// 基础数据类型
|
||||
if (clazz.equals(java.lang.Integer.class) ||
|
||||
clazz.equals(java.lang.Byte.class) ||
|
||||
clazz.equals(java.lang.Long.class) ||
|
||||
clazz.equals(java.lang.Double.class) ||
|
||||
clazz.equals(java.lang.Float.class) ||
|
||||
clazz.equals(java.lang.Character.class) ||
|
||||
clazz.equals(java.lang.Short.class) ||
|
||||
clazz.equals(java.lang.Boolean.class)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// String类型
|
||||
if (clazz.equals(java.lang.String.class)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 数字
|
||||
if (clazz.equals(java.lang.Number.class)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package com.bc.exam.aspect.mybatis;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
|
||||
import com.bc.exam.modules.user.dto.response.SysUserLoginDTO;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import net.sf.jsqlparser.parser.CCJSqlParserManager;
|
||||
import net.sf.jsqlparser.statement.select.PlainSelect;
|
||||
import net.sf.jsqlparser.statement.select.Select;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.ibatis.executor.statement.StatementHandler;
|
||||
import org.apache.ibatis.mapping.MappedStatement;
|
||||
import org.apache.ibatis.mapping.SqlCommandType;
|
||||
import org.apache.ibatis.plugin.Interceptor;
|
||||
import org.apache.ibatis.plugin.Intercepts;
|
||||
import org.apache.ibatis.plugin.Invocation;
|
||||
import org.apache.ibatis.plugin.Plugin;
|
||||
import org.apache.ibatis.plugin.Signature;
|
||||
import org.apache.ibatis.reflection.DefaultReflectorFactory;
|
||||
import org.apache.ibatis.reflection.MetaObject;
|
||||
import org.apache.ibatis.reflection.SystemMetaObject;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.sql.Connection;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* 查询拦截器,用于拦截处理通用的信息、如用户ID、多租户信息等;
|
||||
* 特别注意:此处继承了PaginationInterceptor分页,分页必须在拦截数据后执行,否则容易出现分页不准确,分页计数大于实际数量等问题
|
||||
* @author chenhaodong
|
||||
*/
|
||||
@Log4j2
|
||||
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}),})
|
||||
public class QueryInterceptor extends PaginationInterceptor implements Interceptor {
|
||||
|
||||
/**
|
||||
* 客户ID
|
||||
*/
|
||||
private static final String USER_FILTER = "{{userId}}";
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Object intercept(Invocation invocation) throws Throwable {
|
||||
|
||||
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
|
||||
|
||||
//通过MetaObject优雅访问对象的属性,这里是访问statementHandler的属性
|
||||
MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());
|
||||
//先拦截到RoutingStatementHandler,里面有个StatementHandler类型的delegate变量,其实现类是BaseStatementHandler,然后就到BaseStatementHandler的成员变量mappedStatement
|
||||
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
|
||||
|
||||
//sql语句类型
|
||||
SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
|
||||
|
||||
// 只过滤查询的
|
||||
if (SqlCommandType.SELECT == sqlCommandType) {
|
||||
// 获得原始SQL
|
||||
String sql = statementHandler.getBoundSql().getSql();
|
||||
|
||||
// 不处理
|
||||
if(!sql.contains(USER_FILTER)){
|
||||
return super.intercept(invocation);
|
||||
}
|
||||
// 处理SQL语句
|
||||
String outSql = this.parseSql(sql);
|
||||
// 设置SQL
|
||||
metaObject.setValue("delegate.boundSql.sql", outSql);
|
||||
// 再分页
|
||||
return super.intercept(invocation);
|
||||
}
|
||||
|
||||
return invocation.proceed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object plugin(Object target) {
|
||||
return Plugin.wrap(target, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProperties(Properties properties) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取当前登录用户
|
||||
* @return
|
||||
*/
|
||||
private SysUserLoginDTO getLoginUser() {
|
||||
|
||||
try {
|
||||
return SecurityUtils.getSubject().getPrincipal() != null ? (SysUserLoginDTO) SecurityUtils.getSubject().getPrincipal() : null;
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 替换用户ID
|
||||
* @param sql
|
||||
* @return
|
||||
*/
|
||||
private String processUserId(String sql) {
|
||||
|
||||
// 当前用户
|
||||
SysUserLoginDTO user = this.getLoginUser();
|
||||
String userId = user.getId();
|
||||
if(StringUtils.isNotBlank(userId)){
|
||||
return sql.replace(USER_FILTER, userId);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理注入用户信息
|
||||
* @param src
|
||||
* @return
|
||||
*/
|
||||
private String parseSql(String src) {
|
||||
|
||||
CCJSqlParserManager parserManager = new CCJSqlParserManager();
|
||||
try {
|
||||
|
||||
Select select = (Select) parserManager.parse(new StringReader(src));
|
||||
PlainSelect selectBody = (PlainSelect) select.getSelectBody();
|
||||
|
||||
// 过滤客户
|
||||
String sql = selectBody.toString();
|
||||
|
||||
// 过滤用户ID
|
||||
sql = this.processUserId(sql);
|
||||
|
||||
// 获得SQL
|
||||
return sql;
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return src;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.bc.exam.aspect.mybatis;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.handlers.AbstractSqlParserHandler;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.ibatis.executor.Executor;
|
||||
import org.apache.ibatis.mapping.MappedStatement;
|
||||
import org.apache.ibatis.mapping.SqlCommandType;
|
||||
import org.apache.ibatis.plugin.Interceptor;
|
||||
import org.apache.ibatis.plugin.Intercepts;
|
||||
import org.apache.ibatis.plugin.Invocation;
|
||||
import org.apache.ibatis.plugin.Plugin;
|
||||
import org.apache.ibatis.plugin.Signature;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.Objects;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* 自动给创建时间个更新时间加值
|
||||
* @author chenhaodong
|
||||
*/
|
||||
@Intercepts(value = {@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
|
||||
public class UpdateInterceptor extends AbstractSqlParserHandler implements Interceptor {
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private static final String CREATE_TIME = "createTime";
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private static final String UPDATE_TIME = "updateTime";
|
||||
|
||||
@Override
|
||||
public Object intercept(Invocation invocation) throws Throwable {
|
||||
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
|
||||
// SQL操作命令
|
||||
SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
|
||||
// 获取新增或修改的对象参数
|
||||
Object parameter = invocation.getArgs()[1];
|
||||
// 获取对象中所有的私有成员变量(对应表字段)
|
||||
Field[] declaredFields = parameter.getClass().getDeclaredFields();
|
||||
if (parameter.getClass().getSuperclass() != null) {
|
||||
Field[] superField = parameter.getClass().getSuperclass().getDeclaredFields();
|
||||
declaredFields = ArrayUtils.addAll(declaredFields, superField);
|
||||
}
|
||||
|
||||
String fieldName = null;
|
||||
for (Field field : declaredFields) {
|
||||
fieldName = field.getName();
|
||||
if (Objects.equals(CREATE_TIME, fieldName)) {
|
||||
if (SqlCommandType.INSERT.equals(sqlCommandType)) {
|
||||
field.setAccessible(true);
|
||||
field.set(parameter, new Timestamp(System.currentTimeMillis()));
|
||||
}
|
||||
}
|
||||
if (Objects.equals(UPDATE_TIME, fieldName)) {
|
||||
if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
|
||||
field.setAccessible(true);
|
||||
field.set(parameter, new Timestamp(System.currentTimeMillis()));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return invocation.proceed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object plugin(Object target) {
|
||||
if (target instanceof Executor) {
|
||||
return Plugin.wrap(target, this);
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProperties(Properties properties) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package com.bc.exam.aspect.utils;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.bc.exam.core.api.ApiError;
|
||||
import com.bc.exam.core.api.ApiRest;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* 注入工具类
|
||||
* @author chenhaodong
|
||||
* @date 2019-07-17 09:32
|
||||
*/
|
||||
@Log4j2
|
||||
@Component
|
||||
public class InjectUtils {
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 给对象字段赋值
|
||||
*
|
||||
* @param object 赋值的对象
|
||||
* @param value 值
|
||||
* @param fields 字段
|
||||
* @throws Exception 异常
|
||||
*/
|
||||
public void setValue(Object object, Object value, String... fields) throws Exception {
|
||||
|
||||
//设置同类的属性
|
||||
for (String fieldName : fields) {
|
||||
|
||||
//获取当前
|
||||
Field field = this.getFiled(object.getClass(), fieldName);
|
||||
if(field == null){
|
||||
continue;
|
||||
}
|
||||
|
||||
field.setAccessible(true);
|
||||
field.set(object, value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段名对应的字段
|
||||
*
|
||||
* @param clazz 目标类
|
||||
* @param fieldName 字段名
|
||||
*/
|
||||
private Field getFiled(Class clazz, String fieldName) {
|
||||
|
||||
System.out.println("注入的类:"+clazz.toString());
|
||||
|
||||
//是否具有包含关系
|
||||
try {
|
||||
//获取当前类的属性
|
||||
return clazz.getDeclaredField(fieldName);
|
||||
}catch (Exception e){
|
||||
|
||||
log.error(clazz.toString() + ": not exist field, try superclass " + fieldName);
|
||||
|
||||
//如果为空且存在父类,则往上找
|
||||
if(clazz.getSuperclass()!=null){
|
||||
return this.getFiled(clazz.getSuperclass(), fieldName);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 打印结果返回
|
||||
* @param response
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void restError(HttpServletResponse response) {
|
||||
|
||||
try {
|
||||
|
||||
//固定错误
|
||||
ApiRest apiRest = new ApiRest(ApiError.ERROR_10010002);
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
response.setContentType("application/json");
|
||||
response.getWriter().write(JSON.toJSONString(apiRest));
|
||||
response.getWriter().close();
|
||||
|
||||
}catch (IOException e){
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user