尚庭公寓(三)

首先创建数据库 导入数据库文件

   CREATE DATABASE lease CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

但是我这边又报了一个数据库的错误

[SQL] Query lease start
[ERR] 1293 - Incorrect table definition; there can be only one TIMESTAMP column with CURRENT_TIMESTAMP in DEFAULT or ON UPDATE clause
[ERR] 
INSERT INTO `room_payment_type` VALUES (55, 11, 8, '2023-08-13 23:57:42', NULL, 1);
INSERT INTO `room_payment_type` VALUES (56, 11, 10, '2023-08-13 23:57:42', NULL, 1);
INSERT INTO `room_payment_type` VALUES (57, 12, 8, '2023-08-13 23:58:53', NULL, 1);
INSERT INTO `room_payment_type` VALUES (58, 12, 10, '2023-08-13 23:58:53', NULL, 1);
INSERT INTO `room_payment_type` VALUES (59, 12, 7, '2023-08-13 23:58:53', NULL, 1);
INSERT INTO `room_payment_type` VALUES (60, 12, 6, '2023-08-13 23:58:53', NULL, 1);
INSERT INTO `room_payment_type` VALUES (61, 13, 6, '2023-08-13 23:59:50', NULL, 1);
INSERT INTO `room_payment_type` VALUES (62, 13, 7, '2023-08-13 23:59:50', NULL, 1);
INSERT INTO `room_payment_type` VALUES (63, 13, 8, '2023-08-13 23:59:50', NULL, 1);
INSERT INTO `room_payment_type` VALUES (64, 13, 10, '2023-08-13 23:59:50', NULL, 1);
INSERT INTO `room_payment_type` VALUES (65, 11, 6, '2023-08-14 00:00:35', NULL, 1);
INSERT INTO `room_payment_type` VALUES (66, 11, 7, '2023-08-14 00:00:35', NULL, 1);
INSERT INTO `room_payment_type` VALUES (67, 11, 8, '2023-08-14 00:00:35', NULL, 1);
INSERT INTO `room_payment_type` VALUES (68, 11, 10, '2023-08-14 00:00:35', NULL, 1);
INSERT INTO `room_payment_type` VALUES (69, 14, 6, '2023-08-14 00:06:39', NULL, 1);
INSERT INTO `room_payment_type` VALUES (70, 14, 7, '2023-08-14 00:06:39', NULL, 1);
INSERT INTO `room_payment_type` VALUES (71, 14, 8, '2023-08-14 00:06:39', NULL, 1);
INSERT INTO `room_payment_type` VALUES (72, 14, 10, '2023-08-14 00:06:39', NULL, 1);
INSERT INTO `room_payment_type` VALUES (73, 15, 6, '2023-08-14 00:07:43', NULL, 1);
INSERT INTO `room_payment_type` VALUES (74, 15, 7, '2023-08-14 00:07:43', NULL, 1);
INSERT INTO `room_payment_type` VALUES (75, 15, 8, '2023-08-14 00:07:43', NULL, 1);
INSERT INTO `room_payment_type` VALUES (76, 15, 10, '2023-08-14 00:07:43', NULL, 1);
INSERT INTO `room_payment_type` VALUES (77, 12, 6, '2023-08-14 00:07:58', NULL, 1);
INSERT INTO `room_payment_type` VALUES (78, 12, 7, '2023-08-14 00:07:58', NULL, 1);
INSERT INTO `room_payment_type` VALUES (79, 12, 8, '2023-08-14 00:07:58', NULL, 1);
INSERT INTO `room_payment_type` VALUES (80, 12, 10, '2023-08-14 00:07:58', NULL, 1);
INSERT INTO `room_payment_type` VALUES (81, 16, 6, '2023-08-14 00:09:07', NULL, 1);
INSERT INTO `room_payment_type` VALUES (82, 16, 7, '2023-08-14 00:09:07', NULL, 1);
INSERT INTO `room_payment_type` VALUES (83, 16, 8, '2023-08-14 00:09:07', NULL, 1);
INSERT INTO `room_payment_type` VALUES (84, 16, 10, '2023-08-14 00:09:07', NULL, 1);
INSERT INTO `room_payment_type` VALUES (85, 17, 6, '2023-08-14 10:36:19', NULL, 1);
INSERT INTO `room_payment_type` VALUES (86, 17, 7, '2023-08-14 10:36:19', NULL, 1);
INSERT INTO `room_payment_type` VALUES (87, 17, 8, '2023-08-14 10:36:19', NULL, 1);
INSERT INTO `room_payment_type` VALUES (88, 17, 10, '2023-08-14 10:36:19', NULL, 1);
INSERT INTO `room_payment_type` VALUES (89, 2, 6, '2023-08-14 12:31:24', NULL, 1);
INSERT INTO `room_payment_type` VALUES (90, 2, 7, '2023-08-14 12:31:24', NULL, 1);
INSERT INTO `room_payment_type` VALUES (91, 2, 8, '2023-08-14 12:31:24', NULL, 1);
INSERT INTO `room_payment_type` VALUES (92, 2, 10, '2023-08-14 12:31:24', NULL, 1);
INSERT INTO `room_payment_type` VALUES (93, 3, 8, '2023-08-14 12:31:46', NULL, 1);
INSERT INTO `room_payment_type` VALUES (94, 3, 10, '2023-08-14 12:31:46', NULL, 1);
INSERT INTO `room_payment_type` VALUES (95, 8, 6, '2023-08-14 12:32:33', NULL, 1);
INSERT INTO `room_payment_type` VALUES (96, 8, 7, '2023-08-14 12:32:33', NULL, 1);
INSERT INTO `room_payment_type` VALUES (97, 8, 8, '2023-08-14 12:32:33', NULL, 1);
INSERT INTO `room_payment_type` VALUES (98, 8, 10, '2023-08-14 12:32:33', NULL, 1);
INSERT INTO `room_payment_type` VALUES (99, 9, 6, '2023-08-14 12:32:42', NULL, 1);
INSERT INTO `room_payment_type` VALUES (100, 9, 7, '2023-08-14 12:32:42', NULL, 1);
INSERT INTO `room_payment_type` VALUES (101, 9, 8, '2023-08-14 12:32:42', NULL, 1);
INSERT INTO `room_payment_type` VALUES (102, 9, 10, '2023-08-14 12:32:42', NULL, 1);
INSERT
[ERR] 1050 - Table 'view_appointment' already exists
[ERR] 
CREATE TABLE `view_appointment`  (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '预约id',
  `user_id` bigint NULL DEFAULT NULL COMMENT '用户id',
  `name` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户姓名',
  `phone` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户手机号码',
  `apartment_id` int NULL DEFAULT NULL COMMENT '公寓id',
  `appointment_time` timestamp NULL DEFAULT NULL COMMENT '预约时间',
  `additional_info` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注信息',
  `appointment_status` tinyint NULL DEFAULT NULL COMMENT '预约状态(1:待看房,2:已取消,3已看房)',
  `create_time` timestamp NULL DEFAULT NULL COMMENT '创建时间',
  `update_time` timestamp NULL DEFAULT NULL COMMENT '更新时间',
  `is_deleted` tinyint NULL DEFAULT 0 COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 12 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '预约看房信息表' ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of view_appointment
-- ----------------------------
INSERT INTO `view_appointment` VALUES (1, 1, '张三', '13888888888', 9, '2023-07-14 09:01:01', '无', 1, '2023-07-14 09:40:43', NULL, 0);
INSERT INTO `view_appointment` VALUES (2, 2, '李四', '13666666666', 9, '2023-07-14 09:01:01', '无', 1, '2023-07-27 16:13:54', NULL, 0);
INSERT INTO `view_appointment` VALUES (10, 7, '张三', '13888888888', 9, '2023-08-14 15:40:00', '无', 1, '2023-08-14 12:44:50', '2023-08-14 15:10:04', 0);
INSERT INTO `view_appointment` VALUES (11, 7, '张三', '13888888888', 10, '2023-08-14 14:45:20', '', 1, '2023-08-14 12:45:51', '2023-08-14 14:49:58', 0);

SET FOREIGN_KEY_CHECKS = 1;
[SQL] Finished with error
 

 创建工程
按照如下目录结构创建一个多模块的Maven工程。

lease
├── common(公共模块——工具类、公用配置等)
│   ├── pom.xml
│   └── src
├── model(数据模型——与数据库相对应地实体类)
│   ├── pom.xml
│   └── src
├── web(Web模块)
│   ├── pom.xml
│   ├── web-admin(后台管理系统Web模块——包含mapper、service、controller)
│   │   ├── pom.xml
│   │   └── src
│   └── web-app(移动端Web模块——包含mapper、service、controller)
│       ├── pom.xml
│       └── src
└── pom.xmlweb中的pom文件中引入common和model

在**父工程**的pom.xml文件中增加如下内容 

<!-- 继承Spring Boot父项目 -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.0.5</version>
</parent>

<!-- 注意:直接替换pom文件中原有的properties -->
<properties>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <mybatis-plus.version>3.5.3.1</mybatis-plus.version>
    <swagger.version>2.9.2</swagger.version>
    <jwt.version>0.11.2</jwt.version>
    <easycaptcha.version>1.6.2</easycaptcha.version>
    <minio.version>8.2.0</minio.version>
    <knife4j.version>4.1.0</knife4j.version>
    <aliyun.sms.version>2.0.23</aliyun.sms.version>
</properties>

<!--配置dependencyManagement统一管理依赖版本-->
<dependencyManagement>
    <dependencies>
        <!--mybatis-plus-->
        <!--官方文档:https://baomidou.com/pages/bab2db/ -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>

        <!--knife4j文档-->
        <!--官方文档:https://doc.xiaominfo.com/docs/quick-start -->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
            <version>${knife4j.version}</version>
        </dependency>

        <!--JWT登录认证相关-->
        <!--官方文档:https://github.com/jwtk/jjwt#install-jdk-maven -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>${jwt.version}</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <scope>runtime</scope>
            <version>${jwt.version}</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <scope>runtime</scope>
            <version>${jwt.version}</version>
        </dependency>

        <!--图形验证码-->
        <!--官方文档:https://gitee.com/ele-admin/EasyCaptcha -->
        <dependency>
            <groupId>com.github.whvcse</groupId>
            <artifactId>easy-captcha</artifactId>
            <version>${easycaptcha.version}</version>
        </dependency>

        <!--对象存储,用于存储图像等非结构化数据-->
        <!--官方文档:https://min.io/docs/minio/linux/developers/minio-drivers.html?ref=docs#java-sdk -->
        <dependency>
            <groupId>io.minio</groupId>
            <artifactId>minio</artifactId>
            <version>${minio.version}</version>
        </dependency>

        <!--阿里云短信客户端,用于发送短信验证码-->
        <!--官方文档:https://help.aliyun.com/document_detail/215759.html?spm=a2c4g.215759.0.0.49f32807f4Yc0y -->
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>dysmsapi20170525</artifactId>
            <version>${aliyun.sms.version}</version>
        </dependency>
    </dependencies>
</dependencyManagement>

 

统一管理依赖文件的逻辑 首先dependency下面的version都是调用的不是直接填写的

在这个里面统一去配置 

然后再这里配置好的依赖不会存在依赖库中

这个里面没有 只是当其他模块调用的时候才会存在 但是其他模块的调用就只需要

不需要写版本号了 这是由外部统一管理了 保证了所有模块用的版本号一致

在**web模块**的pom.xml文件中增加如下内容


```xml
<!--包含spring web相关依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!--包含spring test相关依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
```

- 插件

```xml
<!-- Spring Boot Maven插件,用于打包可执行的JAR文件 -->
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>
```

2. 创建application.yml文件**
 

```yaml
server:
  port: 8080
```

 **3. 创建SpringBoot启动类**


```java
@SpringBootApplication
public class AdminWebApplication {
    public static void main(String[] args) {
        SpringApplication.run(AdminWebApplication.class, args);
    }
}

Mybatis-Plus为公用工具,故将其配置于**common模块

<!--mybatis-plus-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>

<!--mysql驱动-->
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
</dependency>

model里面存放的是我们的实体类 

在**model模块**的pom.xml文件中增加如下内容:

<!--mybatis-plus-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>

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

spring:
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    url: jdbc:mysql://<hostname>:<port>/<database>?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=GMT%2b8
    username: <username>
    password: <password>
    hikari:
      connection-test-query: SELECT 1 # 自动检测连接
      connection-timeout: 60000 #数据库连接超时时间,默认30秒
      idle-timeout: 500000 #空闲连接存活最大时间,默认600000(10分钟)
      max-lifetime: 540000 #此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认1800000即30分钟
      maximum-pool-size: 12 #连接池最大连接数,默认是10
      minimum-idle: 10 #最小空闲连接数量
      pool-name: SPHHikariPool # 连接池名称
      
#用于打印框架生成的sql语句,便于调试
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

springboot默认使用的就是hikari连接池再spring-boot-starter-jdbc依赖下 所以不需要引入

**3. 配置类**

在**common模块**下创建`com.atguigu.lease.common.mybatisplus.MybatisPlusConfiguration`类,内容如下:
 

@Configuration
@MapperScan("com.atguigu.lease.web.*.mapper")
public class MybatisPlusConfiguration {
  
}

扫描mapper一个是 逐个写上mapper注解 一个是统一的扫描使用 mapperscan

#### 7.2.1.3 Knife4j配置


在**web模块**的pom.xml文件添加如下内容

> 因为**web-app**模块同样需要Knife4j依赖,故在两个的父工程引入依赖即可

<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
</dependency>

 

在**model模块**的pom.xml文件添加上述内容

> 因为**model模块**下的实体类需要配置Knife4j相关注解,故也需引入Knife4j依赖

<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
</dependency>

**2. 配置类**

后台管理系统和移动端的接口配置并不相同,所以需各自编写一个配置类。在**web-admin模块**下创建`com.atguigu.lease.web.admin.custom.config.Knife4jConfiguration`类,内容如下:
 

@Configuration
public class Knife4jConfiguration {

    @Bean
    public OpenAPI customOpenAPI() {

        return new OpenAPI().info(
                new Info()
                        .title("后台管理系统API")
                        .version("1.0")
                        .description("后台管理系统API"));
    }
    
    @Bean
    public GroupedOpenApi systemAPI() {

        return GroupedOpenApi.builder().group("系统信息管理").
                pathsToMatch(
                        "/admin/system/**"
                ).
                build();
    }

    @Bean
    public GroupedOpenApi loginAPI() {

        return GroupedOpenApi.builder().group("后台登录管理").
                pathsToMatch(
                        "/admin/login/**",
                        "/admin/info"
                ).
                build();
    }
    
    @Bean
    public GroupedOpenApi apartmentAPI() {

        return GroupedOpenApi.builder().group("公寓信息管理").
                pathsToMatch(
                        "/admin/apartment/**",
                        "/admin/room/**",
                        "/admin/label/**",
                        "/admin/facility/**",
                        "/admin/fee/**",
                        "/admin/attr/**",
                        "/admin/payment/**",
                        "/admin/region/**",
                        "/admin/term/**",
                        "/admin/file/**"
                ).build();
    }
    @Bean
    public GroupedOpenApi leaseAPI() {
        return GroupedOpenApi.builder().group("租赁信息管理").
                pathsToMatch(
                        "/admin/appointment/**",
                        "/admin/agreement/**"
                ).build();
    }
    @Bean
    public GroupedOpenApi userAPI() {
        return GroupedOpenApi.builder().group("平台用户管理").
                pathsToMatch(
                        "/admin/user/**"
                ).build();
    }
}

这个配置类把后续所有接口都进行了一个统一的管理

**注意**:`pathsToMatch`参数需要根据实际情况进行配置。

#### 7.2.1.3 生成或导入基础代码

在完成上述配置后,便可使用一些逆向工具自动生成基础代码了(例如实体类、mapper、service等),在使用Mybatis-Plus作为存储层框架时,推荐使用IDEA中的[Mybatis X](https://baomidou.com/pages/ba5b24/)插件。除了可自动生成这些代码,也可直接导入资料中提供的代码。推荐大家直接导入。

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

 

**知识点**:

- 实体类中的公共字段(例如`id`、`create_time`、`update_time`、`is_deleted`)抽取到一个基类,进行统一管理,然后让各实体类继承该基类。


- 实体类中的状态字段(例如`status`)或类型字段(例如`type`),全部使用枚举类型。

  > 状态(类型)字段,在数据库中通常用一个数字表示一个状态(类型)。例如:订单状态(1:待支付,2:待发货,3:待收货,4:已收货,5:已完结)。若实体类中对应的字段也用数字类型,例如`int`,那么程序中就会有大量的如下代码:


  > order.setStatus(1);
  > 
  > if (order.getStatus() == 1) {
  >  order.setStatus(2);
  > }


  > 这些代码后期维护起来会十分麻烦,所以本项目中所有的此类字段均使用枚举类型。例如上述订单状态可定义为以下枚举:


  > public enum Status {
  > 
  >  CANCEL(0, "已取消"),
  >  WAIT_PAY(1, "待支付"),
  >  WAIT_TRANSFER(2, "待发货"),
  >  WAIT_RECEIPT(3, "待收货"),
  >  RECEIVE(4, "已收货"),
  >  COMPLETE(5, "已完结");
  > 
  >  private final Integer value;
  >  private final String desc;
  > 
  >  public Integer value() {
  >      return value;
  >  }
  >  public String desc() {
  >      return desc;
  >  }
  > }


  > 订单实体类中的状态字段定义为`Status`类型:


  > @Data
  > public class Order{
  >     private Integer id;
  >     private Integer userId;
  >     private Status status;
  >     ...
  > }


  > 这样上述代码便可调整为如下效果,后期维护起来会容易许多。


  > order.setStatus(Status.WAIT_PAY);


  
- 所有的实体类均实现了`Serializable`接口,方便对实体对象进行缓存。

- 所有的`Mapper`接口均没有使用`@Mapper`注解,而是使用`@MapperScan`注解统一扫描。
#### 7.2.1.4 导入接口定义代码


资料中提供了所有的Controller代码,并且Controller中定义好了每个接口(只有定义,没有实现),大家可直接导入接口定义相关的代码,然后只专注于接口逻辑的实现。

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

导入完成后,便可启动SpringBoot项目,并访问接口文档了,Knife4j文档的url为:http://localhost:8080/doc.html。

**知识点**:

- vo(View Object):用于封装或定义接口接收及返回的数据的结构。

vo与前端交互 dto与后端交互
- 统一接口返回数据结构:为方便前端对接口数据进行处理,统一接口返回数据结构是一个良好的习惯。

  以下是所有接口统一返回的数据结构

 {
      "code": 200,
      "message": "正常",
      "data": {
          "id": "1",
          "name": "zhangsan",
          "age": 10
      }
  }

返回的参数都是result

  以下是与上述结构相对应的Java类

    - Result

@Data
    public class Result<T> {
    
        //返回码
        private Integer code;
    
        //返回消息
        private String message;
    
        //返回数据
        private T data;
    
        public Result() {
        }
    
        private static <T> Result<T> build(T data) {
            Result<T> result = new Result<>();
            if (data != null)
                result.setData(data);
            return result;
        }
    
        public static <T> Result<T> build(T body, ResultCodeEnum resultCodeEnum) {
            Result<T> result = build(body);
            result.setCode(resultCodeEnum.getCode());
            result.setMessage(resultCodeEnum.getMessage());
            return result;
        }
    
        public static <T> Result<T> ok(T data) {
            return build(data, ResultCodeEnum.SUCCESS);
        }
    
        public static <T> Result<T> ok() {
            return Result.ok(null);
        }
    
        public static <T> Result<T> fail() {
            return build(null, ResultCodeEnum.FAIL);
        }
        
        public static <T> Result<T> fail(Integer code, String message) {
            Result<T> result = build(null);
            result.setCode(code);
            result.setMessage(message);
            return result;
        }
    }


有的有返回值 有的没有 有的是vo
  vo是专门为这个接口提供的一个返回值类型 可能他要返回的数据和之前的实体类不一致 可能多一些数据

vo基于之前的实体类多了一些信息

  - ResultCodeEnum

  
    为方便管理,可将返回码`code`和返回消息`message`封装到枚举类。
  

   @Getter
    public enum ResultCodeEnum {
    
        SUCCESS(200, "成功"),
        FAIL(201, "失败"),
        PARAM_ERROR(202, "参数不正确"),
        SERVICE_ERROR(203, "服务异常"),
        DATA_ERROR(204, "数据异常"),
        ILLEGAL_REQUEST(205, "非法请求"),
        REPEAT_SUBMIT(206, "重复提交"),
        DELETE_ERROR(207, "请先删除子集"),
    
        ADMIN_ACCOUNT_EXIST_ERROR(301, "账号已存在"),
        ADMIN_CAPTCHA_CODE_ERROR(302, "验证码错误"),
        ADMIN_CAPTCHA_CODE_EXPIRED(303, "验证码已过期"),
        ADMIN_CAPTCHA_CODE_NOT_FOUND(304, "未输入验证码"),
    
    
        ADMIN_LOGIN_AUTH(305, "未登陆"),
        ADMIN_ACCOUNT_NOT_EXIST_ERROR(306, "账号不存在"),
        ADMIN_ACCOUNT_ERROR(307, "用户名或密码错误"),
        ADMIN_ACCOUNT_DISABLED_ERROR(308, "该用户已被禁用"),
        ADMIN_ACCESS_FORBIDDEN(309, "无访问权限"),
    
        APP_LOGIN_AUTH(501, "未登陆"),
        APP_LOGIN_PHONE_EMPTY(502, "手机号码为空"),
        APP_LOGIN_CODE_EMPTY(503, "验证码为空"),
        APP_SEND_SMS_TOO_OFTEN(504, "验证法发送过于频繁"),
        APP_LOGIN_CODE_EXPIRED(505, "验证码已过期"),
        APP_LOGIN_CODE_ERROR(506, "验证码错误"),
        APP_ACCOUNT_DISABLED_ERROR(507, "该用户已被禁用"),
    
    
        TOKEN_EXPIRED(601, "token过期"),
        TOKEN_INVALID(602, "token非法");
    
    
        private final Integer code;
    
        private final String message;
    
        ResultCodeEnum(Integer code, String message) {
            this.code = code;
            this.message = message;
        }
    }

先定义一个build 然后往result里面set

 由于`Result`和`ResultCodeEnum`中使用`@Data`、`@Getter`注解,因此需要再**common模块**中引入`lombok`依赖。
  
 

  <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
  </dependency>

### 7.2.2 公寓信息管理

#### 7.2.2.1 房间支付方式管理

房间支付方式管理共有三个接口,分别是**查询全部支付方式列表**、**保存或更新支付方式**和**根据ID删除支付方式**,下面逐一实现。

首先在`PaymentTypeController`中注入`PaymentTypeService`依赖,如下

@Tag(name = "支付方式管理")
@RequestMapping("/admin/payment")
@RestController
public class PaymentTypeController {

    @Autowired
    private PaymentTypeService service;
}

由于service都实现了通用service

所以我们可以直接去用service调用

@Operation(summary = "查询全部支付方式列表")
@GetMapping("list")
public Result<List<PaymentType>> listPaymentType() {
    List<PaymentType> list = service.list();
    return Result.ok(list);
}

ok了 但是在数据的返回中 isdeleted还被返回到前端了 这个数据是逻辑删除了 所以我们要增加一个过滤条件

过滤条件 就是这三行代码 只将没被删除的返回

这样写的话那所有的查询都要加上这些一样的代码 我们可以直接使用mybatis plus的逻辑删除功能

他会自动为查询操作增加is_deleted=0的过滤条件

  - 步骤一:在`application.yml`中增加如下内容

    mybatis-plus:
      global-config:
        db-config:
          logic-delete-field: flag # 全局逻辑删除的实体字段名(配置后可以忽略不配置步骤二)
          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 = "创建时间")
        @JsonIgnore
        private Date createTime;
    
        @Schema(description = "更新时间")
        @JsonIgnore
        private 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`注解,该字段就会在序列化时被忽略。

@restcontroller就是将请求方法的返回值序列化成json字符串在返回给前端  他使用的框架就是json所以说那个忽略的注解 就是叫做jsonignore

  @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;
  
  }

