☰
【A11】服务器底层权限表笔记
2026/10/18 22:17:03 网站建设 项目流程

从服务器职责出发,梳理权限表的定位、结构、使用方式。服务器是"权威账本 + 数据仓库 + 路由",纯响应式;读写与同步由客户端主导;版本号由时间式机制天然承载。


一、服务器职责

1.1 三件事

服务器只做三件事: 1. 维护权限表(grants):谁对什么有权限 2. 存储数据(values):资源的字节 + 版本号 3. 响应请求:按权限返回 / 按权限接收 / 移交仲裁

不解释数据、不执行业务、不做权限计算、不主动推送。

1.2 不做什么

不做原因
不解释 R(字节内容)只存 bytes
不执行业务逻辑客户端负责
不计算权限(继承、组、角色)中间环节暂不考虑,最终落到 grants
不主动下传纯响应式,客户端请求才下发
不做同步决策同步由客户端主导

服务器是"账本 + 仓库 + 路由",不是"业务处理器",也不是"主动推送者"。

1.3 与客户端的分工

服务器:关心设备(pc_id),按表响应请求 客户端:关心登录人(user),按权限表过滤展示,主导读写与同步 服务器以"设备"为单位,纯响应 客户端以"用户"为单位,主动驱动

二、权限表:grants

2.1 结构

CREATETABLEgrants(resourceINTEGERNOTNULL,-- 资源编号pc_idINTEGERNOTNULL,-- 设备编号levelTEXTNOTNULL,-- 'read' | 'own'PRIMARYKEY(resource,pc_id));-- 一个资源只有一个写者CREATEUNIQUEINDEXidx_one_own_per_resourceONgrants(resource)WHERElevel='own';-- 按设备查CREATEINDEXidx_grants_pcONgrants(pc_id);

2.2 语义

有记录 → 能读(客户端可请求数据) level='own' → 还能写(是数据源,隐含读权限) 读:有记录 = 有读权限(客户端可请求) 写:level='own' = 写权限(同时隐含读权限)

注意:有读权限 ≠ 服务器主动下传。有读权限只是"客户端可以请求"。

2.3 唯一性

一个资源: - 多行(多个读者,level='read') - 一行 own(唯一写者,level='own') 由部分唯一索引保证。

三、数据表:values

3.1 结构

CREATETABLEvalues(resourceINTEGERPRIMARYKEY,kindINTEGERNOTNULL,-- 资源类型bytesBLOBNOTNULL,-- 数据字节versionINTEGERNOTNULL-- 版本号(TimeId));

3.2 语义

kind:资源类型(用于客户端反序列化) bytes:数据(不透明,服务器不解释) version:版本号(时间式机制,单调递增)

3.3 版本号的来源

version 直接用 TimeId: - 由时间式同步机制天然承载 - 每次写产生新 TimeId - 单调递增、全局唯一、带物理时间语义 不需要额外的 u64 版本字段。

四、客户端主导的读写与同步

4.1 客户端做什么

客户端: - 决定何时读(拉取数据) - 决定何时写(编辑、上传) - 决定何时同步(上传本地变更、拉取更新) - 处理冲突(用 version 比对) 服务器: - 存数据(values) - 记权限(grants) - 响应请求(pull / push / transfer) - 不做决策

客户端是"驱动者",服务器是"服务者"。

4.2 写权限设备是数据源

写权限设备 D 拥有 R: - D 是数据源 - R 的数据在 D 本地产生 - D 上传给服务器(备份 + 分发) - 平时不需要从服务器下传 - 如果服务器下传给 D,反而可能覆盖更新的数据

4.3 下传的场景

正常情况下传(写权限设备): - 无 异常下传(写权限设备): 1. 设备移交后首次登录 → 新 owner 需要 R 的当前数据 2. 本地数据损坏 / 丢失 → 从服务器恢复 正常下传(读权限设备): - 客户端请求时下发 - 按需拉取

五、下发:pull(客户端请求触发)

5.1 流程

设备 D 请求数据: 1. 客户端发起请求(不是服务器主动) 2. 查 grants WHERE pc_id = D 3. 得到 D 有权限的资源 4. 查 values 拿 bytes + version 5. 返回

关键:客户端请求才下发,服务器不主动推。

5.2 SQL

-- D 有权限的资源(有记录 = 有读权限)SELECTresourceFROMgrantsWHEREpc_id=?;-- 拿数据SELECTresource,kind,bytes,versionFROMvaluesWHEREresourceIN(SELECTresourceFROMgrantsWHEREpc_id=?);

5.3 增量拉取

客户端请求: "给我 last_sync_version 之后的变化" 带 last_sync_version(上次同步到的 TimeId) 服务器返回: version > last_sync_version 的所有变更

按时间式版本增量拉取。


六、接收:push

6.1 流程

设备 D 上传资源 R: 1. 查 grants WHERE resource = R AND pc_id = D AND level = 'own' 2. 存在 → 允许 3. 不存在 → 拒绝 4. 校验 base_version == values[R].version ? - 匹配 → 接受,version = 新 TimeId - 不匹配 → 拒绝,返回服务器最新 5. 更新 values

