首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 實作微信分享到朋友圈功能完整教學

     編輯:Java 相關知識

前言

微信 JS-SDK 分享功能是移動端網頁開發的常見需求。本文提供完整的「後端簽名 + 前端配置」落地方案,涵蓽 Ticket 緩存、簽名算法、前端 wx.config 注入、常見坑點排查,助你避開踩坑陷阱。

整體架構

  1. 前端:頁面載入時 AJAX 取得簽名參數,調用 wx.config 初始化
  2. 後端:驗證 URL、管理 jsapi_ticket(含過期刷新)、按微信規範產生簽名
  3. 核心流程:access_token → jsapi_ticket → 簽名 → 返回前端

後端核心實作

1. 控制器入口

@Controller
@RequestMapping("/WeixinshareController/Api/Inteface")
public class WeixinshareController {

    @Autowired private TicketRepositorySolr ticketRepositorySolr;

    @RequestMapping("/getSignature")
    public String getSignature(HttpServletRequest request, HttpServletResponse response) 
            throws IOException, ParseException {
        String url = request.getParameter("url");  // 必須為完整頁面 URL(含查詢參數,去除 # 後片段)
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        // 1. 從資料庫/緩存獲取 Ticket
        Ticket oldTicket = ticketRepositorySolr.getTicketById("固定_TICKET_ID");
        
        if (oldTicket == null || isExpired(oldTicket, format)) {
            // 首次或過期:重新向微信請求
            return executeTicket(response, url, format);
        } else {
            // 有效:直接使用現有票據產生簽名
            return buildSignatureResponse(response, oldTicket, url);
        }
    }

    private boolean isExpired(Ticket ticket, SimpleDateFormat format) throws ParseException {
        long diff = format.parse(format.format(new Date())).getTime() 
                  - format.parse(ticket.getAcquiretime()).getTime();
        return diff > 7_100_000;  // 7200 秒 = 7_200_000 ms,留 100 秒緩衝
    }
}

2. Ticket 獲取與簽名邏輯

private String executeTicket(HttpServletResponse response, String url, SimpleDateFormat format) 
        throws IOException {
    // 1. 產生隨機字串與時間戳
    String noncestr = new GetRandomStr().getRandomString(15);
    String timestamp = String.valueOf(System.currentTimeMillis());

    // 2. 獲取 access_token
    String tokenUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=YOUR_APPID&secret=YOUR_SECRET";
    String tokenJson = WeixinUtil.httpRequest(tokenUrl, "GET", null);
    String accessToken = new Gson().fromJson(tokenJson, ShareAccessToken.class).getAccess_token();

    // 3. 獲取 jsapi_ticket
    String ticketUrl = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=" + accessToken + "&type=jsapi";
    String ticketJson = WeixinUtil.httpRequest(ticketUrl, "GET", null);
    Ticket ticket = new Gson().fromJson(ticketJson, Ticket.class);
    String jsapiTicket = ticket.getTicket();

    // 4. 持久化 Ticket(含 noncestr、timestamp、acquiretime)
    ticket.setTid("固定_TICKET_ID");
    ticket.setNoncestr(noncestr);
    ticket.setTimestamp(timestamp);
    ticket.setAcquiretime(format.format(new Date()));
    ticketRepositorySolr.addTicketToSolr(ticket);

    // 5. 產生簽名並回應
    return buildSignatureResponse(response, ticket, url);
}

private String buildSignatureResponse(HttpServletResponse response, Ticket ticket, String url) 
        throws IOException {
    String signature = signature(ticket.getTicket(), ticket.getTimestamp(), ticket.getNoncestr(), url);
    
    SignatureBean bean = new SignatureBean();
    bean.setNoncestr(ticket.getNoncestr());
    bean.setTimestamp(ticket.getTimestamp());
    bean.setSignature(signature);
    bean.setUrl(url);

    response.setContentType("application/json;charset=UTF-8");
    response.getWriter().print(new Gson().toJson(bean));
    return null;
}

3. 簽名算法(嚴格遵循微信規範)

