From e90c183c905d542fa773245d92197fdb269a34d4 Mon Sep 17 00:00:00 2001 From: ylweng Date: Tue, 2 Sep 2025 23:41:32 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=9B=B4=E6=96=B0=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E6=96=87=E6=A1=A3=EF=BC=8C=E5=AE=8C=E5=96=84=E9=9C=80=E6=B1=82?= =?UTF-8?q?=E5=92=8C=E6=8A=80=E6=9C=AF=E7=BB=86=E8=8A=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/config/swagger.js | 362 +++++++++++++++++++++-------- backend/package-lock.json | 230 +++++++++++++++++++ backend/package.json | 1 + backend/routes/addresses.js | 439 ++++++++++++++++++++++++++++++++---- backend/routes/auth.js | 181 +++++++++++++++ backend/routes/cart.js | 399 ++++++++++++++++++++++++++++++-- backend/routes/orders.js | 142 +++++++++++- backend/routes/products.js | 124 +++++++++- backend/routes/users.js | 155 ++++++++++++- 9 files changed, 1865 insertions(+), 168 deletions(-) diff --git a/backend/config/swagger.js b/backend/config/swagger.js index 898b67d..9770b52 100644 --- a/backend/config/swagger.js +++ b/backend/config/swagger.js @@ -1,15 +1,16 @@ -const swaggerJsdoc = require('swagger-jsdoc'); +const swaggerJSDoc = require('swagger-jsdoc'); const swaggerUi = require('swagger-ui-express'); +// Swagger 配置选项 const options = { definition: { openapi: '3.0.0', info: { - title: '爱鉴花小程序后端API', + title: '爱鉴花 API 文档', version: '1.0.0', - description: '爱鉴花小程序后端RESTful API文档', + description: '爱鉴花小程序和后端管理系统的 RESTful API 文档', contact: { - name: 'API支持', + name: '技术支持', email: 'support@aijianhua.com' }, license: { @@ -19,15 +20,17 @@ const options = { }, servers: [ { - url: process.env.NODE_ENV === 'production' - ? 'https://api.aijianhua.com' - : `http://localhost:${process.env.PORT || 3200}`, - description: process.env.NODE_ENV === 'production' ? '生产环境' : '开发环境' + url: 'http://localhost:3200/api/v1', + description: '开发环境' + }, + { + url: 'https://api.aijianhua.com/api/v1', + description: '生产环境' } ], components: { securitySchemes: { - BearerAuth: { + bearerAuth: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' @@ -37,117 +40,286 @@ const options = { User: { type: 'object', properties: { - id: { type: 'integer', example: 1 }, - username: { type: 'string', example: 'testuser' }, - phone: { type: 'string', example: '13800138000' }, - email: { type: 'string', example: 'user@example.com' }, - user_type: { type: 'string', enum: ['farmer', 'buyer', 'admin'], example: 'farmer' }, - avatar_url: { type: 'string', example: '/uploads/avatars/avatar.jpg' }, - created_at: { type: 'string', format: 'date-time', example: '2023-01-01T00:00:00Z' }, - last_login: { type: 'string', format: 'date-time', example: '2023-01-01T00:00:00Z' } + id: { + type: 'integer', + example: 1 + }, + username: { + type: 'string', + example: 'user123' + }, + phone: { + type: 'string', + example: '13800138000' + }, + email: { + type: 'string', + example: 'user@example.com' + }, + user_type: { + type: 'string', + example: 'farmer' + }, + avatar_url: { + type: 'string', + example: 'https://example.com/avatar.jpg' + }, + created_at: { + type: 'string', + format: 'date-time', + example: '2023-01-01T00:00:00Z' + }, + last_login: { + type: 'string', + format: 'date-time', + example: '2023-01-01T00:00:00Z' + } } }, Product: { type: 'object', properties: { - id: { type: 'integer', example: 1 }, - name: { type: 'string', example: '玫瑰花' }, - category_id: { type: 'integer', example: 1 }, - price: { type: 'number', format: 'float', example: 29.9 }, - stock: { type: 'integer', example: 100 }, - image: { type: 'string', example: '/uploads/products/rose.jpg' }, - description: { type: 'string', example: '新鲜玫瑰花,香气浓郁' }, - status: { type: 'integer', enum: [0, 1], example: 1 }, - created_at: { type: 'string', format: 'date-time', example: '2023-01-01T00:00:00Z' }, - updated_at: { type: 'string', format: 'date-time', example: '2023-01-01T00:00:00Z' } + id: { + type: 'integer', + example: 1 + }, + name: { + type: 'string', + example: '玫瑰花' + }, + category_id: { + type: 'integer', + example: 1 + }, + category_name: { + type: 'string', + example: '鲜花' + }, + price: { + type: 'number', + example: 29.9 + }, + stock: { + type: 'integer', + example: 100 + }, + image: { + type: 'string', + example: 'https://example.com/product.jpg' + }, + description: { + type: 'string', + example: '新鲜玫瑰花' + }, + status: { + type: 'integer', + example: 1 + }, + created_at: { + type: 'string', + format: 'date-time', + example: '2023-01-01T00:00:00Z' + }, + updated_at: { + type: 'string', + format: 'date-time', + example: '2023-01-01T00:00:00Z' + } } }, Order: { type: 'object', properties: { - id: { type: 'integer', example: 1 }, - order_number: { type: 'string', example: 'O123456789' }, - user_id: { type: 'integer', example: 1 }, - total_amount: { type: 'number', format: 'float', example: 99.9 }, - payment_status: { type: 'string', enum: ['pending', 'paid', 'cancelled'], example: 'pending' }, - shipping_status: { type: 'string', enum: ['pending', 'shipped', 'delivered'], example: 'pending' }, - shipping_address: { type: 'string', example: '北京市朝阳区xxx街道' }, - created_at: { type: 'string', format: 'date-time', example: '2023-01-01T00:00:00Z' }, - updated_at: { type: 'string', format: 'date-time', example: '2023-01-01T00:00:00Z' } - } - }, - Identification: { - type: 'object', - properties: { - id: { type: 'integer', example: 1 }, - user_id: { type: 'integer', example: 1 }, - image_url: { type: 'string', example: '/uploads/identifications/identify-123.jpg' }, - result: { type: 'string', description: 'JSON格式的识别结果' }, - confidence: { type: 'number', format: 'float', example: 0.95 }, - created_at: { type: 'string', format: 'date-time', example: '2023-01-01T00:00:00Z' } - } - }, - Error: { - type: 'object', - properties: { - code: { type: 'integer', example: 400 }, - message: { type: 'string', example: '参数验证失败' }, - data: { type: 'object', nullable: true, example: null } - } - } - }, - responses: { - UnauthorizedError: { - description: '未授权访问', - content: { - 'application/json': { - schema: { $ref: '#/components/schemas/Error' }, - example: { - code: 401, - message: '未提供有效的认证token', - data: null - } + id: { + type: 'integer', + example: 1 + }, + order_number: { + type: 'string', + example: 'ORD202301010001' + }, + user_id: { + type: 'integer', + example: 1 + }, + total_amount: { + type: 'number', + example: 99.9 + }, + payment_status: { + type: 'integer', + example: 1 + }, + shipping_status: { + type: 'integer', + example: 0 + }, + shipping_address: { + type: 'string', + example: '北京市朝阳区xxx街道xxx号' + }, + created_at: { + type: 'string', + format: 'date-time', + example: '2023-01-01T00:00:00Z' + }, + updated_at: { + type: 'string', + format: 'date-time', + example: '2023-01-01T00:00:00Z' } } }, - NotFoundError: { - description: '资源不存在', - content: { - 'application/json': { - schema: { $ref: '#/components/schemas/Error' }, - example: { - code: 404, - message: '资源不存在', - data: null - } + OrderItem: { + type: 'object', + properties: { + id: { + type: 'integer', + example: 1 + }, + order_id: { + type: 'integer', + example: 1 + }, + product_id: { + type: 'integer', + example: 1 + }, + quantity: { + type: 'integer', + example: 2 + }, + price: { + type: 'number', + example: 29.9 } } }, - ValidationError: { - description: '参数验证失败', - content: { - 'application/json': { - schema: { $ref: '#/components/schemas/Error' }, - example: { - code: 400, - message: '参数验证失败', - data: null - } + CartItem: { + type: 'object', + properties: { + id: { + type: 'integer', + example: 1 + }, + user_id: { + type: 'integer', + example: 1 + }, + product_id: { + type: 'integer', + example: 1 + }, + product_name: { + type: 'string', + example: '玫瑰花' + }, + product_image: { + type: 'string', + example: 'https://example.com/product.jpg' + }, + price: { + type: 'number', + example: 29.9 + }, + quantity: { + type: 'integer', + example: 2 + }, + stock: { + type: 'integer', + example: 100 + }, + created_at: { + type: 'string', + format: 'date-time', + example: '2023-01-01T00:00:00Z' + }, + updated_at: { + type: 'string', + format: 'date-time', + example: '2023-01-01T00:00:00Z' + } + } + }, + Address: { + type: 'object', + properties: { + id: { + type: 'integer', + example: 1 + }, + user_id: { + type: 'integer', + example: 1 + }, + recipient: { + type: 'string', + example: '张三' + }, + phone: { + type: 'string', + example: '13800138000' + }, + province: { + type: 'string', + example: '北京市' + }, + city: { + type: 'string', + example: '北京市' + }, + district: { + type: 'string', + example: '朝阳区' + }, + detail: { + type: 'string', + example: 'xxx街道xxx号' + }, + is_default: { + type: 'integer', + example: 1 + }, + created_at: { + type: 'string', + format: 'date-time', + example: '2023-01-01T00:00:00Z' + } + } + }, + Pagination: { + type: 'object', + properties: { + page: { + type: 'integer', + example: 1 + }, + limit: { + type: 'integer', + example: 10 + }, + total: { + type: 'integer', + example: 100 + }, + pages: { + type: 'integer', + example: 10 } } } } }, security: [ - { BearerAuth: [] } + { + bearerAuth: [] + } ] }, - apis: [ - './routes/*.js', - './middlewares/*.js' - ] + apis: ['./routes/*.js'] // 扫描路由文件中的 Swagger 注释 }; -const specs = swaggerJsdoc(options); +const specs = swaggerJSDoc(options); module.exports = { specs, swaggerUi }; \ No newline at end of file diff --git a/backend/package-lock.json b/backend/package-lock.json index c7a0e31..3917aa8 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -22,6 +22,7 @@ "redis": "^4.6.8", "socket.io": "^4.7.2", "sqlite3": "^5.1.7", + "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^4.6.3", "validator": "^13.11.0", "yamljs": "^0.3.0" @@ -33,6 +34,50 @@ "node": ">=14.0.0" } }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "9.1.2", + "resolved": "https://registry.npmmirror.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.2.tgz", + "integrity": "sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg==", + "license": "MIT", + "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.npmmirror.com/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz", + "integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/@apidevtools/swagger-methods": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz", + "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==", + "license": "MIT" + }, + "node_modules/@apidevtools/swagger-parser": { + "version": "10.0.3", + "resolved": "https://registry.npmmirror.com/@apidevtools/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g==", + "license": "MIT", + "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/@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmmirror.com/@gar/promisify/-/promisify-1.1.3.tgz", @@ -40,6 +85,12 @@ "license": "MIT", "optional": true }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmmirror.com/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "license": "MIT" + }, "node_modules/@npmcli/fs": { "version": "1.1.1", "resolved": "https://registry.npmmirror.com/@npmcli/fs/-/fs-1.1.1.tgz", @@ -170,6 +221,12 @@ "@types/node": "*" } }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" + }, "node_modules/@types/node": { "version": "20.19.11", "resolved": "https://registry.npmmirror.com/@types/node/-/node-20.19.11.tgz", @@ -340,6 +397,12 @@ "node": ">= 6" } }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmmirror.com/array-flatten/-/array-flatten-1.1.1.tgz", @@ -636,6 +699,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", + "license": "MIT" + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz", @@ -712,6 +781,15 @@ "color-support": "bin.js" } }, + "node_modules/commander": { + "version": "6.2.0", + "resolved": "https://registry.npmmirror.com/commander/-/commander-6.2.0.tgz", + "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/compressible": { "version": "2.0.18", "resolved": "https://registry.npmmirror.com/compressible/-/compressible-2.0.18.tgz", @@ -902,6 +980,18 @@ "node": ">=8" } }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/dotenv": { "version": "16.6.1", "resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-16.6.1.tgz", @@ -1105,6 +1195,15 @@ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "license": "MIT" }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz", @@ -1736,6 +1835,18 @@ "license": "ISC", "optional": true }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/jsonwebtoken": { "version": "9.0.2", "resolved": "https://registry.npmmirror.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", @@ -1785,6 +1896,13 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmmirror.com/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.", + "license": "MIT" + }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmmirror.com/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -1797,6 +1915,13 @@ "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", "license": "MIT" }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", + "license": "MIT" + }, "node_modules/lodash.isinteger": { "version": "4.0.4", "resolved": "https://registry.npmmirror.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", @@ -1821,6 +1946,12 @@ "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", "license": "MIT" }, + "node_modules/lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmmirror.com/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", + "license": "MIT" + }, "node_modules/lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmmirror.com/lodash.once/-/lodash.once-4.1.1.tgz", @@ -2450,6 +2581,13 @@ "wrappy": "1" } }, + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmmirror.com/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", + "license": "MIT", + "peer": true + }, "node_modules/p-map": { "version": "4.0.0", "resolved": "https://registry.npmmirror.com/p-map/-/p-map-4.0.0.tgz", @@ -3272,6 +3410,59 @@ "node": ">=0.10.0" } }, + "node_modules/swagger-jsdoc": { + "version": "6.2.8", + "resolved": "https://registry.npmmirror.com/swagger-jsdoc/-/swagger-jsdoc-6.2.8.tgz", + "integrity": "sha512-VPvil1+JRpmJ55CgAtn8DIcpBs0bL5L3q5bVQvF4tAW/k/9JYSj7dCpaYCAv5rufe0vcCbBRQXGvzpkWjvLklQ==", + "license": "MIT", + "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.npmmirror.com/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "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.npmmirror.com/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg==", + "license": "MIT", + "dependencies": { + "@apidevtools/swagger-parser": "10.0.3" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/swagger-ui-dist": { "version": "5.28.0", "resolved": "https://registry.npmmirror.com/swagger-ui-dist/-/swagger-ui-dist-5.28.0.tgz", @@ -3588,6 +3779,15 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "license": "ISC" }, + "node_modules/yaml": { + "version": "2.0.0-1", + "resolved": "https://registry.npmmirror.com/yaml/-/yaml-2.0.0-1.tgz", + "integrity": "sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, "node_modules/yamljs": { "version": "0.3.0", "resolved": "https://registry.npmmirror.com/yamljs/-/yamljs-0.3.0.tgz", @@ -3610,6 +3810,36 @@ "dependencies": { "sprintf-js": "~1.0.2" } + }, + "node_modules/z-schema": { + "version": "5.0.5", + "resolved": "https://registry.npmmirror.com/z-schema/-/z-schema-5.0.5.tgz", + "integrity": "sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==", + "license": "MIT", + "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.npmmirror.com/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": "^12.20.0 || >=14" + } } } } diff --git a/backend/package.json b/backend/package.json index a9b324a..0ceff12 100644 --- a/backend/package.json +++ b/backend/package.json @@ -25,6 +25,7 @@ "redis": "^4.6.8", "socket.io": "^4.7.2", "sqlite3": "^5.1.7", + "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^4.6.3", "validator": "^13.11.0", "yamljs": "^0.3.0" diff --git a/backend/routes/addresses.js b/backend/routes/addresses.js index 151ffdd..ecd6a96 100644 --- a/backend/routes/addresses.js +++ b/backend/routes/addresses.js @@ -2,6 +2,61 @@ const express = require('express'); const router = express.Router(); const dbConnector = require('../utils/dbConnector'); +/** + * @swagger + * /api/v1/addresses: + * get: + * summary: 获取用户收货地址列表 + * description: 获取当前用户的所有收货地址,按默认地址和创建时间排序 + * tags: + * - 地址管理 + * security: + * - bearerAuth: [] + * responses: + * 200: + * description: 成功获取收货地址列表 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 200 + * message: + * type: string + * example: 获取成功 + * data: + * type: array + * items: + * $ref: '#/components/schemas/Address' + * 401: + * description: 未授权访问 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 401 + * message: + * type: string + * example: 未授权访问 + * 500: + * description: 服务器内部错误 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 500 + * message: + * type: string + * example: 服务器内部错误 + */ // 获取用户收货地址列表 router.get('/', async (req, res) => { try { @@ -29,12 +84,132 @@ router.get('/', async (req, res) => { } }); +/** + * @swagger + * /api/v1/addresses: + * post: + * summary: 添加收货地址 + * description: 为当前用户添加新的收货地址 + * tags: + * - 地址管理 + * security: + * - bearerAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - recipient + * - phone + * - province + * - city + * - district + * - detail + * properties: + * recipient: + * type: string + * example: "张三" + * description: 收货人姓名 + * phone: + * type: string + * example: "13800138000" + * description: 收货人手机号 + * province: + * type: string + * example: "北京市" + * description: 省份 + * city: + * type: string + * example: "北京市" + * description: 城市 + * district: + * type: string + * example: "朝阳区" + * description: 区/县 + * detail: + * type: string + * example: "xxx街道xxx号" + * description: 详细地址 + * is_default: + * type: integer + * example: 1 + * description: 是否为默认地址 (0-否, 1-是) + * responses: + * 200: + * description: 成功添加收货地址 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 200 + * message: + * type: string + * example: 添加成功 + * data: + * type: object + * properties: + * address_id: + * type: integer + * example: 1 + * 400: + * description: 请求参数错误 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 400 + * message: + * type: string + * example: 收货人姓名、手机号和地址信息不能为空 + * 401: + * description: 未授权访问 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 401 + * message: + * type: string + * example: 未授权访问 + * 500: + * description: 服务器内部错误 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 500 + * message: + * type: string + * example: 服务器内部错误 + */ // 添加收货地址 router.post('/', async (req, res) => { try { const { recipient, phone, province, city, district, detail, is_default } = req.body; const userId = req.user.id; + // 验证必填字段 + if (!recipient || !phone || !province || !city || !district || !detail) { + return res.status(400).json({ + code: 400, + message: '收货人姓名、手机号和地址信息不能为空' + }); + } + // 如果设置为默认地址,先取消其他默认地址 if (is_default) { await dbConnector.query( @@ -50,8 +225,8 @@ router.post('/', async (req, res) => { [userId, recipient, phone, province, city, district, detail, is_default || 0] ); - res.status(201).json({ - code: 201, + res.json({ + code: 200, message: '添加成功', data: { address_id: result.insertId @@ -67,6 +242,125 @@ router.post('/', async (req, res) => { } }); +/** + * @swagger + * /api/v1/addresses/{id}: + * put: + * summary: 更新收货地址 + * description: 更新当前用户的指定收货地址 + * tags: + * - 地址管理 + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: 地址ID + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * recipient: + * type: string + * example: "张三" + * description: 收货人姓名 + * phone: + * type: string + * example: "13800138000" + * description: 收货人手机号 + * province: + * type: string + * example: "北京市" + * description: 省份 + * city: + * type: string + * example: "北京市" + * description: 城市 + * district: + * type: string + * example: "朝阳区" + * description: 区/县 + * detail: + * type: string + * example: "xxx街道xxx号" + * description: 详细地址 + * is_default: + * type: integer + * example: 1 + * description: 是否为默认地址 (0-否, 1-是) + * responses: + * 200: + * description: 成功更新收货地址 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 200 + * message: + * type: string + * example: 更新成功 + * 400: + * description: 请求参数错误 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 400 + * message: + * type: string + * example: 收货人姓名、手机号和地址信息不能为空 + * 401: + * description: 未授权访问 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 401 + * message: + * type: string + * example: 未授权访问 + * 404: + * description: 地址不存在 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 404 + * message: + * type: string + * example: 地址不存在 + * 500: + * description: 服务器内部错误 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 500 + * message: + * type: string + * example: 服务器内部错误 + */ // 更新收货地址 router.put('/:id', async (req, res) => { try { @@ -74,13 +368,21 @@ router.put('/:id', async (req, res) => { const { recipient, phone, province, city, district, detail, is_default } = req.body; const userId = req.user.id; - // 检查地址是否存在 - const address = await dbConnector.query( + // 验证必填字段 + if (!recipient || !phone || !province || !city || !district || !detail) { + return res.status(400).json({ + code: 400, + message: '收货人姓名、手机号和地址信息不能为空' + }); + } + + // 检查地址是否存在且属于当前用户 + const existingAddress = await dbConnector.query( 'SELECT * FROM addresses WHERE id = ? AND user_id = ?', [id, userId] ); - if (address.length === 0) { + if (existingAddress.length === 0) { return res.status(404).json({ code: 404, message: '地址不存在' @@ -96,10 +398,11 @@ router.put('/:id', async (req, res) => { } await dbConnector.query( - `UPDATE addresses SET - recipient = ?, phone = ?, province = ?, city = ?, district = ?, detail = ?, is_default = ?, updated_at = NOW() - WHERE id = ? AND user_id = ?`, - [recipient, phone, province, city, district, detail, is_default || 0, id, userId] + `UPDATE addresses + SET recipient = ?, phone = ?, province = ?, city = ?, district = ?, detail = ?, + is_default = ?, updated_at = NOW() + WHERE id = ?`, + [recipient, phone, province, city, district, detail, is_default || 0, id] ); res.json({ @@ -116,17 +419,99 @@ router.put('/:id', async (req, res) => { } }); +/** + * @swagger + * /api/v1/addresses/{id}: + * delete: + * summary: 删除收货地址 + * description: 删除当前用户的指定收货地址 + * tags: + * - 地址管理 + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: 地址ID + * responses: + * 200: + * description: 成功删除收货地址 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 200 + * message: + * type: string + * example: 删除成功 + * 401: + * description: 未授权访问 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 401 + * message: + * type: string + * example: 未授权访问 + * 404: + * description: 地址不存在 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 404 + * message: + * type: string + * example: 地址不存在 + * 500: + * description: 服务器内部错误 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 500 + * message: + * type: string + * example: 服务器内部错误 + */ // 删除收货地址 router.delete('/:id', async (req, res) => { try { const { id } = req.params; const userId = req.user.id; - await dbConnector.query( - 'DELETE FROM addresses WHERE id = ? AND user_id = ?', + // 检查地址是否存在且属于当前用户 + const existingAddress = await dbConnector.query( + 'SELECT * FROM addresses WHERE id = ? AND user_id = ?', [id, userId] ); + if (existingAddress.length === 0) { + return res.status(404).json({ + code: 404, + message: '地址不存在' + }); + } + + // 删除地址 + await dbConnector.query('DELETE FROM addresses WHERE id = ?', [id]); + res.json({ code: 200, message: '删除成功' @@ -141,36 +526,4 @@ router.delete('/:id', async (req, res) => { } }); -// 设置默认地址 -router.put('/:id/default', async (req, res) => { - try { - const { id } = req.params; - const userId = req.user.id; - - // 先取消所有默认地址 - await dbConnector.query( - 'UPDATE addresses SET is_default = 0 WHERE user_id = ?', - [userId] - ); - - // 设置当前地址为默认 - await dbConnector.query( - 'UPDATE addresses SET is_default = 1, updated_at = NOW() WHERE id = ? AND user_id = ?', - [id, userId] - ); - - res.json({ - code: 200, - message: '设置默认地址成功' - }); - } catch (error) { - console.error('设置默认地址失败:', error); - res.status(500).json({ - code: 500, - message: '服务器内部错误', - error: error.message - }); - } -}); - module.exports = router; \ No newline at end of file diff --git a/backend/routes/auth.js b/backend/routes/auth.js index 33555e8..2d29f54 100644 --- a/backend/routes/auth.js +++ b/backend/routes/auth.js @@ -8,6 +8,106 @@ const router = express.Router(); const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key'; /** + * @swagger + * /api/v1/auth/register: + * post: + * summary: 用户注册 + * description: 创建一个新的用户账户 + * tags: + * - 认证管理 + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - username + * - password + * - phone + * properties: + * username: + * type: string + * example: "user123" + * description: 用户名 + * password: + * type: string + * example: "password123" + * description: 密码(至少6位) + * phone: + * type: string + * example: "13800138000" + * description: 手机号 + * email: + * type: string + * example: "user@example.com" + * description: 邮箱地址 + * user_type: + * type: string + * example: "farmer" + * description: 用户类型 + * responses: + * 201: + * description: 注册成功 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 201 + * message: + * type: string + * example: 注册成功 + * data: + * type: object + * properties: + * user_id: + * type: integer + * example: 1 + * username: + * type: string + * example: "user123" + * phone: + * type: string + * example: "13800138000" + * email: + * type: string + * example: "user@example.com" + * user_type: + * type: string + * example: "farmer" + * token: + * type: string + * example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." + * 400: + * description: 请求参数错误 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 400 + * message: + * type: string + * example: 用户名、密码和手机号为必填项 + * 409: + * description: 用户已存在 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 409 + * message: + * type: string + * example: 用户名、手机号或邮箱已存在 + * * 用户注册 */ router.post('/register', async (req, res, next) => { @@ -96,6 +196,87 @@ router.post('/register', async (req, res, next) => { }); /** + * @swagger + * /api/v1/auth/login: + * post: + * summary: 用户登录 + * description: 使用用户名和密码进行身份验证并获取访问令牌 + * tags: + * - 认证管理 + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - username + * - password + * properties: + * username: + * type: string + * example: "user123" + * description: 用户名 + * password: + * type: string + * example: "password123" + * description: 密码 + * responses: + * 200: + * description: 登录成功 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 200 + * message: + * type: string + * example: 登录成功 + * data: + * type: object + * properties: + * user_id: + * type: integer + * example: 1 + * username: + * type: string + * example: "user123" + * user_type: + * type: string + * example: "farmer" + * token: + * type: string + * example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." + * 400: + * description: 请求参数错误 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 400 + * message: + * type: string + * example: 用户名和密码为必填项 + * 401: + * description: 用户名或密码错误 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 401 + * message: + * type: string + * example: 用户名或密码错误 + * * 用户登录 */ router.post('/login', async (req, res, next) => { diff --git a/backend/routes/cart.js b/backend/routes/cart.js index 36592ed..ef481b2 100644 --- a/backend/routes/cart.js +++ b/backend/routes/cart.js @@ -2,6 +2,70 @@ const express = require('express'); const router = express.Router(); const dbConnector = require('../utils/dbConnector'); +/** + * @swagger + * /api/v1/cart: + * get: + * summary: 获取用户购物车 + * description: 获取当前用户的购物车商品列表及总计信息 + * tags: + * - 购物车管理 + * security: + * - bearerAuth: [] + * responses: + * 200: + * description: 成功获取购物车信息 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 200 + * message: + * type: string + * example: 获取成功 + * data: + * type: object + * properties: + * items: + * type: array + * items: + * $ref: '#/components/schemas/CartItem' + * total_amount: + * type: number + * example: 99.9 + * total_quantity: + * type: integer + * example: 3 + * 401: + * description: 未授权访问 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 401 + * message: + * type: string + * example: 未授权访问 + * 500: + * description: 服务器内部错误 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 500 + * message: + * type: string + * example: 服务器内部错误 + */ // 获取用户购物车 router.get('/', async (req, res) => { try { @@ -36,6 +100,101 @@ router.get('/', async (req, res) => { } }); +/** + * @swagger + * /api/v1/cart/items: + * post: + * summary: 添加商品到购物车 + * description: 将指定商品添加到当前用户的购物车中 + * tags: + * - 购物车管理 + * security: + * - bearerAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - product_id + * - quantity + * properties: + * product_id: + * type: integer + * example: 1 + * description: 商品ID + * quantity: + * type: integer + * example: 2 + * description: 商品数量 + * responses: + * 200: + * description: 成功添加到购物车 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 200 + * message: + * type: string + * example: 添加成功 + * 400: + * description: 请求参数错误 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 400 + * message: + * type: string + * example: 商品数量必须大于0 + * 401: + * description: 未授权访问 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 401 + * message: + * type: string + * example: 未授权访问 + * 404: + * description: 商品不存在 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 404 + * message: + * type: string + * example: 商品不存在 + * 500: + * description: 服务器内部错误 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 500 + * message: + * type: string + * example: 服务器内部错误 + */ // 添加商品到购物车 router.post('/items', async (req, res) => { try { @@ -50,33 +209,38 @@ router.post('/items', async (req, res) => { message: '商品不存在' }); } - - // 检查购物车是否已有该商品 + + if (quantity <= 0) { + return res.status(400).json({ + code: 400, + message: '商品数量必须大于0' + }); + } + + // 检查购物车中是否已存在该商品 const existingItem = await dbConnector.query( 'SELECT * FROM cart_items WHERE user_id = ? AND product_id = ?', [userId, product_id] ); - + if (existingItem.length > 0) { // 更新数量 + const newQuantity = existingItem[0].quantity + quantity; await dbConnector.query( - 'UPDATE cart_items SET quantity = quantity + ?, updated_at = NOW() WHERE id = ?', - [quantity, existingItem[0].id] + 'UPDATE cart_items SET quantity = ? WHERE id = ?', + [newQuantity, existingItem[0].id] ); } else { - // 新增商品 + // 添加新商品到购物车 await dbConnector.query( - 'INSERT INTO cart_items (user_id, product_id, quantity, created_at, updated_at) VALUES (?, ?, ?, NOW(), NOW())', + 'INSERT INTO cart_items (user_id, product_id, quantity) VALUES (?, ?, ?)', [userId, product_id, quantity] ); } - + res.json({ code: 200, - message: '添加成功', - data: { - cart_item_id: existingItem.length > 0 ? existingItem[0].id : null - } + message: '添加成功' }); } catch (error) { console.error('添加购物车失败:', error); @@ -88,31 +252,136 @@ router.post('/items', async (req, res) => { } }); +/** + * @swagger + * /api/v1/cart/items/{id}: + * put: + * summary: 更新购物车商品数量 + * description: 更新购物车中指定商品的数量 + * tags: + * - 购物车管理 + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: 购物车商品项ID + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - quantity + * properties: + * quantity: + * type: integer + * example: 3 + * description: 新的商品数量 + * responses: + * 200: + * description: 成功更新购物车商品数量 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 200 + * message: + * type: string + * example: 更新成功 + * 400: + * description: 请求参数错误 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 400 + * message: + * type: string + * example: 商品数量必须大于0 + * 401: + * description: 未授权访问 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 401 + * message: + * type: string + * example: 未授权访问 + * 404: + * description: 购物车商品项不存在 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 404 + * message: + * type: string + * example: 购物车商品项不存在 + * 500: + * description: 服务器内部错误 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 500 + * message: + * type: string + * example: 服务器内部错误 + */ // 更新购物车商品数量 router.put('/items/:id', async (req, res) => { try { const { id } = req.params; const { quantity } = req.body; const userId = req.user.id; - - // 检查购物车项是否存在 + + if (quantity <= 0) { + return res.status(400).json({ + code: 400, + message: '商品数量必须大于0' + }); + } + + // 检查购物车商品项是否存在且属于当前用户 const cartItem = await dbConnector.query( 'SELECT * FROM cart_items WHERE id = ? AND user_id = ?', [id, userId] ); - + if (cartItem.length === 0) { return res.status(404).json({ code: 404, - message: '购物车项不存在' + message: '购物车商品项不存在' }); } - + + // 更新数量 await dbConnector.query( - 'UPDATE cart_items SET quantity = ?, updated_at = NOW() WHERE id = ?', + 'UPDATE cart_items SET quantity = ? WHERE id = ?', [quantity, id] ); - + res.json({ code: 200, message: '更新成功' @@ -127,23 +396,105 @@ router.put('/items/:id', async (req, res) => { } }); +/** + * @swagger + * /api/v1/cart/items/{id}: + * delete: + * summary: 删除购物车商品 + * description: 从购物车中删除指定商品 + * tags: + * - 购物车管理 + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: 购物车商品项ID + * responses: + * 200: + * description: 成功删除购物车商品 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 200 + * message: + * type: string + * example: 删除成功 + * 401: + * description: 未授权访问 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 401 + * message: + * type: string + * example: 未授权访问 + * 404: + * description: 购物车商品项不存在 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 404 + * message: + * type: string + * example: 购物车商品项不存在 + * 500: + * description: 服务器内部错误 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 500 + * message: + * type: string + * example: 服务器内部错误 + */ // 删除购物车商品 router.delete('/items/:id', async (req, res) => { try { const { id } = req.params; const userId = req.user.id; - - await dbConnector.query( - 'DELETE FROM cart_items WHERE id = ? AND user_id = ?', + + // 检查购物车商品项是否存在且属于当前用户 + const cartItem = await dbConnector.query( + 'SELECT * FROM cart_items WHERE id = ? AND user_id = ?', [id, userId] ); - + + if (cartItem.length === 0) { + return res.status(404).json({ + code: 404, + message: '购物车商品项不存在' + }); + } + + // 删除购物车商品项 + await dbConnector.query('DELETE FROM cart_items WHERE id = ?', [id]); + res.json({ code: 200, message: '删除成功' }); } catch (error) { - console.error('删除购物车失败:', error); + console.error('删除购物车商品失败:', error); res.status(500).json({ code: 500, message: '服务器内部错误', diff --git a/backend/routes/orders.js b/backend/routes/orders.js index 5365a07..2624bc3 100644 --- a/backend/routes/orders.js +++ b/backend/routes/orders.js @@ -5,7 +5,81 @@ const { asyncHandler } = require('../middlewares/errorHandler'); const router = express.Router(); /** - * 获取订单列表 + * @swagger + * /api/v1/orders: + * get: + * summary: 获取订单列表 + * description: 分页获取当前用户的订单列表,支持按状态和日期筛选 + * tags: + * - 订单管理 + * security: + * - bearerAuth: [] + * parameters: + * - in: query + * name: page + * schema: + * type: integer + * default: 1 + * description: 页码 + * - in: query + * name: limit + * schema: + * type: integer + * default: 10 + * description: 每页数量 + * - in: query + * name: status + * schema: + * type: integer + * description: 订单状态 (0-待支付, 1-已支付, 2-已取消) + * - in: query + * name: start_date + * schema: + * type: string + * format: date + * description: 开始日期 (YYYY-MM-DD) + * - in: query + * name: end_date + * schema: + * type: string + * format: date + * description: 结束日期 (YYYY-MM-DD) + * responses: + * 200: + * description: 成功获取订单列表 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 200 + * message: + * type: string + * example: 获取成功 + * data: + * type: object + * properties: + * orders: + * type: array + * items: + * $ref: '#/components/schemas/Order' + * pagination: + * $ref: '#/components/schemas/Pagination' + * 401: + * description: 未授权访问 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 401 + * message: + * type: string + * example: 未授权访问 */ router.get('/', asyncHandler(async (req, res) => { const { page = 1, limit = 10, status, start_date, end_date } = req.query; @@ -68,7 +142,71 @@ router.get('/', asyncHandler(async (req, res) => { })); /** - * 获取订单详情 + * @swagger + * /api/v1/orders/{id}: + * get: + * summary: 获取订单详情 + * description: 根据订单ID获取当前用户的订单详情 + * tags: + * - 订单管理 + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: 订单ID + * responses: + * 200: + * description: 成功获取订单详情 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 200 + * message: + * type: string + * example: 获取成功 + * data: + * type: object + * properties: + * order: + * $ref: '#/components/schemas/Order' + * orderItems: + * type: array + * items: + * $ref: '#/components/schemas/OrderItem' + * 401: + * description: 未授权访问 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 401 + * message: + * type: string + * example: 未授权访问 + * 404: + * description: 订单不存在 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 404 + * message: + * type: string + * example: 订单不存在 */ router.get('/:id', asyncHandler(async (req, res) => { const { id } = req.params; diff --git a/backend/routes/products.js b/backend/routes/products.js index b089a6b..b0aa1de 100644 --- a/backend/routes/products.js +++ b/backend/routes/products.js @@ -6,7 +6,83 @@ const { asyncHandler } = require('../middlewares/errorHandler'); const router = express.Router(); /** - * 获取商品列表 + * @swagger + * /api/v1/products: + * get: + * summary: 获取商品列表 + * description: 分页获取商品列表,支持按分类、价格区间、关键词筛选 + * tags: + * - 商品管理 + * parameters: + * - in: query + * name: page + * schema: + * type: integer + * default: 1 + * description: 页码 + * - in: query + * name: limit + * schema: + * type: integer + * default: 12 + * description: 每页数量 + * - in: query + * name: category_id + * schema: + * type: integer + * description: 分类ID + * - in: query + * name: keyword + * schema: + * type: string + * description: 搜索关键词 + * - in: query + * name: min_price + * schema: + * type: number + * description: 最低价格 + * - in: query + * name: max_price + * schema: + * type: number + * description: 最高价格 + * - in: query + * name: sort_by + * schema: + * type: string + * enum: [name, price, created_at, stock] + * default: created_at + * description: 排序字段 + * - in: query + * name: sort_order + * schema: + * type: string + * enum: [asc, desc] + * default: desc + * description: 排序顺序 + * responses: + * 200: + * description: 成功获取商品列表 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 200 + * message: + * type: string + * example: 获取成功 + * data: + * type: object + * properties: + * products: + * type: array + * items: + * $ref: '#/components/schemas/Product' + * pagination: + * $ref: '#/components/schemas/Pagination' */ router.get('/', optionalAuth, asyncHandler(async (req, res) => { const { @@ -91,7 +167,51 @@ router.get('/', optionalAuth, asyncHandler(async (req, res) => { })); /** - * 获取商品详情 + * @swagger + * /api/v1/products/{id}: + * get: + * summary: 获取商品详情 + * description: 根据商品ID获取商品详情 + * tags: + * - 商品管理 + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: 商品ID + * responses: + * 200: + * description: 成功获取商品详情 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 200 + * message: + * type: string + * example: 获取成功 + * data: + * $ref: '#/components/schemas/Product' + * 404: + * description: 商品不存在 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 404 + * message: + * type: string + * example: 商品不存在 + * data: + * type: null */ router.get('/:id', optionalAuth, asyncHandler(async (req, res) => { const { id } = req.params; diff --git a/backend/routes/users.js b/backend/routes/users.js index d78e2aa..1a12575 100644 --- a/backend/routes/users.js +++ b/backend/routes/users.js @@ -8,7 +8,74 @@ const { asyncHandler } = require('../middlewares/errorHandler'); const router = express.Router(); /** - * 获取用户列表(管理员权限) + * @swagger + * /api/v1/users: + * get: + * summary: 获取用户列表(管理员权限) + * description: 分页获取用户列表,支持按关键词和用户类型筛选 + * tags: + * - 用户管理 + * security: + * - bearerAuth: [] + * parameters: + * - in: query + * name: page + * schema: + * type: integer + * default: 1 + * description: 页码 + * - in: query + * name: limit + * schema: + * type: integer + * default: 10 + * description: 每页数量 + * - in: query + * name: keyword + * schema: + * type: string + * description: 搜索关键词(用户名、手机号或邮箱) + * - in: query + * name: user_type + * schema: + * type: integer + * description: 用户类型 + * responses: + * 200: + * description: 成功获取用户列表 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 200 + * message: + * type: string + * example: 获取成功 + * data: + * type: object + * properties: + * users: + * type: array + * items: + * $ref: '#/components/schemas/User' + * pagination: + * $ref: '#/components/schemas/Pagination' + * 401: + * description: 未授权访问 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 401 + * message: + * type: string + * example: 未授权访问 */ router.get('/', adminRequired, asyncHandler(async (req, res) => { const { page = 1, limit = 10, keyword, user_type } = req.query; @@ -57,7 +124,91 @@ router.get('/', adminRequired, asyncHandler(async (req, res) => { })); /** - * 创建用户(管理员权限) + * @swagger + * /api/v1/users: + * post: + * summary: 创建用户(管理员权限) + * description: 管理员创建新用户 + * tags: + * - 用户管理 + * security: + * - bearerAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - username + * - phone + * - email + * - user_type + * - password + * properties: + * username: + * type: string + * example: "user123" + * phone: + * type: string + * example: "13800138000" + * email: + * type: string + * example: "user@example.com" + * user_type: + * type: integer + * example: 1 + * password: + * type: string + * example: "password123" + * real_name: + * type: string + * example: "张三" + * avatar_url: + * type: string + * example: "https://example.com/avatar.jpg" + * responses: + * 201: + * description: 用户创建成功 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 201 + * message: + * type: string + * example: 用户创建成功 + * data: + * $ref: '#/components/schemas/User' + * 400: + * description: 参数错误 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 400 + * message: + * type: string + * example: 参数错误 + * 409: + * description: 用户名、邮箱或手机号已存在 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 409 + * message: + * type: string + * example: 用户名已存在 */ router.post('/', adminRequired, asyncHandler(async (req, res) => { const { username, phone, email, user_type, password, real_name, avatar_url } = req.body;