大重构:weixin4j精简为weixin4j-base、weixin4j-mp、weixin4j-qy、weixin4j-server四个子工程
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
# Mobile Tools for Java (J2ME)
|
||||
.mtj.tmp/
|
||||
|
||||
# Package Files #
|
||||
*.jar
|
||||
*.war
|
||||
*.ear
|
||||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
|
||||
*~
|
||||
|
||||
# eclipse ignore
|
||||
*.settings/*
|
||||
/.project
|
||||
/.classpath
|
||||
/.tomcatplugin
|
||||
|
||||
# idea ignore
|
||||
/.idea
|
||||
*.iml
|
||||
|
||||
# maven ignore
|
||||
target/*
|
||||
|
||||
# other ignore
|
||||
*.log
|
||||
*.tmp
|
||||
Thumbs.db
|
||||
/target/
|
||||
.DS_Store
|
||||
@@ -0,0 +1,19 @@
|
||||
* 2014-11-03
|
||||
|
||||
+ 得到`weixin4j-mp-server`工程
|
||||
|
||||
* 2014-11-15
|
||||
|
||||
+ 解决`server工程`打包后不能运行问题(`ClassUtil`无法获取jar包里面的类)
|
||||
|
||||
+ 新增被动消息的`加密`以及回复消息的`解密`
|
||||
|
||||
* 2014-11-23
|
||||
|
||||
+ `WeixinServerBootstrap`重命名为`WeixinMpServerBootstrap`
|
||||
|
||||
* 2015-03-25
|
||||
|
||||
+ 新增客服创建、关闭、转接会话事件
|
||||
|
||||
+ 新增deploy.xml远程部署ant脚本
|
||||
@@ -0,0 +1,67 @@
|
||||
weixin4j-server
|
||||
===============
|
||||
|
||||
微信netty服务
|
||||
------------
|
||||
|
||||
功能列表
|
||||
-------
|
||||
|
||||
* `netty构建服务器`
|
||||
|
||||
* `消息分发`
|
||||
|
||||
如何使用
|
||||
--------
|
||||
1.正确填写`weixin.properties`中的属性值
|
||||
|
||||
| 属性名 | 说明 |
|
||||
| :---------- | :-------------- |
|
||||
| account | 微信公众号信息 `json格式` |
|
||||
| token_path | 使用FileTokenHolder时token保存的物理路径 |
|
||||
| qr_path | 调用二维码接口时保存二维码图片的物理路径 |
|
||||
| media_path | 调用媒体接口时保存媒体文件的物理路径 |
|
||||
| bill_path | 调用下载对账单接口保存excel文件的物理路径 |
|
||||
| ca_file | 调用某些接口(支付相关)强制需要auth的ca授权文件 |
|
||||
|
||||
示例(properties中换行用右斜杆\\)
|
||||
|
||||
account={"id":"appId","secret":"appSecret",\\
|
||||
"token":"开放者的token","openId":"公众号的openid 非必须",\\
|
||||
"encodingAesKey":"公众号设置了加密方式且为「安全模式」时需要填入",\\
|
||||
"mchId":"V3.x版本下的微信商户号",\\
|
||||
"partnerId":"V2版本下的财付通的商户号","partnerKey":"V2版本下的财付通商户权限密钥Key",\\
|
||||
"version":"针对微信支付的版本号(目前可能为2,3),如果不填则按照mchId非空与否来做判断",\\
|
||||
"paySignKey":"微信支付中调用API的密钥"}
|
||||
|
||||
token_path=/tmp/weixin/token
|
||||
qr_path=/tmp/weixin/qr
|
||||
media_path=/tmp/weixin/media
|
||||
bill_path=/tmp/weixin/bill
|
||||
ca_file=/tmp/weixin/xxxxx.p12 | xxxxx.pfx
|
||||
|
||||
2.在对应的action中实现自己的具体业务 如 TextAction 则表示收到文本消息
|
||||
|
||||
@Override
|
||||
public ResponseMessage execute(TextMessage inMessage) {
|
||||
return new ResponseMessage(new Text("Hello World!"), inMessage);
|
||||
}
|
||||
|
||||
3.`mvn package`,得到一个zip的压缩包,解压(也可使用deploy.xml部署到远程服务器)到`启动目录`
|
||||
|
||||
4.启动netty服务(`com.foxinmy.weixin4j.mp.startup.WeixinMpServerBootstrap`)
|
||||
|
||||
sh startup.sh start
|
||||
|
||||
> 1.服务的启动脚本[startup.sh](./src/main/startup.sh)需要被注意到,有`JAVA_HOME`和`APP_HOME`两个参数.
|
||||
|
||||
> 2.其中`JAVA_HOME`参数值指的是java运行环境(jre|jdk)的安装根目录,如果与脚本中的值不一致,可以改更为实际的路径或者使用`ln -s t/usr/local/java target`软链接命令创建期望的链接.
|
||||
|
||||
> 3.其中`APP_HOME`参数值指的是本服务的启动目录,此目录需要被正确事先创建好,同时[`deploy.xml`](./deploy.xml)远程部署命令也依赖于此.
|
||||
|
||||
> 4.Ant远程部署[`deploy.xml`](./deploy.xml)的正确执行需要[`jsch`](http://www.jcraft.com/jsch/)包的支持,下载jar包将其引入执行Ant命令时的`classpath`中.
|
||||
|
||||
> 5.一般来说*Action事件处理类中应该有自己的实际业务类(`service`)需要被注入,可以使用org.springframework.context.ApplicationContext#getBeansWithAnnotation(ActionAnnotation.class)函数获取Action集合后再来实现[`AbstractActionMapping`](https://github.com/foxinmy/weixin4j/blob/master/weixin4j-base/src/main/java/com/foxinmy/weixin4j/action/mapping/AbstractActionMapping.java).
|
||||
|
||||
[更新LOG](./CHANGE.md)
|
||||
----------------------
|
||||
@@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project name="mp-server" default="deploy" basedir=".">
|
||||
<property name="app.home" value="." />
|
||||
<property name="target.home" value="${app.home}/target" />
|
||||
<property name="zip.name" value="weixin-mp-server-bin.zip" />
|
||||
<property name="host" value="服务器IP" />
|
||||
<property name="pwd" value="服务器口令" />
|
||||
<property name="main.dir" value="服务部署的主目录,如/usr/local/weixin" />
|
||||
<property name="sub.dir" value="服务部署的子目录,如weixin-mp-server" />
|
||||
|
||||
<target name="removeZip">
|
||||
<echo>
|
||||
${host}:删除[${zip.name}]...
|
||||
</echo>
|
||||
<sshexec host="${host}" username="root" password="${pwd}" trust="true" command="cd ${main.dir};pwd;rm -rf ${main.dir}/${zip.name};" />
|
||||
</target>
|
||||
|
||||
<target name="uploadZip" depends="removeZip">
|
||||
<echo>
|
||||
${host}:上传[${zip.name}]...
|
||||
</echo>
|
||||
<scp file="${target.home}/${zip.name}" todir="root:${pwd}@${host}:${main.dir}" trust="true" />
|
||||
</target>
|
||||
|
||||
<target name="shutdownService" depends="uploadZip">
|
||||
<echo>
|
||||
${host}:停止[${sub.dir}]...
|
||||
</echo>
|
||||
<sshexec host="${host}" username="root" password="${pwd}" trust="true" command="cd ${main.dir}/${sub.dir};pwd;sh startup.sh stop;rm -rf ${main.dir}/${sub.dir};" />
|
||||
</target>
|
||||
|
||||
<target name="unzipProject" depends="shutdownService">
|
||||
<echo>
|
||||
${host}:解压${zip.name}...
|
||||
</echo>
|
||||
<sshexec host="${host}" username="root" password="${pwd}" trust="true" command="cd ${main.dir};pwd;unzip ${zip.name};" />
|
||||
</target>
|
||||
|
||||
<target name="startupService" depends="unzipProject">
|
||||
<echo>
|
||||
${host}:启动服务...
|
||||
</echo>
|
||||
<sshexec host="${host}" username="root" password="${pwd}" trust="true" command="cd ${main.dir}/${sub.dir};pwd;sh startup.sh start;" />
|
||||
</target>
|
||||
|
||||
<target name="build" depends="startupService">
|
||||
<echo>
|
||||
target.home = ${target.home}
|
||||
file.zip = ${target.home}/${zip.name}
|
||||
</echo>
|
||||
</target>
|
||||
<target name="deploy" depends="build">
|
||||
</target>
|
||||
</project>
|
||||
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0"?>
|
||||
<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">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.foxinmy</groupId>
|
||||
<artifactId>weixin4j</artifactId>
|
||||
<version>1.3</version>
|
||||
</parent>
|
||||
<artifactId>weixin4j-server</artifactId>
|
||||
<name>weixin4j-server</name>
|
||||
<url>https://github.com/foxinmy/weixin4j/tree/master/weixin4j-server</url>
|
||||
<description>微信netty服务</description>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<configuration>
|
||||
<finalName>weixin-server</finalName>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-all</artifactId>
|
||||
<version>${netty.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>${jcl.over.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,36 @@
|
||||
<assembly
|
||||
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
|
||||
<id>bin</id>
|
||||
<formats>
|
||||
<format>zip</format>
|
||||
</formats>
|
||||
<includeBaseDirectory>true</includeBaseDirectory>
|
||||
<dependencySets>
|
||||
<dependencySet>
|
||||
<useProjectArtifact>true</useProjectArtifact>
|
||||
<outputDirectory>/lib</outputDirectory>
|
||||
</dependencySet>
|
||||
</dependencySets>
|
||||
<fileSets>
|
||||
<fileSet>
|
||||
<directory>com/foxinmy/weixin4j</directory>
|
||||
<includes>
|
||||
<include>**/*.md</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>src/main</directory>
|
||||
<outputDirectory>/</outputDirectory>
|
||||
<includes>
|
||||
<include>*.sh</include>
|
||||
<include>*.bat</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>src/main/resources</directory>
|
||||
<outputDirectory>/conf</outputDirectory>
|
||||
</fileSet>
|
||||
</fileSets>
|
||||
</assembly>
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.foxinmy.weixin4j.model;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 微信账号信息
|
||||
*
|
||||
* @className WeixinAccount
|
||||
* @author jy
|
||||
* @date 2014年11月18日
|
||||
* @since JDK 1.7
|
||||
* @see
|
||||
*/
|
||||
public class WeixinAccount implements Serializable {
|
||||
private static final long serialVersionUID = -6001008896414323534L;
|
||||
|
||||
/**
|
||||
* 唯一的身份标识
|
||||
*/
|
||||
private String id;
|
||||
/**
|
||||
* 调用接口的密钥
|
||||
*/
|
||||
private String secret;
|
||||
private String token;
|
||||
/**
|
||||
* 安全模式下的加密密钥
|
||||
*/
|
||||
private String encodingAesKey;
|
||||
|
||||
public WeixinAccount() {
|
||||
}
|
||||
|
||||
public WeixinAccount(String id, String secret) {
|
||||
this.id = id;
|
||||
this.secret = secret;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getSecret() {
|
||||
return secret;
|
||||
}
|
||||
|
||||
public void setSecret(String secret) {
|
||||
this.secret = secret;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public String getEncodingAesKey() {
|
||||
return encodingAesKey;
|
||||
}
|
||||
|
||||
public void setEncodingAesKey(String encodingAesKey) {
|
||||
this.encodingAesKey = encodingAesKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "id=" + id + ", secret=" + secret + ", token=" + token
|
||||
+ ", encodingAesKey=" + encodingAesKey;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
WeixinMessageDecoder:对微信消息进行解码
|
||||
|
||||
WeixinMessageEncoder:对微信消息进行编码
|
||||
|
||||
WeixinServerHandler:微信请求处理类
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.foxinmy.weixin4j.server;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToMessageDecoder;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.QueryStringDecoder;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.foxinmy.weixin4j.message.HttpWeixinMessage;
|
||||
import com.foxinmy.weixin4j.model.WeixinAccount;
|
||||
import com.foxinmy.weixin4j.type.EncryptType;
|
||||
import com.foxinmy.weixin4j.util.ConfigUtil;
|
||||
import com.foxinmy.weixin4j.util.Consts;
|
||||
import com.foxinmy.weixin4j.util.MessageUtil;
|
||||
|
||||
/**
|
||||
* 微信消息解码类
|
||||
*
|
||||
* @className WeixinMessageDecoder
|
||||
* @author jy
|
||||
* @date 2014年11月13日
|
||||
* @since JDK 1.7
|
||||
* @see <a
|
||||
* href="http://mp.weixin.qq.com/wiki/0/61c3a8b9d50ac74f18bdf2e54ddfc4e0.html">加密接入指引</a>
|
||||
*/
|
||||
public class WeixinMessageDecoder extends
|
||||
MessageToMessageDecoder<FullHttpRequest> {
|
||||
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, FullHttpRequest req,
|
||||
List<Object> out) throws Exception {
|
||||
String content = req.content().toString(Consts.UTF_8);
|
||||
HttpWeixinMessage message = new HttpWeixinMessage();
|
||||
if (!content.isEmpty()) {
|
||||
// TODO
|
||||
}
|
||||
message.setMethod(req.getMethod().name());
|
||||
QueryStringDecoder queryDecoder = new QueryStringDecoder(req.getUri(),
|
||||
true);
|
||||
log.info("\n=================receive request=================");
|
||||
log.info("{}", req.getMethod());
|
||||
log.info("{}", req.getUri());
|
||||
log.info("{}", content);
|
||||
Map<String, List<String>> parameters = queryDecoder.parameters();
|
||||
String encryptType = parameters.containsKey("encrypt_type") ? parameters
|
||||
.get("encrypt_type").get(0) : EncryptType.RAW.name();
|
||||
message.setEncryptType(EncryptType.valueOf(encryptType.toUpperCase()));
|
||||
String echoStr = parameters.containsKey("echostr") ? parameters.get(
|
||||
"echostr").get(0) : "";
|
||||
message.setEchoStr(echoStr);
|
||||
String timeStamp = parameters.containsKey("timestamp") ? parameters
|
||||
.get("timestamp").get(0) : "";
|
||||
message.setTimeStamp(timeStamp);
|
||||
String nonce = parameters.containsKey("nonce") ? parameters
|
||||
.get("nonce").get(0) : "";
|
||||
message.setNonce(nonce);
|
||||
String signature = parameters.containsKey("signature") ? parameters
|
||||
.get("signature").get(0) : "";
|
||||
message.setSignature(signature);
|
||||
|
||||
message.setOriginalContent(content);
|
||||
if (message.getEncryptType() == EncryptType.AES) {
|
||||
WeixinAccount mpAccount = ConfigUtil.getWeixinAccount();
|
||||
message.setOriginalContent(MessageUtil.aesDecrypt(
|
||||
mpAccount.getId(), mpAccount.getEncodingAesKey(),
|
||||
message.getEncryptContent()));
|
||||
}
|
||||
out.add(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.foxinmy.weixin4j.server;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.foxinmy.weixin4j.message.HttpWeixinMessage;
|
||||
|
||||
/**
|
||||
* 微信被动消息处理类
|
||||
*
|
||||
* @className WeixinServerHandler
|
||||
* @author jy
|
||||
* @date 2014年11月16日
|
||||
* @since JDK 1.7
|
||||
* @see
|
||||
*/
|
||||
public class WeixinMessageHandler extends
|
||||
SimpleChannelInboundHandler<HttpWeixinMessage> {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
public void channelReadComplete(ChannelHandlerContext ctx) {
|
||||
ctx.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
cause.printStackTrace();
|
||||
ctx.close();
|
||||
log.error("catch the exception:{}", cause.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx,
|
||||
HttpWeixinMessage httpMessage) throws Exception {
|
||||
|
||||
String xmlContent = httpMessage.getOriginalContent();
|
||||
|
||||
|
||||
/*
|
||||
log.info("\n=================message in=================\n{}",
|
||||
httpMessage);
|
||||
boolean isGet = httpMessage.getMethod().equals(HttpMethod.GET.name());
|
||||
boolean validate = false;
|
||||
if (isGet || httpMessage.getEncryptType() == EncryptType.RAW) {
|
||||
validate = MessageUtil.signature(httpMessage.getToken(),
|
||||
httpMessage.getTimeStamp(), httpMessage.getNonce()).equals(
|
||||
httpMessage.getSignature());
|
||||
if (isGet && validate) {
|
||||
ctx.write(HttpUtil.createWeixinMessageResponse(
|
||||
httpMessage.getEchoStr(), ContentType.TEXT_PLAIN));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
validate = MessageUtil.signature(httpMessage.getToken(),
|
||||
httpMessage.getTimeStamp(), httpMessage.getNonce(),
|
||||
httpMessage.getEncryptContent()).equals(
|
||||
httpMessage.getSignature());
|
||||
}
|
||||
if (!validate) {
|
||||
ctx.write(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
|
||||
HttpResponseStatus.FORBIDDEN));
|
||||
return;
|
||||
}
|
||||
if (action == null) {
|
||||
ctx.write(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
|
||||
HttpResponseStatus.NOT_FOUND));
|
||||
return;
|
||||
}
|
||||
ResponseMessage response = action.execute(xmlContent);
|
||||
log.info("\n=================message out=================\n{}",
|
||||
response);
|
||||
if (response == null) {
|
||||
ctx.write(HttpUtil.createWeixinMessageResponse("",
|
||||
ContentType.TEXT_PLAIN));
|
||||
return;
|
||||
}
|
||||
if (httpMessage.getEncryptType() == EncryptType.RAW) {
|
||||
ctx.write(HttpUtil.createWeixinMessageResponse(response.toXml(),
|
||||
null));
|
||||
} else {
|
||||
ctx.write(response);
|
||||
}*/
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package com.foxinmy.weixin4j.server;
|
||||
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.handler.codec.http.HttpObjectAggregator;
|
||||
import io.netty.handler.codec.http.HttpServerCodec;
|
||||
|
||||
public class WeixinServerInitializer extends ChannelInitializer<SocketChannel> {
|
||||
|
||||
@Override
|
||||
protected void initChannel(SocketChannel channel) throws Exception {
|
||||
ChannelPipeline pipeline = channel.pipeline();
|
||||
pipeline.addLast(new HttpServerCodec());
|
||||
pipeline.addLast(new HttpObjectAggregator(65536));
|
||||
pipeline.addLast(new WeixinMessageDecoder());
|
||||
//pipeline.addLast(new WeixinMessageEncoder());
|
||||
pipeline.addLast(new WeixinMessageHandler());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.foxinmy.weixin4j.startup;
|
||||
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.handler.logging.LoggingHandler;
|
||||
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import com.foxinmy.weixin4j.server.WeixinServerInitializer;
|
||||
|
||||
/**
|
||||
* 微信netty服务启动程序
|
||||
*
|
||||
* @className WeixinServerBootstrap
|
||||
* @author jy
|
||||
* @date 2014年10月12日
|
||||
* @since JDK 1.7
|
||||
* @see
|
||||
*/
|
||||
public final class WeixinServerBootstrap {
|
||||
|
||||
private final static int port;
|
||||
private final static int workerThreads;
|
||||
static {
|
||||
ResourceBundle netty = ResourceBundle.getBundle("netty");
|
||||
port = Integer.parseInt(netty.getString("port"));
|
||||
workerThreads = Integer.parseInt(netty.getString("workerThreads"));
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
|
||||
EventLoopGroup workerGroup = new NioEventLoopGroup(workerThreads);
|
||||
try {
|
||||
ServerBootstrap b = new ServerBootstrap();
|
||||
b.option(ChannelOption.SO_BACKLOG, 1024);
|
||||
b.group(bossGroup, workerGroup)
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.handler(new LoggingHandler())
|
||||
.childHandler(new WeixinServerInitializer());
|
||||
Channel ch = b.bind(port).sync().channel();
|
||||
System.err.println("weixin server startup OK:" + port);
|
||||
ch.closeFuture().sync();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
bossGroup.shutdownGracefully();
|
||||
workerGroup.shutdownGracefully();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.foxinmy.weixin4j.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Set;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.foxinmy.weixin4j.model.WeixinAccount;
|
||||
|
||||
/**
|
||||
* 商户配置工具类
|
||||
*
|
||||
* @className ConfigUtil
|
||||
* @author jy
|
||||
* @date 2014年10月31日
|
||||
* @since JDK 1.7
|
||||
* @see
|
||||
*/
|
||||
public class ConfigUtil {
|
||||
private final static String CLASSPATH_PREFIX = "classpath:";
|
||||
private final static String CLASSPATH_VALUE;
|
||||
private final static ResourceBundle weixinBundle;
|
||||
static {
|
||||
weixinBundle = ResourceBundle.getBundle("weixin");
|
||||
Set<String> keySet = weixinBundle.keySet();
|
||||
File file = null;
|
||||
CLASSPATH_VALUE = Thread.currentThread().getContextClassLoader()
|
||||
.getResource("").getPath();
|
||||
for (String key : keySet) {
|
||||
if (!key.endsWith("_path")) {
|
||||
continue;
|
||||
}
|
||||
file = new File(getValue(key).replaceFirst(CLASSPATH_PREFIX,
|
||||
CLASSPATH_VALUE));
|
||||
if (!file.exists() && !file.mkdirs()) {
|
||||
System.err.append(String.format("%s create fail.%n",
|
||||
file.getAbsolutePath()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取weixin.properties文件中的key值
|
||||
*
|
||||
* @param key
|
||||
* @return
|
||||
*/
|
||||
public static String getValue(String key) {
|
||||
return weixinBundle.getString(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断属性是否存在[classpath:]如果存在则拼接项目路径后返回 一般用于文件的绝对路径获取
|
||||
*
|
||||
* @param key
|
||||
* @return
|
||||
*/
|
||||
public static String getClassPathValue(String key) {
|
||||
return new File(getValue(key).replaceFirst(CLASSPATH_PREFIX,
|
||||
CLASSPATH_VALUE)).getPath();
|
||||
}
|
||||
|
||||
public static WeixinAccount getWeixinAccount() {
|
||||
String text = getValue("account");
|
||||
return JSON.parseObject(text, WeixinAccount.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.foxinmy.weixin4j.util;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* 常量类
|
||||
*
|
||||
* @className Consts
|
||||
* @author jy
|
||||
* @date 2015年4月19日
|
||||
* @since JDK 1.7
|
||||
* @see
|
||||
*/
|
||||
public final class Consts {
|
||||
|
||||
public static final Charset UTF_8 = Charset.forName("UTF-8");
|
||||
public static final Charset GBK = Charset.forName("GBK");
|
||||
public static final String SUCCESS = "SUCCESS";
|
||||
public static final String FAIL = "FAIL";
|
||||
public static final String SunX509 = "SunX509";
|
||||
public static final String JKS = "JKS";
|
||||
public static final String PKCS12 = "PKCS12";
|
||||
public static final String TLS = "TLS";
|
||||
public static final String X509 = "X.509";
|
||||
public static final String AES = "AES";
|
||||
public static final String CONTENTTYPE$APPLICATION_XML = "application/xml";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.foxinmy.weixin4j.util;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
public final class DigestUtil {
|
||||
|
||||
public static String SHA1(String decript) {
|
||||
try {
|
||||
MessageDigest digest = java.security.MessageDigest
|
||||
.getInstance("SHA-1");
|
||||
digest.update(decript.getBytes());
|
||||
byte messageDigest[] = digest.digest();
|
||||
// Create Hex String
|
||||
StringBuffer hexString = new StringBuffer();
|
||||
// 字节数组转换为 十六进制 数
|
||||
for (int i = 0; i < messageDigest.length; i++) {
|
||||
String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
|
||||
if (shaHex.length() < 2) {
|
||||
hexString.append(0);
|
||||
}
|
||||
hexString.append(shaHex);
|
||||
}
|
||||
return hexString.toString();
|
||||
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static String SHA(String decript) {
|
||||
try {
|
||||
MessageDigest digest = java.security.MessageDigest
|
||||
.getInstance("SHA");
|
||||
digest.update(decript.getBytes());
|
||||
byte messageDigest[] = digest.digest();
|
||||
// Create Hex String
|
||||
StringBuffer hexString = new StringBuffer();
|
||||
// 字节数组转换为 十六进制 数
|
||||
for (int i = 0; i < messageDigest.length; i++) {
|
||||
String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
|
||||
if (shaHex.length() < 2) {
|
||||
hexString.append(0);
|
||||
}
|
||||
hexString.append(shaHex);
|
||||
}
|
||||
return hexString.toString();
|
||||
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static String MD5(String input) {
|
||||
try {
|
||||
// 获得MD5摘要算法的 MessageDigest 对象
|
||||
MessageDigest mdInst = MessageDigest.getInstance("MD5");
|
||||
// 使用指定的字节更新摘要
|
||||
mdInst.update(input.getBytes());
|
||||
// 获得密文
|
||||
byte[] md = mdInst.digest();
|
||||
// 把密文转换成十六进制的字符串形式
|
||||
StringBuffer hexString = new StringBuffer();
|
||||
// 字节数组转换为 十六进制 数
|
||||
for (int i = 0; i < md.length; i++) {
|
||||
String shaHex = Integer.toHexString(md[i] & 0xFF);
|
||||
if (shaHex.length() < 2) {
|
||||
hexString.append(0);
|
||||
}
|
||||
hexString.append(shaHex);
|
||||
}
|
||||
return hexString.toString();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.foxinmy.weixin4j.util;
|
||||
|
||||
import static io.netty.handler.codec.http.HttpHeaders.Names.CONNECTION;
|
||||
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_LENGTH;
|
||||
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE;
|
||||
import static io.netty.handler.codec.http.HttpHeaders.Names.DATE;
|
||||
import static io.netty.handler.codec.http.HttpHeaders.Names.SERVER;
|
||||
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
|
||||
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpHeaders.Values;
|
||||
import io.netty.handler.codec.http.HttpResponse;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* HTTP工具类
|
||||
*
|
||||
* @className HttpUtil
|
||||
* @author jy
|
||||
* @date 2014年11月15日
|
||||
* @since JDK 1.7
|
||||
* @see
|
||||
*/
|
||||
public class HttpUtil {
|
||||
|
||||
public static HttpResponse createWeixinMessageResponse(String content,
|
||||
String contentType) {
|
||||
FullHttpResponse httpResponse = new DefaultFullHttpResponse(HTTP_1_1,
|
||||
OK, Unpooled.copiedBuffer(content, Consts.UTF_8));
|
||||
httpResponse.headers().set(
|
||||
CONTENT_TYPE,
|
||||
String.format("%s;encoding=%s", contentType,
|
||||
Consts.UTF_8.displayName()));
|
||||
httpResponse.headers().set(CONTENT_LENGTH,
|
||||
content.getBytes(Consts.UTF_8).length);
|
||||
httpResponse.headers().set(CONNECTION, Values.KEEP_ALIVE);
|
||||
httpResponse.headers().set(DATE, new Date());
|
||||
httpResponse.headers().set(SERVER, "netty4");
|
||||
return httpResponse;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
package com.foxinmy.weixin4j.util;
|
||||
|
||||
import io.netty.handler.codec.base64.Base64;
|
||||
import io.netty.handler.codec.base64.Base64Encoder;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
/**
|
||||
* 消息工具类
|
||||
*
|
||||
* @className MessageUtil
|
||||
* @author jy
|
||||
* @date 2014年10月31日
|
||||
* @since JDK 1.7
|
||||
* @see
|
||||
*/
|
||||
public class MessageUtil {
|
||||
|
||||
/**
|
||||
* 验证微信签名
|
||||
*
|
||||
* @param signature
|
||||
* 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数
|
||||
* @return 开发者通过检验signature对请求进行相关校验。若确认此次GET请求来自微信服务器
|
||||
* 请原样返回echostr参数内容,则接入生效 成为开发者成功,否则接入失败
|
||||
* @see <a
|
||||
* href="http://mp.weixin.qq.com/wiki/0/61c3a8b9d50ac74f18bdf2e54ddfc4e0.html">接入指南</a>
|
||||
*/
|
||||
public static String signature(String... para) {
|
||||
Arrays.sort(para);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String str : para) {
|
||||
sb.append(str);
|
||||
}
|
||||
return DigestUtil.SHA1(sb.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 对xml消息加密
|
||||
*
|
||||
* @param appId
|
||||
* 应用ID
|
||||
* @param encodingAesKey
|
||||
* 加密密钥
|
||||
* @param xmlContent
|
||||
* 原始消息体
|
||||
* @return aes加密后的消息体
|
||||
* @throws WeixinException
|
||||
*/
|
||||
public static String aesEncrypt(String appId, String encodingAesKey,
|
||||
String xmlContent) throws RuntimeException {
|
||||
byte[] randomBytes = RandomUtil.generateString(16).getBytes(
|
||||
Consts.UTF_8);
|
||||
byte[] xmlBytes = xmlContent.getBytes(Consts.UTF_8);
|
||||
int xmlLength = xmlBytes.length;
|
||||
byte[] orderBytes = new byte[4];
|
||||
orderBytes[3] = (byte) (xmlLength & 0xFF);
|
||||
orderBytes[2] = (byte) (xmlLength >> 8 & 0xFF);
|
||||
orderBytes[1] = (byte) (xmlLength >> 16 & 0xFF);
|
||||
orderBytes[0] = (byte) (xmlLength >> 24 & 0xFF);
|
||||
byte[] appidBytes = appId.getBytes(Consts.UTF_8);
|
||||
int byteLength = randomBytes.length + xmlLength + orderBytes.length
|
||||
+ appidBytes.length;
|
||||
// ... + pad: 使用自定义的填充方式对明文进行补位填充
|
||||
byte[] padBytes = PKCS7Encoder.encode(byteLength);
|
||||
// random + endian + xml + appid + pad 获得最终的字节流
|
||||
byte[] unencrypted = new byte[byteLength + padBytes.length];
|
||||
byteLength = 0;
|
||||
// src:源数组;srcPos:源数组要复制的起始位置;dest:目的数组;destPos:目的数组放置的起始位置;length:复制的长度
|
||||
System.arraycopy(randomBytes, 0, unencrypted, byteLength,
|
||||
randomBytes.length);
|
||||
byteLength += randomBytes.length;
|
||||
System.arraycopy(orderBytes, 0, unencrypted, byteLength,
|
||||
orderBytes.length);
|
||||
byteLength += orderBytes.length;
|
||||
System.arraycopy(xmlBytes, 0, unencrypted, byteLength, xmlBytes.length);
|
||||
byteLength += xmlBytes.length;
|
||||
System.arraycopy(appidBytes, 0, unencrypted, byteLength,
|
||||
appidBytes.length);
|
||||
byteLength += appidBytes.length;
|
||||
System.arraycopy(padBytes, 0, unencrypted, byteLength, padBytes.length);
|
||||
try {
|
||||
byte[] aesKey = Base64.de(encodingAesKey + "=");
|
||||
// 设置加密模式为AES的CBC模式
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
|
||||
SecretKeySpec keySpec = new SecretKeySpec(aesKey, Consts.AES);
|
||||
IvParameterSpec iv = new IvParameterSpec(aesKey, 0, 16);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
|
||||
// 加密
|
||||
byte[] encrypted = cipher.doFinal(unencrypted);
|
||||
// 使用BASE64对加密后的字符串进行编码
|
||||
return Base64.(encrypted);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("-40006,AES加密失败:", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对AES消息解密
|
||||
*
|
||||
* @param appId
|
||||
* @param encodingAesKey
|
||||
* aes加密的密钥
|
||||
* @param encryptContent
|
||||
* 加密的消息体
|
||||
* @return 解密后的字符
|
||||
* @throws WeixinException
|
||||
*/
|
||||
public static String aesDecrypt(String appId, String encodingAesKey,
|
||||
String encryptContent) throws RuntimeException {
|
||||
byte[] aesKey = Base64..decodeBase64(encodingAesKey + "=");
|
||||
byte[] original;
|
||||
try {
|
||||
// 设置解密模式为AES的CBC模式
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
|
||||
SecretKeySpec key_spec = new SecretKeySpec(aesKey, Consts.AES);
|
||||
IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey,
|
||||
0, 16));
|
||||
cipher.init(Cipher.DECRYPT_MODE, key_spec, iv);
|
||||
// 使用BASE64对密文进行解码
|
||||
byte[] encrypted = Base64.decodeBase64(encryptContent);
|
||||
// 解密
|
||||
original = cipher.doFinal(encrypted);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("-40007,AES解密失败:", e);
|
||||
}
|
||||
String xmlContent, fromAppId;
|
||||
try {
|
||||
// 去除补位字符
|
||||
byte[] bytes = PKCS7Encoder.decode(original);
|
||||
// 获取表示xml长度的字节数组
|
||||
byte[] lengthByte = Arrays.copyOfRange(bytes, 16, 20);
|
||||
// 获取xml消息主体的长度(byte[]2int)
|
||||
// http://my.oschina.net/u/169390/blog/97495
|
||||
int xmlLength = lengthByte[3] & 0xff | (lengthByte[2] & 0xff) << 8
|
||||
| (lengthByte[1] & 0xff) << 16
|
||||
| (lengthByte[0] & 0xff) << 24;
|
||||
xmlContent = new String(Arrays.copyOfRange(bytes, 20,
|
||||
20 + xmlLength), Consts.UTF_8);
|
||||
fromAppId = new String(Arrays.copyOfRange(bytes, 20 + xmlLength,
|
||||
bytes.length), Consts.UTF_8);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("-40008,公众平台发送的xml不合法:" + e.getMessage());
|
||||
}
|
||||
// 校验appId是否一致
|
||||
if (!fromAppId.trim().equals(appId)) {
|
||||
throw new RuntimeException("-40005,校验AppID失败");
|
||||
}
|
||||
return xmlContent;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* 对公众平台发送给公众账号的消息加解密示例代码.
|
||||
*
|
||||
* @copyright Copyright (c) 1998-2014 Tencent Inc.
|
||||
*/
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
package com.foxinmy.weixin4j.util;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 提供基于PKCS7算法的加解密接口</br>
|
||||
* 提供接收和推送给公众平台消息的加解密接口(UTF8编码的字符串).
|
||||
* <ol>
|
||||
* <li>第三方回复加密消息给公众平台</li>
|
||||
* <li>第三方收到公众平台发送的消息,验证消息的安全性,并对消息进行解密。</li>
|
||||
* </ol>
|
||||
* 说明:异常java.security.InvalidKeyException:illegal Key Size的解决方案
|
||||
* <ol>
|
||||
* <li>在官方网站下载JCE无限制权限策略文件(JDK7的下载地址:
|
||||
* http://www.oracle.com/technetwork/java/javase
|
||||
* /downloads/jce-7-download-432124.html</li>
|
||||
* <li>下载后解压,可以看到local_policy.jar和US_export_policy.jar以及readme.txt</li>
|
||||
* <li>如果安装了JRE,将两个jar文件放到%JRE_HOME%\lib\security目录下覆盖原来的文件</li>
|
||||
* <li>如果安装了JDK,将两个jar文件放到%JDK_HOME%\jre\lib\security目录下覆盖原来文件</li>
|
||||
* </ol>
|
||||
*/
|
||||
public class PKCS7Encoder {
|
||||
private final static int BLOCK_SIZE = 32;
|
||||
|
||||
/**
|
||||
* 获得对明文进行补位填充的字节.
|
||||
*
|
||||
* @param count
|
||||
* 需要进行填充补位操作的明文字节个数
|
||||
* @return 补齐用的字节数组
|
||||
*/
|
||||
public static byte[] encode(int count) {
|
||||
// 计算需要填充的位数
|
||||
int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE);
|
||||
if (amountToPad == 0) {
|
||||
amountToPad = BLOCK_SIZE;
|
||||
}
|
||||
// 获得补位所用的字符
|
||||
byte target = (byte) (amountToPad & 0xFF);
|
||||
char padChr = (char) target;
|
||||
StringBuilder tmp = new StringBuilder();
|
||||
for (int index = 0; index < amountToPad; index++) {
|
||||
tmp.append(padChr);
|
||||
}
|
||||
return tmp.toString().getBytes(Consts.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除解密后明文的补位字符
|
||||
*
|
||||
* @param decrypted
|
||||
* 解密后的明文
|
||||
* @return 删除补位字符后的明文
|
||||
*/
|
||||
public static byte[] decode(byte[] decrypted) {
|
||||
int pad = (int) decrypted[decrypted.length - 1];
|
||||
if (pad < 1 || pad > 32) {
|
||||
pad = 0;
|
||||
}
|
||||
return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package com.foxinmy.weixin4j.util;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 随机码工具类
|
||||
*
|
||||
* @className RandomUtil
|
||||
* @author jy
|
||||
* @date 2014年10月22日
|
||||
* @since JDK 1.7
|
||||
* @see
|
||||
*/
|
||||
public class RandomUtil {
|
||||
|
||||
private static final String ALLCHAR = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
private static final String LETTERCHAR = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
private static final String NUMBERCHAR = "0123456789";
|
||||
|
||||
/**
|
||||
* 返回一个定长的随机字符串(包含数字和大小写字母)
|
||||
*
|
||||
* @param length
|
||||
* 随机数的长度
|
||||
* @return
|
||||
*/
|
||||
public static String generateString(int length) {
|
||||
StringBuilder sb = new StringBuilder(length);
|
||||
Random random = new Random();
|
||||
for (int i = 0; i < length; i++) {
|
||||
sb.append(ALLCHAR.charAt(random.nextInt(ALLCHAR.length())));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回一个定长的随机纯数字字符串(只包含数字)
|
||||
*
|
||||
* @param length
|
||||
* 随机数的长度
|
||||
* @return
|
||||
*/
|
||||
public static String generateStringByNumberChar(int length) {
|
||||
StringBuilder sb = new StringBuilder(length);
|
||||
Random random = new Random();
|
||||
for (int i = 0; i < length; i++) {
|
||||
sb.append(NUMBERCHAR.charAt(random.nextInt(NUMBERCHAR.length())));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回一个定长的随机纯字母字符串(只包含大小写字母)
|
||||
*
|
||||
* @param length
|
||||
* 随机数的长度
|
||||
* @return
|
||||
*/
|
||||
public static String generateStringByLetterCharr(int length) {
|
||||
StringBuilder sb = new StringBuilder(length);
|
||||
Random random = new Random();
|
||||
for (int i = 0; i < length; i++) {
|
||||
sb.append(LETTERCHAR.charAt(random.nextInt(LETTERCHAR.length())));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回一个定长的随机纯大写字母字符串(只包含大小写字母)
|
||||
*
|
||||
* @param length
|
||||
* 随机数的长度
|
||||
* @return
|
||||
*/
|
||||
public static String generateLowerString(int length) {
|
||||
return generateStringByLetterCharr(length).toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回一个定长的随机纯小写字母字符串(只包含大小写字母)
|
||||
*
|
||||
* @param length
|
||||
* 随机数的长度
|
||||
* @return
|
||||
*/
|
||||
public static String generateUpperString(int length) {
|
||||
return generateStringByLetterCharr(length).toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* 随机获取UUID字符串(无中划线)
|
||||
*
|
||||
* @return UUID字符串
|
||||
*/
|
||||
public static String getUUID() {
|
||||
String uuid = UUID.randomUUID().toString();
|
||||
return uuid.substring(0, 8) + uuid.substring(9, 13)
|
||||
+ uuid.substring(14, 18) + uuid.substring(19, 23)
|
||||
+ uuid.substring(24);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println(System.nanoTime());
|
||||
System.out.println(System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
# \u516c\u4f17\u53f7\u4fe1\u606f
|
||||
account={"id":"appid","secret":"appsecret",\
|
||||
"token":"\u5f00\u653e\u8005\u7684token",\
|
||||
"encodingAesKey":"\u516c\u4f17\u53f7\u8bbe\u7f6e\u4e86\u52a0\u5bc6\u65b9\u5f0f\u4e14\u4e3a\u300c\u5b89\u5168\u6a21\u5f0f\u300d\u65f6\u9700\u8981\u586b\u5165",\
|
||||
"mchId":"V3.x\u7248\u672c\u4e0b\u7684\u5fae\u4fe1\u5546\u6237\u53f7 \u670d\u52a1\u53f7\u652f\u4ed8\u65f6\u9700\u8981\u586b\u5165",\
|
||||
"version":\u6570\u5b57\u7c7b\u578b(2\u6216\u80053):\u5fae\u4fe1\u652f\u4ed8\u7684\u7248\u672c,\u5927\u6982\u57282014-09-14\u4e4b\u524d\u7533\u8bf7\u5e76\u4e14\u901a\u8fc7\u7684\u516c\u4f17\u53f7\u4e3aV2,\u5728\u8fd9\u4e4b\u540e\u5219\u4e3aV3 \u670d\u52a1\u53f7\u652f\u4ed8\u65f6\u9700\u8981\u586b\u5165,\
|
||||
"partnerId":"V2\u7248\u672c\u4e0b\u7684\u8d22\u4ed8\u901a\u7684\u5546\u6237\u53f7 \u670d\u52a1\u53f7\u652f\u4ed8\u65f6\u9700\u8981\u586b\u5165",\
|
||||
"partnerKey":"V2\u7248\u672c\u4e0b\u7684\u8d22\u4ed8\u901a\u5546\u6237\u6743\u9650\u5bc6\u94a5Key \u670d\u52a1\u53f7\u652f\u4ed8\u65f6\u9700\u8981\u586b\u5165",\
|
||||
"paySignKey":"\u5fae\u4fe1\u652f\u4ed8\u4e2d\u8c03\u7528API\u7684\u5bc6\u94a5 \u670d\u52a1\u53f7\u652f\u4ed8\u65f6\u9700\u8981\u586b\u5165"}
|
||||
|
||||
# \u4f7f\u7528FileTokenHolder\u65f6token\u7684\u5b58\u653e\u8def\u5f84
|
||||
token_path=/tmp/weixin/token
|
||||
# \u4e8c\u7ef4\u7801\u4fdd\u5b58\u8def\u5f84
|
||||
qr_path=/tmp/weixin/qr
|
||||
# \u5a92\u4f53\u6587\u4ef6\u4fdd\u5b58\u8def\u5f84
|
||||
media_path=/tmp/weixin/media
|
||||
# \u5bf9\u8d26\u5355\u4fdd\u5b58\u8def\u5f84
|
||||
bill_path=/tmp/weixin/bill
|
||||
# ca\u8bc1\u4e66\u5b58\u653e\u7684\u5b8c\u6574\u8def\u5f84 (V2\u7248\u672c\u540e\u7f00\u4e3a*.pfx,V3\u7248\u672c\u540e\u7f00\u4e3a*.p12)
|
||||
ca_file=/tmp/weixin/xxxxx.p12
|
||||
# classpath\u8def\u5f84\u4e0b\u53ef\u4ee5\u8fd9\u4e48\u5199
|
||||
# ca_file=classpath:xxxxx.pfx
|
||||
@@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!-- For assistance related to logback-translator or configuration -->
|
||||
<!-- files in general, please contact the logback user mailing list -->
|
||||
<!-- at http://www.qos.ch/mailman/listinfo/logback-user -->
|
||||
<!-- -->
|
||||
<!-- For professional support please see -->
|
||||
<!-- http://www.qos.ch/shop/products/professionalSupport -->
|
||||
<!-- -->
|
||||
<configuration>
|
||||
|
||||
<!-- 控制台输出日志 -->
|
||||
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<charset>UTF-8</charset>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - %msg%n
|
||||
</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 文件输出指定项目日志 -->
|
||||
<appender name="file"
|
||||
class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<!--See http://logback.qos.ch/manual/appenders.html#RollingFileAppender -->
|
||||
<!--and http://logback.qos.ch/manual/appenders.html#TimeBasedRollingPolicy -->
|
||||
<!--for further documentation -->
|
||||
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>/tmp/weixin/log/server/weixin.server.%d{yyyy-MM-dd}.log
|
||||
</fileNamePattern>
|
||||
<maxHistory>30</maxHistory>
|
||||
</rollingPolicy>
|
||||
|
||||
<encoder>
|
||||
<charset>UTF-8</charset>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
|
||||
</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 异步输出指定项目日志 -->
|
||||
<appender name="async" class="ch.qos.logback.classic.AsyncAppender">
|
||||
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
|
||||
<discardingThreshold>0</discardingThreshold>
|
||||
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
|
||||
<queueSize>512</queueSize>
|
||||
<!-- 添加附加的appender,最多只能添加一个 -->
|
||||
<appender-ref ref="file" />
|
||||
</appender>
|
||||
|
||||
<logger name="org.apache" level="INFO">
|
||||
<appender-ref ref="stdout" />
|
||||
</logger>
|
||||
|
||||
<logger name="org.springframework" level="INFO">
|
||||
<appender-ref ref="stdout" />
|
||||
</logger>
|
||||
|
||||
<logger name="com.foxinmy.weixin4j" level="INFO">
|
||||
<appender-ref ref="async" />
|
||||
</logger>
|
||||
|
||||
</configuration>
|
||||
@@ -0,0 +1,4 @@
|
||||
# netty\u76d1\u542c\u7aef\u53e3
|
||||
port=8080
|
||||
# \u5de5\u4f5c\u8fdb\u7a0b\u6570
|
||||
workerThreads=20
|
||||
@@ -0,0 +1,5 @@
|
||||
# \u6d4b\u8bd5\u4e4b\u7528 \u6b63\u5f0f\u73af\u5883\u4e0bcopy\u4e00\u4efd\u5230classpath
|
||||
# \u516c\u4f17\u53f7\u4fe1\u606f
|
||||
account={"id":"wx4ab8f8de58159a57","secret":"1d4eb0f4bf556aaed539f30ed05ca795",\
|
||||
"token":"\u5f00\u653e\u8005\u7684token",\
|
||||
"encodingAesKey":"\u516c\u4f17\u53f7\u8bbe\u7f6e\u4e86\u52a0\u5bc6\u65b9\u5f0f\u4e14\u4e3a\u300c\u5b89\u5168\u6a21\u5f0f\u300d\u65f6\u9700\u8981\u586b\u5165"}
|
||||
@@ -0,0 +1,142 @@
|
||||
ulimit -n 110000
|
||||
#JDK home
|
||||
JAVA_HOME="/usr/local/java/"
|
||||
|
||||
#executing user
|
||||
RUNNING_USER=root
|
||||
|
||||
#Run home
|
||||
APP_HOME="/usr/local/weixin/weixin-mp-server"
|
||||
|
||||
#main class
|
||||
APP_MAINCLASS=com.foxinmy.weixin4j.startup.WeixinServerBootstrap
|
||||
|
||||
#classpath
|
||||
CLASSPATH=$APP_HOME/classes
|
||||
for i in "$APP_HOME"/lib/*.jar; do
|
||||
CLASSPATH="$CLASSPATH":"$i"
|
||||
done
|
||||
|
||||
CLASSPATH="$CLASSPATH":"$APP_HOME"/conf
|
||||
|
||||
#jvm options
|
||||
JAVA_OPTS="-Xms256m -Xmx512m -Djava.awt.headless=true -XX:MaxPermSize=128m -server -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=85 -XX:+DisableExplicitGC -Xnoclassgc -Xverify:none"
|
||||
|
||||
#psid
|
||||
psid=0
|
||||
|
||||
checkpid() {
|
||||
javaps=`$JAVA_HOME/bin/jps -l | grep $APP_MAINCLASS`
|
||||
|
||||
if [ -n "$javaps" ]; then
|
||||
psid=`echo $javaps | awk '{print $1}'`
|
||||
else
|
||||
psid=0
|
||||
fi
|
||||
}
|
||||
|
||||
###################################
|
||||
#startup
|
||||
###################################
|
||||
start() {
|
||||
checkpid
|
||||
|
||||
if [ $psid -ne 0 ]; then
|
||||
echo "====================================================="
|
||||
echo "warn: $APP_MAINCLASS already started! (pid=$psid)"
|
||||
echo "====================================================="
|
||||
else
|
||||
echo -n "Starting $APP_MAINCLASS ..."
|
||||
# JAVA_CMD="nohup $JAVA_HOME/bin/java $JAVA_OPTS -classpath $CLASSPATH $APP_MAINCLASS >/dev/null 2>&1 &"
|
||||
JAVA_CMD="$JAVA_HOME/bin/java $JAVA_OPTS -classpath $CLASSPATH $APP_MAINCLASS &"
|
||||
su - $RUNNING_USER -c "$JAVA_CMD"
|
||||
checkpid
|
||||
if [ $psid -ne 0 ]; then
|
||||
echo "(pid=$psid) [OK]"
|
||||
else
|
||||
echo "[Failed]"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
###################################
|
||||
#stop
|
||||
###################################
|
||||
stop() {
|
||||
checkpid
|
||||
|
||||
if [ $psid -ne 0 ]; then
|
||||
echo -n "Stopping $APP_MAINCLASS ...(pid=$psid) "
|
||||
su - $RUNNING_USER -c "kill -9 $psid"
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "[OK]"
|
||||
else
|
||||
echo "[Failed]"
|
||||
fi
|
||||
|
||||
checkpid
|
||||
if [ $psid -ne 0 ]; then
|
||||
stop
|
||||
fi
|
||||
else
|
||||
echo "====================================================="
|
||||
echo "warn: $APP_MAINCLASS is not running"
|
||||
echo "====================================================="
|
||||
fi
|
||||
}
|
||||
|
||||
###################################
|
||||
#status
|
||||
###################################
|
||||
status() {
|
||||
checkpid
|
||||
|
||||
if [ $psid -ne 0 ]; then
|
||||
echo "$APP_MAINCLASS is running! (pid=$psid)"
|
||||
else
|
||||
echo "$APP_MAINCLASS is not running"
|
||||
fi
|
||||
}
|
||||
|
||||
###################################
|
||||
#info
|
||||
###################################
|
||||
info() {
|
||||
echo "System Information:"
|
||||
echo "****************************"
|
||||
echo `head -n 1 /etc/issue`
|
||||
echo `uname -a`
|
||||
echo
|
||||
echo "JAVA_HOME=$JAVA_HOME"
|
||||
echo `$JAVA_HOME/bin/java -version`
|
||||
echo
|
||||
echo "APP_HOME=$APP_HOME"
|
||||
echo "APP_MAINCLASS=$APP_MAINCLASS"
|
||||
echo "****************************"
|
||||
}
|
||||
|
||||
###################################
|
||||
#access only 1 argument:{start|stop|restart|status|info}
|
||||
###################################
|
||||
case "$1" in
|
||||
'start')
|
||||
start
|
||||
;;
|
||||
'stop')
|
||||
stop
|
||||
;;
|
||||
'restart')
|
||||
stop
|
||||
start
|
||||
;;
|
||||
'status')
|
||||
status
|
||||
;;
|
||||
'info')
|
||||
info
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {start|stop|restart|status|info}"
|
||||
exit 1
|
||||
esac
|
||||
exit 0
|
||||
Reference in New Issue
Block a user