springboot

跟随视频练习的代码存放在 GitHub仓库: https://github.com/wuwei1636/java

这边博客我只写了一小部分,详细笔记可以参考该篇博客,或者去狂神的公众号


Springboot

第一个Spring boot项目

使用spring官网生成

打开spring boot官网,地址:https://start.spring.io/

image-20220702095433847

选择之后点击下面最左侧的按钮,下载压缩包,然后解压导入idea即可。

导入:在之前的idea中有import按钮,但是最新版的idea没有导入,直接open解压后的项目就可以直接运行,如果jdk不同的话,就打开setting修改jdk版本

使用spring.io需要科学上网,所以可以使用阿里云的镜像进行springboot的项目创建,步骤同上

阿里云创建spring boot https://start.aliyun.com/bootstrap.html

image-20220702095731963

使用idea生成

打开idea,新建一个项目

image-20220702095825152

Server URL:默认的是 spring.io

但是容易连接不上,所以还是使用阿里云的镜像https://start.aliyun.com/

image-20220702095949054

点击next后选择所需的依赖,然后create即可。

Springboot整合mybatis

步骤:

  1. 导入pom依赖
  2. 配置数据库(application.properties)
  3. 编写实体类 pojo
  4. 编写数据库底层 mapper/ dao
  5. 编写 mybatis 的 mapper.xml (数据库操作)
  6. 编写 service 层接口 和 对应的 service方法
  7. 编写 controller 层
  8. 实现前后端的数据交互(thymeleaf+springboot+mybatis)
  9. 运行测试

以下的代码的都只是为了展示步骤,所以代码没有放全,详细的请参考我GitHub上springboot中的03-web

导入Pom依赖

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
<!--thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--mybatis-->
<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>

配置数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 关闭模板引擎的缓存
spring.thymeleaf.cache=false
# 我们的配置文件的真实位置(国际化,登录界面中英切换)
spring.messages.basename=i18n.login
# 修改日期格式
spring.mvc.format.date=yyyy-MM-dd

# 数据库驱动:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# 数据库连接地址
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=false&characterEncoding=utf-8
# 数据库用户名&密码:
spring.datasource.username=root
spring.datasource.password=123456


#整合mybatis
mybatis.type-aliases-package=com.li.pojo
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml

编写实体类 pojo

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
//员工表 Employee
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Employee {

private Integer id;
private String name;
private String email;
private Integer gender; // 0 : 女 1 : 男

private int departmentId;
private Department department;
private Date birth;

public Employee(Integer id, String name, String email, Integer gender, Department department,int departmentId) {
this.id = id;
this.name = name;
this.email = email;
this.gender = gender;
this.department = department;
this.departmentId = departmentId;
this.birth = new Date();
}
}
1
2
3
4
5
6
7
8
9
//部门表 Department
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Department {
private int id;
private String departmentName;
}

编写数据库底层Mapper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Mapper
@Repository
public interface EmployeeMapper {

int addEmployee(Employee employee);

Collection<Employee> getAllEmployee();

Employee getEmployee(int id);

int update(Employee employee);

int delete(int id);
}
1
2
3
4
5
6
7
8
9
@Mapper // mybatis
@Repository // 被spring托管
public interface DepartmentMapper {
//获取所有的department
Collection<Department> getAllDepartment();

// 通过id获取department
Department getDepartment(Integer id);
}

编写mybatis的Mapper.xml

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
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.li.Mapper.EmployeeMapper">

<resultMap id="EmployeeMap" type="Employee">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="email" column="email"/>
<result property="gender" column="gender"/>
<result property="departmentId" column="departmentId"/>
<result property="birth" column="birth"/>
<association property="department" javaType="Department">
<id property="id" column="departmentId"/>
<result property="departmentName" column="departmentName"/>
</association>
</resultMap>

<select id="getAllEmployee" resultMap="EmployeeMap">
select e.id,e.name,e.email,e.gender,e.departmentId,e.birth,d.departmentName
from mybatis.employee e left join mybatis.department d
on e.departmentId = d.id

