尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

微信小程序用户信息获取方案重构:从getUserProfile到后端解密

微信小程序用户信息获取方案重构:从getUserProfile到后端解密
📅 发布时间:2026/8/3 1:36:42

1. 问题现象与背景:从getUserProfile到“微信用户”的转变

如果你最近在维护或开发一个基于uni-app的微信小程序,并且还在使用wx.getUserProfile这个接口来处理用户登录和获取用户信息,那么你很可能已经遇到了一个令人头疼的问题:新用户授权后,获取到的用户昵称(nickName)不再是他们精心设置的个性化名字,而是统一变成了“微信用户”,同时,用户头像(avatarUrl)也变成了一个灰色的默认头像。

这不是你的代码写错了,也不是服务器配置出了问题,而是微信官方对用户隐私保护策略的一次重大调整。这个变化直接源于微信团队对getUserProfile接口的调整。简单来说,这个曾经用来获取用户敏感信息(如昵称、头像)的接口,其返回的数据结构已经发生了变化。现在,即使用户点击了“允许”授权,你从该接口获取到的userInfo对象里,nickName和avatarUrl字段也已经是脱敏后的默认值,而非真实数据。

这直接导致了一个核心的业务问题:我们无法再依赖前端的一次性授权来直接获取用户的昵称和头像用于界面展示了。对于很多社交、电商、内容类小程序来说,用户的昵称和头像是构建用户身份感、促进社区互动的重要元素。当所有用户都显示为“微信用户”和一个灰色头像时,产品的用户体验和社交属性会大打折扣。

因此,解决这个问题不再是简单的API调用,而是需要我们对小程序的用户登录和用户信息获取流程进行一次重构。我们需要将思路从“前端直接拿”转变为“后端间接取”,并妥善处理新旧用户的兼容问题。接下来,我将结合uni-app的开发特点,详细拆解一套完整、可落地的解决方案。

2. 核心原理剖析:为什么getUserProfile不再返回真实信息?

要解决问题,首先要理解问题的根源。微信调整getUserProfile接口的行为,是其整体用户隐私保护体系升级的一部分。我们可以从几个层面来理解这个变化:

2.1 隐私政策的收紧与“最小必要”原则

近年来,全球范围内对用户数据隐私的监管都在加强。微信作为超级平台,必须对其生态内小程序的数据获取行为进行更严格的管控。核心原则就是“最小必要”,即小程序只能获取其功能正常运行所必需的最少用户信息,且必须经过用户明确同意。

旧的getUserProfile模式存在一个问题:它往往在用户首次进入小程序时,就弹窗请求获取昵称、头像等个人信息。用户可能只是为了使用某个简单功能(比如查个天气),却不得不授权交出这些信息。这不符合“最小必要”原则。因此,微信将获取用户敏感信息的动作与一个明确的、带有用途说明的按钮点击事件强绑定(这就是getUserProfile需要由<button>点击触发的原因),并且进一步,即使授权,返回的也是脱敏数据,将真实信息的获取路径转移到了更可控的后端。

2.2 新旧接口的对比与演进

为了更清晰地理解变化,我们可以对比一下过去和现在的流程:

  • 过去(已废弃的旧模式):

    1. 调用wx.login获取临时登录凭证code。
    2. 调用wx.getUserInfo(无需用户点击)或后来的wx.getUserProfile(需用户点击按钮),直接获取包含nickName和avatarUrl的userInfo对象。
    3. 将code和userInfo中的rawData、signature等一起发送到开发者服务器。
    4. 服务器用code换取openid和session_key,然后用session_key验证signature,验证通过后,即可信任前端传来的userInfo,并将其存入数据库。
  • 现在(必须采用的模式):

    1. 调用wx.login获取临时登录凭证code,发送到开发者服务器。
    2. 服务器用code调用微信接口,换取用户的唯一标识openid和本次会话的密钥session_key。此时,服务器已经可以唯一标识这个用户。
    3. 当且仅当小程序需要为用户展示昵称头像时(例如进入个人中心页),前端提供一个按钮,用户点击后触发wx.getUserProfile。
    4. 该接口返回的userInfo中,nickName为“微信用户”,avatarUrl为灰色头像。但重要的是,它会返回一个加密数据encryptedData和一个初始向量iv。
    5. 前端将encryptedData和iv发送给开发者服务器。
    6. 开发者服务器使用之前换取的session_key,对encryptedData进行对称解密。解密后的数据中,才包含该用户的真实昵称和头像URL。
    7. 服务器将解密得到的真实用户信息与用户的openid关联存储。

关键在于:真实的用户敏感信息(昵称、头像)的解密操作,必须发生在你的、受你控制的开发者服务器上。前端无法独立完成,getUserProfile接口返回的明文信息已是脱敏后的结果。这样,微信平台就能确保用户敏感信息不会在前端环境被不可信的小程序代码泄露或滥用。

2.3 session_key的关键作用与安全边界

session_key是这个流程中的安全核心。它是微信服务器和你的开发者服务器之间的一个“共享秘密”,具有时效性(通常有效期不长)。它的作用有两个:

  1. 用于解密:解密getUserProfile返回的encryptedData,以获取真实用户信息。
  2. 用于签名验证:在旧流程中验证rawData的签名(signature),在新流程中,虽然我们主要用其解密,但理解其验证机制对排查问题有帮助。

前端无法直接获取或使用session_key,这是微信故意设计的安全边界,防止密钥泄露。所有涉及session_key的操作(解密、签名验证)都必须在后端完成。这也意味着,你的后端服务必须具备相应的解密能力。

3. 全新登录与用户信息获取流程实战

理解了原理,我们开始动手改造。整个流程可以分为前端(uni-app)和后端(以Node.js为例)两部分。这里假设你已经有一个可以接收HTTP请求的后端服务。

3.1 前端(uni-app)代码重构

前端的工作变得清晰且专注:获取code,在需要时获取加密数据,并和后端通信。

// 在你的登录页面或App.vue的登录方法中 export default { methods: { async handleLogin() { try { // 1. 调用 wx.login 获取 code const loginRes = await uni.login(); const code = loginRes.code; if (!code) { uni.showToast({ title: '登录失败,请重试', icon: 'none' }); return; } // 2. 将 code 发送到后端,进行首次登录认证,后端会返回自定义登录态(如token) const authRes = await uni.request({ url: 'https://your-server.com/api/wx-auth', // 你的后端接口 method: 'POST', data: { code } }); // 假设后端返回 { token: 'xxx', hasUserInfo: false } const { token, hasUserInfo } = authRes.data; // 存储后端返回的token,用于后续接口鉴权 uni.setStorageSync('auth_token', token); // 3. 根据业务状态决定是否立即获取用户信息 // 例如,如果用户是首次登录(hasUserInfo为false),可以引导其完善信息 if (!hasUserInfo) { // 这里可以先跳转到个人资料页,或者显示一个“完善信息”的按钮 // 我们将在用户点击按钮时触发 getUserProfile } else { // 老用户,已有信息,直接登录成功,跳转首页 uni.switchTab({ url: '/pages/index/index' }); } } catch (error) { console.error('登录流程异常:', error); uni.showToast({ title: '网络或服务异常', icon: 'none' }); } }, // 这是一个独立的按钮点击事件,用于获取并更新用户信息 async onGetUserProfile() { try { // 1. 触发 getUserProfile 弹窗授权 const profileRes = await uni.getUserProfile({ desc: '用于完善会员资料' // 必须声明用途,展示给用户 }); // 2. 此时获取到的 userInfo 中,nickName和avatarUrl是脱敏的 console.log('前端获取的userInfo (脱敏):', profileRes.userInfo); // 输出: { nickName: "微信用户", avatarUrl: "灰色头像URL", ... } // 3. 获取到的加密数据 encryptedData 和 iv 是关键! const { encryptedData, iv } = profileRes; // 4. 将加密数据发送给后端进行解密 const token = uni.getStorageSync('auth_token'); const updateRes = await uni.request({ url: 'https://your-server.com/api/update-user-info', method: 'POST', header: { 'Authorization': `Bearer ${token}` // 携带登录态 }, data: { encryptedData, iv } }); // 5. 后端解密成功并保存后,返回真实的用户信息 const realUserInfo = updateRes.data; // { nickName: '张三', avatarUrl: '真实头像URL' } uni.setStorageSync('userInfo', realUserInfo); // 6. 更新前端UI this.userInfo = realUserInfo; uni.showToast({ title: '信息更新成功', icon: 'success' }); } catch (error) { // 用户拒绝授权或其他错误 if (error.errMsg && error.errMsg.indexOf('deny') > -1) { uni.showToast({ title: '您已拒绝授权', icon: 'none' }); } else { console.error('获取用户信息失败:', error); uni.showToast({ title: '获取信息失败', icon: 'none' }); } } } } }

关键点说明:

  • 分离关注点:登录(wx.login)和获取用户信息(wx.getUserProfile)是两个独立的步骤,不应该捆绑在同一个瞬间完成。
  • 按需获取:getUserProfile应该在用户明确需要提供信息的场景下触发,比如点击“完善资料”、“更新头像昵称”按钮时。
  • encryptedData和iv:这两个参数是获取真实信息的“钥匙”,必须安全地传给后端。

3.2 后端(Node.js + TypeScript)解密实现

后端需要提供两个核心接口:一个用于code换session_key并建立登录态,另一个用于解密encryptedData。

首先,安装必要的依赖:

npm install axios crypto-js
// service/wechat.service.ts - 微信相关服务 import axios from 'axios'; import * as CryptoJS from 'crypto-js'; export class WeChatService { private readonly appId: string; private readonly appSecret: string; constructor(appId: string, appSecret: string) { this.appId = appId; this.appSecret = appSecret; } /** * 1. 使用 code 换取 openid 和 session_key */ async codeToSession(code: string): Promise<{ openid: string; session_key: string }> { const url = `https://api.weixin.qq.com/sns/jscode2session`; const params = { appid: this.appId, secret: this.appSecret, js_code: code, grant_type: 'authorization_code' }; try { const response = await axios.get(url, { params }); const data = response.data; if (data.errcode) { throw new Error(`微信接口错误: ${data.errcode} - ${data.errmsg}`); } return { openid: data.openid, session_key: data.session_key }; } catch (error) { console.error('换取 session_key 失败:', error); throw new Error('微信登录服务暂时不可用'); } } /** * 2. 使用 session_key 解密 encryptedData */ decryptUserInfo(encryptedData: string, iv: string, sessionKey: string): any { // 参数校验 if (!encryptedData || !iv || !sessionKey) { throw new Error('解密参数缺失'); } // 将Base64编码的字符串转换为 WordArray (CryptoJS 所需格式) const encryptedDataWordArray = CryptoJS.enc.Base64.parse(encryptedData); const ivWordArray = CryptoJS.enc.Base64.parse(iv); const sessionKeyWordArray = CryptoJS.enc.Base64.parse(sessionKey); // 使用 AES-128-CBC 模式解密 const decrypted = CryptoJS.AES.decrypt( { ciphertext: encryptedDataWordArray } as any, sessionKeyWordArray, { iv: ivWordArray, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 } ); // 将解密结果转换为 UTF-8 字符串 let decryptedStr; try { decryptedStr = decrypted.toString(CryptoJS.enc.Utf8); } catch (e) { throw new Error('解密失败,session_key可能已过期或不匹配'); } if (!decryptedStr) { throw new Error('解密结果为空'); } // 解析 JSON 字符串 let decryptedData; try { decryptedData = JSON.parse(decryptedStr); } catch (e) { throw new Error('解密后的数据不是有效的JSON'); } // 可选:验证 watermark 中的 appid 是否与自己的 appid 一致,防止数据被篡改 if (decryptedData.watermark && decryptedData.watermark.appid !== this.appId) { throw new Error('解密数据来源异常'); } return decryptedData; } }
// controller/auth.controller.ts - 认证控制器 import { Context } from 'koa'; // 假设使用Koa框架 import { WeChatService } from '../service/wechat.service'; import { generateToken } from '../utils/jwt'; // 假设有生成JWT的工具函数 import { UserModel } from '../model/user.model'; // 假设的用户数据模型 const wechatService = new WeChatService('你的小程序AppID', '你的小程序AppSecret'); export class AuthController { // 接口1:处理 code 登录 static async loginByCode(ctx: Context) { const { code } = ctx.request.body; if (!code) { ctx.status = 400; ctx.body = { code: 400, msg: '参数code缺失' }; return; } try { // 1. 用code换 session_key 和 openid const { openid, session_key } = await wechatService.codeToSession(code); // 2. 查找或创建用户 let user = await UserModel.findOne({ openid }); const isNewUser = !user; if (isNewUser) { user = await UserModel.create({ openid, sessionKey: session_key, // 注意:生产环境建议将session_key加密存储或仅临时使用,不建议长期明文存储 lastLoginAt: new Date() }); } else { // 老用户,更新session_key(因为每次登录code换的session_key都不同) user.sessionKey = session_key; user.lastLoginAt = new Date(); await user.save(); } // 3. 生成自定义登录态(如JWT),返回给前端 const token = generateToken({ userId: user._id, openid }); ctx.body = { code: 200, msg: 'success', data: { token, hasUserInfo: !!user.nickName, // 告知前端用户是否已有昵称头像信息 openid // 可选返回,前端一般不需要 } }; } catch (error) { console.error('登录失败:', error); ctx.status = 500; ctx.body = { code: 500, msg: error.message || '登录服务异常' }; } } // 接口2:处理 encryptedData 解密并更新用户信息 static async updateUserInfo(ctx: Context) { const { encryptedData, iv } = ctx.request.body; const token = ctx.headers.authorization?.replace('Bearer ', ''); // 验证登录态(略,根据你的JWT或Session方案实现) const userInfoFromToken = verifyToken(token); const userId = userInfoFromToken.userId; if (!encryptedData || !iv) { ctx.status = 400; ctx.body = { code: 400, msg: '缺失加密参数' }; return; } try { // 1. 从数据库取出该用户最新的 session_key const user = await UserModel.findById(userId); if (!user) { ctx.status = 404; ctx.body = { code: 404, msg: '用户不存在' }; return; } // 2. 解密数据,获取真实用户信息 const decryptedData = wechatService.decryptUserInfo(encryptedData, iv, user.sessionKey); // decryptedData 结构: { nickName: '真实昵称', avatarUrl: '真实头像URL', gender, country, province, city, ... } // 3. 更新用户信息到数据库 user.nickName = decryptedData.nickName; user.avatarUrl = decryptedData.avatarUrl; user.gender = decryptedData.gender; // ... 更新其他字段 await user.save(); // 4. 返回真实的用户信息给前端 ctx.body = { code: 200, msg: 'success', data: { nickName: decryptedData.nickName, avatarUrl: decryptedData.avatarUrl, // ... 其他需要前端展示的字段 } }; } catch (error) { console.error('更新用户信息失败:', error); // 常见错误:session_key过期。此时应让前端重新走登录流程获取新的code和session_key if (error.message.includes('过期') || error.message.includes('不匹配')) { ctx.status = 401; ctx.body = { code: 1001, msg: '登录状态已过期,请重新登录' }; } else { ctx.status = 500; ctx.body = { code: 500, msg: '用户信息更新失败' }; } } } }

4. 关键细节、避坑指南与性能优化

将流程跑通只是第一步,在实际项目中,你会遇到各种边界情况和性能问题。下面分享一些关键的细节和避坑经验。

4.1 session_key的管理与过期处理

session_key是解密的钥匙,但它是有有效期的(官方文档未明确说明,但实践中发现可能因用户操作而变化或过期)。不当的管理会导致解密失败。

  • 存储策略:不建议将session_key长期明文存储在数据库。一种更安全的做法是:在用户登录时(codeToSession后),将session_key与当前用户的openid关联,加密后短期存储在缓存(如Redis)中,并设置一个合理的TTL(例如2小时)。当需要解密时,从缓存取出并解密使用。如果缓存失效,则提示前端需要重新登录。
  • 过期应对:在解密接口(updateUserInfo)中,如果捕获到解密失败(通常是CryptoJS抛出异常或解密出的数据格式不对),错误信息很可能包含“padding错误”或“解密失败”。此时,应该返回特定的错误码(如上面示例中的1001)给前端。前端收到此错误码后,应清除本地登录态(token),并引导用户重新触发wx.login和登录流程,以获取新的code和session_key。

4.2 用户拒绝授权与体验优化

用户有权拒绝授权getUserProfile。我们的代码需要优雅地处理这种情况。

  • 清晰的引导文案:在触发getUserProfile的按钮上,使用desc参数明确告知用户获取信息的用途,例如“用于在社区显示您的昵称和头像”,这能提高授权率。
  • 提供备选方案:用户拒绝后,不应阻断核心功能。可以:
    1. 使用一个默认的“微信用户”昵称和灰色头像(这正是getUserProfile返回的脱敏数据)作为临时展示。
    2. 在个人中心页,持续显示一个温和的提示,如“完善头像昵称,让朋友们更容易认出你~”,并再次提供触发按钮。
    3. 允许用户手动输入昵称和上传本地图片作为头像,作为微信信息的补充或替代方案。

