ARTICLE DETAIL

资讯详情

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

基于SpringBoot和Vue的房产门户网站设计与实现全流程复盘

基于SpringBoot和Vue的房产门户网站设计与实现全流程复盘 “143号基于SpringBoot和Vue的房产门户网站设计与实现。”看到选题列表里这行字的时候我第一反应是又一个CRUD项目。但真正动手之后我才发现这个题目比想象中扎实得多——房产门户既要面向普通用户提供楼盘展示、房源搜索、预约看房这类前台功能又要给运营人员提供房源审核、用户管理、数据统计的后台能力前后端分离、权限校验、文件上传、分页搜索、项目部署全都要自己搞定。今天我就把这套项目从选题、架构设计、编码实现到部署上线的全过程完整复盘一遍把我踩过的坑和填坑的思路一并写出来。想找完整SpringBootVue练手项目的朋友或者正在准备毕业设计的同学这篇比较适合你。1. 接单前的思考这题目值不值得做1.1 项目定位这不完全是一个普通的增删改查很多同学看到“房产门户网站”这几个字下意识觉得这就是一个无聊的管理系统无非是把楼盘、房源、新闻三个表做一遍CRUD。有这个想法很正常但真把需求拉出来就会发现门户网站和后台管理系统有本质区别。房产门户的核心是全流程。用户端要能完成浏览门户首页、按区域/价格/户型筛选房源、查看楼盘详情和户型图、在线预约看房、提交购房或租房意向。管理端要能完成维护楼盘楼栋信息、发布房源并设置上下架、处理用户预约、管理资讯文章、查看每天的访问量和预约量统计。这些需求组合起来恰好是一个微缩版的中型业务系统。它比玩具项目多了一层真实感有状态流转房源下架、预约状态从待审核变已确认有角色区分管理员、经纪人和普通用户有数据关联楼盘—楼栋—房源—预约一层套一层还有一定的交互复杂度城市切换、筛选联动、轮播图管理。做完之后你拿出去讲不会让人觉得你只是背了一堆框架而是真的做过业务。1.2 技术选型为什么锁定SpringBoot 2.7.18 Vue选型环节我当时纠结了很久SpringBoot到底用2.x还是3.x前端用Vue2还是Vue3这两个问题几乎每个做全栈项目的人都会遇到。我最终的方案是SpringBoot 2.7.18 Vue 2.7后端ORM用MyBatis-Plus数据库用MySQL 8项目管理用Maven权限校验用JWT。选2.7.18而不是SpringBoot 3.x最主要的原因是生态兼容性。SpringBoot 3.0之后强制要求JDK 17而很多教学环境、网上现成的教程模板、老牌第三方库的生态还停留在JDK 8。SpringBoot 2.7.18是2.x分支最后一个维护版本既保留了JDK 8的兼容性又能获得2.x分支的最终修复属于“稳字当头”的选择。如果你不是特别需要SpringBoot 3的新特性做业务系统用2.7.18完全够用而且遇到问题搜资料的范围会大很多。前端为什么不直接上Vue3这里我说句实话如果项目只有你自己从零写Vue3确实可以但一旦要套用现成模板或者参考网上开源的房产后台你会发现大量资源还是Vue2 Element UI的组合。我最后用的是Vue 2.7理由很简单——文档多、坑少、查问题快。Vue 2.7是官方最后支持Vue2的过渡版本Composition API也能用算是兼顾新旧写法的折中方案。提示如果你是第一次独立做全栈项目尽量选择生态成熟、资料多的版本组合。稳妥的第一版比追新的第二版更重要。2. 系统设计把房产门户拆成能落地的模块2.1 前端页面与功能模块划分前端我拆成了门户网站用户端和后台管理管理端两个部分。门户网站放在views/portal目录下包含首页、楼盘列表、楼盘详情、新闻资讯、在线预约、个人中心。这里我踩过Vue路由的第一个大坑楼盘详情页需要在路由里携带楼盘id我第一次图省事用了localStorage临时存id结果用户直接从地址栏打开详情页时就取不到值页面白屏。后来改成路由参数传值并在组件里对空id做了默认跳转处理才算稳定。后台管理端围绕“运营”来设计房源管理、楼盘信息管理、预约审核、用户管理和系统设置。管理端页面大量复用表格、表单、弹窗、上传组件所以我在components目录下封装了PaginationTable和ImageUpload两个公共组件。PaginationTable统一处理分页参数变化、请求接口和刷新列表的逻辑子页面只需要传 columns 配置和请求函数写起来非常省事。这个决策在后端开发阶段帮我节约了至少两天的重复工作量。门户端和管理端的路由权限也要分开处理。我在Vue Router里给管理端页面加了一个meta: { requiresAuth: true }标记配合全局前置守卫beforeEach做登录校验未登录访问后台时直接重定向到登录页。这里要注意前端路由守卫只是用户体验层面的兜底真正的权限控制必须靠后端拦截器完成前端隐藏按钮不能替代后端校验。2.2 后端接口与权限控制设计后端接口我采用标准的RESTful风格返回统一结构{ code, message, data }。无论登录、查询列表还是上传文件返回体都是这个格式。为什么要统一因为前端Axios的响应拦截器可以一次性处理登录过期code401、服务端异常code500和业务报错code400不用在每一个页面里重复写错误判断逻辑。配合Vue的Message组件前端只需要在拦截器里弹出错误提示业务代码会干净很多。权限控制我用SpringBoot拦截器实现。定义AuthInterceptor校验请求头里的JWT字符串从Token中解析出用户id和角色再判断当前请求路径是否属于/api/admin/**。如果是管理端接口只有ADMIN角色放行用户相关接口则检查Token是否存在。配置的时候要特别注意放行规则登录接口、注册接口、门户端的查询接口都应该在addPathPatterns(/**)之外用excludePathPatterns单独放行否则会出现前端已经登录成功、下一秒请求列表又被拦截器拦下来的怪问题。2.3 数据库设计核心表的关系数据库设计是最容易翻车的一环因为表结构一旦定下来后面改起来非常痛苦。我整理的核心表如下表名说明关键字段brand楼盘表id、楼盘名称、区域、地址、均价、经度纬度、封面图、状态building楼栋表id、楼盘id、楼栋号、总层数、开盘日期house房源表id、楼栋id、户型、面积、总价、朝向、楼层、图片、上下架状态appointment预约看房表id、用户id、房源id、预约时间、状态user用户表id、用户名、密码、手机号、角色、头像为什么要把楼盘和楼栋拆成两张表因为一个真实楼盘一定有多栋楼每栋楼的房源是独立存在的。如果不拆把楼栋信息直接冗余到楼盘表里当你要查“这栋楼有哪些可售房源”时就会非常别扭。反过来房源表里我也没有直接存楼盘名称而是通过house - building - brand的JOIN关联查询。这样设计虽然多一层关联但数据一致性最好——楼盘改名时所有房源和预约记录不受影响。一个容易被忽略的点是房源价格字段的类型。房产价格动辄几百万用int可以存但涉及折后价、单位价格时建议用decimal(12,2)后端对应BigDecimal避免浮点数精度丢失。同样的道理适用于面积字段建议用decimal(8,2)。我在开发时就因为面积用了double前端展示出现一串很长的小数后来统一改成BigDecimal才解决。3. 前后端开发实战从空项目到能跑通流程3.1 后端搭建SpringBoot项目创建与依赖配置后端项目我是在IDEA里通过Spring Initializr创建的创建时选了JDK 8、SpringBoot 2.7.18。如果你用IDEA新版创建时可能会发现默认只能选SpringBoot 3.x和JDK 17解决方案有两种一是手动修改初始化服务地址改成https://start.spring.io的老版本支持地址二是直接在Spring官网下载2.7.18版本的ZIP包再导入IDEA。很多同学卡在这一步以为是IDEA坏了其实是默认初始化源更新了。后端核心依赖如下dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency dependency groupIdcom.auth0/groupId artifactIdjava-jwt/artifactId version4.4.0/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId /dependency /dependencies加MyBatis-Plus之后很容易犯一个低级错误忘了在启动类上加MapperScan(com.example.mapper)启动时一直报Invalid bound statement (not found)。这个问题排查起来不难但如果你不知道Mapper接口必须被扫描到可能会浪费半小时在那反复检查XML文件路径。另一个MyBatis-Plus使用细节是需要在配置类里加分页插件否则调用selectPage时SQL不自动拼接LIMIT但接口又不报错数据却全量返回了这个坑我在后面专门讲。3.2 前端搭建Vue环境与项目结构前端用Vue CLI创建项目命令是vue create house-portal。安装依赖时我遇到过node-sass编译不过的问题这个问题在Windows机器上尤其常见因为node-sass需要下载对应Node版本的二进制文件经常下载失败。解决方案是把node-sass换成sass在项目里安装sass和sass-loader10编译速度更快也更不容易出问题。如果你的Node版本在18以上Vue CLI 4.x还会提示版本不支持此时可以升级到Vue CLI 5.x或者直接用Vite构建。项目结构我按下面这种方式组织src ├── api # 所有接口请求 ├── assets # 静态资源 ├── components # 公共组件 ├── router # 路由配置 ├── store # 状态管理 ├── views │ ├── portal # 门户端页面 │ └── admin # 管理端页面 └── utils # 工具函数utils/request.js里统一创建Axios实例baseURL从.env文件读取。.env.development中配置VUE_APP_BASE_APIhttp://localhost:8080/api.env.production中配置线上地址。这样切换环境时只需要改环境变量文件不用动业务代码。Vue项目开发环境调用接口时大概率会遇到跨域问题浏览器控制台报No Access-Control-Allow-Origin header is present。跨域问题我建议在后端统一处理写一个全局CorsFilter比前端配置代理转发更容易定位问题。3.3 第一个联调接口前后端如何对接以“登录”接口为例演示一次完整的前后端联调。后端ControllerPostMapping(/api/auth/login) public Result login(RequestBody LoginDTO dto) { User user userService.login(dto.getUsername(), dto.getPassword()); return Result.success(tokenUtil.generateToken(user)); }前端API模块// src/api/auth.js import request from /utils/request export function login(data) { return request({ url: /auth/login, method: post, data }) }联调过程中我踩了一个非常隐蔽的坑后端实体字段是userId但接口文档里写成了user_id前端照着下划线方式传参导致属性绑定失败。这种情况不会报错登录接口正常返回但用户对象里的id一直是null。排查了半天最终发现是命名规范不统一。所以从项目第一天起就要约定前后端字段统一走驼峰命名数据库字段可以下划线但JSON传输和实体类全部驼峰。最好把接口文档用Apifox或Swagger管理起来每次调整字段都同步更新减少沟通成本。4. 进阶功能与踩坑记录4.1 全局过滤器处理XSS攻击房产门户网站的一个业务特点是大量富文本内容——房源描述、新闻资讯都可能带HTML标签和图片。这就带来XSS跨站脚本攻击风险用户提交的字符串如果直接入库再通过v-html渲染到页面上恶意脚本就会被执行轻则弹窗骚扰重则窃取登录状态。最有效的方案是编写全局过滤器对所有进入后端参数做统一清洗。我是这么实现的在SpringBoot里注册一个OncePerRequestFilter用HttpServletRequestWrapper重写getParameterValues和getInputStream然后分别处理表单参数和JSON请求体。清洗逻辑用JSOUP库白名单模式配置成Safelist.relaxed()能保留常见的段落、图片、链接标签同时去掉script、iframe、事件属性等危险内容。Component public class XssFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { chain.doFilter(new XssRequestWrapper((HttpServletRequest) request), response); } }写这个过滤器时我学到第一课不要用简单的replace(script, )这种字符串替换去防XSS攻击者稍微嵌套编码就能绕过比如scrscriptipt。真正可靠的做法是用HTML解析器在语法树层面清洗。第二课是清洗不要“一刀切”有些业务场景确实允许用户提交含图片的富文本所以白名单得保留img的src属性否则写完的文章图片全被过滤掉又得回头排查。4.2 MyBatis分页插件与多条件搜索房源列表页必须有分页和多条件搜索前端筛选条件包括区域、价格区间、户型、朝向。MyBatis-Plus分页插件的用法很直观先在配置类里声明拦截器Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; }Service查询代码PageHouse page mapper.selectPage(new Page(current, size), new LambdaQueryWrapperHouse() .eq(house.getAreaId() ! null, House::getAreaId, house.getAreaId()) .between(house.getMinPrice() ! null house.getMaxPrice() ! null, House::getPrice, house.getMinPrice(), house.getMaxPrice()) .ge(house.getMinArea() ! null, House::getArea, house.getMinArea()) .like(StringUtils.hasText(house.getKeyword()), House::getTitle, house.getKeyword()) );Lambda条件构造器最大的优势就是条件为null自动跳过不用写一堆if拼接SQL。但分页插件有个坑当SQL里带ORDER BY或者关联查询时插件自动生成的count语句可能报语法错误。比如查询房源列表时需要JOIN楼盘表按区域过滤这时候分页插件生成的SELECT COUNT(*) FROM house ORDER BY ...可能因为子查询或别名问题出错。我的建议是涉及多表关联的分页查询直接写XML自定义SQL把分页参数用MyBatis-Plus的Page透传进去不要过度依赖Wrapper自动生成。虽然多写几行XML但可控性高很多。4.3 文件上传与MinIO对象存储楼盘户型图、房源实拍图、资讯封面都需要上传。本地磁盘存储虽然简单但部署到服务器后目录管理很麻烦备份和迁移都费劲。在真实项目中对象存储是更靠谱的解决方案。我引入了MinIO一个兼容S3协议的开源对象存储服务用Docker就能快速部署docker run -d --name minio \ -p 9000:9000 -p 9001:9001 \ -e MINIO_ROOT_USERadmin \ -e MINIO_ROOT_PASSWORDadmin123456 \ minio/minio server /data --console-address :9001后端集成MinIO Java SDK后写一个FileService上传方法大概长这样public String upload(MultipartFile file) { String objectName UUID.randomUUID().toString() . StringUtils.getFilenameExtension(file.getOriginalFilename()); minioClient.putObject(PutObjectArgs.builder() .bucket(house-portal) .object(objectName) .stream(file.getInputStream(), file.getSize(), -1) .contentType(file.getContentType()) .build()); return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder() .bucket(house-portal) .object(objectName) .method(Method.GET) .build()); }这里我踩过两个坑。第一个是Bucket没有设置为公共读生成的预签名URL用过一段时间会过期前端图片刷新后变裂图。解决方法是直接在MinIO控制台把Bucket策略改成public数据库只存对象名前端拼固定域名访问。第二个坑是文件名重复覆盖如果直接用原始文件名上传用户上传两张都叫img.jpg的图片后者会把前者覆盖掉。正确做法一定是用UUID或时间戳重命名对象名。另外数据库里我建议不要存完整的URL只存对象名即可这样以后换存储服务商或改域名都不用修改存量数据。5. 部署上线与项目复盘5.1 前后端打包与Docker部署项目收尾阶段部署是检验项目完整度的重要一环。后端打包成JAR包执行mvn clean package -DskipTests在target目录下生成house-portal.jar。前端执行npm run build生成dist目录里面是纯静态资源配置到Nginx里即可。生产环境我用了Docker Compose编排了两个容器一个跑Nginx托管前端静态资源并反向代理后端/api路径另一个跑SpringBoot应用。后端DockerfileFROM openjdk:8-jre-alpine COPY house-portal.jar /app/house-portal.jar ENTRYPOINT [java, -jar, /app/house-portal.jar]Nginx关键配置server { listen 80; server_name your-domain.com; root /usr/share/nginx/html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://backend: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; } }注意try_files $uri $uri/ /index.html;这一行Vue是单页应用刷新非首页路由时如果没配置它Nginx会直接返回404。只要在开发环境运行过一次npm run build并放到Nginx里测试就能明白这个配置有多重要。另外如果后端需要记录用户真实IP必须在Nginx里配置X-Real-IP和X-Forwarded-For头否则SpringBoot通过request.getRemoteAddr()拿到的全是Docker容器网关IP。这个点我在做访问量统计时踩坑后才反应过来。5.2 项目复盘哪里值得继续优化部署完成后我花了一天时间做整体复盘列了几条后续可以优化的方向。第一后端参数校验目前是手写if判断代码里有大量重复的判空逻辑统一换成Validated JSR 380注解之后Controller层会清爽很多。第二热点房源和首页楼盘列表可以加Redis缓存减少MySQL查询压力。房产门户的首页数据变化频率低但访问量大非常适合缓存。第三预约成功后可以加一个WebSocket通知让管理员端实时收到新预约提醒而不是靠刷新页面。第四如果房源数据量级上来搜索功能要从MySQL模糊查询升级成Elasticsearch这是房产平台最常见的搜索方案。最后再分享一个我自己的心得做这类全栈项目最大的误区是把精力全部花在界面好不好看上而忽略了把基础链路跑通。我刚开始写这个房产门户时连续两天都在调前端样式和路由动画等到真要登录联调才发现后端接口设计得一团乱。之后我调整了策略先画出一张完整的“数据流动图”把用户从打开首页、搜索房源、提交预约、管理员审阅再到预约回访的整个流程先理顺再去写代码进度反而快了很多。如果你也在做类似的项目真心建议你先花半天把流程理清楚这比多写一个炫酷的轮播图有用得多。
返回列表