修改博文属性
This commit is contained in:
parent
24499f7500
commit
23a89e4b6d
@ -1,9 +1,15 @@
|
|||||||
[id]:2018-08-13
|
---
|
||||||
[type]:javaee
|
id="2018-08-13-10-38"
|
||||||
[tag]:java,spring,springboot
|
title="springboot搭建"
|
||||||
  前面的博客有说到spring boot搭建见另一篇博文,其实那篇博文还没写,现在来填个坑。我们使用spring initializr来构建,idea和eclipse都支持这种方式,构建过程类似,这里以idea为例,详细记录构建过程。
|
headWord="前面的博客有说到spring boot搭建见另一篇博文,其实那篇博文还没写,现在来填个坑。我们使用spring initializr来构建,idea和eclipse都支持这种方式,构建过程类似,这里以idea为例,详细记录构建过程。"
|
||||||
|
tags=["java", "spring","springboot","idea"]
|
||||||
|
category="java"
|
||||||
|
serie="spring boot学习"
|
||||||
|
---
|
||||||
|
|
||||||
###1.选择spring initializr
|
  前面的博客有说到 spring boot 搭建见另一篇博文,其实那篇博文还没写,现在来填个坑。我们使用 spring initializr 来构建,idea 和 eclipse 都支持这种方式,构建过程类似,这里以 idea 为例,详细记录构建过程。
|
||||||
|
|
||||||
|
### 1.选择 spring initializr
|
||||||
|
|
||||||
![1532967570728](./picFolder/1532967570728.png)
|
![1532967570728](./picFolder/1532967570728.png)
|
||||||
|
|
||||||
@ -17,25 +23,25 @@ next
|
|||||||
|
|
||||||
#### 3.选择依赖
|
#### 3.选择依赖
|
||||||
|
|
||||||
  在这里选择spring boot版本和web依赖(忽略sql的依赖,如有需要[点击这里](f),单独将mybatis的整合),后面也可手动编辑pom文件修改增加删除依赖
|
  在这里选择 spring boot 版本和 web 依赖(忽略 sql 的依赖,如有需要[点击这里](f),单独将 mybatis 的整合),后面也可手动编辑 pom 文件修改增加删除依赖
|
||||||
|
|
||||||
![1532967938985](./picFolder/1532967938985.png)
|
![1532967938985](./picFolder/1532967938985.png)
|
||||||
|
|
||||||
这里我们选择web搭建一个简单的REST风格demo。然后next。
|
这里我们选择 web 搭建一个简单的 REST 风格 demo。然后 next。
|
||||||
|
|
||||||
####4.设置项目存放地址
|
#### 4.设置项目存放地址
|
||||||
|
|
||||||
![1532968024509](./picFolder/1532968024509.png)
|
![1532968024509](./picFolder/1532968024509.png)
|
||||||
|
|
||||||
这样就成功构建了一个springboot项目。
|
这样就成功构建了一个 springboot 项目。
|
||||||
|
|
||||||
#### 5.测试
|
#### 5.测试
|
||||||
|
|
||||||
  现在新建一个controller包,包下新建一个HelloController,创建之后项目目录结构如下:
|
  现在新建一个 controller 包,包下新建一个 HelloController,创建之后项目目录结构如下:
