门店核销闭环:小程序扫码 + .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. 小程序提审被驳:类目不符、诱导分享、域名未备案、没填隐私指引,是最常见的四条驳回理由。
相关推荐
samble4 小时前
从零搭建灌装监控系统(十三):报警系统设计,生命周期与通知
c#·wpf·mvvm·modbus·工业控制
miofly6 小时前
GitHub 今日推荐|SCSKiller:游戏着色器预编译消除卡顿
开源·c#·github
愚公搬代码8 小时前
【愚公系列】《微信小程序项目实战(AI编程+视频图解)》014-莫凡商城小程序项目视图容器组件的应用
微信小程序·小程序·ai编程
周杰伦fans21 小时前
8GB显存下模型量化实战指南
人工智能·后端·c#
星光开发者1 天前
基于决策树随机森林算法的小说推荐系统的设计与实现-计算机毕设【课程设计】78485
vue.js·算法·微信小程序·django·php·课程设计·express
samble1 天前
从零搭建灌装监控系统(十二):配置系统,原子写入与容错
c#·wpf·mvvm·modbus·工业控制
流形填表1 天前
题库去重实战:基于题干指纹的重复题清理
开发语言·c#
开开心心就好1 天前
以图搜图找重复图片,本地工具离线就能用
javascript·智能手机·ffmpeg·c#·ocr·word·音视频
QQ_21696290961 天前
基于微信小程序的智能膳食分析系统
java·大数据·spring boot·微信小程序·小程序·旅游