区块链系统开发测试----链码部署开发、系统开发验证

news2024/11/26 6:51:35

一.检查配置环境

检查虚拟机环境,确保有正在运行的Hyperledger Fabric区块链,并且其中chaincode_basic、credit_chaincode链码可以正常调用

查看chaincode_basic、credit_chaincode链码调用

二.开发征信链码代码 

基于现有征信链码,开发征信链码的升级版,使用chaincode-init文件夹中的基础链码模板,创建完善其中lib目录以及index.js内容,在lib中创建CreditPlusContract对象,在对象中添加createCreditSubjectPlus功能,实现征信主体的保存,定义subject变量属性包括(key:征信主体主键,  organizationName:征信主体评价机构名, type:评价类型,score:征信积分,creator:创建人,datetime:评价时间),其中属性除score外其余都为string类型。将subject内容上链保存  

在链码中添加征信主体查询功能(queryCreditSubjectPlus),要求能够查询所有主体内容(包括:key:征信主体主键,  organizationName:征信主体评价机构名, type:评价类型,score:征信积分,creator:创建人,datetime:评价时间)

'use strict';
const { Contract } = require("fabric-contract-api");
class CreditPlusContract extends Contract {
    async createCreditSubjectPlus(ctx, key, organizationName, type, creator, datetime) {
        console.info('=== START:创建征信主体 ===');
        const subject = {
            key: key,
            organizationName: organizationName,
            type: type,
            score: 0,
            creator: creator,
            datetime: datetime
        };
        await ctx.stub.putState(key, Buffer.from(JSON.stringify(subject)));
        console.info('=== END:创建征信主体 ===');
        return subject;
    }
    // 查询征信主体
    async queryCreditSubjectPlus(ctx, subjectKey) {
        console.info('=== START : 查询征信主体 ===');
        const bytes = await ctx.stub.getState(subjectKey);
        if (!bytes || bytes.length === 0) {
            const msg = `${subjectKey} 征信主体不存在`;
            console.warn(msg);
            throw new Error(msg);
        }
        const subject = JSON.parse(bytes.toString());
        console.info('=== END : 查询征信主体 ===');
        return subject;
    }
    
}

module.exports = CreditPlusContract;

 在链码结构中test目录中添加对于createCreditSubjectPlus以及QueryCreditSubjectPlus功能的单元测试,提交测试代码和验证结果。

'use strict';
const sinon = require('sinon');
const chai = require('chai');
const sinonChai = require('sinon-chai');
const expect = chai.expect;
chai.use(sinonChai);
let assert = sinon.assert;
const { Context } = require('fabric-contract-api');
const { ChaincodeStub, ClientIdentity } = require('fabric-shim');
const CreditPlusContract = require('../lib/creditContract');
describe('Credit Chaincode Test', () => {
    let stub, ctx, ClientId;

    beforeEach(() => {
        ctx = new Context();
        stub = sinon.createStubInstance(ChaincodeStub);
        stub.getMspID.returns('Org1');
        ctx.setChaincodeStub(stub);

        ClientId = sinon.createStubInstance(ClientIdentity);

        stub.putState.callsFake((key, value) => {
            if (!stub.states) {
                stub.states = {};
            }
            stub.states[key] = value;
        });
        stub.getState.callsFake(async (key) => {
            let ret;
            if (stub.states) {
                ret = stub.states[key];
            }
            return Promise.resolve(ret);
        });
        stub.deleteState.callsFake(async (key) => {
            if (stub.states) {
                delete stub.states[key];
            }
        });
    });
    describe('Test CreditSubject function', () => {
        it('should return success on createCreditSubject', async () => {
            let creditContract = new CreditPlusContract();
            let creditSubject = await creditContract.createCreditSubjectPlus(ctx, "A001", "My Company", "Company","Admin","2024-05-28 14:35:00");
            let scroe = creditSubject.score;
            expect(scroe).to.equals(0);
        });
        it('should return success on queryCreditSubject', async () => {
            let creditContract = new CreditPlusContract();
            await creditContract.createCreditSubjectPlus(ctx, "A001", "My Company", "Company", "Admin", "2024-05-28 14:35:00");
            let creditSubject = await creditContract.queryCreditSubjectPlus(ctx, "A001");
            let name = creditSubject.organizationName;
            expect(name).to.equals("My Company");
        });
    });
})

测试代码:

 

三.部署征信链码

