联盟链前端不是 DApp——用户通常不直接管理私钥,由后端服务代为签名。REST API 是联盟链应用的标准接口模式。
15.6.1 架构模式
graph LR
User[用户浏览器] --> Frontend[React/Vue 前端]
Frontend --> |HTTP| API[Express/Fastify REST API]
API --> |Gateway| Fabric[Fabric 网络]
API --> Auth[认证层]
style API fill:#e3f2fd
style Fabric fill:#c8e6c9
身份模式对比
| 模式 | 谁签名 | 场景 |
|---|---|---|
| 服务代签 | 后端持有组织 admin 私钥 | 物流、溯源、监管系统 |
| 多级权限 | 用户登录 → 后端按角色转发 | 内部系统 |
| 客户端证书 | 用户自带 x.509 | 高安全需求 |
15.6.2 REST API 设计
typescript
/**
* Express 路由:资产溯源 API
*/
import express from 'express';
import { FabricClientService } from '../fabric-client';
const router = express.Router();
// 资产 CRUD
router.get('/assets/:id', async (req, res) => {
const asset = await fabric.getAsset(req.params.id);
res.json(asset);
});
router.post('/assets', authenticate, authorize('admin'), async (req, res) => {
const { id, color, size, owner } = req.body;
const txId = await fabric.createAsset({ id, color, size, owner, appraisedValue: 0 });
res.status(201).json({ transactionId: txId, status: 'submitted' });
});
router.put('/assets/:id/transfer', async (req, res) => {
const { newOwner, reason } = req.body;
const txId = await fabric.transferAsset(req.params.id, newOwner);
res.json({ transactionId: txId, newOwner, reason });
});
// 溯源查询:时间线
router.get('/assets/:id/history', async (req, res) => {
const history = await fabric.getAssetHistory(req.params.id);
// 返回按时间排序的变更事件
res.json(history.map(h => ({
txId: h.txId,
timestamp: h.timestamp,
owner: h.value.owner,
change: h.isDelete ? 'deleted' : 'modified',
})));
});15.6.3 前端界面设计
graph TD
U[用户] --> List[资产列表页]
List --> Detail[资产详情页]
Detail --> History[变更历史时间线]
Detail --> Transfer[转移操作]
Transfer --> Confirm[确认弹出框]
Confirm --> TxResult[交易结果]
style List fill:#e3f2fd
style Detail fill:#e8f5e9
界面要点
- 权限感知 UI:管理员看到"创建"按钮,普通用户只读
- 交易状态轮询:显示 "待确认" → "已提交" → "已出块"
- 时间线可视化:用时间线组件展示资产从出厂到现在的流转路径
- 二维码/批次码:扫码查询资产
15.7 权限控制与私有数据集合
15.7.1 私有数据集合(Private Data Collection)
联盟链中,并非所有数据都应对所有参与者可见。私有数据集合让敏感字段仅对特定组织可见,同时其公共哈希上链防篡改。
graph TB
subgraph Public["公共数据(所有组织可见)"]
P1[资产ID, 所有者, 公共状态]
P2[公共哈希 = keccak256(私有数据)]
end
subgraph Private["私有数据(仅 Org1 可见)"]
D1[价格, 供应商, 内部评级]
D2[存储于 Org1 的私有数据状态数据库]
end
P2 -.-> |防篡改| D1
style Private fill:#e8f5e9
style Public fill:#fff3e0
集合定义
yaml
# collections_config.json
[
{
"name": "assetPrivateDetails",
"policy": "OR('Org1MSP.member')",
"requiredPeerCount": 1,
"maxPeerCount": 1,
"blockToLive": 0,
"memberOnlyRead": true,
"endorsementPolicy": {
"signaturePolicy": "OR('Org1MSP.peer', 'Org2MSP.peer')"
}
}
]15.7.2 访问控制策略
| 层次 | 机制 | 控制点 |
|---|---|---|
| 网络层 | TLS 证书 + 通道隔离 | 节点能否加入网络 |
| 链码层 | GetMSPID() 检查 | 调用者的组织身份 |
| 数据层 | 私有集合 policy | 谁能看到私有数据 |
| 应用层 | RBAC 角色系统 | 用户权限映射 |
go
// 链码层检查
func (s *SmartContract) ConfidentialOperation(ctx contractapi.TransactionContextInterface, ...) error {
mspID, _ := ctx.GetClientIdentity().GetMSPID()
if mspID != "Org1MSP" && mspID != "Org2MSP" {
return fmt.Errorf("unauthorized: %s", mspID)
}
// 继续执行...
return nil
}> ← 15.5 SDK 后端 | 本章最后一节已合并 |*
评论
0评论加载中…