Spring Boot+Vue前后端分离商城系统实战:从架构到部署 做商城系统这件事我接手的次数不算少了。但每次提起Spring Boot Vue 前后端分离的电子商城还是觉得这套组合里能讲的门道最多。原因很简单它不是一个纯展示型的CRUD项目而是把用户认证、商品管理、购物车、订单流转、库存扣减、支付回调、后台管理这些模块串在了一条完整链路上任何一个环节处理不好后面都是坑。这篇文章就基于这类项目的完整落地过程把设计思路、代码结构、关键实现和我在实际部署中踩过的坑一次说清楚。先对齐一下目标场景Spring Boot负责提供RESTful APIVue负责页面交互和前端路由二者通过JSON格式的接口通信典型的前后端分离架构。它解决的核心问题是前端和后端可以独立开发、独立部署、独立扩展适合电商这种需要快速迭代UI、频繁调整促销逻辑的业务形态。1. 项目定位与技术栈选型为什么又是Spring Boot加Vue1.1 这套组合解决的核心问题商城系统的本质是大量并发读、少量并发写外加复杂的业务状态流转。用Spring Boot做后端意味着你可以快速拿到一个内嵌Tomcat、自带自动装配、生态极其成熟的Java服务。Vue做前端最大的收益是组件化和响应式数据流购物车加减商品、商品列表筛选这类高交互场景用Vue开发效率比传统jQuery高出一大截。我最早做商城项目是用JSP加Servlet那时候前后端代码都堆在一个Web应用里每次改个页面样式都得重启服务更要命的是前端设计师和后端开发根本没法并行工作。后来换成了Spring Boot Vue前端聚焦页面和交互后端只暴露接口协作模式一下就顺了。1.2 从候选方案看选型理由做电商不是只有这一种技术组合我对比过几套方案方案优点缺点适用场景Spring Boot Thymeleaf学习成本低SEO相对友好前后端耦合高交互页面开发效率低简单展示型网站Spring Boot Vue 前后端分离并行开发、部署灵活、适合复杂交互跨域、鉴权、接口规范需要额外处理中大型商城、管理后台Node.js Vue全栈JavaScript语言统一电商核心交易场景的可靠性和生态不如Java小型项目、创业原型Python Django Vue开发快、自带Admin高并发下性能调优空间有限后台管理系统原型对电商这种涉及资金、订单、库存的系统我个人更倾向用Java。不是因为别的语言做不了而是Spring Boot在事务管理、分布式锁、消息队列、微服务拆分这些方向上积累了足够多的成熟方案。你后面业务量起来了从单机部署平滑过渡到微服务架构技术栈不用推翻重来。1.3 Spring Boot自动装配原理与版本选择很多人配置Spring Boot的时候只知道依赖写上就能用但没搞懂它背后的自动装配逻辑这会导致遇到诡异问题的时候毫无头绪。Spring Boot在启动时会扫描META-INF/spring.factories文件里配置的所有AutoConfiguration类然后根据当前classpath下的依赖、配置属性、Bean定义情况决定要不要把这些Bean创建出来。比如你引入了spring-boot-starter-data-redis自动装配就会检测到RedisTemplate需要的类存在于是帮你创建连接工厂和模板对象。这个机制带来的好处是我在切版本的时候体会特别深。Spring Boot 2.7和3.x虽然从用户视角看起来差别不大但底层变化巨大对比项Spring Boot 2.7.xSpring Boot 3.xJDK版本要求JDK 8JDK 17底层Spring版本Spring 5.3Spring 6.0javax包javax.servlet等jakarta.servlet等性能常规利用虚拟线程等新特性吞吐量有提升这里给个非常实际的建议如果你的团队刚上手这个技术栈或者现存服务器上还是JDK 8那就老老实实选Spring Boot 2.7.x。不要因为新项目就想一步到位上3.x后面接各种第三方SDK的时候包名兼容性问题会消耗不少时间。我见过不止一个项目因为硬上3.x导致某个老版本OCR SDK无法使用最后不得不回退版本。2. 数据库设计与模块边界划分先想清楚再写代码商城系统最怕的就是边写代码边改表改到后面业务逻辑和表结构纠缠不清。我通常会把数据库设计和模块边界划分放在写代码之前这个环节省下来的时间和以后排查故障所消耗的时间能差出一个量级。2.1 六张核心表怎么设计一个最小可用的商城系统至少要有用户表、商品表、商品分类表、购物车表、订单表、订单明细表。加上必要的辅助表比如轮播图表、用户地址表差不多十张表以内可以支撑一期上线。以订单表为例我看过太多人把订单表设计成一个大宽表什么字段都往里塞最后索引都建不明白。我常用的订单表结构是CREATE TABLE order ( id bigint(20) NOT NULL AUTO_INCREMENT, order_no varchar(64) NOT NULL COMMENT 订单编号业务展示用, user_id bigint(20) NOT NULL COMMENT 下单用户ID, total_amount decimal(10,2) NOT NULL COMMENT 商品总金额, pay_amount decimal(10,2) NOT NULL COMMENT 实付金额, status tinyint(4) NOT NULL DEFAULT 0 COMMENT 订单状态0待付款 1已付款 2已发货 3已完成 4已取消, address_snapshot varchar(500) DEFAULT NULL COMMENT 收货地址快照冗余存储, pay_type tinyint(4) DEFAULT NULL COMMENT 支付方式, pay_time datetime DEFAULT NULL COMMENT 支付时间, 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_order_no (order_no), KEY idx_user_id (user_id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单主表;这里有个关键点订单表要冗余用户的收货地址快照而不是关联查询地址表。因为用户的收货地址后续可能会修改但订单已经下单了就得保留当时的信息。如果设计成下单时只存一个地址ID后面地址一变历史订单的收货信息就跟着错了这在电商里会直接引发客服灾难。2.2 商品SKU与购物车的粒度问题商品表的设计我建议一张product表存商品公共信息一张product_sku表存具体规格。比如一个T恤有红蓝两色、各有S/M/L三个尺码那就是1条product记录配6条sku记录。购物车表的关键字段是user_id、sku_id、quantity。注意这里关联的是SKU粒度不是商品粒度否则同一个商品不同规格的购买数量就没法独立计算了。我第一次做商城的时候没设计SKU直接在商品表里放了颜色和尺码两个字段做支付库存扣减的时候才发现同一商品的库存没法分离那叫一个狼狈。库存和SKU绑定是电商系统最基本的原则。2.3 订单状态机定义订单的状态流转建议单独定义清楚因为后面写后端业务逻辑、前端按钮显示、运营后台操作权限全都依赖这套状态待付款(0) - 已付款(1) - 已发货(2) - 已完成(3) 待付款(0) - 已取消(4) 已付款(1) - 已取消(4) // 支付后允许退款状态不能想跳就跳比如已取消的订单绝对不能直接跳成已完成。Spring Boot后端的Service层需要写一个状态变更校验方法所有状态更新都走这个方法避免散落在各个业务代码里导致逻辑不一致。3. 后端核心模块实现从登录鉴权到下单减库存3.1 Spring Boot项目结构与JWT认证项目结构我推荐按模块分包而不是按技术层次分包。也就是先controller、service、mapper这个分层你可以保留但在一个商城项目里更建议用user、product、cart、order、common这种按业务域划分的方式。这样每个模块的代码量可控后期拆微服务的时候直接按包平移就行。登录鉴权这块现在的主流方案是JWT。第一次做这个系统的时候我还在用Session加Redis的方案每次请求都要查一次Redis虽然也能用但前端App、小程序都起来后跨域和会话共享的问题就特别麻烦。JWT本质上是一个包含用户ID和过期时间的加密令牌后端不需要存储会话状态天然适合前后端分离架构。核心实现思路// 拦截器里校验JWT public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行预检请求 if (OPTIONS.equalsIgnoreCase(request.getMethod())) { return true; } String token request.getHeader(Authorization); if (StringUtils.hasText(token) token.startsWith(Bearer )) { token token.substring(7); } // 校验并解析 Claims claims JwtUtil.parseToken(token); if (claims ! null) { // 把userId存入request方便后续业务代码获取当前登录用户 request.setAttribute(userId, claims.get(userId)); return true; } // 返回统一未授权响应 response.setStatus(401); response.setContentType(application/json;charsetUTF-8); response.getWriter().write({\code\:401,\message\:\未登录或登录过期\}); return false; } }注册拦截器时要注意放行路径比如用户注册、登录、商品列表、商品详情接口都要放行其余接口才需要鉴权。这里最容易犯的错误是漏了某个静态资源路径导致前端页面加载正常但接口全部401排查半天才发现拦截器把接口拦了。3.2 Spring Boot配置与商品列表的查询优化application.yml是Spring Boot项目的灵魂配置。我的习惯是分三个环境文件application-dev.yml、application-prod.yml、application-test.yml主配置只保留公共项。你在热词里看到的springboot配置搜索量大说明不少人在这个上面卡过。server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/eshop?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver redis: host: localhost port: 6379 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8 mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true商品列表是商城的高频读接口。如果每次请求都从MySQL里现查数据库压力会很大。我用Redis做了一层缓存key按商品分类和分页参数设计比如product:list:1:0:10过期时间设在10到30分钟之间。但这里有个坑商品价格频繁调整时缓存会导致前台展示延迟所以商品更新接口必须主动删除对应缓存让下一次查询重新加载。还有一个常规但重要的优化是给商品表的常用查询字段加索引。SELECT * FROM product WHERE category_id ? ORDER BY sales_count DESC就要求category_id和sales_count上有合适的组合索引。我在线上环境遇到过商品列表查询从30毫秒飙到1秒的情况一查慢日志发现是新上了一个运营人员用的排序字段但索引没跟上。3.3 购物车、订单与库存扣减的事务处理购物车模块相对简单无非是增删改查。但订单模块是整套系统的核心难点尤其是下单减库存这个操作稍不注意就会出现超卖。我第一次做这个功能的时候就是简单的ProductSku sku skuMapper.selectById(skuId); if (sku.getStock() quantity) { sku.setStock(sku.getStock() - quantity); skuMapper.updateById(sku); }这个逻辑在并发量低的时候没问题一旦两个人同时下单两个线程都查到了库存是10都判断可以购买10件就可能都扣减成功。这就是经典的超卖问题。正确做法是使用数据库层面的乐观锁在更新的时候加上库存条件判断UPDATE product_sku SET stock stock - #{quantity} WHERE id #{skuId} AND stock #{quantity}如果受影响行数为0说明库存不足直接抛出业务异常。同时创建订单和扣减库存必须放在同一个事务里Transactional(rollbackFor Exception.class) public Order createOrder(Long userId, ListCartItem items, Long addressId) { // 1. 生成订单号 // 2. 遍历购物车明细扣减库存 // 3. 计算订单金额 // 4. 插入订单主表和订单明细表 // 5. 清空购物车 // 6. 返回订单对象 }这里要特别提醒Transactional默认只对运行时异常回滚RuntimeException和Error可以触发回滚但受检异常比如FileNotFoundException不会回滚。很多新手在这个上面栽跟头所以我习惯显式写明rollbackFor Exception.class。3.4 单元测试与接口自测Spring Boot单元测试的最佳实践是把测试重点放在Service层而不是Controller层。因为真正复杂的业务逻辑都在Service里Controller只是参数的接收和转换壳子。我的常用测试手段是SpringBootTest加上MockMvc测接口但更核心的订单Transaction测试建议直接用真实数据库跑。配置一个test环境数据库每次跑测试前自动清空数据SpringBootTest Transactional class OrderServiceTest { Autowired private OrderService orderService; Test void testCreateOrder_WhenStockEnough_ShouldSucceed() { // 构造用户、商品、购物车数据 // 调用orderService.createOrder() // 断言订单创建成功库存扣减正确 } Test void testCreateOrder_WhenStockNotEnough_ShouldThrow() { // 构造库存不足的场景 // 调用orderService.createOrder() // 断言抛出BusinessException // 断言事务回滚库存没有变化 } }单测不是用来给领导看覆盖率数字的它是你以后改代码敢不敢动的底气。商城这种系统没人敢在没测试保护的情况下重构订单模块。4. 前端页面结构与API对接Vue 3 Element Plus实战4.1 环境与工程搭建vue安装及环境配置的细节热词里vue安装及环境配置搜索量一直居高不下说明这个基础步骤卡倒了一片人。我列一下我这边稳定可用的环境配置路径。首先确认Node版本。Vue 3推荐Node 16以上我用的是Node 18 LTS搭配npm 9。安装完Node后全局安装Vite脚手架# 检查Node版本建议16.0 node -v # 创建Vue 3工程 npm create vitelatest eshop-admin # 选择Vue TypeScript或JavaScript看团队情况 cd eshop-admin npm install npm run dev一个很容易忽略的点npm下载依赖慢的问题。建议先配置淘宝镜像源npm config set registry https://registry.npmmirror.com配置完再npm install速度会提升很多。特别是在公司网络环境下不配镜像的话经常等得怀疑人生。前端工程化方面我用的是Vite而不是Webpack。Vite基于ES Module开发模式冷启动速度比Webpack快得多开发体验完全是两个时代的东西。如果你的项目还在用Vue CLI创建的工程建议新项目直接用Vite。4.2 路由与状态管理vue-router和Pinia的使用Vue Router在商城系统里主要处理两类路由游客可访问的公共页面首页、商品列表、商品详情和需要登录才能访问的页面购物车、个人中心、订单列表。我在实践中的做法是给路由表加一个meta.requiresAuth字段const routes [ { path: /, name: Home, component: HomeView }, { path: /product/:id, name: ProductDetail, component: ProductDetail }, { path: /cart, name: Cart, component: CartView, meta: { requiresAuth: true } }, { path: /order/list, name: OrderList, component: OrderList, meta: { requiresAuth: true } }, { path: /login, name: Login, component: LoginView } ]然后在前置路由守卫里做登录校验router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.meta.requiresAuth !token) { next({ path: /login, query: { redirect: to.fullPath } }) } else { next() } })状态管理我用的是Pinia它是Vue 3官方推荐的状态管理库相比Vuex的API更简洁去掉了mutations概念直接修改stateimport { defineStore } from pinia export const useUserStore defineStore(user, { state: () ({ token: localStorage.getItem(token) || , userInfo: null }), getters: { isLoggedIn: (state) !!state.token }, actions: { setToken(token) { this.token token localStorage.setItem(token, token) }, logout() { this.token this.userInfo null localStorage.removeItem(token) } } })购物车这种需要全局共享数量徽标的状态放Pinia里再合适不过。组件里直接引用store不需要层层传props或者依赖事件总线。4.3 axios封装与computed计算属性axios封装应该是前端项目标配了。我在项目中通常会创建一个utils/request.js统一处理基础URL、超时时间、请求头注入和错误拦截import axios from axios import { ElMessage } from element-plus import router from /router const request axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL || /api, timeout: 10000 }) // 请求拦截器注入token request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }) // 响应拦截器统一处理错误码 request.interceptors.response.use( response { const res response.data if (res.code 0) { return res.data } if (res.code 401) { ElMessage.error(登录已过期请重新登录) router.push(/login) return Promise.reject(new Error(unauthorized)) } ElMessage.error(res.message || 请求失败) return Promise.reject(new Error(res.message)) }, error { ElMessage.error(网络异常请稍后重试) return Promise.reject(error) } ) export default requestComputed计算属性在商城系统里用得非常多。比如购物车页的已选商品总价你完全没必要写方法然后手动监听依赖变化computed会自动追踪响应式依赖script setup langts import { computed } from vue import { useCartStore } from /stores/cart const cartStore useCartStore() const selectedTotalPrice computed(() { return cartStore.cartList .filter(item item.selected) .reduce((sum, item) sum item.price * item.quantity, 0) }) /script这个计算属性会在cartList里的任何元素变化时自动重新求值你不需要手动触发更新这就是Vue 3响应式系统的魅力。5. 前后端联调与安全认证CORS、拦截器与接口权限5.1 CORS跨域问题的排查前后端分离最容易碰到的第一个问题就是跨域。前端跑在5173端口后端跑在8080端口浏览器直接请求就被CORS拦截。解决办法有两种后端统一配置跨域或者通过Nginx反向代理。后端配置相对简单写一个WebMvcConfigurer配置类Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }但注意如果你用了JWT拦截器必须得把OPTIONS预检请求放行否则前端浏览器发出的预检请求会被拦截器拦截返回401实际请求发出的假跨域现象就出现了。我在前文后端拦截器代码里已经加了这个判定这是非常关键的一行。5.2 前端路由守卫与后端接口权限接口权限控制需要后端做不能完全依赖前端隐藏入口。后端可以在JWT里携带用户角色字段然后在需要管理员权限的接口上加自定义注解和拦截器校验。我实践中的做法是这样定义角色枚举USER、ADMINJWT payload里加上role字段后端写一个RequireRole(ADMIN)注解拦截器里解析token后带着注解的接口检查角色不匹配就返回403为什么不建议前端控制因为前端路由和按钮显示都是可以被绕过的。就算页面不显示删除商品的按钮别人直接向后端发一个DELETE /api/admin/products/1请求后端如果不校验权限那这个接口就等于裸奔。接口权限永远在后端做兜底。5.3 Web安全注意事项热词里web安全搜索量很高我只能说你搜对了。商城系统是攻击者的重点目标最常见的几个攻击面SQL注入虽然MyBatis的#{}是预编译的防止注入没问题但如果你图方便用了${}拼SQL那就等于给攻击者开门。我用MyBatis时强制规定能全用#{}绝不用${}必须用到动态字段名排序的场景需要白名单校验。越权漏洞用JWT做认证后很多接口只需要拿到登录用户的id但有的开发图省事让前端传一个userId参数。这是非常危险的比如订单查询接口如果是GET /order/list?userId1攻击者改成userId2就能看别人的订单。正确做法是后端从token中解析出当前登录用户ID前端传什么参数都不信赋值操作时统一覆盖// 错误示范 Long queryUserId request.getParameter(userId); // 正确示范 Long loginUserId (Long) request.getAttribute(userId); // 操作的商品归属、订单归属必须与loginUserId一致参数校验Spring Boot里用Validated配合NotNull、Min、Size等注解在Controller入口就把非法参数拦掉。不要等到Service层再做判断那样既啰嗦又容易漏。6. 打包部署与性能优化从开发机到服务器6.1 前端构建与Nginx部署前端开发完不是直接扔文件到服务器就行。用Vite构建后生成dist目录里面是打包压缩过的静态文件JS、CSS都做了hash命名防止浏览器缓存。Nginx的配置要点有两个一是把/api路径反向代理到后端服务二是Vue Router在history模式下需要配置try_files否则刷新页面会404server { listen 80; server_name your-domain.com; # 前端静态资源 root /var/www/eshop/dist; index index.html; # 关键配置history模式路由刷新不404 location / { try_files $uri $uri/ /index.html; } # API反向代理 location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # 静态资源缓存 location ~* \.(js|css|png|jpg|gif|svg|woff2?)$ { expires 30d; add_header Cache-Control public, no-transform; } }这个配置里最容易出错的是proxy_pass后面要不要带/api。我一般习惯在后端Controller统一设置RequestMapping(/api)这样前端baseURL直接就是/apiNginx里proxy_pass http://127.0.0.1:8080;不带路径路径原样转发逻辑清晰不容易绕晕。6.2 Spring Boot打包与运行Spring Boot应用打包我用Mavenmvn clean package -DskipTests生成target/eshop-0.0.1-SNAPSHOT.jar后用java -jar运行即可。生产环境中我推荐用Systemd托管好处是开机自启、crash后自动重启、日志统一管理[Unit] DescriptionEShop Server Afternetwork.target [Service] Userroot WorkingDirectory/opt/eshop ExecStart/usr/bin/java -Xms512m -Xmx1024m -jar /opt/eshop/eshop.jar Restarton-failure RestartSec5s [Install] WantedBymulti-user.target把以上内容存成/etc/systemd/system/eshop.service执行systemctl daemon-reload systemctl enable eshop systemctl start eshop停止部署是systemctl stop eshop和systemctl start eshop。这套方式比直接nohup java -jar要靠谱得多。如果你服务器上已经装了Docker用Docker部署会省去很多环境上的麻烦。Dockerfile示例FROM openjdk:8-jre-alpine WORKDIR /app COPY target/eshop-0.0.1-SNAPSHOT.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar]构建镜像、起容器docker build -t eshop . docker run -d --name eshop -p 8080:8080 \ -e SPRING_PROFILES_ACTIVEprod \ -v /etc/localtime:/etc/localtime:ro \ eshop热词里提到的springboot jdk1.8打包到docker desktop大概率就是这类打包到容器的问题。按上面的思路走基本没有大毛病注意基础镜像的JDK版本要和项目编译版本一致用openjdk:8还是17要对应好。6.3 缓存、日志与并发经验线上运行一段时间后你会面临一个比开发更难的课题性能排查和不断优化。第一层是缓存。商品列表、商品详情、首页轮播这些读多写少的数据全部走Redis缓存。我在Redis里存的是JSON字符串key设计成可读性高的形式比如eshop:product:detail:1001。缓存穿透的问题频繁查询不存在的商品ID导致打到数据库可以用布隆过滤器解决或者简单点把空值也缓存30秒。第二层是日志。线上问题是没法靠IDEA调试的必须靠日志。我习惯在商城项目的关键路径上加上日志log.info(用户 [{}] 创建订单 [{}]总金额 [{}], userId, orderNo, totalAmount);排查问题时先按userId全局搜一次基本能定位到用户在他整个操作链路里发生了什么。如果所有用户都报错再去按接口路径搜日志看异常堆栈。第三层是并发。订单表经常要按用户刷列表如果没索引就会全表扫描。还有一个容易被忽视的坑如果一个用户在极短时间内重复点击提交订单前端没有做防重复提交后端也没有做幂等处理就会产生重复订单。简单方案是前端按钮提交一次后立即置为loading状态后端可以做一个防重令牌机制// 下单请求携带前端生成的uuidRedis里以uuid为keysetnx成功才处理 boolean valid stringRedisTemplate.opsForValue() .setIfAbsent(order:submit: token, 1, 10, TimeUnit.SECONDS); if (!valid) { throw new BusinessException(请勿重复提交); }这块经验是踩过坑才总结出来的。有段时间线上出现大量重复订单查日志才发现是用户双击了提交按钮两个并发请求都通过了正常校验要不是有防重令牌兜底售后处理起来要人命。我自己在实际开发里有一个体会很深的细节商城这种系统代码里没有多少炫技的空间真正值钱的都是这些笨功夫。表结构是否合理、事务边界是否清晰、索引是否配齐、权限校验是否覆盖所有需要保护的接口、防重逻辑是否到位这些看起来不起眼的点才是支撑一个线上商城稳定运行的核心。开发速度和系统质量并不矛盾但是前提是你在动手之前先把架构想清楚而不是靠后面加班补窟窿。如果你正准备用一个Spring Boot加Vue的项目练手或者交付给客户我建议你把这篇文章里提到的边界问题、权限校验、缓存设计和部署细节都当成硬性要求去验收能全部做到位这个商城系统就算真正能打了。