├── .autod.conf.js
├── .eslintignore
├── .eslintrc
├── .gitignore
├── .travis.yml
├── .vscode
└── settings.json
├── LICENSE
├── README.md
├── app.js
├── app
├── cache
│ ├── web_ip_city_cache_file.txt
│ └── wx_ip_city_cache_file.txt
├── controller
│ ├── api
│ │ ├── emails.js
│ │ ├── errors.js
│ │ ├── remove.js
│ │ ├── system.js
│ │ ├── user.js
│ │ ├── web
│ │ │ ├── ajax.js
│ │ │ ├── analysis.js
│ │ │ ├── environment.js
│ │ │ ├── error.js
│ │ │ ├── pages.js
│ │ │ ├── pvuvip.js
│ │ │ ├── report.js
│ │ │ └── resource.js
│ │ └── wx
│ │ │ ├── ajax.js
│ │ │ ├── analysis.js
│ │ │ ├── error.js
│ │ │ ├── pages.js
│ │ │ ├── pvuvip.js
│ │ │ └── report.js
│ └── web
│ │ ├── home.js
│ │ ├── web.js
│ │ └── wx.js
├── extend
│ └── application.js
├── middleware
│ └── token_required.js
├── model
│ ├── Web
│ │ ├── web_ajaxs.js
│ │ ├── web_environment.js
│ │ ├── web_errors.js
│ │ ├── web_pages.js
│ │ ├── web_pvuvip.js
│ │ ├── web_report.js
│ │ ├── web_resource.js
│ │ └── web_statis.js
│ ├── Wx
│ │ ├── wx_ajaxs.js
│ │ ├── wx_errors.js
│ │ ├── wx_pages.js
│ │ ├── wx_pvuvip.js
│ │ ├── wx_report.js
│ │ └── wx_statis.js
│ ├── email.js
│ ├── ip_library.js
│ ├── system.js
│ └── user.js
├── public
│ ├── css
│ │ └── base.css
│ ├── file
│ │ ├── web-report-axios.min.js.zip
│ │ ├── web-report-default.min.js.zip
│ │ ├── web-report-fetch.min.js.zip
│ │ ├── web-report-jquery.min.js.zip
│ │ ├── web-report-none.min.js.zip
│ │ ├── web-report-vue.min.zip
│ │ └── wx-report-sdk.min.js.zip
│ ├── font
│ │ └── JTUSjIg1_i6t8kCHKm459WlhzQ.woff
│ ├── img
│ │ ├── PopLayer-close.png
│ │ ├── github.png
│ │ ├── icon-1.png
│ │ ├── icon-2.png
│ │ ├── icon-3.png
│ │ ├── loading.gif
│ │ ├── logo.png
│ │ ├── logo_1.png
│ │ ├── wechat.png
│ │ └── weibo.png
│ ├── js
│ │ ├── china.js
│ │ ├── config.js
│ │ ├── util.js
│ │ ├── vue-components.js
│ │ └── vue-filters.js
│ └── lib
│ │ ├── bootstrap
│ │ ├── css
│ │ │ ├── bootstrap-theme.css
│ │ │ ├── bootstrap-theme.css.map
│ │ │ ├── bootstrap-theme.min.css
│ │ │ ├── bootstrap-theme.min.css.map
│ │ │ ├── bootstrap.css
│ │ │ ├── bootstrap.css.map
│ │ │ ├── bootstrap.min.css
│ │ │ └── bootstrap.min.css.map
│ │ ├── fonts
│ │ │ ├── glyphicons-halflings-regular.eot
│ │ │ ├── glyphicons-halflings-regular.svg
│ │ │ ├── glyphicons-halflings-regular.ttf
│ │ │ ├── glyphicons-halflings-regular.woff
│ │ │ └── glyphicons-halflings-regular.woff2
│ │ └── js
│ │ │ ├── bootstrap.js
│ │ │ ├── bootstrap.min.js
│ │ │ └── npm.js
│ │ ├── calendar
│ │ ├── zane-calendar.min.css
│ │ └── zane-calendar.min.js
│ │ ├── page
│ │ ├── page.css
│ │ └── page.js
│ │ └── popup
│ │ ├── popup.css
│ │ └── popup.js
├── router.js
├── router
│ ├── api.js
│ ├── home.js
│ ├── web
│ │ ├── api.js
│ │ └── web.js
│ └── wx
│ │ ├── api.js
│ │ └── web.js
├── schedule
│ ├── delete_report.js
│ ├── ip_task.js
│ ├── pvuvip_pre_day.js
│ ├── pvuvip_pre_minute.js
│ ├── report_task_mongodb.js
│ └── report_task_redis.js
├── service
│ ├── cache.js
│ ├── emails.js
│ ├── errors.js
│ ├── ldap.js
│ ├── remove.js
│ ├── system.js
│ ├── user.js
│ ├── web
│ │ ├── ajaxs.js
│ │ ├── analysis.js
│ │ ├── environment.js
│ │ ├── errors.js
│ │ ├── ip_task.js
│ │ ├── pages.js
│ │ ├── pvuvip.js
│ │ ├── pvuvip_task.js
│ │ ├── report.js
│ │ ├── report_task.js
│ │ ├── resource.js
│ │ └── send_email.js
│ └── wx
│ │ ├── ajaxs.js
│ │ ├── analysis.js
│ │ ├── errors.js
│ │ ├── ip_task.js
│ │ ├── pages.js
│ │ ├── pvuvip.js
│ │ ├── pvuvip_task.js
│ │ ├── report.js
│ │ ├── report_task.js
│ │ └── send_email.js
├── util.js
└── view
│ ├── emails.html
│ ├── errors.html
│ ├── footer.html
│ ├── github.html
│ ├── header.html
│ ├── home.html
│ ├── layout.html
│ ├── login.html
│ ├── selectype.html
│ ├── systems.html
│ ├── users.html
│ ├── web
│ ├── addsystem.html
│ ├── ajaxavg.html
│ ├── ajaxdetail.html
│ ├── ajaxitemdetail.html
│ ├── alarm.html
│ ├── analysisdetail.html
│ ├── analysislist.html
│ ├── diagram.html
│ ├── erroravg.html
│ ├── errordetail.html
│ ├── erroritemdetail.html
│ ├── home.html
│ ├── pagedetails.html
│ ├── pagesavg.html
│ ├── pageslist.html
│ ├── resourcesavg.html
│ ├── resourcesdetail.html
│ ├── resourcesitemdetail.html
│ ├── setting.html
│ ├── side.html
│ ├── slowpageslist.html
│ └── top.html
│ └── wx
│ ├── addsystem.html
│ ├── ajaxavg.html
│ ├── ajaxdetail.html
│ ├── ajaxitemdetail.html
│ ├── analysisdetail.html
│ ├── analysislist.html
│ ├── diagram.html
│ ├── erroravg.html
│ ├── errordetail.html
│ ├── erroritemdetail.html
│ ├── home.html
│ ├── pagedetails.html
│ ├── pagesavg.html
│ ├── pageslist.html
│ ├── setting.html
│ ├── side.html
│ └── top.html
├── appveyor.yml
├── config
├── config.default.js
├── config.prod.js
└── plugin.js
├── demo
├── 01.png
├── 02.png
├── 03.png
├── 04.png
├── 05.png
├── 06.png
├── 07.png
├── 08.png
├── 09.png
├── 10.png
├── 11.png
├── 12.png
├── 20.png
├── 21.png
└── ewm.jpg
├── docker-compose.yml
├── docs
├── SDK.html
├── colony.md
├── configs.md
├── github.md
├── img
│ └── github-login.png
├── index.md
├── iptask.html
├── oneservers.html
├── project.html
├── repeart_task.html
├── replica_set.html
├── simple_deployment.md
├── tasks.md
├── vue.md
└── wait.html
├── dump.rdb
├── lib
└── plugin
│ ├── egg-email
│ ├── LICENSE
│ ├── README.md
│ ├── app.js
│ ├── config
│ │ └── config.default.js
│ ├── lib
│ │ └── email.js
│ └── package.json
│ └── egg-kafka
│ ├── LICENSE
│ ├── README.md
│ ├── app.js
│ ├── config
│ └── config.default.js
│ ├── lib
│ └── kafka.js
│ └── package.json
├── mongodb-restart.sh
├── package.json
├── servers-restart.sh
├── start-docker-compose.sh
├── test
└── app
│ └── controller
│ └── home.test.js
└── yarn.lock
/.autod.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | write: true,
5 | prefix: '^',
6 | plugin: 'autod-egg',
7 | test: [
8 | 'test',
9 | 'benchmark',
10 | ],
11 | dep: [
12 | 'egg',
13 | 'egg-scripts',
14 | ],
15 | devdep: [
16 | 'egg-ci',
17 | 'egg-bin',
18 | 'egg-mock',
19 | 'autod',
20 | 'autod-egg',
21 | 'eslint',
22 | 'eslint-config-egg',
23 | 'webstorm-disable-index',
24 | ],
25 | exclude: [
26 | './test/fixtures',
27 | './dist',
28 | ],
29 | };
30 |
31 |
32 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | coverage
2 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "eslint-config-egg",
3 | "rules": {
4 | "indent": [
5 | "error",
6 | 4
7 | ],
8 | "linebreak-style": 0,
9 | },
10 | "plugins": [
11 | "html"
12 | ],
13 | "settings": {
14 | "html/html-extensions": [
15 | ".html"
16 | ]
17 | }
18 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | logs/
2 | config/config.prod_1.js
3 | npm-debug.log
4 | yarn-error.log
5 | node_modules/
6 | package-lock.json
7 | coverage/
8 | .idea/
9 | run/
10 | .DS_Store
11 | *.sw*
12 | *.un~
13 | buildlogs/
14 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 |
2 | language: node_js
3 | node_js:
4 | - '8'
5 | before_install:
6 | - npm i npminstall -g
7 | install:
8 | - npminstall
9 | script:
10 | - npm run ci
11 | after_script:
12 | - npminstall codecov && codecov
13 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "fileheader.Author": "zane",
3 | "fileheader.LastModifiedBy": "zane"
4 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018-2019 zane
4 | - zane ([@wangweianger](https://github.com/wangweianger))
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = async app => {
4 | app.models = {};
5 | app.beforeStart(async () => {
6 | const ctx = app.createAnonymousContext();
7 | if (app.config.report_data_type === 'kafka') {
8 | // kafka消费者
9 | ctx.service.web.reportTask.saveWebReportDatasForKafka();
10 | ctx.service.wx.reportTask.saveWxReportDatasForKafka();
11 | } else if (app.config.report_data_type === 'redisPubSub') {
12 | ctx.service.wx.reportTask.saveWxReportDatasForRedisPubSub();
13 | }
14 | });
15 | };
16 |
--------------------------------------------------------------------------------
/app/cache/wx_ip_city_cache_file.txt:
--------------------------------------------------------------------------------
1 | "210.21.228":{"city":"深圳市","province":"广东省"},"58.250.17":{"city":"深圳市","province":"广东省"},"223.74.151":{"city":"深圳市","province":"广东省"},"112.80.25":{"city":"苏州市","province":"江苏省"},"113.87.116":{"city":"深圳市","province":"广东省"},
--------------------------------------------------------------------------------
/app/controller/api/emails.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Controller = require('egg').Controller;
4 |
5 | class EmailsController extends Controller {
6 |
7 | async getList() {
8 | const { ctx } = this;
9 | const query = ctx.request.query;
10 | const pageNo = query.pageNo;
11 | const pageSize = query.pageSize || this.app.config.pageSize;
12 | const email = query.email;
13 |
14 | const result = await ctx.service.emails.getList(pageNo, pageSize, email);
15 |
16 | ctx.body = this.app.result({
17 | data: result,
18 | });
19 | }
20 |
21 | async addEmail() {
22 | const { ctx } = this;
23 | const query = ctx.request.body;
24 | const email = query.email;
25 | const name = query.name;
26 | if (!email) throw new Error('新增邮件:邮件地址不能为空!');
27 | if (!name) throw new Error('新增邮件:邮件所属人不能为空!');
28 |
29 | const result = await ctx.service.emails.addEmail(email, name);
30 |
31 | ctx.body = this.app.result({
32 | data: result,
33 | });
34 | }
35 |
36 | async deleteEmail() {
37 | const { ctx } = this;
38 | const query = ctx.request.body;
39 | const id = query.id;
40 | const systemIds = query.systemIds || [];
41 | const email = query.email;
42 |
43 | if (!id) throw new Error('删除邮件:id不能为空!');
44 |
45 | const result = await ctx.service.emails.deleteEmail(id, systemIds, email);
46 |
47 | ctx.body = this.app.result({
48 | data: result,
49 | });
50 | }
51 | }
52 |
53 | module.exports = EmailsController;
54 |
--------------------------------------------------------------------------------
/app/controller/api/errors.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Controller = require('egg').Controller;
4 |
5 | class ErrorsController extends Controller {
6 |
7 | // 获得系统重启信息
8 | async getSysDbErrorList() {
9 | const { ctx } = this;
10 | const result = await ctx.service.errors.getErrorList();
11 |
12 | ctx.body = this.app.result({
13 | data: result,
14 | });
15 | }
16 | }
17 |
18 | module.exports = ErrorsController;
19 |
--------------------------------------------------------------------------------
/app/controller/api/remove.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Controller = require('egg').Controller;
4 |
5 | class RemoveController extends Controller {
6 |
7 | // 清空db1 1日之前无用数据
8 | async deleteDb1WebData() {
9 | const { ctx } = this;
10 | const query = ctx.request.body;
11 |
12 | const result = await ctx.service.remove.deleteDb1WebData(query.type);
13 | ctx.body = this.app.result({
14 | data: result,
15 | });
16 | }
17 |
18 | // 清空db2 number日之前所有性能数据
19 | async deleteDb2WebData() {
20 | const { ctx } = this;
21 | const query = ctx.request.body;
22 | const number = query.number || 10;
23 | const appId = query.appId;
24 |
25 | if (!appId) throw new Error('获得error分类列表:appId不能为空');
26 |
27 | const result = await ctx.service.remove.deleteDb2WebData(appId, number, query.type);
28 | ctx.body = this.app.result({
29 | data: result,
30 | });
31 | }
32 |
33 | }
34 |
35 | module.exports = RemoveController;
36 |
--------------------------------------------------------------------------------
/app/controller/api/system.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Controller = require('egg').Controller;
4 |
5 | class SystemController extends Controller {
6 |
7 | // 新增系统
8 | async addNewSystem() {
9 | const { ctx } = this;
10 | return await ctx.service.system.saveSystemData(ctx);
11 | }
12 |
13 | // 修改系统信息
14 | async updateSystem() {
15 | const { ctx } = this;
16 | return await ctx.service.system.updateSystemData(ctx);
17 | }
18 |
19 | // 根据用户id获取系统列表
20 | async getSysForUserId() {
21 | const { ctx } = this;
22 | const result = await ctx.service.system.getSysForUserId(ctx);
23 | ctx.body = this.app.result({
24 | data: result,
25 | });
26 | }
27 |
28 | // 根据系统ID获得单个系统信息
29 | async getSystemForId() {
30 | const { ctx } = this;
31 | const query = ctx.request.query;
32 | const appId = query.appId;
33 |
34 | const result = await ctx.service.system.getSystemForDb(appId);
35 | ctx.body = this.app.result({
36 | data: result,
37 | });
38 | }
39 |
40 | // 系统列表
41 | async getWebSystemList() {
42 | const { ctx } = this;
43 |
44 | const result = await ctx.service.system.getWebSystemList();
45 | ctx.body = this.app.result({
46 | data: result,
47 | });
48 | }
49 |
50 | // 删除系统中某个用户
51 | async deleteWebSystemUser() {
52 | const { ctx } = this;
53 | const query = ctx.request.body;
54 | const appId = query.appId;
55 | const userToken = query.userToken;
56 |
57 | if (!appId) throw new Error('删除系统中某个用户:appId不能为空');
58 | if (!userToken) throw new Error('删除系统中某个用户:userToken不能为空');
59 |
60 | const result = await ctx.service.system.deleteWebSystemUser(appId, userToken);
61 |
62 | ctx.body = this.app.result({
63 | data: result,
64 | });
65 | }
66 |
67 | // 系统中新增某个用户
68 | async addWebSystemUser() {
69 | const { ctx } = this;
70 | const query = ctx.request.body;
71 | const appId = query.appId;
72 | const userToken = query.userToken;
73 |
74 | if (!appId) throw new Error('系统中新增某个用户:appId不能为空');
75 | if (!userToken) throw new Error('系统中新增某个用户:userToken不能为空');
76 |
77 | const result = await ctx.service.system.addWebSystemUser(appId, userToken);
78 |
79 | ctx.body = this.app.result({
80 | data: result,
81 | });
82 | }
83 |
84 | // 删除某个系统
85 | async deleteSystem() {
86 | const { ctx } = this;
87 | const query = ctx.request.body;
88 | const appId = query.appId;
89 |
90 | if (!appId) throw new Error('删除某个系统:appId不能为空');
91 |
92 | const result = await ctx.service.system.deleteSystem(appId);
93 |
94 | ctx.body = this.app.result({
95 | data: result,
96 | });
97 | }
98 |
99 | // 新增 | 删除 日报邮件
100 | async handleDaliyEmail() {
101 | const { ctx } = this;
102 | const query = ctx.request.body;
103 | const appId = query.appId;
104 | const email = query.email;
105 | const type = query.type || 1;
106 | const item = query.item || 1;
107 |
108 | if (!appId) throw new Error('appId不能为空');
109 |
110 | const result = await ctx.service.system.handleDaliyEmail(appId, email, type, true, item);
111 |
112 | ctx.body = this.app.result({
113 | data: result,
114 | });
115 | }
116 | }
117 |
118 | module.exports = SystemController;
119 |
--------------------------------------------------------------------------------
/app/controller/api/web/ajax.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Controller = require('egg').Controller;
4 |
5 | class AjaxsController extends Controller {
6 |
7 | // 平均页面性能列表
8 | async getPageAjaxsAvg() {
9 | const { ctx } = this;
10 | const query = ctx.request.query;
11 | const appId = query.appId;
12 | const url = query.url;
13 | const beginTime = query.beginTime;
14 | const endTime = query.endTime;
15 |
16 | if (!appId) throw new Error('页面ajax信息:appId不能为空');
17 | if (!url) throw new Error('页面ajax信息:url不能为空');
18 |
19 | const result = await ctx.service.web.ajaxs.getPageAjaxsAvg(appId, url, beginTime, endTime);
20 |
21 | ctx.body = this.app.result({
22 | data: result,
23 | });
24 | }
25 |
26 | // 平均AJAX性能列表
27 | async getAverageAjaxList() {
28 | const { ctx } = this;
29 | const query = ctx.request.query;
30 | const appId = query.appId;
31 |
32 | if (!appId) throw new Error('平均AJAX性能列表:appId不能为空');
33 |
34 | const result = await ctx.service.web.ajaxs.getAverageAjaxList(ctx);
35 |
36 | ctx.body = this.app.result({
37 | data: result,
38 | });
39 | }
40 | // 获得单个api的平均性能数据
41 | async getOneAjaxAvg() {
42 | const { ctx } = this;
43 | const query = ctx.request.query;
44 | const appId = query.appId;
45 | const url = query.url;
46 | const beginTime = query.beginTime;
47 | const endTime = query.endTime;
48 | const type = query.type;
49 |
50 | if (!appId) throw new Error('单个AJAX平均性能数据:appId不能为空');
51 | if (!url) throw new Error('单个AJAX平均性能数据:api地址不能为空');
52 |
53 | const result = await ctx.service.web.ajaxs.getOneAjaxAvg(appId, url, beginTime, endTime, type);
54 |
55 | ctx.body = this.app.result({
56 | data: result,
57 | });
58 | }
59 | // 获得单个api的性能列表数据
60 | async getOneAjaxList() {
61 | const { ctx } = this;
62 | const query = ctx.request.query;
63 | const appId = query.appId;
64 | const url = query.url;
65 | const pageNo = query.pageNo || 1;
66 | const pageSize = query.pageSize || this.app.config.pageSize;
67 | const beginTime = query.beginTime;
68 | const endTime = query.endTime;
69 | const type = query.type;
70 |
71 | if (!appId) throw new Error('单个AJAX平均性能数据:appId不能为空');
72 | if (!url) throw new Error('单个AJAX平均性能数据:api地址不能为空');
73 |
74 | const result = await ctx.service.web.ajaxs.getOneAjaxList(appId, url, pageNo, pageSize, beginTime, endTime, type);
75 |
76 | ctx.body = this.app.result({
77 | data: result,
78 | });
79 | }
80 |
81 | // 获得单个ajax详情信息
82 | async getOneAjaxDetail() {
83 | const { ctx } = this;
84 | const query = ctx.request.query;
85 | const id = query.id;
86 | const appId = query.appId;
87 |
88 | if (!id) throw new Error('获得单个ajax详情信息:id不能为空');
89 | if (!appId) throw new Error('获得单个ajax详情信息:appId不能为空');
90 |
91 | const result = await ctx.service.web.ajaxs.getOneAjaxDetail(appId, id);
92 |
93 | ctx.body = this.app.result({
94 | data: result,
95 | });
96 | }
97 | }
98 |
99 | module.exports = AjaxsController;
100 |
--------------------------------------------------------------------------------
/app/controller/api/web/analysis.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const Controller = require('egg').Controller;
3 |
4 | class AnalysisController extends Controller {
5 |
6 | // 用户漏斗分析列表
7 | async getAnalysislist() {
8 | const { ctx } = this;
9 | const query = ctx.request.query;
10 | const appId = query.appId;
11 | const beginTime = query.beginTime;
12 | const endTime = query.endTime;
13 | const pageNo = query.pageNo;
14 | const ip = query.ip;
15 | const pageSize = query.pageSize || this.app.config.pageSize;
16 |
17 | if (!appId) throw new Error('用户漏斗分析列表:appId不能为空');
18 |
19 | const result = await ctx.service.web.analysis.getAnalysislist(appId, beginTime, endTime, ip, pageNo, pageSize);
20 |
21 | ctx.body = this.app.result({
22 | data: result,
23 | });
24 | }
25 |
26 | // 单个用户行为轨迹列表
27 | async getAnalysisOneList() {
28 | const { ctx } = this;
29 | const query = ctx.request.query;
30 | const appId = query.appId;
31 | const markuser = query.markuser;
32 |
33 | if (!appId) throw new Error('单个用户行为轨迹列表:appId不能为空');
34 | if (!markuser) throw new Error('单个用户行为轨迹列表:markuser不能为空');
35 |
36 | const result = await ctx.service.web.analysis.getAnalysisOneList(appId, markuser);
37 |
38 | ctx.body = this.app.result({
39 | data: result,
40 | });
41 | }
42 |
43 | // top datas
44 | async getTopDatas() {
45 | const { ctx } = this;
46 | const query = ctx.request.query;
47 | const appId = query.appId;
48 | const beginTime = query.beginTime;
49 | const endTime = query.endTime;
50 | const type = query.type || 1;
51 | if (!appId) throw new Error('top页面:appId不能为空');
52 |
53 | const result = await ctx.service.web.analysis.getTopDatas(appId, beginTime, endTime, type);
54 | ctx.body = this.app.result({
55 | data: result,
56 | });
57 | }
58 |
59 | // top datas
60 | async getProvinceAvgCount() {
61 | const { ctx } = this;
62 | const query = ctx.request.query;
63 | const appId = query.appId;
64 | const beginTime = query.beginTime;
65 | const endTime = query.endTime;
66 | const type = query.type || 1;
67 | if (!appId) throw new Error('top页面:appId不能为空');
68 |
69 | const result = await ctx.service.web.analysis.getProvinceAvgCount(appId, beginTime, endTime, type);
70 | ctx.body = this.app.result({
71 | data: result,
72 | });
73 | }
74 | }
75 |
76 | module.exports = AnalysisController;
77 |
--------------------------------------------------------------------------------
/app/controller/api/web/environment.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Controller = require('egg').Controller;
4 |
5 | class EnfironmentController extends Controller {
6 |
7 | // 获得用户系统、地址位置、浏览器分类
8 | async getDataGroupBy() {
9 | const { ctx } = this;
10 | const query = ctx.request.query;
11 | const appId = query.appId;
12 | const beginTime = query.beginTime;
13 | const endTime = query.endTime;
14 | const type = query.type || 1;
15 | const url = query.url;
16 |
17 | if (!appId) throw new Error('页面性能列表:appId不能为空');
18 | if (!url) throw new Error('页面性能列表:url不能为空');
19 |
20 | const result = await ctx.service.web.environment.getDataGroupBy(type, url, appId, beginTime, endTime);
21 |
22 | ctx.body = this.app.result({
23 | data: result,
24 | });
25 | }
26 |
27 | // 根据mark_page获得用户系统信息
28 | async getEnvironmentForPage() {
29 | const { ctx } = this;
30 | const query = ctx.request.query;
31 | const appId = query.appId;
32 | const markPage = query.markPage;
33 |
34 | if (!appId) throw new Error('根据mark_page获得用户系统信息:appId不能为空');
35 | if (!markPage) throw new Error('根据mark_page获得用户系统信息:markPage不能为空');
36 |
37 | const result = await ctx.service.web.environment.getEnvironmentForPage(appId, markPage);
38 |
39 | ctx.body = this.app.result({
40 | data: result,
41 | });
42 | }
43 | }
44 |
45 | module.exports = EnfironmentController;
46 |
--------------------------------------------------------------------------------
/app/controller/api/web/error.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Controller = require('egg').Controller;
4 |
5 | class ErrorController extends Controller {
6 |
7 | // 获得error分类列表
8 | async getAverageErrorList() {
9 | const { ctx } = this;
10 | const query = ctx.request.query;
11 | const appId = query.appId;
12 |
13 | if (!appId) throw new Error('获得error分类列表:appId不能为空');
14 |
15 | const result = await ctx.service.web.errors.getAverageErrorList(ctx);
16 |
17 | ctx.body = this.app.result({
18 | data: result,
19 | });
20 | }
21 |
22 | // 获得单个ERROR资源列表信息
23 | async getOneErrorList() {
24 | const { ctx } = this;
25 | const query = ctx.request.query;
26 | const appId = query.appId;
27 | const url = query.url;
28 | const category = query.category || 'resource';
29 | const pageNo = query.pageNo || 1;
30 | const pageSize = query.pageSize || this.app.config.pageSize;
31 | const beginTime = query.beginTime;
32 | const endTime = query.endTime;
33 |
34 | if (!appId) throw new Error('获得单个ERROR资源列表信息:appId不能为空');
35 | if (!url) throw new Error('获得单个ERROR资源列表信息:url地址不能为空');
36 |
37 | const result = await ctx.service.web.errors.getOneErrorList(appId, url, category, pageNo, pageSize, beginTime, endTime);
38 |
39 | ctx.body = this.app.result({
40 | data: result,
41 | });
42 | }
43 |
44 | // 获得单个api的性能列表数据
45 | async getOneResourceList() {
46 | const { ctx } = this;
47 | const query = ctx.request.query;
48 | const appId = query.appId;
49 | const url = query.url;
50 | const pageNo = query.pageNo || 1;
51 | const pageSize = query.pageSize || this.app.config.pageSize;
52 |
53 | if (!appId) throw new Error('单个Resource性能列表数据:appId不能为空');
54 | if (!url) throw new Error('单个Resource性能列表数据:api地址不能为空');
55 |
56 |
57 | const result = await ctx.service.web.resource.getOneResourceList(appId, url, pageNo, pageSize);
58 |
59 | ctx.body = this.app.result({
60 | data: result,
61 | });
62 | }
63 |
64 | // 单个error详情信息
65 | async getErrorDetail() {
66 | const { ctx } = this;
67 | const query = ctx.request.query;
68 | const id = query.id;
69 | const appId = query.appId;
70 |
71 | if (!id) throw new Error('单个error详情信息:id不能为空');
72 | if (!appId) throw new Error('单个error详情信息:appId不能为空');
73 |
74 | const result = await ctx.service.web.errors.getErrorDetail(appId, id);
75 |
76 | ctx.body = this.app.result({
77 | data: result,
78 | });
79 | }
80 | }
81 |
82 | module.exports = ErrorController;
83 |
--------------------------------------------------------------------------------
/app/controller/api/web/pages.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Controller = require('egg').Controller;
4 |
5 | class PagesController extends Controller {
6 |
7 | // 平均页面性能列表
8 | async getAveragePageList() {
9 | const { ctx } = this;
10 | const query = ctx.request.query;
11 | const appId = query.appId;
12 |
13 | if (!appId) throw new Error('平均页面性能列表:appId不能为空');
14 |
15 | const result = await ctx.service.web.pages.getAveragePageList(ctx);
16 |
17 | ctx.body = this.app.result({
18 | data: result,
19 | });
20 | }
21 |
22 | // 单个页面性能数据列表
23 | async getOnePageList() {
24 | const { ctx } = this;
25 | const query = ctx.request.query;
26 | const appId = query.appId;
27 | const url = query.url;
28 | if (!appId) throw new Error('单个页面性能列表:appId不能为空');
29 | if (!url) throw new Error('单个页面性能列表:url不能为空');
30 |
31 | const result = await ctx.service.web.pages.getOnePageList(ctx);
32 |
33 | ctx.body = this.app.result({
34 | data: result,
35 | });
36 | }
37 |
38 | // 单个页面列表(简单版本)
39 | async getPagesForType() {
40 | const { ctx } = this;
41 | const query = ctx.request.query;
42 | const appId = query.appId;
43 | const url = query.url;
44 | const speedType = query.type || 1;
45 | const pageNo = query.pageNo;
46 | const pageSize = query.pageSize;
47 |
48 | if (!appId) throw new Error('单个页面性能列表:appId不能为空');
49 | if (!url) throw new Error('单个页面性能列表:url不能为空');
50 |
51 | const result = await ctx.service.web.pages.getPagesForType(appId, url, speedType, pageNo, pageSize);
52 |
53 | ctx.body = this.app.result({
54 | data: result,
55 | });
56 | }
57 |
58 | // 单个页面详情
59 | async getPageDetails() {
60 | const { ctx } = this;
61 | const query = ctx.request.query;
62 | const id = query.id;
63 | const appId = query.appId;
64 |
65 | if (!id) throw new Error('单个页面详情:id不能为空');
66 | if (!appId) throw new Error('单个页面详情:appId不能为空');
67 |
68 | const result = await ctx.service.web.pages.getPageDetails(appId, id);
69 |
70 | ctx.body = this.app.result({
71 | data: result,
72 | });
73 | }
74 | }
75 |
76 | module.exports = PagesController;
77 |
--------------------------------------------------------------------------------
/app/controller/api/web/report.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | 'use strict';
3 | const { getUserIp } = require('../../../util')
4 | const Controller = require('egg').Controller;
5 |
6 | class ReportController extends Controller {
7 | constructor(params) {
8 | super(params);
9 | }
10 | // web用户数据上报
11 | async webReport() {
12 | const { ctx } = this;
13 | ctx.set('Access-Control-Allow-Origin', '*');
14 | ctx.set('Content-Type', 'application/json;charset=UTF-8');
15 | ctx.set('X-Response-Time', '2s');
16 | ctx.set('Connection', 'close');
17 | ctx.status = 200;
18 |
19 | const query = ctx.request.body;
20 | if (!query.appId) throw new Error('web端上报数据操作:app_id不能为空');
21 |
22 | query.ip = getUserIp(ctx);
23 | query.url = query.url || ctx.headers.referer;
24 | query.user_agent = ctx.headers['user-agent'];
25 |
26 | if (this.app.config.report_data_type === 'redis') this.saveWebReportDataForRedis(query);
27 | if (this.app.config.report_data_type === 'kafka') this.saveWebReportDataForKafka(query);
28 | if (this.app.config.report_data_type === 'mongodb') this.saveWebReportDataForMongodb(ctx);
29 | }
30 |
31 | // 通过redis 消息队列消费数据
32 | async saveWebReportDataForRedis(query) {
33 | try {
34 | if (this.app.config.redis_consumption.total_limit_web) {
35 | // 限流
36 | const length = await this.app.redis.llen('web_repore_datas');
37 | if (length >= this.app.config.redis_consumption.total_limit_web) return;
38 | }
39 | // 生产者
40 | this.app.redis.lpush('web_repore_datas', JSON.stringify(query));
41 | } catch (e) { console.log(e); }
42 | }
43 |
44 | // 通过kafka 消息队列消费数据
45 | async saveWebReportDataForKafka(query) {
46 | // 生产者
47 | this.app.kafka.send(
48 | 'web',
49 | JSON.stringify(query)
50 | );
51 | }
52 |
53 | // 通过mongodb 数据库存储数据
54 | async saveWebReportDataForMongodb(ctx) {
55 | ctx.service.web.report.saveWebReportData(ctx);
56 | }
57 | }
58 |
59 | module.exports = ReportController;
60 |
--------------------------------------------------------------------------------
/app/controller/api/web/resource.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Controller = require('egg').Controller;
4 |
5 | class ResourceController extends Controller {
6 |
7 | // 根据某个页面获得资源列表
8 | async getResourceForType() {
9 | const { ctx } = this;
10 | const query = ctx.request.query;
11 | const appId = query.appId;
12 | const url = query.url;
13 | const speedType = query.type || 1;
14 | const pageNo = query.pageNo;
15 | const pageSize = query.pageSize;
16 |
17 | if (!appId) throw new Error('单个页面资源性能列表:appId不能为空');
18 | if (!url) throw new Error('单个页面资源性能列表:url不能为空');
19 |
20 | const result = await ctx.service.web.resource.getResourceForType(appId, url, speedType, pageNo, pageSize);
21 |
22 | ctx.body = this.app.result({
23 | data: result,
24 | });
25 | }
26 |
27 | // 获得resource平均性能列表
28 | async getAverageResourceList() {
29 | const { ctx } = this;
30 | const query = ctx.request.query;
31 | const appId = query.appId;
32 |
33 | if (!appId) throw new Error('获得resource平均性能列表:appId不能为空');
34 |
35 | const result = await ctx.service.web.resource.getAverageResourceList(ctx);
36 |
37 | ctx.body = this.app.result({
38 | data: result,
39 | });
40 | }
41 |
42 | // 获得单个Resource的平均性能数据
43 | async getOneResourceAvg() {
44 | const { ctx } = this;
45 | const query = ctx.request.query;
46 | const appId = query.appId;
47 | const url = query.url;
48 | const beginTime = query.beginTime;
49 | const endTime = query.endTime;
50 |
51 | if (!appId) throw new Error('单个Resource平均性能数据:appId不能为空');
52 | if (!url) throw new Error('单个Resource平均性能数据:api地址不能为空');
53 |
54 | const result = await ctx.service.web.resource.getOneResourceAvg(appId, url, beginTime, endTime);
55 |
56 | ctx.body = this.app.result({
57 | data: result,
58 | });
59 | }
60 |
61 | // 获得单个api的性能列表数据
62 | async getOneResourceList() {
63 | const { ctx } = this;
64 | const query = ctx.request.query;
65 | const appId = query.appId;
66 | const url = query.url;
67 | const pageNo = query.pageNo || 1;
68 | const pageSize = query.pageSize || this.app.config.pageSize;
69 | const beginTime = query.beginTime;
70 | const endTime = query.endTime;
71 |
72 | if (!appId) throw new Error('单个Resource性能列表数据:appId不能为空');
73 | if (!url) throw new Error('单个Resource性能列表数据:api地址不能为空');
74 |
75 |
76 | const result = await ctx.service.web.resource.getOneResourceList(appId, url, pageNo, pageSize, beginTime, endTime);
77 |
78 | ctx.body = this.app.result({
79 | data: result,
80 | });
81 | }
82 |
83 | // 获得单个Resource详情信息
84 | async getOneResourceDetail() {
85 | const { ctx } = this;
86 | const query = ctx.request.query;
87 | const id = query.id;
88 | const appId = query.appId;
89 |
90 | if (!id) throw new Error('获得单个Resource详情信息:id不能为空');
91 | if (!appId) throw new Error('获得单个Resource详情信息:appId不能为空');
92 |
93 | const result = await ctx.service.web.resource.getOneResourceDetail(appId, id);
94 |
95 | ctx.body = this.app.result({
96 | data: result,
97 | });
98 | }
99 | }
100 |
101 | module.exports = ResourceController;
102 |
--------------------------------------------------------------------------------
/app/controller/api/wx/ajax.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Controller = require('egg').Controller;
4 |
5 | class AjaxsController extends Controller {
6 |
7 | // 平均页面性能列表
8 | async getPageAjaxsAvg() {
9 | const { ctx } = this;
10 | const query = ctx.request.query;
11 | const appId = query.appId;
12 | const url = query.url;
13 | const beginTime = query.beginTime;
14 | const endTime = query.endTime;
15 |
16 | if (!appId) throw new Error('页面ajax信息:appId不能为空');
17 | if (!url) throw new Error('页面ajax信息:url不能为空');
18 |
19 | const result = await ctx.service.wx.ajaxs.getPageAjaxsAvg(appId, url, beginTime, endTime);
20 |
21 | ctx.body = this.app.result({
22 | data: result,
23 | });
24 | }
25 |
26 | // 平均AJAX性能列表
27 | async getAverageAjaxList() {
28 | const { ctx } = this;
29 | const query = ctx.request.query;
30 | const appId = query.appId;
31 |
32 | if (!appId) throw new Error('平均AJAX性能列表:appId不能为空');
33 |
34 | const result = await ctx.service.wx.ajaxs.getAverageAjaxList(ctx);
35 |
36 | ctx.body = this.app.result({
37 | data: result,
38 | });
39 | }
40 | // 获得单个api的平均性能数据
41 | async getOneAjaxAvg() {
42 | const { ctx } = this;
43 | const query = ctx.request.query;
44 | const appId = query.appId;
45 | const url = query.url;
46 | const beginTime = query.beginTime;
47 | const endTime = query.endTime;
48 | const type = query.type;
49 |
50 | if (!appId) throw new Error('单个AJAX平均性能数据:appId不能为空');
51 | if (!url) throw new Error('单个AJAX平均性能数据:api地址不能为空');
52 |
53 | const result = await ctx.service.wx.ajaxs.getOneAjaxAvg(appId, url, beginTime, endTime, type);
54 |
55 | ctx.body = this.app.result({
56 | data: result,
57 | });
58 | }
59 | // 获得单个api的性能列表数据
60 | async getOneAjaxList() {
61 | const { ctx } = this;
62 | const query = ctx.request.query;
63 | const appId = query.appId;
64 | const url = query.url;
65 | const pageNo = query.pageNo || 1;
66 | const pageSize = query.pageSize || this.app.config.pageSize;
67 | const beginTime = query.beginTime;
68 | const endTime = query.endTime;
69 | const type = query.type;
70 |
71 | if (!appId) throw new Error('单个AJAX平均性能数据:appId不能为空');
72 | if (!url) throw new Error('单个AJAX平均性能数据:api地址不能为空');
73 |
74 | const result = await ctx.service.wx.ajaxs.getOneAjaxList(appId, url, pageNo, pageSize, beginTime, endTime, type);
75 |
76 | ctx.body = this.app.result({
77 | data: result,
78 | });
79 | }
80 |
81 | // 获得单个ajax详情信息
82 | async getOneAjaxDetail() {
83 | const { ctx } = this;
84 | const query = ctx.request.query;
85 | const id = query.id;
86 | const appId = query.appId;
87 |
88 | if (!id) throw new Error('获得单个ajax详情信息:id不能为空');
89 | if (!appId) throw new Error('获得单个ajax详情信息:appId不能为空');
90 |
91 | const result = await ctx.service.wx.ajaxs.getOneAjaxDetail(appId, id);
92 |
93 | ctx.body = this.app.result({
94 | data: result,
95 | });
96 | }
97 | }
98 |
99 | module.exports = AjaxsController;
100 |
--------------------------------------------------------------------------------
/app/controller/api/wx/analysis.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const Controller = require('egg').Controller;
3 |
4 | class AnalysisController extends Controller {
5 |
6 | // 用户漏斗分析列表
7 | async getAnalysislist() {
8 | const { ctx } = this;
9 | const query = ctx.request.query;
10 | const appId = query.appId;
11 | const beginTime = query.beginTime;
12 | const endTime = query.endTime;
13 | const pageNo = query.pageNo;
14 | const ip = query.ip;
15 | const pageSize = query.pageSize || this.app.config.pageSize;
16 |
17 | if (!appId) throw new Error('用户漏斗分析列表:appId不能为空');
18 |
19 | const result = await ctx.service.wx.analysis.getAnalysislist(appId, beginTime, endTime, ip, pageNo, pageSize);
20 |
21 | ctx.body = this.app.result({
22 | data: result,
23 | });
24 | }
25 |
26 | // 单个用户行为轨迹列表
27 | async getAnalysisOneList() {
28 | const { ctx } = this;
29 | const query = ctx.request.query;
30 | const appId = query.appId;
31 | const markuser = query.markuser;
32 |
33 | if (!appId) throw new Error('单个用户行为轨迹列表:appId不能为空');
34 | if (!markuser) throw new Error('单个用户行为轨迹列表:markuser不能为空');
35 |
36 | const result = await ctx.service.wx.analysis.getAnalysisOneList(appId, markuser);
37 |
38 | ctx.body = this.app.result({
39 | data: result,
40 | });
41 | }
42 |
43 | // top datas
44 | async getTopDatas() {
45 | const { ctx } = this;
46 | const query = ctx.request.query;
47 | const appId = query.appId;
48 | const beginTime = query.beginTime;
49 | const endTime = query.endTime;
50 | const type = query.type || 1;
51 | if (!appId) throw new Error('top页面:appId不能为空');
52 |
53 | const result = await ctx.service.wx.analysis.getTopDatas(appId, beginTime, endTime, type);
54 | ctx.body = this.app.result({
55 | data: result,
56 | });
57 | }
58 |
59 | // top datas
60 | async getProvinceAvgCount() {
61 | const { ctx } = this;
62 | const query = ctx.request.query;
63 | const appId = query.appId;
64 | const beginTime = query.beginTime;
65 | const endTime = query.endTime;
66 | const type = query.type || 1;
67 | if (!appId) throw new Error('top页面:appId不能为空');
68 |
69 | const result = await ctx.service.wx.analysis.getProvinceAvgCount(appId, beginTime, endTime, type);
70 | ctx.body = this.app.result({
71 | data: result,
72 | });
73 | }
74 | }
75 |
76 | module.exports = AnalysisController;
77 |
--------------------------------------------------------------------------------
/app/controller/api/wx/error.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Controller = require('egg').Controller;
4 |
5 | class ErrorController extends Controller {
6 |
7 | // 获得error分类列表
8 | async getAverageErrorList() {
9 | const { ctx } = this;
10 | const query = ctx.request.query;
11 | const appId = query.appId;
12 |
13 | if (!appId) throw new Error('获得error分类列表:appId不能为空');
14 |
15 | const result = await ctx.service.wx.errors.getAverageErrorList(ctx);
16 |
17 | ctx.body = this.app.result({
18 | data: result,
19 | });
20 | }
21 |
22 | // 获得单个ERROR资源列表信息
23 | async getOneErrorList() {
24 | const { ctx } = this;
25 | const query = ctx.request.query;
26 | const appId = query.appId;
27 | const url = query.url;
28 | const category = query.category || 'resource';
29 | const pageNo = query.pageNo || 1;
30 | const pageSize = query.pageSize || this.app.config.pageSize;
31 | const beginTime = query.beginTime;
32 | const endTime = query.endTime;
33 |
34 | if (!appId) throw new Error('获得单个ERROR资源列表信息:appId不能为空');
35 | if (!url) throw new Error('获得单个ERROR资源列表信息:url地址不能为空');
36 |
37 | const result = await ctx.service.wx.errors.getOneErrorList(appId, url, category, pageNo, pageSize, beginTime, endTime);
38 |
39 | ctx.body = this.app.result({
40 | data: result,
41 | });
42 | }
43 |
44 | // 获得单个api的性能列表数据
45 | async getOneResourceList() {
46 | const { ctx } = this;
47 | const query = ctx.request.query;
48 | const appId = query.appId;
49 | const url = query.url;
50 | const pageNo = query.pageNo || 1;
51 | const pageSize = query.pageSize || this.app.config.pageSize;
52 |
53 | if (!appId) throw new Error('单个Resource性能列表数据:appId不能为空');
54 | if (!url) throw new Error('单个Resource性能列表数据:api地址不能为空');
55 |
56 |
57 | const result = await ctx.service.web.resource.getOneResourceList(appId, url, pageNo, pageSize);
58 |
59 | ctx.body = this.app.result({
60 | data: result,
61 | });
62 | }
63 |
64 | // 单个error详情信息
65 | async getErrorDetail() {
66 | const { ctx } = this;
67 | const query = ctx.request.query;
68 | const id = query.id;
69 | const appId = query.appId;
70 |
71 | if (!id) throw new Error('单个error详情信息:id不能为空');
72 | if (!appId) throw new Error('单个error详情信息:appId不能为空');
73 |
74 | const result = await ctx.service.wx.errors.getErrorDetail(appId, id);
75 |
76 | ctx.body = this.app.result({
77 | data: result,
78 | });
79 | }
80 | }
81 |
82 | module.exports = ErrorController;
83 |
--------------------------------------------------------------------------------
/app/controller/api/wx/pages.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Controller = require('egg').Controller;
4 |
5 | class PagesController extends Controller {
6 |
7 | // 平均页面性能列表
8 | async getAveragePageList() {
9 | const { ctx } = this;
10 | const query = ctx.request.query;
11 | const appId = query.appId;
12 |
13 | if (!appId) throw new Error('平均页面性能列表:appId不能为空');
14 |
15 | const result = await ctx.service.wx.pages.getAveragePageList(ctx);
16 |
17 | ctx.body = this.app.result({
18 | data: result,
19 | });
20 | }
21 |
22 | // 单个页面性能数据列表
23 | async getOnePageList() {
24 | const { ctx } = this;
25 | const query = ctx.request.query;
26 | const appId = query.appId;
27 | const url = query.url;
28 | if (!appId) throw new Error('单个页面性能列表:appId不能为空');
29 | if (!url) throw new Error('单个页面性能列表:url不能为空');
30 |
31 | const result = await ctx.service.wx.pages.getOnePageList(ctx);
32 |
33 | ctx.body = this.app.result({
34 | data: result,
35 | });
36 | }
37 |
38 | // 单个页面列表(简单版本)
39 | async getPagesForType() {
40 | const { ctx } = this;
41 | const query = ctx.request.query;
42 | const appId = query.appId;
43 | const url = query.url;
44 | const speedType = query.type || 1;
45 | const pageNo = query.pageNo;
46 | const pageSize = query.pageSize;
47 |
48 | if (!appId) throw new Error('单个页面性能列表:appId不能为空');
49 | if (!url) throw new Error('单个页面性能列表:url不能为空');
50 |
51 | const result = await ctx.service.wx.pages.getPagesForType(appId, url, speedType, pageNo, pageSize);
52 |
53 | ctx.body = this.app.result({
54 | data: result,
55 | });
56 | }
57 |
58 | // 单个页面详情
59 | async getPageDetails() {
60 | const { ctx } = this;
61 | const query = ctx.request.query;
62 | const appId = query.appId;
63 | const id = query.id;
64 |
65 | if (!appId) throw new Error('单个页面详情:appId不能为空');
66 | if (!id) throw new Error('单个页面详情:id不能为空');
67 |
68 | const result = await ctx.service.wx.pages.getPageDetails(appId, id, 1);
69 |
70 | ctx.body = this.app.result({
71 | data: result,
72 | });
73 | }
74 |
75 | // 获得用户系统、地址位置、浏览器分类
76 | async getDataGroupBy() {
77 | const { ctx } = this;
78 | const query = ctx.request.query;
79 | const appId = query.appId;
80 | const beginTime = query.beginTime;
81 | const endTime = query.endTime;
82 | const type = query.type || 1;
83 | const url = query.url;
84 |
85 | if (!appId) throw new Error('页面性能列表:appId不能为空');
86 | if (!url) throw new Error('页面性能列表:url不能为空');
87 |
88 | const result = await ctx.service.wx.pages.getDataGroupBy(type, url, appId, beginTime, endTime);
89 |
90 | ctx.body = this.app.result({
91 | data: result,
92 | });
93 | }
94 |
95 | // 单个页面详情(markpage)
96 | async getPageForMarkpage() {
97 | const { ctx } = this;
98 | const query = ctx.request.query;
99 | const appId = query.appId;
100 | const markPage = query.markPage;
101 |
102 | if (!appId) throw new Error('单个页面详情:appId不能为空');
103 | if (!markPage) throw new Error('单个页面详情:markPage不能为空');
104 |
105 | const result = await ctx.service.wx.pages.getPageDetails(appId, markPage, 2);
106 |
107 | ctx.body = this.app.result({
108 | data: result,
109 | });
110 | }
111 |
112 | }
113 |
114 | module.exports = PagesController;
115 |
--------------------------------------------------------------------------------
/app/controller/api/wx/report.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const { getUserIp } = require('../../../util');
3 | const Controller = require('egg').Controller;
4 |
5 | class ReportController extends Controller {
6 |
7 | // 微信端用户数据上报
8 | async wxReport() {
9 | const { ctx } = this;
10 | ctx.set('Access-Control-Allow-Origin', '*');
11 | ctx.set('Content-Type', 'application/json;charset=UTF-8');
12 | ctx.set('X-Response-Time', '2s');
13 | ctx.set('Connection', 'close');
14 | ctx.status = 200;
15 |
16 | const query = ctx.request.body;
17 | if (!query.appId) throw new Error('wx端上报数据操作:app_id不能为空');
18 |
19 | query.ip = getUserIp(ctx);
20 |
21 | if (this.app.config.report_data_type === 'redis') this.saveWxReportDataForRedis(query);
22 | if (this.app.config.report_data_type === 'kafka') this.saveWxReportDataForKafka(query);
23 | if (this.app.config.report_data_type === 'mongodb') this.saveWxReportDataForMongodb(ctx);
24 | }
25 |
26 | // 通过redis 消费者模式存储数据
27 | async saveWxReportDataForRedis(query) {
28 | try {
29 | if (this.app.config.redis_consumption.total_limit_wx) {
30 | // 限流
31 | const length = await this.app.redis.llen('wx_repore_datas');
32 | if (length >= this.app.config.redis_consumption.total_limit_wx) return;
33 | }
34 | this.app.redis.lpush('wx_repore_datas', JSON.stringify(query));
35 | } catch (e) { console.log(e); }
36 | }
37 |
38 | // 通过kafka 消息队列消费数据
39 | async saveWxReportDataForKafka(query) {
40 | // 生产者
41 | this.app.kafka.send(
42 | 'wx',
43 | JSON.stringify(query)
44 | );
45 | }
46 |
47 | // 通过mongodb 数据库存储数据
48 | async saveWxReportDataForMongodb(ctx) {
49 | ctx.service.wx.report.saveWxReportData(ctx);
50 | }
51 |
52 | }
53 |
54 | module.exports = ReportController;
55 |
--------------------------------------------------------------------------------
/app/controller/web/home.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Controller = require('egg').Controller;
4 |
5 | class HomeController extends Controller {
6 |
7 | // 系统应用列表
8 | async systemlist() {
9 | const { ctx } = this;
10 | await ctx.render('home', {
11 | data: {
12 | title: '应用列表',
13 | },
14 | });
15 | }
16 |
17 | // 新增系统选择应用类型
18 | async selectype() {
19 | const { ctx } = this;
20 | await ctx.render('selectype', {
21 | data: {
22 | title: '选择应用类型',
23 | },
24 | });
25 | }
26 |
27 | // 用户登录
28 | async login() {
29 | const { ctx } = this;
30 | await ctx.render('login', {
31 | data: {
32 | title: '登录系统',
33 | gh_client_id: this.app.config.github.client_id,
34 | gh_scope: this.app.config.github.scope,
35 | wb_client_id: this.app.config.weibo.client_id,
36 | wb_scope: this.app.config.weibo.scope,
37 | wx_client_id: this.app.config.wechat.client_id,
38 | },
39 | });
40 | }
41 |
42 | // 系统列表
43 | async systems() {
44 | const { ctx } = this;
45 | await ctx.render('systems', {
46 | data: {
47 | title: '系统列表',
48 | },
49 | });
50 | }
51 |
52 | // 用户管理
53 | async users() {
54 | const { ctx } = this;
55 | await ctx.render('users', {
56 | data: {
57 | title: '用户列表',
58 | },
59 | });
60 | }
61 |
62 | // 系统重启信息
63 | async errors() {
64 | const { ctx } = this;
65 | await ctx.render('errors', {
66 | data: {
67 | title: '系统重启信息',
68 | },
69 | });
70 | }
71 |
72 | // 系统重启信息
73 | async emails() {
74 | const { ctx } = this;
75 | await ctx.render('emails', {
76 | data: {
77 | title: '邮件管理',
78 | },
79 | });
80 | }
81 |
82 | }
83 |
84 | module.exports = HomeController;
85 |
--------------------------------------------------------------------------------
/app/controller/web/wx.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Controller = require('egg').Controller;
4 |
5 | class WxController extends Controller {
6 |
7 | // 新增系统
8 | async wxaddsystem() {
9 | const { ctx } = this;
10 | await ctx.render('wx/addsystem', {
11 | data: {
12 | title: '新增系统',
13 | },
14 | });
15 | }
16 | // 小程序首页
17 | async wxhome() {
18 | const { ctx } = this;
19 | await ctx.render('wx/home', {
20 | data: {
21 | title: '数据分析',
22 | },
23 | });
24 | }
25 | // 小程序设置页面
26 | async wxsetting() {
27 | const { ctx } = this;
28 | await ctx.render('wx/setting', {
29 | data: {
30 | title: '系统设置',
31 | },
32 | });
33 | }
34 |
35 | // 访问页面性能数据
36 | async wxpagesavg() {
37 | const { ctx } = this;
38 | await ctx.render('wx/pagesavg', {
39 | data: {
40 | title: '页面平均性能指标',
41 | },
42 | });
43 | }
44 |
45 | // 单页面访问页面列表性能
46 | async wxpageslist() {
47 | const { ctx } = this;
48 | await ctx.render('wx/pageslist', {
49 | data: {
50 | title: '页面性能数据列表',
51 | },
52 | });
53 | }
54 |
55 | async wxpagedetails() {
56 | const { ctx } = this;
57 | await ctx.render('wx/pagedetails', {
58 | data: {
59 | title: '页面性能详情数据',
60 | },
61 | });
62 | }
63 |
64 | // ajax请求平均性能数据
65 | async wxajaxavg() {
66 | const { ctx } = this;
67 | await ctx.render('wx/ajaxavg', {
68 | data: {
69 | title: 'ajax平均性能指标',
70 | },
71 | });
72 | }
73 |
74 | // ajax详情
75 | async wxajaxdetail() {
76 | const { ctx } = this;
77 | await ctx.render('wx/ajaxdetail', {
78 | data: {
79 | title: 'ajax详情',
80 | },
81 | });
82 | }
83 |
84 | async wxajaxitemdetail() {
85 | const { ctx } = this;
86 | await ctx.render('wx/ajaxitemdetail', {
87 | data: {
88 | title: '单个ajax详情信息',
89 | },
90 | });
91 | }
92 |
93 | // 错误分类列表
94 | async wxerroravg() {
95 | const { ctx } = this;
96 | await ctx.render('wx/erroravg', {
97 | data: {
98 | title: '错误分类列表',
99 | },
100 | });
101 | }
102 |
103 | // 错误详情列表
104 | async wxerrordetail() {
105 | const { ctx } = this;
106 | await ctx.render('wx/errordetail', {
107 | data: {
108 | title: '错误详情列表',
109 | },
110 | });
111 | }
112 | // 错误页面详情信息
113 | async wxerroritemdetail() {
114 | const { ctx } = this;
115 | await ctx.render('wx/erroritemdetail', {
116 | data: {
117 | title: '错误页面详情信息',
118 | },
119 | });
120 | }
121 |
122 | // 用户访问轨迹
123 | async analysislist() {
124 | const { ctx } = this;
125 | await ctx.render('wx/analysislist', {
126 | data: {
127 | title: '用户行为访问轨迹',
128 | },
129 | });
130 | }
131 |
132 | // 访问轨迹详情
133 | async analysisdetail() {
134 | const { ctx } = this;
135 | await ctx.render('wx/analysisdetail', {
136 | data: {
137 | title: '用户访问轨迹详情',
138 | },
139 | });
140 | }
141 | // TOP分析
142 | async wxtop() {
143 | const { ctx } = this;
144 | await ctx.render('wx/top', {
145 | data: {
146 | title: 'TOP指标',
147 | },
148 | });
149 | }
150 |
151 | async wxdiagram() {
152 | const { ctx } = this;
153 | await ctx.render('wx/diagram', {
154 | data: {
155 | title: '全国省份访问量热力图',
156 | },
157 | });
158 | }
159 | }
160 |
161 | module.exports = WxController;
162 |
--------------------------------------------------------------------------------
/app/middleware/token_required.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const { URL } = require('url');
3 |
4 | // 校验用户是否登录
5 | module.exports = () => {
6 | return async function(ctx, next) {
7 | const referer = ctx.request.header.referer || '';
8 | const url = new URL(referer);
9 | if (ctx.app.config.origin && ctx.app.config.origin.indexOf(url.origin) === -1) {
10 | ctx.body = {
11 | code: 1004,
12 | desc: '域名来源有误,请检查config的origin配置',
13 | };
14 | return;
15 | }
16 | const usertoken = ctx.cookies.get('usertoken', {
17 | encrypt: true,
18 | signed: true,
19 | }) || '';
20 | if (!usertoken) {
21 | ctx.body = {
22 | code: 1004,
23 | desc: '用户未登录,请重新登录',
24 | };
25 | return;
26 | }
27 | const data = await ctx.service.user.finUserForToken(usertoken);
28 | if (!data || !data.user_name) {
29 | ctx.cookies.set('usertoken', '');
30 | const descr = data && !data.user_name ? data.desc : '登录用户无效,请重新登录!';
31 | ctx.body = {
32 | code: 1004,
33 | desc: descr,
34 | };
35 | return;
36 | }
37 | await next();
38 | };
39 | };
40 |
--------------------------------------------------------------------------------
/app/model/Web/web_ajaxs.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = app => {
4 | const mongoose = app.mongoose;
5 | const Schema = mongoose.Schema;
6 | const conn = app.mongooseDB.get('db3');
7 |
8 | const WebAjaxsSchema = new Schema({
9 | app_id: { type: String }, // 所属系统ID
10 | create_time: { type: Date, default: Date.now },
11 | url: { type: String }, // 访问的ajaxUrl
12 | speed_type: { type: Number }, // 访问速度类型 1:正常 2:慢
13 | method: { type: String }, // 资源请求方式
14 | duration: { type: Number, default: 0 }, // AJAX响应时间 单位:ms
15 | decoded_body_size: { type: Number, default: 0 }, // 返回字段大小 单位:B
16 | options: { type: String }, // ajax请求参数
17 | full_url: { type: String }, // 完整url
18 | call_url: { type: String }, // 调用页面的URL
19 | mark_page: { type: String }, // 所有资源页面统一标识 html img css js 用户系统信息等
20 | mark_user: { type: String }, // 统一某一时间段用户标识
21 | }, {
22 | shardKey: { _id: 'hashed' },
23 | });
24 |
25 | WebAjaxsSchema.index({ speed_type: 1, url: 1, create_time: -1 });
26 | WebAjaxsSchema.index({ speed_type: 1, call_url: 1, create_time: -1 });
27 |
28 | app.models.WebAjaxs = function(appId) {
29 | return conn.model(`web_ajaxs_${appId}`, WebAjaxsSchema);
30 | };
31 | };
32 |
--------------------------------------------------------------------------------
/app/model/Web/web_environment.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = app => {
4 | const mongoose = app.mongoose;
5 | const Schema = mongoose.Schema;
6 | const conn = app.mongooseDB.get('db3');
7 |
8 | const WebEnvironmentSchema = new Schema({
9 | app_id: { type: String }, // 所属系统
10 | create_time: { type: Date, default: Date.now }, // 用户访问时间
11 | url: { type: String }, // 访问页面的url
12 | mark_page: { type: String }, // 所有资源页面统一标识 html img css js 用户系统信息等
13 | mark_user: { type: String }, // 统一某一时间段用户标识
14 | mark_uv: { type: String }, // 统一uv标识
15 | browser: { type: String }, // 浏览器名称
16 | borwser_version: { type: String }, // 浏览器版本
17 | system: { type: String }, // 操作系统
18 | system_version: { type: String }, // 系统版本
19 | ip: { type: String }, // 访问者IP
20 | county: { type: String }, // 国家
21 | province: { type: String }, // 省
22 | city: { type: String }, // 市
23 | }, {
24 | shardKey: { _id: 'hashed' },
25 | });
26 | WebEnvironmentSchema.index({ url: 1, create_time: -1 });
27 | WebEnvironmentSchema.index({ ip: 1, create_time: -1 });
28 | WebEnvironmentSchema.index({ create_time: -1 });
29 | WebEnvironmentSchema.index({ mark_page: 1 });
30 | WebEnvironmentSchema.index({ mark_user: 1 });
31 |
32 | app.models.WebEnvironment = function(appId) {
33 | return conn.model(`web_environment_${appId}`, WebEnvironmentSchema);
34 | };
35 | };
36 |
--------------------------------------------------------------------------------
/app/model/Web/web_errors.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = app => {
4 | const mongoose = app.mongoose;
5 | const Schema = mongoose.Schema;
6 | const conn = app.mongooseDB.get('db3');
7 |
8 | const WebErrorsSchema = new Schema({
9 | app_id: { type: String }, // 所属系统
10 | url: { type: String }, // 访问页面的url
11 | create_time: { type: Date, default: Date.now }, // 用户访问时间
12 | msg: { type: String }, // 错误信息
13 | category: { type: String }, // 错误类型
14 | resource_url: { type: String }, // 错误资源URL
15 | target: { type: String }, // 资源类型
16 | type: { type: String }, // 错误类型
17 | status: { type: String }, // HTTP状态码
18 | text: { type: String }, // 资源错误提示
19 | col: { type: String }, // js错误列号
20 | line: { type: String }, // js错误行号
21 | querydata: { type: String }, // http请求参数
22 | method: { type: String }, // 资源请求方式
23 | fullurl: { type: String }, // 完整url
24 | mark_page: { type: String }, // 所有资源页面统一标识 html img css js 用户系统信息等
25 | mark_user: { type: String }, // 统一某一时间段用户标识
26 | }, {
27 | shardKey: { _id: 'hashed' },
28 | });
29 |
30 | WebErrorsSchema.index({ category: 1, resource_url: 1, create_time: -1 });
31 | WebErrorsSchema.index({ resource_url: 1, create_time: -1 });
32 | WebErrorsSchema.index({ create_time: -1 });
33 |
34 | app.models.WebErrors = function(appId) {
35 | return conn.model(`web_errors_${appId}`, WebErrorsSchema);
36 | };
37 | };
38 |
--------------------------------------------------------------------------------
/app/model/Web/web_pages.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = app => {
4 | const mongoose = app.mongoose;
5 | const Schema = mongoose.Schema;
6 | const conn = app.mongooseDB.get('db3');
7 |
8 | const WebPagesSchema = new Schema({
9 | app_id: { type: String }, // 所属系统id
10 | create_time: { type: Date, default: Date.now }, // 访问时间
11 | url: { type: String }, // url域名
12 | full_url: { type: String }, // 完整域名
13 | pre_url: { type: String }, // 用户访问的上一个页面,本页面来源
14 | speed_type: { type: Number }, // 访问速度类型 1:正常 2:慢
15 | is_first_in: { type: Number }, // 是否是某次会话的首次进入 2: 首次 1:非首次
16 | mark_page: { type: String }, // 所有资源页面统一标识 html img css js 用户系统信息等
17 | mark_user: { type: String }, // 统一某一时间段用户标识
18 | load_time: { type: Number }, // 页面完全加载时间 单位:ms
19 | dns_time: { type: Number }, // dns解析时间 单位:ms
20 | tcp_time: { type: Number }, // TCP连接时间
21 | dom_time: { type: Number }, // DOM构建时间 单位:ms
22 | resource_list: { type: Array }, // 资源性能数据列表
23 | total_res_size: { type: Number }, // 页面资源大小
24 | white_time: { type: Number }, // 白屏时间 单位:ms
25 | redirect_time: { type: Number }, // 页面重定向时间
26 | unload_time: { type: Number }, // unload 时间
27 | request_time: { type: Number }, // request请求耗时
28 | analysisDom_time: { type: Number }, // 解析dom耗时
29 | ready_time: { type: Number }, // 页面准备时间
30 | screenwidth: { type: Number }, // 屏幕宽度
31 | screenheight: { type: Number }, // 屏幕高度
32 | }, {
33 | shardKey: { _id: 'hashed' },
34 | });
35 |
36 | WebPagesSchema.index({ speed_type: 1, is_first_in: 1, url: 1, create_time: -1 });
37 | WebPagesSchema.index({ create_time: -1 });
38 |
39 | app.models.WebPages = function(appId) {
40 | return conn.model(`web_pages_${appId}`, WebPagesSchema);
41 | };
42 | };
43 |
--------------------------------------------------------------------------------
/app/model/Web/web_pvuvip.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = app => {
4 | const mongoose = app.mongoose;
5 | const Schema = mongoose.Schema;
6 | const conn = app.mongooseDB.get('db3');
7 |
8 | const WebPvUvIpSchema = new Schema({
9 | app_id: { type: String }, // 所属系统ID
10 | pv: { type: Number }, // PV统计
11 | uv: { type: Number }, // uv统计
12 | ip: { type: Number }, // ip统计
13 | ajax: { type: Number }, // ajax访问量统计
14 | bounce: { type: String }, // 跳出率
15 | depth: { type: Number }, // 平均访问深度
16 | flow: { type: Number }, // 流量消费总额
17 | type: { type: Number, default: 1 }, // 1:每分钟数据 2:每小时数据
18 | create_time: { type: Date, default: Date.now },
19 | });
20 |
21 | WebPvUvIpSchema.index({ type: 1, app_id: 1, bounce: 1, depth: 1, create_time: 1 });
22 | WebPvUvIpSchema.index({ type: 1, app_id: 1, create_time: 1 });
23 |
24 | return conn.model('WebPvUvIp', WebPvUvIpSchema);
25 | };
26 |
--------------------------------------------------------------------------------
/app/model/Web/web_report.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = app => {
4 | const mongoose = app.mongoose;
5 | const Schema = mongoose.Schema;
6 | const Mixed = Schema.Types.Mixed;
7 | const conn = app.mongooseDB.get('db1');
8 | if (!conn) return;
9 |
10 | const WebReportSchema = new Schema({
11 | app_id: { type: String }, // 系统标识
12 | create_time: { type: Date, default: Date.now }, // 创建时间
13 | user_agent: { type: String }, // 用户浏览器信息标识
14 | ip: { type: String }, // 用户ip
15 | mark_page: { type: String }, // 所有资源页面统一标识 html img css js 用户系统信息等
16 | mark_user: { type: String }, // 统一某一时间段用户标识
17 | mark_uv: { type: String }, // 统一uv标识
18 | url: { type: String }, // 访问url
19 | pre_url: { type: String }, // 上一页面来源
20 | performance: { type: Mixed }, // 用户浏览器性能数据
21 | error_list: { type: Mixed }, // 错误信息列表
22 | resource_list: { type: Mixed }, // 资源性能数据列表
23 | screenwidth: { type: Number }, // 屏幕宽度
24 | screenheight: { type: Number }, // 屏幕高度
25 | type: { type: Number }, // 1:网页性能上报 2:后续操作ajax上报 3:后续操作错误上报
26 | });
27 | WebReportSchema.index({ create_time: 1 });
28 |
29 | return conn.model('WebReport', WebReportSchema);
30 | };
31 |
--------------------------------------------------------------------------------
/app/model/Web/web_resource.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = app => {
4 | const mongoose = app.mongoose;
5 | const Schema = mongoose.Schema;
6 | const Mixed = Schema.Types.Mixed;
7 | const conn = app.mongooseDB.get('db3');
8 |
9 | const WebResourceSchema = new Schema({
10 | app_id: { type: String }, // 所属系统
11 | create_time: { type: Date, default: Date.now }, // 用户访问时间
12 | url: { type: String }, // 访问页面的url
13 | full_url: { type: String }, // 完整的资源名称
14 | speed_type: { type: Number }, // 访问速度类型 1:正常 2:慢
15 | resource_datas: { type: Mixed }, // 页面所有加载资源json对象
16 | name: { type: String }, // 资源名称
17 | method: { type: String, default: 'GET' }, // 资源请求方式
18 | type: { type: String }, // 资源类型
19 | duration: { type: Number, default: 0 }, // 资源请求耗时
20 | decoded_body_size: { type: Number, default: 0 }, // 资源请求返回大小
21 | next_hop_protocol: { type: String, default: 'http/1.1' }, // 资源请求类型
22 | mark_page: { type: String }, // 所有资源页面统一标识 html img css js 用户系统信息等
23 | mark_user: { type: String }, // 统一某一时间段用户标识
24 | }, {
25 | shardKey: { _id: 'hashed' },
26 | });
27 |
28 | WebResourceSchema.index({ speed_type: 1, name: 1, create_time: -1 });
29 | WebResourceSchema.index({ name: 1, create_time: -1 });
30 | WebResourceSchema.index({ speed_type: 1, url: 1 });
31 |
32 | app.models.WebResource = function(appId) {
33 | return conn.model(`web_resources_${appId}`, WebResourceSchema);
34 | };
35 | };
36 |
--------------------------------------------------------------------------------
/app/model/Web/web_statis.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = app => {
4 | const mongoose = app.mongoose;
5 | const Schema = mongoose.Schema;
6 | const conn = app.mongooseDB.get('db3');
7 |
8 | const WebStatisSchema = new Schema({
9 | app_id: { type: String }, // 所属系统ID
10 | top_pages: { type: Array }, // top访问page列表
11 | top_jump_out: { type: Array }, // top访问跳出率页面列表
12 | top_browser: { type: Array }, // top浏览器排行
13 | provinces: { type: Array }, // 省份访问流量排行
14 | create_time: { type: Date, default: Date.now },
15 | });
16 |
17 | WebStatisSchema.index({ app_id: 1, create_time: 1 });
18 |
19 | return conn.model('WebStatis', WebStatisSchema);
20 | };
21 |
--------------------------------------------------------------------------------
/app/model/Wx/wx_ajaxs.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = app => {
4 | const mongoose = app.mongoose;
5 | const Schema = mongoose.Schema;
6 | const Mixed = Schema.Types.Mixed;
7 | const conn = app.mongooseDB.get('db3');
8 |
9 | const WxAjaxsSchema = new Schema({
10 | app_id: { type: String }, // 系统标识
11 | create_time: { type: Date, default: Date.now }, // 创建时间
12 | mark_page: { type: String }, // 所有资源页面统一标识
13 | mark_user: { type: String }, // 统一某一时间段用户标识
14 | duration: { type: Number }, // 请求耗时
15 | name: { type: String }, // api路径
16 | full_name: { type: String }, // api路径
17 | method: { type: String }, // 请求方式
18 | body_size: { type: Number }, // 返回资源大小
19 | options: { type: Mixed }, // 请求参数
20 | speed_type: { type: Number }, // 访问速度类型 1:正常 2:慢
21 | path: { type: String }, // 所属path路径
22 | }, {
23 | shardKey: { _id: 'hashed' },
24 | });
25 |
26 | WxAjaxsSchema.index({ speed_type: 1, name: 1, create_time: -1 });
27 | WxAjaxsSchema.index({ speed_type: 1, path: 1, create_time: -1 });
28 |
29 | app.models.WxAjaxs = function(appId) {
30 | return conn.model(`wx_ajaxs_${appId}`, WxAjaxsSchema);
31 | };
32 | };
33 |
--------------------------------------------------------------------------------
/app/model/Wx/wx_errors.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = app => {
4 | const mongoose = app.mongoose;
5 | const Schema = mongoose.Schema;
6 | const Mixed = Schema.Types.Mixed;
7 | const conn = app.mongooseDB.get('db3');
8 |
9 | const WxErrorsSchema = new Schema({
10 | app_id: { type: String }, // 系统标识
11 | create_time: { type: Date, default: Date.now }, // 创建时间
12 | mark_page: { type: String }, // 所有资源页面统一标识
13 | mark_user: { type: String }, // 统一某一时间段用户标识
14 | col: { type: Number }, // 错误行
15 | line: { type: Number }, // 错误列
16 | name: { type: String }, // 错误资源名称
17 | msg: { type: String }, // 错误信息
18 | type: { type: String }, // 错误类型
19 | method: { type: String }, // ajax请求方式
20 | status: { type: String }, // ajax请求返回状态
21 | options: { type: Mixed }, // ajax请求参数
22 | path: { type: String }, // 所属path路径
23 | }, {
24 | shardKey: { _id: 'hashed' },
25 | });
26 |
27 | WxErrorsSchema.index({ type: 1, name: 1, create_time: 1 });
28 | WxErrorsSchema.index({ type: 1, path: 1, create_time: 1 });
29 | WxErrorsSchema.index({ name: 1, create_time: -1 });
30 |
31 | app.models.WxErrors = function(appId) {
32 | return conn.model(`wx_errors_${appId}`, WxErrorsSchema);
33 | };
34 | };
35 |
--------------------------------------------------------------------------------
/app/model/Wx/wx_pages.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = app => {
4 | const mongoose = app.mongoose;
5 | const Schema = mongoose.Schema;
6 | const Mixed = Schema.Types.Mixed;
7 | const conn = app.mongooseDB.get('db3');
8 |
9 | const WxPagesSchema = new Schema({
10 | app_id: { type: String }, // 系统标识
11 | create_time: { type: Date, default: Date.now }, // 创建时间
12 | path: { type: String }, // 当前路径
13 | options: { type: Mixed }, // 路径参数
14 | mark_page: { type: String }, // 所有资源页面统一标识
15 | mark_user: { type: String }, // 统一某一时间段用户标识
16 | mark_uv: { type: String }, // 统一uv标识
17 | net: { type: String }, // 网络类型
18 | ip: { type: String }, // 用户ip
19 | county: { type: String }, // 国家
20 | province: { type: String }, // 省
21 | city: { type: String }, // 市
22 | brand: { type: String }, // 手机品牌
23 | model: { type: String }, // 手机型号
24 | screenWidth: { type: String }, // 屏幕宽度
25 | screenHeight: { type: String }, // 屏幕高度
26 | language: { type: String }, // 微信设置的语言
27 | version: { type: String }, // 微信版本号
28 | system: { type: String }, // 操作系统版本
29 | platform: { type: String }, // 客户端平台
30 | SDKVersion: { type: String }, // 客户端基础库版本
31 | }, {
32 | shardKey: { _id: 'hashed' },
33 | });
34 |
35 | WxPagesSchema.index({ path: 1, create_time: -1 });
36 | WxPagesSchema.index({ create_time: -1 });
37 | WxPagesSchema.index({ mark_page: -1 });
38 | WxPagesSchema.index({ mark_user: -1 });
39 |
40 | app.models.WxPages = function(appId) {
41 | return conn.model(`wx_pages_${appId}`, WxPagesSchema);
42 | };
43 | };
44 |
--------------------------------------------------------------------------------
/app/model/Wx/wx_pvuvip.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = app => {
4 | const mongoose = app.mongoose;
5 | const Schema = mongoose.Schema;
6 | const conn = app.mongooseDB.get('db3');
7 |
8 | const WxPvUvIpSchema = new Schema({
9 | app_id: { type: String }, // 所属系统ID
10 | pv: { type: Number }, // PV统计
11 | uv: { type: Number }, // uv统计
12 | ip: { type: Number }, // ip统计
13 | ajax: { type: Number }, // ajax访问量统计
14 | bounce: { type: String }, // 跳出率
15 | depth: { type: Number }, // 平均访问深度
16 | flow: { type: Number }, // 流量消费总额
17 | type: { type: Number, default: 1 }, // 1:每分钟数据 2:每天数据
18 | create_time: { type: Date, default: Date.now },
19 | });
20 |
21 | WxPvUvIpSchema.index({ type: 1, app_id: 1, create_time: 1 });
22 | WxPvUvIpSchema.index({ type: 1, app_id: 1, bounce: 1, depth: 1, create_time: 1 });
23 |
24 | return conn.model('WxPvUvIp', WxPvUvIpSchema);
25 | };
26 |
--------------------------------------------------------------------------------
/app/model/Wx/wx_report.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = app => {
4 | const mongoose = app.mongoose;
5 | const Schema = mongoose.Schema;
6 | const Mixed = Schema.Types.Mixed;
7 | const conn = app.mongooseDB.get('db1');
8 | if (!conn) return;
9 |
10 | const WxReportSchema = new Schema({
11 | app_id: { type: String }, // 系统标识
12 | create_time: { type: Date, default: Date.now }, // 创建时间
13 | errs: { type: Array }, // 用户浏览器信息标识
14 | ip: { type: String }, // 用户ip
15 | mark_page: { type: String }, // 所有资源页面统一标识 html img css js 用户系统信息等
16 | mark_user: { type: String }, // 统一某一时间段用户标识
17 | mark_uv: { type: String }, // 统一uv标识
18 | net: { type: String }, // 网络类型
19 | system: { type: Mixed }, // 用户手机信息
20 | loc: { type: Mixed }, // 用户地理位置信息
21 | userInfo: { type: Mixed }, // 用户信息
22 | pages: { type: Mixed }, // 小程序path路径信息
23 | ajaxs: { type: Array }, // 当前path页面ajax相关信息
24 | type: { type: Number }, // 1:网页级别上报 2:后续操作ajax|error上报
25 | });
26 | WxReportSchema.index({ create_time: 1 });
27 |
28 | return conn.model('WxReport', WxReportSchema);
29 | };
30 |
--------------------------------------------------------------------------------
/app/model/Wx/wx_statis.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = app => {
4 | const mongoose = app.mongoose;
5 | const Schema = mongoose.Schema;
6 | const conn = app.mongooseDB.get('db3');
7 |
8 | const WxStatisSchema = new Schema({
9 | app_id: { type: String }, // 所属系统ID
10 | top_pages: { type: Array }, // top访问page列表
11 | top_jump_out: { type: Array }, // top访问跳出率页面列表
12 | top_brand: { type: Array }, // top手机品牌排行
13 | provinces: { type: Array }, // 省份访问流量排行
14 | create_time: { type: Date, default: Date.now },
15 | });
16 |
17 | WxStatisSchema.index({ app_id: 1, create_time: 1 });
18 |
19 | return conn.model('WxStatis', WxStatisSchema);
20 | };
21 |
--------------------------------------------------------------------------------
/app/model/email.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = app => {
4 | const mongoose = app.mongoose;
5 | const Schema = mongoose.Schema;
6 | const conn = app.mongooseDB.get('db3');
7 |
8 | const EmailSchema = new Schema({
9 | email: { type: String }, // 用户名称
10 | name: { type: String }, // 用户名
11 | system_ids: { type: Array }, // 用户所拥有的应用信息
12 | create_time: { type: Date, default: Date.now }, // 用户访问时间
13 | });
14 |
15 | EmailSchema.index({ email: -1 });
16 |
17 | return conn.model('Email', EmailSchema);
18 | };
19 |
--------------------------------------------------------------------------------
/app/model/ip_library.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = app => {
4 | const mongoose = app.mongoose;
5 | const Schema = mongoose.Schema;
6 | const conn = app.mongooseDB.get('db3');
7 |
8 | const ipLibrarySchema = new Schema({
9 | ip: { type: String }, // ip
10 | country: { type: String }, // 国家
11 | province: { type: String }, // 省
12 | city: { type: String }, // 市
13 | latitude: { type: Number }, // 纬度
14 | longitude: { type: Number }, // 经度
15 | });
16 |
17 | ipLibrarySchema.index({ ip: -1 });
18 |
19 | return conn.model('IpLibrary', ipLibrarySchema);
20 | };
21 |
--------------------------------------------------------------------------------
/app/model/system.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = app => {
4 | const mongoose = app.mongoose;
5 | const Schema = mongoose.Schema;
6 | const conn = app.mongooseDB.get('db3');
7 |
8 | const WebResourceSchema = new Schema({
9 | system_domain: { type: String }, // 系统 域名
10 | system_name: { type: String }, // 系统名称
11 | app_id: { type: String }, // 系统appId标识
12 | type: { type: String, default: 'web' }, // 浏览器:web 微信小程序 :wx
13 | user_id: { type: Array }, // 应用所属用户ID
14 | create_time: { type: Date, default: Date.now }, // 用户访问时间
15 | is_use: { type: Number, default: 0 }, // 是否需要统计 0:是 1:否
16 | slow_page_time: { type: Number, default: 5 }, // 页面加载页面阀值 单位:s
17 | slow_js_time: { type: Number, default: 2 }, // js慢资源阀值 单位:s
18 | slow_css_time: { type: Number, default: 2 }, // 慢加载css资源阀值 单位:S
19 | slow_img_time: { type: Number, default: 2 }, // 慢图片加载资源阀值 单位:S
20 | slow_ajax_time: { type: Number, default: 2 }, // AJAX加载阀值
21 | is_statisi_pages: { type: Number, default: 0 }, // 是否统计页面性能信息 0:是 1:否
22 | is_statisi_ajax: { type: Number, default: 0 }, // 是否统计页面Ajax性能资源 0:是 1:否
23 | is_statisi_resource: { type: Number, default: 0 }, // 是否统计页面加载资源性能信息 0:是 1:否
24 | is_statisi_system: { type: Number, default: 0 }, // 是否存储用户系统信息资源信息 0:是 1:否
25 | is_statisi_error: { type: Number, default: 0 }, // 是否上报页面错误信息 0:是 1:否
26 | is_daily_use: { type: Number, default: 0 }, // 是否发送日报 0:是 1:否
27 | daliy_list: { type: Array }, // 日报列表
28 | is_highest_use: { type: Number, default: 0 }, // 是否发送pv邮件 0:是 1:否
29 | highest_list: { type: Array }, // 突破历史pv峰值时发送邮件列表
30 | });
31 |
32 | WebResourceSchema.index({ app_id: -1, create_time: 1, system_domain: -1, user_id: -1 });
33 |
34 | return conn.model('System', WebResourceSchema);
35 | };
36 |
--------------------------------------------------------------------------------
/app/model/user.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = app => {
4 | const mongoose = app.mongoose;
5 | const Schema = mongoose.Schema;
6 | const conn = app.mongooseDB.get('db3');
7 |
8 | const UserSchema = new Schema({
9 | user_name: { type: String }, // 用户名称
10 | pass_word: { type: String }, // 用户密码
11 | system_ids: { type: Array }, // 用户所拥有的系统Id
12 | is_use: { type: Number, default: 0 }, // 是否禁用 0:正常 1:禁用
13 | level: { type: Number, default: 1 }, // 用户等级(0:管理员,1:普通用户)
14 | token: { type: String }, // 用户秘钥
15 | usertoken: { type: String }, // 用户登录态秘钥
16 | create_time: { type: Date, default: Date.now }, // 用户访问时间
17 | });
18 |
19 | UserSchema.index({ user_name: -1, token: -1 });
20 |
21 | return conn.model('User', UserSchema);
22 | };
23 |
--------------------------------------------------------------------------------
/app/public/file/web-report-axios.min.js.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangweianger/zanePerfor/444fd3cc1aaa77abd6a2924a55955987d74a351e/app/public/file/web-report-axios.min.js.zip
--------------------------------------------------------------------------------
/app/public/file/web-report-default.min.js.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangweianger/zanePerfor/444fd3cc1aaa77abd6a2924a55955987d74a351e/app/public/file/web-report-default.min.js.zip
--------------------------------------------------------------------------------
/app/public/file/web-report-fetch.min.js.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangweianger/zanePerfor/444fd3cc1aaa77abd6a2924a55955987d74a351e/app/public/file/web-report-fetch.min.js.zip
--------------------------------------------------------------------------------
/app/public/file/web-report-jquery.min.js.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangweianger/zanePerfor/444fd3cc1aaa77abd6a2924a55955987d74a351e/app/public/file/web-report-jquery.min.js.zip
--------------------------------------------------------------------------------
/app/public/file/web-report-none.min.js.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangweianger/zanePerfor/444fd3cc1aaa77abd6a2924a55955987d74a351e/app/public/file/web-report-none.min.js.zip
--------------------------------------------------------------------------------
/app/public/file/web-report-vue.min.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangweianger/zanePerfor/444fd3cc1aaa77abd6a2924a55955987d74a351e/app/public/file/web-report-vue.min.zip
--------------------------------------------------------------------------------
/app/public/file/wx-report-sdk.min.js.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangweianger/zanePerfor/444fd3cc1aaa77abd6a2924a55955987d74a351e/app/public/file/wx-report-sdk.min.js.zip
--------------------------------------------------------------------------------
/app/public/font/JTUSjIg1_i6t8kCHKm459WlhzQ.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangweianger/zanePerfor/444fd3cc1aaa77abd6a2924a55955987d74a351e/app/public/font/JTUSjIg1_i6t8kCHKm459WlhzQ.woff
--------------------------------------------------------------------------------
/app/public/img/PopLayer-close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangweianger/zanePerfor/444fd3cc1aaa77abd6a2924a55955987d74a351e/app/public/img/PopLayer-close.png
--------------------------------------------------------------------------------
/app/public/img/github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangweianger/zanePerfor/444fd3cc1aaa77abd6a2924a55955987d74a351e/app/public/img/github.png
--------------------------------------------------------------------------------
/app/public/img/icon-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangweianger/zanePerfor/444fd3cc1aaa77abd6a2924a55955987d74a351e/app/public/img/icon-1.png
--------------------------------------------------------------------------------
/app/public/img/icon-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangweianger/zanePerfor/444fd3cc1aaa77abd6a2924a55955987d74a351e/app/public/img/icon-2.png
--------------------------------------------------------------------------------
/app/public/img/icon-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangweianger/zanePerfor/444fd3cc1aaa77abd6a2924a55955987d74a351e/app/public/img/icon-3.png
--------------------------------------------------------------------------------
/app/public/img/loading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangweianger/zanePerfor/444fd3cc1aaa77abd6a2924a55955987d74a351e/app/public/img/loading.gif
--------------------------------------------------------------------------------
/app/public/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangweianger/zanePerfor/444fd3cc1aaa77abd6a2924a55955987d74a351e/app/public/img/logo.png
--------------------------------------------------------------------------------
/app/public/img/logo_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangweianger/zanePerfor/444fd3cc1aaa77abd6a2924a55955987d74a351e/app/public/img/logo_1.png
--------------------------------------------------------------------------------
/app/public/img/wechat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangweianger/zanePerfor/444fd3cc1aaa77abd6a2924a55955987d74a351e/app/public/img/wechat.png
--------------------------------------------------------------------------------
/app/public/img/weibo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangweianger/zanePerfor/444fd3cc1aaa77abd6a2924a55955987d74a351e/app/public/img/weibo.png
--------------------------------------------------------------------------------
/app/public/js/config.js:
--------------------------------------------------------------------------------
1 |
2 | 'use strict';
3 |
4 | const config = {
5 | // 登陆页面
6 | loginUrl: '/login',
7 |
8 | // 登陆成功后需要跳转到的页面
9 | homeUrl: '/',
10 |
11 | // 根接口
12 | baseApi: '/',
13 |
14 | // ajax 请求超时时间
15 | ajaxtimeout: 15000,
16 |
17 | // 发送验证码时间间隔
18 | msgTime: 60,
19 |
20 | // 七牛图片根地址
21 | imgBaseUrl: 'http://ormfcl92t.bkt.clouddn.com/',
22 |
23 | // 隐藏显示时间
24 | containerShowTime: 10,
25 |
26 | // pagesize 分页数量
27 | pageSize: 50,
28 | };
29 |
30 | window.config = config; // eslint-disable-line
31 |
--------------------------------------------------------------------------------
/app/public/js/vue-components.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | let Component = {
4 | commonsearch:{
5 | template: `
6 |
7 |
8 |
{{timeText}}
9 |
10 |
1分钟
11 | 5分钟
12 | 10分钟
13 | 30分钟
14 | 1小时
15 | 6小时
16 | 12小时
17 | 1天
18 |
19 |
20 |
21 |
`,
22 | props:{
23 | done:{
24 | type:Function,
25 | default:()=>{}
26 | },
27 | },
28 | data:function(){
29 | return{
30 | timeText:'全部'
31 | }
32 | },
33 | mounted(){
34 | let _this=this;
35 | // 添加active样式
36 | let selecttimes = util.getStorage('local', 'userselectTime') || 60000
37 | let objs = $('.select-time li')
38 | for(let i=0,len=objs.length;i {
48 | e.stopPropagation();
49 | $('.select-time').show();
50 | });
51 | $(document).on('click',function(e){
52 | $('.select-time').hide();
53 | });
54 | $('.select-time').click(function(e){
55 | e.stopPropagation();
56 | })
57 | $('.select-time li').on('click',function(e){
58 | $('.select-time li').removeClass('active')
59 | $(this).addClass('active')
60 | let time = $(this).attr('data-time')
61 | let text = $(this).attr('data-text')
62 | _this.timeText = text
63 | util.setStorage('local','userselectTime',time)
64 | })
65 | },
66 | methods:{
67 | timeSure(){
68 | $('.select-time').hide();
69 | this.done&&this.done()
70 | }
71 | },
72 | }
73 | }
74 | for(let n in Component){
75 | Vue.component(n, Component[n])
76 | }
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
--------------------------------------------------------------------------------
/app/public/js/vue-filters.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | // 时间格式化
3 | if(!new Date().format){
4 | Date.prototype.format = function (fmt) { //author: meizz
5 | var o = {
6 | "M+": this.getMonth() + 1, //月份
7 | "d+": this.getDate(), //日
8 | "h+": this.getHours(), //小时
9 | "H+":this.getHours()>12?this.getHours()-12:this.getHours(),
10 | "m+": this.getMinutes(), //分
11 | "s+": this.getSeconds(), //秒
12 | "q+": Math.floor((this.getMonth() + 3) / 3), //季度
13 | "S": this.getMilliseconds() //毫秒
14 | };
15 | if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
16 | for (var k in o)
17 | if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
18 | return fmt;
19 | }
20 | };
21 |
22 | let Filter = {
23 | // 图片地址过滤器
24 | imgBaseUrl:function(img) {
25 | if (!img) return '../images/index/bg-0.png';
26 | if (img.indexOf('http:') !== -1 || img.indexOf('HTTP:') !== -1 || img.indexOf('https:') !== -1 || img.indexOf('HTTPS:') !== -1) {
27 | return img + '?imageslim';
28 | } else {
29 | return config.imgBaseUrl + img + '?imageslim';
30 | }
31 | },
32 | toFixed(val,type=false){
33 | val = parseFloat(val)
34 | if(type){
35 | val = val/1000
36 | return val>0?val.toFixed(3)+' s':val.toFixed(2);
37 | }else{
38 | return val.toFixed(2)+' ms';
39 | }
40 | },
41 | toSize(val){
42 | val=val*1
43 | if(val>=1024){
44 | return (val/1024).toFixed(2)+' KB'
45 | }else if(val>0){
46 | return val.toFixed(2)+' B'
47 | }else{
48 | return 0
49 | }
50 | },
51 | // 时间过滤器
52 | date(value, gengefu, full) {
53 | if (!value) return;
54 | let ty = gengefu || '-';
55 | if (full) {
56 | return new Date(value).format('yyyy' + ty + 'MM' + ty + 'dd hh:mm:ss');
57 | } else {
58 | return new Date(value).format('yyyy' + ty + 'MM' + ty + 'dd');
59 | };
60 | },
61 | //limitTo过滤器
62 | limitTo(value, num) {
63 | if (!value) return;
64 | var text = "";
65 | if (value.length < num) {
66 | text = value;
67 | } else {
68 | text = value.substring(0, num) + '···';
69 | }
70 | return text;
71 | },
72 | // 应用类型过滤器
73 | systemType(val) {
74 | let result = '';
75 | switch (val) {
76 | case 'web':
77 | result = 'WEB浏览器';
78 | break;
79 | case 'wx':
80 | result = '微信小程序';
81 | break;
82 | }
83 | return result;
84 | },
85 | // 流量单位
86 | flow(val = 0) {
87 | let result = 0;
88 | let value = val;
89 | let index = 0;
90 | while (value >= 1024) {
91 | value = value / 1024
92 | index++;
93 | }
94 | value = value.toFixed(2);
95 | if (index >= 4) {
96 | value = value + 'T'
97 | } else if (index >= 3) {
98 | value = value + 'G'
99 | } else if (index >= 2) {
100 | value = value + 'M'
101 | } else if (index >= 1) {
102 | value = value + 'KB'
103 | } else {
104 | value = value + 'B'
105 | }
106 | return value;
107 | },
108 | }
109 |
110 | window.Filter = {};
111 |
112 | for(let n in Filter){
113 | window['Filter'][n] = Filter[n];
114 | }
115 |
--------------------------------------------------------------------------------
/app/public/lib/bootstrap/fonts/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangweianger/zanePerfor/444fd3cc1aaa77abd6a2924a55955987d74a351e/app/public/lib/bootstrap/fonts/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/app/public/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangweianger/zanePerfor/444fd3cc1aaa77abd6a2924a55955987d74a351e/app/public/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/app/public/lib/bootstrap/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangweianger/zanePerfor/444fd3cc1aaa77abd6a2924a55955987d74a351e/app/public/lib/bootstrap/fonts/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/app/public/lib/bootstrap/fonts/glyphicons-halflings-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangweianger/zanePerfor/444fd3cc1aaa77abd6a2924a55955987d74a351e/app/public/lib/bootstrap/fonts/glyphicons-halflings-regular.woff2
--------------------------------------------------------------------------------
/app/public/lib/bootstrap/js/npm.js:
--------------------------------------------------------------------------------
1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment.
2 | require('../../js/transition.js')
3 | require('../../js/alert.js')
4 | require('../../js/button.js')
5 | require('../../js/carousel.js')
6 | require('../../js/collapse.js')
7 | require('../../js/dropdown.js')
8 | require('../../js/modal.js')
9 | require('../../js/tooltip.js')
10 | require('../../js/popover.js')
11 | require('../../js/scrollspy.js')
12 | require('../../js/tab.js')
13 | require('../../js/affix.js')
--------------------------------------------------------------------------------
/app/public/lib/page/page.css:
--------------------------------------------------------------------------------
1 | /*分页插件样式*/
2 | /**width: 1180px; margin: 0 auto; text-align: center; */
3 | .copot-page{width: 100%;color:#555555;font-size:12px;}
4 | #copot-page{padding: 10px 0; color: #a9a9a9;}
5 | .copot-page a{display:inline-block;padding:3px 8px;border:solid 1px #E7E7E7;border-radius:3px;margin-right:5px;text-decoration:none;font-size:12px;background:#fff; color: #a9a9a9;}
6 | .copot-page a:hover{background:#887FFE;color:#fff;}
7 | .copot-page a.nowPage{background:#887FFE;border:solid 1px #887FFE;color:#fff;}
8 | .copot-page .Page-search-span{margin:0 5px 0 15px;}
9 | .copot-page #Page-search{width:40px;height:28px;padding-left:10px;margin:0 3px;border:solid 1px #E7E7E7;}
10 | .copot-page #pageSearch{width:40px;height:28px;font-size:12px;border:solid 1px #887FFE;background:#887FFE;color:#fff;cursor:pointer; }
--------------------------------------------------------------------------------
/app/public/lib/page/page.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | function Page(json) {
4 | this.nowPage = parseInt(json.nowPage); //当前页
5 | this.parent = json.parent; //分页内容所放的div元素
6 | this.call = json.callback;
7 | this.totalCount = json.totalCount;
8 | this.pageSize = json.pageSize;
9 | this.totalPage = Math.ceil(this.totalCount / this.pageSize); //总页数
10 | this.html = "";
11 | this.setting = json.setting || {
12 | defaultPage: 5, //默认展示的页数
13 | firstPageText: "首页", //第一页的字 (可以是:Home)
14 | prevPageText: "上一页", //上一页的字
15 | nextPageText: "下一页", //下一页的字
16 | lastPageText: "尾页" //尾页的字
17 | };
18 | this.centPage = Math.ceil(this.setting.defaultPage / 2); //中间页
19 | this.init(); //初始化
20 | }
21 | //初始化函数
22 | Page.prototype.init = function () {
23 | this.parent.html(""); //清空
24 | this.firstPage(); //首页
25 | this.prevPageText(); //上一页
26 | this.everyPage(); //分页
27 | this.nextPage(); //下一页
28 | this.lastPage(); //尾页
29 | this.totalPageText(); //页数显示信息
30 | this.parent.append(this.html);
31 | this.callback();
32 | }
33 |
34 | //循环页数
35 | Page.prototype.everyPage = function () {
36 | if (this.totalPage <= this.setting.defaultPage) {
37 | for (var i = 1; i <= this.totalPage; i++) {
38 | if (this.nowPage == i) {
39 | this.html += "" + i + "";
40 | } else {
41 | this.html += "" + i + "";
42 | }
43 | }
44 | } else {
45 | for (var i = 1; i <= this.setting.defaultPage; i++) {
46 | var page1 = this.nowPage - this.centPage + i;
47 | var page2 = this.totalPage - this.setting.defaultPage + i;
48 |
49 | if (this.nowPage < this.centPage) {
50 | if (this.nowPage == i) {
51 | this.html += "" + i + "";
52 | } else {
53 | this.html += "" + i + "";
54 | }
55 | } else if (this.nowPage > this.totalPage - this.centPage) {
56 | if (this.setting.defaultPage - (this.totalPage - this.nowPage) == i) {
57 | this.html += "" + this.nowPage + "";
58 | } else {
59 | this.html += "" + page2 + "";
60 | }
61 | } else {
62 | if (this.centPage == i) {
63 | this.html += "" + page1 + "";
64 | } else {
65 | this.html += "" + page1 + "";
66 | }
67 | }
68 | }
69 | }
70 | }
71 |
72 | //首页
73 | Page.prototype.firstPage = function () {
74 | if (this.nowPage > this.centPage && this.totalPage >= this.setting.defaultPage + 1) {
75 | this.html += "" + this.setting.firstPageText + "";
76 | }
77 | }
78 | //上一页
79 | Page.prototype.prevPageText = function () {
80 | if (this.nowPage >= 2) {
81 | this.html += "" + this.setting.prevPageText + "";
82 | }
83 | }
84 |
85 | //下一页
86 | Page.prototype.nextPage = function () {
87 | if (this.totalPage - this.nowPage >= 1) {
88 | this.html += "" + this.setting.nextPageText + "";
89 | }
90 | }
91 |
92 | //尾页
93 | Page.prototype.lastPage = function () {
94 | if (this.totalPage - this.nowPage >= this.centPage && this.totalPage > this.setting.defaultPage) {
95 | this.html += "" + this.setting.lastPageText + "";
96 | }
97 | }
98 |
99 | //总共页数
100 | Page.prototype.totalPageText = function () {
101 | this.html += "共" + this.totalCount + "条记录 每页" + this.pageSize + "条 第" + this.nowPage + "页/共" + this.totalPage + "页";
102 | }
103 |
104 | //点击分页执行的函数
105 | Page.prototype.callback = function () {
106 | var This = this;
107 |
108 | //给每个a绑定事件
109 | this.parent.find("a").bind("click", function () {
110 | window.scrollTo(0, 0)
111 | // Layer.loading({width:300,height:110,srcType:3,title:"正在加载中,请稍后..."});
112 | This.parent.html(""); //清空
113 | var nowPage = $(this).attr("href").substring(1);
114 | //写入分页
115 | new Page({
116 | parent: This.parent,
117 | nowPage: nowPage,
118 | totalPage: This.totalPage,
119 | pageSize: This.pageSize,
120 | totalCount: This.totalCount,
121 | setting: This.setting,
122 | callback: This.call
123 | });
124 |
125 | This.call(nowPage, This.totalPage); //传参
126 |
127 | return false;
128 | });
129 |
130 | }
--------------------------------------------------------------------------------
/app/public/lib/popup/popup.css:
--------------------------------------------------------------------------------
1 | /*layer-mobile-css*/
2 | @keyframes popScaleShow{from{opacity: 0;}to{opacity: 1;}}
3 | @-webkit-keyframes popScaleShow{from{opacity: 0;}to{opacity: 1;}}
4 | @keyframes popScale{from{opacity: 0;transform:scale(0.5,0.5);}to{opacity: 1;transform:scale(1,1);}}
5 | @-webkit-keyframes popScale{from{opacity: 0;-webkit-transform:scale(0.5,0.5);}to{opacity: 1;-webkit-transform:scale(1,1);}}
6 | iframe { display: block;}
7 | .popup{width:100%;box-sizing:border-box;height:100%;position: fixed;left:0;top:0;z-index:100000;animation:popScaleShow 300ms linear;-webkit-animation:popScaleShow 300ms linear;}
8 | .popup .mask{width:100%;height:100%;position: absolute;left:0;top:0;background: rgba(0,0,0,.6);z-index:-1;}
9 | .popup .popup_main{position:fixed;left:50%;top:50%;min-width:350px;min-height:100px;overflow:hidden;max-width:90%!important;
10 | transform:translateX(-50%);-webkit-transform:translateX(-50%);background:transparent;
11 | }
12 | .maminContent{width:100%;height:100%;background: #fff;overflow: hidden;animation:popScale 300ms;-webkit-animation:popScale 300ms;}
13 | .popup .maskMain{background:rgba(0,0,0,.6);color:#fff;}
14 | .popup .header_poup{height:30px;padding-left:46px;margin-top:10px;line-height:50px;position: relative;font-size:16px;}
15 | .popup .header_poup span{display:block;width:15px;height:15px;cursor:pointer;background: url('/public/img/PopLayer-close.png') no-repeat top center;background-size:80% 80%;position: absolute;right:20px;top:15px;}
16 | .popup .header_poup i{width:25px;height:25px;display:inline-block;background: url('/public/img/icon-1.png') no-repeat center center;position: absolute;
17 | left:15px;top:10px;
18 | }
19 | .popup .header_poup i.icon-1{background: url('/public/img/icon-1.png') no-repeat center center;}
20 | .popup .header_poup i.icon-2{background: url('/public/img/icon-2.png') no-repeat center center;}
21 | .popup .header_poup i.icon-3{background: url('/public/img/icon-3.png') no-repeat center center;}
22 | .popup .content{line-height:25px;padding:30px 20px 10px 50px;color:#888;}
23 | .popup .footer{height:60px;width:100%;line-height: 40px;position: relative;margin-top:20px;padding-left:50px;}
24 | .popup .footer span{font-size:14px;height:35px;line-height:35px;cursor:pointer;display:inline-block;text-align:center;color:#4a4a4a;}
25 | .popup .footer .yes{padding:0px 25px;background: #ff4200;color:#fff;border-radius:5px;}
26 | .popup .footer .yes:hover,
27 | .popup .footer .yes5:hover{background:#ef6536;}
28 | .popup .footer .yes5{}
29 | .popup .footer .yesok{background:#ff4200;}
30 | .popup .footer .no{padding:0 15px;border:solid 1px #999;border-radius: 5px;margin-left:15px;}
31 | .popup .footer .no:hover{background:#e8e3e3;}
32 | .popup-hide .popup_main{min-width:100px;}
33 | .popup-hide .popup_main .content{line-height:50px;}
34 | .popup-hide .content{text-align:center;}
35 | .popup-loading{cursor: wait;}
36 | .popup-loading .popup_main{min-width:150px;height:60px;padding:0 15px 0 5px;}
37 | .popup-loading .popup_main .content{line-height:60px;}
38 | .popup-loading .popup_main .content:before{content: "";display: block;width:40px;height:60px;background: url('/public/img/loading.gif') no-repeat center center;float:left;
39 | background-size: 60%;margin-top:-15px;
40 | }
41 | .popup-loading .content{text-align:center;}
42 | .popup .html{line-height:25px;}
43 | .popup-iframe .popup_main{width:80%;height:80%;left:10%;top:10%;}
44 | .popup-iframe .popup_main .contentios{overflow-scrolling:touch;-webkit-overflow-scrolling:touch;overflow-y: scroll; }
45 | .popup .yesHtml{text-align:center;cursor:pointer;color:#f48e08;padding-top:15px;border-top:solid 1px #dcdcdc;}
46 | .popup .yesHtml:hover{color:#b76b07;}
47 | .popup .popup_main .content-no{padding-top:12px;padding-bottom:12px;line-height:25px;}
48 | .popup .popup_main .miss_popup{border-radius: 5px!important;color:#fff;}
49 | .popup .popup_main .miss_popup .content-no{padding-left:20px;color:#fff;}
--------------------------------------------------------------------------------
/app/router.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = app => {
4 | require('./router/home')(app);
5 | require('./router/api')(app);
6 | require('./router/web/web')(app);
7 | require('./router/web/api')(app);
8 | require('./router/wx/web')(app);
9 | require('./router/wx/api')(app);
10 | };
11 |
--------------------------------------------------------------------------------
/app/router/api.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = app => {
4 | const apiV1Router = app.router.namespace('/api/v1/');
5 | const { controller, middleware } = app;
6 | const {
7 | user,
8 | remove,
9 | system,
10 | errors,
11 | emails,
12 | } = controller.api;
13 |
14 | // 校验用户是否登录中间件
15 | const tokenRequired = middleware.tokenRequired();
16 |
17 | // -----------------用户相关------------------
18 | // 用户登录
19 | apiV1Router.post('user/login', user.login);
20 | // 用户注册
21 | apiV1Router.post('user/register', user.register);
22 | // 退出登录
23 | apiV1Router.get('user/logout', tokenRequired, user.logout);
24 | // 获得用户列表
25 | apiV1Router.post('user/getUserList', tokenRequired, user.getUserList);
26 | // 冻结解冻用户
27 | apiV1Router.post('user/setIsUse', tokenRequired, user.setIsUse);
28 | // 删除用户
29 | apiV1Router.post('user/delete', tokenRequired, user.delete);
30 |
31 | // -----------------github 登录------------------
32 | apiV1Router.get('github/callback', user.githubLogin);
33 |
34 | // -----------------新浪微博 登录------------------
35 | apiV1Router.get('weibo/callback', user.weiboLogin);
36 |
37 | // -----------------微信微博 登录------------------
38 | apiV1Router.get('wechat/callback', user.wechatLogin);
39 |
40 | // ----------------系统配置相关---------------
41 | // 新增系统
42 | apiV1Router.post('system/add', tokenRequired, system.addNewSystem);
43 | // 修改系统
44 | apiV1Router.post('system/update', tokenRequired, system.updateSystem);
45 | // 根据用户ID获得系统信息
46 | apiV1Router.get('system/getSysForUserId', tokenRequired, system.getSysForUserId);
47 | // 根据系统ID获得单个系统信息
48 | apiV1Router.get('system/getSystemForId', tokenRequired, system.getSystemForId);
49 | // 获得系统列表
50 | apiV1Router.get('system/web/list', tokenRequired, system.getWebSystemList);
51 | // 删除系统中某个用户
52 | apiV1Router.post('system/deleteUser', tokenRequired, system.deleteWebSystemUser);
53 | // 新增系统中某个用户
54 | apiV1Router.post('system/addUser', tokenRequired, system.addWebSystemUser);
55 | // 删除某个系统
56 | apiV1Router.post('system/deleteSystem', tokenRequired, system.deleteSystem);
57 | // 日报邮件操作
58 | apiV1Router.post('system/handleDaliyEmail', tokenRequired, system.handleDaliyEmail);
59 |
60 | // -------------------清空数据-----------------------------
61 | // 清空db1 1日之前无用数据
62 | apiV1Router.post('remove/deleteDb1WebData', tokenRequired, remove.deleteDb1WebData);
63 | // 清空db2 number日之前所有性能数据
64 | apiV1Router.post('remove/deleteDb2WebData', tokenRequired, remove.deleteDb2WebData);
65 |
66 | // -------------------系统错误信息-----------------------------
67 | apiV1Router.get('errors/getSysDbErrorList', tokenRequired, errors.getSysDbErrorList);
68 |
69 | // -------------------邮件信息-----------------------------
70 | apiV1Router.get('emails/list', tokenRequired, emails.getList);
71 |
72 | apiV1Router.post('emails/add', tokenRequired, emails.addEmail);
73 |
74 | apiV1Router.post('emails/delete', tokenRequired, emails.deleteEmail);
75 |
76 | };
77 |
--------------------------------------------------------------------------------
/app/router/home.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = app => {
4 | const { router, controller } = app;
5 | const { home } = controller.web;
6 |
7 | // 应用列表
8 | router.get('/', home.systemlist);
9 |
10 | // 新增系统选择系统类型
11 | router.get('/selectype', home.selectype);
12 |
13 | // 用户登录
14 | router.get('/login', home.login);
15 |
16 | // 系统列表
17 | router.get('/systems', home.systems);
18 |
19 | // 用户管理
20 | router.get('/users', home.users);
21 |
22 | // 系统重启错误信息
23 | router.get('/errors', home.errors);
24 |
25 | // 邮件管理
26 | router.get('/emails', home.emails);
27 |
28 | };
29 |
--------------------------------------------------------------------------------
/app/router/web/api.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = app => {
4 | const apiV1Router = app.router.namespace('/api/v1/');
5 | const { controller, middleware } = app;
6 | const {
7 | report,
8 | pvuvip,
9 | pages,
10 | environment,
11 | ajax,
12 | resource,
13 | error,
14 | analysis,
15 | } = controller.api.web;
16 |
17 | // 校验用户是否登录中间件
18 | const tokenRequired = middleware.tokenRequired();
19 |
20 | // 浏览器用户数据上报
21 | apiV1Router.post('report/web', report.webReport);
22 |
23 | // ----------------pv uv ip---------------
24 | // 获得实时统计概况
25 | apiV1Router.get('pvuvip/getPvUvIpSurvey', tokenRequired, pvuvip.getPvUvIpSurvey);
26 | // 获得某日统计概况
27 | apiV1Router.get('pvuvip/getPvUvIpSurveyOne', tokenRequired, pvuvip.getPvUvIpSurveyOne);
28 | // 实时获取pv uv ip信息 (多条数据)
29 | apiV1Router.post('pvuvip/getPvUvIpList', tokenRequired, pvuvip.getPvUvIpList);
30 | // 实时获取pv uv ip信息 (单条数据)
31 | apiV1Router.post('pvuvip/getPvUvIpOne', tokenRequired, pvuvip.getPvUvIpOne);
32 | // 获得历史pvuvip统计列表
33 | apiV1Router.get('pvuvip/getHistoryPvUvIplist', tokenRequired, pvuvip.getHistoryPvUvIplist);
34 |
35 | // ----------------- 用户漏斗分析 ----------------
36 | // 用户行为轨迹列表
37 | apiV1Router.get('analysis/getAnalysislist', tokenRequired, analysis.getAnalysislist);
38 | // 单个用户行为轨迹列表
39 | apiV1Router.get('analysis/getAnalysisOneList', tokenRequired, analysis.getAnalysisOneList);
40 | // top list
41 | apiV1Router.get('analysis/getTopDatas', tokenRequired, analysis.getTopDatas);
42 | // 省总访问量排行
43 | apiV1Router.get('analysis/getProvinceAvgCount', tokenRequired, analysis.getProvinceAvgCount);
44 |
45 | // ----------------页面性能分析---------------
46 | // 平均列表
47 | apiV1Router.get('pages/getAveragePageList', tokenRequired, pages.getAveragePageList);
48 | // 单个页面性能列表
49 | apiV1Router.get('pages/getOnePageList', tokenRequired, pages.getOnePageList);
50 | // 单个页面详情
51 | apiV1Router.get('pages/getPageDetails', tokenRequired, pages.getPageDetails);
52 |
53 | // ----------------用户系统位置ip等信息---------------
54 | // 获得用户系统、地址位置、浏览器分类
55 | apiV1Router.get('environment/getDataGroupBy', tokenRequired, environment.getDataGroupBy);
56 | // 根据mark_page获得用户系统信息
57 | apiV1Router.get('environment/getEnvironmentForPage', tokenRequired, environment.getEnvironmentForPage);
58 |
59 | // -------------------ajax-----------------------------
60 | // 根据url获得ajax信息
61 | apiV1Router.get('ajax/getPageAjaxsAvg', tokenRequired, ajax.getPageAjaxsAvg);
62 | // 获得ajax平均性能列表
63 | apiV1Router.get('ajax/getAverageAjaxList', tokenRequired, ajax.getAverageAjaxList);
64 | // 获得单个api的平均性能数据
65 | apiV1Router.get('ajax/getOneAjaxAvg', tokenRequired, ajax.getOneAjaxAvg);
66 | // 获得单个api的性能列表数据
67 | apiV1Router.get('ajax/getOneAjaxList', tokenRequired, ajax.getOneAjaxList);
68 | // 获得单个ajax详情
69 | apiV1Router.get('ajax/getOneAjaxDetail', tokenRequired, ajax.getOneAjaxDetail);
70 |
71 | // -------------------resource资源-----------------------------
72 | // 根据资源类型获得数据
73 | apiV1Router.get('resource/getResourceForType', tokenRequired, resource.getResourceForType);
74 | // 获得resource平均性能列表
75 | apiV1Router.get('resource/getAverageResourceList', tokenRequired, resource.getAverageResourceList);
76 | // 获得单个resource的平均性能数据
77 | apiV1Router.get('resource/getOneResourceAvg', tokenRequired, resource.getOneResourceAvg);
78 | // 获得单个resource的性能列表数据
79 | apiV1Router.get('resource/getOneResourceList', tokenRequired, resource.getOneResourceList);
80 | // 获得单个resource的性能详细信息
81 | apiV1Router.get('resource/getOneResourceDetail', tokenRequired, resource.getOneResourceDetail);
82 |
83 | // -------------------resource资源-----------------------------
84 | // 获得错误分类信息
85 | apiV1Router.get('error/getAverageErrorList', tokenRequired, error.getAverageErrorList);
86 | // 获得单个错误详情列表
87 | apiV1Router.get('error/getOneErrorList', tokenRequired, error.getOneErrorList);
88 | // 单个错误详情
89 | apiV1Router.get('error/getErrorDetail', tokenRequired, error.getErrorDetail);
90 |
91 | };
92 |
--------------------------------------------------------------------------------
/app/router/web/web.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = app => {
4 | const { router, controller } = app;
5 | const { web } = controller.web;
6 |
7 | // ------------------------------ 浏览器 -------------------------------
8 | // 首页pvuvip数据统计
9 | router.get('/web/home', web.webhome);
10 |
11 | // 用户访问轨迹
12 | router.get('/web/analysislist', web.analysislist);
13 |
14 | // 访问轨迹详情
15 | router.get('/web/analysisdetail', web.analysisdetail);
16 |
17 | // 访问页面平均性能
18 | router.get('/web/pagesavg', web.webpagesavg);
19 |
20 | // 单页面访问页面列表性能
21 | router.get('/web/pageslist', web.webpageslist);
22 |
23 | // 单个页面详情
24 | router.get('/web/pagesdetails', web.webpagedetails);
25 |
26 | // 单页面慢性能列表
27 | router.get('/web/slowpageslist', web.webslowpageslist);
28 |
29 | // ajax平均性能列表
30 | router.get('/web/ajaxavg', web.webajaxavg);
31 |
32 | // ajax详情列表
33 | router.get('/web/ajaxdetail', web.webajaxdetail);
34 |
35 | // ajax item详情信息
36 | router.get('/web/ajaxitemdetail', web.webajaxitemdetail);
37 |
38 | // 慢资源列表
39 | router.get('/web/resourcesavg', web.webresourceavg);
40 |
41 | // 慢资源详情
42 | router.get('/web/resourcesdetail', web.webresourcedetail);
43 |
44 | // 单个资源详情信息
45 | router.get('/web/resourcesitemdetail', web.webresourcesitemdetail);
46 |
47 | // 分类错误资源列表
48 | router.get('/web/erroravg', web.weberroravg);
49 |
50 | // 错误详情列表
51 | router.get('/web/errordetail', web.weberrordetail);
52 |
53 | // 错误item详情信息
54 | router.get('/web/erroritemdetail', web.weberroritemdetail);
55 |
56 | // web设置
57 | router.get('/web/setting', web.websetting);
58 |
59 | // web端新增系统
60 | router.get('/web/addsystem', web.webaddsystem);
61 |
62 | // top指标
63 | router.get('/web/top', web.webtop);
64 |
65 | // 城市热力图
66 | router.get('/web/diagram', web.webdiagram);
67 |
68 | // 告警
69 | router.get('/web/alarm', web.webalarm);
70 |
71 | };
72 |
--------------------------------------------------------------------------------
/app/router/wx/api.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = app => {
4 | const apiV1Router = app.router.namespace('/api/v1/wx/');
5 | const { controller, middleware } = app;
6 | const {
7 | report,
8 | pvuvip,
9 | pages,
10 | ajax,
11 | error,
12 | analysis,
13 | } = controller.api.wx;
14 |
15 | // 校验用户是否登录中间件
16 | const tokenRequired = middleware.tokenRequired();
17 |
18 | // 微信小程序数据上报
19 | apiV1Router.post('report/wx', report.wxReport);
20 |
21 | // ----------------pv uv ip---------------
22 | // 获得实时统计概况
23 | apiV1Router.get('pvuvip/getPvUvIpSurvey', tokenRequired, pvuvip.getPvUvIpSurvey);
24 | // 获得某日统计概况
25 | apiV1Router.get('pvuvip/getPvUvIpSurveyOne', tokenRequired, pvuvip.getPvUvIpSurveyOne);
26 | // 实时获取pv uv ip信息 (多条数据)
27 | apiV1Router.post('pvuvip/getPvUvIpList', tokenRequired, pvuvip.getPvUvIpList);
28 | // 实时获取pv uv ip信息 (单条数据)
29 | apiV1Router.post('pvuvip/getPvUvIpOne', tokenRequired, pvuvip.getPvUvIpOne);
30 | // 获得历史pvuvip统计列表
31 | apiV1Router.get('pvuvip/getHistoryPvUvIplist', tokenRequired, pvuvip.getHistoryPvUvIplist);
32 |
33 | // ----------------- 用户漏斗分析 ----------------
34 | // 用户行为轨迹列表
35 | apiV1Router.get('analysis/getAnalysislist', tokenRequired, analysis.getAnalysislist);
36 | // 单个用户行为轨迹列表
37 | apiV1Router.get('analysis/getAnalysisOneList', tokenRequired, analysis.getAnalysisOneList);
38 | // top list
39 | apiV1Router.get('analysis/getTopDatas', tokenRequired, analysis.getTopDatas);
40 | // 省总访问量排行
41 | apiV1Router.get('analysis/getProvinceAvgCount', tokenRequired, analysis.getProvinceAvgCount);
42 |
43 | // ----------------页面性能分析---------------
44 | // 平均列表
45 | apiV1Router.get('pages/getAveragePageList', tokenRequired, pages.getAveragePageList);
46 | // 单个页面性能列表
47 | apiV1Router.get('pages/getOnePageList', tokenRequired, pages.getOnePageList);
48 | // 单个页面详情
49 | apiV1Router.get('pages/getPageDetails', tokenRequired, pages.getPageDetails);
50 | // 获得用户系统、地址位置、浏览器分类
51 | apiV1Router.get('pages/getDataGroupBy', tokenRequired, pages.getDataGroupBy);
52 | // 根据markpage获得页面详情信息
53 | apiV1Router.get('pages/getPageForMarkpage', tokenRequired, pages.getPageForMarkpage);
54 |
55 | // // -------------------ajax-----------------------------
56 | // 根据url获得ajax信息
57 | apiV1Router.get('ajax/getPageAjaxsAvg', tokenRequired, ajax.getPageAjaxsAvg);
58 | // 获得ajax平均性能列表
59 | apiV1Router.get('ajax/getAverageAjaxList', tokenRequired, ajax.getAverageAjaxList);
60 | // 获得单个api的平均性能数据
61 | apiV1Router.get('ajax/getOneAjaxAvg', tokenRequired, ajax.getOneAjaxAvg);
62 | // 获得单个api的性能列表数据
63 | apiV1Router.get('ajax/getOneAjaxList', tokenRequired, ajax.getOneAjaxList);
64 | // 获得单个ajax详情
65 | apiV1Router.get('ajax/getOneAjaxDetail', tokenRequired, ajax.getOneAjaxDetail);
66 |
67 | // -------------------error资源-----------------------------
68 | // 获得错误分类信息
69 | apiV1Router.get('error/getAverageErrorList', tokenRequired, error.getAverageErrorList);
70 | // 获得单个错误详情列表
71 | apiV1Router.get('error/getOneErrorList', tokenRequired, error.getOneErrorList);
72 | // 单个错误详情
73 | apiV1Router.get('error/getErrorDetail', tokenRequired, error.getErrorDetail);
74 |
75 | };
76 |
--------------------------------------------------------------------------------
/app/router/wx/web.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = app => {
4 | const { router, controller } = app;
5 | const { wx } = controller.web;
6 |
7 | // ------------------------------ 微信小程序 -------------------------------
8 |
9 | // web端新增系统
10 | router.get('/wx/addsystem', wx.wxaddsystem);
11 |
12 | // 用户访问轨迹
13 | router.get('/wx/analysislist', wx.analysislist);
14 |
15 | // 访问轨迹详情
16 | router.get('/wx/analysisdetail', wx.analysisdetail);
17 |
18 | // 小程序首页
19 | router.get('/wx/home', wx.wxhome);
20 |
21 | // 小程序设置页面
22 | router.get('/wx/setting', wx.wxsetting);
23 |
24 | // 访问页面平均性能
25 | router.get('/wx/pagesavg', wx.wxpagesavg);
26 |
27 | // 单页面访问页面列表性能
28 | router.get('/wx/pageslist', wx.wxpageslist);
29 |
30 | // 单个页面详情
31 | router.get('/wx/pagesdetails', wx.wxpagedetails);
32 |
33 | // ajax平均性能列表
34 | router.get('/wx/ajaxavg', wx.wxajaxavg);
35 |
36 | // ajax详情列表
37 | router.get('/wx/ajaxdetail', wx.wxajaxdetail);
38 |
39 | // ajax item详情信息
40 | router.get('/wx/ajaxitemdetail', wx.wxajaxitemdetail);
41 |
42 | // 分类错误资源列表
43 | router.get('/wx/erroravg', wx.wxerroravg);
44 |
45 | // 错误详情列表
46 | router.get('/wx/errordetail', wx.wxerrordetail);
47 |
48 | // 错误item详情信息
49 | router.get('/wx/erroritemdetail', wx.wxerroritemdetail);
50 |
51 | // top指标
52 | router.get('/wx/top', wx.wxtop);
53 |
54 | // 城市热力图
55 | router.get('/wx/diagram', wx.wxdiagram);
56 | };
57 |
--------------------------------------------------------------------------------
/app/schedule/delete_report.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // 处理数据定时任务
4 | module.exports = app => {
5 | return {
6 | schedule: {
7 | cron: app.config.pvuvip_task_day_time,
8 | type: 'worker',
9 | disable: app.config.report_data_type !== 'mongodb',
10 | },
11 | // 每天执行一次,定时删除上报的原始数据
12 | async task(ctx) {
13 | if (app.config.is_web_task_run || app.config.is_wx_task_run) {
14 | // 保证集群servers task不冲突
15 | const preminute = await app.redis.get('delete_task_day_time') || '';
16 | const value = app.config.cluster.listen.ip + ':' + app.config.cluster.listen.port;
17 | if (preminute && preminute !== value) return;
18 | if (!preminute) {
19 | await app.redis.set('delete_task_day_time', value, 'EX', 20000);
20 | const preminutetwo = await app.redis.get('delete_task_day_time');
21 | if (preminutetwo !== value) return;
22 | }
23 | if (app.config.is_web_task_run) await ctx.service.remove.deleteDb1WebData(1);
24 | if (app.config.is_wx_task_run) await ctx.service.remove.deleteDb1WebData(2);
25 | }
26 | },
27 | };
28 | };
29 |
--------------------------------------------------------------------------------
/app/schedule/ip_task.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // 处理数据定时任务
4 | module.exports = app => {
5 | return {
6 | schedule: {
7 | cron: app.config.ip_task_time,
8 | type: 'worker',
9 | disable: !(app.config.is_web_task_run || app.config.is_wx_task_run),
10 | },
11 | // 定时处理ip城市地理位置信息
12 | async task(ctx) {
13 | // 保证集群servers task不冲突
14 | const preminute = await app.redis.get('ip_task_time') || '';
15 | const value = app.config.cluster.listen.ip + ':' + app.config.cluster.listen.port;
16 | if (preminute && preminute !== value) return;
17 | if (!preminute) {
18 | await app.redis.set('ip_task_time', value, 'EX', 20000);
19 | const preminutetwo = await app.redis.get('ip_task_time');
20 | if (preminutetwo !== value) return;
21 | }
22 | if (app.config.is_web_task_run) await ctx.service.web.ipTask.saveWebGetIpDatas();
23 | if (app.config.is_wx_task_run) await ctx.service.wx.ipTask.saveWxGetIpDatas();
24 | },
25 | };
26 | };
27 |
--------------------------------------------------------------------------------
/app/schedule/pvuvip_pre_day.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // 执行pvuvip定时任务的时间间隔
4 | module.exports = app => {
5 | return {
6 | schedule: {
7 | cron: app.config.pvuvip_task_day_time,
8 | type: 'worker',
9 | disable: !(app.config.is_web_task_run || app.config.is_wx_task_run),
10 | },
11 | // 定时处pv,uv,ip统计信息 每天执行一次
12 | async task(ctx) {
13 | // 保证集群servers task不冲突
14 | const preminute = await app.redis.get('pvuvip_task_day_time') || '';
15 | const value = app.config.cluster.listen.ip + ':' + app.config.cluster.listen.port;
16 | if (preminute && preminute !== value) return;
17 | if (!preminute) {
18 | await app.redis.set('pvuvip_task_day_time', value, 'EX', 200000);
19 | const preminutetwo = await app.redis.get('pvuvip_task_day_time');
20 | if (preminutetwo !== value) return;
21 | }
22 | if (app.config.is_web_task_run) await ctx.service.web.pvuvipTask.getWebPvUvIpByDay();
23 | if (app.config.is_wx_task_run) await ctx.service.wx.pvuvipTask.getWxPvUvIpByDay();
24 | },
25 | };
26 | };
27 |
--------------------------------------------------------------------------------
/app/schedule/pvuvip_pre_minute.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // 执行pvuvip定时任务的时间间隔 每天定时执行一次
4 | module.exports = app => {
5 | return {
6 | schedule: {
7 | cron: app.config.pvuvip_task_minute_time,
8 | type: 'worker',
9 | disable: !(app.config.is_web_task_run || app.config.is_wx_task_run),
10 | },
11 | // 定时处pv,uv,ip统计信息 每分钟执行一次
12 | async task(ctx) {
13 | // 保证集群servers task不冲突
14 | const preminute = await app.redis.get('pvuvip_task_minute_time') || '';
15 | const value = app.config.cluster.listen.ip + ':' + app.config.cluster.listen.port;
16 | if (preminute && preminute !== value) return;
17 | if (!preminute) {
18 | await app.redis.set('pvuvip_task_minute_time', value, 'EX', 20000);
19 | const preminutetwo = await app.redis.get('pvuvip_task_minute_time');
20 | if (preminutetwo !== value) return;
21 | }
22 | if (app.config.is_web_task_run) await ctx.service.web.pvuvipTask.getWebPvUvIpByMinute();
23 | if (app.config.is_wx_task_run) await ctx.service.wx.pvuvipTask.getWxPvUvIpByMinute();
24 | },
25 | };
26 | };
27 |
--------------------------------------------------------------------------------
/app/schedule/report_task_mongodb.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // 处理数据定时任务
4 | module.exports = app => {
5 | return {
6 | schedule: {
7 | cron: app.config.report_task_time,
8 | type: 'worker',
9 | disable: app.config.report_data_type !== 'mongodb',
10 | },
11 | // 定时处理上报的数据 db1同步到db3数据
12 | async task(ctx) {
13 | if (app.config.is_web_task_run || app.config.is_wx_task_run) {
14 | // 保证集群servers task不冲突
15 | const preminute = await app.redis.get('report_task_for_mongodb') || '';
16 | const value = app.config.cluster.listen.ip + ':' + app.config.cluster.listen.port;
17 | if (preminute && preminute !== value) return;
18 | if (!preminute) {
19 | await app.redis.set('report_task_for_mongodb', value, 'EX', 20000);
20 | const preminutetwo = await app.redis.get('report_task_for_mongodb');
21 | if (preminutetwo !== value) return;
22 | }
23 | // 查询db3是否正常,不正常则重启
24 | try {
25 | const result = await ctx.model.System.count({}).exec();
26 | app.logger.info(`-----------db3--查询db3数据库是否可用----${result}------`);
27 |
28 | if (app.config.is_web_task_run) ctx.service.web.reportTask.saveWebReportDatasForMongodb();
29 | if (app.config.is_wx_task_run) ctx.service.wx.reportTask.saveWxReportDatasForMongodb();
30 | } catch (err) {
31 | app.restartMongodbs('db3', ctx, err);
32 | }
33 | }
34 | },
35 | };
36 | };
37 |
--------------------------------------------------------------------------------
/app/schedule/report_task_redis.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // 处理数据定时任务
4 | module.exports = app => {
5 | return {
6 | schedule: {
7 | cron: app.config.redis_consumption.task_time,
8 | type: 'worker',
9 | disable: !(app.config.report_data_type === 'redis'),
10 | },
11 | // 定时处理上报的数据 redis同步到db3数据
12 | async task(ctx) {
13 | if (app.config.is_web_task_run || app.config.is_wx_task_run) {
14 | // 查询db3是否正常,不正常则重启
15 | try {
16 | const result = await ctx.model.System.count({}).exec();
17 | app.logger.info(`-----------db3--查询db3数据库是否可用----${result}------`);
18 |
19 | if (app.config.is_web_task_run) ctx.service.web.reportTask.saveWebReportDatasForRedis();
20 | if (app.config.is_wx_task_run) ctx.service.wx.reportTask.saveWxReportDatasForRedis();
21 | } catch (err) {
22 | app.restartMongodbs('db3', ctx, err);
23 | }
24 | }
25 | },
26 | };
27 | };
28 |
--------------------------------------------------------------------------------
/app/service/cache.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Service = require('egg').Service;
4 |
5 | class CacheService extends Service {
6 | /*
7 | * @param {*} key
8 | * @returns
9 | * @memberof CacheService
10 | */
11 | async get(key) {
12 | const { redis, logger } = this.app;
13 | const t = Date.now();
14 | let data = await redis.get(key);
15 | if (!data) return;
16 | data = JSON.parse(data);
17 | const duration = (Date.now() - t);
18 | logger.debug('Cache', 'get', key, (duration + 'ms').green);
19 | return data;
20 | }
21 |
22 | /*
23 | * @param {*} key
24 | * @param {*} value
25 | * @param {*} seconds
26 | * @memberof CacheService
27 | */
28 | async setex(key, value, seconds) {
29 | const { redis, logger } = this.app;
30 | const t = Date.now();
31 | value = JSON.stringify(value);
32 | await redis.set(key, value, 'EX', seconds);
33 | const duration = (Date.now() - t);
34 | logger.debug('Cache', 'set', key, (duration + 'ms').green);
35 | }
36 |
37 | /*
38 | * @param {*} key
39 | * @param {*} seconds
40 | * @returns
41 | * @memberof CacheService
42 | */
43 | async incr(key, seconds) {
44 | const { redis, logger } = this.app;
45 | const t = Date.now();
46 | const result = await redis.multi().incr(key).expire(key, seconds)
47 | .exec();
48 | const duration = (Date.now() - t);
49 | logger.debug('Cache', 'set', key, (duration + 'ms').green);
50 | return result[0][1];
51 | }
52 | }
53 |
54 | module.exports = CacheService;
55 |
--------------------------------------------------------------------------------
/app/service/errors.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const Service = require('egg').Service;
3 |
4 | class ErrorsService extends Service {
5 |
6 | /*
7 | * 获得列表信息
8 | *
9 | * @returns
10 | * @memberof ErrorsService
11 | */
12 | async getErrorList() {
13 | const result = await this.app.redis.lrange('db_servers_error_list', 0, -1);
14 | return result ? result : [];
15 | }
16 |
17 | /*
18 | * 保存db和服务重启错误信息
19 | *
20 | * @param {*} type
21 | * @param {*} item
22 | * @param {*} catcherr
23 | * @memberof ErrorsService
24 | */
25 | async saveSysAndDbErrors(type, item, catcherr) {
26 | await this.app.redis.lpush('db_servers_error_list', JSON.stringify({
27 | dbname: type,
28 | shell: item,
29 | catch_error: catcherr,
30 | create_time: new Date(),
31 | }));
32 | }
33 |
34 | }
35 |
36 | module.exports = ErrorsService;
37 |
--------------------------------------------------------------------------------
/app/service/ldap.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const ldap = require('ldapjs');
4 | const Service = require('egg').Service;
5 |
6 | class LdapService extends Service {
7 | constructor(ctx) {
8 | super(ctx);
9 | const { server, ou, dc } = ctx.app.config.ldap;
10 | this.config = { ou, dc };
11 | this.client = ldap.createClient({
12 | url: server,
13 | });
14 | }
15 |
16 | /*
17 | *
18 | *
19 | * @param {*} userName
20 | * @returns
21 | * @memberof LdapService
22 | */
23 | search(userName) {
24 | const { ou, dc } = this.config;
25 | return new Promise((resolve, reject) => {
26 | const str = `cn=${userName}, ou=${ou}, dc=${dc}, dc=com`;
27 | this.client.search(str, {}, (err, res) => {
28 | if (err) {
29 | this.ctx.logger.error(`ldap search error:${err.stack}`);
30 | reject(err);
31 | }
32 | res.on('searchEntry', entry => {
33 | resolve(entry.object);
34 | });
35 | res.on('error', err => {
36 | this.ctx.logger.error(`ldap search searchEntry error:${err.stack}`);
37 | reject(err);
38 | });
39 | res.on('end', err => {
40 | if (err) reject(err);
41 | });
42 | });
43 | });
44 | }
45 | }
46 |
47 | module.exports = LdapService;
48 |
--------------------------------------------------------------------------------
/app/service/remove.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const parser = require('cron-parser');
3 | const Service = require('egg').Service;
4 |
5 | class RemoveService extends Service {
6 |
7 | /*
8 | * 定时删除原始上报数据 一天删一次
9 | *
10 | * @param {string} [type='web']
11 | * @returns
12 | * @memberof RemoveService
13 | */
14 | async deleteDb1WebData(type = 'web') {
15 | try {
16 | const interval = parser.parseExpression(this.app.config.pvuvip_task_day_time);
17 | interval.prev();
18 | interval.prev();
19 | const endTime = new Date(interval.prev().toString());
20 |
21 | const query = { create_time: { $lt: endTime } };
22 | let result = '';
23 | if (type === 'web') {
24 | result = await this.ctx.model.Web.WebReport.remove(query).exec();
25 | } else if (type === 'wx') {
26 | result = await this.ctx.model.Wx.WxReport.remove(query).exec();
27 | }
28 | return result;
29 | } catch (err) {
30 | return {};
31 | }
32 | }
33 |
34 | /*
35 | * 清空db2 number日之前所有性能数据
36 | *
37 | * @param {*} appId
38 | * @param {*} number
39 | * @param {string} [type='web']
40 | * @returns
41 | * @memberof RemoveService
42 | */
43 | async deleteDb2WebData(appId, number, type = 'web') {
44 | number = number * 1;
45 | const interval = parser.parseExpression(this.app.config.pvuvip_task_day_time);
46 | const endTime = new Date(new Date(interval.prev().toString()).getTime() - number * 86400000);
47 | const query = { create_time: { $lt: endTime } };
48 | let result = null;
49 |
50 | if (type === 'web') {
51 | // Ajax
52 | const remove1 = Promise.resolve(this.app.models.WebAjaxs(appId).remove(query).exec());
53 | // Pages
54 | const remove2 = Promise.resolve(this.app.models.WebPages(appId).remove(query).exec());
55 | // Environment
56 | const remove3 = Promise.resolve(this.app.models.WebEnvironment(appId).remove(query).exec());
57 | // Errors
58 | const remove4 = Promise.resolve(this.app.models.WebErrors(appId).remove(query).exec());
59 | // Resource
60 | const remove5 = Promise.resolve(this.app.models.WebResource(appId).remove(query).exec());
61 | result = await Promise.all([ remove1, remove2, remove3, remove4, remove5 ]);
62 | } else if (type === 'wx') {
63 | // Ajax
64 | const remove1 = Promise.resolve(this.ctx.model.Wx.WxAjaxs.remove(query).exec());
65 | // Pages
66 | const remove2 = Promise.resolve(this.ctx.model.Wx.WxPages.remove(query).exec());
67 | // Errors
68 | const remove3 = Promise.resolve(this.ctx.model.Wx.WxErrors.remove(query).exec());
69 | result = await Promise.all([ remove1, remove2, remove3 ]);
70 | }
71 | return result;
72 | }
73 | }
74 |
75 | module.exports = RemoveService;
76 |
--------------------------------------------------------------------------------
/app/service/web/environment.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Service = require('egg').Service;
4 |
5 | class EnvironmentService extends Service {
6 |
7 | // 获得页面性能数据平均值
8 | async getDataGroupBy(type, url, appId, beginTime, endTime) {
9 | type = type * 1;
10 |
11 | const queryjson = { $match: { url } };
12 | if (beginTime && endTime) queryjson.$match.create_time = { $gte: new Date(beginTime), $lte: new Date(endTime) };
13 | const group_id = {
14 | url: '$url',
15 | city: `${type === 1 ? '$city' : ''}`,
16 | browser: `${type === 2 ? '$browser' : ''}`,
17 | system: `${type === 3 ? '$system' : ''}`,
18 | };
19 |
20 | const datas = await this.app.models.WebEnvironment(appId).aggregate([
21 | queryjson,
22 | {
23 | $group: {
24 | _id: group_id,
25 | count: { $sum: 1 },
26 | },
27 | },
28 | { $sort: { count: -1 } },
29 | { $limit: 10 },
30 | ]).read('sp')
31 | .exec();
32 |
33 | return datas;
34 | }
35 |
36 | // 根据mark_page获得用户系统信息
37 | async getEnvironmentForPage(appId, markPage) {
38 | return await this.app.models.WebEnvironment(appId).findOne({ mark_page: markPage }).read('sp')
39 | .exec();
40 | }
41 | }
42 |
43 | module.exports = EnvironmentService;
44 |
--------------------------------------------------------------------------------
/app/service/web/pvuvip_task.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const parser = require('cron-parser');
3 | const Service = require('egg').Service;
4 | class PvuvipTaskService extends Service {
5 |
6 | // 获得web端 pvuvip
7 | async getWebPvUvIpByDay() {
8 | const interval = parser.parseExpression(this.app.config.pvuvip_task_day_time);
9 | const endTime = new Date(interval.prev().toString());
10 | const beginTime = new Date(interval.prev().toString());
11 | const query = { create_time: { $gte: beginTime, $lt: endTime } };
12 |
13 | const datas = await this.ctx.model.System.distinct('app_id', { type: 'web' }).read('sp').exec();
14 | this.groupData(datas, 2, query, beginTime, endTime);
15 | }
16 | // 定时执行每分钟的数据
17 | async getWebPvUvIpByMinute() {
18 | const interval = parser.parseExpression(this.app.config.pvuvip_task_minute_time);
19 | const endTime = new Date(interval.prev().toString());
20 | const beginTime = new Date(interval.prev().toString());
21 | const query = { create_time: { $gte: beginTime, $lt: endTime } };
22 |
23 | const datas = await this.ctx.model.System.distinct('app_id', { type: 'web' }).read('sp').exec();
24 | this.groupData(datas, 1, query, endTime);
25 | }
26 | // 对数据进行分组
27 | groupData(datas, type, query, beginTime, endTime) {
28 | if (!datas && !datas.length) return;
29 | datas.forEach(item => {
30 | // pvuvip
31 | this.savePvUvIpData(item, beginTime, type, query);
32 | // top排行
33 | this.ctx.service.web.analysis.saveRealTimeTopTask(item, type, beginTime, endTime);
34 | });
35 | }
36 |
37 | // 获得pvuvip数据
38 | async savePvUvIpData(appId, endTime, type, query) {
39 | try {
40 | const pvpro = Promise.resolve(this.ctx.service.web.pvuvip.pv(appId, query));
41 | const uvpro = Promise.resolve(this.ctx.service.web.pvuvip.uv(appId, query));
42 | const ippro = Promise.resolve(this.ctx.service.web.pvuvip.ip(appId, query));
43 | const ajpro = Promise.resolve(this.ctx.service.web.pvuvip.ajax(appId, query));
44 | const flpro = Promise.resolve(this.ctx.service.web.pvuvip.flow(appId, query));
45 |
46 | let data = [];
47 | if (type === 1) {
48 | data = await Promise.all([ pvpro, uvpro, ippro, ajpro, flpro ]);
49 | } else if (type === 2) {
50 | const user = Promise.resolve(this.ctx.service.web.pvuvip.user(appId, query));
51 | const bounce = Promise.resolve(this.ctx.service.web.pvuvip.bounce(appId, query));
52 | data = await Promise.all([ pvpro, uvpro, ippro, ajpro, flpro, user, bounce ]);
53 | }
54 | const pv = data[0] || 0;
55 | const uv = data[1].length ? data[1][0].count : 0;
56 | const ip = data[2].length ? data[2][0].count : 0;
57 | const ajax = data[3] || 0;
58 | const flow = data[4] || 0;
59 | const user = type === 2 ? (data[5].length ? data[5][0].count : 0) : 0;
60 | const bounce = type === 2 ? data[6] : 0;
61 |
62 | const pvuvip = this.ctx.model.Web.WebPvuvip();
63 | pvuvip.app_id = appId;
64 | pvuvip.pv = pv;
65 | pvuvip.uv = uv;
66 | pvuvip.ip = ip;
67 | pvuvip.ajax = ajax;
68 | pvuvip.flow = flow;
69 | if (type === 2) pvuvip.bounce = bounce ? (bounce / pv * 100).toFixed(2) + '%' : 0;
70 | if (type === 2) pvuvip.depth = pv && user ? parseInt(pv / user) : 0;
71 | pvuvip.create_time = endTime;
72 | pvuvip.type = type;
73 | await pvuvip.save();
74 |
75 | // 触发日报邮件
76 | if (type === 2) {
77 | this.ctx.service.web.sendEmail.getDaliyDatas({
78 | appId,
79 | pv,
80 | uv,
81 | ip,
82 | ajax,
83 | flow,
84 | bounce: bounce ? (bounce / pv * 100).toFixed(2) + '%' : 0,
85 | depth: pv && user ? parseInt(pv / user) : 0,
86 | }, 'pvuvip');
87 | }
88 | // 流量峰值 超过历史top邮件触达
89 | if (type === 1) {
90 | this.ctx.service.emails.highestPvTipsEmail({ appId, pv, uv, ip, ajax, flow });
91 | }
92 | } catch (err) { console.log(err); }
93 | }
94 |
95 | }
96 |
97 | module.exports = PvuvipTaskService;
98 |
--------------------------------------------------------------------------------
/app/service/web/report.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Service = require('egg').Service;
4 |
5 | class ReportService extends Service {
6 |
7 | // 保存用户上报的数据
8 | async saveWebReportData(ctx) {
9 | const query = ctx.request.body;
10 | const ip = ctx.get('X-Real-IP') || ctx.get('X-Forwarded-For') || ctx.ip;
11 |
12 | const system = await this.service.system.getSystemForAppId(query.appId);
13 | if (!system) return {};
14 | if (system.is_use !== 0) return {};
15 |
16 | const report = ctx.model.Web.WebReport();
17 | report.app_id = query.appId;
18 | report.is_first_in = query.isFristIn ? 2 : 1;
19 | report.create_time = query.time;
20 | report.user_agent = ctx.headers['user-agent'];
21 | report.ip = ip;
22 | report.mark_page = this.app.randomString();
23 | report.mark_user = query.markUser;
24 | report.mark_uv = query.markUv;
25 | report.url = query.url || ctx.headers.referer;
26 | report.pre_url = query.preUrl;
27 | report.performance = query.performance;
28 | report.error_list = query.errorList;
29 | report.resource_list = query.resourceList;
30 | report.screenwidth = query.screenwidth;
31 | report.screenheight = query.screenheight;
32 | report.type = query.type || 1;
33 | report.save();
34 | return {};
35 | }
36 | }
37 |
38 | module.exports = ReportService;
39 |
--------------------------------------------------------------------------------
/app/service/wx/pvuvip_task.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const parser = require('cron-parser');
3 | const Service = require('egg').Service;
4 | class PvuvipTaskService extends Service {
5 |
6 | // 获得web端 pvuvip
7 | async getWxPvUvIpByDay() {
8 | const interval = parser.parseExpression(this.app.config.pvuvip_task_day_time);
9 | const endTime = new Date(interval.prev().toString());
10 | const beginTime = new Date(interval.prev().toString());
11 | const query = { create_time: { $gte: beginTime, $lt: endTime } };
12 |
13 | const datas = await this.ctx.model.System.distinct('app_id', { type: 'wx' }).read('sp').exec();
14 | this.groupData(datas, 2, query, beginTime, endTime);
15 | }
16 | // 定时执行每分钟的数据
17 | async getWxPvUvIpByMinute() {
18 | const interval = parser.parseExpression(this.app.config.pvuvip_task_minute_time);
19 | const endTime = new Date(interval.prev().toString());
20 | const beginTime = new Date(interval.prev().toString());
21 | const query = { create_time: { $gte: beginTime, $lt: endTime } };
22 |
23 | const datas = await this.ctx.model.System.distinct('app_id', { type: 'wx' }).read('sp').exec();
24 | this.groupData(datas, 1, query, endTime);
25 | }
26 | // 对数据进行分组
27 | groupData(datas, type, query, beginTime, endTime) {
28 | if (!datas && !datas.length) return;
29 | datas.forEach(item => {
30 | // pvuvip
31 | this.savePvUvIpData(item, beginTime, type, query);
32 | // top排行
33 | this.ctx.service.wx.analysis.saveRealTimeTopTask(item, type, beginTime, endTime);
34 | });
35 | }
36 |
37 | // 获得pvuvip数据
38 | async savePvUvIpData(appId, endTime, type, query) {
39 | try {
40 | const pvpro = Promise.resolve(this.ctx.service.wx.pvuvip.pv(appId, query));
41 | const uvpro = Promise.resolve(this.ctx.service.wx.pvuvip.uv(appId, query));
42 | const ippro = Promise.resolve(this.ctx.service.wx.pvuvip.ip(appId, query));
43 | const ajpro = Promise.resolve(this.ctx.service.wx.pvuvip.ajax(appId, query));
44 | const flpro = Promise.resolve(this.ctx.service.wx.pvuvip.flow(appId, query));
45 |
46 | let data = [];
47 | if (type === 1) {
48 | data = await Promise.all([ pvpro, uvpro, ippro, ajpro, flpro ]);
49 | } else if (type === 2) {
50 | const user = Promise.resolve(this.ctx.service.wx.pvuvip.user(appId, query));
51 | const bounce = Promise.resolve(this.ctx.service.wx.pvuvip.bounce(appId, query));
52 | data = await Promise.all([ pvpro, uvpro, ippro, ajpro, flpro, user, bounce ]);
53 | }
54 |
55 | const pv = data[0] || 0;
56 | const uv = data[1].length ? data[1][0].count : 0;
57 | const ip = data[2].length ? data[2][0].count : 0;
58 | const ajax = data[3] || 0;
59 | const flow = data[4].length ? data[4][0].amount : 0;
60 | const user = type === 2 ? (data[5].length ? data[5][0].count : 0) : 0;
61 | const bounce = type === 2 ? data[6] : 0;
62 |
63 | const pvuvip = this.ctx.model.Wx.WxPvuvip();
64 | pvuvip.app_id = appId;
65 | pvuvip.pv = pv;
66 | pvuvip.uv = uv;
67 | pvuvip.ip = ip;
68 | pvuvip.ajax = ajax;
69 | pvuvip.flow = flow;
70 | if (type === 2) pvuvip.bounce = bounce ? (bounce / pv * 100).toFixed(2) + '%' : 0;
71 | if (type === 2) pvuvip.depth = pv && user ? parseInt(pv / user) : 0;
72 | pvuvip.create_time = endTime;
73 | pvuvip.type = type;
74 | await pvuvip.save();
75 |
76 | // 每日邮件触达
77 | if (type === 2) {
78 | this.ctx.service.wx.sendEmail.getDaliyDatas({
79 | appId,
80 | pv,
81 | uv,
82 | ip,
83 | ajax,
84 | flow,
85 | bounce: bounce ? (bounce / pv * 100).toFixed(2) + '%' : 0,
86 | depth: pv && user ? parseInt(pv / user) : 0,
87 | }, 'pvuvip');
88 | }
89 | // 流量峰值 超过历史top邮件触达
90 | if (type === 1) {
91 | this.ctx.service.emails.highestPvTipsEmail({ appId, pv, uv, ip, ajax, flow });
92 | }
93 | } catch (err) { console.log(err); }
94 | }
95 | }
96 |
97 | module.exports = PvuvipTaskService;
98 |
--------------------------------------------------------------------------------
/app/service/wx/report.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Service = require('egg').Service;
4 |
5 | class ReportService extends Service {
6 |
7 | // 保存用户上报的数据
8 | async saveWxReportData(ctx) {
9 | const query = ctx.request.body;
10 | const ip = ctx.get('X-Real-IP') || ctx.get('X-Forwarded-For') || ctx.ip;
11 |
12 | // 参数校验
13 | if (!query.appId) throw new Error('web端上报数据操作:app_id不能为空');
14 |
15 | const system = await this.service.system.getSystemForAppId(query.appId);
16 | if (!system) return {};
17 | if (system.is_use !== 0) return {};
18 |
19 | const report = ctx.model.Wx.WxReport();
20 | report.app_id = query.appId;
21 | report.create_time = query.time;
22 | report.errs = query.errs;
23 | report.ip = ip;
24 | report.mark_page = this.app.randomString();
25 | report.mark_user = query.markuser;
26 | report.mark_uv = query.markuv;
27 | report.net = query.net;
28 | report.system = query.system;
29 | report.loc = query.loc;
30 | report.userInfo = query.userInfo;
31 | report.pages = query.pages;
32 | report.ajaxs = query.ajaxs;
33 | report.type = query.type || 1;
34 | report.save();
35 | return {};
36 | }
37 | }
38 | module.exports = ReportService;
39 |
--------------------------------------------------------------------------------
/app/util.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const getUserIp = ctx => {
4 | const res = ctx.req.headers['x-forwarded-for'] ||
5 | ctx.req.headers['x-real-ip'] ||
6 | ctx.req.headers.remote_addr ||
7 | ctx.req.headers.client_ip ||
8 | ctx.req.connection.remoteAddress ||
9 | ctx.req.socket.remoteAddress ||
10 | ctx.req.connection.socket.remoteAddress ||
11 | ctx.ip;
12 | return res.match(/[.\d\w]+/g).join('');
13 | };
14 |
15 |
16 | module.exports = {
17 | getUserIp,
18 | };
19 |
--------------------------------------------------------------------------------
/app/view/errors.html:
--------------------------------------------------------------------------------
1 |
11 |
12 |
15 |
16 |
17 |
18 |
19 |
20 | 重启DB |
21 | 脚本链接 |
22 | 导致重启错误信息 |
23 | 重启时间 |
24 |
25 |
26 | {{item.dbname}} |
27 | {{item.shell}} |
28 | {{item.catch_error}} |
29 | {{item.create_time|date('/',true)}} |
30 |
31 |
32 |
33 |
34 |
35 |
暂无数据!
36 |
37 |
--------------------------------------------------------------------------------
/app/view/footer.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/app/view/github.html:
--------------------------------------------------------------------------------
1 |
2 |
30 |
31 |
32 |
33 |
34 |

35 |

36 |

37 |
{{title}}
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/app/view/header.html:
--------------------------------------------------------------------------------
1 |
101 |
131 |
--------------------------------------------------------------------------------
/app/view/layout.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | <%= data.title %>
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | <% include ./header.html %>
30 | <%- body %>
31 | <% include ./footer.html %>
32 |
33 |
35 |
36 |
--------------------------------------------------------------------------------
/app/view/selectype.html:
--------------------------------------------------------------------------------
1 |
67 |
68 |
选择应用类型
69 |
72 |
73 |
74 |
选择系统类
75 |
76 |
77 |
78 |
WEB浏览器
79 |
统计项目的实时PV/UV/IP,每个页面、AJAX、各种资源的性能数据,上报错误信息,让您更全面的掌控自己的应用。
80 |
81 |
82 |
83 |
84 |
微信小程序
85 |
统计项目的实时PV/UV/IP,AJAX、各种资源的性能数据,上报错误信息,让您更全面的掌控自己的应用。
86 |
87 |
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/app/view/web/alarm.html:
--------------------------------------------------------------------------------
1 | <% include ./side.html %>
2 |
3 |
123 |
124 |
应用告警
125 |
126 |
告警列表
127 |
告警规则
128 |
告警条件
129 |
130 |
131 |
142 |
143 | x
144 |
--------------------------------------------------------------------------------
/app/view/web/errordetail.html:
--------------------------------------------------------------------------------
1 | <% include ./side.html %>
2 |
18 |
19 |
20 |
错误详情列表
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | 错误资源 |
29 | 错误信息 |
30 | 错误类型 |
31 | 所属URL |
32 | 错误资源 |
33 | 错误类型 |
34 | 状态码 |
35 | 类型详情 |
36 | 错误行 |
37 | 错误列 |
38 | 请求方式 |
39 | 访问轨迹 |
40 | 错误时间 |
41 | 操作呢 |
42 |
43 |
44 | {{item.resource_url}} |
45 | {{item.msg}} |
46 | {{item.category}} |
47 | {{item.url}} |
48 | {{item.target}} |
49 | {{item.type}} |
50 | {{item.status}} |
51 | {{item.text}} |
52 | {{item.line}} |
53 | {{item.col}} |
54 | {{item.method}} |
55 | 查看轨迹详情 |
56 | {{item.create_time|date('/',true)}} |
57 | 详情 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
67 |
68 |
暂无数据!
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/app/view/web/resourcesitemdetail.html:
--------------------------------------------------------------------------------
1 | <% include ./side.html %>
2 |
51 |
52 |
53 |
慢资源详细信息
54 |
55 |
56 |
57 |
58 | 资源地址:
59 | {{datas.name}}
60 |
61 |
62 | 资源耗时:
63 | {{datas.duration|toFixed}}
64 |
65 |
66 | 请求方式:
67 | {{datas.method}}
68 |
69 |
70 | 速度类型:
71 | {{datas.speed_type == 1?'正常':'慢'}}
72 |
73 |
74 | body大小:
75 | {{datas.decoded_body_size|toSize}}
76 |
77 |
78 | 生成时间:
79 | {{datas.create_time|date('/',true)}}
80 |
81 |
82 | 所属URL:
83 | {{datas.url}}
84 |
85 |
86 | 来源城市:
87 | {{environment.city||'未知'}}
88 |
89 |
90 | 浏览器:
91 | {{environment.browser}}
92 |
93 |
94 | 浏览器版本:
95 | {{environment.borwser_version}}
96 |
97 |
98 | 操作系统:
99 | {{environment.system}}
100 |
101 |
102 | 操作系统版本:
103 | {{environment.system_version}}
104 |
105 |
106 | IP地址:
107 | {{environment.ip}}
108 |
109 |
110 | 完整URL:
111 | {{datas.full_url}}
112 |
113 |
117 |
118 |
119 |
120 |
--------------------------------------------------------------------------------
/app/view/web/side.html:
--------------------------------------------------------------------------------
1 |
57 |
105 |
--------------------------------------------------------------------------------
/app/view/wx/errordetail.html:
--------------------------------------------------------------------------------
1 | <% include ./side.html %>
2 |
18 |
19 |
20 |
错误详情列表
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | 错误资源 |
29 | 错误信息 |
30 | 错误类型 |
31 | 所属PATH |
32 | 状态码 |
33 | 错误行 |
34 | 错误列 |
35 | 请求方式 |
36 | 访问轨迹 |
37 | 错误时间 |
38 | 操作呢 |
39 |
40 |
41 | {{item.name}} |
42 | {{item.msg}} |
43 | {{item.type}} |
44 | {{item.path}} |
45 | {{item.status}} |
46 | {{item.line}} |
47 | {{item.col}} |
48 | {{item.method}} |
49 | 查看轨迹详情 |
50 | {{item.create_time|date('/',true)}} |
51 | 详情 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
61 |
62 |
暂无数据!
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/app/view/wx/pagedetails.html:
--------------------------------------------------------------------------------
1 | <% include ./side.html %>
2 |
51 |
52 |
53 |
PATH 路径详情信息
54 |
55 |
56 |
57 |
58 | PATH路径:
59 | {{datas.path}}
60 |
61 |
62 | 网络类型:
63 | {{datas.net}}
64 |
65 |
66 | IP:
67 | {{datas.ip}}
68 |
69 |
70 | 所属城市:
71 | {{datas.city||'未知'}}
72 |
73 |
74 | 手机品牌:
75 | {{datas.brand}}
76 |
77 |
78 | 手机类型:
79 | {{datas.model}}
80 |
81 |
82 | 微信版本:
83 | {{datas.version}}
84 |
85 |
86 | 微信语言:
87 | {{datas.language}}
88 |
89 |
90 | 手机系统版本:
91 | {{datas.system}}
92 |
93 |
94 | SDK版本:
95 | {{datas.SDKVersion}}
96 |
97 |
98 | 屏幕宽度:
99 | {{datas.screenWidth}}
100 |
101 |
102 | 屏幕高度:
103 | {{datas.screenHeight}}
104 |
105 |
106 | 生成时间:
107 | {{datas.create_time|date('/',true)}}
108 |
109 |
113 |
114 |
115 |
116 |
--------------------------------------------------------------------------------
/app/view/wx/side.html:
--------------------------------------------------------------------------------
1 |
57 |
93 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | environment:
2 | matrix:
3 | - nodejs_version: '8'
4 |
5 | install:
6 | - ps: Install-Product node $env:nodejs_version
7 | - npm i npminstall && node_modules\.bin\npminstall
8 |
9 | test_script:
10 | - node --version
11 | - npm --version
12 | - npm run test
13 |
14 | build: off
15 |
--------------------------------------------------------------------------------
/config/config.prod.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = () => {
4 | const config = exports = {};
5 |
6 | config.debug = false;
7 |
8 | // 用于安全校验和回调域名根路径 开发路径域名(必填)
9 | // 线上环境此处替换为项目根域名 例如:https://blog.seosiwei.com (这里需要填写http|https和斜杠等字符)
10 | config.origin = 'https://www.xxx.com';
11 |
12 | // 百度地图api key
13 | config.BAIDUAK = 'xxxxxxxxxx';
14 |
15 | // github login
16 | config.github = {
17 | client_id: 'xxxxxx',
18 | client_secret: 'xxxxxx',
19 | scope: [ 'user' ],
20 | };
21 |
22 | // ldap
23 | config.ldap = {
24 | server: 'ldap://xxx', // ldap服务器地址
25 | ou: 'xx', // ou
26 | dc: 'xx', // dc, 非com的另外一层的dc,例如 dc=foobar,dc=com, 这里填 foobar
27 | isLdap: false, // 是否采用ldap;
28 | };
29 |
30 | // 新浪微博 login
31 | config.weibo = {
32 | client_id: 'xxxxxx', // 微博的App Key
33 | client_secret: 'xxxxxx', // 微博的App Secret
34 | scope: [ 'all' ],
35 | };
36 |
37 | // wechat login
38 | config.wechat = {
39 | client_id: 'xxxxxx', // 微信的AppId
40 | client_secret: 'xxxxxx', // 微信的App Secret
41 | };
42 |
43 | // redis配置
44 | config.redis = {
45 | client: {
46 | port: 6379, // Redis port
47 | host: 'xx.xx.xx.xx', // Redis host
48 | password: 'xxxxxx',
49 | db: 0,
50 | },
51 | };
52 |
53 | // mongodb 服务
54 | const dbclients = {
55 | db3: {
56 | // 单机部署
57 | url: 'mongodb://127.0.0.1:27017/performance',
58 | // 副本集 读写分离
59 | // url: 'mongodb://127.0.0.1:28100,127.0.0.1:28101,127.0.0.1:28102/performance?replicaSet=rs1',
60 | // 集群分片
61 | // url: 'mongodb://127.0.0.1:30000/performance',
62 | options: {
63 | poolSize: 100,
64 | keepAlive: 10000,
65 | connectTimeoutMS: 10000,
66 | autoReconnect: true,
67 | reconnectTries: 100,
68 | reconnectInterval: 1000,
69 | },
70 | },
71 | };
72 | if (config.report_data_type === 'mongodb') {
73 | dbclients.db1 = {
74 | // url: 'mongodb://127.0.0.1:27017,127.0.0.1:27018/performance?replicaSet=performance',
75 | url: 'mongodb://127.0.0.1:27019/performance',
76 | options: {
77 | poolSize: 100,
78 | keepAlive: 10000,
79 | connectTimeoutMS: 10000,
80 | autoReconnect: true,
81 | reconnectTries: 100,
82 | reconnectInterval: 1000,
83 | },
84 | };
85 | }
86 |
87 | // mongoose配置
88 | config.mongoose = {
89 | clients: dbclients,
90 | };
91 |
92 | config.security = {
93 | domainWhiteList: [ 'https://xxx.xx.com' ],
94 | csrf: {
95 | enable: false,
96 | ignore: '/api/v1/report/**',
97 | },
98 | };
99 |
100 | return config;
101 | };
102 |
--------------------------------------------------------------------------------
/config/plugin.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const path = require('path');
3 |
4 | exports.ejs = {
5 | enable: true,
6 | package: 'egg-view-ejs',
7 | };
8 |
9 | exports.redis = {
10 | enable: true,
11 | package: 'egg-redis',
12 | };
13 |
14 | exports.mongoose = {
15 | enable: true,
16 | package: 'egg-mongoose',
17 | };
18 |
19 | exports.routerPlus = {
20 | enable: true,
21 | package: 'egg-router-plus',
22 | };
23 |
24 | exports.cors = {
25 | enable: true,
26 | package: 'egg-cors',
27 | };
28 |
29 | exports.email = {
30 | enable: false,
31 | path: path.join(__dirname, '../lib/plugin/egg-email'),
32 | };
33 |
34 | exports.kafka = {
35 | enable: false,
36 | path: path.join(__dirname, '../lib/plugin/egg-kafka'),
37 | };
38 |
39 | exports.alinode = {
40 | enable: false,
41 | package: 'egg-alinode',
42 | };
43 |
44 |
--------------------------------------------------------------------------------
/demo/01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangweianger/zanePerfor/444fd3cc1aaa77abd6a2924a55955987d74a351e/demo/01.png
--------------------------------------------------------------------------------
/demo/02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangweianger/zanePerfor/444fd3cc1aaa77abd6a2924a55955987d74a351e/demo/02.png
--------------------------------------------------------------------------------
/demo/03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangweianger/zanePerfor/444fd3cc1aaa77abd6a2924a55955987d74a351e/demo/03.png
--------------------------------------------------------------------------------
/demo/04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangweianger/zanePerfor/444fd3cc1aaa77abd6a2924a55955987d74a351e/demo/04.png
--------------------------------------------------------------------------------
/demo/05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangweianger/zanePerfor/444fd3cc1aaa77abd6a2924a55955987d74a351e/demo/05.png
--------------------------------------------------------------------------------
/demo/06.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangweianger/zanePerfor/444fd3cc1aaa77abd6a2924a55955987d74a351e/demo/06.png
--------------------------------------------------------------------------------
/demo/07.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangweianger/zanePerfor/444fd3cc1aaa77abd6a2924a55955987d74a351e/demo/07.png
--------------------------------------------------------------------------------
/demo/08.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangweianger/zanePerfor/444fd3cc1aaa77abd6a2924a55955987d74a351e/demo/08.png
--------------------------------------------------------------------------------
/demo/09.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangweianger/zanePerfor/444fd3cc1aaa77abd6a2924a55955987d74a351e/demo/09.png
--------------------------------------------------------------------------------
/demo/10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangweianger/zanePerfor/444fd3cc1aaa77abd6a2924a55955987d74a351e/demo/10.png
--------------------------------------------------------------------------------
/demo/11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangweianger/zanePerfor/444fd3cc1aaa77abd6a2924a55955987d74a351e/demo/11.png
--------------------------------------------------------------------------------
/demo/12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangweianger/zanePerfor/444fd3cc1aaa77abd6a2924a55955987d74a351e/demo/12.png
--------------------------------------------------------------------------------
/demo/20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangweianger/zanePerfor/444fd3cc1aaa77abd6a2924a55955987d74a351e/demo/20.png
--------------------------------------------------------------------------------
/demo/21.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangweianger/zanePerfor/444fd3cc1aaa77abd6a2924a55955987d74a351e/demo/21.png
--------------------------------------------------------------------------------
/demo/ewm.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangweianger/zanePerfor/444fd3cc1aaa77abd6a2924a55955987d74a351e/demo/ewm.jpg
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "0.1"
2 | services:
3 | redis:
4 | image: redis:3.2
5 | ports:
6 | - "6379:6379"
7 | mongo:
8 | image: mongo:3.4
9 | ports:
10 | - "27017:27017"
11 | # 源 https://raw.githubusercontent.com/wurstmeister/kafka-docker/master/docker-compose.yml
12 | zookeeper:
13 | image: wurstmeister/zookeeper
14 | ports:
15 | - "2181:2181"
16 | kafka:
17 | image: wurstmeister/kafka:2.12-2.0.1
18 | ports:
19 | - "9092:9092"
20 | environment:
21 | # your docker host IP (Note: Do not use localhost or 127.0.0.1 as the host ip if you want to run multiple brokers.)
22 | KAFKA_ADVERTISED_HOST_NAME: ${hostIP}
23 | # KAFKA_ZOOKEEPER_CONNECT: zookeeper:2180
24 | KAFKA_ZOOKEEPER_CONNECT: ${hostIP}:2181
25 |
--------------------------------------------------------------------------------
/docs/configs.md:
--------------------------------------------------------------------------------
1 | # config重要配置说明
2 |
3 | ## 一:定时任务启动配置
4 | ```js
5 | // 浏览器端是否开启定时任务,如果不开启,所有的web相关的统计将不会执行(pv,uv,ip,top,省流量统计等)
6 | config.is_web_task_run = true;
7 |
8 | // 小程序端是否开启定时任务,如果不开启,所有的web相关的统计将不会执行(pv,uv,ip,top,省流量统计等)
9 | config.is_wx_task_run = true;
10 | ```
11 |
12 | > 备注:此配置项可以把整个项目拆分成2个项目,针对于web的项目和针对于小程序的项目,可以按需配置
13 |
14 | ## 二:使用redis消息队列还是使用mongodb储存上报原始数据
15 |
16 | ```js
17 | // 此配置有两个值:redus || mongodb
18 | // 如果使用redis则上报的原始数据使用redis消息队列存储
19 | // 如果使用mongodb则上报的原始数据使用mongodb数据库储存
20 | config.report_data_type = 'redis';
21 | 备注:此配置项对上报的方式有很大的影响,推荐使用 redis 消息队列(配置更简单,性能更强)
22 |
23 | // 如果 config.report_data_type = 'redis'; 则需要配置以下参数
24 | config.redis_consumption = {
25 | // 消费者消费生产数据的定时任务
26 | task_time: '*/20 * * * * *',
27 |
28 | // 每次定时任务消费线程数(web端)
29 | thread_web: 1000, // 此配置每次回消费 60/20 * 1000 = 3000 条数据
30 |
31 | // 每次定时任务消费线程数(wx端) (如果值为0则不执行消费任务)
32 | thread_wx: 0,
33 |
34 | // 消息队列池限制数, 0:不限制 number: 限制条数 高并发时服务优雅降级方案
35 | total_limit_web: 100000,
36 | total_limit_wx: 100000,
37 | };
38 | ```
39 |
40 | > 备注:(total_limit_web|total_limit_wx)只有消息队列时可用,若不想限制填为0即可
41 |
42 | ## 三:定时任务时间说明
43 |
44 | ```js
45 | // 此配置于 config.report_data_type = 'mongodb' 相关联,表示每分钟db3从db1同步数据的时间间隔,默认1s
46 | config.report_task_time = '0 */1 * * * *';
47 |
48 | // 每分钟定时统计任务 (包括:pv,uv,ip,top排行)
49 | config.pvuvip_task_minute_time = '0 */1 * * * *';
50 |
51 | // 每天定时统计任务 (包括:pv,uv,ip,top排行,省份流量统计排行)
52 | config.pvuvip_task_day_time = '0 0 0 */1 * *';
53 |
54 | // ip解析为城市信息定时任务时间间隔
55 | config.ip_task_time = '0 */1 * * * *';
56 | ```
57 |
58 | ## 四:IP定时任务解析器使用redis还是mongodb
59 |
60 | ```js
61 | // 此配置有两个值 redus || mongodb,标识ip解析为城市地址是查询的数据方式
62 | config.ip_redis_or_mongodb = 'redis'
63 | ```
64 |
65 | > 备注:ip解析为城市在使用数据库查询之前会优先去本地缓存文件查询一遍,如果没有才会去数据库匹配
66 |
67 | # 五:IP定时任务解析器使用本地缓存设置
68 |
69 | ```js
70 | // 缓存IP地址对应城市信息的本地json文件,优先级高于 config.ip_redis_or_mongodb 配置
71 | config.ip_city_cache_file = {
72 | web: 'web_ip_city_cache_file.txt',
73 | wx: 'wx_ip_city_cache_file.txt',
74 | };
75 | ```
76 |
77 | ## 六:IP定时任务解析器使用redis还是mongodb
78 |
79 | ```js
80 | // 此配置表示 db3同步db1的线程数 只有当 config.report_data_type = 'mongodb' 时才会生效
81 | config.report_thread = 10; // 此配置同步数据相当于10个线程在跑
82 |
83 | // ip地址解析为城市信息线程数 数字越大每次跑的数据越多
84 | config.ip_thread = 10; // 此配置默认每次跑 10 * 60 = 600 条
85 | ```
86 |
87 | ## 七:数据库和servers出现意外时的重启脚本
88 |
89 | ```js
90 | config.shell_restart = {
91 | // mongodb重启shell,如果mongodb服务崩溃了,服务重启脚本(可选填)
92 | mongodb: [ '/data/performance/mongodb-restart.sh' ],
93 |
94 | // node.js服务重启shell,mongodb重启时,数据库连接池有可能会断,这时重启服务(可选填)
95 | servers: [ '/data/performance/servers-restart.sh' ],
96 | };
97 | ```
98 |
99 | > 备注:此配置一般用在单机模式下的保证服务正常方案,集群模式下可不填写
100 | > 程序中有一套检测数据库是否正常机制,如果检测不正常则会重启db,和servers,一直重复直到重启成功,以此保证单机的高可用方案
--------------------------------------------------------------------------------
/docs/github.md:
--------------------------------------------------------------------------------
1 | # github登录授权
2 |
3 | ## 一:登录github
4 |
5 | ## 二:依次进入: Settings -> Developer settings -> New OAuth App -> Register a new OAuth application
6 |
7 | 你会看到如下界面:
8 |
9 | 
10 |
11 | > Homepage URL: 你的根域名,也就是location.origin
12 | > Authorization callback URL: github授权成功之后的回调域名,这里的地址后缀必须是 /api/v1/github/callback
13 |
14 | ## 三:注册成功之后进入OAuth Apps,进入创建好的应用获取Client ID 和 Client Secret。
15 |
16 |
17 | ### 项目中配置授权参数
18 |
19 | - 进入项目的`config/config.default.js` 和 `config/config.prod.js` 配置相应的参数
20 |
21 | ```js
22 | // github login
23 | config.github = {
24 | // github Client ID
25 | client_id: 'xxxxxx',
26 |
27 | // github Client Secret
28 | client_secret: 'xxxxxx',
29 |
30 | // 此参数表示只获取用户信息
31 | scope: [ 'user' ],
32 | };
33 | ```
34 |
35 | END!
--------------------------------------------------------------------------------
/docs/img/github-login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangweianger/zanePerfor/444fd3cc1aaa77abd6a2924a55955987d74a351e/docs/img/github-login.png
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | # zanePerfor是什么?
2 |
3 | ## zanePerfor是一个服务于性能监控的业务平台项目,目前实现了浏览器,微信小程序的相关基础性能统计。
4 |
5 | > 备注:zanePerfor目前还不完善,处于开发初期,我会长期的维护和升级
6 |
7 | > 展望:zanePerfor的目标是解决中小应用的性能统计,支持通用的数据统计和定制化的统计开发,解决高并发下的应用高可用
8 |
9 | > 性能:目前架构理论上来说可支持每日(百万、千万)级PV,未来会持续开发和优化
10 |
11 | ## zanePerfor目前实现了哪些功能?
12 |
13 | ### 浏览器端(WEB)
14 | - 页面级的性能上报(多页面 || 单页面应用程序通用)
15 | - 页面AJAX性能上报
16 | - 页面所有加载资源性能上报(图片,js,css)
17 | - 页面所有错误信息上报(js,css,ajax)
18 | - 微信小程序端
19 | - path路径对应的AJAX性能上报
20 | - 小程序错误信息上报(js,ajax,img)
21 | - 用户设备信息及其网络信息上报
22 | - 后端界面展示功能(web,小程序通用)
23 | - 统计每分钟应用的PV,UV,IP信息,统计每天的PV,UV,IP,跳出率,用户访问平均深度
24 | - 统计实时和每天的应用top最高访问排行,跳出率最高排行
25 | - 统计实时和每天的全国省份流量热力图
26 | - 统计每个用户每次访问的行为轨迹
27 | - 下面用图来展示实现的大致功能
28 | - 实时PV,UV,IP统计
29 |
30 | ### 单页面性能详情列表
31 | 
32 |
33 | ### 单个AJAX性能详情
34 | 
35 |
36 | ### 用户轨迹跟踪
37 | 
38 |
39 | ### 省份实时流量统计
40 | 
41 |
--------------------------------------------------------------------------------
/docs/simple_deployment.md:
--------------------------------------------------------------------------------
1 | # 单机部署入门体验
2 |
3 | ## 一:安装Mongodb数据库
4 |
5 | > 提示:安装Mongodb的教程网上非常之多,我这里也不重复的编写,直接推荐几篇文章
6 |
7 | - 菜鸟教程:http://www.runoob.com/mongodb/mongodb-osx-install.html
8 |
9 | - Mongodb官网:https://docs.mongodb.com/manual/administration/install-enterprise/
10 |
11 | - Mongo中文社区:http://www.mongoing.com/docs/administration/install-community.html
12 |
13 | Mongodb可视化工具推荐Robomongo,非常好用!
14 |
15 | - Robomongo:https://robomongo.org/download
16 |
17 | ## 参考:一份最基础的Mongodb配置
18 | ```js
19 | processManagement:
20 | fork: true
21 | systemLog:
22 | destination: file
23 | path: /usr/local/var/log/mongodb/mongo27017.log
24 | logAppend: true
25 | storage:
26 | dbPath: /Users/MacBook/mongodb/mongodb27017
27 | net:
28 | bindIp: 127.0.0.1
29 | port: 27017
30 | ```
31 |
32 | ### Mongodb各项配置含义参考链接:
33 |
34 | - 官网Config:https://docs.mongodb.com/manual/reference/configuration-options/index.html
35 |
36 | - 会煮咖啡的猫咪:https://www.jianshu.com/p/f9f1454f251f
37 |
38 |
39 | ## 二:安装Redis数据库
40 |
41 | > 提示:安装Redis的教程网上也非常之多,以下链接可参考
42 |
43 | - 菜鸟教程:http://www.runoob.com/redis/redis-install.html
44 |
45 | - Redis中文网:http://www.redis.net.cn/tutorial/3503.html
46 |
47 | Redis可视化工具推荐Redis Desktop Manager,也很好用!
48 |
49 | 官网下载比较困难,对于windows和Mac收费,这里推荐使用我收藏的安装文件安装
50 |
51 | - Mac端链接:链接: https://pan.baidu.com/s/1Z1i22OhfSMh3e4-3E5Q5WA 提取码: 8pd6
52 |
53 | - Windows端链接:链接: https://pan.baidu.com/s/1CbHIXdmPuYuEBfk7rbCjRw 提取码: xkak
54 |
55 | ### 参考:Redis Config参考配置含义,推荐链接
56 |
57 | - 官网Config:https://redis.io/topics/config
58 |
59 | - 菜鸟教程:http://www.runoob.com/redis/redis-conf.html
60 |
61 | ## 三:Node.js服务部署
62 |
63 | ### 一:下载项目文件
64 |
65 | ```js
66 | git clone https://github.com/wangweianger/zanePerfor.git
67 | ```
68 |
69 | ### 二:安装依赖
70 |
71 | ```js
72 | // 查看node版本 node版本需要 >=8.0.0
73 | node -v
74 | // 安装依赖
75 | npm install
76 | ```
77 |
78 | > 如果你的node版本低于8.0.0,请升级
79 |
80 | > 推荐nvm安装node,Linux安装可参考 LINUX系统安装nvm 快速搭建Nodejs开发环境
81 |
82 | ### 三:配置项目Config
83 |
84 | 配置项目端口和Host (本地开发默认即可)
85 |
86 | ```js
87 | config.cluster = {
88 | listen: {
89 | port: 7001,
90 | hostname: '127.0.0.1',
91 | ip: address.ip(),
92 | },
93 | };
94 | ```
95 |
96 | 配置Redis Config(本地开发默认即可)
97 |
98 | ```js
99 | config.redis = {
100 | client: {
101 | port: 6379, // Redis port
102 | host: '127.0.0.1', // Redis host
103 | password: '',
104 | db: 0,
105 | },
106 | };
107 | ```
108 |
109 | 配置Mongodb服务(本地开发默认即可)
110 |
111 | ```js
112 | const dbclients = {
113 | db3: {
114 | url: 'mongodb://127.0.0.1:27019/performance',
115 | options: {
116 | poolSize: 20,
117 | },
118 | },
119 | };
120 | ```
121 | 项目各配置项含义参考说明
122 |
123 | ## 四:启动服务
124 |
125 | ```js
126 | // 本地开发
127 | npm run dev
128 |
129 | // 生产环境
130 | npm run build
131 | ```
132 |
133 | ## 四:登录后台创建应用
134 |
135 | ### 一:浏览器打开启动好的应用链接:http://127.0.0.1:7001
136 | 如果你部署成功会看到如下登录界面
137 |
138 | 注册账号(系统默认admin为管理员,其他账号全是普通用户)
139 | 注册号之后会自动登录系统,点击添加应用,请选择你的应用类型(这里分为WEB 和 微信小程序两种类型应用)
140 |
141 | 填写应用名称和应用域名
142 |
143 | ## 五:下载SDK并进行数据的上报
144 |
145 | 以上步骤完成你会看到如下的部署配置
146 |
147 | SDK使用文档
148 | - WEB端SDK及其使用文档:https://github.com/wangweianger/web-report-sdk
149 | - 小程序端SDK及其使用文档:https://github.com/wangweianger/wx-report-sdk
150 |
151 | ### 多页面部署案例:
152 | ```js
153 | // 头部引入sdk脚本
154 | <\/script src="/js/performance-report-default.min.js"><\/script>
155 | <\script>
156 | Performance({
157 | domain: 'http://127.0.0.1:7001/api/v1/report/web',
158 | add: {
159 | // 建好应用的appId
160 | appId: 'xxxxxxxxxxxxxx'
161 | }
162 | })
163 | <\/script>
164 | ```
165 |
166 | ### 单页面部署案例:
167 |
168 | - 一:项目下新建 performance.js 文件,文件内容如下
169 |
170 | ```js
171 | const Performance = require('common/js/performance-report.min')
172 | try{
173 | Performance({
174 | domain:'https://127.0.0.1:7001/api/v1/report/web',
175 | add:{
176 | // 建好应用的appId
177 | appId: 'xxxxxxxxxxxxxx'
178 | }
179 | })
180 | } catch (e) {}
181 | ```
182 |
183 | - 二:项目main.js入口文件顶部引入SDK插件
184 |
185 | ```js
186 | // main.js 单页面入口文件
187 | // 引入jdk
188 | require('./performance')
189 | // 其他配置项
190 | import Vue from 'vue'
191 | import App from './app'
192 | import VueRouter from 'vue-router'
193 | import routes from './router'
194 | import filter from './filter'
195 | ......
196 | ```
197 |
198 | - 三:webpack配置加入性能SDK
199 |
200 | ```js
201 | // 入口配置
202 | entry: {
203 | performance:path.resolve(__dirname, '../src/performance.js'),
204 | // 其他配置项
205 | ...
206 | },
207 | ```
208 |
209 | ## 六:大功告成,开始第一次的试用吧。
210 | 其他说明:新建应用后,第一次需要给用户分配权限才会显示应用
211 |
--------------------------------------------------------------------------------
/docs/tasks.md:
--------------------------------------------------------------------------------
1 | # 项目定时任务功能说明
2 |
3 | ## 一:消息队列消费者Task (建议上报使用此Task)
4 |
5 | ```js
6 | // 是否执行由 config.report_data_type = 'redis' 配置决定
7 | // 此定时任务执行频次由 config.redis_consumption.task_time 决定
8 | schedule/report_task_redis.js
9 | ```
10 |
11 | > 开启此定时任务,消费者端会定时消费用户上报的原始数据并储存到db中,是非常重要的配置,是后续逻辑处理数据的来源。
12 |
13 | > 配置优势:性能强,使用消息队列,支持秒杀形式的高并发数据上报,并且能实现限流的功能,实现servers集群很方便
14 |
15 | ## 二:db3同步db1数据Task(不建议使用此Task)
16 |
17 | ```js
18 | // 是否执行由 config.report_data_type = 'mongodb' 配置决定
19 | // 此定时任务执行频次由 config.redis_consumption.task_time 决定
20 | schedule/report_task_mongodb.js
21 | ```
22 |
23 | > 开启此定时任务,用户上报的原始数据会先储存到db1,然后再经过定时任务把需要的数据同步到db3
24 |
25 | > 配置劣势:性能比消息队列差很多,配置也稍复杂,没有做限流逻辑,实现了servers集群Task的不重复执行
26 |
27 | ## 三 :每分钟PV,UV,IP统计Task
28 |
29 | ```js
30 | // 此定时任务执行频次由 config.pvuvip_task_minute_time 决定
31 | schedule/pvuvip_pre_minute.js
32 | ```
33 |
34 | > 备注:此Task实现了servers集群模式下的Task不重复执行
35 |
36 |
37 | ## 四:每天凌晨执行一次的Task
38 |
39 | ```js
40 | // 此定时任务执行频次由 config.pvuvip_task_day_time 决定
41 | // 此定时任务主要负责 每日PV,UV,IP,TOP排行,城市流量排行等每日统计功能的Task
42 | schedule/pvuvip_pre_day.js
43 | ```
44 |
45 | > 备注:此Task实现了servers集群模式下的Task不重复执行
46 |
47 | ## 五:定时解析用户IP地理位置Task
48 |
49 | ```js
50 | // 此定时任务执行频次由 config.ip_task_time 决定
51 | // 此定时任务主要是根据用户的IP地址得到城市位置信息,方便后期的省,城市的相关统计
52 | schedule/ip_task.js
53 | ```
54 |
55 | > 备注:此Task实现了servers集群模式下的Task不重复执行
56 |
57 | > 性能说明:此Task会先从本地文件查询,若无再从redis或者mongodb数据库查询
58 |
59 | ## 六:定时删除用户原始上报数据Task
60 |
61 | ```js
62 | // 此定时任务 需要 config.report_data_type = 'mongodb' 时才会执行
63 | // 定时删除用户前2天之前上报的原始数据
64 | schedule/delete_report.js
65 | ```
--------------------------------------------------------------------------------
/docs/vue.md:
--------------------------------------------------------------------------------
1 | ## Vue 项目使用SDK:
2 |
3 | - 进去app目录下public文件夹,解压web-report-vue-min.zip。将里面的文件复制到自己项目下,找到public文件夹或者根目录文件夹下的index.html。添加以下代码:
4 |
5 | ```js
6 |
7 |
15 | ```
16 |
17 | 这里向大家分享一下不同环境下使用的方法:
18 |
19 | - Vue2: 可以在 **.env.build** 等环境配置文件配置环境变量,例如: `VUE_APP_BASE_URL=/abc/index`
20 |
21 | ```js
22 |
23 |
31 | ```
32 |
33 | - Vue3: 可以在 **.env.build** 等环境配置文件配置环境变量,例如: `VITE_BUILD_HOST = 'http://xxxxx:7001'`
34 |
35 | - 然后在Vite.config.ts中新增插件:createHtmlPlugin -> npm install createHtmlPlugin -D。然后在plugins中配置以下代码
36 |
37 | ```js
38 | createHtmlPlugin({
39 | minify: true,
40 | pages: [
41 | {
42 | // entry: "src/main.ts",
43 | filename: "index.html",
44 | template: "index.html",
45 | injectOptions: {
46 | data: {
47 | // title: "index",
48 | injectScript: ``,
49 | },
50 | },
51 | },
52 | ],
53 | }),
54 | ```
55 |
56 | 在src下的utils文件夹新增文件inject.ts
57 |
58 | ```js
59 | window.Performance({
60 | domain: `${window._process_env.VITE_BUILD_HOST}/api/v1/report/web`,
61 | add: {
62 | appId: "ZzdSi3P1XXXX2161087",
63 | },
64 | });
65 | ```
66 |
67 | 这样就可以动态的引入各个环境的域名前缀,部署到各个环境啦。
68 |
69 |
--------------------------------------------------------------------------------
/dump.rdb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangweianger/zanePerfor/444fd3cc1aaa77abd6a2924a55955987d74a351e/dump.rdb
--------------------------------------------------------------------------------
/lib/plugin/egg-email/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2019 Alibaba Group Holding Limited and other contributors.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lib/plugin/egg-email/README.md:
--------------------------------------------------------------------------------
1 | # egg-email
2 |
3 |
--------------------------------------------------------------------------------
/lib/plugin/egg-email/app.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const email = require('./lib/email');
4 |
5 | module.exports = app => {
6 | email(app);
7 | };
8 |
9 |
--------------------------------------------------------------------------------
/lib/plugin/egg-email/config/config.default.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.email = {
4 | };
5 |
--------------------------------------------------------------------------------
/lib/plugin/egg-email/lib/email.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const nodemailer = require('nodemailer');
4 | // const assert = require('assert');
5 |
6 | module.exports = app => {
7 | app.addSingleton('email', createOneClient);
8 | };
9 |
10 | function createOneClient(config, app) {
11 | const isHostProt = config.host && config.port && typeof (config.secure) === 'boolean';
12 | const service = config.service;
13 |
14 | if (!(isHostProt || service) || !config.auth) return {};
15 |
16 | // assert((isHostProt || service) && config.auth, '[egg-email] host and prot or service are require on config');
17 |
18 | app.coreLogger.info('[egg-email] connecting success!');
19 |
20 | const smtpTransport = nodemailer.createTransport(config);
21 |
22 | return smtpTransport;
23 | }
24 |
--------------------------------------------------------------------------------
/lib/plugin/egg-email/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "egg-email",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "app.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "devDependencies": {
12 | "nodemailer": "^5.0.0"
13 | },
14 | "eggPlugin": {
15 | "name": "egg-email"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/lib/plugin/egg-kafka/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2019 Alibaba Group Holding Limited and other contributors.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lib/plugin/egg-kafka/README.md:
--------------------------------------------------------------------------------
1 | # egg-kafka
2 |
3 |
--------------------------------------------------------------------------------
/lib/plugin/egg-kafka/app.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const kafka = require('./lib/kafka');
4 |
5 | module.exports = app => {
6 | if (app.config.report_data_type === 'kafka') kafka(app);
7 | };
8 |
9 |
--------------------------------------------------------------------------------
/lib/plugin/egg-kafka/config/config.default.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.kafka = {
4 | client: { // kafkaClient
5 | kafkaHost: 'localhost:9092',
6 | },
7 | producer: {
8 | web: {
9 | topic: 'zane_perfor_web',
10 | partition: 0, // default 0
11 | attributes: 0, // default: 0
12 | // timestamp: Date.now(),
13 | },
14 | wx: {
15 | topic: 'zane_perfor_wx',
16 | },
17 | },
18 | consumer: {
19 | web: {
20 | topic: 'zane_perfor_web',
21 | offset: 0, // default 0
22 | partition: 0, // default 0
23 | isone: false, // 此参数默认不可更改
24 | },
25 | wx: {
26 | topic: 'zane_perfor_wx',
27 | isone: false,
28 | },
29 | },
30 | };
31 |
--------------------------------------------------------------------------------
/lib/plugin/egg-kafka/lib/kafka.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const assert = require('assert');
4 | const kafka = require('kafka-node');
5 |
6 | module.exports = app => {
7 | app.addSingleton('kafka', createClient);
8 | };
9 |
10 | class Kafka {
11 | constructor(config, app) {
12 | this.config = config || {};
13 | this.app = app;
14 | this.client = null;
15 | this.producer = null;
16 | this.init();
17 | }
18 |
19 | init() {
20 | assert(this.config.kafkaHost, '[egg-kafka] kafkaHost is required on config');
21 | this.client = new kafka.KafkaClient(this.config);
22 | this.producers();
23 | }
24 |
25 | producers() {
26 | const Producer = kafka.Producer;
27 | this.producer = new Producer(this.client);
28 | this.producer.on('ready', () => {
29 | this.app.coreLogger.info('[egg-kafka] the producer is ready.');
30 | });
31 | this.producer.on('error', err => {
32 | this.app.coreLogger.error(`[egg-kafka] have error ${err}`);
33 | });
34 | }
35 |
36 | consumer(type = 'web', fn) {
37 | assert(type, '[egg-kafka] consumers type argument must be required');
38 | const kafkaConfig = this.app.config.kafka;
39 | const consumer = kafkaConfig.consumer[type] || {};
40 | const consumers = Array.isArray(consumer) ? consumer : [ consumer ];
41 | const Consumer = kafka.Consumer;
42 | const _consumer = new Consumer(
43 | this.client,
44 | consumers,
45 | {
46 | autoCommit: true,
47 | }
48 | );
49 | _consumer.on('error', err => {
50 | this.app.coreLogger.error(`[egg-kafka] consumer have error ${err}`);
51 | });
52 | _consumer.on('message', message => {
53 | fn && fn(message);
54 | });
55 | }
56 |
57 | consumerGroup(type = 'web', fn) {
58 | assert(type, '[egg-kafka] consumers type argument must be required');
59 | const kafkaConfig = this.app.config.kafka;
60 | const kafkaHost = kafkaConfig.client.kafkaHost;
61 | const consumerOption = kafkaConfig.consumerGroup[type] || {};
62 | const topic = consumerOption.topic;
63 | consumerOption.kafkaHost = kafkaHost;
64 | const ConsumerGroup = kafka.ConsumerGroup;
65 | const _consumer = new ConsumerGroup(consumerOption, topic);
66 | _consumer.on('error', err => {
67 | this.app.coreLogger.error(`[egg-kafka] consumer have error ${err}`);
68 | });
69 | _consumer.on('message', message => {
70 | fn && fn(message);
71 | });
72 | }
73 |
74 | createTopics(topics) {
75 | assert(Array.isArray(topics), '[egg-kafka] createTopics opction must be Array.');
76 | this.client.createTopics(topics, error => {
77 | if (error) this.app.coreLogger.error(`[egg-kafka] createTopics have error ${error}`);
78 | });
79 | }
80 |
81 | send(type, data) {
82 | assert(type, '[egg-kafka] type is must required.');
83 | if (!data) return;
84 | let producer = this.app.config.kafka.producer[type] || {};
85 | let producers = [];
86 | if (typeof (data) === 'string') {
87 | producer.messages = data;
88 | producers = [ producer ];
89 | } else if (Object.prototype.toString.call(data) === '[object Object]') {
90 | producer = Object.assign({}, producer, data);
91 | producers = [ producer ];
92 | } else if (Object.prototype.toString.call(data) === '[object Array]') {
93 | for (let i = 0; i < data.length; i++) {
94 | data[i] = Object.assign({}, producer, data[i]);
95 | }
96 | producers = data;
97 | }
98 | this.producer.send(producers, (err, data) => {
99 | if (err) assert(err, '[egg-kafka] err. errmsg ${err}');
100 | console.log(data);
101 | });
102 | }
103 | }
104 |
105 | function createClient(config, app) {
106 | const kafka = new Kafka(config, app);
107 | kafka.init();
108 | return kafka;
109 | }
110 |
--------------------------------------------------------------------------------
/lib/plugin/egg-kafka/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "egg-kafka",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "app.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "devDependencies": {
12 | "kafka-node": "^4.0.0"
13 | },
14 | "eggPlugin": {
15 | "name": "egg-kafka"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/mongodb-restart.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 |
4 | #本地
5 | /usr/local/mongodb/bin/mongod -f /usr/local/etc/mongod27017.conf
6 |
7 | /usr/local/redis/src/redis-server /usr/local/redis/redis.conf
8 |
9 | #/usr/src/mongodb/bin/mongod -f /data/mongodb/mongod27017.conf
10 | #/usr/src/mongodb/bin/mongod -f /data/mongodb/mongod27019.conf
11 |
12 |
13 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "zane-performance",
3 | "version": "1.0.0",
4 | "description": "前端性能监控系统",
5 | "private": true,
6 | "dependencies": {
7 | "address": "^1.0.3",
8 | "egg": "^2.2.1",
9 | "egg-alinode": "^2.0.1",
10 | "egg-cors": "^2.1.0",
11 | "egg-mongoose": "^3.1.0",
12 | "egg-redis": "^2.4.0",
13 | "egg-router-plus": "^1.3.0",
14 | "egg-scripts": "^2.10.0",
15 | "egg-socket.io": "^4.1.4",
16 | "ldapjs": "^1.0.2",
17 | "nodemailer": "^6.9.1",
18 | "kafka-node": "^4.0.0",
19 | "md5": "^2.2.1",
20 | "ua-parser-js": "^0.7.18",
21 | "egg-view-ejs": "^2.0.0",
22 | "cron-parser": "^2.6.0"
23 | },
24 | "devDependencies": {
25 | "autod": "^3.0.1",
26 | "autod-egg": "^1.0.0",
27 | "egg-bin": "^4.3.5",
28 | "egg-ci": "^1.8.0",
29 | "egg-email": "^1.0.2",
30 | "egg-mock": "^3.14.0",
31 | "eslint": "^4.11.0",
32 | "eslint-config-egg": "^6.0.0",
33 | "eslint-plugin-html": "^5.0.0",
34 | "webstorm-disable-index": "^1.2.0"
35 | },
36 | "engines": {
37 | "node": ">=8.9.0"
38 | },
39 | "scripts": {
40 | "start": "egg-scripts start --daemon --workers=2 --title=performance",
41 | "stop": "egg-scripts stop --title=performance",
42 | "dev": "egg-bin dev",
43 | "debug": "egg-bin debug",
44 | "test": "npm run lint -- --fix && npm run test-local",
45 | "test-local": "egg-bin test",
46 | "cov": "egg-bin cov",
47 | "lint": "eslint .",
48 | "ci": "npm run lint && npm run cov",
49 | "autod": "autod"
50 | },
51 | "ci": {
52 | "version": "8"
53 | },
54 | "repository": {
55 | "type": "git",
56 | "url": "https://github.com/wangweianger/zanePerfor"
57 | },
58 | "author": "wangweianger",
59 | "license": "MIT"
60 | }
--------------------------------------------------------------------------------
/servers-restart.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | cd /data/performance
4 | npm stop
5 | ps -ef | grep performance | grep -v grep | grep -v PPID | awk '{ print $2}' | xargs kill -9
6 | # sleep 10s
7 | npm start
8 | result=$?
9 |
10 | while [ $result -ne 0 ]
11 | do
12 | npm start
13 | result=$?
14 | if [ $result -eq 0 ]
15 | then
16 | echo "执行成功退出循环"
17 | else
18 | echo "执行失败重新执行"
19 | fi
20 | done
21 |
22 |
--------------------------------------------------------------------------------
/start-docker-compose.sh:
--------------------------------------------------------------------------------
1 | export hostIP='自己的外网IP'
2 | docker-compose up -d --build
--------------------------------------------------------------------------------
/test/app/controller/home.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const { app, assert } = require('egg-mock/bootstrap');
4 |
5 | describe('test/app/controller/home.test.js', () => {
6 |
7 | it('should assert', function* () {
8 | const pkg = require('../../../package.json');
9 | assert(app.config.keys.startsWith(pkg.name));
10 |
11 | // const ctx = app.mockContext({});
12 | // yield ctx.service.xx();
13 | });
14 |
15 | it('should GET /', () => {
16 | return app.httpRequest()
17 | .get('/')
18 | .expect('hi, egg')
19 | .expect(200);
20 | });
21 | });
22 |
--------------------------------------------------------------------------------