实验数据管理与可视化分析系统【双库实现】

一、介绍

实验数据管理与分析系统,实现了对实验数据和统计数据的备份、统一管理、可视化分析展示、操作日志展示等功能。系统角色分为管理员与普通用户,普通用户可以上传实验数据到系统主库,将主库数据迁移到从库并进行操作,然后针对从库数据进行数据可视化展示。系统管理员可以直接操作主库实验数据,并对普通用户进行管理,查看系统用户操作日志。

二、架构

系统采用单体架构,后端使用SpringBoot,前端使用SpringBoot自带模板引擎Thymeleaf和Layui进行界面开发,通过Echarts进行图表展示,数据库使用MySQL数据库。

三、功能

1. 登录鉴权
界面

在这里插入图片描述

描述

加入了登录拦截、系统管理员页面拦截

核心代码

WebConfig.java

public class WebConfig implements WebMvcConfigurer {

    /**
     * 拦截器配置
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns(
                        "/exp_page",
                        "/data_move_page",
                        "/sub_page",
                        "/data_visual_list",
                        "/data_visual",
                        "/dashboard"
                );  //拦截所有页面跳转请求

        registry.addInterceptor(new AuthInterceptor())
                .addPathPatterns(
                        "/user_page",
                        "/log_page",
                        "/admin_exp_page"
                );
    }
}

AuthInterceptor.java

public class AuthInterceptor extends HandlerInterceptorAdapter {

    //请求处理前调用的方法  执行
    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {
        User user = (User) request.getSession().getAttribute("user");
        if (user == null) {//未登录
            response.sendRedirect("/login");//重定向到登录页面
            return false;
        } else {
            if (user.getRole() != 1) { // 非管理员
                response.sendRedirect("/dashboard");//没有管理员权限跳转到仪表盘
                return false;
            }
        }
        return true;
    }
}
2. 仪表盘
界面

在这里插入图片描述

描述

展示系统数据集总数,实验数据总数和可视化总数以及系统用户创建的可视化图表

3. 实验数据上传
界面

在这里插入图片描述

描述

上传实验数据到主库中,目前只支持csv格式的文件

核心代码
	@ExpLog(description = "上传实验数据")
    @Override
    public void uploadExpDataFile(MultipartFile file, ExpCreateReq req) throws Exception {
        BufferedReader reader = new BufferedReader(new InputStreamReader(file.getInputStream(), "UTF-8"));
        // 判断数据库表是否存在
        List<String> dbTables = expMapper.getDBTables(EXP_DB);
        if (dbTables.contains(req.getTableName())) {
            throw new Exception("数据库表已存在");
        }
        // 读取csv文件
        CSVReader csvReader = new CSVReader(reader);
        // 解析表头,动态创建数据库表
        String[] header = csvReader.readNext();
        // 1. 插入表字段关联表
        for (String field : header) {
            expMapper.insertExpTableFieldRel(req.getTableName(), field);
        }
        // 2. 创建表结构
        expMapper.createTable(SqlUtil.createTableSql(req.getTableName(), header));
        // 3. 添加实验信息
        List<String[]> cache = new ArrayList<>(); // 缓存空间
        long count = 0;
        String[] row;
        while ((row = csvReader.readNext()) != null) {
            if (count % Constant.EXP_MEM_CACHE == 0 && count != 0) {
                // 4. 读取缓存批量插入实验数据
                expMapper.insertExpData(SqlUtil.batchInsertDataSql(req.getTableName(), header, cache));
                cache.clear();
            }
            cache.add(row);
            count++;
        }
        // 在执行一次清空缓存
        if (cache.size() > 0) {
            expMapper.insertExpData(SqlUtil.batchInsertDataSql(req.getTableName(), header, cache));
        }
        System.out.println("导入实验数据条数: " + count);
        expMapper.insertExpInfo(req, count);
        // 备份本地文件
        MultipartFileUtil.saveMultipartFile(file, BACKUPS_PATH);
        csvReader.close();
        reader.close();
    }
4. 实验数据迁移
界面

在这里插入图片描述

描述

展示数据集主库和从库数据,支持选择指定数据迁移和所有数据迁移

核心代码
	@ExpLog(description = "实验数据迁移")
    @Override
    public int dataMove(DataMoveReq req) {
        System.out.println(req);
        // 1.查看主题数据库中是否存在该表
        List<String> dbTables = subMapper.getDBTables(SUB_DB);
        Experiment experiment = expMapper.queryExpById(req.getId());
        // 查询表头
        List<ExpTableFieldRel> list = expMapper.queryExpTableField(experiment.getTableName());
        String[] header = list.stream().map(ExpTableFieldRel::getFieldName).collect(Collectors.toList()).toArray(new String[list.size()]);
        if (!dbTables.contains(experiment.getTableName())) {
            // 2. 不存在则创建
            subMapper.createTable(SqlUtil.createTableSql(experiment.getTableName(), header));
        }

        // 3.插入迁移数据
        List<Map<String, Object>> datas = req.getList();
        List<String[]> rows = datas.stream().map(data -> {
            List<String> row = new ArrayList<>();
            for (String s : header) {
                Object o = data.get(s);
                row.add(String.valueOf(o));
            }
            return row.toArray(new String[row.size()]);
        }).collect(Collectors.toList());
        subMapper.insertExpData(SqlUtil.batchInsertDataSql(experiment.getTableName(), header, rows));
        return 1;
    }

    @ExpLog(description = "迁移所有数据")
    @Override
    public int dataMoveAll(int expId) {
        Experiment experiment = expMapper.queryExpById(expId);
        List<Map<String, Object>> datas = expMapper.queryExpData(experiment.getTableName());
        return dataMove(new DataMoveReq(expId, datas));
    }
5. 主题数据操作
界面

在这里插入图片描述

描述

展示数据集从库数据,提供对主题数据查询修改删除功能

核心代码
    @ExpLog(description = "删除主题数据")
    @Override
    public int batchDeleteData(int id, List<Integer> ids) {
        Experiment experiment = expMapper.queryExpById(id);
        ids.forEach(i -> subMapper.deleteData(experiment.getTableName(), i));
        return ids.size();
    }

    @ExpLog(description = "修改主题数据")
    @Override
    public int updateData(int id, Map<String, String> data) {
        Experiment experiment = expMapper.queryExpById(id);
        String sql = SqlUtil.updateDataSql(experiment.getTableName(), data);
        subMapper.updateData(sql);
        return 1;
    }

SqlUtil.java

    /**
     * 修改数据SQL
     */
    public static String updateDataSql(String tableName, Map<String, String> data) {
        StringBuilder sql = new StringBuilder("update ").append(tableName).append(" set ");
        boolean isFirst = true;
        for (String key : data.keySet()) {
            if (!key.equals("id")) {
                if (isFirst) {
                    isFirst = false;
                } else {
                    sql.append(",");
                }
                sql.append("`").append(key).append("`").append("=").append("\"").append(data.get(key)).append("\"");
            }
        }
        sql.append(" where id=").append(data.get("id"));
        System.out.println(sql);
        return sql.toString();
    }
6. 可视化列表
界面

在这里插入图片描述

描述

可视化列表页面,提供可视化的增加修改删除操作

7. 可视化详情
界面

在这里插入图片描述

描述

对数据集进行条件过滤、数据聚合、面板设置等。目前支持图表类型有:条形图、折线图、面积图、饼图和词云图

核心代码

数据聚合

@Override
    public ChartResp queryAggregationData(Integer id, Map filters, YAggregation y, XAggregation x) {
        Experiment experiment = expMapper.queryExpById(id);
        List<VisualFilter> filterList = new ArrayList<>();
        for (Object key : filters.keySet()) {
            VisualFilter f = JSON.parseObject(JSONObject.toJSONString(filters.get(key)), VisualFilter.class);
            filterList.add(f);
        }
        if (StringUtils.isNotBlank(x.getXField())) {
            String sql = SqlUtil.dataAggregationSql(experiment.getTableName(), filterList, y, x);
            List<Map<String, Object>> maps = subMapper.execAggregation(sql);
            List<String> xAxis = new ArrayList<>();
            List<String> yAxis = new ArrayList<>();
            maps.forEach(map -> {
                xAxis.add(String.valueOf(map.get(x.getXField())));
                yAxis.add(String.valueOf(map.get(Constant.SORT_METRIC)));
            });
            return new ChartResp(xAxis, yAxis);
        }
        return new ChartResp();
    }
8. 系统用户管理
界面

在这里插入图片描述

描述

系统管理员可以通过该界面对普通用户进行增加修改删除操作

9. 系统操作日志
界面

在这里插入图片描述

描述

系统操作日志,对用户操作时的描述,地址,参数等信息进行记录

核心代码
public class UserLogAspect {
    private static final Logger logger = LoggerFactory.getLogger(UserLogAspect.class);

