混子日记——校园征信项目4

基于fabric的校园征信系统——beego框架后端代码编写及fabric-sdk-go的调用

beego框架

       beego 是一个快速开发 Go 应用的 HTTP 框架,他可以用来快速开发 API、Web 及后端服务等各种应用,是一个 RESTful 的框架,主要设计灵感来源于 tornado、sinatra 和 flask 这三个框架,但是结合了 Go 本身的一些特性(interface、struct 嵌入等)而设计的一个框架。

路由配置

       我们首先要做的就是设置我们的路由,然后根据路由依次实现对应的处理函数。

package routers

import (
	"school-credit/controllers"
	"github.com/astaxie/beego"
)

func init() {
	beego.Router("/addStuInfo", &controllers.MainController{}, "post: AddStuInfo")
	beego.Router("/getStuInfo", &controllers.MainController{}, "get: GetStuInfo")
	beego.Router("/addSchInfo", &controllers.MainController{}, "post: AddSchInfo")
	beego.Router("/getSchInfo", &controllers.MainController{}, "get: GetSchInfo")
	beego.Router("/addCreInfo", &controllers.MainController{}, "post: AddCreInfo")
	beego.Router("/getCreInfo", &controllers.MainController{}, "get: GetCreInfo")
}

Controller层业务逻辑

       接下来在controller层应该以此实现如下函数:

func (c *MainController) AddStuInfo() {

}
func (c *MainController) GetStuInfo() {

}
func (c *MainController) AddSchInfo() {

}
func (c *MainController) GetSchInfo() {

}
func (c *MainController) AddCreInfo() {

}
func (c *MainController) GetCreInfo() {

}

       我们以添加和查询学生基本信息为例,先获取前端传来的信息,再调用model层的SDK实现向区块中写入或读取数据,再将操作信息传给前端页面:

func (this *MainController) AddStuInfo() {
	//获取前端传入的数据
	StuName := this.GetString("StuName")
	StuSex := this.GetString("StuSex")
	NativePlace := this.GetString("NativePlace")
	Birthday := this.GetString("Birthday")
	Nation := this.GetString("Nation")
	IDNum := this.GetString("IDNum")
	//判断ID是否为空
	if IDNum == "" {
		return
	}
	AdmissionDate := this.GetString("AdmissionDate")
	GraduationTime := this.GetString("GraduationTime")
	SchName := this.GetString("SchName")
	SubName := this.GetString("SubName")
	//将参数放入字符串数组
	var args []string
	args = append(args, "addStuInfo")
	args = append(args, StuName)
	args = append(args, StuSex)
	args = append(args, NativePlace)
	args = append(args, Birthday)
	args = append(args, Nation)
	args = append(args, IDNum)
	args = append(args, AdmissionDate)
	args = append(args, GraduationTime)
	args = append(args, SchName)
	args = append(args, SubName)
    //TODO 调用model层函数实现数据上链
}
func (c *MainController) GetStuInfo() {
    var args []string
    IDNum := this.GetString("IDNum")
    args = append(args, "getStuInfo")
   	args = append(args, IDNum)
    //TODO 调用model层函数实现链上数据查询
}

       接下来我们需要编写model层的功能代码,以支持上层开发

Fabric-SDK-GO

       fabric go sdk是Hyperledger Fabric官方提供的Go语言开发包,应用程序可以利用fabric go sdk与fabric网络进行交互并访问链码。
Fabric SDK

编写SDK配置文件

       首先我们需要将fabric生成的证书文件(crypto-config)以及通道文件(channel-artifacts)复制到项目目录中,后续需要使用到这些文件。
       我们可以参考$GOPATH/src/github.com/hyperledger/fabric-sdk-go/test/fixtures/config/config_test.yaml下的模板文件进行SDK配置文件的编写

       client使用sdk与fabric网络交互,需要告诉sdk两类信息:

  • 我是谁:即当前client的信息,包含所属组织、密钥和证书文件的路径等, 这是每个client专用的信息。
  • 对方是谁:即fabric网络结构的信息,channel、org、orderer和peer等 的怎么组合起当前fabric网络的,这些结构信息应当与configytx.yaml中是一致的。这是通用配置,每个客户端都可以拿来使用。另外,这部分信息并不需要是完整fabric网络信息,如果当前client只和部分节点交互,那配置文件中只需要包含所使用到的网络信息。
    config.yaml内容

参考文章 fabric sdk go新手入门

