构建项目
eclipse 的没有添加插件和配置。请自行百度
idea
- 建议手动在cmd中使用命令行:执行 gradle build -x test --debug 能看到构建的进度和出错的详细信息
- 在命令行构建完成后,用idea打开该项目
#wxsdk
该项目能做什么?
该项目作为JAR包使用,从接入公众号,到开发调用微信api ,搭成了一个架子。 现有部分api 是自用过的。包括在生产环境用过的。
目的就是简化调用操作。 从封装体系来扩展一个 本项目没有的微信API来说,几行代码搞定。
- 统一的生命周期维护
- 统一的请求器
- 统一的api扩展机制
- 使用者只关注暴露的服务调用就ok
1. 文档风格简写定义
- 作废标识 :
[× 2016.04.22]
[类型:废弃;理由:使用新策略维护,稳定性比较好]
作废的内容依次写到作废条目下,将该作废内容移动到,每个模块下专门的废弃条目中
- 更新标识 :
[↑ 2016.04.22]
- 新增标识 :
[* 2016.04.22]
- 解决标识 :
[√ 2016.04.22]
类似bug管理等场景
2. 使用和部署注意事项
特别注意的是:
使用分布式来维护生命周期,所有被部署的机器上的sdk文件必须一样
2.1. 使用前的准备:
- 填充配置文件 initConfig.xml 里面每个属性的说明能在initConfig.xsd中找到,也有注释
- 初始化sdk的入口类说明:
模块 |
初始化入口 |
描述 |
core |
CoreInit |
不使用web的时候,可以直接new CorInit类完成初始化 |
pay |
- |
依赖core中的初始化数据 |
web |
WebInit |
如果依赖了web包,则只需要new WebInit类。该类会自动先去调用core中的初始化操作。 |
注:
- 初始化指定自定义initConfig.xml 路径
在初始化类构造函数中,选择有参构造注入参数:
可为空
:默认加载sdk中的initConfig.xml文件
- 初始化指定使用者机器id:用来定位是哪一台机器在更新维护zk数据
在初始化类构造函数中,选择有参构造注入参数:
可为空
:默认使用 “本机ip:描述” 的字符串作为id
spring.xml中初始化的时候,一定要设置lazy-init=false:让sdk在项目启动时完成初始化,否则可能会出现问题
spring 容器初始化:
<bean class="cn.mrcode.wxsdk.core.CoreInit" lazy-init="false">
<!-- initConfig.xml 可为空使用默认的配置文件 -->
<!--<constructor-arg index="0" value=""/>-->
<!-- 指定该应用的唯一id,用于追踪是哪台机器更新了zk数据 ,可为空,使用默认策略生成id-->
<!--<constructor-arg index="1" value="192.168.5.75"/>-->
</bean>
测试模版中的初始化:
new CoreInit();
注意:
应用的唯一id 建议手动为每一台机器增加,在linux中偶发性获取不到ip地址
2.2. 接入开发者模式
注:
该步骤不是必须的,因为本sdk 所提供的服务都是基于静态方法,该步骤适合需要接入开发者模式的服务器
{
// 设置自己的消息处理器,否则sdk将使用 DefaultInMessageProcessingHandler 作为默认的消息处理器
Access.setInMessageProcessingHandler(myInMessageProcessingHandler);
}
@RequestMapping("/wx")
public void index(HttpServletRequest request, HttpServletResponse response) {
String appId = request.getParameter("appId");// 扩展字段,可用于管理多个微信号
String signature = request.getParameter("signature");// 微信加密签名
String timestamp = request.getParameter("timestamp");// 时间戳
String nonce = request.getParameter("nonce");// 随机数
String echostr = request.getParameter("echostr");// 随机字符串,接入的时候校验
// 从配置上下文中获取到 配置文件中配置的公众号数据 也可以自定义数据源获取
PublicAccount publicAccount = SdkContexts.getConfigContext().getDirstributedConfig().getPublicAccountMap().get(appId);
// inMessageProcessingHandler.openTestMode();
Access.in("",publicAccount,signature,timestamp,nonce,echostr,request,response);
}
2.3. 服务简介
服务路径 |
描述 |
cn.mrcode.wxsdk.core.dialogue.service.Access |
开发者模式消息接入入口 |
cn.mrcode.wxsdk.core.dialogue.service.BaseService |
所有服务的基类,不能直接使用 |
cn.mrcode.wxsdk.core.dialogue.service.CommonService |
获取accesstoken 等公用服务 |
cn.mrcode.wxsdk.core.dialogue.service.TemplateMsgService |
模版消息服务 |
cn.mrcode.wxsdk.core.dialogue.service.ToPromoteService |
推广服务:二维码服务 与 长链接转短链接 |
cn.mrcode.wxsdk.web.service.PayCommonService |
支付的公共服务,包括支付相关的原生api |
cn.mrcode.wxsdk.web.service.CommonService |
获取openid等服务 |
cn.mrcode.wxsdk.web.service.JsSdkService |
微信jssdk相关服务 |
cn.mrcode.wxsdk.web.service.WebCommSV |
与web容器相关的web公用服务,算是一个小扩展 |
2.4. 关于快速测试
- 每个模块都有一个基于 cn.mrcode.wxsdk.core.test.BaseTest 的子类实现;BaseTest 用于为测试类提供统一初始化操作
- cn.mrcode.wxsdk.core.dialogue.service.CommonServiceTest 中是一个获取accesstoken的测试用列模版
2.5. 关于分布式zookpeer维护token和tike的生命周期问题
在线上增加新的公众号支持:(不包括支付:支付动态增加还没有做)
- 更改配置文件中的公众号列表,使用 WebResetZkData(对应的CoreResetZkData 只更新token相关数据,而web的则会自动调用core的再调用自己的) 手动更新zk上的数据(覆盖更新)
- 手动更新完整之后,这个时候在线的机器就会收到 最新的数据
- 把最新的配置文件同步到线上机器,然后重新启动服务器。完成线上机器的配置文件的替换
预计将来做成:能直接使用手动更新操作,新增对公众号的普通维护和支付维护。
zk目录介绍
服务路径 |
描述 |
/wx |
根路径 |
/wx/basetoken |
token 节点路径 |
/wx/ticket |
ticket节点路径 |
/wx/runing |
leader id 存放位置,用于定位是哪一台机器导致更新失效(出现127.0.0.1 霸占了leader) |
2.6. 关于消息/关键词自动回复
sdk实现了默认的规则框架:思路是:
- 配置文件中 根据cn.mrcode.wxsdk.core.dialogue.common.rule中所支持的规则实体,进行json数据填充
- 使用线程( new Thread(new RuleTiminUpdateTask()).start();)去监视文件,然后读取配置文件中的内容到内存中
- 在消息处理器里面根据自有需求获取关键词对应的规则实体,然后判断处理并返回;
RuleTiminUpdateTask.rules.get(keyword);
String type = rule.getType();
- 更具体的描述在 RuleTiminUpdateTask 中的说明;
2.7 本框架中,你需要关注的侧重点
- initConfig.xml 配置文件,配置好所需要的参数
- InMessageProcessingHandler接口 : 生产开发是必须自己实现该接口,编写自己的业务代码,在这个里面调用提供的各种服务和消息等转换(在DefaultInMessageProcessingHandler中有简单的消息类型示例)
3. 引用本sdk出现的FAQ
- Q:项目启动提示某某jar不存在等类似提示
A:项目中存在不兼容的版本jar包,引用sdk的项目中不存在sdk所使用的jar包,解决:引入所需要的jar包
- Q:如果出现 无可用token的日志。
A:最大的可能是每台机器的配置文件不一致。作为leader的机器上的配置文件中没有该appId的信息。
- Q:如果在运行过程中,有token/ticket过期了怎么办?
A:使用 WebResetZkData(对应的CoreResetZkData 只更新token相关数据,而web的则会自动调用core的再调用自己的) 手动更新zk上的数据(覆盖更新)
- Q:提示 wx.sdk.dialogue.common.XMLParaser 中 Node 相关的方法出错,(一般出现在My/eclipse中)
A:打开Java build path --> order and export 选项卡中,把 JRE(就是java jdk包移动到最顶端)
- Q:在使用过程中,特别是在linux中部署的web服务,会出现一台服务器出现两次或多次在zookpeer上注册临时目录,或则就是web容器服务被停止掉了,但是临时目录不会消失
以上问题就会导致出现更新维护数据错乱,或则更新不及时
A:有可能出现的问题有以下几点:
1. 其他使用的zk服务的客户端 的系统时间和zk服务器的不同步,导致通信有问题
2. tomcat配置文件配置有问题,导致项目资源被加载了两次,依赖web容器生命周期的功能 都会存在被启动两次的情况
3. tomcat进程可能存未杀死。
4. 开发和维护sdk注意事项
4.1. 协议约定
在开发和维护的时候,要严格遵守:
- 每个模块之间不能循环依赖
- 请求器中httpClient访问错误通过ReqException抛出
- 微信api返回的协议result 如果有错,通过WxException抛出,sdk最多只能校验result的协议异常。其他业务异常禁止sdk处理。在每个数据返回包中都有业务result字段,需要调用出自己判断处理
- sdk自己的一些规则异常,通过 SdkException抛出
- 严格按照 sdk 结构封装
4.2. 一些策略点
- 多公众号支付,提供的默认httpClient中会在初始化的时候通过支付公众号列表把证书请求器都创建出来。
- 在分布式的情况下,暂时只支持维护固定配置列表中的 token 或则 ticket公众号
-
[* 2016.04.22]
动态新增公众号方法:
- 任意一台服务起上手动重置zk数据(使用test包中cn.mrcode.wxsdk.**.upzk.curator.Client,把当前所有需要维护的公众号,包括新增的(initConfig.xml中) 重置一遍到zk上)
- 然后启动该服务器
原理解析:
- master 任务更新机器,提供对外读,进程写功能:
- 第一次获得master启动更新任务钱,会先从 zk上获取数据,解析成task列表,如果initConfig.xml中存在zk中没有的公众号,那么该公众号将被忽略加入task中
- 启动更新任务,以task列表为准,转换成延迟队列,然后阻塞获取超时的数据
- follow 只提供读功能 :
- 启动时,将会从zk上拉取一次数据,存入本地缓存中。
根据以上两步操作,就能让集群中的新启动的机器立即支持新的公众号服务,但是旧的机器 因为 策略1的限制下,将不能提供对新增公众号的服务支持,采用逐步重新启动所有机器,将能达到过渡更新的效果
- 配置上下文和初始化上下文,在重构中:目标是做成能动态根据配置文件修改后,自动重新加载新的配置文件内容
5. 暂未解决的或则未完善的点
6. 需求
- 进一步封装 初始化相关的代码,完善 封装的安全性。
- 扫码 触发的关注事件,和扫码触发的事件,貌似没有处理。
- 再次抽象化zkmaster
7. 更新问题汇总
2016.06.29
- 抽象生命周期维护任务类,优化该类执行逻辑代码
- 手动重置数据使用默认的test规则
- 修复生命周期维护重构后的一些bug
2016.06.28
- 修复 在liunx中设置sdk默认id的时候偶尔会获取到的ip地址为 127.0.0.1 的bug:根据测试看来,在linux中光靠程序优化处理不了获得正确的ip地址(应该需要修改host文件,显式的配置内网ip)
- 重构test下 curator中的 重置zk上的数据
- 重构提取curator中相关比较多的重复操作
- 增加test下 自动查找leader的path路径类
- 其他部分代码优化
2016.06.27
- 增加leader更新运行信息。用于定位是哪一台机器占用了leader位置
(由于暂时无法取得lockPath 创建的子节点名称,所以使用了新建子节点runing,用于保存leader运行id)
- 在维护的临时目录下写入id,分辨是哪一台机器在使用该目录
2016.06.23
- 在入口处增加id标识,在更新过程中,将会使用该id作为最后更新者定位某一台机器
2016.06.17
- 完善test的前置sdk初始化抽象类,基于基类快速完成测试
- 优化手动更新数据功能
- 无论是手动更新功能还是数据测试功能,都统一从ConfigContext中获取公众号列表信息
- web 中测试的时候,读取不到config.properties 文件,只能单独和 core中的配置文件分开了
- 重新整理了不需要的原生分布式维护生命周期相关类
- 分布式维护生命周期的 lastUpBy 也就是唯一id(用ip的话就不唯一了),现在使用当前机器的ip地址进行赋值
2016.06.16
- 把项目更改为 gradle构建的项目
- 把项目分模块:对话层功能(也就是基础的一些功能,按微信api划分) -- core;pay --> pay; web --> web
- 重构初始化类的逻辑:把web层的初始化分离到web层了: 现在的初始化逻辑相当于:如果没有web层,则直接new CoreInit;有web层就new WebInit
- 分布式下的功能已测(依赖zookpeer管理token 和 ticke);单机版的暂未测试
- 移除initConfig文件中的一些不需要的配置参数
- 手动更新的类文件在core 和 web 下的upzk中。同上面的规则一样,使用到了web 就直接执行web的就能更新core的了
2016.04.22
- 新增手动更新生命周期源:curator.updatezk --> public class Client 类中 main方法:用于动态新增前的准备工作
- 确认文档描述中的一些规范
2016.04.20
- 把简单初步的关键词延迟功能模型增加到本sdk中。并在sdk.dialogue.common.rule.task.RuleTiminUpdateTask 中标识了使用方法
由于定位是sdk,只是提供了一个简单的规则动态解析,规则模型,调用思路 ,要使用的话,需要在项目启动完成后,手动启动task线程,
或则(不使用本sdk提供的简单框架模型,自己模仿该思路定义一套更强大的规则模型)
支持的功能有:
1. 动态修改ruls源:wxrule.properties中的规则内容,task在一定时间内会自动重新获取覆盖内存中的ruls源,方便对规则的修改
2. 单关键字/多关键字 对应单条规则
3. 规则可以返回的类型:文本消息类型,单图文类型
- 新增一套分布式生命周期维护,使用 Curator zk客户端的master策略进行维护。试运行,把根目录集成到了一个里面
使用框架客户端来维护,性能和出错几率减少。
2016.04.19
- 增加了转接多客服消息的outMsg类型支持
2016.03.23
- 在初始化分布式zk的时候,把init方法放到构造函数外面。应该可以防止一些莫名奇妙的问题(比如:父类还没有构造完成,但是会回调子类的一些属性,虽然已经解决过了,但是以后再统一详细的调式吧)
2016.02.23
1.对获取Ticket 和 token的api增加了最后更新者的属性(在分布式中可根据它进行定位是那一台机器更新的)
2016.02.22
- 修改分布式tick和token task 中的日志打印方式。
2016.1.14
- 修改一些日志级别
- 对web项目支持一种session维护openid的支持
2015.11.20
- 简单增加xsd schema文件校验。降低程序中的一部分校验
2015.11.18
- 在配置文件中配置 httpClientClassName 项,可以动态的替换实现(IHttpClinet请求器的实现)
2015.11.16
- 替换sdk底层支持逻辑,支持多公众号支付功能,httpClient请求器/ssl证书请求器,现在由IHttpClinet的子类来管理实现
- 重构异常类:
- SdkException :顶层异常,sdk中的业务规则,等会使用该异常对象抛出。抛出的编码都存在SdkGlobalCode中
- ReqException :httpClient请求器,请求过程中出现的异常,通过该对象抛出,包括远程服务器不可访问等异常消息
- WxException : sdk中微信所返回的所有协议异常都由该对象抛出。比如:调用接口返回的 解析xml错误,token不能使用等异常。
2015.10.23
- 提供IHttpClinet请求器接口,在整个sdk访问过程中,都使用该接口进行远程调用。 为自定义提供请求器做铺垫
- 提供一个默认的DefeatHttpClient请求器实现,该请求器使用使用AsyncHttpClient作为基础实现
2015.10.23
- 新增测试类:解决token/ticket偶然过期问题(一般多出现在 zk服务器重复开关,多个项目更新同一个公众号的情况)。使用方式:测试包下: updateZk.Client.main(); 手动运行
2015.10.20
- 解决初始化 给私有静态常量赋值的 封装安全性问题。已经把显示的setter方法给移除。
2015.10.12
- 新增app端支付参数的获取
2015.10.10
- 统一日志打印,之前的日志打印有bug
- 更改一些类文件的说明信息位置,之前的doc查看不显示
- 新增 发送模版消息功能,增加一种 金融/银行 交成功通知的 模版内容支持。
- 消息处理增加 模版消息发送后的 成功与否的事件推送处理
2015.10.08
- 初始化基础token 和 ticket 休眠策略修改为5秒。(在本机是leader的情况下,有足够的时间刷新token到本地)
- ZkDistributedSingleNodeExecutor 修改超类,解决 由于第一次推送数据,构造块未执行完,造成的空指针问题。
- init类中初始化策略变更:在初始化 ticket前,会尝试先获取本地的token,有日志打印(一般来说,出现无可用的token的日志信息,造成的原因是,每台机器的配置文件不一致。
可以通过 本地可用token的数量 和 尝试获取的进度数量看出来一些端倪)
- 修改生命周期任务机制:为 动态新增修改公众号token做铺垫: 每次获取到数据,就更新本地 task列表(以zk上的列表为准),也就是说:动态增加公众号之前,需要手动的触发更新到zk上。
如果以后有这样的需求。可以写一个小工具类(目前的实现是:DelayQueue队列来获取超时的,每次超时后会重置DelayQueue队列(把最新的task列表放入DelayQueue队列中)
所以这样更新到本地就有一个时间延迟,只有有超时的时候,队列中才会被更新。 处理小工具的时候 需要考虑下这个问题
(1.不能修改公众号的账户信,如果需要。只能把该公众号移除zk,等待leader更新之后,再把该公众号加上))
2015.9.29
- 新增 jssdk ticket 支持分布式维护生命周期
- 解决ZkDistributedSingleNodeExecutor中出现的一些多线程空指针问题
- 暂时解决ZkDistributedSingleNodeExecutor的实现类 由于先初始化父类首次推动数据firstRefresh所照成的 自定义构造参数未实例化的问题。
- 由于引入 ticket 生命周期分布式支持,引发了:本地token没有数据的时候被ticket所调用,采用先初始化 基础token的zk服务,休眠2秒后再执行ticket的初始化操作。
- getAccesstoken操作,由于分布式数据第一次刷新到本地的时差,增加了策略:如果一直没有获取到数据,最多将尝试获取10次,每次休眠500毫秒。
- 新增log4j日志打印,方便调试(没能解决按自定义模版打印消息)
2015.9.23
- 初步实现 ZkDistributedSingleNodeExecutor 分布式单节点容灾
- 把之前写死的 配置,提出了一部分到initConfig.xml中,使用配置文件或则配置类来初始化sdk所需要的一些参数
2015.9.23~以前
初始迁移成独立模块。
8. 一张图看懂本sdk结构
微信封装sdk结构图.jpg
9. sdk层级讲解
说明:层级模块划分按照:微信公众平台后台登录后,左侧菜单中--> 接口权限 中列出的功能进行模块层级的划分
core
- context:
提供上下文环境配置,配置文件的解析配置
- test
测试基类: 用于提供初始化 配置参数等功能;用于测试每个模块的测试用列
- zkhelper.curator
封装操作zookpeer的curator框架工具类,也包括根据机器id查找zk路径
dialogue
按照微信APi划分模块,基础对话模块,文档下面所有的模块都会依赖此模块
common
- common.accessToken
的核心功能之一:
维护accessToken的生命周期,包括单机版(长久未使用,所以没有继续维护了,不保证能正常使用)和分布式版本
- common.exception
包括全局code错误码,和sdk的异常类;sdk中的大部分可捕获异常都会被包装为该包下面的异常类抛出
- common.httpClient
核心功能之一:
提供sdk所操作的http请求接口,和提供默认实现类,可以继承该接口然后在配置文件里面配置,使用自己的http请求器
- common.rule
基础功能框架的实现,sdk不会主动调用该功能,只是演示了一下思路。功能有:自动回复,按关键词回复,按事件触发回复,
要结合InMessageProcessingHandler类来开启调用该模块的功能。里面相关的类说明里面有详细的说明
- common下其他类
都是基础的工具类和实体类
hander
核心功能之一:
开发者模式接入处理接口和默认实现类,需要接入开发者模式的话,此接口是重点实现对象,需要根据自身的业务规则
实现具体所需要的功能,里面有默认的类进行一个示例
protocol
协议数据包,req包实体,res包实体,根据微信api进行划分。
基础会话等api请求回包,全局基类接口等协议都在这里了
service
客户端使用量最多的对外包
:此包封装了该模块下所有的接口请求和服务。一般客户端都只需要使用该类下面的服务类即可
1. Access 是开发者模式入口所需要的参数逻辑进行了简单的封装,一般在开发者回调链接的action中直接委托该类即可
Access.in(inMessageProcessingHandler,"",publicAccount,signature,timestamp,nonce,echostr,request,response);
2. 其他服务
按照微信 api 功能进行封装
pay
微信支付相关的功能都在该模块下进行封装,依赖 dialogue 模块
common
公用操作
包括:支付错误码,返回码,交易类型等
hander
数据辅助处理操作
提供对支付接口所请求的api参数进行校验功能
protocol
协议数据包,req包实体,res包实体,根据微信api进行划分。
支付相关的api请求回报协议都在这里了
service
客户端使用量最多的对外包
:此包封装了该模块下所有的接口请求和服务。一般客户端都只需要使用该类下面的服务类即可
支付功能对外暴露的服务功能
web
微信jssdk相关的api功能都在这里了
common
公用操作
包括:ticket的生命周期维护,与dialogue中的accessToken生命周期是同一类功能;微信网页授权 链接生成功能等
hander
数据辅助处理操作
提供对支付接口所请求的api参数进行校验功能
protocol
协议数据包,req包实体,res包实体,根据微信api进行划分。
支付相关的api请求回报协议都在这里了
service
客户端使用量最多的对外包
:此包封装了该模块下所有的接口请求和服务。一般客户端都只需要使用该类下面的服务类即可
jssdk授权,openid,jssdk支付参数获取等功能