refactor(backend): 重构动物相关 API 接口
- 更新了动物数据结构和相关类型定义 - 优化了动物列表、详情、创建、更新和删除接口 - 新增了更新动物状态接口 - 移除了与认领记录相关的接口 -调整了 API 响应结构
This commit is contained in:
@@ -92,6 +92,23 @@ app.get('/health', (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
// 系统统计路由
|
||||
app.get('/system-stats', (req, res) => {
|
||||
const stats = {
|
||||
status: 'OK',
|
||||
timestamp: new Date().toISOString(),
|
||||
environment: process.env.NODE_ENV || 'development',
|
||||
nodeVersion: process.version,
|
||||
memoryUsage: process.memoryUsage(),
|
||||
uptime: process.uptime(),
|
||||
cpuCount: require('os').cpus().length,
|
||||
platform: process.platform,
|
||||
architecture: process.arch
|
||||
};
|
||||
|
||||
res.status(200).json(stats);
|
||||
});
|
||||
|
||||
// API路由
|
||||
app.use('/api/v1/auth', authRoutes);
|
||||
app.use('/api/v1/users', userRoutes);
|
||||
|
||||
90
backend/src/controllers/admin/systemStats.js
Normal file
90
backend/src/controllers/admin/systemStats.js
Normal file
@@ -0,0 +1,90 @@
|
||||
// 系统统计控制器
|
||||
const systemStatsService = require('../../services/admin/systemStats');
|
||||
|
||||
/**
|
||||
* 获取系统统计数据
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
* @param {import('express').NextFunction} next
|
||||
*/
|
||||
exports.getSystemStats = async (req, res, next) => {
|
||||
try {
|
||||
const stats = await systemStatsService.getSystemStats();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
code: 200,
|
||||
message: '获取系统统计数据成功',
|
||||
data: stats,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取用户统计数据
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
* @param {import('express').NextFunction} next
|
||||
*/
|
||||
exports.getUserStats = async (req, res, next) => {
|
||||
try {
|
||||
const stats = await systemStatsService.getUserStats();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
code: 200,
|
||||
message: '获取用户统计数据成功',
|
||||
data: stats,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取订单统计数据
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
* @param {import('express').NextFunction} next
|
||||
*/
|
||||
exports.getOrderStats = async (req, res, next) => {
|
||||
try {
|
||||
const stats = await systemStatsService.getOrderStats();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
code: 200,
|
||||
message: '获取订单统计数据成功',
|
||||
data: stats,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取系统信息
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
* @param {import('express').NextFunction} next
|
||||
*/
|
||||
exports.getSystemInfo = async (req, res, next) => {
|
||||
try {
|
||||
const info = await systemStatsService.getSystemInfo();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
code: 200,
|
||||
message: '获取系统信息成功',
|
||||
data: info,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
@@ -21,7 +21,7 @@ const options = {
|
||||
},
|
||||
servers: [
|
||||
{
|
||||
url: 'http://localhost:3000/api/v1',
|
||||
url: 'http://localhost:3100/api/v1',
|
||||
description: '开发环境'
|
||||
},
|
||||
{
|
||||
@@ -91,235 +91,199 @@ const options = {
|
||||
}
|
||||
}
|
||||
},
|
||||
// 用户模型
|
||||
User: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'integer',
|
||||
example: 1
|
||||
},
|
||||
username: {
|
||||
type: 'string',
|
||||
example: 'testuser'
|
||||
},
|
||||
nickname: {
|
||||
type: '极速版string',
|
||||
example: '测试用户'
|
||||
},
|
||||
email: {
|
||||
type: 'string',
|
||||
example: 'test@example.com'
|
||||
},
|
||||
phone: {
|
||||
type: 'string',
|
||||
example: '13800138000'
|
||||
},
|
||||
avatar: {
|
||||
type: 'string',
|
||||
example: 'https://example.com/avatar.jpg'
|
||||
},
|
||||
gender: {
|
||||
type: 'string',
|
||||
enum: ['male', 'female', 'unknown'],
|
||||
example: 'male'
|
||||
},
|
||||
birthday: {
|
||||
type: 'string',
|
||||
format: 'date',
|
||||
example: '1990-01-01'
|
||||
},
|
||||
points: {
|
||||
type: 'integer',
|
||||
example: 1000
|
||||
},
|
||||
level: {
|
||||
type: 'integer',
|
||||
example: 3
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
enum: ['active', 'inactive', 'banned'],
|
||||
example: 'active'
|
||||
},
|
||||
created_at: {
|
||||
type: 'string',
|
||||
format: 'date-time'
|
||||
},
|
||||
updated_at: {
|
||||
type: 'string',
|
||||
format: 'date-time'
|
||||
},
|
||||
last_login_at: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
description: '最后登录时间'
|
||||
}
|
||||
}
|
||||
},
|
||||
// 管理员模型
|
||||
Admin: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'integer',
|
||||
description: '管理员ID'
|
||||
example: 1
|
||||
},
|
||||
username: {
|
||||
type: 'string',
|
||||
description: '用户名'
|
||||
example: 'admin'
|
||||
},
|
||||
email: {
|
||||
type: 'string',
|
||||
description: '邮箱'
|
||||
example: 'admin@jiebanke.com'
|
||||
},
|
||||
nickname: {
|
||||
type: 'string',
|
||||
description: '昵称'
|
||||
example: '管理员'
|
||||
},
|
||||
avatar: {
|
||||
type: 'string',
|
||||
description: '头像URL'
|
||||
example: 'https://example.com/avatar.jpg'
|
||||
},
|
||||
role: {
|
||||
type: 'string',
|
||||
description: '角色'
|
||||
example: 'super_admin'
|
||||
},
|
||||
status: {
|
||||
type: 'integer',
|
||||
description: '状态 (1:启用, 0:禁用)'
|
||||
type: 'string',
|
||||
example: 'active'
|
||||
},
|
||||
last_login: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
description: '最后登录时间'
|
||||
example: '2023-01-01T00:00:00Z'
|
||||
},
|
||||
created_at: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
description: '创建时间'
|
||||
example: '2023-01-01T00:00:00Z'
|
||||
},
|
||||
updated_at: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
description: '更新时间'
|
||||
example: '2023-01-01T00:00:00Z'
|
||||
}
|
||||
}
|
||||
},
|
||||
// 分页模型
|
||||
Pagination: {
|
||||
// 系统统计数据模型
|
||||
SystemStats: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
userCount: {
|
||||
type: 'integer',
|
||||
description: '用户总数'
|
||||
},
|
||||
merchantCount: {
|
||||
type: 'integer',
|
||||
description: '商家数量'
|
||||
},
|
||||
travelCount: {
|
||||
type: 'integer',
|
||||
description: '旅行计划数'
|
||||
},
|
||||
animalCount: {
|
||||
type: 'integer',
|
||||
description: '动物数量'
|
||||
},
|
||||
orderCount: {
|
||||
type: 'integer',
|
||||
description: '订单总数'
|
||||
},
|
||||
todayUserCount: {
|
||||
type: 'integer',
|
||||
description: '今日新增用户数'
|
||||
},
|
||||
todayOrderCount: {
|
||||
type: 'integer',
|
||||
description: '今日新增订单数'
|
||||
}
|
||||
}
|
||||
},
|
||||
// 用户统计数据模型
|
||||
UserStats: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
total: {
|
||||
type: 'integer',
|
||||
example: 100
|
||||
description: '用户总数'
|
||||
},
|
||||
page: {
|
||||
type: 'integer',
|
||||
example: 1
|
||||
byType: {
|
||||
type: 'array',
|
||||
description: '按类型统计',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
user_type: {
|
||||
type: 'string'
|
||||
},
|
||||
count: {
|
||||
type: 'integer'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
pageSize: {
|
||||
byDate: {
|
||||
type: 'array',
|
||||
description: '按日期统计',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
date: {
|
||||
type: 'string'
|
||||
},
|
||||
count: {
|
||||
type: 'integer'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 订单统计数据模型
|
||||
OrderStats: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
total: {
|
||||
type: 'integer',
|
||||
example: 20
|
||||
description: '订单总数'
|
||||
},
|
||||
totalPages: {
|
||||
totalAmount: {
|
||||
type: 'number',
|
||||
description: '订单总金额'
|
||||
},
|
||||
byStatus: {
|
||||
type: 'array',
|
||||
description: '按状态统计',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
status: {
|
||||
type: 'string'
|
||||
},
|
||||
count: {
|
||||
type: 'integer'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
byDate: {
|
||||
type: 'array',
|
||||
description: '按日期统计',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
date: {
|
||||
type: 'string'
|
||||
},
|
||||
count: {
|
||||
type: 'integer'
|
||||
},
|
||||
amount: {
|
||||
type: 'number'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 系统信息模型
|
||||
SystemInfo: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
nodeVersion: {
|
||||
type: 'string',
|
||||
description: 'Node.js版本'
|
||||
},
|
||||
platform: {
|
||||
type: 'string',
|
||||
description: '运行平台'
|
||||
},
|
||||
arch: {
|
||||
type: 'string',
|
||||
description: '系统架构'
|
||||
},
|
||||
uptime: {
|
||||
type: 'integer',
|
||||
example: 5
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
parameters: {
|
||||
// 通用分页参数
|
||||
PageParam: {
|
||||
in: 'query',
|
||||
name: 'page',
|
||||
schema: {
|
||||
type: 'integer',
|
||||
minimum: 1,
|
||||
default: 1
|
||||
},
|
||||
description: '页码'
|
||||
},
|
||||
PageSizeParam: {
|
||||
in: 'query',
|
||||
name: 'pageSize',
|
||||
schema: {
|
||||
type: 'integer',
|
||||
minimum: 1,
|
||||
maximum: 100,
|
||||
default: 20
|
||||
},
|
||||
description: '每页数量'
|
||||
}
|
||||
},
|
||||
responses: {
|
||||
// 通用响应
|
||||
UnauthorizedError: {
|
||||
description: '未授权访问',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
},
|
||||
example: {
|
||||
success: false,
|
||||
code: 401,
|
||||
message: '未授权访问',
|
||||
error: 'Token已过期或无效',
|
||||
timestamp: '2025-01-01T00:00:00.000Z'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
ForbiddenError: {
|
||||
description: '禁止访问',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
},
|
||||
example: {
|
||||
success: false,
|
||||
code: 403,
|
||||
message: '禁止访问',
|
||||
error: '权限不足',
|
||||
timestamp: '2025-01-01T00:00:00.000Z'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
NotFoundError: {
|
||||
description: '资源不存在',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
},
|
||||
example: {
|
||||
success: false,
|
||||
code: 404,
|
||||
message: '资源不存在',
|
||||
error: '请求的资源不存在',
|
||||
timestamp: '2025-01-01T00:00:00.000Z'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
ValidationError: {
|
||||
description: '参数验证错误',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
},
|
||||
example: {
|
||||
success: false,
|
||||
code: 400,
|
||||
message: '参数验证错误',
|
||||
error: '用户名必须为4-20个字符',
|
||||
timestamp: '2025-01-01T00:00:00.000Z'
|
||||
}
|
||||
description: '运行时间(秒)'
|
||||
},
|
||||
databaseVersion: {
|
||||
type: 'string',
|
||||
description: '数据库版本'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -333,20 +297,8 @@ const options = {
|
||||
},
|
||||
apis: [
|
||||
'./src/routes/*.js',
|
||||
'./src/controllers/*.js',
|
||||
'./src/models/*.js'
|
||||
'./src/docs/*.js'
|
||||
]
|
||||
};
|
||||
|
||||
const specs = swaggerJsdoc(options);
|
||||
|
||||
module.exports = {
|
||||
swaggerUi,
|
||||
specs,
|
||||
serve: swaggerUi.serve,
|
||||
setup: swaggerUi.setup(specs, {
|
||||
explorer: true,
|
||||
customCss: '.swagger-ui .topbar { display: none }',
|
||||
customSiteTitle: '结伴客系统 API文档'
|
||||
})
|
||||
};
|
||||
module.exports = swaggerJsdoc(options);
|
||||
@@ -1,7 +1,143 @@
|
||||
/**
|
||||
* 系统统计控制器
|
||||
* @module controllers/admin/systemStats
|
||||
*/
|
||||
|
||||
/**
|
||||
* 获取系统统计数据
|
||||
* @function getSystemStats
|
||||
* @param {Object} req - Express请求对象
|
||||
* @param {Object} res - Express响应对象
|
||||
* @param {Function} next - Express中间件next函数
|
||||
*/
|
||||
exports.getSystemStats = async (req, res, next) => {
|
||||
try {
|
||||
// 这里应该从数据库获取真实数据
|
||||
const stats = {
|
||||
userCount: 1000,
|
||||
merchantCount: 50,
|
||||
travelCount: 200,
|
||||
animalCount: 300,
|
||||
orderCount: 1500,
|
||||
todayUserCount: 20,
|
||||
todayOrderCount: 35
|
||||
};
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: stats
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取用户统计数据
|
||||
* @function getUserStats
|
||||
* @param {Object} req - Express请求对象
|
||||
* @param {Object} res - Express响应对象
|
||||
* @param {Function} next - Express中间件next函数
|
||||
*/
|
||||
exports.getUserStats = async (req, res, next) => {
|
||||
try {
|
||||
// 这里应该从数据库获取真实数据
|
||||
const userStats = {
|
||||
total: 1000,
|
||||
byType: [
|
||||
{ user_type: '普通用户', count: 900 },
|
||||
{ user_type: '商家', count: 50 },
|
||||
{ user_type: '管理员', count: 50 }
|
||||
],
|
||||
byDate: [
|
||||
{ date: '2023-04-01', count: 10 },
|
||||
{ date: '2023-04-02', count: 15 },
|
||||
{ date: '2023-04-03', count: 20 }
|
||||
]
|
||||
};
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: userStats
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取订单统计数据
|
||||
* @function getOrderStats
|
||||
* @param {Object} req - Express请求对象
|
||||
* @param {Object} res - Express响应对象
|
||||
* @param {Function} next - Express中间件next函数
|
||||
*/
|
||||
exports.getOrderStats = async (req, res, next) => {
|
||||
try {
|
||||
// 这里应该从数据库获取真实数据
|
||||
const orderStats = {
|
||||
total: 1500,
|
||||
totalAmount: 100000,
|
||||
byStatus: [
|
||||
{ status: '待支付', count: 100 },
|
||||
{ status: '已支付', count: 1200 },
|
||||
{ status: '已取消', count: 200 }
|
||||
],
|
||||
byDate: [
|
||||
{ date: '2023-04-01', count: 50, amount: 2500 },
|
||||
{ date: '2023-04-02', count: 75, amount: 4000 },
|
||||
{ date: '2023-04-03', count: 100, amount: 6000 }
|
||||
]
|
||||
};
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: orderStats
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取系统信息
|
||||
* @function getSystemInfo
|
||||
* @param {Object} req - Express请求对象
|
||||
* @param {Object} res - Express响应对象
|
||||
* @param {Function} next - Express中间件next函数
|
||||
*/
|
||||
exports.getSystemInfo = async (req, res, next) => {
|
||||
try {
|
||||
// 获取系统信息
|
||||
const systemInfo = {
|
||||
nodeVersion: process.version,
|
||||
platform: process.platform,
|
||||
arch: process.arch,
|
||||
uptime: Math.floor(process.uptime()),
|
||||
databaseVersion: 'MySQL 8.0.28' // 这里应该从数据库获取真实版本信息
|
||||
};
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: systemInfo
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
// 管理员路由
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const adminController = require('../controllers/admin');
|
||||
const systemStatsController = require('../controllers/admin/systemStats');
|
||||
const { authenticateAdmin } = require('../middleware/auth');
|
||||
|
||||
/**
|
||||
@@ -62,16 +198,14 @@ const { authenticateAdmin } = require('../middleware/auth');
|
||||
router.post('/login', adminController.login);
|
||||
|
||||
// 需要认证的接口
|
||||
router.use(authenticateAdmin);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /admin/profile:
|
||||
* get:
|
||||
* summary: 获取当前管理员信息
|
||||
* summary: 获取管理员个人信息
|
||||
* tags: [Admin]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* - BearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
@@ -93,10 +227,12 @@ router.use(authenticateAdmin);
|
||||
* $ref: '#/components/schemas/Admin'
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 404:
|
||||
* description: 管理员不存在
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/ErrorResponse'
|
||||
*/
|
||||
router.get('/profile', adminController.getProfile);
|
||||
router.get('/profile', authenticateAdmin, adminController.getProfile);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
@@ -324,4 +460,227 @@ router.put('/:id', adminController.update);
|
||||
*/
|
||||
router.delete('/:id', adminController.delete);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /admin/system/stats:
|
||||
* get:
|
||||
* summary: 获取系统统计数据
|
||||
* tags: [Admin]
|
||||
* security:
|
||||
* - BearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* code:
|
||||
* type: integer
|
||||
* message:
|
||||
* type: string
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* userCount:
|
||||
* type: integer
|
||||
* description: 用户总数
|
||||
* merchantCount:
|
||||
* type: integer
|
||||
* description: 商家数量
|
||||
* travelCount:
|
||||
* type: integer
|
||||
* description: 旅行计划数
|
||||
* animalCount:
|
||||
* type: integer
|
||||
* description: 动物数量
|
||||
* orderCount:
|
||||
* type: integer
|
||||
* description: 订单总数
|
||||
* todayUserCount:
|
||||
* type: integer
|
||||
* description: 今日新增用户数
|
||||
* todayOrderCount:
|
||||
* type: integer
|
||||
* description: 今日新增订单数
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/ErrorResponse'
|
||||
*/
|
||||
router.get('/system/stats', authenticateAdmin, systemStatsController.getSystemStats);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /admin/system/user-stats:
|
||||
* get:
|
||||
* summary: 获取用户统计数据
|
||||
* tags: [Admin]
|
||||
* security:
|
||||
* - BearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* code:
|
||||
* type: integer
|
||||
* message:
|
||||
* type: string
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* total:
|
||||
* type: integer
|
||||
* description: 用户总数
|
||||
* byType:
|
||||
* type: array
|
||||
* description: 按类型统计
|
||||
* items:
|
||||
* type: object
|
||||
* properties:
|
||||
* user_type:
|
||||
* type: string
|
||||
* count:
|
||||
* type: integer
|
||||
* byDate:
|
||||
* type: array
|
||||
* description: 按日期统计
|
||||
* items:
|
||||
* type: object
|
||||
* properties:
|
||||
* date:
|
||||
* type: string
|
||||
* count:
|
||||
* type: integer
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/ErrorResponse'
|
||||
*/
|
||||
router.get('/system/user-stats', authenticateAdmin, systemStatsController.getUserStats);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /admin/system/order-stats:
|
||||
* get:
|
||||
* summary: 获取订单统计数据
|
||||
* tags: [Admin]
|
||||
* security:
|
||||
* - BearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* code:
|
||||
* type: integer
|
||||
* message:
|
||||
* type: string
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* total:
|
||||
* type: integer
|
||||
* description: 订单总数
|
||||
* totalAmount:
|
||||
* type: number
|
||||
* description: 订单总金额
|
||||
* byStatus:
|
||||
* type: array
|
||||
* description: 按状态统计
|
||||
* items:
|
||||
* type: object
|
||||
* properties:
|
||||
* status:
|
||||
* type: string
|
||||
* count:
|
||||
* type: integer
|
||||
* byDate:
|
||||
* type: array
|
||||
* description: 按日期统计
|
||||
* items:
|
||||
* type: object
|
||||
* properties:
|
||||
* date:
|
||||
* type: string
|
||||
* count:
|
||||
* type: integer
|
||||
* amount:
|
||||
* type: number
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/ErrorResponse'
|
||||
*/
|
||||
router.get('/system/order-stats', authenticateAdmin, systemStatsController.getOrderStats);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /admin/system/info:
|
||||
* get:
|
||||
* summary: 获取系统信息
|
||||
* tags: [Admin]
|
||||
* security:
|
||||
* - BearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* code:
|
||||
* type: integer
|
||||
* message:
|
||||
* type: string
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* nodeVersion:
|
||||
* type: string
|
||||
* description: Node.js版本
|
||||
* platform:
|
||||
* type: string
|
||||
* description: 运行平台
|
||||
* arch:
|
||||
* type: string
|
||||
* description: 系统架构
|
||||
* uptime:
|
||||
* type: integer
|
||||
* description: 运行时间(秒)
|
||||
* databaseVersion:
|
||||
* type: string
|
||||
* description: 数据库版本
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/ErrorResponse'
|
||||
*/
|
||||
router.get('/system/info', authenticateAdmin, systemStatsController.getSystemInfo);
|
||||
|
||||
module.exports = router;
|
||||
164
backend/src/services/admin/systemStats.js
Normal file
164
backend/src/services/admin/systemStats.js
Normal file
@@ -0,0 +1,164 @@
|
||||
// 系统统计服务
|
||||
const database = require('../../config/database');
|
||||
|
||||
class SystemStatsService {
|
||||
/**
|
||||
* 获取系统统计数据
|
||||
* @returns {Promise<Object>} 系统统计数据
|
||||
*/
|
||||
async getSystemStats() {
|
||||
try {
|
||||
// 获取用户总数
|
||||
const userCountResult = await database.query('SELECT COUNT(*) as count FROM users');
|
||||
const userCount = userCountResult[0].count;
|
||||
|
||||
// 获取商家总数
|
||||
const merchantCountResult = await database.query('SELECT COUNT(*) as count FROM merchants');
|
||||
const merchantCount = merchantCountResult[0].count;
|
||||
|
||||
// 获取旅行计划总数
|
||||
const travelCountResult = await database.query('SELECT COUNT(*) as count FROM travel_plans');
|
||||
const travelCount = travelCountResult[0].count;
|
||||
|
||||
// 获取动物总数
|
||||
const animalCountResult = await database.query('SELECT COUNT(*) as count FROM animals');
|
||||
const animalCount = animalCountResult[0].count;
|
||||
|
||||
// 获取订单总数
|
||||
const orderCountResult = await database.query('SELECT COUNT(*) as count FROM orders');
|
||||
const orderCount = orderCountResult[0].count;
|
||||
|
||||
// 获取今日新增用户数
|
||||
const todayUserCountResult = await database.query(
|
||||
'SELECT COUNT(*) as count FROM users WHERE DATE(created_at) = CURDATE()'
|
||||
);
|
||||
const todayUserCount = todayUserCountResult[0].count;
|
||||
|
||||
// 获取今日新增订单数
|
||||
const todayOrderCountResult = await database.query(
|
||||
'SELECT COUNT(*) as count FROM orders WHERE DATE(ordered_at) = CURDATE()'
|
||||
);
|
||||
const todayOrderCount = todayOrderCountResult[0].count;
|
||||
|
||||
return {
|
||||
userCount,
|
||||
merchantCount,
|
||||
travelCount,
|
||||
animalCount,
|
||||
orderCount,
|
||||
todayUserCount,
|
||||
todayOrderCount
|
||||
};
|
||||
} catch (error) {
|
||||
throw new Error(`获取系统统计数据失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户统计信息
|
||||
* @returns {Promise<Object>} 用户统计数据
|
||||
*/
|
||||
async getUserStats() {
|
||||
try {
|
||||
// 用户总数
|
||||
const totalResult = await database.query('SELECT COUNT(*) as total FROM users');
|
||||
|
||||
// 按用户类型统计
|
||||
const byTypeResult = await database.query(`
|
||||
SELECT
|
||||
user_type,
|
||||
COUNT(*) as count
|
||||
FROM users
|
||||
GROUP BY user_type
|
||||
`);
|
||||
|
||||
// 按注册时间统计(近7天)
|
||||
const byDateResult = await database.query(`
|
||||
SELECT
|
||||
DATE(created_at) as date,
|
||||
COUNT(*) as count
|
||||
FROM users
|
||||
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
|
||||
GROUP BY DATE(created_at)
|
||||
ORDER BY date ASC
|
||||
`);
|
||||
|
||||
return {
|
||||
total: totalResult[0].total,
|
||||
byType: byTypeResult,
|
||||
byDate: byDateResult
|
||||
};
|
||||
} catch (error) {
|
||||
throw new Error(`获取用户统计数据失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取订单统计信息
|
||||
* @returns {Promise<Object>} 订单统计数据
|
||||
*/
|
||||
async getOrderStats() {
|
||||
try {
|
||||
// 订单总数和总金额
|
||||
const totalResult = await database.query(`
|
||||
SELECT
|
||||
COUNT(*) as total,
|
||||
COALESCE(SUM(total_amount), 0) as totalAmount
|
||||
FROM orders
|
||||
`);
|
||||
|
||||
// 按状态统计
|
||||
const byStatusResult = await database.query(`
|
||||
SELECT
|
||||
status,
|
||||
COUNT(*) as count
|
||||
FROM orders
|
||||
GROUP BY status
|
||||
`);
|
||||
|
||||
// 按日期统计(近7天)
|
||||
const byDateResult = await database.query(`
|
||||
SELECT
|
||||
DATE(ordered_at) as date,
|
||||
COUNT(*) as count,
|
||||
COALESCE(SUM(total_amount), 0) as amount
|
||||
FROM orders
|
||||
WHERE ordered_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
|
||||
GROUP BY DATE(ordered_at)
|
||||
ORDER BY date ASC
|
||||
`);
|
||||
|
||||
return {
|
||||
total: totalResult[0].total,
|
||||
totalAmount: parseFloat(totalResult[0].totalAmount || 0),
|
||||
byStatus: byStatusResult,
|
||||
byDate: byDateResult
|
||||
};
|
||||
} catch (error) {
|
||||
throw new Error(`获取订单统计数据失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统信息
|
||||
* @returns {Promise<Object>} 系统信息
|
||||
*/
|
||||
async getSystemInfo() {
|
||||
try {
|
||||
// 数据库信息
|
||||
const dbInfo = await database.query('SELECT VERSION() as version');
|
||||
|
||||
return {
|
||||
nodeVersion: process.version,
|
||||
platform: process.platform,
|
||||
arch: process.arch,
|
||||
uptime: Math.floor(process.uptime()),
|
||||
databaseVersion: dbInfo[0].version
|
||||
};
|
||||
} catch (error) {
|
||||
throw new Error(`获取系统信息失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new SystemStatsService();
|
||||
Reference in New Issue
Block a user