feat: 修改后端apiclient生成逻辑
fix: 修复debugger获取flag失败的问题 refactor: 重新编写debugger前后端逻辑
This commit is contained in:
parent
6dfd275091
commit
3257a68407
|
@ -13,6 +13,7 @@
|
|||
"@types/lodash": "^4.17.16",
|
||||
"@vueuse/core": "^13.5.0",
|
||||
"async-mutex": "^0.5.0",
|
||||
"axios": "^1.11.0",
|
||||
"echarts": "^5.6.0",
|
||||
"highlight.js": "^11.11.1",
|
||||
"konva": "^9.3.20",
|
||||
|
@ -2357,6 +2358,12 @@
|
|||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/autoprefixer": {
|
||||
"version": "10.4.21",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz",
|
||||
|
@ -2395,6 +2402,17 @@
|
|||
"postcss": "^8.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.11.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz",
|
||||
"integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.4",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
|
@ -2496,6 +2514,19 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001715",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001715.tgz",
|
||||
|
@ -2542,6 +2573,18 @@
|
|||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/complex.js": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.4.2.tgz",
|
||||
|
@ -2735,6 +2778,15 @@
|
|||
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
|
||||
|
@ -2755,6 +2807,20 @@
|
|||
"node": ">=0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/echarts": {
|
||||
"version": "5.6.0",
|
||||
"resolved": "https://registry.npmjs.org/echarts/-/echarts-5.6.0.tgz",
|
||||
|
@ -2814,6 +2880,51 @@
|
|||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-set-tostringtag": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz",
|
||||
|
@ -2979,6 +3090,42 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.9",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
|
||||
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/formdata-polyfill": {
|
||||
"version": "4.0.10",
|
||||
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
|
||||
|
@ -3036,6 +3183,15 @@
|
|||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/gensync": {
|
||||
"version": "1.0.0-beta.2",
|
||||
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
||||
|
@ -3046,6 +3202,43 @@
|
|||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/get-stream": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz",
|
||||
|
@ -3099,6 +3292,18 @@
|
|||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
|
@ -3106,6 +3311,45 @@
|
|||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-tostringtag": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/he": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
|
||||
|
@ -3723,6 +3967,15 @@
|
|||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/mathjs": {
|
||||
"version": "14.4.0",
|
||||
"resolved": "https://registry.npmjs.org/mathjs/-/mathjs-14.4.0.tgz",
|
||||
|
@ -3768,6 +4021,27 @@
|
|||
"node": ">= 0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||
|
@ -4195,6 +4469,12 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/quansync": {
|
||||
"version": "0.2.10",
|
||||
"resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.10.tgz",
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
"@types/lodash": "^4.17.16",
|
||||
"@vueuse/core": "^13.5.0",
|
||||
"async-mutex": "^0.5.0",
|
||||
"axios": "^1.11.0",
|
||||
"echarts": "^5.6.0",
|
||||
"highlight.js": "^11.11.1",
|
||||
"konva": "^9.3.20",
|
||||
|
|
|
@ -279,10 +279,18 @@ async function postProcessApiClient(): Promise<void> {
|
|||
async function generateApiClient(): Promise<void> {
|
||||
console.log('Generating API client...');
|
||||
try {
|
||||
const npxCommand = getCommand('npx');
|
||||
await execAsync(`${npxCommand} nswag openapi2tsclient /input:http://localhost:5000/swagger/v1/swagger.json /output:src/APIClient.ts`);
|
||||
console.log('✓ API client generated successfully');
|
||||
|
||||
const url = 'http://127.0.0.1:5000/GetAPIClientCode';
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch API client code: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
const code = await response.text();
|
||||
|
||||
// 写入 APIClient.ts
|
||||
const filePath = 'src/APIClient.ts';
|
||||
fs.writeFileSync(filePath, code, 'utf8');
|
||||
console.log('✓ API client code fetched and written successfully');
|
||||
|
||||
// 添加后处理步骤
|
||||
await postProcessApiClient();
|
||||
} catch (error) {
|
||||
|
|
|
@ -5,8 +5,11 @@ using Microsoft.AspNetCore.Http.Features;
|
|||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Newtonsoft.Json;
|
||||
using NJsonSchema.CodeGeneration.TypeScript;
|
||||
using NLog;
|
||||
using NLog.Web;
|
||||
using NSwag;
|
||||
using NSwag.CodeGeneration.TypeScript;
|
||||
using NSwag.Generation.Processors.Security;
|
||||
using server.Services;
|
||||
|
||||
|
@ -191,6 +194,34 @@ try
|
|||
// Setup Program
|
||||
MsgBus.Init();
|
||||
|
||||
// Generate API Client
|
||||
app.MapGet("GetAPIClientCode", async (HttpContext context) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var document = await OpenApiDocument.FromUrlAsync($"http://{Global.localhost}:5000/swagger/v1/swagger.json");
|
||||
|
||||
var settings = new TypeScriptClientGeneratorSettings
|
||||
{
|
||||
ClassName = "{controller}Client",
|
||||
UseAbortSignal = false,
|
||||
Template = TypeScriptTemplate.Axios,
|
||||
TypeScriptGeneratorSettings = {
|
||||
},
|
||||
};
|
||||
|
||||
var generator = new TypeScriptClientGenerator(document, settings);
|
||||
var code = generator.GenerateFile();
|
||||
|
||||
return Results.Text(code, "text/plain; charset=utf-8", Encoding.UTF8);
|
||||
}
|
||||
catch (Exception err)
|
||||
{
|
||||
logger.Error(err);
|
||||
return Results.Problem(err.ToString());
|
||||
}
|
||||
});
|
||||
|
||||
app.Run();
|
||||
}
|
||||
catch (Exception exception)
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
<PackageReference Include="NLog" Version="5.4.0" />
|
||||
<PackageReference Include="NLog.Web.AspNetCore" Version="5.4.0" />
|
||||
<PackageReference Include="NSwag.AspNetCore" Version="14.3.0" />
|
||||
<PackageReference Include="NSwag.CodeGeneration.TypeScript" Version="14.4.0" />
|
||||
<PackageReference Include="OpenCvSharp4" Version="4.11.0.20250507" />
|
||||
<PackageReference Include="OpenCvSharp4.runtime.win" Version="4.11.0.20250507" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.10" />
|
||||
|
|
|
@ -6,7 +6,7 @@ using Peripherals.DebuggerClient;
|
|||
namespace server.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// FPGA调试器控制器
|
||||
/// FPGA调试器控制器,提供信号捕获、触发、数据读取等调试相关API
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
|
@ -15,8 +15,81 @@ public class DebuggerController : ControllerBase
|
|||
{
|
||||
private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
|
||||
|
||||
/// <summary>
|
||||
/// 表示单个信号通道的配置信息
|
||||
/// </summary>
|
||||
public class ChannelConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// 通道名称
|
||||
/// </summary>
|
||||
required public string name;
|
||||
/// <summary>
|
||||
/// 通道显示颜色(如前端波形显示用)
|
||||
/// </summary>
|
||||
required public string color;
|
||||
/// <summary>
|
||||
/// 通道信号线宽度(位数)
|
||||
/// </summary>
|
||||
required public UInt32 wireWidth;
|
||||
/// <summary>
|
||||
/// 信号线在父端口中的起始索引(bit)
|
||||
/// </summary>
|
||||
required public UInt32 wireStartIndex;
|
||||
/// <summary>
|
||||
/// 父端口编号
|
||||
/// </summary>
|
||||
required public UInt32 parentPort;
|
||||
/// <summary>
|
||||
/// 捕获模式(如上升沿、下降沿等)
|
||||
/// </summary>
|
||||
required public CaptureMode mode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 调试器整体配置信息
|
||||
/// </summary>
|
||||
public class DebuggerConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// 时钟频率
|
||||
/// </summary>
|
||||
required public UInt32 clkFreq;
|
||||
/// <summary>
|
||||
/// 总端口数量
|
||||
/// </summary>
|
||||
required public UInt32 totalPortNum;
|
||||
/// <summary>
|
||||
/// 捕获深度(采样点数)
|
||||
/// </summary>
|
||||
required public UInt32 captureDepth;
|
||||
/// <summary>
|
||||
/// 触发器数量
|
||||
/// </summary>
|
||||
required public UInt32 triggerNum;
|
||||
/// <summary>
|
||||
/// 所有信号通道的配置信息
|
||||
/// </summary>
|
||||
required public ChannelConfig[] channelConfigs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 单个通道的捕获数据
|
||||
/// </summary>
|
||||
public class ChannelCaptureData
|
||||
{
|
||||
/// <summary>
|
||||
/// 通道名称
|
||||
/// </summary>
|
||||
required public string name;
|
||||
/// <summary>
|
||||
/// 通道捕获到的数据(Base64编码的UInt32数组)
|
||||
/// </summary>
|
||||
required public string data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取调试器实例
|
||||
/// 获取当前用户绑定的调试器实例
|
||||
/// </summary>
|
||||
private DebuggerClient? GetDebugger()
|
||||
{
|
||||
|
@ -50,19 +123,21 @@ public class DebuggerController : ControllerBase
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置捕获模式
|
||||
/// 设置指定信号线的捕获模式
|
||||
/// </summary>
|
||||
/// <param name="wireNum">信号线编号(0~511)</param>
|
||||
/// <param name="mode">捕获模式</param>
|
||||
[HttpPost("SetMode")]
|
||||
[EnableCors("Users")]
|
||||
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||
public async Task<IActionResult> SetMode(int channelNum, CaptureMode mode)
|
||||
public async Task<IActionResult> SetMode(UInt32 wireNum, CaptureMode mode)
|
||||
{
|
||||
if (channelNum > 0x0F)
|
||||
if (wireNum > 512)
|
||||
{
|
||||
return BadRequest($"最多只能建立16个通道");
|
||||
return BadRequest($"最多只能建立512位信号线");
|
||||
}
|
||||
|
||||
try
|
||||
|
@ -71,7 +146,7 @@ public class DebuggerController : ControllerBase
|
|||
if (debugger == null)
|
||||
return BadRequest("用户未绑定有效的实验板");
|
||||
|
||||
var result = await debugger.SetMode((byte)channelNum, mode);
|
||||
var result = await debugger.SetMode(wireNum, mode);
|
||||
if (!result.IsSuccessful)
|
||||
{
|
||||
logger.Error($"设置捕获模式失败: {result.Error}");
|
||||
|
@ -88,7 +163,58 @@ public class DebuggerController : ControllerBase
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动触发器
|
||||
/// 为每个通道中的每根线设置捕获模式
|
||||
/// </summary>
|
||||
/// <param name="config">调试器配置信息,包含所有通道的捕获模式设置</param>
|
||||
[HttpPost("SetChannelsMode")]
|
||||
[EnableCors("Users")]
|
||||
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||
public async Task<IActionResult> SetChannelsMode([FromBody] DebuggerConfig config)
|
||||
{
|
||||
if (config == null || config.channelConfigs == null)
|
||||
return BadRequest("配置无效");
|
||||
|
||||
try
|
||||
{
|
||||
var debugger = GetDebugger();
|
||||
if (debugger == null)
|
||||
return BadRequest("用户未绑定有效的实验板");
|
||||
|
||||
foreach (var channel in config.channelConfigs)
|
||||
{
|
||||
// 检查每个通道的配置
|
||||
if (channel.wireWidth > 32 ||
|
||||
channel.wireStartIndex > 32 ||
|
||||
channel.wireStartIndex + channel.wireWidth > 32)
|
||||
{
|
||||
return BadRequest($"通道 {channel.name} 配置错误");
|
||||
}
|
||||
|
||||
for (uint i = 0; i < channel.wireWidth; i++)
|
||||
{
|
||||
var result = await debugger.SetMode(channel.wireStartIndex * (channel.parentPort * 32) + i, channel.mode);
|
||||
if (!result.IsSuccessful)
|
||||
{
|
||||
logger.Error($"设置通道 {channel.name} 第 {i} 根线捕获模式失败: {result.Error}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, $"设置通道 {channel.name} 第 {i} 根线捕获模式失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.Error(ex, "为每个通道中的每根线设置捕获模式时发生异常");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, "操作失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动触发器,开始信号捕获
|
||||
/// </summary>
|
||||
[HttpPost("StartTrigger")]
|
||||
[EnableCors("Users")]
|
||||
|
@ -120,6 +246,46 @@ public class DebuggerController : ControllerBase
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重新开始触发(刷新后再启动触发器)
|
||||
/// </summary>
|
||||
[HttpPost("RestartTrigger")]
|
||||
[EnableCors("Users")]
|
||||
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||
public async Task<IActionResult> RestartTrigger()
|
||||
{
|
||||
try
|
||||
{
|
||||
var debugger = GetDebugger();
|
||||
if (debugger == null)
|
||||
return BadRequest("用户未绑定有效的实验板");
|
||||
|
||||
var refreshResult = await debugger.Refresh();
|
||||
if (!refreshResult.IsSuccessful)
|
||||
{
|
||||
logger.Error($"刷新调试器状态失败: {refreshResult.Error}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, "刷新调试器状态失败");
|
||||
}
|
||||
|
||||
var startResult = await debugger.StartTrigger();
|
||||
if (!startResult.IsSuccessful)
|
||||
{
|
||||
logger.Error($"启动触发器失败: {startResult.Error}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, "启动触发器失败");
|
||||
}
|
||||
|
||||
return Ok(startResult.Value);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.Error(ex, "重新开始触发时发生异常");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, "操作失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取触发器状态标志
|
||||
/// </summary>
|
||||
|
@ -187,32 +353,105 @@ public class DebuggerController : ControllerBase
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取捕获数据
|
||||
/// 读取捕获数据(等待触发完成后返回各通道采样数据)
|
||||
/// </summary>
|
||||
/// <param name="config">调试器配置信息,包含采样深度、端口数、通道配置等</param>
|
||||
/// <param name="cancellationToken">取消操作的令牌</param>
|
||||
[HttpGet("ReadData")]
|
||||
[EnableCors("Users")]
|
||||
[ProducesResponseType(typeof(string), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ChannelCaptureData[]), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||
public async Task<IActionResult> ReadData([FromQuery] ushort offset = 0)
|
||||
public async Task<IActionResult> ReadData([FromBody] DebuggerConfig config, CancellationToken cancellationToken)
|
||||
{
|
||||
// 检查每个通道的配置
|
||||
foreach (var channel in config.channelConfigs)
|
||||
{
|
||||
if (channel.wireWidth > 32 ||
|
||||
channel.wireStartIndex > 32 ||
|
||||
channel.wireStartIndex + channel.wireWidth > 32)
|
||||
{
|
||||
return BadRequest($"通道 {channel.name} 配置错误");
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var debugger = GetDebugger();
|
||||
if (debugger == null)
|
||||
return BadRequest("用户未绑定有效的实验板");
|
||||
|
||||
var result = await debugger.ReadData(offset);
|
||||
if (!result.IsSuccessful)
|
||||
// 等待捕获标志位
|
||||
while (true)
|
||||
{
|
||||
logger.Error($"读取捕获数据失败: {result.Error}");
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var flagResult = await debugger.ReadFlag();
|
||||
if (!flagResult.IsSuccessful)
|
||||
{
|
||||
logger.Error($"读取捕获标志失败: {flagResult.Error}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, "读取捕获标志失败");
|
||||
}
|
||||
if (flagResult.Value == 1)
|
||||
{
|
||||
var clearResult = await debugger.ClearFlag();
|
||||
if (!clearResult.IsSuccessful)
|
||||
{
|
||||
logger.Error($"清除捕获标志失败: {clearResult.Error}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, "清除捕获标志失败");
|
||||
}
|
||||
break;
|
||||
}
|
||||
await Task.Delay(500, cancellationToken);
|
||||
}
|
||||
|
||||
var dataResult = await debugger.ReadData(config.totalPortNum);
|
||||
if (!dataResult.IsSuccessful)
|
||||
{
|
||||
logger.Error($"读取捕获数据失败: {dataResult.Error}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, "读取捕获数据失败");
|
||||
}
|
||||
|
||||
// 返回Base64编码
|
||||
var base64Data = Convert.ToBase64String(result.Value);
|
||||
return Ok(base64Data);
|
||||
var rawData = dataResult.Value;
|
||||
int depth = (int)config.captureDepth;
|
||||
int portDataLen = 4 * depth;
|
||||
int portNum = (int)config.totalPortNum;
|
||||
var channelDataList = new List<ChannelCaptureData>();
|
||||
|
||||
foreach (var channel in config.channelConfigs)
|
||||
{
|
||||
int port = (int)channel.parentPort;
|
||||
int wireStart = (int)channel.wireStartIndex;
|
||||
int wireWidth = (int)channel.wireWidth;
|
||||
|
||||
// 每个port的数据长度
|
||||
int portOffset = port * portDataLen;
|
||||
|
||||
var channelUintArr = new UInt32[depth];
|
||||
for (int i = 0; i < depth; i++)
|
||||
{
|
||||
// 取出该port的第i个采样点的4字节
|
||||
int sampleOffset = portOffset + i * 4;
|
||||
if (sampleOffset + 4 > rawData.Length)
|
||||
{
|
||||
logger.Error($"数据越界: port {port}, sample {i}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, "数据越界");
|
||||
}
|
||||
UInt32 sample = BitConverter.ToUInt32(rawData, sampleOffset);
|
||||
// 提取wireWidth位
|
||||
UInt32 mask = (wireWidth == 32) ? 0xFFFFFFFF : ((1u << wireWidth) - 1u);
|
||||
channelUintArr[i] = (sample >> wireStart) & mask;
|
||||
}
|
||||
var base64 = Convert.ToBase64String(channelUintArr.SelectMany(BitConverter.GetBytes).ToArray());
|
||||
channelDataList.Add(new ChannelCaptureData { name = channel.name, data = base64 });
|
||||
}
|
||||
|
||||
return Ok(channelDataList.ToArray());
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
logger.Info("读取捕获数据请求被取消");
|
||||
return StatusCode(StatusCodes.Status499ClientClosedRequest, "客户端已取消请求");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -222,7 +461,7 @@ public class DebuggerController : ControllerBase
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新调试器状态
|
||||
/// 刷新调试器状态(重置采集状态等)
|
||||
/// </summary>
|
||||
[HttpPost("Refresh")]
|
||||
[EnableCors("Users")]
|
||||
|
|
|
@ -119,18 +119,18 @@ public class DebuggerClient
|
|||
/// <summary>
|
||||
/// 设置信号捕获模式
|
||||
/// </summary>
|
||||
/// <param name="channelNum">要设置的通道</param>
|
||||
/// <param name="wireNum">要设置的线</param>
|
||||
/// <param name="mode">要设置的捕获模式</param>
|
||||
/// <returns>操作结果,成功返回true,失败返回错误信息</returns>
|
||||
public async ValueTask<Result<bool>> SetMode(byte channelNum, CaptureMode mode)
|
||||
public async ValueTask<Result<bool>> SetMode(UInt32 wireNum, CaptureMode mode)
|
||||
{
|
||||
if (channelNum > 0x0F)
|
||||
if (wireNum > 512)
|
||||
{
|
||||
return new(new ArgumentException($"Channel Num can't be over 16, but receive num: {channelNum}"));
|
||||
return new(new ArgumentException($"Wire Num can't be over 512, but receive num: {wireNum}"));
|
||||
}
|
||||
|
||||
UInt32 data = ((UInt32)mode);
|
||||
var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, DebuggerAddr.Mode + channelNum, data, this.timeout);
|
||||
var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, DebuggerAddr.Mode + wireNum, data, this.timeout);
|
||||
if (!ret.IsSuccessful)
|
||||
{
|
||||
logger.Error($"Failed to set mode: {ret.Error}");
|
||||
|
@ -181,7 +181,7 @@ public class DebuggerClient
|
|||
logger.Error("ReadAddr returned invalid data for flag");
|
||||
return new(new Exception("Failed to read flag"));
|
||||
}
|
||||
return ret.Value.Options.Data[0];
|
||||
return ret.Value.Options.Data[3];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -207,30 +207,26 @@ public class DebuggerClient
|
|||
/// <summary>
|
||||
/// 从指定偏移地址读取捕获的数据
|
||||
/// </summary>
|
||||
/// <param name="offset">数据读取的偏移地址</param>
|
||||
/// <returns>操作结果,成功返回32KB的捕获数据,失败返回错误信息</returns>
|
||||
public async ValueTask<Result<byte[]>> ReadData(UInt16 offset)
|
||||
/// <param name="portNum">Port数量</param>
|
||||
/// <returns>操作结果,成功返回捕获数据,失败返回错误信息</returns>
|
||||
public async ValueTask<Result<byte[]>> ReadData(UInt32 portNum)
|
||||
{
|
||||
var captureData = new byte[1024 * 32];
|
||||
var captureData = new byte[1024 * 4 * portNum];
|
||||
{
|
||||
var ret = await UDPClientPool.ReadAddr4BytesAsync(this.ep, this.taskID, this.captureDataAddr + offset, 512, this.timeout);
|
||||
var ret = await UDPClientPool.ReadAddr4BytesAsync(this.ep, this.taskID, this.captureDataAddr, captureData.Length, this.timeout);
|
||||
if (!ret.IsSuccessful)
|
||||
{
|
||||
logger.Error($"Failed to read data: {ret.Error}");
|
||||
return new(ret.Error);
|
||||
}
|
||||
|
||||
Buffer.BlockCopy(ret.Value, 0, captureData, 0, 512 * 4);
|
||||
}
|
||||
{
|
||||
var ret = await UDPClientPool.ReadAddr4BytesAsync(this.ep, this.taskID, this.captureDataAddr + offset + 512, 512, this.timeout);
|
||||
if (!ret.IsSuccessful)
|
||||
if (ret.Value.Length != captureData.Length)
|
||||
{
|
||||
logger.Error($"Failed to read data: {ret.Error}");
|
||||
return new(ret.Error);
|
||||
logger.Error($"Receive capture data length should be {captureData.Length} instead of {ret.Value.Length}");
|
||||
return new(new Exception($"Receive capture data length should be {captureData.Length} instead of {ret.Value.Length}"));
|
||||
}
|
||||
|
||||
Buffer.BlockCopy(ret.Value, 0, captureData, 512 * 4, 512 * 4);
|
||||
Buffer.BlockCopy(ret.Value, 0, captureData, 0, captureData.Length);
|
||||
}
|
||||
|
||||
return captureData;
|
||||
|
|
4757
src/APIClient.ts
4757
src/APIClient.ts
File diff suppressed because it is too large
Load Diff
|
@ -21,7 +21,7 @@
|
|||
<h3 class="text-xl font-semibold text-slate-600 mb-2">
|
||||
暂无逻辑分析数据
|
||||
</h3>
|
||||
<p class="text-sm text-slate-500">点击下方按钮生成测试数据用于观察</p>
|
||||
<p class="text-sm text-slate-500">点击下方按钮开始捕获</p>
|
||||
</div>
|
||||
|
||||
<!-- <button
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
OscilloscopeApiClient,
|
||||
DebuggerClient,
|
||||
} from "@/APIClient";
|
||||
import axios, { type AxiosInstance } from "axios";
|
||||
|
||||
// 支持的客户端类型联合类型
|
||||
type SupportedClient =
|
||||
|
@ -108,13 +109,27 @@ export class AuthManager {
|
|||
};
|
||||
}
|
||||
|
||||
// 私有方法:创建带认证的Axios实例
|
||||
private static createAuthenticatedAxiosInstance(): AxiosInstance | null {
|
||||
const token = AuthManager.getToken();
|
||||
if (!token) return null;
|
||||
|
||||
const instance = axios.create();
|
||||
instance.interceptors.request.use(config => {
|
||||
config.headers = config.headers || {};
|
||||
(config.headers as any)["Authorization"] = `Bearer ${token}`;
|
||||
return config;
|
||||
});
|
||||
return instance;
|
||||
}
|
||||
|
||||
// 通用的创建已认证客户端的方法(使用泛型)
|
||||
public static createAuthenticatedClient<T extends SupportedClient>(
|
||||
ClientClass: new (baseUrl?: string, http?: any) => T,
|
||||
ClientClass: new (baseUrl?: string, instance?: AxiosInstance) => T,
|
||||
): T {
|
||||
const customHttp = AuthManager.createAuthenticatedHttp();
|
||||
return customHttp
|
||||
? new ClientClass(undefined, customHttp)
|
||||
const axiosInstance = AuthManager.createAuthenticatedAxiosInstance();
|
||||
return axiosInstance
|
||||
? new ClientClass(undefined, axiosInstance)
|
||||
: new ClientClass();
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,13 @@
|
|||
调试器波形捕获
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
class="btn btn-sm btn-primary"
|
||||
@click="startCapture(true)"
|
||||
:disabled="!captureData"
|
||||
>
|
||||
重新捕获
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm btn-error"
|
||||
@click="handleDeleteData"
|
||||
|
@ -18,6 +25,12 @@
|
|||
</div>
|
||||
</h2>
|
||||
<WaveformDisplay :data="captureData">
|
||||
<div class="text-center">
|
||||
<h3 class="text-xl font-semibold text-slate-600 mb-2">
|
||||
暂无逻辑分析数据
|
||||
</h3>
|
||||
<p class="text-sm text-slate-500">点击下方按钮开始捕获</p>
|
||||
</div>
|
||||
<button
|
||||
class="group relative px-8 py-3 bg-gradient-to-r text-white font-medium rounded-lg shadow-lg hover:shadow-xl transform hover:scale-105 transition-all duration-200 ease-in-out focus:outline-none focus:ring-4 active:scale-95"
|
||||
:class="{
|
||||
|
@ -45,51 +58,94 @@
|
|||
<!-- Debugger 通道配置 -->
|
||||
<div class="card m-5 bg-base-200 shadow-2xl">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title mb-4">调试器通道配置</h2>
|
||||
<div class="overflow-x-auto flex flex-col gap-10">
|
||||
<!-- 通道状态概览 -->
|
||||
<div class="flex justify-between">
|
||||
<h2 class="card-title mb-4">调试器通道配置</h2>
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
class="btn btn-sm btn-primary"
|
||||
@click="addChannel"
|
||||
:disabled="!configInited"
|
||||
>
|
||||
添加通道
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm btn-secondary"
|
||||
@click="showConfigDialog = true"
|
||||
>
|
||||
配置
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 配置未初始化时 -->
|
||||
<div
|
||||
v-if="!configInited"
|
||||
class="flex flex-col items-center justify-center py-10"
|
||||
>
|
||||
<div class="text-lg text-slate-500 mb-4">请先进行调试器基本配置</div>
|
||||
<button class="btn btn-primary" @click="showConfigDialog = true">
|
||||
配置调试器
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="configInited" class="overflow-x-auto flex flex-col gap-10">
|
||||
<!-- 状态概览 -->
|
||||
<div
|
||||
class="stats stats-horizontal bg-base-100 shadow flex justify-between"
|
||||
>
|
||||
<div class="stat">
|
||||
<div class="stat-title">总通道数</div>
|
||||
<div class="stat-value text-primary">16</div>
|
||||
<div class="stat-desc">逻辑分析仪通道</div>
|
||||
</div>
|
||||
|
||||
<div class="stat">
|
||||
<div class="stat-title">启用通道</div>
|
||||
<div class="stat-value text-success">
|
||||
{{ channels.filter((ch) => ch.visible).length }}
|
||||
<div class="stat-title">启用端口数</div>
|
||||
<div class="stat-value text-primary">
|
||||
{{ config.totalPortNum }}
|
||||
</div>
|
||||
<div class="stat-desc">当前激活通道</div>
|
||||
<div class="stat-desc">每端口最大32线</div>
|
||||
</div>
|
||||
|
||||
<div class="stat">
|
||||
<div class="stat-title">采样率</div>
|
||||
<div class="stat-value text-info">5MHz</div>
|
||||
<div class="stat-desc">最大采样频率</div>
|
||||
<div class="stat-title">最大线宽数</div>
|
||||
<div class="stat-value text-info">
|
||||
{{ config.totalPortNum * 32 }}
|
||||
</div>
|
||||
<div class="stat-desc">启用端口数 × 32</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-title">已用线宽数</div>
|
||||
<div class="stat-value text-success">
|
||||
{{ channels.reduce((sum, ch) => sum + ch.width, 0) }}
|
||||
</div>
|
||||
<div class="stat-desc">所有通道线宽总和</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-title">采样深度</div>
|
||||
<div class="stat-value text-warning">
|
||||
{{ config.captureDepth }}
|
||||
</div>
|
||||
<div class="stat-desc">每通道采样点数</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-title">时钟频率</div>
|
||||
<div class="stat-value text-accent">{{ config.clkFreq }} MHz</div>
|
||||
<div class="stat-desc">采样时钟</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表格 -->
|
||||
<!-- 通道表格 -->
|
||||
<div class="space-y-2">
|
||||
<!-- 表头 -->
|
||||
<div
|
||||
class="grid grid-cols-6 justify-items-center gap-2 p-2 bg-base-300 rounded-lg text-sm font-medium"
|
||||
class="grid grid-cols-7 justify-items-center gap-2 p-2 bg-base-300 rounded-lg text-sm font-medium"
|
||||
>
|
||||
<span>名称</span>
|
||||
<span>显示</span>
|
||||
<span>颜色</span>
|
||||
<span>触发模式</span>
|
||||
<span>数据位数</span>
|
||||
<span>数据位宽(起始:宽度)</span>
|
||||
<span>父端口编号</span>
|
||||
<span>操作</span>
|
||||
</div>
|
||||
<!-- 通道列表 -->
|
||||
<div
|
||||
v-for="(ch, idx) in channels"
|
||||
:key="idx"
|
||||
class="grid grid-cols-6 justify-items-center gap-2 p-2 bg-base-200 rounded-lg hover:bg-base-300 transition-colors"
|
||||
class="grid grid-cols-7 place-items-center gap-4 p-4 bg-base-200 rounded-lg hover:bg-base-300 transition-colors"
|
||||
>
|
||||
<input
|
||||
v-model="ch.name"
|
||||
|
@ -118,11 +174,17 @@
|
|||
{{ mode.label }}
|
||||
</option>
|
||||
</select>
|
||||
<input
|
||||
v-model="ch.widthStr"
|
||||
class="input input-bordered w-full"
|
||||
placeholder="如0:8"
|
||||
@change="parseWidthStr(idx)"
|
||||
/>
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
max="32"
|
||||
v-model.number="ch.width"
|
||||
min="0"
|
||||
:max="config.totalPortNum - 1"
|
||||
v-model.number="ch.parentPort"
|
||||
class="input input-bordered w-full"
|
||||
/>
|
||||
<button class="btn btn-error" @click="removeChannel(idx)">
|
||||
|
@ -139,19 +201,78 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 配置Dialog -->
|
||||
<dialog v-if="showConfigDialog" class="modal modal-open">
|
||||
<form
|
||||
method="dialog"
|
||||
class="modal-box max-w-fit"
|
||||
@submit.prevent="onConfigSubmit"
|
||||
>
|
||||
<h3 class="font-bold text-lg mb-4">调试器基本配置</h3>
|
||||
<div class="flex flex-col gap-4 w-80">
|
||||
<BaseInputField
|
||||
v-model="config.clkFreq"
|
||||
label="时钟频率 (MHz)"
|
||||
type="number"
|
||||
min="1"
|
||||
max="200"
|
||||
:error="
|
||||
config.clkFreq < 1 || config.clkFreq > 500 ? '范围1~200' : ''
|
||||
"
|
||||
required
|
||||
/>
|
||||
<BaseInputField
|
||||
v-model="config.totalPortNum"
|
||||
label="启用端口数"
|
||||
type="number"
|
||||
min="1"
|
||||
max="16"
|
||||
:error="
|
||||
config.totalPortNum < 1 || config.totalPortNum > 16
|
||||
? '范围1~16'
|
||||
: ''
|
||||
"
|
||||
required
|
||||
/>
|
||||
<BaseInputField
|
||||
v-model="config.captureDepth"
|
||||
label="采样深度"
|
||||
type="number"
|
||||
min="1"
|
||||
max="1048576"
|
||||
:error="
|
||||
config.captureDepth < 1 || config.captureDepth > 1048576
|
||||
? '范围1~1048576'
|
||||
: ''
|
||||
"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="modal-action mt-6">
|
||||
<button class="btn btn-primary" type="submit">确定</button>
|
||||
<button class="btn" type="button" @click="showConfigDialog = false">
|
||||
取消
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { CaptureMode } from "@/APIClient";
|
||||
import { CaptureMode, ChannelConfig, DebuggerConfig } from "@/APIClient";
|
||||
import { useAlertStore } from "@/components/Alert";
|
||||
import BaseInputField from "@/components/InputField/BaseInputField.vue";
|
||||
import type { LogicDataType } from "@/components/WaveformDisplay";
|
||||
import WaveformDisplay from "@/components/WaveformDisplay/WaveformDisplay.vue";
|
||||
import { AuthManager } from "@/utils/AuthManager";
|
||||
import { useRequiredInjection } from "@/utils/Common";
|
||||
import { useLocalStorage } from "@vueuse/core";
|
||||
import axios, { type CancelTokenSource } from "axios";
|
||||
import { isNull } from "lodash";
|
||||
import { Play, Square, Zap } from "lucide-vue-next";
|
||||
import { ref } from "vue";
|
||||
import { ref, reactive, computed } from "vue";
|
||||
|
||||
interface DebugChannel {
|
||||
name: string;
|
||||
|
@ -159,6 +280,15 @@ interface DebugChannel {
|
|||
color: string;
|
||||
trigger: CaptureMode;
|
||||
width: number;
|
||||
widthStr: string; // "start:宽度"
|
||||
start: number;
|
||||
parentPort: number;
|
||||
}
|
||||
|
||||
interface DebuggerSettings {
|
||||
clkFreq: number;
|
||||
totalPortNum: number;
|
||||
captureDepth: number;
|
||||
}
|
||||
|
||||
const triggerModes = [
|
||||
|
@ -169,153 +299,234 @@ const triggerModes = [
|
|||
{ value: CaptureMode.Fall, label: "↓ (下降沿)" },
|
||||
];
|
||||
|
||||
// 基本配置
|
||||
const config = reactive<DebuggerSettings>({
|
||||
clkFreq: 50,
|
||||
totalPortNum: 1,
|
||||
captureDepth: 1024,
|
||||
});
|
||||
const configInited = ref(false);
|
||||
const showConfigDialog = ref(false);
|
||||
|
||||
function onConfigSubmit() {
|
||||
configInited.value = true;
|
||||
showConfigDialog.value = false;
|
||||
// 清空通道
|
||||
channels.value = [];
|
||||
}
|
||||
|
||||
// 通道配置
|
||||
const channels = useLocalStorage<DebugChannel[]>("debugger-channels", []);
|
||||
const captureData = ref<LogicDataType>();
|
||||
const alert = useRequiredInjection(useAlertStore);
|
||||
|
||||
const isCapturing = ref(false);
|
||||
const readCancelTokenSource = ref<CancelTokenSource | null>(null);
|
||||
|
||||
async function startCapture() {
|
||||
if (channels.value.length === 0) {
|
||||
alert.error("请至少添加一个通道");
|
||||
// 解析widthStr为start/width
|
||||
function parseWidthStr(idx: number) {
|
||||
const ch = channels.value[idx];
|
||||
const match = /^(\d+)\s*:\s*(\d+)$/.exec(ch.widthStr);
|
||||
if (isNull(match)) {
|
||||
alert.error("格式错误,应为 起始位:宽度,如 0:7");
|
||||
ch.widthStr = `${ch.start}:${ch.width}`;
|
||||
return;
|
||||
}
|
||||
|
||||
isCapturing.value = true;
|
||||
const client = AuthManager.createAuthenticatedDebuggerClient();
|
||||
const min = Math.min(parseInt(match[1]), parseInt(match[2]));
|
||||
const max = Math.max(parseInt(match[1]), parseInt(match[2]));
|
||||
|
||||
for (let i = 0; i < channels.value.length; i++) {
|
||||
const channel = channels.value[i];
|
||||
if (!channel.visible) continue;
|
||||
if (!channel.name) {
|
||||
alert.error(`通道 ${i + 1} 名称不能为空`);
|
||||
isCapturing.value = false;
|
||||
return;
|
||||
}
|
||||
if (channel.width < 1 || channel.width > 32) {
|
||||
alert.error(`通道 ${i + 1} 数据位数必须在1到32之间`);
|
||||
isCapturing.value = false;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
let ret = await client.setMode(i, channel.trigger);
|
||||
if (!ret) {
|
||||
alert.error(`设置通道 ${i + 1} 触发模式失败`);
|
||||
isCapturing.value = false;
|
||||
return;
|
||||
}
|
||||
} catch (error: any) {
|
||||
alert.error(`设置通道 ${i + 1} 触发模式失败: ${error.message}`);
|
||||
isCapturing.value = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
let ret = await client.startTrigger();
|
||||
if (!ret) {
|
||||
alert.error("开始捕获失败,请检查连接");
|
||||
isCapturing.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
while ((await client.readFlag()) !== 1 && isCapturing.value) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
}
|
||||
|
||||
if (!isCapturing.value) {
|
||||
alert.info("捕获已停止");
|
||||
return;
|
||||
}
|
||||
|
||||
const base64Data = await client.readData(0);
|
||||
const binaryString = atob(base64Data);
|
||||
const bytes = new Uint8Array(binaryString.length);
|
||||
for (let i = 0; i < binaryString.length; i++) {
|
||||
bytes[i] = binaryString.charCodeAt(i);
|
||||
}
|
||||
|
||||
// 数据分割
|
||||
// 1. 统计每个通道的位宽
|
||||
const enabledChannels = channels.value.filter((ch) => ch.visible);
|
||||
const widths = enabledChannels.map((ch) => ch.width);
|
||||
const totalBitsPerSample = widths.reduce((a, b) => a + b, 0);
|
||||
|
||||
// 2. 计算总采样点数
|
||||
const totalBits = bytes.length * 8;
|
||||
const sampleCount = Math.floor(totalBits / totalBitsPerSample);
|
||||
|
||||
// 3. 逐采样点解析
|
||||
let bitOffset = 0;
|
||||
const channelValues: number[][] = enabledChannels.map(() => []);
|
||||
for (let sampleIdx = 0; sampleIdx < sampleCount; sampleIdx++) {
|
||||
for (let chIdx = 0; chIdx < enabledChannels.length; chIdx++) {
|
||||
const width = widths[chIdx];
|
||||
let value = 0;
|
||||
for (let w = 0; w < width; w++) {
|
||||
const absBit = bitOffset + w;
|
||||
const byteIdx = Math.floor(absBit / 8);
|
||||
const bitInByte = absBit % 8;
|
||||
const bit = (bytes[byteIdx] >> (7 - bitInByte)) & 1;
|
||||
value = (value << 1) | bit;
|
||||
}
|
||||
channelValues[chIdx].push(value);
|
||||
bitOffset += width;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 构造LogicDataType
|
||||
const x: number[] = [];
|
||||
const xUnit = "us"; // 5MHz -> 0.2us/point, 但WaveformDisplay支持ns/ms/us/s,选us
|
||||
for (let i = 0; i < sampleCount; i++) {
|
||||
x.push(i * 0.2); // 0.2us per sample
|
||||
}
|
||||
|
||||
const y = enabledChannels.map((ch, idx) => ({
|
||||
enabled: true,
|
||||
type: ch.width === 1 ? ("logic" as const) : ("number" as const),
|
||||
name: ch.name,
|
||||
color: ch.color,
|
||||
value: channelValues[idx],
|
||||
base: ch.width === 1 ? ("bin" as const) : ("hex" as const),
|
||||
}));
|
||||
|
||||
captureData.value = {
|
||||
x: x,
|
||||
y: y,
|
||||
xUnit: "us",
|
||||
};
|
||||
} catch (error: any) {
|
||||
alert.error(`开始捕获失败: ${error.message}`);
|
||||
isCapturing.value = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function stopCapture() {
|
||||
isCapturing.value = false;
|
||||
}
|
||||
|
||||
function handleDeleteData() {
|
||||
captureData.value = undefined;
|
||||
ch.start = min;
|
||||
ch.width = max - min + 1;
|
||||
}
|
||||
|
||||
function addChannel() {
|
||||
if (channels.value.length >= 16) {
|
||||
alert.error("最多只能添加16个通道");
|
||||
if (!configInited.value) {
|
||||
alert.error("请先配置调试器基本参数");
|
||||
return;
|
||||
}
|
||||
if (channels.value.length >= config.totalPortNum * 32) {
|
||||
alert.error("通道数已达最大线宽数");
|
||||
return;
|
||||
}
|
||||
|
||||
channels.value.push({
|
||||
name: `CH${channels.value.length + 1}`,
|
||||
visible: true,
|
||||
color: "#00bcd4",
|
||||
trigger: CaptureMode.None,
|
||||
width: 1,
|
||||
widthStr: "0:1",
|
||||
start: 0,
|
||||
parentPort: 0,
|
||||
});
|
||||
}
|
||||
|
||||
function removeChannel(idx: number) {
|
||||
channels.value.splice(idx, 1);
|
||||
}
|
||||
|
||||
function handleDeleteData() {
|
||||
captureData.value = undefined;
|
||||
}
|
||||
|
||||
function stopCapture() {
|
||||
isCapturing.value = false;
|
||||
if (readCancelTokenSource.value) {
|
||||
readCancelTokenSource.value.cancel("用户手动停止捕获");
|
||||
readCancelTokenSource.value = null;
|
||||
}
|
||||
}
|
||||
|
||||
async function startCapture(isRestart = false) {
|
||||
if (!configInited.value) {
|
||||
alert.error("请先配置调试器基本参数");
|
||||
return;
|
||||
}
|
||||
if (channels.value.length === 0) {
|
||||
alert.error("请至少添加一个通道");
|
||||
return;
|
||||
}
|
||||
// 校验通道参数
|
||||
if (!isRestart) {
|
||||
let usedWires = 0;
|
||||
for (let i = 0; i < channels.value.length; i++) {
|
||||
const ch = channels.value[i];
|
||||
if (!ch.visible) continue;
|
||||
if (!ch.name) {
|
||||
alert.error(`通道 ${i + 1} 名称不能为空`);
|
||||
return;
|
||||
}
|
||||
if (ch.width < 1 || ch.width > 32) {
|
||||
alert.error(`通道 ${i + 1} 数据位宽必须在1到32之间`);
|
||||
return;
|
||||
}
|
||||
if (ch.start < 0 || ch.start + ch.width > 32) {
|
||||
alert.error(`通道 ${i + 1} 起始位+宽度不能超过32`);
|
||||
return;
|
||||
}
|
||||
if (ch.parentPort < 0 || ch.parentPort >= config.totalPortNum) {
|
||||
alert.error(`通道 ${i + 1} 父端口编号超出范围`);
|
||||
return;
|
||||
}
|
||||
usedWires += ch.width;
|
||||
}
|
||||
if (usedWires > config.totalPortNum * 32) {
|
||||
alert.error("所有通道线宽总和不能超过最大线宽数");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
isCapturing.value = true;
|
||||
const client = AuthManager.createAuthenticatedDebuggerClient();
|
||||
|
||||
// 构造API配置
|
||||
const channelConfigs = channels.value
|
||||
.filter((ch) => ch.visible)
|
||||
.map(
|
||||
(ch) =>
|
||||
new ChannelConfig({
|
||||
name: ch.name,
|
||||
color: ch.color,
|
||||
wireWidth: ch.width,
|
||||
wireStartIndex: ch.start,
|
||||
parentPort: ch.parentPort,
|
||||
mode: ch.trigger,
|
||||
}),
|
||||
);
|
||||
|
||||
const apiConfig = new DebuggerConfig({
|
||||
clkFreq: config.clkFreq,
|
||||
totalPortNum: config.totalPortNum,
|
||||
captureDepth: config.captureDepth,
|
||||
triggerNum: 0,
|
||||
channelConfigs: channelConfigs,
|
||||
});
|
||||
|
||||
try {
|
||||
// 设置通道模式
|
||||
if (!isRestart) {
|
||||
let ret = await client.setChannelsMode(apiConfig);
|
||||
if (!ret) {
|
||||
alert.error("设置通道模式失败");
|
||||
isCapturing.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// 启动捕获
|
||||
ret = await client.startTrigger();
|
||||
if (!ret) {
|
||||
alert.error("开始捕获失败,请检查连接");
|
||||
isCapturing.value = false;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
let ret = await client.restartTrigger();
|
||||
if (!ret) {
|
||||
alert.error("重新开始捕获失败,请检查连接");
|
||||
isCapturing.value = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 读取数据
|
||||
readCancelTokenSource.value = axios.CancelToken.source();
|
||||
const readDataPromise = client
|
||||
.readData(apiConfig, readCancelTokenSource.value.token)
|
||||
.then((data) => {
|
||||
const enabledChannels = channelConfigs;
|
||||
const sampleCount = config.captureDepth;
|
||||
|
||||
// 解析数据
|
||||
const y = data.map((cd, idx) => {
|
||||
const ch = enabledChannels[idx];
|
||||
const bin = atob(cd.data);
|
||||
// UInt32数组
|
||||
const arr = [];
|
||||
for (let i = 0; i < bin.length; i += 4) {
|
||||
arr.push(
|
||||
(bin.charCodeAt(i) << 24) |
|
||||
(bin.charCodeAt(i + 1) << 16) |
|
||||
(bin.charCodeAt(i + 2) << 8) |
|
||||
bin.charCodeAt(i + 3),
|
||||
);
|
||||
}
|
||||
// 截取采样深度
|
||||
return {
|
||||
enabled: true,
|
||||
type: ch.wireWidth === 1 ? ("logic" as const) : ("number" as const),
|
||||
name: ch.name,
|
||||
color: ch.color,
|
||||
value: arr.slice(0, sampleCount),
|
||||
base: ch.wireWidth === 1 ? ("bin" as const) : ("hex" as const),
|
||||
};
|
||||
});
|
||||
|
||||
const x: number[] = [];
|
||||
for (let i = 0; i < sampleCount; i++) {
|
||||
x.push(i * (1 / config.clkFreq)); // us
|
||||
}
|
||||
|
||||
captureData.value = {
|
||||
x,
|
||||
y,
|
||||
xUnit: "us",
|
||||
};
|
||||
})
|
||||
.catch((error) => {
|
||||
if (axios.isCancel(error)) {
|
||||
alert.info("捕获已取消");
|
||||
} else {
|
||||
alert.error(`读取数据失败: ${error.message}`);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
isCapturing.value = false;
|
||||
readCancelTokenSource.value = null;
|
||||
});
|
||||
} catch (error: any) {
|
||||
alert.error(`开始捕获失败: ${error.message}`);
|
||||
isCapturing.value = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
Loading…
Reference in New Issue