trae兄弟分分鐘就幹完了,你敢相信麼-是有點像那麼回事
会员模块设计文档
会员模块實現
宠辱不惊,闲看庭前花开花落;去留无意,漫随天外云卷云舒。
📋 项目概述
本模块实现会员卡的完整业务流程,包括后台制卡、前台售卡激活以及后续的充值、消费、退款、积分兑换等功能。
- • 架构:WinForm桌面端(SQLite) + Web后端(ZRAdmin .NET 8) + 前端(Vue3)
- • ORM:SqlSugar(非Code First模式)
- • 金额单位:以"分"存储(long类型),避免浮点数精度问题
- • 密码安全:SHA256 + Salt加密
一、业务流程
1.1 会员卡生命周期
1
后台制卡 → 门店工作人员在后台创建未开通的会员卡
卡状态:未开通(0)
卡状态:未开通(0)
2
前台售卡激活 → 顾客付款后激活卡片,填写档案信息
卡状态:正常(1)
卡状态:正常(1)
3
日常业务 → 充值、消费、退款、积分兑换、修改密码等
4
状态变更 → 冻结、挂失、注销等
二、会员卡状态定义
| 状态值 | 名称 | 说明 | 可充值 | 可消费 | 可激活 |
|---|---|---|---|---|---|
| 0 | 未开通 | 制卡后的初始状态 | ❌ | ❌ | ✅ |
| 1 | 正常 | 正常使用的状态 | ✅ | ✅ | ❌ |
| 2 | 冻结 | 账户冻结 | ❌ | ❌ | ❌ |
| 3 | 挂失 | 卡片遗失挂失 | ❌ | ❌ | ❌ |
| 4 | 注销 | 卡片已注销 | ❌ | ❌ | ❌ |
三、数据模型
3.1 会员卡表 (member_card)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | long | 自增主键 |
| key_id | string(50) | 业务主键(Guid) |
| fk_company_id | string(50) | 公司ID |
| fk_store_id | string(50) | 门店ID |
| card_no | string(20) | 卡号 |
| chip_id | string(32) | 芯片ID |
| phone | string(11) | 手机号 |
| real_name | string(50) | 姓名 |
| password_hash | string(64) | 密码哈希 |
| password_salt | string(32) | 密码盐 |
| status | byte | 状态(0-4) |
| current_balance | long | 当前余额(分) |
| current_principal | long | 本金(分) |
| current_gift | long | 赠送金(分) |
| total_recharge | long | 累计充值(分) |
| total_consume | long | 累计消费(分) |
| total_points | int | 累计积分 |
| current_points | int | 当前积分 |
| discount_rate | decimal(5,2) | 折扣率(%) |
| open_date | datetime | 开卡日期 |
| create_time | datetime | 创建时间 |
3.2 会员充值记录表 (member_recharge)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | long | 自增主键 |
| key_id | string(50) | 业务主键 |
| card_id | long | 会员卡ID |
| card_key_id | string(50) | 会员卡业务主键 |
| recharge_no | string(32) | 充值单号 |
| recharge_type | byte | 类型(1=开卡,2=充值,3=赠送,4=退款) |
| amount | long | 本金金额(分) |
| gift_amount | long | 赠送金额(分) |
| pay_method | byte | 支付方式(1=现金,2=微信,3=支付宝,4=银行卡) |
| operator_id | string(50) | 操作员 |
| recharge_time | datetime | 充值时间 |
3.3 会员消费记录表 (member_consume)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | long | 自增主键 |
| consume_no | string(32) | 消费单号 |
| order_id | string(32) | 关联订单号 |
| total_amount | long | 消费总额(分) |
| actual_amount | long | 实际金额(分) |
| principal_amount | long | 本金扣款(分) |
| gift_amount | long | 赠送金扣款(分) |
| discount_amount | long | 折扣金额(分) |
| earned_points | int | 获得积分 |
四、API接口
| 方法 | 路径 | 权限 | 说明 |
|---|---|---|---|
| GET | /business/MemberCard/list | business:member:list | 查询会员列表 |
| GET | /business/MemberCard/{keyId} | business:member:query | 获取会员详情 |
| POST | /business/MemberCard/make | business:member:make | 后台制卡 |
| POST | /business/MemberCard/activate | business:member:activate | 售卡激活 |
| POST | /business/MemberCard/recharge | business:member:recharge | 会员充值 |
| POST | /business/MemberCard/consume | business:member:consume | 会员消费 |
| POST | /business/MemberCard/refund | business:member:refund | 会员退款 |
| POST | /business/MemberCard/adjust | business:member:adjust | 余额调账 |
| POST | /business/MemberCard/points/exchange | business:member:points | 积分兑换 |
| POST | /business/MemberCard/password | business:member:password | 修改密码 |
| POST | /business/MemberCard/status | business:member:status | 变更状态 |
五、业务规则
5.1 金额计算
- • 金额以分为单位存储,使用long类型避免浮点数精度问题
- • 前端显示时除以100转换为元
- • 消费时赠送金优先扣除,默认30%扣赠送金
5.2 密码安全
- • 使用SHA256 + Salt加密存储
- • Salt长度为16位随机字符串
5.3 消费扣款优先级
- 先扣赠送金(默认30%比例)
- 不足时用本金补足
- 本金不足则拒绝交易
5.4 积分规则
- • 消费1元获得1积分
- • 100积分可兑换1元抵现
六、文件清单
后端 (ZRAdmin .NET 8)
| 文件路径 | 说明 |
|---|---|
ZR.Model/Models/Business/MemberCard.cs |
会员卡实体 |
ZR.Model/Models/Business/MemberRecharge.cs |
充值记录实体 |
ZR.Model/Models/Business/MemberConsume.cs |
消费记录实体 |
ZR.Model/Dto/Business/MemberCardDto.cs |
数据传输对象 |
ZR.Service/Business/IBusinessService/IMemberCardService.cs |
Service接口 |
ZR.Service/Business/MemberCardService.cs |
Service实现 |
ZR.Admin.WebApi/Controllers/Business/MemberCardController.cs |
API控制器 |
前端 (Vue3)
| 文件路径 | 说明 |
|---|---|
src/api/business/member.js |
API接口封装 |
src/views/business/memberCard.vue |
会员管理页面 |
WinForm端
| 文件路径 | 说明 |
|---|---|
POSFramework/Entity/MemberCardStatus.cs |
会员卡状态枚举 |
POSFramework/Dto/MemberCardDto.cs |
Dto定义 |
POSFramework/Service/MemberCardService.cs |
业务逻辑 |
POSFramework/modules/MemberCardForm.cs |
会员主界面 |
POSFramework/modules/MemberCardMakeForm.cs |
制卡界面 |
POSFramework/modules/MemberCardActivateForm.cs |
售卡激活界面 |
七、核心代码片段
7.1 制卡 (MakeCard)
public string MakeCard(MemberCardMakeDto dto, string operatorId)
{
// 检查卡号或芯片ID是否已存在
var exists = Queryable()
.Where(c => c.CardNo == dto.CardNo || c.ChipId == dto.ChipId)
.First();
if (exists != null) throw new Exception($"卡号[{dto.CardNo}]已存在!");
var cardKeyId = Guid.NewGuid().ToString("N");
var card = new MemberCard
{
KeyId = cardKeyId,
FkCompanyId = dto.FkCompanyId,
FkStoreId = dto.FkStoreId,
CardNo = dto.CardNo,
ChipId = dto.ChipId,
Status = MemberCardStatus.NotActivated, // 未开通
DiscountRate = 100,
CreateBy = operatorId,
CreateTime = DateTime.Now
};
Context.Insertable(card).ExecuteCommand();
return cardKeyId;
}
7.2 售卡激活 (ActivateCard)
public string ActivateCard(MemberCardActivateDto dto, string operatorId)
{
var card = GetInfo(dto.CardKeyId);
if (card.Status != MemberCardStatus.NotActivated)
throw new Exception($"当前状态不能激活!");
// 更新档案
card.RealName = dto.RealName;
card.Phone = dto.Phone;
card.Status = MemberCardStatus.Normal; // 激活为正常
card.OpenDate = DateTime.Now;
card.CurrentBalance = dto.Amount + dto.GiftAmount;
card.CurrentPrincipal = dto.Amount;
card.CurrentGift = dto.GiftAmount;
// 设置密码
var (hash, salt) = HashPassword(dto.Password);
card.PasswordHash = hash;
card.PasswordSalt = salt;
Context.Ado.BeginTran();
Context.Updateable(card).ExecuteCommand();
// 创建充值记录...
Context.Ado.CommitTran();
return card.KeyId;
}
7.3 消费 (Consume)
public bool Consume(MemberConsumeDto dto, string operatorId)
{
var card = GetInfo(dto.CardKeyId);
if (card.Status != MemberCardStatus.Normal)
throw new Exception("会员卡状态不允许消费!");
// 计算折扣和实际金额
var discountAmt = (long)(dto.TotalAmount * (100m - card.DiscountRate) / 100m);
var actualAmt = dto.TotalAmount - discountAmt;
// 赠送金优先扣除(30%)
long giftDeduct = (long)(actualAmt * 30 / 100);
long principalDeduct = actualAmt - giftDeduct;
if (card.CurrentGift < giftDeduct) {
principalDeduct += giftDeduct - card.CurrentGift;
giftDeduct = card.CurrentGift;
}
if (card.CurrentPrincipal < principalDeduct)
throw new Exception("本金余额不足!");
card.CurrentPrincipal -= principalDeduct;
card.CurrentGift -= giftDeduct;
card.CurrentBalance = card.CurrentPrincipal + card.CurrentGift;
card.TotalConsume += principalDeduct;
// 创建消费记录...
return true;
}
✅ 完成状态
- ✓ WinForm端会员模块(含制卡、售卡激活)
- ✓ 后端ZRAdmin API Controller
- ✓ 前端Vue3会员页面
- ✓ 数据库表设计(需手动创建)
🔒 会员生物识别升级
将传统密码验证升级为多种生物识别方式,提升安全性和用户体验。
- • 人脸识别:基于ViewFaceCore开源人脸识别库
- • 指纹识别:预留接口,支持集成第三方指纹SDK
- • 多因素验证:支持密码、人脸、指纹混合验证
- • 扩展架构:支持后续添加虹膜、声纹等识别方式
一、升级目标
1.1 业务目标
- • 替代传统密码验证,提升会员身份认证安全性
- • 支持人脸直接识别会员,简化操作流程
- • 为后续扩展指纹、虹膜等识别方式奠定基础
- • 与现有考勤模块(viewface_attendance_ext)共用识别能力
1.2 技术目标
- • 识别准确率:人脸识别 ≥ 99%(阈值70以上)
- • 识别速度:单次识别 ≤ 500ms
- • 支持1:N识别(通过人脸查找会员)
- • 支持1:1验证(验证指定会员身份)
- • 支持多终端同步生物特征数据
二、验证方式对比
| 验证方式 | 安全性 | 便捷性 | 成本 | 实现状态 |
|---|---|---|---|---|
| 密码 | ★★☆ | ★★☆ | ★★★ | 原有 |
| 人脸识别 | ★★★ | ★★★ | ★★☆ | 新增 |
| 指纹识别 | ★★★ | ★★★ | ★★☆ | 预留接口 |
| 卡片 | ★★☆ | ★★★ | ★★☆ | 规划中 |
三、架构设计
3.1 整体架构
数据层
member_biometric - 生物特征存储
member_biometric_log - 识别日志
服务层
IBiometricService - 统一接口
FaceRecognitionService - 人脸识别
FingerprintService - 指纹识别
管理层
BiometricManager - 服务管理
MemberCardService - 会员验证
界面层
MemberBiometricForm - 生物识别管理
3.2 服务接口设计
public interface IBiometricService
{
byte BiometricType { get; }
string ServiceName { get; }
bool IsInitialized { get; }
bool Initialize(string modelPath = null);
Task<(bool success, string featureData, string message)> RegisterAsync(
string cardKeyId, Image image, int threshold, string operatorId);
Task IdentifyAsync(Image image, string companyId, string storeId);
Task<(bool success, decimal similarity, string message)> VerifyAsync(
string cardKeyId, Image image);
bool Delete(string cardKeyId, int indexNo, string operatorId);
void LogIdentify(string cardKeyId, byte result, decimal? similarity,
int durationMs, string deviceId, string operatorId, string remark);
}
3.3 管理器设计
public class BiometricManager
{
// 注册服务
void RegisterService(IBiometricService service);
// 验证会员(支持多种方式)
Task<(bool success, string message)> VerifyMemberAsync(
string cardKeyId, byte authMethod, object authData);
// 识别会员(1:N)
Task IdentifyMemberAsync(
byte biometricType, Image image, string companyId, string storeId);
// 注册生物特征
Task<(bool success, string message)> RegisterBiometricAsync(
string cardKeyId, byte biometricType, Image image, int threshold);
}
四、人脸识别实现
4.1 依赖库
| 库名 | 版本 | 用途 |
|---|---|---|
| ViewFaceCore | 6.0+ | 人脸识别核心库 |
| ViewFaceCore.Model | 6.0+ | 人脸模型定义 |
| AForge.NET | 2.2+ | 摄像头采集 |
4.2 识别流程
1
人脸检测 → FaceDetector.Detect(image)
检测图像中人脸位置和大小
检测图像中人脸位置和大小
2
关键点检测 → FaceLandmarker.Mark(image, faceInfo)
检测68个人脸关键点(眼睛、鼻子、嘴巴等)
检测68个人脸关键点(眼睛、鼻子、嘴巴等)
3
特征提取 → FaceRecognizer.Extract(image, faceInfo, markPoints)
提取512维人脸特征向量
提取512维人脸特征向量
4
特征比对 → FaceRecognizer.Compare(feature1, feature2)
计算余弦相似度(0-1)
计算余弦相似度(0-1)
5
结果判断 → similarity ≥ threshold
根据阈值判断是否匹配
根据阈值判断是否匹配
4.3 特征数据处理
// 序列化特征(存储到数据库)
private string SerializeFeature(FaceFeature feature)
{
return Convert.ToBase64String(feature.Data);
}
// 反序列化特征(从数据库读取)
private FaceFeature DeserializeFeature(string featureData)
{
var data = Convert.FromBase64String(featureData);
return new FaceFeature(data);
}
五、安全机制
5.1 失败锁定
- • 连续失败5次后锁定生物识别
- • 锁定时间:30分钟
- • 可由管理员手动解锁
- • 验证成功后自动重置失败次数
5.2 识别阈值
- • 默认阈值:70%
- • 阈值范围:50%-100%
- • 越高越严格,越低越宽松
- • 每个会员可独立设置阈值
5.3 日志记录
| 字段 | 说明 |
|---|---|
| card_key_id | 会员卡号 |
| biometric_type | 识别类型 |
| result | 结果(0=成功,1=失败,2=锁定) |
| similarity | 相似度 |
| duration_ms | 耗时(毫秒) |
| device_id | 设备ID |
| create_time | 时间 |
六、数据库升级
6.1 member_biometric 表
CREATE TABLE `member_biometric` (
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
`key_id` VARCHAR(50) NOT NULL,
`card_key_id` VARCHAR(50) NOT NULL,
`biometric_type` TINYINT NOT NULL COMMENT '2=人脸,3=指纹',
`feature_data` TEXT NOT NULL COMMENT '特征数据(Base64)',
`feature_version` INT DEFAULT 1,
`index_no` INT DEFAULT 0 COMMENT '索引号(多指纹)',
`is_enabled` BOOLEAN DEFAULT 1,
`threshold` INT DEFAULT 70,
`last_used_time` DATETIME,
`fail_count` INT DEFAULT 0,
`lock_expire_time` DATETIME,
`create_by` VARCHAR(50),
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
`update_by` VARCHAR(50),
`update_time` DATETIME
);
6.2 member_biometric_log 表
CREATE TABLE `member_biometric_log` (
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
`card_key_id` VARCHAR(50) NOT NULL,
`biometric_type` TINYINT NOT NULL,
`result` TINYINT NOT NULL COMMENT '0=成功,1=失败,2=锁定',
`similarity` DECIMAL(5,2),
`duration_ms` INT DEFAULT 0,
`device_id` VARCHAR(50),
`operator_id` VARCHAR(50),
`remark` VARCHAR(255),
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP
);
七、文件清单
| 文件路径 | 类型 | 说明 |
|---|---|---|
POSFramework/Entity/MemberBiometricEntity.cs |
Entity | 生物识别实体类 |
POSFramework/Service/IBiometricService.cs |
Interface | 生物识别服务接口 |
POSFramework/Service/FaceRecognitionService.cs |
Service | 人脸识别服务 |
POSFramework/Service/FingerprintService.cs |
Service | 指纹识别服务(预留) |
POSFramework/Service/BiometricManager.cs |
Manager | 生物识别管理器 |
POSFramework/modules/MemberBiometricForm.cs |
Form | 生物识别管理界面 |
POSFramework/Service/MemberCardService.cs |
Service | 会员服务(已修改) |
八、使用示例
8.1 初始化
// 创建生物识别管理器
var biometricManager = new BiometricManager();
// 注册人脸识别服务
biometricManager.RegisterService(new FaceRecognitionService());
// 初始化服务(加载模型)
biometricManager.InitializeAll("Models/Face");
8.2 注册人脸
// 获取摄像头图像
var faceImage = CaptureFaceImage();
// 注册人脸特征
var (success, message) = await biometricManager.RegisterBiometricAsync(
cardKeyId: "card123",
biometricType: BiometricType.Face,
image: faceImage,
threshold: 70,
operatorId: "admin"
);
8.3 验证会员
// 方式1:人脸验证
var (success, msg) = await memberService.VerifyMemberAsync(
cardKeyId: "card123",
authMethod: BiometricType.Face,
authData: faceImage,
biometricManager: biometricManager
);
// 方式2:密码验证
var (success, msg) = await memberService.VerifyMemberAsync(
cardKeyId: "card123",
authMethod: BiometricType.Password,
authData: "123456"
);
8.4 识别会员
// 通过人脸直接识别会员
var result = await biometricManager.IdentifyMemberAsync(
biometricType: BiometricType.Face,
image: faceImage,
companyId: "company001",
storeId: "store001"
);
if (result.Success)
{
var card = memberService.GetByKeyId(result.CardKeyId);
Console.WriteLine($"识别成功:{card.RealName}");
}
九、与考勤模块集成
9.1 共享识别能力
- • 使用相同的ViewFaceCore库进行人脸识别
- • 人脸特征存储在同一数据库表
- • 会员可使用人脸进行考勤打卡
- • 考勤打卡后自动记录会员消费
9.2 同步机制
- • 人脸特征数据通过UDP局域网同步
- • 定期与服务器MySQL同步
- • 支持离线识别(本地存储特征)
十、扩展计划
| 阶段 | 功能 | 状态 |
|---|---|---|
| Phase 1 | 人脸识别(注册、验证、识别) | 已实现 |
| Phase 2 | 指纹识别接口(预留) | 已实现 |
| Phase 3 | 集成指纹SDK(Upek/Synaptics) | 待实现 |
| Phase 4 | 虹膜识别(预留接口) | 规划中 |
| Phase 5 | 声纹识别(预留接口) | 规划中 |
| Phase 6 | 多因素认证(人脸+指纹) | 规划中 |
✅ 升级完成
- ✓ 人脸识别服务(基于ViewFaceCore)
- ✓ 指纹识别服务(预留接口)
- ✓ 生物识别管理器
- ✓ 会员验证逻辑升级
- ✓ 生物识别管理界面
- ✓ 数据库表设计
- ✓ 安全机制(失败锁定、日志记录)
作者:数据酷软件
出处:https://www.cnblogs.com/datacool/p/20780356/datacool-membar
关于作者:20年编程从业经验,持续关注工业自动化
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明。
联系方式: qq:71008973;wx:6857740733
基于人脸识别的考勤系统 地址: https://gitee.com/afeng124/viewface_attendance_ext
自己开发安卓应用框架 地址: https://gitee.com/afeng124/android-app-frame
WPOS(warehouse+pos) 后台演示地址: http://47.239.106.75:8080/


浙公网安备 33010602011771号