区块链具有不可篡改性以及可追溯性,因此对于一些重要信息区块链更能够保障信息的安全。基于区块链的这两大特点,本篇将介绍如何将区块链应用于政府办公,实现协同办公,数据脱敏上链,以及数据溯源打破数据孤岛等功能。以小程序为载体,体现区块链在实际生活中的具体作用。总体设计分为四个模块。具体如表1.1所示,
表1.1 功能模块分析
区块链技术的去中心化、不可篡改、可信任、可溯源等特点,使得区块链技术不仅在数据安全领域有所作为,在政务服务系统中也可大展身手。通过对大量数据信息 的分析和快速处理,区块链应用开发技术可以迅速将有效信息传递至各部门,为扁平 化管理创造了条件。因此本项目有如下特点与优势。
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处理。由于哈希函数具有单向性,因此即使该哈希值被他人读取了也很难破解用户的密码。
-
用户注册日志记录:
前端界面主要分为用户注册页面和用户登录页面.这里主要讲一下注册页面.通过一个form表单让用户输入关键的信息。注册时候,能自动获取的信息就不让用户手动再输入一遍,增加用户的使用体验。自动获取用户的微信昵称作为用户的账户名,用户需要手工输入6-20位的密码,再通过复选框cu-form-group选择三类身份中的一种。最后点击提交即可完成注册。
//用户注册,输入需要的信息 {{registeredStatus?'登陆后获得发布和下载权限':'注册后获得发布和下载权限'}} 用户账号 输入密码 //用户身份选择可以用下拉框.分为三类个人、企业、政府 确认密码 选择用户身份 {{userTypeList[userType]}}
前端界面效果图如下:
-
注册界面:
-
登陆界面:
后端根据用户微信登录后产生唯一的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号查看留言信息
-
用户留言事件在区块链端的日志记录
-
用户违规发言的扣分记录
-
用户提交留言:用户在表单中输入留言标题和留言内容。通过placeholder给用户提供输入内容事例。防止用户不知道该如何留言。表单设置容纳最大大小为500字,防止内容过多。
建言标题 建言内容 -
用户留言内容展示:后端通过http请求将用户留言的标题、内容、留言时间、上传用户的Id、留言内容是否合规等信息展示到前端来。这里对用户留言合规用绿色表示,如果不合格用红色表示,起到醒目的作用。
建言标题 {{title}} 建言内容 {{content}} 留言时间 {{suggestTime}} 上传用户ID {{userId}} 内容是否合规 {{isCompliance}}
-
用户留言前端界面图:
-
用户查看自己的留言记录图:
-
用户浏览他人留言:
-
用户查看留言内容详情:
后端对用户留言的内容进行判断。如果标题为空或者内容为空,则返回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}} 该用户无打卡记录
后端调用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号查询文件状态
-
领导签字哈希上链:
成功回执信息:
-
审核员审批.校验文件没被篡改、且领导意见为通过、领导签名未被篡改后,提交最终审批结果如下:
交易回执:
-
科员提交文件日志信息:
-
领导审批日志信息:
-
审核结果日志信息:
前端以一个表单的形式让用户手工输入文件的名称和内容,另外选择文件(支持doc和pdf等格式)。选择完后点击上传即可。领导审核的时候,将下载下来该文件,并计算文件的哈希值与区块链上存储的文件哈希值作比对,一致则说明文件没发生篡改,才可以进行后面的步骤,否则将会报错。
文件名称 {{title}} 文件介绍 {{introduction}} 上传时间: {{uploadTime}} 资料提供者ID: {{userId}} 资料总下载次数: {{downloadNum}} 校验可保证你下载的文件并未被他人修改
-
科员提交文件界面图:
-
科员查看文件审核进度:
-
科长签字审核:
-
处长审核:
-
文件公示:
后端代码分为这几个功能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.dataTitle4.后记
- 该项目已获得2020-2021腾讯举办的高校微信小程序比赛华中赛区三等奖
- 所有相关代码已经开源。运行有任何问题可以提issue。如项目对您有帮助,欢迎star支持!github地址点击链接
- 本人关注前沿知识,热衷于开源。获得Fisco Bcos 2021年度贡献MVP
- 目前在准备找Golong后端开发/区块链开发相关实习,有一起的小伙伴可以滴滴.
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)