ARTICLE DETAIL

资讯详情

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

SpringBoot+Vue不动产登记系统:完整设计与实现攻略

SpringBoot+Vue不动产登记系统:完整设计与实现攻略 每年做毕业设计选型的时候不动产登记系统这类题目几乎是常青树。但说实话能把这个题目做好的人不多。很多人一听不动产登记就以为是在写一个简单的房屋信息增删改查结果做出来的系统既没有体现登记业务的核心逻辑也没有分层架构的味道答辩时被老师问两句就露馅了。我在带毕业设计的过程中帮学生重构过好几套类似项目。这里我以SpringBoot Vue为核心完整回顾一套不动产权籍信息管理平台的设计与实现思路。它包含了权籍基础数据管理、登记业务流转、租赁合同管理、权限控制、定时提醒、统计图表这些完整闭环同时也足够贴合前后端分离架构这个答辩高频词。如果你正在准备这类选题或者已经写了半截但不知道怎么往下推这篇文章应该能给你不少可直接抄的作业。1. 为什么选不动产这条赛道毕设选题的业务拆解思路1.1 不动产权籍到底管的是什么先不说代码先把业务搞明白。很多人栽在这个题上就是栽在不知道自己要管理的数据是什么。权籍这个词拆开就是权属和簿籍。往浅了说它回答的是两个问题这套房子是谁的依据是什么往深了说这是一套以不动产单元为核心的数据体系包含坐落位置、建筑面积、用途、权利类型、权利人信息以及登记业务办理过程中产生的各种申请与审批记录。举个例子一套商品房它的基本信息是不动产单元号 坐落 建筑面积 房屋用途然后关联一个或多个权利人。这套房子里发生过一手房买卖、抵押贷款、租赁备案这些业务都是在不动产权籍这张大网下延伸出来的。所以你不能只建一张房屋表和一张业主表草草了事而是要以不动产单元为主轴把所有业务记录挂在它下面。1.2 我圈定的核心业务范围完整的不动产登记系统是一个庞大的政务系统涉及预告、首次、变更、转移、注销、更正等多种登记类型背后还要对接税务、住建、测绘。毕设项目不需要也不可能做这么全但你得有取舍的判断力。我这套系统的定位是面向权籍管理与登记业务办理的简化型管理平台。具体圈定了以下几块系统管理用户、角色、菜单权限。权利人档案管理录入、修改、查询权利人信息。不动产单元管理管理自然幢、逻辑幢、房屋登记信息维护不动产单元号。登记业务办理覆盖首次登记、变更登记、转移登记、预告登记、抵押登记流程上采用受理 - 审核 - 登簿三段式。租赁合同管理登记房屋出租信息维护承租人、租金、租期并做到期提醒。首页统计看板登记业务量、登记类型分布、租赁到期数量等。判断一个业务该不该纳入毕设我的标准很简单它要么能体现数据模型的设计能力要么能体现流程控制的能力要么能体现工程化能力比如定时、权限、图表。纯粹的字段增删改查能砍就砍。1.3 用户角色与权限边界这套系统设计了三个角色角色核心权限系统管理员用户管理、角色权限分配、菜单配置、数据维护登记业务员受理登记申请、录入权籍信息、草拟登记结果查询用户查询不动产单元信息、权利人信息、租赁台账为什么强调角色因为登记业务有明确的内部制衡要求业务员不能直接把一条登记申请置为登簿必须经过审核环节。这个逻辑在答辩时非常加分你可以明确告诉老师权限体系不是装饰它呼应了不动产登记的合规性要求。2. 技术栈选型背后的取舍不是追求流行而是追求稳妥2.1 SpringBoot版本、JDK与数据持久层先说后端。当前毕业设计的主流组合里SpringBoot 2.7.x JDK 8 仍然是最稳的选择不要因为网上全是SpringBoot 3的教程就盲目追新。SpringBoot 3要求JDK 17且部分第三方组件的兼容性调整较多对时间紧张的毕设来说不划算。如果你非要上3.x那至少得预留一周时间处理组件适配问题。持久层我用了MyBatis-Plus理由非常直接单表CRUD零SQL省掉大量机械编码时间。自带分页插件分页查询不用手写Limit逻辑。逻辑删除、自动填充这些功能挺好用适合写进工程化能力的答辩话术里。配合SpringBoot的项目结构标准的包划分如下com.example.realestate ├── controller ├── service ├── mapper ├── entity ├── dto ├── vo ├── config ├── common └── utilsController层只做参数接收和结果封装一切业务判断下沉到Service层。这样分层的目的不是美观而是答辩时你可以指着任意一个类说清楚它属于哪一层、为什么在这一层。我见过太多学生把SQL直接写在Controller里那根本不叫分层。2.2 前端技术栈Vue 3 Vite Element Plus前端我选了Vue 3的组合式API配合Vite构建、Element Plus组件库、Pinia做状态存储、Vue Router做路由、Axios发请求、ECharts画图。这里要说道说道Vue 2还是Vue 3的问题。Vue 2的教程确实多Element UI的答案也遍地都是但它已经进入了维护末期。Vue 3的回答题比如ref和reactive的区别、setup语法糖、Pinia与Vuex的对比在答辩时更容易展开。我建议直接用Vue 3遇到问题查Vue3 Vite 对应组件名即可资料已经足够多了。环境配置层面的坑非常集中第一批倒在初始化上的学生大多是Node版本不对。Vite 5要求Node 18而很多机器默认还是16。安装Vue依赖时一旦报错先执行node -v再执行npm -v确认版本后再决定是否升级。2.3 前后端分离架构到底好在哪这套系统坚持前后端分离是有原因的。后端纯提供RESTful API前端纯做展示和交互。两边可以并行开发也可以独立部署。最直接的好处是前端打包后可以交给Nginx托管后端SpringBoot独立跑在8080端口数据通过JSON交换。答辩的时候老师最常见的问题就是为什么要用前后端分离而不是传统的服务端渲染。你要能说清楚三个点开发职责分离、API复用性强、部署灵活。这三个点后面我会在部署部分具体演示。3. 数据库建模登记系统的根在权籍基础数据3.1 数据模型分两层基础档案与业务流水不动产登记系统的数据库设计我习惯分成两层来看。第一层是基础档案层也叫主数据层。它回答的是现在有哪些房子、哪些人。核心表包括sys_user系统用户。sys_role角色表。user_role用户角色关联表。owner_info权利人档案。estate_unit不动产单元表。第二层是业务流水层它记录的是发生了什么业务、办到哪一步了。核心表包括register_bill登记业务主表。register_step_log登记流程日志表。lease_contract租赁合同表。为什么必须区分这两层因为业务流水是会不断增长的而基础档案相对稳定。如果你把登记信息直接塞到不动产单元表里表结构会被不断变更的需求撑爆后期查询也乱成一团。3.2 核心表字段设计思路estate_unit不动产单元表是整套权籍模型的锚点字段设计上要体现不动产单元号的唯一性。我给的简化设计如下CREATE TABLE estate_unit ( id BIGINT PRIMARY KEY AUTO_INCREMENT, unit_code VARCHAR(64) NOT NULL COMMENT 不动产单元号, building_name VARCHAR(128) COMMENT 楼幢名称, location_desc VARCHAR(255) COMMENT 坐落位置, area DECIMAL(10,2) COMMENT 建筑面积, property_use VARCHAR(32) COMMENT 用途住宅/商业/办公等, status TINYINT DEFAULT 1 COMMENT 1-正常 0-已注销, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_unit_code (unit_code) ) COMMENT 不动产单元表;register_bill登记申请主表则要记录是谁、在什么时间、为哪套不动产、申请办什么事、现在办到哪个环节。核心字段包括登记类型、当前状态、申请人ID、不动产单元ID、申请时间、办理意见。这里强调一个设计细节登记类型用字典值管理不要写死在业务代码里。CREATE TABLE register_bill ( id BIGINT PRIMARY KEY AUTO_INCREMENT, bill_no VARCHAR(32) NOT NULL COMMENT 登记业务编号, register_type VARCHAR(32) COMMENT 登记类型first/change/transfer/..., estate_unit_id BIGINT COMMENT 不动产单元ID, applicant_id BIGINT COMMENT 申请人ID, status TINYINT COMMENT 0-草稿 1-受理 2-审核通过 3-登簿 4-驳回, current_handler VARCHAR(64) COMMENT 当前办理人, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_bill_no (bill_no) ) COMMENT 登记业务主表;另一个容易被忽视的register_step_log表非常关键。它记录每一次状态变更的操作人、操作时间、操作动作和意见。很多同学不做这张表答辩时一旦被问到如何追溯一条登记业务的办理历史就会卡住。3.3 登记状态流转不是外键能解决的很多学生习惯用外键去表达业务关系比如在子表里加个parent_id。但登记业务的流转是有方向、有时序、有条件的行为它不是简单的父子关系。我更推荐用状态字段 流转日志的组合。我的做法是状态只存当前节点历史轨迹全部落到日志表。这样既方便查询当前进度又能完整还原业务过程。这套方案在答辩时可以很自然地说它一方面保证了流程的可追踪性另一方面避免了状态字段的历史值覆盖问题。4. 后端落地状态机、鉴权、定时任务与文件管理4.1 后端工程结构与分层规范后端工程我按SpringBoot标准结构搭启动类放在RealEstateApplicationMapper接口统一加Mapper注解或者在启动类上使用MapperScan。配置文件用application.yml数据库配置、端口、日志级别、文件上传路径都写在里面。server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/real_estate?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: mapper-locations: classpath*:mapper/*.xml configuration: map-underscore-to-camel-case: true一个容易踩的坑是数据库连接串里的serverTimezone。不配这个东西查询日期数据时会遇到时区偏移看起来像是少了8个小时其实就是这没配好。我在联调时被这个问题卡过一下午后来一查就是时区问题。4.2 登记流程的状态机实现登记流程我做了四个状态草稿、受理、审核、登簿。后端接口层面我按动作来划分而不是按状态来划分。也就是说我不提供修改状态这种危险接口而是提供提交受理、提交审核、审批同意、登簿这类语义明确的接口。每个接口内部去校验当前状态是否合法。我定义了一个状态枚举public enum RegisterStatus { DRAFT(0, 草稿), ACCEPTED(1, 受理), APPROVED(2, 审核通过), REGISTERED(3, 登簿), REJECTED(4, 驳回); private final int code; private final String desc; // 构造、getter省略 }流转的时候用同一个状态更新SQL做控制核心逻辑是当前状态必须等于预期状态才允许更新到下一状态防止并发场景下两个人同时操作同一单Update(UPDATE register_bill SET status #{newStatus}, current_handler #{handler} WHERE id #{billId} AND status #{oldStatus}) int updateStatus(Param(billId) Long billId, Param(oldStatus) int oldStatus, Param(newStatus) int newStatus, Param(handler) String handler);每次更新状态时同时写入一条register_step_log。这样状态机 日志表就完整了。答辩时老师问你这系统能不能支持审批驳回、退回某个环节你可以直接展示日志表每一次操作都有记录天然支持回溯。4.3 JWT登录拦截与角色权限登录我用了JWT理由没什么花哨无状态、天然适合前后端分离。用户登录成功后后端返回一个token前端存到Pinia里后续请求在Axios拦截器中加Authorization头。Component public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (token ! null JwtUtils.verify(token)) { return true; } response.setStatus(401); return false; } }权限控制我不建议在毕设里引入Spring Security全家桶学习成本高配置复杂容易把时间耗在过滤器链上。我采用的是自定义注解 拦截器校验角色的轻量方案Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface RequireRole { String[] value(); }在需要权限的接口上加上这个注解然后拦截器里取当前用户的角色去比对。这个方案的好处是代码简单、可解释性强、也能体现你对权限模型的理解。4.4 租赁到期提醒的定时任务毕设里加定时任务是一个小亮点。我在租赁管理模块里做了一个到期前30天自动提醒的功能。SpringBoot实现这个太简单了两步搞定。启动类加EnableScheduling然后在定时任务类上写Component public class LeaseReminderTask { Resource private LeaseContractMapper leaseContractMapper; Scheduled(cron 0 0 8 * * ?) public void reminderLeaseExpire() { LocalDate date LocalDate.now().plusDays(30); ListLeaseContract expiringList leaseContractMapper.selectExpiringBefore(date); for (LeaseContract contract : expiringList) { // 生成提醒记录或者调用消息推送 } } }定时任务一定要做可观测至少加个日志输出否则你演示时都不知道它到底有没有跑。我一般会在任务执行到最后打印一行任务执行完成共扫描租赁合同xx条其中到期预警xx条。这个日志也是答辩时能直接拿出来的干货。4.5 附件上传与访问链路房屋登记业务往往需要上传材料附件比如身份证扫描件、房屋买卖合同、登记申请表。我用的方案是把文件保存到本地磁盘目录然后通过SpringBoot的静态资源映射暴露访问URLfile: upload-dir: D:/dev/real_estate/upload/Configuration public class WebResourceConfig implements WebMvcConfigurer { Value(${file.upload-dir}) private String uploadDir; Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/files/**) .addResourceHandler(file: uploadDir); } }测试的时候有个细节要注意很多同学写测试脚本时试图通过RestTemplate直接传MultipartFile结果总是报错。正确姿势是把文件包装成ByteArrayResource再塞进MultiValueMap。这个坑在对接联调时经常出现提前知道能省不少时间。5. 前端Vue落地从路由守卫到搜索表格的组件化5.1 搭建Vue 3项目的几个关键步骤前端项目我用npm create vitelatest初始化选择Vue模板然后按需安装依赖。目录结构我习惯划分成src ├── api ├── assets ├── components ├── layout ├── router ├── store ├── views ├── utils └── App.vueapi目录里每个模块对应一组后端接口比如api/register.js、api/estate.js。这样做的好处是页面组件中不会到处散落axios.get维护时一眼就能找到某个业务的所有接口。Vue项目初始化时最容易踩的坑是依赖安装失败。npm install报权限错误或者网络超时先把registry切到国内镜像再试。另外就是版本冲突建议锁定主要依赖的版本号不要全用^前缀。5.2 路由守卫与状态存储路由我做了两个层级外层是控制台Layout内层是各业务页面。登录页独立在Layout之外。核心代码是路由守卫router.beforeEach((to, from, next) { const token useUserStore().token if (to.path /login) { next() } else if (!token) { next(/login) } else { next() } })这里有个细节token放Pinia的同时必须同步放一份到localStorage。因为Pinia是内存态刷新页面就没了。刷新后重新从localStorage恢复用户信息这是一个必坑点。路由采用hash还是history模式也要提前想好。如果打算把前端打包后丢进SpringBoot的静态目录建议直接用hash模式省掉生产环境刷新404的问题。如果坚持用history模式就得在后端或Nginx配合处理。5.3 用插槽封装可复用的搜索表格页不动产登记系统里列表页非常多权利人列表、房屋列表、登记列表、租赁列表。如果每个页面都从头写一遍搜索框和表格项目会膨胀得很难看。我封装了一个SearchPro.vue把搜索区域和表格主体抽象成可配置组件核心是靠插槽做扩展。template div classsearch-pro el-form :modelqueryParams inline slot nameform-header / el-form-item el-button typeprimary clickhandleSearch查询/el-button el-button clickhandleReset重置/el-button /el-form-item /el-form el-table :datatableData v-loadingloading slot / el-table-column label操作 aligncenter template #defaultscope slot nameoperations :rowscope.row / /template /el-table-column /el-table el-pagination v-model:current-pagequeryParams.pageNum v-model:page-sizequeryParams.pageSize :totaltotal current-changefetchList / /div /template默认插槽放表格列具名插槽operations放操作按钮。列表页的列千变万化但外壳完全一样。这套组件化思路在答辩时值得重点讲它能体现你理解复用这件事而不仅仅是会调Element Plus的API。5.4 ECharts图表与低代码化思路首页统计看板我用了ECharts做了两个图一个是各登记类型的数量柱状图一个是近期登记业务量趋势折线图。接口返回的数据结构不要让前端做太多二次加工最好后端直接抛出一个适合ECharts的{ name, value }或者{ dates, counts }结构。const res await getRegisterStatistics() const option { xAxis: { data: res.data.dates }, yAxis: {}, series: [{ type: bar, data: res.data.counts }] }如果时间富余可以用el-dialog加一个拖拽组件让用户在配置页里自由调整首页看板的展示卡片顺序。这不难做但对低代码化这个词的呼应非常明显。答辩现场只要演示一次效果就比你说的十句话都有用。6. 联调、部署与演示从开发机到答辩现场6.1 vite代理解决本地联调跨域本地开发时前端跑在5173端口后端跑在8080端口浏览器的同源策略会拦掉请求。最快的解决方案是在Vite配置文件里加代理注意这个方案只作用于开发环境。server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true, rewrite: (path) path.replace(/^\/api/, ) } } }代理设置好以后前端请求/api/login会被转发到后端/login跨域问题直接消失。开发环境和生产环境的接口基础路径不同建议把baseURL拎成环境变量不要硬编码。6.2 生产构建的两种部署姿势生产部署有两种常见姿势两种都值得掌握。第一种把Vue打包放进SpringBoot形成单一Jar包。执行npm run build后把生成的dist目录下的所有文件复制到src/main/resources/static再重新打包SpringBoot。这样一条命令就能启动整个系统非常适合答辩演示环境。缺点是前端代码更新后需要重新打包Jar。第二种Nginx托管前端SpringBoot独立跑后端。server { listen 80; server_name localhost; root /usr/share/nginx/html; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://localhost:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }第二种方式更接近真实的生产形态也更能体现你理解运维部署。建议在毕设论文里把两种方式都写上一个作为开发演示方案一个作为生产部署方案。6.3 答辩演示与源码交付的注意点答辩演示最怕的就是现场数据库是空的。我建议准备一套完整的演示数据覆盖刚录入的草稿单 - 处于审核中的单 - 已完成登簿的单 - 一条即将到期的租赁合同。演示时按业务顺序走第一步登录系统展示不同角色的看到菜单不同。第二步查询一条不动产单元信息展示权籍字段。第三步发起一条转移登记走完受理、审核、登簿流程。第四步展示流程日志。第五步打开首页看板展示统计图表。第六步展示定时任务扫描到的租赁到期提醒。另外源码如果要发给老师或者队友记得把配置文件里的数据库密码、本地路径替换成环境变量或者说明文件避免别人拿到代码后因为绝对路径跑不起来。这种细节能在验收阶段帮你省很多沟通成本。7. 我踩过的坑和答辩时的高频提问7.1 五个高频踩坑复盘第一个坑日期序列化格式问题。后端LocalDateTime返回给前端默认是ISO格式而且Jackson如果没有正确配置时区可能不对。在application.yml里加上统一格式转换spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8第二个坑前端代理跑通了生产环境却跨域。开发环境用Vite代理不需要后端配CORS但前端构建成纯静态资源后如果静态资源和API不在同源跨域就会现形。建议后端统一配置CORS或者在Nginx配置里处理别只在开发环境测试跑一遍就完事。第三个坑MyBatis-Plus逻辑删除字段与唯一索引冲突。如果开启逻辑删除而业务表上又建了唯一索引逻辑删除掉的记录仍然占着唯一索引位置再次插入同字段数据会冲突。演示时一旦遇到要给老师解释清楚或者把唯一索引改成联合唯一索引。第四个坑前端路由history模式刷新404。这个问题在部署部分反复出现。要么用hash模式要么在Nginx里配try_files $uri $uri/ /index.html;。我建议毕设直接用hash模式省心。第五个坑Vue路由参数丢失。列表页跳详情页时如果有同学习惯把对象通过路由参数整个传过去刷新后就会丢。正确做法是传ID详情页再根据ID拉接口。7.2 答辩时的四类高频提问与回应思路为什么选前后端分离架构不要只说主流两个字。展开谈后端独立提供RESTful接口前端负责交互与渲染两个团队可以并行开发前端打包产物可交给CDN或Nginx托管后端的负载能力可以独立扩展在考试或实际项目中同一套后端可以支撑Web端、管理后台端等多端复用。登记状态是怎么控制的直接讲状态机的思路用枚举定义合法状态所有状态变更封装成语义化接口更新时校验旧状态是否符合预期再执行到新状态的变更同步写流程日志。强调这是对业务流程的建模不是简单改一个字段。你的权限系统怎么实现的谈JWT 自定义注解 拦截器。说明为什么不用Spring Security毕设项目角色模型简单安全框架带来的过滤器链和配置复杂度超过收益。同时强调理解方案能在复杂场景下替换为安全框架。定时任务如果单次执行时间超过周期会不会重叠触发这是一个很好的进阶问题体现老师愿意深挖。SpringBoot的Scheduled默认单线程执行如果任务超时下一次触发可能延后或者出现异常。解决方案是加分布式锁或者通过状态标记保证同一时间只有一次任务在执行。你只需要表达出我理解这个风险就已经超过大多数同学了。7.3 如果时间富余还可以加的点如果核心功能都完成了还有精力我建议按顺序加这几样东西性价比从高到低排列操作日志模块记录用户的所有关键操作。数据看板下的API缓存统计接口结果放Redis。不动产单元变更历史做一次比一次完整的图谱式展示。批量导入用EasyExcel导入Excel格式的权籍批量数据。我指导的几届学生里凡是把状态机、日志表、定时任务这几个点都做到了的答辩基本都稳过。这个项目最终代码量不算大但每个模块都能讲出设计逻辑不是那种从网上下载就跑的黑匣子项目。对毕设来说能在答辩时讲明白为什么比写了一千行别人看不懂的代码重要得多。
返回列表