├── .gitignore ├── README.md ├── app-demo ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── riversoft │ │ └── weixin │ │ └── demo │ │ └── app │ │ ├── Application.java │ │ └── WxCallbackController.java │ └── resources │ ├── application.properties │ └── wx-app-settings-test.xml ├── commons ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── riversoft │ └── weixin │ └── demo │ └── commons │ ├── DefaultDuplicatedMessageChecker.java │ └── DuplicatedMessageChecker.java ├── mp-demo ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── riversoft │ │ └── weixin │ │ └── demo │ │ └── mp │ │ ├── Application.java │ │ ├── MpOAuthCallbackController.java │ │ └── WxCallbackController.java │ └── resources │ ├── application.properties │ └── wx-mp-settings-test.xml ├── open-demo ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── riversoft │ │ └── weixin │ │ └── demo │ │ └── open │ │ └── Application.java │ └── resources │ ├── application.properties │ └── wx-open-settings-test.xml ├── pay-demo ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── riversoft │ │ └── weixin │ │ └── demo │ │ └── pay │ │ └── Application.java │ └── resources │ ├── application.properties │ └── wx-pay-settings-test.xml ├── pom.xml └── qydev-demo ├── pom.xml └── src └── main ├── java └── com │ └── riversoft │ └── weixin │ └── demo │ └── qydev │ ├── Application.java │ └── WxCallbackController.java └── resources ├── application.properties └── wx-qy-settings-test.xml /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | .idea/ 4 | *.iml 5 | target/ 6 | pom.xml.tag 7 | pom.xml.releaseBackup 8 | pom.xml.versionsBackup 9 | pom.xml.next 10 | release.properties 11 | dependency-reduced-pom.xml 12 | buildNumber.properties 13 | .mvn/timing.properties 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # weixin-sdk-demo 2 | 3 | 使用spring boot,基于weixin-sdk开发的web应用。 4 | 5 | ## 如何使用? 6 | 7 | ### 企业号 8 | - 修改 wx-qy-settings-test.xml 文件, 根据您的情况修改corpId, corpSecret相关信息。 9 | - 修改application.properties 中默认的agent配置 10 | - agent.default.token=xxxxxxxxx 11 | - agent.default.aesKey=xxxxxxxxxxxxxxxxx 12 | 13 | ### 公众号 14 | - 修改wx-mp-settings-test.xml 文件, 根据您的情况修改appId, appSecret相关信息。 15 | 16 | ### 开放平台 17 | - 修改wx-open-settings-test.xml 文件, 根据您的情况修改appId, appSecret相关信息。 18 | 19 | ### 微信支付 20 | - 修改wx-pay-settings-test.xml 文件, 根据您的情况修改appId, mchID, key, certPath, certPass相关信息。 21 | 22 | ### 小程序 23 | - 修改wx-app-settings-test.xml 文件, 根据您的情况修改appId, appSecret相关信息。 24 | 25 | ### 修改应用服务器端口 26 | - 修改文件:application.properties 27 | 28 | ### 启动 29 | - com.riversoft.weixin.demo.mp.Application 30 | - com.riversoft.weixin.demo.qydev.Application 31 | - com.riversoft.weixin.demo.open.Application 32 | - com.riversoft.weixin.demo.pay.Application 33 | - com.riversoft.weixin.demo.app.Application 34 | 35 | 36 | -------------------------------------------------------------------------------- /app-demo/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | com.riversoft 7 | weixin-sdk-demo 8 | 0.0.1-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | app-demo 13 | Demo for APP 14 | 15 | 16 | 17 | org.springframework.boot 18 | spring-boot-starter-web 19 | 20 | 21 | 22 | com.riversoft 23 | commons 24 | ${project.version} 25 | 26 | 27 | 28 | cn.com.riversoft 29 | weixin-app 30 | 31 | 32 | 33 | commons-io 34 | commons-io 35 | 36 | 37 | 38 | com.fasterxml.jackson.core 39 | jackson-databind 40 | 41 | 42 | com.fasterxml.jackson.core 43 | jackson-annotations 44 | 45 | 46 | 47 | org.codehaus.jackson 48 | jackson-mapper-asl 49 | 50 | 51 | com.fasterxml.jackson.dataformat 52 | jackson-dataformat-xml 53 | 54 | 55 | junit 56 | junit 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /app-demo/src/main/java/com/riversoft/weixin/demo/app/Application.java: -------------------------------------------------------------------------------- 1 | package com.riversoft.weixin.demo.app; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | /** 7 | * Created by exizhai on 10/7/2015. 8 | */ 9 | @SpringBootApplication 10 | public class Application { 11 | 12 | public static void main(String[] args) { 13 | SpringApplication.run(Application.class, args); 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /app-demo/src/main/java/com/riversoft/weixin/demo/app/WxCallbackController.java: -------------------------------------------------------------------------------- 1 | package com.riversoft.weixin.demo.app; 2 | 3 | import com.riversoft.weixin.app.base.AppSetting; 4 | import com.riversoft.weixin.app.care.CareMessages; 5 | import com.riversoft.weixin.app.message.AppXmlMessages; 6 | import com.riversoft.weixin.app.template.Message; 7 | import com.riversoft.weixin.app.template.Templates; 8 | import com.riversoft.weixin.common.decrypt.AesException; 9 | import com.riversoft.weixin.common.decrypt.MessageDecryption; 10 | import com.riversoft.weixin.common.decrypt.SHA1; 11 | import com.riversoft.weixin.common.message.XmlMessageHeader; 12 | import com.riversoft.weixin.demo.commons.DuplicatedMessageChecker; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.stereotype.Controller; 17 | import org.springframework.util.StringUtils; 18 | import org.springframework.web.bind.annotation.RequestBody; 19 | import org.springframework.web.bind.annotation.RequestMapping; 20 | import org.springframework.web.bind.annotation.RequestParam; 21 | import org.springframework.web.bind.annotation.ResponseBody; 22 | 23 | /** 24 | * @borball on 12/29/2016. 25 | */ 26 | @Controller 27 | public class WxCallbackController { 28 | 29 | private static Logger logger = LoggerFactory.getLogger(WxCallbackController.class); 30 | 31 | @Autowired 32 | private DuplicatedMessageChecker duplicatedMessageChecker; 33 | 34 | public void setDuplicatedMessageChecker(DuplicatedMessageChecker duplicatedMessageChecker) { 35 | this.duplicatedMessageChecker = duplicatedMessageChecker; 36 | } 37 | 38 | /** 39 | * 小程序回调接口 40 | * 41 | * @param signature 42 | * @param msg_signature 43 | * @param timestamp 44 | * @param nonce 45 | * @param echostr 46 | * @param encrypt_type 47 | * @param content 48 | * @return 49 | */ 50 | @RequestMapping("/wx/app") 51 | @ResponseBody 52 | public String mp(@RequestParam(value="signature") String signature, 53 | @RequestParam(value="msg_signature", required = false) String msg_signature, 54 | @RequestParam(value="timestamp") String timestamp, 55 | @RequestParam(value="nonce") String nonce, 56 | @RequestParam(value="echostr", required = false) String echostr, 57 | @RequestParam(value="encrypt_type", required = false) String encrypt_type, 58 | @RequestBody(required = false) String content) { 59 | 60 | logger.info("signature={}, msg_signature={}, timestamp={}, nonce={}, echostr={}, encrypt_type={}", signature, msg_signature, timestamp, nonce, echostr, encrypt_type); 61 | 62 | AppSetting appSetting = AppSetting.defaultSettings(); 63 | try { 64 | if(!SHA1.getSHA1(appSetting.getToken(), timestamp, nonce).equals(signature)) { 65 | logger.warn("invalid request."); 66 | return "invalid request."; 67 | } 68 | } catch (AesException e) { 69 | logger.error("check signature failed:", e); 70 | return "invalid request."; 71 | } 72 | 73 | if (!StringUtils.isEmpty(echostr)) { 74 | return echostr; 75 | } 76 | 77 | XmlMessageHeader xmlRequest = null; 78 | if("aes".equals(encrypt_type)) { 79 | try { 80 | MessageDecryption messageDecryption = new MessageDecryption(appSetting.getToken(), appSetting.getAesKey(), appSetting.getAppId()); 81 | xmlRequest = AppXmlMessages.fromXml(messageDecryption.decrypt(msg_signature, timestamp, nonce, content)); 82 | } catch (AesException e) { 83 | } 84 | } else { 85 | xmlRequest = AppXmlMessages.fromXml(content); 86 | } 87 | 88 | dispatch(xmlRequest); 89 | 90 | return ""; 91 | } 92 | 93 | /** 94 | * 具体业务逻辑 95 | * @param xmlRequest 96 | */ 97 | private void dispatch(XmlMessageHeader xmlRequest) { 98 | if(!duplicatedMessageChecker.isDuplicated(xmlRequest.getFromUser() + xmlRequest.getCreateTime().getTime())) { 99 | //如果有需要可以调用客服接口或者模板消息接口发送消息给用户 100 | //Message message = new Message(); 101 | //Templates.defaultTemplates().send(message); 102 | 103 | //CareMessages.defaultCareMessages().text(xmlRequest.getFromUser(), "Hello!"); 104 | //CareMessages.defaultCareMessages().image(xmlRequest.getFromUser(), "image_media_id"); 105 | } else { 106 | logger.warn("Duplicated message: {} @ {}", xmlRequest.getMsgType(), xmlRequest.getFromUser()); 107 | } 108 | 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /app-demo/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=48084 2 | -------------------------------------------------------------------------------- /app-demo/src/main/resources/wx-app-settings-test.xml: -------------------------------------------------------------------------------- 1 | 2 | wx122233344555555 3 | zzzzzzzzzzzzzzzzzzz 4 | xxxxxxxxxxxxxxxx 5 | tttttttttttttttttttttttttttttttt 6 | 9 | -------------------------------------------------------------------------------- /commons/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | com.riversoft 7 | weixin-sdk-demo 8 | 0.0.1-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | commons 13 | Demo commons 14 | 15 | 16 | 17 | org.springframework.boot 18 | spring-boot-starter-web 19 | 20 | 21 | com.google.guava 22 | guava 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /commons/src/main/java/com/riversoft/weixin/demo/commons/DefaultDuplicatedMessageChecker.java: -------------------------------------------------------------------------------- 1 | package com.riversoft.weixin.demo.commons; 2 | 3 | import com.google.common.cache.Cache; 4 | import com.google.common.cache.CacheBuilder; 5 | import org.springframework.stereotype.Component; 6 | 7 | import java.util.concurrent.TimeUnit; 8 | 9 | /** 10 | * Created by exizhai on 11/15/2015. 11 | */ 12 | @Component 13 | public class DefaultDuplicatedMessageChecker implements DuplicatedMessageChecker { 14 | 15 | private static Cache cache = CacheBuilder.newBuilder().expireAfterWrite(15, TimeUnit.SECONDS).maximumSize(10000).build(); 16 | 17 | @Override 18 | public boolean isDuplicated(String msgKey) { 19 | if(cache.getIfPresent(msgKey) == null) { 20 | cache.put(msgKey, msgKey); 21 | return false; 22 | } else { 23 | return true; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /commons/src/main/java/com/riversoft/weixin/demo/commons/DuplicatedMessageChecker.java: -------------------------------------------------------------------------------- 1 | package com.riversoft.weixin.demo.commons; 2 | 3 | /** 4 | * Created by exizhai on 11/15/2015. 5 | */ 6 | public interface DuplicatedMessageChecker { 7 | 8 | boolean isDuplicated(String msgKey); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /mp-demo/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | com.riversoft 7 | weixin-sdk-demo 8 | 0.0.1-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | mp-demo 13 | Demo for MP 14 | 15 | 16 | 17 | com.riversoft 18 | commons 19 | ${project.version} 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-web 25 | 26 | 27 | 28 | cn.com.riversoft 29 | weixin-mp 30 | 31 | 32 | 33 | commons-io 34 | commons-io 35 | 36 | 37 | 38 | com.fasterxml.jackson.core 39 | jackson-databind 40 | 41 | 42 | com.fasterxml.jackson.core 43 | jackson-annotations 44 | 45 | 46 | 47 | org.codehaus.jackson 48 | jackson-mapper-asl 49 | 50 | 51 | com.fasterxml.jackson.dataformat 52 | jackson-dataformat-xml 53 | 54 | 55 | junit 56 | junit 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /mp-demo/src/main/java/com/riversoft/weixin/demo/mp/Application.java: -------------------------------------------------------------------------------- 1 | package com.riversoft.weixin.demo.mp; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | /** 7 | * Created by exizhai on 10/7/2015. 8 | */ 9 | @SpringBootApplication 10 | public class Application { 11 | 12 | public static void main(String[] args) { 13 | SpringApplication.run(Application.class, args); 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /mp-demo/src/main/java/com/riversoft/weixin/demo/mp/MpOAuthCallbackController.java: -------------------------------------------------------------------------------- 1 | package com.riversoft.weixin.demo.mp; 2 | 3 | import com.riversoft.weixin.common.oauth2.AccessToken; 4 | import com.riversoft.weixin.common.oauth2.OpenUser; 5 | import com.riversoft.weixin.mp.oauth2.MpOAuth2s; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RequestParam; 9 | 10 | /** 11 | * Created by exizhai on 12/17/2015. 12 | */ 13 | public class MpOAuthCallbackController { 14 | 15 | private static org.slf4j.Logger logger = LoggerFactory.getLogger(WxCallbackController.class); 16 | 17 | /** 18 | * 公众号OAuth回调接口 19 | * @return 20 | */ 21 | @RequestMapping("/wx/oauth/mp") 22 | public void mp(@RequestParam(value="code") String code, @RequestParam(value="state", required = false) String state) { 23 | 24 | logger.info("code:{}, state:{}", code, state); 25 | AccessToken accessToken = MpOAuth2s.defaultOAuth2s().getAccessToken(code); 26 | OpenUser openUser = MpOAuth2s.defaultOAuth2s().userInfo(accessToken.getAccessToken(), accessToken.getOpenId()); 27 | //do your logic 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /mp-demo/src/main/java/com/riversoft/weixin/demo/mp/WxCallbackController.java: -------------------------------------------------------------------------------- 1 | package com.riversoft.weixin.demo.mp; 2 | 3 | import com.riversoft.weixin.common.decrypt.AesException; 4 | import com.riversoft.weixin.common.decrypt.MessageDecryption; 5 | import com.riversoft.weixin.common.decrypt.SHA1; 6 | import com.riversoft.weixin.common.event.EventRequest; 7 | import com.riversoft.weixin.common.exception.WxRuntimeException; 8 | import com.riversoft.weixin.common.message.XmlMessageHeader; 9 | import com.riversoft.weixin.demo.commons.DuplicatedMessageChecker; 10 | import com.riversoft.weixin.mp.base.AppSetting; 11 | import com.riversoft.weixin.mp.care.CareMessages; 12 | import com.riversoft.weixin.mp.message.MpXmlMessages; 13 | import com.riversoft.weixin.mp.user.Users; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | import org.springframework.beans.factory.annotation.Autowired; 17 | import org.springframework.stereotype.Controller; 18 | import org.springframework.util.StringUtils; 19 | import org.springframework.web.bind.annotation.*; 20 | 21 | /** 22 | * Created by exizhai on 10/7/2015. 23 | */ 24 | @Controller 25 | public class WxCallbackController { 26 | 27 | private static Logger logger = LoggerFactory.getLogger(WxCallbackController.class); 28 | 29 | @Autowired 30 | private DuplicatedMessageChecker duplicatedMessageChecker; 31 | 32 | public void setDuplicatedMessageChecker(DuplicatedMessageChecker duplicatedMessageChecker) { 33 | this.duplicatedMessageChecker = duplicatedMessageChecker; 34 | } 35 | 36 | /** 37 | * 公众号回调接口 38 | * 这里为了演示方便使用单个固定URL,实际使用的时候一个一个系统可能有多个公众号,这样的话需要有个分发逻辑: 39 | * 比如callback url可以定义为 /wx/mp/[公众号的appId],通过appId构造不同的AppSetting 40 | * 41 | * @param signature 42 | * @param msg_signature 43 | * @param timestamp 44 | * @param nonce 45 | * @param echostr 46 | * @param encrypt_type 47 | * @param content 48 | * @return 49 | */ 50 | @RequestMapping("/wx/mp") 51 | @ResponseBody 52 | public String mp(@RequestParam(value="signature") String signature, 53 | @RequestParam(value="msg_signature", required = false) String msg_signature, 54 | @RequestParam(value="timestamp") String timestamp, 55 | @RequestParam(value="nonce") String nonce, 56 | @RequestParam(value="echostr", required = false) String echostr, 57 | @RequestParam(value="encrypt_type", required = false) String encrypt_type, 58 | @RequestBody(required = false) String content) { 59 | 60 | logger.info("signature={}, msg_signature={}, timestamp={}, nonce={}, echostr={}, encrypt_type={}", signature, msg_signature, timestamp, nonce, echostr, encrypt_type); 61 | 62 | AppSetting appSetting = AppSetting.defaultSettings(); 63 | try { 64 | if(!SHA1.getSHA1(appSetting.getToken(), timestamp, nonce).equals(signature)) { 65 | logger.warn("非法请求."); 66 | return "非法请求."; 67 | } 68 | } catch (AesException e) { 69 | logger.error("check signature failed:", e); 70 | return "非法请求."; 71 | } 72 | 73 | if (!StringUtils.isEmpty(echostr)) { 74 | return echostr; 75 | } 76 | 77 | XmlMessageHeader xmlRequest = null; 78 | if("aes".equals(encrypt_type)) { 79 | try { 80 | MessageDecryption messageDecryption = new MessageDecryption(appSetting.getToken(), appSetting.getAesKey(), appSetting.getAppId()); 81 | xmlRequest = MpXmlMessages.fromXml(messageDecryption.decrypt(msg_signature, timestamp, nonce, content)); 82 | XmlMessageHeader xmlResponse = mpDispatch(xmlRequest); 83 | 84 | if(xmlResponse != null) { 85 | try { 86 | return messageDecryption.encrypt(MpXmlMessages.toXml(xmlResponse), timestamp, nonce); 87 | } catch (WxRuntimeException e) { 88 | 89 | } 90 | } 91 | } catch (AesException e) { 92 | } 93 | } else { 94 | xmlRequest = MpXmlMessages.fromXml(content); 95 | XmlMessageHeader xmlResponse = mpDispatch(xmlRequest); 96 | if(xmlResponse != null) { 97 | try { 98 | return MpXmlMessages.toXml(xmlResponse); 99 | } catch (WxRuntimeException e) { 100 | } 101 | } 102 | } 103 | 104 | return ""; 105 | } 106 | 107 | private XmlMessageHeader mpDispatch(XmlMessageHeader xmlRequest) { 108 | if(!duplicatedMessageChecker.isDuplicated(xmlRequest.getFromUser() + xmlRequest.getCreateTime().getTime())) { 109 | String welcome = "您好:" + Users.defaultUsers().get(xmlRequest.getFromUser()).getNickName(); 110 | CareMessages.defaultCareMessages().text(xmlRequest.getFromUser(), welcome); 111 | 112 | if (xmlRequest instanceof EventRequest) { 113 | EventRequest eventRequest = (EventRequest) xmlRequest; 114 | logger.debug("事件请求[{}]", eventRequest.getEventType().name()); 115 | CareMessages.defaultCareMessages().text(xmlRequest.getFromUser(), "事件请求:" + eventRequest.getEventType().name()); 116 | } else { 117 | logger.debug("消息请求[{}]", xmlRequest.getMsgType().name()); 118 | CareMessages.defaultCareMessages().text(xmlRequest.getFromUser(), "消息请求:" + xmlRequest.getMsgType().name()); 119 | } 120 | } else { 121 | logger.warn("Duplicated message: {} @ {}", xmlRequest.getMsgType(), xmlRequest.getFromUser()); 122 | } 123 | 124 | //需要同步返回消息(被动回复)给用户则构造一个XmlMessageHeader类型,比较鸡肋,因为处理逻辑如果比较复杂响应太慢会影响用户感知,建议直接返回null; 125 | //要发送消息给用户可以参考上面的例子使用客服消息接口进行异步发送 126 | return null; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /mp-demo/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=48080 2 | -------------------------------------------------------------------------------- /mp-demo/src/main/resources/wx-mp-settings-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | wx74755fbba35c24da 4 | d4624c36b6795d1d99dcf0547af5443d 5 | wonskd113n 6 | lRE3qfnBHxqzBE5i12v6CzU257ERsOIeMKqfDPdFu5c 7 | 10 | -------------------------------------------------------------------------------- /open-demo/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | com.riversoft 7 | weixin-sdk-demo 8 | 0.0.1-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | open-demo 13 | Demo for OPEN 14 | 15 | 16 | 17 | com.riversoft 18 | commons 19 | ${project.version} 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-web 25 | 26 | 27 | 28 | cn.com.riversoft 29 | weixin-open 30 | 31 | 32 | 33 | commons-io 34 | commons-io 35 | 36 | 37 | 38 | com.fasterxml.jackson.core 39 | jackson-databind 40 | 41 | 42 | com.fasterxml.jackson.core 43 | jackson-annotations 44 | 45 | 46 | 47 | org.codehaus.jackson 48 | jackson-mapper-asl 49 | 50 | 51 | com.fasterxml.jackson.dataformat 52 | jackson-dataformat-xml 53 | 54 | 55 | junit 56 | junit 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /open-demo/src/main/java/com/riversoft/weixin/demo/open/Application.java: -------------------------------------------------------------------------------- 1 | package com.riversoft.weixin.demo.open; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | /** 7 | * Created by exizhai on 10/7/2015. 8 | */ 9 | @SpringBootApplication 10 | public class Application { 11 | 12 | public static void main(String[] args) { 13 | SpringApplication.run(Application.class, args); 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /open-demo/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=48083 2 | -------------------------------------------------------------------------------- /open-demo/src/main/resources/wx-open-settings-test.xml: -------------------------------------------------------------------------------- 1 | 2 | wx1111111111111111 3 | aaa111222333444555fffrrreeeddd1233 4 | 7 | -------------------------------------------------------------------------------- /pay-demo/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | com.riversoft 7 | weixin-sdk-demo 8 | 0.0.1-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | pay-demo 13 | Demo for PAY 14 | 15 | 16 | 17 | com.riversoft 18 | commons 19 | ${project.version} 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-web 25 | 26 | 27 | 28 | cn.com.riversoft 29 | weixin-pay 30 | 31 | 32 | 33 | commons-io 34 | commons-io 35 | 36 | 37 | 38 | com.fasterxml.jackson.core 39 | jackson-databind 40 | 41 | 42 | com.fasterxml.jackson.core 43 | jackson-annotations 44 | 45 | 46 | 47 | org.codehaus.jackson 48 | jackson-mapper-asl 49 | 50 | 51 | com.fasterxml.jackson.dataformat 52 | jackson-dataformat-xml 53 | 54 | 55 | junit 56 | junit 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /pay-demo/src/main/java/com/riversoft/weixin/demo/pay/Application.java: -------------------------------------------------------------------------------- 1 | package com.riversoft.weixin.demo.pay; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | /** 7 | * Created by exizhai on 10/7/2015. 8 | */ 9 | @SpringBootApplication 10 | public class Application { 11 | 12 | public static void main(String[] args) { 13 | SpringApplication.run(Application.class, args); 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /pay-demo/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=48082 2 | -------------------------------------------------------------------------------- /pay-demo/src/main/resources/wx-pay-settings-test.xml: -------------------------------------------------------------------------------- 1 | 2 | xxxxxxxxxxx 3 | yyyyyyyyyyy 4 | zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz 5 | /opt/weixin/... 6 | 12345678 7 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 1.4.3.RELEASE 9 | 10 | 4.0.0 11 | 12 | weixin-sdk-demo 13 | com.riversoft 14 | weixin SDK Demo 15 | 0.0.1-SNAPSHOT 16 | pom 17 | 18 | 19 | 2.4 20 | 0.9.6 21 | 2.8.5 22 | 1.9.13 23 | 18.0 24 | 25 | 26 | 27 | commons 28 | mp-demo 29 | qydev-demo 30 | open-demo 31 | pay-demo 32 | app-demo 33 | 34 | 35 | 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter-web 40 | 1.4.3.RELEASE 41 | 42 | 43 | 44 | cn.com.riversoft 45 | weixin-mp 46 | ${weixin-version} 47 | 48 | 49 | 50 | cn.com.riversoft 51 | weixin-qydev 52 | ${weixin-version} 53 | 54 | 55 | 56 | cn.com.riversoft 57 | weixin-open 58 | ${weixin-version} 59 | 60 | 61 | 62 | cn.com.riversoft 63 | weixin-pay 64 | ${weixin-version} 65 | 66 | 67 | 68 | cn.com.riversoft 69 | weixin-app 70 | ${weixin-version} 71 | 72 | 73 | 74 | commons-io 75 | commons-io 76 | ${commons-io.version} 77 | 78 | 79 | 80 | com.fasterxml.jackson.core 81 | jackson-databind 82 | ${jackson.version} 83 | 84 | 85 | com.fasterxml.jackson.core 86 | jackson-annotations 87 | ${jackson.version} 88 | 89 | 90 | 91 | org.codehaus.jackson 92 | jackson-mapper-asl 93 | ${jackson-mapper-asl.version} 94 | 95 | 96 | com.fasterxml.jackson.dataformat 97 | jackson-dataformat-xml 98 | ${jackson.version} 99 | 100 | 101 | 102 | com.google.guava 103 | guava 104 | ${guava.version} 105 | 106 | 107 | 108 | junit 109 | junit 110 | 4.12 111 | test 112 | 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /qydev-demo/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | com.riversoft 7 | weixin-sdk-demo 8 | 0.0.1-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | qydev-demo 13 | Demo for QYDEV 14 | 15 | 16 | 17 | org.springframework.boot 18 | spring-boot-starter-web 19 | 20 | 21 | 22 | cn.com.riversoft 23 | weixin-qydev 24 | 25 | 26 | 27 | commons-io 28 | commons-io 29 | 30 | 31 | 32 | com.fasterxml.jackson.core 33 | jackson-databind 34 | 35 | 36 | com.fasterxml.jackson.core 37 | jackson-annotations 38 | 39 | 40 | 41 | org.codehaus.jackson 42 | jackson-mapper-asl 43 | 44 | 45 | com.fasterxml.jackson.dataformat 46 | jackson-dataformat-xml 47 | 48 | 49 | junit 50 | junit 51 | 52 | 53 | com.riversoft 54 | commons 55 | 0.0.1-SNAPSHOT 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /qydev-demo/src/main/java/com/riversoft/weixin/demo/qydev/Application.java: -------------------------------------------------------------------------------- 1 | package com.riversoft.weixin.demo.qydev; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | /** 7 | * Created by exizhai on 10/7/2015. 8 | */ 9 | @SpringBootApplication 10 | public class Application { 11 | 12 | public static void main(String[] args) { 13 | SpringApplication.run(Application.class, args); 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /qydev-demo/src/main/java/com/riversoft/weixin/demo/qydev/WxCallbackController.java: -------------------------------------------------------------------------------- 1 | package com.riversoft.weixin.demo.qydev; 2 | 3 | import com.riversoft.weixin.common.decrypt.MessageDecryption; 4 | import com.riversoft.weixin.common.exception.WxRuntimeException; 5 | import com.riversoft.weixin.common.message.XmlMessageHeader; 6 | import com.riversoft.weixin.demo.commons.DuplicatedMessageChecker; 7 | import com.riversoft.weixin.qy.base.CorpSetting; 8 | import com.riversoft.weixin.qy.message.QyXmlMessages; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.beans.factory.annotation.Value; 13 | import org.springframework.stereotype.Controller; 14 | import org.springframework.util.StringUtils; 15 | import org.springframework.web.bind.annotation.*; 16 | 17 | /** 18 | * Created by exizhai on 10/7/2015. 19 | */ 20 | @Controller 21 | public class WxCallbackController { 22 | 23 | private static Logger logger = LoggerFactory.getLogger(WxCallbackController.class); 24 | 25 | /** 26 | * token/aesKey 是和企业号应用绑定的,这里为了演示方便使用外部注入,实际使用的时候一个企业号可能有多个应用,这样的话需要有个分发逻辑。 27 | * 比如callback url可以定义为 /wx/qy/[应用的ID],通过ID查询不同的token和aesKey 28 | */ 29 | @Value("${agent.default.token}") 30 | private String token; 31 | 32 | @Value("${agent.default.aesKey}") 33 | private String aesKey; 34 | 35 | @Autowired 36 | private DuplicatedMessageChecker duplicatedMessageChecker; 37 | 38 | public void setDuplicatedMessageChecker(DuplicatedMessageChecker duplicatedMessageChecker) { 39 | this.duplicatedMessageChecker = duplicatedMessageChecker; 40 | } 41 | 42 | /** 43 | * 企业号回调接口 44 | * 这里为了演示方便使用单个URL,实际使用的时候一个企业号可能有多个应用,这样的话需要有个分发逻辑: 45 | * 比如callback url可以定义为 /wx/qy/[应用的ID],通过ID查询不同的token和aesKey 46 | * 47 | * @param signature 48 | * @param timestamp 49 | * @param nonce 50 | * @param echostr 51 | * @param content 52 | * @return 53 | */ 54 | @RequestMapping("/wx/qy") 55 | @ResponseBody 56 | public String qy(@RequestParam(value="msg_signature") String signature, 57 | @RequestParam(value="timestamp") String timestamp, 58 | @RequestParam(value="nonce") String nonce, 59 | @RequestParam(value="echostr", required = false) String echostr, 60 | @RequestBody(required = false) String content) { 61 | 62 | logger.info("msg_signature={}, nonce={}, timestamp={}, echostr={}", signature, nonce, timestamp, echostr); 63 | 64 | CorpSetting corpSetting = CorpSetting.defaultSettings(); 65 | 66 | try { 67 | MessageDecryption messageDecryption = new MessageDecryption(token, aesKey, corpSetting.getCorpId()); 68 | if (!StringUtils.isEmpty(echostr)) { 69 | String echo = messageDecryption.decryptEcho(signature, timestamp, nonce, echostr); 70 | logger.info("消息签名验证成功."); 71 | return echo; 72 | } else { 73 | XmlMessageHeader xmlRequest = QyXmlMessages.fromXml(messageDecryption.decrypt(signature, timestamp, nonce, content)); 74 | XmlMessageHeader xmlResponse = qyDispatch(xmlRequest); 75 | if(xmlResponse != null) { 76 | try { 77 | return messageDecryption.encrypt(QyXmlMessages.toXml(xmlResponse), timestamp, nonce); 78 | } catch (WxRuntimeException e) { 79 | } 80 | } 81 | } 82 | } catch (Exception e) { 83 | logger.error("callback failed.", e); 84 | } 85 | 86 | return ""; 87 | } 88 | 89 | private XmlMessageHeader qyDispatch(XmlMessageHeader xmlRequest) { 90 | //添加处理逻辑 91 | 92 | //需要同步返回消息(被动响应消息)给用户则构造一个XmlMessageHeader类型,比较鸡肋,因为处理逻辑如果比较复杂响应太慢会影响用户感知,建议直接返回null; 93 | //如果有消息需要发送给用户则可以调用主动消息发送接口进行异步发送 94 | return null; 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /qydev-demo/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=48081 2 | agent.default.token=xxxxxxxxx 3 | agent.default.aesKey=xxxxxxxxxxxxxxxxx -------------------------------------------------------------------------------- /qydev-demo/src/main/resources/wx-qy-settings-test.xml: -------------------------------------------------------------------------------- 1 | 2 | xxxxxxxxxxx 3 | xxxxxxxxxxxxxx 4 | 7 | --------------------------------------------------------------------------------