关于SLF4J

Spring的功能越来越强大,同时也越来越臃肿。比如想快速搭建一个基于Spring的项目,解决依赖问题非常耗时。Spring的项目模板的出现就解决了这个问题,通过这个描述文件,可以快速的找到你所需要的模板。

第一次认识SLF4J就是在这些项目模板里,它的全称是Simple Logging Facade for Java。从字面上可以看出它只是一个Facade,不提供具体的日志解决方案,只服务于各个日志系统。简单说有了它,我们就可以随意的更换日志系统(如java.util.logging、logback、log4j)。比如在开发的时候使用logback,部署的时候可以切换到log4j;如果关闭所有的log,切换到NOP就可以了。只需要更改依赖,提供日志配置文件,免去了修改代码的麻烦。

首先看如何使用:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloWorld {
  public static void main(String[] args) {
    Logger logger = LoggerFactory.getLogger(HelloWorld.class);
    logger.info("Hello World");
  }
}

SLF4J封装了使用起来和其他日志系统一样简单。上面提到过SLF4J不提供具体的日志解决方案,所以使用的时候除了要引用SLF4J包,还要引用具体的日志解决方案包(log4j、logging–JDK提供、logback),还有所对应的binding包(slf4j-log4j、slf4j-jdk14、logback-classic)。

以log4j为例,我们看SLF4J的实现方式。

SLF4J类在初始化的时候会尝试从ClassLoader中org/slf4j/impl/StaticLoggerBinder.class。这个类比较特殊,每个binding包里都有。不同binding包里的StaticLoggerBinder类会去初始化一个相应的实例,如slf4j-log4j里:

/**
 * 截取的部分代码
 */
private StaticLoggerBinder() {
    loggerFactory = new Log4jLoggerFactory();
}

而Log4jLoggerAdapter实现了SLF4J的Logger接口,使用了Adapter模式对Log4j的Logger进行了封装并暴露了Logger的接口,Log4jLoggerFactory持有了Log4jLoggerAdapter的实例。

/**
 * 截取的部分代码
 */
public class Log4jLoggerFactory implements ILoggerFactory {
	public Logger getLogger(String name) {
	    Logger slf4jLogger = null;
	    // protect against concurrent access of loggerMap
	    synchronized (this) {
	        slf4jLogger = (Logger) loggerMap.get(name);
	      if (slf4jLogger == null) {
	        org.apache.log4j.Logger log4jLogger;
	        if(name.equalsIgnoreCase(Logger.ROOT_LOGGER_NAME)) {
	           log4jLogger = LogManager.getRootLogger();
	        } else {
	          log4jLogger = LogManager.getLogger(name);
	        }
	        slf4jLogger = new Log4jLoggerAdapter(log4jLogger);
	        loggerMap.put(name, slf4jLogger);
	      }
	    }
	    return slf4jLogger;
	  }
}

具体的Log解决方案就不做剖析了。

支付宝服务窗平台开发

服务窗平台是为支付宝用户提供服务的平台,平台开发接口是提供服务的基础。开发者在服务窗平台中创建服务窗,并且申请接口权限之后,可以通过阅读该文档来完成服务窗的开发工作。

服务窗平台开发接口向开发者提供包括自定义菜单、消息交互、获取用户基础信息等能力。当用户发消息给服务窗或者与服务窗发生其他交互时,支付宝网关会使用HTTP请求将消息推送给开发者网关,开发者网关也可以通过异步调用的方式给用户发送消息。

更多平台和接口的介绍请看这里

最近有作支付宝服务窗平台的研究,遇到的问题是开发者模式顺利激活之后,为进行测试添加菜单的时候报40002的错误,错误明细是“无效签名”。

初步判断是签名的方法有问题,平台采用了RSA签名加密的机制,要求对请求中所有的数据编码和签名。 浏览了下文档,并未找到具体的编码和签名的实现方式。这时就只能求助源代码了,所幸的是支付宝提供了完整的SDK源代码。

