视频号下载视频思路

要实现「个微登录企微后转发视频号 → 企微回调获取参数 → 解析视频链接 → 小程序推送」,核心是 适配企微外部联系人(个微登录场景)+ 回调消息解析 + 视频链接拼接 + 小程序消息推送 ,全程遵循企微官方接口规范,以下是 完整实现方案(含配置 + 代码 + 测试),聚焦「个微登录(外部联系人)」场景适配:

一、核心逻辑与前提说明

1. 完整流程(个微登录场景)

plaintext

复制代码
个微用户 → 添加企微外部联系人 → 转发视频号到企微聊天框 → 企微触发回调 → 后端解密消息提取 object_id/object_nonce_id → 调用视频号详情接口拼接播放/下载链接 → 后端调用企微发送小程序接口 → 个微用户收到小程序通知 → 点击小程序直接播放/下载视频

2. 关键前提(必须满足)

  • 企微已创建应用(需完成企业认证,个人未认证企业无法使用外部联系人功能);
  • 应用已开启「外部联系人」权限(允许接收个微用户的消息);
  • 企微已关联目标小程序(用于发送小程序消息);
  • 后端服务器支持 HTTPS(企微回调强制要求),公网可访问;
  • 个微用户已添加企微外部联系人(通过企微对外名片添加)。

二、第一步:企微后台核心配置(适配个微登录场景)

1. 应用基础配置(接收外部联系人消息)

登录企微管理后台 → 应用管理 → 选择你的应用(如「视频下载助手」):

(1)配置「接收消息」(回调核心)
  • 开启「接收消息」开关;
  • 选择「加密模式」(推荐,安全且兼容外部联系人消息);
  • 填写配置:
    • 回调 URL:https://你的公网域名/wechat/callback(HTTPS + 公网可达,路径与后端接口一致);
    • Token:自定义字符串(如 wxcp_video_2025),需与后端 application.yml 一致;
    • EncodingAESKey:点击「随机生成」,复制到后端配置(43 位字符,需完全一致);
  • 勾选「接收外部联系人消息」(关键!适配个微登录场景,否则收不到个微用户的转发消息)。
(2)配置「外部联系人权限」
  • 应用详情 → 权限管理 → 找到「外部联系人」相关权限:
    • 勾选「获取外部联系人基本信息」「接收外部联系人消息」「发送外部联系人消息」;
  • 应用可见范围:
    • 内部成员:添加企微内部成员(用于接收个微用户的转发消息);
    • 外部联系人:无需额外设置,个微用户添加企微外部联系人后即可互动。
(3)关联小程序(发送小程序消息必备)
  • 企微管理后台 → 应用管理 → 小程序 → 点击「关联小程序」;
  • 输入你的小程序 AppID → 小程序管理员在微信上确认授权;
  • 关联后,在「可见范围」中勾选「外部联系人」(允许个微用户接收该小程序消息)。

2. 验证回调有效性

  • 配置完成后,点击「回调验证」按钮,提示「验证成功」说明企微能正常访问你的后端;
  • 若验证失败:检查 HTTPS 证书是否有效、URL 是否公网可达、Token/AESKey 是否一致。

三、第二步:服务器 / 网络配置(确保企微能回调)

