在数字化转型浪潮下,餐饮行业的线上化运营已从“可选项”变为“必选项”。本文将以一套成熟的“Vue + SpringBoot 餐饮点餐小程序完整源码包”为切入点,深度解析后台基于RBAC的权限模型设计,以及小程序端与后端服务的高效通信机制,为开发者提供从架构设计到代码落地的全链路参考。

项目整体架构与技术选型

架构概览

源码与演示:c.ymzan.top

该点餐小程序采用“前后端分离”的经典架构,整体分为三层:前端展示层(微信小程序)、后端服务层(SpringBoot微服务)、数据存储层(MySQL + Redis)。各层通过标准化接口通信,职责边界清晰,便于独立迭代与扩展。

  • 前端:基于微信小程序原生框架开发,配合WeUI组件库实现统一UI风格,核心模块包括菜单浏览、购物车、订单提交、支付回调、个人中心等。
  • 后端:采用SpringBoot 2.7.x构建RESTful API服务,集成MyBatis-Plus简化数据访问,Shiro/Spring Security实现权限控制,Redis缓存热点数据(如菜品信息、用户会话),RabbitMQ处理异步任务(如订单超时取消)。
  • 数据库:MySQL 8.0存储结构化数据,核心表包括用户表(t_user)、角色表(t_role)、权限表(t_permission)、菜品表(t_dish)、订单表(t_order)等;Redis用于缓存用户Token、临时订单数据,提升系统响应速度。

技术选型依据

  • Vue生态:虽小程序前端未直接使用Vue.js,但其组件化思想与数据绑定机制与Vue高度相似,且配套的管理后台(商家端)基于Vue3 + Element Plus开发,实现“小程序-管理后台”技术栈统一,降低维护成本。
  • SpringBoot:约定大于配置的特性大幅简化后端搭建流程,内置Tomcat容器、自动配置机制与Starter依赖管理,适合快速迭代餐饮业务场景。
  • RBAC权限模型:餐饮场景涉及多角色协作(如顾客、商家管理员、后厨、服务员),RBAC通过“用户-角色-权限”三层映射,灵活支持权限动态分配,避免过度授权风险。

后台RBAC权限模型深度解析

RBAC核心设计思想

RBAC模型的核心是“权限与角色关联,用户通过角色间接获取权限”,相比直接给用户分配权限的ACL(Access Control List)模型,RBAC更适用于多角色、权限复杂的系统。本项目中,RBAC模型包含五个核心要素:用户(User)、角色(Role)、权限(Permission)、用户角色关联(User-Role)、角色权限关联(Role-Permission)

数据库表结构设计

为实现RBAC,后台设计了5张核心表,结构如下:

(1)用户表(t_user

存储系统用户信息,包括小程序端顾客与后台管理用户。

CREATE TABLE `t_user` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
  `username` varchar(50) NOT NULL COMMENT '用户名(小程序openid/后台登录名)',
  `password` varchar(100) DEFAULT NULL COMMENT '密码(后台用户加密存储)',
  `nick_name` varchar(50) DEFAULT NULL COMMENT '昵称',
  `phone` varchar(20) DEFAULT NULL COMMENT '手机号',
  `status` tinyint DEFAULT '1' COMMENT '状态:0-禁用,1-启用',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB COMMENT='用户表';
(2)角色表(t_role

定义系统角色,如“顾客”“商家管理员”“后厨人员”“服务员”。

CREATE TABLE `t_role` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '角色ID',
  `role_name` varchar(50) NOT NULL COMMENT '角色名称',
  `role_code` varchar(50) NOT NULL COMMENT '角色编码(如ROLE_CUSTOMER、ROLE_ADMIN)',
  `description` varchar(200) DEFAULT NULL COMMENT '角色描述',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_role_code` (`role_code`)
) ENGINE=InnoDB COMMENT='角色表';
(3)权限表(t_permission

定义系统操作权限,粒度细化到“接口级”,如“查询菜品”“修改订单状态”“导出销售报表”。

CREATE TABLE `t_permission` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '权限ID',
  `perm_name` varchar(100) NOT NULL COMMENT '权限名称',
  `perm_code` varchar(100) NOT NULL COMMENT '权限编码(如dish:query、order:update)',
  `url` varchar(200) DEFAULT NULL COMMENT '对应接口URL(如/api/dish/list)',
  `method` varchar(10) DEFAULT NULL COMMENT '请求方法(GET/POST/PUT/DELETE)',
  `description` varchar(200) DEFAULT NULL COMMENT '权限描述',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_perm_code` (`perm_code`)
) ENGINE=InnoDB COMMENT='权限表';
(4)用户角色关联表(t_user_role

实现用户与角色的多对多关联(一个用户可拥有多个角色,一个角色可分配给多个用户)。

CREATE TABLE `t_user_role` (
  `user_id` bigint NOT NULL COMMENT '用户ID',
  `role_id` bigint NOT NULL COMMENT '角色ID',
  PRIMARY KEY (`user_id`, `role_id`),
  CONSTRAINT `fk_user_role_user` FOREIGN KEY (`user_id`) REFERENCES `t_user` (`id`) ON DELETE CASCADE,
  CONSTRAINT `fk_user_role_role` FOREIGN KEY (`role_id`) REFERENCES `t_role` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB COMMENT='用户角色关联表';
(5)角色权限关联表(t_role_permission

实现角色与权限的多对多关联(一个角色可拥有多个权限,一个权限可分配给多个角色)。

CREATE TABLE `t_role_permission` (
  `role_id` bigint NOT NULL COMMENT '角色ID',
  `permission_id` bigint NOT NULL COMMENT '权限ID',
  PRIMARY KEY (`role_id`, `permission_id`),
  CONSTRAINT `fk_role_perm_role` FOREIGN KEY (`role_id`) REFERENCES `t_role` (`id`) ON DELETE CASCADE,
  CONSTRAINT `fk_role_perm_perm` FOREIGN KEY (`permission_id`) REFERENCES `t_permission` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB COMMENT='角色权限关联表';

权限校验流程实现

后台基于Spring Security实现RBAC权限校验,核心流程如下:

(1)用户认证(登录)

小程序端用户通过微信授权登录,后端调用微信接口获取openid,作为用户唯一标识;管理后台用户通过账号密码登录。登录成功后,生成JWT(JSON Web Token)并返回给前端,Token中包含用户ID、角色信息。

// 登录接口示例(小程序端)
@RestController
@RequestMapping("/api/auth")
public class AuthController {
    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    @Autowired
    private UserService userService;

    @PostMapping("/wx-login")
    public Result wxLogin(@RequestBody WxLoginDTO dto) {
        // 1. 调用微信接口获取openid
        String openid = WxApiUtil.getOpenid(dto.getCode());
        // 2. 查询用户是否存在,不存在则创建
        User user = userService.getByUsername(openid);
        if (user == null) {
            user = userService.registerWxUser(openid);
        }
        // 3. 生成Token(包含用户ID、角色列表)
        List<String> roles = userService.getUserRoles(user.getId());
        String token = jwtTokenUtil.generateToken(user.getId().toString(), roles);
        return Result.success(new LoginVO(token, user.getNickName()));
    }
}
(2)权限拦截(过滤器链)

Spring Security通过过滤器链实现请求拦截,核心过滤器JwtAuthenticationFilter负责解析Token、加载用户权限,并将权限信息存入SecurityContextHolder

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException {
        // 1. 从请求头获取Token
        String token = request.getHeader("Authorization");
        if (token != null && token.startsWith("Bearer ")) {
            token = token.substring(7);
            // 2. 验证Token有效性
            if (jwtTokenUtil.validateToken(token)) {
                // 3. 解析用户ID与角色
                String userId = jwtTokenUtil.getUserIdFromToken(token);
                List<String> roles = jwtTokenUtil.getRolesFromToken(token);
                // 4. 构建认证对象
                UserDetails userDetails = userDetailsService.loadUserByUsername(userId);
                UsernamePasswordAuthenticationToken authentication = 
                    new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        }
        chain.doFilter(request, response);
    }
}
(3)方法级权限控制

通过@PreAuthorize注解实现接口级别的权限校验,例如“修改菜品”接口仅允许拥有dish:update权限的角色访问。

@RestController
@RequestMapping("/api/dish")
public class DishController {
    @Autowired
    private DishService dishService;

    // 仅允许拥有dish:update权限的用户访问
    @PutMapping("/{id}")
    @PreAuthorize("hasAuthority('dish:update')")
    public Result updateDish(@PathVariable Long id, @RequestBody DishUpdateDTO dto) {
        dishService.updateById(id, dto);
        return Result.success();
    }
}

权限动态管理

后台管理端提供“角色管理”“权限分配”功能,支持管理员动态调整角色权限。例如,新增“服务员”角色时,可勾选“查看订单”“打印小票”等权限,系统自动更新t_role_permission表,无需重启服务即可生效。

小程序端与后端的通信机制

通信协议与数据格式

小程序端与后端采用HTTPS协议通信,确保数据传输安全。接口数据格式统一为JSON,请求与响应遵循以下规范:

  • 请求头
    • Content-Type: application/json
    • Authorization: Bearer {token}(携带JWT令牌,未登录接口除外)
  • 响应格式
    {
      "code": 200,       // 状态码:200成功,400参数错误,401未授权,403无权限,500系统错误
      "message": "success",
      "data": {}         // 业务数据
    }
    

核心通信流程

(1)登录认证流程

小程序登录采用“微信授权+后端Token验证”模式,流程如下:

  1. 小程序调用wx.login()获取临时code
  2. code发送至后端/api/auth/wx-login接口;
  3. 后端调用微信服务器接口,用code换取openidsession_key
  4. 后端根据openid查询用户,不存在则创建新用户;
  5. 生成JWT Token返回给小程序,小程序存储Token(本地缓存);
  6. 后续请求在Header中携带Token,后端验证通过后放行。
(2)菜品列表加载流程

用户进入点餐页面时,小程序请求菜品列表,流程如下:

  1. 小程序onLoad生命周期调用getDishList()方法;
  2. 检查本地是否有缓存的菜品数据(Redis缓存,有效期5分钟),有则直接使用;
  3. 无缓存则发起HTTP请求:GET /api/dish/list?categoryId=1
  4. 后端Controller接收请求,调用Service层查询数据库(优先查Redis缓存);
  5. 返回菜品列表数据,小程序渲染页面并更新本地缓存。
// 小程序端获取菜品列表示例
async getDishList(categoryId) {
  const cacheKey = `dish_list_${categoryId}`;
  let dishList = wx.getStorageSync(cacheKey);
  if (!dishList) {
    try {
      const res = await wx.request({
        url: `${baseUrl}/api/dish/list`,
        method: 'GET',
        data: { categoryId },
        header: { 'Authorization': `Bearer ${getToken()}` }
      });
      if (res.data.code === 200) {
        dishList = res.data.data;
        wx.setStorageSync(cacheKey, dishList); // 缓存数据
      }
    } catch (err) {
      console.error('获取菜品失败', err);
    }
  }
  this.setData({ dishList });
}
(3)订单提交流程

用户下单时,小程序需完成“购物车数据提交→库存校验→订单创建→支付唤起”全流程,核心步骤如下:

  1. 小程序收集购物车数据(菜品ID、数量、规格),组装为OrderCreateDTO
  2. 发起POST /api/order/create请求,携带Token;
  3. 后端校验库存(Redis原子递减,防止超卖);
  4. 生成订单号,保存订单数据至MySQL,订单状态设为“待支付”;
  5. 调用微信支付统一下单接口,获取prepay_id
  6. 后端返回支付参数(时间戳、随机串、签名)给小程序;
  7. 小程序调用wx.requestPayment()唤起支付界面;
  8. 支付完成后,微信服务器通过回调接口通知后端更新订单状态。

异常处理与重试机制

小程序端需处理网络异常、接口超时等问题,常见策略包括:

  • 网络错误提示:请求失败时显示“网络连接失败,请重试”;
  • 接口重试:对非幂等性接口(如查询)实现自动重试(最多2次),幂等性接口(如支付)需避免重复提交;
  • Token过期处理:拦截401响应,自动刷新Token(通过refresh_token),刷新失败则跳转登录页。
// 封装请求函数,统一处理异常
const request = (options) => {
  return new Promise((resolve, reject) => {
    wx.request({
      ...options,
      success: (res) => {
        if (res.statusCode === 200) {
          if (res.data.code === 200) {
            resolve(res.data);
          } else if (res.data.code === 401) {
            // Token过期,尝试刷新
            refreshToken().then(() => {
              options.header['Authorization'] = `Bearer ${getToken()}`;
              request(options).then(resolve).catch(reject);
            }).catch(() => {
              wx.navigateTo({ url: '/pages/login/index' });
            });
          } else {
            wx.showToast({ title: res.data.message, icon: 'none' });
            reject(res.data);
          }
        } else {
          wx.showToast({ title: '网络错误', icon: 'none' });
          reject(new Error('Network Error'));
        }
      },
      fail: (err) => {
        wx.showToast({ title: '请求失败', icon: 'none' });
        reject(err);
      }
    });
  });
};

性能优化策略

为提升通信效率,项目采用以下优化手段:

  • 接口合并:将“菜品分类”“热门菜品”“推荐菜品”合并为一个接口返回,减少请求次数;
  • 数据压缩:后端开启GZIP压缩,减小传输体积;
  • 缓存策略:小程序端缓存静态数据(如菜品分类),后端缓存热点数据(如销量Top10菜品);
  • 分页加载:订单列表、评价列表采用分页查询,默认每页10条数据。

源码包结构与关键代码解析

后端源码结构

src/main/java/com/example/diancan/
├── config/          # 配置类(SecurityConfig、RedisConfig、SwaggerConfig等)
├── controller/      # 控制器(AuthController、DishController、OrderController等)
├── service/         # 业务逻辑层
│   ├── impl/        # 服务实现类
├── mapper/          # MyBatis Mapper接口
├── entity/          # 实体类(User、Role、Permission、Dish、Order等)
├── dto/             # 数据传输对象(LoginDTO、OrderCreateDTO等)
├── vo/              # 视图对象(LoginVO、DishVO等)
├── security/        # 安全相关(JwtTokenUtil、JwtAuthenticationFilter等)
├── exception/       # 自定义异常(BusinessException、GlobalExceptionHandler)
└── util/            # 工具类(DateUtil、RedisUtil、WxApiUtil等)

前端(小程序)源码结构

miniprogram/
├── pages/           # 页面目录
│   ├── index/       # 首页(菜品浏览)
│   ├── cart/        # 购物车
│   ├── order/       # 订单相关
│   ├── user/        # 个人中心
│   └── login/       # 登录页
├── components/      # 自定义组件(菜品卡片、购物车弹窗等)
├── utils/           # 工具类(request.js、auth.js、cache.js)
├── api/             # 接口封装(dishApi.js、orderApi.js、authApi.js)
├── app.js           # 入口文件
├── app.json         # 全局配置
└── app.wxss         # 全局样式

关键代码片段解析

(1)RBAC权限校验核心工具类
// JwtTokenUtil.java(Token生成与解析)
@Component
public class JwtTokenUtil implements Serializable {
    @Value("${jwt.secret}")
    private String secret;
    @Value("${jwt.expiration}")
    private Long expiration;

    // 生成Token
    public String generateToken(String userId, List<String> roles) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("roles", roles);
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(userId)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }

    // 从Token中获取角色
    public List<String> getRolesFromToken(String token) {
        Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        return (List<String>) claims.get("roles");
    }
}
(2)订单创建接口(含事务与库存校验)
@Service
public class OrderServiceImpl implements OrderService {
    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private DishMapper dishMapper;
    @Autowired
    private RedisTemplate<String, Integer> redisTemplate;

    @Transactional(rollbackFor = Exception.class)
    @Override
    public Order createOrder(OrderCreateDTO dto, Long userId) {
        // 1. 校验库存(Redis原子操作)
        for (OrderItemDTO item : dto.getItems()) {
            String stockKey = "dish:stock:" + item.getDishId();
            Long remain = redisTemplate.opsForValue().decrement(stockKey, item.getQuantity());
            if (remain < 0) {
                // 库存不足,回滚Redis
                redisTemplate.opsForValue().increment(stockKey, item.getQuantity());
                throw new BusinessException("菜品【" + item.getDishName() + "】库存不足");
            }
        }
        // 2. 创建订单
        Order order = new Order();
        order.setOrderNo(generateOrderNo());
        order.setUserId(userId);
        order.setTotalAmount(calculateTotal(dto.getItems()));
        order.setStatus(OrderStatus.PENDING_PAYMENT.getCode());
        orderMapper.insert(order);
        // 3. 保存订单项
        saveOrderItems(order.getId(), dto.getItems());
        // 4. 发送延迟消息(30分钟未支付自动取消)
        rabbitTemplate.convertAndSend("order.delay.exchange", "order.cancel", order.getId(),
                message -> {
                    message.getMessageProperties().setDelay(30 * 60 * 1000);
                    return message;
                });
        return order;
    }
}

在这里插入图片描述

总结与建议

核心技术点回顾

本文解析的餐饮点餐小程序源码包,通过**Vue(管理后台)+ 微信小程序(用户端)+ SpringBoot(后端)**的技术组合,实现了高效的前后端分离架构。其中,后台RBAC权限模型通过“用户-角色-权限”三层映射,灵活支撑多角色协作场景;小程序端与后端的通信则通过HTTPS+JWT实现安全认证,结合缓存、接口合并等策略保障性能。

实践建议

  • 权限粒度控制:避免过度设计权限,初期可按“模块+操作”划分(如dish:query),后期根据业务需求细化;
  • Token安全:JWT密钥定期轮换,设置合理过期时间(小程序端Token建议2小时,refresh_token建议7天);
  • 接口文档:使用Swagger/Knife4j生成在线接口文档,方便前后端联调。

通过本文的解析,开发者可深入理解餐饮点餐小程序的核心技术实现,快速复用源码包中的RBAC权限模型与通信机制,加速同类项目的落地。在实际开发中,还需结合业务场景灵活调整,持续优化系统性能与用户体验。

更多推荐