SpringSecurity

spring Security

相关依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.7</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.70</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency>
1
2
3
4
5
6
7
8
9
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

相关配置

实现RedisSerializer接口

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
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T> {

public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;

static {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
}

private final Class<T> clazz;

public FastJson2JsonRedisSerializer(Class<T> clazz) {
super();
this.clazz = clazz;
}

/**
* 序列化
*/
@Override
public byte[] serialize(T t) throws SerializationException {
if (null == t) {
return new byte[0];
}
return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
}

/**
* 反序列化
*/
@Override
public T deserialize(byte[] bytes) throws SerializationException {
if (null == bytes || bytes.length <= 0) {
return null;
}
String str = new String(bytes, DEFAULT_CHARSET);
return (T) JSON.parseObject(str, clazz);
}
}

配置redisTemplate

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
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
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.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisCacheAutoConfiguration {

@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
FastJson2JsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJson2JsonRedisSerializer<>(Object.class);

StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);

// value序列化方式采用fastJson
template.setValueSerializer(fastJsonRedisSerializer);
// hash的value序列化方式采用fastJson
template.setHashValueSerializer(fastJsonRedisSerializer);

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
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
* spring redis 工具类
**/
@Component
public class RedisUtil {

@Autowired
private RedisTemplate<Object, Object> redisTemplate;

/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @return 缓存的对象
*/
public ValueOperations<Object, Object> setCacheObject(Object key, Object value) {
ValueOperations<Object, Object> operation = redisTemplate.opsForValue();
operation.set(key, value);
return operation;
}

/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param timeout 时间
* @param timeUnit 时间颗粒度
* @return 缓存的对象
*/
public ValueOperations<Object, Object> setCacheObject(Object key, Object value, Integer timeout, TimeUnit timeUnit) {
ValueOperations<Object, Object> operation = redisTemplate.opsForValue();
operation.set(key, value, timeout, timeUnit);
return operation;
}

/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public Object getCacheObject(Object key) {
ValueOperations<Object, Object> operation = redisTemplate.opsForValue();
return operation.get(key);
}

/**
* 删除单个对象
*
* @param key
*/
public void deleteObject(Object key) {
redisTemplate.delete(key);
}

/**
* 删除集合对象
*
* @param collection
*/
public void deleteObject(Collection collection) {
redisTemplate.delete(collection);
}

public Long getExpire(String key) {
return redisTemplate.getExpire(key);
}

public void expire(String key, int expire, TimeUnit timeUnit) {
redisTemplate.expire(key, expire, timeUnit);
}

/**
* 缓存List数据
*
* @param key 缓存的键值
* @param dataList 待缓存的List数据
* @return 缓存的对象
*/
public ListOperations<Object, Object> setCacheList(Object key, List<Object> dataList) {
ListOperations listOperation = redisTemplate.opsForList();
if (null != dataList) {
int size = dataList.size();
for (Object o : dataList) {
listOperation.leftPush(key, o);
}
}
return listOperation;
}

/**
* 获得缓存的list对象
*
* @param key 缓存的键值
* @return 缓存键值对应的数据
*/
public List<Object> getCacheList(String key) {
List<Object> dataList = new ArrayList<>();
ListOperations<Object, Object> listOperation = redisTemplate.opsForList();
Long size = listOperation.size(key);
if (null != size) {
for (int i = 0; i < size; i++) {
dataList.add(listOperation.index(key, i));
}
}
return dataList;
}

/**
* 缓存Set
*
* @param key 缓存键值
* @param dataSet 缓存的数据
* @return 缓存数据的对象
*/
public BoundSetOperations<Object, Object> setCacheSet(String key, Set<Object> dataSet) {
BoundSetOperations<Object, Object> setOperation = redisTemplate.boundSetOps(key);
for (Object o : dataSet) {
setOperation.add(o);
}
return setOperation;
}

/**
* 获得缓存的set
*
* @param key
* @return
*/
public Set<Object> getCacheSet(Object key) {
Set<Object> dataSet = new HashSet<>();
BoundSetOperations<Object, Object> operation = redisTemplate.boundSetOps(key);
dataSet = operation.members();
return dataSet;
}

/**
* 缓存Map
*
* @param key
* @param dataMap
* @return
*/
public HashOperations<Object, Object, Object> setCacheMap(Object key, Map<Object, Object> dataMap) {
HashOperations hashOperations = redisTemplate.opsForHash();
if (null != dataMap) {
for (Map.Entry<Object, Object> entry : dataMap.entrySet()) {
hashOperations.put(key, entry.getKey(), entry.getValue());
}
}
return hashOperations;
}

/**
* 获得缓存的Map
*
* @param key
* @return
*/
public Map<Object, Object> getCacheMap(Object key) {
Map<Object, Object> map = redisTemplate.opsForHash().entries(key);
return map;
}

/**
* 获得缓存的基本对象列表
*
* @param pattern 字符串前缀
* @return 对象列表
*/
public Collection<Object> keys(String pattern) {
return redisTemplate.keys(pattern);
}
}

jwt工具类

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
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;

/**
* @author linZT
* @date 2022-4-1 14:39
* JWT工具类
*/
public class JwtUtils {

/**
* 两个常量: 过期时间;秘钥
*/
public static final long EXPIRE = 1000*60*60*24;
public static final String SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";

/**
* 生成token字符串的方法
* @param id
* @param nickname
* @return
*/
public static String getJwtToken(String id,String nickname){
String JwtToken = Jwts.builder()
//JWT头信息
.setHeaderParam("typ", "JWT")
.setHeaderParam("alg", "HS2256")
//设置分类;设置过期时间 一个当前时间,一个加上设置的过期时间常量
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
//设置token主体信息,存储用户信息
.claim("id", id)
.claim("nickname", nickname)
//设置密钥
.signWith(SignatureAlgorithm.HS256, SECRET)
.compact();
return JwtToken;
}

/**
* 判断token是否存在与有效
* @Param jwtToken
*/
public static boolean checkToken(String jwtToken){
if (StringUtils.isEmpty(jwtToken)){
return false;
}
try{
//验证token
Jwts.parser().setSigningKey(SECRET).parseClaimsJws(jwtToken);
}catch (Exception e){
e.printStackTrace();
return false;
}
return true;
}

/**
* 判断token是否存在与有效
* @Param request
*/
public static boolean checkToken(HttpServletRequest request){
try {
String token = request.getHeader("token");
if (StringUtils.isEmpty(token)){
return false;
}
Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token);
}catch (Exception e){
e.printStackTrace();
return false;
}
return true;
}

/**
* 根据token获取会员id
* @Param request
*/
public static String getMemberIdByJwtToken(HttpServletRequest request){
String token = request.getHeader("token");
if (StringUtils.isEmpty(token)){
return "";
}
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token);
Claims body = claimsJws.getBody();
return (String) body.get("id");
}
}

RedisCache

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
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.TimeUnit;

@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
public class RedisCache
{
@Autowired
public RedisTemplate redisTemplate;

/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
*/
public <T> void setCacheObject(final String key, final T value)
{
redisTemplate.opsForValue().set(key, value);
}

/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param timeout 时间
* @param timeUnit 时间颗粒度
*/
public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
{
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}

/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout)
{
return expire(key, timeout, TimeUnit.SECONDS);
}

/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @param unit 时间单位
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout, final TimeUnit unit)
{
return redisTemplate.expire(key, timeout, unit);
}

/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public <T> T getCacheObject(final String key)
{
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
}

/**
* 删除单个对象
*
* @param key
*/
public boolean deleteObject(final String key)
{
return redisTemplate.delete(key);
}

/**
* 删除集合对象
*
* @param collection 多个对象
* @return
*/
public long deleteObject(final Collection collection)
{
return redisTemplate.delete(collection);
}

/**
* 缓存List数据
*
* @param key 缓存的键值
* @param dataList 待缓存的List数据
* @return 缓存的对象
*/
public <T> long setCacheList(final String key, final List<T> dataList)
{
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
return count == null ? 0 : count;
}

/**
* 获得缓存的list对象
*
* @param key 缓存的键值
* @return 缓存键值对应的数据
*/
public <T> List<T> getCacheList(final String key)
{
return redisTemplate.opsForList().range(key, 0, -1);
}

/**
* 缓存Set
*
* @param key 缓存键值
* @param dataSet 缓存的数据
* @return 缓存数据的对象
*/
public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet)
{
BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
Iterator<T> it = dataSet.iterator();
while (it.hasNext())
{
setOperation.add(it.next());
}
return setOperation;
}

/**
* 获得缓存的set
*
* @param key
* @return
*/
public <T> Set<T> getCacheSet(final String key)
{
return redisTemplate.opsForSet().members(key);
}

/**
* 缓存Map
*
* @param key
* @param dataMap
*/
public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
{
if (dataMap != null) {
redisTemplate.opsForHash().putAll(key, dataMap);
}
}

/**
* 获得缓存的Map
*
* @param key
* @return
*/
public <T> Map<String, T> getCacheMap(final String key)
{
return redisTemplate.opsForHash().entries(key);
}

/**
* 往Hash中存入数据
*
* @param key Redis键
* @param hKey Hash键
* @param value 值
*/
public <T> void setCacheMapValue(final String key, final String hKey, final T value)
{
redisTemplate.opsForHash().put(key, hKey, value);
}

/**
* 获取Hash中的数据
*
* @param key Redis键
* @param hKey Hash键
* @return Hash中的对象
*/
public <T> T getCacheMapValue(final String key, final String hKey)
{
HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
return opsForHash.get(key, hKey);
}

/**
* 删除Hash中的数据
*
* @param key
* @param hkey
*/
public void delCacheMapValue(final String key, final String hkey)
{
HashOperations hashOperations = redisTemplate.opsForHash();
hashOperations.delete(key, hkey);
}

/**
* 获取多个Hash中的数据
*
* @param key Redis键
* @param hKeys Hash键集合
* @return Hash对象集合
*/
public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
{
return redisTemplate.opsForHash().multiGet(key, hKeys);
}

/**
* 获得缓存的基本对象列表
*
* @param pattern 字符串前缀
* @return 对象列表
*/
public Collection<String> keys(final String pattern)
{
return redisTemplate.keys(pattern);
}
}

响应配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class WebUtils {

public static String renderString(HttpServletResponse response,String string){
try {
response.setStatus(200);
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
response.getWriter().print(string);
}catch (IOException e){
e.printStackTrace();
}
return null;
}
}

实现步骤

封装前台传过来的对象

springsecurity会将前端传来的数据(可能是一个实体类,或者只是用户名和密码)封装成Authentication对象,UsernamePasswordAuthenticationToken是其中的一个实现类,AuthenticationManager会对这个封装的对象进行认证

1
2
3
4
5
6
7
8
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
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 LoginServiceImpl implements LoginService {
@Autowired
private AuthenticationManager authenticationManager;

@Autowired
@Qualifier("myRedis")
private RedisTemplate redisTemplate;

@Override
public ResponseResult login(User user) {
//将登陆的信息封装成一个Authentication对象
//UsernamePasswordAuthenticationToken是其中的一个实现类
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword());
//authenticationManager对其进行校验
//会使用自定义的UserDetailsServiceImpl进行校验
Authentication authentication = authenticationManager.authenticate(authenticationToken);
if (Objects.isNull(authentication)){
throw new RuntimeException("登录失败");
}
//认证通过,使用userId生成jwt,并且返回
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
String userid = loginUser.getUser().getId().toString();
String jwtToken = JwtUtils.getJwtToken(userid);
Map<String, String> map = new HashMap<>();
map.put("token",jwtToken);
//将用户完整信息存入Redis
redisTemplate.opsForValue().set("login:"+userid,loginUser.getUser());
return new ResponseResult(200,"登录成功",map);
}
}

自定义数据校验方法

这一步是替换spring security从内存查数据的步骤,自定义为从数据库进行查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//查询用户信息
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.eq("user_name",username);
User user = userMapper.selectOne(userQueryWrapper);

if (user == null){
throw new RuntimeException("用户名错误");
}

//TODO 查询用户权限信息

return new LoginUser(user);
}
}

springsecurity 会将前台传来的密码进行加密,用加密后的数据与数据库进行比较,这也要求用户注册时存入密码也要使用相同的加密方式

1
2
3
4
5
6
7
8
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}

自定义登录接口

1
2
3
4
5
6
7
8
9
10
@RestController
public class LoginContronller {
@Autowired
private LoginService loginService;

@PostMapping("/user/login")
public ResponseResult login(@RequestBody User user){
return loginService.login(user);
}
}
1
2
3
public interface LoginService {
ResponseResult login(User user);
}
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 LoginServiceImpl implements LoginService {
@Autowired
private AuthenticationManager authenticationManager;

@Autowired
@Qualifier("myRedis")
private RedisTemplate redisTemplate;

@Override
public ResponseResult login(User user) {
//将登陆的信息封装成一个Authentication对象
//UsernamePasswordAuthenticationToken是其中的一个实现类
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword());
//authenticationManager对其进行校验
//会使用前面自定义的UserDetailsServiceImpl进行校验
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
if (Objects.isNull(authenticate)){
throw new RuntimeException("登录失败");
}
//认证通过,使用userId生成jwt,并且返回
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
String userid = loginUser.getUser().getId().toString();
String jwtToken = JwtUtils.getJwtToken(userid);
Map<String, String> map = new HashMap<>();
map.put("token",jwtToken);
//将用户完整信息存入Redis
redisTemplate.opsForValue().set("login:"+userid,loginUser);
return new ResponseResult(200,"登录成功",map);
}
}

需要对登录接口进行放行

1
2
3
4
5
6
7
8
9
10
11
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/user/login").anonymous()
.anyRequest().authenticated();
}

认证过滤器

对于任意一个请求都需要进行拦截,对于携带Token的请求进行判断,如果成功就对其进行放行

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
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
@Qualifier("myRedis")
private RedisTemplate redisTemplate;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//获取token
String token = request.getHeader("token");
if (!StringUtils.hasText(token)){
filterChain.doFilter(request,response);
return;
}
String userid = null;
//解析token
try {
userid = JwtUtils.getMemberIdByJwtToken(token);
}catch (Exception e){
throw new RuntimeException("token非法");
}
//从redis中获取用户信息
String redisKey = "login:"+userid;
LoginUser loginUser = (LoginUser) redisTemplate.opsForValue().get(redisKey);
//存入SecruityContextHolder
if (Objects.isNull(loginUser)){
throw new RuntimeException("用户未登录");
}
//TODO 权限封装
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(loginUser,null,null);
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
//放行
filterChain.doFilter(request,response);
}
}
1
2
3
4
5
6
7
8
9
10
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
}

自定义退出接口

1
2
3
4
5
6
7
8
9
10
11
12
@Override
public ResponseResult logout() {
//获取SecurityContextHolder中用户的信息
UsernamePasswordAuthenticationToken authentication
= (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
Long userId = loginUser.getUser().getId();
redisTemplate.delete("login:"+userId);


return new ResponseResult(200,"注销成功");
}

授权

授权

再配置类添加注解

1
@EnableGlobalMethodSecurity(prePostEnabled = true)

相关方法上添加配置

1
@PreAuthorize("hasAuthority('响应权限')")
1
2
3
4
5
6
7
8
9
10
@Service
public class UserDetailsServiceImpl implements UserDetailsService {

......

//TODO 查询用户权限信息
List<String> list = new ArrayList<>(Arrays.asList("自定义的权限","admin"));
return new LoginUser(user,list);
}
}
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
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginUser implements UserDetails {

......

private List<String> permission;

public LoginUser(User user, List<String> permission) {
this.user = user;
this.permission = permission;
}

@JSONField(serialize = false)
private List<SimpleGrantedAuthority> authorities;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
if (authorities==null){
return authorities;
}
authorities = permission.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
return authorities;

}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

......

//TODO 权限封装
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(loginUser,null,loginUser.getAuthorities());
}
}

RBAC权限模型

用户表
id user_name password
1 kuang 123456
2 zhang 123456
角色表
id role
1 admin
2 worker
权限表
id menu
1 insert
2 update
3 delete
4 select
用户角色表
id user_id role_id
1 1 1
2 1 2
3 2 2
角色权限表
id role_id menu_id
1 1 1
2 1 2
3 1 3
4 1 4
5 2 4

根据用户id查询权限

1
2
3
4
5
6
7
SELECT DISTINCT menu.menu
FROM user user
LEFT JOIN user_role ur ON ur.user_id = user.id
LEFT JOIN role_menu rm ON ur.role_id = rm.role_id
LEFT JOIN role role ON ur.role_id = role.id
LEFT JOIN menu menu ON rm.menu_id = menu.id
where user.id = '1'
1
2
3
4
5
6
7
8
9
10
11
12
13
<?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="kuang.mapper.UserMapper">
<select id="selectMenuByUserId" resultType="java.lang.String">
SELECT DISTINCT menu.menu
FROM user user
LEFT JOIN user_role ur ON ur.user_id = user.id
LEFT JOIN role_menu rm ON ur.role_id = rm.role_id
LEFT JOIN role role ON ur.role_id = role.id
LEFT JOIN menu menu ON rm.menu_id = menu.id
where user.id = #{userid}
</select>
</mapper>

自定义异常处理

再springsecurity中

认证出现的异常都会被封装成AuthenticationException,然后调用AuthenticationEntryPoint对象进行异常处理

授权出现的异常都会被封装成AccessDeniedException,然后调用AccessDeniedHandler进行异常处理

只需要自定义AuthenticationEntryPoint和AccessDeniedHandler类并注入到spring容器中即可实现自定义异常处理

1
2
3
4
5
6
7
8
9
10
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {

ResponseResult responseResult = new ResponseResult(HttpStatus.UNAUTHORIZED.value(), "用户认证失败");
String jsonString = JSON.toJSONString(responseResult);
WebUtils.renderString(httpServletResponse,jsonString);
}
}
1
2
3
4
5
6
7
8
9
10
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
ResponseResult responseResult = new ResponseResult(HttpStatus.FORBIDDEN.value(), "用户授权失败");
String jsonString = JSON.toJSONString(responseResult);
WebUtils.renderString(httpServletResponse,jsonString);

}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
private AuthenticationEntryPointImpl authenticationEntryPoint;

@Autowired
private AccessDeniedHandlerImpl accessDeniedHandler;

@Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling().accessDeniedHandler(accessDeniedHandler).authenticationEntryPoint(authenticationEntryPoint);
}
}

跨域

springboot配置跨域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {

registry
//设置允许跨域的路径
.addMapping("/**")
//设置允许跨域的请求
.allowedOriginPatterns("*")
//是否允许Cookic
.allowCredentials(true)
//设置允许的请求方式
.allowedMethods("GET","POST","DELETE","PUT")
//设置允许的header属性
.allowedHeaders("*")
//跨域允许时间
.maxAge(3600);

}
}

springsecurity配置跨域

1
2
3
4
5
6
7
8
9
10
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf();
}
}

spriongcloud

Spring Cloud

SpringCloud

Eureka

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--父工程-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR12</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--微服务工程-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
<version>3.1.3</version>
</dependency>
1
2
3
4
5
6
7
@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class,args);
}
}
1
2
3
4
5
6
7
# eureka微服务
eureka:
client:
fetch-registry: false
register-with-eureka: false
service-url:
defaultZone: http://localhost:自定义端口/eureka/
1
2
3
4
5
# 其他需要使用eureka服务的服务
eureka:
client:
service-url:
defaultZone: http://localhost:自定义端口/eureka/·

访问地址:http://localhost:自定义端口

1
2
3
4
5
6
7
8
9
10
11
12
// 需要使用eureka服务的服务
@Configuration
public class BeanConfiguration {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
// http:// + spring.application.servername + /controller请求
// 不需要写ip+端口号了
restTemplate.getForObject("http://user-service/user/", 自定义.class);

LoadBalancer

1
2
3
4
5
6
7
8
9
10

@Configuration
public class LoadBalancerConfig {
@Bean
public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory){
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class),name);
}
}
1
2
3
4
5
6
7
8
9
@Configuration
@LoadBalancerClient(value = "user-service",configuration = LoadBalancerConfig.class)
public class BeanConfiguration {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}

OpenFeign

1
2
3
4
5
6
7
8
@SpringBootApplication
@MapperScan("com.kuang.mapper")
@EnableFeignClients
public class BorrowApplication {
public static void main(String[] args) {
SpringApplication.run(BorrowApplication.class,args);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
@FeignClient("user-service")
public interface UserClient {

@RequestMapping("/user/{uid}")
User getUserByid(@PathVariable("uid") Integer uid);
}
@FeignClient("book-service")
public interface BookServier {
@RequestMapping("/book/{bid}")
Book getBookByid(@PathVariable("bid") Integer bid);
}

1
2
3
4
5
6
7
8
9
@Autowired
BookServier bookServier;
@Autowired
UserClient userClient;

// 原先的调用方式
// User user = restTemplate.getForObject("http://localhost:8300/user/" + uid, User.class);
// 可以改为
User user = userClient.getBookByid(uid);

Hystrix 服务熔断

Hystrix

1
2
3
4
5
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
1
2
3
4
5
6
7
@EnableHystrix
public class BorrowApplication {
public static void main(String[] args) {
SpringApplication.run(BorrowApplication.class,args);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

@RestController
public class BorrowController {
@Autowired
BorrowService borrowService;
@HystrixCommand(fallbackMethod = "onError")
@RequestMapping("/borrow/{uid}")
public BorrowDetail getBorrowDetailByUid(@PathVariable("uid") Integer uid){
return borrowService.getBorrowDetailByUid(uid);
}

BorrowDetail onError(int uid){
return new BorrowDetail(null, Collections.emptyList());
}
}

OpenFeign

1
2
3
feign:
circuitbreaker:
enabled: true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@FeignClient(value = "user-service",fallback = UserFallbackClient.class)
public interface UserClient {

@RequestMapping("/user/{uid}")
User getUserByid(@PathVariable("uid") Integer uid);
}



@Component
public class UserFallbackClient implements UserClient {
@Override
public User getUserByid(Integer uid) {
return new User(0,0,"error","error");
}
}

监控

新建工程

1
2
3
4
5
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
1
2
3
4
5
server:
port: 8888
hystrix:
dashboard:
proxy-stream-allow-list: "localhost"
1
2
3
4
5
6
7
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixApplication.class,args);
}
}

在要监控的工程中

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
1
2
3
4
5
management:
endpoints:
web:
exposure:
include: '*'

Hystrix Dashboard访问地址 http://localhost:port/hystrix

监控地址 http://localhost:服务端口/actuator/hystrix.stream

Gateway

1
2
3
4
5
6
7
8
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
1
2
3
4
5
6
7
8
9
10
server:
port: 9999

eureka:
client:
service-url:
defaultZone: http://localhost:8400/eureka/
spring:
application:
name: gateway

路由过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Component
public class MyFilter implements GlobalFilter.Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
// 判断是否包含某个参数
List<String> value = request.getQueryParams().get("key");
// 进行一些逻辑的判断
if (value!=null && value.contains("value")){
return chain.filter(exchange);
}else {
return exchange.getResponse().setComplete();
}

}
@Override
public int getOreder(){
return 0;
}
}

Config

1
2
3
4
5
6
7
8
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
eureka:
client:
service-url:
defaultZone: http://localhost:8400/eureka/
server:
port: 1010
spring:
application:
name: config-service
cloud:
config:
server:
git:
uri: # git地址
default-label: main
1
2
3
4
5
6
7
@EnableConfigServer
@SpringBootApplication
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class,args);
}
}

http://ip:port/{git分支}/{服务名称}-{环境}.yml

http://ip:port/{服务名称}/{环境}/{git分支}

bootstrap.yaml

1
2
3
4
5
6
7
8
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
1
2
3
4
5
6
7
spring:
cloud:
config:
name: #远程想要获取的文件名称
uri: #配置服务器 http://ip:port
profile: #环境
label: #分支

SpringCloud Alibaba

nacos

dependencyManagement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.0.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>

子工程

1
2
3
4
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
1
2
3
4
5
6
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848

1
2
3
4
5
6
7
8
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
1
2
3
4
5
6
@FeignClient("book-service")
public interface BookClient {

@RequestMapping("/book/{bid}")
Book getBookByid(@PathVariable("bid") Integer bid);
}
1
2
3
4
5
@FeignClient("user-service")
public interface UserClient {
@RequestMapping("/user/{uid}")
User getUserByid(@PathVariable("uid") Integer uid);
}

常驻服务

1
2
3
4
5
spring:
cloud:
nacos:
discovery:
ephemeral: false #改为常驻服务

集群分区

1
2
3
4
5
spring:
cloud:
nacos:
discovery:
cluster-name: name #改为常驻服务

LoadBalance

1
2
3
4
5
spring:
cloud:
loadbalancer:
nacos:
enable: true

Config

1
2
3
4
5
6
7
8
9
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
<version>3.1.3</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

bootstrap.yaml

1
2
3
4
5
6
7
8
9
10
spring:
application:
name: user-service #与云端配置文件保持一致
profiles:
active: dev #与云端配置文件保持一致
cloud:
nacos:
config:
file-extension: yaml
server-addr: localhost:8848

@RefreshScope配置文件热更新

Sentinel

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</exclusion>
</exclusions>
</dependency>
1
2
3
4
5
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8858

流量控制

@SentinelResource(value=自定义名称)

1
2
3
4
spring:
cloud:
sentinel:
web-context-unify: false
1
2
3
4
5
6
7
8
@RequestMapping("/blocked")
JSONObject blocked(){
JSONObject object = new JSONObject();
object.put("code",403);
object.put("success",false);
object.put("message","你的请求过快,请稍后再试!");
return object;
}
1
2
3
4
spring:
cloud:
sentinel:
block-page: /blocked
1
2
3
4
5
6
7
8
9
@SentinelResource(value=自定义名称,blockHandle="blocked")
//在需要限流的方法前添加注解
public BorrowDetail getBorrowDetailByUid(Integer uid) {
return new BorrowDetail(user,books);
}
//替代方案,返回值要保持一致,参数还要加一个BlockException
public BorrowDetail blocked(Integer uid,lockException e) {
return new BorrowDetail(null,Collections.emptyList());
}
1
2
3
feign:
sentinel:
enable: true
1
2
3
4
5
6
@FeignClient(value = "user-service",fallback = UserClientImpl.class)
public interface UserClient {
@RequestMapping("/user/{uid}")
User getUserByid(@PathVariable("uid") Integer uid);
}

1
2
3
4
5
6
7
8
9
@Component
public class UserClientImpl implements UserClient {
@Override
public User getUserByid(Integer uid) {
User user = new User();
user.setName("我是替代方案");
return user;
}
}

Seata

1
2
3
4
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
1
2
3
4
5
6
7
8
seata:
service:
vgroup-mapping:
# 这里需要对事务组做映射,默认的分组名称为 应用名称-seata-service-group,将其映射到default集群
# 很关键,否则会找不到服务
book-service-seata-service-group: default
grouplist:
default: localhost:8868

每个服务的启动类上添加@EnableAutoDataSourceProxy

在需要支持事务的方法上添加@GlobalTransactional

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
create table `undo_log`
(
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`branch_id` BIGINT(20) NOT NULL,
`xid` VARCHAR(100) NOT NULL,
`context` VARCHAR(128) NOT NULL,
`rollback_info` LONGBLOB NOT NULL,
`log_status` INT(11) NOT NULL,
`log_created` DATETIME NOT NULL,
`log_modified` DATETIME NOT NULL,
`ext` VARCHAR(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
)ENGINE = InnoDB
AUTO_INCREMENT =1
DEFAULT CHARSET utf8;

中间件

OAuth2

1
2
3
4
5
6
7
8
9
10
11
12
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
1
2
3
4
5
spring:
session:
store-type: redis
redis:
host: 180.76.100.158

验证服务器

1
2
3
4
5
6
7
8
9
10
11
12
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
1
2
3
4
server:
port: 8500
servlet:
context-path: /sso
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
package com.kuang.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@SneakyThrows
@Bean
@Override
public UserDetailsService userDetailsService(){
return super.userDetailsServiceBean();
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
auth
.inMemoryAuthentication()
.passwordEncoder(encoder)
.withUser("test").password(encoder.encode("123456")).roles("USER");
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().permitAll();

}
}
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
package com.kuang.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;

import javax.annotation.Resource;

@EnableAuthorizationServer
@Configuration
public class OAuth2Configuration extends AuthorizationServerConfigurerAdapter {

@Resource
private AuthenticationManager manager;

@Resource
UserDetailsService userDetailsService;

private final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();

@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
.passwordEncoder(encoder)
.allowFormAuthenticationForClients()
.checkTokenAccess("permitAll()");
}

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient("web")
.secret(encoder.encode("654321"))
.autoApprove(false)
.scopes("book","user","borrow")
.redirectUris("http://localhost:8100/login")
.authorizedGrantTypes("client_credentials","password","implicit","authorization_code","refresh_token");
}

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.userDetailsService(userDetailsService)
.authenticationManager(manager);
}
}

基于@EnableOAthu2Sso实现
1
2
3
4
5
6
7
8
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
security:
oauth2:
client:
client-id: web
client-secret: 654321
#Token获取地址
access-token-uri: http://localhost:8500/sso/oauth/token
#验证页面地址
user-authorization-uri: http://localhost:8500/sso/oauth/authorize
resource:
#Token信息获取和校验地址
token-info-uri: http://localhost:8500/sso/oauth/check_token
基于@EnableResourceServer实现
1
2
3
4
5
6
7
8
security:
oauth2:
client:
client-id: web
client-secret: 654321
resource:
#Token信息获取和校验地址
token-info-uri: http://localhost:8500/sso/oauth/check_token
1
2
3
4
5
6
7
8
9
@Configuration
public class ResourceConfiguration extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().access("#oauth2.hasScope('book')");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13

