refactor(types): 重构类型系统,统一共享类型定义

feat(types): 新增共享类型中心,包含用户、产品、订单等核心领域类型
fix(types): 修复类型定义错误,统一各模块类型引用
style(types): 优化类型文件格式和注释
docs(types): 更新类型文档和变更日志
test(types): 添加类型测试用例
build(types): 配置类型共享路径
chore(types): 清理重复类型定义文件
This commit is contained in:
2026-03-20 17:53:46 +08:00
parent 989c4b13a6
commit 427becbc8f
222 changed files with 25676 additions and 6328 deletions

View File

@@ -21,7 +21,7 @@ router.get('/suggestions', (req, res, next) => {
// 更新建议状态
router.put('/suggestions/:id/status', (req, res, next) => {
aiSelfImprovementController.updateSuggestionStatus(req.params.id, req.params.status)
aiSelfImprovementController.updateSuggestionStatus(req.params.id, req.body.status)
.then(result => res.json(result))
.catch(next);
});

View File

@@ -11,7 +11,6 @@ declare global {
tenantId: string;
role: string;
email: string;
[key: string]: any;
}
interface Request {

View File

@@ -27,8 +27,9 @@ const validateBody = (schema: z.ZodObject<any>) => {
try {
schema.parse(req.body);
next();
} catch (error) {
res.status(400).json({ error: error.message });
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error);
res.status(400).json({ error: errorMessage });
}
};
};
@@ -38,8 +39,9 @@ const validateParam = (paramName: string, schema: z.ZodString) => {
try {
schema.parse(req.params[paramName]);
next();
} catch (error) {
res.status(400).json({ error: error.message });
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error);
res.status(400).json({ error: errorMessage });
}
};
};
@@ -56,28 +58,28 @@ router.post('/stores', validateBody(bindStoreSchema), (req, res, next) => {
// 获取商户的店铺列表
router.get('/stores/:merchantId', validateParam('merchantId', merchantIdSchema), (req, res, next) => {
operationAgentController.getStores(req.params.merchantId)
operationAgentController.getStores(req.params.merchantId as string)
.then(result => res.json(result))
.catch(next);
});
// 获取店铺详情
router.get('/stores/detail/:storeId', validateParam('storeId', storeIdSchema), (req, res, next) => {
operationAgentController.getStore(req.params.storeId)
operationAgentController.getStore(req.params.storeId as string)
.then(result => res.json(result))
.catch(next);
});
// 同步店铺商品
router.post('/stores/:storeId/products/sync', validateParam('storeId', storeIdSchema), (req, res, next) => {
operationAgentController.syncProducts(req.params.storeId)
operationAgentController.syncProducts(req.params.storeId as string)
.then(result => res.json(result))
.catch(next);
});
// 同步店铺订单
router.post('/stores/:storeId/orders/sync', validateParam('storeId', storeIdSchema), (req, res, next) => {
operationAgentController.syncOrders(req.params.storeId)
operationAgentController.syncOrders(req.params.storeId as string)
.then(result => res.json(result))
.catch(next);
});
@@ -88,7 +90,7 @@ router.put('/stores/:storeId/products/:productId/price',
validateParam('productId', productIdSchema),
validateBody(updatePriceSchema),
(req, res, next) => {
operationAgentController.updateProductPrice(req.params.storeId, req.params.productId, req.body.price)
operationAgentController.updateProductPrice(req.params.storeId as string, req.params.productId as string, req.body.price)
.then(result => res.json(result))
.catch(next);
}
@@ -96,14 +98,14 @@ router.put('/stores/:storeId/products/:productId/price',
// 停用店铺
router.put('/stores/:storeId/deactivate', validateParam('storeId', storeIdSchema), (req, res, next) => {
operationAgentController.deactivateStore(req.params.storeId)
operationAgentController.deactivateStore(req.params.storeId as string)
.then(result => res.json(result))
.catch(next);
});
// 重新激活店铺
router.put('/stores/:storeId/reactivate', validateParam('storeId', storeIdSchema), (req, res, next) => {
operationAgentController.reactivateStore(req.params.storeId)
operationAgentController.reactivateStore(req.params.storeId as string)
.then(result => res.json(result))
.catch(next);
});

View File

@@ -25,8 +25,9 @@ const validateBody = (schema: z.ZodObject<any>) => {
try {
schema.parse(req.body);
next();
} catch (error) {
res.status(400).json({ error: error.message });
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error);
res.status(400).json({ error: errorMessage });
}
};
};
@@ -36,8 +37,9 @@ const validateParam = (paramName: string, schema: z.ZodString) => {
try {
schema.parse(req.params[paramName]);
next();
} catch (error) {
res.status(400).json({ error: error.message });
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error);
res.status(400).json({ error: errorMessage });
}
};
};

