活字格集成企业微信JS-SDK指南

活字格集成企业微信JS-SDK指南

在环境集成和工作流程整合方面,企业微信JS-SDK能够实现扫码、定位、语音识别等多种功能,有效提升用户体验和业务维护效率。本文将介绍如何在活字格中集成企业微信JS-SDK,包括环境准备、后端配置、前端调用以及常见问题的解决。


一、环境准备

1. 注册企业微信账号并创建应用

  • 访问 企业微信官网 注册账号
  • 在管理后台创建自建应用,记录:
    • 企业ID (CorpID)
    • 应用Secret
    • AgentId

2. 配置可信域名、白名单IP和授权回调域

  • 在网页授权设置中,配置可信域名为活字格应用的域名
  • 在授权登录设置中,配置回调域名
  • 在企业可信IP设置中,配置白名单IP

3. 下载并上传企业微信安全提供程序包

  • 下载 qyWeixinSecurityProvider.zip
  • 在活字格“用户账户管理” - “第三方”中上传

4. 配置活字格应用

  • 设置认证模式为“第三方用户集成”
  • 配置 CorpID, Secret, AgentId
  • 发布应用,配置应用域名

二、Java Web API 后端实现

1. 创建项目

  • 使用活字格 Java API 生成器(forguncyJavaExtensionGenerateTool)
  • 在 IDEA 中打开,写自定义后端

在这里插入图片描述
在这里插入图片描述

2. 接口示例:获取 JS-SDK 签名

@Get
public void getJsSdkConfig() throws IOException {
    String url = this.getContext().getRequest().getParameter("url");
    if (url == null || url.isEmpty()) {
        url = this.getContext().getRequest().getHeader("Referer");
    }

    String nonceStr = WeChatUtils.genNonceStr();
    String timestamp = WeChatUtils.genTimestamp();

    try {
        String jsapiTicket = WeChatUtils.getJsApiTicket();
        String signature = WeChatUtils.generateSignature(jsapiTicket, nonceStr, timestamp, url);

        JSONObject json = new JSONObject();
        json.put("appId", WeChatUtils.getCorpId());
        json.put("timestamp", timestamp);
        json.put("nonceStr", nonceStr);
        json.put("signature", signature);

        sendJson(json.toString());

    } catch (Exception e) {
        JSONObject error = new JSONObject();
        error.put("error", e.getMessage());
        sendJson(error.toString());
    }
}

3. 运行签名逻辑(WeChatUtils.java)

  • 获取 access_token
  • 获取 jsapi_ticket
  • 进行 SHA1 签名
  • 返回 appId/timestamp/nonceStr/signature

4. 后端完整代码

以下是 Java Web API 的三个关键模块代码,分别为主接口类、微信工具类和HTTP工具类:

QyWeChatApi.java:后端入口类,定义对外接口
package org.example;

import com.grapecity.forguncy.serverapi.annotation.Get;
import com.grapecity.forguncy.serverapi.entity.ForguncyApi;

import java.io.IOException;
import java.io.PrintWriter;
import org.json.JSONObject;

public class QyWeChatApi extends ForguncyApi {
    @Get
    public void helloWorld() throws IOException {
        this.getContext().getResponse().setContentType("text/plain");
        String message = "Hello World QyWeChatApi";
        PrintWriter out = this.getContext().getResponse().getWriter();
        out.print(message);
        out.close();
    }

    @Get
    public void getJsSdkConfig() throws IOException {
        String url = this.getContext().getRequest().getParameter("url");
        if (url == null || url.isEmpty()) {
            url = this.getContext().getRequest().getHeader("Referer");
        }

        String nonceStr = WeChatUtils.genNonceStr();
        String timestamp = WeChatUtils.genTimestamp();

        try {
            String jsapiTicket = WeChatUtils.getJsApiTicket();
            String signature = WeChatUtils.generateSignature(jsapiTicket, nonceStr, timestamp, url);

            JSONObject json = new JSONObject();
            json.put("appId", WeChatUtils.getCorpId());
            json.put("timestamp", timestamp);
            json.put("nonceStr", nonceStr);
            json.put("signature", signature);

            sendJson(json.toString());

        } catch (Exception e) {
            JSONObject error = new JSONObject();
            error.put("error", e.getMessage());
            sendJson(error.toString());
        }
    }

