文章目录
- 思路
- 普及知识!
- 准备工作
- 代码示例 这里以php的tp框架为例
思路
目前微信小程序是没办法长期订阅的,除了什么教育、交通、医疗行业外。
那么想实现长期订阅,首先就要在开放平台绑定微信公众号和微信小程序。
用户注册小程序的同时,引导用户关注公众号。
如果用户不关注公众号,那么依然无法给用户长期发送消息
如果不适用于兄弟们,那就不用往下看了。
总结一下:
①:用户注册小程序,用户对于小程序的openid和unionid入库。
②:引导用户关注公众号,根据数据库中用户的unionid,更新用户对于公众号的openid信息。
③:公众号发送消息,需要用到用户在公众号中的openid。
④:公众号发送模板消息,然后在消息中跳转回小程序即可。
普及知识!
先普及一下知识。微信开放平台和微信公众平台是两个不同的平台。
微信开放平台上可以绑定公众号和小程序。
微信公众平台上可以选择公众号登录或者小程序登录,进而进入它们的管理后台。
公众号的appid和appsecret和小程序的不一样!
公众号的openid和小程序的openid不一样!
但是,绑定在同一个开放平台上的公众号的unionid和小程序的unionid是相同的。
准备工作
- 将公众号和小程序绑定在同一个开放平台中。
- 公众号管理后台菜单中,广告与服务-小程序管理菜单下,添加要发送消息的小程序。小程序中好像也需要关联一下。
- 在公众号后台设置与开发-基本设置菜单中,启用服务器配置。
启用服务器配置的方法 - 在广告与服务-模板消息中添加消息模板。(很easy,不过多介绍。)
- 检车设置与开发-接口权限中,是否有
获取用户基本信息
的权限。这个一定要有。
代码示例 这里以php的tp框架为例
官方文档:基础消息能力/接收事件推送
以下代码,我们只需要看event事件触发时候的那一部分即可。
//路由
//发送公众号通知
Route::post('sendPublicMessage', 'home/Wechat/sendPublicMessage');
//公众号服务器配置以及回调接口
Route::any('subscribe', 'home/Wechat/subscribe');
//控制器代码-- Wechat类中
<?php
use think\Db;
use think\facade\Log;
use think\Request;
private $appId = 'your appid'; //公众号appid
private $appSecret = 'your appsecret';//公众号appsecret
class Wechat
{
/**
* Description:获取token
* @return mixed
* @date 2024/4/26 14:12
* @author zls
*/
public function getAccessToken()
{
//获取access_token
$tokenAccessUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$this->appId}&secret={$this->appSecret}";
$access_token_json = file_get_contents($tokenAccessUrl);
$access_token_array = json_decode($access_token_json, true);
return $access_token_array['access_token'] ?: '';
}
/**
* Description:公众号回调接口
* @param Request $request
* @return \think\response\Json
* @date 2024/4/25 17:29
* @author zls
*/
public function subscribe(Request $request)
{
//接收参数,验签的标识
$echoStr = $request->param('echostr');
if (!empty($echoStr)) {
//验签-配置服务器配置
$this->valid($request->param());
echo $echoStr;
exit;
} else {
$this->responseMsg($request->param());
exit;
}
}
/**
* Description:公众号服务器验签
* @param $data
* @return bool
* @date 2024/4/26 9:40
* @author zls
*/
public function valid($data)
{
$echoStr = $data['echoStr'];
$signature = $data['signature'];
$timestamp = $data['timestamp'];
$nonce = $data['nonce'];
$token = "123456";//公众号后台自定义的token
$tmpArr = array($token, $timestamp, $nonce);
sort($tmpArr, SORT_STRING);
$tmpStr = implode($tmpArr);
$tmpStr = sha1($tmpStr);
if ($tmpStr == $signature) {
return true;
//不可以在调用的方法中echo $echoStr; 必须要在主方法中echo;
// echo $echoStr;
// exit;
} else {
Log::info("验签失败");
return json(false);
}
}
/**
* Description:响应消息
* @return void
* @date 2024/4/26 9:56
* @author zls
*/
public function responseMsg($data)
{
$postStr = $GLOBALS["HTTP_RAW_POST_DATA"];
if (!empty($postStr)) {
$postObj = simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
$RX_TYPE = trim($postObj->MsgType);
//处理用户发送到公众号的各种类型的消息。
//其实我们只需要关注(event)事件接口即可!别的不用管也行。只是给大家普及一下。
switch ($RX_TYPE) {
//关注、取关等事件
case "event":
$result = $this->receiveEvent($postObj);
break;
//用户发送文字消息
case "text":
$result = $this->receiveText($postObj);
break;
//用户发送图片消息 以下不再一一注释
case "image":
$result = $this->receiveImage($postObj);
break;
case "location":
$result = $this->receiveLocation($postObj);
break;
case "voice":
$result = $this->receiveVoice($postObj);
break;
case "video":
$result = $this->receiveVideo($postObj);
break;
case "link":
$result = $this->receiveLink($postObj);
break;
default:
$result = "unknown msg type: " . $RX_TYPE;
break;
}
Log::info('Result' . $result);
echo $result;
} else {
echo "";
exit;
}
}
/**
* Description:发送公众号消息推送
* @param Request $request
* @return void
* @date 2024/4/8 10:19
* @author zls
*/
public function sendPublicMessage(Request $request)
{
//获取access_token
$access_token = $this->getAccessToken();
//接收者(用户)的OpenID
$toUserOpenId = $request->param('openId');
//模板ID
$templateId = $request->param('templateId');
//模板内容,这里以键值对形式表示
$data = array(
'thing1' => array('value' => '内容1', 'color' => '#173177'),
'thing4' => array('value' => '内容2', 'color' => '#173177'),
'time5' => array('value' => date('Y-m-d H:i:s', time()), 'color' => '#173177'),
'thing6' => array('value' => '内容2', 'color' => '#173177'),
);
//模板其他参数-要跳转到哪个小程序
$miniProgram = array('appid' => '小程序的appid', 'pagepath' => 'pages/index/index');
$time = time();
$url = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=$access_token";
//组装发送的模板数据
$postData = array(
'touser' => $toUserOpenId,
'template_id' => $templateId,
'url' => "http://www.baidu.com",
'miniprogram' => $miniProgram,
'data' => $data,
);
//对数组进行JSON编码
$postData = json_encode($postData, JSON_UNESCAPED_UNICODE);
//发送模板消息
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$result = curl_exec($ch);
if (curl_errno($ch)) {
echo 'Error:' . curl_error($ch);
}
curl_close($ch);
//输出返回结果
echo $result;
}
/**
* Description:获取公众号中的用户信息
* @param $access_token
* @param $open_id
* @return mixed|string
* @date 2024/4/26 14:25
* @author zls
*/
public function getPublicUserInfo($access_token, $open_id)
{
//获取access_token
$userInfo = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=$access_token&openid=$open_id";
$userInfo_json = file_get_contents($userInfo);
return json_decode($userInfo_json, true);
}
/**
* Description:接收事件消息
* @param $object
* @return false|string|null
* @date 2024/4/26 13:59
* @author zls
*/
private function receiveEvent($object)
{
$result = "";
switch ($object->Event) {
case "subscribe":
//关注公众号事件
//获取token
$access_token = $this->getAccessToken();
//获取用户的unionid
$publicUserInfo = $this->getPublicUserInfo($access_token, $object->FromUserName);
$unionId = $publicUserInfo['unionid'];
//更新到用户表
$update_data['public_open_id'] = $object->FromUserName;
$update_data['is_follow'] = 1; //是否关注
Db::table('user_table')->where(['weixin_unionid' => $unionId])->update($update_data);
//欢迎语
$content = '感谢关注!';
//$content .= (!empty($object->EventKey)) ? ("\n来自二维码场景 " . str_replace("qrscene_", "", $object->EventKey)) : "";
break;
case "unsubscribe":
//取关事件
//获取token
$access_token = $this->getAccessToken();
//获取用户的unionid
$publicUserInfo = $this->getPublicUserInfo($access_token, $object->FromUserName);
$unionId = $publicUserInfo['unionid'];
//更新用户表的状态
$update_data['public_open_id'] = $object->FromUserName;
$update_data['is_follow'] = 2;
Db::table('user_table')->where(['weixin_unionid' => $unionId])->update($update_data);
$content = "取消关注";
break;
//下面的时间可以不用看了~
case "SCAN":
$content = "扫描场景 " . $object->EventKey . "; 扫描value ";
break;
case "CLICK":
switch ($object->EventKey) {
case "COMPANY":
$content = array();
$content[] = array("Title" => "多图文1标题", "Description" => "", "PicUrl" => "", "Url" => "");
break;
default:
$content = "点击菜单:" . $object->EventKey;
break;
}
break;
case "LOCATION":
$content = "上传位置:纬度 " . $object->Latitude . ";经度 " . $object->Longitude;
break;
case "VIEW":
$content = "跳转链接 " . $object->EventKey;
break;
case "MASSSENDJOBFINISH":
$content = "消息ID:" . $object->MsgID . ",结果:" . $object->Status . ",粉丝数:" . $object->TotalCount . ",过滤:" . $object->FilterCount . ",发送成功:" . $object->SentCount . ",发送失败:" . $object->ErrorCount;
break;
default:
$content = "" . $object->Event;
break;
}
if (is_array($content)) {
if (isset($content[0])) {
$result = $this->transmitNews($object, $content);
} else if (isset($content['MusicUrl'])) {
$result = $this->transmitMusic($object, $content);
}
} else {
$result = $this->transmitText($object, $content);
}
return $result;
}
//接收文本消息
private function receiveText($object)
{
$keyword = trim($object->Content);
$content = "";
$result = "";
//根据key 查企业的登记信息
$sql = "";
$row = [];
$defaulttxt = !empty($row) ? $row['default'] : '';
if (empty($row) || empty($row['register_code'])) {
$content = "";//不提醒也不提示
} else {
$register_code = $row['register_code'];
$content = $row['welcome'];
$default_company = $row['name'];
//如果用户发送的关键词中含有注册俩字-则执行注册
//以下是一些注册的逻辑 表结构不一样,大家不看也罢~
if (strstr($keyword, '注册')) {
//注册功能
$acc = str_replace('注册', '', $keyword);
$account = trim($acc);
$openId = trim($object->FromUserName);
if (empty($account)) {
$content = "抱歉,注册失败,格式不正确(1004). 请联系客服咨询.";
} else {
//首先检查当前微信号和注册账号是否已经注册过了
$sql = "";
$row = [];
if (!empty($row) && $row['cust_id'] > 0) {
if ($row['login_name1'] == $account && strlen($row['weixin_openid1']) > 5) {
//已经注册过了
$content = $default_company . '欢迎你! 你的账号已经注册过(1001), 无需重复注册!';
} elseif ($row['login_name2'] == $account && strlen($row['weixin_openid2']) > 5) {
//已经注册过了
$content = $default_company . '欢迎你! 你的账号已经注册过(1001), 无需重复注册!';
} else {
if ($row['login_name1'] == $account) {
//数据库更新用户信息操作--省略
$content = $default_company . '欢迎你! 注册成功!可以使用业主系统啦,请访问公众号菜单“客户登录” ';
} elseif ($row['login_name2'] == $account) {
//数据库更新用户信息操作--省略
$content = $default_company . '欢迎你! 注册成功!可以使用业主系统啦,请访问公众号菜单“客户登录”! 下次直接访问公众号菜单登录, 永久免密码登陆!';
}
}
} else {
$content = "抱歉,注册失败(1000). 请联系客服咨询.";
}
}
} elseif (strstr($keyword, $register_code)) {
$acc = str_replace($register_code, '', $keyword);
$content = "欢迎你注册" . $register_code . "企业员工账号!";
$account = trim($acc);
$openId = $object->FromUserName;
//首先检查当前微信号和注册账号是否已经注册过了;
$sql = "";
$checkRow = [];
if (!empty($checkRow) && $checkRow['user_id'] > 0) {
if (strlen($checkRow['weixin_openid']) > 5) {
$content = $default_company . '欢迎你! 你的登录账号已注册过, 无需重复注册(1004)';
} else {
//此处最好获取用户的信息
//后续考虑
//数据库更新用户信息操作--省略
$content = $default_company . '欢迎你! 绑定员工账号成功!请访问公众号菜单“员工登录”! 下次直接访问公众号菜单登录, 永久免密码登陆!';
}
} else {
//账号不存在
$content = "抱歉,注册失败(1003). 账号不存在, 请联系公司人事专员咨询开通.";
}
if (empty($content)) {
$content = "";
}
} elseif (strstr($keyword, '工人')) {
$acc = str_replace('工人', '', $keyword);
$content = "欢迎你注册施工产业工人端口!";
$account = trim($acc);
$openId = $object->FromUserName;
//首先检查当前微信号和注册账号是否已经注册过了;
$sql = "";
$checkRow = [];
if (!empty($checkRow) && $checkRow['user_id'] > 0) {
if (strlen($checkRow['weixin_openid']) > 5) {
$content = $default_company . '欢迎你! 你的账号已注册过, 无需重复注册(1004)';
} else {
//此处最好获取用户的信息
//后续考虑
//数据库更新用户信息操作--省略
$content = $default_company . '欢迎你! 登录账号绑定成功!请访问公众号菜单“工人帮”! 下次直接访问公众号菜单登录, 永久免密码登陆!';
}
} else {
//账号不存在
$content = "抱歉,注册失败(1003). 账号不存在, 请联系公司人事专员咨询开通.";
}
if (empty($content)) {
$content = "";
}
} elseif (strstr($keyword, '伙伴')) {
//合作伙伴注册
$acc = str_replace('伙伴', '', $keyword);
//$content = "欢迎你注册**会员, 注册账号是:".$acc.", from:".$object->FromUserName." to:".$object->ToUserName;
$content = "欢迎你注册" . $default_company . "企业的合作伙伴会员!";
$account = trim($acc);
$openId = $object->FromUserName;
//用户微信ID:from
//首先检查当前微信号和注册账号是否已经注册过了;
//数据库操作--省略
if (!empty($checkRow) && $checkRow['sup_id'] > 0) {
if ($checkRow['status'] == '0') {
$content = '欢迎你! 你的伙伴账号已被冻结, 请联系公司人事专员咨询开通!';
} else {
if (strlen($checkRow['weixin_openid1']) > 5) {
$content = '欢迎你! 你的伙伴账号已经注册过, 请不要重复注册!';
} else {
//此处最好获取用户的信息
//后续考虑
$serno = 'SUP-' . guid();
$GLOBALS['db']->query("UPDATE " . $GLOBALS['pms']->scmtable('supplier') .
" SET weixin_openid1='" . $openId . "'
WHERE sup_id='" . $checkRow['sup_id'] . "'");
$content .= '恭喜注册伙伴账号成功!现在可以正常使用合作伙伴业务系统啦! 下次直接访问公众号菜单登录, 永久免密码登陆!';
//创建新的staff信息
$supply_staff = array('ref_supply_id' => trim($checkRow['sup_id']),
'name' => $checkRow['name'],
'if_allow_weixin' => 1,
'login_name1' => $checkRow['login_name1'],
'weixin_openid1' => $openId,
'if_master' => 1,
'if_job' => 1);
$GLOBALS['db']->autoExecute($GLOBALS['pms']->scmtable('supply_staff'), $supply_staff, 'INSERT');
}
}
} else {
$content = $defaulttxt;//不符合提醒格式,给客户发一个默认消息
}
}
}
if (is_array($content)) {
if (isset($content[0]['PicUrl'])) {
$result = $this->transmitNews($object, $content);
} else if (isset($content['MusicUrl'])) {
$result = $this->transmitMusic($object, $content);
}
} else {
$result = $this->transmitText($object, $content);
}
return $result;
}
//接收图片消息
private function receiveImage($object)
{
$content = array("MediaId" => $object->MediaId);
return $this->transmitImage($object, $content);
}
//接收位置消息
private function receiveLocation($object)
{
$content = "你发送的是位置,纬度为:" . $object->Location_X . ";经度为:" . $object->Location_Y . ";缩放级别为:" . $object->Scale . ";位置为:" . $object->Label;
return $this->transmitText($object, $content);
}
//接收语音消息
private function receiveVoice($object)
{
if (isset($object->Recognition) && !empty($object->Recognition)) {
$content = "你刚才说的是:" . $object->Recognition;
$result = $this->transmitText($object, $content);
} else {
$content = array("MediaId" => $object->MediaId);
$result = $this->transmitVoice($object, $content);
}
return $result;
}
//接收视频消息
private function receiveVideo($object)
{
$content = array("MediaId" => $object->MediaId, "ThumbMediaId" => $object->ThumbMediaId, "Title" => "", "Description" => "");
return $this->transmitVideo($object, $content);
}
//接收链接消息
private function receiveLink($object)
{
$content = "你发送的是链接,标题为:" . $object->Title . ";内容为:" . $object->Description . ";链接地址为:" . $object->Url;
return $this->transmitText($object, $content);
}
//回复文本消息
private function transmitText($object, $content)
{
if (empty($content)) {
return false;
}
$xmlTpl = "<xml>
<ToUserName><![CDATA[%s]]></ToUserName>
<FromUserName><![CDATA[%s]]></FromUserName>
<CreateTime>%s</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[%s]]></Content>
</xml>";
return sprintf($xmlTpl, $object->FromUserName, $object->ToUserName, time(), $content);
}
//回复图片消息
private function transmitImage($object, $imageArray)
{
$itemTpl = "<Image>
<MediaId><![CDATA[%s]]></MediaId>
</Image>";
$item_str = sprintf($itemTpl, $imageArray['MediaId']);
$xmlTpl = "<xml>
<ToUserName><![CDATA[%s]]></ToUserName>
<FromUserName><![CDATA[%s]]></FromUserName>
<CreateTime>%s</CreateTime>
<MsgType><![CDATA[image]]></MsgType>
$item_str
</xml>";
return sprintf($xmlTpl, $object->FromUserName, $object->ToUserName, time());
}
//回复语音消息
private function transmitVoice($object, $voiceArray)
{
$itemTpl = "<Voice>
<MediaId><![CDATA[%s]]></MediaId>
</Voice>";
$item_str = sprintf($itemTpl, $voiceArray['MediaId']);
$xmlTpl = "<xml>
<ToUserName><![CDATA[%s]]></ToUserName>
<FromUserName><![CDATA[%s]]></FromUserName>
<CreateTime>%s</CreateTime>
<MsgType><![CDATA[voice]]></MsgType>
$item_str
</xml>";
return sprintf($xmlTpl, $object->FromUserName, $object->ToUserName, time());
}
//回复视频消息
private function transmitVideo($object, $videoArray)
{
$itemTpl = "<Video>
<MediaId><![CDATA[%s]]></MediaId>
<ThumbMediaId><![CDATA[%s]]></ThumbMediaId>
<Title><![CDATA[%s]]></Title>
<Description><![CDATA[%s]]></Description>
</Video>";
$item_str = sprintf($itemTpl, $videoArray['MediaId'], $videoArray['ThumbMediaId'], $videoArray['Title'], $videoArray['Description']);
$xmlTpl = "<xml>
<ToUserName><![CDATA[%s]]></ToUserName>
<FromUserName><![CDATA[%s]]></FromUserName>
<CreateTime>%s</CreateTime>
<MsgType><![CDATA[video]]></MsgType>
$item_str
</xml>";
return sprintf($xmlTpl, $object->FromUserName, $object->ToUserName, time());
}
//回复图文消息
private function transmitNews($object, $newsArray)
{
if (!is_array($newsArray)) {
return [];
}
$itemTpl = "<item>
<Title><![CDATA[%s]]></Title>
<Description><![CDATA[%s]]></Description>
<PicUrl><![CDATA[%s]]></PicUrl>
<Url><![CDATA[%s]]></Url>
</item>";
$item_str = "";
foreach ($newsArray as $item) {
$item_str .= sprintf($itemTpl, $item['Title'], $item['Description'], $item['PicUrl'], $item['Url']);
}
$xmlTpl = "<xml>
<ToUserName><![CDATA[%s]]></ToUserName>
<FromUserName><![CDATA[%s]]></FromUserName>
<CreateTime>%s</CreateTime>
<MsgType><![CDATA[news]]></MsgType>
<ArticleCount>%s</ArticleCount>
<Articles>$item_str</Articles>
</xml>";
return sprintf($xmlTpl, $object->FromUserName, $object->ToUserName, time(), count($newsArray));
}
//回复音乐消息
private function transmitMusic($object, $musicArray)
{
$itemTpl = "<Music>
<Title><![CDATA[%s]]></Title>
<Description><![CDATA[%s]]></Description>
<MusicUrl><![CDATA[%s]]></MusicUrl>
<HQMusicUrl><![CDATA[%s]]></HQMusicUrl>
</Music>";
$item_str = sprintf($itemTpl, $musicArray['Title'], $musicArray['Description'], $musicArray['MusicUrl'], $musicArray['HQMusicUrl']);
$xmlTpl = "<xml>
<ToUserName><![CDATA[%s]]></ToUserName>
<FromUserName><![CDATA[%s]]></FromUserName>
<CreateTime>%s</CreateTime>
<MsgType><![CDATA[music]]></MsgType>
$item_str
</xml>";
return sprintf($xmlTpl, $object->FromUserName, $object->ToUserName, time());
}
//回复多客服消息
private function transmitService($object)
{
$xmlTpl = "<xml>
<ToUserName><![CDATA[%s]]></ToUserName>
<FromUserName><![CDATA[%s]]></FromUserName>
<CreateTime>%s</CreateTime>
<MsgType><![CDATA[transfer_customer_service]]></MsgType>
</xml>";
return sprintf($xmlTpl, $object->FromUserName, $object->ToUserName, time());
}
}