</select>

<select id="getEmployee" resultType="Employee">
select * from mybatis.employee where id = #{id}
</select>

<insert id="addEmployee" parameterType="Employee">
insert into mybatis.employee (id,name,email,gender,birth,departmentId)
values (#{id},#{name},#{email},#{gender},#{birth},#{departmentId})
</insert>

<update id="update" parameterType="Employee">
update mybatis.employee set name = #{name} ,email = #{email},gender = #{gender}
,birth=#{birth},departmentId=#{departmentId}
where id = #{id}
</update>

<delete id="delete">
delete from mybatis.employee where id = #{id}
</delete>

</mapper>

编写service层接口和对应的service方法

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface EmployeeService {

int addEmployee(Employee employee);

Collection<Employee> getAllEmployee();

Employee getEmployee(int id);

int update(Employee employee);

int delete(int id);

}
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
@Service
public class EmployeeServiceImpl implements EmployeeService{

@Autowired
private EmployeeMapper employeeMapper;

public void setEmployeeMapper(EmployeeMapper employeeMapper) {
this.employeeMapper = employeeMapper;
}

@Override
public int addEmployee(Employee employee) {
return employeeMapper.addEmployee(employee);
}

@Override
public Collection<Employee> getAllEmployee() {
return employeeMapper.getAllEmployee();
}

@Override
public Employee getEmployee(int id) {
return employeeMapper.getEmployee(id);
}

@Override
public int update(Employee employee) {
return employeeMapper.update(employee);
}

@Override
public int delete(int id) {
return employeeMapper.delete(id);
}
}

编写controller层

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
@Controller
public class EmployeeController {

//
@Autowired
private EmployeeService employeeService;

@Autowired
private DepartmentService departmentService;


// list 显示所有成员
@RequestMapping("/emps")
public String list(Model model){
Collection<Employee> employees = employeeService.getAllEmployee();
model.addAttribute("emps",employees);
return "emp/list";
}

// 添加请求 跳转界面
@GetMapping("/emp")
public String toAddEmployee(Model model){
Collection<Department> department = departmentService.getAllDepartment();
model.addAttribute("departments",department);
return "emp/add";
}

// 添加用户实现
@PostMapping("/emp")
public String addEmployee(Employee employee){
employeeService.addEmployee(employee); // 保存员工信息
System.out.println("=====================================>"+employee);
return "redirect:/emps";
}

// 修改信息 跳转界面
@GetMapping("/change/{id}")
public String changeEmp(@PathVariable("id") Integer id, Model model){
Employee employee = employeeService.getEmployee(id);
Collection<Department> department = departmentService.getAllDepartment();
model.addAttribute("emp",employee);
model.addAttribute("change",department);
return "/emp/change";
}

//确认修改请求
@PostMapping("/update")
public String update(Employee employee){
employeeService.update(employee);
return "redirect:/emps";
}

// 删除用户
@GetMapping("/delete/{id}")
public String delete(@PathVariable("id")Integer id){
employeeService.delete(id);
return "redirect:/emps";
}

}

实现前后端数据交互

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
<div th:insert="~{common/commons.html::headbar}"></div>

<div class="container-fluid">
<div class="row">
<div th:insert="~{common/commons.html::sidebar(active = 'list.html')}"></div>

<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<h2><a class="btn btn-success btn-sm" th:href="@{/emp}">添加员工</a></h2>
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th>id</th>
<th>name</th>
<th>email</th>
<th>gender</th>
<th>department</th>
<th>birth</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr th:each="emp:${emps}">
<td th:text="${emp.getId()}"></td>
<td th:text="${emp.getName()}"></td>
<td th:text="${emp.getEmail()}"></td>
<td th:text="${emp.getGender() == 1 ? '男':'女'}"></td>
<td th:text="${emp.getDepartment().getDepartmentName()}"></td>
<td th:text="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm')}"></td>
<td>
<a class="btn btn-sm btn-primary" th:href="@{/change/} + ${emp.getId()}">编辑</a>
<a class="btn btn-sm btn-danger" th:href="@{/delete/} + ${emp.getId()}">删除</a>
</td>
</tr>
</tbody>
</table>
</div>
</main>
</div>
</div>

Springboot整合Mongodb

  1. 导入pom依赖
  2. 修改配置文件 properties
  3. 编写实体类 pojo
  4. 编写 dao层和dao的实现类
  5. 编写service层和service的实现
  6. 编写controller
  7. 测试

导入pom依赖

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
<dependencies>
<!--mongodb-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!--json转换-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.2</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

修改配置文件

1
2
# mongodb配置
spring.data.mongodb.uri=mongodb://localhost:27017/Test

编写实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Data
@AllArgsConstructor
@NoArgsConstructor
@Document(collection = "test1")
public class test1 {

@Id
private String _id;
private int id;
private String name;
private int age;

private List<test2> test2List;

}
1
2
3
4
5
6
7
8
9
10
11
12
@Document(collection = "test2")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class test2 {

@Id
private String _id;
private int id;
private String name;

}

编写dao层

dao层接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public interface SiteFlowDao {

//增
public void add(test1 s1);

//删
public void remove(int id);

//根据名字查找
public test1 findByName(String name);

//更新
public int update(test1 s1);

//多表联查
public List<test1> findListByTest();

}

dao层接口实现

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
@Component
public class SiteFlowDaoImpl implements SiteFlowDao{

@Autowired
MongoTemplate mongoTemplate;


@Override
public void add(test1 s1) {
mongoTemplate.save(s1);
}

@Override
public void remove(int id) {
Query query = new Query(Criteria.where("id").is(id));
mongoTemplate.remove(query,test1.class);
}

@Override
public test1 findByName(String name) {
Query query = new Query(Criteria.where("name").is(name));
test1 one = mongoTemplate.findOne(query, test1.class);
return one;
}

@Override
public int update(test1 s1) {
Query query = new Query(Criteria.where("id").is(s1.getId()));
Update update = new Update();
update.set("name",s1.getName()).set("age",s1.getAge());
UpdateResult updateResult = mongoTemplate.updateMulti(query, update, test1.class);
if(updateResult.getModifiedCount()>0) return 1;
return 0;
}

@Override
public List<test1> findListByTest() {

LookupOperation lookupOperation = LookupOperation.newLookup()
.from("test2") //关联从表名
.localField("id") //主表关联字段
.foreignField("id")//从表关联的字段
.as("test2List"); //查询结果名
// 获取指定字段的数据 ProjectionOperation project = Aggregation.project("id","name","age");

Criteria criteria = Criteria.where("test2List").not().size(0); // 为输出的list 添加条件
AggregationOperation match = Aggregation.match(criteria);
//UnwindOperation unwind = Aggregation.unwind("test2List"); // 分解字段 , 例如 将数组大小为n的元素,分解为n个文档
Aggregation aggregation = Aggregation.newAggregation(lookupOperation,match);
List<test1> deto = mongoTemplate.aggregate(aggregation, "test1",test1.class).getMappedResults();
//// 上面 test1 必须是 主表的名字
//for (test1 test1 : deto) {
// System.out.println(test1);
//}
return deto;
}
}

编写service层

service层接口

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface SiteFlowService {

public void add(test1 s1);

public void remove(int id);

public test1 findByName(String name);

public int update(test1 s1);

public List<test1> findListByTest();

}

service层接口实现

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
@Service
public class SiteFlowServiceImpl implements SiteFlowService{

@Autowired
SiteFlowDao siteFlowDao;

@Override
public void add(test1 s1) {
siteFlowDao.add(s1);
}

@Override
public void remove(int id) {
siteFlowDao.remove(id);
}

@Override
public test1 findByName(String name) {
return siteFlowDao.findByName(name);
}

@Override
public int update(test1 s1) {
return siteFlowDao.update(s1);
}

@Override
public List<test1> findListByTest() {
return siteFlowDao.findListByTest();
}
}

controller层

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
@RestController
public class testController {
@Autowired
SiteFlowService siteFlowService;

@RequestMapping("/add")
public String add(test1 ss1){
siteFlowService.add(ss1);
return "add";
}

@RequestMapping("/delete")
public String delete(int id){
siteFlowService.remove(id);
return "delete";
}

@RequestMapping("/delete/{id}")
public String delete(@PathVariable("id") int id){
siteFlowService.remove(id);
return "delete";
}

@RequestMapping("/update")
public String update(test1 ss1){
siteFlowService.update(ss1);
return "update";
}

@RequestMapping("/TwoList")
public String test(){

List<test1> listByTest = siteFlowService.findListByTest();
System.out.println("======================="+listByTest+"============================");
return "list";

}
}

Swagger

springboot集成swagger

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>3.0.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>

新建 一个SwaggerConfig.java

1
2
3
4
5
@Configuration
@EnableSwagger2
public class swaggerConfig {

}

访问的地址变为:

http://localhost:8080/swagger-ui/或者

http://localhost:8080/swagger-ui/index.html

image-20220706185150574

配置swagger信息

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
@Configuration
@EnableSwagger2
public class swaggerConfig {

//配置了swagger的docket实例
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo());
}

