此組件解決的問題是:java
「誰」在「什麼時間」對「什麼」作了「什麼事」git
本組件目前針對 Spring-boot 作了 Autoconfig,若是是 SpringMVC,也可本身在 xml 初始化 bean
<dependency> <groupId>io.github.mouzt</groupId> <artifactId>bizlog-sdk</artifactId> <version>1.0.1</version> </dependency>
tenant是表明租戶的標識,通常一個服務或者一個業務下的多個服務都寫死一個 tenant 就能夠github
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class) @EnableTransactionManagement @EnableLogRecord(tenant = "com.mzt.test") public class Main { public static void main(String[] args) { SpringApplication.run(Main.class, args); } }
Spring Boot 最新教程推薦看下面這個。web
https://github.com/javastacks...面試
@LogRecordAnnotation(success = "{{#order.purchaseName}}下了一個訂單,購買商品「{{#order.productName}}」,下單結果:{{#_ret}}", prefix = LogRecordType.ORDER, bizNo = "{{#order.orderNo}}") public boolean createOrder(Order order) { log.info("【建立訂單】orderNo={}", order.getOrderNo()); // db insert order return true; }
此時會打印操做日誌 「張三下了一個訂單,購買商品「超值優惠紅燒肉套餐」,下單結果:true」spring
@LogRecordAnnotation( fail = "建立訂單失敗,失敗緣由:「{{#_errorMsg}}」", success = "{{#order.purchaseName}}下了一個訂單,購買商品「{{#order.productName}}」,下單結果:{{#_ret}}", prefix = LogRecordType.ORDER, bizNo = "{{#order.orderNo}}") public boolean createOrder(Order order) { log.info("【建立訂單】orderNo={}", order.getOrderNo()); // db insert order return true; }
其中的 #_errorMsg 是取的方法拋出異常後的異常的 errorMessage。數據庫
好比一個訂單的操做日誌,有些操做日誌是用戶本身操做的,有些操做是系統運營人員作了修改產生的操做日誌,咱們系統不但願把運營的操做日誌暴露給用戶看到,mvc
可是運營指望能夠看到用戶的日誌以及運營本身操做的日誌,這些操做日誌的bizNo都是訂單號,因此爲了擴展添加了類型字段,主要是爲了對日誌作分類,查詢方便,支持更多的業務。intellij-idea
@LogRecordAnnotation( fail = "建立訂單失敗,失敗緣由:「{{#_errorMsg}}」", category = "MANAGER", success = "{{#order.purchaseName}}下了一個訂單,購買商品「{{#order.productName}}」,下單結果:{{#_ret}}", prefix = LogRecordType.ORDER, bizNo = "{{#order.orderNo}}") public boolean createOrder(Order order) { log.info("【建立訂單】orderNo={}", order.getOrderNo()); // db insert order return true; }
若是一個操做修改了不少字段,可是success的日誌模版裏面防止過長不能把修改詳情所有展現出來,這時候須要把修改的詳情保存到 detail 字段,detail 是一個 String ,須要本身序列化。這裏的 #order.toString() 是調用了 Order 的 toString() 方法。app
若是保存 JSON,本身重寫一下 Order 的 toString() 方法就能夠。
@LogRecordAnnotation( fail = "建立訂單失敗,失敗緣由:「{{#_errorMsg}}」", category = "MANAGER_VIEW", detail = "{{#order.toString()}}", success = "{{#order.purchaseName}}下了一個訂單,購買商品「{{#order.productName}}」,下單結果:{{#_ret}}", prefix = LogRecordType.ORDER, bizNo = "{{#order.orderNo}}") public boolean createOrder(Order order) { log.info("【建立訂單】orderNo={}", order.getOrderNo()); // db insert order return true; }
第一種:手工在LogRecord的註解上指定。這種須要方法參數上有operator
@LogRecordAnnotation( fail = "建立訂單失敗,失敗緣由:「{{#_errorMsg}}」", category = "MANAGER_VIEW", detail = "{{#order.toString()}}", operator = "{{#currentUser}}", success = "{{#order.purchaseName}}下了一個訂單,購買商品「{{#order.productName}}」,下單結果:{{#_ret}}", prefix = LogRecordType.ORDER, bizNo = "{{#order.orderNo}}") public boolean createOrder(Order order, String currentUser) { log.info("【建立訂單】orderNo={}", order.getOrderNo()); // db insert order return true; }
這種方法手工指定,須要方法參數上有 operator 參數,或者經過 SpEL 調用靜態方法獲取當前用戶。
第二種:經過默認實現類來自動的獲取操做人,因爲在大部分web應用中當前的用戶都是保存在一個線程上下文中的,因此每一個註解都加一個operator獲取操做人顯得有些重複勞動,因此提供了一個擴展接口來獲取操做人
框架提供了一個擴展接口。
使用框架的業務能夠 implements 這個接口本身實現獲取當前用戶的邏輯,對於使用 Springboot 的只須要實現 IOperatorGetService 接口,而後把這個 Service 做爲一個單例放到 Spring 的上下文中。使用 Spring Mvc 的就須要本身手工裝配這些 bean 了。
@Configuration public class LogRecordConfiguration { @Bean public IOperatorGetService operatorGetService() { return () -> Optional.of(OrgUserUtils.getCurrentUser()) .map(a -> new OperatorDO(a.getMisId())) .orElseThrow(() -> new IllegalArgumentException("user is null")); } } //也能夠這麼搞: @Service public class DefaultOperatorGetServiceImpl implements IOperatorGetService { @Override public OperatorDO getUser() { OperatorDO operatorDO = new OperatorDO(); operatorDO.setOperatorId("SYSTEM"); return operatorDO; } }
對於更新等方法,方法的參數上大部分都是訂單ID、或者產品ID等,好比下面的例子:日誌記錄的success內容是:「更新了訂單{{#orderId}},更新內容爲…」,這種對於運營或者產品來講難以理解,因此引入了自定義函數的功能。
使用方法是在原來的變量的兩個大括號之間加一個函數名稱 例如 「{ORDER{#orderId}}」 其中 ORDER 是一個函數名稱。只有一個函數名稱是不夠的,須要添加這個函數的定義和實現。能夠看下面例子
自定義的函數須要實現框架裏面的IParseFunction的接口,須要實現兩個方法:
這裏有個問題:加了自定義函數後,框架怎麼能調用到呢?
答:對於Spring boot應用很簡單,只須要把它暴露在Spring的上下文中就能夠了,能夠加上Spring的 @Component 或者 @Service 很方便😄。Spring mvc 應用須要本身裝配 Bean。
// 沒有使用自定義函數 @LogRecordAnnotation(success = "更新了訂單{{#orderId}},更新內容爲....", prefix = LogRecordType.ORDER, bizNo = "{{#order.orderNo}}", detail = "{{#order.toString()}}") public boolean update(Long orderId, Order order) { return false; } //使用了自定義函數,主要是在 {{#orderId}} 的大括號中間加了 functionName @LogRecordAnnotation(success = "更新了訂單ORDER{#orderId}},更新內容爲...", prefix = LogRecordType.ORDER, bizNo = "{{#order.orderNo}}", detail = "{{#order.toString()}}") public boolean update(Long orderId, Order order) { return false; } // 還須要加上函數的實現 @Component public class OrderParseFunction implements IParseFunction { @Resource @Lazy //爲了不類加載順序的問題 最好爲Lazy,沒有問題也能夠不加 private OrderQueryService orderQueryService; @Override public String functionName() { // 函數名稱爲 ORDER return "ORDER"; } @Override //這裏的 value 能夠吧 Order 的JSON對象的傳遞過來,而後反解析拼接一個定製的操做日誌內容 public String apply(String value) { if(StringUtils.isEmpty(value)){ return value; } Order order = orderQueryService.queryOrder(Long.parseLong(value)); //把訂單產品名稱加上便於理解,加上 ID 便於查問題 return order.getProductName().concat("(").concat(value).concat(")"); } }
@LogRecordAnnotation(prefix = LogRecordTypeConstant.CUSTOM_ATTRIBUTE, bizNo = "{{#businessLineId}}", success = "{{#disable ? '停用' : '啓用'}}了自定義屬性{ATTRIBUTE{#attributeId}}") public CustomAttributeVO disableAttribute(Long businessLineId, Long attributeId, boolean disable) { return xxx; }
重寫OperatorGetServiceImpl經過上下文獲取用戶的擴展,例子以下
@Service public class DefaultOperatorGetServiceImpl implements IOperatorGetService { @Override public Operator getUser() { return Optional.ofNullable(UserUtils.getUser()) .map(a -> new Operator(a.getName(), a.getLogin())) .orElseThrow(()->new IllegalArgumentException("user is null")); } }
ILogRecordService 保存/查詢日誌的例子,使用者能夠根據數據量保存到合適的存儲介質上,好比保存在數據庫/或者ES。本身實現保存和刪除就能夠了
也能夠只實現查詢的接口,畢竟已經保存在業務的存儲上了,查詢業務能夠本身實現,不走 ILogRecordService 這個接口,畢竟產品經理會提一些千奇百怪的查詢需求。
@Service public class DbLogRecordServiceImpl implements ILogRecordService { @Resource private LogRecordMapper logRecordMapper; @Override @Transactional(propagation = Propagation.REQUIRES_NEW) public void record(LogRecord logRecord) { log.info("【logRecord】log={}", logRecord); LogRecordPO logRecordPO = LogRecordPO.toPo(logRecord); logRecordMapper.insert(logRecordPO); } @Override public List<LogRecord> queryLog(String bizKey, Collection<String> types) { return Lists.newArrayList(); } @Override public PageDO<LogRecord> queryLogByBizNo(String bizNo, Collection<String> types, PageRequestDO pageRequestDO) { return logRecordMapper.selectByBizNoAndCategory(bizNo, types, pageRequestDO); } }
IParseFunction 自定義轉換函數的接口,能夠實現IParseFunction 實現對LogRecord註解中使用的函數擴展
例子:
@Component public class UserParseFunction implements IParseFunction { private final Splitter splitter = Splitter.on(",").trimResults(); @Resource @Lazy private UserQueryService userQueryService; @Override public String functionName() { return "USER"; } @Override // 11,12 返回 11(小明),12(張三) public String apply(String value) { if (StringUtils.isEmpty(value)) { return value; } List<String> userIds = Lists.newArrayList(splitter.split(value)); List<User> misDOList = userQueryService.getUserList(userIds); Map<String, User> userMap = StreamUtil.extractMap(misDOList, User::getId); StringBuilder stringBuilder = new StringBuilder(); for (String userId : userIds) { stringBuilder.append(userId); if (userMap.get(userId) != null) { stringBuilder.append("(").append(userMap.get(userId).getUsername()).append(")"); } stringBuilder.append(","); } return stringBuilder.toString().replaceAll(",$", ""); } }
LogRecordAnnotation 能夠使用的變量出了參數也能夠使用返回值#_ret變量,以及異常的錯誤信息#_errorMsg,也能夠經過SpEL的 T 方式調用靜態方法噢
實現一個 Log的 Context,能夠解決方法參數中沒有的變量可是想使用的問題,初步想法是能夠經過在方法中 add 變量的形式實現,很快就能夠實現了 😄
⚠️ 總體日誌攔截是在方法執行以後記錄的,因此對於方法內部修改了方法參數以後,LogRecordAnnotation 的註解上的 SpEL 對變量的取值是修改後的值哦。
源碼:https://github.com/mouzt/mzt-...
版權聲明:本文爲CSDN博主「mztBang」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處連接及本聲明。
原文連接:https://blog.csdn.net/weixin_...
近期熱文推薦:
1.600+ 道 Java面試題及答案整理(2021最新版)
2.終於靠開源項目弄到 IntelliJ IDEA 激活碼了,真香!
3.阿里 Mock 工具正式開源,幹掉市面上全部 Mock 工具!
4.Spring Cloud 2020.0.0 正式發佈,全新顛覆性版本!
以爲不錯,別忘了隨手點贊+轉發哦!