[fastadmin] 第四十八篇 FastAdmin 实现微信H5网页分享(自定义标题、描述、图片)
FastAdmin 实现微信H5网页分享(自定义标题、描述、图片)
在移动互联网时代,微信分享功能已成为H5应用不可或缺的功能之一。通过自定义分享的标题、描述和图片,可以大大提升用户的分享体验和内容传播效果。本文将详细介绍如何基于FastAdmin框架实现微信H5网页的自定义分享功能。
先看案例
网页内打开分享
分享到聊天框
分享到朋友圈
技术架构
- 后端框架:FastAdmin (基于ThinkPHP)
- 前端技术:Vue.js + 微信JS-SDK
- 微信接口:JS-SDK 分享接口
第一部分:FastAdmin 后端代码编写
1. 创建微信配置控制器
首先,我们需要创建一个专门处理微信JS-SDK配置的控制器。
创建文件:application/api/controller/Wechat.php
<?php
namespace app\api\controller;
use app\common\controller\Api;
use think\Cache;
use think\Log;
use think\Request;
use think\Config;
/**
* 微信JS-SDK配置接口
*/
class Wechat extends Api
{
protected $noNeedLogin = ['config'];
protected $noNeedRight = ['config'];
private $config;
public function _initialize()
{
parent::_initialize();
$this->config = Config::get('wechat');
}
/**
* 获取微信JS-SDK配置
*/
public function config()
{
$url = $this->request->post('url', '');
if (empty($url)) {
$this->error('缺少必要参数: url');
}
if (!filter_var($url, FILTER_VALIDATE_URL)) {
$this->error('无效的URL格式');
}
$accessToken = $this->getAccessToken();
if (!$accessToken) {
$this->error('获取access_token失败');
}
$ticket = $this->getJsapiTicket($accessToken);
if (!$ticket) {
$this->error('获取jsapi_ticket失败');
}
$nonceStr = $this->generateNonceStr();
$timestamp = time();
$signature = $this->generateSignature($ticket, $nonceStr, $timestamp, $url);
$result = [
'appId' => $this->config['app_id'],
'timestamp' => $timestamp,
'nonceStr' => $nonceStr,
'signature' => $signature
];
$this->success('获取成功', $result);
}
/**
* 获取access_token
*/
private function getAccessToken()
{
$cacheKey = 'wechat_access_token';
$accessToken = Cache::get($cacheKey);
if ($accessToken) {
Log::info('使用缓存的access_token: ' . $accessToken);
return $accessToken;
}
$url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$this->config['app_id']}&secret={$this->config['app_secret']}";
$response = $this->httpGet($url);
if (!$response) {
Log::error('获取access_token网络请求失败');
return false;
}
$data = json_decode($response, true);
if (!$data || !isset($data['access_token'])) {
Log::error('获取access_token失败: ' . $response);
return false;
}
// 缓存access_token(提前5分钟过期)
Cache::set($cacheKey, $data['access_token'], $data['expires_in'] - 300);
Log::info('获取新的access_token: ' . $data['access_token']);
return $data['access_token'];
}
/**
* 获取jsapi_ticket
*/
private function getJsapiTicket($accessToken)
{
$cacheKey = 'wechat_jsapi_ticket';
$ticket = Cache::get($cacheKey);
if ($ticket) {
Log::info('使用缓存的jsapi_ticket: ' . $ticket);
return $ticket;
}
$url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token={$accessToken}&type=jsapi";
$response = $this->httpGet($url);
if (!$response) {
Log::error('获取jsapi_ticket网络请求失败');
return false;
}
$data = json_decode($response, true);
if (!$data || !isset($data['ticket'])) {
Log::error('获取jsapi_ticket失败: ' . $response);
return false;
}
// 缓存jsapi_ticket(提前5分钟过期)
Cache::set($cacheKey, $data['ticket'], $data['expires_in'] - 300);
Log::info('获取新的jsapi_ticket: ' . $data['ticket']);
return $data['ticket'];
}
/**
* 生成签名
*/
private function generateSignature($ticket, $nonceStr, $timestamp, $url)
{
$string = "jsapi_ticket={$ticket}&noncestr={$nonceStr}×tamp={$timestamp}&url={$url}";
$signature = sha1($string);
Log::info('签名字符串: ' . $string);
Log::info('生成签名: ' . $signature);
return $signature;
}
/**
* 生成随机字符串
*/
private function generateNonceStr($length = 16)
{
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$str = '';
for ($i = 0; $i < $length; $i++) {
$str .= $chars[mt_rand(0, strlen($chars) - 1)];
}
return $str;
}
/**
* HTTP GET请求
*/
private function httpGet($url)
{
$curl = curl_init();
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_TIMEOUT, 30);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($curl, CURLOPT_URL, $url);
$response = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
if ($httpCode !== 200) {
Log::error("HTTP请求失败,状态码: {$httpCode}");
return false;
}
return $response;
}
}
2. 创建微信配置文件
创建文件:application/extra/wechat.php
<?php
return [
// 微信公众号配置
'app_id' => 'wx12345def', // 替换为你的微信公众号AppID
'app_secret' => 'your_app_secret_here', // 替换为你的微信公众号AppSecret
];
3. 配置路由
编辑文件:application/api/route.php
<?php
use think\Route;
// 微信相关接口
Route::group('wechat', function () {
Route::post('config', 'api/Wechat/config');
});
// 或者直接配置
Route::post('api/wechat/config', 'api/Wechat/config');
4. 配置跨域(如需要)
编辑文件:application/api/behavior/CORS.php
<?php
namespace app\api\behavior;
class CORS
{
public function appInit(&$params)
{
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Headers: Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-Requested-With');
header('Access-Control-Allow-Methods: GET, POST, PATCH, PUT, DELETE');
header('Access-Control-Max-Age: 1728000');
if (request()->isOptions()) {
exit();
}
}
}
第二部分:H5 前端代码编写
1. 引入微信JS-SDK
在HTML页面中引入微信JS-SDK:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>微信分享测试</title>
</head>
<body>
<!-- 页面内容 -->
<!-- 微信JS-SDK -->
<script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
<!-- 你的业务代码 -->
<script src="./js/wechat-share.js"></script>
</body>
</html>
2. 创建微信分享工具类
创建文件:js/wechat-share.js
/**
* 微信分享工具类
*/
class WechatShare {
constructor() {
this.isConfigured = false;
this.shareConfig = {};
}
/**
* 检查是否在微信环境
*/
static isWechat() {
const ua = navigator.userAgent.toLowerCase();
return ua.includes('micromessenger');
}
/**
* 获取微信JS-SDK配置
*/
static async getWechatConfig(url) {
try {
const response = await fetch('/api/wechat/config', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ url })
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const result = await response.json();
if (result.code !== 1) {
throw new Error(result.msg || '获取微信配置失败');
}
return result.data;
} catch (error) {
console.error('获取微信配置失败:', error);
throw error;
}
}
/**
* 配置微信JS-SDK
*/
async config(config) {
return new Promise((resolve, reject) => {
wx.config({
debug: false, // 生产环境设为false
appId: config.appId,
timestamp: config.timestamp,
nonceStr: config.nonceStr,
signature: config.signature,
jsApiList: [
'updateAppMessageShareData', // 新版分享到朋友
'updateTimelineShareData', // 新版分享到朋友圈
'onMenuShareAppMessage', // 旧版分享到朋友(兼容)
'onMenuShareTimeline' // 旧版分享到朋友圈(兼容)
]
});
wx.ready(() => {
console.log('微信JS-SDK配置成功');
this.isConfigured = true;
this.setupShare();
resolve(true);
});
wx.error((res) => {
console.error('微信JS-SDK配置失败:', res);
reject(res);
});
});
}
/**
* 设置分享配置
*/
setShareConfig(config) {
this.shareConfig = {
title: config.title || document.title,
desc: config.desc || document.querySelector('meta[name="description"]')?.content || '',
link: config.link || window.location.href,
imgUrl: config.imgUrl || this.getDefaultShareImage()
};
// 如果已经配置好了,立即设置分享
if (this.isConfigured) {
this.setupShare();
}
}
/**
* 获取默认分享图片
*/
getDefaultShareImage() {
// 尝试获取页面中的第一张图片
const firstImg = document.querySelector('img');
if (firstImg && firstImg.src) {
return firstImg.src;
}
// 或者返回网站logo
return `${window.location.origin}/static/images/logo.png`;
}
/**
* 设置分享内容
*/
setupShare() {
if (!this.isConfigured || !this.shareConfig.title) {
return;
}
console.log('设置分享内容:', this.shareConfig);
// 新版微信分享API
wx.updateAppMessageShareData({
title: this.shareConfig.title,
desc: this.shareConfig.desc,
link: this.shareConfig.link,
imgUrl: this.shareConfig.imgUrl,
success: () => {
console.log('分享到朋友设置成功');
},
fail: (res) => {
console.error('分享到朋友设置失败:', res);
}
});
wx.updateTimelineShareData({
title: this.shareConfig.title,
link: this.shareConfig.link,
imgUrl: this.shareConfig.imgUrl,
success: () => {
console.log('分享到朋友圈设置成功');
},
fail: (res) => {
console.error('分享到朋友圈设置失败:', res);
}
});
// 兼容旧版API
wx.onMenuShareAppMessage({
title: this.shareConfig.title,
desc: this.shareConfig.desc,
link: this.shareConfig.link,
imgUrl: this.shareConfig.imgUrl,
success: () => {
console.log('用户点击分享到朋友');
this.onShareSuccess('friend');
},
cancel: () => {
console.log('用户取消分享到朋友');
}
});
wx.onMenuShareTimeline({
title: this.shareConfig.title,
link: this.shareConfig.link,
imgUrl: this.shareConfig.imgUrl,
success: () => {
console.log('用户点击分享到朋友圈');
this.onShareSuccess('timeline');
},
cancel: () => {
console.log('用户取消分享到朋友圈');
}
});
}
/**
* 分享成功回调
*/
onShareSuccess(type) {
// 可以在这里添加分享成功的统计或其他逻辑
console.log(`分享成功: ${type}`);
// 发送分享统计到后端
this.sendShareAnalytics(type);
}
/**
* 发送分享统计
*/
async sendShareAnalytics(type) {
try {
await fetch('/api/analytics/share', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: type,
title: this.shareConfig.title,
url: this.shareConfig.link,
timestamp: Date.now()
})
});
} catch (error) {
console.error('发送分享统计失败:', error);
}
}
}
// 导出工具类
window.WechatShare = WechatShare;
3. 在页面中使用微信分享
创建文件:js/app.js
// 页面加载完成后初始化微信分享
document.addEventListener('DOMContentLoaded', async function() {
// 检查是否在微信环境
if (!WechatShare.isWechat()) {
console.log('非微信环境,跳过微信分享配置');
return;
}
// 创建微信分享实例
const wechatShare = new WechatShare();
try {
// 获取当前页面URL(去除hash部分)
const currentUrl = window.location.href.split('#')[0];
// 获取微信配置
console.log('正在获取微信配置...');
const config = await WechatShare.getWechatConfig(currentUrl);
// 配置微信JS-SDK
await wechatShare.config(config);
// 设置分享内容
wechatShare.setShareConfig({
title: '三个桃子·时间里的约定|「Epic Soul」人文阅读体 issue01',
desc: '每个生命,都有史诗般的灵魂。一颗从时间里走来的果实,在等时间里重逢的人',
link: 'https://p.test.com/?from=wechat_share',
imgUrl: 'https://p.test.com/images/share-cover.jpg'
});
console.log('微信分享配置完成');
} catch (error) {
console.error('微信分享初始化失败:', error);
}
});
// 动态更新分享内容的函数
function updateShareContent(title, desc, link, imgUrl) {
if (window.wechatShareInstance) {
window.wechatShareInstance.setShareConfig({
title,
desc,
link,
imgUrl
});
}
}
4. Vue.js 项目中的使用方式
如果你在Vue项目中使用,可以这样封装:
创建文件:src/utils/wechat.js
import WechatShare from './wechat-share.js';
export class WechatUtils {
constructor() {
this.wechatShare = null;
this.isInitialized = false;
}
async init() {
if (this.isInitialized) return;
if (!WechatShare.isWechat()) {
console.log('非微信环境');
return;
}
try {
this.wechatShare = new WechatShare();
const currentUrl = window.location.href.split('#')[0];
const config = await WechatShare.getWechatConfig(currentUrl);
await this.wechatShare.config(config);
this.isInitialized = true;
console.log('微信分享初始化成功');
} catch (error) {
console.error('微信分享初始化失败:', error);
}
}
setShare(shareConfig) {
if (this.wechatShare) {
this.wechatShare.setShareConfig(shareConfig);
}
}
}
export default new WechatUtils();
在Vue组件中使用:
<template>
<div class="page">
<h1>{{ pageTitle }}</h1>
<p>{{ pageDesc }}</p>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue';
import wechatUtils from '@/utils/wechat';
const pageTitle = ref('文章标题');
const pageDesc = ref('文章描述');
onMounted(async () => {
// 初始化微信分享
await wechatUtils.init();
// 设置分享内容
wechatUtils.setShare({
title: pageTitle.value,
desc: pageDesc.value,
link: window.location.href,
imgUrl: 'https://your-domain.com/share-image.jpg'
});
});
</script>
微信公众号配置
1. 配置JS接口安全域名
- 登录微信公众平台(https://mp.weixin.qq.com)
- 进入
设置
→公众号设置
→功能设置
- 在”JS接口安全域名”中添加你的域名:
p.test.com
- 下载验证文件并上传到网站根目录
- 点击确认完成配置
2. 获取AppID和AppSecret
- 在微信公众平台进入
开发
→基本配置
- 获取
开发者ID(AppID)
和开发者密码(AppSecret)
- 将这些信息配置到
application/extra/wechat.php
文件中
测试和部署
1. 本地开发测试
由于微信JS-SDK需要安全域名,本地开发时可以:
- 使用Mock数据:在开发环境返回假的配置数据
- 使用内网穿透:使用ngrok等工具将本地服务暴露到公网
- 部署到测试服务器:直接在线上环境测试
2. 生产环境部署
- 确保服务器SSL证书配置正确
- 配置正确的域名到微信公众号
- 检查服务器日志确认接口正常工作
- 在微信中测试分享功能
常见问题和解决方案
1. signature验证失败
原因:
- URL参数不正确(包含了#部分)
- 微信配置信息错误
- 时间戳或随机字符串生成有问题
解决方案:
// 确保URL不包含#部分
const url = window.location.href.split('#')[0];
2. 分享内容不显示
原因:
- 分享图片URL无法访问
- 分享链接格式不正确
- 微信缓存问题
解决方案:
// 确保图片URL是完整的绝对路径
imgUrl: 'https://your-domain.com/images/share.jpg'
// 确保链接是完整的URL
link: 'https://your-domain.com/page'
3. 接口调用失败
原因:
- 服务器网络问题
- 微信API调用频率限制
- AppID或AppSecret错误
解决方案:
- 检查服务器网络连接
- 实施合理的缓存机制
- 验证微信公众号配置
性能优化建议
1. 缓存机制
// 在控制器中实现更智能的缓存
private function getAccessToken()
{
$cacheKey = 'wechat_access_token';
$accessToken = Cache::get($cacheKey);
if ($accessToken) {
return $accessToken;
}
// 获取新token的逻辑...
// 缓存时间设置为实际过期时间减去5分钟
Cache::set($cacheKey, $token, $expiresIn - 300);
return $token;
}
2. 前端优化
// 避免重复初始化
let wechatShareInstance = null;
export const getWechatShare = async () => {
if (wechatShareInstance) {
return wechatShareInstance;
}
wechatShareInstance = new WechatShare();
await wechatShareInstance.init();
return wechatShareInstance;
};
总结
通过本文的详细介绍,我们完成了基于FastAdmin的微信H5分享功能实现:
- 后端部分:创建了微信配置API,处理access_token和jsapi_ticket的获取与缓存
- 前端部分:封装了微信分享工具类,支持自定义标题、描述和图片
- 配置部分:完成了微信公众号的相关配置
这套方案具有以下优点:
- 模块化设计:代码结构清晰,易于维护
- 缓存机制:避免频繁调用微信API
- 错误处理:完善的错误处理和日志记录
- 兼容性好:同时支持新旧版微信分享API
- 易于扩展:可以轻松添加更多微信功能
通过合理的缓存策略和错误处理,这套分享系统能够稳定运行,为用户提供良好的分享体验,助力内容传播和用户增长。
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: