环境
fisco-bcos 2.8
ubuntu 20
solidity 0.4.25[版本有点老,也可以用0.6的版本 ]
前言
大家可以阅读上篇对于这个系统的介绍
上一篇 基于fisco-bcos的共享病例智能合约的设计与实现【2】
这篇将介绍共享病历系统的Roles库合约用RBAC 合约将 患者合约结合起来;
医生合约
pragma solidity^0.4.25;
// 医院信息管理智能合约
// 医生相关信息
contract Doctor{
string public DoctorName;
uint256 public Phone;
string public ProfessionTitle;
uint256 public Age;
uint8 public Gendor; // 0 男,1 女
uint256 public CreateTime;
constructor(string _dName,uint256 _phone,string _pTitle) public{
DoctorName = _dName;
Phone = _phone;
ProfessionTitle = _pTitle;
Age = 0;
Gendor = 0;
CreateTime = now;
}
function getDoctorDetailByD() public view returns(string,uint256,string,uint256,uint8,uint256){
return (DoctorName,Phone,ProfessionTitle,Age,Gendor,CreateTime);
}
function updateDoctorDetailByD(string _name,uint256 _phone,string _pTitle,uint256 _age,uint8 _gendor) public {
if(keccak256(abi.encode(_name)) != keccak256(abi.encode(""))){
DoctorName= _name;
}
if(_phone != 0){
Phone = _phone;
}
if(keccak256(abi.encode(_pTitle)) != keccak256(abi.encode(""))){
ProfessionTitle = _pTitle;
}
if(_age != 0){
Age = _age;
}
Gendor = _gendor;
}
}
这个医生合约 有名字,电话,职称,年龄,性别,创建时间 ,变量。在构造器的时候对其进行初始化,然后updateDoctorDetailByD()
方法可以对其更新,getDoctorDetailByD()
方法可以查看医生的基础信息。
这种设计有点像软件工程的设计实体一样,我已java web为例子
我们每在数据库设计好一张表,如user 表,里面有 id,username,password 三个字段
然后我们使用java 往数据库存储数据的时候,我们是使用 类来映射数据库字段,所以创建了个entity(实体),> User类
public class User {
private Integer id;
private String username;
private String password;
}
将这种设计应用在智能合约上,这个实体 就是医生合约。但是区块链就没有数据库这个层了,因为只要医生合约部署了或者在别的合约里 new
创建出来,就已经将一个医生信息给存储起来了。
病人合约
pragma solidity^0.4.25;
contract Patient{
address public patientId;
string public patientName;
uint256 public phone;
string public allergy; //过敏史 默认:无
uint32 public age; //年龄
uint8 public gender; //性别 默认:0,男 ; 1,女
uint256 public createTime;
// 初始化一个病人信息
constructor(address _amount,uint256 _phone,string _userName) public{
patientId = _amount;
patientName = _userName;
createTime = now;
phone = _phone;
allergy = "无";
gender = 0;
}
function updatePatientByP(string _allergy,uint32 _age,uint8 _gender) public{
if(keccak256(abi.encode(_allergy)) != keccak256(abi.encode(""))){
allergy = _allergy;
}
if(_age != 0){
age = _age;
}
_gender = _gender;
}
function getPatientDetailByP() public view returns(string,uint256,string,uint32,uint8,uint256){
return (patientName,phone,allergy,age,gender,createTime);
}
}
address public patientId;
string public patientName;
uint256 public phone;
string public allergy; //过敏史 默认:无
uint32 public age; //年龄
uint8 public gender; //性别 默认:0,男 ; 1,女
uint256 public createTime;
这个病人合约有病人id,姓名,电话,过敏史,年龄,性别和创建时间,基本与医生合约的变量没啥太大区别。
RBAC合约
这是我设计的一个桥梁合约
,其作用就是用来连接Roles库合约和病人医生合约,将其绑定一起,来方便在后面的业务操作中执行相应 的角色功能和判断其角色权限
构造器
// 角色库使用
using Roles for Roles.Role;
mapping(string=>Roles.Role) roles;
// 管理员
address admin;
// 构造器
constructor(address amount) public{
admin = amount;
}
构造器的用处是设置一个管理员,最后默认是部署合约的那个人
然后 将Roles 附加到Roles.Role 类型上,这样Roles的函数将接收到调用它们的对象作为它们的第一个参数。
– 在这还设置了一个mapping roles,用来做角色区分,将病人和医生的role区分开。
基础方法
// ----------------------------------基础方法---------------------------------------------------
function addRole(address _operater,string memory _role,address _roleDetail) private{
roles[_role].addRole(_operater,_roleDetail);
}
function hasRole(address _operater,string memory _role) public view returns(bool){
return roles[_role].hasRole(_operater);
}
function removeRole(address _operater,string memory _role) public{
roles[_role].removeRole(_operater);
}
function getRoleList(string memory _role) public view returns(address[]){
return roles[_role].getRoleList();
}
这几个方法可以方便的为我们处理Role,封装起来,后续使用
病人相关方法
// 病人用户进行注册
event EventPatientEnroll(address userName,address patient);
function patientEnroll(uint256 _phone,string _userName) public returns(bool){
// address _amount,uint256 _phone,string _userName
Patient patient = new Patient(msg.sender,_phone,_userName);
addRole(msg.sender,"PA",address(patient));
emit EventPatientEnroll(msg.sender,address(patient));
return true;
}
这里就需要new
一个Patient的合约,这个合约在上一篇有讲解,
然后使用addRole()
方法,加入病人Role Mapping, 这里可以看到第一个参数是msg.sender是用户地址,第二个参数是角色标识,第三个参数是病人合约地址。并且调用注册病人事件。
// 病人用户进行信息更新
function updateByPatient(string memory _role,string memory _allergy,uint32 _age,uint8 _gender) onlyPatient(msg.sender) public returns(bool){
address paticentAddress = roles[_role].getRoleDetail(msg.sender);
Patient patient = Patient(paticentAddress);
patient.updatePatientByP(_allergy,_age,_gender);
return true;
}
// 查看病人的详细信息
function getPatientDetail(string memory _role,address amount) public view onlyPatient(amount) returns(string,uint256,string,uint32,uint8,uint256){
address paticentAddress = roles[_role].getRoleDetail(amount);
Patient patient = Patient(paticentAddress);
return patient.getPatientDetailByP();
}
// 病人删除
function removePatient(string memory _role) public onlyPatient(msg.sender) returns(bool){
removeRole(msg.sender,_role);
return true;
}
// 工具类,获取该病人的合约地址
function getPatientAddress2(address amount) public view returns(address){
return roles["PA"].getRoleDetail(amount);
}
这些方法就不作讲解,都是简单的更改查看删除操作,调用的都是之前写好的底层方法。
– 最后一个方法,这里我有个经验,分享给大家
// 工具类,获取该病人的合约地址
function getPatientAddress() public view onlyPatient(tx.origin) returns(address){
return roles["PA"].getRoleDetail(tx.origin);
}
这是我之前写的,用到的是tx.origin ,但是这是有问题的,这会产生钓鱼问题,举个例子,病人A直接调用这个方法没问题,若有恶人B,写了一个方法C[方法里面其实是调用getPatientAddress()],病人A被诱导去调用这个方法C, 这样getPatientAddress 识别的tx.origin 还是病人A,这个方法就通过了权限modify ,恶人B 也相当于形式了病人A的权限。所以避免这个钓鱼漏洞,可以将其改成msg.sender
,若真的需要调用tx.origin ,可以加一条tx.origin == msg.sender
, 但我推荐第一个方法预防,因为后者会拒绝所有来自其他合约的调用。
结语
这篇介绍了病人合约和医院合约和RBAC合约对病人合约的用法,下一篇将介绍如何用RBAC合约结合医生合约,并且还需要介绍医院 和科室合约【因为医生需要和医院和科室进行绑定】