SpringBoot创建父子多级项目


1. Git创建项目

  1. 从Gitee中创建初始仓库。

  2. 通过IDEA创建SpringBoot Initializr项目。
    阿里云的SpringBoot创建器
    这里使用aliyun的Spring Boot创建器,速度快还能使用阿里云的微服务的相关模块。

  3. 在IDEA菜单中,创建一个本地git仓库。
    VCS –> Import Into Version Control –> Create Git Repository
    IDEA中VCS菜单Import Into Version Control
    选择项目文件夹,会在项目文件夹下生成一个.git文件夹。

  4. 修改:VCS –> Git –> Remotes
    修改Remotes

    将其改为Git的地址,我这里用的是Gitee的仓库。
    Remotes修改为git地址

  5. 修改.gitignore文件,忽略无关的文件。这里使用.ignore插件来进行的。

    1. 安装插件后,在项目上右键点击新建文件:
      image-20201015202831023
      选择这个Git忽略文件。

    2. 在弹出窗口中选择相应的应该忽略的文件。这里加了Java和IDEA还有Maven的。
      image-20201015203023884

      添加了之后确定就行了。如果有原来的.gitignore文件,会自动加到里面去。

  6. 之后将其他的没有忽略的文件,右键菜单,Git–>Add,添加到Git版本控制中。Commit,Push即可。
    但是我Push的时候出现了错误。
    image-20201015203637873

    Push rejected
    Push to origin/master was rejected

    这个错误是因为:因为本地仓库和远程仓库的代码不一样

    Gitee上初始创建的项目仓库,和本地的项目仓库并不一样。所以说先检出一下。

  7. 但是检出也出错了。

    image-20201015204001154

    下午 8:37 Can’t Update

                    No tracked branch configured for branch master or the branch doesn't exist.
                    To make your branch track a remote branch call, for example,
                    git branch --set-upstream-to=origin/master master (show balloon)
    

    下午 8:37 Update canceled

按照提示,执行: git branch --set-upstream-to=origin/master master

这个命令的意思是,上传的代码中的origin或者master分支,设为仓库中的master分支。(origin/master是个“或者”,选项,所以说不能直接输入。。)
执行git push –set-upstream origin master
还是报错。

  1. 爷不演了,反正远程仓库里面并没有什么东西,是新建的一个仓库,暴力点,直接强制Push: git push origin master -f。这个命令会强制把本地的Push到仓库中。这样会将本地的项目,将远程仓库中的完全覆盖。
    image-20201015205118654

2. 项目架构

