## 前言

在开发微信小程序时,获取用户的微信运动步数是一个常见需求。但微信运动数据wx.getWeRunData)返回的是加密数据,需要在后端使用 session_key 进行解密。本文将分享一个经过实战验证的完整解决方案,包括前端调用和后端 PHP 解密的核心代码。

## 技术原理

微信小程序的数据加密机制:

- 加密算法:AES-128-CBC

- 密钥:通过 wx.login() 获取的 code 换取 session_key

- 数据结构:加密数据 + PKCS#7 填充

关键点codeencryptedDataiv 必须在同一次登录会话中获取,否则解密会失败。

## 实现步骤

### 1. 前端:小程序端(核心代码)

// 获取微信运动步数的完整流程

getWeRunData() {
  const that = this;
  // 第一步:检查微信运动授权
  wx.getSetting({
    success(res) {
      if (res.authSetting['scope.werun']) {
        // 已授权,直接获取
        that.fetchWeRunData();
      } else {
        // 请求授权
        wx.authorize({
          scope: 'scope.werun',
          success() {
            that.fetchWeRunData();
          },
          fail() {
            wx.showToast({
              title: '需要授权微信运动才能获取步数',
              icon: 'none'
            });
          }
        });
      }
    }
  });
},
// 核心:登录 → 获取加密数据 → 发送后端
fetchWeRunData() {
  const that = this;
  // Step 1: 获取登录凭证 code
  wx.login({
    success(loginRes) {
      if (!loginRes.code) {
        wx.showToast({ title: '登录失败', icon: 'none' });
        return;
      }
      // Step 2: 立即获取微信运动加密数据(关键:必须在同一会话)
      wx.getWeRunData({
        success(werunRes) {
          const { encryptedData, iv } = werunRes;
          // Step 3: 发送到后端解密
          wx.request({
            url: 'https://your-api.com/api/werun/decrypt',
            method: 'POST',
            data: {
              code: loginRes.code,
              encryptedData: encryptedData,
              iv: iv
            },
            success(res) {
              if (res.data.code === 0) {
                const stepList = res.data.data.stepInfoList;
                const latestStep = stepList[0]; // 最新一天的步数
                console.log('今日步数:', latestStep.step);
                console.log('时间戳:', latestStep.timestamp);
              } else {
                wx.showToast({
                  title: res.data.msg || '解密失败',
                  icon: 'none'
                });
              }
            },
            fail(err) {
              wx.showToast({
                title: '网络请求失败',
                icon: 'none'
              });
            }
          });
        },
        fail(err) {
          wx.showToast({
            title: '获取运动数据失败',
            icon: 'none'
          });
        }
      });
    }
  });
}

### 2. 后端:PHP 解密(核心代码)

<?php
/**
 * 微信小程序运动步数解密接口
 */
