以centos 系统为例:

添加httpd-tools工具

1
yum -y install httpd-tools

修改nginx 配置文件

1
vi /etc/nginx/conf.d/default.conf

在server内增加

1
2
3
4
location /basic {
auth_basic "Basic Auth";
auth_basic_user_file "/etc/nginx/.htpasswd";
}

创建一个用户 fedora 替换成你自己的账号,回车输入密码并确认

1
htpasswd -c /etc/nginx/.htpasswd fedora

重启nginx服务

1
/etc/rc.d/init.d/nginx restart

或者

1
service nginx reload

1
service nginx restart

struts2 modelDriven 高级应用

Build Status

#封装的第四种Action传参方式

##传统的struts传参有三种

    1. 通过属性传参
    1. 通过域模型传参
    1. 通过模型驱动传参数

##以上这三种各有利弊,总之一句话概括,无法完全的实现业务和参数分离.总得在自己的Action中定义相关的get set 方法.

希望能实现参数传递跟业务action完全分离.故封装了模型驱动参数获取方式.

#如此一来,action 还可以这样写:

-Action

1
2
3
4
5
6
public class YourAction extends BaseActionSupport<YourParam> {

//TODO your code ....
//在需要获取request参数的地方只需要通过 formParam对象get你需要的参数即可

}

-param class 就是一个简单的po类.随你写喽!

1
2
3
4
public class yourParam{
// 各种 param fields
// 各种 get set
}

#OK 所有的action请求,都会自动的封装到你定义的param类中. 前提这个类要有不带参数的构造方法,要有你希望获取的参数的get set 方法.

其实就是把action中的各种 get set 放到了一个统一的param类中.

一个action 一个 param类. 是不是很清晰.业务跟参数代码分离了. struts action 是不是更易读了.

附: 顺带将 BaseActionSupport 放入了一个applicationContext

这样所有的action 都可以直接使用applicationContext 对象,如果您不喜欢可以自行去掉.

##步骤如下:

    1. 删除 protected ApplicationContext applicationContext;
    1. 删除 applicationContext 的 set方法
    1. 去掉实现的接口 ApplicationContextAware

#不过建议留着吧.

  • 当你用上 spring 的event 的时候,你会需要它.
  • 当你需要动态的根据className 或者 classType 获取bean 的时候,你会需要它.

图

spring-boot-actuator-monitor

基于spring boot的 监控平台

#spring-boot-admin 监控后台

  1. 新建一个maven工程 boot-admin , 继承 spring-boot-starter-parent

    1
    2
    3
    4
    5
    6

    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.2.4.RELEASE</version>
    </parent>
  2. 加入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-server</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-server-ui</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>
<optional>false</optional>
</dependency>
  1. 加入plugin 以 exec jar包运行

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

    <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <executions>
    <execution>
    <goals>
    <goal>repackage</goal>
    </goals>
    <configuration>
    <classifier>exec</classifier>
    </configuration>
    </execution>
    </executions>
    </plugin>
  2. 新建执行执行类 com.izerui.boot.admin.Application.java

1
2
3
4
5
6
7
8
9
@SpringBootApplication
@EnableAdminServer
@EnableDiscoveryClient
public class Application {

public static void main(String[] args) {
SpringApplication.run(Application.class,args);
}
}
  1. 声明监控后台的应用名称、端口、上下文、基本授权验证用户名、密码等信息 application.properties
1
2
3
4
5
6
7
# application
server.port = 8888

