Ver Fonte

添加微信小程序代码

silent há 4 anos atrás
pai
commit
bfa69f530b

+ 4 - 0
pom.xml

@@ -218,6 +218,10 @@
             <artifactId>bcprov-jdk15to18</artifactId>
             <version>1.68</version>
         </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-test</artifactId>
+        </dependency>
 
     </dependencies>
 

+ 27 - 2
src/main/java/org/springblade/common/cache/CacheNames.java

@@ -51,11 +51,36 @@ public interface CacheNames {
 	/**
 	 * 验证码key
 	 */
-	String CAPTCHA_KEY = "blade:auth::blade:captcha:";
+	String CAPTCHA_KEY = "ldt:auth::blade:captcha:";
 
 	/**
 	 * 登录失败key
 	 */
-	String USER_FAIL_KEY = "blade:user::blade:fail:";
+	String USER_FAIL_KEY = "ldt:user::blade:fail:";
 
+
+	/**
+	 * 支付模块
+	 */
+	String PAYMENT = "payment";
+
+	/**
+	 * 流程模块
+	 */
+	String FLOW = "flow";
+
+	/**
+	 * 微信模块
+	 */
+	String WECHAT = "wechat";
+
+	/**
+	 * 平台参数
+	 */
+	String PLATFORM = "saas";
+
+	/**
+	 * 二维码
+	 */
+	String QRCODE = "qrcode";
 }

+ 41 - 0
src/main/java/org/springblade/common/cache/WechatCache.java

@@ -0,0 +1,41 @@
+package org.springblade.common.cache;
+
+import org.springblade.common.enums.ResCode;
+import org.springblade.core.log.exception.ServiceException;
+import org.springblade.core.redis.cache.BladeRedis;
+import org.springblade.core.tool.utils.SpringUtil;
+import org.springblade.core.tool.utils.StringUtil;
+
+import java.util.Objects;
+
+/**
+ * @author: lianghanqiang
+ * @description: 微信模块缓存
+ * @since: 9/6/21 -- 8:54 PM
+ */
+public class WechatCache {
+
+	private static BladeRedis bladeRedis;
+	private static final String AC_PREFIX = "ac";
+
+	static   {
+		bladeRedis = SpringUtil.getBean("bladeRedis");
+		if(Objects.isNull(bladeRedis)){
+			throw new ServiceException(ResCode.USER_PWS_ERROR);
+		}
+	}
+
+	/**
+	 * 	获取缓存的AccessToken信息
+	 * */
+	public static String getAccessToken(String appId){
+		return bladeRedis.get(StringUtil.format("{}:{}:{}",CacheNames.WECHAT,AC_PREFIX,appId));
+	}
+
+	/**
+	 * 	缓存AccessToken信息
+	 * */
+	public static void putAccessToken(String appId,String ac){
+		bladeRedis.setEx(StringUtil.format("{}:{}:{}",CacheNames.WECHAT,AC_PREFIX,appId), ac, 5000L);
+	}
+}

+ 65 - 0
src/main/java/org/springblade/common/enums/ResCode.java

@@ -0,0 +1,65 @@
+package org.springblade.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.springblade.core.tool.api.IResultCode;
+
+/**
+ * @author: lianghanqiang
+ * @description: 响应状态枚举
+ * @since: 7/29/21 -- 2:30 PM
+ */
+@Getter
+@AllArgsConstructor
+public enum ResCode implements IResultCode {
+
+	/*  支付模块  */
+	PAY_FAIL(501,"支付异常"),
+	PAY_TYPE_ERROR(501,"支付方式错误"),
+	PAY_SCENE_ERROR(501,"支付场景错误"),
+	TRADE_ERROR(501,"交易异常"),
+	TRADE_TIME_OUT(501,"订单超时"),
+	INIT_ORDER_FAIL(501,"初始化订单失败"),
+	CHANNEL_NOT_FOUND(501,"积分渠道查找失败!"),
+	USER_PAY_CALLBACK_ERROR(501,"用户支付回调异常"),
+	SEND_POINT_ERROR(501,"赠送积分异常"),
+	GOODS_BILLS_ERROR(501,"商品订单异常"),
+
+
+
+	/*	动态密码	*/
+	ID_NOT_NULL(502,"ID串不能为空"),
+	SECRET_VALIDATE_ERROR(502,"授权码校验失败"),
+
+
+	/*	加密,解密模块	*/
+	ENCODE_FAIL(503,"加密失败"),
+	DECODE_FAIL(503,"解密失败"),
+
+	/*  短信 */
+	SEND_ERROR(504,"短信发送失败"),
+	VALIDATE_FAIL(504,"验证码校验失败!"),
+
+	/*  通用   */
+	FAIL(505,"系统异常!"),
+	PARAMETER_ERROR(505,"参数异常!"),
+	REQUEST_TIMEOUT(505,"请求时间异常!"),
+
+	INVALID_SIGNATURE(413,"无效签名!"),
+
+
+	/*	系统模块 */
+	SYSTEM_ERROR_REDIS(506,"Redis 配置异常!"),
+
+
+	/*	用户	*/
+	USER_LOGIN_FAIL(507,"登录失败!"),
+	USER_PWS_ERROR(507,"密码或用户名错误!"),
+	SHOP_REGISTER_ERROR(507,"商场或商家账号注册异常!"),
+	USER_NOT_FOUNT(507,"用户不存在");
+
+
+	int code;
+	String message;
+
+}

