领导友好型数据展示:Java后端到可视化实战汇报🔥

9/18/2024 Java工作&&汇报

# 优化数据呈现:打造领导友好的数据库汇报方案

在本次分享中,我将探讨一个完整的数据处理与可视化流程,旨在将复杂的系统日志信息转化为直观、易懂的图形展示,以便非技术背景的领导能够轻松理解数据背后的故事。整个过程包括使用DataGrip高效查询日志数据、Java进行数据二次加工以及利用jfreechart库实现数据可视化。此外,我们还将简要介绍如何利用easyexcel和lombok等Java库来辅助数据处理和代码简化,共同构建一个高效、友好的数据展示解决方案。

# 本次日志涉及库表信息展示

操作日志表结构展示

-- auto-generated definition
create table log_operate_log
(
    id               bigint auto_increment comment '日志主键'
        primary key,
    trace_id         varchar(64) charset utf8mb4        null,
    user_id          bigint                             null comment '用户编号',
    user_type        tinyint  default 0                 not null comment '用户类型',
    module           varchar(50) charset utf8mb4        null,
    name             varchar(50) charset utf8mb4        null,
    type             bigint   default 0                 not null comment '操作分类',
    content          varchar(2000) charset utf8mb4      null,
    exts             varchar(512) charset utf8mb4       null,
    request_method   varchar(16) charset utf8mb4        null,
    request_url      varchar(255) charset utf8mb4       null,
    user_ip          varchar(50) charset utf8mb4        null,
    user_agent       varchar(200) charset utf8mb4       null,
    java_method      varchar(512) charset utf8mb4       null,
    java_method_args varchar(7936) charset utf8mb4      null,
    start_time       datetime                           not null comment '操作时间',
    duration         int                                not null comment '执行时长',
    result_code      int      default 0                 not null comment '结果码',
    result_msg       varchar(512) charset utf8mb4       null,
    result_data      varchar(4000) charset utf8mb4      null,
    creator          varchar(64) charset utf8mb4        null,
    creator_dept     varchar(64) charset utf8mb4        null,
    create_time      datetime default CURRENT_TIMESTAMP not null comment '创建时间',
    updater          varchar(64) charset utf8mb4        null,
    update_time      datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
    deleted          bit      default b'0'              not null comment '是否删除',
    tenant_id        bigint   default 0                 not null comment '租户编号'
)
    comment '操作日志记录' collate = utf8mb4_unicode_ci;

菜单权限表结构展示

-- auto-generated definition
create table sys_menu
(
    id                   bigint auto_increment comment '菜单ID'
        primary key,
    parent_id            bigint                                             null comment '父id',
    name                 varchar(255) charset utf8                          null comment '菜单标题',
    url                  varchar(255) charset utf8                          null comment '路径',
    component            varchar(255) charset utf8                          null comment '组件',
    menu_role_type       tinyint(1)                                         null comment '菜单角色类别(1:管理角色,2:业务角色)',
    iz_route             tinyint(1)               default 1                 null comment '是否路由菜单: 0:不是  1:是(默认值1)',
    component_name       varchar(255) charset utf8                          null comment '组件名字',
    redirect             varchar(255) charset utf8                          null comment '一级菜单跳转地址',
    menu_type            int                                                null comment '菜单类型(0:一级菜单; 1:子菜单:2:按钮权限)',
    permission           varchar(255) charset utf8                          null comment '菜单权限编码',
    permission_type      varchar(10) charset utf8 default '0'               null comment '权限策略1显示2禁用',
    sort                 double(8, 2)                                       not null comment '菜单排序',
    always_show          tinyint(1)                                         null comment '聚合子路由: 1是0否',
    icon                 varchar(255) charset utf8                          null comment '菜单图标',
    iz_leaf              tinyint(1)                                         null comment '是否叶子节点:    1是0否',
    keep_alive           tinyint(1)                                         null comment '是否缓存该页面:    1:是   0:不是',
    hidden               int(2)                   default 0                 null comment '是否隐藏路由: 0否,1是',
    hide_tab             int(2)                   default 0                 null comment '是否隐藏tab: 0否,1是',
    description          varchar(255) charset utf8                          null comment '描述',
    rule_flag            int(3)                   default 0                 null comment '是否添加数据权限1是0否',
    status               int(2)                                             null comment '按钮权限状态(1无效0有效)',
    internal_or_external tinyint(1)                                         null comment '外链菜单打开方式 0/内部打开 1/外部打开',
    creator              varchar(64) charset utf8mb4                        null,
    creator_dept         varchar(64) charset utf8mb4                        null,
    create_time          datetime                 default CURRENT_TIMESTAMP not null comment '创建时间',
    updater              varchar(64) charset utf8mb4                        null,
    update_time          datetime                 default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
    deleted              bit                      default b'0'              not null comment '是否删除'
)
    comment '菜单权限表' collate = utf8mb4_unicode_ci;

用户表结构展示

-- auto-generated definition
create table sys_users
(
    id            bigint auto_increment comment '用户ID'
        primary key,
    usercode      varchar(64)                           null,
    username      varchar(30)                           null,
    password      varchar(100)                          null,
    nickname      varchar(30)                           null,
    remark        varchar(2000)                         null,
    dept_id       bigint                                null comment '部门ID',
    post_ids      varchar(255)                          null,
    user_type     tinyint     default 1                 null comment '用户类型 1业务  2管理',
    email         varchar(50)                           null,
    mobile        varchar(11)                           null,
    sex           tinyint     default 0                 null comment '用户性别',
    avatar        varchar(2000)                         null,
    status        tinyint     default 0                 not null comment '帐号状态(0正常 1停用)',
    login_ip      varchar(50)                           null,
    login_date    datetime    default CURRENT_TIMESTAMP null comment '最后登录时间',
    password_date datetime                              null comment '最后修改密码时间',
    lock_date     datetime                              null comment '最后锁定时间',
    valid_date    datetime                              null comment '账号有效时间',
    creator       varchar(64)                           null,
    creator_dept  varchar(64) default ''                null comment '创建者部门',
    create_time   datetime    default CURRENT_TIMESTAMP null comment '创建时间',
    updater       varchar(64)                           null,
    update_time   datetime    default CURRENT_TIMESTAMP null on update CURRENT_TIMESTAMP comment '更新时间',
    deleted       bit         default b'0'              null comment '是否删除',
    tenant_id     bigint      default 0                 null comment '租户编号',
    constraint index_id
        unique (id)
);

# 本次展示需要实现的效果

如何需要根据系统的日志信息得出不同人员访问不同菜单的频率、以及对应的次数情况。当然这些信息能使用到sql直接查询得出。但是本地介绍如何使用Java代码的方式进行对得到的数据进行二次加工,并且使用jfreechart三方库进行对可视化图表的渲染与展示,下面进行一下基础查询的SQL展示。

统计每个菜单访问次数情况SQL示例

SELECT
    m.name AS menu_name,                -- 菜单名称
    COUNT(l.start_time) AS access_count -- 统计每个菜单的访问次数
FROM
    log_operate_log l
JOIN
    sys_menu m ON l.request_url LIKE CONCAT(m.url, '%') -- 关联菜单表
JOIN
    sys_users u ON l.user_id = u.id     -- 关联用户表
GROUP BY
    m.name                              -- 按菜单名称分组
ORDER BY
    access_count DESC;                  -- 按访问次数降序排序

这里给大家介绍一个技巧就是可以使用DataGrip中的导出为excel然后使用Java代码方式,结合EasyExcel进行对数据的处理

image-20240918181009761

只需要在这里进行进行选择位置之后,导出相对应的excel数据。

# 如何使用Java代码的方式进行读取数据&&使用jfreechart进行可视化渲染。

这里需要注意一下就是需要在POM文件中配置jfreechart等三方的依赖然后使用下面的代码逻辑就可以实现对图片的渲染

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>4.0.3</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency>
            <groupId>org.jfree</groupId>
            <artifactId>jfreechart</artifactId>
            <version>1.5.5</version>
        </dependency>
    </dependencies>

折线图图片展示

/**
 * Copyright © 2024年 integration-projects-maven. All rights reserved.
 * ClassName QuickExcelReading.java
 * author 舒一笑 yixiaoshu88@163.com
 * version 1.0.0
 * Description 快速Excel读取次数与频率
 * createTime 2024年09月18日 11:11:00
 */
@Slf4j
public class QuickExcelReading {

    public static void main(String[] args) {
        new QuickExcelReading().simpleRead();
    }

