From aeabacc6a5b93a7fabcb41a1b4f9f1ecc6fe3765 Mon Sep 17 00:00:00 2001 From: xiangheng <11675084@qq.com> Date: Thu, 7 Nov 2024 19:03:35 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84slow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- admin/index.html | 15 ++ admin/public/XErr.umd.js | 3 + admin/src/main.ts | 25 ++- admin/src/views/monitor/project/edit.vue | 6 +- admin/src/views/monitor/slow/index.vue | 87 ++++---- admin/vite.config.ts | 2 + .../monitor_client/monitor_client_ctl.go | 16 +- .../admin/monitor_error/monitor_error_ctl.go | 37 +--- server/admin/monitor_slow/monitor_slow_ctl.go | 197 +++++++++--------- .../admin/monitor_slow/monitor_slow_schema.go | 72 ++++--- server/router/admin/monitor_slow_route.go | 2 +- server/util/img_util/img_util.go | 38 ++++ x_err_sdk/package.json | 16 +- x_err_sdk/types.ts | 11 +- x_err_sdk/vite.config.js | 21 ++ x_err_sdk/web/{err-entry.ts => base.ts} | 41 +++- x_err_sdk/web/index.ts | 8 +- x_err_sdk/web/{err-implement.ts => web.ts} | 56 +---- 18 files changed, 350 insertions(+), 303 deletions(-) create mode 100644 admin/public/XErr.umd.js create mode 100644 server/util/img_util/img_util.go create mode 100644 x_err_sdk/vite.config.js rename x_err_sdk/web/{err-entry.ts => base.ts} (79%) rename x_err_sdk/web/{err-implement.ts => web.ts} (77%) diff --git a/admin/index.html b/admin/index.html index 6ab1d3a..cb97c2d 100644 --- a/admin/index.html +++ b/admin/index.html @@ -13,6 +13,21 @@ padding: 0; } + + + diff --git a/admin/public/XErr.umd.js b/admin/public/XErr.umd.js new file mode 100644 index 0000000..4ff26b7 --- /dev/null +++ b/admin/public/XErr.umd.js @@ -0,0 +1,3 @@ +(function(a,s){typeof exports=="object"&&typeof module<"u"?s(exports):typeof define=="function"&&define.amd?define(["exports"],s):(a=typeof globalThis<"u"?globalThis:a||self,s(a.XErr={}))})(this,function(a){"use strict";var v=Object.defineProperty;var L=(a,s,d)=>s in a?v(a,s,{enumerable:!0,configurable:!0,writable:!0,value:d}):a[s]=d;var r=(a,s,d)=>L(a,typeof s!="symbol"?s+"":s,d);const s=[];for(let t=0;t<256;++t)s.push((t+256).toString(16).slice(1));function d(t,e=0){return(s[t[e+0]]+s[t[e+1]]+s[t[e+2]]+s[t[e+3]]+"-"+s[t[e+4]]+s[t[e+5]]+"-"+s[t[e+6]]+s[t[e+7]]+"-"+s[t[e+8]]+s[t[e+9]]+"-"+s[t[e+10]]+s[t[e+11]]+s[t[e+12]]+s[t[e+13]]+s[t[e+14]]+s[t[e+15]]).toLowerCase()}let m;const g=new Uint8Array(16);function y(){if(!m){if(typeof crypto>"u"||!crypto.getRandomValues)throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");m=crypto.getRandomValues.bind(crypto)}return m(g)}const h={};function w(t,e,i){let n;{const l=Date.now(),o=y();_(h,l,o),n=S(o,h.msecs,h.nsecs,h.clockseq,h.node,e,i)}return e?n:d(n)}function _(t,e,i){return t.msecs??(t.msecs=-1/0),t.nsecs??(t.nsecs=0),e===t.msecs?(t.nsecs++,t.nsecs>=1e4&&(t.node=void 0,t.nsecs=0)):e>t.msecs?t.nsecs=0:e>>24&255,o[c++]=u>>>16&255,o[c++]=u>>>8&255,o[c++]=u&255;const p=e/4294967296*1e4&268435455;o[c++]=p>>>8&255,o[c++]=p&255,o[c++]=p>>>24&15|16,o[c++]=p>>>16&255,o[c++]=n>>>8|128,o[c++]=n&255;for(let f=0;f<6;++f)o[c++]=l[f];return o}class x{constructor(e,i){r(this,"Dns","");r(this,"client_id","");r(this,"Pid","");r(this,"Uid","");r(this,"platform",null);r(this,"MessageList",[]);r(this,"timer",0);r(this,"Push",e=>{var i;this.MessageList.push({...e,ProjectKey:this.Pid,ClientId:this.client_id}),this.MessageList.length>5?this.upload():(i=this.platform)==null||i.setCache("x_err_message_list",this.MessageList)});r(this,"uploadInfo",e=>{var i;if(this.Dns)try{(i=this.platform)==null||i.upload(this.Dns+"/admin/monitor_client/add",{ProjectKey:this.Pid,ClientId:this.client_id,UserId:this.Uid,Width:e.ScreenWidth,Height:e.ScreenHeight}).catch(n=>{})}catch{}});r(this,"uploadSlow",e=>{var i;if(this.Dns)try{(i=this.platform)==null||i.upload(this.Dns+"/admin/monitor_slow/add",{ProjectKey:this.Pid,ClientId:this.client_id,UserId:this.Uid,Path:e.Path,Time:e.Time}).catch(n=>{})}catch{}});if(!e){console.error("props is null");return}if(!i){console.error("platform is null");return}if(this.platform=i,e.Dns&&e.Pid)this.Dns=e.Dns,this.Pid=e.Pid;else{console.error("props.Dns and props.Pid cannot be null");return}e.Uid&&(this.Uid=String(e.Uid)),this.setClientID(),this.SetUid(),this.getLocalMessage(),i.listen(n=>{if(console.log("listenCallback",n),n.Type=="onloadTime"){let l=n;this.uploadSlow(l)}else this.Push(n)}),this.timer=setInterval(()=>{this.upload()},1e3*10)}SetUid(e){var i,n;if(e)this.Uid=String(e),(i=this.platform)==null||i.setCache("x_err_uid",this.Uid);else{const l=(n=this.platform)==null?void 0:n.getCache("x_err_uid");l&&(this.Uid=l)}this.initEnv()}initEnv(){var i;let e=(i=this.platform)==null?void 0:i.getEnvInfo();e&&this.uploadInfo(e)}setClientID(){var i,n;const e=(i=this.platform)==null?void 0:i.getCache("x_err_client_id");e?this.client_id=e:(this.client_id=w(),(n=this.platform)==null||n.setCache("x_err_client_id",this.client_id))}getLocalMessage(){var i;let e=(i=this.platform)==null?void 0:i.getCache("x_err_message_list");e?this.MessageList=e:this.MessageList=[]}upload(){var e,i;if(this.Dns&&this.MessageList.length)try{(e=this.platform)==null||e.upload(this.Dns+"/admin/monitor_error/add",this.MessageList).catch(n=>{}),this.MessageList=[],(i=this.platform)==null||i.delCache("x_err_message_list")}catch{}}unListen(){var e;clearInterval(this.timer),(e=this.platform)==null||e.unListen()}}class T{constructor(e){r(this,"props");r(this,"listenError",e=>{var n;console.error([e]);let i=e.target;i!=null&&i.localName?(i==null?void 0:i.localName)==="img"||(i==null?void 0:i.localName)==="script"?this.callback({Type:"resources",EventType:i==null?void 0:i.localName,Path:i.src,Message:"",Stack:""}):(i==null?void 0:i.localName)==="link"&&this.callback({Type:"resources",EventType:i==null?void 0:i.localName,Path:i.href}):this.callback({Type:"error",EventType:e.type,Path:window.location.href,Message:e.message,Stack:this.handleStack(((n=e.error)==null?void 0:n.stack)||"")})});r(this,"unhandledrejection",e=>{var i,n;console.error(e),e&&typeof e.reason=="string"?this.callback({Type:"error",EventType:e.type,Path:window.location.href,Message:e.reason,Stack:""}):e&&typeof e.reason=="object"&&this.callback({Type:"error",EventType:e.type,Path:window.location.href,Message:((i=e.reason)==null?void 0:i.message)||"",Stack:this.handleStack(((n=e.reason)==null?void 0:n.stack)||"")})});r(this,"onLoad",()=>{const e=performance.getEntriesByType("navigation");if(e.length>0){const i=e[0];console.log("performanceData",i);let n=i.loadEventStart-i.startTime;this.props.onloadTimeOut&&n>this.props.onloadTimeOut&&this.callback({Type:"onloadTime",Path:window.location.href,Time:n})}});this.props={onloadTimeOut:5e3,...e}}upload(e,i){return new Promise(n=>{try{let l=new Image;l.onload=function(o){var c=o;c.preventDefault()},l.onerror=function(){},l.src=e+"?data="+encodeURIComponent(JSON.stringify(i)),n()}catch{n()}})}setCache(e,i){localStorage.setItem(e,JSON.stringify(i))}getCache(e){try{let i=localStorage.getItem(e);return i?JSON.parse(i):null}catch{return null}}delCache(e){localStorage.removeItem(e)}getEnvInfo(){const e={Type:"env",ScreenHeight:0,ScreenWidth:0};return window&&(e.ScreenHeight=window.innerHeight||0,e.ScreenWidth=window.innerWidth||0),e}callback(e){}handleStack(e){let i=[];return e&&e.split(` +`).map((n,l)=>{l<4&&i.push(n)}),i.join(` +`)}listen(e){this.callback=e,window.addEventListener("unhandledrejection",this.unhandledrejection),window.addEventListener("error",this.listenError,!0),window.addEventListener("load",this.onLoad)}unListen(){this.callback=()=>{},window.removeEventListener("error",this.listenError),window.removeEventListener("unhandledrejection",this.unhandledrejection)}}a.XErr=x,a.XErrWeb=T,Object.defineProperty(a,Symbol.toStringTag,{value:"Module"})}); diff --git a/admin/src/main.ts b/admin/src/main.ts index 3f3103e..85e2f32 100644 --- a/admin/src/main.ts +++ b/admin/src/main.ts @@ -11,19 +11,18 @@ import ElementPlus from 'element-plus' import VForm3 from 'vform3-builds' //引入VForm3库 -import { XErr, XErrWeb } from '../../x_err_sdk/web/index' -// { XErr, XErrWeb } -const xErr = new XErr( - { - Dns: `${location.origin}/api`, - Pid: 'e19e3be20de94f49b68fafb4c30668bc', - Uid: '' - }, - new XErrWeb({ - onloadTimeOut: 3000 - }) -) -xErr.SetUid(1) //设置用户ID +// import { Base, Web } from '../../x_err_sdk/web/index' +// const xErr = new Base( +// { +// Dns: `${location.origin}/api`, +// Pid: 'e19e3be20de94f49b68fafb4c30668bc', +// Uid: '' +// }, +// new Web({ +// onloadTimeOut: 300 +// }) +// ) +// xErr.SetUid(1) //设置用户ID const app = createApp(App) app.use(install) diff --git a/admin/src/views/monitor/project/edit.vue b/admin/src/views/monitor/project/edit.vue index ffaf0ef..1c3be9e 100644 --- a/admin/src/views/monitor/project/edit.vue +++ b/admin/src/views/monitor/project/edit.vue @@ -97,10 +97,12 @@ const code = computed(() => { new XErr( { Dns: '${location.origin}/api', - Pid: ${formData.ProjectKey}, + Pid: '${formData.ProjectKey}', Uid: '' }, - new XErrWeb() + new XErrWeb({ + onloadTimeOut: 3000 + }) )` }) const formRules = { diff --git a/admin/src/views/monitor/slow/index.vue b/admin/src/views/monitor/slow/index.vue index a904924..e670794 100644 --- a/admin/src/views/monitor/slow/index.vue +++ b/admin/src/views/monitor/slow/index.vue @@ -9,16 +9,22 @@ label-width="70px" label-position="left" > - - - - - + + + + + + - +
- + + + - - - - - - - + + + + + + + + + diff --git a/admin/vite.config.ts b/admin/vite.config.ts index 443d206..c1c1ca0 100644 --- a/admin/vite.config.ts +++ b/admin/vite.config.ts @@ -23,10 +23,12 @@ export default ({ mode }) => { // 依赖预构建,避免开发刷新 include: ['@wangeditor/editor-for-vue', 'vuedraggable', 'vue-echarts', 'crypto-js'] }, + base: '/', build: { sourcemap: true, rollupOptions: { + external: ['XErr'], output: { manualChunks: { vue: ['vue'], diff --git a/server/admin/monitor_client/monitor_client_ctl.go b/server/admin/monitor_client/monitor_client_ctl.go index 7c47b94..22971e6 100644 --- a/server/admin/monitor_client/monitor_client_ctl.go +++ b/server/admin/monitor_client/monitor_client_ctl.go @@ -13,6 +13,7 @@ import ( "x_admin/core/response" "x_admin/util" "x_admin/util/excel2" + "x_admin/util/img_util" "github.com/gin-gonic/gin" "golang.org/x/sync/singleflight" @@ -141,28 +142,24 @@ func (hd *MonitorClientHandler) Add(c *gin.Context) { data, err := url.QueryUnescape(c.Query("data")) if err != nil { // response.CheckAndRespWithData(c, 0, err) - c.Writer.WriteString("0") + c.Data(200, "image/gif", img_util.EmptyGif()) return } var addReq MonitorClientAddReq json.Unmarshal([]byte(data), &addReq) - // if response.IsFailWithResp(c, util.VerifyUtil.VerifyJSON(c, &addReq)) { - // return - // } lastClient, err := MonitorClientService.DetailByClientId(*addReq.ClientId) uaStr := c.GetHeader("user-agent") ip := c.ClientIP() - // createFlag := true if err == nil { last := lastClient.UserId + lastClient.Width.String() + lastClient.Height.String() + lastClient.Ip + lastClient.Ua newStr := *addReq.UserId + addReq.Width.String() + addReq.Height.String() + ip + uaStr if last == newStr { // 前后数据一样,不用创建新的数据 fmt.Println("前后数据一样,不用创建新的数据") - c.Writer.WriteString("0") + c.Data(200, "image/gif", img_util.EmptyGif()) // response.CheckAndRespWithData(c, 0, nil) return } else { @@ -188,10 +185,9 @@ func (hd *MonitorClientHandler) Add(c *gin.Context) { addReq.Province = ®ionInfo.Province } - createId, _ := MonitorClientService.Add(addReq) - // response.CheckAndRespWithData(c, createId, e) - // c.Value(createId) - c.Writer.WriteString(strconv.Itoa(createId)) + MonitorClientService.Add(addReq) + + c.Data(200, "image/gif", img_util.EmptyGif()) } // @Summary 监控-客户端信息删除 diff --git a/server/admin/monitor_error/monitor_error_ctl.go b/server/admin/monitor_error/monitor_error_ctl.go index 03aed3b..6111b64 100644 --- a/server/admin/monitor_error/monitor_error_ctl.go +++ b/server/admin/monitor_error/monitor_error_ctl.go @@ -1,11 +1,7 @@ package monitor_error import ( - "bytes" "encoding/json" - "image" - "image/color" - "image/gif" "net/http" "net/url" "strconv" @@ -15,6 +11,7 @@ import ( "x_admin/core/response" "x_admin/util" "x_admin/util/excel2" + "x_admin/util/img_util" "github.com/gin-gonic/gin" "golang.org/x/sync/singleflight" @@ -111,7 +108,8 @@ func (hd *MonitorErrorHandler) Detail(c *gin.Context) { func (hd *MonitorErrorHandler) Add(c *gin.Context) { data, err := url.QueryUnescape(c.Query("data")) if err != nil { - response.CheckAndRespWithData(c, 0, err) + // response.CheckAndRespWithData(c, 0, err) + c.Data(200, "image/gif", img_util.EmptyGif()) return } @@ -124,35 +122,8 @@ func (hd *MonitorErrorHandler) Add(c *gin.Context) { for i := 0; i < len(addReq); i++ { MonitorErrorService.Add(addReq[i]) } - img := image.NewRGBA(image.Rect(0, 0, 1, 1)) - - // 设置像素颜色为黑色 - img.Set(0, 0, color.Black) - - // 创建GIF动画 - g := &gif.GIF{ - Image: []*image.Paletted{imageToPaletted(img)}, - Delay: []int{0}, // 延迟时间,单位是10毫秒 - } - var buffer bytes.Buffer - - // 编码GIF到缓冲区 - err = gif.EncodeAll(&buffer, g) - // response.CheckAndRespWithData(c, g, nil) - c.Data(200, "image/gif", buffer.Bytes()) -} - -// 将image.Image转换为*image.Paletted -func imageToPaletted(img image.Image) *image.Paletted { - b := img.Bounds() - pm := image.NewPaletted(b, color.Palette{color.Black}) - for y := b.Min.Y; y < b.Max.Y; y++ { - for x := b.Min.X; x < b.Max.X; x++ { - pm.Set(x, y, img.At(x, y)) - } - } - return pm + c.Data(200, "image/gif", img_util.EmptyGif()) } // @Summary 监控-错误列删除 diff --git a/server/admin/monitor_slow/monitor_slow_ctl.go b/server/admin/monitor_slow/monitor_slow_ctl.go index 2d98d84..531ae77 100644 --- a/server/admin/monitor_slow/monitor_slow_ctl.go +++ b/server/admin/monitor_slow/monitor_slow_ctl.go @@ -1,38 +1,42 @@ package monitor_slow import ( + "encoding/json" "net/http" + "net/url" "strconv" "strings" "time" - "github.com/gin-gonic/gin" "x_admin/core/request" "x_admin/core/response" "x_admin/util" "x_admin/util/excel2" + "x_admin/util/img_util" + + "github.com/gin-gonic/gin" "golang.org/x/sync/singleflight" ) - type MonitorSlowHandler struct { requestGroup singleflight.Group } -// @Summary 监控-错误列列表 -// @Tags monitor_slow-监控-错误列 -// @Produce json -// @Param Token header string true "token" -// @Param PageNo query int true "页码" -// @Param PageSize query int true "每页数量" -// @Param ProjectKey query string false "项目key" -// @Param ClientId query string false "sdk生成的客户端id" -// @Param UserId query string false "用户id" -// @Param Path query string false "URL地址" -// @Param Time query number false "时间" -// @Param CreateTimeStart query string false "创建时间" -// @Param CreateTimeEnd query string false "创建时间" -//@Success 200 {object} response.Response{ data=response.PageResp{ lists=[]MonitorSlowResp}} "成功" -//@Router /api/admin/monitor_slow/list [get] +// @Summary 监控-错误列列表 +// @Tags monitor_slow-监控-错误列 +// @Produce json +// @Param Token header string true "token" +// @Param PageNo query int true "页码" +// @Param PageSize query int true "每页数量" +// @Param ProjectKey query string false "项目key" +// @Param ClientId query string false "sdk生成的客户端id" +// @Param UserId query string false "用户id" +// @Param Path query string false "URL地址" +// @Param Time query number false "时间" +// @Param CreateTimeStart query string false "创建时间" +// @Param CreateTimeEnd query string false "创建时间" +// +// @Success 200 {object} response.Response{ data=response.PageResp{ lists=[]MonitorSlowResp}} "成功" +// @Router /api/admin/monitor_slow/list [get] func (hd *MonitorSlowHandler) List(c *gin.Context) { var page request.PageReq var listReq MonitorSlowListReq @@ -46,18 +50,18 @@ func (hd *MonitorSlowHandler) List(c *gin.Context) { response.CheckAndRespWithData(c, res, err) } -// @Summary 监控-错误列列表-所有 -// @Tags monitor_slow-监控-错误列 -// @Produce json -// @Param ProjectKey query string false "项目key" -// @Param ClientId query string false "sdk生成的客户端id" -// @Param UserId query string false "用户id" -// @Param Path query string false "URL地址" -// @Param Time query number false "时间" -// @Param CreateTimeStart query string false "创建时间" -// @Param CreateTimeEnd query string false "创建时间" -// @Success 200 {object} response.Response{ data=[]MonitorSlowResp} "成功" -// @Router /api/admin/monitor_slow/listAll [get] +// @Summary 监控-错误列列表-所有 +// @Tags monitor_slow-监控-错误列 +// @Produce json +// @Param ProjectKey query string false "项目key" +// @Param ClientId query string false "sdk生成的客户端id" +// @Param UserId query string false "用户id" +// @Param Path query string false "URL地址" +// @Param Time query number false "时间" +// @Param CreateTimeStart query string false "创建时间" +// @Param CreateTimeEnd query string false "创建时间" +// @Success 200 {object} response.Response{ data=[]MonitorSlowResp} "成功" +// @Router /api/admin/monitor_slow/listAll [get] func (hd *MonitorSlowHandler) ListAll(c *gin.Context) { var listReq MonitorSlowListReq if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &listReq)) { @@ -67,13 +71,13 @@ func (hd *MonitorSlowHandler) ListAll(c *gin.Context) { response.CheckAndRespWithData(c, res, err) } -// @Summary 监控-错误列详情 -// @Tags monitor_slow-监控-错误列 -// @Produce json -// @Param Token header string true "token" -// @Param Id query number false "错误id" -// @Success 200 {object} response.Response{ data=MonitorSlowResp} "成功" -// @Router /api/admin/monitor_slow/detail [get] +// @Summary 监控-错误列详情 +// @Tags monitor_slow-监控-错误列 +// @Produce json +// @Param Token header string true "token" +// @Param Id query number false "错误id" +// @Success 200 {object} response.Response{ data=MonitorSlowResp} "成功" +// @Router /api/admin/monitor_slow/detail [get] func (hd *MonitorSlowHandler) Detail(c *gin.Context) { var detailReq MonitorSlowDetailReq if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &detailReq)) { @@ -87,52 +91,58 @@ func (hd *MonitorSlowHandler) Detail(c *gin.Context) { response.CheckAndRespWithData(c, res, err) } - -// @Summary 监控-错误列新增 -// @Tags monitor_slow-监控-错误列 -// @Produce json -// @Param Token header string true "token" -// @Param ProjectKey body string false "项目key" -// @Param ClientId body string false "sdk生成的客户端id" -// @Param UserId body string false "用户id" -// @Param Path body string false "URL地址" -// @Param Time body number false "时间" -// @Success 200 {object} response.Response "成功" -// @Router /api/admin/monitor_slow/add [post] +// @Summary 监控-错误列新增 +// @Tags monitor_slow-监控-错误列 +// @Produce json +// @Param Token header string true "token" +// @Param ProjectKey body string false "项目key" +// @Param ClientId body string false "sdk生成的客户端id" +// @Param UserId body string false "用户id" +// @Param Path body string false "URL地址" +// @Param Time body number false "时间" +// @Success 200 {object} response.Response "成功" +// @Router /api/admin/monitor_slow/add [post] func (hd *MonitorSlowHandler) Add(c *gin.Context) { - var addReq MonitorSlowAddReq - if response.IsFailWithResp(c, util.VerifyUtil.VerifyJSON(c, &addReq)) { + data, err := url.QueryUnescape(c.Query("data")) + if err != nil { + c.Data(200, "image/gif", img_util.EmptyGif()) return } - createId, e := MonitorSlowService.Add(addReq) - response.CheckAndRespWithData(c,createId, e) + + var addReq MonitorSlowAddReq + json.Unmarshal([]byte(data), &addReq) + + MonitorSlowService.Add(addReq) + c.Data(200, "image/gif", img_util.EmptyGif()) } -// @Summary 监控-错误列编辑 -// @Tags monitor_slow-监控-错误列 -// @Produce json -// @Param Token header string true "token" -// @Param Id body number false "错误id" -// @Param ProjectKey body string false "项目key" -// @Param ClientId body string false "sdk生成的客户端id" -// @Param UserId body string false "用户id" -// @Param Path body string false "URL地址" -// @Param Time body number false "时间" -// @Success 200 {object} response.Response "成功" -// @Router /api/admin/monitor_slow/edit [post] + +// @Summary 监控-错误列编辑 +// @Tags monitor_slow-监控-错误列 +// @Produce json +// @Param Token header string true "token" +// @Param Id body number false "错误id" +// @Param ProjectKey body string false "项目key" +// @Param ClientId body string false "sdk生成的客户端id" +// @Param UserId body string false "用户id" +// @Param Path body string false "URL地址" +// @Param Time body number false "时间" +// @Success 200 {object} response.Response "成功" +// @Router /api/admin/monitor_slow/edit [post] func (hd *MonitorSlowHandler) Edit(c *gin.Context) { var editReq MonitorSlowEditReq if response.IsFailWithResp(c, util.VerifyUtil.VerifyJSON(c, &editReq)) { return } - response.CheckAndRespWithData(c,editReq.Id, MonitorSlowService.Edit(editReq)) + response.CheckAndRespWithData(c, editReq.Id, MonitorSlowService.Edit(editReq)) } -// @Summary 监控-错误列删除 -// @Tags monitor_slow-监控-错误列 -// @Produce json -// @Param Token header string true "token" -// @Param Id body number false "错误id" -// @Success 200 {object} response.Response "成功" -// @Router /api/admin/monitor_slow/del [post] + +// @Summary 监控-错误列删除 +// @Tags monitor_slow-监控-错误列 +// @Produce json +// @Param Token header string true "token" +// @Param Id body number false "错误id" +// @Success 200 {object} response.Response "成功" +// @Router /api/admin/monitor_slow/del [post] func (hd *MonitorSlowHandler) Del(c *gin.Context) { var delReq MonitorSlowDelReq if response.IsFailWithResp(c, util.VerifyUtil.VerifyJSON(c, &delReq)) { @@ -143,6 +153,7 @@ func (hd *MonitorSlowHandler) Del(c *gin.Context) { // @Summary 监控-错误列删除-批量 // @Tags monitor_slow-监控-错误列 +// // @Produce json // @Param Token header string true "token" // @Param Ids body string false "逗号分割的id" @@ -162,20 +173,18 @@ func (hd *MonitorSlowHandler) DelBatch(c *gin.Context) { response.CheckAndResp(c, MonitorSlowService.DelBatch(Ids)) } - - -// @Summary 监控-错误列导出 -// @Tags monitor_slow-监控-错误列 -// @Produce json -// @Param Token header string true "token" -// @Param ProjectKey query string false "项目key" -// @Param ClientId query string false "sdk生成的客户端id" -// @Param UserId query string false "用户id" -// @Param Path query string false "URL地址" -// @Param Time query number false "时间" -// @Param CreateTimeStart query string false "创建时间" -// @Param CreateTimeEnd query string false "创建时间" -// @Router /api/admin/monitor_slow/ExportFile [get] +// @Summary 监控-错误列导出 +// @Tags monitor_slow-监控-错误列 +// @Produce json +// @Param Token header string true "token" +// @Param ProjectKey query string false "项目key" +// @Param ClientId query string false "sdk生成的客户端id" +// @Param UserId query string false "用户id" +// @Param Path query string false "URL地址" +// @Param Time query number false "时间" +// @Param CreateTimeStart query string false "创建时间" +// @Param CreateTimeEnd query string false "创建时间" +// @Router /api/admin/monitor_slow/ExportFile [get] func (hd *MonitorSlowHandler) ExportFile(c *gin.Context) { var listReq MonitorSlowListReq if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &listReq)) { @@ -186,18 +195,18 @@ func (hd *MonitorSlowHandler) ExportFile(c *gin.Context) { response.FailWithMsg(c, response.SystemError, "查询信息失败") return } - f, err := excel2.Export(res,MonitorSlowService.GetExcelCol(), "Sheet1", "监控-错误列") + f, err := excel2.Export(res, MonitorSlowService.GetExcelCol(), "Sheet1", "监控-错误列") if err != nil { response.FailWithMsg(c, response.SystemError, "导出失败") return } - excel2.DownLoadExcel("监控-错误列" + time.Now().Format("20060102-150405"), c.Writer, f) + excel2.DownLoadExcel("监控-错误列"+time.Now().Format("20060102-150405"), c.Writer, f) } -// @Summary 监控-错误列导入 -// @Tags monitor_slow-监控-错误列 -// @Produce json -// @Router /api/admin/monitor_slow/ImportFile [post] +// @Summary 监控-错误列导入 +// @Tags monitor_slow-监控-错误列 +// @Produce json +// @Router /api/admin/monitor_slow/ImportFile [post] func (hd *MonitorSlowHandler) ImportFile(c *gin.Context) { file, _, err := c.Request.FormFile("file") if err != nil { @@ -206,7 +215,7 @@ func (hd *MonitorSlowHandler) ImportFile(c *gin.Context) { } defer file.Close() importList := []MonitorSlowResp{} - err = excel2.GetExcelData(file, &importList,MonitorSlowService.GetExcelCol()) + err = excel2.GetExcelData(file, &importList, MonitorSlowService.GetExcelCol()) if err != nil { c.String(http.StatusInternalServerError, err.Error()) return @@ -214,4 +223,4 @@ func (hd *MonitorSlowHandler) ImportFile(c *gin.Context) { err = MonitorSlowService.ImportFile(importList) response.CheckAndResp(c, err) -} \ No newline at end of file +} diff --git a/server/admin/monitor_slow/monitor_slow_schema.go b/server/admin/monitor_slow/monitor_slow_schema.go index 72ec211..3fc13c9 100644 --- a/server/admin/monitor_slow/monitor_slow_schema.go +++ b/server/admin/monitor_slow/monitor_slow_schema.go @@ -1,63 +1,61 @@ package monitor_slow + import ( "x_admin/core" - ) -//MonitorSlowListReq 监控-错误列列表参数 +// MonitorSlowListReq 监控-错误列列表参数 type MonitorSlowListReq struct { - ProjectKey *string // 项目key - ClientId *string // sdk生成的客户端id - UserId *string // 用户id - Path *string // URL地址 - Time *float64 // 时间 - CreateTimeStart *string // 开始创建时间 - CreateTimeEnd *string // 结束创建时间 + ProjectKey *string // 项目key + ClientId *string // sdk生成的客户端id + UserId *string // 用户id + Path *string // URL地址 + Time *float64 // 时间 + CreateTimeStart *string // 开始创建时间 + CreateTimeEnd *string // 结束创建时间 } - - -//MonitorSlowAddReq 监控-错误列新增参数 +// MonitorSlowAddReq 监控-错误列新增参数 type MonitorSlowAddReq struct { - ProjectKey *string // 项目key - ClientId *string // sdk生成的客户端id - UserId *string // 用户id - Path *string // URL地址 - Time core.NullFloat // 时间 + ProjectKey *string // 项目key + ClientId *string // sdk生成的客户端id + UserId *string // 用户id + Path *string // URL地址 + Time core.NullFloat // 时间 } -//MonitorSlowEditReq 监控-错误列编辑参数 +// MonitorSlowEditReq 监控-错误列编辑参数 type MonitorSlowEditReq struct { - Id int // 错误id - ProjectKey *string // 项目key - ClientId *string // sdk生成的客户端id - UserId *string // 用户id - Path *string // URL地址 - Time core.NullFloat // 时间 + Id int // 错误id + ProjectKey *string // 项目key + ClientId *string // sdk生成的客户端id + UserId *string // 用户id + Path *string // URL地址 + Time core.NullFloat // 时间 } -//MonitorSlowDetailReq 监控-错误列详情参数 +// MonitorSlowDetailReq 监控-错误列详情参数 type MonitorSlowDetailReq struct { - Id int // 错误id + Id int // 错误id } -//MonitorSlowDelReq 监控-错误列删除参数 +// MonitorSlowDelReq 监控-错误列删除参数 type MonitorSlowDelReq struct { - Id int // 错误id + Id int // 错误id } -//MonitorSlowDelReq 监控-错误列批量删除参数 +// MonitorSlowDelReq 监控-错误列批量删除参数 type MonitorSlowDelBatchReq struct { Ids string } -//MonitorSlowResp 监控-错误列返回信息 +// MonitorSlowResp 监控-错误列返回信息 type MonitorSlowResp struct { - Id int // 错误id - ProjectKey string // 项目key - ClientId string // sdk生成的客户端id - UserId string // 用户id - Path string // URL地址 - Time core.NullFloat // 时间 - CreateTime core.NullTime // 创建时间 + Id int // 错误id + ProjectKey string // 项目key + ClientId string // sdk生成的客户端id + UserId string // 用户id + Path string // URL地址 + Time core.NullFloat // 时间 + CreateTime core.NullTime // 创建时间 } diff --git a/server/router/admin/monitor_slow_route.go b/server/router/admin/monitor_slow_route.go index a62dee5..6a17d9c 100644 --- a/server/router/admin/monitor_slow_route.go +++ b/server/router/admin/monitor_slow_route.go @@ -45,7 +45,7 @@ INSERT INTO x_system_auth_menu (pid, menu_type, menu_name, perms,is_cache, is_sh // MonitorSlowRoute(rg) func MonitorSlowRoute(rg *gin.RouterGroup) { handle := monitor_slow.MonitorSlowHandler{} - rg.POST("/monitor_slow/add", middleware.RecordLog("慢接口新增"), handle.Add) + rg.GET("/monitor_slow/add", middleware.RecordLog("慢接口新增"), handle.Add) r := rg.Group("/", middleware.TokenAuth()) r.GET("/monitor_slow/list", handle.List) r.GET("/monitor_slow/listAll", handle.ListAll) diff --git a/server/util/img_util/img_util.go b/server/util/img_util/img_util.go new file mode 100644 index 0000000..940ee70 --- /dev/null +++ b/server/util/img_util/img_util.go @@ -0,0 +1,38 @@ +package img_util + +import ( + "bytes" + "image" + "image/color" + "image/gif" +) + +func EmptyGif() []byte { + img := image.NewRGBA(image.Rect(0, 0, 1, 1)) + + // 设置像素颜色为黑色 + img.Set(0, 0, color.Black) + + // 创建GIF动画 + g := &gif.GIF{ + Image: []*image.Paletted{imageToPaletted(img)}, + Delay: []int{0}, // 延迟时间,单位是10毫秒 + } + var buffer bytes.Buffer + + // 编码GIF到缓冲区 + gif.EncodeAll(&buffer, g) + return buffer.Bytes() +} + +// 将image.Image转换为*image.Paletted +func imageToPaletted(img image.Image) *image.Paletted { + b := img.Bounds() + pm := image.NewPaletted(b, color.Palette{color.Black}) + for y := b.Min.Y; y < b.Max.Y; y++ { + for x := b.Min.X; x < b.Max.X; x++ { + pm.Set(x, y, img.At(x, y)) + } + } + return pm +} diff --git a/x_err_sdk/package.json b/x_err_sdk/package.json index f3e501c..87ea194 100644 --- a/x_err_sdk/package.json +++ b/x_err_sdk/package.json @@ -2,21 +2,25 @@ "name": "@adtkcn/x_err_sdk", "version": "1.0.0", "description": "错误监控sdk", - "main": "dist/web/index.js", + "main": "dist/web/XErr.umd.js", "private": false, + "file": [ + "dist" + ], "scripts": { - "build": "npm run build:web&&npm run build:ts", - "build:web": "esbuild web/index.ts --bundle --sourcemap --minify --outfile=dist/web/index.js --format=esm --target=es2015", + "build": "vite build&&npm run build:ts", "build:ts": "tsc --emitDeclarationOnly", "outdated": "pnpm outdated" }, - "keywords": ["x_err_sdk","X_admin"], + "keywords": [ + "x_err_sdk", + "X_admin" + ], "author": "adtkcn", "license": "ISC", - "dependencies": {}, "devDependencies": { "typescript": "^5.6.3", "uuid": "^11.0.2", - "esbuild": "^0.24.0" + "vite": "^5.4.10" } } diff --git a/x_err_sdk/types.ts b/x_err_sdk/types.ts index 36afcc0..8837771 100644 --- a/x_err_sdk/types.ts +++ b/x_err_sdk/types.ts @@ -5,13 +5,20 @@ export type LogWithEnv = { ScreenWidth?: number; }; export type LogWithError = { - Type: "error"|"event"|"resources"|'click'|'historyChange'|'hashChange'|'onloadTime'; + Type: "error"|"event"|"resources"|'click'; EventType: string; Path:string; Message?: string; Stack?: string; }; +export type ISlow = { + Type: "onloadTime" + + Path:string; + Time: number; +}; + // 扩展必须实现的接口 export interface IErrorEvent { @@ -23,4 +30,4 @@ export interface IErrorEvent { listen(callback: ListenCallbackFn): void; unListen(): void; } -export type ListenCallbackFn = (params: LogWithError) => void; +export type ListenCallbackFn = (params: LogWithError|ISlow) => void; diff --git a/x_err_sdk/vite.config.js b/x_err_sdk/vite.config.js new file mode 100644 index 0000000..6d60961 --- /dev/null +++ b/x_err_sdk/vite.config.js @@ -0,0 +1,21 @@ +import { defineConfig, loadEnv } from 'vite' + +export default defineConfig({ + build: { + lib: { + entry: 'web/index.ts', // 库的入口文件 + name: 'XErr', // 库的名称 + fileName: (format) => `web/XErr.${format}.js`, // 输出文件名 + }, + rollupOptions: { + // 外部化处理不想打包进库的依赖 + // external: ['vue'], + output: { + globals: { + // vue: 'Vue' + } + } + } + } + }) + \ No newline at end of file diff --git a/x_err_sdk/web/err-entry.ts b/x_err_sdk/web/base.ts similarity index 79% rename from x_err_sdk/web/err-entry.ts rename to x_err_sdk/web/base.ts index 53e48d5..2165e20 100644 --- a/x_err_sdk/web/err-entry.ts +++ b/x_err_sdk/web/base.ts @@ -1,5 +1,5 @@ import { v1 as uuid_v1 } from "uuid"; -import type { LogWithError, IErrorEvent, LogWithEnv } from "../types"; +import type { LogWithError, IErrorEvent,ISlow, LogWithEnv } from "../types"; type Uid = string | number; @@ -9,7 +9,7 @@ type Props = { Uid?: Uid; //用户标识 }; -class XLog { +class Base { private Dns: string = ""; private client_id: string = ""; private Pid: string = ""; @@ -38,17 +38,23 @@ class XLog { return; } if (props.Uid) { - this.Uid = props.Uid; + this.Uid = String(props.Uid); } this.setClientID(); this.SetUid(); this.getLocalMessage(); + // 监听错误 - platform.listen((params: LogWithError) => { + platform.listen((params: LogWithError|ISlow) => { console.log("listenCallback", params); - - this.Push(params); + if(params.Type=='onloadTime'){ + let slow=params as ISlow; + this.uploadSlow(slow); + }else{ + this.Push(params); + } + }); this.timer = setInterval(() => { @@ -59,8 +65,8 @@ class XLog { // 设置用户id public SetUid(uid?: Uid) { if (uid) { - this.Uid = uid; - this.platform?.setCache("x_err_uid", uid); + this.Uid =String(uid) ; + this.platform?.setCache("x_err_uid", this.Uid); } else { const u_id = this.platform?.getCache("x_err_uid"); @@ -127,6 +133,23 @@ class XLog { }); } catch (error) {} } + public uploadSlow=(envInfo: ISlow) =>{ + if (!this.Dns) return; //未设置Dns服务器不上传 + + try { + this.platform + ?.upload(this.Dns + `/admin/monitor_slow/add`, { + ProjectKey: this.Pid, + ClientId: this.client_id, + UserId: this.Uid, + Path: envInfo.Path, + Time: envInfo.Time, + }) + .catch((err: any) => { + // 上传失败 + }); + } catch (error) {} + } // 上传文件 public upload() { @@ -149,4 +172,4 @@ class XLog { this.platform?.unListen(); } } -export default XLog; +export default Base; diff --git a/x_err_sdk/web/index.ts b/x_err_sdk/web/index.ts index 2e42de5..ad828c5 100644 --- a/x_err_sdk/web/index.ts +++ b/x_err_sdk/web/index.ts @@ -1,6 +1,4 @@ -import XErr from "./err-entry" -import XErrWeb from "./err-implement" +import Base from "./base"; +import Web from "./web"; - -export {XErr,XErrWeb} - \ No newline at end of file +export { Base, Web }; \ No newline at end of file diff --git a/x_err_sdk/web/err-implement.ts b/x_err_sdk/web/web.ts similarity index 77% rename from x_err_sdk/web/err-implement.ts rename to x_err_sdk/web/web.ts index d6feca0..e884429 100644 --- a/x_err_sdk/web/err-implement.ts +++ b/x_err_sdk/web/web.ts @@ -2,18 +2,18 @@ import type { LogWithError, LogWithEnv, ListenCallbackFn, - IErrorEvent, + IErrorEvent,ISlow } from "../types"; interface LoggerProps { // timeout:number onloadTimeOut?: number; } -class Logger implements IErrorEvent { +class Web implements IErrorEvent { props: LoggerProps; constructor(props?: LoggerProps) { this.props = { - onloadTimeOut: 3000, + onloadTimeOut: 5000, ...props, }; } @@ -67,7 +67,7 @@ class Logger implements IErrorEvent { } return env; } - private callback(err: LogWithError): void {} + private callback(err: LogWithError|ISlow): void {} private listenError = (err: any) => { console.error([err]); let target = err.target; @@ -117,46 +117,8 @@ class Logger implements IErrorEvent { }); } }; - // private listenClick = (e: Event) => { - // let target = e.target as HTMLElement; - // let tagName = target?.localName; - // let name = [tagName]; - // if (target.id) { - // name.push("#" + target.id); - // } - // target.classList.forEach((item) => { - // name.push("." + item); - // }); - // if (target.innerText) { - // name.push(":" + target.innerText.slice(0, 20)); - // } - // this.callback({ - // Type: "click", - // EventType: "click", - // Path: window.location.href, - // Message: name.join(""), - // Stack: "", - // }); - // }; - // private listenHistoryRouterChange = () => { - // this.callback({ - // Type: "historyChange", - // EventType: "popstate", - // Path: window.location.href, - // Message: "", - // Stack: "", - // }); - // }; - // // 监听hash - // private listenHashRouterChange = () => { - // this.callback({ - // Type: "hashChange", - // EventType: "hashChange", - // Path: window.location.href, - // Message: "", - // Stack: "", - // }); - // }; + + private handleStack(stack: string): string { let newStack: string[] = []; if (stack) { @@ -186,10 +148,8 @@ class Logger implements IErrorEvent { // 页面加载时间5s以上 this.callback({ Type: "onloadTime", - EventType: "onloadTime", Path: window.location.href, - Message: "时间:" + parseFloat(onloadTime.toFixed(2)) + "ms", - Stack: "", + Time:onloadTime }); } } @@ -216,4 +176,4 @@ class Logger implements IErrorEvent { } } -export default Logger; +export default Web;