2.1父项目

  1. 修改pom.xml文件
    这里要用父子项目结构,所以说首先在<artifactId> 节点后面添加 pom类型

    1
    2
    <artifactId>guli_parent3</artifactId>
    <packaging>pom</packaging>

    并且增加一个继承spring-boot-starter的parent节点:

    1
    2
    3
    4
    5
    6
    <!--添加partent标签,上面继承SpringBoot启动器-->
    <parent>
    <groupId>org.springframework.boot</groupId>
    <version>2.2.1.RELEASE</version>
    <artifactId>spring-boot-starter-parent</artifactId>
    </parent>

    替换掉properties下的版本控制

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    <properties>
    <java.version>1.8</java.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <spring-boot.version>2.2.1.RELEASE</spring-boot.version>
    <guli.version>0.0.1-SNAPSHOT</guli.version>
    <mybatis-plus.version>3.0.5</mybatis-plus.version>
    <velocity.version>2.0</velocity.version>
    <swagger.version>2.7.0</swagger.version>
    <aliyun.oss.version>2.8.3</aliyun.oss.version>
    <jodatime.version>2.10.1</jodatime.version>
    <poi.version>3.17</poi.version>
    <commons-fileupload.version>1.3.1</commons-fileupload.version>
    <commons-io.version>2.6</commons-io.version>
    <httpclient.version>4.5.1</httpclient.version>
    <jwt.version>0.7.0</jwt.version>
    <aliyun-java-sdk-core.version>4.3.3</aliyun-java-sdk-core.version>
    <aliyun-sdk-oss.version>3.1.0</aliyun-sdk-oss.version>
    <aliyun-java-sdk-vod.version>2.15.2</aliyun-java-sdk-vod.version>
    <aliyun-java-vod-upload.version>1.4.13</aliyun-java-vod-upload.version>
    <aliyun-sdk-vod-upload.version>1.4.11</aliyun-sdk-vod-upload.version>
    <fastjson.version>1.2.28</fastjson.version>
    <gson.version>2.8.6</gson.version>
    <json.version>20170516</json.version>
    <commons-dbutils.version>1.7</commons-dbutils.version>
    <canal.client.version>1.1.0</canal.client.version>
    <docker.image.prefix>zx</docker.image.prefix>
    <cloud-alibaba.version>0.2.2.RELEASE</cloud-alibaba.version>
    </properties>

    删除depentcys,替换dependencyMangment:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
        <dependencyManagement>
    <dependencies>
    <!--Spring Cloud-->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-dependencies</artifactId>
    <version>Hoxton.RELEASE</version>
    <type>pom</type>
    <scope>import</scope>
    </dependency>

    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
    <version>${cloud-alibaba.version}</version>
    <type>pom</type>
    <scope>import</scope>
    </dependency>
    <!--mybatis-plus 持久层-->
    <dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>${mybatis-plus.version}</version>
    </dependency>

    <!-- velocity 模板引擎, Mybatis Plus 代码生成器需要 -->
    <dependency>
    <groupId>org.apache.velocity</groupId>
    <artifactId>velocity-engine-core</artifactId>
    <version>${velocity.version}</version>
    </dependency>

    <!--swagger-->
    <dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>${swagger.version}</version>
    </dependency>
    <!--swagger ui-->
    <dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>${swagger.version}</version>
    </dependency>

    <!-- &lt;!&ndash;aliyunOSS&ndash;&gt;-->
    <!-- <dependency>-->
    <!-- <groupId>com.aliyun.oss</groupId>-->
    <!-- <artifactId>aliyun-sdk-oss</artifactId>-->
    <!-- <version>${aliyun.oss.version}</version>-->
    <!-- </dependency>-->

    <!--日期时间工具-->
    <dependency>
    <groupId>joda-time</groupId>
    <artifactId>joda-time</artifactId>
    <version>${jodatime.version}</version>
    </dependency>

    <!--xls-->
    <dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>${poi.version}</version>
    </dependency>
    <!--xlsx-->
    <dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>${poi.version}</version>
    </dependency>

    <!--文件上传-->
    <dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>${commons-fileupload.version}</version>
    </dependency>

    <!--commons-io-->
    <dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>${commons-io.version}</version>
    </dependency>

    <!--httpclient-->
    <dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>${httpclient.version}</version>
    </dependency>

    <dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>${gson.version}</version>
    </dependency>

    <!-- JWT -->
    <dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>${jwt.version}</version>
    </dependency>

    <!--aliyun-->
    <dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>aliyun-java-sdk-core</artifactId>
    <version>${aliyun-java-sdk-core.version}</version>
    </dependency>
    <dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>${aliyun-sdk-oss.version}</version>
    </dependency>
    <dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>aliyun-java-sdk-vod</artifactId>
    <version>${aliyun-java-sdk-vod.version}</version>
    </dependency>
    <!-- <dependency>-->
    <!-- <groupId>com.aliyun</groupId>-->
    <!-- <artifactId>aliyun-java-vod-upload</artifactId>-->
    <!-- <version>${aliyun-java-vod-upload.version}</version>-->
    <!-- </dependency>-->
    <!-- <dependency>-->
    <!-- <groupId>com.aliyun</groupId>-->
    <!-- <artifactId>aliyun-sdk-vod-upload</artifactId>-->
    <!-- <version>${aliyun-sdk-vod-upload.version}</version>-->
    <!-- </dependency>-->
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>${fastjson.version}</version>
    </dependency>
    <dependency>
    <groupId>org.json</groupId>
    <artifactId>json</artifactId>
    <version>${json.version}</version>
    </dependency>

    <dependency>
    <groupId>commons-dbutils</groupId>
    <artifactId>commons-dbutils</artifactId>
    <version>${commons-dbutils.version}</version>
    </dependency>

    <dependency>
    <groupId>com.alibaba.otter</groupId>
    <artifactId>canal.client</artifactId>
    <version>${canal.client.version}</version>
    </dependency>
    </dependencies>
    </dependencyManagement>

2.2子项目

