文章目录
一、准备工作
- 一个已备案的网站
- 一个已认证的公众号(注意,个人权限是不可以的,需要企业权限)。
确保你有一个企业级的微信公众号,并完成企业认证。个人公众号可能无法获取全部接口权限。
- 获取AppID和AppSecret
在微信公众平台登录后,进入“开发”部分,选择“基本配置”,在这里你可以找到你的AppID和AppSecret。
二、微信公众号后台设置
-
设置JS接口安全域名:
进入公众号设置的“功能设置”中,填写“JS接口安全域名”。需确保你的网页服务部署在此域名下,且该域名已通过ICP备案。 -
设置IP白名单:
在“安全中心”或“开发者中心”设置服务器IP白名单,以便微信服务器能够与之通信。
三、获取签名(含代码)
3.1 获取access_token
- 接口说明:
使用你的AppID和AppSecret,通过GET请求到https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET来获取access_token。
参考以下文档获取access_token(有效期7200秒,开发者必须在自己的服务全局缓存access_token):
官方文档:https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html
- golang代码示例
/*
获取小程序全局唯一后台接口调用凭据:
https://developers.weixin.qq.com/minigame/dev/api-backend/open-api/access-token/auth.getAccessToken.html
*/
package weixinclient
import (
"encoding/json"
"errors"
"fmt"
"github.com/gin-gonic/gin"
)
type AccessTokenVo struct {
AccessToken string `json:"access_token"`
ExpiresIn int64 `json:"expires_in"`
Errcode int64 `json:"errcode"`
Errmsg string `json:"errmsg"`
}
func GetAccessToken(c *gin.Context) (*AccessTokenVo, error) {
ctx := commonx.GetTrace(c)
ps := fmt.Sprintf("grant_type=client_credential&appid=%s&secret=%s", config.BookAppid, config.BookSecret)
ep := getEndPoint("GetAccessToken")
resp, err := doGet(c, ep, ps, nil)
dlog.Infof("%v||GetSessionBycode resp=%v,err=%v", ctx, resp, err)
if err != nil || len(resp) == 0 {
return nil, errors.New("call API(GetAccessToken) fail")
}
var vo AccessTokenVo
err = json.Unmarshal(resp, &vo)
if err != nil || vo.Errcode > 0 {
err = fmt.Errorf("err=%v", vo.Errmsg)
return nil, err
}
return &vo, nil
}
返回示例:
{
"access_token": "80_PR606SNAcwOIyhsRuuOVC11eHDiy1ZqKMiWn6JoxYZH3ANGt13s5DWgiWtIbk0JAxn5LBKyZBMK5-cP5q_NBvTVdFtIf9utExtktae_7c1t4Wm9aBkEd5fuYpw4MMQfAHARRV",
"expires_in": 7200
}
3.2 获取jsapi_ticket
- 接口说明:
使用上一步获取的access_token,通过GET请求到https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi&access_token=ACCESS_TOKEN来获取jsapi_ticket。
参考以下文档获取access_token(有效期7200秒,开发者必须在自己的服务全局缓存access_token)
官方文档:https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html
- 请求方式:
请求方式:GET
请求参数:上一步获取到的 access_token
请求地址:https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi
注意:有效期7200秒, 这里建议将 access_token 和 jsapi_ticket 都在服务器端进行获取并缓存,前端通过接口调取结果
- jsapi_ticket 说明:
jsapi_ticket是公众号用于调用微信 JS 接口的临时票据。正常情况下,jsapi_ticket的有效期为7200秒,通过access_token来获取。由于获取jsapi_ticket的 api 调用次数非常有限,频繁刷新jsapi_ticket会导致 api 调用受限,影响自身业务,开发者必须在自己的服务全局缓存jsapi_ticket (这里建议将 access_token 和 jsapi_ticket 都在服务器端进行获取并缓存,前端通过接口调取结果)。
- golang代码示例:
package weixinclient
import (
"encoding/json"
"errors"
"fmt"
"github.com/gin-gonic/gin"
)
type GetticketVo struct {
Ticket string `json:"ticket"`
ExpiresIn int64 `json:"expires_in"`
Errcode int64 `json:"errcode"`
Errmsg string `json:"errmsg"`
}
func Getticket(c *gin.Context, accessToken string) (*GetticketVo, error) {
ctx := commonx.GetTrace(c)
ps := fmt.Sprintf("access_token=%s&type=jsapi", accessToken)
ep := getEndPoint("Getticket")
resp, err := doGet(c, ep, ps, nil)
dlog.Infof("%v||Getticket resp=%v,err=%v", ctx, resp, err)
if err != nil || len(resp) == 0 {
return nil, errors.New("call API(Getticket) fail")
}
var vo GetticketVo
err = json.Unmarshal(resp, &vo)
if err != nil || vo.Errcode > 0 || vo.Errmsg != "ok" {
err = fmt.Errorf("err=%v", vo.Errmsg)
return nil, err
}
return &vo, nil
}
成功返回如下JSON:
{
"errcode":0,
"errmsg":"ok",
"ticket":"caLdikRXVbTPdHSM05e5u5sUoXNKd8-41ZO3MhKoyN5OfkWITDGgnr2fwJ0m9E8NYzWKVZvdVtaUgWvsdshFKA",
"expires_in":7200
}
3.3 根据获取到的 ticket 来生成签名
- 签名说明
微信开发中,ticket 通常用于获取 JS-SDK 的配置参数,包括签名(signature)。这些参数使得你能够在网页上使用微信的 JS-SDK。以下是一个基本的步骤说明如何使用获取到的 ticket 来生成签名:
准备用于签名的参数:nonceStr(随机字符串), timestamp(时间戳), url(当前网页的 URL,不包含 # 及其后面部分)和 jsapi_ticket。
将这些参数按照字段名的ASCII 码从小到大排序(字典序),并且使用 URL 键值对的格式(即 key1=value1&key2=value2…)拼接成字符串。
对拼接后的字符串进行 SHA1 加密,得到的结果即是签名(signature)。
- golang代码示例
package main
import (
"crypto/sha1"
"encoding/hex"
"fmt"
"sort"
"strings"
"time"
)
// GenerateSignature 生成微信JS-SDK的签名
func GenerateSignature(nonceStr, url, jsapiTicket string) string {
// 实时获取当前时间戳
timestamp := fmt.Sprintf("%d", time.Now().Unix())
// 准备用于签名的原始数据
params := map[string]string{
"jsapi_ticket": jsapiTicket,
"noncestr": nonceStr,
"timestamp": timestamp,
"url": url,
}
// 对参数名进行排序并拼接
var keys []string
for k := range params {
keys = append(keys, k)
}
sort.Strings(keys)
var signStrings []string
for _, key := range keys {
signStrings = append(signStrings, fmt.Sprintf("%s=%s", key, params[key]))
}
signString := strings.Join(signStrings, "&")
// 使用SHA1进行签名
h := sha1.New()
h.Write([]byte(signString))
signature := hex.EncodeToString(h.Sum(nil))
return signature
}
func main() {
nonceStr := "Wm3WZYTPz0wzccnW" // 这个应该是随机生成的字符串
url := "http://mp.weixin.qq.com?params=value" // 你的网页URL
jsapiTicket := "bxLdikRXVbTPdHSM05e5u5sUoXNKd8-41ZO3MhKoyN5OfkWITDGgnr2fwJ0m9E8NYzWKVZvdVtaUgWvsdshFKA" // 从微信服务器获取的jsapi_ticket
// 生成签名
signature := GenerateSignature(nonceStr, url, jsapiTicket)
fmt.Println("生成的签名是:", signature)
}
在这个示例中,GenerateSignature 函数接受四个参数:nonceStr(随机字符串),timestamp(时间戳),url(当前网页的URL),和jsapiTicket(从微信服务器获取的票据)。函数内部会对这些参数进行字典排序,拼接成一个待签名的字符串,然后使用SHA1算法生成签名。
请注意,为了简化示例,这里直接提供了nonceStr、timestamp、url和jsapiTicket的示例值。在实际应用中,我们需要根据具体情况动态获取这些值。特别是jsapiTicket,你需要先从微信服务器获取。
最后,main 函数中调用了 GenerateSignature 并打印出了生成的签名。我们可以将这段代码集成到我们的Go应用中,并根据需要进行适当的修改和调整。
- noncestr 的含义是什么?应该如何动态获取?
noncestr是一个随机字符串,通常用于确保请求的唯一性和安全性。在微信JS-SDK、微信支付等接口中,noncestr 作为一个重要的参数,用于防止重放攻击和确保请求的时效性。
在微信开发中,noncestr 通常需要你自己生成,并确保其唯一性和随机性。以下是几种生成 noncestr 的方法:
-
使用随机数或UUID:
你可以使用编程语言中的随机数生成函数或者UUID生成库来创建一个唯一的字符串。例如,在Go语言中,你可以使用crypto/rand库生成一个随机数,并将其转换为字符串,或者使用第三方库如github.com/google/uuid来生成一个UUID。 -
时间戳结合随机数:
为了增加noncestr的复杂性,你也可以将当前的时间戳与随机数结合起来使用。这样既可以保证每次请求的noncestr都是唯一的,也能通过时间戳增加一定的时效性验证。 -
使用安全的随机数生成器:
在安全性要求较高的场景下,应使用安全的随机数生成器来产生noncestr,以确保其不可预测性。
golang动态获取 noncestr
package main
import (
"crypto/rand"
"encoding/hex"
"fmt"
"log"
)
// GenerateNonceStr 生成一个随机的 noncestr
func GenerateNonceStr(length int) (string, error) {
bytes := make([]byte, length/2) // 因为一个字节可以表示为两个16进制数字
if _, err := rand.Read(bytes); err != nil {
return "", err
}
return hex.EncodeToString(bytes), nil
}
func main() {
nonceStr, err := GenerateNonceStr(32) // 生成一个32个字符长的noncestr
if err != nil {
log.Fatalf("Failed to generate nonceStr: %v", err)
}
fmt.Println("Generated nonceStr:", nonceStr)
}
得到的签名:
e851776f519a6c8d716bc61a5dec87b042d14c3e
四、H5页面配置与分享设置
注意:从这一步开始,后面的部分属于前端工作
4.1 引入微信JS-SDK
在需要调用 JS 接口的页面引入如下 JS文件,(支持https):http://res.wx.qq.com/open/js/jweixin-1.6.0.js
如需进一步提升服务稳定性,当上述资源不可访问时,可改访问:http://res2.wx.qq.com/open/js/jweixin-1.6.0.js(支持https)。
备注:支持使用 AMD/CMD 标准模块加载方法加载
4.2 通过 config 接口注入权限验证配置
所有需要使用 JS-SDK 的页面必须先注入配置信息,否则将无法调用(同一个 url 仅需调用一次,对于变化 url 的SPA的web app可在每次 url 变化时进行调用,目前 Android 微信客户端不支持 pushState 的H5新特性,所以使用 pushState 来实现web app的页面会导致签名失败,此问题会在Android6.2中修复)。
注意:
- 引入JS文件后,直接执行下列代码
- debug: true 用来调试的,如果不想alert弹出,改成false即可
- alert 弹出框中 errMsg 不一定就是错误,知识提示信息,例如 updateAppMessageShareData:ok 代表的是updateAppMessageShareData接口是没有问题的。
- 签名用的 noncestr 和 timestamp 必须与 wx.config 中的 nonceStr 和 timestamp 相同。
- jsApiList 接口列表:https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html#63
- jsApiList 接口列表 例如 wx.updateAppMessageShareData({ 配置 }) jsApiList填写 [‘updateAppMessageShareData’] 即可
wx.config({
debug: true, // 开启调试模式,调用的所有 api 的返回值会在客户端 alert 出来,若要查看传入的参数,可以在 pc 端打开,参数信息会通过 log 打出,仅在 pc 端时才会打印。
appId: '', // 必填,公众号的唯一标识
timestamp: , // 必填,生成签名的时间戳
nonceStr: '', // 必填,生成签名的随机串
signature: '',// 必填,签名(这里用上面第三步得到的签名)
jsApiList: [] // 必填,需要使用的 JS 接口列表
});
4.3 通过 ready 接口处理成功验证
在wx.ready函数中配置onMenuShareTimeline、onMenuShareAppMessage等接口实现微信分享功能,并设置自定义的分享标题、描述、缩略图及链接。
wx.ready(function(){
// config信息验证后会执行 ready 方法,所有接口调用都必须在 config 接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在 ready 函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在 ready 函数中。
});
4.4 通过 error 接口处理失败验证
使用wx.error函数处理验证失败的情况。
wx.error(function(res){
// config信息验证失败会执行 error 函数,如签名过期导致验证失败,具体错误信息可以打开 config 的debug模式查看,也可以在返回的 res 参数中查看,对于 SPA 可以在这里更新签名。
});
4.5 js代码示例
// 模拟从后端获取签名等配置信息的函数
function getWechatConfig(url) {
// 通常情况下,以下代码应由后端服务生成并返回给前端
// 这里仅为示例,实际项目中,signature, nonceStr, timestamp 应该在服务器端生成
const appId = 'YOUR_APP_ID'; // 替换为你的AppID
const jsApiList = ['onMenuShareTimeline', 'onMenuShareAppMessage'];
// 以下为模拟数据,实际开发中应由服务器端提供
const nonceStr = 'Wm3WZYTPz0wzccnW'; // 随机字符串
const timestamp = Math.floor(Date.now() / 1000).toString(); // 当前时间戳
const signature = 'SOME_SIGNATURE_STRING'; // 签名,应由后端根据算法生成
return {
appId,
nonceStr,
timestamp,
signature,
jsApiList
};
}
// 调用微信JS-SDK配置
function initWechatSDK() {
const currentUrl = encodeURIComponent(location.href.split('#')[0]);
const config = getWechatConfig(currentUrl);
wx.config({
debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: config.appId, // 必填,公众号的唯一标识
timestamp: config.timestamp, // 必填,生成签名的时间戳
nonceStr: config.nonceStr, // 必填,生成签名的随机串
signature: config.signature, // 必填,签名
jsApiList: config.jsApiList // 必填,需要使用的JS接口列表
});
wx.ready(function () {
// 在这里调用 API
configShare();
});
wx.error(function (res) {
// config信息验证失败会执行error函数,如签名过期(7200s)等原因触发error函数
console.error('微信JS-SDK配置失败', res);
});
}
// 配置微信分享
function configShare() {
const shareData = {
title: '自定义分享标题', // 分享标题
desc: '自定义分享描述', // 分享描述
link: 'https://example.com', // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl: 'https://example.com/path/to/image.jpg', // 分享图标
type: '', // 分享类型,music、video或link,不填默认为link
dataUrl: '', // 如果type是music或video,则要提供数据链接,默认为空
success: function () {
// 用户点击了分享后执行的回调函数
console.log('分享成功');
}
};
wx.onMenuShareTimeline(shareData); // 分享到朋友圈
wx.onMenuShareAppMessage(shareData); // 分享给朋友
wx.onMenuShareQQ(shareData); // 分享到QQ
wx.onMenuShareWeibo(shareData); // 分享到微博
}
// 初始化微信JS-SDK
initWechatSDK();