Skip to content

活动功能

活动是一个可管理的时间段聚会/事件,支持签到和通行证两大子功能。 遵循"即时渲染二维码"原则:活动码/签到码/通行码均通过 qr_content 字段即时渲染,不写入 qr_codes 表。


1. 入口

入口
Android主菜单 → 活动,或侧边栏 → 活动
管理后台侧边栏 → 活动管理(Activities.vue)、签到记录管理(CheckIn.vue)

前端路由 / 导航

Android:

  • ActivityListScreen.kt → 活动列表主页,使用 ActivityViewModel
  • ActivityDetailScreen.kt → 活动详情页
  • ActivityCreateScreen.kt / ActivityEditScreen.kt → 创建/编辑表单,使用 ActivityFormViewModel
  • 入口位于 AppNavigation.kt 中注册 activitiesNavigation 路由

管理后台:

  • /activities → Activities.vue(活动管理:列表/详情/创建/编辑/删除)
  • /check-in → CheckIn.vue(签到记录管理:列表/搜索/删除)

2. 数据库表结构

2.1 activities(活动主表)

sql
CREATE TABLE activities (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    creator_id BIGINT NOT NULL COMMENT '创建人ID(永远是人)',
    owner_type VARCHAR(20) NOT NULL DEFAULT 'PERSONAL' COMMENT '归属容器:PERSONAL/ORGANIZATION/DEPARTMENT(未来)',
    owner_id BIGINT NOT NULL DEFAULT 0 COMMENT '容器ID:个人=creator_id,组织=org_id,部门=dept_id',
    activity_name VARCHAR(100) NOT NULL,
    qr_content VARCHAR(500) COMMENT '活动码内容(即时渲染,不存qr_codes)',
    description TEXT,
    cover_url VARCHAR(500),
    location VARCHAR(200),
    latitude DOUBLE,
    longitude DOUBLE,
    start_time BIGINT,
    end_time BIGINT,
    status VARCHAR(20) DEFAULT 'DRAFT',
    max_participants INT,
    visibility VARCHAR(20) NOT NULL DEFAULT 'INVITE_ONLY' COMMENT 'PUBLIC公开/RESTRICTED指定容器内部/INVITE_ONLY仅邀请',
    restricted_scope_type VARCHAR(20) COMMENT 'RESTRICTED时必填:ORGANIZATION/DEPARTMENT',
    restricted_scope_id BIGINT COMMENT 'RESTRICTED时必填:容器ID',
    invite_code VARCHAR(10) COMMENT '仅INVITE_ONLY生成,二维码携带即邀请凭证',
    has_check_in BOOLEAN DEFAULT FALSE,
    has_pass BOOLEAN DEFAULT FALSE,
    created_at BIGINT NOT NULL,
    updated_at BIGINT NOT NULL,
    INDEX idx_owner (owner_type, owner_id),
    INDEX idx_discover (visibility, status, start_time),
    INDEX idx_status (status)
);

status 枚举值:

  • DRAFT → 草稿(初始状态)
  • ACTIVE → 进行中
  • ENDED → 已结束
  • CANCELLED → 已取消

状态流转(在 ActivityService.updateStatus() 中校验):

DRAFT → ACTIVE, CANCELLED
ACTIVE → ENDED, CANCELLED
其他状态 → 不允许转换

visibility 枚举值(§可见性架构):

  • PUBLIC → 公开:所有人可见可参与,发现页展示
  • RESTRICTED → 指定容器内部:restricted_scope_type/id 指向 ORGANIZATION(或未来 DEPARTMENT),仅该容器成员可见可参与
  • INVITE_ONLY → 仅邀请:创建者/容器成员/持邀请码(invite_code)可查看参与

可见性矩阵:

创建场景可选可见性默认
个人活动(PERSONAL)PUBLIC / INVITE_ONLYINVITE_ONLY
组织活动(ORGANIZATION)PUBLIC / RESTRICTED(→本组织) / INVITE_ONLYRESTRICTED(→本组织)

二维码内容规则:

  • INVITE_ONLY:activity:{id}:{inviteCode}(码即邀请凭证;重置邀请码后旧码作废)
  • PUBLIC / RESTRICTED:activity:{id}

容器转移规则: owner 变更时 restricted_scope 强制重算到新容器;转个人时 RESTRICTED 回退 INVITE_ONLY 并生成邀请码。

2.2 check_in_configs(签到配置)

sql
CREATE TABLE check_in_configs (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    activity_id BIGINT NOT NULL UNIQUE,
    qr_content VARCHAR(500) COMMENT '签到码内容(即时渲染,不存qr_codes)',
    gps_required BOOLEAN DEFAULT FALSE,
    latitude DOUBLE,
    longitude DOUBLE,
    radius INT DEFAULT 100,
    max_sign_per_user INT DEFAULT 1,
    check_in_code VARCHAR(8) UNIQUE,
    created_at BIGINT NOT NULL,
    FOREIGN KEY (activity_id) REFERENCES activities(id) ON DELETE CASCADE
);