+ 24 - 0
src/main/java/org/springblade/wx/config/ConfigCommon.java

@@ -0,0 +1,24 @@
+package org.springblade.wx.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.PropertySource;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+/**
+ * @author: lianghanqiang
+ * @description: 微信通用配置
+ * @since: 9/18/21 -- 10:43 AM
+ */
+
+@Component
+@Data
+@PropertySource(value = {"wxConfig.properties"})
+@ConfigurationProperties(prefix = "wx.common")
+public class ConfigCommon {
+
+	List<String> businessUrls;
+	String domain;
+}

+ 15 - 0
src/main/java/org/springblade/wx/config/ConfigForClient.java

@@ -0,0 +1,15 @@
+package org.springblade.wx.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.PropertySource;
+import org.springframework.stereotype.Component;
+
+@Component
+@Data
+@PropertySource(value = {"wxConfig.properties"})
+@ConfigurationProperties(prefix = "wx.client")
+public class ConfigForClient implements WeChatConfig {
+	String appId;
+	String appSecret;
+}

+ 20 - 0
src/main/java/org/springblade/wx/config/ConfigForMall.java

@@ -0,0 +1,20 @@
+package org.springblade.wx.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.PropertySource;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author: lianghanqiang
+ * @description:
+ * @since: 9/6/21 -- 8:44 PM
+ */
+@Component
+@Data
+@PropertySource(value = {"wxConfig.properties"})
+@ConfigurationProperties(prefix = "wx.mall")
+public class ConfigForMall implements WeChatConfig {
+	String appId;
+	String appSecret;
+}

+ 16 - 0
src/main/java/org/springblade/wx/config/ConfigForOfficial.java

@@ -0,0 +1,16 @@
+package org.springblade.wx.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.PropertySource;
+import org.springframework.stereotype.Component;
+
+@Component
+@Data
+@PropertySource(value = {"wxConfig.properties"})
+@ConfigurationProperties(prefix = "wx.official")
+public class ConfigForOfficial implements WeChatConfig {
+	String appId;
+	String appSecret;
+	String[] tradeAuthDirList;
+}

+ 24 - 0
src/main/java/org/springblade/wx/config/ConfigForShop.java

@@ -0,0 +1,24 @@
+package org.springblade.wx.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.PropertySource;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author: lianghanqiang
+ * @description:
+ * @since: 9/6/21 -- 8:43 PM
+ */
+@Component
+@Data
+@PropertySource(value = {"wxConfig.properties"})
+@ConfigurationProperties(prefix = "wx.shop")
+public class ConfigForShop implements WeChatConfig {
+	String appId;
+	String appSecret;
+	String signPage;
+	String authPage;
+	String appName;
+	String paymentPage;
+}

+ 12 - 0
src/main/java/org/springblade/wx/config/WeChatConfig.java

@@ -0,0 +1,12 @@
+package org.springblade.wx.config;
+
+/**
+ * @author: lianghanqiang
+ * @description:
+ * @since: 9/6/21 -- 8:32 PM
+ */
+public interface WeChatConfig {
+
+	String getAppId();
+	String getAppSecret();
+}

+ 61 - 0
src/main/java/org/springblade/wx/constant/MessageTemplate.java

@@ -0,0 +1,61 @@
+package org.springblade.wx.constant;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.HashMap;
+
+/**
+ * @author: lianghanqiang
+ * @description: 微信消息模板
+ * @since: 9/6/21 -- 8:16 PM
+ */
+@Getter
+@AllArgsConstructor
+public enum MessageTemplate {
+
+	/**
+	 * 商户入驻签约
+	 */
+	INVITATION_SIGN("yAtQ6AY8zBHDT1PxXHv7x7gS-qiN1DnSedN4MWLbHwk", "" +
+		"{\n" +
+		" \"phrase1\": {\n" +
+		"    \"value\": \"%s\"\n" +
+		" },\n" +
+		" \"thing2\": {\n" +
+		"    \"value\": \"%s\"\n" +
+		" }\n" +
+		"}"),
+	PAYMENT_NOTICE("2TcoWv042wo8Bf0SLRkjSLG00JAb9bE-usMy_aAW3Js", "" +
+		"{\n" +
+		"    \"amount3\":{\n" +
+		"        \"DATA\":\"%s\"\n" +
+		"    },\n" +
+		"    \"phrase1\":{\n" +
+		"        \"DATA\":\"%s\"\n" +
+		"    },\n" +
+		"    \"character_string8\":{\n" +
+		"        \"DATA\":\"%s\"\n" +
+		"    },\n" +
+		"    \"time4\":{\n" +
+		"        \"DATA\":\"%s\"\n" +
+		"    }\n" +
+		"}"),
+	;
+
+	String templateId;
+	String contentString;
+
+	public String buildRequestBody(String openId, JSONObject content, String page) {
+		HashMap<String, Object> params = new HashMap<>(4);
+		params.put("touser", openId);
+		params.put("miniprogram_state", "trial");
+		params.put("template_id", this.getTemplateId());
+		params.put("page", page);
+		params.put("data", content);
+		return JSON.toJSONString(params);
+	}
+
+}

+ 10 - 0
src/main/java/org/springblade/wx/constant/MiniProgramType.java

@@ -0,0 +1,10 @@
+package org.springblade.wx.constant;
+
+/**
+ * @author: lianghanqiang
+ * @description: 小程序类型
+ * @since: 9/6/21 -- 8:48 PM
+ */
+public enum  MiniProgramType {
+	CLIENT,SHOP,MALL
+}

+ 60 - 0
src/main/java/org/springblade/wx/constant/WeChatApi.java

@@ -0,0 +1,60 @@
+package org.springblade.wx.constant;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author: lianghanqiang
+ * @description: 微信服务API
+ * @since: 9/6/21 -- 8:05 PM
+ */
+@Getter
+@AllArgsConstructor
+public enum WeChatApi {
+
+	/**
+	 * 	小程序发送模板消息
+	 * */
+	sendTemplateMessage("https://api.weixin.qq.com/cgi-bin/message/subscribe/send","POST"),
+
+
+	/**
+	 * 获取用户AccessToken
+	 * */
+	fetchAccessToken("https://api.weixin.qq.com/cgi-bin/token","GET"),
+
+
+	/**
+	 * OCR-营业执照
+	 */
+	OCR_BIZLICENSE("https://api.weixin.qq.com/cv/ocr/bizlicense?img_url=%s&access_token=%s", "POST"),
+
+	/**
+	 * OCR-身份证
+	 */
+	OCR_ID_CARD("https://api.weixin.qq.com/cv/ocr/idcard?type=photo&img_url=%s&access_token=%s", "POST"),
+
+	/**
+	 * OCR-银行卡
+	 */
+	OCR_BANK("https://api.weixin.qq.com/cv/ocr/bankcard?type=MODE&img_url=%s&access_token=&s", "POST"),
+
+	/**
+	 * 获取小程序码api
+	 */
+	createWxaQrCode("https://api.weixin.qq.com/cgi-bin/wxaapp/createwxaqrcode?access_token=%s", "GET"),
+
+	/**
+	 * 	获取小程序码--无数量限制
+	 * */
+	getUnLimit("https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=%s","POST"),
+	;
+
+	String api;
+	String requestMethods;
+
+	public String buildUrl( String ... params){
+		return String.format(this.api,params);
+	}
+
+}

+ 146 - 0
src/main/java/org/springblade/wx/controller/WXController.java