通过查看源代码发现,发送消息时的数据参数有三类:

  • 请求的主体
  • 必须的参数,包括API的方法名、API版本号、APP的ID、签名方式、终端的类型、终端的相关信息、时间戳,还有最重要的是一个就是三类参数(主体、必须和可选)中所有参数以参数名排序后的签名
  • 可选的参数,包括格式、访问令牌、SDK版本、产品码

以添加菜单为例:

  • 请求的主体就是JSON格式的菜单数据,参数名是“biz_content”
{"button":[{"actionParam":"ZFB_HFCZ","actionType":"out","name":"话费充值"},{"name":"查询","subButton":[{"actionParam":"ZFB_YECX","actionType":"out","name":"余额查询"},{"actionParam":"ZFB_LLCX","actionType":"out","name":"流量查询"},{"actionParam":"ZFB_HFCX","actionType":"out","name":"话费查询"}]},{"actionParam":"http://m.alipay.com","actionType":"link","name":"最新优惠"}]}
  • 必须的参数(终端相关的信息可为空):
app_id=2014072300007148
method=alipay.mobile.public.menu.add
charset=GBK
sign_type=RSA
version=1.0
sign=/*所有参数的签名*/
terminal_type=
terminal_info=
  • 可选参数(此处未用到):
format=json
auth_toke=/**/用于OAuth2.0的授权验证
alipay_sdk=alipay-sdk-java-dynamicVersionNo
prod_code=

以上都在SDK中得以实现,用SDK提供的接口和类,添加Menu的代码就是


		//AlipayMobilePublicMenuAddRequest类中已经封装了API方法名:alipay.mobile.public.menu.add
		AlipayMobilePublicMenuAddRequest addMenuRequest = new AlipayMobilePublicMenuAddRequest();
		//请求的主体
		addMenuRequest.setBizContent("{\"button\":[{\"actionParam\":\"ZFB_HFCZ\",\"actionType\":\"out\",\"name\":\"话费充值\"},{\"name\":\"查询\",\"subButton\":[{\"actionParam\":\"ZFB_YECX\",\"actionType\":\"out\",\"name\":\"余额查询\"},{\"actionParam\":\"ZFB_LLCX\",\"actionType\":\"out\",\"name\":\"流量查询\"},{\"actionParam\":\"ZFB_HFCX\",\"actionType\":\"out\",\"name\":\"话费查询\"}]},{\"actionParam\":\"http://m.alipay.com\",\"actionType\":\"link\",\"name\":\"最新优惠\"}]}");
		//API版本号
		addMenuRequest.setApiVersion("1.0");
		//支付宝服务窗网关
		String serverUrl = "https://openapi.alipay.com/gateway.do";
		//APP ID (测试号)
		String appId = "2014072300007148";
		//开发者私钥(测试)
		String privateKey = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMiAec6fsssguUoRN3oEVEnQaqBLZjeafXAxCbKH3MTJaXPmnXOtqFFqFtcB8J9KqyFI1+o6YBDNIdFWMKqOwDDWPKqtdo90oGav3QMikjGYjIpe/gYYCQ/In/oVMVj326GmKrSpp0P+5LNCx59ajRpO8//rnOLd6h/tNxnfahanAgMBAAECgYEAusouMFfJGsIWvLEDbPIhkE7RNxpnVP/hQqb8sM0v2EkHrAk5wG4VNBvQwWe2QsAuY6jYNgdCPgTNL5fLaOnqkyy8IobrddtT/t3vDX96NNjHP4xfhnMbpGjkKZuljWKduK2FAh83eegrSH48TuWS87LjeZNHhr5x4C0KHeBTYekCQQD5cyrFuKua6GNG0dTj5gA67R9jcmtcDWgSsuIXS0lzUeGxZC4y/y/76l6S7jBYuGkz/x2mJaZ/b3MxxcGQ01YNAkEAzcRGLTXgTMg33UOR13oqXiV9cQbraHR/aPmS8kZxkJNYows3K3umNVjLhFGusstmLIY2pIpPNUOho1YYatPGgwJBANq8vnj64p/Hv6ZOQZxGB1WksK2Hm9TwfJ5I9jDu982Ds6DV9B0L4IvKjHvTGdnye234+4rB4SpGFIFEo+PXLdECQBiOPMW2cT8YgboxDx2E4bt8g9zSM5Oym2Xeqs+o4nKbcu96LipNRkeFgjwXN1708QuNNMYsD0nO+WIxqxZMkZsCQHtS+Jj/LCnQZgLKxXZAllxqSTlBln2YnBgk6HqHLp8Eknx2rUXhoxE1vD9tNmom6PiaZlQyukrQkp5GOMWDMkU=";
		//编码集
		String charset = "GBK";
		AlipayClient alipayClient = new DefaultAlipayClient(serverUrl, appId, privateKey, null, charset);

		try {
			//发送请求
			//发送时会添加其他必要参数和对所有参数进行排序、签名和编码
			AlipayMobilePublicMenuAddResponse addMenuResponse = alipayClient.execute(addMenuRequest);
			if(addMenuResponse != null){
				System.out.println(addMenuResponse.getCode());
				System.out.println(addMenuResponse.getMsg());
			}
		} catch (AlipayApiException e) {
			e.printStackTrace();
		}
