实现过程:
第一步:自定义拦截器
第二步:sql处理的工具类,请求头中获取当前用户信息处理类,枚举类,以供在自定义拦截器中使用
第三步:设定开关,在yml配置文件中设定开关值,不设定,默认为不开启。
第三步: 使用,在数据源配置中,注册自定义的拦截器插件。
自定义拦截器,实现 Interceptor 接口以及相关注解,增加一个JdbcTemplate 属性,可传参的构造方法,参数为JdbcTemplate ,为的是在多数据源的情况下,可以明确指定操作数据源。获取相关的数据库信息。
代码实现:
@Component
@ConditionalOnBean({DabaiDataSourceConfig.class})
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class,
Object.class, RowBounds.class, ResultHandler.class})})
@AutoConfigureAfter({PageHelperAutoConfiguration.class})
@Service
@Data
public class MybatisInterceptor implements Interceptor {
private JdbcTemplate jdbcTemplate ;
public MybatisInterceptor(){
}
//该构造器为了在创建数据源时,注册插件,传入指定的数据源,以便对应的数据库操作。
public MybatisInterceptor(JdbcTemplate jdbcTemplate){
this.jdbcTemplate =jdbcTemplate;
}
private static final Logger log = LoggerFactory.getLogger(MybatisInterceptor.class);
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object returnValue = null;
// 执行结果
returnValue = invocation.proceed();
try {
// 获取拦截方法的参数
Object[] args = invocation.getArgs();
MappedStatement mappedStatement = (MappedStatement) args[0];
Configuration configuration = mappedStatement.getConfiguration();
Object parameter = null;
if (invocation.getArgs().length > 1) {
parameter = invocation.getArgs()[1];
}
BoundSql boundSql = mappedStatement.getBoundSql(parameter);
/**
*以下可以写相关的sql操作
**/
。。。
} catch (Exception e) {
log.info("拦截器异常", e.getMessage());
}
return returnValue;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
sql处理的工具类
@Slf4j
@Service
public class SqlTools {
private SqlTools() {
}
/**
* 解析BoundSql,生成不含占位符的SQL语句
* @DHL
* @param configuration
* @param boundSql
* @return java.lang.String
*/
public static String showSql(Configuration configuration, BoundSql boundSql) {
Object parameterObject = boundSql.getParameterObject();
// 获取方法参数
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
// 格式化SQL
String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
if (parameterMappings.size() > 0 && parameterObject != null) {
// 类型解析器
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
sql = sql.replaceFirst("\\?", getParameterValue(parameterObject));
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
for (ParameterMapping parameterMapping : parameterMappings) {
String propertyName = parameterMapping.getProperty();
String[] s = metaObject.getObjectWrapper().getGetterNames();
Arrays.toString(s);
if (metaObject.hasGetter(propertyName)) {
Object obj = metaObject.getValue(propertyName);
sql = sql.replaceFirst("\\?", getParameterValue(obj));
} else if (boundSql.hasAdditionalParameter(propertyName)) {
Object obj = boundSql.getAdditionalParameter(propertyName);
sql = sql.replaceFirst("\\?", getParameterValue(obj));
}
}
}
}
return sql;
}
/**
* 获取表名的注释
* @param map
* @param jdbcTemplate
* @return
*/
public static String getTableNameComment(Map map,JdbcTemplate jdbcTemplate){
StringBuffer sb = new StringBuffer();
sb.append(" SELECT TABLE_COMMENT FROM information_schema.TABLES WHERE ");
sb.append(" table_schema = '"+map.get("databaseName").toString()+"'");
sb.append(" AND TABLE_NAME ='"+map.get("tableName").toString()+"'");
String sql = sb.toString();
Map<String, Object> resultMap = jdbcTemplate.queryForMap(sql);
if(Objects.nonNull(resultMap)&&!resultMap.isEmpty()){
String tableNameComment = resultMap.get("TABLE_COMMENT").toString();
return tableNameComment;
}
return "";
}
/**
* 若为字符串或者日期类型,则在参数两边添加''
*
* @param obj
* @return java.lang.String
*/
private static String getParameterValue(Object obj) {
String value;
if (obj instanceof String) {
value = "'" + obj.toString() + "'";
} else if (obj instanceof Date) {
DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
value = "'" + formatter.format(new Date()) + "'";
} else {
if (obj != null) {
value = obj.toString();
} else {
value = "";
}
}
return value;
}
/**
* @return
* @desc 操作前获取数据(修改或者删除的时候需要查询操作前的数据)
*/
public static List getDataBeforeOperate(MappedStatement mappedStatement, BoundSql boundSql, Object parameter, JdbcTemplate jdbcTemplate) throws SQLException {
List resultList = new ArrayList();
//SELECT和INSERT操作不用记录修改前的值,直接返回
if (parameter == null || SqlCommandType.SELECT.equals(mappedStatement.getSqlCommandType())
|| SqlCommandType.INSERT.equals(mappedStatement.getSqlCommandType())) {
return resultList;
}
String operateType = SqlTools.SqlCommandType(mappedStatement);
boolean batchFlag = SqlTools.getbatchFlag(parameter);
String tableName = SqlTools.getTableName(boundSql, operateType);
// 如果动态数据源不存在,则尝试从数据源重载
// 通过数据源名称获取数据源
String primarykeyName = SqlTools.getFieldName(boundSql, operateType, batchFlag);
List<Map> primarykeyList = SqlTools.reflectGetListValue(primarykeyName, parameter, batchFlag);
//遍历主键列表
for (Map map : primarykeyList) {
for (Object obj : map.keySet()) {
//获取主键值
Object primarykeyValue = map.get(obj);
queryData(jdbcTemplate, tableName, primarykeyName, primarykeyValue, resultList);
}
}
return resultList;
}
/**
* 判断sql操作类型 :update和delete的时候才需要去查询修改前的值
*
* @param mappedStatement
* @return java.lang.String
*/
public static String SqlCommandType(MappedStatement mappedStatement) {
if (SqlCommandType.SELECT.equals(mappedStatement.getSqlCommandType())) {
return OperateTypeCode.SELECT.getCode();
}
if (SqlCommandType.INSERT.equals(mappedStatement.getSqlCommandType())) {
return OperateTypeCode.INSERT.getCode();
}
if (SqlCommandType.UPDATE.equals(mappedStatement.getSqlCommandType())) {
return OperateTypeCode.UPDATE.getCode();
}
if (SqlCommandType.DELETE.equals(mappedStatement.getSqlCommandType())) {
return OperateTypeCode.DELETE.getCode();
}
return null;
}
/**
* 获取批次标记
*
* @param parameter
* @return boolen
*/
private static boolean getbatchFlag(Object parameter) {
if (null != parameter && parameter.toString().contains("param")) {
return true;
}
return false;
}
/**
* 获取表名
* 截取规则 :
* 1.替换sql命令类型标签(select delete insert update)
* 2.替换掉from标签
* 3.截取从0开始,\n结束
*/
public static String getTableName(BoundSql boundSql, String sqlType) {
if (StringUtils.isBlank(sqlType) || StringUtils.isBlank(boundSql.getSql())) {
return "";
}
//根据sqlType 去除update标签,delete标签,from标签,去除前后空格
String tableStr = boundSql.getSql().toLowerCase();
String tableName =null;
if(OperateTypeCode.INSERT.getCode().equals(sqlType)){
tableStr = tableStr.substring(tableStr.indexOf("into") + 5, tableStr.indexOf("(")).trim();
}else if(OperateTypeCode.DELETE.getCode().equals(sqlType)){
tableStr = tableStr.substring(tableStr.indexOf("from") + 5, tableStr.indexOf(" where")).trim();
}else if(OperateTypeCode.UPDATE.getCode().equals(sqlType)){
tableStr = tableStr.substring(tableStr.indexOf("update") + 7, tableStr.indexOf(" set")).trim();
}
int blank = tableStr.indexOf(" ");
if(blank!=-1){
tableStr.substring(0, blank);
}
tableName=tableStr;
tableStr = boundSql.getSql().toLowerCase();
if(StringUtil.isNotEmpty(tableName)){
int length =tableName.length()+tableStr.indexOf(tableName);
tableName = boundSql.getSql().substring(tableStr.indexOf(tableName),length);
}
return tableName;
}
/**
* 获取sql获取FieldName
* 截取规则 :
* (1)去掉\n ,?,=
* (2)从where索引位置开始截取, 到sqlStr最后一个位置 ,替换掉where操作符,去除空格
* (3) batchFlag=true代表批量,需要从新解析(截取fieldName,从beginIndex=0开始截取,endIndex=;结束
*/
private static String getFieldName(BoundSql boundSql, String sqlType, boolean batchFlag) {
if (StringUtils.isBlank(sqlType) || StringUtils.isBlank(boundSql.getSql())) {
return "";
}
String sqlStr = boundSql.getSql().replaceAll("\n", "").replaceAll("\\?", "").replaceAll("=", "");
String fieldName = sqlStr.substring(sqlStr.indexOf("where"), sqlStr.length()).replace("where", "").trim();
//解析批量
if (batchFlag) {
fieldName = fieldName.substring(0, fieldName.indexOf(";")).trim();
}
return fieldName;
}
/**
* 根据属性名获取属性值
*/
private static Object getFieldValueByName(String fieldName, Object o) {
if (StringUtils.isBlank(fieldName) || o == null) {
return null;
}
try {
String firstLetter = fieldName.substring(0, 1).toUpperCase();
String getter = "get" + firstLetter + fieldName.substring(1);
Method method = o.getClass().getMethod(getter, new Class[]{});
Object value = method.invoke(o, new Object[]{});
return value;
} catch (Exception e) {
log.error(e.getMessage(), e);
return null;
}
}
/**
* 根据属性名获取属性值
*
* @param fieldName
* @param object
* @return
*/
private static Object getFieldValueByFieldName(String fieldName, Object object) {
try {
Field field = object.getClass().getDeclaredField(fieldName);
//设置对象的访问权限,保证对private的属性的访问
field.setAccessible(true);
return field.get(object);
} catch (Exception e) {
log.error(e.getMessage(), e);
return null;
}
}
/**
* 反射获取 @Param注解标记list属性值
*
* @param primarykeyName 主键名称
* @param object 对象
* @param batchFlag batchFlag=true批量 ,batchFlag=false代表不是批量操作
* @return
*/
private static List<Map> reflectGetListValue(String primarykeyName, Object object, boolean batchFlag) {
List<Map> resultList = new ArrayList<>();
if (null == object) {
return resultList;
}
//单个参数解析
Map argsValueMap = new HashMap();
if (!batchFlag) {
Map argsMaps = beanToMap(object);
for (Object obj : argsMaps.keySet()) {
if (obj.equals(primarykeyName)) {
argsValueMap.put("obj", argsMaps.get(obj));
}
}
resultList.add(argsValueMap);
return resultList;
}
//批量解析
Map<String, Object> parameterMap = (Map) object;
for (Map.Entry<String, Object> entery : parameterMap.entrySet()) {
String key = entery.getKey();
if (key.indexOf("param") < 0) {
List<Object> objList = (List) entery.getValue();
if (objList != null) {
for (Object obj : objList) {
Map<String, Object> listChild = new HashMap<>();
Class<?> clz = obj.getClass();
Field[] declaredFields = clz.getDeclaredFields();
for (int i = 0; i < declaredFields.length; i++) {
Field field = declaredFields[i];
field.setAccessible(true);
try {
//Object value = field.get(obj);
if (StringUtils.equals(primarykeyName, field.getName())) {
Object value = field.get(obj);
listChild.put(field.getName(), value);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
resultList.add(listChild);
}
}
}
}
return resultList;
}
/**
* bean转map
*
* @param bean
* @return map
*/
private static <T> Map<String, Object> beanToMap(T bean) {
Map<String, Object> map = new HashMap<>();
if (bean != null) {
BeanMap beanMap = BeanMap.create(bean);
for (Object key : beanMap.keySet()) {
map.put(String.valueOf(key), beanMap.get(key));
}
}
return map;
}
/**
* @param jdbcTemplate 操作类型
* @param tableName 表名
* @param primarykeyName 主键名称
* @param primarykeyValue 主键值
* @param resultList 查询结果集
* @return
* @desc 操作前获取数据(修改或者删除的时候需要查询操作前的数据)
*/
private static void queryData(JdbcTemplate jdbcTemplate, String tableName, String primarykeyName, Object primarykeyValue, List resultList) {
if (StringUtils.isNotEmpty(tableName) && StringUtils.isNotEmpty(primarykeyName) && primarykeyValue != null) {
StringBuffer sb = new StringBuffer();
sb.append(" select * from ");
sb.append(tableName);
sb.append(" where ");
sb.append(primarykeyName);
sb.append(" = ? ");
String sql = sb.toString();
List<Map<String, Object>> resultMaps = jdbcTemplate.queryForList(sql, primarykeyValue);
resultList.add(resultMaps);
}
}
}
枚举静态变量类
public enum OperateTypeCode {
SELECT("select","查询"),
INSERT("insert","新增"),
UPDATE("update","修改"),
DELETE("delete","删除"),
FLUSH("flush","flush");
private static Map<String, OperateTypeCode> lookup = new HashMap<>();
static {
for (OperateTypeCode s : EnumSet.allOf(OperateTypeCode.class)) {
lookup.put(s.code, s);
}
}
@Getter
private final String code;
@Getter
private final String value;
OperateTypeCode(String code, String value) {
this.code = code;
this.value = value;
}
public static Optional<OperateTypeCode> getFromCode(String code) {
return Optional.ofNullable(lookup.getOrDefault(code, null));
}
/**
* 根据编码获取描述信息
*
* @param code 枚举字符串
* @return true or false
*/
public static String getDesc(String code) {
for (OperateTypeCode flag : OperateTypeCode.values()) {
if (code.contains(flag.code)) {
return flag.value;
}
}
return null;
}
}
请求信息处理类 :
阿咚这边的项目是按下边方式处理,通过网关去内网调用,登陆人信息放到header 中,同学可以根据自己的项目情况去调整或则自己定制。
@Slf4j
@Component
public class HttpRequestUtil {
private HttpRequestUtil(){}
/**
* 获取head中用户信息
* @return
*/
private static String getUserinfoStr(){
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if(servletRequestAttributes == null ){
throw new BusinessException("error.00010", HttpRequestUtil.class);
}
HttpServletRequest request = servletRequestAttributes.getRequest();
//获取用户信息
String userInfoStr = request.getHeader("userInfo");//用户信息节点 可以根据自己的项目信息实际情况去替换
//判断为空
if(StringUtils.isEmpty(userInfoStr) || "undefined".equals(userInfoStr)){
return "";
}
log.info("获取resultObject信息解密前:"+JSON.toJSONString(userInfoStr));
//对用户信息进行编码
String userinfoStr = new String(new Base64().decode(userInfoStr));
return userinfoStr;
}
/**
* 获取用户信息
* @return
*/
public static UserInfo getUserInfoNotThrowException(){
//将String字符串信息转化为JSONObject数据格式
JSONObject userinfoObject = JSON.parseObject(getUserinfoStr());
JSONObject resultObject = userinfoObject.getJSONObject("result");
log.info("获取resultObject信息解密后:"+JSON.toJSONString(resultObject));
//判断返回结果数据是否为空,或者手机号是否为空
if(resultObject == null){
return null;
}
//用户信息
JSONObject userinfoJson =resultObject.getJSONObject("userInfo");//用户信息节点 可以根据自己的项目信息实际情况去替换
UserInfo userInfo = JSON.toJavaObject(userinfoJson,UserInfo.class);
log.info("获取后台运营用户信息:"+userinfoJson);
return operateUserInfo;
}
}
根据SqlTools 和HttpRequestUtil 两个类,自行实现自己想要的功能。以下举例阿咚的实现功能,
实现将当前登陆人员的信息和操作的sql信息记录入库,在SqlTools 还标有可以查询DML操作之前的数据是什么状态。可以根据情况自行调用。
//数据库操作类型
String operateType = SqlTools.SqlCommandType(mappedStatement);
String tableName = SqlTools.getTableName(boundSql, operateType);
if(!Objects.isNull(tableName) && !tableName.contains("AA,BB,CC")){ //不想记录的表操作
Map<String,String> map =new HashMap<>();
map.put("databaseName","你的数据库库名");
map.put("tableName",tableName);
String tableNameComment = SqlTools.getTableNameComment(map,jdbcTemplate);
String userId ="admin";
String name ="admin";
String category ="管理员";
UserInfo userInfo = HttpRequestUtil.getUserInfoNotThrowException();
if(Objects.nonNull(userInfo)){
userId=userInfo.getId();
name=userInfo.getName();
}
StringBuffer sb = new StringBuffer();
sb.append("INSERT INTO `manage_operation_record` (`id`, `category`, `operation`, `remarks`, `status`, `reviser_id`, `reviser`, `table_name`, `table_desc`, `data_id`, `content`, `createdAt`, `updatedAt`) VALUES (");
sb.append("'"+ UuidUtils.base58Uuid() +"',");//主键
sb.append("'"+ category+"',");//category 分类
sb.append("'"+ OperateTypeCode.getDesc(operateType).toString() +"',");//operation 操作类型
sb.append("'"+name+":操作"+tableNameComment+"',");//remarks 备注
if(Objects.nonNull(returnValue)){
sb.append("1,");//status 状态 成功
}else{
sb.append("0,");//status 状态 失败
}
sb.append("'"+ userId+"',");//reviser_id 操作用户ID
sb.append("'"+ name +"',");//reviser 操作用户名 或手机号
sb.append("'"+ tableName +"',");//table_name 表明
sb.append("'"+ tableNameComment +"',");//table_desc 表注释
sb.append("'',");//data_id 数据id
// 获取sql语句
String sql = SqlTools.showSql(configuration, boundSql);
if(StringUtil.isEmpty(sql)&&sql.length()>200){
sql = sql.substring(0,200);
}
JSONObject json = new JSONObject();
json.put("sql",sql.replaceAll("\"",""));
sb.append("'"+ json.toJSONString() +"',");//content sql 内容 但是长度如果大于200 截断
sb.append("'"+ LocalDateTime.now() +"',");//createdAt 创建时间
sb.append("'"+ LocalDateTime.now() +"')");//updatedAt 更新时间
String sqlInsert =sb.toString();
jdbcTemplate.execute(sqlInsert);
log.info("Sql :{}", JSON.toJSONString(sql));
}
设定开关,在yml配置文件中设定开关值,不设定,默认为不开启。
阿咚使用的yml文件的形式
interceptor-switch:
MybatisInterceptorSwitch: 1
使用,在数据源配置中,注册自定义的拦截器插件。
数据源配置中添加
@Autowired private Environment env;
数据源注册插件的位置
if(StringUtil.isNotEmpty(mybatisInterceptorSwitch)&&"1".equals(mybatisInterceptorSwitch)){
bean.setPlugins(new Interceptor[]{
new PaginationInterceptor(),new MybatisInterceptor(new JdbcTemplate(dataSource))
});
}else{
bean.setPlugins(new Interceptor[]{
new PaginationInterceptor()
});
}
以上为全部的实现,如果项目中没有多数据源的情况,则不需要这后两步,创建自定义的 MybatisInterceptor 拦截器时,不需要可穿参的构造器。