# basic security
security.ignored=/api/**
security.user.name=boot
security.user.password=boot1234
  1. mvn install 后 执行 java -jar boot-admin-exec.jar

  2. 访问 http://主机ID:端口/boot-admin


注册应用到监控后台

  1. 确保应用继承 spring-boot-starter-parent

    1
    2
    3
    4
    5
    6

    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.2.4.RELEASE</version>
    </parent>
  2. 引入依赖

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>
<optional>false</optional>
</dependency>
  1. 配置被监控的应用的名称和监控后台地址 application.properties

    jar 方式运行

    web application

    server.port=28584
    info.version=@pom.version@
    info.info=微信代理服务
    management.context-path=/management
    server.context-path=/@pom.artifactId@
    endpoints.jmx.domain=@pom.artifactId@
    spring.application.name=@pom.artifactId@

    boot admin

    spring.boot.admin.url=http://192.168.1.128:8888/boot-admin


    war 方式运行

    web application

    info.version=@pom.version@
    info.info=阿里支付服务
    management.context-path=/management
    endpoints.jmx.domain=@pom.artifactId@
    spring.application.name=@pom.artifactId@

    boot admin

    spring.boot.admin.url=http://192.168.1.128:8888
    spring.boot.admin.client.service-url=http://192.168.1.106:8098/qq-pay-web

注意: 别忘了添加logging配置
# LOGGING
logging.file=/tmp/logs/offline-proxy-tcp.log

注意: 有些应用被权限框架拦截了。故需要加入 
management.context-path=/management 
并在权限框架内 将 /management/** = anon 权限放开,声明url注意上下文。
  1. 配置logback的jmx接口调用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
    <include resource="org/springframework/boot/logging/logback/base.xml"/>
    <jmxConfigurator/>
    <logger name="com.izerui" level="DEBUG"/>
    <root level="INFO">
    <appender-ref ref="CONSOLE" />
    <appender-ref ref="FILE" />
    </root>
    </configuration>
  2. 发布自定义jmx接口

在spring bean 类的头信息上增加注解 @ManagedResource(currencyTimeLimit = 20) 和 @Lazy

声明属性: @ManagedAttribute(description = "当前TCP连接数")

声明方法: @ManagedOperation(description = "关闭对应IP的所有连接")
          @ManagedOperationParameters(
            @ManagedOperationParameter(name = "ip", description = "终端IP")
          )

参考资料:

实现cas ticket基于redis的集群

目的

克服cas单点故障,将cas认证请求分发到多台cas服务器上,降低负载。

实现思路:

采用统一的ticket存取策略,所有ticket的操作都从中央缓存redis中存取。
采用session共享,session的存取都从中央缓存redis中存取。

前提:

这里只讲解如何实现cas ticket的共享,关于session的共享请移步:

实现步骤:

  1. 基于cas源码 新增模块 cas-server-integration-redis

    pom.xml 文件如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    	
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
    <artifactId>cas-server</artifactId>
    <groupId>org.jasig.cas</groupId>
    <version>4.0.2</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cas-server-integration-redis</artifactId>

    <dependencies>

    <dependency>
    <groupId>org.jasig.cas</groupId>
    <artifactId>cas-server-core</artifactId>
    <version>${project.version}</version>
    </dependency>

    <dependency>
    <groupId>org.jasig.cas</groupId>
    <artifactId>cas-server-support-saml</artifactId>
    <version>${project.version}</version>
    <scope>provided</scope>
    </dependency>


    <!-- redis -->
    <dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>1.5.1.RELEASE</version>
    </dependency>
    <dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.2</version>
    </dependency>
    <dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.6.2</version>
    </dependency>
    </dependencies>

    </project>
  2. 添加类到 cas-server-integration-redis 模块的 org.jasig.cas.ticket.registry 包下

    RedisTicketRegistry.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
/*
* Licensed to Jasig under one or more contributor license
* agreements. See the NOTICE file distributed with this work
* for additional information regarding copyright ownership.
* Jasig licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a
* copy of the License at the following location:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jasig.cas.ticket.registry;

import org.jasig.cas.ticket.ServiceTicket;
import org.jasig.cas.ticket.Ticket;
import org.jasig.cas.ticket.TicketGrantingTicket;
import org.springframework.beans.factory.DisposableBean;

import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
* Key-value ticket registry implementation that stores tickets in redis keyed on the ticket ID.
*
* @author Scott Battaglia
* @author Marvin S. Addison
* @since 3.3
*/
public final class RedisTicketRegistry extends AbstractDistributedTicketRegistry implements DisposableBean {

private final static String TICKET_PREFIX = "TICKETGRANTINGTICKET:";

/** redis client. */
@NotNull
private final TicketRedisTemplate client;

/**
* TGT cache entry timeout in seconds.
*/
@Min(0)
private final int tgtTimeout;

/**
* ST cache entry timeout in seconds.
*/
@Min(0)
private final int stTimeout;


/**
* Creates a new instance using the given redis client instance, which is presumably configured via
* <code>net.spy.redis.spring.redisClientFactoryBean</code>.
*
* @param client redis client.
* @param ticketGrantingTicketTimeOut TGT timeout in seconds.
* @param serviceTicketTimeOut ST timeout in seconds.
*/
public RedisTicketRegistry(final TicketRedisTemplate client, final int ticketGrantingTicketTimeOut,
final int serviceTicketTimeOut) {
this.tgtTimeout = ticketGrantingTicketTimeOut;
this.stTimeout = serviceTicketTimeOut;
this.client = client;
}

protected void updateTicket(final Ticket ticket) {
logger.debug("Updating ticket {}", ticket);
try {
this.client.boundValueOps(TICKET_PREFIX+ticket.getId()).set(ticket,getTimeout(ticket), TimeUnit.SECONDS);
} catch (final Exception e) {
logger.error("Failed updating {}", ticket, e);
}
}

public void addTicket(final Ticket ticket) {
logger.debug("Adding ticket {}", ticket);
try {
this.client.boundValueOps(TICKET_PREFIX+ticket.getId()).set(ticket,getTimeout(ticket),TimeUnit.SECONDS);
}catch (final Exception e) {
logger.error("Failed adding {}", ticket, e);
}
}

public boolean deleteTicket(final String ticketId) {
logger.debug("Deleting ticket {}", ticketId);
try {
this.client.delete(TICKET_PREFIX+ticketId);
return true;
} catch (final Exception e) {
logger.error("Failed deleting {}", ticketId, e);
}
return false;
}

public Ticket getTicket(final String ticketId) {
try {
final Ticket t = (Ticket) this.client.boundValueOps(TICKET_PREFIX+ticketId).get();
if (t != null) {
return getProxiedTicketInstance(t);
}
} catch (final Exception e) {
logger.error("Failed fetching {} ", ticketId, e);
}
return null;
}

/**
* {@inheritDoc}
* This operation is not supported.
*
* @throws UnsupportedOperationException if you try and call this operation.
*/
@Override
public Collection<Ticket> getTickets() {
Set<Ticket> tickets = new HashSet<Ticket>();
Set<String> keys = this.client.keys(TICKET_PREFIX + "*");
for (String key:keys){
Ticket ticket = this.client.boundValueOps(TICKET_PREFIX+key).get();
if(ticket==null){
this.client.delete(TICKET_PREFIX+key);
}else{
tickets.add(ticket);
}
}
return tickets;
}

public void destroy() throws Exception {
//do nothing
}

/**
* @param sync set to true, if updates to registry are to be synchronized
* @deprecated As of version 3.5, this operation has no effect since async writes can cause registry consistency issues.
*/
@Deprecated
public void setSynchronizeUpdatesToRegistry(final boolean sync) {}

@Override
protected boolean needsCallback() {
return true;
}

private int getTimeout(final Ticket t) {
if (t instanceof TicketGrantingTicket) {
return this.tgtTimeout;
} else if (t instanceof ServiceTicket) {
return this.stTimeout;
}
throw new IllegalArgumentException("Invalid ticket type");
}
}
**TicketRedisTemplate.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
package org.jasig.cas.ticket.registry;