4.3 新旧用户兼容与数据迁移

如果你的小程序在调整接口前已经上线,数据库中存有老用户通过旧方式获取的真实昵称和头像。你需要一套兼容逻辑。

  • 用户表设计:用户表应有字段标识信息来源,例如:
    { openid: String, nickName: String, avatarUrl: String, userInfoSource: { // 信息来源 type: String, enum: ['old_wx_api', 'new_wx_decrypt', 'manual_input'], default: 'new_wx_decrypt' }, // ... 其他字段 }
  • 登录时判断:在登录接口中,查询到老用户后,直接返回其存储的真实信息给前端,并标记hasUserInfo: true。这样老用户无需重新授权。
  • 更新信息:当老用户主动点击“更新信息”并授权后,走新的解密流程,覆盖旧数据,并将userInfoSource更新为new_wx_decrypt。

4.4 性能与安全优化建议

  • 头像URL存储与处理:微信返回的头像URL是有时效性的(通常几小时后失效)。直接存这个URL到数据库,未来可能显示失败。
    • 推荐做法:在后端解密获取到头像URL后,立即用你的服务器(或云函数)去下载这个图片,存储到你自己的对象存储(如阿里云OSS、腾讯云COS)或CDN上,然后将这个永久的、你自己的URL存入数据库并返回给前端。这个过程可以异步进行,避免阻塞登录主流程。
  • 防刷与限流:code换session_key的接口和用户信息解密接口都应做好限流,防止恶意攻击。
  • Token刷新机制:前端存储的token应设置有效期。可以在请求拦截器中判断token是否临近过期,调用一个刷新接口获取新的token,实现无感续期。

4.5 一个常见的坑:encryptedData解密失败

除了session_key过期,解密失败还可能因为:

  1. 参数传递错误:确保前端将getUserProfile返回的完整encryptedData和iv字符串(是Base64编码的)原封不动地传给后端,不要做任何解码或修改。
  2. 编码问题:在后端解密时,确保传入CryptoJS的sessionKey、iv、encryptedData都是正确的WordArray格式。使用CryptoJS.enc.Base64.parse()进行转换是标准做法。
  3. 多端同步问题:如果用户同时在手机和电脑上登录小程序,后登录的设备会使得先登录设备的session_key失效。业务设计上需要考虑这种场景。

5. 完整的前后端交互时序与状态管理

为了更宏观地把握整个流程,我们可以梳理一下从用户进入小程序到信息完整展示的完整交互时序,并讨论在uni-app中如何管理这些状态。

5.1 交互时序图(逻辑描述)

  1. 启动与静默登录:

    • 小程序启动,App.vue的onLaunch中调用uni.login获取code。
    • 将code发送给后端/api/wx-auth。
    • 后端用code换得openid和session_key,生成自定义token,并查询数据库判断用户是否已存在(hasUserInfo)。
    • 后端返回token和hasUserInfo标志。
    • 前端存储token。
  2. 判断与引导:

    • 前端根据hasUserInfo标志决定流程。
    • 若为true(老用户),可直接跳转首页,并从本地缓存或再次请求后端获取已存储的用户信息展示。
    • 若为false(新用户),可进入一个“欢迎页”或“个人资料页”,页面中央有一个明显的按钮(如“微信一键登录”或“完善资料”)。
  3. 授权与更新:

    • 用户点击按钮,触发uni.getUserProfile,弹出授权窗口。
    • 用户同意后,前端拿到encryptedData和iv。
    • 前端带着token、encryptedData、iv请求后端/api/update-user-info。
    • 后端用token关联的session_key解密数据,将真实昵称头像存入数据库。
    • 后端返回真实信息给前端。
    • 前端更新本地缓存和页面状态,完成流程。

5.2 Uni-app中的状态管理方案

对于用户登录态(token)和用户信息(userInfo),需要一个全局的状态管理方案。

  • 简单方案(Vuex/Pinia):对于大多数小程序,使用Vuex或Pinia来管理全局状态是清晰的选择。

    // stores/user.js (Pinia示例) import { defineStore } from 'pinia'; export const useUserStore = defineStore('user', { state: () => ({ token: uni.getStorageSync('auth_token') || '', userInfo: uni.getStorageSync('userInfo') || null, hasLogged: false }), actions: { setToken(token) { this.token = token; uni.setStorageSync('auth_token', token); }, setUserInfo(info) { this.userInfo = info; uni.setStorageSync('userInfo', info); }, logout() { this.token = ''; this.userInfo = null; this.hasLogged = false; uni.removeStorageSync('auth_token'); uni.removeStorageSync('userInfo'); }, // 一个组合了登录和更新信息的action async loginAndFetchUserInfo() { // 1. 执行登录流程,获取token... // 2. 检查是否需要获取用户信息... // 3. 如果需要,触发getUserProfile并更新... } } });

    在需要用户信息的页面,通过store.userInfo来获取和展示。在App.vue的onLaunch中,可以尝试用缓存的token自动登录。

  • 请求拦截器:在uni.request的全局配置或拦截器中,自动为每个请求添加Authorization头,携带token。

    // utils/request.js import { useUserStore } from '@/stores/user'; const userStore = useUserStore(); const request = (options) => { // 添加token到header options.header = { ...options.header, 'Authorization': `Bearer ${userStore.token}` }; return uni.request(options); }; export default request;

5.3 网络异常与重试机制

网络请求可能失败。对于登录和获取用户信息这样的关键流程,需要增加健壮性。

  • uni.login重试:uni.login可能因网络问题失败。可以封装一个带重试的函数,最多重试2-3次。
  • 解密失败的重试逻辑:当后端返回session_key过期错误(如自定义错误码1001)时,前端的重试不应该是简单的再次调用解密接口,而应该回到流程的起点:清除旧token,重新执行uni.login-> 获取新code-> 调用登录接口换取新的session_key和token-> 再用新的token去重试用户信息更新接口。这个流程可以封装在统一的错误处理函数中。

通过以上五个部分的详细拆解,我们从现象、原理、实战、避坑到全局设计,完整地覆盖了解决“微信用户”和灰色头像问题的全过程。这套方案不仅解决了当前的问题,也建立了一个更安全、更符合规范的小程序用户体系。在实际开发中,请务必根据你的具体业务逻辑和后端技术栈进行适配和调整。

相关新闻

  • 从快速上手到真正掌握:建立最小可行认知与操作闭环
  • 计算机毕业设计选题推荐:2026年10个Spring Boot项目(SpringBoot+Vue+论文+源码)
  • 基于reComputer AI盒子与TensorRT优化的实时OCR边缘计算方案实践

最新新闻

  • 2026年应届生黑科技榜单9款AI论文网站横评!
  • 2026抖音代运营机构盘点:B 端实体企业短视频服务商选型参考指南
  • C语言阶段性学习复盘
  • SMART技术详解:从硬盘健康监测到预测性维护实战指南
  • 基于Intel NCS2与OpenVINO的边缘AI推理实战:从模型转换到性能调优
  • 论文格式总是调不对,有哪些专业的一键生成论文工具推荐?

日新闻

  • 112、LLC谐振变换器的输入电压瞬态仿真分析
  • 2026深圳疑难签证办理指南:拒签再签/商务签/高端定制机构怎么选 - 互联网科技品牌测评
  • C-LODOP在Edge等现代浏览器中的部署、适配与实战应用

周新闻

  • 怀化母婴除甲醛公司测甲醛中心怎么选:康之居母婴除甲醛标准、流程、避坑指南 - 信誉隆金银铂奢回收
  • 三步打造你的终极音乐中心:foobox-cn网络电台功能完整指南
  • Lance湖仓格式:为多模态AI工作流设计的终极数据存储方案

月新闻

  • ClickHouse版本管理深度实战:4步构建零风险升级与回滚体系
  • Java 23 种设计模式:从踩坑到精通 | 番外:责任链模式 —— 物流审批流程实战
  • 华硕笔记本性能解放指南:G-Helper轻量级控制工具全面解析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号