ブロックチェーンを使ったチケットアプリ
SCP Blockchain サービスの REST API テスト画面
アーキテクチャ
{
"authenticationMethod": "route",
"logout": {
"logoutEndpoint": "/logout",
"logoutPage": "logout.html"
},
"routes": [
{
"source": "^/api/(.*)$",
"target": "$1",
"destination": "python",
"authenticationType": "xsuaa",
"csrfProtection": false
},
{
"source": "^/(.*)$",
"target": "$1",
"destination": "myapp",
"authenticationType": "xsuaa"
}
]
}
---
#application settings
applications:
#approuter settings
- name: approuter
memory: 128M
routes:
- route: approuter-sXXXXXXXXXXtrial.cfapps.eu10.hana.ondemand.com
env: # env variables
TENANT_HOST_PATTERN: 'approuter-(.*).cfapps.eu10.hana.ondemand.com'
SAP_JWT_TRUST_ACL: '[{"clientid":"*","identityzone":"*"}]'
destinations: >
[
{
"name": "myapp",
"url": "https://myapp-sXXXXXXXXXXtrial.cfapps.eu10.hana.ondemand.com/",
"forwardAuthToken": true
},
{
"name": "python",
"url": "https://myapi-sXXXXXXXXXXtrial.cfapps.eu10.hana.ondemand.com/",
"forwardAuthToken": true
}
]
services: # binded services
- my-xsuaa # instantie to uaa service
xsuaa サービスインスタンス作成
$ npm run build && cf push
async created() {
// axiosでユーザ情報を取得
let response = await this.$http.get('/uaa/userinfo');
var userinfo = response.data;
// 例えば、Vuexにカレントユーザをストアしておく
this.$store.dispatch('setCurrentUser', userinfo);
},
{
"user_id":"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
"user_name":"kaori@example.com",
"given_name":"Kaori",
"family_name":"Sukenobe",
"email":"kaori@example.com",
"email_verified":true,
"previous_logon_time":null,
"sub":"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
"name":"Kaori Sukenobe"
}
---
applications:
- name: my-api
host: ksukenobe-api
path: .
memory: 512M
command: python server.py
env:
# Blockchain service
CHAINCODE_ID: 'chaincode id'
# QR Generator configuration
QR_VERSION: 5
ERR_CORRECT_VAL: 'H'
CONTRAST_VAL: '1.0'
BRIGHTNESS_VAL: '1.0'
IS_COLOR: true
IS_PIXELATE: false
services:
- my-xsuaa
- tickettransfer.dev
python-3.6.x
$ pip download -d vendor -r requirements.txt --platform manylinux1_x86_64 --only-binary=:all:
uaa_service = env.get_service(name='my-xsuaa').credentials
baas_service = env.get_service(name='tickettransfer.dev').credentials
def xsuaa_token(f):
@wraps(f)
def decorated_view(*args, **kwargs):
if 'authorization' not in request.headers:
error_message = {
'error': 'authorization header not found'
}
return make_response(jsonify(error_message), 403)
access_token = request.headers.get('authorization')[7:]
security_context = xssec.create_security_context(access_token, uaa_service)
isAuthorized = security_context.check_scope('openid')
if not isAuthorized:
error_message = {
'error': 'not authorized token'
}
return make_response(jsonify(error_message), 403)
return f(*args, **kwargs)
return decorated_view
@app.route('/assets/tickets', methods=['get'])
@xsuaa_token
def get_my_tickets():
'''
自分が所有するチケットを取得します.
'''
owner = request.args.get('owner')
print('get_my_ticket is called: owner -> {}'.format(owner))
oauth_token_headers = {'Authorization': 'Bearer {}'.format(
get_oauth_token(
baas_service['oAuth']['url'] + '/oauth/token?grant_type=client_credentials',
baas_service['oAuth']['clientId'],
baas_service['oAuth']['clientSecret'])
)}
response = requests.get(
baas_service['serviceUrl'] + '/chaincodes/' + CHAINCODE_ID + '/latest/assets/tickets?owner={}'.format(owner),
headers=oauth_token_headers)
return jsonify(response.json()), response.status_code
チェーンコードに必要なもの
package main
import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
"strconv"
"strings"
"time"
"github.com/hyperledger/fabric/core/chaincode/shim"
"github.com/hyperledger/fabric/protos/peer"
)
type SimpleAsset struct {
}
type Ticket struct {
ObjectType string `json:"docType"`
ID string `json:"id"`
Event Event `json:"event"`
Owner string `json:"owner"`
Status string `json:"status"`
}
関数 | 役割 |
Init | Instantiate、Upgradeのときに呼び出される。 |
Invoke | 台帳操作(更新・参照)のときに呼び出される。 |
func main() {
if err := shim.Start(new(SimpleAsset)); err != nil {
fmt.Printf("Error starting SimpleAsset chaincode: %s", err)
}
}
// Instantiate, Upgradeで呼び出される
func (t *SimpleAsset) Init(stub shim.ChaincodeStubInterface) peer.Response {
fmt.Println("Init() is called.")
return Success(http.StatusNoContent, "OK", nil)
}
// 台帳操作時に呼び出される
func (t *SimpleAsset) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
fn, args := stub.GetFunctionAndParameters()
var result string
var err error
// 関数名で処理をディスパッチする
if fn == "init" {
return t.Init(stub)
} else if fn == "initTickets" {
return t.initTickets(stub, args)
} else if fn == "editTicket" {
return t.editTicket(stub, args)
} else if fn == "deleteTicket" {
return t.deleteTicket(stub, args)
} else if fn == "getHistory" {
return t.getHistory(stub, args)
} else if fn == "getAllAssets" {
return t.getAllAssets(stub)
} else if fn == "getTicketByOwner" {
return t.getTicketByOwner(stub, args)
} else if fn == "getTicketById" {
return t.getTicketByID(stub, args)
}
if err != nil {
fmt.Println("error")
}
return shim.Success([]byte(result))
}
// チケットIDをキーにデータを取得する
func (t *SimpleAsset) getTicketByID(stub shim.ChaincodeStubInterface, args []string) peer.Response {
fmt.Println("----- getTicketByID() start. ")
if len(args) < 1 {
return Error(http.StatusBadRequest, "Incorrect number of arguments. Expecting 1")
}
id := args[0]
// キーを指定してステートを読み取るAPIを実行
jsonAsBytes, err := stub.GetState(id)
if err != nil {
return Error(http.StatusInternalServerError, "Failed to get ticket: "+id)
} else if jsonAsBytes == nil {
return Error(http.StatusNotFound, "Ticket is not found. key: "+id)
}
fmt.Println("----- getTicketByID() end. ")
return Success(http.StatusOK, "OK", jsonAsBytes)
}
// チケットのトランザクションを取得する
func (t *SimpleAsset) getHistory(stub shim.ChaincodeStubInterface, args []string) peer.Response {
fmt.Println("----- getHistory() start.")
if len(args) < 1 {
return Error(http.StatusBadRequest, "Incorrect number of arguments. Expecting 1")
}
id := args[0]
// ステートの変更履歴を取得するAPIを実行
resultsIterator, err := stub.GetHistoryForKey(id)
if err != nil {
fmt.Printf(err.Error())
return Error(http.StatusInternalServerError, err.Error())
}
defer resultsIterator.Close()
var buffer bytes.Buffer
buffer.WriteString("[")
bArrayMemberAlreadyWritten := false
for resultsIterator.HasNext() {
response, err := resultsIterator.Next()
if err != nil {
return Error(http.StatusInternalServerError, err.Error())
}
if bArrayMemberAlreadyWritten == true {
buffer.WriteString(",")
}
buffer.WriteString("{\"TxId\":")
buffer.WriteString("\"")
buffer.WriteString(response.TxId)
buffer.WriteString("\"")
buffer.WriteString(", \"Value\":")
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("- getHistory returning:\n%s\n", buffer.String())
fmt.Println("----- getHistory() end.")
return Success(http.StatusOK, "OK", buffer.Bytes())
}
# OpenAPI.yaml file
swagger: "2.0"
info:
version: "1.0.0"
title: "tickettransfer"
consumes:
- application/x-www-form-urlencoded
produces:
- application/json
parameters:
ticketId:
name: ticket_id
in: path
required: true
type: string
paths:
/assets/tickets/{ticket_id}:
get:
operationId: getTicketById
parameters:
- $ref: '#/parameters/ticketId'
responses:
200:
description: "OK"
404:
description: "Not Found"
/assets/tickets/{ticket_id}/history:
get:
operationId: getHistory
parameters:
- $ref: '#/parameters/ticketId'
responses:
200:
description: "OK"
// indexOwner.json
{
"index" : {
"fields" : [
"docType","owner"
]
},
"ddoc" : "indexOwnerDoc",
"name" : "indexOwner",
"type":"json"
}
// 自分が所有するチケットを取得する
func (t *SimpleAsset) getTicketByOwner(stub shim.ChaincodeStubInterface, args []string) peer.Response {
fmt.Println("----- getTicketByOwner() start. ")
if len(args) < 1 {
return Error(http.StatusBadRequest, "Incorrect number of arguments. Expecting 1")
}
owner := strings.ToLower(args[0])
queryString := fmt.Sprintf("{\"selector\":{\"docType\":\"ticket\",\"owner\":\"%s\"}}", owner)
queryResults, err := getQueryResultForQueryString(stub, queryString)
if err != nil {
return Error(http.StatusInternalServerError, err.Error())
}
fmt.Println("----- getTicketByOwner() end. ")
return Success(http.StatusOK, "OK", queryResults)
}
// 内部関数:リッチクエリを使ってデータを取得する
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, err := constructQueryResponseFromIterator(resultsIterator)
if err != nil {
return nil, err
}
fmt.Printf("- getQueryResultForQueryString queryResult:\n%s\n", buffer.String())
return buffer.Bytes(), nil
}
package main
import (
"fmt"
"testing"
"github.com/hyperledger/fabric/core/chaincode/shim"
)
func TestInit(t *testing.T) {
simpleCC := new(SimpleAsset)
mockStub := shim.NewMockStub("mockstub", simpleCC)
mockStub.MockTransactionStart("tx0")
response := simpleCC.Init(mockStub)
mockStub.MockTransactionEnd("tx0")
if s := response.GetStatus(); s != 200 {
fmt.Println("Init test failed")
t.FailNow()
}
}
func TestNewTicket(t *testing.T) {
simpleCC := new(SimpleAsset)
mockStub := shim.NewMockStub("mockstub", simpleCC)
fmt.Println("--- 初期データを登録する")
mockStub.MockTransactionStart("tx1")
response := simpleCC.initLedger(mockStub)
mockStub.MockTransactionEnd("tx1")
fmt.Println("Status: " + fmt.Sprint(response.GetStatus()))
fmt.Println("Payload: " + string(response.GetPayload()))
fmt.Println("Message: " + response.GetMessage())
fmt.Println("--- チケットを登録する")
mockStub.MockTransactionStart("tx2")
initTickets := `[
{
"docType": "ticket",
"id" : "t-98a692bd-f8b3-44a4-aab6-7fc8bba8a40c",
"performanceid" : "p1",
"owner" : "98f3e258-bcbe-4fac-a454-511d22770f7b",
"status" : "RESERVED"
},
{
"docType": "ticket",
"id" : "t-baa1d5ec-7d5b-4e72-a0cd-6ea6ed34936b",
"performanceid" : "p3",
"owner" : "1c55ca5a-0abb-4162-9537-cf9366a402a0",
"status" : "RESERVED"
}
]`
args := []string{initTickets}
response = simpleCC.initTickets(mockStub, args)
mockStub.MockTransactionEnd("tx2")
fmt.Println("Status: " + fmt.Sprint(response.GetStatus()))
fmt.Println("Payload: " + string(response.GetPayload()))
fmt.Println("Message: " + response.GetMessage())
fmt.Println("--- 全アセットを取得する")
mockStub.MockTransactionStart("tx3")
response = simpleCC.getAllAssets(mockStub)
mockStub.MockTransactionEnd("tx3")
fmt.Println("Status: " + fmt.Sprint(response.GetStatus()))
fmt.Println("Payload: " + string(response.GetPayload()))
fmt.Println("Message: " + response.GetMessage())
}
チェーンコードのテスト
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
12 | |
12 | |
7 | |
5 | |
5 | |
4 | |
4 | |
3 | |
3 | |
3 |