init weixin4j-server-with-springmvc

This commit is contained in:
jinyu 2017-03-29 13:05:14 +08:00
parent c76d07c8d5
commit 291c263a54
95 changed files with 7878 additions and 0 deletions

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry including="**/*.java" kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

1
weixin4j-serverX/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target/

23
weixin4j-serverX/.project Normal file
View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>weixin4j-serverX</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View File

@ -0,0 +1,4 @@
eclipse.preferences.version=1
encoding//src/main/java=UTF-8
encoding//src/test/java=UTF-8
encoding/<project>=UTF-8

View File

@ -0,0 +1,5 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
org.eclipse.jdt.core.compiler.compliance=1.6
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.source=1.6

View File

@ -0,0 +1,4 @@
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1

48
weixin4j-serverX/pom.xml Normal file
View File

@ -0,0 +1,48 @@
<?xml version="1.0"?>
<project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.foxinmy</groupId>
<artifactId>weixin4j</artifactId>
<version>1.7.4</version>
</parent>
<artifactId>weixin4j-serverX</artifactId>
<version>0.0.1</version>
<name>weixin4j-serverX</name>
<url>https://github.com/foxinmy/weixin4j/tree/master/weixin4j-serverX</url>
<description>微信消息接入服务(spring mvc实现)</description>
<properties>
<spring.version>4.2.0.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<scope>provided</scope>
<version>2.1</version>
</dependency>
</dependencies>
<developers>
<developer>
<email>174975857@qq.com</email>
<name>Yz</name>
</developer>
</developers>
</project>

View File

@ -0,0 +1,19 @@
package com.zone.weixin4j.annotation;
import org.springframework.stereotype.Component;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
/**
* Created by Yz on 2017/3/13.
* WxMessageHandler
* WxMessageHandler 注解声明处理消息内容的类
*/
@Target({java.lang.annotation.ElementType.TYPE})
@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface WxMessageHandler {
}

View File

@ -0,0 +1,19 @@
package com.zone.weixin4j.annotation;
import org.springframework.stereotype.Component;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
/**
* Created by Yz on 2017/3/13.
* WxMessageInterceptor
* WxMessageInterceptor 注解声明拦截消息内容的类
*/
@Target({java.lang.annotation.ElementType.TYPE})
@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface WxMessageInterceptor {
}

View File

@ -0,0 +1,888 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zone.weixin4j.base64;
import com.zone.weixin4j.util.ServerToolkits;
import java.math.BigInteger;
/**
* <p>
* <font color="red">reference of apache pivot</font>
* </p>
*
* Provides Base64 encoding and decoding as defined by <a
* href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>.
*
* <p>
* This class implements section <cite>6.8. Base64
* Content-Transfer-Encoding</cite> from RFC 2045 <cite>Multipurpose Internet
* Mail Extensions (MIME) Part One: Format of Internet Message Bodies</cite> by
* Freed and Borenstein.
* </p>
* <p>
* The class can be parameterized in the following manner with various
* constructors:
* <ul>
* <li>URL-safe mode: Default off.</li>
* <li>Line length: Default 76. Line length that aren't multiples of 4 will
* still essentially end up being multiples of 4 in the encoded data.
* <li>Line separator: Default is CRLF ("\r\n")</li>
* </ul>
* </p>
* <p>
* Since this class operates directly on byte streams, and not character
* streams, it is hard-coded to only encode/decode character encodings which are
* compatible with the lower 127 ASCII chart (ISO-8859-1, Windows-1252, UTF-8,
* etc).
* </p>
* <p>
* This class is thread-safe.
* </p>
*
* @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>
* @since 1.0
* @version $Id: Base64.java 1447577 2013-02-19 02:45:18Z julius $
*/
public class Base64 extends BaseNCodec {
/**
* BASE32 characters are 6 bits in length. They are formed by taking a block
* of 3 octets to form a 24-bit string, which is converted into 4 BASE64
* characters.
*/
private static final int BITS_PER_ENCODED_BYTE = 6;
private static final int BYTES_PER_UNENCODED_BLOCK = 3;
private static final int BYTES_PER_ENCODED_BLOCK = 4;
/**
* Chunk separator per RFC 2045 section 2.1.
*
* <p>
* N.B. The next major release may break compatibility and make this field
* private.
* </p>
*
* @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section
* 2.1</a>
*/
static final byte[] CHUNK_SEPARATOR = { '\r', '\n' };
/**
* This array is a lookup table that translates 6-bit positive integer index
* values into their "Base64 Alphabet" equivalents as specified in Table 1
* of RFC 2045.
*
* Thanks to "commons" project in ws.apache.org for this code.
* http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
*/
private static final byte[] STANDARD_ENCODE_TABLE = { 'A', 'B', 'C', 'D',
'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q',
'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3',
'4', '5', '6', '7', '8', '9', '+', '/' };
/**
* This is a copy of the STANDARD_ENCODE_TABLE above, but with + and /
* changed to - and _ to make the encoded Base64 results more URL-SAFE. This
* table is only used when the Base64's mode is set to URL-SAFE.
*/
private static final byte[] URL_SAFE_ENCODE_TABLE = { 'A', 'B', 'C', 'D',
'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q',
'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3',
'4', '5', '6', '7', '8', '9', '-', '_' };
/**
* This array is a lookup table that translates Unicode characters drawn
* from the "Base64 Alphabet" (as specified in Table 1 of RFC 2045) into
* their 6-bit positive integer equivalents. Characters that are not in the
* Base64 alphabet but fall within the bounds of the array are translated to
* -1.
*
* Note: '+' and '-' both decode to 62. '/' and '_' both decode to 63. This
* means decoder seamlessly handles both URL_SAFE and STANDARD base64. (The
* encoder, on the other hand, needs to know ahead of time what to emit).
*
* Thanks to "commons" project in ws.apache.org for this code.
* http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
*/
private static final byte[] DECODE_TABLE = { -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, 62, -1, 62, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
-1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1,
-1, 63, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 };
/**
* Base64 uses 6-bit fields.
*/
/** Mask used to extract 6 bits, used when encoding */
private static final int MASK_6BITS = 0x3f;
// The static final fields above are used for the original static byte[]
// methods on Base64.
// The private member fields below are used with the new streaming approach,
// which requires
// some state be preserved between calls of encode() and decode().
/**
* Encode table to use: either STANDARD or URL_SAFE. Note: the DECODE_TABLE
* above remains static because it is able to decode both STANDARD and
* URL_SAFE streams, but the encodeTable must be a member variable so we can
* switch between the two modes.
*/
private final byte[] encodeTable;
// Only one decode table currently; keep for consistency with Base32 code
private final byte[] decodeTable = DECODE_TABLE;
/**
* Line separator for encoding. Not used when decoding. Only used if
* lineLength > 0.
*/
private final byte[] lineSeparator;
/**
* Convenience variable to help us determine when our buffer is going to run
* out of room and needs resizing.
* <code>decodeSize = 3 + lineSeparator.length;</code>
*/
private final int decodeSize;
/**
* Convenience variable to help us determine when our buffer is going to run
* out of room and needs resizing.
* <code>encodeSize = 4 + lineSeparator.length;</code>
*/
private final int encodeSize;
/**
* Creates a Base64 codec used for decoding (all modes) and encoding in
* URL-unsafe mode.
* <p>
* When encoding the line length is 0 (no chunking), and the encoding table
* is STANDARD_ENCODE_TABLE.
* </p>
*
* <p>
* When decoding all variants are supported.
* </p>
*/
public Base64() {
this(0);
}
/**
* Creates a Base64 codec used for decoding (all modes) and encoding in the
* given URL-safe mode.
* <p>
* When encoding the line length is 76, the line separator is CRLF, and the
* encoding table is STANDARD_ENCODE_TABLE.
* </p>
*
* <p>
* When decoding all variants are supported.
* </p>
*
* @param urlSafe
* if {@code true}, URL-safe encoding is used. In most cases this
* should be set to {@code false}.
* @since 1.4
*/
public Base64(final boolean urlSafe) {
this(MIME_CHUNK_SIZE, CHUNK_SEPARATOR, urlSafe);
}
/**
* Creates a Base64 codec used for decoding (all modes) and encoding in
* URL-unsafe mode.
* <p>
* When encoding the line length is given in the constructor, the line
* separator is CRLF, and the encoding table is STANDARD_ENCODE_TABLE.
* </p>
* <p>
* Line lengths that aren't multiples of 4 will still essentially end up
* being multiples of 4 in the encoded data.
* </p>
* <p>
* When decoding all variants are supported.
* </p>
*
* @param lineLength
* Each line of encoded data will be at most of the given length
* (rounded down to nearest multiple of 4). If lineLength <= 0,
* then the output will not be divided into lines (chunks).
* Ignored when decoding.
* @since 1.4
*/
public Base64(final int lineLength) {
this(lineLength, CHUNK_SEPARATOR);
}
/**
* Creates a Base64 codec used for decoding (all modes) and encoding in
* URL-unsafe mode.
* <p>
* When encoding the line length and line separator are given in the
* constructor, and the encoding table is STANDARD_ENCODE_TABLE.
* </p>
* <p>
* Line lengths that aren't multiples of 4 will still essentially end up
* being multiples of 4 in the encoded data.
* </p>
* <p>
* When decoding all variants are supported.
* </p>
*
* @param lineLength
* Each line of encoded data will be at most of the given length
* (rounded down to nearest multiple of 4). If lineLength <= 0,
* then the output will not be divided into lines (chunks).
* Ignored when decoding.
* @param lineSeparator
* Each line of encoded data will end with this sequence of
* bytes.
* @throws IllegalArgumentException
* Thrown when the provided lineSeparator included some base64
* characters.
* @since 1.4
*/
public Base64(final int lineLength, final byte[] lineSeparator) {
this(lineLength, lineSeparator, false);
}
/**
* Creates a Base64 codec used for decoding (all modes) and encoding in
* URL-unsafe mode.
* <p>
* When encoding the line length and line separator are given in the
* constructor, and the encoding table is STANDARD_ENCODE_TABLE.
* </p>
* <p>
* Line lengths that aren't multiples of 4 will still essentially end up
* being multiples of 4 in the encoded data.
* </p>
* <p>
* When decoding all variants are supported.
* </p>
*
* @param lineLength
* Each line of encoded data will be at most of the given length
* (rounded down to nearest multiple of 4). If lineLength <= 0,
* then the output will not be divided into lines (chunks).
* Ignored when decoding.
* @param lineSeparator
* Each line of encoded data will end with this sequence of
* bytes.
* @param urlSafe
* Instead of emitting '+' and '/' we emit '-' and '_'
* respectively. urlSafe is only applied to encode operations.
* Decoding seamlessly handles both modes. <b>Note: no padding is
* added when using the URL-safe alphabet.</b>
* @throws IllegalArgumentException
* The provided lineSeparator included some base64 characters.
* That's not going to work!
* @since 1.4
*/
public Base64(final int lineLength, final byte[] lineSeparator,
final boolean urlSafe) {
super(BYTES_PER_UNENCODED_BLOCK, BYTES_PER_ENCODED_BLOCK, lineLength,
lineSeparator == null ? 0 : lineSeparator.length);
// TODO could be simplified if there is no requirement to reject invalid
// line sep when length <=0
// @see test case Base64Test.testConstructors()
if (lineSeparator != null) {
if (containsAlphabetOrPad(lineSeparator)) {
final String sep = ServerToolkits.newStringUtf8(lineSeparator);
throw new IllegalArgumentException(
"lineSeparator must not contain base64 characters: ["
+ sep + "]");
}
if (lineLength > 0) { // null line-sep forces no chunking rather
// than throwing IAE
this.encodeSize = BYTES_PER_ENCODED_BLOCK
+ lineSeparator.length;
this.lineSeparator = new byte[lineSeparator.length];
System.arraycopy(lineSeparator, 0, this.lineSeparator, 0,
lineSeparator.length);
} else {
this.encodeSize = BYTES_PER_ENCODED_BLOCK;
this.lineSeparator = null;
}
} else {
this.encodeSize = BYTES_PER_ENCODED_BLOCK;
this.lineSeparator = null;
}
this.decodeSize = this.encodeSize - 1;
this.encodeTable = urlSafe ? URL_SAFE_ENCODE_TABLE
: STANDARD_ENCODE_TABLE;
}
/**
* Returns our current encode mode. True if we're URL-SAFE, false otherwise.
*
* @return true if we're in URL-SAFE mode, false otherwise.
* @since 1.4
*/
public boolean isUrlSafe() {
return this.encodeTable == URL_SAFE_ENCODE_TABLE;
}
/**
* <p>
* Encodes all of the provided data, starting at inPos, for inAvail bytes.
* Must be called at least twice: once with the data to encode, and once
* with inAvail set to "-1" to alert encoder that EOF has been reached, to
* flush last remaining bytes (if not multiple of 3).
* </p>
* <p>
* <b>Note: no padding is added when encoding using the URL-safe
* alphabet.</b>
* </p>
* <p>
* Thanks to "commons" project in ws.apache.org for the bitwise operations,
* and general approach.
* http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
* </p>
*
* @param in
* byte[] array of binary data to base64 encode.
* @param inPos
* Position to start reading data from.
* @param inAvail
* Amount of bytes available from input for encoding.
* @param context
* the context to be used
*/
@Override
void encode(final byte[] in, int inPos, final int inAvail,
final Context context) {
if (context.eof) {
return;
}
// inAvail < 0 is how we're informed of EOF in the underlying data we're
// encoding.
if (inAvail < 0) {
context.eof = true;
if (0 == context.modulus && lineLength == 0) {
return; // no leftovers to process and not using chunking
}
final byte[] buffer = ensureBufferSize(encodeSize, context);
final int savedPos = context.pos;
switch (context.modulus) { // 0-2
case 0: // nothing to do here
break;
case 1: // 8 bits = 6 + 2
// top 6 bits:
buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 2)
& MASK_6BITS];
// remaining 2:
buffer[context.pos++] = encodeTable[(context.ibitWorkArea << 4)
& MASK_6BITS];
// URL-SAFE skips the padding to further reduce size.
if (encodeTable == STANDARD_ENCODE_TABLE) {
buffer[context.pos++] = PAD;
buffer[context.pos++] = PAD;
}
break;
case 2: // 16 bits = 6 + 6 + 4
buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 10)
& MASK_6BITS];
buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 4)
& MASK_6BITS];
buffer[context.pos++] = encodeTable[(context.ibitWorkArea << 2)
& MASK_6BITS];
// URL-SAFE skips the padding to further reduce size.
if (encodeTable == STANDARD_ENCODE_TABLE) {
buffer[context.pos++] = PAD;
}
break;
default:
throw new IllegalStateException("Impossible modulus "
+ context.modulus);
}
context.currentLinePos += context.pos - savedPos; // keep track of
// current line
// position
// if currentPos == 0 we are at the start of a line, so don't add
// CRLF
if (lineLength > 0 && context.currentLinePos > 0) {
System.arraycopy(lineSeparator, 0, buffer, context.pos,
lineSeparator.length);
context.pos += lineSeparator.length;
}
} else {
for (int i = 0; i < inAvail; i++) {
final byte[] buffer = ensureBufferSize(encodeSize, context);
context.modulus = (context.modulus + 1)
% BYTES_PER_UNENCODED_BLOCK;
int b = in[inPos++];
if (b < 0) {
b += 256;
}
context.ibitWorkArea = (context.ibitWorkArea << 8) + b; // BITS_PER_BYTE
if (0 == context.modulus) { // 3 bytes = 24 bits = 4 * 6 bits to
// extract
buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 18)
& MASK_6BITS];
buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 12)
& MASK_6BITS];
buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 6)
& MASK_6BITS];
buffer[context.pos++] = encodeTable[context.ibitWorkArea
& MASK_6BITS];
context.currentLinePos += BYTES_PER_ENCODED_BLOCK;
if (lineLength > 0 && lineLength <= context.currentLinePos) {
System.arraycopy(lineSeparator, 0, buffer, context.pos,
lineSeparator.length);
context.pos += lineSeparator.length;
context.currentLinePos = 0;
}
}
}
}
}
/**
* <p>
* Decodes all of the provided data, starting at inPos, for inAvail bytes.
* Should be called at least twice: once with the data to decode, and once
* with inAvail set to "-1" to alert decoder that EOF has been reached. The
* "-1" call is not necessary when decoding, but it doesn't hurt, either.
* </p>
* <p>
* Ignores all non-base64 characters. This is how chunked (e.g. 76
* character) data is handled, since CR and LF are silently ignored, but has
* implications for other bytes, too. This method subscribes to the
* garbage-in, garbage-out philosophy: it will not check the provided data
* for validity.
* </p>
* <p>
* Thanks to "commons" project in ws.apache.org for the bitwise operations,
* and general approach.
* http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
* </p>
*
* @param in
* byte[] array of ascii data to base64 decode.
* @param inPos
* Position to start reading data from.
* @param inAvail
* Amount of bytes available from input for encoding.
* @param context
* the context to be used
*/
@Override
void decode(final byte[] in, int inPos, final int inAvail,
final Context context) {
if (context.eof) {
return;
}
if (inAvail < 0) {
context.eof = true;
}
for (int i = 0; i < inAvail; i++) {
final byte[] buffer = ensureBufferSize(decodeSize, context);
final byte b = in[inPos++];
if (b == PAD) {
// We're done.
context.eof = true;
break;
} else {
if (b >= 0 && b < DECODE_TABLE.length) {
final int result = DECODE_TABLE[b];
if (result >= 0) {
context.modulus = (context.modulus + 1)
% BYTES_PER_ENCODED_BLOCK;
context.ibitWorkArea = (context.ibitWorkArea << BITS_PER_ENCODED_BYTE)
+ result;
if (context.modulus == 0) {
buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 16) & MASK_8BITS);
buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 8) & MASK_8BITS);
buffer[context.pos++] = (byte) (context.ibitWorkArea & MASK_8BITS);
}
}
}
}
}
// Two forms of EOF as far as base64 decoder is concerned: actual
// EOF (-1) and first time '=' character is encountered in stream.
// This approach makes the '=' padding characters completely optional.
if (context.eof && context.modulus != 0) {
final byte[] buffer = ensureBufferSize(decodeSize, context);
// We have some spare bits remaining
// Output all whole multiples of 8 bits and ignore the rest
switch (context.modulus) {
// case 0 : // impossible, as excluded above
case 1: // 6 bits - ignore entirely
// TODO not currently tested; perhaps it is impossible?
break;
case 2: // 12 bits = 8 + 4
context.ibitWorkArea = context.ibitWorkArea >> 4; // dump the
// extra 4
// bits
buffer[context.pos++] = (byte) ((context.ibitWorkArea) & MASK_8BITS);
break;
case 3: // 18 bits = 8 + 8 + 2
context.ibitWorkArea = context.ibitWorkArea >> 2; // dump 2 bits
buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 8) & MASK_8BITS);
buffer[context.pos++] = (byte) ((context.ibitWorkArea) & MASK_8BITS);
break;
default:
throw new IllegalStateException("Impossible modulus "
+ context.modulus);
}
}
}
/**
* Tests a given byte array to see if it contains only valid characters
* within the Base64 alphabet. Currently the method treats whitespace as
* valid.
*
* @param arrayOctet
* byte array to test
* @return {@code true} if all bytes are valid characters in the Base64
* alphabet or if the byte array is empty; {@code false}, otherwise
* @deprecated 1.5 Use {@link #isBase64(byte[])}, will be removed in 2.0.
*/
@Deprecated
public static boolean isArrayByteBase64(final byte[] arrayOctet) {
return isBase64(arrayOctet);
}
/**
* Returns whether or not the <code>octet</code> is in the base 64 alphabet.
*
* @param octet
* The value to test
* @return {@code true} if the value is defined in the the base 64 alphabet,
* {@code false} otherwise.
* @since 1.4
*/
public static boolean isBase64(final byte octet) {
return octet == PAD_DEFAULT
|| (octet >= 0 && octet < DECODE_TABLE.length && DECODE_TABLE[octet] != -1);
}
/**
* Tests a given String to see if it contains only valid characters within
* the Base64 alphabet. Currently the method treats whitespace as valid.
*
* @param base64
* String to test
* @return {@code true} if all characters in the String are valid characters
* in the Base64 alphabet or if the String is empty; {@code false},
* otherwise
* @since 1.5
*/
public static boolean isBase64(final String base64) {
return isBase64(ServerToolkits.getBytesUtf8(base64));
}
/**
* Tests a given byte array to see if it contains only valid characters
* within the Base64 alphabet. Currently the method treats whitespace as
* valid.
*
* @param arrayOctet
* byte array to test
* @return {@code true} if all bytes are valid characters in the Base64
* alphabet or if the byte array is empty; {@code false}, otherwise
* @since 1.5
*/
public static boolean isBase64(final byte[] arrayOctet) {
for (int i = 0; i < arrayOctet.length; i++) {
if (!isBase64(arrayOctet[i]) && !isWhiteSpace(arrayOctet[i])) {
return false;
}
}
return true;
}
/**
* Encodes binary data using the base64 algorithm but does not chunk the
* output.
*
* @param binaryData
* binary data to encode
* @return byte[] containing Base64 characters in their UTF-8
* representation.
*/
public static byte[] encodeBase64(final byte[] binaryData) {
return encodeBase64(binaryData, false);
}
/**
* Encodes binary data using the base64 algorithm but does not chunk the
* output.
*
* NOTE: We changed the behaviour of this method from multi-line chunking
* (commons-codec-1.4) to single-line non-chunking (commons-codec-1.5).
*
* @param binaryData
* binary data to encode
* @return String containing Base64 characters.
* @since 1.4 (NOTE: 1.4 chunked the output, whereas 1.5 does not).
*/
public static String encodeBase64String(final byte[] binaryData) {
return ServerToolkits.newStringUtf8(encodeBase64(binaryData, false));
}
/**
* Encodes binary data using a URL-safe variation of the base64 algorithm
* but does not chunk the output. The url-safe variation emits - and _
* instead of + and / characters. <b>Note: no padding is added.</b>
*
* @param binaryData
* binary data to encode
* @return byte[] containing Base64 characters in their UTF-8
* representation.
* @since 1.4
*/
public static byte[] encodeBase64URLSafe(final byte[] binaryData) {
return encodeBase64(binaryData, false, true);
}
/**
* Encodes binary data using a URL-safe variation of the base64 algorithm
* but does not chunk the output. The url-safe variation emits - and _
* instead of + and / characters. <b>Note: no padding is added.</b>
*
* @param binaryData
* binary data to encode
* @return String containing Base64 characters
* @since 1.4
*/
public static String encodeBase64URLSafeString(final byte[] binaryData) {
return ServerToolkits.newStringUtf8(encodeBase64(binaryData, false, true));
}
/**
* Encodes binary data using the base64 algorithm and chunks the encoded
* output into 76 character blocks
*
* @param binaryData
* binary data to encode
* @return Base64 characters chunked in 76 character blocks
*/
public static byte[] encodeBase64Chunked(final byte[] binaryData) {
return encodeBase64(binaryData, true);
}
/**
* Encodes binary data using the base64 algorithm, optionally chunking the
* output into 76 character blocks.
*
* @param binaryData
* Array containing binary data to encode.
* @param isChunked
* if {@code true} this encoder will chunk the base64 output into
* 76 character blocks
* @return Base64-encoded data.
* @throws IllegalArgumentException
* Thrown when the input array needs an output array bigger than
* {@link Integer#MAX_VALUE}
*/
public static byte[] encodeBase64(final byte[] binaryData,
final boolean isChunked) {
return encodeBase64(binaryData, isChunked, false);
}
/**
* Encodes binary data using the base64 algorithm, optionally chunking the
* output into 76 character blocks.
*
* @param binaryData
* Array containing binary data to encode.
* @param isChunked
* if {@code true} this encoder will chunk the base64 output into
* 76 character blocks
* @param urlSafe
* if {@code true} this encoder will emit - and _ instead of the
* usual + and / characters. <b>Note: no padding is added when
* encoding using the URL-safe alphabet.</b>
* @return Base64-encoded data.
* @throws IllegalArgumentException
* Thrown when the input array needs an output array bigger than
* {@link Integer#MAX_VALUE}
* @since 1.4
*/
public static byte[] encodeBase64(final byte[] binaryData,
final boolean isChunked, final boolean urlSafe) {
return encodeBase64(binaryData, isChunked, urlSafe, Integer.MAX_VALUE);
}
/**
* Encodes binary data using the base64 algorithm, optionally chunking the
* output into 76 character blocks.
*
* @param binaryData
* Array containing binary data to encode.
* @param isChunked
* if {@code true} this encoder will chunk the base64 output into
* 76 character blocks
* @param urlSafe
* if {@code true} this encoder will emit - and _ instead of the
* usual + and / characters. <b>Note: no padding is added when
* encoding using the URL-safe alphabet.</b>
* @param maxResultSize
* The maximum result size to accept.
* @return Base64-encoded data.
* @throws IllegalArgumentException
* Thrown when the input array needs an output array bigger than
* maxResultSize
* @since 1.4
*/
public static byte[] encodeBase64(final byte[] binaryData,
final boolean isChunked, final boolean urlSafe,
final int maxResultSize) {
if (binaryData == null || binaryData.length == 0) {
return binaryData;
}
// Create this so can use the super-class method
// Also ensures that the same roundings are performed by the ctor and
// the code
final Base64 b64 = isChunked ? new Base64(urlSafe) : new Base64(0,
CHUNK_SEPARATOR, urlSafe);
final long len = b64.getEncodedLength(binaryData);
if (len > maxResultSize) {
throw new IllegalArgumentException(
"Input array too big, the output array would be bigger ("
+ len + ") than the specified maximum size of "
+ maxResultSize);
}
return b64.encode(binaryData);
}
/**
* Decodes a Base64 String into octets
*
* @param base64String
* String containing Base64 data
* @return Array containing decoded data.
* @since 1.4
*/
public static byte[] decodeBase64(final String base64String) {
return new Base64().decode(base64String);
}
/**
* Decodes Base64 data into octets
*
* @param base64Data
* Byte array containing Base64 data
* @return Array containing decoded data.
*/
public static byte[] decodeBase64(final byte[] base64Data) {
return new Base64().decode(base64Data);
}
// Implementation of the Encoder Interface
// Implementation of integer encoding used for crypto
/**
* Decodes a byte64-encoded integer according to crypto standards such as
* W3C's XML-Signature
*
* @param pArray
* a byte array containing base64 character data
* @return A BigInteger
* @since 1.4
*/
public static BigInteger decodeInteger(final byte[] pArray) {
return new BigInteger(1, decodeBase64(pArray));
}
/**
* Encodes to a byte64-encoded integer according to crypto standards such as
* W3C's XML-Signature
*
* @param bigInt
* a BigInteger
* @return A byte array containing base64 character data
* @throws NullPointerException
* if null is passed in
* @since 1.4
*/
public static byte[] encodeInteger(final BigInteger bigInt) {
if (bigInt == null) {
throw new NullPointerException(
"encodeInteger called with null parameter");
}
return encodeBase64(toIntegerBytes(bigInt), false);
}
/**
* Returns a byte-array representation of a <code>BigInteger</code> without
* sign bit.
*
* @param bigInt
* <code>BigInteger</code> to be converted
* @return a byte array representation of the BigInteger parameter
*/
static byte[] toIntegerBytes(final BigInteger bigInt) {
int bitlen = bigInt.bitLength();
// round bitlen
bitlen = ((bitlen + 7) >> 3) << 3;
final byte[] bigBytes = bigInt.toByteArray();
if (((bigInt.bitLength() % 8) != 0)
&& (((bigInt.bitLength() / 8) + 1) == (bitlen / 8))) {
return bigBytes;
}
// set up params for copying everything but sign bit
int startSrc = 0;
int len = bigBytes.length;
// if bigInt is exactly byte-aligned, just skip signbit in copy
if ((bigInt.bitLength() % 8) == 0) {
startSrc = 1;
len--;
}
final int startDst = bitlen / 8 - len; // to pad w/ nulls as per spec
final byte[] resizedBytes = new byte[bitlen / 8];
System.arraycopy(bigBytes, startSrc, resizedBytes, startDst, len);
return resizedBytes;
}
/**
* Returns whether or not the <code>octet</code> is in the Base64 alphabet.
*
* @param octet
* The value to test
* @return {@code true} if the value is defined in the the Base64 alphabet
* {@code false} otherwise.
*/
@Override
protected boolean isInAlphabet(final byte octet) {
return octet >= 0 && octet < decodeTable.length
&& decodeTable[octet] != -1;
}
}

