springcloud各组件目标

软件架构发展史

1、单体架构
部署形式:一个应用、一个DB、一个WebServer
优点:快速、灵活、技术要求低
缺点:升级迭代难、业务支撑能力弱、部署扩展性差

2、SOA
部署形式:多个分布式应用集群部署、统一DB/按业务拆分DB、多个WebServer
优点:业务模块内部高内聚,业务模块之间低耦合、业务模块目标清晰、模块升级服务独立、针对业务洪峰可通过水平扩展来支撑
缺点:ESB(企业服务总线)职责过多(职责:统一服务管理、协议转换、消息转换、消息路由服务监控等)

3、微服务(比SOA组件化和服务化更彻底)
部署形式:多个分布式应用集群部署、多个服务模块DB、多个WebServer
优点:在SOA优点之上增加开发语言和数据库类型的多样化支持、服务治理职责更为单一
缺点:事务问题成为必须要解决的问题、系统复杂度增高
与SOA区别:去掉ESB的大一统职责组件,将大一统职责组件进行拆分,组件职责进一步细化。

springcloud组件

  • Eureka 负责服务的注册与发现,很好将各服务连接起来。通过feign简化接口调用方式。
  • Hystrix 负责监控服务之间的调用情况,连续多次失败进行熔断保护。
  • Hystrix dashboard+Turbine 负责监控 Hystrix的熔断情况,并给予图形化的展示
  • Spring Cloud Config 提供了统一的配置中心服务
  • 当配置文件发生变化的时候,Spring Cloud Bus 负责通知各服务去获取最新的配置信息
  • 所有对外的请求和服务,我们都通过Spring Cloud Gateway来进行转发,起到API网关的作用
  • 监控我们使用Sleuth+Zipkin+springAdmin将所有的请求数据及路径记录下来

Eureka(服务中心)

Eureka是Netflix开源的一款提供服务注册和发现的产品。完成服务注册、负载均衡和故障转移的功能。
依赖spring-cloud-starter-eureka
同质化产品:Consul、Feature、zookeeper、etcd等

Feign(HTTP客户端)

Feign是一个声明式的伪Http客户端,它使得写Http客户端变得更简单。Feign默认集成了Ribbon,并和Eureka结合,默认实现了负载均衡的效果。
依赖spring-cloud-starter-feign

Hystrix(服务熔断/故障隔离)

避免一个服务故障导致调用该服务的其它N个服务等待引起级联故障而造成服务雪崩(服务雪崩效应是一种因“服务提供者”的不可用导致“服务消费者”的不可用,并将不可用逐渐放大的过程。)
针对故障服务Hystrix会间隔时间进行检查,如果服务恢复将继续提供服务。

Hystrix-dashboard+Turbine (监控工具)

Hystrix-dashboard是针对Hystrix进行实时监控的管理工具,能展示Hystrix Command的请求响应时间, 请求成功率等数据。添加Turbine工具则能同时展示多个服务信息。

Spring Cloud Config (配置中心)

将服务的配置信息服务化,通过Config Client连接到Config Server进行获取,通过手动Refresh可完成在服务的运行期间重新加载配置更新。

Spring Cloud Bus(广播指令)

Spring Cloud Bus通过轻量消息代理连接各个分布的服务节点。可通过它广播消息指令到任一服务节点。比如与Spring Cloud Config配合使用自动识别配置变化,可通过该组件通知服务节点自动Refresh完成配置的更新。

Spring Cloud Gateway (服务网关)

基于SpringFramework5和SpringBoot2实现了动态路由、HTTP请求的路由匹配、过滤器可以修改下游HTTP请求和HTTP响应等功能。
同质化产品:Zuul、Linkerd等

Spring Cloud Sleuth+Zipkin+springAdmin (消息链路跟踪)

随着服务的越来越多,对调用链的分析会越来越复杂。系统的架构改进需要监控服务和服务之间通讯的各项指标达成情况如(服务间调用关系、调用链、各服务消耗时间等)。Zipkin是Twitter的一个开源项目,允许开发者收集 Twitter 各个服务上的监控数据,并提供查询接口。


观点仅代表自己,期待你的留言。

软件测试点汇总

功能性测试

  1. 需求功能完成度测试(业务逻辑、功能点等)
  2. 值类型测试
  3. 值乱码测试(特殊字符、表情符等)
  4. 值边界测试
  5. 关联功能测试
  6. 请求重复提交/请求中断测试(快速重复提交请求、发起请求后dismiss功能界面等)
  7. 运行速度测试(app启动、页面切换等)
  8. 程序异常测试
  9. 场景回滚测试

