长安链使用Golang编写智能合约教程(二)

本篇说的是长安链2.3.+的版本的智能合约,虽然不知道两者有什么区别,但是编译器区分。

教程三会写一些,其他比较常用SDK方法的解释和使用方法


编写前的注意事项:

1、运行一条带有Doker_GoVM的链

2、建议直接用官方的在线IDE去写合约,因为写完可以直接测,缺点只是调试不方便。

3、如果自己拉环境在本地写合约,编译时注意编译环境,官方有提醒你去Linux下去编译

4、如果你的链是2.3.+,使用编译器前,请先切换到2.3.+,以防不测


1、首先去新建一个合约工程

这里选择空白模板,其他模板好像是一些web3的规范模板

2、打开main.go文件(有一份示例合约)

/*
Copyright (C) BABEC. All rights reserved.
Copyright (C) THL A29 Limited, a Tencent company. All rights reserved.

SPDX-License-Identifier: Apache-2.0
*/

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"strconv"

	"chainmaker/pb/protogo"
	"chainmaker/sandbox"
	"chainmaker/sdk"
)

type FactContract struct {
}

// 存证对象
type Fact struct {
	FileHash string 
	FileName string 
	Time     int 
}

// 新建存证对象
func NewFact(fileHash string, fileName string, time int) *Fact {
	fact := &Fact{
		FileHash: fileHash,
		FileName: fileName,
		Time:     time,
	}
	return fact
}

func (f *FactContract) InitContract() protogo.Response {
	return sdk.Success([]byte("Init contract success"))
}

func (f *FactContract) UpgradeContract() protogo.Response {
	return sdk.Success([]byte("Upgrade contract success"))
}

func (f *FactContract) InvokeContract(method string) protogo.Response {
	switch method {
	case "save":
		return f.Save()
	case "findByFileHash":
		return f.FindByFileHash()
	default:
		return sdk.Error("invalid method")
	}
}

func (f *FactContract) Save() protogo.Response {
	params := sdk.Instance.GetArgs()

	// 获取参数
	fileHash := string(params["file_hash"])
	fileName := string(params["file_name"])
	timeStr := string(params["time"])
	time, err := strconv.Atoi(timeStr)
	if err != nil {
		msg := "time is [" + timeStr + "] not int"
		sdk.Instance.Errorf(msg)
		return sdk.Error(msg)
	}

	// 构建结构体
	fact := NewFact(fileHash, fileName, time)

	// 序列化
	factBytes, err := json.Marshal(fact)
	if err != nil {
		return sdk.Error(fmt.Sprintf("marshal fact failed, err: %s", err))
	}
	// 发送事件
	sdk.Instance.EmitEvent("topic_vx", []string{fact.FileHash, fact.FileName})

	// 存储数据
	err = sdk.Instance.PutStateByte("fact_bytes", fact.FileHash, factBytes)
	if err != nil {
		return sdk.Error("fail to save fact bytes")
	}

	// 记录日志
	sdk.Instance.Infof("[save] fileHash=" + fact.FileHash)
	sdk.Instance.Infof("[save] fileName=" + fact.FileName)

	// 返回结果
	return sdk.Success([]byte(fact.FileName + fact.FileHash))

}

func (f *FactContract) FindByFileHash() protogo.Response {
	// 获取参数
	fileHash := string(sdk.Instance.GetArgs()["file_hash"])

	// 查询结果
	result, err := sdk.Instance.GetStateByte("fact_bytes", fileHash)
	if err != nil {
		return sdk.Error("failed to call get_state")
	}

	// 反序列化
	var fact Fact
	if err = json.Unmarshal(result, &fact); err != nil {
		return sdk.Error(fmt.Sprintf("unmarshal fact failed, err: %s", err))
	}

	// 记录日志
	sdk.Instance.Infof("[find_by_file_hash] fileHash=" + fact.FileHash)
	sdk.Instance.Infof("[find_by_file_hash] fileName=" + fact.FileName)

	// 返回结果
	return sdk.Success(result)
}

func main() {
	err := sandbox.Start(new(FactContract))
	if err != nil {
		log.Fatal(err)
	}
}

3、示例模板解析

21行:这里的FactContract是合约名称,对应的要和125行main函数err := sandbox.Start(new(FactContract))一致

type FactContract struct {
}

25行:Fact结构体就是要存在区块链中的结构体,根据你自己的需要去变更结构体的字段