    /**
     * 快捷 JSON 输出
     */
    private void sendJson(String json) throws IOException {
        this.getContext().getResponse().setContentType("application/json");
        this.getContext().getResponse().setCharacterEncoding("UTF-8");
        PrintWriter out = this.getContext().getResponse().getWriter();
        out.print(json);
        out.flush();
        out.close();
    }
}
WeChatUtils.java:JS-SDK 签名核心工具类
package org.example;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Formatter;
import java.util.UUID;
import java.util.concurrent.locks.ReentrantLock;
import org.json.JSONObject;

/**
 * ==================================================
 * This class WeChatUtils is responsible for 企业微信 JS-SDK 权限签名接口封装.
 *
 * @author Darker
 * @version 1.0
 * ==================================================
 */
public class WeChatUtils {

    // 企业微信配置项
    private static final String CORP_ID = "企业微信ID";
    private static final String CORP_SECRET = "自建应用密钥";
    private static final String AGENT_ID = "自建应用ID";

    private static final String ACCESS_TOKEN_URL = "https://qyapi.weixin.qq.com/cgi-bin/gettoken";
    private static final String JSAPI_TICKET_URL = "https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket";

    private static String accessToken;
    private static long tokenExpiryTime;

    private static String jsapiTicket;
    private static long ticketExpiryTime;

    private static final ReentrantLock lock = new ReentrantLock();

    /**
     * 获取 access_token(带缓存)
     */
    public static String getAccessToken() throws IOException {
        long currentTime = System.currentTimeMillis();
        if (accessToken == null || currentTime >= tokenExpiryTime) {
            lock.lock();
            try {
                if (accessToken == null || currentTime >= tokenExpiryTime) {
                    String url = ACCESS_TOKEN_URL + "?corpid=" + CORP_ID + "&corpsecret=" + CORP_SECRET;
                    String response = HttpUtils.httpGet(url);
                    JSONObject json = new JSONObject(response);
                    accessToken = json.getString("access_token");
                    int expiresIn = json.getInt("expires_in");
                    tokenExpiryTime = currentTime + (expiresIn - 200) * 1000L;
                }
            } finally {
                lock.unlock();
            }
        }
        return accessToken;
    }

    /**
     * 获取 jsapi_ticket(带缓存)
     */
    public static String getJsApiTicket() throws IOException {
        long currentTime = System.currentTimeMillis();
        if (jsapiTicket == null || currentTime >= ticketExpiryTime) {
            lock.lock();
            try {
                if (jsapiTicket == null || currentTime >= ticketExpiryTime) {
                    String accessToken = getAccessToken();
                    String url = JSAPI_TICKET_URL + "?access_token=" + accessToken;
                    String response = HttpUtils.httpGet(url);
                    JSONObject json = new JSONObject(response);
                    jsapiTicket = json.getString("ticket");
                    int expiresIn = json.getInt("expires_in");
                    ticketExpiryTime = currentTime + (expiresIn - 200) * 1000L;
                }
            } finally {
                lock.unlock();
            }
        }
        return jsapiTicket;
    }

    /**
     * 生成微信 JS-SDK 的签名
     */
    public static String generateSignature(String jsapiTicket, String nonceStr, String timestamp, String url)
            throws NoSuchAlgorithmException {
        String string1 = "jsapi_ticket=" + jsapiTicket +
                "&noncestr=" + nonceStr +
                "&timestamp=" + timestamp +
                "&url=" + url;
        MessageDigest crypt = MessageDigest.getInstance("SHA-1");
        crypt.reset();
        crypt.update(string1.getBytes(StandardCharsets.UTF_8));
        return byteToHex(crypt.digest());
    }