// 配置swagger的信息=apiInfo
private ApiInfo apiInfo(){

Contact contact = new Contact("李坤松", "https://nan-bluesky.top/", "1537628435@qq.com");

return new ApiInfo(
"李坤松的swagger文档",
"遇事不决,但问春风",
"v1.0",
"https://nan-bluesky.top/",
contact,
"Apache 2.0",
"http://www.apache.org/licenses/LICENSE-2.0",
new ArrayList()

);
}

}

image-20220706190844903

swagger配置扫描接口

Docket:select()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
.select()
// RequestHandlerSelectors 配置要扫描接口的方式
// basePackage 指定要扫描的包
// any() 扫描全部
// none() 都不扫描
//withClassAnnotation() 扫描类上的注解,参数是一个注解的反射对象
// withMethodAnnotation() 扫描方法上的注解
.apis(RequestHandlerSelectors.basePackage("com.li.swagger.controller"))
// paths 过滤带有()请求路径的接口
.paths(PathSelectors.ant("/"))
.build();
}

配置是否启动swagger

在指定环境中,启动swagger

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

//配置了swagger的docket实例
@Bean
public Docket docket(Environment environment){

// 设置要显示的swagger环境
Profiles pro = Profiles.of("dev");
//获取项目的环境
boolean b = environment.acceptsProfiles(pro);



return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
.enable(b)
.select()
// RequestHandlerSelectors 配置要扫描接口的方式
// basePackage 指定要扫描的包
// any() 扫描全部
// none() 都不扫描
//withClassAnnotation() 扫描类上的注解,参数是一个注解的反射对象
// withMethodAnnotation() 扫描方法上的注解
.apis(RequestHandlerSelectors.basePackage("com.li.swagger.controller"))
// paths 过滤带有()请求路径的接口
.build();
}

