Skip to content

微信小程序嵌入 H5 登录对接文档

本文档面向第三方小程序开发团队,说明如何在微信小程序中通过 web-view 嵌入构播云 H5 直播间,并完成微信小程序登录对接。

约定:下文中 小程序端 指接入方微信小程序,平台方 指我方。

1. 方案概述

小程序嵌入 H5 时,H5 本身不处理登录,而是由小程序端完成微信登录后获取平台方 Token,再透传给 H5,使其打开即处于登录状态。

鉴权流程图

flowchart TD
    A[用户] --> B[小程序端]
    B --> C[授权前置页面,码和内容我方提供]
    C -->|允许授权| D[登录成功,跳转,根据分享码计算]
    C -->|拒绝授权| X[停止授权报错提示页面]

核心流程:

  1. 用户进入小程序登录页,携带直播间跳转参数:参数 aliasroomId等 (相关参数联系我方技术人员获取);
  2. 小程序端调用微信 uni.login 获取临时 code
  3. 小程序端调用平台接口,用 code 和小程序应用标识换取 OpenIdUnionId
  4. 小程序端上报用户信息,换取平台方 TokenExpiresAt
  5. 小程序端打开 web-view,加载 H5 直播间,并在 URL 中追加 tokenexpires_at
sequenceDiagram
    participant User as 用户
    participant Mini as 小程序端
    participant Wechat as 微信
    participant Server as 平台方 Lemon 接口
    participant H5 as 平台方 H5

    User->>Mini: 打开登录页,携带 alias / roomId
    Mini->>Wechat: uni.login 获取 code
    Wechat-->>Mini: 返回 code
    Mini->>Server: GET /sso/oauth/callback/wechat_mini
    Server-->>Mini: 返回 OpenId / UnionId
    Mini->>Server: PUT /sso/oauth/callback/wechat_mini/user_info
    Server-->>Mini: 返回 Token / ExpiresAt
    Mini->>H5: web-view 加载 H5 URL,追加 token / expires_at
    H5-->>User: 展示已登录直播间

2. 接入准备

配置项 示例 说明
MiniProgram_APP_NAME mini_xxx 平台方分配的小程序应用标识,联系平台方获取
H5 基础域名 https://example.xxx.com 平台方 H5 直播间域名

小程序端还需要确认:

  1. 小程序已具备微信登录能力,可调用 uni.login({ provider: 'weixin' })
  2. 小程序已配置合法 web-view 业务域名;
  3. 小程序中存在登录页与 H5 承载页:
  4. 登录页:参考实现为 /pages/transit/index,可按项目实际路由自定义;
  5. H5 承载页:参考实现为 /pages/live/webview,可按项目实际路由自定义。

3. 入口参数规则

登录页负责接收直播间参数,解析后组装 H5 直播间路径。

3.1 普通页面路径

/pages/transit/index?a={alias}&r={roomId}&inviteId={inviteId}
参数 必填 说明
a / alias 企业或直播间别名
r / roomId 直播间 ID
inviteId 邀请人 ID(字符串),用于 H5 继续透传

示例:

/pages/transit/index?a=demo&r=10001&inviteId=888

登录页解析后会生成 H5 相对路径:

/{alias}/{roomId}
/{alias}/{roomId}?inviteId={inviteId}

4. 登录页登录流程与接口对应关系

参考实现中,登录页由 pages/transit/index.vue 负责页面状态与路由处理,登录相关接口统一封装在 pages/transit/composables/useTransitAuth.js 中。流程和接口对应关系如下:

流程步骤 参考方法/组件 对应接口 成功处理 失败处理
解析入口参数 initTransit 得到 aliasroomIdinviteId,生成 targetPath 参数缺失时展示错误
判断是否可静默登录 checkSilentLogin lastTransitParams.aliasroomId 与本次一致时尝试静默登录 不一致时展示手动登录表单
静默登录 trySilentLogin 6.1 微信 code 换 OpenId / UnionId6.2 上报用户信息并换取 Token 缓存 lemon_tokenlemon_expires_at 后进入 H5 返回 false,降级展示手动登录表单
手动登录表单校验 LoginForm.vue 头像、昵称、协议均满足后触发 login 事件 提示用户补全头像/昵称或同意协议
手动登录 handleManualLogin 6.16.26.3 上传微信临时头像 换取 Token,必要时上传头像并二次更新用户信息 提示“登录失败,请重试”,停留在登录表单
记录参数并进入 H5 recordTransitParamsnavigateToLive 缓存 lastTransitParams,跳转 /pages/live/webview 跳转失败时由小程序路由或 web-view 错误处理

4.1 参数解析与 H5 路径生成

