SpringBoot 使用 AOP校验入参字段和翻译响应字段
组织架构
参考项目:Jeecg-Boot
校验入参数据中的字典字段和关联表字段
创建 DictVerify 注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 字典校验注解
* (注解加在方法或类属性上)
*
* @author Windows
*/
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DictVerify {
/**
* 字典数据的code或关联表的关联字段名(加注解必须要的,设置默认值是方便加在方法上)
*/
String dicCode() default "";
/**
* 关联表需要展示字段(比如)
*/
String dicText() default "";
/**
* 代码注释(用于msg提示)
*/
String dicMsg() default "";
/**
* 关联表名(数据库表名)
*/
String dicTable() default "";
}
创建AOP切面
import cn.hutool.core.util.StrUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.mohrss.leaf.baseinformation.api.dict.dto.QueryBa10FeignDTO;
import org.mohrss.leaf.baseinformation.api.dict.entity.Ba10Feign;
import org.mohrss.leaf.common.aspect.annotation.DictVerify;
import org.mohrss.leaf.common.constant.CommonConstants;
import org.mohrss.leaf.common.utils.RedisUtils;
import org.mohrss.leaf.common.utils.oConvertUtils;
import org.mohrss.leaf.common.vo.DictModel;
import org.mohrss.leaf.entity.dto.JsonResponse;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* 字典校验AOP类
*
* @author Windows
*/
@Aspect
@Component
@Slf4j
@Order(10)
public class DictVerifyAspect {
@Resource
private CommonMapper commonMapper;
@Resource
private RedisUtils redisUtils;
/**
* 定义切点Pointcut 加了DictVerify注解的方法才需要校验
* 这里根据自己控制器的包名来修改
*/
@Pointcut("execution(public *控制器的包路径.*Controller.*(..)) && @annotation(DictVerify注解的包路径)")
public void verifyService() {
}
@SneakyThrows
@Around("verifyService()")
public Object doBefore(ProceedingJoinPoint pjp) {
// 获取接口返回的数据对象
// Object result = pjp.proceed();
// System.out.println("result:" + JSONObject.toJSONString(result));
// 获取接口方法的入参
Object[] args = pjp.getArgs();
// System.out.println("pipArray:" + JSONObject.toJSONString(args));
Object obj = args[0];
for (Field field : oConvertUtils.getAllFields(obj)) {
if (field.getAnnotation(DictVerify.class) != null) {
// 获取注解中的值
String table = field.getAnnotation(DictVerify.class).dicTable();
String code = field.getAnnotation(DictVerify.class).dicCode();
String text = field.getAnnotation(DictVerify.class).dicText();
String msg = field.getAnnotation(DictVerify.class).dicMsg();
// 设置允许通过反射访问私有变量
field.setAccessible(true);
// 获取属性值
String value = field.get(obj).toString();
if (StrUtil.isNotEmpty(value)) {
// 校验关联表
if (StrUtil.isNotEmpty(table)) {
// 先查缓存
String dictCode = String.format("%s,%s,%s", table, text, code);
String redisKey = String.format("sys:cache:dicTable:SimpleKey [%s,%s]", dictCode, value);
if (!redisUtils.exists(redisKey)) {
// 公共的查询不同表不同字段的方法
DictModel dictModel = commonMapper.queryTableDictCountByKey(table, text, code, value);
if (dictModel == null) {
return JsonResponse.Fail(1, "请输入正确的" + msg);
}
// 存入redis缓存,保留5分钟
redisUtils.set(redisKey, dictModel.getText(), 300, TimeUnit.SECONDS);
}
} else { // 校验字典表
// 先查缓存
String keyString = String.format("sys:cache:dict:%s:%s", code, value);
if (!redisUtils.exists(keyString)) {
// 通过查数据库或缓存校验当前属性的值在指定字典类型中存在
QueryBa10FeignDTO queryBa10FeignDTO = new QueryBa10FeignDTO();
queryBa10FeignDTO.setAaa100(code);
queryBa10FeignDTO.setAaa102(value);
// 查询字典表,这里需要根据自己系统的字典表来修改 start
Dict dict= commomMapper.queryDictTextByKey(code,value);
// 查询字典表,这里需要根据自己系统的字典表来修改 end
if (dict== null) {
return JsonResponse.Fail(1, "请输入正确的" + msg);
}
String redisKey = String.format("sys:cache:dict:%s:%s", code, value);
redisUtils.set(redisKey, ba10 .getAaa103());
}
}
}
}
}
return pjp.proceed();
}
}
在方法上添加注解
在需要校验字段或者关联表数据是否存在的方法上添加注解,一般是新增和修改需要。
@ApiOperation(value = "新增数据")
@PostMapping("insertInfo")
@DictVerify
public JsonResponse insertInfo(@RequestBody @Valid InsertDTO insertDTO) {
return this.testService.insertInfo(insertBb80DTO);
}
在需要校验的字段上添加注解
校验关联表是否存在数据
dicTable=表名, dicCode = “关联字段名”, dicText = “需要展示的字段名”,dicMsg = “用于前端的提示消息”
@ApiModelProperty(value = "学校编号", required = true)
@DictVerify(dicTable = "scholl", dicCode = "school_no", dicText = "school_name", dicMsg = "学校编号")
private String schoolNo;
校验字典表是否存在数据
这里的dicCode就是字典表中字典类型
@ApiModelProperty(value = "是否毕业", required = true)
@DictVerify(dicCode = "graduation", dicMsg = "是否毕业")
private String graduation;
翻译响应字段
创建 Dict 注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 字典翻译注解
*
* @author Windows
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Dict {
/**
* 数据code
*/
String dicCode();
/**
* 数据Text
*/
String dicText() default "";
/**
* 数据字典表
*/
String dictTable() default "";
}
创建 Dict AOP切面
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.mohrss.leaf.baseinformation.api.dict.entity.Ba10Feign;
import org.mohrss.leaf.baseinformation.api.dict.vo.DictModel;
import org.mohrss.leaf.baseinformation.api.dict.vo.DictModelMany;
import org.mohrss.leaf.common.aspect.annotation.Dict;
import org.mohrss.leaf.common.constant.CommonConstants;
import org.mohrss.leaf.common.utils.ConvertUtils;
import org.mohrss.leaf.common.utils.RedisUtils;
import org.mohrss.leaf.common.utils.SqlInjectionUtil;
import org.mohrss.leaf.entity.dto.JsonResponse;
import org.mohrss.leaf.supervision.enforcement.dao.CommonMapper;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* 字典数据翻译AOP类
*
* @author Windows
*/
@Aspect
@Component
@Slf4j
@Order(1)
public class DictAspect {
@Resource
private RedisUtils redisUtils;
@Resource
private ObjectMapper objectMapper;
@Resource
private CommonMapper commonMapper;
/**
* 定义切点Pointcut
* 根据自己控制器包路径配置
*/
@Pointcut("execution(public * 自己的包路径.*Controller.*(..))")
public void excludeService() {
}
@Around("excludeService()")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
long time1 = System.currentTimeMillis();
Object result = pjp.proceed();
long time2 = System.currentTimeMillis();
log.debug("获取JSON数据 耗时:" + (time2 - time1) + "ms");
long start = System.currentTimeMillis();
this.parseDictText(result);
long end = System.currentTimeMillis();
log.debug("注入字典到JSON数据 耗时" + (end - start) + "ms");
return result;
}
/**
* 本方法针对返回对象为Result 的IPage的分页列表数据进行动态字典注入
* 字典注入实现 通过对实体类添加注解@dict 来标识需要的字典内容,字典分为单字典code即可 ,table字典 code table text配合使用与原来jeecg的用法相同
* 示例为SysUser 字段为sex 添加了注解@Dict(dicCode = "sex") 会在字典服务立马查出来对应的text 然后在请求list的时候将这个字典text,已字段名称加_dictText形式返回到前端
*
* @param result 响应数据
*/
private Object parseDictText(Object result) {
if (result instanceof JsonResponse) {
// 响应数据是分页列表数据
if (((JsonResponse) result).getData() instanceof IPage) {
// 判断是否含有字典注解,没有注解返回 start-----
Boolean hasDict = checkHasDict(((IPage) ((JsonResponse) result).getData()).getRecords());
if (!hasDict) {
return result;
}
//判断是否含有字典注解,没有注解返回 end -----
this.resultIsList(result);
} else if (((JsonResponse) result).getData() != null) { // 响应数据是单个对象数据时
this.resultIsDetail(result);
}
}
return result;
}
/**
* 响应数据是分页列表数据
*/
private void resultIsList(Object result) {
// 列表数据集合 ,把数据转成json
List<JSONObject> items = new ArrayList<>();
//step.1 筛选出加了 Dict 注解的字段列表
List<Field> dictFieldList = new ArrayList<>();
// 字典数据列表, key = 字典code,value=数据列表
Map<String, List<String>> dataListMap = new HashMap<>();
//取出结果集
List<Object> records = ((IPage) ((JsonResponse) result).getData()).getRecords();
log.debug(" __ 进入字典翻译切面 DictAspect —— ");
// 遍历数据列表,
for (Object record : records) {
String json = "{}";
try {
//解决@JsonFormat注解解析不了的问题详见SysAnnouncement类的@JsonFormat
json = objectMapper.writeValueAsString(record);
} catch (JsonProcessingException e) {
log.error("json解析失败" + e.getMessage(), e);
}
//防止restcontroller返回json数据后key顺序错乱 -----
JSONObject item = JSONObject.parseObject(json, Feature.OrderedField);
//update-begin--Author:scott -- Date:20190603 ----for:解决继承实体字段无法翻译问题------
// 遍历所有字段,把字典Code取出来,放到 map 里
for (Field field : ConvertUtils.getAllFields(record)) {
String value = item.getString(field.getName());
if (ConvertUtils.isEmpty(value)) {
continue;
}
//update-end--Author:scott -- Date:20190603 ----for:解决继承实体字段无法翻译问题------
if (field.getAnnotation(Dict.class) != null) {
if (!dictFieldList.contains(field)) {
dictFieldList.add(field);
}
String code = field.getAnnotation(Dict.class).dicCode();
String text = field.getAnnotation(Dict.class).dicText();
String table = field.getAnnotation(Dict.class).dictTable();
List<String> dataList;
String dictCode = code;
if (StrUtil.isNotEmpty(table)) {
dictCode = String.format("%s,%s,%s", table, text, code);
}
// 判断一个map中是否存在这个key,如果存在则处理value的数据,如果不存在,则创建一个满足value要求的数据结构放到value中
dataList = dataListMap.computeIfAbsent(dictCode, k -> new ArrayList<>());
// list 去重添加
this.listAddAllDeduplicate(dataList, Arrays.asList(value.split(",")));
}
}
items.add(item);
}
//step.2 调用翻译方法,一次性翻译
Map<String, List<DictModel>> translText = this.translateAllDict(dataListMap);
//step.3 将翻译结果填充到返回结果里
for (JSONObject record : items) {
for (Field field : dictFieldList) {
String code = field.getAnnotation(Dict.class).dicCode();
String text = field.getAnnotation(Dict.class).dicText();
String table = field.getAnnotation(Dict.class).dictTable();
String fieldDictCode = code;
// 如果指定了表名则需要拼接字符串
if (StrUtil.isNotEmpty(table)) {
fieldDictCode = String.format("%s,%s,%s", table, text, code);
}
String value = record.getString(field.getName());
if (ConvertUtils.isNotEmpty(value)) {
List<DictModel> dictModels = translText.get(fieldDictCode);
if (dictModels == null || dictModels.size() == 0) {
continue;
}
String textValue = this.translDictText(dictModels, value);
record.put(field.getName() + CommonConstants.DICT_TEXT_SUFFIX, textValue);
}
}
}
((IPage) ((JsonResponse) result).getData()).setRecords(items);
}
/**
* 响应数据是单个对象数据时
*/
private void resultIsDetail(Object result) {
// 获取响应里面的data数据
Object data = ((JsonResponse) result).getData();
String json = "{}";
try {
//解决@JsonFormat注解解析不了的问题详见SysAnnouncement类的@JsonFormat
json = objectMapper.writeValueAsString(data);
} catch (JsonProcessingException e) {
log.error("json解析失败" + e.getMessage(), e);
}
// 防止restcontroller返回json数据后key顺序错乱
JSONObject item = JSONObject.parseObject(json, Feature.OrderedField);
//step.1 筛选出加了 Dict 注解的字段列表
List<Field> dictFieldList = new ArrayList<>();
// 字典数据列表, key = 字典code,value=数据列表
Map<String, List<String>> dataListMap = new HashMap<>();
// 遍历所有字段,把字典Code取出来,放到 map 里
for (Field field : ConvertUtils.getAllFields(data)) {
// 根据对象的字段名获取字段的值
String value = item.getString(field.getName());
if (ConvertUtils.isEmpty(value)) {
continue;
}
// 查看字段是否添加了 Dict 注解
if (field.getAnnotation(Dict.class) != null) {
// 不存在则添加到列表
if (!dictFieldList.contains(field)) {
dictFieldList.add(field);
}
String code = field.getAnnotation(Dict.class).dicCode(); // 数据库字段名
String text = field.getAnnotation(Dict.class).dicText(); // 数据库需要展示的字段
String table = field.getAnnotation(Dict.class).dictTable(); // 数据库表名
List<String> dataList;
String dictCode = code;
if (StrUtil.isNotEmpty(table)) {
dictCode = String.format("%s,%s,%s", table, text, code);
}
dataList = dataListMap.computeIfAbsent(dictCode, k -> new ArrayList<>());
this.listAddAllDeduplicate(dataList, Arrays.asList(value.split(",")));
}
}
//step.2 调用翻译方法,一次性翻译(把添加过注解属性翻译出来)
Map<String, List<DictModel>> translText = this.translateAllDict(dataListMap);
//step.3 将翻译结果填充到返回结果里
for (Field field : dictFieldList) {
String code = field.getAnnotation(Dict.class).dicCode();
String text = field.getAnnotation(Dict.class).dicText();
String table = field.getAnnotation(Dict.class).dictTable();
String fieldDictCode = code;
// 如果指定了表名则需要拼接字符串
if (StrUtil.isNotEmpty(table)) {
fieldDictCode = String.format("%s,%s,%s", table, text, code);
}
String value = item.getString(field.getName());
// 判断是否为空、""、"null"
if (ConvertUtils.isNotEmpty(value)) {
List<DictModel> dictModels = translText.get(fieldDictCode);
if (dictModels == null || dictModels.size() == 0) {
continue;
}
String textValue = this.translDictText(dictModels, value);
// 把翻译过的数据添加到对象里
item.put(field.getName() + CommonConstants.DICT_TEXT_SUFFIX, textValue);
}
}
((JsonResponse) result).setData(item);
}
/**
* list 去重添加
*/
private void listAddAllDeduplicate(List<String> dataList, List<String> addList) {
// 筛选出dataList中没有的数据
List<String> filterList = addList.stream().filter(i -> !dataList.contains(i)).collect(Collectors.toList());
dataList.addAll(filterList);
}
/**
* 一次性把所有的字典都翻译了
* 1. 所有的普通数据字典的所有数据只执行一次SQL
* 2. 表字典相同的所有数据只执行一次SQL
*
* @param dataListMap 字典数据列表, key = 字典code,value=数据列表
* @return 翻译后的字典文本,key=dicCode
*/
private Map<String, List<DictModel>> translateAllDict(Map<String, List<String>> dataListMap) {
// 翻译后的字典文本,key=dicCode
Map<String, List<DictModel>> translText = new HashMap<>();
// 需要翻译的数据(有些可以从redis缓存中获取,就不走数据库查询)
List<String> needTranslData = new ArrayList<>();
//step.1 先通过redis中获取缓存字典数据
for (String dictCode : dataListMap.keySet()) {
List<String> dataList = dataListMap.get(dictCode);
if (dataList.size() == 0) {
continue;
}
// 表字典需要翻译的数据
List<String> needTranslDataTable = new ArrayList<>();
for (String s : dataList) {
String data = s.trim();
if (data.length() == 0) {
continue; //跳过循环
}
if (dictCode.contains(",")) {
String keyString = String.format(CommonConstants.DICT_TABLE_CACHE_PREFIX, dictCode, data);
if (redisUtils.exists(keyString)) {
try {
String text = ConvertUtils.getString(redisUtils.get(keyString));
List<DictModel> list = translText.computeIfAbsent(dictCode, k -> new ArrayList<>());
list.add(new DictModel(data, text));
} catch (Exception e) {
log.warn(e.getMessage());
}
} else if (!needTranslDataTable.contains(data)) {
// 去重添加
needTranslDataTable.add(data);
}
} else {
String keyString = String.format(CommonConstants.DICT_TEXT_CACHE_PREFIX, dictCode, data);
if (redisUtils.exists(keyString)) {
try {
String text = ConvertUtils.getString(redisUtils.get(keyString));
List<DictModel> list = translText.computeIfAbsent(dictCode, k -> new ArrayList<>());
list.add(new DictModel(data, text));
} catch (Exception e) {
log.warn(e.getMessage());
}
} else if (!needTranslData.contains(data)) {
// 去重添加
needTranslData.add(data);
}
}
}
//step.2 调用数据库翻译表字典
if (needTranslDataTable.size() > 0) {
String[] arr = dictCode.split(",");
String table = arr[0], text = arr[1], code = arr[2];
String values = String.join(",", needTranslDataTable);
// 数据库查看字段的描述
// @dict注解支持 dicTable 设置where条件
String filterSql = null;
if (table.toLowerCase().indexOf("where") > 0) {
String[] arr1 = table.split(" (?i)where ");
table = arr1[0];
filterSql = arr1[1];
}
String[] tableAndFields = new String[]{table, text, code};
SqlInjectionUtil.filterContent(tableAndFields);
SqlInjectionUtil.specialFilterContentForDictSql(filterSql);
// 这里是公共的方法DAO 根据自己的业务定义
List<DictModel> texts = commonMapper.queryTableDictByKeysAndFilterSql(table, text, code, filterSql, Arrays.asList(values.split(",")));
// log.info("translateDictFromTableByKeys.result:" + texts);
List<DictModel> list = translText.computeIfAbsent(dictCode, k -> new ArrayList<>());
list.addAll(texts);
// 做 redis 缓存
for (DictModel dict : texts) {
String redisKey = String.format(CommonConstants.DICT_TABLE_CACHE_PREFIX, dictCode, dict.getValue());
try {
// 保留5分钟
redisUtils.set(redisKey, dict.getText(), 300, TimeUnit.SECONDS);061
} catch (Exception e) {
log.warn(e.getMessage(), e);
}
}
}
}
//step.3 调用数据库进行翻译普通字典
if (needTranslData.size() > 0) {
List<String> dictCodeList = Arrays.asList(dataListMap.keySet().toArray(new String[]{}));
// 将不包含逗号的字典code筛选出来,因为带逗号的是表字典,而不是普通的数据字典
List<String> filterDictCodes = dictCodeList.stream().filter(key -> !key.contains(",")).collect(Collectors.toList());
String dictCodes = String.join(",", filterDictCodes);
String values = String.join(",", needTranslData);
// 公共DAO 查询字典表 start
List<DictModelMany> dictModelManies = commonMapper.queryManyDictByKeys(Arrays.asList(dictCodes.split(",")), Arrays.asList(values.split(",")));
// 公共DAO 查询字典表 end
Map<String, List<DictModel>> dictMap = new HashMap(5);
for (DictModelMany dict : dictModelManies) {
List<DictModel> dictItemList = dictMap.computeIfAbsent(dict.getDictCode(), i -> new ArrayList<>());
dictItemList.add(new DictModel(dict.getValue(), dict.getText()));
}
//系统字典数据应该包括自定义的java类-枚举 start
// Map<String, List<DictModel>> enumRes = ResourceUtil.queryManyDictByKeys(dictCodeList, keys);
// dictMap.putAll(enumRes);
//系统字典数据应该包括自定义的java类-枚举 end
Map<String, List<DictModel>> manyDict = dictMap;
// log.info("translateManyDict.result:" + manyDict);
for (String dictCode : manyDict.keySet()) {
List<DictModel> list = translText.computeIfAbsent(dictCode, k -> new ArrayList<>());
List<DictModel> newList = manyDict.get(dictCode);
list.addAll(newList);
// 做 redis 缓存
for (DictModel dict : newList) {
String redisKey = String.format(CommonConstants.DICT_TEXT_CACHE_PREFIX, dictCode, dict.getValue());
try {
redisUtils.set(redisKey, dict.getText());
} catch (Exception e) {
log.warn(e.getMessage(), e);
}
}
}
}
return translText;
}
/**
* 字典值替换文本
*
* @param dictModels 字典集合
* @param values 文本内容
* @return 替换后的文本
*/
private String translDictText(List<DictModel> dictModels, String values) {
List<String> result = new ArrayList<>();
// 允许多个逗号分隔,允许传数组对象
String[] splitVal = values.split(",");
for (String val : splitVal) {
String dictText = val;
for (DictModel dict : dictModels) {
if (val.equals(dict.getValue())) {
dictText = dict.getText();
break;
}
}
result.add(dictText);
}
return String.join(",", result);
}
/**
* 翻译字典文本
*
* @param code
* @param text
* @param table
* @param key
* @return
*/
@Deprecated
private String translateDictValue(String code, String text, String table, String key) {
if (ConvertUtils.isEmpty(key)) {
return null;
}
StringBuffer textValue = new StringBuffer();
String[] keys = key.split(",");
for (String k : keys) {
String tmpValue = null;
log.debug(" 字典 key : " + k);
if (k.trim().length() == 0) {
continue; //跳过循环
}
//update-begin--Author:scott -- Date:20210531 ----for: !56 优化微服务应用下存在表字段需要字典翻译时加载缓慢问题-----
if (StrUtil.isNotEmpty(table)) {
log.info("--DictAspect------dicTable=" + table + " ,dicText= " + text + " ,dicCode=" + code);
String keyString = String.format("sys:cache:dicTable::SimpleKey [%s,%s,%s,%s]", table, text, code, k.trim());
if (redisUtils.exists(keyString)) {
try {
tmpValue = ConvertUtils.getString(redisUtils.get(keyString));
} catch (Exception e) {
log.warn(e.getMessage());
}
} else {
tmpValue = commonMapper.queryTableDictTextByKey(table, text, code, k.trim());
}
} else {
String keyString = String.format(CommonConstants.DICT_TEXT_CACHE_PREFIX, code, k.trim());
if (redisUtils.exists(keyString)) {
try {
tmpValue = ConvertUtils.getString(redisUtils.get(keyString));
} catch (Exception e) {
log.warn(e.getMessage());
}
} else {
// 通过字典的code和入参的值查字典表是否存在记录
tmpValue = commonMapper.queryDictTextByKey(code, k.trim());
}
}
//update-end--Author:scott -- Date:20210531 ----for: !56 优化微服务应用下存在表字段需要字典翻译时加载缓慢问题-----
if (tmpValue != null) {
if (!"".equals(textValue.toString())) {
textValue.append(",");
}
textValue.append(tmpValue);
}
}
return textValue.toString();
}
/**
* 检测返回结果集中是否包含Dict注解
*
* @param records 结果集
* @return 是否包含注解
*/
private Boolean checkHasDict(List<Object> records) {
if (ConvertUtils.isNotEmpty(records) && records.size() > 0) {
for (Field field : ConvertUtils.getAllFields(records.get(0))) {
if (ConvertUtils.isNotEmpty(field.getAnnotation(Dict.class))) {
return true;
}
}
}
return false;
}
}
在需要翻译的字段上添加注解
需要关联表翻译的
dicTable=表名, dicCode = “关联字段名”, dicText = “需要展示的字段名”
@ApiModelProperty(value = "学校编号")
@Dict(dicTable = "scholl", dicCode = "school_no", dicText = "school_name")
private String schoolNo;
关联字典表翻译的
这里的dicCode就是字典表中字典类型
@ApiModelProperty(value = "是否毕业")
@Dict(dicCode = "graduation")
private String graduation;
查询结果
加了Dict 注解的就会帮我翻译出来,并不会覆盖原来的值
{
"schoolNo":"1001",
"schoolNo_dictText":"学校名称",
"graduation":"1",
"graduation_dictText":"是",
}
CommonMapper、CommonMapper.xml、DictModel、DictModelMany、CommonConstants
CommonMapper
import org.apache.ibatis.annotations.Param;
import org.mohrss.leaf.baseinformation.api.dict.vo.DictModel;
import org.mohrss.leaf.baseinformation.api.dict.vo.DictModelMany;
import java.util.List;
/**
* 代码表(Ba10)表数据库访问层
*
* @author ddz
* @since 2022-12-19 10:05:17
*/
public interface CommonMapper {
/**
* 查询字典表的数据
*
* @param table 表名
* @param text 显示字段名
* @param code 存储字段名
* @param filterSql 条件sql
* @param codeValues 存储字段值 作为查询条件in
* @return 集合
*/
List<DictModel> queryTableDictByKeysAndFilterSql(@Param("table") String table, @Param("text") String text, @Param("code") String code, @Param("filterSql") String filterSql, @Param("codeValues") List<String> codeValues);
/**
* 通过查询指定table的 text code key 获取字典值
*
* @param table 表名
* @param text 展示字段名
* @param code 字段名
* @param key 字段值
* @return 展示字段值
*/
String queryTableDictTextByKey(@Param("table") String table, @Param("text") String text, @Param("code") String code, @Param("key") String key);
/**
* 通过指定table的 code key 查询是否存在记录
*
* @param table 表名
* @param text 展示字段名
* @param code 字段名
* @param key 字段值
* @return 行数
*/
DictModel queryTableDictCountByKey(@Param("table") String table, @Param("text") String text, @Param("code") String code, @Param("key") String key);
/**
* 可通过多个字典code查询翻译文本
*
* @param dictCodeList 多个字典code
* @param keys 数据列表
* @return 响应集合
*/
List<DictModelMany> queryManyDictByKeys(@Param("dictCodeList") List<String> dictCodeList, @Param("keys") List<String> keys);
}
CommonMapper.xml
根据自己需求来编写SQL
<select id="queryTableDictByKeysAndFilterSql" parameterType="String"
resultType="org.mohrss.leaf.baseinformation.api.dict.vo.DictModel">
select ${text} as "text", ${code} as "value" from ${table} where ${code} IN (
<foreach item="key" collection="codeValues" separator=",">
#{key}
</foreach>
)
<if test="filterSql != null and filterSql != ''">
and ${filterSql}
</if>
</select>
<!--通过查询指定table的 text code key 获取字典值-->
<select id="queryTableDictTextByKey" parameterType="String" resultType="String">
select ${text} as "text" from ${table} where ${code}= #{key}
</select>
<!--通过指定table的 code key 查询是否存在记录-->
<select id="queryTableDictCountByKey" parameterType="String"
resultType="org.mohrss.leaf.baseinformation.api.dict.vo.DictModel">
select ${text} as "text", ${code} as "value" from ${table} where ${code}= #{key}
</select>
<!-- 通过字典code获取字典数据,可批量查询 -->
<select id="queryManyDictByKeys" parameterType="String"
resultType="org.mohrss.leaf.baseinformation.api.dict.vo.DictModelMany">
SELECT
AAA100 AS dict_code,
AAA103 AS "text",
AAA102 AS "value"
FROM
BA10
WHERE AAA100 IN (
<foreach item="dictCode" collection="dictCodeList" separator=",">
#{dictCode}
</foreach>
)
AND AAA102 IN (
<foreach item="key" collection="keys" separator=",">
#{key}
</foreach>
)
</select>
DictModel
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.io.Serializable;
/**
* 查询字典类
* @author Windows
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@JsonIgnoreProperties(ignoreUnknown = true)
public class DictModel implements Serializable{
private static final long serialVersionUID = 1L;
public DictModel() {
}
public DictModel(String value, String text) {
this.value = value;
this.text = text;
}
/**
* 字典value
*/
private String value;
/**
* 字典文本
*/
private String text;
/**
* 特殊用途: JgEditableTable
*/
public String getTitle() {
return this.text;
}
/**
* 特殊用途: vue3 Select组件
*/
public String getLabel() {
return this.text;
}
}
DictModelMany
package org.mohrss.leaf.common.constant;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 查询多个字典时用到
* @author Windows
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class DictModelMany extends DictModel {
/**
* 字典code,根据多个字段code查询时才用到,用于区分不同的字典选项
*/
private String dictCode;
}
公共工具类
RedisUtils
前提是项目中配置好 redis 连接
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* Redis工具类
*
* @author Windows
*/
@Configuration
public class RedisUtils {
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Bean(name = "redisTemplate")
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, String> template = new RedisTemplate<String, String>();
template.setConnectionFactory(factory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setHashKeySerializer(new GenericJackson2JsonRedisSerializer());
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
template.afterPropertiesSet();
return template;
}
/**
* 如果使用注解注入RedisTemplate对象,则不需要该setter方法
*/
public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 获取Hash键值对的数量
*
* @param key 键
* @return 数量
*/
public long getHashSize(String key) {
return redisTemplate.opsForHash().size(key);
}
/**
* 正则表达式匹配key
*
* @param pattern 正则表达式
* @return 结果
*/
public Object keys(String pattern) {
if (StrUtil.isNotEmpty(pattern)) {
return redisTemplate.keys(pattern);
}
return null;
}
/**
* String类型缓存获取
*
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* String类型缓存保存
*
* @param key 键
* @param value 值
* @return true:成功;false:失败
*/
public boolean set(String key, Object value) {
try {
if (StrUtil.isNotEmpty(key) && null != value) {
redisTemplate.opsForValue().set(key, value);
return true;
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 指定缓存失效时间
*
* @param key 键
* @param time 时间(秒)
*/
public void expire(String key, long time) {
try {
if (StrUtil.isNotEmpty(key) && time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 根据key获取过期时间
*
* @param key 键 不能为null
* @return 时间(秒)返回0代表为永久有效;-1代表key不存在
*/
public long getExpire(String key) {
if (StrUtil.isNotEmpty(key)) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
return -1L;
}
/**
* 判断key是否存在
*
* @param key 键
* @return true:存在;false:不存在
*/
public boolean exists(String key) {
try {
if (StrUtil.isNotEmpty(key)) {
return redisTemplate.hasKey(key);
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 删除缓存(批量)
*
* @param keys(可变参数)可以传一个值或多个
*/
@SuppressWarnings("unchecked")
public void delete(String... keys) {
if (keys != null && keys.length > 0) {
if (keys.length == 1) {
redisTemplate.delete(keys[0]);
} else {
// 批量删除
redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(keys));
}
}
}
/**
* String类型缓存保存并设置时间
*
* @param key 键
* @param value 值
* @param time 时间(秒):time要大于0 如果time小于等于0 将设置无限期
* @return true:成功;false:失败
*/
public boolean set(String key, Object value, long time) {
try {
if (StrUtil.isNotEmpty(key) && null != value) {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time,
TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* String类型缓存保存并设置时间
*
* @param key 键
* @param value 值
* @param time 时间(秒):time要大于0 如果time小于等于0 将设置无限期
* @param unit 时间格式
* @return true:成功;false:失败
*/
public boolean set(String key, Object value, long time, TimeUnit unit) {
try {
if (StrUtil.isNotEmpty(key) && null != value) {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, unit);
} else {
set(key, value);
}
return true;
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 递增
*
* @param key 键
* @param delta 要增加几(大于0)
* @return 使用事务事为0
*/
public long increment(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
if (StrUtil.isNotEmpty(key)) {
return redisTemplate.opsForValue().increment(key, delta);
} else {
return -1L;
}
}
/**
* 递减
*
* @param key 键
* @param delta 要减少几(小于0)
* @return 使用事务事为0
*/
public long decrement(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
if (StrUtil.isNotEmpty(key)) {
return redisTemplate.opsForValue().increment(key, -delta);
} else {
return -1L;
}
}
/**
* 获取hash结构的数据 (Hash类型)
*
* @param key 键: 不能为null
* @param item 项: 不能为null
* @return 值
*/
public Object getHash(String key, String item) {
if (StrUtil.isNotEmpty(key) && StrUtil.isNotEmpty(item)) {
return redisTemplate.opsForHash().get(key, item);
} else {
return null;
}
}
/**
* 获取Key对应的所有键值HashMap
*
* @param key 键
* @return 对应的多个键值
*/
public Map<Object, Object> getHashMap(String key) {
if (StrUtil.isNotEmpty(key)) {
return redisTemplate.opsForHash().entries(key);
} else {
return null;
}
}
/**
* 保存hashMap结构的数据(HashMap类型)
* Map<Object,List> 结构
*
* @param key 键
* @param map 对应多个键值
* @return true:成功;false:失败
*/
public boolean setHashMapList(String key, Map<Object, List<JSONObject>> map) {
try {
if (StrUtil.isNotEmpty(key) && null != map && map.size() > 0) {
redisTemplate.opsForHash().putAll(key, map);
return true;
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 保存hashMap结构的数据(HashMap类型)
* Map<Object,List> 结构
*
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true:成功; false:失败
*/
public boolean setHashMapList(String key, Map<Object, List<JSONObject>> map, long time) {
try {
if (StrUtil.isNotEmpty(key) && null != map && map.size() > 0) {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 保存hashMap结构的数据(HashMap类型)
*
* @param key 键
* @param map 对应多个键值
* @return true:成功;false:失败
*/
public boolean setHashMap(String key, Map<Object, Object> map) {
try {
if (StrUtil.isNotEmpty(key) && null != map && map.size() > 0) {
redisTemplate.opsForHash().putAll(key, map);
return true;
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 保存hashMap结构的数据,并设置时间 (HashMap类型)
*
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true:成功; false:失败
*/
public boolean setHashMap(String key, Map<Object, Object> map, long time) {
try {
if (StrUtil.isNotEmpty(key) && null != map && map.size() > 0) {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 向一个hash类型的数据中保存数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @return true:成功;false:失败
*/
public boolean setHash(String key, String item, Object value) {
try {
if (StrUtil.isNotEmpty(key) && StrUtil.isNotEmpty(item)
&& null != value) {
redisTemplate.opsForHash().put(key, item, value);
return true;
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 向一个hash类型的数据中保存数据,如果不存在将创建,指定保存时间
*
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash数据时间,这里将会替换原有的时间
* @return true:成功;false:失败
*/
public boolean setHash(String key, String item, Object value, long time) {
try {
if (StrUtil.isNotEmpty(key) && StrUtil.isNotEmpty(item)
&& null != value) {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 删除hash数据中的值
*
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public void deleteHash(String key, Object... item) {
if (StrUtil.isNotEmpty(key) && null != item) {
redisTemplate.opsForHash().delete(key, item);
}
}
/**
* 判断hash数据中是否有该项的值
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return true:存在; false:不存在
*/
public boolean existHashKey(String key, String item) {
if (StrUtil.isNotEmpty(key) && StrUtil.isNotEmpty(item)) {
return redisTemplate.opsForHash().hasKey(key, item);
} else {
return false;
}
}
/**
* hash递增,如果不存在,就会创建一个,并把新增后的值返回
*
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
* @return -1,失败
*/
public double incrementHash(String key, String item, double by) {
if (StrUtil.isNotEmpty(key) && StrUtil.isNotEmpty(item)) {
return redisTemplate.opsForHash().increment(key, item, by);
} else {
return -1;
}
}
/**
* hash递减
*
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
* @return -1,失败
*/
public double decrementHash(String key, String item, double by) {
if (StrUtil.isNotEmpty(key) && StrUtil.isNotEmpty(item)) {
return redisTemplate.opsForHash().increment(key, item, -by);
} else {
return -1;
}
}
// ============================set=============================
/**
* 根据key获取Set中的所有值
*
* @param key 键
* @return 值
*/
public Set<Object> getSet(String key) {
try {
if (StrUtil.isNotEmpty(key)) {
return redisTemplate.opsForSet().members(key);
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 根据value从一个set中查询,是否存在
*
* @param key 键
* @param value 值
* @return true:存在; false:不存在
*/
public boolean existSetKey(String key, Object value) {
try {
if (StrUtil.isNotEmpty(key) && null != value) {
return redisTemplate.opsForSet().isMember(key, value);
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 将数据放入set缓存,设置过期时间
*
* @param key 键
* @param values 值
* @return 成功个数
*/
public long setSet(String key, Object... values) {
try {
if (StrUtil.isNotEmpty(key) && null != values) {
return redisTemplate.opsForSet().add(key, values);
}
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
/**
* 将set数据放入缓存,指定保存时间
*
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
public long setSetExpire(String key, long time, Object... values) {
try {
if (StrUtil.isNotEmpty(key) && null != values) {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0) {
expire(key, time);
}
return count;
}
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
/**
* 获取set缓存的长度
*
* @param key 键
* @return 使用事务事为0
*/
public long getSetSize(String key) {
try {
if (StrUtil.isNotEmpty(key)) {
return redisTemplate.opsForSet().size(key);
}
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
/**
* 移除值为value的
*
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
public long removeSet(String key, Object... values) {
try {
if (StrUtil.isNotEmpty(key) && null != values) {
return redisTemplate.opsForSet().remove(key, values);
}
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
// ===============================list=================================
/**
* 获取list缓存的内容
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
* @return 结果集
*/
public List<Object> getList(String key, long start, long end) {
try {
if (StrUtil.isNotEmpty(key)) {
return redisTemplate.opsForList().range(key, start, end);
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 获取list缓存的长度
*
* @param key 键
* @return 长度
*/
public long getListSize(String key) {
try {
if (StrUtil.isNotEmpty(key)) {
return redisTemplate.opsForList().size(key);
}
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
/**
* 通过索引 获取list中的值
*
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
* @return 值
*/
public Object getListIndex(String key, long index) {
try {
if (StrUtil.isNotEmpty(key)) {
return redisTemplate.opsForList().index(key, index);
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return 是否成功
*/
public boolean setList(String key, List<Object> value) {
try {
if (StrUtil.isNotEmpty(key) && null != value) {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 将list放入缓存,指定时间
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return 是否成功
*/
public boolean setListExpire(String key, List<Object> value, long time) {
try {
if (StrUtil.isNotEmpty(key) && null != value) {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0) {
expire(key, time);
}
return true;
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 根据索引修改list中的某条数据
*
* @param key 键
* @param index 索引
* @param value 值
* @return 是否成功
*/
public boolean updateListIndex(String key, long index, Object value) {
try {
if (StrUtil.isNotEmpty(key) && null != value) {
redisTemplate.opsForList().set(key, index, value);
return true;
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 移除N个值为value
*
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
public long removeList(String key, long count, Object value) {
try {
if (StrUtil.isNotEmpty(key) && null != value) {
return redisTemplate.opsForList().remove(key, count, value);
}
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
}
ConvertUtils
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.BeanUtils;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.sql.Date;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 转换
* @author Windows
*/
@Slf4j
public class ConvertUtils {
public static boolean isEmpty(Object object) {
if (object == null) {
return (true);
}
if ("".equals(object)) {
return (true);
}
if ("null".equals(object)) {
return (true);
}
return (false);
}
public static boolean isNotEmpty(Object object) {
if (object != null && !"".equals(object) && !"null".equals(object)) {
return (true);
}
return (false);
}
public static String decode(String strIn, String sourceCode, String targetCode) {
return code2code(strIn, sourceCode, targetCode);
}
public static String StrToUTF(String strIn, String sourceCode, String targetCode) {
strIn = "";
try {
strIn = new String(strIn.getBytes(StandardCharsets.ISO_8859_1), "GBK");
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return strIn;
}
private static String code2code(String strIn, String sourceCode, String targetCode) {
String strOut = null;
if (strIn == null || "".equals(strIn.trim())) {
return strIn;
}
try {
byte[] b = strIn.getBytes(sourceCode);
for (byte value : b) {
System.out.print(value + " ");
}
strOut = new String(b, targetCode);
} catch (Exception e) {
e.printStackTrace();
return null;
}
return strOut;
}
public static int getInt(String s, int defval) {
if (s == null || "".equals(s)) {
return (defval);
}
try {
return (Integer.parseInt(s));
} catch (NumberFormatException e) {
return (defval);
}
}
public static int getInt(String s) {
if (s == null || "".equals(s)) {
return 0;
}
try {
return (Integer.parseInt(s));
} catch (NumberFormatException e) {
return 0;
}
}
public static int getInt(String s, Integer df) {
if (s == null || "".equals(s)) {
return df;
}
try {
return (Integer.parseInt(s));
} catch (NumberFormatException e) {
return 0;
}
}
public static Integer[] getInts(String[] s) {
Integer[] integer = new Integer[s.length];
if (s == null) {
return null;
}
for (int i = 0; i < s.length; i++) {
integer[i] = Integer.parseInt(s[i]);
}
return integer;
}
public static double getDouble(String s, double defval) {
if (s == null || "".equals(s)) {
return (defval);
}
try {
return (Double.parseDouble(s));
} catch (NumberFormatException e) {
return (defval);
}
}
public static double getDou(Double s, double defval) {
if (s == null) {
return (defval);
}
return s;
}
/*public static Short getShort(String s) {
if (StringUtil.isNotEmpty(s)) {
return (Short.parseShort(s));
} else {
return null;
}
}*/
public static int getInt(Object object, int defval) {
if (isEmpty(object)) {
return (defval);
}
try {
return (Integer.parseInt(object.toString()));
} catch (NumberFormatException e) {
return (defval);
}
}
public static Integer getInt(Object object) {
if (isEmpty(object)) {
return null;
}
try {
return (Integer.parseInt(object.toString()));
} catch (NumberFormatException e) {
return null;
}
}
public static int getInt(BigDecimal s, int defval) {
if (s == null) {
return (defval);
}
return s.intValue();
}
public static Integer[] getIntegerArry(String[] object) {
int len = object.length;
Integer[] result = new Integer[len];
try {
for (int i = 0; i < len; i++) {
result[i] = new Integer(object[i].trim());
}
return result;
} catch (NumberFormatException e) {
return null;
}
}
public static String getString(String s) {
return (getString(s, ""));
}
/**
* 转义成Unicode编码
*/
/*public static String escapeJava(Object s) {
return StringEscapeUtils.escapeJava(getString(s));
}*/
public static String getString(Object object) {
if (isEmpty(object)) {
return "";
}
return (object.toString().trim());
}
public static String getString(int i) {
return (String.valueOf(i));
}
public static String getString(float i) {
return (String.valueOf(i));
}
public static String getString(String s, String defval) {
if (isEmpty(s)) {
return (defval);
}
return (s.trim());
}
public static String getString(Object s, String defval) {
if (isEmpty(s)) {
return (defval);
}
return (s.toString().trim());
}
public static long stringToLong(String str) {
long test = 0L;
try {
test = Long.parseLong(str);
} catch (Exception e) {
}
return test;
}
/**
* 获取本机IP
*/
public static String getIp() {
String ip = null;
try {
InetAddress address = InetAddress.getLocalHost();
ip = address.getHostAddress();
} catch (UnknownHostException e) {
e.printStackTrace();
}
return ip;
}
/**
* 判断一个类是否为基本数据类型。
*
* @param clazz 要判断的类。
* @return true 表示为基本数据类型。
*/
private static boolean isBaseDataType(Class clazz) throws Exception {
return (clazz.equals(String.class) || clazz.equals(Integer.class) || clazz.equals(Byte.class) || clazz.equals(Long.class) || clazz.equals(Double.class) || clazz.equals(Float.class) || clazz.equals(Character.class) || clazz.equals(Short.class) || clazz.equals(BigDecimal.class) || clazz.equals(BigInteger.class) || clazz.equals(Boolean.class) || clazz.equals(Date.class) || clazz.isPrimitive());
}
/**
* @param request IP
* @return IP Address
*/
public static String getIpAddrByRequest(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
/**
* @return 本机IP
* @throws SocketException
*/
public static String getRealIp() throws SocketException {
String localip = null;// 本地IP,如果没有配置外网IP则返回它
String netip = null;// 外网IP
Enumeration<NetworkInterface> netInterfaces = NetworkInterface.getNetworkInterfaces();
InetAddress ip = null;
boolean finded = false;// 是否找到外网IP
while (netInterfaces.hasMoreElements() && !finded) {
NetworkInterface ni = netInterfaces.nextElement();
Enumeration<InetAddress> address = ni.getInetAddresses();
while (address.hasMoreElements()) {
ip = address.nextElement();
if (!ip.isSiteLocalAddress() && !ip.isLoopbackAddress() && ip.getHostAddress().indexOf(":") == -1) {// 外网IP
netip = ip.getHostAddress();
finded = true;
break;
} else if (ip.isSiteLocalAddress() && !ip.isLoopbackAddress() && ip.getHostAddress().indexOf(":") == -1) {// 内网IP
localip = ip.getHostAddress();
}
}
}
if (netip != null && !"".equals(netip)) {
return netip;
} else {
return localip;
}
}
/**
* java去除字符串中的空格、回车、换行符、制表符
*
* @param str
* @return
*/
public static String replaceBlank(String str) {
String dest = "";
if (str != null) {
Pattern p = Pattern.compile("\\s*|\t|\r|\n");
Matcher m = p.matcher(str);
dest = m.replaceAll("");
}
return dest;
}
/**
* 判断元素是否在数组内
*
* @param substring
* @param source
* @return
*/
public static boolean isIn(String substring, String[] source) {
if (source == null || source.length == 0) {
return false;
}
for (int i = 0; i < source.length; i++) {
String aSource = source[i];
if (aSource.equals(substring)) {
return true;
}
}
return false;
}
/**
* 获取Map对象
*/
public static Map<Object, Object> getHashMap() {
return new HashMap<Object, Object>();
}
/**
* SET转换MAP
*/
public static Map<Object, Object> SetToMap(Set<Object> setobj) {
Map<Object, Object> map = getHashMap();
for (Iterator iterator = setobj.iterator(); iterator.hasNext(); ) {
Map.Entry<Object, Object> entry = (Map.Entry<Object, Object>) iterator.next();
map.put(entry.getKey().toString(), entry.getValue() == null ? "" : entry.getValue().toString().trim());
}
return map;
}
public static boolean isInnerIP(String ipAddress) {
boolean isInnerIp = false;
long ipNum = getIpNum(ipAddress);
/**
* 私有IP:A类 10.0.0.0-10.255.255.255 B类 172.16.0.0-172.31.255.255 C类 192.168.0.0-192.168.255.255 当然,还有127这个网段是环回地址
**/
long aBegin = getIpNum("10.0.0.0");
long aEnd = getIpNum("10.255.255.255");
long bBegin = getIpNum("172.16.0.0");
long bEnd = getIpNum("172.31.255.255");
long cBegin = getIpNum("192.168.0.0");
long cEnd = getIpNum("192.168.255.255");
isInnerIp = isInner(ipNum, aBegin, aEnd) || isInner(ipNum, bBegin, bEnd) || isInner(ipNum, cBegin, cEnd) || ipAddress.equals("127.0.0.1");
return isInnerIp;
}
private static long getIpNum(String ipAddress) {
String[] ip = ipAddress.split("\\.");
long a = Integer.parseInt(ip[0]);
long b = Integer.parseInt(ip[1]);
long c = Integer.parseInt(ip[2]);
long d = Integer.parseInt(ip[3]);
long ipNum = a * 256 * 256 * 256 + b * 256 * 256 + c * 256 + d;
return ipNum;
}
private static boolean isInner(long userIp, long begin, long end) {
return (userIp >= begin) && (userIp <= end);
}
/**
* 将下划线大写方式命名的字符串转换为驼峰式。
* 如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。</br>
* 例如:hello_world->helloWorld
*
* @param name 转换前的下划线大写方式命名的字符串
* @return 转换后的驼峰式命名的字符串
*/
public static String camelName(String name) {
StringBuilder result = new StringBuilder();
// 快速检查
if (name == null || name.isEmpty()) {
// 没必要转换
return "";
} else if (!name.contains("_")) {
// 不含下划线,仅将首字母小写
//update-begin--Author:zhoujf Date:20180503 for:TASK #2500 【代码生成器】代码生成器开发一通用模板生成功能
//update-begin--Author:zhoujf Date:20180503 for:TASK #2500 【代码生成器】代码生成器开发一通用模板生成功能
return name.substring(0, 1).toLowerCase() + name.substring(1).toLowerCase();
//update-end--Author:zhoujf Date:20180503 for:TASK #2500 【代码生成器】代码生成器开发一通用模板生成功能
}
// 用下划线将原始字符串分割
String camels[] = name.split("_");
for (String camel : camels) {
// 跳过原始字符串中开头、结尾的下换线或双重下划线
if (camel.isEmpty()) {
continue;
}
// 处理真正的驼峰片段
if (result.length() == 0) {
// 第一个驼峰片段,全部字母都小写
result.append(camel.toLowerCase());
} else {
// 其他的驼峰片段,首字母大写
result.append(camel.substring(0, 1).toUpperCase());
result.append(camel.substring(1).toLowerCase());
}
}
return result.toString();
}
/**
* 将下划线大写方式命名的字符串转换为驼峰式。
* 如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。</br>
* 例如:hello_world,test_id->helloWorld,testId
*
* @param names 转换前的下划线大写方式命名的字符串
* @return 转换后的驼峰式命名的字符串
*/
public static String camelNames(String names) {
if (names == null || names.equals("")) {
return null;
}
StringBuffer sf = new StringBuffer();
String[] fs = names.split(",");
for (String field : fs) {
field = camelName(field);
sf.append(field + ",");
}
String result = sf.toString();
return result.substring(0, result.length() - 1);
}
//update-begin--Author:zhoujf Date:20180503 for:TASK #2500 【代码生成器】代码生成器开发一通用模板生成功能
/**
* 将下划线大写方式命名的字符串转换为驼峰式。(首字母写)
* 如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。</br>
* 例如:hello_world->HelloWorld
*
* @param name 转换前的下划线大写方式命名的字符串
* @return 转换后的驼峰式命名的字符串
*/
public static String camelNameCapFirst(String name) {
StringBuilder result = new StringBuilder();
// 快速检查
if (name == null || name.isEmpty()) {
// 没必要转换
return "";
} else if (!name.contains("_")) {
// 不含下划线,仅将首字母小写
return name.substring(0, 1).toUpperCase() + name.substring(1).toLowerCase();
}
// 用下划线将原始字符串分割
String camels[] = name.split("_");
for (String camel : camels) {
// 跳过原始字符串中开头、结尾的下换线或双重下划线
if (camel.isEmpty()) {
continue;
}
// 其他的驼峰片段,首字母大写
result.append(camel.substring(0, 1).toUpperCase());
result.append(camel.substring(1).toLowerCase());
}
return result.toString();
}
//update-end--Author:zhoujf Date:20180503 for:TASK #2500 【代码生成器】代码生成器开发一通用模板生成功能
/**
* 将驼峰命名转化成下划线
*
* @param para
* @return
*/
public static String camelToUnderline(String para) {
if (para.length() < 3) {
return para.toLowerCase();
}
StringBuilder sb = new StringBuilder(para);
int temp = 0;//定位
//从第三个字符开始 避免命名不规范
for (int i = 2; i < para.length(); i++) {
if (Character.isUpperCase(para.charAt(i))) {
sb.insert(i + temp, "_");
temp += 1;
}
}
return sb.toString().toLowerCase();
}
/**
* 随机数
*
* @param place 定义随机数的位数
*/
public static String randomGen(int place) {
String base = "qwertyuioplkjhgfdsazxcvbnmQAZWSXEDCRFVTGBYHNUJMIKLOP0123456789";
StringBuffer sb = new StringBuffer();
Random rd = new Random();
for (int i = 0; i < place; i++) {
sb.append(base.charAt(rd.nextInt(base.length())));
}
return sb.toString();
}
/**
* 获取类的所有属性,包括父类
*
* @param object 实体类
* @return 所有的属性
*/
public static Field[] getAllFields(Object object) {
Class<?> clazz = object.getClass();
List<Field> fieldList = new ArrayList<>();
while (clazz != null) {
fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
clazz = clazz.getSuperclass();
}
Field[] fields = new Field[fieldList.size()];
fieldList.toArray(fields);
return fields;
}
/**
* 将map的key全部转成小写
*
* @param list
* @return
*/
public static List<Map<String, Object>> toLowerCasePageList(List<Map<String, Object>> list) {
List<Map<String, Object>> select = new ArrayList<>();
for (Map<String, Object> row : list) {
Map<String, Object> resultMap = new HashMap<>();
Set<String> keySet = row.keySet();
for (String key : keySet) {
String newKey = key.toLowerCase();
resultMap.put(newKey, row.get(key));
}
select.add(resultMap);
}
return select;
}
/**
* 将entityList转换成modelList
*
* @param fromList
* @param tClass
* @param <F>
* @param <T>
* @return
*/
public static <F, T> List<T> entityListToModelList(List<F> fromList, Class<T> tClass) {
if (fromList == null || fromList.isEmpty()) {
return null;
}
List<T> tList = new ArrayList<>();
for (F f : fromList) {
T t = entityToModel(f, tClass);
tList.add(t);
}
return tList;
}
public static <F, T> T entityToModel(F entity, Class<T> modelClass) {
log.debug("entityToModel : Entity属性的值赋值到Model");
Object model = null;
if (entity == null || modelClass == null) {
return null;
}
try {
model = modelClass.newInstance();
} catch (InstantiationException e) {
log.error("entityToModel : 实例化异常", e);
} catch (IllegalAccessException e) {
log.error("entityToModel : 安全权限异常", e);
}
BeanUtils.copyProperties(entity, model);
return (T) model;
}
/**
* 判断 list 是否为空
*
* @param list
* @return true or false
* list == null : true
* list.size() == 0 : true
*/
public static boolean listIsEmpty(Collection list) {
return (list == null || list.size() == 0);
}
/**
* 判断 list 是否不为空
*
* @param list
* @return true or false
* list == null : false
* list.size() == 0 : false
*/
public static boolean listIsNotEmpty(Collection list) {
return !listIsEmpty(list);
}
/**
* 读取静态文本内容
*
* @param url
* @return
*/
public static String readStatic(String url) {
String json = "";
try {
//换个写法,解决springboot读取jar包中文件的问题
InputStream stream = ConvertUtils.class.getClassLoader().getResourceAsStream(url.replace("classpath:", ""));
json = IOUtils.toString(stream, "UTF-8");
} catch (IOException e) {
log.error(e.getMessage(), e);
}
return json;
}
}
SqlInjectionUtil
防止SQL注入的
package org.mohrss.leaf.common.utils;
import cn.hutool.crypto.SecureUtil;
import lombok.extern.slf4j.Slf4j;
import org.mohrss.leaf.core.framework.web.exception.BusinessException;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* sql注入处理工具类
* @author Windows
*/
@Slf4j
public class SqlInjectionUtil {
/**
* sign 用于表字典加签的盐值【SQL漏洞】
* (上线修改值 20200501,同步修改前端的盐值)
*/
private final static String TABLE_DICT_SIGN_SALT = "20200501";
private final static String XSS_STR = "and |extractvalue|updatexml|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |;|or |+|user()";
/**
* 正则 user() 匹配更严谨
*/
private final static String REGULAR_EXPRE_USER = "user[\\s]*\\([\\s]*\\)";
/**正则 show tables*/
private final static String SHOW_TABLES = "show\\s+tables";
/**
* sql注释的正则
*/
private final static Pattern SQL_ANNOTATION = Pattern.compile("/\\*.*\\*/");
/**
* 针对表字典进行额外的sign签名校验(增加安全机制)
* @param dictCode:
* @param sign:
* @param request:
* @Return: void
*/
public static void checkDictTableSign(String dictCode, String sign, HttpServletRequest request) {
//表字典SQL注入漏洞,签名校验
String accessToken = request.getHeader("X-Access-Token");
String signStr = dictCode + SqlInjectionUtil.TABLE_DICT_SIGN_SALT + accessToken;
String javaSign = SecureUtil.md5(signStr);
if (!javaSign.equals(sign)) {
log.error("表字典,SQL注入漏洞签名校验失败 :" + sign + "!=" + javaSign+ ",dicCode=" + dictCode);
throw new BusinessException("无权限访问!");
}
log.info(" 表字典,SQL注入漏洞签名校验成功!sign=" + sign + ",dicCode=" + dictCode);
}
/**
* sql注入过滤处理,遇到注入关键字抛异常
* @param value
*/
public static void filterContent(String value) {
filterContent(value, null);
}
/**
* sql注入过滤处理,遇到注入关键字抛异常
*
* @param value
* @return
*/
public static void filterContent(String value, String customXssString) {
if (value == null || "".equals(value)) {
return;
}
// 校验sql注释 不允许有sql注释
checkSqlAnnotation(value);
// 统一转为小写
value = value.toLowerCase();
//SQL注入检测存在绕过风险 https://gitee.com/jeecg/jeecg-boot/issues/I4NZGE
//value = value.replaceAll("/\\*.*\\*/","");
String[] xssArr = XSS_STR.split("\\|");
for (int i = 0; i < xssArr.length; i++) {
if (value.indexOf(xssArr[i]) > -1) {
log.error("请注意,存在SQL注入关键词---> {}", xssArr[i]);
log.error("请注意,值可能存在SQL注入风险!---> {}", value);
throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
}
}
//update-begin-author:taoyan date:2022-7-13 for: 除了XSS_STR这些提前设置好的,还需要额外的校验比如 单引号
if (customXssString != null) {
String[] xssArr2 = customXssString.split("\\|");
for (int i = 0; i < xssArr2.length; i++) {
if (value.indexOf(xssArr2[i]) > -1) {
log.error("请注意,存在SQL注入关键词---> {}", xssArr2[i]);
log.error("请注意,值可能存在SQL注入风险!---> {}", value);
throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
}
}
}
//update-end-author:taoyan date:2022-7-13 for: 除了XSS_STR这些提前设置好的,还需要额外的校验比如 单引号
if(Pattern.matches(SHOW_TABLES, value) || Pattern.matches(REGULAR_EXPRE_USER, value)){
throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
}
return;
}
/**
* sql注入过滤处理,遇到注入关键字抛异常
* @param values
*/
public static void filterContent(String[] values) {
filterContent(values, null);
}
/**
* sql注入过滤处理,遇到注入关键字抛异常
*
* @param values
* @return
*/
public static void filterContent(String[] values, String customXssString) {
String[] xssArr = XSS_STR.split("\\|");
for (String value : values) {
if (value == null || "".equals(value)) {
return;
}
// 校验sql注释 不允许有sql注释
checkSqlAnnotation(value);
// 统一转为小写
value = value.toLowerCase();
//SQL注入检测存在绕过风险 https://gitee.com/jeecg/jeecg-boot/issues/I4NZGE
//value = value.replaceAll("/\\*.*\\*/","");
for (int i = 0; i < xssArr.length; i++) {
if (value.indexOf(xssArr[i]) > -1) {
log.error("请注意,存在SQL注入关键词---> {}", xssArr[i]);
log.error("请注意,值可能存在SQL注入风险!---> {}", value);
throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
}
}
//update-begin-author:taoyan date:2022-7-13 for: 除了XSS_STR这些提前设置好的,还需要额外的校验比如 单引号
if (customXssString != null) {
String[] xssArr2 = customXssString.split("\\|");
for (int i = 0; i < xssArr2.length; i++) {
if (value.indexOf(xssArr2[i]) > -1) {
log.error("请注意,存在SQL注入关键词---> {}", xssArr2[i]);
log.error("请注意,值可能存在SQL注入风险!---> {}", value);
throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
}
}
}
//update-end-author:taoyan date:2022-7-13 for: 除了XSS_STR这些提前设置好的,还需要额外的校验比如 单引号
if(Pattern.matches(SHOW_TABLES, value) || Pattern.matches(REGULAR_EXPRE_USER, value)){
throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
}
}
return;
}
/**
* 【提醒:不通用】
* 仅用于字典条件SQL参数,注入过滤
*
* @param value
* @return
*/
//@Deprecated
public static void specialFilterContentForDictSql(String value) {
String specialXssStr = " exec |extractvalue|updatexml| insert | select | delete | update | drop | count | chr | mid | master | truncate | char | declare |;|+|user()";
String[] xssArr = specialXssStr.split("\\|");
if (value == null || "".equals(value)) {
return;
}
// 校验sql注释 不允许有sql注释
checkSqlAnnotation(value);
// 统一转为小写
value = value.toLowerCase();
//SQL注入检测存在绕过风险 https://gitee.com/jeecg/jeecg-boot/issues/I4NZGE
//value = value.replaceAll("/\\*.*\\*/","");
for (int i = 0; i < xssArr.length; i++) {
if (value.indexOf(xssArr[i]) > -1 || value.startsWith(xssArr[i].trim())) {
log.error("请注意,存在SQL注入关键词---> {}", xssArr[i]);
log.error("请注意,值可能存在SQL注入风险!---> {}", value);
throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
}
}
if(Pattern.matches(SHOW_TABLES, value) || Pattern.matches(REGULAR_EXPRE_USER, value)){
throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
}
return;
}
/**
* 【提醒:不通用】
* 仅用于Online报表SQL解析,注入过滤
* @param value
* @return
*/
//@Deprecated
public static void specialFilterContentForOnlineReport(String value) {
String specialXssStr = " exec |extractvalue|updatexml| insert | delete | update | drop | chr | mid | master | truncate | char | declare |user()";
String[] xssArr = specialXssStr.split("\\|");
if (value == null || "".equals(value)) {
return;
}
// 校验sql注释 不允许有sql注释
checkSqlAnnotation(value);
// 统一转为小写
value = value.toLowerCase();
//SQL注入检测存在绕过风险 https://gitee.com/jeecg/jeecg-boot/issues/I4NZGE
//value = value.replaceAll("/\\*.*\\*/"," ");
for (int i = 0; i < xssArr.length; i++) {
if (value.indexOf(xssArr[i]) > -1 || value.startsWith(xssArr[i].trim())) {
log.error("请注意,存在SQL注入关键词---> {}", xssArr[i]);
log.error("请注意,值可能存在SQL注入风险!---> {}", value);
throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
}
}
if(Pattern.matches(SHOW_TABLES, value) || Pattern.matches(REGULAR_EXPRE_USER, value)){
throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
}
return;
}
/**
* 判断给定的字段是不是类中的属性
* @param field 字段名
* @param clazz 类对象
* @return
*/
public static boolean isClassField(String field, Class clazz){
Field[] fields = clazz.getDeclaredFields();
for(int i=0;i<fields.length;i++){
String fieldName = fields[i].getName();
String tableColumnName = ConvertUtils.camelToUnderline(fieldName);
if(fieldName.equalsIgnoreCase(field) || tableColumnName.equalsIgnoreCase(field)){
return true;
}
}
return false;
}
/**
* 判断给定的多个字段是不是类中的属性
* @param fieldSet 字段名set
* @param clazz 类对象
* @return
*/
public static boolean isClassField(Set<String> fieldSet, Class clazz){
Field[] fields = clazz.getDeclaredFields();
for(String field: fieldSet){
boolean exist = false;
for(int i=0;i<fields.length;i++){
String fieldName = fields[i].getName();
String tableColumnName = ConvertUtils.camelToUnderline(fieldName);
if(fieldName.equalsIgnoreCase(field) || tableColumnName.equalsIgnoreCase(field)){
exist = true;
break;
}
}
if(!exist){
return false;
}
}
return true;
}
/**
* 校验是否有sql注释
* @return
*/
public static void checkSqlAnnotation(String str){
Matcher matcher = SQL_ANNOTATION.matcher(str);
if(matcher.find()){
String error = "请注意,值可能存在SQL注入风险---> \\*.*\\";
log.error(error);
throw new RuntimeException(error);
}
}
}