微信公众号手动上传图片

1 背景

平时写博客用的Markdown,但是每次想要在微信公众号上发表文章比较麻烦,因为微信公众号不支持除了mp.weixin.qq.com以外的其它域名,因此无法在微信公众号中的引用阿里云OSS中的图床

2 目标

把任意一张图片上传到微信公众号

3 微信公众号限制

微信公众平台开发概述
微信限制

3.1 Token限制

微信公众号获取Token的限制如下

公众平台以access_token为接口调用凭据,来调用接口,所有接口的调用需要先获取access_token,access_token在2小时内有效,过期需要重新获取,但1天内获取次数有限,开发者需自行存储,详见获取接口调用凭据(access_token)文档

3.2 素材限制,这里我们主要关系图片的限制

微信公众号图片限制如下

1、最近更新:永久图片素材新增后,将带有URL返回给开发者,开发者可以在腾讯系域名内使用(腾讯系域名外使用,图片将被屏蔽)。
2、公众号的素材库保存总数量有上限:图文消息素材、图片素材上限为100000,其他类型为1000。
3、素材的格式大小等要求与公众平台官网一致:
图片(image): 10M,支持bmp/png/jpeg/jpg/gif格式
语音(voice):2M,播放长度不超过60s,mp3/wma/wav/amr格式
视频(video):10MB,支持MP4格式
缩略图(thumb):64KB,支持JPG格式
4、图文消息的具体内容中,微信后台将过滤外部的图片链接,图片url需通过"上传图文消息内的图片获取URL"接口上传图片获取。
5、"上传图文消息内的图片获取URL"接口所上传的图片,不占用公众号的素材库中图片数量的100000个的限制,图片仅支持jpg/png格式,大小必须在1MB以下。
6、图文消息支持正文中插入自己账号和其他公众号已群发文章链接的能力。

4 实现

4.1 第一步:启用开发者密码

登录微信公众号,点击设置与开发 -> 基本设置 -> 公众号开发信息,然后启用开发者密码就可以了

⚠注意:启用开发者密码之后,需要保存好开发者密码,如果遗忘了,就只能选择重置

4.2 第二步:本地保存AppID以及AppSecret

为了不泄露AppID, AppSecret,大家可以用自己喜欢的方式保存下来,我这里保存到了Windows电脑的环境变量当中了,如下所示:

4.3 第三步:配置IP白名单

微信公众号设置了IP白名单,只有在白名单的客户端IP地址,才让其通过。否者,直接惨无人道的拒绝。如果忘记了设置白名单,即使配置正确了AppID, AppSecret,依然还是无法正确上传图片。此时,得到的错误为:get access_token error : errcode=40164 , errormsg=invalid ip 149.14.208.84 ipv6 ::ffff:149.14.208.84, not in whitelist rid: 660xxxd-3874544585790e-3a28a13c

此时,我们只需要打开微信公众号平台,点击设置与开发 -> 基本设置 -> 公众号开发信息,然后设置IP白名单即可。

4.4 第四步:获取微信AccessToken

微信公众号的API定义如下

上传图文消息内的图片获取URL
本接口所上传的图片不占用公众号的素材库中图片数量的100000个的限制。图片仅支持jpg/png格式,大小必须在1MB以下。
接口调用请求说明
http请求方式: POST,https协议 https://api.weixin.qq.com/cgi-bin/media/uploadimg?access_token=ACCESS_TOKEN 调用示例(使用curl命令,用FORM表单方式上传一个图片): curl -F media=@test.jpg “https://api.weixin.qq.com/cgi-bin/media/uploadimg?access_token=ACCESS_TOKEN”
如果手动获取AccessToken,那么我们需要定时刷新AccessToken。而且刷新频率不能太高,否则超过微信公众号的限制,就无法获取AccessToken

这里我选择使用GitHub上大佬们开源的第三方库,我是用的是silenceper/wechat开源项目

silenceper/wechat的使用非常简单,示例代码如下:

// 使用memcache保存access_token,也可选择redis或自定义cache
wc := wechat.NewWechat()
memory := cache.NewMemory()
cfg := &offConfig.Config{
    AppID:     "xxx",
    AppSecret: "xxx",
    Token:     "xxx",
    // EncodingAESKey: "xxxx",
    Cache: memory,
}
officialAccount := wc.GetOfficialAccount(cfg)

// 传入request和responseWriter
server := officialAccount.GetServer(req, rw)
// 设置接收消息的处理方法
server.SetMessageHandler(func(msg *message.MixMessage) *message.Reply {

    // 回复消息:演示回复用户发送的消息
    text := message.NewText(msg.Content)
    return &message.Reply{MsgType: message.MsgTypeText, MsgData: text}
})

// 处理消息接收以及回复
err := server.Serve()
if err != nil {
    fmt.Println(err)
    return
}
// 发送回复的消息
server.Send()

实际我们在使用的时候,其实可以不用设置Token,因为这个库在实际执行的时候,会自动重新请求AccessToekn

4.4.1 获取AccessToken源码简单分析

接口定义定义如下,非常简单。

// AccessTokenHandle AccessToken 接口
type AccessTokenHandle interface {
	GetAccessToken() (accessToken string, err error)
}

我们默认使用的就是DefaultAccessToken实现,如下所示:

// GetAccessToken 获取access_token,先从cache中获取,没有则从服务端获取
func (ak *DefaultAccessToken) GetAccessToken() (accessToken string, err error) {
	return ak.GetAccessTokenContext(context.Background())
}

// GetAccessTokenContext 获取access_token,先从cache中获取,没有则从服务端获取
func (ak *DefaultAccessToken) GetAccessTokenContext(ctx context.Context) (accessToken string, err error) {
	// 先从cache中取
	accessTokenCacheKey := fmt.Sprintf("%s_access_token_%s", ak.cacheKeyPrefix, ak.appID)

	if val := ak.cache.Get(accessTokenCacheKey); val != nil {
		if accessToken = val.(string); accessToken != "" {
			return
		}
	}

	// 加上lock,是为了防止在并发获取token时,cache刚好失效,导致从微信服务器上获取到不同token
	ak.accessTokenLock.Lock()
	defer ak.accessTokenLock.Unlock()

	// 双检,防止重复从微信服务器获取
	if val := ak.cache.Get(accessTokenCacheKey); val != nil {
		if accessToken = val.(string); accessToken != "" {
			return
		}
	}

	// cache失效,从微信服务器获取
	var resAccessToken ResAccessToken
	if resAccessToken, err = GetTokenFromServerContext(ctx, fmt.Sprintf(accessTokenURL, ak.appID, ak.appSecret)); err != nil {
		return
	}

	if err = ak.cache.Set(accessTokenCacheKey, resAccessToken.AccessToken, time.Duration(resAccessToken.ExpiresIn-1500)*time.Second); err != nil {
		return
	}
	accessToken = resAccessToken.AccessToken
	return
}

上面的代码没有什么难度,accessTokenURL其实就是一个常量pattern,定义为https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s,其实就是微信公众号定义的API

原理就是先从缓存当中获取AccessToken,如果缓存当中有,就直接使用缓存中的;如果缓存中的AccessToken失效了,就只能向微信公众号请求了。

4.5 第五步:上传图片

上传图片的代码非常简单,仅仅是库的调用,如下所示:

package main

import (
	"fmt"
	"github.com/golang/demo/tools"
	"github.com/silenceper/wechat/cache"
	"github.com/silenceper/wechat/v2"
	offConfig "github.com/silenceper/wechat/v2/officialaccount/config"
	"log"
)

const (
	WeChatAppID     string = "WeChatAppID"
	WeChatAppSecret string = "WeChatAppSecret"
)

