☰
SpringBoot+Vue全栈旅游网站管理系统开发实战
2026/10/11 18:33:03 网站建设 项目流程

“这种‘旅游网站+后台管理系统’的项目,代码看起来不难,但真要从0到1完整做出来,数据库要几张表、接口要怎么设计、图片上传放哪里、前后端怎么联调、部署到线上有哪些坑,每一个环节都能让实战经验不足的人卡住好几天。”

这是某开发者做“七彩云南文化旅游网站管理系统”这段全栈练习之后跟我说得最实在的一句话。这个项目表面上看是一个典型的信息展示类网站,但把它当成纯静态页面来做、或者只顾着堆CRUD接口,都会错失它的核心价值。本文不打算复述一套“XX系统包括用户模块、景点模块”的规范化产品文档,而是按照实际开发顺序,把这一套基于SpringBoot、Vue、MySQL、MyBatis的技术方案,从需求判断、数据库设计、接口实现、前端页面到环境部署,一层层讲清楚,顺带把过程中最容易被忽略的细节和现场踩坑记录也放进来,适合正在准备独立开发全栈项目、即将进入求职面试、或者想用实战项目查漏补缺的Java学习者参考。

1. 项目拆解:文化旅游网站的真实需求边界

1.1 先想清楚:这个系统除了展示信息,还要管什么

看到“七彩云南”这个主题,很多人第一反应是做一个漂亮的静态页面,放几张风光图,加上景区介绍、旅游攻略就算完事。但实际上,一套能称为“管理系统”的网站,一定包含两个完全不同的使用视角:普通游客在前台浏览内容,运营人员或管理员在后台维护数据。

从游客视角出发,核心需求是信息获取,包括精品旅游路线的展示、景点介绍、图文攻略、旅游资讯等内容的浏览与搜索,同时游客还应该有注册、登录、收藏喜欢的内容、以及发表评论或游记反馈的互动能力。但这里有一个很容易被初学者忽略的点:游客不会愿意为了看一篇攻略先注册再登录,通常的做法是浏览功能完全开放,只有收藏、评论、点赞这类“写”操作才需要登录态。

从管理视角出发,管理员要能做产品形态的维护,包括景点基本信息的上传与更新、攻略文章的发布与置顶、首页轮播图的动态更换、用户信息的查看与禁用、评论的审核与删除,有条件的话还应该有一个简单的浏览数据统计面板,用来看看哪些景点最热门。这些功能最终都要落到“增删改查”上,但如果仅仅是四个操作堆表格,后台会让运营人员用得很痛苦,所以一定要加上状态控制、模糊搜索、分页列表这些在实际运营中必不可少的细节。

站在项目练习的角度,这套系统最值得做的部分其实不是“展示”,而是“管理”——景点和攻略在后台被添加之后,前台能不能按预期展示;游客收藏之后,在后台能不能查到这个用户的收藏记录;删掉一条攻略之后,相关的推荐位和轮播图会不会因为外键约束导致前端报错。把这些业务闭环理顺了,这个项目就算真正吃透了。

1.2 技术选型:为什么偏偏是SpringBoot + Vue + MyBatis

确定业务边界后,再回头看技术选型就非常清晰了。SpringBoot是目前Java后端开发的事实标准,内置Tomcat,集成大量Starter依赖,可以让开发者把精力放在业务逻辑而不是环境配置上。Vue作为前端渐进式框架,配合Element UI或者Element Plus组件库,能在短时间内搭建出观感不错的管理界面。MySQL存旅游网站这种以结构化数据为主的内容系统再合适不过,MyBatis则让SQL保持完全可控,尤其在多条件联查、动态SQL这些场景下,比全自动ORM更容易优化和排查问题。

有同学曾经问过,现在无论是云数据库还是云开发平台,都有很多“傻瓜式”方案,为什么还要坚持用这套组合?我的看法是:这套组合在国内开发者生态中太成熟了,教程丰富,面试也认可。更重要的是,这套组合的学习路径完全符合Java后端工程师的技能成长曲线,从JDBC到MyBatis到SpringBoot是一个递进关系,能把每一层都理解透,后面切换任何新技术栈都不会发怵。而且前后端分离架构本身就是一个值得练习的落地方案,前端的端口转发、跨域处理、Token鉴权、网关接入这些真实工作中必用的技能,在这个项目里都会自然地碰到,并不会刻意制造难度。