@@ -0,0 +1,146 @@
+package org.springblade.wx.controller;
+
+import cn.hutool.core.lang.Assert;
+import cn.hutool.http.HttpUtil;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springblade.wx.config.ConfigForClient;
+import org.springblade.wx.utils.Sign;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+
+@RestController
+@Slf4j
+@RequestMapping("/wx")
+@Api(tags = "登录模块")
+public class WXController {
+	@Resource
+	private ConfigForClient configForClient;
+	@Resource
+	private StringRedisTemplate stringRedisTemplate;
+	private static final String ACCESS_TOKEN = "access_token:";
+	private static final String JsAPI_TICKET = "jsApi_ticket:";
+
+	/**
+	 * @param userCode 前端返回的用户code
+	 * 	1、通过code获取用户的accessToken,openId等信息
+	 * 	2、判断accessToken是否有效
+	 * 	3、通过accessToken获取用户信息
+	 * */
+	@GetMapping("/getUserInfo")
+	@ApiOperation("获取用户信息")
+	public R getUserInfo(String userCode){
+		//获取用户accessToken
+		JSONObject ac = getLoginAccessToken(userCode);
+		String accessToken =  Optional.ofNullable(ac.getString("access_token")).orElseThrow(() -> new RuntimeException(ac.getString("errmsg")));
+		String openId =  Optional.ofNullable(ac.getString("openid")).orElseThrow(() -> new RuntimeException(ac.getString("errmsg")));
+		String refreshToken =  Optional.ofNullable(ac.getString("refresh_token")).orElseThrow(() -> new RuntimeException(ac.getString("errmsg")));
+		//校验Token是否有效
+		if(!verifyAc(openId,accessToken)){
+			accessToken = refreshToken(refreshToken);
+		}
+		//返回用户信息
+		return R.data(userInfo(accessToken,openId));
+	}
+
+	@GetMapping("/getShareData")
+	@ApiOperation("获取分享所需参数")
+	public R getShareData(String url){
+		//获取app的accessToken
+		String accessToken = getAccessToken();
+		Assert.notNull(accessToken, "获取程序accessToken失败");
+		String jsApiTicket = getJsApiTicket(accessToken);
+		Assert.notNull(jsApiTicket, "获取jsApiTicket失败");
+		Map<String, String> ret = Sign.getSign(jsApiTicket, url);
+		ret.put("appId", configForClient.getAppId());
+		return R.data(ret);
+	}
+	/**
+	 *
+	 * @pram accessToken
+	 * @return
+	 */
+	private String getJsApiTicket(String accessToken){
+		String ticket = stringRedisTemplate.opsForValue().get(JsAPI_TICKET + accessToken);
+		if (StringUtil.isBlank(ticket)) {
+			String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token="+ accessToken +"&type=jsapi";//这个url链接和参数不能变
+			String s = HttpUtil.get(url);
+			JSONObject jsonObject = JSON.parseObject(s);
+			ticket = jsonObject.getString("ticket");
+			if (StringUtil.isNotBlank(ticket)) {
+				stringRedisTemplate.opsForValue().set(JsAPI_TICKET+accessToken,ticket,7000,TimeUnit.SECONDS);
+			}
+		}
+		return ticket;
+	}
+
+	/**
+	 * 获取accessToken
+	 * 跟下面的登录accessToken是不一样的
+	 * @return
+	 */
+	private String getAccessToken() {
+		String appId = configForClient.getAppId();
+		String appSecret = configForClient.getAppSecret();
+		String accessTokenKey = ACCESS_TOKEN + appId;
+		//从缓存中获取
+		String accessToken = stringRedisTemplate.opsForValue().get(accessTokenKey);
+		if (StringUtil.isBlank(accessToken)) {
+			String acUrl =  "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="+appId+"&secret="+ appSecret;
+			String s = HttpUtil.get(acUrl);
+			JSONObject jsonObject = JSON.parseObject(s);
+			accessToken = jsonObject.getString("access_token");
+			if (StringUtil.isNotBlank(accessToken)) {
+				stringRedisTemplate.opsForValue().set(accessTokenKey, accessToken, 7000, TimeUnit.SECONDS);
+			}
+		}
+		return accessToken;
+	}
+
+	/**
+	 * 获取登录的用户的accessToken
+	 * @param code
+	 * @return
+	 */
+	private JSONObject getLoginAccessToken(String code){
+		String acUrl =  "https://api.weixin.qq.com/sns/oauth2/access_token?appid="
+			+ configForClient.getAppId()+"&secret="
+			+ configForClient.getAppSecret()+"&code="
+			+code+"&grant_type=authorization_code";
+		String acString= HttpUtil.get(acUrl);
+		return JSONObject.parseObject(acString);
+	}
+	//获取用户信息
+	private JSONObject userInfo(String ac, String openId){
+		String url = "https://api.weixin.qq.com/sns/userinfo?access_token=" + ac + "&openid=" + openId;
+		 String res= HttpUtil.get(url);
+		return JSONObject.parseObject(res);
+	}
+	//刷新Token
+	private String refreshToken(String refreshToken){
+		String url = "https://api.weixin.qq.com/sns/oauth2/refresh_token?appid="+ configForClient.getAppId()+"&grant_type=refresh_token&refresh_token="+refreshToken;
+		String res = HttpUtil.get(url);
+		JSONObject resObj = JSONObject.parseObject(res);
+		return  Optional.ofNullable(resObj.getString("access_token")).orElseThrow(() -> new RuntimeException(resObj.getString("errmsg")));
+	}
+	//校验Token
+	private boolean verifyAc(String openId,String accessToken){
+		String url = "https://api.weixin.qq.com/sns/auth?access_token="+accessToken+"&openid="+accessToken;
+		String res = HttpUtil.get(url);
+		JSONObject resObj = JSONObject.parseObject(res);
+		String status = resObj.getString("errmsg");
+		return "ok".equals(status);
+	}
+}