func main() {
	appId, err := tools.GetEnvVar(WeChatAppID)
	if err != nil {
		log.Fatalf("获取微信AppID环境变量失败:%s\n", err)
	}
	appSecret, err := tools.GetEnvVar(WeChatAppSecret)
	if err != nil {
		log.Fatalf("获取微信AppSecret环境变量失败:%s\n", err)
	}

	wc := wechat.NewWechat()
	memory := cache.NewMemory()
	cfg := &offConfig.Config{
		AppID:     appId,
		AppSecret: appSecret,
		Cache:     memory,
	}
	oa := wc.GetOfficialAccount(cfg)

	material := oa.GetMaterial()
	url, err := material.ImageUpload("C:\\Users\\David\\Downloads\\k8s-arch.png")
	if err != nil {
		log.Fatalf("上传图片失败,%s\n", err)
	}
	fmt.Printf("图片访问地址为:%s\n", url)

}

程序的直接结果如下:

API server listening at: 127.0.0.1:63061
图片访问地址为:http://mmbiz.qpic.cn/mmbiz_png/Il8kTo0FA4QM55lVJICZ61S64uPW8iaJgABcsdIvClwOdqTW5UGE1QPOfv6uicbdmV9Z11hcrJkVCFHb4sYoQWpw/0

此时,可以直接在诸如Typora这类的Markdown工具中直接以图片的方式验证,上面的地址是否能够否访问,如下所示:

如果在Markdown工具当中可以正确访问图片,说明图片已经上传成功了。

⚠注意:千万不要在自己的网站中插入上传到微信公众号的图片,因为微信做了防盗链,浏览器在请求的时候会自动加入Referrer,此时微信公众号会拒绝来自其他网站的请求

4.5.1 删除图片

如果我们想要删除测试的图片,此时我们可以通过微信公众号的删除接口删除图片。

官方API接口定义如下:

在新增了永久素材后,开发者可以根据本接口来删除不再需要的永久素材,节省空间。
请注意:
1、请谨慎操作本接口,因为它可以删除公众号在公众平台官网素材管理模块中新建的图文消息、语音、视频等素材(但需要先通过获取素材列表来获知素材的media_id) 2、临时素材无法通过本接口删除 3、调用该接口需https协议
接口调用请求说明
http请求方式: POST https://api.weixin.qq.com/cgi-bin/material/del_material?access_token=ACCESS_TOKEN
{
“media_id”:MEDIA_ID
}

让我比较疑惑的是,我从哪里获取这个MEDIA_ID,因为我在上传图片的时候,根本就没有看到MEDIA_ID相关的信息,直达我翻到了获取素材列表接口,我才明白过来,原来我们在删除图片的时候,是需要先通过获取素材列表接口查询图片的MEDIA_ID才能删除的。

返回值类似这样:

{
   "total_count": TOTAL_COUNT,
   "item_count": ITEM_COUNT,
   "item": [{
       "media_id": MEDIA_ID,
       "content": {
           "news_item": [{
               "title": TITLE,
               "thumb_media_id": THUMB_MEDIA_ID,
               "show_cover_pic": SHOW_COVER_PIC(0 / 1),
               "author": AUTHOR,
               "digest": DIGEST,
               "content": CONTENT,
               "url": URL,
               "content_source_url": CONTETN_SOURCE_URL
           },
           //多图文消息会在此处有多篇文章
           ]
        },
        "update_time": UPDATE_TIME
    },
    //可能有多个图文消息item结构
  ]
}

根据接口的返回值,我猜测微信官方是不希望我们直接删除图片的,因为这一张图片,很有可能在很多个文章当中使用,如果我们觉得在某个文章中不再使用这个图片了,就直接删除;很有可能导致我们其它因为这张图片的文章图片获取失败。

5 总结

总体来说,微信公众号上传图片还是比较简单的,一个API直接调用即可,没有什么难度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值