大重构:weixin4j精简为weixin4j-base、weixin4j-mp、weixin4j-qy、weixin4j-server四个子工程

This commit is contained in:
jinyu
2015-04-27 21:02:51 +08:00
parent 69c9bdffa4
commit 784d1f33ea
221 changed files with 24504 additions and 0 deletions
+36
View File
@@ -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);
}*/
}
}
@@ -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"}
+142
View File
@@ -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