##### 2. 保存或更新支付方式


在`PaymentTypeController`中增加如下内容

@Operation(summary = "保存或更新支付方式")
@PostMapping("saveOrUpdate")
public Result saveOrUpdatePaymentType(@RequestBody PaymentType paymentType) {
    service.saveOrUpdate(paymentType);
    return Result.ok();
}

这样进行测试的时候 使用增加一条方法 但是查不到数据 

在数据库中查到是因为 is_deleted是null(可以设置默认值 但是没设置)

保存或更新数据时,前端通常不会传入`isDeleted`、`createTime`、`updateTime`这三个字段,因此我们需要手动赋值。但是数据库中几乎每张表都有上述字段,所以手动去赋值就显得有些繁琐。为简化上述操作,我们可采取以下措施。

- `is_deleted`字段:可将数据库中该字段的默认值设置为0。

- `create_time`和`update_time`:可使用mybatis-plus的自动填充功能,所谓自动填充,就是通过统一配置,在插入或更新数据时,自动为某些字段赋值

  - 为相关字段配置触发填充的时机,例如`create_time`需要在插入数据时填充,而`update_time`需要在更新数据时填充。具体配置如下,观察`@TableField`注解中的`fill`属性。
 

@Data
    public class BaseEntity {
    
        @Schema(description = "主键")
        @TableId(value = "id", type = IdType.AUTO)
        private Long id;
    
        @Schema(description = "创建时间")
        @JsonIgnore
        @TableField(value = "create_time", fill = FieldFill.INSERT)
        private Date createTime;
    
        @Schema(description = "更新时间")
        @JsonIgnore
        @TableField(value = "update_time", fill = FieldFill.UPDATE)
        private Date updateTime;
    
        @Schema(description = "逻辑删除")
        @JsonIgnore
        @TableLogic
        @TableField("is_deleted")
        private Byte isDeleted;
    
    }

  - 配置自动填充的内容,具体配置如下
    在**common模块**下创建`com.atguigu.lease.common.mybatisplus.MybatisMetaObjectHandler`类,内容如下: 

