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
注意:需根据实际情况修改hostname
、port
、database
、username
、password
。
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-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
属性存在时,该配置类才会生效。
@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。
-
-
配置所需依赖
如需调用阿里云的短信服务,需使用其提供的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-id
、access-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
到后端。 - 首先校验
phone
和code
是否为空,若为空,直接响应手机号码为空
或者验证码为空
,若不为空则进入下步判断。 - 根据
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. 根据条件分页查询房间列表
-
查看请求和响应的数据结构
-
请求数据结构
-
current
和size
为分页相关参数,分别表示当前所处页面和每个页面的记录数。 -
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 >= #{queryVo.minRent} and ri.rent <= #{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文件中的
<
和>
是特殊符号,需要转义处理。原符号 转义符号 <
<
>
>
-
Mybatis-Plus分页插件注意事项
使用Mybatis-Plus的分页插件进行分页查询时,如果结果需要使用
<collection>
进行映射,只能使用**嵌套查询(Nested Select for Collection),而不能使用嵌套结果映射(Nested Results for Collection)**。嵌套查询和嵌套结果映射是Collection映射的两种方式,下面通过一个案例进行介绍
例如有
room_info
和graph_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分页查询房间列表
-
查看请求和响应的数据结构
-
请求的数据结构
current
和size
为分页相关参数,分别表示当前所处页面和每个页面的记录数。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;
}
-
查看请求和响应的数据结构
-
请求的数据结构
current
和size
为分页相关参数,分别表示当前所处页面和每个页面的记录数。 -
响应的数据结构
查看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 启动前端项目
-
导入前端项目
将移动端的前端项目(rentHouseH5)导入
vscode
或者WebStorm
,打开终端,在项目根目录执行以下命令,安装所需依赖npm install
-
配置后端接口地址
修改项目根目录下的
.env.development
文件中的VITE_APP_BASE_URL
变量的值为后端接口的地址,此处改为http://localhost:8081
即可,如下VITE_APP_BASE_URL='http://localhost:8081'
注意:
上述主机名和端口号需要根据实际情况进行修改。
-
启动前端项目
上述配置完成之后,便可执行以下命令启动前端项目了
npm run dev
-
访问前端项目
在浏览器中访问前端项目,并逐个测试每个页面的相关功能。