告别 300 元年费 + 企业资质!轻量版微信扫码登录模块,Web/WinForm 直接集成

75 阅读7分钟

前言

作为个人开发者或小团队,你是否也遇到过这样的困境:想给项目加个微信扫码登录功能,结果被官方要求的「企业资质认证」「300 元年费」「域名备案」拦住去路?流程繁琐不说,成本也让很多小型项目望而却步。

1.gif

登录又是所有系统不可或缺的,为解决这个小麻烦,我开发了一套免企业资质、免年费、免备案的轻量版微信扫码登录模块,无需复杂配置,Web 和 WinForm 项目都能快速集成。目前已稳定运行,今天把完整方案分享给大家,附详细集成教程和真实可复用的代码~

一、核心亮点(为什么选它?)

  1. 零门槛接入:无需企业资质、不用交 300 元年费、不用备案域名,个人开发者 / 小团队直接用
  2. 双端支持:同时适配 Web 项目(Vue/React/ 原生 HTML)和 WinForm 桌面应用
  3. 集成超简单:Web 端用 iframe 嵌套 + 事件监听,WinForm 直接复用现成代码,5 分钟搞定
  4. 稳定可靠:二维码有效期 60 秒自动刷新,扫码结果实时响应,无额外复杂依赖
  5. 完全免费:个人 / 商业使用均无费用,后续会持续迭代功能
  6. 原生适配:WinForm 代码基于 CefSharp开发,兼容主流桌面应用框架

二、功能演示

1. 可直接集成的二维码

在线演示地址:ScanQrcode
(注:实际使用时可直接通过接口获取,无需自己生成)

2. 扫码效果

用户用微信扫码后,模块实时返回用户 OpenId(唯一标识),无需授权登录,快速完成身份校验,WinForm 端自动更新状态并跳转主页。

08dbc0bd3efedc8b627c72de29c572a4_v2-b1571cbe8928a9d10ccd55e02a261483_1440w.jpg

WinForm 端登录页

通过pictureBox控件显示二维码,60 秒内未扫码自动刷新,扫码成功后自动存储 OpenId 并关闭登录页,进入系统主页。

bb547329f938f3ffd05183e2e9ccfc5e_v2-d9dee45ca1b900ac41b086b676002c08_1440w.jpg

2.gif

三、详细集成教程

(一)Web 项目集成(原生 HTML/Vue/React 通用)

1. 核心原理

用 iframe 嵌套官方二维码,通过window.postMessage监听扫码结果回调,拿到 OpenId 后完成登录逻辑。

2. 完整代码示例(原生 HTML)

<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<title>微信扫码登录(免资质版)</title>
<style>
/* 样式优化,适配不同屏幕 */
body {
  margin: 0;
  padding: 0;
  height: 100vh;
  font-family: "Microsoft YaHei", Arial, sans-serif;
  background: #f5f6fa;
  display: flex;
  align-items: center;
  justify-content: center;
}
.container {
  display: flex;
  width: 900px;
  height: 400px;
  box-shadow: 0 4px 24px rgba(0, 0, 0, 0.1);
  border-radius: 16px;
  background: #fff;
  overflow: hidden;
}
.left {
  flex: 1;
  background: #4f8cff;
  color: #fff;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 0 60px;
}
.left h1 {
  font-size: 28px;
  margin-bottom: 20px;
}
.left p {
  font-size: 16px;
  line-height: 1.8;
  opacity: 0.9;
}
.right {
  flex: 1;
  background: #f0f4ff;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 32px;
}
.qr-container {
  width: 180px;
  height: 180px;
  background: #fff;
  border: 1px solid #ddd;
  border-radius: 8px;
  overflow: hidden;
  position: relative;
  margin-bottom: 20px;
}
/* iframe缩放适配 */
.qr-container iframe {
  width: 430px;
  height: 430px;
  border: none;
  transform: scale(0.4186);
  transform-origin: top left;
  display: block;
}
.right span {
  font-size: 15px;
  color: #333;
}
/* 响应式适配手机 */
@media (max-width: 1000px) {
  .container {
    flex-direction: column;
    width: 98vw;
    height: auto;
    margin-top: 20px;
  }
  .left, .right {
    width: 100%;
    height: 250px;
    padding: 20px;
  }
  .qr-container {
    width: 150px;
    height: 150px;
  }
}
</style>
</head>
<body>
<div class="container">
  <div class="left">
    <h1>欢迎使用本系统</h1>
    <p>高效、安全、便捷的管理平台<br>助力您的业务快速成长</p>
  </div>
  <div class="right">
    <div class="qr-container">
      <!-- 嵌套二维码iframe -->
      <iframe src="http://106.14.223.151:18007/qrcode" frameborder="0" scrolling="no"></iframe>
    </div>
    <span>请使用微信扫码登录</span>
  </div>
</div>