兼容性测试

  1. 不同设备兼容性(分辨率、设备品牌、设备ROM版本、不同ROM厂家、不同尺寸等)
  2. 不同app版本兼容性
  3. 网络兼容性(移动网络、不同运营商网络、WIFI、弱网、断网等)
  4. 第三方软件兼容性(输入法软件、蓝牙软件等)

升级测试

  1. 全新安装/卸载测试(apk安装/卸载、应用市场软件内安装/卸载等)
  2. 增量/全量升级测试(数字签名测试、跨版本升级等)
  3. 系统资源不足时测试(低电量、断电、硬盘空间不足、内存不足等)

接口测试

  1. 数据正确性测试(按接口文档和数据库存储的数据进行计算后验证)
  2. 网络连通性(DNS异常、client断网、server端超时、server端异常、client签名证书异常等)
  3. 数据流量测试(是否有超大数据包传递等)
  4. 数据安全性(数据传输是否加密或混淆等)
  5. 调用方安全性(接口调用方是否鉴权、是否被模拟回调等)
  6. 系统时间篡改对功能的影响

UI测试

  1. 界面测试(UI布局、界面颜色、风格统一性、字体大小、提示文案位置、界面返回路径等)
  2. 内容测试(文案内容、错别字、版权、专利、隐私内容、敏感词、敏感图片等)
  3. 横竖屏切换、前后台切换等

交互测试

  1. app中断测试(来电话、来短信、低电量、待机、插拔数据线、插拔耳机、断网、闹钟,日历提醒,蓝牙提醒等)
  2. 多个app争夺系统资源时对app的影响(声道播放、相机等)
  3. 用户打扰测试(push通知是否在免打扰时段内通知、push通知关闭状态下是否依然通知等)
  4. 长时间使用、长时间后台等
  5. 手势测试(单指滑动,单指单击,单指双击,单指长按,单指缩放,多指点击等)

缓存量测试

  1. 第一次使用时存入缓存大量数据对app的影响
  2. 日常使用时更新缓存大量数据对app的影响
  3. 重新安装保存原缓存的大量缓存数据更新对app的影响

安全性测试

  1. 数据安全(是否加密传输、关键数据是否容易被篡改等)
  2. 证书安全(SSL证书、SSL密钥等)
  3. 临时目录数据、缓存数据是否安全存储不被恶意读取
  4. 软件权限安全性(扣费风险、隐私泄露风险、非法授权访问等)

性能测试(测试工具:LR、NeoLoad、ApacheBench、jmeter、http_load等)

  1. 极限测试(在各种边界压力情况下,如电池、存储、网速等,验证App是否能正确响应)
  2. 响应能力测试(测试各功能的响应时间要求)
  3. 压力测试(反复/长期操作下、系统资源是否占用正常)

自动化测试(工具:Selenium、Appium、Watir、Katalon Studio、Monkey Test等)

  1. 通过编写语言脚本对功能或接口进行自动化调用,减少人力测试成本。

观点仅代表自己,期待你的留言。

SpringBoot(一)

配置演进

  1. spring1.x时代xml配置,优点:取代new实例、ioc、实例对象池化管理
  2. spring2.x时代注解配置,优点:减少配置量
  3. spring3.x时代Java配置,优点:类型安全,可重构

POM简化

增加parent module依赖,自动引入默认的jar配置

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

知识点

一. WEB应用添加spring-boot-starter-web启动器

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

二. 更改jdk依赖版本,在properties下增加java.version配置jdk版本,如:

1
2
3
<properties>
<java.version>1.7</java.version>
</properties>

三. spring-boot-devtools实现热部署调试

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

四. spring-boot-test实现单元测试

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<scope>test</scope>
</dependency>

1
2
3
4
5
6
7
8
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = Application.class)
public class TestCls {
@Test
public void hello() {
//....
}
}

五. 程序读取配置文件
src/main/resources/application.properties

1
cfg.name=abc

1) 环境对象实例中获取

1
2
3
4
5
6
7
8
public class A {
@Resource
private org.springframework.core.env.Environment env;

public void hello() {
System.out.println(env.getProperty("cfg.name"));// 输出abc
}
}

2) Value获取

1
2
3
4
5
6
7
8
public class A {
@Value("${cfg.name}")
private String cfgName;

public void hello() {
System.out.println(cfgName);// 输出abc
}
}

3) ConfigurationProperties获取

1
2
3
4
5
6
@Component
@ConfigurationProperties("cfg")
@Setter
public class A {
private String name;
}