    /**
     * SHA-1 摘要转 Hex 字符串
     */
    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;
    }

    /** 获取 CorpId */
    public static String getCorpId() {
        return CORP_ID;
    }

    /** 获取 AgentId */
    public static String getAgentId() {
        return AGENT_ID;
    }

    /** 生成随机 nonceStr */
    public static String genNonceStr() {
        return UUID.randomUUID().toString().replace("-", "").substring(0, 16);
    }

    /** 生成时间戳(秒) */
    public static String genTimestamp() {
        return Long.toString(System.currentTimeMillis() / 1000);
    }
}

HttpUtils.java:基础 HTTP 请求工具类

package org.example;

import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;

/**
 * ==================================================
 * This class HttpUtils is responsible for [功能描述].
 *
 * @author Darker
 * @version 1.0
 * ==================================================
 */

public class HttpUtils {
    /**
     * 发起 HTTP GET 请求,并返回响应结果(纯文本或 JSON)
     * @param urlStr 完整的请求地址,例如 https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=xxx&corpsecret=xxx
     * @return 响应结果字符串
     * @throws IOException 网络请求错误时抛出异常
     */
    public static String httpGet(String urlStr) throws IOException {
        URI uri = URI.create(urlStr);
        URL url = uri.toURL();

        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("GET");
        conn.setConnectTimeout(5000);
        conn.setReadTimeout(5000);

        return readResponse(conn);
    }

    /**
     * 发起 HTTP POST 请求,提交字符串类型的请求体,并返回响应结果
     *
     * @param urlStr 请求地址
     * @param body 请求体(一般为 JSON 或 form-urlencoded)
     * @param contentType 请求类型,如 "application/json"、"application/x-www-form-urlencoded"
     */
    public static String httpPost(String urlStr, String body, String contentType) throws IOException {
        URI uri = URI.create(urlStr);
        URL url = uri.toURL();

        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("POST");
        conn.setConnectTimeout(5000);
        conn.setReadTimeout(5000);

        conn.setDoOutput(true); // 启用输出流发送数据
        conn.setRequestProperty("Content-Type", contentType);

        try (OutputStream os = conn.getOutputStream()) {
            byte[] input = body.getBytes(StandardCharsets.UTF_8);
            os.write(input, 0, input.length);
        }

        return readResponse(conn);
    }

    /**
     * 从指定的 HttpURLConnection 中读取响应内容(适用于 GET 和 POST)
     *
     * <p>该方法会以 UTF-8 编码读取响应体的所有内容,适用于企业微信接口返回的 JSON 数据。</p>
     *
     * @param conn 已建立连接的 HttpURLConnection 对象(必须已调用 getInputStream)
     * @return 响应体字符串(一般是 JSON 格式)
     * @throws IOException 读取流出错或连接中断时抛出
     */
    private static String readResponse(HttpURLConnection conn) throws IOException {
        BufferedReader reader = new BufferedReader(
                new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8));

        StringBuilder result = new StringBuilder();
        String line;

        while ((line = reader.readLine()) != null) {
            result.append(line);
        }

        reader.close();
        conn.disconnect();
        return result.toString();
    }
}

三、前端 JS 调用

1. 初始化 wx.config

var u = navigator.userAgent;
var isAndroid = u.indexOf('Android') > -1 || u.indexOf('Linux') > -1;
var isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/);
let innerurl = ''
if (isAndroid) {
    innerurl = location.href
}
if (isIOS) {
    innerurl = location.href.split('#')[0]
}
var url = Forguncy.ForguncyData.ForguncyRoot + 'customapi/wechatapi/getsignature';

