parent
d764e17d1e
commit
d15741ce8f
14 changed files with 820 additions and 0 deletions
@ -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<Map> 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<String, Object> map = new HashMap<>(); |
||||||
|
map.put("openid", openid); |
||||||
|
map.put("userInfo", null); |
||||||
|
//根据openid获取本地用户信息
|
||||||
|
WeChatUser user = weChatUserService.getOne(Wrappers.<WeChatUser>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<String,Object> 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<String, Object> 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()); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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.<WeChatUser>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.<WeChatUser>lambdaQuery().eq(WeChatUser::getOpenId, openId))); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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; |
||||||
|
|
||||||
|
} |
||||||
@ -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; |
||||||
|
|
||||||
|
} |
||||||
@ -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; |
||||||
|
|
||||||
|
} |
||||||
@ -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<WeChatUser> { |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,5 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
||||||
|
<mapper namespace="org.springblade.weixin.mapper.WeChatUserMapper"> |
||||||
|
|
||||||
|
</mapper> |
||||||
@ -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<WeChatUser> { |
||||||
|
|
||||||
|
} |
||||||
@ -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<WeChatUserMapper, WeChatUser> implements IWeChatUserService { |
||||||
|
|
||||||
|
} |
||||||
@ -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<BasicHeader> headers) throws Exception { |
||||||
|
return doPost(url, requestBody, ContentType.APPLICATION_JSON, headers); |
||||||
|
} |
||||||
|
|
||||||
|
public static String doPost(String url, String requestBody, ContentType contentType, List<BasicHeader> headers) |
||||||
|
throws Exception { |
||||||
|
return doPost(url, requestBody, contentType, headers, getHttpClient()); |
||||||
|
} |
||||||
|
|
||||||
|
public static String doPost(String url, String requestBody, ContentType contentType, List<BasicHeader> 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<BasicHeader> 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<String, String> 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<NameValuePair> 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<BasicHeader> 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<BasicHeader> 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); |
||||||
|
} |
||||||
|
} |
||||||
@ -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 <T> |
||||||
|
*/ |
||||||
|
public static <T> 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 <T> |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
public static <T> 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 <T> |
||||||
|
*/ |
||||||
|
public static <T> T stringToObj(String jsonString, Class<T> 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; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -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<String, Object> 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<String> responseEntity = restTemplate.getForEntity(url, String.class); |
||||||
|
Map<String, Object> map = com.alibaba.fastjson.JSONObject.parseObject(responseEntity.getBody(), new TypeReference<Map<String, Object>>() { |
||||||
|
}); |
||||||
|
return JSONUtil.parseObj(map); |
||||||
|
} |
||||||
|
|
||||||
|
//解密
|
||||||
|
private Map<String, Object> 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<>(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
Loading…
Reference in new issue