相关配置如下:

# 配置文件的名字,可以不写
name: "school-credit-network"

version: 1.0.0

# client 相关配置
client:
  # 此 SDK 实例属于哪个组织
  organization: OrgSchool
  # 日志级别
  logging:
    level: info
  # 证书所在目录
  cryptoconfig:
    path: /home/surface/workspace/src/school-credit/conf/crypto-config
  # 这种方式就是把用户名和密码直接存储在本地的一个文件中,而用户和密码对通过一个别名来引用,这样可以避免密码明文格式可能会存在的安全问题
  credentialStore:
    path: /tmp/schoolcredit-service-store

  # 区块链密码服务提供者,指定加密策略
  BCCSP:
    security:
      enabled: true
      default:
        provider: "SW"
      hashAlgorithm: "SHA2"
      softVerify: true
      level: 256

  tlsCerts:
  	# 证书池策略,默认为false,提高身份认证速率
    systemCertPool: true
    client:
      keyfile: /home/surface/workspace/src/school-credit/conf/crypto-config/peerOrganizations/orgschool.rjxy.cn/users/User1@orgschool.rjxy.cn/tls/client.key
      certfile: /home/surface/workspace/src/school-credit/conf/crypto-config/peerOrganizations/orgschool.rjxy.cn/users/User1@orgschool.rjxy.cn/tls/client.crt


# channel 相关配置
channels:
  # channelID
  schoolchannel:
    # orderer 组织必须指定
    orderers:
      - orderer.rjxy.cn
	# 添加到该 channel 中的组织的 peer 列表
    peers:
      peer0.orgschool.rjxy.cn:
        endorsingPeer: true
        chaincodeQuery: true
        ledgerQuery: true
        eventSource: true

      peer1.orgschool.rjxy.cn:
        endorsingPeer: true
        chaincodeQuery: true
        ledgerQuery: true
        eventSource: true

    policies:
      queryChannelConfig:
        minResponses: 1
        maxTargets: 1
        retryOpts:
          attempts: 5
          initialBackoff: 500ms
          maxBackoff: 5s
          backoffFactor: 2.0

  # channelID
  unionchannel:
    # orderer 组织必须指定
    orderers:
      - orderer.rjxy.cn
	# 添加到该 channel 中的组织的 peer 列表
    peers:
      peer0.orgschool.rjxy.cn:
        endorsingPeer: true
        chaincodeQuery: true
        ledgerQuery: true
        eventSource: true

      peer1.orgschool.rjxy.cn:
        endorsingPeer: true
        chaincodeQuery: true
        ledgerQuery: true
        eventSource: true
  
      peer0.orgenterprise.rjxy.cn:
        endorsingPeer: true
        chaincodeQuery: true
        ledgerQuery: true
        eventSource: true

      peer1.orgenterprise.rjxy.cn:
        endorsingPeer: true
        chaincodeQuery: true
        ledgerQuery: true
        eventSource: true
    
      peer0.orgcredit.rjxy.cn:
        endorsingPeer: true
        chaincodeQuery: true
        ledgerQuery: true
        eventSource: true

      peer1.orgcredit.rjxy.cn:
        endorsingPeer: true
        chaincodeQuery: true
        ledgerQuery: true
        eventSource: true
 
    policies:
      queryChannelConfig:
        minResponses: 1
        maxTargets: 1
        retryOpts:
          attempts: 5
          initialBackoff: 500ms
          maxBackoff: 5s
          backoffFactor: 2.0

# organizations 相关配置
organizations:
  OrgSchool:
    # configtx.yaml organizations -> ID
    mspid: OrgSchoolMSP

    cryptoPath: /home/surface/workspace/src/school-credit/conf/crypto-config/peerOrganizations/orgschool.rjxy.cn/users/{userName}@orgschool.rjxy.cn/msp
    peers:
    - peer0.orgschool.rjxy.cn
    - peer1.orgschool.rjxy.cn
 
  OrgEnterprise:
    # configtx.yaml organizations -> ID
    mspid: OrgEnterpriseMSP

    cryptoPath: /home/surface/workspace/src/school-credit/conf/crypto-config/peerOrganizations/orgenterprise.rjxy.cn/users/{userName}@orgenterprise.rjxy.cn/msp
    peers:
    - peer0.orgenterprise.rjxy.cn
    - peer1.orgenterprise.rjxy.cn
 
  OrgCredit:
    # configtx.yaml organizations -> ID
    mspid: OrgCreditMSP

    cryptoPath: /home/surface/workspace/src/school-credit/conf/crypto-config/peerOrganizations/orgcredit.rjxy.cn/users/{userName}@orgcredit.rjxy.cn/msp
    peers:
    - peer0.orgcredit.rjxy.cn
    - peer1.orgcredit.rjxy.cn


  Orderer:
    mspID: OrdererMSP
    cryptoPath: /home/surface/workspace/src/school-credit/conf/crypto-config/ordererOrganizations/rjxy.cn/users/Admin@rjxy.cn/msp

# orderer 相关配置
orderers:
  orderer.rjxy.cn:
    url: localhost:7050
    grpcOptions:
      ssl-target-name-override: orderer.rjxy.cn
      keep-alive-time: 0s
      keep-alive-timeout: 20s
      keep-alive-permit: false
      fail-fast: false
      allow-insecure: false

    tlsCACerts:
      path: /home/surface/workspace/src/school-credit/conf/crypto-config/ordererOrganizations/rjxy.cn/tlsca/tlsca.rjxy.cn-cert.pem

# peer 相关配置
peers:
  peer0.orgschool.rjxy.cn:
    url: localhost:7051
    eventUrl: localhost:7053
    grpcOptions:
      ssl-target-name-override: peer0.orgschool.rjxy.cn
      keep-alive-time: 0s
      keep-alive-timeout: 20s
      keep-alive-permit: false
      fail-fast: false
      allow-insecure: false

    tlsCACerts:
      path: /home/surface/workspace/src/school-credit/conf/crypto-config/peerOrganizations/orgschool.rjxy.cn/tlsca/tlsca.orgschool.rjxy.cn-cert.pem

  peer1.orgschool.rjxy.cn:
    url: localhost:8051
    eventUrl: localhost:8053
    grpcOptions:
      ssl-target-name-override: peer1.orgschool.rjxy.cn
      keep-alive-time: 0s
      keep-alive-timeout: 20s
      keep-alive-permit: false
      fail-fast: false
      allow-insecure: false

    tlsCACerts:
      path: /home/surface/workspace/src/school-credit/conf/crypto-config/peerOrganizations/orgschool.rjxy.cn/tlsca/tlsca.orgschool.rjxy.cn-cert.pem
 
  peer0.orgenterprise.rjxy.cn:
    url: localhost:9051
    eventUrl: localhost:9053
    grpcOptions:
      ssl-target-name-override: peer0.orgenterprise.rjxy.cn
      keep-alive-time: 0s
      keep-alive-timeout: 20s
      keep-alive-permit: false
      fail-fast: false
      allow-insecure: false

    tlsCACerts:
      path: /home/surface/workspace/src/school-credit/conf/crypto-config/peerOrganizations/orgenterprise.rjxy.cn/tlsca/tlsca.orgenterprise.rjxy.cn-cert.pem

  peer1.orgenterprise.rjxy.cn:
    url: localhost:10051
    eventUrl: localhost:10053
    grpcOptions:
      ssl-target-name-override: peer1.orgenterprise.rjxy.cn
      keep-alive-time: 0s
      keep-alive-timeout: 20s
      keep-alive-permit: false
      fail-fast: false
      allow-insecure: false

    tlsCACerts:
      path: /home/surface/workspace/src/school-credit/conf/crypto-config/peerOrganizations/orgenterprise.rjxy.cn/tlsca/tlsca.orgenterprise.rjxy.cn-cert.pem

  peer0.orgcredit.rjxy.cn:
    url: localhost:11051
    eventUrl: localhost:11053
    grpcOptions:
      ssl-target-name-override: peer0.orgcredit.rjxy.cn
      keep-alive-time: 0s
      keep-alive-timeout: 20s
      keep-alive-permit: false
      fail-fast: false
      allow-insecure: false

    tlsCACerts:
      path: /home/surface/workspace/src/school-credit/conf/crypto-config/peerOrganizations/orgcredit.rjxy.cn/tlsca/tlsca.orgcredit.rjxy.cn-cert.pem

  peer1.orgcredit.rjxy.cn:
    url: localhost:12051
    eventUrl: localhost:12053
    grpcOptions:
      ssl-target-name-override: peer1.orgcredit.rjxy.cn
      keep-alive-time: 0s
      keep-alive-timeout: 20s
      keep-alive-permit: false
      fail-fast: false
      allow-insecure: false

    tlsCACerts:
      path: /home/surface/workspace/src/school-credit/conf/crypto-config/peerOrganizations/orgcredit.rjxy.cn/tlsca/tlsca.orgcredit.rjxy.cn-cert.pem

model层代码编写

       我们在models层操作需要用到 Fabric_SDK ,所以我们首先要做的就是先拿到 SDK 句柄,创建channel,添加节点,安装链代码,实例化链代码之手,才能进行数据的操作。首先我们定义一个结构体,该结构体里面存放一些与创建Fabric-SDK 有关的配置信息,格式如下:

package models

import (
	"github.com/astaxie/beego"
	"github.com/hyperledger/fabric-sdk-go/pkg/client/channel"
	"github.com/hyperledger/fabric-sdk-go/pkg/client/event"
	"github.com/hyperledger/fabric-sdk-go/pkg/client/resmgmt"
	"github.com/hyperledger/fabric-sdk-go/pkg/fabsdk"
)
//定义存放与Fabric-SDK有关配置值信息的结构体
type FabricSetup struct {
	ConfigFile      string				// 初始化 SDK 对应的配置文件
	OrgID           string              //组织节点ID
	OrdererID       string				// orderer节点
	ChannelID       string				// ChannelID
	ChainCodeID     string				// ChainCodeID
	ChannelConfig   string				// 与channel相关的配置文件
	ChaincodeGoPath string				// GOPATH 环境变量
	ChaincodePath   string				// ChainCode 存放路径
	OrgAdmin        string				// Admin 用户名
	OrgName         string				// 组织名称
	UserName        string				// 普通用户名
	eventID         string				// eventID
	initialized     bool                //是否已经初始化
	client          *channel.Client     //pkg/client/channel支持访问Fabric网络上的通道。channel客户端实例提供与指定通道上的Peer节点进行交互的处理函数。channel客户端可以在指定通道上查询链码,执行链码以及注册或注销链码事件。如果应用程序需要与Fabric网络的多条通道进行交互,需要为每条通道创建一个单独的通道客户端实例。
	resMgmtClient   *resmgmt.Client     //resmgmt支持在Fabric网络上创建和更新资源。resmgmt允许管理员创建、更新通道,并允许Peer节点加入通道。管理员还可以在Peer节点上执行与链码相关的操作,例如安装,实例化和升级链码。
	sdk             *fabsdk.FabricSDK   //fabsdk是Fabric SDK的主要包,fabsdk支持客户端使用Hyperledger Fabric区块链网络。fabsdk基于配置创建上下文环境,上下文环境会在client包使用。
	event           *event.Client       //event包支持访问Fabric网络上的通道事件。事件客户端可以接收区块事件,过滤区块事件,链码事件和交易状态事件。
}

type Application struct {
	beego.Controller
	FabricSetup *FabricSetup
}
// 学生基本信息
type StuInfo struct {
	StuName         string `json:"stuname"`    // 学生姓名
	StuSex          string `json:"stusex"` // 学生性别
	NativePlace     string `json:"nativeplace"`    //籍贯
	Birthday        string `json:"birthday"`  // 出生日期
	Nation          string `json:"nation"`  // 民族
	IDNum           string `json:"idnum"` // 身份证号
	AdmissionDate   string `json:"admissiondate"` // 入学日期
	GraduationTime  string `json:"graduationtime"` // 毕业日期
	SchName         string `json:"schname"` // 学校名称
	SubName         string `json:"subname"` // 专业名称
}
// 学校记录信息
type SchInfo struct {
	StuGPA            string `json:"stugpa"`    // 学生绩点
	ExcellentRecord   string `json:"excellentrecord"` // 优良记录
	BadRecord         string `json:"badrecord"`    //不良记录
	StuLoan           string `json:"stuloan"`  // 助学贷款记录
	GraduationComment string `json:"graduationcomment"`  // 毕业评价

}
// 征信机构信息
type CreInfo struct {
	BankOverdraft       string `json:"bankoverdraft"`    // 信用卡透支记录
	AntCreditPayOverDue string `json:"antcreditpayoverdue"` // 花呗逾期记录
	DidiTaxiArrears     string `json:"diditaxiarrears"`    //滴滴欠款记录
	SesameCredit        string `json:"sesamecredit"`  // 芝麻信用积分

}
// 学生征信档案
type StuCreInfo struct {
	StuInfo     StuInfo `json:"stuinfo"`    // 学生基本信息
	SchInfo     SchInfo `json:"schinfo"` // 学校记录信息
	CreInfo     CreInfo `json:"creinfo"`    //征信机构信息
}

       下一步我们新建setup.go文件,在里面利用fabric-sdk-go设计FabricSetup结构体的方法,包括SDK的初始化,链码的安装和实例化以及资源的释放。代码如下:

