轻量级ORM框架DbUtils和OrmLite

  最近在树莓派上部署了一套数据抓取工具,需要将抓取的数据录入到MySQL,特意找了下一些轻量级的ORM框架,这里简单介绍下DbUtils和OrmLite的配置和使用。

1. DbUtils

  Apache旗下的,速度与稳定性不言而喻,但其配置还是有点啰嗦,依赖dbcp连接池。

  地址:http://commons.apache.org/proper/commons-dbutils/

添加pom依赖:

<dependency>
	<groupId>commons-dbutils</groupId>
	<artifactId>commons-dbutils</artifactId>
	<version>1.6</version>
</dependency>
<dependency>
	<groupId>commons-dbcp</groupId>
	<artifactId>commons-dbcp</artifactId>
	<version>1.4</version>
</dependency>

添加dbcp.properties配置文件,放在classpath中:

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://192.168.91.128:3306/db
username=steven
password=steven
initialSize=10
maxActive=50
maxIdle=20
minIdle=5
maxWait=60000
#附带连接属性
connectionProperties=useUnicode=true;characterEncoding=utf8
#自动提交(auto-commit)
defaultAutoCommit=true
#只读(read-only)
defaultReadOnly=
#事务级别(TransactionIsolation)
defaultTransactionIsolation=READ_COMMITTED

新增JdbcUtil.java来加载属性文件

package com.dorole.utils;

import java.io.InputStream;
import java.sql.SQLException;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.commons.dbcp.BasicDataSourceFactory;

import com.mysql.jdbc.Connection;

public class JdbcUtil {
	private static DataSource ds;
	private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();

	static {
		try {
			Properties prop = new Properties();
			InputStream in = JdbcUtil.class.getClassLoader()
					.getResourceAsStream("dbcp.properties");
			prop.load(in);
			ds = BasicDataSourceFactory.createDataSource(prop);
		} catch (Exception e) {
			throw new ExceptionInInitializerError(e);
		}
	}

	public static DataSource getDataSource() {
		return ds;
	}