+ 192 - 0
src/main/java/org/springblade/wx/controller/WxAppController.java

@@ -0,0 +1,192 @@
+package org.springblade.wx.controller;
+
+import cn.hutool.core.lang.Assert;
+import cn.hutool.crypto.Mode;
+import cn.hutool.crypto.Padding;
+import cn.hutool.crypto.symmetric.AES;
+import cn.hutool.http.Header;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
+import cn.hutool.http.HttpUtil;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.oss.model.BladeFile;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springblade.modules.resource.builder.oss.OssBuilder;
+import org.springblade.wx.config.ConfigForClient;
+import org.springblade.wx.constant.MiniProgramType;
+import org.springblade.wx.constant.WeChatApi;
+import org.springblade.wx.dto.DataDto;
+import org.springblade.wx.dto.JumpWxa;
+import org.springblade.wx.dto.SchemeDto;
+import org.springblade.wx.service.WeChatService;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.mock.web.MockMultipartFile;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @author cy-computer
+ */
+@RestController
+@Slf4j
+@RequestMapping("/wx-app")
+@Api(tags = "微信小程序登录模块")
+@AllArgsConstructor
+public class WxAppController {
+
+	/**
+	 * 对象存储构建类
+	 */
+	private final OssBuilder ossBuilder;
+
+	private ConfigForClient configForClient;
+	private StringRedisTemplate stringRedisTemplate;
+	private WeChatService weChatService;
+
+
+	private static final String ACCESS_TOKEN = "access_token:";
+
+
+	@ApiOperation("获取开发数据")
+	@PostMapping("/getOpenData")
+	public R getOpenData(@RequestBody DataDto dataDto) {
+		byte[] key = Base64.getDecoder().decode(dataDto.getSessionKey().replaceAll(" ", "+"));
+		byte[] iv = Base64.getDecoder().decode(dataDto.getIv().replaceAll(" ", "+"));
+		byte[] content = Base64.getDecoder().decode(dataDto.getEncryptedData().replaceAll(" ", "+"));
+		AES aes = new AES(Mode.CBC, Padding.PKCS5Padding, key, iv);
+		return R.data(new String(aes.decrypt(content)));
+	}
+
+	@ApiOperation("获取小程序码")
+	@GetMapping("/getCode")
+	public R getCode(String scene,MiniProgramType type,String page) throws IOException {
+		HttpResponse unlimitCode = weChatService.getUnlimitCode(scene, type, page);
+		byte[] bytes = unlimitCode.bodyBytes();
+		BladeFile bladeFile = this.uploadImage(bytes);
+		return R.data(bladeFile);
+	}
+
+	@ApiOperation("获取access_token")
+	@GetMapping("/getAccessToken")
+	public R getAccessTokenOfClient() {
+
+		String appId = configForClient.getAppId();
+		String appSecret = configForClient.getAppSecret();
+		String accessTokenKey = ACCESS_TOKEN + appId;
+		//从缓存中获取
+		String accessToken = stringRedisTemplate.opsForValue().get(accessTokenKey);
+		if (StringUtil.isBlank(accessToken)) {
+			StringBuilder sb = new StringBuilder("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=");
+			sb.append(appId).append("&secret=").append(appSecret);
+			String url = sb.toString();
+			String s = HttpUtil.get(url);
+			JSONObject jsonObject = JSON.parseObject(s);
+			accessToken = jsonObject.getString("access_token");
+			if (StringUtil.isNotBlank(accessToken)) {
+				stringRedisTemplate.opsForValue().set(accessTokenKey, accessToken, 7000, TimeUnit.SECONDS);
+			}
+		}
+		return R.data(accessToken);
+	}
+
+	@ApiOperation("获取GenerateScheme")
+	@GetMapping("/getGenerateScheme")
+	public R getGenerateScheme(String path, String query) {
+		SchemeDto schemeDto = new SchemeDto();
+		schemeDto.setJump_wxa(new JumpWxa(path,query));
+		String accessToken = (String)this.getAccessTokenOfClient().getData();
+		String url = "https://api.weixin.qq.com/wxa/generatescheme?access_token=" + accessToken;
+
+		String s = HttpRequest.post(url)
+			.header(Header.CONTENT_TYPE, "application/json")
+			.body(JSON.toJSONString(schemeDto))
+			.execute().body();
+		JSONObject jsonObject = JSON.parseObject(s);
+		return R.data(jsonObject);
+	}
+
+	@ApiOperation("获取openId")
+	@GetMapping("/getOpenId")
+	public R getOpenId(String code,String type) {
+		//默认获取C端的openId
+		if (StringUtil.isBlank(type)) {
+			type = "CLIENT";
+		}
+		return R.data(weChatService.getOpenId(code, MiniProgramType.valueOf(type)));
+	}
+
+
+	@ApiOperation("获取小程序码")
+	@GetMapping("/createWxaQrCode")
+	public R createWxaQrCode(String path,@RequestParam String type) throws IOException {
+		//根据类型获取accessToken
+		String accessToken = weChatService.fetchAccessToken(MiniProgramType.valueOf(type));
+		Assert.notNull(accessToken, "获取accessToken失败");
+
+		String url = WeChatApi.createWxaQrCode.buildUrl(accessToken);
+		RestTemplate rest = new RestTemplate();
+
+
+		Map<String,Object> param = new HashMap<>(4);
+		param.put("path", path);
+		param.put("width", 430);
+
+		MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
+		HttpEntity requestEntity = new HttpEntity(param, headers);
+		ResponseEntity<byte[]> entity = rest.exchange(url, HttpMethod.POST, requestEntity, byte[].class, new Object[0]);
+		log.info("调用小程序生成微信永久二维码URL接口返回结果:" + entity.getBody());
+
+		byte[] result = entity.getBody();
+		BladeFile bladeFile = this.uploadImage(result);
+		return R.data(bladeFile);
+	}
+
+	private BladeFile uploadImage(byte[] bytes) throws IOException {
+		BladeFile bladeFile = new BladeFile();
+
+		InputStream inputStream = null;
+		try {
+			inputStream = new ByteArrayInputStream(bytes);
+			MultipartFile multipartFile =new MockMultipartFile("file", "shopQrCode.jpg", "text/plain", inputStream);
+			//上传到对象存储
+			bladeFile = ossBuilder.template().putFile(multipartFile.getOriginalFilename(), multipartFile.getInputStream());
+		} catch (IOException e) {
+			e.printStackTrace();
+		}finally {
+			if (inputStream != null) {
+				inputStream.close();
+			}
+		}
+		return bladeFile;
+	}
+
+	private String replaceEnter(String str){
+		String reg ="[\n-\r]";
+		Pattern p = Pattern.compile(reg);
+		Matcher m = p.matcher(str);
+		return m.replaceAll("");
+	}
+
+}