<script>
// 监听iframe回调的扫码结果
window.addEventListener('message', function(event) {
  // 安全校验:仅接收指定域名的回调(必加!防止恶意请求)
  if (event.origin !== 'http://106.14.223.151:18007') return;
  
  try {
    // 解析回调数据(包含用户OpenId)
    const scanData = JSON.parse(event.data);
    console.log('扫码成功,用户信息:', scanData.value);
    
    // 这里可扩展自己的业务逻辑:跳转主页/注册页、保存用户信息等
    alert(`登录成功!用户ID:${scanData.value.openId}`);
    // window.location.href = `/home?openId=${scanData.value.openId}`;
  } catch (e) {
    console.error('解析扫码结果失败:', e);
  }
});
</script>
</body>
</html>

3. 集成步骤(3 步搞定)

  1. 复制上面的 HTML 代码,直接嵌入自己的登录页;
  2. 按需修改样式(颜色、尺寸、文案),适配项目 UI;
  3. message事件回调中,添加自己的登录 / 注册逻辑(如通过 OpenId 查询用户、创建新用户等)。

(二)WinForm 项目集成(真实可复用代码)

1. 核心依赖

需提前通过 NuGet 安装以下包:

  • Newtonsoft.Json(解析 JSON 响应)
  • CefSharp.WinForms(桌面端浏览器内核支持)
  • System.Net.Http(HTTP 请求工具)

2. 核心接口说明

接口名称接口地址说明
获取二维码http://106.14.223.151:18003/api/WeChat/loginCode返回二维码图片字节流,直接用于显示
轮询扫码状态http://106.14.223.151:18003/api/WeChat/LoginState2 秒轮询一次,返回 3 种状态
刷新二维码(超时)http://106.14.223.151:18003/api/WeChat/loginCode?sign=timeout二维码过期后重新获取
扫码成功标识http://106.14.223.151:18003/api/WeChat/loginCode?sign=success扫码成功后更新图片标识

3. 轮询状态说明

  1. 二维码待扫码(正常状态):
{
  "statusCode": 200,
  "remark": "",
  "message": "",
  "value": {"reqId": "55db77b440c9839bada7f692bdfd819c"}
}

2.扫码成功(返回 OpenId):

{
  "statusCode": 200,
  "remark": "",
  "message": "",
  "value": {"openId": "wxoid_fjhxf7cau76"} // 用户唯一标识
}

3.二维码失效 / 未使用:

{
  "statusCode": 300,
  "remark": "",
  "message": "",
  "remark": "Operation failed"
}
  1. 完整可复用代码(直接复制到项目中)
using CefSharp;
using CefSharp.WinForms;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
using System.Windows.Forms;

namespace zhuang.codeprint.winform.Frms
{
    public partial class frmLogin : Form
    {
        private HttpClient httpClient;
        private System.Timers.Timer aTimer = new System.Timers.Timer(2000); // 2秒轮询一次

        private bool isPolling = false; // 轮询状态标志
        private int pollingCount = 0; // 轮询次数计数器
        private const int MaxPollingAttempts = 30; // 最大轮询次数(60秒,超时自动刷新)

        public frmLogin()
        {
            InitializeComponent();
            this.Text = "系统登录";
            httpClient = new HttpClient();
        }

        // 窗体加载时初始化二维码和轮询
        private async void frmLogin_Load(object sender, EventArgs e)
        {
            string imageUrl = "http://106.14.223.151:18003/api/WeChat/loginCode";
            await LoadImageFromUrlAsync(imageUrl);
            isPolling = true;
            // 绑定轮询事件
            aTimer.Elapsed += new System.Timers.ElapsedEventHandler(TimedEvent);    
            aTimer.AutoReset = true;  // 设置持续轮询
            aTimer.Enabled = true;    // 启用定时器
        }

