diff --git a/ruoyi-ui/src/api/monitor/teemlink.js b/ruoyi-ui/src/api/monitor/teemlink.js new file mode 100644 index 000000000..b5d8adf55 --- /dev/null +++ b/ruoyi-ui/src/api/monitor/teemlink.js @@ -0,0 +1,85 @@ +import request from '@/utils/request' + +// 查询定时任务调度列表 +export function list(query) { + return request({ + url: '/monitor/teemlink/list', + method: 'get', + params: query + }) +} + +// 查询定时任务调度详细 +export function get(tableName, columnName) { + const data = { + tableName, + columnName + } + return request({ + url: '/monitor/teemlink/stat', + method: 'get', + params: data + }) +} + +// 新增定时任务调度 +export function addJob(data) { + return request({ + url: '/monitor/job', + method: 'post', + data: data + }) +} + +// 修改定时任务调度 +export function updateJob(data) { + return request({ + url: '/monitor/job', + method: 'put', + data: data + }) +} + +// 删除定时任务调度 +export function delJob(jobId) { + return request({ + url: '/monitor/teemlink/' + jobId, + method: 'delete' + }) +} + +// 导出定时任务调度 +export function exportJob(query) { + return request({ + url: '/monitor/teemlink/export', + method: 'get', + params: query + }) +} + +// 任务状态修改 +export function changeJobStatus(jobId, status) { + const data = { + jobId, + status + } + return request({ + url: '/monitor/teemlink/changeStatus', + method: 'put', + data: data + }) +} + + +// 定时任务立即执行一次 +export function runJob(jobId, jobGroup) { + const data = { + jobId, + jobGroup + } + return request({ + url: '/monitor/teemlink/run', + method: 'put', + data: data + }) +} diff --git a/ruoyi-ui/src/views/monitor/teemlink/index.vue b/ruoyi-ui/src/views/monitor/teemlink/index.vue new file mode 100644 index 000000000..906e48433 --- /dev/null +++ b/ruoyi-ui/src/views/monitor/teemlink/index.vue @@ -0,0 +1,185 @@ +<template> + <div class="app-container"> + <el-form :model="queryParams" :rules="rules" ref="queryForm" :inline="true" label-width="100px"> + <el-form-item label="表名" prop="tableName" clearable> + <el-input + v-model="queryParams.tableName" + placeholder="请输入表名" + clearable + size="small" + @keyup.enter.native="handleQuery" + /> + </el-form-item> + <el-form-item label="字段名称" prop="columnName" clearable> + <el-input + v-model="queryParams.columnName" + placeholder="请输入字段名称" + clearable + size="small" + @keyup.enter.native="handleQuery" + /> + </el-form-item> + <!-- <el-form-item label="字段类型" prop="fieldType" clearable> + <el-select v-model="queryParams.fieldType" placeholder="请选字段类型"> + <el-option + v-for="item in fieldTypeOptions" + :value="item.value" + :label="item.label" + :key="item.value" + ></el-option> + </el-select> + </el-form-item>--> + <el-form-item> + <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button> + <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button> + </el-form-item> + </el-form> + + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button + type="warning" + icon="el-icon-download" + size="mini" + @click="handleExport" + v-hasPermi="['system:user:export']" + >导出</el-button> + </el-col> + </el-row> + + <el-table v-loading="loading" :data="dataList" @selection-change="handleSelectionChange"> + <el-table-column label="中文表名" align="center" prop="tableChineseName" /> + <el-table-column label="数据库表" align="center" prop="tableDbName" /> + <el-table-column label="中文字段名" align="center" prop="columnChineseName" /> + <el-table-column label="数据库字段名" align="center" prop="columnDbName" /> + <el-table-column label="字段类型" align="center" prop="fieldType" /> + <el-table-column label="字典类型" align="center" prop="dictType" /> + <el-table-column label="异常数据比(%)" align="center" prop="stat" /> + <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> + <template slot-scope="scope"> + <el-button + size="mini" + type="text" + @click="handleStat(scope.row)" + v-hasPermi="['monitor:teemlink:list']" + >数据分析</el-button> + </template> + </el-table-column> + </el-table> + + <pagination + v-show="total > 0" + :total="total" + :page.sync="queryParams.pageIndex" + :limit.sync="queryParams.pageSize" + @pagination="getList" + /> + </div> +</template> + +<script> +import { getToken } from "@/utils/auth"; +import { list, get } from "@/api/monitor/teemlink"; + +export default { + name: "teemLinkStat", + data() { + return { + // 遮罩层 + loading: true, + // 选中数组 + ids: [], + // 非单个禁用 + single: true, + // 非多个禁用 + multiple: true, + // 总条数 + total: 0, + // 办公基价表格数据 + dataList: [], + // 弹出层标题 + title: "", + // 是否显示弹出层 + open: false, + // 查询参数 + queryParams: { + tableName: undefined, + columnName: undefined, + pageIndex: 1, + pageSize: 10 + }, + yearMonthList: [], + fieldTypeOptions: [ + { value: "字符型", label: "字符型" }, + { value: "数值型", label: "数值型" }, + { value: "日期型", label: "日期型" }, + { value: "字典型", label: "字典型" } + ], + // 表单参数 + form: {} + }; + }, + created() { + this.loading = false; + }, + methods: { + /** 查询办公基价列表 */ + getList() { + this.$refs["queryForm"].validate(valid => { + if (valid) { + this.loading = true; + list(this.queryParams).then(response => { + this.dataList = response.rows; + this.total = response.total; + this.loading = false; + }); + } + }); + }, + // 取消按钮 + cancel() { + this.open = false; + this.reset(); + }, + // 表单重置 + reset() { + this.form = { + id: undefined + }; + this.resetForm("form"); + }, + /** 搜索按钮操作 */ + handleQuery() { + this.queryParams.pageIndex = 1; + this.getList(); + }, + /** 重置按钮操作 */ + resetQuery() { + this.resetForm("queryForm"); + this.handleQuery(); + }, + // 多选框选中数据 + handleSelectionChange(selection) { + this.ids = selection.map(item => item.id); + this.single = selection.length != 1; + this.multiple = !selection.length; + }, + /** 统计 */ + handleStat(row) { + this.reset(); + get(row.tableDbName, row.columnDbName).then(response => { + if(response.data.count === response.data.validCount) { + row.stat = '无'; + return; + } + row.stat = + parseFloat( + ((response.data.count - response.data.validCount) / + response.data.count) * + 100 + ).toFixed(2) + "%"; + }); + } + } +}; +</script> diff --git a/ruoyi/pom.xml b/ruoyi/pom.xml index 5526b8dcd..ba121a28c 100644 --- a/ruoyi/pom.xml +++ b/ruoyi/pom.xml @@ -183,7 +183,13 @@ <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-actuator</artifactId> </dependency> - + + <dependency> + <groupId>com.squareup.okhttp3</groupId> + <artifactId>okhttp</artifactId> + <version>3.8.1</version> + </dependency> + <!-- swagger2--> <dependency> <groupId>io.springfox</groupId> diff --git a/ruoyi/src/main/java/com/ruoyi/project/monitor/controller/TeemLinkMonitorController.java b/ruoyi/src/main/java/com/ruoyi/project/monitor/controller/TeemLinkMonitorController.java new file mode 100644 index 000000000..3797f4af6 --- /dev/null +++ b/ruoyi/src/main/java/com/ruoyi/project/monitor/controller/TeemLinkMonitorController.java @@ -0,0 +1,113 @@ +package com.ruoyi.project.monitor.controller; + +import com.alibaba.fastjson.JSON; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.redis.RedisCache; +import com.ruoyi.framework.web.controller.BaseController; +import com.ruoyi.framework.web.domain.AjaxResult; +import com.ruoyi.framework.web.page.TableDataInfo; +import com.ruoyi.project.monitor.domain.TeemLinkExt; +import com.ruoyi.project.monitor.domain.TeemLinkMonitorQueryModel; +import com.ruoyi.project.monitor.domain.TeemLinkTable; +import com.ruoyi.project.monitor.service.ITeemLinkMonitorService; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * teemlink数据监控 + * + * @author lihe + */ +@RestController +@RequestMapping("/monitor/teemlink") +public class TeemLinkMonitorController extends BaseController { + + @Value("${uv.formMonitor}") + private String teemLinkPath; + @Autowired + private ITeemLinkMonitorService teemLinkMonitorService; +// @Autowired +// private RestTemplate restTemplate; + + @Autowired + private RedisCache redisCache; + + /** + * teemlink表单查询 + * + * @return + */ + @GetMapping("/list") + public TableDataInfo list(TeemLinkMonitorQueryModel monitorQueryModel) { + + List<TeemLinkExt> list = redisCache.getCacheList("teemlink_form"); + if (null == list || 0 == list.size()) { + OkHttpClient client = new OkHttpClient(); + Request request = new Request.Builder() + .url(teemLinkPath) + .build(); + try (Response response = client.newCall(request).execute()) { + + TeemLinkTable[] arr = JSON.parseObject(response.body().string(), TeemLinkTable[].class); + list = buildTeemLinkExt(Arrays.asList(arr)); + } catch (IOException e) { + e.printStackTrace(); + } + } + + + List<TeemLinkExt> selectList = + list.stream() + .filter(sp -> (StringUtils.isNotEmpty(monitorQueryModel.getTableName()) + && ((sp.getTableChineseName().contains(monitorQueryModel.getTableName())) || + (sp.getTableDbName().contains(monitorQueryModel.getTableName()))) || (StringUtils.isEmpty(monitorQueryModel.getTableName()))) + ).collect(Collectors.toList()); + int total = selectList.size(); + selectList = selectList.stream().skip((monitorQueryModel.getPageIndex() - 1) * monitorQueryModel.getPageSize()) + .limit(monitorQueryModel.getPageSize()).collect(Collectors.toList()); + + return getDataTable(selectList, total); + } + + private List<TeemLinkExt> buildTeemLinkExt(List<TeemLinkTable> teemLinkTables) { + List<TeemLinkExt> linkExts = new LinkedList<>(); + + teemLinkTables.forEach(tableLinkTable -> { + tableLinkTable.getColumns().forEach(teemLinkColumn -> { + TeemLinkExt teemLinkExt = new TeemLinkExt(); + teemLinkExt.setTableChineseName(tableLinkTable.getChineseName()); + teemLinkExt.setTableDbName(tableLinkTable.getDbName()); + teemLinkExt.setColumnChineseName(teemLinkColumn.getChineseName()); + teemLinkExt.setColumnDbName(teemLinkColumn.getDbName()); + teemLinkExt.setFieldType(teemLinkColumn.getFieldType()); + teemLinkExt.setDictType(teemLinkColumn.getDictType()); + linkExts.add(teemLinkExt); + }); + }); + + return linkExts; + } + + /** + * teemlink表单数据统计 + * + * @return + */ + @GetMapping("/stat") + public AjaxResult stat(@RequestParam("tableName") String tableName, @RequestParam("columnName") String columnName) { + return AjaxResult.success(teemLinkMonitorService.stat(tableName, columnName)); + } +} diff --git a/ruoyi/src/main/java/com/ruoyi/project/monitor/domain/TeemLinkColumn.java b/ruoyi/src/main/java/com/ruoyi/project/monitor/domain/TeemLinkColumn.java new file mode 100644 index 000000000..9593e0e21 --- /dev/null +++ b/ruoyi/src/main/java/com/ruoyi/project/monitor/domain/TeemLinkColumn.java @@ -0,0 +1,66 @@ +package com.ruoyi.project.monitor.domain; + +/** + * teemlink配置的表单列 + */ +public class TeemLinkColumn { + /** + * 中文名称 + */ + private String chineseName; + /** + * 数据库名称 + */ + private String dbName; + /** + * 字段类型 + * 字符、日期、数值、字典 + */ + private String fieldType; + /** + * 字典类型 + */ + private String dictType; + + public String getChineseName() { + return chineseName; + } + + public void setChineseName(String chineseName) { + this.chineseName = chineseName; + } + + public String getDbName() { + return dbName; + } + + public void setDbName(String dbName) { + this.dbName = dbName; + } + + public String getFieldType() { + return fieldType; + } + + public void setFieldType(String fieldType) { + this.fieldType = fieldType; + } + + public String getDictType() { + return dictType; + } + + public void setDictType(String dictType) { + this.dictType = dictType; + } + + @Override + public String toString() { + return "TeemLinkColumn{" + + "chineseName='" + chineseName + '\'' + + ", dbName='" + dbName + '\'' + + ", fieldType='" + fieldType + '\'' + + ", dictType='" + dictType + '\'' + + '}'; + } +} diff --git a/ruoyi/src/main/java/com/ruoyi/project/monitor/domain/TeemLinkExt.java b/ruoyi/src/main/java/com/ruoyi/project/monitor/domain/TeemLinkExt.java new file mode 100644 index 000000000..98c33577a --- /dev/null +++ b/ruoyi/src/main/java/com/ruoyi/project/monitor/domain/TeemLinkExt.java @@ -0,0 +1,71 @@ +package com.ruoyi.project.monitor.domain; + +/** + * 展示用的model + * @author lihe + */ +public class TeemLinkExt { + private String tableChineseName; + private String tableDbName; + private String columnChineseName; + private String columnDbName; + private String fieldType; + private String dictType; + private Float stat; + + public String getTableChineseName() { + return tableChineseName; + } + + public void setTableChineseName(String tableChineseName) { + this.tableChineseName = tableChineseName; + } + + public String getTableDbName() { + return tableDbName; + } + + public void setTableDbName(String tableDbName) { + this.tableDbName = tableDbName; + } + + public String getColumnChineseName() { + return columnChineseName; + } + + public void setColumnChineseName(String columnChineseName) { + this.columnChineseName = columnChineseName; + } + + public String getColumnDbName() { + return columnDbName; + } + + public void setColumnDbName(String columnDbName) { + this.columnDbName = columnDbName; + } + + public String getFieldType() { + return fieldType; + } + + public void setFieldType(String fieldType) { + this.fieldType = fieldType; + } + + public String getDictType() { + return dictType; + } + + public void setDictType(String dictType) { + this.dictType = dictType; + } + + public Float getStat() { + return stat; + } + + public void setStat(Float stat) { + this.stat = stat; + } +} diff --git a/ruoyi/src/main/java/com/ruoyi/project/monitor/domain/TeemLinkMonitorQueryModel.java b/ruoyi/src/main/java/com/ruoyi/project/monitor/domain/TeemLinkMonitorQueryModel.java new file mode 100644 index 000000000..1acd76cdf --- /dev/null +++ b/ruoyi/src/main/java/com/ruoyi/project/monitor/domain/TeemLinkMonitorQueryModel.java @@ -0,0 +1,43 @@ +package com.ruoyi.project.monitor.domain; + +/** + * + */ +public class TeemLinkMonitorQueryModel { + private Integer pageIndex; + private Integer pageSize; + private String tableName; + private String columnName; + + public Integer getPageIndex() { + return pageIndex; + } + + public void setPageIndex(Integer pageIndex) { + this.pageIndex = pageIndex; + } + + public Integer getPageSize() { + return pageSize; + } + + public void setPageSize(Integer pageSize) { + this.pageSize = pageSize; + } + + public String getTableName() { + return tableName; + } + + public void setTableName(String tableName) { + this.tableName = tableName; + } + + public String getColumnName() { + return columnName; + } + + public void setColumnName(String columnName) { + this.columnName = columnName; + } +} diff --git a/ruoyi/src/main/java/com/ruoyi/project/monitor/domain/TeemLinkStat.java b/ruoyi/src/main/java/com/ruoyi/project/monitor/domain/TeemLinkStat.java new file mode 100644 index 000000000..f853dd21c --- /dev/null +++ b/ruoyi/src/main/java/com/ruoyi/project/monitor/domain/TeemLinkStat.java @@ -0,0 +1,27 @@ +package com.ruoyi.project.monitor.domain; + +/** + * teemlink数据统计 + * + * @author lihe + */ +public class TeemLinkStat { + private Integer count; + private Integer validCount; + + public Integer getCount() { + return count; + } + + public void setCount(Integer count) { + this.count = count; + } + + public Integer getValidCount() { + return validCount; + } + + public void setValidCount(Integer validCount) { + this.validCount = validCount; + } +} diff --git a/ruoyi/src/main/java/com/ruoyi/project/monitor/domain/TeemLinkTable.java b/ruoyi/src/main/java/com/ruoyi/project/monitor/domain/TeemLinkTable.java new file mode 100644 index 000000000..45e8e2048 --- /dev/null +++ b/ruoyi/src/main/java/com/ruoyi/project/monitor/domain/TeemLinkTable.java @@ -0,0 +1,48 @@ +package com.ruoyi.project.monitor.domain; + +import java.util.List; + +/** + * teemlink配置的表单 + * + * @author lihe + */ +public class TeemLinkTable { + private String chineseName; + private String dbName; + private List<TeemLinkColumn> columns; + + public String getChineseName() { + return chineseName; + } + + public void setChineseName(String chineseName) { + this.chineseName = chineseName; + } + + public String getDbName() { + return dbName; + } + + public void setDbName(String dbName) { + this.dbName = dbName; + } + + public List<TeemLinkColumn> getColumns() { + return columns; + } + + public void setColumns(List<TeemLinkColumn> columns) { + this.columns = columns; + } + + @Override + public String toString() { + return "TeemLinkTable{" + + "chineseName='" + chineseName + '\'' + + ", dbName='" + dbName + '\'' + + ", columns=" + columns + + '}'; + } +} + diff --git a/ruoyi/src/main/java/com/ruoyi/project/monitor/mapper/TeemLinkMonitorMapper.java b/ruoyi/src/main/java/com/ruoyi/project/monitor/mapper/TeemLinkMonitorMapper.java new file mode 100644 index 000000000..dcd0a2529 --- /dev/null +++ b/ruoyi/src/main/java/com/ruoyi/project/monitor/mapper/TeemLinkMonitorMapper.java @@ -0,0 +1,24 @@ +package com.ruoyi.project.monitor.mapper; + +import com.baomidou.dynamic.datasource.annotation.DS; +import com.ruoyi.project.monitor.domain.TeemLinkStat; +import com.ruoyi.project.monitor.domain.TeemLinkTable; +import org.apache.ibatis.annotations.Param; + +/** + * teemlink数据监控 + * + * @author lihe + */ +@DS("teemlink") +public interface TeemLinkMonitorMapper { + + /** + * 查看某表的字段数据统计 + * + * @param tableName + * @param columnName + * @return + */ + TeemLinkStat getColumnStat(@Param("tableName") String tableName, @Param("columnName") String columnName); +} diff --git a/ruoyi/src/main/java/com/ruoyi/project/monitor/service/ITeemLinkMonitorService.java b/ruoyi/src/main/java/com/ruoyi/project/monitor/service/ITeemLinkMonitorService.java new file mode 100644 index 000000000..03c7019e5 --- /dev/null +++ b/ruoyi/src/main/java/com/ruoyi/project/monitor/service/ITeemLinkMonitorService.java @@ -0,0 +1,22 @@ +package com.ruoyi.project.monitor.service; + +import com.ruoyi.project.monitor.domain.SysOperLog; +import com.ruoyi.project.monitor.domain.TeemLinkStat; + +import java.util.List; + +/** + * teemlink 服务层 + * + * @author ruoyi + */ +public interface ITeemLinkMonitorService { + /** + * 数据统计 + * + * @param tableName + * @param columnName + * @return + */ + TeemLinkStat stat(String tableName, String columnName); +} diff --git a/ruoyi/src/main/java/com/ruoyi/project/monitor/service/impl/TeemLinkMonitorServiceImpl.java b/ruoyi/src/main/java/com/ruoyi/project/monitor/service/impl/TeemLinkMonitorServiceImpl.java new file mode 100644 index 000000000..76e1608df --- /dev/null +++ b/ruoyi/src/main/java/com/ruoyi/project/monitor/service/impl/TeemLinkMonitorServiceImpl.java @@ -0,0 +1,23 @@ +package com.ruoyi.project.monitor.service.impl; + +import com.ruoyi.project.monitor.domain.TeemLinkStat; +import com.ruoyi.project.monitor.mapper.TeemLinkMonitorMapper; +import com.ruoyi.project.monitor.service.ITeemLinkMonitorService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * 服务层处理 + * + * @author ruoyi + */ +@Service +public class TeemLinkMonitorServiceImpl implements ITeemLinkMonitorService { + @Autowired + private TeemLinkMonitorMapper teemLinkMonitorMapper; + + @Override + public TeemLinkStat stat(String tableName, String columnName) { + return teemLinkMonitorMapper.getColumnStat(tableName, columnName); + } +} diff --git a/ruoyi/src/main/resources/application.yml b/ruoyi/src/main/resources/application.yml index 3bc2c238a..f39ec85b7 100644 --- a/ruoyi/src/main/resources/application.yml +++ b/ruoyi/src/main/resources/application.yml @@ -13,8 +13,9 @@ ruoyi: # 获取ip地址开关 addressEnabled: false uv: - dataUrl: http://172.16.30.74:9500/api - aitificialOfficeBasePriceUrl: /price/office/artificial?yearMonth=%d&lastYearMonth=%d + dataUrl: http://172.16.30.74:9500/api/ + aitificialOfficeBasePriceUrl: price/office/artificial?yearMonth=%d&lastYearMonth=%d + formMonitor: http://172.16.30.247:9800/tables # 开发环境配置 server: diff --git a/ruoyi/src/main/resources/mybatis/monitor/TeemLinkMonitorMapper.xml b/ruoyi/src/main/resources/mybatis/monitor/TeemLinkMonitorMapper.xml new file mode 100644 index 000000000..f2e3559e9 --- /dev/null +++ b/ruoyi/src/main/resources/mybatis/monitor/TeemLinkMonitorMapper.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE mapper +PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" +"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="com.ruoyi.project.monitor.mapper.TeemLinkMonitorMapper"> + <select id="getColumnStat" resultType="com.ruoyi.project.monitor.domain.TeemLinkStat"> + select count(1) as count, + (select count(1) from ${tableName} where ${columnName} is not null and #{columnName} + <![CDATA[ <> ]]> '') as validCount + from ${tableName} + </select> +</mapper> \ No newline at end of file