1. 本地开发(用 ngrok 穿透,无需云服务器)

  • 下载 ngrok:https://ngrok.com/
  • 执行命令:ngrok http 8080(生成临时 HTTPS 域名,如 https://abc123.ngrok.io);
  • 同步更新企微回调 URL 为 https://abc123.ngrok.io/wechat/callback
  • 保持 ngrok 终端开启(关闭后穿透失效)。

2. 线上部署(云服务器,如阿里云 / 腾讯云)

  • 配置安全组:放行 8080 端口(后端端口)和 443 端口(HTTPS);

  • 部署 HTTPS 证书:用 Let's Encrypt 免费证书或云厂商 SSL 证书,通过 Nginx 反向代理: nginx

    复制代码
    server {
      listen 443 ssl;
      server_name yourdomain.com; # 你的域名
    
      ssl_certificate /usr/local/nginx/conf/cert/yourdomain.pem; # 证书公钥
      ssl_certificate_key /usr/local/nginx/conf/cert/yourdomain.key; # 证书私钥
    
      location /wechat/callback {
        proxy_pass http://127.0.0.1:8080; # 转发到后端
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
      }
    }

三、第三步:后端代码实现(核心逻辑)

基于 Spring Boot + 企微 SDK 4.7.8-20251105.104605,适配外部联系人(个微登录)场景,核心实现「回调接收→参数提取→视频解析→小程序推送」。

1. 依赖配置(pom.xml)

xml

复制代码
<dependencies>
  <!-- Spring Boot核心 -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <!-- 企微SDK(支持外部联系人+小程序消息) -->
  <dependency>
    <groupId>com.github.binarywang</groupId>
    <artifactId>weixin-java-cp</artifactId>
    <version>4.7.8-20251105.104605</version>
  </dependency>
  <!-- HTTP客户端 -->
  <dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.11.0</version>
  </dependency>
  <!-- JSON解析 -->
  <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>2.0.32</version>
  </dependency>
  <!-- 日志 -->
  <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
  </dependency>
</dependencies>

<!-- 快照仓库(拉取企微SDK快照版) -->
<repositories>
  <repository>
    <id>sonatype-snapshots</id>
    <url>https://oss.sonatype.org/content/repositories/snapshots/</url>
    <snapshots>
      <enabled>true</enabled>
    </snapshots>
  </repository>
</repositories>

2. 配置文件(application.yml)

yaml

复制代码
wechat:
  cp:
    corp-id: 你的企微CorpID # 我的企业→企业信息
    corp-secret: 你的应用Secret # 应用管理→你的应用→Secret
    agent-id: 你的应用AgentID # 应用管理→你的应用→AgentID(整数)
    callback-token: 你的回调Token # 与企微后台一致
    aes-key: 你的EncodingAESKey # 与企微后台一致(43位)
  miniapp:
    appid: 你的小程序AppID # 小程序后台→开发→开发设置
    page-path: /pages/play/index # 小程序播放/下载页面路径
  channels:
    detail-api-url: "https://channels.weixin.qq.com/web/api/feed/detail?eid=export%{objectId}_%{nonceId}"

server:
  port: 8080
  servlet:
    context-path: /

3. 企微配置类(初始化 SDK)

java

运行

复制代码
import com.github.binarywang.weixin.cp.config.WxCpConfigStorage;
import com.github.binarywang.weixin.cp.config.impl.WxCpDefaultConfigImpl;
import com.github.binarywang.weixin.cp.service.WxCpService;
import com.github.binarywang.weixin.cp.service.impl.WxCpServiceImpl;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class WxCpConfig {

  @Value("${wechat.cp.corp-id}")
  private String corpId;

  @Value("${wechat.cp.corp-secret}")
  private String corpSecret;

  @Value("${wechat.cp.agent-id}")
  private Integer agentId;

  @Value("${wechat.cp.callback-token}")
  private String callbackToken;

  @Value("${wechat.cp.aes-key}")
  private String aesKey;

  @Bean
  public WxCpConfigStorage wxCpConfigStorage() {
    WxCpDefaultConfigImpl config = new WxCpDefaultConfigImpl();
    config.setCorpId(corpId);
    config.setCorpSecret(corpSecret);
    config.setAgentId(agentId);
    config.setToken(callbackToken);
    config.setAesKey(aesKey);
    config.setHttpConnectTimeoutMs(10000);
    config.setHttpReadTimeoutMs(30000);
    return config;
  }

  @Bean
  public WxCpService wxCpService(WxCpConfigStorage configStorage) {
    WxCpServiceImpl service = new WxCpServiceImpl();
    service.setWxCpConfigStorage(configStorage);
    // 启用外部联系人消息支持(适配个微登录场景)
    service.getExternalContactService();
    return service;
  }
}

4. 回调接口(接收企微消息,提取核心参数)

java

运行

复制代码
import com.github.binarywang.weixin.cp.bean.message.WxCpXmlMessage;
import com.github.binarywang.weixin.cp.bean.message.WxCpXmlOutMessage;
import com.github.binarywang.weixin.cp.service.WxCpService;
import com.google.common.base.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/wechat/callback")
public class WxCpCallbackController {

  private static final Logger log = LoggerFactory.getLogger(WxCpCallbackController.class);

  @Autowired
  private WxCpService wxCpService;

  @Autowired
  private VideoService videoService;

  /**
   * 企微回调验证(GET请求)
   */
  @GetMapping
  public String verifyCallback(
          @RequestParam("msg_signature") String msgSignature,
          @RequestParam("timestamp") String timestamp,
          @RequestParam("nonce") String nonce,
          @RequestParam("echostr") String echostr) {
    try {
      String decryptEchostr = wxCpService.decryptEchoStr(msgSignature, timestamp, nonce, echostr);
      log.info("回调验证成功:{}", decryptEchostr);
      return decryptEchostr;
    } catch (Exception e) {
      log.error("回调验证失败", e);
      return "验证失败";
    }
  }

  /**
   * 接收企微消息(POST请求,适配内部/外部联系人)
   */
  @PostMapping(produces = "application/xml;charset=UTF-8")
  public String receiveMessage(
          @RequestParam("msg_signature") String msgSignature,
          @RequestParam("timestamp") String timestamp,
          @RequestParam("nonce") String nonce,
          @RequestBody String requestBody) {
    try {
      log.info("接收企微消息:{}", requestBody);

      // 1. 解密消息
      WxCpXmlMessage inMessage = WxCpXmlMessage.fromXml(requestBody);
      inMessage = wxCpService.decryptMessage(inMessage, msgSignature, timestamp, nonce);
      log.info("解密后消息:{}", inMessage);

      // 2. 筛选视频号视频消息(msgType=sphfeed,feedType=4)
      if ("sphfeed".equals(inMessage.getMsgType()) && "4".equals(inMessage.getFeedType())) {
        String objectId = inMessage.getObjectId();
        String nonceId = inMessage.getObjectNonceId();
        String receiverId = inMessage.getToUser(); // 接收人ID(内部成员userId/外部联系人openId)
        String senderId = inMessage.getFromUser(); // 发送人ID(个微用户=openId,企微用户=userId)

        // 校验参数
        if (Strings.isNullOrEmpty(objectId) || Strings.isNullOrEmpty(nonceId)) {
          log.error("参数缺失:objectId={}, nonceId={}", objectId, nonceId);
          return buildReply("获取视频信息失败");
        }

        // 3. 解析视频链接并发送小程序消息
        String videoUrl = videoService.parseVideoUrl(objectId, nonceId);
        if (videoUrl != null) {
          // 适配外部联系人:发送给个微用户(senderId是外部联系人openId)
          videoService.sendMiniProgramMessage(senderId, videoUrl);
          return buildReply("视频解析成功,小程序通知已发送~");
        } else {
          return buildReply("视频解析失败,请稍后重试");
        }
      }

      return buildReply("暂不支持该类型消息");
    } catch (Exception e) {
      log.error("处理消息异常", e);
      return buildReply("处理失败");
    }
  }

  /**
   * 构建企微回复消息
   */
  private String buildReply(String content) {
    WxCpXmlOutMessage outMessage = WxCpXmlOutMessage.TEXT()
            .content(content)
            .fromUser(wxCpService.getWxCpConfigStorage().getCorpId())
            .toUser("")
            .build();
    return outMessage.toXml();
  }
}

5. 视频解析与小程序推送服务

java

运行

复制代码
import com.alibaba.fastjson.JSONObject;
import com.github.binarywang.weixin.cp.bean.WxCpMessage;
import com.github.binarywang.weixin.cp.service.WxCpService;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.net.URLEncoder;
import java.util.concurrent.TimeUnit;

@Service
public class VideoService {

  private static final Logger log = LoggerFactory.getLogger(VideoService.class);
  private final OkHttpClient okHttpClient;

  @Autowired
  private WxCpService wxCpService;

  @Value("${wechat.channels.detail-api-url}")
  private String videoDetailApiUrl;

  @Value("${wechat.miniapp.appid}")
  private String miniAppId;

  @Value("${wechat.miniapp.page-path}")
  private String miniPagePath;

  public VideoService() {
    this.okHttpClient = new OkHttpClient.Builder()
            .connectTimeout(10, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS)
            .retryOnConnectionFailure(true)
            .build();
  }

  /**
   * 调用视频号详情接口,拼接播放/下载链接(url + urlToken)
   */
  public String parseVideoUrl(String objectId, String nonceId) {
    try {
      // 1. 拼接视频号详情接口URL
      String apiUrl = videoDetailApiUrl
              .replace("{objectId}", objectId)
              .replace("{nonceId}", nonceId);
      log.info("调用视频号详情接口:{}", apiUrl);

      // 2. 模拟浏览器请求头(避免被拦截)
      Request request = new Request.Builder()
              .url(apiUrl)
              .header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
              .header("Referer", "https://channels.weixin.qq.com/")
              .header("Accept", "application/json, text/plain, */*")
              .build();

      Response response = okHttpClient.newCall(request).execute();
      if (!response.isSuccessful()) {
        log.error("接口调用失败,状态码:{}", response.code());
        return null;
      }

      // 3. 解析响应,提取url和urlToken
      String responseBody = response.body().string();
      JSONObject result = JSONObject.parseObject(responseBody);
      JSONObject data = result.getJSONObject("data");
      if (data == null) {
        log.error("响应无data字段:{}", responseBody);
        return null;
      }

      JSONObject objectDesc = data.getJSONObject("object_desc");
      JSONObject media = objectDesc.getJSONArray("media").getJSONObject(0);
      String baseUrl = media.getString("url");
      String urlToken = media.getString("url_token");

      // 4. 拼接完整播放/下载链接(直接访问可播放/下载)
      String videoUrl = baseUrl + (baseUrl.contains("?") ? "&token=" : "?token=") + urlToken;
      log.info("视频链接拼接成功:{}", videoUrl);
      return videoUrl;
    } catch (Exception e) {
      log.error("解析视频链接失败", e);
      return null;
    }
  }

  /**
   * 调用企微接口,发送小程序消息(适配内部/外部联系人)
   */
  public void sendMiniProgramMessage(String receiverId, String videoUrl) {
    try {
      // URL编码视频链接(避免特殊字符截断)
      String encodedVideoUrl = URLEncoder.encode(videoUrl, "UTF-8");
      // 小程序页面路径(携带视频链接参数)
      String finalPagePath = miniPagePath + "?url=" + encodedVideoUrl;

      // 构建企微小程序消息
      WxCpMessage message = WxCpMessage.MINIPROGRAM_NOTICE()
              .agentId(wxCpService.getWxCpConfigStorage().getAgentId())
              .toUser(receiverId) // 接收人ID:内部成员=userId,外部联系人=openId
              .title("视频播放通知")
              .appId(miniAppId) // 关联的小程序AppID
              .page(finalPagePath) // 小程序页面(含视频链接)
              .description("你转发的视频已解析完成,点击直接播放/下载")
              .build();

      // 发送消息(企微SDK自动处理access_token,支持外部联系人)
      wxCpService.messageSend(message);
      log.info("已向{}发送小程序消息,页面路径:{}", receiverId, finalPagePath);
    } catch (Exception e) {
      log.error("发送小程序消息失败", e);
    }
  }
}

四、第四步:小程序页面开发(播放 / 下载视频)

小程序需创建「播放 / 下载页面」,接收后端传递的视频链接,实现直接播放和下载功能,示例代码如下:

1. 页面结构(pages/play/index.wxml)

xml

复制代码
<view class="container">
  <view class="title">视频播放/下载</view>
  <!-- 视频播放组件(直接播放拼接后的链接) -->
  <video 
    src="{{videoUrl}}" 
    class="video-player"
    controls
    enable-play-gesture
  ></video>
  <!-- 下载按钮 -->
  <button bindtap="downloadVideo" class="download-btn">
    保存视频到手机
  </button>
</view>

2. 页面样式(pages/play/index.wxss)

css

复制代码
.container {
  padding: 20rpx;
  box-sizing: border-box;
}

.title {
  font-size: 32rpx;
  font-weight: bold;
  margin-bottom: 20rpx;
  text-align: center;
}

.video-player {
  width: 100%;
  height: 400rpx;
  background-color: #f5f5f5;
  border-radius: 16rpx;
  margin-bottom: 30rpx;
}

.download-btn {
  background-color: #2f54eb;
  color: white;
  font-size: 30rpx;
  border-radius: 8rpx;
}

3. 页面逻辑(pages/play/index.js)

javascript

运行

复制代码
Page({
  data: {
    videoUrl: '' // 接收后端传递的视频链接
  },

  onLoad(options) {
    // 接收并解码视频链接(后端已URL编码)
    const videoUrl = decodeURIComponent(options.url);
    this.setData({ videoUrl });
    console.log("视频链接:", videoUrl);
  },

  // 下载视频到手机
  downloadVideo() {
    const { videoUrl } = this.data;
    wx.showLoading({ title: '下载中...' });

    // 调用微信下载API(需配置小程序download合法域名,或用web-view跳转)
    wx.downloadFile({
      url: videoUrl,
      filePath: `${wx.env.USER_DATA_PATH}/video_${Date.now()}.mp4`,
      success: (res) => {
        if (res.statusCode === 200) {
          // 下载成功后保存到相册
          wx.saveVideoToPhotosAlbum({
            filePath: res.filePath,
            success: () => {
              wx.hideLoading();
              wx.showToast({ title: '下载成功!已保存到相册' });
            },
            fail: (err) => {
              wx.hideLoading();
              wx.showToast({ title: '保存失败,请授权相册权限', icon: 'none' });
              console.error("保存失败:", err);
            }
          });
        }
      },
      fail: (err) => {
        wx.hideLoading();
        wx.showToast({ title: '下载失败,请重试', icon: 'none' });
        console.error("下载失败:", err);
      }
    });
  }
});

4. 小程序配置(app.json)

json

复制代码
{
  "pages": [
    "pages/play/index" // 新增播放页面路径
  ],
  "window": {
    "backgroundTextStyle": "light",
    "navigationBarBackgroundColor": "#fff",
    "navigationBarTitleText": "视频助手",
    "navigationBarTextStyle": "black"
  },
  "sitemapLocation": "sitemap.json"
}

5. 小程序发布

  • 用微信开发者工具上传代码(版本号如 1.0.0);
  • 小程序后台 → 版本管理 → 提交审核(个人主体 1-2 小时通过);
  • 审核通过后发布「线上版本」(用户可正常访问)。

五、第五步:测试流程(验证完整功能)

  1. 准备工作

    • 个微用户添加企微外部联系人(通过企微对外名片);
    • 启动后端应用,确保 ngrok 穿透正常(本地开发)或云服务器部署成功;
    • 企微后台确认应用回调验证成功。
  2. 测试步骤

    • 个微用户打开视频号,选择任意视频 → 转发 → 选择企微外部联系人;
    • 后端日志打印「接收企微消息」「解密后消息」「视频链接拼接成功」;
    • 个微用户收到企微的「小程序通知」(标题 "视频播放通知");
    • 点击通知进入小程序,视频自动加载播放,点击「保存视频到手机」可下载到相册。

六、关键适配与问题排查

1. 个微登录(外部联系人)场景适配

  • 确保企微应用已开启「外部联系人」权限(应用管理→权限管理);
  • 发送小程序消息时,receiverId 是外部联系人的 openId(回调消息的 fromUser),企微 API 支持给外部联系人发送小程序消息;
  • 若发送失败:检查应用「可见范围」是否包含外部联系人,或外部联系人是否已授权消息接收。

2. 常见问题排查

问题现象 原因 解决方案
收不到回调消息 回调 URL 不是 HTTPS / 公网不可达 用 ngrok 穿透或配置云服务器 HTTPS
解析不到 objectId/nonceId 消息类型不是 sphfeed 或 feedType≠4 确认转发的是视频号视频(非其他类型)
视频链接无法播放 链接拼接错误 / 视频号接口调整 打印视频号接口响应,检查 url 和 urlToken 字段
小程序消息发送失败 企微未关联小程序 企微后台→应用管理→小程序→关联目标小程序
小程序无法下载视频 未配置 download 合法域名 小程序后台→开发→服务器域名→添加视频链接的域名(或用 web-view 跳转)

总结

核心实现逻辑:企微回调接收外部联系人(个微)的视频号消息 → 提取参数解析视频链接 → 调用企微小程序消息接口推送 → 小程序承载播放 / 下载。关键配置是企微应用的回调、外部联系人权限、小程序关联,代码核心是回调消息解密、视频链接拼接、小程序消息推送,全程适配个微登录场景,确保用户操作流程简洁(转发→收通知→播放 / 下载)。

按以上步骤配置后,即可实现 "个微转发视频号到企微 → 自动推送小程序播放 / 下载链接" 的完整功能,无需用户额外操作,体验流畅。

相关推荐
AscendKing3 天前
LandPPT - AI驱动的PPT生成平台
人工智能·好好学电脑·hhxdn.com
AscendKing11 天前
一个开源免费的验证码系统简介 天爱验证码
开源·验证码·好好学电脑