新建子项目service

  1. New –> Module,新建子项目。选择Maven工程。
    image-20201016195347364
    修改pom打包方式:

    1
    2
    <artifactId>service</artifactId>
    <packaging>pom</packaging>

    增加依赖:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    <dependencies>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    </dependency>

    <!--hystrix依赖,主要是用 @HystrixCommand -->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>

    <!--服务注册-->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!--服务调用-->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

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

    <!--mysql-->
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    </dependency>

    <!-- velocity 模板引擎, Mybatis Plus 代码生成器需要 -->
    <dependency>
    <groupId>org.apache.velocity</groupId>
    <artifactId>velocity-engine-core</artifactId>
    </dependency>

    <!--swagger-->
    <dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    </dependency>
    <dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    </dependency>

    <!--lombok用来简化实体类:需要安装lombok插件-->
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    </dependency>

    <!--xls-->
    <dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    </dependency>

    <dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    </dependency>

    <dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    </dependency>

    <!--httpclient-->
    <dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    </dependency>
    <!--commons-io-->
    <dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    </dependency>
    <!--gson-->
    <dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    </dependency>

    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    </dependency>
    </dependencies>

导包之坑

阿里云maven镜像

2020年最新的阿里云的maven镜像库变了,修改一下。把原来的都删掉了。

1
2
3
4
5
6
7
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云公共仓库</name> //不要怀疑,就是中文的
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
</mirrors>
父项目pom

修改springboot的版本为2.2.1.RELEASE。

首先,先把父项目pom里面的aliyun-skd-oss给注释了,因为写重复了一个

1
2
3
4
5
6
<!--            &lt;!&ndash;aliyunOSS&ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>com.aliyun.oss</groupId>-->
<!-- <artifactId>aliyun-sdk-oss</artifactId>-->
<!-- <version>${aliyun.oss.version}</version>-->
<!-- </dependency>-->

之后,阿里云上传组件飘红,暂未解决。

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-vod-upload</artifactId>
<version>${aliyun-java-vod-upload.version}</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-sdk-vod-upload</artifactId>
<version>${aliyun-sdk-vod-upload.version}</version>
</dependency>
子项目pom

提示几个unknow,给它写上特定版本。这里版本是根据IDEA提示随机添加的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.3.RELEASE</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.21</version>
</dependency>
<!--lombok用来简化实体类:需要安装lombok插件-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>

这里mysql的版本改了一下,用了个比较新的,因为旧的mysql驱动是com.mysql.jdbc.Driver而新的是com.mysql.cj.jdbc.Driver,多了个cj,不太一样。

新建子项目service-edu

image-20201016205021480

新建配置文件application.properties

1
2
3
4
5
6
7
8
9
10
11
12
13
# 服务端口
server.port=8001
# 服务名
spring.application.name=service-edu
# 环境设置:dev、test、prod
spring.profiles.active=dev
# mysql数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/guli?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=123456
#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

在test文件夹下,新建Mybatis-Plus代码生成器:
image-20201016211954717

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package com.songx64.eduservice;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import org.junit.Test;

public class CodeGenerator {

@Test
public void run() {

// 1、创建代码生成器
AutoGenerator mpg = new AutoGenerator();

// 2、全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
System.out.println(projectPath);

// gc.setOutputDir(projectPath + "/src/main/java");
//改为绝对路径,系统自动获取可能会有问题
gc.setOutputDir("F:\\AWork\\Project\\MyLearn\\guli_parent3\\service\\service-edu" + "/src/main/java");

gc.setAuthor("songx64");
gc.setOpen(false); //生成后是否打开资源管理器
gc.setFileOverride(false); //重新生成时文件是否覆盖
/*
* mp生成service层代码,默认接口名称第一个字母有 I
* UcenterService
* */
gc.setServiceName("%sService"); //去掉Service接口的首字母I
gc.setIdType(IdType.ID_WORKER); //主键策略
gc.setDateType(DateType.ONLY_DATE);//定义生成的实体类中日期类型
gc.setSwagger2(true);//开启Swagger2模式

mpg.setGlobalConfig(gc);

// 3、数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/guli?serverTimezone=GMT%2B8");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("123456");
dsc.setDbType(DbType.MYSQL);
mpg.setDataSource(dsc);

// 4、包配置
PackageConfig pc = new PackageConfig();
pc.setParent("com.songx64");//包名字com.songx64
pc.setModuleName("eduservice"); //模块名

pc.setController("controller");//包名字com.songx64.controller
pc.setEntity("entity");
pc.setService("service");
pc.setMapper("mapper");
mpg.setPackageInfo(pc);

// 5、策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setInclude("edu_teacher");
strategy.setNaming(NamingStrategy.underline_to_camel);//数据库表映射到实体的命名策略
strategy.setTablePrefix(pc.getModuleName() + "_"); //生成实体时去掉表前缀

strategy.setColumnNaming(NamingStrategy.underline_to_camel);//数据库表字段映射到实体的命名策略
strategy.setEntityLombokModel(true); // lombok 模型 @Accessors(chain = true) setter链式操作

strategy.setRestControllerStyle(true); //restful api风格控制器
strategy.setControllerMappingHyphenStyle(true); //url中驼峰转连字符

mpg.setStrategy(strategy);

// 6、执行
mpg.execute();
}

}

