FISCO-BCOS应用实战:区块链实战应用开发分享

FISCO-BCOS应用实战:区块链实战应用开发分享,第1张

FISCO-BCOS应用实战:区块链实战应用开发分享 政务通——区块链助力政府办公 1.项目简介

​ 区块链具有不可篡改性以及可追溯性,因此对于一些重要信息区块链更能够保障信息的安全。基于区块链的这两大特点,本篇将介绍如何将区块链应用于政府办公,实现协同办公,数据脱敏上链,以及数据溯源打破数据孤岛等功能。以小程序为载体,体现区块链在实际生活中的具体作用。总体设计分为四个模块。具体如表1.1所示,

​ 表1.1 功能模块分析

功能模块技术特点1. 用户管理模块注册时候对用户信息进行资产数字化处理,用户密码等关键信息脱敏上链。存储的是通过sha256运算后的哈希值,保障了用户的安全。用户登录时,输入密码进行一次哈希运算,与链上比对,即完成“确权”,验证一致才可登陆。2. 建言献策模块用户留言内容记录上链,同时对留言内容调用外部api,如果留言内容涉及敏感词,则扣除用户信用积分。打造一个好的社会信用生态3巡检模块用户打卡记录上链,涉及“数据溯源”4.政务合作模块体现联盟链的**“多方协作”**特点. 2 项目优势

区块链技术的去中心化、不可篡改、可信任、可溯源等特点,使得区块链技术不仅在数据安全领域有所作为,在政务服务系统中也可大展身手。通过对大量数据信息 的分析和快速处理,区块链应用开发技术可以迅速将有效信息传递至各部门,为扁平 化管理创造了条件。因此本项目有如下特点与优势。

1、区块链技术可在政府部门间构建起分布式对等网络,让政府组织结构的信息传 递更加直接高效,部门间可运用区块链技术直接进行点对点信息传递。

2、区块链分布式的模式特点可以实现多部门间的数据同享,可使得政务管理层级 减少,部门与部门间、上级与下级间的沟通会更顺畅,对于人员的需求也会相应减少。 政府部门可利用区块链技术打造高效的行政系统,推动政府治理和公共服务模式创新。

3、每个在区块链上获取数据的主体是平等关系,需要共同承担管理责任。数据的 变动和更改会同步在整个网络节点上更新,这种变动需要每个参与者确认,即使部分 数据库系统出现失灵或错误,其他节点数据依然完整,数据库系统依旧可以正常有效 运转。

4、公共服务部门利用区块链技术可以降低成本、保证数据安全、增加信任、透明 度和可靠性。区块链的特性使得数据可以追根溯源,数据安全性提升且不能随意变动, 有助于建立权威数据库,进而建立更安全、开放、包容高效的公共服务平台。

3.系统实现

​ 区块链部分基于FISCO BCOS 开发。FISCO BCOS 是由国内企业主导研发、对外开源、安全可控的企业级金融联盟链底层平台。另外通过微信小程序作为媒介,客户端由小程序和后台管理网站通过https 请求,经过 nginx 进行负载均衡。后台采用 django,将用户的 access_token等存储在redis 缓存服务器中,进行定时刷新。 前端采用小程序的原生框架,采用 WXML + WXSS + JS 进行原生开发与布局。

3.1用户管理模块

​ 该模块包含用户注册登陆以及管理员对用户信用积分的管理。登录功能是可确权登录的 *** 作手段,以此实现用户的 *** 作安全性,确保用户的账 号数据安全为用户本人 *** 作。用户在正确登录小程序后,后台会获取登录用户的身份, 根据身份给予该用户不同的权限进行 *** 作。

3.1.1 合约代码

**1.功能说明:**本合约实现功能主要为:1.用户注册2.用户登录3.查看用户信息

  • activateUser(string memory _userid,string memory _username,string memory _userpassword, string memory _usertype):用户实现注册,传入用户的ID号、名字、密码,用户身份类别。
  • Login(string memory _userid,string memory _userpassword)用户的ID、用户的密码
  • getUserRecordArray(string userid)用户的ID
