业务关系
- 类别(Category) 是基础分类,分为菜品分类和套餐分类两种类型
- 菜品(Dish) 属于某个菜品分类,可以有多种口味(DishFlavor)
- 套餐(Setmeal) 属于某个套餐分类,可以包含多个菜品(SetmealDish关联)
关系图:
类别(Category)
├── 菜品分类
│ └── 菜品(Dish)
│ └── 菜品口味(DishFlavor)
└── 套餐分类
└── 套餐(Setmeal)
└── 套餐菜品关系(SetmealDish)
各模块增删查改前提条件
类别(Category)管理
新增
– 无特殊限制,直接创建启用状态的分类
删除
– 分类下不能有关联的菜品或套餐,否则抛出 DeletionNotAllowedException
修改
– 无特殊限制,可修改分类基本信息
启用/停用
– 直接修改分类状态
菜品(Dish)管理
新增
– 无特殊限制,创建菜品及其口味信息
删除
– 菜品必须处于停售状态(DISABLE),否则抛出异常
– 菜品不能被任何套餐关联,否则抛出异常
修改
– 无特殊限制,可修改菜品信息和口味
启用/停用
– 直接修改菜品状态
– 如果停用菜品,且该菜品被套餐关联,则同时停用相关套餐
套餐(Setmeal)管理
新增
– 无特殊限制,创建套餐及关联菜品信息
删除
– 套餐必须处于停售状态,否则抛出 DeletionNotAllowedException
修改
– 无特殊限制,可修改套餐信息和关联菜品
启用/停用
– 启用套餐时,如果套餐内包含停售的菜品,则不能启用,抛出 DeletionNotAllowedException
– 停用套餐无特殊限制
这些设计保证了数据的一致性和业务逻辑的完整性。
数据库表的关系
根据代码分析,数据库表之间的关系如下:
数据库表关系
1. Category(分类表)
- 作为基础分类表,包含菜品分类和套餐分类
- type 字段区分分类类型(菜品或套餐)
2. Dish(菜表)
category_id外键关联 Category 表- 与 DishFlavor 表通过
dish_id关联
3. DishFlavor(菜品口味表)
dish_id外键关联 Dish 表- 存储菜品的各种口味信息
4. Setmeal(套餐表)
category_id外键关联 Category 表- 与 SetmealDish 表通过
setmeal_id关联
5. SetmealDish(套餐菜品关系表)
关系图谱
Category <--(category_id)-- Dish <--(dish_id)-- DishFlavor
^ |
| |
Category <--(category_id)-- Setmeal <--(setmeal_id)-- SetmealDish --(dish_id)--> Dish
这种设计实现了分类管理、菜品管理、套餐管理的完整业务关系,支持复杂的关联查询和业务约束检查。
HttpClient
客户端编程工具包
Maven坐标
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
Get请求
HttpClient发送请求步骤:
- 创建HttpClient对象
- 创建Http请求对象
- 调用HttpClient的execute方法发送请求
测试
@Test
public void test() throws IOException {
//创建HttpClient对象
CloseableHttpClient client= HttpClients.createDefault();
//创建请求对象
HttpGet httpGet=new HttpGet("http://localhost:8080/user/shop/status");
//发送请求,接收响应结果
CloseableHttpResponse response=client.execute(httpGet);
//解析响应结果
int statusCode=response.getStatusLine().getStatusCode();
System.out.println(statusCode);
HttpEntity entity=response.getEntity();
String data= EntityUtils.toString(entity);
System.out.println("返回数据:"+data);
//释放资源
response.close();
client.close();
}
Post请求
在HttpClientTest中添加POST方式请求方法,相比GET请求来说,POST请求若携带参数需要封装请求体对象,并将该对象设置在请求对象中。
实现步骤:
- 创建HttpClient对象
- 创建请求对象
- 发送请求,接收响应结果
- 解析响应结果
- 关闭资源
微信开发者小程序
微信开发小程序密钥:ee1bbc131fbe07ca653e1c2d5620f8e3
ID:wx8d43af60809ec22f