修改相关配置,主要是

  • 生成路径,最好用绝对路径;
  • 数据库连接的名字,用户名密码;
  • 包名,author名

之后运行即可。会自动生成代码:

image-20201016212139439

编辑Teacher的控制类,启动类

EduTeacherController

  1. 自动注入Service,@Autowired
  2. 编写查询函数,@GetMapping
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.songx64.eduservice.controller;

@RestController
@RequestMapping("/eduservice/edu-teacher")
public class EduTeacherController {
@Autowired
private EduTeacherService eduTeacherService;

@GetMapping("/findAll")
public List<EduTeacher> findAllTeacher(){
List<EduTeacher> list = eduTeacherService.list(null);
return list;
}
}

EduConfig

创建配置类,扫描mapper。不加入的话mapper扫描不到可能会报错。

创建config包,com.songx64.eduservice.config

@MapperScan(包名)

1
2
3
4
5
6
7
8
9
package com.songx64.eduservice.config;

import org.mybatis.spring.annotation.MapperScan;
@Configuration
@EnableTransactionManagement
@MapperScan("com.songx64.eduservice.mapper")
public class EduConfig {
}

启动类

在Edu-Service根目录下添加启动类:

1
2
3
4
5
6
7
8
9
package com.songx64.eduservice;

@SpringBootApplication
public class EduApplication {

public static void main(String[] args) {
SpringApplication.run(EduApplication.class, args);
}
}

报错:java.lang.ClassNotFoundException: org.springframework.boot.web.servlet.filter.OrderedHttpPutFormContentFilter

报错解决:

版本问题解决

错误:springboot的各种包都找不到。

原因:在父配置文件中,少加了个标签:

1
2
3
4
5
 <parent>
<groupId>org.springframework.boot</groupId>
<version>2.2.1.RELEASE</version>
<artifactId>spring-boot-starter-parent</artifactId>
</parent>

这个自然是从Springboot官方的parent节点继承spring-boot-starter,没有这个原来的各种starter都没有集成进去。
原来我的方式是在一个个的子模块中重新集成了springboot的starter,而不是继承过来的。

启动报错问题解决

错误:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘eduTeacherController’: Unsatisfied dependency expressed through field ‘eduTeacherService’;

nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘eduTeacherServiceImpl’: Unsatisfied dependency expressed through field ‘baseMapper’;

nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type ‘com.songx64.eduservice.mapper.EduTeacherMapper’ available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

这个是说找不到eduTeacherServiceImpl,还有baseMapper。

原因:忘了在config类中添加@Configuration注解

1
2
3
4
5
6
7
8
9
package com.songx64.eduservice.config;


@Configuration
@EnableTransactionManagement
@MapperScan("com.songx64.eduservice.mapper")
public class EduConfig {
}

访问报错:

image-20201030172436277

原因:用的https访问的。https://localhost:8001/eduservice/edu-teacher/findAll

解决:修改为http协议即可。http://localhost:8001/eduservice/edu-teacher/findAll

Swagger2

Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。

1.新建common模块

image-20201102185152951

2.在common/pom.xml中引入相关模块

org.springframework.boot spring-boot-starter-web provided
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
    <!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<scope>provided </scope>
</dependency>

<!--lombok用来简化实体类:需要安装lombok插件-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided </scope>
</dependency>

<!--swagger-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<scope>provided </scope>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<scope>provided </scope>
</dependency>

<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- spring2.X集成redis所需common-pool2
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency>-->
</dependencies>

3.新建SwaggerConfig配置类

image-20201102191706272

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package com.songx64.servicebase.config;

import com.google.common.base.Predicates;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

/**
* Created on 2020/11/2,下午 7:06
* @author SongX64
* @desc Swagger配置类,该类里面的应该是固定的,主要用来设置文档的主题信息,比如文档的大标题,副标题,公司名等
*/

@Configuration//托管spring
@EnableSwagger2//开启swagger功能
public class SwaggerConfig {

@Bean
public Docket webApiConfig() {

//通过调用下面的自定义方法webApiInfo,获得文档的主要信息
return new Docket(DocumentationType.SWAGGER_2)
.groupName("MySwaggerTest")
.apiInfo(webApiInfo())
.select()
.paths(Predicates.not(PathSelectors.regex("/admin/.*")))
.paths(Predicates.not(PathSelectors.regex("/error.*")))
.build();

}

/**
* 在线文档的信息,展现在文档页面中
*/
private ApiInfo webApiInfo() {

return new ApiInfoBuilder()
.title("网站-课程中心API文档")
.description("本文档描述了课程中心微服务接口定义")
.version("1.0")
.contact(new Contact("Song", "http://项目实际访问地址.com", "666666@qq.com"))
.build();
}
}

配置类中的信息显示在页面中:

image-20201115180034982

4.引入service-base模块

去service模块中的pom文件,引入自己的service-base模块

1
2
3
4
5
6
<!--引入自己写的service-base模块-->
<dependency>
<groupId>com.songx64</groupId>
<artifactId>service-base</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>

5.编写文档说明注解

EduTeacherController.java类中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Api(tags = "讲师管理控制器")
@RestController
@RequestMapping("/eduservice/edu-teacher")
public class EduTeacherController {
@Autowired
private EduTeacherService eduTeacherService;

@ApiOperation(value = "列出所有讲师")
@GetMapping("/findAll")
public List<EduTeacher> findAllTeacher() {
List<EduTeacher> list = eduTeacherService.list(null);
return list;
}

@ApiOperation(value = "删除讲师", notes = "根据Id删除讲师")
@DeleteMapping("/{id}")
public boolean deleteTeacherById(
@ApiParam(name = "id", value = "讲师ID", required = true)
@PathVariable String id) {
return eduTeacherService.removeById(id);
}
}

新增了一个deleteTeacherById方法,用来测试。

同时一些Swagger的注解:
image-20201115175729745

6.测试

最终进行测试,访问http://localhost:8001/swagger-ui.html

image-20201115174930198

错误记录

找不到同项目的包

因为找不到包,从而没能扫描到swaggerConfig,显示:

image-20201115180342063

最终也没找到原因,只能用另外办法了:
在子项目中引入另一个子项目:

service-edu的pom.xml中,引入service_base模块

1
2
3
4
5
6
7
8
9
10
11
12
13
<artifactId>service-edu</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.songx64</groupId>
<artifactId>service_base</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>

swagger配置报错

太长的完整报错不贴了,主要就是:

org.springframework.beans.factory.BeanDefinitionStoreException:

Failed to process import candidates for configuration class [springfox.documentation.swagger2.configuration.Swagger2DocumentationConfiguration];

nested exception is java.io.FileNotFoundException: class path resource [com/google/common/base/Supplier.class] cannot be opened because it does not exist

删除了service目录下的swagger依赖:

service的pom.xml中,删除:

1
2
3
4
5
6
7
8
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
</dependency>

相关知识

pom文件中的<scope>标签

  • 对于scope=compile的情况(默认scope),
    也就是说这个项目在编译,测试,运行阶段都需要这个artifact对应的jar包在classpath中。

  • 而对于scope=provided的情况,则可以认为这个provided是目标容器已经provide这个artifact。
    换句话说,它只影响到编译,测试阶段。
    在编译测试阶段,我们需要这个artifact对应的jar包在classpath中,
    而在运行阶段,假定目标的容器(比如我们这里的liferay容器)已经提供了这个jar包,所以无需我们这个artifact对应的jar包了。

总之就是,provided不会把jar包打进去。

比如父级已经把这个包打进去了,在子项目中就可以用provided,子项目的包不会被打进去,这样就可以避免包冲突了。

08.统一结果返回

为啥

项目中我们会将响应封装成json返回,一般我们会将所有接口的数据格式统一, 使==前端(iOS Android, Web)==对数据的操作更一致、轻松。

也就是说,后端和显示是无关的。前端接受数据就可以。后端就是提供数据的。

一般会包含状态码、返回消息、数据这几部分内容。

在这里,我们定义统一结果:

1
2
3
4
5
6
{
"success": 布尔, //响应是否成功
"code": 数字, //响应码
"message": 字符串, //返回消息
"data": HashMap //返回数据,放在键值对中
}

咋整

  1. 在common下创建子模块common_utils,新建包com.songx64.commonutils
  2. 新建接口ResultCode:
1
2
3
4
5
6
public interface ResultCode{
//成功
public static Integer SUCCESS = 20000;
//失败
public static Integer ERROR = 20001;
}
  1. 新建结果类R
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Data
public class R{
//四个属性
@ApiModelProperty(value = "是否成功")
private Boolean success;

@ApiModelProperty("状态码")
private Integer code;

@ApiModelProperty("消息")
private String message;

@ApiModelProperty("数据")
private HashMap<String,Object> data;

//私有化构造方法,单例模式
private R(){}

//返回函数
}