pragma solidity ^0.4.25;
pragma experimental ABIEncoderV2;
import "../lib/SafeMath.sol";
import "../utils/TimeUtil.sol";
import "../utils/StringUtil.sol";
import "../utils/TypeConvertUtil.sol";
import "./TableDefTools.sol";
contract UserControl is TableDefTools{
    
     using TimeUtil for *;
     using SafeMath for *;
     using TypeConvertUtil for *;
     using StringUtil for *;
   
    constructor() public{
        //初始化需要用到的表。用户信息表
        initTableStruct(t_user_struct, TABLE_USER_NAME, TABLE_USER_PRIMARYKEY, TABLE_USER_FIELDS);
    }
    // 事件
    event REGISTER_USER_EVENT(string userid,string usertype,string activatetime); //注册用户事件.记录注册人身份,类型,注册时间
    event DEL_CREDITPOINT_EVENT(string user_id,string grade,string time); //扣分时间,扣分人id、扣分数、扣分时间
    event ADD_CREDITPOINT_EVENT(string user_id,string grade,string time);
   
    function activateUser(string memory _userid,string memory _username,string memory _userpassword, string memory _usertype) public  returns(int8){
                 // 获得当前的时间
        string memory _passwordhash=TypeConvertUtil.bytes32ToString(sha256(abi.encode(_userid,_userpassword)));
        string memory nowDate = TimeUtil.getNowDate();
        string memory firstFiveParams=StringUtil.strConcat7(_username,',',_passwordhash,',',nowDate,',',_usertype);
        string memory lastTwoParams = "1,100";
        string memory storeFields = StringUtil.strConcat3(firstFiveParams,',',lastTwoParams);
        emit REGISTER_USER_EVENT(StringUtil.strConcat2("注册人的ID为:",_userid),StringUtil.strConcat2("注册人身份为:",_usertype),StringUtil.strConcat2("注册时间为:",nowDate)); 
        return insertOneRecord(t_user_struct,_userid,storeFields,false);//最后的false代表主键下记录不可重复
    }
    
    function Login(string memory _userid,string memory _userpassword) public view returns (int8,string) {
         string memory _passwordhash=TypeConvertUtil.bytes32ToString(sha256(abi.encode(_userid,_userpassword)));
         return loginInToJson(t_user_struct,_userid,_passwordhash);
    }
    
    function getUserRecordArray(string userid) public view returns(int8, string[]){
        return selectoneRecordToArray(t_user_struct, userid, ["user_id",userid]);
    }
}

2使用实例:

  • 用户注册:

通过调用activateUser传入用户的ID、姓名、密码、身份类别完成注册。

  • 查询用户信息:

    ​ 因为区块链上的信息是公开透明的。因此不应当把密码等隐私数据直接上链。而应当对隐私数据进行**“脱敏上链”**,在这里我们对用户的密码进行了hash处理。由于哈希函数具有单向性,因此即使该哈希值被他人读取了也很难破解用户的密码。

  • 用户注册日志记录:

3.1.2 前端代码

​ 前端界面主要分为用户注册页面和用户登录页面.这里主要讲一下注册页面.通过一个form表单让用户输入关键的信息。注册时候,能自动获取的信息就不让用户手动再输入一遍,增加用户的使用体验。自动获取用户的微信昵称作为用户的账户名,用户需要手工输入6-20位的密码,再通过复选框cu-form-group选择三类身份中的一种。最后点击提交即可完成注册。

    
      
      
        
          //用户注册,输入需要的信息
          {{registeredStatus?'登陆后获得发布和下载权限':'注册后获得发布和下载权限'}}
          
            用户账号
            
          
          
            输入密码
            
          
          
            确认密码
            
          
					//用户身份选择可以用下拉框.分为三类个人、企业、政府
          
          选择用户身份
            
              
                {{userTypeList[userType]}}
              
            
          
          
          
        
      
    

前端界面效果图如下:

  • 注册界面:

  • 登陆界面:

3.1.3 后端代码

​ 后端根据用户微信登录后产生唯一的openId自动作为用户的Id,作为用户注册的唯一主键。通过该主键获取用户的微信号、微信昵称、密码哈希、微信头像、注册状态、信用积分、用户身份类别、下载文件记录等信息。