	public static Connection getConnection() throws SQLException {
		try {
			Connection conn = tl.get();
			if (conn == null) {
				conn = (Connection) ds.getConnection();
				tl.set(conn);
			}
			return conn;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
}

插入数据:

QueryRunner runner = new QueryRunner(JdbcUtil.getDataSource());
String sql = "INSERT INTO `orders` (`storeName`, `telecomNumber`) VALUES (?, ?)";
Object params[] = { order.getStoreName(), order.getTelecomNumber(), };
try {
	runner.update(sql, params);
} catch (SQLException e) {
	e.printStackTrace();
}

2. OrmLite

  这个在Android上用的比较多,结合轻量级的数据库HSQLDB,非常适合嵌入式设备,但这里我们使用MySQL做演示,有意思的是连BaseDao都给封装好了,配置简单,易于使用。

  地址:http://ormlite.com/

添加pom依赖:

<dependency>
	<groupId>com.j256.ormlite</groupId>
	<artifactId>ormlite-jdbc</artifactId>
	<version>4.9</version>
</dependency>

新增OrderDao接口,默认的增删改查已经有了,故可以留空:

package com.dorole.dao;

import com.dorole.model.Order;
import com.j256.ormlite.dao.Dao;

public interface OrderDao extends Dao<Order, Integer> {

}

新增OrderDaoImpl实现类

package com.dorole.dao.impl;

import java.sql.SQLException;

import com.dorole.dao.OrderDao;
import com.dorole.model.Order;
import com.j256.ormlite.dao.BaseDaoImpl;
import com.j256.ormlite.support.ConnectionSource;

public class OrderDaoImpl extends BaseDaoImpl<Order, Integer> implements
		OrderDao {
	public OrderDaoImpl(ConnectionSource connectionSource,
			Class<Order> dataClass) throws SQLException {
		super(connectionSource, dataClass);
	}
}

初始化:

OrderDao orderDao = null;
ConnectionSource connectionSource;
try {
	connectionSource = new JdbcConnectionSource(
			"jdbc:mysql://192.168.91.128:3306/db");
	((JdbcConnectionSource) connectionSource).setUsername("steven");
	((JdbcConnectionSource) connectionSource).setPassword("steven");
	orderDao = new OrderDaoImpl(connectionSource, Order.class);
} catch (SQLException e) {
	e.printStackTrace();
}

插入数据:

try {
	Order order = new Order();
	orderDao.create(order);
} catch (SQLException e) {
	e.printStackTrace();
}

为花图相册开启全站https

是否为花图相册开启全站https其实考虑已久:一来是发现目前越来越多的网站都走向了https,即便不是电子商务的网站,例如百度。二是服务器计算能力越来越强大,似乎也不必要在乎加解密,握手所消耗的这点资源。三是出于程序员思维,好东西自然要折腾一番,且不说在Chrome地址栏中绿色的scheme是多么的诱人。

1. SSL证书分类

SSL证书大致可以分为Class 1~5这五种,级别依次递增,数字越高,信用级别越高,自然收费也更高。
Class 1(DV),只验证域名真实性,即保证所访问的域名是真实有效的,这个级别的证书申请也是最简单的,只需提供域名管理者邮箱或者以域名为后缀的几个特定邮箱即可。通常用于个人或邮件。
Class 2(IV)验证域名和个人身份证明。
Class 3(OV)验证域名和组织机构信息。
Class 4(EV)电子商务,网上交易等。
Class 5私有组织或政府部门,比较少见。

这里虽然有等级之分,其实也就是准入门槛越来越高而已,增加了造假难度,但底层加密方式可以都是一样的。

2. 证书申请

这里不得不提到两家免费证书提供商,国内的Wosign和国外的StartSSL,这两家都有试用过,这里简单记录下使用过程:

Wosign的免费证书申请地址隐藏比较深,官网拉倒最下面才有个注册入口。由于是国内的,注册就容易多了,基本上顺着向导很容易就能拿到证书,而且提供的证书非常全面,涵盖了常用的几个服务器,包括Apache,Nginx,Tomcat等,可以说非常便捷。

StartSSL就麻烦多了,首先注册时候填写的资料必须是个人的真实的,或者说看起来要真实。其二需要人工审核,一般也很快,十分钟左右就能收到邮件。而这家最该死的设计就是以后的登录必须靠首次登陆所安装的证书来识别,因此需要备份好证书。可惜的是我这里Chrome下首次安装失败了,建议用IE。意味着我只要退出登录了就再也进不来了。不过并不妨碍继续申请域名证书,首先是验证域名,再申请证书,得到的私钥需要用openssl解密,或者在toolbox中在线解密。然后再下载他们家的sub class1证书附加到域名证书中,才可以正常使用。

在实际测试中,Wosign的免费证书顶级CA居然是StartSSL签发的,Wosign作为一个二级CA,有点奇怪,证书链比StartSSL多一层,理论上验证也要花费更多时间。

3. Nginx服务器配置

主要是增加443端口监听,别忘了在防火墙上也要允许通过443端口。开启ssl,很简单。ssl_ciphers可能还需要优化,目前来讲RC4算法已经不太安全了,可以禁用。由于加密所需要的握手次数和时间都增加不少,因此很有必要调节下ssl_session的缓存大小和超时时间。官方资料是1m cache大概能存储4000个会话,timeout 10m是十分钟。部分代码如下:

listen       80;
listen       443 ssl;
server_name  snapast.com www.snapast.com;

ssl			on;
ssl_certificate		/etc/nginx/ssl/ssl.crt;
ssl_certificate_key	/etc/nginx/ssl/ssl.key;
ssl_protocols		TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers 		EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
ssl_prefer_server_ciphers	on;
ssl_session_cache	shared:SSL:10m;
ssl_session_timeout	10m;

开启后,还需要将非https请求从定向到https,这里我做了两步处理:首先host不等于snapast.com的转为snapast.com,即将带www的转为不带www。scheme不是https的转到带https,同时带上上下文和参数。

if ($host != 'snapast.com') {
rewrite ^/(.*)$ https://snapast.com/$1 permanent;
}

if ($scheme != "https") {
rewrite ^/(.*)$ https://snapast.com/$1 permanent;
}

4. 其他说明

Tomcat也可以开启http所访问,但由于都是内网,因此没必要再开启https了,但需要注意的是,在Tomcat看来还是http的请求,所以通过request.getScheme()得到的依然是http,凡有用到的需要注意一下,容易出现在拼接basePath的地方。

出于安全规范,全站https后,若有脚本等资源还是http引入的浏览器都会拒绝加载,而我之前用的51.la统计工具便不支持https,试过腾讯统计也不支持,目前发现百度统计可以用,因为他的脚本没有限定scheme。

到此就算完成了,欢迎访问花图相册看效果:https://snapast.com:cool:

使用Spring Session做分布式会话管理

  在Web项目开发中,会话管理是一个很重要的部分,用于存储与用户相关的数据。通常是由符合session规范的容器来负责存储管理,也就是一旦容器关闭,重启会导致会话失效。因此打造一个高可用性的系统,必须将session管理从容器中独立出来。而这实现方案有很多种,下面简单介绍下:

  第一种是使用容器扩展来实现,大家比较容易接受的是通过容器插件来实现,比如基于Tomcat的tomcat-redis-session-manager,基于Jetty的jetty-session-redis等等。好处是对项目来说是透明的,无需改动代码。不过前者目前还不支持Tomcat 8,或者说不太完善。个人觉得由于过于依赖容器,一旦容器升级或者更换意味着又得从新来过。并且代码不在项目中,对开发者来说维护也是个问题。

  第二种是自己写一套会话管理的工具类,包括Session管理和Cookie管理,在需要使用会话的时候都从自己的工具类中获取,而工具类后端存储可以放到Redis中。很显然这个方案灵活性最大,但开发需要一些额外的时间。并且系统中存在两套Session方案,很容易弄错而导致取不到数据。

  第三种是使用框架的会话管理工具,也就是本文要说的spring-session,可以理解是替换了Servlet那一套会话管理,既不依赖容器,又不需要改动代码,并且是用了spring-data-redis那一套连接池,可以说是最完美的解决方案。当然,前提是项目要使用Spring Framework才行。

  这里简单记录下整合的过程:

  如果项目之前没有整合过spring-data-redis的话,这一步需要先做,在maven中添加这两个依赖:

<dependency>
	<groupId>org.springframework.data</groupId>
	<artifactId>spring-data-redis</artifactId>
	<version>1.5.2.RELEASE</version>
</dependency>
<dependency>
	<groupId>org.springframework.session</groupId>
	<artifactId>spring-session</artifactId>
	<version>1.0.2.RELEASE</version>
</dependency>

  再在applicationContext.xml中添加以下bean,用于定义redis的连接池和初始化redis模版操作类,自行替换其中的相关变量。

<!-- redis -->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
</bean>

<bean id="jedisConnectionFactory"
	class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
	<property name="hostName" value="${redis.host}" />
	<property name="port" value="${redis.port}" />
	<property name="password" value="${redis.pass}" />
	<property name="timeout" value="${redis.timeout}" />
	<property name="poolConfig" ref="jedisPoolConfig" />
	<property name="usePool" value="true" />
</bean>

<bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
	<property name="connectionFactory" ref="jedisConnectionFactory" />
</bean>

<!-- 将session放入redis -->
<bean id="redisHttpSessionConfiguration"
class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
	<property name="maxInactiveIntervalInSeconds" value="1800" />
</bean>

  这里前面几个bean都是操作redis时候使用的,最后一个bean才是spring-session需要用到的,其中的id可以不写或者保持不变,这也是一个约定优先配置的体现。这个bean中又会自动产生多个bean,用于相关操作,极大的简化了我们的配置项。其中有个比较重要的是springSessionRepositoryFilter,它将在下面的代理filter中被调用到。maxInactiveIntervalInSeconds表示超时时间,默认是1800秒。写上述配置的时候我个人习惯采用xml来定义,官方文档中有采用注解来声明一个配置类。

  然后是在web.xml中添加一个session代理filter,通过这个filter来包装Servlet的getSession()。需要注意的是这个filter需要放在所有filter链最前面

<!-- delegatingFilterProxy -->
<filter>
	<filter-name>springSessionRepositoryFilter</filter-name>
	<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
	<filter-name>springSessionRepositoryFilter</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>

  这样便配置完毕了,需要注意的是,spring-session要求Redis Server版本不低于2.8

  验证:使用redis-cli就可以查看到session key了,且浏览器Cookie中的jsessionid已经替换为session。

127.0.0.1:6379> KEYS *
1) "spring:session:expirations:1440922740000"
2) "spring:session:sessions:35b48cb4-62f8-440c-afac-9c7e3cfe98d3"