View File

@ -0,0 +1,521 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zone.weixin4j.base64;
import com.zone.weixin4j.util.ServerToolkits;
import java.util.Arrays;
/**
* <p>
* <font color="red">reference of apache pivot</font>
* </p>
*
* Abstract superclass for Base-N encoders and decoders.
*
* <p>
* This class is thread-safe.
* </p>
*
* @version $Id: BaseNCodec.java 1465182 2013-04-06 04:03:12Z ggregory $
*/
public abstract class BaseNCodec {
/**
* Holds thread context so classes can be thread-safe.
*
* This class is not itself thread-safe; each thread must allocate its own
* copy.
*
* @since 1.7
*/
static class Context {
/**
* Place holder for the bytes we're dealing with for our based logic.
* Bitwise operations store and extract the encoding or decoding from
* this variable.
*/
int ibitWorkArea;
/**
* Place holder for the bytes we're dealing with for our based logic.
* Bitwise operations store and extract the encoding or decoding from
* this variable.
*/
long lbitWorkArea;
/**
* Buffer for streaming.
*/
byte[] buffer;
/**
* Position where next character should be written in the buffer.
*/
int pos;
/**
* Position where next character should be read from the buffer.
*/
int readPos;
/**
* Boolean flag to indicate the EOF has been reached. Once EOF has been
* reached, this object becomes useless, and must be thrown away.
*/
boolean eof;
/**
* Variable tracks how many characters have been written to the current
* line. Only used when encoding. We use it to make sure each encoded
* line never goes beyond lineLength (if lineLength > 0).
*/
int currentLinePos;
/**
* Writes to the buffer only occur after every 3/5 reads when encoding,
* and every 4/8 reads when decoding. This variable helps track that.
*/
int modulus;
Context() {
}
/**
* Returns a String useful for debugging (especially within a debugger.)
*
* @return a String useful for debugging.
*/
@SuppressWarnings("boxing")
// OK to ignore boxing here
@Override
public String toString() {
return String.format(
"%s[buffer=%s, currentLinePos=%s, eof=%s, ibitWorkArea=%s, lbitWorkArea=%s, "
+ "modulus=%s, pos=%s, readPos=%s]", this
.getClass().getSimpleName(), Arrays
.toString(buffer), currentLinePos, eof,
ibitWorkArea, lbitWorkArea, modulus, pos, readPos);
}
}
/**
* EOF
*
* @since 1.7
*/
static final int EOF = -1;
/**
* MIME chunk size per RFC 2045 section 6.8.
*
* <p>
* The {@value} character limit does not count the trailing CRLF, but counts
* all other characters, including any equal signs.
* </p>
*
* @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section
* 6.8</a>
*/
public static final int MIME_CHUNK_SIZE = 76;
/**
* PEM chunk size per RFC 1421 section 4.3.2.4.
*
* <p>
* The {@value} character limit does not count the trailing CRLF, but counts
* all other characters, including any equal signs.
* </p>
*
* @see <a href="http://tools.ietf.org/html/rfc1421">RFC 1421 section
* 4.3.2.4</a>
*/
public static final int PEM_CHUNK_SIZE = 64;
private static final int DEFAULT_BUFFER_RESIZE_FACTOR = 2;
/**
* Defines the default buffer size - currently {@value} - must be large
* enough for at least one encoded block+separator
*/
private static final int DEFAULT_BUFFER_SIZE = 8192;
/** Mask used to extract 8 bits, used in decoding bytes */
protected static final int MASK_8BITS = 0xff;
/**
* Byte used to pad output.
*/
protected static final byte PAD_DEFAULT = '='; // Allow static access to
// default
protected final byte PAD = PAD_DEFAULT; // instance variable just in case it
// needs to vary later
/**
* Number of bytes in each full block of unencoded data, e.g. 4 for Base64
* and 5 for Base32
*/
private final int unencodedBlockSize;
/**
* Number of bytes in each full block of encoded data, e.g. 3 for Base64 and
* 8 for Base32
*/
private final int encodedBlockSize;
/**
* Chunksize for encoding. Not used when decoding. A value of zero or less
* implies no chunking of the encoded data. Rounded down to nearest multiple
* of encodedBlockSize.
*/
protected final int lineLength;
/**
* Size of chunk separator. Not used unless {@link #lineLength} > 0.
*/
private final int chunkSeparatorLength;
/**
* Note <code>lineLength</code> is rounded down to the nearest multiple of
* {@link #encodedBlockSize} If <code>chunkSeparatorLength</code> is zero,
* then chunking is disabled.
*
* @param unencodedBlockSize
* the size of an unencoded block (e.g. Base64 = 3)
* @param encodedBlockSize
* the size of an encoded block (e.g. Base64 = 4)
* @param lineLength
* if &gt; 0, use chunking with a length <code>lineLength</code>
* @param chunkSeparatorLength
* the chunk separator length, if relevant
*/
protected BaseNCodec(final int unencodedBlockSize,
final int encodedBlockSize, final int lineLength,
final int chunkSeparatorLength) {
this.unencodedBlockSize = unencodedBlockSize;
this.encodedBlockSize = encodedBlockSize;
final boolean useChunking = lineLength > 0 && chunkSeparatorLength > 0;
this.lineLength = useChunking ? (lineLength / encodedBlockSize)
* encodedBlockSize : 0;
this.chunkSeparatorLength = chunkSeparatorLength;
}
/**
* Returns true if this object has buffered data for reading.
*
* @param context
* the context to be used
* @return true if there is data still available for reading.
*/
boolean hasData(final Context context) { // package protected for access
// from I/O streams
return context.buffer != null;
}
/**
* Returns the amount of buffered data available for reading.
*
* @param context
* the context to be used
* @return The amount of buffered data available for reading.
*/
int available(final Context context) { // package protected for access from
// I/O streams
return context.buffer != null ? context.pos - context.readPos : 0;
}
/**
* Get the default buffer size. Can be overridden.
*
* @return {@link #DEFAULT_BUFFER_SIZE}
*/
protected int getDefaultBufferSize() {
return DEFAULT_BUFFER_SIZE;
}
/**
* Increases our buffer by the {@link #DEFAULT_BUFFER_RESIZE_FACTOR}.
*
* @param context
* the context to be used
*/
private byte[] resizeBuffer(final Context context) {
if (context.buffer == null) {
context.buffer = new byte[getDefaultBufferSize()];
context.pos = 0;
context.readPos = 0;
} else {
final byte[] b = new byte[context.buffer.length
* DEFAULT_BUFFER_RESIZE_FACTOR];
System.arraycopy(context.buffer, 0, b, 0, context.buffer.length);
context.buffer = b;
}
return context.buffer;
}
/**
* Ensure that the buffer has room for <code>size</code> bytes
*
* @param size
* minimum spare space required
* @param context
* the context to be used
*/
protected byte[] ensureBufferSize(final int size, final Context context) {
if ((context.buffer == null)
|| (context.buffer.length < context.pos + size)) {
return resizeBuffer(context);
}
return context.buffer;
}
/**
* Extracts buffered data into the provided byte[] array, starting at
* position bPos, up to a maximum of bAvail bytes. Returns how many bytes
* were actually extracted.
* <p>
* Package protected for access from I/O streams.
*
* @param b
* byte[] array to extract the buffered data into.
* @param bPos
* position in byte[] array to start extraction at.
* @param bAvail
* amount of bytes we're allowed to extract. We may extract fewer
* (if fewer are available).
* @param context
* the context to be used
* @return The number of bytes successfully extracted into the provided
* byte[] array.
*/
int readResults(final byte[] b, final int bPos, final int bAvail,
final Context context) {
if (context.buffer != null) {
final int len = Math.min(available(context), bAvail);
System.arraycopy(context.buffer, context.readPos, b, bPos, len);
context.readPos += len;
if (context.readPos >= context.pos) {
context.buffer = null; // so hasData() will return false, and
// this method can return -1
}
return len;
}
return context.eof ? EOF : 0;
}
/**
* Checks if a byte value is whitespace or not. Whitespace is taken to mean:
* space, tab, CR, LF
*
* @param byteToCheck
* the byte to check
* @return true if byte is whitespace, false otherwise
*/
protected static boolean isWhiteSpace(final byte byteToCheck) {
switch (byteToCheck) {
case ' ':
case '\n':
case '\r':
case '\t':
return true;
default:
return false;
}
}
/**
* Encodes a byte[] containing binary data, into a String containing
* characters in the Base-N alphabet. Uses UTF8 encoding.
*
* @param pArray
* a byte array containing binary data
* @return A String containing only Base-N character data
*/
public String encodeToString(final byte[] pArray) {
return ServerToolkits.newStringUtf8(encode(pArray));
}
/**
* Encodes a byte[] containing binary data, into a String containing
* characters in the appropriate alphabet. Uses UTF8 encoding.
*
* @param pArray
* a byte array containing binary data
* @return String containing only character data in the appropriate
* alphabet.
*/
public String encodeAsString(final byte[] pArray) {
return ServerToolkits.newStringUtf8(encode(pArray));
}
/**
* Decodes a String containing characters in the Base-N alphabet.
*
* @param pArray
* A String containing Base-N character data
* @return a byte array containing binary data
*/
public byte[] decode(final String pArray) {
return decode(ServerToolkits.getBytesUtf8(pArray));
}
/**
* Decodes a byte[] containing characters in the Base-N alphabet.
*
* @param pArray
* A byte array containing Base-N character data
* @return a byte array containing binary data
*/
public byte[] decode(final byte[] pArray) {
if (pArray == null || pArray.length == 0) {
return pArray;
}
final Context context = new Context();
decode(pArray, 0, pArray.length, context);
decode(pArray, 0, EOF, context); // Notify decoder of EOF.
final byte[] result = new byte[context.pos];
readResults(result, 0, result.length, context);
return result;
}
/**
* Encodes a byte[] containing binary data, into a byte[] containing
* characters in the alphabet.
*
* @param pArray
* a byte array containing binary data
* @return A byte array containing only the basen alphabetic character data
*/
public byte[] encode(final byte[] pArray) {
if (pArray == null || pArray.length == 0) {
return pArray;
}
final Context context = new Context();
encode(pArray, 0, pArray.length, context);
encode(pArray, 0, EOF, context); // Notify encoder of EOF.
final byte[] buf = new byte[context.pos - context.readPos];
readResults(buf, 0, buf.length, context);
return buf;
}
// package protected for access from I/O streams
abstract void encode(byte[] pArray, int i, int length, Context context);
// package protected for access from I/O streams
abstract void decode(byte[] pArray, int i, int length, Context context);
/**
* Returns whether or not the <code>octet</code> is in the current alphabet.
* Does not allow whitespace or pad.
*
* @param value
* The value to test
*
* @return {@code true} if the value is defined in the current alphabet,
* {@code false} otherwise.
*/
protected abstract boolean isInAlphabet(byte value);
/**
* Tests a given byte array to see if it contains only valid characters
* within the alphabet. The method optionally treats whitespace and pad as
* valid.
*
* @param arrayOctet
* byte array to test
* @param allowWSPad
* if {@code true}, then whitespace and PAD are also allowed
*
* @return {@code true} if all bytes are valid characters in the alphabet or
* if the byte array is empty; {@code false}, otherwise
*/
public boolean isInAlphabet(final byte[] arrayOctet,
final boolean allowWSPad) {
for (int i = 0; i < arrayOctet.length; i++) {
if (!isInAlphabet(arrayOctet[i])
&& (!allowWSPad || (arrayOctet[i] != PAD)
&& !isWhiteSpace(arrayOctet[i]))) {
return false;
}
}
return true;
}
/**
* Tests a given String to see if it contains only valid characters within
* the alphabet. The method treats whitespace and PAD as valid.
*
* @param basen
* String to test
* @return {@code true} if all characters in the String are valid characters
* in the alphabet or if the String is empty; {@code false},
* otherwise
* @see #isInAlphabet(byte[], boolean)
*/
public boolean isInAlphabet(final String basen) {
return isInAlphabet(ServerToolkits.getBytesUtf8(basen), true);
}
/**
* Tests a given byte array to see if it contains any characters within the
* alphabet or PAD.
*
* Intended for use in checking line-ending arrays
*
* @param arrayOctet
* byte array to test
* @return {@code true} if any byte is a valid character in the alphabet or
* PAD; {@code false} otherwise
*/
protected boolean containsAlphabetOrPad(final byte[] arrayOctet) {
if (arrayOctet == null) {
return false;
}
for (final byte element : arrayOctet) {
if (PAD == element || isInAlphabet(element)) {
return true;
}
}
return false;
}
/**
* Calculates the amount of space needed to encode the supplied array.
*
* @param pArray
* byte[] array which will later be encoded
*
* @return amount of space needed to encoded the supplied array. Returns a
* long since a max-len array will require > Integer.MAX_VALUE
*/
public long getEncodedLength(final byte[] pArray) {
// Calculate non-chunked size - rounded up to allow for padding
// cast to long is needed to avoid possibility of overflow
long len = ((pArray.length + unencodedBlockSize - 1) / unencodedBlockSize)
* (long) encodedBlockSize;
if (lineLength > 0) { // We're using chunking
// Round up to nearest multiple
len += ((len + lineLength - 1) / lineLength) * chunkSeparatorLength;
}
return len;
}
}

View File

@ -0,0 +1,109 @@
package com.zone.weixin4j.controller;
import com.zone.weixin4j.exception.HttpResponseException;
import com.zone.weixin4j.exception.MessageInterceptorException;
import com.zone.weixin4j.exception.WeixinException;
import com.zone.weixin4j.response.WeixinResponse;
import com.zone.weixin4j.service.WeiXin4jContextAware;
import com.zone.weixin4j.service.WxService;
import com.zone.weixin4j.util.AesToken;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestParam;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
/**
* Created by Yz on 2017/3/14.
* WxController
* Spring 主入口需继承的类 / 模版
*/
public abstract class WxController {
private final Log logger = LogFactory.getLog(getClass());
private final String defaultCharset = "UTF-8";
@Autowired
protected WxService wxService;
@Autowired
protected WeiXin4jContextAware weiXin4jContextAware;
protected abstract void doRequest(HttpServletRequest request, HttpServletResponse response, @RequestParam(required = false) String encrypt_type, @RequestParam(required = false) String echostr, @RequestParam(required = false) String timestamp, @RequestParam(required = false) String nonce,
@RequestParam(required = false) String signature, @RequestParam(required = false) String msg_signature, @RequestParam(required = false) String weixin_id);
protected void processMessage(HttpServletRequest request, HttpServletResponse response, String encrypt_type, String echostr, String timestamp, String nonce,
String signature, String msg_signature, String weixin_id) {
String messageContent = getMessageContent(request);
logger.info("read original message {}" + messageContent);
AesToken aesToken = weiXin4jContextAware.getAesTokenMap().get(StringUtils.isEmpty(weixin_id) ? "" : weixin_id);
WeixinResponse weixinResponse = null;
try {
weixinResponse = wxService.processRequest(request.getRequestURI(), encrypt_type, echostr, timestamp, nonce, signature, msg_signature, messageContent, aesToken, request);
response.setCharacterEncoding(defaultCharset);
writeMessage(wxService.transferResponse(weixinResponse), response);
} catch (WeixinException e) {
logger.error("errorCode : " + e.getErrorCode() + " , errorMsg : " + e.getErrorMsg(), e.getCause());
e.printStackTrace();
writeMessage("", response);
} catch (HttpResponseException e) {
logger.error(e.getMessage(), e.getCause());
response.setStatus(e.getHttpResponseStatus().getCode());
response.setContentType(e.getHttpResponseStatus().getReasonPhrase());
writeMessage("", response);
} catch (MessageInterceptorException e) {
logger.error("errorCode : " + e.getErrorCode() + " , errorMsg : " + e.getErrorMsg(), e.getCause());
writeMessage("", response);
}
}
protected String getMessageContent(HttpServletRequest request) {
try {
// 从request中取得输入流
InputStream inputStream = request.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, defaultCharset));
String line;
StringBuilder buf = new StringBuilder();
while ((line = reader.readLine()) != null) {
buf.append(line);
}
reader.close();
inputStream.close();
return buf.toString();
} catch (IOException e) {
logger.error(e.getMessage(), e.getCause());
}
return "";
}
protected void writeMessage(String result, HttpServletResponse response) {
response.setContentType("application/xml");
response.setCharacterEncoding("UTF-8");
try {
PrintWriter writer = response.getWriter();
writer.write(result);
writer.flush();
writer.close();
} catch (IOException e) {
logger.error(e.getMessage(), e.getCause());
}
}
public void setWxService(WxService wxService) {
this.wxService = wxService;
}
public void setWeiXin4jContextAware(WeiXin4jContextAware weiXin4jContextAware) {
this.weiXin4jContextAware = weiXin4jContextAware;
}
public WeiXin4jContextAware getWeiXin4jContextAware() {
return weiXin4jContextAware;
}
}

View File

@ -0,0 +1,187 @@
package com.zone.weixin4j.dispatcher;
import com.zone.weixin4j.message.*;
import com.zone.weixin4j.message.event.*;
import com.zone.weixin4j.mp.event.*;
import com.zone.weixin4j.qy.event.BatchjobresultMessage;
import com.zone.weixin4j.qy.event.EnterAgentEventMessage;
import com.zone.weixin4j.request.WeixinMessage;
import com.zone.weixin4j.type.AccountType;
import com.zone.weixin4j.type.EventType;
import com.zone.weixin4j.type.MessageType;
import java.util.HashMap;
import java.util.Map;
/**
* 默认MessageMatcher实现(可以改进)
*
* @className DefaultMessageMatcher
* @author jinyu(foxinmy@gmail.com)
* @date 2015年6月10日
* @since JDK 1.6
* @see
*/
public class DefaultMessageMatcher implements WeixinMessageMatcher {
private final Map<WeixinMessageKey, Class<? extends WeixinMessage>> messageClassMap;
public DefaultMessageMatcher() {
messageClassMap = new HashMap<WeixinMessageKey, Class<? extends WeixinMessage>>();
initMessageClass();
}
private void initMessageClass() {
// /////////////////////////////////////////////////
/******************** 普通消息 ********************/
// /////////////////////////////////////////////////
initGeneralMessageClass();
// /////////////////////////////////////////////////
/******************** 事件消息 ********************/
// /////////////////////////////////////////////////
initEventMessageClass();
// /////////////////////////////////////////////////
/***************** 公众平台事件消息 *****************/
// /////////////////////////////////////////////////
initMpEventMessageClass();
// /////////////////////////////////////////////////
/****************** 企业号事件消息 ******************/
// /////////////////////////////////////////////////
initQyEventMessageClass();
}
private void initGeneralMessageClass() {
for (AccountType accountType : AccountType.values()) {
messageClassMap.put(new WeixinMessageKey(MessageType.text.name(),
null, accountType), TextMessage.class);
messageClassMap.put(new WeixinMessageKey(MessageType.image.name(),
null, accountType), ImageMessage.class);
messageClassMap.put(new WeixinMessageKey(MessageType.voice.name(),
null, accountType), VoiceMessage.class);
messageClassMap.put(new WeixinMessageKey(MessageType.video.name(),
null, accountType), VideoMessage.class);
messageClassMap.put(
new WeixinMessageKey(MessageType.shortvideo.name(), null,
accountType), VideoMessage.class);
messageClassMap.put(
new WeixinMessageKey(MessageType.location.name(), null,
accountType), LocationMessage.class);
messageClassMap.put(new WeixinMessageKey(MessageType.link.name(),
null, accountType), LinkMessage.class);
}
}
private void initEventMessageClass() {
String messageType = MessageType.event.name();
EventType[] eventTypes = new EventType[] { EventType.subscribe,
EventType.unsubscribe };
for (EventType eventType : eventTypes) {
messageClassMap.put(
new WeixinMessageKey(messageType, eventType.name(),
AccountType.MP),
ScribeEventMessage.class);
}
for (EventType eventType : eventTypes) {
messageClassMap.put(
new WeixinMessageKey(messageType, eventType.name(),
AccountType.QY),
ScribeEventMessage.class);
}
for (AccountType accountType : AccountType.values()) {
messageClassMap.put(new WeixinMessageKey(messageType,
EventType.location.name(), accountType),
LocationEventMessage.class);
messageClassMap.put(new WeixinMessageKey(messageType,
EventType.location_select.name(), accountType),
MenuLocationEventMessage.class);
for (EventType eventType : new EventType[] { EventType.click,
EventType.view }) {
messageClassMap.put(
new WeixinMessageKey(messageType, eventType.name(),
accountType), MenuEventMessage.class);
}
for (EventType eventType : new EventType[] {
EventType.scancode_push, EventType.scancode_waitmsg }) {
messageClassMap.put(
new WeixinMessageKey(messageType, eventType.name(),
accountType), MenuScanEventMessage.class);
}
for (EventType eventType : new EventType[] {
EventType.pic_sysphoto, EventType.pic_photo_or_album,
EventType.pic_weixin }) {
messageClassMap.put(
new WeixinMessageKey(messageType, eventType.name(),
accountType), MenuPhotoEventMessage.class);
}
}
}
private void initMpEventMessageClass() {
String messageType = MessageType.event.name();
AccountType accountType = AccountType.MP;
messageClassMap.put(
new WeixinMessageKey(messageType, EventType.scan.name(),
accountType),
ScanEventMessage.class);
messageClassMap.put(new WeixinMessageKey(messageType,
EventType.masssendjobfinish.name(), accountType),
MassEventMessage.class);
messageClassMap.put(new WeixinMessageKey(messageType,
EventType.templatesendjobfinish.name(), accountType),
TemplatesendjobfinishMessage.class);
messageClassMap.put(new WeixinMessageKey(messageType,
EventType.kf_create_session.name(), accountType),
KfCreateEventMessage.class);
messageClassMap.put(new WeixinMessageKey(messageType,
EventType.kf_close_session.name(), accountType),
KfCloseEventMessage.class);
messageClassMap.put(new WeixinMessageKey(messageType,
EventType.kf_switch_session.name(), accountType),
KfSwitchEventMessage.class);
EventType[] eventTypes = new EventType[] {
EventType.qualification_verify_success,
EventType.naming_verify_success, EventType.annual_renew,
EventType.verify_expired };
for (EventType eventType : eventTypes) {
messageClassMap.put(
new WeixinMessageKey(messageType, eventType.name(),
accountType), VerifyExpireEventMessage.class);
}
eventTypes = new EventType[] { EventType.qualification_verify_success,
EventType.naming_verify_fail };
for (EventType eventType : eventTypes) {
messageClassMap.put(
new WeixinMessageKey(messageType, eventType.name(),
accountType), VerifyFailEventMessage.class);
}
}
private void initQyEventMessageClass() {
String messageType = MessageType.event.name();
messageClassMap.put(new WeixinMessageKey(messageType,
EventType.batch_job_result.name(), AccountType.QY),
BatchjobresultMessage.class);
messageClassMap.put(new WeixinMessageKey(messageType,
EventType.enter_agent.name(), AccountType.QY),
EnterAgentEventMessage.class);
//messageClassMap.put(new WeixinMessageKey(messageType,
// EventType.suite.name(), AccountType.QY),
//SuiteMessage.class);
}
@Override
public Class<? extends WeixinMessage> match(WeixinMessageKey messageKey) {
return messageClassMap.get(messageKey);
}
@Override
public void regist(WeixinMessageKey messageKey,
Class<? extends WeixinMessage> messageClass) {
Class<?> clazz = match(messageKey);
if (clazz != null) {
throw new IllegalArgumentException("duplicate messagekey '"
+ messageKey + "' define for " + clazz);
}
messageClassMap.put(messageKey, messageClass);
}
}