@Component
    public class MybatisMetaObjectHandler implements MetaObjectHandler {
        @Override
        public void insertFill(MetaObject metaObject) {
            this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
        }
    
        @Override
        public void updateFill(MetaObject metaObject) {
            this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
        }
    }


  在做完上述配置后,当写入数据时,Mybatis-Plus会自动将实体对象的`create_time`字段填充为当前时间,当更新数据时,则会自动将实体对象的`update_time`字段填充为当前时间。 

为is_deleted设计默认值 

##### 3. 根据ID删除支付方式
在`PaymentTypeController`中增加如下内容

@Operation(summary = "根据ID删除支付方式")
@DeleteMapping("deleteById")
public Result deletePaymentById(@RequestParam Long id) {
    service.removeById(id);
    return Result.ok();
}

**知识点**:

MybatisPlus逻辑删除功能的使用。


#### 7.2.2.2 房间租期管理

**房间租期管理**共有三个接口,分别是**查询全部租期列表**、**保存或更新租期信息**和**根据ID删除租期**,具体实现如下。

在`LeaseTermController`中增加如下内容

@Tag(name = "租期管理")
@RequestMapping("/admin/term")
@RestController
public class LeaseTermController {

    @Autowired
    private 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();
    }
}

#### 7.2.2.3 标签管理

