項目编写-后台【java项目04】

43 阅读10分钟

home公寓项目/租赁管理模块

租房项目的后台管理功能全部代码实现,后台管理程序!

共计包含五大模块: 公寓信息管理\租赁信息管理\用户信息管理\系统管理\登录管理!!!

一、看房预约管理

1.1 看房预约管理介绍

看房预约管理共有两个接口,分别是根据条件分页查询预约信息根据ID更新预约状态,下面逐一实现!!

image.png

首先在ViewAppointmentController中注入ViewAppointmentService,如下

@Tag(name = "预约看房管理")
@RequestMapping("/admin/appointment")
@RestController
public class ViewAppointmentController {

    @Autowired
    private ViewAppointmentService service;
}

1.2 根据条件分页查询预约信息

  • 查看请求和响应的数据结构

    • 请求数据结构

      • currentsize为分页相关参数,分别表示当前所处页面每个页面的记录数

      • AppointmentQueryVo为看房预约的查询条件,详细结构如下:

        @Data
        @Schema(description = "预约看房查询实体")
        public class AppointmentQueryVo {
        
            @Schema(description="预约公寓所在省份")
            private Long provinceId;
        
            @Schema(description="预约公寓所在城市")
            private Long cityId;
        
            @Schema(description="预约公寓所在区")
            private Long districtId;
        
            @Schema(description="预约公寓所在公寓")
            private Long apartmentId;
        
            @Schema(description="预约用户姓名")
            private String name;
        
            @Schema(description="预约用户手机号码")
            private String phone;
        }
        
    • 响应数据结构

      单个看房预约信息的结构可查看web-admin模块下的com.mytest.lease.web.admin.vo.appointment.AppointmentVo,内容如下:

      @Data
      @Schema(description = "预约看房信息")
      public class AppointmentVo extends ViewAppointment {
      
          @Schema(description = "预约公寓信息")
          private ApartmentInfo apartmentInfo;
      }
      
  • 编写Controller层逻辑

    ViewAppointmentController中增加如下内容

    @Operation(summary = "分页查询预约信息")
    @GetMapping("page")
    public Result<IPage<AppointmentVo>> page(@RequestParam long current, @RequestParam long size, AppointmentQueryVo queryVo) {
        IPage<AppointmentVo> page = new Page<>(current, size);
        IPage<AppointmentVo> list = service.pageAppointmentByQuery(page, queryVo);
        return Result.ok(list);
    }
    
  • 编写Service层逻辑

    • ViewAppointmentService中增加如下内容

      IPage<AppointmentVo> pageAppointmentByQuery(IPage<AppointmentVo> page, AppointmentQueryVo queryVo);
      
    • ViewAppointmentServiceImpl中增加如下内容

      @Override
      public IPage<AppointmentVo> pageAppointmentByQuery(IPage<AppointmentVo> page, AppointmentQueryVo queryVo) {
          return viewAppointmentMapper.pageAppointmentByQuery(page, queryVo);
      }
      
  • 编写Mapper层逻辑

    • ViewAppointmentMapper中增加如下内容

      IPage<AppointmentVo> pageAppointmentByQuery(IPage<AppointmentVo> page, AppointmentQueryVo queryVo);
      
    • ViewAppointmentMapper.xml中增加如下内容

      <resultMap id="AppointmentVoMap" type="com.mytest.lease.web.admin.vo.appointment.AppointmentVo" autoMapping="true">
          <id property="id" column="id"/>
          <association property="apartmentInfo" javaType="com.mytest.lease.model.entity.ApartmentInfo" autoMapping="true">
              <id property="id" column="apartment_id"/>
              <result property="name" column="apartment_name"/>
          </association>
      </resultMap>
      
      <select id="pageAppointmentByQuery" resultMap="AppointmentVoMap">
          select va.id,
                 va.user_id,
                 va.name,
                 va.phone,
                 va.appointment_time,
                 va.additional_info,
                 va.appointment_status,
                 ai.id   apartment_id,
                 ai.name apartment_name,
                 ai.district_id,
                 ai.district_name,
                 ai.city_id,
                 ai.city_name,
                 ai.province_id,
                 ai.province_name
          from view_appointment va
                   left join
               apartment_info ai
               on va.apartment_id = ai.id and ai.is_deleted=0
          <where>
              va.is_deleted = 0
              <if test="queryVo.provinceId != null">
                  and ai.province_id = #{queryVo.provinceId}
              </if>
              <if test="queryVo.cityId != null">
                  and ai.city_id = #{queryVo.cityId}
              </if>
              <if test="queryVo.districtId != null">
                  and ai.district_id = #{queryVo.districtId}
              </if>
              <if test="queryVo.apartmentId != null">
                  and va.apartment_id = #{queryVo.apartmentId}
              </if>
              <if test="queryVo.name != null and queryVo.name != ''">
                  and va.name like concat('%',#{queryVo.name},'%')
              </if>
              <if test="queryVo.phone != null and queryVo.phone != ''">
                  and va.phone like concat('%',#{queryVo.phone},'%')
              </if>
          </where>
      </select>
      

    知识点

    ViewAppointment实体类中的appointmentTime字段为Date类型,Date类型的字段在序列化成JSON字符串时,需要考虑两个点,分别是格式时区。本项目使用JSON序列化框架为Jackson,具体配置如下

    • 格式

      格式可按照字段单独配置,也可全局配置,下面分别介绍

      • 单独配置

        在指定字段增加@JsonFormat注解,如下

        @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
        private Date appointmentTime;
        
      • 全局配置

        application.yml中增加如下内容

        spring:
          jackson:
            date-format: yyyy-MM-dd HH:mm:ss
        
    • 时区

      时区同样可按照字段单独配置,也可全局配置,下面分别介绍

      • 单独配置

        在指定字段增加@JsonFormat注解,如下

        @JsonFormat(timezone = "GMT+8")
        private Date appointmentTime;
        
      • 全局配置

        spring:
          jackson:
            time-zone: GMT+8
        

    推荐格式按照字段单独配置,时区全局配置。

1.3 根据ID更新预约状态

ViewAppointmentController中增加如下内容

@Operation(summary = "根据id更新预约状态")
@PostMapping("updateStatusById")
public Result updateStatusById(@RequestParam Long id, @RequestParam AppointmentStatus status) {

    LambdaUpdateWrapper<ViewAppointment> updateWrapper = new LambdaUpdateWrapper<>();
    updateWrapper.eq(ViewAppointment::getId, id);
    updateWrapper.set(ViewAppointment::getAppointmentStatus, status);
    service.update(updateWrapper);
    return Result.ok();
}

二、租约管理

租约管理共有五个接口需要实现,除此之外,还需实现一个定时任务,用于检查租约是否到期以及修改到期状态。下面逐一实现!!

image.png

首先在LeaseAgreementController中注入LeaseAgreementService,如下

@Tag(name = "租约管理")
@RestController
@RequestMapping("/admin/agreement")
public class LeaseAgreementController {

    @Autowired
    private LeaseAgreementService service;
}

2.1 保存或更新租约信息

LeaseAgreementController中增加如下内容

@Operation(summary = "保存或修改租约信息")
@PostMapping("saveOrUpdate")
public Result saveOrUpdate(@RequestBody LeaseAgreement leaseAgreement) {
    service.saveOrUpdate(leaseAgreement);
    return Result.ok();
}

2.2 根据条件分页查询租约列表

  • 查看请求和响应的数据结构

    • 请求数据结构

      • currentsize为分页相关参数,分别表示当前所处页面每个页面的记录数

      • AgreementQueryVo为公寓的查询条件,详细结构如下:

        @Data
        @Schema(description = "租约查询实体")
        public class AgreementQueryVo {
        
            @Schema(description = "公寓所处省份id")
            private Long provinceId;
        
            @Schema(description = "公寓所处城市id")
            private Long cityId;
        
            @Schema(description = "公寓所处区域id")
            private Long districtId;
        
            @Schema(description = "公寓id")
            private Long apartmentId;
        
            @Schema(description = "房间号")
            private String roomNumber;
        
            @Schema(description = "用户姓名")
            private String name;
        
            @Schema(description = "用户手机号码")
            private String phone;
        }
        
    • 响应数据结构

      单个租约信息的结构可查看com.mytest.lease.web.admin.vo.agreement.AgreementVo,内容如下:

      @Data
      @Schema(description = "租约信息")
      public class AgreementVo extends LeaseAgreement {
      
          @Schema(description = "签约公寓信息")
          private ApartmentInfo apartmentInfo;
      
          @Schema(description = "签约房间信息")
          private RoomInfo roomInfo;
      
          @Schema(description = "支付方式")
          private PaymentType paymentType;
      
          @Schema(description = "租期")
          private LeaseTerm leaseTerm;
      }
      
  • 编写Controller层逻辑

    LeaseAgreementController中增加如下内容

    @Operation(summary = "根据条件分页查询租约列表")
    @GetMapping("page")
    public Result<IPage<AgreementVo>> page(@RequestParam long current, @RequestParam long size, AgreementQueryVo queryVo) {
        IPage<AgreementVo> page = new Page<>(current, size);
        IPage<AgreementVo> list = service.pageAgreementByQuery(page, queryVo);
        return Result.ok(list);
    }
    
  • 编写Service层逻辑

    • LeaseAgreementService中增加如下内容

      IPage<AgreementVo> pageAgreementByQuery(IPage<AgreementVo> page, AgreementQueryVo queryVo);
      
    • LeaseAgreementServiceImpl中增加如下内容

      @Override
      public IPage<AgreementVo> pageAgreementByQuery(IPage<AgreementVo> page, AgreementQueryVo queryVo) {
      
          return leaseAgreementMapper.pageAgreementByQuery(page, queryVo);
      }
      
  • 编写Mapper层逻辑

    • LeaseAgreementMapper中增加如下内容

      IPage<AgreementVo> pageAgreementByQuery(IPage<AgreementVo> page, AgreementQueryVo queryVo);
      
    • LeaseAgreementMapper.xml中增加如下内容

      <resultMap id="agreementVoMap" type="com.mytest.lease.web.admin.vo.agreement.AgreementVo" autoMapping="true">
          <id property="id" column="id"/>
          <association property="apartmentInfo" javaType="com.mytest.lease.model.entity.ApartmentInfo" autoMapping="true">
              <id property="id" column="apartment_id"/>
              <result property="name" column="apartment_name"/>
          </association>
          <association property="roomInfo" javaType="com.mytest.lease.model.entity.RoomInfo" autoMapping="true">
              <id property="id" column="room_id"/>
          </association>
          <association property="paymentType" javaType="com.mytest.lease.model.entity.PaymentType" autoMapping="true">
              <id property="id" column="payment_type_id"/>
              <result property="name" column="payment_type_name"/>
          </association>
          <association property="leaseTerm" javaType="com.mytest.lease.model.entity.LeaseTerm" autoMapping="true">
              <id property="id" column="lease_term_id"/>
          </association>
      </resultMap>
      
      <select id="pageAgreementByQuery" resultMap="agreementVoMap">
          select la.id,
                 la.phone,
                 la.name,
                 la.identification_number,
                 la.lease_start_date,
                 la.lease_end_date,
                 la.rent,
                 la.deposit,
                 la.status,
                 la.source_type,
                 la.additional_info,
                 ai.id   apartment_id,
                 ai.name apartment_name,
                 ai.district_id,
                 ai.district_name,
                 ai.city_id,
                 ai.city_name,
                 ai.province_id,
                 ai.province_name,
                 ri.id   room_id,
                 ri.room_number,
                 pt.id   payment_type_id,
                 pt.name payment_type_name,
                 pt.pay_month_count,
                 lt.id   lease_term_id,
                 lt.month_count,
                 lt.unit
          from  lease_agreement la
                   left join
                apartment_info ai
               on la.apartment_id = ai.id and ai.is_deleted=0
                   left join
                room_info ri
               on la.room_id = ri.id and ri.is_deleted=0
                   left join
                payment_type pt
               on la.payment_type_id = pt.id and pt.is_deleted=0
                   left join
                lease_term lt
               on la.lease_term_id = lt.id and lt.is_deleted=0
              <where>
                  la.is_deleted = 0
                  <if test="queryVo.provinceId != null">
                      and ai.province_id = #{queryVo.provinceId}
                  </if>
                  <if test="queryVo.cityId != null">
                      and ai.city_id = #{queryVo.cityId}
                  </if>
                  <if test="queryVo.districtId != null">
                      and ai.district_id = #{queryVo.districtId}
                  </if>
                  <if test="queryVo.apartmentId != null">
                      and la.apartment_id = #{queryVo.apartmentId}
                  </if>
                  <if test="queryVo.roomNumber != null and queryVo.roomNumber != ''">
                      and ri.room_number like concat('%',#{queryVo.roomNumber},'%')
                  </if>
                  <if test="queryVo.name != null and queryVo.name != ''">
                      and la.name like concat('%',#{queryVo.name},'%')
                  </if>
                  <if test="queryVo.phone != null and queryVo.phone != ''">
                      and la.phone like concat('%',#{queryVo.phone},'%')
                  </if>
              </where>
      </select>
      

2.3 根据ID查询租约信息

  • 编写Controller层逻辑

    LeaseAgreementController中增加如下内容

    @Operation(summary = "根据id查询租约信息")
    @GetMapping(name = "getById")
    public Result<AgreementVo> getById(@RequestParam Long id) {
        AgreementVo apartment = service.getAgreementById(id);
        return Result.ok(apartment);
    }
    
  • 编写Service层逻辑

    • LeaseAgreementService中增加如下内容

      AgreementVo getAgreementById(Long id);
      
    • LeaseAgreementServiceImpl中增加如下内容

      @Override
      public AgreementVo getAgreementById(Long id) {
      
          //1.查询租约信息
          LeaseAgreement leaseAgreement = leaseAgreementMapper.selectById(id);
      
          //2.查询公寓信息
          ApartmentInfo apartmentInfo = apartmentInfoMapper.selectById(leaseAgreement.getApartmentId());
      
          //3.查询房间信息
          RoomInfo roomInfo = roomInfoMapper.selectById(leaseAgreement.getRoomId());
      
          //4.查询支付方式
          PaymentType paymentType = paymentTypeMapper.selectById(leaseAgreement.getPaymentTypeId());
      
          //5.查询租期
          LeaseTerm leaseTerm = leaseTermMapper.selectById(leaseAgreement.getLeaseTermId());
      
          AgreementVo adminAgreementVo = new AgreementVo();
          BeanUtils.copyProperties(leaseAgreement, adminAgreementVo);
          adminAgreementVo.setApartmentInfo(apartmentInfo);
          adminAgreementVo.setRoomInfo(roomInfo);
          adminAgreementVo.setPaymentType(paymentType);
          adminAgreementVo.setLeaseTerm(leaseTerm);
          return adminAgreementVo;
      }
      

2.4 根据ID删除租约信息

LeaseAgreementController中增加如下内容

@Operation(summary = "根据id删除租约信息")
@DeleteMapping("removeById")
public Result removeById(@RequestParam Long id) {
    service.removeById(id);
    return Result.ok();
}

2.5 根据ID更新租约状态

后台管理系统需要多个修改租约状态的接口,例如修改租约状态为已取消修改租约状态为已退租等等。为省去重复编码,此处将多个接口合并为一个如下,注意,在生产中应避免这样的写法。

LeaseAgreementController中增加如下内容

@Operation(summary = "根据id更新租约状态")
@PostMapping("updateStatusById")
public Result updateStatusById(@RequestParam Long id, @RequestParam LeaseStatus status) {
    LambdaUpdateWrapper<LeaseAgreement> updateWrapper = new LambdaUpdateWrapper<>();
    updateWrapper.eq(LeaseAgreement::getId, id);
    updateWrapper.set(LeaseAgreement::getStatus, status);
    service.update(updateWrapper);
    return Result.ok();
}

2.6 定时检查租约状态

本节内容是通过定时任务定时检查租约是否到期。SpringBoot内置了定时任务,具体实现如下。

  • 启用Spring Boot定时任务

    在SpringBoot启动类上增加@EnableScheduling注解,如下

    @SpringBootApplication
    @EnableScheduling
    public class AdminWebApplication {
        public static void main(String[] args) {
            SpringApplication.run(AdminWebApplication.class, args);
        }
    }
    
  • 编写定时逻辑

    web-admin模块下创建com.mytest.lease.web.admin.schedule.ScheduledTasks类,内容如下

    @Component
    public class ScheduledTasks {
    
        @Autowired
        private LeaseAgreementService leaseAgreementService;
    
        @Scheduled(cron = "0 0 0 * * *")
        public void checkLeaseStatus() {
    
            LambdaUpdateWrapper<LeaseAgreement> updateWrapper = new LambdaUpdateWrapper<>();
            Date now = new Date();
            updateWrapper.le(LeaseAgreement::getLeaseEndDate, now);
            updateWrapper.in(LeaseAgreement::getStatus, LeaseStatus.SIGNED, LeaseStatus.WITHDRAWING);
            updateWrapper.set(LeaseAgreement::getStatus, LeaseStatus.EXPIRED);
    
            leaseAgreementService.update(updateWrapper);
        }
    }
    

    知识点:

    SpringBoot中的cron表达式语法如下

      ┌───────────── second (0-59)
      │ ┌───────────── minute (0 - 59)
      │ │ ┌───────────── hour (0 - 23)
      │ │ │ ┌───────────── day of the month (1 - 31)
      │ │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
      │ │ │ │ │ ┌───────────── day of the week (1 - 7 or SUN-SAT)
      │ │ │ │ │ │ ┌───────────── year (空,或者1970-2099)        
      │ │ │ │ │ │ │ 
      *  *  * *  *  *  * 
      
    除了数字之外,还可以使用特殊字符来指定特定的值:
    
    *:代表所有可能的值,例如 * 在分钟字段中代表每分钟。
    ,:用于指定多个值,例如 1,5,10 在分钟字段中代表第1、第5和第10分钟。
    -:用于指定一个范围,例如 1-5 在分钟字段中代表从第1分钟到第5分钟。
    /:用于指定增量值,例如 */5 在分钟字段中代表每隔5分钟。
    ?:在日期和星期字段中,用于指示该字段不被指定。
    L:在日期字段中,用于指定最后一天,例如 L 在日期字段中代表每月最后一天。  
      
    0/5 * * * * ? 每隔5秒执行一次 【/间隔】 【?用日期和星期,一方放弃】
    0 */1 * * * ? 每隔1分钟执行一次
    0 0 5-15 * * ? 每天5-15每个整点执行一遍
    0 0/3 * * * ? 每三分钟触发一次
    0 0-5 14 * * ? 在每天下午2点到下午2:05期间的每1分钟触发 
    0 0/5 14 * * ? 在每天下午2点到下午2:55期间的每5分钟触发
    0 0/5 14,18 * * ? 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发 【,多个节点】
    0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时
    0 0 10,14,16 * * ? 每天上午10点,下午2点,40 0 12 ? * WED 表示每个星期三中午120 0 17 ? * TUES,THUR,SAT 每周二、四、六下午五点
    0 10,44 14 ? 3 WED 每年三月的星期三的下午2:102:44触发 
    0 15 10 ? * MON-FRI 周一至周五的上午10:15触发
    0 0 23 L * ? 每月最后一天23点执行一次
    0 15 10 L * ? 每月最后一日的上午10:15触发 
    0 15 10 ? * 6L 每月的最后一个星期五上午10:15触发 
    0 15 10 * * ? 2005 2005年的每天上午10:15触发 
    0 15 10 ? * 6L 2002-2005 2002年至2005年的每月的最后一个星期五上午10:15触发 
    0 15 10 ? * 6#3 每月的第三个星期五上午10:15触发
    
    
    "30 * * * * ?" 每半分钟触发任务
    "30 10 * * * ?" 每小时的1030秒触发任务
    "30 10 1 * * ?" 每天11030秒触发任务
    "30 10 1 20 * ?" 每月2011030秒触发任务
    "30 10 1 20 10 ? *" 每年102011030秒触发任务
    "30 10 1 20 10 ? 2011" 2011102011030秒触发任务
    "30 10 1 ? 10 * 2011" 201110月每天11030秒触发任务
    "30 10 1 ? 10 SUN 2011" 201110月每周日11030秒触发任务
    "15,30,45 * * * * ?" 每15秒,30秒,45秒时触发任务
    "15-45 * * * * ?" 1545秒内,每秒都触发任务
    "15/5 * * * * ?" 每分钟的每15秒开始触发,每隔5秒触发一次
    "15-30/5 * * * * ?" 每分钟的15秒到30秒之间开始触发,每隔5秒触发一次
    "0 0/3 * * * ?" 每小时的第00秒开始,每三分钟触发一次
    "0 15 10 ? * MON-FRI" 星期一到星期五的10150秒触发任务
    "0 15 10 L * ?" 每个月最后一天的10150秒触发任务
    "0 15 10 LW * ?" 每个月最后一个工作日的10150秒触发任务
    "0 15 10 ? * 5L" 每个月最后一个星期四的10150秒触发任务
    "0 15 10 ? * 5#3" 每个月第三周的星期四的10150秒触发任务
    

参考: developer.aliyun.com/article/849…