package models

import (
	"fmt"
	"github.com/hyperledger/fabric-sdk-go/pkg/client/channel"
	"github.com/hyperledger/fabric-sdk-go/pkg/client/resmgmt"
	"github.com/hyperledger/fabric-sdk-go/pkg/common/errors/retry"
	"github.com/hyperledger/fabric-sdk-go/pkg/core/config"
	"github.com/hyperledger/fabric-sdk-go/pkg/fab/ccpackager/gopackager"
	"github.com/hyperledger/fabric-sdk-go/pkg/fabsdk"
	"github.com/hyperledger/fabric-sdk-go/third_party/github.com/hyperledger/fabric/common/cauthdsl"
	"github.com/pkg/errors"
)

//初始化配置文件以及客户端方法
func (setup *FabricSetup) Initialize() error {
	//判断是否已经初始化
	if setup.initialized == true {
		return errors.New("sdk already initialized")
	}
	//通过配置文件初始化SDK
	sdk, err := fabsdk.New(config.FromFile(setup.ConfigFile))
	if err != nil {
		return errors.WithMessage(err, "failed to create sdk")
	}
	setup.sdk = sdk
	fmt.Println("SDK created")

	//通过 SDK 实例,基于用户和组织创建上下文[为 创建 resMgmtCli 做准备]
	resourceManagerClientcontext := setup.sdk.Context(fabsdk.WithUser(setup.OrgAdmin), fabsdk.WithOrg(setup.OrgName))
	// 创建资源管理客户端 - resMgmtCli
	resMgmtClient, err := resmgmt.New(resourceManagerClientcontext)
	if err != nil {
		return errors.WithMessage(err, "failed to create channel management client from Admin identity")
	}
	setup.resMgmtClient = resMgmtClient
	fmt.Println("ResourceManager client created")
    //利用资源管理客户端,创建 channel
	req := resmgmt.SaveChannelRequest{
		ChannelID:         setup.ChannelID,
		ChannelConfigPath: setup.ChannelConfig,
	}
	txID, err := setup.resMgmtClient.SaveChannel(req)
	if err != nil || txID.TransactionID == ""{
		return errors.WithMessage(err, "failed to save channel")
	}
	fmt.Println("Channel created")
	// 把组织添加到 channel 中的时候,一般制定一些重试的策略,和指定 orderer节点的网络位置
	err = setup.resMgmtClient.JoinChannel(setup.ChannelID, resmgmt.WithRetry(retry.DefaultResMgmtOpts), resmgmt.WithOrdererEndpoint(setup.OrdererID))
	if err != nil {
		return errors.WithMessage(err,"Failed to join channel!")
	}
	fmt.Println("Joined channel!")
	fmt.Println("Initialize Successful!")
	return nil


}


//安装链代码以及实例化方法
func (setup *FabricSetup) InstallAndInstantiate() error {
	//准备安装链代码的参数
	ccPkg, err := gopackager.NewCCPackage(setup.ChaincodePath, setup.ChaincodeGoPath)
	if err != nil {
		return errors.WithMessage(err, "Chaincode packager error")
	}
	installRequest := resmgmt.InstallCCRequest{
		Name:    setup.ChainCodeID,
		Path:    "chaincode",
		Version: "1.0",
		Package: ccPkg,
	}
	//安装链代码
	_, err = setup.resMgmtClient.InstallCC(installRequest)
	if err != nil {
		fmt.Println("chaincode install error")
	}
	fmt.Println("chanincode install success")
	//实例化链代码,实例化之前需要制定一个背书策略
	ccpolity := cauthdsl.SignedByAnyMember([]string{"OrgSchoolMSP"})
	txID, err := setup.resMgmtClient.InstantiateCC(
		setup.ChannelID,
		resmgmt.InstantiateCCRequest{
			Name:    setup.ChainCodeID,
			Path:    setup.ChaincodeGoPath,
			Version: "1.0",
			Args:    nil,
			Policy:  ccpolity,
		},
	)
	if err != nil || txID.TransactionID == "" {
	fmt.Println("Instantiate chaincode error!")
		return err
	}
	fmt.Println("chaincode instantiate success!")
    //实例化完成之后就可以创建一个客户端client来操作账本数据了
	clientContext := setup.sdk.ChannelContext(setup.ChannelID, fabsdk.WithUser(setup.UserName))
	setup.client, err = channel.New(clientContext)
	if err != nil {
		return errors.WithMessage(err, "failed to create new channel client")
	}
	fmt.Println("Channel client created")

	fmt.Println("Chaincode Installation & Instantiation Successful")
	return nil

}


