This commit is contained in:
2024-03-01 20:28:14 +08:00
commit 076c21dc36
491 changed files with 84482 additions and 0 deletions

View File

@@ -0,0 +1,23 @@
/**
* Created by gxl.gao on 2017/10/24.
*/
import StatisticsPage from './statisticsClientPage/index'
module.exports = function () {
this.bindHook('header_menu', function (menu) {
menu.statisticsPage = {
path: '/statistic',
name: '系统信息',
icon: 'bar-chart',
adminFlag: true
}
})
this.bindHook('app_route', function (app) {
app.statisticsPage = {
path: '/statistic',
component: StatisticsPage
}
})
}

View File

@@ -0,0 +1,176 @@
/**
* Created by gxl.gao on 2017/10/24.
*/
const baseController = require('controllers/base.js');
const statisMockModel = require('./statisMockModel.js');
const groupModel = require('models/group.js');
const projectModel = require('models/project.js');
const interfaceModel = require('models/interface.js');
const interfaceCaseModel = require('models/interfaceCase.js');
const yapi = require('yapi.js');
const config = require('./index.js');
const commons = require('./util.js');
const os = require('os');
let cpu = require('cpu-load');
class statisMockController extends baseController {
constructor(ctx) {
super(ctx);
this.Model = yapi.getInst(statisMockModel);
this.groupModel = yapi.getInst(groupModel);
this.projectModel = yapi.getInst(projectModel);
this.interfaceModel = yapi.getInst(interfaceModel);
this.interfaceCaseModel = yapi.getInst(interfaceCaseModel);
}
/**
* 获取所有统计总数
* @interface statismock/count
* @method get
* @category statistics
* @foldnumber 10
* @returns {Object}
*/
async getStatisCount(ctx) {
try {
let groupCount = await this.groupModel.getGroupListCount();
let projectCount = await this.projectModel.getProjectListCount();
let interfaceCount = await this.interfaceModel.getInterfaceListCount();
let interfaceCaseCount = await this.interfaceCaseModel.getInterfaceCaseListCount();
return (ctx.body = yapi.commons.resReturn({
groupCount,
projectCount,
interfaceCount,
interfaceCaseCount
}));
} catch (err) {
ctx.body = yapi.commons.resReturn(null, 400, err.message);
}
}
/**
* 获取所有mock接口数据信息
* @interface statismock/get
* @method get
* @category statistics
* @foldnumber 10
* @returns {Object}
*/
async getMockDateList(ctx) {
try {
let mockCount = await this.Model.getTotalCount();
let mockDateList = [];
if (!this.getRole() === 'admin') {
return (ctx.body = yapi.commons.resReturn(null, 405, '没有权限'));
}
// 默认时间是30 天为一周期
let dateInterval = commons.getDateRange();
mockDateList = await this.Model.getDayCount(dateInterval);
return (ctx.body = yapi.commons.resReturn({ mockCount, mockDateList }));
} catch (err) {
ctx.body = yapi.commons.resReturn(null, 400, err.message);
}
}
/**
* 获取邮箱状态信息
* @interface statismock/getSystemStatus
* @method get
* @category statistics
* @foldnumber 10
* @returns {Object}
*/
async getSystemStatus(ctx) {
try {
let mail = '';
if (yapi.WEBCONFIG.mail && yapi.WEBCONFIG.mail.enable) {
mail = await this.checkEmail();
// return ctx.body = yapi.commons.resReturn(result);
} else {
mail = '未配置';
}
let load = (await this.cupLoad()) * 100;
let systemName = os.platform();
let totalmem = commons.transformBytesToGB(os.totalmem());
let freemem = commons.transformBytesToGB(os.freemem());
let uptime = commons.transformSecondsToDay(os.uptime());
let data = {
mail,
systemName,
totalmem,
freemem,
uptime,
load: load.toFixed(2)
};
return (ctx.body = yapi.commons.resReturn(data));
} catch (err) {
ctx.body = yapi.commons.resReturn(null, 400, err.message);
}
}
checkEmail() {
return new Promise((resolve, reject) => {
let result = {};
yapi.mail.verify(error => {
if (error) {
result = '不可用';
resolve(result);
} else {
result = '可用';
resolve(result);
}
});
});
}
async groupDataStatis(ctx) {
try {
let groupData = await this.groupModel.list();
let result = [];
for (let i = 0; i < groupData.length; i++) {
let group = groupData[i];
let groupId = group._id;
const data = {
name: group.group_name,
interface: 0,
mock: 0,
project: 0
};
result.push(data);
let projectCount = await this.projectModel.listCount(groupId);
let projectData = await this.projectModel.list(groupId);
let interfaceCount = 0;
for (let j = 0; j < projectData.length; j++) {
let project = projectData[j];
interfaceCount += await this.interfaceModel.listCount({
project_id: project._id
});
}
let mockCount = await this.Model.countByGroupId(groupId);
data.interface = interfaceCount;
data.project = projectCount;
data.mock = mockCount;
}
return (ctx.body = yapi.commons.resReturn(result));
} catch (err) {
ctx.body = yapi.commons.resReturn(null, 400, err.message);
}
}
cupLoad() {
return new Promise((resolve, reject) => {
cpu(1000, function(load) {
resolve(load);
});
});
}
}
module.exports = statisMockController;

