文章目录
一、项目演示
项目演示地址: 视频地址
二、项目介绍
项目描述:这是一个基于SpringBoot+Vue框架 开发的民宿房屋租赁系统。首先,这是一个前后端分离的项目,代码简洁规范,注释说明详细,易于理解和学习。其次,后端采用微服务架构,功能丰富,具有一个民宿房屋租赁系统该有的所有功能。
项目功能:此项目分为三个 角色:普通用户、房东和管理员。
普通用户 有登录、注册、按条件筛选房屋信息、查看房屋详情、预订房屋下单、管理个人订单、收藏房屋、评价房屋、申请成为房东、提交意见反馈、查看个人数据统计等等功能。
房东 有登录、申请成为房东、发布房屋、管理个人发布的房屋信息、管理个人订单、查看个人房屋评价信息、查看个人数据统计等等功能。
管理员有登录、查看数据统计信息、管理所有用户信息、审核房东申请信息、审核房屋信息、管理所有房屋信息、管理所有订单信息、管理所有评价信息、管理所有设施信息、管理所有意见反馈信息等等功能。
应用技术:SpringBoot3 + Spring Cloud Alibaba(Nacos + Sentinel + OpenFeign + Gateway)+ Vue3 + MySQL + MyBatis-Plus + ElementUI-Plus + Pinia + Vite
运行环境:IntelliJ IDEA + MySQL + Nacos + JDK17 + Maven + Node + Visual Studio Code
三、运行截图






















