<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          MyBatisPlus 又搞事情!發(fā)布神器,一個依賴輕松搞定權限問題!

          共 34206字,需瀏覽 69分鐘

           ·

          2022-06-20 11:49

          點擊關注公眾號,Java干貨及時送達

          作者:qq_39853669

          來源:blog.csdn.net/qq_39853669/article/details/124248022


          今天介紹一個 MyBatis - Plus 官方發(fā)布的神器:mybatis-mate 為 mp 企業(yè)級模塊,支持分庫分表,數據審計、數據敏感詞過濾(AC算法),字段加密,字典回寫(數據綁定),數據權限,表結構自動生成 SQL 維護等,旨在更敏捷優(yōu)雅處理數據

          1. 主要功能

          • 字典綁定
          • 字段加密
          • 數據脫敏
          • 表結構動態(tài)維護
          • 數據審計記錄
          • 數據范圍(數據權限)
          • 數據庫分庫分表、動態(tài)據源、讀寫分離、數- - 據庫健康檢查自動切換。

          2.使用

          2.1 依賴導入

          Spring Boot 引入自動依賴注解包

          <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-mate-starter</artifactId>
            <version>1.2.4</version>
          </dependency>

          注解(實體分包使用)

          <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-mate-annotation</artifactId>
            <version>1.2.4</version>
          </dependency>

          2.2 字段數據綁定(字典回寫)

          例如 user_sex 類型 sex 字典結果映射到 sexText 屬性

          @FieldDict(type = "user_sex", target = "sexText")
          private Integer sex;
          private String sexText;

          實現(xiàn) IDataDict 接口提供字典數據源,注入到 Spring 容器即可。

          @Component
          public class DataDict implements IDataDict {

              /**
               * 從數據庫或緩存中獲取
               */

              private Map<String, String> SEX_MAP = new ConcurrentHashMap<String, String>() {{
                  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 注解即可加密存儲,會自動解密查詢結果,支持全局配置加密密鑰算法,及注解密鑰算法,可以實現(xiàn) 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 執(zhí)行 resources 目錄 TestJson.http 文件測試
              @PostMapping("/json")
              public String json(@RequestBody Article article) throws Exception {
                  return ParamsConfig.toJson(article);
              }
          }

          2.5 DDL 數據結構自動維護

          解決升級表結構初始化,版本發(fā)布更新 SQL 維護問題,目前支持 MySql、PostgreSQL。

          @Component
          public class PostgresDdl implements IDdl {

              /**
               * 執(zhí)行 SQL 腳本方式
               */

              @Override
              public List<String> getSqlFiles() {
                  return Arrays.asList(
                          // 內置包方式
                          "db/tag-schema.sql",
                          // 文件絕對路徑方式
                          "D:\\db\\tag-data.sql"
                  );
              }
          }

          不僅僅可以固定執(zhí)行,也可以動態(tài)執(zhí)行?。?/p>

          ddlScript.run(new StringReader("DELETE FROM user;\n" +
                          "INSERT INTO user (id, username, password, sex, email) VALUES\n" +
                          "(20, 'Duo', '123456', 0, '[email protected]');"));

          它還支持多數據源執(zhí)行?。?!

          @Component
          public class MysqlDdl implements IDdl {
              @Override
              public void sharding(Consumer<IDdl> 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);
                      });
                  }
              }

              /**
               * 執(zhí)行 SQL 腳本方式
               */

              @Override
              public List<String> getSqlFiles() {
                  return Arrays.asList("db/user-mysql.sql");
              }
          }

          2.6 動態(tài)多數據源主從自由切換

          @Sharding 注解使數據源不限制隨意使用切換,你可以在 mapper 層添加注解,按需求指哪打哪??!

          @Mapper
          @Sharding("mysql")
          public interface UserMapper extends BaseMapper<User{
              @Sharding("postgres")
              Long selectByUsername(String username);
          }

          你也可以自定義策略統(tǒng)一調兵遣將

          @Component
          public class MyShardingStrategy extends RandomShardingStrategy {
              /**
               * 決定切換數據源 key {@link ShardingDatasource}
               *
               * @param group 動態(tài)數據庫組
               * @param invocation {@link Invocation}
               * @param sqlCommandType {@link SqlCommandType}
               */

              @Override
              public void determineDatasourceKey(String group, Invocation invocation, SqlCommandType sqlCommandType) {
                  // 數據源組 group 自定義選擇即可, keys 為數據源組內主從多節(jié)點,可隨機選擇或者自己控制
                  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 # 數據節(jié)點
                    ...

          2.7 分布式事務日志打印

          部分配置如下:

          /**
           * <p>
           * 性能分析攔截器,用于輸出每條 SQL 語句及其執(zhí)行時間
           * </p>
           */

          @Slf4j
          @Component
          @Intercepts({@Signature(type = StatementHandler.classmethod "query", args = {Statement.classResultHandler.class}),
                  @Signature(type 
          = StatementHandler.classmethod "update", args = {Statement.class}),
                  @Signature(type 
          = StatementHandler.classmethod "batch", args = {Statement.class})})
          public class PerformanceInterceptor implements Interceptor 
          {
              /**
               * SQL 執(zhí)行最大時長,超過自動停止運行,有助于發(fā)現(xiàn)問題。
               */

              private long maxTime = 0;
              /**
               * SQL 是否格式化
               */

              private boolean format = false;
              /**
               * 是否寫入日志文件<br>
               * true 寫入日志文件,不阻斷程序執(zhí)行!<br>
               * 超過設定的最大執(zhí)行時長異常提示!
               */

              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);
                  }

                  // 計算執(zhí)行 SQL 耗時
                  long start = SystemClock.now();
                  Object result = invocation.proceed();
                  long timing = SystemClock.now() - start;

                  // 格式化 SQL 打印執(zhí)行結果
                  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("\n Execute SQL:").append(sqlFormat(originalSql, format)).append("\n");
                  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<Integer> 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<Integer> 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 = {
                  // 關聯(lián)表 user 別名 u 指定部門字段權限
                  @DataColumn(alias = "u", name = "department_id"),
                  // 關聯(lián)表 user 別名 u 指定手機號字段(自己判斷處理)
                  @DataColumn(alias = "u", name = "mobile")
          })
          @Select("select u.* from user u")
          List<User> selectTestList(IPage<User> page, Long id, @Param("name") String username);

          模擬業(yè)務處理邏輯:

          @Bean
          public IDataScopeProvider dataScopeProvider() {
              return new AbstractDataScopeProvider() {
                  @Override
                  protected void setWhere(PlainSelect plainSelect, Object[] args, DataScopeProperty dataScopeProperty) {
                      // args 中包含 mapper 方法的請求參數,需要使用可以自行獲取
                      /*
                          // 測試數據權限,最終執(zhí)行 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())) {
                          // 業(yè)務 test 類型
                          List<DataColumnProperty> dataColumns = dataScopeProperty.getColumns();
                          for (DataColumnProperty dataColumn : dataColumns) {
                              if ("department_id".equals(dataColumn.getName())) {
                                  // 追加部門字段 IN 條件,也可以是 SQL 語句
                                  Set<String> 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));
                              }
                          }
                      }
                  }
              };
          }

          最終執(zhí)行 SQL 輸出:

          SELECT u.* FROM user u
            WHERE (u.department_id IN ('1''2''3''5'))
            AND u.mobile LIKE '%1533%' LIMIT 110

          目前僅有付費版本,了解更多 mybatis-mate 使用示例詳見:

          https://gitee.com/baomidou/mybatis-mate-examples

              

          1、拖動文件就能觸發(fā)7-Zip安全漏洞,波及所有版本

          2、進程切換的本質是什么?

          3、一次 SQL 查詢優(yōu)化原理分析:900W+ 數據,從 17s 到 300ms

          4、Redis數據結構為什么既省內存又高效?

          5、IntelliJ IDEA快捷鍵大全 + 動圖演示

          6、全球第三瀏覽器,封殺中國用戶這種操作!

          點在看

          瀏覽 46
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  91丨国产丨精品丨丝袜 | 日韩人妻无码一级毛片欧美 | 日本黄色大片日本美女 | 偷拍自拍第五页 | 热久久a 人妻3区 |