“诊疗信息的分布式记录系统”版本间的差异
第31行: | 第31行: | ||
===ChainCode代码=== | ===ChainCode代码=== | ||
− | {{{ | + | {{{{ |
package main | package main | ||
第516行: | 第516行: | ||
} | } | ||
− | }}} | + | }}}} |
2018年7月13日 (五) 16:56的版本
项目背景
- 数据安全,隐私难以保证
- “信息孤岛”现象严重
- 中心化数据库价格高昂
项目思路
1.写入区块的信息
- 患者公钥
- 患者信息(不可识别)
- 就诊时间
- 诊疗结果
- 医院签名
- 患者签名
2.实现功能
- 新建节点(医疗机构、患者)
- 写入信息(医疗机构)
- 查询信息(患者)
- 查询信息(医疗机构)
ChainCode实现接口
增
- insertPatient
改
- updatePatientState
查
- queryPatientByKey
- queryRecordByDisease
- queryPatientHistory
- queryPatientByRange
ChainCode代码
{{{{ package main
import ( "fmt" "strings" "encoding/json" "bytes" "time" "strconv"
"github.com/hyperledger/fabric/core/chaincode/shim" pb "github.com/hyperledger/fabric/protos/peer" )
type SimpleChaincode struct { }
type record struct { ObjectType string `json:"docType"` RecordKey string `json:"rkey"` PatientKey string `json:"pkey"` PatientSex string `json:"psex"` PatientAge int `json:"page"` DateInfo time.Time `json:"date"` PatientDisease string `json:"info"` }
type jsontime time.Time
const timeFormat = "2006-01-02 15:04:05"
func main() { err := shim.Start(new(SimpleChaincode)) if err != nil { fmt.Printf("Error starting Simple chaincode: %s", err) } }
func (recordTime jsontime) MarshalJSON() ([]byte, error) { var stamp = fmt.Sprintf("\"%s\"", time.Time(recordTime).Format(timeFormat)) return []byte(stamp), nil }
func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response { return shim.Success(nil) }
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response { function, args := stub.GetFunctionAndParameters() fmt.Println("Invoke is running " + function) if function == "insertRecord" { return t.insertPatient(stub, args) } else if function == "queryPatientByKey" { return t.queryPatientByKey(stub, args) } else if function == "queryPatientByAge" { return t.queryRecordByAge(stub, args) } else if function == "queryRecordByKey" { return t.queryRecordByKey(stub, args) } else if function == "queryRecordByDisease" { return t.queryRecordByDisease(stub, args) } else if function == "queryRecordBySex" { return t.queryRecordBySex(stub, args) } else if function == "queryPatientHistory" { return t.queryPatientHistory(stub, args) } else if function == "updatePatientState" { return t.updatePatientState(stub, args) } else if function == "queryPatientByRange" { return t.queryPatientByRange(stub, args) } fmt.Println("Invoke did not find func: " + function) //not supported func return shim.Error("Received unknown function invocation") }
func (t *SimpleChaincode) insertPatient(stub shim.ChaincodeStubInterface, args []string) pb.Response { var err error // 0 1 2 3 4 5 // "0123","0000","male","35","2006-01-02 15:04:05","Headache" if len(args) != 6 { return shim.Error("Insert: incorrect number of arguments. Expecting 4") }
// === Insert Running === fmt.Println("Insert- start insert patient") for i := 0; i < 6; i++ { if len(args[i]) <= 0 { return shim.Error("Insert: " + strconv.Itoa(i) + "th argument must be a non-empty string/integer/time") } } recordKey := args[0] patientKey := args[1] patientSex := strings.ToLower(args[2]) patientAge, err := strconv.Atoi(args[3]) if err != nil { return shim.Error("Insert: 3rd argument must be a numeric integer") } dateInfo, err := time.Parse(timeFormat, args[4]) if err != nil { return shim.Error("Insert: 4rd argument must be in time format") } patientDisease := strings.ToLower(args[5])
// === Check if patient exists === patientAsBytes, err := stub.GetState(patientKey) if err != nil { return shim.Error("Insert: failed to get patient. " + err.Error()) } else if patientAsBytes != nil { fmt.Println("Insert: This patient already exists--" + patientKey) return shim.Error("Insert: This patient already exists--" + patientKey) }
// === Create record object and marshal to JSON === objectType := "record" record := &record{objectType, recordKey, patientKey, patientSex, patientAge, dateInfo, patientDisease} recordJSONasBytes, err := json.Marshal(record) if err != nil { return shim.Error("Insert: " + err.Error()) }
// === Save record to state === err = stub.PutState(patientKey, recordJSONasBytes) if err != nil { return shim.Error("Insert: " + err.Error()) }
// ==== Index the patient to enable age-based range queries, e.g. return all 35-year-old patients ==== // An 'index' is a normal key/value entry in state. // The key is a composite key, with the elements that you want to range query on listed first. // In our case, the composite key is based on indexName~patientAge~patientKey. // This will enable very efficient state range queries based on composite keys matching indexName~color~* indexName := "disease~pkey" diseaseKeyIndexKey, err := stub.CreateCompositeKey(indexName, []string{record.PatientDisease, record.PatientKey}) if err != nil { return shim.Error(err.Error()) } // Save index entry to state. Only the key name is needed, no need to store a duplicate copy of the marble. // Note - passing a 'nil' value will effectively delete the key from state, therefore we pass null character as value value := []byte{0x00} stub.PutState(diseaseKeyIndexKey, value)
// === Record saved and indexed. Return success ==== fmt.Println("- end init patient") return shim.Success(nil) }
func (t *SimpleChaincode) queryPatientByKey(stub shim.ChaincodeStubInterface, args []string) pb.Response { var patientKey, jsonResp string var err error
if len(args) != 1 { return shim.Error("Query: Incorrect number of arguments. Expecting key of the patient to query") }
patientKey = args[0] valAsbytes, err := stub.GetState(patientKey) if err != nil { jsonResp = "{\"Error\":\"Failed to get state for " + patientKey + "\"}" return shim.Error(jsonResp) } else if valAsbytes == nil { jsonResp = "{\"Error\":\"Patient does not exist: " + patientKey + "\"}" return shim.Error(jsonResp) }
return shim.Success(valAsbytes) }
func (t *SimpleChaincode) queryRecordByKey(stub shim.ChaincodeStubInterface, args []string) pb.Response { // 0 // "0123" if len(args) != 1 { return shim.Error("Query: incorrect number of arguments. Expecting 1") }
recordKey := args[0] queryString := fmt.Sprintf("{\"selector\":{\"doctype\":\"record\",\"rkey\":\"%s\"}}", recordKey)
var err error queryResults, err := getQueryResultForQueryString(stub, queryString) if err != nil { return shim.Error(err.Error()) } return shim.Success(queryResults) }
func (t *SimpleChaincode) queryRecordByAge(stub shim.ChaincodeStubInterface, args []string) pb.Response { // 0 // "35" if len(args) != 1 { return shim.Error("Query: incorrect number of arguments. Expecting 1") } var err error patientAge, err := strconv.Atoi(args[0]) if err != nil { return shim.Error("Query: 1th argument must be a numeric integer") }
queryString := fmt.Sprintf("{\"selector\":{\"doctype\":\"record\",\"page\":%s}}", strconv.Itoa(patientAge))
queryResults, err := getQueryResultForQueryString(stub, queryString) if err != nil { return shim.Error(err.Error()) } return shim.Success(queryResults) }
func (t *SimpleChaincode) queryRecordByDisease(stub shim.ChaincodeStubInterface, args []string) pb.Response { // 0 // "headache" if len(args) != 1 { return shim.Error("Query: incorrect number of arguments. Expecting 1") } var err error patientDisease := strings.ToLower(args[0])
diseasePatientResultsIterator, err := stub.GetStateByPartialCompositeKey("disease~pkey", []string{patientDisease}) if err != nil { return shim.Error(err.Error()) } defer diseasePatientResultsIterator.Close()
// Iterate through result set and for each patient found var buffer bytes.Buffer var jsonResp string buffer.WriteString("[") for diseasePatientResultsIterator.HasNext() { // Note that we don't get the value (2nd return variable), we'll just get the patient key from the composite key responseRange, err := diseasePatientResultsIterator.Next() if err != nil { return shim.Error(err.Error()) }
// Get the disease and patient key from disease~pkey composite key objectType, compositeKeyParts, err := stub.SplitCompositeKey(responseRange.Key) if err != nil { return shim.Error(err.Error()) } returnedDisease := compositeKeyParts[0] returnedPatientKey := compositeKeyParts[1] fmt.Printf("- found a patient from index:%s disease:%s patinet key:%s\n", objectType, returnedDisease, returnedPatientKey) valAsbytes, err := stub.GetState(returnedPatientKey) if err != nil { jsonResp = "{\"Error\":\"Failed to get state for " + returnedPatientKey + "\"}" return shim.Error(jsonResp) } else if valAsbytes == nil { jsonResp = "{\"Error\":\"Patient does not exist: " + returnedPatientKey + "\"}" return shim.Error(jsonResp) } buffer.Write(valAsbytes) buffer.WriteByte('\n') } fmt.Printf("- found patients info as follows:%s\n", buffer.String()) return shim.Success(buffer.Bytes()) }
func (t *SimpleChaincode) queryRecordBySex(stub shim.ChaincodeStubInterface, args []string) pb.Response { // 0 // "male" if len(args) != 1 { return shim.Error("Query: incorrect number of arguments. Expecting 1") } var err error patientSex := strings.ToLower(args[0])
queryString := fmt.Sprintf("{\"selector\":{\"doctype\":\"record\",\"psex\":\"%s\"}}", patientSex)
queryResults, err := getQueryResultForQueryString(stub, queryString) if err != nil { return shim.Error(err.Error()) } return shim.Success(queryResults) }
func (t *SimpleChaincode) queryPatientHistory(stub shim.ChaincodeStubInterface, args []string) pb.Response { if len(args) < 1 { return shim.Error("Query: Incorrect number of arguments. Expecting 1") }
patientKey := args[0]
fmt.Printf("Query- start query patient's history: %s\n", patientKey)
resultsIterator, err := stub.GetHistoryForKey(patientKey) if err != nil { return shim.Error(err.Error()) } defer resultsIterator.Close()
// buffer is a JSON array containing historic values for the marble var buffer bytes.Buffer buffer.WriteString("[")
bArrayMemberAlreadyWritten := false for resultsIterator.HasNext() { response, err := resultsIterator.Next() if err != nil { return shim.Error(err.Error()) } // Add a comma before array members, suppress it for the first array member if bArrayMemberAlreadyWritten == true { buffer.WriteString(",") } buffer.WriteString("{\"TxId\":") buffer.WriteString("\"") buffer.WriteString(response.TxId) buffer.WriteString("\"")
buffer.WriteString(", \"Value\":") // if it was a delete operation on given key, then we need to set the //corresponding value null. Else, we will write the response.Value //as-is (as the Value itself a JSON marble) if response.IsDelete { buffer.WriteString("null") } else { buffer.WriteString(string(response.Value)) }
buffer.WriteString(", \"Timestamp\":") buffer.WriteString("\"") buffer.WriteString(time.Unix(response.Timestamp.Seconds, int64(response.Timestamp.Nanos)).String()) buffer.WriteString("\"")
buffer.WriteString(", \"IsDelete\":") buffer.WriteString("\"") buffer.WriteString(strconv.FormatBool(response.IsDelete)) buffer.WriteString("\"")
buffer.WriteString("}") bArrayMemberAlreadyWritten = true } buffer.WriteString("]")
fmt.Printf("- query patient's history returning:\n%s\n", buffer.String())
return shim.Success(buffer.Bytes()) }
func (t *SimpleChaincode) queryPatientByRange(stub shim.ChaincodeStubInterface, args []string) pb.Response { if len(args) < 2 { return shim.Error("RangeQuery: Incorrect number of arguments. Expecting 2") } startKey := args[0] endKey := args[1] resultsIterator, err := stub.GetStateByRange(startKey, endKey) if err != nil { return shim.Error(err.Error()) } defer resultsIterator.Close() // buffer is a JSON array containing QueryResults var buffer bytes.Buffer buffer.WriteString("[") bArrayMemberAlreadyWritten := false for resultsIterator.HasNext() { queryResponse, err := resultsIterator.Next() if err != nil { return shim.Error(err.Error()) } // Add a comma before array members, suppress it for the first array member if bArrayMemberAlreadyWritten == true { buffer.WriteString(",") } buffer.WriteString("{\"Key\":") buffer.WriteString("\"") buffer.WriteString(queryResponse.Key) buffer.WriteString("\"") buffer.WriteString(", \"Record\":") // Record is a JSON object, so we write as-is buffer.WriteString(string(queryResponse.Value)) buffer.WriteString("}") bArrayMemberAlreadyWritten = true } buffer.WriteString("]") fmt.Printf("- queryPatientByRange queryResult:\n%s\n", buffer.String()) return shim.Success(buffer.Bytes()) }
func getQueryResultForQueryString(stub shim.ChaincodeStubInterface, queryString string) ([]byte, error) { fmt.Printf("- getQueryResultForQueryString queryString:\n%s\n", queryString)
resultsIterator, err := stub.GetQueryResult(queryString) if err != nil { return nil, err } defer resultsIterator.Close()
// buffer is a JSON array containing QueryRecords var buffer bytes.Buffer buffer.WriteString("[")
bArrayMemberAlreadyWritten := false for resultsIterator.HasNext() { queryResponse, err := resultsIterator.Next() if err != nil { return nil, err } // Add a comma before array members, suppress it for the first array member if bArrayMemberAlreadyWritten == true { buffer.WriteString(",") } buffer.WriteString("{\"Key\":") buffer.WriteString("\"") buffer.WriteString(queryResponse.Key) buffer.WriteString("\"")
buffer.WriteString(", \"Record\":") // Record is a JSON object, so we write as-is buffer.WriteString(string(queryResponse.Value)) buffer.WriteString("}") bArrayMemberAlreadyWritten = true } buffer.WriteString("]") fmt.Printf("- getQueryResultForQueryString queryResult:\n%s\n", buffer.String()) return buffer.Bytes(), nil }
func (t *SimpleChaincode) updatePatientState(stub shim.ChaincodeStubInterface, args []string) pb.Response {
// 0 1 2 3 4 // '"0124","0000","35","2006-01-02 15:04:05","legbroke" if len(args) < 4 { return shim.Error("Update: incorrect number of arguments. Expecting 4") } var err error recordKey := args[0] patientKey := args[1] newAge, err := strconv.Atoi(args[2]) if err != nil { return shim.Error("Update: 3rd argument must be a numeric integer") } newDate, err := time.Parse(timeFormat, args[3]) if err != nil { return shim.Error("Update: 4rd argument must be in time format") } newDisease := strings.ToLower(args[4]) fmt.Println("Update- start update patient state ", patientKey, ":", recordKey, newAge, newDate, newDisease)
patientAsBytes, err := stub.GetState(patientKey) if err != nil { return shim.Error("Update: failed to get patient. " + err.Error()) } else if patientAsBytes == nil { fmt.Println("Update: This patient does not exist--" + patientKey) return shim.Error("Update: This patient does not exist--" + patientKey) } patientToTransfer := record{} err = json.Unmarshal(patientAsBytes, &patientToTransfer) if err != nil { return shim.Error(err.Error()) }
// Maintain the index indexName := "disease~pkey" diseaseKeyIndexKey, err := stub.CreateCompositeKey(indexName, []string{patientToTransfer.PatientDisease, patientToTransfer.PatientKey}) if err != nil { return shim.Error(err.Error()) }
// Delete index entry to state err = stub.DelState(diseaseKeyIndexKey) if err != nil { return shim.Error("Update: Failed to maintain index :" + err.Error()) } return shim.Success(nil)
patientToTransfer.PatientAge = newAge patientToTransfer.DateInfo = newDate patientToTransfer.PatientDisease = newDisease //change info
// Create new index entry to state newDiseaseKeyIndexKey, err := stub.CreateCompositeKey(indexName, []string{patientToTransfer.PatientDisease, patientToTransfer.PatientKey}) if err != nil { return shim.Error(err.Error()) } // Save index entry to state. Only the key name is needed, no need to store a duplicate copy of the marble. // Note - passing a 'nil' value will effectively delete the key from state, therefore we pass null character as value value := []byte{0x00} stub.PutState(newDiseaseKeyIndexKey, value)
patientJSONasBytes, _ := json.Marshal(patientToTransfer) err = stub.PutState(patientKey, patientJSONasBytes) //rewrite the patient if err != nil { return shim.Error(err.Error()) } fmt.Println("- end update patient state (success)") return shim.Success(nil) }
}}}}