资讯详情

在线房屋租赁系统与电子签约全流程设计与实现

📅 2026/9/15 8:37:46 | 华诺云谱 👁 阅读
在线房屋租赁系统与电子签约全流程设计与实现
我最初接这个项目的时候第一反应是“这不就是一个带合同的租房平台吗”但真正把需求理完、数据流画完、签约逻辑跑通之后才发现最难的根本不是信息发布而是电子签约这条链路的可信度和系统整体的状态一致性。m225在线房屋租赁和电子签约系统就是这么来的一套把房源管理、看房预约、租客认证、电子签名、合同存证、签约状态流转全流程线上化的系统。它不是做一个简单的分类信息站而是要把租房最核心的“签字画押”这个环节也搬到线上并且要做到合同内容在签约后不可篡改、整个过程可追溯、每一笔操作都有据可查。这篇文章我会把整个设计与实现复盘一遍从需求拆解、技术选型、数据库设计到电子签约的底层实现细节再到我踩过的坑和排查记录全部写清楚适合正在做同类项目或者想了解房屋租赁系统怎么落地的人参考。1. 项目背景与整体设计思路m225这个项目代号是我自己起的内部编号实际开发周期大约五周属于一个从零搭建的在线房屋租赁与电子签约一体化系统。最开始的需求其实很直白做一个小型房屋租赁平台让房东能发房源、租客能找房、最后双方能在线签订租赁合同。但需求越往下拆发现真正值得做的核心并不在“房源展示”这个层面而在于怎么把房屋租赁这件事从线下的“看房—谈价—签纸面合同—交接钥匙”完整搬到线上同时保证合同效力。1.1 这个系统要解决的真实痛点房屋租赁市场存在几个老生常谈但一直没被小系统解决好的问题。第一是信息不透明房源是否真实、房东身份是否可靠租客很难判断。第二是合同管理混乱纸质合同难保存、容易涂改、内容版本难以统一。第三是签约流程繁琐双方要约时间、见面、签字、交换纸质合同跨城市租房尤其麻烦。针对这些问题m225把系统的重心从“信息展示”转向“业务闭环”。用户不再只是浏览房源而是在平台内完成从咨询、预约看房、实名认证到在线签约的全过程。特别是电子签约模块核心要解决两个问题一是签约双方身份可信二是合同内容在签署后不能被篡改。后者是电子签约最敏感的环节如果系统连这一点都做不到电子合同就没有任何实际价值。1.2 核心角色与业务链路设计系统的用户角色划分为三类租客、房东、平台管理员。租客侧的核心体验是找房、约看、签约房东侧的核心体验是发房、接单、发起签约管理员侧的核心体验是审核、统计、异常处理。这个三角色模型几乎是房屋租赁系统的标准结构但关键在角色之间的业务链路如何串联。我设计的核心链路是“发房-审房-找房-约看-认证-签约-履约”。房东发布房源后管理员审核通过才能上架租客浏览房源后提交看房预约或在线咨询确认意向后双方进入实名认证环节通过之后房东发起电子合同租客在线签署合同状态自动流转为“生效中”。整条链路没有一步需要线下完成也没有一步允许跳过前置状态比如没有完成实名认证的用户不能发起签约没有审核通过的房源不能对外展示。为什么要把流程卡得这么死因为房屋租赁的业务场景里一旦合同出了问题后续纠纷的代价极高。系统设计阶段宁可牺牲一部分灵活性也要保证每一步都有迹可循。状态机在这里起到了关键作用房源状态和合同状态都不能随意跳转比如“已出租”的房源不能再次被预约未完成的合同不允许被删除这些约束在代码层面硬性控制而不是靠人工判断。2. 技术选型与系统架构设计技术选型阶段没有追求新奇框架而是尽量选择成熟稳定、团队上手成本低的组合。这个系统最终采用的是经典的前后端分离架构后端用Spring Boot前端用Vue 3数据库用MySQL缓存用Redis对象存储用云OSS全文检索暂时用MySQL的LIKE加索引方案解决因为项目初期房源量并不需要引入独立的搜索引擎。2.1 技术栈选择及选型理由后端选了Spring Boot 2.7.x这是目前社区生态最成熟、问题排查资料最丰富的版本。持久层框架用的是MyBatis-Plus而不是Spring Data JPA原因是租赁业务中动态查询场景非常多比如房源价格区间筛选、状态组合查询、签约记录分页等MyBatis-Plus的QueryWrapper能让这类条件拼装清晰直观也方便后续手写复杂SQL进行优化。数据库使用MySQL 8.0InnoDB引擎utf8mb4字符集。签约记录这类表对事务要求极高InnoDB在这方面的表现最稳定。Redis主要承担两方面工作一是房源热数据的缓存避免每次查询都打到数据库二是分布式锁的实现保证同一份合同不会被双方重复签署。文件存储方面房源的实拍图片、户型图以及签署后的合同PDF都存放在OSS数据库只保存文件的访问路径这样设计能让应用服务器无状态化后续横向扩容会非常简单。前端选的是Vue 3加Element Plus组件库开发效率很高。状态管理用了Pinia路由用的Vue Router。租客端和房东端没有拆分成两个独立前端项目而是在同一个SPA里通过路由级权限控制进行页面隔离管理员后台也是同一套前端工程只靠不同路由模块和接口权限做区分。好处是部署简单、代码复用率高坏处是路由守卫的逻辑要写得很细致每个路由都需要声明允许访问的角色列表。2.2 系统模块划分与核心接口整个系统在逻辑上拆成六个核心模块用户认证与权限模块、房源管理模块、在线预约与消息模块、合同与电子签约模块、支付与账单模块预留、平台审核与统计模块。这里强烈建议在项目一开始就做模块边界划分而不是写到哪里算哪里。我见过太多没有明确分层的项目最后合同模块里混着房源查询代码房源模块里混着用户逻辑维护成本高到离谱。核心接口设计上我按照业务维度做了Restful风格划分几个关键接口如下模块接口路径功能说明用户POST /api/user/register用户注册区分租客/房东角色用户POST /api/user/realname-auth实名认证对接身份证识别与活体检测房源POST /api/house/publish房东发布房源房源GET /api/house/query租客端房源列表查询支持多条件筛选房源PUT /api/house/audit管理员审核房源改变房源状态合同POST /api/contract/create-draft根据房源详情生成合同草稿合同POST /api/contract/sign发起/完成电子签署内含防篡改逻辑合同GET /api/contract/verify验证合同完整性与签署信息一致性接口设计时遵循了一个原则所有写操作都做成幂等接口。尤其是发房源、创建合同、签署合同这几个动作前端可能因为网络原因重试多次同一请求如果被执行两遍就会产生脏数据。幂等性的实现方式不复杂前端每次发起操作时生成一个requestId后端在Redis中记录这个请求ID如果同一ID在短时间内再次到达直接返回第一次的结果。3. 数据库设计与状态一致性保障数据库设计是这个项目中最能体现“设计与实现”含量的部分。在线租房不是简单的内容管理数据表之间的关联关系复杂尤其是合同表和签约记录表需要能够完整还原“一笔签约是怎么发生的”。如果表设计不到位后期不管是查纠纷还是做统计都会非常痛苦。3.1 核心表结构与字段逻辑用户表相对常规核心字段包括手机号、昵称、角色类型、实名状态、身份证号加密存储、真实姓名。这里特别注意身份证号不能明文存落库前需要做加密处理接口返回时也要做脱敏。房源表是业务表里最关键的一张字段设计上我花了比较多时间。除了基本信息标题、描述、面积、朝向、楼层、租金、租赁方式之外还设计了房源编号、发布人ID、所在区域编码、经度纬度坐标、房源状态、审核状态、审核意见等字段。为什么要单独存经纬度因为后续要做地图找房和距离排序没有坐标信息只能做行政区划筛选产品形态差很多。合同表的核心设计在于它不仅仅是存一个PDF路径而是要把合同的结构化信息拆出来。我设计了合同编号、关联房源ID、出租方ID、承租方ID、起止日期、月租金、押金、付款方式、合同模板版本号、合同内容摘要、合同状态等字段。其中合同内容摘要非常关键它是对合同全文做哈希后的字符串验签的时候拿它和合同文件重新计算的哈希做比对。签约记录表记录每一次签署事件包括签名人ID、签署时间、签署方式短信验证码/人脸识别、签名值、设备指纹、IP地址。这张表相当于整个电子签约过程的“黑匣子”任何一次签署动作都被记录下来事后可以完整复盘。3.2 状态机设计与流转约束租房业务本质上是一串紧密咬合的状态流转过程。我在系统里设计了两个最主要的枚举状态房源状态和合同状态。房源状态的流转路径是待审核 - 审核通过上架 - 已出租 - 已下架。任何状态变更都必须通过后台服务层的断言校验待审核房源不能出现在租客端列表已出租房源如果被再次提交签约请求服务端必须直接拒绝并返回明确错误信息。合同状态的流转路径更细化一下草稿 - 待签署 - 部分签署 - 已签署 - 生效中 - 已到期 - 已终止。这里面有两个容易踩坑的状态“部分签署”和“已签署”。部分签署指的是房东已经签署但租客尚未完成签署已签署则意味着双方都完成了签署动作此时合同才具备完整效力。状态之间禁止跳级。比如“草稿”不能直接变成“已签署”“已签署”也不能直接变成“已到期”必须经过时间判断或者双方确认才能流转。这个约束我放在了数据库的update语句里利用MyBatis-Plus的UpdateWrapper在更新时带上当前状态条件如果实际更新的行数为0说明状态已经被其他请求改变直接抛出并发异常。这种做法比单纯依赖应用层判断要可靠得多。4. 电子签约模块的核心实现细节电子签约是m225系统里价值最高也最需要讲清楚逻辑的模块。做之前我查了不少资料也试用过第三方的电子签约API但考虑到项目需要一个可运行的独立实现最终还是选择自己写核心逻辑把散列计算、摘要存证、验签比对这三步做扎实。4.1 电子签约的底层原理与信任基础电子签约能被认可依赖的不是某个炫酷的前端交互而是三个技术点数据完整性、签署身份可信性、时间可信性。数据完整性通过哈希散列实现。合同文件无论是一个PDF还是一段HTML通过SHA-256算法计算后可以得到一个定长的摘要字符串。任何对合同内容哪怕一个字节的改动都会导致摘要完全不同。这个摘要就是“数字指纹”签约时把这份指纹固化保存之后任何时候重新计算合同文件的摘要只要和保存的不一致就说明合同被篡改过。身份可信性通过实名认证体系实现。房东和租客在签约前都必须在系统内完成实名认证包括身份证号码核验和人脸活体检测。签署时记录签署人的手机号、认证信息、签署操作的具体时间把用户的实名信息与签名动作绑定这样签名无法被抵赖。时间可信性通过可信时间戳来保证。签约记录中保存服务器标准时间并对时间戳本身也参与摘要计算。假设合同签署于2024年6月1日这个时间信息就不能在事后被改成其他日期否则摘要就不一致。4.2 签约核心流程与关键代码实现整个签约流程我拆成四步生成待签署合同、计算合同摘要、签署人完成签名、存证并通知验签。为了方便说明我摘录后端的核心代码逻辑。第一步根据房源信息和租赁条款生成合同文件核心代码如下public String generateContractDraft(CreateDraftDTO dto) { // 构造合同内容这里使用模板引擎渲染 ContractTemplate template contractTemplateMapper.selectById(dto.getTemplateId()); String content template.render(dto.getHouseInfo(), dto.getTenantInfo()); // 将合同内容写入PDF文件 ByteArrayOutputStream bos new ByteArrayOutputStream(); PdfWriter writer new PdfWriter(bos); PdfDocument pdf new PdfDocument(writer); Document document new Document(pdf); document.add(new Paragraph(content)); document.close(); // 将PDF上传OSS拿到合同文件的地址 String fileUrl ossService.upload(contract_ dto.getContractNo(), bos.toByteArray()); return fileUrl; }第二步计算合同文件的SHA-256摘要这一步是防篡改的根基public String calculateContractDigest(byte[] contractFileBytes) { try { MessageDigest digest MessageDigest.getInstance(SHA-256); byte[] hash digest.digest(contractFileBytes); StringBuilder sb new StringBuilder(); for (byte b : hash) { sb.append(String.format(%02x, b)); } return sb.toString(); } catch (NoSuchAlgorithmException e) { throw new BizException(不支持的摘要算法); } }第三步签署人确认签约时把签署人认证标识、签署时间和合同摘要绑定起来生成一条签名记录Transactional(rollbackFor Exception.class) public SignResult sign(SignRequest request) { // 分布式锁防止同一合同并发签署 String lockKey CONTRACT_LOCK_ request.getContractNo(); boolean locked redisLock.tryLock(lockKey, 10, TimeUnit.SECONDS); if (!locked) { throw new BizException(当前合同正在签署中请勿重复操作); } try { Contract contract contractMapper.selectByNo(request.getContractNo()); // 校验合同状态只有PENDING_SIGN且当前用户是参与方时允许签署 if (!ContractStatus.PENDING_SIGN.getCode().equals(contract.getStatus())) { throw new BizException(合同状态不允许签署); } if (!request.getUserId().equals(contract.getLandlordId()) !request.getUserId().equals(contract.getTenantId())) { throw new BizException(当前用户不是合同签署方); } // 重新计算合同文件摘要防止内容在签署前被改动 byte[] contractBytes ossService.download(contract.getFileUrl()); String currentDigest calculateContractDigest(contractBytes); if (!currentDigest.equals(contract.getContentDigest())) { throw new BizException(合同内容已被修改签署已中止); } // 记录签名动作 SignRecord record new SignRecord(); record.setContractNo(contract.getContractNo()); record.setUserId(request.getUserId()); record.setSignedTime(LocalDateTime.now()); record.setDigest(currentDigest); signRecordMapper.insert(record); // 更新合同签署状态 updateContractSignStatus(contract); return SignResult.success(contract.getContractNo(), currentDigest); } finally { redisLock.unlock(lockKey); } }第四步验签逻辑。任何时间点只要上传合同文件和签署记录系统就能重新计算摘要并与历史记录比对public VerifyResult verify(String contractNo) { Contract contract contractMapper.selectByNo(contractNo); byte[] contractBytes ossService.download(contract.getFileUrl()); String currentDigest calculateContractDigest(contractBytes); boolean integrity currentDigest.equals(contract.getContentDigest()); ListSignRecord records signRecordMapper.selectByContractNo(contractNo); // 签署记录必须包含双方签名才算完整 boolean signedByBoth records.stream() .anyMatch(r - r.getUserId().equals(contract.getLandlordId())) records.stream() .anyMatch(r - r.getUserId().equals(contract.getTenantId())); return VerifyResult.builder() .integrity(integrity) .signedByBoth(signedByBoth) .signRecords(records) .build(); }这套逻辑的核心思想就是合同文件作为唯一事实来源摘要作为合同内容的固化快照签署记录作为链路证明。三者缺一不可。4.3 防篡改与实际部署中的校验策略防篡改不能只依赖用户主动验签系统自身也要在关键节点做被动校验。我在三个节点做了强制校验合同下载时、合同发起签署时、合同归档入数据库时。每次都会重新计算摘要并和历史值比对一旦不一致立即报警并阻断操作。这里分享一个真实的部署经验合同文件上传OSS后OSS会返回一个ETag值实际上就是文件的MD5。这个值我会额外存一份用来做第一层快速校验。SHA-256摘要做第二层校验仔细核对两个摘要能确保文件在存储链路中没有被替换。还有一点常被忽略合同模板的版本管理。租赁合同并不是一直不变的平台每隔一段时间可能会调整条款模板。如果租客在2024年1月签的合同用的是V1版本模板那么这个合同的摘要必须基于V1模板生成的PDF计算。所以在合同表中保存模板版本号归档时才能确保后续任何验签操作使用的模板不会出现混淆。5. 租赁业务核心流程的完整线上闭环技术框架和数据设计都定下来之后真正的工作量在业务环节的整合。m225的租赁业务流程需要用户在前端完成一系列操作每一步之间都有明确的先后关系。做好这个闭环系统才算真正可投入使用。5.1 房源发布与平台审核机制房东认证通过后可以发布房源。发布表单包含房屋类型、厅室、面积、朝向、楼层、租金价格、押金方式、入住时间、房屋描述、配套设置、实拍图片等字段。前端做了比较严格的校验租金必须是有效数字范围图片格式限定在jpg、png、webp大小不超过5兆至少上传3张实拍图避免出现挂羊头卖狗肉的情况。后端在接收房源数据时做了两件额外的事设置房源编号和计算坐标点。房源编号使用时间戳加随机数生成确保唯一经纬度通过高德地理编码接口根据地址自动解析存入数据库。所有待审核房源的初始状态都是“待审核”只有管理员从后台审核通过房源才会出现在租客端的搜索列表中。审核的运营逻辑也值得说一句我设计了一个“人工抽审自动预审”的机制。自动预审能拦截明显违规的房源比如租金远低于区域平均价、图片格式异常、营业执照缺失等。通过预审的房源进入人工抽审队列管理员能快速查看房源完整信息、图片和坐标标记一键通过或驳回。5.2 看房预约与租客身份核验房源上架后租客浏览房源详情页时可以发起看房预约。预约操作不是简单的留言而是必须选择具体的时间段系统会校验该时间段房东是否已被其他预约占用避免撞车。预约成功后房东端会收到待确认消息房东确认后双方会得到明确的看房时间和地点。很多租赁平台走到这一步就停了但m225在预约环节之后强制加入了租客实名认证环节。原因很实际房东最担心的是把房子给谁住了都不知道。预约看房前查看租客是否已实名可以让房东过滤掉大量无效咨询。租客实名认证的流程包含身份证拍照识别和人脸活体检测认证信息成功后会同步到签约模块后续签合同不再需要重复认证。在实际测试中我发现强制实名确实增加了一部分用户流失但从最终合同签约转化率来看留下的用户质量明显更高房东也更愿意配合平台流程。这一步取舍是值得的。5.3 合同发起、电子签署与履约交接当双方确认租赁意向后房东在“我的房源-选定租客-发起合同”入口发起电子合同。系统根据房源信息和租客信息自动填充合同核心字段房东只需要核对起止日期、月租金、押金、付款周期、违约金等条款然后点击“发送给租客”合同状态变为“待签署”。租客收到签署通知后进入合同详情页逐条预览合同条款。确认无误后完成签署此时系统记录租客的签名信息。房东侧也会收到签约完成通知合同状态自动变为“生效中”。整个合同文件的PDF会同步归档到OSS数据库保存文件地址和摘要。履约交接环节我做了两个实用功能到期提醒和退租确认。合同到期前30天和7天分别向双方发送提醒通知退租时房东确认房屋完好无损后点击“确认退租”合同状态变为“已终止”系统自动生成退租记录。这样整个租住生命周期在平台上形成了完整闭环。6. 常见问题排查与避坑经验实录项目开发过程中踩了不少坑有好几个问题排查了大半天才定位到根因。整理出来给做同类系统的朋友一个参考。有些问题不做到一定量级的并发或者数据量确实不容易暴露但等暴露再处理就晚了。6.1 并发签署同一合同导致状态错乱开发和联调阶段我同时用两个账号模拟房东和租客在同一时间点击签署发现合同状态偶尔会变成“已签署”但签署记录表里只有一条签名记录。原因很清楚两个请求并发查询时都读到合同状态为“待签署”于是都通过了状态校验各自插入一条记录状态更新互相覆盖。这里有两个问题的根因一个是数据库并发控制没做另一个是获取锁的粒度不够。解决办法Redis分布式锁的key用合同编号固定住同一份合同的签署请求必须串行执行同时update合同状态时加上 where status PENDING_SIGN 条件受影响行数不是1就说明状态已过期。双重校验之后这个并发问题彻底解决了。6.2 Redis缓存与数据库房源数据不一致房源列表页为了提高响应速度把热门房源缓存到了Redis里。结果管理员审核通过了一间房源前台列表里始终不出现或者房东下架了房源前端仍然能搜到。原因是没有做缓存失效策略。这个问题排查起来其实不难通过日志发现查询接口直接命中缓存根本没有走到数据库。解决方式并不复杂审核房源、下架房源、修改房源信息等所有写操作执行后主动删除该房源对应的缓存key。热门列表使用简化后的房源信息结构并设置短TTL比如五分钟过期。不再盲目追求“永久缓存手动同步”的方案缓存一致性从设计上就降低复杂度。6.3 合同PDF中文乱码与格式错乱生成合同PDF时发生了一个很典型的问题模板渲染出来的中文内容在PDF里全是乱码部分段落格式也错乱。排查后发现是项目中缺少中文字体文件PDF默认字体不支持中文编码。解决方案非常直接引入一款支持中文的字体文件比如思源黑体或者阿里巴巴普惠体在生成PDF时显式指定字体类型。同时把PDF模板中的样式规则从复杂的Table结构改成简单的段落和分段这样不同长度内容的渲染结果都保持稳定。如果项目里也有类似需求建议早点把字体和模板测试到位不要等到上线前再处理那会非常被动。6.4 实名认证接口响应超时对接第三方实名认证服务时遇到过接口响应偶尔超过5秒甚至更久的场景。前端用户已经提交了身份证信息页面一直转圈用户体验极差。排查后发现第三方接口的QPS限制比较低高峰期会发生排队服务端默认的HTTP连接超时时间是3秒排队期间的请求直接超时中断。处理思路是双保险一是把外呼操作从同步改为异步用户提交后立刻返回“认证处理中”的状态认证结果通过WebSocket或轮询通知前端二是在服务端设置合理的超时时间并增加重试机制比如单次超时改为10秒失败后最多重试3次重试之间间隔2秒。经过这个调整没有再出现过用户认证被中断的情况。6.5 常见问题速查表问题现象排查思路解决建议同一合同被重复签署检查状态判断是否原子加Redis分布式锁update条件带状态前台房源与后台不一致检查缓存是否有失效策略写操作后主动删缓存热点数据短TTLPDF中文乱码检查字体资源是否配置引入中文字体文件并显式指定图片上传失败检查OSS的Bucket权限策略修改为只允许带签名的URL访问签约后验签失败检查下载文件是否被篡改比对OSS ETag与计算出的摘要值用户登录状态丢失检查JWT过期时间设置合理的过期时间并接入刷新机制7. 部署环境与项目目录参考很多朋友做这类项目到“代码能跑”就结束了但我建议把部署环境也纳入设计一并考虑。m225采用Docker Compose做本地和测试环境编排正式环境使用云服务器加云数据库加对象存储。Docker镜像里包含了Spring Boot应用、Redis、Nginx三个服务MySQL使用云数据库的托管实例每周自动备份。项目目录结构按照Maven标准布局核心模块以package区分com.m225 ├── common // 通用工具、异常、常量 ├── config // 全局配置、拦截器、跨域 ├── security // JWT认证与权限控制 ├── module │ ├── user // 用户、认证、实名 │ ├── house // 房源、审核、坐标 │ ├── contract // 电子合同、签署、验签 │ ├── order // 预约与消息 │ └── admin // 后台管理 └── util // 工具类如哈希计算、PDF生成这个结构的好处是职责单一新增一个业务模块时直接新增一个package不需要改动已有模块的代码扩展性和可维护性都比较好。8. 写在最后的经验复盘m225这个项目做下来我个人最深的感受是在线房屋租赁系统真正难的不是房源展示和搜索而是把合同签署这件事做扎实、做可信。如果你也想做同类系统我建议提前想清楚几个问题用户的身份怎么验证、合同摘要怎么算、合同文件存哪里、签约记录怎么保存、验签入口怎么设计。这五个问题想清楚了代码实现只是时间问题想不清楚就动手后面大概率要大改。另外有一个非常实际的建议电子签约的防篡改校验一定要做到“有事前校验、事中记录、事后可查”不要只写一个verify接口就觉得大功告成。真实业务中合同内容在签署前被篡改的可能性虽然小但一旦发生就是灾难。我把内容摘要的比对放到签署前和签署后各做了一遍牺牲了一点性能换来了完整的安全性。最后再分享一个小技巧项目里所有涉及合同状态变化的操作日志都单独记录一份不要和其他操作日志混在一起。合同纠纷一旦出现需要的是从签约到履约的完整时间线混在一起查起来非常吃力。独立日志表的设计虽然微不足道但真到排查问题的时候你会感谢当初多写的这几行代码。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。