跳到内容

活码

受众:开发者 摘要:活码模块基础版能力、模块边界、API、后台入口和后续扩展计划。

spring-open-module-live-code 提供活码 / 动态二维码业务能力。基础版先做“一个长期二维码对应一个可调整目标地址”,并已支持 SVG / PNG 二维码、二维码物料清单、后台预览、批量创建、批量下载、扫码统计、扫码分析摘要、异常访问风险摘要、转化事件摘要、转化事件明细、转化事件 CSV 导出、扫码日趋势、扫码明细、渠道统计、地区统计、Campaign 运营统计、企业治理摘要、规则与安全治理审计及 CSV 导出、业务联动 Contributor、时间 / 渠道 / 场景 / 地区 / 设备 / 语言 / A-B 百分比规则分流、二维码样式、Logo 覆盖和分组标签。

什么时候使用

适合这些场景:

  • 物料二维码长期不变,但落地页需要随运营调整。
  • 线下海报、门店桌牌、活动展架、售后卡片等二维码入口。
  • 企业微信群、客服入口、产品说明书、活动报名页等可替换目标。
  • 需要统计扫码次数、最近扫码时间,并按时间、渠道、场景、地区、设备或百分比灰度分流不同目标。

如果只是把长 URL 缩短成短 URL,请使用 短链接。如果二维码承载支付、登录、邀请或营销活动,应由对应业务模块发起,活码只提供入口与解析能力。

模块边界

Maven 模块spring-open-module-live-code
API 前缀/api/v1/live-codes/**
配置前缀spring.open.live-code.*
Java 包名com.springopen.module.livecode
数据表live_codelive_code_scan_recordlive_code_conversion_event
权限码前缀live-code:*

应用默认引入该模块,可通过配置关闭:

yaml
spring:
  open:
    live-code:
      enabled: true

spring.open.live-code.enabled 已通过模块 ServiceProvider 声明为配置中心元数据。启用配置中心后,后台可以发现并维护活码模块启停配置;未通过配置中心覆盖时继续使用 application.yml / 环境变量中的默认装配值。

活码目标地址的域名安全策略也通过配置中心元数据声明,运行时会优先读取配置中心,未配置时等价于不限制:

配置说明
spring.open.live-code.security.allowed-target-domains活码目标链接允许访问的域名列表,留空表示不限制;支持逗号、分号、换行或 JSON 数组。
spring.open.live-code.security.blocked-target-domains活码目标链接禁止访问的域名列表,命中后保存和解析都会拒绝;支持逗号、分号、换行或 JSON 数组。

匹配规则:禁止列表优先;允许列表为空时默认放行;允许列表不为空时,目标域名必须命中其中一项。example.com 会匹配自身及其子域名,*.example.com 只匹配子域名,.example.com 也只匹配子域名。策略会同时覆盖主 targetUrlruleJson.routes[].targetUrlruleJson.fallbackTargetUrl,避免已发布二维码在运营调整后继续跳转到被禁用域名。

活码公开解析和扫码跳转入口支持 API 配额治理。配额配置通过配置中心运行期生效,默认启用 300 次 / 60 秒的同一客户端 IP + 同一活码编码窗口限制;限流 key 只保存活码编码和客户端 IP 的摘要,不保存明文 IP:

配置说明
spring.open.live-code.quota.public-resolve.enabled是否启用活码公开解析和扫码跳转入口的 API 配额限制。
spring.open.live-code.quota.public-resolve.max-attempts同一客户端 IP 对同一活码编码在一个窗口期内允许解析的最大次数;小于等于 0 表示不限制。
spring.open.live-code.quota.public-resolve.window-seconds活码公开解析 API 配额的固定窗口秒数;小于等于 0 表示不限制。
spring.open.live-code.quota.public-resolve.store活码公开解析 API 配额使用的 RateLimit store,留空时使用 RateLimit starter 默认 store。

活码异常访问风险摘要也通过配置中心声明阈值。后台扫码分析摘要会展示风险等级、风险分、爬虫占比、最高频 IP 及其扫码占比,样本数不足时只展示指标、不提升风险等级:

配置说明
spring.open.live-code.risk.min-scan-sample异常访问风险判断的最小扫码样本数,默认 10
spring.open.live-code.risk.medium-bot-scan-rate-percent疑似爬虫扫码占比达到该百分比时标记为中风险,默认 20
spring.open.live-code.risk.high-bot-scan-rate-percent疑似爬虫扫码占比达到该百分比时标记为高风险,默认 50
spring.open.live-code.risk.medium-top-ip-rate-percent单一 IP 扫码占比达到该百分比时标记为中风险,默认 50
spring.open.live-code.risk.high-top-ip-rate-percent单一 IP 扫码占比达到该百分比时标记为高风险,默认 70

二维码物料治理

后台活码列表的“预览”操作会同时读取二维码 SVG 和物料清单。物料清单接口为 GET /api/v1/admin/live-codes/live-codes/{id}/qr-code-material,返回扫码入口、物料版本、SVG / PNG 文件名、尺寸、留白、颜色和 Logo 配置状态,方便运营确认线下海报、桌牌或活动物料使用的是当前版本。

物料版本格式为 lc-{id}-{updatedAt},其中时间来自活码最近更新时间;目标地址、规则、样式或 Logo 配置发生变更后,后台可以用该版本识别需要重新导出的物料。

SVG 资源会按 styleJson.logoUrl 渲染中心 Logo;PNG 资源只复用尺寸、留白和颜色,不抓取外部 Logo 图片,避免后端在导出位图时对任意外部地址发起请求。需要带 Logo 的位图物料时,应优先导出 SVG 后通过受控内网转码流程生成。

数据表

活码模块当前维护三张表:

说明
live_code活码主体,保存活码编码、目标 URL、分组、标签、状态、有效期、访问密码、规则 JSON、样式 JSON、扫码次数和扩展信息
live_code_scan_record扫码明细,保存活码 ID、编码、标题、命中目标、渠道、场景、地区、设备、IP、Referer、User-Agent 和扫码时间
live_code_conversion_event转化事件,保存活码 ID、编码、事件名、幂等 key、转化金额、渠道、场景、地区、设备、IP、Referer、User-Agent、扩展元数据和转化时间

活码编码 code 全局唯一,只允许字母、数字、下划线和中划线,长度 3 到 64。活码编码一旦创建会长期占用,逻辑删除后也不会重新分配给其他活码,避免线下二维码被误解析到新内容。

机制说明

活码的后台管理、公开解析和浏览器跳转都统一走 LiveCodeService。Controller 只做权限、请求转换和响应包装,不在入口层重复业务判断。

保存活码时,后端会统一规范化 codetargetUrl、有效期和访问密码动作。code 不传时生成 8 位编码;targetUrl 基础版只允许 http / https,且必须通过当前域名安全策略;startsAtendsAt 必须形成合法时间窗口。

更新访问密码时必须通过 accessPasswordAction 显式表达意图:

  • keep 或不传:保留原密码。
  • change:使用本次 accessPassword 修改密码。
  • clear:清空访问密码。

解析活码时会依次检查编码、启用状态、有效期和访问密码。由于部分数据库默认 collation 大小写不敏感,后端会先查候选记录,再在 Java 内用 String.equals 精确匹配真实 code,避免 AbC123abc123 被误认为同一个公开入口。

扫码次数通过 MyBatis-Plus setIncrBy 在数据库侧原子递增,避免高并发扫码时统计丢失。解析成功后会同时写入 live_code_scan_record,记录渠道、场景、地区、设备、IP、Referer、User-Agent 和本次命中的目标 URL,便于后台分析真实投放效果。后台列表支持按运营分组和标签筛选;Campaign 统计复用 groupName 作为运营活动桶,按分组聚合活码数量、启用数量、扫码次数和最近扫码时间,不额外新增 Campaign 表。ruleJson 支持时间窗口、渠道、场景、地区、设备、语言和 A-B 百分比分流,styleJson 支持二维码前景色、背景色、尺寸、留白和居中 Logo 覆盖。

后台扫码分析抽屉会先读取 GET /api/v1/admin/live-codes/live-codes/{id}/analysis-summary,展示 PV、独立 IP、去重 Referer、疑似爬虫扫码数、转化事件数、去重转化 key 和最近转化时间;最近转化事件通过 GET /api/v1/admin/live-codes/live-codes/conversion-events 下钻查看,也可以通过 GET /api/v1/admin/live-codes/live-codes/conversion-events/export 导出 CSV 留档,渠道、地区、趋势和扫码明细仍走各自接口,便于运营先看摘要,再排查投放来源、转化效果或异常访问。

落地页完成注册、下单、留资等业务动作后,可以调用 POST /api/v1/live-codes/live-codes/{code}/conversions 记录转化事件。conversionKey 非空时作为同一活码、同一事件名下的幂等 key,重复上报会返回既有事件,不重复写入;该接口只记录转化,不递增扫码次数。

企业治理元数据

活码模块不直接接管支付、登录、邀请、Marketing、审批、团队或租户的业务状态。需要把活码纳入企业运营治理时,可以在 extraJson.enterpriseGovernance 写入受控元数据,供后台运营、审计和外部业务模块按约定读取:

json
{
  "enterpriseGovernance": {
    "tenantId": "tenant-a",
    "teamId": "growth-team",
    "ownerUserId": "10001",
    "approvalStatus": "pending",
    "approvalTicketId": "APP-20260701-001",
    "brandMaterialStatus": "pending",
    "allowedChannels": ["poster", "wechat"],
    "blockedScenes": ["risk"],
    "allowedRegions": ["CN-ZJ", "CN-SH"],
    "blockedDevices": ["Bot"],
    "linkages": {
      "payment": {
        "enabled": true,
        "operationKey": "cashier-poster-a"
      },
      "login": {
        "enabled": true,
        "scene": "member-sign-in"
      },
      "invite": {
        "enabled": true,
        "inviteCode": "SUMMER"
      },
      "marketing": {
        "enabled": true,
        "campaignCode": "summer-2026"
      }
    }
  }
}

保存活码时会校验治理元数据结构:approvalStatus 只允许 draft / pending / approved / rejectedbrandMaterialStatus 只允许 not_required / pending / approved / rejected,渠道、场景、地区和设备名单只允许非空字符串或非空字符串数组。linkages 内置 payment / login / invite / marketing 四类开关;其他模块或应用装配层可以注册 LiveCodeBusinessLinkageContributor Spring Bean 贡献新的联动 key。未登记的 linkage key 会在创建或更新时返回业务错误,避免运营误把任意 JSON 当作治理配置。

后台活码列表会展示企业治理标签、审批状态、品牌物料状态和业务联动类型;表单提供标准治理 JSON 示例按钮,方便运营按项目统一格式填写。列表操作支持提交治理审批、通过治理审批和驳回治理审批:动作会接入 Approval Workflow live-code.governance.review 校验状态流转和权限,把最新动作、操作人、备注、TraceId 和处理时间写回 extraJson.enterpriseGovernance,便于后台审计和排障。该审批只管理活码入口治理状态,不替代 IAM 团队权限、支付、登录、邀请或 Marketing 等 owning module 的真实业务状态。

后台管理接口同时支持团队范围权限:live-code:live-code:team-viewteam-createteam-updateteam-deleteteam-review。拥有全局权限时可管理全部活码;只有团队权限时,接口会按 extraJson.enterpriseGovernance.ownerUserIdteamId 收窄到当前用户本人或授权团队范围。团队 ID 由应用装配层或团队模块实现 LiveCodeTeamAccessResolver 提供,活码模块不直接依赖 IAM / Organization;没有 resolver 时,团队权限只能操作 ownerUserId 等于当前用户的活码。团队范围创建会在缺失 ownerUserId 时自动写入当前用户,且不能把活码分配给未授权团队或其他负责人。

治理摘要接口用于看板统计,响应会保留内置四类固定计数字段,并通过 linkageCounts 返回全部已登记联动 key 的动态计数:

http
GET /api/v1/admin/live-codes/live-codes/governance-summary

响应包含已配置治理元数据的活码数量、租户隔离、团队归属、待审批、品牌物料通过、访问治理和支付 / 登录 / 邀请 / Marketing 联动覆盖数。该接口只读取 extraJson 汇总,不新增表,也不替代 owning module 的真实支付、登录、邀请或营销状态机。

业务联动的运行期执行通过事件 contract 完成。其他模块或应用装配层先实现 LiveCodeBusinessLinkageContributor 注册自己的 linkage key,再监听 LiveCodeBusinessLinkageTriggeredEvent

  • trigger=resolve:公开解析或 /q/{code} 跳转成功、扫码明细写入后触发。
  • trigger=conversion:落地页首次写入转化事件后触发;带 conversionKey 的重复上报只返回既有事件,不重复触发。
  • 事件携带 liveCodeIdcodetargetUrlresolvedTargetUrl、启用的 linkageKeys、对应 linkageMetadataJson、渠道、场景、地区、设备、语言、IP、Referer、User-Agent、转化事件名和转化 key。

推荐监听器使用 @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)。Live Code 只保证入口和事件上下文,不直接调用 Payment、IAM、Distribution、Marketing 或其他 owning module,也不写它们的状态机或审计记录。

规则与安全治理审计接口用于发布二维码物料前的运营自检:

http
GET /api/v1/admin/live-codes/live-codes/rule-audit-report

响应聚合当前访问范围内的活码总量、启用数量、规则覆盖、fallback 覆盖、二维码样式 / Logo 覆盖、密码保护、访问治理、审批状态、域名安全配置、公开入口配额和异常访问风险阈值。该接口只读现有活码和配置中心运行值,不写活码状态、不改变公开解析或扫码跳转行为。

需要在发布前留档时,可以导出同一访问范围内的规则与安全治理审计 CSV:

http
GET /api/v1/admin/live-codes/live-codes/rule-audit-report/export

CSV 固定输出规则覆盖、物料治理、安全域名、公开入口配额和风险阈值字段。Admin 活码页的审计卡片提供下载入口,便于运营在批量导出二维码物料前归档当前治理快照。

公开解析和 /q/{code} 跳转会执行 allowedChannels / blockedChannels / allowedScenes / blockedScenes / allowedRegions / blockedRegions / allowedDevices / blockedDevices:禁止名单优先;允许名单非空时,当前渠道、场景、地区或设备必须命中名单;名单支持大小写无关匹配和 * 通配。未通过治理规则时返回业务错误,不会增加扫码次数,也不会写入扫码明细。渠道为空时按 direct 处理,场景、地区或设备为空且配置了对应允许名单时会被拒绝;设备由扫码 User-Agent 粗分为 MobileTabletDesktopBot

后台管理 API

后台接口需要登录并校验权限:

下表列出全局权限。若只授予对应团队权限,接口仍可调用,但只能访问当前用户本人或 LiveCodeTeamAccessResolver 返回团队范围内的活码。

方法路径权限说明
GET/api/v1/admin/live-codes/live-codeslive-code:live-code:view分页查询活码
GET/api/v1/admin/live-codes/live-codes/statisticslive-code:live-code:view查询活码统计总览
GET/api/v1/admin/live-codes/live-codes/campaign-statisticslive-code:live-code:view按运营分组查询 Campaign 活码数量、启用数量、扫码次数和最近扫码时间
GET/api/v1/admin/live-codes/live-codes/governance-summarylive-code:live-code:view查询企业治理摘要,聚合租户、团队、审批、访问治理和业务联动覆盖数
GET/api/v1/admin/live-codes/live-codes/rule-audit-reportlive-code:live-code:view查询规则、物料、安全、公开入口配额和风险阈值的只读审计报告
GET/api/v1/admin/live-codes/live-codes/rule-audit-report/exportlive-code:live-code:view导出规则、物料、安全、公开入口配额和风险阈值 CSV
GET/api/v1/admin/live-codes/live-codes/scan-recordslive-code:live-code:view分页查询扫码明细,可按活码、编码、渠道、地区和设备筛选
GET/api/v1/admin/live-codes/live-codes/conversion-eventslive-code:live-code:view分页查询转化事件,可按活码、事件名、转化 key、渠道、场景、地区和设备筛选
GET/api/v1/admin/live-codes/live-codes/conversion-events/exportlive-code:live-code:view导出转化事件 CSV,可按活码、事件名、转化 key、渠道、场景、地区和设备筛选
GET/api/v1/admin/live-codes/live-codes/scan-records/exportlive-code:live-code:view导出扫码明细 CSV,可按活码、编码、渠道、地区和设备筛选
GET/api/v1/admin/live-codes/live-codes/{id}/analysis-summarylive-code:live-code:view查询指定活码扫码和转化分析摘要,包含 PV、独立 IP、去重来源、疑似爬虫、最近扫码时间、转化事件数、去重转化 key 和最近转化时间
GET/api/v1/admin/live-codes/live-codes/{id}/channel-statisticslive-code:live-code:view查询指定活码的渠道扫码次数和最近扫码时间
GET/api/v1/admin/live-codes/live-codes/{id}/region-statisticslive-code:live-code:view查询指定活码的地区扫码次数和最近扫码时间
GET/api/v1/admin/live-codes/live-codes/{id}/scan-trendlive-code:live-code:view查询指定活码最近一段时间的按天扫码趋势
GET/api/v1/admin/live-codes/live-codes/{id}live-code:live-code:view查看活码详情
GET/api/v1/admin/live-codes/live-codes/{id}/qr-code.svglive-code:live-code:view生成指向 /q/{code} 的 SVG 二维码
GET/api/v1/admin/live-codes/live-codes/{id}/qr-code.pnglive-code:live-code:view生成指向 /q/{code} 的 PNG 二维码
POST/api/v1/admin/live-codes/live-codes/qr-codes.ziplive-code:live-code:view按 ID 批量下载 SVG 二维码 ZIP
POST/api/v1/admin/live-codes/live-codeslive-code:live-code:create创建活码
POST/api/v1/admin/live-codes/live-codes/batchlive-code:live-code:create批量创建活码,任一条失败时整体回滚
POST/api/v1/admin/live-codes/live-codes/{id}/governance/submitlive-code:live-code:update提交活码企业治理审批,draft / rejected 可进入 pending
POST/api/v1/admin/live-codes/live-codes/{id}/governance/approvelive-code:live-code:review通过活码企业治理审批,pending 进入 approved
POST/api/v1/admin/live-codes/live-codes/{id}/governance/rejectlive-code:live-code:review驳回活码企业治理审批,pending 进入 rejected,必须填写备注
PUT/api/v1/admin/live-codes/live-codes/{id}live-code:live-code:update修改活码
DELETE/api/v1/admin/live-codes/live-codes/{id}live-code:live-code:delete删除活码
DELETE/api/v1/admin/live-codes/live-codeslive-code:live-code:delete批量删除活码

创建请求示例:

json
{
  "code": "support",
  "title": "售后服务入口",
  "targetUrl": "https://example.com/support",
  "description": "线下售后卡片二维码",
  "groupName": "线下活动",
  "tags": "展会,售后",
  "status": 1,
  "startsAt": "2026-06-01T00:00:00",
  "endsAt": "2026-12-31T23:59:59",
  "accessPassword": "123456",
  "ruleJson": "{\"fallbackTargetUrl\":\"https://example.com/default\",\"routes\":[{\"targetUrl\":\"https://example.com/mobile-poster\",\"startsAt\":\"2026-06-01T09:00:00\",\"endsAt\":\"2026-06-01T18:00:00\",\"channel\":[\"poster\",\"wechat\"],\"scene\":\"store\",\"device\":\"Mobile\"}]}",
  "styleJson": "{\"foregroundColor\":\"#0057ff\",\"backgroundColor\":\"#f8fafc\",\"size\":320,\"margin\":2,\"logoUrl\":\"/assets/logo.png\",\"logoSize\":72,\"logoPadding\":8,\"logoBackgroundColor\":\"#ffffff\"}"
}

code 可不传,后端会生成 8 位编码。targetUrl 和规则内所有目标地址只允许 http / https

批量创建接口接收 items 数组,数组项字段和单条创建一致。后端会逐条复用单条创建校验和规范化逻辑,并在同一个事务里保存;任意一条编码重复、目标地址非法或规则 JSON 不合法时,本次批量创建整体回滚。单次最多 100 条:

json
{
  "items": [
    {
      "code": "campaign-poster-a",
      "title": "活动海报 A",
      "targetUrl": "https://example.com/campaign/a",
      "groupName": "夏季活动",
      "tags": "poster,offline",
      "status": 1
    },
    {
      "code": "campaign-poster-b",
      "title": "活动海报 B",
      "targetUrl": "https://example.com/campaign/b",
      "groupName": "夏季活动",
      "tags": "poster,offline",
      "status": 1
    }
  ]
}

分页查询支持 keywordstatusgroupNametagkeyword 会匹配编码、标题、目标地址、分组和标签;groupName 精确匹配运营分组;tag 对标签字符串做包含匹配。首段标签以逗号分隔字符串保存,适合后台快速筛选和列表展示。

修改活码时,扫码密码采用显式动作,避免表单未填写时误清空旧密码:

accessPasswordAction说明
keep 或不传保持原密码
change使用本次 accessPassword 修改密码
clear清空访问密码

规则分流

ruleJson 用于同一个二维码在不同时间、渠道、场景、地区、设备、语言或百分比灰度下跳转到不同目标。当前生产接入时间窗口、channelsceneregiondevicelanguagetrafficPercent 分流,适合活动页、工作时段客服入口、线下海报、门店桌牌、区域活动、移动端专属落地页、多语言落地页和 A-B 测试。

可直接复制这个结构:

json
{
  "fallbackTargetUrl": "https://example.com/default",
  "routes": [
    {
      "targetUrl": "https://example.com/workday",
      "startsAt": "2026-06-01T09:00:00",
      "endsAt": "2026-06-01T18:00:00"
    },
    {
      "targetUrl": "https://example.com/night",
      "startsAt": "2026-06-01T18:00:00",
      "endsAt": "2026-06-02T09:00:00"
    },
    {
      "targetUrl": "https://example.com/mobile-poster",
      "channel": ["poster", "wechat"],
      "scene": "store",
      "region": ["CN-ZJ", "CN-SH"],
      "device": "Mobile"
    },
    {
      "targetUrl": "https://example.com/zh",
      "language": ["zh", "zh-CN"]
    },
    {
      "targetUrl": "https://example.com/variant-a",
      "trafficPercent": 30
    }
  ]
}

匹配规则:

  • routes 按数组顺序匹配,命中第一条满足全部条件的 route 后返回该 targetUrl
  • startsAt 可不填;填写后表示当前时间必须大于等于该时间。
  • endsAt 可不填;填写后表示当前时间必须早于该时间。
  • 同一条规则同时填写 startsAtendsAt 时,endsAt 必须晚于 startsAt
  • channel 可填字符串或字符串数组,对应公开解析和 /q/{code}channel 查询参数;未传时按 direct 处理。
  • scene 可填字符串或字符串数组,对应公开解析和 /q/{code}scene 查询参数。
  • region 可填字符串或字符串数组,对应公开解析和 /q/{code}region 查询参数,例如 CN-ZJCN-SH 或业务自定义区域编码。
  • device 可填字符串或字符串数组,后端根据 User-Agent 粗分为 Bot / Tablet / Mobile / Desktop 后匹配。
  • language 可填字符串或字符串数组,对应公开解析和 /q/{code}Accept-Language 首选语言;zh 可命中 zh-CNzh-TW 等子标签,zh-CN 只命中同一具体标签。
  • trafficPercent 可填 0100 的整数,后端会基于活码编码、渠道、场景、地区、IP、Referer、设备和 User-Agent 生成稳定 bucket;bucket 小于 trafficPercent 时命中。0 永不命中,100 总是命中。
  • 同一条 route 同时填写时间、渠道、场景、地区、设备、语言和 trafficPercent 时,必须全部满足才算命中。
  • 没有任何规则命中时,优先返回 fallbackTargetUrl;未配置 fallback 时返回活码主 targetUrl

ruleJson 必须是 JSON Object;routes 必须是数组;每条 route 必须有合法的 http / https targetUrl。选择器字段只允许非空字符串或非空字符串数组,数组项也不能是空白字符串,匹配时忽略大小写;语言标签会把下划线归一为短横线后匹配;trafficPercent 只允许 0100 的整数。配置错误会在保存活码时返回业务错误,避免扫码时才暴露异常或因空选择器静默落到 fallback。

如果解析时没有任何 route 命中并实际使用了 fallbackTargetUrl,Live Code 会发布 live-code.route.fallback-used 通知规则事件源。应用层监听器只按 Notification 通知规则的运行时配置发送站内信 / 邮件等通道;默认未配置接收人时静默跳过,不影响扫码解析、扫码次数和扫码明细写入。事件 payload 包含活码 ID、编码、标题、主目标、兜底目标、渠道、场景、地区、设备、语言、IP、Referer、User-Agent 和发生时间,可用于运营检查规则覆盖是否过窄。

后台统计

后台统计接口用于管理端看板和扫码分析入口:

http
GET /api/v1/admin/live-codes/live-codes/statistics

响应包含活码总量、启用 / 禁用数量、未开始数量、已过期数量、密码保护数量、扫码总次数、最新扫码时间,以及按扫码次数排序的 Top 活码列表:

json
{
  "code": "200",
  "message": "成功",
  "data": {
    "total": 12,
    "enabled": 10,
    "disabled": 2,
    "notStarted": 1,
    "expired": 3,
    "passwordProtected": 4,
    "totalScans": 358,
    "latestScannedAt": "2026-06-03T17:30:00",
    "topLiveCodes": [
      {
        "id": 1,
        "code": "support",
        "title": "售后服务入口",
        "targetUrl": "https://example.com/support",
        "scanCount": 128,
        "lastScannedAt": "2026-06-03T17:30:00"
      }
    ]
  }
}

Top 活码列表只统计未删除记录,按 scanCount 倒序、lastScannedAt 倒序、id 倒序取前 5 条。

后台活码管理页的“分析”入口会同时读取渠道统计、地区统计、扫码趋势和扫码明细:

http
GET /api/v1/admin/live-codes/live-codes/{id}/channel-statistics
GET /api/v1/admin/live-codes/live-codes/{id}/region-statistics
GET /api/v1/admin/live-codes/live-codes/{id}/scan-trend?days=14
GET /api/v1/admin/live-codes/live-codes/scan-records?liveCodeId=1&page=1&size=10
GET /api/v1/admin/live-codes/live-codes/scan-records/export?liveCodeId=1&channel=poster

扫码日趋势默认返回最近 14 天,days 最大 90;响应按日期升序返回连续日期桶,某天没有扫码时 scanCount0,适合后台趋势图直接渲染。地区统计按 region 聚合扫码次数和最近扫码时间,空地区归为 未知。扫码明细返回本次解析的渠道、场景、地区、设备、IP、命中目标和扫码时间。扫码明细导出返回 text/csv;charset=UTF-8 附件,字段包含活码 ID、编码、标题、主目标、本次命中目标、扫码时间、渠道、场景、地区、设备、IP、Referer 和 User-Agent,并复用列表筛选条件。转化事件导出同样返回 text/csv;charset=UTF-8 附件,字段包含活码 ID、编码、标题、事件名、转化 key、转化金额、渠道、场景、地区、设备、IP、Referer、User-Agent 和转化时间,适合投放复盘或人工对账留档。channel 为空时后端归为 direct;设备会按 User-Agent 粗分为 DesktopMobileTabletBot未知。分析摘要中的转化事件数来自公开转化上报表,去重转化 key 只统计非空 conversionKey,最近转化时间用于判断投放落地页是否仍有有效转化。

二维码生成

后台可直接生成可扫码 SVG 二维码:

http
GET /api/v1/admin/live-codes/live-codes/1/qr-code.svg?baseUrl=https://example.com

响应类型为 image/svg+xml,内容是指向浏览器跳转入口的 QR Code。baseUrl 可选:

  • 不传 baseUrl:二维码内容为相对入口 /q/{code},适合后台内嵌预览。
  • https://example.com:二维码内容为 https://example.com/q/{code},适合下载后投放到线下物料。
  • baseUrl 可以包含部署上下文路径,例如 https://example.com/app 会生成 https://example.com/app/q/{code}

baseUrl 只允许 http / https,且不能包含 query 或 fragment。二维码图形由 ZXing Core 生成 SVG,不依赖图片文件落盘。

需要位图素材时可下载单个 PNG 二维码:

http
GET /api/v1/admin/live-codes/live-codes/1/qr-code.png?baseUrl=https://example.com

响应类型为 image/png。PNG 使用同一套二维码内容、尺寸、留白、前景色和背景色配置;如果 styleJson 配置了 logoUrl,PNG 会使用更高纠错级别生成基础位图,但不会由服务端抓取远程 Logo 图片,避免二维码下载链路成为外部图片代理或 SSRF 入口。需要带 Logo 的品牌物料时继续使用 SVG 资源。

批量下载接口会按请求 ID 生成 ZIP 附件,每个活码一个 SVG 文件:

http
POST /api/v1/admin/live-codes/live-codes/qr-codes.zip
Content-Type: application/json
json
{
  "ids": [1, 2, 3],
  "baseUrl": "https://example.com"
}

后台“活码管理”列表支持单条“预览”操作:页面会用当前登录态拉取 qr-code.svg,在弹窗中展示二维码,并提供扫码入口复制和单张 PNG 下载。ZIP 内文件名使用活码编码,例如 support.svgbaseUrl 规则与单个 SVG / PNG 接口一致;同一请求里的重复 ID 会去重并保留首次出现顺序。后台列表也支持勾选多条记录后直接批量下载 SVG 二维码。

二维码样式可通过后台保存的 styleJson 生效。“活码管理”表单已提供结构化样式控件,可配置前景色、背景色、二维码尺寸、留白、Logo 地址、Logo 尺寸、Logo 留白和 Logo 背板色,并会在保存时同步生成 styleJson;仍可从既有 JSON 读取回表单,便于兼容历史数据。

json
{
  "foregroundColor": "#0057ff",
  "backgroundColor": "#f8fafc",
  "size": 320,
  "margin": 2,
  "logoUrl": "/assets/logo.png",
  "logoSize": 72,
  "logoPadding": 8,
  "logoBackgroundColor": "#ffffff"
}

foregroundColor 可用别名 darkColorcolorbackgroundColor 可用别名 lightColor。颜色只支持 #RGB#RRGGBBsize 范围为 128 到 1024,margin 范围为 0 到 8。logoUrl 只允许 http / https 绝对地址或以 / 开头的站内绝对路径,不允许协议相对地址、反斜线或 javascript: 等脚本协议;配置后二维码使用更高纠错级别并在中心渲染 Logo。logoSize 不传时按二维码尺寸自动计算,最大不超过二维码宽度的三分之一;logoPadding 不传时默认为 6,最大 32;logoBackgroundColor 使用同样的十六进制颜色规则。配置不合法时,创建 / 更新活码会返回样式配置错误。

公开解析与跳转

方法路径说明
GET/api/v1/live-codes/live-codes/{code}返回无密码活码解析结果,适合 App / PC 自行处理跳转
POST/api/v1/live-codes/live-codes/{code}/resolve解析带密码活码,请求体提交 accessPassword,避免密码出现在 URL 和访问日志
POST/api/v1/live-codes/live-codes/{code}/conversions记录落地页转化事件,支持 conversionKey 幂等上报
GET/q/{code}浏览器 302 跳转到目标 URL

公开解析和跳转入口都支持可选 channel / scene / region 查询参数,并会读取浏览器 Accept-Language 首选语言,用于区分海报、门店、社媒、活动批次、区域活动和多语言落地页等投放来源。例如:

http
GET /q/support?channel=poster&scene=store-a&region=CN-ZJ

公开解析返回:

json
{
  "code": "200",
  "message": "成功",
  "data": {
    "code": "support",
    "title": "售后服务入口",
    "targetUrl": "https://example.com/support",
    "passwordRequired": false
  }
}

如果活码未开始、已禁用、已过期或密码错误,接口返回统一 Result 业务错误。带密码的活码应使用 POST /resolve 验证,再由前端自行跳转;/q/{code} 第一阶段只适合无密码活码。

扫码次数由后端原子递增,避免高并发扫码时统计丢失。公开解析返回的是本次规则分流后的 targetUrl,后台保存的主目标地址不会被规则命中结果覆盖;后台扫码明细会保存本次命中的实际目标地址和地区参数。

转化事件请求示例:

http
POST /api/v1/live-codes/live-codes/support/conversions?channel=poster&scene=store-a&region=CN-ZJ
Content-Type: application/json
json
{
  "eventName": "lead",
  "conversionKey": "lead-20260630-0001",
  "conversionValue": "99.00",
  "metadataJson": "{\"form\":\"contact\"}"
}

eventName 不传时默认为 conversionconversionKey 建议使用订单号、表单提交编号、注册流水号等业务侧稳定标识。转化接口会采集同一请求的渠道、场景、地区、设备、IP、Referer 和 User-Agent,方便后台把扫码来源和转化来源放在同一个活码分析摘要里观察。

权限与菜单

迁移会写入:

资源code
菜单live-code
菜单live-code:live-code
查看live-code:live-code:view
创建live-code:live-code:create
修改live-code:live-code:update
删除live-code:live-code:delete
查看团队范围live-code:live-code:team-view
创建团队范围live-code:live-code:team-create
修改团队范围live-code:live-code:team-update
删除团队范围live-code:live-code:team-delete
审批团队范围live-code:live-code:team-review

默认授予 super-admin 角色。

分阶段规划

阶段目标说明
Phase 1 基础版活码 CRUD、公开解析、/q/{code} 跳转、状态、有效期、访问密码、扫码次数已按最小闭环实现
Phase 2 国内常见能力SVG 二维码生成、后台预览、批量创建、批量下载、时间规则分流、扫码明细、渠道统计、地区统计、二维码样式、Logo 覆盖、分组、状态批量管理、落地页模板SVG / PNG 二维码、后台预览、批量创建、批量下载、扫码统计、扫码日趋势、扫码明细、渠道统计、地区统计、时间 / 渠道 / 场景 / 地区 / 设备 / A-B 百分比规则分流、基础二维码样式、Logo 覆盖和分组标签已接入;落地页继续按功能簇推进
Phase 3 国际化运营能力品牌域名、Campaign、设备 / 地区 / 语言规则分流、Webhook参考 Bitly / Rebrandly 类链接平台的运营分析和团队协作
Phase 4 企业治理团队权限、审计、风险检测、多租户隔离面向企业生产使用;扫码明细 CSV 导出、转化事件 CSV 导出、公开解析 API 配额、转化事件首段、企业治理元数据、治理摘要和团队范围权限已接入

活码模块不直接承载支付、登录、邀请、营销活动业务逻辑;这些业务通过事件、应用装配层或后续业务模块接入。

Released under the Apache License 2.0.