//资源释放方法
func (setup *FabricSetup) CloseSDK() {
	setup.sdk.Close()
}

       再接下来我们创建fabricinit.go文件,完成关于FabricSetup结构体的初始化工作,代码如下:

package models

import (
	"fmt"
	"os"
)

var App Application

//init一旦加载,所有与创建sdk相关的一些配置就被初始化
func init(){
	fSetup := FabricSetup{
		ConfigFile: "conf/schoolconfig.yaml",
		OrgAdmin: "Admin",
		UserName: "User1",
		OrgName: "OrgSchool",
		ChainCodeID: "unionchaincode",
		ChaincodePath: "school-credit/chaincode/union",
		ChaincodeGoPath: os.Getenv("GOPATH"),
		ChannelID: "unionchannel",
		ChannelConfig: "/home/surface/workspace/src/school-credit/conf/channel-artifacts/unionchannel.tx",
		OrdererID: "OrgSchoolMSP",
	}
	//调用方法初始化sdk,创建通道,加入通道
	err := fSetup.Initialize()
	if err != nil {
		fmt.Printf("Unable Initizlize SDK,%v!\n",err)
		return
	}
	//调用方法安装链码以及实例化链码
	err = fSetup.InstallAndInstantiate()
	if err != nil {
		fmt.Printf("Unable InstallAndInstantiate,%v!\n",err)
	}
	defer fSetup.CloseSDK()

	App = Application{
		FabricSetup : &fSetup,
	}
}

       现在我们已经可以在model层利用SDK在其所对应的channel中调用链码的方法了,我们以添加学生信息为例,创建addStuInfo.go文件,其中代码如下:

package models

import "github.com/hyperledger/fabric-sdk-go/pkg/client/channel"

func (this *Application) AddStuInfo(args []string) (string, error) {
	//定义二维byte数组转换字符数组类型作为channel.Request的fcn参数
	var tempArgs [][]byte
	for i :=1; i < len(args); i++ {
		tempArgs = append(tempArgs, []byte(args[i]))
	}

	request := channel.Request{ChaincodeID: this.FabricSetup.ChainCodeID, Fcn: args[0], Args: tempArgs}
	response, err := this.FabricSetup.client.Execute(request)
	if err != nil {
		//添加失败
		return"", err
	}
	//添加成功
	return  string(response.TransactionID), nil
}

       完成model层方法的编写,我们则可以在controller层调用它,完成数据的添加和查询操作。此时我们完善controller层的代码,如下:

func (this *MainController) AddStuInfo() {
	//获取前端传入的数据
	StuName := this.GetString("StuName")
	StuSex := this.GetString("StuSex")
	NativePlace := this.GetString("NativePlace")
	Birthday := this.GetString("Birthday")
	Nation := this.GetString("Nation")
	IDNum := this.GetString("IDNum")
	//判断ID是否为空
	if IDNum == "" {
		return
	}
	AdmissionDate := this.GetString("AdmissionDate")
	GraduationTime := this.GetString("GraduationTime")
	SchName := this.GetString("SchName")
	SubName := this.GetString("SubName")
	//将参数放入字符串数组
	var args []string
	args = append(args, "addStuInfo")
	args = append(args, StuName)
	args = append(args, StuSex)
	args = append(args, NativePlace)
	args = append(args, Birthday)
	args = append(args, Nation)
	args = append(args, IDNum)
	args = append(args, AdmissionDate)
	args = append(args, GraduationTime)
	args = append(args, SchName)
	args = append(args, SubName)
    //TODO 调用model层函数实现数据上链
	ret, err := models.App.AddStuInfo(args)
	if err != nil {
		fmt.Println("AddStuInfo  error...")
	}
	fmt.Println("<---  添加学生个人信息结果  --->", ret)
}

       至此我们的后端代码已经编写完成,可以将其部署在服务器上通过web端访问,查看是否跑通,后续我们将会利用vue进行简单的前端页面设计。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值