View File

@@ -0,0 +1,10 @@
/**
* Created by gxl.gao on 2017/10/24.
*/
module.exports = {
server: true,
client: true,
httpCodes: [
100,101,102,200,201,202,203,204,205,206,207,208,226,300,301,302,303,304,305,307,308,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,422,423,424,426,428,429,431,500,501,502,503,504,505,506,507,508,510,511
]
}

View File

@@ -0,0 +1,82 @@
/**
* Created by gxl.gao on 2017/10/24.
*/
const yapi = require('yapi.js');
const mongoose = require('mongoose');
const controller = require('./controller');
const statisModel = require('./statisMockModel.js');
const commons = require('./util.js');
module.exports = function() {
yapi.connect.then(function() {
let Col = mongoose.connection.db.collection('statis_mock');
Col.createIndex({
interface_id: 1
});
Col.createIndex({
project_id: 1
});
Col.createIndex({
group_id: 1
});
Col.createIndex({
time: 1
});
Col.createIndex({
date: 1
});
});
this.bindHook('add_router', function(addRouter) {
addRouter({
controller: controller,
method: 'get',
path: 'statismock/count',
action: 'getStatisCount'
});
addRouter({
controller: controller,
method: 'get',
path: 'statismock/get',
action: 'getMockDateList'
});
addRouter({
controller: controller,
method: 'get',
path: 'statismock/get_system_status',
action: 'getSystemStatus'
});
addRouter({
controller: controller,
method: 'get',
path: 'statismock/group_data_statis',
action: 'groupDataStatis'
});
});
// MockServer生成mock数据后触发
this.bindHook('mock_after', function(context) {
let interfaceId = context.interfaceData._id;
let projectId = context.projectData._id;
let groupId = context.projectData.group_id;
//let ip = context.ctx.originalUrl;
let ip = yapi.commons.getIp(context.ctx);
let data = {
interface_id: interfaceId,
project_id: projectId,
group_id: groupId,
time: yapi.commons.time(),
ip: ip,
date: commons.formatYMD(new Date())
};
let inst = yapi.getInst(statisModel);
try {
inst.save(data).then();
} catch (e) {
yapi.commons.log('mockStatisError', e);
}
});
};

View File

@@ -0,0 +1,75 @@
/**
* Created by gxl.gao on 2017/10/24.
*/
const yapi = require('yapi.js');
const baseModel = require('models/base.js');
class statisMockModel extends baseModel {
getName() {
return 'statis_mock';
}
getSchema() {
return {
interface_id: { type: Number, required: true },
project_id: { type: Number, required: true },
group_id: { type: Number, required: true },
time: Number, //'时间戳'
ip: String,
date: String
};
}
countByGroupId(id){
return this.model.countDocuments({
group_id: id
})
}
save(data) {
let m = new this.model(data);
return m.save();
}
getTotalCount() {
return this.model.countDocuments({});
}
async getDayCount(timeInterval) {
let end = timeInterval[1];
let start = timeInterval[0];
let data = [];
const cursor = this.model.aggregate([
{
$match: {
date: { $gt: start, $lte: end }
}
},
{
$group: {
_id: '$date', //$region is the column name in collection
count: { $sum: 1 }
}
},
{
$sort: { _id: 1 }
}
]).cursor({}).exec();
await cursor.eachAsync(doc => data.push(doc));
return data;
}
list() {
return this.model.find({}).select('date').exec();
}
up(id, data) {
data.up_time = yapi.commons.time();
return this.model.updateOne({
_id: id
}, data, { runValidators: true });
}
}
module.exports = statisMockModel;