配置api的文档名字

1
.groupName("lks")

配置多个分组,多个docket即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Bean
public Docket docket1(){
return new Docket(DocumentationType.SWAGGER_2).groupName("a");
}

@Bean
public Docket docket2(){
return new Docket(DocumentationType.SWAGGER_2).groupName("b");
}

@Bean
public Docket docket3(){
return new Docket(DocumentationType.SWAGGER_2).groupName("c");
}

测试

实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.li.swagger.pojo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;


@ApiModel("用户实体类")
public class User {

@ApiModelProperty("用户名")
public String name;

@ApiModelProperty("用户密码")
public String passwrod;
}

controller

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
package com.li.swagger.controller;


import com.li.swagger.pojo.User;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Api(tags = "hello的controller类")
@RestController
public class helloController {

@GetMapping(value = "/hello")
public String Hello(){
return "hello";
}


@PostMapping("/user")
public User user(){
return new User();
}


// Opera接口,不是放在类上,是接口上
@ApiOperation("hello控制类")
@GetMapping("/hello2")
public String hello2(@ApiParam("用户名") String username){
return "hello2"+username;
}

@ApiOperation("post控制类")
@PostMapping("/hello3")
public String hello3(@ApiParam("用户名") String username){
return "hello2"+username;
}

}

任务

