尚庭公寓-小程序接口

7. 项目开发

7.4 移动端后端开发

7.4.1 项目初始配置

7.4.1.1 SpringBoot配置

1. 创建application.yml文件

web-app模块src/main/resources目录下创建application.yml配置文件,内容如下:

yaml 复制代码
server:
  port: 8081

2. 创建SpringBoot启动类

web-app模块 下创建com.atguigu.lease.AppWebApplication类,内容如下:

java 复制代码
@SpringBootApplication
public class AppWebApplication {
    public static void main(String[] args) {
        SpringApplication.run(AppWebApplication.class);
    }
}
7.4.1.2 Mybatis-Plus配置

在web-admin模块的application.yml文件增加如下内容:

yaml 复制代码
spring:
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    url: jdbc:mysql://<hostname>:<port>/<database>?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=GMT%2b8
    username: <username>
    password: <password>
    hikari:
      connection-test-query: SELECT 1 # 自动检测连接
      connection-timeout: 60000 #数据库连接超时时间,默认30秒
      idle-timeout: 500000 #空闲连接存活最大时间,默认600000(10分钟)
      max-lifetime: 540000 #此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认1800000即30分钟
      maximum-pool-size: 12 #连接池最大连接数,默认是10
      minimum-idle: 10 #最小空闲连接数量
      pool-name: SPHHikariPool # 连接池名称
  jackson:
      time-zone: GMT+8

#用于打印框架生成的sql语句,便于调试
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

注意 :需根据实际情况修改hostnameportdatabaseusernamepassword

7.4.1.3 Knife4j配置

1. 配置类

web-app模块 下创建com.atguigu.lease.web.app.custom.config.Knife4jConfiguration类,内容如下:

java 复制代码
@Configuration
public class Knife4jConfiguration {

    @Bean
    public OpenAPI customOpenAPI() {
        return new OpenAPI()
                .info(new Info()
                        .title("APP接口")
                        .version("1.0")
                        .description("用户端APP接口")
                        .termsOfService("http://doc.xiaominfo.com")
                        .license(new License().name("Apache 2.0")
                                .url("http://doc.xiaominfo.com")));
    }
    

    @Bean
    public GroupedOpenApi loginAPI() {
        return GroupedOpenApi.builder().group("登录信息").
                pathsToMatch("/app/login/**", "/app/info").
                build();
    }

    @Bean
    public GroupedOpenApi personAPI() {
        return GroupedOpenApi.builder().group("个人信息").
                pathsToMatch(
                        "/app/history/**",
                        "/app/appointment/**",
                        "/app/agreement/**"
                ).
                build();
    }

    @Bean
    public GroupedOpenApi lookForRoomAPI() {
        return GroupedOpenApi.builder().group("找房信息").
                pathsToMatch(
                        "/app/apartment/**",
                        "/app/room/**",
                        "/app/payment/**",
                        "/app/region/**",
                        "/app/term/**"
                ).
                build();
    }
}

2. application.yml配置文件

在application.yml文件中增加如下配置:

yml 复制代码
springdoc:
  default-flat-param-object: true
7.2.1.4 导入基础代码

导入的代码和目标位置如下:

导入代码 模块 包名/路径 说明
mapper接口 web-app com.atguigu.lease.web.app.mapper
mapper xml web-app src/main/resources/mapper
service web-app com.atguigu.lease.web.app.service
serviceImpl web-app com.atguigu.lease.web.app.service.impl
7.2.1.5 导入接口定义代码

需要导入的代码和目标位置如下:

导入代码 模块 包名/路径 说明
controller web-app com.atguigu.lease.web.app.controller
vo web-app com.atguigu.lease.web.app.vo View Object,用于封装或定义接口接受及返回的数据结构
7.2.1.6 启动项目

由于common模块 中配置了MinioClient 这个Bean,并且web-app模块 依赖于common模块 ,因此在启动AppWebApplication 时,SpringBoot会创建一个MinioClient实例,但是由于web-app模块 的application.yml文件中并未提供MinioClient所需的参数(web-app模块暂时不需要使用MinioClient),因此MinioClient实例的创建会失败。

为解决该问题,可以为MinioClient的配置类增加一个条件注解@ConditionalOnProperty,如下,该注解表达的含义是只有当minio.endpoint属性存在时,该配置类才会生效。

java 复制代码
@Configuration
@EnableConfigurationProperties(MinioProperties.class)
@ConditionalOnProperty(name = "minio.endpoint")
public class MinioConfiguration {

    @Autowired
    private MinioProperties properties;

    @Bean
    public MinioClient minioClient() {
        return MinioClient.builder().endpoint(properties.getEndpoint()).credentials(properties.getAccessKey(), properties.getSecretKey()).build();
    }
}

完成上述配置后,便可启动SpringBoot项目,并访问接口文档了,Knife4j文档的url为:http://localhost:8081/doc.html

7.4.2 登录管理

7.4.2.1 登陆流程

移动端的具体登录流程如下图所示

根据上述登录流程,可分析出,登录管理共需三个接口,分别是获取短信验证码登录查询登录用户的个人信息 。除此之外,同样需要编写HandlerInterceptor来为所有受保护的接口增加验证JWT的逻辑。

7.4.2.2 接口开发

首先在LoginController中注入LoginService,如下

java 复制代码
@RestController
@Tag(name = "登录管理")
@RequestMapping("/app/")
public class LoginController {

    @Autowired
    private LoginService service;
}
1. 获取短信验证码

