@@ -12,6 +12,7 @@ | |||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||||
<downloadJavadocs>true</downloadJavadocs> | <downloadJavadocs>true</downloadJavadocs> | ||||
<downloadSources>true</downloadSources> | <downloadSources>true</downloadSources> | ||||
<httpclient.version>4.3.5</httpclient.version> | |||||
</properties> | </properties> | ||||
<dependencies> | <dependencies> | ||||
<dependency> | <dependency> | ||||
@@ -23,8 +24,13 @@ | |||||
<dependency> | <dependency> | ||||
<groupId>org.apache.httpcomponents</groupId> | <groupId>org.apache.httpcomponents</groupId> | ||||
<artifactId>fluent-hc</artifactId> | <artifactId>fluent-hc</artifactId> | ||||
<version>4.3.5</version> | |||||
<version>${httpclient.version}</version> | |||||
</dependency> | </dependency> | ||||
<dependency> | |||||
<groupId>org.apache.httpcomponents</groupId> | |||||
<artifactId>httpmime</artifactId> | |||||
<version>${httpclient.version}</version> | |||||
</dependency> | |||||
<dependency> | <dependency> | ||||
<groupId>javax.xml.bind</groupId> | <groupId>javax.xml.bind</groupId> | ||||
<artifactId>jaxb-api</artifactId> | <artifactId>jaxb-api</artifactId> | ||||
@@ -20,6 +20,17 @@ public class WxConsts { | |||||
public static final String EVT_VIEW = "VIEW"; | public static final String EVT_VIEW = "VIEW"; | ||||
public static final String EVT_MASS_SEND_JOB_FINISH = "MASSSENDJOBFINISH"; | public static final String EVT_MASS_SEND_JOB_FINISH = "MASSSENDJOBFINISH"; | ||||
public static final String MEDIA_IMAGE = "image"; | |||||
public static final String MEDIA_VOICE = "voice"; | |||||
public static final String MEDIA_VIDEO = "video"; | |||||
public static final String MEDIA_THUMB = "thumb"; | |||||
public static final String FILE_JPG = "jpeg"; | |||||
public static final String FILE_PNG = "png"; | |||||
public static final String FILE_MP3 = "mp3"; | |||||
public static final String FILE_ARM = "arm"; | |||||
public static final String FILE_MP4 = "mp4"; | |||||
public static final String ST_SEND_SUCCESS = "send success"; | public static final String ST_SEND_SUCCESS = "send success"; | ||||
public static final String ST_SEND_FAIL = "send fail"; | public static final String ST_SEND_FAIL = "send fail"; | ||||
public static final String ST_涉嫌广告 = "err(10001)"; | public static final String ST_涉嫌广告 = "err(10001)"; | ||||
@@ -1,7 +1,12 @@ | |||||
package chanjarster.weixin.api; | package chanjarster.weixin.api; | ||||
import java.io.File; | |||||
import java.io.IOException; | |||||
import java.io.InputStream; | |||||
import chanjarster.weixin.bean.WxCustomMessage; | import chanjarster.weixin.bean.WxCustomMessage; | ||||
import chanjarster.weixin.bean.WxMenu; | import chanjarster.weixin.bean.WxMenu; | ||||
import chanjarster.weixin.bean.result.WxUploadResult; | |||||
import chanjarster.weixin.exception.WxErrorException; | import chanjarster.weixin.exception.WxErrorException; | ||||
/** | /** | ||||
@@ -24,7 +29,7 @@ public interface WxService { | |||||
/** | /** | ||||
* <pre> | * <pre> | ||||
* 获取access_token,本方法线程安全 | * 获取access_token,本方法线程安全 | ||||
* 且在多线程同时刷新时只刷新一次,避免超出200次/日的调用次数上限 | |||||
* 且在多线程同时刷新时只刷新一次,避免超出2000次/日的调用次数上限 | |||||
* | * | ||||
* 另:本service的所有方法都会在access_token过期是调用此方法 | * 另:本service的所有方法都会在access_token过期是调用此方法 | ||||
* | * | ||||
@@ -36,6 +41,32 @@ public interface WxService { | |||||
*/ | */ | ||||
public void refreshAccessToken() throws WxErrorException; | public void refreshAccessToken() throws WxErrorException; | ||||
/** | |||||
* <pre> | |||||
* 上传多媒体文件 | |||||
* 上传的多媒体文件有格式和大小限制,如下: | |||||
* 图片(image): 1M,支持JPG格式 | |||||
* 语音(voice):2M,播放长度不超过60s,支持AMR\MP3格式 | |||||
* 视频(video):10MB,支持MP4格式 | |||||
* 缩略图(thumb):64KB,支持JPG格式 | |||||
* | |||||
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=上传下载多媒体文件 | |||||
* </pre> | |||||
* @param mediaType 媒体类型, 请看{@link WxConsts} | |||||
* @param fileType 文件类型,请看{@link WxConsts} | |||||
* @param inputStream 输入流 | |||||
* @throws WxErrorException | |||||
*/ | |||||
public WxUploadResult uploadMedia(String mediaType, String fileType, InputStream inputStream) throws WxErrorException, IOException; | |||||
/** | |||||
* @see #uploadMedia(String, String, InputStream) | |||||
* @param mediaType | |||||
* @param file | |||||
* @throws WxErrorException | |||||
*/ | |||||
public WxUploadResult uploadMedia(String mediaType, File file) throws WxErrorException; | |||||
/** | /** | ||||
* <pre> | * <pre> | ||||
* 发送客服消息 | * 发送客服消息 | ||||
@@ -1,26 +1,33 @@ | |||||
package chanjarster.weixin.api; | package chanjarster.weixin.api; | ||||
import java.io.File; | |||||
import java.io.FileOutputStream; | |||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.io.InputStream; | |||||
import java.security.MessageDigest; | import java.security.MessageDigest; | ||||
import java.util.Arrays; | import java.util.Arrays; | ||||
import java.util.UUID; | |||||
import java.util.concurrent.atomic.AtomicBoolean; | import java.util.concurrent.atomic.AtomicBoolean; | ||||
import org.apache.commons.lang3.StringUtils; | import org.apache.commons.lang3.StringUtils; | ||||
import org.apache.http.Consts; | import org.apache.http.Consts; | ||||
import org.apache.http.HttpEntity; | |||||
import org.apache.http.client.ClientProtocolException; | import org.apache.http.client.ClientProtocolException; | ||||
import org.apache.http.client.methods.CloseableHttpResponse; | import org.apache.http.client.methods.CloseableHttpResponse; | ||||
import org.apache.http.client.methods.HttpGet; | import org.apache.http.client.methods.HttpGet; | ||||
import org.apache.http.client.methods.HttpPost; | import org.apache.http.client.methods.HttpPost; | ||||
import org.apache.http.entity.ContentType; | import org.apache.http.entity.ContentType; | ||||
import org.apache.http.entity.StringEntity; | import org.apache.http.entity.StringEntity; | ||||
import org.apache.http.entity.mime.MultipartEntityBuilder; | |||||
import org.apache.http.impl.client.BasicResponseHandler; | import org.apache.http.impl.client.BasicResponseHandler; | ||||
import org.apache.http.impl.client.CloseableHttpClient; | import org.apache.http.impl.client.CloseableHttpClient; | ||||
import org.apache.http.impl.client.HttpClients; | import org.apache.http.impl.client.HttpClients; | ||||
import chanjarster.weixin.bean.WxAccessToken; | import chanjarster.weixin.bean.WxAccessToken; | ||||
import chanjarster.weixin.bean.WxCustomMessage; | import chanjarster.weixin.bean.WxCustomMessage; | ||||
import chanjarster.weixin.bean.WxError; | |||||
import chanjarster.weixin.bean.WxMenu; | import chanjarster.weixin.bean.WxMenu; | ||||
import chanjarster.weixin.bean.result.WxError; | |||||
import chanjarster.weixin.bean.result.WxUploadResult; | |||||
import chanjarster.weixin.exception.WxErrorException; | import chanjarster.weixin.exception.WxErrorException; | ||||
import chanjarster.weixin.util.Utf8ResponseHandler; | import chanjarster.weixin.util.Utf8ResponseHandler; | ||||
@@ -132,11 +139,21 @@ public class WxServiceImpl implements WxService { | |||||
} | } | ||||
} | } | ||||
protected String post(String uri, String data) throws WxErrorException { | |||||
public WxUploadResult uploadMedia(String mediaType, String fileType, InputStream inputStream) throws WxErrorException, IOException { | |||||
return uploadMedia(mediaType, createTmpFile(inputStream, fileType)); | |||||
} | |||||
public WxUploadResult uploadMedia(String mediaType, File file) throws WxErrorException { | |||||
String url = "http://file.api.weixin.qq.com/cgi-bin/media/upload?type=" + mediaType; | |||||
String json = post(url, file); | |||||
return WxUploadResult.fromJson(json); | |||||
} | |||||
protected String post(String uri, Object data) throws WxErrorException { | |||||
return execute("POST", uri, data); | return execute("POST", uri, data); | ||||
} | } | ||||
protected String get(String uri, String data) throws WxErrorException { | |||||
protected String get(String uri, Object data) throws WxErrorException { | |||||
return execute("GET", uri, data); | return execute("GET", uri, data); | ||||
} | } | ||||
@@ -146,7 +163,7 @@ public class WxServiceImpl implements WxService { | |||||
* @return 微信服务端返回的结果 | * @return 微信服务端返回的结果 | ||||
* @throws WxErrorException | * @throws WxErrorException | ||||
*/ | */ | ||||
protected String execute(String method, String uri, String data) throws WxErrorException { | |||||
protected String execute(String method, String uri, Object data) throws WxErrorException { | |||||
if (StringUtils.isBlank(wxConfigStorage.getAccessToken())) { | if (StringUtils.isBlank(wxConfigStorage.getAccessToken())) { | ||||
refreshAccessToken(); | refreshAccessToken(); | ||||
} | } | ||||
@@ -160,18 +177,30 @@ public class WxServiceImpl implements WxService { | |||||
if ("POST".equals(method)) { | if ("POST".equals(method)) { | ||||
HttpPost httpPost = new HttpPost(uriWithAccessToken); | HttpPost httpPost = new HttpPost(uriWithAccessToken); | ||||
if (data != null) { | if (data != null) { | ||||
StringEntity entity = new StringEntity(data, Consts.UTF_8); | |||||
httpPost.setEntity(entity); | |||||
if (data instanceof String) { | |||||
StringEntity entity = new StringEntity((String)data, Consts.UTF_8); | |||||
httpPost.setEntity(entity); | |||||
} | |||||
if (data instanceof File) { | |||||
File file = (File) data; | |||||
HttpEntity entity = MultipartEntityBuilder | |||||
.create() | |||||
.addBinaryBody("media", file) | |||||
.build(); | |||||
httpPost.setEntity(entity); | |||||
httpPost.setHeader("Content-Type", ContentType.MULTIPART_FORM_DATA.toString()); | |||||
} | |||||
} | } | ||||
CloseableHttpResponse response = httpclient.execute(httpPost); | CloseableHttpResponse response = httpclient.execute(httpPost); | ||||
resultContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); | resultContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); | ||||
} else if ("GET".equals(method)) { | } else if ("GET".equals(method)) { | ||||
if (data != null) { | if (data != null) { | ||||
uriWithAccessToken += uriWithAccessToken.endsWith("&") ? data : '&' + data; | |||||
if (data instanceof String) { | |||||
uriWithAccessToken += uriWithAccessToken.endsWith("&") ? data : '&' + (String)data; | |||||
} | |||||
} | } | ||||
HttpGet httpGet = new HttpGet(uriWithAccessToken); | HttpGet httpGet = new HttpGet(uriWithAccessToken); | ||||
CloseableHttpResponse response = httpclient.execute(httpGet); | CloseableHttpResponse response = httpclient.execute(httpGet); | ||||
response.setHeader("Content-Type", ContentType.APPLICATION_JSON.toString()); | |||||
resultContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); | resultContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); | ||||
} | } | ||||
@@ -196,8 +225,38 @@ public class WxServiceImpl implements WxService { | |||||
} | } | ||||
} | } | ||||
protected File createTmpFile(InputStream inputStream, String fileType) throws IOException { | |||||
FileOutputStream fos = null; | |||||
try { | |||||
File tmpFile = File.createTempFile(UUID.randomUUID().toString(), '.' + fileType); | |||||
tmpFile.deleteOnExit(); | |||||
fos = new FileOutputStream(tmpFile); | |||||
int read = 0; | |||||
byte[] bytes = new byte[1024 * 100]; | |||||
while ((read = inputStream.read(bytes)) != -1) { | |||||
fos.write(bytes, 0, read); | |||||
} | |||||
fos.flush(); | |||||
return tmpFile; | |||||
} finally { | |||||
if (inputStream != null) { | |||||
try { | |||||
inputStream.close(); | |||||
} catch (IOException e) { | |||||
} | |||||
} | |||||
if (fos != null) { | |||||
try { | |||||
fos.close(); | |||||
} catch (IOException e) { | |||||
} | |||||
} | |||||
} | |||||
} | |||||
public void setWxConfigStorage(WxConfigStorage wxConfigProvider) { | public void setWxConfigStorage(WxConfigStorage wxConfigProvider) { | ||||
this.wxConfigStorage = wxConfigProvider; | this.wxConfigStorage = wxConfigProvider; | ||||
} | } | ||||
} | } |
@@ -1,4 +1,4 @@ | |||||
package chanjarster.weixin.bean; | |||||
package chanjarster.weixin.bean.result; | |||||
import java.util.HashMap; | import java.util.HashMap; | ||||
import java.util.Map; | import java.util.Map; |
@@ -0,0 +1,54 @@ | |||||
package chanjarster.weixin.bean.result; | |||||
import chanjarster.weixin.util.WxGsonBuilder; | |||||
public class WxUploadResult { | |||||
protected String type; | |||||
protected String media_id; | |||||
protected String thumb_media_id; | |||||
protected long created_at; | |||||
public String getType() { | |||||
return type; | |||||
} | |||||
public void setType(String type) { | |||||
this.type = type; | |||||
} | |||||
public String getMedia_id() { | |||||
return media_id; | |||||
} | |||||
public void setMedia_id(String media_id) { | |||||
this.media_id = media_id; | |||||
} | |||||
public long getCreated_at() { | |||||
return created_at; | |||||
} | |||||
public void setCreated_at(long created_at) { | |||||
this.created_at = created_at; | |||||
} | |||||
public static WxUploadResult fromJson(String json) { | |||||
return WxGsonBuilder.create().fromJson(json, WxUploadResult.class); | |||||
} | |||||
public String getThumb_media_id() { | |||||
return thumb_media_id; | |||||
} | |||||
public void setThumb_media_id(String thumb_media_id) { | |||||
this.thumb_media_id = thumb_media_id; | |||||
} | |||||
@Override | |||||
public String toString() { | |||||
return "WxUploadResult [type=" + type + ", media_id=" + media_id + ", thumb_media_id=" + thumb_media_id | |||||
+ ", created_at=" + created_at + "]"; | |||||
} | |||||
} |
@@ -1,6 +1,6 @@ | |||||
package chanjarster.weixin.exception; | package chanjarster.weixin.exception; | ||||
import chanjarster.weixin.bean.WxError; | |||||
import chanjarster.weixin.bean.result.WxError; | |||||
public class WxErrorException extends Exception { | public class WxErrorException extends Exception { | ||||
@@ -1,5 +1,6 @@ | |||||
package chanjarster.weixin.api; | package chanjarster.weixin.api; | ||||
import java.io.IOException; | |||||
import java.io.InputStream; | import java.io.InputStream; | ||||
import javax.xml.bind.JAXBException; | import javax.xml.bind.JAXBException; | ||||
@@ -16,6 +17,7 @@ import org.testng.annotations.Test; | |||||
import chanjarster.weixin.bean.WxCustomMessage; | import chanjarster.weixin.bean.WxCustomMessage; | ||||
import chanjarster.weixin.bean.WxMenu; | import chanjarster.weixin.bean.WxMenu; | ||||
import chanjarster.weixin.bean.WxMenu.WxMenuButton; | import chanjarster.weixin.bean.WxMenu.WxMenuButton; | ||||
import chanjarster.weixin.bean.result.WxUploadResult; | |||||
import chanjarster.weixin.exception.WxErrorException; | import chanjarster.weixin.exception.WxErrorException; | ||||
import chanjarster.weixin.util.XmlTransformer; | import chanjarster.weixin.util.XmlTransformer; | ||||
@@ -26,12 +28,12 @@ public class WxServiceTest { | |||||
@BeforeTest | @BeforeTest | ||||
public void prepare() throws JAXBException { | public void prepare() throws JAXBException { | ||||
InputStream is1 = ClassLoader.getSystemResourceAsStream("test-config.xml"); | InputStream is1 = ClassLoader.getSystemResourceAsStream("test-config.xml"); | ||||
WxXmlConfigStorage config1 = XmlTransformer.fromXml(WxXmlConfigStorage.class, is1); | |||||
WxXmlConfigStorage config = XmlTransformer.fromXml(WxXmlConfigStorage.class, is1); | |||||
this.wxService = new WxServiceImpl(); | this.wxService = new WxServiceImpl(); | ||||
this.wxService.setWxConfigStorage(config1); | |||||
this.wxService.setWxConfigStorage(config); | |||||
} | } | ||||
@Test | |||||
@Test() | |||||
public void testRefreshAccessToken() throws WxErrorException { | public void testRefreshAccessToken() throws WxErrorException { | ||||
WxConfigStorage configStorage = wxService.wxConfigStorage; | WxConfigStorage configStorage = wxService.wxConfigStorage; | ||||
String before = configStorage.getAccessToken(); | String before = configStorage.getAccessToken(); | ||||
@@ -43,7 +45,7 @@ public class WxServiceTest { | |||||
Assert.assertTrue(StringUtils.isNotBlank(after)); | Assert.assertTrue(StringUtils.isNotBlank(after)); | ||||
} | } | ||||
@Test(dependsOnMethods = "testRefreshAccessToken") | |||||
@Test(dependsOnMethods = "testRefreshAccessToken", enabled = false) | |||||
public void sendCustomMessage() throws WxErrorException { | public void sendCustomMessage() throws WxErrorException { | ||||
WxXmlConfigStorage configProvider = (WxXmlConfigStorage) wxService.wxConfigStorage; | WxXmlConfigStorage configProvider = (WxXmlConfigStorage) wxService.wxConfigStorage; | ||||
WxCustomMessage message = new WxCustomMessage(); | WxCustomMessage message = new WxCustomMessage(); | ||||
@@ -54,22 +56,41 @@ public class WxServiceTest { | |||||
wxService.sendCustomMessage(message); | wxService.sendCustomMessage(message); | ||||
} | } | ||||
@Test(dataProvider = "menu", dependsOnMethods = "testRefreshAccessToken") | |||||
@Test(dataProvider = "menu", dependsOnMethods = "testRefreshAccessToken", enabled = false) | |||||
public void testCreateMenu(WxMenu wxMenu) throws WxErrorException { | public void testCreateMenu(WxMenu wxMenu) throws WxErrorException { | ||||
wxService.createMenu(wxMenu); | wxService.createMenu(wxMenu); | ||||
} | } | ||||
@Test(dependsOnMethods = { "testRefreshAccessToken" , "testCreateMenu"}) | |||||
@Test(dependsOnMethods = { "testRefreshAccessToken" , "testCreateMenu"}, enabled = false) | |||||
public void testGetMenu() throws WxErrorException { | public void testGetMenu() throws WxErrorException { | ||||
Assert.assertNotNull(wxService.getMenu()); | Assert.assertNotNull(wxService.getMenu()); | ||||
} | } | ||||
@Test(dependsOnMethods = { "testRefreshAccessToken", "testGetMenu" }) | |||||
@Test(dependsOnMethods = { "testRefreshAccessToken", "testGetMenu" }, enabled = false) | |||||
public void testDeleteMenu() throws WxErrorException { | public void testDeleteMenu() throws WxErrorException { | ||||
wxService.deleteMenu(); | wxService.deleteMenu(); | ||||
} | } | ||||
@Test | |||||
@Test(dependsOnMethods = { "testRefreshAccessToken" }, dataProvider="uploadFiles", enabled = true) | |||||
public void testUploadMedia1(String mediaType, String fileType, String fileName) throws WxErrorException, IOException { | |||||
InputStream inputStream = ClassLoader.getSystemResourceAsStream(fileName); | |||||
WxUploadResult res = wxService.uploadMedia(mediaType, fileType, inputStream); | |||||
System.out.println(res.toString()); | |||||
} | |||||
@DataProvider | |||||
public Object[][] uploadFiles() { | |||||
return new Object[][] { | |||||
new Object[] { WxConsts.MEDIA_IMAGE, WxConsts.FILE_PNG, "mm.png" }, | |||||
new Object[] { WxConsts.MEDIA_IMAGE, WxConsts.FILE_JPG, "mm.jpeg" }, | |||||
new Object[] { WxConsts.MEDIA_VOICE, WxConsts.FILE_MP3, "mm.mp3" }, | |||||
new Object[] { WxConsts.MEDIA_VIDEO, WxConsts.FILE_MP4, "mm.mp4" }, | |||||
new Object[] { WxConsts.MEDIA_THUMB, WxConsts.FILE_PNG, "mm.png" }, | |||||
new Object[] { WxConsts.MEDIA_THUMB, WxConsts.FILE_JPG, "mm.jpeg" } | |||||
}; | |||||
} | |||||
@Test(enabled = false) | |||||
public void testCheckSignature() throws WxErrorException { | public void testCheckSignature() throws WxErrorException { | ||||
String timestamp = "23234235423246"; | String timestamp = "23234235423246"; | ||||
String nonce = "y7didfkcmvnbd90sdofjkiefhsd"; | String nonce = "y7didfkcmvnbd90sdofjkiefhsd"; | ||||
@@ -3,6 +3,8 @@ package chanjarster.weixin.bean; | |||||
import org.testng.Assert; | import org.testng.Assert; | ||||
import org.testng.annotations.Test; | import org.testng.annotations.Test; | ||||
import chanjarster.weixin.bean.result.WxError; | |||||
@Test | @Test | ||||
public class WxErrorTest { | public class WxErrorTest { | ||||