异步

添加注解@Async

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Service
public class AsyncService {

@Async
public void hello(){

try{
Thread.sleep(3000);
}catch (InterruptedException e){
e.printStackTrace();
}

System.out.println("数据正在处理");

}

}
1
2
3
4
5
6
7
8
9
@EnableAsync
@SpringBootApplication
public class Springboot09TestApplication {

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

}

邮件发送

pom.xml 导入依赖

1
2
3
4
5
<!--email配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>

开启qq邮箱权限

image-20220707104734783

application.properties编写配置

1
2
3
4
5
6
# 发送邮件配置
spring.mail.host=smtp.qq.com
spring.mail.username=********@qq.com
spring.mail.password=开启邮箱权限获取的授权码
# 开启加密验证
spring.mail.properties.mail.smtl.ssl.enable=true
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
@SpringBootTest
class Springboot09TestApplicationTests {

@Autowired
JavaMailSenderImpl mailSender;

@Test
void contextLoads() {

// 一个简单的邮件发送
SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
simpleMailMessage.setSubject("你好呀"); // 邮件题目
simpleMailMessage.setText("谢谢你的贡献"); // 邮件内容

simpleMailMessage.setTo("15378435@qq.com");
simpleMailMessage.setFrom("15628435@qq.com");
mailSender.send(simpleMailMessage);

}

@Test
void contextLoad() throws MessagingException {
// 一个复杂的邮件

MimeMessage mimeMessage = mailSender.createMimeMessage();
// 组装
MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage,true);

messageHelper.setSubject("你好呀");
messageHelper.setText("<p>谢谢你的贡献</p>");

// 附件 文件可以使用本地绝对地址
messageHelper.addAttachment("infinity-1966300.jpg",new File("E:\\Pictures\\bgc\\infinity-1966300.jpg"));

messageHelper.setTo("15378435@qq.com");
messageHelper.setFrom("15378435@qq.com");

mailSender.send(mimeMessage);

}
}

邮件发送整合的方法

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
/**
*
* @param html : 是否支持html
* @param subject 邮件的标题
* @param text 邮件的内容
* @param sendToEmail 发送邮件给谁
* @param sendFromEmail 谁来发送邮件
* @throws MessagingException
* @Author lks
*/
public void sendMail(boolean html,String subject,String text,String sendToEmail,String sendFromEmail) throws MessagingException {
MimeMessage mimeMessage = mailSender.createMimeMessage();
// 组装
MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage,html);

messageHelper.setSubject(subject);
// 默认支持html格式是true
messageHelper.setText(text,true);

// 附件 文件可以使用本地绝对地址
messageHelper.addAttachment("infinity-1966300.jpg",new File("E:\\Pictures\\bgc\\infinity-1966300.jpg"));

messageHelper.setTo(sendToEmail);
messageHelper.setFrom(sendFromEmail);

mailSender.send(mimeMessage);
}

定时任务

1
2
3
4
5
6
7
8
9
10
11
@Service
public class ScheduledService {

// 在特定的时间执行
// cron 表达式 秒 分 时 日 月 周几
@Scheduled(cron = "00 09 11 * * ?")
public void hello(){
System.out.println("你被执行了");
}

}
1
2
3
4
5
6
7
8
9
10
@EnableAsync
@EnableScheduling // 开启定时任务的注解
@SpringBootApplication
public class Springboot09TestApplication {

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

}

Redis整合

测试的时候,要打开redis-server.exe

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