6.2 SQL

-- 校验 D 是否是 R 的写者SELECT1FROMgrantsWHEREresource=?ANDpc_id=?ANDlevel='own';-- 校验版本 + 更新UPDATEvaluesSETbytes=?,version=?WHEREresource=?ANDversion=?;-- 影响行数 0 → 版本冲突

写必须 level = ‘own’,且版本匹配。

6.3 冲突处理

版本冲突时: 客户端收到: - 服务器最新 version - 服务器最新 bytes 客户端决定: - 合并(重新编辑,基于新版本) - 覆盖(放弃服务器,强制上传) - 放弃(放弃本地)

冲突处理在客户端。


七、移交

7.1 流程

移交时需要服务器: 1. A 请求移交 R 给 B 2. 服务器查 grants:R 的 own 是不是 A? 3. 是 → 更新 grants:R 的 own 改为 B 4. 返回成功 5. A 本地清除,B 本地获得

7.2 移交后的首次下传

B 获得 R 的写权限后: - B 的设备上还没有 R 的数据 - B 首次登录时,请求 R 的数据 - 服务器下发 R 的最新 bytes + version - B 本地存储,开始作为新的数据源

移交后首次登录,下传一次。

7.3 对比

操作权限来源服务器参与下传?
读(read 设备)本地缓存请求时✅ 请求触发
写(平时)本地缓存上传时❌ 不下传
写(移交后首次)服务器确认 + 下发✅ 一次性
写(本地损坏)服务器请求恢复✅ 请求触发
移交服务器✅❌

八、时间式版本号

8.1 版本号的本质

版本号 = TimeId(时间式同步机制) - 单调递增 - 全局唯一 - 带物理时间语义 每次写: values[R].version = 新 TimeId 不需要额外的 u64 版本字段。

8.2 用途

1. 冲突检测: 客户端 base_version vs 服务器 version 2. 增量拉取: version > last_sync_version 3. 审计: version 带时间,可追溯

8.3 统一

一套 TimeId,覆盖: - 棒 ID - 版本号 - 冲突检测 - 增量同步

九、完整的服务器代码骨架

pubstructServer{grants:GrantsTable,values:ValuesTable,}implServer{/// 拉取权限:客户端问"我能访问什么"fnpull_grants(&self,pc:PcId)->Result<Vec<Grant>>{self.grants.find_by_pc(pc)}/// 拉取数据:客户端请求它有权访问的资源/// 客户端请求触发,服务器不主动推fnpull_values(&self,pc:PcId,resources:&[ResourceId],since:Option<TimeId>,)->Result<Vec<ServerResource>>{// 校验 pc 对这些资源有权限letallowed:Vec<_>=resources.iter().filter(|r|self.grants.has_access(**r,pc).unwrap_or(false)).collect();// 按 since 过滤增量allowed.iter().filter_map(|r|self.values.get(*r).ok()).filter(|v|since.map_or(true,|s|v.version>s)).collect()}/// 接收:pc 上传资源 Rfnpush(&self,pc:PcId,resource:ResourceId,base_version:TimeId,bytes:Vec<u8>,)->Result<TimeId>{// 1. 校验写权限letlevel=self.grants.level_of(resource,pc)?;iflevel!=Level::Own{returnErr(Error::NotOwner);}// 2. 校验版本letcurrent=self.values.get(resource)?;ifcurrent.version!=base_version{returnErr(Error::VersionConflict{latest:current.version,latest_bytes:current.bytes,});}// 3. 生成新版本 + 存储letnew_version=TimeId::next();self.values.update(resource,bytes,new_version)?;Ok(new_version)}/// 移交:把 R 的写权限从 A 转给 Bfntransfer(&self,actor:PcId,resource:ResourceId,new_pc:PcId)->Result<()>{letlevel=self.grants.level_of(resource,actor)?;iflevel!=Level::Own{returnErr(Error::NotOwner);}self.grants.update_owner(resource,new_pc)?;Ok(())}}

四个方法:pull_grants / pull_values / push / transfer,全部响应式。


十、中间环节暂不考虑

10.1 暂不考虑

暂不考虑: - 权限继承(子资源继承父资源) - 组授权(组可读 → 组成员可读) - 角色授权(角色可读 → 角色成员可读) - 动态权限(按条件计算)

10.2 grants 是"最终形态"

不管权限怎么计算(继承、组、角色), 最终都落到 grants 表: (resource, pc_id, level) 服务器只认 grants。

10.3 先跑通

第一步: - grants 表手动维护 - pull_grants / pull_values / push / transfer - 时间式版本号 - 跑通 第二步: - 加继承、组、角色 - 自动生成 grants

十一、一句话

服务器是"权威账本 + 数据仓库 + 路由":维护 grants(权限)和 values(数据 + 版本),纯响应式。读写与同步由客户端主导;写权限设备是数据源,平时不下传;移交后首次登录、本地损坏时才下传。

版本号由时间式同步机制(TimeId)天然承载,一套机制覆盖棒 ID、版本、冲突检测、增量同步。grants 是权限的最终形态,中间环节暂不考虑。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询