init weixin4j-server-with-springmvc
This commit is contained in:
parent
c76d07c8d5
commit
291c263a54
26
weixin4j-serverX/.classpath
Normal file
26
weixin4j-serverX/.classpath
Normal 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
1
weixin4j-serverX/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target/
|
||||
23
weixin4j-serverX/.project
Normal file
23
weixin4j-serverX/.project
Normal 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>
|
||||
@ -0,0 +1,4 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding//src/main/java=UTF-8
|
||||
encoding//src/test/java=UTF-8
|
||||
encoding/<project>=UTF-8
|
||||
5
weixin4j-serverX/.settings/org.eclipse.jdt.core.prefs
Normal file
5
weixin4j-serverX/.settings/org.eclipse.jdt.core.prefs
Normal 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
|
||||
4
weixin4j-serverX/.settings/org.eclipse.m2e.core.prefs
Normal file
4
weixin4j-serverX/.settings/org.eclipse.m2e.core.prefs
Normal file
@ -0,0 +1,4 @@
|
||||
activeProfiles=
|
||||
eclipse.preferences.version=1
|
||||
resolveWorkspaceProjects=true
|
||||
version=1
|
||||
48
weixin4j-serverX/pom.xml
Normal file
48
weixin4j-serverX/pom.xml
Normal 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>
|
||||
@ -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 {
|
||||
}
|
||||
@ -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 {
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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 > 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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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 + "]";
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
@ -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() + "]";
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
+ "]";
|
||||
}
|
||||
}
|
||||
@ -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() + "]";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
普通消息
|
||||
-------
|
||||
|
||||
当普通微信用户向公众账号发消息时,微信服务器将POST消息的XML数据包到开发者填写的URL上。
|
||||
|
||||
微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次
|
||||
|
||||
关于重试的消息排重,推荐使用msgid排重。
|
||||
|
||||
假如服务器无法保证在五秒内处理并回复,可以直接回复空串,微信服务器不会对此作任何处理,并且不会发起重试。
|
||||
@ -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()
|
||||
+ "]";
|
||||
}
|
||||
}
|
||||
@ -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() + "]";
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
/**
|
||||
* 语音格式,如amr,speex等
|
||||
*/
|
||||
@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()
|
||||
+ "]";
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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() + "]";
|
||||
}
|
||||
}
|
||||
@ -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() + "]";
|
||||
}
|
||||
}
|
||||
@ -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() + "]";
|
||||
}
|
||||
}
|
||||
@ -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() + "]";
|
||||
}
|
||||
}
|
||||
@ -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() + "]";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
菜单事件消息
|
||||
|
||||
用户点击自定义菜单后,微信会把点击事件推送给开发者,请注意,点击菜单弹出子菜单,不会产生上报。请注意,第3个到第8个的所有事件,仅支持微信iPhone5.4.1以上版本,和Android5.4以上版本的微信用户,旧版本微信用户点击后将没有回应,开发者也不能正常接收到事件推送。
|
||||
@ -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
|
||||
}
|
||||
@ -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 + "]";
|
||||
}
|
||||
}
|
||||
@ -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() + "]";
|
||||
}
|
||||
}
|
||||
@ -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() + "]";
|
||||
}
|
||||
}
|
||||
@ -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 + "]";
|
||||
}
|
||||
}
|
||||
@ -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 success”或“send fail”或“err(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() + "]";
|
||||
}
|
||||
}
|
||||
@ -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() + "]";
|
||||
}
|
||||
}
|
||||
@ -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() + "]";
|
||||
}
|
||||
}
|
||||
@ -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() + "]";
|
||||
}
|
||||
}
|
||||
@ -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() + "]";
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
+ "]";
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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 + "]";
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
+ "]";
|
||||
}
|
||||
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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;
|
||||
/**
|
||||
* 回调包ID,uint64类型,企业内唯一
|
||||
*/
|
||||
@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 + "]";
|
||||
}
|
||||
}
|
||||
@ -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() + "]";
|
||||
}
|
||||
}
|
||||
@ -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() + "]";
|
||||
}
|
||||
}
|
||||
@ -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() + "]";
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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 + "]";
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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 + "]";
|
||||
}
|
||||
}
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
@ -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";
|
||||
}
|
||||
}
|
||||
@ -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";
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
/**
|
||||
* 图片链接,支持JPG、PNG格式,较好的效果为大图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 + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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 + "]";
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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";
|
||||
}
|
||||
}
|
||||
@ -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";
|
||||
}
|
||||
}
|
||||
@ -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";
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
@ -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);
|
||||
|
||||
}
|
||||
@ -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;
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
WeixinMessageDecoder:对微信消息解码
|
||||
|
||||
WeixinRequestHandler:微信请求处理类
|
||||
|
||||
WeixinResponseEncoder:对微信回复编码
|
||||
|
||||
SingleResponseEncoder:对微信单一回复编码
|
||||
@ -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 + "]";
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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();
|
||||
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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 + "]";
|
||||
}
|
||||
}
|
||||
@ -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"));
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user