四、主要代码
1.预订下单代码
java
/**
* 预订下单:校验房屋存在且审核通过、日期合法、人数不超上限,生成订单快照落库(初始状态为待支付)
*
* @param order 订单信息(取houseId、checkIn、checkOut、guests)
* @param userId 当前登录用户id(网关透传)
* @return 响应结果
*/
@Override
public Result<String> addOrder(Order order, Integer userId) {
// 参数基础校验
if (order == null || order.getHouseId() == null
|| order.getCheckIn() == null || order.getCheckOut() == null
|| order.getGuests() == null) {
return Result.error("房屋id、入住退房日期和入住人数不能为空");
}
// 校验入住日期不能早于今天
if (order.getCheckIn().isBefore(LocalDate.now())) {
return Result.error("入住日期不能早于今天");
}
// 校验退房日期必须晚于入住日期
if (!order.getCheckOut().isAfter(order.getCheckIn())) {
return Result.error("退房日期必须晚于入住日期");
}
// 校验入住人姓名(数据库guest_name为varchar(32))
if (StrUtil.isBlank(order.getGuestName())) {
return Result.error("入住人姓名不能为空");
}
if (order.getGuestName().trim().length() > 32) {
return Result.error("入住人姓名长度不能超过32个字符");
}
// 校验联系电话格式
if (StrUtil.isBlank(order.getPhone()) || !order.getPhone().matches("^1[3-9]\\d{9}$")) {
return Result.error("联系电话格式不正确");
}
// 校验房屋是否存在且审核通过(调用房屋服务查询)
House house = getHouseById(order.getHouseId());
if (house == null) {
return Result.error("房屋不存在");
}
// 房东不能预订自己发布的房源
if (Objects.equals(house.getUserId(), userId)) {
return Result.error("不能预订自己发布的房源");
}
if (!Objects.equals(house.getState(), HOUSE_STATE_APPROVED)) {
return Result.error("该房屋未通过审核,暂不能预订");
}
// 校验入住人数不能超过房屋最大容纳人数
if (order.getGuests() > house.getMaxNum()) {
return Result.error("入住人数不能超过房屋最大容纳人数" + house.getMaxNum() + "人");
}
// 计算入住晚数与订单总金额(订单总额=房价×晚数,押金不计入订单总额)
long nights = ChronoUnit.DAYS.between(order.getCheckIn(), order.getCheckOut());
BigDecimal totalAmount = house.getPrice().multiply(BigDecimal.valueOf(nights));
// 查询房屋封面图(取排序最靠前的一张,无图时使用默认图;调用图片服务查询)
List<Picture> coverPictures = listPicturesByRefs(List.of(order.getHouseId()), PICTURE_TYPE_HOUSE);
Picture cover = coverPictures.isEmpty() ? null : coverPictures.get(0);
// 生成订单快照并落库
Order save = new Order();
save.setOrderNo(DateUtil.format(new Date(), "yyyyMMddHHmmssSSS") + RandomUtil.randomNumbers(4));
save.setUserId(userId);
save.setHouseId(house.getId());
save.setHouseName(house.getName());
save.setHouseImage(cover != null ? cover.getPhoto() : DEFAULT_HOUSE_IMAGE);
save.setCheckIn(order.getCheckIn());
save.setCheckOut(order.getCheckOut());
save.setNights((int) nights);
save.setGuests(order.getGuests());
save.setGuestName(order.getGuestName().trim());
save.setPhone(order.getPhone());
save.setPrice(house.getPrice());
save.setDeposit(house.getPledgePrice());
save.setTotalAmount(totalAmount);
save.setStatus(ORDER_STATUS_PENDING_PAYMENT);
save.setRemark(order.getRemark());
save.setCreateTime(LocalDateTime.now());
orderMapper.insert(save);
return Result.success("预订成功", null);
}
2.保存房屋代码
java
/**
* 保存房屋(新增或修改,同时保存关联设施和图片)
* 权限规则:管理员(3)可管理所有房屋并可指定房东;房东(2)仅能管理自己名下的房屋
*
* @param house 房屋信息(含关联设施id集合、图片路径集合)
* @param loginUserId 当前登录用户id(网关透传)
* @param loginRoleId 当前登录用户角色(网关透传)
* @return 响应结果
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Result<String> saveHouse(House house, Integer loginUserId, Integer loginRoleId) {
// 参数基础校验
if (StrUtil.isBlank(house.getName()) || StrUtil.isBlank(house.getCity())
|| StrUtil.isBlank(house.getLocation())) {
return Result.error("房屋名称、城市和详细位置不能为空");
}
if (house.getPrice() == null || house.getPrice().compareTo(BigDecimal.ZERO) < 0) {
return Result.error("请填写正确的入住价格");
}
if (house.getPledgePrice() != null && house.getPledgePrice().compareTo(BigDecimal.ZERO) < 0) {
return Result.error("请填写正确的押金金额");
}
if (house.getHouseType() == null || house.getHouseType() < 1 || house.getHouseType() > 6) {
return Result.error("请选择正确的房屋类型");
}
if (ROLE_ADMIN.equals(loginRoleId)) {
// 管理员新增房屋时必须指定房东
if (house.getId() == null && house.getUserId() == null) {
return Result.error("请选择房屋所属房东");
}
} else if (ROLE_LANDLORD.equals(loginRoleId)) {
if (house.getId() == null) {
// 房东新增房屋:房屋归属于自己
house.setUserId(loginUserId);
}
} else {
return Result.error("没有保存房屋的权限");
}
// 补全默认值
if (house.getMaxNum() == null || house.getMaxNum() < 1) {
house.setMaxNum(1);
}
if (house.getPledgePrice() == null) {
house.setPledgePrice(BigDecimal.ZERO);
}
if (house.getId() == null) {
// 新增房屋(id由数据库自增生成,评分默认0.0由评价产生,状态默认待审核)
house.setRate(BigDecimal.ZERO);
house.setState(HOUSE_STATE_PENDING);
house.setRemark("");
houseMapper.insert(house);
// 保存关联设施和图片
saveHouseRelation(house);
return Result.success("添加房屋成功,请等待管理员审核", null);
}
// 修改房屋
House dbHouse = houseMapper.selectById(house.getId());
if (dbHouse == null) {
return Result.error("房屋不存在");
}
// 房东只能修改自己名下的房屋,且房屋归属保持不变
if (!ROLE_ADMIN.equals(loginRoleId)) {
if (!dbHouse.getUserId().equals(loginUserId)) {
return Result.error("只能修改自己名下的房屋");
}
house.setUserId(dbHouse.getUserId());
}
// 评分由订单评价产生,不随表单修改;修改后需重新审核,清空原审核备注
house.setRate(dbHouse.getRate());
house.setState(HOUSE_STATE_PENDING);
house.setRemark("");
houseMapper.updateById(house);
// 重新保存关联设施和图片(先删后插)
saveHouseRelation(house);
return Result.success("修改房屋成功,请等待管理员重新审核", null);
}
3.用户登录代码
java
/**
* 用户登录:校验手机号、密码、角色,签发JWT token
* 管理后台需传角色(2房东/3管理员);租客端可不传角色,默认以普通用户(1)身份登录
*
* @param loginDTO 登录参数(手机号/密码/角色)
* @return 响应结果,data 为 token 字符串
*/
@Override
public Result<String> login(LoginDTO loginDTO) {
// 参数基础校验
if (loginDTO == null || StrUtil.isBlank(loginDTO.getPhone())
|| StrUtil.isBlank(loginDTO.getPassword())) {
return Result.error("请填写完整的登录信息");
}
// 校验登录角色合法性(未传角色时跳过,按账号实际角色登录,支持房东/管理员登录前台web)
if (loginDTO.getRoleId() != null && !ROLE_LANDLORD.equals(loginDTO.getRoleId())
&& !ROLE_ADMIN.equals(loginDTO.getRoleId()) && !ROLE_NORMAL.equals(loginDTO.getRoleId())) {
return Result.error("请选择正确的登录角色");
}
// 根据手机号查询用户
User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
.eq(User::getPhone, loginDTO.getPhone()));
if (user == null || !loginDTO.getPassword().equals(user.getPassword())) {
return Result.error("手机号或密码错误");
}
// 显式传入角色时校验与账号实际角色是否一致(后台管理端按角色入口登录)
if (loginDTO.getRoleId() != null && !loginDTO.getRoleId().equals(user.getRoleId())) {
return Result.error("当前账号无此角色登录权限");
}
// 签发JWT token(OAuth2 JWT,含 iss/jti/roles 标准声明,按账号实际角色签发)
String token = jwtTokenService.createToken(String.valueOf(user.getId()), user.getRoleId());
return Result.success("登录成功", token);
}