import org.jasig.cas.ticket.Ticket;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
* Created by serv on 2015/7/19.
*/
public class TicketRedisTemplate extends RedisTemplate<String, Ticket> {

public TicketRedisTemplate() {
RedisSerializer<String> string = new StringRedisSerializer();
JdkSerializationRedisSerializer jdk = new JdkSerializationRedisSerializer();
setKeySerializer(string);
setValueSerializer(jdk);
setHashKeySerializer(string);
setHashValueSerializer(jdk);
}

public TicketRedisTemplate(RedisConnectionFactory connectionFactory) {
this();
setConnectionFactory(connectionFactory);
afterPropertiesSet();
}
}
  1. cas-server-webapp 添加刚才新增模块的依赖

    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.jasig.cas</groupId>
    <artifactId>cas-server-integration-redis</artifactId>
    <version>${project.version}</version>
    </dependency>
  2. 修改 spring配置文件 cas-server-webapp 模块 WEB-INF\spring-configuration\ticketRegistry.xml

    {% codeblock %}
    		
    		{% endcodeblock %}
    

    替换为

    {% codeblock %}
    		
    	        
    	
    	        
    	        
    	
    	        
    	        
    	    
    	
    	    
    	
    	    
    		{% endcodeblock %}
    

    注意: 里面的 jedisConnFactory链接信息 修改为自己的连接串,这里选择database 1为存放cas票据的数据库

  1. 重新编译 mvn install

    生成的cas.war 部署到多个已经做过session共享的tomcat容器中。

tomcat-redis-session-manager

使用redis配置tomcat共享session

结构图:

分析:

分布式web server集群部署后需要实现session共享,针对 tomcat 服务器的实现方案多种多样,
比如 tomcat cluster session 广播、nginx IP hash策略、nginx sticky module等方案,
本文主要介绍了使用 redis 服务器进行 session 统一存储管理的共享方案。