+ 13 - 0
src/main/java/org/springblade/wx/dto/DataDto.java

@@ -0,0 +1,13 @@
+package org.springblade.wx.dto;
+
+import lombok.Data;
+
+/**
+ * @author cy-computer
+ */
+@Data
+public class DataDto {
+	private String sessionKey;
+	private String encryptedData;
+	private String iv;
+}

+ 15 - 0
src/main/java/org/springblade/wx/dto/JumpWxa.java

@@ -0,0 +1,15 @@
+package org.springblade.wx.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+/**
+ * Created By lidexi in 2021/8/13
+ **/
+@Data
+@AllArgsConstructor
+public class JumpWxa {
+
+	private String path;
+	private String query;
+}

+ 14 - 0
src/main/java/org/springblade/wx/dto/SchemeDto.java

@@ -0,0 +1,14 @@
+package org.springblade.wx.dto;
+
+import lombok.Data;
+
+/**
+ * Created By lidexi in 2021/8/13
+ **/
+@Data
+public class SchemeDto {
+
+	private JumpWxa jump_wxa;
+}
+
+

+ 167 - 0
src/main/java/org/springblade/wx/service/WeChatService.java

@@ -0,0 +1,167 @@
+package org.springblade.wx.service;
+
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
+import cn.hutool.http.HttpUtil;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.http.protocol.HTTP;
+import org.springblade.common.cache.WechatCache;
+import org.springblade.core.log.exception.ServiceException;
+import org.springblade.core.redis.cache.BladeRedis;
+import org.springblade.core.tool.utils.SpringUtil;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springblade.wx.config.ConfigForClient;
+import org.springblade.wx.config.ConfigForMall;
+import org.springblade.wx.config.ConfigForShop;
+import org.springblade.wx.config.WeChatConfig;
+import org.springblade.wx.constant.MessageTemplate;
+import org.springblade.wx.constant.MiniProgramType;
+import org.springblade.wx.constant.WeChatApi;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.Assert;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * @author: lianghanqiang
+ * @description: 微信服务
+ * @since: 9/6/21 -- 8:09 PM
+ */
+@Service
+@Slf4j
+public class WeChatService {
+
+	@Autowired
+	BladeRedis bladeRedis;
+
+	/**
+	 * 小程序发送模板消息
+	 *
+	 * @return*/
+	public HttpResponse sendTemplateMessage(String openId, MessageTemplate messageTemplate,JSONObject content, String page, MiniProgramType miniProgramType){
+
+		Assert.notNull(messageTemplate,()->{ throw new ServiceException("找不到响应的消息模板"); });
+		String token = fetchAccessToken(miniProgramType);
+		String query = "?access_token="+token;
+		HttpResponse execute = HttpRequest.post(WeChatApi.sendTemplateMessage.getApi() + query)
+			.body(messageTemplate.buildRequestBody(openId, content, page))
+			.execute();
+		log.info("微信发送消息模板响应:{}",execute.body());
+		return  execute;
+	}
+
+	/**
+	 * 	获取小程序的AccessToken
+	 * */
+	public String fetchAccessToken(MiniProgramType type){
+
+		WeChatConfig config = getConfig(type);
+		String accessToken = WechatCache.getAccessToken(config.getAppId());
+
+		//缓存没有请求微信
+		if(Objects.isNull(accessToken)){
+			HttpResponse response = HttpRequest.get(WeChatApi.fetchAccessToken.getApi())
+				.form("grant_type", "client_credential")
+				.form("appid", config.getAppId())
+				.form("secret", config.getAppSecret())
+				.execute();
+			JSONObject jsonObject = JSON.parseObject(response.body());
+			accessToken = jsonObject.getString("access_token");
+
+			if (StringUtil.isNotBlank(accessToken)) {
+				WechatCache.putAccessToken(config.getAppId(),accessToken);
+			}
+		}
+
+		return accessToken;
+	}
+
+	/**
+	 * 获取openId
+	 * @return
+	 */
+	public JSONObject getOpenId(String code,MiniProgramType type){
+		WeChatConfig config = getConfig(type);
+		String appId = config.getAppId();
+		String appSecret = config.getAppSecret();
+
+		StringBuilder sb = new StringBuilder("https://api.weixin.qq.com/sns/jscode2session?appid=");
+		sb.append(appId)
+			.append("&secret=")
+			.append(appSecret)
+			.append("&js_code=")
+			.append(code)
+			.append("&grant_type=authorization_code");
+		String url = sb.toString();
+		String s = HttpUtil.get(url);
+		JSONObject jsonObject = JSON.parseObject(s);
+		return jsonObject;
+	}
+
+	/**
+	 * OCR-营业执照
+	 * @return
+	 */
+	public HttpResponse  ocrBizilicense(String imgUrl,MiniProgramType type){
+		String token = fetchAccessToken(type);
+		HttpResponse response = HttpRequest.post(WeChatApi.OCR_BIZLICENSE.buildUrl(imgUrl, token)).execute();
+		return response;
+	}
+
+	/**
+	 * OCR-身份证
+	 * @return
+	 */
+	public HttpResponse  ocrIdcard(String imgUrl,MiniProgramType type){
+		String token = fetchAccessToken(type);
+		HttpResponse response = HttpRequest.post(WeChatApi.OCR_ID_CARD.buildUrl(imgUrl, token)).execute();
+		return response;
+	}
+
+	/**
+	 * OCR-银行卡
+	 * @return
+	 */
+	public HttpResponse  ocrBank(String imgUrl,MiniProgramType type){
+		String token = fetchAccessToken(type);
+		HttpResponse response = HttpRequest.post(WeChatApi.OCR_BANK.buildUrl(imgUrl, token)).execute();
+		return response;
+	}
+
+	/**
+	 * 生成小程序码--无数量限制
+	 * @return
+	 */
+	public HttpResponse getUnlimitCode(String queryData,MiniProgramType type,String page){
+		String token = fetchAccessToken(type);
+		Map<String,String> params = new HashMap(){{
+			put("page",page);
+			put("scene",queryData);
+		}};
+		HttpResponse response = HttpRequest.post(WeChatApi.getUnLimit.buildUrl(token)).header(HTTP.CONTENT_TYPE, "application/json").body(JSON.toJSONString(params)).execute();
+
+		return response;
+	}
+
+
+	/**
+	 * 获取对应小程序的配置文件
+	 * */
+	private WeChatConfig getConfig(MiniProgramType type){
+		switch (type){
+			case MALL:
+				return SpringUtil.getBean(ConfigForMall.class);
+			case SHOP:
+				return SpringUtil.getBean(ConfigForShop.class);
+			case CLIENT:
+				return SpringUtil.getBean(ConfigForClient.class);
+			default:
+				return null;
+		}
+	}
+}