2.3 check_in_records(签到记录)

sql
CREATE TABLE check_in_records (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    activity_id BIGINT NOT NULL,
    user_id BIGINT DEFAULT 0,
    check_in_time BIGINT NOT NULL,
    latitude DOUBLE,
    longitude DOUBLE,
    location_desc VARCHAR(200),
    device_info VARCHAR(100),
    is_offline BOOLEAN DEFAULT FALSE,
    is_synced BOOLEAN DEFAULT TRUE,
    created_at BIGINT NOT NULL,
    FOREIGN KEY (activity_id) REFERENCES activities(id) ON DELETE CASCADE,
    INDEX idx_activity_user (activity_id, user_id)
);

2.4 pass_configs(通行配置)

sql
CREATE TABLE pass_configs (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    activity_id BIGINT NOT NULL UNIQUE,
    qr_content VARCHAR(500) COMMENT '通行码内容(即时渲染,不存qr_codes)',
    valid_from BIGINT,
    valid_until BIGINT,
    max_uses_per_pass INT,
    requires_approval BOOLEAN DEFAULT FALSE,
    created_at BIGINT NOT NULL,
    FOREIGN KEY (activity_id) REFERENCES activities(id) ON DELETE CASCADE
);

2.5 passes(通行证实例)

sql
CREATE TABLE passes (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    template_id BIGINT,
    activity_id BIGINT,
    holder_name VARCHAR(50) NOT NULL,
    holder_info VARCHAR(200),
    pass_code VARCHAR(20) NOT NULL UNIQUE,
    status VARCHAR(20) DEFAULT 'ACTIVE',
    issued_at BIGINT NOT NULL,
    expires_at BIGINT,
    use_count INT DEFAULT 0,
    created_at BIGINT NOT NULL,
    FOREIGN KEY (template_id) REFERENCES pass_templates(id) ON DELETE SET NULL,
    FOREIGN KEY (activity_id) REFERENCES activities(id) ON DELETE CASCADE
);

2.6 pass_usage_records(通行使用记录)

sql
CREATE TABLE pass_usage_records (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    pass_id BIGINT NOT NULL,
    verified_by BIGINT,
    location VARCHAR(200),
    device_info VARCHAR(100),
    used_at BIGINT NOT NULL,
    result VARCHAR(20) NOT NULL,
    -- 其他字段略
);

3. 后端 API 说明

3.1 活动 CRUD

Controller: ActivityController (/api/activities)

方法端点描述请求体
GET/api/activities活动列表query: ownerType?(PERSONAL/ORGANIZATION,默认 PERSONAL), ownerId?
GET/api/activities/{id}活动详情query: inviteCode?(INVITE_ONLY 凭证)
POST/api/activities创建活动CreateActivityRequest(含 ownerType/ownerId/visibility/restrictedScope)
PUT/api/activities/{id}更新活动UpdateActivityRequest(含 visibility 切换/容器转移)
DELETE/api/activities/{id}删除活动-
PATCH/api/activities/{id}/status更新状态{ "status": "..." }
PATCH/api/activities/{id}/invite-code重置邀请码(仅 INVITE_ONLY,旧码作废)-

发现页(§可见性架构):

方法端点描述
GET/api/discover/activities公开活动(PUBLIC+ACTIVE,分页,含 ownerName)
GET/api/discover/orgs公开组织(PUBLIC+ACTIVE,分页,含 memberCount)

创建流程(server):

  1. 配额检查:按归属容器(PERSONAL→个人配额 / ORGANIZATION→组织配额)
  2. 权限:ORGANIZATION 容器需 ORG 域 activity:create(OWNER/ADMIN;MEMBER 无)
  3. 可见性默认值:个人 INVITE_ONLY / 组织 RESTRICTED(→本组织);个人禁 RESTRICTED
  4. INVITE_ONLY 自动生成 6 位邀请码,qr_content = activity:{id}:{inviteCode}
  5. hasCheckIn = true 且传了 checkInConfig → 创建 CheckInConfig
  6. hasPass = true 且传了 passConfig → 创建 PassConfig

可见性校验(详情/签到/通行证/落地页统一走 ActivityVisibility):

  • PUBLIC:任何人
  • RESTRICTED:restricted_scope 指向容器成员(MembershipService)
  • INVITE_ONLY:创建者 / 容器成员 / 邀请码凭证
  • H5 落地页 /s/activity/{id}/s/check-in/{id}:非 PUBLIC 不泄露内容,仅提示受限

3.2 管理后台 API

