最近在研发一个JIRA的插件,具体场景如下:
当测试人员提交一个问题时,需要通过QQ通知到开发人员,并且当问题属于SIT测试BUG时,需要自动的打开SVN上对应主干源代码的写权限。当开发人员修复并关闭问题时,需要自动的关闭SVN上对应主干源代码的写权限。
基本实现思路如下:
- 开发一个JIRA插件,基于ActiveMQ客户端,并监听JIRA的内部事件,如果是触发提交问题的事件,就将该事件发布到MQ主题中;
- 开发一个服务器应用程序,订阅MQ上由JIRA插件发布的事件消息,并进行对应的处理,例如发送QQ消息或是修改SVN权限等。
实际上实现JIRA插件这一端的程序并不困难,关键是查阅JIRA SDK相关手册,基本上没什么难度,下一篇文章,我们就来看看如何实现这个JIRA插件。这篇文章主要是关注如何实现与QQ的通讯,着实花费了不少心思。通过Google,发现目前网上大部分是基于WebQQ实现的,然后下载了一个什么iQQ的实现代码,没什么设计思想,基本上就是代码的堆叠,不过可读性还算好,基本能看懂,于是乎就有了想搞一个WebQQ客户端API库的冲动,方便今后的使用和扩展。经过几天的整理和重构终于把WebQQ核心部分抽出来并形成一个API库,以下为API接口定义:
/** * WebQQ客户端接口 * @author Administrator * @version $Id: WebQQClient.java, v 0.1 2013-1-21 下午2:58:32 Administrator Exp $ */ public interface WebQQClient { /** * QQ登录 * <blockquote> * QQ完成登录需要经过三次服务调用: * <ol> * <li>检查QQ账户,是否需要验证码输入; * <li>正式登录; * <li>渠道登录; * </ol> * </blockquote> * @param member 需要提供QQ号码和登录密码 * @return true 登录成功 false 登录失败 */ boolean login(Member member); /** * 加载QQ好友详情 * <blockquote> * 前置条件: 必须已经成功登录 * </blockquote> * @param account 需要加载的好友QQ号码(QQ系统内部还有一个uin标识) * @return QQ会员详情 */ Member loadFriend(String account); /** * 加载QQ好友的个性化签名 * <blockquote> * 前置条件: 必须已经成功登录 * </blockquote> * @param account 需要加载的好友QQ号码(QQ系统内部还有一个uin标识) * @return */ Member loadFriendSignature(String account); /** * 加载QQ好友的等级 * <blockquote> * 前置条件: 必须已经成功登录 * </blockquote> * @param account 需要加载的好友QQ号码(QQ系统内部还有一个uin标识) * @return */ Member loadFriendLevel(String account); /** * 改变当前已登录QQ会员的状态 * <blockquote> * 前置条件: 必须已经成功登录 * </blockquote> * @param newStatus 当前的新状态 */ void changeStatus(Status newStatus); /** * 向指定的QQ好友发送消息 * <blockquote> * 前置条件: * <ol> * <li>必须已经成功登录; * <li>已经成功加载了好友列表,因为发送QQ消息需要好友的uin字段(一个内部的标识,每次登录该字段值都不一样),而非QQ号码; * <li>已经成功启动QQ心跳检测任务,否则无法发送QQ消息; * </ol> * </blockquote> * @param account 发送消息的好友的QQ号码 * @param message 发送的消息内容 * @return true 发送成功 false 发送失败 */ boolean sendMessageToFriend(String account, String message); /** * 向指定的QQ群发送消息 * <blockquote> * 前置条件: * <ol> * <li>必须已经成功登录; * <li>已经成功加载了好友群列表,因为发送QQ消息需要好友群的uin字段(一个内部的标识,每次登录该字段值都不一样),而非群号码; * <li>已经成功启动QQ心跳检测任务,否则无法发送QQ消息; * </ol> * </blockquote> * @param account 发送消息的QQ群号 * @param message 发送的消息内容 * @return */ boolean sendMessageToGroup(String account, String message); /** * QQ登出 * <blockquote> * 前置条件: 必须已经成功登录 * </blockquote> * @return true 登出成功 false 登出失败 */ boolean logout(); /** * 查询好友列表 * <blockquote> * 前置条件: 必须已经成功登录 * </blockquote> * @return 好友列表 */ List<Member> findFriends(); /** * 查询在线的好友列表 * <blockquote> * 前置条件: 必须已经成功登录 * </blockquote> * @return 在线好友列表 */ List<Member> findOnlineFriends(); /** * 查询QQ群列表 * <blockquote> * 前置条件: 必须已经成功登录 * </blockquote> * @return */ List<Group> findGroups(); }该API库的主要功能有:
- QQ登录;
- 查询QQ好友列表;
- 查询QQ群列表;
- 查询在线QQ好友列表;
- 加载QQ好友详情;
- 加载QQ好友个性化签名;
- 加载QQ好友等级;
- 更变当前登录状态;
- 向QQ好友发送消息;
- 向好友QQ群发送群消息;
- QQ心跳检查线程任务,用于保持在线状态;
- 提供QQ事件监听器接口,用于扩展QQ消息和QQ群消息的接收处理;
- QQ登出;
当然因为是一个API库,所以和UI相关的功能基本上都没有实现,有待感兴趣的同学继续完善。
该API库的依赖库如下:
- Apache Commons Lang
- Apache Commons Collections
- Apache Commons Beanutils
- Apache Commons Logging
- Spring Framework
- Apache Log4j
- JSON
- Storevm Commons
如果是使用maven管理依赖的话,那就简单了,可以看我项目源代码中提供的pom.xml文件。
以下介绍使用的方法,由于该API是依赖Spring框架的,所以要做的就是配置几个Bean,以下假设您使用Maven工具以及Spring进行开发:
1. 在pom.xml中配置如下依赖:
<dependency> <groupId>org.storevm</groupId> <artifactId>webqq-client</artifactId> <version>1.0.0</version> </dependency>当然,这个Jar在Maven中央库是没有的,所以需要在pom.xml中添加上我的Maven私有库地址:
<repositories> <repository> <id>StoreVM-repo</id> <name>StoreVM Maven Repository</name> <url>http://mvn.storevm.org:8081/mvn/content/groups/public</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>true</enabled> </snapshots> </repository> </repositories>OK,最后执行
mvn eclipse:eclipse
回到Eclipse上,刷新一下自己的项目,就能看到新加入的库了。
2. 接着在您项目的Spring配置文件中定义如下的 Bean:
<bean id="webQQClientContainer" class="org.storevm.im.WebQQClientContainer"> <property name="account" value="您的QQ号码"/> <property name="password" value="您的QQ登录密码"/> </bean>WebQQClientContainer类实现了InitializingBean和DisposableBean接口,所以在Bean加载的时候会执行QQ登录相关的操作,而在Bean卸载的时候会执行了QQ登出的操作,部分源代码如下:
/** * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() */ @Override public void afterPropertiesSet() throws Exception { Member member = new Member(); member.getAccount().setAccount(qqNumber); member.getAccount().setPassword(qqPassword); member.setStatus(Status.ONLINE); //登录WebQQ,登录之后就可以发送消息了 boolean success = webQQClient.login(member); if (success) { //如果登录成功,则加载在线好友列表和QQ群列表 List<Member> olineFriends = webQQClient.findOnlineFriends(); List<Group> groups = webQQClient.findGroups(); //启动心跳检测任务 heartbeatTaskExecutor.startup(); LogUtils.info(LOGGER, "启动WebQQ客户端容器完成. 在线好友数=[{0}], QQ群数=[{1}]", olineFriends.size(), groups.size()); } else { LogUtils.warn(LOGGER, "启动WebQQ客户端失败, account={0}", qqNumber); } }
/** * @see org.springframework.beans.factory.DisposableBean#destroy() */ @Override public void destroy() throws Exception { //当Bean销毁时,关闭WebQQ客户端管理容器 heartbeatTaskExecutor.shutdown(); boolean success = webQQClient.logout(); if (success) { LogUtils.warn(LOGGER, "关闭WebQQ客户端完成, account={0}", qqNumber); } else { LogUtils.warn(LOGGER, "关闭WebQQ客户端失败, account={0}", qqNumber); } }
经过以上的配置,您就可以在程序运行的任何时刻调用WebQQClient接口发送QQ消息了。这里需要注意的是,Jar包中已经定义了相关bean的配置,无需在你自己的项目中重复定义Bean,不过需要通过设置classpath*:applicationContext.xml的方式来加载Jar包中的Spring配置文件。
如果您的应用程序是Web程序,我也写了一个ServletContextListener接口用于在Web容器启动时初始化WebQQ客户端:
1. 在web.xml中添加如下配置:
<context-param> <param-name>account</param-name> <param-value>您的QQ号码</param-value> </context-param> <context-param> <param-name>password</param-name> <param-value>您的QQ登录密码</param-value> </context-param>
<listener> <listener-class>org.storevm.im.WebQQClinetListener</listener-class> </listener>通过上述配置,当web容器启动时会执行QQ登录操作,然后您就可以在应用程序的运行过程中发送QQ消息,当然在容器关闭时会执行QQ登出的操作。
关于QQ消息的接收,我采用事件监听器的方式进行实现,您需要做的就是实现WebQQEventListener接口以及InitializingBean和DisposableBean接口,以下为一个简单的事件监听器实现代码:
/** * 接收QQ消息的监听器实现 * @author Administrator * @version $Id: ReceiveFriendMessageListener.java, v 0.1 2013-1-24 下午3:28:15 Administrator Exp $ */ public class ReceiveMessageListener implements WebQQEventListener, InitializingBean, DisposableBean { /* logger */ private static final Logger LOGGER = Logger.getLogger(ReceiveMessageListener.class); /* 事件发布器 */ private WebQQEventPublisher eventPublisher; /** * 构造函数 */ public ReceiveMessageListener() { } /** * @see org.springframework.beans.factory.DisposableBean#destroy() */ @Override public void destroy() throws Exception { eventPublisher.unregister(this); //注销 } /** * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() */ @Override public void afterPropertiesSet() throws Exception { eventPublisher.register(this); //注册监听器 } /** * @see org.storevm.im.core.event.WebQQEventListener#onWebQQEvent(org.storevm.im.core.event.WebQQEvent) */ @Override public void onWebQQEvent(WebQQEvent event) { LogUtils.info(LOGGER, "接收到QQ事件, event.code={0}, body={1}", event.getEventCode(), event.getBody()); } /** * Setter method for property <tt>eventPublisher</tt>. * * @param eventPublisher value to be assigned to property eventPublisher */ public void setEventPublisher(WebQQEventPublisher eventPublisher) { this.eventPublisher = eventPublisher; } }以上代码的关键点是注册监听器和onWebQQEvent方法的实现。你也可以注册多个监听器,用于监听多个事件。更多的单元测试请下载源代码查看。
以下提供SVN源代码地址:
http://svn.storevm.org/svn/webqqclient/trunk
用户名和密码均为:reader
项目使用Maven进行管理,关于maven的使用请自行参考网上其他文章。最后希望对此有兴趣的同学可以相互交流经验技术。