咋用

  1. 引入模块,service,pom.xml

    1
    2
    3
    4
    5
    <dependency>
    <groupId>com.songx64</groupId>
    <artifactId>common_utils</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    </dependency>
  2. 修改Controller里的方法,返回结果改为R
    com\songx64\eduservice\controller\EduTeacherController.java

    1
       
  3. 运行,进入swagger查看

  4. 有个TODO,删除的地方有问题

后台讲师管理模块

1.分页查询

1.1 配置分页插件

com.songx64.eduservice.config.EduConfig类中,粘贴mybatis的config配置插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class EduConfig {
/**
* 分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
/**
* 逻辑删除插件
*/
@Bean
public ISqlInjector sqlInjector() {
return new LogicSqlInjector();
}
}

1.2 编写讲师分页查询接口的方法

主要过程就是:

  1. 新建一个Page对象
  2. 调用page方法查询到Page对象中
  3. 从Page对象中取出数据,返回

EduTeacherController类中的分页查询方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 3.分页查询方法
* current 当前页
* limit 每页记录数
*/
@ApiOperation("分页查询讲师")
@GetMapping("pageTeacher/{current}/{limit}")
public R pageListTeacher(
@ApiParam(name = "current", value = "当前页数")
@PathVariable Long current,
@ApiParam(name = "limit", value = "每页记录数")
@PathVariable Long limit) {
//创建Page对象,传入参数当前页1和每页记录数3
Page<EduTeacher> pageTeacher = new Page<>(current, limit);
//调用方法实现分页:分页对象,条件
//封装自带的page方法,直接分页查询,结果放入Page对象中
eduTeacherService.page(pageTeacher, null);
//从Page对象中得到查询结果,total是总条数
List<EduTeacher> records = pageTeacher.getRecords();
Long total = pageTeacher.getTotal();
return R.ok().data("total", total).data("rows", records);
}
  • Page pageTeacher = new Page<>(current, limit); //新建对象
  • eduTeacherService.page(pageTeacher, null);//通过page方法查询,放入对象
  • List records = pageTeacher.getRecords();//得到数据List
  • Long total = pageTeacher.getTotal(); //total是总条数

2.条件分页查询

多条件分页查询

image-20210301105424957

  1. 新建VO对象
  2. 新建查询方法(先写到Controller中,之后放到Service中)
  3. 查询方法中:
    1. 创建Page对象
    2. 构造QueryWrapper查询条件
    3. 调用page查询
    4. 返回结果

传入参数对象实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/*
* 4. 条件分页查询方法
* */
@GetMapping("pageTeacherCondition/{current}/{limit}")
public R pageTeacherCondition(@ApiParam(name = "current", value = "当前页数")
@PathVariable Long current,
@ApiParam(name = "limit", value = "每页记录数")
@PathVariable Long limit,
EduTeacherQuery eduTeacherQuery) {
//新建Page对象
Page<EduTeacher> pageTeahcer = new Page<>(current, limit);
QueryWrapper<EduTeacher> wrapper = new QueryWrapper<>();
//多条件查询,可以通过xml动态sql
//这里单表可以直接通过判断拼接,条件值不为空就拼接条件
String name = eduTeacherQuery.getName();
Integer level = eduTeacherQuery.getLevel();
String begin = eduTeacherQuery.getBegin();
String end = eduTeacherQuery.getEnd();
//StringUtils是springframework包中的字符串工具类
if (!StringUtils.isEmpty(name)) {
//构造条件
wrapper.like("name", name);
}
if (!StringUtils.isEmpty(level)) {
wrapper.eq("level", level);
}
if (!StringUtils.isEmpty(begin)) {
wrapper.ge("gmt_create", begin);
}
if (!StringUtils.isEmpty(end)) {
wrapper.le("gmt_create", end);
}
//条件查询
eduTeacherService.page(pageTeahcer, wrapper);

Long total = pageTeahcer.getTotal();
List<EduTeacher> records = pageTeahcer.getRecords();
return R.ok().data("total", total).data("rows", records);
}

image-20210301114457743

关于@RequestBody和@ResponseBody注解

  • @ResponseBody是用来返回数据的,返回JSON格式的数据;
  • @RequestBody是使用JSON来传递数据,把传过来的数据封装到对象中;

上面的方法如果要使用@ResponseBody注解的话,要改为Post方式进行请求,Get方式不行