运行结果(请使用正确的APP ID和开发者密钥):
200
成功

从Google搜索结果中移除信息

为什么说起这个呢?这还得从毕业那年说起。毕业的时候初出茅庐,到处找工作:招聘网站、技术论坛。其中就有过一次在CSDN上贴过自己的简历想找人看下简历,当时忘记隐去个人的一些信息,之后也忘记去删除帖子了。现在是只要在Google中搜索自己的手机号码,结果中的第一条就是那篇简历。本人的名字,出生年月,邮箱和毕业院校等都在上面。如今信息安全也都是大家谈论的话题,也应尽量避免信息的泄漏。所以就想到要去删除这些信息。

这就想到了Google了,正好Google也有这个功能。

如果您想从 Google 搜索结果中移除照片、个人资料链接或网页,您通常需要与相关网站的所有者(即网站站长)联系并让他们移除这些信息。

如果您需要从搜索结果中移除敏感的个人信息(例如您的银行帐号或手写签名图片),则可使用此页面请求我们移除此类信息。请参阅我们的移除政策,详细了解 Google 会移除哪些信息。

既然如此,只能先找网站所有者了。便联系了论坛的版主和客服(有QQ的),验证了信息之后很顺利的删除了帖子。

之后便是去Google,在输入框中输入要删除的信息。Google会先检测页面是否存在,然后根据提示请求删除。

最后就是正待Google的审核了,且不知道失效如何。

后记:14 Mar, Google搜索结果中的CSDN源已经被删除了。但是发现CSDN的帖子被另一个网站的爬虫爬过去,果断联系网站工作人员,几分钟后就答复删除了。然后又在Google上提交了一次,至此Google上已经搜不到任何关于这个手机号码的信息了。

我猜想,任何网站都有义务去保护个人信息不被泄漏的。如果大家发现类似情况,可以做下参考。

博客搬家完成

一直使用的虚拟主机马上就要过期了,考虑再三,还是转入了VPS。并不是说虚拟主机不好,之前的主机空间、流量都够且大,客服支持迅速到位。

之所以要换完全是因为VPS的可折腾性更加高,可以满足折腾和科学上网的需要。

VPS最后选择了Digital Ocean(DO)的最低档,5$/月。单核cpu,512Mb内存,20G SSD,流量1Tb/月。按照我现在的需求来说完全足够了,安装了LEMP环境,搭起了wordpress。当然还有科学上网,SS+VPN。

使用邀请码注册可以有10$打入账户,也就是说前两个月是免费的,如有需要可以用6382c9480781。对大陆的用户来说,CA节点是比较快而稳定的。

博客搬家还算顺利,遇到的唯一问题就是主页显示空白,最后发现是wp_options表中有个redirect_uri的字段中资源路径要改成新服务器的路径。