+ 70 - 0
src/main/java/org/springblade/wx/utils/Sign.java

@@ -0,0 +1,70 @@
+package org.springblade.wx.utils;
+
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Formatter;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.UUID;
+
+public class Sign {
+
+    public static Map<String, String> getSign(String jsapi_ticket, String url) {
+        Map<String, String> ret = new TreeMap<>();
+        String nonce_str = create_nonce_str();
+        String timestamp = create_timestamp();
+        String string1;
+        String signature = "";
+
+        string1 = "jsapi_ticket=" + jsapi_ticket +
+                  "&noncestr=" + nonce_str +
+                  "&timestamp=" + timestamp +
+                  "&url=" + url;
+
+        System.out.println(string1);
+
+        try
+        {
+            MessageDigest crypt = MessageDigest.getInstance("SHA-1");
+            crypt.reset();
+            crypt.update(string1.getBytes("UTF-8"));
+            signature = byteToHex(crypt.digest());
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            e.printStackTrace();
+        }
+        catch (UnsupportedEncodingException e)
+        {
+            e.printStackTrace();
+        }
+
+        ret.put("url", url);
+        ret.put("jsapi_ticket", jsapi_ticket);
+        ret.put("nonceStr", nonce_str);
+        ret.put("timestamp", timestamp);
+        ret.put("signature", signature);
+
+        return ret;
+    }
+
+    private static String byteToHex(final byte[] hash) {
+        Formatter formatter = new Formatter();
+        for (byte b : hash)
+        {
+            formatter.format("%02x", b);
+        }
+        String result = formatter.toString();
+        formatter.close();
+        return result;
+    }
+
+    private static String create_nonce_str() {
+        return UUID.randomUUID().toString();
+    }
+
+    private static String create_timestamp() {
+        return Long.toString(System.currentTimeMillis() / 1000);
+    }
+}