1
2
3
4
5
6
7
8
@PostMapping("pageTeacherCondition/{current}/{limit}")
public R pageTeacherCondition(@ApiParam(name = "current", value = "当前页数")
@PathVariable Long current,
@ApiParam(name = "limit", value = "每页记录数")
@PathVariable Long limit,
@RequestBody(required = false) EduTeacherQuery eduTeacherQuery) {
……
}

这里要加上required=false,保证为空的时候也能传入;

改为@ResponeBody注解后,传入的是一个JSON字符串:

image-20210301114301637

提醒:面试时候可能问问题“你用过什么注解”,这里最好不要说太基础的Controller之类的,可以提一嘴刚才这两个以及他们的区别之类的东西。

相关知识:关于VO和DTO

关于VO和DTO:https://blog.csdn.net/zjrbiancheng/article/details/6253232

  • VO(View Object):视图对象,用于展示层,它的作用是把某个指定页面(或组件)的所有数据封装起来。
  • DTO(Data Transfer Object):数据传输对象,这个概念来源于J2EE的设计模式,原来的目的是为了EJB的分布式应用提供粗粒度的数据实体,以减少分布式调用的次数,从而提高分布式调用的性能和降低网络负载,但在这里,我泛指用于展示层与服务层之间的数据传输对象。

VO与DTO的区别

​ 大家可能会有个疑问(在笔者参与的项目中,很多程序员也有相同的疑惑):既然DTO是展示层与服务层之间传递数据的对象,为什么还需要一个VO呢?对!对于绝大部分的应用场景来说,DTO和VO的属性值基本是一致的,而且他们通常都是POJO,因此没必要多此一举,但不要忘记这是实现层面的思维,对于设计层面来说,概念上还是应该存在VO和DTO,因为两者有着本质的区别,DTO代表服务层需要接收的数据和返回的数据,而VO代表展示层需要显示的数据。
​ 用一个例子来说明可能会比较容易理解:例如服务层有一个getUser的方法返回一个系统用户,其中有一个属性是gender(性别),对于服务层来说,它只从语义上定义:1-男性,2-女性,0-未指定,而对于展示层来说,它可能需要用“帅哥”代表男性,用“美女”代表女性,用“秘密”代表未指定。说到这里,可能你还会反驳,在服务层直接就返回“帅哥美女”不就行了吗?对于大部分应用来说,这不是问题,但设想一下,如果需求允许客户可以定制风格,而不同风格对于“性别”的表现方式不一样,又或者这个服务同时供多个客户端使用(不同门户),而不同的客户端对于表现层的要求有所不同,那么,问题就来了。再者,回到设计层面上分析,从职责单一原则来看,服务层只负责业务,与具体的表现形式无关,因此,它返回的DTO,不应该出现与表现形式的耦合。
​ 理论归理论,这到底还是分析设计层面的思维,是否在实现层面必须这样做呢?一刀切的做法往往会得不偿失,下面我马上会分析应用中如何做出正确的选择。

VO与DTO的应用

​ 上面只是用了一个简单的例子来说明VO与DTO在概念上的区别,本节将会告诉你如何在应用中做出正确的选择。
​ 在以下才场景中,我们可以考虑把VO与DTO二合为一(注意:是实现层面):
当需求非常清晰稳定,而且客户端很明确只有一个的时候,没有必要把VO和DTO区分开来,这时候VO可以退隐,用一个DTO即可,为什么是VO退隐而不是DTO?回到设计层面,服务层的职责依然不应该与展示层耦合,所以,对于前面的例子,你很容易理解,DTO对于“性别”来说,依然不能用“帅哥美女”,这个转换应该依赖于页面的脚本(如JavaScript)或其他机制(JSTL、EL、CSS)
即使客户端可以进行定制,或者存在多个不同的客户端,如果客户端能够用某种技术(脚本或其他机制)实现转换,同样可以让VO退隐

以下场景需要优先考虑VO、DTO并存:

上述场景的反面场景
因为某种技术原因,比如某个框架(如Flex)提供自动把POJO转换为UI中某些Field时,可以考虑在实现层面定义出VO,这个权衡完全取决于使用框架的自动转换能力带来的开发和维护效率提升与设计多一个VO所多做的事情带来的开发和维护效率的下降之间的比对。
如果页面出现一个“大视图”,而组成这个大视图的所有数据需要调用多个服务,返回多个DTO来组装(当然,这同样可以通过服务层提供一次性返回一个大视图的DTO来取代,但在服务层提供一个这样的方法是否合适,需要在设计层面进行权衡)。

