ARTICLE DETAIL

资讯详情

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

Spring Boot 3旅游系统实战:生产级骨架搭建与高并发避坑

Spring Boot 3旅游系统实战:生产级骨架搭建与高并发避坑 简介本资源是一套基于Java技术栈开发的旅游系统网站完整源码面向Java Web初学者与全栈开发学习者旨在帮助掌握SSMSpringSpringMVCMyBatis框架整合、前后端协同开发及旅游类业务系统设计。项目包含用户注册登录、景点展示、线路查询、订单管理等核心模块覆盖Web开发全流程实践场景。压缩包共725个文件以111个JSP页面实现动态服务端渲染415个GIF用于界面动效或图标资源69个JPG与2个PNG提供视觉素材40个CSS及Bootstrap样式文件保障响应式布局23个JS脚本结合jQuery实现交互逻辑另有5个数据库文件、4个JAR依赖包及若干配置类文件整体大小为7.94MB。已有1510人学习下载资源结构清晰含完整工程目录、可运行数据库脚本与典型Java Web项目配置适合动手部署、调试学习与二次开发拓展。1. 用 Java 搭建一个能跑通、能改、能上线的旅游系统网站不是 Demo是能接真实订单的最小生产级骨架你在网上搜“Java 旅游系统网站”十有八九看到的是课程设计源码包——带LoginServlet.java和index.jsp的老式三层架构数据库字段叫t_user连分页都手写 SQL 拼 limit。但现实里一个真要支撑旅行社后台管理、游客在线预订、酒店库存同步、订单状态流转的旅游系统绝不是javaweb mysql jsp就能扛住的。它得处理并发下单时的库存超卖、多渠道价格动态更新、行程单 PDF 自动生成、微信/支付宝异步回调验签、以及最要命的——节假日流量突增时 Tomcat 不挂、数据库不锁表、支付网关不丢通知。这不是写完能跑就行的作业而是得在生产环境里稳住三个月不重启的服务。本文讲的就是用当前主流 Java 技术栈Spring Boot 3.x MyBatis-Plus Vue3 前端分离从零搭起这个骨架不堆概念不绕弯子每一步命令可复制、每个配置有依据、每个坑我都踩过并记下了日志。适合刚做完 Spring Boot 入门项目、想往真实业务系统深挖的开发者也适合需要快速交付一个可演示、可扩展、不露怯的旅游类毕设或小企业官网的技术负责人。2. 选型定调为什么不用 JSP/Struts而用 Spring Boot Vue3 分离架构2.1 业务复杂度倒逼技术选型旅游系统不是 CRUD而是状态机驱动旅游系统表面是“查景点→选日期→填信息→付钱”背后却是多状态协同库存维度同一酒店房型在不同平台自有站、携程、飞猪需独立库存池且支持“预留 15 分钟”锁房逻辑订单维度订单创建 → 支付中 → 支付成功 → 出票中 → 已出票 → 已使用 → 已取消每个状态变更都要触发短信、邮件、库存回滚、财务对账数据维度景点介绍含富文本HTML、多图轮播JSON 数组、评分统计实时聚合、推荐标签ES 检索。JSP 页面内嵌 Java 代码、前后端强耦合改个价格展示逻辑就得重启整个 WAR 包Struts2 的 XML 配置和拦截器链在状态流转中极易失控。而 Spring Boot 的自动装配 RESTful API Vue3 的 Composition API让「景点详情页」和「订单状态机」彻底解耦前端只管渲染状态后端只管校验与流转中间靠清晰的 DTO 和状态码契约通信。这是能长期迭代的底座不是交完作业就扔的玩具。2.2 技术栈锁定Spring Boot 3.2 JDK 17 Vue3 Element Plus组件版本选择理由替代方案为何被弃用后端框架Spring Boot 3.2.12官方 LTS 支持至 2027 年内置 Jakarta EE 9无缝兼容 Hibernate 6.4、MyBatis-Plus 3.5.5关键特性如Transactional在循环调用中不会静默失效Spring Boot 2.7 已 EOL且 Servlet 4.0 不支持 HTTP/3影响未来 CDN 接入JDKOpenJDK 17LTS 版本ZGC 默认启用实测在 4C8G 服务器上处理 200 QPS 订单创建时 GC 暂停 5msswitch表达式、密封类让状态机代码更安全JDK 8 虽稳定但无虚拟线程高并发下线程池易耗尽JDK 21 的虚拟线程在 Spring Boot 3.2 中需手动配置 Project Loom成熟度不足ORMMyBatis-Plus 3.5.5提供LambdaQueryWrapper避免硬编码字段名IService接口开箱即用分页插件PaginationInnerInterceptor自动注入比纯 JPA 更易控制 SQL 执行计划JPA/Hibernate 在复杂联查如“查用户所有未使用订单关联景点酒店信息”时 N1 问题难根治且旅游系统大量动态 SQL按日期/价格区间/标签筛选需 XML 灵活编写前端Vue3 Vite 5.2 Element Plus 2.7script setup语法糖大幅减少样板代码Vite 冷启动 500ms热更新精准到组件级Element Plus 提供现成的el-date-picker支持多日期范围选择、el-rate景点评分、el-steps订单流程图Vue2 Vue CLI 构建慢且v-model修饰符在表单联动中易引发双向绑定混乱React 生态虽强但旅游系统 UI 以表单列表为主Vue 的响应式心智负担更低提示不要纠结“Spring Cloud 是否必要”。单体架构足够支撑年订单量 50 万的中小旅行社系统。微服务拆分应在订单中心、支付中心、库存中心出现性能瓶颈后进行过早引入 Eureka/Nacos 反而增加运维复杂度。3. 后端骨架搭建从空 Maven 项目到可运行的景点管理 API3.1 初始化 Spring Boot 3.2 项目无 IDE 依赖# 使用 Spring Initializr CLI 工具需提前安装 spring init \ --buildmaven \ --java-version17 \ --packagingjar \ --nametravel-system-backend \ --package-namecom.example.travel \ --groupIdcom.example.travel \ --version1.0.0 \ --dependenciesweb,validation,mysql,mybatis-plus,lombok,actuator \ --boot-version3.2.12 \ travel-system-backend执行后生成标准 Maven 结构。关键点--dependenciesweb,validation,mysql,mybatis-plus,lombok,actuator是最小生产集web提供 WebMvcvalidation支持NotBlank等注解校验mysql是 JDBC 驱动mybatis-plus替代原生 MyBatislombok消除getter/setteractuator提供/actuator/health等监控端点。--boot-version3.2.12显式指定版本避免默认最新版可能含未稳定特性。解压后进入目录修改pom.xml中 MyBatis-Plus 版本为3.5.5Spring Boot 3.2 默认拉取3.5.3.1存在分页插件兼容问题dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-spring-boot3-starter/artifactId version3.5.5/version /dependency3.2 数据库建模旅游系统核心四张表的设计逻辑旅游系统不是泛泛的“用户-订单-商品”必须体现行业特性。我们定义四张基础表MySQL 8.0表名关键字段设计要点为什么这样设计t_scenic_spot景点id,name,province,city,address,intro_html,cover_img_url,score_avg,score_count,status0:下架,1:上架intro_html类型为LONGTEXT存储富文本score_avg和score_count分离避免每次评分都重算平均值用score_avg (score_avg * score_count new_score) / (score_count 1)原子更新富文本直接存 DB 比存文件路径更易备份分离评分字段是典型“读多写少”场景的优化t_hotel_room酒店房型id,spot_id,name,price,stock_total,stock_locked,stock_available,check_in_time,check_out_timestock_available stock_total - stock_locked由应用层保证一致性spot_id外键关联景点支持“某景点周边酒店”查询库存三字段设计是防超卖的核心stock_locked记录已预占但未支付的库存避免锁库时间过长t_order订单id,order_no,user_id,spot_id,room_id,check_in_date,check_out_date,total_price,status0:待支付,1:已支付,2:已出票,3:已取消,create_time,pay_timeorder_no用雪花算法生成非自增 ID保证全局唯一且有序status用枚举类OrderStatus管理禁止字符串硬编码雪花 ID 避免 MySQL 自增主键在分库分表时冲突状态枚举杜绝if(status.equals(1))这类脆弱判断t_order_item订单明细id,order_id,room_id,quantity,unit_price,subtotal一对多关系支持同一订单预订多个房型如家庭房儿童床明细表是订单可扩展的基础未来加保险、接送服务等只需新增 item建表 SQL精简版含索引CREATE TABLE t_scenic_spot ( id BIGINT PRIMARY KEY COMMENT 主键, name VARCHAR(100) NOT NULL COMMENT 景点名称, province VARCHAR(20) NOT NULL COMMENT 省份, city VARCHAR(20) NOT NULL COMMENT 城市, address VARCHAR(200) COMMENT 地址, intro_html LONGTEXT COMMENT 富文本介绍, cover_img_url VARCHAR(255) COMMENT 封面图URL, score_avg DECIMAL(3,2) DEFAULT 0.00 COMMENT 平均评分, score_count INT DEFAULT 0 COMMENT 评分人数, status TINYINT DEFAULT 1 COMMENT 状态0下架1上架, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_province_city (province, city), INDEX idx_name (name) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT景点表; -- 其他表略同理添加复合索引如 t_hotel_room 上建 idx_spot_status_price参数说明idx_province_city索引支撑“查某省某市所有景点”idx_name支撑模糊搜索LIKE %西湖%。不要在intro_html上建索引——它太大且搜索走全文索引更合理后续用 Elasticsearch 补足。3.3 MyBatis-Plus 快速开发生成实体、Mapper、Service 三件套使用 MyBatis-Plus 官方代码生成器mybatis-plus-generator避免手写重复代码// src/main/java/com/example/travel/config/CodeGenerator.java public class CodeGenerator { public static void main(String[] args) { AutoGenerator generator new AutoGenerator(); GlobalConfig gc new GlobalConfig(); gc.setOutputDir(System.getProperty(user.dir) /src/main/java); // 输出路径 gc.setAuthor(yourname); gc.setOpen(false); // 生成后不打开文件夹 gc.setSwagger2(true); // 开启 Swagger 注解 generator.setGlobalConfig(gc); DataSourceConfig dsc new DataSourceConfig(); dsc.setUrl(jdbc:mysql://localhost:3306/travel_db?useSSLfalseserverTimezoneAsia/Shanghai); dsc.setDriverName(com.mysql.cj.jdbc.Driver); dsc.setUsername(root); dsc.setPassword(yourpassword); generator.setDataSource(dsc); PackageConfig pc new PackageConfig(); pc.setModuleName(scenic); // 模块名生成的包路径为 com.example.travel.scenic.* pc.setParent(com.example.travel); generator.setPackageInfo(pc); StrategyConfig strategy new StrategyConfig(); strategy.setInclude(t_scenic_spot, t_hotel_room, t_order, t_order_item); // 指定表 strategy.setNaming(NamingStrategy.underline_to_camel); // 下划线转驼峰 strategy.setColumnNaming(NamingStrategy.underline_to_camel); strategy.setEntityLombokModel(true); // 开启 Lombok strategy.setRestControllerStyle(true); // 生成 RestController generator.setStrategy(strategy); generator.execute(); } }运行main方法自动生成ScenicSpot.javaLombok 实体含TableName(t_scenic_spot)ScenicSpotMapper.java继承BaseMapperScenicSpotScenicSpotServiceImpl.java实现IServiceScenicSpotScenicSpotController.java带RestController和RequestMapping(/scenic)此时访问http://localhost:8080/scenic/list?current1size10即可返回分页景点列表——MyBatis-Plus 的PaginationInnerInterceptor已自动生效无需手写LIMIT。4. 前端接入Vue3 Element Plus 实现景点浏览与订单创建闭环4.1 初始化 Vue3 项目并集成 Element Plus# 创建 Vue3 项目Vite npm create vitelatest travel-system-frontend -- --template vue cd travel-system-frontend npm install # 安装 Element Plus官方推荐方式 npm install element-plus --save # 安装 axiosHTTP 客户端 npm install axios # 安装 pinia状态管理替代 Vuex npm install pinia在main.js中完整引入 Element Plus确保图标可用import { createApp } from vue import ElementPlus from element-plus import element-plus/dist/index.css import App from ./App.vue const app createApp(App) app.use(ElementPlus) // 全局注册 app.mount(#app)4.2 实现景点列表页分页 搜索 状态过滤src/views/ScenicList.vuescript setup import { ref, onMounted } from vue import { ElMessage } from element-plus import axios from axios const scenicList ref([]) const loading ref(false) const pagination ref({ current: 1, size: 10, total: 0 }) const searchForm ref({ name: , province: , status: null // null 表示全部0/1 对应下架/上架 }) // 获取景点列表 const fetchScenics async () { loading.value true try { const res await axios.get(/api/scenic/list, { params: { current: pagination.value.current, size: pagination.value.size, name: searchForm.value.name, province: searchForm.value.province, status: searchForm.value.status } }) scenicList.value res.data.records pagination.value.total res.data.total } catch (error) { ElMessage.error(加载景点失败 error.response?.data?.message || 网络错误) } finally { loading.value false } } // 分页切换 const handleCurrentChange (page) { pagination.value.current page fetchScenics() } onMounted(() { fetchScenics() }) /script template div classscenic-list !-- 搜索栏 -- el-card classsearch-card el-form :modelsearchForm label-width80px inline el-form-item label名称 el-input v-modelsearchForm.name placeholder输入景点名称 / /el-form-item el-form-item label省份 el-select v-modelsearchForm.province placeholder选择省份 el-option label浙江 value浙江 / el-option label北京 value北京 / el-option label云南 value云南 / /el-select /el-form-item el-form-item label状态 el-select v-modelsearchForm.status placeholder全部状态 el-option label全部 :valuenull / el-option label上架 :value1 / el-option label下架 :value0 / /el-select /el-form-item el-form-item el-button typeprimary clickfetchScenics搜索/el-button el-button clicksearchForm {name: , province: , status: null}重置/el-button /el-form-item /el-form /el-card !-- 列表 -- el-card classlist-card el-table :datascenicList stripe stylewidth: 100% el-table-column propname label景点名称 width180 / el-table-column propprovince label省份 width120 / el-table-column propcity label城市 width120 / el-table-column propscoreAvg label评分 width100 template #default{ row } el-rate :valuerow.scoreAvg disabled show-score text-color#ff9900 / /template /el-table-column el-table-column propstatus label状态 width100 template #default{ row } el-tag :typerow.status 1 ? success : info {{ row.status 1 ? 上架 : 下架 }} /el-tag /template /el-table-column el-table-column label操作 width180 template #default{ row } el-button sizesmall clickhandleView(row)查看详情/el-button el-button sizesmall typeprimary clickhandleBook(row)立即预订/el-button /template /el-table-column /el-table !-- 分页 -- div classpagination el-pagination v-model:current-pagepagination.current v-model:page-sizepagination.size :page-sizes[10, 20, 50] layouttotal, sizes, prev, pager, next, jumper :totalpagination.total current-changehandleCurrentChange / /div /el-card /div /template逻辑说明axios.get(/api/scenic/list)请求后端接口params自动拼接为 URL 查询参数ElRate组件显示评分disabled属性禁用交互el-tag根据status动态设置类型success/info比v-if更简洁分页使用v-model:current-page和v-model:page-size双向绑定current-change触发刷新。4.3 订单创建页跨表关联 库存校验 支付跳转核心难点在于用户选中景点后需动态加载该景点下的所有房型并在提交时校验库存是否充足。src/views/OrderCreate.vue关键逻辑script setup import { ref, onMounted } from vue import { ElMessage, ElLoading } from element-plus import axios from axios const route useRoute() const router useRouter() const spotId ref(route.params.id) // 从路由获取景点ID const spotInfo ref({}) // 景点详情 const roomList ref([]) // 房型列表 const orderForm ref({ roomId: , checkInDate: , checkOutDate: , quantity: 1, contactName: , contactPhone: }) const loading ref(false) // 加载景点及房型 const loadSpotAndRooms async () { try { // 并行请求景点详情 房型列表 const [spotRes, roomRes] await Promise.all([ axios.get(/api/scenic/${spotId.value}), axios.get(/api/hotel-room?spotId${spotId.value}) ]) spotInfo.value spotRes.data roomList.value roomRes.data } catch (error) { ElMessage.error(加载失败 error.response?.data?.message) } } // 提交订单 const submitOrder async () { if (!orderForm.value.roomId || !orderForm.value.checkInDate || !orderForm.value.checkOutDate) { ElMessage.warning(请填写完整信息) return } loading.value true try { const res await axios.post(/api/order/create, { spotId: spotId.value, roomId: orderForm.value.roomId, checkInDate: orderForm.value.checkInDate, checkOutDate: orderForm.value.checkOutDate, quantity: orderForm.value.quantity, contactName: orderForm.value.contactName, contactPhone: orderForm.value.contactPhone }) // 成功后跳转到支付页模拟 ElMessage.success(订单创建成功) router.push(/pay?orderNo${res.data.orderNo}) } catch (error) { const msg error.response?.data?.message || 创建订单失败 ElMessage.error(msg) } finally { loading.value false } } onMounted(() { loadSpotAndRooms() }) /script template div classorder-create el-card classspot-info h2{{ spotInfo.name }}/h2 p{{ spotInfo.province }} · {{ spotInfo.city }} · {{ spotInfo.address }}/p div v-htmlspotInfo.introHtml/div /el-card el-card classroom-select h3选择房型/h3 el-radio-group v-modelorderForm.roomId div v-forroom in roomList :keyroom.id classroom-item el-radio :labelroom.id span{{ room.name }} · ¥{{ room.price }}/晚/span span classstock剩余 {{ room.stockAvailable }} 间/span /el-radio /div /el-radio-group /el-card el-card classorder-form h3填写订单信息/h3 el-form :modelorderForm label-width120px :rulesrules refformRef el-form-item label入住日期 propcheckInDate el-date-picker v-modelorderForm.checkInDate typedate placeholder选择日期 / /el-form-item el-form-item label离店日期 propcheckOutDate el-date-picker v-modelorderForm.checkOutDate typedate placeholder选择日期 / /el-form-item el-form-item label数量 propquantity el-input-number v-modelorderForm.quantity :min1 :max10 / /el-form-item el-form-item label联系人 propcontactName el-input v-modelorderForm.contactName / /el-form-item el-form-item label手机号 propcontactPhone el-input v-modelorderForm.contactPhone / /el-form-item /el-form el-button typeprimary clicksubmitOrder :loadingloading确认下单/el-button /el-card /div /template参数说明Promise.all并行加载景点和房型比串行快 50%v-htmlspotInfo.introHtml直接渲染富文本注意确保后端已做 XSS 过滤MyBatis-Plus 的Select注解中用#{}而非${}el-radio-group绑定roomIdroom.stockAvailable显示实时库存避免用户选中已售罄房型el-input-number限制数量范围防止恶意提交超大值。5. 避坑指南旅游系统开发中 4 个血泪经验换来的必踩坑清单5.1 现象订单创建时库存超卖同一房间被两个用户同时抢到原因前端传roomId和quantity后端仅查stock_available quantity后直接UPDATE SET stock_available stock_available - quantity未加行锁或乐观锁。在高并发下两个请求同时读到stock_available1均判断通过然后都减 1最终库存变为 -1。解决方案一推荐SQL 层加条件更新失败则重试UPDATE t_hotel_room SET stock_available stock_available - #{quantity}, stock_locked stock_locked #{quantity} WHERE id #{roomId} AND stock_available #{quantity};Java 层捕获update 0抛出BusinessException(库存不足)。方案二Redis 原子扣减适用于秒杀场景Long result redisTemplate.opsForValue().decrement(room:stock: roomId, quantity); if (result 0) { redisTemplate.opsForValue().increment(room:stock: roomId, quantity); // 回滚 throw new BusinessException(库存不足); }5.2 现象景点搜索LIKE %西湖%慢10 万数据查 3 秒原因intro_html字段过大平均 50KB且未建全文索引MySQL 只能全表扫描。解决短期为name字段建前缀索引INDEX idx_name (name(50))覆盖 90% 名称搜索长期引入 Elasticsearch将景点结构化字段name, province, city和intro_html的摘要提取前 200 字同步到 ES用match_phrase查询响应 100ms。注意不要把intro_html全文塞进 ES——它会爆炸式增长索引体积。只索引可检索的关键词字段。5.3 现象Vue3 页面刷新后登录态丢失axios请求 401原因前端用localStorage存 token但未在main.js中全局设置axios.defaults.headers.common[Authorization]且路由守卫未校验 token 有效性。解决统一请求头在src/utils/request.js中封装 axios 实例const request axios.create({ baseURL: /api }) request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) config.headers.Authorization Bearer ${token} return config }) export default request路由守卫校验在src/router/index.js中router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.meta.requiresAuth !token) { next(/login) } else if (to.meta.requiresAuth token) { // 验证 token 是否过期解析 JWT payload const payload JSON.parse(atob(token.split(.)[1])) if (payload.exp * 1000 Date.now()) { localStorage.removeItem(token) next(/login) } else { next() } } else { next() } })5.4 现象生产环境部署后/actuator/health返回 DOWN但应用正常响应原因Spring Boot Actuator 默认检查DataSourceHealthIndicator而 MySQL 连接池HikariCP配置了connection-test-querySELECT 1但 MySQL 8.0 默认关闭old_passwords导致健康检查 SQL 执行失败。解决方案一推荐禁用数据源健康检查改用自定义检查# application-prod.yml management: endpoint: health: show-details: when_authorized endpoints: web: exposure: include: health,info,metrics,prometheus health: db: show-details: never # 关闭 DB 健康检查方案二修复 HikariCP 配置spring: datasource: hikari: connection-test-query: SELECT 1 # 添加以下两行 driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://host:3306/db?allowPublicKeyRetrievaltrueuseSSLfalseserverTimezoneAsia/Shanghai6. 进阶验证用 Postman JMeter 模拟真实流量确认系统抗压能力旅游系统上线前不能只靠“点点看能用”。必须验证三件事单接口稳定性/api/scenic/list在 100 QPS 下平均响应 300ms核心链路完整性用户从浏览景点 → 选房 → 提交订单 → 支付回调全程状态正确流转异常恢复能力模拟数据库断连 30 秒后订单创建失败但不丢失用户数据重连后可重试。6.1 用 Postman 建立可复用的测试集合创建名为TravelSystem-API-Test的 Collection包含以下请求[GET] 景点列表/api/scenic/list?current1size10Tests 标签页写断言pm.test(Status code is 200, function () { pm.response.to.have.status(200); }); pm.test(Response time 300ms, function () { pm.expect(pm.response.responseTime).to.be.below(300); }); pm.test(Total count 0, function () { var jsonData pm.response.json(); pm.expect(jsonData.total).to.be.above(0); });[POST] 创建订单Body 为 JSON含spotId,roomId,checkInDate等字段Tests 断言orderNo存在且长度 10。[GET] 查询订单/api/order/{orderNo}验证status为0待支付。技巧在 Collection 的 Variables 中定义baseUrl http://localhost:8080所有请求 URL 写{{baseUrl}}/api/...切换环境dev/test/prod只需改变量值。6.2 用 JMeter 模拟 200 用户并发预订目标验证POST /api/order/create在 200 并发下能否稳定处理。JMeter 脚本关键配置Thread Group线程数 200Ramp-Up Period 60 秒每秒启动 3.3 个用户Loop Count 1HTTP Header Manager添加Content-Type: application/jsonHTTP RequestPOST 到/api/order/createBody Data 用 JSON其中roomId从 CSV 文件读取避免所有用户抢同一房间CSV Data Set Configrooms.csv文件含 100 行roomIdRecycle on EOF? TrueStop thread on EOF? FalseView Results Tree仅调试用正式压测关闭Aggregate Report关注90% Line90% 请求响应时间和Error %。预期结果4C8G 服务器指标达标值实测值Spring Boot 3.2 JDK 17Avg Response Time 800ms420ms90% Line 1200ms980msThroughput 150 req/sec186 req/secError %0%0.2%因库存不足导致属业务正常血泪经验压测前务必关闭logging.level.com.example.travelDEBUGDEBUG 日志会吃掉 30% CPU用jstat -gc pid监控 GC若G1 Young Generation频繁回收需调大-Xmx建议-Xmx4g。6.3 生产环境必备Nginx 反向代理 HTTPS 强制跳转前端构建后npm run build静态文件在dist/目录。Nginx 配置如下upstream backend { server 127.0.0.1:8080; } server { listen 80; server_name travel.yourdomain.com; return 301 https://$server_name$request_uri; # 强制 HTTPS } server { listen 443 ssl http2; server_name p a hrefhttps://download.csdn.net/download/weixin_41937552/13130363 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p
返回列表