Ver Fonte

公众号支付接口开发

zsf há 4 semanas atrás
pai
commit
d0347c371b

+ 28 - 0
src/main/java/com/rf/psychological/mp/model/JsapiSignature.java

@@ -0,0 +1,28 @@
+package com.rf.psychological.mp.model;
+
+import lombok.*;
+
+import java.io.Serializable;
+
+/**
+ * @Author:zzf
+ * @Date:2024/7/19:10:43
+ * @Description:
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class JsapiSignature implements Serializable {
+    private static final long serialVersionUID = -1116808193154384804L;
+
+    private String appId;
+
+    private String nonceStr;
+
+    private long timestamp;
+
+    private String url;
+
+    private String signature;
+}

+ 2 - 0
src/main/java/com/rf/psychological/mp/package-info.java

@@ -0,0 +1,2 @@
+package com.rf.psychological.mp;
+//微信公众号

+ 159 - 0
src/main/java/com/rf/psychological/mp/rest/MpController.java

@@ -0,0 +1,159 @@
+package com.rf.psychological.mp.rest;
+
+import com.alibaba.fastjson.JSONObject;
+import com.rf.psychological.base.rest.BaseController;
+import com.rf.psychological.mp.model.JsapiSignature;
+import com.rf.psychological.mp.utils.MpUtils;
+import com.rf.psychological.opLog.annotation.OperationLogAnnotation;
+import com.rf.psychological.security.SafetyProcess;
+import com.rf.psychological.user.service.UserService;
+import com.rf.psychological.utils.Result;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.*;
+
+/**
+ * @Author:zzf
+ * @Date:2024/7/17:19:05
+ * @Description:
+ */
+@CrossOrigin //跨域
+@RestController
+@Slf4j
+@RequestMapping("/mp/api")
+@Api(tags = "微信公众号")
+public class MpController extends BaseController {
+
+    @Value("${weixin.mptoken}")
+    private String mptoken;
+
+    @Autowired
+    private MpUtils mpUtils;
+
+    @Autowired
+    private RedisTemplate<String,String> redisTemplate;
+
+
+
+
+
+    /**
+     * 服务器验证接口
+     * @param signature 微信加密啊签名
+     * @param timestamp 时间戳
+     * @param nonce 随机数
+     * @param echostr 随机字符串
+     * @return
+     */
+    @GetMapping("/message")
+    public String check(String signature,String timestamp,String nonce,String echostr){
+
+        log.info("signature:{} == timestamp:{} == nonce:{} == echostr:{}",signature,timestamp,nonce,echostr);
+        if (signature.equals(mpUtils.getSignature(mptoken,timestamp,nonce))){
+            return echostr;
+        }
+        return "";
+    }
+
+
+
+
+
+    @GetMapping("/code2accesstoken/{code}")
+    @ApiOperation("code2accesstoken")
+    @SafetyProcess
+    public Result code2accesstoken(@PathVariable String code){
+        JSONObject jsonObject = mpUtils.code2accesstoken(code);
+        return success(jsonObject);
+    }
+
+
+    @GetMapping("/signature2")
+    //@OperationLogAnnotation("获取JS-SDK签名")
+    @ApiOperation(value = "获取JS-SDK签名")
+    public Result signature2(String url) {
+        String jsapiTicket = redisTemplate.opsForValue().get("jsapi_ticket");
+        Map<String, String> sign = sign(jsapiTicket, url);
+        JsapiSignature signature = new JsapiSignature();
+
+        signature.setUrl(sign.put("url", url));
+        signature.setNonceStr(sign.put("nonceStr", sign.get("nonceStr")));
+        signature.setTimestamp(Long.parseLong(Objects.requireNonNull(sign.put("timestamp", sign.get("timestamp")))));
+        signature.setSignature(sign.put("signature", sign.get("signature")));
+        return success(sign);
+    }
+    public static Map<String, String> sign(String jsapi_ticket, String url) {
+        Map<String, String> ret = new HashMap<String, String>();
+//        String nonce_str = RandomUtils.getRandomStr();
+        String nonce_str = create_nonce_str();
+//        double random = Math.random();
+//        String nonce_str = (random + "").split("\\.")[1];
+        System.out.println(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);
+        log.info(ret.toString());
+        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);
+    }
+
+
+
+
+}