方法端点描述
GET/admin/activities后台活动列表(支持 keyword/status/分页)
POST/admin/activities新建活动(管理后台端)
PUT/admin/activities/{id}更新活动
GET/admin/activities/{id}活动详情(含签到记录)
DELETE/admin/activities/{id}删除活动
GET/admin/check-in签到记录列表
DELETE/admin/check-in/{id}删除签到记录
GET/admin/passes通行证列表
DELETE/admin/passes/{id}删除通行证
PUT/admin/passes/{id}/revoke吊销通行证
GET/admin/passes/{id}/records通行使用记录

4. Android 端功能流程

活动相关领域模型、Android 文件清单参见 /reference/android.md 第 3.4.6-3.4.8 节。

4.1 活动列表(ActivityListScreen + ActivityViewModel)

4.1 活动列表(ActivityListScreen + ActivityViewModel)

流程:

  1. 进入活动列表页 → ActivityViewModel 调用 repository.getActivities(orgId)
  2. 列表支持下拉刷新 + 按时间倒序展示
  3. 列表项含:活动名称、封面(coverUrl)、地点、时间、状态标签、签到/通行功能标记
  4. 点击列表项 → 跳转 ActivityDetailScreen
  5. 右上角"+"按钮 → 跳转 ActivityCreateScreen

视图模型状态:

kotlin
data class ActivityListUiState(
    val activities: List<ActivityModel> = emptyList(),
    val isLoading: Boolean = false,
    val error: String? = null
)

4.2 活动详情(ActivityDetailScreen)

展示内容:

  • 基本信息:名称、描述、时间、地点
  • 状态标签(进行中/已结束/已取消)
  • hasCheckIn = true → 显示"签到"按钮,点击进入签到界面
  • hasPass = true → 显示"通行证"入口,点击进入通行证列表

支持的上下文操作:

  • 编辑 → 跳转 ActivityEditScreen
  • 删除 → 确认弹窗后调用 repository.deleteActivity(id)
  • 分享 → 通过 qrContent 生成分享二维码

4.3 创建/编辑活动(ActivityCreateScreen / ActivityEditScreen + ActivityFormViewModel)

表单字段(第 2 步改版):

字段类型必填说明
activityNameString活动名称(最大 100 字符)
descriptionString-活动描述
regionString-所在地区(省市区,第 2 步表单/POI/地图回填)
locationString-详细地址+门牌号(第 2 步详细地址行)
latitude/longitudeDouble-地图选点/定位坐标
contactNameString表单必填联系人(红星必填,前端校验)
contactPhoneString-手机号
startTimeLong(timestamp)-开始时间
endTimeLong(timestamp)-结束时间
hasCheckInBoolean-是否启用签到功能
hasPassBoolean-是否启用通行功能
orgIdLong-所属组织(可选)

第 2 步地址区: 地图(AmapWebMap 高德 JS API,固定 240dp 贴边;点击/拖动取中心回填、右上 X 收起、右下定位)→ 表单(搜索 POI 候选下拉 / 智能粘贴 / 所在地区 / 详细地址 / 联系人 / 手机号,行间细线)。字段由 AddressViewModel 管理,同步 ActivityFormViewModel 后走统一保存。

创建流程(Android):

  1. 三步引导:基本信息 → 时间地点 → 子功能(下一步=保存草稿)
  2. 第 3 步保存草稿(不发布)或发布
  3. 成功后 Toast 提示 + 返回列表页

编辑流程(Android,草稿点击进入): 三步引导与创建一致;下一步=更新(updateActivity),第 3 步保存草稿/发布(DRAFT→ACTIVE)。

ActivityFormViewModel 核心函数:

  • createActivity(onSuccess) — 创建新活动
  • updateActivity(activityId, onSuccess) — 更新已有活动
  • loadActivity(activityId) — 编辑时回填表单
  • setOrgId(orgId), toggleCheckIn(), togglePass(), updateRegion(region) — 表单状态更新

AddressViewModel(第 2 步表单,新增):

  • AddressUiState:searchText / poiResults / region / detailAddress / contactName / contactPhone / mapCollapsed / hintBarVisible / locate*
  • parseClipboardText(text) — 智能粘贴解析(占位:手机号/姓名/地址正则)
  • saveAddress() — 占位
  • 字段变化实时同步 ActivityFormViewModel(region→region、detailAddress→location)

4.4 签到(CheckInScreen)

前端渲染:

  • 扫描活动码 → 解析 activity:{id} → 跳转签到页面
  • 签到页面显示:活动名称、当前签到状态、签到按钮
  • 位置约束:若 checkInConfig.gpsRequired = true,校验 GPS 距离是否在半径内
  • 码约束:若设置了 checkInConfig.checkInCode,用户需输入签到码

