ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

yshop多租户点餐系统实战:OAuth2权限与MybatisPlus租户隔离

yshop多租户点餐系统实战:OAuth2权限与MybatisPlus租户隔离 简介这是一套面向企业及个人二次开发的意象点餐扫码点餐系统源码覆盖在线点餐、外卖与自取、多门店及SaaS多租户等场景采用Java与uniappVue3前后端分离架构支持H5与微信小程序。后端基于SpringBoot、Spring Security OAuth2、MybatisPlus、JWT与Redis前端使用Vue3功能涵盖商品多规格SKU管理、店铺管理、云小票打印、图片素材库、订单管理、积分兑换、充值、优惠券、微信公众号、门店移动端、分销商、桌面扫码点餐、拼团、邀请有礼与会员卡等模块。资源包共2005个文件约17.63MB其中1324个java文件承载后端业务逻辑257个vue与161个js文件构成前端页面与交互另有xml、html、css、sql、yaml等配置与样式文件目录结构完整便于按模块检索与二次开发。目前已有149人学习下载适合具备一定Java与Vue基础、需要快速搭建点餐系统的开发者参考。1. 扫码点餐系统选型为什么 yshop 的多租户架构值得拆一遍上个月帮一个做连锁轻食的朋友看系统他手里三套点餐后台门店一多就乱A 店改个菜品价格要登 A 后台B 店加个规格要登 B 后台财务对账还得把三份订单导出来拼。这种场景下SaaS 多租户不是噱头是刚需。yshop 意象点餐系统就是冲着这个痛点来的——一套部署多门店、多租户共用每个门店有自己的商品、订单、会员和员工数据隔离靠租户 ID 而不是靠多开数据库。技术栈是 SpringBoot Spring Security OAuth2 MybatisPlus Redis 的后端前端用 uniappVue3一套代码同时出 H5 和微信小程序扫码点餐、外卖自取、云小票打印、积分兑换、优惠券、拼团、分销商这些餐饮高频功能都内置了。适合谁一是想省掉从零搭权限和租户体系的中小团队二是接私活需要快速交付点餐小程序的个人开发者三是拿它当 SpringBoot uniapp 全栈学习样本的人。下面按「是什么 → 怎么跑起来 → 坑在哪 → 怎么改」的顺序拆。2. 多租户与权限底座OAuth2 JWT MybatisPlus 怎么串起来2.1 租户隔离到底隔离在哪一层多租户系统最容易翻车的地方是把「多门店」和「多租户」混为一谈。yshop 里这两个是不同层级租户tenant是 SaaS 层面的隔离单位一个租户可以拥有多个门店store门店下面才是商品、订单、桌台。数据隔离落在 MybatisPlus 这一层常见做法是给业务表加tenant_id字段再用 MybatisPlus 的租户插件在 SQL 执行前自动拼上WHERE tenant_id ?业务代码里几乎不用手写这个条件。这么选的理由很直接如果靠每个租户一个独立库运维成本随租户数线性上涨备份、升级、加字段都是灾难如果靠代码里手动加条件迟早有人漏写一漏就是跨租户数据泄露。插件式拦截把这件事收敛到一个地方改起来也集中。需要留意的边界是不是所有表都该被租户插件拦截。像系统字典、全局配置这类公共表如果也被拼上tenant_id查出来就是空。MybatisPlus 提供了忽略表配置通常在这几个地方声明// MybatisPlus 租户插件配置忽略不需要隔离的公共表 Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); TenantLineInnerInterceptor tenant new TenantLineInnerInterceptor(); tenant.setTenantLineHandler(new TenantLineHandler() { Override public Expression getTenantId() { // 从当前登录上下文取租户ID通常来自 JWT 解析结果 return new LongValue(SecurityUtils.getTenantId()); } Override public boolean ignoreTable(String tableName) { // 公共表不参与租户隔离按实际表名补充 return Arrays.asList(sys_dict, sys_config, gen_table) .contains(tableName.toLowerCase()); } }); interceptor.addInnerInterceptor(tenant); return interceptor; }逻辑说明getTenantId()是每次 SQL 执行时动态取值所以租户上下文必须在线程内可取一般用 ThreadLocal 存。参数上ignoreTable返回 true 的表会被跳过新增公共表时记得回来补这是最容易忘的一步。失败时先看日志里拼出来的 SQL 有没有多出tenant_id条件没有就查上下文是否为空多了不该有的就查忽略表名单。2.2 OAuth2 JWT 的登录链路yshop 用 Spring Security OAuth2 做认证JWT 做令牌载体Redis 存令牌和会话状态。为什么不全用 JWT 无状态因为点餐场景有强制下线、令牌续期、多端登录互踢的需求纯 JWT 做不到即时失效得靠 Redis 兜一层黑名单或版本号。登录大致链路小程序端调/auth/login带账号密码或微信 code → 认证服务校验 → 生成 access_token 和 refresh_token → access_token 里塞用户 ID、租户 ID、角色 → 前端存起来后续请求放 Header。这里有个实操细节微信小程序登录和账号密码登录走的是不同 grant_type前者通常自定义一个wechat类型在WechatLoginHandler里拿 code 换 openid 再查绑定用户。# 账号密码登录拿 access_token curl -X POST http://localhost:8080/auth/oauth/token \ -H Content-Type: application/x-www-form-urlencoded \ -H Authorization: Basic base64(clientId:clientSecret) \ -d grant_typepasswordusernameadminpassword123456tenantId1 # 返回体里 access_token 用于后续接口refresh_token 用于续期参数说明Authorization头是 OAuth2 客户端凭证的 Base64tenantId是自定义扩展参数用来在登录阶段就确定租户上下文。如果返回 401先确认 clientId 是否在oauth_client_details表里且authorized_grant_types包含 password如果返回租户为空检查自定义参数有没有被过滤器透传。2.3 权限到按钮这一级怎么落Spring Security 的权限模型在 yshop 里是「用户 → 角色 → 菜单/按钮权限」三级。后端接口用PreAuthorize(ss.hasPermi(order:list))这类注解控制前端 uniapp 侧根据登录返回的权限码决定按钮显不显示。注意前端隐藏只是体验真正的拦截必须在后端否则改个请求就能越权。实操上新增一个功能模块的权限顺序是菜单表加记录 → 角色菜单关联表授权 → 后端接口加注解 → 前端拿权限码判断。漏掉任何一步都会出现「菜单看得见但点进去 403」或者「接口能调但菜单不显示」的玄学现象排查时按这个顺序倒推基本能定位。3. 从源码到跑起来后端启动、数据库初始化与 uniapp 打包3.1 后端环境与依赖版本对齐拿到源码第一步不是急着mvn spring-boot:run而是先把版本对齐。yshop 这类 SpringBoot 项目对 JDK 和 Maven 版本敏感常见组合是 JDK 8 或 11、Maven 3.6、MySQL 5.7/8.0、Redis 5。JDK 版本不对启动时报的错往往和真实原因差十万八千里比如Unsupported class file major version其实是编译版本问题不是代码问题。# 确认环境 java -version # 建议 1.8 或 11 mvn -v # 建议 3.6 以上 mysql --version # 5.7 或 8.0 redis-server --version # 导入数据库先建库再导 mysql -uroot -p -e CREATE DATABASE yshop DEFAULT CHARACTER SET utf8mb4; mysql -uroot -p yshop sql/yshop.sql # 改配置数据库、Redis、租户默认值 # application.yml 里重点看 spring.datasource 和 spring.redis 两段逻辑说明建库时字符集必须 utf8mb4否则商品名里的 emoji 或特殊符号会入库失败。SQL 文件通常包含基础菜单和默认租户导入后先确认sys_tenant表里有初始租户记录没有的话登录会卡在租户解析。参数上spring.datasource.url记得加useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai时区不设对订单时间会差 8 小时对账时能让人怀疑人生。3.2 uniappVue3端配置与打包前端是 uniapp Vue3一套代码出 H5 和微信小程序。跑之前先确认 HBuilderX 版本或 CLI 版本支持 Vue3老版本打开会报语法错误。接口地址在config或.env文件里配小程序端有个硬性要求请求域名必须是 HTTPS 且在小程序后台配置过白名单本地调试要在开发者工具里勾「不校验合法域名」。// uniapp 请求封装示例统一带 token 和租户 const request (options) { return new Promise((resolve, reject) { uni.request({ url: baseUrl options.url, method: options.method || GET, data: options.data || {}, header: { Content-Type: application/json, // token 从本地存储取登录后写入 Authorization: Bearer uni.getStorageSync(access_token), tenantId: uni.getStorageSync(tenant_id) }, success: (res) { if (res.statusCode 401) { // 令牌过期走刷新或跳登录 uni.reLaunch({ url: /pages/login/login }); return; } resolve(res.data); }, fail: reject }); }); };逻辑说明Authorization用 Bearer 前缀是 OAuth2 标准tenantId放 Header 是为了让后端过滤器在无状态请求里也能还原租户上下文。参数上baseUrl在 H5 和小程序下可能不同建议用条件编译区分。打包小程序时如果遇到source size exceed max limit 2mb说明主包超了常见解法是把非首屏页面拆到分包图片资源挪到 CDN别硬塞。3.3 扫码点餐的桌台与订单流转扫码点餐的核心链路是顾客扫桌台二维码 → 带桌台 ID 进小程序 → 选菜下单 → 订单落到对应门店和桌台 → 后厨云小票打印。桌台二维码里通常编码了门店 ID 和桌台 ID扫码后前端解析并写入下单上下文。这里有个容易忽略的点桌台状态要能区分「空闲/占用/待清台」否则两拨客人扫同一桌会串单。订单状态机是另一个重点常见状态有「待支付/已支付/制作中/已完成/已取消」。状态流转必须由后端校验前端传什么状态都信的话改个参数就能把已取消订单改成已完成。云小票打印一般对接的是网络打印机走的是打印机厂商的云 API配置里填打印机编号和密钥打印失败要有重试和告警不然高峰期漏单没人知道。4. 避坑与排查多租户点餐系统最容易翻车的五件事4.1 登录成功但接口全 403现象账号密码登录返回了 token但调任何业务接口都是 403。原因通常是权限码没对上或者 JWT 解析出的角色在 Redis 里查不到对应权限集合。解决先看后端日志里PreAuthorize拦截的权限码是什么再去sys_menu表确认这个权限码存在且已分配给当前角色如果权限码对但还 403检查 Redis 里缓存的权限是否过期清掉缓存重登。4.2 跨租户查到别人的数据现象A 租户登录订单列表里出现了 B 租户的订单。原因多半是某张业务表没被租户插件覆盖或者该表在忽略名单里被误加。解决打开 MybatisPlus 的 SQL 日志看这条查询有没有拼tenant_id条件没有就检查表名是否在ignoreTable里有就移出去。血泪经验是新增表时一定要回归测一遍跨租户查询别等上线才发现。4.3 小程序请求全部失败但 H5 正常现象H5 端接口通小程序端全部请求失败。原因基本是域名白名单或 HTTPS 问题小程序强制 HTTPS 且域名要在后台配置。解决开发者工具里临时勾「不校验合法域名」先验证逻辑确认是域名问题后把接口域名配到小程序后台的 request 合法域名里证书要有效自签证书不行。4.4 订单时间差 8 小时现象下单时间和实际时间对不上差整 8 小时。原因是数据库连接时区没设或者 JVM 时区和数据库时区不一致。解决JDBC URL 加serverTimezoneAsia/Shanghai同时确认 MySQL 的time_zone参数两边对齐。这个坑在跨时区部署时更明显别只改一头。4.5 云小票打印偶发失败现象大部分订单能打偶尔漏打。原因可能是打印机离线、网络抖动或者打印任务并发时没做队列。解决打印调用要加超时和重试失败落库标记后台提供「重新打印」入口并发高的门店建议把打印任务丢进队列串行处理别在下单主流程里同步等打印结果否则打印慢会拖垮下单响应。5. 二次开发进阶改一个功能要动哪几个文件拿到 yshop 做二次开发最怕的是改一处漏三处。以「给商品加一个会员价字段」为例完整链路是数据库pms_product加字段 → 实体类加属性 → Mapper XML 的查询和插入语句补字段 → Service 层计算逻辑 → 前端商品编辑页加输入框 → 小程序商品详情展示会员价。少任何一环要么存不进去要么查不出来要么前端不显示。验证改动是否生效我一般用「三查法」查数据库确认字段落库、查接口返回确认字段透出、查前端渲染确认展示正确。三步都过才算完成只测前端很容易被缓存骗过去。-- 加字段后确认落库别只看接口返回 ALTER TABLE pms_product ADD COLUMN member_price DECIMAL(10,2) DEFAULT NULL COMMENT 会员价; SELECT id, name, price, member_price FROM pms_product WHERE id 1;参数说明DECIMAL(10,2)是金额常用类型别用 float浮点误差在金额上迟早出事。DEFAULT NULL表示未设置会员价时走原价业务逻辑里要判空。多租户场景下改功能还有个额外注意点任何新增的查询都要确认租户插件是否覆盖新增的写操作要确认tenant_id有没有自动填充。我习惯在改完后用两个不同租户的账号各跑一遍确认数据互不可见。从那以后我每次动涉及租户表的代码都强制走一遍双租户回归这个习惯帮我挡掉过好几次线上事故。希望帮到你。本文还有配套的精品资源点击获取
返回列表