Java 项目实战: 外卖平台-数据库环境搭建与十一张表结构解析

数据库环境搭建与十一张表结构解析

纲要

本篇完成两件事:把 reggie 数据库建起来,并把 11 张表 的业务含义与字段设计讲透。

  • 建库 :图形界面与命令行两种方式,字符集 utf8mb4
  • 建表 :执行 数据模型/db_reggie.sql 脚本,图形界面导入与 source 命令两种方式
  • source 命令的坑SQL 文件路径不能包含中文,否则解析失败
  • 表清单employeecategorydishdish_flavorsetmealsetmeal_dishuseraddress_bookshopping_cartordersorder_detail
  • 三类表角色:主数据表、关系表、单据表
  • 字段设计约定bigint 主键(雪花 ID)、is_deleted 逻辑删除、create_time / update_time / create_user / update_user 公共字段

数据库环境搭建

创建数据库

两种方式任选其一。

图形界面方式 :连接本地 MySQL,右键新建数据库,库名 reggie,字符集指定 utf8mb4

命令行方式

sql 复制代码
-- 登录 MySQL
mysql -uroot -p

-- 查看已有库
SHOW DATABASES;

-- 创建数据库,字符集必须为 utf8mb4(支持 emoji 与四字节字符)
CREATE DATABASE reggie CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

-- 切换到目标库
USE reggie;

-- 查看当前库下的表
SHOW TABLES;

字符集选择 utf8mb4 而不是 utf8MySQLutf8 实际只支持最多三个字节,遇到 emoji 或部分生僻字会报 Incorrect string valueutf8mb4 才是完整的 UTF-8 实现。

导入表结构

项目资料提供了 项目课程/资料/数据模型/db_reggie.sql,其中是一组 CREATE TABLE 语句,并带有初始化 INSERT 数据。

脚本的建表语句统一采用「先删后建」的写法:

sql 复制代码
DROP TABLE IF EXISTS `employee`;
CREATE TABLE `employee` (
  ...
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='员工信息';

这样重复执行脚本不会产生「表已存在」错误,保证幂等。

图形界面导入 :选中 reggie 库 → 右键 → 运行 SQL 文件 → 选择 db_reggie.sql → 开始执行。

命令行导入

sql 复制代码
USE reggie;
SOURCE D:\db_reggie.sql;
SHOW TABLES;

source 命令的中文路径问题

这是一个高频踩坑点。source 命令在打开 SQL 文件时无法正确解析含中文的路径 ,会直接报 error

text 复制代码
mysql> source D:\课程资料\数据模型\db_reggie.sql
Failed to open file 'D:\课程资料\数据模型\db_reggie.sql', error: 2

解决办法:把 SQL 文件临时复制到不含中文 的目录(例如磁盘根目录),再执行 source。图形界面导入不受此限制。

十一张表总览

#mermaid-svg-vhj0QRy4TdeBqbf8{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-vhj0QRy4TdeBqbf8 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-vhj0QRy4TdeBqbf8 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-vhj0QRy4TdeBqbf8 .error-icon{fill:#552222;}#mermaid-svg-vhj0QRy4TdeBqbf8 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-vhj0QRy4TdeBqbf8 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-vhj0QRy4TdeBqbf8 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-vhj0QRy4TdeBqbf8 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-vhj0QRy4TdeBqbf8 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-vhj0QRy4TdeBqbf8 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-vhj0QRy4TdeBqbf8 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-vhj0QRy4TdeBqbf8 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-vhj0QRy4TdeBqbf8 .marker.cross{stroke:#333333;}#mermaid-svg-vhj0QRy4TdeBqbf8 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-vhj0QRy4TdeBqbf8 p{margin:0;}#mermaid-svg-vhj0QRy4TdeBqbf8 .entityBox{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-vhj0QRy4TdeBqbf8 .relationshipLabelBox{fill:hsl(80, 100%, 96.2745098039%);opacity:0.7;background-color:hsl(80, 100%, 96.2745098039%);}#mermaid-svg-vhj0QRy4TdeBqbf8 .relationshipLabelBox rect{opacity:0.5;}#mermaid-svg-vhj0QRy4TdeBqbf8 .labelBkg{background-color:rgba(248.6666666666, 255, 235.9999999999, 0.5);}#mermaid-svg-vhj0QRy4TdeBqbf8 .edgeLabel .label{fill:#9370DB;font-size:14px;}#mermaid-svg-vhj0QRy4TdeBqbf8 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-vhj0QRy4TdeBqbf8 .edge-pattern-dashed{stroke-dasharray:8,8;}#mermaid-svg-vhj0QRy4TdeBqbf8 .node rect,#mermaid-svg-vhj0QRy4TdeBqbf8 .node circle,#mermaid-svg-vhj0QRy4TdeBqbf8 .node ellipse,#mermaid-svg-vhj0QRy4TdeBqbf8 .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-vhj0QRy4TdeBqbf8 .relationshipLine{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-vhj0QRy4TdeBqbf8 .marker{fill:none!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-vhj0QRy4TdeBqbf8 .edgeLabel{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-vhj0QRy4TdeBqbf8 .edgeLabel .label rect{fill:rgba(232,232,232, 0.8);}#mermaid-svg-vhj0QRy4TdeBqbf8 .edgeLabel .label text{fill:#333;}#mermaid-svg-vhj0QRy4TdeBqbf8 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 维护
分类
分类
拥有口味
包含
被包含
拥有地址
加购
下单
明细
收货地址
EMPLOYEE
CATEGORY
DISH
SETMEAL
DISH_FLAVOR
SETMEAL_DISH
USER
ADDRESS_BOOK
SHOPPING_CART
ORDERS
ORDER_DETAIL

表名 业务含义 归属端 表类型
employee 员工信息(后台登录账号) 管理端 主数据
category 菜品与套餐分类 管理端 主数据
dish 菜品 管理端 主数据
dish_flavor 菜品口味(规格) 管理端 关系表
setmeal 套餐 管理端 主数据
setmeal_dish 套餐与菜品关系 管理端 关系表
user C 端用户 移动端 主数据
address_book 地址簿 移动端 主数据
shopping_cart 购物车 移动端 单据表
orders 订单 双端 单据表
order_detail 订单明细 双端 单据表

按业务域再归类

#mermaid-svg-ztsAQ7seNM9eVIRW{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ztsAQ7seNM9eVIRW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ztsAQ7seNM9eVIRW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ztsAQ7seNM9eVIRW .error-icon{fill:#552222;}#mermaid-svg-ztsAQ7seNM9eVIRW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ztsAQ7seNM9eVIRW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ztsAQ7seNM9eVIRW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ztsAQ7seNM9eVIRW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ztsAQ7seNM9eVIRW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ztsAQ7seNM9eVIRW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ztsAQ7seNM9eVIRW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ztsAQ7seNM9eVIRW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ztsAQ7seNM9eVIRW .marker.cross{stroke:#333333;}#mermaid-svg-ztsAQ7seNM9eVIRW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ztsAQ7seNM9eVIRW p{margin:0;}#mermaid-svg-ztsAQ7seNM9eVIRW .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ztsAQ7seNM9eVIRW .cluster-label text{fill:#333;}#mermaid-svg-ztsAQ7seNM9eVIRW .cluster-label span{color:#333;}#mermaid-svg-ztsAQ7seNM9eVIRW .cluster-label span p{background-color:transparent;}#mermaid-svg-ztsAQ7seNM9eVIRW .label text,#mermaid-svg-ztsAQ7seNM9eVIRW span{fill:#333;color:#333;}#mermaid-svg-ztsAQ7seNM9eVIRW .node rect,#mermaid-svg-ztsAQ7seNM9eVIRW .node circle,#mermaid-svg-ztsAQ7seNM9eVIRW .node ellipse,#mermaid-svg-ztsAQ7seNM9eVIRW .node polygon,#mermaid-svg-ztsAQ7seNM9eVIRW .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ztsAQ7seNM9eVIRW .rough-node .label text,#mermaid-svg-ztsAQ7seNM9eVIRW .node .label text,#mermaid-svg-ztsAQ7seNM9eVIRW .image-shape .label,#mermaid-svg-ztsAQ7seNM9eVIRW .icon-shape .label{text-anchor:middle;}#mermaid-svg-ztsAQ7seNM9eVIRW .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ztsAQ7seNM9eVIRW .rough-node .label,#mermaid-svg-ztsAQ7seNM9eVIRW .node .label,#mermaid-svg-ztsAQ7seNM9eVIRW .image-shape .label,#mermaid-svg-ztsAQ7seNM9eVIRW .icon-shape .label{text-align:center;}#mermaid-svg-ztsAQ7seNM9eVIRW .node.clickable{cursor:pointer;}#mermaid-svg-ztsAQ7seNM9eVIRW .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ztsAQ7seNM9eVIRW .arrowheadPath{fill:#333333;}#mermaid-svg-ztsAQ7seNM9eVIRW .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ztsAQ7seNM9eVIRW .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ztsAQ7seNM9eVIRW .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ztsAQ7seNM9eVIRW .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ztsAQ7seNM9eVIRW .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ztsAQ7seNM9eVIRW .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ztsAQ7seNM9eVIRW .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ztsAQ7seNM9eVIRW .cluster text{fill:#333;}#mermaid-svg-ztsAQ7seNM9eVIRW .cluster span{color:#333;}#mermaid-svg-ztsAQ7seNM9eVIRW div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ztsAQ7seNM9eVIRW .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ztsAQ7seNM9eVIRW rect.text{fill:none;stroke-width:0;}#mermaid-svg-ztsAQ7seNM9eVIRW .icon-shape,#mermaid-svg-ztsAQ7seNM9eVIRW .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ztsAQ7seNM9eVIRW .icon-shape p,#mermaid-svg-ztsAQ7seNM9eVIRW .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ztsAQ7seNM9eVIRW .icon-shape .label rect,#mermaid-svg-ztsAQ7seNM9eVIRW .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ztsAQ7seNM9eVIRW .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ztsAQ7seNM9eVIRW .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ztsAQ7seNM9eVIRW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 后台管理域
employee
用户域(移动端)
user
address_book
shopping_cart
orders
order_detail
菜品域(管理端)
category
dish
dish_flavor
setmeal
setmeal_dish

表结构逐张解析

employee 员工表

管理端的登录账号表。后台登录页输入的用户名与密码,就来自这张表。

sql 复制代码
CREATE TABLE `employee` (
  `id`         bigint(20)  NOT NULL COMMENT '主键',
  `name`       varchar(32) NOT NULL COMMENT '姓名',
  `username`   varchar(32) NOT NULL COMMENT '用户名',
  `password`   varchar(64) NOT NULL COMMENT '密码',
  `phone`      varchar(11) NOT NULL COMMENT '手机号',
  `sex`        varchar(2)  NOT NULL COMMENT '性别',
  `id_number`  varchar(18) NOT NULL COMMENT '身份证号',
  `status`     int(11)     NOT NULL DEFAULT '1' COMMENT '状态 0:禁用,1:正常',
  `create_time` datetime   NOT NULL COMMENT '创建时间',
  `update_time` datetime   NOT NULL COMMENT '更新时间',
  `create_user` bigint(20) NOT NULL COMMENT '创建人',
  `update_user` bigint(20) NOT NULL COMMENT '修改人',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `idx_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='员工信息';

设计要点:

  • username 上有唯一索引 idx_username,这是新增员工时重复账号报错 Duplicate entry 的根源,后续由全局异常处理器捕获
  • status 字段支撑「启用 / 禁用员工账号」功能:禁用后员工无法登录,但记录不删除
  • idbigint,对应 JavaLong;若实体误用 Integer,雪花 ID 会溢出

初始化数据中包含默认管理员账号(admin),密码经 MD5 加密存储。

category 分类表

菜品分类与套餐分类共用一张表,靠 type 字段区分。

sql 复制代码
CREATE TABLE `category` (
  `id`          bigint(20)  NOT NULL COMMENT '主键',
  `type`        int(11)     DEFAULT NULL COMMENT '类型 1 菜品分类 2 套餐分类',
  `name`        varchar(64) NOT NULL COMMENT '分类名称',
  `sort`        int(11)     NOT NULL DEFAULT '0' COMMENT '顺序',
  `create_time` datetime    NOT NULL COMMENT '创建时间',
  `update_time` datetime    NOT NULL COMMENT '更新时间',
  `create_user` bigint(20)  NOT NULL COMMENT '创建人',
  `update_user` bigint(20)  NOT NULL COMMENT '修改人',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `idx_category_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='菜品及套餐分类';
type 含义 示例数据
1 菜品分类 湘菜、川菜、粤菜、饮品、主食
2 套餐分类 商务套餐、儿童套餐

name 唯一索引意味着分类名不可重复,新增重名分类会触发唯一约束异常。

dish 菜品表

sql 复制代码
CREATE TABLE `dish` (
  `id`          bigint(20)   NOT NULL COMMENT '主键',
  `name`        varchar(64)  NOT NULL COMMENT '菜品名称',
  `category_id` bigint(20)   NOT NULL COMMENT '菜品分类id',
  `price`       decimal(10,2) DEFAULT NULL COMMENT '菜品价格',
  `code`        varchar(64)  NOT NULL COMMENT '商品码',
  `image`       varchar(200) NOT NULL COMMENT '图片',
  `description` varchar(400) DEFAULT NULL COMMENT '描述信息',
  `status`      int(11)      NOT NULL DEFAULT '1' COMMENT '0 停售 1 起售',
  `sort`        int(11)      NOT NULL DEFAULT '0' COMMENT '顺序',
  `create_time` datetime     NOT NULL COMMENT '创建时间',
  `update_time` datetime     NOT NULL COMMENT '更新时间',
  `create_user` bigint(20)   NOT NULL COMMENT '创建人',
  `update_user` bigint(20)   NOT NULL COMMENT '修改人',
  `is_deleted`  int(11)      NOT NULL DEFAULT '0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `idx_dish_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='菜品管理';
  • price 使用 decimal(10,2) 而非 float/double,避免浮点精度丢失;Java 侧对应 BigDecimal
  • image 存的是文件名 (如 f966a38e-0780-40be-bb52-5699d13cb3d9.jpg),不是完整路径,实际路径由 reggie.path 配置项拼接
  • status 控制起售 / 停售;is_deleted 控制逻辑删除。两者独立:停售的菜品仍可被管理端看到,删除的菜品不再出现

dish_flavor 菜品口味表

sql 复制代码
CREATE TABLE `dish_flavor` (
  `id`          bigint(20)   NOT NULL COMMENT '主键',
  `dish_id`     bigint(20)   NOT NULL COMMENT '菜品',
  `name`        varchar(64)  NOT NULL COMMENT '口味名称',
  `value`       varchar(500) DEFAULT NULL COMMENT '口味数据list',
  `create_time` datetime     NOT NULL COMMENT '创建时间',
  `update_time` datetime     NOT NULL COMMENT '更新时间',
  `create_user` bigint(20)   NOT NULL COMMENT '创建人',
  `update_user` bigint(20)   NOT NULL COMMENT '修改人',
  `is_deleted`  int(11)      NOT NULL DEFAULT '0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='菜品口味关系表';

关键设计:value 字段存的是**JSON 数组字符串**,一行代表一个口味维度,维度下的多个选项以 JSON 数组形式塞进一个字段:

text 复制代码
name = '辣度',  value = '["不辣","微辣","中辣","重辣"]'
name = '忌口',  value = '["不要葱","不要蒜","不要香菜","不要辣"]'
name = '温度',  value = '["热饮","常温","去冰","少冰","多冰"]'
name = '甜味',  value = '["无糖","少糖","半糖","多糖","全糖"]'

Java 侧该字段映射为 List<String>,需要自定义类型处理器或直接使用 String 接收后手工解析。这种设计牺牲了查询能力(无法按单个口味选项检索),换取了写入的简洁性 ------ 一个菜品的所有口味维度只需要 N 行(N = 维度数)。

setmeal 套餐表

sql 复制代码
CREATE TABLE `setmeal` (
  `id`          bigint(20)    NOT NULL COMMENT '主键',
  `category_id` bigint(20)    NOT NULL COMMENT '菜品分类id',
  `name`        varchar(64)   NOT NULL COMMENT '套餐名称',
  `price`       decimal(10,2) NOT NULL COMMENT '套餐价格',
  `status`      int(11)       DEFAULT NULL COMMENT '状态 0:停用 1:启用',
  `code`        varchar(32)   DEFAULT NULL COMMENT '编码',
  `description` varchar(512)  DEFAULT NULL COMMENT '描述信息',
  `image`       varchar(255)  DEFAULT NULL COMMENT '图片',
  `create_time` datetime      NOT NULL COMMENT '创建时间',
  `update_time` datetime      NOT NULL COMMENT '更新时间',
  `create_user` bigint(20)    NOT NULL COMMENT '创建人',
  `update_user` bigint(20)    NOT NULL COMMENT '修改人',
  `is_deleted`  int(11)       NOT NULL DEFAULT '0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `idx_setmeal_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='套餐';

套餐本质是「若干菜品的集合 + 一个独立价格」。套餐价格与其中菜品价格之和没有强制一致约束,由运营自行定价。

setmeal_dish 套餐菜品关系表

sql 复制代码
CREATE TABLE `setmeal_dish` (
  `id`          bigint(20)    NOT NULL COMMENT '主键',
  `setmeal_id`  varchar(32)   NOT NULL COMMENT '套餐id',
  `dish_id`     varchar(32)   NOT NULL COMMENT '菜品id',
  `name`        varchar(32)   DEFAULT NULL COMMENT '菜品名称(冗余字段)',
  `price`       decimal(10,2) DEFAULT NULL COMMENT '菜品原价(冗余字段)',
  `copies`      int(11)       NOT NULL COMMENT '份数',
  `sort`        int(11)       NOT NULL DEFAULT '0' COMMENT '排序',
  `create_time` datetime      NOT NULL COMMENT '创建时间',
  `update_time` datetime      NOT NULL COMMENT '更新时间',
  `create_user` bigint(20)    NOT NULL COMMENT '创建人',
  `update_user` bigint(20)    NOT NULL COMMENT '修改人',
  `is_deleted`  int(11)       NOT NULL DEFAULT '0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='套餐菜品关系';

nameprice冗余字段 。冗余的收益:展示套餐内菜品列表时无需再 JOIN dish 表;代价:菜品改价后关系表里的历史价格不会同步,展示的是加入套餐时的快照价格 ------ 这在订单类业务中恰恰是期望行为。

copies 表示这个菜品在套餐里有几份。

user 用户表

sql 复制代码
CREATE TABLE `user` (
  `id`         bigint(20)   NOT NULL COMMENT '主键',
  `name`       varchar(50)  DEFAULT NULL COMMENT '姓名',
  `phone`      varchar(100) NOT NULL COMMENT '手机号',
  `sex`        varchar(2)   DEFAULT NULL COMMENT '性别',
  `id_number`  varchar(18)  DEFAULT NULL COMMENT '身份证号',
  `avatar`     varchar(500) DEFAULT NULL COMMENT '头像',
  `status`     int(11)      DEFAULT '0' COMMENT '状态 0:禁用,1:正常',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='用户信息';

这是 C 端用户表,与 employee 完全独立 ------ 两套账号体系,两套登录逻辑。phone 是移动端登录的唯一凭证,验证码登录后若手机号不存在则自动注册。

注意该表没有 create_time / update_time / is_deleted 字段,是 11 张表里结构最简单的一张。

address_book 地址簿表

sql 复制代码
CREATE TABLE `address_book` (
  `id`             bigint(20)   NOT NULL COMMENT '主键',
  `user_id`        bigint(20)   NOT NULL COMMENT '用户id',
  `consignee`      varchar(50)  NOT NULL COMMENT '收货人',
  `sex`            tinyint(4)   NOT NULL COMMENT '性别 0 女 1 男',
  `phone`          varchar(11)  NOT NULL COMMENT '手机号',
  `province_code`  varchar(12)  DEFAULT NULL COMMENT '省级区划编号',
  `province_name`  varchar(32)  DEFAULT NULL COMMENT '省级名称',
  `city_code`      varchar(12)  DEFAULT NULL COMMENT '市级区划编号',
  `city_name`      varchar(32)  DEFAULT NULL COMMENT '市级名称',
  `district_code`  varchar(12)  DEFAULT NULL COMMENT '区级区划编号',
  `district_name`  varchar(32)  DEFAULT NULL COMMENT '区级名称',
  `detail`         varchar(200) DEFAULT NULL COMMENT '详细地址',
  `label`          varchar(100) DEFAULT NULL COMMENT '标签',
  `is_default`     tinyint(1)   NOT NULL DEFAULT '0' COMMENT '默认 0 否 1是',
  `create_time`    datetime     NOT NULL COMMENT '创建时间',
  `update_time`    datetime     NOT NULL COMMENT '更新时间',
  `create_user`    bigint(20)   NOT NULL COMMENT '创建人',
  `update_user`    bigint(20)   NOT NULL COMMENT '修改人',
  `is_deleted`     int(11)      NOT NULL DEFAULT '0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='地址管理';
  • 省市区采用编号 + 名称双存:编号用于后续对接物流系统,名称用于直接展示,避免联查行政区划表
  • label 存「公司」「家」这类标签
  • is_default 标记默认地址。设置默认地址的业务规则是:先将该用户所有地址置为非默认,再把目标地址置为默认 ------ 这是一个需要两条 SQL 的更新操作

shopping_cart 购物车表

sql 复制代码
CREATE TABLE `shopping_cart` (
  `id`           bigint(20)    NOT NULL COMMENT '主键',
  `name`         varchar(50)   DEFAULT NULL COMMENT '名称',
  `image`        varchar(100)  DEFAULT NULL COMMENT '图片',
  `user_id`      bigint(20)    NOT NULL COMMENT '主键',
  `dish_id`      bigint(20)    DEFAULT NULL COMMENT '菜品id',
  `setmeal_id`   bigint(20)    DEFAULT NULL COMMENT '套餐id',
  `dish_flavor`  varchar(50)   DEFAULT NULL COMMENT '口味',
  `number`       int(11)       NOT NULL DEFAULT '1' COMMENT '数量',
  `amount`       decimal(10,2) NOT NULL COMMENT '金额',
  `create_time`  datetime      DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='购物车';

dish_idsetmeal_id 互斥 :加购菜品时 setmeal_idNULL,加购套餐时 dish_idNULL。这个约定决定了「添加购物车」的逻辑必须先判断是菜品还是套餐。

amount单项小计(单价 × 数量),不是单价。

dish_flavor 存用户选中的口味字符串,且它参与「同一菜品是否算作同一购物项」的判定 ------ 辣度不同应当视为两条购物车记录。

orders 订单表

sql 复制代码
CREATE TABLE `orders` (
  `id`               bigint(20)    NOT NULL COMMENT '主键',
  `number`           varchar(50)   DEFAULT NULL COMMENT '订单号',
  `status`           int(11)       NOT NULL DEFAULT '1' COMMENT '订单状态 1待付款,2待派送,3已派送,4已完成,5已取消',
  `user_id`          bigint(20)    NOT NULL COMMENT '下单用户',
  `address_book_id`  bigint(20)    NOT NULL COMMENT '地址id',
  `order_time`       datetime      NOT NULL COMMENT '下单时间',
  `checkout_time`    datetime      NOT NULL COMMENT '结账时间',
  `pay_method`       int(11)       NOT NULL DEFAULT '1' COMMENT '支付方式 1微信,2支付宝',
  `amount`           decimal(10,2) NOT NULL COMMENT '实收金额',
  `remark`           varchar(100)  DEFAULT NULL COMMENT '备注',
  `phone`            varchar(255)  DEFAULT NULL,
  `address`          varchar(255)  DEFAULT NULL,
  `user_name`        varchar(255)  DEFAULT NULL,
  `consignee`        varchar(255)  DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='订单表';

表名是 orders 而不是 order ------ 因为 orderSQL 保留字,用它做表名每次都要加反引号,极易出错。这是数据库命名的通用实践。

phone / address / consignee 属于冗余快照:下单瞬间把地址簿里的信息抄一份到订单里。这样即使后续用户修改了地址簿,历史订单的配送信息也不会变。

订单状态机:
#mermaid-svg-dgYmr32kWA7wOesq{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-dgYmr32kWA7wOesq .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-dgYmr32kWA7wOesq .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-dgYmr32kWA7wOesq .error-icon{fill:#552222;}#mermaid-svg-dgYmr32kWA7wOesq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-dgYmr32kWA7wOesq .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-dgYmr32kWA7wOesq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-dgYmr32kWA7wOesq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-dgYmr32kWA7wOesq .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-dgYmr32kWA7wOesq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-dgYmr32kWA7wOesq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-dgYmr32kWA7wOesq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-dgYmr32kWA7wOesq .marker.cross{stroke:#333333;}#mermaid-svg-dgYmr32kWA7wOesq svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-dgYmr32kWA7wOesq p{margin:0;}#mermaid-svg-dgYmr32kWA7wOesq defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-dgYmr32kWA7wOesq g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-dgYmr32kWA7wOesq g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-dgYmr32kWA7wOesq g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-dgYmr32kWA7wOesq g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-dgYmr32kWA7wOesq g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-dgYmr32kWA7wOesq .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-dgYmr32kWA7wOesq .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-dgYmr32kWA7wOesq .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-dgYmr32kWA7wOesq .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-dgYmr32kWA7wOesq .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-dgYmr32kWA7wOesq .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-dgYmr32kWA7wOesq .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-dgYmr32kWA7wOesq .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dgYmr32kWA7wOesq .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-dgYmr32kWA7wOesq .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dgYmr32kWA7wOesq .edgeLabel .label text{fill:#333;}#mermaid-svg-dgYmr32kWA7wOesq .label div .edgeLabel{color:#333;}#mermaid-svg-dgYmr32kWA7wOesq .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-dgYmr32kWA7wOesq .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-dgYmr32kWA7wOesq .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-dgYmr32kWA7wOesq .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-dgYmr32kWA7wOesq .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-dgYmr32kWA7wOesq .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-dgYmr32kWA7wOesq .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-dgYmr32kWA7wOesq #statediagram-barbEnd{fill:#333333;}#mermaid-svg-dgYmr32kWA7wOesq .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-dgYmr32kWA7wOesq .cluster-label,#mermaid-svg-dgYmr32kWA7wOesq .nodeLabel{color:#131300;}#mermaid-svg-dgYmr32kWA7wOesq .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-dgYmr32kWA7wOesq .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-dgYmr32kWA7wOesq .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-dgYmr32kWA7wOesq .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-dgYmr32kWA7wOesq .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-dgYmr32kWA7wOesq .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-dgYmr32kWA7wOesq .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-dgYmr32kWA7wOesq .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-dgYmr32kWA7wOesq .note-edge{stroke-dasharray:5;}#mermaid-svg-dgYmr32kWA7wOesq .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-dgYmr32kWA7wOesq .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-dgYmr32kWA7wOesq .statediagram-note text{fill:black;}#mermaid-svg-dgYmr32kWA7wOesq .statediagram-note .nodeLabel{color:black;}#mermaid-svg-dgYmr32kWA7wOesq .statediagram .edgeLabel{color:red;}#mermaid-svg-dgYmr32kWA7wOesq #dependencyStart,#mermaid-svg-dgYmr32kWA7wOesq #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-dgYmr32kWA7wOesq .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-dgYmr32kWA7wOesq :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 1 用户提交订单
2 支付完成
3 商家发货
4 用户确认
5 超时或主动取消
待付款
待派送
已派送
已完成
已取消

order_detail 订单明细表

sql 复制代码
CREATE TABLE `order_detail` (
  `id`           bigint(20)    NOT NULL COMMENT '主键',
  `name`         varchar(50)   DEFAULT NULL COMMENT '名字',
  `image`        varchar(100)  DEFAULT NULL COMMENT '图片',
  `order_id`     bigint(20)    NOT NULL COMMENT '订单id',
  `dish_id`      bigint(20)    DEFAULT NULL COMMENT '菜品id',
  `setmeal_id`   bigint(20)    DEFAULT NULL COMMENT '套餐id',
  `dish_flavor`  varchar(50)   DEFAULT NULL COMMENT '口味',
  `number`       int(11)       NOT NULL DEFAULT '1' COMMENT '数量',
  `amount`       decimal(10,2) NOT NULL COMMENT '金额',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='订单明细';

结构与 shopping_cart 高度相似 ------ 这不是巧合:下单时的数据来源是购物车,明细是购物车项的持久化快照。区别在于多了一个 order_id 外键指向 orders

一次下单会写入 1 条 orders + N 条 order_detail ,必须包在同一个事务里,这正是启动类上加 @EnableTransactionManagement 的原因。

字段设计中的三条通用约定

主键统一 bigint + 雪花算法

所有表主键为 bigint(20),由 MyBatis-PlusASSIGN_ID 策略用雪花算法生成。Java 实体必须用 Long 。这条约定有一个直接后果:LongIDJavaScript 中超过 2^53 会精度丢失,因此必须配置消息转换器把 Long 序列化为字符串 ------ 这是后续 JacksonObjectMapper 存在的原因。

is_deleted 逻辑删除

dishsetmealcategorydish_flavorsetmeal_dishaddress_book 六张表带 is_deleted 字段。业务上的「删除」实际是 UPDATE ... SET is_deleted = 1,数据仍在库中,可恢复、可审计。

四个公共字段

create_time / update_time / create_user / update_user 在多张表中重复出现。如果每张表的每次插入更新都要手工 set,会产生大量重复代码 ------ 这直接催生了后续「公共字段自动填充」章节的 MyMetaObjectHandler

完整可运行代码

建库与导入脚本

sql 复制代码
-- ============ 步骤一:创建数据库 ============
CREATE DATABASE IF NOT EXISTS reggie
  DEFAULT CHARACTER SET utf8mb4
  DEFAULT COLLATE utf8mb4_general_ci;

USE reggie;

-- ============ 步骤二:执行 db_reggie.sql 导入 11 张表 ============
-- 命令行方式(注意路径不要含中文):
-- SOURCE D:/db_reggie.sql;

-- ============ 步骤三:校验 ============
SHOW TABLES;

-- 预期输出 11 张表:
-- address_book / category / dish / dish_flavor / employee
-- order_detail / orders / setmeal / setmeal_dish / shopping_cart / user

-- ============ 步骤四:确认初始化数据 ============
SELECT id, name, type, sort FROM category ORDER BY type, sort;
SELECT id, username, name, status FROM employee;

与表对应的实体类

EmployeeDishFlavor 为例,展示字段到属性的映射规则。

src/main/java/com/itheima/reggie/entity/Employee.java

java 复制代码
package com.itheima.reggie.entity;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * 员工实体,对应 employee 表
 * 表名 employee 与类名 Employee 通过驼峰 + 首字母规则映射
 * 字段 id_number 与属性 idNumber 通过 map-underscore-to-camel-case 映射
 */
@Data
@TableName("employee")
public class Employee implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId
    private Long id;

    private String name;

    private String username;

    private String password;

    private String phone;

    private String sex;

    private String idNumber;

    /** 状态:0 禁用,1 正常 */
    private Integer status;

    /** 插入时自动填充 */
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    /** 插入与更新时均自动填充 */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;

    @TableField(fill = FieldFill.INSERT)
    private Long createUser;

    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updateUser;
}

src/main/java/com/itheima/reggie/entity/DishFlavor.java

java 复制代码
package com.itheima.reggie.entity;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * 菜品口味实体,对应 dish_flavor 表
 * value 字段在库中以 JSON 数组字符串形式存储,如 ["不辣","微辣"]
 */
@Data
@TableName("dish_flavor")
public class DishFlavor implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId
    private Long id;

    /** 所属菜品 id */
    private Long dishId;

    /** 口味维度名称,如 辣度 / 忌口 / 温度 / 甜味 */
    private String name;

    /** 口味选项,JSON 数组字符串 */
    @TableField(value = "`value`")
    private String value;

    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;

    @TableField(fill = FieldFill.INSERT)
    private Long createUser;

    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updateUser;

    /** 逻辑删除标记,0 未删除,1 已删除 */
    private Integer isDeleted;
}

注意 value 字段用 @TableField(value = "`value`") 显式加了反引号 ------ value 在部分 SQL 方言中是关键字,加反引号可以避免 SQL 拼接失败。

持久层接口

src/main/java/com/itheima/reggie/mapper/EmployeeMapper.java

java 复制代码
package com.itheima.reggie.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.reggie.entity.Employee;
import org.apache.ibatis.annotations.Mapper;

/**
 * 继承 BaseMapper 后即拥有 insert / deleteById / updateById / selectById / selectList 等基础方法
 */
@Mapper
public interface EmployeeMapper extends BaseMapper<Employee> {
}

src/main/java/com/itheima/reggie/mapper/DishFlavorMapper.java

java 复制代码
package com.itheima.reggie.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.reggie.entity.DishFlavor;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface DishFlavorMapper extends BaseMapper<DishFlavor> {
}

验证环境可用的最小测试

java 复制代码
package com.itheima.reggie;

import com.itheima.reggie.entity.Employee;
import com.itheima.reggie.mapper.EmployeeMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
public class ReggieApplicationTests {

    @Autowired
    private EmployeeMapper employeeMapper;

    /**
     * 验证数据库环境:能查到初始化数据即说明建库建表与数据源配置均正确
     */
    @Test
    public void testDataSource() {
        List<Employee> employees = employeeMapper.selectList(null);
        employees.forEach(e -> System.out.println(e.getUsername() + " -> " + e.getName()));
    }
}

API 速览

API / 注解 所属 作用
CREATE DATABASE ... CHARACTER SET utf8mb4 MySQL 创建支持四字节字符的库
SOURCE <file> MySQL 客户端 执行 SQL 脚本文件,路径不可含中文
SHOW TABLES MySQL 列出当前库所有表
@TableName MyBatis-Plus 建立实体类与表名的映射
@TableId MyBatis-Plus 标识主键字段,配合全局 id-type 生成 ID
@TableField MyBatis-Plus 字段名映射与填充策略声明
@TableField(fill = FieldFill.INSERT) MyBatis-Plus 插入时自动填充
@TableField(fill = FieldFill.INSERT_UPDATE) MyBatis-Plus 插入与更新时均自动填充
BaseMapper<T> MyBatis-Plus 提供单表 CRUD 基础方法
@Mapper MyBatis 将接口注册为 MyBatis 映射器
@Data Lombok 生成 getter / setter / toString
@SpringBootTest Spring Boot 加载完整容器用于集成测试

官方文档

总结

本篇围绕 reggie 数据库的落地,完成了从建库、导表到逐张解析的完整闭环,核心收获可以归纳为四点:

  • 环境搭建 :字符集必须选 utf8mb4 才能完整支持 emoji 与生僻字;source 命令无法解析含中文的路径,遇到报错先把 SQL 文件复制到纯英文目录再执行。
  • 表结构设计 :11 张表按业务可归为三类 ------ 主数据表(employeecategorydishsetmealuseraddress_book)、关系表(dish_flavorsetmeal_dish)与单据表(shopping_cartordersorder_detail)。关系表与单据表大量使用冗余快照字段,用空间换查询性能,这在订单类业务中是刻意为之。
  • 通用约定 :主键统一 bigint + 雪花算法(Java 侧必须用 Long,且要配置序列化器避免前端精度丢失);is_deleted 逻辑删除保证数据可恢复;create_time / update_time / create_user / update_user 四个公共字段催生了后续的自动填充处理器。
  • 代码落地 :实体类通过 @TableName@TableId@TableField 与表结构一一对应,BaseMapper 直接提供单表 CRUD,配合一个最小测试即可验证整条数据链路是否打通。

至此,数据库层已经就绪。下一步建议进入「公共字段自动填充」与「登录鉴权」章节,把 MyMetaObjectHandlerJWT 拦截器串起来,让后台管理端真正跑起来。

相关推荐
计算机毕设定制辅导-无忧学长5 小时前
《基于SpringBoot的未成年人健康知识科普平台的设计与实现》
java·开发语言·vue.js·spring boot·未成年人健康知识科普平台
王六米。5 小时前
RAG知识库建设 文档解析 切分与索引如何衔接
服务器·前端·网络·企业数字化转型·智钳智能盒子
白远山5 小时前
上海24小时自助健身房系统软件开发实战指南
java·架构·uni-app·需求分析
泡海椒5 小时前
jquick-pdf 表格实战:动态数据 PDF 报表生成
java·开发语言·pdf
OxYGC6 小时前
[AI工程] Spring AI 第十四篇:Agent 五种模式在 2.0 里怎么写
java·spring·ai·ai编程
code2cat6 小时前
【随笔】从聊天到调用工具:理解MCP在AI应用中的位置
java·人工智能
2501_933923256 小时前
Spring Bean作用域揭秘:单例、原型、请求与会话的区别
java·后端·spring·java-ee
wuyk5556 小时前
【Socket 进阶之路】第 7 章 IO 多路复用 select & poll 完整实战|多 fd 监听、超时等待、优缺点横向对比
服务器·开发语言·网络·数据库·物联网
学编程就要猛6 小时前
基于Spring AI 的智能聊天机器人
java·spring ai·chat robot