View File

@ -0,0 +1,112 @@
package com.zone.weixin4j.dispatcher;
import com.zone.weixin4j.exception.WeixinException;
import com.zone.weixin4j.handler.WeixinMessageHandler;
import com.zone.weixin4j.interceptor.WeixinMessageInterceptor;
import com.zone.weixin4j.request.WeixinMessage;
import com.zone.weixin4j.request.WeixinRequest;
import com.zone.weixin4j.response.WeixinResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* 微信消息的处理执行
*
* @author jinyu(foxinmy@gmail.com)
* @className MessageHandlerExecutor
* @date 2015年5月7日
* @see com.zone.weixin4j.handler.WeixinMessageHandler
* @see com.zone.weixin4j.interceptor.WeixinMessageInterceptor
* @since JDK 1.6
*/
public class MessageHandlerExecutor {
private final Log logger = LogFactory.getLog(getClass());
/**
* 消息处理器
*/
private final WeixinMessageHandler messageHandler;
/**
* 消息拦截器
*/
private final WeixinMessageInterceptor[] messageInterceptors;
private int interceptorIndex = -1;
public MessageHandlerExecutor(WeixinMessageHandler messageHandler, WeixinMessageInterceptor[] messageInterceptors) {
this.messageHandler = messageHandler;
this.messageInterceptors = messageInterceptors;
}
public WeixinMessageHandler getMessageHandler() {
return messageHandler;
}
/**
* 执行预拦截动作
*
* @param request 微信请求信息
* @param message 微信消息
* @return true则继续执行往下执行
* @throws WeixinException
*/
public boolean applyPreHandle(WeixinRequest request, WeixinMessage message)
throws WeixinException {
if (messageInterceptors != null) {
for (int i = 0; i < messageInterceptors.length; i++) {
WeixinMessageInterceptor interceptor = messageInterceptors[i];
if (!interceptor.preHandle(request, message, messageHandler)) {
triggerAfterCompletion(request, null, message, null);
return false;
}
this.interceptorIndex = i;
}
}
return true;
}
/**
* MessageHandler处理玩请求后的动作
*
* @param request 微信请求
* @param response 处理后的响应
* @param message 微信消息
* @throws WeixinException
*/
public void applyPostHandle(WeixinRequest request, WeixinResponse response,
WeixinMessage message) throws WeixinException {
if (messageInterceptors == null) {
return;
}
for (int i = messageInterceptors.length - 1; i >= 0; i--) {
WeixinMessageInterceptor interceptor = messageInterceptors[i];
interceptor.postHandle(request, response, message, messageHandler);
}
}
/**
* 全部执行完毕后触发
*
* @param request 微信请求
* @param response 微信响应 可能为空
* @param message 微信消息
* @param exception 处理时可能的异常
* @throws WeixinException
*/
public void triggerAfterCompletion(WeixinRequest request,
WeixinResponse response, WeixinMessage message, Exception exception)
throws WeixinException {
if (messageInterceptors == null) {
return;
}
for (int i = this.interceptorIndex; i >= 0; i--) {
WeixinMessageInterceptor interceptor = messageInterceptors[i];
try {
interceptor.afterCompletion(request, response, message, messageHandler, exception);
} catch (WeixinException e) {
logger.error("MessageInterceptor.afterCompletion threw exception", e);
}
}
}
}

View File

