环境
fisco-bcos 2.8
ubuntu 20
solidity 0.4.25[版本有点老,也可以用0.6的版本 ]
前言
大家可以阅读上篇对于这个系统的RBAC合约讲解
上一篇 基于fisco-bcos的共享病例智能合约的设计与实现【4】
这篇将讲解这个系统部署合约SyStem
构造器和继承合约
contract System is RBAC{
address owner;
constructor() public RBAC(msg.sender){
owner = msg.sender;
}
}
部署合约System需要继承RBAC的桥梁合约,等于将其RBAC的所有方法导入到System 合约,并且在构造器对其RBAC进行传参,表明不仅部署者是owner ,也是RBAC的admin。
出诊相关功能
// -----------------------------------出诊-------------------------------------
// 出诊列表
mapping(address=>address[]) workListMapping;
mapping(address=>bool) workBoolMapping;
// 出诊
function addWorkForDoctor(address _hospitalA,address _departmentA,address _doctorA) public onlyAdmin(msg.sender) returns(bool) {
require(RBAC.hasDoctor(_hospitalA,_departmentA,_doctorA),"医生不存在");
require(!workBoolMapping[_doctorA],"该医生已经出诊了");
Hospital hospital = Hospital(_hospitalA);
Department department = Department(_departmentA);
Doctor doctor = Doctor(_doctorA);
Work work = new Work(hospital.HospitalName(),department.DepartmentName(),_doctorA,doctor.DoctorName());
workBoolMapping[_doctorA] =true;
workListMapping[_departmentA].push(address(work));
return true;
}
// 出诊该部门的出诊列表
function getWorkForList(address _hospitalA,address _departmentA) public view returns(address[]){
require(RBAC.hasDepartment(_hospitalA,_departmentA),"该部门不存在");
return workListMapping[_departmentA];
}
// 获取出诊细节 ps :需要拿这个方法测错误,强制转换成不符合的合约看下是怎么报错的
function getWorkDetail(address _hospitalA,address _departmentA,address _workA) public view returns(string,string,address,string){
bool temp = utilByWork(_hospitalA,_departmentA, _workA);
require(temp,"并无该出诊记录");
Work work = Work(_workA);
return work.getWorkDetailByW();
}
// 结束出诊
function finalWork(address _hospitalA,address _departmentA,address _workA) public onlyAdmin(msg.sender) returns(bool){
require(RBAC.hasDepartment(_hospitalA,_departmentA),"该部门不存在");
Work work = Work(_workA);
address doctorA = work.doctorID();
require(workBoolMapping[doctorA],"并无该出诊记录");
workBoolMapping[doctorA] =false;
address[] storage workListA = workListMapping[_departmentA];
for(uint i= 0;i<workListA.length;i++){
if(_workA == workListA[i]){
for(uint j = i;j<workListA.length-1;j++){
workListA[j] = workListA[j+1];
}
workListA.length--;
break;
}
}
return true;
}
// 工具类,可以确定这个work是否真实存在,防止报错
function utilByWork(address _hospitalA,address _departmentA,address _workA) private view returns(bool){
bool temp =false;
require(RBAC.hasDepartment(_hospitalA,_departmentA),"该部门不存在");
address[] storage workList = workListMapping[_departmentA];
for(uint i;i<workList.length;i++){
if(_workA == workList[i]){
temp = true;
break;
}
}
return temp;
}
解释一下部分方法的用途
addWorkForDoctor
出诊操作,这里设置了一个科室出诊mapping[科室地址=> work AddressList]和出诊mapping[医生地址=>bool],医生出诊,将其出诊mapping设置为true, 科室出诊mapping 将其生成 的Work对象地址,映射到医生科室的address下的workAddressList ,push 进去
pragma solidity^0.4.25;
// 科室出诊信息管理智能合约
//Work
contract Work{
string public hospital; //医院名称
string public department; //科室
address public doctorID;//医生id
string public doctorName;//医生名字
constructor(string _h,string _d,address _dID,string _dName) public{
hospital = _h;
department = _d;
doctorID = _dID;
doctorName = _dName;
}
function getWorkDetailByW() public view returns(string,string,address,string){
return (hospital,department,doctorID,doctorName);
}
}
在出诊List 记录好各种医生的出诊信息,到时候病人查看就能展示出诊列表进行选择医生就诊
getWorkForList和getWorkDetail
这两个方法在应用层需要组合使用,因为科室出诊mappping只是保存其出诊合约对象的地址,拿到mapping里面的list 后,再循环拿地址做参数调用getWorkDetail,整理好数据展示给前端
finalWork
结束出诊,医生自己调用该方法,将其从科室出诊mapping和出诊mapping中将自己的信息删除掉
预约相关功能
// -----------------------------------预约-------------------------------------
// 病人预约挂号
//当前问诊预约人数
mapping(address => address[]) doctorByAppointmentIng;
// 已经完成问诊的人数
mapping(address=>address[]) doctorByAppointmentOld;
// 病人的挂号 (由于不想设计的太复杂,只能预约一个)
mapping(address=> address) patientAppoint;
// 病人的预约历史记录,正在预约或者就诊中的不算
mapping(address=>address[]) patientAppointHistory;
function addAppointment(string _apTime,address _hospitalA,address _departmentA,address _workA) public onlyPatient(msg.sender) returns(bool) {
address patientA =RBAC.getPatientAddress();
Patient patient = Patient(patientA);
require(patientAppoint[patientA] ==address(0),"你已经预约过一个,需要取消之前的再进行预约");
bool temp = utilByWork(_hospitalA,_departmentA,_workA);
require(temp,"并无该出诊记录");
Work work = Work(_workA);
Appointment appointment = new Appointment(patientA,work.doctorID(),patient.patientName(),work.doctorName(),_apTime);
address[] storage list = doctorByAppointmentIng[work.doctorID()];
list.push(address(appointment));
patientAppoint[address(patient)] = address(appointment);
return true;
}
// 当前医生查看自己是否有预约挂号人员 特殊情况不适用view
function getAppointmentByDocotor() public view returns(address[]){
address doctorA = RBAC.getDoctorAddress();
return doctorByAppointmentIng[doctorA] ;
}
// 当前医生查看自己以前诊断结束的人员 特殊情况不适用view
function getAppointmentByDocotorByOldHistory() public view returns(address[]){
address doctorA = RBAC.getDoctorAddress();
return doctorByAppointmentOld[doctorA];
}
// 病人查看自己的预约号 特殊情况不适用view
function getAppointmentByPatient() public view onlyPatient(msg.sender) returns(address){
address patientA =RBAC.getPatientAddress();
Patient patient = Patient(patientA);
return patientAppoint[address(patient)];
}
// 查看预约挂号细节 特殊情况不适用view
function getAppointmentDetail(address _appA) public view returns(address,address,string,string,string,uint256) {
Appointment appointment = Appointment(_appA);
if(hasRole(msg.sender,"PA")){
address patientA = RBAC.getPatientAddress();
require(appointment.PatientID() == patientA ,"你不具有该预约的查看权限 病人");
}else if(hasRole(msg.sender,"DO")){
address doctorA = RBAC.getDoctorAddress();
require(appointment.DoctorID() == doctorA ,"你不具有该预约的查看权限 医生");
}else{
require(false,"你不具有该预约的查看权限 啥也不说");
}
return appointment.getAppointmentDetailByD();
}
// 医生确认就诊进入就诊状态
function changeStatus(address _appA) public onlyDoctor(msg.sender) returns(bool){
Appointment appointment = Appointment(_appA);
address doctorA = RBAC.getDoctorAddress();
require(appointment.DoctorID() == doctorA,"你不是该预约的医生");
appointment.setStatus(1);
return true;
}
// 病人取消预约
function cancelAppointment(address _appA) public onlyPatient(msg.sender) returns(bool){
address patientA =RBAC.getPatientAddress();
require(patientAppoint[patientA]!=address(0),"你并没有预约");
Appointment appointment = Appointment(_appA);
require(appointment.PatientID()==patientA,"你不是该预约病人");
appointment.setStatus(3);
// 将当前病人预约清零
Patient patient = Patient(patientA);
patientAppoint[address(patient)] = address(0);
// 推送到病人历史挂号上
address[] storage patientOldList = patientAppointHistory[appointment.PatientID()];
patientOldList.push(_appA);
// 将医生的正在预约号清除
clearIngByDoctor(_appA);
// 并不需要推送到医生的历史挂号上
// doctorByAppointmentOld[msg.sender].push(_appA);
return true;
}
// 工具类,清理正在预约的号
function clearIngByDoctor(address _appA) private {
Appointment appointment = Appointment(_appA);
address DoctorID = appointment.DoctorID();
address[] storage appointmentListIng = doctorByAppointmentIng[DoctorID];
for(uint i = 0;i<appointmentListIng.length;i++){
if(_appA == appointmentListIng[i]){
for(uint j = i;j<appointmentListIng.length-1;j++){
appointmentListIng[j] = appointmentListIng[j+1];
}
appointmentListIng.length--;
break;
}
}
}
// 医生诊断结束
function finalAppointment(address _appA) public onlyDoctor(msg.sender) returns(bool){
Appointment appointment = Appointment(_appA);
address doctorA = RBAC.getDoctorAddress();
require(appointment.DoctorID() == doctorA,"你不是该预约的医生");
appointment.setStatus(2);
// 清除正在预约的号
clearIngByDoctor(_appA);
//推送到医生的历史挂号上
address[] storage doctorOldList = doctorByAppointmentOld[doctorA];
doctorOldList.push(_appA);
// 将当前病人预约清零
patientAppoint[appointment.PatientID()] = address(0);
// 推送到病人历史挂号上
address[] storage patientOldList = patientAppointHistory[appointment.PatientID()];
patientOldList.push(_appA);
return true;
}
这里介绍一下 所用的变量
mapping(address => address[]) doctorByAppointmentIng;
这个映射的是医生id => appointment 的addressList
这个Appointment 是预约挂号合约,病人挂号时候,会生成一个新的Appointment合约对象,并放入到这个映射中的挂号list 中,所以这个映射意思就是,医生可以有多个预约挂号单
pragma solidity^0.4.25;
// 预约挂号信息管理智能合约
// 记录病人预约医院科室医生信息,记录医生是否进行诊断等信息
contract Appointment{
address public PatientID;
address public DoctorID;
string public PatientName;
string public DoctorName;
string public ApTime;
uint256 public State; //预约挂号状态 0-预约成功,1-
// 诊断中,2-诊断结束,3取消
//预约
// 构造器
constructor(address _pID,address _dID,string _pNAME,string _dName,string _aTime) public{
PatientID = _pID;
DoctorID = _dID;
PatientName = _pNAME;
DoctorName = _dName;
ApTime = _aTime;
State = 0;
}
function getAppointmentDetailByD() public view returns(address,address,string,string,string,uint256){
return (PatientID,DoctorID,PatientName,DoctorName,ApTime,State);
}
function setStatus(uint256 _status) public {
State = _status;
}
}
mapping(address=>address[]) doctorByAppointmentOld;
这个是预约挂号旧数组 key是doctoraddress,意思是当诊断结束或者取消的时候,需要将其之前的Appointment转移到这个mapping ,方便以后医生查询历史信息
mapping(address=> address) patientAppoint;
这个映射是对应的 病人地址=》appointment挂号单,由于不想设计太复杂,一个病人只能在同一时间预约一个挂号
mapping(address=>address[]) patientAppointHistory;
这个也是预约挂号旧数组 ,但这个key是patientAddress ,这个列表是方便病人自己的挂号历史记录,正在预约或者就诊中的不算进入
方法就不讲解了,大部分方法有注释,可以自行阅读
授权共享病历相关功能
这个就是整个系统的核心部分,病人设置授权码,医生拿着授权码获得权限查看病人在这个系统的所有病历信息
// -----------------------------------授权共享病历-------------------------------------
//授权码mapping
mapping(address=>uint256) patientAccessMapping;
function updateAccessByPatient() public onlyPatient(msg.sender) returns(uint256){
uint256 access = uint256(keccak256(abi.encodePacked(now,msg.sender)));
patientAccessMapping[msg.sender] = access;
return access;
}
function cancelAccessByPatient() public onlyPatient(msg.sender) returns(bool){
patientAccessMapping[msg.sender] = 0;
return true;
}
function getAccessByPaticent() public view onlyPatient(msg.sender) returns(uint256){
require(patientAccessMapping[msg.sender] !=0,"你还没生成授权码,请去生成");
return patientAccessMapping[msg.sender];
}
function getAccessByDoctor(address patientA,uint256 accessNumber) public view onlyDoctor(msg.sender) onlyPatient(patientA) returns(address[]){
require(patientAccessMapping[patientA] == accessNumber,"授权码不对,请重试");
//逻辑待补全
return getEMRList(patientA);
}
通过patientAccessMapping 映射进行病人与授权码一一对应。
但是我需要提个建议,我使用的生成方法是 uint256(keccak256(abi.encodePacked(now,msg.sender)));
这样其实是不安全的做法,通常对于这种生成类似随机数的,我推荐是在链下使用应用层生成相应 的随机数,再进行数据加密将其上链,不然数据就在链上裸奔了。
而且我参考的那篇论文,是使用二维码方式进行授权的,更加方便,各位可以根据自己的实际情况考虑自己的实现方式。
病历相关功能
// -----------------------------------病历-------------------------------------
//病历存储
mapping(address=>address[]) patientByEMR;
function getEMRList(address patientA) private view returns(address[]){
return patientByEMR[patientA];
}
//特殊情况不适用view, 病人查看自己的病历
function getEMRListByPatient() public view onlyPatient(msg.sender) returns(address[]){
return patientByEMR[msg.sender];
}
function createEMR(address patientA,string _desc,string _drugDetail) public onlyPatient(patientA) onlyDoctor(msg.sender) returns(bool){
address patientB = RBAC.getPatientAddress2(patientA);
Patient patient = Patient(patientB);
address doctorA = RBAC.getDoctorAddress();
Doctor doctor = Doctor(doctorA);
EMR emr = new EMR(patientA,patient.patientName(),msg.sender,doctor.DoctorName(),_desc,_drugDetail);
patientByEMR[patientA].push(address(emr));
return true;
}
function getEMRDetail(address _emrA,address patientA,uint256 accessNumber) public view onlyPatient(patientA) onlyDoctor(msg.sender) returns(address,string,address,string,string,string){
require(patientAccessMapping[patientA] == accessNumber,"授权码不对,请重试");
EMR emr = EMR(_emrA);
return emr.getEMRDetailByE();
}
function getEMRDetailByPatient4(address _emrA) public view onlyPatient(msg.sender) returns(address,string,address,string,string,string){
EMR emr = EMR(_emrA);
return emr.getEMRDetailByE();
}
这块代码,就是医生就诊结束,给这次会诊填写病历信息EMR ,并且存储在patientByEMR的mapping映射上,方便病人查看信息
结语
如果是传统web开发的话,一个共享病历系统还是比较简单实现,但是将其系统用solidity 写合约来实现,就会难度倍增,因为其语言特性和区块链世界的底层存储,让我们需要考虑更多的事情,性能和存储资源必须得做到最优化。
这个共享病历系统已经讲解完毕,谢谢大家阅读。虽然有些功能还能有更加优化的地方,但是实在抽不出时间再编写了o(╥﹏╥)o。