3.添加讲师

3.1 修改自动填充时间

1.实体类EduTeacher上面加注解@TableField,插入时和更新时填充

1
2
3
4
5
6
7
@ApiModelProperty(value = "创建时间")
@TableField(fill = FieldFill.INSERT)
private Date gmtCreate;

@ApiModelProperty(value = "更新时间")
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date gmtModified;

2.自定义实现接口功能,自动填充

封装至common模块,servicebase下,新建handler包,新建MyMetaObjectHandler类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.songx64.servicebase.handler;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
* Created on 2021/3/1,下午 3:35
*
* @author SongX64
*/
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
//这里传入的是属性名称,不是字段名称
this.setFieldValByName("gmtCreate", new Date(), metaObject);
this.setFieldValByName("gmtModified", new Date(), metaObject);
}

@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("gmtModified", new Date(), metaObject);
}
}

这里大概就是通过@TableFiled注解,然后通过MyMetaObjectHandler处理填充值

Mybatis-Plus文档:https://baomidou.com/guide/auto-fill-metainfo.html

3.2 添加讲师方法

wdnmd,修改实体类之后Jrebel热部署不管用?

遇到问题:说是id字段的类型不匹配,dismatch。修改重启换版本之后,不用Jrebel运行而是用原来的运行,可以了就。草

1
2
3
4
5
6
7
8
9
10
11
12
/*
* 5.添加讲师方法
* */
@PostMapping("addTeacher")
public R addTeacher(@RequestBody EduTeacher eduTeacher) {
boolean save = eduTeacherService.save(eduTeacher);
if (save) {
return R.ok();
} else {
return R.error();
}
}

tmd为啥行了呢?为啥不行呢?

4.修改讲师

4.1 根据Id来查询讲师

这个没啥好说的,直接调用就完事了。不过要回忆一下这个@PathVariable注解。

1
2
3
4
5
6
7
8
9
/*
* 6.根据讲师id进行查询
* */
@ApiOperation("根据Id查询讲师")
@GetMapping("getTeacher/{id}")
public R getTeacher(@PathVariable String id) {
EduTeacher eduTeacher = eduTeacherService.getById(id);
return R.ok().data("teacher", eduTeacher);
}

4.2 根据ID修改讲师

这里用了两种方式,一种Post请求,一种Put请求

Post请求方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*
* 7.1 讲师修改功能
* */
@ApiOperation("根据ID修改讲师,Post方法")
@PostMapping("updateTeacher")
public R updateTeacher(@RequestBody EduTeacher eduTeacher) {
boolean flag = eduTeacherService.updateById(eduTeacher);
if (flag) {
return R.ok();
} else {
return R.error();
}
}

Put请求方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*
* 7.2 Put方式修改讲师
* */
@ApiOperation("根据ID修改讲师,Put方法")
@PutMapping("{id}")
public R updateTeacherById(@PathVariable String id, @RequestBody EduTeacher eduTeacher) {
eduTeacher.setId(id);
boolean flag = eduTeacherService.updateById(eduTeacher);
if (flag) {
return R.ok();
} else {
return R.error();
}
}

相关知识:关于Put和Post

https://blog.csdn.net/qq_36183935/article/details/80570062

PUT和POST

PUT和POS都有更改指定URI的语义.但PUT被定义为idempotent的方法,POST则不是.idempotent的方法:如果一个方法重复执行

多次,产生的效果是一样的,那就是idempotent的。也就是说:

PUT请求:如果两个请求相同,后一个请求会把第一个请求覆盖掉。(所以PUT用来改资源)

Post请求:后一个请求不会把第一个请求覆盖掉。(所以Post用来增资源)

https://www.zhihu.com/question/48482736

一个例子是网不好的时候,post提交后没收到响应,于是客户端再次尝试提交,成功后刷新看到新建了两条资源,如果用put的话就不会出现这样的情况。所以尽量使用put去代替post


文章作者: SongX64
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 SongX64 !
 上一篇
基于知识图谱的用户理解 基于知识图谱的用户理解
将知识图谱应用于用户画像的一个报告,复旦大学肖仰华教授
2020-12-24
下一篇 
使用文本聚类自动构建多方面的用户画像并将其应用于专家发现和过滤问题 使用文本聚类自动构建多方面的用户画像并将其应用于专家发现和过滤问题
2019年论文, 使用文本聚类自动构建多方面的用户画像并将其应用于专家发现和过滤问题,没看完,后面实验部分跳过了
2020-11-04
  目录