**标签管理**共有三个接口,分别是**[根据类型]查询标签列表**、**保存或更新标签信息**和**根据ID删除标签**,下面逐一实现。

首先在`LabelController`中注入`LabelInfoService`依赖,如下

@Tag(name = "标签管理")
@RestController
@RequestMapping("/admin/label")
public class LabelController {

    @Autowired
    private LabelInfoService service;
}

##### 1. [根据类型]查询标签列表

在`LabelController`中增加如下内容

@Operation(summary = "(根据类型)查询标签列表")
@GetMapping("list")
public Result<List<LabelInfo>> labelList(@RequestParam(required = false) ItemType type) {

    LambdaQueryWrapper<LabelInfo> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(type != null, LabelInfo::getType, type);
    List<LabelInfo> list = service.list(queryWrapper);
    return Result.ok(list);
}

required=false就是可以传也可以不传 在测试的时候报了一个类型转换的错误

**知识点**:

上述接口的功能是根据**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`枚举类如下

  ```java
  public enum ItemType {
  
      APARTMENT(1, "公寓"),
      ROOM(2, "房间");
  
      private Integer code;
      private String name;
  
      ItemType(Integer code, String name) {
          this.code = code;
          this.name = name;
      }
  }

从数字一到itemtype的转换类型不匹配那就是在这一步出错了