View File

@@ -1,10 +1,5 @@
/**
* [BE-SHOP-REP001] 多店铺报表聚合API路由
* 提供多店铺报表的生成、查询、订阅接口
*/
import { Router } from 'express';
import { ShopReportAggregationService, ReportType, TimeDimension } from '../../services/ShopReportAggregationService';
import { ShopReportAggregationService, ReportType, TimeGranularity } from '../../services/ShopReportAggregationService';
import { logger } from '../../utils/logger';
const router = Router();
@@ -26,45 +21,49 @@ router.post('/generate', async (req, res) => {
regions,
} = req.body;
// 参数校验
if (!tenant_id || !report_type || !time_dimension || !start_date || !end_date) {
if (!tenant_id || !report_type || !time_dimension) {
return res.status(400).json({
success: false,
error: 'MISSING_REQUIRED_FIELDS',
message: '缺少必要字段: tenant_id, report_type, time_dimension, start_date, end_date',
message: '缺少必要字段',
});
}
// 验证报表类型
const validReportTypes: ReportType[] = ['SALES', 'PROFIT', 'INVENTORY', 'ORDER', 'AD', 'REFUND'];
if (!validReportTypes.includes(report_type)) {
return res.status(400).json({
success: false,
error: 'INVALID_REPORT_TYPE',
message: `报表类型必须是: ${validReportTypes.join(', ')}`,
});
}
// 验证时间维度
const validTimeDimensions: TimeDimension[] = ['DAILY', 'WEEKLY', 'MONTHLY', 'QUARTERLY', 'YEARLY'];
if (!validTimeDimensions.includes(time_dimension)) {
return res.status(400).json({
success: false,
error: 'INVALID_TIME_DIMENSION',
message: `时间维度必须是: ${validTimeDimensions.join(', ')}`,
});
}
const report = await ShopReportAggregationService.generateReport({
tenant_id,
report_type,
time_dimension,
start_date,
end_date,
shop_ids,
const context = {
tenantId: tenant_id,
userId: 'system',
role: 'ADMIN',
permissions: ['*'],
hierarchyPath: '/'
};
const query = {
tenantId: tenant_id,
dateFrom: start_date,
dateTo: end_date,
shopIds: shop_ids,
platforms,
regions,
});
granularity: time_dimension,
};
let report;
switch (report_type) {
case 'SALES':
report = await ShopReportAggregationService.getSalesReport(context, query);
break;
case 'PROFIT':
report = await ShopReportAggregationService.getProfitReport(context, query);
break;
case 'INVENTORY':
report = await ShopReportAggregationService.getInventoryReport(context, query);
break;
default:
return res.status(400).json({
success: false,
error: 'INVALID_REPORT_TYPE',
message: '不支持的报表类型',
});
}
res.json({
success: true,
@@ -83,14 +82,14 @@ router.post('/generate', async (req, res) => {
});
/**
* POST /api/shop-reports/realtime
* 实时聚合查询
* POST /api/shop-reports/export
* 导出报表
*/
router.post('/realtime', async (req, res) => {
router.post('/export', async (req, res) => {
try {
const { tenant_id, shop_ids, metrics, start_date, end_date } = req.body;
const { reportId, format, tenantId } = req.body;
if (!tenant_id || !shop_ids || !metrics || !start_date || !end_date) {
if (!reportId || !format || !tenantId) {
return res.status(400).json({
success: false,
error: 'MISSING_REQUIRED_FIELDS',
@@ -98,209 +97,55 @@ router.post('/realtime', async (req, res) => {
});
}
const results = await ShopReportAggregationService.realtimeAggregation({
tenant_id,
shop_ids,
metrics,
start_date,
end_date,
});
res.json({
success: true,
data: results,
});
} catch (error) {
logger.error('[ShopReport] Realtime aggregation error:', error);
res.status(500).json({
success: false,
error: 'REALTIME_AGGREGATION_ERROR',
message: '实时聚合查询失败',
});
}
});
/**
* GET /api/shop-reports/history
* 获取历史报表列表
*/
router.get('/history', async (req, res) => {
try {
const tenantId = (req as any).user?.tenantId || req.query.tenant_id;
const reportType = req.query.report_type as ReportType;
const limit = parseInt(req.query.limit as string) || 10;
if (!tenantId) {
return res.status(400).json({
success: false,
error: 'MISSING_TENANT_ID',
message: '缺少租户ID',
});
}
const reports = await ShopReportAggregationService.getHistoricalReports(
tenantId,
reportType,
limit
const result = await ShopReportAggregationService.exportReport(
{
tenantId,
userId: 'system',
role: 'ADMIN',
permissions: ['*'],
hierarchyPath: '/'
},
reportId,
format
);
res.json({
success: true,
data: reports,
data: result,
message: '报表导出成功',
});
} catch (error) {
logger.error('[ShopReport] Get history error:', error);
logger.error('[ShopReport] Export report error:', error);
const errorMessage = error instanceof Error ? error.message : '报表导出失败';
res.status(500).json({
success: false,
error: 'GET_HISTORY_ERROR',
message: '获取历史报表失败',
error: 'EXPORT_REPORT_ERROR',
message: errorMessage,
});
}
});
/**
* GET /api/shop-reports/:reportId
* 获取报表详情
* DELETE /api/shop-reports/cache/:tenantId
* 清除报表缓存
*/
router.get('/:reportId', async (req, res) => {
router.delete('/cache/:tenantId', async (req, res) => {
try {
const { reportId } = req.params;
const { tenantId } = req.params;
const report = await ShopReportAggregationService.getReportById(reportId);
if (!report) {
return res.status(404).json({
success: false,
error: 'REPORT_NOT_FOUND',
message: '报表不存在',
});
}
await ShopReportAggregationService.clearReportCache(tenantId);
res.json({
success: true,
data: report,
message: '报表缓存清除成功',
});
} catch (error) {
logger.error('[ShopReport] Get report error:', error);
logger.error('[ShopReport] Clear cache error:', error);
const errorMessage = error instanceof Error ? error.message : '报表缓存清除失败';
res.status(500).json({
success: false,
error: 'GET_REPORT_ERROR',
message: '获取报表失败',
});
}
});
/**
* POST /api/shop-reports/subscriptions
* 创建报表订阅
*/
router.post('/subscriptions', async (req, res) => {
try {
const {
tenant_id,
report_type,
time_dimension,
frequency,
recipients,
} = req.body;
const userId = (req as any).user?.id;
if (!tenant_id || !report_type || !time_dimension || !frequency || !recipients) {
return res.status(400).json({
success: false,
error: 'MISSING_REQUIRED_FIELDS',
message: '缺少必要字段',
});
}
const subscription = await ShopReportAggregationService.createSubscription(
tenant_id,
userId,
report_type,
time_dimension,
frequency,
recipients
);
res.json({
success: true,
data: subscription,
message: '订阅创建成功',
});
} catch (error) {
logger.error('[ShopReport] Create subscription error:', error);
res.status(500).json({
success: false,
error: 'CREATE_SUBSCRIPTION_ERROR',
message: '创建订阅失败',
});
}
});
/**
* GET /api/shop-reports/subscriptions/my
* 获取当前用户的订阅列表
*/
router.get('/subscriptions/my', async (req, res) => {
try {
const tenantId = (req as any).user?.tenantId;
const userId = (req as any).user?.id;
if (!tenantId || !userId) {
return res.status(401).json({
success: false,
error: 'UNAUTHORIZED',
message: '未登录',
});
}
const subscriptions = await ShopReportAggregationService.getUserSubscriptions(
tenantId,
userId
);
res.json({
success: true,
data: subscriptions,
});
} catch (error) {
logger.error('[ShopReport] Get subscriptions error:', error);
res.status(500).json({
success: false,
error: 'GET_SUBSCRIPTIONS_ERROR',
message: '获取订阅列表失败',
});
}
});
/**
* POST /api/shop-reports/init-tables
* 初始化数据库表(仅管理员)
*/
router.post('/init-tables', async (req, res) => {
try {
const userRole = (req as any).user?.role;
if (userRole !== 'ADMIN') {
return res.status(403).json({
success: false,
error: 'FORBIDDEN',
message: '只有管理员可以执行此操作',
});
}
await ShopReportAggregationService.initTables();
res.json({
success: true,
message: '数据库表初始化成功',
});
} catch (error) {
logger.error('[ShopReport] Init tables error:', error);
res.status(500).json({
success: false,
error: 'INIT_TABLES_ERROR',
message: '初始化数据库表失败',
error: 'CLEAR_CACHE_ERROR',
message: errorMessage,
});
}
});