步骤分析:
- 小程序端,调用wx.login()获取code,就是授权码。
- 小程序端,调用wx.request()发送请求并携带code,请求开发者服务器(自己编写的后端服务)。
- 开发者服务端,通过HttpClient向微信接口服务发送请求,并携带appId+appsecret+code三个参数。
- 开发者服务端,接收微信接口服务返回的数据,session_key+opendId等。opendId是微信用户的唯一标识。
- 开发者服务端,自定义登录态,生成令牌(token)和openid等数据返回给小程序端,方便后绪请求身份校验。
- 小程序端,收到自定义登录态,存储storage。
- 小程序端,后绪通过wx.request()发起业务请求时,携带token。
- 开发者服务端,收到请求后,通过携带的token,解析当前登录用户的id。
- 开发者服务端,身份校验通过后,继续相关的业务逻辑处理,最终返回业务数据。
客户端查找操作
根据分类ID查找菜品和套餐
需求:查找正在启用的套餐和菜品
查询的数据包括两个表的内容时,需要分别定义两个实例返回数据,在一起返回
//根据分类查询菜品(包含口味)
@Override
public List<DishVO> typelist(Dish dish) {
List<Dish> dishList=dishMapper.getTypeId(dish);
List<DishVO> dishVOS=new ArrayList<>();
for(Dish d:dishList){
DishVO dishVO=new DishVO();
BeanUtils.copyProperties(d,dishVO);
List<DishFlavor> dishFlavors=dishFlavorMapper.getByDishId(d.getId());
dishVO.setFlavors(dishFlavors);
dishVOS.add(dishVO);
}
return dishVOS;
}
Spring Cache
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
在SpringCache中提供了很多缓存操作的注解,常见的是以下的几个:
| 注解 | 说明 |
|---|---|
| @EnableCaching | 开启缓存注解功能,通常加在启动类上 |
| @Cacheable | 在方法执行前先查询缓存中是否有数据,如果有数据,则直接返回缓存数据;如果没有缓存数据,调用方法并将方法返回值放到缓存中 |
| @CachePut | 将方法的返回值放到缓存中 |
| @CacheEvict | 将一条或多条数据从缓存中删除 |
在spring boot项目中,使用缓存技术只需在项目中导入相关缓存技术的依赖包,并在启动类上使用@EnableCaching开启缓存支持即可。
例如,使用Redis作为缓存技术,只需要导入Spring data Redis的maven坐标即可。
微信支付
https://pay.weixin.qq.com/static/product/product_index.shtml

微信支付相关接口:
JSAPI下单:商户系统调用该接口在微信支付服务后台生成预支付交易单(对应时序图的第5步)

微信小程序调起支付:通过JSAPI下单接口获取到发起支付的必要参数prepay_id,然后使用微信支付提供的小程序方法调起小程序支付(对应时序图的第10步)