在虚拟机指定Hyperledger Fabric中实现对应链码的部署并验证部署情况。

将链码名修改切credit_chaincode_plus,删除node_modules目录以及对应package-lock.json文件,上传至服务器chaincode目录下:

打包测试:

export FABRIC_CFG_PATH=${PWD}/config

peer lifecycle chaincode package ./chaincode/credit_chaincode_plus.tar.gz --path ./chaincode/credit_chaincode_plus --lang node --label credit_chaincode_plus_1.0

 

查看打包结果:

安装链码:运行以下进入fabric-cli容器:

docker exec -it fabric-cli bash

1. 在org1中安装:运行以下链码安装:

. scripts/set-env.sh 1 0 7051
peer lifecycle chaincode install chaincode/credit_chaincode_plus.tar.gz

 

2. 在org2中安装,运行以下链码安装:

. scripts/set-env.sh 2 0 9051
peer lifecycle chaincode install chaincode/credit_chaincode_plus.tar.gz

 

3. 查看安装情况

peer lifecycle chaincode queryinstalled

 

批准链码
org1批准链码

1. 设置链码环境变量

export CC_PACKAGE_ID=credit_chaincode_plus_1.0:9415a0be8812a91c2e510619a4d2a6a5cd06a8cf8f9cef96ee4eec2f456ab7ec

2. 设置Org1环境变量

. scripts/set-env.sh 1 0 7051

3.批准链码

peer lifecycle chaincode approveformyorg -o orderer.example.com:7050 --ordererTLSHostnameOverride orderer.example.com --channelID $CHANNEL_NAME --name credit_chaincode_plus --version 1.0 --package-id $CC_PACKAGE_ID --sequence 1 --tls --cafile $ORDERER_CA

 

1. 设置Org2环境变量

. scripts/set-env.sh 2 0 9051

2.批准链码

peer lifecycle chaincode approveformyorg -o orderer.example.com:7050 --ordererTLSHostnameOverride orderer.example.com --channelID $CHANNEL_NAME --name credit_chaincode_plus --version 1.0 --package-id $CC_PACKAGE_ID --sequence 1 --tls --cafile $ORDERER_CA

 

检查提交准备

peer lifecycle chaincode checkcommitreadiness --channelID $CHANNEL_NAME --name credit_chaincode_plus --version 1.0 --sequence 1 --tls --cafile $ORDERER_CA --output json

 

提交链码

peer lifecycle chaincode commit -o orderer.example.com:7050 --ordererTLSHostnameOverride orderer.example.com --channelID $CHANNEL_NAME --name credit_chaincode_plus --version 1.0 --sequence 1 --tls --cafile $ORDERER_CA --peerAddresses peer0.org1.example.com:7051 --tlsRootCertFiles $PEER0_ORG1_CA --peerAddresses peer0.org2.example.com:9051 --tlsRootCertFiles $PEER0_ORG2_CA

 

查询提交的链码

peer lifecycle chaincode querycommitted --channelID $CHANNEL_NAME --name credit_chaincode_plus --tls --cafile $ORDERER_CA

 

查看运行镜像形成容器情况

docker logs -f b1ddd21303a5

 

调用createCreditSubject功能

peer chaincode invoke -o orderer.example.com:7050 --tls --cafile $ORDERER_CA --channelID $CHANNEL_NAME --name credit_chaincode_plus --peerAddresses peer0.org1.example.com:7051 --tlsRootCertFiles $PEER0_ORG1_CA --peerAddresses peer0.org2.example.com:9051 --tlsRootCertFiles $PEER0_ORG2_CA -c '{"function":"createCreditSubjectPlus", "Args":["A001", "My Company", "Company","Admin","2024-05-28 14:35:00"]}'

 

调用queryCreditSubject功能

peer chaincode query -C $CHANNEL_NAME --name credit_chaincode_plus -c '{"function":"queryCreditSubjectPlus","Args":["A001"]}'

 

四.部署删除和更新代码 

在链码中添加DeleteCreditSubject,实现按subject变量的key属性删除数据功能

在链码中添加UpdateSubjectScore功能,实现按key更新subject,更新subject变量中的score

'use strict';

const { Contract } = require("fabric-contract-api");