View File

@@ -0,0 +1,77 @@
/**
* Created by gxl.gao on 2017/10/25.
*/
import React, { Component } from 'react';
// import PropTypes from 'prop-types'
import axios from 'axios';
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend } from 'recharts';
import { Spin } from 'antd';
class StatisChart extends Component {
static propTypes = {};
constructor(props) {
super(props);
this.state = {
showLoading: true,
chartDate: {
mockCount: 0,
mockDateList: []
}
};
}
UNSAFE_componentWillMount() {
this.getMockData();
}
// 获取mock 请求次数信息
async getMockData() {
let result = await axios.get('/api/plugin/statismock/get');
if (result.data.errcode === 0) {
let mockStatisData = result.data.data;
this.setState({
showLoading: false,
chartDate: { ...mockStatisData }
});
}
}
render() {
const width = 1050;
const { mockCount, mockDateList } = this.state.chartDate;
return (
<div>
<Spin spinning={this.state.showLoading}>
<div className="statis-chart-content">
<h3 className="statis-title">mock 接口访问总数为{mockCount.toLocaleString()}</h3>
<div className="statis-chart">
<LineChart
width={width}
height={300}
data={mockDateList}
margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
>
<XAxis dataKey="_id" />
<YAxis />
<CartesianGrid strokeDasharray="7 3" />
<Tooltip />
<Legend />
<Line
name="mock统计值"
type="monotone"
dataKey="count"
stroke="#8884d8"
activeDot={{ r: 8 }}
/>
</LineChart>
</div>
<div className="statis-footer">过去3个月mock接口调用情况</div>
</div>
</Spin>
</div>
);
}
}
export default StatisChart;

View File

@@ -0,0 +1,47 @@
import React from 'react';
import { Table } from 'antd';
import PropTypes from 'prop-types';
const columns = [
{
title: 'Group',
dataIndex: 'name',
key: 'name'
},
{
title: '项目',
dataIndex: 'project',
key: 'project'
},
{
title: '接口',
dataIndex: 'interface',
key: 'interface'
},
{
title: 'mock数据',
dataIndex: 'mock',
key: 'mock'
}
];
const StatisTable = props => {
const { dataSource } = props;
return (
<div className="m-row-table">
<h3 className="statis-title">分组数据详情</h3>
<Table
className="statis-table"
pagination={false}
dataSource={dataSource}
columns={columns}
/>
</div>
);
};
StatisTable.propTypes = {
dataSource: PropTypes.array
};
export default StatisTable;

View File