该接口需向登录手机号码发送短信验证码,各大云服务厂商都提供短信服务,本项目使用阿里云完成短信验证码功能,下面介绍具体配置。

  • 配置短信服务

    • 开通短信服务

      • 阿里云官网,注册阿里云账号,并按照指引,完成实名认证(不认证,无法购买服务)

      • 找到短信服务,选择免费开通

      • 进入短信服务控制台,选择快速学习和测试

      • 找到发送测试 下的API发送测试,绑定测试用的手机号(只有绑定的手机号码才能收到测试短信),然后配置短信签名和短信模版,这里选择**[专用]测试签名/模版**。

    • 创建AccessKey

      云账号 AccessKey 是访问阿里云 API 的密钥,没有AccessKey无法调用短信服务。点击页面右上角的头像,选择AccessKey管理 ,然后创建AccessKey

  • 配置所需依赖

    如需调用阿里云的短信服务,需使用其提供的SDK,具体可参考官方文档

    common模块的pom.xml文件中增加如下内容

    xml 复制代码
    <dependency>
        <groupId>com.aliyun</groupId>
        <artifactId>dysmsapi20170525</artifactId>
    </dependency>
  • 配置发送短信客户端

    • application.yml中增加如下内容

      yml 复制代码
      aliyun:
        sms:
          access-key-id: <access-key-id>
          access-key-secret: <access-key-secret>
          endpoint: dysmsapi.aliyuncs.com

      注意

      上述access-key-idaccess-key-secret需根据实际情况进行修改。

    • common模块 中创建com.atguigu.lease.common.sms.AliyunSMSProperties类,内容如下

      java 复制代码
      @Data
      @ConfigurationProperties(prefix = "aliyun.sms")
      public class AliyunSMSProperties {
      
          private String accessKeyId;
      
          private String accessKeySecret;
      
          private String endpoint;
      }
    • common模块 中创建com.atguigu.lease.common.sms.AliyunSmsConfiguration类,内容如下

      java 复制代码
      @Configuration
      @EnableConfigurationProperties(AliyunSMSProperties.class)
      @ConditionalOnProperty(name = "aliyun.sms.endpoint")
      public class AliyunSMSConfiguration {
      
          @Autowired
          private AliyunSMSProperties properties;
      
          @Bean
          public Client smsClient() {
              Config config = new Config();
              config.setAccessKeyId(properties.getAccessKeyId());
              config.setAccessKeySecret(properties.getAccessKeySecret());
              config.setEndpoint(properties.getEndpoint());
              try {
                  return new Client(config);
              } catch (Exception e) {
                  throw new RuntimeException(e);
              }
      
          }
      }
  • 配置Redis连接参数

    yaml 复制代码
    spring: 
      data:
        redis:
          host: 192.168.10.101
          port: 6379
          database: 0
  • 编写Controller层逻辑

    LoginController中增加如下内容

    java 复制代码
    @GetMapping("login/getCode")
    @Operation(summary = "获取短信验证码")
    public Result getCode(@RequestParam String phone) {
        service.getSMSCode(phone);
        return Result.ok();
    }
  • 编写Service层逻辑

    • 编写发送短信逻辑

      • SmsService中增加如下内容

        java 复制代码
        void sendCode(String phone, String verifyCode);
      • SmsServiceImpl中增加如下内容

        java 复制代码
        @Override
        public void sendCode(String phone, String code) {
        
            SendSmsRequest smsRequest = new SendSmsRequest();
            smsRequest.setPhoneNumbers(phone);
            smsRequest.setSignName("阿里云短信测试");
            smsRequest.setTemplateCode("SMS_154950909");
            smsRequest.setTemplateParam("{\"code\":\"" + code + "\"}");
            try {
                client.sendSms(smsRequest);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    • 编写生成随机验证码逻辑

      common模块 中创建com.atguigu.lease.common.utils.VerifyCodeUtil类,内容如下

      java 复制代码
      public class VerifyCodeUtil {
          public static String getVerifyCode(int length) {
              StringBuilder builder = new StringBuilder();
              Random random = new Random();
              for (int i = 0; i < length; i++) {
                  builder.append(random.nextInt(10));
              }
              return builder.toString();
          }
      }
    • 编写获取短信验证码逻辑

      • LoginServcie中增加如下内容

        java 复制代码
        void getSMSCode(String phone);
      • LoginServiceImpl中增加如下内容

        java 复制代码
        @Override
        public void getSMSCode(String phone) {
        
            //1. 检查手机号码是否为空
            if (!StringUtils.hasText(phone)) {
                throw new LeaseException(ResultCodeEnum.APP_LOGIN_PHONE_EMPTY);
            }
        
            //2. 检查Redis中是否已经存在该手机号码的key
            String key = RedisConstant.APP_LOGIN_PREFIX + phone;
            boolean hasKey = redisTemplate.hasKey(key);
            if (hasKey) {
                //若存在,则检查其存在的时间
                Long expire = redisTemplate.getExpire(key, TimeUnit.SECONDS);
                if (RedisConstant.APP_LOGIN_CODE_TTL_SEC - expire < RedisConstant.APP_LOGIN_CODE_RESEND_TIME_SEC) {
                    //若存在时间不足一分钟,响应发送过于频繁
                    throw new LeaseException(ResultCodeEnum.APP_SEND_SMS_TOO_OFTEN);
                }
            }
        
            //3.发送短信,并将验证码存入Redis
            String verifyCode = VerifyCodeUtil.getVerifyCode(6);
            smsService.sendCode(phone, verifyCode);
            redisTemplate.opsForValue().set(key, verifyCode, RedisConstant.APP_LOGIN_CODE_TTL_SEC, TimeUnit.SECONDS);
        }

        注意

        需要注意防止频繁发送短信。

2. 登录和注册接口
  • 登录注册校验逻辑

    • 前端发送手机号码phone和接收到的短信验证码code到后端。
    • 首先校验phonecode是否为空,若为空,直接响应手机号码为空或者验证码为空,若不为空则进入下步判断。
    • 根据phone从Redis中查询之前保存的验证码,若查询结果为空,则直接响应验证码已过期 ,若不为空则进入下一步判断。
    • 比较前端发送的验证码和从Redis中查询出的验证码,若不同,则直接响应验证码错误,若相同则进入下一步判断。
    • 使用phone从数据库中查询用户信息,若查询结果为空,则创建新用户,并将用户保存至数据库,然后进入下一步判断。
    • 判断用户是否被禁用,若被禁,则直接响应账号被禁用,否则进入下一步。
    • 创建JWT并响应给前端。
  • 接口实现

    • 编写Controller层逻辑

      LoginController中增加如下内容

      java 复制代码
      @PostMapping("login")
      @Operation(summary = "登录")
      public Result<String> login(LoginVo loginVo) {
          String token = service.login(loginVo);
          return Result.ok(token);
      }
    • 编写Service层逻辑

      • LoginService中增加如下内容

        java 复制代码
        String login(LoginVo loginVo);
      • LoginServiceImpl总增加如下内容

        java 复制代码
        @Override
        public String login(LoginVo loginVo) {
        
            //1.判断手机号码和验证码是否为空
            if (!StringUtils.hasText(loginVo.getPhone())) {
                throw new LeaseException(ResultCodeEnum.APP_LOGIN_PHONE_EMPTY);
            }
        
            if (!StringUtils.hasText(loginVo.getCode())) {
                throw new LeaseException(ResultCodeEnum.APP_LOGIN_CODE_EMPTY);
            }
        
            //2.校验验证码
            String key = RedisConstant.APP_LOGIN_PREFIX + loginVo.getPhone();
            String code = redisTemplate.opsForValue().get(key);
            if (code == null) {
                throw new LeaseException(ResultCodeEnum.APP_LOGIN_CODE_EXPIRED);
            }
        
            if (!code.equals(loginVo.getCode())) {
                throw new LeaseException(ResultCodeEnum.APP_LOGIN_CODE_ERROR);
            }
        
            //3.判断用户是否存在,不存在则注册(创建用户)
            LambdaQueryWrapper<UserInfo> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(UserInfo::getPhone, loginVo.getPhone());
            UserInfo userInfo = userInfoService.getOne(queryWrapper);
            if (userInfo == null) {
                userInfo = new UserInfo();
                userInfo.setPhone(loginVo.getPhone());
                userInfo.setStatus(BaseStatus.ENABLE);
                userInfo.setNickname("用户-"+loginVo.getPhone().substring(6));
                userInfoService.save(userInfo);
            }
        
            //4.判断用户是否被禁
            if (userInfo.getStatus().equals(BaseStatus.DISABLE)) {
                throw new LeaseException(ResultCodeEnum.APP_ACCOUNT_DISABLED_ERROR);
            }
        
            //5.创建并返回TOKEN
            return JwtUtil.createToken(userInfo.getId(), loginVo.getPhone());
        }
    • 编写HandlerInterceptor

      • 编写AuthenticationInterceptor

        web-app模块 创建com.atguigu.lease.web.app.custom.interceptor.AuthenticationInterceptor,内容如下

        java 复制代码
        @Component
        public class AuthenticationInterceptor implements HandlerInterceptor {
        
            @Override
            public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
                String token = request.getHeader("access-token");
        
                Claims claims = JwtUtil.parseToken(token);
                Long userId = claims.get("userId", Long.class);
                String username = claims.get("username", String.class);
                LoginUserHolder.setLoginUser(new LoginUser(userId, username));
        
                return true;
            }
        
            @Override
            public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
                LoginUserHolder.clear();
            }
        }
      • 注册AuthenticationInterceptor

        web-app模块 创建com.atguigu.lease.web.app.custom.config.WebMvcConfiguration,内容如下

        java 复制代码
        @Configuration
        public class WebMvcConfiguration implements WebMvcConfigurer {
        
            @Autowired
            private AuthenticationInterceptor authenticationInterceptor;
        
            @Override
            public void addInterceptors(InterceptorRegistry registry) {
                registry.addInterceptor(this.authenticationInterceptor).addPathPatterns("/app/**").excludePathPatterns("/app/login/**");
            }
        }
  • Knife4j增加认证相关配置

    在增加上述拦截器后,为方便继续调试其他接口,可以获取一个长期有效的Token,将其配置到Knife4j的全局参数中。

3.查询登录用户的个人信息
  • 查看响应数据结构

    查看web-app模块 下的com.atguigu.lease.web.app.vo.user.UserInfoVo,内容如下

    java 复制代码
    @Schema(description = "用户基本信息")
    @Data
    @AllArgsConstructor
    public class UserInfoVo {
    
        @Schema(description = "用户昵称")
        private String nickname;
    
        @Schema(description = "用户头像")
        private String avatarUrl;
    }
  • 编写Controller层逻辑

    LoginController中增加如下内容

    java 复制代码
    @GetMapping("info")
    @Operation(summary = "获取登录用户信息")
    public Result<UserInfoVo> info() {
        UserInfoVo info = service.getUserInfoById(LoginUserHolder.getLoginUser().getUserId());
        return Result.ok(info);
    }
  • 编写Service层逻辑

    • LoginService中增加如下内容

      java 复制代码
      UserInfoVo getUserInfoId(Long id);
    • LoginServiceImpl中增加如下内容

      java 复制代码
      @Override
      public UserInfoVo getUserInfoId(Long id) {
          UserInfo userInfo = userInfoService.getById(id);
          return new UserInfoVo(userInfo.getNickname(), userInfo.getAvatarUrl());
      }

7.4.3 找房

7.4.3.1 地区信息

对于找房模块,地区信息共需三个接口,分别是查询省份列表根据省份ID查询城市列表根据城市ID查询区县列表,具体实现如下

RegionController中增加如下内容

java 复制代码
@Tag(name = "地区信息")
@RestController
@RequestMapping("/app/region")
public class RegionController {