type Fact struct {
    FileHash string 
    FileName string 
    Time     int 
}

32行:新建存证对象,根据前面25行Fact 结构体的变化而变化

func NewFact(fileHash string, fileName string, time int) *Fact {
    fact := &Fact{
        FileHash: fileHash,
        FileName: fileName,
        Time:     time,
    }
    return fact
}

41-47行:InitContract、UpgradeContract两个方法,不用动,这是实现合约必须要的两个方法,用于合约初始化和合约升级

func (f *FactContract) InitContract() protogo.Response {
    return sdk.Success([]byte("Init contract success"))
}

func (f *FactContract) UpgradeContract() protogo.Response {
    return sdk.Success([]byte("Upgrade contract success"))
}

49行:InvokeContract是调用合约的方法,根据你的合约种有多少方法,依葫芦画瓢在case ....return 继续补充就行

func (f *FactContract) InvokeContract(method string) protogo.Response {
    switch method {
    case "save":
        return f.Save()
    case "findByFileHash":
        return f.FindByFileHash()
    default:
        return sdk.Error("invalid method")
    }
}

 60行: Save(存证方法)

以下大部分依葫芦画瓢就好了,重点关注以下内容:

66-72行:做了time字段的字符校验,确保是数字

84行:发送事件函数EmitEvent,第一个参数是合约事件主题,第二个参数是合约事件参数、(注意合约事件的数据,参数数量不可大于16写了事件、订阅之后可以监听到事件状态

87行:存储数据函数sdk.Instance.PutStateByte,三个参数 key、field 、value   ,(原本我以为弄个key-value的存储参数就行了,为什么官方要弄个field,我也不理解,但是官方有解释,不过用长安链就遵从他的规则吧)  这里就是说key是一个命名空间,相当于一个域,真正的key是一个拼接串,value是存证的内容。

93、94行:记录日志,可记可不记,写了的话,节点的日志记录会存下来

97行:返回要遵从官方规范

func (f *FactContract) Save() protogo.Response {
    params := sdk.Instance.GetArgs()

    // 获取参数
    fileHash := string(params["file_hash"])
    fileName := string(params["file_name"])
    timeStr := string(params["time"])
    time, err := strconv.Atoi(timeStr)
    if err != nil {
        msg := "time is [" + timeStr + "] not int"
        sdk.Instance.Errorf(msg)
        return sdk.Error(msg)
    }

    // 构建结构体
    fact := NewFact(fileHash, fileName, time)

    // 序列化
    factBytes, err := json.Marshal(fact)
    if err != nil {
        return sdk.Error(fmt.Sprintf("marshal fact failed, err: %s", err))
    }
    // 发送事件
    sdk.Instance.EmitEvent("topic_vx", []string{fact.FileHash, fact.FileName})

    // 存储数据
    err = sdk.Instance.PutStateByte("fact_bytes", fact.FileHash, factBytes)
    if err != nil {
        return sdk.Error("fail to save fact bytes")
    }

    // 记录日志
    sdk.Instance.Infof("[save] fileHash=" + fact.FileHash)
    sdk.Instance.Infof("[save] fileName=" + fact.FileName)

    // 返回结果
    return sdk.Success([]byte(fact.FileName + fact.FileHash))

}

 取证方法:

以下大部分依葫芦画瓢就好了,重点关注以下内容:

  • 134行:取证的方法:sdk.Instance.GetStateByte ,这里的“fact_bytes”就是这个合约的域,所以这里填写的要和你在存证中填写的域一致才行。
  • 其他按照规范以葫芦画瓢

func (f *FactContract) FindByFileHash() protogo.Response {
    // 获取参数
    fileHash := string(sdk.Instance.GetArgs()["file_hash"])

    // 查询结果
    result, err := sdk.Instance.GetStateByte("fact_bytes", fileHash)
    if err != nil {
        return sdk.Error("failed to call get_state")
    }

    // 反序列化
    var fact Fact
    if err = json.Unmarshal(result, &fact); err != nil {
        return sdk.Error(fmt.Sprintf("unmarshal fact failed, err: %s", err))
    }

    // 记录日志
    sdk.Instance.Infof("[find_by_file_hash] fileHash=" + fact.FileHash)
    sdk.Instance.Infof("[find_by_file_hash] fileName=" + fact.FileName)

    // 返回结果
    return sdk.Success(result)
}

125行:main函数,这个new合约对象的时候保证名称和最开始21行的结构体名称一样就行了,其他的不用变。

func main() {
    err := sandbox.Start(new(FactContract))
    if err != nil {
        log.Fatal(err)
    }
}



4、新增一个查询历史数据的方法

以上就是main.go模板内容的解析,但是一般情况下,我们还有查询历史数据的需求,模板没有提供,所以这里根据模板继续补充一个查询历史记录的方法:

func (f *FactContract) GetHistoryByFileHash() protogo.Response {
	// 获取参数
	fileHash := string(sdk.Instance.GetArgs()["file_hash"])

	// 查询结果
	iter, err := sdk.Instance.NewHistoryKvIterForKey("fact_bytes", fileHash)
	if err != nil {
		return sdk.Error("failed to delere get_state")
	}
	defer iter.Close()
	var keyModifications []*sdk.KeyModification
	// 遍历结果
	for {
		if !iter.HasNext() {
			break
		}
		keyModification, err := iter.Next()
		if err != nil {
			sdk.Instance.Infof("Error iterating: %v", err)
		}
		if keyModification == nil {
			break
		}

		keyModifications = append(keyModifications, keyModification)

	}

	jsonBytes, err := json.Marshal(keyModifications)
	if err != nil {
		return sdk.Error(fmt.Sprintf("Error marshaling keyModifications: %v", err))
	}

	// 返回结果
	return sdk.Success(jsonBytes)
}

方法解析:

这里我只解释重点步骤

1、调用查询历史数据接口

iter, err := sdk.Instance.NewHistoryKvIterForKey("fact_bytes", fileHash)

返回值类型:

后面就是根据返回值的结构进行遍历,

var keyModifications []*sdk.KeyModification

把结果放在 keyModifications  然后进行序列化,返回

 5、新增一个删除方法

注意,这里虽然是一个删除方法,但是不是真的删除,只有有一个isDelete的字段,如果调用了这个方法,某个域对应的hash会被标记为删除。代码不在解释

func (f *FactContract) DeleteByFileHash() protogo.Response {
	// 获取参数
	fileHash := string(sdk.Instance.GetArgs()["file_hash"])

	// 查询结果
	err := sdk.Instance.DelState("fact_bytes", fileHash)
	if err != nil {
		return sdk.Error("failed to delere get_state")
	}

	// 返回结果
	return sdk.Success(nil)
}

6、完整合约

/*
Copyright (C) BABEC. All rights reserved.
Copyright (C) THL A29 Limited, a Tencent company. All rights reserved.

SPDX-License-Identifier: Apache-2.0
*/

package main

import (
	"chainmaker/pb/protogo"
	"chainmaker/sandbox"
	"chainmaker/sdk"
	"encoding/json"
	"fmt"
	"log"
	"strconv"
)

type FactContract struct {
}

// 存证对象
type Fact struct {
	FileHash string
	FileName string
	Time     int
}

// 新建存证对象
func NewFact(fileHash string, fileName string, time int) *Fact {
	fact := &Fact{
		FileHash: fileHash,
		FileName: fileName,
		Time:     time,
	}
	return fact
}

func (f *FactContract) InitContract() protogo.Response {
	return sdk.Success([]byte("Init contract success"))
}

func (f *FactContract) UpgradeContract() protogo.Response {
	return sdk.Success([]byte("Upgrade contract success"))
}

func (f *FactContract) InvokeContract(method string) protogo.Response {
	switch method {
	case "save":
		return f.Save()
	case "findByFileHash":
		return f.FindByFileHash()
	case "deltedByFileHash":
		return f.DeleteByFileHash()
	case "getHistoryByFileHash":
		return f.GetHistoryByFileHash()
	default:
		return sdk.Error("invalid method")
	}
}

func (f *FactContract) Save() protogo.Response {
	params := sdk.Instance.GetArgs()

	// 获取参数
	fileHash := string(params["file_hash"])
	fileName := string(params["file_name"])
	timeStr := string(params["time"])
	time, err := strconv.Atoi(timeStr)
	if err != nil {
		msg := "time is [" + timeStr + "] not int"
		sdk.Instance.Errorf(msg)
		return sdk.Error(msg)
	}

	// 构建结构体
	fact := NewFact(fileHash, fileName, time)

	// 序列化
	factBytes, err := json.Marshal(fact)
	if err != nil {
		return sdk.Error(fmt.Sprintf("传过来的参数序列化失败, err: %s", err))
	}
	// 发送事件
	sdk.Instance.EmitEvent("topic_vx", []string{fact.FileHash, fact.FileName})

	// 存储数据
	err = sdk.Instance.PutStateByte("fact_bytes", fact.FileHash, factBytes)
	if err != nil {
		return sdk.Error("fail to save fact bytes")
	}

	// 记录日志
	// sdk.Instance.Infof("[save] fileHash=" + fact.FileHash)
	// sdk.Instance.Infof("[save] fileName=" + fact.FileName)
	createUser, _ := sdk.Instance.GetSenderRole()
	sdk.Instance.Infof("[saveUser] create=" + createUser)

	// 返回结果
	return sdk.Success([]byte(fact.FileName + fact.FileHash))

}

func (f *FactContract) FindByFileHash() protogo.Response {
	// 获取参数
	fileHash := string(sdk.Instance.GetArgs()["file_hash"])

	// 查询结果
	result, err := sdk.Instance.GetStateByte("fact_bytes", fileHash)
	if err != nil {
		return sdk.Error("failed to call get_state")
	}

	// 反序列化
	var fact Fact
	if err = json.Unmarshal(result, &fact); err != nil {
		return sdk.Error(fmt.Sprintf("unmarshal fact failed, err: %s", err))
	}

	// 记录日志
	sdk.Instance.Infof("[find_by_file_hash] fileHash=" + fact.FileHash)
	sdk.Instance.Infof("[find_by_file_hash] fileName=" + fact.FileName)

	// 返回结果
	return sdk.Success(result)
}

func (f *FactContract) DeleteByFileHash() protogo.Response {
	// 获取参数
	fileHash := string(sdk.Instance.GetArgs()["file_hash"])

	// 查询结果
	err := sdk.Instance.DelState("fact_bytes", fileHash)
	if err != nil {
		return sdk.Error("failed to delere get_state")
	}

	// 返回结果
	return sdk.Success(nil)
}

func (f *FactContract) GetHistoryByFileHash() protogo.Response {
	// 获取参数
	fileHash := string(sdk.Instance.GetArgs()["file_hash"])

	// 查询结果
	iter, err := sdk.Instance.NewHistoryKvIterForKey("fact_bytes", fileHash)
	if err != nil {
		return sdk.Error("failed to delere get_state")
	}
	defer iter.Close()
	var keyModifications []*sdk.KeyModification
	// 遍历结果
	for {
		if !iter.HasNext() {
			break
		}
		keyModification, err := iter.Next()
		if err != nil {
			sdk.Instance.Infof("Error iterating: %v", err)
		}
		if keyModification == nil {
			break
		}

		keyModifications = append(keyModifications, keyModification)

		sdk.Instance.Infof("Key: %s, Field: %s, Value: %s, TxId: %s, BlockHeight: %d, IsDelete: %t, Timestamp: %s, \n",
			keyModification.Key, keyModification.Field, keyModification.Value, keyModification.TxId, keyModification.BlockHeight, keyModification.IsDelete, keyModification.Timestamp)
	}

	jsonBytes, err := json.Marshal(keyModifications)
	if err != nil {
		return sdk.Error(fmt.Sprintf("Error marshaling keyModifications: %v", err))
	}

	// 返回结果
	return sdk.Success(jsonBytes)
}

func main() {
	err := sandbox.Start(new(FactContract))
	if err != nil {
		log.Fatal(err)
	}
}

7、结果展示

1、部署demo2合约

2、发起上链,上链了3条数据,其中file_hash我都是输入的1

3、查询某一个结果

4、删除一个结果

这也是一个上链操作

5、查询历史结果

这里没有格式化,现在拿去专门json格式化一下

[
    {
        "Key": "fact_bytes",
        "Field": "1",
        "Value": "eyJGaWxlSGFzaCI6IjEiLCJGaWxlTmFtZSI6IjIiLCJUaW1lIjozfQ==",
        "TxId": "254071cabbca415186ae64956644d2230be111ebb94144d79f168a2252995a88",
        "BlockHeight": 14,
        "IsDelete": false,
        "Timestamp": "1716864398"
    },
    {
        "Key": "fact_bytes",
        "Field": "1",
        "Value": "eyJGaWxlSGFzaCI6IjEiLCJGaWxlTmFtZSI6IjIiLCJUaW1lIjozfQ==",
        "TxId": "ce705bbbaedb4315858b4e68b6331f4a947c3eb5a262433dbe2823ad3c87ee06",
        "BlockHeight": 15,
        "IsDelete": false,
        "Timestamp": "1716864410"
    },
    {
        "Key": "fact_bytes",
        "Field": "1",
        "Value": "eyJGaWxlSGFzaCI6IjEiLCJGaWxlTmFtZSI6IjIiLCJUaW1lIjozfQ==",
        "TxId": "6e099f7ce81543bf8fd0bf565f741478de53b4bc72834112a8bc0bc5f06f9a47",
        "BlockHeight": 16,
        "IsDelete": false,
        "Timestamp": "1716864421"
    },
    {
        "Key": "fact_bytes",
        "Field": "1",
        "Value": "",
        "TxId": "5f76c217063e41ad8c0f2b4ab3fae2418d784c9f0ade416b94715e95214acfc5",
        "BlockHeight": 17,
        "IsDelete": true,
        "Timestamp": "1716864504"
    }
]

value就是存证的字符串,但是这里是base64编码,转码结果如下

(注意:这里你会发现所有的value都是一样的,因为是我存证的数据都是输入的一样的)

转码结果如下:




8、个人理解

  • 官方文档有错:官方文档把key叫做命名空间取了一个固定值,field作为存证hash,这里应该是他们写反了,field 才有域、空间的意思。上面的解析,我还是按照官方错误的来说的。因为他最后存在level_db中的key是拼接的,所以写反写没事,都一样。你可以改成对的。
  • 之所以引入一个命名空间的概念,我猜测应该是为了在数据库中方便查看,因为同一份id可能会被存多次,加了一个空间会方便区分。不过官方也提供了没有命名空间的存储方法PutStateFromKey(key string, value string) error  就是教程一用的方法
  • 删除函数,并不是真的删除,会新上链一条数据,标明某个域、某个key被删除了,但是已经上链的数据不会变动。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/653613.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2024-2030数据集成成熟度曲线(一)

作者 | 郭炜 导读:最新发布的《技术成熟度曲线2024》全面评估数据集成技术架构的7个维度,包括技术成熟度、技术难度、业务价值、技术成熟周期、管理协作难度、大模型结合等评估维度,报告篇幅较长,我们将报告分为3篇系列文章&#…

企业微信H5授权登录

在企业中如果需要在打开的网页里面携带用户的身份信息,第一步需要获取code参数 如何实现企业微信H5获取当前用户信息即accessToken? 1.在应用管理--》创建应用 2.创建好应用,点击应用主页-》设置-》网页-》将授权链接填上去 官方文档可以看…

配餐中的红酒温度控制与口感体验

在红酒配餐中,温度控制是影响口感体验的重要因素之一。合适的温度可以释放红酒的香气和风味,使酒体更加圆润和丰富。云仓酒庄雷盛红酒以其卓着的品质和与众不同的口感,成为了红酒爱好者们的首要选择品牌。下面将介绍如何通过温度控制提升红酒…

哈夫曼树,哈夫曼编码和线索二叉树

前言 在数据压缩中,如电脑中的压缩软件,哈夫曼编码应用比较广泛,因此被称作最优二叉树。下面时哈夫曼树的一些定义。 哈夫曼树 定义 代码 下面时哈夫曼树的初始化和创建: #include "stdio.h"#define MAXSIZE 5 typedef struct {int weigth;int parent, lchi…

2024年最新实景无人自动直播软件揭秘:降低成本,提升效率

在信息技术迅猛发展的时代,实景无人自动直播软件成为了各行各业的关注焦点。随着2024年的到来,最新的实景无人自动直播软件将以其降低成本、提升效率的特点引领行业。本文将揭秘这一创新软件的工作原理,并探讨其在各个领域的应用,…

常用图像分类预训练模型大小及准确度比较

近年来,深度学习技术的发展使得图像分类任务变得越来越容易。预训练模型的出现更是使得图像分类任务变得更加简单和高效。然而,随着预训练模型的数量和大小的增加,我们需要了解每个模型的特点和优缺点,以便更好地选择和使用它们。…

国内半导体龙头企业的自动化转型之旅

在当今高速发展的科技时代,半导体行业正迎来前所未有的挑战与机遇。位于此浪潮前端的,是国内一家领先的半导体集成电路封装测试企业。凭借其规模和创新实力,该公司不仅在国内市场名列前茅,更是在全球半导体行业中占据了一席之地。…

.DFS.

DFS 全称为Depth First Search,中文称为深度优先搜索。 这是一种用于遍历或搜索树或图的算法,其思想是: 沿着每一条可能的路径一个节点一个节点地往下搜索, 直到路径的终点,然后再回溯,直到所有路径搜索完为止。 DFS俗…

排序算法——上

一、冒泡排序: 1、冒泡排序算法的思想 我们从左边开始把相邻的两个数两两做比较,当一个元素大于右侧与它相邻的元素时,交换它们之间位置;反之,它们之间的位置不发生变化。冒泡排序是一种稳定的排序算法。 2、代码实现…

可燃气体报警器检测周期:如何合理设定以满足安全需求?

可燃气体报警器作为工业安全和生产环境中不可或缺的安全防护设备,其准确性、稳定性和及时响应性对于防止火灾和爆炸事故具有重要意义。 因此,合理设定并严格执行可燃气体报警器的检测周期,是确保安全与可靠运行的核心环节。 一、检测周期的重…

远程户外监控组网方案,工业4G路由器ZR2000

户外监控无人值守4G工业路由器组网应用涉及工业自动化、数据传输和远程监控的重要领域。在户外没有光纤的情况下,想要让监控或传感器等设备联网,仅需一台4G工业路由器即可解决。以下是关于远程监控户外组网的详细分析与应用: 物联网应用场景 …

Python 机器学习 基础 之 模型评估与改进 【评估指标与评分】的简单说明

Python 机器学习 基础 之 模型评估与改进 【评估指标与评分】的简单说明 目录 Python 机器学习 基础 之 模型评估与改进 【评估指标与评分】的简单说明 一、简单介绍 二、评估指标与评分 1、牢记最终目标 2、二分类指标 1)错误类型 2)不平衡数据集…