@ -0,0 +1,337 @@
package com.zone.weixin4j.dispatcher;
import com.zone.weixin4j.annotation.WxMessageHandler;
import com.zone.weixin4j.annotation.WxMessageInterceptor;
import com.zone.weixin4j.exception.HttpResponseException;
import com.zone.weixin4j.exception.MessageInterceptorException;
import com.zone.weixin4j.exception.WeixinException;
import com.zone.weixin4j.handler.DebugMessageHandler;
import com.zone.weixin4j.handler.WeixinMessageHandler;
import com.zone.weixin4j.interceptor.WeixinMessageInterceptor;
import com.zone.weixin4j.request.WeixinMessage;
import com.zone.weixin4j.request.WeixinRequest;
import com.zone.weixin4j.response.BlankResponse;
import com.zone.weixin4j.response.WeixinResponse;
import com.zone.weixin4j.service.WeiXin4jContextAware;
import com.zone.weixin4j.service.context.WeiXin4jContextAwareImpl;
import com.zone.weixin4j.socket.WeixinMessageTransfer;
import com.zone.weixin4j.util.ServerToolkits;
import com.zone.weixin4j.xml.MessageTransferHandler;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import java.io.ByteArrayInputStream;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* 微信消息分发器
*
* @author jinyu(foxinmy@gmail.com)
* @className WeixinMessageDispatcher
* @date 2015年5月7日
* @updateBy Yz(174975857@qq.com)
* @since JDK 1.6
*/
@Component
@DependsOn({"weiXin4jContextAware"})
public class WeixinMessageDispatcher {
@Autowired
private WeiXin4jContextAware weiXin4jContextAware;
private final Log logger = LogFactory.getLog(getClass());
/**
* 消息处理器
*/
private List<WeixinMessageHandler> messageHandlerList = new ArrayList<WeixinMessageHandler>();
private WeixinMessageHandler[] messageHandlers;
/**
* 消息拦截器
*/
private List<WeixinMessageInterceptor> messageInterceptorList = new ArrayList<WeixinMessageInterceptor>();
private WeixinMessageInterceptor[] messageInterceptors;
/**
* 消息匹配
*/
private WeixinMessageMatcher messageMatcher;
/**
* 消息转换
*/
private Map<Class<? extends WeixinMessage>, Unmarshaller> messageUnmarshaller;
/**
* 是否总是响应请求,如未匹配到MessageHandler时回复空白消息
*/
private boolean alwaysResponse;
public WeixinMessageDispatcher() {
this(new DefaultMessageMatcher());
}
public WeixinMessageDispatcher(WeixinMessageMatcher messageMatcher) {
this.messageMatcher = messageMatcher;
this.messageUnmarshaller = new ConcurrentHashMap<Class<? extends WeixinMessage>, Unmarshaller>();
}
@PostConstruct
public void init() {
try {
this.getMessageHandlers();
this.getMessageInterceptors();
if (weiXin4jContextAware.isOpenAlwaysResponse()) {
this.openAlwaysResponse();
}
if (weiXin4jContextAware.isUseDebugMessageHandler()) {
if(null == messageHandlerList){
messageHandlerList = new ArrayList<WeixinMessageHandler>();
messageHandlerList.add(DebugMessageHandler.global);
}
}
this.messageMatcher = weiXin4jContextAware.getWeixinMessageMatcher() == null ? new DefaultMessageMatcher() : weiXin4jContextAware.getWeixinMessageMatcher();
this.messageUnmarshaller = new ConcurrentHashMap<Class<? extends WeixinMessage>, Unmarshaller>();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 对消息进行一系列的处理,包括 拦截匹配分发等动作
*
* @param request 微信请求
* @throws WeixinException
*/
public WeixinResponse doDispatch(final WeixinRequest request) throws WeixinException, HttpResponseException, MessageInterceptorException {
WeixinMessageTransfer messageTransfer = MessageTransferHandler.parser(request);
WeiXin4jContextAwareImpl.getWeixinMessageTransfer().set(messageTransfer);
WeixinMessageKey messageKey = defineMessageKey(messageTransfer, request);
Class<? extends WeixinMessage> targetClass = messageMatcher.match(messageKey);
WeixinMessage message = messageRead(request.getOriginalContent(), targetClass);
logger.info(String.format("define %s matched %s", messageKey, targetClass));
MessageHandlerExecutor handlerExecutor = getHandlerExecutor(request, messageKey, message, messageTransfer.getNodeNames());
if (handlerExecutor == null || handlerExecutor.getMessageHandler() == null) {
return noHandlerFound(request, message);
}
if (!handlerExecutor.applyPreHandle(request, message)) {
throw new MessageInterceptorException(" Interceptor Not Accept !! ");
}
Exception exception = null;
WeixinResponse response = null;
try {
response = handlerExecutor.getMessageHandler().doHandle(request, message, messageTransfer.getNodeNames());
handlerExecutor.applyPostHandle(request, response, message);
} catch (Exception e) {
exception = e;
}
handlerExecutor.triggerAfterCompletion(request, response, message, exception);
return response;
}
/**
* 声明messagekey
*
* @param messageTransfer 基础消息
* @param request 请求信息
* @return
*/
protected WeixinMessageKey defineMessageKey(
WeixinMessageTransfer messageTransfer, WeixinRequest request) {
return new WeixinMessageKey(messageTransfer.getMsgType(),
messageTransfer.getEventType(),
messageTransfer.getAccountType());
}
/**
* 未匹配到handler时触发
*
* @param request 微信请求
* @param message 微信消息
*/
protected WeixinResponse noHandlerFound(WeixinRequest request, WeixinMessage message) throws HttpResponseException {
logger.warn(String.format("no handler found for %s", request));
if (alwaysResponse) {
return BlankResponse.global;
} else {
throw new HttpResponseException(HttpResponseException.HttpResponseStatus.NOT_FOUND);
}
}
/**
* MessageHandlerExecutor
*
* @param request 微信请求
* @param messageKey 消息的key
* @param message 微信消息
* @param nodeNames 节点名称集合
* @return MessageHandlerExecutor
* @throws WeixinException
* @see MessageHandlerExecutor
*/
protected MessageHandlerExecutor getHandlerExecutor(
WeixinRequest request,
WeixinMessageKey messageKey, WeixinMessage message,
Set<String> nodeNames) throws WeixinException {
WeixinMessageHandler[] messageHandlers = getMessageHandlers();
if (messageHandlers == null) {
return null;
}
logger.info(String.format("resolve message handlers %s", this.messageHandlerList));
List<WeixinMessageHandler> matchedMessageHandlers = new ArrayList<WeixinMessageHandler>();
for (WeixinMessageHandler handler : messageHandlers) {
if (handler.canHandle(request, message, nodeNames)) {
matchedMessageHandlers.add(handler);
}
}
if (matchedMessageHandlers.isEmpty()) {
return null;
}
Collections.sort(matchedMessageHandlers,
new Comparator<WeixinMessageHandler>() {
@Override
public int compare(WeixinMessageHandler m1,
WeixinMessageHandler m2) {
return m2.weight() - m1.weight();
}
});
logger.info(String.format("matched message handlers %s", matchedMessageHandlers));
return new MessageHandlerExecutor(matchedMessageHandlers.get(0), getMessageInterceptors());
}
/**
* 获取所有的handler
*
* @return handler集合
* @throws WeixinException
* @see com.zone.weixin4j.handler.WeixinMessageHandler
*/
public WeixinMessageHandler[] getMessageHandlers() throws WeixinException {
if (this.messageHandlers == null) {
String[] beanNamesForAnnotation = this.weiXin4jContextAware.getApplicationContext().getBeanNamesForAnnotation(WxMessageHandler.class);
for (String str : beanNamesForAnnotation) {
Object bean = this.weiXin4jContextAware.getApplicationContext().getBean(str);
if (bean instanceof WeixinMessageHandler) {
this.messageHandlerList.add((WeixinMessageHandler) this.weiXin4jContextAware.getApplicationContext().getBean(str));
}
}
}
if (messageHandlerList != null && !this.messageHandlerList.isEmpty()) {
this.messageHandlers = this.messageHandlerList.toArray(new WeixinMessageHandler[this.messageHandlerList.size()]);
}
return this.messageHandlers;
}
/**
* 获取所有的interceptor
*
* @return interceptor集合
* @throws WeixinException
*/
public WeixinMessageInterceptor[] getMessageInterceptors()
throws WeixinException {
if (this.messageInterceptors == null) {
String[] beanNamesForAnnotation = this.weiXin4jContextAware.getApplicationContext().getBeanNamesForAnnotation(WxMessageInterceptor.class);
for (String str : beanNamesForAnnotation) {
Object bean = this.weiXin4jContextAware.getApplicationContext().getBean(str);
if (bean instanceof WeixinMessageInterceptor) {
this.messageInterceptorList.add((WeixinMessageInterceptor) this.weiXin4jContextAware.getApplicationContext().getBean(str));
}
}
}
if (this.messageInterceptorList != null && !this.messageInterceptorList.isEmpty()) {
Collections.sort(messageInterceptorList,
new Comparator<WeixinMessageInterceptor>() {
@Override
public int compare(WeixinMessageInterceptor m1, WeixinMessageInterceptor m2) {
return m2.weight() - m1.weight();
}
});
this.messageInterceptors = this.messageInterceptorList.toArray(new WeixinMessageInterceptor[this.messageInterceptorList.size()]);
}
logger.info(String.format("resolve message interceptors %s", this.messageInterceptorList));
return this.messageInterceptors;
}
/**
* jaxb读取微信消息
*
* @param message xml消息
* @param clazz 消息类型
* @return 消息对象
* @throws WeixinException
*/
protected <M extends WeixinMessage> M messageRead(String message,
Class<M> clazz) throws WeixinException {
if (clazz == null) {
return null;
}
try {
Source source = new StreamSource(new ByteArrayInputStream(
ServerToolkits.getBytesUtf8(message)));
JAXBElement<M> jaxbElement = getUnmarshaller(clazz).unmarshal(
source, clazz);
return jaxbElement.getValue();
} catch (JAXBException e) {
throw new WeixinException(e);
}
}
/**
* xml消息转换器
*
* @param clazz 消息类型
* @return 消息转换器
* @throws WeixinException
*/
protected Unmarshaller getUnmarshaller(Class<? extends WeixinMessage> clazz)
throws WeixinException {
Unmarshaller unmarshaller = messageUnmarshaller.get(clazz);
if (unmarshaller == null) {
try {
JAXBContext jaxbContext = JAXBContext.newInstance(clazz);
unmarshaller = jaxbContext.createUnmarshaller();
messageUnmarshaller.put(clazz, unmarshaller);
} catch (JAXBException e) {
throw new WeixinException(e);
}
}
return unmarshaller;
}
public void setMessageHandlerList(
List<WeixinMessageHandler> messageHandlerList) {
this.messageHandlerList = messageHandlerList;
}
public void setMessageInterceptorList(
List<WeixinMessageInterceptor> messageInterceptorList) {
this.messageInterceptorList = messageInterceptorList;
}
public void registMessageClass(WeixinMessageKey messageKey,
Class<? extends WeixinMessage> messageClass) {
messageMatcher.regist(messageKey, messageClass);
}
public WeixinMessageMatcher getMessageMatcher() {
return this.messageMatcher;
}
/**
* 打开总是响应开关,如未匹配到MessageHandler时回复空白消息
*/
public void openAlwaysResponse() {
this.alwaysResponse = true;
}
}

View File

@ -0,0 +1,88 @@
package com.zone.weixin4j.dispatcher;
import com.zone.weixin4j.type.AccountType;
import com.zone.weixin4j.util.ServerToolkits;
import java.io.Serializable;
/**
* 微信消息key
*
* @className WeixinMessageKey
* @author jinyu(foxinmy@gmail.com)
* @date 2015年6月9日
* @since JDK 1.6
* @see
*/
public class WeixinMessageKey implements Serializable {
private static final long serialVersionUID = -691330687850400289L;
private String messageType;
private String eventType;
private AccountType accountType;
public WeixinMessageKey(String messageType, String eventType,
AccountType accountType) {
this.messageType = messageType;
this.eventType = eventType;
this.accountType = accountType;
}
public String getMessageType() {
return messageType;
}
public String getEventType() {
return eventType;
}
public AccountType getAccountType() {
return accountType;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((accountType == null) ? 0 : accountType.hashCode());
result = prime * result
+ ((ServerToolkits.isBlank(eventType)) ? 0 : eventType.hashCode());
result = prime
* result
+ ((ServerToolkits.isBlank(messageType)) ? 0 : messageType
.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
WeixinMessageKey other = (WeixinMessageKey) obj;
if (accountType != other.accountType)
return false;
if (eventType == null) {
if (other.eventType != null)
return false;
} else if (!eventType.equalsIgnoreCase(other.eventType))
return false;
if (messageType == null) {
if (other.messageType != null)
return false;
} else if (!messageType.equalsIgnoreCase(other.messageType))
return false;
return true;
}
@Override
public String toString() {
return "WeixinMessageKey [messageType=" + messageType + ", eventType="
+ eventType + ", accountType=" + accountType + "]";
}
}

View File

@ -0,0 +1,34 @@
package com.zone.weixin4j.dispatcher;
import com.zone.weixin4j.request.WeixinMessage;
/**
* 微信消息匹配
*
* @className WeixinMessageMatcher
* @author jinyu(foxinmy@gmail.com)
* @date 2015年5月17日
* @since JDK 1.6
* @see DefaultMessageMatcher
*/
public interface WeixinMessageMatcher {
/**
* 匹配消息类型
*
* @param messageKey
* 消息key
* @return 消息类型
*/
public Class<? extends WeixinMessage> match(WeixinMessageKey messageKey);
/**
* 注册消息类型程序没有及时更新而微信又产生了新的消息类型
*
* @param messageKey
* 消息key
* @param messageClass
* 消息类型
*/
public void regist(WeixinMessageKey messageKey,
Class<? extends WeixinMessage> messageClass);
}

View File

@ -0,0 +1,359 @@
package com.zone.weixin4j.exception;
/**
* Created by Yz on 2017/3/15.
* HttpResponseException -- 异常类
*/
public class HttpResponseException extends Exception {
private static final long serialVersionUID = 1932809035537660697L;
private HttpResponseStatus httpResponseStatus;
public HttpResponseException(HttpResponseStatus httpResponseStatus) {
this.httpResponseStatus = httpResponseStatus;
}
public HttpResponseStatus getHttpResponseStatus() {
return httpResponseStatus;
}
public void setHttpResponseStatus(HttpResponseStatus httpResponseStatus) {
this.httpResponseStatus = httpResponseStatus;
}
public static class HttpResponseStatus {
/**
* 100 Continue
*/
public static final HttpResponseStatus CONTINUE = newStatus(100, "Continue");
/**
* 101 Switching Protocols
*/
public static final HttpResponseStatus SWITCHING_PROTOCOLS = newStatus(101, "Switching Protocols");
/**
* 102 Processing (WebDAV, RFC2518)
*/
public static final HttpResponseStatus PROCESSING = newStatus(102, "Processing");
/**
* 200 OK
*/
public static final HttpResponseStatus OK = newStatus(200, "OK");
/**
* 201 Created
*/
public static final HttpResponseStatus CREATED = newStatus(201, "Created");
/**
* 202 Accepted
*/
public static final HttpResponseStatus ACCEPTED = newStatus(202, "Accepted");
/**
* 203 Non-Authoritative Information (since HTTP/1.1)
*/
public static final HttpResponseStatus NON_AUTHORITATIVE_INFORMATION =
newStatus(203, "Non-Authoritative Information");
/**
* 204 No Content
*/
public static final HttpResponseStatus NO_CONTENT = newStatus(204, "No Content");
/**
* 205 Reset Content
*/
public static final HttpResponseStatus RESET_CONTENT = newStatus(205, "Reset Content");
/**
* 206 Partial Content
*/
public static final HttpResponseStatus PARTIAL_CONTENT = newStatus(206, "Partial Content");
/**
* 207 Multi-Status (WebDAV, RFC2518)
*/
public static final HttpResponseStatus MULTI_STATUS = newStatus(207, "Multi-Status");
/**
* 300 Multiple Choices
*/
public static final HttpResponseStatus MULTIPLE_CHOICES = newStatus(300, "Multiple Choices");
/**
* 301 Moved Permanently
*/
public static final HttpResponseStatus MOVED_PERMANENTLY = newStatus(301, "Moved Permanently");
/**
* 302 Found
*/
public static final HttpResponseStatus FOUND = newStatus(302, "Found");
/**
* 303 See Other (since HTTP/1.1)
*/
public static final HttpResponseStatus SEE_OTHER = newStatus(303, "See Other");
/**
* 304 Not Modified
*/
public static final HttpResponseStatus NOT_MODIFIED = newStatus(304, "Not Modified");
/**
* 305 Use Proxy (since HTTP/1.1)
*/
public static final HttpResponseStatus USE_PROXY = newStatus(305, "Use Proxy");
/**
* 307 Temporary Redirect (since HTTP/1.1)
*/
public static final HttpResponseStatus TEMPORARY_REDIRECT = newStatus(307, "Temporary Redirect");
/**
* 400 Bad Request
*/
public static final HttpResponseStatus BAD_REQUEST = newStatus(400, "Bad Request");
/**
* 401 Unauthorized
*/
public static final HttpResponseStatus UNAUTHORIZED = newStatus(401, "Unauthorized");
/**
* 402 Payment Required
*/
public static final HttpResponseStatus PAYMENT_REQUIRED = newStatus(402, "Payment Required");
/**
* 403 Forbidden
*/
public static final HttpResponseStatus FORBIDDEN = newStatus(403, "Forbidden");
/**
* 404 Not Found
*/
public static final HttpResponseStatus NOT_FOUND = newStatus(404, "Not Found");
/**
* 405 Method Not Allowed
*/
public static final HttpResponseStatus METHOD_NOT_ALLOWED = newStatus(405, "Method Not Allowed");
/**
* 406 Not Acceptable
*/
public static final HttpResponseStatus NOT_ACCEPTABLE = newStatus(406, "Not Acceptable");
/**
* 407 Proxy Authentication Required
*/
public static final HttpResponseStatus PROXY_AUTHENTICATION_REQUIRED =
newStatus(407, "Proxy Authentication Required");
/**
* 408 Request Timeout
*/
public static final HttpResponseStatus REQUEST_TIMEOUT = newStatus(408, "Request Timeout");
/**
* 409 Conflict
*/
public static final HttpResponseStatus CONFLICT = newStatus(409, "Conflict");
/**
* 410 Gone
*/
public static final HttpResponseStatus GONE = newStatus(410, "Gone");
/**
* 411 Length Required
*/
public static final HttpResponseStatus LENGTH_REQUIRED = newStatus(411, "Length Required");
/**
* 412 Precondition Failed
*/
public static final HttpResponseStatus PRECONDITION_FAILED = newStatus(412, "Precondition Failed");
/**
* 413 Request Entity Too Large
*/
public static final HttpResponseStatus REQUEST_ENTITY_TOO_LARGE =
newStatus(413, "Request Entity Too Large");
/**
* 414 Request-URI Too Long
*/
public static final HttpResponseStatus REQUEST_URI_TOO_LONG = newStatus(414, "Request-URI Too Long");
/**
* 415 Unsupported Media Type
*/
public static final HttpResponseStatus UNSUPPORTED_MEDIA_TYPE = newStatus(415, "Unsupported Media Type");
/**
* 416 Requested Range Not Satisfiable
*/
public static final HttpResponseStatus REQUESTED_RANGE_NOT_SATISFIABLE =
newStatus(416, "Requested Range Not Satisfiable");
/**
* 417 Expectation Failed
*/
public static final HttpResponseStatus EXPECTATION_FAILED = newStatus(417, "Expectation Failed");
/**
* 421 Misdirected Request
* <p>
* <a href="https://tools.ietf.org/html/draft-ietf-httpbis-http2-15#section-9.1.2">421 Status Code</a>
*/
public static final HttpResponseStatus MISDIRECTED_REQUEST = newStatus(421, "Misdirected Request");
/**
* 422 Unprocessable Entity (WebDAV, RFC4918)
*/
public static final HttpResponseStatus UNPROCESSABLE_ENTITY = newStatus(422, "Unprocessable Entity");
/**
* 423 Locked (WebDAV, RFC4918)
*/
public static final HttpResponseStatus LOCKED = newStatus(423, "Locked");
/**
* 424 Failed Dependency (WebDAV, RFC4918)
*/
public static final HttpResponseStatus FAILED_DEPENDENCY = newStatus(424, "Failed Dependency");
/**
* 425 Unordered Collection (WebDAV, RFC3648)
*/
public static final HttpResponseStatus UNORDERED_COLLECTION = newStatus(425, "Unordered Collection");
/**
* 426 Upgrade Required (RFC2817)
*/
public static final HttpResponseStatus UPGRADE_REQUIRED = newStatus(426, "Upgrade Required");
/**
* 428 Precondition Required (RFC6585)
*/
public static final HttpResponseStatus PRECONDITION_REQUIRED = newStatus(428, "Precondition Required");
/**
* 429 Too Many Requests (RFC6585)
*/
public static final HttpResponseStatus TOO_MANY_REQUESTS = newStatus(429, "Too Many Requests");
/**
* 431 Request Header Fields Too Large (RFC6585)
*/
public static final HttpResponseStatus REQUEST_HEADER_FIELDS_TOO_LARGE =
newStatus(431, "Request Header Fields Too Large");
/**
* 500 Internal Server Error
*/
public static final HttpResponseStatus INTERNAL_SERVER_ERROR = newStatus(500, "Internal Server Error");
/**
* 501 Not Implemented
*/
public static final HttpResponseStatus NOT_IMPLEMENTED = newStatus(501, "Not Implemented");
/**
* 502 Bad Gateway
*/
public static final HttpResponseStatus BAD_GATEWAY = newStatus(502, "Bad Gateway");
/**
* 503 Service Unavailable
*/
public static final HttpResponseStatus SERVICE_UNAVAILABLE = newStatus(503, "Service Unavailable");
/**
* 504 Gateway Timeout
*/
public static final HttpResponseStatus GATEWAY_TIMEOUT = newStatus(504, "Gateway Timeout");
/**
* 505 HTTP Version Not Supported
*/
public static final HttpResponseStatus HTTP_VERSION_NOT_SUPPORTED =
newStatus(505, "HTTP Version Not Supported");
/**
* 506 Variant Also Negotiates (RFC2295)
*/
public static final HttpResponseStatus VARIANT_ALSO_NEGOTIATES = newStatus(506, "Variant Also Negotiates");
/**
* 507 Insufficient Storage (WebDAV, RFC4918)
*/
public static final HttpResponseStatus INSUFFICIENT_STORAGE = newStatus(507, "Insufficient Storage");
/**
* 510 Not Extended (RFC2774)
*/
public static final HttpResponseStatus NOT_EXTENDED = newStatus(510, "Not Extended");
/**
* 511 Network Authentication Required (RFC6585)
*/
public static final HttpResponseStatus NETWORK_AUTHENTICATION_REQUIRED =
newStatus(511, "Network Authentication Required");
private static HttpResponseStatus newStatus(int statusCode, String reasonPhrase) {
return new HttpResponseStatus(statusCode, reasonPhrase, true);
}
private final int code;
private final String reasonPhrase;
public int getCode() {
return code;
}
public String getReasonPhrase() {
return reasonPhrase;
}
/**
* Creates a new instance with the specified {@code code} and its {@code reasonPhrase}.
*/
private HttpResponseStatus(int code, String reasonPhrase, boolean bytes) {
if (code < 0) {
throw new IllegalArgumentException(
"code: " + code + " (expected: 0+)");
}
if (reasonPhrase == null) {
throw new NullPointerException("reasonPhrase");
}
for (int i = 0; i < reasonPhrase.length(); i++) {
char c = reasonPhrase.charAt(i);
// Check prohibited characters.
switch (c) {
case '\n':
case '\r':
throw new IllegalArgumentException(
"reasonPhrase contains one of the following prohibited characters: " +
"\\r\\n: " + reasonPhrase);
}
}
this.code = code;
this.reasonPhrase = reasonPhrase;
}
}
}

View File

@ -0,0 +1,46 @@
package com.zone.weixin4j.exception;
/**
* Created by Yz on 2017/3/15.
* 微信消息拦截器异常
*/
public class MessageInterceptorException extends Exception {
private static final long serialVersionUID = -1094109872039360113L;
private String code;
private String msg;
public MessageInterceptorException(String errorCode, String errorMsg) {
this.code = errorCode;
this.msg = errorMsg;
}
public MessageInterceptorException(String errorMsg) {
this.code = "-1";
this.msg = errorMsg;
}
public MessageInterceptorException(Exception e) {
super(e);
}
public MessageInterceptorException(String errorMsg, Exception e) {
super(e);
this.msg = errorMsg;
}
public String getErrorCode() {
return code;
}
public String getErrorMsg() {
return msg;
}
@Override
public String getMessage() {
return this.code + "," + this.msg;
}
}

View File

@ -0,0 +1,50 @@
package com.zone.weixin4j.exception;
/**
* 微信异常
*
* @className WeixinException
* @author jinyu(foxinmy@gmail.com)
* @date 2014年4月10日
* @since JDK 1.6
* @see
*/
public class WeixinException extends Exception {
private static final long serialVersionUID = 7148145661883468514L;
private String errorCode;
private String errorMsg;
public WeixinException(String errorCode, String errorMsg) {
this.errorCode = errorCode;
this.errorMsg = errorMsg;
}
public WeixinException(String errorMsg) {
this.errorCode = "-1";
this.errorMsg = errorMsg;
}
public WeixinException(Exception e) {
super(e);
}
public WeixinException(String errorMsg, Exception e) {
super(e);
this.errorMsg = errorMsg;
}
public String getErrorCode() {
return errorCode;
}
public String getErrorMsg() {
return errorMsg;
}
@Override
public String getMessage() {
return this.errorCode + "," + this.errorMsg;
}
}

View File

@ -0,0 +1,47 @@
package com.zone.weixin4j.handler;
import com.zone.weixin4j.exception.WeixinException;
import com.zone.weixin4j.request.WeixinMessage;
import com.zone.weixin4j.request.WeixinRequest;
import com.zone.weixin4j.response.TextResponse;
import com.zone.weixin4j.response.WeixinResponse;
import java.util.Set;
/**
* 调试消息处理器
*
* @className DebugMessageHandler
* @author jinyu(foxinmy@gmail.com)
* @date 2015年5月17日
* @since JDK 1.6
* @see
*/
public class DebugMessageHandler implements WeixinMessageHandler {
public static final DebugMessageHandler global = new DebugMessageHandler();
private DebugMessageHandler() {
}
@Override
public boolean canHandle(WeixinRequest request, WeixinMessage message,
Set<String> nodeNames) throws WeixinException {
return true;
}
@Override
public WeixinResponse doHandle(WeixinRequest request, WeixinMessage message,
Set<String> nodeNames) throws WeixinException {
String content = message == null ? request.getOriginalContent()
.replaceAll("\\!\\[CDATA\\[", "").replaceAll("\\]\\]", "")
: message.toString();
return new TextResponse(content);
}
@Override
public int weight() {
return 0;
}
}

View File

@ -0,0 +1,73 @@
package com.zone.weixin4j.handler;
import com.zone.weixin4j.exception.WeixinException;
import com.zone.weixin4j.request.WeixinMessage;
import com.zone.weixin4j.request.WeixinRequest;
import com.zone.weixin4j.response.WeixinResponse;
import com.zone.weixin4j.util.ClassUtil;
import java.util.Set;
/**
* 消息适配器:对于特定的消息类型进行适配,如text文本voice语音消息
*
* @className MessageHandlerAdapter
* @author jinyu(foxinmy@gmail.com)
* @date 2015年5月17日
* @since JDK 1.6
* @see com.foxinmy.weixin4j.request.WeixinMessage
*/
@SuppressWarnings("unchecked")
public abstract class MessageHandlerAdapter<M extends WeixinMessage> implements
WeixinMessageHandler {
@Override
public boolean canHandle(WeixinRequest request, WeixinMessage message,
Set<String> nodeNames) throws WeixinException {
return message != null
&& message.getClass() == ClassUtil.getGenericType(getClass())
&& canHandle0(request, (M) message);
}
/**
* 能否处理请求
*
* @param request
* 微信请求
* @param message
* 微信消息
* @return true则执行doHandler0
* @throws WeixinException
*/
public boolean canHandle0(WeixinRequest request, M message)
throws WeixinException {
return true;
}
@Override
public WeixinResponse doHandle(WeixinRequest request,
WeixinMessage message, Set<String> nodeNames)
throws WeixinException {
return doHandle0(request, (M) message);
}
/**
* 处理请求
*
* @param request
* 微信请求
* @param message
* 微信消息
* @return
*/
public abstract WeixinResponse doHandle0(WeixinRequest request, M message)
throws WeixinException;
/**
* 缺省值为1,存在多个匹配到的MessageHandler则比较weight大小
*/
@Override
public int weight() {
return 1;
}
}

View File

@ -0,0 +1,58 @@
package com.zone.weixin4j.handler;
import com.zone.weixin4j.exception.WeixinException;
import com.zone.weixin4j.request.WeixinMessage;
import com.zone.weixin4j.request.WeixinRequest;
import java.util.HashSet;
import java.util.Set;
/**
* 多个消息类型适配
*
* @className MultipleMessageHandlerAdapter
* @author jinyu(foxinmy@gmail.com)
* @date 2016年3月12日
* @since JDK 1.6
* @see
*/
public abstract class MultipleMessageHandlerAdapter implements WeixinMessageHandler {
private final Set<Class<? extends WeixinMessage>> messageClasses;
public MultipleMessageHandlerAdapter(Class<? extends WeixinMessage>... messageClasses) {
if (messageClasses == null) {
throw new IllegalArgumentException("messageClasses not be empty");
}
this.messageClasses = new HashSet<Class<? extends WeixinMessage>>(
Math.max((int) (messageClasses.length / .75f) + 1, 16));
for (Class<? extends WeixinMessage> clazz : messageClasses) {
this.messageClasses.add(clazz);
}
}
@Override
public boolean canHandle(WeixinRequest request, WeixinMessage message, Set<String> nodeNames)
throws WeixinException {
return message != null && messageClasses.contains(message.getClass()) && canHandle0(request, message);
}
/**
* 能否处理请求
*
* @param request
* 微信请求
* @param message
* 微信消息
* @return true则执行doHandler
* @throws WeixinException
*/
public boolean canHandle0(WeixinRequest request, WeixinMessage message) throws WeixinException {
return true;
}
@Override
public int weight() {
return 1;
}
}

View File

@ -0,0 +1,56 @@
package com.zone.weixin4j.handler;
import com.zone.weixin4j.exception.WeixinException;
import com.zone.weixin4j.request.WeixinMessage;
import com.zone.weixin4j.request.WeixinRequest;
import com.zone.weixin4j.response.WeixinResponse;
import java.util.Set;
/**
* 微信消息处理器
*
* @className WeixinMessageHandler
* @author jinyu(foxinmy@gmail.com)
* @date 2015年5月7日
* @since JDK 1.6
* @see MessageHandlerAdapter
* @see MultipleMessageHandlerAdapter
*/
public interface WeixinMessageHandler {
/**
* 能否处理请求
*
* @param request
* 微信请求
* @param message
* 微信消息
* @param nodeNames
* 节点名称集合
* @return true则执行doHandle
*/
public boolean canHandle(WeixinRequest request, WeixinMessage message,
Set<String> nodeNames) throws WeixinException;
/**
* 处理请求
*
* @param request
* 微信请求
* @param message
* 微信消息
* @param nodeNames
* 节点名称集合
* @return 回复内容
*/
public WeixinResponse doHandle(WeixinRequest request, WeixinMessage message,
Set<String> nodeNames) throws WeixinException;
/**
* 用于匹配到多个MessageHandler时权重降序排列,数字越大优先级越高
*
* @return 权重
*/
public int weight();
}

View File

@ -0,0 +1,45 @@
package com.zone.weixin4j.interceptor;
import com.zone.weixin4j.exception.WeixinException;
import com.zone.weixin4j.handler.WeixinMessageHandler;
import com.zone.weixin4j.request.WeixinMessage;
import com.zone.weixin4j.request.WeixinRequest;
import com.zone.weixin4j.response.WeixinResponse;
/**
* 消息拦截适配
*
* @author jinyu(foxinmy@gmail.com)
* @className MessageInterceptorAdapter
* @date 2015年5月14日
* @see
* @since JDK 1.6
*/
public abstract class MessageInterceptorAdapter implements
WeixinMessageInterceptor {
@Override
public boolean preHandle(
WeixinRequest request, WeixinMessage message, WeixinMessageHandler handler)
throws WeixinException {
return true;
}
@Override
public void postHandle(
WeixinRequest request, WeixinResponse response, WeixinMessage message,
WeixinMessageHandler handler) throws WeixinException {
}
@Override
public void afterCompletion(
WeixinRequest request, WeixinResponse response, WeixinMessage message,
WeixinMessageHandler handler, Exception exception)
throws WeixinException {
}
@Override
public int weight() {
return 0;
}
}

View File

@ -0,0 +1,66 @@
package com.zone.weixin4j.interceptor;
import com.zone.weixin4j.exception.WeixinException;
import com.zone.weixin4j.handler.WeixinMessageHandler;
import com.zone.weixin4j.request.WeixinMessage;
import com.zone.weixin4j.request.WeixinRequest;
import com.zone.weixin4j.response.WeixinResponse;
/**
* 微信消息拦截器
*
* @author jinyu(foxinmy@gmail.com)
* @className WeixinMessageInterceptor
* @date 2015年5月7日
* @see MessageInterceptorAdapter
* @since JDK 1.6
*/
public interface WeixinMessageInterceptor {
/**
* 执行handler前
*
* @param request 微信请求
* @param message 微信消息
* @param handler 消息处理器
* @return 返回true执行下一个拦截器
* @throws WeixinException
*/
boolean preHandle(WeixinRequest request,
WeixinMessage message, WeixinMessageHandler handler)
throws WeixinException;
/**
* 执行handler后
*
* @param request 微信请求
* @param response 微信响应
* @param message 微信消息
* @param handler 消息处理器
* @throws WeixinException
*/
void postHandle(WeixinRequest request,
WeixinResponse response, WeixinMessage message,
WeixinMessageHandler handler) throws WeixinException;
/**
* 全部执行后
*
* @param request 微信请求
* @param message 微信消息
* @param handler 消息处理器
* @param exception 执行异常
* @throws WeixinException
*/
void afterCompletion(WeixinRequest request,
WeixinResponse response, WeixinMessage message,
WeixinMessageHandler handler, Exception exception)
throws WeixinException;
/**
* 用于匹配到多个MessageHandler时权重降序排列,数字越大优先级越高
*
* @return 权重
*/
int weight();
}

View File

@ -0,0 +1,52 @@
package com.zone.weixin4j.message;
import com.zone.weixin4j.request.WeixinMessage;
import com.zone.weixin4j.type.MessageType;
import javax.xml.bind.annotation.XmlElement;
/**
* 图片消息
*
* @className ImageMessage
* @author jinyu(foxinmy@gmail.com)
* @date 2014年4月6日
* @since JDK 1.6
* @see <a
* href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140453&token=&lang=zh_CN">订阅号服务号的图片消息</a>
* @see <a
* href="http://qydev.weixin.qq.com/wiki/index.php?title=%E6%8E%A5%E6%94%B6%E6%99%AE%E9%80%9A%E6%B6%88%E6%81%AF#image.E6.B6.88.E6.81.AF">企业号的图片消息</a>
*/
public class ImageMessage extends WeixinMessage {
private static final long serialVersionUID = 8430800898756567016L;
public ImageMessage() {
super(MessageType.image.name());
}
/**
* 图片链接
*/
@XmlElement(name = "PicUrl")
private String picUrl;
/**
* 图片消息媒体id可以调用多媒体文件下载接口拉取数据
*/
@XmlElement(name = "MediaId")
private String mediaId;
public String getPicUrl() {
return picUrl;
}
public String getMediaId() {
return mediaId;
}
@Override
public String toString() {
return "ImageMessage [picUrl=" + picUrl + ", mediaId=" + mediaId + ", "
+ super.toString() + "]";
}
}

View File

@ -0,0 +1,63 @@
package com.zone.weixin4j.message;
import com.zone.weixin4j.request.WeixinMessage;
import com.zone.weixin4j.type.MessageType;
import javax.xml.bind.annotation.XmlElement;
/**
* 链接消息
*
* @className LinkMessage
* @author jinyu(foxinmy@gmail.com)
* @date 2014年4月6日
* @since JDK 1.6
* @see <a href=
* "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140453&token=&lang=zh_CN">
* 订阅号服务号的链接消息</a>
* @see <a href=
* "http://qydev.weixin.qq.com/wiki/index.php?title=%E6%8E%A5%E6%94%B6%E6%99%AE%E9%80%9A%E6%B6%88%E6%81%AF#link.E6.B6.88.E6.81.AF">
* 企业号的链接消息</a>
*/
public class LinkMessage extends WeixinMessage {
private static final long serialVersionUID = 754952745115497030L;
public LinkMessage() {
super(MessageType.link.name());
}
/**
* 消息标题
*/
@XmlElement(name = "Title")
private String title;
/**
* 消息描述
*/
@XmlElement(name = "Description")
private String description;
/**
* 消息链接
*/
@XmlElement(name = "Url")
private String url;
public String getTitle() {
return title;
}
public String getDescription() {
return description;
}
public String getUrl() {
return url;
}
@Override
public String toString() {
return "LinkMessage [title=" + title + ", description=" + description + ", url=" + url + ", " + super.toString()
+ "]";
}
}

View File

@ -0,0 +1,70 @@
package com.zone.weixin4j.message;
import com.zone.weixin4j.request.WeixinMessage;
import com.zone.weixin4j.type.MessageType;
import javax.xml.bind.annotation.XmlElement;
/**
* 地理位置消息
*
* @className LocationMessage
* @author jinyu(foxinmy@gmail.com)
* @date 2014年4月6日
* @since JDK 1.6
* @see <a
* href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140453&token=&lang=zh_CN">订阅号服务号的地理位置消息</a>
* @see <a
* href="http://qydev.weixin.qq.com/wiki/index.php?title=%E6%8E%A5%E6%94%B6%E6%99%AE%E9%80%9A%E6%B6%88%E6%81%AF#location.E6.B6.88.E6.81.AF">企业号的地理位置消息</a>
*/
public class LocationMessage extends WeixinMessage {
private static final long serialVersionUID = 2866021596599237334L;
public LocationMessage() {
super(MessageType.location.name());
}
/**
* 地理位置维度
*/
@XmlElement(name = "Location_X")
private double x;
/**
* 地理位置经度
*/
@XmlElement(name = "Location_Y")
private double y;
/**
* 地图缩放大小
*/
@XmlElement(name = "Scale")
private double scale;
/**
* 地理位置信息
*/
@XmlElement(name = "Label")
private String label;
public double getX() {
return x;
}
public double getY() {
return y;
}
public double getScale() {
return scale;
}
public String getLabel() {
return label;
}
@Override
public String toString() {
return "LocationMessage [x=" + x + ", y=" + y + ", scale=" + scale
+ ", label=" + label + ", " + super.toString() + "]";
}
}

View File

@ -0,0 +1,10 @@
普通消息
-------
当普通微信用户向公众账号发消息时微信服务器将POST消息的XML数据包到开发者填写的URL上。
微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次
关于重试的消息排重推荐使用msgid排重。
假如服务器无法保证在五秒内处理并回复,可以直接回复空串,微信服务器不会对此作任何处理,并且不会发起重试。

View File

@ -0,0 +1,43 @@
package com.zone.weixin4j.message;
import com.zone.weixin4j.request.WeixinMessage;
import com.zone.weixin4j.type.MessageType;
import javax.xml.bind.annotation.XmlElement;
/**
* 文本消息
*
* @className TextMessage
* @author jinyu(foxinmy@gmail.com)
* @date 2014年4月6日
* @since JDK 1.6
* @see <a
* href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140453&token=&lang=zh_CN">订阅号服务号的文本消息</a>
* @see <a
* href="http://qydev.weixin.qq.com/wiki/index.php?title=%E6%8E%A5%E6%94%B6%E6%99%AE%E9%80%9A%E6%B6%88%E6%81%AF#text.E6.B6.88.E6.81.AF">企业号的文本消息</a>
*/
public class TextMessage extends WeixinMessage {
private static final long serialVersionUID = -7018053906644190260L;
public TextMessage() {
super(MessageType.text.name());
}
/**
* 消息内容
*/
@XmlElement(name = "Content")
private String content;
public String getContent() {
return content;
}
@Override
public String toString() {
return "TextMessage [content=" + content + ", " + super.toString()
+ "]";
}
}

View File

@ -0,0 +1,52 @@
package com.zone.weixin4j.message;
import com.zone.weixin4j.request.WeixinMessage;
import com.zone.weixin4j.type.MessageType;
import javax.xml.bind.annotation.XmlElement;
/**
* 视频消息
*
* @className VideoMessage
* @author jinyu(foxinmy@gmail.com)
* @date 2014年4月6日
* @since JDK 1.6
* @see <a
* href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140453&token=&lang=zh_CN">订阅号服务号的视频消息</a>
* @see <a
* href="http://qydev.weixin.qq.com/wiki/index.php?title=%E6%8E%A5%E6%94%B6%E6%99%AE%E9%80%9A%E6%B6%88%E6%81%AF#video.E6.B6.88.E6.81.AF">企业号的视频消息</a>
*/
public class VideoMessage extends WeixinMessage {
private static final long serialVersionUID = -1013075358679078381L;
public VideoMessage() {
super(MessageType.video.name());
}
/**
* 视频消息媒体id可以调用多媒体文件下载接口拉取数据
*/
@XmlElement(name = "MediaId")
private String mediaId;
/**
* 视频消息缩略图的媒体id可以调用多媒体文件下载接口拉取数据
*/
@XmlElement(name = "ThumbMediaId")
private String thumbMediaId;
public String getMediaId() {
return mediaId;
}
public String getThumbMediaId() {
return thumbMediaId;
}
@Override
public String toString() {
return "VideoMessage [mediaId=" + mediaId + ", thumbMediaId="
+ thumbMediaId + ", " + super.toString() + "]";
}
}

View File

@ -0,0 +1,65 @@
package com.zone.weixin4j.message;
import com.zone.weixin4j.request.WeixinMessage;
import com.zone.weixin4j.type.MessageType;
import javax.xml.bind.annotation.XmlElement;
/**
* 语音消息
* <p>
* 开通语音识别功能,用户每次发送语音给公众号时,微信会在推送的语音消息XML数据包中,赋值到Recongnition字段.
* </p>
*
* @className VoiceMessage
* @author jinyu(foxinmy@gmail.com)
* @date 2014年4月6日
* @since JDK 1.6
* @see <a
* href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140453&token=&lang=zh_CN">订阅号服务号的语音消息</a>
* @see <a
* href="http://qydev.weixin.qq.com/wiki/index.php?title=%E6%8E%A5%E6%94%B6%E6%99%AE%E9%80%9A%E6%B6%88%E6%81%AF#voice.E6.B6.88.E6.81.AF">企业号的语音消息</a>
*/
public class VoiceMessage extends WeixinMessage {
private static final long serialVersionUID = -7988380977182214003L;
public VoiceMessage() {
super(MessageType.voice.name());
}
/**
* 语音消息媒体id可以调用多媒体文件下载接口拉取数据
*/
@XmlElement(name = "MediaId")
private String mediaId;
/**
* 语音格式如amrspeex等
*/
@XmlElement(name = "Format")
private String format;
/**
* 语音识别结果UTF8编码
*/
@XmlElement(name = "Recognition")
private String recognition;
public String getRecognition() {
return recognition;
}
public String getMediaId() {
return mediaId;
}
public String getFormat() {
return format;
}
@Override
public String toString() {
return "VoiceMessage [mediaId=" + mediaId + ", format=" + format
+ ", recognition=" + recognition + ", " + super.toString()
+ "]";
}
}

View File

@ -0,0 +1,49 @@
package com.zone.weixin4j.message.event;
import com.zone.weixin4j.request.WeixinMessage;
import com.zone.weixin4j.type.MessageType;
import javax.xml.bind.annotation.XmlElement;
/**
* 事件消息基类
*
* @className EventMessage
* @author jinyu(foxinmy@gmail.com)
* @date 2014年4月6日
* @since JDK 1.6
* @see <a
* href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140454&token=&lang=zh_CN">订阅号服务号的事件推送</a>
* @see <a
* href="http://qydev.weixin.qq.com/wiki/index.php?title=%E6%8E%A5%E6%94%B6%E4%BA%8B%E4%BB%B6">企业号的事件消息</a>
*/
public class EventMessage extends WeixinMessage {
private static final long serialVersionUID = 7703667223814088865L;
protected EventMessage() {
// jaxb requried
}
public EventMessage(String eventType) {
super(MessageType.event.name());
this.eventType = eventType;
}
/**
* 事件类型
*
* @see com.foxinmy.weixin4j.type.EventType
*/
@XmlElement(name = "Event")
private String eventType;
public String getEventType() {
return eventType;
}
@Override
public String toString() {
return "eventType=" + eventType + ", " + super.toString();
}
}

View File

@ -0,0 +1,60 @@
package com.zone.weixin4j.message.event;
import com.zone.weixin4j.type.EventType;
import javax.xml.bind.annotation.XmlElement;
/**
* 上报地理位置事件
*
* @className LocationEventMessage
* @author jinyu(foxinmy@gmail.com)
* @date 2014年4月6日
* @since JDK 1.6
* @see <a
* href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140454&token=&lang=zh_CN">订阅号服务号的上报地理位置事件</a>
* @see <a
* href="http://qydev.weixin.qq.com/wiki/index.php?title=%E6%8E%A5%E6%94%B6%E4%BA%8B%E4%BB%B6#.E4.B8.8A.E6.8A.A5.E5.9C.B0.E7.90.86.E4.BD.8D.E7.BD.AE.E4.BA.8B.E4.BB.B6">企业号的上报地理位置事件</a>
*/
public class LocationEventMessage extends EventMessage {
private static final long serialVersionUID = -2030716800669824861L;
public LocationEventMessage() {
super(EventType.location.name());
}
/**
* 地理位置纬度
*/
@XmlElement(name="Latitude")
private double latitude;
/**
* 地理位置经度
*/
@XmlElement(name="Longitude")
private double longitude;
/**
* 地理位置精度
*/
@XmlElement(name="Precision")
private double precision;
public double getLatitude() {
return latitude;
}
public double getLongitude() {
return longitude;
}
public double getPrecision() {
return precision;
}
@Override
public String toString() {
return "LocationEventMessage [latitude=" + latitude + ", longitude="
+ longitude + ", precision=" + precision + ", "
+ super.toString() + "]";
}
}

View File

@ -0,0 +1,55 @@
package com.zone.weixin4j.message.event;
import com.zone.weixin4j.type.EventType;
import javax.xml.bind.annotation.XmlElement;
/**
* 自定义菜单事件(view|click)
*
* @className MenuEventMessage
* @author jinyu(foxinmy@gmail.com)
* @date 2014年4月6日
* @since JDK 1.6
* @see <a
* href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140454&token=&lang=zh_CN">订阅号服务号的菜单事件</a>
* @see <a
* href="http://qydev.weixin.qq.com/wiki/index.php?title=%E6%8E%A5%E6%94%B6%E4%BA%8B%E4%BB%B6#.E4.B8.8A.E6.8A.A5.E8.8F.9C.E5.8D.95.E4.BA.8B.E4.BB.B6">企业号的菜单事件</a>
*/
public class MenuEventMessage extends EventMessage {
private static final long serialVersionUID = -1049672447995366063L;
public MenuEventMessage() {
super(EventType.click.name());
}
public MenuEventMessage(EventType eventType) {
super(eventType.name());
}
/**
* 事件KEY值与自定义菜单接口中KEY值对应
*/
@XmlElement(name = "EventKey")
private String eventKey;
/**
* 指菜单ID如果是个性化菜单则可以通过这个字段知道是哪个规则的菜单被点击了
*/
@XmlElement(name = "MenuID")
private String menuId;
public String getEventKey() {
return eventKey;
}
public String getMenuId() {
return menuId;
}
@Override
public String toString() {
return "MenuEventMessage [eventKey=" + eventKey + ", menuId=" + menuId
+ ", " + super.toString() + "]";
}
}

View File

@ -0,0 +1,109 @@
package com.zone.weixin4j.message.event;
import com.zone.weixin4j.type.EventType;
import javax.xml.bind.annotation.XmlElement;
import java.io.Serializable;
/**
* 弹出地理位置选择器的事件推送
*
* @className MenuLocationEventMessage
* @author jinyu(foxinmy@gmail.com)
* @date 2014年9月30日
* @since JDK 1.6
* @see <a
* href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140454&token=&lang=zh_CN">订阅号服务号的弹出地理位置选择事件推送</a>
* @see <a
* href="http://qydev.weixin.qq.com/wiki/index.php?title=%E6%8E%A5%E6%94%B6%E4%BA%8B%E4%BB%B6#.E5.BC.B9.E5.87.BA.E5.9C.B0.E7.90.86.E4.BD.8D.E7.BD.AE.E9.80.89.E6.8B.A9.E5.99.A8.E7.9A.84.E4.BA.8B.E4.BB.B6.E6.8E.A8.E9.80.81">企业号的弹出地理位置选择事件推送</a>
*/
public class MenuLocationEventMessage extends MenuEventMessage {
private static final long serialVersionUID = 145223888272819563L;
public MenuLocationEventMessage() {
super(EventType.location_select);
}
/**
* 发送的位置消息
*/
@XmlElement(name = "SendLocationInfo")
private LocationInfo locationInfo;
public LocationInfo getLocationInfo() {
return locationInfo;
}
/**
* 地理位置信息
*
* @className LocationInfo
* @author jinyu(foxinmy@gmail.com)
* @date 2015年3月29日
* @since JDK 1.6
* @see
*/
public static class LocationInfo implements Serializable {
private static final long serialVersionUID = 4904181780216819965L;
/**
* 地理位置维度
*/
@XmlElement(name = "Location_X")
private double x;
/**
* 地理位置经度
*/
@XmlElement(name = "Location_Y")
private double y;
/**
* 地图缩放大小
*/
@XmlElement(name = "Scale")
private double scale;
/**
* 地理位置信息
*/
@XmlElement(name = "Label")
private String label;
/**
* 朋友圈POI的名字可能为空
*/
@XmlElement(name = "Poiname")
private String poiname;
public double getX() {
return x;
}
public double getY() {
return y;
}
public double getScale() {
return scale;
}
public String getLabel() {
return label;
}
public String getPoiname() {
return poiname;
}
@Override
public String toString() {
return "LocationInfo [x=" + x + ", y=" + y + ", scale=" + scale
+ ", label=" + label + ", poiname=" + poiname + "]";
}
}
@Override
public String toString() {
return "MenuLocationEventMessage [locationInfo=" + locationInfo + ", "
+ super.toString() + "]";
}
}

View File

@ -0,0 +1,107 @@
package com.zone.weixin4j.message.event;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import java.io.Serializable;
import java.util.List;
/**
* 弹出拍照或者相册发图的事件推送(pic_sysphoto|pic_photo_or_album|pic_weixin)
*
* @className MenuPhotoEventMessage
* @author jinyu(foxinmy@gmail.com)
* @date 2014年9月30日
* @since JDK 1.6
* @see <a
* href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140454&token=&lang=zh_CN">订阅号服务号的系统发图的事件推送</a>
* @see <a
* href="http://qydev.weixin.qq.com/wiki/index.php?title=%E6%8E%A5%E6%94%B6%E4%BA%8B%E4%BB%B6#.E5.BC.B9.E5.87.BA.E7.B3.BB.E7.BB.9F.E6.8B.8D.E7.85.A7.E5.8F.91.E5.9B.BE.E7.9A.84.E4.BA.8B.E4.BB.B6.E6.8E.A8.E9.80.81">企业号的系统发图的事件推送</a>
*/
public class MenuPhotoEventMessage extends MenuEventMessage {
private static final long serialVersionUID = 3142350663022709730L;
/**
* 发送的图片信息
*/
@XmlElement(name = "SendPicsInfo")
private PictureInfo pictureInfo;
public PictureInfo getPictureInfo() {
return pictureInfo;
}
/**
* 图片信息
*
* @className PictureInfo
* @author jinyu(foxinmy@gmail.com)
* @date 2015年3月29日
* @since JDK 1.6
* @see
*/
public static class PictureInfo implements Serializable {
private static final long serialVersionUID = -3361375879168233258L;
/**
* 发送的图片数量
*/
@XmlElement(name = "Count")
private int count;
/**
* 图片列表
*/
@XmlElementWrapper(name = "PicList")
@XmlElement(name = "item")
private List<PictureItem> items;
public int getCount() {
return count;
}
public List<PictureItem> getItems() {
return items;
}
@Override
public String toString() {
return "PictureInfo [count=" + count + ", items=" + items + "]";
}
}
/**
* 图片
*
* @className PictureItem
* @author jinyu(foxinmy@gmail.com)
* @date 2015年3月29日
* @since JDK 1.6
* @see
*/
public static class PictureItem implements Serializable {
private static final long serialVersionUID = -7636697449096645590L;
/**
* 图片的MD5值开发者若需要可用于验证接收到图片
*/
@XmlElement(name = "PicMd5Sum")
private String md5;
public String getMd5() {
return md5;
}
@Override
public String toString() {
return "PictureItem [md5=" + md5 + "]";
}
}
@Override
public String toString() {
return "MenuPhotoEventMessage [pictureInfo=" + pictureInfo + ", "
+ super.toString() + "]";
}
}

View File

@ -0,0 +1,74 @@
package com.zone.weixin4j.message.event;
import javax.xml.bind.annotation.XmlElement;
import java.io.Serializable;
/**
* 扫码推事件(scancode_push|scancode_waitmsg)
*
* @className MenuScanEventMessage
* @author jinyu(foxinmy@gmail.com)
* @date 2014年9月30日
* @since JDK 1.6
* @see <a
* href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140454&token=&lang=zh_CN">订阅号服务号的扫码推事件</a>
* @see <a
* href="http://qydev.weixin.qq.com/wiki/index.php?title=%E6%8E%A5%E6%94%B6%E4%BA%8B%E4%BB%B6#.E6.89.AB.E7.A0.81.E6.8E.A8.E4.BA.8B.E4.BB.B6.E7.9A.84.E4.BA.8B.E4.BB.B6.E6.8E.A8.E9.80.81">企业号的的扫码推事件</a>
*/
public class MenuScanEventMessage extends MenuEventMessage {
private static final long serialVersionUID = 3142350663022709730L;
/**
* 扫描信息
*/
@XmlElement(name = "ScanCodeInfo")
private ScanInfo scanInfo;
public ScanInfo getScanInfo() {
return scanInfo;
}
/**
* 扫描信息
*
* @className ScanInfo
* @author jinyu(foxinmy@gmail.com)
* @date 2015年3月29日
* @since JDK 1.6
* @see
*/
public static class ScanInfo implements Serializable {
private static final long serialVersionUID = 2237570238164900421L;
/**
* 扫描类型一般是qrcode
*/
@XmlElement(name = "ScanType")
private String type;
/**
* 扫描结果即二维码对应的字符串信息
*/
@XmlElement(name = "ScanResult")
private String result;
public String getType() {
return type;
}
public String getResult() {
return result;
}
@Override
public String toString() {
return "ScanInfo [type=" + type + ", result=" + result + "]";
}
}
@Override
public String toString() {
return "MenuScanEventMessage [scanInfo=" + scanInfo + ", "
+ super.toString() + "]";
}
}

View File

@ -0,0 +1,3 @@
菜单事件消息
用户点击自定义菜单后微信会把点击事件推送给开发者请注意点击菜单弹出子菜单不会产生上报。请注意第3个到第8个的所有事件仅支持微信iPhone5.4.1以上版本和Android5.4以上版本的微信用户,旧版本微信用户点击后将没有回应,开发者也不能正常接收到事件推送。

View File

@ -0,0 +1,28 @@
package com.zone.weixin4j.mp.component;
/**
* 应用组件回调事件
*
* @className ComponentEventType
* @author jinyu(foxinmy@gmail.com)
* @date 2016年7月5日
* @since JDK 1.6
*/
public enum ComponentEventType {
/**
* 推送ticket
*/
component_verify_ticket,
/**
* 取消授权
*/
unauthorized,
/**
* 授权成功
*/
authorized,
/**
* 授权更新
*/
updateauthorized
}

View File

@ -0,0 +1,105 @@
package com.zone.weixin4j.mp.component;
import javax.xml.bind.annotation.*;
import java.io.Serializable;
import java.util.Date;
/**
* 组件消息
*
* @className ComponentMessage
* @author jinyu(foxinmy@gmail.com)
* @date 2016年7月5日
* @since JDK 1.6
*/
@XmlRootElement(name = "xml")
@XmlAccessorType(XmlAccessType.FIELD)
public class ComponentMessage implements Serializable {
private static final long serialVersionUID = -7243616276403632118L;
/**
* 第三方平台appid
*/
@XmlElement(name = "AppId")
private String appId;
/**
* 事件类型
*/
@XmlElement(name = "InfoType")
private String eventType;
/**
* 时间戳
*/
@XmlElement(name = "CreateTime")
private long createTime;
/**
* Ticket内容
*/
@XmlElement(name = "ComponentVerifyTicket")
private String verifyTicket;
/**
* 授权方的Appid
*/
@XmlElement(name = "AuthorizerAppid")
private String authAppId;
/**
* 授权码可用于换取公众号的接口调用凭据
*/
@XmlElement(name = "AuthorizationCode")
private String authCode;
/**
* 授权码过期时间
*/
@XmlElement(name = "AuthorizationCodeExpiredTime")
private long authCodeExpiredTime;
public String getAppId() {
return appId;
}
public String getEventType() {
return eventType;
}
@XmlTransient
public ComponentEventType getFormatEventType() {
return ComponentEventType.valueOf(eventType);
}
public long getCreateTime() {
return createTime;
}
@XmlTransient
public Date getFormatCreateTime() {
return createTime > 0l ? new Date(createTime * 1000l) : null;
}
public String getVerifyTicket() {
return verifyTicket;
}
public String getAuthAppId() {
return authAppId;
}
public String getAuthCode() {
return authCode;
}
public long getAuthCodeExpiredTime() {
return authCodeExpiredTime;
}
@XmlTransient
public Date getFormatAuthCodeExpiredTime() {
return authCodeExpiredTime > 0l ? new Date(authCodeExpiredTime * 1000l) : null;
}
@Override
public String toString() {
return "ComponentMessage [appId=" + appId + ", eventType=" + eventType + ", createTime=" + createTime
+ ", verifyTicket=" + verifyTicket + ", authAppId=" + authAppId + ", authCode=" + authCode
+ ", authCodeExpiredTime=" + authCodeExpiredTime + "]";
}
}

View File

@ -0,0 +1,41 @@
package com.zone.weixin4j.mp.event;
import com.zone.weixin4j.message.event.EventMessage;
import com.zone.weixin4j.type.EventType;
import javax.xml.bind.annotation.XmlElement;
/**
* 客服关闭会话事件
*
* @className KfCloseEventMessage
* @author jinyu(foxinmy@gmail.com)
* @date 2015年3月22日
* @since JDK 1.6
* @see <a
* href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1455864026&token=&lang=zh_CN">会话状态通知事件</a>
*/
public class KfCloseEventMessage extends EventMessage {
private static final long serialVersionUID = 3644449346935205541L;
public KfCloseEventMessage() {
super(EventType.kf_close_session.name());
}
/**
* 客服账号
*/
@XmlElement(name = "KfAccount")
private String kfAccount;
public String getKfAccount() {
return kfAccount;
}
@Override
public String toString() {
return "KfCloseEventMessage [kfAccount=" + kfAccount + ", ="
+ super.toString() + "]";
}
}

View File

@ -0,0 +1,41 @@
package com.zone.weixin4j.mp.event;
import com.zone.weixin4j.message.event.EventMessage;
import com.zone.weixin4j.type.EventType;
import javax.xml.bind.annotation.XmlElement;
/**
* 客服接入会话事件
*
* @className KfCreateEventMessage
* @author jinyu(foxinmy@gmail.com)
* @date 2015年3月22日
* @since JDK 1.6
* @see <a
* href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1455864026&token=&lang=zh_CN">会话状态通知事件</a>
*/
public class KfCreateEventMessage extends EventMessage {
private static final long serialVersionUID = -8968189700999202108L;
public KfCreateEventMessage() {
super(EventType.kf_create_session.name());
}
/**
* 客服账号
*/
@XmlElement(name = "KfAccount")
private String kfAccount;
public String getKfAccount() {
return kfAccount;
}
@Override
public String toString() {
return "KfCreateEventMessage [kfAccount=" + kfAccount + ", ="
+ super.toString() + "]";
}
}

View File

@ -0,0 +1,50 @@
package com.zone.weixin4j.mp.event;
import com.zone.weixin4j.message.event.EventMessage;
import com.zone.weixin4j.type.EventType;
import javax.xml.bind.annotation.XmlElement;
/**
* 客服转接会话事件
*
* @className KfSwitchEventMessage
* @author jinyu(foxinmy@gmail.com)
* @date 2015年3月22日
* @since JDK 1.6
* @see <a
* href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1455864026&token=&lang=zh_CN">会话状态通知事件</a>
*/
public class KfSwitchEventMessage extends EventMessage {
private static final long serialVersionUID = 4319501074109623413L;
public KfSwitchEventMessage() {
super(EventType.kf_switch_session.name());
}
/**
* 来自的客服账号
*/
@XmlElement(name = "FromKfAccount")
private String fromKfAccount;
/**
* 转移给客服账号
*/
@XmlElement(name = "ToKfAccount")
private String toKfAccount;
public String getFromKfAccount() {
return fromKfAccount;
}
public String getToKfAccount() {
return toKfAccount;
}
@Override
public String toString() {
return "KfSwitchEventMessage [fromKfAccount=" + fromKfAccount
+ ", toKfAccount=" + toKfAccount + "]";
}
}

View File

@ -0,0 +1,80 @@
package com.zone.weixin4j.mp.event;
import com.zone.weixin4j.message.event.EventMessage;
import com.zone.weixin4j.type.EventType;
import javax.xml.bind.annotation.XmlElement;
/**
* 群发消息事件推送
*
* @className MassEventMessage
* @author jinyu(foxinmy@gmail.com)
* @date 2014年4月27日
* @since JDK 1.6
* @see <a
* href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN">群发回调</a>
*/
public class MassEventMessage extends EventMessage {
private static final long serialVersionUID = -1660543255873723895L;
public MassEventMessage() {
super(EventType.masssendjobfinish.name());
}
/**
* 群发后的状态信息 send successsend failerr(num)
*/
@XmlElement(name = "Status")
private String status;
/**
* group_id下粉丝数或者openid_list中的粉丝数
*/
@XmlElement(name = "TotalCount")
private int totalCount;
/**
* 过滤过滤是指特定地区性别的过滤用户设置拒收的过滤用户接收已超4条的过滤准备发送的粉丝数原则上FilterCount =
* SentCount + ErrorCount
*/
@XmlElement(name = "FilterCount")
private int filterCount;
/**
* 发送成功的粉丝数
*/
@XmlElement(name = "SentCount")
private int sentCount;
/**
* 发送失败的粉丝数
*/
@XmlElement(name = "ErrorCount")
private int errorCount;
public String getStatus() {
return status;
}
public int getTotalCount() {
return totalCount;
}
public int getFilterCount() {
return filterCount;
}
public int getSentCount() {
return sentCount;
}
public int getErrorCount() {
return errorCount;
}
@Override
public String toString() {
return "MassEventMessage [status=" + status + ", totalCount="
+ totalCount + ", filterCount=" + filterCount + ", sentCount="
+ sentCount + ", errorCount=" + errorCount + ", "
+ super.toString() + "]";
}
}

View File

@ -0,0 +1,60 @@
package com.zone.weixin4j.mp.event;
import com.zone.weixin4j.message.event.EventMessage;
import com.zone.weixin4j.type.EventType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlTransient;
/**
* 扫描二维码事件
*
* @className ScanEventMessage
* @author jinyu(foxinmy@gmail.com)
* @date 2014年4月6日
* @since JDK 1.6
* @see <a
* href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140454&token=&lang=zh_CN">扫描二维码事件</a>
*/
public class ScanEventMessage extends EventMessage {
private static final long serialVersionUID = 8078674062833071562L;
public ScanEventMessage() {
super(EventType.scan.name());
}
public ScanEventMessage(String eventType) {
super(eventType);
}
/**
* 事件KEY值是一个32位无符号整数即创建二维码时的二维码scene_id
*/
@XmlElement(name = "EventKey")
private String eventKey;
/**
* 二维码的ticket可用来换取二维码图片
*/
@XmlElement(name = "Ticket")
private String ticket;
public String getEventKey() {
return eventKey;
}
public String getTicket() {
return ticket;
}
@XmlTransient
public String getParameter() {
return eventKey.replaceFirst("qrscene_", "");
}
@Override
public String toString() {
return "ScanEventMessage [eventKey=" + eventKey + ", ticket=" + ticket
+ ", " + super.toString() + "]";
}
}

View File

@ -0,0 +1,27 @@
package com.zone.weixin4j.mp.event;
import com.zone.weixin4j.type.EventType;
/**
* 关注/取消关注事件</br> <font color="red">包括直接关注与扫描关注</font>
*
* @className ScribeEventMessage
* @author jinyu(foxinmy@gmail.com)
* @date 2014年4月6日
* @since JDK 1.6
* @see <a
* href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140454&token=&lang=zh_CN">订阅号服务号的关注/取消关注事件</a>
*/
public class ScribeEventMessage extends ScanEventMessage {
private static final long serialVersionUID = -6846321620262204915L;
public ScribeEventMessage() {
super(EventType.subscribe.name());
}
@Override
public String toString() {
return "ScribeEventMessage [" + super.toString() + "]";
}
}

View File

@ -0,0 +1,41 @@
package com.zone.weixin4j.mp.event;
import com.zone.weixin4j.message.event.EventMessage;
import com.zone.weixin4j.type.EventType;
import javax.xml.bind.annotation.XmlElement;
/**
* 模板消息事件推送(公众平台)
*
* @className TemplatesendjobfinishMessage
* @author jinyu(foxinmy@gmail.com)
* @date 2014年9月19日
* @since JDK 1.6
* @see <a
* href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1433751277&token=&lang=zh_CN">模板消息事件推送</a>
*/
public class TemplatesendjobfinishMessage extends EventMessage {
private static final long serialVersionUID = -2903359365988594012L;
public TemplatesendjobfinishMessage() {
super(EventType.templatesendjobfinish.name());
}
/**
* 推送状态 如failed: system failed
*/
@XmlElement(name = "Status")
private String status;
public String getStatus() {
return status;
}
@Override
public String toString() {
return "TemplatesendjobfinishMessage [status=" + status + ", "
+ super.toString() + "]";
}
}

View File

@ -0,0 +1,48 @@
package com.zone.weixin4j.mp.event;
import com.zone.weixin4j.message.event.EventMessage;
import com.zone.weixin4j.type.EventType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlTransient;
import java.util.Date;
/**
* 认证通知(资质认证成功/名称认证成功/年审通知/认证过期失效通知)
*
* @className VerifyExpireEventMessage
* @author jinyu(foxinmy@gmail.com)
* @date 2015年10月25日
* @since JDK 1.6
* @see <a
* href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1455785130&token=&lang=zh_CN">认证事件</a>
*/
public class VerifyExpireEventMessage extends EventMessage {
private static final long serialVersionUID = -4309074299189681095L;
public VerifyExpireEventMessage() {
super(EventType.annual_renew.name());
}
/**
* 有效期 (整形)指的是时间戳将于该时间戳认证过期
*/
@XmlElement(name = "EventKey")
private long expiredTime;
public long getExpiredTime() {
return expiredTime;
}
@XmlTransient
public Date getFormatExpiredTime() {
return new Date(expiredTime * 1000l);
}
@Override
public String toString() {
return "VerifyExpireEventMessage [expiredTime=" + expiredTime + ", "
+ super.toString() + "]";
}
}

View File

@ -0,0 +1,54 @@
package com.zone.weixin4j.mp.event;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlTransient;
import java.util.Date;
/**
* 认证失败事件(资质认证失败/名称认证失败)
*
* @className VerifyFailEventMessage
* @author jinyu(foxinmy@gmail.com)
* @date 2015年10月25日
* @since JDK 1.6
* @see <a href=
* "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1455785130&token=&lang=zh_CN">
* 认证事件</a>
*/
public class VerifyFailEventMessage extends VerifyExpireEventMessage {
/**
*
*/
private static final long serialVersionUID = 2155899086751787490L;
/**
* 失败发生时间 (整形)时间戳
*/
@XmlElement(name = "FailTime")
private long failTime;
/**
* 认证失败的原因
*/
@XmlElement(name = "FailReason")
private String failReason;
public long getFailTime() {
return failTime;
}
@XmlTransient
public Date getFormatFailTime() {
return new Date(failTime * 1000l);
}
public String getFailReason() {
return failReason;
}
@Override
public String toString() {
return "VerifyFailEventMessage [failTime=" + failTime + ", failReason=" + failReason + ", " + super.toString()
+ "]";
}
}

View File

@ -0,0 +1,33 @@
package com.zone.weixin4j.qy.chat;
/**
* 会话事件
*
* @className ChatEventType
* @author jinyu(foxinmy@gmail.com)
* @date 2015年8月1日
* @since JDK 1.6
* @see
*/
public enum ChatEventType {
/**
* 创建会话
*/
create_chat,
/**
* 修改会话
*/
update_chat,
/**
* 退出会话
*/
quit_chat,
/**
* 订阅事件
*/
subscribe,
/**
* 取消订阅事件
*/
unsubscribe;
}

View File

@ -0,0 +1,240 @@
package com.zone.weixin4j.qy.chat;
import com.zone.weixin4j.type.MessageType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlTransient;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
/**
* 会话事件或消息
*
* @className ChatItem
* @author jinyu(foxinmy@gmail.com)
* @date 2015年8月1日
* @since JDK 1.6
* @see
*/
public class ChatItem implements Serializable {
private static final long serialVersionUID = -5921235260175596270L;
private final String LIST_SEPARATOR = "\\|";
/**
* 操作成员UserID
*/
@XmlElement(name = "FromUserName")
private String operatorId;
/**
* 消息创建时间整型
*/
@XmlElement(name = "CreateTime")
private long createTime;
/**
* 消息类型
*
*/
@XmlElement(name = "MsgType")
private String msgType;
/**
* 事件类型
*/
@XmlElement(name = "Event")
private String eventType;
/**
* 会话id
*/
@XmlElement(name = "ChatId")
private String chatId;
/**
* 会话标题
*/
@XmlElement(name = "Name")
private String chatName;
/**
* 管理员userid
*/
@XmlElement(name = "Owner")
private String ownerId;
/**
* 会话成员列表
*/
@XmlElement(name = "UserList")
private String members;
/**
* 会话新增成员列表
*/
@XmlElement(name = "AddUserList")
private String addMembers;
/**
* 会话删除成员列表
*/
@XmlElement(name = "DelUserList")
private String deleteMembers;
/**
* 消息ID 64位整型
*/
@XmlElement(name = "MsgId")
private long msgId;
/**
* 接收人
*/
@XmlElement(name = "Receiver")
private ChatReceiver receiver;
/**
* 文本消息内容
*/
@XmlElement(name = "Content")
private String content;
/**
* 图片消息链接
*/
@XmlElement(name = "PicUrl")
private String picUrl;
/**
* 链接消息标题
*/
@XmlElement(name = "Title")
private String title;
/**
* 链接消息描述
*/
@XmlElement(name = "Description")
private String description;
/**
* 链接消息链接
*/
@XmlElement(name = "Url")
private String url;
/**
* 图片语音文件消息的媒体id可以调用获取媒体文件接口拉取数据
*/
@XmlElement(name = "MediaId")
private String mediaId;
public String getOperatorId() {
return operatorId;
}
public long getCreateTime() {
return createTime;
}
@XmlTransient
public Date getFormatCreateTime() {
return createTime > 0l ? new Date(createTime * 1000l) : null;
}
public String getMsgType() {
return msgType;
}
@XmlTransient
public MessageType getFormatMsgType() {
return msgType != null ? MessageType.valueOf(msgType) : null;
}
public String getEventType() {
return eventType;
}
@XmlTransient
public ChatEventType getFormatEventType() {
return eventType != null ? ChatEventType.valueOf(eventType) : null;
}
public String getChatId() {
return chatId;
}
public String getChatName() {
return chatName;
}
public String getOwnerId() {
return ownerId;
}
public String getMembers() {
return members;
}
@XmlTransient
public List<String> getFormatMembers() {
return members != null ? Arrays.asList(members.split(LIST_SEPARATOR))
: null;
}
public String getAddMembers() {
return addMembers;
}
@XmlTransient
public List<String> getFormatAddMembers() {
return addMembers != null ? Arrays.asList(addMembers
.split(LIST_SEPARATOR)) : null;
}
public String getDeleteMembers() {
return deleteMembers;
}
@XmlTransient
public List<String> getFormatDeleteMembers() {
return deleteMembers != null ? Arrays.asList(deleteMembers
.split(LIST_SEPARATOR)) : null;
}
public long getMsgId() {
return msgId;
}
public ChatReceiver getReceiver() {
return receiver;
}
public String getContent() {
return content;
}
public String getPicUrl() {
return picUrl;
}
public String getMediaId() {
return mediaId;
}
public static long getSerialversionuid() {
return serialVersionUID;
}
public String getTitle() {
return title;
}
public String getDescription() {
return description;
}
public String getUrl() {
return url;
}
@Override
public String toString() {
return "ChatItem [operatorId=" + operatorId + ", createTime="
+ createTime + ", msgType=" + msgType + ", eventType="
+ eventType + ", chatId=" + chatId + ", chatName=" + chatName
+ ", ownerId=" + ownerId + ", members=" + members
+ ", addMembers=" + addMembers + ", deleteMembers="
+ deleteMembers + ", msgId=" + msgId + ", receiver=" + receiver
+ ", content=" + content + ", picUrl=" + picUrl + ", title="
+ title + ", description=" + description + ", url=" + url
+ ", mediaId=" + mediaId + "]";
}
}

View File

@ -0,0 +1,49 @@
package com.zone.weixin4j.qy.chat;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlTransient;
import java.io.Serializable;
/**
* 接收人
*
* @className ChatReceiver
* @author jinyu(foxinmy@gmail.com)
* @date 2015年8月1日
* @since JDK 1.6
* @see
*/
public class ChatReceiver implements Serializable {
private static final long serialVersionUID = -3870813624685620828L;
/**
* 成员id|会话id
*/
@XmlElement(name = "id")
private String targetId;
/**
* 群聊|单聊|userid|openid
*/
@XmlElement(name = "type")
private String chatType;
public String getTargetId() {
return targetId;
}
public String getChatType() {
return chatType;
}
@XmlTransient
public ChatType getFormatChatType() {
return ChatType.valueOf(chatType);
}
@Override
public String toString() {
return "ChatReceiver [targetId=" + targetId + ", chatType=" + chatType
+ "]";
}
}

View File

@ -0,0 +1,21 @@
package com.zone.weixin4j.qy.chat;
/**
* 会话类型
*
* @className ChatType
* @author jinyu(foxinmy@gmail.com)
* @date 2015年7月31日
* @since JDK 1.6
* @see
*/
public enum ChatType {
/**
* 单聊
*/
single,
/**
* 群聊
*/
group
}

View File

@ -0,0 +1,81 @@
package com.zone.weixin4j.qy.chat;
import com.zone.weixin4j.type.AgentType;
import javax.xml.bind.annotation.*;
import java.io.Serializable;
import java.util.List;
/**
* 企业号聊天服务回调消息
*
* @className WeixinChatMessage
* @author jinyu(foxinmy@gmail.com)
* @date 2015年8月1日
* @since JDK 1.6
* @see
*/
@XmlRootElement(name = "xml")
@XmlAccessorType(XmlAccessType.FIELD)
public class WeixinChatMessage implements Serializable {
private static final long serialVersionUID = 6788124387186831643L;
/**
* 企业号CorpID
*/
@XmlElement(name = "ToUserName")
private String corpId;
/**
* 应用类型
*/
@XmlElement(name = "AgentType")
private String agentType;
/**
* 消息数量
*/
@XmlElement(name = "ItemCount")
private int itemCount;
/**
* 会话事件或消息
*/
@XmlElement(name = "Item")
public List<ChatItem> items;
/**
* 回调包IDuint64类型企业内唯一
*/
@XmlElement(name = "PackageId")
private String packageId;
public String getCorpId() {
return corpId;
}
public String getAgentType() {
return agentType;
}
@XmlTransient
public AgentType getFormatAgentType() {
return AgentType.valueOf(agentType);
}
public int getItemCount() {
return itemCount;
}
public List<ChatItem> getItems() {
return items;
}
public String getPackageId() {
return packageId;
}
@Override
public String toString() {
return "WeixinChatMessage [corpId=" + corpId + ", agentType="
+ agentType + ", itemCount=" + itemCount + ", items=" + items
+ ", packageId=" + packageId + "]";
}
}

View File

@ -0,0 +1,100 @@
package com.zone.weixin4j.qy.event;
import com.zone.weixin4j.message.event.EventMessage;
import com.zone.weixin4j.type.EventType;
import javax.xml.bind.annotation.XmlElement;
import java.io.Serializable;
/**
* 异步任务事件完成通知
*
* @className BatchjobresultMessage
* @author jinyu(foxinmy@gmail.com)
* @date 2015年3月31日
* @since JDK 1.6
* @see <a
* href="http://qydev.weixin.qq.com/wiki/index.php?title=%E6%8E%A5%E6%94%B6%E4%BA%8B%E4%BB%B6#.E5.BC.82.E6.AD.A5.E4.BB.BB.E5.8A.A1.E5.AE.8C.E6.88.90.E4.BA.8B.E4.BB.B6.E6.8E.A8.E9.80.81">异步任务事件完成通知</a>
*/
public class BatchjobresultMessage extends EventMessage {
private static final long serialVersionUID = 8014540441322209657L;
public BatchjobresultMessage() {
super(EventType.batch_job_result.name());
}
/**
* 任务信息
*/
@XmlElement(name = "BatchJob")
private BatchJob batchJob;
public BatchJob getBatchJob() {
return batchJob;
}
/**
* 任务信息
*
* @className BatchJob
* @author jinyu(foxinmy@gmail.com)
* @date 2015年4月1日
* @since JDK 1.6
* @see
*/
public static class BatchJob implements Serializable {
private static final long serialVersionUID = -7520032656787156391L;
/**
* 异步任务id最大长度为64字符
*/
@XmlElement(name = "JobId")
private String jobId;
/**
* 操作类型字符串目前分别有 1. sync_user(增量更新成员) 2. replace_user(全量覆盖成员) 3.
* invite_user(邀请成员关注) 4. replace_party(全量覆盖部门)
*
* @see com.foxinmy.weixin4j.qy.type.BatchType
*/
@XmlElement(name = "JobType")
private String jobType;
/**
* 返回码
*/
@XmlElement(name = "ErrCode")
private String ErrCode;
/**
* 对返回码的文本描述内容
*/
@XmlElement(name = "ErrMsg")
private String errMsg;
public String getJobId() {
return jobId;
}
public String getJobType() {
return jobType;
}
public String getErrCode() {
return ErrCode;
}
public String getErrMsg() {
return errMsg;
}
@Override
public String toString() {
return "[jobId=" + jobId + ", jobType=" + jobType + ", ErrCode="
+ ErrCode + ", errMsg=" + errMsg + "]";
}
}
@Override
public String toString() {
return "BatchjobresultMessage [batchJob=" + batchJob + ", "
+ super.toString() + "]";
}
}

View File

@ -0,0 +1,42 @@
package com.zone.weixin4j.qy.event;
import com.zone.weixin4j.message.event.EventMessage;
import com.zone.weixin4j.type.EventType;
import javax.xml.bind.annotation.XmlElement;
/**
* 用户进入应用的事件推送(企业号)本事件只有在应用的回调模式中打开上报开关时上报
*
* @className EnterAgentEventMessage
* @author jinyu(foxinmy@gmail.com)
* @date 2014年12月28日
* @since JDK 1.6
* @see <a href=
* "http://qydev.weixin.qq.com/wiki/index.php?title=%E6%8E%A5%E6%94%B6%E4%BA%8B%E4%BB%B6#.E7.94.A8.E6.88.B7.E8.BF.9B.E5.85.A5.E5.BA.94.E7.94.A8.E7.9A.84.E4.BA.8B.E4.BB.B6.E6.8E.A8.E9.80.81"
* >用户进入应用的事件推送</a>
*/
public class EnterAgentEventMessage extends EventMessage {
private static final long serialVersionUID = 7675732524832500820L;
public EnterAgentEventMessage() {
super(EventType.enter_agent.name());
}
/**
* 事件KEY值与自定义菜单接口中KEY值对应
*/
@XmlElement(name = "EventKey")
private String eventKey;
public String getEventKey() {
return eventKey;
}
@Override
public String toString() {
return "EnterAgentEventMessage [eventKey=" + eventKey + ", "
+ super.toString() + "]";
}
}

View File

@ -0,0 +1,28 @@
package com.zone.weixin4j.qy.event;
import com.zone.weixin4j.message.event.EventMessage;
import com.zone.weixin4j.type.EventType;
/**
* 关注/取消关注事件</br> <font color="red">包括直接关注与扫描关注</font>
*
* @className ScribeEventMessage
* @author jinyu(foxinmy@gmail.com)
* @date 2014年4月6日
* @since JDK 1.6
* @see <a
* href="http://qydev.weixin.qq.com/wiki/index.php?title=%E6%8E%A5%E6%94%B6%E4%BA%8B%E4%BB%B6#.E6.88.90.E5.91.98.E5.85.B3.E6.B3.A8.2F.E5.8F.96.E6.B6.88.E5.85.B3.E6.B3.A8.E4.BA.8B.E4.BB.B6">成员关注/取消关注事件</a>
*/
public class ScribeEventMessage extends EventMessage {
private static final long serialVersionUID = -6846321620262204915L;
public ScribeEventMessage() {
super(EventType.subscribe.name());
}
@Override
public String toString() {
return "ScribeEventMessage [" + super.toString() + "]";
}
}

View File

@ -0,0 +1,26 @@
package com.zone.weixin4j.qy.suite;
/**
* 应用套件回调事件
*
* @className SuiteEventType
* @author jinyu(foxinmy@gmail.com)
* @date 2015年6月21日
* @since JDK 1.6
* @see <a
* href="http://qydev.weixin.qq.com/wiki/index.php?title=%E7%AC%AC%E4%B8%89%E6%96%B9%E5%9B%9E%E8%B0%83%E5%8D%8F%E8%AE%AE">第三方回调协议</a>
*/
public enum SuiteEventType {
/**
* 推送ticket
*/
suite_ticket,
/**
* 变更授权
*/
change_auth,
/**
* 取消授权
*/
cancel_auth;
}

View File

@ -0,0 +1,83 @@
package com.zone.weixin4j.qy.suite;
import javax.xml.bind.annotation.*;
import java.io.Serializable;
import java.util.Date;
/**
* 套件消息
*
* @className SuiteMessage
* @author jinyu(foxinmy@gmail.com)
* @date 2015年6月23日
* @since JDK 1.6
* @see
*/
@XmlRootElement(name = "xml")
@XmlAccessorType(XmlAccessType.FIELD)
public class SuiteMessage implements Serializable {
private static final long serialVersionUID = 6457919241019021514L;
/**
* 应用套件的SuiteId
*/
@XmlElement(name = "SuiteId")
private String suiteId;
/**
* 事件类型
*/
@XmlElement(name = "InfoType")
private String eventType;
/**
* 时间戳
*/
@XmlElement(name = "TimeStamp")
private long timeStamp;
/**
* Ticket内容
*/
@XmlElement(name = "SuiteTicket")
private String suiteTicket;
/**
* 授权方企业号的corpid
*/
@XmlElement(name = "AuthCorpId")
private String authCorpId;
public String getSuiteId() {
return suiteId;
}
public String getEventType() {
return eventType;
}
@XmlTransient
public SuiteEventType getFormatEventType() {
return SuiteEventType.valueOf(eventType);
}
public long getTimeStamp() {
return timeStamp;
}
@XmlTransient
public Date getFormatTimeStamp() {
return timeStamp > 0l ? new Date(timeStamp * 1000l) : null;
}
public String getSuiteTicket() {
return suiteTicket;
}
public String getAuthCorpId() {
return authCorpId;
}
@Override
public String toString() {
return "SuiteMessage [suiteId=" + suiteId + ", eventType="
+ eventType + ", timeStamp=" + timeStamp + ", suiteTicket="
+ suiteTicket + ", authCorpId=" + authCorpId + "]";
}
}

View File

@ -0,0 +1,161 @@
package com.zone.weixin4j.request;
import com.zone.weixin4j.type.MessageType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlTransient;
import java.io.Serializable;
import java.util.Date;
/**
* 微信消息基类
*
* @className WeixinMessage
* @author jinyu(foxinmy@gmail.com)
* @date 2015年5月6日
* @since JDK 1.6
* @see com.foxinmy.weixin4j.message.ImageMessage
* @see com.foxinmy.weixin4j.message.LinkMessage
* @see com.foxinmy.weixin4j.message.LocationMessage
* @see com.foxinmy.weixin4j.message.TextMessage
* @see com.foxinmy.weixin4j.message.VideoMessage
* @see com.foxinmy.weixin4j.message.VoiceMessage
* @see com.foxinmy.weixin4j.message.event.EventMessage
* @see com.foxinmy.weixin4j.message.event.LocationEventMessage
* @see com.foxinmy.weixin4j.message.event.MenuEventMessage
* @see com.foxinmy.weixin4j.message.event.MenuLocationEventMessage
* @see com.foxinmy.weixin4j.message.event.MenuPhotoEventMessage
* @see com.foxinmy.weixin4j.message.event.MenuScanEventMessage
* @see com.foxinmy.weixin4j.mp.event.KfCloseEventMessage
* @see com.foxinmy.weixin4j.mp.event.KfCreateEventMessage
* @see com.foxinmy.weixin4j.mp.event.KfSwitchEventMessage
* @see com.foxinmy.weixin4j.mp.event.MassEventMessage
* @see com.foxinmy.weixin4j.mp.event.ScanEventMessage
* @see com.foxinmy.weixin4j.mp.event.ScribeEventMessage
* @see com.foxinmy.weixin4j.mp.event.TemplatesendjobfinishMessage
* @see com.foxinmy.weixin4j.qy.event.BatchjobresultMessage
* @see com.foxinmy.weixin4j.qy.event.EnterAgentEventMessage
* @see com.foxinmy.weixin4j.qy.event.ScribeEventMessage
*/
public class WeixinMessage implements Serializable {
private static final long serialVersionUID = 7761192742840031607L;
/**
* 开发者微信号
*/
@XmlElement(name = "ToUserName")
private String toUserName;
/**
* 发送方账号 即用户的openid
*/
@XmlElement(name = "FromUserName")
private String fromUserName;
/**
* 消息创建时间 系统毫秒数
*/
@XmlElement(name = "CreateTime")
private long createTime;
/**
* 消息类型
*/
@XmlElement(name = "MsgType")
private String msgType;
/**
* 消息ID 可用于排重
*/
@XmlElement(name = "MsgId")
private long msgId;
/**
* 企业号独有的应用ID
*/
@XmlElement(name = "AgentID")
private String agentId;
public WeixinMessage() {
// jaxb required
}
public WeixinMessage(String msgType) {
this.msgType = msgType;
}
public String getToUserName() {
return toUserName;
}
public String getFromUserName() {
return fromUserName;
}
public long getCreateTime() {
return createTime;
}
@XmlTransient
public Date getFormatCreateTime() {
return new Date(createTime * 1000l);
}
public String getMsgType() {
return msgType;
}
@XmlTransient
public MessageType getFormatMsgType() {
return MessageType.valueOf(msgType);
}
public long getMsgId() {
return msgId;
}
public String getAgentId() {
return agentId;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((agentId == null) ? 0 : agentId.hashCode());
result = prime * result + (int) (createTime ^ (createTime >>> 32));
result = prime * result
+ ((fromUserName == null) ? 0 : fromUserName.hashCode());
result = prime * result + (int) (msgId ^ (msgId >>> 32));
result = prime * result + ((msgType == null) ? 0 : msgType.hashCode());
result = prime * result
+ ((toUserName == null) ? 0 : toUserName.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
WeixinMessage other = (WeixinMessage) obj;
if (msgId > 0l && other.getMsgId() > 0l) {
return msgId == other.getMsgId();
}
return fromUserName.equals(other.getFromUserName())
&& createTime == other.getCreateTime();
}
@Override
public String toString() {
String toString = " toUserName=" + toUserName + ", fromUserName="
+ fromUserName + ", createTime=" + createTime + ", msgType="
+ msgType;
if (msgId > 0l) {
toString += ", msgId=" + msgId;
}
if (agentId != null) {
toString += ", agentId=" + agentId;
}
return toString;
}
}

View File

@ -0,0 +1,161 @@
package com.zone.weixin4j.request;
import com.zone.weixin4j.type.EncryptType;
import com.zone.weixin4j.util.AesToken;
import javax.servlet.http.HttpServletRequest;
/**
* 微信请求
*
* @author jinyu(foxinmy@gmail.com)
* @className WeixinRequest
* @date 2015年3月29日
* @see
* @since JDK 1.6
*/
public class WeixinRequest {
/**
* 请求的URI
*/
private String uri;
// 以下字段每次被动消息时都会带上
/**
* 随机字符串
*/
private String echoStr;
/**
* 时间戳
*/
private String timeStamp;
/**
* 随机数
*/
private String nonce;
/**
* 参数签名
*/
private String signature;
/**
* AES模式下消息签名
*/
private String msgSignature;
/**
* 加密类型(POST时存在)
*
* @see com.zone.weixin4j.type.EncryptType
*/
private EncryptType encryptType;
/**
* xml消息明文主体
*/
private String originalContent;
/**
* xml消息密文主体(AES时存在)
*/
private String encryptContent;
/**
* aes & token
*/
private AesToken aesToken;
private HttpServletRequest request;
public WeixinRequest(String uri,
EncryptType encryptType, String echoStr, String timeStamp,
String nonce, String signature, String msgSignature,
String originalContent, String encryptContent, AesToken aesToken) {
this.uri = uri;
this.encryptType = encryptType;
this.echoStr = echoStr;
this.timeStamp = timeStamp;
this.nonce = nonce;
this.signature = signature;
this.msgSignature = msgSignature;
this.originalContent = originalContent;
this.encryptContent = encryptContent;
this.aesToken = aesToken;
}
public WeixinRequest(String uri,
EncryptType encryptType, String echoStr, String timeStamp,
String nonce, String signature, String msgSignature,
String originalContent, String encryptContent, AesToken aesToken, HttpServletRequest request) {
this.uri = uri;
this.encryptType = encryptType;
this.echoStr = echoStr;
this.timeStamp = timeStamp;
this.nonce = nonce;
this.signature = signature;
this.msgSignature = msgSignature;
this.originalContent = originalContent;
this.encryptContent = encryptContent;
this.aesToken = aesToken;
this.request = request;
}
public String getUri() {
return uri;
}
public String getEchoStr() {
return echoStr;
}
public String getTimeStamp() {
return timeStamp;
}
public String getNonce() {
return nonce;
}
public String getSignature() {
return signature;
}
public String getMsgSignature() {
return msgSignature;
}
public EncryptType getEncryptType() {
return encryptType;
}
public String getOriginalContent() {
return originalContent;
}
public String getEncryptContent() {
return encryptContent;
}
public AesToken getAesToken() {
return aesToken;
}
public HttpServletRequest getHttpServletRequest() {
return request;
}
public WeixinRequest setHttpServletRequest(HttpServletRequest httpServletRequest) {
this.request = httpServletRequest;
return this;
}
@Override
public String toString() {
return "WeixinRequest [uri=" + uri + ", echoStr=" + echoStr
+ ", timeStamp=" + timeStamp + ", nonce=" + nonce
+ ", signature=" + signature + ", msgSignature=" + msgSignature
+ ", encryptType=" + encryptType + ", originalContent="
+ originalContent + ", encryptContent=" + encryptContent
+ ", aesToken=" + aesToken + "]";
}
}

View File

@ -0,0 +1,19 @@
package com.zone.weixin4j.response;
/**
* 空白回复(避免微信服务器重复推送消息)
*
* @className BlankResponse
* @author jinyu(foxinmy@gmail.com)
* @date 2015年5月7日
* @since JDK 1.6
* @see
*/
public class BlankResponse extends SingleResponse {
public static final BlankResponse global = new BlankResponse();
private BlankResponse() {
super("success");
}
}

View File

@ -0,0 +1,37 @@
package com.zone.weixin4j.response;
/**
* 回复图片消息
*
* @className ImageResponse
* @author jinyu(foxinmy@gmail.com)
* @date 2015年5月5日
* @since JDK 1.6
* @see
*/
public class ImageResponse implements WeixinResponse {
/**
* 通过上传多媒体文件得到的id
*/
private String mediaId;
public ImageResponse(String mediaId) {
this.mediaId = mediaId;
}
@Override
public String toContent() {
return String.format(
"<Image><MediaId><![CDATA[%s]]></MediaId></Image>", mediaId);
}
public String getMediaId() {
return mediaId;
}
@Override
public String getMsgType() {
return "image";
}
}

View File

@ -0,0 +1,98 @@
package com.zone.weixin4j.response;
/**
* 回复音乐消息
*
* @className MusicResponse
* @author jinyu(foxinmy@gmail.com)
* @date 2015年5月5日
* @since JDK 1.6
* @see
*/
public class MusicResponse implements WeixinResponse {
/**
* 缩略图的媒体id通过上传多媒体文件得到的id
*/
private String thumbMediaId;
/**
* 音乐标题
*/
private String title;
/**
* 音乐描述
*/
private String desc;
/**
* 音乐链接
*/
private String musicUrl;
/**
* 高质量音乐链接WIFI环境优先使用该链接播放音乐
*/
private String hqMusicUrl;
public MusicResponse(String thumbMediaId) {
this.thumbMediaId = thumbMediaId;
}
@Override
public String toContent() {
StringBuilder content = new StringBuilder();
content.append("<Music>");
content.append(String.format(
"<ThumbMediaId><![CDATA[%s]]></ThumbMediaId>", thumbMediaId));
content.append(String.format("<Title><![CDATA[%s]]></Title>",
title != null ? title : ""));
content.append(String.format(
"<Description><![CDATA[%s]]></Description>",
desc != null ? desc : ""));
content.append(String.format("<MusicUrl><![CDATA[%s]]></MusicUrl>",
musicUrl != null ? musicUrl : ""));
content.append(String.format("<HQMusicUrl><![CDATA[%s]]></HQMusicUrl>",
hqMusicUrl != null ? hqMusicUrl : ""));
content.append("</Music>");
return content.toString();
}
public String getThumbMediaId() {
return thumbMediaId;
}
public String getMusicUrl() {
return musicUrl;
}
public void setMusicUrl(String musicUrl) {
this.musicUrl = musicUrl;
}
public String getHqMusicUrl() {
return hqMusicUrl;
}
public void setHqMusicUrl(String hqMusicUrl) {
this.hqMusicUrl = hqMusicUrl;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
@Override
public String getMsgType() {
return "music";
}
}

View File

@ -0,0 +1,159 @@
package com.zone.weixin4j.response;
import java.util.ArrayList;
import java.util.List;
/**
* 回复图文消息
*
* @className NewsResponse
* @author jinyu(foxinmy@gmail.com)
* @date 2015年5月5日
* @since JDK 1.6
* @see
*/
public class NewsResponse implements WeixinResponse {
/**
* 图文集合
*/
private List<Article> articleList;
public NewsResponse(List<Article> articleList) {
this.articleList = articleList;
}
public NewsResponse(Article article) {
this.articleList = new ArrayList<Article>();
this.articleList.add(article);
}
public void pushArticle(Article article) {
articleList.add(article);
}
public void pushFirstArticle(Article article) {
articleList.add(0, article);
}
public void pushLastArticle(Article article) {
articleList.add(articleList.size(), article);
}
public Article removeLastArticle() {
return articleList.remove(articleList.size() - 1);
}
public Article removeFirstArticle() {
return articleList.remove(0);
}
public List<Article> getArticleList() {
return articleList;
}
@Override
public String toContent() {
StringBuilder content = new StringBuilder();
content.append(String.format("<ArticleCount>%d</ArticleCount>",
articleList.size()));
content.append("<Articles>");
for (Article article : articleList) {
content.append("<item>");
content.append(String.format("<Title><![CDATA[%s]]></Title>",
article.getTitle() != null ? article.getTitle() : ""));
content.append(String.format(
"<Description><![CDATA[%s]]></Description>",
article.getDesc() != null ? article.getDesc() : ""));
content.append(String.format("<Url><![CDATA[%s]]></Url>",
article.getUrl() != null ? article.getUrl() : ""));
content.append(String.format("<PicUrl><![CDATA[%s]]></PicUrl>",
article.getPicUrl() != null ? article.getPicUrl() : ""));
content.append("</item>");
}
content.append("</Articles>");
return content.toString();
}
@Override
public String getMsgType() {
return "news";
}
/**
* 图文消息对象
*
* @className Article
* @author jinyu(foxinmy@gmail.com)
* @date 2015年5月5日
* @since JDK 1.6
* @see
*/
public static class Article {
/**
* 图文消息标题
*/
private String title;
/**
* 图文消息描述
*/
private String desc;
/**
* 点击图文消息跳转链接
*/
private String url;
/**
* 图片链接支持JPGPNG格式较好的效果为大图360*200小图200*200
*/
private String picUrl;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getPicUrl() {
return picUrl;
}
public void setPicUrl(String picUrl) {
this.picUrl = picUrl;
}
public Article() {
}
public Article(String title, String desc, String url, String picUrl) {
this.title = title;
this.desc = desc;
this.url = url;
this.picUrl = picUrl;
}
@Override
public String toString() {
return "Article [title=" + title + ", desc=" + desc + ", url="
+ url + ", picUrl=" + picUrl + "]";
}
}
}

View File

@ -0,0 +1,34 @@
package com.zone.weixin4j.response;
/**
* 单一内容回复
*
* @className SingleResponse
* @author jinyu(foxinmy@gmail.com)
* @date 2015年8月3日
* @since JDK 1.6
* @see
*/
public class SingleResponse implements WeixinResponse {
private final String content;
public SingleResponse(String content) {
this.content = content;
}
@Override
public String toContent() {
return content;
}
@Override
public String getMsgType() {
return "single";
}
@Override
public String toString() {
return "SingleResponse [content=" + content + "]";
}
}

View File

@ -0,0 +1,36 @@
package com.zone.weixin4j.response;
/**
* 回复文本消息
*
* @className TextResponse
* @author jinyu(foxinmy@gmail.com)
* @date 2015年5月5日
* @since JDK 1.6
* @see
*/
public class TextResponse implements WeixinResponse {
/**
* 回复的消息内容换行在content中能够换行微信客户端就支持换行显示
*/
private String content;
public TextResponse(String content) {
this.content = content;
}
@Override
public String toContent() {
return String.format("<Content><![CDATA[%s]]></Content>", content);
}
@Override
public String getMsgType() {
return "text";
}
public String getContent() {
return content;
}
}

View File

@ -0,0 +1,43 @@
package com.zone.weixin4j.response;
/**
* 消息转移到客服
*
* @className TransferCustomerResponse
* @author jinyu(foxinmy@gmail.com)
* @date 2015年5月5日
* @since JDK 1.6
* @see <a
* href="http://mp.weixin.qq.com/wiki/5/ae230189c9bd07a6b221f48619aeef35.html">转移消息到多客服</a>
*/
public class TransferCustomerResponse implements WeixinResponse {
/**
* 指定会话接入的客服账号
*/
private String kfAccount;
public TransferCustomerResponse(String kfAccount) {
this.kfAccount = kfAccount;
}
public String getKfAccount() {
return kfAccount;
}
@Override
public String toContent() {
String content = "";
if (kfAccount != null && !kfAccount.trim().isEmpty()) {
content = String
.format("<TransInfo><KfAccount><![CDATA[%s]]></KfAccount></TransInfo>",
kfAccount);
}
return content;
}
@Override
public String getMsgType() {
return "transfer_customer_service";
}
}

View File

@ -0,0 +1,76 @@
package com.zone.weixin4j.response;
/**
* 回复视频消息
*
* @className VideoResponse
* @author jinyu(foxinmy@gmail.com)
* @date 2015年5月5日
* @since JDK 1.6
* @see
*/
public class VideoResponse implements WeixinResponse {
/**
* 通过上传多媒体文件得到的id
*/
private String mediaId;
/**
* 视频消息标题
*/
private String title;
/**
* 视频消息描述
*/
private String desc;
public VideoResponse(String mediaId) {
this.mediaId = mediaId;
}
public VideoResponse(String mediaId, String title, String desc) {
this.mediaId = mediaId;
this.title = title;
this.desc = desc;
}
@Override
public String toContent() {
StringBuilder content = new StringBuilder();
content.append("<Video>");
content.append(String.format("<MediaId><![CDATA[%s]]></MediaId>",
mediaId));
content.append(String.format("<Title><![CDATA[%s]]></Title>",
title != null ? title : ""));
content.append(String.format(
"<Description><![CDATA[%s]]></Description>",
desc != null ? desc : ""));
content.append("</Video>");
return content.toString();
}
public String getMediaId() {
return mediaId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
@Override
public String getMsgType() {
return "video";
}
}

View File

@ -0,0 +1,37 @@
package com.zone.weixin4j.response;
/**
* 回复语音消息
*
* @className VoiceResponse
* @author jinyu(foxinmy@gmail.com)
* @date 2015年5月5日
* @since JDK 1.6
* @see
*/
public class VoiceResponse implements WeixinResponse {
/**
* 通过上传多媒体文件得到的id
*/
private String mediaId;
public VoiceResponse(String mediaId) {
this.mediaId = mediaId;
}
@Override
public String toContent() {
return String.format(
"<Voice><MediaId><![CDATA[%s]]></MediaId></Voice>", mediaId);
}
public String getMediaId() {
return mediaId;
}
@Override
public String getMsgType() {
return "voice";
}
}

View File

@ -0,0 +1,39 @@
package com.zone.weixin4j.response;
/**
* 微信被动消息回复
*
* @className WeixinResponse
* @author jinyu(foxinmy@gmail.com)
* @date 2015年5月5日
* @since JDK 1.6
* @see TextResponse
* @see ImageResponse
* @see MusicResponse
* @see VoiceResponse
* @see VideoResponse
* @see NewsResponse
* @see TransferCustomerResponse
* @see SingleResponse
* @see BlankResponse
* @see <a href=
* "http://https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140543&token=&lang=zh_CN">订阅号服务号的被动响应消息</a>
* @see <a
* href="http://qydev.weixin.qq.com/wiki/index.php?title=%E8%A2%AB%E5%8A%A8%E5%93%8D%E5%BA%94%E6%B6%88%E6%81%AF">企业号的被动响应消息</a>
*/
public interface WeixinResponse {
/**
* 回复的消息类型
*
* @return
*/
public String getMsgType();
/**
* 回复的消息内容
*
* @return
*/
public String toContent();
}

View File

@ -0,0 +1,32 @@
package com.zone.weixin4j.service;
import com.zone.weixin4j.dispatcher.WeixinMessageMatcher;
import com.zone.weixin4j.util.AesToken;
import org.springframework.context.ApplicationContext;
import java.util.List;
import java.util.Map;
/**
* Created by Yz on 2017/3/15.
* WeiXin4j上下文
*/
public interface WeiXin4jContextAware {
boolean isOpenAlwaysResponse();
boolean isUseDebugMessageHandler();
List<AesToken> getAesTokens();
ApplicationContext getApplicationContext();
Map<String, AesToken> getAesTokenMap();
void setAesTokenMap(Map<String, AesToken> aesTokenMap);
public WeixinMessageMatcher getWeixinMessageMatcher();
public void setWeixinMessageMatcher(WeixinMessageMatcher weixinMessageMatcher);
}

View File

@ -0,0 +1,23 @@
package com.zone.weixin4j.service;
import com.zone.weixin4j.exception.HttpResponseException;
import com.zone.weixin4j.exception.MessageInterceptorException;
import com.zone.weixin4j.exception.WeixinException;
import com.zone.weixin4j.request.WeixinRequest;
import com.zone.weixin4j.response.WeixinResponse;
import com.zone.weixin4j.util.AesToken;
import javax.servlet.http.HttpServletRequest;
/**
* Created by Yz on 2017/3/14.
* WxServiceImpl
*/
public interface WxService {
WeixinResponse processRequest(String uri, String encryptType, String echostr, String timestamp, String nonce, String signature, String msg_signature, String messageContent, AesToken aesToken, HttpServletRequest request) throws WeixinException, HttpResponseException, MessageInterceptorException;
String transferResponse(WeixinResponse weixinResponse) throws WeixinException;
}

View File

@ -0,0 +1,107 @@
package com.zone.weixin4j.service.context;
import com.zone.weixin4j.dispatcher.WeixinMessageMatcher;
import com.zone.weixin4j.service.WeiXin4jContextAware;
import com.zone.weixin4j.socket.WeixinMessageTransfer;
import com.zone.weixin4j.spring.TokenGenerater;
import com.zone.weixin4j.util.AesToken;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.NamedThreadLocal;
import org.springframework.util.StringUtils;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Created by Yz on 2017/3/15.
* WeiXin4j上下文
*/
public class WeiXin4jContextAwareImpl implements ApplicationContextAware, WeiXin4jContextAware {
private static final ThreadLocal<WeixinMessageTransfer> weixinMessageTransfer = new NamedThreadLocal<WeixinMessageTransfer>("WeixinMessageTransfer");
private ApplicationContext applicationContext;
private boolean openAlwaysResponse;
private boolean useDebugMessageHandler;
private List<AesToken> aesTokens;
private TokenGenerater tokenGenerater;
private WeixinMessageMatcher weixinMessageMatcher;
private Map<String, AesToken> aesTokenMap = new HashMap<String, AesToken>();
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public boolean isOpenAlwaysResponse() {
return openAlwaysResponse;
}
public void setOpenAlwaysResponse(boolean openAlwaysResponse) {
this.openAlwaysResponse = openAlwaysResponse;
}
public boolean isUseDebugMessageHandler() {
return useDebugMessageHandler;
}
public void setUseDebugMessageHandler(boolean useDebugMessageHandler) {
this.useDebugMessageHandler = useDebugMessageHandler;
}
public List<AesToken> getAesTokens() {
return aesTokens;
}
public void setAesTokens(List<AesToken> aesTokens) {
this.aesTokens = aesTokens;
}
public void setTokenGenerater(TokenGenerater tokenGenerater) {
this.tokenGenerater = tokenGenerater;
}
public ApplicationContext getApplicationContext() {
return applicationContext;
}
public void init() {
this.aesTokens = tokenGenerater.getAesTokens();
for(AesToken aesToken : this.aesTokens){
this.aesTokenMap.put(StringUtils.isEmpty(aesToken.getWeixinId()) ? "" : aesToken.getWeixinId(), aesToken);
}
}
public void destroy() {
this.applicationContext = null;
}
public static ThreadLocal<WeixinMessageTransfer> getWeixinMessageTransfer() {
return weixinMessageTransfer;
}
public Map<String, AesToken> getAesTokenMap() {
return aesTokenMap;
}
public void setAesTokenMap(Map<String, AesToken> aesTokenMap) {
this.aesTokenMap = aesTokenMap;
}
public WeixinMessageMatcher getWeixinMessageMatcher() {
return weixinMessageMatcher;
}
public void setWeixinMessageMatcher(WeixinMessageMatcher weixinMessageMatcher) {
this.weixinMessageMatcher = weixinMessageMatcher;
}
}

View File

@ -0,0 +1,130 @@
package com.zone.weixin4j.service.impl;
import com.zone.weixin4j.dispatcher.WeixinMessageDispatcher;
import com.zone.weixin4j.exception.HttpResponseException;
import com.zone.weixin4j.exception.MessageInterceptorException;
import com.zone.weixin4j.exception.WeixinException;
import com.zone.weixin4j.request.WeixinRequest;
import com.zone.weixin4j.response.SingleResponse;
import com.zone.weixin4j.response.WeixinResponse;
import com.zone.weixin4j.service.WxService;
import com.zone.weixin4j.socket.WeixinResponseEncoder;
import com.zone.weixin4j.type.EncryptType;
import com.zone.weixin4j.util.AesToken;
import com.zone.weixin4j.util.MessageUtil;
import com.zone.weixin4j.util.ServerToolkits;
import com.zone.weixin4j.xml.EncryptMessageHandler;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.http.HttpServletRequest;
/**
* Created by Yz on 2017/3/14.
* WxServiceImpl
*/
@Component
@DependsOn({"weiXin4jContextAware", "weixinMessageDispatcher"})
public class WxServiceImpl implements WxService {
private final Log logger = LogFactory.getLog(getClass());
@Autowired
private WeixinMessageDispatcher messageDispatcher;
@Autowired
private WeixinResponseEncoder weixinResponseEncoder;
/**
* 处理Request
*
* @throws WeixinException
* @throws HttpResponseException
*/
@Override
public WeixinResponse processRequest(String uri, String encrypt_type, String echostr, String timestamp, String nonce, String signature, String msg_signature, String messageContent, AesToken aesToken, HttpServletRequest request) throws WeixinException, HttpResponseException, MessageInterceptorException {
EncryptType encryptType = !StringUtils.isEmpty(encrypt_type) ? EncryptType.valueOf(encrypt_type.toUpperCase()) : EncryptType.RAW;
String encryptContent = null;
if (!ServerToolkits.isBlank(messageContent) && encryptType == EncryptType.AES) {
if (ServerToolkits.isBlank(aesToken.getAesKey())) {
logger.error("EncodingAESKey not be empty in safety(AES) mode");
}
EncryptMessageHandler encryptHandler = EncryptMessageHandler.parser(messageContent);
encryptContent = encryptHandler.getEncryptContent();
/**
* 企业号第三方套件 _
*/
if (aesToken.getWeixinId().startsWith("tj")) {
aesToken = new AesToken(encryptHandler.getToUserName(), aesToken.getToken(), aesToken.getAesKey());
}
messageContent = MessageUtil.aesDecrypt(aesToken.getWeixinId(), aesToken.getAesKey(), encryptContent);
}
WeixinRequest weixinRequest = new WeixinRequest(uri, encryptType, echostr, timestamp, nonce, signature, msg_signature, messageContent, encryptContent, aesToken).setHttpServletRequest(request);
if (aesToken == null || (ServerToolkits.isBlank(weixinRequest.getSignature()) && ServerToolkits.isBlank(weixinRequest.getMsgSignature()))) {
throw new HttpResponseException(HttpResponseException.HttpResponseStatus.BAD_REQUEST);
}
if (request.getMethod().equalsIgnoreCase(RequestMethod.GET.toString())) {
return doGet(weixinRequest);
} else if (request.getMethod().equalsIgnoreCase(RequestMethod.POST.toString())) {
return doPost(weixinRequest);
} else {
return otherwise(weixinRequest);
}
}
/**
* 处理Get请求
*
* @throws WeixinException
* @throws HttpResponseException
*/
protected WeixinResponse doGet(WeixinRequest request) throws WeixinException, HttpResponseException {
if (!ServerToolkits.isBlank(request.getSignature()) && MessageUtil.signature(request.getAesToken().getToken(), request.getTimeStamp(), request.getNonce()).equals(request.getSignature())) {
return new SingleResponse(request.getEchoStr());
}
// XML消息签名验证
if (!ServerToolkits.isBlank(request.getMsgSignature()) && MessageUtil.signature(request.getAesToken().getToken(), request.getTimeStamp(), request.getNonce(), request.getEchoStr()).equals(request.getMsgSignature())) {
return new SingleResponse(MessageUtil.aesDecrypt(null, request.getAesToken().getAesKey(), request.getEchoStr()));
}
throw new HttpResponseException(HttpResponseException.HttpResponseStatus.FORBIDDEN);
}
/**
* 处理Post请求
*
* @throws WeixinException
* @throws HttpResponseException
*/
protected WeixinResponse doPost(WeixinRequest request) throws HttpResponseException, MessageInterceptorException, WeixinException {
// URL参数签名验证
if (!ServerToolkits.isBlank(request.getSignature()) && !MessageUtil.signature(request.getAesToken().getToken(), request.getTimeStamp(), request.getNonce()).equals(request.getSignature())) {
throw new HttpResponseException(HttpResponseException.HttpResponseStatus.FORBIDDEN);
}
// XML消息签名验证
if (request.getEncryptType() == EncryptType.AES && !MessageUtil.signature(request.getAesToken().getToken(), request.getTimeStamp(), request.getNonce(), request.getEncryptContent()).equals(request.getMsgSignature())) {
throw new HttpResponseException(HttpResponseException.HttpResponseStatus.FORBIDDEN);
}
return messageDispatcher.doDispatch(request);
}
protected WeixinResponse otherwise(WeixinRequest weixinRequest) throws HttpResponseException {
throw new HttpResponseException(HttpResponseException.HttpResponseStatus.METHOD_NOT_ALLOWED);
}
public String transferResponse(WeixinResponse weixinResponse) throws WeixinException {
if(weixinResponse instanceof SingleResponse){
return weixinResponse.toContent();
} else {
return weixinResponseEncoder.encode(weixinResponse);
}
}
}

View File

@ -0,0 +1,7 @@
WeixinMessageDecoder:对微信消息解码
WeixinRequestHandler:微信请求处理类
WeixinResponseEncoder:对微信回复编码
SingleResponseEncoder:对微信单一回复编码

View File

@ -0,0 +1,109 @@
package com.zone.weixin4j.socket;
import com.zone.weixin4j.type.AccountType;
import com.zone.weixin4j.type.EncryptType;
import com.zone.weixin4j.util.AesToken;
import java.io.Serializable;
import java.util.Set;
/**
* 消息传递
*
* @className WeixinMessageTransfer
* @author jinyu(foxinmy@gmail.com)
* @date 2015年6月23日
* @since JDK 1.6
* @see
*/
public class WeixinMessageTransfer implements Serializable {
private static final long serialVersionUID = 7779948135156353261L;
/**
* aes & token
*/
private AesToken aesToken;
/**
* 加密类型
*/
private EncryptType encryptType;
/**
* 消息接收方
*/
private String toUserName;
/**
* 消息发送方
*/
private String fromUserName;
/**
* 账号
*/
private AccountType accountType;
/**
* 消息类型
*/
private String msgType;
/**
* 事件类型
*/
private String eventType;
/**
* 节点集合
*/
private Set<String> nodeNames;
public WeixinMessageTransfer(AesToken aesToken, EncryptType encryptType,
String toUserName, String fromUserName, AccountType accountType,
String msgType, String eventType, Set<String> nodeNames) {
this.aesToken = aesToken;
this.encryptType = encryptType;
this.toUserName = toUserName;
this.fromUserName = fromUserName;
this.accountType = accountType;
this.msgType = msgType;
this.eventType = eventType;
this.nodeNames = nodeNames;
}
public AesToken getAesToken() {
return aesToken;
}
public EncryptType getEncryptType() {
return encryptType;
}
public String getToUserName() {
return toUserName;
}
public String getFromUserName() {
return fromUserName;
}
public AccountType getAccountType() {
return accountType;
}
public String getMsgType() {
return msgType;
}
public String getEventType() {
return eventType;
}
public Set<String> getNodeNames() {
return nodeNames;
}
@Override
public String toString() {
return "WeixinMessageTransfer [aesToken=" + aesToken + ", encryptType="
+ encryptType + ", toUserName=" + toUserName
+ ", fromUserName=" + fromUserName + ", accountType="
+ accountType + ", msgType=" + msgType + ", eventType="
+ eventType + ", nodeNames=" + nodeNames + "]";
}
}

View File

@ -0,0 +1,73 @@
package com.zone.weixin4j.socket;
import com.zone.weixin4j.exception.WeixinException;
import com.zone.weixin4j.response.WeixinResponse;
import com.zone.weixin4j.service.context.WeiXin4jContextAwareImpl;
import com.zone.weixin4j.type.EncryptType;
import com.zone.weixin4j.util.AesToken;
import com.zone.weixin4j.util.MessageUtil;
import com.zone.weixin4j.util.ServerToolkits;
import org.springframework.stereotype.Component;
/**
* 微信回复编码类
*
* @author jinyu(foxinmy@gmail.com)
* @className WeixinResponseEncoder
* @date 2014年11月13日
* @see <a
* href="http://mp.weixin.qq.com/wiki/0/61c3a8b9d50ac74f18bdf2e54ddfc4e0.html">加密接入指引</a>
* @see com.zone.weixin4j.response.WeixinResponse
* @since JDK 1.6
*/
@Component
public class WeixinResponseEncoder {
private final String XML_START = "<xml>";
// ---------------明文节点
private final String ELEMENT_TOUSERNAME = "<ToUserName><![CDATA[%s]]></ToUserName>";
private final String ELEMENT_FROMUSERNAME = "<FromUserName><![CDATA[%s]]></FromUserName>";
private final String ELEMENT_CREATETIME = "<CreateTime><![CDATA[%d]]></CreateTime>";
private final String ELEMENT_MSGTYPE = "<MsgType><![CDATA[%s]]></MsgType>";
// ---------------密文节点
private final String ELEMENT_MSGSIGNATURE = "<MsgSignature><![CDATA[%s]]></MsgSignature>";
private final String ELEMENT_ENCRYPT = "<Encrypt><![CDATA[%s]]></Encrypt>";
private final String ELEMENT_TIMESTAMP = "<TimeStamp><![CDATA[%s]]></TimeStamp>";
private final String ELEMENT_NONCE = "<Nonce><![CDATA[%s]]></Nonce>";
private final String XML_END = "</xml>";
public String encode(WeixinResponse response) throws WeixinException {
WeixinMessageTransfer messageTransfer = WeiXin4jContextAwareImpl.getWeixinMessageTransfer().get();
EncryptType encryptType = messageTransfer.getEncryptType();
StringBuilder content = new StringBuilder();
content.append(XML_START);
content.append(String.format(ELEMENT_TOUSERNAME,
messageTransfer.getFromUserName()));
content.append(String.format(ELEMENT_FROMUSERNAME,
messageTransfer.getToUserName()));
content.append(String.format(ELEMENT_CREATETIME,
System.currentTimeMillis() / 1000l));
content.append(String.format(ELEMENT_MSGTYPE, response.getMsgType()));
content.append(response.toContent());
content.append(XML_END);
if (encryptType == EncryptType.AES) {
AesToken aesToken = messageTransfer.getAesToken();
String nonce = ServerToolkits.generateRandomString(32);
String timestamp = Long
.toString(System.currentTimeMillis() / 1000l);
String encrtypt = MessageUtil.aesEncrypt(aesToken.getWeixinId(),
aesToken.getAesKey(), content.toString());
String msgSignature = MessageUtil.signature(aesToken.getToken(),
nonce, timestamp, encrtypt);
content.delete(0, content.length());
content.append(XML_START);
content.append(String.format(ELEMENT_NONCE, nonce));
content.append(String.format(ELEMENT_TIMESTAMP, timestamp));
content.append(String.format(ELEMENT_MSGSIGNATURE, msgSignature));
content.append(String.format(ELEMENT_ENCRYPT, encrtypt));
content.append(XML_END);
}
return content.toString();
}
}

View File

@ -0,0 +1,24 @@
package com.zone.weixin4j.spring;
import com.zone.weixin4j.util.AesToken;
import java.util.List;
/**
* Created by Yz on 2017/3/15.
* 配置文件中获取Token
*/
public class PropertyTokenGenerater extends TokenGenerater {
private List<AesToken> aesTokens;
@Override
public List<AesToken> getAesTokens() {
return aesTokens;
}
public void setAesTokens(List<AesToken> aesTokens) {
this.aesTokens = aesTokens;
}
}

View File

@ -0,0 +1,15 @@
package com.zone.weixin4j.spring;
import com.zone.weixin4j.util.AesToken;
import java.util.List;
/**
* Created by Yz on 2017/3/15.
* 生成Token
*/
public abstract class TokenGenerater {
abstract public List<AesToken> getAesTokens();
}

View File

@ -0,0 +1,21 @@
package com.zone.weixin4j.type;
/**
* 账号类型
*
* @className AccountType
* @author jinyu(foxinmy@gmail.com)
* @date 2015年5月18日
* @since JDK 1.6
* @see
*/
public enum AccountType {
/**
* 公众号
*/
MP,
/**
* 企业号
*/
QY
}

View File

@ -0,0 +1,26 @@
package com.zone.weixin4j.type;
/**
* 应用类型
*
* @className AgentType
* @author jinyu(foxinmy@gmail.com)
* @date 2015年8月1日
* @since JDK 1.6
* @see
*/
public enum AgentType {
/**
* 聊天应用
*/
chat,
// 企业客服回调
/**
* 企业号内部客服客户为企业号通讯录成员
*/
kf_internal,
/**
* 企业号外部客服客户为服务号openid
*/
kf_external
}

View File

@ -0,0 +1,21 @@
package com.zone.weixin4j.type;
/**
* 消息加密类型
*
* @className EncryptType
* @author jinyu(foxinmy@gmail.com)
* @date 2014年11月23日
* @since JDK 1.6
* @see
*/
public enum EncryptType {
/**
* 明文模式
*/
RAW,
/**
* 密文模式
*/
AES;
}

View File

@ -0,0 +1,159 @@
package com.zone.weixin4j.type;
/**
* 事件类型
*
* @className EventType
* @author jinyu(foxinmy@gmail.com)
* @date 2014年9月30日
* @since JDK 1.6
* @see
*/
public enum EventType {
/**
* 关注事件
*
*/
subscribe,
/**
* 取消关注事件
*
*/
unsubscribe,
/**
* 上报地理位置事件
*
* @see com.foxinmy.weixin4j.message.event.LocationEventMessage
*/
location,
/**
* 菜单点击关键字事件
*
* @see com.foxinmy.weixin4j.message.event.MenuEventMessage
*/
view,
/**
* 菜单点击链接事件
*
* @see com.foxinmy.weixin4j.message.event.MenuEventMessage
*/
click,
/**
* 菜单扫描事件
*
* @see com.foxinmy.weixin4j.message.event.MenuScanEventMessage
*/
scancode_push,
/**
* 菜单扫描并调出等待界面事件
*
* @see com.foxinmy.weixin4j.message.event.MenuScanEventMessage
*/
scancode_waitmsg,
/**
* 菜单弹出拍照事件
*
* @see com.foxinmy.weixin4j.message.event.MenuPhotoEventMessage
*/
pic_sysphoto,
/**
* 菜单弹出发图事件
*
* @see com.foxinmy.weixin4j.message.event.MenuPhotoEventMessage
*/
pic_photo_or_album,
/**
* 菜单弹出发图事件
*
* @see com.foxinmy.weixin4j.message.event.MenuPhotoEventMessage
*/
pic_weixin,
/**
* 菜单发送地理位置事件
*
* @see com.foxinmy.weixin4j.message.event.MenuLocationEventMessage
*/
location_select,
// ------------------------------公众平台特有------------------------------
/**
* 二维码扫描事件
*
* @see com.foxinmy.weixin4j.mp.event.ScanEventMessage
*/
scan,
/**
* 群发消息事件
*
* @see com.foxinmy.weixin4j.mp.event.MassEventMessage
*/
masssendjobfinish,
/**
* 模板消息事件
*
* @see com.foxinmy.weixin4j.mp.event.TemplatesendjobfinishMessage
*/
templatesendjobfinish,
/**
* 客服接入会话事件
*
* @see com.foxinmy.weixin4j.mp.event.KfCreateEventMessage
*/
kf_create_session,
/**
* 客服关闭会话事件
*
* @see com.foxinmy.weixin4j.mp.event.KfCloseEventMessage
*/
kf_close_session,
/**
* 客服转接会话事件
*
* @see com.foxinmy.weixin4j.mp.event.KfSwitchEventMessage
*/
kf_switch_session,
/**
* 资质认证成功事件
*/
qualification_verify_success,
/**
* 资质认证失败事件
*/
qualification_verify_fail,
/**
* 名称认证成功事件
*/
naming_verify_success,
/**
* 名称认证失败事件
*/
naming_verify_fail,
/**
* 年审通知事件
*/
annual_renew,
/**
* 认证过期失效通知
*/
verify_expired,
// ------------------------------企业号特有------------------------------
/**
* 异步任务完成事件
*
* @see com.foxinmy.weixin4j.qy.event.BatchjobresultMessage
*/
batch_job_result,
/**
* 进入企业号应用事件
*
* @see com.foxinmy.weixin4j.qy.event.EnterAgentEventMessage
*/
enter_agent,
/**
* 第三方应用套件消息
* @see com.foxinmy.weixin4j.qy.suite.WeixinSuiteMessage
*/
suite;
}

View File

@ -0,0 +1,60 @@
package com.zone.weixin4j.type;
/**
*
* 消息类型
*
* @author jinyu(foxinmy@gmail.com)
*
*/
public enum MessageType {
/**
* 文字消息
*
* @see com.foxinmy.weixin4j.message.TextMessage
*/
text,
/**
* 图片消息
*
* @see com.foxinmy.weixin4j.message.ImageMessage
*/
image,
/**
* 语音消息
*
* @see com.foxinmy.weixin4j.message.VoiceMessage
*/
voice,
/**
* 视频消息
*
* @see com.foxinmy.weixin4j.message.VideoMessage
*/
video,
/**
* 小视频消息
*
* @see com.foxinmy.weixin4j.message.VideoMessage
*/
shortvideo,
/**
* 位置消息
*
* @see com.foxinmy.weixin4j.message.LocationMessage
*/
location,
/**
* 链接消息
*
* @see com.foxinmy.weixin4j.message.LinkMessage
*/
link,
/**
* 事件消息
*
* @see com.foxinmy.weixin4j.message.event.EventMessage
*/
event;
}

View File

@ -0,0 +1,74 @@
package com.zone.weixin4j.util;
import java.io.Serializable;
/**
* aes & token
*
* @className AesToken
* @author jinyu(foxinmy@gmail.com)
* @date 2015年5月6日
* @since JDK 1.6
* @see
*/
public class AesToken implements Serializable {
private static final long serialVersionUID = -6001008896414323534L;
/**
* 账号ID(原始id/appid/corpid)
*/
private String weixinId;
/**
* 开发者的token
*/
private String token;
/**
* 安全模式下的加密密钥
*/
private String aesKey;
/**
* 一般为明文模式
*
* @param token
* 开发者的Token
*/
public AesToken(String token) {
this(null, token, null);
}
/**
* 一般为AES加密模式
*
* @param weixinId
* 公众号的应用ID(原始id/appid/corpid)
* @param token
* 开发者Token
* @param aesKey
* 解密的EncodingAESKey
*/
public AesToken(String weixinId, String token, String aesKey) {
this.weixinId = weixinId;
this.token = token;
this.aesKey = aesKey;
}
public String getWeixinId() {
return weixinId;
}
public String getToken() {
return token;
}
public String getAesKey() {
return aesKey;
}
@Override
public String toString() {
return "AesToken [weixinId=" + weixinId + ", token=" + token
+ ", aesKey=" + aesKey + "]";
}
}

View File

@ -0,0 +1,212 @@
package com.zone.weixin4j.util;
import com.zone.weixin4j.exception.WeixinException;
import java.io.*;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.JarURLConnection;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* 对class的获取
*
* @className ClassUtil
* @author jinyu(foxinmy@gmail.com)
* @date 2014年10月31日
* @since JDK 1.6
* @see
*/
public final class ClassUtil {
private final static String POINT = ".";
private final static String CLASS = ".class";
/**
* 获取某个包下所有的class信息
*
* @param packageName
* 包名
* @return
*/
public static List<Class<?>> getClasses(String packageName)
throws WeixinException {
String packageFileName = packageName.replace(POINT, File.separator);
URL fullPath = getDefaultClassLoader().getResource(packageFileName);
String protocol = fullPath.getProtocol();
if (protocol.equals(ServerToolkits.PROTOCOL_FILE)) {
try {
File dir = new File(fullPath.toURI());
return findClassesByFile(dir, packageName);
} catch (URISyntaxException e) {
throw new WeixinException(e);
}
} else if (protocol.equals(ServerToolkits.PROTOCOL_JAR)) {
try {
return findClassesByJar(
((JarURLConnection) fullPath.openConnection())
.getJarFile(),
packageName);
} catch (IOException e) {
throw new WeixinException(e);
}
}
return null;
}
/**
* 扫描目录下所有的class对象
*
* @param dir
* 文件目录
* @param packageName
* 包的全限类名
* @return
*/
private static List<Class<?>> findClassesByFile(File dir, String packageName) {
List<Class<?>> classes = new ArrayList<Class<?>>();
File[] files = dir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File file, String name) {
return file.isDirectory() || file.getName().endsWith(CLASS);
}
});
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
classes.addAll(findClassesByFile(file, packageName + POINT
+ file.getName()));
} else {
try {
classes.add(Class.forName(packageName + POINT
+ file.getName().replace(CLASS, "")));
} catch (ClassNotFoundException e) {
;
}
}
}
}
return classes;
}
/**
* 扫描jar包下所有的class对象
*
* @param jar
* jar包对象
* @param packageName
* 包的全限类名
* @return
*/
private static List<Class<?>> findClassesByJar(JarFile jar,
String packageName) {
List<Class<?>> classes = new ArrayList<Class<?>>();
Enumeration<JarEntry> jarEntries = jar.entries();
while (jarEntries.hasMoreElements()) {
JarEntry jarEntry = jarEntries.nextElement();
if (jarEntry.isDirectory()) {
continue;
}
String className = jarEntry.getName()
.replace(File.separator, POINT);
if (!className.startsWith(packageName)
|| !className.endsWith(CLASS)) {
continue;
}
try {
classes.add(Class.forName(className.replace(CLASS, "")));
} catch (ClassNotFoundException e) {
;
}
}
return classes;
}
public static Object deepClone(Object obj) throws WeixinException {
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
try {
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(obj);
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
return ois.readObject();
} catch (IOException e) {
throw new WeixinException(e);
} catch (ClassNotFoundException e) {
throw new WeixinException(e);
} finally {
try {
if (bos != null) {
bos.close();
}
if (oos != null) {
oos.close();
}
if (bis != null) {
bis.close();
}
if (ois != null) {
ois.close();
}
} catch (IOException e) {
;// ignore
}
}
}
/**
* 获得泛型类型
*
* @param object
* @return
*/
public static Class<?> getGenericType(Class<?> clazz) {
if(clazz == Object.class){
return null;
}
Type type = clazz.getGenericSuperclass();
if (type instanceof ParameterizedType) {
ParameterizedType ptype = ((ParameterizedType) type);
Type[] args = ptype.getActualTypeArguments();
return (Class<?>) args[0];
}
return getGenericType(clazz.getSuperclass());
}
public static ClassLoader getDefaultClassLoader() {
ClassLoader cl = null;
try {
cl = Thread.currentThread().getContextClassLoader();
} catch (Throwable ex) {
// Cannot access thread context ClassLoader - falling back...
}
if (cl == null) {
// No thread context class loader -> use class loader of this class.
cl = ClassUtil.class.getClassLoader();
if (cl == null) {
// getClassLoader() returning null indicates the bootstrap
// ClassLoader
try {
cl = ClassLoader.getSystemClassLoader();
} catch (Throwable ex) {
// Cannot access system ClassLoader - oh well, maybe the
// caller can live with null...
}
}
}
return cl;
}
public static void main(String[] args) throws WeixinException {
System.err.println(getClasses("com.foxinmy.weixin4j.qy.event"));
}
}

