init:代码初始化
This commit is contained in:
200
src/services/priceService.js
Normal file
200
src/services/priceService.js
Normal 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;
|
||||
Reference in New Issue
Block a user