上一篇 基于Redis实现的日志记录组件——超实用(4)AOP和SPEL实现
六、前端页面
前端页面采用H5 + 原生js6语法写成,界面设计以简洁轻量实用为主。
顶部是组件列表,列出了所有的日志组件,对每个组件都可以进行清空、选中、编辑。
- 清空会清空redis里对应组件的日志。
- 选中会将该组件的日志实时的在底下列出来。
- 编辑可以修改组件的各项信息,修改保存之后就会实时应用。
中间部分是日志的显示区间控制,和刷新频率。
显示区间的索引和redis 的lrange 语法一致,-1代表末尾。
后续可能还会加上特殊的过滤选项,如关键字、日期区间、操作类型等。
底部则是日志的呈现区域。日志的刷新是通过setInterval 定时调接口实现的。
前端的代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>DAWN-REDIS-LOG</title>
<style>
html {
font-family: sans-serif;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
body {
margin: 10px;
}
td, th {
border-left: 1px solid #cbcbcb;
font-size: inherit;
margin: 0;
overflow: visible;
padding: .5em 1em;
background-color: transparent;
border-bottom: 1px solid #cbcbcb;
}
table {
border-collapse: collapse;
border-spacing: 0;
empty-cells: show;
border: 1px solid #cbcbcb;
}
table caption {
color: #000;
font: italic 85%/1 arial, sans-serif;
padding: 1em 0;
text-align: center;
}
table thead {
background-color: #e0e0e0;
color: #000;
text-align: left;
vertical-align: bottom;
}
table tbody > tr:last-child > td {
border-bottom-width: 0;
}
</style>
<script>
const START_DEFAULT = -1, END_DEFAULT = -1, FRESH_DEFAULT = 2;
let componentMap = {};
let compSelected = null;
let compEdited = null;
let task = null;
let start = START_DEFAULT;
let end = END_DEFAULT;
let fresh = FRESH_DEFAULT;
async function getData(path) {
return new Promise((resolve, reject) => {
const request = new XMLHttpRequest();
request.open("GET", path);
request.onload = function () {
try {
if (this.status === 200) {
resolve(JSON.parse(this.response));
} else {
reject(this.status + " " + this.statusText);
}
} catch (e) {
reject(e.message);
}
};
request.onerror = function () {
reject(this.status + " " + this.statusText);
};
request.send();
})
}
function formatData(date) {
let s1 = date.split('T');
let s2 = s1[1].split('+');
return `${s1[0]} ${s2[0]}`
}
function formatDuring(mss) {
let days = parseInt(mss / (1000 * 60 * 60 * 24));
days = days ? days + '天' : '';
let hours = parseInt((mss % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
hours = hours ? hours + '小时' : '';
let minutes = parseInt((mss % (1000 * 60 * 60)) / (1000 * 60));
minutes = minutes ? minutes + '分钟' : '';
let seconds = (mss % (1000 * 60)) / 1000;
seconds = seconds ? seconds + '秒' : '';
return days + hours + minutes + seconds;
}
async function mounted() {
const res = await getData("/redisLog/searchComponent");
if (res && res.code === 200) {
componentMap = res.data;
await render();
}
}
async function render() {
console.log(componentMap)
let componentTableBody = document.getElementById("componentTableBody");
let components = "";
let i = 0;
for (let key of Object.keys(componentMap)) {
let comp = componentMap[key];
i++;
let compHtml = comp.name === compEdited ? renderEditRow(i, comp) : renderRow(i, comp);
components += compHtml;
}
componentTableBody.innerHTML = components;
setConfig();
}
function renderEditRow(i, comp) {
return `
<tr>
<td>${i}</td>
<td>${comp.name}</td>
<td><input id="description" type="text" maxlength="30" value="${comp.description}"></td>
<td><input id="expireMillis" type="number" style="width: 120px;" value="${comp.expireMillis}" min="0" maxlength="15">ms</td>
<td><input id="sizeLimit" type="number" value="${comp.sizeLimit}" min="0" maxlength="10"></td>
<td><input id="checkProportion" type="number" value="${comp.checkProportion}" min="0" max="1" maxlength="3"></td>
<td>${formatData(comp.createDate)}</td>
<td>${formatData(comp.updateDate)}</td>
<td>
<button οnclick="doUpdateComp('${comp.name}')">应用</button>
</td>
</tr>
`;
}
function renderRow(i, comp) {
return `
<tr>
<td>${i}</td>
<td>${comp.name}</td>
<td>${comp.description}</td>
<td>${formatDuring(comp.expireMillis)}</td>
<td>${comp.sizeLimit}</td>
<td>${comp.checkProportion}</td>
<td>${formatData(comp.createDate)}</td>
<td>${formatData(comp.updateDate)}</td>
<td>
<button οnclick="clearComp('${comp.name}')">清空</button>
<button οnclick="selectComp('${comp.name}')">选中</button>
<button οnclick="editComp('${comp.name}')">编辑</button>
</td>
</tr>
`;
}
function editComp(name) {
compEdited = name;
render();
}
function closeTask() {
if (task) {
clearInterval(task);
}
}
function reCreateScheduleTask() {
closeTask();
task = setInterval(() => {
if (compSelected) {
selectComp(compSelected);
}
}, fresh * 1000);
}
function clearComp(name) {
getData(`/redisLog/${name}/log/clear`);
}
function getValue(id) {
let value = document.getElementById(id).value;
if (value !== 0 && ! value) {
throw Error('请填写' + id)
}
return value;
}
async function doUpdateComp(name) {
try {
let description = getValue('description');
let expireMillis = getValue('expireMillis');
let sizeLimit = getValue('sizeLimit');
let checkProportion = getValue('checkProportion');
await updateComponent(name, description, expireMillis, 'MILLISECONDS', sizeLimit, checkProportion);
compEdited = null;
await mounted();
} catch (e) {
console.warn(e);
alert(e)
}
}
async function updateComponent(component, description, expire, timeUnit, sizeLimit, checkProportion) {
await getData(`/redisLog/${component}/update?` +
`description=${description}&expire=${expire}&timeUnit=${timeUnit}&sizeLimit=${sizeLimit}&checkProportion=${checkProportion}`);
}
async function selectComp(name) {
const res = await getData(`/redisLog/${name}/log/search?start=${start}&end=${end}`);
if (res && res.code === 200) {
compSelected = name;
let comp = res.data;
let log = document.getElementById("log");
let logTitle = document.getElementById("logTitle");
logTitle.textContent = `${name}日志(实际${comp.start}-${comp.end})`;
let logs = comp.logs;
log.value = logs.join('\n');
log.scrollTop = log.scrollHeight;
}
}
function setConfig() {
start = document.getElementById('start').value;
if (start !== 0 && !start) {
start = START_DEFAULT;
document.getElementById('start').value = start;
}
end = document.getElementById('end').value;
if (end !== 0 && !end) {
end = END_DEFAULT;
document.getElementById('end').value = end;
}
fresh = document.getElementById('fresh').value;
fresh = parseFloat(fresh);
if (!fresh || fresh <= 0) {
fresh = FRESH_DEFAULT;
document.getElementById('end').value = fresh;
}
reCreateScheduleTask();
}
window.onload = mounted;
</script>
</head>
<body>
<div style="width: 100%;">
<h3>DAWN-REDIS-LOG</h3>
<table style="width: 100%;">
<thead>
<tr>
<th style="width: 5%">#</th>
<th style="width: 10%">组件</th>
<th style="width: 20%">描述</th>
<th style="width: 12%">过期时间</th>
<th style="width: 10%">大小限制</th>
<th style="width: 13%">过期检查开始占比</th>
<th style="width: 10%">创建时间</th>
<th style="width: 10%">更新时间</th>
<th style="width: 10%">操作</th>
</tr>
</thead>
<tbody id="componentTableBody">
</tbody>
</table>
<div style="margin-top: 25px;">
<span style="font-weight: bold;">区间:</span>
<label for="start">
<input type="number" id="start" style="line-height: 25px;" value="-1">
</label>
-
<label for="end">
<input type="number" id="end" style="line-height: 25px;" value="-1">
</label>
<span style="font-weight: bold;">刷新频率(秒):</span>
<label for="end">
<input type="number" id="fresh" style="line-height: 25px;" value="2">
</label>
<button style="margin-left: 10px;" onclick="setConfig()">应用</button>
<button style="margin-left: 10px;" onclick="closeTask()">停止刷新</button>
</div>
<h3 id="logTitle">日志</h3>
<label>
<textarea id="log" style="width: 100%;height: 500px;padding: 0;border: 1px solid #cbcbcb;">
</textarea>
</label>
</div>
</body>
</html>
主要的渲染都是依赖js来完成的,renderEditRow渲染可编辑的列,renderRow渲染查看的列。
代码中可以看到react和vue的那种模板语法的痕迹。
七、后端接口
后端接口并不多,仅有以下几个:
controller 层的代码不做太多处理,只是调用service层。这一点符合mvc的要求。
@RestController
@RequestMapping("/redisLog")
@Tag(name = "redisLog", description = "RedisLog管理")
public class RedisLogController {
@Resource
private RedisLogService redisLogService;
@GetMapping("/searchComponent")
@Operation(summary = "查询所有日志组件的信息", tags = "searchComponent",
description = "查询所有日志组件的信息")
public Result<Map<String, RedisLogComponent>> searchComponent() {
return Result.success(redisLogService.getRedisLogComponentMap());
}
@GetMapping("/{component}/log/search")
@Operation(summary = "查询某个组件的日志", tags = "searchComponentLog",
description = "查询某个组件的日志")
public Result<RedisLogComponent> componentLog(@PathVariable String component,
@RequestParam long start,
@RequestParam long end) {
return Result.success(redisLogService.readLogFromComponent(component, start, end));
}
@GetMapping("/{component}/log/clear")
@Operation(summary = "清空某个组件的日志", tags = "clearComponentLog",
description = "清空某个组件的日志")
public Result<Boolean> clearLog(@PathVariable String component) {
return Result.success(redisLogService.clearComponent(component));
}
@GetMapping("/{component}/update")
@Operation(summary = "更新某个组件的信息", tags = "updateComponent",
description = "更新某个组件的信息,所有配置都会立即被生效")
public Result<RedisLogComponent> updateComponent(@PathVariable String component,
@RequestParam String description,
@RequestParam Long expire,
@RequestParam TimeUnit timeUnit,
@RequestParam long sizeLimit,
@RequestParam float checkProportion) {
return Result.success(redisLogService.updateComponent(
component, description, expire, timeUnit, sizeLimit, checkProportion));
}
}
本篇是基于Redis实现的日志记录组件系列的最后一篇了,谢谢收看。