必要环境:

  • java1.7
  • tomcat7
  • redis2.8

nginx 负载均衡配置

  1. 修改nginx conf配置文件加入
1
2
3
4
5
6
upstream tomcat {
server 200.10.10.67:8110;
server 200.10.10.67:8120;
server 200.10.10.44:8110;
server 200.10.10.66:8110;
}
  1. 配置 相应的server或者 location地址到 http://tomcat

tomcat session共享配置步骤

  1. 添加redis session集群依赖的jar包到 TOMCAT_BASE/lib 目录下

  1. 修改 TOMCAT_BASE/conf 目录下的 context.xml 文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <Valve className="com.orangefunction.tomcat.redissessions.RedisSessionHandlerValve" />
    <Manager className="com.orangefunction.tomcat.redissessions.RedisSessionManager"
    host="localhost"
    port="6379"
    database="0"
    maxInactiveInterval="60"
    sessionPersistPolicies="PERSIST_POLICY_1,PERSIST_POLICY_2,.."
    sentinelMaster="SentinelMasterName"
    sentinels="sentinel-host-1:port,sentinel-host-2:port,.."/>

    属性解释:

    • host redis服务器地址
    • port redis服务器的端口号
    • database 要使用的redis数据库索引
    • maxInactiveInterval session最大空闲超时时间,如果不填则使用tomcat的超时时长,一般tomcat默认为1800 即半个小时
    • sessionPersistPolicies session保存策略,除了默认的策略还可以选择的策略有:
    • [SAVE_ON_CHANGE]:每次 session.setAttribute() 、 session.removeAttribute() 触发都会保存. 注意:此功能无法检测已经存在redis的特定属性的变化,权衡:这种策略会略微降低会话的性能,任何改变都会保存到redis中.
    • [ALWAYS_SAVE_AFTER_REQUEST]: 每一个request请求后都强制保存,无论是否检测到变化.注意:对于更改一个已经存储在redis中的会话属性,该选项特别有用. 权衡:如果不是所有的request请求都要求改变会话属性的话不推荐使用,因为会增加并发竞争的情况。
    • sentinelMaster redis集群主节点名称(Redis集群是以分片(Sharding)加主从的方式搭建,满足可扩展性的要求)
    • sentinels redis集群列表配置(类似zookeeper,通过多个Sentinel来提高系统的可用性)
    • connectionPoolMaxTotal
    • connectionPoolMaxIdle jedis最大能够保持idel状态的连接数
    • connectionPoolMinIdle 与connectionPoolMaxIdle相反
    • maxWaitMillis jedis池没有对象返回时,最大等待时间
    • minEvictableIdleTimeMillis
    • softMinEvictableIdleTimeMillis
    • numTestsPerEvictionRun
    • testOnCreate
    • testOnBorrow jedis调用borrowObject方法时,是否进行有效检查
    • testOnReturn jedis调用returnObject方法时,是否进行有效检查
    • testWhileIdle
    • timeBetweenEvictionRunsMillis
    • evictionPolicyClassName
    • blockWhenExhausted
    • jmxEnabled
    • jmxNameBase
    • jmxNamePrefix
  2. 重启tomcat,session存储即可生效

又是一个清明小长假,一个人远在深圳也没有回家,闲来无事写了一个zookeeper的数据浏览器。
上图:

使用上的技术:

  1. spring boot
  2. flex
  3. zookeeper curator client
  4. shiro security
  5. fastxml jackson
  6. spring mvc restful

基于spring boot微服务做的一个应用,以 独立jar包发布,内嵌了tomcat容器。因为一直也没有找到合适的可以方便管理和查看 zookeeper 数据的一个东东。就自己做了一个,方便公司的zk管理。

有兴趣的。或者也有同样需求的可以down下来玩玩。 东西虽小五张俱全。操作权限该有的都有。

项目地址: https://github.com/izerui/zookeeper-explorer

网上类似的代码也不少.但是都只是自己工程中单独贴出来的代码片段. 并不适合通用场景,存在很多bug
举个例子:

  1. 如果画框所属的父组件存在滚动的条. 获取的矩形区域大小和坐标就会错乱
  2. 如果画矩形过程中,鼠标移出目标组件, 该矩形事件无法继续传播,等等.一些bug

今天我把自己工程画框单独剥离出来成一个组件调用.

如下图:

下面是一个demo工程代码,及整个源码提供大家下载

RectangleDraw.rar