06-公寓租赁项目-后台管理-公寓管理篇
尚庭公寓项目/公寓管理模块
https://www.yuque.com/pkqzyh/qg2yge/5ba67653b51379d18df61b9c14c3e946
一、属性管理
属性管理页面包含公寓和房间各种可选的属性信息,其中包括房间的可选支付方式、房间的可选租期、房间的配套、公寓的配套等等。其所需接口如下
1.1 房间支付方式管理
房间支付方式管理共有三个接口,分别是查询全部支付方式列表、保存或更新支付方式和根据ID删除支付方式,下面逐一实现。
首先在PaymentTypeController
中注入PaymentTypeService
依赖,如下
@Tag(name = "支付方式管理")
@RequestMapping("/admin/payment")
@RestController
public class PaymentTypeController {@Autowiredprivate PaymentTypeService service;
}
1.1.1 查询全部支付方式列表
在PaymentTypeController
中增加如下内容
@Operation(summary = "查询全部支付方式列表")
@GetMapping("list")
public Result<List<PaymentType>> listPaymentType() {List<PaymentType> list = service.list();return Result.ok(list);
}
知识点:
- 逻辑删除功能由于数据库中所有表均采用逻辑删除策略,所以查询数据时均需要增加过滤条件
is_deleted=0
。上述操作虽不难实现,但是每个查询接口都要考虑到,也显得有些繁琐。为简化上述操作,可以使用Mybatis-Plus提供的逻辑删除功能,它可以自动为查询操作增加is_deleted=0
过滤条件,并将删除操作转为更新语句。具体配置如下,详细信息可参考官方文档。- 步骤一:在
application.yml
中增加如下内容
- 步骤一:在
mybatis-plus:global-config:db-config:logic-delete-field: is_deleted # 全局逻辑删除的实体字段名(配置后可以忽略不配置步骤二)logic-delete-value: 1 # 逻辑已删除值(默认为 1)logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
- 步骤二:在实体类中的删除标识字段上增加`@TableLogic`注解
@Data
public class BaseEntity {@Schema(description = "主键")@TableId(value = "id", type = IdType.AUTO)private Long id;@Schema(description = "创建时间")@JsonIgnoreprivate Date createTime;@Schema(description = "更新时间")@JsonIgnoreprivate Date updateTime;@Schema(description = "逻辑删除")@JsonIgnore@TableLogic@TableField("is_deleted")private Byte isDeleted;}
注意:逻辑删除功能只对Mybatis-Plus自动注入的sql起效,也就是说,对于手动在Mapper.xml
文件配置的sql不会生效,需要单独考虑。
- **忽略特定字段(扩展)**通常情况下接口响应的Json对象中并不需要
create_time
、update_time
、is_deleted
等字段,这时只需在实体类中的相应字段添加@JsonIgnore
注解,该字段就会在序列化时被忽略。具体配置如下,详细信息可参考Jackson官方文档。
@Data
public class BaseEntity {@Schema(description = "主键")@TableId(value = "id", type = IdType.AUTO)private Long id;@Schema(description = "创建时间")@JsonIgnore@TableField(value = "create_time")private Date createTime;@Schema(description = "更新时间")@JsonIgnore@TableField(value = "update_time")private Date updateTime;@Schema(description = "逻辑删除")@JsonIgnore@TableField("is_deleted")private Byte isDeleted;}
1.1.2 保存或更新支付方式
在PaymentTypeController
中增加如下内容
@Operation(summary = "保存或更新支付方式")
@PostMapping("saveOrUpdate")
public Result saveOrUpdatePaymentType(@RequestBody PaymentType paymentType) {service.saveOrUpdate(paymentType);return Result.ok();
}
此时,我们发现数据库逻辑填充字段并没有值,因为数据库在设计的时候,is_deleted 默认为 NULL,为了解决这个问题,我们有以下几种方法
ALTER TABLE lease.payment_type
MODIFY COLUMN `is_deleted` tinyint NULL DEFAULT 0 COMMENT '是否删除';
知识点:
保存或更新数据时,前端通常不会传入isDeleted
、createTime
、updateTime
这三个字段,因此我们需要手动赋值。但是数据库中几乎每张表都有上述字段,所以手动去赋值就显得有些繁琐。为简化上述操作,我们可采取以下措施。
is_deleted
字段:可将数据库中该字段的默认值设置为0
。create_time
和update_time
:可使用mybatis-plus的自动填充功能,所谓自动填充,就是通过统一配置,在插入或更新数据时,自动为某些字段赋值,具体配置如下,详细信息可参考官方文档。
在做完上述配置后,当写入数据时,Mybatis-Plus会自动将实体对象的create_time
字段填充为当前时间,当更新数据时,则会自动将实体对象的update_time
字段填充为当前时间。
- 为相关字段配置触发填充的时机,例如`create_time`需要在插入数据时填充,而`update_time`需要在更新数据时填充。具体配置如下,观察`@TableField`注解中的`fill`属性。
package com.pkq.lease.model.entity;import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;import java.io.Serializable;
import java.util.Date;@Data
public class BaseEntity implements Serializable {@Schema(description = "主键")@TableId(value = "id", type = IdType.AUTO)private Long id;@Schema(description = "创建时间")@TableField(value = "create_time",fill = FieldFill.INSERT)@JsonIgnoreprivate Date createTime;@Schema(description = "更新时间")@TableField(value = "update_time",fill = FieldFill.INSERT_UPDATE)@JsonIgnoreprivate Date updateTime;@Schema(description = "逻辑删除")@TableField(value = "is_deleted")@TableLogic@JsonIgnoreprivate Byte isDeleted;}
- 配置自动填充的内容,具体配置如下在**common模块**下创建`com.pkq.lease.common.mybatisplus.MybatisMetaObjectHandler`类,内容如下:
@Component
public class MybatisMetaObjectHandler implements MetaObjectHandler {@Overridepublic void insertFill(MetaObject metaObject) {this.strictInsertFill(metaObject, "createTime", Date.class, new Date());this.strictInsertFill(metaObject, "updateTime", Date.class, new Date());}@Overridepublic void updateFill(MetaObject metaObject) {this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());}
}
1.1.3 根据ID删除支付方式
在PaymentTypeController
中增加如下内容
@Operation(summary = "根据ID删除支付方式")
@DeleteMapping("deleteById")
public Result deletePaymentById(@RequestParam Long id) {service.removeById(id);return Result.ok();
}
知识点:
MybatisPlus
逻辑删除功能的使用。
Jackson
忽略字段
MyBatisPlus
自动填充字段
1.2 房间租期管理(作业)
房间租期管理共有三个接口,分别是查询全部租期列表、保存或更新租期信息和根据ID删除租期,具体实现如下。
在LeaseTermController
中增加如下内容
@Tag(name = "租期管理")
@RequestMapping("/admin/term")
@RestController
public class LeaseTermController {@Autowiredprivate LeaseTermService service;@GetMapping("list")@Operation(summary = "查询全部租期列表")public Result<List<LeaseTerm>> listLeaseTerm() {List<LeaseTerm> list = service.list();return Result.ok(list);}@PostMapping("saveOrUpdate")@Operation(summary = "保存或更新租期信息")public Result saveOrUpdate(@RequestBody LeaseTerm leaseTerm) {service.saveOrUpdate(leaseTerm);return Result.ok();}@DeleteMapping("deleteById")@Operation(summary = "根据ID删除租期")public Result deleteLeaseTermById(@RequestParam Long id) {service.removeById(id);return Result.ok();}
}
1.3 标签管理
标签管理共有三个接口,分别是**[根据类型]查询标签列表、保存或更新标签信息和根据ID删除标签**,下面逐一实现。
首先在LabelController
中注入LabelInfoService
依赖,如下
@Tag(name = "标签管理")
@RestController
@RequestMapping("/admin/label")
public class LabelController {@Autowiredprivate LabelInfoService service;
}
⭐⭐⭐1.3.1 [根据类型]查询标签列表 (重点)
在LabelController
中增加如下内容
:::color3
如果前端没有传递要查询的标签类型,那么就查询所有的标签信息
如果前端传递了要查询的标签类型,就需要进行条件查询
:::
@GetMapping("/list")@Operation(summary = "根据类型查询标签列表")public Result<List<LabelInfo>> getAllLabel(@RequestParam(required = false) ItemType type) {LambdaQueryWrapper<LabelInfo> queryWrapper = new LambdaQueryWrapper<>();if (type != null) {queryWrapper.eq(LabelInfo::getType, type.getCode());}List<LabelInfo> labelInfoList = labelInfoService.list(queryWrapper);return Result.ok(labelInfoList);}
:::color3
但是这个时候进行调试,会发现报错。
如果前端传递的是 **<font style="color:#DF2A3F;">1</font>**
,Spring MVC 默认会尝试将 **<font style="color:#DF2A3F;">1</font>**
绑定到 **<font style="color:#DF2A3F;">ItemType</font>**
枚举。
- 问题:Spring MVC 默认使用
**<font style="color:#DF2A3F;">Enum.valueOf</font>**
方法,根据枚举名称(如**<font style="color:#DF2A3F;">APARTMENT</font>**
)进行绑定,而不是根据******<font style="color:#DF2A3F;">code</font>**
。 - 结果:传递
**<font style="color:#DF2A3F;">1</font>**
会导致绑定失败,因为**<font style="color:#DF2A3F;">ItemType</font>**
中没有名称为**<font style="color:#DF2A3F;">"1"</font>**
的枚举实例。
SpringMVC 框架可以把我们前端传递的 type=1,转换为 ItemType 类型
WebDataBinder:提供了数据转换的绑定接口,但是存在问题,它是把参数值转换为 ItemType.APARTMENT,而 不是和枚举的值进行转换
:::
解决方案
3.1 自定义属性编辑器
通过 <font style="color:rgb(36, 41, 47);">@InitBinder</font>
注册自定义的属性编辑器,将 <font style="color:rgb(36, 41, 47);">code</font>
转换为枚举实例。
示例代码
复制代码
@RestController
public class ItemController {@InitBinderpublic void initBinder(WebDataBinder binder) {binder.registerCustomEditor(ItemType.class, new PropertyEditorSupport() {@Overridepublic void setAsText(String text) throws IllegalArgumentException {int code = Integer.parseInt(text);for (ItemType type : ItemType.values()) {if (type.getCode().equals(code)) {setValue(type);return;}}throw new IllegalArgumentException("Invalid code: " + code);}});}@PostMapping("/item")public String createItem(@RequestParam ItemType type) {return "Item type: " + type.getName();}
}
3.2 实现 **<font style="color:rgb(36, 41, 47);">Converter</font>**
接口
通过实现 <font style="color:rgb(36, 41, 47);">Converter</font>
接口,将 <font style="color:rgb(36, 41, 47);">code</font>
转换为枚举实例。
示例代码
复制代码
@Component
public class ItemTypeConverter implements Converter<String, ItemType> {@Overridepublic ItemType convert(String source) {int code = Integer.parseInt(source);for (ItemType type : ItemType.values()) {if (type.getCode().equals(code)) {return type;}}throw new IllegalArgumentException("Invalid code: " + code);}
}
然后在控制器中直接使用:
@RestController
public class ItemController {@PostMapping("/item")public String createItem(@RequestParam ItemType type) {return "Item type: " + type.getName();}
}
3.3 使用 **<font style="color:rgb(36, 41, 47);">@JsonCreator</font>**
在枚举类中添加 <font style="color:rgb(36, 41, 47);">@JsonCreator</font>
方法,支持通过 <font style="color:rgb(36, 41, 47);">code</font>
反序列化枚举实例。
示例代码
复制代码
public enum ItemType implements BaseEnum {APARTMENT(1, "公寓"),ROOM(2, "房间");@EnumValueprivate Integer code;@JsonValueprivate String name;@Overridepublic Integer getCode() {return this.code;}@Overridepublic String getName() {return name;}ItemType(Integer code, String name) {this.code = code;this.name = name;}@JsonCreatorpublic static ItemType fromCode(Integer code) {for (ItemType type : ItemType.values()) {if (type.getCode().equals(code)) {return type;}}throw new IllegalArgumentException("Invalid code: " + code);}
}
知识点:
上述接口的功能是根据type(公寓/房间),查询标签列表。由于这个type字段在数据库、实体类、前后端交互的过程中有多种不同的形式,因此在请求和响应的过程中,type字段会涉及到多次类型转换。
首先明确一下type字段的各种形式:
- 数据库中数据库中的type字段为
tinyint
类型
+-------------+--------------+
| Field | Type |
+-------------+--------------+
| id | bigint |
| type | tinyint |
| name | varchar(255) |
| create_time | timestamp |
| update_time | timestamp |
| is_deleted | tinyint |
+-------------+--------------+
- 实体类实体类中的type字段为
ItemType
枚举类型LabelInfo
实体类如下
@Schema(description = "标签信息表")
@TableName(value = "label_info")
@Data
public class LabelInfo extends BaseEntity {private static final long serialVersionUID = 1L;@Schema(description = "类型")@TableField(value = "type")private ItemType type;@Schema(description = "标签名称")@TableField(value = "name")private String name;
}
ItemType
枚举类如下
public enum ItemType {APARTMENT(1, "公寓"),ROOM(2, "房间");private Integer code;private String name;ItemType(Integer code, String name) {this.code = code;this.name = name;}
}
- 前后端交互中前后端交互所传递的数据中type字段为数字(1/2)。
具体转换过程如下图所示:
- 请求流程
说明
- SpringMVC中的
WebDataBinder
组件负责将HTTP的请求参数绑定到Controller方法的参数,并实现参数类型的转换。 - Mybatis中的
TypeHandler
用于处理Java中的实体对象与数据库之间的数据类型转换。
- SpringMVC中的
- 响应流程
说明
- SpringMVC中的
HTTPMessageConverter
组件负责将Controller方法的返回值(Java对象)转换为HTTP响应体中的JSON字符串,或者将请求体中的JSON字符串转换为Controller方法中的参数(Java对象),例如下一个接口保存或更新标签信息
- SpringMVC中的
下面介绍一下每个环节的类型转换原理
:::color3
- WebDataBinder枚举类型转换
WebDataBinder
依赖于Converter
实现类型转换,若Controller方法声明的@RequestParam
参数的类型不是String
,WebDataBinder
就会自动进行数据类型转换。SpringMVC提供了常用类型的转换器,例如String
到Integer
、String
到Date
,String
到Boolean
等等,其中也包括String
到枚举类型,但是String
到枚举类型的默认转换规则是根据实例名称(“APARTMENT”)转换为枚举对象实例(ItemType.APARTMENT)。若想实现**<font style="color:#DF2A3F;">code</font>**
属性到枚举对象实例的转换,需要自定义**<font style="color:#DF2A3F;">Converter</font>**
,代码如下,具体内容可参考官方文档。
:::
但是我们有很多的枚举类型都需要考虑类型转换这个问题,按照上述思路,我们需要为每个枚举类型都定义一个Converter,并且每个Converter的转换逻辑都完全相同,针对这种情况,我们使用**<font style="color:#DF2A3F;">ConverterFactory</font>**
接口更为合适,这个接口可以将同一个转换逻辑应用到一个接口的所有实现类,因此我们可以定义一个BaseEnum
接口,然后另所有的枚举类都实现该接口,然后就可以自定义ConverterFactory
,集中编写各枚举类的转换逻辑了。具体实现如下:
- 在**web-admin模块**自定义`com.pkq.lease.web.admin.custom.converter.StringToItemTypeConverter`
@Component
public class StringToItemTypeConverter implements Converter<String, ItemType> {@Overridepublic ItemType convert(String code) {for (ItemType value : ItemType.values()) {if (value.getCode().equals(Integer.valueOf(code))) {return value;}}throw new IllegalArgumentException("code非法");}
}
- 注册上述的`StringToItemTypeConverter`,在**web-admin模块**创建`com.atguigu.lease.web.admin.custom.config.WebMvcConfiguration`,内容如下:
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {@Autowiredprivate StringToItemTypeConverter stringToItemTypeConverter;@Overridepublic void addFormatters(FormatterRegistry registry) {registry.addConverter(this.stringToItemTypeConverter);}
}
- 在**model模块**定义`com.atguigu.lease.model.enums.BaseEnum`接口
public interface BaseEnum {Integer getCode();String getName();
}
- 令所有`com.pkq.lease.model.enums`包下的枚举类都实现`BaseEnun`接口
- 在**web-admin模块**自定义`com.pkq.lease.web.admin.custom.converter.StringToBaseEnumConverterFactory`
@Component
public class StringToBaseEnumConverterFactory implements ConverterFactory<String, BaseEnum> {//通过 enumType.getEnumConstants() 获取枚举的所有实例。//遍历枚举实例,调用 getCode() 方法(BaseEnum 接口中的方法)与输入的字符串进行比较。//如果找到匹配的枚举,返回该枚举;否则抛出异常。@Overridepublic <T extends BaseEnum> Converter<String, > getConverter(Class<T> targetType) {return new Converter<String, T>() {@Overridepublic T convert(String source) {//Class.getEnumConstants() 方法是 Java 反射 API 中的一个方法,用于获取表示枚举类型的 Class 对象中所有枚举常量的数组for (T enumConstant : targetType.getEnumConstants()) {if (enumConstant.getCode().equals(Integer.valueOf(source))) {return enumConstant;}}throw new IllegalArgumentException("非法的枚举值:" + source);}};}
}
- 注册上述的`ConverterFactory`,在**web-admin模块**创建`com.pkq.lease.web.admin.custom.config.WebMvcConfiguration`,内容如下:
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {@Autowiredprivate StringToBaseEnumConverterFactory stringToBaseEnumConverterFactory;@Overridepublic void addFormatters(FormatterRegistry registry) {registry.addConverterFactory(this.stringToBaseEnumConverterFactory);}
}
**注意:**最终采用的是ConverterFactory
方案,因此StringToItemTypeConverter
相关代码可以直接删除。
- TypeHandler枚举类型转换Mybatis预置的
TypeHandler
可以处理常用的数据类型转换,例如String
、Integer
、Date
等等,其中也包含枚举类型,但是枚举类型的默认转换规则是枚举对象实例(ItemType.APARTMENT)和实例名称(“APARTMENT”)相互映射。若想实现code
属性到枚举对象实例的相互映射,需要自定义TypeHandler
。不过MybatisPlus提供了一个通用的处理枚举类型的TypeHandler。其使用十分简单,只需在ItemType
枚举类的code
属性上增加一个注解@EnumValue
,Mybatis-Plus便可完成从ItemType
对象到code
属性之间的相互映射,具体配置如下。
public enum ItemType {APARTMENT(1, "公寓"),ROOM(2, "房间");@EnumValueprivate Integer code;private String name;ItemType(Integer code, String name) {this.code = code;this.name = name;}
}
- HTTPMessageConverter枚举类型转换
HttpMessageConverter
依赖于Json序列化框架(默认使用Jackson)。其对枚举类型的默认处理规则也是枚举对象实例(ItemType.APARTMENT)和实例名称(“APARTMENT”)相互映射。不过其提供了一个注解@JsonValue
,同样只需在ItemType
枚举类的code
属性上增加一个注解@JsonValue
,Jackson便可完成从ItemType
对象到code
属性之间的互相映射。具体配置如下,详细信息可参考Jackson官方文档。
@Getter
public enum ItemType {APARTMENT(1, "公寓"),ROOM(2, "房间");@EnumValue@JsonValueprivate Integer code;private String name;ItemType(Integer code, String name) {this.code = code;this.name = name;}
}
1.3.2 保存或更新标签信息
在LabelController
中增加如下内容
@Operation(summary = "保存或更新标签信息")
@PostMapping("saveOrUpdate")
public Result saveOrUpdateLabel(@RequestBody LabelInfo labelInfo) {service.saveOrUpdate(labelInfo);return Result.ok();
}
1.3.3 根据ID删除标签
在LabelController
中增加如下内容
@Operation(summary = "根据id删除标签信息")
@DeleteMapping("deleteById")
public Result deleteLabelById(@RequestParam Long id) {service.removeById(id);return Result.ok();
}
1.4 配套管理(作业)
配套管理共有三个接口,分别是**[根据类型]查询配套列表**、保存或更新配套信息和根据ID删除配套,具体实现如下。
在FacilityController
中增加如下内容
@Tag(name = "配套管理")
@RestController
@RequestMapping("/admin/facility")
public class FacilityController {@Autowiredprivate FacilityInfoService service;@Operation(summary = "[根据类型]查询配套信息列表")@GetMapping("list")public Result<List<FacilityInfo>> listFacility(@RequestParam(required = false) ItemType type) {LambdaQueryWrapper<FacilityInfo> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(type != null, FacilityInfo::getType, type);List<FacilityInfo> list = service.list(queryWrapper);return Result.ok(list);}@Operation(summary = "新增或修改配套信息")@PostMapping("saveOrUpdate")public Result saveOrUpdate(@RequestBody FacilityInfo facilityInfo) {service.saveOrUpdate(facilityInfo);return Result.ok();}@Operation(summary = "根据id删除配套信息")@DeleteMapping("deleteById")public Result removeFacilityById(@RequestParam Long id) {service.removeById(id);return Result.ok();}}
1.5 基本属性管理
房间基本属性管理共有五个接口,分别是保存或更新属性名称、保存或更新属性值、查询全部属性名称和属性值列表、根据ID删除属性名称、根据ID删除属性值。下面逐一是实现。
:::color3
attr_key房间基本属性表存储房间的属性
attr_value 房间基本属性值表存储房间属性对应的值
:::
首先在AttrController
中注入AttrKeyService
和AttrValueService
,如下:
@Tag(name = "房间属性管理")
@RestController
@RequestMapping("/admin/attr")
public class AttrController {@Autowiredprivate AttrKeyService attrKeyService;@Autowiredprivate AttrValueService attrValueService;
}
1.5.1 保存或更新属性名称
在AttrController
增加如下内容
@Operation(summary = "保存或更新属性名称")
@PostMapping("key/saveOrUpdate")
public Result saveOrUpdateAttrKey(@RequestBody AttrKey attrKey) {attrKeyService.saveOrUpdate(attrKey);return Result.ok();
}
1.5.2 保存或更新属性值
在AttrController
中增加如下内容
@Operation(summary = "保存或更新属性值")
@PostMapping("value/saveOrUpdate")
public Result saveOrUpdateAttrValue(@RequestBody AttrValue attrValue) {attrValueService.saveOrUpdate(attrValue);return Result.ok();
}
1.5.3 查询全部属性名称和属性值列表
- 多领域模型介绍
- POJO : POJO指的是普通的Java对象,没有任何特殊限制或要求,不依赖于特定的框架或接口。它通常用于表示简单的数据对象,只包含私有字段、对应的
getter
和setter
方法以及一些业务逻辑。 - Entity: Entity表示系统中具有独特身份的业务对象。它通常映射到数据库表中的记录,有唯一的标识符(ID)并包含与业务相关的数据和行为。
- VO: VO(Value Object)是一种用于表示值的对象,通常是不可变的,只包含数据而没有业务行为。它用于封装一组相关的数据,常用于传递数据结构。
- POJO : POJO指的是普通的Java对象,没有任何特殊限制或要求,不依赖于特定的框架或接口。它通常用于表示简单的数据对象,只包含私有字段、对应的
- 查看响应的数据结构查看web-admin模块下的
com.pkq.lease.web.admin.vo.attr.AttrKeyVo
,内容如下:
@Data
public class AttrKeyVo extends AttrKey {@Schema(description = "属性值列表")private List<AttrValue> attrValueList;
}
- 编写Controller层逻辑在
AttrController
中添加如下内容
@Operation(summary = "查询全部属性名称和属性值列表")
@GetMapping("list")
public Result<List<AttrKeyVo>> listAttrInfo() {List<AttrKeyVo> list = attrKeyService.listAttrInfo();return Result.ok(list);
}
- 编写Service层逻辑在
AttrKeyService
中增加如下内容
List<AttrKeyVo> listAttrInfo();
在AttrKeyServiceImpl
中增加如下内容
@Autowired
private AttrKeyMapper mapper;@Override
public List<AttrKeyVo> listAttrInfo() {return mapper.listAttrInfo();
}
- 编写Mapper层逻辑在
AttrKeyMapper
中增加如下内容
List<AttrKeyVo> listAttrInfo();
对应的在AttrKeyMapper.xml
中增加如下内容
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.pkq.lease.web.admin.mapper.AttrKeyMapper"><resultMap id="BaseResultMap" type="com.pkq.lease.web.admin.vo.attr.AttrKeyVo"><id property="id" column="id"/><result property="name" column="key_name"/><collection property="attrValueList" ofType="com.pkq.lease.model.entity.AttrValue"><id column="value_id" property="id"/><result column="value_name" property="name"/><result column="key_id" property="attrKeyId"/></collection></resultMap><select id="listAttrInfo" resultMap="BaseResultMap">select k.id,k.name key_name,v.id value_id,v.name value_name,v.attr_key_id key_idfrom attr_key kleft join attr_value v on k.id = v.attr_key_id and v.is_deleted = 0where k.is_deleted = 0</select></mapper>
1.5.4 根据ID删除属性名称
在AttrController
中增加如下内容,注意删除属性名称时,会一并删除其下的所有属性值
@Operation(summary = "根据id删除属性名称")
@DeleteMapping("key/deleteById")
public Result removeAttrKeyById(@RequestParam Long attrKeyId) {//删除attrKeyattrKeyService.removeById(attrKeyId);//删除attrValueLambdaQueryWrapper<AttrValue> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(AttrValue::getAttrKeyId, attrKeyId);attrValueService.remove(queryWrapper);return Result.ok();
}
1.5.5 根据ID删除属性值
在AttrController
中增加如下内容
@Operation(summary = "根据id删除属性值")
@DeleteMapping("value/deleteById")
public Result removeAttrValueById(@RequestParam Long id) {attrValueService.removeById(id);return Result.ok();
}
1.6 公寓杂费管理(作业)
公寓杂费管理共有五个接口,分别是保存或更新杂费名称、保存或更新杂费值、查询全部杂费名称和杂费值列表、根据ID删除杂费名称、根据ID删除杂费值。下面逐一实现!
首先在FeeController
中注入FeeKeyService
和FeeValueService
,如下
@Tag(name = "房间杂费管理")
@RestController
@RequestMapping("/admin/fee")
public class FeeController {@Autowiredprivate FeeKeyService feeKeyService;@Autowiredprivate FeeValueService feeValueService;
}
1.6.1 保存或更新杂费名称
在FeeController
中增加如下内容
@Operation(summary = "保存或更新杂费名称")
@PostMapping("key/saveOrUpdate")
public Result saveOrUpdateFeeKey(@RequestBody FeeKey feeKey) {feeKeyService.saveOrUpdate(feeKey);return Result.ok();
}
1.6.2 保存或更新杂费值
在FeeController
中增加如下内容
@Operation(summary = "保存或更新杂费值")
@PostMapping("value/saveOrUpdate")
public Result saveOrUpdateFeeValue(@RequestBody FeeValue feeValue) {feeValueService.saveOrUpdate(feeValue);return Result.ok();
}
1.6.3 查询全部杂费名称和杂费值列表
- 查看响应的数据结构查看web-admin模块下创的
com.pkq.lease.web.admin.vo.fee.FeeKeyVo
,内容如下
@Data
public class FeeKeyVo extends FeeKey {@Schema(description = "杂费value列表")private List<FeeValue> feeValueList;
}
- 编写Controller层逻辑在
FeeController
中增加如下内容
@Operation(summary = "查询全部杂费名称和杂费值列表")
@GetMapping("list")
public Result<List<FeeKeyVo>> feeInfoList() {List<FeeKeyVo> list = feeKeyService.listFeeInfo();return Result.ok(list);
}
- 编写Service层逻辑
- 在
FeeKeyService
中增加如下内容
- 在
List<FeeKeyVo> listFeeInfo();
- 在`FeeKeyServiceImpl`中增加如下内容
@Autowired
private FeeKeyMapper mapper;@Override
public List<FeeKeyVo> listFeeInfo() {return mapper.listFeeInfo();
}
- 编写Mapper层逻辑
- 在
FeeKeyMapper
中增加如下内容
- 在
List<FeeKeyVo> listFeeInfo();
- 在`FeeKeyMapper.xml`中增加如下内容
<resultMap id="FeeInfoList" type="com.pkq.lease.web.admin.vo.fee.FeeKeyVo"><id property="id" column="id"/><result property="name" column="key_name"/><collection property="feeValueList" ofType="com.pkq.lease.model.entity.FeeValue"><id column="value_id" property="id"/><result column="value_name" property="name"/><result column="value_unit" property="unit"/><result column="key_id" property="feeKeyId"/></collection></resultMap><select id="listFeeInfo" resultMap="FeeInfoList">select k.id,k.name key_name,v.id value_id,v.name value_name,v.unit value_unit,v.fee_key_id key_idfrom fee_key kleft join fee_value v on k.id = v.fee_key_id and v.is_deleted = 0where k.is_deleted = 0
</select>
1.6.4 根据ID删除杂费名称
在FeeController
中增加如下内容
@Operation(summary = "根据id删除杂费名称")
@DeleteMapping("key/deleteById")
public Result deleteFeeKeyById(@RequestParam Long feeKeyId) {//删除杂费名称feeKeyService.removeById(feeKeyId);//删除杂费名称下的杂费值LambdaQueryWrapper<FeeValue> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(FeeValue::getFeeKeyId, feeKeyId);feeValueService.remove(queryWrapper);return Result.ok();
}
1.6.5 根据ID删除杂费值
在FeeController
中增加如下内容
@Operation(summary = "根据id删除杂费值")
@DeleteMapping("value/deleteById")
public Result deleteFeeValueById(@RequestParam Long id) {feeValueService.removeById(id);return Result.ok();
}
二、公寓信息管理
主要包含公寓的六个接口,以及地区和文件处理接口!!!
2.1 地区信息管理(作业)
地区信息管理共有三个接口,分别是查询省份信息列表,根据省份ID查询城市信息列表和根据城市ID查询区县信息列表,具体实现如下!
在RegionInfoController
中增加如下内容
@Tag(name = "地区信息管理")
@RestController
@RequestMapping("/admin/region")
public class RegionInfoController {@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);}
}
2.2 图片上传管理
概述 — MinIO中文文档 | MinIO Windows中文文档
由于公寓、房间等实体均包含图片信息,所以在新增或修改公寓、房间信息时,需要上传图片,因此我们需要实现一个上传图片的接口。
1. 图片上传流程
下图展示了新增房间或公寓时,上传图片的流程。
可以看出图片上传接口接收的是图片文件,返回的Minio对象的URL。
2. 图片上传接口开发
下面为该接口的具体实现
- 配置Minio Client
- 引入Minio Maven依赖在common模块的
pom.xml
文件增加如下内容:
- 引入Minio Maven依赖在common模块的
<dependency><groupId>io.minio</groupId><artifactId>minio</artifactId>
</dependency>
- 配置Minio相关参数在`application.yml`中配置Minio的`endpoint`、`accessKey`、`secretKey`、`bucketName`等参数
minio:endpoint: http://<hostname>:<port>access-key: <access-key>secret-key: <secret-key>bucket-name: <bucket-name>
注意:上述<hostname>
、<port>
等信息需根据实际情况进行修改。
- 在**common模块**中创建`com.pkq.lease.common.minio.MinioProperties`,内容如下
@ConfigurationProperties(prefix = "minio")
@Data
public class MinioProperties {private String endpoint;private String accessKey;private String secretKey;private String bucketName;
}
- 在**common模块**中创建`com.pkq.lease.common.minio.MinioConfiguration`,内容如下
@Configuration
@EnableConfigurationProperties(MinioProperties.class)
public class MinioConfiguration {@Autowiredprivate MinioProperties properties;@Beanpublic MinioClient minioClient() {return MinioClient.builder().endpoint(properties.getEndpoint()).credentials(properties.getAccessKey(), properties.getSecretKey()).build();}
}
- 开发图片上传接口
- 编写Controller层逻辑在
FileUploadController
中增加如下内容
- 编写Controller层逻辑在
@Tag(name = "文件管理")
@RequestMapping("/admin/file")
@RestController
public class FileUploadController {@Autowiredprivate FileService service;@Operation(summary = "上传文件")@PostMapping("upload")public Result<String> upload(@RequestParam MultipartFile file) {String url = service.upload(file);return Result.ok(url);}
}
说明:MultipartFile
是Spring框架中用于处理文件上传的类,它包含了上传文件的信息(如文件名、文件内容等)。
- 编写Service层逻辑* 在`FileService`中增加如下内容
String upload(MultipartFile file);
* 在`FileServiceImpl`中增加如下内容
:::color3
- 检查指定的 Bucket 是否存在,如果不存在则创建并设置 Bucket 的访问策略。
- 生成唯一的文件名(按日期和 UUID 组织)。
- 使用
<font style="color:rgb(64, 64, 64);">MinioClient</font>
将文件上传到 Minio。 - 返回文件的完整访问 URL。
:::
@Autowired
private MinioProperties properties;@Autowired
private MinioClient client;@Override
public String upload(MultipartFile file) {try {boolean bucketExists = client.bucketExists(BucketExistsArgs.builder().bucket(properties.getBucketName()).build());if (!bucketExists) {client.makeBucket(MakeBucketArgs.builder().bucket(properties.getBucketName()).build());client.setBucketPolicy(SetBucketPolicyArgs.builder().bucket(properties.getBucketName()).config(createBucketPolicyConfig(properties.getBucketName())).build());}String filename = new SimpleDateFormat("yyyyMMdd").format(new Date()) + "/" + UUID.randomUUID() + "-" + file.getOriginalFilename();client.putObject(PutObjectArgs.builder().bucket(properties.getBucketName()).object(filename).stream(file.getInputStream(), file.getSize(), -1).contentType(file.getContentType()).build());return String.join("/", properties.getEndpoint(), properties.getBucketName(), filename);} catch (Exception e) {e.printStackTrace();}return null;
}private String createBucketPolicyConfig(String bucketName) {return """{"Statement" : [ {"Action" : "s3:GetObject","Effect" : "Allow","Principal" : "*","Resource" : "arn:aws:s3:::%s/*"} ],"Version" : "2012-10-17"}""".formatted(bucketName);
}
注意:上述createBucketPolicyConfig
方法的作用是生成用于描述指定bucket访问权限的JSON字符串。最终生成的字符串格式如下,其表示,允许(Allow
)所有人(*
)获取(s3:GetObject
)指定桶(<bucket-name>
)的内容。
{"Statement" : [ {"Action" : "s3:GetObject","Effect" : "Allow","Principal" : "*","Resource" : "arn:aws:s3:::<bucket-name>/*"} ],"Version" : "2012-10-17"
}
由于公寓、房间的图片为公开信息,所以将其设置为所有人可访问。默认servlet限制上传数据大小为1MB,修改添加如何配置:
spring: servlet:multipart:max-file-size: 100MBmax-request-size: 150MB
* **异常处理**+ **问题说明**上述代码只是对`MinioClient`方法抛出的各种异常进行了捕获,然后打印了异常信息,目前这种处理逻辑,无论Minio是否发生异常,前端在上传文件时,总是会收到成功的响应信息。可按照以下步骤进行操作,查看具体现象关闭虚拟机中的Minio服务
systemctl stop minio
启动项目,并上传文件,观察接收的响应信息
+ **问题解决思路**为保证前端能够接收到正常的错误提示信息,应该将Service方法的异常抛出到Controller方法中,然后在Controller方法中对异常进行捕获并处理。具体操作如下**Service层代码**
@Override
public String upload(MultipartFile file) throws Exception{boolean bucketExists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(properties.getBucketName()).build());if (!bucketExists) {minioClient.makeBucket(MakeBucketArgs.builder().bucket(properties.getBucketName()).build());minioClient.setBucketPolicy(SetBucketPolicyArgs.builder().bucket(properties.getBucketName()).config(createBucketPolicyConfig(properties.getBucketName())).build());}String filename = new SimpleDateFormat("yyyyMMdd").format(new Date()) +"/" + UUID.randomUUID() + "-" + file.getOriginalFilename();minioClient.putObject(PutObjectArgs.builder().bucket(properties.getBucketName()).stream(file.getInputStream(), file.getSize(), -1).object(filename).contentType(file.getContentType()).build());return String.join("/",properties.getEndpoint(),properties.getBucketName(),filename);
}
Controller层代码
public Result<String> upload(@RequestParam MultipartFile file) {try {String url = fileService.upload(file);return Result.ok(url);} catch (Exception e) {e.printStackTrace();return Result.fail();}
}
+ **全局异常处理**按照上述写法,所有的Controller层方法均需要增加`try-catch`逻辑,使用Spring MVC提供的**全局异常处理**功能,可以将所有处理异常的逻辑集中起来,进而统一处理所有异常,使代码更容易维护。具体用法如下,详细信息可参考[官方文档](https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller/ann-exceptionhandler.html):在**common模块**中创建`com.pkq.lease.common.exception.GlobalExceptionHandler`类,内容如下
@RestControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(Exception.class)public Result error(Exception e){e.printStackTrace();return Result.fail();}
}
上述代码中的关键注解的作用如下@ControllerAdvice
用于声明处理全局Controller方法异常的类@ExceptionHandler
用于声明处理异常的方法,value
属性用于声明该方法处理的异常类型@ResponseBody
表示将方法的返回值作为HTTP的响应体注意:全局异常处理功能由SpringMVC提供,因此需要在common模块的pom.xml
中引入如下依赖
<!--spring-web-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
+ **修改Controller层代码**由于前文的`GlobalExceptionHandler`会处理所有Controller方法抛出的异常,因此Controller层就无需关注异常的处理逻辑了,因此Controller层代码可做出如下调整。
public Result<String> upload(@RequestParam MultipartFile file) throws Exception {String url = service.upload(file);return Result.ok(url);
}
2.3 公寓管理
:::color3
公寓和房间都可能有多张图片,为了进行区分,图片表要有一个类型字段来进行区分
:::
公寓管理共有六个接口,下面逐一实现。
首先在ApartmentController
中注入ApartmentInfoService
,如下
@Tag(name = "公寓信息管理")
@RestController
@RequestMapping("/admin/apartment")
public class ApartmentInfoController {@Autowiredprivate ApartmentInfoService apartmentInfoService;
}
2.3.1⭐⭐⭐ 保存或更新公寓信息(难)
- 查看请求的数据结构查看web-admin模块中的
com.pkq.lease.web.admin.vo.apartment.ApartmentSubmitVo
类,内容如下:
@Schema(description = "公寓信息")
@Data
public class ApartmentSubmitVo extends ApartmentInfo {@Schema(description="公寓配套id")private List<Long> facilityInfoIds;@Schema(description="公寓标签id")private List<Long> labelIds;@Schema(description="公寓杂费值id")private List<Long> feeValueIds;@Schema(description="公寓图片id")private List<GraphVo> graphVoList;}
- 编写Controller层逻辑在
ApartmentController
中增加如下内容
@Operation(summary = "保存或更新公寓信息")
@PostMapping("saveOrUpdate")
public Result saveOrUpdate(@RequestBody ApartmentSubmitVo apartmentSubmitVo) {apartmentInfoService.saveOrUpdateApartment(apartmentSubmitVo);return Result.ok();
}
- 编写Service层逻辑
注意:此接口前端不会传递城市名和省份名,需要自行查询和处理(联调测试)
- 在`ApartmentInfoService`中增加如下内容
void saveOrUpdateApartment(ApartmentSubmitVo apartmentSubmitVo);
- 在`ApartmentInfoServiceImpl`中增加如下内容**注意**:所需`Service`和`Mapper`的注入语句省略未写。
@Override
public void saveOrUpdateApartment(ApartmentSubmitVo apartmentSubmitVo) {boolean isUpdate = apartmentSubmitVo.getId()!=null;super.saveOrUpdate(apartmentSubmitVo);if (isUpdate){//1.删除图片列表LambdaQueryWrapper<GraphInfo> graphQueryWrapper = new LambdaQueryWrapper<>();graphQueryWrapper.eq(GraphInfo::getItemType, ItemType.APARTMENT);graphQueryWrapper.eq(GraphInfo::getItemId,apartmentSubmitVo.getId());graphInfoService.remove(graphQueryWrapper);//2.删除配套列表LambdaQueryWrapper<ApartmentFacility> facilityQueryWrapper = new LambdaQueryWrapper<>();facilityQueryWrapper.eq(ApartmentFacility::getApartmentId,apartmentSubmitVo.getId());apartmentFacilityService.remove(facilityQueryWrapper);//3.删除标签列表LambdaQueryWrapper<ApartmentLabel> labelQueryWrapper = new LambdaQueryWrapper<>();labelQueryWrapper.eq(ApartmentLabel::getApartmentId,apartmentSubmitVo.getId());apartmentLabelService.remove(labelQueryWrapper);//4.删除杂费列表LambdaQueryWrapper<ApartmentFeeValue> feeQueryWrapper = new LambdaQueryWrapper<>();feeQueryWrapper.eq(ApartmentFeeValue::getApartmentId,apartmentSubmitVo.getId());apartmentFeeValueService.remove(feeQueryWrapper);}//1.插入图片列表List<GraphVo> graphVoList = apartmentSubmitVo.getGraphVoList();if (!CollectionUtils.isEmpty(graphVoList)){ArrayList<GraphInfo> graphInfoList = new ArrayList<>();for (GraphVo graphVo : graphVoList) {GraphInfo graphInfo = new GraphInfo();graphInfo.setItemType(ItemType.APARTMENT);graphInfo.setItemId(apartmentSubmitVo.getId());graphInfo.setName(graphVo.getName());graphInfo.setUrl(graphVo.getUrl());graphInfoList.add(graphInfo);}graphInfoService.saveBatch(graphInfoList);}//2.插入配套列表List<Long> facilityInfoIdList = apartmentSubmitVo.getFacilityInfoIds();if (!CollectionUtils.isEmpty(facilityInfoIdList)){ArrayList<ApartmentFacility> facilityList = new ArrayList<>();for (Long facilityId : facilityInfoIdList) {ApartmentFacility apartmentFacility = new ApartmentFacility();apartmentFacility.setApartmentId(apartmentSubmitVo.getId());apartmentFacility.setFacilityId(facilityId);facilityList.add(apartmentFacility);}apartmentFacilityService.saveBatch(facilityList);}//3.插入标签列表List<Long> labelIds = apartmentSubmitVo.getLabelIds();if (!CollectionUtils.isEmpty(labelIds)) {List<ApartmentLabel> apartmentLabelList = new ArrayList<>();for (Long labelId : labelIds) {ApartmentLabel apartmentLabel = new ApartmentLabel();apartmentLabel.setApartmentId(apartmentSubmitVo.getId());apartmentLabel.setLabelId(labelId);apartmentLabelList.add(apartmentLabel);}apartmentLabelService.saveBatch(apartmentLabelList);}//4.插入杂费列表List<Long> feeValueIds = apartmentSubmitVo.getFeeValueIds();if (!CollectionUtils.isEmpty(feeValueIds)) {ArrayList<ApartmentFeeValue> apartmentFeeValueList = new ArrayList<>();for (Long feeValueId : feeValueIds) {ApartmentFeeValue apartmentFeeValue = new ApartmentFeeValue();apartmentFeeValue.setApartmentId(apartmentSubmitVo.getId());apartmentFeeValue.setFeeValueId(feeValueId);apartmentFeeValueList.add(apartmentFeeValue);}apartmentFeeValueService.saveBatch(apartmentFeeValueList);}
}
2.3.2 根据条件分页查询公寓列表
如果有空闲房间就说明可入住
- 查看请求和响应的数据结构
- 请求数据结构
current
和size
为分页相关参数,分别表示当前所处页面和每个页面的记录数。ApartmentQueryVo
为公寓的查询条件,详细结构如下:
- 请求数据结构
@Data
@Schema(description = "公寓查询实体")
public class ApartmentQueryVo {@Schema(description = "省份id")private Long provinceId;@Schema(description = "城市id")private Long cityId;@Schema(description = "区域id")private Long districtId;
}
- **响应数据结构**单个公寓信息记录可查看`com.pkq.lease.web.admin.vo.apartment.ApartmentItemVo`,内容如下:
@Data
@Schema(description = "后台管理系统公寓列表实体")
public class ApartmentItemVo extends ApartmentInfo {@Schema(description = "房间总数")private Long totalRoomCount;@Schema(description = "空闲房间数")private Long freeRoomCount;}
- 配置Mybatis-Plus分页插件在common模块中的
com.pkq.lease.common.mybatisplus.MybatisPlusConfiguration
中增加如下内容:
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));return interceptor;
}
- 接口实现
注意:
默认情况下Knife4j为该接口生成的接口文档如下图所示,其中的queryVo参数不方便调试
可在application.yml文件中增加如下配置,将queryVo做打平处理
springdoc:default-flat-param-object: true
将spring.default-flat-param-object
参数设置为true
后,效果如下。
- **编写Controller层逻辑**在`ApartmentController`中增加如下内容:
@Operation(summary = "根据条件分页查询公寓列表")
@GetMapping("pageItem")
public Result<IPage<ApartmentItemVo>> pageItem(@RequestParam long current, @RequestParam long size, ApartmentQueryVo queryVo) {IPage<ApartmentItemVo> page = new Page<>(current, size);IPage<ApartmentItemVo> list = apartmentInfoService.pageApartmentItemByQuery(page, queryVo);return Result.ok(list);
}
- **编写Service层逻辑*** 在`ApartmentInfoService`中增加如下内容
IPage<ApartmentItemVo> pageApartmentItemByQuery(IPage<ApartmentItemVo> page, ApartmentQueryVo queryVo);
* 在`ApartmentInfoServiceImpl`中增加如下内容
@Override
public IPage<ApartmentItemVo> pageApartmentItemByQuery(IPage<ApartmentItemVo> page, ApartmentQueryVo queryVo) {return apartmentInfoMapper.pageApartmentItemByQuery(page, queryVo);
}
- **编写Mapper层逻辑*** 在`ApartmentInfoMapper`中增加如下内容
IPage<ApartmentItemVo> pageApartmentItemByQuery(IPage<ApartmentItemVo> page, ApartmentQueryVo queryVo);
- sql 语句编写
然后把三张表联表查询
SELECTai.id,ai.`name`,ai.address_detail,ai.province_name,ai.city_name,ai.district_name, ai.phone,ai.is_release,IFNULL(ar.totalRoomCount,0), IFNULL(ar.totalRoomCount,0) - IFNULL(la.rentRoomCnt ,0) as freeRoomCount
FROM( SELECT * FROM apartment_info WHERE is_deleted = 0 AND province_id = 11 AND city_id = 1101 AND district_id = 110114 ) AS aiLEFT JOIN ( SELECT apartment_id, count(*) as totalRoomCount FROM room_info WHERE is_deleted = 0 GROUP BY apartment_id ) AS ar ON ai.id = ar.apartment_idLEFT JOIN ( SELECT apartment_id, count(*) as rentRoomCnt FROM lease_agreement WHERE is_deleted = 0 AND `status` IN ( 2, 5 ) GROUP BY apartment_id ) AS la ON la.apartment_id = ai.id
* 在`ApartmentInfoMapper.xml`中增加如下内容
<select id="pageApartmentItemByQuery" resultType="com.pkq.lease.web.admin.vo.apartment.ApartmentItemVo">select ai.id,ai.name,ai.introduction,ai.district_id,ai.district_name,ai.city_id,ai.city_name,ai.province_id,ai.province_name,ai.address_detail,ai.latitude,ai.longitude,ai.phone,ai.is_release,ifnull(tc.cnt,0) total_room_count,ifnull(tc.cnt,0) - ifnull(cc.cnt,0) free_room_countfrom (select id,name,introduction,district_id,district_name,city_id,city_name,province_id,province_name,address_detail,latitude,longitude,phone,is_releasefrom apartment_info<where>is_deleted=0<if test="queryVo.provinceId != null">and province_id=#{queryVo.provinceId}</if><if test="queryVo.cityId != null">and city_id=#{queryVo.cityId}</if><if test="queryVo.districtId != null">and district_id=#{queryVo.districtId}</if></where>) aileft join(select apartment_id,count(*) cntfrom room_infowhere is_deleted = 0and is_release = 1group by apartment_id) tcon ai.id = tc.apartment_idleft join(select apartment_id,count(*) cntfrom lease_agreementwhere is_deleted = 0and status in (2, 5)group by apartment_id) ccon ai.id = cc.apartment_id</select>
2.3.3 根据ID获取公寓详细信息
- 查看响应数据结构查看web-admin下的
com.pkq.lease.web.admin.vo.apartment.ApartmentDetailVo
,内容如下
@Schema(description = "公寓信息")
@Data
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 List<FeeValueVo> feeValueVoList;
}
- 编写Controller层逻辑在
ApartmentController
中增加如下内容
@Operation(summary = "根据ID获取公寓详细信息")
@GetMapping("getDetailById")
public Result<ApartmentDetailVo> getDetailById(@RequestParam Long id) {ApartmentDetailVo apartmentInfo = apartmentInfoService.getApartmentDetailById(id);return Result.ok(apartmentInfo);
}
- 编写Service层逻辑
- 在
ApartmentInfoService
中增加如下内容
- 在
ApartmentDetailVo getApartmentDetailById(Long id);
- 在`ApartmentInfoServiceImpl`中增加如下内容
@Override
public ApartmentDetailVo getApartmentDetailById(Long id) {//1.查询ApartmentInfoApartmentInfo apartmentInfo = this.getById(id);if (apartmentInfo == null) {return null;}//2.查询GraphInfoList<GraphVo> graphVoList = graphInfoMapper.selectListByItemTypeAndId(ItemType.APARTMENT, id);//3.查询LabelInfoList<LabelInfo> labelInfoList = labelInfoMapper.selectListByApartmentId(id);//4.查询FacilityInfoList<FacilityInfo> facilityInfoList = facilityInfoMapper.selectListByApartmentId(id);//5.查询FeeValueList<FeeValueVo> feeValueVoList = feeValueMapper.selectListByApartmentId(id);ApartmentDetailVo adminApartmentDetailVo = new ApartmentDetailVo();BeanUtils.copyProperties(apartmentInfo, adminApartmentDetailVo);adminApartmentDetailVo.setGraphVoList(graphVoList);adminApartmentDetailVo.setLabelInfoList(labelInfoList);adminApartmentDetailVo.setFacilityInfoList(facilityInfoList);adminApartmentDetailVo.setFeeValueVoList(feeValueVoList);return adminApartmentDetailVo;
}
- 编写Mapper层逻辑
- 编写公寓图片查询逻辑
- 在
GraphInfoMapper
中增加如下内容
- 在
- 编写公寓图片查询逻辑
List<GraphVo> selectListByItemTypeAndId(ItemType itemType, Long itemId);
* 在`GraphInfoMapper.xml`中增加如下内容
<select id="selectListByItemTypeAndId" resultType="com.pkq.lease.web.admin.vo.graph.GraphVo">selectname,urlfrom graph_infowhere is_deleted=0and item_type=#{itemType}and item_id=#{itemId}
</select>
- 编写公寓标签查询逻辑* 在`LabelInfoMapper`中增加如下内容
List<LabelInfo> selectListByApartmentId(Long id);
* 在`LabelInfoMapper.xml`中增加如下内容
<select id="selectListByApartmentId" resultType="com.pkq.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>
- 编写公寓配套查询逻辑* 在`FacilityInfoMapper`中增加如下内容
List<FacilityInfo> selectListByApartmentId(Long id);
* 在`FacilityInfoMapper.xml`中增加如下内容
<select id="selectListByApartmentId" resultType="com.pkq.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>
- 编写公寓杂费查询逻辑* 在`FeeValueMapper`中增加如下内容
List<FeeValueVo> selectListByApartmentId(Long id);
* 在`FeeValueMapper.xml`中增加如下内容
<select id="selectListByApartmentId" resultType="com.pkq.lease.web.admin.vo.fee.FeeValueVo">SELECT fv.id,fv.name,fv.unit,fv.fee_key_id,fk.name AS fee_key_nameFROM fee_value fvJOIN fee_key fk ON fv.fee_key_id = fk.idWHERE fv.is_deleted = 0AND fk.is_deleted = 0and fv.id in (select fee_value_idfrom apartment_fee_valuewhere is_deleted = 0and apartment_id = #{id})
</select>
2.3.4 根据ID删除公寓信息
- 编写Controller层逻辑在
ApartmentController
中增加如下内容
@Operation(summary = "根据id删除公寓信息")
@DeleteMapping("removeById")
public Result removeById(@RequestParam Long id) {service.removeApartmentById(id);return Result.ok();
}
- 编写Service层逻辑
- 在
ApartmentInfoService
中增加如下内容
- 在
void removeApartmentById(Long id);
- 在`ApartmentInfoServiceImpl`中增加如下内容
@Override
public void removeApartmentById(Long id) {super.removeById(id);//1.删除GraphInfoLambdaQueryWrapper<GraphInfo> graphQueryWrapper = new LambdaQueryWrapper<>();graphQueryWrapper.eq(GraphInfo::getItemType, ItemType.APARTMENT);graphQueryWrapper.eq(GraphInfo::getItemId, id);graphInfoService.remove(graphQueryWrapper);//2.删除ApartmentLabelLambdaQueryWrapper<ApartmentLabel> labelQueryWrapper = new LambdaQueryWrapper<>();labelQueryWrapper.eq(ApartmentLabel::getApartmentId, id);apartmentLabelService.remove(labelQueryWrapper);//3.删除ApartmentFacilityLambdaQueryWrapper<ApartmentFacility> facilityQueryWrapper = new LambdaQueryWrapper<>();facilityQueryWrapper.eq(ApartmentFacility::getApartmentId, id);apartmentFacilityService.remove(facilityQueryWrapper);//4.删除ApartmentFeeValueLambdaQueryWrapper<ApartmentFeeValue> feeQueryWrapper = new LambdaQueryWrapper<>();feeQueryWrapper.eq(ApartmentFeeValue::getApartmentId, id);apartmentFeeValueService.remove(feeQueryWrapper);}
知识点:由于公寓下会包含房间信息,因此在删除公寓时最好先判断一下该公寓下是否存在房间信息,若存在,则提醒用户先删除房间信息后再删除公寓信息,判断逻辑如下
LambdaQueryWrapper<RoomInfo> roomQueryWrapper = new LambdaQueryWrapper<>();
roomQueryWrapper.eq(RoomInfo::getApartmentId, id);
Long count = roomInfoMapper.selectCount(roomQueryWrapper);
if (count > 0) {//直接为前端返回如下响应:先删除房间信息再删除公寓信息
}
想要直接为前端返回响应,可利用前边配置的全局异常处理功能(此处直接抛出异常,全局异常处理器捕获到异常后,便会直接为前端返回响应结果)。为灵活设置响应信息,可自定义异常类,如下在common模块创建com.pkq.lease.common.exception.LeaseException
类,内容如下:
@Data
public class LeaseException extends RuntimeException {//异常状态码private Integer code;/*** 通过状态码和错误消息创建异常对象* @param message* @param code*/public LeaseException(String message, Integer code) {super(message);this.code = code;}/*** 根据响应结果枚举对象创建异常对象* @param resultCodeEnum*/public LeaseException(ResultCodeEnum resultCodeEnum) {super(resultCodeEnum.getMessage());this.code = resultCodeEnum.getCode();}@Overridepublic String toString() {return "LeaseException{" +"code=" + code +", message=" + this.getMessage() +'}';}
}
在common模块的com.pkq.lease.common.exception.GlobalExceptionHandler
类中,增加自定义异常类的处理逻辑
@ExceptionHandler(LeaseException.class)
@ResponseBody
public Result error(LeaseException e){e.printStackTrace();return Result.fail(e.getCode(), e.getMessage());
}
为Result新增一个构造方法,如下
public static <T> Result<T> fail(Integer code, String message) {Result<T> result = build(null);result.setCode(code);result.setMessage(message);return result;
}
removeApartmentById
方法的最终实现如下
@Override
public void removeApartmentById(Long id) {LambdaQueryWrapper<RoomInfo> roomQueryWrapper = new LambdaQueryWrapper<>();roomQueryWrapper.eq(RoomInfo::getApartmentId, id);Long count = roomInfoMapper.selectCount(roomQueryWrapper);if (count > 0) {throw new LeaseException(ResultCodeEnum.DELETE_ERROR);}//1.删除GraphInfoLambdaQueryWrapper<GraphInfo> graphQueryWrapper = new LambdaQueryWrapper<>();graphQueryWrapper.eq(GraphInfo::getItemType, ItemType.APARTMENT);graphQueryWrapper.eq(GraphInfo::getItemId, id);graphInfoService.remove(graphQueryWrapper);//2.删除ApartmentLabelLambdaQueryWrapper<ApartmentLabel> labelQueryWrapper = new LambdaQueryWrapper<>();labelQueryWrapper.eq(ApartmentLabel::getApartmentId, id);apartmentLabelService.remove(labelQueryWrapper);//3.删除ApartmentFacilityLambdaQueryWrapper<ApartmentFacility> facilityQueryWrapper = new LambdaQueryWrapper<>();facilityQueryWrapper.eq(ApartmentFacility::getApartmentId, id);apartmentFacilityService.remove(facilityQueryWrapper);//4.删除ApartmentFeeValueLambdaQueryWrapper<ApartmentFeeValue> feeQueryWrapper = new LambdaQueryWrapper<>();feeQueryWrapper.eq(ApartmentFeeValue::getApartmentId, id);apartmentFeeValueService.remove(feeQueryWrapper);//5.删除ApartmentInfosuper.removeById(id);
}
2.3.5 根据ID修改公寓发布状态
在ApartmentController
中增加如下内容:
@Operation(summary = "根据id修改公寓发布状态")
@PostMapping("updateReleaseStatusById")
public Result updateReleaseStatusById(@RequestParam Long id, @RequestParam ReleaseStatus status) {LambdaUpdateWrapper<ApartmentInfo> updateWrapper = new LambdaUpdateWrapper<>();updateWrapper.eq(ApartmentInfo::getId, id);updateWrapper.set(ApartmentInfo::getIsRelease, status);service.update(updateWrapper);return Result.ok();
}
2.3.6 根据区县ID查询公寓信息列表
在ApartmentController
中增加如下内容:
@Operation(summary = "根据区县id查询公寓信息列表")
@GetMapping("listInfoByDistrictId")
public Result<List<ApartmentInfo>> listInfoByDistrictId(@RequestParam Long id) {LambdaQueryWrapper<ApartmentInfo> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(ApartmentInfo::getDistrictId, id);List<ApartmentInfo> list = service.list(queryWrapper);return Result.ok(list);
}
三、房间管理
主要包含房间管理的六个接口!!
3.1 房间管理实现
房间管理共有六个接口,下面逐一实现
首先在RoomController
中注入RoomInfoService
,如下
@Tag(name = "房间信息管理")
@RestController
@RequestMapping("/admin/room")
public class RoomController {@Autowiredprivate RoomInfoService service;
}
3.1.1 保存或更新房间信息
- 查看请求的数据结构查看web-admin模块中的
com.pkq.lease.web.admin.vo.room.RoomSubmitVo
,内容如下
@Data
@Schema(description = "房间信息")
public class RoomSubmitVo extends RoomInfo {@Schema(description = "图片列表")private List<GraphVo> graphVoList;@Schema(description = "属性信息列表")private List<Long> attrValueIds;@Schema(description = "配套信息列表")private List<Long> facilityInfoIds;@Schema(description = "标签信息列表")private List<Long> labelInfoIds;@Schema(description = "支付方式列表")private List<Long> paymentTypeIds;@Schema(description = "可选租期列表")private List<Long> leaseTermIds;
}
- 编写Controller层逻辑在
RoomController
中增加如下内容
@Operation(summary = "保存或更新房间信息")
@PostMapping("saveOrUpdate")
public Result saveOrUpdate(@RequestBody RoomSubmitVo roomSubmitVo) {service.saveOrUpdateRoom(roomSubmitVo);return Result.ok();
}
- 编写Service 层逻辑在
RoomInfoService
中增加如下内容
void saveOrUpdateRoom(RoomSubmitVo roomSubmitVo);
在RoomInfoServiceImpl
中增加如下内容
@Override
public void saveOrUpdateRoom(RoomSubmitVo roomSubmitVo) {boolean isUpdate = roomSubmitVo.getId() != null;super.saveOrUpdate(roomSubmitVo);//若为更新操作,则先删除与Room相关的各项信息列表if (isUpdate) {//1.删除原有graphInfoListLambdaQueryWrapper<GraphInfo> graphQueryWrapper = new LambdaQueryWrapper<>();graphQueryWrapper.eq(GraphInfo::getItemType, ItemType.ROOM);graphQueryWrapper.eq(GraphInfo::getItemId, roomSubmitVo.getId());graphInfoService.remove(graphQueryWrapper);//2.删除原有roomAttrValueListLambdaQueryWrapper<RoomAttrValue> attrQueryMapper = new LambdaQueryWrapper<>();attrQueryMapper.eq(RoomAttrValue::getRoomId, roomSubmitVo.getId());roomAttrValueService.remove(attrQueryMapper);//3.删除原有roomFacilityListLambdaQueryWrapper<RoomFacility> facilityQueryWrapper = new LambdaQueryWrapper<>();facilityQueryWrapper.eq(RoomFacility::getRoomId, roomSubmitVo.getId());roomFacilityService.remove(facilityQueryWrapper);//4.删除原有roomLabelListLambdaQueryWrapper<RoomLabel> labelQueryWrapper = new LambdaQueryWrapper<>();labelQueryWrapper.eq(RoomLabel::getRoomId, roomSubmitVo.getId());roomLabelService.remove(labelQueryWrapper);//5.删除原有paymentTypeListLambdaQueryWrapper<RoomPaymentType> paymentQueryWrapper = new LambdaQueryWrapper<>();paymentQueryWrapper.eq(RoomPaymentType::getRoomId, roomSubmitVo.getId());roomPaymentTypeService.remove(paymentQueryWrapper);//6.删除原有leaseTermListLambdaQueryWrapper<RoomLeaseTerm> termQueryWrapper = new LambdaQueryWrapper<>();termQueryWrapper.eq(RoomLeaseTerm::getRoomId, roomSubmitVo.getId());roomLeaseTermService.remove(termQueryWrapper);}//1.保存新的graphInfoListList<GraphVo> graphVoList = roomSubmitVo.getGraphVoList();if (!CollectionUtils.isEmpty(graphVoList)) {ArrayList<GraphInfo> graphInfoList = new ArrayList<>();for (GraphVo graphVo : graphVoList) {GraphInfo graphInfo = new GraphInfo();graphInfo.setItemType(ItemType.ROOM);graphInfo.setItemId(roomSubmitVo.getId());graphInfo.setName(graphVo.getName());graphInfo.setUrl(graphVo.getUrl());graphInfoList.add(graphInfo);}graphInfoService.saveBatch(graphInfoList);}//2.保存新的roomAttrValueListList<Long> attrValueIds = roomSubmitVo.getAttrValueIds();if (!CollectionUtils.isEmpty(attrValueIds)) {List<RoomAttrValue> roomAttrValueList = new ArrayList<>();for (Long attrValueId : attrValueIds) {RoomAttrValue roomAttrValue = RoomAttrValue.builder().roomId(roomSubmitVo.getId()).attrValueId(attrValueId).build();roomAttrValueList.add(roomAttrValue);}roomAttrValueService.saveBatch(roomAttrValueList);}//3.保存新的facilityInfoListList<Long> facilityInfoIds = roomSubmitVo.getFacilityInfoIds();if (!CollectionUtils.isEmpty(facilityInfoIds)) {List<RoomFacility> roomFacilityList = new ArrayList<>();for (Long facilityInfoId : facilityInfoIds) {RoomFacility roomFacility = RoomFacility.builder().roomId(roomSubmitVo.getId()).facilityId(facilityInfoId).build();roomFacilityList.add(roomFacility);}roomFacilityService.saveBatch(roomFacilityList);}//4.保存新的labelInfoListList<Long> labelInfoIds = roomSubmitVo.getLabelInfoIds();if (!CollectionUtils.isEmpty(labelInfoIds)) {ArrayList<RoomLabel> roomLabelList = new ArrayList<>();for (Long labelInfoId : labelInfoIds) {RoomLabel roomLabel = RoomLabel.builder().roomId(roomSubmitVo.getId()).labelId(labelInfoId).build();roomLabelList.add(roomLabel);}roomLabelService.saveBatch(roomLabelList);}//5.保存新的paymentTypeListList<Long> paymentTypeIds = roomSubmitVo.getPaymentTypeIds();if (!CollectionUtils.isEmpty(paymentTypeIds)) {ArrayList<RoomPaymentType> roomPaymentTypeList = new ArrayList<>();for (Long paymentTypeId : paymentTypeIds) {RoomPaymentType roomPaymentType = RoomPaymentType.builder().roomId(roomSubmitVo.getId()).paymentTypeId(paymentTypeId).build();roomPaymentTypeList.add(roomPaymentType);}roomPaymentTypeService.saveBatch(roomPaymentTypeList);}//6.保存新的leaseTermListList<Long> leaseTermIds = roomSubmitVo.getLeaseTermIds();if (!CollectionUtils.isEmpty(leaseTermIds)) {ArrayList<RoomLeaseTerm> roomLeaseTerms = new ArrayList<>();for (Long leaseTermId : leaseTermIds) {RoomLeaseTerm roomLeaseTerm = RoomLeaseTerm.builder().roomId(roomSubmitVo.getId()).leaseTermId(leaseTermId).build();roomLeaseTerms.add(roomLeaseTerm);}roomLeaseTermService.saveBatch(roomLeaseTerms);}
}
3.1.2 根据条件分页查询房间列表
- 查看请求和响应的数据结构
- 请求数据结构
current
和size
为分页相关参数,分别表示当前所处页面和每个页面的记录数。RoomQueryVo
为房间的查询条件,详细结构如下:
- 请求数据结构
@Schema(description = "房间查询实体")
@Data
public class RoomQueryVo {@Schema(description = "省份Id")private Long provinceId;@Schema(description = "城市Id")private Long cityId;@Schema(description = "区域Id")private Long districtId;@Schema(description = "公寓Id")private Long apartmentId;
}
- **响应数据结构**单个房间信息记录可查看`com.pkq.lease.web.admin.vo.room.RoomItemVo`,内容如下:
@Data
@Schema(description = "房间信息")
public class RoomItemVo extends RoomInfo {@Schema(description = "租约结束日期")private Date leaseEndDate;@Schema(description = "当前入住状态")private Boolean isCheckIn;@Schema(description = "所属公寓信息")private ApartmentInfo apartmentInfo;
}
- 编写Controller层逻辑在
RoomController
中增加如下内容
@Operation(summary = "根据条件分页查询房间列表")
@GetMapping("pageItem")
public Result<IPage<RoomItemVo>> pageItem(@RequestParam long current, @RequestParam long size, RoomQueryVo queryVo) {IPage<RoomItemVo> page = new Page<>(current, size);IPage<RoomItemVo> result = service.pageRoomItemByQuery(page, queryVo);return Result.ok(result);
}
- 编写Service 层逻辑
- 在
RoomInfoService
中增加如下内容
- 在
IPage<RoomItemVo> pageRoomItemByQuery(IPage<RoomItemVo> page, RoomQueryVo queryVo);
- 在`RoomInfoServiceImpl`中增加如下内容
@Override
public IPage<RoomItemVo> pageRoomItemByQuery(IPage<RoomItemVo> page, RoomQueryVo queryVo) {return roomInfoMapper.pageRoomItemByQuery(page, queryVo);
}
- 编写Mapper层逻辑
- 在
RoomInfoMapper
中增加如下内容
- 在
IPage<RoomItemVo> pageRoomItemByQuery(IPage<RoomItemVo> page, RoomQueryVo queryVo);
- 在`RoomInfoMapper.xml`中增加如下内容
<resultMap id="RoomItemVoMap" type="com.pkq.lease.web.admin.vo.room.RoomItemVo" ><id property="id" column="id"/><association property="apartmentInfo" javaType="com.atguigu.lease.model.entity.ApartmentInfo" ><id property="id" column="apart_id"/><result property="isRelease" column="apart_is_release"/></association></resultMap><!-- 2,5是代表正在出租的房子-->
<select id="pageRoomItemByQuery" resultMap="RoomItemVoMap">select ri.*,la.room_id is not null is_check_in,la.lease_end_date,ai.id apart_id,ai.name,ai.introduction,ai.district_id,ai.district_name,ai.city_id,ai.city_name,ai.province_id,ai.province_name,ai.address_detail,ai.latitude,ai.longitude,ai.phone,ai.is_release apart_is_releasefrom room_info rileft join lease_agreement laon ri.id = la.room_idand la.is_deleted = 0and la.status in (2,5)left join apartment_info aion ri.apartment_id = ai.idand ai.is_deleted = 0<where>ri.is_deleted = 0<if test="queryVo.provinceId != null">ai.province_id = #{queryVo.provinceId}</if><if test="queryVo.cityId != null">and ai.city_id = #{queryVo.cityId}</if><if test="queryVo.districtId != null">and ai.district_id = #{queryVo.districtId}</if><if test="queryVo.apartmentId != null">and ri.apartment_id = #{queryVo.apartmentId}</if></where></select>
mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImplauto-mapping-behavior: full #自动映射
3.1.3 根据ID获取房间详细信息
- 查看响应数据结构查看web-admin下的
com.pkq.lease.web.admin.vo.room.RoomDetailVo
,内容如下
@Schema(description = "房间信息")
@Data
public class RoomDetailVo extends RoomInfo {@Schema(description = "所属公寓信息")private ApartmentInfo apartmentInfo;@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<LeaseTerm> leaseTermList;
}
- 编写Controller层逻辑在
RoomController
中增加如下内容
@Operation(summary = "根据id获取房间详细信息")
@GetMapping("getDetailById")
public Result<RoomDetailVo> getDetailById(@RequestParam Long id) {RoomDetailVo roomInfo = service.getRoomDetailById(id);return Result.ok(roomInfo);
}
- 编写Service 层逻辑
- 在
RoomInfoService
中增加如下内容
- 在
RoomDetailVo getRoomDetailById(Long id);
- 在`RoomInfoServiceImpl`中增加如下内容
@Override
public RoomDetailVo getRoomDetailById(Long id) {//1.查询RoomInfoRoomInfo roomInfo = roomInfoMapper.selectById(id);//2.查询所属公寓信息ApartmentInfo apartmentInfo = apartmentInfoMapper.selectById(roomInfo.getApartmentId());//3.查询graphInfoListList<GraphVo> graphVoList = graphInfoMapper.selectListByItemTypeAndId(ItemType.ROOM, id);//4.查询attrValueListList<AttrValueVo> attrvalueVoList = attrValueMapper.selectListByRoomId(id);//5.查询facilityInfoListList<FacilityInfo> facilityInfoList = facilityInfoMapper.selectListByRoomId(id);//6.查询labelInfoListList<LabelInfo> labelInfoList = labelInfoMapper.selectListByRoomId(id);//7.查询paymentTypeListList<PaymentType> paymentTypeList = paymentTypeMapper.selectListByRoomId(id);//8.查询leaseTermListList<LeaseTerm> leaseTermList = leaseTermMapper.selectListByRoomId(id);RoomDetailVo adminRoomDetailVo = new RoomDetailVo();BeanUtils.copyProperties(roomInfo, adminRoomDetailVo);adminRoomDetailVo.setApartmentInfo(apartmentInfo);adminRoomDetailVo.setGraphVoList(graphVoList);adminRoomDetailVo.setAttrValueVoList(attrvalueVoList);adminRoomDetailVo.setFacilityInfoList(facilityInfoList);adminRoomDetailVo.setLabelInfoList(labelInfoList);adminRoomDetailVo.setPaymentTypeList(paymentTypeList);adminRoomDetailVo.setLeaseTermList(leaseTermList);return adminRoomDetailVo;
}
- 编写Mapper层逻辑
- 编写房间属性查询逻辑
- 在
AttrValueMapper
中增加如下内容
- 在
- 编写房间属性查询逻辑
List<AttrValueVo> selectListByRoomId(Long id);
* 在`AttrValueMapper.xml`中增加如下内容
<select id="selectListByRoomId" resultType="com.pkq.lease.web.admin.vo.attr.AttrValueVo">select v.id,v.name,v.attr_key_id,k.name attr_key_namefrom attr_value vjoin attr_key k on v.attr_key_id = k.idwhere v.is_deleted = 0and k.is_deleted = 0and v.id in (select attr_value_idfrom room_attr_valuewhere is_deleted = 0and room_id = #{id})
</select>
- 编写房间配套查询逻辑* 在`FacilityInfoMapper`中增加如下内容
List<FacilityInfo> selectListByRoomId(Long id);
* 在`FacilityInfoMapper.xml`中增加如下内容
<select id="selectListByRoomId" resultType="com.pkq.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.pkq.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.pkq.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>
- 编写房间可选租期查询逻辑* 在`Mapper`中增加如下内容
List<LeaseTerm> selectListByRoomId(Long id);
* 在`Mapper.xml`中增加如下内容
<select id="selectListByRoomId" resultType="com.pkq.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>
3.1.4 根据ID删除房间信息
- 编写Controller层逻辑在
RoomController
中增加如下内容
@Operation(summary = "根据id删除房间信息")
@DeleteMapping("removeById")
public Result removeById(@RequestParam Long id) {service.removeRoomById(id);return Result.ok();
}
- 编写Service 层逻辑
- 在
RoomInfoService
中增加如下内容
- 在
void removeRoomById(Long id);
- 在`RoomInfoServiceImpl`中增加如下内容
@Override
public void removeRoomById(Long id) {//1.删除RoomInforemoveById(id);//2.删除graphInfoListLambdaQueryWrapper<GraphInfo> graphQueryWrapper = new LambdaQueryWrapper<>();graphQueryWrapper.eq(GraphInfo::getItemType, ItemType.ROOM);graphQueryWrapper.eq(GraphInfo::getItemId, id);graphInfoService.remove(graphQueryWrapper);//3.删除attrValueListLambdaQueryWrapper<RoomAttrValue> attrQueryWrapper = new LambdaQueryWrapper<>();attrQueryWrapper.eq(RoomAttrValue::getRoomId, id);roomAttrValueService.remove(attrQueryWrapper);//4.删除facilityInfoListLambdaQueryWrapper<RoomFacility> facilityQueryWrapper = new LambdaQueryWrapper<>();facilityQueryWrapper.eq(RoomFacility::getRoomId, id);roomFacilityService.remove(facilityQueryWrapper);//5.删除labelInfoListLambdaQueryWrapper<RoomLabel> labelQueryWrapper = new LambdaQueryWrapper<>();labelQueryWrapper.eq(RoomLabel::getRoomId, id);roomLabelService.remove(labelQueryWrapper);//6.删除paymentTypeListLambdaQueryWrapper<RoomPaymentType> paymentQueryWrapper = new LambdaQueryWrapper<>();paymentQueryWrapper.eq(RoomPaymentType::getRoomId, id);roomPaymentTypeService.remove(paymentQueryWrapper);//7.删除leaseTermListLambdaQueryWrapper<RoomLeaseTerm> termQueryWrapper = new LambdaQueryWrapper<>();termQueryWrapper.eq(RoomLeaseTerm::getRoomId, id);roomLeaseTermService.remove(termQueryWrapper);
}
3.1.5 根据id修改房间发布状态
在RoomController
中增加如下内容
@Operation(summary = "根据id修改房间发布状态")
@PostMapping("updateReleaseStatusById")
public Result updateReleaseStatusById(Long id, ReleaseStatus status) {LambdaUpdateWrapper<RoomInfo> updateWrapper = new LambdaUpdateWrapper<>();updateWrapper.eq(RoomInfo::getId, id);updateWrapper.set(RoomInfo::getIsRelease, status);service.update(updateWrapper);return Result.ok();
}
3.1.6 根据公寓ID查询房间列表
在RoomController
中增加如下内容
@GetMapping("listBasicByApartmentId")
@Operation(summary = "根据公寓id查询房间列表")
public Result<List<RoomInfo>> listBasicByApartmentId(Long id) {LambdaQueryWrapper<RoomInfo> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(RoomInfo::getApartmentId, id);queryWrapper.eq(RoomInfo::getIsRelease, ReleaseStatus.RELEASED);List<RoomInfo> roomInfoList = service.list(queryWrapper);return Result.ok(roomInfoList);
}
相关文章:
06-公寓租赁项目-后台管理-公寓管理篇
尚庭公寓项目/公寓管理模块 https://www.yuque.com/pkqzyh/qg2yge/5ba67653b51379d18df61b9c14c3e946 一、属性管理 属性管理页面包含公寓和房间各种可选的属性信息,其中包括房间的可选支付方式、房间的可选租期、房间的配套、公寓的配套等等。其所需接口如下 1.1…...
目前主流OCR/语义理解/ASR
OCR 基于多篇专业评测的结果,以下是目前免费开源OCR工具的推荐排名(侧重中文场景): 1. RapidOCR 优势:基于PaddleOCR优化,在印刷中文、自然场景文字识别中综合评分第一,支持180度旋转和低对比…...
Selenium 元素定位方法详解
Selenium 提供了多种元素定位方式,掌握这些方法是进行 Web 自动化测试的基础。以下是主要的元素定位方法及其使用示例: 1. 基本定位方法 1.1 通过 ID 定位 element driver.find_element(By.ID, "element_id") 1.2 通过 Name 定位 element …...
fastGPT—前端开发获取api密钥调用机器人对话接口(HTML实现)
官网文档链接:OpenAPI 介绍 | FastGPT 首先按照文档说明创建api密钥 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-sca…...
c语言数据结构--------拓扑排序和逆拓扑排序(Kahn算法和DFS算法实现)
#include <stdio.h> #include <string.h> #include <stdbool.h> #include <stdlib.h>//使用卡恩算法(Kahn)和深度优先算法(DFS)实现//拓扑排序和逆拓扑排序//拓扑排序和逆拓扑排序顶点顺序相反//图,邻接矩阵存储 #define MaxVertexNum 100 …...
日期类的实现
本文运用c类和对象中的构造函数, 析构函数 ,拷贝构造函数 , 赋值运算符重载等为大家模拟实现日期类的操作 #define _CRT_SECURE_NO_WARNINGS 1 #include"date.h" void Date:: showinfo() {cout << _year << "年&…...
3dgs通俗讲解
3d gaussian splatting:基于splatting和机器学习的三维重建方法。 特点: 无深度学习简单的机器学习大量的CG知识复杂的线性代数对GPU的高性能编程 一、什么是splatting 1、选择“雪球”; 为什么使用核(雪球) 各向…...
源码分析之Leaflet比例尺控件Control.Scale实现原理
概述 Control.Scale 是一个用于显示地图比例尺的控件,是 Leaflet 中实现比例尺控件的核心逻辑,用于在地图上动态显示公制(米/千米)和英制(英尺/英里)的比例尺。 源码分析 源码实现 Control.Scale的源码…...
【无标题 langsmith
【GPT入门】第32课 langsmith介绍与实战 1.lang smith作用2.lang smith配置方法3. 上手第一个lang smith3.1 可运行代码3.2 lang smith 官网,个人项目下 1.lang smith作用 LangSmith是由LangChain开发的一个平台,主要用于构建生产级LLM应用程序…...
智能建造新范式:装配式建筑 4.0 的数字化进阶
在全球数字化与可持续发展的浪潮中,建筑业正经历着第四次工业革命的深刻变革。装配式建筑4.0的出现,标志着建筑行业从传统的“钢筋水泥时代”迈向“数据驱动时代”,其核心在于通过技术融合重构建筑全生命周期的生产方式,实现从设计…...
从标准输入中读取所有内容sys.stdin.read()
sys.stdin.read().strip() 用于从标准输入中读取所有内容并去除首尾的空白字符。 1. sys.stdin.read() 作用:从标准输入流中读取所有内容,直到遇到文件结束符(EOF)。在命令行中,EOF 可以通过 CtrlD(Linux…...
网络:华为数通HCIA学习:静态路由基础
文章目录 前言静态路由基础静态路由应用场景 静态路由配置静态路由在串行网络的配置静态路由在以太网中的配置 负载分担配置验证 路由备份(浮动静态路由)配置验证 缺省路由配置验证 总结 华为HCIA 基础实验-静态路由 & eNSP静态路由 基础…...
DAY 35 leetcode 202--哈希表.快乐数
题号202 编写一个算法来判断一个数 n 是不是快乐数。 「快乐数」 定义为: 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。如果这个过程 结果为 1&a…...
Linux Command nmap 网络扫描
tags: 网络 文章目录 简介原理端口状态选项基本扫描发现扫描禁用DNS名称解析无ping扫描 端口扫描版本检测防火墙规避技术故障排除和调试NMAP 脚本 简介 Nmap(“ Network Mapper ”)是一个用于网络探索和安全审计的开源工具。它旨在快速扫描大型网络&…...
根据源码分析vue中nextTick的实现原理
根据源码分析vue中nextTick的实现原理 一. 核心变量定义二. 异步策略选择(降级处理)1. 微任务优先2. 降级到 MutationObserver3. 降级到宏任务 三、回调执行逻辑四、 nextTick 函数实现五、 与 Vue 更新流程的结合六、关键设计…...
Linux内核TCP/IP协议栈中的设计模式:从面向对象到系统级软件的跨界实践
引言 设计模式(Design Patterns)自GoF(Gang of Four)在1994年提出以来,已成为软件工程领域的核心概念。尽管其经典定义基于面向对象编程(OOP),但设计模式的本质是解决复杂问题的经验总结,而非局限于特定编程范式。本文以Linux内核的TCP/IP协议栈为例,探讨设计模式在…...
风云可测:华为AI天气大模型将暴雨预测误差缩至3公里内
华为云正式发布全球首个气象专用人工智能大模型"盘古气象",实现台风路径24小时预测误差<30公里、暴雨落区72小时精度91%,较传统数值预报效率提升10000倍。本文基于对西北太平洋10个台风回溯测试、全国2360个气象站验证数据,解析…...
DeepSeek-R1 面试题汇总
Deepseek-r1 面试宝典 原文地址:https://articles.zsxq.com/id_91kirfu15qxw.html DeepSeek-R1 面试题汇总 DeepSeek-R1 面试题汇总 GRPO(Group Relative Policy Optimization)常见面试题汇总篇 DeepSeek-R1 DeepSeek-R1-Zero 常见面试题汇总…...
ASM1042A型CANFD芯片通信可靠性研究
摘要 本文旨在深入探讨ASM1042A型CAN-FD芯片在多节点通信中的可靠性表现。通过对芯片的电气特性、测试环境、多节点通信测试结果等多方面进行分析,结合实验数据与理论研究,全面评估其在复杂通信场景下的性能与可靠性。研究结果表明,ASM1042A…...
Java8 到 Java21 系列之 Stream API:数据处理的新方式(Java 8)
Java 8 到 Java 21 系列之 Stream API:数据处理的新方式(Java 8) 系列目录 Java8 到 Java21 系列之 Lambda 表达式:函数式编程的开端(Java 8)Java 8 到 Java 21 系列之 Stream API:数据处理的…...
【每日一个知识点】分布式数据湖与实时计算
在现代数据架构中,分布式数据湖(Distributed Data Lake) 结合 实时计算(Real-time Computing) 已成为大数据处理的核心模式。数据湖用于存储海量的结构化和非结构化数据,而实时计算则确保数据能够被迅速处理…...
接口自动化学习三:参数化parameterize
使用parametrize之前: def add(x,y):return xy class TestAddFunction(object):def test01(self):resadd(2,4)assert 6resdef test02(self):resadd(4,6)assert 10resparametrize参数化之后: import pytest def add(x,y):return xydata[(10,20,30),(200…...
呼叫中心系统压力测试文档
前期准备 用户需要准备两台配置相同的服务器,A服务器和B服务器。我们在这两台服务器上部署相同授权的程序。 配置流程 1. 创建话术 A服务器和B服务器都需要创建压力测试放音的话术,用于放音。按图操作: 2. 线路和线路组配置 A服务器&am…...
从0开始的构建的天气预报小时钟(基于STM32F407ZGT6,ESP8266 + SSD1309)——第1章 简单的介绍一下ESP8266和他的编程指令
目录 ESP8266编程指令前导——三种工作模式 ESP8266编程指令 工作确认指令(用于非穿透模式下) 设置工作模式:ATCWMODEX 两个重要的复位 硬复位ATRESTORE 软复位ATRST 加入Wifi ATCWJAP 开始一次TCP通信 进入和退出穿透模式 进入 ES…...
Cadence Integrity 3D-IC的解密
Early System-Level Analysis and Signoff Flow 请看下期发布...
清晰易懂的 Flutter 开发环境搭建教程
Flutter 是 Google 推出的跨平台应用开发框架,支持 iOS/Android/Web/桌面应用开发。本教程将手把手教你完成 Windows/macOS/Linux 环境下的 Flutter 安装与配置,从零到运行第一个应用,全程避坑指南! 一、安装 Flutter SDK 1. 下载…...
NO.63十六届蓝桥杯备战|基础算法-⼆分答案|木材加工|砍树|跳石头(C++)
⼆分答案可以处理⼤部分「最⼤值最⼩」以及「最⼩值最⼤」的问题。如果「解空间」在从⼩到⼤的「变化」过程中,「判断」答案的结果出现「⼆段性」,此时我们就可以「⼆分」这个「解空间」,通过「判断」,找出最优解。 这个「⼆分答案…...
Python星球日记 - 第1天:欢迎来到Python星球
🌟引言: 上一篇:Python星球日记专栏介绍(持续更新ing) 名人说:莫听穿林打叶声,何妨吟啸且徐行。—— 苏轼《定风波莫听穿林打叶声》 创作者:Code_流苏(CSDN)(一个喜欢古诗…...
去中心化交易所(DEX)
核心概念与DEX类型 DEX vs CEX 中心化交易所(CEX)风险:资产托管风险(如2019年超2.9亿美元被盗)、隐私泄露(如50万用户信息泄漏)。 DEX优势:用户自持资产(非托管&#x…...
HTTP数据传输的几个关键字Header
本文着重针对http在传输数据时的几种封装方式进行描述。 1. Content-Type(描述body内容类型以及字符编码) HTTP的Content-Type用于定义数据传输的媒体类型(MIME类型),主要分为以下几类: (一)、基础文本类型 text/plain …...
Redis 的 Raft 选举协议
Redis 的 Raft 选举协议 主要用于 Redis Sentinel 和 Redis Cluster 的高可用实现中(尽管 Redis Cluster 默认使用类似 Gossip 的协议,但 Raft 的思想在 Sentinel 的领导者选举中有体现)。以下是关于 Raft 协议在 Redis 中的应用及脑裂问题的详细解析: 一、Redis 中的 Raft…...
sshd启动报错“Failed to start OpenSSH Server daemon”
“systemctl restart sshd”启动sshd服务异常,报错“Failed to start OpenSSH Server daemon”。 使用sshd -t命令检查sshd配置文件,返回关键信息gssapikexalgorithms相关错误。 解决方法 禁用 GSSAPI 相关的 KEX 算法 编辑sshd配置文件,注…...
MIT6.828 Lab3-2 Print a page table (easy)
实验内容 实现一个函数来打印页表的内容,帮助我们更好地理解 xv6 的三级页表结构。 修改内容 kernel/defs.h中添加函数声明,方便其它函数调用 void vmprint(pagetable_t);// lab3-2 Print a page tablekernel/vm.c中添加函数具体定义 采用…...
AI本地部署之ragflow
Ubunturagflowdeepseek本地部署目录 一、配置说明1. 软件配置说明2. 硬件配置说明 二、RagFlow安装和部署1. 前置条件2. 安装注:如果发现没有出现这个界面,可以进入ragflow/docker/ragflow-logs这个路径,查看ragflow_server.log文件中的内容&…...
源码分析之Leaflet属性控件Control.Attribution实现原理
概述 Control.Attribution 是一个 Leaflet 地图控件,用于显示地图的版权信息。它可以显示地图提供者的名称和链接,以及地图上的图层的版权信息。 源码分析 源码实现 Control.Attribution的源码实现如下 var ukrainianFlag <svg aria-hidden"…...
NO.62十六届蓝桥杯备战|基础算法-二分查找|查找元素的第一个和最后一个位置|牛可乐和魔法封印|A-B数对|烦恼的高考意愿(C++)
⼆分算法是我觉得在基础算法篇章中最难的算法。⼆分算法的原理以及模板其实是很简单的,主要的难点在于问题中的各种各样的细节问题。因此,⼤多数情况下,只是背会⼆分模板并不能解决题⽬,还要去处理各种乱七⼋糟的边界问题 34. 在…...
开源模型应用落地-Qwen2.5-Omni-7B模型-部署 “光速” 指南
一、前言 2025年3月,阿里巴巴通义千问团队开源的全模态大模型Qwen2.5-Omni-7B,犹如一记惊雷划破AI领域的长空。这个仅70亿参数的"小巧巨人",以端到端的架构实现了对文本、图像、音频、视频的全模态感知,更通过创新的Thinker-Talker双核架构,将人类"接收-思…...
顺序容器 -forward list单链表
forward list单链表是C11加入到STL的。 使用forward list,必须包含头文件<forward_list> #include <forward_list> 这个头文件被定义在命名空间std内。 namespace std {template <typename T,typename Allocator allocator<T> >class …...
C++:算术运算符
程序员Amin 🙈作者简介:练习时长两年半,全栈up主 🙉个人主页:程序员Amin 🙊 P S : 点赞是免费的,却可以让写博客的作者开心好久好久😎 📚系列专栏:Java全…...
缺页异常导致的iowait打印出相关文件的绝对路径
一、背景 在之前的博客 增加等IO状态的唤醒堆栈打印及缺页异常导致iowait分析-CSDN博客 里,我们进一步优化了D状态和等IO状态的事件的堆栈打印,补充了唤醒堆栈打印,也分析了一种比较典型的缺页异常filemap_fault导致的iowait的情况。 在这篇…...
【Centos】centos7内核升级-亲测有效
相关资源 通过网盘分享的文件:脚本升级 链接: https://pan.baidu.com/s/1yrCnflT-xWhAPVQRx8_YUg?pwd52xy 提取码: 52xy –来自百度网盘超级会员v5的分享 使用教程 将脚本文件上传到服务器的一个目录 执行更新命令 yum install -y linux-firmware执行脚本即可 …...
多模态模型:专栏概要与内容目录
文章目录 多模态模型📚 核心内容模块Stable Diffusion基础教程Stable Diffusion原理深度解析部署与环境配置其他多模态模型实践 多模态模型 🔥 专栏简介 | 解锁AI绘画与多模态模型的技术奥秘 探索多模态AI技术,掌握Stable Diffusion等流行框…...
1. 购物车
1. 购物车 咱们购物车基于 V2 装饰器进行开发,底气来源于 自定义组件混用场景指导 1.1. 素材整合 observedv2和Trace 数据模型和页面 // 其他略 // 购物车 export interface CartGoods {count: number;id: string;name: string;picture: string;price: number;…...
frp 让服务器远程调用本地的服务(比如你的java 8080项目)
1、服务器上安装frp 2、本地安装frp 服务器上 frps.toml 配置信息: bindPort 30000auth.token "密码" # 客户端连接密码vhostHTTPPort 8082 本地 frpc.toml serverAddr "服务器ip" serverPort 30000 auth.token "服务器上设置的…...
《AI大模型应知应会100篇》第56篇:LangChain快速入门与应用示例
第56篇:LangChain快速入门与应用示例 前言 最近最火的肯定非Manus和OpenManus莫属,因为与传统AI工具仅提供信息不同,Manus能完成端到端的任务闭环。例如用户发送“筛选本月抖音爆款视频”,它会自动完成: 爬取平台数据…...
大模型——如何在本地部署微软的OmniParser V2
微软的 OmniParser V2 是一款尖端的人工智能屏幕解析器,可通过分析屏幕截图从图形用户界面中提取结构化数据,使人工智能代理能够与屏幕元素进行无缝交互。该工具是构建自主图形用户界面代理的完美选择,它改变了自动化和工作流程优化的游戏规则。在本指南中,我们将介绍如何在…...
Oracle触发器使用(一):DML触发器
Oracle触发器使用(一):DML触发器 DML触发器条件谓词触发器INSTEAD OF DML触发器复合DML触发器Oracle数据库中的触发器(Trigger)本质上也是PL/SQL代码,触发器可以被Enable或者Disable,但是不能像存储过程那样被直接调用执行。 触发器不能独立存在,而是定义在表、视图、…...
智慧园区大屏如何实现全局监测:监测意义、内容、方式
智慧园区的价值不容小觑呀,可以说园区的大部分数据都在这个大屏上,监测数据越多,那么大屏的价值就越大。很多小伙伴拿到需求后感觉无从下手,本文在这里智慧园区大屏可以监测哪些内容、监测的意义、监测的方式等,欢迎点…...
LeetCode 解题思路 31(Hot 100)
解题思路: 递归参数: 字符串 s、结果集 result、当前路径 path、回文子串数组 dp、开始位置 start。递归过程: 当当前路径 path 的长度等于 s.length() 时,说明已经分割完成,加入结果集。若当前起止位置满足回文条件…...
fastAPI详细介绍以及使用方法
FastAPI是一个现代的Python web框架,它提供快速构建API的能力。它具有高性能、易用性和文档自动生成的特点,使得开发者能够快速开发高效的API服务。 以下是一些FastAPI的主要特点和优势: 快速:FastAPI基于Python 3.6的异步框架St…...