├── LICENSE ├── README.md ├── pom.xml └── src ├── main ├── java │ └── cn │ │ └── newtol │ │ └── weixin │ │ ├── Enum │ │ └── ResultEnum.java │ │ ├── WeixinApplication.java │ │ ├── aspect │ │ └── HttpAspect.java │ │ ├── config │ │ ├── JacksonConfig.java │ │ └── SessionConfig.java │ │ ├── controller │ │ └── WeiXinBaseController.java │ │ ├── domain │ │ ├── BaseDO.java │ │ ├── HttpClientResult.java │ │ ├── Result.java │ │ ├── WeiXinSendAutoMessage.java │ │ ├── WeiXinUserInfo.java │ │ └── dto │ │ │ ├── BaseWeiXinMessage.java │ │ │ ├── RedirectUrlWeiXinConfig.java │ │ │ ├── WeiXinConfigInfo.java │ │ │ ├── WeiXinMenu.java │ │ │ ├── WeiXinReceiveMessage.java │ │ │ ├── WeiXinUserInfoOpenId.java │ │ │ ├── WeiXinVerify.java │ │ │ └── WeiXinWebAuthorize.java │ │ ├── exceptions │ │ └── TestException.java │ │ ├── handle │ │ └── ExceptionHandle.java │ │ ├── repository │ │ └── BaseUserInfoRepository.java │ │ ├── service │ │ ├── WeiXinBaseService.java │ │ └── WeiXinBaseServiceImpl.java │ │ └── utils │ │ ├── EncryptUtil.java │ │ ├── HttpClientUtil.java │ │ ├── HttpServletUtil.java │ │ ├── JacksonUtil.java │ │ ├── RedisUtil.java │ │ ├── ResultUtil.java │ │ └── WeiXinUtil.java └── resources │ └── application.yml └── test └── java └── cn └── newtol └── weixin └── WeixinApplicationTests.java /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SpringBoot-wechat 2 | SpringBoot开发微信公众号后台(可同时为多个公众号提供服务,非第三方平台) 3 | 4 | ## 配置使用 5 | 6 | ### 接入微信服务器 7 | 1. 将application.yml的weiXin.token更改为在微信服务器配置的token,如果需要服务于多个公众号,将每个公众号的token设置为一致即可接入 8 | 9 | ### 网页授权 10 | 实现为不同公众号分为提供网页授权和跳转服务 11 | 1. 将在微信服务器将网页授权的跳转地址和application.yml中weiXin.getWebAuthorize的设置为:`http://xxx/xxx/getWebAuthorize` 12 | 2. 请求`http://xxx/xxx/setRedirectUrl`设置需要跳转的链接,参数为appId,appSecret,redirectUrl,获取返回的state 13 | 3. 将第二步获取到的state添加`http://xxx/redirectUrl?state=STATE`中,将该链接设置为自动回复或者菜单中,即可实现网页授权和跳转 14 | 15 | ### 获取AccessToken 16 | 1. 请求`http://xxx/accessToken`,即可获得accessToken,当一个公众号有多个子项目运行时,可以避免accessToken冲突 17 | 18 | ### 自定义菜单实现 19 | 20 | 1. 在application.yml中按照yml格式进行菜单编辑,例如: 21 | ```yml 22 | menu: 23 | button: 24 | - sub_button: 25 | - clickButton: 26 | type: click 27 | name: 点击 28 | key: hi 29 | - clickButton: 30 | type: click 31 | name: 点击2 32 | key: hi1 33 | name: 视图 34 | - sub_button: 35 | - clickButton: 36 | type: view 37 | name: 视图2 38 | url: http://www.baidu.com 39 | name: 菜单 40 | ``` 41 | 2. 重启项目后,请求:`http://xxx//menu`,菜单即可生效 42 | 43 | ### 自动回复 44 | 1. 请求`http://xxx/setAutoMessage`,设置自动回复的内容(支持回复语音、图片、文字、图文、视频等)例如: 45 | ```json 46 | { 47 | "fromUserName":"你的微信公众号账号", 48 | "key":"图文", 49 | "msgType":"news", 50 | "articleCount":2, 51 | "articles":{ 52 | "item":[{ 53 | "title":"test", 54 | "description":"这是测试内容", 55 | "picUrl":"http://hongyan.cqupt.edu.cn/images/index_top.jpg", 56 | "url":"http://hongyan.cqupt.edu.cn/" 57 | 58 | },{ 59 | "title":"test2", 60 | "description":"这是测试内容3", 61 | "picUrl":"http://www.hers.cn/uploadfile/2011/1006/20111006022157183.jpg", 62 | "url":"http://mp.weixin.qq.com/mp/appmsg/show?__biz=MjM5MDE4Njg2MQ==&appmsgid=10000072&itemidx=1&sign=bea6deb75836dbe1249dcf394e8f3c21#wechat_redirect" 63 | }] 64 | } 65 | ``` 66 | 67 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | cn.newtol 7 | weixin 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | weixin 12 | Demo project for Spring Boot 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 2.1.0.RELEASE 18 | 19 | 20 | 21 | UTF-8 22 | UTF-8 23 | 1.8 24 | 25 | 26 | 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-data-redis 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-web 35 | 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter-test 40 | test 41 | 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-starter-aop 46 | 47 | 48 | 49 | org.projectlombok 50 | lombok 51 | provided 52 | 53 | 54 | 55 | org.apache.httpcomponents 56 | httpclient 57 | 58 | 59 | 60 | org.jsoup 61 | jsoup 62 | 1.11.3 63 | 64 | 65 | 66 | com.fasterxml.jackson.dataformat 67 | jackson-dataformat-xml 68 | 69 | 70 | com.alibaba 71 | fastjson 72 | 1.2.35 73 | 74 | 75 | 76 | org.springframework.boot 77 | spring-boot-configuration-processor 78 | true 79 | 80 | 81 | 82 | org.springframework.session 83 | spring-session-data-redis 84 | 85 | 86 | 87 | 88 | org.springframework.boot 89 | spring-boot-starter-data-jpa 90 | 91 | 92 | org.springframework.boot 93 | spring-boot-starter-jdbc 94 | 95 | 96 | mysql 97 | mysql-connector-java 98 | runtime 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | org.springframework.boot 107 | spring-boot-maven-plugin 108 | 109 | 110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /src/main/java/cn/newtol/weixin/Enum/ResultEnum.java: -------------------------------------------------------------------------------- 1 | package cn.newtol.weixin.Enum; 2 | 3 | /** 4 | * @Author: 公众号:Newtol 5 | * @Description: 6 | * @Date: Created in 13:46 2018/11/10 7 | */ 8 | 9 | @SuppressWarnings("AlibabaLowerCamelCaseVariableNaming") 10 | public enum ResultEnum { 11 | /** 12 | * 系统未知错误 13 | */ 14 | UNKONW_ERROR(-1,"未知错误:"), 15 | /** 16 | * 请求成功 17 | */ 18 | SUCCESS(0,"success"), 19 | /** 20 | * 请求缺少参数 21 | */ 22 | LACK_PARAMETER(1,"缺少参数"), 23 | /** 24 | * 微信公众号的appId或者appSecret错误 25 | */ 26 | ERROR_WEIXINBASEINFO(2,"appId或者appSecret错误"); 27 | 28 | /** 29 | * 错误码 30 | */ 31 | private Integer errorCode; 32 | /** 33 | * 错误提示信息 34 | */ 35 | private String message; 36 | 37 | ResultEnum(Integer errorCode, String message) { 38 | this.errorCode = errorCode; 39 | this.message = message; 40 | } 41 | 42 | public Integer getErrorCode() { 43 | return errorCode; 44 | } 45 | 46 | 47 | public String getMessage() { 48 | return message; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/cn/newtol/weixin/WeixinApplication.java: -------------------------------------------------------------------------------- 1 | package cn.newtol.weixin; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.data.jpa.repository.config.EnableJpaAuditing; 6 | 7 | @EnableJpaAuditing 8 | @SpringBootApplication 9 | public class WeixinApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(WeixinApplication.class, args); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/cn/newtol/weixin/aspect/HttpAspect.java: -------------------------------------------------------------------------------- 1 | //package cn.newtol.weixin.aspect; 2 | // 3 | ////import org.aspectj.lang.JoinPoint; 4 | ////import org.aspectj.lang.annotation.Aspect; 5 | ////import org.aspectj.lang.annotation.Before; 6 | ////import org.aspectj.lang.annotation.Pointcut; 7 | //import org.aspectj.lang.JoinPoint; 8 | //import org.aspectj.lang.annotation.Aspect; 9 | //import org.aspectj.lang.annotation.Before; 10 | //import org.aspectj.lang.annotation.Pointcut; 11 | //import org.springframework.stereotype.Component; 12 | //import org.springframework.web.context.request.RequestContextHolder; 13 | //import org.springframework.web.context.request.ServletRequestAttributes; 14 | // 15 | //import javax.servlet.http.HttpServletRequest; 16 | //import javax.servlet.http.HttpServletResponse; 17 | //import java.io.IOException; 18 | // 19 | ///** 20 | // * @Author: 公众号:Newtol 21 | // * @Description: 22 | // * @Date: Created in 15:35 2018/11/10 23 | // */ 24 | //@Aspect 25 | //@Component 26 | //public class HttpAspect { 27 | // @Pointcut("execution(public * cn.newtol.weixin.controller.WeiXinBaseController.setSession(..))") 28 | // public void log() { 29 | // 30 | // } 31 | // 32 | // @Before("log()") 33 | // public void forward(JoinPoint joinPoint) throws IOException { 34 | // ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); 35 | // HttpServletRequest request = attributes.getRequest(); 36 | // HttpServletResponse response = attributes.getResponse(); 37 | // if(request.getSession().getAttribute("User") == null || "".equals(request.getSession().getAttribute("User"))){ 38 | // StringBuffer url = request.getRequestURL(); 39 | // System.out.println(url); 40 | // response.setContentType("text/html;charset=UTF-8"); 41 | // // 要重定向的新位置 42 | // String site = new String("http://www.baidu.com"); 43 | // response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY); 44 | // response.setHeader("Location", site); 45 | // } 46 | // 47 | // } 48 | //} 49 | -------------------------------------------------------------------------------- /src/main/java/cn/newtol/weixin/config/JacksonConfig.java: -------------------------------------------------------------------------------- 1 | package cn.newtol.weixin.config; 2 | 3 | import com.fasterxml.jackson.core.JsonGenerator; 4 | import com.fasterxml.jackson.core.JsonProcessingException; 5 | import com.fasterxml.jackson.databind.JsonSerializer; 6 | import com.fasterxml.jackson.databind.ObjectMapper; 7 | import com.fasterxml.jackson.databind.SerializerProvider; 8 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | import org.springframework.context.annotation.Primary; 12 | import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; 13 | 14 | import java.io.IOException; 15 | 16 | /** 17 | * @Author: 公众号:Newtol 18 | * @Description: 19 | * @Date: Created in 22:17 2018/11/20 20 | */ 21 | @Configuration 22 | public class JacksonConfig { 23 | @Bean 24 | @Primary 25 | @ConditionalOnMissingBean(ObjectMapper.class) 26 | public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) { 27 | ObjectMapper objectMapper = builder.createXmlMapper(false).build(); 28 | objectMapper.getSerializerProvider().setNullValueSerializer(new JsonSerializer() { 29 | 30 | @Override 31 | public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException { 32 | jsonGenerator.writeString(""); 33 | } 34 | }); 35 | return objectMapper; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/cn/newtol/weixin/config/SessionConfig.java: -------------------------------------------------------------------------------- 1 | package cn.newtol.weixin.config; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; 5 | 6 | 7 | /** 8 | * @Author: 公众号:Newtol 9 | * @Description: 10 | * @Date: Created in 11:05 2018/11/13 11 | */ 12 | @Configuration 13 | @EnableRedisHttpSession(maxInactiveIntervalInSeconds = 600) 14 | public class SessionConfig { 15 | 16 | 17 | } -------------------------------------------------------------------------------- /src/main/java/cn/newtol/weixin/controller/WeiXinBaseController.java: -------------------------------------------------------------------------------- 1 | package cn.newtol.weixin.controller; 2 | 3 | import cn.newtol.weixin.domain.Result; 4 | import cn.newtol.weixin.domain.WeiXinSendAutoMessage; 5 | import cn.newtol.weixin.domain.dto.*; 6 | import cn.newtol.weixin.service.WeiXinBaseService; 7 | import cn.newtol.weixin.utils.ResultUtil; 8 | import com.fasterxml.jackson.annotation.JsonInclude; 9 | import com.fasterxml.jackson.core.JsonProcessingException; 10 | import com.fasterxml.jackson.dataformat.xml.XmlMapper; 11 | import org.springframework.stereotype.Controller; 12 | import org.springframework.web.bind.annotation.*; 13 | import javax.annotation.Resource; 14 | import javax.servlet.http.HttpServletRequest; 15 | import javax.servlet.http.HttpServletResponse; 16 | import javax.validation.Valid; 17 | import java.security.NoSuchAlgorithmException; 18 | 19 | 20 | /** 21 | * @Author: 公众号:Newtol 22 | * @Description: 23 | * @Date: Created in 18:51 2018/11/10 24 | */ 25 | @Controller 26 | public class WeiXinBaseController { 27 | 28 | @Resource 29 | private WeiXinBaseService weiXinBaseService; 30 | 31 | 32 | /** 33 | * @Author: 公众号:Newtol 34 | * @Description: 接入微信服务器 35 | * @Date: Created in 20:47 36 | * @param: WeiXinBase 微信服务器传来的四个参数:signature,timestamp,nonce,echostr 37 | */ 38 | 39 | @GetMapping(value = "/") 40 | @ResponseBody 41 | public String joinWeiXin(@Valid WeiXinVerify weiXinVerify){ 42 | return weiXinBaseService.joinWeiXin(weiXinVerify); 43 | } 44 | /** 45 | * @Author: 公众号:Newtol 46 | * @Description: 接收微信服务器消息 47 | * @Date: Created in 22:41 48 | * @param: 49 | */ 50 | 51 | @PostMapping(value = "/",consumes = "text/xml; charset=utf-8", produces = "text/xml; charset=utf-8") 52 | @ResponseBody 53 | public String getWeiXinMessage(@RequestBody WeiXinReceiveMessage weiXinReceiveMessage) throws JsonProcessingException { 54 | System.out.println(weiXinReceiveMessage.toString()); 55 | 56 | return weiXinBaseService.sendAutoMessage(weiXinReceiveMessage); 57 | } 58 | /** 59 | * @Author: 公众号:Newtol 60 | * @Description: AccessToken获取 61 | * @Date: Created in 21:13 62 | * @param: 63 | */ 64 | 65 | @GetMapping(value = "/accessToken") 66 | @ResponseBody 67 | public Result getAccessToken(@Valid WeiXinConfigInfo weiXinConfigInfo) throws Exception { 68 | return weiXinBaseService.getAccessToken(weiXinConfigInfo); 69 | } 70 | 71 | /** 72 | * @Author: 公众号:Newtol 73 | * @Description: 微信公众号菜单的设置 74 | * @Date: Created in 19:58 75 | * @param: 76 | */ 77 | @GetMapping(value = "/menu") 78 | @ResponseBody 79 | public Result setMenu(@Valid WeiXinConfigInfo weiXinConfigInfo) throws Exception { 80 | return weiXinBaseService.setWeiXinMenu(weiXinConfigInfo); 81 | } 82 | 83 | /** 84 | * @Author: 公众号:Newtol 85 | * @Description: 获取网页授权 86 | * @Date: Created in 20:58 87 | * @param: 88 | */ 89 | @GetMapping(value = "/getWebAuthorize") 90 | @ResponseBody 91 | public void getWeiXinWebAuthorize(@Valid WeiXinWebAuthorize weiXinWebAuthorize, HttpServletRequest request, HttpServletResponse response) throws Exception { 92 | weiXinBaseService.weiXinWebAuthorize(weiXinWebAuthorize,request,response); 93 | } 94 | 95 | /** 96 | * @Author: 公众号:Newtol 97 | * @Description: 获取跳转微信网页授权获取链接时需要使用的state 98 | * @Date: Created in 13:07 99 | * @param: 100 | */ 101 | @GetMapping(value = "/setRedirectUrl") 102 | @ResponseBody 103 | public Result setRedirectUrl(@Valid RedirectUrlWeiXinConfig weiXinRedirectUrl) throws NoSuchAlgorithmException { 104 | return weiXinBaseService.setRedirect(weiXinRedirectUrl); 105 | } 106 | 107 | /** 108 | * @Author: 公众号:Newtol 109 | * @Description: 跳转到微信获取网页授权的链接地址 110 | * @Date: Created in 19:21 111 | * @param: 112 | */ 113 | @GetMapping(value = "/redirectUrl") 114 | @ResponseBody 115 | public void redirectToWeiXin(@RequestParam String state, HttpServletResponse response){ 116 | weiXinBaseService.redirectToWeiXin(response,state); 117 | } 118 | 119 | @RequestMapping(value = "/test", method = RequestMethod.POST, consumes = { "text/xml" }, produces = { "application/xml" }) 120 | @ResponseBody 121 | public Result setSession(@RequestBody WeiXinSendAutoMessage weiXinSendAutoMessage) throws JsonProcessingException { 122 | System.out.println(weiXinSendAutoMessage.toString()); 123 | XmlMapper xmlMapper = new XmlMapper(); 124 | xmlMapper.setDefaultUseWrapper(false); 125 | //字段为null,自动忽略,不再序列化 126 | xmlMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); 127 | String xml = xmlMapper.writeValueAsString(weiXinSendAutoMessage); 128 | System.out.println(xml); 129 | return ResultUtil.success(); 130 | } 131 | 132 | 133 | /** 134 | * @Author: 公众号:Newtol 135 | * @Description: 设置自动回复内容 136 | * @Date: Created in 23:57 137 | * @param: 138 | */ 139 | @PostMapping(value = "/setAutoMessage") 140 | @ResponseBody 141 | public Result setAutoMessage(@RequestBody WeiXinSendAutoMessage weiXinSendAutoMessage){ 142 | return ResultUtil.success(weiXinBaseService.setAutoMessage(weiXinSendAutoMessage)); 143 | } 144 | } -------------------------------------------------------------------------------- /src/main/java/cn/newtol/weixin/domain/BaseDO.java: -------------------------------------------------------------------------------- 1 | package cn.newtol.weixin.domain; 2 | 3 | import lombok.Data; 4 | import org.springframework.data.annotation.CreatedDate; 5 | import org.springframework.data.annotation.LastModifiedDate; 6 | import org.springframework.data.jpa.domain.support.AuditingEntityListener; 7 | import org.springframework.stereotype.Component; 8 | 9 | import javax.persistence.*; 10 | import java.util.Date; 11 | 12 | /** 13 | * @Author: 公众号:Newtol 14 | * @Description: 15 | * @Date: Created in 1:16 2018/11/25 16 | */ 17 | @Data 18 | @Component 19 | @MappedSuperclass 20 | @EntityListeners(AuditingEntityListener.class) 21 | public abstract class BaseDO { 22 | @Id 23 | @GeneratedValue(strategy = GenerationType.IDENTITY) 24 | public Integer id; 25 | 26 | @CreatedDate 27 | @Column(updatable = false) 28 | public Date createTime ; 29 | @LastModifiedDate 30 | @Column 31 | public Date updateTime ; 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/cn/newtol/weixin/domain/HttpClientResult.java: -------------------------------------------------------------------------------- 1 | package cn.newtol.weixin.domain; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @Author: 公众号:Newtol 7 | * @Description: 8 | * @Date: Created in 16:50 2018/11/10 9 | */ 10 | 11 | @Data 12 | public class HttpClientResult{ 13 | private static final long serialVersionUID = 2168152194164783950L; 14 | /** 15 | * 响应状态码 16 | */ 17 | private int code; 18 | 19 | /** 20 | * 响应数据 21 | */ 22 | private String content; 23 | 24 | 25 | public HttpClientResult(int code, String content) { 26 | this.code = code; 27 | this.content = content; 28 | } 29 | 30 | public HttpClientResult(int code) { 31 | this.code = code; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/cn/newtol/weixin/domain/Result.java: -------------------------------------------------------------------------------- 1 | package cn.newtol.weixin.domain; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @Author: 公众号:Newtol 7 | * @Description: 8 | * @Date: Created in 13:13 2018/11/10 9 | */ 10 | 11 | @Data 12 | public class Result { 13 | /** 14 | * 错误码 15 | */ 16 | private Integer errorCode; 17 | 18 | /** 19 | * 错误提示信息 20 | */ 21 | private String message; 22 | 23 | /** 24 | * 数据块 25 | */ 26 | private T data; 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/cn/newtol/weixin/domain/WeiXinSendAutoMessage.java: -------------------------------------------------------------------------------- 1 | package cn.newtol.weixin.domain; 2 | 3 | import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; 4 | 5 | import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; 6 | import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlText; 7 | import lombok.Data; 8 | import org.springframework.stereotype.Component; 9 | 10 | import javax.validation.constraints.NotEmpty; 11 | 12 | /** 13 | * @Author: 公众号:Newtol 14 | * @Description: 15 | * @Date: Created in 19:26 2018/11/27 16 | */ 17 | @Data 18 | @Component 19 | @JacksonXmlRootElement(localName = "xml") 20 | public class WeiXinSendAutoMessage { 21 | /** 22 | * 接收者Id 23 | */ 24 | @JacksonXmlProperty(localName = "ToUserName") 25 | private String toUserName; 26 | /** 27 | * 发送者Id 28 | */ 29 | @NotEmpty(message = "发送者不能为空") 30 | @JacksonXmlProperty(localName = "FromUserName") 31 | private String fromUserName; 32 | /** 33 | * 消息创建时间 34 | */ 35 | @JacksonXmlProperty(localName = "CreateTime") 36 | private Long createTime; 37 | /** 38 | * 消息类型 39 | * 文本为:text; 图片为:image; 语音为:voice; 40 | * 41 | */ 42 | @NotEmpty(message = "文章类型不能为空") 43 | @JacksonXmlProperty(localName = "MsgType") 44 | private String msgType; 45 | 46 | /** 47 | * 回复的关键词 48 | */ 49 | @JacksonXmlText 50 | private String key; 51 | 52 | /** 53 | * 多媒体Id,回复图片、语音时使用 54 | */ 55 | 56 | @Data 57 | public static class MediaId { 58 | @JacksonXmlText(value =false) 59 | @JacksonXmlProperty(localName = "MediaId") 60 | private String mediaId; 61 | public MediaId(String mediaId) { 62 | this.mediaId = mediaId; 63 | } 64 | public MediaId() { 65 | } 66 | } 67 | 68 | /** 69 | * 自动回复图片消息时使用 70 | */ 71 | @JacksonXmlProperty(localName = "Image") 72 | private MediaId image; 73 | 74 | /** 75 | * 回复语音消息时使用 76 | */ 77 | @JacksonXmlProperty(localName = "Voice") 78 | private MediaId voice; 79 | 80 | /** 81 | * 回复文本消息 82 | */ 83 | @JacksonXmlProperty(localName = "Content") 84 | private String Content; 85 | 86 | 87 | /** 88 | * 回复视频消息 89 | */ 90 | @Data 91 | public static class VideoContent { 92 | @JacksonXmlText(value =false) 93 | @JacksonXmlProperty(localName = "MediaId") 94 | private String mediaId; 95 | @JacksonXmlText(value =false) 96 | @JacksonXmlProperty(localName = "Title") 97 | private String title; 98 | @JacksonXmlText(value =false) 99 | @JacksonXmlProperty(localName = "Description") 100 | private String description; 101 | } 102 | @JacksonXmlProperty(localName = "Video") 103 | private VideoContent video; 104 | 105 | /** 106 | * 回复音乐消息 107 | */ 108 | 109 | @Data 110 | public static class MusicContent{ 111 | public MusicContent() { 112 | } 113 | 114 | /** 115 | * 音乐标题 116 | */ 117 | @JacksonXmlText(value =false) 118 | @JacksonXmlProperty(localName = "Title") 119 | private String title; 120 | /** 121 | * 音乐描述 122 | */ 123 | @JacksonXmlText(value =false) 124 | @JacksonXmlProperty(localName = "Description") 125 | private String description ; 126 | /** 127 | * 音乐链接 128 | */ 129 | @JacksonXmlText(value =false) 130 | @JacksonXmlProperty(localName = "MusicUrl") 131 | private String musicUrl ; 132 | /** 133 | * 高质量音乐链接,WIFI环境优先使用该链接播放音乐 134 | */ 135 | @JacksonXmlText(value =false) 136 | @JacksonXmlProperty(localName = "HQMusicUrl") 137 | private String hqMusicUrl; 138 | /** 139 | * 缩略图的媒体id,通过素材管理中的接口上传多媒体文件,得到的id 140 | */ 141 | @JacksonXmlText(value =false) 142 | @JacksonXmlProperty(localName ="ThumbMediaId" ) 143 | private String thumbMediaId; 144 | } 145 | 146 | @JacksonXmlProperty(localName = "Music") 147 | private MusicContent music; 148 | 149 | 150 | /** 151 | * 回复图文消息 152 | */ 153 | @Data 154 | public static class PicAndContent { 155 | public PicAndContent() { 156 | } 157 | 158 | /** 159 | * 图文消息个数;当用户发送文本、图片、视频、图文、地理位置这五种消息时,开发者只能回复1条图文消息;其余场景最多可回复8条图文消息 160 | */ 161 | @JacksonXmlText(value =false) 162 | @JacksonXmlProperty(localName ="ArticleCount" ) 163 | private Integer articleCount; 164 | /** 165 | * 图文消息信息,注意,如果图文数超过限制,则将只发限制内的条数 166 | */ 167 | @JacksonXmlText(value =false) 168 | @JacksonXmlProperty(localName ="Articles" ) 169 | private String articles; 170 | /** 171 | * 图文消息标题 172 | */ 173 | @JacksonXmlText(value =false) 174 | @JacksonXmlProperty(localName ="Title" ) 175 | private String title; 176 | /** 177 | * 图文消息描述 178 | */ 179 | @JacksonXmlText(value =false) 180 | @JacksonXmlProperty(localName ="Description" ) 181 | private String description ; 182 | /** 183 | * 图片链接,支持JPG、PNG格式,较好的效果为大图360*200,小图200*200 184 | */ 185 | @JacksonXmlText(value =false) 186 | @JacksonXmlProperty(localName ="PicUrl" ) 187 | private String picUrl; 188 | /** 189 | * 图文跳转链接 190 | */ 191 | @JacksonXmlText(value =false) 192 | @JacksonXmlProperty(localName ="Url" ) 193 | private String url; 194 | } 195 | 196 | @JacksonXmlProperty(localName = "Articles") 197 | private String articles; 198 | 199 | } 200 | -------------------------------------------------------------------------------- /src/main/java/cn/newtol/weixin/domain/WeiXinUserInfo.java: -------------------------------------------------------------------------------- 1 | package cn.newtol.weixin.domain; 2 | 3 | import lombok.Data; 4 | import org.springframework.stereotype.Component; 5 | import javax.persistence.*; 6 | import java.io.Serializable; 7 | 8 | 9 | /** 10 | * @Author: 公众号:Newtol 11 | * @Description: 12 | * @Date: Created in 22:09 2018/11/12 13 | */ 14 | @Component 15 | @Data 16 | @Entity 17 | @Table(name = "weixin_userinfo") 18 | public class WeiXinUserInfo extends BaseDO implements Serializable { 19 | @Column(nullable = false,unique = true) 20 | private String openId; 21 | @Column(nullable = false) 22 | private String nickName; 23 | @Column 24 | private String city; 25 | @Column 26 | private Integer sex; 27 | @Column(nullable = false) 28 | private String headImgUrl; 29 | @Column 30 | private String province; 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/cn/newtol/weixin/domain/dto/BaseWeiXinMessage.java: -------------------------------------------------------------------------------- 1 | package cn.newtol.weixin.domain.dto; 2 | 3 | import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; 4 | import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; 5 | import lombok.Data; 6 | import org.springframework.stereotype.Component; 7 | 8 | import javax.validation.constraints.NotEmpty; 9 | 10 | /** 11 | * @Author: 公众号:Newtol 12 | * @Description: 13 | * @Date: Created in 11:01 2018/11/29 14 | */ 15 | 16 | @Data 17 | @Component 18 | public abstract class BaseWeiXinMessage { 19 | /** 20 | * 接收者Id 21 | */ 22 | @JacksonXmlProperty(localName = "ToUserName") 23 | private String toUserName; 24 | /** 25 | * 发送者Id 26 | */ 27 | @NotEmpty(message = "发送者不能为空") 28 | @JacksonXmlProperty(localName = "FromUserName") 29 | private String fromUserName; 30 | /** 31 | * 消息创建时间 32 | */ 33 | @JacksonXmlProperty(localName = "CreateTime") 34 | private Long createTime; 35 | /** 36 | * 消息类型 37 | * 文本为:text; 图片为:image; 语音为:voice; 38 | * 39 | */ 40 | @NotEmpty(message = "文章类型不能为空") 41 | @JacksonXmlProperty(localName = "MsgType") 42 | private String msgType; 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/cn/newtol/weixin/domain/dto/RedirectUrlWeiXinConfig.java: -------------------------------------------------------------------------------- 1 | package cn.newtol.weixin.domain.dto; 2 | 3 | import lombok.Data; 4 | import org.springframework.stereotype.Component; 5 | 6 | import javax.validation.constraints.NotEmpty; 7 | 8 | /** 9 | * @Author: 公众号:Newtol 10 | * @Description: 11 | * @Date: Created in 13:13 2018/11/24 12 | */ 13 | @Data 14 | @Component 15 | public class RedirectUrlWeiXinConfig extends WeiXinConfigInfo { 16 | 17 | /** 获取网页授权时,需要跳转的链接 */ 18 | @NotEmpty(message = "跳转链接不能为空") 19 | private String redirectUrl; 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/cn/newtol/weixin/domain/dto/WeiXinConfigInfo.java: -------------------------------------------------------------------------------- 1 | package cn.newtol.weixin.domain.dto; 2 | 3 | import lombok.Data; 4 | import org.springframework.stereotype.Component; 5 | 6 | 7 | import javax.validation.constraints.NotEmpty; 8 | 9 | 10 | /** 11 | * @Author: 公众号:Newtol 12 | * @Description: 微信公众号的基本信息:appId和appSecret 13 | * @Date: Created in 13:03 2018/11/12 14 | */ 15 | @Data 16 | @Component 17 | public class WeiXinConfigInfo { 18 | /** 公众号的appId*/ 19 | @NotEmpty(message = "appId不能为空" ) 20 | public String appId; 21 | 22 | /** 公众号的appSecret*/ 23 | @NotEmpty(message = "appSecret不能为空") 24 | public String appSecret; 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/cn/newtol/weixin/domain/dto/WeiXinMenu.java: -------------------------------------------------------------------------------- 1 | package cn.newtol.weixin.domain.dto; 2 | 3 | import lombok.Data; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | import org.springframework.stereotype.Component; 6 | 7 | /** 8 | * @Author: 公众号:Newtol 9 | * @Description: 菜单 10 | * @Date: Created in 0:17 2018/11/11 11 | */ 12 | @Data 13 | @Component 14 | @ConfigurationProperties(prefix= "menu") 15 | public class WeiXinMenu { 16 | /** 菜单 */ 17 | private SubButton[] button; 18 | 19 | 20 | @Data 21 | public static class SubButton { 22 | /** 二级菜单 */ 23 | private Button[] sub_button; 24 | /** 二级菜单的名字 */ 25 | private String name; 26 | } 27 | 28 | @Data 29 | public static class Button { 30 | /** 按钮的种类 */ 31 | private String type; 32 | /** 按钮的名字 */ 33 | private String name; 34 | /** 当类型为view类型时,跳转的链接*/ 35 | private String url; 36 | /** 当类型为小程序时,小程序的appId*/ 37 | private String appId; 38 | /** 当类型为小程序时使用*/ 39 | private String pagePath; 40 | /** 当类型为click时,发送给后台的key*/ 41 | private String key; 42 | 43 | } 44 | 45 | 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/cn/newtol/weixin/domain/dto/WeiXinReceiveMessage.java: -------------------------------------------------------------------------------- 1 | package cn.newtol.weixin.domain.dto; 2 | 3 | import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; 4 | import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; 5 | import lombok.Data; 6 | 7 | /** 8 | * @Author: 公众号:Newtol 9 | * @Description: 10 | * @Date: Created in 22:44 2018/11/25 11 | */ 12 | @Data 13 | @JacksonXmlRootElement(localName = "xml") 14 | public class WeiXinReceiveMessage extends BaseWeiXinMessage{ 15 | /** 16 | * 文本消息内容 17 | */ 18 | @JacksonXmlProperty(localName = "Content") 19 | private String content; 20 | /** 21 | * 消息ID 22 | */ 23 | @JacksonXmlProperty(localName = "MsgId") 24 | private Long msgId; 25 | /** 26 | * 图片链接 27 | */ 28 | @JacksonXmlProperty(localName = "PicUrl") 29 | private String picUrl; 30 | /** 31 | * 图片消息媒体id,可以调用多媒体文件下载接口拉取数据 32 | */ 33 | @JacksonXmlProperty(localName ="MediaId" ) 34 | private String mediaId; 35 | /** 36 | * 语音格式 37 | */ 38 | @JacksonXmlProperty(localName = "Format") 39 | private String format; 40 | /** 41 | * 语音识别结果 42 | */ 43 | @JacksonXmlProperty(localName = "Recognition") 44 | private String recognition; 45 | /** 46 | * 视频消息缩略图的媒体id,可以调用多媒体文件下载接口拉取数据。 47 | */ 48 | @JacksonXmlProperty(localName = "ThumbMediaId") 49 | private String thumbMediaId; 50 | /** 51 | * 地理位置维度 52 | */ 53 | @JacksonXmlProperty(localName = "Location_X") 54 | private String location_X; 55 | /** 56 | * 地理位置经度 57 | */ 58 | @JacksonXmlProperty(localName = "Location_Y") 59 | private String location_Y; 60 | /** 61 | * 地图缩放大小 62 | */ 63 | @JacksonXmlProperty(localName = "Scale") 64 | private String scale; 65 | /** 66 | * 地理位置信息 67 | */ 68 | @JacksonXmlProperty(localName = "Label") 69 | private String label; 70 | /** 71 | * 消息标题 72 | */ 73 | @JacksonXmlProperty(localName = "Title") 74 | private String title; 75 | 76 | /** 77 | * 消息描述 78 | */ 79 | @JacksonXmlProperty(localName = "Description") 80 | private String description; 81 | 82 | /** 83 | * 消息链接 84 | */ 85 | @JacksonXmlProperty(localName = "Url") 86 | private String url; 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/cn/newtol/weixin/domain/dto/WeiXinUserInfoOpenId.java: -------------------------------------------------------------------------------- 1 | package cn.newtol.weixin.domain.dto; 2 | 3 | import lombok.Data; 4 | import org.springframework.stereotype.Component; 5 | 6 | /** 7 | * @Author: 公众号:Newtol 8 | * @Description: 9 | * @Date: Created in 14:42 2018/11/24 10 | */ 11 | @Data 12 | @Component 13 | public class WeiXinUserInfoOpenId { 14 | /** snsapi_userinfo为scope时,获取用户信息使用的access_token */ 15 | private String accessToken; 16 | /** 用户的openId */ 17 | private String openId; 18 | /** 用来刷新用户access_token使用 */ 19 | private String refreshToken; 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/cn/newtol/weixin/domain/dto/WeiXinVerify.java: -------------------------------------------------------------------------------- 1 | package cn.newtol.weixin.domain.dto; 2 | 3 | import lombok.Data; 4 | 5 | 6 | import org.springframework.stereotype.Component; 7 | 8 | import javax.validation.constraints.NotEmpty; 9 | 10 | 11 | /** 12 | * @Author: 公众号:Newtol 13 | * @Description: 接入微信服务器时,微信服务器传过来的参数 14 | * @Date: Created in 19:08 2018/11/10 15 | */ 16 | @Data 17 | @Component 18 | public class WeiXinVerify { 19 | @NotEmpty(message = "微信接入缺少参数") 20 | private String signature; 21 | @NotEmpty(message = "微信接入缺少参数") 22 | private String timestamp; 23 | @NotEmpty(message = "微信接入缺少参数") 24 | private String nonce; 25 | @NotEmpty(message = "微信接入缺少参数") 26 | private String echostr; 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/cn/newtol/weixin/domain/dto/WeiXinWebAuthorize.java: -------------------------------------------------------------------------------- 1 | package cn.newtol.weixin.domain.dto; 2 | 3 | import lombok.Data; 4 | import org.springframework.stereotype.Component; 5 | 6 | import javax.validation.constraints.NotEmpty; 7 | 8 | /** 9 | * @Author: 公众号:Newtol 10 | * @Description: 获取网页授权时,需要的code和state 11 | * @Date: Created in 21:13 2018/11/22 12 | */ 13 | @Component 14 | @Data 15 | public class WeiXinWebAuthorize { 16 | @NotEmpty(message = "code不能为空") 17 | private String code; 18 | @NotEmpty(message = "state不能为空") 19 | private String state; 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/cn/newtol/weixin/exceptions/TestException.java: -------------------------------------------------------------------------------- 1 | package cn.newtol.weixin.exceptions; 2 | 3 | import cn.newtol.weixin.Enum.ResultEnum; 4 | import lombok.Data; 5 | 6 | /** 7 | * @Author: 公众号:Newtol 8 | * @Description: 9 | * @Date: Created in 13:35 2018/11/10 10 | */ 11 | @Data 12 | public class TestException extends RuntimeException { 13 | private Integer errorCode; 14 | 15 | public TestException(ResultEnum resultEnum) { 16 | super(resultEnum.getMessage()); 17 | this.errorCode = resultEnum.getErrorCode(); 18 | } 19 | 20 | public Integer getErrorCode() { 21 | return errorCode; 22 | } 23 | 24 | public void setErrorCode(Integer errorCode) { 25 | this.errorCode = errorCode; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/cn/newtol/weixin/handle/ExceptionHandle.java: -------------------------------------------------------------------------------- 1 | package cn.newtol.weixin.handle; 2 | 3 | import cn.newtol.weixin.Enum.ResultEnum; 4 | import cn.newtol.weixin.domain.Result; 5 | import cn.newtol.weixin.exceptions.TestException; 6 | import cn.newtol.weixin.utils.ResultUtil; 7 | import com.fasterxml.jackson.core.JsonProcessingException; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.validation.BindException; 11 | import org.springframework.web.bind.annotation.ControllerAdvice; 12 | import org.springframework.web.bind.annotation.ExceptionHandler; 13 | import org.springframework.web.bind.annotation.ResponseBody; 14 | 15 | /** 16 | * @Author: 公众号:Newtol 17 | * @Description: 18 | * @Date: Created in 13:55 2018/11/10 19 | */ 20 | 21 | @ControllerAdvice 22 | public class ExceptionHandle { 23 | 24 | 25 | private static final Logger logger = LoggerFactory.getLogger(ExceptionHandle.class); 26 | /** 27 | * @Author: 公众号:Newtol 28 | * @Description: 拦截一些自定义的警告 29 | * @Date: Created in 0:07 30 | * @param: 31 | */ 32 | @ExceptionHandler(TestException.class) 33 | @ResponseBody 34 | public Result testExceptionHandler(TestException e){ 35 | return ResultUtil.error(e.getErrorCode(),e.getMessage()); 36 | } 37 | 38 | /** 39 | * @Author: 公众号:Newtol 40 | * @Description: 拦截参数验证错误 41 | * @Date: Created in 0:07 42 | * @param: 43 | */ 44 | @ExceptionHandler(BindException.class) 45 | @ResponseBody 46 | public Result validExceptionHandler(BindException e){ 47 | String msg = e.getBindingResult().getFieldError().getDefaultMessage(); 48 | logger.info("参数验证错误:"+msg); 49 | return ResultUtil.error(ResultEnum.LACK_PARAMETER.getErrorCode(),msg); 50 | } 51 | 52 | /** 53 | * @Author: 公众号:Newtol 54 | * @Description: 系统未知错误拦截 55 | * @Date: Created in 0:15 56 | * @param: 57 | */ 58 | @ExceptionHandler(Exception.class) 59 | @ResponseBody 60 | public Result systemExceptionHandler(Exception e){ 61 | logger.error("系统未知错误:"+e.getMessage()); 62 | return ResultUtil.error(ResultEnum.UNKONW_ERROR); 63 | } 64 | 65 | /** 66 | * @Author: 公众号:Newtol 67 | * @Description: XML序列化失败 68 | * @Date: Created in 23:19 69 | * @param: 70 | */ 71 | @ExceptionHandler(JsonProcessingException.class) 72 | @ResponseBody 73 | public void jsonProcessingExceptionHandler (Exception e){ 74 | logger.error("XML序列化失败"+e.getMessage()); 75 | } 76 | 77 | 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/cn/newtol/weixin/repository/BaseUserInfoRepository.java: -------------------------------------------------------------------------------- 1 | package cn.newtol.weixin.repository; 2 | 3 | import cn.newtol.weixin.domain.WeiXinUserInfo; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | /** 7 | * @Author: 公众号:Newtol 8 | * @Description: 9 | * @Date: Created in 16:31 2018/11/24 10 | */ 11 | public interface BaseUserInfoRepository extends JpaRepository { 12 | /** 13 | * @Author: 公众号:Newtol 14 | * @Description: 通过openId查找用户 15 | * @Date: Created in 20:56 16 | * @param: 17 | */ 18 | WeiXinUserInfo findBaseUserInfoByOpenId(String openId); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/cn/newtol/weixin/service/WeiXinBaseService.java: -------------------------------------------------------------------------------- 1 | package cn.newtol.weixin.service; 2 | 3 | import cn.newtol.weixin.domain.Result; 4 | import cn.newtol.weixin.domain.WeiXinSendAutoMessage; 5 | import cn.newtol.weixin.domain.dto.*; 6 | import com.fasterxml.jackson.core.JsonProcessingException; 7 | import org.springframework.stereotype.Service; 8 | 9 | import javax.servlet.http.HttpServletRequest; 10 | import javax.servlet.http.HttpServletResponse; 11 | import java.security.NoSuchAlgorithmException; 12 | 13 | /** 14 | * @Author: 公众号:Newtol 15 | * @Description: 16 | * @Date: Created in 19:21 2018/11/10 17 | */ 18 | @Service 19 | public interface WeiXinBaseService { 20 | 21 | /** 22 | * @Author: 公众号:Newtol 23 | * @Description: 接入微信服务器 24 | * @Date: Created in 19:38 25 | * @param: 26 | */ 27 | String joinWeiXin(WeiXinVerify weiXinVerify); 28 | 29 | /** 30 | * @Author: 公众号:Newtol 31 | * @Description: 获取access_token 32 | * @Date: Created in 19:38 33 | * @param: 34 | */ 35 | Result getAccessToken(WeiXinConfigInfo weiXinConfigInfo) throws Exception; 36 | 37 | /** 38 | * @Author: 公众号:Newtol 39 | * @Description: 设置微信公众号的后台 40 | * @Date: Created in 19:49 41 | * @param: 42 | */ 43 | Result setWeiXinMenu(WeiXinConfigInfo weiXinConfigInfo) throws Exception; 44 | 45 | /** 46 | * @Author: 公众号:Newtol 47 | * @Description: 微信获取网页授权 48 | * @Date: Created in 22:15 49 | * @param: 50 | */ 51 | void weiXinWebAuthorize(WeiXinWebAuthorize weiXinWebAuthorize, HttpServletRequest request, HttpServletResponse response) throws Exception; 52 | 53 | /** 54 | * @Author: 公众号:Newtol 55 | * @Description: 网页授权时,获取code时,使用的state 56 | * @Date: Created in 13:16 57 | * @param: 58 | */ 59 | Result setRedirect(RedirectUrlWeiXinConfig weiXinRedirectUrl) throws NoSuchAlgorithmException; 60 | 61 | /** 62 | * @Author: 公众号:Newtol 63 | * @Description: 跳转到微信网页授权链接 64 | * @Date: Created in 19:51 65 | * @param: 66 | */ 67 | void redirectToWeiXin(HttpServletResponse response,String state); 68 | 69 | /** 70 | * @Author: 公众号:Newtol 71 | * @Description: 微信自动回复关键词 72 | * @Date: Created in 22:44 73 | * @param: 74 | */ 75 | String sendAutoMessage(WeiXinReceiveMessage weiXinReceiveMessage) throws JsonProcessingException; 76 | 77 | /** 78 | * @Author: 公众号:Newtol 79 | * @Description: 设置自动回复内容 80 | * @Date: Created in 23:58 81 | * @param: 82 | */ 83 | Result setAutoMessage(WeiXinSendAutoMessage weiXinSendAutoMessage); 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/cn/newtol/weixin/service/WeiXinBaseServiceImpl.java: -------------------------------------------------------------------------------- 1 | package cn.newtol.weixin.service; 2 | 3 | import cn.newtol.weixin.Enum.ResultEnum; 4 | import cn.newtol.weixin.domain.WeiXinSendAutoMessage; 5 | import cn.newtol.weixin.domain.WeiXinUserInfo; 6 | import cn.newtol.weixin.domain.HttpClientResult; 7 | import cn.newtol.weixin.domain.Result; 8 | import cn.newtol.weixin.domain.dto.*; 9 | import cn.newtol.weixin.repository.BaseUserInfoRepository; 10 | import cn.newtol.weixin.utils.*; 11 | import com.alibaba.fastjson.JSON; 12 | import com.alibaba.fastjson.JSONObject; 13 | import com.fasterxml.jackson.annotation.JsonInclude; 14 | import com.fasterxml.jackson.core.JsonProcessingException; 15 | import com.fasterxml.jackson.dataformat.xml.XmlMapper; 16 | import org.springframework.beans.factory.annotation.Autowired; 17 | import org.springframework.beans.factory.annotation.Value; 18 | import org.slf4j.Logger; 19 | import org.slf4j.LoggerFactory; 20 | import org.springframework.stereotype.Component; 21 | import org.springframework.stereotype.Service; 22 | import javax.annotation.Resource; 23 | import javax.servlet.http.HttpServletRequest; 24 | import javax.servlet.http.HttpServletResponse; 25 | import java.security.NoSuchAlgorithmException; 26 | import java.util.HashMap; 27 | import java.util.Map; 28 | 29 | /** 30 | * @Author: 公众号:Newtol 31 | * @Description: 32 | * @Date: Created in 19:23 2018/11/10 33 | */ 34 | @Service 35 | @Component 36 | public class WeiXinBaseServiceImpl implements WeiXinBaseService { 37 | 38 | private static final Logger logger = LoggerFactory.getLogger(WeiXinBaseServiceImpl.class); 39 | 40 | @Resource 41 | private WeiXinUtil weiXinUtil; 42 | 43 | @Autowired 44 | BaseUserInfoRepository baseUserInfoRepository; 45 | 46 | @Autowired 47 | RedisUtil redisUtil; 48 | 49 | @Autowired 50 | HttpServletUtil httpServletUtil; 51 | 52 | @Autowired 53 | JacksonUtil jacksonUtil; 54 | 55 | /** 56 | * 从配置文件中获取redis中AccessToken存入的key 57 | */ 58 | @Value("${weiXin.accessTokenKey}") 59 | private String accessTokenKey; 60 | 61 | /** 62 | * 微信服务器配置的token 63 | */ 64 | @Value("${weiXin.token}") 65 | private String token; 66 | 67 | /** 68 | * RedirectUrl链接的key 69 | */ 70 | @Value("${weiXin.redirectUrlKey}") 71 | private String redirectUrlKey; 72 | 73 | /** 74 | * 跳转到本地的网页授权页面 75 | */ 76 | @Value("${weiXin.getWebAuthorize}") 77 | private String getWebAuthorizeUrl; 78 | 79 | /** 80 | * 微信公众号菜单 81 | */ 82 | @Resource 83 | private WeiXinMenu weiXinMenu; 84 | 85 | @Override 86 | public String joinWeiXin(WeiXinVerify weiXinVerify) { 87 | return weiXinUtil.joinWeiXin(weiXinVerify,token); 88 | } 89 | 90 | @Override 91 | public Result getAccessToken(WeiXinConfigInfo weiXinConfigInfo) throws Exception { 92 | String access_token = weiXinUtil.getAccessToken(weiXinConfigInfo.getAppId(), weiXinConfigInfo.getAppSecret(), accessTokenKey); 93 | Map map = new HashMap<>(1); 94 | map.put("access_token",access_token); 95 | return ResultUtil.success(map); 96 | } 97 | 98 | /** 99 | * @param weiXinConfigInfo 100 | * @Author: 公众号:Newtol 101 | * @Description: 设置微信公众号的菜单 102 | * @Date: Created in 19:49 103 | */ 104 | @Override 105 | public Result setWeiXinMenu(WeiXinConfigInfo weiXinConfigInfo) throws Exception { 106 | String access_token = weiXinUtil.getAccessToken(weiXinConfigInfo.getAppId(), weiXinConfigInfo.getAppSecret(), accessTokenKey); 107 | if (access_token == null ||"".equals(access_token)){ 108 | logger.error("请求菜单时access_token为空:"+ weiXinConfigInfo); 109 | return ResultUtil.error(ResultEnum.ERROR_WEIXINBASEINFO); 110 | } 111 | /* 封装url,参数,Json数据*/ 112 | String url = "https://api.weixin.qq.com/cgi-bin/menu/create"; 113 | Map map = new HashMap<>(1); 114 | map.put("access_token",access_token); 115 | String jsonData = JSON.toJSONString(weiXinMenu); 116 | HttpClientResult httpClientResult = HttpClientUtil.doPostForJson(url,map,jsonData); 117 | /* 获取返回结果*/ 118 | Result result = ResultUtil.success(httpClientResult); 119 | logger.info("微信公众号菜单设置请求的JSON参数:"+jsonData); 120 | return result; 121 | } 122 | 123 | /** 124 | * @param weiXinWebAuthorize 125 | * @Author: 公众号:Newtol 126 | * @Description: 获取网页授权时,用于获取用户openId 127 | * @Date: Created in 21:16 128 | * @param: 129 | */ 130 | @Override 131 | public void weiXinWebAuthorize(WeiXinWebAuthorize weiXinWebAuthorize, HttpServletRequest request, HttpServletResponse response) throws Exception { 132 | /* 从redis中获取appId,appSecret,redirectUrl等信息*/ 133 | String stateInfo = redisUtil.getHash(redirectUrlKey,weiXinWebAuthorize.getState()); 134 | RedirectUrlWeiXinConfig weiXinRedirectUrl = JSONObject.parseObject(stateInfo, RedirectUrlWeiXinConfig.class); 135 | String redirectUrl = weiXinRedirectUrl.getRedirectUrl(); 136 | 137 | /* 获取openId等信息 */ 138 | WeiXinUserInfoOpenId weiXinUserInfoOpenId = getUserOpenId(weiXinRedirectUrl.getAppId(),weiXinRedirectUrl.getAppSecret(),weiXinWebAuthorize.getCode()); 139 | 140 | /* 获取公众号的access_token信息 */ 141 | String accessToken = weiXinUtil.getAccessToken(weiXinRedirectUrl.getAppId(),weiXinRedirectUrl.getAppSecret(), accessTokenKey); 142 | 143 | /* 获取用户信息 */ 144 | WeiXinUserInfo weiXinUserInfo = weiXinUtil.getUserInfo(accessToken, weiXinUserInfoOpenId.getOpenId()); 145 | 146 | // WeiXinUserInfo weiXinUserInfo = getWeiXinNoSubUserInfo(weiXinUserInfoOpenId); 147 | 148 | /* 看数据库是否存在,存在则更新,不存在就插入*/ 149 | WeiXinUserInfo weiXinUserInfoFromDatabase = baseUserInfoRepository.findBaseUserInfoByOpenId(weiXinUserInfoOpenId.getOpenId()); 150 | if (weiXinUserInfoFromDatabase != null || "".equals(weiXinUserInfoFromDatabase)){ 151 | weiXinUserInfo.setId(weiXinUserInfoFromDatabase.getId()); 152 | } 153 | 154 | /* 保存数据库*/ 155 | baseUserInfoRepository.save(weiXinUserInfo); 156 | /* 保存Session */ 157 | Map map = new HashMap<>(2); 158 | map.put("User",weiXinUserInfo); 159 | map.put("stateInfo",stateInfo); 160 | httpServletUtil.setSession(map,request); 161 | /* 重定向到业务页面*/ 162 | httpServletUtil.redirectToUrl(response,redirectUrl); 163 | } 164 | 165 | /** 166 | * @Author: 公众号:Newtol 167 | * @Description: 网页授权时,用户授权获取用户信息 168 | * @Date: Created in 23:42 169 | * @param: 170 | */ 171 | public WeiXinUserInfo getWeiXinNoSubUserInfo(WeiXinUserInfoOpenId weiXinUserInfoOpenId) throws Exception { 172 | String url = "https://api.weixin.qq.com/sns/userinfo"; 173 | Map map = new HashMap<>(3); 174 | map.put("access_token",weiXinUserInfoOpenId.getAccessToken()); 175 | map.put("openid",weiXinUserInfoOpenId.getOpenId()); 176 | map.put("lang","zh_CN"); 177 | HttpClientResult httpClientResult = HttpClientUtil.doGet(url,map); 178 | System.out.println(httpClientResult.getContent()); 179 | WeiXinUserInfo weiXinUserInfo = JSONObject.parseObject(httpClientResult.getContent(), WeiXinUserInfo.class); 180 | if (weiXinUserInfo ==null || "".equals(weiXinUserInfo)){ 181 | logger.info("获取用户信息失败:"+httpClientResult.getContent()+"==="+"openid"+weiXinUserInfoOpenId.getOpenId()+"==="+"access_token"+weiXinUserInfoOpenId.getAccessToken()); 182 | } 183 | return weiXinUserInfo; 184 | } 185 | /** 186 | * @Author: 公众号:Newtol 187 | * @Description: 网页授权时获取用户的openId 188 | * @Date: Created in 15:56 189 | * @param: 190 | */ 191 | private WeiXinUserInfoOpenId getUserOpenId(String appId, String appSecret, String code) throws Exception { 192 | String url = "https://api.weixin.qq.com/sns/oauth2/access_token"; 193 | /* 封装请求的参数*/ 194 | Map map = new HashMap<>(4); 195 | map.put("appid",appId); 196 | map.put("secret",appSecret); 197 | map.put("code",code); 198 | map.put("grant_type","authorization_code"); 199 | /* 获取请求结果 */ 200 | HttpClientResult httpClientResult = HttpClientUtil.doGet(url,map); 201 | return JSONObject.parseObject(httpClientResult.getContent(), WeiXinUserInfoOpenId.class); 202 | } 203 | 204 | /** 205 | * @param weiXinRedirectUrl 206 | * @Author: 公众号:Newtol 207 | * @Description: 网页授权时,设置跳转的链接,即为返回获取code时,使用的state 208 | * @Date: Created in 13:16 209 | * @param: 210 | */ 211 | @Override 212 | public Result setRedirect(RedirectUrlWeiXinConfig weiXinRedirectUrl) throws NoSuchAlgorithmException { 213 | String state = EncryptUtil.md5(weiXinRedirectUrl.toString()); 214 | String redirectUrl = JSONObject.toJSONString(weiXinRedirectUrl); 215 | redisUtil.setHash(redirectUrlKey,state,redirectUrl); 216 | return ResultUtil.success(state); 217 | } 218 | 219 | /** 220 | * @param response 221 | * @param state 222 | * @Author: 公众号:Newtol 223 | * @Description: 跳转到微信网页授权链接 224 | * @Date: Created in 19:51 225 | * @param: 226 | */ 227 | @Override 228 | public void redirectToWeiXin(HttpServletResponse response, String state) { 229 | String stateInfo = redisUtil.getHash(redirectUrlKey,state); 230 | RedirectUrlWeiXinConfig weiXinRedirectUrl = JSONObject.parseObject(stateInfo, RedirectUrlWeiXinConfig.class); 231 | String url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid="+weiXinRedirectUrl.getAppId()+"&redirect_uri="+getWebAuthorizeUrl+"&response_type=code&scope=snsapi_base&state="+state+"#wechat_redirect"; 232 | httpServletUtil.redirectToUrl(response,url); 233 | } 234 | 235 | /** 236 | * @param weiXinReceiveMessage 237 | * @Author: 公众号:Newtol 238 | * @Description: 微信自动回复关键词 239 | * @Date: Created in 22:44 240 | * @param: 241 | */ 242 | @Override 243 | public String sendAutoMessage(WeiXinReceiveMessage weiXinReceiveMessage) throws JsonProcessingException { 244 | /* 返回结果*/ 245 | String result = null; 246 | /* 获得公众号的id*/ 247 | String appId = weiXinReceiveMessage.getToUserName(); 248 | /* 获得关键词*/ 249 | String content = weiXinReceiveMessage.getContent(); 250 | /* 如果查找到关键词就根据关键词回复,否则调用客服接口*/ 251 | if (content == null ) { 252 | //调用客服接口 253 | return result; 254 | } 255 | /* 获得需要回复的内容,为空则调用客服接口*/ 256 | String str = redisUtil.getHash(appId,content); 257 | if (str == null || "".equals(str)){ 258 | //调用客服接口 259 | return result; 260 | } 261 | 262 | WeiXinSendAutoMessage weiXinSendAutoMessage = JSONObject.parseObject(str,WeiXinSendAutoMessage.class); 263 | /* 设置接收的用户openId*/ 264 | String openId = weiXinReceiveMessage.getFromUserName(); 265 | weiXinSendAutoMessage.setToUserName(openId); 266 | /* 设置时间*/ 267 | Long createTime = System.currentTimeMillis()/1000; 268 | weiXinSendAutoMessage.setCreateTime(createTime); 269 | /* 将数据转换为xml格式*/ 270 | result = jacksonUtil.getXmlFromBean(weiXinSendAutoMessage); 271 | System.out.println("re:"+result); 272 | return result; 273 | } 274 | 275 | /** 276 | * @param 277 | * @Author: 公众号:Newtol 278 | * @Description: 设置自动回复图片内容 279 | * @Date: Created in 23:58 280 | * @param: 281 | */ 282 | @Override 283 | public Result setAutoMessage(WeiXinSendAutoMessage weiXinSendAutoMessage) { 284 | String appId = weiXinSendAutoMessage.getFromUserName(); 285 | String content = JSONObject.toJSONString(weiXinSendAutoMessage); 286 | redisUtil.setHash(appId,weiXinSendAutoMessage.getKey(),content); 287 | return ResultUtil.success(weiXinSendAutoMessage); 288 | } 289 | 290 | } 291 | -------------------------------------------------------------------------------- /src/main/java/cn/newtol/weixin/utils/EncryptUtil.java: -------------------------------------------------------------------------------- 1 | package cn.newtol.weixin.utils; 2 | import sun.misc.BASE64Decoder; 3 | import sun.misc.BASE64Encoder; 4 | 5 | import java.io.IOException; 6 | import java.io.UnsupportedEncodingException; 7 | import java.security.MessageDigest; 8 | import java.security.NoSuchAlgorithmException; 9 | 10 | /** 11 | * @Author: REN 12 | * @Description: 加密工具类 13 | * @Date: Created in 21:57 2018/3/20 14 | */ 15 | public class EncryptUtil { 16 | //md5加密 17 | public static String md5(String data) throws NoSuchAlgorithmException { 18 | MessageDigest md = MessageDigest.getInstance("MD5"); 19 | md.update(data.getBytes()); 20 | StringBuffer buf = new StringBuffer(); 21 | byte[] bits = md.digest(); 22 | for (int i = 0; i < bits.length; i++) { 23 | int a = bits[i]; 24 | if (a < 0){ 25 | a += 256; 26 | } 27 | if (a < 16){ 28 | buf.append("0"); 29 | } 30 | buf.append(Integer.toHexString(a)); 31 | } 32 | return buf.toString(); 33 | } 34 | /** 35 | * BASE64解密 36 | * 37 | * @param key 38 | * @return 39 | * @throws Exception 40 | */ 41 | public static String decryptBASE64(String key) { 42 | byte[] bt; 43 | try { 44 | bt = (new BASE64Decoder()).decodeBuffer(key); 45 | //如果出现乱码可以改成: String(bt, "utf-8")或 gbk 46 | return new String(bt,"utf-8"); 47 | 48 | } catch (IOException e) { 49 | e.printStackTrace(); 50 | return ""; 51 | } 52 | } 53 | 54 | /** 55 | * BASE64加密 56 | * 57 | * @param key 58 | * @return 59 | * @throws Exception 60 | */ 61 | public static String encryptBASE64(String key) { 62 | byte[] bt = key.getBytes(); 63 | return (new BASE64Encoder()).encodeBuffer(bt); 64 | } 65 | 66 | private static final char[] digit = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; 67 | 68 | /** 69 | * 此类不需要实例化 70 | */ 71 | private EncryptUtil() { 72 | } 73 | 74 | /** 75 | * 32位MD5加密,结果大写 76 | * @param str 77 | * @return 78 | */ 79 | public static String MD5(String str) { 80 | return encode(str, "MD5"); 81 | } 82 | 83 | /** 84 | * 32位SHA1加密,结果大写 85 | * @param str 86 | * @return 87 | */ 88 | public static String sha1(String str) { 89 | return encode(str, "SHA-1"); 90 | } 91 | 92 | private static String encode(String str, String algorithm) { 93 | String rs = null; 94 | try { 95 | MessageDigest md = MessageDigest.getInstance(algorithm); 96 | byte[] digest = md.digest(str.toString().getBytes("UTF-8")); 97 | rs = byteToStr(digest); 98 | } catch (NoSuchAlgorithmException e) { 99 | System.out.println("该加密方式不存在"); 100 | } catch (UnsupportedEncodingException e) { 101 | e.printStackTrace(); 102 | } 103 | return rs; 104 | } 105 | /** 106 | * 将byte数组变为16进制对应的字符串 107 | * @param byteArray byte数组 108 | * @return 转换结果 109 | */ 110 | private static String byteToStr(byte[] byteArray) { 111 | int len = byteArray.length; 112 | StringBuilder strDigest = new StringBuilder(len * 2); 113 | for (byte aByteArray : byteArray) { 114 | strDigest.append(byteToHexStr(aByteArray)); 115 | } 116 | return strDigest.toString(); 117 | } 118 | 119 | private static String byteToHexStr(byte mByte) { 120 | char[] tempArr = new char[2]; 121 | tempArr[0] = digit[(mByte >>> 4) & 0X0F]; 122 | tempArr[1] = digit[mByte & 0X0F]; 123 | return new String(tempArr); 124 | 125 | } 126 | } -------------------------------------------------------------------------------- /src/main/java/cn/newtol/weixin/utils/HttpClientUtil.java: -------------------------------------------------------------------------------- 1 | package cn.newtol.weixin.utils; 2 | 3 | import cn.newtol.weixin.domain.HttpClientResult; 4 | import java.io.IOException; 5 | import java.io.UnsupportedEncodingException; 6 | import java.util.ArrayList; 7 | import java.util.HashMap; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.Map.Entry; 11 | import java.util.Set; 12 | 13 | import org.apache.http.HttpStatus; 14 | import org.apache.http.NameValuePair; 15 | import org.apache.http.client.config.RequestConfig; 16 | import org.apache.http.client.entity.UrlEncodedFormEntity; 17 | import org.apache.http.client.methods.CloseableHttpResponse; 18 | import org.apache.http.client.methods.HttpDelete; 19 | import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; 20 | import org.apache.http.client.methods.HttpGet; 21 | import org.apache.http.client.methods.HttpPost; 22 | import org.apache.http.client.methods.HttpPut; 23 | import org.apache.http.client.methods.HttpRequestBase; 24 | import org.apache.http.client.utils.URIBuilder; 25 | import org.apache.http.entity.StringEntity; 26 | import org.apache.http.impl.client.CloseableHttpClient; 27 | import org.apache.http.impl.client.HttpClients; 28 | import org.apache.http.message.BasicNameValuePair; 29 | import org.apache.http.util.EntityUtils; 30 | 31 | 32 | /** 33 | * @Author: 公众号:Newtol 34 | * @Description: http请求工具类 35 | * @Date: Created in 16:45 2018/11/10 36 | */ 37 | public class HttpClientUtil { 38 | // 编码格式。发送编码格式统一用UTF-8 39 | private static final String ENCODING = "UTF-8"; 40 | 41 | // 设置连接超时时间,单位毫秒。 42 | private static final int CONNECT_TIMEOUT = 6000; 43 | 44 | // 请求获取数据的超时时间(即响应时间),单位毫秒。 45 | private static final int SOCKET_TIMEOUT = 6000; 46 | 47 | /** 48 | * 发送get请求;不带请求头和请求参数 49 | * 50 | * @param url 请求地址 51 | * @return 52 | * @throws Exception 53 | */ 54 | public static HttpClientResult doGet(String url) throws Exception { 55 | return doGet(url, null, null); 56 | } 57 | 58 | /** 59 | * 发送get请求;带请求参数 60 | * 61 | * @param url 请求地址 62 | * @param params 请求参数集合 63 | * @return 64 | * @throws Exception 65 | */ 66 | public static HttpClientResult doGet(String url, Map params) throws Exception { 67 | return doGet(url, null, params); 68 | } 69 | 70 | /** 71 | * 发送get请求;带请求头和请求参数 72 | * 73 | * @param url 请求地址 74 | * @param headers 请求头集合 75 | * @param params 请求参数集合 76 | * @return 77 | * @throws Exception 78 | */ 79 | public static HttpClientResult doGet(String url, Map headers, Map params) throws Exception { 80 | // 创建httpClient对象 81 | CloseableHttpClient httpClient = HttpClients.createDefault(); 82 | 83 | // 创建访问的地址 84 | URIBuilder uriBuilder = new URIBuilder(url); 85 | if (params != null) { 86 | Set> entrySet = params.entrySet(); 87 | for (Entry entry : entrySet) { 88 | uriBuilder.setParameter(entry.getKey(), entry.getValue()); 89 | } 90 | } 91 | 92 | // 创建http对象 93 | HttpGet httpGet = new HttpGet(uriBuilder.build()); 94 | /** 95 | * setConnectTimeout:设置连接超时时间,单位毫秒。 96 | * setConnectionRequestTimeout:设置从connect Manager(连接池)获取Connection 97 | * 超时时间,单位毫秒。这个属性是新加的属性,因为目前版本是可以共享连接池的。 98 | * setSocketTimeout:请求获取数据的超时时间(即响应时间),单位毫秒。 如果访问一个接口,多少时间内无法返回数据,就直接放弃此次调用。 99 | */ 100 | RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(CONNECT_TIMEOUT).setSocketTimeout(SOCKET_TIMEOUT).build(); 101 | httpGet.setConfig(requestConfig); 102 | 103 | // 设置请求头 104 | packageHeader(headers, httpGet); 105 | 106 | // 创建httpResponse对象 107 | CloseableHttpResponse httpResponse = null; 108 | 109 | try { 110 | // 执行请求并获得响应结果 111 | return getHttpClientResult(httpResponse, httpClient, httpGet); 112 | } finally { 113 | // 释放资源 114 | release(httpResponse, httpClient); 115 | } 116 | } 117 | 118 | /** 119 | * 发送post请求;不带请求头和请求参数 120 | * 121 | * @param url 请求地址 122 | * @return 123 | * @throws Exception 124 | */ 125 | public static HttpClientResult doPost(String url) throws Exception { 126 | return doPost(url, null, null); 127 | } 128 | 129 | /** 130 | * 发送post请求;带请求参数 131 | * 132 | * @param url 请求地址 133 | * @param params 参数集合 134 | * @return 135 | * @throws Exception 136 | */ 137 | public static HttpClientResult doPost(String url, Map params) throws Exception { 138 | return doPost(url, null, params); 139 | } 140 | 141 | 142 | /** 143 | * @Author: 公众号:Newtol 144 | * @Description: url无参数,JSON数据的POST方法 145 | * @Date: Created in 20:01 146 | * @param: 147 | */ 148 | public static HttpClientResult doPostForJson(String url,String content){ 149 | return doPostForJson(url, content); 150 | } 151 | 152 | /** 153 | * @Author: 公众号:Newtol 154 | * @Description: url有参数,有Json数据的POST方法 155 | * @Date: Created in 20:01 156 | * @param: 157 | */ 158 | 159 | public static HttpClientResult doPostForJson(String url, Map params, String content) throws Exception { 160 | // 创建httpClient对象 161 | CloseableHttpClient httpClient = HttpClients.createDefault(); 162 | 163 | // 创建访问的地址 164 | URIBuilder uriBuilder = new URIBuilder(url); 165 | if (params != null) { 166 | Set> entrySet = params.entrySet(); 167 | for (Entry entry : entrySet) { 168 | uriBuilder.setParameter(entry.getKey(), entry.getValue()); 169 | } 170 | } 171 | 172 | // 创建http对象 173 | HttpPost httpPost = new HttpPost(uriBuilder.build()); 174 | RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(CONNECT_TIMEOUT).setSocketTimeout(SOCKET_TIMEOUT).build(); 175 | httpPost.setConfig(requestConfig); 176 | 177 | StringEntity se = new StringEntity(content.toString(),"UTF-8"); 178 | se.setContentType("application/json;charset=UTf-8"); 179 | 180 | 181 | httpPost.setEntity(se); 182 | 183 | 184 | // 创建httpResponse对象 185 | CloseableHttpResponse httpResponse = null; 186 | 187 | try { 188 | // 执行请求并获得响应结果 189 | return getHttpClientResult(httpResponse, httpClient, httpPost); 190 | } finally { 191 | // 释放资源 192 | release(httpResponse, httpClient); 193 | } 194 | } 195 | 196 | 197 | 198 | /** 199 | * 发送post请求;带请求头和请求参数 200 | * 201 | * @param url 请求地址 202 | * @param headers 请求头集合 203 | * @param params 请求参数集合 204 | * @return 205 | * @throws Exception 206 | */ 207 | public static HttpClientResult doPost(String url, Map headers, Map params) throws Exception { 208 | // 创建httpClient对象 209 | CloseableHttpClient httpClient = HttpClients.createDefault(); 210 | 211 | // 创建http对象 212 | HttpPost httpPost = new HttpPost(url); 213 | /** 214 | * setConnectTimeout:设置连接超时时间,单位毫秒。 215 | * setConnectionRequestTimeout:设置从connect Manager(连接池)获取Connection 216 | * 超时时间,单位毫秒。这个属性是新加的属性,因为目前版本是可以共享连接池的。 217 | * setSocketTimeout:请求获取数据的超时时间(即响应时间),单位毫秒。 如果访问一个接口,多少时间内无法返回数据,就直接放弃此次调用。 218 | */ 219 | RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(CONNECT_TIMEOUT).setSocketTimeout(SOCKET_TIMEOUT).build(); 220 | httpPost.setConfig(requestConfig); 221 | packageHeader(headers, httpPost); 222 | 223 | // 封装请求参数 224 | packageParam(params, httpPost); 225 | 226 | // 创建httpResponse对象 227 | CloseableHttpResponse httpResponse = null; 228 | 229 | try { 230 | // 执行请求并获得响应结果 231 | return getHttpClientResult(httpResponse, httpClient, httpPost); 232 | } finally { 233 | // 释放资源 234 | release(httpResponse, httpClient); 235 | } 236 | } 237 | 238 | 239 | 240 | 241 | /** 242 | * Description: 封装请求头 243 | * @param params 244 | * @param httpMethod 245 | */ 246 | public static void packageHeader(Map params, HttpRequestBase httpMethod) { 247 | // 封装请求头 248 | if (params != null) { 249 | Set> entrySet = params.entrySet(); 250 | for (Entry entry : entrySet) { 251 | // 设置到请求头到HttpRequestBase对象中 252 | httpMethod.setHeader(entry.getKey(), entry.getValue()); 253 | } 254 | } 255 | } 256 | 257 | /** 258 | * Description: 封装请求参数 259 | * 260 | * @param params 261 | * @param httpMethod 262 | * @throws UnsupportedEncodingException 263 | */ 264 | public static void packageParam(Map params, HttpEntityEnclosingRequestBase httpMethod) 265 | throws UnsupportedEncodingException { 266 | // 封装请求参数 267 | if (params != null) { 268 | List nvps = new ArrayList(); 269 | Set> entrySet = params.entrySet(); 270 | for (Entry entry : entrySet) { 271 | nvps.add(new BasicNameValuePair(entry.getKey(), entry.getValue())); 272 | } 273 | 274 | // 设置到请求的http对象中 275 | httpMethod.setEntity(new UrlEncodedFormEntity(nvps, ENCODING)); 276 | } 277 | } 278 | 279 | /** 280 | * Description: 获得响应结果 281 | * 282 | * @param httpResponse 283 | * @param httpClient 284 | * @param httpMethod 285 | * @return 286 | * @throws Exception 287 | */ 288 | public static HttpClientResult getHttpClientResult(CloseableHttpResponse httpResponse, 289 | CloseableHttpClient httpClient, HttpRequestBase httpMethod) throws Exception { 290 | // 执行请求 291 | httpResponse = httpClient.execute(httpMethod); 292 | 293 | // 获取返回结果 294 | if (httpResponse != null && httpResponse.getStatusLine() != null) { 295 | String content = ""; 296 | if (httpResponse.getEntity() != null) { 297 | content = EntityUtils.toString(httpResponse.getEntity(), ENCODING); 298 | } 299 | return new HttpClientResult(httpResponse.getStatusLine().getStatusCode(), content); 300 | } 301 | return new HttpClientResult(HttpStatus.SC_INTERNAL_SERVER_ERROR); 302 | } 303 | 304 | /** 305 | * Description: 释放资源 306 | * 307 | * @param httpResponse 308 | * @param httpClient 309 | * @throws IOException 310 | */ 311 | public static void release(CloseableHttpResponse httpResponse, CloseableHttpClient httpClient) throws IOException { 312 | // 释放资源 313 | if (httpResponse != null) { 314 | httpResponse.close(); 315 | } 316 | if (httpClient != null) { 317 | httpClient.close(); 318 | } 319 | } 320 | } 321 | -------------------------------------------------------------------------------- /src/main/java/cn/newtol/weixin/utils/HttpServletUtil.java: -------------------------------------------------------------------------------- 1 | package cn.newtol.weixin.utils; 2 | 3 | import org.springframework.stereotype.Service; 4 | 5 | import javax.servlet.http.HttpServletRequest; 6 | import javax.servlet.http.HttpServletResponse; 7 | import java.util.Map; 8 | import java.util.Set; 9 | 10 | /** 11 | * @Author: 公众号:Newtol 12 | * @Description: httpServlet工具类 13 | * @Date: Created in 19:26 2018/11/25 14 | */ 15 | @Service 16 | public class HttpServletUtil { 17 | 18 | /** 19 | * @Author: 公众号:Newtol 20 | * @Description: 设置session 21 | * @Date: Created in 19:34 22 | * @param: 23 | */ 24 | public void setSession(Map map, HttpServletRequest request){ 25 | if (map != null) { 26 | Set> entrySet = map.entrySet(); 27 | for (Map.Entry entry : entrySet) { 28 | request.getSession().setAttribute(entry.getKey(), entry.getValue()); 29 | } 30 | } 31 | } 32 | 33 | public void redirectToUrl(HttpServletResponse response,String url){ 34 | response.setContentType("text/html;charset=UTF-8"); 35 | response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY); 36 | response.setHeader("Location", url); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/cn/newtol/weixin/utils/JacksonUtil.java: -------------------------------------------------------------------------------- 1 | package cn.newtol.weixin.utils; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.fasterxml.jackson.core.JsonProcessingException; 5 | import com.fasterxml.jackson.dataformat.xml.XmlMapper; 6 | import org.springframework.stereotype.Service; 7 | 8 | /** 9 | * @Author: 公众号:Newtol 10 | * @Description: Jackson 转换工具类 11 | * @Date: Created in 23:14 2018/11/28 12 | */ 13 | @Service 14 | public class JacksonUtil { 15 | public String getXmlFromBean(Object object) throws JsonProcessingException { 16 | XmlMapper xmlMapper = new XmlMapper(); 17 | xmlMapper.setDefaultUseWrapper(false); 18 | //字段为null,自动忽略,不再序列化 19 | xmlMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); 20 | String result = xmlMapper.writeValueAsString(object); 21 | return result; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/cn/newtol/weixin/utils/RedisUtil.java: -------------------------------------------------------------------------------- 1 | package cn.newtol.weixin.utils; 2 | 3 | import org.springframework.data.redis.core.HashOperations; 4 | import org.springframework.data.redis.core.StringRedisTemplate; 5 | import org.springframework.data.redis.core.ValueOperations; 6 | import org.springframework.stereotype.Service; 7 | 8 | import javax.annotation.Resource; 9 | import java.util.Map; 10 | import java.util.concurrent.TimeUnit; 11 | 12 | /** 13 | * @Author: 公众号:Newtol 14 | * @Description: Redis工具类 15 | * @Date: Created in 18:55 2018/11/9 16 | */ 17 | @Service 18 | public class RedisUtil { 19 | @Resource 20 | private StringRedisTemplate stringRedisTemplate; 21 | /** 22 | * @Author: REN 23 | * @Description: string有过期时间 24 | * @Date: Created in 20:01 25 | * @param key 键 26 | * @param value 值 27 | * @param timeout 过期时间 28 | * @param timeUnit 计时方式 29 | */ 30 | public void setString(String key, String value,long timeout,TimeUnit timeUnit){ 31 | ValueOperations valueOperations = stringRedisTemplate.opsForValue(); 32 | valueOperations.set(key, value,timeout,timeUnit); 33 | } 34 | 35 | /** 36 | * @Author: REN 37 | * @Description: string无过期时间 38 | * @Date: Created in 20:00 39 | * @param: key 40 | */ 41 | public void setString(String key,String value){ 42 | ValueOperations valueOperations = stringRedisTemplate.opsForValue(); 43 | valueOperations.set(key, value); 44 | } 45 | 46 | /** 47 | * get redis: string类型 48 | * @param key key 49 | * @return 50 | */ 51 | public String getString(String key){ 52 | return stringRedisTemplate.opsForValue().get(key); 53 | } 54 | 55 | /** 56 | * set redis: hash类型 57 | * @param key key 58 | * @param filedKey filedkey 59 | * @param value value 60 | */ 61 | public void setHash(String key, String filedKey, String value){ 62 | HashOperations hashOperations = stringRedisTemplate.opsForHash(); 63 | hashOperations.put(key,filedKey, value); 64 | } 65 | 66 | public void setHash(String key , Map map){ 67 | HashOperations hashOperations = stringRedisTemplate.opsForHash(); 68 | hashOperations.putAll(key,map); 69 | } 70 | 71 | /** 72 | * get redis: hash类型 73 | * @param key key 74 | * @param filedkey filedkey 75 | * @return 76 | */ 77 | public String getHash(String key, String filedkey){ 78 | return (String) stringRedisTemplate.opsForHash().get(key, filedkey); 79 | } 80 | 81 | } 82 | 83 | -------------------------------------------------------------------------------- /src/main/java/cn/newtol/weixin/utils/ResultUtil.java: -------------------------------------------------------------------------------- 1 | package cn.newtol.weixin.utils; 2 | 3 | import cn.newtol.weixin.Enum.ResultEnum; 4 | import cn.newtol.weixin.domain.Result; 5 | 6 | /** 7 | * @Author: 公众号:Newtol 8 | * @Description: 9 | * @Date: Created in 13:14 2018/11/10 10 | */ 11 | public class ResultUtil { 12 | /** 13 | * @Author: 公众号:Newtol 14 | * @Description: 请求成功,返回带参数结果 15 | * @Date: Created in 21:16 16 | * @param: 17 | */ 18 | public static Result success(Object object){ 19 | Result result = new Result(); 20 | result.setErrorCode(ResultEnum.SUCCESS.getErrorCode()); 21 | result.setMessage(ResultEnum.SUCCESS.getMessage()); 22 | result.setData(object); 23 | return result; 24 | } 25 | /** 26 | * @Author: 公众号:Newtol 27 | * @Description: 请求成功,返回不带参数结果 28 | * @Date: Created in 21:16 29 | * @param: 30 | */ 31 | public static Result success(){ 32 | return success(null); 33 | } 34 | 35 | /** 36 | * @Author: 公众号:Newtol 37 | * @Description: 访问错误,自定义返回参数 38 | * @Date: Created in 21:17 39 | * @param: 40 | */ 41 | public static Result error(Integer code ,String msg){ 42 | Result result = new Result(); 43 | result.setErrorCode(code); 44 | result.setMessage(msg); 45 | return result; 46 | } 47 | 48 | /** 49 | * @Author: 公众号:Newtol 50 | * @Description: 访问错误,使用预定义返回参数 51 | * @Date: Created in 21:17 52 | * @param: 53 | */ 54 | public static Result error(ResultEnum resultEnum){ 55 | Result result = new Result(); 56 | result.setErrorCode(resultEnum.getErrorCode()); 57 | result.setMessage(resultEnum.getMessage()); 58 | return result; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/cn/newtol/weixin/utils/WeiXinUtil.java: -------------------------------------------------------------------------------- 1 | package cn.newtol.weixin.utils; 2 | 3 | import cn.newtol.weixin.Enum.ResultEnum; 4 | import cn.newtol.weixin.domain.WeiXinUserInfo; 5 | import cn.newtol.weixin.domain.HttpClientResult; 6 | import cn.newtol.weixin.domain.dto.WeiXinVerify; 7 | import cn.newtol.weixin.exceptions.TestException; 8 | import com.alibaba.fastjson.JSONObject; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.stereotype.Component; 12 | import org.springframework.stereotype.Service; 13 | import javax.annotation.Resource; 14 | import java.sql.SQLException; 15 | import java.util.Arrays; 16 | import java.util.HashMap; 17 | import java.util.Map; 18 | import java.util.concurrent.TimeUnit; 19 | 20 | /** 21 | * @Author: 公众号:Newtol 22 | * @Description: 微信后台工具类 23 | * @Date: Created in 17:46 2018/11/24 24 | */ 25 | @Component 26 | @Service 27 | public class WeiXinUtil { 28 | private static final Logger logger = LoggerFactory.getLogger(WeiXinUtil.class); 29 | @Resource 30 | private RedisUtil redisUtil; 31 | 32 | /** 33 | * @Author: 公众号:Newtol 34 | * @Description: 获取access_token 35 | * @Date: Created in 19:00 36 | * @param: 37 | */ 38 | public synchronized String getAccessToken(String appId,String appSecret,String key) throws Exception { 39 | String accessToken = getAccessTokenFromDatabase(appId,key); 40 | if (accessToken == null || "".equals(accessToken)) { 41 | accessToken = getAccessTokenFromWeiXin(appId,appSecret,key); 42 | } 43 | return accessToken; 44 | } 45 | 46 | /** 47 | * @Author: 公众号:Newtol 48 | * @Description: 从Redis获取AccessToken 49 | * @Date: Created in 22:40 50 | * @param: 51 | */ 52 | private String getAccessTokenFromDatabase(String appId,String key) throws SQLException, ClassNotFoundException { 53 | String accessToken = redisUtil.getString(key+appId); 54 | return accessToken; 55 | } 56 | 57 | /** 58 | * @Author: 公众号:Newtol 59 | * @Description: 从微信服务器重新获取AccessToken并存入Redis 60 | * @Date: Created in 22:39 61 | * @param: 62 | */ 63 | private String getAccessTokenFromWeiXin(String appId,String appSecret,String key) throws Exception,TestException { 64 | String accessToken = null; 65 | 66 | /*准备发送GET请求*/ 67 | String url = "https://api.weixin.qq.com/cgi-bin/token"; 68 | HashMap hashMap = new HashMap<>(); 69 | hashMap.put("grant_type","client_credential"); 70 | hashMap.put("appid",appId); 71 | hashMap.put("secret",appSecret); 72 | 73 | /*获取返回结果*/ 74 | HttpClientResult httpClientResult = HttpClientUtil.doGet(url,hashMap); 75 | JSONObject jsonObject = JSONObject.parseObject(httpClientResult.getContent()); 76 | accessToken = jsonObject.getString("access_token"); 77 | 78 | /*获取失败就抛出异常*/ 79 | if (accessToken == null || "".equals(accessToken)){ 80 | logger.info("请求AccessToken错误:"+"===appid:"+appId+"===appSecret:"+appSecret); 81 | throw new TestException(ResultEnum.ERROR_WEIXINBASEINFO); 82 | } 83 | 84 | /*因为Access_Token过期时间为7200所以将Redis中的AccessToken过期时间提前50s过期,将key设置为配置文件中的key+appId的形式*/ 85 | redisUtil.setString(key+appId,accessToken,7150, TimeUnit.SECONDS); 86 | return accessToken; 87 | } 88 | /** 89 | * @Author: 公众号:Newtol 90 | * @Description: 接入微信服务器 91 | * @Date: Created in 18:22 92 | * @param: weiXinVerify 93 | */ 94 | public String joinWeiXin(WeiXinVerify weiXinVerify,String token) { 95 | String list[] = {token,weiXinVerify.getTimestamp(),weiXinVerify.getNonce()}; 96 | //字典排序 97 | Arrays.sort(list); 98 | //拼接字符串 99 | StringBuilder builder = new StringBuilder(); 100 | for (String str : list) { 101 | builder.append(str); 102 | } 103 | //sha1加密 104 | String hashcode = EncryptUtil.sha1(builder.toString()); 105 | //不区分大小写差异情况下比较是否相同 106 | if (hashcode.equalsIgnoreCase(weiXinVerify.getSignature())) { 107 | //响应输出 108 | return weiXinVerify.getEchostr(); 109 | } 110 | logger.error("微信接入失败"); 111 | return null; 112 | } 113 | 114 | /** 115 | * @Author: 公众号:Newtol 116 | * @Description: 获取微信用户信息 117 | * @Date: Created in 19:19 118 | * @param: 119 | */ 120 | 121 | public WeiXinUserInfo getUserInfo(String access_token, String openId) throws Exception { 122 | String url = "https://api.weixin.qq.com/cgi-bin/user/info"; 123 | Map map = new HashMap<>(); 124 | map.put("access_token",access_token); 125 | map.put("openid",openId); 126 | map.put("lang","zh_CN"); 127 | HttpClientResult httpClientResult = HttpClientUtil.doGet(url,map); 128 | System.out.println(httpClientResult.getContent()); 129 | WeiXinUserInfo weiXinUserInfo = JSONObject.parseObject(httpClientResult.getContent(), WeiXinUserInfo.class); 130 | if (weiXinUserInfo ==null || "".equals(weiXinUserInfo)){ 131 | logger.info("获取用户信息失败:"+httpClientResult.getContent()+"==="+"openid"+openId+"==="+"access_token"+access_token); 132 | } 133 | return weiXinUserInfo; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 9090 3 | spring: 4 | redis: 5 | database: 0 6 | host: localhost 7 | password: 8 | port: 6379 9 | jedis: 10 | pool: 11 | max-active: 8 12 | max-wait: -1 13 | max-idle: 8 14 | min-idle: 0 15 | 16 | datasource: 17 | driver-class-name: com.mysql.cj.jdbc.Driver 18 | url: jdbc:mysql://localhost:3306/weixin?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai 19 | username: root 20 | password: 21 | jpa: 22 | hibernate: 23 | ddl-auto: update 24 | show-sql: true 25 | logging: 26 | level: 27 | cn: 28 | newtol: debug 29 | path: weiXin 30 | pattern: 31 | file: "%d{yyyy-MM-dd} === [%thread] === %-5level === %logger{50} ==== %msg%n" 32 | console: "%d{yyyy-MM-dd} [%thread] %-5level %logger{50} - %msg%n" 33 | 34 | --- 35 | # 微信接入部分配置 36 | weiXin: 37 | # 微信服务器配置的token 38 | token: bbbbbbbbbb 39 | # Redis中access_token存储的key 40 | accessTokenKey: accessToken_ 41 | # 跳转链接的key 42 | redirectUrlKey: redirect_Url 43 | # 获取网页授权时,使用的链接地址 44 | getWebAuthorize: http://newtol.wezoz.com/getWebAuthorize 45 | 46 | 47 | 48 | # appId: wxc5e0d5b55b141e56 49 | # appSecret: 782fc50a471b88e87e544885b2dcc782 50 | # getWebAuthorize: http://ys5p5e.natappfree.cc/getOpenId 51 | 52 | # {"url":"http:\/\/mmbiz.qpic.cn\/mmbiz_jpg\/icor8DRGziah4INEIVibGtiapicKQzvibVicNeqwbkUEHefqlNYHeLc94yibH7ZV3FJqdviaZXL0HPUlUvtldfRwnUc2npA\/0"} 53 | 54 | # {"media_id":"tP0y34JmbzKUFw-m4Zy6U3VZcoCjUvql9yZfVTa9mwM","url":"http:\/\/mmbiz.qpic.cn\/mmbiz_jpg\/icor8DRGziah4INEIVibGtiapicKQzvibVicNeqwbkUEHefqlNYHeLc94yibH7ZV3FJqdviaZXL0HPUlUvtldfRwnUc2npA\/0?wx_fmt=jpeg"} 55 | # {"media_id":"tP0y34JmbzKUFw-m4Zy6U16PpUXJjo0OP5hxBce3P5s","url":"http:\/\/mmbiz.qpic.cn\/mmbiz_jpg\/icor8DRGziah4INEIVibGtiapicKQzvibVicNeqnvMSEPvK3csvlvKPKaBmwAKMZ9ibUVc9icJXJicNPNM9ViaLrs04YUmJPg\/0?wx_fmt=jpeg"} 56 | menu: 57 | button: 58 | - sub_button: 59 | - clickButton: 60 | type: click 61 | name: 点击 62 | key: hi 63 | - clickButton: 64 | type: click 65 | name: 点击2 66 | key: hi1 67 | name: 视图 68 | - sub_button: 69 | - clickButton: 70 | type: view 71 | name: 视图2 72 | url: http://newtol.wezoz.com/redirectUrl?state=010e635c254a61fa5af79b246e7b2697 73 | name: 菜单 74 | 75 | -------------------------------------------------------------------------------- /src/test/java/cn/newtol/weixin/WeixinApplicationTests.java: -------------------------------------------------------------------------------- 1 | package cn.newtol.weixin; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class WeixinApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | --------------------------------------------------------------------------------