一、介绍
实验数据管理与分析系统,实现了对实验数据和统计数据的备份、统一管理、可视化分析展示、操作日志展示等功能。系统角色分为管理员与普通用户,普通用户可以上传实验数据到系统主库,将主库数据迁移到从库并进行操作,然后针对从库数据进行数据可视化展示。系统管理员可以直接操作主库实验数据,并对普通用户进行管理,查看系统用户操作日志。
二、架构
系统采用单体架构,后端使用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. 实验数据管理
界面
描述
系统管理员可以对实验数据直接操作
四、总结
后续将添加地图可视化,丰富数据聚合类型