尚庭公寓-小程序接口

7. 项目开发

7.4 移动端后端开发

7.4.1 项目初始配置

7.4.1.1 SpringBoot配置

1. 创建application.yml文件

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

server:port: 8081

2. 创建SpringBoot启动类

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

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

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

spring:datasource:type: com.zaxxer.hikari.HikariDataSourceurl: jdbc:mysql://<hostname>:<port>/<database>?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=GMT%2b8username: <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 #连接池最大连接数,默认是10minimum-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类,内容如下:

@Configuration
public class Knife4jConfiguration {@Beanpublic 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")));}@Beanpublic GroupedOpenApi loginAPI() {return GroupedOpenApi.builder().group("登录信息").pathsToMatch("/app/login/**", "/app/info").build();}@Beanpublic GroupedOpenApi personAPI() {return GroupedOpenApi.builder().group("个人信息").pathsToMatch("/app/history/**","/app/appointment/**","/app/agreement/**").build();}@Beanpublic GroupedOpenApi lookForRoomAPI() {return GroupedOpenApi.builder().group("找房信息").pathsToMatch("/app/apartment/**","/app/room/**","/app/payment/**","/app/region/**","/app/term/**").build();}
}

2. application.yml配置文件

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

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

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

导入代码模块包名/路径说明
mapper接口web-appcom.atguigu.lease.web.app.mapper
mapper xmlweb-appsrc/main/resources/mapper
serviceweb-appcom.atguigu.lease.web.app.service
serviceImplweb-appcom.atguigu.lease.web.app.service.impl
7.2.1.5 导入接口定义代码

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

导入代码模块包名/路径说明
controllerweb-appcom.atguigu.lease.web.app.controller
voweb-appcom.atguigu.lease.web.app.voView 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属性存在时,该配置类才会生效。

@Configuration
@EnableConfigurationProperties(MinioProperties.class)
@ConditionalOnProperty(name = "minio.endpoint")
public class MinioConfiguration {@Autowiredprivate MinioProperties properties;@Beanpublic 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,如下

@RestController
@Tag(name = "登录管理")
@RequestMapping("/app/")
public class LoginController {@Autowiredprivate LoginService service;
}
1. 获取短信验证码

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

  • 配置短信服务

    • 开通短信服务

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

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

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

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

    • 创建AccessKey

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

      image-20230808104345383

  • 配置所需依赖

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

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

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

    • application.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类,内容如下

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

      @Configuration
      @EnableConfigurationProperties(AliyunSMSProperties.class)
      @ConditionalOnProperty(name = "aliyun.sms.endpoint")
      public class AliyunSMSConfiguration {@Autowiredprivate AliyunSMSProperties properties;@Beanpublic 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连接参数

    spring: data:redis:host: 192.168.10.101port: 6379database: 0
    
  • 编写Controller层逻辑

    LoginController中增加如下内容

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

    • 编写发送短信逻辑

      • SmsService中增加如下内容

        void sendCode(String phone, String verifyCode);
        
      • SmsServiceImpl中增加如下内容

        @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类,内容如下

      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中增加如下内容

        void getSMSCode(String phone);
        
      • LoginServiceImpl中增加如下内容

        @Override
        public void getSMSCode(String phone) {//1. 检查手机号码是否为空if (!StringUtils.hasText(phone)) {throw new LeaseException(ResultCodeEnum.APP_LOGIN_PHONE_EMPTY);}//2. 检查Redis中是否已经存在该手机号码的keyString 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.发送短信,并将验证码存入RedisString 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中增加如下内容

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

      • LoginService中增加如下内容

        String login(LoginVo loginVo);
        
      • LoginServiceImpl总增加如下内容

        @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.创建并返回TOKENreturn JwtUtil.createToken(userInfo.getId(), loginVo.getPhone());
        }
        
    • 编写HandlerInterceptor

      • 编写AuthenticationInterceptor

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

        @Component
        public class AuthenticationInterceptor implements HandlerInterceptor {@Overridepublic 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;}@Overridepublic 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,内容如下

        @Configuration
        public class WebMvcConfiguration implements WebMvcConfigurer {@Autowiredprivate AuthenticationInterceptor authenticationInterceptor;@Overridepublic 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,内容如下

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

    LoginController中增加如下内容

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

    • LoginService中增加如下内容

      UserInfoVo getUserInfoId(Long id);
      
    • LoginServiceImpl中增加如下内容

      @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中增加如下内容

@Tag(name = "地区信息")
@RestController
@RequestMapping("/app/region")
public class RegionController {@Autowiredprivate ProvinceInfoService provinceInfoService;@Autowiredprivate CityInfoService cityInfoService;@Autowiredprivate 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中增加如下内容

@Tag(name = "支付方式接口")
@RestController
@RequestMapping("/app/payment")
public class PaymentTypeController {@Autowiredprivate 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,如下

@Tag(name = "房间信息")
@RestController
@RequestMapping("/app/room")
public class RoomController {@AutowiredRoomInfoService roomInfoService;
}
1. 根据条件分页查询房间列表
  • 查看请求和响应的数据结构

    • 请求数据结构

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

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

        @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,内容如下:

      @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中增加如下内容

    @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中增加如下内容

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

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

    • RoomInfoMapper中增加如下内容

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

      <!-- 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">selectri.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_releasefrom room_info rileft join apartment_info ai on ri.apartment_id = ai.id and ai.is_deleted = 0<where>ri.is_deleted = 0and ri.is_release = 1and ri.id not in(select room_idfrom lease_agreementwhere is_deleted = 0and 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 (selectroom_idfrom room_payment_typewhere is_deleted = 0and 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,urlfrom graph_infowhere is_deleted = 0and item_type = 2and item_id = #{id}
      </select><!-- 根据公寓ID查询标签列表 -->
      <select id="selectLabelInfoListByRoomId" resultType="com.atguigu.lease.model.entity.LabelInfo">select id,type,namefrom label_infowhere is_deleted = 0and id in (select label_idfrom room_labelwhere is_deleted = 0and 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两张表,其关系为一对多,如下

        在这里插入图片描述

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

        [{"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}]}
        ]
        

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

        • 嵌套结果映射

          <select id="selectRoomPage" resultMap="RoomPageMap">select ri.id room_id,ri.number,ri.rent,gi.id graph_id,gi.url,gi.room_idfrom room_info rileft 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>
          

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

        在这里插入图片描述

        • 嵌套查询

          <select id="selectRoomPage" resultMap="RoomPageMap">select id,number,rentfrom 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_idfrom graph_infowhere 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,内容如下

    @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中增加如下内容

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

    • 编写Service层逻辑

      • RoomInfoService中增加如下内容

        RoomDetailVo getDetailById(Long id);
        
      • RoomInfoServiceImpl中增加如下内容

        @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中增加如下内容

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

          <select id="selectListByItemTypeAndId" resultType="com.atguigu.lease.web.app.vo.graph.GraphVo">select name,urlfrom graph_infowhere is_deleted = 0and item_type = #{itemType}and item_id = #{id}
          </select>
          
      • 编写查询房间可选租期逻辑

        • LeaseTermMapper中增加如下内容

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

          <select id="selectListByRoomId" resultType="com.atguigu.lease.model.entity.LeaseTerm">select id,month_count,unitfrom lease_termwhere is_deleted = 0and id in (select lease_term_idfrom room_lease_termwhere is_deleted = 0and room_id = #{id})
          </select>
          
      • 编写查询房间配套逻辑

        • FacilityInfoMapper中增加如下内容

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

          <select id="selectListByRoomId" resultType="com.atguigu.lease.model.entity.FacilityInfo">select id,type,name,iconfrom facility_infowhere is_deleted = 0and id in (select facility_idfrom room_facilitywhere is_deleted = 0and room_id = #{id})
          </select>
          
      • 编写查询房间标签逻辑

        • LabelInfoMapper中增加如下内容

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

          <select id="selectListByRoomId" resultType="com.atguigu.lease.model.entity.LabelInfo">select id,type,namefrom label_infowhere is_deleted = 0and id in (select label_idfrom room_labelwhere is_deleted = 0and room_id = #{id})
          </select>
          
      • 编写查询房间可选支付方式逻辑

        • PaymentTypeMapper中增加如下内容

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

          <select id="selectListByRoomId" resultType="com.atguigu.lease.model.entity.PaymentType">select id,name,pay_month_count,additional_infofrom payment_typewhere is_deleted = 0and id in (select payment_type_idfrom room_payment_typewhere is_deleted = 0and room_id = #{id})
          </select>
          
      • 编写查询房间属性逻辑

        • AttrValueMapper中增加如下内容

          List<AttrValueVo> selectListByRoomId(Long id);
          
        • AttrValueMapper.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_namefrom attr_value avleft join attr_key ak on av.attr_key_id = ak.id and ak.is_deleted = 0where av.is_deleted = 0and av.id in (select attr_value_idfrom room_attr_valuewhere is_deleted = 0and room_id = #{id})
          </select>
          
      • 编写查询房间杂费逻辑

        • FeeValueMapper中增加如下内容

          List<FeeValueVo> selectListByApartmentId(Long id);
          
        • FeeValueMapper.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_namefrom fee_value fvleft join fee_key fk on fv.fee_key_id = fk.id and fk.is_deleted = 0where fv.is_deleted = 0and fv.id in (select fee_value_idfrom apartment_fee_valuewhere is_deleted = 0and apartment_id = #{id})
          </select>
          
  • 编写查询所属公寓信息逻辑

    • 编写Service层逻辑

      ApartmentInfoService中增加如下内容

      ApartmentItemVo selectApartmentItemVoById(Long id);
      

      ApartmentInfoServiceImpl中增加如下内容

      @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中增加如下内容

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

          <select id="selectListByApartmentId" resultType="com.atguigu.lease.model.entity.LabelInfo">select id,type,namefrom label_infowhere is_deleted = 0and id in (select label_idfrom apartment_labelwhere is_deleted = 0and apartment_id = #{id})</select>
        
      • 编写查询公寓最小租金逻辑

        • RoomInfoMapper中增加如下内容

          BigDecimal selectMinRentByApartmentId(Long id);
          
        • RoomInfoMapper.xml中增加如下内容

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

    • 请求的数据结构

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

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

        @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中增加如下内容

    @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中增加如下内容

    IPage<RoomItemVo> pageItemByApartmentId(IPage<RoomItemVo> page, Long id);
    

    RoomInfoServiceImpl中增加如下内容

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

    RoomInfoMapper中增加如下内容

    IPage<RoomItemVo> pageItemByApartmentId(IPage<RoomItemVo> page, Long id);
    

    RoomInfoMapper.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_releasefrom room_info rileft join apartment_info ai on ri.apartment_id = ai.id and ai.is_deleted = 0where ri.is_deleted = 0and ri.is_release = 1and ai.id = #{id}and ri.id not in (select room_idfrom lease_agreementwhere is_deleted = 0and status in (2, 5))</select>
    
7.4.3.5 公寓信息

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

首先在ApartmentController中注入ApartmentInfoService,如下

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

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

    @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中增加如下内容

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

    • ApartmentInfoService中增加如下内容

      ApartmentDetailVo getDetailById(Long id);
      
    • ApartmentInfoServiceImpl中增加如下内容

      @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中增加如下内容

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

        <select id="selectListByApartmentId" resultType="com.atguigu.lease.model.entity.FacilityInfo">select id,type,name,iconfrom facility_infowhere is_deleted = 0and id in (select facility_idfrom apartment_facilitywhere is_deleted = 0and apartment_id = #{id})
        </select>

7.4.4 个人中心

7.4.4.1 浏览历史

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

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

首先在BrowsingHistoryController中注入BrowsingHistoryService,如下

@RestController
@Tag(name = "浏览历史管理")
@RequestMapping("/app/history")
public class BrowsingHistoryController {@Autowiredprivate BrowsingHistoryService service;
}
  • 查看请求和响应的数据结构

    • 请求的数据结构

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

    • 响应的数据结构

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

      @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中增加如下内容

    @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中增加如下逻辑

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

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

    • BrowsingHistoryMapper中增加如下逻辑

      IPage<HistoryItemVo> pageHistoryItemByUserId(Page<HistoryItemVo> page, Long userId);
      
    • BrowsingHistoryMapper.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_namefrom browsing_history bhleft join room_info ri on bh.room_id = ri.id and ri.is_deleted=0left join apartment_info ai on ri.apartment_id = ai.id and ai.is_deleted=0where bh.is_deleted = 0and bh.user_id = #{userId}order by browse_time desc
      </select><select id="selectGraphVoByRoomId" resultType="com.atguigu.lease.web.app.vo.graph.GraphVo">select url,namefrom graph_infowhere is_deleted = 0and item_type = 2and item_id = #{room_id}
      </select>
      
2.保存浏览历史
  • 触发保存浏览历史

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

    browsingHistoryService.saveHistory(LoginUserContext.getLoginUser().getUserId(), id);
    
  • 编写Service层逻辑

    • BrowsingHistoryService中增加如下内容

      void saveHistory(Long userId, Long roomId);
      
    • BrowsingHistoryServiceImpl中增加如下内容

      @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 注解,如下

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

        @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,如下

@Tag(name = "看房预约信息")
@RestController
@RequestMapping("/app/appointment")
public class ViewAppointmentController {@Autowiredprivate ViewAppointmentService service;
}
1. 保存或更新看房预约

ViewAppointmentController中增加如下内容

@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,如下

    @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中增加如下内容

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

    • ViewAppointmentService中增加如下内容

      List<AppointmentItemVo> listItemByUserId(Long userId);
      
    • ViewAppointmentServiceImpl中增加如下内容

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

    • ViewAppointmentMapper中增加如下内容

      List<AppointmentItemVo> listItemByUserId(Long userId);
      
    • ViewAppointmentMapper.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.urlfrom view_appointment valeft join apartment_info ai on va.apartment_id = ai.id and ai.is_deleted = 0left join graph_info gi on gi.item_type = 1 and gi.item_id = ai.id and gi.is_deleted = 0where va.is_deleted = 0and va.user_id = #{userId}order by va.create_time desc
      </select>
      
3. 根据ID查询预约详情信息
  • 查看相应的数据结构

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

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

    ViewAppointmentController中增加如下内容

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

    • ViewAppointmentService中增加如下内容

      AppointmentDetailVo getDetailById(Long id);
      
    • ViewAppointmentServiceImpl中增加如下内容

      @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,如下

@RestController
@RequestMapping("/app/agreement")
@Tag(name = "租约信息")
public class LeaseAgreementController {@Autowiredprivate LeaseAgreementService service;
}
1. 获取个人租约基本信息列表
  • 查看响应的数据结构

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

    @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中增加如下内容

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

    • LeaseAgreementService中增加如下内容

      List<AgreementItemVo> listItemByPhone(String phone);
      
    • LeaseAgreementServiceImpl中增加如下内容

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

    • LeaseAgreementMapper中增加如下内容

      List<AgreementItemVo> listItemByPhone(String phone);
      
    • LeaseAgreementMapper.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.urlfrom lease_agreement laleft join apartment_info ai on la.apartment_id = ai.id and ai.is_deleted = 0left join room_info ri on la.room_id = ri.id and ri.is_deleted = 0left join graph_info gi on gi.item_type = 2 and gi.item_id = ri.id and gi.is_deleted = 0where la.is_deleted = 0and la.phone = #{phone}</select>
      
2. 根据ID获取租约详细信息
  • 查看响应的数据结构

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

    @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中增加如下内容

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

    • LeaseAgreementService中增加如下内容

      AgreementDetailVo getDetailById(Long id);
      
    • LeaseAgreementServiceImpl中增加如下内容

      @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中增加如下内容

    @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中增加如下内容

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

    PaymentTypeController中增加如下内容

    @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中增加如下内容

    List<PaymentType> listByRoomId(Long id);
    

    PaymentTypeServiceImpl中增加如下内容

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

    LeaseTermController中增加如下内容

    @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中曾加如下内容

    List<LeaseTerm> listByRoomId(Long id);
    

    LeaseTermServiceImpl中增加如下内容

    @Override
    public List<LeaseTerm> listByRoomId(Long id) {return leaseTermMapper.selectListByRoomId(id);
    }
    

7.5 移动端前后端联调

7.5.1 启动后端项目

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

7.5.2 启动前端项目

  1. 导入前端项目

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

    npm install
    
  2. 配置后端接口地址

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

    VITE_APP_BASE_URL='http://localhost:8081'
    

    注意

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

  3. 启动前端项目

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

    npm run dev
    
  4. 访问前端项目

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/464906.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

练习LabVIEW第三十八题

学习目标&#xff1a; 刚学了LabVIEW&#xff0c;在网上找了些题&#xff0c;练习一下LabVIEW&#xff0c;有不对不好不足的地方欢迎指正&#xff01; 第三十八题&#xff1a; 创建一个VI&#xff0c;实现对按钮状态的指示和按钮“按下”持续时间简单计算功能&#xff0c;按…

HTMLCSS:3D 旋转卡片的炫酷动画

效果演示 这段代码是一个HTML和CSS的组合&#xff0c;用于创建一个具有3D效果的动画卡片。 HTML <div class"obj"><div class"objchild"><span class"inn6"><h3 class"text">我是谁&#xff1f;我在那<…

微控制器(MCU)如何运行存储在Flash的程序???

忙&#xff0c;太忙了&#xff01;&#xff01;&#xff01;忙完就好了。MCU运行不也就如此&#xff1f;在微控制器单元&#xff08;MCU&#xff09;中&#xff0c;我们所编写的程序时通常是存储在闪存&#xff08;Flash&#xff09;中。当MCU启动时&#xff0c;它会从闪存中读…

yolov8涨点系列之引入CBAM注意力机制

文章目录 YOLOv8 中添加注意力机制 CBAM 具有多方面的好处特征增强与选择通道注意力方面空间注意力方面 提高模型性能计算效率优化&#xff1a; yolov8增加CBAM具体步骤CBAM代码(1)在__init.pyconv.py文件的__all__内添加‘CBAM’(2)conv.py文件复制粘贴CBAM代码(3)修改task.py…

如何无缝更换WordPress主题:关键步骤详解

更换WordPress主题对于希望刷新网站外观或改善用户体验的站长来说&#xff0c;是一项常见但不容忽视的任务。无论是为了提升性能还是实现新的设计风格&#xff0c;在更换主题时&#xff0c;确保不遗漏任何重要细节至关重要。本文将详细介绍更换WordPress主题的关键步骤&#xf…

推荐一款PowerPoint转Flash工具:iSpring Suite

iSpring Suite是一款PowerPoint转Flash工具&#xff0c;使用iSpring Suite 8可以轻松的将PPT演示文档转换为对Web友好的Flash影片格式。软件界面简洁&#xff0c;使用方便。为什么要转换成flash格式呢?Flash格式的最大特点是体积小巧、易于分发&#xff0c;兼容所有的操作系统…

【案例】故障雪花屏

开发平台&#xff1a;Unity 6.0 开发工具&#xff1a;Shader Graph 参考视频&#xff1a;【U2D Shader Graph】❄️雪❄️花❄️屏❄️   一、效果图 二、Shader Graph 路线图 三、案例分析 核心思路&#xff1a;雪花屏幕效果 &#xff08;混合&#xff09; 原图像 最终图像…

ffplay 实现视频流中音频的延迟

ffplay -rtsp_transport tcp -i rtsp://admin:1234qwer192.168.1.64:554/Streaming/Channels/101 -vn -af "adelay5000|5000"在这个命令中&#xff1a; -vn 参数表示只播放音频。 -af "adelay5000|5000" 参数表示将音频延迟5000毫秒&#xff08;即5秒&…

科技资讯|谷歌Play应用商店有望支持 XR 头显,AR / VR设备有望得到发展

据 Android Authority 报道&#xff0c;谷歌似乎正在为其 Play 商店增加对 XR 头显的支持。该媒体在 Play 商店的代码中发现了相关的线索&#xff0c;包括一个代表头显的小图标以及对“XR 头显”的提及。 谷歌也可能改变了此前拒绝将 Play 商店引入 Meta Quest 头显的决定。今…

Pr 视频效果:超级键

视频效果/键控/超级键 Keying/Ultra Key 超级键 Ultra Key效果是 Premiere Pro 中功能强大的抠像工具&#xff0c;主要用于绿幕/蓝幕抠像。通过选择要抠除的颜色&#xff08;通常是绿幕或蓝幕的颜色&#xff09;&#xff0c;即可以将该颜色的像素设为透明&#xff0c;实现主体与…

Git使用指南

目录 工作机制基本框架:流程图 基本命令分支操作远程仓库本地仓库关联远程仓库 参考 工作机制 基本框架: Workspace&#xff1a;开发者工作区&#xff0c;也就是你当前写代码的目录&#xff0c;它一般保持的是最新仓库代码。Index / Stage&#xff1a;暂存区&#xff0c;最早…

计算机网络:简述LAN口模式下NAT和代理的区别

LAN口模式 NAT和代理的区别 LAN口模式下的NAT和代理的区别主要体现在定义、功能和应用场景上。 # NAT和代理的定义和功能 ‌NAT&#xff08;网络地址转换&#xff09;‌&#xff1a;NAT是一种网络地址翻译技术&#xff0c;它将内部私有IP地址转换为公网IP地址&#xff0c;使得…

qt QFile详解

1、概述 QFile类是Qt框架中用于读取和写入文本和二进制文件资源的I/O工具类。它继承自QFileDevice类&#xff0c;后者又继承自QIODevice类。QFile类提供了一个接口&#xff0c;允许开发者以二进制模式或文本模式对文件进行读写操作。默认情况下&#xff0c;QFile假定文件内容为…

105. UE5 GAS RPG 搭建主菜单

在这一篇&#xff0c;我们将实现对打开游戏显示的主菜单进行搭建&#xff0c;主菜单将显示游戏主角&#xff0c;游戏名称和进入游戏和退出游戏两个按钮。 搭建菜单场景 我们将主菜单设置为一个单独的场景&#xff0c;前面可以显示对应的UI控件&#xff0c;用于玩家操作&#…

单臂路由技术,eNSP实验讲解

单臂路由技术&#xff0c;eNSP实验讲解 一、简要介绍1、概念2、工作原理3、优点4、缺点5、应用场景举例 二、eNSP仿真实验1、步骤一&#xff1a;2、步骤二&#xff1a;3、步骤三&#xff1a;4、步骤四&#xff1a; 三、总结。 一、简要介绍 1、概念 单臂路由&#xff08;Rout…

机器学习—构建一个神经网络

如何在Tensorflow中构建神经网络&#xff1f; 回到之前的例子&#xff0c;如果你想做钱进支柱&#xff0c;初始化数据x创建第一层&#xff0c;如下图所示计算一个1&#xff0c;然后创建第二层并计算一个2&#xff0c;所以这是一种明确的向前推进的方式。 事实证明&#xff0c;…

JMeter与大模型融合应用之jmeter.properties配置文件新增配置

JMeter与大模型融合应用之jmeter.properties配置文件新增配置 背景 Apache JMeter 是一款流行的开源性能测试工具&#xff0c;它允许用户通过创建和运行多种类型的测试来评估应用程序的性能。jmeter.properties 文件是 JMeter 的主要配置文件之一&#xff0c;用户可以在其中进…

ffmpeg:视频字幕嵌入(GPU加速)

实现方案 参考指令 ffmpeg -i input_video.mp4 -vf "subtitlessubtitles.srt" output_video.mp4 解决因文件名称复杂导致的指令执行失败问题&#xff08;引号给文件框起来&#xff09; ffmpeg -i "A.mp4" -vf "subtitlesB.srt" "c.mp4&qu…

04_CC2530+Uart串口通信

04_CC2530UART串口通信 串口通信基本概念 串行通信: 数据字节一位位地依次传送的通信方式, 串行通信的速度慢, 但用的传输线条数少, 成本低&#xff0c;适用于远距离的数据传送并行通信: 数据字节的各位同事传送的通信方式, 优点是数据传送速度快, 缺点是占用的传输线条数多,…

基于SSM社区便民服务管理系统JAVA|VUE|Springboot计算机毕业设计源代码+数据库+LW文档+开题报告+答辩稿+部署教+代码讲解

源代码数据库LW文档&#xff08;1万字以上&#xff09;开题报告答辩稿 部署教程代码讲解代码时间修改教程 一、开发工具、运行环境、开发技术 开发工具 1、操作系统&#xff1a;Window操作系统 2、开发工具&#xff1a;IntelliJ IDEA或者Eclipse 3、数据库存储&#xff1a…