class User(models.Model):
    #自动生成openId作为用户的唯一ID
    openId = models.CharField(db_index=True, max_length=100, default='0', null=True)  # 唯一表示微信用户的id
    userWxName = models.CharField(verbose_name='用户微信名', max_length=40, null=True)  # 用户微信名
    userLoginName = models.CharField(verbose_name='用户登录名', max_length=10, null=True)
    userPassword = models.CharField(verbose_name='密码通过sha256计算后的hash值', max_length=64, null=True)
    avatarUrl = models.URLField(max_length=255, null=True)  # 用户头像
    activateTime = models.DateTimeField(verbose_name='注册时间', null=True)
    registeredStatus = models.BooleanField(verbose_name='注册状态', default=False)
    userCreditPoint = models.IntegerField(verbose_name='信用积分', default=100)#给定默认信用积分为100分
    userTypeChoices = [(0, '个人'), (1, '企业'), (2, '政府机构')] #通过用户在前端界面选择的序号确定身份类别
    userType = models.PositiveSmallIntegerField(verbose_name='用户类型', choices=userTypeChoices, null=True, blank=True)
    downloadRecord = models.ManyToManyField("OfficeFile", blank=True, through='DownloadRecord',
                                            through_fields=('user', 'officeFile'), related_name='userDownloadRecord')
    #获取用户信息
    def getUserInfo(self,request):
    obj = json.loads(request.body)
    user = get_user(obj)
    if not user:
        return HttpResponse('false')  # token过期
    else:
        if obj['funType'] == 0:
            user.userWxName = obj['name']
            user.avatarUrl = obj['avatarUrl']
            user.save()
        userTypeList = []
        choices = User.userTypeChoices
        for c in choices:
            userTypeList.append(c[1])
        data = {'userType': user.userType, 'userTypeList': userTypeList}
        if obj['funType'] == 1:
            data['avatarUrl'] = user.avatarUrl
            data['nickName'] = user.userWxName
        return JsonResponse(data, safe=False)
3.2 建言献策模块

​ 每位用户可在建言献策界面的文本框内输入建言标题和建言内容,确保内容无误后点击“提交建议”,建言数据将上传到本地数据库和区块链上,后端调用 API 对用户所发布的内容进行违规词检测替换,若内容有不文明用词将扣除用户 1 点信用值,且将建言标题内容和信用扣除记录上传到区块链上(用户违规事件记录上链)。每位用户可在小程序首页浏览所有用户提交的建议,且每条 建议会显示建议提交者 ID、提交时间以及是否违规,同时也可在个人中心的信用值记 录查看自己提交的建言的详细记录

3.2.1 合约代码

**1.功能说明:**本合约实现功能主要为:1.用户留言 2.查看用户留言记录

  • suggest(string memory _proposeid,string memory _userid,string memory _title,string memory _content):用户留言:传入留言内容Id、用户的ID号、留言标题、留言内容。
  • getSuggestRecordJson(string _proposeid):返回留言内容:传入留言内容ID号,以json形式返回留言内容
pragma solidity ^0.4.25;
pragma experimental ABIEncoderV2;
import "../lib/SafeMath.sol";
import "../utils/TimeUtil.sol";
import "./TableDefTools.sol";