webdatebinder的 原理

按照他说的 他包括string到枚举类型 但是string到枚举类型的默认转换规则是根据实例名称转换为枚举对象实例的 所以说前端要给我们传过来的是department 或者是room这样的实例名称 而不能是1 或者2这种 所以我们要去自定义converter

 

这个包都是自定义的东西 在里面创建一个converter

 - 在**web-admin模块**自定义`com.atguigu.lease.web.admin.custom.converter.StringToItemTypeConverter`

这种不太好 因为当增加enum或者修改的时候还得修改这里 直接使用下面的代码

(所有的枚举类型都有下面的静态方法)

    @Component
    public class StringToItemTypeConverter implements Converter<String, ItemType> {
        @Override
        public 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 {
    
        @Autowired
        private StringToItemTypeConverter stringToItemTypeConverter;
    
        @Override
        public void addFormatters(FormatterRegistry registry) {
            registry.addConverter(this.stringToItemTypeConverter);
        }
    }

将上面那个类加上component 注入到bean中 然后这个类直接this就ok

  但是我们有很多的枚举类型都需要考虑类型转换这个问题,按照上述思路,我们需要为每个枚举类型都定义一个Converter,并且每个Converter的转换逻辑都完全相同,针对这种情况,我们使用[`ConverterFactory`](https://docs.spring.io/spring-framework/reference/core/validation/convert.html#core-convert-ConverterFactory-SPI)接口更为合适,这个接口可以将同一个转换逻辑应用到一个接口的所有实现类,因此我们可以定义一个`BaseEnum`接口,然后另所有的枚举类都实现该接口,然后就可以自定义`ConverterFactory`,集中编写各枚举类的转换逻辑了。具体实现如下:

  - 在**model模块**定义`com.atguigu.lease.model.enums.BaseEnum`接口

    public interface BaseEnum {
        Integer getCode();
        String getName();
    }

 - 令所有`com.atguigu.lease.model.enums`包下的枚举类都实现`BaseEnun`接口

  - 在**web-admin模块**自定义`com.atguigu.lease.web.admin.custom.converter.StringToBaseEnumConverterFactory`
 

    @Component
    public class StringToBaseEnumConverterFactory implements ConverterFactory<String, BaseEnum> {
        @Override
        public <T extends BaseEnum> Converter<String, T> getConverter(Class<T> targetType) {
            return new Converter<String, T>() {
                @Override
                public T convert(String source) {
    
                    for (T enumConstant : targetType.getEnumConstants()) {
                        if (enumConstant.getCode().equals(Integer.valueOf(source))) {
                            return enumConstant;
                        }
                    }
                    throw new IllegalArgumentException("非法的枚举值:" + source);
                }
            };
        }
    }

一个是传入类型一个是目标类型

 - 注册上述的`ConverterFactory`,在**web-admin模块**创建`com.atguigu.lease.web.admin.custom.config.WebMvcConfiguration`,内容如下:
 

 @Configuration
    public class WebMvcConfiguration implements WebMvcConfigurer {
    
        @Autowired
        private StringToBaseEnumConverterFactory stringToBaseEnumConverterFactory;
    
        @Override
        public void addFormatters(FormatterRegistry registry) {
            registry.addConverterFactory(this.stringToBaseEnumConverterFactory);
        }
    }

- **TypeHandler枚举类型转换**

 Mybatis预置的`TypeHandler`可以处理常用的数据类型转换,例如`String`、`Integer`、`Date`等等,其中也包含枚举类型,但是枚举类型的默认转换规则是枚举对象实例(ItemType.APARTMENT)和实例名称("APARTMENT")相互映射。若想实现`code`属性到枚举对象实例的相互映射,需要自定义`TypeHandler`。

  不过MybatisPlus提供了一个[通用的处理枚举类型的TypeHandler](https://baomidou.com/pages/8390a4/)。其使用十分简单,只需在`ItemType`枚举类的`code`属性上增加一个注解`@EnumValue`,Mybatis-Plus便可完成从`ItemType`对象到`code`属性之间的相互映射,具体配置如下。
 

  public enum ItemType {
  
      APARTMENT(1, "公寓"),
      ROOM(2, "房间");
  
      @EnumValue
      private Integer code;
      private String name;
  
      ItemType(Integer code, String name) {
          this.code = code;
          this.name = name;
      }
  }

加上enumvalue之后它显示的变成一个type了

这也不是我们想要的效果 这个是相应的问题出生在这里

- **HTTPMessageConverter枚举类型转换**

  `HttpMessageConverter`依赖于Json序列化框架(默认使用Jackson)。其对枚举类型的默认处理规则也是枚举对象实例(ItemType.APARTMENT)和实例名称("APARTMENT")相互映射。不过其提供了一个注解`@JsonValue`,同样只需在`ItemType`枚举类的`code`属性上增加一个注解`@JsonValue`,Jackson便可完成从`ItemType`对象到`code`属性之间的互相映射。具体配置如下,详细信息可参考Jackson[官方文档](https://fasterxml.github.io/jackson-annotations/javadoc/2.8/com/fasterxml/jackson/annotation/JsonValue.html)。
 

  @Getter
  public enum ItemType {
  
      APARTMENT(1, "公寓"),
      ROOM(2, "房间");
  
      @EnumValue
    	@JsonValue
      private Integer code;
      private String name;
  
      ItemType(Integer code, String name) {
          this.code = code;
          this.name = name;
      }
  }

##### 2.保存或更新标签信息

在`LabelController`中增加如下内容

 

@Operation(summary = "保存或更新标签信息")
@PostMapping("saveOrUpdate")
public Result saveOrUpdateFacility(@RequestBody LabelInfo labelInfo) {
    service.saveOrUpdate(labelInfo);
    return Result.ok();
}

##### 3. 根据ID删除标签

在`LabelController`中增加如下内容

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

#### 7.2.2.4 配套管理

**配套管理**共有三个接口,分别是**[根据类型]查询配套列表**、**保存或更新配套信息**和**根据ID删除配套**,具体实现如下。

在`FacilityController`中增加如下内容

@Tag(name = "配套管理")
@RestController
@RequestMapping("/admin/facility")
public class FacilityController {

    @Autowired
    private 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();
    }

}

  • 20
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值