苍穹外卖项目 - 第5天冲刺日志
日期:2025-11-30
冲刺周期:第5天/共7天
会议时间:09:00 - 09:15
会议地点:开发室
参会人员:李靖华 温尚熙 谢斯越 郑哲磊
一、站立会议照片

团队成员正在讨论数据统计功能的实现细节
二、会议内容记录
郑哲磊(后端负责人)
昨天已完成的工作:
- ✅ [WI-049] 完成订单提交接口
- ✅ [WI-050] 实现订单状态管理
- ✅ [WI-051] 完成订单查询和详情接口
- ✅ [WI-052] 实现库存扣减和并发控制
今天计划完成的工作:
- [WI-065] 完成数据统计接口
- [WI-066] 实现WebSocket实时通知
- [WI-067] 完成报表导出功能
- [WI-068] 优化数据库查询性能
工作中遇到的困难:
- 数据统计涉及大量聚合查询,需要优化SQL
- WebSocket连接管理需要考虑断线重连
谢斯越(前端负责人)
昨天已完成的工作:
- ✅ [WI-053] 完成订单管理页面
- ✅ [WI-054] 实现订单状态流转操作
- ✅ [WI-055] 完成订单详情页面
- ✅ [WI-056] 实现数据统计图表
今天计划完成的工作:
- [WI-069] 完成数据统计页面
- [WI-070] 实现报表导出功能
- [WI-071] 优化页面响应速度
- [WI-072] 完善错误提示和用户体验
工作中遇到的困难:
- 大数据量图表渲染性能需要优化
- 报表导出格式需要与后端协商
温尚熙(小程序开发)
昨天已完成的工作:
- ✅ [WI-057] 完成订单详情页面
- ✅ [WI-058] 实现订单支付功能(模拟)
- ✅ [WI-059] 完成订单评价功能
- ✅ [WI-060] 实现订单催单功能
今天计划完成的工作:
- [WI-073] 实现消息通知功能
- [WI-074] 完成用户个人中心
- [WI-075] 优化小程序性能
- [WI-076] 完善异常处理
工作中遇到的困难:
- 小程序消息推送需要申请模板消息权限
- 部分页面在低端设备上加载较慢
李靖华(测试与文档)
昨天已完成的工作:
- ✅ [WI-061] 执行订单模块接口测试
- ✅ [WI-062] 进行并发测试
- ✅ [WI-063] 编写部署文档
- ✅ [WI-064] 进行安全测试
今天计划完成的工作:
- [WI-077] 进行全面集成测试
- [WI-078] 编写用户操作手册
- [WI-079] 进行兼容性测试
- [WI-080] 整理项目文档
工作中遇到的困难:
- 集成测试用例较多,执行时间较长
- 需要在多种设备和浏览器上进行兼容性测试
三、燃尽图
剩余工作量(小时)
120 |●| \
100 | ●| \\80 | \| ●60 | \\| \40 | ●| \\20 | ●| \0 |________________________________●1 2 3 4 5 6 7 (天数)图例:
● —— 实际进度(实线)
- - - 理想进度(虚线)
燃尽图说明:
- 当前剩余工作量:18小时(完成102小时)
- 理想剩余工作量:26小时
- 进度状态:✅ 显著快于预期进度
- 燃尽速度:24小时/天
项目收敛分析:
- 第5天项目进入收尾阶段,主要功能已全部完成
- 实际进度大幅领先理想进度,项目提前进入测试和优化阶段
- 剩余工作主要是测试、优化和文档整理
- 预计可以提前1天完成所有任务
四、代码/文档签入记录
温尚熙 - 数据统计与WebSocket实时通知模块
- 模块名称:sky-server 数据统计与WebSocket模块
- 提交内容:
- 完成营业额统计接口
- 实现订单统计和用户统计
- 添加WebSocket服务端
- 优化统计查询SQL
代码示例:
// ReportController.java - 数据统计控制器
@RestController
@RequestMapping("/admin/report")
@Slf4j
@Api(tags = "数据统计相关接口")
public class ReportController {@Autowiredprivate ReportService reportService;@GetMapping("/turnoverStatistics")@ApiOperation("营业额统计")public Result<TurnoverReportVO> turnoverStatistics(@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin,@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end) {log.info("营业额统计:{} - {}", begin, end);TurnoverReportVO turnoverReportVO = reportService.getTurnoverStatistics(begin, end);return Result.success(turnoverReportVO);}@GetMapping("/ordersStatistics")@ApiOperation("订单统计")public Result<OrderReportVO> ordersStatistics(@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin,@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end) {OrderReportVO orderReportVO = reportService.getOrderStatistics(begin, end);return Result.success(orderReportVO);}@GetMapping("/userStatistics")@ApiOperation("用户统计")public Result<UserReportVO> userStatistics(@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin,@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end) {UserReportVO userReportVO = reportService.getUserStatistics(begin, end);return Result.success(userReportVO);}@GetMapping("/export")@ApiOperation("导出运营数据报表")public void export(HttpServletResponse response) {reportService.exportBusinessData(response);}
}
// ReportServiceImpl.java - 数据统计业务逻辑
@Service
@Slf4j
public class ReportServiceImpl implements ReportService {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate UserMapper userMapper;@Autowiredprivate RedisTemplate redisTemplate;public TurnoverReportVO getTurnoverStatistics(LocalDate begin, LocalDate end) {// 先从Redis缓存获取String cacheKey = "report:turnover:" + begin + ":" + end;TurnoverReportVO cached = (TurnoverReportVO) redisTemplate.opsForValue().get(cacheKey);if (cached != null) {return cached;}// 构建日期列表List<LocalDate> dateList = new ArrayList<>();dateList.add(begin);while (!begin.equals(end)) {begin = begin.plusDays(1);dateList.add(begin);}// 查询每天的营业额List<Double> turnoverList = new ArrayList<>();for (LocalDate date : dateList) {LocalDateTime beginTime = LocalDateTime.of(date, LocalTime.MIN);LocalDateTime endTime = LocalDateTime.of(date, LocalTime.MAX);// 查询当天已完成订单的总金额Map map = new HashMap();map.put("begin", beginTime);map.put("end", endTime);map.put("status", Orders.COMPLETED);Double turnover = orderMapper.sumByMap(map);turnover = turnover == null ? 0.0 : turnover;turnoverList.add(turnover);}// 封装返回结果TurnoverReportVO turnoverReportVO = TurnoverReportVO.builder().dateList(StringUtils.join(dateList, ",")).turnoverList(StringUtils.join(turnoverList, ",")).build();// 缓存结果(1小时)redisTemplate.opsForValue().set(cacheKey, turnoverReportVO, 1, TimeUnit.HOURS);return turnoverReportVO;}/*** 导出运营数据报表*/public void exportBusinessData(HttpServletResponse response) {// 查询最近30天的运营数据LocalDate dateBegin = LocalDate.now().minusDays(30);LocalDate dateEnd = LocalDate.now().minusDays(1);// 查询概览数据BusinessDataVO businessDataVO = getBusinessData(dateBegin, dateEnd);// 通过POI将数据写入Excel文件InputStream in = this.getClass().getClassLoader().getResourceAsStream("template/运营数据报表模板.xlsx");try {XSSFWorkbook excel = new XSSFWorkbook(in);XSSFSheet sheet = excel.getSheet("Sheet1");// 填充数据sheet.getRow(1).getCell(1).setCellValue("时间:" + dateBegin + "至" + dateEnd);sheet.getRow(3).getCell(2).setCellValue(businessDataVO.getTurnover());sheet.getRow(3).getCell(4).setCellValue(businessDataVO.getOrderCompletionRate());sheet.getRow(3).getCell(6).setCellValue(businessDataVO.getNewUsers());// 通过输出流将Excel文件下载到客户端浏览器ServletOutputStream out = response.getOutputStream();excel.write(out);out.close();excel.close();} catch (IOException e) {e.printStackTrace();}}
}
// WebSocketServer.java - WebSocket服务端
@Component
@ServerEndpoint("/ws/{userId}")
@Slf4j
public class WebSocketServer {// 存放会话对象private static Map<String, Session> sessionMap = new HashMap();/*** 连接建立成功调用的方法*/@OnOpenpublic void onOpen(Session session, @PathParam("userId") String userId) {log.info("客户端:{} 建立连接", userId);sessionMap.put(userId, session);}/*** 收到客户端消息后调用的方法*/@OnMessagepublic void onMessage(String message, @PathParam("userId") String userId) {log.info("收到来自客户端:{} 的信息:{}", userId, message);}/*** 连接关闭调用的方法*/@OnClosepublic void onClose(@PathParam("userId") String userId) {log.info("连接断开:{}", userId);sessionMap.remove(userId);}/*** 群发消息*/public void sendToAllClient(String message) {Collection<Session> sessions = sessionMap.values();for (Session session : sessions) {try {session.getBasicRemote().sendText(message);} catch (Exception e) {e.printStackTrace();}}}/*** 向指定客户端发送消息*/public void sendToClient(String userId, String message) {Session session = sessionMap.get(userId);if (session != null) {try {session.getBasicRemote().sendText(message);} catch (Exception e) {e.printStackTrace();}}}
}
<!-- OrderMapper.xml - 统计查询SQL优化 -->
<mapper namespace="com.sky.mapper.OrderMapper"><!-- 根据条件统计营业额 --><select id="sumByMap" resultType="java.lang.Double">SELECT SUM(amount) FROM orders<where><if test="begin != null">AND order_time >= #{begin}</if><if test="end != null">AND order_time <= #{end}</if><if test="status != null">AND status = #{status}</if></where></select><!-- 添加索引优化查询性能 --><!-- CREATE INDEX idx_order_time_status ON orders(order_time, status); -->
</mapper>
李靖华 - 管理端数据统计页面模块
- 模块名称:sky-admin 数据统计页面
- 提交内容:
- 完成数据统计页面
- 实现报表导出功能
- 优化图表渲染性能
- 完善错误提示
代码示例:
<!-- Statistics.vue - 数据统计页面 -->
<template><div class="statistics-container"><!-- 日期选择 --><el-card class="filter-card"><el-form :inline="true"><el-form-item label="统计日期"><el-date-pickerv-model="dateRange"type="daterange"range-separator="至"start-placeholder="开始日期"end-placeholder="结束日期"@change="handleDateChange"/></el-form-item><el-form-item><el-button type="primary" @click="loadData">查询</el-button><el-button @click="handleExport">导出报表</el-button></el-form-item></el-form></el-card><!-- 数据概览 --><el-row :gutter="20" class="overview-row"><el-col :span="6"><el-card><div class="stat-item"><div class="stat-label">营业额</div><div class="stat-value">¥{{ overview.turnover?.toFixed(2) }}</div></div></el-card></el-col><el-col :span="6"><el-card><div class="stat-item"><div class="stat-label">订单数</div><div class="stat-value">{{ overview.orderCount }}</div></div></el-card></el-col><el-col :span="6"><el-card><div class="stat-item"><div class="stat-label">新增用户</div><div class="stat-value">{{ overview.newUsers }}</div></div></el-card></el-col><el-col :span="6"><el-card><div class="stat-item"><div class="stat-label">订单完成率</div><div class="stat-value">{{ overview.orderCompletionRate }}%</div></div></el-card></el-col></el-row><!-- 营业额趋势图 --><el-card class="chart-card"><template #header><span>营业额趋势</span></template><div ref="turnoverChartRef" style="height: 400px"></div></el-card><!-- 订单统计图 --><el-card class="chart-card"><template #header><span>订单统计</span></template><div ref="orderChartRef" style="height: 400px"></div></el-card><!-- 用户增长图 --><el-card class="chart-card"><template #header><span>用户增长</span></template><div ref="userChartRef" style="height: 400px"></div></el-card></div>
</template><script setup>
import { ref, reactive, onMounted, nextTick } from 'vue'
import * as echarts from 'echarts'
import { getTurnoverStatisticsApi, getOrderStatisticsApi, getUserStatisticsApi, exportReportApi } from '@/api/report'
import { ElMessage } from 'element-plus'const dateRange = ref([])
const overview = reactive({turnover: 0,orderCount: 0,newUsers: 0,orderCompletionRate: 0
})const turnoverChartRef = ref(null)
const orderChartRef = ref(null)
const userChartRef = ref(null)let turnoverChart = null
let orderChart = null
let userChart = null// 初始化图表
const initCharts = () => {turnoverChart = echarts.init(turnoverChartRef.value)orderChart = echarts.init(orderChartRef.value)userChart = echarts.init(userChartRef.value)// 响应式调整window.addEventListener('resize', () => {turnoverChart?.resize()orderChart?.resize()userChart?.resize()})
}// 加载营业额数据
const loadTurnoverData = async () => {const [begin, end] = dateRange.valueconst { data } = await getTurnoverStatisticsApi({begin: begin.toISOString().split('T')[0],end: end.toISOString().split('T')[0]})const dateList = data.dateList.split(',')const turnoverList = data.turnoverList.split(',').map(Number)// 更新概览数据overview.turnover = turnoverList.reduce((a, b) => a + b, 0)// 渲染图表turnoverChart.setOption({title: { text: '营业额趋势' },tooltip: {trigger: 'axis',formatter: (params) => {return `${params[0].name}<br/>营业额: ¥${params[0].value.toFixed(2)}`}},xAxis: {type: 'category',data: dateList},yAxis: {type: 'value',axisLabel: {formatter: '¥{value}'}},series: [{name: '营业额',type: 'line',data: turnoverList,smooth: true,areaStyle: {color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ offset: 0, color: 'rgba(24, 144, 255, 0.3)' },{ offset: 1, color: 'rgba(24, 144, 255, 0.1)' }])}}]})
}// 加载订单数据
const loadOrderData = async () => {const [begin, end] = dateRange.valueconst { data } = await getOrderStatisticsApi({begin: begin.toISOString().split('T')[0],end: end.toISOString().split('T')[0]})const dateList = data.dateList.split(',')const orderCountList = data.orderCountList.split(',').map(Number)const validOrderCountList = data.validOrderCountList.split(',').map(Number)// 更新概览数据overview.orderCount = orderCountList.reduce((a, b) => a + b, 0)const validOrderCount = validOrderCountList.reduce((a, b) => a + b, 0)overview.orderCompletionRate = ((validOrderCount / overview.orderCount) * 100).toFixed(2)// 渲染图表orderChart.setOption({title: { text: '订单统计' },tooltip: { trigger: 'axis' },legend: { data: ['订单总数', '有效订单'] },xAxis: {type: 'category',data: dateList},yAxis: { type: 'value' },series: [{name: '订单总数',type: 'bar',data: orderCountList},{name: '有效订单',type: 'bar',data: validOrderCountList}]})
}// 加载用户数据
const loadUserData = async () => {const [begin, end] = dateRange.valueconst { data } = await getUserStatisticsApi({begin: begin.toISOString().split('T')[0],end: end.toISOString().split('T')[0]})const dateList = data.dateList.split(',')const newUserList = data.newUserList.split(',').map(Number)const totalUserList = data.totalUserList.split(',').map(Number)// 更新概览数据overview.newUsers = newUserList.reduce((a, b) => a + b, 0)// 渲染图表userChart.setOption({title: { text: '用户增长' },tooltip: { trigger: 'axis' },legend: { data: ['新增用户', '总用户数'] },xAxis: {type: 'category',data: dateList},yAxis: { type: 'value' },series: [{name: '新增用户',type: 'line',data: newUserList},{name: '总用户数',type: 'line',data: totalUserList}]})
}// 加载所有数据
const loadData = async () => {if (!dateRange.value || dateRange.value.length !== 2) {ElMessage.warning('请选择日期范围')return}try {await Promise.all([loadTurnoverData(),loadOrderData(),loadUserData()])ElMessage.success('数据加载成功')} catch (error) {ElMessage.error('数据加载失败')}
}// 导出报表
const handleExport = async () => {try {const response = await exportReportApi()const blob = new Blob([response.data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })const url = window.URL.createObjectURL(blob)const link = document.createElement('a')link.href = urllink.download = `运营数据报表_${new Date().getTime()}.xlsx`link.click()window.URL.revokeObjectURL(url)ElMessage.success('导出成功')} catch (error) {ElMessage.error('导出失败')}
}onMounted(() => {// 默认最近7天const end = new Date()const begin = new Date()begin.setDate(begin.getDate() - 7)dateRange.value = [begin, end]nextTick(() => {initCharts()loadData()})
})
</script><style scoped>
.statistics-container {padding: 20px;
}.filter-card {margin-bottom: 20px;
}.overview-row {margin-bottom: 20px;
}.stat-item {text-align: center;
}.stat-label {font-size: 14px;color: #666;margin-bottom: 10px;
}.stat-value {font-size: 24px;font-weight: bold;color: #1890ff;
}.chart-card {margin-bottom: 20px;
}
</style>
谢斯越
- 提交内容:
- 完成消息通知功能
- 实现用户个人中心
- 优化小程序性能
- 完善异常处理
郑哲磊
- 提交内容:
- 完成全面集成测试
- 编写用户操作手册
- 进行兼容性测试
- 整理项目文档