基于fisco-bcos的共享病例智能合约的设计与实现【5】

环境

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。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

已久依依

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值