六. spring-boot-starter-actuator实现应用运行状态监控

  • 设置management.endpoint.<id>.enabled=true/false(id是endpoint的id)来完成一个endpoint的开启和关闭
  • 自定义检测指标:实现HealthIndicator接口或继承AbstractHealthIndicator类
  • 可通过Spring Security或Shiro来保障Actuator Endpoints的安全
  • 设置management.endpoint.health.show-details=always则可查看详细的应用健康信息

七、框架接口

  • CommandLineRunner、ApplicationRunner接口在容器启动成功后的Spring框架加载前回调(类似开机自启动),适合初始读取资源或加载通讯证书等

打包

一. 编译生成jar包程序(默认包类型)

1
2
3
4
5
6
7
8
9
<packaging>jar</packaging>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

输出: 生成带tomcat-plugin的package.jar,通过java -jar package.jar运行。

二. 编译生成war包程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--1. 更改package类型-->
<packaging>war</packaging>
<!--2. 修改tomcat的依赖为provided-->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<!--3. 设置输出package的名称-->
<finalName>package</finalName>
</build>

调整启动类:

1
2
3
4
5
6
7
8
9
10
@SpringBootApplication
public class ClientApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(ClientApplication.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(getClass());
}
}

输出: 生成package.war,将war放入到tomcat或其它的支持java运行的服务器中运行。

三. appassembler-maven-plugin生成跨平台启动脚本

1
2
3
4
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>appassembler-maven-plugin</artifactId>
</plugin>

执行 mvn package appassembler:assemble完成脚本的生成
优点:

  • jar包分散容易管理
  • 编译发布代码速度快
  • 可配置jvm相关启动参数
  • 日志管理
  • shell启动,停止、重启方便

四、docker-maven-plugin生成docker使用包
优点:

  • 减少Dockerfile编写
  • 增强代码一致性
  • 编译部署方便
  • 快速运维。
    缺点:
  • 编译后包比较大
  • 无法替换局部jar文件

观点仅代表自己,期待你的留言。

Redis数据类型特点

Redis数据类型特点

  1. string:可独立设置expire time。
  2. list:特有的LPUSH、RPUSH、LPOP、RPOP、LPOPRPUSH等函数可以无缝的支持生产者、消费者架构模式。
  3. hash:基于Hash算法的,对于项的查找时间复杂度是O(1)。
  4. set:最大的特点就是集合的计算能力,inter交集、union并集、diff差集,这些特点可以用来做高性能的交叉计算或者剔除数据。SINTERSTORE命令将交集计算后的结果存储在一个目标集合中。
  5. zset:在set的基础上提供了排序的功能,可用于多维度算法计算得分后找最佳结果的场景。

由于Redis是Signle-Thread单线程模型,基于这个特性我们就可以使用Redis提供的pipeline管道来提交一连串带有逻辑的命令集合,这些命令在处理期间不会被其他客户端的命令干扰。


观点仅代表自己,期待你的留言。

Shiro完成RestfulApi的会话保持实例

背景

针对用户身份权限管理包含账户权限登录认证+会话保持两个部分,在 移动端+服务平台前后端分离 的项目框架下,一般会涉及到通过token来进行用户登录会话的保持。
以下我将通过在HTTP Header中增加token的方式在RestfulApi的服务端进行权限校验与会话保持。

扩展分析

  1. 扩展org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO: 完成用户Session的存取。
  2. 扩展org.apache.shiro.web.session.mgt.DefaultWebSessionManager: 完成用户Session标识的获取。

源码

org.wujianjun.apps.web.auth.TokenSessionManager

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class TokenSessionManager extends DefaultWebSessionManager {

public static final String ACCESS_TOKEN = "x-access-token";
private final Logger logger = LoggerFactory.getLogger(getClass());

@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
final String accessToken = WebUtils.toHttp(request).getHeader(this.ACCESS_TOKEN);
if (StringUtils.isBlank(accessToken)) {
return null;
}
// 设置当前session状态
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, ShiroHttpServletRequest.URL_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, accessToken);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
return accessToken;
}
}

org.wujianjun.apps.web.auth.RedisSessionDAO

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
@Component
public class RedisSessionDAO extends EnterpriseCacheSessionDAO {

@Resource
private RedisTemplate<String, String> redisTemplate;

public RedisSessionDAO(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}

public RedisTemplate<String, String> getRedisTemplate() {
return redisTemplate;
}

public void setRedisTemplate(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}

@Override
protected Session doReadSession(Serializable serializable) {
final Object validAccessToken = redisTemplate.opsForHash().get(RedisConst.REDIS_ACCESS_TOKEN_KEY, serializable);
if (validAccessToken == null) {
return null;
}
final SimpleSession simpleSession = new SimpleSession();
simpleSession.setId(serializable);
final SysUser sysUser = JSON.parseObject(validAccessToken.toString(), SysUser.class);
simpleSession.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, new SimplePrincipalCollection(sysUser, "authorRealm"));
simpleSession.setAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY, Boolean.TRUE);
return simpleSession;
}

