功能概述
商品更新记录功能用于跟踪和管理小象买菜系统中商品信息的变更历史,包括价格调整、库存变动、商品上下架等操作记录。
数据库设计
商品更新记录表(product_update_log)
```sql
CREATE TABLE product_update_log (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 记录ID,
product_id BIGINT NOT NULL COMMENT 商品ID,
update_type VARCHAR(20) NOT NULL COMMENT 更新类型(price/stock/status/info),
old_value TEXT COMMENT 旧值(JSON格式),
new_value TEXT COMMENT 新值(JSON格式),
update_by BIGINT COMMENT 操作人ID,
update_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 更新时间,
remark VARCHAR(255) COMMENT 备注,
INDEX idx_product_id (product_id),
INDEX idx_update_time (update_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=商品更新记录表;
```
后端实现
1. 实体类
```java
@Data
public class ProductUpdateLog {
private Long id;
private Long productId;
private String updateType; // price, stock, status, info
private String oldValue; // JSON字符串
private String newValue; // JSON字符串
private Long updateBy;
private Date updateTime;
private String remark;
}
```
2. 记录商品更新的AOP切面
```java
@Aspect
@Component
public class ProductUpdateAspect {
@Autowired
private ProductUpdateLogService productUpdateLogService;
// 拦截商品服务中的更新方法
@AfterReturning(pointcut = "execution(* com.xiaoxiang.service.ProductService.update*(..))",
returning = "result")
public void afterProductUpdate(JoinPoint joinPoint, Object result) {
Object[] args = joinPoint.getArgs();
if (args == null || args.length == 0) return;
// 获取商品ID - 根据实际方法参数调整
Long productId = null;
if (args[0] instanceof Long) {
productId = (Long) args[0];
} else if (args[0] instanceof Product) {
productId = ((Product) args[0]).getId();
}
if (productId == null) return;
// 获取方法名确定更新类型
String methodName = joinPoint.getSignature().getName();
String updateType = determineUpdateType(methodName);
// 获取旧值(可以从缓存或数据库获取更新前的值)
// 这里简化处理,实际应从缓存或数据库获取
Product oldProduct = getOldProductFromCacheOrDB(productId);
// 获取新值(从参数中获取)
Product newProduct = null;
if (args[0] instanceof Product) {
newProduct = (Product) args[0];
}
// 创建更新记录
if (oldProduct != null && newProduct != null) {
ProductUpdateLog log = new ProductUpdateLog();
log.setProductId(productId);
log.setUpdateType(updateType);
log.setOldValue(JSON.toJSONString(oldProduct));
log.setNewValue(JSON.toJSONString(newProduct));
log.setUpdateBy(getCurrentUserId()); // 需要实现获取当前用户ID的方法
log.setUpdateTime(new Date());
productUpdateLogService.save(log);
}
}
private String determineUpdateType(String methodName) {
if (methodName.contains("Price")) return "price";
if (methodName.contains("Stock")) return "stock";
if (methodName.contains("Status")) return "status";
return "info";
}
// 其他辅助方法...
}
```
3. 手动记录更新(适用于无法用AOP拦截的情况)
```java
@Service
public class ProductServiceImpl implements ProductService {
@Autowired
private ProductUpdateLogService productUpdateLogService;
@Override
public boolean updateProductPrice(Long productId, BigDecimal newPrice) {
// 1. 获取旧价格
Product product = productMapper.selectById(productId);
BigDecimal oldPrice = product.getPrice();
// 2. 更新价格
boolean success = productMapper.updatePrice(productId, newPrice) > 0;
// 3. 记录更新日志
if (success) {
ProductUpdateLog log = new ProductUpdateLog();
log.setProductId(productId);
log.setUpdateType("price");
log.setOldValue(oldPrice.toString());
log.setNewValue(newPrice.toString());
log.setUpdateBy(getCurrentUserId());
productUpdateLogService.save(log);
}
return success;
}
}
```
前端实现
1. 商品更新记录列表页面
```vue
商品更新记录
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[10, 20, 50, 100]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
/>
<script>
export default {
data() {
return {
updateLogs: [],
currentPage: 1,
pageSize: 10,
total: 0,
productId: this.$route.params.id
}
},
created() {
this.fetchUpdateLogs();
},
methods: {
fetchUpdateLogs() {
this.$api.getProductUpdateLogs({
productId: this.productId,
pageNum: this.currentPage,
pageSize: this.pageSize
}).then(response => {
this.updateLogs = response.data.list;
this.total = response.data.total;
});
},
handleSizeChange(val) {
this.pageSize = val;
this.fetchUpdateLogs();
},
handleCurrentChange(val) {
this.currentPage = val;
this.fetchUpdateLogs();
}
}
}
```
2. 商品更新记录详情弹窗
```vue
更新类型: {{ updateLog.updateType }}
旧值
{{ formatValue(updateLog.oldValue) }} 新值
{{ formatValue(updateLog.newValue) }}
<script>
export default {
props: {
updateLog: Object,
visible: Boolean
},
data() {
return {
dialogVisible: this.visible
}
},
watch: {
visible(newVal) {
this.dialogVisible = newVal;
},
dialogVisible(newVal) {
this.$emit(update:visible, newVal);
}
},
methods: {
formatValue(value) {
try {
const obj = JSON.parse(value);
return JSON.stringify(obj, null, 2);
} catch (e) {
return value;
}
}
}
}
```
高级功能扩展
1. 差异对比功能
```java
// 在ProductUpdateLogService中添加方法
public Map
compareChanges(ProductUpdateLog log) {
Map result = new HashMap<>();
try {
Product oldProduct = JSON.parseObject(log.getOldValue(), Product.class);
Product newProduct = JSON.parseObject(log.getNewValue(), Product.class);
// 比较各个字段的变化
Map changes = new HashMap<>();
// 价格比较
if (!oldProduct.getPrice().equals(newProduct.getPrice())) {
changes.put("price", Map.of(
"old", oldProduct.getPrice(),
"new", newProduct.getPrice()
));
}
// 库存比较
if (!oldProduct.getStock().equals(newProduct.getStock())) {
changes.put("stock", Map.of(
"old", oldProduct.getStock(),
"new", newProduct.getStock()
));
}
// 状态比较
if (!oldProduct.getStatus().equals(newProduct.getStatus())) {
changes.put("status", Map.of(
"old", oldProduct.getStatus(),
"new", newProduct.getStatus()
));
}
// 其他字段比较...
result.put("changes", changes);
result.put("updateTime", log.getUpdateTime());
result.put("updateBy", log.getUpdateBy());
} catch (Exception e) {
// 处理异常
}
return result;
}
```
2. 更新记录查询接口
```java
@RestController
@RequestMapping("/api/product-logs")
public class ProductUpdateLogController {
@Autowired
private ProductUpdateLogService productUpdateLogService;
@GetMapping
public Result> getUpdateLogs(
@RequestParam Long productId,
@RequestParam(required = false) String updateType,
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) {
PageHelper.startPage(pageNum, pageSize);
List logs = productUpdateLogService.findByProductIdAndType(productId, updateType);
PageInfo pageInfo = new PageInfo<>(logs);
return Result.success(pageInfo);
}
@GetMapping("/{id}/detail")
public Result