简介:一份基于 Java SpringBoot 与 MySQL 的 fuint 会员营销系统完整源码,面向实体店铺经营者、产品经理及 Java 后端开发者,用于解决会员管理、精准营销与收银一体化问题。系统内置优惠券、预存卡、集次卡(计次)、储值卡、电子券、会员积分、支付收款等核心模块,覆盖汽车 4S 店、花店、甜品店、餐饮门店及线上电商等多种零售场景,既可独立作为会员中台,也能充当收银系统使用,降低多套系统并行带来的数据割裂成本。资源包共 531 个文件,其中以 422 个 Java 文件为主体,另有 XML 配置、SQL 数据库脚本、Shell 部署脚本、PNG/JPG 图片素材及 properties 配置等,压缩包体积约 5.5MB,目录结构清晰。目前已有 696 人学习下载。整套源码包含前台微信小程序、H5、后端 API 与后台管理四端,适合需要搭建私域会员体系或研究 SpringBoot 多端项目架构的读者参考,可快速移植到实际店铺运营中。
1. 实体店铺会员管理和营销系统:先分清四个端点再动手
一家服装线下体验店的做法是:试穿不设导购催单,结账时店员只说一句“加个微信,小程序里下单能积分”,顾客回到家收到微信里的会员卡和一张满减券。这套东西背后就是标题里那句“实体店铺会员管理和营销系统,包含前台微信小程序、h5、后端api、后台管理三部分”。它要解决的不是“做一个小程序”,而是把散在收银台、柜员、微信聊天和纸质卡里的会员信息,收进同一套账本:小程序和H5对顾客是前台,后端API是记账先生,后台管理是店长看板。适合谁?单店老板想搞会员体系,或者接单的小团队、独立开发者想按这套结构交付。拆开看其实是四个端,缺一个,会员系统就会变成“只能看不能买”的摆设。
2. 四端架构与技术选型:微信小程序、H5、后端API、后台管理怎么配合
2.1 选型对比:原生小程序、uni-app、Java后端、Node后端怎么选
| 端 | 常见选型 | 我推荐 | 边界提醒 |
|---|---|---|---|
| 微信小程序 | 原生 WXML / uni-app / Taro | uni-app,团队会 Vue 就能上手 | 别用纯 H5 套壳,交互和登录态都是坑 |
| H5 | Vue3 + Vant / React + antd-mobile | Vue3 + Vant | 别用 jQuery 拼页面,后期改不动 |
| 后端 API | Spring Boot / Node.js(NestJS) / PHP | Spring Boot | 涉及钱和账,别用无事务的脚本框架 |
| 后台管理 | Vue3 + Element Plus / React + Ant Design | Vue3 + Element Plus | 别一上来接重型工作流引擎 |
实体店会员系统的核心是钱和账,选型的权重跟纯互联网项目不一样。用户量不大,并发也不高,但储值、积分、次卡、优惠券这几样都涉及资金流水,后端必须把事务、行锁、对账这些基本功扛住。Spring Boot 在这方面最稳,社区资料也多,遇到问题搜得到答案。Node.js 不是不行,但团队里如果没人写过事务补偿,后面查账会很痛苦。
小程序端选 uni-app 的理由很务实:一套代码同时出微信小程序和 H5。标题里正好两个前台都要,uni-app 能省掉一半重复工作。但它有个代价——主包会比原生的大,后面 5.1 会专门讲 2MB 超限怎么拆。如果你只做微信小程序、不做 H5,原生开发其实更轻,渲染性能也更好,不存在编译这一层黑匣子。
H5 端在这个系统里承担的角色是“不占小程序的流量入口”:朋友圈分享、公众号菜单、短信里附带的链接,用户不用装 App、不用打开微信扫小程序码,点开就能逛。登录方式不要复用微信小程序的 code 换 openid,H5 老老实实用手机号验证码登录,或者走公众号网页授权。同一个手机号在不同端注册,后端用手机号作为会员主键合并,这是后面登录态不串号的前提。
2.2 微信小程序登录获取手机号:从 wx.login 到静默注册的一条链路
小程序端最容易被新手写错的,是把“静默登录”和“获取手机号”混成一步。正确链路是:进来先 wx.login 拿临时 code,后端用 code 换 openid,创建或找到会员,发一个自己的 token 给前端;用户点击“获取手机号”按钮后,再把新的动态 code 传给后端换手机号。手机号是敏感信息,不能在小程序端自己解析。
// pages/login/login.js Page({ onLoad() { this.silentLogin() }, // 1. 静默登录:wx.login 拿 code,后端换 openid,返回业务 token silentLogin() { const that = this wx.login({ success(res) { wx.request({ url: 'https://api.yourshop.com/api/mp/login', method: 'POST', data: { code: res.code }, success(r) { if (r.data.code === 0) { wx.setStorageSync('token', r.data.data.token) } } }) } }) }, // 2. 用户主动点击“获取手机号”按钮后,把动态 code 发给后端绑定 onGetPhone(e) { const phoneCode = e.detail.code // 注意不是手机号明文 wx.request({ url: 'https://api.yourshop.com/api/mp/bindPhone', method: 'POST', data: { code: phoneCode }, header: { Authorization: wx.getStorageSync('token') }, success() { wx.showToast({ title: '绑定成功' }) } }) } })后端收到 login 里的 code 后,拿 code 去调微信的 jscode2session 接口,换回 openid 和 session_key。openid 用来查会员表,存在就返回 token,不存在就建一条新会员记录再返回 token。session_key 只在后端使用,不要下发到前端,也不要拿它当业务凭证,它只是后续解密手机号、解 unionId 用的钥匙。
bindPhone 接口收到的 code 是微信端生成的动态手机号凭证,后端拿它换手机号明文。这里有个容易被忽略的点:新用户第一次进小程序时还没有手机号,店铺照样要让他能浏览、能领新人券,只是下单前必须补手机号。所以设计会员表时,openid 和 phone 都允许为空,登录态靠 token 维持,手机号是后续逐步补齐的。
提示:wx.login 的 code 五分钟有效,且只能用一次。后端接口要做幂等,同一个 code 被重复提交时,第二次必须返回明确错误,不能把同一笔用户记录建两次。
2.3 同一份API喂饱三端:响应封装和按端裁剪
后台管理要看到会员完整手机号,小程序和 H5 只能看脱敏后的号码;后台管理能看到成本价,前端永远不能。做法是后端统一返回 Result 结构,再按端裁剪字段。
// 统一响应封装 @Data public class Result<T> { private int code; // 0 成功,非 0 业务错误 private String message; private T data; } @GetMapping("/api/member/info") public Result<MemberVO> info(@RequestHeader("clientType") String clientType) { Member m = memberService.getCurrent(); if ("admin".equals(clientType)) { return Result.ok(MemberVO.fullInfo(m)); // 含完整手机号、归属门店、备注 } return Result.ok(MemberVO.shortInfo(m)); // 脱敏手机号 + 余额 + 积分 }接口路径建议按端分开:/api/mp/**给小程序,/api/h5/**给 H5,/api/admin/**给后台管理。同一个会员详情接口,三端各自调各自的路径,后端内部共享同一个 service,返回不同 VO。token 也要区分:小程序 token、H5 token、后台 token 是三种东西,Redis 里的 key 前缀不同,过期时间不同,权限范围也不同。
按端裁剪看着是小事,实际是后台管理和小程序共用后端 API 时最常见的翻车点。有的项目图省事,所有端调同一个接口,后台能看到的数据前端也能看到,店铺毛利、进货成本全暴露了。接口文档里必须把每个字段标注清楚哪些端可见,上线前拿 postman 或 charles 抓一遍包核对,这一条能省掉大量售后投诉。
3. 会员数据模型与储值闭环:储值、积分、次卡先想清楚再写代码
3.1 会员主表与三本账:储值流水、积分流水、次卡消费的落库字段
会员系统最忌讳“只存一个余额字段”。储值余额、积分、次卡剩余次数这些数字,本质上都是“结果”,真正的底账是流水。余额可能是错的,流水永远不会错——对账、退款、纠纷处理,全靠流水还原事实。
-- 会员主表 CREATE TABLE member ( id BIGINT PRIMARY KEY AUTO_INCREMENT, phone VARCHAR(20) DEFAULT NULL, openid VARCHAR(64) DEFAULT NULL, balance DECIMAL(10,2) NOT NULL DEFAULT 0.00, -- 储值余额,冗余字段 points INT NOT NULL DEFAULT 0, -- 积分余额,冗余字段 level TINYINT NOT NULL DEFAULT 0, -- 会员等级 status TINYINT NOT NULL DEFAULT 1, -- 1 正常 0 冻结 created_at DATETIME NOT NULL ); -- 储值流水表,资金账本,只追加不修改 CREATE TABLE wallet_flow ( id BIGINT PRIMARY KEY AUTO_INCREMENT, member_id BIGINT NOT NULL, amount DECIMAL(10,2) NOT NULL, -- 正数收入,负数支出 biz_type TINYINT NOT NULL, -- 1 充值 2 消费 3 退款 4 赠送 ref_no VARCHAR(64) NOT NULL, -- 关联单号:订单号/充值单号/退款单号 remark VARCHAR(255) DEFAULT '', created_at DATETIME NOT NULL, KEY idx_member (member_id, created_at) );积分流水和次卡消费流水结构类似,biz_type 换成积分的变动类型(赚取、消费、过期)、次卡的核销记录即可。一句话总结:余额字段可以冗余在会员表上方便查询,但每一次变动必须写流水,而且流水表和业务单据(订单、充值单)要能对上。ref_no 就是用来关联的,查账时一条流水对应一张单据,缺一个就是漏洞。
实体店场景下,这张会员主表别过度设计。有的团队一上来就加性别、生日、职业、喜好一堆字段,实际运营根本用不上。先保留手机号、openid、余额、积分、等级、状态这六个核心字段,等营销活动真需要生日券了,再加 birthday 也不迟,MySQL 加字段的成本很低。
3.2 储值扣款为什么必须走事务:余额被扣成负数的一次复盘
储值扣款是最容易出资金事故的地方。两个请求同时进来,都读到余额 10 元,各扣 8 元,如果不用锁,两个请求都认为自己扣成功了,最后余额变成 2 元,店铺白亏 6 元。解决办法是数据库行锁加事务。
@Service public class WalletService { @Transactional(rollbackFor = Exception.class) public void consume(Long memberId, BigDecimal amount) { // 1. 行锁读余额,同一时刻只有一个事务能拿到这一行 MemberWallet wallet = walletMapper.selectByMemberIdForUpdate(memberId); if (wallet.getBalance().compareTo(amount) < 0) { throw new BizException("余额不足"); } // 2. 扣减余额 wallet.setBalance(wallet.getBalance().subtract(amount)); walletMapper.updateById(wallet); // 3. 写流水,流水和扣款必须在同一个事务里 walletFlowMapper.insert(WalletFlow.consume(memberId, amount, orderNo)); } }selectByMemberIdForUpdate 是 MySQL 的悲观锁,查询命中会员主键后,这行记录被锁住,其他事务必须等当前事务提交才能继续。配合 @Transactional,扣余和写流水要么一起成功,要么一起回滚,不会出现“钱扣了流水没记”的烂账。
很多人问能不能用 Redis 的 DECR 先扣,再异步同步到数据库。我的建议是实体店这种量级完全没必要。一台门店收银系统一天能跑几百笔消费已经算忙了,MySQL 行锁完全扛得住。Redis 预扣方案还要处理 Redis 宕机、数据回拨、对账差异,复杂度翻倍,收益为零。这个小系统的“铁律”就是:涉及钱的变更,全部走数据库事务,不做异步扣款。退款同理,必须走事务,并且事务里要校验原订单是否已退过,避免重复退款。
3.3 会员等级与成长值:决定优惠力度的计算口径
会员等级用来做差异化营销:普通会员不打折,银卡 95 折,金卡 9 折,黑卡 85 折加生日双倍积分。等级计算的依据是成长值,成长值的设计要简单——消费 1 元积 1 成长值,签到送小份成长值,生日当天双倍。等级门槛按店铺客单价倒推:比如平均客单价 100 元,银卡门槛可以设在累计消费 1000 元,金卡 3000 元,这样顾客大概来 10 次就能升一级。
等级变更要记录日志,因为折扣和权益直接跟等级挂钩。用户差 10 元升级,结账时系统自动提醒“再买 10 元可升级金卡”,这一句话往往就能拉高当期客单价。实现上,会员表里 level 字段只存当前等级,另建一张 level_log 记录每次升降级的时间、原因、操作人。活动运营要发“保级关怀”时,直接从 level_log 里捞一个月内降级的用户。
等级体系最容易踩的坑是“全店统一折扣”。连锁店如果有的门店是加盟的,加盟商未必愿意参加总部的折扣活动。我一般建议把折扣字段提到门店配置层面:总部定等级门槛,各门店自己定折扣力度,后台管理里给店长一个开关。这个设计在单店阶段看不出价值,一开分店就变成刚需。
4. 营销玩法落地:优惠券、次卡、微信订阅消息的主链路设计
4.1 优惠券的生成、领取与核销:有效期、限领与幂等
优惠券玩法看起来简单,落地时最容易在“核销”这个环节翻车。生成和领取都是常规写库,真正难的是核销时的校验:券是否属于当前用户、是否过期、是否已使用、是否和当前订单匹配,以及同一笔订单重复提交时不能被核销两次。
@Service public class CouponService { @Transactional(rollbackFor = Exception.class) public void useCoupon(Long memberId, Long couponId, String orderNo) { // 1. 幂等:同一订单号已经核销过就直接返回,不重复扣减优惠 CouponUsage usage = usageMapper.selectByOrderNo(orderNo); if (usage != null) { return; } // 2. 查券并校验状态 Coupon coupon = couponMapper.selectByMemberAndId(memberId, couponId); if (coupon == null || coupon.getStatus() != 0) { throw new BizException("券不存在或不可用"); } if (coupon.getEndTime().before(new Date())) { throw new BizException("券已过期"); } // 3. 标记已用,写核销流水 coupon.setStatus(2); // 0 未用 1 锁定 2 已核销 couponMapper.updateById(coupon); usageMapper.insert(memberId, couponId, orderNo); } }这个接口有三个关键点。第一个是幂等,orderNo 是核销的唯一凭证,同一笔订单无论用户怎么重复点击、重复调接口,最终只生效一次。第二个是状态流转,券从“未用”到“已核销”最好有“锁定”这个中间态,用户下单时先锁券,支付成功再正式核销,支付失败就解锁,这样能避免用户同时下两单、一券两用。第三个是有效期校验必须用数据库时间或者后端服务器时间,不能用前端传过来的时间,前端调一下系统时间这张券就“复活”了。
券的领取规则也要控:新人券每人限领一张、节日券每个账号限领一张、分享裂变券每天限领三张。最简单的实现是 Redis 里放一个领取记录 key,过期时间和活动结束时间一致。接口层面再挂一个频控,同一 IP 一分钟只能领五次。别小看这个频控,实体店活动上线后被人拿脚本刷券,刷走一万张满减券的案例是真实存在的。
4.2 微信小程序订阅消息授权弹框:一次性模板的触达边界与变通
小程序和 H5 的触达逻辑完全不同。H5 如果挂在公众号菜单里,可以用服务号模板消息,一条消息能提前设置好,用户不感知。小程序里的订阅消息则是典型的“一次性授权弹框”:用户不点上那个弹框,你一条消息都发不出去;点了一次,也只能发一条模板消息。
微信小程序订阅消息授权弹框的规则是:用户点击“允许”后,开发者获得一次下发机会,用完就得等用户下一次主动触发。唯一能做“变长期”的路径,是用户在弹框里勾选“总是保持以上选择”,这样同一条模板在有效期内可以多次下发,但这不是开发者能强制做到的,完全看用户意愿。
所以运营策略要跟着规则走:别在用户刚注册时一次性把“开卡提醒”“消费通知”“优惠到期”三张模板全弹出来,用户大概率一个都不点。常见是排队提醒、核销成功、余额变动通知这三类,每一类都在对应业务动作发生时才弹一次授权。比如用户支付成功,弹“余额变动提醒”的授权,用户刚花完钱,这时候允许率最高。下一次储值扣款,这条模板消息就能发出去,用户看到余额变动的反馈,反而觉得系统正规。
真正需要“群发触达”的营销,比如“您有一张 20 元券即将到期”,靠订阅消息是不可靠的。替代方案是接一家短信服务商,成本一条几分钱,但送达率接近百分之百。实体店铺的复购周期本来就长,短信是性价比最高的触达方式,配合 H5 落地页,用户点开短信链接就能看到券并到店核销。
4.3 到店自提与免密核销:把营销动作收口到门店
券发了、消息触达了,最后一步是核销。实体店的核销场景分两种:用户在收银台结账时报手机号,店员在后台管理里手工核销;另一种是用户在小程序里收到券,到店出示核销码,店员拿后台的扫码枪扫一下。第二种体验更好,也更适合“线上下单、到店自提”的玩法。
服装体验店那种“不售卖、加微信、推小程序、小程序下单”的模式,核销码是刚需。用户在 H5 或小程序里选中商品、用优惠券抵扣、下单付款,到店后出示一个二维码,店员一扫看到订单号和券状态,确认后就地发货。这个流程里有一个容易被忽略的点:核销操作要记录操作人,后台管理里能查到“这张券是哪个店员在哪个门店核销的”,出了纠纷有据可查。
免密核销要注意二维码的时效性。静态二维码可以被截图转发,我见过有人把核销码截图发给朋友,朋友拿去门店冒领。解决方案是生成动态二维码,每三十秒刷新一次,店员扫码时后端校验二维码的时间和订单状态。实体店系统里这个刷新机制很简单,小程序端定时器重新拉一个码就行,别让用户长时间停留在一个静态页面。
5. 避坑指南:这5个坑决定会员系统能不能上线
5.1 小程序包体超2MB:uniapp 打包里的分包与主包瘦身
现象:uni-app 项目打包后提示source size 2612kb exceed max limit 2mb,微信开发者工具拒绝预览,真机也跑不了。
原因:微信小程序主包限制 2MB,uni-app 会把公共组件、页面、图片都塞进主包。活动页、会员详情页、海报分享页这些低频页面占了大量体积,而首页和支付页其实很小。
解决:用微信的分包机制,把非核心页面拆进 subPackages。
{ "pages": [ "pages/index/index", "pages/order/confirm", "pages/member/index" ], "subPackages": [ { "root": "pages/marketing", "pages": [ "coupon/coupon", "signin/signin", "invite/invite" ] } ] }tabBar 页面必须留在主包,分包里的页面不能互相 require 主包之外的模块。图片不要本地存放,全部走 CDN,主包里只留图标和启动图。分包配置完,主包体积立刻能从 2.6MB 降到 1.2MB 以下。还有一类隐形体积来自 uni-app 自带的组件库,如果只用了 button 和 input,考虑换成原生组件,编译产物能再小不少。
5.2 登录态在 H5 和小程序之间串号
现象:同一个手机,打开微信小程序是用户 A,再打开 H5 链接却显示用户 B 的会员信息;或者反过来,后台管理里查到的会员资料跟顾客实际身份对不上。
原因:小程序和 H5 共用了同一套 token 存储机制。uni-app 在小程序里用wx.setStorageSync存 token,H5 端在浏览器里用 localStorage 存 token,两边存储的 key 名一模一样。如果同一个域名下既有 H5 又有小程序的 web-view,浏览器端 token 就被小程序的 web-view 覆盖了。
解决:token 的 key 按端加前缀,小程序存MP_TOKEN,H5 存H5_TOKEN,后端 Redis 里同样区分前缀。H5 嵌在小程序 web-view 里时,WebView 有一套独立的 storage,把 H5 的 token 通过 URL 参数传进去,H5 拿到参数写自己的 storage,而不是去读小程序的。
更彻底的方案是后端在签发 token 时带上 clientType,每次请求校验 token 的端类型和当前请求路径的端类型。小程序 token 被放到 H5 请求头里,直接拒绝。这个校验加在网关或拦截器里,几行代码的事,能拦掉很多跨端串号的隐性 bug。
5.3 微信小程序 10002 与顶部导航栏高度:真机上的两块黑匣子
现象:真机上偶发请求失败,报 10002;自定义导航栏在 iPhone 上偏上、在安卓上偏下,胶囊按钮盖住了标题。
原因:10002 在小程序里属于请求阶段的网络异常,最常见于开发工具里没勾选“不校验合法域名”、真机上请求地址没配到 request 合法域名列表、或者接口域名证书过期。顶部导航栏错位是因为自定义导航需要自己算状态栏高度,不同机型状态栏高度不一样,硬编码一个固定值必然翻车。
解决:域名校验和证书问题在微信公众平台后台把 request 合法域名配齐,开发工具里打开“不校验合法域名”只用于本地联调。导航栏高度用接口取,不要硬编码。
// 获取胶囊按钮位置和状态栏高度,动态计算自定义导航栏高度 const menuRect = wx.getMenuButtonBoundingClientRect() const { statusBarHeight } = wx.getWindowInfo() // 导航栏总高度 = 状态栏高度 + 胶囊高度 + 胶囊上下间距 const navHeight = menuRect.height + (menuRect.top - statusBarHeight) * 2拿到 height 后,页面的自定义导航容器设置成这个高度,顶部内容向下偏移同样的值。注意wx.getSystemInfoSync已经废弃,新版本用wx.getWindowInfo。真机兼容有时就是玄学,iPhone 和安卓各借一台测试机,把胶囊按钮位置打出来对比,比反复猜测快得多。
5.4 营销活动被薅:负数、重复领取与并发脚本
现象:满减券被脚本刷走几千张;储值余额在活动期间被扣成负数;同一个新人礼包被同一个手机号领了三次。
原因:接口没有幂等,没有频控。领取接口只判断“用户是否领过”,但同一用户同时发五个请求,五个请求都查到“没领过”,然后都执行插入。扣款的并发问题前面说过,本质上是同一类问题:先查后写,中间没有锁。
解决:领取接口加“每人限领 + 频控 + 重复请求拦截”。每人限领用数据库唯一索引兜底,比如uk_member_coupon(member_id, coupon_activity_id),并发插入时只有一个能成功。频控在 Redis 里做,key 是活动ID:用户ID:日期,一分钟只能请求五次。扣款接口用悲观锁和事务。所有涉及券、余额、积分的接口都补一个请求流水号参数,后端用 Redis SETNX 做幂等,重复请求直接返回上一次结果。
5.5 审核被拒:会员储值为什么会被判成虚拟支付
现象:小程序提审时被拒,理由是“涉及虚拟支付”,但你的店铺明明是实体店,卖的是真实商品。
原因:微信小程序 iOS 端禁止虚拟支付,会员储值、余额充值这类功能容易被系统判定为“充值虚拟资产”。哪怕余额最终是用来买实体商品的,审核人员看到的只是“充值”这个动作。
解决:不要在收银台页面直接引导用户“储值 200 送 20”。常见做法是:小程序/H5 只展示余额和消费记录,不提供充值入口;储值在门店收银台完成,店员在后台管理里录入充值单并收款;或者走“线下扫码付定金、到店核销转储值”的变通路径。提交审核时,类目选“实体店 - 零售/商超”,简介里写清楚是线下门店会员服务,不要出现“充值、储值、余额支付”这类字眼。这个坎迈过去之后,后续版本迭代会顺畅很多。
6. 从单店到多店:把会员系统做成能验证的经营工具
6.1 经营看板怎么做:三个比一天流水更值得盯的指标
会员系统上线后,后台管理不能只展示“今日新会员 5 人”这种浅层数据。门店老板真正该盯的是三个指标:复购率、储值余额、券核销率。复购率反映会员体系起没起作用——近九十天内产生过两笔及以上订单的会员数除以活跃会员数;储值余额反映店铺的现金流蓄水池——余额越高,说明顾客把未来的消费提前锁在了店里;券核销率反映营销活动有没有被浪费——发了 1000 张券,核销 80 张,核销率 8%,说明券给得再多也没用,不如缩小券量、提高面额。
后台管理里给这三组数各放一张趋势图,按周看,比看日流水有用得多。数据落地不复杂,复购率和核销率用一条聚合 SQL 就能算,指标口径定下来之后写死,别让店长自己调。报表页面单独走一个查询服务,别跟会员列表抢同一个接口,门店拉报表的时候把收银接口拖慢了得不偿失。
6.2 多门店与角色拆分:数据权限先于功能开发
开第二家店之前,先想清楚数据权限。最常见的设计是加一张门店表和一张员工表,会员表上挂 store_id,不同角色的账号只能看自己门店的数据。店长看本店全部报表,店员只有核销和录单权限,总部账号跨门店看全局。这里有一个容易忽视的坑:一个顾客可能在小程序上注册,然后去任意一家分店消费。会员的归属门店只能按“首次开卡门店”记录,但余额必须全门店通用,积分也全门店通用,只有业绩归属算到核销门店头上。这个口径如果不定清楚,两家店会因为业绩分成吵起来。
我自己做第一个版本时,把会员表设计了二十多个字段,后来砍掉一半才真正跑顺。会员系统做得好的门店,核心不是功能有多花哨,而是储值、积分、优惠券这三条钱和券的链路从不出错,后台数据老板看得明白。希望帮到你。
本文还有配套的精品资源,点击获取