测试

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
@SpringBootTest
class Springboot10RedisApplicationTests {

@Autowired
private RedisTemplate redisTemplate;

/**
* 测试的时候 要开启 redis-server.exe
*/
@Test
void contextLoads() {

// redisTemplate
// opsForValue() 操作字符串 ,类似string
// 获取链接
// RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
// connection.flushDb();
// connection.flushAll();

redisTemplate.opsForValue().set("myksy","lks");
System.out.println(redisTemplate.opsForValue().get("myksy"));

}

@Test
void text1() throws JsonProcessingException {
User user = new User("李坤松", 4);
String s = new ObjectMapper().writeValueAsString(user);
redisTemplate.opsForValue().set("user",s);
System.out.println(redisTemplate.opsForValue().get("user"));
}

}

redis config

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
@Configuration
public class RedisConfig {
/**
* 编写自定义的 redisTemplate
* 这是一个比较固定的模板
*/
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
// 为了开发方便,直接使用<String, Object>
RedisTemplate<String, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);

// Json 配置序列化
// 使用 jackson 解析任意的对象
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
// 使用 objectMapper 进行转义
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
// String 的序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

// key 采用 String 的序列化方式
template.setKeySerializer(stringRedisSerializer);
// Hash 的 key 采用 String 的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value 采用 jackson 的序列化方式
template.setValueSerializer(jackson2JsonRedisSerializer);
// Hash 的 value 采用 jackson 的序列化方式
template.setHashValueSerializer(jackson2JsonRedisSerializer);
// 把所有的配置 set 进 template
template.afterPropertiesSet();

return template;
}
}

redis 工具类

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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
@Component
public final class RedisUtil {

@Autowired
private RedisTemplate<String, Object> redisTemplate;

// =============================common============================
/**
* 指定缓存失效时间
* @param key 键
* @param time 时间(秒)
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 根据key 获取过期时间
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}


/**
* 判断key是否存在
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}


/**
* 删除缓存
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}


// ============================String=============================

/**
* 普通缓存获取
* @param key 键
* @return
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}

/**
* 普通缓存放入
* @param key 键
* @param value 值
* @return true成功 false失败
*/

public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}


/**
* 普通缓存放入并设置时间
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/

public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}


/**
* 递增
* @param key 键
* @param delta 要增加几(大于0)
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}


/**
* 递减
* @param key 键
* @param delta 要减少几(小于0)
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}


// ================================Map=================================

/**
* HashGet
* @param key 键 不能为null
* @param item 项 不能为null
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}

/**
* 获取hashKey对应的所有键值
* @param key 键
* @return 对应的多个键值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}

/**
* HashSet
* @param key 键
* @param map 对应多个键值
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}


/**
* HashSet 并设置时间
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}


/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}


/**
* 删除hash表中的值
*
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}


/**
* 判断hash表中是否有该项的值
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}


/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
*
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}


/**
* hash递减
*
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}


// ============================set=============================

/**
* 根据key获取Set中的所有值
* @param key 键
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}


/**
* 根据value从一个set中查询,是否存在
*
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}


/**
* 将数据放入set缓存
*
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}


/**
* 将set数据放入缓存
*
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0)
expire(key, time);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}


/**
* 获取set缓存的长度
*
* @param key 键
*/
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}


/**
* 移除值为value的
*
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/

public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}

// ===============================list=================================

/**
* 获取list缓存的内容
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}


/**
* 获取list缓存的长度
*
* @param key 键
*/
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}


/**
* 通过索引 获取list中的值
*
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
*/
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}


/**
* 将list放入缓存
*
* @param key 键
* @param value 值
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}


/**
* 将list放入缓存
* @param key 键
* @param value 值
* @param time 时间(秒)
*/
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}

}


/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}

}


/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}


/**
* 根据索引修改list中的某条数据
*
* @param key 键
* @param index 索引
* @param value 值
* @return
*/

public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}


