
每次有人问我“旅游指南系统”这种项目到底怎么做我都会先泼一盆冷水别看它名字听着唬人拆开来看它其实就是一个“标准的前后端分离CRUD项目”只不过在增删改查之上多了搜索、推荐、地图展示、收藏评论这几个有点意思的模块。你把这个项目吃透了Spring Boot Vue这条技术路线上最核心的东西你基本就都碰过了以后再做类似的管理系统、资讯平台、内容社区都是同一套骨架。今天这篇就围绕这个“基于SpringBoot Vue的旅游指南系统”详细聊聊。它包含完整的源码、数据库脚本和配套文档核心功能覆盖了用户登录注册、景点信息展示、景点搜索与分类筛选、旅行攻略发布、收藏、评论、点赞、个人中心管理以及后台对景点和用户的管理。适合的人群很明确一是正在准备毕业设计或课程项目的学生二是想从头到尾理解前后端分离项目如何落地的新人开发者三是想拿一套成熟代码做二次开发的个人开发者。我会从技术选型、数据库设计、后端核心逻辑、前端实现、打包部署到常见坑排查完整地过一遍。1. 项目定调与技术选型为什么是SpringBoot Vue1.1 一个典型的旅游指南系统到底要解决什么问题先想清楚这个系统存在的意义再谈代码实现。旅游指南系统的用户端解决的是“我想出去玩但不知道去哪、怎么玩、值不值得”的信息获取问题。所以核心功能必须贴合这个场景首页要能看到热门景点推荐景点列表要支持按城市、按分类自然风光、人文古迹、主题乐园等筛选景点详情页要有图文介绍、地图位置、用户评价还要有收藏功能方便用户把想去的景点攒起来。更进一步用户自己也可以写旅行攻略、发布游记对景点进行打分评论这样整个系统就从一个单向展示平台变成了有UGC属性的内容社区。管理端则解决“平台方怎么维护数据”的问题包括景点信息的增删改、用户状态的停用启用、评论内容的审核与删除。单看每一个功能都不复杂但合在一起就要求整个系统必须有一个清晰的分层设计前端负责交互和渲染后端通过RESTful API提供数据数据库负责持久化存储。这也是我选前后端分离架构的根本原因——不是因为它“新潮”而是因为这个项目的业务边界天然就是分离的。1.2 技术选型背后的逻辑后端用Spring Boot几乎是这个项目类型的标准答案。它内置了Tomcat简化了SSM时代繁琐的XML配置Starter机制让集成MyBatis-Plus、Spring Security这类组件变得非常快。更重要的是市面上绝大多数同类项目、教程、岗位需求都集中在这套技术栈上遇到问题随便一搜就能找到解决方案对于做毕设或者新手练手来说这一点比“技术最前沿”重要得多。前端选Vue的理由也类似组件化开发让首页、列表页、详情页、用户中心这些独立页面之间的代码互不干扰Vue Router做前端路由Vuex或Pinia管理登录状态和全局数据Axios统一处理HTTP请求。如果不用框架纯写jQuery堆页面这个项目做到一半你自己就会崩溃——景点详情页、攻略编辑页、后台管理页之间有大量重复的布局逻辑组件化能把这些彻底收拢起来。我见过很多人纠结要不要用“若依”这种快速开发平台我的看法是若依适合“赶工期交差”但如果你想把一个项目真正吃透最好还是从零搭一遍把每个配置文件、每个依赖的作用搞明白。旅游指南系统的价值不在于“堆功能”而在于它是你完整走一遍“设计数据库 - 写后端接口 - 写前端页面 - 联调 - 部署上线”全流程的最佳载体。2. 数据库设计旅游系统最值钱的部分2.1 角色权限与核心表结构我坚持认为数据库设计是整个旅游指南系统里最值得花时间的地方比写接口、写页面都值钱。因为一套业务逻辑的底层支撑全看表怎么建、关系怎么定。这个系统我拆成了九张核心表用户表、角色表、景点表、景点分类表、评论表、收藏表、攻略表、攻略分类表、图片表。用户表重点关注的是字段的完整性除了常规的id、用户名、密码、昵称、头像、手机号、邮箱之外我加了status字段做账号停用标记加了created_at和updated_at方便排查问题。密码存的是BCrypt加密后的密文绝对不允许存明文——这一点直接关系到系统安全面试时也经常被问到。景点表是整个业务的核心字段设计上我建议把“能让用户快速做决策”的信息都直接放在主表里景点名称、所属城市、分类id、封面图、简介、详细描述、门票价格、建议游玩时长、开放时间、热度值。热度值这个字段是故意冗余出来的目的就是做“热门景点推荐”时不用实时连表统计点赞数加浏览数直接按热度值排序性能上省一大截。攻略表对应“游记/旅行日志”这类UGC内容字段相对灵活标题、封面、目的地、出发时间、行程天数、人均花费、内容正文、点击量。内容正文这种长文本我用的是TEXT类型不做全文索引因为实际开发中更常见的是搜标题和摘要正文检索一般交给Elasticsearch这类搜索引擎这个项目规模用不上。角色权限参考的是最朴素的“用户-角色”模型普通用户可以浏览景点、发攻略、收藏、评论管理员负责维护景点和用户。不用搞太复杂的菜单权限表、操作权限表这个量级的系统两层角色足够清晰也方便理解权限控制的逻辑。2.2 外键的去留、索引设计与时区陷阱一个容易让新手困扰的问题是“要不要建外键”。我的建议是实体表之间可以不建物理外键但必须在逻辑上维护关系。比如评论表的user_id关联用户spot_id关联景点如果在数据库层面强制加外键约束删除用户或删除景点时会有一堆级联限制后期做数据清理会非常痛苦。更常见的做法是保留这些字段但不建FOREIGN KEY约束由业务层在代码里保证引用关系的正确性。当然前提是你心里要清楚逻辑关系——不能删了景点之后评论还挂在那边这在业务上就是脏数据。索引方面我重点建了这几个景点表的city字段单列索引支撑按城市筛选。景点表的category_id字段单列索引支撑按分类筛选。景点表的heat字段单列索引支撑热门排序。评论表的spot_id字段索引用于加速某个景点下的评论列表查询。收藏表建了user_id spot_id联合唯一索引既防止同一用户重复收藏同一个景点又加速“当前用户是否已收藏”的判断。这个联合唯一索引非常关键它是从业务逻辑倒推出来的设计收藏这个动作天然不允许重复与其在业务代码里先查一遍再插入不如让数据库从机制上做约束。另一个容易被忽略的是MySQL的时区问题。如果数据库连接串里不指定时区日期字段可能出现“差8小时”的情况——比如你存进去是上午10点查出来变成凌晨2点。解决方式是在JDBC连接串里加serverTimezoneAsia/Shanghai同时注意MySQL驱动要选对版本。这个问题我在部署阶段还会再提到因为它几乎每个人都会遇到一次。2.3 项目的SQL脚本为什么不能只用一次拿到源码包后数据库目录下通常有一个后缀为.sql的初始化脚本直接导入就能跑。但我要提醒你这个脚本是“一次性初始化”用的里面只包含建表语句和一部分初始数据。你后续在系统里新增的用户、景点、评论都存到了MySQL的真实数据目录里不会再自动写回这个SQL文件。所以如果你想把整个项目交付出去或者换一台电脑重新部署正确的做法是用mysqldump把最新的完整数据导出来再加进去而不是只拷贝初始脚本。我实际项目交付的时候就吃过这个亏复制了初始SQL给别人结果对方跑起来后发现管理员的密码根本对不上因为他那边数据库里存的是我后来改过的密文。这个点你们做交付、做备份时一定要记住。3. 后端实现核心模块与关键逻辑3.1 JWT登录认证与权限控制的完整流程登录鉴权是整个后端项目里含金量最高的一块面试被问的概率也最大。这个系统用的是JWTJSON Web Token方案核心思路是用户登录成功后服务端签发一个经过加密签名的Token字符串返回给前端前端后续每次请求都在Authorization请求头里带上这个Token服务端解析验证通过后就认为是合法用户。对比传统的Session方案JWT最直接的收益就是解决了前后端分离场景下的状态共享问题后端服务无状态化不需要维护Session水平扩容也方便。代价是Token一旦签发在有效期内很难主动作废所以一般是设置一个合理的过期时间比如24小时配合前端路由守卫在Token过期后自动跳转登录页重新获取。在代码结构上用户的密码校验走的是BCryptPasswordEncoder——明文密码和密文做匹配同样的明文每次加密出来的密文都不同但matches方法能完成校验。这个机制比MD5加盐更安全也省去了自己实现盐值管理的麻烦。权限控制这块我建议学到“注解拦截器”或“注解Spring Security过滤器”这两层系统实际采用的是基于Token的拦截器实现对/admin/**开头的接口做管理员身份校验对普通的用户操作接口校验Token是否存在有效。代码层面只保留了“用户”和“管理员”两个角色配合注解做方法级权限控制清晰也不复杂。3.2 景点列表分页、条件筛选与搜索的热度排序原理景点列表页是用户浏览频率最高的地方接口设计的合理与否直接影响用户体验。接口参数大致是pageNum、pageSize、city城市、categoryId分类、sort排序方式。排序方式我支持了三种——按热度、按评论数、按最新创建时间。这里有个很关键的实现细节分页查询不能用LIMIT写死了事得配合MyBatis-Plus的分页插件PaginationInnerInterceptor在配置类里注册好然后在Service层调用page()方法就能自动完成分页和返回总条数。如果不用插件手动写COUNT查询加LIMIT查询代码能多写一倍还不一定对。搜索功能方面最简单的实现是LIKE %关键字%模糊匹配。当搜索字段为null或空串时like条件可以动态拼接上去。虽然数据量大时全表LIKE会慢但这个系统量级完全够用。如果你想把搜索体验做得更好一点可以对景点的英文名或者拼音首字母建一个单独的索引字段用户输入bj就能搜出北京相关景点——这个扩展逻辑我最后会单独提一下。热度排序背后有一个“热度值刷新”的小技巧。最笨的方式是每次用户访问景点详情就同步对数据库做一次heat heat 1的更新但这样并发高的时候数据库压力会很大。更合理的做法有两种一是用Redis做计数器定时异步刷回MySQL二是“浏览数点赞数”加权计算的视图或定时任务。我这次采用的是相对稳妥的“同步更新异步统计推荐”折中方案进入详情页时只针对heat字段做一次累加更新而在首页推荐列表里展示的“推荐值”是由热点数据接口算出来的两个数据不耦合既保证用户看到的热度实时变化又不会让排序逻辑拖着详情页接口的性能。3.3 收藏、评论、点赞的并发安全与事务处理收藏模块是最容易出问题的部分因为它的逻辑涉及“判断是否已收藏”和“写入收藏表”两步操作。假设用户快速连点两次收藏按钮或者前端没做防抖就可能出现两条完全相同的收藏记录。这里我前面提到过的联合唯一索引就起作用了——即便代码层并发没控制好数据库层也会拒绝重复插入你只要在代码里捕获一下“Duplicate entry”异常给用户一个友好的提示就行。评论模块的逻辑也比较典型发表评论时需要同时做两件事——往评论表插入记录以及更新景点表的评论数、评论分数。这个业务必须放到一个Transactional事务里任何一个步骤失败都要回滚否则会出现评论存在但统计数字不对的脏数据。删除评论的时候也要考虑是物理删除还是逻辑删除加一个deleted字段标记实操中更推荐逻辑删除万一误删还能恢复管理后台的“审核”功能也是基于这个字段来实现“隐藏”效果的。点赞功能在并发场景下的实现是另一道坎。最简单的方式是点赞表like_record插入记录景点表like_count加一同样用事务包起来。但如果并发量高就会出现同一时刻多人点赞、加数不准确的问题。常见解法有三种悲观锁SELECT FOR UPDATE、乐观锁版本号条件更新、Redis原子自增。对这个项目规模来说我用的是乐观锁——更新景点热度时加一个WHERE heat_version ?条件如果更新失败说明数据被其他请求改过重试一次或者直接丢弃即可。实现简单面试时也能给你加分。3.4 图片上传与静态资源映射的配置细节景点封面、开发商展示图、攻略配图这些都需要文件上传功能。后端用MultipartFile接收前端上传的文件做一个“按日期分目录存储”的策略/profile/upload/2025/01/08/uuid.jpg文件名用UUID重命名避免用户上传同名文件互相覆盖。存储路径上有一个经典坑如果你把上传的图片保存在后端项目的target目录或者打包后的jar包内部部署时插上包再上传图片就会出现“图片消失”的诡异事件因为jar包内容被覆盖了。正确的方式是在application.yml里配置一个独立的绝对路径file.upload-dir/data/upload添加一个WebMvcConfigurer把该路径映射为/profile/**的静态资源URL。这样图片存到了部署机器上固定的目录重启服务不会丢Nginx接管静态请求也可以直接映射到同一路径。文件类型校验也一定要做。我见过不少直接把原始文件名和ContentType拿去存的项目结果用户传一个包含恶意代码的HTML文件通过URL访问后出安全问题。所以在FileUploadUtils里我会做两件事一是校验扩展名是否在内置白名单里图片只允许.jpg、.jpeg、.png、.gif、.webp二是限制单文件大小比如10MB超出就报错。4. 前端实现Vue组件化与页面交互4.1 工程结构路由规划决定开发效率前端的工程结构直接决定你后期加功能、改页面的效率。我的Vue项目推荐结构如下src/ api/ // 所有接口请求定义 assets/ // 静态资源 components/ // 通用组件头部导航、轮播图、景点卡片等 layout/ // 后台管理布局 router/ // 路由配置 store/ // 全局状态管理登录信息、用户信息 views/ // 页面视图 front/ // 用户端页面 home/ // 首页 spot/ // 景点列表与详情 guide/ // 攻略列表与详情 user/ // 个人中心收藏、我的攻略、我的评论 admin/ // 管理端页面 spotManage/ // 景点管理 userManage/ // 用户管理 commentManage/ // 评论管理 utils/ // 工具函数request封装、token缓存router/index.js里需要按模块规划路由层级并加上access路由守卫。判断逻辑是访问需要登录的页面时检查store里有没有Token没有就重定向到登录页访问/admin开头的页面时检查角色字段不是管理员就跳到首页并提示无权限。把这段逻辑写在全局前置守卫里比在每个页面里手动判断要干净得多。4.2 请求封装的规范性Axios拦截器到底拦截了什么Axios拦截器是前端项目里必写的工具类。utils/request.js里我会在请求拦截器中做两件事一是把Token从store取出拼成Authorization: Bearer xxx放进请求头二是把请求参数里的时间格式化避免传到后端之后出现格式错误。在响应拦截器中统一处理后端返回的code字段为200时返回数据为401时说明Token失效清除本地登录信息并跳转登录页为500时统一调Element UI的Message.error做提示。这样做的好处是业务代码里再也不需要每个接口都手写错误捕获逻辑出问题时提示风格统一排查问题也快。特别是管理员删除景点时如果后台返回业务错误比如“该景点下存在评论请先删除评论”拦截器会把后端message直接弹给用户前后端联调时会流畅很多。4.3 首页、景点列表、旅行攻略三大核心页面的实现思路首页承担的是“留住用户”的职责一般包含三个区域顶部轮播图展示精选景点、中部热门景点卡片区、底部城市推荐导航。轮播图我用到的是Element UI的el-carousel数据由热门景点接口返回景点卡片是一个通用组件SpotCard.vue上方是封面图下方是标题、城市和门票价格通过父组件传入的spot对象渲染。这个组件在首页和列表页都能复用这就是组件化最直观的价值。景点列表页的核心是“筛选 分页 排序”的联动交互。我用el-select做城市和分类筛选用el-radio-group做排序切换每次参数变化都重新调用接口并用el-pagination分页。这里有一个交互细节要注意用户筛选完城市并翻到第3页后如果再切换排序页码必须重置回1否则会出现“第3页但筛选条件不匹配”的错乱。在watch排序和筛选条件变化时手动把currentPage重置为1即可。景点详情页除了展示基本信息外还会加载地图组件。这里我建议用“内嵌地图二维码”或者便宜的静态地图API方案避免去对接复杂的Web地图SDK。核心需求只是给用户一个位置参考。地图下方是评论区采用两层结构评论列表 发表评论的输入框。每次用户发完评论后刷新列表并顺带更新景点页面的评论数展示这里正好对应后端那个事务逻辑。攻略页的开发重点在“富文本编辑”。用户发攻略时会用到带格式的文本编辑器我采用的是wangeditor这类轻量编辑器你如果做毕设也可以直接集成。有一点必须注意编辑器生成的HTML片段不能直接拼接到页面里渲染需要先经过过滤清洗防止XSS攻击。比较简单的处理方式是在后端Java侧引入Jsoup这个HTML解析库只保留标签白名单p、img、strong、h2等其余标签和脚本全部剔除。这个安全习惯非常值得养成。5. 打包与部署从本地跑通到线上可用5.1 前后端分离部署的环境准备与注意事项本地开发时很多人习惯了前端npm run serve起一个8080端口后端Spring Boot起一个8080端口两者通过Vite或Webpack的代理转发解决跨域。但真正部署时这种开发模式是不适合的生产环境会用“静态文件 Nginx反向代理”方式前端打包成纯静态资源放在Nginx的html目录下Nginx把/api开头的请求转发到后端地址其余路径直接返回静态文件。部署前你需要准备的东西很简单一台Linux服务器或本地虚拟机、JDK 8、MySQL 5.7/8.0、Maven、Nginx。后端项目先执行mvn clean package -DskipTests打出一个可执行jar包再用java -jar启动前端在项目根目录执行npm run build把生成的dist目录整个拷到服务器上指定位置。有一点我要特别强调生产环境的数据库密码、Redis密码绝不能写死在application.yml里明文提交到Git仓库。要么用Jasypt加密要么用环境变量占位符部署时在启动命令里传-Dspring.profiles.activeprod并加载对应的application-prod.yml。这不是小题大做密码泄露导致服务器被挖矿的案例我看过太多次了。5.2 Nginx配置里的关键坑与数据库导入的完整操作Nginx配置是部署环节里让人最头疼的部分一个分号写错整站都起不来。我贴一段实际可用的关键配置片段server { listen 80; server_name yourdomain.com; gzip on; gzip_types text/plain text/css application/json application/javascript application/xml; root /usr/share/nginx/html/dist; index index.html; location / { try_files $uri $uri/ /index.html; } location /prod-api/ { rewrite ^/prod-api/(.*)$ /$1 break; 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; } location /profile/ { alias /data/upload/; } }配置里最关键的是try_files $uri $uri/ /index.html;。前端路由是history模式如果用浏览器直接访问/spot/1这种二级路由地址Nginx会把请求发给后端而后端没有这个路由就会出现404。这行配置的作用就是当请求的文件不存在时回退到index.html让前端路由自己去解析。我见过太多人部署完首页能打开、点浏览器刷新就404基本都是忘了这行。数据库导入相对简单先在MySQL里创建一个实例数据库设置好字符集utf8mb4再用命令行导入SQL脚本mysql -uroot -p tour_db tour_db.sql导入后检查两个地方一是t_user表里是否有初始管理员账号如果脚本里没带你需要用后端写一个初始化的Runner类启动时把默认管理员的密码用BCrypt加密写入二是所有表名、字段名的命名风格是否一致——驼峰命名和小写加下划线混用会在MyBatis-Plus的map-underscore-to-camel-case配置没打开时报错找不到字段。5.3 部署后必须验证的三件事启动完服务、配好Nginx之后不要急着拍胸脯说上线了至少验证这三件事第一前端页面能打开并且logo、图片、样式都正常加载没有404资源。如果样式全丢了大概率是静态资源路径写死了绝对路径需要把打包配置里的publicPath改成相对路径或/根路径。第二登录、注册、接口请求都通。打开浏览器开发者工具点击登录后看Network面板如果请求响应里带了数据说明/prod-api的代理或重写配置正确如果返回404或502先查Nginx日志/var/log/nginx/error.log定位是转发到后端失败还是后端服务没起来。第三退出登录、刷新页面时不会跳转到奇怪的地方。这部分主要排查的是Token在localStorage里的存取逻辑是否正常——记住一定不要用sessionStorage存Token否则用户关闭标签页再打开就得重新登录体验很差。6. 常见问题与排查记录6.1 从零复现项目时最容易踩的五个坑我把自己在实际部署运行这个系统时遇到的典型问题整理成了一张表供你参考现象根本原因排查与修复方案后端启动报端口被占用8080端口被其他进程占用Linux执行lsof -i:8080或Windows执行netstat -ano前端请求报跨域前端页面和后端接口不在同源本地开发用Vite代理配置proxy生产环境必须走Nginx反向代理不要把Access-Control-Allow-Origin设为*数据库时间相差8小时JDBC连接串没有指定时区连接串加serverTimezoneAsia/Shanghai注意MySQL 8.x驱动还需要allowPublicKeyRetrievaltrue列表页分页数据不对MyBatis-Plus分页插件未注册检查Configuration里是否加了PaginationInnerInterceptor并设置DbType.MYSQL前端打包后跳转404history模式路由没有回退在Nginx的location /配置里加try_files $uri $uri/ /index.html;这五条基本覆盖了“拿到一套新项目从0到1跑起来”的绝大部分阻路障碍。前两条属于环境问题后三条属于配置问题都不涉及改业务代码耐心排查一定能解决。6.2 图片上传失败或加载不出来的定位思路图片相关的问题在实际使用中非常高频而且症状五花八门前端上报成功但图片裂了、本地上传没问题部署后就不行、图片偶尔能打开偶尔打不开。我建议按这个顺序定位先看数据库里存的图片URL是什么形式。如果存的是相对路径比如/profile/upload/xxx.jpg那么页面访问http://yourdomain/profile/xxx.jpg时得确认Nginx配置里的location /profile/是否正确指向了后端存储目录。如果存的是localhost:8080/profile/xxx.jpg这类带地址的字符串那部署后你八成会看到所有图片都打不开因为你把前端域名换成了线上地址但数据库里的图片URL还指着本地。解决这个问题的统一思路是数据库里只存相对路径前端统一加一个CDN或站内前缀去拼完整地址。这样无论本地开发还是线上部署只要换一个全局变量就能让所有图片指向正确位置不需要改数据库。另外上传图片后立即访问有时候会报“404 Not Found”这是Nginx缓存了旧的静态资源索引。在Nginx配置里关掉sendfile或配置sendfile_max_chunk 1m再nginx -s reload一般就能解决。6.3 用户密码丢了这种“后勤”问题怎么应急处理管理员的密码忘了这是交付系统或长时间运行后非常容易遇到的问题。很多人的第一反应是去数据库里看密码但看到的是BCrypt密文没法反解回明文——这正是加密的意义。正确做法是写一个临时的启动类或者用测试方法调一下BCryptPasswordEncoder.encode(新密码)然后把生成的密文UPDATE到数据库对应用户的password字段。这样三分钟就能重置密码不用重新部署整个系统。这里要特别提醒如果你直接拷贝网上搜来的密文粘到数据库里大概率是登不进去的因为BCrypt每次加密都会生成不同的盐。一定要用你当前项目里同一个BCryptPasswordEncoder实例去编码才能保证版本兼容和校验通过。7. 从这套系统里你还能扩展出什么这个项目的代码并不是终点。我在实际使用过程中发现只要理解了它的骨架往里面挂新功能其实非常顺畅。比如想加“天气预报功能”你只要对接一个天气API在景点表里加一个weather_info字段再在详情页的接口聚合里把温度、天气状态返回给前端展示一两天就能搞定。想加“行程规划”功能本质上就是新增一张路线表和路线详情表前端加一个可拖拽排序的列表后端的增删改查你已经有了现成的模板。我之前帮某高校的同学做毕设扩展时就是在这个系统的基础上加了“推荐路线规划”和“旅游社区问答”两个模块前端加了两个页面后端加了三个控制器和对应的Mapper大概一个周末的时间就完成了。所以这套系统的价值更像是一个“母版”项目助力你快速验证和搭载自己的新想法。再往深走一步如果你发现自己对这个项目的“用户登录注册 管理后台维护数据 列表搜索分页 详情展示 评论点赞收藏”这套模板已经很熟练了那恭喜你你基本已经掌握了市面上60%以上的“管理系统类”项目的开发套路。不管是图书管理系统、商品管理系统、新闻发布系统还是二手交易平台它们的内核高度一致。把旅游指南系统从头到尾写过一遍这些变体项目对你来说就是换个业务流程、换套表结构、换几个字段的事。这也是我写这篇文章最想传达的一件事做项目最忌讳“只跑起来不问为什么”最怕只停留在复制粘贴的层面。如果你能把这个项目中每一个“选择”——为什么用JWT、为什么联合唯一索引、为什么事务包住两步操作、为什么Nginx要回退到index.html——都讲清楚那它对你的技术成长才是真正有意义的。剩下的事情就交给时间和你多写几个项目去沉淀了。