class CreditPlusContract extends Contract {
    async createCreditSubjectPlus(ctx, key, organizationName, type, creator, datetime) {
        console.info('=== START:创建征信主体 ===');
        const subject = {
            key: key,
            organizationName: organizationName,
            type: type,
            score: 0,
            creator: creator,
            datetime: datetime
        };
        await ctx.stub.putState(key, Buffer.from(JSON.stringify(subject)));
        console.info('=== END:创建征信主体 ===');
        return subject;
    }
    // 查询征信主体
    async queryCreditSubjectPlus(ctx, subjectKey) {
        console.info('=== START : 查询征信主体 ===');
        const bytes = await ctx.stub.getState(subjectKey);
        if (!bytes || bytes.length === 0) {
            const msg = `${subjectKey} 征信主体不存在`;
            console.warn(msg);
            throw new Error(msg);
        }
        const subject = JSON.parse(bytes.toString());
        console.info('=== END : 查询征信主体 ===');
        return subject;
    }
    async deleteCreditSubject(ctx, id) {
        const exists = await this.creditSubjectExists(ctx, id);
        if (!exists) {
            throw new Error(`The asset ${id} does not exist`);
        }
        return ctx.stub.deleteState(id);
    }

    async creditSubjectExists(ctx, id) {
        const assetJSON = await ctx.stub.getState(id);
        return assetJSON && assetJSON.length > 0;
    }
    async updateSubjectScore(ctx,subjectKey,inputScore) {
        const exists = await this.creditSubjectExists(ctx,subjectKey);
        if (!exists) {
            throw new Error(`The credit subject ${subjectKey} does not exist`);
        }
        const bytes = await ctx.stub.getState(subjectKey);
        if (!bytes || bytes.length ===0){
            const msg = `${subjectKey} 征信主体不存在`;
            console.warn(msg)
            throw new Error (msg);
        }
        var subject = JSON.parse(bytes.toString());
        subject.score=inputScore;
        return ctx.stub.putState(subjectKey, Buffer.from(JSON.stringify(subject)));
    }
    
}

module.exports = CreditPlusContract;

在链码结构中test目录中添加对于DeleteCreditSubject以及UpdateSubjectScore功能的单元测试 

 

'use strict';
const sinon = require('sinon');
const chai = require('chai');
const sinonChai = require('sinon-chai');
const expect = chai.expect;
chai.use(sinonChai);
let assert = sinon.assert;
const { Context } = require('fabric-contract-api');
const { ChaincodeStub, ClientIdentity } = require('fabric-shim');
const CreditPlusContract = require('../lib/creditContract');
describe('Credit Chaincode Test', () => {
    let stub, ctx, ClientId;

    beforeEach(() => {
        ctx = new Context();
        stub = sinon.createStubInstance(ChaincodeStub);
        stub.getMspID.returns('Org1');
        ctx.setChaincodeStub(stub);

        ClientId = sinon.createStubInstance(ClientIdentity);

        stub.putState.callsFake((key, value) => {
            if (!stub.states) {
                stub.states = {};
            }
            stub.states[key] = value;
        });
        stub.getState.callsFake(async (key) => {
            let ret;
            if (stub.states) {
                ret = stub.states[key];
            }
            return Promise.resolve(ret);
        });
        stub.deleteState.callsFake(async (key) => {
            if (stub.states) {
                delete stub.states[key];
            }
        });
    });
    describe('Test CreditSubject function', () => {
        it('should return success on createCreditSubject', async () => {
            let creditContract = new CreditPlusContract();
            let creditSubject = await creditContract.createCreditSubjectPlus(ctx, "A001", "My Company", "Company","Admin","2024-05-28 14:35:00");
            let scroe = creditSubject.score;
            expect(scroe).to.equals(0);
        });
        it('should return success on queryCreditSubject', async () => {
            let creditContract = new CreditPlusContract();
            await creditContract.createCreditSubjectPlus(ctx, "A001", "My Company", "Company", "Admin", "2024-05-28 14:35:00");
            let creditSubject = await creditContract.queryCreditSubjectPlus(ctx, "A001");
            let name = creditSubject.organizationName;
            expect(name).to.equals("My Company");
        });
        it('should return sucess on DeleteCreditSubject', async () => {
            let creditContract = new CreditPlusContract();
            await creditContract.createCreditSubjectPlus(ctx, "A001", "My Company", "Company", "Admin", "2024-05-28 14:35:00");
            await creditContract.deleteCreditSubject(ctx,"A001");
            let ret = await stub.getState('A001');
            expect(ret).to.equal(undefined);
        });
        it('should return sucess on updateSubjectScore', async () => {
            let creditContract = new CreditPlusContract();
            await creditContract.createCreditSubjectPlus(ctx, "A001", "My Company", "Company", "Admin", "2024-05-28 14:35:00");
            await creditContract.updateSubjectScore(ctx, "A001",10);
            let ret = JSON.parse(await stub.getState('A001'));
            expect(ret.score).to.eql(10);
        });
    });
})