@Override
protected void doUpdate(Session session) {
PrincipalCollection existingPrincipals = (PrincipalCollection)session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
if (existingPrincipals == null) {
return;
}
final Object primaryPrincipal = existingPrincipals.getPrimaryPrincipal();
if (primaryPrincipal instanceof SysUser) {
final SysUser sysUser = (SysUser)primaryPrincipal;
redisTemplate.opsForHash().put(RedisConst.REDIS_ACCESS_TOKEN_KEY, session.getId(), JSON.toJSONString(sysUser));
}
}

@Override
protected void doDelete(Session session) {
redisTemplate.opsForHash().delete(RedisConst.REDIS_ACCESS_TOKEN_KEY, session.getId());
}
}

spring-shiro.xml

1
2
3
4
5
6
7
8
9
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager" p:realm-ref="authorRealm">
<property name="cacheManager">
<bean class="org.apache.shiro.cache.ehcache.EhCacheManager" />
</property>
<property name="sessionManager">
<bean class="org.wujianjun.apps.web.auth.TokenSessionManager" p:sessionDAO-ref="redisSessionDAO"
p:deleteInvalidSessions="false" p:sessionIdCookieEnabled="false" p:sessionValidationSchedulerEnabled="false"/>

</property>
</bean>

SessoinId扩展

如果需要自己定义sessionId的生成,只需要给 org.apache.shiro.session.mgt.eis.AbstractSessionDAO 设置sessionIdGenerator的属性值即可。

1
2
3
4
5
<bean class="org.wujianjun.apps.web.auth.RedisSessionDAO">
<property name="sessionIdGenerator">
<bean class="org.wujianjun.apps.web.auth.JWTSessionIdGenerator" />
</property>
</bean>


观点仅代表自己,期待你的留言。

使用openssl与keytool完成https配置实例(转)

keytool单向认证实例

1) 为服务器生成证书

1
wujianjun@smzc ~ $ keytool -genkey -keyalg RSA -dname "cn=127.0.0.1,ou=inspur,o=none,l=shandong,st=jinan,c=cn" -alias server -keypass 111111 -keystore server.keystore -storepass 111111 -validity 3650

注:cn=127.0.0.1配置的是服务器IP

2) 生成csr

生成csr文件用于提交CA认证生成证书使用。

1
wujianjun@smzc ~ $ keytool -certReq -alias server -keystore server.keystore -file ca.csr

3) 生成cer

这个ca.cer是为了解决不信任时要导入的

1
wujianjun@smzc ~ $ keytool -export -alias server -keystore server.keystore -file ca.cer -storepass 111111

4) tomcat配置ssl

clientAuth=”false”代表单向认证,配置如下:

1
2
3
4
5
<Connector SSLEnabled="true" clientAuth="false"
maxThreads="150" port="8443"
protocol="org.apache.coyote.http11.Http11Protocol"或者HTTP/1.1
scheme="https" secure="true" sslProtocol="TLS"
keystoreFile="/home/server.keystore" keystorePass="111111"/>

注: Http11Protocol支持HTTP/1.1协议,是http1.1协议的ProtocolHandler实现。

5) 常见问题

  • 服务器的证书不受信任
    解决方法:
    1、选择“继续前往(不安全)”,也能访问,但是此时就是以普通的HTTP方式进行信息传输了。
    2、选择生成的ca.cer文件,将证书存储在 “受信任的证书颁发机构” ,就可以通过HTTPS正常访问了。
  • 程序访问Https异常
    sun.security.validator.ValidatorException: PKIX path building failed…
    需要将生成的证书(ca.cer ) 导入到jdk中
    执行以下命令:
    1
    wujianjun@smzc ~ $ keytool -import -alias tomcatsso -file "ca.cer" -keystore "D:\java\jdk1.6.0_11\jre\lib\security\cacerts" -storepass changeit

其中changeit是jre默认的密码。
No subject alternative names present,请在生成keystore 注意CN必须要为域名(或机器名称)例如 localhost 不能为IP 。
No name matching localhost found,表示你生成keystore CN的名称和你访问的名称不一致。

openssl双向认证实例

Linux环境下,在home下建立out32dll目录,在此目录下建立ca、client、server三个文件夹。以下命令均在out32dll目录下执行。

1) 模拟CA生成证书

  • 创建私钥

    1
    wujianjun@smzc ~/out32dll $ openssl genrsa -out ca/ca-key.pem 1024
  • 创建证书请求

    1
    wujianjun@smzc ~/out32dll $ openssl req -new -out ca/ca-req.csr -key ca/ca-key.pem
  • 自签署证书

    1
    wujianjun@smzc ~/out32dll $ openssl x509 -req -in ca/ca-req.csr -out ca/ca-cert.pem -signkey ca/ca-key.pem -days 3650
  • 将证书导出成浏览器支持的.p12格式 (供浏览器不受信任时导入)

    1
    wujianjun@smzc ~/out32dll $ openssl pkcs12 -export -clcerts -in ca/ca-cert.pem -inkey ca/ca-key.pem -out ca/ca.p12

密码:111111

2) 生成Server证书

  • 创建私钥

    1
    wujianjun@smzc ~/out32dll $ openssl genrsa -out server/server-key.pem 1024
  • 创建Server证书请求

    1
    wujianjun@smzc ~/out32dll $ openssl req -new -out server/server-req.csr -key server/server-key.pem
  • 使用CA证书签署Server证书

    1
    wujianjun@smzc ~/out32dll $ openssl x509 -req -in server/server-req.csr -out server/server-cert.pem -signkey server/server-key.pem -CA ca/ca-cert.pem -CAkey ca/ca-key.pem -CAcreateserial -days 3650
  • 将证书导出成浏览器支持的.p12格式

    1
    wujianjun@smzc ~/out32dll $ openssl pkcs12 -export -clcerts -in server/server-cert.pem -inkey server/server-key.pem -out server/server.p12

密码:111111

3) 生成Clinet证书

  • 创建私钥

    1
    wujianjun@smzc ~/out32dll $ openssl genrsa -out client/client-key.pem 1024
  • 创建Client证书请求

    1
    wujianjun@smzc ~/out32dll $ openssl req -new -out client/client-req.csr -key client/client-key.pem
  • 使用CA证书签署Client证书

    1
    wujianjun@smzc ~/out32dll $ openssl x509 -req -in client/client-req.csr -out client/client-cert.pem -signkey client/client-key.pem -CA ca/ca-cert.pem -CAkey ca/ca-key.pem -CAcreateserial -days 3650
  • 将证书导出成浏览器支持的.p12格式

    1
    wujianjun@smzc ~/out32dll $ openssl pkcs12 -export -clcerts -in client/client-cert.pem -inkey client/client-key.pem -out client/client.p12

密码:111111

4) 根据CA证书生成jks文件

1
wujianjun@smzc ~/out32dll $ keytool -keystore truststore.jks -keypass 222222 -storepass 222222 -alias ca -import -trustcacerts -file /home/out32dll/ca/ca-cert.pem

5) 服务器配置(tomcat6示例)

修改conf/server.xml。 将keystoreFile、truststoreFile的路径填写为正确的放置路径。

1
2
3
4
5
<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"
maxThreads="150" scheme="https" secure="true"
clientAuth="true" sslProtocol="TLS"
keystoreFile="/home/out32dll/server/server.p12" keystorePass="111111" keystoreType="PKCS12"
truststoreFile="/home/out32dll/truststore.jks" truststorePass="222222" truststoreType="JKS"/>

6) 导入证书(IE示例)

将ca.p12,client.p12分别导入到IE中去(打开IE->Internet选项->内容->证书)。 ca.p12导入至 受信任的根证书颁发机构,client.p12导入至个人。
进行浏览器访问双向Https时会弹出客户端证书供选择


观点仅代表自己,期待你的留言。

Linux常用命令备注(nslookup,find,grep,sed,awk)

nslookup 指定DNS服务器解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
wujianjun@smzc ~ $ nslookup m.vvip-u.com 10.28.17.101
Server: 10.28.17.101
Address: 10.28.17.101#53

Name: m.vvip-u.com
Address: 10.28.17.80

wujianjun@smzc ~ $ nslookup m.vvip-u.com
Server: 127.0.1.1
Address: 127.0.1.1#53

Non-authoritative answer:
Name: m.vvip-u.com
Address: 120.77.124.39

BIND 局域网DNS程序

https://www.isc.org/downloads/bind/

find包含某关键字的文件内容

1
wujianjun@smzc ~ $ find /smapp/logs -name “*” | xargs grep “keywords”
1
wujianjun@smzc ~ $ grep -nR "keywords" /smapp/logs

grep + RegExp (提取行首不是abc的行)

1
wujianjun@smzc ~ $ grep “^[^abc]” /smapp/logs

文本替换

1
wujianjun@smzc ~ $ sed -n ‘s/oldk/newk/g’ file

先删除1到3行,然后用bb替换aa;

1
wujianjun@smzc ~ $ sed -e ’1,3d’ -e ‘s/aa/bb/g’ file

文本处理

打印所有内容行(相当于cat)

1
2
3
4
5
wujianjun@smzc ~ $ awk '{print $0}' result.txt
18100000011 - "status":"SUCCESS"
18100000012 - "status":"SUCCESS"
18100000013 - "status":"SUCCESS"
18100000014 - "status":"SUCCESS"

空格分隔逐行内容并打印第一个内容

1
2
3
4
5
wujianjun@smzc ~ $ awk '{print $1}' result.txt
18100000011
18100000012
18100000013
18100000014


观点仅代表自己,期待你的留言。

Shiro完成短信验证码登录的实例

分析

Shiro通过org.apache.shiro.realm.Realm进行身份与权限的校验,通过org.apache.shiro.realm.jdbc.JdbcRealm来查看,
我决定继承自org.apache.shiro.realm.AuthorizingRealm来实现身份校验逻辑和权限标识符获取的逻辑。

org.apache.shiro.realm.AuthorizingRealm
两个抽象方法:
1、 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection):
主要用于通过当前身份来获取Permissions。

2、 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException
主要是用户在登录时调用此方法完成用户身份初步校验,注意:校验凭证(密码、验证码等)由Shiro进行校验不需要手动在此进行校验。

源码

spring-shiro.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
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm">
<bean class="com.smzc.apps.order.web.auth.OrderAuthorizingRealm" />
</property>
<property name="cacheManager">
<bean class="org.apache.shiro.cache.ehcache.EhCacheManager" />
</property>
<property name="sessionManager">
<bean class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<property name="sessionIdCookie">
<bean class="org.apache.shiro.web.servlet.SimpleCookie">
<constructor-arg value="SESSIONID"/>
<property name="httpOnly" value="true"/>
<property name="maxAge" value="-1"/>
</bean>
</property>
</bean>
</property>
</bean>

<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager" />
<property name="arguments">
<list>
<ref bean="securityManager"/>
</list>
</property>
</bean>

<!-- shiroFilter -->
<bean id="shiroFilterFactoryBean" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- Shiro的核心安全接口,这个属性是必须的 -->
<property name="securityManager" ref="securityManager" />
<!-- 要求登录时的链接,非必须的属性,默认会自动寻找Web工程根目录下的"/login.jsp"页面 -->
<property name="loginUrl" value="/views/login" />
<!-- 登录成功后要跳转的连接 -->
<property name="successUrl" value="/views/index" />
<!-- 用户访问未对其授权的资源时,所显示的连接 -->
<property name="unauthorizedUrl" value="/views/common/unauthorized" />
<property name="filters">
<map>
<entry key="logout">
<bean class="org.apache.shiro.web.filter.authc.LogoutFilter">
<property name="redirectUrl" value="/views/login"/>
</bean>
</entry>
</map>
</property>
<!-- 配置Shiro过滤链 -->
<property name="filterChainDefinitions">
<value>
/api/user/login/** = anon
/api/** = authc
/logout = logout
/** = anon
</value>
</property>
</bean>

OrderAuthorizingRealm.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class OrderAuthorizingRealm extends AuthorizingRealm implements InitializingBean {
@Resource
private AuthorizingService authorizingService;
@Value("${app.role.permissions:}")
private String rolePermissionsString;
private Map<String, Set<String>> rolePermissions;

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
final SysUser sysUser = (SysUser)super.getAvailablePrincipal(principalCollection);
final String role = sysUser.getUserRole().getRole();
final SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(Sets.newHashSet(role));
authorizationInfo.setStringPermissions(rolePermissions.get(role));
return authorizationInfo;
}

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
final UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
final String mobile = usernamePasswordToken.getUsername();
final String validateCode = authorizingService.getCodeByMobile(mobile);
if (StringUtils.isBlank(validateCode)) {
throw new ExpiredCredentialsException("验证码已失效#[" + mobile + "]");
}
final List<SysUser> sysUserList = authorizingService.getUserByMobile(mobile);
if (CollectionUtils.isEmpty(sysUserList)) {
throw new UnknownAccountException("用户账户不存在#[" + mobile + "]");
}
if (sysUserList.size() > 1) {
throw new ConcurrentAccessException("用户存在多个账户#[" + mobile + "]");
}
final SysUser sysUser = sysUserList.iterator().next();
if (!sysUser.getStatus().isAllowLogin()) {
throw new DisabledAccountException("用户账户不可用#[" + mobile + "]");
}
// 注意:SimpleAuthenticationInfo中principal表示验证主体,供后续获取权限标识符和当前登录用户信息使用, credentials表示正确的凭证串,shiro会自动与用户登录时填入的值进行密钥匹配后进行对比。
// credentials也可以通过setCredentialsSalt设置加密的salt
return new SimpleAuthenticationInfo(sysUser, validateCode.toCharArray(), super.getName());
}

@Override
public void afterPropertiesSet() throws Exception {
if (StringUtils.isBlank(rolePermissionsString)) {
throw new NullPointerException("未初始化权限配置");
}
rolePermissions = Maps.newHashMap();
final JSONObject jsonObject = JSON.parseObject(rolePermissionsString);
final Iterator<Map.Entry<String, Object>> entryIterator = jsonObject.entrySet().iterator();
while (entryIterator.hasNext()) {
final Map.Entry<String, Object> objectEntry = entryIterator.next();
rolePermissions.put(objectEntry.getKey(), Sets.newHashSet(objectEntry.getValue().toString().split(",")));
}
}
}

UserApiController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@RestController
@RequestMapping(value = "/api/user", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public class UserApiController extends BasicApiController {

@Resource
private SysUserService userService;

/**
* 用户登录
*/