View File

@ -0,0 +1,43 @@
package com.zone.weixin4j.util;
/**
* Hex工具类
*
* @className HexUtil
* @author jinyu(foxinmy@gmail.com)
* @date 2015年5月17日
* @since JDK 1.6
* @see
*/
public final class HexUtil {
/**
* Used to build output as Hex
*/
private static final char[] DIGITS_LOWER = { '0', '1', '2', '3', '4', '5',
'6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
/**
* Used to build output as Hex
*/
private static final char[] DIGITS_UPPER = { '0', '1', '2', '3', '4', '5',
'6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
private static char[] encodeHex(final byte[] data, final char[] toDigits) {
final int l = data.length;
final char[] out = new char[l << 1];
// two characters form the hex value.
for (int i = 0, j = 0; i < l; i++) {
out[j++] = toDigits[(0xF0 & data[i]) >>> 4];
out[j++] = toDigits[0x0F & data[i]];
}
return out;
}
public static String encodeHexString(final byte[] data) {
return new String(encodeHex(data, true));
}
public static char[] encodeHex(final byte[] data, final boolean toLowerCase) {
return encodeHex(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER);
}
}

View File

@ -0,0 +1,167 @@
package com.zone.weixin4j.util;
import com.zone.weixin4j.base64.Base64;
import com.zone.weixin4j.exception.WeixinException;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Arrays;
/**
* 消息工具类
*
* @className MessageUtil
* @author jinyu(foxinmy@gmail.com)
* @date 2014年10月31日
* @since JDK 1.6
* @see
*/
public final class MessageUtil {
/**
* 验证微信签名
*
* @param signature
* 微信加密签名signature结合了开发者填写的token参数和请求中的timestamp参数nonce参数
* @return 开发者通过检验signature对请求进行相关校验若确认此次GET请求来自微信服务器
* 请原样返回echostr参数内容则接入生效 成为开发者成功否则接入失败
* @see <a
* href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421135319&token=&lang=zh_CN">接入指南</a>
*/
public static String signature(String... para) {
Arrays.sort(para);
StringBuffer sb = new StringBuffer();
for (String str : para) {
sb.append(str);
}
return ServerToolkits.digestSHA1(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 WeixinException {
/**
* 其中msg_encrypt=Base64_Encode(AES_Encrypt [random(16B)+ msg_len(4B) +
* msg + $AppId])
*
* random(16B)为16字节的随机字符串msg_len为msg长度占4个字节(网络字节序)$AppId为公众账号的AppId
*/
byte[] randomBytes = ServerToolkits.getBytesUtf8(ServerToolkits
.generateRandomString(16));
byte[] xmlBytes = ServerToolkits.getBytesUtf8(xmlContent);
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 = ServerToolkits.getBytesUtf8(appId);
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.decodeBase64(encodingAesKey + "=");
// 设置加密模式为AES的CBC模式
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
SecretKeySpec keySpec = new SecretKeySpec(aesKey, ServerToolkits.AES);
IvParameterSpec iv = new IvParameterSpec(aesKey, 0, 16);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
// 加密
byte[] encrypted = cipher.doFinal(unencrypted);
// 使用BASE64对加密后的字符串进行编码
// return Base64.encodeBase64String(encrypted);
return Base64
.encodeBase64String(encrypted);
} catch (Exception e) {
throw new WeixinException("-40006", "AES加密失败:" + e.getMessage());
}
}
/**
* 对AES消息解密
*
* @param appId
* @param encodingAesKey
* aes加密的密钥
* @param encryptContent
* 加密的消息体
* @return 解密后的字符
* @throws WeixinException
*/
public static String aesDecrypt(String appId, String encodingAesKey,
String encryptContent) throws WeixinException {
byte[] aesKey = Base64.decodeBase64(encodingAesKey + "=");
byte[] original;
try {
// 设置解密模式为AES的CBC模式
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
SecretKeySpec key_spec = new SecretKeySpec(aesKey, ServerToolkits.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 WeixinException("-40007", "AES解密失败:" + e.getMessage());
}
String xmlContent, fromAppId;
try {
// 去除补位字符
byte[] bytes = PKCS7Encoder.decode(original);
/**
* AES加密的buf由16个字节的随机字符串4个字节的msg_len(网络字节序)msg和$AppId组成
* 其中msg_len为msg的长度$AppId为公众帐号的AppId
*/
// 获取表示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 = ServerToolkits.newStringUtf8(Arrays.copyOfRange(bytes, 20,
20 + xmlLength));
fromAppId = ServerToolkits.newStringUtf8(Arrays.copyOfRange(bytes,
20 + xmlLength, bytes.length));
} catch (Exception e) {
throw new WeixinException("-40008", "xml内容不合法:" + e.getMessage());
}
// 校验appId是否一致
if (appId != null && !fromAppId.trim().equals(appId)) {
throw new WeixinException("-40005", "校验AppID失败,expect " + appId
+ ",but actual is " + fromAppId);
}
return xmlContent;
}
}

View File

@ -0,0 +1,70 @@
/**
* 对公众平台发送给公众账号的消息加解密示例代码.
*
* @copyright Copyright (c) 1998-2014 Tencent Inc.
*/
// ------------------------------------------------------------------------
package com.zone.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(ServerToolkits.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);
}
}

