第四天项目
苍穹外卖项目 - 第4天冲刺日志
日期:2025-11-29
冲刺周期:第4天/共7天
参会人员:李靖华 温尚熙 谢斯越 郑哲磊
二、会议内容记录
郑哲磊(后端负责人)
昨天已完成的工作:
- ✅ [WI-033] 完成菜品管理CRUD接口
- ✅ [WI-034] 实现图片上传功能(阿里云OSS)
- ✅ [WI-035] 完成套餐管理接口
- ✅ [WI-036] 实现微信用户登录接口
今天计划完成的工作:
- [WI-049] 完成订单提交接口
- [WI-050] 实现订单状态管理
- [WI-051] 完成订单查询和详情接口
- [WI-052] 实现库存扣减和并发控制
工作中遇到的困难:
- 订单并发控制需要使用Redis分布式锁
- 订单状态流转逻辑复杂,需要仔细设计状态机
谢斯越(前端负责人)
昨天已完成的工作:
- ✅ [WI-037] 完成菜品管理页面
- ✅ [WI-038] 实现图片上传组件
- ✅ [WI-039] 完成套餐管理页面
- ✅ [WI-040] 优化页面加载性能
今天计划完成的工作:
- [WI-053] 完成订单管理页面
- [WI-054] 实现订单状态流转操作
- [WI-055] 完成订单详情页面
- [WI-056] 实现数据统计图表
工作中遇到的困难:
- 订单状态流转的按钮权限控制较复杂
- ECharts图表配置需要学习
温尚熙(小程序开发)
昨天已完成的工作:
- ✅ [WI-041] 完成地址管理功能
- ✅ [WI-042] 实现订单确认页面
- ✅ [WI-043] 完成订单提交功能
- ✅ [WI-044] 实现订单列表页面
今天计划完成的工作:
- [WI-057] 完成订单详情页面
- [WI-058] 实现订单支付功能(模拟)
- [WI-059] 完成订单评价功能
- [WI-060] 实现订单催单功能
工作中遇到的困难:
- 订单支付流程需要与后端协商
- 订单评价的星级评分组件需要自定义
李靖华(测试与文档)
昨天已完成的工作:
- ✅ [WI-045] 执行菜品管理接口测试
- ✅ [WI-046] 编写集成测试用例
- ✅ [WI-047] 进行性能测试
- ✅ [WI-048] 更新用户手册
今天计划完成的工作:
- [WI-061] 执行订单模块接口测试
- [WI-062] 进行并发测试
- [WI-063] 编写部署文档
- [WI-064] 进行安全测试
工作中遇到的困难:
- 并发测试需要模拟大量用户同时下单
- 安全测试工具OWASP ZAP的使用需要学习
三、燃尽图
剩余工作量(小时)
120 |●
| \
100 | ●
| \\
80 | \
| ●
60 | \\
| \
40 | ●
| \\
20 | \
| ●
0 |______________________________
1 2 3 4 5 6 7 (天数)
图例:
● —— 实际进度(实线)
- - - 理想进度(虚线)
燃尽图说明:
- 当前剩余工作量:42小时(完成78小时)
- 理想剩余工作量:51小时
- 进度状态:✅ 持续快于预期进度
- 燃尽速度:26小时/天
项目收敛分析:
- 第4天项目进入中后期,核心功能基本完成
- 实际进度持续领先理想进度,项目风险进一步降低
- 订单模块是最复杂的模块,需要重点关注质量
- 预计可以提前半天完成所有开发任务
四、代码/文档签入记录
郑哲磊- 订单管理与并发控制模块
- 模块名称:sky-server 订单管理模块
- 提交内容:
- 完成订单提交和查询接口
- 实现Redis分布式锁
- 添加订单状态流转逻辑
- 实现库存扣减和回滚机制
代码示例:
// OrderController.java - 订单管理控制器
@RestController
@RequestMapping("/user/order")
@Slf4j
@Api(tags = "订单相关接口")
public class OrderController {
@Autowired
private OrderService orderService;
@PostMapping("/submit")
@ApiOperation("用户下单")
public Result<OrderSubmitVO> submit(@RequestBody OrdersSubmitDTO ordersSubmitDTO) {
log.info("用户下单:{}", ordersSubmitDTO);
OrderSubmitVO orderSubmitVO = orderService.submitOrder(ordersSubmitDTO);
return Result.success(orderSubmitVO);
}
@GetMapping("/historyOrders")
@ApiOperation("历史订单查询")
public Result<PageResult> page(int page, int pageSize, Integer status) {
PageResult pageResult = orderService.pageQuery(page, pageSize, status);
return Result.success(pageResult);
}
@GetMapping("/orderDetail/{id}")
@ApiOperation("查询订单详情")
public Result<OrderVO> details(@PathVariable Long id) {
OrderVO orderVO = orderService.details(id);
return Result.success(orderVO);
}
}
// OrderServiceImpl.java - 订单业务逻辑(含分布式锁)
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private OrderDetailMapper orderDetailMapper;
@Autowired
private ShoppingCartMapper shoppingCartMapper;
@Autowired
private RedisTemplate redisTemplate;
@Transactional
public OrderSubmitVO submitOrder(OrdersSubmitDTO ordersSubmitDTO) {
// 获取当前用户id
Long userId = BaseContext.getCurrentId();
// 查询购物车数据
ShoppingCart shoppingCart = new ShoppingCart();
shoppingCart.setUserId(userId);
List<ShoppingCart> shoppingCartList = shoppingCartMapper.list(shoppingCart);
if (shoppingCartList == null || shoppingCartList.size() == 0) {
throw new ShoppingCartBusinessException(MessageConstant.SHOPPING_CART_IS_NULL);
}
// 使用Redis分布式锁防止重复下单
String lockKey = "order:submit:" + userId;
Boolean lock = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
if (!lock) {
throw new OrderBusinessException("订单提交中,请勿重复操作");
}
try {
// 构造订单数据
Orders orders = new Orders();
BeanUtils.copyProperties(ordersSubmitDTO, orders);
orders.setUserId(userId);
orders.setNumber(String.valueOf(System.currentTimeMillis()));
orders.setStatus(Orders.PENDING_PAYMENT);
orders.setPayStatus(Orders.UN_PAID);
orders.setOrderTime(LocalDateTime.now());
// 向订单表插入1条数据
orderMapper.insert(orders);
// 向订单明细表插入n条数据
List<OrderDetail> orderDetailList = new ArrayList<>();
for (ShoppingCart cart : shoppingCartList) {
OrderDetail orderDetail = new OrderDetail();
BeanUtils.copyProperties(cart, orderDetail);
orderDetail.setOrderId(orders.getId());
orderDetailList.add(orderDetail);
// 扣减库存(使用乐观锁)
boolean success = deductStock(cart.getDishId(), cart.getNumber());
if (!success) {
throw new OrderBusinessException("库存不足");
}
}
orderDetailMapper.insertBatch(orderDetailList);
// 清空购物车
shoppingCartMapper.deleteByUserId(userId);
// 封装返回结果
OrderSubmitVO orderSubmitVO = OrderSubmitVO.builder()
.id(orders.getId())
.orderNumber(orders.getNumber())
.orderAmount(orders.getAmount())
.orderTime(orders.getOrderTime())
.build();
return orderSubmitVO;
} finally {
// 释放锁
redisTemplate.delete(lockKey);
}
}
/**
* 扣减库存(乐观锁)
*/
private boolean deductStock(Long dishId, Integer number) {
// UPDATE dish SET stock = stock - #{number}
// WHERE id = #{dishId} AND stock >= #{number}
int rows = dishMapper.deductStock(dishId, number);
return rows > 0;
}
}
// RedisLockUtil.java - Redis分布式锁工具类
@Component
@Slf4j
public class RedisLockUtil {
@Autowired
private RedisTemplate<String, String> redisTemplate;
/**
* 获取锁
* @param key 锁的key
* @param value 锁的value(通常使用UUID)
* @param expireTime 过期时间(秒)
* @return 是否获取成功
*/
public boolean tryLock(String key, String value, long expireTime) {
Boolean result = redisTemplate.opsForValue()
.setIfAbsent(key, value, expireTime, TimeUnit.SECONDS);
return Boolean.TRUE.equals(result);
}
/**
* 释放锁(使用Lua脚本保证原子性)
*/
public boolean releaseLock(String key, String value) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) else return 0 end";
Long result = redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(key),
value
);
return Long.valueOf(1).equals(result);
}
}
<!-- OrderMapper.xml - 订单Mapper -->
<mapper namespace="com.sky.mapper.OrderMapper">
<!-- 订单分页查询 -->
<select id="pageQuery" resultType="com.sky.entity.Orders">
SELECT * FROM orders
<where>
<if test="userId != null">
AND user_id = #{userId}
</if>
<if test="status != null">
AND status = #{status}
</if>
</where>
ORDER BY order_time DESC
</select>
</mapper>
谢斯越 - 管理端订单管理页面模块
- 模块名称:sky-admin 订单管理页面
- 提交内容:
- 完成订单管理页面
- 实现订单状态流转操作
- 添加订单详情弹窗
- 实现数据统计图表
<!-- OrderList.vue - 订单列表页面 -->
<template>
<div class="order-container">
<!-- 搜索筛选 -->
<el-form :inline="true" :model="queryForm">
<el-form-item label="订单号">
<el-input v-model="queryForm.number" placeholder="请输入订单号" />
</el-form-item>
<el-form-item label="订单状态">
<el-select v-model="queryForm.status" placeholder="请选择状态">
<el-option label="全部" :value="null" />
<el-option label="待付款" :value="1" />
<el-option label="待接单" :value="2" />
<el-option label="已接单" :value="3" />
<el-option label="派送中" :value="4" />
<el-option label="已完成" :value="5" />
<el-option label="已取消" :value="6" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
<!-- 订单列表 -->
<el-table :data="orderList" style="width: 100%">
<el-table-column prop="number" label="订单号" width="180" />
<el-table-column prop="consignee" label="收货人" />
<el-table-column prop="phone" label="手机号" />
<el-table-column prop="amount" label="订单金额">
<template #default="{ row }">
¥{{ row.amount.toFixed(2) }}
</template>
</el-table-column>
<el-table-column prop="status" label="订单状态">
<template #default="{ row }">
<el-tag :type="getStatusType(row.status)">
{{ getStatusText(row.status) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="orderTime" label="下单时间" width="180" />
<el-table-column label="操作" width="300">
<template #default="{ row }">
<el-button size="small" @click="handleDetail(row)">详情</el-button>
<el-button v-if="row.status === 2" size="small" type="success"
@click="handleAccept(row.id)">接单</el-button>
<el-button v-if="row.status === 3" size="small" type="primary"
@click="handleDeliver(row.id)">派送</el-button>
<el-button v-if="row.status === 4" size="small" type="success"
@click="handleComplete(row.id)">完成</el-button>
<el-button v-if="row.status < 4" size="small" type="danger"
@click="handleCancel(row.id)">取消</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="page"
v-model:page-size="pageSize"
:total="total"
@current-change="getOrderList"
/>
<!-- 订单详情弹窗 -->
<el-dialog v-model="detailVisible" title="订单详情" width="800px">
<div class="order-detail">
<el-descriptions :column="2" border>
<el-descriptions-item label="订单号">{{ currentOrder.number }}</el-descriptions-item>
<el-descriptions-item label="订单状态">
{{ getStatusText(currentOrder.status) }}
</el-descriptions-item>
<el-descriptions-item label="收货人">{{ currentOrder.consignee }}</el-descriptions-item>
<el-descriptions-item label="手机号">{{ currentOrder.phone }}</el-descriptions-item>
<el-descriptions-item label="收货地址" :span="2">
{{ currentOrder.address }}
</el-descriptions-item>
<el-descriptions-item label="订单金额">
¥{{ currentOrder.amount?.toFixed(2) }}
</el-descriptions-item>
<el-descriptions-item label="下单时间">
{{ currentOrder.orderTime }}
</el-descriptions-item>
</el-descriptions>
<h4 style="margin-top: 20px">订单明细</h4>
<el-table :data="currentOrder.orderDetailList" border>
<el-table-column prop="name" label="菜品名称" />
<el-table-column prop="number" label="数量" />
<el-table-column prop="amount" label="金额">
<template #default="{ row }">
¥{{ row.amount.toFixed(2) }}
</template>
</el-table-column>
</el-table>
</div>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { getOrderListApi, acceptOrderApi, deliverOrderApi,
completeOrderApi, cancelOrderApi, getOrderDetailApi } from '@/api/order'
import { ElMessage, ElMessageBox } from 'element-plus'
const orderList = ref([])
const page = ref(1)
const pageSize = ref(10)
const total = ref(0)
const detailVisible = ref(false)
const currentOrder = ref({})
const queryForm = reactive({
number: '',
status: null
})
const statusMap = {
1: { text: '待付款', type: 'info' },
2: { text: '待接单', type: 'warning' },
3: { text: '已接单', type: 'primary' },
4: { text: '派送中', type: 'success' },
5: { text: '已完成', type: 'success' },
6: { text: '已取消', type: 'danger' }
}
const getStatusText = (status) => statusMap[status]?.text || '未知'
const getStatusType = (status) => statusMap[status]?.type || 'info'
const getOrderList = async () => {
const { data } = await getOrderListApi({
page: page.value,
pageSize: pageSize.value,
...queryForm
})
orderList.value = data.records
total.value = data.total
}
const handleDetail = async (row) => {
const { data } = await getOrderDetailApi(row.id)
currentOrder.value = data
detailVisible.value = true
}
const handleAccept = async (id) => {
await ElMessageBox.confirm('确认接单?', '提示')
await acceptOrderApi(id)
ElMessage.success('接单成功')
getOrderList()
}
const handleDeliver = async (id) => {
await deliverOrderApi(id)
ElMessage.success('派送成功')
getOrderList()
}
const handleComplete = async (id) => {
await completeOrderApi(id)
ElMessage.success('订单已完成')
getOrderList()
}
const handleCancel = async (id) => {
const { value } = await ElMessageBox.prompt('请输入取消原因', '取消订单')
await cancelOrderApi({ id, cancelReason: value })
ElMessage.success('订单已取消')
getOrderList()
}
onMounted(() => {
getOrderList()
})
</script>
温尚熙
- 提交内容:
- 完成订单详情页面
- 实现模拟支付功能
- 添加订单评价功能
- 实现订单催单功能
李靖华
- 提交内容:
- 完成订单模块接口测试
- 进行并发测试(1000并发)
- 编写部署文档v1.0
- 进行安全测试
浙公网安备 33010602011771号