我直接说结论如果你现在想找一个既能练手、又能直接拿去生产环境的Java全栈项目基于SpringBootVue的墙绘产品展示交易平台是个相当合适的参考系。这个项目把电商交易、内容展示、后台管理三个核心场景串在一起技术栈又恰好是当前中小型团队最常用的那套组合SpringBoot做后端服务Vue做前端界面MyBatis管数据持久层MySQL存业务数据。它不像电商大厂动辄几十个微服务那样复杂也不像单体CRUD那样单薄——它正好卡在两中间让你能看清一个产品交易系统从数据库建模、接口设计到前端联调的全过程。我拿到这套源码后完整跑了一遍又对照业务逻辑做了一次代码级拆解。这篇文章不打算罗列文件清单而是从业务需求推导到代码落地把关键的模块设计思路、表结构、核心接口、前端页面交互、部署方式全部讲透。同时会补充一些我在实际运行中踩过的坑比如MyBatis缓存导致的数据不一致、订单并发修改问题、支付回调幂等处理等这些才是项目能真正从“demo”走向“可用”的关键。无论你是准备拿它做课程设计、毕业设计还是想移植成其他非标品交易平台这篇文章都能帮你省不少时间。1. 项目整体设计与模块拆解1.1 墙绘交易平台到底解决什么业务问题先弄清楚业务本质。墙绘产品不同于普通标品它有两个很特殊的属性一是强定制二是重展示。用户买一幅墙绘本质上是买了“设计图案上门绘制/材料输出后续服务”的复合商品所以平台需要在用户下单前充分展示实景效果、案例风格和可定制维度。这个产品的核心不是“把画卖出去”而是“让用户在看图过程中产生购买决策”。所以平台的功能结构必须围绕两条主线展开展示线作品列表、风格分类、大图详情、设计师信息、案例效果图。交易线购物车、订单提交、支付、订单状态跟踪、退款/售后入口。除了用户端平台还必须有一个运营后台让管理员处理作品上架、分类维护、订单审核、数据统计等日常操作。如果源码里只有用户端这个平台其实是残缺的。好在当前这套源码里用户端和管理端是同时存在的只是共用了一套后端接口通过角色权限来区分功能边界这也是大多数中小型系统采用的做法。1.2 系统功能模块地图把整个系统拆开看核心模块如下用户模块注册、登录手机号/邮箱、个人信息管理、地址维护、订单查询。管理员和普通用户共用用户表使用角色字段区分。作品模块墙绘作品的CRUD、多图展示主图轮播图、风格标签、价格策略、上架/下架状态。分类模块按风格现代、北欧、中式、儿童房等或按空间客厅、卧室、书房等做商品分类。订单模块购物车加入、提交订单、多状态管理待支付、已支付、制作中、已发货、已完成、已取消、订单明细快照。支付模块对接模拟支付或真实第三方支付保存流水号、支付时间、回调状态。管理后台数据看板销售统计、订单量、作品管理、用户管理、分类管理、订单审核与发货操作。评论收藏用户对作品评价打分收藏喜欢的作品便于后续下单。这些模块对学习者来说恰好覆盖了全栈开发的主要技术面增删改查、用户认证、文件上传、关联查询、事务操作、权限控制。它不是那种只放着美丽UI但毫无业务逻辑的“花瓶项目”代码里是有真实业务规则的。1.3 系统角色与权限边界这套系统中的角色不应该干巴巴地设计成“管理员/用户”两极。在实际墙绘业务场景里至少要区分四种角色源码里虽然可能没有完全区分但你做二次开发时可以按这个思路扩展角色核心操作典型页面游客未登录用户浏览作品、查看详情首页、列表页、详情页注册用户加购、下单、支付、评论、收藏购物车、订单中心、个人中心内容运营/客服作品上架、分类管理、审核评论、处理退款管理后台作品列表、订单审核页超级管理员全部权限、数据统计、用户禁用后台看板、系统设置权限控制实现时最常用的方案是Spring Security或Interceptor注解。如果这套源码用的是简单的拦截器方式那直接判断session里的role字段即可如果用了Spring Security则重点关注PreAuthorize注解的权限表达式。两种方案各有优势前者更轻量适合课程设计后者更安全适合生产。2. 技术选型解析为什么偏偏是这套组合2.1 SpringBoot不只是配置简化的框架很多人对SpringBoot的理解停留在“不用写繁琐的XML配置”实际上SpringBoot带来的核心价值是自动配置生态整合。针对这个墙绘平台SpringBoot带来三个直接好处第一内嵌Tomcat部署时一个java -jar就完事不需要单独伺候Web容器。第二Starter依赖体系把MyBatis、MySQL驱动、文件上传、参数校验等常用功能封装成开箱即用的组件减少大量样板代码。第三配合Actuator可以快速暴露健康检查、性能指标接口方便上线后排查问题。我在检查源码时重点关注了pom.xml里的依赖版本组合。对于这类项目SpringBoot 2.7.x是比较稳妥的选择它和MyBatis-Spring-Boot-Starter 2.3.x、MySQL Connector 8.0.x兼容性很好。如果你非要捡新用SpringBoot 3.x那必须注意它基于Jakarta命名空间很多旧版代码的javax.*导入要批量替换MyBatis Starter也要换新版本否则连接数据库时报错会让人摸不着头脑。2.2 MyBatis半自动ORM为什么更适合交易类系统和JPA/Hibernate相比MyBatis常被人诟病“SQL要自己写”但在交易类系统里这恰恰是优点。订单查询常涉及多表关联、动态条件、聚合统计用JPA拼Specification不仅难读性能还容易失控。而MyBatis可以直接在XML里编写精确SQL复杂报表场景还能用script标签动态拼条件。另外MyBatis的一级缓存和二级缓存机制是面试高频考点也是实际开发中的双刃剑。这个项目中如果开启了二级缓存在商品信息变更时如果没有显式清空缓存就会导致前端看到旧数据。我在代码运行时发现过类似问题后台修改了作品价格前台商品详情仍然显示旧价格排查后发现是Mapper的二级缓存没有配置flushCache。所以建议项目中默认关闭二级缓存只依赖一级缓存SqlSession级别来避免脏数据问题。2.3 Vue 2还是Vue 3这个项目给我们的选择参考这套源码大概率是Vue 2 Element UI的组合原因是Vue 2生态非常稳定网上的教程和踩坑记录多课程设计中很少遇到解决不了的兼容性问题。如果你打算移植到Vue 3则要适配Element Plus且所有$children、.sync修饰符等旧写法都需要替换工作量大不少。前端页面必然涉及多个核心视图首页/作品列表页用卡片式布局展示墙绘作品支持筛选条件。作品详情页轮播图、价格、风格标签、设计师介绍、加入购物车按钮。购物车页面选择商品、修改数量、计算总价、结算。订单确认页填写收货地址、选择支付方式、提交订单。个人中心/后台管理页订单列表、作品管理、数据图表。用Vue Router管理这些页面时要注意路由守卫的搭配未登录用户访问“订单确认页”时应重定向到登录页管理员访问后台页面时应校验角色权限。这些都是体现项目成熟度的小细节。2.4 MySQL存储引擎和字符集的正确姿势墙绘平台的业务数据并不复杂但涉及事务和并发操作。InnoDB引擎是标配但有两件事是最容易被忽略的第一字符集。建库时如果使用utf8mb4_general_ci则表情符号和生僻字都能存储避免用户输入特殊符号时应用报错。很多旧项目用的是utf8存储emoji时直接失败数据写入静默丢失。建议建表语句统一使用CREATE DATABASE wall_painting DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;第二事务隔离级别。MySQL默认是可重复读REPEATABLE READ在这个级别下订单状态的并发更新需要格外注意。下面在“订单状态机”部分会给出具体的处理方案。3. 数据库设计一张订单里面藏了多少门道3.1 核心数据表结构与字段设计先看用户表CREATE TABLE user ( id bigint(20) NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 登录用户名, password varchar(100) NOT NULL COMMENT BCrypt加密后密码, phone varchar(20) DEFAULT NULL, email varchar(100) DEFAULT NULL, avatar varchar(255) DEFAULT NULL COMMENT 头像地址, role tinyint(4) NOT NULL DEFAULT 1 COMMENT 角色: 0-管理员, 1-用户, status tinyint(4) NOT NULL DEFAULT 1 COMMENT 状态: 1-正常, 0-禁用, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;注意密码字段直接明文存储是大忌。项目里如果用了BCryptPasswordEncoder或MD5盐那还说得过去如果明文存储建议第一步就改成BCrypt加密这是安全红线。墙绘作品表是平台的“货架”设计时要考虑交易属性CREATE TABLE work ( id bigint(20) NOT NULL AUTO_INCREMENT, title varchar(200) NOT NULL COMMENT 作品标题, style varchar(50) DEFAULT NULL COMMENT 风格标签, cover_url varchar(500) NOT NULL COMMENT 封面图URL, detail_urls text COMMENT 详情图URL, 逗号分隔, description text COMMENT 作品描述, price decimal(10,2) NOT NULL COMMENT 价格, unit varchar(20) DEFAULT 平方米 COMMENT 计价单位, stock int(11) DEFAULT 99 COMMENT 库存/排期额度, sales_count int(11) DEFAULT 0 COMMENT 销量, status tinyint(4) NOT NULL DEFAULT 0 COMMENT 0-下架, 1-上架, designer_id bigint(20) DEFAULT NULL COMMENT 设计师用户ID, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_style (style), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT墙绘作品表;注意stock字段墙绘产品往往不是实物库存而是“可排期数”或“每月可接单量”下单时扣减这个额度。所以这个字段常出现在事务更新里需要配合条件更新防止超卖后面会细说。订单主表是整个交易链路的中心CREATE TABLE order ( id bigint(20) NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 业务订单号, user_id bigint(20) NOT NULL, total_amount decimal(10,2) NOT NULL COMMENT 订单总金额, pay_amount decimal(10,2) NOT NULL COMMENT 实付金额, status tinyint(4) NOT NULL COMMENT 状态: 0-待支付, 1-已支付, 2-制作中, 3-已发货, 4-已完成, 5-已取消, receiver_name varchar(50) NOT NULL, receiver_phone varchar(20) NOT NULL, receiver_address varchar(200) NOT NULL, remark varchar(500) DEFAULT NULL, pay_time datetime DEFAULT NULL, cancel_time datetime DEFAULT NULL, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_user_id (user_id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单主表;订单明细表保存下单时刻的快照数据CREATE TABLE order_item ( id bigint(20) NOT NULL AUTO_INCREMENT, order_id bigint(20) NOT NULL, work_id bigint(20) NOT NULL, work_title varchar(200) NOT NULL, work_cover varchar(500) DEFAULT NULL, price decimal(10,2) NOT NULL COMMENT 下单时单价, quantity int(11) NOT NULL DEFAULT 1, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_order_id (order_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单明细表;订单明细为什么必须存一份“冗余”的work_title和work_cover而不是直接关联作品表因为墙绘作品随时可能被下架、改价、改标题。如果下单后商品信息变了历史订单就不该跟着变。这是电商系统设计的一个基本原则——订单表要保存交易发生时刻的数据快照。3.2 表关系设计与索引规划从E-R关系上看用户与订单一对多一个用户多个订单订单与订单明细一对多作品与订单明细多对多间接关联通过订单明细用户与作品收藏多对多需要中间表favorite索引规划方面三个字段要特别关注order_no建唯一索引因为它是查询订单的常用入参且必须唯一user_id建普通索引支撑“我的订单”查询work_id在订单明细表建索引支撑“按销量排作品”这类后台统计。如果数据量上了几十万条没有索引的MySQL在这些查询上会直接教做人。3.3 数据库初始化脚本中的坑有些源码在初始化脚本里会混入测试数据甚至把管理员账号的密码设置成明文123456。这类数据只适合本地开发线上必须改。另外如果建表语句里使用了ENGINEMyISAM要立刻改成InnoDB否则没有事务支持订单这种强一致性业务会出大问题。4. 后端核心实现从接口到业务的完整链路4.1 SpringBoot项目分包结构与启动流程这套系统的后端分包一般如下com.wall.painting ├── controller # 接口层 ├── service # 业务逻辑层 │ └── impl ├── mapper # MyBatis Mapper接口 ├── entity # 数据库实体 ├── dto # 请求/返回数据封装 ├── config # 配置类拦截器/跨域/文件上传 ├── common # 通用返回结果、异常处理、工具类 └── WallApplication.java这种分层是标准做法。需要注意的是controller层只负责接收参数和返回结果不写业务逻辑业务规则写在service层mapper层只做数据访问。我看到过不少人把代码全堆在controller里图一时方便后面复杂业务一来就乱了。启动执行WallApplication.java后SpringBoot默认扫描同包及子包下的所有组件。如果你的启动类放错包路径Controller扫描不到就会出现“页面404后台无报错”的诡异现象这点需要特别注意。4.2 MyBatis核心配置XML映射与动态SQLMyBatis中Mapper接口与XML文件通过命名空间绑定。为了让XML不跟接口分离导致编译期难以追踪建议把XML文件放在resources/mapper目录下并在application.yml中指定mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.wall.painting.entity configuration: map-underscore-to-camel-case: truemap-underscore-to-camel-case这个配置非常实用开启后数据库的create_time字段可以自动映射到实体类的createTime属性省去大量手动映射。订单列表查询是最典型的动态SQL场景需要按状态、时间、用户ID等条件组合查询select idselectOrderList resultTypecom.wall.painting.dto.OrderDTO SELECT id, order_no, total_amount, pay_amount, status, create_time FROM order where if testuserId ! null AND user_id #{userId} /if if teststatus ! null AND status #{status} /if if teststartTime ! null AND create_time gt; #{startTime} /if if testendTime ! null AND create_time lt; #{endTime} /if /where ORDER BY create_time DESC /selectwhere标签会自动去掉第一个多余的AND避免手动拼接的尴尬。排序时如果允许用户传排序字段一定要做白名单校验不能直接把前端传值拼进ORDER BY否则容易被SQL注入。4.3 事务管理与并发下单处理订单提交不是单表update而是多表事务插入订单主表、插入订单明细、扣减作品库存、清空购物车。这四步操作必须在一个事务里任何一步失败都要整体回滚。SpringBoot中使用Transactional需要注意必须在Spring管理下的service类上使用不能写在controller。默认发生RuntimeException时回滚如果方法内捕获了异常而不抛出事务就不会回滚。多个方法通过内部this调用时Transactional可能失效原因是Spring代理机制。需要注入自身或者拆分事务边界。库存扣减是防止超卖的关键。墙绘作品是按“排期额度”控制下单的正确写法Transactional(rollbackFor Exception.class) public boolean createOrder(Long userId, Long workId, Integer quantity) { // 用条件更新来扣减库存避免超卖 int updated workMapper.deductStock(workId, quantity); if (updated 0) { throw new BusinessException(库存不足); } // 插入订单主表、订单明细、清空购物车... }对应的SQLUPDATE work SET stock stock - #{quantity} WHERE id #{workId} AND stock #{quantity}这条SQL是在数据库层面保证“扣减时库存必须足够”如果影响行数为0则说明库存不足。这个方案比“先select再update”靠谱得多后者在高并发下会因为竞态条件而出错。4.4 订单状态机设计购物车到订单的生命周期订单状态是一个有限状态机各状态之间的流转必须受到约束。最忌讳的是用户直接POST请求把订单状态从“待支付”改成“已确认”。后端必须做状态流向校验。当前状态允许动作目标状态待支付用户支付已支付待支付用户取消已取消待支付超时自动取消定时任务已取消已支付管理员确认/开始制作制作中制作中管理员发货/交付已发货已发货用户确认收货已完成已完成用户发起售后申请退款处理中可选实现时可以在OrderStatusEnum里定义枚举通过一个Map记录合法的状态流转组合public enum OrderStatusEnum { WAIT_PAY(0, 待支付), PAID(1, 已支付), MAKING(2, 制作中), SHIPPED(3, 已发货), COMPLETED(4, 已完成), CANCELLED(5, 已取消); public static boolean canTransition(Integer from, Integer to) { // 定义合法流转关系 return false; } }若状态流转不合法直接抛异常。这套机制能拦住大量不懂业务流程的人“瞎调接口”。4.5 支付流程与回调幂等处理真实生产环境会接入微信支付或支付宝但课程设计里多数是“模拟支付”点击支付后直接修改订单状态。如果你要接真实支付核心难点是回调处理。支付回调有两个地狱级问题重复通知和丢失通知。第三方支付平台会多次通知同一个支付结果如果你的回调接口没有做幂等订单状态就可能被重复修改甚至发放两次权益。幂等处理的标准做法Transactional(rollbackFor Exception.class) public void handlePayCallback(String orderNo, String tradeNo) { // 1. 根据订单号查询订单 // 2. 如果订单状态已经是PAID直接返回不重复处理 // 3. 如果订单状态是WAIT_PAY才执行业务更新 }更稳妥的方式是在订单表增加trade_no第三方支付流水号并建唯一索引数据库层面防止重复写入。5. 前端核心实现Vue如何把交易流程串起来5.1 Vue项目结构与环境准备前端根目录一般是wall-painting-web核心结构如下src ├── api # 接口封装 ├── assets # 静态资源 ├── components # 公共组件 ├── router # 路由配置 ├── store # Vuex状态管理 ├── views # 页面组件 ├── utils # 工具函数axios等 └── App.vue环境准备时node_modules的安装速度取决于网络建议配置npm镜像。Vue 2项目推荐使用Node 14.x或16.xVue 3项目推荐Node 16或18。版本不匹配时常见的报错是Node Sass编译失败这种问题基本都是因为Node和node-sass版本不对应。5.2 路由设计与导航守卫墙绘平台的路由可分为三类公开路由首页、作品列表、作品详情。游客可访问。需要登录的路由购物车、订单确认、个人中心、订单列表。管理员路由后台管理相关的所有页面。Vue Router的导航守卫适合做访问控制router.beforeEach((to, from, next) { const token localStorage.getItem(token); if (to.meta.requiresAuth !token) { next({ path: /login, query: { redirect: to.fullPath } }); return; } if (to.meta.requiresAdmin localStorage.getItem(role) ! 0) { next({ path: /403 }); return; } next(); });这个守卫的写法比我见过的一些“只在页面里判断存不存在token”的方式强不少。它不仅拦截未登录用户还能做角色级管控。登录后跳回原来想访问的页面这个体验细节在用户侧非常加分。5.3 核心页面交互逻辑拆解作品列表页是流量入口核心交互是筛选和分页。筛选条件有分类、风格、价格区间、排序方式。接口设计上建议用一个聚合接口GET /api/work/list?style北欧priceMin100priceMax500sortsales而不是每个筛选条件单独调一次接口。前端只需要把筛选条件合并到请求参数里然后调用同一个列表接口。作品详情页是转化率关键涉及字段多接口返回结构建议{ id: 1, title: 客厅北欧风抽象墙绘, style: 北欧, price: 899.00, unit: 平方米, coverUrl: ..., detailUrls: [..., ...], description: 本作品采用环保丙烯颜料, salesCount: 23, designer: { id: 8, name: 某某设计师, avatar: ... }, favorited: true }点“加入购物车”后不需要跳转直接用Message提示成功引导用户去购物车结算这个微交互能明显提升成单率。购物车页面最需要注意的是选中状态管理。用户可能勾选多个商品结算时传的是“选中的购物车项ID数组”而不是全部商品。后端接口设计POST /api/cart/checked Body: { cartItemIds: [1, 3, 5] }订单确认页要回显收货地址、商品明细和支付金额。这里个人经验是前端拿到购物车选中项之后要把计算总价的工作交给后端。前端只负责展示如果前端自己算总价很容易被改请求参数绕过。后端在下单接口里根据商品ID重新从库里取价格保证金额可信。5.4 Axios封装与前后端联调细节Axios封装要看两个关键点请求拦截器和响应拦截器。请求拦截器统一加Tokenservice.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers[Authorization] Bearer token; } return config; });响应拦截器统一处理错误码service.interceptors.response.use( response { const res response.data; if (res.code ! 200) { if (res.code 401) { // token失效跳转登录 router.push(/login); } Message.error(res.message); return Promise.reject(new Error(res.message)); } return res; }, error { Message.error(服务器异常请稍后重试); return Promise.reject(error); } );联调阶段最常见的坑是跨域问题。本地开发时前端是localhost:8080后端是localhost:8081端口不同必定产生跨域。后端配置CORS是最省事的方式Configuration public class CorsConfig { Bean public CorsFilter corsFilter() { CorsConfiguration config new CorsConfiguration(); config.addAllowedOriginPattern(*); config.addAllowedMethod(*); config.addAllowedHeader(*); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return new CorsFilter(source); } }6. 部署上线操作本地跑起来到服务器部署全流程6.1 本地环境准备与初始化启动项目前按顺序检查四件事JDK版本SpringBoot 2.x对应JDK 8或11SpringBoot 3.x需要JDK 17。Maven配置检查settings.xml里的镜像地址推荐使用阿里云镜像否则依赖下载会非常慢。MySQL版本5.7或8.0均可但注意8.0的驱动类名和连接URL略有不同driver-class-name使用com.mysql.cj.jdbc.Driver。Node环境node -v确认版本安装依赖时如果报错先删node_modules重新安装。导入数据库时mysql -u root -p wall_painting.sql验证数据库是否有表以及初始数据。后端配置文件application.yml要改数据库账号密码spring: datasource: url: jdbc:mysql://localhost:3306/wall_painting?useUnicodetruecharacterEncodingutf8mb4useSSLfalseserverTimezoneAsia/Shanghai username: root password: your_password6.2 前端打包与后端部署前端构建npm run build构建产物在dist目录里面是静态文件可以用Nginx直接托管。更常用的方式是让SpringBoot把前端静态文件也一起打包把dist中的文件复制到src/main/resources/static目录然后重新打包后端。Linux服务器上的部署步骤# 上传jar包到服务器 scp wall-painting-server.jar rootyour_server:/opt/wall-painting/ # 启动 cd /opt/wall-painting nohup java -jar wall-painting-server.jar --spring.profiles.activeprod app.log 21 # 查看日志 tail -f app.log如果不想让进程被意外杀掉用systemd管理服务更专业写一个wall.service文件实现开机自启、崩溃自动重启。7. 源码学习与二次开发指南7.1 如何高效阅读这套源码不要从头到尾一行行读那是事倍功半。正确顺序是先看pom.xml和application.yml搞懂依赖和配置。再看数据库表梳理表关系和核心字段。然后从“作品列表接口”进代码controller - service - mapper完整走一遍请求链路。之后跟踪“创建订单”的代码理解事务和库存扣减。最后看前端如何调接口验证自己的理解。读代码时多留意异常处理是怎么做的。通用返回类Result可以帮忙统一规范public class ResultT { private Integer code; private String message; private T data; // 成功/失败静态方法... }7.2 从“能跑”到“好用”需要补的短板如果你要基于这套源码做二次开发我建议优先补这些能力功能增强方面可以支付成功后自动发站内信或邮件通知提升用户感知管理员后台补一个数据可视化看板统计每日订单量和销售额作品详情页增加相似推荐提升客单价。性能优化方面Redis缓存热点作品数据减轻MySQL压力MyBatis开启批处理后台批量上下架作品时不至于一条条执行Nginx配置动静分离图片文件不再经过后端。安全加固方面登录接口增加验证码和失败次数限制防止爆破上传图片时校验文件类型和大小防止恶意文件SQL语句统一使用#{}占位符从根上杜绝注入。7.3 这套源码还能改造成什么墙绘交易平台的业务骨架可以平滑移植到其他非标品交易场景比如手工艺品定制平台、装饰画电商、字体/插画版权交易站、生日蛋糕定制商城等。只需要改掉商品表字段和业务规则订单、支付、用户、后台管理这些核心模块全部复用。我实际试过把“墙绘作品表”改成“手工艺品表”改动量集中在作品字段和分类模块其余代码基本没动这说明这套源码的模块划分是合理的。8. 常见问题与排查技巧8.1 数据库连接与编码问题问题1启动时报Access denied for user rootlocalhost检查application.yml里的账号密码和MySQL实际账号权限是否一致特别注意MySQL 8.0的默认认证插件可能是caching_sha2_password旧版驱动会连接失败换用最新的MySQL Connector即可。问题2插入数据后中文变成问号大概率是数据库或表字符集不是utf8mb4。执行ALTER TABLE work CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;同时检查连接URL里有没有characterEncodingutf8mb4。问题3数据库表名与MySQL关键字冲突order是MySQL的保留字建表时必须加反引号SQL语句里所有引用都别忘。这是最容易阴到新手的一个点。8.2 SpringBoot与MyBatis集成问题问题1Mapper接口无法注入启动报No qualifying beanMapper接口缺少Mapper注解或者启动类上少了MapperScan(com.wall.painting.mapper)。问题2XML里的SQL解析报错提示Invalid bound statement (not found)原因通常是XML文件没有被打包到classes目录检查pom.xml里是否漏了resources配置。最直接的解决办法是在build节点里加resources resource directorysrc/main/java/directory includes include**/*.xml/include /includes /resource /resources问题3MyBatis开启二级缓存后数据不更新在application.yml里关掉二级缓存mybatis: configuration: cache-enabled: false或者在不需要缓存的Mapper XML中的select标签上加useCachefalse。对于交易类系统默认关闭缓存更稳妥。8.3 前端联调常见问题问题1页面能打开但接口请求404先看后端控制台有没有对应请求日志。如果后端没有日志多半是请求路径不对如果后端有日志但前端报404检查是否有网关或代理层挡住了请求。问题2登录后刷新页面用户信息消失典型原因是没有把用户信息持久化到本地存储而是只放到了Vuex里刷新后Vuex状态重置。应在登录成功后同步保存一份到localStorage或sessionStorage并在页面初始化时从本地存储恢复。问题3上传图片后无法访问检查文件上传目录是否设置了静态资源映射。SpringBoot默认只映射classpath:/static/你上传到本地磁盘的目录如果不额外配置Configuration public class WebConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceHandler(file: your_local_path /); } }那个图片在浏览器里自然访问不到。8.4 部署运行后的问题问题1服务器上启动后端口被占用用lsof -i:8080查端口占用情况找到进程后kill掉再启动。建议把端口号提前规划好避免和服务器上其他服务冲突。问题2构建好的jar包是30秒内闪退最有效的排查方式是执行java -jar app.jar前端运行看报错信息。八成是数据库连接配置错误或者是端口被占用导致启动失败。问题3MySQL内存占用过高如果是2G内存的小服务器MySQL默认配置可能太大在my.cnf里调小innodb_buffer_pool_sizeinnodb_buffer_pool_size 256M同时限制max_connections 100小服务器也能跑稳。9. 我的一些个人补充真把项目跑起来之后我最大的感受是代码量本身不是这套源码最大的价值模块划分和业务边界才是。团队开发时最怕就是所有代码堆在一起、Service层大而全、Order和Work逻辑相互渗透。这套项目虽然规模不大但分层思想是对的。墙绘这种非标品交易最核心的不是支付环节而是“下单前要充分展示商品属性下单后有明确的状态推进”这两个点搞定系统就算立住了。最后分享一个小技巧我在做二次开发的时候经常用/actuator/health端点检查服务状态。如果服务器上MySQL挂掉了这个端点会返回DOWN省了很多排查时间。套用一句话源码不是终点运行起来能说出为什么这么设计才是真的学到手了。