|
||||||
|
|
||||||
![1532969025023](./picFolder/1532969025023.png)
|
![1532969025023](./picFolder/1532969025023.png)
|
||||||
|
|
||||||
HelloController代码如下:
|
HelloController 代码如下:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@RestController
|
@RestController
|
||||||
@ -48,4 +54,4 @@ public class HelloController{
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
然后运行项目,访问localhost:8080/home/hello即可看到hello字符串。
|
然后运行项目,访问 localhost:8080/home/hello 即可看到 hello 字符串。
|
||||||
|
@ -1,16 +1,22 @@
|
|||||||
[id]:2018-08-20
|
---
|
||||||
[type]:javaee
|
id="2018-08-20-10-38"
|
||||||
[tag]:java,spring,springsecurity,scurity
|
title="springboot+security整合(1)"
|
||||||
|
headWord="javaee中的非常重要的一个安全认证框架,但是略微重量级,但是既然领导交代要学,那就学吧。。。"
|
||||||
|
tags=["java", "spring","springboot","spring-security","security"]
|
||||||
|
category="java"
|
||||||
|
serie="spring boot学习"
|
||||||
|
---
|
||||||
|
|
||||||
**说明springboot版本2.0.3**
|
**说明 springboot 版本 2.0.3<br/>项目地址:[点击跳转](https://github.com/FleyX/demo-project/tree/master/springboot_spirngsecurity_demo)**
|
||||||
|
|
||||||
##一、 介绍
|
## 一、 介绍
|
||||||
|
|
||||||
  Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
|
  Spring Security 是一个能够为基于 Spring 的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在 Spring 应用上下文中配置的 Bean,充分利用了 Spring IoC,DI(控制反转 Inversion of Control ,DI:Dependency Injection 依赖注入)和 AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
|
||||||
|
|
||||||
##二、 环境搭建
|
## 二、 环境搭建
|
||||||
|
|
||||||
|
  建立 springboot2 项目,加入 security 依赖,mybatis 依赖
|
||||||
|
|
||||||
  建立springboot2项目,加入security依赖,mybatis依赖
|
|
||||||
```xml
|
```xml
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
@ -27,7 +33,9 @@
|
|||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
```
|
```
|
||||||
数据库为传统的用户--角色--权限,权限表记录了url和method,springboot配置文件如下:
|
|
||||||
|
数据库为传统的用户--角色--权限,权限表记录了 url 和 method,springboot 配置文件如下:
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
mybatis:
|
mybatis:
|
||||||
type-aliases-package: com.example.demo.entity
|
type-aliases-package: com.example.demo.entity
|
||||||
@ -44,7 +52,9 @@ spring:
|
|||||||
charset: utf-8
|
charset: utf-8
|
||||||
enabled: true
|
enabled: true
|
||||||
```
|
```
|
||||||
springboot启动类中加入如下代码,设置路由匹配规则。
|
|
||||||
|
springboot 启动类中加入如下代码,设置路由匹配规则。
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@Override
|
@Override
|
||||||
protected void configurePathMatch(PathMatchConfigurer configurer) {
|
protected void configurePathMatch(PathMatchConfigurer configurer) {
|
||||||
@ -53,20 +63,24 @@ protected void configurePathMatch(PathMatchConfigurer configurer) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 三、 security配置
|
## 三、 security 配置
|
||||||
|
|
||||||
  默认情况下security是无需任何自定义配置就可使用的,我们不考虑这种方式,直接讲如何个性化登录过程。
|
  默认情况下 security 是无需任何自定义配置就可使用的,我们不考虑这种方式,直接讲如何个性化登录过程。
|
||||||
|
|
||||||
|
#### 1、 建立 security 配置文件,目前配置文件中还没有任何配置。
|
||||||
|
|
||||||
#### 1、 建立security配置文件,目前配置文件中还没有任何配置。
|
|
||||||
```java
|
```java
|
||||||
@Configuration
|
@Configuration
|
||||||
public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 2、 个性化登录,security中的登录如下:
|
#### 2、 个性化登录,security 中的登录如下:
|
||||||
|
|
||||||
![登录过程](./picFolder/pic1.png)
|
![登录过程](./picFolder/pic1.png)
|
||||||
- security需要一个user的实体类实现`UserDetails`接口,该实体类最后与系统中用户的实体类分开,代码如下:
|
|
||||||
|
- security 需要一个 user 的实体类实现`UserDetails`接口,该实体类最后与系统中用户的实体类分开,代码如下:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public class SecurityUser implements UserDetails{
|
public class SecurityUser implements UserDetails{
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
@ -74,7 +88,7 @@ public class SecurityUser implements UserDetails{
|
|||||||
private String name;
|
private String name;
|
||||||
List<GrantedAuthority> authorities;
|
List<GrantedAuthority> authorities;
|
||||||
|
|
||||||
public User(string name,string password) {
|
public SecurityUser(string name,string password) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
@ -125,7 +139,9 @@ public class SecurityUser implements UserDetails{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- 编写了实体类还需要编写一个服务类SecurityService实现`UserDetailsService`接口,重写loadByUsername方法,通过这个方法根据用户名获取用户信息,代码如下:
|
|
||||||
|
- 编写了实体类还需要编写一个服务类 SecurityService 实现`UserDetailsService`接口,重写 loadByUsername 方法,通过这个方法根据用户名获取用户信息,代码如下:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@Component
|
@Component
|
||||||
public class SecurityUserService implements UserDetailsService {
|
public class SecurityUserService implements UserDetailsService {
|
||||||
@ -158,7 +174,9 @@ public class SecurityUserService implements UserDetailsService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- 通常我们会对密码进行加密,所有还要编写一个passwordencode类,实现PasswordEncoder接口,代码如下:
|
|
||||||
|
- 通常我们会对密码进行加密,所有还要编写一个 passwordencode 类,实现 PasswordEncoder 接口,代码如下:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@Component
|
@Component
|
||||||
public class MyPasswordEncoder implements PasswordEncoder {
|
public class MyPasswordEncoder implements PasswordEncoder {
|
||||||
@ -178,7 +196,9 @@ public class MyPasswordEncoder implements PasswordEncoder {
|
|||||||
```
|
```
|
||||||
|
|
||||||
#### 3、 编辑配置文件
|
#### 3、 编辑配置文件
|
||||||
- 编写config Bean以使用上面定义的验证逻辑,securityUserService、myPasswordEncoder通过@Autowired引入。
|
|
||||||
|
- 编写 config Bean 以使用上面定义的验证逻辑,securityUserService、myPasswordEncoder 通过@Autowired 引入。
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@Override
|
@Override
|
||||||
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
|
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
|
||||||
@ -186,7 +206,9 @@ protected void configure(AuthenticationManagerBuilder auth) throws Exception {
|
|||||||
.passwordEncoder(myPasswordEncoder);
|
.passwordEncoder(myPasswordEncoder);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- 然后编写configure Bean(和上一个不一样,参数不同),实现security验证逻辑,代码如下:
|
|
||||||
|
- 然后编写 configure Bean(和上一个不一样,参数不同),实现 security 验证逻辑,代码如下:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@Override
|
@Override
|
||||||
protected void configure(HttpSecurity http) throws Exception {
|
protected void configure(HttpSecurity http) throws Exception {
|
||||||
@ -214,13 +236,15 @@ protected void configure(AuthenticationManagerBuilder auth) throws Exception {
|
|||||||
.logoutSuccessUrl("public/logoutSuccess")
|
.logoutSuccessUrl("public/logoutSuccess")
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
到这里便可实现security与springboot的基本整合。
|
|
||||||
|
到这里便可实现 security 与 springboot 的基本整合。
|
||||||
|
|
||||||
## 四、实现记住我功能
|
## 四、实现记住我功能
|
||||||
|
|
||||||
#### 1、 建表
|
#### 1、 建表
|
||||||
|
|
||||||
  记住我功能需要数据库配合实现,首先要在数据库建一张表用户保存cookie和用户名,数据库建表语句如下:不能做修改
|
  记住我功能需要数据库配合实现,首先要在数据库建一张表用户保存 cookie 和用户名,数据库建表语句如下:不能做修改
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
CREATE TABLE `persistent_logins` (
|
CREATE TABLE `persistent_logins` (
|
||||||
`username` varchar(64) NOT NULL,
|
`username` varchar(64) NOT NULL,
|
||||||
@ -231,8 +255,10 @@ CREATE TABLE `persistent_logins` (
|
|||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 2、 编写rememberMeservice Bean
|
#### 2、 编写 rememberMeservice Bean
|
||||||
|
|
||||||
  代码如下:
|
  代码如下:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@Bean
|
@Bean
|
||||||
public RememberMeServices rememberMeServices(){
|
public RememberMeServices rememberMeServices(){
|
||||||
@ -245,13 +271,17 @@ CREATE TABLE `persistent_logins` (
|
|||||||
return rememberMeServices;
|
return rememberMeServices;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
dataSource为@Autowired引入
|
|
||||||
|
|
||||||
#### 3、 配置文件设置remember
|
dataSource 为@Autowired 引入
|
||||||
  在config(HttpSecurity http)中加入记住我功能
|
|
||||||
|
#### 3、 配置文件设置 remember
|
||||||
|
|
||||||
|
  在 config(HttpSecurity http)中加入记住我功能
|
||||||
|
|
||||||
```java
|
```java
|
||||||
.rememberMe()
|
.rememberMe()
|
||||||
.rememberMeServices(rememberMeServices())
|
.rememberMeServices(rememberMeServices())
|
||||||
.key("INTERNAL_SECRET_KEY")
|
.key("INTERNAL_SECRET_KEY")
|
||||||
```
|
```
|
||||||
在登录表单中设置remember-me即可实现记住我功能。
|
|
||||||
|
在登录表单中设置 remember-me 即可实现记住我功能。
|
||||||
|
@ -1,3 +1,11 @@
|
|||||||
|
---
|
||||||
|
id="2018-08-21-10-38"
|
||||||
|
title="springboot+security整合(2)"
|
||||||
|
headWord="文接上篇,上一篇中登录验证都由security帮助我们完成了,如果我们想要增加一个验证码登录或者其它的自定义校验就没办法了,因此这一篇讲解如何实现这个功能。"
|
||||||
|
tags=["java", "spring","springboot","spring-security","security"]
|
||||||
|
category="java"
|
||||||
|
serie="spring boot学习"
|
||||||
|
---
|
||||||
[id]:2018-08-21
|
[id]:2018-08-21
|
||||||
[type]:javaee
|
[type]:javaee
|
||||||
[tag]:java,spring,springsecurity,scurity
|
[tag]:java,spring,springsecurity,scurity
|
||||||
|
@ -1,13 +1,19 @@
|
|||||||
[id]:2018-08-22
|
---
|
||||||
[type]:javaee
|
id="2018-08-22-10-38"
|
||||||
[tag]:java,spring,springsecurity,scurity
|
title="springboot+security整合(3)"
|
||||||
|
headWord="文接上篇,上篇说了那个啥自定义校验的功能,这篇来学学如何自定义鉴权。感觉都定义到这个地步,都不太需要security框架了,再自己整整缓存方面的功能就是一个功能完成的鉴权模块了。"
|
||||||
|
tags=["java", "spring","springboot","spring-security","security"]
|
||||||
|
category="java"
|
||||||
|
serie="spring boot学习"
|
||||||
|
---
|
||||||
|
|
||||||
  这篇讲解如何自定义鉴权过程,实现根据数据库查询出的url和method是否匹配当前请求的url和method来决定有没有权限。security鉴权过程如下:
|
  这篇讲解如何自定义鉴权过程,实现根据数据库查询出的 url 和 method 是否匹配当前请求的 url 和 method 来决定有没有权限。security 鉴权过程如下:
|
||||||
![鉴权流程](./picFolder/pic2.png)
|
![鉴权流程](./picFolder/pic2.png)
|
||||||
|
|
||||||
##一、 重写metadataSource类
|
## 一、 重写 metadataSource 类
|
||||||
|
|
||||||
|
1. 编写 MyGranteAuthority 类,让权限包含 url 和 method 两个部分。
|
||||||
|
|
||||||
1. 编写MyGranteAuthority类,让权限包含url和method两个部分。
|
|
||||||
```java
|
```java
|
||||||
public class MyGrantedAuthority implements GrantedAuthority {
|
public class MyGrantedAuthority implements GrantedAuthority {
|
||||||
private String method;
|
private String method;
|
||||||
@ -42,7 +48,9 @@ public class MyGrantedAuthority implements GrantedAuthority {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
2. 编写MyConfigAttribute类,实现ConfigAttribute接口,代码如下:
|
|
||||||
|
2. 编写 MyConfigAttribute 类,实现 ConfigAttribute 接口,代码如下:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public class MyConfigAttribute implements ConfigAttribute {
|
public class MyConfigAttribute implements ConfigAttribute {
|
||||||
private HttpServletRequest httpServletRequest;
|
private HttpServletRequest httpServletRequest;
|
||||||
@ -71,7 +79,9 @@ public class MyConfigAttribute implements ConfigAttribute {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
3. 编写MySecurityMetadataSource类,获取当前url所需要的权限
|
|
||||||
|
3. 编写 MySecurityMetadataSource 类,获取当前 url 所需要的权限
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@Component
|
@Component
|
||||||
public class MySecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
|
public class MySecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
|
||||||
@ -120,9 +130,10 @@ public class MySecurityMetadataSource implements FilterInvocationSecurityMetadat
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
##二、 编写MyAccessDecisionManager类
|
## 二、 编写 MyAccessDecisionManager 类
|
||||||
|
|
||||||
|
  实现 AccessDecisionManager 接口以实现权限判断,直接 return 说明验证通过,如不通过需要抛出对应错误,代码如下:
|
||||||
|
|
||||||
  实现AccessDecisionManager接口以实现权限判断,直接return说明验证通过,如不通过需要抛出对应错误,代码如下:
|
|
||||||
```java
|
```java
|
||||||
@Component
|
@Component
|
||||||
public class MyAccessDecisionManager implements AccessDecisionManager{
|
public class MyAccessDecisionManager implements AccessDecisionManager{
|
||||||
@ -161,8 +172,9 @@ public class MyAccessDecisionManager implements AccessDecisionManager{
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
##三、 编写MyFilterSecurityInterceptor类
|
## 三、 编写 MyFilterSecurityInterceptor 类
|
||||||
  该类继承AbstractSecurityInterceptor类,实现Filter接口,代码如下:
|
  该类继承 AbstractSecurityInterceptor 类,实现 Filter 接口,代码如下:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@Component
|
@Component
|
||||||
public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
|
public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
|
||||||
@ -213,8 +225,10 @@ public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor imp
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 四、 加入到security的过滤器链中
|
## 四、 加入到 security 的过滤器链中
|
||||||
|
|
||||||
```java
|
```java
|
||||||
.addFilterBefore(urlFilterSecurityInterceptor,FilterSecurityInterceptor.class)
|
.addFilterBefore(urlFilterSecurityInterceptor,FilterSecurityInterceptor.class)
|
||||||
```
|
```
|
||||||
|
|
||||||
完成
|
完成
|
@ -1,23 +1,29 @@
|
|||||||
[id]:2018-08-25
|
---
|
||||||
[type]:javaee
|
id="2018-08-25-10-38"
|
||||||
[tag]:java,spring,websocket
|
title="springboot整合WebSocket"
|
||||||
|
headWord="webSocket作为http单向连接的补充,实现了服务端,浏览器端的双向通信,还是有必要了解了解"
|
||||||
|
tags=["java", "spring","springboot","WebSocket"]
|
||||||
|
category="java"
|
||||||
|
serie="spring boot学习"
|
||||||
|
---
|
||||||
|
|
||||||
<h3 id="#一、背景">一、背景</h3>
|
### 一、背景
|
||||||
|
|
||||||
  我们都知道http协议只能浏览器单方面向服务器发起请求获得响应,服务器不能主动向浏览器推送消息。想要实现浏览器的主动推送有两种主流实现方式:
|
  我们都知道 http 协议只能浏览器单方面向服务器发起请求获得响应,服务器不能主动向浏览器推送消息。想要实现浏览器的主动推送有两种主流实现方式:
|
||||||
|
|
||||||
- 轮询:缺点很多,但是实现简单
|
- 轮询:缺点很多,但是实现简单
|
||||||
- websocket:在浏览器和服务器之间建立tcp连接,实现全双工通信
|
- websocket:在浏览器和服务器之间建立 tcp 连接,实现全双工通信
|
||||||
|
|
||||||
  springboot使用websocket有两种方式,一种是实现简单的websocket,另外一种是实现**STOMP**协议。这一篇实现简单的websocket,STOMP下一篇在讲。
|
  springboot 使用 websocket 有两种方式,一种是实现简单的 websocket,另外一种是实现**STOMP**协议。这一篇实现简单的 websocket,STOMP 下一篇在讲。
|
||||||
|
|
||||||
**注意:如下都是针对使用springboot内置容器**
|
**注意:如下都是针对使用 springboot 内置容器**
|
||||||
|
|
||||||
<h3 id="二、实现">二、实现</h3>
|
### 二、实现
|
||||||
|
|
||||||
<h4 id="1、依赖引入">1、依赖引入</h4>
|
#### 1、依赖引入
|
||||||
|
|
||||||
|
  要使用 websocket 关键是`@ServerEndpoint`这个注解,该注解是 javaee 标准中的注解,tomcat7 及以上已经实现了,如果使用传统方法将 war 包部署到 tomcat 中,只需要引入如下 javaee 标准依赖即可:
|
||||||
|
|
||||||
  要使用websocket关键是`@ServerEndpoint`这个注解,该注解是javaee标准中的注解,tomcat7及以上已经实现了,如果使用传统方法将war包部署到tomcat中,只需要引入如下javaee标准依赖即可:
|
|
||||||
```xml
|
```xml
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>javax</groupId>
|
<groupId>javax</groupId>
|
||||||
@ -26,7 +32,9 @@
|
|||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
```
|
```
|
||||||
如使用springboot内置容器,无需引入,springboot已经做了包含。我们只需引入如下依赖即可:
|
|
||||||
|
如使用 springboot 内置容器,无需引入,springboot 已经做了包含。我们只需引入如下依赖即可:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
@ -36,9 +44,10 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
```
|
```
|
||||||
|
|
||||||
<h4 id="2、注入Bean">2、注入Bean</h4>
|
#### 2、注入 Bean
|
||||||
|
|
||||||
|
  首先注入一个**ServerEndpointExporter**Bean,该 Bean 会自动注册使用@ServerEndpoint 注解申明的 websocket endpoint。代码如下:
|
||||||
|
|
||||||
  首先注入一个**ServerEndpointExporter**Bean,该Bean会自动注册使用@ServerEndpoint注解申明的websocket endpoint。代码如下:
|
|
||||||
```java
|
```java
|
||||||
@Configuration
|
@Configuration
|
||||||
public class WebSocketConfig {
|
public class WebSocketConfig {
|
||||||
@ -49,9 +58,10 @@ public class WebSocketConfig {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
<h4 id="3、申明endpoint">3、申明endpoint</h4>
|
#### 3、申明 endpoint
|
||||||
|
|
||||||
|
  建立**MyWebSocket.java**类,在该类中处理 websocket 逻辑
|
||||||
|
|
||||||
  建立**MyWebSocket.java**类,在该类中处理websocket逻辑
|
|
||||||
```java
|
```java
|
||||||
@ServerEndpoint(value = "/websocket") //接受websocket请求路径
|
@ServerEndpoint(value = "/websocket") //接受websocket请求路径
|
||||||
@Component //注册到spring容器中
|
@Component //注册到spring容器中
|
||||||
@ -135,49 +145,47 @@ public class MyWebSocket {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
<h4 id="4、客户的实现">4、客户的实现</h4>
|
#### 4、客户的实现
|
||||||
|
|
||||||
|
  客户端使用 h5 原生 websocket,部分浏览器可能不支持。代码如下:
|
||||||
|
|
||||||
  客户端使用h5原生websocket,部分浏览器可能不支持。代码如下:
|
|
||||||
```html
|
```html
|
||||||
<html>
|
<html>
|
||||||
|
<head>
|
||||||
<head>
|
|
||||||
<title>websocket测试</title>
|
<title>websocket测试</title>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<button onclick="sendMessage()">测试</button>
|
<button onclick="sendMessage()">测试</button>
|
||||||
<script>
|
<script>
|
||||||
let socket = new WebSocket("ws://localhost:8080/websocket");
|
let socket = new WebSocket('ws://localhost:8080/websocket');
|
||||||
socket.onerror = err => {
|
socket.onerror = err => {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
};
|
};
|
||||||
socket.onopen = event => {
|
socket.onopen = event => {
|
||||||
console.log(event);
|
console.log(event);
|
||||||
};
|
};
|
||||||
socket.onmessage = mess => {
|
socket.onmessage = mess => {
|
||||||
console.log(mess);
|
console.log(mess);
|
||||||
};
|
};
|
||||||
socket.onclose = () => {
|
socket.onclose = () => {
|
||||||
console.log("连接关闭");
|
console.log('连接关闭');
|
||||||
};
|
};
|
||||||
|
|
||||||
function sendMessage() {
|
function sendMessage() {
|
||||||
if (socket.readyState === 1)
|
if (socket.readyState === 1) socket.send('这是一个测试数据');
|
||||||
socket.send("这是一个测试数据");
|
else alert('尚未建立websocket连接');
|
||||||
else
|
}
|
||||||
alert("尚未建立websocket连接");
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
```
|
```
|
||||||
|
|
||||||
<h3 id="三、测试">三、测试</h3>
|
### 三、测试
|
||||||
|
|
||||||
|
  建立一个 controller 测试群发,代码如下:
|
||||||
|
|
||||||
  建立一个controller测试群发,代码如下:
|
|
||||||
```java
|
```java
|
||||||
@RestController
|
@RestController
|
||||||
public class HomeController {
|
public class HomeController {
|
||||||
@ -188,7 +196,9 @@ public class HomeController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
然后打开上面的html,可以看到浏览器和服务器都输出连接成功的信息:
|
|
||||||
|
然后打开上面的 html,可以看到浏览器和服务器都输出连接成功的信息:
|
||||||
|
|
||||||
```
|
```
|
||||||
浏览器:
|
浏览器:
|
||||||
Event {isTrusted: true, type: "open", target: WebSocket, currentTarget: WebSocket, eventPhase: 2, …}
|
Event {isTrusted: true, type: "open", target: WebSocket, currentTarget: WebSocket, eventPhase: 2, …}
|
||||||
@ -196,14 +206,17 @@ Event {isTrusted: true, type: "open", target: WebSocket, currentTarget: WebSock
|
|||||||
服务端:
|
服务端:
|
||||||
2018-08-01 14:05:34.727 INFO 12708 --- [nio-8080-exec-1] com.fxb.h5websocket.MyWebSocket : 新的连接加入:0
|
2018-08-01 14:05:34.727 INFO 12708 --- [nio-8080-exec-1] com.fxb.h5websocket.MyWebSocket : 新的连接加入:0
|
||||||
```
|
```
|
||||||
|
|
||||||
点击测试按钮,可在服务端看到如下输出:
|
点击测试按钮,可在服务端看到如下输出:
|
||||||
|
|
||||||
```
|
```
|
||||||
2018-08-01 15:00:34.644 INFO 12708 --- [nio-8080-exec-6] com.fxb.h5websocket.MyWebSocket : 收到客户端2消息:这是一个测试数据
|
2018-08-01 15:00:34.644 INFO 12708 --- [nio-8080-exec-6] com.fxb.h5websocket.MyWebSocket : 收到客户端2消息:这是一个测试数据
|
||||||
```
|
```
|
||||||
再次打开html页面,这样就有两个websocket客户端,然后在浏览器访问[localhost:8080/broadcast](localhost:8080/broadcast)测试群发功能,每个客户端都会输出如下信息:
|
|
||||||
|
再次打开 html 页面,这样就有两个 websocket 客户端,然后在浏览器访问[localhost:8080/broadcast](localhost:8080/broadcast)测试群发功能,每个客户端都会输出如下信息:
|
||||||
|
|
||||||
```
|
```
|
||||||
MessageEvent {isTrusted: true, data: "这是一条测试广播", origin: "ws://localhost:8080", lastEventId: "", source: null, …}
|
MessageEvent {isTrusted: true, data: "这是一条测试广播", origin: "ws://localhost:8080", lastEventId: "", source: null, …}
|
||||||
```
|
```
|
||||||
<br/>
|
|
||||||
  源码可在[github]()上下载,记得点赞,star哦
|
|
||||||
|
|
||||||
|
  源码可在 [github 下载](https://github.com/FleyX/demo-project/tree/master/h5websocket) 上下载,记得点赞,star 哦
|
||||||
|
@ -1,16 +1,23 @@
|
|||||||
[id]:2018-09-01
|
---
|
||||||
[type]:javaee
|
id="2018-09-01-10-38"
|
||||||
[tag]:java,spring,mysql,mybatis,xml
|
title="springboot整合Mybatis(xml和注解)"
|
||||||
|
headWord="写的一手好sql就能玩转mybatis,入门难度相较于hibernate小得多。"
|
||||||
|
tags=["java", "spring","springboot","mysql","mybatis","xml","注解"]
|
||||||
|
category="java"
|
||||||
|
serie="spring boot学习"
|
||||||
|
---
|
||||||
|
|
||||||
## 写在前面
|
## 写在前面
|
||||||
|
|
||||||
刚毕业的第一份工作是java开发,项目中需要用到mybatis,特此记录学习过程,这只是一个简单demo,mybatis用法很多不可能全部写出来,有更复杂的需求建议查看mybatis的官方中文文档,[点击跳转](http://www.mybatis.org/mybatis-3/zh/index.html)。下面时项目环境/版本。
|
  项目源代码在github,地址为:[https://github.com/FleyX/demo-project/tree/master/mybatis-test](https://github.com/FleyX/demo-project/tree/master/mybatis-test),有需要的自取。
|
||||||
|
|
||||||
|
  刚毕业的第一份工作是 java 开发,项目中需要用到 mybatis,特此记录学习过程,这只是一个简单 demo,mybatis 用法很多不可能全部写出来,有更复杂的需求建议查看 mybatis 的官方中文文档,[点击跳转](http://www.mybatis.org/mybatis-3/zh/index.html)。下面时项目环境/版本。
|
||||||
|
|
||||||
- 开发工具:IDEA
|
- 开发工具:IDEA
|
||||||
- jdk版本:1.8
|
- jdk 版本:1.8
|
||||||
- springboot版本:2.03
|
- springboot 版本:2.03
|
||||||
|
|
||||||
其他依赖版本见下面pom.xml:
|
其他依赖版本见下面 pom.xml:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
@ -87,13 +94,13 @@
|
|||||||
|
|
||||||
## 1.创建项目
|
## 1.创建项目
|
||||||
|
|
||||||
使用idea中的spring initializr生成maven项目,项目命令为mybatis-test,选择web,mysql,mybatis依赖,即可成功。(详细过程不赘述,如有需要学习springboot创建过程,可参考[这篇文章]()。
|
使用 idea 中的 spring initializr 生成 maven 项目,项目命令为 mybatis-test,选择 web,mysql,mybatis 依赖,即可成功。(详细过程不赘述,如有需要学习 springboot 创建过程,可参考[这篇文章](http://tapme.top/blog/detail/2018-08-13-10-38)。
|
||||||
|
|
||||||
然后依照上面的pom文件,补齐缺少的依赖。接着创建包entity,service和mybatis映射文件夹mapper,创建。为了方便配置将application.properties改成application.yml。由于我们时REST接口,故不需要static和templates目录。修改完毕后的项目结构如下:
|
然后依照上面的 pom 文件,补齐缺少的依赖。接着创建包 entity,service 和 mybatis 映射文件夹 mapper,创建。为了方便配置将 application.properties 改成 application.yml。由于我们时 REST 接口,故不需要 static 和 templates 目录。修改完毕后的项目结构如下:
|
||||||
|
|
||||||
![项目结构](./picFolder/pic1.png)
|
![项目结构](./picFolder/pic1.png)
|
||||||
|
|
||||||
修改启动类,增加`@MapperScan("com.example.mybatistest.dao") `,以自动扫描dao目录,避免每个dao都手动加`@Mapper`注解。代码如下:
|
修改启动类,增加`@MapperScan("com.example.mybatistest.dao")`,以自动扫描 dao 目录,避免每个 dao 都手动加`@Mapper`注解。代码如下:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
@ -105,7 +112,7 @@ public class MybatisTestApplication {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
修改application.yml,配置项目,代码如下:
|
修改 application.yml,配置项目,代码如下:
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
mybatis:
|
mybatis:
|
||||||
@ -158,7 +165,7 @@ spring:
|
|||||||
|
|
||||||
## 2.编写代码
|
## 2.编写代码
|
||||||
|
|
||||||
首先创建数据表,sql语句如下:
|
首先创建数据表,sql 语句如下:
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
CREATE TABLE `user` (
|
CREATE TABLE `user` (
|
||||||
@ -170,7 +177,7 @@ CREATE TABLE `user` (
|
|||||||
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;
|
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;
|
||||||
```
|
```
|
||||||
|
|
||||||
然后在entity包中创建实体类User.java
|
然后在 entity 包中创建实体类 User.java
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public class User {
|
public class User {
|
||||||
@ -190,7 +197,7 @@ public class User {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
在dao包下创建UserDao.java
|
在 dao 包下创建 UserDao.java
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public interface UserDao {
|
public interface UserDao {
|
||||||
@ -203,7 +210,7 @@ public interface UserDao {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
在mapper文件夹下创建UserMapper.xml,具体的xml编写方法查看文首的官方文档。
|
在 mapper 文件夹下创建 UserMapper.xml,具体的 xml 编写方法查看文首的官方文档。
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
@ -249,13 +256,13 @@ public interface UserDao {
|
|||||||
</mapper>
|
</mapper>
|
||||||
```
|
```
|
||||||
|
|
||||||
至此使用mybatis的代码编写完了,之后要用时调用dao接口中的方法即可。
|
至此使用 mybatis 的代码编写完了,之后要用时调用 dao 接口中的方法即可。
|
||||||
|
|
||||||
## 3.测试
|
## 3.测试
|
||||||
|
|
||||||
我们通过编写service,controller然后使用postman进行测试。
|
我们通过编写 service,controller 然后使用 postman 进行测试。
|
||||||
|
|
||||||
首先编写UserService.java,代码如下:
|
首先编写 UserService.java,代码如下:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@Component
|
@Component
|
||||||
@ -287,7 +294,7 @@ public class UserService {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
编写UserController.java
|
编写 UserController.java
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@RestController
|
@RestController
|
||||||
@ -320,7 +327,7 @@ public class UserController {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
启动项目,通过postman进行请求测试,测试结果如下:
|
启动项目,通过 postman 进行请求测试,测试结果如下:
|
||||||
|
|
||||||
- 插入数据:
|
- 插入数据:
|
||||||
|
|
||||||
@ -330,19 +337,15 @@ public class UserController {
|
|||||||
|
|
||||||
![查询](./picFolder/pic3.png)
|
![查询](./picFolder/pic3.png)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
- 分页查询
|
- 分页查询
|
||||||
|
|
||||||
![分页查询](./picFolder/pic4.png)
|
![分页查询](./picFolder/pic4.png)
|
||||||
|
|
||||||
|
## 4.注解编写 sql
|
||||||
|
|
||||||
|
上面使用的是 xml 方式编写 sql 代码,其实 mybatis 也支持在注解中编写 sql,这样可以避免编写复杂的 xml 查询文件,但同时也将 sql 语句耦合到了代码中,也不易实现复杂查询,因此多用于简单 sql 语句的编写。
|
||||||
|
|
||||||
## 4.注解编写sql
|
要使用注解首先将 applicaton.yml 配置文件中的`mapper-locations: classpath:mapper/*.xml`注释掉。然后在 UserDao.java 中加入 sql 注解,代码如下:
|
||||||
|
|
||||||
上面使用的是xml方式编写sql代码,其实mybatis也支持在注解中编写sql,这样可以避免编写复杂的xml查询文件,但同时也将sql语句耦合到了代码中,也不易实现复杂查询,因此多用于简单sql语句的编写。
|
|
||||||
|
|
||||||
要使用注解首先将applicaton.yml配置文件中的`mapper-locations: classpath:mapper/*.xml`注释掉。然后在UserDao.java中加入sql注解,代码如下:
|
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public interface UserDao {
|
public interface UserDao {
|
||||||
@ -360,7 +363,3 @@ public interface UserDao {
|
|||||||
```
|
```
|
||||||
|
|
||||||
然后重新启动项目测试,测试结果跟上面完全一样。
|
然后重新启动项目测试,测试结果跟上面完全一样。
|
||||||
|
|
||||||
```
|
|
||||||
如果对你有帮助记得点赞、收藏哦!
|
|
||||||
```
|
|
@ -1,34 +1,46 @@
|
|||||||
[id]:2018-09-05
|
---
|
||||||
[type]:javaee
|
id="2018-09-05-10-38"
|
||||||
[tag]:java,spring,springboot,activemq
|
title="springboot整合ActiveMQ(1)"
|
||||||
|
headWord="稍大的项目中都会用到mq消息队列,so这个自然也是要学的,为啥是activeMQ呢?刚好目前的项目中用的就是这个拉。"
|
||||||
|
tags=["java", "spring","springboot","消息队列","activeMQ"]
|
||||||
|
category="java"
|
||||||
|
serie="spring boot学习"
|
||||||
|
---
|
||||||
|
|
||||||
|
**说明:acitveMQ 版本为:5.9.1,springboot 版本为 2.0.3,项目地址:[点击跳转](https://github.com/FleyX/demo-project/tree/master/jms_demo)**<br/>
|
||||||
|
|
||||||
**说明:acitveMQ版本为:5.9.1,springboot版本为2.0.3**<br/>
|
|
||||||
## 一. 下载安装(windows)
|
## 一. 下载安装(windows)
|
||||||
  官方下载地址:[点我跳转](http://activemq.apache.org/download-archives.html),选择windows安装包下载,然后解压,解压后运行bin目录下的**activemq.bat**启动服务,无报错即可启动成功。默认管理地址为:[localhost:8161/admin](localhost:8161/admin),默认管理员账号密码为**admin**/**admin**。
|
|
||||||
|
|
||||||
## 二. springboot整合
|
  官方下载地址:[点我跳转](http://activemq.apache.org/download-archives.html),选择 windows 安装包下载,然后解压,解压后运行 bin 目录下的**activemq.bat**启动服务,无报错即可启动成功。默认管理地址为:[localhost:8161/admin](localhost:8161/admin),默认管理员账号密码为**admin**/**admin**。
|
||||||
|
|
||||||
###  1. 创建springboot项目
|
## 二. springboot 整合
|
||||||
|
|
||||||
|
### 1. 创建 springboot 项目
|
||||||
|
|
||||||
|
  创建 springboot web 项目,加入 spring-boot-starter-activemq 依赖。
|
||||||
|
|
||||||
  创建springboot web项目,加入spring-boot-starter-activemq依赖。
|
|
||||||
```xml
|
```xml
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-activemq</artifactId>
|
<artifactId>spring-boot-starter-activemq</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
```
|
```
|
||||||
  然后编辑配合文件,加上一个配置:61616为activeMQ的默认端口,暂时不做其他配置,使用默认值。
|
|
||||||
|
  然后编辑配合文件,加上一个配置:61616 为 activeMQ 的默认端口,暂时不做其他配置,使用默认值。
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
spring:
|
spring:
|
||||||
activemq:
|
activemq:
|
||||||
broker-url: tcp://localhost:61616
|
broker-url: tcp://localhost:61616
|
||||||
```
|
```
|
||||||
###  2. 创建生产者消费者
|
|
||||||
  springboot中activeMQ的默认配置为**生产-消费者模式**,还有一种模式为**发布-订阅模式**后面再讲。项目目录如下:
|
|
||||||
![项目目录](./picFolder/pic1.png)
|
|
||||||
|
|
||||||
  首先编写配置类Config.java,代码如下
|
### 2. 创建生产者消费者
|
||||||
|
|
||||||
|
  springboot 中 activeMQ 的默认配置为**生产-消费者模式**,还有一种模式为**发布-订阅模式**后面再讲。项目目录如下:
|
||||||
|
![项目目录](./picFolder/pic1.png)
|
||||||
|
|
||||||
|
  首先编写配置类 Config.java,代码如下
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@Configuration
|
@Configuration
|
||||||
public class Config {
|
public class Config {
|
||||||
@ -43,10 +55,12 @@ public class Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
上面的代码建立了两个消息队列queue1,queue2,分别由queue1和queue2这两个Bean注入到Spring容器中。程序运行后会在activeMQ的管理页面->queue中看到如下:
|
|
||||||
|
上面的代码建立了两个消息队列 queue1,queue2,分别由 queue1 和 queue2 这两个 Bean 注入到 Spring 容器中。程序运行后会在 activeMQ 的管理页面->queue 中看到如下:
|
||||||
![队列](./picFolder/pic2.png)
|
![队列](./picFolder/pic2.png)
|
||||||
|
|
||||||
  生产者Producer.java代码如下:
|
  生产者 Producer.java 代码如下:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@RestController
|
@RestController
|
||||||
public class Producer {
|
public class Producer {
|
||||||
@ -70,9 +84,11 @@ public class Producer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
上面的类创建了两个GET接口,访问这两个接口分别向queue1和queue2中发送消息。
|
|
||||||
|
|
||||||
消费者Comsumer.java代码如下:
|
上面的类创建了两个 GET 接口,访问这两个接口分别向 queue1 和 queue2 中发送消息。
|
||||||
|
|
||||||
|
消费者 Comsumer.java 代码如下:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@Component //将该类注解到Spring 容器中
|
@Component //将该类注解到Spring 容器中
|
||||||
public class Comsumer {
|
public class Comsumer {
|
||||||
@ -101,17 +117,20 @@ public class Comsumer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
上面的代码定义了4个消费者,每两个消费一个消息队列。
|
|
||||||
|
|
||||||
##  3. 运行
|
上面的代码定义了 4 个消费者,每两个消费一个消息队列。
|
||||||
|
|
||||||
|
### 3. 运行
|
||||||
|
|
||||||
|
  启动项目后分别向/queue1?message=niihao,/queue2?message=nihaoa 发送 http 请求,然后我们可以在控制台中看到如下输出:
|
||||||
|
|
||||||
  启动项目后分别向/queue1?message=niihao,/queue2?message=nihaoa发送http请求,然后我们可以在控制台中看到如下输出:
|
|
||||||
```
|
```
|
||||||
2I'm from queue2:nihaoa
|
2I'm from queue2:nihaoa
|
||||||
1I'm from queue2:nihaoa
|
1I'm from queue2:nihaoa
|
||||||
2I'm from queue1:nihao
|
2I'm from queue1:nihao
|
||||||
1I'm from queue1:nihao
|
1I'm from queue1:nihao
|
||||||
```
|
```
|
||||||
|
|
||||||
消息都成功被消费者消费,从打印结果也可看出生产者消费者的一个特点:一个消息只会被一个消费者消费。同时在管理页面中可以看到:
|
消息都成功被消费者消费,从打印结果也可看出生产者消费者的一个特点:一个消息只会被一个消费者消费。同时在管理页面中可以看到:
|
||||||
![运行结果](./picFolder/pic3.png)
|
![运行结果](./picFolder/pic3.png)
|
||||||
每个消息队列有两个消费者,队列进入了三个消息,出了三个消息,说明消息都被消费掉了,如果注释掉消费者代码,再次运行,然后发送消息就会发现MessagesEnqueued数量大于MessagesDequeued,然后再让消费者上线会立即消费掉队列中的消息。
|
每个消息队列有两个消费者,队列进入了三个消息,出了三个消息,说明消息都被消费掉了,如果注释掉消费者代码,再次运行,然后发送消息就会发现 MessagesEnqueued 数量大于 MessagesDequeued,然后再让消费者上线会立即消费掉队列中的消息。
|
||||||
|
@ -1,3 +1,11 @@
|
|||||||
|
---
|
||||||
|
id="2018-09-06-10-38"
|
||||||
|
title="springboot整合ActiveMQ(2)"
|
||||||
|
headWord="接着上文来说,这里来说如何实现activemq的主从备份"
|
||||||
|
tags=["java", "spring","springboot","消息队列","activeMQ"]
|
||||||
|
category="java"
|
||||||
|
serie="spring boot学习"
|
||||||
|
---
|
||||||
[id]:2018-09-06
|
[id]:2018-09-06
|
||||||
[type]:javaee
|
[type]:javaee
|
||||||
[tag]:java,spring,activemq
|
[tag]:java,spring,activemq
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
[id]:2018-09-10
|
---
|
||||||
[type]:javaee
|
id="2018-09-10-10-38"
|
||||||
[tag]:java,spring,springboot,mybatis,读写分离
|
title="springboot配置读写分离(Mybatis)"
|
||||||
|
headWord="近日工作任务较轻,有空学习学习技术,遂来研究如果实现读写分离。这里用博客记录下过程,一方面可备日后查看,同时也能分享给大家(网上的资料真的大都是抄来抄去,,还不带格式的,看的真心难受)。"
|
||||||
|
tags=["java", "spring","springboot","mysql","主从备份","读写分离"]
|
||||||
|
category="java"
|
||||||
|
serie="spring boot学习"
|
||||||
|
---
|
||||||
|
|
||||||
  近日工作任务较轻,有空学习学习技术,遂来研究如果实现读写分离。这里用博客记录下过程,一方面可备日后查看,同时也能分享给大家(网上的资料真的大都是抄来抄去,,还不带格式的,看的真心难受)。
|
  近日工作任务较轻,有空学习学习技术,遂来研究如果实现读写分离。这里用博客记录下过程,一方面可备日后查看,同时也能分享给大家(网上的资料真的大都是抄来抄去,,还不带格式的,看的真心难受)。
|
||||||
|
|
||||||
@ -10,23 +15,23 @@
|
|||||||
|
|
||||||
  一个项目中数据库最基础同时也是最主流的是单机数据库,读写都在一个库中。当用户逐渐增多,单机数据库无法满足性能要求时,就会进行读写分离改造(适用于读多写少),写操作一个库,读操作多个库,通常会做一个数据库集群,开启主从备份,一主多从,以提高读取性能。当用户更多读写分离也无法满足时,就需要分布式数据库了(可能以后会学习怎么弄)。
|
  一个项目中数据库最基础同时也是最主流的是单机数据库,读写都在一个库中。当用户逐渐增多,单机数据库无法满足性能要求时,就会进行读写分离改造(适用于读多写少),写操作一个库,读操作多个库,通常会做一个数据库集群,开启主从备份,一主多从,以提高读取性能。当用户更多读写分离也无法满足时,就需要分布式数据库了(可能以后会学习怎么弄)。
|
||||||
|
|
||||||
  正常情况下读写分离的实现,首先要做一个一主多从的数据库集群,同时还需要进行数据同步。这一篇记录如何用mysql搭建一个一主多次的配置,下一篇记录代码层面如何实现读写分离。
|
  正常情况下读写分离的实现,首先要做一个一主多从的数据库集群,同时还需要进行数据同步。这一篇记录如何用 mysql 搭建一个一主多次的配置,下一篇记录代码层面如何实现读写分离。
|
||||||
|
|
||||||
## 2、搭建一主多从数据库集群
|
## 2、搭建一主多从数据库集群
|
||||||
|
|
||||||
  主从备份需要多台虚拟机,我是用wmware完整克隆多个实例,注意直接克隆的虚拟机会导致每个数据库的uuid相同,需要修改为不同的uuid。修改方法参考这个:[点击跳转](https://blog.csdn.net/pratise/article/details/80413198)。
|
  主从备份需要多台虚拟机,我是用 wmware 完整克隆多个实例,注意直接克隆的虚拟机会导致每个数据库的 uuid 相同,需要修改为不同的 uuid。修改方法参考这个:[点击跳转](https://blog.csdn.net/pratise/article/details/80413198)。
|
||||||
|
|
||||||
- 主库配置
|
- 主库配置
|
||||||
|
|
||||||
主数据库(master)中新建一个用户用于从数据库(slave)读取主数据库二进制日志,sql语句如下:
|
主数据库(master)中新建一个用户用于从数据库(slave)读取主数据库二进制日志,sql 语句如下:
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
mysql> CREATE USER 'repl'@'%' IDENTIFIED BY '123456';#创建用户
|
mysql> CREATE USER 'repl'@'%' IDENTIFIED BY '123456';#创建用户
|
||||||
mysql> GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';#分配权限
|
mysql> GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';#分配权限
|
||||||
mysql>flush privileges; #刷新权限
|
mysql>flush privileges; #刷新权限
|
||||||
```
|
```
|
||||||
|
|
||||||
同时修改mysql配置文件开启二进制日志,新增部分如下:
|
同时修改 mysql 配置文件开启二进制日志,新增部分如下:
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
[mysqld]
|
[mysqld]
|
||||||
@ -61,7 +66,7 @@
|
|||||||
MASTER_LOG_POS=154;
|
MASTER_LOG_POS=154;
|
||||||
```
|
```
|
||||||
|
|
||||||
接着运行`start slave;`开启备份,正常情况如下图所示:Slave_IO_Running和Slave_SQL_Running都为yes。
|
接着运行`start slave;`开启备份,正常情况如下图所示:Slave_IO_Running 和 Slave_SQL_Running 都为 yes。
|
||||||
|
|
||||||
![1536223020742](./picFolder/pic2.png)
|
![1536223020742](./picFolder/pic2.png)
|
||||||
|
|
||||||
@ -81,21 +86,21 @@ binlog-do-db = game
|
|||||||
|
|
||||||
## 3、代码层面进行读写分离
|
## 3、代码层面进行读写分离
|
||||||
|
|
||||||
  代码环境是springboot+mybatis+druib连接池。想要读写分离就需要配置多个数据源,在进行写操作是选择写的数据源,读操作时选择读的数据源。其中有两个关键点:
|
  代码环境是 springboot+mybatis+druib 连接池。想要读写分离就需要配置多个数据源,在进行写操作是选择写的数据源,读操作时选择读的数据源。其中有两个关键点:
|
||||||
|
|
||||||
- 如何切换数据源
|
- 如何切换数据源
|
||||||
- 如何根据不同的方法选择正确的数据源
|
- 如何根据不同的方法选择正确的数据源
|
||||||
|
|
||||||
### 1)、如何切换数据源
|
### 1)、如何切换数据源
|
||||||
|
|
||||||
  通常用springboot时都是使用它的默认配置,只需要在配置文件中定义好连接属性就行了,但是现在我们需要自己来配置了,spring是支持多数据源的,多个datasource放在一个HashMap`TargetDataSource`中,通过`dertermineCurrentLookupKey`获取key来觉定要使用哪个数据源。因此我们的目标就很明确了,建立多个datasource放到TargetDataSource中,同时重写dertermineCurrentLookupKey方法来决定使用哪个key。
|
  通常用 springboot 时都是使用它的默认配置,只需要在配置文件中定义好连接属性就行了,但是现在我们需要自己来配置了,spring 是支持多数据源的,多个 datasource 放在一个 HashMap`TargetDataSource`中,通过`dertermineCurrentLookupKey`获取 key 来觉定要使用哪个数据源。因此我们的目标就很明确了,建立多个 datasource 放到 TargetDataSource 中,同时重写 dertermineCurrentLookupKey 方法来决定使用哪个 key。
|
||||||
|
|
||||||
### 2)、如何选择数据源
|
### 2)、如何选择数据源
|
||||||
|
|
||||||
  事务一般是注解在Service层的,因此在开始这个service方法调用时要确定数据源,有什么通用方法能够在开始执行一个方法前做操作呢?相信你已经想到了那就是**切面 **。怎么切有两种办法:
|
  事务一般是注解在 Service 层的,因此在开始这个 service 方法调用时要确定数据源,有什么通用方法能够在开始执行一个方法前做操作呢?相信你已经想到了那就是**切面 **。怎么切有两种办法:
|
||||||
|
|
||||||
- 注解式,定义一个只读注解,被该数据标注的方法使用读库
|
- 注解式,定义一个只读注解,被该数据标注的方法使用读库
|
||||||
- 方法名,根据方法名写切点,比如getXXX用读库,setXXX用写库
|
- 方法名,根据方法名写切点,比如 getXXX 用读库,setXXX 用写库
|
||||||
|
|
||||||
### 3)、代码编写
|
### 3)、代码编写
|
||||||
|
|
||||||
@ -123,9 +128,9 @@ mysql:
|
|||||||
driver-class-name: com.mysql.jdbc.Driver
|
driver-class-name: com.mysql.jdbc.Driver
|
||||||
```
|
```
|
||||||
|
|
||||||
#### b、编写DbContextHolder类
|
#### b、编写 DbContextHolder 类
|
||||||
|
|
||||||
  这个类用来设置数据库类别,其中有一个ThreadLocal用来保存每个线程的是使用读库,还是写库。代码如下:
|
  这个类用来设置数据库类别,其中有一个 ThreadLocal 用来保存每个线程的是使用读库,还是写库。代码如下:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
/**
|
/**
|
||||||
@ -162,9 +167,9 @@ public class DbContextHolder {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### c、重写determineCurrentLookupKey方法
|
#### c、重写 determineCurrentLookupKey 方法
|
||||||
|
|
||||||
  spring在开始进行数据库操作时会通过这个方法来决定使用哪个数据库,因此我们在这里调用上面DbContextHolder类的`getDbType()`方法获取当前操作类别,同时可进行读库的负载均衡,代码如下:
|
  spring 在开始进行数据库操作时会通过这个方法来决定使用哪个数据库,因此我们在这里调用上面 DbContextHolder 类的`getDbType()`方法获取当前操作类别,同时可进行读库的负载均衡,代码如下:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public class MyAbstractRoutingDataSource extends AbstractRoutingDataSource {
|
public class MyAbstractRoutingDataSource extends AbstractRoutingDataSource {
|
||||||
@ -191,7 +196,7 @@ public class MyAbstractRoutingDataSource extends AbstractRoutingDataSource {
|
|||||||
|
|
||||||
#### d、编写配置类
|
#### d、编写配置类
|
||||||
|
|
||||||
  由于要进行读写分离,不能再用springboot的默认配置,我们需要手动来进行配置。首先生成数据源,使用@ConfigurProperties自动生成数据源:
|
  由于要进行读写分离,不能再用 springboot 的默认配置,我们需要手动来进行配置。首先生成数据源,使用@ConfigurProperties 自动生成数据源:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
/**
|
/**
|
||||||
@ -208,9 +213,9 @@ public class MyAbstractRoutingDataSource extends AbstractRoutingDataSource {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
读数据源类似,注意有多少个读库就要设置多少个读数据源,Bean名为read+序号。
|
读数据源类似,注意有多少个读库就要设置多少个读数据源,Bean 名为 read+序号。
|
||||||
|
|
||||||
  然后设置数据源,使用的是我们之前写的MyAbstractRoutingDataSource类
|
  然后设置数据源,使用的是我们之前写的 MyAbstractRoutingDataSource 类
|
||||||
|
|
||||||
```java
|
```java
|
||||||
/**
|
/**
|
||||||
@ -228,7 +233,7 @@ public class MyAbstractRoutingDataSource extends AbstractRoutingDataSource {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
  接着需要设置sqlSessionFactory
|
  接着需要设置 sqlSessionFactory
|
||||||
|
|
||||||
```java
|
```java
|
||||||
/**
|
/**
|
||||||
@ -266,7 +271,7 @@ public class MyAbstractRoutingDataSource extends AbstractRoutingDataSource {
|
|||||||
|
|
||||||
#### a、注解式
|
#### a、注解式
|
||||||
|
|
||||||
  首先定义一个只读注解,被这个注解方法使用读库,其他使用写库,如果项目是中途改造成读写分离可使用这个方法,无需修改业务代码,只要在只读的service方法上加一个注解即可。
|
  首先定义一个只读注解,被这个注解方法使用读库,其他使用写库,如果项目是中途改造成读写分离可使用这个方法,无需修改业务代码,只要在只读的 service 方法上加一个注解即可。
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@Target({ElementType.METHOD,ElementType.TYPE})
|
@Target({ElementType.METHOD,ElementType.TYPE})
|
||||||
@ -275,7 +280,7 @@ public @interface ReadOnly {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
  然后写一个切面来切换数据使用哪种数据源,重写getOrder保证本切面优先级高于事务切面优先级,在启动类加上`@EnableTransactionManagement(order = 10) `,为了代码如下:
|
  然后写一个切面来切换数据使用哪种数据源,重写 getOrder 保证本切面优先级高于事务切面优先级,在启动类加上`@EnableTransactionManagement(order = 10)`,为了代码如下:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@Aspect
|
@Aspect
|
||||||
@ -312,4 +317,4 @@ public class ReadOnlyInterceptor implements Ordered {
|
|||||||
|
|
||||||
![1536312274474](./picFolder/pic3.png)
|
![1536312274474](./picFolder/pic3.png)
|
||||||
|
|
||||||
  断断续续写了好几天终于是写完了,,,如果有帮助到你,,欢迎star哦,,这里是完整代码地址:[点击跳转](https://github.com/FleyX/demo-project/tree/master/dxfl)
|
  断断续续写了好几天终于是写完了,,,如果有帮助到你,,欢迎 star 哦,,这里是完整代码地址:[点击跳转](https://github.com/FleyX/demo-project/tree/master/dxfl)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
id="2018-11-19-15-57-00"
|
id="2018-11-19-15-57-00"
|
||||||
title="springCloud之config"
|
title="springCloud学习1(集中式配置管理)"
|
||||||
headWord="本篇主要用于记录如何在spring cloud中将服务配置与服务代码分离开来,通过向集中的配置服务请求获取某个微服务需要的配置。同时如何对敏感信息进行加密,比如密码一类的配置项"
|
headWord="本篇主要用于记录如何在spring cloud中将服务配置与服务代码分离开来,通过向集中的配置服务请求获取某个微服务需要的配置。"
|
||||||
tags=["spring-boot", "spring-cloud-config","git"]
|
tags=["spring-boot", "spring-cloud-config","git"]
|
||||||
category="java"
|
category="java"
|
||||||
serie="springCloud实战"
|
serie="springCloud实战"
|
||||||
@ -280,4 +280,4 @@ spring:
|
|||||||
## 总结
|
## 总结
|
||||||
|
|
||||||
  本篇只是用到了 spring-cloud-config 这个来进行配置集中管理,并没有涉及到微服务,在下一篇将开始微服务的学习。
|
  本篇只是用到了 spring-cloud-config 这个来进行配置集中管理,并没有涉及到微服务,在下一篇将开始微服务的学习。
|
||||||
  本篇两个项目代码存放于:[记得补充啊]()
|
  本篇两个项目代码存放于:[点击跳转](https://github.com/FleyX/demo-project/tree/master/springcloud/%E7%AC%AC%E4%B8%80%E7%AF%87%E6%89%80%E7%94%A8%E4%BB%A3%E7%A0%81)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
id="2018-11-22-15-57"
|
id="2018-11-22-15-57"
|
||||||
title="springCloud之服务发现"
|
title="springCloud学习2(服务发现)"
|
||||||
headWord="在任何分布式架构中,都需要找到机器所在的物理地址,这个概念自分布式计算开始就已经存在,并且被正式称为服务发现,本篇是对服务发现的一个学习总结"
|
headWord="在任何分布式架构中,都需要找到机器所在的物理地址,这个概念自分布式计算开始就已经存在,并且被正式称为服务发现,本篇是对服务发现的一个学习总结"
|
||||||
tags=["spring-boot", "spring-cloud-config","git"]
|
tags=["spring-boot", "spring-cloud-config","git"]
|
||||||
category="java"
|
category="java"
|
||||||
@ -437,4 +437,4 @@ public Licensing getLicensingByFeign(@PathVariable("orgId") String orgId) {
|
|||||||
|
|
||||||
# 总结
|
# 总结
|
||||||
|
|
||||||
  这一节磨磨蹭蹭写了好几天,虽然例子很简单,但是相信应该是能够看懂的。由于篇幅原因代码没有全部贴上,想要查看完整代码,可以访问这个链接:[记得补全啊]()。
|
  这一节磨磨蹭蹭写了好几天,虽然例子很简单,但是相信应该是能够看懂的。由于篇幅原因代码没有全部贴上,想要查看完整代码,可以访问这个链接:[点击跳转](https://github.com/FleyX/demo-project/tree/master/springcloud/%E7%AC%AC%E4%BA%8C%E7%AF%87%E4%BB%A3%E7%A0%81)。
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
---
|
---
|
||||||
id="2018-11-28-15-57-00"
|
id="2018-11-28-15-57-00"
|
||||||
title="springCloud之Netflix Hystrix弹性客户端"
|
title="springCloud学习3(Netflix Hystrix弹性客户端)"
|
||||||
headWord="在任何分布式架构中,都需要找到机器所在的物理地址,这个概念自分布式计算开始就已经存在,并且被正式称为服务发现,本篇是对服务发现的一个学习总结"
|
headWord="在任何分布式架构中,都需要找到机器所在的物理地址,这个概念自分布式计算开始就已经存在,并且被正式称为服务发现,本篇是对服务发现的一个学习总结"
|
||||||
tags=["spring-boot", "spring-cloud","netflix-hystrix"]
|
tags=["spring-boot", "spring-cloud","netflix-hystrix","熔断"]
|
||||||
category="java"
|
category="java"
|
||||||
serie="springCloud实战"
|
serie="springCloud实战"
|
||||||
---
|
---
|
||||||
@ -170,22 +170,24 @@ public class OrganizationFeignClientImpl implements OrganizationFeignClient{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
然后修改OrganizationFeignClient接口的注解,将`@FeignClient("organizationservice")`改为`@FeignClient(name="organizationservice",fallback = OrganizationFeignClientImpl.class`。
|
|
||||||
|
然后修改 OrganizationFeignClient 接口的注解,将`@FeignClient("organizationservice")`改为`@FeignClient(name="organizationservice",fallback = OrganizationFeignClientImpl.class`。
|
||||||
|
|
||||||
  重启项目,多次访问[localhost:10011/licensingByFeign/11313/](localhost:10011/licensingByFeign/11313/),可发现后备服务起作用了。
|
  重启项目,多次访问[localhost:10011/licensingByFeign/11313/](localhost:10011/licensingByFeign/11313/),可发现后备服务起作用了。
|
||||||
|
|
||||||
  在确认是否要启用后备服务时,要注意以下两点:
|
  在确认是否要启用后备服务时,要注意以下两点:
|
||||||
- 后备是一种在资源操时或失败时提供行动方案的机制。如果只是用后备来捕获操时异常然后只做日志记录,那只需要try..catch即可,捕获HystrixRuntimeException异常。
|
|
||||||
|
|
||||||
- 注意后备方法所执行的操作。如果在后备服务中调用另一个分布式服务,需要注意用@HystrixCommand方法注解包装后备方法。
|
- 后备是一种在资源操时或失败时提供行动方案的机制。如果只是用后备来捕获操时异常然后只做日志记录,那只需要 try..catch 即可,捕获 HystrixRuntimeException 异常。
|
||||||
|
|
||||||
|
- 注意后备方法所执行的操作。如果在后备服务中调用另一个分布式服务,需要注意用@HystrixCommand 方法注解包装后备方法。
|
||||||
|
|
||||||
## 4、实现舱壁模式
|
## 4、实现舱壁模式
|
||||||
|
|
||||||
  在基于微服务的应用程序中,通常需要调用多个微服务来完成特定的任务,在不适用舱壁的模式下,这些调用默认是使用同一批线程来执行调用的,而这些线程是为了处理整个Java容器的请求而预留的。因此在存在大量请求的情况下,一个服务出现性能问题会导致Java容器内的所有线程被占用,同时阻塞新请求,最终容器彻底崩溃。
|
  在基于微服务的应用程序中,通常需要调用多个微服务来完成特定的任务,在不适用舱壁的模式下,这些调用默认是使用同一批线程来执行调用的,而这些线程是为了处理整个 Java 容器的请求而预留的。因此在存在大量请求的情况下,一个服务出现性能问题会导致 Java 容器内的所有线程被占用,同时阻塞新请求,最终容器彻底崩溃。
|
||||||
|
|
||||||
  Hystrix使用线程池来委派所有对远程服务的调用,默认情况下这个线程池有10个工作线程。但是这样很容易出现一个运行缓慢的服务占用全部的线程,所有hystrix提供了一种一种易于使用的机制,在不同的远程资源调用间创建‘舱壁’,将不同服务的调用隔离到不同的线程池中,使之互不影响。
|
  Hystrix 使用线程池来委派所有对远程服务的调用,默认情况下这个线程池有 10 个工作线程。但是这样很容易出现一个运行缓慢的服务占用全部的线程,所有 hystrix 提供了一种一种易于使用的机制,在不同的远程资源调用间创建‘舱壁’,将不同服务的调用隔离到不同的线程池中,使之互不影响。
|
||||||
|
|
||||||
  要实现隔离的线程池,只需要在`@HystrixCommand`上加入线程池的注解,这里以ribbon为例(Feign类似)。修改licensingservice中service包下的OrganizaitonByRibbonService类,将`getOrganizationWithRibbon`方法的注解改为如下:
|
  要实现隔离的线程池,只需要在`@HystrixCommand`上加入线程池的注解,这里以 ribbon 为例(Feign 类似)。修改 licensingservice 中 service 包下的 OrganizaitonByRibbonService 类,将`getOrganizationWithRibbon`方法的注解改为如下:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@HystrixCommand(commandProperties = {
|
@HystrixCommand(commandProperties = {
|
||||||
@ -197,8 +199,9 @@ public class OrganizationFeignClientImpl implements OrganizationFeignClient{
|
|||||||
@HystrixProperty(name = "maxQueueSize", value = "10")
|
@HystrixProperty(name = "maxQueueSize", value = "10")
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
如果将`maxQueueSize`属性值设为-1,将使用`SynchronousQueue`保存所有的传入请求,同步队列会强制要求正在处理中的请求数量永远不能超过线程池的大小。设为大于1的值将使用`LinkedBlockingQueue`。
|
|
||||||
|
|
||||||
  **注意**:示例代码中都是硬编码属性值到Hystrix注解中的。在实际应用环境中,一般都是将配置项配置在Spring Cloud Config中的,方便统一管理。
|
如果将`maxQueueSize`属性值设为-1,将使用`SynchronousQueue`保存所有的传入请求,同步队列会强制要求正在处理中的请求数量永远不能超过线程池的大小。设为大于 1 的值将使用`LinkedBlockingQueue`。
|
||||||
|
|
||||||
本次用到全部代码:[记得补全代码呀]()
|
  **注意**:示例代码中都是硬编码属性值到 Hystrix 注解中的。在实际应用环境中,一般都是将配置项配置在 Spring Cloud Config 中的,方便统一管理。
|
||||||
|
|
||||||
|
本次用到全部代码:[点击跳转](https://github.com/FleyX/demo-project/tree/master/springcloud/%E7%AC%AC%E4%B8%89%E7%AF%87%E6%89%80%E7%94%A8%E4%BB%A3%E7%A0%81)
|
||||||
|
@ -1,22 +1,27 @@
|
|||||||
[id]:2018-09-22
|
---
|
||||||
[type]:java
|
id="2018-09-22-15-57-00"
|
||||||
[tag]:java,reflect,excel,hssfworksheet
|
title="java导出EXCEL文件"
|
||||||
|
headWord="最近在java上做了一个EXCEL的导出功能,写了一个通用类,在这里分享分享,该类支持多sheet,且无需手动进行复杂的类型转换."
|
||||||
|
tags=["reflex", "java","excel","hssfworksheet"]
|
||||||
|
category="java"
|
||||||
|
serie="java工具集"
|
||||||
|
---
|
||||||
|
|
||||||
## 一、背景
|
## 一、背景
|
||||||
|
|
||||||
  最近在java上做了一个EXCEL的导出功能,写了一个通用类,在这里分享分享,该类支持多sheet,且无需手动进行复杂的类型转换,只需提供三个参数即可:
|
  最近在 java 上做了一个 EXCEL 的导出功能,写了一个通用类,在这里分享分享,该类支持多 sheet,且无需手动进行复杂的类型转换,只需提供三个参数即可:
|
||||||
|
|
||||||
- `fileName`
|
- `fileName`
|
||||||
|
|
||||||
excel文件名
|
excel 文件名
|
||||||
|
|
||||||
- `HasMap<String,List<?>> data`
|
- `HasMap<String,List<?>> data`
|
||||||
|
|
||||||
具体的数据,每个List代表一张表的数据,?表示可为任意的自定义对象
|
具体的数据,每个 List 代表一张表的数据,?表示可为任意的自定义对象
|
||||||
|
|
||||||
- `LinkedHashMap<String,String[][]> headers`
|
- `LinkedHashMap<String,String[][]> headers`
|
||||||
|
|
||||||
`Stirng`代表sheet名。每个`String[][] `代表一个sheet的定义,举个例子如下:
|
`Stirng`代表 sheet 名。每个`String[][]`代表一个 sheet 的定义,举个例子如下:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
String[][] header = {
|
String[][] header = {
|
||||||
@ -26,11 +31,11 @@
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
其中的field1,field2,field3为对象中的属性名,参数1,参数2,参数3为列名,实际上这个指定了列的名称和这个列用到数据对象的哪个属性。
|
其中的 field1,field2,field3 为对象中的属性名,参数 1,参数 2,参数 3 为列名,实际上这个指定了列的名称和这个列用到数据对象的哪个属性。
|
||||||
|
|
||||||
## 二、怎么用
|
## 二、怎么用
|
||||||
|
|
||||||
  以一个例子来说明怎么用,假设有两个类A和B定义如下:
|
  以一个例子来说明怎么用,假设有两个类 A 和 B 定义如下:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public class A{
|
public class A{
|
||||||
@ -44,14 +49,14 @@ public class B{
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
现在我们通过查询数据库获得了A和B的两个列表:
|
现在我们通过查询数据库获得了 A 和 B 的两个列表:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
List<A> dataA = .....;
|
List<A> dataA = .....;
|
||||||
List<B> dataB = .....;
|
List<B> dataB = .....;
|
||||||
```
|
```
|
||||||
|
|
||||||
我们将这两个导出到excel中,首先需要定义sheet:
|
我们将这两个导出到 excel 中,首先需要定义 sheet:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
String[][] sheetA = {
|
String[][] sheetA = {
|
||||||
@ -65,7 +70,7 @@ String[][] sheetB = {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
然后将数据汇总构造一个ExcelUtil:
|
然后将数据汇总构造一个 ExcelUtil:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
String fileName = "测试Excel";
|
String fileName = "测试Excel";
|
||||||
@ -89,7 +94,7 @@ workbook.writeToResponse(workbook,request,response);
|
|||||||
|
|
||||||
  这里简单说明下实现过程,从调用`createExcel()`这里开始
|
  这里简单说明下实现过程,从调用`createExcel()`这里开始
|
||||||
|
|
||||||
####1、遍历headers创建sheet
|
#### 1、遍历 headers 创建 sheet
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public HSSFWorkbook createExcel() throws Exception {
|
public HSSFWorkbook createExcel() throws Exception {
|
||||||
@ -107,7 +112,7 @@ workbook.writeToResponse(workbook,request,response);
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
将workbook,sheet名,表头数据,行数据传入crateSheet方法中创建sheet。
|
将 workbook,sheet 名,表头数据,行数据传入 crateSheet 方法中创建 sheet。
|
||||||
|
|
||||||
#### 2、创建表头
|
#### 2、创建表头
|
||||||
|
|
||||||
@ -136,7 +141,7 @@ workbook.writeToResponse(workbook,request,response);
|
|||||||
|
|
||||||
#### 3、插入行数据
|
#### 3、插入行数据
|
||||||
|
|
||||||
  这里是最重要的部分,首先通过数据的类对象获取它的反射属性Field类,然后将属性名和Field做一个hash映射,避免循环查找,提高插入速度,接着通过一个switch语句,根据属性类别设值,主要代码如下:
|
  这里是最重要的部分,首先通过数据的类对象获取它的反射属性 Field 类,然后将属性名和 Field 做一个 hash 映射,避免循环查找,提高插入速度,接着通过一个 switch 语句,根据属性类别设值,主要代码如下:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
/**
|
/**
|
||||||
@ -176,6 +181,7 @@ private void setCell(HSSFCell cell, Object obj, Map<String, Field> fieldMap, Str
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
完整代码可以到github上查看下载,这里就不列出来了。
|
|
||||||
|
|
||||||
github地址:[点击跳转]()
|
完整代码可以到 github 上查看下载,这里就不列出来了。
|
||||||
|
|
||||||
|
github 地址:[点击跳转]()
|
||||||
|
@ -1 +1,8 @@
|
|||||||
{"2018-11-22-15-57-00":14,"2018-11-20-10-38-05":18,"2018-11-19-15-57-00":3,"2018-10-22-10-38-05":14,"2018-10-21-10-38-05":3,"2018-10-20-10-38-05":20}
|
{
|
||||||
|
"2018-11-22-15-57-00": 14,
|
||||||
|
"2018-11-20-10-38-05": 18,
|
||||||
|
"2018-11-19-15-57-00": 3,
|
||||||
|
"2018-10-22-10-38-05": 14,
|
||||||
|
"2018-10-21-10-38-05": 3,
|
||||||
|
"2018-10-20-10-38-05": 20
|
||||||
|
}
|
@ -4,7 +4,7 @@ title="vscode开发调试typescript"
|
|||||||
headWord="本篇用于记录如何在vscode下开发typescript以及端点调试ts代码"
|
headWord="本篇用于记录如何在vscode下开发typescript以及端点调试ts代码"
|
||||||
tags=["vscode", "node","typescript","ts"]
|
tags=["vscode", "node","typescript","ts"]
|
||||||
category="node"
|
category="node"
|
||||||
serie="node开发环境配置"
|
serie="node开发配置"
|
||||||
---
|
---
|
||||||
|
|
||||||
### 1、安装 typescript
|
### 1、安装 typescript
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
[id]:2018-10-01
|
---
|
||||||
[type]:项目
|
id="2018-10-01-13-58"
|
||||||
[tag]:node,vue,element-ui,axios,koa,redis,mysql
|
title="node,vue开发教学管理系统"
|
||||||
|
headWord="毕业才刚刚两个多月而已,现在想想大学生活是已经是那么的遥不可及,感觉已经过了好久好久,社会了两个月才明白学校的好啊。。。"
|
||||||
|
tags=["node", "vue","element-ui","axios","koa","redis","mysql","jwt"]
|
||||||
|
category="node"
|
||||||
|
serie="项目"
|
||||||
|
---
|
||||||
|
|
||||||
  毕业才刚刚两个多月而已,现在想想大学生活是那么的遥不可及,感觉已经过了好久好久,社会了两个月才明白学校的好啊。。。额,扯远了,自从毕业开始就想找个时间写下毕设的记录总结,结果找了好久好久到今天才开始动笔。
|
  毕业才刚刚两个多月而已,现在想想大学生活是已经是那么的遥不可及,感觉已经过了好久好久,社会了两个月才明白学校的好啊。。。额,扯远了,自从毕业开始就想找个时间写下毕设的记录总结,结果找了好久好久到今天才开始动笔。
|
||||||
|
|
||||||
  我的毕业设计题目是:教学辅助系统的设计与实现,,是不是很俗。。。至于为啥是这个题目呢,完全是被导师坑了。。。。。
|
  我的毕业设计题目是:教学辅助系统的设计与实现,,是不是很俗。。。至于为啥是这个题目呢,完全是被导师坑了。。。。。
|
||||||
|
|
||||||
@ -35,24 +40,24 @@
|
|||||||
|
|
||||||
## 2、架构选择
|
## 2、架构选择
|
||||||
|
|
||||||
  大三的时候了解到Node.js这个比较“奇葩"的异步语言,再加上在公司实习了三个月也是用的node开发,对node已经比较熟悉了,于是就用它做了后台,前端用最近比较火的vue.js做单页应用。当时还想着负载均衡啥的,就没有用传统的session,cookie机制,转而用jwt做的基于token的身份认证,同时后台接口也是类Restful风格的(因为纯正的Rest接口太难设计了)。
|
  大三的时候了解到 Node.js 这个比较“奇葩"的异步语言,再加上在公司实习了三个月也是用的 node 开发,对 node 已经比较熟悉了,于是就用它做了后台,前端用最近比较火的 vue.js 做单页应用。当时还想着负载均衡啥的,就没有用传统的 session,cookie 机制,转而用 jwt 做的基于 token 的身份认证,同时后台接口也是类 Restful 风格的(因为纯正的 Rest 接口太难设计了)。
|
||||||
|
|
||||||
总的来说后台用了以下技术和框架:
|
总的来说后台用了以下技术和框架:
|
||||||
|
|
||||||
  总的来说后台用了以下技术和框架:
|
  总的来说后台用了以下技术和框架:
|
||||||
|
|
||||||
- 语言:Node.js
|
- 语言:Node.js
|
||||||
- web框架:kOA
|
- web 框架:kOA
|
||||||
- 前后台传输协议:jwt
|
- 前后台传输协议:jwt
|
||||||
- 缓存:redis
|
- 缓存:redis
|
||||||
- 数据库:mysql
|
- 数据库:mysql
|
||||||
- 编程题判题核心:[青岛大学OJ判题核心](https://github.com/QingdaoU/JudgeServer)
|
- 编程题判题核心:[青岛大学 OJ 判题核心](https://github.com/QingdaoU/JudgeServer)
|
||||||
- 代码判重:[SIM](https://dickgrune.com/Programs/similarity_tester/)
|
- 代码判重:[SIM](https://dickgrune.com/Programs/similarity_tester/)
|
||||||
|
|
||||||
前台技术如下:
|
前台技术如下:
|
||||||
|
|
||||||
- 框架:Vue.js
|
- 框架:Vue.js
|
||||||
- UI框架:Element-UI
|
- UI 框架:Element-UI
|
||||||
- 图表组件:G2
|
- 图表组件:G2
|
||||||
|
|
||||||
## 3、系统基础框架搭建
|
## 3、系统基础框架搭建
|
||||||
@ -61,24 +66,24 @@
|
|||||||
|
|
||||||
### 1、后台
|
### 1、后台
|
||||||
|
|
||||||
  一个web后台最重要的无非那么几个部分:路由;权限验证;数据持久化。
|
  一个 web 后台最重要的无非那么几个部分:路由;权限验证;数据持久化。
|
||||||
|
|
||||||
#### a、路由
|
#### a、路由
|
||||||
|
|
||||||
KOA作为一个web框架其实它本身并没有提供路由功能,需要配合使用koa-router来实现路由,koa-router以类似下面这样的风格来进行路由:
|
KOA 作为一个 web 框架其实它本身并没有提供路由功能,需要配合使用 koa-router 来实现路由,koa-router 以类似下面这样的风格来进行路由:
|
||||||
|
|
||||||
  KOA作为一个web框架其实它本身并没有提供路由功能,需要配合使用koa-router来实现路由,koa-router以类似下面这样的风格来进行路由:
|
  KOA 作为一个 web 框架其实它本身并没有提供路由功能,需要配合使用 koa-router 来实现路由,koa-router 以类似下面这样的风格来进行路由:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const app = require("koa");
|
const app = require('koa');
|
||||||
const router = require("koa-router");
|
const router = require('koa-router');
|
||||||
router.get("/hello",koa=>{
|
router.get('/hello', koa => {
|
||||||
koa.response="hello";
|
koa.response = 'hello';
|
||||||
});
|
});
|
||||||
app.use(router.routes())
|
app.use(router.routes());
|
||||||
```
|
```
|
||||||
|
|
||||||
显然这样在项目中是很不方便的,如果每个路由都要手动进行挂载,很难将每个文件中的路由都挂载到一个router中。因此在参考网上的实现后,我写了一个方法在启动时自动扫描某个文件夹下所有的路由文件并挂载到router中,代码如下:
|
显然这样在项目中是很不方便的,如果每个路由都要手动进行挂载,很难将每个文件中的路由都挂载到一个 router 中。因此在参考网上的实现后,我写了一个方法在启动时自动扫描某个文件夹下所有的路由文件并挂载到 router 中,代码如下:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
@ -87,55 +92,55 @@ const koaBody = require('koa-body');
|
|||||||
const config = require('../config/config.js');
|
const config = require('../config/config.js');
|
||||||
|
|
||||||
function addMapping(router, filePath) {
|
function addMapping(router, filePath) {
|
||||||
let mapping = require(filePath);
|
let mapping = require(filePath);
|
||||||
for (let url in mapping) {
|
for (let url in mapping) {
|
||||||
if (url.startsWith('GET ')) {
|
if (url.startsWith('GET ')) {
|
||||||
let temp = url.substring(4);
|
let temp = url.substring(4);
|
||||||
router.get(temp, mapping[url]);
|
router.get(temp, mapping[url]);
|
||||||
console.log(`----GET:${temp}`);
|
console.log(`----GET:${temp}`);
|
||||||
} else if (url.startsWith('POST ')) {
|
} else if (url.startsWith('POST ')) {
|
||||||
let temp = url.substring(5);
|
let temp = url.substring(5);
|
||||||
router.post(temp, mapping[url]);
|
router.post(temp, mapping[url]);
|
||||||
console.log(`----POST:${temp}`);
|
console.log(`----POST:${temp}`);
|
||||||
} else if (url.startsWith('PUT ')) {
|
} else if (url.startsWith('PUT ')) {
|
||||||
let temp = url.substring(4);
|
let temp = url.substring(4);
|
||||||
router.put(temp, mapping[url]);
|
router.put(temp, mapping[url]);
|
||||||
console.log(`----PUT:${temp}`)
|
console.log(`----PUT:${temp}`);
|
||||||
} else if (url.startsWith('DELETE ')) {
|
} else if (url.startsWith('DELETE ')) {
|
||||||
let temp = url.substring(7);
|
let temp = url.substring(7);
|
||||||
router.delete(temp, mapping[url]);
|
router.delete(temp, mapping[url]);
|
||||||
console.log(`----DELETE: ${temp}`);
|
console.log(`----DELETE: ${temp}`);
|
||||||
} else {
|
} else {
|
||||||
console.log(`xxxxx无效路径:${url}`);
|
console.log(`xxxxx无效路径:${url}`);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function addControllers(router, filePath) {
|
function addControllers(router, filePath) {
|
||||||
let files = fs.readdirSync(filePath);
|
let files = fs.readdirSync(filePath);
|
||||||
files.forEach(element => {
|
files.forEach(element => {
|
||||||
let temp = path.join(filePath, element);
|
let temp = path.join(filePath, element);
|
||||||
let state = fs.statSync(temp);
|
let state = fs.statSync(temp);
|
||||||
if (state.isDirectory()) {
|
if (state.isDirectory()) {
|
||||||
addControllers(router, temp);
|
addControllers(router, temp);
|
||||||
} else {
|
} else {
|
||||||
if (!temp.endsWith('Helper.js')) {
|
if (!temp.endsWith('Helper.js')) {
|
||||||
console.log('\n--开始处理: ' + element + "路由");
|
console.log('\n--开始处理: ' + element + '路由');
|
||||||
addMapping(router, temp);
|
addMapping(router, temp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function engine(router, folder) {
|
function engine(router, folder) {
|
||||||
addControllers(router, folder);
|
addControllers(router, folder);
|
||||||
return router.routes();
|
return router.routes();
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = engine;
|
module.exports = engine;
|
||||||
```
|
```
|
||||||
|
|
||||||
然后在index.js中use此方法:
|
然后在 index.js 中 use 此方法:
|
||||||
|
|
||||||
```
|
```
|
||||||
const RouterMW = require("./middleware/controllerEngine.js");
|
const RouterMW = require("./middleware/controllerEngine.js");
|
||||||
@ -150,45 +155,54 @@ const knowledgePointDao = require('../dao/knowledgePointDao.js');
|
|||||||
/**
|
/**
|
||||||
* 返回某门课的全部知识点,按章节分类
|
* 返回某门课的全部知识点,按章节分类
|
||||||
*/
|
*/
|
||||||
exports["GET /course/:c_id/knowledge_point"] = async (ctx, next) => {
|
exports['GET /course/:c_id/knowledge_point'] = async (ctx, next) => {
|
||||||
let res = await knowledgePointDao.getPontsOrderBySection(ctx.params.c_id);
|
let res = await knowledgePointDao.getPontsOrderBySection(ctx.params.c_id);
|
||||||
ctx.onSuccess(res);
|
ctx.onSuccess(res);
|
||||||
}
|
};
|
||||||
|
|
||||||
//返回某位学生知识点答题情况
|
//返回某位学生知识点答题情况
|
||||||
exports["GET /user/:u_id/course/:c_id/knowledge_point/condition"]=async(ctx,next)=>{
|
exports['GET /user/:u_id/course/:c_id/knowledge_point/condition'] = async (
|
||||||
let {u_id,c_id}=ctx.params;
|
ctx,
|
||||||
let res = await knowledgePointDao.getStudentCondition(u_id,c_id);
|
next
|
||||||
ctx.onSuccess(res);
|
) => {
|
||||||
}
|
let { u_id, c_id } = ctx.params;
|
||||||
|
let res = await knowledgePointDao.getStudentCondition(u_id, c_id);
|
||||||
|
ctx.onSuccess(res);
|
||||||
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
#### b、权限验证
|
#### b、权限验证
|
||||||
|
|
||||||
  权限管理是一个系统最重要的部分之一,目前主流的方式为**基于角色的权限管理**, 一个用户对应多个角色,每个角色对应多个权限(本系统中每个用户对应一个身份,每个身份对应多个角色)。我们的系统如何实现的呢?先从登录开始说起,本系统抛弃了传统的cookie,session模式,使用json web token(JWT)来做身份认证,用户登录后返回一个token给客户端,代码如下所示:
|
  权限管理是一个系统最重要的部分之一,目前主流的方式为**基于角色的权限管理**, 一个用户对应多个角色,每个角色对应多个权限(本系统中每个用户对应一个身份,每个身份对应多个角色)。我们的系统如何实现的呢?先从登录开始说起,本系统抛弃了传统的 cookie,session 模式,使用 json web token(JWT)来做身份认证,用户登录后返回一个 token 给客户端,代码如下所示:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
//生成随机盐值
|
//生成随机盐值
|
||||||
let str = StringHelper.getRandomString(0, 10);
|
let str = StringHelper.getRandomString(0, 10);
|
||||||
//使用该盐值生成token
|
//使用该盐值生成token
|
||||||
let token = jwt.sign({
|
let token = jwt.sign(
|
||||||
u_id: userInfo.u_id,
|
{
|
||||||
isRememberMe
|
u_id: userInfo.u_id,
|
||||||
}, str, {
|
isRememberMe
|
||||||
expiresIn: isRememberMe ? config.longTokenExpiration:config.shortTokenExpiration
|
},
|
||||||
});
|
str,
|
||||||
|
{
|
||||||
|
expiresIn: isRememberMe
|
||||||
|
? config.longTokenExpiration
|
||||||
|
: config.shortTokenExpiration
|
||||||
|
}
|
||||||
|
);
|
||||||
//token-盐值存入redis,如想让该token过期,redis中清楚该token键值对即可
|
//token-盐值存入redis,如想让该token过期,redis中清楚该token键值对即可
|
||||||
await RedisHelper.setString(token, str, 30 * 24 * 60 * 60);
|
await RedisHelper.setString(token, str, 30 * 24 * 60 * 60);
|
||||||
res.code = 1;
|
res.code = 1;
|
||||||
res.info = '登录成功';
|
res.info = '登录成功';
|
||||||
res.data = {
|
res.data = {
|
||||||
u_type: userInfo.u_type,
|
u_type: userInfo.u_type,
|
||||||
u_id: userInfo.u_id,
|
u_id: userInfo.u_id,
|
||||||
token
|
token
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
以后每次客户端请求都要在header中设置该token,然后每次服务端收到请求都先验证是否拥有权限,验证代码使用`router.use(auth)`,挂载到koa-router中,这样每次在进入具体的路由前都要先执行auth方法进行权限验证,主要验证代码逻辑如下:
|
以后每次客户端请求都要在 header 中设置该 token,然后每次服务端收到请求都先验证是否拥有权限,验证代码使用`router.use(auth)`,挂载到 koa-router 中,这样每次在进入具体的路由前都要先执行 auth 方法进行权限验证,主要验证代码逻辑如下:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
/**
|
/**
|
||||||
@ -197,120 +211,143 @@ res.data = {
|
|||||||
* 3 已登录,无操作权限 403
|
* 3 已登录,无操作权限 403
|
||||||
* 4 token已过期
|
* 4 token已过期
|
||||||
*/
|
*/
|
||||||
let verify = async (ctx) => {
|
let verify = async ctx => {
|
||||||
let token = ctx.headers.authorization;
|
let token = ctx.headers.authorization;
|
||||||
if (typeof (token) != 'string') {
|
if (typeof token != 'string') {
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
let yan = await redisHelper.getString(token);
|
let yan = await redisHelper.getString(token);
|
||||||
if (yan == null) {
|
if (yan == null) {
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
let data;
|
let data;
|
||||||
try {
|
try {
|
||||||
data = jwt.verify(token, yan);
|
data = jwt.verify(token, yan);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
if (data.exp * 1000 < Date.now()) {
|
if (data.exp * 1000 < Date.now()) {
|
||||||
return 4;
|
return 4;
|
||||||
}
|
}
|
||||||
//判断是否需要刷新token,如需要刷新将新token写入响应头
|
//判断是否需要刷新token,如需要刷新将新token写入响应头
|
||||||
if (!data.isRememberMe && (data.exp * 1000 - Date.now()) < 30 * 60 * 1000) {
|
if (!data.isRememberMe && data.exp * 1000 - Date.now() < 30 * 60 * 1000) {
|
||||||
//token有效期不足半小时,重新签发新token给客户端
|
//token有效期不足半小时,重新签发新token给客户端
|
||||||
let newYan = StringHelper.getRandomString(0, 10);
|
let newYan = StringHelper.getRandomString(0, 10);
|
||||||
let newToken = jwt.sign({
|
let newToken = jwt.sign(
|
||||||
u_id: data.u_id,
|
{
|
||||||
isRememberMe:false
|
u_id: data.u_id,
|
||||||
}, newYan, {
|
isRememberMe: false
|
||||||
expiresIn: config.shortTokenExpiration
|
},
|
||||||
});
|
newYan,
|
||||||
// await redisHelper.deleteKey(token);
|
{
|
||||||
await redisHelper.setString(newToken, newYan,config.shortTokenExpiration);
|
expiresIn: config.shortTokenExpiration
|
||||||
ctx.response.set('new-token', newToken);
|
}
|
||||||
ctx.response.set('Access-Control-Expose-Headers','new-token');
|
);
|
||||||
}
|
// await redisHelper.deleteKey(token);
|
||||||
//获取用户信息
|
await redisHelper.setString(newToken, newYan, config.shortTokenExpiration);
|
||||||
let userInfoKey = data.u_id + '_userInfo';
|
ctx.response.set('new-token', newToken);
|
||||||
let userInfo = await redisHelper.getString(userInfoKey);
|
ctx.response.set('Access-Control-Expose-Headers', 'new-token');
|
||||||
if (userInfo == null || Object.keys(userInfo).length != 3) {
|
}
|
||||||
userInfo = await mysqlHelper.first(`select u_id,u_type,j_id from user where u_id=?`, data.u_id);
|
//获取用户信息
|
||||||
await redisHelper.setString(userInfoKey, JSON.stringify(userInfo), 24 * 60 * 60);
|
let userInfoKey = data.u_id + '_userInfo';
|
||||||
}else{
|
let userInfo = await redisHelper.getString(userInfoKey);
|
||||||
userInfo = JSON.parse(userInfo);
|
if (userInfo == null || Object.keys(userInfo).length != 3) {
|
||||||
}
|
userInfo = await mysqlHelper.first(
|
||||||
ctx.userInfo = userInfo;
|
`select u_id,u_type,j_id from user where u_id=?`,
|
||||||
//更新用户上次访问时间
|
data.u_id
|
||||||
mysqlHelper.execute(`update user set last_login_time=? where u_id=?`,Date.now(),userInfo.u_id);
|
);
|
||||||
//管理员拥有全部权限
|
await redisHelper.setString(
|
||||||
if (userInfo.u_type == 0) {
|
userInfoKey,
|
||||||
return 1;
|
JSON.stringify(userInfo),
|
||||||
}
|
24 * 60 * 60
|
||||||
//获取该用户类型权限
|
);
|
||||||
let authKey = userInfo.j_id + '_authority';
|
} else {
|
||||||
let urls = await redisHelper.getObject(authKey);
|
userInfo = JSON.parse(userInfo);
|
||||||
// let urls = null;
|
}
|
||||||
if (urls == null) {
|
ctx.userInfo = userInfo;
|
||||||
urls = await mysqlHelper.row(`
|
//更新用户上次访问时间
|
||||||
|
mysqlHelper.execute(
|
||||||
|
`update user set last_login_time=? where u_id=?`,
|
||||||
|
Date.now(),
|
||||||
|
userInfo.u_id
|
||||||
|
);
|
||||||
|
//管理员拥有全部权限
|
||||||
|
if (userInfo.u_type == 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
//获取该用户类型权限
|
||||||
|
let authKey = userInfo.j_id + '_authority';
|
||||||
|
let urls = await redisHelper.getObject(authKey);
|
||||||
|
// let urls = null;
|
||||||
|
if (urls == null) {
|
||||||
|
urls = await mysqlHelper.row(
|
||||||
|
`
|
||||||
select b.r_id,b.url,b.method from jurisdiction_resource a inner join resource b on a.r_id = b.r_id where a.j_id=?
|
select b.r_id,b.url,b.method from jurisdiction_resource a inner join resource b on a.r_id = b.r_id where a.j_id=?
|
||||||
`, userInfo.j_id);
|
`,
|
||||||
let temp = {};
|
userInfo.j_id
|
||||||
urls.forEach(item => {
|
);
|
||||||
temp[item.url + item.method] = true;
|
let temp = {};
|
||||||
})
|
urls.forEach(item => {
|
||||||
await redisHelper.setObject(authKey, temp);
|
temp[item.url + item.method] = true;
|
||||||
urls = temp;
|
});
|
||||||
}
|
await redisHelper.setObject(authKey, temp);
|
||||||
//判断是否拥有权限
|
urls = temp;
|
||||||
if (urls.hasOwnProperty(ctx._matchedRoute.replace(config.url_prefix, '') + ctx.method)) {
|
}
|
||||||
return 1;
|
//判断是否拥有权限
|
||||||
} else {
|
if (
|
||||||
return 3;
|
urls.hasOwnProperty(
|
||||||
}
|
ctx._matchedRoute.replace(config.url_prefix, '') + ctx.method
|
||||||
}
|
)
|
||||||
|
) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
根据用户id获取用户身份id,根据用户身份id从redis中获取拥有的权限,如为null,从mysql数据库中拉取,并存入redis中,然后判断是否拥有要访问的url权限。
|
根据用户 id 获取用户身份 id,根据用户身份 id 从 redis 中获取拥有的权限,如为 null,从 mysql 数据库中拉取,并存入 redis 中,然后判断是否拥有要访问的 url 权限。
|
||||||
|
|
||||||
#### c、数据持久化
|
#### c、数据持久化
|
||||||
|
|
||||||
  本系统中使用mysql存储数据,redis做缓存,由于当时操作库不支持promise,故对它两做了个promise封装,方便代码中调用,参见:[MysqlHelper](https://github.com/FleyX/teach_system/tree/master/teachSystem/util/MysqlHelper.js),[RedisHelper.js](https://github.com/FleyX/teach_system/tree/master/teachSystem/util/RedisHelper.js)。
|
  本系统中使用 mysql 存储数据,redis 做缓存,由于当时操作库不支持 promise,故对它两做了个 promise 封装,方便代码中调用,参见:[MysqlHelper](https://github.com/FleyX/teach_system/tree/master/teachSystem/util/MysqlHelper.js),[RedisHelper.js](https://github.com/FleyX/teach_system/tree/master/teachSystem/util/RedisHelper.js)。
|
||||||
|
|
||||||
### 2、前端
|
### 2、前端
|
||||||
|
|
||||||
  前端使用vue-cli构建vue项目,主要用到了vue-router,element-ui,axios这三个组件。
|
  前端使用 vue-cli 构建 vue 项目,主要用到了 vue-router,element-ui,axios 这三个组件。
|
||||||
|
|
||||||
#### a、路由组织
|
#### a、路由组织
|
||||||
|
|
||||||
  单页应用需要前端自己组织路由。本系统将路由分成了三个部分:公共,管理端,学生端。index.js如下:
|
  单页应用需要前端自己组织路由。本系统将路由分成了三个部分:公共,管理端,学生端。index.js 如下:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
export default new Router({
|
export default new Router({
|
||||||
mode: 'history',
|
mode: 'history',
|
||||||
base: '/app/',
|
base: '/app/',
|
||||||
routes: [{
|
routes: [
|
||||||
path: '',
|
{
|
||||||
name: 'indexPage',
|
path: '',
|
||||||
component: IndexPage
|
name: 'indexPage',
|
||||||
},
|
component: IndexPage
|
||||||
{
|
},
|
||||||
path: '/about',
|
{
|
||||||
name: 'about',
|
path: '/about',
|
||||||
component: About
|
name: 'about',
|
||||||
},
|
component: About
|
||||||
|
},
|
||||||
Admin,
|
Admin,
|
||||||
Client,
|
Client,
|
||||||
Public,
|
Public,
|
||||||
{
|
{
|
||||||
path: '*',
|
path: '*',
|
||||||
name: "NotFound",
|
name: 'NotFound',
|
||||||
component: NotFound
|
component: NotFound
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
其中的Admin,Client,Public分别为各部分的路由,以子路由的形式一级级组织。如下所示:
|
其中的 Admin,Client,Public 分别为各部分的路由,以子路由的形式一级级组织。如下所示:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
export default {
|
export default {
|
||||||
@ -357,24 +394,22 @@ export default {
|
|||||||
......
|
......
|
||||||
```
|
```
|
||||||
|
|
||||||
其中的beforEnter为钩子函数,每次进入路由时执行该函数,用于判断用户是否登录。这里涉及到了一个前端鉴权的概念,由于前后端分离了,前端也必须做鉴权以免用户进入到了无权限的页面,这里我只是简单的做了登录判断,更详细的url鉴权也可实现,只需在对应的钩子函数中进行鉴权操作,更多关于钩子函数信息[点击这里](https://router.vuejs.org/zh/guide/advanced/navigation-guards.html)。
|
其中的 beforEnter 为钩子函数,每次进入路由时执行该函数,用于判断用户是否登录。这里涉及到了一个前端鉴权的概念,由于前后端分离了,前端也必须做鉴权以免用户进入到了无权限的页面,这里我只是简单的做了登录判断,更详细的 url 鉴权也可实现,只需在对应的钩子函数中进行鉴权操作,更多关于钩子函数信息[点击这里](https://router.vuejs.org/zh/guide/advanced/navigation-guards.html)。
|
||||||
|
|
||||||
#### b、请求封装
|
#### b、请求封装
|
||||||
|
|
||||||
  前端还有一个比较重要的部分是ajax请求的处理,请求处理还保护错误处理,有些错误只需要统一处理,而有些又需要独立的处理,这样一来就需要根据业务需求进行一下请求封装了,对结果进行处理后再返回给调用者。我的实现思路是发起请求,收到响应后先对错误进行一个同意弹窗提示,然后再将错误继续向后传递,调用者可选择性的捕获错误进行针对性处理,主要代码如下:
|
  前端还有一个比较重要的部分是 ajax 请求的处理,请求处理还保护错误处理,有些错误只需要统一处理,而有些又需要独立的处理,这样一来就需要根据业务需求进行一下请求封装了,对结果进行处理后再返回给调用者。我的实现思路是发起请求,收到响应后先对错误进行一个同意弹窗提示,然后再将错误继续向后传递,调用者可选择性的捕获错误进行针对性处理,主要代码如下:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
request = (url, method, params, form, isFormData, type) => {
|
request = (url, method, params, form, isFormData, type) => {
|
||||||
let token;
|
let token;
|
||||||
if (type == 'admin')
|
if (type == 'admin') token = getToken();
|
||||||
token = getToken();
|
else token = getClientToken();
|
||||||
else
|
|
||||||
token = getClientToken();
|
|
||||||
let headers = {
|
let headers = {
|
||||||
'Authorization': token
|
Authorization: token
|
||||||
};
|
};
|
||||||
if (isFormData) {
|
if (isFormData) {
|
||||||
headers['Content-Type'] = "multipart/form-data";
|
headers['Content-Type'] = 'multipart/form-data';
|
||||||
}
|
}
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
axios({
|
axios({
|
||||||
@ -382,75 +417,92 @@ request = (url, method, params, form, isFormData, type) => {
|
|||||||
method,
|
method,
|
||||||
params,
|
params,
|
||||||
data: form,
|
data: form,
|
||||||
headers,
|
headers
|
||||||
// timeout:2000
|
// timeout:2000
|
||||||
}).then(res => {
|
|
||||||
resolve(res.data);
|
|
||||||
//检查是否有更新token
|
|
||||||
// console.log(res);
|
|
||||||
if (res.headers['new-token'] != undefined) {
|
|
||||||
console.log('set new token');
|
|
||||||
if (vm.$route.path.startsWith('/admin')){
|
|
||||||
localStorage.setItem("token",res.headers['new-token']);
|
|
||||||
window.token = undefined;
|
|
||||||
}else if(vm.$route.path.startsWith('/client')){
|
|
||||||
localStorage.setItem("clientToken",res.headers['new-token']);
|
|
||||||
window.clientToken = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).catch(err => {
|
|
||||||
reject(err);
|
|
||||||
if (err.code == 'ECONNABORTED') {
|
|
||||||
alertNotify("错误", "请求超时", "error");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (err.message == 'Network Error') {
|
|
||||||
alertNotify("错误", "无法连接服务器", 'error');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (err.response != undefined) {
|
|
||||||
switch (err.response.status) {
|
|
||||||
case 401:
|
|
||||||
if (window.isGoToLogin) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
//使用该变量表示是否已经弹窗提示了,避免大量未登录弹窗堆积。
|
|
||||||
window.isGoToLogin = true;
|
|
||||||
vm.$alert(err.response.data, "警告", {
|
|
||||||
type: "warning",
|
|
||||||
showClose: false
|
|
||||||
}).then(res => {
|
|
||||||
window.isGoToLogin = false;
|
|
||||||
if (vm.$route.path.startsWith('/admin/')) {
|
|
||||||
clearInfo();
|
|
||||||
vm.$router.replace("/public/admin_login");
|
|
||||||
} else {
|
|
||||||
clearClientInfo();
|
|
||||||
vm.$router.replace("/public/client_login");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case 403:
|
|
||||||
alertNotify("Error:403", '拒绝执行:' + err.response.data, "error");
|
|
||||||
break;
|
|
||||||
case 404:
|
|
||||||
alertNotify("Error:404", "找不到资源:" + url.substr(0, url.indexOf('?')), 'error');
|
|
||||||
break;
|
|
||||||
case 400:
|
|
||||||
alertNotify("Error:400", "请求参数错误:" + err.response.data, 'error');
|
|
||||||
break;
|
|
||||||
case 500:
|
|
||||||
alertNotify("Error:500", "服务器内部错误:" + err.response.data, 'error');
|
|
||||||
default:
|
|
||||||
console.log('存在错误未处理:' + err);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log(err);
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
})
|
.then(res => {
|
||||||
}
|
resolve(res.data);
|
||||||
|
//检查是否有更新token
|
||||||
|
// console.log(res);
|
||||||
|
if (res.headers['new-token'] != undefined) {
|
||||||
|
console.log('set new token');
|
||||||
|
if (vm.$route.path.startsWith('/admin')) {
|
||||||
|
localStorage.setItem('token', res.headers['new-token']);
|
||||||
|
window.token = undefined;
|
||||||
|
} else if (vm.$route.path.startsWith('/client')) {
|
||||||
|
localStorage.setItem('clientToken', res.headers['new-token']);
|
||||||
|
window.clientToken = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
reject(err);
|
||||||
|
if (err.code == 'ECONNABORTED') {
|
||||||
|
alertNotify('错误', '请求超时', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (err.message == 'Network Error') {
|
||||||
|
alertNotify('错误', '无法连接服务器', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (err.response != undefined) {
|
||||||
|
switch (err.response.status) {
|
||||||
|
case 401:
|
||||||
|
if (window.isGoToLogin) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//使用该变量表示是否已经弹窗提示了,避免大量未登录弹窗堆积。
|
||||||
|
window.isGoToLogin = true;
|
||||||
|
vm.$alert(err.response.data, '警告', {
|
||||||
|
type: 'warning',
|
||||||
|
showClose: false
|
||||||
|
}).then(res => {
|
||||||
|
window.isGoToLogin = false;
|
||||||
|
if (vm.$route.path.startsWith('/admin/')) {
|
||||||
|
clearInfo();
|
||||||
|
vm.$router.replace('/public/admin_login');
|
||||||
|
} else {
|
||||||
|
clearClientInfo();
|
||||||
|
vm.$router.replace('/public/client_login');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 403:
|
||||||
|
alertNotify(
|
||||||
|
'Error:403',
|
||||||
|
'拒绝执行:' + err.response.data,
|
||||||
|
'error'
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 404:
|
||||||
|
alertNotify(
|
||||||
|
'Error:404',
|
||||||
|
'找不到资源:' + url.substr(0, url.indexOf('?')),
|
||||||
|
'error'
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 400:
|
||||||
|
alertNotify(
|
||||||
|
'Error:400',
|
||||||
|
'请求参数错误:' + err.response.data,
|
||||||
|
'error'
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 500:
|
||||||
|
alertNotify(
|
||||||
|
'Error:500',
|
||||||
|
'服务器内部错误:' + err.response.data,
|
||||||
|
'error'
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
console.log('存在错误未处理:' + err);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
  到这里就算是简单介绍完了,,想要更加深入了解的可以去github查看源代码,地址如下:[https://github.com/FleyX/teach_system,](https://github.com/FleyX/teach_system)记得star哦!
|
  到这里就算是简单介绍完了,,想要更加深入了解的可以去 github 查看源代码,地址如下:[https://github.com/FleyX/teach_system,](https://github.com/FleyX/teach_system)记得 star 哦!
|
||||||
|
@ -1,21 +1,25 @@
|
|||||||
[id]:2018-09-20
|
---
|
||||||
[type]:软件
|
id="2018-09-20-10-58"
|
||||||
[tag]:git,crlf,lf
|
title="git crlf、lf自动转换问题"
|
||||||
|
headWord="踩坑,都是从git拉下来的,就我老是报错,怎么都跑不起来,,花了半天时间排查原因。。。"
|
||||||
|
tags=["git","crlf","lf","flyway"]
|
||||||
|
category="其他"
|
||||||
|
serie="踩坑"
|
||||||
|
---
|
||||||
|
|
||||||
  项目组最近加了一个新功能到代码中,使用flyway记录数据库版本变更,,,该工具会记录每次数据库结构的修改并生成sql文件存在指定目录上(当然必须用它来变更数据库,外部的变更它是无法感知的),然后每次启动时flyway会检查使用的数据库和当前项目代码中的sql变更版本是否一致,一致正常启动,不一致中如果是数据库落后将会更新数据库(这样能够保证代码在任何地方运行数据库都是一致的),否则就报错了。数据库中有一张表记录版本信息,如下图:
|
  项目组最近加了一个新功能到代码中,使用 flyway 记录数据库版本变更,该工具会记录每次数据库结构的修改并生成 sql 文件存在指定目录上(当然必须用它来变更数据库,外部的变更它是无法感知的),然后每次启动时 flyway 会检查使用的数据库和当前项目代码中的 sql 变更版本是否一致,一致正常启动,不一致中如果是数据库落后将会更新数据库(这样能够保证代码在任何地方运行数据库都是一致的),否则就报错了。数据库中有一张表记录版本信息,如下图:
|
||||||
|
|
||||||
![版本记录](./picFolder/版本记录.PNG),同时本地代码中也有一个文件夹保存每次操作的sql语句,如下图:
|
![版本记录](./picFolder/版本记录.PNG),同时本地代码中也有一个文件夹保存每次操作的 sql 语句,如下图:
|
||||||
|
|
||||||
![版本sql](./picFolder/版本sql.PNG)
|
![版本sql](./picFolder/版本sql.PNG)
|
||||||
|
|
||||||
通过对比checksum值来判断当前sql语句和生成数据库的执行语句是否一致,checksum值由CRC32计算后处理得出。
|
通过对比 checksum 值来判断当前 sql 语句和生成数据库的执行语句是否一致,checksum 值由 CRC32 计算后处理得出。
|
||||||
|
|
||||||
  然后问题就来了,组中的其他人搭建好flyway后,项目文件生成了两个sql文件,我用git拉下来后启动报错,checkupsum值对不上,,然后我又不懂这个flyway完全不知道咋回事,然后就根据报错的位置一点点找到checkup值生成的代码,发现是CRC32计算的,,(就这么搞了一两个小时才发现是文件不一致了),但是都是从git拉的怎么就我不一致呢???想到可能是文件换行符的问题,遂把那几个sql文件的文件换行符全换成了crlf(windows中的换行符),然后居然就能够运行。。。关于为啥都从git拉取的文件换行符会不一样原因是:他们都用的那个小乌龟的可视化,我用的命令行。可视化工具自动配置了文件换行符的自动转换(这是git的一个智能功能,上传时将文件换行符替换为lf,,拉取时再替换为crlf,,这样保证中心仓库使用UNIX风格的换行符,,本地能够根据运行环境使用相对应的换行符风格),但是命令行并没有配置。
|
  然后问题就来了,组中的其他人搭建好 flyway 后,项目文件生成了两个 sql 文件,我用 git 拉下来后启动报错,checkupsum 值对不上,,然后我又不懂这个 flyway 完全不知道咋回事,然后就根据报错的位置一点点找到 checkup 值生成的代码,发现是 CRC32 计算的,,(就这么搞了一两个小时才发现是文件不一致了),但是都是从 git 拉的怎么就我不一致呢???想到可能是文件换行符的问题,遂把那几个 sql 文件的文件换行符全换成了 crlf(windows 中的换行符),然后居然就能够运行。。。关于为啥都从 git 拉取的文件换行符会不一样原因是:他们都用的那个小乌龟的可视化,我用的命令行。可视化工具自动配置了文件换行符的自动转换(这是 git 的一个智能功能,上传时将文件换行符替换为 lf,,拉取时再替换为 crlf,,这样保证中心仓库使用 UNIX 风格的换行符,,本地能够根据运行环境使用相对应的换行符风格),但是命令行并没有配置。
|
||||||
|
|
||||||
  解决办法也很简单,开启git 的自动转换。
|
  解决办法也很简单,开启 git 的自动转换。
|
||||||
|
|
||||||
```
|
```
|
||||||
git config --global core.autocrlf true //开启换行符自动转换
|
git config --global core.autocrlf true //开启换行符自动转换
|
||||||
git config --global core.safecrlf true //禁止混用换行符
|
git config --global core.safecrlf true //禁止混用换行符
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -1,19 +1,24 @@
|
|||||||
[id]:2018-09-30
|
---
|
||||||
[type]:java
|
id="2018-09-30-10-58"
|
||||||
[tag]:java,正则,pattern
|
title="正则匹配之正向/反向预测先行搜索"
|
||||||
|
headWord="今天领导让我写几个正则表达式来对密码做强度验证,听到写正则表达式内心是这样的感觉(哈哈,三分钟搞定,今天又可以打鱼了)..但是显然不会这么简单."
|
||||||
|
tags=["java","正则匹配","pattern","预测先行搜索"]
|
||||||
|
category="java"
|
||||||
|
serie="正则匹配"
|
||||||
|
---
|
||||||
|
|
||||||
##一、背景
|
## 一、背景
|
||||||
|
|
||||||
  今天领导让我写几个正则表达式来对密码做强度验证,听到写正则表达式内心是这样的感觉(哈哈,三分钟搞定,今天又可以打鱼了)。需求如下:密码组成只能是**数字**,**字母**,**英文可见半角符号**,然后需要如下4个表达式:
|
  今天领导让我写几个正则表达式来对密码做强度验证,听到写正则表达式内心是这样的感觉(哈哈,三分钟搞定,今天又可以打鱼了)。需求如下:密码组成只能是**数字**,**字母**,**英文可见半角符号**,然后需要如下 4 个表达式:
|
||||||
|
|
||||||
- 长度6位及以上
|
- 长度 6 位及以上
|
||||||
- 长度6位及以上,包含数字,包含字母
|
- 长度 6 位及以上,包含数字,包含字母
|
||||||
- 长度6位及以上,包含数字,包含字母,包含半角符号
|
- 长度 6 位及以上,包含数字,包含字母,包含半角符号
|
||||||
- 长度六位及以上,包含数字,包含大写字母,包含小写字母,包含半角符号
|
- 长度六位及以上,包含数字,包含大写字母,包含小写字母,包含半角符号
|
||||||
|
|
||||||
  看完需求我就有点懵了,包含数字或者字母我会写,但是同时存在还要在一个表达式中就有点懵了。
|
  看完需求我就有点懵了,包含数字或者字母我会写,但是同时存在还要在一个表达式中就有点懵了。
|
||||||
|
|
||||||
##二、解决方法
|
## 二、解决方法
|
||||||
|
|
||||||
  以第三种为例,这个可以分解为如下需求:
|
  以第三种为例,这个可以分解为如下需求:
|
||||||
|
|
||||||
@ -24,19 +29,19 @@
|
|||||||
|
|
||||||
关键是如何同时满足前三个条件,在我有限的知识里并不知道怎么搞,然后只好求助于万能的百度了,最终在找了几个小时后发现如下几个关键词,来源[菜鸟教程](http://www.runoob.com/java/java-regular-expressions.html) :
|
关键是如何同时满足前三个条件,在我有限的知识里并不知道怎么搞,然后只好求助于万能的百度了,最终在找了几个小时后发现如下几个关键词,来源[菜鸟教程](http://www.runoob.com/java/java-regular-expressions.html) :
|
||||||
|
|
||||||
- (?=*pattern*) :正向预测先行搜索
|
- (?=_pattern_) :正向预测先行搜索
|
||||||
|
|
||||||
名字看着高大上,不明所以,看完示例大概明白什么意思,这个表达式匹配从这个表达式起始的字符串(我也不知道咋解释),就是假设这样一个表达式abc(?=[abc]) ,用它来匹配abc123字符串,(?=[abc])只会对作用于后面的123,这个显然是不匹配的后整个就不匹配了,然后关键来了名字里有**预测**两个字,这两个字表名了这个表达式的特性:不占用字符,匹配后如果匹配成功就继续匹配了好像从来不存在这个东西一样,匹配失败就立即返回失败了。利用这个特性我们就可以给正则加限制条件了。
|
名字看着高大上,不明所以,看完示例大概明白什么意思,这个表达式匹配从这个表达式起始的字符串(我也不知道咋解释),就是假设这样一个表达式 abc(?=[abc]) ,用它来匹配 abc123 字符串,(?=[abc])只会对作用于后面的 123,这个显然是不匹配的后整个就不匹配了,然后关键来了名字里有**预测**两个字,这两个字表名了这个表达式的特性:不占用字符,匹配后如果匹配成功就继续匹配了好像从来不存在这个东西一样,匹配失败就立即返回失败了。利用这个特性我们就可以给正则加限制条件了。
|
||||||
|
|
||||||
- (?!*pattern*) :反向预测先行搜索
|
- (?!_pattern_) :反向预测先行搜索
|
||||||
|
|
||||||
概念和上面一样,但是效果是相反的,abc(?![abc]),对于abc123是匹配成功的,对于abca匹配失败,如下所示:
|
概念和上面一样,但是效果是相反的,abc(?![abc]),对于 abc123 是匹配成功的,对于 abca 匹配失败,如下所示:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
reg = /abc(?![abc])/;
|
reg = /abc(?![abc])/;
|
||||||
reg.test("abc123")
|
reg.test('abc123');
|
||||||
//返回true
|
//返回true
|
||||||
reg.test("abca")
|
reg.test('abca');
|
||||||
//返回false
|
//返回false
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -44,9 +49,8 @@
|
|||||||
|
|
||||||
## 三、结果
|
## 三、结果
|
||||||
|
|
||||||
  对于存在字母我们可以用这样的表达式`(?=.\*?[a-zA-Z]+.\*?),来检查是否存在至少一个字母,最后对于需求3的表达式如下:(半角字符我用的ASCII码里的16进制表示的)
|
  对于存在字母我们可以用这样的表达式`(?=.\*?[a-zA-Z]+.\*?),来检查是否存在至少一个字母,最后对于需求 3 的表达式如下:(半角字符我用的 ASCII 码里的 16 进制表示的)
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
^(?=.*?\d+.*?)(?=.*?[a-zA-Z]+.*?)(?=.*?[\x21-\x2F\x3A-\x40\x5B-\x60\x7B-\x7E]+.*?)[\da-zA-Z\x21-\x2F\x3A-\x40\x5B-\x60\x7B-\x7E]{6,}$
|
^(?=.*?\d+.*?)(?=.*?[a-zA-Z]+.*?)(?=.*?[\x21-\x2F\x3A-\x40\x5B-\x60\x7B-\x7E]+.*?)[\da-zA-Z\x21-\x2F\x3A-\x40\x5B-\x60\x7B-\x7E]{6,}$
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
[id]:2018-10-02
|
---
|
||||||
[type]:数据库
|
id="2018-10-03-10-58"
|
||||||
[tag]:acid,sql,分布式,事务
|
title="正则匹配之正向/反向预测先行搜索"
|
||||||
|
headWord="分布式听说很火,但是分布式事务有很难解决,so来了解了解"
|
||||||
|
tags=["分布式","sql","2PC","TCC","异步补偿"]
|
||||||
|
category="数据库"
|
||||||
|
serie="分布式事务"
|
||||||
|
---
|
||||||
|
|
||||||
## 前言
|
## 前言
|
||||||
|
|
@ -1,6 +1,11 @@
|
|||||||
[id]:2018-10-03
|
---
|
||||||
[type]:数据库
|
id="2018-10-02-10-58"
|
||||||
[tag]:acid,sql,分布式,事务
|
title="数据库4个特性及隔离级别"
|
||||||
|
headWord="了解,了解。"
|
||||||
|
tags=["acid","sql","事务"]
|
||||||
|
category="数据库"
|
||||||
|
serie="分布式事务"
|
||||||
|
---
|
||||||
|
|
||||||
## 一、 数据库事务四大特性ACID
|
## 一、 数据库事务四大特性ACID
|
||||||
|
|
||||||
|
@ -1,3 +1,11 @@
|
|||||||
|
---
|
||||||
|
id="2018-08-04-10-58"
|
||||||
|
title="nat模式虚拟机主机相互ping通"
|
||||||
|
headWord="如题。"
|
||||||
|
tags=["vmware","nat","ping","ubuntu"]
|
||||||
|
category="java"
|
||||||
|
serie="正则匹配"
|
||||||
|
---
|
||||||
[id]:2018-08-04
|
[id]:2018-08-04
|
||||||
[type]:软件
|
[type]:软件
|
||||||
[tag]:vmware,vps,nat,ubuntu
|
[tag]:vmware,vps,nat,ubuntu
|
||||||
|
Loading…
x
Reference in New Issue
Block a user