实现:
import java.lang.annotation.*;
/**
* =====================================
* *******开发部
* =====================================
*
* @author 开发者
* @version 1.0-SNAPSHOT
*
* @date 2023/2/6
*/
@Target({ElementType.TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface SensitiveEntity {
}
import ********.SensitiveEntity;
import ********.AesService;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Objects;
import java.util.Properties;
/**
* =====================================
* ***********开发部
* =====================================
*
* @version 1.0-SNAPSHOT
* @description
* @date 2023/2/10
*/
@Component
@Intercepts({
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}),
@Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class),
@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})
@Slf4j
public class MyBatisInterceptor implements Interceptor {
@Resource
private AesService aesService;
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object target = invocation.getTarget();
//拦截sql结果处理器
if (target instanceof ResultSetHandler) {
return resultDecrypt(invocation);
}
//拦截sql参数处理器
if (target instanceof ParameterHandler) {
return parameterEncrypt(invocation);
}
//拦截sql语句处理器
if (target instanceof StatementHandler) {
return replaceSql(invocation);
}
return invocation.proceed();
}
/**
* 对mybatis映射结果进行字段解密
*
* @param invocation 参数
* @return 结果
* @throws Throwable 异常
*/
private Object resultDecrypt(Invocation invocation) throws Throwable {
//取出查询的结果
Object resultObject = invocation.proceed();
if (Objects.isNull(resultObject)) {
return null;
}
//基于selectList
if (resultObject instanceof ArrayList) {
ArrayList resultList = (ArrayList) resultObject;
if (CollectionUtils.isEmpty(resultList) || !needToDecrypt(resultList.get(0))) {
return resultObject;
}
for (Object result : resultList) {
//逐一解密
aesService.decrypt(result);
}
//基于selectOne
} else {
if (needToDecrypt(resultObject)) {
aesService.decrypt(resultObject);
}
}
return resultObject;
}
/**
* mybatis映射参数进行加密
*
* @param invocation 参数
* @return 结果
* @throws Throwable 异常
*/
private Object parameterEncrypt(Invocation invocation) throws Throwable {
//@Signature 指定了 type= parameterHandler 后,这里的 invocation.getTarget() 便是parameterHandler
//若指定ResultSetHandler ,这里则能强转为ResultSetHandler
ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
// 获取参数对像,即 mapper 中 paramsType 的实例
Field parameterField = parameterHandler.getClass().getDeclaredField("parameterObject");
parameterField.setAccessible(true);
//取出实例
Object parameterObject = parameterField.get(parameterHandler);
if (null == parameterObject) {
return invocation.proceed();
}
Class<?> parameterObjectClass = parameterObject.getClass();
//校验该实例的类是否被@SensitiveEntity所注解
SensitiveEntity sensitiveEntity = AnnotationUtils.findAnnotation(parameterObjectClass, SensitiveEntity.class);
//未被@SensitiveEntity所注解 则为null
if (Objects.isNull(sensitiveEntity)) {
return invocation.proceed();
}
//取出当前当前类所有字段,传入加密方法
Field[] declaredFields = parameterObjectClass.getDeclaredFields();
aesService.encrypt(declaredFields, parameterObject);
return invocation.proceed();
}
/**
* 替换mybatis Sql中的加密Key
*
* @param invocation 参数
* @return 结果
* @throws Throwable 异常
*/
private Object replaceSql(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
BoundSql boundSql = statementHandler.getBoundSql();
//获取到原始sql语句
String sql = boundSql.getSql();
sql = aesService.replaceAll(sql);
if (null == sql){
return invocation.proceed();
}
//通过反射修改sql语句
Field field = boundSql.getClass().getDeclaredField("sql");
field.setAccessible(true);
field.set(boundSql, sql);
return invocation.proceed();
}
/**
* 判断是否包含需要加解密对象
*
* @param object 参数
* @return 结果
*/
private boolean needToDecrypt(Object object) {
Class<?> objectClass = object.getClass();
SensitiveEntity sensitiveEntity = AnnotationUtils.findAnnotation(objectClass, SensitiveEntity.class);
return Objects.nonNull(sensitiveEntity);
}
@Override
public Object plugin(Object target) {
return Interceptor.super.plugin(target);
}
@Override
public void setProperties(Properties properties) {
Interceptor.super.setProperties(properties);
}
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.Base64Utils;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* =====================================
* ****************开发部
* =====================================
*
* @author 开发者
* @version 1.0-SNAPSHOT
* @description
* @date 2023/2/10
*/
@Component
@Slf4j
public class AesFieldUtils {
/**
* 加密算法
*/
private final String KEY_ALGORITHM = "AES";
/**
* 算法/模式/补码方式
*/
private final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";
/**
* 编码格式
*/
private final String CODE = "utf-8";
/**
* base64验证规则
*/
private final String BASE64_RULE = "^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)$";
/**
* 正则验证对象
*/
private final Pattern PATTERN = Pattern.compile(BASE64_RULE);
/**
* 加解密 密钥key
*/
@Value("${encrypt.key}")
public static String encryptKey;
/**
* @param content 加密字符串
* @return 加密结果
*/
public String encrypt(String content) {
return encrypt(content, encryptKey);
}
/**
* 加密
*
* @param content 加密参数
* @param key 加密key
* @return 结果字符串
*/
public String encrypt(String content, String key) {
//判断如果已经是base64加密字符串则返回原字符串
if (isBase64(content)) {
return content;
}
byte[] encrypted = encrypt2bytes(content, key);
if (null == encrypted || encrypted.length < 1) {
log.info("加密字符串[{}]转字节为null", content);
return null;
}
return Base64Utils.encodeToString(encrypted);
}
/**
* @param content 加密字符串
* @param key 加密key
* @return 返回加密字节
*/
public byte[] encrypt2bytes(String content, String key) {
try {
byte[] raw = key.getBytes(CODE);
SecretKeySpec secretKeySpec = new SecretKeySpec(raw, KEY_ALGORITHM);
Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
return cipher.doFinal(content.getBytes(CODE));
} catch (Exception e) {
log.error("failed to encrypt: {} of {}", content, e);
return null;
}
}
/**
* @param content 加密字符串
* @return 返回加密结果
*/
public String decrypt(String content) {
try {
return decrypt(content, encryptKey);
} catch (Exception e) {
log.error("failed to decrypt: {}, e: {}", content, e);
return null;
}
}
/**
* 解密
*
* @param content 解密字符串
* @param key 解密key
* @return 解密结果
*/
public String decrypt(String content, String key) throws Exception {
//不是base64格式字符串则不进行解密
if (!isBase64(content)) {
return content;
}
return decrypt(Base64Utils.decodeFromString(content), key);
}
/**
* @param content 解密字节
* @param key 解密key
* @return 返回解密内容
*/
public String decrypt(byte[] content, String key) throws Exception {
if (key == null) {
log.error("AES key should not be null");
return null;
}
byte[] raw = key.getBytes(CODE);
SecretKeySpec keySpec = new SecretKeySpec(raw, KEY_ALGORITHM);
Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, keySpec);
try {
byte[] original = cipher.doFinal(content);
return new String(original, CODE);
} catch (Exception e) {
log.error("failed to decrypt content: {}/ key: {}, e: {}", content, key, e);
return null;
}
}
/**
* 判断是否为 base64加密
*
* @param str 参数
* @return 结果
*/
private boolean isBase64(String str) {
Matcher matcher = PATTERN.matcher(str);
return matcher.matches();
}
}
import *****************.SensitiveField;
import *****************.AesService;
import *****************.AesFieldUtils;
import *****************.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.util.Objects;
/**
* =====================================
* *****************开发部
* =====================================
*
* @author 开发者
* @version 1.0-SNAPSHOT
* @description
* @date 2023/2/10
*/
@Service
public class AesServiceImpl implements AesService {
@Value("${aes.key}")
private String key;
@Value("${aes.keyField}")
private String keyField;
@Resource
private AesFieldUtils aesFieldUtils;
@Override
public <T> T encrypt(Field[] declaredFields, T paramsObject) throws Exception {
for (Field field : declaredFields) {
//取出所有被EncryptDecryptField注解的字段
SensitiveField sensitiveField = field.getAnnotation(SensitiveField.class);
if (Objects.isNull(sensitiveField)) {
continue;
}
field.setAccessible(true);
Object object = field.get(paramsObject);
//暂时只实现String类型的加密
if (object instanceof String) {
String value = (String) object;
//如果映射字段值为空,并且以==结尾则跳过不进行加密
if (!StringUtils.noEmpty(value)) {
continue;
}
//加密 这里我使用自定义的AES加密工具
field.set(paramsObject, aesFieldUtils.encrypt(value, key));
}
}
return paramsObject;
}
@Override
public <T> T decrypt(T result) throws Exception {
//取出resultType的类
Class<?> resultClass = result.getClass();
Field[] declaredFields = resultClass.getDeclaredFields();
for (Field field : declaredFields) {
//取出所有被EncryptDecryptField注解的字段
SensitiveField sensitiveField = field.getAnnotation(SensitiveField.class);
if (Objects.isNull(sensitiveField)) {
continue;
}
field.setAccessible(true);
Object object = field.get(result);
//只支持String的解密
if (object instanceof String) {
String value = (String) object;
//如果映射字段值为空,并且不已==结尾则跳过不进行解密
if (!StringUtils.noEmpty(value)) {
continue;
}
//对注解的字段进行逐一解密
field.set(result, aesFieldUtils.decrypt(value, key));
}
}
return result;
}
@Override
public String replaceAll(String sql) {
if (sql.contains(keyField)) {
return sql.replaceAll(keyField, key);
}
return null;
}
}
import **************.SensitiveEntity;
import **************.SensitiveField;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.activerecord.Model;
@SensitiveEntity
@TableName("t_users")
public class Users extends Model<Users> {
/**
*
* This field was generated by MyBatis Generator.
* This field corresponds to the database column t_users.id
*
* @mbg.generated Wed Feb 01 10:03:44 CST 2023
*/
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
/**
*
* This field was generated by MyBatis Generator.
* This field corresponds to the database column t_users.user_id
*
* @mbg.generated Wed Feb 01 10:03:44 CST 2023
*/
private String userId;
/**
*
* This field was generated by MyBatis Generator.
* This field corresponds to the database column t_users.user_name
*
* @mbg.generated Wed Feb 01 10:03:44 CST 2023
*/
private String userName;
/**
*
* This field was generated by MyBatis Generator.
* This field corresponds to the database column t_users.nick_name
*
* @mbg.generated Wed Feb 01 10:03:44 CST 2023
*/
private String nickName;
/**
*
* This field was generated by MyBatis Generator.
* This field corresponds to the database column t_users.password
*
* @mbg.generated Wed Feb 01 10:03:44 CST 2023
*/
@SensitiveField
private String password;
/**
*
* This field was generated by MyBatis Generator.
* This field corresponds to the database column t_users.pwd_duration
*
* @mbg.generated Wed Feb 01 10:03:44 CST 2023
*/
private String pwdDuration;
@SensitiveField
private String birth;
}
本文原创:如有使用请标明出处
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev
我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当
我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss
我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢
这是一道面试题,我没有答对,但还是很好奇怎么解。你有N个人的大家庭,分别是1,2,3,...,N岁。你想给你的大家庭拍张照片。所有的家庭成员都排成一排。“我是家里的friend,建议家庭成员安排如下:”1岁的家庭成员坐在这一排的最左边。每两个坐在一起的家庭成员的年龄相差不得超过2岁。输入:整数N,1≤N≤55。输出:摄影师可以拍摄的照片数量。示例->输入:4,输出:4符合条件的数组:[1,2,3,4][1,2,4,3][1,3,2,4][1,3,4,2]另一个例子:输入:5输出:6符合条件的数组:[1,2,3,4,5][1,2,3,5,4][1,2,4,3,5][1,2,4,5,3][
我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden
我知道我可以指定某些字段来使用pluck查询数据库。ids=Item.where('due_at但是我想知道,是否有一种方法可以指定我想避免从数据库查询的某些字段。某种反拔?posts=Post.where(published:true).do_not_lookup(:enormous_field) 最佳答案 Model#attribute_names应该返回列/属性数组。您可以排除其中一些并传递给pluck或select方法。像这样:posts=Post.where(published:true).select(Post.attr
我已经构建了一些serverspec代码来在多个主机上运行一组测试。问题是当任何测试失败时,测试会在当前主机停止。即使测试失败,我也希望它继续在所有主机上运行。Rakefile:namespace:specdotask:all=>hosts.map{|h|'spec:'+h.split('.')[0]}hosts.eachdo|host|begindesc"Runserverspecto#{host}"RSpec::Core::RakeTask.new(host)do|t|ENV['TARGET_HOST']=hostt.pattern="spec/cfengine3/*_spec.r
我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI