范文健康探索娱乐情感热点
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

mybatismate轻松搞定数据权限

  0、简介
  mybatis-mate 为 mp 企业级模块,支持分库分表,数据审计、数据敏感词过滤(AC算法),字段加密,字典回写(数据绑定),数据权限,表结构自动生成 SQL 维护等,旨在更敏捷优雅处理数据。  1、主要功能字典绑定  字段加密  数据脱敏  表结构动态维护  数据审计记录  数据范围(数据权限)  数据库分库分表、动态数据源、读写分离、数据库健康检查自动切换。  2、使用2.1 依赖导入
  Spring Boot 引入自动依赖注解包     com.baomidou   mybatis-mate-starter   1.0.8 
  注解(实体分包使用)     com.baomidou   mybatis-mate-annotation   1.0.8  2.2 字段数据绑定(字典回写)
  例如 user_sex 类型 sex 字典结果映射到 sexText 属性  @FieldDict(type = "user_sex", target = "sexText") private Integer sex;  private String sexText;
  实现 IDataDict 接口提供字典数据源,注入到 Spring 容器即可。  @Component public class DataDict implements IDataDict {      /**      * 从数据库或缓存中获取      */     private Map SEX_MAP = new ConcurrentHashMap() {{         put("0", "女");         put("1", "男");     }};      @Override     public String getNameByCode(FieldDict fieldDict, String code) {         System.err.println("字段类型:" + fieldDict.type() + ",编码:" + code);         return SEX_MAP.get(code);     } } 2.3 字段加密
  属性 @FieldEncrypt 注解即可加密存储,会自动解密查询结果,支持全局配置加密密钥算法,及注解密钥算法,可以实现 IEncryptor 注入自定义算法。  @FieldEncrypt(algorithm = Algorithm.PBEWithMD5AndDES) private String password; 2.4 字段脱敏
  属性 @FieldSensitive 注解即可自动按照预设策略对源数据进行脱敏处理,默认 SensitiveType 内置 9 种常用脱敏策略。
  例如:中文名、银行卡账号、手机号码等 脱敏策略。
  也可以自定义策略如下:  @FieldSensitive(type = "testStrategy") private String username;  @FieldSensitive(type = SensitiveType.mobile) private String mobile;
  自定义脱敏策略 testStrategy 添加到默认策略中注入 Spring 容器即可。  @Configuration public class SensitiveStrategyConfig {      /**      * 注入脱敏策略      */     @Bean     public ISensitiveStrategy sensitiveStrategy() {         // 自定义 testStrategy 类型脱敏处理         return new SensitiveStrategy().addStrategy("testStrategy", t -> t + "***test***");     } }
  例如文章敏感词过滤  /**  * 演示文章敏感词过滤  */ @RestController public class ArticleController {     @Autowired     private SensitiveWordsMapper sensitiveWordsMapper;      // 测试访问下面地址观察请求地址、界面返回数据及控制台( 普通参数 )     // 无敏感词 http://localhost:8080/info?content=tom&see=1&age=18     // 英文敏感词 http://localhost:8080/info?content=my%20content%20is%20tomcat&see=1&age=18     // 汉字敏感词 http://localhost:8080/info?content=%E7%8E%8B%E5%AE%89%E7%9F%B3%E5%94%90%E5%AE%8B%E5%85%AB%E5%A4%A7%E5%AE%B6&see=1     // 多个敏感词 http://localhost:8080/info?content=%E7%8E%8B%E5%AE%89%E7%9F%B3%E6%9C%89%E4%B8%80%E5%8F%AA%E7%8C%ABtomcat%E6%B1%A4%E5%A7%86%E5%87%AF%E7%89%B9&see=1&size=6     // 插入一个字变成非敏感词 http://localhost:8080/info?content=%E7%8E%8B%E7%8C%AB%E5%AE%89%E7%9F%B3%E6%9C%89%E4%B8%80%E5%8F%AA%E7%8C%ABtomcat%E6%B1%A4%E5%A7%86%E5%87%AF%E7%89%B9&see=1&size=6     @GetMapping("/info")     public String info(Article article) throws Exception {         return ParamsConfig.toJson(article);     }       // 添加一个敏感词然后再去观察是否生效 http://localhost:8080/add     // 观察【猫】这个词被过滤了 http://localhost:8080/info?content=%E7%8E%8B%E5%AE%89%E7%9F%B3%E6%9C%89%E4%B8%80%E5%8F%AA%E7%8C%ABtomcat%E6%B1%A4%E5%A7%86%E5%87%AF%E7%89%B9&see=1&size=6     // 嵌套敏感词处理 http://localhost:8080/info?content=%E7%8E%8B%E7%8C%AB%E5%AE%89%E7%9F%B3%E6%9C%89%E4%B8%80%E5%8F%AA%E7%8C%ABtomcat%E6%B1%A4%E5%A7%86%E5%87%AF%E7%89%B9&see=1&size=6     // 多层嵌套敏感词 http://localhost:8080/info?content=%E7%8E%8B%E7%8E%8B%E7%8C%AB%E5%AE%89%E7%9F%B3%E5%AE%89%E7%9F%B3%E6%9C%89%E4%B8%80%E5%8F%AA%E7%8C%ABtomcat%E6%B1%A4%E5%A7%86%E5%87%AF%E7%89%B9&see=1&size=6     @GetMapping("/add")     public String add() throws Exception {         Long id = 3L;         if (null == sensitiveWordsMapper.selectById(id)) {             System.err.println("插入一个敏感词:" + sensitiveWordsMapper.insert(new SensitiveWords(id, "猫")));             // 插入一个敏感词,刷新算法引擎敏感词             SensitiveWordsProcessor.reloadSensitiveWords();         }         return "ok";     }      // 测试访问下面地址观察控制台( 请求json参数 )     // idea 执行 resources 目录 TestJson.http 文件测试     @PostMapping("/json")     public String json(@RequestBody Article article) throws Exception {         return ParamsConfig.toJson(article);     } } 2.5 DDL 数据结构自动维护
  解决升级表结构初始化,版本发布更新 SQL 维护问题,目前支持 MySql、PostgreSQL。  @Component public class PostgresDdl implements IDdl {      /**      * 执行 SQL 脚本方式      */     @Override     public List getSqlFiles() {         return Arrays.asList(                 // 内置包方式                 "db/tag-schema.sql",                 // 文件绝对路径方式                 "D:dbtag-data.sql"         );     } }
  不仅仅可以固定执行,也可以动态执行!!  ddlScript.run(new StringReader("DELETE FROM user; " +                 "INSERT INTO user (id, username, password, sex, email) VALUES " +                 "(20, "Duo", "123456", 0, "Duo@baomidou.com");"));
  它还支持多种数据源执行!!!  @Component public class MysqlDdl implements IDdl {      @Override     public void sharding(Consumer consumer) {         // 多数据源指定,主库初始化从库自动同步         String group = "mysql";         ShardingGroupProperty sgp = ShardingKey.getDbGroupProperty(group);         if (null != sgp) {             // 主库             sgp.getMasterKeys().forEach(key -> {                 ShardingKey.change(group + key);                 consumer.accept(this);             });             // 从库             sgp.getSlaveKeys().forEach(key -> {                 ShardingKey.change(group + key);                 consumer.accept(this);             });         }     }      /**      * 执行 SQL 脚本方式      */     @Override     public List getSqlFiles() {         return Arrays.asList("db/user-mysql.sql");     } } 2.6 动态多数据源主从自由切换
  @Sharding 注解使数据源不限制随意使用切换,你可以在 mapper 层添加注解,按需求指哪打哪!!  @Mapper @Sharding("mysql") public interface UserMapper extends BaseMapper {      @Sharding("postgres")     Long selectByUsername(String username); }
  你也可以自定义策略统一调兵遣将  @Component public class MyShardingStrategy extends RandomShardingStrategy {      /**      * 决定切换数据源 key {@link ShardingDatasource}      *      * @param group          动态数据库组      * @param invocation     {@link Invocation}      * @param sqlCommandType {@link SqlCommandType}      */     @Override     public void determineDatasourceKey(String group, Invocation invocation, SqlCommandType sqlCommandType) {         // 数据源组 group 自定义选择即可, keys 为数据源组内主从多节点,可随机选择或者自己控制         this.changeDatabaseKey(group, sqlCommandType, keys -> chooseKey(keys, invocation));     } }
  可以开启主从策略,当然也是可以开启健康检查!!!
  具体配置:  mybatis-mate:   sharding:     health: true # 健康检测     primary: mysql # 默认选择数据源     datasource:       mysql: # 数据库组         - key: node1           ...         - key: node2           cluster: slave # 从库读写分离时候负责 sql 查询操作,主库 master 默认可以不写           ...       postgres:         - key: node1 # 数据节点           ... 2.7 分布式事务日志打印
  部分配置如下:  /**  * 

* 性能分析拦截器,用于输出每条 SQL 语句及其执行时间 * */ @Slf4j @Component @Intercepts({@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}), @Signature(type = StatementHandler.class, method = "update", args = {Statement.class}), @Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})}) public class PerformanceInterceptor implements Interceptor { /** * SQL 执行最大时长,超过自动停止运行,有助于发现问题。 */ private long maxTime = 0; /** * SQL 是否格式化 */ private boolean format = false; /** * 是否写入日志文件   * true 写入日志文件,不阻断程序执行!   * 超过设定的最大执行时长异常提示! */ private boolean writeInLog = false; @Override public Object intercept(Invocation invocation) throws Throwable { Statement statement; Object firstArg = invocation.getArgs()[0]; if (Proxy.isProxyClass(firstArg.getClass())) { statement = (Statement) SystemMetaObject.forObject(firstArg).getValue("h.statement"); } else { statement = (Statement) firstArg; } MetaObject stmtMetaObj = SystemMetaObject.forObject(statement); try { statement = (Statement) stmtMetaObj.getValue("stmt.statement"); } catch (Exception e) { // do nothing } if (stmtMetaObj.hasGetter("delegate")) {//Hikari try { statement = (Statement) stmtMetaObj.getValue("delegate"); } catch (Exception e) { } } String originalSql = null; if (originalSql == null) { originalSql = statement.toString(); } originalSql = originalSql.replaceAll("[s]+", " "); int index = indexOfSqlStart(originalSql); if (index > 0) { originalSql = originalSql.substring(index); } // 计算执行 SQL 耗时 long start = SystemClock.now(); Object result = invocation.proceed(); long timing = SystemClock.now() - start; // 格式化 SQL 打印执行结果 Object target = PluginUtils.realTarget(invocation.getTarget()); MetaObject metaObject = SystemMetaObject.forObject(target); MappedStatement ms = (MappedStatement) metaObject.getValue("delegate.mappedStatement"); StringBuilder formatSql = new StringBuilder(); formatSql.append(" Time:").append(timing); formatSql.append(" ms - ID:").append(ms.getId()); formatSql.append(" Execute SQL:").append(sqlFormat(originalSql, format)).append(" "); if (this.isWriteInLog()) { if (this.getMaxTime() >= 1 && timing > this.getMaxTime()) { log.error(formatSql.toString()); } else { log.debug(formatSql.toString()); } } else { System.err.println(formatSql); if (this.getMaxTime() >= 1 && timing > this.getMaxTime()) { throw new RuntimeException(" The SQL execution time is too large, please optimize ! "); } } return result; } @Override public Object plugin(Object target) { if (target instanceof StatementHandler) { return Plugin.wrap(target, this); } return target; } @Override public void setProperties(Properties prop) { String maxTime = prop.getProperty("maxTime"); String format = prop.getProperty("format"); if (StringUtils.isNotEmpty(maxTime)) { this.maxTime = Long.parseLong(maxTime); } if (StringUtils.isNotEmpty(format)) { this.format = Boolean.valueOf(format); } } public long getMaxTime() { return maxTime; } public PerformanceInterceptor setMaxTime(long maxTime) { this.maxTime = maxTime; return this; } public boolean isFormat() { return format; } public PerformanceInterceptor setFormat(boolean format) { this.format = format; return this; } public boolean isWriteInLog() { return writeInLog; } public PerformanceInterceptor setWriteInLog(boolean writeInLog) { this.writeInLog = writeInLog; return this; } public Method getMethodRegular(Class<?> clazz, String methodName) { if (Object.class.equals(clazz)) { return null; } for (Method method : clazz.getDeclaredMethods()) { if (method.getName().equals(methodName)) { return method; } } return getMethodRegular(clazz.getSuperclass(), methodName); } /** * 获取sql语句开头部分 * * @param sql * @return */ private int indexOfSqlStart(String sql) { String upperCaseSql = sql.toUpperCase(); Set set = new HashSet<>(); set.add(upperCaseSql.indexOf("SELECT ")); set.add(upperCaseSql.indexOf("UPDATE ")); set.add(upperCaseSql.indexOf("INSERT ")); set.add(upperCaseSql.indexOf("DELETE ")); set.remove(-1); if (CollectionUtils.isEmpty(set)) { return -1; } List list = new ArrayList<>(set); Collections.sort(list, Integer::compareTo); return list.get(0); } private final static SqlFormatter sqlFormatter = new SqlFormatter(); /** * 格式sql * * @param boundSql * @param format * @return */ public static String sqlFormat(String boundSql, boolean format) { if (format) { try { return sqlFormatter.format(boundSql); } catch (Exception ignored) { } } return boundSql; } }   使用: @RestController @AllArgsConstructor public class TestController { private BuyService buyService; // 数据库 test 表 t_order 在事务一致情况无法插入数据,能够插入说明多数据源事务无效 // 测试访问 http://localhost:8080/test // 制造事务回滚 http://localhost:8080/test?error=true 也可通过修改表结构制造错误 // 注释 ShardingConfig 注入 dataSourceProvider 可测试事务无效情况 @GetMapping("/test") public String test(Boolean error) { return buyService.buy(null != error && error); } } 2.8 数据权限   mapper 层添加注解: // 测试 test 类型数据权限范围,混合分页模式 @DataScope(type = "test", value = { // 关联表 user 别名 u 指定部门字段权限 @DataColumn(alias = "u", name = "department_id"), // 关联表 user 别名 u 指定手机号字段(自己判断处理) @DataColumn(alias = "u", name = "mobile") }) @Select("select u.* from user u") List selectTestList(IPage page, Long id, @Param("name") String username);   模拟业务处理逻辑: @Bean public IDataScopeProvider dataScopeProvider() { return new AbstractDataScopeProvider() { @Override protected void setWhere(PlainSelect plainSelect, Object[] args, DataScopeProperty dataScopeProperty) { // args 中包含 mapper 方法的请求参数,需要使用可以自行获取 /* // 测试数据权限,最终执行 SQL 语句 SELECT u.* FROM user u WHERE (u.department_id IN ("1", "2", "3", "5")) AND u.mobile LIKE "%1533%" */ if ("test".equals(dataScopeProperty.getType())) { // 业务 test 类型 List dataColumns = dataScopeProperty.getColumns(); for (DataColumnProperty dataColumn : dataColumns) { if ("department_id".equals(dataColumn.getName())) { // 追加部门字段 IN 条件,也可以是 SQL 语句 Set deptIds = new HashSet<>(); deptIds.add("1"); deptIds.add("2"); deptIds.add("3"); deptIds.add("5"); ItemsList itemsList = new ExpressionList(deptIds.stream().map(StringValue::new).collect(Collectors.toList())); InExpression inExpression = new InExpression(new Column(dataColumn.getAliasDotName()), itemsList); if (null == plainSelect.getWhere()) { // 不存在 where 条件 plainSelect.setWhere(new Parenthesis(inExpression)); } else { // 存在 where 条件 and 处理 plainSelect.setWhere(new AndExpression(plainSelect.getWhere(), inExpression)); } } else if ("mobile".equals(dataColumn.getName())) { // 支持一个自定义条件 LikeExpression likeExpression = new LikeExpression(); likeExpression.setLeftExpression(new Column(dataColumn.getAliasDotName())); likeExpression.setRightExpression(new StringValue("%1533%")); plainSelect.setWhere(new AndExpression(plainSelect.getWhere(), likeExpression)); } } } } }; }   最终执行 SQL 输出: SELECT u.* FROM user u WHERE (u.department_id IN ("1", "2", "3", "5")) AND u.mobile LIKE "%1533%" LIMIT 1, 10   目前仅有付费版本,了解更多 mybatis-mate 使用示例详见:   https://gitee.com/baomidou/mybatis-mate-examples   原文链接:https://mp.weixin.qq.com/s/ygzsFpeT2jsjcAgUseHLQg


第十一代思域vs广本INTEGRA型格居家or运动在第十一代思域上市之前,网友们已经为其外饰造型争论不休,主要原因还是第十代思域在中国市场取得了前所未有的成功,不仅销量上成为东本的拳头产品,更是让思域车主挑战所有600万一下跑车这短试第二代CS55PLUS以小UNIK之势,赶超博越长安CS55上市至今已经4年多时间,其功劳不仅仅是带来了近50万辆的销量支撑,更是让长安汽车在SUV产品线上的价格体系得到稳固和强化,同时让长安汽车通过这款车争取到更多年轻用户的喜帝豪L官图首发撞衫没所谓,有料才重要?在今年成都车展上,吉利汽车刚刚首发亮相了基于能量风暴新设计风格打造的首款SUV博越X,没想到的是,没过几天,吉利就通过网络发布了帝豪L的官图。从第一眼视觉感受来看,帝豪L也用上了与探店凡尔赛C5X14万起步,值得一试?探店雪铁龙凡尔赛C5X之前,一位小伙伴向我吐槽法系车在个性化市场理应具备最多优势(合资品牌里),从DS到标致508L,法国人将一手好牌打得稀烂不说,像标致508L加长不仅需要单独开盲目乐观的定价,嘉华注定一上市就失败本周大家最关注的新车应该是小鹏P5,可能只有小部分MPV潜在买家还在关注东风悦达起亚嘉华的正式售价。不过,当第四代嘉华的价格亮出时,引起业内的一片哗然,大多数意见认为东风悦达起亚第9月销量破5。6万,试过特斯拉ModelY后我却拔草了从数据上看,特斯拉中国在9月份新车销量达到56006辆,不仅同比增幅高达394,同时ModelY更是力压哈弗H6,登顶9月份SUV销量榜。实际上,在销量数据公布之前,也就是国庆假期以颤抖之身追赶,怀敬畏之心挑战vivoTWS2主动降噪真无线耳机如果vivo是个棋手的话,他一定是那种落子之前很沉得住气的类型,往好了说,不浮躁,往坏了说有点急人。明明是一个从创立品牌之初就很重视音频的品牌,明明Xplay5s是许多人心目中的手少数派报告森海塞尔HD250BT蓝牙便携耳机记得十几年前,我刚去北京那会儿,坐北京的出租车,都爱跟司机神侃,北京的哥能侃那是全国闻名的,号称手里掖着一副牌,谁上车跟谁来,天南海北上到中央下到地方就没他不知道的,虽然聊得比较八LGStyler衣物护理机,除菌除皱褶样样精通,商务人士的制胜法宝明天要出席一个重要场合,拿出西装一看上面居然有皱褶,挤出时间现熨烫吧,还真是挺费劲的。遇到这种情况是不是特别烦恼?如果你曾有过以上的情况,那么一定要准备一款LGStyler衣物护理抽奖中了扫地机器人,别掉坑!UONI由利A1Pro才是最佳选择哈哈,抽中一等奖了,奖品是扫地机器人,原价2680元现在只要380就能领取!对于这样的天降横财,是不是很多人都比较兴奋?在我们大多数人的印象中,扫地机高达2000多元,一下子减少这上下班乘车时间长,如何利用?哈氪拾光Pro主动降噪,给你清净为了生活,很多人上班需要乘坐很长时间的公交。如何利用这段时间?今天笔者就教大家一个好方法!准备一款降噪耳机,戴上后可以隔绝大部分人声以及机器声,不管是欣赏音乐还是小憩一会都不会受到
宝马发布i4iX量产车型!高性能长续航合二为一,还有M系首款电车作者JimmyMa来源极果编辑部现在电动车新势力们都折腾的风生水起,传统车企怎么能坐得住?从大众的ID。3ID。4奥迪的etronetronGT到纯电的悍马猛禽。如果再不推出几款有为何其他品牌手机暂时无法搭载鸿蒙系统?相信终会争着来适配的很多除华为之外的手机用户,也表现出对鸿蒙系统的浓厚兴趣,并纷纷表示希望在自己手机上使用鸿蒙系统。除了荣耀在从华为分出之前的部分老机型手机,华为还要负责适配提供鸿蒙系统升级选项之外,斯诺登的大礼包曝光美监听后,意大利松口华为5G根据路透社上月20日消息称,意大利已经同意沃达丰在意大利的分公司安装5G设备,目前沃达丰与华为对此均没有做出任何回应。但是消息显示,此次意大利同意华为安装设备的前提条件很多,包括完看完岚图FREE的高效增程谁还记得48V轻混近日某知名汽车媒体以头条的形式宣告了自家调查结果48V轻混在政策及市场上的前景堪忧。作为普及新能源车型的又一种技术,48V的混动技术似乎也没有完成历史赋予的使命。时下就没有兼顾性能鸿蒙,来了!我已尝鲜大众报业大众日报客户端202106031548476月2日晚,被称为划时代系统的华为鸿蒙操作系统正式上线!这意味着鸿蒙手机已经变成面向市场的正式产品。鸿蒙操作系统是一款面向全场景的华为P50很不一样!但余承东无奈道上市时间不确定,努力想办法按照华为以往的正常惯例,华为P系列手机都将会在3月份发布,而华为P50系列到了6月1号都还没有官方消息,已经延迟两个多月了,不由得让人往坏方向想华为P系列还会有吗?就在6月2号鸿蒙荣耀X20外观曝光后置三摄设计或将主打轻薄游戏机型荣耀官方已经决定将于6月16日召开新品发布会,推出新一代旗舰机型荣耀50系列,而在旗舰机型之外,荣耀还将一同推出主打游戏用户的手机荣耀X20。据微博数码博主数码闲聊站爆料,荣耀官方加密货币交易所Gate因漏洞造成损失,原比特儿注册主体智数近日拟注销,提供法币借贷OTC业务6月3日晚间,加密货币交易所Gate发布声明,因SUN拆分后被按原价抵押借贷,造成9。6万USDT未归还。事件相关代币SUN属孙宇晨发起项目,自2020年9月启动挖矿。据说明公告,无视频,不生活!探馆网络视听新技术新感受来源人民网原创稿人民网成都6月4日电(记者李彤)6月3日至5日,第九届中国网络视听大会在成都举办。会议首次设立了高新视听技术体验展,划分为互动娱乐展区5G视听展区智能影音展区三大区蚂蚁消金获批开业,承接花呗借呗业务移动支付网消息6月3日消息,重庆银保监局发布关于重庆蚂蚁消费金融有限公司(以下简称蚂蚁消金)开业的批复,正式批准其开业。据移动支付网了解,2020年8月21日,蚂蚁集团与其他投资者iOS14。7beta2发布,升级需谨慎苹果凌晨1点推送了iOS14。7beta2版本,同时苹果还推送了tvOS14。7watchOS7。6beta2版本。这次iOS14。7beta2主要带来了哪些新功能?iOS14。7