测试代码:

 

五.更新部署征信链码

在链码中重新部署开发链码,实现链码更新(要求version、sequence有迭代痕迹)

打包测试:

export FABRIC_CFG_PATH=${PWD}/config

peer lifecycle chaincode package ./chaincode/credit_chaincode_plus.tar.gz --path ./chaincode/credit_chaincode_plus --lang node --label credit_chaincode_plus_1.1

 

安装链码
运行以下进入fabric-cli容器:

docker exec -it fabric-cli bash

1. 在org1中安装 | 运行以下链码安装:

. scripts/set-env.sh 1 0 7051
peer lifecycle chaincode install chaincode/credit_chaincode_plus.tar.gz

 

2. 在org2中安装
运行以下链码安装:

. scripts/set-env.sh 2 0 9051
peer lifecycle chaincode install chaincode/credit_chaincode_plus.tar.gz

 

3. 查看安装情况

peer lifecycle chaincode queryinstalled

 

三.批准链码
org1批准链码

设置链码环境变量

export CC_PACKAGE_ID=credit_chaincode_plus_1.1:a7d65de21f706c25029b84cbbf7de2163d06d9b8e9fc33fd20a5a59cc3a18b01

2. 设置Org1环境变量

. scripts/set-env.sh 1 0 7051

3.批准链码

peer lifecycle chaincode approveformyorg -o orderer.example.com:7050 --ordererTLSHostnameOverride orderer.example.com --channelID $CHANNEL_NAME --name credit_chaincode_plus --version 1.1 --package-id $CC_PACKAGE_ID --sequence 2 --tls --cafile $ORDERER_CA

 

1. 设置Org2环境变量

. scripts/set-env.sh 2 0 9051

2.批准链码

peer lifecycle chaincode approveformyorg -o orderer.example.com:7050 --ordererTLSHostnameOverride orderer.example.com --channelID $CHANNEL_NAME --name credit_chaincode_plus --version 1.1 --package-id $CC_PACKAGE_ID --sequence 2 --tls --cafile $ORDERER_CA

 

检查提交准备

peer lifecycle chaincode checkcommitreadiness --channelID $CHANNEL_NAME --name credit_chaincode_plus --version 1.1 --sequence 2 --tls --cafile $ORDERER_CA --output json

 

提交链码

peer lifecycle chaincode commit -o orderer.example.com:7050 --ordererTLSHostnameOverride orderer.example.com --channelID $CHANNEL_NAME --name credit_chaincode_plus --version 1.1 --sequence 2 --tls --cafile $ORDERER_CA --peerAddresses peer0.org1.example.com:7051 --tlsRootCertFiles $PEER0_ORG1_CA --peerAddresses peer0.org2.example.com:9051 --tlsRootCertFiles $PEER0_ORG2_CA

 

查询提交的链码

peer lifecycle chaincode querycommitted --channelID $CHANNEL_NAME --name credit_chaincode_plus --tls --cafile $ORDERER_CA

 

查看运行镜像形成容器情况

docker logs -f b1ddd21303a5

 

调用createCreditSubject功能

peer chaincode invoke -o orderer.example.com:7050 --tls --cafile $ORDERER_CA --channelID $CHANNEL_NAME --name credit_chaincode_plus --peerAddresses peer0.org1.example.com:7051 --tlsRootCertFiles $PEER0_ORG1_CA --peerAddresses peer0.org2.example.com:9051 --tlsRootCertFiles $PEER0_ORG2_CA -c '{"function":"createCreditSubjectPlus", "Args":["A001", "My Company", "Company","Admin","2024-05-28 14:35:00"]}'

调用queryCreditSubject功能

peer chaincode query -C $CHANNEL_NAME --name credit_chaincode_plus -c '{"function":"queryCreditSubjectPlus","Args":["A001"]}'

调用deleteCreditSubject功能