闪光灯那些事(二)

  自从走上玩灯的道路后,便一发不可收拾,俗话说人像玩灯,风景玩镜,而我更喜欢结合两者,烈日下依然可以压低天空亮度。同样喜欢用灯的朋友可以看看传说中的“一灯大师”Zack Arias的闪灯视频。

  由于刚入门的时候选择了一款小巧的Nissin i40,但该灯不支持主控,以及功率略低。仅作机顶使用到没啥问题,但要离机引闪就不行了,首先不支持手动,毕竟人家是需要转盘来控制的。其次闪光补偿幅度也很小,可能是本身功率就很小的缘故吧。于是打算换一支主控灯,首先当然是考虑国产灯了,便宜嘛。

  在闪客日记论坛中泡了几天后入了一支沃龙SP-600,此灯在国产货中口碑算是不错的,做工也还凑活,唯一不足的是在我永诺622c离机引闪下曝光过度,大概误差有2ev样子,但机顶使用是正常的,升级了622c-tx依旧如此,没办法只好退了。之后又购入了斯丹德DF-800,这货拿到手就感觉low到极致了,做工不是一般的粗糙,连扩散板都很难抠出来,打出来的光斑也是不规则,暗角超级大,感觉虚标了闪光指数,只好退货处理。两只灯都有一个共同的问题:连续全光输出几次后电池发热极其严重,取出来都烫手。灯头倒是不怎么热,我可是在空调房里面测试的,用的是爱乐普电池,真怕爆炸。。。至于永诺的灯,不敢再试了,网评烧的最多的就是它了,可人家销量高啊,大概都抱着坏了再换的打算而用的。

  最后还是选择了佳能自己的600ex-rt,做工啥的都不用说了,使用622c完美引闪,不愧为原厂最强灯,测试下来耗电也不算大,关键是电池也没见发烫,安全第一,太棒了。

  写下这篇文章纪录下折腾的一些心得,想在摄影这条路一直走下去的朋友,一步到位还是很有道理的,能省下不少时间,毕竟时间才是最宝贵的。

