[fastadmin] 第四十八篇 FastAdmin 实现微信H5网页分享(自定义标题、描述、图片)

FastAdmin 实现微信H5网页分享(自定义标题、描述、图片)

在移动互联网时代,微信分享功能已成为H5应用不可或缺的功能之一。通过自定义分享的标题、描述和图片,可以大大提升用户的分享体验和内容传播效果。本文将详细介绍如何基于FastAdmin框架实现微信H5网页的自定义分享功能。

先看案例

网页内打开分享
[fastadmin] 第四十八篇 FastAdmin 实现微信H5网页分享(自定义标题、描述、图片)

分享到聊天框

[fastadmin] 第四十八篇 FastAdmin 实现微信H5网页分享(自定义标题、描述、图片)

分享到朋友圈

[fastadmin] 第四十八篇 FastAdmin 实现微信H5网页分享(自定义标题、描述、图片)

[fastadmin] 第四十八篇 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}&timestamp={$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接口安全域名

  1. 登录微信公众平台(https://mp.weixin.qq.com
  2. 进入 设置公众号设置功能设置
  3. 在”JS接口安全域名”中添加你的域名:p.test.com
  4. 下载验证文件并上传到网站根目录
  5. 点击确认完成配置

2. 获取AppID和AppSecret

  1. 在微信公众平台进入 开发基本配置
  2. 获取 开发者ID(AppID)开发者密码(AppSecret)
  3. 将这些信息配置到 application/extra/wechat.php 文件中

测试和部署

1. 本地开发测试

由于微信JS-SDK需要安全域名,本地开发时可以:

  1. 使用Mock数据:在开发环境返回假的配置数据
  2. 使用内网穿透:使用ngrok等工具将本地服务暴露到公网
  3. 部署到测试服务器:直接在线上环境测试

2. 生产环境部署

  1. 确保服务器SSL证书配置正确
  2. 配置正确的域名到微信公众号
  3. 检查服务器日志确认接口正常工作
  4. 在微信中测试分享功能

常见问题和解决方案

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分享功能实现:

  1. 后端部分:创建了微信配置API,处理access_token和jsapi_ticket的获取与缓存
  2. 前端部分:封装了微信分享工具类,支持自定义标题、描述和图片
  3. 配置部分:完成了微信公众号的相关配置

这套方案具有以下优点:

  • 模块化设计:代码结构清晰,易于维护
  • 缓存机制:避免频繁调用微信API
  • 错误处理:完善的错误处理和日志记录
  • 兼容性好:同时支持新旧版微信分享API
  • 易于扩展:可以轻松添加更多微信功能

通过合理的缓存策略和错误处理,这套分享系统能够稳定运行,为用户提供良好的分享体验,助力内容传播和用户增长。

本作品采用《CC 协议》,转载必须注明作者和本文链接
• 15年技术深耕:理论扎实 + 实战丰富,教学经验让复杂技术变简单 • 8年企业历练:不仅懂技术,更懂业务落地与项目实操 • 全栈服务力:技术培训 | 软件定制开发 | AI智能化升级 关注「上海PHP自学中心」获取实战干货
wangchunbo
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!
司机 @ 某医疗行业
文章
314
粉丝
353
喜欢
565
收藏
1135
排名:61
访问:12.6 万
私信
所有博文
社区赞助商