@RequestMapping(value = "/login/{mobileNo}/{code}", method = RequestMethod.GET)
public Output login(@PathVariable String mobileNo, @PathVariable String code) throws ServiceException {
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(mobileNo, code);
subject.login(token);
token.clear();
return MapOutput.createSuccess();
}

/**
* 用户登出
*/

@RequestMapping(value = "/logout/{mobileNo}", method = RequestMethod.GET)
public Output logout(@PathVariable String mobileNo) throws ServiceException {
SecurityUtils.getSubject().logout();
return MapOutput.createSuccess();
}
}

Shiro凭证匹配器配置

加密方式都为org.apache.shiro.authc.credential.CredentialsMatcher的实现类
通过org.apache.shiro.realm.AuthenticatingRealmprivate CredentialsMatcher credentialsMatcher设置凭据的匹配实现类
如:

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
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm">
<!--BEGIN: 设置凭证器------------------>
<bean class="com.smzc.apps.order.web.auth.OrderAuthorizingRealm">
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.PasswordMatcher" />
</property>
</bean>
<!--END: 设置凭证器------------------>
</property>
<property name="cacheManager">
<bean class="org.apache.shiro.cache.ehcache.EhCacheManager" />
</property>
<property name="sessionManager">
<bean class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<property name="sessionIdCookie">
<bean class="org.apache.shiro.web.servlet.SimpleCookie">
<constructor-arg value="SESSIONID"/>
<property name="httpOnly" value="true"/>
<property name="maxAge" value="-1"/>
</bean>
</property>
</bean>
</property>
</bean>

