ARTICLE DETAIL

资讯详情

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

SSM+Vue全栈实战:汉服文化平台开发与部署全解析

SSM+Vue全栈实战:汉服文化平台开发与部署全解析 简介基于SSMVue的汉服文化平台网站设计与实现项目包面向计算机相关专业正在做毕业设计的学生及需要项目实战的Java学习者也适用于课程设计和期末大作业场景。项目包含完整源码、数据库脚本、开发说明文档管理员端与用户端功能划分清晰前台集成首页、服装展示、论坛、购物车、在线客服等模块。压缩包共867个文件以Java源码、Vue组件、JavaScript脚本、HTML/CSS页面、SVG图标及SQL脚本为主附带部署与开发所需的配置文件整体约21.74MB。已有138人学习下载内含万字设计文档、演示视频、部署视频教程和代码讲解视频并配套全套软件工具可辅助读者快速搭建运行环境理解SSM与Vue项目的整合思路和常见问题排查路径。1. 基于SSMVue的汉服文化平台这个项目到底解决什么问题适合谁来做汉服文化平台网站不是静态展示页它一般要同时管内容、用户、活动和商品是个典型的“内容交互”全栈项目。基于SSMVue的实现方案后端用SSM三件套做接口前端用Vue做单页应用前后端分离、数据通过JSON交流。标题里带了源码、论文、部署视频说明这是一个课程设计或毕业设计级别的完整交付物。适合谁有Java基础、想拿一个能跑通、能答辩、能写进简历的全栈作品的人。如果你也在纠结“汉服文化平台怎么搭”这篇文章把数据库建表、SSM后端、Vue前端、部署联调、避坑和并发一致性全部拆开讲一遍你照着做就能跑通。2. 架构拆解SSM三个框架和Vue前端在汉服平台里各自管什么2.1 请求链路里的Spring、SpringMVC、MyBatis很多人 java 基础学完之后第一个动手项目就是 SSM。它解决同一个问题当浏览器把请求发给服务端SSM 内部做了一次非常标准的分工。Spring 负责用 IoC 容器管理对象把 Controller、Service、Mapper 的依赖关系通过 Autowired 自动注入SpringMVC 负责接收 HTTP 请求把 URL 映射到具体的 Controller 方法MyBatis 负责把 SQL 语句和 Java 方法的调用绑定起来处理数据库读写。放到汉服文化平台里一次“查询汉服列表”的完整链路是前端 Vue 用 axios 发 GET /api/hanfu/page?page1size10 请求这个请求先到 SpringMVC 的 DispatcherServlet它根据 URL 找到 HanfuController 的 page 方法Controller 调 HanfuService 的业务方法Service 里通过 HanfuMapper 接口执行 MyBatis 映射的 SQL 查询 MySQL结果封装成 JSON 返回给前端。这条链路由三个框架协作完成每一层都可以单独测试出问题时定位非常容易。选这个组合是因为汉服平台的核心业务就是用户管理、内容展示、活动报名、评论互动本质上是 CRUD 加上关联查询没有分布式和消息队列的复杂度。MyBatis 在这种场景的优势尤其明显很多查询很碎比如按朝代筛选汉服款式、按热度排序文化文章、统计活动报名人数MyBatis 允许你在 XML 里写动态 SQL用if标签拼条件比全自动 ORM 更直接。而且 SSM 在 java 面试中也是常被问到的框架组合能把这条链路讲清楚比背 java 面试题八股文有用得多。注意一个常见误用很多人喜欢把 SQL 写死在 Java 代码里用字符串拼接参数。其实规范做法是让 Mapper 接口和 XML 配合namespace 指向接口全类名SQL 里用#{}占位符传参不要用${}直接拼接。#{}会预编译能防 SQL 注入这是 MyBatis 最需要守住的底线。2.2 Vue端如何组织页面组件化、路由、Axios与状态保持Vue 在前端解决的是页面展示和跳转。汉服文化平台的页面很多首页轮播、汉服图鉴列表、文章详情、活动报名、个人中心。如果每个页面都是一个独立 html用户每次跳转都要刷新整个页面体验会很差。Vue 的组件化把轮播、卡片、评论列表这些重复出现的内容抽成组件一个页面只是一组组件拼装后的结果修改单个组件不会影响其他页面。页面跳转由 vue-router 负责。典型的路由配置是/首页/hanfu汉服图鉴/article/:id文章详情/activity活动列表/login登录。这里的:id就是 vue 动态路由参数在详情页组件里用useRoute()拿到route.params.id再向后端发查询请求。热词里的 vue 路由参数、vue 动态路由指的就是这种用法。千万不要在路由配置里给每个 id 都写死一个路径那会变成静态路由的灾难。向后端发请求我一般用 axios并且会做一层统一封装。在src/utils/request.js里创建一个 axios 实例配置baseURL: /api、超时时间、请求拦截器和响应拦截器。请求拦截器从 localStorage 取 token 塞进请求头响应拦截器检查后端返回的 codecode 不是 0 就直接 reject。这样每个页面调用接口时不需要重复处理登录失效和业务错误代码会清爽很多。热点里搜 vue axios 的人多半就是卡在拦截器这一步。状态保持方面汉服平台通常只需要保存当前登录用户信息和登录状态Vue 3 里用 Pinia、Vue 2 里用 Vuex 都可以。如果只是少数几个组件共享数据不一定非得引入状态库直接导出一个 reactive 对象就够了。要避免的是把后端返回的大列表也放进全局状态那会增加不必要的内存压力也会让组件之间的数据流变得难查。很多新手在 vue 入门时容易把状态管理当成必配项其实小项目里能省就省。2.3 前后端数据契约接口路径、JSON结构和状态码怎么定前后端分离项目最怕的就是前端写一个路径、后端写另一个路径最后联调时互相怀疑。我一般会先把数据契约定死再动手写代码。先约定路径。所有接口统一加/api前缀后端 Controller 的RequestMapping也写/api/xxx。比如前端 axios 的 baseURL 设为/api实际请求/api/hanfu/page后端 HanfuController 类上写RequestMapping(/api/hanfu)方法上再写GetMapping(/page)。这样路径的来源是唯一的不会出现前端拼多了或少了一段的情况。再约定返回结构。后端所有接口返回同一个 Result 对象格式固定为{ code, message, data }。code 为 0 表示成功非 0 表示业务错误。分页接口的 data 固定成{ total, list }前端封装分页组件时不用每个接口单独适配。如果有的接口返回数组、有的返回对象、有的返回{ success: true }前端拦截器会越写越复杂最后乱七八糟。HTTP 状态码层面正常 200、未登录 401、无权限 403、参数错误 400、服务器内部错误 500。但很多项目把业务错误也用 200 返回靠 code 区分这样是可以接受的只要别把业务错误和 HTTP 错误混在同一套判断里。很多人搜“前后端分离”其实核心就是把这套契约定在前端开发之前而不是边写边改。3. 落地顺序从数据库建表到前后端接口联调的关键代码3.1 数据库表设计用户、款式、文章、活动、评论之间的关联建库之前先想清楚业务关系。汉服文化平台至少有五张核心表用户表、汉服款式表、文化文章表、活动表、报名表。评论表可以作为第六张但评论往往挂在文章或汉服款式下关系更简单。以下是一套可以跑通的最小表结构。CREATE DATABASE hanfu_db CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, nickname VARCHAR(50), avatar VARCHAR(255), role TINYINT DEFAULT 2 COMMENT 1管理员 2普通用户, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE hanfu ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, dynasty VARCHAR(30) COMMENT 朝代, description TEXT, image_url VARCHAR(255), price DECIMAL(10,2), stock INT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE article ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, title VARCHAR(200) NOT NULL, content LONGTEXT, view_count INT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES user(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE activity ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(200) NOT NULL, location VARCHAR(200), start_time DATETIME, max_people INT DEFAULT 50, signup_count INT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE signup ( id INT PRIMARY KEY AUTO_INCREMENT, activity_id INT NOT NULL, user_id INT NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_activity_user (activity_id, user_id), FOREIGN KEY (activity_id) REFERENCES activity(id), FOREIGN KEY (user_id) REFERENCES user(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;逻辑说明user 表是最基础的登录表汉服平台不需要复杂权限系统role 字段就够区分管理员和普通用户。hanfu 表存汉服款式price 和 stock 提前留好了商品化入口article 表是文化文章user_id 指向作者activity 和 signup 是一对多的报名关系signup 表上的唯一索引 uk_activity_user 能在数据库层挡住一个人重复报名。外键在这个数据量下建议保留它能保证引用完整性代价只是插入顺序必须严格先插 user再插 article。注意字符集用 utf8mb4不要用 utf8因为文章正文可能包含生僻汉字和 emojiutf8 会在写入时报错或变成问号。所有表都用 InnoDB支持事务和外键。stock 字段如果后面要做购买流程还需要再加版本号做乐观锁那是后话。3.2 后端接口骨架Controller、Service、Mapper 各写什么以后端“分页查询汉服列表”为例先写 Mapper 接口和 XML。// HanfuMapper.java package com.hanfu.mapper; import com.hanfu.entity.Hanfu; import org.apache.ibatis.annotations.Param; import java.util.List; public interface HanfuMapper { ListHanfu selectPage( Param(offset) int offset, Param(size) int size, Param(keyword) String keyword, Param(dynasty) String dynasty ); int selectCount(Param(keyword) String keyword, Param(dynasty) String dynasty); }!-- HanfuMapper.xml -- mapper namespacecom.hanfu.mapper.HanfuMapper select idselectPage resultTypecom.hanfu.entity.Hanfu SELECT id, name, dynasty, description, image_url, price, stock FROM hanfu where if testkeyword ! null and keyword ! AND name LIKE CONCAT(%, #{keyword}, %) /if if testdynasty ! null and dynasty ! AND dynasty #{dynasty} /if /where ORDER BY id DESC LIMIT #{offset}, #{size} /select /mapper逻辑说明SQL 写在 XML 里where加if动态拼接条件能避免“where 前面多一个 and”的经典问题。Param指定参数名XML 里用#{keyword}取值。注意 LIMIT 的 offset 和 size 都是整数MyBatis 会安全地设置参数不用担心注入。selectCount 用来返回总条数给前端分页组件算页数用。Service 层的关键是业务组织和事务控制。以下是一个简单 Service 实现。Service public class HanfuServiceImpl implements HanfuService { Autowired private HanfuMapper hanfuMapper; Override public ListHanfu page(int offset, int size, String keyword, String dynasty) { return hanfuMapper.selectPage(offset, size, keyword, dynasty); } Override public int count(String keyword, String dynasty) { return hanfuMapper.selectCount(keyword, dynasty); } }Controller 层只负责接收参数、调用 Service、返回 Result。RestController RequestMapping(/api/hanfu) public class HanfuController { Autowired private HanfuService hanfuService; GetMapping(/page) public Result page( RequestParam(defaultValue 1) int page, RequestParam(defaultValue 10) int size, RequestParam(required false) String keyword, RequestParam(required false) String dynasty ) { int offset (page - 1) * size; ListHanfu list hanfuService.page(offset, size, keyword, dynasty); int total hanfuService.count(keyword, dynasty); return Result.ok(PageData.of(total, list)); } }参数说明page是第几页size是每页几条Controller 里把 page 换算成 offset而不是让前端传 offset。required false表示 keyword 和 dynasty 可以缺省。Result.ok 是静态方法统一包装返回值。这里有一个容易被忽略的点前端分页组件传的 page 从 1 开始而 SQL 的 LIMIT offset 从 0 开始所以必须做(page - 1) * size换算漏掉这一步第一页会凭空少一条数据。3.3 Vue前端初始化Vite创建项目、axios封装、路由挂载标题没有限定 Vue 版本如果是从零做我更推荐 Vue 3 Vite Element Plus组件库生态完整如果是为了配合老教材用 Vue 2 Element UI 也完全可以前后端接口部分没有任何区别。npm create vitelatest hanfu-web -- --template vue cd hanfu-web npm install vue-router4 axios element-plus说明Vite 创建的是 Vue 3 项目模板src 下有 main.js、App.vue、components 目录。vue-router4 对应 Vue 3axios 是请求库element-plus 是 UI 组件库。如果你用 Vue 2则装 vue-router3 和 element-ui版本对应错误会出现路由不生效或组件白屏。然后封装 axios 实例。// src/utils/request.js import axios from axios const request axios.create({ baseURL: /api, timeout: 10000 }) request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) config.headers.Authorization token return config }) request.interceptors.response.use(res { const data res.data if (data.code ! 0) { return Promise.reject(new Error(data.message)) } return data }, err { return Promise.reject(err) }) export default request逻辑说明baseURL 设置为/api这样请求/api/hanfu/page时axios 会拼成当前域名下的/api/hanfu/page再依赖 devServer 代理转发到后端。响应拦截器把code ! 0的响应全部 reject页面调用方只需要 catch 一次就行。注意这里返回的是data而不是res所以页面里const { list } await request.get(...)拿到的是业务数据不用再取 .data。在 main.js 里挂载路由、Element Plus 和 axios 实例。// src/main.js import { createApp } from vue import App from ./App.vue import router from ./router import ElementPlus from element-plus import element-plus/dist/index.css import request from ./utils/request const app createApp(App) app.use(router) app.use(ElementPlus) app.config.globalProperties.$http request app.mount(#app)参数说明app.use(router)把路由插件注册进去app.use(ElementPlus)让所有页面能用 el-button、el-table 这类组件。把 request 挂到全局属性$http后选项式 API 里可以用this.$http调用但组合式 API 没有 this更建议直接在页面里import request。main.js 是前端入口这里漏掉任何一个 use 都会导致页面白屏或样式丢失。路由文件src/router/index.js里正常导出路由数组再在 main.js 里 use 即可。4. 本地部署把后端war包和前端构建产物跑通在同一个浏览器4.1 后端环境准备JDK、Maven、Tomcat、MySQL 的参数配置先把环境对齐。SSM 项目用 JDK 1.8 最稳Maven 3.6 以上MySQL 5.7 或 8.0Tomcat 8.5 或 9。JDK 安装后要先检查 java 环境变量配置对不对IDEA 里能跑、命令行 mvn 命令找不到多半是 JAVA_HOME 和 PATH 没有配好。后端工程用 IDEA 打开后先等 Maven 依赖下载完成。SSM 的核心依赖有 spring-webmvc、mybatis、mybatis-spring、mysql-connector-java、druid 连接池、jackson-databind。版本选择上Spring 5.1.x 配 MyBatis 3.5.x 是经过大量项目验证的组合不建议随手升到最高版。依赖冲突的典型表现是启动时 NoSuchMethodError 或 ClassNotFoundException出现这种情况先检查 pom 里是不是把老版本的 spring 也带进来了。数据库连接串放在src/main/resources/db.properties。jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/hanfu_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password123456参数说明MySQL 5.7 的 driver 是com.mysql.jdbc.DriverMySQL 8 必须改成com.mysql.cj.jdbc.Driver写错启动直接报 ClassNotFoundException。url 里的useUnicodetruecharacterEncodingutf8保证中文写入正确serverTimezoneAsia/Shanghai解决 MySQL 8 的时区报错。连接池我用 druid它的配置里initialSize、maxActive这两个参数调整一下单机项目保持默认也可以。部署方式有两种常见选择IDEA 里配置 Tomcat 直接跑或者mvn clean package打出 war 包放进 Tomcat 的 webapps 目录。我倾向打 war 包部署因为更贴近真实环境而且能提前发现打进去的配置文件遗漏问题。pom.xml 里 packaging 要设置成packagingwar/packaging同时把 sqljdbc 这种运行期才需要的依赖标注为 provided避免打进 war 里引起冲突。4.2 前端构建npm install 的细节与 build 产物前端依赖安装建议用npm install安装完成后先跑npm run dev看开发服务能不能起来。Vite 默认端口是 5173启动成功后用浏览器打开localhost:5173能看到页面。出现vite命令找不到说明依赖没有装齐删除 node_modules 和 package-lock.json 后重新装一遍。打包命令是npm run build默认在 dist 目录生成静态文件。构建产物可以直接放进 Nginx 的 html 目录也可以扔到 Tomcat 里。这里有一个高频坑vue-router 如果用 history 模式直接访问/article/1会 404因为服务器不知道这个路径对应谁。Nginx 需要加一条回退规则。server { listen 80; server_name localhost; root /usr/local/nginx/html/hanfu; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://localhost:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }参数说明try_files $uri $uri/ /index.html会让所有前端路由在找不到真实文件时回退到 index.html由 vue-router 接管。location /api/把接口请求反向代理到后端 Tomcat 的 8080 端口这样前后端就处在同一个域名下不再有跨域问题。如果项目部署在子路径还需要调整 Vite 的 base 配置否则 js/css 加载路径会带着绝对路径前缀。如果你不想装 Nginx也可以用 Tomcat 部署前端 dist。把打包产物放到 Tomcat 的 webapps/ROOT 目录注意在 Vue 项目里设置base: ./让资源路径变成相对路径。这个方法更适合演示和答辩缺点是并发能力不如 Nginx但对汉服文化平台这种负载足够。4.3 联调核心跨域 CORS 与 vite 代理开发期前后端端口不一致比如前端是 5173、后端是 8080浏览器会拦截跨域请求。推荐双管齐下。后端加 CORS 配置前端 devServer 代理也配上这样不管从浏览器还是从代理调试都能正常工作。后端的 CORS 配置用 Spring MVC 的 WebMvcConfigurer。Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/api/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }逻辑说明addMapping(/api/**)只对接口路径生效。allowedOriginPatterns(*)配合allowCredentials(true)不会像allowedOrigins(*)那样抛异常这是 Spring 5 之后的写法。allowedMethods里必须有 OPTIONS因为浏览器在带自定义 Header 的请求前会先发 OPTIONS 预检。如果后端登录校验拦截了 OPTIONS也会导致前端报 403需要把预检请求放行。前端 Vite 代理配置在vite.config.js。export default defineConfig({ server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })参数说明proxy把以/api开头请求转发到http://localhost:8080。changeOrigin: true会把请求头里的 Host 改成目标域名防止后端校验 Host 时出错。如果你的后端接口本身没有/api前缀需要加rewrite: path path.replace(/^\/api/, )把前缀去掉再转发。这套配置只在npm run dev时有效打包后的联调靠 Nginx 完成。5. 避坑/常见问题排查SSMVue项目最容易翻车的5个地方5.1 Mapper 接口报 BindingExceptionMyBatis 扫描路径漏配现象启动 Tomcat 或跑单元测试时报org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.hanfu.mapper.HanfuMapper.selectPage。原因MyBatis 启动时没有扫到对应的 Mapper.xml或者 Mapper 接口的 namespace 和 XML 里的不一致。常见的情况是 XML 放在了src/main/java目录下没有被打进最终的 classes或者 spring 配置里没有配置 mapperLocations。解决在spring-mybatis.xml里给 SqlSessionFactoryBean 指定 mapperLocations。bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ property namemapperLocations valueclasspath:mapper/*.xml/ property nametypeAliasesPackage valuecom.hanfu.entity/ /bean同时检查target/classes/mapper目录下有没有编译后的 XML。如果没有把 XML 挪到src/main/resources/mapper下这是最稳妥的做法。还有一个细节Mapper 接口和 XML 的 namespace 必须完全一致少写一个包路径都会匹配不上。5.2 中文乱码编码过滤器顺序和连接串一起排查现象前端提交中文后端存进 MySQL 变成问号或者后端返回 JSON 里的中文变成乱码。原因乱码链条有三环——Tomcat 接收请求的编码、SpringMVC 响应的编码、MySQL 连接的编码。任何一环不是 UTF-8都会出问题。Tomcat 8 对 GET 请求的 URI 默认是 UTF-8但 POST 表单不一定SpringMVC 的过滤器如果没有强制编码响应的 Content-Type 也可能不是 UTF-8。解决在 web.xml 最前面配置 Spring 的 CharacterEncodingFilter。filter filter-nameencoding/filter-name filter-classorg.springframework.web.filter.CharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param init-param param-nameforceEncoding/param-name param-valuetrue/param-value /init-param /filter filter-mapping filter-nameencoding/filter-name url-pattern/*/url-pattern /filter-mapping注意这个过滤器要放在其他过滤器前面并且forceEncoding设为 true表示请求和响应都强制用 UTF-8。同时检查 MySQL 库表字符集和连接串都统一为 utf8mb4缺一不可。我自己遇到过一次数据库表已经是 utf8mb4但连接串漏了 characterEncoding那次的排查过程真的能浪费一个下午。5.3 前端请求 404/403路径前缀不一致还是跨域拦截现象前端页面能打开但所有接口在 Network 面板里显示 404 或 403。点开请求 URL发现是/api/hanfu/page而后端实际路径是/hanfu/page多了/api前缀对不上或者是浏览器报跨域 403。原因404 基本都是 baseURL 里的/api与后端RequestMapping不匹配。403 有两种常见情况跨域预检请求 OPTIONS 没有被放行或者 Token 没过后端拦截器。解决优先统一路径契约让后端 Controller 类上统一写RequestMapping(/api/...)前端 baseURL 保持/api两边都改不如后端只改一处。跨域 403 就检查 CorsConfig 是否生效以及 SpringMVC 拦截器是否把 OPTIONS 请求拦截了。在拦截器里加一个判断请求方法是 OPTIONS 就直接放行否则会一直卡在预检。5.4 Vue 打包后刷新 404history 模式下的 Nginx 回退现象本地npm run dev一切正常部署到 Nginx 后访问首页没问题但直接在地址栏输入/article/1或者刷新该页面返回 404。原因vue-router 开启 history 模式后路由切换是前端 JS 动态渲染的但地址栏里的/article/1是一个真实 URL浏览器会向服务器发起请求Nginx 找不到这个物理路径于是返回 404。解决在 Nginx 的 server 块里加try_files $uri $uri/ /index.html;。如果项目部署在子路径还需要把 Vue 的 base 也改成对应路径。另一个更省心的方案是直接把 vue-router 切成 hash 模式URL 里带#刷新时不会向服务器发未知路径请求。汉服平台这种 SEO 要求不高的网站用 hash 模式完全够用而且能少踩一个坑。5.5 启动报时区错误或端口被占用MySQL 8 和 Tomcat 的经典问题现象项目启动时控制台报The server time zone value ... is unrecognized或者 Tomcat 启动卡住提示Port 8080 was already in use。原因MySQL 8 默认使用服务器系统时区JDBC 驱动无法识别端口占用一般是上一个进程没退出或者另一个服务已经在用 8080。解决连接串尾部加serverTimezoneAsia/Shanghai注意是驼峰写法。端口占用在 Windows 上用netstat -ano | findstr 8080查出 PID然后任务管理器结束进程如果端口被其他服务占用就把 Tomcat 的 server.xml 里 Connector 端口改成 8081同时把前端代理和 Nginx 的 proxy_pass 同步改掉。这个坑没有技术含量但几乎每个第一次跑 SSM 项目的人都会撞上。6. 交付之前接口自测、前端调试与并发报名的一个实务技巧6.1 用 Apifox/Postman 把接口边界过一遍写完接口不要急着让前端联调。先用 Apifox 或 Postman 把分页查询的边界条件全跑一遍page1size1、page超出总页数、keyword传空格、dynasty传不存在的值。RequestParam的defaultValue只照顾了缺失情况空字符串和非法值还是要靠代码判断。把这些边界提前清理掉能省后面联调时的大量来回。6.2 用 Vue Devtools 查路由参数和组件状态前端页面渲染不出内容时不要急着翻源码。先开 Vue Devtools看当前路由有没有拿到id参数再看 Network 面板请求是否发出去了。很多时候是/article/:id里的 id 是 undefined请求都没发页面自然空白。热词里很多人搜 vue devtools 插件下载装完没有效果时检查一下浏览器扩展和 Vue 的版本是否匹配Vue 3 项目用旧版 devtools 是看不到组件树的。6.3 一个技巧事务和唯一索引挡住活动报名超卖活动报名是所有这类平台最容易出并发问题的场景。两个人同时点报名如果只做前端判断signup_count会加两次人数超卖。我自己在处理这类问题时有三层防线数据库唯一索引、Service 事务、条件更新。Transactional public Result signUp(int activityId, int userId) { signupMapper.insert(activityId, userId); int updated activityMapper.increaseSignupCount(activityId); if (updated 0) { throw new RuntimeException(活动人数已满); } return Result.ok(); }逻辑说明signupMapper.insert如果遇到重复报名会命中 signup 表的唯一索引uk_activity_user抛出 DuplicateKeyException事务回滚从数据库层拦截重复。increaseSignupCount的 SQL 写成UPDATE activity SET signup_count signup_count 1 WHERE id #{id} AND signup_count max_people条件更新保证并发下不会超过人数上限。两个操作必须在同一个事务里否则 insert 成功但 update 失败会留下脏数据。这是我在做这类项目时的习惯先把唯一约束、事务边界和数据一致性找出来再去调页面样式。一套源码能跑只是及格在并发场景下不翻车才是真正能交付的状态。希望帮到你。本文还有配套的精品资源点击获取
返回列表