業務背景
一個虛擬貨幣系統,需要日常監控,例如每日的新增流量統計、交易流水統計、異常交易統計等。
每日統計一次以上信息,並將統計信息添加到excel表中,以郵件的形式進行發送。
業務需求
1.可以在監控使用者無感知的情況下,添加新的監控任務
2.監控信息以excel文件形式告知管理者
監控設計
1.首先看監控使用者(定時任務)如何觸發所有的監控:
2.monitorExecutor的代碼
/**
* 監控任務鏈的執行器。外部想執行所有監控請調用{@link #execute()}方法
* 使用案例:{@link com.cesgroup.coin.cron.SystemMonitorTask#executeMonitorTask}
* createTime: 2019-04-19 14:45
* @author zack
*/
@Slf4j
@Component
@ConditionalOnBean(CoinMonitor.class)
public class MonitorExecutor implements ApplicationContextAware, InitializingBean {
private ApplicationContext applicationContext;
private Map<String, CoinMonitor> coinMonitorMap;
private volatile CoinMonitor endOfMonitorChain;
@Override
public void afterPropertiesSet() {
this.coinMonitorMap = applicationContext.getBeansOfType(CoinMonitor.class);
log.info("加載的CoinMonitor:{}",coinMonitorMap);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
/**
* 執行監控任務鏈,並將監控信息彙總到一個數據Map,以進行和Excel模板附件的結合,最後生成郵件附件Resource
* 注:easyPoi的模板和數據只能結合一次。所以每個監控的internalExecute()方法傳遞的是數據而不是生成的文件流。
* @return 因爲我們系統的監控信息都是以excel文件形式告知管理者,所以監控鏈的最終產物是文件
*/
public FileSystemResource execute() {
initMonitorChain();
assert endOfMonitorChain != null;
//執行所有監控,獲得包含所有監控信息的excel附件
return endOfMonitorChain.execute();
}
private void initMonitorChain() {
if (endOfMonitorChain == null) {
synchronized (this) {
if (endOfMonitorChain == null) {
int index = 0;
CoinMonitor monitor = null;
for (CoinMonitor nextMonitor : coinMonitorMap.values()) {
if (index == 0) {
monitor = nextMonitor;
} else {
monitor = monitor.addMonitor(nextMonitor);
}
index++;
}
this.endOfMonitorChain = monitor;
}
}
}
}
}
3.所有監控的父類,CoinMonitor
/**
* 所有監控類都必須繼承此接口,並被納入spring的bean管理。如此才能每天定時執行監控任務。
* 監控執行入口:{@link MonitorExecutor#execute}
* 監控類示例:{@link UserBalanceMonitor}
* createTime: 2019-04-17 10:38
* @author zack
*/
public abstract class CoinMonitor {
protected Logger log = LoggerFactory.getLogger(CoinMonitor.class);
private CoinMonitor lastMonitor;
/**
* 添加下一個需要執行的監控,以便彙總監控信息,統一將信息添加到郵件附件
*/
CoinMonitor addMonitor(CoinMonitor nextMonitor) {
nextMonitor.lastMonitor = this;
return nextMonitor;
}
/**
* 執行監控任務鏈,並將監控信息彙總到一個數據Map,以進行和Excel模板附件的結合,最後生成郵件附件Resource
* 注:easyPoi的模板和數據只能結合一次。所以每個監控的internalExecute()方法傳遞的是數據而不是生成的文件流。
* @return 因爲我們系統的監控信息都是以excel文件形式告知管理者,所以監控鏈的最終產物是文件
*/
FileSystemResource execute() {
Map<Integer, Map<String, Object>> sheetsData = new HashMap<>();
sheetsData = execute(sheetsData);
return mergeDataIntoExcelTemplate(sheetsData);
}
/**
* @param sheetsData 上一個監控產生的Excel附件所需的Data
* @return excel監控附件所需數據,key是excel表單的sheet的num,從0開始
*/
private Map<Integer, Map<String, Object>> execute(Map<Integer, Map<String, Object>> sheetsData) {
sheetsData = addMonitorData(sheetsData);
if (lastMonitor != null) {
sheetsData = lastMonitor.execute(sheetsData);
}
return sheetsData;
}
/**
* 將數據和excel模板相結合,生成最終的excel附件
* @param sheetsData excel監控附件所需的全部數據,key是excel表單的sheet的num,從0開始
* @return 郵件Excel附件Resource對象
*/
private FileSystemResource mergeDataIntoExcelTemplate(Map<Integer, Map<String, Object>> sheetsData) {
TemplateExportParams excelTemplate = new TemplateExportParams(
Constant.MONOTOR_EXCEL_TEMPLATE_CLASSPATH,true);
Map<Integer, Map<String, Object>> defaultSheetsData = addDataToEverySheet(excelTemplate,sheetsData);
try (FileOutputStream fos =
new FileOutputStream(Constant.MONITOR_TEMP_DATA_PATH)) {
Workbook workbook = MutiSheetExcelExportUtil.exportExcel(defaultSheetsData, excelTemplate);
workbook.write(fos);
} catch (Exception e) {
log.error("導出Excel監控數據異常:{}",e);
}
return new FileSystemResource(Constant.MONITOR_TEMP_DATA_PATH);
}
/**
* 實現爲excel的每個sheet頁添加空的數據,避免某個monitor因爲異常沒有對相應sheet頁提供數據,造成全部監控信息的丟失
* @param excelTemplate 監控信息的excel模板
* @param sheetsData 所有監控器產生的監控信息
* @return 就算某個sheet頁沒有信息,也爲其賦予空map
*/
@SuppressWarnings("unchecked")
private Map<Integer, Map<String, Object>> addDataToEverySheet(TemplateExportParams excelTemplate,
Map<Integer, Map<String, Object>> sheetsData) {
Workbook wb = ExcelCache.getWorkbook(excelTemplate.getTemplateUrl(), excelTemplate.getSheetNum(),
excelTemplate.isScanAllsheet());
int sheetNum = wb.getNumberOfSheets();
Map<Integer, Map<String, Object>> defaultSheetsData = new HashMap<>();
for (int i = 0;i < sheetNum;i++) {
defaultSheetsData.put(i,new HashMap<>());
}
for (Map.Entry entry : sheetsData.entrySet()) {
defaultSheetsData.put((Integer) entry.getKey(),(Map<String, Object>) entry.getValue());
}
return defaultSheetsData;
}
/**
* 每個監控器需要執行的監控任務
* 示例:{@link UserBalanceMonitor#addMonitorData}
* @param sheetsData excel附件所需數據的集合,key是sheet的num,從0開始
* @return 添加上本次監控信息的map數據集(key是sheet的num,value是對應sheet的數據)
*/
protected abstract Map<Integer, Map<String, Object>> addMonitorData(Map<Integer, Map<String, Object>> sheetsData);
}