feat(backend): 添加 Swagger 文档并优化认证接口

- 在 .env 文件中添加 ENABLE_SWAGGER 环境变量
- 在 app.js 中集成 Swagger UI
- 重构 auth 路由,添加请求参数验证
- 更新 API 文档,遵循 OpenAPI 3.0 规范
-优化认证接口的错误处理和响应格式
This commit is contained in:
2025-08-30 15:29:51 +08:00
parent 7f9bfbb381
commit 0cad74b06f
28 changed files with 2123 additions and 691 deletions

View File

@@ -2,6 +2,7 @@
NODE_ENV=development
PORT=3001
HOST=0.0.0.0
ENABLE_SWAGGER=true
# MySQL数据库配置
DB_HOST=192.168.0.240
@@ -47,8 +48,4 @@ WECHAT_SECRET=your-wechat-secret
# 文件上传配置
UPLOAD_MAX_SIZE=10485760
UPLOAD_ALLOWED_TYPES=image/jpeg,image/png,image/gif
# 调试配置
DEBUG=jiebanke:*
LOG_LEVEL=info
UPLOAD_ALLOWED_TYPES=image/jpeg,image/png,image/gif

View File

@@ -27,6 +27,8 @@
"multer": "^1.4.5-lts.1",
"mysql2": "^3.14.3",
"redis": "^5.8.2",
"swagger-jsdoc": "^6.2.8",
"swagger-ui-express": "^5.0.1",
"winston": "^3.11.0",
"xss-clean": "^0.1.4"
},
@@ -53,6 +55,46 @@
"node": ">=6.0.0"
}
},
"node_modules/@apidevtools/json-schema-ref-parser": {
"version": "9.1.2",
"resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.2.tgz",
"integrity": "sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg==",
"dependencies": {
"@jsdevtools/ono": "^7.1.3",
"@types/json-schema": "^7.0.6",
"call-me-maybe": "^1.0.1",
"js-yaml": "^4.1.0"
}
},
"node_modules/@apidevtools/openapi-schemas": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz",
"integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==",
"engines": {
"node": ">=10"
}
},
"node_modules/@apidevtools/swagger-methods": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz",
"integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg=="
},
"node_modules/@apidevtools/swagger-parser": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.0.3.tgz",
"integrity": "sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g==",
"dependencies": {
"@apidevtools/json-schema-ref-parser": "^9.0.6",
"@apidevtools/openapi-schemas": "^2.0.4",
"@apidevtools/swagger-methods": "^3.0.2",
"@jsdevtools/ono": "^7.1.3",
"call-me-maybe": "^1.0.1",
"z-schema": "^5.0.1"
},
"peerDependencies": {
"openapi-types": ">=7"
}
},
"node_modules/@babel/code-frame": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
@@ -1047,6 +1089,11 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@jsdevtools/ono": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz",
"integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg=="
},
"node_modules/@mongodb-js/saslprep": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.0.tgz",
@@ -1166,6 +1213,12 @@
"@redis/client": "^5.8.2"
}
},
"node_modules/@scarf/scarf": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz",
"integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==",
"hasInstallScript": true
},
"node_modules/@sinclair/typebox": {
"version": "0.27.8",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
@@ -1264,6 +1317,11 @@
"@types/istanbul-lib-report": "*"
}
},
"node_modules/@types/json-schema": {
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="
},
"node_modules/@types/node": {
"version": "24.3.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz",
@@ -1451,8 +1509,7 @@
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
},
"node_modules/array-flatten": {
"version": "1.1.1",
@@ -1597,8 +1654,7 @@
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"node_modules/basic-auth": {
"version": "2.0.1",
@@ -1673,7 +1729,6 @@
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -1801,6 +1856,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/call-me-maybe": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz",
"integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ=="
},
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -2028,6 +2088,14 @@
"node": ">= 0.8"
}
},
"node_modules/commander": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz",
"integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==",
"engines": {
"node": ">= 6"
}
},
"node_modules/component-emitter": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz",
@@ -2040,8 +2108,7 @@
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
},
"node_modules/concat-stream": {
"version": "1.6.2",
@@ -2264,7 +2331,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
"integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
"dev": true,
"dependencies": {
"esutils": "^2.0.2"
},
@@ -2574,7 +2640,6 @@
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -2919,8 +2984,7 @@
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
"dev": true
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
},
"node_modules/fsevents": {
"version": "2.3.3",
@@ -3268,7 +3332,6 @@
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
"dev": true,
"dependencies": {
"once": "^1.3.0",
"wrappy": "1"
@@ -4059,7 +4122,6 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dev": true,
"dependencies": {
"argparse": "^2.0.1"
},
@@ -4245,6 +4307,12 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/lodash.get": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
"integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==",
"deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead."
},
"node_modules/lodash.includes": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
@@ -4255,6 +4323,12 @@
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
},
"node_modules/lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
"deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead."
},
"node_modules/lodash.isinteger": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
@@ -4281,6 +4355,11 @@
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"dev": true
},
"node_modules/lodash.mergewith": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz",
"integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ=="
},
"node_modules/lodash.once": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
@@ -4465,7 +4544,6 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"dependencies": {
"brace-expansion": "^1.1.7"
},
@@ -4855,7 +4933,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"dev": true,
"dependencies": {
"wrappy": "1"
}
@@ -4883,6 +4960,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/openapi-types": {
"version": "12.1.3",
"resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz",
"integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==",
"peer": true
},
"node_modules/optionator": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@@ -4990,7 +5073,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -5949,6 +6031,78 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/swagger-jsdoc": {
"version": "6.2.8",
"resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.2.8.tgz",
"integrity": "sha512-VPvil1+JRpmJ55CgAtn8DIcpBs0bL5L3q5bVQvF4tAW/k/9JYSj7dCpaYCAv5rufe0vcCbBRQXGvzpkWjvLklQ==",
"dependencies": {
"commander": "6.2.0",
"doctrine": "3.0.0",
"glob": "7.1.6",
"lodash.mergewith": "^4.6.2",
"swagger-parser": "^10.0.3",
"yaml": "2.0.0-1"
},
"bin": {
"swagger-jsdoc": "bin/swagger-jsdoc.js"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/swagger-jsdoc/node_modules/glob": {
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"deprecated": "Glob versions prior to v9 are no longer supported",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/swagger-parser": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.3.tgz",
"integrity": "sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg==",
"dependencies": {
"@apidevtools/swagger-parser": "10.0.3"
},
"engines": {
"node": ">=10"
}
},
"node_modules/swagger-ui-dist": {
"version": "5.28.0",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.28.0.tgz",
"integrity": "sha512-I9ibQtr77BPzT28WFWMVktzQOtWzoSS2J99L0Att8gDar1atl1YTRI7NUFSr4kj8VvWICgylanYHIoHjITc7iA==",
"dependencies": {
"@scarf/scarf": "=1.4.0"
}
},
"node_modules/swagger-ui-express": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz",
"integrity": "sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==",
"dependencies": {
"swagger-ui-dist": ">=5.0.0"
},
"engines": {
"node": ">= v0.10.32"
},
"peerDependencies": {
"express": ">=4.0.0 || >=5.0.0-beta"
}
},
"node_modules/test-exclude": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
@@ -6322,8 +6476,7 @@
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"node_modules/write-file-atomic": {
"version": "4.0.2",
@@ -6375,6 +6528,14 @@
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"dev": true
},
"node_modules/yaml": {
"version": "2.0.0-1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz",
"integrity": "sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==",
"engines": {
"node": ">= 6"
}
},
"node_modules/yargs": {
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
@@ -6413,6 +6574,34 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/z-schema": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz",
"integrity": "sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==",
"dependencies": {
"lodash.get": "^4.4.2",
"lodash.isequal": "^4.5.0",
"validator": "^13.7.0"
},
"bin": {
"z-schema": "bin/z-schema"
},
"engines": {
"node": ">=8.0.0"
},
"optionalDependencies": {
"commander": "^9.4.1"
}
},
"node_modules/z-schema/node_modules/commander": {
"version": "9.5.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz",
"integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==",
"optional": true,
"engines": {
"node": "^12.20.0 || >=14"
}
}
}
}

View File

@@ -37,6 +37,8 @@
"multer": "^1.4.5-lts.1",
"mysql2": "^3.14.3",
"redis": "^5.8.2",
"swagger-jsdoc": "^6.2.8",
"swagger-ui-express": "^5.0.1",
"winston": "^3.11.0",
"xss-clean": "^0.1.4"
},
@@ -49,4 +51,4 @@
"engines": {
"node": ">=16.0.0"
}
}
}

View File

@@ -5,6 +5,8 @@ const morgan = require('morgan')
const rateLimit = require('express-rate-limit')
const xss = require('xss-clean')
const hpp = require('hpp')
const swaggerUi = require('swagger-ui-express')
const swaggerSpec = require('./config/swagger')
console.log('🔧 初始化Express应用...')
@@ -70,6 +72,12 @@ app.use(hpp({ // 防止参数污染
// 静态文件服务
app.use('/uploads', express.static('uploads'))
// Swagger文档路由
if (process.env.NODE_ENV === 'development' || process.env.ENABLE_SWAGGER === 'true') {
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec))
console.log('📚 Swagger文档已启用: http://localhost:3001/api-docs')
}
// 健康检查路由
app.get('/health', (req, res) => {
res.status(200).json({

View File

@@ -0,0 +1,133 @@
const swaggerJsdoc = require('swagger-jsdoc')
const options = {
definition: {
openapi: '3.0.0',
info: {
title: '结伴客API文档',
version: '1.0.0',
description: '结伴客小程序后端API接口文档'
},
servers: [
{
url: 'http://localhost:3001/api/v1',
description: '开发环境服务器'
},
{
url: 'https://your-domain.com/api/v1',
description: '生产环境服务器'
}
],
components: {
securitySchemes: {
bearerAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT'
}
},
schemas: {
User: {
type: 'object',
properties: {
id: {
type: 'integer',
description: '用户ID'
},
openid: {
type: 'string',
description: '微信openid'
},
username: {
type: 'string',
description: '用户名'
},
nickname: {
type: 'string',
description: '昵称'
},
avatar: {
type: 'string',
description: '头像URL'
},
gender: {
type: 'string',
enum: ['male', 'female', 'other'],
description: '性别'
},
birthday: {
type: 'string',
format: 'date',
description: '生日'
},
phone: {
type: 'string',
description: '手机号'
},
email: {
type: 'string',
description: '邮箱'
},
status: {
type: 'string',
enum: ['active', 'inactive', 'banned'],
description: '用户状态'
},
level: {
type: 'integer',
description: '用户等级'
},
points: {
type: 'integer',
description: '用户积分'
},
created_at: {
type: 'string',
format: 'date-time',
description: '创建时间'
},
updated_at: {
type: 'string',
format: 'date-time',
description: '更新时间'
}
}
},
ApiResponse: {
type: 'object',
properties: {
success: {
type: 'boolean',
description: '请求是否成功'
},
code: {
type: 'integer',
description: '状态码'
},
message: {
type: 'string',
description: '响应消息'
},
data: {
type: 'object',
description: '响应数据'
}
}
}
}
},
security: [
{
bearerAuth: []
}
]
},
apis: [
'./src/routes/*.js',
'./src/controllers/*.js'
]
}
const specs = swaggerJsdoc(options)
module.exports = specs

View File

@@ -1,33 +1,329 @@
const express = require('express')
const { catchAsync } = require('../utils/errors')
const { authenticate, optionalAuthenticate } = require('../middleware/auth')
const {
register,
login,
getCurrentUser,
updateProfile,
changePassword,
wechatLogin
} = require('../controllers/authControllerMySQL')
const { body } = require('express-validator')
const authController = require('../controllers/authControllerMySQL')
const router = express.Router()
// 用户注册
router.post('/register', catchAsync(register))
/**
* @swagger
* tags:
* name: Auth
* description: 用户认证相关接口
*/
// 用户登录
router.post('/login', catchAsync(login))
/**
* @swagger
* /auth/register:
* post:
* summary: 用户注册
* tags: [Auth]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - username
* - password
* properties:
* username:
* type: string
* description: 用户名
* example: testuser
* password:
* type: string
* description: 密码
* example: password123
* nickname:
* type: string
* description: 昵称
* example: 测试用户
* email:
* type: string
* description: 邮箱
* example: test@example.com
* phone:
* type: string
* description: 手机号
* example: 13800000000
* responses:
* 201:
* description: 注册成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: object
* properties:
* user:
* $ref: '#/components/schemas/User'
* token:
* type: string
* message:
* type: string
* 400:
* description: 请求参数错误
* 500:
* description: 服务器内部错误
*/
router.post(
'/register',
[
body('username').notEmpty().withMessage('用户名不能为空'),
body('password').isLength({ min: 6 }).withMessage('密码长度不能少于6位')
],
authController.register
)
// 微信登录
router.post('/wechat-login', catchAsync(wechatLogin))
/**
* @swagger
* /auth/login:
* post:
* summary: 用户登录
* tags: [Auth]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - username
* - password
* properties:
* username:
* type: string
* description: 用户名/邮箱/手机号
* example: testuser
* password:
* type: string
* description: 密码
* example: password123
* responses:
* 200:
* description: 登录成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: object
* properties:
* user:
* $ref: '#/components/schemas/User'
* token:
* type: string
* message:
* type: string
* 400:
* description: 请求参数错误
* 401:
* description: 用户名或密码错误
* 404:
* description: 用户不存在
* 500:
* description: 服务器内部错误
*/
router.post(
'/login',
[
body('username').notEmpty().withMessage('用户名不能为空'),
body('password').notEmpty().withMessage('密码不能为空')
],
authController.login
)
// 获取当前用户信息(需要认证)
router.get('/me', authenticate, catchAsync(getCurrentUser))
/**
* @swagger
* /auth/me:
* get:
* summary: 获取当前用户信息
* tags: [Auth]
* security:
* - bearerAuth: []
* responses:
* 200:
* description: 获取成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: object
* properties:
* user:
* $ref: '#/components/schemas/User'
* 401:
* description: 未授权
* 500:
* description: 服务器内部错误
*/
router.get('/me', authController.getCurrentUser)
// 更新用户信息(需要认证)
router.put('/profile', authenticate, catchAsync(updateProfile))
/**
* @swagger
* /auth/profile:
* put:
* summary: 更新用户个人信息
* tags: [Auth]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* nickname:
* type: string
* description: 昵称
* avatar:
* type: string
* description: 头像URL
* gender:
* type: string
* enum: [male, female, other]
* description: 性别
* birthday:
* type: string
* format: date
* description: 生日
* responses:
* 200:
* description: 更新成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: object
* properties:
* user:
* $ref: '#/components/schemas/User'
* message:
* type: string
* 400:
* description: 请求参数错误
* 401:
* description: 未授权
* 500:
* description: 服务器内部错误
*/
router.put('/profile', authController.updateProfile)
/**
* @swagger
* /auth/password:
* put:
* summary: 修改密码
* tags: [Auth]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - currentPassword
* - newPassword
* properties:
* currentPassword:
* type: string
* description: 当前密码
* newPassword:
* type: string
* description: 新密码
* responses:
* 200:
* description: 修改成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* message:
* type: string
* 400:
* description: 请求参数错误
* 401:
* description: 当前密码错误
* 500:
* description: 服务器内部错误
*/
router.put(
'/password',
[
body('currentPassword').notEmpty().withMessage('当前密码不能为空'),
body('newPassword').isLength({ min: 6 }).withMessage('新密码长度不能少于6位')
],
authController.changePassword
)
/**
* @swagger
* /auth/wechat:
* post:
* summary: 微信登录/注册
* tags: [Auth]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - code
* properties:
* code:
* type: string
* description: 微信授权码
* userInfo:
* type: object
* description: 微信用户信息
* responses:
* 200:
* description: 登录成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: object
* properties:
* user:
* $ref: '#/components/schemas/User'
* token:
* type: string
* message:
* type: string
* 400:
* description: 请求参数错误
* 500:
* description: 服务器内部错误
*/
router.post('/wechat', authController.wechatLogin)
// 修改密码(需要认证)
router.put('/password', authenticate, catchAsync(changePassword))
module.exports = router

View File

@@ -96,8 +96,8 @@ const startServer = async () => {
console.log(`📊 环境: ${process.env.NODE_ENV || 'development'}`)
console.log(`⏰ 启动时间: ${new Date().toLocaleString()}`)
console.log('💾 数据库: MySQL')
console.log(`🔴 Redis: ${redisConfig.isConnected() ? '已连接' : '未连接'}`)
console.log(`🐰 RabbitMQ: ${rabbitMQConfig.isConnected() ? '已连接' : '未连接'}`)
console.log(`🔴 Redis: ${redisConfig.isConnected ? '已连接' : '未连接'}`)
console.log(`🐰 RabbitMQ: ${rabbitMQConfig.isConnected ? '已连接' : '未连接'}`)
console.log('========================================\n')
})
@@ -130,7 +130,7 @@ const startServer = async () => {
}
// 关闭RabbitMQ连接
if (rabbitMQConfig.isConnected()) {
if (rabbitMQConfig.isConnected) {
console.log('🔐 关闭RabbitMQ连接...')
await rabbitMQConfig.close()
console.log('✅ RabbitMQ连接已关闭')

30
backend/test-swagger.js Normal file
View File

@@ -0,0 +1,30 @@
const http = require('http');
// 发送请求到Swagger UI
const options = {
hostname: 'localhost',
port: 3001,
path: '/api-docs/',
method: 'GET'
};
const req = http.request(options, (res) => {
console.log(`状态码: ${res.statusCode}`);
res.on('data', (chunk) => {
// 检查响应中是否包含Swagger UI的关键字
if (chunk.toString().includes('Swagger UI')) {
console.log('Swagger UI 已成功启动并运行');
} else {
console.log('收到响应但可能不是Swagger UI页面');
}
// 只输出前200个字符来检查内容
console.log('响应前200字符:', chunk.toString().substring(0, 200));
});
});
req.on('error', (error) => {
console.error('请求出错:', error.message);
});
req.end();