private String signature(String jsapiTicket, String timestamp, String noncestr, String url) {
    // 1. 組裝參數
    String[] params = {
        "jsapi_ticket=" + jsapiTicket,
        "timestamp=" + timestamp,
        "noncestr=" + noncestr,
        "url=" + url
    };
    // 2. 字典序排序
    Arrays.sort(params);
    // 3. 拼接字串
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < params.length; i++) {
        sb.append(params[i]);
        if (i != params.length - 1) sb.append("&");
    }
    // 4. SHA1 加密並轉十六進位
    try {
        MessageDigest md = MessageDigest.getInstance("SHA-1");
        byte[] digest = md.digest(sb.toString().getBytes("UTF-8"));
        return bytesToHex(digest);
    } catch (NoSuchAlgorithmException e) {
        throw new RuntimeException("SHA-1 算法不可用", e);
    }
}

private String bytesToHex(byte[] bytes) {
    StringBuilder sb = new StringBuilder();
    for (byte b : bytes) {
        sb.append(String.format("%02x", b));
    }
    return sb.toString();
}

4. HTTPS 請求工具類

public class WeixinUtil {
    public static String httpRequest(String url, String method, String output) {
        try {
            TrustManager[] tm = { new MyX509TrustManager() };  // 信任所有憑證(生產環境請妥善驗證)
            SSLContext ssl = SSLContext.getInstance("SSL", "SunJSSE");
            ssl.init(null, tm, new SecureRandom());
            
            HttpsURLConnection conn = (HttpsURLConnection) new URL(url).openConnection();
            conn.setSSLSocketFactory(ssl.getSocketFactory());
            conn.setRequestMethod(method);
            conn.setDoOutput(true);
            conn.setDoInput(true);
            
            if ("POST".equalsIgnoreCase(method) && output != null) {
                try (OutputStream os = conn.getOutputStream()) {
                    os.write(output.getBytes("UTF-8"));
                }
            }
            
            try (BufferedReader br = new BufferedReader(
                    new InputStreamReader(conn.getInputStream(), "UTF-8"))) {
                StringBuilder sb = new StringBuilder();
                String line;
                while ((line = br.readLine()) != null) sb.append(line);
                return sb.toString();
            }
        } catch (Exception e) {
            throw new RuntimeException("微信 API 請求失敗", e);
        }
    }
}

前端整合

<script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
<script>
var pageUrl = location.href.split('#')[0];  // 關鍵:去除 # 片段

$.get("WeixinshareController/Api/Inteface/getSignature", {url: pageUrl}, function(data) {
    var config = JSON.parse(data);
    wx.config({
        debug: false,
        appId: 'YOUR_APPID',
        timestamp: config.timestamp,
        nonceStr: config.noncestr,
        signature: config.signature,
        jsApiList: [
            'onMenuShareTimeline',    // 分享到朋友圈
            'onMenuShareAppMessage',  // 分享給朋友
            'onMenuShareQQ',
            'onMenuShareQZone'
        ]
    });
});

wx.ready(function() {
    var shareData = {
        title: '明日醫療資訊',
        desc: '精選醫療健康資訊',
        link: pageUrl,
        imgUrl: 'https://example.com/share-logo.png'
    };
    
    wx.onMenuShareTimeline(shareData);
    wx.onMenuShareAppMessage(shareData);
    wx.onMenuShareQQ(shareData);
    wx.onMenuShareQZone(shareData);
});

wx.error(function(res) {
    console.error('微信配置失敗:', res);  // 開發期務必開啟 debug: true 排查
});
</script>

常見錯誤排查清單

現象可能原因解決方案
config:invalid signature簽名參數不一致1. 確認 noncestr/timestamp 與 wx.config 完全一致
2. URL 必須為完整頁面 URL(含查詢參數,去除 #)
3. 簽名用的 jsapi_ticket 對應的 appid 必須與 wx.config 一致
config:invalid url domain域名未綁定微信公眾平台 → 開發配置 → JS介面安全域名 綁定正確域名
config:ok 但分享不生效debug 為 true 干擾生產環境設定 debug: false
簽名驗證工具通過但實際失敗前後端 noncestr/timestamp 不同步後端簽名時使用的 noncestr/timestamp 必須原樣返回給前端,前端直接透傳給 wx.config

關鍵最佳實踐

  1. Ticket 集中緩存:全服務共用單一 Ticket,避免並發重複請求導致頻率限制
  2. 簽名參數透傳:後端產生的 noncestr、timestamp 必須原封不動返回前端,不可前端自行生成
  3. URL 處理:前端傳入 location.href.split('#')[0],後端簽名時直接使用,不可重新組裝
  4. HTTPS 環境:微信 JS-SDK 僅支援 HTTPS 頁面(localhost 開發可用內網穿透工具)
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved