拓冰建站拓冰建站
首页 / 资讯中心 / 正文

FastAdmin多开版源码解析:超人名片2.8.9租户隔离实现

简介超人名片2.8.9小程序源码多开版是一套面向微信小程序开发者及企业用户的名片管理解决方案支持同时运行多个名片应用实例并集成了雷达代理无限版功能便于多项目切换、客户行为分析与精准营销。资源共3138个文件压缩包约38.29MB其中以js逻辑脚本、wxml/wxss页面结构、json配置及png/gif图片素材为主同时包含html、php、less等前后端辅助文件目录结构完整适合具备一定小程序基础的开发者进行二次开发与定制。当前已有385人学习下载。通过该源码开发者可深入理解名片类小程序的模块划分包括用户管理、名片设计、雷达数据追踪等核心功能并利用微信开发者工具完成调试、预览与发布。对于希望打造独立品牌名片应用、优化营销转化效率的企业和开发者而言这是一份可直接运行且具有明确扩展价值的参考资源。1. 超人名片2.8.9多开版源码解决的是什么问题大多数名片类小程序一套代码只能跑一个实例换品牌或换团队就需要重新部署和改数据库。超人名片2.8.9 多开版采用“一套源码、多套实例”的方案通过租户字段和启动参数区分当前打开的是哪个名片应用雷达模块采集到的用户行为也按实例隔离存储。后端基于 FastAdmin小程序端是原生微信小程序代码压缩包内包含数据库脚本、后端接口和前后台静态资源适合代理商、多品牌企业以及需要同时维护开发/测试环境的工程师。这个版本最有价值的部分是“雷达代理无限版转新应用”机制它让运营人员可以在后端动态创建新的名片实例不需要改动业务代码。但我实际拆包后发现多开的数据隔离和雷达触点归属才是决定这套源码能不能落地生产的关键。下面从后端结构、数据库、前端数据流、部署排错和二次开发几个方面展开。2. FastAdmin 多应用模式与多开版目录结构2.1 从静态资源推断后端框架选型资源包里的 common.css、backend.min.css、frontend.min.css、bootstrap.css、fastadmin.css 基本揭示了后台管理端是基于 FastAdmin 构建的。FastAdmin 是 ThinkPHP 生态里常见的管理后台框架它自带管理员权限、菜单管理和插件机制开发者可以快速套一层 API 模块给小程序使用。多开场景下FastAdmin 的“单代码、多模块”结构比复制一套完整代码更合适因为升级时只需要替换公共代码不需要在十几个副本里分别打补丁。这套源码里还有 foo.bar 这种没有后缀的入口文件常见作用是作为 API 网关的伪装入口后面实际会跳转到 index.php 或独立的 api 入口文件。多开版的业务逻辑不全在 admin 模块里很大一部分在 api 或 addons 模块中所以拆包时不要被 public 目录里的编译后的 CSS 迷惑。2.2 源码目录映射与各目录职责我按典型 FastAdmin 项目结构还原的解压后目录如下supman_card ├── application │ ├── api │ │ ├── controller │ │ │ ├── Card.php │ │ │ ├── Radar.php │ │ │ └── Tenant.php │ │ ├── model │ │ │ ├── CardUser.php │ │ │ └── RadarLog.php │ │ └── config.php │ ├── admin │ │ ├── controller │ │ │ └── Tenant.php │ │ └── view │ │ └── tenant │ └── common │ └── traits ├── addons │ └── radar │ ├── controller │ ├── model │ └── event.php ├── public │ ├── backend.min.css │ ├── frontend.min.css │ ├── fastadmin.css │ └── bootstrap.css ├── database │ └── supman_card.sql └── 小程序 ├── app.js ├── app.json └── pages ├── card └── radar各目录的主要作用如下表目录或文件在多开版里的作用application/api微信小程序调用的接口包含名片、雷达、租户控制器application/admin后台管理端Tenant 控制器在这里处理“新建应用”addons/radar雷达模块独立插件方便事件订阅和二次开发database/supman_card.sql初始化数据库脚本包含多开字段小程序/pages/card名片展示与雷达按钮所在页面public/*.cssFastAdmin 后台样式前端小程序不直接依赖注意 bootstrap.css 在前后台都可能出现因为 FastAdmin 后台基于 Bootstrap不要把后台的样式文件误删否则管理端界面会错乱。2.3 用命令行快速创建第二个名片实例这个多开版的后台通常已经提供了“新建应用”表单我也建议在服务器上用命令行快速执行cd /var/www/supman_card php think tenant:create --name销售二部 --templatedefault执行这条命令会做三件事在 fa_tenant 表插入新租户并生成 app_id复制默认雷达规则和名片样式配置到新租户创建独立上传目录避免不同品牌用户的头像、名片背景互相覆盖。参数说明--name租户在后台显示的名称会写入 fa_tenant.name--template模板名资源包自带 default 和 agency 两套agency 通常给代理商用。执行成功后会输出app_id: 10002。这个 app_id 需要配置到小程序端的请求头或者放进带参数的二维码场景值里用户扫码后自动进入对应实例。我不推荐直接往 fa_tenant 里插一条记录来完成“多开”因为新建名片实例时还应复制菜单权限和雷达关键词表。如果源码里没有复制菜单建议在 Tenant 控制器的 create 方法中补上 fa_auth_group 的角色规则复制逻辑否则新实例的管理员登录后台后只能看到空白页。3. 多开实例的数据库模型租户字段与雷达日志表3.1 给业务表补充 tenant_id 字段的迁移脚本我拿到的超人名片 2.8.9 多开版数据库脚本中已经给主要业务表加了 tenant_id但如果你是从旧版升级上来的资源需要手动跑下面这段 SQLALTER TABLE fa_card_user ADD COLUMN tenant_id INT(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT 多开实例ID AFTER id; ALTER TABLE fa_card_radar_log ADD COLUMN tenant_id INT(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT 租户ID AFTER id; ALTER TABLE fa_card_radar_log ADD KEY idx_tenant_time (tenant_id, created_at);第一条 SQL 给名片用户表增加所属实例标记第二条给雷达日志表增加归属标记第三条创建联合索引用于按租户和时间范围筛选雷达记录。多开项目最常见的错误是只改用户表不改日志表导致后台切换到租户 B 时雷达统计接口查出来的是所有租户的汇总数据问题定位起来很费劲。3.2 雷达日志表字段及用途说明雷达日志是这套源码运营价值最高的表。它的核心字段如下字段名类型说明idint(11)主键tenant_idint(11)所属名片实例user_idint(11)被雷达命中的用户card_idint(11)名片 IDactionvarchar(20)查看名片、点击电话、复制微信等动作rssismallint(6)蓝牙信号强度负值越大代表越近locationvarchar(255)触发雷达时所在的小程序页面路径created_atdatetime记录时间RSSI 是负值例如 -55 表示设备离蓝牙信标很近-85 已经比较远。业务上可以把 -60 以上视为高意向触点。这里的 location 不是 GPS 坐标而是小程序页面路径用来判断用户是在名片首页、产品页还是优惠券页被雷达捕获的。3.3 FastAdmin 模型层自动过滤租户数据多开版的 SQL 条件必须由框架层保障否则开发时很容易漏写 where。我在公共模型上通常会加一个全局 Scope让所有模型查询自动带上 tenant_id?php namespace app\api\model; use think\Model; use app\common\traits\TenantScope; class CardUser extends Model { use TenantScope; }定义在 common/traits/TenantScope.php?php namespace app\common\traits; trait TenantScope { protected static function boot() { parent::boot(); static::addGlobalScope(tenant, function ($query) { $tid request()-header(X-Tenant-Id, 0); if ($tid 0) { $query-where(tenant_id, $tid); } }); } }说明请求进入接口时中间件把X-Tenant-Id放进 request header。模型查询时自动追加这个条件可以有效防止跨租户读数据。使用这个方案的唯一要求是小程序端所有请求都要带上这个 header不能只在某个接口里写死。还要注意不要拿 FastAdmin 后台的 admin_id 当租户标识因为一个管理员可以代理多个名片实例。4. 前端小程序多开切换与雷达名片的数据流4.1 全局状态与启动参数解析多开版的第一个关键点是启动时识别用户扫的是哪个实例的码。在 app.js 中我一般这样解析小程序码的 scene// app.js App({ globalData: { appId: 0, version: 2.8.9 }, onLaunch(options) { let sceneStr decodeURIComponent(options.scene || ); let match sceneStr.match(/app_id(\d)/); let lastAppId wx.getStorageSync(current_app_id); this.globalData.appId match ? match[1] : (lastAppId || 1); wx.setStorageSync(current_app_id, this.globalData.appId); } });代码逻辑并不复杂先从扫码启动参数里匹配app_id没有就取本地最近一次打开的实例再兜底到 1。这样用户扫不同客户的名片码会自动进入对应的品牌实例不需要手动切换。注意 scene 参数需要decodeURIComponent解码否则小程序码生成的参数会以编码后的形式出现正则匹配不到。4.2 名片页 WXML 模板与雷达按钮名片页使用原生小程序 WXML。一个实用的模板包含用户头像、公司信息和两个核心按钮view classcard-panel view classuser-info image src{{userInfo.avatar}} classavatar / text{{userInfo.name}}/text text{{companyName}}/text /view button open-typecontact bindcontactonContact联系我/button button bindtaponRadarScan loading{{radarLoading}} {{radarLoading ? 扫描中... : 雷达探测}} /button /view这里有两个容易踩坑的地方联系客服必须使用 button 的 open-type不能用普通 view 模拟雷达按钮需要绑定 loading 状态避免用户连续点击导致多次并发扫描。同时建议在页面 onLoad 里调用wx.setNavigationBarTitle动态设置标题把当前名片所属团队名称显示在顶部导航栏这样多开实例在用户侧也能明显区分wx.setNavigationBarTitle({ title: 销售二部·超人名片 });4.3 雷达扫描的蓝牙API调用与数据上报雷达功能不是 GPS 定位而是基于蓝牙信号探测附近设备。点击按钮后的处理逻辑如下// pages/card/index.js onRadarScan() { wx.startBluetoothDevicesDiscovery({ allowDuplicatesKey: true, interval: 3000, success: (res) { wx.onBluetoothDeviceFound((found) { let device found.devices[0]; if (device.RSSI -60) { this.reportRadarHit(device); } }); }, fail: (err) { if (err.errCode 10001) { wx.showModal({ title: 提示, content: 请先打开手机蓝牙 }); } } }); }参数说明allowDuplicatesKey: true表示允许同一设备重复上报这样能持续跟踪信号变化interval是扫描间隔单位毫秒3000 是折中设置间隔太短耗电快太长会漏掉短暂停留的客户RSSI 阈值 -60 需要根据实际信标位置调整。命中后调用后端上报接口reportRadarHit(device) { const app getApp(); wx.request({ url: https://yourdomain.com/api/radar/hit, method: POST, header: { X-Tenant-Id: app.globalData.appId }, data: { mac: device.deviceId, rssi: device.RSSI }, fail: () { const pending wx.getStorageSync(radar_pending) || []; pending.push({ mac: device.deviceId, rssi: device.RSSI, time: Date.now() }); wx.setStorageSync(radar_pending, pending); } }); }这里的 fail 回调会先把雷达命中记录存到本地radar_pending下次启动或网络恢复后批量补偿上传。这样即使客户在信号不好的电梯间里触发了雷达数据也不会丢。常见情况对比如下表场景处理方式同一设备持续触发允许重复上报后端按停留时长过滤蓝牙未打开提示用户先开启蓝牙用户来回走动用 RSSI 阈值判断是否进入“近距离”状态网络不可用暂存 radar_pending恢复后补报5. 部署、联调与常见问题排查5.1 上线前需要确认的配置项多开版部署到真实服务器时需要确认的不只是 PHP 版本和伪静态规则。下面这几项经常影响小程序是否能正常访问小程序后台的 request 合法域名必须是 https并且 SSL 证书不能是自签名雷达没用到额外蓝牙域名但 uploadFile 域名也要在小程序后台填否则名片头像上传会失败FastAdmin 默认入口 public/index.phpNginx 要配置好 pathinfo 模式否则接口返回 404每个实例的 app_id 要提前创建好并写入数据库不能依赖前端硬编码。5.2 用 curl 验证接口与租户隔离部署完成后先用 curl 验证接口是否正常响应curl -i -X POST https://yourdomain.com/api/card/index \ -H X-Tenant-Id: 10002 \ -H Content-Type: application/json \ -d {user_id:123}观察返回结果HTTP 200 且 data 里有 tenant_id10002 说明租户隔离生效如果 data 里返回 tenant_id10001说明中间件没有从 header 取到租户号如果返回 403需要检查 FastAdmin 的 token 校验或 IP 白名单返回 500 则看 runtime/log 目录下的当日日志PHP 错误和未捕获异常都在里面。5.3 多开版常见问题汇总下面是我的排查清单现象可能原因解决办法小程序首页白屏app.json 中 pages 首项路径错误确认 pages/card/index 是第一个页面雷达按钮点击无反应手机蓝牙未开或权限未授予先调用 wx.openBluetoothAdapter接口返回其它租户数据X-Tenant-Id 未传或模型未加 scope检查请求拦截器与 TenantScope 是否生效后台登录后菜单为空新建租户时没有复制角色规则在 Tenant::create 中复制 auth_group上传名片图失败uploadFile 域名未配置在小程序后台补 uploadFile 合法域名日志表增长过快雷达记录全部落入同一张表按 tenant_id 分区或加归档任务补充一点不要因为多开就关闭 FastAdmin 的验证码和登录 token 校验。多开接口暴露在公网后攻击者很容易遍历 tenant_id 拉取数据。我建议给接口加签名验证用租户的 app_id 和 secret 生成时间戳签名$expected md5($appId . $tenantSecret . $timestamp); if (!hash_equals($expected, $_SERVER[HTTP_SIGN])) { return json([code 403, msg sign error]); }这样一来只拿到 tenant_id 并不能直接请求接口攻击者还必须拿到对应实例的 secret。hash_equals用来做字符串比较能避免时序攻击一般 PHP 7 以上都内置。6. 进阶自定义雷达命中规则与多开扩展点6.1 用停留时长和白名单过滤雷达日志默认实现里 RSSI 大于 -60 就记录一次真实场景下会带来很多无效数据。比如客户只是路过门店门口也会被记录成高意向线索。我会在 addons/radar/model/RadarLog.php 中增加一个 shouldHit 方法public function shouldHit($tenantId, $mac, $rssi) { $cacheKey radar:{$tenantId}:{$mac}; $firstSeen Cache::get($cacheKey); if (!$firstSeen) { Cache::set($cacheKey, time(), 120); return false; } $staySeconds time() - $firstSeen; $blacklisted Blacklist::where(mac, $mac)-value(id); return $staySeconds 10 $rssi -65 !$blacklisted; }这段逻辑用缓存记录设备第一次出现时间停留超过 10 秒才写入雷达日志RSSI 阈值也上调到 -65同时命中白名单的设备直接跳过。白名单通常放自家员工手机或门店测试机避免自己人刷出一堆假线索。注意 Cache 过期时间要与停留判断窗口一致我这里设了 120 秒超过后重新计时。6.2 多开租户的事件订阅与自动通知如果希望雷达命中后第一时间通知对应租户的管理员可以在 addons/radar/event.php 里监听命中事件Event::listen(radar.hit, function ($log) { $admin Db::name(tenant) -where(id, $log[tenant_id]) -value(admin_openid); // 调用微信订阅消息或公众号模板消息推送 });这里的关键是事件回调里取出tenant_id再找到该租户绑定的管理员 openid推送内容可以带上命中用户的来源页面。多开版比单机版有优势的地方就在这里事件总线不需要关心当前是哪个实例只要数据里带着 tenant_id路由就可以精确分发。6.3 验证雷达命中是否按租户隔离我一般用日志验证事件是否生效grep radar.hit runtime/log/$(date %Y%m%d).log | tail -20如果看到radar.hit后面跟着不同的 tenant_id说明事件正确区分了实例。再用 SQL 查询某个租户当天命中次数SELECT tenant_id, COUNT(*) FROM fa_card_radar_log WHERE created_at CURDATE() GROUP BY tenant_id;检查结果里 tenant_id 与预期一致就可以认为多开实例的雷达链路完整跑通。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门