├── monitor-web
├── mock
│ ├── .gitkeep
│ └── config.ts
├── src
│ ├── global.less
│ ├── constants
│ │ └── index.ts
│ ├── pages
│ │ ├── index.less
│ │ ├── index.tsx
│ │ ├── micro
│ │ │ └── index.tsx
│ │ ├── Error
│ │ │ ├── index.less
│ │ │ ├── js
│ │ │ │ └── index.tsx
│ │ │ └── index.tsx
│ │ ├── Overview
│ │ │ └── index.tsx
│ │ └── Performance
│ │ │ └── index.tsx
│ ├── tailwind.css
│ ├── api
│ │ ├── modules
│ │ │ ├── performance.ts
│ │ │ ├── action.ts
│ │ │ └── error.ts
│ │ └── index.ts
│ ├── app.ts
│ ├── util
│ │ └── index.ts
│ └── index.tsx
├── .prettierignore
├── .prettierrc
├── typings.d.ts
├── README.md
├── .editorconfig
├── tailwind.config.js
├── .gitignore
├── tsconfig.json
├── .umirc.ts
├── package.json
└── config
│ └── routes.ts
├── monitor-sdk
├── src
│ ├── .babelrc
│ ├── monitor
│ │ ├── handle
│ │ │ ├── errorTrack.d.ts
│ │ │ ├── performance
│ │ │ │ ├── index.d.ts
│ │ │ │ ├── interfaceIndicator.ts
│ │ │ │ ├── domContentLoadIndicator.ts
│ │ │ │ ├── resourceIndicator.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── firstMeaningfulPaintIndicator.ts
│ │ │ │ └── basicIndicator.ts
│ │ │ ├── baseHandler.d.ts
│ │ │ ├── cache.d.ts
│ │ │ ├── pageTrack.d.ts
│ │ │ ├── cache.ts
│ │ │ ├── errorTrack.ts
│ │ │ └── pageTrack.ts
│ │ ├── report
│ │ │ ├── actionTracker.d.ts
│ │ │ ├── index.d.ts
│ │ │ ├── actionTracker.ts
│ │ │ └── index.ts
│ │ ├── class
│ │ │ ├── injectFetch.d.ts
│ │ │ ├── injectXHR.d.ts
│ │ │ ├── PromiseError.ts
│ │ │ ├── ConsoleError.ts
│ │ │ ├── JsError.ts
│ │ │ ├── ResourceError.ts
│ │ │ └── CorsError.ts
│ │ └── load
│ │ │ ├── loadConfig.d.ts
│ │ │ └── loadConfig.ts
│ ├── config
│ │ ├── index.ts
│ │ └── constant.ts
│ ├── test
│ │ ├── index.spec.ts
│ │ ├── JsError.spec.ts
│ │ ├── PromiseError.spec.ts
│ │ └── ConsoleError.spec.ts
│ ├── index.d.ts
│ ├── util
│ │ ├── index.d.ts
│ │ └── index.ts
│ ├── index.ts
│ ├── type
│ │ ├── index.d.ts
│ │ └── index.ts
│ └── index.html
├── js
│ ├── common
│ │ ├── errorTrack.d.ts
│ │ ├── loadConfig.d.ts
│ │ └── pageTrack.d.ts
│ ├── index.d.ts
│ ├── util
│ │ └── index.d.ts
│ └── type
│ │ └── index.d.ts
├── example
│ └── vue3-test-sdk
│ │ ├── src
│ │ ├── common
│ │ │ └── const.ts
│ │ ├── utils
│ │ │ └── axios.ts
│ │ ├── App.vue
│ │ ├── api
│ │ │ ├── modules
│ │ │ │ └── user.ts
│ │ │ └── index.ts
│ │ ├── router
│ │ │ └── index.ts
│ │ ├── main.ts
│ │ └── views
│ │ │ ├── Test.vue
│ │ │ └── Home.vue
│ │ ├── env.d.ts
│ │ ├── public
│ │ └── favicon.ico
│ │ ├── tsconfig.config.json
│ │ ├── .eslintrc.cjs
│ │ ├── vite.config.ts
│ │ ├── index.html
│ │ ├── .gitignore
│ │ ├── tsconfig.json
│ │ ├── package.json
│ │ └── README.md
├── .idea
│ ├── .gitignore
│ ├── jsLinters
│ │ └── eslint.xml
│ ├── inspectionProfiles
│ │ └── Project_Default.xml
│ ├── modules.xml
│ └── monitor-sdk.iml
├── .gitignore
├── rollup.config.js
├── README.md
├── LICENSE
├── tsconfig.json
├── package.json
└── webpack.config.js
├── monitor-node
├── README.md
├── src
│ ├── config
│ │ ├── mysql.ts
│ │ └── swagger.ts
│ ├── types
│ │ └── index.ts
│ ├── routes
│ │ ├── error
│ │ │ ├── js
│ │ │ │ ├── add.ts
│ │ │ │ └── find.ts
│ │ │ ├── blankScreen
│ │ │ │ ├── add.ts
│ │ │ │ └── find.ts
│ │ │ ├── cors
│ │ │ │ ├── add.ts
│ │ │ │ └── find.ts
│ │ │ ├── interface
│ │ │ │ ├── add.ts
│ │ │ │ └── find.ts
│ │ │ ├── promise
│ │ │ │ ├── add.ts
│ │ │ │ └── find.ts
│ │ │ ├── resource
│ │ │ │ ├── add.ts
│ │ │ │ └── find.ts
│ │ │ └── consoleError
│ │ │ │ ├── add.ts
│ │ │ │ └── find.ts
│ │ ├── performance
│ │ │ └── pv
│ │ │ │ ├── add.ts
│ │ │ │ └── find.ts
│ │ ├── behaviour
│ │ │ ├── hash
│ │ │ │ ├── add.ts
│ │ │ │ └── find.ts
│ │ │ └── history
│ │ │ │ ├── add.ts
│ │ │ │ └── find.ts
│ │ ├── user
│ │ │ ├── logout.ts
│ │ │ ├── forget.ts
│ │ │ ├── register.ts
│ │ │ ├── login.ts
│ │ │ └── sendCaptcha.ts
│ │ └── doc
│ │ │ └── api.ts
│ ├── utils
│ │ ├── response.ts
│ │ ├── env.ts
│ │ ├── index.ts
│ │ └── middleware
│ │ │ ├── find.ts
│ │ │ └── add.ts
│ ├── entity
│ │ └── User.ts
│ ├── influxdb.ts
│ └── app.ts
├── .gitignore
├── sendEmail.js
├── ormconfig.js
├── tsconfig.json
└── package.json
├── .idea
├── .gitignore
├── modules.xml
├── vcs.xml
└── monitor-system.iml
└── README.md
/monitor-web/mock/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/monitor-web/src/global.less:
--------------------------------------------------------------------------------
1 | @tailwind utilities;
2 |
--------------------------------------------------------------------------------
/monitor-sdk/src/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/preset-env"]
3 | }
4 |
--------------------------------------------------------------------------------
/monitor-node/README.md:
--------------------------------------------------------------------------------
1 | # 技术选型
2 |
3 | Node.js + Koa + InfluxDB + TypeORM
4 |
5 | #
--------------------------------------------------------------------------------
/monitor-sdk/js/common/errorTrack.d.ts:
--------------------------------------------------------------------------------
1 | export declare function errorCatch(): void;
2 |
--------------------------------------------------------------------------------
/monitor-web/src/constants/index.ts:
--------------------------------------------------------------------------------
1 | export const REQUESTIP = 'http://localhost: 8080';
2 |
--------------------------------------------------------------------------------
/monitor-web/src/pages/index.less:
--------------------------------------------------------------------------------
1 | .title {
2 | background: rgb(121, 242, 157);
3 | }
4 |
--------------------------------------------------------------------------------
/monitor-sdk/src/monitor/handle/errorTrack.d.ts:
--------------------------------------------------------------------------------
1 | export declare function errorCatch(): void;
2 |
--------------------------------------------------------------------------------
/monitor-sdk/example/vue3-test-sdk/src/common/const.ts:
--------------------------------------------------------------------------------
1 | export const REQUESTIP = "http://localhost:9000";
2 |
--------------------------------------------------------------------------------
/monitor-sdk/src/monitor/handle/performance/index.d.ts:
--------------------------------------------------------------------------------
1 | export declare function getPerformance(): void;
2 |
--------------------------------------------------------------------------------
/monitor-sdk/src/config/index.ts:
--------------------------------------------------------------------------------
1 | export const userInform = {
2 | appId: 'foursheep',
3 | userId: 'syandeg',
4 | }
5 |
--------------------------------------------------------------------------------
/monitor-sdk/src/monitor/handle/performance/interfaceIndicator.ts:
--------------------------------------------------------------------------------
1 | export function getInterfaceIndicator () {
2 |
3 | }
4 |
--------------------------------------------------------------------------------
/monitor-sdk/src/monitor/report/actionTracker.d.ts:
--------------------------------------------------------------------------------
1 | export default function tracker(actionType: any, data: any): void;
2 |
--------------------------------------------------------------------------------
/monitor-sdk/example/vue3-test-sdk/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | declare module "monitor-system-sdk";
4 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Editor-based HTTP Client requests
5 | /httpRequests/
6 |
--------------------------------------------------------------------------------
/monitor-web/.prettierignore:
--------------------------------------------------------------------------------
1 | **/*.md
2 | **/*.svg
3 | **/*.ejs
4 | **/*.html
5 | package.json
6 | .umi
7 | .umi-production
8 | .umi-test
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | - [首屏渲染时间的计算](https://cloud.tencent.com/developer/article/1650697)
2 | - [前端监控实践——FMP的智能获取算法](https://www.codercto.com/a/40349.html)
3 |
--------------------------------------------------------------------------------
/monitor-sdk/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Editor-based HTTP Client requests
5 | /httpRequests/
6 |
--------------------------------------------------------------------------------
/monitor-web/src/tailwind.css:
--------------------------------------------------------------------------------
1 | @import 'tailwindcss/base';
2 |
3 | @import 'tailwindcss/components';
4 |
5 | @import 'tailwindcss/utilities';
6 |
--------------------------------------------------------------------------------
/monitor-node/src/config/mysql.ts:
--------------------------------------------------------------------------------
1 | export const host = '120.79.193.126';
2 | export const database = 'minitordb';
3 | export const password = '1127';
4 |
--------------------------------------------------------------------------------
/monitor-node/src/types/index.ts:
--------------------------------------------------------------------------------
1 | // 接口响应数据
2 | export interface ApiResponseProps {
3 | code?: number;
4 | msg?: string;
5 | data?: any;
6 | }
--------------------------------------------------------------------------------
/monitor-sdk/src/monitor/class/injectFetch.d.ts:
--------------------------------------------------------------------------------
1 | export declare const proxyFetch: (sendHandler: Function | null | undefined, loadHandler: Function) => void;
2 |
--------------------------------------------------------------------------------
/monitor-sdk/src/monitor/class/injectXHR.d.ts:
--------------------------------------------------------------------------------
1 | export declare const proxyXmlHttp: (sendHandler: Function | null | undefined, loadHandler: Function) => void;
2 |
--------------------------------------------------------------------------------
/monitor-sdk/example/vue3-test-sdk/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HCYETY/monitor-system/HEAD/monitor-sdk/example/vue3-test-sdk/public/favicon.ico
--------------------------------------------------------------------------------
/monitor-sdk/src/monitor/handle/baseHandler.d.ts:
--------------------------------------------------------------------------------
1 | export declare const initHttpHandler: () => void;
2 | export declare const timestampToTime: (time: any) => string;
3 |
--------------------------------------------------------------------------------
/monitor-web/src/api/modules/performance.ts:
--------------------------------------------------------------------------------
1 | import { post } from '@/api/index';
2 |
3 | // 查找候选人信息接口
4 | export function findPv(data?: any) {
5 | return post('/api/pv', data);
6 | }
7 |
--------------------------------------------------------------------------------
/monitor-sdk/src/monitor/handle/cache.d.ts:
--------------------------------------------------------------------------------
1 | export declare function addCache(data: any): void;
2 | export declare function getCache(): any[];
3 | export declare function clearCache(): void;
4 |
--------------------------------------------------------------------------------
/monitor-sdk/js/common/loadConfig.d.ts:
--------------------------------------------------------------------------------
1 | import { initOptions } from "../type";
2 | /**
3 | * 加载配置
4 | * @param {*} options
5 | */
6 | export declare function loadConfig(options: initOptions): void;
7 |
--------------------------------------------------------------------------------
/monitor-sdk/src/monitor/report/index.d.ts:
--------------------------------------------------------------------------------
1 | export declare function lazyReport(interfaceUrl: string, param: any): void;
2 | export declare function report(interfaceUrl: string, data: object): void;
3 |
--------------------------------------------------------------------------------
/monitor-sdk/src/monitor/handle/pageTrack.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * history路由监听
3 | */
4 | export declare function historyPageTrack(): void;
5 | /**
6 | * hash路由监听
7 | */
8 | export declare function hashPageTrack(): void;
9 |
--------------------------------------------------------------------------------
/monitor-sdk/src/monitor/load/loadConfig.d.ts:
--------------------------------------------------------------------------------
1 | import { initOptions } from "../../type/index";
2 | /**
3 | * 加载配置
4 | * @param {*} options
5 | */
6 | export declare function loadConfig(options: initOptions): void;
7 |
--------------------------------------------------------------------------------
/monitor-sdk/.idea/jsLinters/eslint.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/monitor-sdk/src/test/index.spec.ts:
--------------------------------------------------------------------------------
1 | import injectConsole from '../monitor/injectConsole'
2 | import { describe, expect, it } from 'vitest'
3 | describe('first', () => {
4 | it('1 + 1 shoule be 2', () => {
5 |
6 | })
7 |
8 | })
9 |
--------------------------------------------------------------------------------
/monitor-web/src/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import styles from './index.less';
2 |
3 | export default function IndexPage() {
4 | return (
5 |
6 |
Page index
7 |
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/monitor-web/src/pages/micro/index.tsx:
--------------------------------------------------------------------------------
1 | import { MicroApp } from 'umi';
2 |
3 | export default () => {
4 | return (
5 |
6 | {/*ddd*/}
7 | {/**/}
8 |
9 | )
10 | }
11 |
--------------------------------------------------------------------------------
/monitor-sdk/src/monitor/report/actionTracker.ts:
--------------------------------------------------------------------------------
1 | import { lazyReport } from "./index";
2 |
3 | // 手动上报错误
4 | export default function tracker(actionType, data) {
5 | lazyReport('action', {
6 | actionType,
7 | data,
8 | })
9 | };
10 |
--------------------------------------------------------------------------------
/monitor-web/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "all",
4 | "printWidth": 80,
5 | "overrides": [
6 | {
7 | "files": ".prettierrc",
8 | "options": { "parser": "json" }
9 | }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/monitor-web/src/api/modules/action.ts:
--------------------------------------------------------------------------------
1 | import { post } from '@/api/index';
2 |
3 | export function findHistory(data?: any) {
4 | return post('/api/history', data);
5 | }
6 | export function findHash(data?: any) {
7 | return post('/api/hash', data);
8 | }
9 |
--------------------------------------------------------------------------------
/monitor-sdk/js/index.d.ts:
--------------------------------------------------------------------------------
1 | import { errorCatch } from './common/errorTrack';
2 | import { initOptions } from "./type";
3 | /**
4 | * 初始化配置
5 | * @param {*} options
6 | */
7 | declare function init(options: initOptions): void;
8 | export { init, errorCatch };
9 |
--------------------------------------------------------------------------------
/monitor-sdk/src/index.d.ts:
--------------------------------------------------------------------------------
1 | import { errorCatch } from './common/errorTrack';
2 | import { initOptions } from "./type";
3 | /**
4 | * 初始化配置
5 | * @param {*} options
6 | */
7 | declare function init(options: initOptions): void;
8 | export { init, errorCatch, };
9 |
--------------------------------------------------------------------------------
/monitor-sdk/src/monitor/handle/cache.ts:
--------------------------------------------------------------------------------
1 | const cache = []
2 |
3 | export function addCache(data) {
4 | cache.push(data)
5 | }
6 | export function getCache() {
7 | return cache
8 | }
9 |
10 | export function clearCache() {
11 | cache.length = 0
12 | }
--------------------------------------------------------------------------------
/monitor-node/src/routes/error/js/add.ts:
--------------------------------------------------------------------------------
1 | import { Context } from 'koa';
2 | import add from '../../../utils/middleware/add';
3 |
4 | export default async(ctx: Context) => {
5 | const bucket = 'jsError';
6 | ctx.body = add({ bucket, request: ctx.req });
7 | }
8 |
--------------------------------------------------------------------------------
/monitor-node/src/routes/performance/pv/add.ts:
--------------------------------------------------------------------------------
1 | import { Context } from 'koa';
2 | import add from '../../../utils/middleware/add';
3 |
4 | export default async(ctx: Context) => {
5 | const bucket = 'pv';
6 | ctx.body = add({ bucket, request: ctx.req });
7 | }
8 |
--------------------------------------------------------------------------------
/monitor-sdk/example/vue3-test-sdk/src/utils/axios.ts:
--------------------------------------------------------------------------------
1 | import axios, { AxiosInstance } from "axios";
2 |
3 | export const axiosIntance: AxiosInstance = axios.create({
4 | baseURL:
5 | "https://www.fastmock.site/mock/9ab9b8491d2a9d63435a7b0cf893f84f/test",
6 | });
7 |
--------------------------------------------------------------------------------
/monitor-node/src/routes/behaviour/hash/add.ts:
--------------------------------------------------------------------------------
1 | import { Context } from 'koa';
2 | import add from '../../../utils/middleware/add';
3 |
4 | export default async(ctx: Context) => {
5 | const bucket = 'hashPage';
6 |
7 | ctx.body = add({ bucket, request: ctx.req });
8 | }
--------------------------------------------------------------------------------
/monitor-node/src/routes/error/blankScreen/add.ts:
--------------------------------------------------------------------------------
1 | import { Context } from 'koa';
2 | import add from '../../../utils/middleware/add';
3 |
4 | export default async(ctx: Context) => {
5 | const bucket = 'blankScreen';
6 | ctx.body = add({ bucket, request: ctx.req });
7 | }
--------------------------------------------------------------------------------
/monitor-node/src/routes/error/cors/add.ts:
--------------------------------------------------------------------------------
1 | import { Context } from 'koa';
2 | import add from '../../../utils/middleware/add';
3 |
4 | export default async(ctx: Context) => {
5 | const bucket = 'corsError';
6 | ctx.body = add({ bucket, request: ctx.req });
7 | }
8 |
--------------------------------------------------------------------------------
/monitor-node/src/routes/behaviour/history/add.ts:
--------------------------------------------------------------------------------
1 | import { Context } from 'koa';
2 | import add from '../../../utils/middleware/add';
3 |
4 | export default async(ctx: Context) => {
5 | const bucket = 'historyPage';
6 |
7 | ctx.body = add({ bucket, request: ctx.req });
8 | }
--------------------------------------------------------------------------------
/monitor-node/src/routes/error/interface/add.ts:
--------------------------------------------------------------------------------
1 | import { Context } from 'koa';
2 | import add from '../../../utils/middleware/add';
3 |
4 | export default async(ctx: Context) => {
5 | const bucket = 'interfaceError';
6 | ctx.body = add({ bucket, request: ctx.req });
7 | }
--------------------------------------------------------------------------------
/monitor-node/src/routes/error/promise/add.ts:
--------------------------------------------------------------------------------
1 | import { Context } from 'koa';
2 | import add from '../../../utils/middleware/add';
3 |
4 | export default async(ctx: Context) => {
5 | const bucket = 'promiseError';
6 | ctx.body = add({ bucket, request: ctx.req });
7 | }
8 |
--------------------------------------------------------------------------------
/monitor-node/src/routes/error/resource/add.ts:
--------------------------------------------------------------------------------
1 | import { Context } from 'koa';
2 | import add from '../../../utils/middleware/add';
3 |
4 | export default async(ctx: Context) => {
5 | const bucket = 'resourceError';
6 | ctx.body = add({ bucket, request: ctx.req });
7 | }
8 |
--------------------------------------------------------------------------------
/monitor-sdk/example/vue3-test-sdk/tsconfig.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@vue/tsconfig/tsconfig.node.json",
3 | "include": ["vite.config.*", "vitest.config.*", "cypress.config.*"],
4 | "compilerOptions": {
5 | "composite": true,
6 | "types": ["node"]
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/monitor-node/src/routes/error/consoleError/add.ts:
--------------------------------------------------------------------------------
1 | import { Context } from 'koa';
2 | import add from '../../../utils/middleware/add';
3 |
4 | export default async(ctx: Context) => {
5 | const bucket = 'consoleError';
6 | ctx.body = add({ bucket, request: ctx.req });
7 | }
8 |
--------------------------------------------------------------------------------
/monitor-sdk/src/monitor/class/PromiseError.ts:
--------------------------------------------------------------------------------
1 | import { JsError } from './JsError';
2 | export class PromiseError extends JsError {
3 | constructor(message, type, errorType, fileName, position, selector) {
4 | super(message, type, errorType, fileName, position, selector);
5 | }
6 | }
--------------------------------------------------------------------------------
/monitor-node/src/routes/performance/pv/find.ts:
--------------------------------------------------------------------------------
1 | import { Context } from 'koa';
2 | import find from "../../../utils/middleware/find";
3 |
4 | export default async(ctx: Context) => {
5 | const bucket = 'pv';
6 | return ctx.body = find({ bucket, queryTime: (ctx.query.time) as string });
7 | }
8 |
--------------------------------------------------------------------------------
/monitor-node/src/routes/error/cors/find.ts:
--------------------------------------------------------------------------------
1 | import { Context } from 'koa';
2 | import find from "../../../utils/middleware/find";
3 |
4 | export default async(ctx: Context) => {
5 | const bucket = 'corsError';
6 | return ctx.body = find({ bucket, queryTime: (ctx.query.time) as string });
7 | }
8 |
--------------------------------------------------------------------------------
/monitor-node/src/routes/error/consoleError/find.ts:
--------------------------------------------------------------------------------
1 | import { Context } from 'koa';
2 | import find from "../../../utils/middleware/find";
3 |
4 | export default async(ctx: Context) => {
5 | const bucket = 'consoleError';
6 | return ctx.body = find({ bucket, queryTime: (ctx.query.time) as string });
7 | }
8 |
--------------------------------------------------------------------------------
/monitor-node/src/routes/error/resource/find.ts:
--------------------------------------------------------------------------------
1 | import { Context } from 'koa';
2 | import find from "../../../utils/middleware/find";
3 |
4 | export default async(ctx: Context) => {
5 | const bucket = 'resourceError';
6 | return ctx.body = find({ bucket, queryTime: (ctx.query.time) as string });
7 | }
8 |
--------------------------------------------------------------------------------
/monitor-sdk/js/util/index.d.ts:
--------------------------------------------------------------------------------
1 | import { mechanismType } from "../type";
2 | export declare function getErrorKey(event: ErrorEvent | Event): mechanismType.JS | mechanismType.RS | mechanismType.CS;
3 | export declare function getLastEvent(): any;
4 | export declare function getSelector(pathsOrTarget: any): any;
5 |
--------------------------------------------------------------------------------
/monitor-web/typings.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.css';
2 | declare module '*.less';
3 | declare module '*.png';
4 | declare module '*.svg' {
5 | export function ReactComponent(
6 | props: React.SVGProps,
7 | ): React.ReactElement;
8 | const url: string;
9 | export default url;
10 | }
11 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/monitor-sdk/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/monitor-web/README.md:
--------------------------------------------------------------------------------
1 | # umi project
2 |
3 | ## Getting Started
4 |
5 | Install dependencies,
6 |
7 | ```bash
8 | $ yarn
9 | ```
10 |
11 | Start the dev server,
12 |
13 | ```bash
14 | $ yarn start
15 | ```
16 |
17 | ----
18 |
19 | TailwindCss 配置
20 | [解决方法](https://blog.jiahonzheng.com/post/umi-tailwind/)
21 |
--------------------------------------------------------------------------------
/monitor-sdk/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/monitor-sdk/example/vue3-test-sdk/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 | Home
3 | |
4 | Test
5 |
6 |
7 |
8 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/monitor-node/src/routes/error/js/find.ts:
--------------------------------------------------------------------------------
1 | import { Context } from 'koa';
2 | import find from "../../../utils/middleware/find";
3 |
4 | export default async(ctx: Context) => {
5 | const bucket = 'jsError';
6 | console.log(bucket)
7 | return ctx.body = await find({ bucket, queryTime: (ctx.query.time) as string });
8 | }
9 |
--------------------------------------------------------------------------------
/monitor-sdk/js/common/pageTrack.d.ts:
--------------------------------------------------------------------------------
1 | import { PageInformation } from "../type";
2 | import parser from 'ua-parser-js';
3 | export declare function getUserAgent(): parser.IResult;
4 | export declare function getPageInfo(): PageInformation;
5 | export declare function getPerformanceTiming(): void;
6 | export declare const getPv: () => void;
7 |
--------------------------------------------------------------------------------
/monitor-web/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
15 | [Makefile]
16 | indent_style = tab
17 |
--------------------------------------------------------------------------------
/monitor-sdk/example/vue3-test-sdk/src/api/modules/user.ts:
--------------------------------------------------------------------------------
1 | import { post, get } from '../index';
2 |
3 | // 登录接口, 示例:
4 | export function login(data: { url?: string; method?: string; data?: string }) {
5 | return post('/login', data);
6 | }
7 | // 获取 console.error 异常数据
8 | export function consoleErrorGet() {
9 | return get('/console-error');
10 | }
--------------------------------------------------------------------------------
/monitor-sdk/example/vue3-test-sdk/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 | require("@rushstack/eslint-patch/modern-module-resolution");
3 |
4 | module.exports = {
5 | root: true,
6 | extends: [
7 | "plugin:vue/vue3-essential",
8 | "eslint:recommended",
9 | "@vue/eslint-config-typescript/recommended",
10 | "@vue/eslint-config-prettier",
11 | ],
12 | };
13 |
--------------------------------------------------------------------------------
/monitor-web/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | mode: "jit",
3 | purge: ["./src/**/*.{ts,tsx,js,jsx}"],
4 | darkMode: false, // or 'media' or 'class'
5 | theme: {
6 | backgroundColor: theme => ({
7 | ...theme('colors'),
8 | dark70: 'rgba(0,0,0,.7)'
9 | }),
10 | extend: {
11 | },
12 | },
13 | variants: {},
14 | plugins: [],
15 | };
16 |
--------------------------------------------------------------------------------
/monitor-node/src/routes/behaviour/hash/find.ts:
--------------------------------------------------------------------------------
1 | import { Context } from 'koa';
2 | import find from "../../../utils/middleware/find";
3 |
4 | export default async(ctx: Context) => {
5 | const bucket = 'hashPage';
6 | const query = `from(bucket: "${bucket}")
7 | |> range(start: -122h)`;
8 |
9 | return ctx.body = find({ bucket, queryTime: (ctx.query.time) as string });
10 | }
11 |
--------------------------------------------------------------------------------
/monitor-web/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /npm-debug.log*
6 | /yarn-error.log
7 | /yarn.lock
8 | /package-lock.json
9 |
10 | # production
11 | /dist
12 |
13 | # misc
14 | .DS_Store
15 |
16 | # umi
17 | /src/.umi
18 | /src/.umi-production
19 | /src/.umi-test
20 | /.env.local
21 |
--------------------------------------------------------------------------------
/monitor-node/src/routes/behaviour/history/find.ts:
--------------------------------------------------------------------------------
1 | import { Context } from 'koa';
2 | import find from "../../../utils/middleware/find";
3 |
4 | export default async(ctx: Context) => {
5 | const bucket = 'historyPage';
6 | const query = `from(bucket: "${bucket}")
7 | |> range(start: -122h)`;
8 |
9 | return ctx.body = find({ bucket, queryTime: (ctx.query.time) as string });
10 | }
11 |
--------------------------------------------------------------------------------
/monitor-node/src/routes/error/blankScreen/find.ts:
--------------------------------------------------------------------------------
1 | import { Context } from 'koa';
2 | import find from "../../../utils/middleware/find";
3 |
4 | export default async(ctx: Context) => {
5 | const bucket = 'blankScreen';
6 | const query = `from(bucket: "${bucket}")
7 | |> range(start: -122h)`;
8 |
9 | return ctx.body = find({ bucket, queryTime: (ctx.query.time) as string });
10 | }
11 |
--------------------------------------------------------------------------------
/monitor-sdk/example/vue3-test-sdk/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { fileURLToPath, URL } from "node:url";
2 |
3 | import { defineConfig } from "vite";
4 | import vue from "@vitejs/plugin-vue";
5 |
6 | // https://vitejs.dev/config/
7 | export default defineConfig({
8 | plugins: [vue()],
9 | resolve: {
10 | alias: {
11 | "@": fileURLToPath(new URL("./src", import.meta.url)),
12 | },
13 | },
14 | });
15 |
--------------------------------------------------------------------------------
/monitor-node/src/routes/error/interface/find.ts:
--------------------------------------------------------------------------------
1 | import { Context } from 'koa';
2 | import find from "../../../utils/middleware/find";
3 |
4 | export default async(ctx: Context) => {
5 | const bucket = 'interfaceError';
6 | const query = `
7 | from(bucket: "${bucket}")
8 | |> range(start: -122h)
9 | `;
10 |
11 | return ctx.body = find({ bucket, queryTime: (ctx.query.time) as string });
12 | }
13 |
--------------------------------------------------------------------------------
/monitor-sdk/src/monitor/class/ConsoleError.ts:
--------------------------------------------------------------------------------
1 | export class ConsoleError {
2 | public message: string;
3 | public column: number;
4 | public row: number;
5 | public stack: string;
6 | public url: string;
7 |
8 | constructor(message, column, row, stack, url) {
9 | this.message = message;
10 | this.column = column;
11 | this.row = row;
12 | this.stack = stack;
13 | this.url = url;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/monitor-sdk/example/vue3-test-sdk/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Vite Index
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/monitor-sdk/example/vue3-test-sdk/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | .DS_Store
12 | dist
13 | dist-ssr
14 | coverage
15 | *.local
16 |
17 | /cypress/videos/
18 | /cypress/screenshots/
19 |
20 | # Editor directories and files
21 | .vscode/*
22 | !.vscode/extensions.json
23 | .idea
24 | *.suo
25 | *.ntvs*
26 | *.njsproj
27 | *.sln
28 | *.sw?
29 |
--------------------------------------------------------------------------------
/monitor-node/src/utils/response.ts:
--------------------------------------------------------------------------------
1 | import { ApiResponseProps } from '../types';
2 |
3 | const defaultRes = {
4 | code: 0,
5 | msg: '',
6 | data: {},
7 | }
8 |
9 | // 接口统一的响应格式
10 | export class ApiResponse {
11 | res: ApiResponseProps = {}
12 |
13 | constructor(params: ApiResponseProps) {
14 | this.res = Object.assign({}, defaultRes, params);
15 | }
16 |
17 | // koa 在 ctx.body 上会自动调用 toJSON 方法
18 | toJSON() {
19 | return this.res;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/monitor-sdk/src/util/index.d.ts:
--------------------------------------------------------------------------------
1 | import { mechanismType } from "../type";
2 | export declare function getErrorKey(event: ErrorEvent | Event): mechanismType.JS | mechanismType.RS | mechanismType.CS;
3 | export declare function getLastEvent(): Event;
4 | export declare function getSelector(pathsOrTarget: any): any;
5 | export declare function nowTime(data: {
6 | hms?: boolean;
7 | time?: number;
8 | }): number | string;
9 | export declare function isLoad(callback: any): void;
10 |
--------------------------------------------------------------------------------
/monitor-web/mock/config.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | '/api/getMicro': {
3 | apps: [
4 | {
5 | name: 'qiankun-umi', // 唯一 id
6 | entry: 'http://localhost:8001/', // html entry
7 | },
8 | {
9 | name: 'micro-umi', // 唯一 id
10 | entry: 'http://localhost:8002', // html entry
11 | },
12 | {
13 | name: 'micro-vue', // 唯一 id
14 | entry: 'http://localhost:8003', // html entry
15 | },
16 | ],
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/monitor-node/.gitignore:
--------------------------------------------------------------------------------
1 | #/node_modules
2 | #/.idea
3 | /dist
4 | .DS_Store
5 | node_modules
6 |
7 |
8 | # local env files
9 | .env.local
10 | .env.*.local
11 |
12 | # Log files
13 | npm-debug.log*
14 | yarn-debug.log*
15 | yarn-error.log*
16 | pnpm-debug.log*
17 |
18 | # Editor directories and files
19 | .idea
20 | .vscode
21 | *.suo
22 | *.ntvs*
23 | *.njsproj
24 | *.sln
25 | *.sw?
26 |
27 |
28 | src/
29 | plugins/
30 | public/
31 | vue.config.js
32 | babel.config.js
33 | *.map
34 | *.html
35 |
--------------------------------------------------------------------------------
/monitor-node/src/utils/env.ts:
--------------------------------------------------------------------------------
1 | import { ClientOptions, InfluxDB } from "@influxdata/influxdb-client";
2 |
3 | export const INFLUX_URL: string | ClientOptions = 'http://localhost:8086'
4 | export const INFLUX_TOKEN: string = '2bUXF61Qg7NWumhb1T6l9qwM70qq4ewURkv5GGISZWNCd9_jveXhd2rDUERvtPCwDCLRxJp2-Y4NPyPmQzPVUw=='
5 | export const INFLUX_ORG: string = 'influxdb'
6 |
7 | export const clientDB = new InfluxDB({url: INFLUX_URL, token: INFLUX_TOKEN});
8 | export const queryApi = clientDB.getQueryApi(INFLUX_ORG);
9 |
--------------------------------------------------------------------------------
/monitor-sdk/example/vue3-test-sdk/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@vue/tsconfig/tsconfig.web.json",
3 | "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
4 | "compilerOptions": {
5 | "baseUrl": ".",
6 | "jsx": "preserve",
7 | "lib": ["es2015", "ES2020","dom"],
8 | "paths": {
9 | "@/*": ["./src/*"]
10 | },
11 | "preserveValueImports": false
12 | },
13 |
14 | "references": [
15 | {
16 | "path": "./tsconfig.config.json"
17 | }
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/.idea/monitor-system.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/monitor-sdk/.gitignore:
--------------------------------------------------------------------------------
1 | #/node_modules
2 | #/.idea
3 | /dist
4 | .DS_Store
5 | node_modules
6 |
7 |
8 | # local env files
9 | .env.local
10 | .env.*.local
11 |
12 | # Log files
13 | npm-debug.log*
14 | yarn-debug.log*
15 | yarn-error.log*
16 | pnpm-debug.log*
17 |
18 | # Editor directories and files
19 | .idea
20 | .vscode
21 | *.suo
22 | *.ntvs*
23 | *.njsproj
24 | *.sln
25 | *.sw?
26 |
27 |
28 | # src/
29 | plugins/
30 | public/
31 | vue.config.js
32 | babel.config.js
33 | *.map
34 | *.html
35 |
36 | .npmignore
37 | lib
--------------------------------------------------------------------------------
/monitor-sdk/.idea/monitor-sdk.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/monitor-web/src/app.ts:
--------------------------------------------------------------------------------
1 | // 从接口中获取子应用配置,export 出的 qiankun 变量是一个 promise
2 | export const qiankun = fetch('/api/getMicro')
3 | .then(res => {
4 | return res.json();
5 | })
6 | .then(({ apps }) => ({
7 | // 注册子应用信息
8 | apps,
9 | // 完整生命周期钩子请看 https://qiankun.umijs.org/zh/api/#registermicroapps-apps-lifecycles
10 | lifeCycles: {
11 | afterMount: (props: any) => {
12 | console.log(props);
13 | },
14 | },
15 | // 支持更多的其他配置,详细看这里 https://qiankun.umijs.org/zh/api/#start-opts
16 | }));
17 |
--------------------------------------------------------------------------------
/monitor-sdk/src/monitor/class/JsError.ts:
--------------------------------------------------------------------------------
1 | export class JsError {
2 | public message: string;
3 | public type: string;
4 | public errorType: string;
5 | public fileName: string;
6 | public position: string;
7 | public selector: string;
8 |
9 | constructor(message, type, errorType, fileName, position, selector) {
10 | this.message = message;
11 | this.type = type;
12 | this.errorType = errorType;
13 | this.fileName = fileName;
14 | this.position = position;
15 | this.selector = selector;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/monitor-node/src/entity/User.ts:
--------------------------------------------------------------------------------
1 | import { Entity, PrimaryGeneratedColumn, Column } from "typeorm";
2 | import { database } from "../config/mysql";
3 |
4 | @Entity({ database })
5 | export default class User {
6 | @PrimaryGeneratedColumn()
7 | key: number = 0;
8 |
9 | @Column()
10 | email: string = '';
11 |
12 | @Column()
13 | password: string = '';
14 |
15 | @Column()
16 | captcha: number = 0;
17 |
18 | @Column("bigint")
19 | runtime_captcha: number | undefined;
20 |
21 | @Column({ default: null })
22 | session: string = '';
23 | }
--------------------------------------------------------------------------------
/monitor-sdk/src/monitor/class/ResourceError.ts:
--------------------------------------------------------------------------------
1 | export class ResourceError {
2 | public type: string;
3 | public url: string;
4 | public message: string;
5 | public html: string;
6 | public errorType: string;
7 | public tagName: string;
8 | public selector: string;
9 |
10 | constructor(type, url, message, html, errorType, tagName, selector) {
11 | this.type = type;
12 | this.url = url;
13 | this.message = message;
14 | this.html = html;
15 | this.errorType = errorType;
16 | this.tagName = tagName;
17 | this.selector = selector;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/monitor-web/src/util/index.ts:
--------------------------------------------------------------------------------
1 | // export function createColor() {
2 | // return '#' + (function(color) {
3 | // return (color += '0123456789abcdef'[Math.floor(Math.random()*16)]) && (color.length === 6) ? color : arguments.callee(color);
4 | // })('');
5 | // }
6 |
7 | export function createColor() {
8 | const r = Math.floor(Math.random() * 255);
9 | const g = Math.floor(Math.random() * 255);
10 | const b = Math.floor(Math.random() * 255);
11 | const a = Math.random(); // 如果是 rgb ,则固定该值即可
12 | const color = `rgba(${r}, ${g}, ${b}, ${a})`;
13 | return color;
14 | }
15 |
--------------------------------------------------------------------------------
/monitor-node/sendEmail.js:
--------------------------------------------------------------------------------
1 | const nodemailer = require('nodemailer');
2 |
3 | const config = {
4 | service: 'qq',
5 | host: 'smtp.qq.com',
6 | port: 465,
7 | auth: {
8 | user: 'syandeg@qq.com',
9 | pass: 'xatmepzzhcxejcee'
10 | }
11 | };
12 |
13 | const transporter = nodemailer.createTransport(config);
14 |
15 | module.exports = function sendEmail(email) {
16 | transporter.sendMail(email, (error, info) => {
17 | if (error) {
18 | return console.log(error);
19 | } else {
20 | console.log('email 已经发送成功:', info.response)
21 | }
22 | })
23 | return
24 | }
--------------------------------------------------------------------------------
/monitor-sdk/example/vue3-test-sdk/src/router/index.ts:
--------------------------------------------------------------------------------
1 | import { createRouter, createWebHistory } from "vue-router";
2 | import App from "../App.vue";
3 | import Home from "../views/Home.vue";
4 | import Test from "../views/Test.vue";
5 |
6 | const router = createRouter({
7 | history: createWebHistory(import.meta.env.BASE_URL),
8 | routes: [
9 | {
10 | path: "/test",
11 | name: "Test",
12 | component: Test,
13 | },
14 | {
15 | path: "/home",
16 | name: "Home",
17 | component: Home,
18 | },
19 | ],
20 | });
21 |
22 | export default router;
23 |
--------------------------------------------------------------------------------
/monitor-node/ormconfig.js:
--------------------------------------------------------------------------------
1 | const { database, host, password } = require("./src/config/mysql");
2 |
3 | module.exports = [
4 | {
5 | type: 'mysql', // 数据库类型
6 | host: host, // 连接域名
7 | port: '3306', // 连接端口
8 | username: 'root', // 用户名
9 | password: password, // 密码
10 | database: database, // 数据库名
11 | logging: false, // 是否有日志
12 | synchronize: true, // 是否自动建表
13 | entities: [__dirname + '/src/entity/*{.ts,.Error}'], // entity/model存放位置
14 | timezone: 'z', // 以本地时区时间为主
15 | dateStrings: 'TIMESTAMP'
16 | }
17 | ]
18 |
--------------------------------------------------------------------------------
/monitor-node/src/influxdb.ts:
--------------------------------------------------------------------------------
1 | const Influx = require('influx'); //导包
2 |
3 | // 定义数据库连接和数据格式,创建client
4 | const client = new Influx.InfluxDB({
5 | database: 'mydb',
6 | username: 'root',
7 | password: 'root',
8 | hosts: [{ host: 'xx.xx.xx.xxx' }],
9 | schema: [
10 | {
11 | measurement: 'test', //类似于数据表的概念
12 | fields: { //数据表的字段,定义类型,FLOAT/INTEGER/STRING/BOOLEAN
13 | field1:Influx.FieldType.INTEGER,
14 | field2:Influx.FieldType.INTEGER,
15 | }, // tag 也是里面的字段,是自带索引光环。查询速度杠杠的。
16 | tags: ['tag1','tag2']
17 | }
18 | ]
19 | });
20 |
--------------------------------------------------------------------------------
/monitor-web/src/pages/Error/index.less:
--------------------------------------------------------------------------------
1 | //.whole {
2 | // background-color: #fcfcfc;
3 | // width: 100%;
4 | // height: 100%;
5 | // margin: 20px;
6 | //}
7 |
8 | .bottom {
9 | display: flex;
10 | margin: 20px;
11 | }
12 |
13 | .box {
14 | flex: 1;
15 |
16 | .chartTitle {
17 |
18 | .iconBox {
19 | display: flex;
20 | -ms-flex-align: center;
21 | align-items: center;
22 |
23 | .labelStr {
24 | cursor: pointer;
25 | font-size: 1.5em;
26 | }
27 | }
28 |
29 | .timeLabel {
30 | color: #a3a5b0;
31 | font-size: .12rem;
32 | margin: 10px 0px 15px 0px;
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/monitor-sdk/rollup.config.js:
--------------------------------------------------------------------------------
1 | import pkg from './package.json'
2 | import typescript from '@rollup/plugin-typescript'
3 | import sourceMaps from "rollup-plugin-sourcemaps";
4 |
5 | export default {
6 | input: "./src/index.ts",
7 | output: [
8 | // 1.cjs -> commonjs
9 | // 2.esm
10 | {
11 | format: "cjs",
12 | file: pkg.main,
13 | sourcemap: true
14 | },
15 | {
16 | format: "es",
17 | file: pkg.module,
18 | sourcemap: true
19 | },
20 | ],
21 | plugins: [
22 | typescript({
23 | exclude: "node_modules/**",
24 | typescript: require("typescript")
25 | }),
26 | sourceMaps()
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/monitor-sdk/src/monitor/handle/errorTrack.ts:
--------------------------------------------------------------------------------
1 | import { handleJs, handlePromise, initHttpHandler, injectConsole } from '@monitor/handle/baseHandler';
2 |
3 | export function errorCatch() {
4 | console.log('%c%s', 'font-size: 24px; color: red', '开始监控网页报错');
5 |
6 | // 监控 js/资源/跨域 错误
7 | window.addEventListener(
8 | 'error',
9 | event => {
10 | handleJs(event)
11 | },
12 | true
13 | )
14 |
15 | // 监控 promise 错误
16 | window.addEventListener(
17 | 'unhandledrejection',
18 | event => {
19 | handlePromise(event)
20 | },
21 | true
22 | )
23 |
24 | // 监控 console.error 错误
25 | injectConsole();
26 |
27 | // 监控 http 错误
28 | initHttpHandler();
29 | }
30 |
--------------------------------------------------------------------------------
/monitor-node/src/routes/user/logout.ts:
--------------------------------------------------------------------------------
1 | import { Context } from 'koa';
2 | import { getManager } from "typeorm";
3 | import User from '../../entity/User';
4 | import { ApiResponse } from "../../utils/response";
5 |
6 | export default async (ctx:Context) => {
7 | const cookie = ctx.request.body.cookie;
8 | const userRepository = getManager().getRepository(User);
9 | // @ts-ignore
10 | const user = await userRepository.findOne({ session: cookie });
11 | if (user) {
12 | // 删除 session 字段即可
13 | user.session = '';
14 | await userRepository.save(user);
15 | ctx.body = new ApiResponse({
16 | code: 200,
17 | msg: '已退出登录',
18 | data: {
19 | status: true
20 | }
21 | });
22 | }
23 | }
--------------------------------------------------------------------------------
/monitor-web/src/api/modules/error.ts:
--------------------------------------------------------------------------------
1 | import { post, get } from '@/api/index';
2 |
3 | // 查找候选人信息接口
4 | export function findJsError() {
5 | return get('/error/js');
6 | }
7 | export function findPromise(data?: any) {
8 | return post('/api/promise', data);
9 | }
10 | export function findResource(data?: any) {
11 | return post('/api/resource', data);
12 | }
13 | export function findCors(data?: any) {
14 | return post('/api/cors', data);
15 | }
16 | export function findConsoleError(data?: any) {
17 | return post('/api/console-error', data);
18 | }
19 | export function findInterface(data?: any) {
20 | return post('/api/interface', data);
21 | }
22 | export function findBlankScreen(data?: any) {
23 | return post('/api/blankScreen', data);
24 | }
25 |
--------------------------------------------------------------------------------
/monitor-sdk/example/vue3-test-sdk/src/main.ts:
--------------------------------------------------------------------------------
1 | import { createApp } from "vue";
2 | import App from "./App.vue";
3 | import { init, errorCatch, } from "monitor-system-sdk";
4 | import router from "./router";
5 | // import "./assets/main.css";
6 |
7 | const app = createApp(App);
8 |
9 | init({
10 | // appId, // 系统id
11 | cookie: 'syandeg', // 用户id
12 | // userId: window.localStorage.getItem('session_id') || 'foursheep', // 用户id
13 | reportUrl: 'http://localhost:8080/report', // 后端url
14 | // autoTracker, // 自动埋点
15 | // delay: 0, // 延迟和合并上报的功能
16 | hashPage: true, // 是否是 hash 路由
17 | errorReport: true, // 是否开启错误监控
18 | performanceReport: true // 是否开启性能监控
19 | })
20 |
21 | app.use(router);
22 |
23 | app.mount("#app");
24 |
--------------------------------------------------------------------------------
/monitor-sdk/src/config/constant.ts:
--------------------------------------------------------------------------------
1 | import { userInform } from '@config';
2 |
3 | export const getBasicParams = {
4 | appId: userInform.appId,
5 | userID: userInform.userId,
6 | startTime: new Date().getTime(),
7 | pageUrl: window.location.href,
8 | }
9 |
10 | export enum EntryTypes {
11 | paint = 'paint',
12 | navigation = 'navigation',
13 | resource = 'resource',
14 | LCP = 'largest-contentful-paint',
15 | FID = 'first-input',
16 | CLS = 'layout-shift',
17 | }
18 | export enum PerformanceInfoType {
19 | CLS = 'cumulative-layout-shift',
20 | NT = 'navigation-timing',
21 | FP = 'first-paint',
22 | FCP = 'first-contentful-paint',
23 | LCP = 'largest-contentful-paint',
24 | FID = 'first-input-delay',
25 | RF = 'resource-flow',
26 | }
27 |
--------------------------------------------------------------------------------
/monitor-sdk/src/test/JsError.spec.ts:
--------------------------------------------------------------------------------
1 | import { JsError } from '../monitor/class/JsError'
2 | import { describe, expect, it } from 'vitest'
3 | describe('JsError', () => {
4 | it('happy path', () => {
5 | expect(
6 | new JsError(
7 | 'jsError',
8 | 'http://localhost:8080/',
9 | "Uncaught TypeError: Cannot set property 'error' of undefined",
10 | '71:34',
11 | '',
12 | 'error'
13 | )
14 | ).toMatchInlineSnapshot(`
15 | JsError {
16 | "errorType": "Uncaught TypeError: Cannot set property 'error' of undefined",
17 | "fileName": "71:34",
18 | "message": "jsError",
19 | "position": "",
20 | "selector": "error",
21 | "type": "http://localhost:8080/",
22 | }
23 | `)
24 | })
25 | })
26 |
--------------------------------------------------------------------------------
/monitor-web/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "esnext",
5 | "moduleResolution": "node",
6 | "resolveJsonModule": true,
7 | "importHelpers": true,
8 | "jsx": "react-jsx",
9 | "esModuleInterop": true,
10 | "sourceMap": true,
11 | "baseUrl": "./",
12 | "strict": true,
13 | "paths": {
14 | "@/*": ["src/*"],
15 | "@@/*": ["src/.umi/*"]
16 | },
17 | "allowSyntheticDefaultImports": true
18 | },
19 | "include": [
20 | "mock/**/*",
21 | "src/**/*",
22 | "config/**/*",
23 | ".umirc.ts",
24 | "typings.d.ts"
25 | ],
26 | "exclude": [
27 | "node_modules",
28 | "lib",
29 | "es",
30 | "dist",
31 | "typings",
32 | "**/__test__",
33 | "test",
34 | "docs",
35 | "tests"
36 | ]
37 | }
38 |
--------------------------------------------------------------------------------
/monitor-sdk/src/test/PromiseError.spec.ts:
--------------------------------------------------------------------------------
1 | import { PromiseError } from '../monitor/class/PromiseError'
2 | import { describe, expect, it } from 'vitest'
3 | describe('JsError', () => {
4 | it('happy path', () => {
5 | expect(
6 | new PromiseError(
7 | "Cannot set property 'error' of undefined",
8 | 'unhandledrejection',
9 | 'unhandledrejectionError',
10 | 'http://localhost:8080/',
11 | '75:38',
12 | ''
13 | )
14 | ).toMatchInlineSnapshot(`
15 | PromiseError {
16 | "errorType": "unhandledrejectionError",
17 | "fileName": "http://localhost:8080/",
18 | "message": "Cannot set property 'error' of undefined",
19 | "position": "75:38",
20 | "selector": "",
21 | "type": "unhandledrejection",
22 | }
23 | `)
24 | })
25 | })
26 |
--------------------------------------------------------------------------------
/monitor-sdk/src/test/ConsoleError.spec.ts:
--------------------------------------------------------------------------------
1 | import { ConsoleError } from '../monitor/class/ConsoleError'
2 | import { describe, expect, it } from 'vitest'
3 | describe('JsError', () => {
4 | it('happy path', () => {
5 | expect(
6 | new ConsoleError(
7 | '错误捕获222',
8 | 27,
9 | 82,
10 | `Error: 错误捕获222 at bugConsole (http://localhost:8080/:82:27) at HTMLButtonElement.onclick (http://localhost:8080/:21:62)`,
11 | 'http://localhost:8080/'
12 | )
13 | ).toMatchInlineSnapshot(`
14 | ConsoleError {
15 | "column": 27,
16 | "message": "错误捕获222",
17 | "row": 82,
18 | "stack": "Error: 错误捕获222 at bugConsole (http://localhost:8080/:82:27) at HTMLButtonElement.onclick (http://localhost:8080/:21:62)",
19 | "url": "http://localhost:8080/",
20 | }
21 | `)
22 | })
23 | })
24 |
--------------------------------------------------------------------------------
/monitor-web/.umirc.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'umi';
2 | import routes from './config/routes';
3 |
4 | export default defineConfig({
5 | nodeModulesTransform: {
6 | type: 'none',
7 | },
8 | layout: {
9 | title: '前端监控系统',
10 | },
11 | routes,
12 | fastRefresh: {},
13 | mfsu: {},
14 | extraPostCSSPlugins: [
15 | require("tailwindcss"),
16 | require("autoprefixer"),
17 | // require('postcss-import'),
18 | // require('postcss-nested'), // or require('postcss-nesting')
19 | // require('tailwindcss')({
20 | // config: './tailwind.config.ts',
21 | // }),
22 | ],
23 |
24 | // 在开发模式下,主应用加载微应用的图片等静态资源的代理
25 | proxy: {
26 | '/xx': {
27 | target: 'http://localhost:xxxx',
28 | changeOrigin: true,
29 | },
30 | },
31 | // 开启`qiankun`主应用
32 | qiankun: {
33 | master: {},
34 | },
35 | });
36 |
--------------------------------------------------------------------------------
/monitor-sdk/README.md:
--------------------------------------------------------------------------------
1 | ## Why
2 |
3 | ## Install
4 |
5 | ```shell
6 | npm i monitor-system-sdk --save
7 | ```
8 |
9 | ## Usage
10 |
11 | in index.js
12 |
13 | ```js
14 | import { init } from 'monitor-system-sdk';
15 |
16 | init(initOptions);
17 | ```
18 | ```ts
19 | interface initOptions {
20 | appId?: string; // 系统id
21 | cookie: string; // 用户id
22 | reportUrl: string; // 后端url
23 | delay?: number; // 延迟和合并上报的功能
24 | autoTracker?: boolean; // 自动埋点
25 | hashPage: boolean; // 是否为 hash 路由
26 | errorReport: boolean;// 是否开启错误监控
27 | performanceReport: boolean // 是否开启性能监控
28 | }
29 | ```
30 |
31 | # Function
32 |
33 | - [x] 监控 JavaScript 异常
34 | - [x] 监控 Promise 异常
35 | - [x] 监控 console.error 异常
36 | - [x] 监控 resource 异常
37 | - [x] 监控跨域异常
38 | - [x] 监控白屏异常
39 | - [x] 监控接口异常
40 | - [x] 监控页面路由跳转
41 | - [x] 监控页面性能
42 | - [x] 监控网站信息
43 | - [x] 监控用户行为
44 |
--------------------------------------------------------------------------------
/monitor-node/src/config/swagger.ts:
--------------------------------------------------------------------------------
1 | const Router = require('koa-router');
2 | const path = require('path');
3 | const swaggerJSDoc = require('swagger-jsdoc');
4 |
5 | const swaggerRouter = new Router({
6 | prefix: '/swagger' // 路由前缀
7 | })
8 | const swaggerDefinition = {
9 | info: {
10 | title: 'monitor-system-node API 接口文档',
11 | version: '1.0.0',
12 | description: '做一份详细的接口文档',
13 | },
14 | // openapi: '3.0.1',
15 | }
16 | const options = {
17 | swaggerDefinition,
18 | apis: [path.join(__dirname, '../routes/doc/*.ts')] // 写有注解的router的存放地址,
19 | }
20 | const swaggerSpec = swaggerJSDoc(options);
21 | // 通过路由获取生成的注解文件
22 | swaggerRouter.get('/swagger.json', async function (ctx: { set: (arg0: string, arg1: string) => void; body: any; }) {
23 | ctx.set('Content-Type', 'application/json')
24 | ctx.body = swaggerSpec
25 | })
26 |
27 | export default swaggerRouter;
--------------------------------------------------------------------------------
/monitor-node/src/routes/user/forget.ts:
--------------------------------------------------------------------------------
1 | import { Context } from 'koa';
2 | import { getManager } from "typeorm";
3 | import User from '../../entity/User';
4 | import { ApiResponse } from "../../utils/response";
5 |
6 | export default async (ctx: Context) => {
7 | try{
8 | const { email, captcha, cypher } = ctx.request.body;
9 | const userRepository = getManager().getRepository(User);
10 | const saveUser = await userRepository.findOne({where: { email, captcha }});
11 | if (!saveUser) {
12 | ctx.body = new ApiResponse({
13 | code: 200,
14 | msg: '请输入正确的验证码',
15 | data: {
16 | status: false
17 | }
18 | });
19 | } else {
20 | saveUser.password = cypher;
21 | await userRepository.save(saveUser);
22 | ctx.body = new ApiResponse({
23 | code: 200,
24 | msg: '密码修改成功',
25 | data: {
26 | status: true
27 | }
28 | });
29 | }
30 | } catch{(err: any) => ctx.body = {err}}
31 | }
--------------------------------------------------------------------------------
/monitor-sdk/src/index.ts:
--------------------------------------------------------------------------------
1 | import { loadConfig } from '@monitor/load/loadConfig';
2 | import { historyPageTrack, hashPageTrack } from '@monitor/handle/pageTrack';
3 | import { errorCatch } from '@monitor/handle/errorTrack';
4 | import { getPerformance } from '@monitor/handle/performance';
5 | import { initOptions } from '@type';
6 | import { lazyReport, index } from '@monitor/report';
7 | import { getCache } from '@monitor/handle/cache';
8 |
9 | /**
10 | * 初始化配置
11 | * @param {*} options
12 | */
13 | function init(options: initOptions) {
14 | // ------- 加载配置 ----------
15 | // 1.拿到配置信息
16 | // 2.注入监控代码
17 | loadConfig(options);
18 |
19 | // // -------- uv统计 -----------
20 | // lazyReport('user', '加载应用');
21 | //
22 | // ------ 防止卸载时还有剩余的埋点数据没发送 ------
23 | // window.addEventListener('unload', () => {
24 | // const data = getCache();
25 | // // index(data);
26 | //
27 | // if (data.length > 0) {
28 | // index(data);
29 | // }
30 | // });
31 | }
32 | // errorCatch();
33 | getPerformance();
34 |
35 | export { init, errorCatch };
36 |
--------------------------------------------------------------------------------
/monitor-node/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | // "target": "ES2017",
4 | "target": "es5", /* 解析结果 */
5 | "module": "commonjs", /* 遵循的模块化规范 */
6 | "strict": true,
7 | "esModuleInterop": true, /* 支持以import xx from ‘xx’格式引入 */
8 | "skipLibCheck": true,/* 跳过声明文件的类型检查 */
9 | "forceConsistentCasingInFileNames": true, /* 禁止对同一个文件的不一致的引用 */
10 | "experimentalDecorators": true,
11 | "sourceMap": true,
12 | "watch": true,
13 | // "outDir": "../dist",
14 | "emitDecoratorMetadata": true,
15 | "declaration": true,
16 | "noImplicitAny": false, // 防止对象隐式具有'任何'类型
17 | "strictNullChecks": false,
18 | },
19 | "include": [
20 | "src/**/*",
21 | "./ormconfig.js",
22 | "sendEmail.js",
23 | ],/* 指定需要编译的目录范围 */
24 | "exclude": ["node_modules"], /* 忽略类型检查的目录 */
25 | "baseUrl": "./",
26 | "paths": {
27 | "@*": ["./src/*"],
28 | "@config": ["./src/config"],
29 | "@entity": ["./src/entity"],
30 | "@routes": ["./src/routes"],
31 | "@types": ["./src/types"],
32 | "@utils": ["./src/utils"],
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/monitor-sdk/example/vue3-test-sdk/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue3-test-sdk",
3 | "version": "0.0.0",
4 | "type": "module",
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "run-p type-check build-only",
8 | "preview": "vite preview --port 4173",
9 | "build-only": "vite build",
10 | "type-check": "vue-tsc --noEmit",
11 | "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
12 | },
13 | "dependencies": {
14 | "monitor-system-sdk": "file:../..",
15 | "vue": "^3.2.37",
16 | "vue-router": "^4.1.2"
17 | },
18 | "devDependencies": {
19 | "@rushstack/eslint-patch": "^1.1.0",
20 | "@types/node": "^16.11.45",
21 | "@vitejs/plugin-vue": "^3.0.1",
22 | "@vue/eslint-config-prettier": "^7.0.0",
23 | "@vue/eslint-config-typescript": "^11.0.0",
24 | "@vue/tsconfig": "^0.1.3",
25 | "axios": "^0.27.2",
26 | "eslint": "^8.5.0",
27 | "eslint-plugin-vue": "^9.0.0",
28 | "npm-run-all": "^4.1.5",
29 | "prettier": "^2.5.1",
30 | "typescript": "~4.7.4",
31 | "vite": "^3.0.5",
32 | "vue-tsc": "^0.38.8"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/monitor-sdk/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 foursheep
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 |
23 |
--------------------------------------------------------------------------------
/monitor-sdk/src/monitor/handle/performance/domContentLoadIndicator.ts:
--------------------------------------------------------------------------------
1 | // 获取页面节点加载指标
2 | import {getBasicParams} from "@config/constant";
3 | import {lazyReport} from "@monitor/report/index";
4 | import {performanceIndicatorObj} from "@monitor/handle/performance/index";
5 |
6 | let isDomContentLoaded = false;
7 | export function getDomContentLoadIndicator (performanceIndicator: performanceIndicatorObj) {
8 | const entryHandler = (list: PerformanceObserverEntryList) => {
9 | const entries = list.getEntries() as PerformanceNavigationTiming[];
10 | for (const entry of entries) {
11 | if (entry.name === window.location.href) {
12 | observer.disconnect();
13 | const domContentLoadIndicator = entry.domContentLoadedEventStart;
14 | isDomContentLoaded = true;
15 | console.log('页面节点加载指标', domContentLoadIndicator);
16 | performanceIndicator.DCL = domContentLoadIndicator;
17 | // lazyReport('/dom-content-load-indicator', domContentLoadIndicator);
18 | observer.disconnect();
19 | }
20 | }
21 | };
22 | const observer = new PerformanceObserver(entryHandler);
23 | observer.observe({ type: "navigation", buffered: true });
24 | }
25 |
--------------------------------------------------------------------------------
/monitor-node/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | // 生成 n 位数的包含大小写字母、数字的随机数
2 | export function generateMixed (n: number): string {
3 | let strData = "";
4 | for (let i = 0; i < n; i++) {
5 | const num = Math.floor(Math.random() * 10);
6 | const az = String.fromCharCode(Math.random() * 26 + 65);
7 | const AZ = String.fromCharCode(Math.random() * 26 + 97);
8 | strData += num + az + AZ;
9 | }
10 | return strData;
11 | }
12 |
13 | // 获取当前时间戳:毫秒格式 或者 hh:mm:ss格式
14 | // 不带参数是毫秒格式,否则为 hh:mm:ss 格式
15 | export function nowTime(data?: { click: boolean }): number | string {
16 | if (data && data.click && data.click === true) {
17 | const time = new Date();
18 | const hour = time.getHours();
19 | const minute = time.getMinutes();
20 | const second = time.getSeconds();
21 | const timer = hour + ':' + minute + ':' + second + ' ';
22 | return timer;
23 | }
24 | const time = new Date().getTime();
25 | return time;
26 | }
27 |
28 | // 生成 6 位数字的验证码
29 | export function createSixNum(): number {
30 | let num = "";
31 | for(let i = 0; i < 6; i++) {
32 | num += Math.floor(Math.random() * 10);
33 | }
34 | return +num;
35 | }
--------------------------------------------------------------------------------
/monitor-node/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "server",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "build": "tsc",
9 | "start": "tsc dist/index.ts",
10 | "dev": "nodemon --watch src -e ts,tsx --exec ts-node src/app.ts"
11 | },
12 | "keywords": [],
13 | "author": "",
14 | "license": "ISC",
15 | "dependencies": {
16 | "@influxdata/influxdb-client": "^1.28.0",
17 | "@influxdata/influxdb-client-apis": "^1.28.0",
18 | "@types/koa": "^2.13.5",
19 | "@types/koa-bodyparser": "^4.3.7",
20 | "@types/koa-cors": "^0.0.2",
21 | "@types/koa-router": "^7.4.4",
22 | "@types/koa2-cors": "^2.0.2",
23 | "@types/node": "^18.7.5",
24 | "co-body": "^6.1.0",
25 | "influx": "^5.9.3",
26 | "koa": "^2.13.4",
27 | "koa-bodyparser": "^4.3.0",
28 | "koa-cors": "^0.0.16",
29 | "koa-router": "^12.0.0",
30 | "koa2-cors": "^2.0.6",
31 | "koa2-swagger-ui": "^5.6.0",
32 | "mysql": "^2.18.1",
33 | "nodemailer": "^6.7.8",
34 | "swagger-jsdoc": "^6.2.5",
35 | "typeorm": "^0.3.7"
36 | },
37 | "devDependencies": {
38 | "nodemon": "^2.0.19",
39 | "ts-node": "^10.9.1",
40 | "typescript": "^4.7.4"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/monitor-node/src/routes/user/register.ts:
--------------------------------------------------------------------------------
1 | import { Context } from 'koa';
2 | import { getManager } from "typeorm";
3 | import User from '../../entity/User';
4 | import { ApiResponse } from "../../utils/response";
5 | import { nowTime } from '../../utils/index';
6 |
7 | export default async (ctx: Context) => {
8 | const { email, password, captcha } = ctx.request.body;
9 | const userRepository = getManager().getRepository(User);
10 | const saveUsers = await userRepository.findOne({where: { email }});
11 | const doneUser = await userRepository.findOne({where: { email, captcha: +captcha, password }});
12 | const data = { status: false };
13 |
14 | let msg: string = '';
15 | if (doneUser) {
16 | msg = '邮箱已存在,请注册新的邮箱';
17 | } else if (saveUsers && +captcha !== saveUsers.captcha) {
18 | msg = '验证码输入错误,请重新输入';
19 | } else if (saveUsers && +captcha === saveUsers.captcha) {
20 | const judgeTime = +saveUsers?.runtime_captcha + 5 * 60 * 1000;
21 | const nowtime = nowTime();
22 | if (nowtime < judgeTime) {
23 | saveUsers.password = password;
24 | await userRepository.save(saveUsers);
25 |
26 | data.status = true;
27 | msg = '注册成功';
28 | } else {
29 | msg = '验证码有效期已到,请重新获取';
30 | }
31 | }
32 |
33 | ctx.body = new ApiResponse({
34 | code: 200,
35 | msg,
36 | data
37 | });
38 | }
--------------------------------------------------------------------------------
/monitor-sdk/js/type/index.d.ts:
--------------------------------------------------------------------------------
1 | import { IResult } from "ua-parser-js";
2 | export interface initOptions {
3 | appId: string;
4 | userId: string;
5 | reportUrl: string;
6 | delay: number;
7 | autoTracker: boolean;
8 | hashPage: boolean;
9 | errorReport: boolean;
10 | }
11 | export interface PageInformation {
12 | host: string;
13 | hostname: string;
14 | href: string;
15 | protocol: string;
16 | origin: string;
17 | port: string;
18 | pathname: string;
19 | search: string;
20 | hash: string;
21 | title: string;
22 | language: string;
23 | userAgent: string | IResult;
24 | winScreen: string;
25 | docScreen: string;
26 | }
27 | export declare enum mechanismType {
28 | JS = "js",
29 | RS = "resource",
30 | UJ = "unhandledrejection",
31 | HP = "http",
32 | CS = "cors",
33 | VUE = "vue"
34 | }
35 | export declare enum metricsName {
36 | PI = "page-information",
37 | OI = "origin-information",
38 | RCR = "router-change-record",
39 | CBR = "click-behavior-record",
40 | CDR = "custom-define-record",
41 | HT = "http-record"
42 | }
43 | export interface HttpMetrics {
44 | method: string;
45 | url: string | URL;
46 | body: Document | XMLHttpRequestBodyInit | null | undefined | ReadableStream;
47 | requestTime: number;
48 | responseTime: number;
49 | status: number;
50 | statusText: string;
51 | response?: any;
52 | }
53 |
--------------------------------------------------------------------------------
/monitor-web/src/api/index.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { REQUESTIP } from '@/constants';
3 |
4 | axios.defaults.withCredentials = true;
5 |
6 | // Add a request interceptor
7 | axios.interceptors.request.use(function (config) {
8 | // Do something before request is sent
9 | return config;
10 | }, function (error) {
11 | // Do something with request error
12 | return Promise.reject(error);
13 | });
14 |
15 | // Add a response interceptor
16 | axios.interceptors.response.use(function (response) {
17 | // Any status code that lie within the range of 2xx cause this function to trigger
18 | // Do something with response data
19 | return response;
20 | }, function (error) {
21 | // Any status codes that falls outside the range of 2xx cause this function to trigger
22 | // Do something with response error
23 | return Promise.reject(error);
24 | });
25 |
26 | export function generateHttpApi(method: 'get' | 'post') {
27 | return async (url: string, params?: any) => {
28 | const data = method === 'get' ? {
29 | params
30 | } : {
31 | data: params
32 | };
33 | url = REQUESTIP + url;
34 | try {
35 | const response = await axios({
36 | url,
37 | method,
38 | ...data,
39 | });
40 | return response.data;
41 | } catch (error) {
42 | return await Promise.reject(error);
43 | }
44 | }
45 | }
46 |
47 | export const get = generateHttpApi('get');
48 | export const post = generateHttpApi('post');
49 |
--------------------------------------------------------------------------------
/monitor-node/src/routes/user/login.ts:
--------------------------------------------------------------------------------
1 | import { Context } from 'koa';
2 | import { getManager } from "typeorm";
3 | import { generateMixed } from '../../utils';
4 | import User from '../../entity/User';
5 | import { ApiResponse } from "../../utils/response";
6 |
7 | export default async (ctx: Context) => {
8 | const { email, password, cookie } = ctx.request.body;
9 | const userRepository = getManager().getRepository(User);
10 | const saveUsers = await userRepository.findOne({ where: { email: email }});
11 | let code: number = 0;
12 | let msg: string = '';
13 | let data = { isLogin: false };
14 |
15 | if ((!saveUsers || !saveUsers.password) && !cookie) {
16 | code = 0;
17 | msg = '你还未注册,请先注册';
18 | } else if (saveUsers && !cookie) {
19 | if (password === saveUsers.password) {
20 | // 设置20位数的 session 随机数,同时和查找到的用户信息一并存进数据库中
21 | const session = generateMixed(20)
22 | saveUsers.session = session;
23 | await userRepository.save(saveUsers)
24 | // 设置cookie
25 | ctx.cookies.set(
26 | 'session', session, { httpOnly: false, maxAge: 1296000000 }
27 | )
28 |
29 | data.isLogin = true;
30 | code = 200;
31 | msg = '登录成功';
32 | } else {
33 | code = 0;
34 | msg = '邮箱账号或密码输入错误';
35 | }
36 | } else if (!saveUsers && cookie) {
37 | const findCookieUser = await userRepository.findOne({ where: { session: cookie }});
38 | if (findCookieUser) {
39 | data.isLogin = true;
40 | code = 200;
41 | msg = '你已处于登录状态';
42 | }
43 | }
44 | ctx.body = new ApiResponse({
45 | code, msg, data
46 | })
47 | }
--------------------------------------------------------------------------------
/monitor-sdk/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2015", //用来指定ts被编译为js的版本
4 | "module": "ESNext", //指定要使用模块化的规范
5 | "moduleResolution": "node",
6 | "outDir": "./dist/",
7 | "experimentalDecorators": true,
8 | "allowSyntheticDefaultImports": true,
9 | "sourceMap": true,
10 | "strict": true, //检查严格模式总开关
11 | "strictNullChecks": false, //严格检查空值
12 | "noImplicitAny": false, //不允许隐式的any类型
13 | "noImplicitThis": false, //不允许明类型的this
14 | "alwaysStrict": false, //编译后是否使用严格模式
15 | "allowJs": false, //是否对js文件进行编译 默认是false
16 | "checkJs": false, //检查js代码是否符合语法规范 默认是false
17 | "declaration": true,
18 | "declarationMap": false,
19 | "declarationDir": "./dist/types", // 声明文件打包的位置
20 | "removeComments": false, //编译成js后是否移除注释
21 | "noImplicitReturns": true,
22 | "noEmit": false, //不生成编译后的文件
23 | "noEmitOnError": false, //有错误时不生成编译文件
24 | "importHelpers": true,
25 | "lib": ["es5", "dom"], //指定项目中所用到的库
26 | "typeRoots": ["node_modules/@types"],
27 | // "outDir": "./js", //用来指定编译后文件所在路径
28 | // "rootDir": "./src",
29 | "baseUrl": "./",
30 | "paths": {
31 | "@*": ["./src/*"],
32 | "@config": ["./src/config"],
33 | "@monitor": ["./src/monitor"],
34 | "@type": ["./src/type"],
35 | "@util": ["./src/util"],
36 | }
37 | // "types": ["vitest/global"]
38 | // "outFile": "./js/main.js" //将代码合并成一个文件,模块化规范需要为amd或者system
39 | },
40 | "include": ["./src/**/*.ts", "./src/**/*.js"], //被编译的文件目录
41 | "files": ["src/index.ts"],
42 | "exclude": ["node_modules", "dist", "**/*.spec.ts"] //不希望被编译的文件
43 | }
44 |
--------------------------------------------------------------------------------
/monitor-node/src/routes/error/promise/find.ts:
--------------------------------------------------------------------------------
1 | import { Context } from 'koa';
2 | import find from "../../../utils/middleware/find";
3 |
4 | export default async(ctx: Context) => {
5 | const bucket = 'promiseError';
6 | return ctx.body = await find({ bucket, queryTime: (ctx.query.time) as string });
7 | }
8 |
9 | // const chalk = require('chalk');
10 | // var asciichart = require ('asciichart');
11 | // const maxLength = 100;
12 | // const bots = [];
13 | // const humans = [];
14 | // var config = {
15 | // height: 18, // any height you want
16 | // colors: [
17 | // asciichart.blue,
18 | // asciichart.red,
19 | // asciichart.default, // default color
20 | // undefined, // equivalent to default
21 | // ]
22 | // }
23 | //
24 | // function pushRow(row) {
25 | // if (bots.length >= maxLength) {
26 | // bots.shift ();
27 | // }
28 | // if (humans.length >= maxLength) {
29 | // humans.shift ();
30 | // }
31 | //
32 | // row.isBot == 'true' ? bots.push( row['_value']) : humans.push( row['_value']);
33 | // }
34 | //
35 | // function render(){
36 | // if(bots.length != 0 && humans.length != 0) {
37 | // const plt = asciichart.plot ([bots,humans], config).split ('\n');
38 | // chart.setLine (0, chalk.blue('Bots: '+bots[bots.length-1]) + ' ' + chalk.red('Humans: '+humans[humans.length-1]));
39 | // plt.forEach ((line, i) => {
40 | // chart.setLine (i + 1, line);
41 | // });
42 | // }
43 | // screen.render();
44 | // }
45 | //
46 | // function sleep(ms) {
47 | // return new Promise(resolve => setTimeout(resolve, ms));
48 | // }
49 |
--------------------------------------------------------------------------------
/monitor-sdk/example/vue3-test-sdk/src/api/index.ts:
--------------------------------------------------------------------------------
1 | import axios, { AxiosResponse } from "axios";
2 | import { REQUESTIP } from "@/common/const";
3 |
4 | axios.defaults.withCredentials = true;
5 |
6 | // axios.interceptors.request.use(function (config) {
7 | //
8 | // return config;
9 | // }, function (error) {
10 | //
11 | // return Promise.reject(error);
12 | // });
13 | //
14 | // axios.interceptors.response.use(
15 | // async (response: AxiosResponse) => {
16 | // return response.data;
17 | // },
18 | // (error: any) => {
19 | // // 接口有响应,但是返回错误
20 | // if (error.response) {
21 | // // 有响应,首先获取状态码,根据状态码来判断什么时候需要收集异常
22 | // let response = error.response;
23 | // if (response.status >= 400) {
24 | //
25 | // }
26 | // }
27 | // // 接口没响应,请求一直挂起,多数是接口崩溃了
28 | // else {
29 | //
30 | // }
31 | // return Promise.reject(error);
32 | // }
33 | // );
34 |
35 | export function generateHttpApi(method: "get" | "post") {
36 | return async (url: string, params?: any) => {
37 | const data =
38 | method === "get"
39 | ? {
40 | params,
41 | }
42 | : {
43 | data: params,
44 | };
45 | url = REQUESTIP + url;
46 | try {
47 | const response = await axios({
48 | url,
49 | method,
50 | ...data,
51 | });
52 | return response.data;
53 | } catch (error) {
54 | return await Promise.reject(error);
55 | }
56 | };
57 | }
58 |
59 | export const get = generateHttpApi("get");
60 | export const post = generateHttpApi("post");
61 |
--------------------------------------------------------------------------------
/monitor-web/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "start": "umi dev",
5 | "build": "umi build",
6 | "postinstall": "umi generate tmp",
7 | "prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'",
8 | "test": "umi-test",
9 | "test:coverage": "umi-test --coverage"
10 | },
11 | "gitHooks": {
12 | "pre-commit": "lint-staged"
13 | },
14 | "lint-staged": {
15 | "*.{js,jsx,less,md,json}": [
16 | "prettier --write"
17 | ],
18 | "*.ts?(x)": [
19 | "prettier --parser=typescript --write"
20 | ]
21 | },
22 | "dependencies": {
23 | "@ant-design/charts": "^1.4.2",
24 | "@ant-design/icons": "^4.7.0",
25 | "@ant-design/pro-components": "^2.3.4",
26 | "@ant-design/pro-layout": "^6.38.17",
27 | "@craco/craco": "^6.4.5",
28 | "@umijs/plugin-qiankun": "^2.40.0",
29 | "add": "^2.0.6",
30 | "axios": "^0.27.2",
31 | "chart.js": "^3.9.1",
32 | "qiankun": "^2.8.0",
33 | "react": "17.x",
34 | "react-dom": "17.x",
35 | "react-tooltip": "^4.2.21",
36 | "recharts": "^2.1.14",
37 | "tailwind": "^4.0.0",
38 | "umi": "^3.5.32",
39 | "yarn": "^1.22.19"
40 | },
41 | "devDependencies": {
42 | "@tailwindcss/postcss7-compat": "^2.2.17",
43 | "@types/react": "^17.0.0",
44 | "@types/react-dom": "^17.0.0",
45 | "@umijs/preset-react": "1.x",
46 | "@umijs/test": "^3.5.32",
47 | "autoprefixer": "^9",
48 | "lint-staged": "^10.0.7",
49 | "postcss": "^7",
50 | "postcss-import": "^15.0.0",
51 | "postcss-nested": "^5.0.6",
52 | "prettier": "^2.2.0",
53 | "tailwindcss": "npm:@tailwindcss/postcss7-compat",
54 | "typescript": "^4.1.2",
55 | "yorkie": "^2.0.0"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/monitor-sdk/src/monitor/handle/performance/resourceIndicator.ts:
--------------------------------------------------------------------------------
1 | // 获取页面资源指标
2 | import {getBasicParams} from "@config/constant";
3 | import {lazyReport} from "@monitor/report/index";
4 | import {performanceIndicatorObj} from "@monitor/handle/performance/index";
5 |
6 | export function getResourceIndicator (performanceIndicator: performanceIndicatorObj) {
7 | const entryHandler = (list: PerformanceObserverEntryList) => {
8 | for (const entry of list.getEntries() as PerformanceResourceTiming[]) {
9 | if (entry.initiatorType !== "xmlhttprequest") {
10 | console.log('@@@###', entry)
11 | const resourceIndicator = {
12 | ...getBasicParams,
13 | url: entry.name, // 资源url
14 | duration: entry.duration, // 资源加载耗时
15 | dns: entry.domainLookupEnd - entry.domainLookupStart, // DNS 耗时
16 | tcp: entry.connectEnd - entry.connectStart, // 建立 tcp 连接耗时
17 | redirect: entry.redirectEnd - entry.redirectStart, // 重定向耗时
18 | ttfb: entry.responseStart, // 首字节时间
19 | protocol: entry.nextHopProtocol, // 请求协议
20 | resourceSize: entry.decodedBodySize, // 资源解压后的大小
21 | isCache:
22 | entry.transferSize === 0 ||
23 | (entry.transferSize !== 0 && entry.encodedBodySize === 0), // 是否命中缓存
24 | bodySize: entry.transferSize, // 资源大小
25 | headerSize: entry.transferSize - entry.encodedBodySize, // 资源头部大小
26 | };
27 | console.log('资源指标', resourceIndicator);
28 | lazyReport('/resource-indicator', resourceIndicator);
29 | observer.disconnect();
30 | }
31 | }
32 | // lazyReport("/resource-indicator", resourceIndicator);
33 | };
34 | const observer = new PerformanceObserver(entryHandler);
35 | observer.observe({ type: "resource", buffered: true });
36 | }
37 |
--------------------------------------------------------------------------------
/monitor-sdk/example/vue3-test-sdk/README.md:
--------------------------------------------------------------------------------
1 | # vue3-test-sdk
2 |
3 | This template should help get you started developing with Vue 3 in Vite.
4 |
5 | ## Recommended IDE Setup
6 |
7 | [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
8 |
9 | ## Type Support for `.vue` Imports in TS
10 |
11 | TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
12 |
13 | If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
14 |
15 | 1. Disable the built-in TypeScript Extension
16 | 1) Run `Extensions: Show Built-in Extensions` from VSCode's command palette
17 | 2) Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
18 | 2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.
19 |
20 | ## Customize configuration
21 |
22 | See [Vite Configuration Reference](https://vitejs.dev/config/).
23 |
24 | ## Project Setup
25 |
26 | ```sh
27 | npm install
28 | ```
29 |
30 | ### Compile and Hot-Reload for Development
31 |
32 | ```sh
33 | npm run dev
34 | ```
35 |
36 | ### Type-Check, Compile and Minify for Production
37 |
38 | ```sh
39 | npm run build
40 | ```
41 |
42 | ### Lint with [ESLint](https://eslint.org/)
43 |
44 | ```sh
45 | npm run lint
46 | ```
47 |
--------------------------------------------------------------------------------
/monitor-sdk/src/monitor/load/loadConfig.ts:
--------------------------------------------------------------------------------
1 | import { errorCatch } from '@monitor/handle/errorTrack';
2 | import { initOptions } from "@type/index";
3 | import { hashPageTrack, historyPageTrack } from "@monitor/handle/pageTrack";
4 | import { getPerformance } from "@monitor/handle/performance";
5 | import { blankScreen } from "@monitor/handle/baseHandler";
6 |
7 | /**
8 | * 加载配置
9 | * @param {*} options
10 | */
11 | export function loadConfig(options: initOptions): void {
12 | const {
13 | appId, // 系统id
14 | cookie, // 用户id
15 | reportUrl, // 后端url
16 | autoTracker, // 自动埋点
17 | delay, // 延迟和合并上报的功能
18 | hashPage, // 是否hash录有
19 | errorReport, // 是否开启错误监控
20 | performanceReport // 是否开启性能监控
21 | } = options;
22 |
23 | // --------- appId ----------------
24 | // if (appId) {
25 | // window['_monitor_app_id_'] = appId;
26 | // }
27 | //
28 | // --------- userId ----------------
29 | if (cookie) {
30 | window['_monitor_user_cookie_'] = cookie;
31 | }
32 |
33 | // --------- 服务端地址 ----------------
34 | if (reportUrl) {
35 | window['_monitor_report_url_'] = reportUrl;
36 | }
37 |
38 | // -------- 合并上报的间隔 ------------
39 | if (delay) {
40 | window['_monitor_delay_'] = delay;
41 | }
42 |
43 | // --------- 是否开启错误监控和白屏监控 ------------
44 | if (errorReport) {
45 | errorCatch();
46 | blankScreen();
47 | }
48 | //
49 | // // --------- 是否开启无痕埋点 ----------
50 | // if (autoTracker) {
51 | // autoTrackerReport();
52 | // }
53 |
54 | // ----------- 路由监听 --------------
55 | if (hashPage) {
56 | hashPageTrack(); // hash路由上报
57 | } else {
58 | historyPageTrack(); // history路由上报
59 | }
60 |
61 | // ----------- 性能监控 --------------
62 | if (performanceReport) {
63 | getPerformance();
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/monitor-sdk/src/monitor/class/CorsError.ts:
--------------------------------------------------------------------------------
1 | export class CorsError {
2 |
3 | private _errorType;
4 | private _type;
5 | private _message;
6 | private _url;
7 | private _method;
8 | private _status;
9 | private _response;
10 | private _request;
11 | private _params;
12 |
13 | constructor(errorType, type, message, url, method, status, response, request, params) {
14 | this._errorType = errorType;
15 | this._type = type;
16 | this._url = url;
17 | this._message = message;
18 | this._method = method;
19 | this._status = status;
20 | this._response = response;
21 | this._request = request;
22 | this._params = params;
23 | }
24 |
25 | public get errorType() {
26 | return this._errorType;
27 | }
28 | public set errorType(value) {
29 | this._errorType = value;
30 | }
31 | public get type() {
32 | return this._type;
33 | }
34 | public set type(value) {
35 | this._type = value;
36 | }
37 | public get message() {
38 | return this._message;
39 | }
40 | public set message(value) {
41 | this._message = value;
42 | }
43 | public get url() {
44 | return this._url;
45 | }
46 | public set url(value) {
47 | this._url = value;
48 | }
49 | public get method() {
50 | return this._method;
51 | }
52 | public set method(value) {
53 | this._method = value;
54 | }
55 | public get status() {
56 | return this._status;
57 | }
58 | public set status(value) {
59 | this._status = value;
60 | }
61 | public get response() {
62 | return this._response;
63 | }
64 | public set response(value) {
65 | this._response = value;
66 | }
67 | public get request() {
68 | return this._request;
69 | }
70 | public set request(value) {
71 | this._request = value;
72 | }
73 | public get params() {
74 | return this._params;
75 | }
76 | public set params(value) {
77 | this._params = value;
78 | }
79 |
80 | }
81 |
--------------------------------------------------------------------------------
/monitor-web/config/routes.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 参考配置:https://pro.ant.design/zh-CN/docs/new-page
3 | * name:string 配置菜单的 name,如果配置了国际化,name 为国际化的 key。
4 | * icon:string 配置菜单的图标,默认使用 antd 的 icon 名,默认不适用二级菜单的 icon。
5 | * access:string 权限配置,需要预先配置权限
6 | * hideChildrenInMenu:true 用于隐藏不需要在菜单中展示的子路由。
7 | * hideInMenu:true 可以在菜单中不展示这个路由,包括子路由。
8 | * hideInBreadcrumb:true 可以在面包屑中不展示这个路由,包括子路由。
9 | * headerRender:false 当前路由不展示顶栏
10 | * footerRender:false 当前路由不展示页脚
11 | * menuRender: false 当前路由不展示菜单
12 | * menuHeaderRender: false 当前路由不展示菜单顶栏
13 | * flatMenu 子项往上提,只是不展示父菜单
14 | */
15 | const routes = [
16 | {
17 | name: "react子应用",
18 | path: "/qiankun-umi",
19 | icon: 'smile',
20 | microApp: "qiankun-umi",
21 | },
22 | {
23 | name: 'umi子应用',
24 | path: '/micro-umi',
25 | icon: 'smile',
26 | microApp: 'micro-umi'
27 | },
28 | {
29 | name: 'vue子应用',
30 | path: '/micro-vue',
31 | icon: 'smile',
32 | microApp: 'micro-vue'
33 | },
34 |
35 | {
36 | path: '/',
37 | component: '@/index.tsx',
38 | },
39 | {
40 | path: '/overview',
41 | name: '概览',
42 | icon: 'AppstoreOutlined',
43 | component: '@/pages/Overview'
44 | },
45 | {
46 | name: '异常监控',
47 | path: '/error',
48 | icon: 'BugOutlined',
49 | component: '@/pages/Error',
50 | },
51 | {
52 | name: 'js错误',
53 | path: '/error/js',
54 | component: '@/pages/Error/js',
55 | hideInMenu: 'true',
56 | },
57 | {
58 | name: '性能监控',
59 | path: '/performance',
60 | icon: 'AreaChartOutlined',
61 | component: './Performance'
62 | },
63 | {
64 | name: '行为监控',
65 | path: '/action',
66 | icon: 'EyeOutlined',
67 | routes: [
68 | {
69 | path: '/action/hash',
70 | name: 'hash 路由',
71 | // component: ''
72 | },
73 | {
74 | path: '/action/history',
75 | name: 'history 路由',
76 | // component: ''
77 | }
78 | ]
79 | },
80 | ];
81 | export default routes;
82 |
--------------------------------------------------------------------------------
/monitor-sdk/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "monitor-system-sdk",
3 | "version": "1.0.3",
4 | "description": "前端监控 SDK",
5 | "author": "foursheep",
6 | "main": "lib/monitor.cjs.js",
7 | "module": "lib/monitor.esm.js",
8 | "scripts": {
9 | "dev": "webpack-dev-server --config webpack.config.js",
10 | "build": "webpack --mode=production --node-env=production",
11 | "build:rollup": "rollup -c rollup.config.js --source",
12 | "test": "vitest"
13 | },
14 | "keywords": [
15 | "前端监控系统",
16 | "原生 sdk"
17 | ],
18 | "private": false,
19 | "license": "MIT",
20 | "files": [
21 | "lib"
22 | ],
23 | "repository": {
24 | "type": "git",
25 | "url": "git@github.com:HCYETY/monitor-system.git"
26 | },
27 | "devDependencies": {
28 | "@babel/core": "^7.18.10",
29 | "@rollup/plugin-typescript": "^8.3.4",
30 | "autoprefixer": "^10.4.8",
31 | "babel-loader": "^8.2.5",
32 | "babel-plugin-import": "^1.13.5",
33 | "bowser": "^2.11.0",
34 | "clean-webpack-plugin": "^4.0.0",
35 | "css-loader": "^6.7.1",
36 | "cssnano": "^5.1.12",
37 | "file-loader": "^6.2.0",
38 | "fs": "^0.0.1-security",
39 | "html-webpack-plugin": "^5.5.0",
40 | "http-proxy-middleware": "^2.0.6",
41 | "less": "^4.1.3",
42 | "less-loader": "^11.0.0",
43 | "mini-css-extract-plugin": "^2.6.1",
44 | "rollup": "^2.78.1",
45 | "rollup-plugin-sourcemaps": "^0.6.3",
46 | "ts-loader": "^9.3.1",
47 | "tslib": "^2.4.0",
48 | "typescript": "^4.7.4",
49 | "url-loader": "^4.1.1",
50 | "user-agent": "^1.0.4",
51 | "vitest": "^0.22.1",
52 | "webpack": "^5.74.0",
53 | "webpack-bundle-analyzer": "^4.5.0",
54 | "webpack-cli": "^4.10.0",
55 | "webpack-deep-scope-plugin": "^1.6.2",
56 | "webpack-dev-server": "^4.9.3",
57 | "webpack-merge": "^5.8.0"
58 | },
59 | "dependencies": {
60 | "@babel/preset-env": "^7.18.10",
61 | "@types/ua-parser-js": "^0.7.36",
62 | "axios": "^0.27.2",
63 | "source-map": "^0.7.4",
64 | "ua-parser-js": "^1.0.2"
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/monitor-sdk/src/monitor/report/index.ts:
--------------------------------------------------------------------------------
1 | import { addCache, getCache, clearCache } from "../handle/cache";
2 |
3 | let timer = null;
4 |
5 | export function lazyReport(interfaceUrl: string, param): void {
6 | const cookie: string = window['_monitor_user_cookie_'];
7 | const delay: number = window['_monitor_delay_'];
8 |
9 | const logParams = {
10 | cookie, // 用户id
11 | interfaceUrl, // error/action/visit/user
12 | data: param, // 上报的数据
13 | currentTime: new Date().getTime(),
14 | currentPage: window.location.href,
15 | ua: window.navigator.userAgent,
16 | }
17 |
18 | // let logParamsString = JSON.stringify(logParams);
19 | // addCache(logParamsString);
20 |
21 | // const data = getCache();
22 | // let data = JSON.stringify(param);
23 |
24 | // if (delay === 0) {
25 | return index(interfaceUrl, logParams);
26 | // }
27 |
28 | // if (data.length > 10) {
29 | // index(interfaceUrl, data);
30 | // clearTimeout(timer);
31 | // return;
32 | // }
33 |
34 | // clearTimeout(timer);
35 | //
36 | // timer = setTimeout(() => {
37 | // index(interfaceUrl, logParams);
38 | // }, delay);
39 | }
40 |
41 | export function index(interfaceUrl: string, data: object): void {
42 | const url = window['_monitor_report_url_'];
43 |
44 | // ------- fetch方式上报 -------
45 | // 跨域问题
46 | // fetch(url, {
47 | // method: 'POST',
48 | // body: JSON.stringify(data),
49 | // headers: {
50 | // 'Content-Type': 'application/json',
51 | // },
52 | // }).then(res => {
53 | // console.log(res);
54 | // }).catch(err => {
55 | // console.error(err);
56 | // })
57 |
58 | // ------- navigator/img方式上报 -------
59 | // 不会有跨域问题
60 | if (navigator.sendBeacon) { // 支持sendBeacon的浏览器
61 | navigator.sendBeacon(url + interfaceUrl, JSON.stringify(data));
62 | } else { // 不支持sendBeacon的浏览器
63 | let oImage = new Image();
64 | oImage.src = `${url}${interfaceUrl}?logs=${data}`;
65 | }
66 | // clearCache();
67 | }
68 |
--------------------------------------------------------------------------------
/monitor-node/src/routes/user/sendCaptcha.ts:
--------------------------------------------------------------------------------
1 | import { Context } from 'koa';
2 | import sendEmail from '../../../sendEmail.js';
3 | import { createSixNum } from '../../utils/index';
4 | import { getManager } from "typeorm";
5 | import User from '../../entity/User';
6 | import { ApiResponse } from "../../utils/response";
7 |
8 | export default async (ctx:Context) => {
9 | try{
10 | const { email, password } = ctx.request.body;
11 | console.log(email)
12 | const userRepository = getManager().getRepository(User);
13 | const saveUsers = await userRepository.findOne({where: { email, password }});
14 | // const nowtime = nowTime();
15 | if (saveUsers) {
16 | ctx.body = new ApiResponse({
17 | code: 200,
18 | msg: '该邮箱已注册,可直接登录',
19 | data: { status: false }
20 | });
21 | } else {
22 | const code = createSixNum();
23 | const saveUser = await userRepository.findOne({ where: { email }});
24 | const nowtime = new Date().getTime();
25 | const mail = {
26 | from: '1164939253@qq.com',
27 | to: email,
28 | subject: '您注册账号的验证码为',
29 | text:'您的验证码为' + code + ',有效期为五分钟!'
30 | };
31 | sendEmail(mail);
32 | if (saveUser) {
33 | saveUser.captcha = code;
34 | saveUser.runtime_captcha = nowtime;
35 | await userRepository.save(saveUser);
36 | } else {
37 | const newUser = new User();
38 | newUser.email = email;
39 | newUser.captcha = code;
40 | newUser.runtime_captcha = nowtime;
41 | await userRepository.save(newUser);
42 | }
43 |
44 | ctx.body = new ApiResponse({
45 | code: 200,
46 | msg: '邮箱验证码已发送,请注意在有效期内输入',
47 | data: {status: true, captchaTime: nowtime}
48 | });
49 | }
50 | } catch{(err: any) => ctx.body = {err}}
51 | }
--------------------------------------------------------------------------------
/monitor-node/src/utils/middleware/find.ts:
--------------------------------------------------------------------------------
1 | import { queryApi } from '../../utils/env';
2 | import { ApiResponse } from "../../utils/response";
3 | import {ParsedUrlQuery} from "querystring";
4 |
5 | export default async function find(params: { bucket: string, queryTime?: string, query?: string }) {
6 | return await new Promise((resolve, reject) => {
7 | let res: any = null;
8 | let arr: any[] = [];
9 | const { bucket, queryTime, query } = params;
10 | let time: string = queryTime ? queryTime : '-30d';
11 | const fluxQuery = query ? query : `from(bucket: "${bucket}")
12 | |> range(start: ${time})
13 | `;
14 | // |> filter(fn: (r) => r._measurement == "${cookie}")
15 | // |> filter(fn: (r) => r._field == "sss")
16 |
17 | queryApi.queryRows(fluxQuery, {
18 | next(row: any, tableMeta: { toObject: (arg0: any) => any }) {
19 | const o = tableMeta.toObject(row)
20 | arr.push(o);
21 | if (arr.length > 0) {
22 | res = new ApiResponse({
23 | code: 200,
24 | msg: '数据查询成功',
25 | data: {
26 | status: true,
27 | response: arr
28 | }
29 | })
30 | console.log(`${bucket}查询成功`)
31 | resolve(res);
32 | }
33 | },
34 | error() {
35 | res = new ApiResponse({
36 | code: 500,
37 | msg: '数据查询失败',
38 | data: {
39 | status: false,
40 | }
41 | })
42 | reject(res);
43 | },
44 | complete() {
45 | if (arr.length <= 0) {
46 | res = new ApiResponse({
47 | code: 0,
48 | msg: '查无此数据',
49 | data: {
50 | status: false,
51 | }
52 | })
53 | reject(res);
54 | }
55 | }
56 | })
57 | })
58 | }
59 |
--------------------------------------------------------------------------------
/monitor-node/src/utils/middleware/add.ts:
--------------------------------------------------------------------------------
1 | import { Point } from '@influxdata/influxdb-client';
2 | import { clientDB, INFLUX_ORG } from '../env';
3 | import { ApiResponse } from "../response";
4 | import coBody from 'co-body';
5 |
6 | export default async function add (paramObj: { bucket: string, request: object }) {
7 | const { bucket, request } = paramObj;
8 |
9 | try {
10 | const params = await coBody.json(request);
11 | const { cookie, data, inquiry } = params;
12 | const keyArr = Object.keys(data);
13 | const writeApi = clientDB.getWriteApi(INFLUX_ORG, bucket);
14 | // writeApi.useDefaultTags({host: 'host1'});
15 | // 将前端传来的数据存进时序数据库
16 | const point = new Point(cookie);
17 | // const point = new Point(cookie).tag('sensor_id', 'TLM010');
18 | keyArr.forEach((key: string) => {
19 | let type = typeof data[key];
20 | if (type === 'number') {
21 | type = (Number.isInteger(data[key]) === true) ? 'number' : 'bigint';
22 | }
23 | console.log('type2', type, data[key])
24 | switch (type) {
25 | case 'string':
26 | point.stringField(key, data[key]);
27 | break;
28 | case 'number':
29 | point.intField(key, data[key]);
30 | break;
31 | case 'bigint':
32 | point.floatField(key, data[key]);
33 | break;
34 | case 'boolean':
35 | point.booleanField(key, data[key]);
36 | break;
37 | case 'object':
38 | const str = JSON.stringify(data[key]);
39 | point.stringField(key, str);
40 | break;
41 | }
42 | })
43 | writeApi.writePoint(point);
44 | await writeApi.close();
45 |
46 | return new ApiResponse({
47 | code: 200,
48 | msg: '数据写入成功',
49 | data: {
50 | status: true,
51 | }
52 | })
53 | } catch (err) {
54 | return new ApiResponse({
55 | code: 500,
56 | msg: '数据写入失败',
57 | data: {
58 | status: false,
59 | }
60 | })
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/monitor-sdk/src/monitor/handle/performance/index.ts:
--------------------------------------------------------------------------------
1 | import { lazyReport } from "@monitor/report/index";
2 | import { getBasicIndicator } from "@monitor/handle/performance/basicIndicator";
3 | import { getResourceIndicator } from "@monitor/handle/performance/resourceIndicator";
4 | import { getDomContentLoadIndicator } from "@monitor/handle/performance/domContentLoadIndicator";
5 | import { getFirstScreenIndicator } from "@monitor/handle/performance/firstMeaningfulPaintIndicator";
6 | import {userInform} from "@config";
7 |
8 | export interface performanceIndicatorObj {
9 | FP: number,
10 | FCP: number,
11 | LCP: number,
12 | FID: number,
13 | CLS: number,
14 | FMP: number,
15 | TTI: number,
16 | DCL: number,
17 | };
18 |
19 | export function getPerformance(): void {
20 | console.log('%c%s', 'font-size: 24px; color: green', '开始监控网页性能');
21 |
22 | let performanceIndicator: performanceIndicatorObj = {
23 | FP: 0,
24 | FCP: 0,
25 | LCP: 0,
26 | FID: 0,
27 | CLS: 0,
28 | FMP: 0,
29 | TTI: 0,
30 | DCL: 0,
31 | };
32 | const reportIndicator = {
33 | appId: userInform.appId,
34 | userID: userInform.userId,
35 | performanceIndicator
36 | }
37 |
38 | window.addEventListener('load', () => {
39 | if (window.requestIdleCallback) {
40 | window.requestIdleCallback(() => {
41 | getPv(performanceIndicator);
42 | getBasicIndicator(performanceIndicator);
43 | getFirstScreenIndicator(performanceIndicator);
44 | getDomContentLoadIndicator(performanceIndicator);
45 | getResourceIndicator(performanceIndicator);
46 | lazyReport('/performance-indicator', reportIndicator);
47 | })
48 | } else {
49 | setTimeout(function () {
50 | getPv(performanceIndicator);
51 | getBasicIndicator(performanceIndicator);
52 | getFirstScreenIndicator(performanceIndicator);
53 | getResourceIndicator(performanceIndicator);
54 | getDomContentLoadIndicator(performanceIndicator);
55 | lazyReport('/performance-indicator', reportIndicator);
56 | })
57 | }
58 | }, true);
59 |
60 | function getPv(performanceIndicator: performanceIndicatorObj) {
61 | const pvLog = {
62 | type: "pv",
63 | startTime: performance.now(),
64 | pageURL: window.location.href,
65 | referrer: document.referrer,
66 | uuid: 0,
67 | }
68 | console.log('%c%s%o', 'color: green', '获取 pv', pvLog)
69 | lazyReport('/pv-indicator', pvLog);
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/monitor-sdk/src/type/index.d.ts:
--------------------------------------------------------------------------------
1 | export interface initOptions {
2 | appId?: string;
3 | cookie: string;
4 | reportUrl: string;
5 | delay: number;
6 | autoTracker?: boolean;
7 | hashPage: boolean;
8 | errorReport: boolean;
9 | performanceReport: boolean;
10 | }
11 | export interface userAgent {
12 | browserName: string;
13 | browserVersion: string;
14 | osName: string;
15 | osVersion: string;
16 | deviceType: string;
17 | deviceVendor: string;
18 | deviceModel: string;
19 | engineName: string;
20 | engineVersion: string;
21 | cpuArchitecture: string;
22 | ua: string;
23 | }
24 | export interface PageInformation {
25 | host: string;
26 | hostname: string;
27 | href: string;
28 | protocol: string;
29 | origin: string;
30 | port: string;
31 | pathname: string;
32 | search: string;
33 | hash: string;
34 | title: string;
35 | language: string;
36 | userAgent: userAgent;
37 | winScreen: string;
38 | docScreen: string;
39 | }
40 | export interface performanceType {
41 | redirect: number;
42 | appCache: number;
43 | DNS: number;
44 | TCP: number;
45 | SSL: number;
46 | request: number;
47 | response: number;
48 | Trans: number;
49 | DOM: number;
50 | FirstByte: number;
51 | processing: number;
52 | Load: number;
53 | Res: number;
54 | DomReady: number;
55 | domParse: number;
56 | TTFB: number;
57 | FP: number;
58 | TTI: number;
59 | }
60 | export interface routeType {
61 | beforeUrl: string;
62 | currentUrl: string;
63 | type: string;
64 | startTime: number;
65 | duration: number;
66 | endTime: number;
67 | }
68 | export declare enum mechanismType {
69 | JS = "jsError",
70 | RS = "resourceError",
71 | UJ = "unhandledrejectionError",
72 | HP = "httpError",
73 | CS = "corsError",
74 | VUE = "vueError"
75 | }
76 | export declare enum metricsName {
77 | PI = "page-information",
78 | OI = "origin-information",
79 | RCR = "router-change-record",
80 | CBR = "click-behavior-record",
81 | CDR = "custom-define-record",
82 | HT = "http-record"
83 | }
84 | export interface httpMetrics {
85 | method: string;
86 | url: string | URL;
87 | body: Document | XMLHttpRequestBodyInit | null | undefined | ReadableStream;
88 | requestTime: number;
89 | responseTime?: number;
90 | status?: number;
91 | statusText?: string;
92 | response?: any;
93 | type?: string;
94 | timeStamp?: string;
95 | message?: string;
96 | duration?: number;
97 | }
98 |
--------------------------------------------------------------------------------
/monitor-sdk/example/vue3-test-sdk/src/views/Test.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
页面停留时间
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
51 |
52 |
53 |
69 |
--------------------------------------------------------------------------------
/monitor-web/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { PageContainer, ProCard } from '@ant-design/pro-components';
2 | import { useState } from 'react';
3 | import {
4 | SettingOutlined,
5 | DatabaseOutlined,
6 | DownOutlined,
7 | } from '@ant-design/icons';
8 |
9 | const getShowProject = function () {
10 | return (
11 |
15 | 查看大屏
16 |
17 |
18 | }
19 | // style={{ width: '50%', height: '50%' }}
20 | bordered
21 | headerBordered
22 | >
23 |
24 |
25 |
26 | 1219
27 | 位活跃用户
28 |
29 |
30 | 1219
31 | 用户总数
32 |
33 |
34 | 1219
35 | 老用户数
36 |
37 |
38 | 1219
39 | 新用户数
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | js报错率:9.9%
49 | js报错率:9.9%
50 |
51 |
52 | js报错率:9.9%
53 | js报错率:9.9%
54 |
55 |
56 |
57 |
58 | )
59 | }
60 | const Index = () => {
61 | const [responsive, setResponsive] = useState(false);
62 |
63 | return (
64 |
65 |
66 | {getShowProject()}
67 | {getShowProject()}
68 | {getShowProject()}
69 |
70 |
71 |
72 | {/**/}
73 | {/*
*/}
80 | {/*
*/}
81 |
82 | )
83 | };
84 | export default Index;
85 |
--------------------------------------------------------------------------------
/monitor-sdk/src/monitor/handle/performance/firstMeaningfulPaintIndicator.ts:
--------------------------------------------------------------------------------
1 | import {lazyReport} from "@monitor/report/index";
2 | import {performanceIndicatorObj} from "@monitor/handle/performance/index";
3 |
4 | export type Entry = {
5 | startTime: number;
6 | roots: Node[];
7 | };
8 |
9 | export function getFirstScreenIndicator (performanceIndicator: performanceIndicatorObj) {
10 | const entries: Entry[] = [];
11 | const ignoreEleList: string[] = ['script', 'style', 'link', 'br', 'meta'];
12 |
13 | // 查看当前元素及其祖先元素是否在数组中
14 | function isInclude (node: Node, arr): boolean {
15 | if (!node || node === document.documentElement) {
16 | return false;
17 | } else if (arr.includes(node)) {
18 | return true;
19 | }
20 | return isInclude(node.parentNode, arr);
21 | }
22 |
23 | // 判断对应target是否在首屏中
24 | function isInFirstScreen (node): boolean {
25 | if (!node || !node.getBoundingClientRect()) {
26 | return false;
27 | }
28 |
29 | const rect = node.getBoundingClientRect();
30 | const screenWidth = window.innerWidth;
31 | const screenHeight = window.innerHeight;
32 | return rect.left >= 0
33 | && rect.left < screenWidth
34 | && rect.top >= 0
35 | && rect.top < screenHeight;
36 | }
37 |
38 | function getFirstScreenTime (): number {
39 | let result: number = 0;
40 | entries.forEach(function (entry) {
41 | for (const node of entry.roots) {
42 | if (isInFirstScreen(node)) {
43 | result = entry.startTime;
44 | break;
45 | }
46 | }
47 | });
48 |
49 | (window.performance.getEntriesByType('resource') as PerformanceResourceTiming[])
50 | .forEach(function (resource) {
51 | if (resource.initiatorType === 'img'
52 | && resource.fetchStart < result
53 | && resource.responseEnd > result
54 | ) {
55 | result = resource.responseEnd;
56 | }
57 | });
58 | // 最终结果
59 | console.log(result);
60 | return result;
61 | }
62 |
63 | const callback = function (mutationList: MutationRecord[]) {
64 | if (!mutationList || !mutationList.forEach) return;
65 |
66 | performanceIndicator.FMP = getFirstScreenTime();
67 |
68 | const entry: Entry = {
69 | startTime: 0,
70 | roots: []
71 | }
72 |
73 | requestAnimationFrame(() => {
74 | entry.startTime = performance.now();
75 | });
76 |
77 | mutationList.forEach(function (mutation) {
78 | if (!mutation || !mutation.addedNodes || !mutation.addedNodes.forEach) return;
79 |
80 | mutation.addedNodes.forEach(function (node) {
81 | if (
82 | node.nodeType === 1 &&
83 | !ignoreEleList.includes((node as HTMLElement).tagName) &&
84 | !isInclude(node, entry.roots)
85 | ) {
86 | entry.roots.push(node);
87 | }
88 | })
89 | })
90 | if (entry.roots.length) {
91 | entries.push(entry);
92 | }
93 | }
94 |
95 | const observer = new MutationObserver(callback);
96 | // 设置观察目标,接受两个参数: target:观察目标,options:通过对象成员来设置观察选项
97 | // 设为 childList: true, subtree: true 表示用来监听 DOM 节点插入、删除和修改时
98 | observer.observe(document, {
99 | childList: true,
100 | subtree: true
101 | })
102 |
103 | // 只监听 5 s 之内的变化
104 | setTimeout(function () {
105 | // lazyReport('/first-screen-indicator', performanceIndicator);
106 | observer.disconnect();
107 | }, 5000);
108 | }
109 |
--------------------------------------------------------------------------------
/monitor-sdk/src/type/index.ts:
--------------------------------------------------------------------------------
1 | export interface initOptions {
2 | appId?: string; // 系统id
3 | cookie: string; // 用户id
4 | reportUrl: string; // 后端url
5 | delay?: number; // 延迟和合并上报的功能
6 | autoTracker?: boolean; // 自动埋点
7 | hashPage: boolean; // 是否hash录有
8 | errorReport: boolean;// 是否开启错误监控
9 | performanceReport: boolean // 是否开启性能监控
10 | }
11 |
12 | // 网页 userAgent 信息
13 | export interface userAgent {
14 | browserName: string,
15 | browserVersion: string,
16 | osName: string,
17 | osVersion: string,
18 | deviceType: string,
19 | deviceVendor: string,
20 | deviceModel: string,
21 | engineName: string,
22 | engineVersion: string,
23 | cpuArchitecture: string,
24 | ua: string,
25 | }
26 |
27 | // 网页及浏览器信息
28 | export interface PageInformation {
29 | category: metricsName,
30 | host: string;
31 | hostname: string;
32 | href: string;
33 | protocol: string;
34 | origin: string;
35 | port: string;
36 | pathname: string;
37 | search: string;
38 | hash: string;
39 | // 网页标题
40 | title: string;
41 | // 浏览器的语种 (eg:zh) ; 这里截取前两位,有需要也可以不截取
42 | language: string;
43 | // 用户 userAgent 信息
44 | userAgent: userAgent;
45 | // 屏幕宽高 (eg:1920x1080) 屏幕宽高意为整个显示屏的宽高
46 | windowScreen?: string;
47 | // 文档宽高 (eg:1388x937) 文档宽高意为当前页面显示的实际宽高(有的同学喜欢半屏显示)
48 | docScreen?: string;
49 | // 节点宽高
50 | nodeScreen: string;
51 | }
52 |
53 | // 页面性能数据
54 | export interface performanceType {
55 | redirect: number,
56 | appCache: number,
57 | DNS: number,
58 | TCP: number,
59 | SSL: number,
60 | request: number,
61 | response: number,
62 | Trans: number,
63 | DOM: number,
64 | FirstByte: number,
65 | processing: number,
66 | Load: number,
67 | Res: number,
68 | DomReady: number,
69 | domParse: number,
70 | TTFB: number,
71 | FP: number,
72 | TTI: number,
73 | }
74 |
75 | // 上报的路由信息
76 | export interface routeType {
77 | beforeUrl: string, // 用户来路地址
78 | currentUrl: string, // 路由跳转地址
79 | type: string, // 用户来路方式
80 | startTime: number,
81 | duration: number,
82 | endTime: number,
83 | }
84 |
85 | // 异常错误类型
86 | export enum mechanismType {
87 | JS = 'jsError',
88 | RS = 'resourceError',
89 | UJ = 'unhandledrejectionError',
90 | HP = 'httpError',
91 | CS = 'corsError',
92 | VUE = 'vueError',
93 | }
94 |
95 | // 用户行为所做的参数
96 | export enum metricsName {
97 | PI = 'page-information',
98 | OI = 'origin-information',
99 | RCR = 'router-change-record',
100 | CBR = 'click-behavior-record',
101 | CDR = 'custom-define-record',
102 | HT = 'http-record',
103 | }
104 |
105 | // 接口请求方式
106 | export enum InterfaceMethod {
107 | xhr = 'xhr',
108 | fetch = 'fetch'
109 | }
110 | // 接口性能的数据结构
111 | export interface InterfaceType {
112 | // 开始发送到请求拿到数据的时长
113 | duration: number,
114 | // 最终请求的得到的类型 load/error/abort
115 | event: string,
116 | // 请求方法
117 | method: string,
118 | // request请求题大小
119 | requestSize: number,
120 | // response响应体大小
121 | responseSize: number,
122 | // 状态码
123 | status: number,
124 | // 是否成功
125 | success: boolean,
126 | // 请求方式 (xhr/fetch)
127 | type: InterfaceMethod.xhr | InterfaceMethod.fetch,
128 | // 请求地址
129 | url: string,
130 | }
131 |
132 | export interface httpMetrics {
133 | method: string;
134 | url: string | URL;
135 | body: Document | XMLHttpRequestBodyInit | null | undefined | ReadableStream;
136 | requestTime: number;
137 | responseTime?: number;
138 | status?: number;
139 | statusText?: string;
140 | response?: any;
141 | type?: string,
142 | timeStamp?: string,
143 | message?: string,
144 | duration?: number
145 | }
146 |
147 |
148 | // 白屏初始化
149 | export type BlackScreenDataType = {
150 | wrapperElements: string[];
151 | emptyPoints: number;
152 | };
153 |
--------------------------------------------------------------------------------
/monitor-web/src/pages/Overview/index.tsx:
--------------------------------------------------------------------------------
1 | import { PageContainer, ProCard, StatisticCard, ProFormDatePicker } from '@ant-design/pro-components';
2 | import { Column } from '@ant-design/plots';
3 |
4 | export default () => {
5 | const data = [
6 | {
7 | country: 'Asia',
8 | year: '1750',
9 | value: 502,
10 | },
11 | {
12 | country: 'Asia',
13 | year: '1800',
14 | value: 635,
15 | },
16 | {
17 | country: 'Asia',
18 | year: '1850',
19 | value: 809,
20 | },
21 | {
22 | country: 'Asia',
23 | year: '1900',
24 | value: 947,
25 | },
26 | {
27 | country: 'Asia',
28 | year: '1950',
29 | value: 1402,
30 | },
31 | {
32 | country: 'Asia',
33 | year: '1999',
34 | value: 3634,
35 | },
36 | {
37 | country: 'Asia',
38 | year: '2050',
39 | value: 5268,
40 | },
41 | {
42 | country: 'Africa',
43 | year: '1750',
44 | value: 106,
45 | },
46 | {
47 | country: 'Africa',
48 | year: '1800',
49 | value: 107,
50 | },
51 | {
52 | country: 'Africa',
53 | year: '1850',
54 | value: 111,
55 | },
56 | {
57 | country: 'Africa',
58 | year: '1900',
59 | value: 133,
60 | },
61 | {
62 | country: 'Africa',
63 | year: '1950',
64 | value: 221,
65 | },
66 | {
67 | country: 'Africa',
68 | year: '1999',
69 | value: 767,
70 | },
71 | {
72 | country: 'Africa',
73 | year: '2050',
74 | value: 1766,
75 | },
76 | {
77 | country: 'Europe',
78 | year: '1750',
79 | value: 163,
80 | },
81 | {
82 | country: 'Europe',
83 | year: '1800',
84 | value: 203,
85 | },
86 | {
87 | country: 'Europe',
88 | year: '1850',
89 | value: 276,
90 | },
91 | {
92 | country: 'Europe',
93 | year: '1900',
94 | value: 408,
95 | },
96 | {
97 | country: 'Europe',
98 | year: '1950',
99 | value: 547,
100 | },
101 | {
102 | country: 'Europe',
103 | year: '1999',
104 | value: 729,
105 | },
106 | {
107 | country: 'Europe',
108 | year: '2050',
109 | value: 628,
110 | },
111 | ];
112 | const config = {
113 | data,
114 | xField: 'year',
115 | yField: 'value',
116 | seriesField: 'country',
117 | isPercent: true,
118 | isStack: true,
119 | label: {
120 | position: 'middle',
121 | content: (item) => {
122 | return item.value.toFixed(2);
123 | },
124 | style: {
125 | fill: '#fff',
126 | },
127 | },
128 | };
129 |
130 | return (
131 |
134 | {/**/}
135 |
136 |
137 | 1219
138 | 位活跃用户
139 |
140 |
141 | 1219
142 | 位活跃用户
143 |
144 |
145 | 1219
146 | 位活跃用户
147 |
148 |
149 | 1219
150 | 位活跃用户
151 |
152 |
153 | 1219
154 | 位活跃用户
155 |
156 |
157 | 1219
158 | 位活跃用户
159 |
160 |
161 |
162 | 导出}>
163 |
164 |
165 | {/**/}
166 |
167 | )
168 | }
169 |
--------------------------------------------------------------------------------
/monitor-sdk/example/vue3-test-sdk/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
异常数据
4 |
5 |
前端异常
6 |
7 |
8 |
9 |
10 |
11 |

12 |
13 |
14 |
15 |
接口异常
16 |
17 |
18 |
19 |
20 |
21 |
白屏异常
22 |
23 |
24 |
25 |
26 |
行为数据
27 |
28 |
29 |
30 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
137 |
138 |
139 |
--------------------------------------------------------------------------------
/monitor-sdk/webpack.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const path = require('path');
3 | const HtmlWebpackPlugin = require('html-webpack-plugin');
4 | const {
5 | CleanWebpackPlugin
6 | } = require('clean-webpack-plugin');
7 | const MiniCssExtractPlugin = require('mini-css-extract-plugin');
8 | // const WebpackDeepScopeAnalysisPlugin = require('webpack-deep-scope-plugin').default;
9 |
10 | const isProduction = process.env.NODE_ENV == "production";
11 |
12 | const postCssLoaderConfig = {
13 | loader: 'postcss-loader',
14 | options: {
15 | plugins: [
16 | require('autoprefixer')({
17 | overrideBrowserslist: [
18 | 'Chrome > 31',
19 | 'ff > 31',
20 | 'ie >= 10'
21 | ]
22 | })
23 | ]
24 | }
25 | };
26 |
27 | const commonConfig = {
28 | entry: './src/index.ts',
29 | context: process.cwd(),
30 | output: {
31 | path: path.resolve(__dirname, 'dist'),
32 | filename: 'monitor.js',
33 | environment: {
34 | arrowFunction: false
35 | },
36 | },
37 | devtool: isProduction ? "source-map" : "cheap-module-source-map",
38 | devServer: {
39 | static: path.join(__dirname, 'dist'),
40 | compress: true,
41 | port: 3000,
42 | hot: true,
43 | // open: true,
44 | // progress: true,
45 | historyApiFallback: true
46 | },
47 | plugins: [
48 | new HtmlWebpackPlugin({
49 | template: path.resolve(__dirname, './src/index.html'),
50 | filename: 'index.html'
51 | }),
52 | new CleanWebpackPlugin(),
53 | new MiniCssExtractPlugin({
54 | filename: "static/css/[name].[hash].css",
55 | }),
56 | new webpack.HotModuleReplacementPlugin(),
57 | // new WebpackDeepScopeAnalysisPlugin(),
58 | ],
59 | module: {
60 | rules: [{
61 | test: /\.(jsx?|tsx?)$/,
62 | loader: 'babel-loader',
63 | exclude: /node_modules/,
64 | options: {
65 | plugins: [
66 | ["import", {
67 | libraryName: "antd",
68 | style: "css"
69 | }]
70 | ]
71 | }
72 | }, {
73 | test: /\.ts$/,
74 | use: [
75 | //配置babel
76 | {
77 | //指定加载器
78 | loader: 'babel-loader',
79 | //设置 babel
80 | options: {
81 | //设置预定义的环境
82 | presets: [
83 | //指定环境插件
84 | '@babel/preset-env'
85 | ]
86 | }
87 | },
88 | 'ts-loader'
89 | ],
90 | exclude: /node_modules/
91 | }, {
92 | test: /\.css$/,
93 | use: [
94 | MiniCssExtractPlugin.loader,
95 | 'css-loader',
96 | postCssLoaderConfig,
97 | ]
98 | }, {
99 | test: /\.ttf$/,
100 | use: ['file-loader']
101 | }, {
102 | test: /\.less$/,
103 | use: [MiniCssExtractPlugin.loader, 'css-loader', postCssLoaderConfig, 'less-loader']
104 | }, {
105 | test: /.*\.(gif|png|svg|jpe?g)$/i,
106 | use: [{
107 | loader: 'url-loader',
108 | options: {
109 | limit: 5120,
110 | name: 'static/imgs/[name].[hash:8].[ext]',
111 | publicPath: '../../'
112 | }
113 | }]
114 | }]
115 | },
116 | resolve: {
117 | extensions: ['.ts', '.js', '.tsx', '.jsx', '.json'],
118 | // mainFiles: ['index.ts', 'index'],
119 | alias: {
120 | '@': path.resolve(__dirname, './src'),
121 | '@config': path.resolve(__dirname, './src/config'),
122 | '@monitor': path.resolve(__dirname, './src/monitor'),
123 | '@type': path.resolve(__dirname, './src/type'),
124 | '@util': path.resolve(__dirname, './src/util'),
125 | },
126 | },
127 | externals: {
128 | fs: require('fs')
129 | }
130 | };
131 |
132 |
133 |
134 | module.exports = () => {
135 | if (isProduction) {
136 | commonConfig.mode = "production";
137 | } else {
138 | commonConfig.mode = "development";
139 | }
140 | return commonConfig;
141 | }
142 |
--------------------------------------------------------------------------------
/monitor-sdk/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
8 |
9 | monitor-sdk
10 |
11 |
12 |
13 |
14 |
15 |
异常数据
16 |
17 |
前端异常
18 |
19 |
20 |
21 |
22 |
23 |

24 |
25 |
26 |
27 |
接口异常
28 |
29 |
30 |
31 |
32 |
33 |
白屏异常
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
行为数据
43 |
44 |
45 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
141 |
142 |
143 |
144 |
--------------------------------------------------------------------------------
/monitor-sdk/src/monitor/handle/performance/basicIndicator.ts:
--------------------------------------------------------------------------------
1 | // 获取页面基本性能数据
2 | import { EntryTypes, getBasicParams, PerformanceInfoType } from "@config/constant";
3 | import { performanceIndicatorObj } from "@monitor/handle/performance/index";
4 |
5 | interface PerformanceLayoutShift extends PerformanceEntry {
6 | value: number;
7 | hadRecentInput: boolean;
8 | }
9 |
10 | export function getBasicIndicator(performanceIndicator: performanceIndicatorObj) {
11 | // 获取 FP 、 FCP 、 LCP 、 CLS 的函数
12 | function getSimpleIndicator (type: string, entriesByName?: string) {
13 | let isLCPDone = false;
14 | let clsScore = 0;
15 |
16 | const callback = (entryList: PerformanceObserverEntryList) => {
17 | if (typeof entriesByName === 'string') {
18 | for (const entry of entryList.getEntriesByName(entriesByName)) {
19 | const indicator = entry.startTime;
20 |
21 | if (entriesByName === PerformanceInfoType.FP) {
22 | performanceIndicator.FP = indicator;
23 | } else if (entriesByName === PerformanceInfoType.FCP) {
24 | performanceIndicator.FCP = indicator;
25 | } else if (entriesByName === '') {
26 | performanceIndicator.LCP = indicator;
27 | isLCPDone = true;
28 | }
29 |
30 | }
31 | } else {
32 | const entries = entryList.getEntries() as PerformanceLayoutShift[];
33 | for (const entry of entries) {
34 | if (!entry.hadRecentInput) {
35 | clsScore += entry.value;
36 | performanceIndicator.CLS = clsScore;
37 | console.log('cls增加了')
38 | }
39 | }
40 | }
41 | observer.disconnect();
42 | }
43 |
44 | const observer = new PerformanceObserver(callback);
45 | observer.observe({ type, buffered: true });
46 | }
47 |
48 | // FP
49 | getSimpleIndicator(EntryTypes.paint, PerformanceInfoType.FP);
50 |
51 | // FCP
52 | getSimpleIndicator(EntryTypes.paint, PerformanceInfoType.FCP);
53 |
54 | // LCP
55 | getSimpleIndicator(EntryTypes.LCP, '');
56 |
57 | // FID TODO
58 | function FID () {
59 | const callback = (entryList) => {
60 | // const firstInput = entryList.getEntries()[0];
61 | // const fid = firstInput?.processingStart - firstInput?.startTime;
62 | // performanceIndicator.FID = fid;
63 | const entries = entryList.getEntries();
64 | const entry = entries[entries.length - 1];
65 | const fid = entry.processingStart - entry.startTime;
66 | performanceIndicator.FID = fid;
67 | }
68 | const observer = new PerformanceObserver(callback);
69 | observer.observe({ type: EntryTypes.FID, buffered: true });
70 | }
71 | FID();
72 |
73 | // CLS
74 | window.addEventListener(
75 | 'click',
76 | () => {
77 | setTimeout(() => {
78 | getSimpleIndicator(EntryTypes.CLS);
79 | }, 1000);
80 | },
81 | true
82 | )
83 |
84 | // TTI TODO
85 | function getTti () {
86 | const tti = window.performance.timing.domInteractive - performance.timing.fetchStart;
87 | // const callback = (entryList) => {
88 | // for (const entry of entryList.getEntries()) {
89 | // if (entry.entryType === 'longtask') {
90 | //
91 | // }
92 | // }
93 | // }
94 | // const observer = new PerformanceObserver(callback);
95 | // observer.observe({ entryTypes: ['longtask'] });
96 | }
97 |
98 |
99 | // const {
100 | // redirectStart, redirectEnd,
101 | // domainLookupStart, domainLookupEnd,
102 | // fetchStart,
103 | // connectStart, connectEnd,
104 | // secureConnectionStart,
105 | // responseStart, responseEnd,
106 | // requestStart,
107 | // domComplete,
108 | // domLoading,
109 | // loadEventStart, loadEventEnd,
110 | // domInteractive,
111 | // navigationStart,
112 | // domContentLoadedEventEnd,
113 | // // domContentLoaded,
114 | // // domContentLoaded,
115 | // // } = window.performance.getEntriesByType('navigation');
116 | // } = window.performance.timing;
117 | // const redirect = redirectEnd - redirectStart; // 重定向
118 | // const appCache = domainLookupStart - fetchStart; // 缓存
119 | // const DNS = domainLookupEnd - domainLookupStart; // DNS 解析耗时
120 | // const TCP = connectEnd - connectStart; // TCP 连接耗时
121 | // const SSL = connectEnd - secureConnectionStart; // SSL 握手
122 | // const request = responseStart - requestStart; // 请求耗时
123 | // const response = responseEnd - responseStart; // 响应耗时
124 | // const Trans = responseEnd - responseStart; // 内容传输耗时
125 | // const DOM = domInteractive - responseEnd; // DOM解析耗时
126 | // const FirstByte = responseStart - domainLookupStart; // 首包时间
127 | // const processing = domComplete - domLoading;
128 | // const Load = loadEventStart - fetchStart; // 页面完全加载时间
129 | // const Res = loadEventStart - domContentLoadedEventEnd; // 资源加载耗时
130 | // const DomReady = domContentLoadedEventEnd - fetchStart;
131 | // const domParse = domInteractive - responseEnd; // DOM 解析耗时
132 | // const TTFB = responseStart - requestStart;
133 | // // const TTFB = responseStart - navigationStart;
134 | // const FP = responseEnd - fetchStart; // 首次渲染时间 / 白屏时间
135 | // const TTI = domInteractive - fetchStart; // 首次可交互时间
136 | // // const ready = domContentLoaded - fetchStart; // HTML 加载完成时间
137 | // // const resourceLoad = domComplete - domContentLoaded;
138 | //
139 | // const performanceLog: performanceType = {
140 | // redirect,
141 | // appCache,
142 | // DNS,
143 | // TCP,
144 | // SSL,
145 | // request,
146 | // response,
147 | // Trans,
148 | // DOM,
149 | // FirstByte,
150 | // processing,
151 | // Load,
152 | // Res,
153 | // DomReady,
154 | // domParse,
155 | // TTFB,
156 | // FP,
157 | // TTI,
158 | // }
159 | // return performanceLog;
160 | }
161 |
--------------------------------------------------------------------------------
/monitor-node/src/app.ts:
--------------------------------------------------------------------------------
1 | // // 引入koa
2 | // import Koa from 'koa';
3 | // import http from 'http';
4 | // import path from 'path';
5 | // import cors from 'koa-cors';
6 | // import influx from 'influx';
7 | //
8 | // // 创建koa实例
9 | // const app = new Koa()
10 | // // 创建服务器
11 | // const server: http.Server = new http.Server(app.callback())
12 | // app.use(cors());
13 | // app.use(async (ctx, next)=> {
14 | // ctx.set('Access-Control-Allow-Origin', '*');
15 | // ctx.set('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , Access-Control-Allow-Credentials');
16 | // ctx.set('Access-Control-Allow-Methods', 'POST, GET, OPTIONS');
17 | // ctx.set('Access-Control-Allow-Credentials', 'true');
18 | // ctx.set('Vary', 'Origin');
19 | // if (ctx.method == 'OPTIONS') {
20 | // ctx.body = 200;
21 | // } else {
22 | // await next();
23 | // }
24 | // })
25 | //
26 | // // 中间件
27 | // app.use(async (ctx) => {
28 | // console.log('addd')
29 | // ctx.body = 'Hello World'
30 | // })
31 | // // 监听端口
32 | // app.listen(9000, () => {
33 | // console.log('run success')
34 | // console.log('app started at port 9000...')
35 | // })
36 |
37 | import Koa from 'koa';
38 | import Router from 'koa-router';
39 | import bodyParser from 'koa-bodyparser';
40 | import cors from 'koa2-cors';
41 | import { createConnections } from "typeorm";
42 | import { koaSwagger } from "koa2-swagger-ui";
43 | import swaggerRouter from './config/swagger';
44 |
45 | import sendCaptcha from './routes/user/sendCaptcha';
46 | import login from './routes/user/login';
47 | import register from './routes/user/register';
48 | import forget from './routes/user/forget';
49 | import logout from './routes/user/logout';
50 |
51 | // js 异常
52 | import addJs from './routes/error/js/add';
53 | import findJs from './routes/error/js/find';
54 | // promise 异常
55 | import addPromise from './routes/error/promise/add';
56 | import findPromise from './routes/error/promise/find';
57 | // resource 异常
58 | import addResource from './routes/error/resource/add';
59 | import findResource from './routes/error/resource/find';
60 | // cors 异常
61 | import addCors from './routes/error/cors/add';
62 | import findCors from './routes/error/cors/find';
63 | // console.error 异常
64 | import addConsoleError from './routes/error/consoleError/add';
65 | import findConsoleError from './routes/error/consoleError/find';
66 | // interface 异常
67 | import addInterfaceError from './routes/error/interface/add';
68 | import findInterfaceError from './routes/error/interface/find';
69 | // blankScreen 异常
70 | import addBlankScreen from './routes/error/blankScreen/add';
71 | import findBlankScreen from './routes/error/blankScreen/find';
72 |
73 | // pv 指标
74 | import addPv from './routes/performance/pv/add';
75 | import findPv from './routes/performance/pv/find';
76 | // interface 指标
77 | import addInterfaceIndicator from './routes/performance/interface/add';
78 | import findInterfaceIndicator from './routes/performance/interface/find';
79 | // resource 指标
80 | import addResourceIndicator from './routes/performance/resource/add';
81 | import findResourceIndicator from './routes/performance/resource/find';
82 | // 性能指标
83 | import addPerformanceIndicator from './routes/performance/allPerformance/add';
84 | import findPerformanceIndicator from './routes/performance/allPerformance/find';
85 |
86 | // history 路由
87 | import addHistory from './routes/behaviour/history/add';
88 | import findHistory from './routes/behaviour/history/find';
89 | // hash 路由
90 | import addHash from './routes/behaviour/hash/add';
91 | import findHash from './routes/behaviour/hash/find';
92 |
93 | createConnections ()
94 | .then(async () => {
95 | const app = new Koa();
96 | const router = new Router();
97 |
98 | // 处理cookie跨域
99 | const corsOptions ={
100 | origin: 'http://localhost:5014',
101 | credentials: true,
102 | optionSuccessStatus: 200
103 | }
104 | app.use(cors(corsOptions));
105 |
106 | // 处理 Navigator.sendBeacon 传参
107 | app.use(async function(ctx,next) {
108 | //判断请求的路由路径
109 | if (/^.*\/beacon\/.+$/.test(ctx.path)) {
110 | ctx.disableBodyParser = true;
111 | }
112 | await next();
113 | })
114 | // 处理 post 请求的参数
115 | app.use(bodyParser());
116 |
117 | app.use(swaggerRouter.routes()).use(swaggerRouter.allowedMethods())
118 | const swaggerOption = {
119 | routePrefix: '/swagger', // host at /swagger instead of default /docs
120 | swaggerOptions: {
121 | url: '/swagger/swagger.json' // example path to json 其实就是之后swagger-jsdoc生成的文档地址
122 | }
123 | }
124 | app.use(koaSwagger(swaggerOption));
125 |
126 | router.post('/api/send-captcha', sendCaptcha);
127 | router.post('/api/login', login);
128 | router.post('/api/register', register);
129 | // router.post('/api/forget_password', forget);
130 | // router.post('/api/logout', logout);
131 |
132 | // js 异常
133 | router.post('/report/js-error', addJs);
134 | router.get('/error/js', findJs);
135 |
136 | // promise 异常
137 | router.post('/report/promise-error', addPromise);
138 | router.get('/error/promise', findPromise);
139 |
140 | // resource 异常
141 | router.post('/report/resource-error', addResource);
142 | router.get('/error/resource', findResource);
143 |
144 | // cors 异常
145 | router.post('/report/cors-error', addCors);
146 | router.get('/error/cors', findCors);
147 |
148 | // console.error 异常
149 | router.post('/report/console-error', addConsoleError);
150 | router.get('/error/console', findConsoleError);
151 |
152 | // interface 异常
153 | router.post('/report/interface-error', addInterfaceError);
154 | router.get('/error/interface', findInterfaceError);
155 |
156 | // 白屏异常
157 | router.post('/report/blank-screen-error', addBlankScreen);
158 | router.get('/error/blank-screen', findBlankScreen);
159 |
160 | // interface 指标
161 | router.post('/report/interface-indicator', addInterfaceIndicator);
162 | router.get('/indicator/interface', findInterfaceIndicator);
163 |
164 | // 资源指标
165 | router.post('/report/resource-indicator', addResourceIndicator);
166 | router.get('/indicator/resource', findResourceIndicator);
167 |
168 | // 性能指标
169 | router.post('/report/performance-indicator', addPerformanceIndicator);
170 | router.get('/indicator/performance', findPerformanceIndicator);
171 |
172 | // pv 指标
173 | router.post('/report/pv-indicator', addPv);
174 | router.get('/indicator/pv', findPv);
175 |
176 | // hash 路由
177 | router.post('/report/hash', addHash);
178 | router.get('/api/hash', findHistory);
179 |
180 | // history 路由
181 | router.post('/report/history', addHistory);
182 | router.get('/api/history', findHash);
183 |
184 | // 组装匹配好的路由,返回一个合并好的中间件
185 | app.use(router.routes());
186 |
187 | app.listen(8080, () => {
188 | console.log('网站服务器启动成功,请访问 http://localhost:8080');
189 | console.log('访问 influxdb ,请访问 http://localhost:8086');
190 | console.log('swagger api 文档可访问:http://localhost:8080/swagger');
191 | })
192 | })
193 | .catch((error: any) => console.log('TypeOrm连接失败', error))
194 |
--------------------------------------------------------------------------------
/monitor-web/src/pages/Error/js/index.tsx:
--------------------------------------------------------------------------------
1 | import type { ProColumns } from '@ant-design/pro-components';
2 | import { PageContainer, ProCard, ProTable, ProBreadcrumb, TableDropdown } from '@ant-design/pro-components';
3 | import {
4 | DownOutlined,
5 | QuestionCircleOutlined,
6 | CheckCircleOutlined,
7 | StopOutlined,
8 | AndroidOutlined,
9 | AppleOutlined,
10 | WindowsOutlined,
11 | AndroidFilled,
12 | AppleFilled,
13 | WindowsFilled,
14 | } from '@ant-design/icons';
15 | import { Button, Tooltip, Space, Table , Select, Tag, } from 'antd';
16 | import { PageHeaderWrapper } from '@ant-design/pro-layout';
17 | import type { CustomTagProps } from 'rc-select/lib/BaseSelect';
18 |
19 | import {useEffect, useState} from 'react';
20 | import {createColor} from "@/util";
21 | import {findJsError} from "@/api/modules/error";
22 | import {useRequest} from "@@/plugin-request/request";
23 |
24 | import axios from 'axios';
25 |
26 | const valueEnum = {
27 | 0: 'unsolved',
28 | 1: 'handled',
29 | 2: 'fixed',
30 | };
31 |
32 | export type TableListItem = {
33 | key: number;
34 | errorDes: string;
35 | status: string;
36 | errorTime: number;
37 | androidNumber: number;
38 | iosNumber: number;
39 | windowNumber: number;
40 | person: number;
41 | handler: string;
42 | };
43 | const tableListDataSource: TableListItem[] = [];
44 |
45 | const creators = ['付小小', '曲丽丽', '林东东', '陈帅帅', '兼某某'];
46 |
47 |
48 | // person
49 | for (let i = 0; i < 5; i += 1) {
50 | tableListDataSource.push({
51 | key: i,
52 | errorDes: 'AppName',
53 | status: valueEnum[Math.floor(Math.random() * 10) % 3],
54 | errorTime: Date.now() - Math.floor(Math.random() * 100000),
55 | androidNumber: Math.floor(Math.random() * 20),
56 | iosNumber: Math.floor(Math.random() * 20),
57 | windowNumber: Math.floor(Math.random() * 20),
58 | person: Math.floor(Math.random() * 20),
59 | handler: creators[Math.floor(Math.random() * creators.length)],
60 | });
61 | }
62 |
63 | const tagRender = (props: CustomTagProps) => {
64 | const { label, value, closable, onClose } = props;
65 | const onPreventMouseDown = (event: React.MouseEvent) => {
66 | event.preventDefault();
67 | event.stopPropagation();
68 | };
69 | return (
70 |
77 | {label}
78 |
79 | );
80 | };
81 | const options = ['foursheep', '四羊', 'syandeg'];
82 | // const options = [{ value: 'gold' }, { value: 'lime' }, { value: 'green' }, { value: 'cyan' }];
83 |
84 | const columns: ProColumns[] = [
85 | {
86 | title: '错误描述',
87 | tip: '报错信息过长会自动收缩',
88 | width: 80,
89 | copyable: true,
90 | ellipsis: true,
91 | dataIndex: 'errorDes',
92 | render: (_) => {_},
93 | },
94 | {
95 | title: '状态',
96 | width: 80,
97 | dataIndex: 'status',
98 | valueEnum: {
99 | unsolved: { text: '未解决', status: 'Error' },
100 | handled: { text: '处理中', status: 'Processing' },
101 | fixed: { text: '已修复', status: 'Success' },
102 | },
103 | },
104 | {
105 | title: '报错时间',
106 | width: 140,
107 | key: 'since',
108 | dataIndex: 'errorTime',
109 | valueType: 'date',
110 | sorter: (a, b) => a.createdAt - b.createdAt,
111 | },
112 | {
113 | title: '发生次数',
114 | width: 80,
115 | search: false,
116 | dataIndex: 'number',
117 | render: (dom, record) => (
118 |
119 | {record.androidNumber}
120 | {record.iosNumber}
121 | {record.windowNumber}
122 |
123 | )
124 | // initialValue: 'all',
125 | // valueEnum: {
126 | // all: { text: '全部', status: 'Default' },
127 | // close: { text: '关闭', status: 'Default' },
128 | // running: { text: '运行中', status: 'Processing' },
129 | // online: { text: '已上线', status: 'Success' },
130 | // error: { text: '异常', status: 'Error' },
131 | // },
132 | },
133 | {
134 | title: '影响人数',
135 | width: 80,
136 | search: false,
137 | dataIndex: 'person',
138 | // align: 'right',
139 | sorter: (a, b) => a.containers - b.containers,
140 | },
141 | {
142 | title: '处理人',
143 | width: 80,
144 | dataIndex: 'handler',
145 | render: () =>
146 |
162 | },
163 | {
164 | title: '操作',
165 | width: 180,
166 | key: 'option',
167 | valueType: 'option',
168 | render: () => [
169 | 链路,
170 | 报警,
171 | 监控,
172 | ,
179 | ],
180 | },
181 | ];
182 |
183 | const overview = (
184 |
185 |
186 |
187 | )
188 | const errorList = (
189 |
190 | columns={columns}
191 | rowSelection={{
192 | // 自定义选择项参考: https://ant.design/components/table-cn/#components-table-demo-row-selection-custom
193 | // 注释该行则默认不显示下拉选项
194 | selections: [Table.SELECTION_ALL, Table.SELECTION_INVERT],
195 | }}
196 | tableAlertRender={({selectedRowKeys, selectedRows, onCleanSelected}) =>
197 | 已选 {selectedRowKeys.length} 项
198 |
199 |
200 | }
201 | // tableAlertOptionRender={false}
202 | request={(params, sorter, filter) => {
203 | // 表单搜索项会从 params 传入,传递给后端接口。
204 | console.log(params, sorter, filter);
205 | return Promise.resolve({
206 | data: tableListDataSource,
207 | success: true,
208 | });
209 | }}
210 | rowKey="key"
211 | pagination={{
212 | showQuickJumper: true,
213 | }}
214 | search={{
215 | labelWidth: 'auto',
216 | }}
217 | dateFormatter="string"
218 | headerTitle='错误列表'
219 | toolBarRender={() => [
220 | ,
221 | ,
225 | ,
228 | ]}
229 | />
230 | )
231 | const versionAnalysis = (
232 |
233 |
234 |
235 | )
236 |
237 | export default () => {
238 | let arr: any[] = [];
239 | // const { data, error, loading } = useRequest((services) => {
240 | // return services.getUserList('/error/js');
241 | // });
242 |
243 | useEffect(() => {
244 |
245 | findJsError().then(res => {
246 |
247 | console.log('res', res)
248 | })
249 | // arr = [...res.data.response];
250 | axios.get('/error/js').then((res) => {
251 | console.log('jserror', res)
252 | })
253 | console.log('arr', arr)
254 | })
255 |
256 |
257 | return (
258 |
259 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 | );
292 | };
293 |
--------------------------------------------------------------------------------
/monitor-sdk/src/util/index.ts:
--------------------------------------------------------------------------------
1 | import { AxiosError } from 'axios';
2 | import sourceMap from 'source-map';
3 | import { httpMetrics, mechanismType } from '@type';
4 | import { ResourceError } from '@monitor/class/ResourceError';
5 | import { CorsError } from '@monitor/class/CorsError';
6 | import { JsError } from '@monitor/class/JsError';
7 | import { PromiseError } from '@monitor/class/PromiseError';
8 | import { sourcemap } from '@/sourcemap.js';
9 | import axios from 'axios';
10 | import fs from 'fs';
11 | import path from 'path';
12 |
13 | // 判断是 JS异常、静态资源异常、还是跨域异常
14 | export function getErrorKey(event: ErrorEvent | Event) {
15 | // const isJsError = event instanceof ErrorEvent;
16 | // if (!isJsError) return mechanismType.RS;
17 |
18 | // 有 e.target.src(href) 的认定为资源加载错误
19 | const target = event.target;
20 | // const isElementTarget: boolean = target && (target.src || target.href);
21 | const isElementTarget: boolean = target instanceof HTMLScriptElement || target instanceof HTMLLinkElement || target instanceof HTMLImageElement;
22 | if (isElementTarget) {
23 | return mechanismType.RS;
24 | }
25 | if (event instanceof AxiosError) {
26 | return mechanismType.CS;
27 | }
28 | // return event.message === 'Script error.' ? mechanismType.CS : mechanismType.JS;
29 | return mechanismType.JS;
30 | };
31 |
32 | // 获取用户最后一个交互事件
33 | export function getLastEvent() {
34 | let lastEvent: Event;
35 | [
36 | 'click',
37 | 'mousedown',
38 | // 'mouseover',
39 | 'keydown',
40 | 'touchstart',
41 | ].forEach(eventType => {
42 | window.addEventListener(
43 | eventType,
44 | (event) => {
45 | lastEvent = event;
46 | },
47 | {
48 | capture: true,
49 | passive: true // 默认不阻止默认事件
50 | }
51 | );
52 | })
53 | return lastEvent;
54 | }
55 |
56 | // 获取选择器
57 | export function getSelector(pathsOrTarget: any) {
58 | console.log('params', pathsOrTarget)
59 |
60 | const handleSelector = function (pathArr: any) {
61 | return pathArr
62 | .reverse()
63 | .filter((element: any) => {
64 | // 去除 document 和 window
65 | return element !== document && element !== window;
66 | })
67 | .map((element: any) => {
68 | const { id, className, tagName } = element;
69 | if (id) {
70 | return `${tagName.toLowerCase()}#${id}`;
71 | } else if (className && typeof className === 'string') {
72 | return `${tagName.toLowerCase()}.${className}`;
73 | } else {
74 | return tagName.toLowerCase();
75 | }
76 | })
77 | .join(' ');
78 | }
79 |
80 | if (Array.isArray(pathsOrTarget)) {
81 | return handleSelector(pathsOrTarget);
82 | } else {
83 | let pathArr = [];
84 | while (pathsOrTarget) {
85 | pathArr.push(pathsOrTarget);
86 | pathsOrTarget = pathsOrTarget.parentNode;
87 | }
88 | return handleSelector(pathArr);
89 | }
90 | }
91 |
92 | // 获取当前时间戳:hh:mm:ss 格式 或者 毫秒格式
93 | // 参数为 data.hms === true 是 hh:mm:ss 格式,否则为毫秒格式
94 | // 可以参数 data.time 创建 Date 对象
95 | export function nowTime(data: { hms?: boolean, time?: number }): number | string {
96 | const { hms, time } = data;
97 | if (hms === true) {
98 | const newTime = time ? new Date(time) : new Date();
99 | const hour = newTime.getHours();
100 | const minute = newTime.getMinutes();
101 | const second = newTime.getSeconds();
102 | const timer = hour + ':' + minute + ':' + second + ' ';
103 | return timer;
104 | }
105 | return new Date().getTime();
106 | }
107 |
108 | // 判断白屏的时机
109 | export function isLoad(callback) {
110 | if (document.readyState === 'complete') {
111 | callback();
112 | } else {
113 | window.addEventListener('load', callback);
114 | }
115 | }
116 |
117 | // 计算用时
118 | export function calcDuration(metrics: httpMetrics) {
119 | metrics.duration = metrics.responseTime - metrics.requestTime;
120 | }
121 |
122 |
123 |
124 | const LoadSourceMap = (url) => axios.get(url)
125 |
126 | // 根据行数获取源文件行数 todo
127 | export async function getPosition (lineno, colno) {
128 | // // Get file content
129 | // const sourceMap = require('source-map');
130 | // const readFile = function (filePath) {
131 | // return new Promise(function (resolve, reject) {
132 | // fs.readFile(filePath, {encoding:'utf-8'},
133 | // function(error, data) {
134 | // if (error) {
135 | // console.log(error)
136 | // return reject(error);
137 | // }
138 | // resolve(JSON.parse(data));
139 | // });
140 | // });
141 | // };
142 | // // Find the source location
143 | // async function searchSource(filePath, line, column) {
144 | // const rawSourceMap = await readFile(filePath)
145 | // const consumer = await new sourceMap.SourceMapConsumer(rawSourceMap);
146 | // const res = consumer.originalPositionFor({ 'line' : line, 'column' : column });
147 | // consumer.destroy();
148 | // return res;
149 | // }
150 |
151 |
152 |
153 | // async function parse(url, lineno, colno) {
154 | // const mapObj = JSON.parse(getMapFileContent(url))
155 | // const consumer = await new sourceMap.SourceMapConsumer(mapObj)
156 | // // 将 webpack://source-map-demo/./src/index.js 文件中的 ./ 去掉
157 | // const sources = mapObj.sources.map(item => format(item))
158 | // // 根据压缩后的报错信息得出未压缩前的报错行列数和源码文件
159 | // const originalInfo = consumer.originalPositionFor({ line: lineno, column: colno })
160 | // // sourcesContent 中包含了各个文件的未压缩前的源码,根据文件名找出对应的源码
161 | // const originalFileContent = mapObj.sourcesContent[sources.indexOf(originalInfo.source)]
162 | // return {
163 | // file: originalInfo.source,
164 | // content: originalFileContent,
165 | // line: originalInfo.line,
166 | // column: originalInfo.column,
167 | // // msg: error.msg,
168 | // // error: error.error
169 | // }
170 | // }
171 | //
172 | // function format(item) {
173 | // return item.replace(/(\.\/)*/g, '')
174 | // }
175 | //
176 | // function getMapFileContent(url) {
177 | // return fs.readFileSync(path.resolve(__dirname, `./maps/${url.split('/').pop()}.map`), 'utf-8')
178 | // }
179 |
180 |
181 |
182 |
183 |
184 | // console.log('@@@@@', sourcemap)
185 | // var sourceMap = require('source-map');
186 | // var rawSourceMap = require(sourcemap);
187 | // sourceMap.SourceMapConsumer.with(rawSourceMap, null, consumer => {
188 | // console.log("originalPositionFor: ", "\n", consumer.originalPositionFor({ source: "./", line: 10, column: 74647 }));
189 | // });
190 | //
191 | // const sourceData = await LoadSourceMap("monitor.js.map")
192 | // const fileContent = sourceData.data;
193 | //
194 | // const consumer = await new sourceMap.SourceMapConsumer(fileContent);
195 | //
196 | // const position = consumer.originalPositionFor({
197 | // line: lineno,
198 | // column: colno
199 | // })
200 | //
201 | // console.log('@@源文件及错误行和列', position);
202 | //
203 | // // if (!position.source) return;
204 | //
205 | // // position.content = consumer.sourceContentFor(position.source)
206 | //
207 | // const co = consumer.sourceContentFor(position.source);
208 | // // co 包含了源文件所有的源码
209 | // const coList = co.split("\n");
210 | // // 按需取行即可
211 | // console.log('coList', coList)
212 | //
213 | // return position;
214 | }
215 |
216 | // 创建 js error 对象
217 | export function createJsError(
218 | message: any,
219 | type: any,
220 | filename: any,
221 | lineno: any,
222 | colno: any,
223 | selector: any
224 | ) {
225 | // todo sourceMap
226 | const obj = getPosition(lineno, colno);
227 | console.log('obj', obj);
228 |
229 | return new JsError(
230 | message,
231 | type,
232 | mechanismType.JS,
233 | filename,
234 | `${lineno}:${colno}`,
235 | selector
236 | );
237 | }
238 |
239 | // 创建 resource error 对象
240 | export function createResourceError(event: any, src: string, outerHTML: any, tagName: any) {
241 | return new ResourceError(
242 | event.type,
243 | src,
244 | `GET ${src} net::ERR_CONNECTION_REFUSED`,
245 | outerHTML,
246 | mechanismType.RS,
247 | tagName,
248 | getSelector(event.path)
249 | );
250 | }
251 |
252 | // 创建 cors error 对象
253 | export function createCorsError(
254 | name: string,
255 | message: string,
256 | url: string,
257 | method: string,
258 | response: any,
259 | request: any,
260 | params: any,
261 | data: any
262 | ) {
263 | return new CorsError(
264 | mechanismType.CS,
265 | name,
266 | message,
267 | url,
268 | method,
269 | response.status,
270 | response ? JSON.stringify(response) : '',
271 | request ? JSON.stringify(request) : '',
272 | { query: params, body: data }
273 | )
274 | }
275 |
276 | // 创建 promise error 对象
277 | export function createPromiseError(
278 | message: string,
279 | event: any,
280 | filename: string,
281 | line: number,
282 | column: number,
283 | selector: any
284 | ) {
285 | return new PromiseError(
286 | message,
287 | event.type,
288 | mechanismType.UJ,
289 | filename,
290 | `${line}:${column}`,
291 | selector
292 | )
293 | }
294 |
--------------------------------------------------------------------------------
/monitor-web/src/pages/Error/index.tsx:
--------------------------------------------------------------------------------
1 | import { PageContainer, ProCard, StatisticCard } from '@ant-design/pro-components';
2 | import RcResizeObserver from 'rc-resize-observer';
3 | import { useState, useEffect } from 'react';
4 | import { Area, RingProgress, DualAxes, Liquid } from '@ant-design/plots';
5 | // import style from './index.less';
6 | import { RightOutlined, QuestionCircleOutlined } from '@ant-design/icons';
7 | import ReactTooltip from "react-tooltip";
8 | import { useHistory } from 'react-router-dom'
9 | import jsError from '@/pages/Error/js';
10 | import { Link } from 'react-router-dom';
11 |
12 | const { Statistic, Divider } = StatisticCard;
13 |
14 | const getRingProgress2 = () => {
15 | const config = {
16 | height: 100,
17 | width: 100,
18 | percent: 0.25,
19 | outline: {
20 | border: 4,
21 | distance: 8,
22 | },
23 | wave: {
24 | length: 128,
25 | },
26 | };
27 | return ;
28 | };
29 | const getRingProgress = function () {
30 | const config2 = {
31 | height: 100,
32 | width: 100,
33 | autoFit: false,
34 | percent: 0.6,
35 | color: ['#F4664A', '#E8EDF3'],
36 | innerRadius: 0.85,
37 | radius: 0.98,
38 | statistic: {
39 | title: {
40 | style: {
41 | color: '#363636',
42 | fontSize: '12px',
43 | lineHeight: '14px',
44 | },
45 | formatter: () => 'JS错误',
46 | },
47 | },
48 | };
49 |
50 | return (
51 |
52 | )
53 | }
54 |
55 | export default () => {
56 | const history = useHistory();
57 | const [responsive, setResponsive] = useState(false);
58 | const [data, setData] = useState([]);
59 |
60 | useEffect(() => {
61 | asyncFetch();
62 | }, []);
63 |
64 | const asyncFetch = () => {
65 | fetch('https://gw.alipayobjects.com/os/bmw-prod/360c3eae-0c73-46f0-a982-4746a6095010.json')
66 | .then((response) => response.json())
67 | .then((json) => setData(json))
68 | .catch((error) => {
69 | console.log('fetch data failed', error);
70 | });
71 | };
72 |
73 | const config = {
74 | data: [
75 | { errorNum: 15, time: '13日01:00' },
76 | { errorNum: 45, time: '12日04:00' },
77 | { errorNum: 75, time: '11日06:00' },
78 | { errorNum: 95, time: '13日08:00' },
79 | { errorNum: 125, time: '13日10:00' },
80 | { errorNum: 150, time: '13日11:00' },
81 | { errorNum: 170, time: '13日24:00' },
82 | ],
83 | xField: 'time',
84 | yField: 'errorNum',
85 | xAxis: {
86 | range: [0, 1],
87 | },
88 | };
89 |
90 | const data2 = [
91 | {
92 | year: '1991',
93 | value: 3,
94 | count: 10,
95 | },
96 | {
97 | year: '1992',
98 | value: 4,
99 | count: 4,
100 | },
101 | {
102 | year: '1993',
103 | value: 3.5,
104 | count: 5,
105 | },
106 | {
107 | year: '1994',
108 | value: 5,
109 | count: 5,
110 | },
111 | {
112 | year: '1995',
113 | value: 4.9,
114 | count: 4.9,
115 | },
116 | {
117 | year: '1996',
118 | value: 6,
119 | count: 35,
120 | },
121 | {
122 | year: '1997',
123 | value: 7,
124 | count: 7,
125 | },
126 | {
127 | year: '1998',
128 | value: 9,
129 | count: 1,
130 | },
131 | {
132 | year: '1999',
133 | value: 13,
134 | count: 20,
135 | },
136 | ];
137 | const config3 = {
138 | data: [data2, data2],
139 | xField: 'year',
140 | yField: ['value', 'count'],
141 | geometryOptions: [
142 | {
143 | geometry: 'line',
144 | smooth: false,
145 | color: '#5B8FF9',
146 | label: {
147 | formatter: (datum) => {
148 | return `${datum.value}个`;
149 | },
150 | },
151 | lineStyle: {
152 | lineWidth: 3,
153 | lineDash: [5, 5],
154 | },
155 | },
156 | {
157 | geometry: 'line',
158 | smooth: true,
159 | color: '#5AD8A6',
160 | lineStyle: {
161 | lineWidth: 4,
162 | opacity: 0.5,
163 | },
164 | label: {
165 | formatter: (datum) => {
166 | return `${datum.count}个`;
167 | },
168 | },
169 | point: {
170 | shape: 'circle',
171 | size: 4,
172 | style: {
173 | opacity: 0.5,
174 | stroke: '#5AD8A6',
175 | fill: '#fff',
176 | },
177 | },
178 | },
179 | ],
180 | };
181 |
182 | return (
183 |
184 | {/*
*/}
185 |
186 |
187 |
188 | {getRingProgress()}
189 |
190 |
191 |
192 | {getRingProgress()}
193 |
194 |
195 |
196 | {getRingProgress()}
197 |
198 |
199 |
200 | {getRingProgress()}
201 |
202 |
203 |
204 |
205 |
206 | {getRingProgress()}
207 |
208 |
209 |
210 | {getRingProgress()}
211 |
212 |
213 |
214 | {getRingProgress()}
215 |
216 |
217 |
218 | {getRingProgress()}
219 |
220 |
221 |
222 |
223 |
224 |
225 | history.push({ pathname: '/error/js' })}>JS异常}
227 | extra='2022-9-13'
228 | tooltip='程序运行时出错,使用window.onerror进行异常捕获并上报'
229 | headerBordered
230 | gutter={8}
231 | style={{ maxWidth: 300, marginTop: 20 }}
232 | >
233 | {/*
2022-9-13
*/}
234 |
235 |
236 |
237 |
history.push({ pathname: '/error/js' })}>Promise异常 }
239 | extra='2022-9-13'
240 | tooltip='程序运行时出错,使用window.unhandledrejection进行异常捕获并上报'
241 | headerBordered
242 | gutter={8}
243 | style={{ maxWidth: 300, marginTop: 20 }}
244 | >
245 | {/*2022-9-13
*/}
246 |
247 |
248 |
249 | history.push({ pathname: '/error/js' })}>资源异常}
251 | extra='2022-9-13'
252 | tooltip='静态资源加载失败的数量统计'
253 | headerBordered
254 | gutter={8}
255 | style={{ maxWidth: 300, marginTop: 20 }}
256 | >
257 | {/*2022-9-13
*/}
258 |
259 |
260 |
261 |
262 |
263 | history.push({ pathname: '/error/js' })}>console.error异常}
265 | extra='2022-9-13'
266 | tooltip='用户自定义报错,console.error打印的错误均视为自定义错误'
267 | headerBordered
268 | gutter={8}
269 | style={{ maxWidth: 300, marginTop: 20 }}
270 | >
271 | {/*2022-9-13
*/}
272 |
273 |
274 |
275 | history.push({ pathname: '/error/js' })}>跨域异常}
277 | extra='2022-9-13'
278 | tooltip="程序运行时出错,使用window.addEventListener('error')进行异常捕获并上报"
279 | headerBordered
280 | gutter={8}
281 | style={{ maxWidth: 300, marginTop: 20 }}
282 | >
283 | {/*2022-9-13
*/}
284 |
285 |
286 |
287 | history.push({ pathname: '/error/js' })}>白屏异常}
289 | extra='2022-9-13'
290 | tooltip='用户自定义报错,console.error打印的错误均视为自定义错误'
291 | headerBordered
292 | gutter={8}
293 | style={{ maxWidth: 300, marginTop: 20 }}
294 | >
295 | {/*2022-9-13
*/}
296 |
297 |
298 |
299 |
300 |
301 | history.push({ pathname: '/error/js' })}>接口异常}
303 | extra='2022-9-13'
304 | tooltip='程序运行时出错,使用window.onerror进行异常捕获并上报'
305 | headerBordered
306 | gutter={8}
307 | style={{ maxWidth: 300, marginTop: 20 }}
308 | >
309 | {/*2022-9-13
*/}
310 |
311 |
312 |
313 |
314 |
315 | );
316 | };
317 |
318 |
--------------------------------------------------------------------------------
/monitor-sdk/src/monitor/handle/pageTrack.ts:
--------------------------------------------------------------------------------
1 | import { metricsName, PageInformation, routeType, userAgent } from "@type/index";
2 | import { lazyReport } from "@monitor/report/index";
3 | import bowser from "bowser";
4 | import parser from "ua-parser-js";
5 |
6 | window.addEventListener('click', (e) => {
7 | getClickInform(e);
8 | }, true);
9 |
10 | // 获取 userAgent 信息,如:用户设备类型,浏览器版本,webview引擎类型
11 | export const getUserAgent = function (): userAgent {
12 | const browserData = bowser.parse(navigator.userAgent);
13 | const parserData = parser(navigator.userAgent);
14 |
15 | const browserName = browserData.browser.name || parserData.browser.name; // 浏览器名
16 | const browserVersion = browserData.browser.version || parserData.browser.version; // 浏览器版本号
17 | const osName = browserData.os.name || parserData.os.name; // 操作系统名
18 | const osVersion = parserData.os.version || browserData.os.version; // 操作系统版本号
19 | const deviceType = browserData.platform.type || parserData.device.type; // 设备类型
20 | const deviceVendor = browserData.platform.vendor || parserData.device.vendor || ''; // 设备所属公司
21 | const deviceModel = browserData.platform.model || parserData.device.model || ''; // 设备型号
22 | const engineName = browserData.engine.name || parserData.engine.name; // engine名
23 | const engineVersion = browserData.engine.version || parserData.engine.version; // engine版本号
24 | const cpuArchitecture = parserData.cpu.architecture;
25 | const ua = parserData.ua;
26 |
27 | const userAgentObj: userAgent = {
28 | browserName,
29 | browserVersion,
30 | osName,
31 | osVersion,
32 | deviceType,
33 | deviceVendor,
34 | deviceModel,
35 | engineName,
36 | engineVersion,
37 | cpuArchitecture,
38 | ua
39 | }
40 | return userAgentObj;
41 | }
42 | console.log('%c%s%o', 'color: green', '获取 userAgent 信息,如:用户设备类型,浏览器版本,webview引擎类型', getUserAgent());
43 |
44 | // 获取 PI 页面基本信息
45 | export const getPageInfo = function (target?: Element): PageInformation {
46 | const {
47 | host,
48 | hostname,
49 | href,
50 | protocol,
51 | origin,
52 | port,
53 | pathname,
54 | search,
55 | hash
56 | } = window.location;
57 | const { width, height } = window.screen;
58 | const docWidth = document.documentElement.clientWidth || document.body.clientWidth;
59 | const docHeight = document.documentElement.clientHeight || document.body.clientHeight;
60 | const { language } = navigator;
61 | const userAgent = getUserAgent();
62 | const { top, left } = target?.getBoundingClientRect();
63 |
64 | const pageInfoObj: PageInformation = {
65 | host,
66 | hostname,
67 | href,
68 | protocol,
69 | origin,
70 | port,
71 | pathname,
72 | search,
73 | hash,
74 | userAgent,
75 | category: metricsName.PI,
76 | title: document.title,
77 | language: language.substring(0, 2),
78 | windowScreen: `${width} x ${height}`,
79 | docScreen: `${docWidth} x ${docHeight}`,
80 | nodeScreen: `${top} x ${left}`,
81 | }
82 | return pageInfoObj;
83 | };
84 |
85 | // 初始化 CBR 点击事件的获取和返回
86 | const clickMountList = ['button'].map((x) => x.toLowerCase());
87 | export const getClickInform = function (e: MouseEvent | any): void {
88 | // 这里是根据 tagName 进行是否需要捕获事件的依据,可以根据自己的需要,额外判断id\class等
89 | // 先判断浏览器支持 e.path ,从 path 里先取
90 | let target = e.path?.find((x: Element) => clickMountList.includes(x.tagName?.toLowerCase()));
91 | // 不支持 path 就再判断 target
92 | target = target || (clickMountList.includes(e.target.tagName?.toLowerCase()) ? e.target : undefined);
93 | if (!target) return;
94 |
95 | const metrics = {
96 | tagInfo: {
97 | id: target.id,
98 | classList: Array.from(target?.classList),
99 | tagName: target?.tagName,
100 | text: target?.textContent,
101 | html: document.documentElement.outerHTML,
102 | inner: target?.outerHTML,
103 | },
104 | timestamp: new Date().getTime(), // 点击的时间
105 | pageInfo: getPageInfo(target), // 页面信息
106 | };
107 |
108 | // 除开商城业务外,一般不会特意上报点击行为的数据,都是作为辅助检查错误的数据存在;
109 | // this.metrics.add(metricsName.CBR, metrics);
110 | // 行为记录 不需要携带 完整的pageInfo
111 | // delete metrics.pageInfo;
112 | // 记录到行为记录追踪
113 | const behavior = {
114 | category: metricsName.CBR,
115 | data: metrics,
116 | };
117 | const oldBehavior = localStorage.getItem('click_behavior');
118 | let newBehavior = [];
119 | if (oldBehavior) {
120 | newBehavior = JSON.parse(oldBehavior);
121 | }
122 | newBehavior.push(behavior);
123 | localStorage.setItem('click_behavior', JSON.stringify(newBehavior));
124 |
125 | if (newBehavior.length > 50) {
126 | // lazyReport('/hash', behavior);
127 | }
128 | console.log('%c%s%o', 'color: green', '点击事件 log数据', behavior)
129 | };
130 | console.log('%c%s', 'color: green', '页面点击事件已监控');
131 |
132 |
133 | // 重写 pushState 和 replaceState 方法
134 | const createHistoryEvent = function (name) {
135 | // 拿到原来的处理方法
136 | const origin = window.history[name];
137 |
138 | return function (event) {
139 | if (name === 'replaceState') {
140 | const { current } = event;
141 | const pathName = location.pathname;
142 | if (current === pathName) {
143 | let res = origin.apply(this, arguments);
144 | return res;
145 | }
146 | }
147 |
148 | let res = origin.apply(this, arguments);
149 | let e = new Event(name);
150 | // @ts-ignore
151 | e.arguments = arguments;
152 | window.dispatchEvent(e);
153 | return res;
154 | };
155 | };
156 |
157 | // history路由监听
158 | export function historyPageTrack (): void {
159 | console.log('%c%s', 'font-size: 24px; color: skyblue', '开始监控 history 路由跳转');
160 |
161 | let beforeTime = Date.now(); // 进入页面的时间
162 | let beforeUrl: string = ''; // 上一个页面
163 |
164 | // 获取在某个页面的停留时间
165 | function getDuration(): { curTime: number, duration: number } {
166 | let curTime = Date.now();
167 | let duration = curTime - beforeTime;
168 | beforeTime = curTime;
169 | return { curTime, duration };
170 | }
171 |
172 | window.history.pushState = createHistoryEvent('pushState');
173 | window.history.replaceState = createHistoryEvent('replaceState');
174 |
175 | // 记录当前页面信息,并更新上一个页面的 endTime
176 | const recordBehaviors = () => {
177 | console.log('%c%s%s', 'color: skyblue', 'history路由跳转beforeUrl', beforeUrl);
178 | // let routeList: routeType[] = [];
179 | const currentUrl = window.location.href;
180 | let time = Date.now();
181 | // let behaviors = localStorage.getItem('current_behavior');
182 | const typeNum = window.performance?.navigation.type;
183 | let type = '';
184 | switch (typeNum) {
185 | case 0:
186 | type = '点击链接、地址栏输入、表单提交、脚本操作等';
187 | break;
188 | case 1:
189 | type = '点击重新加载按钮、location.reload';
190 | break;
191 | case 2:
192 | type = '点击前进或后退按钮';
193 | break;
194 | case 3:
195 | type = '任何其他来源。即非刷新/非前进后退、非点击链接/地址栏输入/表单提交/脚本操作等';
196 | break;
197 | }
198 | const routeTemplate: routeType = {
199 | beforeUrl,
200 | currentUrl,
201 | type,
202 | startTime: time,
203 | duration: getDuration().duration,
204 | endTime: getDuration().curTime,
205 | }
206 | beforeUrl = currentUrl;
207 | // if (behaviors) {
208 | // routeList = JSON.parse(behaviors);
209 | // const len = routeList.length;
210 | // if (len > 0) {
211 | // routeList[len - 1].endTime = time;
212 | // routeList[len - 1].duration = time - routeList[len - 1].startTime;
213 | // }
214 | // }
215 | // routeList.push(routeTemplate);
216 | console.log('%c%s%o', 'color: skyblue', 'routeTemplate', routeTemplate);
217 | lazyReport('/history', routeTemplate);
218 | // localStorage.setItem('current_behavior', JSON.stringify(routeList));
219 | }
220 |
221 | [
222 | // history.go()、history.back()、history.forward() 监听
223 | 'popstate',
224 | // history.pushState() 监听
225 | 'pushState',
226 | // history.replaceState() 监听
227 | 'replaceState',
228 | // 页面 load 监听
229 | 'load',
230 | // 页面 beforeunload 监听
231 | 'beforeunload'
232 | ].map((type: string) => {
233 | window.addEventListener(type, function () {
234 | if (type === 'beforeunload') {
235 | // 上报
236 | }
237 | recordBehaviors();
238 | })
239 | });
240 | }
241 |
242 | // hash路由监听
243 | export function hashPageTrack (): void {
244 | console.log('%c%s', 'font-size: 24px; color: skyblue', '开始监控 hash 路由跳转');
245 |
246 | let beforeTime: number = Date.now(); // 进入页面的时间
247 | let beforeUrl: string = ''; // 上一个页面
248 |
249 | // 获取在某个页面的停留时间
250 | function getDuration(): { curTime: number, duration: number } {
251 | let curTime: number = Date.now();
252 | let duration: number = curTime - beforeTime;
253 | beforeTime = curTime;
254 | return { curTime, duration };
255 | }
256 |
257 | // 记录当前页面信息,并更新上一个页面的 endTime
258 | const recordBehaviors = function (): void {
259 | const currentUrl: string = window.location.href;
260 | let time: number = Date.now();
261 | const typeNum: number = window.performance?.navigation.type;
262 | let type: string = '';
263 | switch (typeNum) {
264 | case 0:
265 | type = '点击链接、地址栏输入、表单提交、脚本操作等';
266 | break;
267 | case 1:
268 | type = '点击重新加载按钮、location.reload';
269 | break;
270 | case 2:
271 | type = '点击前进或后退按钮';
272 | break;
273 | case 3:
274 | type = '任何其他来源。即非刷新/非前进后退、非点击链接/地址栏输入/表单提交/脚本操作等';
275 | break;
276 | }
277 | const routeTemplate: routeType = {
278 | beforeUrl,
279 | currentUrl,
280 | type,
281 | startTime: time,
282 | duration: getDuration().duration,
283 | endTime: getDuration().curTime,
284 | }
285 | beforeUrl = currentUrl;
286 | lazyReport('/hash', routeTemplate);
287 | console.log('%c%s%o', 'color: skyblue', 'routeTemplate', routeTemplate);
288 | }
289 |
290 | window.history.pushState = createHistoryEvent('pushState');
291 |
292 | [
293 | // history.hash() 监听
294 | 'hashchange',
295 | // history.pushState() 监听
296 | 'pushState',
297 | // 页面 load 监听
298 | 'load',
299 | // 页面 beforeunload 监听
300 | 'beforeunload'
301 | ].map((type: string) => {
302 | window.addEventListener(type, function () {
303 | recordBehaviors();
304 | })
305 | });
306 | }
307 |
--------------------------------------------------------------------------------
/monitor-node/src/routes/doc/api.ts:
--------------------------------------------------------------------------------
1 | // 定义模型 可以公用 schema $ref
2 | /**
3 | * @swagger
4 | * definitions:
5 | * Login:
6 | * required:
7 | * - username
8 | * - password
9 | * properties:
10 | * username:
11 | * type: string
12 | * password:
13 | * type: string
14 | * path:
15 | * type: string
16 | * ReportSuccess:
17 | * type: object
18 | * required:
19 | * - code
20 | * - msg
21 | * - data
22 | * properties:
23 | * code:
24 | * type: integer
25 | * format: int64
26 | * example: 200
27 | * msg:
28 | * type: string
29 | * example: 数据写入成功
30 | * data:
31 | * type: object
32 | * properties:
33 | * status:
34 | * type: boolean
35 | * example: true
36 | * response:
37 | * type: object
38 | * ReportFail:
39 | * type: object
40 | * required:
41 | * - code
42 | * - msg
43 | * - data
44 | * properties:
45 | * code:
46 | * type: integer
47 | * format: int64
48 | * example: 500
49 | * msg:
50 | * type: string
51 | * example: 数据写入失败
52 | * data:
53 | * type: object
54 | * properties:
55 | * status:
56 | * type: boolean
57 | * example: false
58 | * FindSuccess:
59 | * type: object
60 | * required:
61 | * - code
62 | * - msg
63 | * - data
64 | * properties:
65 | * code:
66 | * type: integer
67 | * format: int64
68 | * example: 200
69 | * msg:
70 | * type: string
71 | * example: 数据查询成功
72 | * data:
73 | * type: object
74 | * properties:
75 | * status:
76 | * type: boolean
77 | * example: true
78 | * response:
79 | * type: object
80 | * FindFail:
81 | * type: object
82 | * required:
83 | * - code
84 | * - msg
85 | * - data
86 | * properties:
87 | * code:
88 | * type: integer
89 | * format: int64
90 | * example: 500
91 | * msg:
92 | * type: string
93 | * example: 数据查询失败
94 | * data:
95 | * type: object
96 | * properties:
97 | * status:
98 | * type: boolean
99 | * example: false
100 | */
101 |
102 | // promiseError 接口
103 | /**
104 | * @swagger
105 | * /index/promise-error:
106 | * post:
107 | * description: sdk 向服务器上报 promise 异常数据
108 | * tags: [promise 异常模块]
109 | * produces:
110 | * - application/json
111 | * parameters:
112 | * - name: cookie
113 | * description: 用户 cookie
114 | * required: true
115 | * in: formData
116 | * type: string
117 | * - name: message
118 | * description: 报错信息
119 | * required: true
120 | * in: formData
121 | * type: string
122 | * - name: type
123 | * description: 数据类型
124 | * in: formData
125 | * required: true
126 | * type: string
127 | * - name: errorType
128 | * description: 错误类型
129 | * in: formData
130 | * required: true
131 | * type: string
132 | * - name: fileName
133 | * description: 报错文件位置
134 | * in: formData
135 | * required: true
136 | * type: string
137 | * - name: position
138 | * description: 报错信息在文件中的定位
139 | * in: formData
140 | * required: true
141 | * type: string
142 | * - name: selector
143 | * description: 错误堆栈
144 | * in: formData
145 | * required: true
146 | * type: string
147 | * - name: is_solve
148 | * description: 报错是否解决
149 | * in: formData
150 | * required: false
151 | * type: boolean
152 | * responses:
153 | * '200':
154 | * description: 数据写入成功
155 | * schema:
156 | * $ref: '#/definitions/ReportSuccess'
157 | * '500':
158 | * description: 数据写入失败
159 | * schema:
160 | * $ref: '#/definitions/ReportFail'
161 | */
162 | /**
163 | * @swagger
164 | * /api/promise-error?time=-30d:
165 | * get:
166 | * description: web 管理端向服务器请求 promise 异常数据
167 | * tags: [promise 异常模块]
168 | * produces:
169 | * - application/json
170 | * responses:
171 | * '200':
172 | * description: promise 异常数据查询成功
173 | * schema:
174 | * $ref: '#/definitions/FindSuccess'
175 | * '500':
176 | * description: promise 异常数据查询失败
177 | * schema:
178 | * $ref: '#/definitions/FindFail'
179 | * '0':
180 | * description: promise 异常数据查询失败,查无此数据
181 | * schema:
182 | * type: object
183 | * required:
184 | * - code
185 | * - msg
186 | * - data
187 | * properties:
188 | * code:
189 | * type: integer
190 | * format: int64
191 | * example: 0
192 | * msg:
193 | * type: string
194 | * example: 查无此数据
195 | * data:
196 | * type: object
197 | * properties:
198 | * status:
199 | * type: boolean
200 | * example: false
201 | */
202 |
203 | // console.error 接口
204 | /**
205 | * @swagger
206 | * /index/console-error:
207 | * post:
208 | * description: sdk 向服务器上报 console.error 异常数据
209 | * tags: [console.error 异常模块]
210 | * produces:
211 | * - application/json
212 | * parameters:
213 | * - name: url
214 | * description: 报错 url
215 | * required: true
216 | * in: formData
217 | * type: string
218 | * - name: row
219 | * description: 报错位置的行号
220 | * in: formData
221 | * required: true
222 | * type: integer
223 | * - name: column
224 | * description: 报错位置的列号
225 | * in: formData
226 | * required: true
227 | * type: integer
228 | * - name: message
229 | * description: 报错信息
230 | * in: formData
231 | * required: true
232 | * type: string
233 | * - name: stack
234 | * description: 错误堆栈
235 | * in: formData
236 | * required: true
237 | * type: string
238 | * - name: is_solve
239 | * description: 开发者是否解决异常
240 | * in: formData
241 | * required: false
242 | * type: boolean
243 | * responses:
244 | * '200':
245 | * description: 数据写入成功
246 | * schema:
247 | * $ref: '#/definitions/ReportSuccess'
248 | * '500':
249 | * description: 数据写入失败
250 | * schema:
251 | * $ref: '#/definitions/ReportFail'
252 | */
253 | /**
254 | * @swagger
255 | * /api/console-error:
256 | * get:
257 | * description: web 管理端向服务器请求 promise 异常数据
258 | * tags: [console.error 异常模块]
259 | * produces:
260 | * - application/json
261 | * responses:
262 | * '200':
263 | * description: console.error 异常数据查询成功
264 | * schema:
265 | * $ref: '#/definitions/FindSuccess'
266 | * '500':
267 | * description: console.error 异常数据查询失败
268 | * schema:
269 | * $ref: '#/definitions/FindFail'
270 | */
271 |
272 | // 登录接口
273 | /**
274 | * @swagger
275 | * /api/send-captcha:
276 | * post:
277 | * description: 发送验证码
278 | * tags: [用户登入模块]
279 | * produces:
280 | * - application/json
281 | * parameters:
282 | * - name: email
283 | * description: 邮箱账号
284 | * in: formData
285 | * required: true
286 | * type: string
287 | * - name: password
288 | * description: 密码
289 | * in: formData
290 | * required: false
291 | * type: string
292 | * responses:
293 | * '200':
294 | * description: 成功发送验证码
295 | * schema:
296 | * type: object
297 | * required:
298 | * - code
299 | * - msg
300 | * - data
301 | * properties:
302 | * code:
303 | * type: integer
304 | * format: int64
305 | * example: 200
306 | * msg:
307 | * type: string
308 | * example: 验证码已成功发送
309 | * data:
310 | * type: object
311 | * properties:
312 | * status:
313 | * type: boolean
314 | * example: true
315 | * captchaTime:
316 | * type: integer
317 | * format: int64
318 | */
319 | /**
320 | * @swagger
321 | * /api/login:
322 | * post:
323 | * description: 用户登录
324 | * tags: [用户登入模块]
325 | * produces:
326 | * - application/json
327 | * parameters:
328 | * - name: email
329 | * description: 邮箱账号
330 | * in: formData
331 | * required: true
332 | * type: string
333 | * - name: password
334 | * description: 密码
335 | * in: formData
336 | * required: false
337 | * type: string
338 | * - name: captcha
339 | * description: 验证码
340 | * in: formData
341 | * required: false
342 | * type: integer
343 | * - name: cookie
344 | * description: 用户 cookie
345 | * in: formData
346 | * required: false
347 | * type: string
348 | * responses:
349 | * '200':
350 | * description: 登录成功的响应
351 | * schema:
352 | * type: object
353 | * required:
354 | * - code
355 | * - msg
356 | * - data
357 | * properties:
358 | * code:
359 | * type: integer
360 | * format: int64
361 | * example: 200
362 | * msg:
363 | * type: string
364 | * example: 登录成功
365 | * data:
366 | * type: object
367 | * properties:
368 | * status:
369 | * type: boolean
370 | * example: true
371 | */
372 | /**
373 | * @swagger
374 | * /api/register:
375 | * post:
376 | * description: 用户注册
377 | * tags: [用户登入模块]
378 | * produces:
379 | * - application/json
380 | * parameters:
381 | * - name: email
382 | * description: 邮箱账号
383 | * in: formData
384 | * required: true
385 | * type: string
386 | * - name: password
387 | * description: 密码
388 | * in: formData
389 | * required: true
390 | * type: string
391 | * - name: captcha
392 | * description: 验证码
393 | * in: formData
394 | * required: true
395 | * type: integer
396 | * responses:
397 | * '200':
398 | * description: 注册成功的响应
399 | * schema:
400 | * type: object
401 | * required:
402 | * - code
403 | * - msg
404 | * - data
405 | * properties:
406 | * code:
407 | * type: integer
408 | * format: int64
409 | * example: 200
410 | * msg:
411 | * type: string
412 | * example: 注册成功
413 | * data:
414 | * type: object
415 | * properties:
416 | * status:
417 | * type: boolean
418 | * example: true
419 | */
420 |
--------------------------------------------------------------------------------
/monitor-web/src/pages/Performance/index.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import { PageContainer, ProCard, StatisticCard, ProFormDatePicker } from '@ant-design/pro-components';
3 | import { Column, Area } from '@ant-design/plots';
4 |
5 | const { Statistic } = StatisticCard;
6 |
7 |
8 | const getPerformColumn = function () {
9 | const data = [
10 | {
11 | type: '家具家电',
12 | sales: 38,
13 | },
14 | {
15 | type: '粮油副食',
16 | sales: 52,
17 | },
18 | {
19 | type: '生鲜水果',
20 | sales: 61,
21 | },
22 | {
23 | type: '美容洗护',
24 | sales: 145,
25 | },
26 | {
27 | type: '母婴用品',
28 | sales: 48,
29 | },
30 | {
31 | type: '进口食品',
32 | sales: 38,
33 | },
34 | {
35 | type: '食品饮料',
36 | sales: 38,
37 | },
38 | {
39 | type: '家庭清洁',
40 | sales: 38,
41 | },
42 | ];
43 |
44 | const config = {
45 | data,
46 | xField: 'type',
47 | yField: 'sales',
48 | autoFit: true,
49 | // width: 100,
50 | // height: 100,
51 | label: {
52 | // 可手动配置 label 数据标签位置
53 | position: 'middle',
54 | // 'top', 'bottom', 'middle',
55 | // 配置样式
56 | style: {
57 | fill: '#FFFFFF',
58 | opacity: 0.6,
59 | },
60 | },
61 | xAxis: {
62 | label: {
63 | autoHide: true,
64 | autoRotate: false,
65 | },
66 | },
67 | meta: {
68 | type: {
69 | alias: '类别',
70 | },
71 | sales: {
72 | alias: '销售额',
73 | },
74 | },
75 | };
76 |
77 | return (
78 |
79 | )
80 | }
81 |
82 | const getPerformArea = function () {
83 | const config = {
84 | data: [
85 | {
86 | "Date": "2010-01",
87 | "scales": 1998
88 | },
89 | {
90 | "Date": "2010-02",
91 | "scales": 1850
92 | },
93 | {
94 | "Date": "2010-03",
95 | "scales": 1720
96 | },
97 | {
98 | "Date": "2010-04",
99 | "scales": 1818
100 | },
101 | {
102 | "Date": "2010-05",
103 | "scales": 1920
104 | },
105 | {
106 | "Date": "2010-06",
107 | "scales": 1802
108 | },
109 | {
110 | "Date": "2010-07",
111 | "scales": 1945
112 | },
113 | {
114 | "Date": "2010-08",
115 | "scales": 1856
116 | },
117 | {
118 | "Date": "2010-09",
119 | "scales": 2107
120 | },
121 | {
122 | "Date": "2010-10",
123 | "scales": 2140
124 | },
125 | {
126 | "Date": "2010-11",
127 | "scales": 2311
128 | },
129 | {
130 | "Date": "2010-12",
131 | "scales": 1972
132 | },
133 | {
134 | "Date": "2011-01",
135 | "scales": 1760
136 | },
137 | {
138 | "Date": "2011-02",
139 | "scales": 1824
140 | },
141 | {
142 | "Date": "2011-03",
143 | "scales": 1801
144 | },
145 | {
146 | "Date": "2011-04",
147 | "scales": 2001
148 | },
149 | {
150 | "Date": "2011-05",
151 | "scales": 1640
152 | },
153 | {
154 | "Date": "2011-06",
155 | "scales": 1502
156 | },
157 | {
158 | "Date": "2011-07",
159 | "scales": 1621
160 | },
161 | {
162 | "Date": "2011-08",
163 | "scales": 1480
164 | },
165 | {
166 | "Date": "2011-09",
167 | "scales": 1549
168 | },
169 | {
170 | "Date": "2011-10",
171 | "scales": 1390
172 | },
173 | {
174 | "Date": "2011-11",
175 | "scales": 1325
176 | },
177 | {
178 | "Date": "2011-12",
179 | "scales": 1250
180 | },
181 | {
182 | "Date": "2012-01",
183 | "scales": 1394
184 | },
185 | {
186 | "Date": "2012-02",
187 | "scales": 1406
188 | },
189 | {
190 | "Date": "2012-03",
191 | "scales": 1578
192 | },
193 | {
194 | "Date": "2012-04",
195 | "scales": 1465
196 | },
197 | {
198 | "Date": "2012-05",
199 | "scales": 1689
200 | },
201 | {
202 | "Date": "2012-06",
203 | "scales": 1755
204 | },
205 | {
206 | "Date": "2012-07",
207 | "scales": 1495
208 | },
209 | {
210 | "Date": "2012-08",
211 | "scales": 1508
212 | },
213 | {
214 | "Date": "2012-09",
215 | "scales": 1433
216 | },
217 | {
218 | "Date": "2012-10",
219 | "scales": 1344
220 | },
221 | {
222 | "Date": "2012-11",
223 | "scales": 1201
224 | },
225 | {
226 | "Date": "2012-12",
227 | "scales": 1065
228 | },
229 | {
230 | "Date": "2013-01",
231 | "scales": 1255
232 | },
233 | {
234 | "Date": "2013-02",
235 | "scales": 1429
236 | },
237 | {
238 | "Date": "2013-03",
239 | "scales": 1398
240 | },
241 | {
242 | "Date": "2013-04",
243 | "scales": 1678
244 | },
245 | {
246 | "Date": "2013-05",
247 | "scales": 1524
248 | },
249 | {
250 | "Date": "2013-06",
251 | "scales": 1688
252 | },
253 | {
254 | "Date": "2013-07",
255 | "scales": 1500
256 | },
257 | {
258 | "Date": "2013-08",
259 | "scales": 1670
260 | },
261 | {
262 | "Date": "2013-09",
263 | "scales": 1734
264 | },
265 | {
266 | "Date": "2013-10",
267 | "scales": 1699
268 | },
269 | {
270 | "Date": "2013-11",
271 | "scales": 1508
272 | },
273 | {
274 | "Date": "2013-12",
275 | "scales": 1680
276 | },
277 | {
278 | "Date": "2014-01",
279 | "scales": 1750
280 | },
281 | {
282 | "Date": "2014-02",
283 | "scales": 1602
284 | },
285 | {
286 | "Date": "2014-03",
287 | "scales": 1834
288 | },
289 | {
290 | "Date": "2014-04",
291 | "scales": 1722
292 | },
293 | {
294 | "Date": "2014-05",
295 | "scales": 1430
296 | },
297 | {
298 | "Date": "2014-06",
299 | "scales": 1280
300 | },
301 | {
302 | "Date": "2014-07",
303 | "scales": 1367
304 | },
305 | {
306 | "Date": "2014-08",
307 | "scales": 1155
308 | },
309 | {
310 | "Date": "2014-09",
311 | "scales": 1289
312 | },
313 | {
314 | "Date": "2014-10",
315 | "scales": 1104
316 | },
317 | {
318 | "Date": "2014-11",
319 | "scales": 1246
320 | },
321 | {
322 | "Date": "2014-12",
323 | "scales": 1098
324 | },
325 | {
326 | "Date": "2015-01",
327 | "scales": 1189
328 | },
329 | {
330 | "Date": "2015-02",
331 | "scales": 1276
332 | },
333 | {
334 | "Date": "2015-03",
335 | "scales": 1033
336 | },
337 | {
338 | "Date": "2015-04",
339 | "scales": 956
340 | },
341 | {
342 | "Date": "2015-05",
343 | "scales": 845
344 | },
345 | {
346 | "Date": "2015-06",
347 | "scales": 1089
348 | },
349 | {
350 | "Date": "2015-07",
351 | "scales": 944
352 | },
353 | {
354 | "Date": "2015-08",
355 | "scales": 1043
356 | },
357 | {
358 | "Date": "2015-09",
359 | "scales": 893
360 | },
361 | {
362 | "Date": "2015-10",
363 | "scales": 840
364 | },
365 | {
366 | "Date": "2015-11",
367 | "scales": 934
368 | },
369 | {
370 | "Date": "2015-12",
371 | "scales": 810
372 | },
373 | {
374 | "Date": "2016-01",
375 | "scales": 782
376 | },
377 | {
378 | "Date": "2016-02",
379 | "scales": 1089
380 | },
381 | {
382 | "Date": "2016-03",
383 | "scales": 745
384 | },
385 | {
386 | "Date": "2016-04",
387 | "scales": 680
388 | },
389 | {
390 | "Date": "2016-05",
391 | "scales": 802
392 | },
393 | {
394 | "Date": "2016-06",
395 | "scales": 697
396 | },
397 | {
398 | "Date": "2016-07",
399 | "scales": 583
400 | },
401 | {
402 | "Date": "2016-08",
403 | "scales": 456
404 | },
405 | {
406 | "Date": "2016-09",
407 | "scales": 524
408 | },
409 | {
410 | "Date": "2016-10",
411 | "scales": 398
412 | },
413 | {
414 | "Date": "2016-11",
415 | "scales": 278
416 | },
417 | {
418 | "Date": "2016-12",
419 | "scales": 195
420 | },
421 | {
422 | "Date": "2017-01",
423 | "scales": 145
424 | },
425 | {
426 | "Date": "2017-02",
427 | "scales": 207
428 | }
429 | ],
430 | xField: 'Date',
431 | yField: 'scales',
432 | annotations: [
433 | {
434 | type: 'text',
435 | position: ['min', 'median'],
436 | content: '中位数',
437 | offsetY: -4,
438 | style: {
439 | textBaseline: 'bottom',
440 | },
441 | },
442 | {
443 | type: 'line',
444 | start: ['min', 'median'],
445 | end: ['max', 'median'],
446 | style: {
447 | stroke: 'red',
448 | lineDash: [2, 2],
449 | },
450 | },
451 | ],
452 | };
453 |
454 | return (
455 |
456 | )
457 | }
458 | export default () => {
459 |
460 | // const [date, setDate] = useState([]);
461 | //
462 | // useEffect(() => {
463 | // asyncFetch();
464 | // }, []);
465 | //
466 | // const asyncFetch = () => {
467 | // fetch('https://gw.alipayobjects.com/os/bmw-prod/1d565782-dde4-4bb6-8946-ea6a38ccf184.json')
468 | // .then((response) => response.json())
469 | // .then((json) => setDate(json))
470 | // .catch((error) => {
471 | // console.log('fetch data failed', error);
472 | // });
473 | // };
474 |
475 |
476 |
477 | return (
478 |
479 |
480 |
481 |
482 |
483 |
490 | {getPerformArea()}
491 |
492 |
493 |
500 | {getPerformColumn()}
501 |
502 |
503 |
510 | {getPerformColumn()}
511 |
512 |
513 |
514 |
515 |
516 |
517 |
518 | 158.91ms
519 |
520 |
521 | 158.91ms
522 |
523 |
524 | 158.91ms
525 |
526 |
527 |
528 |
529 |
530 |
536 |
537 |
538 |
539 | ,
544 | }}
545 | />
546 | ,
551 | }}
552 | />
553 |
554 |
555 |
562 |
569 |
570 |
571 |
578 | }
579 | />
580 |
581 |
589 | }
590 | />
591 |
592 |
593 |
594 | )
595 | }
596 |
--------------------------------------------------------------------------------