contract Suggest is TableDefTools{
    
     using TimeUtil for *;
     using SafeMath for *;
     event SUGGEST(string Proposeid,string  Userid,string _title,string SuggestContent,string time);//留言事件.留言人ID,留言标题,内容。时间
   
    constructor() public{
        //初始化需要用到的表。建言献策表
        initTableStruct(t_propose_struct, TABLE_PROPOSE_NAME, TABLE_PROPOSE_PRIMARYKEY, TABLE_PROPOSE_FIELDS);
    }
   
   function suggest(string memory _proposeid,string memory _userid,string memory _title,string memory _content) public  returns(int8){
        //获取时间
        string memory nowDate = TimeUtil.getNowDate();
        string memory suggestfields=StringUtil.strConcat4("建议标题为:",_title,"建议内容为:",_content);
        string memory storeFields = StringUtil.strConcat5(_userid,',',suggestfields,',',nowDate);
        emit SUGGEST(StringUtil.strConcat2("留言的ID号为:",_proposeid),StringUtil.strConcat2("留言人的ID为:",_userid),
        StringUtil.strConcat2("留言标题为:",_title),StringUtil.strConcat2("留言内容为:",_content),StringUtil.strConcat2("留言时间为:",nowDate)); 
        return (insertOneRecord(t_propose_struct,_proposeid,storeFields,false));
    }
    
    function getSuggestRecordJson(string _proposeid) public view returns(int8, string){
        return selectoneRecordToJson(t_propose_struct, _proposeid);
    }

2使用实例:

  • 用户留言:

  • 用户留言成功的回执信息

  • 通过留言内容的Id号查看留言信息

  • 用户留言事件在区块链端的日志记录

  • 用户违规发言的扣分记录

3.2.2 前端代码
  1. 用户提交留言:用户在表单中输入留言标题和留言内容。通过placeholder给用户提供输入内容事例。防止用户不知道该如何留言。表单设置容纳最大大小为500字,防止内容过多。

    
      	
        建言标题
        
      
      
        建言内容
        	
        
      
      
        
        
        
      
    
  2. 用户留言内容展示:后端通过http请求将用户留言的标题、内容、留言时间、上传用户的Id、留言内容是否合规等信息展示到前端来。这里对用户留言合规用绿色表示,如果不合格用红色表示,起到醒目的作用。


  
    
    建言标题
  

{{title}}

  
    
    建言内容
  

{{content}}

  留言时间
  {{suggestTime}}


  上传用户ID
  {{userId}}


  内容是否合规
  {{isCompliance}}

  • 用户留言前端界面图:

  • 用户查看自己的留言记录图:

  • 用户浏览他人留言:

  • 用户查看留言内容详情:

3.2.3 后端代码

后端对用户留言的内容进行判断。如果标题为空或者内容为空,则返回error给用户,提醒用户输入为空。对用户留言的内容与从github搜集的违规词语料库进行匹配。若用户的留言内容违规,那么将调用api对用户的信用积分进行扣分处理。

def addSuggest(request):
    obj = json.loads(request.body)
    user = get_user(obj)
    if not user:
        return HttpResponse('false')  # token过期
    else:
        suggest = Suggest(user_id=user.id, suggestTitle=obj['suggestTitle'], suggestContent=obj['suggestContent'])
        suggest.save()
        addSuggestToChain(suggest.id, user.userLoginName, suggest.suggestTitle, suggest.suggestContent)
        content = getSuggestFromChain(suggest.id)[1]
        suggestFromChain = content[1]
        titleStartIndex = re.search('建议标题为:', suggestFromChain).span()[1]
        contentStartIndex = re.search('建议内容为:', suggestFromChain).span()[1]
        data1 = checkContent(suggestFromChain[titleStartIndex:contentStartIndex - 6])
        data2 = checkContent(suggestFromChain[contentStartIndex:])
        suggest.changeSuggestTitle = data1['text']
        suggest.changeSuggestContent = data2['text']
        if data1['num'] > 0 or data2['num'] > 0:
            suggest.isCompliance = False
            user.userCreditPoint = user.userCreditPoint - 1
            user.save()
        else:
            suggest.isCompliance = True
        suggest.save()
        if suggest.isCompliance is False:
            suggesterId = getSuggesterId(suggest.id)
            updateUserCredit(suggesterId, 1, 0)
    return HttpResponse("success")
  #判断用户的留言内容是否违规,从github上搜集违规词存入keywords.txt文件中,如果用户留言内容在违规词内则判定为违规
  def checkContent(content):
    gfw = DFAFilter()
    gfw.parse("keywords.txt")
    number = len(content.split("*"))-1
    text = gfw.filter(content, "*")
    data = {"num": len(text.split("*"))-1-number, "text": text}
    return data
  #获取用户留言细息,展示到前端页面
  def returnSuggestList(request):
    obj = json.loads(request.body)
    pageSize = 10
    currentPage = obj['currentPage']
    startRow = (currentPage - 1) * pageSize
    endRow = currentPage * pageSize
    suggests = Suggest.objects.all().order_by('-id')[startRow:endRow]
    suggestList = []
    #遍历所有的留言记录,取出留言的Id号、留言标题、留言内容、是否违规、留言时间
    for s in suggests:
        obj = {"id": s.id, "userId": s.user.userLoginName, 'title': s.changeSuggestTitle,
               "content": s.changeSuggestContent,
               "isCompliance": s.isCompliance, "suggestTime": s.suggestTime.strftime("%Y.%m.%d")}
        suggestList.append(obj)
    return JsonResponse({"suggestList": suggestList})
  
3.3 巡检模块

​ 该模块模拟了在实际生活中,政府部门经常会有一些任务,要求在什么时候去哪些 地方巡查,也就涉及到用户打卡,我们将用户打卡记录上链,避免了代打卡,甚至篡改数据库等问题。同时领导可以通过下属的用户 ID查看他的打卡记录信息。

3.3.1 合约代码

**1.功能说明:**本合约实现功能主要为:1.用户打卡2.查看用户打卡信息记录

  • ClockIn(string memory _userid,string memory _location,string memory _time)实现用户打卡功能:传入用户Id号,打卡地点,打卡时间

  • getUserClockInfo(string _userid)查看用户打卡记录;传入用户Id

    pragma solidity ^0.4.25;
    import "../utils/TimeUtil.sol";
    import "../utils/StringUtil.sol";
    import "./TableDefTools.sol";
    pragma experimental ABIEncoderV2;
    contract Track is TableDefTools{
        
        using TimeUtil for *;
        using StringUtil for *;
    
        
        constructor() public{
            //初始化需要用到的表。数据资源表
            initTableStruct(t_track_struct, TABLE_TRACK_NAME, TABLE_TRACK_PRIMARYKEY, TABLE_TRACK_FIELDS);
        }
          //定义事件日志信息
       event TRACK_EVENT(string user_id,string user_location,string time);//用户打卡记录日志。谁在哪里什么时间打的卡
        
        function ClockIn(string memory _userid,string memory _location,string memory _time) public returns(int8){
            string memory storeFields=StringUtil.strConcat3(_location,',',_time);
            emit TRACK_EVENT(StringUtil.strConcat2("用户ID:",_userid),StringUtil.strConcat2("打卡地点:",_location),StringUtil.strConcat2("到达时间:",_time));
            return insertOneRecord(t_track_struct,_userid,storeFields,true);
        }
        
        function getUserClockInfo(string _userid) public view returns(int8, string){
            return selectoneRecordToJson(t_track_struct,_userid);
        }
    }
    

    2 使用实例:

    • 用户打卡:小程序自动获取用户的Id唯一主键、调用wx.getLocation(Object object)获取用户

    的打卡地点.

    用户成功打卡完成后返回信息如下:

    • 通过输入用户的Id以json的形式返回用户的打卡信息:

      查询输入:

    查询成功返回如下图所示,查询失败返回错误码

用户打卡信息日志记录:

3.3.2 前端代码

​ 用户打卡界面以两个按钮为主。点击授权获取地址按钮可以调用微信的获取用户地址的接口,得到当前所在位置的经纬度,查看无误后,点击打卡即可。


  
  
    
      当前地址

      {{localText}}

      

      
    
  

  • 用户打卡的前端界面图如下:

  • 查询用户打卡信息:

    查询用户打卡页面在页面上方设置了一个搜索框,用户可以输入查询的用户Id账号,点击搜索后调用合约接口,返回用户的打卡记录,即用户的打卡地点和打卡时间。

    
    		
    		搜索
    
    
      
        
        用户打卡记录
      
    
    
      
        
          {{item.fields.user_location}}
        
        
          
            {{item.fields.arrival_time}}
          
        
      
    
    
        
        该用户无打卡记录
        
    
    
3.3.3 后端代码

​ 后端调用wx.getLocation获取用户当前定位的经纬度.将该信息以及用户的Id通过合约api接口调用,存入区块链上。若用户未授权获取定位点击了打卡将会通过wx.showModeld窗的形式给用户提醒未能获得您的定位,请点击“授权获得地址”按钮以获得定位

  //调用wx.getLocation获取用户当前定位的经纬度
  showLocal: function () {
    var that = this;
    wx.getLocation({
      type: 'gcj02',
      success: function (res) {
        let latitude = res.latitude//经度
        let longitude = res.longitude//纬度
        that.setData({
          latitude,
          longitude
        })
        that.getMapCity(latitude, longitude)
      }
    })
  },
      postAddressOnChain: function(){ 
    var that = this
    if(!that.data.canClockIn){
      wx.showModal({
        title: '温馨提示',
        content: '未能获得您的定位,请点击“授权获得地址”按钮以获得定位',
        showCancel: false,
        success (res) {
          if (res./confirm/i) {
            console.log('用户点击确定')
          } else if (res.cancel) {
            console.log('用户点击取消')
          }
        }
      })
      return
    } else {
      const app = getApp()
      //获取时间
      var date = new Date()
      var year = date.getFullYear() + '年'
      var month = date.getMonth() + 1 + '月'
      var day = date.getDate() + '日'
      var hour = date.getHours() + '点'
      var minute = date.getMinutes() + '分'
      var second = date.getSeconds() + '秒'
      var dateText = year + month + day + hour + minute + second
      //将信息上链
      var objToChain = {
        "groupId" :5,
        "signUserId": "fee97843cf0c45d683bade8fdebe724f",
        "contractAbi":[{"constant":true,"inputs":[{"name":"_userid","type":"string"}],"name":"getUserCityArray","outputs":[{"name":"","type":"int8"},{"name":"","type":"string[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_userid","type":"string"}],"name":"getUserCityJson","outputs":[{"name":"","type":"int8"},{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_fields","type":"string[]"},{"name":"index","type":"uint256"},{"name":"values","type":"string"}],"name":"getChangeFieldsString","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_userid","type":"string"},{"name":"_location","type":"string"},{"name":"_time","type":"string"}],"name":"ClockIn","outputs":[{"name":"","type":"int8"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"user_id","type":"string"},{"indexed":false,"name":"user_location","type":"string"},{"indexed":false,"name":"time","type":"string"}],"name":"TRACK_EVENT","type":"event"}],
        "contractAddress":"0x8546174c5fe38243e1dcfb65e1347919fe0f45ba",
         "funcName":"ClockIn",
        "funcParam":[app.globalData.idNumber,that.data.localText,dateText],
        "useCns":false
      }
      //通过http请求与链上数据进行交互
      http.httpToChain(objToChain, function (res) {
        if(res.message == 'success' || res.message == 'Success'){
          wx.showToast({
            title: '打卡成功',
            icon: 'success',
            duration: 2000
          })
        }
      })
    }
  },
3.4 政务合作模块

​ 政府要发布某一条消息往往不是某一个人决定的,而是多级领导审核后同意才会通过,在文件传输过程中,如何保障数据不被篡改,区块链就可以起到很大的作用。但是区块链存储数据对资源消耗特别大,因此我们决定对数据**“轻装”上链**。 政府公文文件的 pdf 放链下本地数据库,文件的哈希值上链。需要验证的时候将 pdf 再做一次哈希运算与链上对比,一致则可以保证文件的未被篡改。

3.4.1 合约代码

**1.功能说明:**本合约实现功能主要为:1.科员提交材料2.查询申请材料当前状态 并以Json字符串方式输出3.领导签字图片信息,审核结果信息上链。4.将审核结果决定是否公示

  • applyFordocument(string memory _applicationid,string memory _userid,string memory _informationhash):传入文件的Id号、申请人Id、文件的哈希值
  • getApplyJson(string memory _applicationid):传入文件的Id号,以Json形式返回文件信息
  • checkApplyResearch(string memory _applicationid,string memory checkhash):文件Id号、领导签字图片哈希值
  • GiveResultToUser(string _checkerid,string memory _applicationid,string memory checkresult):审核人Id号、材料Id、审核结果
pragma solidity ^0.4.25;
pragma experimental ABIEncoderV2;
import "../utils/TimeUtil.sol";
import "./TableDefTools.sol";


contract GovCooperation is TableDefTools{
    
     using TimeUtil for *;
   
    constructor() public{
        //初始化需要用到的表。文件材料申请审核
        //公示政府文件表
        initTableStruct(t_application_struct, TABLE_APPLICATION_NAME, TABLE_APPLICATION_PRIMARYKEY, TABLE_APPLICATION_FIELDS);
    }
    //
    event APPLY_document_EVENT(string userid,string application_id,string date);
    event Track_LeaderSignature(string applicationid,string signHash,string date);//记录领导签字
    event Tack_Final_Result(string applicationid,string checkerid,string result,string date);//记录最后校验部结果
   
    function applyFordocument(string memory _applicationid,string memory _userid,string memory _informationhash) public returns(int8){
         // 获得当前的日期
        string memory nowDate = TimeUtil.getNowDate();
        string memory firstTwoFields=StringUtil.strConcat5(_userid,',',_informationhash,',',nowDate);
        string memory lastFourParams = ",审核中,审核中,审核中";
        string memory storeFields = StringUtil.strConcat2(firstTwoFields,lastFourParams);
        emit APPLY_document_EVENT(StringUtil.strConcat2("申请人的ID号为:",_userid),StringUtil.strConcat2("材料的ID为:",_applicationid),StringUtil.strConcat2("申请时间为:",nowDate)); 
        return insertOneRecord(t_application_struct,_applicationid,storeFields,false);//最后的false代表主键下记录不可重复
    }
    
      function getApplyJson(string memory _applicationid) public view returns(int8, string){
        return selectoneRecordToJson(t_application_struct,_applicationid);
    }
    
    function checkApplyResearch(string memory _applicationid,string memory checkhash) public returns(int8){
        // 获得当前的日期
        string memory nowDate = TimeUtil.getNowDate();
          //查询用户申请信息返回状态
        int8 queryRetCode; 
        //更新用户申请保送表后返回状态
        int8 updateRetCode;
        // 数据表返回信息
        string[] memory retArray;
         // 查看该用户申请审核信息
        (queryRetCode, retArray) = selectoneRecordToArray(t_application_struct, _applicationid, ["application_id", _applicationid]);
          // 若存在该用户记录
        if(queryRetCode == SUCCESS_RETURN){
             string memory changedFieldsStr = getChangeFieldsString(retArray,3,checkhash);
             updateRetCode = (updateOneRecord(t_application_struct,_applicationid,changedFieldsStr));
            if(updateRetCode == SUCCESS_RETURN){
                //记录日志
              emit Track_LeaderSignature(StringUtil.strConcat2("材料Id为:",_applicationid),
              StringUtil.strConcat2("领导签字图片的哈希为:",checkhash),StringUtil.strConcat2("申请时间为:",nowDate)); 
               return SUCCESS_RETURN;
            }
            else{
                return FAIL_RETURN;
            }
        }else{
            return FAIL_RETURN;
        }
    }
    
    function GiveResultToUser(string _checkerid,string memory _applicationid,string memory checkresult) public returns(int8){
        //查询文件信息返回状态
        int8 queryRetCode; 
        //更新文件后返回状态
        int8 updateRetCode;
        // 数据表返回信息
        string[] memory retArray;
        // 获得当前的日期
        string memory nowDate = TimeUtil.getNowDate();
        // 查看该科员申请审核信息
        (queryRetCode, retArray) = selectoneRecordToArray(t_application_struct, _applicationid, ["application_id", _applicationid]);
         // 若存在该用户记录
        if(queryRetCode == SUCCESS_RETURN){
            //修改科员申请表中的审核时间
             string memory changedFieldsStr = getChangeFieldsString(retArray, 5, nowDate);
            //修改
            updateRetCode = (updateOneRecord(t_application_struct, _applicationid,changedFieldsStr));
            if(updateRetCode == SUCCESS_RETURN){
            string memory changedFieldsStr2 = getChangeFieldsString(retArray, 4, checkresult);
            emit Tack_Final_Result(StringUtil.strConcat2("审核人Id为:",_checkerid),StringUtil.strConcat2("审核材料id为:",_applicationid),
            StringUtil.strConcat2("审核结果为:",checkresult),StringUtil.strConcat2("审核完成时间为:",nowDate)); 
            return (updateOneRecord(t_application_struct,_applicationid,changedFieldsStr2)); 
            }
        }
        else{
            // 若不存在该科员提交记录
            return FAIL_RETURN;
        }
    }
}

2 使用实例:

  • 科员上传文件

  • 输入文件id号查询文件状态

  • 领导签字哈希上链:

成功回执信息:

  • 审核员审批.校验文件没被篡改、且领导意见为通过、领导签名未被篡改后,提交最终审批结果如下:

    交易回执:

  • 科员提交文件日志信息:

  • 领导审批日志信息:

  • 审核结果日志信息:

3.4.2 前端代码

​ 前端以一个表单的形式让用户手工输入文件的名称和内容,另外选择文件(支持doc和pdf等格式)。选择完后点击上传即可。领导审核的时候,将下载下来该文件,并计算文件的哈希值与区块链上存储的文件哈希值作比对,一致则说明文件没发生篡改,才可以进行后面的步骤,否则将会报错。



  
    
    文件名称
  


  
    {{title}}
  


  
    
    文件介绍
  


  
    {{introduction}}
  


  
    
    上传时间:{{uploadTime}}
  


  
    
    资料提供者ID:{{userId}}
  


  
    
    资料总下载次数:{{downloadNum}}
  


校验可保证你下载的文件并未被他人修改


  

  • 科员提交文件界面图:

  • 科员查看文件审核进度:

  • 科长签字审核:

  • 处长审核:

  • 文件公示:

3.4.3 后端代码

​ 后端代码分为这几个功能1.查看文件的状态;2.将领导审核的意见存储上链3.将领导审核的签名哈希上链;通过这些功能来保障信息是没有被篡改过的,做到协同办公,最后将文件审核结果公示给用户。

# 查看文件当前状态
def returnApplyStatus(request):
    obj = json.loads(request.body)
    user = get_user(obj)
    if not user:
        return HttpResponse('false')  # token过期
    else:
      	#通过文件的id查询文件的当前状态
        a = OfficeFile.objects.get(id=obj['fileId'])
        data = {'uploadTime': a.uploadTime.strftime("%Y.%m.%d"),
                "informationlink": a.informationlink, "informationHash": a.informationHash,
                "boss1Opinion": a.boss1Opinion, "boss1Signlink": a.boss1Signlink, "boss1SignHash": a.boss1SignHash,
                "boss2Opinion": a.boss2Opinion, "boss2Signlink": a.boss2Signlink, "boss2SignHash": a.boss2SignHash,
                "checkHash": a.checkHash, "researchResult": a.researchResult, 'reviewResult': a.reviewResult
                }
        #以json形式返回结果
        return JsonResponse(data)

#领导审核
def postApplicationListToAdminToSign(request):  # 领导签字列表推送
    obj = json.loads(request.body)
    user = get_user(obj)
    if not user:
        return HttpResponse('false')  # token过期
    else:
        whichBoss = obj['identity']
        departmentId = obj['departmentId']
        pageSize = 10
        currentPage = obj['currentPage']
        startRow = (currentPage - 1) * pageSize
        endRow = currentPage * pageSize
        if whichBoss == 1:
            officeFiles = OfficeFile.objects.filter(workingStatus=False, boss1Opinion=None,
                                                    officeMember__department__id=departmentId).order_by('-id')[
                          startRow:endRow]
        else:
            officeFiles = OfficeFile.objects.filter(workingStatus=False, boss2Opinion=None,
                                                    officeMember__department__id=departmentId).exclude(
                boss1Opinion=None).order_by('-id')[startRow:endRow]
        officeFileList = []
        for o in officeFiles:
            data = {"fileId": o.id, 'uploadTime': o.uploadTime.strftime("%Y.%m.%d"),
                    "informationlink": o.informationlink, "informationHash": o.informationHash, 'title': o.dataTitle,
                    "introduction": o.dataIntroduction, 'userId': o.officeMember.user.userLoginName}
            officeFileList.append(data)
        return JsonResponse({"officeFileList": officeFileList})
#将领导签字图片的哈希值上链
def bossSign(request):
    obj = json.loads(request.body)
    user = get_user(obj)
    if not user:
        return HttpResponse('false')  # token过期
    else:
        whichBoss = obj['whichBoss']
        applicationForm = OfficeFile.objects.get(id=obj['id'])
        if whichBoss == 1:
            applicationForm.boss1Opinion = obj['boss1Opinion']
            applicationForm.boss1Signlink = obj['boss1Signlink']
            applicationForm.boss1SignHash = obj['boss1SignHash']
            if obj['boss1Opinion'] is False:
                applicationForm.researchResult = False
                applicationForm.workingStatus = False
        else:
            applicationForm.boss2Opinion = obj['boss2Opinion']
            applicationForm.boss2Signlink = obj['boss2Signlink']
            applicationForm.boss2SignHash = obj['boss2SignHash']
            if obj['boss2Opinion'] is False:
                applicationForm.researchResult = False
                applicationForm.workingStatus = False
        if applicationForm.boss1Opinion is not None and applicationForm.boss2Opinion is not None:
            s = hashlib.sha256()  # Get the hash algorithm.
            s.update((applicationForm.boss1SignHash + applicationForm.boss2SignHash).encode("utf8"))  # Hash the data.
            applicationForm.checkHash = s.hexdigest()  # Get he hash value.
            bossSignOnChain(obj['id'], s.hexdigest())
        applicationForm.save()
        return HttpResponse("success")
class OfficeFile(models.Model):
  	#
    dataTitle = models.CharField('文件标题', max_length=50)
    dataIntroduction = models.CharField('文件简介', max_length=255)
    informationlink = models.CharField('文件链接', max_length=255)
    informationHash = models.CharField('文件哈希值', max_length=64)
    uploadTime = models.DateTimeField(verbose_name='上传时间', auto_now_add=True)
    downloadsNum = models.IntegerField('下载次数', default=0)
    boss1Signlink = models.CharField('签字图片1下载链接', max_length=255, null=True)
    boss1SignHash = models.CharField('签字图片1哈希值', max_length=64, null=True)
    boss1Opinion = models.BooleanField('领导1意见', null=True)
    boss2Signlink = models.CharField('签字图片2下载链接', max_length=255, null=True)
    boss2SignHash = models.CharField('签字图片2哈希值', max_length=64, null=True)
    boss2Opinion = models.BooleanField('领导2意见', null=True)
    checkHash = models.CharField(verbose_name='审核结果哈希值', null=True, max_length=64)
    reviewResult = models.BooleanField(verbose_name='审核结果', null=True)
    researchResult = models.BooleanField(verbose_name='最终结果', null=True)  # 最终意见opinion
    workingStatus = models.BooleanField(verbose_name='工作已经完成', default=False, null=True)
    class meta:
        verbose_name_plural = "办公流程"
    def __str__(self):
        return self.dataTitle
4.后记
  • 该项目已获得2020-2021腾讯举办的高校微信小程序比赛华中赛区三等奖
  • 所有相关代码已经开源。运行有任何问题可以提issue。如项目对您有帮助,欢迎star支持!github地址点击链接
  • 本人关注前沿知识,热衷于开源。获得Fisco Bcos 2021年度贡献MVP
  • 目前在准备找Golong后端开发/区块链开发相关实习,有一起的小伙伴可以滴滴.

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5680876.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存