    @Autowired
    private ProvinceInfoService provinceInfoService;

    @Autowired
    private CityInfoService cityInfoService;

    @Autowired
    private DistrictInfoService districtInfoService;

    @Operation(summary="查询省份信息列表")
    @GetMapping("province/list")
    public Result<List<ProvinceInfo>> listProvince(){
        List<ProvinceInfo> list = provinceInfoService.list();
        return Result.ok(list);
    }

    @Operation(summary="根据省份id查询城市信息列表")
    @GetMapping("city/listByProvinceId")
    public Result<List<CityInfo>> listCityInfoByProvinceId(@RequestParam Long id){
        LambdaQueryWrapper<CityInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(CityInfo::getProvinceId,id);
        List<CityInfo> list = cityInfoService.list(queryWrapper);
        return Result.ok(list);
    }

    @GetMapping("district/listByCityId")
    @Operation(summary="根据城市id查询区县信息")
    public Result<List<DistrictInfo>> listDistrictInfoByCityId(@RequestParam Long id){
        LambdaQueryWrapper<DistrictInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(DistrictInfo::getCityId,id);
        List<DistrictInfo> list = districtInfoService.list(queryWrapper);
        return Result.ok(list);
    }
}
7.4.3.2 支付方式

对于找房模块,支付方式共需一个接口,即获取全部支付方式列表,具体实现如下

PaymentTypeController中增加如下内容

java 复制代码
@Tag(name = "支付方式接口")
@RestController
@RequestMapping("/app/payment")
public class PaymentTypeController {

    @Autowired
    private PaymentTypeService service;

    @Operation(summary = "获取全部支付方式列表")
    @GetMapping("list")
    public Result<List<PaymentType>> list() {
        List<PaymentType> list = service.list();
        return Result.ok(list);
    }
}
7.4.3.4 房间信息

房间信息共需三个接口,分别是根据条件分页查询房间列表根据ID查询房间详细信息根据公寓ID分页查询房间列表,下面逐一实现

首先在RoomController中注入RoomInfoService,如下

java 复制代码
@Tag(name = "房间信息")
@RestController
@RequestMapping("/app/room")
public class RoomController {

