尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

SSM超市订单系统实战:从配置对齐到Tomcat部署全解析

SSM超市订单系统实战:从配置对齐到Tomcat部署全解析 简介这是一套面向Java Web初学者与课程设计者的完整超市订单管理系统实战项目基于SSMSpringSpringMVCMyBatis框架、MySQL数据库与JSP动态页面技术构建解决中小型超市日常订单、供应商及用户统一管理的业务需求。资源包共383个文件含74个核心依赖jar包、40个JSP页面实现登录、订单列表、表单提交等交互、30个Java源码如UserController、BillController、Provider等控制器与实体类、38个XML配置文件Spring与MyBatis映射、46个JS脚本及36个PNG图标资源整体压缩后26.2MB结构清晰、模块职责分明。已有2568人学习下载提供可直接部署运行的完整工程含SQL建表语句、Tomcat适配配置及基础权限控制逻辑涵盖订单增删改查、供应商维护、角色化权限管理及异常处理机制代码规范、注释充分适合用于Java Web课程实践、毕业设计参考或SSM框架入门项目复现。1. 这不是又一个“Hello World”Demo一个真实可跑的超市订单系统它用SSM把Java Web开发的骨架全露出来了你可能已经看过几十个“SSM整合教程”但它们大多卡在spring-context.xml配完就戛然而止——没登录页、没真实SQL执行日志、没JSP里EL表达式怎么和MyBatis返回的List联动、更没人告诉你BillController.class重复写了两遍意味着什么。这个超市订单管理系统不是教学幻灯片而是一个能直接部署到Tomcat 8.5、连上MySQL 5.7、打开浏览器就能增删查改供应商和订单的完整工程。它不炫技不用Redis缓存、不接前端框架、不搞微服务拆分但每一行代码都在回答一个实际问题当用户点击“提交订单”按钮SpringMVC如何把表单参数转成Bill对象MyBatis怎么把select语句里的#{providerId}映射到Provider实体的id字段JSP页面里${billList[0].productName}为什么能取到值而${bill.productName}却报NullPointerException如果你正卡在SSM三层架构的“胶水层”——即Controller怎么把Service结果塞进Model再交给View渲染——那这个项目就是为你拆解的。它适合两类人刚学完Spring IoC但写不出完整CRUD的Java初学者以及需要快速验证SSM老项目迁移可行性的运维/测试工程师。2. SSM三层结构不是概念图是四张必须对齐的配置表和三类必须重写的类SSM框架的落地难点从来不在“会不会写注解”而在配置文件、Mapper接口、Controller方法、JSP页面这四者之间的数据流向是否严格对齐。本系统用最朴素的方式暴露了这种对齐关系所有Controller类名都带.class后缀如UserController.class这不是笔误而是提醒你——这些类必须被Spring容器扫描到并实例化所有实体类User.class,Provider.class,Bill.class的字段命名必须与MySQL表字段完全一致否则MyBatis的resultMap自动映射会静默失败所有JSP页面路径必须匹配RequestMapping的value值否则404错误不会告诉你缺了哪个斜杠。2.1 Spring配置applicationContext.xml里藏着事务控制的开关逻辑Spring的核心作用是管理Bean生命周期和事务。本系统的applicationContext.xml中关键配置集中在tx:annotation-driven/和bean idtransactionManager两处!-- 数据源配置 -- bean iddataSource classorg.springframework.jdbc.datasource.DriverManagerDataSource property namedriverClassName valuecom.mysql.cj.jdbc.Driver/ property nameurl valuejdbc:mysql://localhost:3306/supermarket?useSSLfalseamp;serverTimezoneUTCamp;characterEncodingutf8/ property nameusername valueroot/ property namepassword value123456/ /bean !-- MyBatis SqlSessionFactory配置 -- bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ property nameconfigLocation valueclasspath:mybatis-config.xml/ property namemapperLocations valueclasspath:mapper/*.xml/ /bean !-- 事务管理器 -- bean idtransactionManager classorg.springframework.jdbc.datasource.DataSourceTransactionManager property namedataSource refdataSource/ /bean tx:annotation-driven transaction-managertransactionManager/注意url中的serverTimezoneUTC是MySQL 8.0必需参数漏掉会导致java.sql.SQLException: The server time zone value ... is unrecognizedcharacterEncodingutf8确保中文不乱码mapperLocations指向src/main/resources/mapper/下的XML文件路径错一位就会报Invalid bound statement (not found)。事务生效的前提是Service层方法必须加Transactional注解且该方法不能是private或final——因为Spring AOP代理机制依赖JDK动态代理或CGLIBprivate方法无法被拦截。例如BillServiceImpl.java中Service(billService) public class BillServiceImpl implements BillService { Autowired private BillMapper billMapper; Transactional // ← 此注解必须存在且方法为public Override public boolean addBill(Bill bill) { int rows billMapper.insertBill(bill); if (rows 0) { // 模拟业务逻辑更新供应商库存 Provider provider new Provider(); provider.setId(bill.getProviderId()); provider.setBalance(provider.getBalance() - bill.getTotalPrice()); // 实际需查库再更新 return providerMapper.updateProvider(provider) 0; } return false; } }如果addBill()方法声明为private boolean addBill(...)即使加了Transactional事务也不会生效——Spring根本无法代理它。2.2 MyBatis映射XML里的resultMap比Select注解更能暴露字段映射陷阱本系统坚持使用XML方式定义SQLsrc/main/resources/mapper/BillMapper.xml因为它能清晰暴露字段映射的断点。比如Bill实体有providerId字段而数据库表bill中对应列为providerid小写若不显式配置resultMapMyBatis默认按驼峰规则转换providerId→provider_id但实际列名是providerid就会导致providerId始终为null!-- mapper/BillMapper.xml -- mapper namespacecom.supermarket.dao.BillMapper resultMap idBillResultMap typecom.supermarket.pojo.Bill id propertyid columnid/ result propertybillCode columnbillCode/ result propertyproductName columnproductName/ result propertyproviderId columnproviderid/ !-- ← 关键column名必须与数据库列名完全一致 -- result propertytotalPrice columntotalPrice/ result propertycreatedBy columncreatedBy/ result propertycreationDate columncreationDate/ /resultMap select idgetBillList resultMapBillResultMap SELECT * FROM bill where if testproductName ! null and productName ! AND productName LIKE CONCAT(%, #{productName}, %) /if if testproviderId ! null and providerId ! 0 AND providerid #{providerId} /if /where ORDER BY creationDate DESC /select /mapperif标签内的#{providerId}是预编译占位符防止SQL注入而columnproviderid必须与MySQL中DESC bill显示的实际列名区分大小写完全一致。若数据库列名为PROVIDERID全大写此处也必须写PROVIDERID否则MyBatis无法将查询结果赋值给Bill.providerId。2.3 Controller与JSP的数据桥接Model.addAttribute()不是摆设是ViewResolver找页面的唯一线索SpringMVC的DispatcherServlet通过InternalResourceViewResolver将逻辑视图名解析为物理路径。BillController.class中Controller RequestMapping(/bill) public class BillController { Autowired private BillService billService; RequestMapping(value /list, method RequestMethod.GET) public String getBillList(Model model, RequestParam(value productName, required false) String productName, RequestParam(value providerId, required false, defaultValue 0) Integer providerId) { ListBill billList billService.getBillList(productName, providerId); model.addAttribute(billList, billList); // ← 关键key必须与JSP中${billList}一致 model.addAttribute(providerList, billService.getProviderList()); // 供下拉框选择 return billlist; // ← 返回逻辑视图名ViewResolver自动拼成/WEB-INF/jsp/billlist.jsp } }return billlist触发InternalResourceViewResolver的解析规则bean classorg.springframework.web.servlet.view.InternalResourceViewResolver property nameprefix value/WEB-INF/jsp// property namesuffix value.jsp/ /bean最终定位到/WEB-INF/jsp/billlist.jsp。而JSP中${billList}能取到值前提是model.addAttribute(billList, ...)的key字符串与EL表达式完全一致——大小写、下划线都不能错。若Controller写model.addAttribute(bill_list, ...)JSP写${billList}结果必为空列表。2.4 JSP页面的EL与JSTL${}不是万能的它依赖PageContext的Scope链billlist.jsp中展示订单列表c:forEach items${billList} varbill varStatusstatus tr classodd gradeX td${status.count}/td td${bill.billCode}/td td${bill.productName}/td tdfmt:formatNumber value${bill.totalPrice} pattern#,##0.00//td td c:forEach items${providerList} varp c:if test${p.id bill.providerId} ${p.proName} /c:if /c:forEach /td tdfmt:formatDate value${bill.creationDate} patternyyyy-MM-dd HH:mm:ss//td td a href${pageContext.request.contextPath}/bill/view/${bill.id}查看/a a href${pageContext.request.contextPath}/bill/delete/${bill.id}删除/a /td /tr /c:forEach这里的关键细节c:forEach的items属性必须是List类型若billList是nullJSTL会抛出javax.servlet.jsp.JspTagException: Dont know how to iterate over supplied items${bill.providerId}取值成功说明Bill实体的getProviderId()方法返回非null值且providerId字段在数据库中不为NULLfmt:formatNumber和fmt:formatDate需要在JSP顶部声明% taglib prefixfmt urihttp://java.sun.com/jsp/jstl/fmt %否则会报According to TLD or attribute directive in tag file, attribute value does not accept any expressions${pageContext.request.contextPath}获取应用上下文路径如/supermarket避免硬编码/bill/view/...导致部署到子路径时404。3. MySQL建表与索引没有索引的WHERE条件就是生产环境的定时炸弹本系统数据库设计直指超市业务核心bill订单、provider供应商、user用户三张主表外加role角色表实现权限控制。建表语句不是照搬教科书而是针对高频查询场景做了索引优化。3.1 核心表结构与字符集强制统一-- 创建数据库显式指定字符集避免乱码 CREATE DATABASE supermarket CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE supermarket; -- 用户表 CREATE TABLE user ( id INT(11) NOT NULL AUTO_INCREMENT, userCode VARCHAR(15) NOT NULL COMMENT 用户编码, userName VARCHAR(15) NOT NULL COMMENT 用户名, userPassword VARCHAR(32) NOT NULL COMMENT 密码MD5加密, gender INT(10) DEFAULT NULL COMMENT 性别, birthday DATE DEFAULT NULL COMMENT 出生日期, phone VARCHAR(15) DEFAULT NULL COMMENT 电话, address VARCHAR(30) DEFAULT NULL COMMENT 地址, userRole INT(10) NOT NULL COMMENT 用户角色, createdBy INT(10) NOT NULL COMMENT 创建者, creationDate DATETIME NOT NULL COMMENT 创建时间, modifyBy INT(10) DEFAULT NULL COMMENT 更新者, modifyDate DATETIME DEFAULT NULL COMMENT 更新时间, PRIMARY KEY (id), KEY idx_userCode (userCode) USING BTREE, -- 用于登录查询 KEY idx_userRole (userRole) USING BTREE -- 用于角色权限过滤 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 订单表 CREATE TABLE bill ( id INT(11) NOT NULL AUTO_INCREMENT, billCode VARCHAR(20) NOT NULL COMMENT 订单编码, productName VARCHAR(20) NOT NULL COMMENT 商品名称, productDesc VARCHAR(200) DEFAULT NULL COMMENT 商品描述, productUnit VARCHAR(10) DEFAULT NULL COMMENT 商品单位, productCount DECIMAL(10,2) DEFAULT NULL COMMENT 商品数量, totalPrice DECIMAL(10,2) NOT NULL COMMENT 总金额, isPayment INT(10) DEFAULT 0 COMMENT 是否支付0未支付1已支付, providerId INT(11) NOT NULL COMMENT 供应商ID, createdBy INT(11) NOT NULL COMMENT 创建者, creationDate DATETIME NOT NULL COMMENT 创建时间, modifyBy INT(11) DEFAULT NULL COMMENT 更新者, modifyDate DATETIME DEFAULT NULL COMMENT 更新时间, PRIMARY KEY (id), KEY idx_billCode (billCode) USING BTREE, -- 精确查询订单号 KEY idx_productName (productName) USING BTREE, -- 模糊搜索商品名 KEY idx_providerId (providerId) USING BTREE, -- 关联查询供应商 KEY idx_creationDate (creationDate) USING BTREE -- 按时间排序/范围查询 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 供应商表 CREATE TABLE provider ( id INT(11) NOT NULL AUTO_INCREMENT, proCode VARCHAR(20) NOT NULL COMMENT 供应商编码, proName VARCHAR(20) NOT NULL COMMENT 供应商名称, proDesc VARCHAR(200) DEFAULT NULL COMMENT 供应商描述, proContact VARCHAR(20) DEFAULT NULL COMMENT 联系人, proPhone VARCHAR(20) DEFAULT NULL COMMENT 联系电话, proAddress VARCHAR(50) DEFAULT NULL COMMENT 地址, proFax VARCHAR(20) DEFAULT NULL COMMENT 传真, createdBy INT(11) NOT NULL COMMENT 创建者, creationDate DATETIME NOT NULL COMMENT 创建时间, modifyBy INT(11) DEFAULT NULL COMMENT 更新者, modifyDate DATETIME DEFAULT NULL COMMENT 更新时间, PRIMARY KEY (id), KEY idx_proCode (proCode) USING BTREE, -- 精确查询供应商编码 KEY idx_proName (proName) USING BTREE -- 模糊搜索供应商名称 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;提示utf8mb4是MySQL 5.5.3推荐字符集支持Emoji和四字节UTF-8字符USING BTREE显式声明B树索引避免旧版本MySQL默认用哈希索引不支持范围查询。3.2 索引失效的五个真实场景及修复方案即使建了索引查询仍可能全表扫描。以下是本系统SQL中必须规避的坑场景错误示例修复方案原因LIKE前导%WHERE productName LIKE %手机%改为WHERE productName LIKE 手机%或用全文索引B树索引无法从中间匹配隐式类型转换WHERE providerId 1providerId是INTWHERE providerId 1字符串转数字导致索引失效函数操作字段WHERE DATE(creationDate) 2024-01-01WHERE creationDate 2024-01-01 AND creationDate 2024-01-02函数使索引失效OR连接无索引字段WHERE providerId 1 OR phone 138...phone无索引为phone字段加索引或拆成UNION查询OR条件中任一字段无索引整条WHERE失效统计行数用COUNT(*)SELECT COUNT(*) FROM bill WHERE productName LIKE 苹果%添加复合索引KEY idx_productName_creationDate (productName, creationDate)单列索引无法覆盖排序需求验证索引是否生效在MySQL命令行执行EXPLAIN SELECT * FROM bill WHERE productName LIKE 苹果%;观察key列是否显示索引名rows是否远小于table总行数。3.3 外键约束的取舍为什么本系统用代码校验代替FOREIGN KEYbill.providerId理论上应设为外键引用provider.id但本系统选择在Service层做校验Override public boolean addBill(Bill bill) { // 1. 校验供应商是否存在 Provider provider providerMapper.getProviderById(bill.getProviderId()); if (provider null) { throw new RuntimeException(供应商ID不存在 bill.getProviderId()); } // 2. 执行插入 return billMapper.insertBill(bill) 0; }原因有三性能外键约束在INSERT/UPDATE时触发额外查询高并发下单条SQL延迟增加灵活性供应商可能被软删除isDeleted1但外键约束只认物理存在部署简化某些云MySQL服务如阿里云RDS默认禁用外键避免上线报错。代价是开发者必须手动维护数据一致性——每次新增订单前必须调用providerMapper.getProviderById()确认供应商有效。4. Tomcat部署与常见启动失败诊断从war包到404每一步都有明确日志锚点将项目打包为WAR部署到Tomcat不是复制粘贴就完事。本系统在pom.xml中明确指定了Servlet版本和插件配置确保生成的WAR符合Tomcat 8.5规范。4.1 Maven构建pom.xml里的三个致命配置点properties project.build.sourceEncodingUTF-8/project.build.sourceEncoding maven.compiler.source1.8/maven.compiler.source maven.compiler.target1.8/maven.compiler.target spring.version5.2.22.RELEASE/spring.version mybatis.version3.4.6/mybatis.version /properties dependencies !-- Servlet API 必须 provided否则Tomcat会冲突 -- dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version3.1.0/version scopeprovided/scope !-- ← 关键避免打包进WAR -- /dependency !-- JSP相关依赖 -- dependency groupIdjavax.servlet.jsp/groupId artifactIdjsp-api/artifactId version2.2/version scopeprovided/scope /dependency dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency !-- MyBatis-Spring整合包 -- dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version1.3.2/version /dependency /dependencies build plugins plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-war-plugin/artifactId version3.3.2/version configuration failOnMissingWebXmlfalse/failOnMissingWebXml !-- ← Tomcat 7支持无web.xml -- warNamesupermarket/warName !-- 生成supermarket.war -- /configuration /plugin /plugins /build注意scopeprovided/scope表示Servlet API由Tomcat提供Maven打包时不包含否则会与Tomcat自带的servlet-api.jar冲突启动时报java.lang.NoSuchMethodError: javax.servlet.http.HttpServletRequest.isAsyncStarted()。4.2 Tomcat启动日志逐行解读定位404/500/ClassNotFoundException的黄金路径将supermarket.war放入$TOMCAT_HOME/webapps/后启动Tomcat关键日志位置catalina.out主日志流记录启动全过程localhost.date.log记录当前应用的Servlet初始化日志manager.date.log记录WAR部署状态。典型失败场景与日志特征现象catalina.out关键日志原因解决方案404所有页面INFO [main] org.apache.catalina.startup.HostConfig.deployWAR Deploying web application archive [...] supermarket.warINFO [main] org.apache.catalina.startup.HostConfig.deployWAR Deployment of web application archive [...] supermarket.war has finished in [X] msWAR部署成功但web.xml缺失或DispatcherServlet未注册检查src/main/webapp/WEB-INF/web.xml是否存在或确认WebServlet注解是否正确500空指针SEVERE [http-nio-8080-exec-1] org.apache.catalina.core.StandardWrapperValve.invoke Servlet.service() for servlet [springmvc] in context with path [/supermarket] threw exceptionjava.lang.NullPointerException at com.supermarket.controller.BillController.getBillList(BillController.java:32)Controller中Service注入失败检查applicationContext.xml中context:component-scan base-packagecom.supermarket.service/是否覆盖Service包路径ClassNotFoundExceptionSEVERE [main] org.apache.catalina.startup.HostConfig.deployWAR Error waiting for multi-thread deployment of WAR filesjava.lang.ClassNotFoundException: com.mysql.cj.jdbc.DriverMySQL驱动jar未放入$TOMCAT_HOME/lib/将mysql-connector-java-8.0.28.jar复制到Tomcat的lib目录而非仅放在项目WEB-INF/lib验证MySQL连接在applicationContext.xml的bean iddataSource配置后添加以下代码测试连接!-- 测试数据源 -- bean idtestDataSource classorg.springframework.jdbc.datasource.init.DataSourceInitializer property namedataSource refdataSource/ property namedatabasePopulator bean classorg.springframework.jdbc.datasource.init.ResourceDatabasePopulator property namesqlScriptEncoding valueUTF-8/ property namescripts list valueclasspath:sql/init.sql/value /list /property /bean /property /bean并在src/main/resources/sql/init.sql中写SELECT 1;启动时若报错则证明数据库连接失败。4.3 JSP编译路径与class文件定位修改JSP后无需重启Tomcat的实操技巧Tomcat运行时会将JSP编译为Servlet类存放于$TOMCAT_HOME/work/Catalina/localhost/supermarket/org/apache/jsp/。例如billlist.jsp编译后生成billlist_jsp.java和billlist_jsp.class。提示修改JSP后Tomcat默认会自动重新编译需init-paramparam-namedevelopment/param-nameparam-valuetrue/param-value/init-param在web.xml中配置。若发现修改不生效可手动删除work目录下对应JSP的class文件再刷新页面触发重编译。验证JSP编译结果访问http://localhost:8080/supermarket/bill/list后检查work目录下是否有billlist_jsp.class若有说明JSP解析成功若无检查web.xml中servlet-mapping是否将*.jsp映射到JspServlet。5. 权限控制与密码安全RBAC模型在SSM中的最小可行实现本系统权限控制不依赖Shiro或Spring Security而是用最简RBAC基于角色的访问控制模型user.userRole字段关联role.idController中通过SessionAttribute(userRole)获取当前角色再用if-else判断操作权限。这种方式牺牲了扩展性但胜在逻辑透明、调试直观。5.1 角色表设计与登录态绑定CREATE TABLE role ( id INT(11) NOT NULL AUTO_INCREMENT, roleName VARCHAR(15) NOT NULL COMMENT 角色名称, roleDesc VARCHAR(30) DEFAULT NULL COMMENT 角色描述, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 初始化角色 INSERT INTO role VALUES (1,管理员,拥有全部权限),(2,普通用户,仅能查看自己的订单);登录成功后将用户角色存入Session// UserController.java RequestMapping(value /login, method RequestMethod.POST) public String login(RequestParam String userCode, RequestParam String userPassword, Model model, HttpSession session) { User user userService.login(userCode, userPassword); if (user ! null) { session.setAttribute(user, user); session.setAttribute(userRole, user.getUserRole()); // ← 关键存入角色ID return redirect:/bill/list; } else { model.addAttribute(error, 用户名或密码错误); return login; } }5.2 Controller层权限拦截用ModelAttribute预加载角色信息在BaseController中定义通用方法避免每个Controller重复写权限判断Controller public class BaseController { ModelAttribute public void addUserRole(Model model, HttpSession session) { User user (User) session.getAttribute(user); if (user ! null) { model.addAttribute(userRole, user.getUserRole()); } } } Controller RequestMapping(/bill) public class BillController extends BaseController { // 继承BaseController RequestMapping(value /list, method RequestMethod.GET) public String getBillList(Model model, ...) { Integer userRole (Integer) model.asMap().get(userRole); if (userRole null || userRole ! 1) { // 只有管理员能看全部订单 // 普通用户只能看自己创建的订单 ListBill billList billService.getBillListByCreatedBy(user.getId(), productName, providerId); model.addAttribute(billList, billList); } else { ListBill billList billService.getBillList(productName, providerId); model.addAttribute(billList, billList); } return billlist; } }5.3 密码存储MD5加盐不是银弹但比明文强一万倍用户密码不直接存明文而是用MD5加固定盐值salt哈希// UserServiceImpl.java Override public boolean saveUser(User user) { String salt supermarket_2024; // 固定盐值实际项目应为随机盐 String md5Password DigestUtils.md5Hex(user.getUserPassword() salt); user.setUserPassword(md5Password); return userDao.add(user) 0; } Override public User login(String userCode, String userPassword) { String salt supermarket_2024; String md5Password DigestUtils.md5Hex(userPassword salt); return userDao.getLoginUser(userCode, md5Password); }注意DigestUtils.md5Hex()来自commons-codec库需在pom.xml中添加dependency groupIdcommons-codec/groupId artifactIdcommons-codec/artifactId version1.15/version /dependency虽然MD5已被认为不安全存在碰撞攻击但在本教学系统中它比明文存储更符合安全常识——真正的生产环境应使用BCryptPasswordEncoder或PBKDF2WithHmacSHA256。5.4 JSP页面的权限裁剪用JSTLc:if实现前端可见性控制在billlist.jsp中根据角色动态显示操作按钮c:if test${userRole 1} a href${pageContext.request.contextPath}/bill/add新增订单/a /c:if td c:if test${userRole 1} a href${pageContext.request.contextPath}/bill/delete/${bill.id}删除/a /c:if a href${pageContext.request.contextPath}/bill/view/${bill.id}查看/a /td这种裁剪只是UI层隐藏真正的权限控制必须在Controller或Service层完成——因为用户可以绕过JSP直接发DELETE请求。所以BillController.deleteBill()方法内必须再次校验RequestMapping(value /delete/{id}, method RequestMethod.GET) public String deleteBill(PathVariable Integer id, Model model, HttpSession session) { Integer userRole (Integer) session.getAttribute(userRole); if (userRole ! 1) { model.addAttribute(error, 权限不足); return error; } billService.deleteBill(id); return redirect:/bill/list; }这才是RBAC的完整闭环Session存角色 → Controller读角色 → Service执行校验 → JSP按角色渲染。本文还有配套的精品资源点击获取
返回列表