From d15741ce8f5cffb1f0a8665e753c5f52d033cef9 Mon Sep 17 00:00:00 2001 From: litao Date: Fri, 18 Aug 2023 18:05:32 +0800 Subject: [PATCH] =?UTF-8?q?2023=E5=B9=B48=E6=9C=8818=E6=97=A518:05:23?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 24 ++ .../springblade/common/enums/DictEnum.java | 4 + .../controller/AppEbLoginController.java | 112 ++++++++ .../controller/WeChatUserController.java | 45 ++++ .../weixin/entity/WeChatPhone.java | 14 + .../weixin/entity/WeChatPhoneInfo.java | 16 ++ .../springblade/weixin/entity/WeChatUser.java | 32 +++ .../weixin/mapper/WeChatUserMapper.java | 28 ++ .../weixin/mapper/WeChatUserMapper.xml | 5 + .../weixin/service/IWeChatUserService.java | 29 +++ .../service/impl/WeChatUserServiceImpl.java | 36 +++ .../weixin/utils/HttpClientSslUtils.java | 242 ++++++++++++++++++ .../springblade/weixin/utils/JsonUtil.java | 112 ++++++++ .../springblade/weixin/utils/WeChatUtil.java | 121 +++++++++ 14 files changed, 820 insertions(+) create mode 100644 src/main/java/org/springblade/weixin/controller/AppEbLoginController.java create mode 100644 src/main/java/org/springblade/weixin/controller/WeChatUserController.java create mode 100644 src/main/java/org/springblade/weixin/entity/WeChatPhone.java create mode 100644 src/main/java/org/springblade/weixin/entity/WeChatPhoneInfo.java create mode 100644 src/main/java/org/springblade/weixin/entity/WeChatUser.java create mode 100644 src/main/java/org/springblade/weixin/mapper/WeChatUserMapper.java create mode 100644 src/main/java/org/springblade/weixin/mapper/WeChatUserMapper.xml create mode 100644 src/main/java/org/springblade/weixin/service/IWeChatUserService.java create mode 100644 src/main/java/org/springblade/weixin/service/impl/WeChatUserServiceImpl.java create mode 100644 src/main/java/org/springblade/weixin/utils/HttpClientSslUtils.java create mode 100644 src/main/java/org/springblade/weixin/utils/JsonUtil.java create mode 100644 src/main/java/org/springblade/weixin/utils/WeChatUtil.java diff --git a/pom.xml b/pom.xml index fb5a80b..998c63d 100644 --- a/pom.xml +++ b/pom.xml @@ -172,6 +172,30 @@ lombok provided + + + com.github.binarywang + weixin-java-miniapp + 3.8.0 + + + + cn.hutool + hutool-all + 5.4.0 + + + + org.projectlombok + lombok + true + + + + com.alibaba + fastjson + 1.2.75 + diff --git a/src/main/java/org/springblade/common/enums/DictEnum.java b/src/main/java/org/springblade/common/enums/DictEnum.java index 84f3096..075c056 100644 --- a/src/main/java/org/springblade/common/enums/DictEnum.java +++ b/src/main/java/org/springblade/common/enums/DictEnum.java @@ -88,6 +88,10 @@ public enum DictEnum { * 用户平台 */ USER_TYPE("user_type"), + /** + * 微信小程序 + */ + WECHAT_APP("weChat_app"), ; final String name; diff --git a/src/main/java/org/springblade/weixin/controller/AppEbLoginController.java b/src/main/java/org/springblade/weixin/controller/AppEbLoginController.java new file mode 100644 index 0000000..8a85e6f --- /dev/null +++ b/src/main/java/org/springblade/weixin/controller/AppEbLoginController.java @@ -0,0 +1,112 @@ +package org.springblade.weixin.controller; + +import com.baomidou.mybatisplus.core.toolkit.StringUtils; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import io.swagger.annotations.Api; +import lombok.AllArgsConstructor; +import org.springblade.common.cache.DictCache; +import org.springblade.common.enums.DictEnum; +import org.springblade.core.log.exception.ServiceException; +import org.springblade.core.tool.api.R; +import org.springblade.core.tool.support.Kv; +import org.springblade.core.tool.utils.SpringUtil; +import org.springblade.modules.auth.endpoint.BladeTokenEndPoint; +import org.springblade.modules.system.entity.User; +import org.springblade.modules.system.service.IUserService; +import org.springblade.weixin.entity.WeChatPhone; +import org.springblade.weixin.entity.WeChatPhoneInfo; +import org.springblade.weixin.entity.WeChatUser; +import org.springblade.weixin.service.IWeChatUserService; +import org.springblade.weixin.utils.WeChatUtil; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.Map; + +@RestController +@Api(tags = "登录-小程序") +@AllArgsConstructor +@RequestMapping("/app") +public class AppEbLoginController { + + private final IWeChatUserService weChatUserService; + + /** + * 获取openid + * + * @param weChatPhone + * @return + */ + @PostMapping("/login") + public R login(@RequestBody WeChatPhone weChatPhone) { + //小程序appId appSecret +// String appId = "wx432c2efe6df3b97a"; +// String appSecret = "859df8b167e74223e9237dee1b344524"; + String appId = DictCache.getValue(DictEnum.WECHAT_APP, "appId"); + String appSecret = DictCache.getValue(DictEnum.WECHAT_APP, "appSecret"); + + //小程序需要传来一个code + cn.hutool.json.JSONObject accessTokenJson = WeChatUtil.getCode2Session(weChatPhone.getCode(), appId, appSecret); + String openid = accessTokenJson.get("openid", String.class); + System.out.println("accessTokenJson:" + accessTokenJson.toString()); + + Map map = new HashMap<>(); + map.put("openid", openid); + map.put("userInfo", null); + //根据openid获取本地用户信息 + WeChatUser user = weChatUserService.getOne(Wrappers.lambdaQuery().eq(WeChatUser::getOpenId, openid)); + if (user != null) { + map.put("userInfo", user); +// BladeTokenEndPoint point = SpringUtil.getBean(BladeTokenEndPoint.class); +// Kv admin = point.token("000000", "admin", "21232f297a57a5a743894a0e4a801fc3", "", ""); + } + //获得响应的数据 +// Map responseBody = map; + + //if (responseBody.get("errcode")!=null&&responseBody.get("errcode").equals(40029)){ + // return CommonResult.failed("code 无效"); + // } + // 解密用户信息 +// String encryptedData = request.get("encryptedData"); +// String iv = request.get("iv"); +// String sessionKey = (String) responseBody.get("session_key"); +// Map userInfo = getDecryptedUserInfo(encryptedData, sessionKey, iv); + + //这里可以对用户信息进行一些操作 + return R.data(map); + } + + /** + * 用前端请求接口获取的code换取用户手机号 + * 前端需要请求的接口:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/getPhoneNumber.html + * + * @param weChatPhone + * @return + */ + @PostMapping("/phone") + public R getPhone(@RequestBody WeChatPhone weChatPhone) { + //小程序appId appSecret +// String appId = "wx432c2efe6df3b97a"; +// String appSecret = "859df8b167e74223e9237dee1b344524"; + String appId = DictCache.getValue(DictEnum.WECHAT_APP, "appId"); + String appSecret = DictCache.getValue(DictEnum.WECHAT_APP, "appSecret"); + + // 1.请求微信接口服务,获取accessToken + cn.hutool.json.JSONObject accessTokenJson = WeChatUtil.getAccessToken(appId, appSecret); + System.out.println("accessTokenJson:" + accessTokenJson); + String accessToken = accessTokenJson.get("access_token", String.class); + System.out.println("accessToken:" + accessToken); + + // 2.请求微信接口服务,获取用户手机号信息 + if (StringUtils.isBlank(accessToken)) { + throw new ServiceException("获取access_token失败"); + } + + cn.hutool.json.JSONObject phoneNumberJson = WeChatUtil.getPhoneNumber(weChatPhone.getCode(), accessToken); + System.out.println("phoneNumberJson:" + phoneNumberJson); + WeChatPhoneInfo phoneInfo = phoneNumberJson.get("phone_info", WeChatPhoneInfo.class); + System.out.println("phoneInfo:" + phoneInfo.toString()); + return R.data(phoneInfo.getPurePhoneNumber()); + } + +} \ No newline at end of file diff --git a/src/main/java/org/springblade/weixin/controller/WeChatUserController.java b/src/main/java/org/springblade/weixin/controller/WeChatUserController.java new file mode 100644 index 0000000..5bc8b1a --- /dev/null +++ b/src/main/java/org/springblade/weixin/controller/WeChatUserController.java @@ -0,0 +1,45 @@ +package org.springblade.weixin.controller; + +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import lombok.AllArgsConstructor; +import org.springblade.core.mp.support.Condition; +import org.springblade.core.mp.support.Query; +import org.springblade.core.tool.api.R; +import org.springblade.core.tool.utils.Func; +import org.springblade.weixin.entity.WeChatUser; +import org.springblade.weixin.service.IWeChatUserService; +import org.springframework.web.bind.annotation.*; + +@RestController +@AllArgsConstructor +@RequestMapping("/weChatUser") +public class WeChatUserController { + + private final IWeChatUserService weChatUserService; + + @GetMapping("/list") + public R list(WeChatUser weChatUser, Query query) { + return R.data(weChatUserService.page(Condition.getPage(query), Wrappers.lambdaQuery(weChatUser))); + } + + @PostMapping("/save") + public R save(@RequestBody WeChatUser weChatUser) { + return R.status(weChatUserService.save(weChatUser)); + } + + @PostMapping("/update") + public R update(@RequestBody WeChatUser weChatUser) { + return R.status(weChatUserService.updateById(weChatUser)); + } + + @PostMapping("/delete") + public R delete(@RequestParam String ids) { + return R.status(weChatUserService.deleteLogic(Func.toLongList(ids))); + } + + @GetMapping("/getWeChatUser") + public R getWeChatUser(String openId) { + return R.data(weChatUserService.getOne(Wrappers.lambdaQuery().eq(WeChatUser::getOpenId, openId))); + } + +} \ No newline at end of file diff --git a/src/main/java/org/springblade/weixin/entity/WeChatPhone.java b/src/main/java/org/springblade/weixin/entity/WeChatPhone.java new file mode 100644 index 0000000..edf4589 --- /dev/null +++ b/src/main/java/org/springblade/weixin/entity/WeChatPhone.java @@ -0,0 +1,14 @@ +package org.springblade.weixin.entity; + +import lombok.Data; + +@Data +public class WeChatPhone { + // getPhoneNumber接口返回的code + private String code; + // 小程序的appid(一般是在程序中配置,不需要前端传参) + private String appid; + // 小程序的secretKey(一般是在程序中配置,不需要前端传参) + private String secretKey; + +} diff --git a/src/main/java/org/springblade/weixin/entity/WeChatPhoneInfo.java b/src/main/java/org/springblade/weixin/entity/WeChatPhoneInfo.java new file mode 100644 index 0000000..047cf7e --- /dev/null +++ b/src/main/java/org/springblade/weixin/entity/WeChatPhoneInfo.java @@ -0,0 +1,16 @@ +package org.springblade.weixin.entity; + +import lombok.Data; + +@Data +public class WeChatPhoneInfo { + // 用户绑定的手机号(国外手机号会有区号) + private String phoneNumber; + // 没有区号的手机号 + private String purePhoneNumber; + // 区号 + private String countryCode; + // 数据水印 + private String watermark; + +} diff --git a/src/main/java/org/springblade/weixin/entity/WeChatUser.java b/src/main/java/org/springblade/weixin/entity/WeChatUser.java new file mode 100644 index 0000000..78b38b3 --- /dev/null +++ b/src/main/java/org/springblade/weixin/entity/WeChatUser.java @@ -0,0 +1,32 @@ +package org.springblade.weixin.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import org.springblade.core.tenant.mp.TenantEntity; + +@Data +@TableName("eh_wx_user") +public class WeChatUser extends TenantEntity { + private static final long serialVersionUID = 1L; + + /** + * 微信用户唯一标识 + */ + private String openId; + + /** + * 微信用户唯一标识 + */ + private String username; + + /** + * 微信用户唯一标识 + */ + private String phone; + + /** + * 微信用户唯一标识 + */ + private String avatar; + +} diff --git a/src/main/java/org/springblade/weixin/mapper/WeChatUserMapper.java b/src/main/java/org/springblade/weixin/mapper/WeChatUserMapper.java new file mode 100644 index 0000000..9b4749f --- /dev/null +++ b/src/main/java/org/springblade/weixin/mapper/WeChatUserMapper.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018-2028, Chill Zhuang All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * Neither the name of the dreamlu.net developer nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * Author: Chill 庄骞 (smallchill@163.com) + */ +package org.springblade.weixin.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.springblade.weixin.entity.WeChatUser; + +/** + * Mapper 接口 + * @author BladeX + */ +public interface WeChatUserMapper extends BaseMapper { + +} diff --git a/src/main/java/org/springblade/weixin/mapper/WeChatUserMapper.xml b/src/main/java/org/springblade/weixin/mapper/WeChatUserMapper.xml new file mode 100644 index 0000000..c498422 --- /dev/null +++ b/src/main/java/org/springblade/weixin/mapper/WeChatUserMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/src/main/java/org/springblade/weixin/service/IWeChatUserService.java b/src/main/java/org/springblade/weixin/service/IWeChatUserService.java new file mode 100644 index 0000000..c2e9564 --- /dev/null +++ b/src/main/java/org/springblade/weixin/service/IWeChatUserService.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2018-2028, Chill Zhuang All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * Neither the name of the dreamlu.net developer nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * Author: Chill 庄骞 (smallchill@163.com) + */ +package org.springblade.weixin.service; + +import org.springblade.core.mp.base.BaseService; +import org.springblade.weixin.entity.WeChatUser; + +/** + * 服务类 + * + * @author BladeX + */ +public interface IWeChatUserService extends BaseService { + +} diff --git a/src/main/java/org/springblade/weixin/service/impl/WeChatUserServiceImpl.java b/src/main/java/org/springblade/weixin/service/impl/WeChatUserServiceImpl.java new file mode 100644 index 0000000..416cbb8 --- /dev/null +++ b/src/main/java/org/springblade/weixin/service/impl/WeChatUserServiceImpl.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2018-2028, Chill Zhuang All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * Neither the name of the dreamlu.net developer nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * Author: Chill 庄骞 (smallchill@163.com) + */ +package org.springblade.weixin.service.impl; + +import org.springblade.core.mp.base.BaseServiceImpl; +import org.springblade.modules.system.entity.ApiScope; +import org.springblade.modules.system.mapper.ApiScopeMapper; +import org.springblade.modules.system.service.IApiScopeService; +import org.springblade.weixin.entity.WeChatUser; +import org.springblade.weixin.mapper.WeChatUserMapper; +import org.springblade.weixin.service.IWeChatUserService; +import org.springframework.stereotype.Service; + +/** + * 服务实现类 + * + * @author BladeX + */ +@Service +public class WeChatUserServiceImpl extends BaseServiceImpl implements IWeChatUserService { + +} diff --git a/src/main/java/org/springblade/weixin/utils/HttpClientSslUtils.java b/src/main/java/org/springblade/weixin/utils/HttpClientSslUtils.java new file mode 100644 index 0000000..5c792b3 --- /dev/null +++ b/src/main/java/org/springblade/weixin/utils/HttpClientSslUtils.java @@ -0,0 +1,242 @@ +package org.springblade.weixin.utils; + +import lombok.extern.slf4j.Slf4j; +import org.apache.http.NameValuePair; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.DefaultHttpRequestRetryHandler; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.message.BasicHeader; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.util.EntityUtils; +import org.slf4j.MDC; +import org.springframework.http.HttpStatus; +import org.springframework.util.CollectionUtils; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@Slf4j +public class HttpClientSslUtils { + /** + * 默认的字符编码格式 + */ + private static final String DEFAULT_CHAR_SET = "UTF-8"; + /** + * 默认连接超时时间 (毫秒) + */ + private static final Integer DEFAULT_CONNECTION_TIME_OUT = 2000; + /** + * 默认socket超时时间 (毫秒) + */ + private static final Integer DEFAULT_SOCKET_TIME_OUT = 3000; + + /** + * socketTimeOut上限 + */ + private static final Integer SOCKET_TIME_OUT_UPPER_LIMIT = 10000; + + /** + * socketTimeOut下限 + */ + private static final Integer SOCKET_TIME_OUT_LOWER_LIMIT = 1000; + + private static CloseableHttpClient getHttpClient() { + RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(DEFAULT_SOCKET_TIME_OUT) + .setConnectTimeout(DEFAULT_CONNECTION_TIME_OUT).build(); + return HttpClients.custom().setDefaultRequestConfig(requestConfig) + .setRetryHandler(new DefaultHttpRequestRetryHandler()).build(); + } + + private static CloseableHttpClient getHttpClient(Integer socketTimeOut) { + RequestConfig requestConfig = + RequestConfig.custom().setSocketTimeout(socketTimeOut).setConnectTimeout(DEFAULT_CONNECTION_TIME_OUT) + .build(); + return HttpClients.custom().setDefaultRequestConfig(requestConfig) + .setRetryHandler(new DefaultHttpRequestRetryHandler()).build(); + } + + public static String doPost(String url, String requestBody) throws Exception { + return doPost(url, requestBody, ContentType.APPLICATION_JSON); + } + + public static String doPost(String url, String requestBody, Integer socketTimeOut) throws Exception { + return doPost(url, requestBody, ContentType.APPLICATION_JSON, null, socketTimeOut); + } + + public static String doPost(String url, String requestBody, ContentType contentType) throws Exception { + return doPost(url, requestBody, contentType, null); + } + + public static String doPost(String url, String requestBody, List headers) throws Exception { + return doPost(url, requestBody, ContentType.APPLICATION_JSON, headers); + } + + public static String doPost(String url, String requestBody, ContentType contentType, List headers) + throws Exception { + return doPost(url, requestBody, contentType, headers, getHttpClient()); + } + + public static String doPost(String url, String requestBody, ContentType contentType, List headers, + Integer socketTimeOut) throws Exception { + if (socketTimeOut < SOCKET_TIME_OUT_LOWER_LIMIT || socketTimeOut > SOCKET_TIME_OUT_UPPER_LIMIT) { + log.error("socketTimeOut非法"); + throw new Exception(); + } + return doPost(url, requestBody, contentType, headers, getHttpClient(socketTimeOut)); + } + + + /** + * 通用Post远程服务请求 + * + * @param url 请求url地址 + * @param requestBody 请求体body + * @param contentType 内容类型 + * @param headers 请求头 + * @return String 业务自行解析 + * @throws Exception + */ + public static String doPost(String url, String requestBody, ContentType contentType, List headers, + CloseableHttpClient client) throws Exception { + + // 构造http方法,设置请求和传输超时时间,重试3次 + CloseableHttpResponse response = null; + long startTime = System.currentTimeMillis(); + try { + HttpPost post = new HttpPost(url); + if (!CollectionUtils.isEmpty(headers)) { + for (BasicHeader header : headers) { + post.setHeader(header); + } + } + StringEntity entity = + new StringEntity(requestBody, ContentType.create(contentType.getMimeType(), DEFAULT_CHAR_SET)); + post.setEntity(entity); + response = client.execute(post); + if (response.getStatusLine().getStatusCode() != HttpStatus.OK.value()) { + log.error("业务请求返回失败:{}", EntityUtils.toString(response.getEntity())); + throw new Exception(); + } + String result = EntityUtils.toString(response.getEntity()); + return result; + } finally { + releaseResourceAndLog(url, requestBody, response, startTime); + } + } + + /** + * 暂时用于智慧园区业务联调方式 + * + * @param url 业务请求url + * @param param 业务参数 + * @return + * @throws Exception + */ + public static String doPostWithUrlEncoded(String url, + Map param) throws Exception { + // 创建Httpclient对象 + CloseableHttpClient httpClient = getHttpClient(); + CloseableHttpResponse response = null; + long startTime = System.currentTimeMillis(); + try { + // 创建Http Post请求 + HttpPost httpPost = new HttpPost(url); + // 创建参数列表 + if (param != null) { + List paramList = new ArrayList<>(); + for (String key : param.keySet()) { + paramList.add(new BasicNameValuePair(key, param.get(key))); + } + // 模拟表单 + UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList, DEFAULT_CHAR_SET); + httpPost.setEntity(entity); + } + // 执行http请求 + response = httpClient.execute(httpPost); + if (response.getStatusLine().getStatusCode() != HttpStatus.OK.value()) { + log.error("业务请求返回失败:{}", EntityUtils.toString(response.getEntity())); + throw new Exception(); + } + String resultString = EntityUtils.toString(response.getEntity(), DEFAULT_CHAR_SET); + return resultString; + } finally { + releaseResourceAndLog(url, param == null ? null : param.toString(), response, startTime); + } + } + + private static void releaseResourceAndLog(String url, String request, CloseableHttpResponse response, long startTime) { + if (null != response) { + try { + response.close(); + recordInterfaceLog(startTime, url, request); + } catch (IOException e) { + log.error(e.getMessage()); + } + } + } + + public static String doGet(String url) throws Exception { + return doGet(url, ContentType.DEFAULT_TEXT); + } + + public static String doGet(String url, ContentType contentType) throws Exception { + return doGet(url, contentType, null); + } + + public static String doGet(String url, List headers) throws Exception { + return doGet(url, ContentType.DEFAULT_TEXT, headers); + } + + /** + * 通用Get远程服务请求 + * + * @param url 请求参数 + * @param contentType 请求参数类型 + * @param headers 请求头可以填充 + * @return String 业务自行解析数据 + * @throws Exception + */ + public static String doGet(String url, ContentType contentType, List headers) throws Exception { + CloseableHttpResponse response = null; + long startTime = System.currentTimeMillis(); + try { + CloseableHttpClient client = getHttpClient(); + HttpGet httpGet = new HttpGet(url); + if (!CollectionUtils.isEmpty(headers)) { + for (BasicHeader header : headers) { + httpGet.setHeader(header); + } + } + if (contentType != null) { + httpGet.setHeader("Content-Type", contentType.getMimeType()); + } + response = client.execute(httpGet); + if (response.getStatusLine().getStatusCode() != HttpStatus.OK.value()) { + log.error("业务请求返回失败:{}", EntityUtils.toString(response.getEntity())); + throw new Exception(); + } + String result = EntityUtils.toString(response.getEntity()); + return result; + } finally { + releaseResourceAndLog(url, null, response, startTime); + } + } + + private static void recordInterfaceLog(long startTime, String url, String request) { + long endTime = System.currentTimeMillis(); + long timeCost = endTime - startTime; + MDC.put("totalTime", String.valueOf(timeCost)); + MDC.put("url", url); + MDC.put("logType", "third-platform-service"); + log.info("HttpClientSslUtils 远程请求:{} 参数:{} 耗时:{}ms", url, request, timeCost); + } +} diff --git a/src/main/java/org/springblade/weixin/utils/JsonUtil.java b/src/main/java/org/springblade/weixin/utils/JsonUtil.java new file mode 100644 index 0000000..84510b8 --- /dev/null +++ b/src/main/java/org/springblade/weixin/utils/JsonUtil.java @@ -0,0 +1,112 @@ +package org.springblade.weixin.utils; + + +import com.alibaba.fastjson.TypeReference; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.StringUtils; + +import java.text.SimpleDateFormat; + + + +@Slf4j +public class JsonUtil { + /** + * 定义映射对象 + */ + public static ObjectMapper objectMapper = new ObjectMapper(); + + /** + * 日期格式化 + */ + private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; + + static { + //对象的所有字段全部列入 + objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + //取消默认转换timestamps形式 + objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); + //忽略空Bean转json的错误 + objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); + //所有的日期格式都统一为以下的样式,即yyyy-MM-dd HH:mm:ss + objectMapper.setDateFormat(new SimpleDateFormat(DATE_FORMAT)); + //忽略 在json字符串中存在,但是在java对象中不存在对应属性的情况。防止错误 + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + + } + + /** + * string转JsonNode + * + * @param jsonString + * @return com.fasterxml.jackson.databind.JsonNode + */ + public static JsonNode stringToJsonNode(String jsonString) throws JsonProcessingException { + + return objectMapper.readTree(jsonString); + + } + + /** + * 对象转json字符串 + * + * @param obj + * @param + */ + public static String objToString(T obj) { + + if (obj == null) { + return null; + } + try { + return obj instanceof String ? (String) obj : objectMapper.writeValueAsString(obj); + } catch (JsonProcessingException e) { + log.warn("Parse Object to String error : {}", e.getMessage()); + return null; + } + } + + /** + * 对象转格式化的字符串字符串 + * + * @param obj + * @param + * @return + */ + public static String objToPrettyString(T obj) { + if (obj == null) { + return null; + } + try { + return obj instanceof String ? (String) obj : objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj); + } catch (JsonProcessingException e) { + log.warn("Parse Object to String error : {}", e.getMessage()); + return null; + } + } + + /** + * json字符串转对象 + * + * @param jsonString + * @param cls + * @param + */ + public static T stringToObj(String jsonString, Class cls) { + if (StringUtils.isEmpty(jsonString) || cls == null) { + return null; + } + try { + return cls.equals(String.class) ? (T) jsonString : objectMapper.readValue(jsonString, cls); + } catch (JsonProcessingException e) { + log.warn("Parse String to Object error : {}", e.getMessage()); + return null; + } + } +} diff --git a/src/main/java/org/springblade/weixin/utils/WeChatUtil.java b/src/main/java/org/springblade/weixin/utils/WeChatUtil.java new file mode 100644 index 0000000..42c2c21 --- /dev/null +++ b/src/main/java/org/springblade/weixin/utils/WeChatUtil.java @@ -0,0 +1,121 @@ +package org.springblade.weixin.utils; + +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.alibaba.fastjson.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.codec.binary.Base64; +import org.springblade.core.http.util.HttpUtil; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.RestTemplate; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +public class WeChatUtil { + + /** + * 请求微信接口服务,获取小程序全局唯一后台接口调用凭据(access_token) + * https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/access-token/auth.getAccessToken.html + * + * @param appid + * @param secretKey + * @return + */ + public static JSONObject getAccessToken(String appid, String secretKey) { + String result = null; + try { + String baseUrl = "https://api.weixin.qq.com/cgi-bin/token"; + HashMap requestParam = new HashMap<>(); + // 小程序 appId + requestParam.put("grant_type", "client_credential"); + // 小程序唯一凭证id + requestParam.put("appid", appid); + // 小程序 appSecret(小程序的唯一凭证密钥) + requestParam.put("secret", secretKey); + // 发送GET请求读取调用微信接口获取openid用户唯一标识 + result = HttpUtil.get(baseUrl, requestParam); + } catch (Exception e) { + e.printStackTrace(); + } + return JSONUtil.parseObj(result); + } + + /** + * 请求微信接口服务,用code换取用户手机号(每个code只能使用一次,code的有效期为5min) + * https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/phonenumber/phonenumber.getPhoneNumber.html + * + * @param code + * @param accessToken + * @return + */ + public static JSONObject getPhoneNumber(String code, String accessToken) { + String result = null; + try { + // 接口调用凭证:accessToken + String baseUrl = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=" + accessToken; + JSONObject jsonObject = new JSONObject(); + jsonObject.put("code", code); + String reqJsonStr = JsonUtil.objToString(jsonObject); + result = HttpClientSslUtils.doPost(baseUrl, reqJsonStr); + + return JSONUtil.parseObj(result); + } catch (Exception e) { + e.printStackTrace(); + } + return JSONUtil.parseObj(result); + } + + /** + * 请求微信接口服务,用code换取用户手机号(每个code只能使用一次,code的有效期为5min) + * https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/phonenumber/phonenumber.getPhoneNumber.html + * + * @param code + * @return + */ + public static JSONObject getCode2Session(String code, String appId, String appSecret) { + //调用微信小程序登录接口 + String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + appId + + "&secret=" + appSecret + + "&js_code=" + code + + "&grant_type=authorization_code"; + RestTemplate restTemplate = new RestTemplate(); + + ResponseEntity responseEntity = restTemplate.getForEntity(url, String.class); + Map map = com.alibaba.fastjson.JSONObject.parseObject(responseEntity.getBody(), new TypeReference>() { + }); + return JSONUtil.parseObj(map); + } + + //解密 + private Map getDecryptedUserInfo(String encryptedData, String sessionKey, String iv) { + byte[] encryptedDataByte = Base64.decodeBase64(encryptedData); + byte[] sessionKeyByte = Base64.decodeBase64(sessionKey); + byte[] ivByte = Base64.decodeBase64(iv); + + try { + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + SecretKeySpec secretKeySpec = new SecretKeySpec(sessionKeyByte, "AES"); + IvParameterSpec ivParameterSpec = new IvParameterSpec(ivByte); + cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec); + byte[] decryptedByte = cipher.doFinal(encryptedDataByte); + String decryptedData = new String(decryptedByte, StandardCharsets.UTF_8); + + // 将解密后的数据转为Map + ObjectMapper mapper = new ObjectMapper(); + return mapper.readValue(decryptedData, Map.class); + } catch (Exception e) { + e.printStackTrace(); + } + + return new HashMap<>(); + } + +}