    public void simpleRead() {
        String fileName = "excel-quick-java/src/main/resources/菜单访问总数汇总.xlsx";

        // 使用PageReadListener读取数据,修改批处理数量为200
        EasyExcel.read(fileName, DemoData.class, new PageReadListener<DemoData>(dataList -> {
                    // 使用Map<String, Integer> 来统计每个menu_name的访问次数
                    Map<String, Integer> menuAccessCountMap = new HashMap<>();
                    int totalAccessCount = 0;

                    // 遍历每条数据
                    for (DemoData demoData : dataList) {
                        log.info("读取到一条数据: {}", demoData);

                        // 累计各menu_name的访问次数
                        menuAccessCountMap.merge(demoData.getMenuName(), demoData.getAccessCount(), Integer::sum);
                        totalAccessCount += demoData.getAccessCount();
                    }

                    // 对map按照访问次数进行倒序排序,并只取前10项
                    Map<String, Integer> sortedMenuAccessCountMap = menuAccessCountMap.entrySet()
                            .stream()
                            .sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue()))  // 按访问次数降序排序
                            .limit(10)  // 只取前10个
                            .collect(java.util.stream.Collectors.toMap(
                                    Map.Entry::getKey,
                                    Map.Entry::getValue,
                                    (oldValue, newValue) -> oldValue,
                                    java.util.LinkedHashMap::new
                            ));

                    // 开始构建Markdown表格
                    StringBuilder markdownTable = new StringBuilder();
                    markdownTable.append("| 功能名称 | 访问次数 | 占比 |\n");
                    markdownTable.append("| --- | --- | --- |\n");

                    // 计算每个menu_name的访问次数占比并生成Markdown表格行
                    int finalTotalAccessCount = totalAccessCount;
                    sortedMenuAccessCountMap.forEach((menuName, count) -> {
                        double percentage = (count * 100.0) / finalTotalAccessCount;
                        markdownTable.append(String.format("| %s | %d次 | %.2f%% |\n", menuName, count, percentage));
                    });

                    // 输出Markdown表格
                    System.out.println(markdownTable.toString());

                    // 渲染饼图
                    renderPieChart(sortedMenuAccessCountMap);

                }, 200)) // 批处理大小设为200
                .sheet().doRead();
    }

    // 渲染饼图的方法
    public void renderPieChart(Map<String, Integer> sortedMenuAccessCountMap) {
        DefaultPieDataset dataset = new DefaultPieDataset();

        // 将sortedMenuAccessCountMap数据放入dataset中
        sortedMenuAccessCountMap.forEach(dataset::setValue);

        // 创建饼图
        JFreeChart pieChart = ChartFactory.createPieChart(
                "功能访问次数分布(前10项)",  // 图表标题
                dataset,             // 数据集
                true,                // 是否显示图例
                true,                // 是否使用工具提示
                false                // 是否生成URLs
        );

        // 自定义饼图样式
        PiePlot plot = (PiePlot) pieChart.getPlot();
        plot.setLabelFont(new java.awt.Font("SansSerif", java.awt.Font.PLAIN, 12));  // 设置字体大小
        plot.setNoDataMessage("没有数据");
        plot.setCircular(true);  // 设置为圆形饼图
        plot.setLabelGap(0.02);  // 设置标签与饼图的距离

        // 设置标签在饼图外部显示
        plot.setSimpleLabels(false);  // 使用连接线将标签显示在饼图外部

        // 保存饼图为图片
        int width = 960;    // 增加图片宽度
        int height = 720;   // 增加图片高度
        File pieChartFile = new File("功能访问次数分布饼图.png");
        try {
            ChartUtils.saveChartAsPNG(pieChartFile, pieChart, width, height);
            System.out.println("饼图已生成: " + pieChartFile.getAbsolutePath());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

# 折线图图片性质展示

运行代码之后就可以在资源路径下得到图片的信息

image-20240918181443925

渲染的图片图片展示(这里有一个坑需要注意就是这个三方框架对中文好像不是很友好,然后就是出现这下面图片的效果)

# 柱状图效果展示

# 代码展示

/**
 * Copyright © 2024年 integration-projects-maven. All rights reserved.
 * ClassName QuickExcelReading2.java
 * author 舒一笑 yixiaoshu88@163.com
 * version 1.0.0
 * Description 快速Excel读取次数与频率人员访问情况
 * createTime 2024年09月18日 12:59:00
 */
@Slf4j
public class QuickExcelReading2 {

    public static void main(String[] args) {
        new QuickExcelReading2().simpleRead();
    }

    public void simpleRead() {
        String fileName = "E:\\ALearn\\code-improves-excel-efficiency\\excel-quick-java\\src\\main\\resources\\用户访问次数.xlsx";

        // 使用PageReadListener读取数据,修改批处理数量为200
        EasyExcel.read(fileName, DemoData.class, new PageReadListener<DemoData>(dataList -> {
                    // 使用Map<String, Integer> 来统计每个menu_name的访问次数
                    Map<String, Integer> menuAccessCountMap = new HashMap<>();
                    int totalAccessCount = 0;

                    // 遍历每条数据
                    for (DemoData demoData : dataList) {
                        log.info("读取到一条数据: {}", demoData);
                        if (menuAccessCountMap.get(demoData.getNickName()) == null){
                            menuAccessCountMap.put(demoData.getNickName(), demoData.getAccessCount());
                            totalAccessCount += demoData.getTotalAccessCount();
                        }
                        // 累计各menu_name的访问次数
//                        menuAccessCountMap.merge(demoData.getNickName(), demoData.getAccessCount(), Integer::sum);
//                        totalAccessCount += demoData.getTotalAccessCount();
                    }

                    // 对map按照访问次数进行倒序排序,并只取前10项
                    Map<String, Integer> sortedMenuAccessCountMap = menuAccessCountMap.entrySet()
                            .stream()
                            .sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue()))  // 按访问次数降序排序
                            .limit(10)  // 只取前10个
                            .collect(java.util.stream.Collectors.toMap(
                                    Map.Entry::getKey,
                                    Map.Entry::getValue,
                                    (oldValue, newValue) -> oldValue,
                                    java.util.LinkedHashMap::new
                            ));

                    // 开始构建Markdown表格
                    StringBuilder markdownTable = new StringBuilder();
                    markdownTable.append("| 功能名称 | 访问次数 | 占比 |\n");
                    markdownTable.append("| --- | --- | --- |\n");

                    // 计算每个menu_name的访问次数占比并生成Markdown表格行
                    int finalTotalAccessCount = totalAccessCount;
                    sortedMenuAccessCountMap.forEach((menuName, count) -> {
                        double percentage = (count * 100.0) / finalTotalAccessCount;
                        markdownTable.append(String.format("| %s | %d次 | %.2f%% |\n", menuName, count, percentage));
                    });

                    // 输出Markdown表格
                    System.out.println(markdownTable.toString());

                    // 渲染饼图
                    renderPieChart(sortedMenuAccessCountMap);
                    // 渲染柱状图
                    renderBarChart(sortedMenuAccessCountMap);

                }, 65588)) // 批处理大小设为200
                .sheet().doRead();
    }

    // 渲染饼图的方法
    public void renderPieChart(Map<String, Integer> sortedMenuAccessCountMap) {
        DefaultPieDataset dataset = new DefaultPieDataset();

        // 将sortedMenuAccessCountMap数据放入dataset中
        sortedMenuAccessCountMap.forEach(dataset::setValue);

        // 创建饼图
        JFreeChart pieChart = ChartFactory.createPieChart(
                "功能访问次数分布(前10项)",  // 图表标题
                dataset,             // 数据集
                true,                // 是否显示图例
                true,                // 是否使用工具提示
                false                // 是否生成URLs
        );

        // 自定义饼图样式
        PiePlot plot = (PiePlot) pieChart.getPlot();
        plot.setLabelFont(new java.awt.Font("SansSerif", java.awt.Font.PLAIN, 12));  // 设置字体大小
        plot.setNoDataMessage("没有数据");
        plot.setCircular(true);  // 设置为圆形饼图
        plot.setLabelGap(0.02);  // 设置标签与饼图的距离

        // 设置标签在饼图外部显示
        plot.setSimpleLabels(false);  // 使用连接线将标签显示在饼图外部

        // 保存饼图为图片
        int width = 960;    // 增加图片宽度
        int height = 720;   // 增加图片高度
        File pieChartFile = new File("人员访问次数分布饼图.png");
        try {
            ChartUtils.saveChartAsPNG(pieChartFile, pieChart, width, height);
            System.out.println("饼图已生成: " + pieChartFile.getAbsolutePath());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 渲染柱状图的方法
    public void renderBarChart(Map<String, Integer> sortedMenuAccessCountMap) {
        DefaultCategoryDataset dataset = new DefaultCategoryDataset();

        // 将sortedMenuAccessCountMap数据放入dataset中
        sortedMenuAccessCountMap.forEach((menuName, count) -> {
            dataset.addValue(count, "访问次数", menuName);
        });

        // 创建柱状图
        JFreeChart barChart = ChartFactory.createBarChart(
                "功能访问次数分布(前10项)",  // 图表标题
                "功能名称",                // X轴标签
                "访问次数",                // Y轴标签
                dataset,                   // 数据集
                PlotOrientation.VERTICAL,  // 图表方向:垂直
                true,                      // 是否显示图例
                true,                      // 是否使用工具提示
                false                      // 是否生成URLs
        );

        // 自定义柱状图样式
        CategoryPlot plot = barChart.getCategoryPlot();
        plot.setRangeGridlinePaint(java.awt.Color.BLACK);  // 设置网格线颜色
        BarRenderer renderer = (BarRenderer) plot.getRenderer();
        renderer.setDrawBarOutline(false);  // 不绘制边框

        // 设置柱状图的柱子颜色
        renderer.setSeriesPaint(0, new java.awt.Color(79, 129, 189));  // 自定义颜色

        // 保存柱状图为图片
        int width = 960;    // 图片宽度
        int height = 720;   // 图片高度
        File barChartFile = new File("人员访问次数分布柱状图.png");
        try {
            ChartUtils.saveChartAsPNG(barChartFile, barChart, width, height);
            System.out.println("柱状图已生成: " + barChartFile.getAbsolutePath());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

image-20240918181640506

好了,本次的分享就先到这里,下次再给大家分享一下如何使用python结合三方库进行图片的渲染,这方面不得不说确实python要强很多~

    我很快乐-周兴哲
    致逝去的青春