第四天项目

苍穹外卖项目 - 第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
    • 进行安全测试
posted @ 2025-12-03 19:58  清月明风  阅读(3)  评论(0)    收藏  举报