@@ -0,0 +1,210 @@
/**
* Created by gxl.gao on 2017/10/25.
*/
import React, { Component } from 'react';
import { connect } from 'react-redux';
import axios from 'axios';
import PropTypes from 'prop-types';
import './index.scss';
// import { withRouter } from 'react-router-dom';
import { Row, Col, Tooltip, Icon } from 'antd';
import { setBreadcrumb } from 'client/reducer/modules/user';
import StatisChart from './StatisChart';
import StatisTable from './StatisTable';
const CountOverview = props => (
<Row type="flex" justify="space-start" className="m-row">
<Col className="gutter-row" span={6}>
<span>
分组总数
<Tooltip placement="rightTop" title="统计yapi中一共开启了多少可见的公共分组">
<Icon className="m-help" type="question-circle" />
</Tooltip>
</span>
<h2 className="gutter-box">{props.date.groupCount}</h2>
</Col>
<Col className="gutter-row" span={6}>
<span>
项目总数
<Tooltip placement="rightTop" title="统计yapi中建立的所有项目总数">
<Icon className="m-help" type="question-circle" />
</Tooltip>
</span>
<h2 className="gutter-box">{props.date.projectCount}</h2>
</Col>
<Col className="gutter-row" span={6}>
<span>
接口总数
<Tooltip placement="rightTop" title="统计yapi所有项目中的所有接口总数">
{/*<a href="javascript:void(0)" className="m-a-help">?</a>*/}
<Icon className="m-help" type="question-circle" />
</Tooltip>
</span>
<h2 className="gutter-box">{props.date.interfaceCount}</h2>
</Col>
<Col className="gutter-row" span={6}>
<span>
测试接口总数
<Tooltip placement="rightTop" title="统计yapi所有项目中的所有测试接口总数">
{/*<a href="javascript:void(0)" className="m-a-help">?</a>*/}
<Icon className="m-help" type="question-circle" />
</Tooltip>
</span>
<h2 className="gutter-box">{props.date.interfaceCaseCount}</h2>
</Col>
</Row>
);
CountOverview.propTypes = {
date: PropTypes.object
};
const StatusOverview = props => (
<Row type="flex" justify="space-start" className="m-row">
<Col className="gutter-row" span={6}>
<span>
操作系统类型
<Tooltip
placement="rightTop"
title="操作系统类型,返回值有'darwin', 'freebsd', 'linux', 'sunos' , 'win32'"
>
<Icon className="m-help" type="question-circle" />
</Tooltip>
</span>
<h2 className="gutter-box">{props.data.systemName}</h2>
</Col>
<Col className="gutter-row" span={6}>
<span>
cpu负载
<Tooltip placement="rightTop" title="cpu的总负载情况">
<Icon className="m-help" type="question-circle" />
</Tooltip>
</span>
<h2 className="gutter-box">{props.data.load} %</h2>
</Col>
<Col className="gutter-row" span={6}>
<span>
系统空闲内存总量 / 内存总量
<Tooltip placement="rightTop" title="系统空闲内存总量 / 内存总量">
<Icon className="m-help" type="question-circle" />
</Tooltip>
</span>
<h2 className="gutter-box">
{props.data.freemem} G / {props.data.totalmem} G{' '}
</h2>
</Col>
<Col className="gutter-row" span={6}>
<span>
邮箱状态
<Tooltip placement="rightTop" title="检测配置文件中配置邮箱的状态">
<Icon className="m-help" type="question-circle" />
</Tooltip>
</span>
<h2 className="gutter-box">{props.data.mail}</h2>
</Col>
</Row>
);
StatusOverview.propTypes = {
data: PropTypes.object
};
@connect(
null,
{
setBreadcrumb
}
)
class statisticsPage extends Component {
static propTypes = {
setBreadcrumb: PropTypes.func
};
constructor(props) {
super(props);
this.state = {
count: {
groupCount: 0,
projectCount: 0,
interfaceCount: 0,
interfactCaseCount: 0
},
status: {
mail: '',
systemName: '',
totalmem: '',
freemem: '',
uptime: ''
},
dataTotal: []
};
}
async UNSAFE_componentWillMount() {
this.props.setBreadcrumb([{ name: '系统信息' }]);
this.getStatisData();
this.getSystemStatusData();
this.getGroupData();
}
// 获取统计数据
async getStatisData() {
let result = await axios.get('/api/plugin/statismock/count');
if (result.data.errcode === 0) {
let statisData = result.data.data;
this.setState({
count: { ...statisData }
});
}
}
// 获取系统信息
async getSystemStatusData() {
let result = await axios.get('/api/plugin/statismock/get_system_status');
if (result.data.errcode === 0) {
let statusData = result.data.data;
this.setState({
status: { ...statusData }
});
}
}
// 获取分组详细信息
async getGroupData() {
let result = await axios.get('/api/plugin/statismock/group_data_statis');
if (result.data.errcode === 0) {
let statusData = result.data.data;
statusData.map(item => {
return (item['key'] = item.name);
});
this.setState({
dataTotal: statusData
});
}
}
render() {
const { count, status, dataTotal } = this.state;
return (
<div className="g-statistic">
<div className="content">
<h2 className="title">系统状况</h2>
<div className="system-content">
<StatusOverview data={status} />
</div>
<h2 className="title">数据统计</h2>
<div>
<CountOverview date={count} />
<StatisTable dataSource={dataTotal} />
<StatisChart />
</div>
</div>
</div>
);
}
}
export default statisticsPage;