        // 从接口加载二维码图片
        private async Task LoadImageFromUrlAsync(string imageUrl)
        {
            try
            {
                ShowLoadingIndicator(); // 显示加载状态

                // 下载图片字节流
                byte[] imageBytes = await httpClient.GetByteArrayAsync(imageUrl);

                // 转换为图片并显示
                using (MemoryStream ms = new MemoryStream(imageBytes))
                {
                    pictureBox1.Image = Image.FromStream(ms);
                    pictureBox1.SizeMode = PictureBoxSizeMode.Zoom; // 自适应缩放
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show($"加载二维码失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
                ShowErrorImage(); // 显示错误图片(可自行实现)
            }
        }

        // 轮询事件:查询扫码状态
        private async void TimedEvent(object sender, ElapsedEventArgs e)
        {
            if (!isPolling) return;

            pollingCount++;

            // 超过最大轮询次数(60秒),刷新二维码
            if (pollingCount > MaxPollingAttempts)
            {
                StopPolling();
                this.Invoke(new Action(async () =>
                {
                    // 加载新的二维码(超时标识)
                    await LoadImageFromUrlAsync("http://106.14.223.151:18003/api/WeChat/loginCode?sign=timeout");
                    // 重置轮询状态,重新开始
                    pollingCount = 0;
                    isPolling = true;
                    aTimer.Enabled = true;
                }));
                return;
            }

            try
            {
                // 调用轮询接口
                string pollingUrl = $"http://106.14.223.151:18003/api/WeChat/LoginState";
                var response = await httpClient.GetAsync(pollingUrl);

                if (response.IsSuccessStatusCode)
                {
                    string jsonResponse = await response.Content.ReadAsStringAsync();
                    Console.WriteLine("轮询结果:" + jsonResponse);
                    JObject result = JObject.Parse(jsonResponse);

                    // 状态码200表示接口正常响应
                    if (result["statusCode"]?.ToString() == "200")
                    {
                        JObject value = result["value"] as JObject;
                        // 包含openId表示扫码成功
                        if (value != null && value.ContainsKey("openId"))
                        {
                            StopPolling(); // 停止轮询
                            this.Invoke(new Action(async () =>
                            {
                                // 加载扫码成功标识图片
                                await LoadImageFromUrlAsync("http://106.14.223.151:18003/api/WeChat/loginCode?sign=success");
                                string openId = value["openId"].ToString();
                                Define.OpenId = openId; // 存储OpenId到全局变量
                                MessageBox.Show($"登录成功!用户OpenId:{openId}", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
                                this.DialogResult = DialogResult.OK; // 关闭登录页,返回主页面
                            }));
                        }
                    }
                    else
                    {
                        // 接口响应异常,刷新二维码
                        StopPolling();
                        this.Invoke(new Action(async () =>
                        {
                            await LoadImageFromUrlAsync("http://106.14.223.151:18003/api/WeChat/loginCode?sign=timeout");
                            pollingCount = 0;
                            isPolling = true;
                            aTimer.Enabled = true;
                        }));
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"轮询异常:{ex.Message}");
                // 异常不停止轮询,仅记录日志
            }
        }

        // 停止轮询
        private void StopPolling()
        {
            isPolling = false;
            aTimer.Enabled = false;
        }

        // 显示加载状态(可根据项目需求实现,如显示加载动画)
        private void ShowLoadingIndicator()
        {
            // 示例:pictureBox1.Image = Properties.Resources.Loading;
        }

        // 显示错误图片(可根据项目需求实现)
        private void ShowErrorImage()
        {
            // 示例:pictureBox1.Image = Properties.Resources.Error;
        }

        // 释放资源(可选,优化内存)
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                httpClient?.Dispose();
                aTimer?.Dispose();
                pictureBox1?.Dispose();
            }
            base.Dispose(disposing);
        }
    }
}

5. 集成步骤(4 步完成)

  1. 在 WinForm 项目中创建frmLogin窗体,添加pictureBox1控件(用于显示二维码);
  2. 通过 NuGet 安装依赖包(Newtonsoft.Json、CefSharp.WinForms、DevComponents.DotNetBar);
  3. 复制上述完整代码到frmLogin.cs文件中,确保命名空间zhuang.codeprint.winform.Frms与项目一致;
  4. 检查全局变量Define.OpenId是否存在,若不存在需创建Define类并添加静态属性:
public static class Define
{
    public static string OpenId { get; set; }
}
  1. 运行项目,登录窗体将自动加载二维码,扫码成功后自动存储 OpenId 并跳转主页。

62fb962ab126bd604e3656214fd74235_v2-ddbe5261cf3a5ac0c9107e188e3e9b8d_1440w.jpg

四、注意事项

  1. 安全校验:Web 端务必保留event.origin校验,仅允许http://106.14.223.151:18007的回调,防止恶意请求;
  2. 依赖安装:WinForm 项目需确保所有依赖包版本匹配,避免兼容性问题;
  3. 控件配置:WinForm 窗体需添加pictureBox1控件,且SizeMode设为Zoom
  4. 接口稳定性:当前演示接口为测试环境,生产环境建议自行部署服务(后续将开放部署教程);
  5. 二维码有效期:默认 60 秒超时自动刷新,可通过修改MaxPollingAttempts参数调整(如改为 15 表示 30 秒超时);
  6. 全局变量Define.OpenId用于存储用户唯一标识,后续可通过该值实现用户信息查询、权限校验等逻辑。

五、后续迭代计划

  1. 开放完整部署教程,支持用户自行搭建服务;

  2. 增加自定义二维码样式(Logo、颜色、尺寸);

  3. 支持获取微信用户昵称、头像等基础信息;

  4. 增加扫码登录日志、异常重试机制;

  5. 适配更多平台(Unity、Electron 等)。

如果大家有其他需求或遇到集成问题,欢迎在评论区留言,我会第一时间回复并迭代优化~