隧道token:Yzk2YWRlNWQtOWFhNS00YmUzLTlkYTItZjYyNDc4YmY1Zjcy
订单管理
1. orders(订单表)
- 主表,记录订单的基本信息。
- 字段示例:
id,user_id,status,pay_status,amount,address,phone,consignee,order_time,checkout_time,cancel_time等。 - 业务关系:
- 一个订单属于一个用户。
- 一个订单包含多个订单明细。
- 一个订单可以有支付、状态变更等操作记录。
2. order_detail(订单明细表)
- 从表,记录订单中的具体商品信息。
- 字段示例:
id,order_id,dish_id,setmeal_id,name,price,number等。 - 业务关系:
- 一个订单对应多个订单明细。
- 一个订单明细关联一个菜品或套餐。
3. address_book(地址簿表)
- 用户地址表,记录用户的收货地址。
- 字段示例:
id,user_id,consignee,phone,detail,city_name,district_name等。 - 业务关系:
- 一个用户可以有多个地址。
- 一个订单关联一个地址。
4. shopping_cart(购物车表)
- 用户购物车表,记录用户加入购物车的商品。
- 字段示例:
id,user_id,dish_id,setmeal_id,name,price,number,create_time等。 - 业务关系:
- 一个用户对应一个购物车(逻辑上)。
- 购物车中的商品在提交订单后会清空。
5. user(用户表)
- 用户表,记录用户信息。
- 字段示例:
id,openid,name,phone,status,create_time等。 - 业务关系:
- 一个用户可以下多个订单。
- 一个用户可以有多个地址。
业务操作流程
用户端:
- 查看购物车 →
shopping_cart - 选择地址 →
address_book - 提交订单 → 创建
orders记录,写入order_detail,清空shopping_cart - 支付订单 → 更新
orders中的支付状态 - 查看历史订单 → 关联查询
orders+order_detail - 取消/再来一单 → 更新
orders状态或复制到购物车
管理端:
- 订单搜索/统计 → 查询
orders并统计状态 - 接单/拒单/派送/完成 → 更新
orders状态 - 查看订单详情 → 关联查询
orders+order_detail
SpringTask
定时触发一些任务
cron表达式
cron表达式其实就是一个字符串,通过cron表达式可以定义任务触发的时间
构成规则:分为6或7个域,由空格分隔开,每个域代表一个含义
每个域的含义分别为:秒、分钟、小时、日、月、周、年(可选)
cron表达式在线生成器:https://cron.qqe2.com/
通配符:
* 表示所有值;
? 表示未说明的值,即不关心它为何值;
– 表示一个指定的范围;
, 表示附加一个可能值;
/ 符号前表示开始时间,符号后表示每次递增的值;
cron表达式案例:
*/5 * * * * ? 每隔5秒执行一次
0 */1 * * * ? 每隔1分钟执行一次
0 0 5-15 * * ? 每天5-15点整点触发
0 0/3 * * * ? 每三分钟触发一次
0 0-5 14 * * ? 在每天下午2点到下午2:05期间的每1分钟触发
0 0/5 14 * * ? 在每天下午2点到下午2:55期间的每5分钟触发
0 0/5 14,18 * * ? 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时
0 0 10,14,16 * * ? 每天上午10点,下午2点,4点
订单状态定时处理
@Scheduled(cron = "0 * * * * ?")
public void processTimeoutOrder() {
log.info("处理支付超时订单:{}", new Date());
LocalDateTime localDateTime = LocalDateTime.now().plusMinutes(-15);
List<Orders> ordersList = orderMapper.processSelect(Orders.PENDING_PAYMENT, localDateTime);
if (ordersList != null && !ordersList.isEmpty()) {
ordersList.forEach(orders -> {
orders.setStatus(Orders.CANCELLED);
orders.setCancelTime(LocalDateTime.now());
orders.setCancelReason("支付超时,取消订单");
orderMapper.update(orders);
});
}
}
WebSocket
WebSocket 是基于 TCP 的一种新的网络协议。它实现了浏览器与服务器全双工通信——浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接, 并进行双向数据传输。
HTTP协议和WebSocket协议对比:
- HTTP是短连接
- WebSocket是长连接
- HTTP通信是单向的,基于请求响应模式
- WebSocket支持双向通信
- HTTP和WebSocket底层都是TCP连接
WebSocket缺点:
服务器长期维护长连接需要一定的成本
各个浏览器支持程度不一
WebSocket 是长连接,受网络限制比较大,需要处理好重连
结论:WebSocket并不能完全取代HTTP,它只适合在特定的场景下使用
maven坐标
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
服务端组件
@Component
@ServerEndpoint(“/ws/{sid}”)
public class WebSocketServer {
//存放会话对象
private static Map<String, Session> sessionMap = new HashMap();
/**
* 连接建立成功调用的方法
*/
@OnOpen
public void onOpen(Session session, @PathParam("sid") String sid) {
System.out.println("客户端:" + sid + "建立连接");
sessionMap.put(sid, session);
}
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息
*/
@OnMessage
public void onMessage(String message, @PathParam("sid") String sid) {
System.out.println("收到来自客户端:" + sid + "的信息:" + message);
}
/**
* 连接关闭调用的方法
*
* @param sid
*/
@OnClose
public void onClose(@PathParam("sid") String sid) {
System.out.println("连接断开:" + sid);
sessionMap.remove(sid);
}
/**
* 群发
*
* @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();
}
}
}
配置类
/**
* WebSocket配置类,用于注册WebSocket的Bean
*/
@Configuration
public class WebSocketConfiguration {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
Apache POI
操作Excel文件
maven坐标
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.16</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.16</version>
</dependency>
相关条目
- javaweb
- Mybatis-plus
- Mysql
- redis