离线能力:

  • 网络不可用时将记录保存至本地 Room(syncStatus = "PendingCreate"
  • 应用恢复网络后通过 SyncManager 自动同步

4.5 通行证(PassScreen)

流程:

  1. 查看活动下所有已发放的通行证列表
  2. 每个通行证显示:持证人、通行码、状态、已使用次数
  3. 核验通行证:扫描通行码 → 服务端核验 → 返回核验结果
  4. 发放通行证:输入持证人信息 → 调用 issueActivityPass()

5. 管理后台功能流程

5.1 活动管理(Activities.vue)

功能清单:

  • 列表查询: 活动编号、活动名称、所属组织、子功能标签(签到/通行)、状态(色标签)、地点、时间、人数上限
  • 搜索筛选: 按活动名称搜索(keyword),按状态筛选(statusFilter)
  • 新建活动: 弹窗表单,字段同 Android 创建表单
  • 编辑活动: 弹窗回填已有数据
  • 删除活动: 弹窗确认,调用后台 API 删除
  • 详情弹窗:
    • Tab 1 基本信息(活动属性列表)
    • Tab 2 签到记录(活动关联的签到记录列表,需 hasCheckIn 为 true)
    • Tab 3 通行证(活动关联的通行证列表,需 hasPass 为 true)

5.2 签到记录管理(CheckIn.vue)

功能清单:

  • 签到记录列表:ID、用户ID、活动ID、签到时间、位置描述、GPS坐标、是否离线
  • 按活动筛选(activityFilter)
  • 删除签到记录

6. 领域模型

参见 /reference/android.md 第 3.4.6-3.4.8 节的 ActivityModelCheckInConfigModelPassConfigModel 定义。


7. 关键逻辑与约束

7.1 创建活动的配额校验

kotlin
// ActivityService.createActivity()
when {
    request.orgId != null -> quotaService.checkOrgActivityQuota(request.orgId)
    else                 -> quotaService.checkUserActivityQuota(userId)
}
if (request.orgId != null) {
    // 校验操作者是否是该组织成员
    val member = orgMemberRepository.findByOrgIdAndUserId(request.orgId, userId)
    if (member == null) throw BusinessException("你不是该组织的成员,无法在该组织下创建活动")
}

7.2 子功能配置管理

  • 关闭签到: 删除 checkInConfigs 中关联记录
  • 关闭通行: 删除 passConfigs 中关联记录,且将所有该活动的有效通行证状态置为 REVOKED
  • 开启签到/通行: 若已有配置则更新,无则新建

7.3 活动转移

通过 UpdateActivityRequest.orgId 修改活动所属组织:

  • 需校验操作者在新组织中的成员身份
  • 仅在 newOrgId != oldOrgId 时执行转移

7.4 二维码渲染

  • 活动码格式: activity:{id}
  • 签到码格式: check-in:{id}
  • 通行码格式: pass:{id}
  • 均不写入 qr_codes 表,通过 qr_content 字段即时渲染

8. 服务端文件清单

9. 文件清单

Android 端活动相关文件参见 /reference/android.md

文件用途
entity/Activity.kt活动实体
entity/CheckInConfig.kt签到配置实体
entity/PassConfig.kt通行配置实体
entity/Pass.kt通行证实例实体
repository/ActivityRepository.kt活动数据仓库
service/ActivityService.kt活动业务逻辑
controller/ActivityController.kt活动 API 控制器
controller/admin/ActivityAdminController.kt管理后台活动控制器
dto/ActivityDto.kt活动请求/响应 DTO

权限语义(RBAC,接入)

双域资源语义(RBAC v3.3,§1.2.8/§1.2.10/§1.2.11):个人活动(org_id 空)走 APP 域,组织活动(org_id 非空)走 ORG 域。

操作权限点说明
列表/详情双域 activity:read(orgId 为空降级 APP)MEMBER 预置只读
创建双域 activity:create(orgId 为空降级 APP)组织内仅 OWNER/ADMIN(MEMBER 只读,Service 层校验)
编辑/删除/改状态activity:update / activity:delete(Service findOwned 双域判定)个人活动严格属主;组织活动按 ORG 域角色
签到checkin:create参与语义:组织成员身份即够,MEMBER 预置)活动有 orgId 时校验成员资格,不按角色拦截
签到记录/统计checkin:read(查记录对成员开放)组织活动按 ORG 域校验(MEMBER 预置)
发放/核验/撤销通行证pass:issue / pass:verify / pass:revoke活动创建者豁免 + 组织角色(ADMIN/OWNER);MEMBER 对非自己创建的活动无权限(Service 按 activity.userId 判定)

关键行为

  • 属主语义(决策 B):组织活动由组织角色管理(ADMIN/自定义角色可管理组织内全部活动);个人活动属主私有
  • 活动创建者豁免仅作用于通行证管理(pass:*),活动本身的编辑/删除仍按角色