View File

@ -0,0 +1,105 @@
package com.zone.weixin4j.util;
import com.zone.weixin4j.socket.WeixinMessageTransfer;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Random;
/**
* 工具包
*
* @className ServerToolkits
* @author jinyu(foxinmy@gmail.com)
* @date 2015年12月26日
* @since JDK 1.7
* @see
*/
public final class ServerToolkits {
private static final String ALLCHAR = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
public static final Charset UTF_8 = Charset.forName("UTF-8");
public static final String AES = "AES";
public static final String SHA1 = "SHA-1";
public static final String PROTOCOL_FILE = "file";
public static final String PROTOCOL_JAR = "jar";
public static final String CONTENTTYPE$APPLICATION_XML = "application/xml";
public static final String CONTENTTYPE$TEXT_PLAIN = "text/plain";
/**
* 返回一个定长的随机字符串(包含数字和大小写字母)
*
* @param length
* 随机数的长度
* @return
*/
public static String generateRandomString(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 ctor
*/
public static void makeConstructorAccessible(Constructor<?> ctor) {
if ((!Modifier.isPublic(ctor.getModifiers()) || !Modifier.isPublic(ctor
.getDeclaringClass().getModifiers())) && !ctor.isAccessible()) {
ctor.setAccessible(true);
}
}
/**
* SHA1签名
*
* @param content
* 待签名字符串
* @return 签名后的字符串
*/
public static String digestSHA1(String content) {
byte[] data = ServerToolkits.getBytesUtf8(content);
try {
return HexUtil.encodeHexString(MessageDigest.getInstance(SHA1)
.digest(data));
} catch (NoSuchAlgorithmException e) {
return null;
}
}
private static String newString(final byte[] bytes, final Charset charset) {
return bytes == null ? null : new String(bytes, charset);
}
public static byte[] getBytesUtf8(final String content) {
return content != null ? content.getBytes(UTF_8) : null;
}
public static String newStringUtf8(final byte[] bytes) {
return newString(bytes, UTF_8);
}
/**
* 判断字符串是否为空
*
* @param cs
* @return
*/
public static boolean isBlank(final CharSequence cs) {
int strLen;
if (cs == null || (strLen = cs.length()) == 0) {
return true;
}
for (int i = 0; i < strLen; i++) {
if (Character.isWhitespace(cs.charAt(i)) == false) {
return false;
}
}
return true;
}
}

View File

@ -0,0 +1,81 @@
package com.zone.weixin4j.xml;
import com.zone.weixin4j.util.ServerToolkits;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;
import java.io.ByteArrayInputStream;
import java.io.IOException;
/**
* 获取加密的密文内容
*
* @className EncryptMessageHandler
* @author jinyu(foxinmy@gmail.com)
* @date 2015年5月17日
* @since JDK 1.6
* @see
*/
public class EncryptMessageHandler extends DefaultHandler {
private String toUserName;
private String encryptContent;
private String content;
@Override
public void startDocument() throws SAXException {
toUserName = null;
encryptContent = null;
}
@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
}
@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
if (localName.equalsIgnoreCase("encrypt")) {
encryptContent = content;
} else if (localName.equalsIgnoreCase("tousername")) {
toUserName = content;
}
}
@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
this.content = new String(ch, start, length);
}
public String getToUserName() {
return toUserName;
}
public String getEncryptContent() {
return encryptContent;
}
private final static EncryptMessageHandler global = new EncryptMessageHandler();
public static EncryptMessageHandler parser(String xmlContent)
throws RuntimeException {
try {
XMLReader xmlReader = XMLReaderFactory.createXMLReader();
xmlReader.setContentHandler(global);
xmlReader.parse(new InputSource(new ByteArrayInputStream(xmlContent
.getBytes(ServerToolkits.UTF_8))));
} catch (IOException e) {
throw new RuntimeException(e);
} catch (SAXException e) {
throw new RuntimeException(e);
}
return global;
}
}