@Configuration
public class WebConfiguration {

@Resource
OAuth2ClientContext context;

@Bean
public OAuth2RestTemplate restTemplate(){
return new OAuth2RestTemplate(new ClientCredentialsResourceDetails(),context);
}

}
OpenFeign 整合 OAuth2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Configuration
public class OAuth2FeignRequestInterceptor implements RequestInterceptor {
private static final String AUTHORIZATION_HEADER = "Authorization";

private static final String BEARER_TOKEN_TYPE = "Bearer";

private final OAuth2RestTemplate oAuth2RestTemplate;

public OAuth2FeignRequestInterceptor(OAuth2RestTemplate oAuth2RestTemplate) {
this.oAuth2RestTemplate = oAuth2RestTemplate;
}

@Override
public void apply(RequestTemplate requestTemplate) {
requestTemplate.header(AUTHORIZATION_HEADER,
String.format("%s %s",
BEARER_TOKEN_TYPE,
oAuth2RestTemplate.getAccessToken().toString()));
}

}
JWT

验证服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
//追加注入两个类
@Bean
public JwtAccessTokenConverter tokenConverter(){
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("kuang");
return converter;
}
@Bean
public TokenStore tokenStore(JwtAccessTokenConverter converter){
return new JwtTokenStore(converter);
}
}
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
@EnableAuthorizationServer
@Configuration
public class OAuth2Configuration extends AuthorizationServerConfigurerAdapter {

@Resource
private AuthenticationManager manager;

@Resource
UserDetailsService userDetailsService;

@Resource
TokenStore store;

@Resource
JwtAccessTokenConverter converter;

private AuthorizationServerTokenServices serverTokenServices(){
DefaultTokenServices services = new DefaultTokenServices();
services.setSupportRefreshToken(true);
services.setTokenStore(store);
services.setTokenEnhancer(converter);
return services;
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.tokenServices(serverTokenServices())
.userDetailsService(userDetailsService)
.authenticationManager(manager);
}
}

资源服务器

1
2
3
4
5
security:
oauth2:
resource:
jwt:
key-value: kuang

Redis

主从复制

老版本slaveof ip port

新版本replicaof ip port

解除slaveof no one或者replicaof no one

哨兵模式

创建一个redis服务

配置文件只保留sentinel monitor 名称 ip port 1

启动时添加参数redis-servier --sentinel

ShardingJDVBC

分库
1
2
3
4
5
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>5.1.0</version>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
spring:
shardingsphere:
datasource:
names: db0,db1
db0:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://180.76.100.158:3306/shardingstudy
username: root
password: Kuang001102
db1:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://180.76.100.158:3305/shardingstudy
username: root
password: Kuang001102
mybatis:
mapper-locations: classpath:mapper/*.xml
1
2
3
4
5
6
7
8
@Data
@AllArgsConstructor
@NonDeterministic
public class User {
int id;
String name;
String password;
}
1
2
3
4
5
6
public interface UserMapper {

User getUserById(int id);

int addUser(User user);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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.kuang.mapper.UserMapper">

<select id="getUserById" resultType="com.kuang.entity.User">
select * from user where id = #{id}
</select>

<insert id="addUser" parameterType="com.kuang.entity.User">
insert into user(id,name,password) values (#{id},#{name},#{password})
</insert>
</mapper>
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
spring:
shardingsphere:
datasource:
names: db0,db1
db0:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://180.76.100.158:3306/shardingstudy
username: root
password: Kuang001102
db1:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://180.76.100.158:3305/shardingstudy
username: root
password: Kuang001102
rules:
sharding:
tables:
#这里填写表的名称
test:
#填写实际的路由节点
#简写 db$->{0..1].test
actual-data-nodes: db0.user,db1.user
#策略
database-strategy:
#使用标准配置
standard:
#参与分片运算的字段
sharding-column: id
#自定义算法名称
sharding-algorithm-name: my-alg
sharding-algorithms:
#自定义算法名称
my-alg:
#算法名称,官方提供了很多种
type: MOD
props:
sharding-count: 2
props:
#开启日志,方便观察
sql-show: 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
42
43
spring:
shardingsphere:
datasource:
names: db0,db1
db0:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://180.76.100.158:3306/shardingstudy
username: root
password: Kuang001102
db1:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://180.76.100.158:3305/shardingstudy
username: root
password: Kuang001102
rules:
sharding:
tables:
#这里填写表的名称,逻辑表
user:
#填写实际的路由节点
#简写 db$->{0..1].test
actual-data-nodes: db0.user,db1.user
#策略
table-strategy:
#使用标准配置
standard:
#参与分片运算的字段
sharding-column: id
#自定义算法名称
sharding-algorithm-name: my-alg
sharding-algorithms:
#自定义算法名称
my-alg:
#算法名称,官方提供了很多种
type: INLINE
props:
algorithm-expression: test_$->{id % 2}
allow-range-query-with-inline-sharding: false
props:
#开启日志,方便观察
sql-show: 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
42
43
44
45
46
47
48
49
50
51
spring:
shardingsphere:
datasource:
names: db0,db1
db0:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://180.76.100.158:3306/shardingstudy
username: root
password: Kuang001102
db1:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://180.76.100.158:3305/shardingstudy
username: root
password: Kuang001102
rules:
sharding:
tables:
#这里填写表的名称,逻辑表
user:
#填写实际的路由节点
#简写 db$->{0..1].test
actual-data-nodes: db0.user,db1.user
#策略
database-strategy:
#使用标准配置
standard:
#参与分片运算的字段
sharding-column: id
#自定义算法名称
sharding-algorithm-name: my-alg
#主键生成策略
key-generate-strategy:
column: id
key-generate-name: my-gen
key-generators:
my-gen:
type: SNOWFLAKE
props:
worder-id: 666
sharding-algorithms:
#自定义算法名称
my-alg:
#算法名称,官方提供了很多种
type: MOD
props:
sharding-count: 2
props:
#开启日志,方便观察
sql-show: true

主键类型为long,int长度不够

读写分离

前提是配置了主从模式,从机并且配置了只读模式

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
spring:
shardingsphere:
datasource:
names: db0,db1
db0:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://180.76.100.158:3306/shardingstudy
username: root
password: Kuang001102
db1:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://180.76.100.158:3305/shardingstudy
username: root
password: Kuang001102
rules:
readwrite-splitting:
data-sources:
#自定义名称
user-db:
#使用静态类型,动态Dynamic类型可以自动发现auto-aware-data-source-name
type: Static
props:
#写库只能配置一个
write-data-source-name: db0
#读库可以配置多个
read-data-source-names: db1
#负载均衡策略
load-balancer-name: my-load
load-balancers:
my-load:
type: ROUND_ROBIN
sharding:
tables:
#这里填写表的名称,逻辑表
user:
#填写实际的路由节点
#简写 db$->{0..1].test
actual-data-nodes: db0.user,db1.user
#策略
database-strategy:
#使用标准配置
standard:
#参与分片运算的字段
sharding-column: id
#自定义算法名称
sharding-algorithm-name: my-alg
#主键生成策略
key-generate-strategy:
column: id
key-generate-name: my-gen
key-generators:
my-gen:
type: SNOWFLAKE
props:
worder-id: 666
sharding-algorithms:
#自定义算法名称
my-alg:
#算法名称,官方提供了很多种
type: MOD
props:
sharding-count: 2
props:
#开启日志,方便观察
sql-show: true

RabbitMQ

docker安装rabbitmq

1
2
3
4
5
6
7
docker pull rabbitmq:management
docker run -d -p 5672:5672 -p 15672:15672 --name rabbitmq --restart=always --hostname myRabbit rabbitmq:management
docker exec -it rabbitmq
rabbitmq-plugins enable rabbitmq_management
rabbitmqctl add_user admin Kuang001102
rabbitmqctl set_user_tags admin administrator
rabbitmqctl set_permissions -p "/" username ".*" ".*" ".*"
1
2
3
4
5
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.14.2</version>
</dependency>

基本步骤

创建连接工厂
1
2
3
4
5
6
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("180.76.100.158");
factory.setPort(5672);
factory.setUsername("admin");
factory.setPassword("Kuang001102");
factory.setVirtualHost("/");
创建连接
1
2
//创建连接
connection = factory.newConnection("生产者");
创建连接通道
1
2
//通过连接获取通道
channel = connection.createChannel();
创建交换机
1
2
3
4
5
6
7
8
9
10
11
//准备交换机
String exchangeName = "fanout-exchange";
String type = "fanout";
/*
@params1 exchange:交换机名称
@params2 type:官网指定的交换机类型,不能自定义
@params3 持久化:是否持久化
@params4 自动删除:是否自动删除
@params5 额外参数
*/
channel.exchangeDeclare(exchangeName, type, false, false, null);
创建消息队列
1
2
3
4
5
6
7
8
9
/*
@params1 队列的名称
@params2 是否要持久化 durable=false
@params3 排他性
@params4 是否自动删除
@params5 携带一些附加参数
*/
String queueName = "queue1";
channel.queueDeclare(queueName,false,false,false,null);
将交换机和消息队列绑定
1
2
3
String routeKey = "";
//将交换器与队列通过路由键绑定
channel.queueBind(queueName, exchangeName, routeKey);
发送消息
1
2
3
4
5
6
7
8
9
10
11
12
//准备消息内容
String message = "hello world";
//发送消息给队列
//发送消息给队列
/*
@params1 exchange:指定交换机,参数为空则为默认交换机
@params2 消息队列
@params3 路由key
@params4 BasicProperties props
@params5 字节数组类型的消息
*/
channel.basicPublish(exchangeName,queueName,routeKey,null,message.getBytes());
关闭连接
1
2
channel.close();
connection.close();

