外观
活动功能
活动是一个可管理的时间段聚会/事件,支持签到和通行证两大子功能。 遵循"即时渲染二维码"原则:活动码/签到码/通行码均通过 qr_content 字段即时渲染,不写入 qr_codes 表。
1. 入口
| 端 | 入口 |
|---|---|
| Android | 主菜单 → 活动,或侧边栏 → 活动 |
| 管理后台 | 侧边栏 → 活动管理(Activities.vue)、签到记录管理(CheckIn.vue) |
前端路由 / 导航
Android:
ActivityListScreen.kt→ 活动列表主页,使用ActivityViewModelActivityDetailScreen.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_ONLY | INVITE_ONLY |
| 组织活动(ORGANIZATION) | PUBLIC / RESTRICTED(→本组织) / INVITE_ONLY | RESTRICTED(→本组织) |
二维码内容规则:
- 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):
- 配额检查:按归属容器(PERSONAL→个人配额 / ORGANIZATION→组织配额)
- 权限:ORGANIZATION 容器需 ORG 域
activity:create(OWNER/ADMIN;MEMBER 无) - 可见性默认值:个人 INVITE_ONLY / 组织 RESTRICTED(→本组织);个人禁 RESTRICTED
- INVITE_ONLY 自动生成 6 位邀请码,qr_content =
activity:{id}:{inviteCode} - 若
hasCheckIn = true且传了checkInConfig→ 创建CheckInConfig - 若
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)
流程:
- 进入活动列表页 →
ActivityViewModel调用repository.getActivities(orgId) - 列表支持下拉刷新 + 按时间倒序展示
- 列表项含:活动名称、封面(coverUrl)、地点、时间、状态标签、签到/通行功能标记
- 点击列表项 → 跳转
ActivityDetailScreen - 右上角"+"按钮 → 跳转
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 步改版):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| activityName | String | ✅ | 活动名称(最大 100 字符) |
| description | String | - | 活动描述 |
| region | String | - | 所在地区(省市区,第 2 步表单/POI/地图回填) |
| location | String | - | 详细地址+门牌号(第 2 步详细地址行) |
| latitude/longitude | Double | - | 地图选点/定位坐标 |
| contactName | String | 表单必填 | 联系人(红星必填,前端校验) |
| contactPhone | String | - | 手机号 |
| startTime | Long(timestamp) | - | 开始时间 |
| endTime | Long(timestamp) | - | 结束时间 |
| hasCheckIn | Boolean | - | 是否启用签到功能 |
| hasPass | Boolean | - | 是否启用通行功能 |
| orgId | Long | - | 所属组织(可选) |
第 2 步地址区: 地图(AmapWebMap 高德 JS API,固定 240dp 贴边;点击/拖动取中心回填、右上 X 收起、右下定位)→ 表单(搜索 POI 候选下拉 / 智能粘贴 / 所在地区 / 详细地址 / 联系人 / 手机号,行间细线)。字段由 AddressViewModel 管理,同步 ActivityFormViewModel 后走统一保存。
创建流程(Android):
- 三步引导:基本信息 → 时间地点 → 子功能(下一步=保存草稿)
- 第 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)
流程:
- 查看活动下所有已发放的通行证列表
- 每个通行证显示:持证人、通行码、状态、已使用次数
- 核验通行证:扫描通行码 → 服务端核验 → 返回核验结果
- 发放通行证:输入持证人信息 → 调用
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 节的ActivityModel、CheckInConfigModel、PassConfigModel定义。
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:*),活动本身的编辑/删除仍按角色