登录页加载时执行 initTransit(options)

  1. 读取 options.a / options.aliasoptions.r / options.roomId
  2. 读取可选参数 options.inviteId
  3. aliasroomId 缺失时,展示“参数缺失,无法进入直播间”;
  4. 解析成功后生成 targetPath
const alias = options.a || options.alias || '';
const roomId = options.r || options.roomId || '';
const inviteId = options.inviteId || '';

let path = '/' + alias + '/' + roomId;
if (inviteId) {
  path += '?inviteId=' + inviteId;
}
targetPath.value = path;

4.2 静默登录判断与执行

登录页使用 checkSilentLogin(alias, roomId) 判断是否尝试静默登录。该方法只比较缓存的 lastTransitParams,不额外判断 Token 是否过期。

缓存结构:

{
  "alias": "demo",
  "roomId": "10001",
  "timestamp": 1919202030300
}

aliasroomId 与上次一致时,执行 trySilentLogin()

  1. 调用 uni.login({ provider: 'weixin' }) 获取微信临时 code
  2. 调用 6.1 微信 code 换 OpenId / UnionId
  3. 调用 6.2 上报用户信息并换取 Token,仅提交 AppOpenIDUnionID
  4. 将返回的 TokenExpiresAt 写入 lemon_tokenlemon_expires_at
  5. 调用 recordTransitParams(alias, roomId),再通过 navigateToLive(targetPath) 进入 H5。

静默登录任一步骤异常时返回 false,页面切换到手动登录表单。

4.3 手动登录与头像更新

首次进入、上次进入参数不一致,或静默登录失败时展示 LoginForm.vue。表单负责收集:

  1. open-type="chooseAvatar" 返回的 avatarUrl
  2. type="nickname" 输入的昵称;
  3. 用户协议和隐私政策勾选状态。

用户点击登录后,登录页执行 handleManualLogin(userInfo)

  1. 调用 uni.login 获取微信临时 code
  2. 调用 6.1 微信 code 换 OpenId / UnionId
  3. 调用 6.2 上报用户信息并换取 Token,首次提交 AppNicknameOpenIDUnionID
  4. 从返回结果中取 TokenExpiresAt
  5. 若头像地址为微信临时文件路径,调用 6.3 上传微信临时头像
  6. 头像上传成功后,再次调用 6.2,补充 Avatar 更新用户资料;
  7. 缓存 lemon_tokenlemon_expires_at
  8. 登录成功后记录 lastTransitParams,再跳转 H5。

5. H5 承载页行为

小程序端获取 Token 后,跳转到:

/pages/live/webview?url={encodedH5Url}&token={token}&expires_at={expiresAt}

其中:

  • encodedH5UrlencodeURIComponent 后的 H5 直播间 URL;
  • 参考实现中 H5 URL 形如:https://example.xxx.com/{alias}/{roomId}
  • 如果存在 inviteId,H5 URL 形如:https://example.xxx.com/{alias}/{roomId}?inviteId={inviteId}

H5 承载页处理规则:

  1. 读取 url 参数并执行 decodeURIComponent
  2. 校验 URL 必须以 http://https:// 开头;
  3. 将除 url 以外的参数全部追加到 H5 URL;
  4. 最终由 web-view 加载完整 URL。

最终加载示例:

https://example.xxx.com/demo/10001?inviteId=888&token=xxxx&expires_at=1919202090

注意:分享路径不要直接使用带 token 的 H5 URL。参考实现中,分享时会根据 lastTransitParams 重新生成 /pages/transit/index?a={alias}&r={roomId},让被分享用户重新走登录页登录流程。

6. 接口定义

下列接口编号与第 4 节流程表对应。参考实现中接口统一封装在 sheep/api/live/sso.js

6.1 微信 code 换 OpenId / UnionId

  • 流程位置:静默登录 trySilentLogin 与手动登录 handleManualLogin
  • 调用方:小程序端 → 平台方 Lemon 接口
  • 请求方式GET
  • 接口路径
GET /sso/oauth/callback/wechat_mini?code={code}&app={app}

请求参数:

参数 必填 说明
code 微信 uni.login 返回的临时登录凭证
app 平台方分配的小程序应用标识,对应 MiniProgram_APP_NAME

请求示例:

curl "https://api.gouboyun.tv/lemon/sso/oauth/callback/wechat_mini?code=wx_code_xxx&app=mini_xxx"

成功响应示例:

{
  "Code": 0,
  "Message": "Success",
  "Error": "",
  "Data": {
    "OpenId": "o_xxxxxxxx",
    "UnionId": "u_xxxxxxxx"
  }
}
字段 类型 说明
Data.OpenId String 当前微信小程序用户 OpenId
Data.UnionId String 微信开放平台 UnionId,可能为空

参考封装:

export function getSsoOauthCallbackWechatMini(code, app) {
  return new Promise((resolve, reject) => {
    uni.request({
      url: `${baseURL}/sso/oauth/callback/wechat_mini`,
      method: 'GET',
      data: { code, app },
      success: (res) => {
        resolve(res.data.Data);
      },
      fail: (err) => {
        reject(err);
      },
    });
  });
}

6.2 上报用户信息并换取 Token

  • 流程位置:静默登录 trySilentLogin 与手动登录 handleManualLogin
  • 调用方:小程序端 → 平台方 Lemon 接口
  • 请求方式PUT
  • Content-Typeapplication/json
  • 接口路径
PUT /sso/oauth/callback/wechat_mini/user_info

静默登录请求示例:

{
  "App": "mini_xxx",
  "OpenID": "o_xxxxxxxx",
  "UnionID": "u_xxxxxxxx"
}

手动登录首次请求示例:

{
  "App": "mini_xxx",
  "Nickname": "张三",
  "OpenID": "o_xxxxxxxx",
  "UnionID": "u_xxxxxxxx"
}

头像上传成功后二次更新示例:

{
  "App": "mini_xxx",
  "Nickname": "张三",
  "OpenID": "o_xxxxxxxx",
  "UnionID": "u_xxxxxxxx",
  "Avatar": "https://example.com/avatar.png"
}

请求字段:

字段 必填 说明
App 平台方分配的小程序应用标识
OpenID 微信小程序用户 OpenId
UnionID 微信开放平台 UnionId,没有时传空字符串
Nickname 用户昵称,手动登录时传入
Avatar 用户头像 URL,头像上传成功后再次调用该接口更新

成功响应示例:

{
  "Code": 0,
  "Message": "Success",
  "Error": "",
  "Data": {
    "Token": "eyJhbGciOi...",
    "ExpiresAt": 1919202090
  }
}
字段 类型 说明
Data.Token String 平台方 H5 登录令牌
Data.ExpiresAt Integer Token 过期时间(Unix 秒级时间戳)

小程序端应将返回值写入本地缓存:

// 缓存平台方 H5 登录态,后续跳转 web-view 时透传
uni.setStorageSync('lemon_token', data.Token);
uni.setStorageSync('lemon_expires_at', data.ExpiresAt);

参考封装:

export function putSsoOauthCallbackWechatMiniUserInfo(data) {
  return new Promise((resolve, reject) => {
    uni.request({
      url: `${baseURL}/sso/oauth/callback/wechat_mini/user_info`,
      method: 'PUT',
      data,
      success: (res) => {
        resolve(res.data.Data);
      },
      fail: (err) => {
        reject(err);
      },
    });
  });
}

6.3 上传微信临时头像(辅助接口)

  • 流程位置:手动登录 handleManualLogin 获取 Token 后,且头像为临时路径时调用
  • 调用方:小程序端 → 平台方 Lemon 接口
  • 请求方式POST
  • 接口路径
POST /global/upload/avatar

请求要求:

说明
文件字段名 file
Header Authorization: Bearer {Token}
使用场景 用户选择的头像为 wxfile://http(s)://tmp/ 临时路径时上传

成功响应示例:

{
  "Code": 0,
  "Message": "Success",
  "Error": "",
  "Data": "https://example.com/avatar.png"
}

上传成功后,使用返回的 Data 作为 Avatar,再次调用 6.2 上报用户信息并换取 Token 更新头像。

头像上传接口封装:

export function uploadAvatar(filePath, token) {
  return new Promise((resolve, reject) => {
    uni.uploadFile({
      url: `${baseURL}/global/upload/avatar`,
      filePath,
      name: 'file',
      header: {
        Authorization: `Bearer ${token}`,
      },
      success: (res) => {
        const data = JSON.parse(res.data);
        resolve(data.Data);
      },
      fail: (err) => {
        reject(err);
      },
    });
  });
}

手动登录中头像上传与二次更新的完整顺序:

