代码2:
backend\src\main\java\com\rental\entity\ViewingFeedback.java
package com.rental.entity;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 看房反馈实体类
*/
@Data
public class ViewingFeedback implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 反馈ID
*/
private Long id;
/**
* 预约ID
*/
private Long appointmentId;
/**
* 用户ID
*/
private Long userId;
/**
* 房源ID
*/
private Long houseId;
/**
* 反馈内容
*/
private String feedbackContent;
/**
* 满意度:1-5星
*/
private Integer satisfactionLevel;
/**
* 是否公开:0-私密,1-公开
*/
private Integer isPublic;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
}
backend\src\main\java\com\rental\exception\BusinessException.java
package com.rental.exception;
import lombok.Getter;
/**
* 业务异常
*/
@Getter
public class BusinessException extends RuntimeException {
/**
* 错误码
*/
private final Integer code;
/**
* 错误消息
*/
private final String message;
public BusinessException(String message) {
this(500, message);
}
public BusinessException(Integer code, String message) {
super(message);
this.code = code;
this.message = message;
}
}
backend\src\main\java\com\rental\exception\GlobalExceptionHandler.java
package com.rental.exception;
import com.rental.dto.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.Set;
/**
* 全局异常处理器
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 处理自定义异常
*/
@ExceptionHandler(BusinessException.class)
public Result<Void> handleBusinessException(BusinessException e) {
log.error("业务异常:{}", e.getMessage(), e);
return Result.error(e.getCode(), e.getMessage());
}
/**
* 处理未授权异常
*/
@ExceptionHandler(UnauthorizedException.class)
public Result<Void> handleUnauthorizedException(UnauthorizedException e) {
log.error("未授权异常:{}", e.getMessage(), e);
return Result.error(401, e.getMessage());
}
/**
* 处理参数校验异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<Void> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
log.error("参数校验异常:{}", e.getMessage(), e);
BindingResult bindingResult = e.getBindingResult();
StringBuilder sb = new StringBuilder("参数校验失败:");
for (FieldError fieldError : bindingResult.getFieldErrors()) {
sb.append(fieldError.getField()).append(":").append(fieldError.getDefaultMessage()).append(", ");
}
String msg = sb.toString();
if (msg.endsWith(", ")) {
msg = msg.substring(0, msg.length() - 2);
}
return Result.error(400, msg);
}
/**
* 处理参数绑定异常
*/
@ExceptionHandler(BindException.class)
public Result<Void> handleBindException(BindException e) {
log.error("参数绑定异常:{}", e.getMessage(), e);
BindingResult bindingResult = e.getBindingResult();
StringBuilder sb = new StringBuilder("参数绑定失败:");
for (FieldError fieldError : bindingResult.getFieldErrors()) {
sb.append(fieldError.getField()).append(":").append(fieldError.getDefaultMessage()).append(", ");
}
String msg = sb.toString();
if (msg.endsWith(", ")) {
msg = msg.substring(0, msg.length() - 2);
}
return Result.error(400, msg);
}
/**
* 处理约束违反异常
*/
@ExceptionHandler(ConstraintViolationException.class)
public Result<Void> handleConstraintViolationException(ConstraintViolationException e) {
log.error("约束违反异常:{}", e.getMessage(), e);
Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
StringBuilder sb = new StringBuilder("参数校验失败:");
for (ConstraintViolation<?> violation : violations) {
sb.append(violation.getPropertyPath()).append(":").append(violation.getMessage()).append(", ");
}
String msg = sb.toString();
if (msg.endsWith(", ")) {
msg = msg.substring(0, msg.length() - 2);
}
return Result.error(400, msg);
}
/**
* 处理其他异常
*/
@ExceptionHandler(Exception.class)
public Result<Void> handleException(Exception e) {
log.error("系统异常:{}", e.getMessage(), e);
return Result.error(500, "系统繁忙,请稍后再试");
}
}
Custom Exceptions
backend\src\main\java\com\rental\exception\UnauthorizedException.java
package com.rental.exception;
/**
* 未授权异常
*/
public class UnauthorizedException extends RuntimeException {
public UnauthorizedException() {
super("未授权,请先登录");
}
public UnauthorizedException(String message) {
super(message);
}
}
MyBatis Mapper XML Files
backend\src\main\java\com\rental\interceptor\AuthenticationInterceptor.java
package com.rental.interceptor;
import com.rental.dto.Result;
import com.rental.entity.User;
import com.rental.exception.UnauthorizedException;
import com.rental.service.UserService;
import com.rental.util.JwtUtil;
import com.rental.util.JsonUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 认证拦截器
*/
public class AuthenticationInterceptor implements HandlerInterceptor {
@Autowired
private UserService userService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 从请求头中获取token
String token = request.getHeader("Authorization");
if (StringUtils.hasText(token) && token.startsWith("Bearer ")) {
token = token.substring(7);
}
// 如果没有token,返回未授权错误
if (!StringUtils.hasText(token)) {
handleUnauthorized(response);
return false;
}
try {
// 验证token
Long userId = JwtUtil.getUserId(token);
if (userId == null) {
handleUnauthorized(response);
return false;
}
// 查询用户信息
User user = userService.getUserById(userId);
if (user == null || user.getStatus() != 1) {
handleUnauthorized(response);
return false;
}
// 将用户信息存入请求属性中
request.setAttribute("userId", userId);
request.setAttribute("user", user);
return true;
} catch (Exception e) {
handleUnauthorized(response);
return false;
}
}
/**
* 处理未授权请求
*/
private void handleUnauthorized(HttpServletResponse response) throws IOException {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write(JsonUtil.toJson(Result.error(401, "未授权,请先登录")));
}
}
JWT Utility
backend\src\main\java\com\rental\interceptor\AuthInterceptor.java
package com.rental.interceptor;
import com.rental.util.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 认证拦截器
*/
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Autowired
private JwtUtil jwtUtil;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 放行OPTIONS请求
if ("OPTIONS".equals(request.getMethod())) {
return true;
}
// 获取token
String token = request.getHeader("Authorization");
if (token != null && token.startsWith("Bearer ")) {
token = token.substring(7);
// 验证token
if (jwtUtil.validateToken(token)) {
// 将用户ID和用户名存入请求属性
request.setAttribute("userId", jwtUtil.getUserIdFromToken(token));
request.setAttribute("username", jwtUtil.getUsernameFromToken(token));
return true;
}
}
// 认证失败
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"code\":401,\"message\":\"未授权\"}");
return false;
}
}
backend\src\main\java\com\rental\service\AppointmentService.java
package com.rental.service;
import com.rental.dto.AppointmentDTO;
import com.rental.entity.Appointment;
import com.rental.dto.Result;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* 预约服务接口
*/
public interface AppointmentService {
/**
* 创建预约
*/
Result<Appointment> createAppointment(AppointmentDTO appointmentDTO, Long userId);
/**
* 确认预约
*/
Result<Void> confirmAppointment(Long appointmentId, Long landlordId);
/**
* 取消预约
*/
Result<Void> cancelAppointment(Long appointmentId, Long userId);
/**
* 完成预约
*/
Result<Void> completeAppointment(Long appointmentId, Long landlordId);
/**
* 获取预约详情
*/
Result<Appointment> getAppointmentById(Long appointmentId);
/**
* 获取用户的预约列表
*/
Result<List<Appointment>> getUserAppointments(Long userId);
/**
* 获取房东的预约列表
*/
Result<List<Appointment>> getLandlordAppointments(Long landlordId);
/**
* 获取房源的预约列表
*/
Result<List<Appointment>> getHouseAppointments(Long houseId);
/**
* 获取指定日期范围内的预约
*/
Result<List<Appointment>> getAppointmentsByDateRange(Date startDate, Date endDate, Long userId);
/**
* 获取预约统计数据
*/
Result<Map<String, Object>> getAppointmentStatistics(Long userId);
}
backend\src\main\java\com\rental\service\FavoriteService.java
package com.rental.service;
import com.rental.dto.HouseDTO;
import com.rental.entity.Favorite;
import java.util.List;
/**
* 收藏服务接口
*/
public interface FavoriteService {
/**
* 添加收藏
*
* @param userId 用户ID
* @param houseId 房源ID
* @return 是否成功
*/
boolean addFavorite(Long userId, Long houseId);
/**
* 取消收藏
*
* @param userId 用户ID
* @param houseId 房源ID
* @return 是否成功
*/
boolean cancelFavorite(Long userId, Long houseId);
/**
* 查询用户是否收藏房源
*
* @param userId 用户ID
* @param houseId 房源ID
* @return 是否收藏
*/
boolean isFavorite(Long userId, Long houseId);
/**
* 查询用户收藏列表
*
* @param userId 用户ID
* @return 收藏列表
*/
List<HouseDTO> getFavoriteList(Long userId);
}
backend\src\main\java\com\rental\service\HouseReviewService.java
package com.rental.service;
import com.rental.dto.ReviewDTO;
import com.rental.entity.HouseReview;
import com.rental.dto.Result;
import java.util.List;
import java.util.Map;
/**
* 房源评价服务接口
*/
public interface HouseReviewService {
/**
* 提交房源评价
*/
Result<HouseReview> submitReview(ReviewDTO reviewDTO, Long userId);
/**
* 更新房源评价
*/
Result<Void> updateReview(Long reviewId, ReviewDTO reviewDTO, Long userId);
/**
* 删除房源评价
*/
Result<Void> deleteReview(Long reviewId, Long userId);
/**
* 获取评价详情
*/
Result<HouseReview> getReviewById(Long reviewId);
/**
* 获取房源的评价列表
*/
Result<List<HouseReview>> getHouseReviews(Long houseId);
/**
* 获取用户的评价列表
*/
Result<List<HouseReview>> getUserReviews(Long userId);
/**
* 获取房源评价统计数据
*/
Result<Map<String, Object>> getHouseReviewStatistics(Long houseId);
/**
* 检查用户是否已评价房源
*/
Result<Boolean> checkUserReviewed(Long houseId, Long userId);
}
backend\src\main\java\com\rental\service\HouseService.java
package com.rental.service;
import com.rental.dto.HouseDTO;
import com.rental.dto.HouseSearchDTO;
import com.rental.dto.PageResult;
import com.rental.entity.House;
import java.util.List;
/**
* 房源服务接口
*/
public interface HouseService {
/**
* 根据ID查询房源
*
* @param id 房源ID
* @return 房源信息
*/
HouseDTO getHouseById(Long id);
/**
* 根据房东ID查询房源列表
*
* @param landlordId 房东ID
* @return 房源列表
*/
List<HouseDTO> getHouseListByLandlordId(Long landlordId);
/**
* 查询房源列表
*
* @param house 查询条件
* @return 房源列表
*/
List<HouseDTO> getHouseList(House house);
/**
* 搜索房源
*
* @param searchDTO 搜索条件
* @return 分页结果
*/
PageResult<HouseDTO> searchHouse(HouseSearchDTO searchDTO);
/**
* 新增房源
*
* @param houseDTO 房源信息
* @return 房源ID
*/
Long addHouse(HouseDTO houseDTO);
/**
* 更新房源
*
* @param houseDTO 房源信息
* @return 是否成功
*/
boolean updateHouse(HouseDTO houseDTO);
/**
* 删除房源
*
* @param id 房源ID
* @return 是否成功
*/
boolean deleteHouse(Long id);
/**
* 批量删除房源
*
* @param ids 房源ID数组
* @return 是否成功
*/
boolean deleteHouseBatch(Long[] ids);
/**
* 更新房源状态
*
* @param id 房源ID
* @param status 状态
* @return 是否成功
*/
boolean updateHouseStatus(Long id, Integer status);
}
backend\src\main\java\com\rental\service\ReviewReplyService.java
package com.rental.service;
import com.rental.dto.ReplyDTO;
import com.rental.entity.ReviewReply;
import com.rental.dto.Result;
import java.util.List;
/**
* 评价回复服务接口
*/
public interface ReviewReplyService {
/**
* 提交评价回复
*/
Result<ReviewReply> submitReply(ReplyDTO replyDTO, Long userId);
/**
* 更新评价回复
*/
Result<Void> updateReply(Long replyId, ReplyDTO replyDTO, Long userId);
/**
* 删除评价回复
*/
Result<Void> deleteReply(Long replyId, Long userId);
/**
* 获取回复详情
*/
Result<ReviewReply> getReplyById(Long replyId);
/**
* 获取评价的回复列表
*/
Result<List<ReviewReply>> getReviewReplies(Long reviewId);
}
2.6 Service Implementations
backend\src\main\java\com\rental\service\UserService.java
package com.rental.service;
import com.rental.dto.LoginDTO;
import com.rental.dto.RegisterDTO;
import com.rental.dto.UserDTO;
import com.rental.entity.User;
import java.util.List;
/**
* 用户服务接口
*/
public interface UserService {
/**
* 用户登录
*
* @param loginDTO 登录信息
* @return 用户信息
*/
UserDTO login(LoginDTO loginDTO);
/**
* 用户注册
*
* @param registerDTO 注册信息
* @return 用户信息
*/
UserDTO register(RegisterDTO registerDTO);
/**
* 根据ID查询用户
*
* @param id 用户ID
* @return 用户信息
*/
UserDTO getUserById(Long id);
/**
* 根据用户名查询用户
*
* @param username 用户名
* @return 用户信息
*/
UserDTO getUserByUsername(String username);
/**
* 查询用户列表
*
* @param user 查询条件
* @return 用户列表
*/
List<UserDTO> getUserList(User user);
/**
* 更新用户信息
*
* @param user 用户信息
* @return 是否成功
*/
boolean updateUser(User user);
/**
* 更新用户状态
*
* @param id 用户ID
* @param status 状态
* @return 是否成功
*/
boolean updateUserStatus(Long id, Integer status);
/**
* 删除用户
*
* @param id 用户ID
* @return 是否成功
*/
boolean deleteUser(Long id);
/**
* 更新用户密码
*
* @param id 用户ID
* @param oldPassword 旧密码
* @param newPassword 新密码
* @return 是否成功
*/
boolean updatePassword(Long id, String oldPassword, String newPassword);
}
backend\src\main\java\com\rental\service\VerificationService.java
package com.rental.service;
import com.rental.dto.LandlordVerificationDTO;
import com.rental.dto.StudentVerificationDTO;
import com.rental.entity.LandlordVerification;
import com.rental.entity.StudentVerification;
import java.util.List;
/**
* 认证服务接口
*/
public interface VerificationService {
/**
* 提交学生认证
*
* @param verificationDTO 学生认证信息
* @return 是否成功
*/
boolean submitStudentVerification(StudentVerificationDTO verificationDTO);
/**
* 提交房东认证
*
* @param verificationDTO 房东认证信息
* @return 是否成功
*/
boolean submitLandlordVerification(LandlordVerificationDTO verificationDTO);
/**
* 根据用户ID查询学生认证
*
* @param userId 用户ID
* @return 学生认证信息
*/
StudentVerification getStudentVerificationByUserId(Long userId);
/**
* 根据用户ID查询房东认证
*
* @param userId 用户ID
* @return 房东认证信息
*/
LandlordVerification getLandlordVerificationByUserId(Long userId);
/**
* 查询学生认证列表
*
* @param verification 查询条件
* @return 学生认证列表
*/
List<StudentVerification> getStudentVerificationList(StudentVerification verification);
/**
* 查询房东认证列表
*
* @param verification 查询条件
* @return 房东认证列表
*/
List<LandlordVerification> getLandlordVerificationList(LandlordVerification verification);
/**
* 审核学生认证
*
* @param id 认证ID
* @param status 状态:1-通过,3-拒绝
* @param remark 备注
* @return 是否成功
*/
boolean auditStudentVerification(Long id, Integer status, String remark);
/**
* 审核房东认证
*
* @param id 认证ID
* @param status 状态:1-通过,3-拒绝
* @param remark 备注
* @return 是否成功
*/
boolean auditLandlordVerification(Long id, Integer status, String remark);
}
backend\src\main\java\com\rental\service\ViewingFeedbackService.java
package com.rental.service;
import com.rental.dto.FeedbackDTO;
import com.rental.entity.ViewingFeedback;
import com.rental.dto.Result;
import java.util.List;
/**
* 看房反馈服务接口
*/
public interface ViewingFeedbackService {
/**
* 提交看房反馈
*/
Result<ViewingFeedback> submitFeedback(FeedbackDTO feedbackDTO, Long userId);
/**
* 更新看房反馈
*/
Result<Void> updateFeedback(Long feedbackId, FeedbackDTO feedbackDTO, Long userId);
/**
* 删除看房反馈
*/
Result<Void> deleteFeedback(Long feedbackId, Long userId);
/**
* 设置反馈是否公开
*/
Result<Void> setFeedbackPublic(Long feedbackId, Integer isPublic, Long userId);
/**
* 获取反馈详情
*/
Result<ViewingFeedback> getFeedbackById(Long feedbackId);
/**
* 获取用户的反馈列表
*/
Result<List<ViewingFeedback>> getUserFeedbacks(Long userId);
/**
* 获取房源的公开反馈列表
*/
Result<List<ViewingFeedback>> getHousePublicFeedbacks(Long houseId);
/**
* 获取预约的反馈
*/
Result<ViewingFeedback> getAppointmentFeedback(Long appointmentId);
}
backend\src\main\java\com\rental\service\impl\AppointmentServiceImpl.java
package com.rental.service.impl;
import com.rental.dao.AppointmentDao;
import com.rental.dao.HouseDao;
import com.rental.dto.AppointmentDTO;
import com.rental.entity.Appointment;
import com.rental.entity.House;
import com.rental.dto.Result;
import com.rental.exception.BusinessException;
import com.rental.service.AppointmentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
/**
* 预约服务实现类
*/
@Service
public class AppointmentServiceImpl implements AppointmentService {
@Autowired
private AppointmentDao appointmentDao;
@Autowired
private HouseDao houseDao;
@Override
@Transactional
public Result<Appointment> createAppointment(AppointmentDTO appointmentDTO, Long userId) {
// 检查房源是否存在
House house = houseDao.selectById(appointmentDTO.getHouseId());
if (house == null) {
return Result.error(404, "房源不存在");
}
// 检查房源状态是否正常
if (house.getStatus() != 1) {
return Result.error(400, "房源状态异常,无法预约");
}
// 检查时间冲突
int conflict = appointmentDao.checkTimeConflict(appointmentDTO.getHouseId(), appointmentDTO.getAppointmentTime());
if (conflict > 0) {
return Result.error(400, "该时间段已有预约,请选择其他时间");
}
// 创建预约
Appointment appointment = new Appointment();
appointment.setHouseId(appointmentDTO.getHouseId());
appointment.setUserId(userId);
appointment.setLandlordId(house.getLandlordId());
appointment.setAppointmentTime(appointmentDTO.getAppointmentTime());
appointment.setContactName(appointmentDTO.getContactName());
appointment.setContactPhone(appointmentDTO.getContactPhone());
appointment.setAppointmentNotes(appointmentDTO.getAppointmentNotes());
appointment.setStatus(0); // 待确认
appointmentDao.insert(appointment);
return Result.success(appointment);
}
@Override
@Transactional
public Result<Void> confirmAppointment(Long appointmentId, Long landlordId) {
Appointment appointment = appointmentDao.selectById(appointmentId);
if (appointment == null) {
return Result.error(404, "预约不存在");
}
// 检查是否是房东本人操作
if (!appointment.getLandlordId().equals(landlordId)) {
return Result.error(403, "无权操作此预约");
}
// 检查预约状态
if (appointment.getStatus() != 0) {
return Result.error(400, "预约状态异常,无法确认");
}
// 更新预约状态为已确认
appointmentDao.updateStatus(appointmentId, 1);
return Result.success();
}
@Override
@Transactional
public Result<Void> cancelAppointment(Long appointmentId, Long userId) {
Appointment appointment = appointmentDao.selectById(appointmentId);
if (appointment == null) {
return Result.error(404, "预约不存在");
}
// 检查是否是用户本人或房东操作
if (!appointment.getUserId().equals(userId) && !appointment.getLandlordId().equals(userId)) {
return Result.error(403, "无权操作此预约");
}
// 检查预约状态
if (appointment.getStatus() > 1) {
return Result.error(400, "预约状态异常,无法取消");
}
// 更新预约状态为已取消
appointmentDao.updateStatus(appointmentId, 2);
return Result.success();
}
@Override
@Transactional
public Result<Void> completeAppointment(Long appointmentId, Long landlordId) {
Appointment appointment = appointmentDao.selectById(appointmentId);
if (appointment == null) {
return Result.error(404, "预约不存在");
}
// 检查是否是房东本人操作
if (!appointment.getLandlordId().equals(landlordId)) {
return Result.error(403, "无权操作此预约");
}
// 检查预约状态
if (appointment.getStatus() != 1) {
return Result.error(400, "预约状态异常,无法完成");
}
// 更新预约状态为已完成
appointmentDao.updateStatus(appointmentId, 3);
return Result.success();
}
@Override
public Result<Appointment> getAppointmentById(Long appointmentId) {
Appointment appointment = appointmentDao.selectById(appointmentId);
if (appointment == null) {
return Result.error(404, "预约不存在");
}
return Result.success(appointment);
}
@Override
public Result<List<Appointment>> getUserAppointments(Long userId) {
List<Appointment> appointments = appointmentDao.selectByUserId(userId);
return Result.success(appointments);
}
@Override
public Result<List<Appointment>> getLandlordAppointments(Long landlordId) {
List<Appointment> appointments = appointmentDao.selectByLandlordId(landlordId);
return Result.success(appointments);
}
@Override
public Result<List<Appointment>> getHouseAppointments(Long houseId) {
List<Appointment> appointments = appointmentDao.selectByHouseId(houseId);
return Result.success(appointments);
}
@Override
public Result<List<Appointment>> getAppointmentsByDateRange(Date startDate, Date endDate, Long userId) {
if (startDate == null || endDate == null) {
return Result.error(400, "日期范围不能为空");
}
if (startDate.after(endDate)) {
return Result.error(400, "开始日期不能晚于结束日期");
}
List<Appointment> appointments = appointmentDao.selectByDateRange(startDate, endDate);
// 过滤出与用户相关的预约
List<Appointment> filteredAppointments = new ArrayList<>();
for (Appointment appointment : appointments) {
if (appointment.getUserId().equals(userId) || appointment.getLandlordId().equals(userId)) {
filteredAppointments.add(appointment);
}
}
return Result.success(filteredAppointments);
}
@Override
public Result<Map<String, Object>> getAppointmentStatistics(Long userId) {
Map<String, Object> statistics = new HashMap<>();
Appointment query = new Appointment();
query.setUserId(userId);
List<Appointment> userAppointments = appointmentDao.selectList(query);
// 统计各状态预约数量
int pendingCount = 0;
int confirmedCount = 0;
int canceledCount = 0;
int completedCount = 0;
for (Appointment appointment : userAppointments) {
switch (appointment.getStatus()) {
case 0:
pendingCount++;
break;
case 1:
confirmedCount++;
break;
case 2:
canceledCount++;
break;
case 3:
completedCount++;
break;
}
}
statistics.put("total", userAppointments.size());
statistics.put("pending", pendingCount);
statistics.put("confirmed", confirmedCount);
statistics.put("canceled", canceledCount);
statistics.put("completed", completedCount);
return Result.success(statistics);
}
}
backend\src\main\java\com\rental\service\impl\FavoriteServiceImpl.java
package com.rental.service.impl;
import com.rental.dao.FavoriteDao;
import com.rental.dao.HouseDao;
import com.rental.dto.HouseDTO;
import com.rental.entity.Favorite;
import com.rental.entity.House;
import com.rental.service.FavoriteService;
import com.rental.service.HouseService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
/**
* 收藏服务实现类
*/
@Service
public class FavoriteServiceImpl implements FavoriteService {
@Autowired
private FavoriteDao favoriteDao;
@Autowired
private HouseDao houseDao;
@Autowired
private HouseService houseService;
@Override
@Transactional
public boolean addFavorite(Long userId, Long houseId) {
// 查询是否已收藏
Favorite favorite = favoriteDao.selectByUserIdAndHouseId(userId, houseId);
if (favorite != null) {
return true;
}
// 查询房源是否存在
House house = houseDao.selectById(houseId);
if (house == null) {
throw new RuntimeException("房源不存在");
}
// 添加收藏
favorite = new Favorite();
favorite.setUserId(userId);
favorite.setHouseId(houseId);
favorite.setCreateTime(new Date());
return favoriteDao.insert(favorite) > 0;
}
@Override
@Transactional
public boolean cancelFavorite(Long userId, Long houseId) {
return favoriteDao.deleteByUserIdAndHouseId(userId, houseId) > 0;
}
@Override
public boolean isFavorite(Long userId, Long houseId) {
Favorite favorite = favoriteDao.selectByUserIdAndHouseId(userId, houseId);
return favorite != null;
}
@Override
public List<HouseDTO> getFavoriteList(Long userId) {
// 查询用户收藏列表
List<Favorite> favorites = favoriteDao.selectByUserId(userId);
if (favorites == null || favorites.isEmpty()) {
return new ArrayList<>();
}
// 查询房源信息
List<Long> houseIds = favorites.stream().map(Favorite::getHouseId).collect(Collectors.toList());
List<HouseDTO> houseDTOs = new ArrayList<>();
for (Long houseId : houseIds) {
HouseDTO houseDTO = houseService.getHouseById(houseId);
if (houseDTO != null) {
houseDTOs.add(houseDTO);
}
}
return houseDTOs;
}
}
backend\src\main\java\com\rental\service\impl\HouseReviewServiceImpl.java
package com.rental.service.impl;
import com.rental.dao.HouseDao;
import com.rental.dao.HouseReviewDao;
import com.rental.dto.ReviewDTO;
import com.rental.entity.House;
import com.rental.entity.HouseReview;
import com.rental.dto.Result;
import com.rental.service.HouseReviewService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 房源评价服务实现类
*/
@Service
public class HouseReviewServiceImpl implements HouseReviewService {
@Autowired
private HouseReviewDao houseReviewDao;
@Autowired
private HouseDao houseDao;
@Override
@Transactional
public Result<HouseReview> submitReview(ReviewDTO reviewDTO, Long userId) {
// 检查房源是否存在
House house = houseDao.selectById(reviewDTO.getHouseId());
if (house == null) {
return Result.error(404, "房源不存在");
}
// 检查用户是否已评价过该房源
HouseReview existingReview = houseReviewDao.selectByHouseIdAndUserId(reviewDTO.getHouseId(), userId);
if (existingReview != null) {
return Result.error(400, "您已评价过该房源,不能重复评价");
}
// 创建评价
HouseReview review = new HouseReview();
review.setHouseId(reviewDTO.getHouseId());
review.setUserId(userId);
review.setContent(reviewDTO.getContent());
review.setRating(reviewDTO.getRating());
review.setLocationRating(reviewDTO.getLocationRating());
review.setCleanlinessRating(reviewDTO.getCleanlinessRating());
review.setValueRating(reviewDTO.getValueRating());
review.setLandlordRating(reviewDTO.getLandlordRating());
// 处理评价图片
if (reviewDTO.getImages() != null && !reviewDTO.getImages().isEmpty()) {
String images = String.join(",", reviewDTO.getImages());
review.setImages(images);
}
houseReviewDao.insert(review);
// 更新房源评分
updateHouseRating(reviewDTO.getHouseId());
return Result.success(review);
}
@Override
@Transactional
public Result<Void> updateReview(Long reviewId, ReviewDTO reviewDTO, Long userId) {
// 检查评价是否存在
HouseReview review = houseReviewDao.selectById(reviewId);
if (review == null) {
return Result.error(404, "评价不存在");
}
// 检查是否是评价用户本人
if (!review.getUserId().equals(userId)) {
return Result.error(403, "无权修改此评价");
}
// 更新评价
review.setContent(reviewDTO.getContent());
review.setRating(reviewDTO.getRating());
review.setLocationRating(reviewDTO.getLocationRating());
review.setCleanlinessRating(reviewDTO.getCleanlinessRating());
review.setValueRating(reviewDTO.getValueRating());
review.setLandlordRating(reviewDTO.getLandlordRating());
// 处理评价图片
if (reviewDTO.getImages() != null && !reviewDTO.getImages().isEmpty()) {
String images = String.join(",", reviewDTO.getImages());
review.setImages(images);
}
houseReviewDao.update(review);
// 更新房源评分
updateHouseRating(review.getHouseId());
return Result.success();
}
@Override
@Transactional
public Result<Void> deleteReview(Long reviewId, Long userId) {
// 检查评价是否存在
HouseReview review = houseReviewDao.selectById(reviewId);
if (review == null) {
return Result.error(404, "评价不存在");
}
// 检查是否是评价用户本人
if (!review.getUserId().equals(userId)) {
return Result.error(403, "无权删除此评价");
}
houseReviewDao.deleteById(reviewId);
// 更新房源评分
updateHouseRating(review.getHouseId());
return Result.success();
}
@Override
public Result<HouseReview> getReviewById(Long reviewId) {
HouseReview review = houseReviewDao.selectById(reviewId);
if (review == null) {
return Result.error(404, "评价不存在");
}
return Result.success(review);
}
@Override
public Result<List<HouseReview>> getHouseReviews(Long houseId) {
List<HouseReview> reviews = houseReviewDao.selectByHouseId(houseId);
return Result.success(reviews);
}
@Override
public Result<List<HouseReview>> getUserReviews(Long userId) {
List<HouseReview> reviews = houseReviewDao.selectByUserId(userId);
return Result.success(reviews);
}
@Override
public Result<Map<String, Object>> getHouseReviewStatistics(Long houseId) {
List<HouseReview> reviews = houseReviewDao.selectByHouseId(houseId);
Map<String, Object> statistics = new HashMap<>();
if (reviews.isEmpty()) {
statistics.put("totalCount", 0);
statistics.put("averageRating", 0.0);
statistics.put("averageLocationRating", 0.0);
statistics.put("averageCleanlinessRating", 0.0);
statistics.put("averageValueRating", 0.0);
statistics.put("averageLandlordRating", 0.0);
statistics.put("ratingDistribution", new int[]{0, 0, 0, 0, 0});
return Result.success(statistics);
}
// 计算平均评分
double averageRating = reviews.stream().mapToInt(HouseReview::getRating).average().orElse(0.0);
double averageLocationRating = reviews.stream().mapToInt(HouseReview::getLocationRating).average().orElse(0.0);
double averageCleanlinessRating = reviews.stream().mapToInt(HouseReview::getCleanlinessRating).average().orElse(0.0);
double averageValueRating = reviews.stream().mapToInt(HouseReview::getValueRating).average().orElse(0.0);
double averageLandlordRating = reviews.stream().mapToInt(HouseReview::getLandlordRating).average().orElse(0.0);
// 计算评分分布
int[] ratingDistribution = new int[5]; // 1-5星评分分布
for (HouseReview review : reviews) {
ratingDistribution[review.getRating() - 1]++;
}
statistics.put("totalCount", reviews.size());
statistics.put("averageRating", averageRating);
statistics.put("averageLocationRating", averageLocationRating);
statistics.put("averageCleanlinessRating", averageCleanlinessRating);
statistics.put("averageValueRating", averageValueRating);
statistics.put("averageLandlordRating", averageLandlordRating);
statistics.put("ratingDistribution", ratingDistribution);
return Result.success(statistics);
}
@Override
public Result<Boolean> checkUserReviewed(Long houseId, Long userId) {
HouseReview review = houseReviewDao.selectByHouseIdAndUserId(houseId, userId);
return Result.success(review != null);
}
/**
* 更新房源评分
*/
private void updateHouseRating(Long houseId) {
Double averageRating = houseReviewDao.calculateAverageRating(houseId);
if (averageRating != null) {
House house = houseDao.selectById(houseId);
if (house != null) {
// 这里假设House实体类中有rating字段,实际项目中需要根据具体情况调整
// house.setRating(averageRating);
// houseDao.update(house);
}
}
}
}
backend\src\main\java\com\rental\service\impl\HouseServiceImpl.java
package com.rental.service.impl;
import com.rental.dao.HouseDao;
import com.rental.dao.HouseDetailDao;
import com.rental.dao.HouseImageDao;
import com.rental.dto.HouseDTO;
import com.rental.dto.HouseDetailDTO;
import com.rental.dto.HouseSearchDTO;
import com.rental.dto.PageResult;
import com.rental.entity.House;
import com.rental.entity.HouseDetail;
import com.rental.entity.HouseImage;
import com.rental.service.HouseService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
/**
* 房源服务实现类
*/
@Service
public class HouseServiceImpl implements HouseService {
@Autowired
private HouseDao houseDao;
@Autowired
private HouseDetailDao houseDetailDao;
@Autowired
private HouseImageDao houseImageDao;
@Override
public HouseDTO getHouseById(Long id) {
// 查询房源
House house = houseDao.selectById(id);
if (house == null) {
return null;
}
return convertToDTO(house);
}
@Override
public List<HouseDTO> getHouseListByLandlordId(Long landlordId) {
List<House> houses = houseDao.selectByLandlordId(landlordId);
return houses.stream().map(this::convertToDTO).collect(Collectors.toList());
}
@Override
public List<HouseDTO> getHouseList(House house) {
List<House> houses = houseDao.selectList(house);
return houses.stream().map(this::convertToDTO).collect(Collectors.toList());
}
@Override
public PageResult<HouseDTO> searchHouse(HouseSearchDTO searchDTO) {
// 构建查询条件
String keyword = searchDTO.getKeyword();
Double minPrice = searchDTO.getMinPrice();
Double maxPrice = searchDTO.getMaxPrice();
String houseType = searchDTO.getHouseType();
Integer hasElevator = null;
// 查询房源列表
List<House> houses = houseDao.search(keyword, minPrice, maxPrice, houseType, hasElevator);
// 计算总数
long total = houses.size();
// 分页
int pageNum = searchDTO.getPageNum();
int pageSize = searchDTO.getPageSize();
int fromIndex = (pageNum - 1) * pageSize;
int toIndex = Math.min(fromIndex + pageSize, houses.size());
if (fromIndex > houses.size()) {
houses = new ArrayList<>();
} else {
houses = houses.subList(fromIndex, toIndex);
}
// 转换为DTO
List<HouseDTO> houseDTOs = houses.stream().map(this::convertToDTO).collect(Collectors.toList());
return new PageResult<>(total, pageNum, pageSize, houseDTOs);
}
@Override
@Transactional
public Long addHouse(HouseDTO houseDTO) {
// 创建房源
House house = new House();
BeanUtils.copyProperties(houseDTO, house);
house.setCreateTime(new Date());
house.setUpdateTime(new Date());
house.setStatus(1); // 默认上架
// 保存房源
houseDao.insert(house);
// 保存房源详情
if (houseDTO.getDetail() != null) {
HouseDetail houseDetail = new HouseDetail();
BeanUtils.copyProperties(houseDTO.getDetail(), houseDetail);
houseDetail.setHouseId(house.getId());
houseDetailDao.insert(houseDetail);
}
// 保存房源图片
if (houseDTO.getImages() != null && !houseDTO.getImages().isEmpty()) {
List<HouseImage> houseImages = new ArrayList<>();
for (int i = 0; i < houseDTO.getImages().size(); i++) {
HouseImage houseImage = new HouseImage();
houseImage.setHouseId(house.getId());
houseImage.setImageUrl(houseDTO.getImages().get(i));
houseImage.setIsCover(i == 0 ? 1 : 0); // 第一张图片作为封面
houseImage.setSort(i + 1);
houseImages.add(houseImage);
}
houseImageDao.batchInsert(houseImages);
}
return house.getId();
}
@Override
@Transactional
public boolean updateHouse(HouseDTO houseDTO) {
// 查询房源
House house = houseDao.selectById(houseDTO.getId());
if (house == null) {
throw new RuntimeException("房源不存在");
}
// 更新房源
BeanUtils.copyProperties(houseDTO, house);
house.setUpdateTime(new Date());
houseDao.update(house);
// 更新房源详情
if (houseDTO.getDetail() != null) {
HouseDetail houseDetail = houseDetailDao.selectByHouseId(house.getId());
if (houseDetail == null) {
houseDetail = new HouseDetail();
BeanUtils.copyProperties(houseDTO.getDetail(), houseDetail);
houseDetail.setHouseId(house.getId());
houseDetailDao.insert(houseDetail);
} else {
BeanUtils.copyProperties(houseDTO.getDetail(), houseDetail);
houseDetailDao.update(houseDetail);
}
}
// 更新房源图片
if (houseDTO.getImages() != null && !houseDTO.getImages().isEmpty()) {
// 删除原有图片
houseImageDao.deleteByHouseId(house.getId());
// 保存新图片
List<HouseImage> houseImages = new ArrayList<>();
for (int i = 0; i < houseDTO.getImages().size(); i++) {
HouseImage houseImage = new HouseImage();
houseImage.setHouseId(house.getId());
houseImage.setImageUrl(houseDTO.getImages().get(i));
houseImage.setIsCover(i == 0 ? 1 : 0); // 第一张图片作为封面
houseImage.setSort(i + 1);
houseImages.add(houseImage);
}
houseImageDao.batchInsert(houseImages);
}
return true;
}
@Override
@Transactional
public boolean deleteHouse(Long id) {
// 删除房源详情
houseDetailDao.deleteByHouseId(id);
// 删除房源图片
houseImageDao.deleteByHouseId(id);
// 删除房源
return houseDao.deleteById(id) > 0;
}
@Override
@Transactional
public boolean deleteHouseBatch(Long[] ids) {
for (Long id : ids) {
deleteHouse(id);
}
return true;
}
@Override
@Transactional
public boolean updateHouseStatus(Long id, Integer status) {
return houseDao.updateStatus(id, status) > 0;
}
/**
* 将House实体转换为HouseDTO
*/
private HouseDTO convertToDTO(House house) {
if (house == null) {
return null;
}
HouseDTO houseDTO = new HouseDTO();
BeanUtils.copyProperties(house, houseDTO);
// 查询房源详情
HouseDetail houseDetail = houseDetailDao.selectByHouseId(house.getId());
if (houseDetail != null) {
HouseDetailDTO houseDetailDTO = new HouseDetailDTO();
BeanUtils.copyProperties(houseDetail, houseDetailDTO);
houseDTO.setDetail(houseDetailDTO);
}
// 查询房源图片
List<HouseImage> houseImages = houseImageDao.selectByHouseId(house.getId());
if (houseImages != null && !houseImages.isEmpty()) {
List<String> images = houseImages.stream()
.sorted((a, b) -> a.getSort() - b.getSort())
.map(HouseImage::getImageUrl)
.collect(Collectors.toList());
houseDTO.setImages(images);
}
return houseDTO;
}
}
backend\src\main\java\com\rental\service\impl\ReviewReplyServiceImpl.java
package com.rental.service.impl;
import com.rental.dao.HouseReviewDao;
import com.rental.dao.ReviewReplyDao;
import com.rental.dto.ReplyDTO;
import com.rental.entity.HouseReview;
import com.rental.entity.ReviewReply;
import com.rental.dto.Result;
import com.rental.service.ReviewReplyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* 评价回复服务实现类
*/
@Service
public class ReviewReplyServiceImpl implements ReviewReplyService {
@Autowired
private ReviewReplyDao reviewReplyDao;
@Autowired
private HouseReviewDao houseReviewDao;
@Override
@Transactional
public Result<ReviewReply> submitReply(ReplyDTO replyDTO, Long userId) {
// 检查评价是否存在
HouseReview review = houseReviewDao.selectById(replyDTO.getReviewId());
if (review == null) {
return Result.error(404, "评价不存在");
}
// 创建回复
ReviewReply reply = new ReviewReply();
reply.setReviewId(replyDTO.getReviewId());
reply.setUserId(userId);
reply.setContent(replyDTO.getContent());
reviewReplyDao.insert(reply);
return Result.success(reply);
}
@Override
@Transactional
public Result<Void> updateReply(Long replyId, ReplyDTO replyDTO, Long userId) {
// 检查回复是否存在
ReviewReply reply = reviewReplyDao.selectById(replyId);
if (reply == null) {
return Result.error(404, "回复不存在");
}
// 检查是否是回复用户本人
if (!reply.getUserId().equals(userId)) {
return Result.error(403, "无权修改此回复");
}
// 更新回复
reply.setContent(replyDTO.getContent());
reviewReplyDao.update(reply);
return Result.success();
}
@Override
@Transactional
public Result<Void> deleteReply(Long replyId, Long userId) {
// 检查回复是否存在
ReviewReply reply = reviewReplyDao.selectById(replyId);
if (reply == null) {
return Result.error(404, "回复不存在");
}
// 检查是否是回复用户本人
if (!reply.getUserId().equals(userId)) {
return Result.error(403, "无权删除此回复");
}
reviewReplyDao.deleteById(replyId);
return Result.success();
}
@Override
public Result<ReviewReply> getReplyById(Long replyId) {
ReviewReply reply = reviewReplyDao.selectById(replyId);
if (reply == null) {
return Result.error(404, "回复不存在");
}
return Result.success(reply);
}
@Override
public Result<List<ReviewReply>> getReviewReplies(Long reviewId) {
List<ReviewReply> replies = reviewReplyDao.selectByReviewId(reviewId);
return Result.success(replies);
}
}
2.7 Controllers
backend\src\main\java\com\rental\service\impl\UserServiceImpl.java
package com.rental.service.impl;
import com.rental.dao.RoleDao;
import com.rental.dao.UserDao;
import com.rental.dto.LoginDTO;
import com.rental.dto.RegisterDTO;
import com.rental.dto.UserDTO;
import com.rental.entity.Role;
import com.rental.entity.User;
import com.rental.service.UserService;
import com.rental.util.JwtUtil;
import com.rental.util.PasswordEncoder;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
/**
* 用户服务实现类
*/
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Autowired
private RoleDao roleDao;
@Autowired
private JwtUtil jwtUtil;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDTO login(LoginDTO loginDTO) {
// 根据用户名查询用户
User user = userDao.selectByUsername(loginDTO.getUsername());
if (user == null) {
throw new RuntimeException("用户名或密码错误");
}
// 校验密码
if (!passwordEncoder.matches(loginDTO.getPassword(), user.getPassword())) {
throw new RuntimeException("用户名或密码错误");
}
// 校验用户状态
if (user.getStatus() != 1) {
throw new RuntimeException("账号已被禁用");
}
// 生成token
String token = jwtUtil.generateToken(user.getId(), user.getUsername());
// 转换为DTO
UserDTO userDTO = convertToDTO(user);
userDTO.setToken(token);
return userDTO;
}
@Override
@Transactional
public UserDTO register(RegisterDTO registerDTO) {
// 校验用户名是否已存在
User existUser = userDao.selectByUsername(registerDTO.getUsername());
if (existUser != null) {
throw new RuntimeException("用户名已存在");
}
// 校验手机号是否已存在
existUser = userDao.selectByPhone(registerDTO.getPhone());
if (existUser != null) {
throw new RuntimeException("手机号已被注册");
}
// 创建用户
User user = new User();
user.setUsername(registerDTO.getUsername());
user.setPassword(passwordEncoder.encode(registerDTO.getPassword()));
user.setPhone(registerDTO.getPhone());
user.setStatus(1);
user.setCreateTime(new Date());
user.setUpdateTime(new Date());
// 设置角色
Role role = roleDao.selectByCode(registerDTO.getRole());
if (role == null) {
throw new RuntimeException("角色不存在");
}
// 保存用户
userDao.insert(user);
// 保存用户角色关系
userDao.insertUserRole(user.getId(), role.getId());
// 生成token
String token = jwtUtil.generateToken(user.getId(), user.getUsername());
// 转换为DTO
UserDTO userDTO = convertToDTO(user);
userDTO.setToken(token);
return userDTO;
}
@Override
public UserDTO getUserById(Long id) {
User user = userDao.selectById(id);
if (user == null) {
return null;
}
return convertToDTO(user);
}
@Override
public UserDTO getUserByUsername(String username) {
User user = userDao.selectByUsername(username);
if (user == null) {
return null;
}
return convertToDTO(user);
}
@Override
public List<UserDTO> getUserList(User user) {
List<User> users = userDao.selectList(user);
return users.stream().map(this::convertToDTO).collect(Collectors.toList());
}
@Override
@Transactional
public boolean updateUser(User user) {
user.setUpdateTime(new Date());
return userDao.update(user) > 0;
}
@Override
@Transactional
public boolean updateUserStatus(Long id, Integer status) {
return userDao.updateStatus(id, status) > 0;
}
@Override
@Transactional
public boolean deleteUser(Long id) {
return userDao.deleteById(id) > 0;
}
@Override
@Transactional
public boolean updatePassword(Long id, String oldPassword, String newPassword) {
// 查询用户
User user = userDao.selectById(id);
if (user == null) {
throw new RuntimeException("用户不存在");
}
// 校验旧密码
if (!passwordEncoder.matches(oldPassword, user.getPassword())) {
throw new RuntimeException("原密码错误");
}
// 更新密码
user.setPassword(passwordEncoder.encode(newPassword));
user.setUpdateTime(new Date());
return userDao.update(user) > 0;
}
/**
* 将User实体转换为UserDTO
*/
private UserDTO convertToDTO(User user) {
if (user == null) {
return null;
}
UserDTO userDTO = new UserDTO();
BeanUtils.copyProperties(user, userDTO);
// 查询用户角色
List<Role> roles = roleDao.selectByUserId(user.getId());
if (roles != null && !roles.isEmpty()) {
List<String> roleList = roles.stream().map(Role::getCode).collect(Collectors.toList());
userDTO.setRoles(roleList);
userDTO.setRole(roleList.get(0));
} else {
userDTO.setRoles(new ArrayList<>());
}
return userDTO;
}
}
backend\src\main\java\com\rental\service\impl\VerificationServiceImpl.java
package com.rental.service.impl;
import com.rental.dao.LandlordVerificationDao;
import com.rental.dao.StudentVerificationDao;
import com.rental.dao.UserDao;
import com.rental.dto.LandlordVerificationDTO;
import com.rental.dto.StudentVerificationDTO;
import com.rental.entity.LandlordVerification;
import com.rental.entity.StudentVerification;
import com.rental.entity.User;
import com.rental.service.VerificationService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.List;
/**
* 认证服务实现类
*/
@Service
public class VerificationServiceImpl implements VerificationService {
@Autowired
private StudentVerificationDao studentVerificationDao;
@Autowired
private LandlordVerificationDao landlordVerificationDao;
@Autowired
private UserDao userDao;
@Override
@Transactional
public boolean submitStudentVerification(StudentVerificationDTO verificationDTO) {
// 查询用户
User user = userDao.selectById(verificationDTO.getUserId());
if (user == null) {
throw new RuntimeException("用户不存在");
}
// 查询是否已提交认证
StudentVerification existVerification = studentVerificationDao.selectByUserId(verificationDTO.getUserId());
if (existVerification != null) {
// 如果已认证或认证中,不允许重复提交
if (existVerification.getStatus() == 1 || existVerification.getStatus() == 2) {
throw new RuntimeException("认证已提交,请勿重复提交");
}
// 更新认证信息
BeanUtils.copyProperties(verificationDTO, existVerification);
existVerification.setStatus(2); // 认证中
existVerification.setUpdateTime(new Date());
studentVerificationDao.update(existVerification);
} else {
// 创建认证信息
StudentVerification verification = new StudentVerification();
BeanUtils.copyProperties(verificationDTO, verification);
verification.setStatus(2); // 认证中
verification.setCreateTime(new Date());
verification.setUpdateTime(new Date());
studentVerificationDao.insert(verification);
}
return true;
}
@Override
@Transactional
public boolean submitLandlordVerification(LandlordVerificationDTO verificationDTO) {
// 查询用户
User user = userDao.selectById(verificationDTO.getUserId());
if (user == null) {
throw new RuntimeException("用户不存在");
}
// 查询是否已提交认证
LandlordVerification existVerification = landlordVerificationDao.selectByUserId(verificationDTO.getUserId());
if (existVerification != null) {
// 如果已认证或认证中,不允许重复提交
if (existVerification.getStatus() == 1 || existVerification.getStatus() == 2) {
throw new RuntimeException("认证已提交,请勿重复提交");
}
// 更新认证信息
BeanUtils.copyProperties(verificationDTO, existVerification);
existVerification.setStatus(2); // 认证中
existVerification.setUpdateTime(new Date());
landlordVerificationDao.update(existVerification);
} else {
// 创建认证信息
LandlordVerification verification = new LandlordVerification();
BeanUtils.copyProperties(verificationDTO, verification);
verification.setStatus(2); // 认证中
verification.setCreateTime(new Date());
verification.setUpdateTime(new Date());
landlordVerificationDao.insert(verification);
}
return true;
}
@Override
public StudentVerification getStudentVerificationByUserId(Long userId) {
return studentVerificationDao.selectByUserId(userId);
}
@Override
public LandlordVerification getLandlordVerificationByUserId(Long userId) {
return landlordVerificationDao.selectByUserId(userId);
}
@Override
public List<StudentVerification> getStudentVerificationList(StudentVerification verification) {
return studentVerificationDao.selectList(verification);
}
@Override
public List<LandlordVerification> getLandlordVerificationList(LandlordVerification verification) {
return landlordVerificationDao.selectList(verification);
}
@Override
@Transactional
public boolean auditStudentVerification(Long id, Integer status, String remark) {
// 查询认证信息
StudentVerification verification = studentVerificationDao.selectById(id);
if (verification == null) {
throw new RuntimeException("认证信息不存在");
}
// 更新认证状态
studentVerificationDao.updateStatus(id, status, remark);
// 如果认证通过,更新用户信息
if (status == 1) {
User user = userDao.selectById(verification.getUserId());
if (user != null) {
user.setRealName(verification.getRealName());
user.setUpdateTime(new Date());
userDao.update(user);
}
}
return true;
}
@Override
@Transactional
public boolean auditLandlordVerification(Long id, Integer status, String remark) {
// 查询认证信息
LandlordVerification verification = landlordVerificationDao.selectById(id);
if (verification == null) {
throw new RuntimeException("认证信息不存在");
}
// 更新认证状态
landlordVerificationDao.updateStatus(id, status, remark);
// 如果认证通过,更新用户信息
if (status == 1) {
User user = userDao.selectById(verification.getUserId());
if (user != null) {
user.setRealName(verification.getRealName());
user.setUpdateTime(new Date());
userDao.update(user);
}
}
return true;
}
}
backend\src\main\java\com\rental\service\impl\ViewingFeedbackServiceImpl.java
package com.rental.service.impl;
import com.rental.dao.AppointmentDao;
import com.rental.dao.HouseDao;
import com.rental.dao.ViewingFeedbackDao;
import com.rental.dto.FeedbackDTO;
import com.rental.entity.Appointment;
import com.rental.entity.House;
import com.rental.entity.ViewingFeedback;
import com.rental.dto.Result;
import com.rental.service.ViewingFeedbackService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* 看房反馈服务实现类
*/
@Service
public class ViewingFeedbackServiceImpl implements ViewingFeedbackService {
@Autowired
private ViewingFeedbackDao viewingFeedbackDao;
@Autowired
private AppointmentDao appointmentDao;
@Autowired
private HouseDao houseDao;
@Override
@Transactional
public Result<ViewingFeedback> submitFeedback(FeedbackDTO feedbackDTO, Long userId) {
// 检查预约是否存在
Appointment appointment = appointmentDao.selectById(feedbackDTO.getAppointmentId());
if (appointment == null) {
return Result.error(404, "预约不存在");
}
// 检查是否是预约用户本人
if (!appointment.getUserId().equals(userId)) {
return Result.error(403, "无权提交此预约的反馈");
}
// 检查预约状态是否已完成
if (appointment.getStatus() != 3) {
return Result.error(400, "预约尚未完成,无法提交反馈");
}
// 检查是否已提交过反馈
ViewingFeedback existingFeedback = viewingFeedbackDao.selectByAppointmentId(feedbackDTO.getAppointmentId());
if (existingFeedback != null) {
return Result.error(400, "已提交过反馈,不能重复提交");
}
// 创建反馈
ViewingFeedback feedback = new ViewingFeedback();
feedback.setAppointmentId(feedbackDTO.getAppointmentId());
feedback.setUserId(userId);
feedback.setHouseId(appointment.getHouseId());
feedback.setFeedbackContent(feedbackDTO.getFeedbackContent());
feedback.setSatisfactionLevel(feedbackDTO.getSatisfactionLevel());
feedback.setIsPublic(feedbackDTO.getIsPublic());
viewingFeedbackDao.insert(feedback);
return Result.success(feedback);
}
@Override
@Transactional
public Result<Void> updateFeedback(Long feedbackId, FeedbackDTO feedbackDTO, Long userId) {
// 检查反馈是否存在
ViewingFeedback feedback = viewingFeedbackDao.selectById(feedbackId);
if (feedback == null) {
return Result.error(404, "反馈不存在");
}
// 检查是否是反馈用户本人
if (!feedback.getUserId().equals(userId)) {
return Result.error(403, "无权修改此反馈");
}
// 更新反馈
feedback.setFeedbackContent(feedbackDTO.getFeedbackContent());
feedback.setSatisfactionLevel(feedbackDTO.getSatisfactionLevel());
feedback.setIsPublic(feedbackDTO.getIsPublic());
viewingFeedbackDao.update(feedback);
return Result.success();
}
@Override
@Transactional
public Result<Void> deleteFeedback(Long feedbackId, Long userId) {
// 检查反馈是否存在
ViewingFeedback feedback = viewingFeedbackDao.selectById(feedbackId);
if (feedback == null) {
return Result.error(404, "反馈不存在");
}
// 检查是否是反馈用户本人
if (!feedback.getUserId().equals(userId)) {
return Result.error(403, "无权删除此反馈");
}
viewingFeedbackDao.deleteById(feedbackId);
return Result.success();
}
@Override
@Transactional
public Result<Void> setFeedbackPublic(Long feedbackId, Integer isPublic, Long userId) {
// 检查反馈是否存在
ViewingFeedback feedback = viewingFeedbackDao.selectById(feedbackId);
if (feedback == null) {
return Result.error(404, "反馈不存在");
}
// 检查是否是反馈用户本人
if (!feedback.getUserId().equals(userId)) {
return Result.error(403, "无权修改此反馈");
}
// 更新公开状态
viewingFeedbackDao.updateIsPublic(feedbackId, isPublic);
return Result.success();
}
@Override
public Result<ViewingFeedback> getFeedbackById(Long feedbackId) {
ViewingFeedback feedback = viewingFeedbackDao.selectById(feedbackId);
if (feedback == null) {
return Result.error(404, "反馈不存在");
}
return Result.success(feedback);
}
@Override
public Result<List<ViewingFeedback>> getUserFeedbacks(Long userId) {
List<ViewingFeedback> feedbacks = viewingFeedbackDao.selectByUserId(userId);
return Result.success(feedbacks);
}
@Override
public Result<List<ViewingFeedback>> getHousePublicFeedbacks(Long houseId) {
List<ViewingFeedback> feedbacks = viewingFeedbackDao.selectPublicByHouseId(houseId);
return Result.success(feedbacks);
}
@Override
public Result<ViewingFeedback> getAppointmentFeedback(Long appointmentId) {
ViewingFeedback feedback = viewingFeedbackDao.selectByAppointmentId(appointmentId);
if (feedback == null) {
return Result.error(404, "该预约暂无反馈");
}
return Result.success(feedback);
}
}
backend\src\main\java\com\rental\util\JsonUtil.java
package com.rental.util;
import com.fasterxml.jackson.core.
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
/**
* JSON工具类
*/
public class JsonUtil {
private static final ObjectMapper objectMapper = new ObjectMapper();
static {
// 注册Java 8时间模块
objectMapper.registerModule(new JavaTimeModule());
// 禁用日期时间作为时间戳输出
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
}
/**
* 对象转JSON字符串
*
* @param obj 对象
* @return JSON字符串
*/
public static String toJson(Object obj) {
try {
return objectMapper.writeValueAsString(obj);
} catch (JsonProcessingException e) {
throw new RuntimeException("转换JSON失败", e);
}
}
/**
* JSON字符串转对象
*
* @param json JSON字符串
* @param clazz 对象类型
* @param <T> 泛型
* @return 对象
*/
public static <T> T fromJson(String json, Class<T> clazz) {
try {
return objectMapper.readValue(json, clazz);
} catch (JsonProcessingException e) {
throw new RuntimeException("解析JSON失败", e);
}
}
}
Global Exception Handler
backend\src\main\java\com\rental\util\JwtUtil.java
package com.rental.util;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* JWT工具类
*/
@Component
public class JwtUtil {
private static String secret;
private static Long expiration;
@Value("${jwt.secret}")
public void setSecret(String secret) {
JwtUtil.secret = secret;
}
@Value("${jwt.expiration}")
public void setExpiration(Long expiration) {
JwtUtil.expiration = expiration;
}
/**
* 生成token
*
* @param userId 用户ID
* @return token
*/
public static String generateToken(Long userId) {
Map<String, Object> claims = new HashMap<>();
claims.put("userId", userId);
return generateToken(claims);
}
/**
* 从token中获取用户ID
*
* @param token token
* @return 用户ID
*/
public static Long getUserId(String token) {
Claims claims = getClaimsFromToken(token);
return claims != null ? claims.get("userId", Long.class) : null;
}
/**
* 验证token是否过期
*
* @param token token
* @return 是否过期
*/
public static boolean isTokenExpired(String token) {
Claims claims = getClaimsFromToken(token);
return claims != null && claims.getExpiration().before(new Date());
}
/**
* 生成token
*
* @param claims 数据声明
* @return token
*/
private static String generateToken(Map<String, Object> claims) {
Date createdDate = new Date();
Date expirationDate = new Date(createdDate.getTime() + expiration * 1000);
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(createdDate)
.setExpiration(expirationDate)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
/**
* 从token中获取数据声明
*
* @param token token
* @return 数据声明
*/
private static Claims getClaimsFromToken(String token) {
Claims claims;
try {
claims = Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
claims = null;
}
return claims;
}
}
Password Utility
backend\src\main\java\com\rental\util\PasswordEncoder.java
package com.rental.util;
import org.springframework.stereotype.Component;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
/**
* 密码编码器
*/
@Component
public class PasswordEncoder {
/**
* 加密密码
*
* @param rawPassword 原始密码
* @return 加密后的密码
*/
public String encode(String rawPassword) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] hash = md.digest(rawPassword.getBytes());
return Base64.getEncoder().encodeToString(hash);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("加密失败", e);
}
}
/**
* 校验密码
*
* @param rawPassword 原始密码
* @param encodedPassword 加密后的密码
* @return 是否匹配
*/
public boolean matches(String rawPassword, String encodedPassword) {
return encode(rawPassword).equals(encodedPassword);
}
}
backend\src\main\java\com\rental\util\PasswordUtil.java
package com.rental.util;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
/**
* 密码工具类
*/
public class PasswordUtil {
/**
* 加密密码
*
* @param password 原始密码
* @return 加密后的密码
*/
public static String encode(String password) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(password.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(hash);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("加密密码失败", e);
}
}
/**
* 验证密码
*
* @param rawPassword 原始密码
* @param encodedPassword 加密后的密码
* @return 是否匹配
*/
public static boolean matches(String rawPassword, String encodedPassword) {
return encode(rawPassword).equals(encodedPassword);
}
}
JSON Utility
backend\src\main\resources\application.properties
# 服务配置
server.port=8080
server.servlet.context-path=/
# 数据库配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/rental_system?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=123456
# MyBatis配置
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.type-aliases-package=com.rental.entity
mybatis.configuration.map-underscore-to-camel-case=true
# 日志配置
logging.level.com.rental=debug
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
# 文件上传配置
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=100MB
# 文件上传路径
file.upload.path=D:/rental-system/upload/
# 文件访问路径
file.access.path=/upload/
# JWT配置
jwt.secret=rental-system-secret-key
jwt.expiration=86400000
# 分页配置
pagehelper.helper-dialect=mysql
pagehelper.reasonable=true
pagehelper.support-methods-arguments=true
pagehelper.params=count=countSql
backend\src\main\resources\application.yml
server:
port: 8080
servlet:
context-path: /api
spring:
application:
name: rental-system
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/rental_system?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
druid:
initial-size: 5
min-idle: 5
max-active: 20
max-wait: 60000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
validation-query: SELECT 1 FROM DUAL
test-while-idle: true
test-on-borrow: false
test-on-return: false
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
filters: stat,wall
redis:
host: localhost
port: 6379
database: 0
timeout: 10000
servlet:
multipart:
max-file-size: 10MB
max-request-size: 20MB
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.rental.entity
configuration:
map-underscore-to-camel-case: true
cache-enabled: true
logging:
level:
com.rental: debug
# JWT配置
jwt:
# JWT加密密钥
secret: rental-system-secret
# token有效时间(单位:秒)
expiration: 86400
# 文件上传配置
file:
upload-dir: ./uploads
allowed-types: jpg,jpeg,png,gif
max-size: 10485760
backend\src\main\resources\schema.sql
-- 创建数据库
CREATE DATABASE IF NOT EXISTS rental_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
USE rental_system;
-- 用户表
CREATE TABLE IF NOT EXISTS `user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`username` varchar(50) NOT NULL COMMENT '用户名',
`password` varchar(100) NOT NULL COMMENT '密码',
`real_name` varchar(50) DEFAULT NULL COMMENT '真实姓名',
`phone` varchar(20) DEFAULT NULL COMMENT '手机号码',
`email` varchar(100) DEFAULT NULL COMMENT '邮箱',
`avatar` varchar(255) DEFAULT NULL COMMENT '头像',
`gender` tinyint(1) DEFAULT 0 COMMENT '性别:0-未知,1-男,2-女',
`status` tinyint(1) DEFAULT 1 COMMENT '状态:0-禁用,1-正常',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_username` (`username`),
UNIQUE KEY `idx_phone` (`phone`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
-- 角色表
CREATE TABLE IF NOT EXISTS `role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '角色ID',
`name` varchar(50) NOT NULL COMMENT '角色名称',
`code` varchar(50) NOT NULL COMMENT '角色编码',
`description` varchar(255) DEFAULT NULL COMMENT '角色描述',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_code` (`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表';
-- 用户角色关联表
CREATE TABLE IF NOT EXISTS `user_role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`role_id` bigint(20) NOT NULL COMMENT '角色ID',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_user_role` (`user_id`, `role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户角色关联表';
-- 学生认证表
CREATE TABLE IF NOT EXISTS `student_verification` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`student_id` varchar(50) NOT NULL COMMENT '学号',
`school` varchar(100) NOT NULL COMMENT '学校',
`college` varchar(100) DEFAULT NULL COMMENT '学院',
`major` varchar(100) DEFAULT NULL COMMENT '专业',
`id_card` varchar(50) NOT NULL COMMENT '身份证号',
`id_card_front` varchar(255) NOT NULL COMMENT '身份证正面照',
`id_card_back` varchar(255) NOT NULL COMMENT '身份证背面照',
`student_card` varchar(255) NOT NULL COMMENT '学生证照片',
`status` tinyint(1) DEFAULT 0 COMMENT '状态:0-待审核,1-已通过,2-已拒绝',
`remark` varchar(255) DEFAULT NULL COMMENT '备注',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='学生认证表';
-- 房东认证表
CREATE TABLE IF NOT EXISTS `landlord_verification` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`real_name` varchar(50) NOT NULL COMMENT '真实姓名',
`id_card` varchar(50) NOT NULL COMMENT '身份证号',
`id_card_front` varchar(255) NOT NULL COMMENT '身份证正面照',
`id_card_back` varchar(255) NOT NULL COMMENT '身份证背面照',
`house_certificate` varchar(255) DEFAULT NULL COMMENT '房产证照片',
`status` tinyint(1) DEFAULT 0 COMMENT '状态:0-待审核,1-已通过,2-已拒绝',
`remark` varchar(255) DEFAULT NULL COMMENT '备注',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='房东认证表';
-- 房源表
CREATE TABLE IF NOT EXISTS `house` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '房源ID',
`landlord_id` bigint(20) NOT NULL COMMENT '房东ID',
`title` varchar(100) NOT NULL COMMENT '标题',
`price` decimal(10,2) NOT NULL COMMENT '租金',
`area` decimal(10,2) NOT NULL COMMENT '面积',
`house_type` varchar(50) NOT NULL COMMENT '户型',
`floor` varchar(50) DEFAULT NULL COMMENT '楼层',
`orientation` varchar(50) DEFAULT NULL COMMENT '朝向',
`decoration` varchar(50) DEFAULT NULL COMMENT '装修',
`community` varchar(100) NOT NULL COMMENT '小区名称',
`address` varchar(255) NOT NULL COMMENT '详细地址',
`longitude` decimal(10,6) DEFAULT NULL COMMENT '经度',
`latitude` decimal(10,6) DEFAULT NULL COMMENT '纬度',
`contact` varchar(50) DEFAULT NULL COMMENT '联系人',
`contact_phone` varchar(20) DEFAULT NULL COMMENT '联系电话',
`status` tinyint(1) DEFAULT 0 COMMENT '状态:0-待审核,1-已上架,2-已下架,3-已出租',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_landlord_id` (`landlord_id`),
KEY `idx_community` (`community`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='房源表';
-- 房源详情表
CREATE TABLE IF NOT EXISTS `house_detail` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`house_id` bigint(20) NOT NULL COMMENT '房源ID',
`house_category` varchar(50) DEFAULT NULL COMMENT '房源类别:整租、合租',
`rent_type` varchar(50) DEFAULT NULL COMMENT '出租方式:月付、季付、半年付、年付',
`payment_type` varchar(50) DEFAULT NULL COMMENT '付款方式:押一付一、押一付三、押二付三等',
`has_elevator` tinyint(1) DEFAULT NULL COMMENT '是否有电梯:0-无,1-有',
`heating_type` varchar(50) DEFAULT NULL COMMENT '供暖方式',
`water_fee` varchar(50) DEFAULT NULL COMMENT '水费',
`electricity_fee` varchar(50) DEFAULT NULL COMMENT '电费',
`gas_fee` varchar(50) DEFAULT NULL COMMENT '燃气费',
`internet_fee` varchar(50) DEFAULT NULL COMMENT '网费',
`property_fee` varchar(50) DEFAULT NULL COMMENT '物业费',
`has_parking` tinyint(1) DEFAULT NULL COMMENT '是否有停车位:0-无,1-有',
`check_in_time` date DEFAULT NULL COMMENT '入住时间',
`min_rent_period` int(11) DEFAULT NULL COMMENT '最短租期(月)',
`max_rent_period` int(11) DEFAULT NULL COMMENT '最长租期(月)',
`facilities` text DEFAULT NULL COMMENT '配套设施',
`transportation` text DEFAULT NULL COMMENT '交通情况',
`surroundings` text DEFAULT NULL COMMENT '周边配套',
`description` text DEFAULT NULL COMMENT '房源描述',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_house_id` (`house_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='房源详情表';
-- 房源图片表
CREATE TABLE IF NOT EXISTS `house_image` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`house_id` bigint(20) NOT NULL COMMENT '房源ID',
`url` varchar(255) NOT NULL COMMENT '图片URL',
`is_cover` tinyint(1) DEFAULT 0 COMMENT '是否封面:0-否,1-是',
`sort` int(11) DEFAULT 0 COMMENT '排序',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `idx_house_id` (`house_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='房源图片表';
-- 收藏表
CREATE TABLE IF NOT EXISTS `favorite` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`house_id` bigint(20) NOT NULL COMMENT '房源ID',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_user_house` (`user_id`, `house_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='收藏表';
-- 浏览记录表
CREATE TABLE IF NOT EXISTS `browse_record` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`house_id` bigint(20) NOT NULL COMMENT '房源ID',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_house_id` (`house_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='浏览记录表';
-- 预约看房表
CREATE TABLE IF NOT EXISTS `appointment` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`house_id` bigint(20) NOT NULL COMMENT '房源ID',
`landlord_id` bigint(20) NOT NULL COMMENT '房东ID',
`appointment_time` datetime NOT NULL COMMENT '预约时间',
`contact` varchar(50) NOT NULL COMMENT '联系人',
`contact_phone` varchar(20) NOT NULL COMMENT '联系电话',
`remark` varchar(255) DEFAULT NULL COMMENT '备注',
`status` tinyint(1) DEFAULT 0 COMMENT '状态:0-待确认,1-已确认,2-已取消,3-已完成',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_house_id` (`house_id`),
KEY `idx_landlord_id` (`landlord_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='预约看房表';
-- 合同表
CREATE TABLE IF NOT EXISTS `contract` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`house_id` bigint(20) NOT NULL COMMENT '房源ID',
`landlord_id` bigint(20) NOT NULL COMMENT '房东ID',
`tenant_id` bigint(20) NOT NULL COMMENT '租客ID',
`contract_no` varchar(50) NOT NULL COMMENT '合同编号',
`start_date` date NOT NULL COMMENT '开始日期',
`end_date` date NOT NULL COMMENT '结束日期',
`rent` decimal(10,2) NOT NULL COMMENT '租金',
`deposit` decimal(10,2) NOT NULL COMMENT '押金',
`payment_type` varchar(50) NOT NULL COMMENT '付款方式',
`payment_cycle` int(11) NOT NULL COMMENT '付款周期(月)',
`file_url` varchar(255) DEFAULT NULL COMMENT '合同文件URL',
`status` tinyint(1) DEFAULT 0 COMMENT '状态:0-待签署,1-已签署,2-已终止',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_contract_no` (`contract_no`),
KEY `idx_house_id` (`house_id`),
KEY `idx_landlord_id` (`landlord_id`),
KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='合同表';
-- 租金支付记录表
CREATE TABLE IF NOT EXISTS `rent_payment` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`contract_id` bigint(20) NOT NULL COMMENT '合同ID',
`tenant_id` bigint(20) NOT NULL COMMENT '租客ID',
`amount` decimal(10,2) NOT NULL COMMENT '金额',
`payment_date` date NOT NULL COMMENT '支付日期',
`payment_method` varchar(50) DEFAULT NULL COMMENT '支付方式',
`status` tinyint(1) DEFAULT 0 COMMENT '状态:0-未支付,1-已支付',
`remark` varchar(255) DEFAULT NULL COMMENT '备注',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_contract_id` (`contract_id`),
KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='租金支付记录表';
-- 初始化角色数据
INSERT INTO `role` (`name`, `code`, `description`) VALUES
('管理员', 'admin', '系统管理员'),
('房东', 'landlord', '房源发布者'),
('学生', 'student', '租房学生');
-- 初始化管理员账号
INSERT INTO `user` (`username`, `password`, `real_name`, `phone`, `email`, `status`) VALUES
('admin', 'jGl25bVBBBW96Qi9Te4V37Fnqchz/Eu4qB9vKrRIqRg=', '管理员', '13800138000', 'admin@example.com', 1);
-- 关联管理员角色
INSERT INTO `user_role` (`user_id`, `role_id`) VALUES (1, 1);
backend\src\main\resources\mapper\AppointmentMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.rental.dao.AppointmentDao">
<resultMap id="BaseResultMap" type="com.rental.entity.Appointment">
<id column="id" property="id"/>
<result column="house_id" property="houseId"/>
<result column="user_id" property="userId"/>
<result column="landlord_id" property="landlordId"/>
<result column="appointment_time" property="appointmentTime"/>
<result column="contact_name" property="contactName"/>
<result column="contact_phone" property="contactPhone"/>
<result column="appointment_notes" property="appointmentNotes"/>
<result column="status" property="status"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
</resultMap>
<sql id="Base_Column_List">
id, house_id, user_id, landlord_id, appointment_time, contact_name, contact_phone,
appointment_notes, status, create_time, update_time
</sql>
<select id="selectById" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from appointment
where id = #{id}
</select>
<select id="selectByUserId" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from appointment
where user_id = #{userId}
order by appointment_time desc
</select>
<select id="selectByLandlordId" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from appointment
where landlord_id = #{landlordId}
order by appointment_time desc
</select>
<select id="selectByHouseId" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from appointment
where house_id = #{houseId}
order by appointment_time desc
</select>
<select id="selectByDateRange" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from appointment
where appointment_time between #{startDate} and #{endDate}
order by appointment_time
</select>
<select id="selectList" parameterType="com.rental.entity.Appointment" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from appointment
<where>
<if test="houseId != null">
and house_id = #{houseId}
</if>
<if test="userId != null">
and user_id = #{userId}
</if>
<if test="landlordId != null">
and landlord_id = #{landlordId}
</if>
<if test="status != null">
and status = #{status}
</if>
</where>
order by appointment_time desc
</select>
<select id="checkTimeConflict" resultType="java.lang.Integer">
select count(1)
from appointment
where house_id = #{houseId}
and status in (0, 1)
and appointment_time between date_sub(#{appointmentTime}, interval 1 hour)
and date_add(#{appointmentTime}, interval 1 hour)
</select>
<insert id="insert" parameterType="com.rental.entity.Appointment" useGeneratedKeys="true" keyProperty="id">
insert into appointment
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="houseId != null">house_id,</if>
<if test="userId != null">user_id,</if>
<if test="landlordId != null">landlord_id,</if>
<if test="appointmentTime != null">appointment_time,</if>
<if test="contactName != null">contact_name,</if>
<if test="contactPhone != null">contact_phone,</if>
<if test="appointmentNotes != null">appointment_notes,</if>
<if test="status != null">status,</if>
create_time,
update_time,
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="houseId != null">#{houseId},</if>
<if test="userId != null">#{userId},</if>
<if test="landlordId != null">#{landlordId},</if>
<if test="appointmentTime != null">#{appointmentTime},</if>
<if test="contactName != null">#{contactName},</if>
<if test="contactPhone != null">#{contactPhone},</if>
<if test="appointmentNotes != null">#{appointmentNotes},</if>
<if test="status != null">#{status},</if>
now(),
now(),
</trim>
</insert>
<update id="update" parameterType="com.rental.entity.Appointment">
update appointment
<set>
<if test="appointmentTime != null">appointment_time = #{appointmentTime},</if>
<if test="contactName != null">contact_name = #{contactName},</if>
<if test="contactPhone != null">contact_phone = #{contactPhone},</if>
<if test="appointmentNotes != null">appointment_notes = #{appointmentNotes},</if>
<if test="status != null">status = #{status},</if>
update_time = now(),
</set>
where id = #{id}
</update>
<update id="updateStatus">
update appointment
set status = #{status}, update_time = now()
where id = #{id}
</update>
<delete id="deleteById" parameterType="java.lang.Long">
delete from appointment
where id = #{id}
</delete>
</mapper>
backend\src\main\resources\mapper\HouseDetailMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.rental.dao.HouseDetailDao">
<resultMap id="BaseResultMap" type="com.rental.entity.HouseDetail">
<id column="id" property="id" />
<result column="house_id" property="houseId" />
<result column="house_category" property="houseCategory" />
<result column="rent_type" property="rentType" />
<result column="payment_type" property="paymentType" />
<result column="has_elevator" property="hasElevator" />
<result column="heating_type" property="heatingType" />
<result column="water_fee" property="waterFee" />
<result column="electricity_fee" property="electricityFee" />
<result column="gas_fee" property="gasFee" />
<result column="internet_fee" property="internetFee" />
<result column="property_fee" property="propertyFee" />
<result column="has_parking" property="hasParking" />
<result column="check_in_time" property="checkInTime" />
<result column="min_rent_period" property="minRentPeriod" />
<result column="max_rent_period" property="maxRentPeriod" />
<result column="facilities" property="facilities" />
<result column="transportation" property="transportation" />
<result column="surroundings" property="surroundings" />
<result column="description" property="description" />
</resultMap>
<sql id="Base_Column_List">
id, house_id, house_category, rent_type, payment_type, has_elevator, heating_type,
water_fee, electricity_fee, gas_fee, internet_fee, property_fee, has_parking,
check_in_time, min_rent_period, max_rent_period, facilities, transportation,
surroundings, description
</sql>
<select id="selectById" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List" />
FROM house_detail
WHERE id = #{id}
</select>
<select id="selectByHouseId" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List" />
FROM house_detail
WHERE house_id = #{houseId}
</select>
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
INSERT INTO house_detail (
house_id, house_category, rent_type, payment_type, has_elevator, heating_type,
water_fee, electricity_fee, gas_fee, internet_fee, property_fee, has_parking,
check_in_time, min_rent_period, max_rent_period, facilities, transportation,
surroundings, description
) VALUES (
#{houseId}, #{houseCategory}, #{rentType}, #{paymentType}, #{hasElevator}, #{heatingType},
#{waterFee}, #{electricityFee}, #{gasFee}, #{internetFee}, #{propertyFee}, #{hasParking},
#{checkInTime}, #{minRentPeriod}, #{maxRentPeriod}, #{facilities}, #{transportation},
#{surroundings}, #{description}
)
</insert>
<update id="update">
UPDATE house_detail
<set>
<if test="houseCategory != null">house_category = #{houseCategory},</if>
<if test="rentType != null">rent_type = #{rentType},</if>
<if test="paymentType != null">payment_type = #{paymentType},</if>
<if test="hasElevator != null">has_elevator = #{hasElevator},</if>
<if test="heatingType != null">heating_type = #{heatingType},</if>
<if test="waterFee != null">water_fee = #{waterFee},</if>
<if test="electricityFee != null">electricity_fee = #{electricityFee},</if>
<if test="gasFee != null">gas_fee = #{gasFee},</if>
<if test="internetFee != null">internet_fee = #{internetFee},</if>
<if test="propertyFee != null">property_fee = #{propertyFee},</if>
<if test="hasParking != null">has_parking = #{hasParking},</if>
<if test="checkInTime != null">check_in_time = #{checkInTime},</if>
<if test="minRentPeriod != null">min_rent_period = #{minRentPeriod},</if>
<if test="maxRentPeriod != null">max_rent_period = #{maxRentPeriod},</if>
<if test="facilities != null">facilities = #{facilities},</if>
<if test="transportation != null">transportation = #{transportation},</if>
<if test="surroundings != null">surroundings = #{surroundings},</if>
<if test="description != null">description = #{description},</if>
</set>
WHERE id = #{id}
</update>
<delete id="deleteById">
DELETE FROM house_detail
WHERE id = #{id}
</delete>
<delete id="deleteByHouseId">
DELETE FROM house_detail
WHERE house_id = #{houseId}
</delete>
</mapper>