├── 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 | 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 | 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 | 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 | 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 | {/*
*/} 74 | {/* Home*/} 75 | {/* Product*/} 76 | {/* Article*/} 77 | {/* About*/} 78 | {/* Log in*/} 79 | {/*
*/} 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 | 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 | --------------------------------------------------------------------------------