+ 36 - 0
src/main/java/com/rf/psychological/mp/schedule/Task.java

@@ -0,0 +1,36 @@
+package com.rf.psychological.mp.schedule;
+
+import com.rf.psychological.mp.utils.MpUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+/**
+ * @Author:zzf
+ * @Date:2024/7/18:16:26
+ * @Description:
+ */
+@Slf4j
+@Component
+public class Task {
+
+    @Autowired
+    private MpUtils mpUtils;
+
+
+
+    /**
+     * 获取接口访问凭证
+     * 失效时间两小时,两小时最后的五分钟内新老token可以一起使用,刷新事件115分钟
+     * @return
+     */
+    @Scheduled(fixedRate = 1000*6900)
+    private  void getAccessToken(){
+        //1.accesstoken
+        mpUtils.getMpAccessToken();
+        //2.jsapiTicket
+        mpUtils.getMpJsapiTicket();
+    }
+
+}

+ 149 - 0
src/main/java/com/rf/psychological/mp/utils/MpUtils.java

@@ -0,0 +1,149 @@
+package com.rf.psychological.mp.utils;
+
+
+import com.alibaba.fastjson.JSONObject;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.RestTemplate;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @Author:zzf
+ * @Date:2024/7/17:19:24
+ * @Description: mp 工具类
+ */
+@Component
+@Slf4j
+public class MpUtils {
+
+
+    @Value("${weixin.mpAppid}")
+    private String mpAppid;
+
+    @Value("${weixin.mpSecret}")
+    private String mpSecret;
+
+    @Value("${weixin.domain}")
+    private String domain;
+
+    @Value("${weixin.accessToken}")
+    private String accessTokenUrl;
+
+    @Value("${weixin.mpCode2AccessToken}")
+    private String mpCode2AccessTokenUrl;
+
+    @Value("${weixin.mpJsapiTicket}")
+    private String mpJsapiTicketUrl;
+
+    @Autowired
+    private RestTemplate restTemplate;
+
+    @Autowired
+    private RedisTemplate<String,String> redisTemplate;
+
+
+
+
+
+    /**
+     *获取加密签名
+     * @param token
+     * @param timestamp
+     * @param nonce
+     * @return
+     */
+    public  String getSignature(String token, String timestamp, String nonce){
+        List<String> list = Arrays.asList(token, timestamp, nonce);
+        Collections.sort(list);
+        StringBuilder stringBuilder = new StringBuilder();
+        list.forEach(stringBuilder::append);
+        MessageDigest instance;
+        try {
+            instance = MessageDigest.getInstance("sha1");
+            byte[] digest = instance.digest(stringBuilder.toString().getBytes());
+            StringBuilder sum = new StringBuilder();
+            for (byte b : digest) {
+                sum.append(Integer.toHexString((b>>4)&15));
+                sum.append(Integer.toHexString(b&15));
+            }
+            return sum.toString();
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+
+    /**
+     * code 获取 accesstoken
+     * @param code
+     * @return
+     */
+    public JSONObject code2accesstoken(String code) {
+        String requestUrl = domain +String.format(mpCode2AccessTokenUrl, mpAppid, mpSecret,code);
+        ResponseEntity<String> response = restTemplate.getForEntity(requestUrl, String.class);
+        JSONObject jsonObject = JSONObject.parseObject(response.getBody());
+        if (!jsonObject.containsKey("errcode")){
+            redisTemplate.opsForValue().set("access_token", jsonObject.getString("access_token"));
+            return jsonObject;
+        }else {
+            log.error("code2accessToken 异常:");
+            log.error("errcode:"+jsonObject.getString("errcode"));
+            log.error("errmsg:"+jsonObject.getString("errmsg"));
+        }
+        return null;
+    }
+
+    /**
+     * 获取web access_token
+     * @return
+     */
+    public void getMpAccessToken(){
+        log.info("获取accessToken");
+        String requestUrl = domain+String.format(accessTokenUrl, mpAppid, mpSecret);
+        // 发起GET请求获取凭证
+        log.info(requestUrl);
+        ResponseEntity<String> response = restTemplate.getForEntity(requestUrl, String.class);
+        JSONObject jsonObject = JSONObject.parseObject(response.getBody());
+        if(!jsonObject.containsKey("errcode")){
+            redisTemplate.opsForValue().set("access_token",jsonObject.getString("access_token"));
+        }else {
+            log.error("获取accessToken异常:");
+            log.error("errcode:"+jsonObject.getString("errcode"));
+            log.error("errmsg:"+jsonObject.getString("errmsg"));
+        }
+    }
+
+    /**
+     * 获取jsapi_ticket
+     * @return
+     */
+    public void getMpJsapiTicket(){
+        log.info("获取jsapi_ticket");
+        String mpWebAccessToken = redisTemplate.opsForValue().get("access_token");
+        if(StringUtils.isNotEmpty(mpWebAccessToken)){
+            String requestUrl = domain +String.format(mpJsapiTicketUrl,mpWebAccessToken);
+            ResponseEntity<String> response = restTemplate.getForEntity(requestUrl, String.class);
+            JSONObject jsonObject = JSONObject.parseObject(response.getBody());
+            if(jsonObject.getIntValue("errcode") == 0){
+                redisTemplate.opsForValue().set("jsapi_ticket",jsonObject.getString("ticket"));
+            }else {
+                log.error("获取jsapi_ticket异常:");
+                log.error("errcode:"+jsonObject.getString("errcode"));
+                log.error("errmsg:"+jsonObject.getString("errmsg"));
+            }
+        }else{
+            log.error("access_token 为空,将不再获取jsapi_ticket");
+        }
+
+    }
+}

+ 22 - 0
src/main/java/com/rf/psychological/mp/utils/RandomUtils.java

@@ -0,0 +1,22 @@
+package com.rf.psychological.mp.utils;
+
+/**
+ * @Author:zzf
+ * @Date:2024/7/19:10:26
+ * @Description:
+ */
+public class RandomUtils {
+
+    private static final String RANDOM_STR = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+
+    private static final java.util.Random RANDOM = new java.util.Random();
+
+    public static String getRandomStr() {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < 16; i++) {
+            sb.append(RANDOM_STR.charAt(RANDOM.nextInt(RANDOM_STR.length())));
+        }
+        return sb.toString();
+    }
+
+}

+ 86 - 0
src/main/java/com/rf/psychological/mp/utils/Sign.java

@@ -0,0 +1,86 @@
+package com.rf.psychological.mp.utils;
+
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Formatter;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * @Author:zzf
+ * @Date:2024/7/18:15:30
+ * @Description:
+ */
+public class Sign {
+
+    public static void main(String[] args) {
+        String jsapi_ticket = "jsapi_ticket";
+
+        // 注意 URL 一定要动态获取,不能 hardcode
+        String url = "http://example.com";
+        Map<String, String> ret = sign(jsapi_ticket, url);
+        for (Map.Entry entry : ret.entrySet()) {
+            System.out.println(entry.getKey() + ", " + entry.getValue());
+        }
+    };
+
+    public static Map<String, String> sign(String jsapi_ticket, String url) {
+        Map<String, String> ret = new HashMap<String, String>();
+        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);
+    }
+}

+ 1 - 1
src/main/resources/config/application.yml

@@ -1,7 +1,7 @@
 spring:
   profiles:
     #默认启用test配置文件
-    active: public
+    active: test
   #  mvc:
   #    static-path-pattern: /findFile/**
   #  resources: