init:代码初始化

This commit is contained in:
bai
2026-01-06 09:19:12 +08:00
parent 2dff90de4a
commit ba478d70cc
34 changed files with 11917 additions and 1 deletions

View File

@@ -0,0 +1,200 @@
const Price = require('../models/Price');
const { validateAndCleanPricesData } = require('../utils/validator');
/**
* 价格服务
* 处理价格相关的业务逻辑
*/
class PriceService {
/**
* 按地区查询价格
*/
static async getByRegion(region, date, page = 1, pageSize = 20) {
try {
const prices = await Price.getByRegion(region, date);
// 手动分页
const total = prices.length;
const startIndex = (page - 1) * pageSize;
const endIndex = startIndex + parseInt(pageSize, 10);
const paginatedData = prices.slice(startIndex, endIndex);
return {
success: true,
data: paginatedData,
pagination: {
page: parseInt(page, 10),
pageSize: parseInt(pageSize, 10),
total,
totalPages: Math.ceil(total / pageSize)
}
};
} catch (error) {
throw error;
}
}
/**
* 搜索价格数据
*/
static async search(filters) {
try {
// 解析分页参数
const page = parseInt(filters.page) || 1;
const pageSize = parseInt(filters.pageSize) || 20;
const searchFilters = {
material: filters.material,
specification: filters.specification,
startDate: filters.startDate,
endDate: filters.endDate,
region: filters.region,
page,
pageSize
};
// 获取数据
const data = await Price.search(searchFilters);
// 获取总数
const total = await Price.count({
material: filters.material,
specification: filters.specification,
startDate: filters.startDate,
endDate: filters.endDate,
region: filters.region
});
return {
success: true,
data,
pagination: {
page,
pageSize,
total,
totalPages: Math.ceil(total / pageSize)
}
};
} catch (error) {
throw error;
}
}
/**
* 获取价格统计
*/
static async getStats(filters) {
try {
const stats = await Price.getStats(filters);
// 计算趋势(如果提供了天数)
let trend = null;
let changeRate = null;
if (filters.days && stats.avgPrice) {
const previousStats = await Price.getStats({
region: filters.region,
material: filters.material,
days: parseInt(filters.days) * 2 // 获取双倍天数的范围
});
if (previousStats.avgPrice) {
const currentAvg = parseFloat(stats.avgPrice);
const previousAvg = parseFloat(previousStats.avgPrice);
const change = currentAvg - previousAvg;
changeRate = (change / previousAvg * 100).toFixed(2);
trend = change > 0 ? 'up' : change < 0 ? 'down' : 'stable';
}
}
return {
success: true,
data: {
count: stats.count,
avgPrice: stats.avgPrice ? parseFloat(parseFloat(stats.avgPrice).toFixed(2)) : null,
minPrice: stats.minPrice,
maxPrice: stats.maxPrice,
stdDev: stats.stdDev ? parseFloat(parseFloat(stats.stdDev).toFixed(2)) : null,
trend,
changeRate: changeRate ? `${changeRate > 0 ? '+' : ''}${changeRate}%` : null
}
};
} catch (error) {
throw error;
}
}
/**
* 获取价格趋势
*/
static async getTrend(filters) {
try {
const trend = await Price.getTrend(filters);
return {
success: true,
data: trend.map(item => ({
date: item.date,
avgPrice: item.avgPrice ? parseFloat(parseFloat(item.avgPrice).toFixed(2)) : null,
minPrice: item.minPrice,
maxPrice: item.maxPrice
})),
meta: {
total: trend.length,
filters: {
region: filters.region || null,
material: filters.material || null,
days: filters.days || null
}
}
};
} catch (error) {
throw error;
}
}
/**
* 导入数据
*/
static async importData(prices) {
try {
if (!Array.isArray(prices) || prices.length === 0) {
throw new Error('无效的数据格式');
}
// 验证并清洗数据
const validation = validateAndCleanPricesData(prices);
if (validation.validCount === 0) {
return {
success: false,
message: '没有有效的数据可以导入',
data: {
total: validation.total,
imported: 0,
errors: validation.errors
}
};
}
// 批量插入有效数据
const result = await Price.batchInsert(validation.validData);
return {
success: true,
message: `成功导入 ${result} 条数据${validation.errorCount > 0 ? `${validation.errorCount} 条数据因格式错误被跳过` : ''}`,
data: {
imported: result,
total: validation.total,
validCount: validation.validCount,
errorCount: validation.errorCount,
errors: validation.errorCount > 0 ? validation.errors.slice(0, 10) : undefined // 只返回前 10 个错误
}
};
} catch (error) {
throw error;
}
}
}
module.exports = PriceService;