一、功能概述
商品迭代记录功能用于跟踪和管理叮咚买菜平台上商品的变更历史,包括商品信息修改、上下架状态变化、价格调整等关键操作记录,以实现商品生命周期的可追溯性。
二、系统架构设计
1. 数据库设计
```sql
-- 商品迭代记录表
CREATE TABLE product_iteration_log (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
product_id BIGINT NOT NULL COMMENT 商品ID,
change_type VARCHAR(50) NOT NULL COMMENT 变更类型(上架/下架/修改信息/价格调整等),
old_value TEXT COMMENT 变更前值(JSON格式),
new_value TEXT COMMENT 变更后值(JSON格式),
change_field VARCHAR(100) COMMENT 变更字段(多个字段用逗号分隔),
operator_id BIGINT COMMENT 操作人ID,
operator_name VARCHAR(50) COMMENT 操作人姓名,
change_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 变更时间,
ip_address VARCHAR(50) COMMENT 操作IP,
reason VARCHAR(255) COMMENT 变更原因,
INDEX idx_product_id (product_id),
INDEX idx_change_time (change_time)
);
```
2. 后端服务设计
```java
// 商品迭代记录服务接口
public interface ProductIterationService {
/
* 记录商品变更
* @param productId 商品ID
* @param changeType 变更类型
* @param oldValue 变更前值
* @param newValue 变更后值
* @param changeFields 变更字段
* @param operator 操作人信息
* @param reason 变更原因
*/
void recordProductChange(Long productId, String changeType,
String oldValue, String newValue,
Set
changeFields,
OperatorInfo operator, String reason);
/
* 查询商品变更历史
* @param productId 商品ID
* @param startTime 开始时间
* @param endTime 结束时间
* @param pageNum 页码
* @param pageSize 每页大小
* @return 变更记录分页结果
*/
PageResult queryProductChanges(
Long productId, Date startTime, Date endTime,
Integer pageNum, Integer pageSize);
}
```
三、核心功能实现
1. 变更记录拦截机制
使用AOP或中间件拦截商品相关服务的修改操作:
```java
@Aspect
@Component
public class ProductChangeAspect {
@Autowired
private ProductIterationService iterationService;
@AfterReturning(pointcut = "execution(* com.dingdong.product.service.ProductService.update*(..))",
returning = "result")
public void afterProductUpdate(JoinPoint joinPoint, Object result) {
// 获取方法参数
Object[] args = joinPoint.getArgs();
Long productId = (Long) args[0]; // 假设第一个参数是productId
// 获取当前用户信息
OperatorInfo operator = SecurityContextHolder.getOperator();
// 获取修改前数据(可从缓存或数据库获取)
Product oldProduct = productCache.get(productId);
// 获取修改后数据(从参数或结果中获取)
Product newProduct = parseNewProduct(args);
// 比较差异并记录
Set changedFields = compareProductChanges(oldProduct, newProduct);
if (!changedFields.isEmpty()) {
iterationService.recordProductChange(
productId,
"INFO_UPDATE",
JSON.toJSONString(oldProduct),
JSON.toJSONString(newProduct),
changedFields,
operator,
"常规信息更新"
);
}
}
}
```
2. 价格变更专项处理
```java
@Service
public class ProductPriceServiceImpl implements ProductPriceService {
@Autowired
private ProductIterationService iterationService;
@Override
public boolean updateProductPrice(Long productId, BigDecimal newPrice, String reason) {
// 获取当前价格
BigDecimal oldPrice = productDao.getPriceById(productId);
// 更新价格
boolean success = productDao.updatePrice(productId, newPrice);
if (success && !newPrice.equals(oldPrice)) {
OperatorInfo operator = SecurityContextHolder.getOperator();
iterationService.recordProductChange(
productId,
"PRICE_UPDATE",
oldPrice.toString(),
newPrice.toString(),
Collections.singleton("price"),
operator,
reason
);
}
return success;
}
}
```
3. 上下架状态变更处理
```java
@Service
public class ProductStatusServiceImpl implements ProductStatusService {
@Autowired
private ProductIterationService iterationService;
@Override
public boolean changeProductStatus(Long productId, ProductStatus newStatus, String reason) {
Product product = productDao.getById(productId);
ProductStatus oldStatus = product.getStatus();
boolean success = productDao.updateStatus(productId, newStatus);
if (success && !oldStatus.equals(newStatus)) {
OperatorInfo operator = SecurityContextHolder.getOperator();
iterationService.recordProductChange(
productId,
"STATUS_UPDATE",
oldStatus.name(),
newStatus.name(),
Collections.singleton("status"),
operator,
reason
);
}
return success;
}
}
```
四、前端展示实现
1. 商品详情页变更历史标签页
```vue
商品变更历史
查看详情
@current-change="handlePageChange"
:current-page="pagination.currentPage"
:page-size="pagination.pageSize"
layout="prev, pager, next"
:total="pagination.total">
:visible.sync="detailDialogVisible"
:change-data="currentChange" />
<script>
export default {
data() {
return {
historyList: [],
pagination: {
currentPage: 1,
pageSize: 10,
total: 0
},
detailDialogVisible: false,
currentChange: null
}
},
created() {
this.loadHistory();
},
methods: {
loadHistory() {
const params = {
productId: this.$route.params.id,
page: this.pagination.currentPage,
size: this.pagination.pageSize
};
api.getProductChangeHistory(params).then(res => {
this.historyList = res.data.list;
this.pagination.total = res.data.total;
});
},
showChangeDetail(row) {
this.currentChange = row;
this.detailDialogVisible = true;
},
handlePageChange(page) {
this.pagination.currentPage = page;
this.loadHistory();
}
}
}
```
2. 变更详情弹窗组件
```vue
{{ fieldMap[key] || key }}
变更前:
{{ formatValue(key, value.old) }}
变更后:
{{ formatValue(key, value.new) }}
<script>
export default {
props: {
visible: Boolean,
changeData: Object
},
data() {
return {
fieldMap: {
name: 商品名称,
price: 价格,
stock: 库存,
status: 状态,
description: 商品描述
}
}
},
computed: {
changeDiff() {
if (!this.changeData) return {};
const oldData = JSON.parse(this.changeData.oldValue || {});
const newData = JSON.parse(this.changeData.newValue || {});
const diff = {};
// 简单比较逻辑,实际应根据changeFields筛选
Object.keys(newData).forEach(key => {
if (JSON.stringify(oldData[key]) !== JSON.stringify(newData[key])) {
diff[key] = {
old: oldData[key],
new: newData[key]
};
}
});
return diff;
}
},
methods: {
formatValue(field, value) {
if (field === price) {
return `¥${value.toFixed(2)}`;
}
if (field === status) {
const statusMap = {
ON_SALE: 在售,
OFF_SALE: 下架,
DELETED: 已删除
};
return statusMap[value] || value;
}
return value;
}
}
}
```
五、高级功能实现
1. 变更自动通知
```java
@Service
public class ChangeNotificationService {
@Autowired
private EmailService emailService;
@Autowired
private SmsService smsService;
@Async
public void notifyAboutCriticalChange(ProductIterationLog log) {
if (isCriticalChange(log)) {
// 获取相关人员列表
List recipients = getRecipientsForProduct(log.getProductId());
// 发送邮件
String emailContent = buildEmailContent(log);
recipients.stream()
.filter(r -> r.getEmail() != null)
.forEach(r -> emailService.send(
r.getEmail(),
"商品重要变更通知",
emailContent
));
// 发送短信(仅限关键联系人)
String smsContent = buildSmsContent(log);
recipients.stream()
.filter(r -> r.getPhone() != null && r.isKeyContact())
.forEach(r -> smsService.send(
r.getPhone(),
smsContent
));
}
}
private boolean isCriticalChange(ProductIterationLog log) {
return "PRICE_UPDATE".equals(log.getChangeType()) ||
"STATUS_UPDATE".equals(log.getChangeType());
}
}
```
2. 变更数据可视化
```javascript
// 使用ECharts实现变更趋势图表
function initChangeChart(containerId, productId) {
const chart = echarts.init(document.getElementById(containerId));
function fetchData() {
api.getProductChangeStats(productId).then(res => {
const option = {
tooltip: {
trigger: axis
},
legend: {
data: [价格变更, 状态变更, 信息变更]
},
xAxis: {
type: category,
data: res.data.dates
},
yAxis: {
type: value
},
series: [
{
name: 价格变更,
type: bar,
data: res.data.priceChanges
},
{
name: 状态变更,
type: bar,
data: res.data.statusChanges
},
{
name: 信息变更,
type: bar,
data: res.data.infoChanges
}
]
};
chart.setOption(option);
});
}
fetchData();
setInterval(fetchData, 3600000); // 每小时刷新
}
```
六、性能优化与安全考虑
1. 数据存储优化:
- 对oldValue/newValue使用压缩存储
- 对历史数据按月份分区存储
- 实现数据归档策略,保留最近6个月详细记录,更早数据只保留关键字段
2. 查询优化:
- 为product_id和change_time建立复合索引
- 实现分页查询缓存
- 对高频查询字段建立单独索引
3. 安全考虑:
- 记录操作人IP地址
- 实现操作日志防篡改机制(如数字签名)
- 对敏感商品变更实现双人复核机制
- 记录变更原因并要求填写
七、部署与监控
1. 部署方案:
- 与主系统同集群部署,共享数据库连接池
- 实现独立的变更记录服务,避免影响主交易链路
2. 监控指标:
- 变更记录写入延迟
- 查询响应时间
- 每月变更记录数量趋势
- 关键变更类型占比
3. 告警规则:
- 连续5分钟无变更记录写入(可能系统异常)
- 价格变更频率超过阈值
- 同一商品短时间内频繁变更
八、实施路线图
1. 第一阶段(2周):
- 完成数据库设计和基础记录功能
- 实现商品信息修改的自动记录
2. 第二阶段(3周):
- 完善价格和状态变更的专项处理
- 开发前端变更历史展示页面
3. 第三阶段(2周):
- 实现变更通知功能
- 添加数据可视化组件
4. 第四阶段(1周):
- 性能优化和安全加固
- 编写操作文档和培训材料
该实现方案可根据叮咚买菜实际业务需求和技术栈进行调整,核心思想是通过自动化记录商品生命周期中的关键变更,提高商品管理的可追溯性和合规性,同时为数据分析提供基础数据支持。