    private LogService logService;
    private HttpSession httpSession;

    @Autowired
    public UserLogAspect(LogService logService, HttpSession httpSession) {
        this.logService = logService;
        this.httpSession = httpSession;
    }

    /**
     * 注解Pointcut切入点
     * 定义出一个或一组方法,当执行这些方法时可产生通知
     * 指向你的切面类方法
     * 由于这里使用了自定义注解所以指向你的自定义注解
     */
    @Pointcut("@annotation(com.example.exp.annotation.ExpLog)")
    public void logPointCut() {
    }

    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long beginTime = System.currentTimeMillis();
        // 执行方法
        Object result = point.proceed();
        // 执行时长(毫秒)
        long time = System.currentTimeMillis() - beginTime;
        // 异步保存日志
        saveLog(point, time, null);
        return result;
    }

    /**
     * 异常通知 记录操作报错日志
     */
    @AfterThrowing(pointcut = "logPointCut()", throwing = "e")
    public void doAfterThrowing(JoinPoint point, Throwable e) throws Exception {
        saveLog(point, 0, e.getMessage());
    }

    void saveLog(JoinPoint joinPoint, long time, String errorInfo) throws Exception {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        UserLog userLog = new UserLog();
        ExpLog expLog = method.getAnnotation(ExpLog.class);
        if (expLog != null) {
            // 注解上的描述
            userLog.setDes(expLog.description());
        }
        // 获取request
        HttpServletRequest request = HttpContextUtils.getRequest();
        // 设置根路径
        String path = request.getContextPath();
        String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path;
        userLog.setRoot(basePath);
        // 获取uri
        userLog.setUri(request.getRequestURI());
        // 请求方式
        userLog.setMethod(request.getMethod());
        // 用户名
        User user = (User) httpSession.getAttribute("user");
        String username = user.getUsername();
        // 请求的参数
        userLog.setParams(getServiceMethodParams(joinPoint));
        if (StringUtils.isBlank(username)) {
            userLog.setUsername("获取到的用户名为空");
        } else {
            userLog.setUsername(username);
        }
        userLog.setSpendTime((int) time);
        // 相应状态 1:成功, 0:失败
        userLog.setResult(1);
        // 日志类型
        userLog.setType("info");
        // 错误信息
        if (errorInfo != null) {
            userLog.setErrorInfo(errorInfo);
            userLog.setResult(0);
            userLog.setType("error");
        }
        // 保存系统日志
        logService.save(userLog);
    }

    /**
     * 获取json格式的参数用于存储到数据库中
     *
     * @param joinPoint
     * @return
     * @throws Exception
     */
    private String getServiceMethodParams(JoinPoint joinPoint)
            throws Exception {
        Object[] arguments = joinPoint.getArgs();
        ObjectMapper om = new ObjectMapper();
        // 上传文件时的特殊处理
        String methodName = joinPoint.getSignature().getName();
        if (methodName.equals("uploadExpDataFile")) {
            return om.writeValueAsString(arguments[1]);
        }
        return om.writeValueAsString(arguments);
    }

}
10. 实验数据管理
界面

在这里插入图片描述

描述

系统管理员可以对实验数据直接操作

四、总结

1.后续将添加地图可视化,丰富数据聚合类型
2.将从库主题数据存放到ES中,提升检索效率,并提供更多的数据聚合分析方式。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值