<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>

          mybatis-plus 官方發(fā)布神器,一個依賴輕松搞定數(shù)據(jù)權(quán)限,再也不用自己實(shí)現(xiàn)了!

          共 34592字,需瀏覽 70分鐘

           ·

          2021-11-08 14:25

          點(diǎn)擊關(guān)注公眾號,回復(fù)“2T”獲取2TB學(xué)習(xí)資源!

          互聯(lián)網(wǎng)架構(gòu)師后臺回復(fù) 2T 有特別禮包

          上一篇:杭州程序員從互聯(lián)網(wǎng)跳央企,曬一天工作和收入,網(wǎng)友:待一年就廢

          簡介

          mybatis-mate 為 mp 企業(yè)級模塊,支持分庫分表,數(shù)據(jù)審計(jì)、數(shù)據(jù)敏感詞過濾(AC算法),字段加密,字典回寫(數(shù)據(jù)綁定),數(shù)據(jù)權(quán)限,表結(jié)構(gòu)自動生成 SQL 維護(hù)等,旨在更敏捷優(yōu)雅處理數(shù)據(jù)。

          主要功能

          2、使用

          2.1 依賴導(dǎo)入

          Spring Boot 引入自動依賴注解包

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

          注解(實(shí)體分包使用)

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

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

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

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

          private String sexText;

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

          信搜索互聯(lián)網(wǎng)架構(gòu)師,在后臺發(fā)送:2T,可以在線閱讀。

          @Component
          public class DataDict implements IDataDict {

              /**
               * 從數(shù)據(jù)庫或緩存中獲取
               */
              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 注解即可加密存儲,會自動解密查詢結(jié)果,支持全局配置加密密鑰算法,及注解密鑰算法,可以實(shí)現(xiàn) IEncryptor 注入自定義算法。

          @FieldEncrypt(algorithm = Algorithm.PBEWithMD5AndDES)
          private String password;

          2.4 字段脫敏

          屬性 @FieldSensitive 注解即可自動按照預(yù)設(shè)策略對源數(shù)據(jù)進(jìn)行脫敏處理,默認(rèn) SensitiveType 內(nèi)置 9 種常用脫敏策略。

          例如:中文名、銀行卡賬號、手機(jī)號碼等 脫敏策略。

          也可以自定義策略如下:

          @FieldSensitive(type = "testStrategy")
          private String username;

          @FieldSensitive(type = SensitiveType.mobile)
          private String mobile;

          自定義脫敏策略 testStrategy 添加到默認(rèn)策略中注入 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;

              // 測試訪問下面地址觀察請求地址、界面返回?cái)?shù)據(jù)及控制臺( 普通參數(shù) )
              // 無敏感詞 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參數(shù) )
              // idea 執(zhí)行 resources 目錄 TestJson.http 文件測試
              @PostMapping("/json")
              public String json(@RequestBody Article article) throws Exception {
                  return ParamsConfig.toJson(article);
              }
          }

          2.5 DDL 數(shù)據(jù)結(jié)構(gòu)自動維護(hù)

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

          信搜索互聯(lián)網(wǎng)架構(gòu)師,在后臺發(fā)送:2T,可以在線閱讀。

          @Component
          public class PostgresDdl implements IDdl {

              /**
               * 執(zhí)行 SQL 腳本方式
               */
              @Override
              public List<String> getSqlFiles() {
                  return Arrays.asList(
                          // 內(nèi)置包方式
                          "db/tag-schema.sql",
                          // 文件絕對路徑方式
                          "D:\\db\\tag-data.sql"
                  );
              }
          }

          不僅僅可以固定執(zhí)行,也可以動態(tài)執(zhí)行!!

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

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

          @Component
          public class MysqlDdl implements IDdl {

              @Override
              public void sharding(Consumer<IDdl> consumer) {
                  // 多數(shù)據(jù)源指定,主庫初始化從庫自動同步
                  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)多數(shù)據(jù)源主從自由切換

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

          @Mapper
          @Sharding("mysql")
          public interface UserMapper extends BaseMapper<User> {

              @Sharding("postgres")
              Long selectByUsername(String username);
          }

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

          @Component
          public class MyShardingStrategy extends RandomShardingStrategy {

              /**
               * 決定切換數(shù)據(jù)源 key {@link ShardingDatasource}
               *
               * @param group          動態(tài)數(shù)據(jù)庫組
               * @param invocation     {@link Invocation}
               * @param sqlCommandType {@link SqlCommandType}
               */
              @Override
              public void determineDatasourceKey(String group, Invocation invocation, SqlCommandType sqlCommandType) {
                  // 數(shù)據(jù)源組 group 自定義選擇即可, keys 為數(shù)據(jù)源組內(nèi)主從多節(jié)點(diǎn),可隨機(jī)選擇或者自己控制
                  this.changeDatabaseKey(group, sqlCommandType, keys -> chooseKey(keys, invocation));
              }
          }

          可以開啟主從策略,當(dāng)然也是可以開啟健康檢查?。。?/p>

          具體配置:

          mybatis-mate:
            sharding:
              health: true # 健康檢測
              primary: mysql # 默認(rèn)選擇數(shù)據(jù)源
              datasource:
                mysql: # 數(shù)據(jù)庫組
                  - key: node1
                    ...
                  - key: node2
                    cluster: slave # 從庫讀寫分離時候負(fù)責(zé) sql 查詢操作,主庫 master 默認(rèn)可以不寫
                    ...
                postgres:
                  - key: node1 # 數(shù)據(jù)節(jié)點(diǎn)
                    ...

          2.7 分布式事務(wù)日志打印

          部分配置如下:

          /**
           * <p>
           * 性能分析攔截器,用于輸出每條 SQL 語句及其執(zhí)行時間
           * </p>
           */
          @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 執(zhí)行最大時長,超過自動停止運(yùn)行,有助于發(fā)現(xiàn)問題。
               */
              private long maxTime = 0;
              /**
               * SQL 是否格式化
               */
              private boolean format = false;
              /**
               * 是否寫入日志文件<br>
               * true 寫入日志文件,不阻斷程序執(zhí)行!<br>
               * 超過設(shè)定的最大執(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);
                  }

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

                  // 格式化 SQL 打印執(zhí)行結(jié)果
                  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;

              // 數(shù)據(jù)庫 test 表 t_order 在事務(wù)一致情況無法插入數(shù)據(jù),能夠插入說明多數(shù)據(jù)源事務(wù)無效
              // 測試訪問 http://localhost:8080/test
              // 制造事務(wù)回滾 http://localhost:8080/test?error=true 也可通過修改表結(jié)構(gòu)制造錯誤
              // 注釋 ShardingConfig 注入 dataSourceProvider 可測試事務(wù)無效情況
              @GetMapping("/test")
              public String test(Boolean error) {
                  return buyService.buy(null != error && error);
              }
          }

          2.8 數(shù)據(jù)權(quán)限

          mapper 層添加注解:

          信搜索互聯(lián)網(wǎng)架構(gòu)師,在后臺發(fā)送:2T,可以在線閱讀。

          // 測試 test 類型數(shù)據(jù)權(quán)限范圍,混合分頁模式
          @DataScope(type = "test", value = {
                  // 關(guān)聯(lián)表 user 別名 u 指定部門字段權(quán)限
                  @DataColumn(alias = "u", name = "department_id"),
                  // 關(guān)聯(lián)表 user 別名 u 指定手機(jī)號字段(自己判斷處理)
                  @DataColumn(alias = "u", name = "mobile")
          })
          @Select("select u.* from user u")
          List<User> selectTestList(IPage<User> page, Long id, @Param("name") String username);

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

          @Bean
          public IDataScopeProvider dataScopeProvider() {
              return new AbstractDataScopeProvider() {
                  @Override
                  protected void setWhere(PlainSelect plainSelect, Object[] args, DataScopeProperty dataScopeProperty) {
                      // args 中包含 mapper 方法的請求參數(shù),需要使用可以自行獲取
                      /*
                          // 測試數(shù)據(jù)權(quán)限,最終執(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è)務(wù) 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 1, 10

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

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

          感謝您的閱讀,也歡迎您發(fā)表關(guān)于這篇文章的任何建議,關(guān)注我,技術(shù)不迷茫!小編到你上高速。

              · END ·
          最后,關(guān)注公眾號互聯(lián)網(wǎng)架構(gòu)師,在后臺回復(fù):2T,可以獲取我整理的 Java 系列面試題和答案,非常齊全。


          正文結(jié)束


          推薦閱讀 ↓↓↓

          1.不認(rèn)命,從10年流水線工人,到谷歌上班的程序媛,一位湖南妹子的勵志故事

          2.深圳一普通中學(xué)老師工資單曝光,秒殺程序員,網(wǎng)友:敢問是哪個學(xué)校畢業(yè)的?

          3.從零開始搭建創(chuàng)業(yè)公司后臺技術(shù)棧

          4.程序員一般可以從什么平臺接私活?

          5.清華大學(xué):2021 元宇宙研究報告!

          6.為什么國內(nèi) 996 干不過國外的 955呢?

          7.這封“領(lǐng)導(dǎo)痛批95后下屬”的郵件,句句扎心!

          8.15張圖看懂瞎忙和高效的區(qū)別!


          瀏覽 48
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <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>
                  日韩mv欧美mv国产网站 | 波多野结衣精品在线 | 人妻少妇嫩草AV无码 | 色色网的五月天 | 在线免费看黄色片 |