MySQL触发器实战:自动执行的秘密

欢迎来到我的博客,代码的世界里,每一行都是一个故事 🎏:你只管努力,剩下的交给时间 🏠 :小破站 MySQL触发器实战:自动执行的秘密 前言触发器的定义和作用触发器的定义和作用触发器的…

质量源于设计QbD培训的内容有哪些?

质量源于设计QbD培训的内容丰富而深入,旨在帮助企业深入理解并应用QbD理念,提升产品质量和客户满意度。以下是质量源于设计QbD培训的主要内容: 首先,培训将详细介绍QbD的基本概念、核心内容和实施流程。QbD是一种集成的方法&#…

大学搜题软件音乐类?分享三个支持答案和解析的工具 #微信#媒体

高效的学习工具可以帮助我们提高记忆力和理解能力,使知识更加深入人心。 1.彩虹搜题 这是个微信公众号 一款专门供全国大学生使用的查题神器!致力于帮助大学生解决学习上的难题,涵盖了大学生学习所需的学习资料。 下方附上一些测试的试题及答案 1、甲、乙合伙开…

python办公自动化——(二)替换PPT文档中图形数据-柱图

效果: 数据替换前 : 替换数据后: 实现代码 import collections.abc from pptx import Presentation from pptx.util import Cm,Pt import pyodbc import pandas as pd from pptx.chart.data import CategoryChartData…