简单模式

生产者

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
public class Producer {
public static void main(String[] args) {
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("180.76.100.158");
factory.setPort(5672);
factory.setUsername("admin");
factory.setPassword("Kuang001102");
factory.setVirtualHost("/");
Connection connection =null;
Channel channel = null;
//创建连接
try {
connection = factory.newConnection("生产者");
//通过连接获取通道
channel = connection.createChannel();
//通过通道创建交换机,声明队列,绑定关系,路由key,发送消息和接收消息
/*
@params1 队列的名称
@params2 是否要持久化 durable=false
@params3 排他性
@params4 是否自动删除
@params5 携带一些附加参数
*/
String queueName = "queue1";
channel.queueDeclare(queueName,false,false,false,null);
//准备消息内容
String message = "hello world";
//发送消息给队列
channel.basicPublish("",queueName,null,message.getBytes());
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭通道
if (channel != null && channel.isOpen()){
try {
channel.close();
} catch (Exception e) {
e.printStackTrace();
}
}
//关闭连接
if (connection != null && connection.isOpen()){
try {
connection.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}


}
}

消费者

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
public class Consumer {
public static void main(String[] args) {
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("180.76.100.158");
factory.setPort(5672);
factory.setUsername("admin");
factory.setPassword("Kuang001102");
factory.setVirtualHost("/");
Connection connection =null;
Channel channel = null;
//创建连接
try {
connection = factory.newConnection("生产者");
//通过连接获取通道
channel = connection.createChannel();
//获取信息
channel.basicConsume("queue1", true, new DeliverCallback() {
@Override
public void handle(java.lang.String s, Delivery delivery) throws IOException {
System.out.println(delivery.getBody());
}
}, new CancelCallback() {
@Override
public void handle(java.lang.String s) throws IOException {
System.out.println("接收失败");
}
});
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭通道
if (channel != null && channel.isOpen()){
try {
channel.close();
} catch (Exception e) {
e.printStackTrace();
}
}
//关闭连接
if (connection != null && connection.isOpen()){
try {
connection.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}

Fanout模式

自定义交换机

自定义消息队列

交换机绑定消息队列

生产者
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
package com.kuang.routing;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class Producer {
public static void main(String[] args) {
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("180.76.100.158");
factory.setPort(5672);
factory.setUsername("admin");
factory.setPassword("Kuang001102");
factory.setVirtualHost("/");
Connection connection =null;
Channel channel = null;
try {
//创建连接
connection = factory.newConnection("生产者");
//通过连接获取通道
channel = connection.createChannel();
//准备消息内容
String message = "hello world";
//准备交换机
//确保rabbitmq中存在此交换机
String exchangeName = "fanout-exchange";
String type = "fanout";
//定义路由key
String routeKey = "";
//发送消息给队列
channel.basicPublish(exchangeName,routeKey,null,message.getBytes());
System.out.println("发送消息成功");
} catch (Exception e) {
e.printStackTrace();
System.out.println("发送消息失败");
} finally {
//关闭通道
if (channel != null && channel.isOpen()){
try {
channel.close();
} catch (Exception e) {
e.printStackTrace();
}
}
//关闭连接
if (connection != null && connection.isOpen()){
try {
connection.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}


}
}

消费者
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
package com.kuang.routing;

import com.rabbitmq.client.*;


import java.io.IOException;

public class Consumer {
public static Runnable runnable = new Runnable() {
@Override
public void run() {
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("180.76.100.158");
factory.setPort(5672);
factory.setUsername("admin");
factory.setPassword("Kuang001102");
factory.setVirtualHost("/");
//获取队列名称
final java.lang.String queueName = Thread.currentThread().getName();
Connection connection =null;
Channel channel = null;
//创建连接
try {
connection = factory.newConnection("生产者");
//通过连接获取通道
channel = connection.createChannel();

//获取信息
channel.basicConsume(queueName, true, new DeliverCallback() {
@Override
public void handle(java.lang.String s, Delivery delivery) throws IOException {
System.out.println(queueName+"=======>"+new String(delivery.getBody(),"UTF-8"));
System.in.read();
}
}, new CancelCallback() {
@Override
public void handle(java.lang.String s) throws IOException {
System.out.println("接收失败");
}
});
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭通道
if (channel != null && channel.isOpen()){
try {
channel.close();
} catch (Exception e) {
e.printStackTrace();
}
}
//关闭连接
if (connection != null && connection.isOpen()){
try {
connection.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
};


public static void main(String[] args) {
new Thread(runnable,"queue1").start();
new Thread(runnable,"queue2").start();
new Thread(runnable,"queue3").start();
}
}

direct模式

1
2
3
4
5
6
7
//准备交换机
//确保rabbitmq中存在此交换机
String exchangeName = "direct-exchange";
//定义路由key
String routeKey = "wechat";
//发送消息给队列
channel.basicPublish(exchangeName,routeKey,null,message.getBytes());

topic模式

1
2
3
4
5
6
7
8
9
// # : 代表匹配一级或者多级
// * : 至少匹配一级
//准备交换机
//确保rabbitmq中存在此交换机
String exchangeName = "topic-exchange";
//定义路由key
String routeKey = "com.email.wechat";
//发送消息给队列
channel.basicPublish(exchangeName,routeKey,null,message.getBytes());

Springboot整合rabbitmq

1
2
3
4
5
6
7
8
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit-test</artifactId>
</dependency>
1
2
3
4
5
6
7
8
9
10
server:
port: 8080

spring:
rabbitmq:
username: admin
password: Kuang001102
virtual-host: /
host: 180.76.100.158:22
port: 5672
生产者
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
@Service
public class OrderService {
@Resource
private RabbitTemplate rabbitTemplate;
/**
* @description: 模拟用户下单
* @param userId:
* @param productId:
* @param num:
* @return void
* @author: Kuang
* @date: 2022/7/24 15:58
*/


public void makeOrder(String userId,String productId,int num){
// 1 :根据商品id查询库存是否充足
// 2 :保存订单
String orderId = UUID.randomUUID().toString();
System.out.println("订单生成成功");
// 3 : 通过mq来完成消息的分发
// 参数1:交换机 参数2:路由key/queue队列名称 参数3:消息内容
String exchangeName = "fanout_order_exchange";
String routingKey = "";
rabbitTemplate.convertAndSend(exchangeName,routingKey,orderId);
}
}
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
@Configuration
public class RabbitMQConfiguration {
// 1:声明注册fanout模式的交换机
@Bean
public FanoutExchange fanoutExchange(){
return new FanoutExchange("fanout_order_exchange",true,false);
}
// 2:声明队列 sms.fanout.queue email.fanout.queue wechat.fanout.queue
@Bean
public Queue smsQueue(){
return new Queue("sms.fanout.queue",true);
}
@Bean
public Queue emailQueue(){
return new Queue("email.fanout.queue",true);
}
@Bean
public Queue wechatQueue(){
return new Queue("wechat.fanout.queue",true);
}
// 3:完成绑定关系(队列和交换机完成绑定关系)
@Bean
public Binding smsBingding(){
return BindingBuilder.bind(smsQueue()).to(fanoutExchange());
}
@Bean
public Binding emailBingding(){
return BindingBuilder.bind(emailQueue()).to(fanoutExchange());
}
@Bean
public Binding wechatBingding(){
return BindingBuilder.bind(wechatQueue()).to(fanoutExchange());
}
}

1
orderService.makeOrder("1","1",12);
消费者
1
2
3
4
5
6
7
8
@RabbitListener(queues = {"email.fanout.queue"})
@Service
public class EmailConsumer {
@RabbitHandler
public void receiveMessage(String message){
System.out.println("email========>"+message);
}
}

elasticsearch

9300 :集群内部通信

9200:http访问端口

老版本

索引操作

创建索引

put形式发送http://127.0.0.1:9200/索引名称

查看索引

get形式发送http://127.0.0.1:9200/索引名称

查看所有索引http://127.0.0.1:9200/_cat/indices?v

删除索引

delete形式发送http://127.0.0.1:9200/索引名称

文档操作

创建文档

  • post形式发送http://127.0.0.1:9200/索引名称/_doc/自定义id

  • 请求体中包含json数据
    {
        内容
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18

    #### 获取文档数据

    以`get`形式发送`http://127.0.0.1:9200/索引名称/_doc/自定义id`

    以`get`形式发送`http://127.0.0.1:9200/索引名称/_search`获取所有

    #### 修改文档

    ##### 全量修改

    - 以`put`形式发送`http://127.0.0.1:9200/索引名称/_doc/自定义id`

    - ```json
    请求体中包含json数据
    {
    内容
    }
局部修改
  • post形式发送http://127.0.0.1:9200/索引名称/_update/自定义id

  • 请求体中包含json数据
    {
        "doc":{
            需要修改的内容
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22

    #### 删除文档

    以`delete`形式发送`http://127.0.0.1:9200/索引名称/_doc/自定义id`

    ### 查询

    #### 条件查询

    1. 以`get`形式发送`http://127.0.0.1:9200/索引名称/_search?q=Json键值对`

    2. - 以`get`形式发送`http://127.0.0.1:9200/索引名称/_search`

    - ```json
    请求体中包含json数据
    {
    "query":{
    "match":{
    键值对(这时已经被进行分词操作了)
    }
    }
    }
    - ```json 请求体中包含json数据 { "query":{ "match_all":{ } } }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    3. - 以`get`形式发送`http://127.0.0.1:9200/索引名称/_search`

    - ```json
    请求体中包含json数据
    {
    "query":{
    "match":{
    键值对
    }
    },
    "from":开始索引,
    "size":查询多少数据,
    "source":[所需要的字段],
    "sort":{
    根据哪个字段排序:{
    "order":排序方式 (asc,desc)
    }
    }
    }

多条件查询

  • get形式发送http://127.0.0.1:9200/索引名称/_search

  • 请求体中包含json数据
    {
        "query":{
            "bool":{
                "must":[(must表示必须,should表示或者)
                    {
                        "match":{
                            需要匹配的键值对
                        }
                    },
                    {
                        "match":{
                            需要匹配的键值对
                        }
                    },
                    }
                ],
                "filter":{
                    "range":{
                        需要查询范围的字段:{
                            "gt":数值
                        }
                    }
                }
            }
        },
        "highlight":{
            "fields":{
                字段:{}
    
            
            }
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

    #### 聚合查询

    - 以`get`形式发送`http://127.0.0.1:9200/索引名称/_search`

    - ```json
    {
    "aggs":{
    分组名称:{
    "terms":{
    "field":分组字段
    }
    },
    平均值名称:{
    "terms":{
    "field":分组字段
    }
    }

    }
    }

elasticsearch

安装es

  1. 下载地址:Elasticsearch:官方分布式搜索和分析引擎 | Elastic
  2. 开启跨域支持
1
2
http.cors.enabled: true
http.cors.allow-origin: "*"

安装可视化插件

  1. 下载地址: elasticsearch-head
  2. 解压进入文件夹执行cnpm install
  3. 执行npm run start

安装Kibana

  1. 下载地址 Kibana:数据的探索、可视化和分析 | Elastic

  2. 配置文件汉化i18n.locale: "zh-CN"

  3. 使用

下载ik分词器

  1. 下载地址 Release v7.6.1 · medcl/elasticsearch-analysis-ik (github.com)

  2. 解压至elasticsearch的plugins中

  3. 配置ik分词器

    创建自定义.dic文件

    IKAnalyzer.yml文件配置自定义字典

Rest风格操作

put /索引名/~类型~/id

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
put /test1/type1/1
{
"name":"kuang",
"age":21
}
put /test2
{
"mapping":{
"properties":{
"name":{
"type":"test"
},
"age":{
"type":"long"
},
"birthday":{
"type":"date"
}
}
}
}

get /索引名[~类型~][id]

post /索引名/~类型~/id/_update

springboot

1
2
3
4
5
6
7
<!-- https://mvnrepository.com/artifact/org.elasticsearch/elasticsearch -->
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>7.6.1</version>
</dependency>

1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
public class ElasticSearchConfig {
@Bean
public RestHighLevelClient restHighLevelClient(){
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("localhost",9200,"http")
)
);
return client;

}
}
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
@Autowired
@Qualifier("restHighLevelClient")
private RestHighLevelClient client;
@Test
void testCreateIndex() throws IOException {
CreateIndexRequest request = new CreateIndexRequest("kuang_index");
CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);
System.out.println(response);
}
@Test
void testAddDocument() throws IOException {
User user = new User("zhangsan",18);
// 创建请求
IndexRequest request = new IndexRequest("kuang_index");
// 设置规则put /kuang_index/_doc/1
request.id("1");
// 设置超时时间
request.timeout("1s");
// 将数据放入请求
IndexRequest source = request.source(JSON.toJSONString(user), XContentType.JSON);
// 客户端发送请求
IndexResponse response = client.index(request, RequestOptions.DEFAULT);


}
@Test
void testGetDocument() throws IOException {
// 创建请求 get /kuang_index/_doc/1
GetRequest request = new GetRequest("kuang_index","1");
// 只获取_source的内容
request.fetchSourceContext(new FetchSourceContext(false));
request.storedFields("_none");
// 判断文档是否存在
boolean ExistResult = client.exists(request, RequestOptions.DEFAULT);
// 获取文档内容
GetResponse response = client.get(request, RequestOptions.DEFAULT);

}


nginx

安装nginx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#安装编译工具
yum -y install make zlib zlib-devel gcc-c++ libtool openssl openssl-devel
#安装PCRE
# http://downloads.sourceforge.net/project/pcre/pcre/8.35/pcre-8.35.tar.gz
#或者
cd /usr/local/src/
wget http://downloads.sourceforge.net/project/pcre/pcre/8.35/pcre-8.35.tar.gz
tar zxvf pcre-8.35.tar.gz
cd pcre-8.35
./configure
make && make install
pcre-config --version
#安装nginx
#https://nginx.org/en/download.html
#或者
cd /usr/local/src/
wget http://nginx.org/download/nginx-1.6.2.tar.gz
tar zxvf nginx-1.6.2.tar.gz
cd nginx-1.6.2
./configure --prefix=/usr/local/webserver/nginx --with-http_stub_status_module --with-http_ssl_module --with-pcre=/usr/local/src/pcre-8.35
make
make install
/usr/local/webserver/nginx/sbin/nginx -v
#安装成功后,nginx文件夹/usr/local/nginx

常用命令

1
2
3
4
5
6
7
8
#使用nginx命令必须提前进入nginx目录中/usr/local/nginx/sbin
cd /usr/local/nginx/sbin
#启用nginx
./nginx
#关闭nginx
./nginx -s stop
#重新加载nginx
./nginx -s reload

配置文件

1
#文件位置/usr/local/nginx/conf/nginx.conf

全局块

1
2
 #处理并发数量
worker_processes 1;

events块

1
2
#支持最大连接数
worker_connections 1024;

http块

  • http全局块
  • http server块

反向代理

1
2
3
4
5
6
7
8
9
10
11
server{
listen 端口号;
server_name ip地址;
location 请求表达式(正则表达式){
proxy_pass 跳转ip:端口号;
}
location 请求表达式(正则表达式){
proxy_pass 跳转ip:端口号;
}
多个location
}

负载均衡

轮询

1
2
3
4
5
6
7
8
9
10
11
upstream 负载均衡服务名称{
server ip:port [weight=权重系数];
server ip:port [weight=权重系数];
}
server{
listen port;
server_name ip;
local 表达式{
proxy_pass http://负载均衡服务名称
}
}

权重

1
2
3
4
5
6
7
8
9
10
11
upstream 负载均衡服务名称{
server ip:port [weight=权重系数];
server ip:port [weight=权重系数];
}
server{
listen port;
server_name ip;
local 表达式{
proxy_pass http://负载均衡服务名称
}
}

ip_hash,每个ip只访问一台服务器

1
2
3
4
5
6
7
8
9
10
11
12
upstream 负载均衡服务名称{
ip_hash;
server ip:port;
server ip:port;
}
server{
listen port;
server_name ip;
local 表达式{
proxy_pass http://负载均衡服务名称
}
}

fair,根据相应时间来分配

1
2
3
4
5
6
7
8
9
10
11
12
upstream 负载均衡服务名称{
server ip:port [weight=权重系数];
server ip:port [weight=权重系数];
fair;
}
server{
listen port;
server_name ip;
local 表达式{
proxy_pass http://负载均衡服务名称
}
}

动静分离

1
2
3
4
5
6
7
8
server{
listen port;
server_name ip;
local /image{
root /data;#当访问/image时,linux会找系统中/data/image文件夹中的内容
autoindex on;#列出文件夹中的文件
}
}

高可用集群

keepalived

1
2
3
4
yum install keepalived -y
#安装完成后的路径为/etc/keepalived/keepalived.conf
systemctl start keepalived.server
#开启keepalived
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
#全局配置
global_defs {
notification_email {
acassen@firewall.loc
failover@firewall.loc
sysadmin@firewall.loc
}
notification_email_from Alexandre.Cassen@firewall.loc
smtp_server 192.168.17.129 #主机ip
smtp_connect_timeout 30
router_id 服务器主机ip #=127.0.0.1=
}

#脚本配置
vrrp_script chk_http_port {
script "/usr/local/src/nginx_check.sh"#脚本路径
interval 2 #(检测脚本执行的间隔);每隔2s检查一次
weight 2 #设置服务器权重
}

#虚拟ip的配置
vrrp_instance VI_1 {
state BACKUP # Master为Master;Salve为BACKUP
interface ens33 #网卡;可以用ip addr 看网卡名
virtual_router_id 51 # 主、备机的 virtual_router_id 必须相同
priority 90 # 主、备机取不同的优先级,主机值较大,备份机值较小
advert_int 1 #每1s发送一次心跳
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
192.168.17.50 // VRRP H 虚拟ip地址,多台keepalived绑定一个ip;必须同一网段
}
}
1
2
3
4
5
6
7
8
9
10
11
#在/usr/local/src 添加检测脚本
#!/bin/bash
A=`ps -C nginx –no-header |wc -l`
if [ $A -eq 0 ];then
/usr/local/nginx/sbin/nginx #nginx启动路径
sleep 2
if [ `ps -C nginx --no-header |wc -l` -eq 0 ];then
killall keepalived #nginx挂掉后,杀掉这个服务器的所有keepalived进程
fi
fi

springboot-03

自动注入

  1. yaml方式

    在类上加Component、ConfigurationProperties(perfix=yaml中的前缀)

  2. properties方式

    • 在类上加Component、PropertySource(value="classpath:properites位置")

    • 在相应字段上加@Value("${properties中配置的值}")

多环境配置

spring.profiles.active=dev选择生效配置

application-dev.propertiesapplication-test.properties

静态资源

Thymeleaf

1
2
3
4
5
6
7
8
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
1
2
3
4
5
6
7
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"

<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
  • 普通文本 th:文本元素="${变量}"或者[[ ${变量} ]]
  • 超链接 th:链接元素="@{变量}"
  • 遍历元素 th:each="item:${items}" th:text="${item}"
  • 国际化消息 th:文本元素="#{变量}"
  • ``逻辑判断 th:text=”${表达式 ? 结果}”`
  • 三元运算 th:text="${表达式?'选项1':'选项2'}"

自定义SpringMVC

  • 在自定义类上添加@Configuration注解
  • 实现WebMvcConfigurer接口
  • 写相关的类并注入到springboot中

springboot将类注入方法

@Bean

public 要注入的类 className(){

​ return new 要注入的类;

}

Mybatis

  • 在主类上添加@MapperScan("com.kuang.mapper")
  • 在mapper接口添加@Mapper
1
2
3
4
5
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
1
2
3
4
5
6
7
<?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">
<!--namespace属性:用于指定当前的映射文件和哪个接口文件进行映射,需要指定接口的文件路径,需要标注包的完整路径接口-->
<mapper namespace="com.kuang.mapper.UserMapper">
</mapper>
1
2
3
4
5
6
7
8
9
10
11
12
13
@Mapper
@Repository
public interface UserMapper {
List<User> queryUserList();

User queryUserById(Integer id);

void addUser(User user);

void updateUser(User user);

void deleteUser(Integer id);
}
1
2
3
4
5
6
7
8
9
10
11
@Controller
public class UserController {
@Autowired
private UserMapper userMapper;
@ResponseBody
@RequestMapping("/user/queryAll")
public List<User> queryUserList(){
return userMapper.queryUserList();
}
}

1
2
3
4
5
6
7
8
9
10
11
12
# 数据库驱动:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据源名称
spring.datasource.name=defaultDataSource
# 数据库连接地址
spring.datasource.url=jdbc:mysql://localhost:3306/springbootstudy?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
# 数据库用户名&密码:
spring.datasource.username=root
spring.datasource.password=123456
#mybatis
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
#mybatis.type-aliases-package=com.kuang.pojo

SpringSecurity

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</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
//授权
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
//首页所有人可以访问,功能页面只有对应的人可以访问
http.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3");
//没有权限跳转登陆页面,会发送/login请求
http.formLogin().loginPage("/toLogin");
//注销
http.logout().
deleteCookies("remove").invalidateHttpSession(true)
.logoutSuccessUrl("/");
//记住我
http.rememberMe();

}
}

认证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

//认证,Springboot 2.1.X 可以直接使用
//密码编码:PsswordEncoder
//在Spring Security 5.0+ 新增了很多加密方法
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("zhangsan").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1")
.and()
.withUser("lisi").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2");
}
}

整合Thymeleaf

1
2
3
4
5
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>
1
2
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
</html>

Shiro

快速开始

Apache Shiro Tutorial | Apache Shiro

pom.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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>

<!-- This plugin is only to test run our little application. It is not
needed in most Shiro-enabled applications: -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.1</version>
<executions>
<execution>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<classpathScope>test</classpathScope>
<mainClass>Tutorial</mainClass>
</configuration>
</plugin>
</plugins>
</build>

<dependencies>
<!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>

<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.9.0</version>
</dependency>
<!-- Shiro uses SLF4J for logging. We'll use the 'simple' binding
in this example app. See http://www.slf4j.org for more info. -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.14</version>
</dependency>
</dependencies>
log4j2.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
<Configuration name="ConfigTest" status="ERROR" monitorInterval="5">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Logger name="org.springframework" level="warn" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<Logger name="org.apache" level="warn" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<Logger name="net.sf.ehcache" level="warn" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<Logger name="org.apache.shiro.util.ThreadContext" level="warn" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
shiro.ini
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[users]
root = secret, admin
guest = guest, guest
presidentskroob = 12345, president
darkhelmet = ludicrousspeed, darklord, schwartz
lonestarr = vespa, goodguy, schwartz

# -----------------------------------------------------------------------------
# Roles with assigned permissions
# roleName = perm1, perm2, ..., permN
# -----------------------------------------------------------------------------
[roles]
admin = *
schwartz = lightsaber:*
goodguy = winnebago:drive:eagle5
Toturial
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
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Tutorial {

private static final transient Logger log = LoggerFactory.getLogger(Tutorial.class);

public static void main(String[] args) {
log.info("My First Apache Shiro Application");

Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);

// get the currently executing user:
Subject currentUser = SecurityUtils.getSubject();

// Do some stuff with a Session (no need for a web or EJB container!!!)
Session session = currentUser.getSession();
session.setAttribute("someKey", "aValue");
String value = (String) session.getAttribute("someKey");
if (value.equals("aValue")) {
log.info("Retrieved the correct value! [" + value + "]");
}

// let's login the current user so we can check against roles and permissions:
if (!currentUser.isAuthenticated()) {
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
token.setRememberMe(true);
try {
currentUser.login(token);
} catch (UnknownAccountException uae) {
log.info("There is no user with username of " + token.getPrincipal());
} catch (IncorrectCredentialsException ice) {
log.info("Password for account " + token.getPrincipal() + " was incorrect!");
} catch (LockedAccountException lae) {
log.info("The account for username " + token.getPrincipal() + " is locked. " +
"Please contact your administrator to unlock it.");
}
// ... catch more exceptions here (maybe custom ones specific to your application?
catch (AuthenticationException ae) {
//unexpected condition? error?
}
}

//say who they are:
//print their identifying principal (in this case, a username):
log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");

//test a role:
if (currentUser.hasRole("schwartz")) {
log.info("May the Schwartz be with you!");
} else {
log.info("Hello, mere mortal.");
}

//test a typed permission (not instance-level)
if (currentUser.isPermitted("lightsaber:wield")) {
log.info("You may use a lightsaber ring. Use it wisely.");
} else {
log.info("Sorry, lightsaber rings are for schwartz masters only.");
}

//a (very powerful) Instance Level permission:
if (currentUser.isPermitted("winnebago:drive:eagle5")) {
log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " +
"Here are the keys - have fun!");
} else {
log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
}

//all done - log out!
currentUser.logout();

System.exit(0);
}
}

Springboot

导入依赖
1
2
3
4
5
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.9.0</version>
</dependency>
shiro配置
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
package com.kuang.config;

import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {

//ShiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
factoryBean.setSecurityManager(securityManager);

//添加过滤器
/*
aono: 无需认证就可以访问
authc: 必须认证了才可以访问
user: 必须拥有 记住我 功能才能访问
perms: 拥有对某个资源的权限才能访问
role: 拥有某个角色权限才能访问
*/
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/user/*","authc");
factoryBean.setFilterChainDefinitionMap(filterMap);

factoryBean.setLoginUrl("/toLogin");

return factoryBean;
}

//DefaultWebSecurity
@Bean(name="securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm")UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm);

return securityManager;
}
//创建realm对象,需要自定义类
@Bean(name="userRealm")
public UserRealm userRealm(){
return new UserRealm();
}
}


认证授权
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
package com.kuang.config;

import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

public class UserRealm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String name = "root";
String password = "123456";

UsernamePasswordToken userToken = (UsernamePasswordToken)authenticationToken;

if(!userToken.getUsername().equals(name)){
return null;
}

return new SimpleAuthenticationInfo("",password,"");
}
}

mybatis

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
package com.kuang.config;

import com.kuang.pojo.User;
import com.kuang.service.UserServiceImpl;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;

public class UserRealm extends AuthorizingRealm {
UserServiceImpl userService;
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
Subject subject = SecurityUtils.getSubject();
User currentUser = (User) subject.getPrincipal();

//根据用户进行操作授权
//info.addObjectPermission();
return info;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken userToken = (UsernamePasswordToken)authenticationToken;

User user = userService.queryUserByName("userToken.getUsername()");


if (user==null){
return null;
}

return new SimpleAuthenticationInfo(user,user.getPassword(),"");
}
}

Thymeleaf

1
2
3
4
5
6
7
<!-- https://mvnrepository.com/artifact/com.github.theborakompanioni/thymeleaf-extras-shiro -->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.1.0</version>
</dependency>

1
2
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">

Swagger

依赖
1
2
3
4
5
6
7
8
9
10
11
12
<!-- 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>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>3.0.0</version>
</dependency>
配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Configuration
@EnableSwagger2
public class SwaggerConfig {

@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo());
}

private ApiInfo apiInfo(){
return new ApiInfo(
"Kuang Swagger Api",
"有朝一日,成为一个高手",
"1.0",
"https://zuoyenuandong.github.io/",
new Contact("kuang", "https://zuoyenuandong.github.io/", "1400584782@qq.com"),
"Apache 2.0",
"http://www.apache.org/licenses/LICENSE-2.0",
new ArrayList());

}

}
访问链接

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

注释
  • ApiModel(""):在实体类上面添加注释
  • ApiModelProperty(""):在实体类字段添加注释
  • ApiOperation(""):方法添加注释
  • ApiParam(""):方法参数添加注释

任务

异步任务

  • 主类上添加@EnableAsync
  • 方法上添加@Async

邮件任务

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

)

1
2
3
4
5
# 邮箱
spring.mail.username=1400584782@qq.com
spring.mail.password=pyprtzvykygyfffd
spring.mail.host=smtp.qq.com
spring.mail.properties.mail.smtp.ssl.enable=true
简单邮件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@SpringBootTest
class SpringbootStudy08JobApplicationTests {
@Autowired
JavaMailSenderImpl javaMailSender;
// 简单邮件
@Test
void contextLoads() {

SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
simpleMailMessage.setSubject("springboot mail service");
simpleMailMessage.setText("hello world");
simpleMailMessage.setTo("2597972416@qq.com");
simpleMailMessage.setFrom("1400584782@qq.com");
javaMailSender.send(simpleMailMessage);
}

}
复杂邮件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
void contextLoads2() throws MessagingException {

MimeMessage mimeMessage = javaMailSender.createMimeMessage();
MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage);
messageHelper.setSubject("springboot mail service");
//可以传输html
messageHelper.setText("<p style='color:blue'>hello world</p>",true);
//添加附件
//messageHelper.addAttachment();
messageHelper.setTo("2597972416@qq.com");
messageHelper.setFrom("1400584782@qq.com");
javaMailSender.send(mimeMessage);

}

定时任务

TaskScheduler:任务调度者

TaskExecutor:任务执行者

EnableScheduling:开启定时任务

Scheduled:什么时候执行

1
2
3
4
5
6
7
8
@Service
public class ScheduledService {
//秒 分 时 日 月 周几
@Scheduled(cron = "0 * * * * 0-7")//任何时候的第0秒执行
public void hello(){
System.out.println("hello world");
}
}

Dobbo Zookeeper

准备

  • 下载zookeeperdubbo-admin
  • dubbo-admin打包成一个jar包mvn clean package -Dmaven.test.skip=true
  • dubbo-admin的端口号为7001
  • zookeeper的端口号为2181,默认用户名和密码都是root

springboot

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
<!-- https://mvnrepository.com/artifact/org.apache.dubbo/dubbo-spring-boot-starter -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.8</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.github.sgroschupf/zkclient -->
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version>0.1</version>
</dependency>
<!--解决依赖冲突-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.8</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>

springboot(02)

Web开发

静态资源访问

访问类路径下的 /static /public /resource /META-INF/resource 目录都可以直接访问到静态资源

访问路径: 当前项目根路径/ + 静态资源名

可以通过修改配置文件 spring.mvc.static-path-pattern=/自定义前缀/**改变访问路径

可以通过修改配置文件spring.resource.static-locations = classpath:/自定义文件夹/

请求参数处理

表单隐藏提交 <input name="_method" type="hidden" value="自定义">并且form表单提交方式为post

自定义支持PUT、DELETE、PATRCH请求

修改配置文件spring.mvc.hiddenmethod.filter.enable=true

RestFul风格请求单个参数

访问请求localhost:8080/api/1

解析到后台

1
2
3
4
5
6
7
8
@RestController
public class MyController {
@ResponseBody
@GetMapping("/api/{id}")
public String helloWorld(@PathVariable("id") Integer id){
return ""+id;
}
}
RestFul风格请求多个参数

访问请求localhost:8080/user/root/password/123456

解析到后台

1
2
3
4
5
6
7
8
9
10
11
12
13
@RestController
public class MyController {
@GetMapping("/user/{name}/password/{password}")
public Map<String,Object> helloWorld(@PathVariable("name") String name,
@PathVariable("password") String password,
@PathVariable Map<String,String> kv){
Map<String, Object> map = new HashMap<>();
map.put("name",name);
map.put("password",password);
map.put("kvmap",kv);
return map;
}
}
常规请求多个参数

访问请求localhost:8080/hello?name=root&password=123456

解析到后台

1
2
3
4
5
6
7
8
9
10
@GetMapping("/hello")
public Map<String,Object> helloWorld02(@RequestParam("name") String name,
@RequestParam("password") String password,
@RequestParam Map<String,String> kv){
Map<String, Object> map = new HashMap<>();
map.put("name",name);
map.put("password",password);
map.put("kvmap",kv);
return map;
}
矩阵变量请求

访问请求localhost:8080/matrix/user01;name=root;tag=student,boy

springboot开启矩阵变量请求两种方式

1
2
3
4
5
6
7
8
9
@Configuration(proxyBeanMethods = false)
public class MyConfig implements WebMvcConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer configurer){
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration(proxyBeanMethods = false)
public class MyConfig {
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
};
}
}

解析到后台

1
2
3
4
5
6
7
8
@GetMapping("/matrix/{user01}")
public Map<String,Object> helloWorld03(@MatrixVariable("name") String name,
@MatrixVariable("tag")List<String> tag){
Map<String,Object> map = new HashMap<>();
map.put("name",name);
map.put("tag",tag);
return map;
}

访问请求localhost:8080/matrix02/user01;name=root/user02;name=admin

解析到后台

1
2
3
4
5
6
7
8
@GetMapping("/matrix02/{user01}/{user02}")
public Map<String,Object> helloWorld04(@MatrixVariable(value = "name",pathVar = "user01") String user01Name,
@MatrixVariable(value = "name",pathVar = "user02") String user02Name){
Map<String,Object> map = new HashMap<>();
map.put("user01Name",user01Name);
map.put("user02Name",user02Name);
return map;
}

响应参数处理

map、model、request数据是直接存在相应里面的

自定义封装

用来将接收到的数据自定义封装成一个对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Bean
public WebMvcConfigurer webMvcConfigurer(){
@Override
public void addFormatters(FormatterRegistry registry){
registry.addConverter(new Converter<String, Person>() {
//自定义封装
@Override
public Person convert(String s) {

return null;
}
});
}
};
}

Json返回

jackson.jar + @ResponseBody

可以给前端直接返回json数据

shell

Shebang

shebang表示文本文件中第一行前连个字符#!

1
2
3
4
5
6
histroy  #查看历史记录
-c 清楚记录
-r 恢复记录

!历史id 快速执行历史命令
!! 执行上一条命令

变量

变量赋值等于号不能有空格

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$0                     脚本名称和脚本路径

$n 脚本的第n个参数

$# 脚本参数的总数

$* 所有脚本参数

$@ 所有脚本参数



$? 上次命令的返回码

\$$ 当前脚本的进程号

$! 上一次后台进程的pid

$_ 在此执行前一个脚本的最后变量

bash一些基础命令

echo

1
2
3
4
5
6
7
8
9
10
11
-n 不换行输出

-e 解析字符串中的特殊符号

\n 不换行

\r 回车

\t 制表符

\b 退格

eval 执行多个命令

exec 不创建子进程执行后续命令,且执行完毕后,自动exit

shell 子串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
${变量}        返回变量值

${#变量} 返回变量长度,字符长度

${变量:start} 返回start数值之后的字符

${变量:start:length} 提取start之后的length个字符

${变量#word} 从变量开头删除最短匹配的word子串

${变量##word} 从变量开头删除最长匹配的word子串

${变量%word} 从变量结尾删除最短匹配的word子串

${变量%%word} 从变量结尾删除最长匹配的word子串

${变量/pattern/string} 用string替换第一个pattern

${变量//pattern/string} 用string替换所有pattern

for循环

1
2
3
4
5
6
7
8
9
10
11
for item in items

do

shell脚本

done



for n in {1..3} // 循环遍历三次

shell拓展变量

1
2
3
4
5
6
7
${name:-word} 如果name变量值为空,返回word字符串,可以被赋值

${name:=word}如果name变量为空,用word替代变量值,且返回其值

${name:?word} 如果name变量为空,返回word,否则返回变量值

${name:+word} 如果name变量为空,什么都不做,否则返回word返回

数值计算

(())

函数

函数名称(){

}

kubernetes

kubectl命令

kubectl [cmd] [type] [name] [flags]

cmd 指定要对资源执行的操作,例如create、get、delete

type 指定资源类型,比如deployment、pod、service

name 指定资源名称,名称大小写敏感

flags 指定额外的可选参数

DevOps

Gitlab安装

虚拟机可以关闭防火墙systemctl stop firewalld

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cd /usr/local
mkdir docker
cd docker
mkdir gitlab_docker

docker search gitlab
docker pull gitlab/gitlab-ce

vim docker-compose.yml

docker-compose up -d

docker-compose logs -f
#进入容器内部查看初始密码
docker exec -it gitlab bash
cat /etc/gitlab/initial_root_password
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
version: '3.1'
services:
gitlab:
image: 'gitlab/gitlab-ce:latest'
container_name: gitlab
restart: always
environment:
GITLAB_OMNIBUS_CONFIG: |
external_url '宿主机ip:8929'
gitlab_rails['gitlab_shell_ssh_port'] = 2224
ports:
- '8929:8929'
- '2224:2224'
volumes:
- './config:/etc/gitlab'
- './logs:/var/log/gitlab'
- './data:/var/opt/gitlab'

Maven安装

1
2
3
tar -zxvf jdk.tar.gz -C /usr/local

tar -zxvf maven.tar.gz -C /usr/local
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
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>
<mirror>
<id>alimaven</id>
<mirrorOf>central</mirrorOf>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/repositories/central/</url>
</mirror>

<profile>
<id>jdk8</id>
<activeByDefault>true</activeByDefault>
<jdk>1.8</jdk>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
</properties>
</profile>

<activeProfiles>
<activeProfile>jdk8</activeProfile>
</activeProfiles>

Jenkins安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#去官网(jenkins.io)中寻找到Jenkins的LTS版本
docker pull jenkins/jenkins:2.332.2-lts(实时更新)
cd /usr/local
mkdir docker
cd docker
mkdir jenkins_docker
cd jenkins_docker
vim docker-compose.yml
docker-compose up -d
#查看日志会发现data没有权限写文件
docker logs -f jenkins

#修改data权限
chmod -R 777 data

docker-compose restart

#再次查看日志可以看到初始化密码
docker logs -f jenkins
1
2
3
4
5
6
7
8
9
10
version: "3.1"
services:
jenkins:
image: jenkins/jenkins:2.332.2-lts
container_name: jenkins
ports:
- 8080:8080
- 50000:50000
volumes:
- ./data/:/var/jenkins_home/

第一次运行Jenkins初始化会花费很长时间 宿主机ip:8080 ,Jenkins首次安装推荐插件出错 No such plugin: cloudbees-folder

  • 解决方案一

Jenkins首次安装推荐插件出错 No such plugin: cloudbees-folder 超详细解决方案

  • 解决方案二
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!--修改数据卷中的hudson.model.UpdateCenter.xml文件-->
<?xml version='1.1' encoding='UTF-8'?>
<sites>
<site>
<id>default</id>
<url>https://updates.jenkins.io/update-center.json</url>
</site>
</sites>
<!--将下载地址替换为http://mirror.esuni.jp/jenkins/updates/update-center.json-->
<?xml version='1.1' encoding='UTF-8'?>
<sites>
<site>
<id>default</id>
<url>http://mirror.esuni.jp/jenkins/updates/update-center.json</url>
</site>
</sites>
<!--清华大学的插件源也可以
https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json -->

1
2
3
4
5
#再次重启Jenkins容器,访问Jenkins
docker-compose restart
#查看密码登录Jenkins,并登录下载插件
docker exec -it jenkins\
cat /var/jenkins_home/secrets/initialAdminPassword

进入Jenkins后安装 Git Parameter Publish Over SSH插件

1
2
3
4
#通过数据卷将jdk和maven映射到/var/jenkins_home/中
cd /usr/local/docker/jenkins_docker/data
mv /usr/local/jdk ./
mv /usr/local/maven ./

配置jdk

配置maven

配置OpenSSL