async function handleManualLogin(userInfo) {
  try {
    const loginRes = await new Promise((resolve, reject) => {
      uni.login({
        provider: 'weixin',
        success: resolve,
        fail: reject,
      });
    });

    if (loginRes.errMsg !== 'login:ok' || !loginRes.code) {
      uni.showToast({ title: '微信登录失败', icon: 'none' });
      return false;
    }

    // 第一步:微信 code 换取 OpenId / UnionId
    const { OpenId, UnionId } = await getSsoOauthCallbackWechatMini(
      loginRes.code,
      getAppName(),
    );

    // 第二步:先用昵称和微信身份换取平台方 Token
    const data = await putSsoOauthCallbackWechatMiniUserInfo({
      App: getAppName(),
      Nickname: userInfo.nickname,
      OpenID: OpenId,
      UnionID: UnionId || '',
    });

    const token = data.Token;
    const expiresAt = data.ExpiresAt;
    let avatarUrl = userInfo.avatarUrl;

    // 第三步:微信 chooseAvatar 返回临时路径时,先上传为平台可访问的头像 URL
    const isLocalAvatar =
      /^wxfile:\/\//.test(avatarUrl) || /^https?:\/\/tmp\//.test(avatarUrl);

    if (isLocalAvatar && token) {
      try {
        const uploadData = await uploadAvatar(avatarUrl, token);
        if (uploadData) {
          avatarUrl = uploadData;

          // 第四步:头像上传成功后,二次更新用户资料中的 Avatar
          await putSsoOauthCallbackWechatMiniUserInfo({
            App: getAppName(),
            Nickname: userInfo.nickname,
            OpenID: OpenId,
            UnionID: UnionId || '',
            Avatar: avatarUrl,
          });
        }
      } catch (e) {
        // 参考实现中头像上传失败不阻断登录,只记录错误
        console.error('[handleManualLogin] 头像上传失败:', e);
      }
    }

    // 第五步:缓存 Token,供跳转 web-view 时透传给 H5
    uni.setStorageSync('lemon_token', token);
    uni.setStorageSync('lemon_expires_at', expiresAt);

    return true;
  } catch (e) {
    console.error('[handleManualLogin] 手动登录失败:', e);
    uni.showToast({ title: '登录失败,请重试', icon: 'none' });
    return false;
  }
}

6.4 错误响应处理

所有接口响应均包含以下公共字段:

字段 说明
Code 0 表示成功,非 0 表示失败
Message 结果描述
Error 错误详情,成功时为空字符串

常见错误场景:

场景 可能原因 建议处理
code 失效 微信临时登录凭证已过期(有效期约 5 分钟) 重新调用 uni.login 获取新 code
app 标识无效 小程序应用标识未在平台方配置 联系平台方确认 MiniProgram_APP_NAME
上传文件失败 微信临时头像无法上传或服务端异常 参考实现中不阻断登录,仅记录错误
服务端错误 平台方服务异常 提示用户稍后重试

7. 分享与重新进入

H5 承载页支持小程序分享。分享时应回到登录页,而不是分享当前 web-view 的完整 H5 地址。

推荐分享路径:

/pages/transit/index?a={alias}&r={roomId}

处理原因:

  1. token 属于当前用户登录态,不应通过分享链接外泄;
  2. 被分享用户需要重新调用微信登录,生成自己的 OpenIdUnionIdToken
  3. 回到登录页后,可以复用同一套参数校验、静默/手动登录流程。

8. 异常处理

场景 建议提示/处理
缺少 aliasroomId 提示“参数缺失,无法进入直播间”
uni.login 失败 提示“微信登录失败”
静默登录失败 降级展示手动登录表单
用户未填写头像或昵称 提示“请完善头像和昵称”
用户未同意协议 提示“请先同意用户协议和隐私政策”
H5 URL 缺失 提示“缺少页面链接参数”
H5 URL 非 http/https 提示“页面链接格式不正确”
web-view 加载失败 提示“页面加载失败,请稍后重试”

9. 接入验收清单

  • 小程序可通过普通路径进入登录页,并正确解析 aliasroomIdinviteId
  • 同一直播间二次进入时可尝试静默登录;
  • 首次进入或静默登录失败时可展示头像、昵称、协议确认表单;
  • 登录成功后能缓存 lemon_tokenlemon_expires_at
  • 手动登录时头像能成功上传并更新;
  • H5 承载页最终 URL 包含 tokenexpires_at
  • 分享路径回到 /pages/transit/index,不包含 token
  • web-view 业务域名已在微信小程序后台配置。

附录:关键跳转代码示例

// 登录成功后调用,targetPath 为登录页解析生成的 H5 相对路径,如 /demo/10001
function navigateToLive(targetPath) {
  const token = uni.getStorageSync('lemon_token');
  const expiresAt = uni.getStorageSync('lemon_expires_at');
  const baseUrl = 'https://example.xxx.com';
  const livePath = baseUrl + targetPath;

  // 将 H5 地址编码后传给 web-view 承载页,同时透传登录态
  uni.redirectTo({
    url: `/pages/live/webview?url=${encodeURIComponent(livePath)}&token=${token}&expires_at=${expiresAt}`,
  });
}
function buildUrlWithParams(baseUrl, params) {
  const validParams = [];
  for (const key in params) {
    const value = params[key];
    if (value !== undefined && value !== '') {
      validParams.push(key + '=' + encodeURIComponent(value));
    }
  }
  if (validParams.length === 0) {
    return baseUrl;
  }
  const queryString = validParams.join('&');
  return baseUrl.indexOf('?') !== -1
    ? baseUrl + '&' + queryString
    : baseUrl + '?' + queryString;
}