View File

@@ -0,0 +1,83 @@
@import '../../../client/styles/mixin';
.g-statistic {
@include row-width-limit;
margin: 0 auto .24rem;
margin-top: 24px;
min-width: 11.2rem;
.content {
-webkit-box-flex: 1;
padding: 24px;
width:100%;
background: #fff;
min-height: 5rem;
// overflow-x: scroll;
}
.m-row {
border-bottom: 1px solid #f0f0f0;
padding: 16px 0;
}
.m-row-table {
padding-top: 16px
}
.statis-table {
margin-left: 16px;
}
.m-help {
margin-left: 5px;
border-radius: 12px;
color: #2395f1;
}
.gutter-row {
padding-left: 24px;
border-left: 1px solid #f0f0f0;
}
.gutter-row:first-child {
border-left: 0
}
.gutter-box {
margin-top: 8px;
//margin-bottom: 16px;
//margin: 8px 0 16px;
}
.statis-chart-content {
margin-top: 8px;
}
.statis-title{
padding: 8px 8px 24px;
}
.statis-chart{
margin:0 auto;
text-align: center;
}
.statis-footer{
margin:16px 0;
text-align: center;
width: 1050px;
}
.title{
font-size: 16px;
font-weight: 400;
margin-bottom: 0.16rem;
border-left: 3px solid #2395f1;
padding-left: 8px;
}
.system-content{
margin-bottom: 16px;
}
}

View File

@@ -0,0 +1,56 @@
const fs = require('fs-extra');
const yapi = require('../../server/yapi.js');
const commons = require('../../server/utils/commons');
const dbModule = require('../../server/utils/db.js');
const userModel = require('../../server/models/user.js');
const mongoose = require('mongoose');
yapi.commons = commons;
yapi.connect = dbModule.connect();
const convert2Decimal = num => (num > 9 ? num : `0${num}`);
const formatYMD = (val, joinStr = '-') => {
let date = val;
if (typeof val !== 'object') {
val = val * 1000;
date = new Date(val);
}
return `${[
date.getFullYear(),
convert2Decimal(date.getMonth() + 1),
convert2Decimal(date.getDate())
].join(joinStr)}`;
};
function run() {
let time = yapi.commons.time() - 10000000;
let data = i => {
time = time - yapi.commons.rand(10000, 1000000);
return {
interface_id: 94,
project_id: 25,
group_id: 19,
time: time,
ip: '1.1.1.1',
date: formatYMD(time)
};
};
yapi.connect
.then(function() {
let logCol = mongoose.connection.db.collection('statis_mock');
let arr = [];
for (let i = 0; i < 11; i++) {
if (arr.length >= 5) {
logCol.insert(arr);
arr = [];
}
arr.push(data(i));
}
})
.catch(function(err) {
throw new Error(err.message);
});
}
run();

View File

@@ -0,0 +1,150 @@
/**
* 获取所需要的日期区间点
* @param time {Number} Number是ele日期区间选择组件返回的结果
* Number是之前时刻距离今天的间隔天数默认是90天
* @param start {String} 日期对象,日期区间的开始点 '2017-01-17 00:00:00'
* @param withToday {Boolean} 是否包含今天
* @return {Array} ['2017-01-17 00:00:00', '2017-01-20 23:59:59']
*/
exports.getDateRange = (time = 90, start = false, withToday = true) => {
const gapTime = time * 24 * 3600 * 1000;
if (!start) {
// 没有规定start时间
let endTime = getNowMidnightDate().getTime();
if (!withToday) {
endTime -= 86400000;
}
return [this.formatYMD(endTime - gapTime), this.formatYMD(endTime - 1000)];
}
const startTime = dateSpacialWithSafari(start);
const endTime = startTime + (gapTime - 1000);
return [start, this.formatYMD(endTime)];
}
// 时间
const convert2Decimal = num => (num > 9 ? num : `0${num}`)
/**
* 获取距今天之前多少天的所有时间
* @param time {Number} Number是ele日期区间选择组件返回的结果
* Number是之前时刻距离今天的间隔天数默认是30天
* @return {Array} ['2017-01-17', '2017-01-28', '2017-10-29',...]
*/
exports.getDateInterval = (time = 30) => {
// const gapTime = time * 24 * 3600 * 1000;
// 今天
let endTime = new Date().getTime();
let timeList = []
for (let i = 0; i < time; i++) {
const gapTime = i * 24 * 3600 * 1000;
const time = this.formatYMD(endTime - gapTime);
timeList.push(time);
}
return timeList;
}
/**获取2017-10-27 00:00:00 和 2017-10-27 23:59:59的时间戳
* @param date {String} "2017-10-27"
* @return {Array} [ 1509033600000, 1509119999000 ]
*/
exports.getTimeInterval = (date) => {
const startTime = (getNowMidnightDate(date).getTime()-86400000)/1000;
const endTime =(getNowMidnightDate(date).getTime()-1000)/1000;
return [startTime, endTime];
}
/**
* 获取当前时间午夜0点的日期对象
*/
const getNowMidnightDate = (time) => {
let date;
if (time) {
date = new Date(time);
} else {
date = new Date();
}
return new Date(date.getFullYear(), date.getMonth(), date.getDate() + 1);
}
/**
* 格式化 年、月、日、时、分、秒
* @param val {Object or String or Number} 日期对象 或是可new Date的对象或时间戳
* @return {String} 2017-01-20 20:00:00
*/
const formatDate = val => {
let date = val;
if (typeof val !== 'object') {
date = new Date(val);
}
return `${[
date.getFullYear(),
convert2Decimal(date.getMonth() + 1),
convert2Decimal(date.getDate())
].join('-')} ${[
convert2Decimal(date.getHours()),
convert2Decimal(date.getMinutes()),
convert2Decimal(date.getSeconds())
].join(':')}`;
}
/**
* 格式化年、月、日
* @param val {Object or String or Number} 日期对象 或是可new Date的对象或时间戳
* @return {String} 2017-01-20
*/
exports.formatYMD = (val, joinStr = '-') => {
let date = val;
if (typeof val !== 'object') {
date = new Date(val);
}
return `${[
date.getFullYear(),
convert2Decimal(date.getMonth() + 1),
convert2Decimal(date.getDate())
].join(joinStr)}`;
}
/**
* 获取所需的时间差值,
* tipnew Date('2017-01-17 00:00:00')在safari下不可用需进行替换
* @param Array ['2017-01-17 00:00:00', '2017-01-20 23:59:59']
* @return {Number} 3
*/
exports.getDayGapFromRange = dateRange => {
const startTime = dateSpacialWithSafari(dateRange[0]);
const endTime = dateSpacialWithSafari(dateRange[1]);
return Math.ceil((endTime - startTime) / 86400000);
}
/**
* dateSpacialWithSafari 格式话safari下通用的格式
* @param str {String} 2017-04-19T11:01:19.074+0800 or 2017-10-10 10:10:10
* @return {number} date.getTime()
*/
const dateSpacialWithSafari = str => {
if (str.indexOf('T') > -1) {
let date;
str.replace(/(\d{4})-(\d{2})-(\d{2})\w(\d{2}):(\d{2}):(\d{2})/, (match, p1, p2, p3, p4, p5, p6) => {
date = new Date(p1, +p2 - 1, p3, p4, p5, p6);
return;
})
return date.getTime();
}
return new Date(str.replace(/-/g, '/')).getTime();
}
/**
* 将内存单位从字节(b)变成GB
*/
exports.transformBytesToGB = bytes => {
return (bytes/1024/1024/1024).toFixed(2)
}
exports.transformSecondsToDay = seconds => {
return (seconds/3600/24).toFixed(2)
}