视频号下载视频思路

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

一、核心逻辑与前提说明

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 跳转)

总结

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

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

相关推荐
AscendKing16 天前
centos修改jar下面的doc文件 虽然成功修改 但是不生效需要重启jar
jar·好好学电脑
AscendKing1 个月前
一款针对IT团队开发的简单好用的文档管理系统
开源·好好学电脑·hhxdn.com
AscendKing2 个月前
一个开源笔记和待办事项应用Joplin简介
好好学电脑·hhxdn.com
AscendKing2 个月前
一个开源的 LLM 应用开发平台
好好学电脑·hhxdn.com
AscendKing2 个月前
开源项目分享 图像深度学习Demo项目
开源·好好学电脑·hhxdn.com
AscendKing2 个月前
开源白板工具(SaaS),一体化白板,包含思维导图、流程图、自由画等
开源·流程图·好好学电脑·hhxdn.com
AscendKing2 个月前
6 个成熟的 JS 开源视频编辑项目
好好学电脑·hhxdn.com
AscendKing2 个月前
LandPPT - AI驱动的PPT生成平台
人工智能·好好学电脑·hhxdn.com
AscendKing2 个月前
一个开源免费的验证码系统简介 天爱验证码
开源·验证码·好好学电脑