Java全栈项目-大学生租房管理系统(2)

代码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>

backend\src\main\resources\mapper\HouseMapper.xml

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天天进步2015

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值