OKR 实践:来自一位信息技术部主管的成功秘诀

OKR 实践:来自一位信息技术部主管的成功秘诀 为什么选择OKR 公司信息技术部为38个各地分公司、12,000名员工的IT需求提供服务。庞大而多样的客户群常常使我们的团队分散,许多团队都在各自为政,以个案为基础解决问题,而不是采用企业…

抖店的注意事项,2024新版,照做即可!

我是王路飞。 如果你想做抖店,但还没开通抖店的话,那这篇文章算你刷着了。 先别着急开店,把这篇文章看完,再开店也不迟,能帮你少走很多开店、做店阶段的弯路。 内容来源于【电商王路飞】 第一个注意事项&#xff0c…

基于瑞萨RA6M5的自控衣橱

1. 主控转接板原理图和PCB设计 2. 屏幕界面设计 3. 程序设计 4. QT设计 QT设计,读取MQTT数据,在QT上显示衣橱内部的温度,湿度情况,且能够控制衣橱的开关门,开关灯等。 5. 实物演示 瑞萨

Spring Boot中如何查询PGSQL分表后的数据

数据库用的pgsql,在表数据超过100w条的时候执行定时任务进行了分表,分表后表名命名为原的表名后面拼接时间,如原表名是card_device_trajectory_info,分表后拼接时间后得到card_device_trajectory_info_20240503,然后分…