View File

@ -0,0 +1,104 @@
package com.zone.weixin4j.xml;
import com.zone.weixin4j.request.WeixinRequest;
import com.zone.weixin4j.socket.WeixinMessageTransfer;
import com.zone.weixin4j.type.AccountType;
import com.zone.weixin4j.util.ServerToolkits;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
/**
* 微信消息
*
* @className MessageTransferHandler
* @author jinyu(foxinmy@gmail.com)
* @date 2015年5月17日
* @since JDK 1.6
* @see
*/
public class MessageTransferHandler extends DefaultHandler {
private String fromUserName;
private String toUserName;
private String msgType;
private String eventType;
private boolean isQY;
private Set<String> nodeNames;
private String content;
@Override
public void startDocument() throws SAXException {
fromUserName = null;
toUserName = null;
msgType = null;
eventType = null;
isQY = false;
nodeNames = new HashSet<String>();
}
@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
nodeNames.add(localName);
localName = localName.toLowerCase();
if (localName.equals("fromusername")) {
fromUserName = content;
} else if (localName.equals("tousername")) {
toUserName = content;
} else if (localName.equals("msgtype")) {
msgType = content.toLowerCase();
} else if (localName.equals("event")) {
eventType = content.toLowerCase();
} else if (localName.startsWith("agent") // 应用信息
|| localName.startsWith("suite") // 套件信息
|| localName.equals("batchjob")) { // 批量任务
isQY = true;
}
}
@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
this.content = new String(ch, start, length);
}
private AccountType getAccountType() {
if (isQY) {
return AccountType.QY;
}
if (ServerToolkits.isBlank(msgType)
&& ServerToolkits.isBlank(eventType)) {
return null;
}
return AccountType.MP;
}
private static MessageTransferHandler global = new MessageTransferHandler();
public static WeixinMessageTransfer parser(WeixinRequest request)
throws RuntimeException {
try {
XMLReader xmlReader = XMLReaderFactory.createXMLReader();
xmlReader.setContentHandler(global);
xmlReader.parse(new InputSource(new ByteArrayInputStream(request
.getOriginalContent().getBytes(ServerToolkits.UTF_8))));
} catch (IOException e) {
throw new RuntimeException(e);
} catch (SAXException e) {
throw new RuntimeException(e);
}
return new WeixinMessageTransfer(request.getAesToken(),
request.getEncryptType(), global.toUserName,
global.fromUserName, global.getAccountType(), global.msgType,
global.eventType, global.nodeNames);
}
}