门店核销闭环:小程序扫码 + .NET 幂等接口 + WPF 看板,一天能跑通

做线下门店类外包,核销是绕不开的需求。本文把一套最小可用的闭环拆开:小程序负责扫、.NET WebAPI 负责判、WPF 负责看和打。

一、为什么是三端,而不是一个后端搞定

  • 顾客/店员在手机上操作 → 必须小程序(扫码、wx.login、订阅消息都是小程序能力)。
  • 判定「码是否有效、扫过没」→ 必须服务端,且要扛并发。
  • 门店电脑要看实时数据、打小票 → 桌面端最稳,断网也能查本地记录。

三端解耦后,哪块要迭代都不动另外两块。

这里插一句前端工程上的小事:小程序原生 wx.request 是回调式的,多人协作建议包一层 Promise,统一带 Authorization 和错误提示,否则每个页面都写一遍 wx.showLoading/hideLoading 很快会乱。我习惯在项目里放一个 request() 封装,所有页面只调它,核销、登录、订阅下发走同一套错误处理。

二、小程序端:扫完调用接口

js 复制代码
Page({
  scan() {
    wx.scanCode({
      onlyFromCamera: true,
      scanType: ['qrCode', 'barCode'],
      success: (res) => this.verify(res.result)
    })
  },
  async verify(code) {
    const token = wx.getStorageSync('token')
    const { data } = await request({
      url: 'https://api.yourdomain.com/api/verify',
      method: 'POST',
      header: { Authorization: 'Bearer ' + token },
      data: { code }
    })
    wx.showToast({ title: data.ok ? '核销成功' : (data.msg || '失败'), icon: data.ok ? 'success' : 'none' })
  }
})

授权用 wx.login 拿 code,后端走 code2session 换 openid,session_key 只留服务端。订阅消息在核销成功后 wx.requestSubscribeMessage 申请,后端用缓存的 access_token 下发。

三、核心:.NET 核销接口的幂等

重复核销 = 同码并发或连扫两次。用「行锁 + 状态位」解决:

csharp 复制代码
[HttpPost("api/verify")]
public async Task<IActionResult> Verify([FromBody] VerifyDto dto)
{
    await using var tx = await _db.Database.BeginTransactionAsync();
    var c = await _db.Coupons
        .FromSqlInterpolated($"SELECT * FROM Coupons WITH (UPDLOCK, HOLDLOCK) WHERE Code={dto.Code}")
        .FirstOrDefaultAsync();
    if (c == null) return NotFound(new { msg = "核销码不存在" });
    if (c.Status == 1) return Conflict(new { msg = "该码已核销" }); // 第二次扫直接拦截
    c.Status = 1; c.VerifiedAt = DateTime.Now; c.Operator = dto.Operator;
    await _db.SaveChangesAsync();
    await tx.CommitAsync();
    _ = SendVerifyNotice(c.OpenId);
    return Ok(new { ok = true, no = c.No });
}

SQL Server 用 UPDLOCK + HOLDLOCK,MySQL 用 FOR UPDATE,SQLite 单机靠唯一约束 + 事务。返回 409 给前端提示「已核销」,比笼统的「失败」体验好很多。

顺手说一下怎么验证幂等真的生效:本地起两个线程或用一个接口压测工具,对同一 Code 同时发十次 POST,正确结果是恰好一次 ok=true、其余九次都是 409。如果十次都成功,说明锁没生效或事务边界没包住判断------这是上线前必测的一项,很多团队跳过这步,上线当天被顾客连扫两下就露了馅。

四、WPF 门店看板 + 小票

门店网络不稳,本地 SQLite 存一份记录:

csharp 复制代码
public List<VerifyLog> Today() => new SQLiteConnection("Data Source=store.db;")
    .Query<VerifyLog>("SELECT * FROM VerifyLog WHERE date(VerifiedAt)=date('now') ORDER BY VerifiedAt DESC")
    .ToList();

小票走串口 ESC/POS(热敏机通用指令):初始化 \x1B\x40,切纸 \x1D\x56\x00。

桌面端在这里的价值很直接:扫码、看数、出纸全在一台门店电脑上闭环,不依赖浏览器、不依赖网速,开机就能干活。

五、数据表与部署要补的细节

核销接口依赖一张券表,最小字段就够了:

sql 复制代码
CREATE TABLE Coupons (
  Id INT IDENTITY PRIMARY KEY,
  Code NVARCHAR(64) UNIQUE NOT NULL,  -- 核销码,全局唯一
  No   NVARCHAR(32),
  Status TINYINT NOT NULL DEFAULT 0,   -- 0 未核销 / 1 已核销
  OpenId NVARCHAR(64),
  Operator NVARCHAR(32),
  VerifiedAt DATETIME
);

Code 上加唯一约束,是从数据库层面给幂等再上一道保险:即使应用层锁失效,重复写入也会抛唯一冲突。小程序端还要在后台把接口域名加进 request 合法域名白名单(本地可勾「不校验合法域名」调试),并填好隐私指引声明收集 openid 的用途。WebAPI 部署到公网 HTTPS 即可,回调地址必须外网可达。

六、上线前两个最容易踩的坑

  1. 幂等没测透:拿两个线程同时打同一码,看是否只有一个生效。
  2. 小程序提审被驳:类目不符、诱导分享、域名未备案、没填隐私指引,是最常见的四条驳回理由。
相关推荐
吴可可12310 小时前
C#调用摄像头拍照的三种API方案
c#
Msir_Modern15 小时前
Noita 元胞沙盒笔记
算法·unity·c#·游戏引擎·元胞自动机
星之擎17 小时前
Task 很优雅的取消方式是什么?C# 里面。
开发语言·c#
andrsted17 小时前
企业培训时间难统一?练题簿让在线免费刷题与培训考核更好安排
学习·微信小程序·小程序·学习方法·刷题
拖孩17 小时前
一个人 + AI 做的小程序,今天,正式回本了
前端·后端·微信小程序
czhc114007566318 小时前
一周概念复盘:预热、漂移与门禁
c#
李纲明18 小时前
微信小程序 wx.uploadFile 上传失败:合法域名、临时路径与 HTTPS 证书排查
微信小程序·小程序·uploadfile·devdiy·上传失败
云中飞鸿19 小时前
.Net和C#有什么关系?(从C++视角观察)
开发语言·c#·.net
淡海水21 小时前
07-04-源码-.NET Core GC清扫与压缩
unity·架构·c#·.netcore·gc·1024程序员节
zwm2698888151 天前
hd 3#负一西送风,能远程启不能远程停
c#