try {
    Forguncy.Page.getCell("barURLValue").setValue(url);
    Forguncy.Page.getCell("barInnerurlValue").setValue(innerurl);
} catch (e) {
    alert(e);
}

var data =
{
    "url": innerurl,
};

$.get(url, data, function (result) {
    alert("result:" + result);
    var valueList = result;
    wx.config({
        debug: false,
        appId: valueList.corpid,
        timestamp: valueList.timestamp,
        nonceStr: valueList.nonceStr,
        signature: valueList.signature,
        jsApiList: ["startRecord", "stopRecord", "translateVoice", "scanQRCode", "playVoice", "pauseVoice", 'translateVoice',"stopVoice", "openLocation", "getLocation"] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2
    });

    wx.ready(function () {
        alert("签名信息获取成功!");
    });

    wx.error(function (res) {
        alert("签名信息获取失败:" + res);
    });
}).fail(function (xhr, status, error) {
    alert("请求失败!" 
    + "\n状态:" + status
    + "\n错误信息:" + error
    + "\n响应内容:" + xhr.responseText);
});

2. 扫码功能

wx.scanQRCode({
    // 默认为0,扫描结果由微信处理,1则直接返回扫描结果
    needResult: 1,
    desc: 'scanQRCode desc',
    success: function (res) {
        //扫码后获取结果参数赋值给Input
        var url = res.resultStr;
        //商品条形码,取","后面的
        if (url.indexOf(",") >= 0) {
            var tempArray = url.split(',');
            var tempNum = tempArray[1];
            try {
                Forguncy.Page.getCell("barCodeValue").setValue(tempNum);
            } catch (e) {
                alert(e);
            }
        } else {
            try {
                Forguncy.Page.getCell("barCodeValue").setValue(url);
            } catch (e) {
                alert(e);
            }
        }
    },

    error: function(res) {
        if (res.errMsg.indexOf('function_not_exist') > 0) {
            alert('版本过低请升级')
        }
    }
});

3. 定位和打开地图

wx.getLocation({
    type: 'gcj02', // 默认为wgs84的gps坐标,如果要返回直接给openLocation用的火星坐标,可传入'gcj02'
    success: function (res) {
        var latitude = res.latitude; 	// 纬度,浮点数,范围为90 ~ -90
        var longitude = res.longitude ; // 经度,浮点数,范围为180 ~ -180。
        var speed = res.speed; 			// 速度,以米/每秒计
        var accuracy = res.accuracy; 	// 位置精度
        wx.openLocation({
            latitude: latitude, 		// 纬度,浮点数,范围为90 ~ -90
            longitude: longitude, 		// 经度,浮点数,范围为180 ~ -180。
            name: '当前位置', 			// 位置名
            address: '', 				// 地址详情说明
            scale: 16, 					// 地图缩放级别,整形值,范围从1~28。默认为最大
        });
    }
});

四、常见问题和揭秘

问题:签名失败

  • 确保 URL 不包含 #
  • 确保 JS-SDK 域名签名同步

问题:access_token/jsapi_ticket 无效

  • 确保有效期内缓存成功
  • 确保企业ID和Secret正确

问题:界面一直刷新或卡死,可能出现在 JSON 解析位置

如这段代码中:

var valueList = JSON.parse(result);

需要特别注意——有些情况下,result 可能已经是一个 JSON 对象(尤其是在活字格平台的某些 JS 执行环境中可能会自动解析响应),此时再调用 JSON.parse() 会抛出异常,导致页面白屏、报错或不断刷新。

推荐写法(兼容对象/字符串):

var valueList = typeof result === "string" ? JSON.parse(result) : result;

通过类型判断,确保 valueList 始终是有效对象,提升兼容性与健壮性。


附录链接

  1. 活字格官方文档 - 集成企业微信
  2. 活字格Java扩展文档
  3. 活字格社区应用示例讨论
  4. 企业微信开发者文档
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值