1.3 功能清单:把“完整源码”拆到能落地的颗粒度

结合业务需求,我把功能模块整理成下面这个对照表,既能保证开发时不漏功能,也能在后端设计接口时明确接口边界:

端侧功能模块核心操作
游客端(前台)首页展示轮播图、推荐景点、热门攻略
游客端(前台)景点列表分类筛选、关键词搜索、分页展示
游客端(前台)景点详情图文详情、收藏、评论、浏览量统计
游客端(前台)旅游攻略文章列表、文章详情、点赞
游客端(前台)个人中心注册、登录、收藏列表
管理端(后台)登录鉴权管理员登录、权限拦截
管理端(后台)景点管理列表查询、新增、编辑、删除、上下架
管理端(后台)攻略管理富文本编辑、发布、置顶、删除
管理端(后台)评论管理审核、删除、按景点筛选
管理端(后台)用户管理列表、查询、禁用/启用
管理端(后台)轮播管理图片上传、排序、启用
管理端(后台)数据概览总游客量、景点数、评论数、热门景区Top5

模块定下来之后,前后端并行开发时只需要按这个清单逐个实现即可,不会出现“突然发现少个字段,结果要回头改表”的低级紧急情况。

2. 数据库设计:先想清楚数据关系和约束

2.1 核心表设计,七张表就够用了

旅游网站的数据量级在起步阶段不会很大,但表关系会比想象中复杂一点,至少要考虑用户、景点、攻略、评论、收藏几类实体。很多初学者在设计表时只想着“把字段填满”,却忽略了外键关系和字段类型选择,导致后期查数据特别别扭,这里直接给出一个经过验证的建表方案。

用户表是所有“带状态”功能的基础,除了常见的id、用户名、密码之外,一定要加昵称、头像、手机号、状态这几个字段。密码字段建议使用varchar(100)而不是varchar(32),因为BCrypt加密后的密文长度是60,预留长一点避免后面改加密方式还要改表结构。

景点表是整个系统的内容核心,字段比较多:景点名称、所在地区、封面图、门票价格、开放时间、评分、浏览量、简介、详细内容、状态、创建时间。地区和分类这类字段建议单独用编码表示,不要直接存“云南省XX市”这种冗余字符串,这样后期做地区筛选时才能用SQL直接等值匹配。

攻略表与用户表存在作者关系,核心字段包括标题、封面图、作者ID、所属景点ID、摘要、内容、浏览量、点赞数、状态、是否置顶。攻略和景点建议做一个弱关联,因为多篇攻略可以关联同一个景点,也可以不关联任何景点,强外键约束会让内容运营非常痛苦。

评论表设计时,建议直接把冗余字段存进去:评论所属的景点ID或攻略ID(通过类型字段区分)、评论人ID、评论人昵称、评论人头像、评论内容、状态。乍看违反了“不要冗余”的规范,但在真实场景下,前台展示评论列表不可能每次都JOIN一次用户表,效率极低,属于用空间换时间的合理取舍。

收藏表最简单,但业务上有一个关键约束必须加上:user_id和scenic_id需要建联合唯一索引,这样才能保证一个用户对同一个景点只能收藏一次,重复点击收藏时要么直接返回成功,要么提示已经收藏。如果不加这个索引,代码里要先查再插,并发环境下很可能保存两条重复记录,而这种脏数据通常要运营很久之后才会被发现。

轮播图和管理员表分别是前后台的信息入口,轮播图表存图片地址、跳转链接、排序值、状态;管理员表存管理员账号、密码、角色、状态。角色字段不一定要做得很复杂,但至少要有超级管理员和普通运营两种区分,为后台权限拦截留一个入口。

2.2 字段类型与默认值,细节里藏着的坑

设计表时最容易在字段类型上犯低级错误。经验原则是:业务状态用tinyint,比如启用禁用、上下架、是否置顶,取值控制在0和1,不要用int,避免出现“状态等于3是什么意思”这种无人能回答的问题;价格字段用decimal(10,2),不要用float,否则会出现9.9显示成9.899999999这种让人莫名其妙的效果;时间字段统一用datetime,不要一部分用date、一部分用varchar存字符串,否则早晚后悔;富文本内容用mediumtext,比如攻略正文和景点详情,要能容纳较长的图文内容,不要用varchar充满了一个极限值才报错。

默认值这一块很多人图省事不想填,结果“为什么新增了一条用户记录,创建时间却是null”就成了日经问题。创建时间这类字段完全可以在数据库层面通过default CURRENT_TIMESTAMP自动维护,前端和后端都不需要额外传值。状态字段默认填0,启用禁用逻辑保持一致。

2.3 索引设计的简单原则

旅游网站的查询场景主要是“按地区筛选景点”“按标题搜索攻略”“按用户查找收藏记录”,这个场景决定了索引设计路径。简单原则有两个:外键字段一定要加索引,否则联表查询随着数据量增长会肉眼可见地变慢;高频筛选字段一定要加索引,比如地区、状态、是否置顶。但也不要给每个字段都加索引,因为插入和更新时维护索引也会消耗性能,正确做法是先不加,后面在测试环境用慢查询日志看查询瓶颈,再补齐必要索引。用这个方式处理一个几千条数据的小系统,优化效果会比“全表加索引”更清晰。

3. 后端工程实现:分层、鉴权与代码细节

3.1 项目分包设计,Controller别越权

后端工程代码结构直接决定这个项目是能“继续维护”还是“写完就废”,推荐的包结构如下:

com.example.travel ├── config # 跨域配置、拦截器注册、文件上传配置 ├── controller # 前台接口与后台接口统一入口 ├── service # 业务逻辑层接口 ├── service.impl # 业务逻辑实现 ├── mapper # MyBatis数据访问接口 ├── entity # 数据库实体类 ├── dto # 请求参数封装,如登录参数、查询条件 ├── vo # 返回视图对象,如景点卡片、评论展示 ├── util # 通用工具类,如JWT工具、结果封装 └── common # 全局异常处理、统一结果类

很多实战项目在编写比较紧急时会跳过service层,直接在Controller里写业务逻辑,然后调Mapper。这在接口少的时候确实快,但只要业务复杂度稍微上来一点,Controller会迅速膨胀,相同的逻辑散落在不同接口里,后面想改一个字都提心吊胆。分层不是“徒增工作量”,而是给未来的自己留一条活路。以这个旅游项目为例,新增景点时可能需要同时更新景点表的浏览量字段、生成一条操作日志、刷新缓存中的热门列表,这个组合逻辑放在service里,Controller就只负责接收参数和返回结果,职责划分很干净。

3.2 统一响应结果,联调时省下的全是口水

前后端分离项目最怕“各说各话”:后端返回了{code:200, data:{...}},前端解析代码却取的是status字段,联调现场就开始扯皮。从第一天起就统一ResponseResult结构:

@Data public class Result<T> { private Integer code; private String message; private T data; public static <T> Result<T> success(T data) { Result<T> result = new Result<>(); result.setCode(200); result.setMessage("操作成功"); result.setData(data); return result; } public static <T> Result<T> error(String message) { Result<T> result = new Result<>(); result.setCode(500); result.setMessage(message); return result; } }

所有Controller方法一律返回这个通用结构,后端发生业务异常时不要裸抛一堆技术堆栈给前端,应该在全局异常处理器中将错误转成统一格式。前端拿到响应后先判断code === 200再继续处理数据,逻辑非常清晰。说句题外话,这个结构在面试时可以顺势讲出“统一状态码、统一异常映射、统一响应结构”的三统一思想,是一个不错的加分项。

3.3 登录注册与JWT鉴权

用户登录注册是很多网站的“第一道门槛”,这个模块虽然不复杂,但细节非常多。密码存储一定不要用MD5那种可破解的明文哈希,应该使用BCrypt加密,Spring Security自带BCryptPasswordEncoder,单拎出来用也很方便。注册时要做用户名唯一校验,同时给前端一个具体的提示语,不要只回“注册失败”这种让用户猜的文案。

登录成功后的状态保持方案,业界主流是JWT,优点是无状态、不占服务端内存、方便分布式部署。可以在JWT工具类中生成Token:

public String generateToken(User user) { Map<String, Object> claims = new HashMap<>(); claims.put("userId", user.getId()); claims.put("username", user.getUsername()); return Jwts.builder() .setClaims(claims) .setSubject(user.getUsername()) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + 7 * 24 * 60 * 60 * 1000L)) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); }

服务端写一个拦截器处理Token解析,在SpringBoot中通过注册WebMvcConfigurer添加拦截器即可,放行登录接口、景点接口、攻略接口,拦截需要登录态的收藏、评论和个人中心接口。这里有一个容易踩的坑:拦截器里只解析Token还不够,异常缓存等需求一旦引入,后续每个人都会往拦截器里加代码,所以要提前给拦截器设计好“白名单数组”,每次调试遇到“为什么我明明登录了还报未登录”这种问题时,先确认接口路径是否在白名单里。

管理员登录和用户登录最好分成两套接口,以便业务上彻底隔离前端的游客与后台管理员身份,也可以避免管理员能随意拿用户Token访问前台接口的权限混乱。

另外,用户注册后不应该立刻开放所有权限,建议在用户状态字段中增加“未激活”这一状态,通过审核后才能使用收藏评论功能。这样后台才能对恶意注册做基本管控。

3.4 景点查询接口与MyBatis动态SQL

景点列表是前台访问频率最高的接口,表现为支持多个可选条件:地区、分类、关键词、价格区间、排序方式。如果是一套“一个查询对应一个SQL”的写法,你会发现需要写七八个几乎相同的方法,维护成本直接拉满。此时就是MyBatis动态SQL发挥价值的地方:

<select id="selectByCondition" resultType="com.example.travel.vo.ScenicSpotVO"> SELECT * FROM scenic_spot <where> <if test="keyword != null and keyword != ''"> AND (name LIKE CONCAT('%', #{keyword}, '%') OR summary LIKE CONCAT('%', #{keyword}, '%')) </if> <if test="region != null and region != ''"> AND region = #{region} </if> <if test="minPrice != null"> AND ticket_price &gt;= #{minPrice} </if> <if test="maxPrice != null"> AND ticket_price &lt;= #{maxPrice} </if> <if test="status != null"> AND status = #{status} </if> </where> ORDER BY <choose> <when test="sortType == 'hot'">view_count DESC</when> <when test="sortType == 'price'">ticket_price ASC</when> <otherwise>create_time DESC</otherwise> </choose> </select>

这里有一个细节:如果动态SQL中的符号是>或者<,在XML里一定要转义,写&gt;和&lt;,否则XML解析直接报错,整段SQL都跑不起来。分页的话,初学者先用PageHelper这种插件就能平滑搞定,等你理解了PageHelper底层原理,再手动写limit也不迟。先说清楚:PageHelper的用法是紧跟着分页拦截的第一条查询语句生效,不是随意在哪一行都能调的。很多人把System.out.println(pageInfo)放在临界位置,导致分页失效查出了全表数据,这种问题不熟原理几乎没法排查。

3.5 上传图片与富文本内容管理

后台管理必然会涉及图片上传,涉及本地存储和对象存储两种方案。本地存储只需要将图片写入项目指定的静态目录,再映射一个URL前缀,本地和测试环境足够用,还可以让前端直接用完整URL访问。但这里有个关键坑:SpringBoot默认映射的静态资源路径不包括外部自定义目录,比如D:/travel-images/。

解决方式是在配置类中注册资源映射:

@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/images/**") .addResourceLocations("file:" + uploadPath + "/"); } }

攻略发布用的编辑器一定会生成大量带样式的HTML内容,这些内容存数据库时要做XSS过滤,否则任何游客发一条带<script>标签的评论,所有访问页面的人都会被波及。前端渲染这类富文本时也应该使用v-html渲染接口返回的安全内容,不要直接拼接用户输入。后端保存前可以做一次白名单过滤,只保留p、img、strong、h2、h3这些常用标签,其它一律剥离。这套规则不复杂,但对整站安全非常关键。

4. Vue前端:页面架构与联调细节

4.1 技术版本选择,Vue2还是Vue3

技术选型时经常纠结这个问题。我的建议是:新项目一律Vue3,框架和配套组件库的长期维护都面向Vue3,Vue2虽然生态庞大,但已经进入维护末期。Vue3配合Vite做开发体验非常顺畅,启动速度快,组合式API也让代码复用变得顺手,配合Element Plus,管理端页面可以直接套用表格和表单组件。

前端工程结构建议如下:

src ├── api # 各模块的axios请求封装 ├── assets # 静态资源 ├── components # 公共组件,如轮播图、分页组件 ├── router # 路由定义与路由守卫 ├── store # 登录态管理 ├── views │ ├── front # 前台页面 │ └── admin # 后台管理页面 └── utils # axios实例、工具函数

setUp入口,Axios封装必做,否则每个组件都写一遍带Headers的逻辑,改一个baseURL要全项目翻。正确的做法是创建一个axios实例,在请求拦截器中带上Token,在响应拦截器中统一处理code!==200的情况,比如登录过期自动跳转登录页。

4.2 前台首页与列表详情

前台首页推荐用轮播图组件展示后台配置的滚动图片,在轮播图下方放一个“推荐景点”区域,数据来源是后台标记了推荐状态的景点。如果你使用Element Plus,可以用el-carousel实现轮播;如果用Swiper,注意懒加载配置,避免一次性加载十几张大图把首屏拉垮。

景点详情页要考虑路由参数传递、图片懒加载和浏览量上报。浏览量上报可以在详情页组件挂载时调一个专用接口,每次进入详情页就把view_count + 1。这里涉及一个安全细节:如果不做限流,恶意刷新可以让一个景点的浏览量在几分钟内变成天文数字,简单的做法是按用户或IP做一天内只记一次浏览量的限制。虽然是“小问题”,但真实系统往往就是在这些细节上被攻击的。

列表页的筛选栏要和后端查询参数一一对应,每次点搜索时重新组装查询参数并请求第一页。分页组件绑定currentPage和pageSize,切换时重新请求即可。Axios封装时要把query参数直接放在config的params上,不要手动拼字符串,避免URL编码问题困扰。

4.3 管理界面:表格+弹窗+表单三件套

后台管理页面的形态基本就是“表格展示+弹窗编辑”的组合。用Element Plus开发时,el-table绑定服务端返回的列表数据,el-pagination负责分页,表单放在el-dialog里,提交成功后关闭弹窗并重新加载表格,这套流程很固定但极易出错。

最容易出问题的点是编辑与新增共用同一个弹窗表单组件:编辑时表单要回填数据,新增时表单要清空数据。我的经验是弹窗组件中监听visible状态,每次打开时重置表单;编辑时等数据回填完成后再显示弹窗,避免出现表单闪烁一下变成空白的问题。另一个细节是删除操作一定要有二次确认,Element Plus的ElMessageBox.confirm可以直接用,不要让自己在测试环境手滑删掉一条真实数据后追悔莫及。

图片上传组件在后台使用非常多,推荐使用Element Plus的el-upload,设置action为后端上传接口,headers带上Token,成功回调中把返回的图片地址写入表单字段。这样表单与图片文件在提交时实际上是分离的:图片已经上传完成,表单提交的是图片的URL字符串而已。

4.4 路由守卫与登录状态

前端实现页面级权限控制,核心是路由守卫。在路由配置中给需要登录态的页面加meta: { requiresAuth: true },然后在全局前置守卫中判断:

router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (to.matched.some(record => record.meta.requiresAuth)) { if (token) { next() } else { next('/login') } } else { next() } })

虽然后端接口也做了鉴权,但前端还做一层的原因是“页面级别体验”:用户未登录时点击收藏按钮,最佳体验是直接跳转登录页而不是等接口返回401后再弹提示。这里建议前端和后端各守一道,不要嫌重复,越权漏洞往往就出现在“只做了前端校验没做后端校验”的项目里。

5. 从0到1把项目跑起来:环境与部署细节

5.1 本地开发环境清单

在开始之前把环境问题一次性解决,能省掉很多折腾。完整清单:

软件版本建议说明
JDK1.8或11SpringBoot 2.7或旧版项目都支持
Maven3.6及以上依赖管理
MySQL5.7或8.0测试阶段两者均可
Node.js14及以上Vue3+Vite要求不高,但建议LTS
IDEIDEA后端开发;VSCode亦可
开发者工具Navicat/DBeaver导SQL看数据

5.2 后端启动步骤

先在MySQL里创建数据库,建议字符集utf8mb4,然后导入项目提供的SQL脚本。这里的坑是不可低估的:SQL脚本里如果有CREATE DATABASE语句,执行前要确认当前账号是否有建库权限;表名和字段名的反引号不要省略,否则“table is not exists”的错误能让人怀疑人生。

修改application.yml中的数据源配置:

spring: datasource: url: jdbc:mysql://localhost:3306/travel_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: yourpassword driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

map-underscore-to-camel-case必须开启,否则数据库字段create_time无法自动映射到实体类的createTime属性。下拉依赖后执行mvn spring-boot:run,看到启动成功的日志就算后端起飞了。启动阶段经常出现端口被占用,解决方式要么改端口,要么干掉占用进程,命令行两条命令就能搞定。

5.3 前端启动步骤

前端调试最舒服的方式是用Vite代理解决跨域。在vite.config.js中配置:

server: { port: 3000, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, '') } } }

这里的关键是把后端接口前缀配成/api,代理转发时去掉前缀。这样前端的axios请求路径可以写成/api/scenic/list,Vite开发服务器会自动把请求转发到后端的8080端口,完全没有跨域问题。配置完成后执行npm install安装依赖,再npm run dev启动,浏览器访问http://localhost:3000就能联调了。

生产部署时推荐用Nginx同时托管前端静态文件并反向代理后端API:

server { listen 80; server_name your-domain.com; root /var/www/travel-front/dist; index index.html; location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location / { try_files $uri $uri/ /index.html; } }

后端环境用java -jar travel-server.jar启动,确保MySQL和上传目录的路径与配置一致,整套系统就算正式跑起来了。

6. 常见问题与排查实录,踩过的坑别再踩

6.1 跨域问题出现了,但代理也配了,为什么还报跨域

Vite代理能解决开发环境,但如果你直接从前端向http://localhost:8080发起请求,没有走代理,肯定还会跨域。后端也要配置全局跨域,两个层面都要处理,不是二选一。有同学问我“前端都本地联调了为什么后端还要配跨域”,答案很简单:你无法保证前端永远通过代理请求后端,线上通过Nginx反代也一样,但只要哪天你的静态页面部署在一个域名,API部署在另一个域名,跨域会立刻变成事故。后端加一段CORS配置是稳定做法。

6.2 MyBatis映射实体类属性,为什么查询结果全是null

除了上面提到的map-underscore-to-camel-case配置,还有可能是实体类中没有给属性生成getter/setter。如果手写实体类,确实会漏掉,使用Lombok加上@Data就能解决。另外,Mapper接口的@Param注解不要省略,多个参数时不加注解会报“Parameter ‘xxx’ not found”异常。

6.3 时间字段前端显示格式不对

后端返回的Java Date序列化后默认是时间戳,前端显示无敌长的一串数字。可以在统一返回中配置spring.jackson.date-format=yyyy-MM-dd HH:mm:ss并指定time-zone=GMT+8,也可以在实体字段上用@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")。两种方式都可以,但建议统一用一种,防止不同模块格式不一致。

6.4 本地图片上传成功,前端却访问不到

非常经典的坑,上传目录和静态资源映射是两个独立的问题。数据库里存的如果是D:/travel-images/xxx.jpg这种绝对路径,前端是无法直接访问本地文件系统的。正确做法是存相对路径,比如/images/xxx.jpg,再通过SpringBoot资源映射或Nginx静态映射提供访问。

6.5 中文乱码,MySQL建表时没设定字符集

解决方案是统一utf8mb4。建库时显式指定,连接字符串加上characterEncoding=utf8,前端页面meta中也声明UTF-8。三步全做到,基本不会再遇到乱码。如果已经插入乱码数据,不要试图改字段字符集补救,尽量直接清掉数据重新导入更省心。

6.6 页面可以访问,但登录后API一直返回401

排查思路按顺序来:先看请求头有没有带上Authorization,再看拦截器白名单有没有误拦截,最后确认JWT密钥前后端是否一致。还有一个经常出现的问题:Token过期时间设太短,开发调试时数据一多就把登录态撑过期了,建议开发阶段设置7天有效期,上线前再认真调短。


把整套“七彩云南文化旅游网站管理系统”从需求、表结构、后端、前端的实现逻辑和部署细节走完,能感受到一个典型的全栈项目需要具备什么样的“肌肉记忆”。这几天我帮那位开发者兄弟整理项目文档时,他最大的收获不是会写了一个景点列表接口,而是理解了为什么简单的功能也要按分层、按统一响应、按权限隔离来写——这些东西在教程里往往被一笔带过,但真正决定代码能不能被接手和维护的恰恰就是它们。如果你现在刚起步做类似的项目,我建议先从建表开始,把表结构反复推敲两遍,再用Postman把每个接口的请求响应跑顺,最后再去写前端页面。后面你想在这个系统上加民宿预订、导游预约、订单支付,表结构和接口设计都能顺理成章地扩展,整个项目才会真正变成属于你自己的“可以讲出来的案例”。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询