    @Autowired
    RoomInfoService roomInfoService;
}
1. 根据条件分页查询房间列表
  • 查看请求和响应的数据结构

    • 请求数据结构

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

      • RoomQueryVo为房间的查询条件,详细结构如下:

        java 复制代码
        @Data
        @Schema(description = "房间查询实体")
        public class RoomQueryVo {
        
            @Schema(description = "省份Id")
            private Long provinceId;
        
            @Schema(description = "城市Id")
            private Long cityId;
        
            @Schema(description = "区域Id")
            private Long districtId;
        
            @Schema(description = "最小租金")
            private BigDecimal minRent;
        
            @Schema(description = "最大租金")
            private BigDecimal maxRent;
        
            @Schema(description = "支付方式")
            private Long paymentTypeId;
        
            @Schema(description = "价格排序方式", allowableValues = {"desc", "asc"})
            private String orderType;
        
        }
    • 响应数据结构

      单个房间信息记录可查看com.atguigu.lease.web.app.vo.room.RoomItemVo,内容如下:

      java 复制代码
      @Schema(description = "APP房间列表实体")
      @Data
      public class RoomItemVo {
      
          @Schema(description = "房间id")
          private Long id;
      
          @Schema(description = "房间号")
          private String roomNumber;
      
          @Schema(description = "租金(元/月)")
          private BigDecimal rent;
      
          @Schema(description = "房间图片列表")
          private List<GraphVo> graphVoList;
      
          @Schema(description = "房间标签列表")
          private List<LabelInfo> labelInfoList;
      
          @Schema(description = "房间所属公寓信息")
          private ApartmentInfo apartmentInfo;
      }
  • 编写Controller层逻辑

    RoomController中增加如下内容

    java 复制代码
    @Operation(summary = "分页查询房间列表")
    @GetMapping("pageItem")
    public Result<IPage<RoomItemVo>> pageItem(@RequestParam long current, @RequestParam long size, RoomQueryVo queryVo) {
        Page<RoomItemVo> page = new Page<>(current, size);
        IPage<RoomItemVo> list = roomInfoService.pageRoomItemByQuery(page, queryVo);
        return Result.ok(list);
    }
  • 编写Service层逻辑

    • RoomInfoService中增加如下内容

      java 复制代码
      IPage<RoomItemVo> pageRoomItemByQuery(Page<RoomItemVo> page, RoomQueryVo queryVo);
    • RoomInfoServiceImpl中增加如下内容

      java 复制代码
      @Override
      public IPage<RoomItemVo> pageRoomItemByQuery(Page<RoomItemVo> page, RoomQueryVo queryVo) {
          return roomInfoMapper.pageRoomItemByQuery(page, queryVo);
      }
  • 编写Mapper层逻辑

    • RoomInfoMapper中增加如下内容

      java 复制代码
      IPage<RoomItemVo> pageRoomItemByQuery(Page<RoomItemVo> page, RoomQueryVo queryVo);
    • RoomInfoMapper中增加如下内容

      xml 复制代码
      <!-- result map -->
      <resultMap id="RoomItemVoMap" type="com.atguigu.lease.web.app.vo.room.RoomItemVo" autoMapping="true">
          <id column="id" property="id"/>
          <!--映射公寓信息-->
          <association property="apartmentInfo" javaType="com.atguigu.lease.model.entity.ApartmentInfo"
                       autoMapping="true">
              <id column="id" property="id"/>
          </association>
          <!--映射图片列表-->
          <collection property="graphVoList" ofType="com.atguigu.lease.web.app.vo.graph.GraphVo"
                      select="selectGraphVoListByRoomId" column="id"/>
          <!--映射标签列表-->
          <collection property="labelInfoList" ofType="com.atguigu.lease.model.entity.LabelInfo"
                      select="selectLabelInfoListByRoomId" column="id"/>
      </resultMap>
      
      <!-- 根据条件查询房间列表 -->
      <select id="pageItem" resultMap="RoomItemVoMap">
          select
              ri.id,
              ri.room_number,
              ri.rent,
              ai.id apartment_id,
              ai.name,
              ai.introduction,
              ai.district_id,
              ai.district_name,
              ai.city_id,
              ai.city_name,
              ai.province_id,
              ai.province_name,
              ai.address_detail,
              ai.latitude,
              ai.longitude,
              ai.phone,
              ai.is_release
          from room_info ri
          left join apartment_info ai on ri.apartment_id = ai.id and ai.is_deleted = 0
          <where>
              ri.is_deleted = 0
              and ri.is_release = 1
              and ri.id not in(
                  select room_id
                  from lease_agreement
                  where is_deleted = 0
                  and status in(2,5))
              <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.minRent != null and queryVo.maxRent != null">
                  and (ri.rent &gt;= #{queryVo.minRent} and ri.rent &lt;= #{queryVo.maxRent})
              </if>
              <if test="queryVo.paymentTypeId != null">
                  and ri.id in (
                  select
                  room_id
                  from room_payment_type
                  where is_deleted = 0
                  and payment_type_id = #{queryVo.paymentTypeId}
                  )
              </if>
          </where>
          <if test="queryVo.orderType == 'desc' or queryVo.orderType == 'asc'">
              order by ri.rent ${queryVo.orderType}
          </if>
      </select>
      
      <!-- 根据房间ID查询图片列表 -->
      <select id="selectGraphVoListByRoomId" resultType="com.atguigu.lease.web.app.vo.graph.GraphVo">
          select id,
                 name,
                 item_type,
                 item_id,
                 url
          from graph_info
          where is_deleted = 0
            and item_type = 2
            and item_id = #{id}
      </select>
      
      <!-- 根据公寓ID查询标签列表 -->
      <select id="selectLabelInfoListByRoomId" resultType="com.atguigu.lease.model.entity.LabelInfo">
          select id,
                 type,
                 name
          from label_info
          where is_deleted = 0
            and id in (select label_id
                       from room_label
                       where is_deleted = 0
                         and room_id = #{id})
      </select>

      知识点

      • xml文件<>的转义

        由于xml文件中的<>是特殊符号,需要转义处理。

        原符号 转义符号
        < &lt;
        > &gt;
      • Mybatis-Plus分页插件注意事项

        使用Mybatis-Plus的分页插件进行分页查询时,如果结果需要使用<collection>进行映射,只能使用**嵌套查询(Nested Select for Collection),而不能使用 嵌套结果映射(Nested Results for Collection)**。

        嵌套查询嵌套结果映射是Collection映射的两种方式,下面通过一个案例进行介绍

        例如有room_infograph_info两张表,其关系为一对多,如下

        现需要查询房间列表及其图片信息,期望返回的结果如下

        json 复制代码
        [
            {
                "id": 1,
                "number": 201,
                "rent": 2000,
                "graphList": [
                    {
                        "id": 1,
                        "url": "http://",
                        "roomId": 1
                    },
                    {
                        "id": 2,
                        "url": "http://",
                        "roomId": 1
                    }
                ]
            },
            {
                "id": 2,
                "number": 202,
                "rent": 3000,
                "graphList": [
                    {
                        "id": 3,
                        "url": "http://",
                        "roomId": 2
                    },
                    {
                        "id": 4,
                        "url": "http://",
                        "roomId": 2
                    }
                ]
            }
        ]

        为得到上述结果,可使用以下两种方式

        • 嵌套结果映射

          xml 复制代码
          <select id="selectRoomPage" resultMap="RoomPageMap">
              select ri.id room_id,
                     ri.number,
                     ri.rent,
              	   gi.id graph_id,
                     gi.url,
                     gi.room_id
              from room_info ri
             	left join graph_info gi on ri.id=gi.room_id
          </select>
          
          <resultMap id="RoomPageMap" type="RoomInfoVo" autoMapping="true">
              <id column="room_id" property="id"/>
              <collection property="graphInfoList" ofType="GraphInfo" autoMapping="true">
                  <id column="graph_id" property="id"/>
              </collection>
          </resultMap>

          这种方式的执行原理如下图所示

        • 嵌套查询

          xml 复制代码
          <select id="selectRoomPage" resultMap="RoomPageMap">
              select id,
                     number,
                     rent
              from room_info
          </select>
          
          <resultMap id="RoomPageMap" type="RoomInfoVo" autoMapping="true">
              <id column="id" property="id"/>
              <collection property="graphInfoList" ofType="GraphInfo" select="selectGraphByRoomId" 				 	column="id"/>
          </resultMap>
          
          <select id="selectGraphByRoomId" resultType="GraphInfo">
              select id,
                     url,
              	   room_id
              from graph_info
              where room_id = #{id}
          </select>

          这种方法使用两个独立的查询语句来获取一对多关系的数据。首先,Mybatis会执行主查询来获取room_info列表,然后对于每个room_info,Mybatis都会执行一次子查询来获取其对应的graph_info

        若现在使用MybatisPlus的分页插件进行分页查询,假如查询的内容是第1 页,每页2条记录,则上述两种方式的查询结果分别是

        • 嵌套结果映射

        • 嵌套查询

        显然嵌套结果映射的分页逻辑是存在问题的。

2. 根据ID查询房间详细信息
  • 查看响应数据结构

    查看web-app模块 下的com.atguigu.lease.web.app.vo.room.RoomDetailVo,内容如下

    java 复制代码
    @Data
    @Schema(description = "APP房间详情")
    public class RoomDetailVo extends RoomInfo {
    
        @Schema(description = "所属公寓信息")
        private ApartmentItemVo apartmentItemVo;
    
        @Schema(description = "图片列表")
        private List<GraphVo> graphVoList;
    
        @Schema(description = "属性信息列表")
        private List<AttrValueVo> attrValueVoList;
    
        @Schema(description = "配套信息列表")
        private List<FacilityInfo> facilityInfoList;
    
        @Schema(description = "标签信息列表")
        private List<LabelInfo> labelInfoList;
    
        @Schema(description = "支付方式列表")
        private List<PaymentType> paymentTypeList;
    
        @Schema(description = "杂费列表")
        private List<FeeValueVo> feeValueVoList;
    
        @Schema(description = "租期列表")
        private List<LeaseTerm> leaseTermList;
    
    }
  • 编写Controller层逻辑

    RoomController中增加如下内容

    java 复制代码
    @Operation(summary = "根据id获取房间的详细信息")
    @GetMapping("getDetailById")
    public Result<RoomDetailVo> getDetailById(@RequestParam Long id) {
        RoomDetailVo roomInfo = service.getDetailById(id);
        return Result.ok(roomInfo);
    }
  • 编写查询房间信息逻辑

    • 编写Service层逻辑

      • RoomInfoService中增加如下内容

        java 复制代码
        RoomDetailVo getDetailById(Long id);
      • RoomInfoServiceImpl中增加如下内容

        java 复制代码
        @Override
        public RoomDetailVo getDetailById(Long id) {
            //1.查询房间信息
            RoomInfo roomInfo = roomInfoMapper.selectById(id);
            if (roomInfo == null) {
                return null;
            }
            //2.查询图片
            List<GraphVo> graphVoList = graphInfoMapper.selectListByItemTypeAndId(ItemType.ROOM, id);
            //3.查询租期
            List<LeaseTerm> leaseTermList = leaseTermMapper.selectListByRoomId(id);
            //4.查询配套
            List<FacilityInfo> facilityInfoList = facilityInfoMapper.selectListByRoomId(id);
            //5.查询标签
            List<LabelInfo> labelInfoList = labelInfoMapper.selectListByRoomId(id);
            //6.查询支付方式
            List<PaymentType> paymentTypeList = paymentTypeMapper.selectListByRoomId(id);
            //7.查询基本属性
            List<AttrValueVo> attrValueVoList = attrValueMapper.selectListByRoomId(id);
            //8.查询杂费信息
            List<FeeValueVo> feeValueVoList = feeValueMapper.selectListByApartmentId(roomInfo.getApartmentId());
            //9.查询公寓信息
            ApartmentItemVo apartmentItemVo = apartmentInfoService.selectApartmentItemVoById(roomInfo.getApartmentId());
        
            RoomDetailVo roomDetailVo = new RoomDetailVo();
            BeanUtils.copyProperties(roomInfo, roomDetailVo);
        
            roomDetailVo.setApartmentItemVo(apartmentItemVo);
            roomDetailVo.setGraphVoList(graphVoList);
            roomDetailVo.setAttrValueVoList(attrValueVoList);
            roomDetailVo.setFacilityInfoList(facilityInfoList);
            roomDetailVo.setLabelInfoList(labelInfoList);
            roomDetailVo.setPaymentTypeList(paymentTypeList);
            roomDetailVo.setFeeValueVoList(feeValueVoList);
            roomDetailVo.setLeaseTermList(leaseTermList);
        
            return roomDetailVo;
        }
    • 编写Mapper层逻辑

      • 编写查询房间图片逻辑

        • GraphInfoMapper中增加如下内容

          java 复制代码
          List<GraphVo> selectListByItemTypeAndId(ItemType itemType, Long id);
        • GraphInfoMapper.xml增加如下内容

          xml 复制代码
          <select id="selectListByItemTypeAndId" resultType="com.atguigu.lease.web.app.vo.graph.GraphVo">
              select name,
                     url
              from graph_info
              where is_deleted = 0
                and item_type = #{itemType}
                and item_id = #{id}
          </select>
      • 编写查询房间可选租期逻辑

        • LeaseTermMapper中增加如下内容

          java 复制代码
          List<LeaseTerm> selectListByRoomId(Long id);
        • LeaseTermMapper.xml中增加如下内容

          xml 复制代码
          <select id="selectListByRoomId" resultType="com.atguigu.lease.model.entity.LeaseTerm">
              select id,
                     month_count,
                     unit
              from lease_term
              where is_deleted = 0
                and id in (select lease_term_id
                           from room_lease_term
                           where is_deleted = 0
                             and room_id = #{id})
          </select>
      • 编写查询房间配套逻辑

        • FacilityInfoMapper中增加如下内容

          java 复制代码
          List<FacilityInfo> selectListByRoomId(Long id);
        • FacilityInfoMapper.xml中增加如下内容

          xml 复制代码
          <select id="selectListByRoomId" resultType="com.atguigu.lease.model.entity.FacilityInfo">
              select id,
                     type,
                     name,
                     icon
              from facility_info
              where is_deleted = 0
                and id in (select facility_id
                           from room_facility
                           where is_deleted = 0
                             and room_id = #{id})
          </select>
      • 编写查询房间标签逻辑

        • LabelInfoMapper中增加如下内容

          java 复制代码
          List<LabelInfo> selectListByRoomId(Long id);
        • LabelInfoMapper.xml中增加如下内容

          xml 复制代码
          <select id="selectListByRoomId" resultType="com.atguigu.lease.model.entity.LabelInfo">
              select id,
                     type,
                     name
              from label_info
              where is_deleted = 0
                and id in (select label_id
                           from room_label
                           where is_deleted = 0
                             and room_id = #{id})
          </select>
      • 编写查询房间可选支付方式逻辑

        • PaymentTypeMapper中增加如下内容

          java 复制代码
          List<PaymentType> selectListByRoomId(Long id);
        • PaymentTypeMapper.xml中增加如下内容

          xml 复制代码
          <select id="selectListByRoomId" resultType="com.atguigu.lease.model.entity.PaymentType">
              select id,
                     name,
                     pay_month_count,
                     additional_info
              from payment_type
              where is_deleted = 0
                and id in (select payment_type_id
                           from room_payment_type
                           where is_deleted = 0
                             and room_id = #{id})
          </select>
      • 编写查询房间属性逻辑

        • AttrValueMapper中增加如下内容

          java 复制代码
          List<AttrValueVo> selectListByRoomId(Long id);
        • AttrValueMapper.xml中增加如下内容

          xml 复制代码
          <select id="selectListByRoomId" resultType="com.atguigu.lease.web.app.vo.attr.AttrValueVo">
              select av.id,
                     av.name,
                     av.attr_key_id,
                     ak.name attr_key_name
              from attr_value av
                       left join attr_key ak on av.attr_key_id = ak.id and ak.is_deleted = 0
              where av.is_deleted = 0
                and av.id in (select attr_value_id
                              from room_attr_value
                              where is_deleted = 0
                                and room_id = #{id})
          </select>
      • 编写查询房间杂费逻辑

        • FeeValueMapper中增加如下内容

          java 复制代码
          List<FeeValueVo> selectListByApartmentId(Long id);
        • FeeValueMapper.xml中增加如下内容

          xml 复制代码
          <select id="selectListByApartmentId" resultType="com.atguigu.lease.web.app.vo.fee.FeeValueVo">
              select fv.id,
                     fv.name,
                     fv.unit,
                     fv.fee_key_id,
                     fk.name fee_key_name
              from fee_value fv
                       left join fee_key fk on fv.fee_key_id = fk.id and fk.is_deleted = 0
              where fv.is_deleted = 0
                and fv.id in (select fee_value_id
                              from apartment_fee_value
                              where is_deleted = 0
                                and apartment_id = #{id})
          </select>
  • 编写查询所属公寓信息逻辑

    • 编写Service层逻辑

      ApartmentInfoService中增加如下内容

      java 复制代码
      ApartmentItemVo selectApartmentItemVoById(Long id);

      ApartmentInfoServiceImpl中增加如下内容

      java 复制代码
      @Override
      public ApartmentItemVo selectApartmentItemVoById(Long id) {
      
          ApartmentInfo apartmentInfo = apartmentInfoMapper.selectById(id);
      
          List<LabelInfo> labelInfoList = labelInfoMapper.selectListByApartmentId(id);
      
          List<GraphVo> graphVoList = graphInfoMapper.selectListByItemTypeAndId(ItemType.APARTMENT, id);
      
          BigDecimal minRent = roomInfoMapper.selectMinRentByApartmentId(id);
      
          ApartmentItemVo apartmentItemVo = new ApartmentItemVo();
          BeanUtils.copyProperties(apartmentInfo, apartmentItemVo);
      
          apartmentItemVo.setGraphVoList(graphVoList);
          apartmentItemVo.setLabelInfoList(labelInfoList);
          apartmentItemVo.setMinRent(minRent);
          return apartmentItemVo;
      }
  • 编写Mapper层逻辑

    • 编写查询标签信息逻辑

      • LabelInfoMapper中增加如下内容

        java 复制代码
          List<LabelInfo> selectListByApartmentId(Long id);
      • LabelInfoMapper.xml中增加如下内容

        xml 复制代码
          <select id="selectListByApartmentId" resultType="com.atguigu.lease.model.entity.LabelInfo">
              select id,
                     type,
                     name
              from label_info
              where is_deleted = 0
                and id in (select label_id
                           from apartment_label
                           where is_deleted = 0
                             and apartment_id = #{id})
          </select>
      • 编写查询公寓最小租金逻辑

        • RoomInfoMapper中增加如下内容

          java 复制代码
          BigDecimal selectMinRentByApartmentId(Long id);
        • RoomInfoMapper.xml中增加如下内容

          xml 复制代码
          <select id="selectMinRentByApartmentId" resultType="java.math.BigDecimal">
              select min(rent)
              from room_info
              where is_deleted = 0
              and is_release = 1
              and apartment_id = #{id}
          </select>
3.根据公寓ID分页查询房间列表
  • 查看请求和响应的数据结构

    • 请求的数据结构

      • currentsize为分页相关参数,分别表示当前所处页面每个页面的记录数
      • id为公寓ID。
    • 响应的数据结构

      • 查看web-admin模块 下的com.atguigu.lease.web.app.vo.room.RoomItemVo,如下

        java 复制代码
        @Schema(description = "APP房间列表实体")
        @Data
        public class RoomItemVo {
        
            @Schema(description = "房间id")
            private Long id;
        
            @Schema(description = "房间号")
            private String roomNumber;
        
            @Schema(description = "租金(元/月)")
            private BigDecimal rent;
        
            @Schema(description = "房间图片列表")
            private List<GraphVo> graphVoList;
        
            @Schema(description = "房间标签列表")
            private List<LabelInfo> labelInfoList;
        
            @Schema(description = "房间所属公寓信息")
            private ApartmentInfo apartmentInfo;
        
        }
  • 编写Controller层逻辑

    RoomController中增加如下内容

    java 复制代码
    @Operation(summary = "根据公寓id分页查询房间列表")
    @GetMapping("pageItemByApartmentId")
    public Result<IPage<RoomItemVo>> pageItemByApartmentId(@RequestParam long current, @RequestParam long size, @RequestParam Long id) {
        IPage<RoomItemVo> page = new Page<>(current, size);
        IPage<RoomItemVo> result = service.pageItemByApartmentId(page, id);
        return Result.ok(result);
    }
  • 编写Service层逻辑

    RoomInfoService中增加如下内容

    java 复制代码
    IPage<RoomItemVo> pageItemByApartmentId(IPage<RoomItemVo> page, Long id);

    RoomInfoServiceImpl中增加如下内容

    java 复制代码
    @Override
    public IPage<RoomItemVo> pageItemByApartmentId(IPage<RoomItemVo> page, Long id) {
        return roomInfoMapper.pageItemByApartmentId(page, id);
    }
  • 编写Mapper层逻辑

    RoomInfoMapper中增加如下内容

    java 复制代码
    IPage<RoomItemVo> pageItemByApartmentId(IPage<RoomItemVo> page, Long id);

    RoomInfoMapper.xml中增加如下内容

    xml 复制代码
    <select id="pageItemByApartmentId" resultMap="RoomItemVoMap">
        select ri.id,
               ri.room_number,
               ri.rent,
               ai.id apartment_id,
               ai.name,
               ai.introduction,
               ai.district_id,
               ai.district_name,
               ai.city_id,
               ai.city_name,
               ai.province_id,
               ai.province_name,
               ai.address_detail,
               ai.latitude,
               ai.longitude,
               ai.phone,
               ai.is_release
        from room_info ri
                 left join apartment_info ai on ri.apartment_id = ai.id and ai.is_deleted = 0
        where ri.is_deleted = 0
          and ri.is_release = 1
          and ai.id = #{id}
          and ri.id not in (select room_id
                            from lease_agreement
                            where is_deleted = 0
                              and status in (2, 5))
    
    </select>
7.4.3.5 公寓信息

公寓信息只需一个接口,即根据ID查询公寓详细信息,具体实现如下

首先在ApartmentController中注入ApartmentInfoService,如下

java 复制代码
@RestController
@Tag(name = "公寓信息")
@RequestMapping("/app/apartment")
public class ApartmentController {
    @Autowired
    private ApartmentInfoService service;
}
  • 查看响应的数据结构

    查看web-app模块 下的com.atguigu.lease.web.app.vo.apartment.ApartmentDetailVo,内容如下

    java 复制代码
    @Data
    @Schema(description = "APP端公寓信息详情")
    public class ApartmentDetailVo extends ApartmentInfo {
    
        @Schema(description = "图片列表")
        private List<GraphVo> graphVoList;
    
        @Schema(description = "标签列表")
        private List<LabelInfo> labelInfoList;
    
        @Schema(description = "配套列表")
        private List<FacilityInfo> facilityInfoList;
    
        @Schema(description = "租金最小值")
        private BigDecimal minRent;
    }
  • 编写Controller层逻辑

    ApartmentController中增加如下内容

    java 复制代码
    @Operation(summary = "根据id获取公寓信息")
    @GetMapping("getDetailById")
    public Result<ApartmentDetailVo> getDetailById(@RequestParam Long id) {
        ApartmentDetailVo apartmentDetailVo = service.getApartmentDetailById(id);
        return Result.ok(apartmentDetailVo);
    }
  • 编写Service层逻辑

    • ApartmentInfoService中增加如下内容

      java 复制代码
      ApartmentDetailVo getDetailById(Long id);
    • ApartmentInfoServiceImpl中增加如下内容

      java 复制代码
      @Override
      public ApartmentDetailVo getDetailById(Long id) {
          //1.查询公寓信息
          ApartmentInfo apartmentInfo = apartmentInfoMapper.selectById(id);
          //2.查询图片信息
          List<GraphVo> graphVoList = graphInfoMapper.selectListByItemTypeAndId(ItemType.APARTMENT, id);
          //3.查询标签信息
          List<LabelInfo> labelInfoList = labelInfoMapper.selectListByApartmentId(id);
          //4.查询配套信息
          List<FacilityInfo> facilityInfoList = facilityInfoMapper.selectListByApartmentId(id);
          //5.查询最小租金
          BigDecimal minRent = roomInfoMapper.selectMinRentByApartmentId(id);
      
          ApartmentDetailVo apartmentDetailVo = new ApartmentDetailVo();
      
          BeanUtils.copyProperties(apartmentInfo, apartmentDetailVo);
          apartmentDetailVo.setGraphVoList(graphVoList);
          apartmentDetailVo.setLabelInfoList(labelInfoList);
          apartmentDetailVo.setFacilityInfoList(facilityInfoList);
          apartmentDetailVo.setMinRent(minRent);
          return apartmentDetailVo;
      }
  • 编写Mapper层逻辑

    • 编写查询公寓配套逻辑

      • FacilityInfoMapper中增加如下内容

        java 复制代码
        List<FacilityInfo> selectListByApartmentId(Long id);
      • FacilityInfoMapper.xml中增加如下内容

        xml 复制代码
        <select id="selectListByApartmentId" resultType="com.atguigu.lease.model.entity.FacilityInfo">
            select id,
                   type,
                   name,
                   icon
            from facility_info
            where is_deleted = 0
              and id in (select facility_id
                         from apartment_facility
                         where is_deleted = 0
                           and apartment_id = #{id})
        </select>

7.4.4 个人中心

7.4.4.1 浏览历史

浏览历史指的是浏览房间详情的历史,关于浏览历史,有两项工作需要完成,一是提供一个查询浏览历史列表的接口,二是在浏览完房间详情后,增加保存浏览历史的逻辑,下面分别实现。

1.分页查询浏览历史列表

首先在BrowsingHistoryController中注入BrowsingHistoryService,如下

java 复制代码
@RestController
@Tag(name = "浏览历史管理")
@RequestMapping("/app/history")
public class BrowsingHistoryController {

    @Autowired
    private BrowsingHistoryService service;
}
  • 查看请求和响应的数据结构

    • 请求的数据结构

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

    • 响应的数据结构

      查看web-admin模块 下的com.atguigu.lease.web.app.vo.history.HistoryItemVo,如下

      java 复制代码
      @Data
      @Schema(description = "浏览历史基本信息")
      public class HistoryItemVo extends BrowsingHistory {
      
          @Schema(description = "房间号")
          private String roomNumber;
      
          @Schema(description = "租金")
          private BigDecimal rent;
      
          @Schema(description = "房间图片列表")
          private List<GraphVo> roomGraphVoList;
      
          @Schema(description = "公寓名称")
          private String apartmentName;
      
          @Schema(description = "省份名称")
          private String provinceName;
      
          @Schema(description = "城市名称")
          private String cityName;
      
          @Schema(description = "区县名称")
          private String districtName;
      }
  • 编写Controller层逻辑

    BrowsingHistoryController中增加如下内容

    java 复制代码
    @Operation(summary = "获取浏览历史")
    @GetMapping("pageItem")
    private Result<IPage<HistoryItemVo>> page(@RequestParam long current, @RequestParam long size) {
        Page<HistoryItemVo> page = new Page<>(current, size);
        IPage<HistoryItemVo> result = service.pageHistoryItemByUserId(page, LoginUserHolder.getLoginUser().getUserId());
        return Result.ok(result);
    }
  • 编写Service层逻辑

    • BrowsingHistoryService中增加如下逻辑

      java 复制代码
      IPage<HistoryItemVo> pageHistoryItemByUserId(Page<HistoryItemVo> page, Long userId);
    • BrowsingHistoryServiceImpl中增加如下逻辑

      java 复制代码
      @Override
      public IPage<HistoryItemVo> pageHistoryItemByUserId(Page<HistoryItemVo> page, Long userId) {
          return browsingHistoryMapper.pageHistoryItemByUserId(page, userId);
      }
  • 编写Mapper层逻辑

    • BrowsingHistoryMapper中增加如下逻辑

      java 复制代码
      IPage<HistoryItemVo> pageHistoryItemByUserId(Page<HistoryItemVo> page, Long userId);
    • BrowsingHistoryMapper.xml中增加如下逻辑

      xml 复制代码
      <resultMap id="HistoryItemVoMap" type="com.atguigu.lease.web.app.vo.history.HistoryItemVo" autoMapping="true">
          <id property="id" column="id"/>
          <result property="roomId" column="room_id"/>
          <collection property="roomGraphVoList" ofType="com.atguigu.lease.web.app.vo.graph.GraphVo"
                      select="selectGraphVoByRoomId" column="room_id"/>
      </resultMap>
      
      <select id="pageHistoryItemByUserId" resultMap="HistoryItemVoMap">
          select bh.id,
                 bh.user_id,
                 bh.room_id,
                 bh.browse_time,
                 ri.room_number,
                 ri.rent,
                 ai.name apartment_name,
                 ai.district_name,
                 ai.city_name,
                 ai.province_name
          from browsing_history bh
                   left join room_info ri on bh.room_id = ri.id and ri.is_deleted=0
                   left join apartment_info ai on ri.apartment_id = ai.id and ai.is_deleted=0
          where bh.is_deleted = 0
            and bh.user_id = #{userId}
          order by browse_time desc
      </select>
      
      <select id="selectGraphVoByRoomId" resultType="com.atguigu.lease.web.app.vo.graph.GraphVo">
          select url,
                 name
          from graph_info
          where is_deleted = 0
            and item_type = 2
            and item_id = #{room_id}
      </select>
2.保存浏览历史
  • 触发保存浏览历史

    保存浏览历史的动作应该在浏览房间详情时触发,所以在RoomInfoServiceImpl中的getDetailById方法的最后增加如下内容

    java 复制代码
    browsingHistoryService.saveHistory(LoginUserContext.getLoginUser().getUserId(), id);
  • 编写Service层逻辑

    • BrowsingHistoryService中增加如下内容

      java 复制代码
      void saveHistory(Long userId, Long roomId);
    • BrowsingHistoryServiceImpl中增加如下内容

      java 复制代码
      @Override
      public void saveHistory(Long userId, Long roomId) {
      
          LambdaQueryWrapper<BrowsingHistory> queryWrapper = new LambdaQueryWrapper<>();
          queryWrapper.eq(BrowsingHistory::getUserId, userId);
          queryWrapper.eq(BrowsingHistory::getRoomId, roomId);
          BrowsingHistory browsingHistory = browsingHistoryMapper.selectOne(queryWrapper);
      
          if (browsingHistory != null) {
              browsingHistory.setBrowseTime(new Date());
              browsingHistoryMapper.updateById(browsingHistory);
          } else {
              BrowsingHistory newBrowsingHistory = new BrowsingHistory();
              newBrowsingHistory.setUserId(userId);
              newBrowsingHistory.setRoomId(roomId);
              newBrowsingHistory.setBrowseTime(new Date());
              browsingHistoryMapper.insert(newBrowsingHistory);
          }
      }

      知识点

      保存浏览历史的动作不应影响前端获取房间详情信息,故此处采取异步操作。Spring Boot提供了@Async注解来完成异步操作,具体使用方式为:

      • 启用Spring Boot异步操作支持

        在 Spring Boot 主应用程序类上添加 @EnableAsync 注解,如下

        java 复制代码
        @SpringBootApplication
        @EnableAsync
        public class AppWebApplication {
            public static void main(String[] args) {
                SpringApplication.run(AppWebApplication.class);
            }
        }
      • 在要进行异步处理的方法上添加 @Async 注解,如下

        java 复制代码
        @Override
        @Async
        public void saveHistory(Long userId, Long roomId) {
        
            LambdaQueryWrapper<BrowsingHistory> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(BrowsingHistory::getUserId, userId);
            queryWrapper.eq(BrowsingHistory::getRoomId, roomId);
            BrowsingHistory browsingHistory = browsingHistoryMapper.selectOne(queryWrapper);
        
            if (browsingHistory != null) {
                browsingHistory.setBrowseTime(new Date());
                browsingHistoryMapper.updateById(browsingHistory);
            } else {
                BrowsingHistory newBrowsingHistory = new BrowsingHistory();
                newBrowsingHistory.setUserId(userId);
                newBrowsingHistory.setRoomId(roomId);
                newBrowsingHistory.setBrowseTime(new Date());
                browsingHistoryMapper.insert(newBrowsingHistory);
            }
        }
7.4.4.2 预约看房

预约看房管理共需三个接口,分别是保存或更新看房预约查询个人预约列表根据ID查询预约详情信息,下面逐一实现

首先在ViewAppointmentController中注入ViewAppointmentService,如下

java 复制代码
@Tag(name = "看房预约信息")
@RestController
@RequestMapping("/app/appointment")
public class ViewAppointmentController {

    @Autowired
    private ViewAppointmentService service;
}
1. 保存或更新看房预约

ViewAppointmentController中增加如下内容

java 复制代码
@Operation(summary = "保存或更新看房预约")
@PostMapping("/saveOrUpdate")
public Result saveOrUpdate(@RequestBody ViewAppointment viewAppointment) {

    viewAppointment.setUserId(LoginUserHolder.getLoginUser().getUserId());
    service.saveOrUpdate(viewAppointment);
    return Result.ok();
}
2. 查询个人预约看房列表
  • 查看响应的数据结构

    查看web-app模块 下的com.atguigu.lease.web.app.vo.appointment.AppointmentItemVo,如下

    java 复制代码
    @Data
    @Schema(description = "APP端预约看房基本信息")
    public class AppointmentItemVo {
    
        @Schema(description = "预约Id")
        private Long id;
    
        @Schema(description = "预约公寓名称")
        private String apartmentName;
    
        @Schema(description = "公寓图片列表")
        private List<GraphVo> graphVoList;
    
        @Schema(description = "预约时间")
        @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
        private Date appointmentTime;
    
        @Schema(description = "当前预约状态")
        private AppointmentStatus appointmentStatus;
    }
  • 编写Controller层逻辑

    ViewAppointmentController中增加如下内容

    java 复制代码
    @Operation(summary = "查询个人预约看房列表")
    @GetMapping("listItem")
    public Result<List<AppointmentItemVo>> listItem() {
        List<AppointmentItemVo> list = service.listItemByUserId(LoginUserHolder.getLoginUser().getUserId());
        return Result.ok(list);
    }
  • 编写Service层逻辑

    • ViewAppointmentService中增加如下内容

      java 复制代码
      List<AppointmentItemVo> listItemByUserId(Long userId);
    • ViewAppointmentServiceImpl中增加如下内容

      java 复制代码
      @Override
      public List<AppointmentItemVo> listItemByUserId(Long userId) {
          return viewAppointmentMapper.listItemByUserId(userId);
      }
  • 编写Mapper层逻辑

    • ViewAppointmentMapper中增加如下内容

      java 复制代码
      List<AppointmentItemVo> listItemByUserId(Long userId);
    • ViewAppointmentMapper.xml中增加如下内容

      xml 复制代码
      <resultMap id="AppointmentItemVoMap" type="com.atguigu.lease.web.app.vo.appointment.AppointmentItemVo"
                 autoMapping="true">
          <id column="id" property="id"/>
          <collection property="graphVoList" ofType="com.atguigu.lease.web.app.vo.graph.GraphVo" autoMapping="true"/>
      </resultMap>
      
      <select id="listItemByUserId" resultMap="AppointmentItemVoMap">
          select va.id,
                 va.appointment_time,
                 va.appointment_status,
                 ai.name apartment_name,
                 gi.name,
                 gi.url
          from view_appointment va
                   left join apartment_info ai on va.apartment_id = ai.id and ai.is_deleted = 0
                   left join graph_info gi on gi.item_type = 1 and gi.item_id = ai.id and gi.is_deleted = 0
          where va.is_deleted = 0
            and va.user_id = #{userId}
          order by va.create_time desc
      </select>
3. 根据ID查询预约详情信息
  • 查看相应的数据结构

    查看web-app模块下的com.atguigu.lease.web.app.vo.appointment.AppointmentDetailVo,内容如下

    java 复制代码
    @Data
    @Schema(description = "APP端预约看房详情")
    public class AppointmentDetailVo extends ViewAppointment {
    
        @Schema(description = "公寓基本信息")
        private ApartmentItemVo apartmentItemVo;
    }
  • 编写Controller层逻辑

    ViewAppointmentController中增加如下内容

    java 复制代码
    @GetMapping("getDetailById")
    @Operation(summary = "根据ID查询预约详情信息")
    public Result<AppointmentDetailVo> getDetailById(Long id) {
        AppointmentDetailVo appointmentDetailVo = service.getDetailById(id);
        return Result.ok(appointmentDetailVo);
    }
  • 编写Service层逻辑

    • ViewAppointmentService中增加如下内容

      java 复制代码
      AppointmentDetailVo getDetailById(Long id);
    • ViewAppointmentServiceImpl中增加如下内容

      java 复制代码
      @Override
      public AppointmentDetailVo getDetailById(Long id) {
      
          ViewAppointment viewAppointment = viewAppointmentMapper.selectById(id);
      
          ApartmentItemVo apartmentItemVo = apartmentInfoService.selectApartmentItemVoById(viewAppointment.getApartmentId());
      
          AppointmentDetailVo agreementDetailVo = new AppointmentDetailVo();
          BeanUtils.copyProperties(viewAppointment, agreementDetailVo);
      
          agreementDetailVo.setApartmentItemVo(apartmentItemVo);
      
          return agreementDetailVo;
      }
7.4.4.3 租约管理

租约管理共有六个接口,分别是获取个人租约基本信息列表根据ID获取租约详细信息根据ID更新租约状态保存或更新租约根据房间ID获取可选支付方式根据房间ID获取可选租期,下面逐一实现

首先在LeaseAgreementController中注入LeaseAgreementService,如下

java 复制代码
@RestController
@RequestMapping("/app/agreement")
@Tag(name = "租约信息")
public class LeaseAgreementController {

    @Autowired
    private LeaseAgreementService service;
}
1. 获取个人租约基本信息列表
  • 查看响应的数据结构

    查看web-appp模块 下的com.atguigu.lease.web.app.vo.agreement.AgreementItemVo,内容如下

    java 复制代码
    @Data
    @Schema(description = "租约基本信息")
    public class AgreementItemVo {
    
        @Schema(description = "租约id")
        private Long id;
    
        @Schema(description = "房间图片列表")
        private List<GraphVo> roomGraphVoList;
    
        @Schema(description = "公寓名称")
        private String apartmentName;
    
        @Schema(description = "房间号")
        private String roomNumber;
    
        @Schema(description = "租约状态")
        private LeaseStatus leaseStatus;
    
        @Schema(description = "租约开始日期")
        @JsonFormat(pattern = "yyyy-MM-dd")
        private Date leaseStartDate;
    
        @Schema(description = "租约结束日期")
        @JsonFormat(pattern = "yyyy-MM-dd")
        private Date leaseEndDate;
    
        @Schema(description = "租约来源")
        private LeaseSourceType sourceType;
    
        @Schema(description = "租金")
        private BigDecimal rent;
    }
  • 编写Controller层逻辑

    LeaseAgreementController中增加如下内容

    java 复制代码
    @Operation(summary = "获取个人租约基本信息列表")
    @GetMapping("listItem")
    public Result<List<AgreementItemVo>> listItem() {
        List<AgreementItemVo> result = service.listItemByPhone(LoginUserHolder.getLoginUser().getUsername());
        return Result.ok(result);
    }
  • 编写Service层逻辑

    • LeaseAgreementService中增加如下内容

      java 复制代码
      List<AgreementItemVo> listItemByPhone(String phone);
    • LeaseAgreementServiceImpl中增加如下内容

      java 复制代码
      @Override
      public List<AgreementItemVo> listItemByPhone(String phone) {
          return leaseAgreementMapper.listItemByPhone(phone);
      }
  • 编写Mapper层逻辑

    • LeaseAgreementMapper中增加如下内容

      java 复制代码
      List<AgreementItemVo> listItemByPhone(String phone);
    • LeaseAgreementMapper.xml中增加如下内容

      xml 复制代码
      <resultMap id="AgreementItemVoMap" type="com.atguigu.lease.web.app.vo.agreement.AgreementItemVo" autoMapping="true">
          <id property="id" column="id"/>
          <collection property="roomGraphVoList" ofType="com.atguigu.lease.web.app.vo.graph.GraphVo" autoMapping="true"/>
      </resultMap>
      
      <select id="listItemByPhone" resultMap="AgreementItemVoMap">
          select la.id,
                 la.lease_start_date,
                 la.lease_end_date,
                 la.rent,
                 la.payment_type_id,
                 la.status lease_status,
                 la.source_type,
                 ai.name apartment_name,
                 ri.room_number,
                 gi.name,
                 gi.url
          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 graph_info gi on gi.item_type = 2 and gi.item_id = ri.id and gi.is_deleted = 0
          where la.is_deleted = 0
            and la.phone = #{phone}
      
      </select>
2. 根据ID获取租约详细信息
  • 查看响应的数据结构

    查看web-app模块 下的com.atguigu.lease.web.app.vo.agreement.AgreementDetailVo,内容如下

    java 复制代码
    @Data
    @Schema(description = "租约详细信息")
    public class AgreementDetailVo extends LeaseAgreement {
    
        @Schema(description = "租约id")
        private Long id;
    
        @Schema(description = "公寓名称")
        private String apartmentName;
    
        @Schema(description = "公寓图片列表")
        private List<GraphVo> apartmentGraphVoList;
    
        @Schema(description = "房间号")
        private String roomNumber;
    
        @Schema(description = "房间图片列表")
        private List<GraphVo> roomGraphVoList;
    
        @Schema(description = "支付方式")
        private String paymentTypeName;
    
        @Schema(description = "租期月数")
        private Integer leaseTermMonthCount;
    
        @Schema(description = "租期单位")
        private String leaseTermUnit;
    
    }
  • 编写Controller层逻辑

    LeaseAgreementController中增加如下内容

    java 复制代码
    @Operation(summary = "根据id获取租约详细信息")
    @GetMapping("getDetailById")
    public Result<AgreementDetailVo> getDetailById(@RequestParam Long id) {
        AgreementDetailVo agreementDetailVo = service.getDetailById(id);
        return Result.ok(agreementDetailVo);
    }
  • 编写Service层逻辑

    • LeaseAgreementService中增加如下内容

      java 复制代码
      AgreementDetailVo getDetailById(Long id);
    • LeaseAgreementServiceImpl中增加如下内容

      java 复制代码
      @Override
      public AgreementDetailVo getDetailById(Long id) {
      
          //1.查询租约信息
          LeaseAgreement leaseAgreement = leaseAgreementMapper.selectById(id);
          if (leaseAgreement == null) {
              return null;
          }
          //2.查询公寓信息
          ApartmentInfo apartmentInfo = apartmentInfoMapper.selectById(leaseAgreement.getApartmentId());
      
          //3.查询房间信息
          RoomInfo roomInfo = roomInfoMapper.selectById(leaseAgreement.getRoomId());
      
          //4.查询图片信息
          List<GraphVo> roomGraphVoList = graphInfoMapper.selectListByItemTypeAndId(ItemType.ROOM, leaseAgreement.getRoomId());
          List<GraphVo> apartmentGraphVoList = graphInfoMapper.selectListByItemTypeAndId(ItemType.APARTMENT, leaseAgreement.getApartmentId());
      
          //5.查询支付方式
          PaymentType paymentType = paymentTypeMapper.selectById(leaseAgreement.getPaymentTypeId());
      
          //6.查询租期
          LeaseTerm leaseTerm = leaseTermMapper.selectById(leaseAgreement.getLeaseTermId());
      
          AgreementDetailVo agreementDetailVo = new AgreementDetailVo();
          BeanUtils.copyProperties(leaseAgreement, agreementDetailVo);
          agreementDetailVo.setApartmentName(apartmentInfo.getName());
          agreementDetailVo.setRoomNumber(roomInfo.getRoomNumber());
          agreementDetailVo.setApartmentGraphVoList(apartmentGraphVoList);
          agreementDetailVo.setRoomGraphVoList(roomGraphVoList);
          agreementDetailVo.setPaymentTypeName(paymentType.getName());
          agreementDetailVo.setLeaseTermMonthCount(leaseTerm.getMonthCount());
          agreementDetailVo.setLeaseTermUnit(leaseTerm.getUnit());
      
          return agreementDetailVo;
      }
3. 根据ID更新租约状态
  • 编写Controller层逻辑

    LeaseAgreementController中增加如下内容

    java 复制代码
    @Operation(summary = "根据id更新租约状态", description = "用于确认租约和提前退租")
    @PostMapping("updateStatusById")
    public Result updateStatusById(@RequestParam Long id, @RequestParam LeaseStatus leaseStatus) {
        LambdaUpdateWrapper<LeaseAgreement> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(LeaseAgreement::getId, id);
        updateWrapper.set(LeaseAgreement::getStatus, leaseStatus);
        service.update(updateWrapper);
        return Result.ok();
    }
4. 保存或更新租约
  • 编写Controller层逻辑

    LeaseAgreementController中增加如下内容

    java 复制代码
    @Operation(summary = "保存或更新租约", description = "用于续约")
    @PostMapping("saveOrUpdate")
    public Result saveOrUpdate(@RequestBody LeaseAgreement leaseAgreement) {
        service.saveOrUpdate(leaseAgreement);
        return Result.ok();
    }
5. 根据房间ID获取可选支付方式
  • 编写Controller层逻辑

    PaymentTypeController中增加如下内容

    java 复制代码
    @Operation(summary = "根据房间id获取可选支付方式列表")
    @GetMapping("listByRoomId")
    public Result<List<PaymentType>> list(@RequestParam Long id) {
        List<PaymentType> list = service.listByRoomId(id);
        return Result.ok(list);
    }
  • 编写Service层逻辑

    PaymentTypeService中增加如下内容

    java 复制代码
    List<PaymentType> listByRoomId(Long id);

    PaymentTypeServiceImpl中增加如下内容

    java 复制代码
    @Override
    public List<PaymentType> listByRoomId(Long id) {
        return paymentTypeMapper.selectListByRoomId(id);
    }
6.根据房间ID获取可选租期
  • 编写Controller层逻辑

    LeaseTermController中增加如下内容

    java 复制代码
    @GetMapping("listByRoomId")
    @Operation(summary = "根据房间id获取可选获取租期列表")
    public Result<List<LeaseTerm>> list(@RequestParam Long id) {
        List<LeaseTerm> list = service.listByRoomId(id);
        return Result.ok(list);
    }
  • 编写Service层逻辑

    LeaseTermServcie中曾加如下内容

    java 复制代码
    List<LeaseTerm> listByRoomId(Long id);

    LeaseTermServiceImpl中增加如下内容

    java 复制代码
    @Override
    public List<LeaseTerm> listByRoomId(Long id) {
        return leaseTermMapper.selectListByRoomId(id);
    }

7.5 移动端前后端联调

7.5.1 启动后端项目

启动后端项目,供前端调用接口。

7.5.2 启动前端项目

  1. 导入前端项目

    将移动端的前端项目(rentHouseH5 )导入vscode或者WebStorm,打开终端,在项目根目录执行以下命令,安装所需依赖

    bash 复制代码
    npm install
  2. 配置后端接口地址

    修改项目根目录下的.env.development文件中的VITE_APP_BASE_URL变量的值为后端接口的地址,此处改为http://localhost:8081即可,如下

    ini 复制代码
    VITE_APP_BASE_URL='http://localhost:8081'

    注意

    上述主机名和端口号需要根据实际情况进行修改。

  3. 启动前端项目

    上述配置完成之后,便可执行以下命令启动前端项目了

    bash 复制代码
    npm run dev
  4. 访问前端项目

    在浏览器中访问前端项目,并逐个测试每个页面的相关功能。

相关推荐
IT枫斗者5 分钟前
如何解决Java EasyExcel 导出报内存溢出
java·服务器·开发语言·网络·分布式·物联网
whn19776 分钟前
虚拟机上搭建达梦DSC简略步骤
数据库
爱编程的小生7 分钟前
Easyexcel(4-模板文件)
java·excel
求积分不加C8 分钟前
Kafka怎么发送JAVA对象并在消费者端解析出JAVA对象--示例
java·分布式·kafka·linq
喝醉酒的小白9 分钟前
Consumer Group
数据库
2401_8576363912 分钟前
实验室管理平台:Spring Boot技术构建
java·spring boot·后端
问窗16 分钟前
微服务中Spring boot的包扫描范围
java·spring boot·微服务
是程序喵呀33 分钟前
SpringMVC详解
java·spring·spring-mvc
疯一样的码农38 分钟前
Apache Maven 标准文件目录布局
java·maven·apache
LIT-涛1 小时前
JavaEE初学07
数据库·oracle·java-ee