上一篇 基于Redis实现的日志记录组件——超实用(4)AOP和SPEL实现

六、前端页面

前端页面采用H5 + 原生js6语法写成,界面设计以简洁轻量实用为主。

redis 日志如何看_html


顶部是组件列表,列出了所有的日志组件,对每个组件都可以进行清空、选中、编辑。

  • 清空会清空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的那种模板语法的痕迹。

七、后端接口

后端接口并不多,仅有以下几个:

redis 日志如何看_redis_02


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实现的日志记录组件系列的最后一篇了,谢谢收看。