/**
* 移除N个值为value
*
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/

public long lRemove(String key, long count, Object value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}

}

}

分布式Dubbo+Zookeeper+Springboot

Zookeeper 安装地址,bin.tar.gz 时编译版的,要下这个

https://archive.apache.org/dist/zookeeper/

如果是按照狂神的进行学习,下载3.4.14,不然后面会出现报错的问题

springboot整合springsecurity

demo代码

狂神说的springboot整合springsecurity(没有使用数据库)

SpringSecurity的核心功能:

  • 用户认证(Authentication):系统判断用户是否能登录
  • 用户授权(Authorization):系统判断用户是否有权限去做某些事情

导入依赖

1
2
3
4
5
6
7
8
9
<!--springsecurity的依赖 和 thymeleaf对springsecurity的依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>

配置Mybatis

具体参考上面 Springboot整合mybatis

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 数据库驱动:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据源名称
spring.datasource.name=defaultDataSource
# 数据库连接地址
spring.datasource.url=jdbc:mysql://localhost:3306/myself?serverTimezone=UTC
# 数据库用户名&密码:
spring.datasource.username= root
spring.datasource.password= 123456
#下面这些内容是为了让MyBatis映射
#指定Mybatis的Mapper文件
mybatis.mapper-locations=classpath:mybatis/mapper/*xml
#指定Mybatis的实体目录
mybatis.type-aliases-package=com.li.pojo
# 应用服务 WEB 访问端口

数据库的设计

image-20220918200703517

SpringSecurity 分析

自定义SpringSecurity

自定义springsecurity需要先创建一个 SecurityConfig的类 继承 WebSecurityConfigurerAdapter

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
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
private UserDetailsService userDetailsService;

// 密码加密 springsecurity 必须进行密码加密
@Bean
public PasswordEncoder password() {
return new BCryptPasswordEncoder();
}

//认证
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}

//授权
@Override
protected void configure(HttpSecurity http) throws Exception {
// 登录
http.formLogin()
.loginPage("/login") // 自定义登录的界面
.defaultSuccessUrl("/hello").permitAll()
.and().csrf().disable();

// 设置访问权限
http.authorizeRequests()
// 这里的权限验证不需要 添加 'ROLE_'
.antMatchers("/hello1").hasRole("vip1") // 只有 vip1的用户才可以访问 /hello1 的请求
.antMatchers("/","/user/login","/user/register","/register").permitAll(); // 所有人都可以访问的请求

// 所有请求都必须经过认证才能访问,必须登录
http.authorizeRequests()
.anyRequest().authenticated();

// 遇到没有权限访问跳转的界面
http.exceptionHandling().accessDeniedPage("/uncheck");

}

// 忽略拦截,对静态资源所需的js,css等不设置拦截
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers("/**/*.js","/**/*.css","/**/*.jpg");
}
}

实现登录验证

使用UserDetailsService 接口从数据库中获取信息,实现登录验证功能

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
@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {

@Autowired
UserMapper userMapper;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

// 使用已经编辑好的 usermapper 进行数据库的查询操作
com.li.pojo.User user = userMapper.getUserByName(username);

System.out.println(user);
if(user == null){
throw new UsernameNotFoundException("用户名不存在");
}

// springsecurity 对role 有规定,前缀需要有一个 ROLE_ , 否则无法实现权限的验证
List<GrantedAuthority> auth = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_"+user.getRole());

// 构建security的 User 对象
return new User(user.getUsername(),new BCryptPasswordEncoder().encode(user.getPassword()),auth);

}
}

编辑controller

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
@Controller
public class LoginController {

@Autowired
UserService userService;

// 登录后的第一个界面
@RequestMapping("/hello")
public String hello(Model model){
model.addAttribute("msg1","输出");
return "view/hello";
}

// 登录
@RequestMapping({"/login","/"})
public String login(){
return "view/login";
}

// 注册
@RequestMapping("/user/register")
public String register(User user, String pwd2, Model model){
System.out.println(pwd2);
if(user.getPassword().equals(pwd2)){
userService.addUser(user);
System.out.println("注册成功");
}
else model.addAttribute("msg","请输入完整的账号密码");
return "redirect:view/login";
}

// vip1 才能访问
@RequestMapping("/hello1")
public String hello1(){
return "view/hello1";
}

// 没有权限访问
@RequestMapping("/uncheck")
public String uncheck(){
return "view/403";
}

}