class WeRunController
{
    private $appId = 'your_app_id';
    private $appSecret = 'your_app_secret';
    /**
     * 主接口
     */
    public function decrypt(Request $request)
    {
        $code = $request->post('code');
        $encryptedData = $request->post('encryptedData');
        $iv = $request->post('iv');
        if (empty($code) || empty($encryptedData) || empty($iv)) {
            return $this->error('参数缺失');
        }
        // 1. 获取 session_key
        $session = $this->getSessionKey($code);
        if (!$session || empty($session['session_key'])) {
            return $this->error('获取登录态失败');
        }
        // 2. 解密数据
        $result = $this->decryptData($encryptedData, $session['session_key'], $iv);
        if ($result === false) {
            return $this->error('解密失败');
        }
        return $this->success($result);
    }
    /**
     * 获取 session_key
     */
    private function getSessionKey($code)
    {
        $url = 'https://api.weixin.qq.com/sns/jscode2session';
        $params = [
            'appid'      => $this->appId,
            'secret'     => $this->appSecret,
            'js_code'    => $code,
            'grant_type' => 'authorization_code'
        ];
        $response = file_get_contents($url . '?' . http_build_query($params));
        return json_decode($response, true);
    }
    /**
     * AES-128-CBC 解密
     */
    private function decryptData($encryptedData, $sessionKey, $iv)
    {
        // Base64 解码(关键:三个参数都要解码)
        $sessionKeyDecoded = base64_decode($sessionKey, true);
        $encryptedDataDecoded = base64_decode($encryptedData, true);
        $ivDecoded = base64_decode($iv, true);
        // 校验解码结果
        if ($sessionKeyDecoded === false || 
            $encryptedDataDecoded === false || 
            $ivDecoded === false) {
            return false;
        }
        // 校验长度(AES-128 要求 key 和 iv 都是 16 字节)
        if (strlen($sessionKeyDecoded) !== 16 || strlen($ivDecoded) !== 16) {
            return false;
        }
        // AES-128-CBC 解密
        $decrypted = openssl_decrypt(
            $encryptedDataDecoded,
            'AES-128-CBC',
            $sessionKeyDecoded,
            OPENSSL_RAW_DATA,
            $ivDecoded
        );
        if ($decrypted === false) {
            return false;
        }
        // 去除 PKCS#7 填充
        $pad = ord(substr($decrypted, -1));
        if ($pad >= 1 && $pad <= 16) {
            $decrypted = substr($decrypted, 0, -$pad);
        }
        // 解析 JSON
        $data = json_decode($decrypted, true);
        if (json_last_error() !== JSON_ERROR_NONE) {
            return false;
        }
        // 可选:校验 appid
        if (isset($data['watermark']['appid']) && 
            $data['watermark']['appid'] !== $this->appId) {
            return false;
        }
        return $data;
    }
}

## 关键技术点

### 1. 前端调用顺序

必须严格按照以下顺序执行

wx.login() → wx.getWeRunData() → wx.request()

在同一个回调链中完成,确保 code 和加密数据是同一次会话。

### 2. 后端解密步骤

1. Base64 解码sessionKeyencryptedDataiv 都需要解码

2. 长度校验:解码后 sessionKeyiv 必须是 16 字节

3. AES 解密:使用 openssl_decrypt 进行 AES-128-CBC 解密

4. 去除填充:去除 PKCS#7 填充(最后 1-16 字节)

5. JSON 解析:解析得到运动数据

### 3. 返回数据结构

{

  "stepInfoList": [

    {

      "timestamp": 1735142400,  // 时间戳(秒)

      "step": 12345             // 步数

    }

  ],

  "watermark": {

    "appid": "wx636346c6c2761890",

    "timestamp": 1735185123

  }

}

## 常见错误及解决方案

### 1. 错误invalid key length

原因sessionKey 未正确解码,或长度不是 16 字节

解决:确保 base64_decode($sessionKey) 后长度为 16

### 2. 错误:填充值异常(pad > 16)

原因

- codeencryptedData 不是同一会话

- sessionKeyiv 解码错误

解决

- 确保前端在同一回调链中获取 codeencryptedData

- 检查三个参数的 base64 解码是否成功

### 3. 错误:JSON 解析失败

原因:PKCS#7 填充未正确去除

解决:检查填充值是否在 1-16 范围内,正确截取字符串

### 4. 错误:appid 校验失败

原因:使用的 appId 与小程序不匹配

解决:确认后端配置的 appIdappSecret 正确

## 注意事项

1. 安全性:不要在前端存储或打印 session_key

2. 时效性code 只能使用一次,且有效期 5 分钟

3. session_key 有效期:一般情况下不会过期,除非用户重新授权

4. 授权:首次获取需要用户授权 scope.werun

5. 测试:建议在真机上测试,开发者工具可能有差异

## 总结

微信小程序运动步数解密的核心要点:

1. 前端确保在同一会话中获取 code 和加密数据

2. 后端正确进行 Base64 解码和 AES-128-CBC 解密

3. 注意 PKCS#7 填充的处理

4. 做好错误处理和日志记录

通过本文的方案,你可以快速实现微信小程序运动步数的获取和解密功能。如果遇到问题,重点检查前端调用顺序和后端解密步骤。