peer chaincode invoke -o orderer.example.com:7050 --tls --cafile $ORDERER_CA --channelID $CHANNEL_NAME --name credit_chaincode_plus --peerAddresses peer0.org1.example.com:7051 --tlsRootCertFiles $PEER0_ORG1_CA --peerAddresses peer0.org2.example.com:9051 --tlsRootCertFiles $PEER0_ORG2_CA -c '{"function":"deleteCreditSubject", "Args":["A001"]}'

 

调用updateSubjectScore功能(这边如果嫌麻烦的话可以更新功能之后再调用删除功能)

peer chaincode invoke -o orderer.example.com:7050 --tls --cafile $ORDERER_CA --channelID $CHANNEL_NAME --name credit_chaincode_plus --peerAddresses peer0.org1.example.com:7051 --tlsRootCertFiles $PEER0_ORG1_CA --peerAddresses peer0.org2.example.com:9051 --tlsRootCertFiles $PEER0_ORG2_CA -c '{"function":"updateSubjectScore", "Args":["A002","10"]}'

 

 


 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

迁移基于MicroBlaze处理器的设计

迁移基于MicroBlaze处理器的设计 生成系统基础设施(MicroBlaze、AXI_Interconnect, Clk_Wiz、Proc_Sys_Reset) 生成系统基础设施(MicroBlaze、AXI_Interconnect、Clk_Wiz和 Proc_Sys_Reset): 1.使用所需的板…

Media Encoder 2024 for Mac媒体编码器安装教程ME2024安装包下载

安装 步骤 1,双击打开下载好的安装包。 2,选择install ame_24...双击打开启动安装程序。 3,点击install。 4,输入电脑密码。 5,软件安装中... 6,安装结束点击好。 7,返回打开的镜像 选择激活补…

力扣HOT100 - 1143. 最长公共子序列

解题思路&#xff1a; 动态规划 class Solution {public int longestCommonSubsequence(String text1, String text2) {int m text1.length(), n text2.length();int[][] dp new int[m 1][n 1];for (int i 1; i < m; i) {char c1 text1.charAt(i - 1);for (int j 1…

Window VScode配置Conda教程(成功版)

VScode配置Conda 参考博文&#xff1a;https://blog.csdn.net/qq_51831335/article/details/126757014Anaconda安装&#xff08;注意勾选自动配置环境变量&#xff01;&#xff09; 官网&#xff1a;https://www.anaconda.com/download/success VScode配置 python插件安装安装 …

makefile一些特殊且常用的符号

$^&#xff1a;表示所有的依赖文件列表&#xff0c;多个文件以空格分隔。 $&#xff1a;表示目标文件的名称。 $<&#xff1a;表示第一个依赖文件的名称。 $*&#xff1a;表示目标文件的主文件名&#xff08;不包括扩展名&#xff09;。 $?&#xff1a;表示所有比目标文件更…

【AI算法岗面试八股面经【超全整理】——机器学习】

AI算法岗面试八股面经【超全整理】 概率论信息论机器学习深度学习CVNLP 目录 1、回归损失函数2、分类损失函数3、误差&#xff08;Error&#xff09;、偏差&#xff08;Bias&#xff09;、方差&#xff08;Variance&#xff09;4、PCA&#xff08;Principle Component Analysi…

Golang协程和通道

文章目录 协程&#xff08;goroutine&#xff09;基本介绍GMP模型协程间共享变量 通道&#xff08;channel&#xff09;基本介绍channel的定义方式channel的读写channel的关闭channel的遍历方式只读/只写channelchannel最佳案例select语句 协程&#xff08;goroutine&#xff0…

springboot项目部署到linux服务器

springboot后端 修改前 修改后 vue前端 修改前 将地址中的 localhost改为 ip 重新生成war包 war上传到linux的tomcat的webapps下 其他环境配置和macOS大差不差 Tomcat安装使用与部署Web项目的三种方法_tomcat部署web项目-CSDN博客

回文链表(快慢指针解法之在推进过程中反转)

归纳编程学习的感悟&#xff0c; 记录奋斗路上的点滴&#xff0c; 希望能帮到一样刻苦的你&#xff01; 如有不足欢迎指正&#xff01; 共同学习交流&#xff01; &#x1f30e;欢迎各位→点赞 &#x1f44d; 收藏⭐ 留言​&#x1f4dd;抱怨深处黑暗&#xff0c;不如提灯前行…

海顺新材将携手LG化学,开启人类更美好未来的“零”碳之旅

继与东华大学成立先进低维材料中心后&#xff0c;海顺新材在可持续发展方向再响重鼓&#xff0c;与LG化学创新单一材质达成初步合作意向&#xff0c;未来&#xff0c;双方将有望在环保膜材领域展开合作。 自“双碳”目标提出以来&#xff0c;全球经济出现一项很重要的特征&…

MySQL进阶之(九)数据库的设计规范

九、数据库的设计规范 9.1 范式的概念9.1.1 范式概述9.1.2 键和相关属性 9.2 常见的范式9.2.1 第一范式9.2.2 第二范式9.2.3 第三范式9.2.4 第四范式9.2.5 第五范式&#xff08;域键范式&#xff09; 9.3 反范式化9.3.1 概述9.3.2 举例9.3.3 反范式化新问题9.3.4 通用场景 9.4 …

K210 数字识别 教程

一、烧写固件 连接k210开发板&#xff0c;点开烧录固件工具&#xff0c;选中固件&#xff0c;并下载 二、模型训练 网站&#xff1a;MaixHub 1、上传文件 2、开始标记数据 添加9个标签&#xff0c;命名为1~9&#xff0c;按键盘w开始标记&#xff0c;键盘D可以下一张图片&…

解读makefile中的.PHONY

在 Makefile 中&#xff0c;.PHONY 是一个特殊的目标&#xff0c;用于声明伪目标&#xff08;phony target&#xff09;。伪目标是指并不代表实际构建结果的目标&#xff0c;而是用来触发特定动作或命令的标识。通常情况下&#xff0c;.PHONY 会被用来声明一组需要执行的动作&a…

利用迭代方法求解线性方程组(Matlab)

一、问题描述 利用迭代方法求解线性方程组。 二、实验目的 掌握Jacobi 方法和Gauss-Seidel 方法的原理&#xff0c;能够编写代码实现两种迭代方法&#xff1b;能够利用代码分析线性方程组求解中的误差情况。 三、实验内容及要求 用代码实现&#xff1a;对下列方程中重新组织…

@ConfigurationProperties结合Nacos配置动态刷新之底层原理分析

Hello&#xff0c;我是大都督周瑜&#xff0c;本文给大家分析一下ConfigurationProperties结合Nacos配置动态刷新的底层原理&#xff0c;记得点赞、关注、分享哦&#xff01; 公众号&#xff1a;IT周瑜 应用背景 假如在Nacos中有Data ID为common.yml的配置项&#xff1a; m…

Pytorch 1.9.0环境安装

pytorch官方链接: https://pytorch.org/get-started/previous-versions/ 安装指令&#xff1a;conda install pytorch1.9.0 torchvision0.10.0 torchaudio0.9.0 cudatoolkit11.3 -c pytorch -c conda-forge 报错&#xff1a;Solving environment: unsuccessful initial attemp…

「Python Socket超能力:网络世界的隐形斗篷!」

Hi&#xff0c;我是阿佑&#xff0c;今天将带领大家揭开Python Socket编程的神秘面纱&#xff0c;赋予我们的网络应用隐形斗篷般的超能力&#xff01; 深入探讨Socket编程的革命性力量&#xff0c;教你如何用Python的Socket模块来构建强大的网络应用。从简单的HTTP服务器到复杂…

高效编写大模型 Prompt 提示词,解锁 AI 无限创意潜能

随着 ChatGPT 的出现&#xff0c;AI 成为新的焦点&#xff0c;有人说过“未来 50%的工作将是提示词工作”&#xff0c;目前很多公司也在开始招聘 Prompt 提示词工程师。Prompt&#xff08;提示词&#xff09;成为了连接创意与技术的桥梁&#xff0c;它不仅是简单的指令&#xf…

Grafana详解

目录 ​编辑 一、Grafana的主要特点 二、Grafana的基本功能 三、Grafana的使用方法 Grafana是一款开源的数据可视化工具&#xff0c;主要用于大规模指标数据的可视化展现。下面将详细介绍Grafana的特点、功能以及基本使用方法。 一、Grafana的主要特点 跨平台性&#xff…

在Ubuntu系统中使用Systemctl添加启动项的详细指南

在Ubuntu系统中使用Systemctl添加启动项的详细指南 在Ubuntu系统中&#xff0c;systemctl 是管理systemd服务的主要工具。通过它&#xff0c;你可以添加、启动、停止、重启、启用和禁用服务。 什么是Systemctl&#xff1f; systemctl 是一个用于管理systemd系统和服务管理器…