使用Java控制路由器获取公网IP

  不知道是公网IP不够用了,还是什么鬼原因,近期我这的联通ADSL拨号很大程度上获取的是一个10.开头的内网IP。虽说通常情况下无需关心,但跑PT,VPN等速度上大打折扣。投诉无果后只能自己写个脚本来自动更换IP。

  其原理很简单,模拟登录到路由器上检查WANIP是否是10.或0.开头,如是则断开重连,以此循环。代码是Java编写,无任何依赖,运行在树莓派上,24小时监视,在运营商完全分配内网IP之前还可以挣扎一阵子。有需要的朋友可以参考下。

  我这用的是TP-LINK WR720N路由器,设置了局域网IP为192.168.30.1 端口88,通过Chrome登录到路由器,可以在开发者工具中查看到Basic加密的Key,替换相应的位置即可。

查询IP的链接

http://192.168.30.1:88/userRpm/StatusRpm.htm

断开拨号的链接

http://192.168.30.1:88/userRpm/StatusRpm.htm?Disconnect=%B6%CF%20%CF%DF&wan=1

重新拨号的链接

http://192.168.30.1:88/userRpm/StatusRpm.htm?Connect=%C1%AC%20%BD%D3&wan=1

代码如下:

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class CheckIP {
	private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat(
			"yyyy-MM-dd HH:mm:ss");

	public static void main(String[] args) throws Exception {
		do {
			String currentIP = getIP();
			if (currentIP.startsWith("10.") || currentIP.startsWith("0.")) {
				System.out.println(simpleDateFormat.format(new Date())
						+ " 检测到异常:" + currentIP);
				getHtml("http://192.168.30.1:88/userRpm/StatusRpm.htm?Disconnect=%B6%CF%20%CF%DF&wan=1");
				Thread.sleep(1000 * 1);
				getHtml("http://192.168.30.1:88/userRpm/StatusRpm.htm?Connect=%C1%AC%20%BD%D3&wan=1");
				Thread.sleep(1000 * 3);
			}
			Thread.sleep(1000 * 3);
		} while (true);
	}

	private static String getIP() throws Exception {
		String wanPara = getHtml("http://192.168.30.1:88/userRpm/StatusRpm.htm");
		if (null != wanPara) {
			wanPara = wanPara.substring(wanPara.indexOf("var wanPara"),
					wanPara.length());
			wanPara = wanPara.substring(0, wanPara.indexOf(");") + 2);
		}
		return getFirstIp(wanPara);
	}

	private static String getHtml(String address) throws Exception {
		URL url = new URL(address);
		URLConnection connection = url.openConnection();
		connection.setRequestProperty("Authorization", "Basic YWRtaW46d3Npa3Nr");
		connection.connect();
		InputStream inputStream = null;
		StringBuffer stringBuffer = new StringBuffer();
		inputStream = connection.getInputStream();
		BufferedReader bufferedReader = new BufferedReader(
				new InputStreamReader(inputStream));
		String line;
		while ((line = bufferedReader.readLine()) != null) {
			stringBuffer.append(line);
		}
		bufferedReader.close();
		inputStream.close();
		return stringBuffer.toString();
	}

	private static String getFirstIp(String packet) {
		Pattern p = Pattern.compile("\\d+\\.\\d+\\.\\d+\\.\\d+");
		Matcher m = p.matcher(packet);
		if (m.find()) {
			return m.group();
		} else {
			return null;
		}
	}
}

编译

pi@raspberrypi ~ $ javac CheckIP.java

后台运行

pi@raspberrypi ~ $ nohup java CheckIP &

日志

pi@raspberrypi ~ $ tail -f nohup.out