背景介绍
该文档是在慕课网实战课程《2019版 微服务时代Spring Boot企业微信点餐系统》基础上总结而成,旨在记录Spring Boot一些相关知识,文章中涉及的代码都经过验证,可以直接使用。
该文档作为个人参考资料,会长期更新。
慕课网课程地址:2019版 微服务时代Spring Boot企业微信点餐系统
数据库设计
微信点餐数据库 - SQL.md
1 | -- 类目 |
API接口
商品列表
1 | GET /sell/buyer/product/list |
参数
1 | 无 |
返回
1 | { |
创建订单
1 | POST /sell/buyer/order/create |
参数
1 | name: "张三" |
返回
1 | { |
订单列表
1 | GET /sell/buyer/order/list |
参数
1 | openid: 18eu2jwk2kse3r42e2e |
返回
1 | { |
查询订单详情
1 | GET /sell/buyer/order/detail |
参数
1 | openid: 18eu2jwk2kse3r42e2e |
返回
1 | { |
取消订单
1 | POST /sell/buyer/order/cancel |
参数
1 | openid: 18eu2jwk2kse3r42e2e |
返回
1 | { |
获取openid
1 | 重定向到 /sell/wechat/authorize |
参数
1 | returnUrl: http://xxx.com/abc //【必填】 |
返回
1 | http://xxx.com/abc?openid=oZxSYw5ldcxv6H0EU67GgSXOUrVg |
支付订单
1 | 重定向 /sell/pay/create |
参数
1 | orderId: 161899085773669363 |
返回
1 | http://xxx.com/abc/order/161899085773669363 |
IntelliJ Idea 项目结构
POM依赖
pom.xml
1 | <?xml version="1.0" encoding="UTF-8"?> |
应用配置
全局配置文件
application.yml
1 | spring: |
开发配置文件
application-dev.yml
1 | spring: |
生产配置文件
application-prod.yml
1 | spring: |
配置文件类
BlogProperties
1 | package com.mindex.config; |
引用配置信息
BlogPropertiesTest
1 | package com.mindex.config; |
自定义配置文件
WechatAccountConfig
1 | package com.imooc.config; |
引用自定义的配置文件
WechatMpConfig
1 | package com.imooc.config; |
日志处理
SLF4j
Logback
logback-spring.xml
1 | <?xml version="1.0" encoding="UTF-8" ?> |
Swagger2 文档工具
引入POM依赖
1 | <dependency> |
创建Swagger配置类
1 | package com.mindex.config; |
在controller中引入Swagger注解
1 | package com.mindex.controller; |
启动tomcat查看文档
IDEA插件
JRebel插件
引入POM依赖
1 | <dependency> |
配置Application.java
1 |
|
配置maven project选项
选中Lifecycle-clean及compile
安装JRebel插件
配置JRebel插件
在IDEA里新建一个部署配置项。
运行测试
Lombok插件
好处:安装了Lombok插件和pom引用依赖后,可以简化代码,例如:无需再写get/set/toString方法,打印日志时直接使用log关键字等。
安装Lombok插件
引用pom依赖
1 | <dependencies> |
好处1:只要使用@Data、@Getter、@Setter、@ToString等注解,无需再写繁琐的get/set/toString方法,Lombok会在编译时自动加入代码。
1 | package com.imooc.dataobject; |
好处2:输出日志时,可以直接使用log关键字输出,支持参数引用。
1 | package com.imooc; |
项目运行类(主入口)
SellApplication
1 | package com.imooc; |
Enums枚举类
ResultEnum
1 | package com.imooc.enums; |
CodeEnum
1 | package com.imooc.enums; |
OrderStatusEnum
1 | package com.imooc.enums; |
PayStatusEnum
1 | package com.imooc.enums; |
ProductStatusEnum
1 | package com.imooc.enums; |
Util工具类
可以把常用的方法放在util包里,比如拼接VO视图、生成唯一编码等;
构造结果VO视图 - ResultVOUtil
1 | package com.imooc.utils; |
生成随机id - KeyUtil
1 | package com.imooc.utils; |
object转json - JsonUtil
1 | package com.imooc.utils; |
Cookie工具类 - CookieUtil
1 | package com.imooc.utils; |
比较金额(double类型)是否相等 - MathUtil
1 | package com.imooc.utils; |
EnumUtil
1 | package com.imooc.utils; |
Date2LongSerializer
1 | package com.imooc.utils.serializer; |
Data Object层(Entity)
主要用来映射数据库表及字段关系。
ProductCategory
1 | package com.imooc.dataobject; |
ProductInfo
1 | package com.imooc.dataobject; |
OrderMaster
1 | package com.imooc.dataobject; |
OrderDetail
1 | package com.imooc.dataobject; |
Repository层 (DAO) - JPA
JpaRepository对数据库常用操作进行了封装,通过继承JpaRepository可以快速实现数据库操作。
JpaRepository第一个参数是data object,第二个是该data object的主键。
Repository定义
ProductCategoryRepository
1 | package com.imooc.repository; |
repository单元测试
ProductCategoryRepositoryTest
1 | package com.imooc.repository; |
ProductInfoRepository
1 | package com.imooc.repository; |
OrderMasterRepository
1 | package com.imooc.repository; |
OrderDetailRepository
1 | package com.imooc.repository; |
Service层
CategoryService
CategoryService
1 | package com.imooc.service; |
需要使用
@Service
注解
CategoryServiceImpl
1 | package com.imooc.service.impl; |
CategoryServiceImplTest
1 | package com.imooc.service.impl; |
ProductService
ProductService
1 | package com.imooc.service; |
ProductServiceImpl
1 | package com.imooc.service.impl; |
ProductServiceImplTest
1 | package com.imooc.service.impl; |
OrderService
OrderService
1 | package com.imooc.service; |
OrderServiceImpl
1 | package com.imooc.service.impl; |
OrderServiceImplTest
1 | package com.imooc.service.impl; |
BuyerService
BuyerService
1 | package com.imooc.service; |
BuyerServiceImpl
1 | package com.imooc.service.impl; |
Controller层
@RestController 注解,直接返回json格式;
@RequestMapping(“buyer/product”) 注解声明服务的url前缀;
BuyerProductController
1 | package com.imooc.controller; |
BuyerOrderController
1 | package com.imooc.controller; |
SellerCategoryController
1 | package com.imooc.controller; |
SellerOrderController
1 | package com.imooc.controller; |
SellerProductController
1 | package com.imooc.controller; |
WechatController
1 | package com.imooc.controller; |
WeixinController
1 | package com.imooc.controller; |
VO视图层
引入VO对象主要是解决更容易返回前端所需要的数据结构。
要返回的数据格式如下:
第一层VO - ResultVO
1 | package com.imooc.VO; |
第二层VO - ProductVO
1 | package com.imooc.VO; |
第三层VO - ProductInfoVO
1 | package com.imooc.VO; |
DTO层
引入DTO对象主要是为了返回的数据结构与实体对象的数据结构不匹配的问题。
可以简单理解成数据库视图。
OrderDTO
1 | package com.imooc.dto; |
CartDTO
1 | package com.imooc.dto; |
Form类
CategoryForm
1 | package com.imooc.form; |
ProductForm
1 | package com.imooc.form; |
OrderForm
1 | package com.imooc.form; |
Exception异常处理
自定义异常
SellException
1 | package com.imooc.exception; |
ResponseBankException
1 | package com.imooc.exception; |
SellerAuthorizeException
1 | package com.imooc.exception; |
自定义异常处理器
SellExceptionHandler
1 | package com.imooc.handler; |
统一异常处理
ThymeleafTest
假设访问一个不存在的页面,抛出异常1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16package com.mindex.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
public class ThymeleafTest {
"/hello") (
public String hello() throws Exception {
throw new Exception("发生错误");
}
}
创建全局异常处理类
通过使用
@ControllerAdvice
定义统一的异常处理类,而不是在每个Controller中逐个定义。@ExceptionHandler
用来定义函数针对的异常类型,最后将Exception对象和请求URL映射到error.html
中。
1 | package com.mindex.exception; |
实现
error.html
页面展示:在templates
目录下创建error.html
,将请求的URL和Exception对象的message输出。
启动该应用,访问:
http://localhost:8080/hello
,可以看到如下错误提示页面。
通过实现上述内容之后,我们只需要在Controller中抛出Exception,当然我们可能会有多种不同的Exception。然后在
@ControllerAdvice
类中,根据抛出的具体Exception类型匹配@ExceptionHandler
中配置的异常类型来匹配错误映射和处理。
返回json格式
创建统一的JSON返回对象,code:消息类型,message:消息内容,url:请求的url,data:请求返回的数据。
1 | package com.mindex.entities; |
创建一个自定义异常,用来实验捕获该异常,并返回json。1
2
3
4
5
6
7
8package com.mindex.exception;
public class MyException extends Exception {
public MyException(String message) {
super(message);
}
}
Controller
中增加json映射,抛出MyException
异常。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15package com.mindex.controller;
import com.mindex.exception.MyException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
public class HelloController {
"/json") (
public String json() throws MyException {
throw new MyException("发生错误2");
}
}
为MyException
异常创建对应的处理。1
2
3
4
5
6
7
8
9
10 (value = MyException.class)
public ErrorInfo<String> jsonErrorHandler(HttpServletRequest req, MyException e) throws Exception {
ErrorInfo<String> r = new ErrorInfo<>();
r.setMessage(e.getMessage());
r.setCode(ErrorInfo.ERROR);
r.setData("Some Data");
r.setUrl(req.getRequestURL().toString());
return r;
}
启动应用,访问: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
![](https://ws1.sinaimg.cn/large/006By2pOgy1fqtwhe1igxj30xy0cmdif.jpg)
---
## Authorize用户有效性鉴定
### SellerAuthorizeAspect
```java
package com.imooc.aspect;
import com.imooc.constant.CookieConstant;
import com.imooc.constant.RedisConstant;
import com.imooc.exception.SellerAuthorizeException;
import com.imooc.utils.CookieUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
@Aspect
@Component
@Slf4j
public class SellerAuthorizeAspect {
@Autowired
private StringRedisTemplate redisTemplate;
@Pointcut("execution(public * com.imooc.controller.Seller*.*(..))" +
"&& !execution(public * com.imooc.controller.SellerUserController.*(..))")
public void verify() {
}
@Before("verify()")
public void doVerify() {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
//查询cookie
Cookie cookie = CookieUtil.get(request, CookieConstant.TOKEN);
if (cookie == null) {
log.warn("【登录校验】Cookie中查不到token");
throw new SellerAuthorizeException();
}
//去redis里查询
String tokenValue = redisTemplate.opsForValue().get(String.format(RedisConstant.TOKEN_PREFIX, cookie.getValue()));
if (StringUtils.isEmpty(tokenValue)) {
log.warn("【登录校验】Redis中查不到token");
throw new SellerAuthorizeException();
}
}
}
使用Mabatis注解方式实现增删改查
mapper层
ProductCategoryMapper
1 | package com.imooc.dataobject.mapper; |
mapper单元测试
ProductCategoryMapperTest
1 | package com.imooc.dataobject.mapper; |
DAO层
ProductCategoryDao
1 | package com.imooc.dataobject.dao; |
对象转换
Form表单对象(Json)转成DTO
OrderForm2OrderDTOConverter
1 | package com.imooc.converter; |
Data Object转DTO
OrderMaster2OrderDTOConverter
1 | package com.imooc.converter; |
中文字符乱码
application.properties
1 | spring.http.encoding.force=true |
IDEA设置
网页模板
Freemarker
common/header.ftl
1 | <head> |
common/nav.ftl
1 | <nav class="navbar navbar-inverse navbar-fixed-top" id="sidebar-wrapper" role="navigation"> |
common/success.ftl
1 | <html> |
common/error.ftl
1 | <html> |
category/index.ftl
category/list.ftl
1 | <html> |
product/index.ftl
1 | <html> |
product/list.ftl
1 | <html> |
order/list.ftl
1 | <html> |
order/detail.ftl
1 | <html> |
CSS样式
static/css
style.css
1 | body { |
Thymeleaf
pom.xml
1 | <dependency> |
index.html
注意:模板的位置可以直接放在src/main/resources/templates/目录下。
application.properties
1 | spring.http.encoding.force=true |
ThymeleafTest.java
1 | package com.mindex.controller; |
运行结果如下:
数据库操作
JDBC方式 - JdbcTemplate
引入POM依赖1
2
3
4
5
6
7
8
9<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.21</version>
</dependency>
修改配置文件application.properties
1
2
3
4spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=welcome
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
Service接口:UserService.java
1
2
3
4
5
6
7
8
9
10
11
12package com.mindex.service;
public interface UserService {
void create(String name, Integer age);
void deleteByName(String name);
Integer getAllUsers();
void deleteAllUsers();
}
Service实现:UserServiceImpl.java
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
33package com.mindex.service.impl;
import com.mindex.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
public class UserServiceImpl implements UserService {
private JdbcTemplate jdbcTemplate;
public void create(String name, Integer age) {
jdbcTemplate.update("INSERT INTO user (name, age) VALUES (?,?);", name, age);
}
public void deleteByName(String name) {
jdbcTemplate.update("delete from USER where NAME = ?", name);
}
public Integer getAllUsers() {
return jdbcTemplate.queryForObject("select count(1) from USER", Integer.class);
}
public void deleteAllUsers() {
jdbcTemplate.update("delete from USER");
}
}
单元测试:UserServiceImplTest.java
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
46package com.mindex.service.impl;
import com.mindex.service.UserService;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
(SpringJUnit4ClassRunner.class)
public class UserServiceImplTest {
private UserService userService;
public void create() throws Exception {
// 插入5个用户
userService.create("a", 1);
userService.create("b", 2);
userService.create("c", 3);
userService.create("d", 4);
userService.create("e", 5);
Assert.assertEquals(5, userService.getAllUsers().intValue());
}
public void deleteByName() throws Exception {
userService.deleteByName("b");
userService.deleteByName("c");
Assert.assertEquals(3, userService.getAllUsers().intValue());
}
public void getAllUsers() throws Exception {
}
public void deleteAllUsers() throws Exception {
}
}
JPA方式 - Spring-data-jpa
引入pom依赖:pom.xml
1
2
3
4<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
在application.properties
创建数据库连接信息。1
2
3
4
5spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=123qweasd
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.properties.hibernate.hbm2ddl.auto=create-drop
spring.jpa.properties.hibernate.hbm2ddl.auto
是hibernate的配置属性,其主要作用是:自动创建、更新、验证数据库表结构。该参数的几种配置如下:
create
:每次加载hibernate时都会删除上一次的生成的表,然后根据你的model类再重新来生成新表,哪怕两次没有任何改变也要这样执行,这就是导致数据库表数据丢失的一个重要原因。create-drop
:每次加载hibernate时根据model类生成表,但是sessionFactory一关闭,表就自动删除。update
:最常用的属性,第一次加载hibernate时根据model类会自动建立起表的结构(前提是先建立好数据库),以后加载hibernate时根据model类自动更新表结构,即使表结构改变了但表中的行仍然存在不会删除以前的行。要注意的是当部署到服务器后,表结构是不会被马上建立起来的,是要等应用第一次运行起来后才会。validate
:每次加载hibernate时,验证创建数据库表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值。
实体类(Entity)
创建一个User实体,包含id(主键)、name(姓名)、age(年龄)属性,通过ORM框架其会被映射到数据库表中,由于配置了hibernate.hbm2ddl.auto
,在应用启动的时候框架会自动去数据库中创建对应的表。
User
1 | package com.mindex.entities; |
数据访问接口(Repository)
UserRepository
下面针对User实体创建对应的Repository接口实现对该实体的数据访问,如下代码:UserRepository.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18package com.mindex.repository;
import com.mindex.entities.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface UserRepository extends JpaRepository<User, Long> {
User findByName(String name);
User findByNameAndAge(String name, Integer age);
"from User u where u.name=:name") (
User findUser(@Param("name") String name);
}
在Spring-data-jpa中,只需要编写类似上面这样的接口就可实现数据访问。不再像我们以往编写了接口时候还需要自己编写接口实现类,直接减少了我们的文件清单。
下面对上面的UserRepository
做一些解释,该接口继承自JpaRepository
,通过查看JpaRepository
接口的API文档,可以看到该接口本身已经实现了创建(save)、更新(save)、删除(delete)、查询(findAll、findOne)等基本操作的函数,因此对于这些基础操作的数据访问就不需要开发者再自己定义。
在我们实际开发中,JpaRepository
接口定义的接口往往还不够或者性能不够优化,我们需要进一步实现更复杂一些的查询或操作。由于本文重点在spring boot中整合spring-data-jpa,在这里先抛砖引玉简单介绍一下spring-data-jpa中让我们兴奋的功能,后续再单独开篇讲一下spring-data-jpa中的常见使用。
在上例中,我们可以看到下面两个函数:
User findByName(String name)
User findByNameAndAge(String name, Integer age)
它们分别实现了按name查询User实体和按name和age查询User实体,可以看到我们这里没有任何类SQL语句就完成了两个条件查询方法。这就是Spring-data-jpa的一大特性:通过解析方法名创建查询。
除了通过解析方法名来创建查询外,它也提供通过使用@Query 注解来创建查询,您只需要编写JPQL语句,并通过类似“:name”来映射@Param指定的参数,就像例子中的第三个findUser函数一样。
Spring-data-jpa的能力远不止本文提到的这些,由于本文主要以整合介绍为主,对于Spring-data-jpa的使用只是介绍了常见的使用方式。
单元测试
UserRepositoryTest
在完成了上面的数据访问接口之后,按照惯例就是编写对应的单元测试来验证编写的内容是否正确。这里就不多做介绍,主要通过数据操作和查询来反复验证操作的正确性。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
51package com.mindex.repository;
import com.mindex.entities.User;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
(SpringJUnit4ClassRunner.class)
public class UserRepositoryTest {
private UserRepository userRepository;
public void test() throws Exception {
// 创建10条记录
userRepository.save(new User("AAA", 10));
userRepository.save(new User("BBB", 20));
userRepository.save(new User("CCC", 30));
userRepository.save(new User("DDD", 40));
userRepository.save(new User("EEE", 50));
userRepository.save(new User("FFF", 60));
userRepository.save(new User("GGG", 70));
userRepository.save(new User("HHH", 80));
userRepository.save(new User("III", 90));
userRepository.save(new User("JJJ", 100));
// 测试findAll, 查询所有记录
Assert.assertEquals(10, userRepository.findAll().size());
// 测试findByName, 查询姓名为FFF的User
Assert.assertEquals(60, userRepository.findByName("FFF").getAge().longValue());
// 测试findUser, 查询姓名为FFF的User
Assert.assertEquals(60, userRepository.findUser("FFF").getAge().longValue());
// 测试findByNameAndAge, 查询姓名为FFF并且年龄为60的User
Assert.assertEquals("FFF", userRepository.findByNameAndAge("FFF", 60).getName());
// 测试删除姓名为AAA的User
userRepository.delete(userRepository.findByName("AAA"));
// 测试findAll, 查询所有记录, 验证上面的删除是否成功
Assert.assertEquals(9, userRepository.findAll().size());
}
}
集成Redis
手动配置集成Redis
引入POM依赖:pom.xml
1
2
3
4
5<!--此处并不是直接使用spring提供的redis-starter-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
外部配置文件:application.yaml
1
2
3
4
5
6
7
8
9jedis:
host: 127.0.0.1
port: 6379
pool:
max-idle: 300
min-idle: 10
max-total: 600
max-wait: 1000
block-when-exhausted: true
Java配置类(替代传统xml):RedisConfig.java
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
40package com.mindex.config;
import lombok.Data;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class RedisConfig {
"jedis.config") (
public JedisPoolConfig jedisPoolConfig(@Value("${jedis.pool.min-idle}") int minIdle,
@Value("${jedis.pool.max-idle}") int maxIdle,
@Value("${jedis.pool.max-wait}") int maxWaitMillis,
@Value("${jedis.pool.block-when-exhausted}") boolean blockWhenExhausted,
@Value("${jedis.pool.max-total}") int maxTotal) {
JedisPoolConfig config = new JedisPoolConfig();
config.setMinIdle(minIdle);
config.setMaxIdle(maxIdle);
config.setMaxWaitMillis(maxWaitMillis);
config.setMaxTotal(maxTotal);
// 连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true
config.setBlockWhenExhausted(blockWhenExhausted);
// 是否启用pool的jmx管理功能, 默认true
config.setJmxEnabled(true);
return config;
}
public JedisPool jedisPool(@Qualifier("jedis.config") JedisPoolConfig config,
@Value("${jedis.host}") String host,
@Value("${jedis.port}") int port) {
return new JedisPool(config, host, port);
}
}
Service接口定义:RedisService.java
1
2
3
4
5
6
7
8
9package com.mindex.service;
public interface RedisService {
String get(String key);
boolean set(String key, String val);
}
Service接口实现类:RedisServiceImpl.java
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
40package com.mindex.service.impl;
import com.mindex.service.RedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
public class RedisServiceImpl implements RedisService {
// 此处直接注入即可
private JedisPool jedisPool;
public String get(String key) {
Jedis jedis = this.jedisPool.getResource();
String ret;
try {
ret = jedis.get(key);
} finally {
if (jedis != null)
jedis.close();
}
return ret;
}
public boolean set(String key, String val) {
Jedis jedis = this.jedisPool.getResource();
try {
return "OK".equals(jedis.set(key, val));
} finally {
if (jedis != null)
jedis.close();
}
}
}
测试:RedisServiceImplTest.java
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
28package com.mindex.service.impl;
import com.mindex.service.RedisService;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
(SpringRunner.class)
public class RedisServiceImplTest {
private RedisService redisService;
public void testGet() {
// test set
boolean status = this.redisService.set("foo", "bar");
Assert.assertTrue(status);
// test get
String str = this.redisService.get("foo");
Assert.assertEquals("bar", str);
}
}
在Redis中检查结果:
使用spring-boot-starter-data-redis
外部配置文件:application.yaml
1
2
3
4
5
6
7
8
9jedis:
host: 127.0.0.1
port: 6379
pool:
max-idle: 300
min-idle: 10
max-total: 600
max-wait: 1000
block-when-exhausted: true
配置类:RedisConfig1.java
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
64package com.mindex.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
public class RedisConfig1 {
private RedisConnectionFactory redisConnectionFactory;
/**
* 实例化 RedisTemplate 对象
*
*/
public RedisTemplate<String, Object> functionDomainRedisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
this.initRedisTemplate(redisTemplate, redisConnectionFactory);
return redisTemplate;
}
/**
* 序列化设置
*/
private void initRedisTemplate(RedisTemplate<String, Object> redisTemplate, RedisConnectionFactory factory) {
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new JdkSerializationRedisSerializer());
redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
redisTemplate.setConnectionFactory(factory);
}
public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForHash();
}
public ValueOperations<String, Object> valueOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForValue();
}
public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForList();
}
public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForSet();
}
public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForZSet();
}
}
简单测试:RedisAnotherConfigTest.java
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
39package com.mindex;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.test.context.junit4.SpringRunner;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
(SpringRunner.class)
public class RedisAnotherConfigTest {
private ValueOperations<String, Object> valueOperations;
private RedisTemplate<String, Object> redisTemplate;
public void contextLoads() {
}
public void testStringOps() {
this.valueOperations.set("k1", "spring-redis");
Boolean hasKey = this.redisTemplate.hasKey("k1");
assertEquals(true, hasKey);
Object str = this.valueOperations.get("k1");
assertNotNull(str);
assertEquals("spring-redis", str.toString());
}
}
邮件
使用JavaMailSender发送邮件
引入POM依赖:1
2
3
4<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
增加配置:application.properties
注意:这里的
password
是邮箱授权码,不是邮箱密码。
1
2
3
4
5
6 spring.mail.host=smtp.qq.com
spring.mail.username=85648606@qq.com
spring.mail.password=clqpsraiifwqbidg
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.starttls.required=true
单元测试:MailTest.java1
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
28package com.mindex;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.test.context.junit4.SpringRunner;
(SpringRunner.class)
public class MailTest {
private JavaMailSender mailSender;
public void sendSimpleMail() throws Exception {
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom("85648606@qq.com");
message.setTo("wfgdlut@msn.com");
message.setSubject("主题:简单邮件");
message.setText("测试邮件内容");
mailSender.send(message);
}
}
发送html格式邮件
1 |
|
发送包含内嵌图片的邮件
1 | /** |
发送包含附件的邮件
1 | /** |