Files
littleTiger/application/common/logic/WechatLogic.php
2019-02-28 19:48:21 +08:00

1533 lines
54 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* tpshop
* ============================================================================
* 版权所有 2015-2027 深圳搜豹网络科技有限公司,并保留所有权利。
* 网站地址: http://www.tp-shop.cn
* ----------------------------------------------------------------------------
* 这不是一个自由软件!您只能在不用于商业目的的前提下对程序代码进行修改和使用 .
* 不允许对程序代码以任何形式任何目的的再发布。
* 如果商业用途务必到官方购买正版授权, 以免引起不必要的法律纠纷.
* ============================================================================
*/
namespace app\common\logic;
use think\Db;
use think\Cache;
use think\Image;
use think\Validate;
use app\common\model\WxNews;
use app\common\model\WxReply;
use app\common\model\WxTplMsg;
use app\common\model\WxKeyword;
use app\common\model\WxMaterial;
use app\common\logic\wechat\WechatUtil;
/**
* 微信公众号的业务逻辑类
*/
class WechatLogic
{
static private $wx_user = null;
static private $wechat_obj;
public function __construct($config = null)
{
if (!self::$wx_user) {
if ($config === null) {
$config = Db::name('wx_user')->find();
}
self::$wx_user = $config;
self::$wechat_obj = new WechatUtil(self::$wx_user);
}
}
/**
* 处理接收推送消息
*/
public function handleMessage()
{
self::$wechat_obj->registerMsgEvent(WechatUtil::EVENT_TEXT, function ($msg) {
$this->handleTextMsg($msg);
});
self::$wechat_obj->registerMsgEvent(WechatUtil::EVENT_CLICK, function ($msg) {
$this->handleClickEvent($msg);
});
self::$wechat_obj->registerMsgEvent(WechatUtil::EVENT_SUBSCRIBE, function ($msg) {
$this->handleSubscribeEvent($msg);
});
self::$wechat_obj->handleMsgEvent();
}
/**
* 处理关注事件
* @param array $msg
* @return array
*/
private function handleSubscribeEvent($msg)
{
$openid = $msg['FromUserName'];
if (!$openid) {
exit("openid无效");
}
if ($msg['MsgType'] != 'event' || $msg['Event'] != 'subscribe') {
$this->replyError($msg , "不是关注事件");
}
if (! Db::name('oauth_users')->where('openid', $openid)->find()) {
if (false === ($wxdata = self::$wechat_obj->getFanInfo($openid))) {
$this->replyError($msg , self::$wechat_obj->getError());
}
$userData = [
'head_pic' => $wxdata['headimgurl'],
'nickname' => $wxdata['nickname'] ?: '微信用户',
'sex' => $wxdata['sex'] ?: 0,
'reg_time' => time(),
'password' => '',
'is_distribut' => 1,
];
// 由场景值获取分销一级id
if (!empty($msg['EventKey'])) {
$userData['first_leader'] = substr($msg['EventKey'], strlen('qrscene_'));
if ($userData['first_leader']) {
$first_leader = Db::name('users')->where('user_id', $userData['first_leader'])->find();
if ($first_leader) {
$userData['second_leader'] = $first_leader['first_leader']; // 第一级推荐人
$userData['third_leader'] = $first_leader['second_leader']; // 第二级推荐人
//他上线分销的下线人数要加1
Db::name('users')->where('user_id', $userData['first_leader'])->setInc('underling_number');
Db::name('users')->where('user_id', $userData['second_leader'])->setInc('underling_number');
Db::name('users')->where('user_id', $userData['third_leader'])->setInc('underling_number');
}
} else {
$userData['first_leader'] = 0;
}
}
$is_bind_account = tpCache('basic.is_bind_account');
if($is_bind_account && $userData['first_leader']){
//如果是绑定账号, 把first_leader保存到cookie
setcookie('user_id', $userData['first_leader'] ,null,'/');
//缓存关注时候传过来的上级idUser/bind_guide用到
Cache::set($openid,$userData['first_leader'],86400);
}else if(!$is_bind_account){//非绑定账号才给注册
//此处注册也送积分
$user_id = Db::name('users')->insertGetId($userData);
$isRegIntegral = tpCache('integral.is_reg_integral');
$pay_points = 0;
if($isRegIntegral==1){
$pay_points = tpCache('integral.reg_integral');
}
if($pay_points > 0){
accountLog($user_id, 0,$pay_points, '会员注册赠送积分'); // 记录日志流水
}
Db::name('oauth_users')->insert([
'user_id' => $user_id,
'openid' => $openid,
'unionid' => isset($wxdata['unionid']) ? $wxdata['unionid'] : '',
'oauth' => 'weixin',
'oauth_child' => 'mp',
]);
}
}
$this->replySubscribe($msg['ToUserName'], $openid);
}
/**
* 关注时回复消息
*/
private function replySubscribe($from, $to)
{
$result_str = $this->createReplyMsg($from, $to, WxReply::TYPE_FOLLOW);
if ( ! $result_str) {
//没有设置关注回复,则默认回复如下:
$store_name = tpCache("shop_info.store_name");
$result_str = self::$wechat_obj->createReplyMsgOfText($from, $to, "欢迎来到 $store_name !\n商城入口:".SITE_URL.'/mobile');
}
exit($result_str);
}
/**
* 创建回复消息
* @param $from string 发送方
* @param $to string 被发送方
* @param $type string WxReply的类型
* @param array $data 附加数据
* @return string
*/
private function createReplyMsg($from, $to, $type, $data = [])
{
if ($type != WxReply::TYPE_KEYWORD) {
$reply = WxReply::get(['type' => $type]);
} else {
$wx_keyword = WxKeyword::get(['keyword' => $data['keyword'], 'type' => WxKeyword::TYPE_AUTO_REPLY], 'wxReply');
$wx_keyword && $reply = $wx_keyword->wx_reply;
}
if (empty($reply)) {
return '';
}
$resultStr = '';
if ($reply->msg_type == WxReply::MSG_TEXT && $reply['data']) {
$resultStr = self::$wechat_obj->createReplyMsgOfText($from, $to, $reply['data']);
} elseif ($reply->msg_type == WxReply::MSG_NEWS) {
$resultStr = $this->createNewsReplyMsg($from, $to, $reply->material_id);
} else {
//扩展其他类型如imagevoice等
}
return $resultStr;
}
/**
* 处理点击事件
* @param array $msg
*/
private function handleClickEvent($msg)
{
$from = $msg['ToUserName'];
$to = $msg['FromUserName'];
$eventKey = $msg['EventKey'];
$distribut = tpCache('distribut');
// 分销二维码图片
if ($eventKey === $distribut['qrcode_menu_word']) {
$this->replyMyQrcode($msg);
}
// 关键字自动回复
$this->replyKeyword($from, $to, $eventKey);
}
/**
* 回复我的二维码
*/
private function replyMyQrcode($msg)
{
$fromUsername = $msg['FromUserName'];
$toUsername = $msg['ToUserName'];
$wechatObj = self::$wechat_obj;
$user = Db::name('oauth_users')->alias('o')->join('__USERS__ u', 'u.user_id=o.user_id')
->field('u.*')->where('o.openid', $fromUsername)->find();
if (!$user) {
$content = '请进入商城: '.SITE_URL.' , 再获取二维码哦';
$reply = $wechatObj->createReplyMsgOfText($toUsername, $fromUsername, $content);
exit($reply);
}
//获取缓存的图片id
$distribut = tpCache('distribut');
$mediaId = $this->getCacheQrcodeMedia($user['user_id'], $user['head_pic'], $distribut['qr_big_back']);
if (!$mediaId) {
$mediaId = $this->createQrcodeMedia($msg, $user['user_id'], $user['head_pic'], $distribut['qr_big_back']);
}
//回复图片消息
$reply = $wechatObj->createReplyMsgOfImage($toUsername, $fromUsername, $mediaId);
exit($reply);
}
private function createQrcodeMedia($msg, $userId, $headPic, $qrBackImg)
{
$wechatObj = self::$wechat_obj;
//创建二维码关注url
$qrCode = $wechatObj->createTempQrcode(2592000, $userId);
if (!(is_array($qrCode) && $qrCode['url'])) {
$this->replyError($msg, '创建二维码失败');
}
//创建分销二维码图片
empty($headPic) && $headPic = '/public/images/icon_goods_thumb_empty_300.png'; //没有头像用默认图片
$shareImg = $this->createShareQrCode('.'.$qrBackImg, $qrCode['url'], $headPic);
if (!$shareImg) {
$this->replyError($msg, '生成图片失败');
}
//上传二维码图片
if (!($mediaInfo = $wechatObj->uploadTempMaterial($shareImg, 'image'))) {
@unlink($shareImg);
$this->replyError($msg, '上传图片失败');
}
@unlink($shareImg);
$this->setCacheQrcodeMedia($userId, $headPic, $qrBackImg, $mediaInfo);
return $mediaInfo['media_id'];
}
private function getCacheQrcodeMedia($userId, $headPic, $qrBackImg)
{
$symbol = md5("{$headPic}:{$qrBackImg}");
$mediaIdCache = "distributQrCode:{$userId}:{$symbol}";
$config = cache($mediaIdCache);
if (!$config) {
return false;
}
//$config = json_decode($config);
//有效期3天259200s,提前5小时(18000s)过期
if (!(is_array($config) && $config['media_id'] && ($config['created_at'] + 259200 - 18000) > time())) {
return false;
}
return $config['media_id'];
}
private function setCacheQrcodeMedia($userId, $headPic, $qrBackImg, $mediaInfo)
{
$symbol = md5("{$headPic}:{$qrBackImg}");
$mediaIdCache = "distributQrCode:{$userId}:{$symbol}";
cache($mediaIdCache, $mediaInfo);
}
/**
* 处理点击推送事件
* @param array $msg
*/
private function handleTextMsg($msg)
{
$from = $msg['ToUserName'];
$to = $msg['FromUserName'];
$keyword = trim($msg['Content']);
//分销二维码图片
$distribut = tpCache('distribut');
if ($distribut['qrcode_input_word'] === $keyword) {
$this->replyMyQrcode($msg);
}
// 关键字自动回复
$this->replyKeyword($from, $to, $keyword);
}
/**
* 关键字自动回复
* @param $from
* @param $to
* @param $keyword
*/
private function replyKeyword($from, $to, $keyword)
{
if (!$keyword) {
$this->replyDefault($from, $to);
}
$resultStr = $this->createReplyMsg($from, $to, WxReply::TYPE_KEYWORD, ['keyword' => $keyword]);
if ($resultStr) {
exit($resultStr);
} else {
$this->replyDefault($from, $to);
}
}
/**
* 创建图文回复消息
*/
private function createNewsReplyMsg($fromUser, $toUser, $material_id)
{
$material = WxMaterial::get(['id' => $material_id, 'type' => WxMaterial::TYPE_NEWS], 'wxNews');
if (!$material || !$material->wx_news) {
return '';
}
$articles = [];
foreach ($material->wx_news as $news) {
$articles[] = [
'title' => $news->title,
'description' => $news->digest ?: $news->content_digest,
'picurl' => SITE_URL . $news->thumb_url,
'url' => SITE_URL . url('/mobile/article/news', ['id' => $news->id])
];
}
return self::$wechat_obj->createReplyMsgOfNews($fromUser, $toUser, $articles);
}
/**
* 默认回复
* @param array $msg
*/
private function replyDefault($from, $to)
{
$resultStr = $this->createReplyMsg($from, $to, WxReply::TYPE_DEFAULT);
if ( ! $resultStr) {
//没有设置默认回复,则默认回复如下:
$store_name = tpCache("shop_info.store_name");
$resultStr = self::$wechat_obj->createReplyMsgOfText($from, $to, "欢迎来到 $store_name !");
}
exit($resultStr);
}
/**
* 错误回复
*/
private function replyError($msg, $extraMsg = '')
{
$fromUsername = $msg['FromUserName'];
$toUsername = $msg['ToUserName'];
$wechatObj = self::$wechat_obj;
if ($wechatObj->isDedug()) {
$content = '错误信息:';
$content .= $wechatObj->getError() ?: '';
$content .= $extraMsg ?: '';
} elseif ($extraMsg) {
$content = '系统信息:'.$extraMsg;
} else {
$content = '系统正在处理...';
}
$resultStr = $wechatObj->createReplyMsgOfText($toUsername, $fromUsername, $content);
exit($resultStr);
}
/**
* 创建分享二维码图片
* @param string $backImg 背景大图片
* @param string $qrText 二维码文本:关注入口
* @param string $headPic 头像路径
* @return string 图片路径
*/
private function createShareQrCode($backImg, $qrText, $headPic)
{
if (!is_file($backImg) || !$headPic || !$qrText) {
return false;
}
vendor('phpqrcode.phpqrcode');
vendor('topthink.think-image.src.Image');
$qr_code_path = UPLOAD_PATH.'qr_code/';
!file_exists($qr_code_path) && mkdir($qr_code_path, 0777, true);
/* 生成二维码 */
$qr_code_file = $qr_code_path.time().rand(1, 10000).'.png';
\QRcode::png($qrText, $qr_code_file, QR_ECLEVEL_M);
$QR = Image::open($qr_code_file);
$QR_width = $QR->width();
//$QR_height = $QR->height();
/* 添加背景图 */
if ($backImg && is_file($backImg)) {
$back =Image::open($backImg);
$backWidth = $back->width();
$backHeight = $back->height();
//生成的图片大小以540*960为准
if ($backWidth <= $backHeight) {
$refWidth = 540;
$refHeight = 960;
if (($backWidth / $backHeight) > ($refWidth / $refHeight)) {
$backRatio = $refWidth / $backWidth;
$backWidth = $refWidth;
$backHeight = $backHeight * $backRatio;
} else {
$backRatio = $refHeight / $backHeight;
$backHeight = $refHeight;
$backWidth = $backWidth * $backRatio;
}
} else {
$refWidth = 960;
$refHeight = 540;
if (($backWidth / $backHeight) > ($refWidth / $refHeight)) {
$backRatio = $refHeight / $backHeight;
$backHeight = $refHeight;
$backWidth = $backWidth * $backRatio;
} else {
$backRatio = $refWidth / $backWidth;
$backWidth = $refWidth;
$backHeight = $backHeight * $backRatio;
}
}
$shortSize = $backWidth > $backHeight ? $backHeight : $backWidth;
$QR_width = $shortSize / 2;
$QR_height = $QR_width;
$QR->thumb($QR_width, $QR_height, \think\Image::THUMB_CENTER)->save($qr_code_file, null, 100);
$back->thumb($backWidth, $backHeight, \think\Image::THUMB_CENTER)
->water($qr_code_file, \think\Image::WATER_CENTER, 90)->save($qr_code_file, null, 100);
$QR = $back;
}
/* 添加头像 */
if ($headPic) {
//如果是网络头像
if (strpos($headPic, 'http') === 0) {
//下载头像
$ch = curl_init();
curl_setopt($ch,CURLOPT_URL, $headPic);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$file_content = curl_exec($ch);
curl_close($ch);
//保存头像
if ($file_content) {
$head_pic_path = $qr_code_path.time().rand(1, 10000).'.png';
file_put_contents($head_pic_path, $file_content);
$headPic = $head_pic_path;
}
}
//如果是本地头像
if (file_exists($headPic)) {
$logo = Image::open($headPic);
$logo_width = $logo->height();
$logo_height = $logo->width();
$logo_qr_width = $QR_width / 5;
$scale = $logo_width / $logo_qr_width;
$logo_qr_height = $logo_height / $scale;
$logo_file = $qr_code_path.time().rand(1, 10000);
$logo->thumb($logo_qr_width, $logo_qr_height)->save($logo_file, null, 100);
$QR = $QR->water($logo_file, \think\Image::WATER_CENTER);
unlink($logo_file);
}
if (!empty($head_pic_path)) {
unlink($head_pic_path);
}
}
//加上有效时间
$valid_date = date('Y.m.d', strtotime('+30 days'));
$QR->text('有效时间 '.$valid_date, "./vendor/topthink/think-captcha/assets/zhttfs/1.ttf", 16, '#FFFFFF', Image::WATER_SOUTH)->save($qr_code_file);
return $qr_code_file;
}
/**
* 获取粉丝列表
*/
public function getFanList($p, $num = 10)
{
$wechatObj = self::$wechat_obj;
if (!$access_token = $wechatObj->getAccessToken()) {
return ['status' => -1, 'msg' => $wechatObj->getError()];
}
$p = intval($p) > 0 ? intval($p) : 1;
$offset = ($p - 1) * $num;
$max = 10000; //粉丝列表每次只能拉取的数量
/* 获取所有粉丝列表openid并缓存 */
$fans_key = 'wechat.fan_list';
if (!$fans = Cache::get($fans_key)) {
$next_openid = '';
$fans = [];
do {
$ids = $wechatObj->getFanIdList($next_openid);
if ($ids === false) {
return ['status' => -1, 'msg' => $wechatObj->getError()];
}
if($ids['data']['openid']){
$fans = array_merge($fans, $ids['data']['openid']);
}
$next_openid = $ids['next_openid'];
} while ($ids['total'] > $max && $ids['count'] == $max);
Cache::set($fans_key, $fans, 3600); //缓存列表一个小时
}
/* 获取指定粉丝,并获取详细信息 */
$part_fans = array_slice($fans, $offset, $num);
$user_list = [];
$fan_key = 'wechat.fan_info';
foreach ($part_fans as $openid) {
if (!$fan = Cache::get($fan_key.'.'.$openid)) {
$fan = $wechatObj->getFanInfo($openid, $access_token);
if ($fan === false) {
continue;//不要因为一个粉丝的离开而影响整个列表
}
$fan['tags'] = $wechatObj->getFanTagNames($fan['tagid_list']);
if ($fan['tags'] === false) {
continue;//不要因为一个粉丝的离开而影响整个列表
}
Cache::set($fan_key.'.'.$openid, $fan, 3600); //缓存粉丝一个小时
}
//查user_id
$user_id = db('oauth_users')->where(['openid'=>$openid])->value('user_id');
$fan['user_id'] = $user_id?$user_id:0;
$user_list[$openid] = $fan;
}
return ['status' => 1, 'msg' => '获取成功', 'result' => [
'total' => count($fans),
'list' => $user_list
]];
}
/**
* 商城用户里的粉丝列表
*/
public function getUserFanList($p, $num = 10, $keyword= '')
{
$wechatObj = self::$wechat_obj;
if (!$access_token = $wechatObj->getAccessToken()) {
return ['status' => -1, 'msg' => $wechatObj->getError()];
}
$p = intval($p) > 0 ? intval($p) : 1;
$condition = ['o.openid' => ['<>', ''], 'o.oauth' => 'weixin', 'o.oauth_child' => 'mp'];
$keyword = trim($keyword);
$keyword && $condition['o.openid|u.nickname'] = ['like', "%$keyword%"];
$query = Db::name('oauth_users')->field('o.*')->alias('o')->join('__USERS__ u', 'u.user_id = o.user_id')->where($condition);
$copyQuery = clone $query;
$users = $query->page($p, $num)->select();
$user_num = $copyQuery->count();
$fan_key = 'wechat.user_fan_info';
foreach ($users as &$user) {
if (!$fan = Cache::get($fan_key.'.'.$user['openid'])) {
$fan = $wechatObj->getFanInfo($user['openid'], $access_token);
if ($fan === false) {
continue;//不要因为一个粉丝的离开而影响整个列表
}
Cache::set($fan_key.'.'.$user['openid'], $fan, 3600); //缓存粉丝一个小时
}
$user['weixin'] = $fan;
}
return ['status' => 1, 'msg' => '获取成功', 'result' => [
'total' => $user_num,
'list' => $users
]];
}
/**
* 新建和更新文本素材
* (图文素材只需保存在本地,微信不存储文本素材)
*/
public function createOrUpdateText($material_id, $data)
{
$validate = new Validate([
['title','require|max:64','标题必填|标题最多64字'],
['content','require|max:600','内容必填|内容最多600字'],
]);
if (!$validate->check($data)) {
return ['status' => -1, 'msg' => $validate->getError()];
}
$text = [
'type' => 'text',
'update_time' => time(),
'data' => [
'title' => $data['title'],
'content' => $data['content'],
]
];
if ($material_id) {
if (!$material = WxMaterial::get(['id' => $material_id, 'type' => WxMaterial::TYPE_TEXT])) {
return ['status' => -1, 'msg' => '文本素材不存在'];
}
$material->save($text);
} else {
$material = WxMaterial::create($text);
}
return ['status' => 1, 'msg' => '素材提交成功!', 'result' => $material->id];
}
/**
* 删除文本素材
*/
public function deleteText($material_id)
{
if (!$material_id || !$material = WxMaterial::get(['id' => $material_id, 'type' => WxMaterial::TYPE_TEXT])) {
return ['status' => -1, 'msg' => '文本素材不存在'];
}
$material->delete();
return ['status' => 1, 'msg' => '删除文本素材成功'];
}
/**
* 新建和更新图文素材
*/
public function createOrUpdateNews($material_id, $news_id, $data)
{
$article = [
"title" => $data['title'],
//"thumb_media_id" => $data['thumb_media_id'],
"thumb_url" => $data['thumb_url'],
"author" => $data['author'],
"digest" => $data['digest'],
"show_cover_pic" => $data['show_cover_pic'] ? 1 : 0,
"content" => $data['content'],
"content_source_url" => $data['content_source_url'],
"material_id" => $material_id,
"update_time" => time(),
];
if ($material_id) {
if (!$material = WxMaterial::get(['id' => $material_id, 'type' => WxMaterial::TYPE_NEWS])) {
return ['status' => -1, 'msg' => '图文素材不存在'];
}
if ($news_id) {
//更新单图文
if (!$news = WxNews::get(['id' => $news_id, 'material_id' => $material_id])) {
return ['status' => -1, 'msg' => '单图文素材不存在'];
}
$news->save($article);
if ($material->media_id) {
$material->save(['media_id' => 0]); // 需要重新上传
}
} else {
//新增单图文
$all_news = WxNews::all(['material_id' => $material_id]);
$max_news_per_material = 8;
if (count($all_news) >= $max_news_per_material) {
return ['status' => -1, 'msg' => "一个图文素材中的文章最多 $max_news_per_material"];
}
WxNews::create($article);
}
$material->save([
'update_time' => time(),
'media_id' => 0 // 需要重新上传
]);
} else {
//新增多图文
$material = WxMaterial::create([
'type' => WxMaterial::TYPE_NEWS,
'update_time' => time(),
]);
$article['material_id'] = $material->id;
WxNews::create($article);
}
//先不用上传到微信服务器,等实际使用的时候再上传
return ['status' => 1, 'msg' => '素材提交成功!'];
}
/**
* 删除图文素材
* @param $material_id int 素材id
* @return array
*/
public function deleteNews($material_id)
{
if (!$material_id || !$material = WxMaterial::get(['id' => $material_id, 'type' => WxMaterial::TYPE_NEWS], 'wxNews')) {
return ['status' => -1, 'msg' => '素材不存在'];
}
if (WxReply::get(['material_id' => $material_id, 'msg_type' => WxReply::MSG_NEWS])) {
return ['status' => -1, 'msg' => '该素材正被自动回复使用,无法删除'];
}
if ($material->media_id) {
self::$wechat_obj->delMaterial($material->media_id);
}
if (is_array($material->wx_news)) {
foreach ($material->wx_news as $news) {
$news->delete();
}
}
$material->delete();
return ['status' => 1, 'msg' => '删除图文成功'];
}
/**
* 删除单图文
* @param $news_id int 单图文的id
* @return array
*/
public function deleteSingleNews($news_id)
{
if (!$news_id || !$news = WxNews::get($news_id, 'wxMaterial')) {
return ['status' => -1, 'msg' => '单图文素材不存在'];
}
if (!$news->wx_material) {
return ['status' => -1, 'msg' => '该单图文所属素材不存在'];
}
if (count($news->wx_material->wx_news) == 1) {
return $this->deleteNews($news->material_id);
} else {
if ($news->wx_material->media_id) {
$news->wx_material->save(['media_id' => 0]); // 需要重新上传
}
$news->delete();
}
return ['status' => 1, 'msg' => '删除单图文成功'];
}
/**
* 上传图文
* @param $material WxMaterial
* @return array
*/
private function uploadNews($material)
{
$articles = [];
foreach ($material->wx_news as $news) {
// 1.获取或上传封面
if ($thumb = WxMaterial::get(['type' => WxMaterial::TYPE_IMAGE, 'key' => md5($news['thumb_url'])])) {
$thumb_media_id = $thumb->media_id;
} else {
$thumb = self::$wechat_obj->uploadMaterial('.'.$news['thumb_url'], 'image');
if ($thumb === false) {
return ['status' => -1, 'msg' => self::$wechat_obj->getError()];
}
$thumb_media_id = $thumb['media_id'];
WxMaterial::create([
'type' => WxMaterial::TYPE_IMAGE,
'key' => md5($news['thumb_url']),
'media_id' => $thumb_media_id,
'update_time' => time(),
'data' => [
'url' => $news['thumb_url'],
'mp_url' => $thumb['url'],
]
]);
}
// 2.将文章中的图片上传
$news['content'] = htmlspecialchars_decode($news['content']);
if (preg_match_all('#<img .*?src="(.*?)".*?/>#i', $news['content'], $matches)) {
$imgs = array_unique($matches[1]);
foreach ($imgs as $img) {
if (stripos($img, 'http') === 0) {
continue;
}
// 3.获取或上传文章中图片
if ($news_image = WxMaterial::get(['type' => WxMaterial::TYPE_NEWS_IMAGE, 'key' => md5($img)])) {
$news_image_url = $news_image->data['mp_url'];
} else {
$news_image_url = self::$wechat_obj->uploadNewsImage('.'.$img);
if ($news_image_url === false) {
return ['status' => -1, 'msg' => self::$wechat_obj->getError()];
}
WxMaterial::create([
'type' => WxMaterial::TYPE_NEWS_IMAGE,
'key' => md5($img),
'update_time' => time(),
'data' => [
'url' => $news['thumb_url'],
'mp_url' => $news_image_url,
]
]);
}
$news['content'] = str_replace($img, $news_image_url, $news['content']);
}
}
$articles[] = [
"title" => $news['title'],
"thumb_media_id" => $thumb_media_id,
"author" => $news['author'] ?: '',
"digest" => $news['digest'] ?: '',
"show_cover_pic" => $news['show_cover_pic'] ? 1 : 0,
"content" => $news['content'],
"content_source_url" => $news['content_source_url'],
];
}
$news_media_id = self::$wechat_obj->uploadNews($articles);
if ($news_media_id === false) {
return ['status' => -1, 'msg' => self::$wechat_obj->getError()];
}
$material->save(['media_id' => $news_media_id]);
return ['status' => 1, 'msg' => '上传成功', 'result' => $news_media_id];
}
/**
* 发送图文消息
* @param $material_id int 素材id
* @param $openids array|string 可多个用户openid
* @param int $to_all 0由openids决定1所有粉丝
* @return array
*/
public function sendNewsMsg($material_id, $openids, $to_all = 0)
{
$material = WxMaterial::get(['id' => $material_id, 'type' => WxMaterial::TYPE_NEWS], 'wxNews');
if (!$material || !$material->wx_news) {
return ['status' => -1, 'msg' => '该素材不存在'];
}
if ($material->media_id) {
$news_media_id = $material->media_id;
if (false === self::$wechat_obj->getMaterial($material->media_id)) {
$news_media_id = 0; //获取失败,可能被手动删了,需要重新上传
}
}
if (empty($news_media_id)) {
$return = $this->uploadNews($material);
if ($return['status'] != 1) {
return $return;
}
$news_media_id = $return['result'];
}
// 5.发送消息
if ($to_all) {
$result = self::$wechat_obj->sendMsgToAll(0, 'mpnews', $news_media_id);
} else {
$result = self::$wechat_obj->sendMsg($openids, 'mpnews', $news_media_id);
}
if ($result === false) {
return ['status' => -1, 'msg' => self::$wechat_obj->getError()];
}
return ['status' => 1, 'msg' => '发送成功'];
}
/**
* 删除图片
* @param $url string 存储在本地的url
*/
public function deleteImage($url)
{
if (strpos($url, 'weixin_mp_image/') === false) {
return;
}
if (!$image = WxMaterial::get(['type' => WxMaterial::TYPE_IMAGE, 'key' => md5($url)])) {
return;
}
if ($image->media_id) {
self::$wechat_obj->delMaterial($image->media_id);
}
}
/**
* 系统默认模板消息
* @return array
*/
public function getDefaultTemplateMsg($template_sn = null)
{
$templates = [
[
"template_sn" => "TM00016",
"title" => "订单提交成功",
"content" =>
"{{first.DATA}}\n\n"
."订单号:{{orderID.DATA}}\n"
."待付金额:{{orderMoneySum.DATA}}\n"
."{{backupFieldName.DATA}}{{backupFieldData.DATA}}\n"
."{{remark.DATA}}",
], [
"template_sn" => "OPENTM204987032",
"title" => "订单支付成功通知",
"content" =>
"{{first.DATA}}\n"
."订单:{{keyword1.DATA}}\n"
."支付状态:{{keyword2.DATA}}\n"
."支付日期:{{keyword3.DATA}}\n"
."商户:{{keyword4.DATA}}\n"
."金额:{{keyword5.DATA}}\n"
."{{remark.DATA}}",
], [
"template_sn" => "OPENTM202243318",
"title" => "订单发货通知",
"content" =>
"{{first.DATA}}\n"
."订单内容:{{keyword1.DATA}}\n"
."物流服务:{{keyword2.DATA}}\n"
."快递单号:{{keyword3.DATA}}\n"
."收货信息:{{keyword4.DATA}}\n"
."{{remark.DATA}}",
], [
"template_sn" => "OPENTM401833445",
"title" => "余额变动提示",
"content" =>
"{{first.DATA}}\n"
."变动时间:{{keyword1.DATA}}\n"
."变动类型:{{keyword2.DATA}}\n"
."变动金额:{{keyword3.DATA}}\n"
."当前余额:{{keyword4.DATA}}\n"
."{{remark.DATA}}",
], [
"template_sn" => "OPENTM207126233",
"title" => "分销商申请成功",
"content" =>
"{{first.DATA}}\n"
."分销商名称:{{keyword1.DATA}}\n"
."分销商电话:{{keyword2.DATA}}\n"
."申请时间:{{keyword3.DATA}}\n"
."{{remark.DATA}}",
], [
"template_sn" => "OPENTM201812627",
"title" => "佣金提醒",
"content" =>
"{{first.DATA}}\n"
."佣金金额:{{keyword1.DATA}}\n"
."时间:{{keyword2.DATA}}\n"
."{{remark.DATA}}",
], [
"template_sn" => "OPENTM407307456",
"title" => "开团成功通知",
"content" =>
"{{first.DATA}}\n"
."商品名称:{{keyword1.DATA}}\n"
."商品价格:{{keyword2.DATA}}\n"
."组团人数:{{keyword3.DATA}}\n"
."拼团类型:{{keyword4.DATA}}\n"
."组团时间:{{keyword5.DATA}}\n"
."{{remark.DATA}}",
], [
"template_sn" => "OPENTM400048581",
"title" => "参团成功通知",
"content" =>
"{{first.DATA}}\n"
."拼团名:{{keyword1.DATA}}\n"
."拼团价:{{keyword2.DATA}}\n"
."有效期:{{keyword3.DATA}}\n"
."{{remark.DATA}}",
], [
"template_sn" => "OPENTM407456411",
"title" => "拼团成功通知",
"content" =>
"{{first.DATA}}\n"
."订单编号:{{keyword1.DATA}}\n"
."团购商品:{{keyword2.DATA}}\n"
."{{remark.DATA}}",
], [
"template_sn" => "OPENTM400940587",
"title" => "拼团退款提醒",
"content" =>
"{{first.DATA}}\n"
."单号:{{keyword1.DATA}}\n"
."商品:{{keyword2.DATA}}\n"
."原因:{{keyword3.DATA}}\n"
."退款:{{keyword4.DATA}}\n"
."{{remark.DATA}}",
]
];
$templates = convert_arr_key($templates, 'template_sn');
$valid_sns = ['OPENTM204987032', 'OPENTM202243318']; //目前支持的模板
$valid_templates = [];
foreach ($valid_sns as $sn) {
if (isset($templates[$sn])) {
$valid_templates[$sn] = $templates[$sn];
}
}
if ($template_sn) {
return $valid_templates[$template_sn];
}
return $valid_templates;
}
/**
* 配置模板
* @param $data array 配置
*/
public function setTemplateMsg($template_sn, $data)
{
if (!isset($data['is_use']) && !isset($data['remark'])) {
return ['status' => -1, 'msg' => '参数为空'];
}
$tpls = $this->getDefaultTemplateMsg();
if (!key_exists($template_sn, $tpls)) {
return ['status' => -1, 'msg' => "模板消息的编号[$template_sn]不存在"];
}
if ($tpl_msg = WxTplMsg::get(['template_sn' => $template_sn])) {
$tpl_msg->save($data);
} else {
if (!$template_id = self::$wechat_obj->addTemplateMsg($template_sn)) {
return ['status' => -1, 'msg' => self::$wechat_obj->getError()];
}
WxTplMsg::create([
'template_id' => $template_id,
'template_sn' => $template_sn,
'title' => $tpls[$template_sn]['title'],
'is_use' => isset($data['is_use']) ? $data['is_use'] : 0,
'remark' => isset($data['remark']) ? $data['remark'] : '',
'add_time' => time(),
]);
}
return ['status' => 1, 'msg' => '操作成功'];
}
/**
* 重置模板消息
*/
public function resetTemplateMsg($template_sn)
{
if (!$template_sn) {
return ['status' => -1, 'msg' => '参数不为空'];
}
if ($tpl_msg = WxTplMsg::get(['template_sn' => $template_sn])) {
if ($tpl_msg->template_id) {
self::$wechat_obj->delTemplateMsg($tpl_msg->template_id);
}
$tpl_msg->delete();
}
return ['status' => 1, 'msg' => '操作成功'];
}
/**
* 发送模板消息(订单支付成功通知)
* @param $order array 订单数据
*/
public function sendTemplateMsgOnPaySuccess($order)
{
if ( ! $order) {
return ['status' => -1, 'msg' => '订单不存在'];
}
$template_sn = 'OPENTM204987032';
if ( ! $this->getDefaultTemplateMsg($template_sn)) {
return ['status' => -1, 'msg' => '消息模板不存在'];
}
$tpl_msg = WxTplMsg::get(['template_sn' => $template_sn, 'is_use' => 1]);
if ( ! $tpl_msg || ! $tpl_msg->template_id) {
return ['status' => -1, 'msg' => '消息模板未开启'];
}
$user = Db::name('oauth_users')->where(['user_id' => $order['user_id'], 'oauth' => 'weixin', 'oauth_child' => 'mp'])->find();
if ( ! $user || ! $user['openid']) {
return ['status' => -1, 'msg' => '用户不存在或不是微信用户'];
}
$store_name = tpCache('shop_info.store_name');
$data = [
'first' => ['value' => '订单支付成功!'],
'keyword1' => ['value' => $order['order_sn']],
'keyword2' => ['value' => '已支付'],
'keyword3' => ['value' => date('Y-m-d H:i', $order['pay_time'])],
'keyword4' => ['value' => $store_name],
'keyword5' => ['value' => $order['order_amount']],
'remark' => ['value' => $tpl_msg->remark ?: ''],
];
$url = SITE_URL.url('/mobile/order/order_detail?id='.$order['order_id']);
$return = self::$wechat_obj->sendTemplateMsg($user['openid'], $tpl_msg->template_id, $url, $data);
if ($return === false) {
return ['status' => -1, 'msg' => self::$wechat_obj->getError()];
}
return ['status' => 1, 'msg' => '发送模板消息成功'];
}
/**
* 发送模板消息(订单发货通知)
* @param $deliver array 物流信息
*/
public function sendTemplateMsgOnDeliver($deliver)
{
if ( ! $deliver) {
return ['status' => -1, 'msg' => '订单物流不存在'];
}
$template_sn = 'OPENTM202243318';
if ( ! $this->getDefaultTemplateMsg($template_sn)) {
return ['status' => -1, 'msg' => '消息模板不存在'];
}
$tpl_msg = WxTplMsg::get(['template_sn' => $template_sn, 'is_use' => 1]);
if ( ! $tpl_msg || ! $tpl_msg->template_id) {
return ['status' => -1, 'msg' => '消息模板未开启'];
}
$user = Db::name('oauth_users')->where(['user_id' => $deliver['user_id'], 'oauth' => 'weixin', 'oauth_child' => 'mp'])->find();
if ( ! $user || ! $user['openid']) {
return ['status' => -1, 'msg' => '用户不存在或不是微信用户'];
}
// 收货地址
$province = getRegionName($deliver['province']);
$city = getRegionName($deliver['city']);
$district = getRegionName($deliver['district']);
$full_address = $province.' '.$city.' '.$district.' '. $deliver['address'];
$order_goods = Db::name('order_goods')->where('order_id', $deliver['order_id'])->find();
$data = [
'first' => ['value' => "订单{$deliver['order_sn']}发货成功!"],
'keyword1' => ['value' => $order_goods['goods_name']],
'keyword2' => ['value' => $deliver['shipping_name']],
'keyword3' => ['value' => $deliver['delivery_sn']],
'keyword4' => ['value' => $full_address],
'remark' => ['value' => $tpl_msg->remark ?: ''],
];
$url = SITE_URL.url('/mobile/order/order_detail?id='.$deliver['order_id']);
$return = self::$wechat_obj->sendTemplateMsg($user['openid'], $tpl_msg->template_id, $url, $data);
if ($return === false) {
return ['status' => -1, 'msg' => self::$wechat_obj->getError()];
}
return ['status' => 1, 'msg' => '发送模板消息成功'];
}
/**
* 图片插件中展示的列表
* @param $size int 拉取多少
* @param $start int 开始位置
* @return string
*/
public function getPluginImages($size, $start = 0)
{
$data = self::$wechat_obj->getMaterialList('image', $size * $start, $size);
if ($data === false) {
return json_encode([
"state" => self::$wechat_obj->getError(),
"list" => [],
"start" => $start,
"total" => 0
]);
}
$list = [];
foreach ($data['item'] as $item) {
$list[] = [
'url' => $item['url'],
'mtime' => $item['update_time'],
'name' => $item['name'],
];
}
return json_encode([
"state" => "no match file",
"list" => $list,
"start" => $start,
"total" => $data['total_count']
]);
}
/**
* 修正关键字
* @param $keywords
* @return array
*/
private function trimKeywords($keywords)
{
$keywords = explode(',', $keywords);
$keywords = array_map('trim', $keywords);
$keywords = array_unique($keywords);
foreach ($keywords as $k => $keyword) {
if (!$keyword) {
unset($keywords[$k]);
}
}
return array_values($keywords);
}
/**
* 更新关键字
* @param $reply_id int 回复id
* @param $wx_keywords WxKeyword[]
* @param $keywords array 关键字数组
*/
private function updateKeywords($reply_id, $wx_keywords, $keywords)
{
$wx_keywords = convert_arr_key($wx_keywords, 'keyword');
//先删除不存在的keyword
foreach ($wx_keywords as $key => $word) {
if (!in_array($key, $keywords)) {
$word->delete();
unset($wx_keywords[$key]);
}
}
//创建要设置的keyword
foreach ($keywords as $keyword) {
if (!isset($wx_keywords[$keyword])) {
WxKeyword::create([
'keyword' => $keyword,
'pid' => $reply_id,
'type' => WxKeyword::TYPE_AUTO_REPLY
]);
}
}
}
/**
* 检查文本自动回复表单
*/
private function checkTextAutoReplyForm(&$data)
{
if ($data['type'] == WxReply::TYPE_KEYWORD) {
$rules = [
['type', 'require', '回复类型必需'],
['keywords','require','关键词必填'],
['rule','require|max:32','规则名必填|规则名最多32字'],
['content','require|max:600','文本内容必填|文本内容最多600字'],
];
} else {
$rules = [
['type', 'require', '回复类型必需'],
['content','max:600','文本内容最多600字'],
];
}
$validate = new Validate($rules);
if (!$validate->check($data)) {
return ['status' => -1, 'msg' => $validate->getError()];
}
if ( ! key_exists($data['type'], WxReply::getAllType())) {
return ['status' => -1, 'msg' => '回复类型不存在'];
}
if ($data['type'] == WxReply::TYPE_KEYWORD) {
if (!$data['keywords'] = $this->trimKeywords($data['keywords'])) {
return ['status' => -1, 'msg' => '关键字不存在'];
}
}
return ['status' => 1, 'msg' => '检查成功'];
}
/**
* 添加文本自动回复
*/
public function addTextAutoReply($data)
{
$return = $this->checkTextAutoReplyForm($data);
if ($return['status'] != 1) {
return $return;
}
if ($data['type'] == WxReply::TYPE_KEYWORD) {
if (WxKeyword::get(['keyword' => ['in', $data['keywords']], 'type' => WxKeyword::TYPE_AUTO_REPLY])) {
return ['status' => -1, 'msg' => '有关键字被其他规则使用'];
}
}
$reply = WxReply::create([
'rule' => $data['rule'],
'update_time' => time(),
'type' => $data['type'],
'msg_type' => WxReply::MSG_TEXT,
'data' => $data['content'],
]);
if ($data['type'] == WxReply::TYPE_KEYWORD) {
foreach ($data['keywords'] as $keyword) {
WxKeyword::create([
'keyword' => $keyword,
'pid' => $reply->id,
'type' => WxKeyword::TYPE_AUTO_REPLY
]);
}
}
return ['status' => 1, 'msg' => '添加成功'];
}
/**
* 更新文本自动回复
* @param $reply_id int 回复id
* @param $data array
* @return array
*/
public function updateTextAutoReply($reply_id, $data)
{
$return = $this->checkTextAutoReplyForm($data);
if ($return['status'] != 1) {
return $return;
}
$with = ($data['type'] == WxReply::TYPE_KEYWORD) ? 'wxKeywords' : [];
if (!$reply = WxReply::get(['id' => $reply_id], $with)) {
return ['status' => -1, 'msg' => '该自动回复不存在'];
}
if ($data['type'] == WxReply::TYPE_KEYWORD) {
$keyword_ids = get_arr_column($reply->wx_keywords, 'id');
if (WxKeyword::all(['keyword' => ['in', $data['keywords']], 'type' => WxKeyword::TYPE_AUTO_REPLY, 'id' => ['not in', $keyword_ids]])) {
return ['status' => -1, 'msg' => '有关键字被其他规则使用'];
}
$this->updateKeywords($reply_id, $reply->wx_keywords, $data['keywords']);
}
$reply->save([
'rule' => $data['rule'],
'update_time' => time(),
'data' => $data['content'],
'material_id' => 0,
'msg_type' => WxReply::MSG_TEXT
]);
return ['status' => 1, 'msg' => '更新成功'];
}
/**
* 检查文本自动回复表单
*/
private function checkNewsAutoReplyForm(&$data)
{
if ($data['type'] == WxReply::TYPE_KEYWORD) {
$rules = [
['keywords','require','关键词必填'],
['rule','require|max:32','规则名必填|规则名最多32字'],
['type', 'require', '回复类型必需'],
['material_id','require','关联素材id必需'],
];
} else {
$rules = [
['type', 'require', '回复类型必需'],
['material_id','require','关联素材id必需'],
];
}
$validate = new Validate($rules);
if (!$validate->check($data)) {
return ['status' => -1, 'msg' => $validate->getError()];
}
if ($data['type'] == WxReply::TYPE_KEYWORD) {
if (!$data['keywords'] = $this->trimKeywords($data['keywords'])) {
return ['status' => -1, 'msg' => '关键字不存在'];
}
}
if (!WxMaterial::get(['id' => $data['material_id'], 'type' => WxMaterial::TYPE_NEWS])) {
return ['status' => -1, 'msg' => '关联图文素材不存在'];
}
return ['status' => 1, 'msg' => '检查成功'];
}
/**
* 新增图文自动回复
*/
public function addNewsAutoReply($data)
{
$return = $this->checkNewsAutoReplyForm($data);
if ($return['status'] != 1) {
return $return;
}
if ($data['type'] == WxReply::TYPE_KEYWORD) {
if (WxKeyword::get(['keyword' => ['in', $data['keywords']], 'type' => WxKeyword::TYPE_AUTO_REPLY])) {
return ['status' => -1, 'msg' => '有关键字被其他规则使用'];
}
}
$reply = WxReply::create([
'rule' => $data['rule'],
'update_time' => time(),
'type' => $data['type'],
'msg_type' => WxReply::MSG_NEWS,
'material_id' => $data['material_id'],
]);
if ($data['type'] == WxReply::TYPE_KEYWORD) {
foreach ($data['keywords'] as $keyword) {
WxKeyword::create([
'keyword' => $keyword,
'pid' => $reply->id,
'type' => WxKeyword::TYPE_AUTO_REPLY
]);
}
}
return ['status' => 1, 'msg' => '添加成功'];
}
/**
* 更新图文自动回复
* @param $reply_id int 回复id
* @param $data array
* @return array
*/
public function updateNewsAutoReply($reply_id, $data)
{
$return = $this->checkNewsAutoReplyForm($data);
if ($return['status'] != 1) {
return $return;
}
$with = ($data['type'] == WxReply::TYPE_KEYWORD) ? 'wxKeywords' : [];
if (!$reply = WxReply::get(['id' => $reply_id], $with)) {
return ['status' => -1, 'msg' => '该自动回复不存在'];
}
if ($data['type'] == WxReply::TYPE_KEYWORD) {
$keyword_ids = get_arr_column($reply->wx_keywords, 'id');
if (WxKeyword::all(['keyword' => ['in', $data['keywords']], 'type' => WxKeyword::TYPE_AUTO_REPLY, 'id' => ['not in', $keyword_ids]])) {
return ['status' => -1, 'msg' => '有关键字被其他规则使用'];
}
$this->updateKeywords($reply_id, $reply->wx_keywords, $data['keywords']);
}
$reply->save([
'rule' => $data['rule'],
'update_time' => time(),
'material_id' => $data['material_id'],
'msg_type' => WxReply::MSG_NEWS,
'data' => '',
]);
return ['status' => 1, 'msg' => '更新成功'];
}
/**
* 添加自动回复
*/
public function addAutoReply($type, $data)
{
if ($type == 'text') {
return $this->addTextAutoReply($data);
} elseif ($type == 'news') {
return $this->addNewsAutoReply($data);
} else {
return ['status' => -1, 'msg' => '自动回复类型不存在'];
}
}
/**
* 更新自动回复
*/
public function updateAutoReply($type, $reply_id, $data)
{
if ($type == 'text') {
return $this->updateTextAutoReply($reply_id, $data);
} elseif ($type == 'news') {
return $this->updateNewsAutoReply($reply_id, $data);
} else {
return ['status' => -1, 'msg' => '自动回复类型不存在'];
}
}
/**
* 删除自动回复
*/
public function deleteAutoReply($reply_id)
{
if (!$reply = WxReply::get(['id' => $reply_id])) {
return ['status' => -1, 'msg' => '该自动回复不存在'];
}
if ($reply->type == WxReply::TYPE_KEYWORD) {
WxKeyword::where(['pid' => $reply_id])->delete();
}
$reply->delete();
return ['status' => 1, 'msg' => '删除成功'];
}
}