Golang项目中使用captcha作为验证码调用（captcha不止图片验证，一下以图片验证为例）先说说验证码调用思路：以注册为例在前端请求后端获取验证图片，等待验证，后端返回图片和对应的id(标识这个图片的唯一标识符)，在前端进行验证等待验证完毕生成对应的验证值，在注册的时候一并提交到后端进行判断，后端先进行判断传进来的验证码的参数等是否正确在进行判断注册流程（例如接下来在判断用户名是否存在等）这里使用bee go进行演示，其他的框架例如gin或原生go都大同小异生成验证码的内容func (c Captcha) GetImg() { fmt.Println("ok") capt := captcha.GetCaptcha() // 生成验证码 dots, b64, tb64, key, err := capt.Generate() if err != nil { panic(err) return } // 主图base64 fmt.Println(len(b64)) //fmt.Println("base 64 : ", b64) // 缩略图base64 fmt.Println(len(tb64)) fmt.Println("base 64 : ", tb64) // 唯一key fmt.Println(key) // 文本位置验证数据 fmt.Println(dots) }生成验证码将唯一标识符写入用于判断可以看到这里写入的方法是写入一个json文件中，这里作为演示但实际使用中较多的是存入Redis中func writeCache(v interface{}, file string) { bt, _ := json.Marshal(v) month := time.Now().Month().String() cacheDir := getCacheDir() + month + "/" _ = os.MkdirAll(cacheDir, 0660) file = cacheDir + file + ".json" logFile, _ := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644) defer logFile.Close() // 检查过期文件 并清理 checkCacheOvertimeFile() _, _ = io.WriteString(logFile, string(bt)) }检查过期文件进行清理防止过多文件占用大量内存/** * @Description: 检查缓存超时文件， 30分钟 */ func checkCacheOvertimeFile() { files, files1, _ := listDir(getCacheDir()) for _, table := range files1 { temp, _, _ := listDir(table) for _, temp1 := range temp { files = append(files, temp1) } } for _, file := range files { t := GetFileCreateTime(file) ex := time.Now().Unix() - t if ex > (60 * 30) { _ = os.Remove(file) } } }//接口方法 func (c Captcha) GetCaptchaData() { fmt.Println("获取验证码") capt := captcha.GetCaptcha() dots, b64, tb64, key, err := capt.Generate() if err != nil { bt, _ := json.Marshal(map[string]interface{}{ "code": 1, "message": "GenCaptcha err", }) c.Ctx.WriteString(string(bt)) return } writeCache(dots, key) bt, _ := json.Marshal(map[string]interface{}{ "code": 0, "image_base64": b64, "thumb_base64": tb64, "captcha_key": key, }) c.Ctx.WriteString(string(bt)) }验证验证码若通过校验则返回 0验证验证码代码：func (c Captcha) CheckCaptcha() { fmt.Println("验证验证码") code := 1 dots := c.GetString("dots") key := c.GetString("key") if dots == "" || key == "" { bt, _ := json.Marshal(map[string]interface{}{ "code": code, "message": "dots or key param is empty", }) c.Ctx.WriteString(string(bt)) } cacheData := readCache(key) if cacheData == "" { bt, _ := json.Marshal(map[string]interface{}{ "code": code, "message": "illegal key", }) c.Ctx.WriteString(string(bt)) } src := strings.Split(dots, ",") var dct map[int]captcha.CharDot if err := json.Unmarshal([]byte(cacheData), &dct); err != nil { bt, _ := json.Marshal(map[string]interface{}{ "code": code, "message": "illegal key", }) c.Ctx.WriteString(string(bt)) } chkRet := false if (len(dct) * 2) == len(src) { for i, dot := range dct { j := i * 2 k := i*2 + 1 sx, _ := strconv.ParseFloat(fmt.Sprintf("%v", src[j]), 64) sy, _ := strconv.ParseFloat(fmt.Sprintf("%v", src[k]), 64) // 检测点位置 // chkRet = captcha.CheckPointDist(int64(sx), int64(sy), int64(dot.Dx), int64(dot.Dy), int64(dot.Width), int64(dot.Height)) // 校验点的位置,在原有的区域上添加额外边距进行扩张计算区域,不推荐设置过大的padding // 例如：文本的宽和高为30，校验范围x为10-40，y为15-45，此时扩充5像素后校验范围宽和高为40，则校验范围x为5-45，位置y为10-50 chkRet = captcha.CheckPointDistWithPadding(int64(sx), int64(sy), int64(dot.Dx), int64(dot.Dy), int64(dot.Width), int64(dot.Height), 5) if !chkRet { break } } } if chkRet { // 通过校验 code = 0 } bt, _ := json.Marshal(map[string]interface{}{ "code": code, }) c.Ctx.WriteString(string(bt)) }前端核心调用代码/** * 处理请求验证码 */ handleRequestCaptCode() { this.captBase64 = '' this.captThumbBase64 = '' this.captKey = '' this.$axios({ method: 'get', url: '/api/api/go_captcha_data', }).then((response) => { const { data = {} } = response; if ((data['code'] || 0) === 0) { if (this.$lodash.isEmpty(data)) { return } this.captBase64 = data['image_base64'] || '' this.captThumbBase64 = data['thumb_base64'] || '' this.captKey = data['captcha_key'] || '' } else { this.$message({ message: `获取人机验证数据失败`, type: 'warning' }) } }) }/** * 处理验证码校验请求 */ handleConfirm(dots) { if (this.$lodash.size(dots) <= 0) { this.$message({ message: `请进行人机验证再操作`, type: 'warning' }) return } let dotArr = [] this.$lodash.forEach(dots, (dot) => { dotArr.push(dot.x, dot.y) }) this.$axios({ headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, method: 'post', url: '/api/api/go_captcha_check_data', data: Qs.stringify({ dots: dotArr.join(','), key: this.captKey }) }).then((response) => { const { data = {} } = response; if ((data['code'] || 0) === 0) { this.$message({ message: `人机验证成功`, type: 'success' }) this.captStatus = 'success' this.captAutoRefreshCount = 0 } else { this.$message({ message: `人机验证失败`, type: 'warning' }) if (this.captAutoRefreshCount > 5) { this.captAutoRefreshCount = 0 this.captStatus = 'over' return } this.handleRequestCaptCode() this.captAutoRefreshCount += 1 this.captStatus = 'error' } }) }到这里就讲完了，可以在验证阶段的时候把验证方法加入到你的注册(或其他)接口中，先进行验证验证码是否正确在进行接下来的注册操作
FISCO BCOS使用GO SDK设置合约调用者设置合约调用者合约代码：用于实现如set时需要的管理员账户，get可以查询当前管理员地址pragma solidity ^0.4.25; contract tranceUser{ address owner; constructor(){ owner = msg.sender; } function set(address _a) public returns(bool) { require(owner == msg.sender,"owner can do it"); owner = _a; return true; } function get() view public returns(string memory){ return addressToString(owner); } function addressToString(address _address) internal pure returns (string memory) { bytes32 value = bytes32(uint256(uint160(_address))); bytes memory alphabet = "0123456789abcdef"; bytes memory str = new bytes(42); str[0] = '0'; str[1] = 'x'; for (uint256 i = 0; i < 20; i++) { str[2+i*2] = alphabet[uint256((value[i + 12] & 0xF0) >> 4)]; str[3+i*2] = alphabet[uint256(value[i + 12] & 0x0F)]; } return string(str); } } 案例方法一：在合约部署前进行加载如果需要设置部署合约调用者的话需要在部署合约时进行加载 fromAddress := "0x1f2b33660b0fa7ca7f3aec0cb674edeeb9228126" // 输入指定的账户地址 privateKey := "b5f1563c6f8a9792a065de6da30268ed7ce84e3e505a349de3a1b6846b8e6190" // 替换为正确的私钥 // 解析私钥 key, err := crypto.HexToECDSA(privateKey) if err != nil { fmt.Println("私钥解析错误:", err) return } // 创建新的TransactOpts对象，并设置From属性和签名者 auth := bind.NewKeyedTransactor(key) auth.From = common.HexToAddress(fromAddress) //部署合约时传入auth即可 _, _, instance, err := store.DeployKVTableTest(auth, client) session := store.KVTableTestSession{Contract: instance, CallOpts: *client.GetCallOpts(), TransactOpts: *client.GetTransactOpts()}案例方法二：例如有个单独的功能需要不同的用户调用有不同的效果只需要在实例化的时候进行传入即可，需要传入账户地址和私钥func transKey() { fromAddress := "0x1f2b33660b0fa7ca7f3aec0cb674edeeb9228126" // 输入指定的账户地址 privateKey := "9e139fba57613ee036c59321788cf52a09ea4271b6982a4a73e2228b522a77c8" // 替换为正确的私钥 //解析私钥 key, err := crypto.HexToECDSA(privateKey) if err != nil { fmt.Println("私钥解析错误:", err) return } //// 创建新的TransactOpts对象，并设置From属性和签名者 auth := bind.NewKeyedTransactor(key) auth.From = common.HexToAddress(fromAddress) files, _ := conf.ParseConfigFile("config.toml") client, _ := client.Dial(&files[0]) instance, _ := tranceUser.NewTranceUser(common.HexToAddress("0x57cdd9f703447d59d93f7dca163f4f86a6c3a2b0"), client) session := tranceUser.TranceUserSession{Contract: instance, CallOpts: *client.GetCallOpts(), TransactOpts: *auth} get, _ := session.Get() fmt.Println("修改前：" + get) address := common.HexToAddress("0x2a7a07be060902b931754dba742165e27fdfe5b0") session.Set(address) get, _ = session.Get() fmt.Println("修改后：" + get) }
FISCO BCOS GO SDK 使用教程go控制台使用控制台使用 在使用控制台需要先拉取代码或下载代码，然后对配置文件config.toml进行更改: 拉取代码并编译 git clone https://github.com/FISCO-BCOS/go-sdk.git //git clone https://gitee.com/FISCO-BCOS/go-sdk.git cd go-sdk go mod tidy go build cmd/console.go配置密钥地址成功Package功能使用以下的示例是通过import的方式来使用go-sdk，如引入RPC控制台库:import "github.com/FISCO-BCOS/go-sdk/client"Solidity合约编译为Go文件在利用SDK进行项目开发时，对智能合约进行操作时需要将Solidity智能合约利用go-sdk的abigen工具转换为Go文件代码。整体上主要包含了五个流程：准备需要编译的智能合约配置好相应版本的solc编译器构建go-sdk的合约编译工具abigen编译生成go文件使用生成的go文件进行合约调用下面的内容作为一个示例进行使用介绍。1.提供一份简单的样例智能合约Store.sol如下:pragma solidity ^0.4.25; contract Store { event ItemSet(bytes32 key, bytes32 value); string public version; mapping (bytes32 => bytes32) public items; constructor(string _version) public { version = _version; } function setItem(bytes32 key, bytes32 value) external { items[key] = value; emit ItemSet(key, value); } }2.安装对应版本的solc编译器，目前FISCO BCOS默认的solc编译器版本为0.4.25。# 如果是国密则添加-g选项 bash tools/download_solc.sh -v 0.4.253.构建go-sdk的代码生成工具abigen# 下面指令在go-sdk目录下操作，编译生成abigen工具 go build ./cmd/abigen执行命令后，检查根目录下是否存在abigen，并将准备的智能合约Store.sol放置在一个新的目录下：mkdir ./store mv Store.sol ./store4.编译生成go文件，先利用solc将合约文件生成abi和bin文件，以前面所提供的Store.sol为例：# 国密请使用 ./solc-0.4.25-gm --bin --abi -o ./store ./store/Store.sol ./solc-0.4.25 --bin --abi -o ./store ./store/Store.solStore.sol目录下会生成Store.bin和Store.abi。此时利用abigen工具将Store.bin和Store.abi转换成Store.go：# 国密请使用 ./abigen --bin ./store/Store.bin --abi ./store/Store.abi --pkg store --type Store --out ./store/Store.go --smcrypto=true ./abigen --bin ./store/Store.bin --abi ./store/Store.abi --pkg store --type Store --out ./store/Store.go最后store目录下面存在以下文件：Store.abi Store.bin Store.go Store.sol5.调用生成的Store.go文件进行合约调用至此，合约已成功转换为go文件，用户可根据此文件在项目中利用SDK进行合约操作。具体的使用可参阅下一节。部署智能合约创建main函数，调用Store合约，touch store_main.go下面的例子先部署合约，在部署过程中设置的Store.sol合约中有一个公开的名为version的全局变量，这种公开的成员将自动创建getter函数，然后调用Version()来获取version的值。写入智能合约需要我们用私钥来对交易事务进行签名，我们创建的智能合约有一个名为SetItem的外部方法，它接受soliditybytes32类型的两个参数（key，value）。 这意味着在Go文件中需要传递一个长度为32个字节的字节数组。package main import ( "fmt" "log" "github.com/FISCO-BCOS/go-sdk/client" "github.com/FISCO-BCOS/go-sdk/conf" "github.com/FISCO-BCOS/go-sdk/store" // import store ) func main(){ configs, err := conf.ParseConfigFile("config.toml") if err != nil { log.Fatalf("ParseConfigFile failed, err: %v", err) } client, err := client.Dial(&configs[0]) if err != nil { log.Fatal(err) } input := "Store deployment 1.0" address, tx, instance, err := store.DeployStore(client.GetTransactOpts(), client, input) if err != nil { log.Fatal(err) } fmt.Println("contract address: ", address.Hex()) // the address should be saved, will use in next example fmt.Println("transaction hash: ", tx.Hash().Hex()) // load the contract // contractAddress := common.HexToAddress("contract address in hex String") // instance, err := store.NewStore(contractAddress, client) // if err != nil { // log.Fatal(err) // } fmt.Println("================================") storeSession := &store.StoreSession{Contract: instance, CallOpts: *client.GetCallOpts(), TransactOpts: *client.GetTransactOpts()} version, err := storeSession.Version() if err != nil { log.Fatal(err) } fmt.Println("version :", version) // "Store deployment 1.0" // contract write interface demo fmt.Println("================================") key := [32]byte{} value := [32]byte{} copy(key[:], []byte("foo")) copy(value[:], []byte("bar")) tx, receipt, err := storeSession.SetItem(key, value) if err != nil { log.Fatal(err) } fmt.Printf("tx sent: %s\n", tx.Hash().Hex()) fmt.Printf("transaction hash of receipt: %s\n", receipt.GetTransactionHash()) // read the result result, err := storeSession.Items(key) if err != nil { log.Fatal(err) } fmt.Println("get item: " + string(result[:])) // "bar" }使用go-sdk写项目:可以进行新部署合约或者加载已经部署的 合约，方法一：部署合约 // 部署新合约方法 input := "Store deployment 1.0" address, tx, instance, err := store.DeployStore(client.GetTransactOpts(), client, input) if err != nil { log.Fatal(err) } fmt.Println("contract address: ", address.Hex()) // the address should be saved, will use in next example fmt.Println("transaction hash: ", tx.Hash().Hex())方法二：加载合约 ，可以在控制台/webase-front中部署获取合约地址// 加载这个合约 contractAddress := common.HexToAddress("0xe19fd49a363b7a2b61bc79f0b659b34623a4343f") instance, err = store.NewStore(contractAddress, client) if err != nil { log.Fatal(err) }进行加载合约（也可以说是实例化合约）//加载合约 storeSession := &store.StoreSession{Contract: instance, CallOpts: *client.GetCallOpts(), TransactOpts: *client.GetTransactOpts()}//调用合约功能 tx, receipt, err := storeSession.SetItem(key, value) if err != nil { log.Fatal(err) }完整调用代码package main import ( "fmt" "github.com/ethereum/go-ethereum/common" "log" "github.com/FISCO-BCOS/go-sdk/client" "github.com/FISCO-BCOS/go-sdk/conf" "github.com/FISCO-BCOS/go-sdk/store" // import store ) func main() { configs, err := conf.ParseConfigFile("config.toml") if err != nil { log.Fatalf("ParseConfigFile failed, err: %v", err) } client, err := client.Dial(&configs[0]) if err != nil { log.Fatal(err) } // // 部署新合约方法 // input := "Store deployment 1.0" // address, tx, instance, err := store.DeployStore(client.GetTransactOpts(), client, input) // if err != nil { // log.Fatal(err) // } // fmt.Println("contract address: ", address.Hex()) // the address should be saved, will use in next example // fmt.Println("transaction hash: ", tx.Hash().Hex()) // //load the contract contractAddress := common.HexToAddress("0xe19fd49a363b7a2b61bc79f0b659b34623a4343f") instance, err := store.NewStore(contractAddress, client) if err != nil { log.Fatal(err) } fmt.Println("================================") storeSession := &store.StoreSession{Contract: instance, CallOpts: *client.GetCallOpts(), TransactOpts: *client.GetTransactOpts()} version, err := storeSession.Version() if err != nil { log.Fatal(err) } fmt.Println("version :", version) // "Store deployment 1.0" // contract write interface demo // fmt.Println("================================") key := [32]byte{} value := [32]byte{} copy(key[:], []byte("foo")) copy(value[:], []byte("bar66")) tx, receipt, err := storeSession.SetItem(key, value) if err != nil { log.Fatal(err) } fmt.Printf("tx sent: %s\n", tx.Hash().Hex()) fmt.Printf("transaction hash of receipt: %s\n", receipt.GetTransactionHash()) // fmt.Println(key) // read the result result, err := storeSession.Items(key) if err != nil { log.Fatal(err) } fmt.Println("get item: " + string(result[:])) // "bar" }