ARTICLE DETAIL

资讯详情

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

SSM+JSP网上招投标系统毕设实战:建表、部署与避坑全攻略

SSM+JSP网上招投标系统毕设实战:建表、部署与避坑全攻略 简介这是一份基于SSM框架的Java网上招投标系统毕业设计项目适合Java方向毕业生、课程设计学生以及需要快速搭建完整Web系统的开发者学习参考。项目前端采用HTML/JSP结合Vue组件后台使用SpringSpringMVCMyBatis代码注释清晰数据库脚本和部署环境配置齐全可有效降低上手门槛。压缩包共812个文件以Java源码、Vue/HTML前端页面、JS脚本、CSS样式以及数据库SQL脚本为主附带图片、字体图标和开发环境配置文件整体大小仅17.32MB便于下载和本地部署。资源内含项目源码、数据库脚本、一键安装运行脚本及开发工具配置经过严格调试确保可运行系统功能完善、界面美观能直接用于毕业设计、期末大作业或课程设计。目前已有57人学习使用适合需要完整项目参考和快速复用的人群。1. 一个zip解决不了毕设SSMJSP网上招投标系统跑通才是第一关毕业设计季最常见的开场是电脑里多了一个标题为“Java毕业设计-SSMJSPHTML网上招投标系统(附源码数据库教程.zip”的压缩包。源码、数据库脚本、教程说明都齐了看起来只差最后一步启动。可实际动手时大多数人的第一个坎不在业务逻辑而在把这个SSM项目在IDEA本地跑起来依赖标红、Tomcat闪退、数据库连不上每一步都在劝退。这类网上招投标系统的核心价值是把招标公告发布、投标报名、标书上传、开标定标整条链路做进一个完整的Web项目适合拿它做JavaWeb毕设、但还不熟悉SSM整合的在校生也适合第一次接触传统SSM项目组合的开发新手。源码在硬盘上不等于系统在运行跑通才是这个zip真正开始生效的时刻。2. 从业务到建表招投标系统的公告、投标、标书状态怎么设计2.1 一条招标公告要经过几个状态流程拆解一个网上招投标系统业务上至少要覆盖六个环节招标方发布公告投标方查看公告并报名报名通过后上传标书投标截止后开标随后评标最后对中标结果公示。毕业设计里的SSM项目不可能做到电子政务那种完整度但主流程必须闭环——从“发布公告”到“产生中标结果”每一步都要有页面承接、有状态记录。我在这一类系统里一般把招标公告的状态建模成五个草稿、公告中、投标截止、评标中、已定标。草稿状态给招标方留修改空间公告中表示允许报名和投递投标截止后按钮置灰评标中意味着老师演示时会看的一段“开标记录”已定标则是整个流程的输出。这个状态机不复杂但能让答辩时的业务讲解非常顺。状态流转最好用数字存别用中文字符串。原因有两个第一SQL里写WHERE status 已定标很容易被空格、全半角字符坑到第二页面上要按状态变按钮文案和颜色数字适合在EL表达式里做c:if判断。下面的表是我在表结构里常用的取值约定status值含义页面表现1草稿只有招标方可见可编辑2公告中登录用户可见投标按钮开放3投标截止列表只读不能提交标书4评标中评委可看投标方只读5已定标展示中标人全流程结束时间字段也直接跟在公告表上end_time 一到前端按钮即使还亮着后端也要在Service层再判断一次。页面禁用只是体验问题后端拦截才是逻辑问题这条后面避坑章节还会提到。2.2 五张核心表撑起主流程MySQL建表SQL与字段边界先用SQL脚本把数据库和表建起来。招投标系统的用户角色分管理员、招标方、投标方三类实际落库我用两张表用户表存账号和角色ID角色表只做角色名解释。业务侧再加招标公告表、投标记录表、附件表五张表足够覆盖毕设需要。CREATE DATABASE tender_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE tender_db; -- 用户表 CREATE TABLE sys_user ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 用户主键, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录名, password VARCHAR(64) NOT NULL COMMENT MD5加密后的密码, real_name VARCHAR(50) NOT NULL COMMENT 真实姓名, role_id INT NOT NULL COMMENT 角色ID1管理员 2招标方 3投标方, phone VARCHAR(20) DEFAULT NULL COMMENT 联系电话, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; -- 角色表 CREATE TABLE sys_role ( id INT PRIMARY KEY, role_name VARCHAR(20) NOT NULL COMMENT 角色名 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT角色表; -- 招标公告表 CREATE TABLE tender ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 公告主键, title VARCHAR(100) NOT NULL COMMENT 招标标题, content TEXT COMMENT 招标内容, publisher_id INT NOT NULL COMMENT 发布人ID关联sys_user, budget_amount DECIMAL(12,2) DEFAULT NULL COMMENT 预算金额, start_time DATETIME NOT NULL COMMENT 报名开始时间, end_time DATETIME NOT NULL COMMENT 投标截止时间, status TINYINT DEFAULT 1 COMMENT 状态1草稿 2公告中 3已截止 4评标中 5已定标, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_publisher (publisher_id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT招标公告表; -- 投标记录表 CREATE TABLE bid_record ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 投标记录ID, tender_id INT NOT NULL COMMENT 招标公告ID, bidder_id INT NOT NULL COMMENT 投标方用户ID, bid_price DECIMAL(12,2) NOT NULL COMMENT 投标报价, description VARCHAR(500) COMMENT 投标说明, file_url VARCHAR(255) COMMENT 标书文件存储路径, status TINYINT DEFAULT 1 COMMENT 状态1待审查 2已通过 3已中标 4未中标, submit_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 提交时间, UNIQUE KEY uk_tender_bidder (tender_id, bidder_id), KEY idx_bidder (bidder_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT投标记录表; -- 附件表 CREATE TABLE attachment ( id INT PRIMARY KEY AUTO_INCREMENT, biz_type VARCHAR(20) NOT NULL COMMENT 业务类型tender或bid, biz_id INT NOT NULL COMMENT 业务记录ID, file_name VARCHAR(200) NOT NULL COMMENT 原始文件名, file_url VARCHAR(255) NOT NULL COMMENT 存储路径, upload_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT附件表;几个字段边界要解释一下。金额字段我用DECIMAL(12,2)而不是DOUBLE因为浮点运算是二进制近似评标时算总价、比报价会出幺蛾子12位精度支持到十亿级别的金额毕设项目足够。UNIQUE KEY uk_tender_bidder (tender_id, bidder_id)这行是防重复投标的兜底一个投标方对同一个招标公告只能有一条投标记录。这个唯一约束在第6章会配合代码再做一层逻辑校验属于常规做法里的双保险。建表时把唯一索引加上比只写代码检查保险得多数据库级别的约束永远不会因为漏写一行if判断而失效。sys_user.password字段只存MD5摘要不存明文。老毕设项目里经常看到直接存明文密码的答辩时被老师一问“你这密码安全吗”就卡壳。哪怕用的是数据库教程脚本也建议在Service注册逻辑里对密码做一次MD5再入库。2.3 SSM三件套怎么分活一次投标请求在三个框架间的落地链路SSM是Spring、SpringMVC、MyBatis三个框架的组合刚入门JSP的人最容易把它们当成三个互相独立的东西记其实它们是一条请求链路上的三段分工。Spring管对象SpringMVC管HTTP请求分发MyBatis管SQL和数据映射。拿“提交标书”这个操作讲浏览器发一个POST请求到TomcatTomcat把请求交给SpringMVC的DispatcherServletDispatcherServlet根据RequestMapping找到BidController里的submit方法Controller把请求参数封装成BidRecord对象交给Service层。Service层校验截止时间、校验状态调用MyBatis生成的Mapper接口Mapper执行SQL把记录落到数据库最后返回结果给页面。一次正常的数据库增删改查在这个结构里要穿四层每一层各干各的这也是导师愿意认可这类项目的原因三层架构能讲清楚比一个放满业务逻辑的Servlet类有说服力。事务边界放在Service层用Transactional注解标在方法上。比如“保存投标记录”这个操作如果只插一条表事务不事务看起来差别不大但同一个Service方法里要做“插入bid_record 更新tender表的报名人数”两个动作时不加事务就会出现记录插进去了、人数没加上这种脏数据。毕设要我写数据一致性相关问题我会从这一处入手讲。提示SSM项目里Spring的applicationContext.xml负责管Service、Mapper、数据源spring-mvc.xml只扫描Controller。这个父子容器关系保持默认即可别为了省事把两个扫描配成同一个package否则Controller里的事务代理很容易失效。3. 把zip跑成能打开登录页的站点版本对齐、配置文件、部署顺序3.1 新版IDEA导入老项目的三个前置检查JDK、Maven、Tomcat版本对齐下载来的zip解压后常见目录结构一般是源码文件夹、一个sql文件、一份教程文档。源码拿到手不要急着点开先检查本机环境。做SSM毕设的人我建议把环境统一成JDK 1.8、Maven 3.6.3、Tomcat 8.5或9.0、IDEA 2021及之后版本。注意Tomcat 10以上不要用Tomcat 10把包名从javax.servlet换成了jakarta.servlet老项目的代码全是在javax下编译的直接部署会报各种ClassNotFound异常这类报错排查起来非常耗时。IDEA打开项目后第一步点右侧Maven面板看依赖能不能正常解析。如果pom.xml里几百行依赖全部标红或者一直在下载状态先检查两个地方本地Maven仓库路径配没配settings.xml里的镜像地址通不通。IDEA默认可能用了内置的Maven仓库路径在C盘用户目录下下载慢而且容易半路失败用下面的方法换成自己指定的仓库# 在IDEA里File - Settings - Build, Execution, Deployment - Build Tools - Maven # 把 Maven home path 指到本地解压的 maven 3.6.3 目录 # 把 User settings file 指到本地 conf/settings.xml # 在 settings.xml 里找到 本地仓库路径配置 localRepositoryD:/maven-repo/localRepository配置完成后IDEA右下角会提示重新导入Maven项目等依赖全部下载完再继续下一步。这个检查要先做因为后面的编译和部署全部建立在依赖完整上。还要检查Project Structure里的Project SDK和Language Level。老毕设源码多数基于Java 8语法把Language Level指到8Modules里每个模块的依赖也要对应。曾经见过一个项目JDK配的17Maven编译直接报“无效的源发行版”改回8立刻通过。这类问题通常在File - Project Structure - Project里语言级别一次就能改好。3.2 核对jdbc.properties和spring-mvc.xml参数写错项目直接白屏版本环境没有用接着看配置。跑SSM项目最核心的是三份配置文件jdbc.properties管数据库连接applicationContext.xml管容器与MyBatisspring-mvc.xml管Web请求与静态资源。先说jdbc.properties这是登录页能不能查出用户数据的关键jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/tender_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password你的数据库密码url里有三个参数值得记一下。useUnicodetruecharacterEncodingutf8解决JSP页面与数据库之间的中文乱码useSSLfalse是为了避免MySQL 8.0在本地连接时老报警告或者直接拒绝serverTimezoneAsia/Shanghai处理时区问题不加它MySQL 8.0会报“The server time zone value”的异常。如果你的MySQL是8.0以上版本驱动类名改成com.mysql.cj.jdbc.Driver旧的com.mysql.jdbc.Driver在新版驱动里已经标记过时能连但会有打印提示。spring-mvc.xml里我最看重三块注解驱动、静态资源放行、视图解析器。SSM项目页面目录一般约定为WEB-INF/views源码里如果看到不同的包结构对应改prefix前缀就行。!-- 扫描Controller层 -- context:component-scan base-packagecom.tender.controller/ !-- 开启SpringMVC注解驱动 -- mvc:annotation-driven/ !-- 放行静态资源CSS、JS、图片放在/static/下 -- mvc:resources mapping/static/** location/static// !-- JSP视图解析器 -- bean classorg.springframework.web.servlet.view.InternalResourceViewResolver property nameprefix value/WEB-INF/views// property namesuffix value.jsp/ /beanmvc:resources这一行特别容易被漏掉。SSM项目用了SpringMVC拦截所有请求后如果没有显式放行静态资源浏览器里CSS、图片全部404页面光秃秃的只剩HTML标签。第一次跑这类项目的人十有八九在这里踩坑后面避坑章节会再展开。还有一点容易忽略spring-mvc.xml把suffix配成.jsp那Controller返回的字符串就是视图名比如return tender/list实际文件必须放在WEB-INF/views/tender/list.jsp路径不能有一级对不上否则Tomcat报404错误日志里是“Could not resolve view with name”。3.3 用Maven拉依赖pom.xml里最关键的标签与慢依赖处理SSM项目依赖坐标看起来多核心就这几个Spring全家桶、MyBatis、MyBatis-Spring适配包、MySQL驱动、JSTL、Servlet API。我一般会在pom.xml里固定成下面这份组合版本彼此验证过不冲突properties spring.version5.2.5.RELEASE/spring.version /properties dependencies !-- Spring核心 -- dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version${spring.version}/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-jdbc/artifactId version${spring.version}/version /dependency !-- MyBatis与Spring桥接 -- dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version3.5.6/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version2.0.6/version /dependency !-- MySQL驱动 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.22/version /dependency !-- JSP标准标签库 -- dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency /dependencies这里解释一下版本选择。Spring用5.2.x对JDK8完全兼容同时支持javax命名空间比老掉牙的4.x更稳。MyBatis用3.5.6配合mybatis-spring 2.0.6这套组合编译出来的项目在Tomcat 9.0下运行正常。mysql-connector-java选8.0.22驱动类名与前面jdbc.properties对应。Maven下载慢这个问题在地域网络环境下很常见。解决方式是在settings.xml里配阿里云镜像用镜像地址替换中央仓库mirror idaliyunmaven/id namealiyun maven/name urlhttps://maven.aliyun.com/repository/public/url mirrorOfcentral/mirrorOf /mirror配置完建议把本地仓库里之前下了一半的lastUpdated文件删掉不然Maven会认为依赖已存在而不重新下载。我通常会直接删掉D:/maven-repo下所有以.lastUpdated结尾的文件再执行一次mvn clean compile输出BUILD SUCCESS说明依赖完整。3.4 部署到Tomcat本地验证war exploded和war的区别本地验证用IDEA的Tomcat运行方式。在Run/Debug Configurations里新增Tomcat Server - LocalDeployment选项卡加一个Artifact。这里要说明一个最常见的迷惑点SSM项目在IDEA里会被识别成war包如果你看到Maven构建它是war直接把它部署上去IDEA每次改代码都要重新打war再重启开发调试很痛苦。正确的做法是在Deployment里选war exploded。war exploded的意思是“解压后的war目录”Tomcat直接指向编译输出目录修改JSP甚至不用重启刷新页面就能看到效果war是打包后的单文件适合最后导出给老师或部署到服务器用。两者的本质区别只是一个用目录、一个用压缩包别在本地开发时选错。部署后访问地址一般是http://localhost:8080/项目上下文路径/。上下文路径在Deployment选项卡的Application context里比如填/tender那就是http://localhost:8080/tender。这个路径直接影响后面页面里所有ajax请求和表单提交地址初学者经常忽略它导致登录成功跳转到一个404页面。我习惯让上下文路径跟数据库名保持一致减少混淆。4. JSP与HTML怎么分工列表页、Ajax交互和标书上传下载4.1 JSP页面用EL和JSTL替代Java代码片段传统JSP项目里页面后缀是.jsp但不要在JSP里写大段% %脚本片段。这是老项目的常见坏习惯也是导师翻代码时最反感的部分。JSP的价值在于用EL表达式和JSTL标签把Java代码从页面里剥离出去让页面只做展示。下面的代码是从一个招标公告列表页里摘出来的常见写法循环渲染公告列表并根据状态显示不同按钮% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % % taglib prefixfmt urihttp://java.sun.com/jsp/jstl/fmt % table classtable table-hover thead tr th公告标题/th th预算金额/th th投标截止时间/th th状态/th th操作/th /tr /thead tbody c:forEach items${pageResult.list} vartender tr td${tender.title}/td tdfmt:formatNumber value${tender.budgetAmount} pattern#,##0.00//td tdfmt:formatDate value${tender.endTime} patternyyyy-MM-dd HH:mm//td td c:choose c:when test${tender.status 1}草稿/c:when c:when test${tender.status 2}公告中/c:when c:when test${tender.status 3}已截止/c:when c:when test${tender.status 5}已定标/c:when /c:choose /td td c:if test${tender.status 2} a hrefjavascript:void(0) onclicksubmitBid(${tender.id})我要投标/a /c:if /td /tr /c:forEach /tbody /table注意上面用了fmt:formatDate日期格式化在JSP里必须经过这个标签处理直接输出${tender.endTime}会显示成一连串时间戳数字。金额同理用fmt:formatNumber把64600.0变成64,600.00演示时好看答辩时也能说明你考虑了用户体验。页面顶部那两行% taglib %是JSP指令作用是引入JSTL标签库。缺了它c:forEach会被当成普通文本输出到页面上或者直接报错。新手把JSP从一处复制到另一处时经常漏掉这两行。4.2 从HTML页面用Ajax调后端接口请求路径和上下文问题有些页面是纯HTML放在webapp根目录下不走JSP渲染。这时前端拿到的数据要看接口返回的JSON。常见做法是用jQuery的ajax方法请求Controller注意路径要拼上项目的context path否则部署在非ROOT路径下时接口全404。!doctype html html langzh-cn head meta charsetutf-8 meta nameviewport contentwidthdevice-width, initial-scale1 title投标操作/title script srchttps://cdn.jsdelivr.net/npm/jquery3.6.0/dist/jquery.min.js/script /head body script // 从服务端渲染的 hidden 字段里拿 contextPath var contextPath $(#contextPath).val(); function submitBid(tenderId) { var price $(#bidPrice).val(); if (!price || parseFloat(price) 0) { alert(请填写正确的投标报价); return; } $.post(contextPath /bid/submit, { tenderId: tenderId, bidPrice: price }, function (res) { if (res.code 200) { alert(投标成功); window.location.reload(); } else { alert(res.msg); } }, json); } /script /body /html这里的关键参数是contextPath。如果你的项目部署在Tomcat的root上下文那contextPath是空字符串可以直接写/bid/submit部署在/tender下就必须写成/tender/bid/submit。为了避免写死我一般在JSP正文里嵌一个隐藏域input typehidden idcontextPath value${pageContext.request.contextPath}/${pageContext.request.contextPath}是JSP EL内置的上下文路径获取方式页面渲染后自动变成/tender不管部署在哪里都正确。纯HTML页面没有EL表达式那就只能在前端脚本里硬编码或者从后端某个接口带回来这也是我尽量把动态页面做成JSP的原因。还有一种更稳妥的写法是把$.post的url改成相对路径不带前导斜杠比如$(#contextPath).val() /bid/submit这种拼接永远别在ajax里写死带http://localhost:8080的绝对地址换端口、换服务器就要改代码。提醒一下检查一下js放的位置如果HTML在根目录而JS文件在/static/js/下引用路径也要带上${pageContext.request.contextPath}。4.3 标书文件上传下载MultipartFile和文件落盘路径设计招投标系统离不开文件上传标书、资质证明都要作为附件存下来。SSM项目里用MultipartFile接收上传文件是最常规的做法。先在spring-mvc.xml里注入上传解析器bean idmultipartResolver classorg.springframework.web.multipart.commons.CommonsMultipartResolver property namemaxUploadSize value10485760/ property namedefaultEncoding valueUTF-8/ /beanmaxUploadSize的单位是字节10485760对应10MB。毕设项目一般10MB够用老师上传的标书文档通常不大如果你支持上传图纸或带图片的PDF可以把这个值调到20MB但不要无限放大太大占用磁盘也影响Tomcat吞吐。defaultEncodingUTF-8解决上传文件名中文乱码。Controller里的接收代码PostMapping(/bid/file/upload) ResponseBody public Result upload(RequestParam(file) MultipartFile file, RequestParam(tenderId) Integer tenderId, HttpServletRequest request) { if (file null || file.isEmpty()) { return Result.error(上传文件不能为空); } String originalFilename file.getOriginalFilename(); String ext originalFilename.substring(originalFilename.lastIndexOf(.)); String uuidName UUID.randomUUID().toString().replace(-, ) ext; // 存储到项目外部目录避免重新部署导致文件丢失 String saveDir D:/tender_upload/; File dir new File(saveDir); if (!dir.exists()) { dir.mkdirs(); } try { file.transferTo(new File(saveDir uuidName)); } catch (IOException e) { return Result.error(文件保存失败 e.getMessage()); } // 保存附件记录到attachment表 attachmentService.save(bid, tenderId, originalFilename, saveDir uuidName); return Result.success(上传成功, saveDir uuidName); }保存路径的设计是个经典坑。我明确把上传目录放到D:/tender_upload/这种项目外部路径而不是放在Tomcat部署目录的/upload/下面。原因很简单IDEA里每次重新部署war exploded时Tomcat会清空旧的部署目录放在webapp下的上传文件全部丢失。外部目录不受重新部署影响演示时上传的标书文件能一直保留到答辩结束。下载的实现思路是在数据库里查attachment记录拿到fileUrl然后通过response.getOutputStream()把文件流写回浏览器设置Content-Disposition响应头为attachment;filenamexxx即可触发浏览器下载。5. 避坑清单从项目导入到答辩演示最常翻车的六个地方5.1 导入IDEA后Java文件全部标红Maven仓库没配好现象打开zip里的源码所有import com.*、import org.springframework.*全部标红项目结构里没有JDK和依赖库整个工程看起来是坏的。原因IDEA没有把项目识别成Maven工程或者本地Maven配置不正确依赖根本没下载到本地仓库。这不是源码问题是环境识别问题。解决先用IDEA打开项目根目录下的pom.xml右键选择“Add as Maven Project”让IDEA把它当Maven工程导入然后在Settings里检查Maven home path和User settings file是否指向你本地的Maven。如果本地连Maven都没装直接去官网下载3.6.3版解压到磁盘配好镜像再回到IDEA刷新。这个步骤走完标红基本会在一分钟内消除。5.2 Tomcat启动秒退JDK与Tomcat版本不兼容现象Run Tomcat后控制台闪一下就结束查看日志发现启动失败或者有UnsupportedClassVersionError。这类报错在本地和服务器环境都可能出现。原因Tomcat运行在JDK版本上而项目编译用的JDK版本和Tomcat支持的版本不一致典型的是JDK17编译的class在JDK8上运行或者老项目代码里用了javax.servlet包但在Tomcat 10以上版本部署。解决统一到JDK1.8同时在IDEA的Project Structure里把Project SDK和Modules的Language level都改成8。若项目已经在Tomcat 10环境换回Tomcat 8.5或9.0这两个版本对老毕设项目最友好。Tomcat 9.0配JDK1.8是SSM项目的黄金组合我在多个项目里验证过稳定且问题少。5.3 登录页出来但列表查不到数据MySQL连接参数与驱动版本不匹配现象项目启动成功页面能打开但一旦查数据库就抛异常日志里有Communications link failure或Access denied for user有时还会在控制台看到“Public Key Retrieval is not allowed”。原因第一MySQL服务端版本和驱动版本不一致比如MySQL 8.0却配了5.x的驱动第二连接URL缺少useSSLfalse或serverTimezone参数导致握手失败第三密码不对或者root账户不允许远程登录。解决检查本地MySQL版本8.0以上把驱动坐标换成mysql-connector-java 8.0.22jdbc.properties里的driverClass改成com.mysql.cj.jdbc.Driverurl加上useSSLfalseserverTimezoneAsia/Shanghai。密码特殊符号多的检查jdbc.properties里有没有被空格影响。另外在测试时直接在Navicat里用同一账号连一遍能连上说明配置问题连不上说明MySQL权限问题。5.4 页面中文一片乱码三处编码必须一致现象登录页正常进了系统后所有中文标题、公告内容变成问号或乱码前端页面能显示中文但数据库里存进去是乱码。原因编码问题要在三处对齐——文件编码、请求响应编码、数据库连接编码。JSP文件本身的编码不对页面渲染就乱数据库连接URL里没加characterEncodingutf8数据读写乱tomcat的URIEncoding没配URL传参中文会乱。解决IDEA里Settings - Editor - File Encodings把Global Encoding、Project Encoding、Default encoding for properties files全部改为UTF-8这一步对源码与数据库脚本是统一编码。jdbc.properties里的characterEncodingutf8必须保留。Tomcat启动可以在catalina.bat里加-Dfile.encodingUTF-8但一般只改IDEA编码和数据库连接参数就能解决。MySQL建库脚本里用utf8mb4前面建表SQL已经写了别改成默认的latin1。5.5 打开页面CSS和图片全丢SpringMVC把静态资源拦了现象首页HTML结构出来了但样式全无控制台里一堆/static/css/...返回404图片也是裂开的。原因DispatcherServlet默认拦截所有请求配置/静态资源的请求也进了SpringMVC的处理链但是Controller里没有对应映射导致404。解决在spring-mvc.xml里加静态资源放行配置。两种方式任选我用的是mvc:resources映射明确把/static/和/css/目录交给DefaultServlet处理。还有一招是用mvc:default-servlet-handler/把所有SpringMVC找不到的静态请求放回Tomcat默认Servlet这个配置更省事适合静态资源散布在多个目录的项目。加完配置后重启Tomcat验证一次CSS和图片能恢复正常。5.6 本地能跑、war包放到服务器跑不起来环境差异排查现象本地IDEA运行一切正常导出war包传到云服务器Tomcat部署后访问报404或者启动时class找不到。原因本地环境JDK8、Tomcat9服务器可能装了OpenJDK11或Tomcat10。老项目在Tomcat10上的典型症状就是NoClassDefFoundError因为javax.servlet的包路径变了。另一种可能是项目打成war后上下文路径变了访问地址里没有带上项目名。解决先看服务器上Tomcat版本如果日志里报jakarta.servlet相关错误把Tomcat换回9.0或8.5。再确认war包解压目录名访问地址加上目录名/tender实在不ok就把war改名成ROOT.war部署在Tomcat根路径访问地址直接用IP加端口。数据库也要检查服务器上的MySQL版本、“jdbc.properties”里写的localhost是不是服务器地址、账号有没有远程访问权限。本地与服务器环境差异是这类老项目部署的固定痛点装环境时用尽所有工具对齐版本是最省后续时间的做法。6. 做一个防重复投标校验一条SQL加一次Service拦截答辩时老师最喜欢挑的一种问题是“能不能重复投”如果系统允许同一个投标方对同一个招标公告反复提交标书那评标环节的公正性就无从谈起。这里演示一个前置校验加数据库约束的完整处理作为二次开发动手的参考。数据库层前面已经建了唯一索引uk_tender_bidder现在在Mapper里写一个查重方法select idcountByTenderAndBidder resultTypeint SELECT COUNT(1) FROM bid_record WHERE tender_id #{tenderId} AND bidder_id #{bidderId} /selectService层在校验截止时间之后、执行插入之前调用它Transactional public Result submitBid(BidRecord bid) { int count bidMapper.countByTenderAndBidder(bid.getTenderId(), bid.getBidderId()); if (count 0) { return Result.error(您已对该标段提交过标书不能重复投标); } try { bidMapper.save(bid); return Result.success(投标成功); } catch (DuplicateKeyException e) { return Result.error(重复投标系统已拦截); } }加DuplicateKeyException捕获是防止并发场景下两次请求同时通过查询检查然后都由唯一索引拦截此时返回给用户的是友好提示而不是500异常。这便是数据库约束做兜底的思路Service层查重是业务体验唯一索引是最终防线。验证方法很简单用一个投标方账号对同一个公告提交两次投标第一次提示成功第二次提示重复不能投再用另一个投标方账号提交确认能正常投递。把这两步结果截个图答辩时可以说明“我从界面校验到数据库约束做了两层控制”。项目跑通后再推荐一个自检动作把数据库中tender表的end_time改成过期时间用投标方账号进去看能不能继续提交标书。大多数毕设项目的后端只在Service层判断状态但对时间边界处理得粗糙这一手检查能提前发现“截止时间失效”的隐患。我自己做项目时养成的习惯是先写数据约束再写业务判断顺序颠倒过来很容易漏掉兜底老项目的坑大多是这种不起眼的边界条件早一天发现答辩就少一分手抖。希望这篇SSM招投标系统的落地笔记帮到你照着这个顺序跑通它再往自己的业务方向加功能就不难了。本文还有配套的精品资源点击获取
返回列表