+ 31 - 0
src/main/java/org/springblade/wx/utils/UrlTransform.java

@@ -0,0 +1,31 @@
+package org.springblade.wx.utils;
+
+import org.springblade.core.tool.utils.SpringUtil;
+import org.springblade.wx.config.ConfigCommon;
+
+/**
+ * @author: lianghanqiang
+ * @description: 用于处理小程序业务域名,在配置文件配置所需拦截的域名信息,配置好服务器反向代理即可
+ * @since: 9/18/21 -- 10:40 AM
+ */
+public class UrlTransform {
+	private static ConfigCommon configCommon;
+
+	 static{
+	 	configCommon = SpringUtil.getBean(ConfigCommon.class);
+	 }
+
+	 /**
+	  * 将所需要拦截的业务域名转换成服务器域名
+	  * @param url 需要校验的请求地址
+	  * */
+	 public static String cheakUrl(String url){
+
+		 for (String item: configCommon.getBusinessUrls()) {
+			 if(url.contains(item)){
+				 return url.replace(item, configCommon.getDomain());
+			 }
+		 }
+		return url;
+	 }
+}

+ 46 - 0
src/main/resources/wxConfig.properties

@@ -0,0 +1,46 @@
+#爱效科技
+#wx.appId = wx7e7a38f071b360e9
+#wx.appSecret = 4e862f91b48ac85a91380fa0991c9fda
+
+#guoxin
+#wx.appId = wx6dacff40877a1b1a
+#wx.appSecret = eac73798c4c509e43dc7cb475de82f4d
+
+#南粤药业
+#wx.appId = wx322e246549d7620d
+#wx.appSecret = 118059d75bc8c1a08162fc8a8cf5ac04
+
+# 音乐之声
+#wx.appId = wxf3f84c916a741cac
+#wx.appSecret = 8377629994436f17210500a11bbce6cc
+
+#联兑通客户端
+wx.client.appId = wx9ad53e8c83ae1a51
+wx.client.appSecret = 9b9b5eafad345f5664e5e373b8cae10a
+
+
+wx.shop.appId = wx2db9c41e85d5a4fc
+wx.shop.appSecret = 89a59cc73a3d77ac1e636ced0531cd08
+wx.shop.signPage = pages/webView/webView?url=%s
+wx.shop.authPage = pages/webView/webView?url=%s
+wx.shop.appName = 联兑通商家
+wx.shop.paymentPage = /pages/my-bills/my-bills
+
+
+#联兑通大B端
+wx.mall.appId = wxdb5d1dcc471a46d4
+wx.mall.appSecret = aac0f305b9358fc03e19c3b3bb4e24e0
+
+
+
+wx.common.domain = https://ldt.guosen-fumao.cn/api/
+wx.common.businessUrls[0] = https://elecsign.yeepay.com/
+wx.common.businessUrls[1] = http://img.yeepay.com/
+
+#授权目录
+wx.official.tradeAuthDirList[0] = https://ldt.guosen-fumao.cn/
+wx.official.tradeAuthDirList[1] = https://cash.yeepay.com/newwap/
+wx.official.tradeAuthDirList[2] = https://shouyin.yeepay.com/nc-cashier-wap/
+#公众号
+wx.official.appId = wxd84c9cf56b4d69ef
+wx.official.appSecret = 544c77d678a34c5a50d32e5710eff92e