Shiro内置的FilterChain

  1. Shiro验证URL时,URL匹配成功便不再继续匹配查找(所以要注意配置文件中的URL顺序,尤其在使用通配符时)
    故filterChainDefinitions的配置顺序为自上而下,以最上面的为准
  2. 当运行一个Web应用程序时,Shiro将会创建一些有用的默认Filter实例,并自动地在[main]项中将它们置为可用
    自动地可用的默认的Filter实例是被DefaultFilter枚举类定义的,枚举的名称字段就是可供配置的名称
    anon—————org.apache.shiro.web.filter.authc.AnonymousFilter
    authc————–org.apache.shiro.web.filter.authc.FormAuthenticationFilter
    authcBasic———org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
    logout————-org.apache.shiro.web.filter.authc.LogoutFilter
    noSessionCreation–org.apache.shiro.web.filter.session.NoSessionCreationFilter
    perms————–org.apache.shiro.web.filter.authz.PermissionAuthorizationFilter
    port—————org.apache.shiro.web.filter.authz.PortFilter
    rest—————org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
    roles————–org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
    ssl—————-org.apache.shiro.web.filter.authz.SslFilter
    user—————org.apache.shiro.web.filter.authz.UserFilter

  3. 通常可将这些过滤器分为两组
    anon,authc,authcBasic,user是第一组认证过滤器
    perms,port,rest,roles,ssl是第二组授权过滤器
    注意user和authc不同:当应用开启了rememberMe时,用户下次访问时可以是一个user,但绝不会是authc,因为authc是需要重新认证的
    user表示用户不一定已通过认证,只要曾被Shiro记住过登录状态的用户就可以正常发起请求,比如rememberMe
    说白了,以前的一个用户登录时开启了rememberMe,然后他关闭浏览器,下次再访问时他就是一个user,而不会authc

  4. 举几个例子
    /admin=authc,roles[admin] 表示用户必需已通过认证,并拥有admin角色才可以正常发起’/admin’请求
    /edit=authc,perms[admin:edit] 表示用户必需已通过认证,并拥有admin:edit权限才可以正常发起’/edit’请求
    /home=user 表示用户不一定需要已经通过认证,只需要曾经被Shiro记住过登录状态就可以正常发起’/home’请求

  5. 各默认过滤器常用如下(注意URL Pattern里用到的是两颗星,这样才能实现任意层次的全匹配)
    /admins/=anon 无参,表示可匿名使用,可以理解为匿名用户或游客
    /admins/user/
    =authc 无参,表示需认证才能使用
    /admins/user/=authcBasic 无参,表示httpBasic认证
    /admins/user/
    =user 无参,表示必须存在用户,当登入操作时不做检查
    /admins/user/=ssl 无参,表示安全的URL请求,协议为https
    /admins/user/
    =perms[user:add:]
    参数可写多个,多参时必须加上引号,且参数之间用逗号分割,如/admins/user/**=perms[“user:add:
    ,user:modify:“]
    当有多个参数时必须每个参数都通过才算通过,相当于isPermitedAll()方法
    /admins/user/=port[8081]
    当请求的URL端口不是8081时,跳转到schemal://serverName:8081?queryString
    其中schmal是协议http或https等,serverName是你访问的Host,8081是Port端口,queryString是你访问的URL里的?后面的参数
    /admins/user/
    =rest[user]
    根据请求的方法,相当于/admins/user/=perms[user:method],其中method为post,get,delete等
    /admins/user/
    =roles[admin]
    参数可写多个,多个时必须加上引号,且参数之间用逗号分割,如/admins/user/*
    =roles[“admin,guest”]
    当有多个参数时必须每个参数都通过才算通过,相当于hasAllRoles()方法


观点仅代表自己,期待你的留言。

Java对中国夏令时的展示

中国夏令时制度实行时间

中华人民共和国在1986年~1991年实行了夏令时制度,每年夏令时实行时间如下:

1
2
3
4
5
6
1986年5月4日至9月14日(1986年因是实行夏令时的第一年,从5月4日开始到9月14日结束)
1987年4月12日至9月13日,
1988年4月10日至9月11日,
1989年4月16日至9月17日,
1990年4月15日至9月16日,
1991年4月14日至9月15日。

JDK已有对夏令时的处理

Java的jdk在Date的toString中已经包含夏令时的计算,以下代码可以印证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void main(String[] args) throws Exception {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String sTime = "1986-09-13 22:00:00";
sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));
Date time = sdf.parse(sTime);
System.out.println(time.getTime());
System.out.println(time);
Calendar cd = Calendar.getInstance();
cd.setTime(time);
// 2小时以后是几点?
cd.add(Calendar.HOUR, 2);
time = cd.getTime();
System.out.println("------------------------------");
System.out.println(time.getTime());
System.out.println(time);
}

打印结果:

1
2
3
4
5
527000400000
Sat Sep 13 22:00:00 CDT 1986
------------------------------
527007600000
Sat Sep 13 23:00:00 CST 1986

分析: 上面代码中1986-09-1322:00:00加上2小时,应该变为1986-09-13 24:00:00(或者1986-09-14 00:00:00),但由于在9月14日零点退出夏令时,时钟向后调整1小时,实际变为1986-09-13 23:00:00。
注意:从9月14日零点退出夏令时,java的Date.toString打印的时区也从CDT恢复为CST( ChinaStandard Time UT+8:00)。

又如:

1
2
3
4
5
6
wujianjun@smzc ~ $ date
2018年 08月 24日 星期五 19:20:41 CST
wujianjun@smzc ~ $ date -d @579279600
1988年 05月 11日 星期三 00:00:00 CDT
wujianjun@smzc ~ $ date -d @599587200
1989年 01月 01日 星期日 00:00:00 CST

结论: 只要是在实行夏令时的时段都是CDT时间,其它都是CST


观点仅代表自己,期待你的留言。