苍穹外卖后端技术栈梳理:Java 实战进阶笔记
前言
🚀作为一名软件工程大三学生,我通过《苍穹外卖》项目从 Java 基础进阶到企业级后端开发。这是一个模拟外卖平台的实战项目,涵盖管理端(员工操作)和用户端(微信小程序),涉及 RESTful API、数据库操作和实时通信。本文梳理项目后端技术栈,分享核心用途、应用场景、代码示例和学习心得,希望为 Java 学习者和后端开发者提供参考!
项目简介
《苍穹外卖》是一个前后端分离的 Java 企业级项目,模拟外卖平台的业务流程,包括员工管理、菜品管理、订单处理和数据统计等。后端基于 SpringBoot 提供 RESTful API,整合多种技术实现高效开发。以下是后端技术栈的详细解析。
技术栈解析
Web框架
SpringBoot
用途:简化 Spring 配置,提供自动装配和嵌入式服务器(如 Tomcat),适合快速开发 RESTful API。
项目应用:搭建项目核心框架,处理 HTTP 请求(如 /admin/dish 新增菜品)。
代码实例:
package com.sky.controller.admin;
import com.sky.dto.DishDTO;
import com.sky.dto.DishPageQueryDTO;
import com.sky.entity.Dish;
import com.sky.result.PageResult;
import com.sky.result.Result;
import com.sky.service.DishService;
import com.sky.vo.DishVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Set;
/**
* 菜品管理
*/
@RestController
@RequestMapping("/admin/dish")
@Api(tags = "菜品相关接口")
@Slf4j
public class DishController {
@Autowired
private DishService dishService;
@Autowired
private RedisTemplate redisTemplate;
/**
* 新增菜品
* @param dishDTO
* @return
*/
@PostMapping
@ApiOperation("新增菜品")
public Result save(@RequestBody DishDTO dishDTO) {
log.info("新增菜品:{}", dishDTO);
dishService.saveWithFlavor(dishDTO);
//清理缓存数据
String key = "dish_" + dishDTO.getCategoryId();
cleanCache(key);
return Result.success();
}
}
心得:SpringBoot 的注解(如 @RestController)和 application.yml 让开发更高效,极大降低配置难度。
SpringMVC
用途:基于 MVC 模式,处理 HTTP 请求和响应,分离业务逻辑。
项目应用:实现 Controller 层,处理订单提交、员工登录等请求。
心得:掌握 @RequestMapping 和 RESTful 规范让我对 API 设计有了更深理解。
MyBatis
用途:MyBatis 提供灵活的 ORM。
项目应用:操作 MySQL 数据库,如新增菜品和对应口味。
代码示例:
package com.sky.mapper;
import com.github.pagehelper.Page;
import com.sky.annotation.AutoFill;
import com.sky.dto.DishPageQueryDTO;
import com.sky.entity.Dish;
import com.sky.enumeration.OperationType;
import com.sky.vo.DishVO;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
import java.util.Map;
@Mapper
public interface DishMapper {
/**
* 插入菜品数据
* @param dish
*/
@AutoFill(value = OperationType.INSERT)
void insert(Dish dish);
}
package com.sky.service.impl;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.sky.annotation.AutoFill;
import com.sky.constant.MessageConstant;
import com.sky.constant.StatusConstant;
import com.sky.dto.DishDTO;
import com.sky.dto.DishPageQueryDTO;
import com.sky.entity.Dish;
import com.sky.entity.DishFlavor;
import com.sky.exception.DeletionNotAllowedException;
import com.sky.mapper.DishFlavorMapper;
import com.sky.mapper.DishMapper;
import com.sky.mapper.SetmealDishMapper;
import com.sky.mapper.SetmealMapper;
import com.sky.result.PageResult;
import com.sky.service.DishService;
import com.sky.vo.DishVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
@Service
@Slf4j
public class DishServiceImpl implements DishService {
@Autowired
private DishMapper dishMapper;
/**
* 新增菜品和对应口味
* @param dishDTO
*/
@Transactional
public void saveWithFlavor(DishDTO dishDTO) {
Dish dish = new Dish();
BeanUtils.copyProperties(dishDTO, dish);
//向菜品表插入1条数据
dishMapper.insert(dish);
//获取insert 操作返回的id
Long dishId = dish.getId();
//向口味表插入n条数据
List<DishFlavor> flavors = dishDTO.getFlavors();
if(flavors != null && flavors.size() > 0){
flavors.forEach((dishFlavor) -> dishFlavor.setDishId(dishId));
//向口味表插入n条数据
dishFlavorMapper.insertBatch(flavors);
}
}
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.OrderMapper">
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
insert into orders (number, status, user_id, address_book_id, order_time, checkout_time, pay_method,
pay_status, amount, remark, phone, address, consignee,
estimated_delivery_time, delivery_status,pack_amount, tableware_number, tableware_status)
values (#{number}, #{status}, #{userId}, #{addressBookId}, #{orderTime}, #{checkoutTime}, #{payMethod},
#{payStatus}, #{amount}, #{remark}, #{phone}, #{address}, #{consignee},
#{estimatedDeliveryTime}, #{deliveryStatus},#{packAmount}, #{tablewareNumber}, #{tablewareStatus})
</insert>
</mapper>
心得:通过mapper映射文件实现复杂的SQL
数据库
MySQL
这里就不在赘述了,因为大家都有了一定的基础
Redis
用途:高性能键值存储,用于缓存和分布式场景。
项目应用:缓存热门菜品、存储会话(如 SET session:user:id value)。
代码示例:
package com.sky.controller.user;
import com.sky.constant.StatusConstant;
import com.sky.entity.Dish;
import com.sky.result.Result;
import com.sky.service.DishService;
import com.sky.vo.DishVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController("userDishController")
@RequestMapping("/user/dish")
@Slf4j
@Api(tags = "C端-菜品浏览接口")
public class DishController {
@Autowired
private DishService dishService;
@Autowired
private RedisTemplate redisTemplate;
/**
* 根据分类id查询菜品
* @param categoryId
* @return
*/
@GetMapping("/list")
@ApiOperation("根据分类id查询菜品")
public Result<List<DishVO>> list(Long categoryId) {
//查询redis中是都存在菜品数据
String key = "dish_" + categoryId;
//如果存在,直接返回,无需查询数据放入redis中
List<DishVO> list = (List<DishVO>) redisTemplate.opsForValue().get(key);
if (list != null && list.size() > 0) {
//如果存在,直接返回,无需查询数据库
return Result.success(list);
}
Dish dish = new Dish();
dish.setCategoryId(categoryId);
dish.setStatus(StatusConstant.ENABLE);//查询起售中的菜品
//如果不存在,查询数据库,将查询到的数据库放入redis中
list = dishService.listWithFlavor(dish);
redisTemplate.opsForValue().set(key,list);
list = dishService.listWithFlavor(dish);
return Result.success(list);
}
}
心得:Redis 的 String 和 Hash 结构简单易用,学习缓存策略(如防缓存穿透)很实用。
认证授权:JWT
用途:JSON Web Token 提供无状态认证,验证用户身份。
项目应用:实现员工和用户登录,生成/验证 Token。
代码示例:
package com.sky.controller.admin;
import com.sky.constant.JwtClaimsConstant;
import com.sky.dto.EmployeeDTO;
import com.sky.dto.EmployeeLoginDTO;
import com.sky.dto.EmployeePageQueryDTO;
import com.sky.entity.Employee;
import com.sky.properties.JwtProperties;
import com.sky.result.PageResult;
import com.sky.result.Result;
import com.sky.service.EmployeeService;
import com.sky.utils.JwtUtil;
import com.sky.vo.EmployeeLoginVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
/**
* 员工管理
*/
@RestController
@RequestMapping("/admin/employee")
@Slf4j
@Api(tags = "员工相关接口")
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
@Autowired
private JwtProperties jwtProperties;
/**
* 登录
*
* @param employeeLoginDTO
* @return
*/
@PostMapping("/login")
@ApiOperation("员工登录")
public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) {
log.info("员工登录:{}", employeeLoginDTO);
Employee employee = employeeService.login(employeeLoginDTO);
//登录成功后,生成jwt令牌
Map<String, Object> claims = new HashMap<>();
claims.put(JwtClaimsConstant.EMP_ID, employee.getId());
String token = JwtUtil.createJWT(
jwtProperties.getAdminSecretKey(),
jwtProperties.getAdminTtl(),
claims);
EmployeeLoginVO employeeLoginVO = EmployeeLoginVO.builder()
.id(employee.getId())
.userName(employee.getUsername())
.name(employee.getName())
.token(token)
.build();
return Result.success(employeeLoginVO);
}
}
实时通信:WebSocket
用途:支持全双工通信,实现实时消息推送。
项目应用:新订单提醒,管理员接收语音通知。
代码示例:
package com.sky.websocket;
import org.springframework.stereotype.Component;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
* WebSocket服务
*/
@Component
@ServerEndpoint("/ws/{sid}")
public class WebSocketServer {
//存放会话对象
private static Map<String, Session> sessionMap = new HashMap();
/**
* 群发
*
* @param message
*/
public void sendToAllClient(String message) {
Collection<Session> sessions = sessionMap.values();
for (Session session : sessions) {
try {
//服务器向客户端发送消息
session.getBasicRemote().sendText(message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
接口文档:Swagger / Knife4j
用途:自动生成 API 文档,便于前后端联调。
项目应用:通过 Knife4j 生成 RESTful API 文档(如 /swagger-ui/index.html)。
心得:Swagger 的 @ApiOperation 注解让接口文档更清晰,调试效率大幅提升。
代码实例:
/**
* 通过knife4j生成接口文档
* @return
*/
@Bean
public Docket docket1() {
ApiInfo apiInfo = new ApiInfoBuilder()
.title("苍穹外卖项目接口文档")
.version("2.0")
.description("苍穹外卖项目接口文档")
.build();
Docket docket = new Docket(DocumentationType.SWAGGER_2)
.groupName("管理端接口")
.apiInfo(apiInfo)
.select()
.apis(RequestHandlerSelectors.basePackage("com.sky.controller.admin"))
.paths(PathSelectors.any())
.build();
return docket;
}
@Bean
public Docket docket2() {
ApiInfo apiInfo = new ApiInfoBuilder()
.title("苍穹外卖项目接口文档")
.version("2.0")
.description("苍穹外卖项目接口文档")
.build();
Docket docket = new Docket(DocumentationType.SWAGGER_2)
.groupName("用户端接口")
.apiInfo(apiInfo)
.select()
.apis(RequestHandlerSelectors.basePackage("com.sky.controller.user"))
.paths(PathSelectors.any())
.build();
return docket;
}
}
服务器:Nginx
用途:高性能服务器,支持反向代理和负载均衡。
项目应用:代理前端请求,优化静态资源访问(如 proxy_pass http://localhost:8080)。
心得:Nginx 配置简单,学习反向代理让我理解了前后端分离部署。
其他技术
|
技术 |
用途 |
项目应用 |
心得 |
|---|---|---|---|
|
MD5 加密 |
加密用户密码,增强安全性 |
保护用户密码(如 MD5(user:password)) |
简单高效,安全性基础 |
|
Spring Data Redis |
简化 Redis 操作 |
实现缓存和会话管理(如 @Cacheable) |
注解式操作提升开发效率 |
|
Apache POI |
处理 Excel 文件的导入导出 |
导出订单统计报表(如营业额数据) |
快速实现报表功能,学习成本低 |
|
Spring Task |
定时任务调度 |
定时清理过期订单或生成统计报表 |
注解式调度简单易用 |
|
Spring Cache |
简化缓存管理 |
缓存菜品查询结果,减少数据库压力 |
注解驱动,易于整合 Redis |
|
HttpClient |
发送 HTTP 请求 |
调用微信支付或第三方接口 |
灵活处理外部 API 调用 |
Apache POI 代码示例(导出运营数据报表):
package com.sky.service.impl;
import com.sky.dto.GoodsSalesDTO;
import com.sky.entity.Orders;
import com.sky.mapper.OrderMapper;
import com.sky.mapper.UserMapper;
import com.sky.service.ReportService;
import com.sky.service.WorkspaceService;
import com.sky.vo.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
@Slf4j
public class ReportServiceImpl implements ReportService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private UserMapper userMapper;
@Autowired
private WorkspaceService workspaceService;
/**
* 导出运营数据报表
* @param response
*/
@Override
public void exportBusinessData(HttpServletResponse response) {
//1. 查询数据库,获取近30天的营业数据
LocalDate begin = LocalDate.now().minusDays(30);
LocalDate end = LocalDate.now().minusDays(1);
//查询概览数据
BusinessDataVO businessData = workspaceService.getBusinessData(LocalDateTime.of(begin, LocalTime.MIN), LocalDateTime.of(end, LocalTime.MAX));
//2. 通过POI将数据写入到Excel文件中
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("template/运营数据报表模板.xlsx");
try {
//基于模版文件创建一个新的Excel文件
XSSFWorkbook excel = new XSSFWorkbook(inputStream);
//获取表格文件的Sheet页
XSSFSheet sheet = excel.getSheet("Sheet1");
//填充数据--时间
sheet.getRow(1).getCell(1).setCellValue("时间:" + begin + "至" + end);
//获得第四行
XSSFRow row = sheet.getRow(3);
row.getCell(2).setCellValue(businessData.getTurnover());
row.getCell(4).setCellValue(businessData.getOrderCompletionRate());
row.getCell(6).setCellValue(businessData.getNewUsers());
//获得第五行
row = sheet.getRow(4);
row.getCell(2).setCellValue(businessData.getValidOrderCount());
row.getCell(4).setCellValue(businessData.getUnitPrice());
//填充明细数据
for(int i = 0; i < 30; i++){
LocalDate date = begin.plusDays(i);
//查询某一天的营业数据
BusinessDataVO bd = workspaceService.getBusinessData(LocalDateTime.of(date, LocalTime.MIN), LocalDateTime.of(date, LocalTime.MAX));
row = sheet.getRow(7 + i);
row.getCell(1).setCellValue(date.toString());
row.getCell(2).setCellValue(bd.getTurnover());
row.getCell(3).setCellValue(bd.getValidOrderCount());
row.getCell(4).setCellValue(bd.getOrderCompletionRate());
row.getCell(5).setCellValue(bd.getUnitPrice());
row.getCell(6).setCellValue(bd.getNewUsers());
}
//3. 通过输出流将Excel文件下载到客户端浏览器
ServletOutputStream outputStream = response.getOutputStream();
excel.write(outputStream);
//关闭资源
excel.close();
outputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
POI 心得:POI 的 XSSFWorkbook 和 Sheet API 简单易用,快速实现了 Excel 导出功能,适合报表需求。
Spring Task 代码示例(处理超时订单和一直处于派送中的订单):
package com.sky.task;
import com.sky.entity.Orders;
import com.sky.mapper.OrderMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.List;
/**
* 定时任务类,定时处理订单状态
*/
@Component
@Slf4j
public class OrderTask {
@Autowired
private OrderMapper orderMapper;
/**
* 处理超时订单的方法
*/
@Scheduled(cron = "0 * * * * ?")//每分钟执行一次
public void processTimeoutOrder()
{
log.info("处理超时订单:{}", LocalDateTime.now());
LocalDateTime time = LocalDateTime.now().plusMinutes(-15);
List<Orders> ordersList = orderMapper.getByStatusAndOrderTimeLT(Orders.PENDING_PAYMENT, time);
if (ordersList != null && ordersList.size() > 0)
{
for (Orders orders : ordersList)
{
orders.setStatus(Orders.CANCELLED);
orders.setCancelReason("订单超时,自动取消");
orders.setCancelTime(LocalDateTime.now());
orderMapper.update(orders);
}
}
}
/**
* 处理一致处于派送中状态的订单
*/
@Scheduled(cron = "0 0 1 * * ? ")//每天凌晨1点钟触发一次
public void processDeliveryOrder()
{
log.info("处理处于派送中状态的订单:{}", LocalDateTime.now());
LocalDateTime time = LocalDateTime.now().plusMinutes(-60);
List<Orders> ordersList = orderMapper.getByStatusAndOrderTimeLT(Orders.DELIVERY_IN_PROGRESS, time);
if (ordersList != null && ordersList.size() > 0)
{
for (Orders orders : ordersList)
{
orders.setStatus(Orders.COMPLETED);
orderMapper.update(orders);
}
}
}
}
Spring Task 心得:@Scheduled 注解让定时任务配置非常简单,Cron 表达式灵活,支持多种调度场景。
Spring Cache 代码示例(缓存套餐查询):
package com.sky.controller.user;
import com.sky.constant.StatusConstant;
import com.sky.entity.Setmeal;
import com.sky.result.Result;
import com.sky.service.SetmealService;
import com.sky.vo.DishItemVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController("userSetmealController")
@RequestMapping("/user/setmeal")
@Api(tags = "C端-套餐浏览接口")
public class SetmealController {
@Autowired
private SetmealService setmealService;
/**
* 条件查询
*
* @param categoryId
* @return
*/
@GetMapping("/list")
@ApiOperation("根据分类id查询套餐")
@Cacheable(cacheNames = "setmealCache", key = "#categoryId")
public Result<List<Setmeal>> list(Long categoryId) {
Setmeal setmeal = new Setmeal();
setmeal.setCategoryId(categoryId);
setmeal.setStatus(StatusConstant.ENABLE);
List<Setmeal> list = setmealService.list(setmeal);
return Result.success(list);
}
}
Spring Cache 心得:@Cacheable 注解与 Redis 整合,自动管理缓存,减少数据库查询,提升性能。
HttpClient 代码示例(调用微信服务,获取用户openid):
package com.sky.service.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.sky.constant.MessageConstant;
import com.sky.dto.UserLoginDTO;
import com.sky.entity.User;
import com.sky.exception.LoginFailedException;
import com.sky.mapper.UserMapper;
import com.sky.properties.WeChatProperties;
import com.sky.service.UserService;
import com.sky.utils.HttpClientUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import static com.fasterxml.jackson.databind.type.LogicalType.Map;
@Service
@Slf4j
public class UserServiceImpl implements UserService {
@Autowired
public WeChatProperties weChatProperties;
@Autowired
public UserMapper userMapper;
//微信服务接口地址
public static final String WX_LOGIN = "https://api.weixin.qq.com/sns/jscode2session";
/**
* 调取微信服务,获取openid
* @param code
* @return
*/
private String getOpenId(String code){
//调用微信接口服务,获得当前微信用户的openid
Map<String, String> map = new HashMap<>();
map.put("appid",weChatProperties.getAppid());
map.put("secret",weChatProperties.getSecret());
map.put("js_code",code);
map.put("grant_type","authorization_code");
String json = HttpClientUtil.doGet(WX_LOGIN, map);
JSONObject jsonObject = JSON.parseObject(json);
String openid = jsonObject.getString("openid");
return openid;
}
}
HttpClient 心得:HttpClient 适合调用外部 API(如微信服务),支持复杂的 HTTP 请求配置。
结语
💡如果你也处于 Java 后端学习的阶段,我强烈推荐动手实践这个项目,不仅能巩固所学,还能锻炼项目思维,对后期找实习/就业非常有帮助。
🔗如果你觉得本文有帮助,欢迎点赞收藏支持,也欢迎留言一起交流技术心得!
🚀🚀🚀后期将会进一步推出每个技术栈的详细描述,敬请期待📢📢📢
更多推荐



所有评论(0)