Java sdk使用加载账户私钥调用合约

news2024/10/6 12:30:01

Java sdk使用加载账户私钥调用合约

1.智能合约案例

1.2 智能合约的流程

image-20230304233838681

1.2 智能合约详细代码

实现了一个简单的商店功能。它定义了三个结构体:用户、商家和商品,以及对应的映射关系。它提供了一些方法用于注册用户和商家,创建商品,更新用户余额,购买商品等等。它还包括一些修饰符,用于限制只有用户或商家可以调用特定的方法。用户购买商品主要涉及到的是,检测当前用户是否有用户的权限,商家生产出售商品检测当前的用户是否有商家的权限。

主要的三个结构体

  • User
  • Merchant
  • Commodity

三个结构体分别是用户、商家、商品。

具体业务关系如下:

  1. 用户注册和商家注册 该合约允许用户和商家进行注册,分别在 userMapmerchantMap 中将对应的用户地址和信息映射起来。
  2. 创建商品 商家可以调用 createCommodity 方法创建一种新的商品,包括商品名称、价格和数量,并将此商品信息存储在 commodityMapcommoditys 数组中,同时将商品数量映射到相应的商家地址(即 commodityToMerchantMap)上。
  3. 更新用户余额 用户可以通过 updateBalance 方法更新其账户余额。
  4. 购买商品 用户可以通过 buyCommodity 方法购买已有的商品,其中需要传入商品ID和购买数量。如果用户余额足够且商品库存充足,则会购买成功,同时商家收到相应的款项。
  5. 分页查询商品 用户可通过 queryAllCommoditys 方法查看所有商品并按照分页方式展示。
  6. 查询用户信息和商家信息 用户和商家都可以通过 queryUserInfoqueryMerchantInfo 方法查询自己的信息。
pragma solidity ^0.4.25;
pragma experimental ABIEncoderV2;


contract Shop {
    // 用户的结构体
    struct User {
        address userAddress;
        string  userName;
        uint256 userBalance;
        Role    role;
    }
    
    // 商家的结构体
    struct Merchant {
        address merchantAddress;
        uint256 merchantBalance;
        Role    role;
    }
    
    // 商品的结构体
    struct Commodity {
        uint256 commodityId;
        string  commodityName;
        uint256 commodityPrice;
        uint256 commodityQuantity;
    }
    
    // 资产ID
    uint256 public commodityCount;
    // 角色枚举
    enum Role { UserType,MerchantType }
    
    // 地址映射用户信息
    mapping(address => User) userMap;
    // 地址映射商家信息
    mapping(address => Merchant) merchantMap;
    // 商品ID映射商品信息
    mapping(uint256 => Commodity) commodityMap;
    // 商品ID映射售卖的商家
    mapping(uint256 => address) commodityToMerchantMap;
    
    // 存储所有商品
    Commodity[] commoditys;
    
    
    // 判断当前是不是用户
    modifier AuthUser(address _userAddress) {
        require(userMap[_userAddress].role == Role.UserType,"当前不是用户");
        _;
    }
    
    // 判断当前是不是商家
    modifier AuthMerchant(address _merchantAddress) {
        require(merchantMap[_merchantAddress].role == Role.MerchantType,"当前不是商家");
        _;
    }
    // 注册事件 
    event Registered(address indexed _Address);
	// 商家添加商品事件
	event CreateCommodity(address indexed _Address,string _name);
	// 用户更新余额事件
    event UpdateBalance(address indexed _Address,uint256 indexed _amount);
    // 用户购买商品事件
    event BuyCommodity(address indexed _Address,uint256 indexed _commodityId);
    
    // 企业注册
    function registerUser(
   		address _userAddress,
   		string _userName
   	) public {
        User memory user = User({
            userAddress: _userAddress,
            userName: _userName,
            userBalance: 0,
            role: Role.UserType
        });
        userMap[_userAddress] = user;
        emit Registered(_userAddress);
    }
    
    // 商家注册
    function registerMerchant(
   		address _merchantAddress,
   		uint256 _merchantBalance
   	) public {
        Merchant memory merchant = Merchant({
            merchantAddress: _merchantAddress,
            merchantBalance: _merchantBalance,
            role: Role.MerchantType
        }); 
        merchantMap[_merchantAddress] = merchant;
        emit Registered(_merchantAddress);
    }
    
    
    // 创建资产 
    function createCommodity(
    	string memory _name,
    	uint256 _price,
    	uint256 _quantity
    ) public AuthMerchant(msg.sender) {
        Commodity memory commodity = Commodity({
            commodityId: commodityCount,
            commodityName: _name,
            commodityPrice: _price,
            commodityQuantity: _quantity
        });
        commodityMap[commodityCount] = commodity;
        commoditys.push(commodity);
        commodityToMerchantMap[commodityCount] = msg.sender;
        commodityCount++;
        emit CreateCommodity(msg.sender,_name);
    }
    
    // 更新余额
    function updateBalance(
    	address _userAddress,
    	uint256 _amount
    ) public AuthUser(msg.sender) {
        userMap[_userAddress].userBalance += _amount;
        emit UpdateBalance(msg.sender,_amount);
    }
    
    // 用户购买一个物品
    function buyCommodity(
    	uint256 _commodityId,
    	uint256 _amount
    ) public AuthUser(msg.sender) {
        require(userMap[msg.sender].userBalance >= _amount,"当前余额不足");
        require(commodityMap[_commodityId].commodityQuantity != 0,"当前的物品没有库存");
        userMap[msg.sender].userBalance -= _amount;
        commodityMap[_commodityId].commodityQuantity--;
        merchantMap[commodityToMerchantMap[_commodityId]].merchantBalance += _amount;
        for (uint i = 0; i < commoditys.length; i++){
            if (commoditys[i].commodityId == _commodityId){
                commoditys[i].commodityQuantity--;
            }
        }
        emit BuyCommodity(msg.sender,_commodityId);
    }
    
    
    // 分页查询商品
    function queryAllCommoditys(
    	uint256 page,
    	uint256 pageSize
    ) public returns(Commodity[] memory) {
        require(page > 0, "页数不能为0");
        uint256 startIndex = (page - 1) * pageSize; // 计算起始索引
        uint256 endIndex = startIndex + pageSize > commoditys.length ? commoditys.length : startIndex + pageSize; // 计算结束索引
        Commodity[] memory CommodityArr = new Commodity[](endIndex - startIndex); // 创建每页大小的 Enterprise 数组
        for (uint i = startIndex; i < endIndex; i++){
            CommodityArr[i - startIndex] = commoditys[i];
        }
        return CommodityArr;
    }
    
    // 查询用户信息
    function queryUserInfo() public returns(
    	address,
    	string memory,
    	uint256,
    	Role
    ) {
        User memory user = userMap[msg.sender];
        return (user.userAddress,user.userName,user.userBalance,user.role);
    }
    
    // 查询商家信息
    function queryMerchantInfo() public AuthMerchant(msg.sender) returns(
    	address,
    	uint256,
    	Role
    )  {
        Merchant memory merchant = merchantMap[msg.sender];
        return (merchant.merchantAddress,merchant.merchantBalance,merchant.role);
    }
    
}

1.3 使用WeBASE-Front导出

使用基于FISCO BCOS区块链平台,以及WeBASE-Front部署智能合约,导出Java项目。

image-20230304234059356

使用IDEA查看当前的项目。

image-20230304234346997

2.Java SDK加载私钥调用合约

使用场景

Webase-front部署智能合约时,私钥的切换通常用于以下场景:

  1. 部署智能合约:在部署智能合约时,需要使用部署账户的私钥对合约进行签名,确保合约被正确地部署到区块链网络中。
  2. 调用智能合约:在调用智能合约时,需要使用调用账户的私钥对调用进行签名,确保调用请求的安全性和可信度。
  3. 管理智能合约:在管理智能合约时,可能需要使用不同的账户私钥进行操作,例如更新合约、授权其他账户对合约进行操作等。

在这些场景下,私钥的切换可以确保智能合约的安全性和可信度,同时也可以保护账户的私密信息不被泄露。

私钥存储的方式

  • Java SDK支持通过私钥字符串或者文件加载,所以账户的私钥可以存储在数据库中或者本地文件
  • 本地文件支持两种存储格式,其中PKCS12加密存储,而PEM格式明文存储。
  • 开发业务时可以根据实际业务场景选择私钥的存储管理方式。

这里我选择将私钥加密存储到数据库中,通过用户的地址去查询该地址的私钥或者公钥的方式,用户需要权限的时候,通过用户的地址获取私钥,通过加载用户的私钥传入调用合约的方法中。脱离默认的合约调用者。

针对Java SDK调用合约,有基于ABI和基于BIN的方式调用合约。

  • 官网链接-基于ABI和BIN的方式: https://fisco-bcos-documentation.readthedocs.io/zh_CN/v2.8.0/docs/sdk/java_sdk/assemble_transaction.html
  • 官网链接-账户管理: https://fisco-bcos-documentation.readthedocs.io/zh_CN/v2.8.0/docs/sdk/java_sdk/key_tool.html

如下是完整的项目结构图。

image-20230305153951516

2.1 创建数据库表存储公私钥

虽然把私钥和公钥存储在Mysql中,但是还是不算很安全,所以存储的时候需要加盐加密。

create database shop default character set utf8mb4;

create table shop_userKey(
    user_address varchar(60) comment '用户地址',
    user_privateKey text comment '用户私钥',
    user_publicKey text comment '用户公钥'
) comment '用户的公私钥表';

2.2 Gradlew添加依赖

在dependencies中添加相对应的依赖。

  • mybatis-plus
  • mysql-connector-java
  • fastjson
	compile group: 'com.baomidou', name: 'mybatis-plus-boot-starter', version: '3.5.0'
    implementation 'mysql:mysql-connector-java:8.0.27'
    implementation group: 'com.alibaba', name: 'fastjson', version: '2.0.24'
    implementation group: 'org.apache.directory.studio', name: 'org.apache.commons.codec', version: '1.8'

2.3 连接数据库

spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/shop?serverTimezone=UTC&useSSL=false

mybatis.configuration.cache-enabled=true
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
mybatis-plus.mapper-locations=classpath:/mapper/*.xml
mybatis-plus.global-config.id-type=auto
mybatis-plus.global-config.db-config.table-prefix=shop_

2.3 创建实体类对象

创建UserKey对象,主要是用来操作数据库。在用户注册的时候,需要给用户返回地址和公私钥,私钥仅提供一次下载。

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
@TableName(value = "shop_userKey")
public class UserKey {

    @TableField(value = "user_address")
    private String userAddress;
    @TableField(value = "user_privateKey")
    private String userPrivateKey;
    @TableField(value = "user_publicKey")
    private String userPublicKey;
}

创建User对象,用于返回前端页面用户的数据。

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class User {
    // 用户地址
    private String  userAddress;
    // 用户名称
    private String  userName;
    // 用户余额
    private int     userBalance;
    // 用户角色
    private int     role;
}

创建Merchant对象,用于返回前端页面商家的信息。

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.math.BigInteger;

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Merchant {
    
    // 商家地址
    private String  merchantAddress;
	// 商家余额
    private int 	merchantBalance;
	// 用户角色
    private int 	role;
}

创建Commodity的对象,用于返回前端页面的商品信息或者商品集合

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.math.BigInteger;

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Commodity {
    // 商品ID
    private  int commodityId;
    // 商品名称
    private  String  commodityName;
    // 商品价格
    private  int commodityPrice;
    // 商品数量
    private  int  commodityQuantity;
}

2.4 数据持久层

创建UserKeyMapper的接口,继承Mybatis-plus的接口,实现对数据库的CRUD操作。

这里主要是查询和删除两个操作,后续测试暂时需要用到这两个操作,如果有具体的场景还需要详细的操作。

@Mapper
public interface UserKeyMapper extends BaseMapper<UserKey> {
}


public interface UserKeyService extends IService<UserKey> {
    public UserKey selectByUserAddress(String address);

    public boolean deleteByUserAddress(String address);
}


@Service
public class UserKeyServiceImpl extends ServiceImpl<UserKeyMapper, UserKey> implements UserKeyService {

    @Autowired
    private UserKeyMapper userKeyMapper;
    @Override
    public UserKey selectByUserAddress(String address) {
        if (address.isEmpty()){
            return null;
        }
        LambdaQueryWrapper<UserKey> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserKey::getUserAddress, address);
        return userKeyMapper.selectOne(queryWrapper);
    }

    @Override
    public boolean deleteByUserAddress(String address) {
        if (address.isEmpty()){
            return false;
        }
        LambdaQueryWrapper<UserKey> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserKey::getUserAddress, address);
        userKeyMapper.delete(queryWrapper);
        return true;
    }
}

2.5 使用AES算法加密工具类

AES算法是一种对称密钥加密算法,用于保护数据的机密性。它使用相同的密钥进行加密和解密操作。在AES算法中,数据被分成固定长度的块,并通过多轮的密钥混合和替换运算来加密。解密则通过逆向的操作将密文转换为明文。AES算法以其高安全性、高效性和广泛应用而受到广泛认可。

package org.example.Shop.utils;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.util.Base64;

public class EncryptionUtils {

    private static final String ALGORITHM = "AES"; // 加密算法
    private static final String TRANSFORMATION = "AES/ECB/PKCS5Padding"; // 加密模式

    private static final String KEY = "MySecretKey12345";
    /**
     * 加密数据
     *
     * @param data 待加密的数据
     * @param KEY 密钥
     * @return 加密后的数据
     */
    public static String encrypt(String data) throws Exception {
        Key secretKey = new SecretKeySpec(KEY.getBytes(StandardCharsets.UTF_8), ALGORITHM);
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);
        byte[] encryptedBytes = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(encryptedBytes);
    }

    /**
     * 解密数据
     *
     * @param encryptedData 加密后的数据
     * @param KEY 密钥
     * @return 解密后的数据
     */
    public static String decrypt(String encryptedData) throws Exception {
        Key secretKey = new SecretKeySpec(KEY.getBytes(StandardCharsets.UTF_8), ALGORITHM);
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        cipher.init(Cipher.DECRYPT_MODE, secretKey);
        byte[] decodedBytes = Base64.getDecoder().decode(encryptedData);
        byte[] decryptedBytes = cipher.doFinal(decodedBytes);
        return new String(decryptedBytes, StandardCharsets.UTF_8);
    }
}

2.6 业务层实现

导出Java项目之后,在service层中,有写好的业务方法,可以直接调用,但是有一些方法和业务并不满足需求,比如需要切换账户调用合约,需要加载用户的私钥再调用合约进行交易。

  • 用户注册的业务默认
  • 商家注册的业务默认

Java SDK的org.fisco.bcos.sdk.crypto.CryptoSuite提供了账户生成功能。

// 创建非国密类型的CryptoSuite
CryptoSuite cryptoSuite = new CryptoSuite(CryptoType.ECDSA_TYPE);
// 随机生成非国密公私钥对
CryptoKeyPair cryptoKeyPair = cryptoSuite.createKeyPair();
// 获取账户地址
String accountAddress = cryptoKeyPair.getAddress();
// 获取账户私钥
String hexPrivateKey = cryptoKeyPair.getHexPrivateKey();
// 获取账户公钥
String hexPublicKey = cryptoKeyPair.getHexPublicKey();

加载用户的私钥:

CryptoSuite cryptoSuite = new CryptoSuite(CryptoType.ECDSA_TYPE);
// 加载用户私钥        
CryptoKeyPair cryptoKeyPair = cryptoSuite.getKeyPairFactory().createKeyPair(EncryptionUtils.decrypt(userKey.getUserPrivateKey()));

由于我这里是随机生产的地址,所以没用pemp12的方式保存和获取。

如下图:

商家需要创建一个苹果,但是创建苹果的用户和地址必须是商家的,不能是其他未注册或者是购买商品的用户去创建的。

image-20230305162713999

2.6.1 添加用户注册获取公私钥

UserKeyServiceImpl的实现类中,添加一个方法makeNewUserKey(),用于生成用户新注册时候随机生成的地址和公私钥,以及加密公私钥返回给数据库。

@Service
public class UserKeyServiceImpl extends ServiceImpl<UserKeyMapper, UserKey> implements UserKeyService {

    @Autowired
    private UserKeyMapper userKeyMapper;

    @SneakyThrows
    @Override
    public UserKey makeNewUserKey() {
        // 创建非国密类型的CryptoSuite
        CryptoSuite cryptoSuite = new CryptoSuite(CryptoType.ECDSA_TYPE);
        // 随机生成非国密公私钥对
        CryptoKeyPair cryptoKeyPair = cryptoSuite.createKeyPair();
        // 获取账户地址
        String keyPairAddress = cryptoKeyPair.getAddress();
        // 获取账户私钥
        String hexPrivateKey = cryptoKeyPair.getHexPrivateKey();
        // 获取账户公钥
        String hexPublicKey = cryptoKeyPair.getHexPublicKey();

        UserKey userKey = new UserKey();
        userKey.setUserAddress(keyPairAddress);
        // 对数据进行AES算法加密
        userKey.setUserPrivateKey(EncryptionUtils.encrypt(hexPrivateKey));
        userKey.setUserPublicKey(EncryptionUtils.encrypt(hexPublicKey));
        // 加密后存储数据库
        userKeyMapper.insert(userKey);
        return userKey;
    }
    // 其他业务代码
	......
}

2.6.2 添加加载账户私钥

如下的操作是在service层的默认导出的ShopService类中修改。

image-20230305163216365

这是一个公共的方法,可以复用,用户和商家传递私钥对合约的调用。

  /**
   * 加载用户账户切换业务
   * @param cryptoKeyPair
   * @param params
   * @return
   * @throws Exception
   */
  private TransactionResponse getTransactionResponse(CryptoKeyPair cryptoKeyPair,String funcName,List<Object> params) throws Exception {
      // 过创建和使用AssembleTransactionProcessor对象来调用和查询等操作。不部署合约,那么就不需要复制binary文件
    AssembleTransactionProcessor transactionProcessor = TransactionProcessorFactory
            .createAssembleTransactionProcessor(client, cryptoKeyPair,"src/main/resources/abi/","");
      // 使用同步方式发送交易
    TransactionResponse transactionResponse = transactionProcessor
            .sendTransactionAndGetResponseByContractLoader("Shop",this.address,funcName, params);
    return transactionResponse;
  }

2.6.3 添加商家加载私钥

商家注册之后需要保存地址信息,调用合约的时候携带了商家的地址,通过商家的地址查询获取数据库中加密的私钥,然后该方法主要是商家传入私钥和合约方法,通过加载商家的私钥,即可调用创建商品的方法。

  /**
   * 商家添加商品信息
   * @param input
   * @param cryptoKeyPair
   * @param params
   * @return
   * @throws Exception
   */
  public boolean createCommodity(CryptoKeyPair cryptoKeyPair,ShopCreateCommodityInputBO input) throws Exception {
    TransactionResponse transactionResponse = getTransactionResponse(cryptoKeyPair,"createCommodity", input.toArgs());
    return transactionResponse.getReceiptMessages().equals("Success");
  }

  /**
   * 查询商家的信息
   * @param cryptoKeyPair
   * @param params
   * @return
   * @throws Exception
   */
  public Merchant queryMerchantInfo(CryptoKeyPair cryptoKeyPair, List<Object> params) throws Exception {
    TransactionResponse transactionResponse = getTransactionResponse(cryptoKeyPair,"queryMerchantInfo", params);
    return Optional.of(transactionResponse)
            .filter(response -> "Success".equals(response.getReceiptMessages()))
            .map(response -> JSON.parseArray(response.getValues()))
            .filter(objects -> objects.size() >= 3)
            .map(objects -> {
              Merchant merchant = new Merchant();
              merchant.setMerchantAddress(objects.getString(0));
              merchant.setMerchantBalance(objects.getInteger(1));
              merchant.setRole(objects.getInteger(2));
              return merchant;
            })
            .orElse(null);
  }

2.6.4 添加用户加载私钥

需要用户加载私钥进行的操作的是:

  • 更新用户的余额
  • 用户购买商品

这里的操作和上面是一样的,都是需要验证用户加载私钥后,是否为当前私钥的用户地址。

  /**
   * 用户更新账户余额
   * @param cryptoKeyPair
   * @param input
   * @return
   * @throws Exception
   */
  public TransactionResponse updateBalance(CryptoKeyPair cryptoKeyPair,ShopUpdateBalanceInputBO input) throws Exception {
    return getTransactionResponse(cryptoKeyPair,"updateBalance", input.toArgs());
  }

  /**
   * 用户购买商品
   * @param cryptoKeyPair
   * @param input
   * @return
   * @throws Exception
   */
  public TransactionResponse buyCommodity(CryptoKeyPair cryptoKeyPair,ShopBuyCommodityInputBO input) throws Exception {
    return getTransactionResponse(cryptoKeyPair,"buyCommodity", input.toArgs());
  }

2.6.5 完整的业务代码

@Service
@NoArgsConstructor
@Data
public class ShopService {
  public static final String ABI = org.example.Shop.utils.IOUtil.readResourceAsString("abi/Shop.abi");

  public static final String BINARY = org.example.Shop.utils.IOUtil.readResourceAsString("bin/ecc/Shop.bin");

  public static final String SM_BINARY = org.example.Shop.utils.IOUtil.readResourceAsString("bin/sm/Shop.bin");

  @Value("${system.contract.shopAddress}")
  private String address;

  @Autowired
  private Client client;

  AssembleTransactionProcessor txProcessor;

  @PostConstruct
  public void init() throws Exception {
    this.txProcessor = TransactionProcessorFactory.createAssembleTransactionProcessor(this.client, this.client.getCryptoSuite().getCryptoKeyPair());
  }

  /**
   * 用户更新账户余额
   * @param cryptoKeyPair
   * @param input
   * @return
   * @throws Exception
   */
  public TransactionResponse updateBalance(CryptoKeyPair cryptoKeyPair,ShopUpdateBalanceInputBO input) throws Exception {
    return getTransactionResponse(cryptoKeyPair,"updateBalance", input.toArgs());
  }

  /**
   * 用户购买商品
   * @param cryptoKeyPair
   * @param input
   * @return
   * @throws Exception
   */
  public TransactionResponse buyCommodity(CryptoKeyPair cryptoKeyPair,ShopBuyCommodityInputBO input) throws Exception {
    return getTransactionResponse(cryptoKeyPair,"buyCommodity", input.toArgs());
  }

  public TransactionResponse registerUser(ShopRegisterUserInputBO input) throws Exception {
    return this.txProcessor.sendTransactionAndGetResponse(this.address, ABI, "registerUser", input.toArgs());
  }

  /**
   * 查询商家的信息
   * @param cryptoKeyPair
   * @param params
   * @return
   * @throws Exception
   */
  public Merchant queryMerchantInfo(CryptoKeyPair cryptoKeyPair, List<Object> params) throws Exception {
    TransactionResponse transactionResponse = getTransactionResponse(cryptoKeyPair,"queryMerchantInfo", params);
    return Optional.of(transactionResponse)
            .filter(response -> "Success".equals(response.getReceiptMessages()))
            .map(response -> JSON.parseArray(response.getValues()))
            .filter(objects -> objects.size() >= 3)
            .map(objects -> {
              Merchant merchant = new Merchant();
              merchant.setMerchantAddress(objects.getString(0));
              merchant.setMerchantBalance(objects.getInteger(1));
              merchant.setRole(objects.getInteger(2));
              return merchant;
            })
            .orElse(null);
  }

  /**
   * 加载用户账户切换业务
   * @param cryptoKeyPair
   * @param params
   * @return
   * @throws Exception
   */
  private TransactionResponse getTransactionResponse(CryptoKeyPair cryptoKeyPair,String funcName,List<Object> params) throws Exception {
    AssembleTransactionProcessor transactionProcessor = TransactionProcessorFactory
            .createAssembleTransactionProcessor(client, cryptoKeyPair,"src/main/resources/abi/","");

    TransactionResponse transactionResponse = transactionProcessor
            .sendTransactionAndGetResponseByContractLoader("Shop",this.address,funcName, params);
    return transactionResponse;
  }

  /**
   * 商家添加商品信息
   * @param input
   * @param cryptoKeyPair
   * @param params
   * @return
   * @throws Exception
   */
  public boolean createCommodity(CryptoKeyPair cryptoKeyPair,ShopCreateCommodityInputBO input) throws Exception {
    TransactionResponse transactionResponse = getTransactionResponse(cryptoKeyPair,"createCommodity", input.toArgs());
    return transactionResponse.getReceiptMessages().equals("Success");
  }

  public TransactionResponse queryAllCommoditys(ShopQueryAllCommoditysInputBO input) throws Exception {
    return this.txProcessor.sendTransactionAndGetResponse(this.address, ABI, "queryAllCommoditys", input.toArgs());
  }

  public TransactionResponse queryUserInfo() throws Exception {
    return this.txProcessor.sendTransactionAndGetResponse(this.address, ABI, "queryUserInfo", Arrays.asList());
  }

  public TransactionResponse registerMerchant(ShopRegisterMerchantInputBO input) throws Exception {
    return this.txProcessor.sendTransactionAndGetResponse(this.address, ABI, "registerMerchant", input.toArgs());
  }

  public CallResponse commodityCount() throws Exception {
    return this.txProcessor.sendCall(this.client.getCryptoSuite().getCryptoKeyPair().getAddress(), this.address, ABI, "commodityCount", Arrays.asList());
  }
}

2.7 控制层实现

这里主要是做测试,为了更加的安全隐患,实际开发模式中需要使用POST请求,不能暴露出来。

2.7.1 RegisterController

主要实现的是:

  1. 用户的注册接口
  2. 商家的注册接口
  3. 用户和商家创建新的地址和随机公私钥接口
API接口请求类型请求参数
/registerGET
/register/userPOSTShopRegisterUserInputBO
/register/merchantPOSTShopRegisterMerchantInputBO

用户的请求接口示例:

http://localhost:8088/register/user

{
    "_userAddress": "0xaf9f11d639600dffa32ce7f1c3474d1e280cf791",
    "_userName": "张三"
}

商家的请求接口示例:

http://localhost:8088/register/merchant

{
    "_merchantAddress": "0xaf9f11d639600dffa32ce7f1c3474d1e280cf791",
    "_merchantBalance": 1000
}
@RestController
@RequestMapping("register")
public class RegisterController {

    @Autowired
    private UserKeyService userKeyService;

    @Autowired
    private ShopService shopService;

    /**
     * 用户注册创建新的随机地址和公私钥
     * @return
     */
    @SneakyThrows
    @GetMapping
    public CommonResponse getAddressAndPrivateKey(){
        return CommonResponse.ok("Success",userKeyService.makeNewUserKey());
    }

    /**
     * 用户注册
     * @param registerUserInputBO
     * @return
     */
    @SneakyThrows
    @PostMapping("user")
    public CommonResponse registerUser(@RequestBody ShopRegisterUserInputBO registerUserInputBO){
        if (StringUtils.isBlank(registerUserInputBO.get_userAddress()) || StringUtils.isBlank(registerUserInputBO.get_userName())){
            return CommonResponse.fail("400",new RuntimeException("参数为空"));
        }
        TransactionResponse transactionResponse = shopService.registerUser(registerUserInputBO);
        return CommonResponse.ok("注册成功",transactionResponse.getReceiptMessages());
    }

    /**
     * 商家注册
     * @param registerMerchantInputBO
     * @return
     */
    @SneakyThrows
    @PostMapping("merchant")
    public CommonResponse registerMerchant(@RequestBody ShopRegisterMerchantInputBO registerMerchantInputBO){
        if (StringUtils.isBlank(registerMerchantInputBO.get_merchantAddress())){
            return CommonResponse.fail("400",new RuntimeException("参数为空"));
        }
        TransactionResponse transactionResponse = shopService.registerMerchant(registerMerchantInputBO);
        return CommonResponse.ok("注册成功",transactionResponse.getReceiptMessages());
    }
}

2.7.2 UserController

主要实现的是:

  1. 用户获取商品的列表接口
  2. 用户购买商品的接口
  3. 用户更新账户余额的接口
API接口请求类型请求参数
/users/{address}POSTShopQueryAllCommoditysInputBO
/users/{address}POSTShopBuyCommodityInputBO
/users/{address}PUTBigInteger

用户的接口示例:

// 查询商品的列表
POST http://localhost:8088/users/{address}

{
    "page": 1,
    "pageSize": 4
}

// 用户购买商品
POST http://localhost:8088/users/{address}
{
    "_commodityId": 1,
    "_amount": 100
}

// 更新用户的余额
PUT http://localhost:8088/users/{address}?balance=1000
@RestController
@RequestMapping("users")
public class UserController {

    @Autowired
    private Client client;
    @Autowired
    private ShopService shopService;
    @Autowired
    private UserKeyService userKeyService;

    @SneakyThrows
    @GetMapping("{address}")
    public CommonResponse getCommodityList(@PathVariable String address,@RequestBody ShopQueryAllCommoditysInputBO commoditysInputBO){
        if (address.isEmpty() || commoditysInputBO.toArgs().isEmpty()){
            return CommonResponse.fail("400",new RuntimeException("查询异常"));
        }
        TransactionResponse transactionResponse = shopService.queryAllCommoditys(commoditysInputBO);
        if (transactionResponse.getReceiptMessages().equals("Success")){
            String responseValues = transactionResponse.getValues();
            int pageSize = commoditysInputBO.getPageSize();
            // 使用 intStream.range() 方法生成 [0, pageSize-1] 的整数流
            List<Commodity> commodities = IntStream.range(0, pageSize)
                    // 把每一个整数映射成一个 JSONArray 对象
                    .mapToObj(i -> JSON.parseArray(responseValues).getJSONArray(0).getJSONArray(i))
                    // 把每个 JSONArray 转换成 Commodity 对象
                    .map(jsonArray -> {
                        // 根据 jsonArray 创建一个 Commodity 对象
                        Commodity commodity = new Commodity();
                        commodity.setCommodityId((Integer) jsonArray.get(0));
                        commodity.setCommodityName((String) jsonArray.get(1));
                        commodity.setCommodityPrice((Integer) jsonArray.get(2));
                        commodity.setCommodityQuantity((Integer) jsonArray.get(3));
                        return commodity;
                    })
                    // 把所有 Commodity 对象收集到一个 List 中
                    .collect(Collectors.toList());
            return CommonResponse.ok("查询成功",commodities);
        }
        return CommonResponse.fail("400",new RuntimeException("查询异常"));
    }

    @SneakyThrows
    @PostMapping("{address}")
    public CommonResponse buyCommodity(@PathVariable String address, @RequestBody ShopBuyCommodityInputBO commodityInputBO){
        if (StringUtils.isBlank(address)){
            return CommonResponse.fail("400", new RuntimeException("当前用户无效"));
        }
        // 获取当前用户的私钥,并使用其创建加密套件及密钥对
        UserKey userKey = userKeyService.selectByUserAddress(address);
        CryptoSuite cryptoSuite = new CryptoSuite(CryptoType.ECDSA_TYPE);
        CryptoKeyPair cryptoKeyPair = cryptoSuite.getKeyPairFactory().createKeyPair(EncryptionUtils.decrypt(userKey.getUserPrivateKey()));
        // 购买商品操作
        TransactionResponse transactionResponse = shopService.buyCommodity(cryptoKeyPair, commodityInputBO);
        if (transactionResponse.getReceiptMessages().equals("Success")){
            return CommonResponse.ok("购买成功",address);
        }
        return CommonResponse.fail("400",new RuntimeException("购买失败"));
    }

    @SneakyThrows
    @PutMapping("{address}")
    public CommonResponse updateBalance(@PathVariable String address,@RequestParam BigInteger balance){
        if (StringUtils.isBlank(address)){
            return CommonResponse.fail("400", new RuntimeException("当前用户无效"));
        }
        // 获取当前用户的私钥,并使用其创建加密套件及密钥对
        UserKey userKey = userKeyService.selectByUserAddress(address);
        CryptoSuite cryptoSuite = new CryptoSuite(CryptoType.ECDSA_TYPE);
        CryptoKeyPair cryptoKeyPair = cryptoSuite
                .getKeyPairFactory()
                .createKeyPair(EncryptionUtils.decrypt(userKey.getUserPrivateKey()));

        ShopUpdateBalanceInputBO balanceInputBO = new ShopUpdateBalanceInputBO(address, balance);
        // 更新账户余额操作
        TransactionResponse transactionResponse = shopService.updateBalance(cryptoKeyPair,balanceInputBO);
        if (transactionResponse.getReceiptMessages().equals("Success")){
            return CommonResponse.ok("更新成功",balance);
        }
        return CommonResponse.fail("400",new RuntimeException("更新失败"));
    }
}

2.7.3 MerchantController

主要实现的是:

  1. 查看商家内部信息的接口
  2. 商家创建商品的接口
API接口请求类型请求参数
/merchants?address={address}GETaddress
/merchants/addressPOSTShopCreateCommodityInputBO

商家的接口请求示例:

GET http://localhost:8088/merchants/?address={address}

POST http://localhost:8088/merchants/address
{
    "_name": "苹果",
    "_price": 100,
    "_quantity": 20
}
@RestController
@RequestMapping("merchants")
public class MerchantController {
    @Autowired
    private UserKeyService userKeyService;

    @Autowired
    private ShopService shopService;

    /**
     * 查询商家信息接口
     *
     * @param address   用户地址(商家地址)
     * @return          响应结果对象
     */
    @SneakyThrows
    @GetMapping
    public CommonResponse queryMerchant(@RequestParam String address)  {
        if (StringUtils.isEmpty(address)){
            return CommonResponse.fail("400",new RuntimeException("查询失败"));
        }
        // 获取商家用户的私钥,并使用其创建加密套件及密钥对
        UserKey userKey = userKeyService.selectByUserAddress(address);
        CryptoSuite cryptoSuite = new CryptoSuite(CryptoType.ECDSA_TYPE);
        CryptoKeyPair cryptoKeyPair = null;
        cryptoKeyPair = cryptoSuite.getKeyPairFactory().createKeyPair(EncryptionUtils.decrypt(userKey.getUserPrivateKey()));
        List<Object> params = new ArrayList<>();

        // 调用查询商家信息 假设这是商家的内部信息 普通用户不可查询
        Merchant merchant = shopService.queryMerchantInfo(cryptoKeyPair, params);
        if (Objects.isNull(merchant)){
            return CommonResponse.fail("400",new RuntimeException("当前角色不是商家"));
        }
        return CommonResponse.ok("查询成功",merchant);
    }

    /**
     * 创建商品接口
     *
     * @param address                   用户地址(商家地址)
     * @param createCommodityInputBO     商品信息参数对象
     * @return                          响应结果对象
     */
    @SneakyThrows
    @PostMapping("{address}")
    public CommonResponse createCommodity(@PathVariable String address,@RequestBody ShopCreateCommodityInputBO createCommodityInputBO){
        if (StringUtils.isEmpty(createCommodityInputBO.get_name())){
            return CommonResponse.fail("400",new RuntimeException("商品的名称不能为空"));
        }
        // 获取商家用户的私钥,并使用其创建加密套件及密钥对
        UserKey userKey = userKeyService.selectByUserAddress(address);
        System.out.println(userKey.getUserPrivateKey());
        CryptoSuite cryptoSuite = new CryptoSuite(CryptoType.ECDSA_TYPE);
        CryptoKeyPair cryptoKeyPair = cryptoSuite.getKeyPairFactory().createKeyPair(EncryptionUtils.decrypt(userKey.getUserPrivateKey()));
        // 商家调用创建商品
        boolean commodity = shopService.createCommodity(cryptoKeyPair, createCommodityInputBO);
        return CommonResponse.ok("创建成功",commodity);
    }
}

2.8 API测试

2.8.1 测试注册用户

GET http://localhost:8088/register

POST http://localhost:8088/register/user

1.发送GET请求获取如下的信息:

  • 用户的地址: 0x17b1730bb77db57313ac59c20c0aaae2efbaeab6
  • 用户加密的私钥: dzuF553WpMInWPeLqjP/dyZ4s381sEPeQni4+mY/L7Y50P46JZkLXiV7RaMYXjMxz1AD+cq9ktjWH4ZCX2jOXAbb0lJO2pSgRxctIDarhYM=

image-20230305221854226

2.使用上面的用户地址,去注册一个叫”张三“的用户。如下图是注册成功。

image-20230305222248111

3.这里显示注册成功,然后我们通过AES算法的加密工具类解密如下:

得到解密后的私钥: c8c25e5a9bc3485e90a2cab239861eae9e51a2d57bbb60ea7eda9a4c83267eac

image-20230305222758718

4.在WeBASE-Front中导入私钥,并且创建zhangsan的测试用户。

image-20230305222932099

成功导入之后,直接使用这个账号调用queryUserInfo的合约。因为我写查看用户合约的时候我加了检测当前地址是否有权限。

image-20230305223114434

可以发现如下是直接交易成功,并且我是能查看到张三的信息的。

  • zhangsan的用户地址:0x17B1730bb77db57313aC59C20C0AAAe2EFBAeAB6

对比一下上面接口的测试获取的用户地址,用户地址一模一样。

image-20230305223623743

2.8.2 测试注册商家

GET http://localhost:8088/register

POST http://localhost:8088/register/merchant

1.发送GET请求获取如下的信息:

  • 商家的地址: 0x4ab865d51fbf01e2a102a86e56fe9e7358382dce
  • 商家加密的私钥:SxWg6lFxmvmp/uyIA0GBmkE1iaCh6jioFigyk+VSbTP4OuyK4HGSbinGKFTzDmwft7dz5hOsiJyUgQ0lQXeuqwbb0lJO2pSgRxctIDarhYM=

image-20230305224313691

2.使用新的用户地址注册商家,注册资金为1000元。

image-20230305224500695

3.通过解密后得到商家的私钥

  • 商家的私钥: f143333a17243b5d804c4162adbb68bf28fc75dfb7e3db680d48a1f1e2dfcb48

在WeBASE-Front导入私钥商家的名称为merchant,调用查询商家的方法。

image-20230305224817909

调用当前的queryMerchantInfo的函数,查看交易如下:

  • merchant的用户地址: 0x4aB865D51fbf01E2A102A86e56Fe9E7358382DcE

image-20230305224954366

当我使用zhangsan的用户去查看企业的内部信息的时候,会交易失败。显示当前不是商家。

image-20230305225216954

4.使用接口测试查询商家的信息,结果和上面一样。

image-20230305225905882

2.8.3 测试商家的添加商品

POST http://localhost:8088/merchants/0x4ab865d51fbf01e2a102a86e56fe9e7358382dce

1.使用商家的地址0x4ab865d51fbf01e2a102a86e56fe9e7358382dce,添加两个商品,一个是苹果,一个是香蕉。如下添加成功。

image-20230305230204202

image-20230305230228403

2.使用用户分页查询当前的所有数据。

image-20230305235634870

2.8.4 测试用户购买商品

PUT http://localhost:8088/users/{address}?balance=1000

POST http://localhost:8088/users/{address}

1.给用户的账户更新余额100元。然后查询当前用户的余额。

image-20230306012030231

如下是使用WeBASE-Front查看的余额。

image-20230306013545696

2.用户购买商品,确定只有注册的用户是可以购买成功的。

image-20230306012341889

通过WeBASE-Front调用zhangsan的账户查看个人信息,成功扣款和购买商品。

image-20230306012519581

查看当前的所有商品信息,香蕉已经扣款数量为1。

image-20230306012718338

查看当前的商家余额,已经到账100元。

image-20230306013035272

2.8.5 测试是否加载了私钥

1.我特意在合约中用户购买商品的业务写了触发事件,假设用户购买了该商品,会记录当前的地址是谁购买的,可以通过查看Event事件的msg.sender的地址是不是该用户的。

如下是合约的详细部分:

  • 有AuthUser修饰符函数确定当前是否具备用户的权限
  • 有BuyCommodity的事件

image-20230306014021787

2.在用户购买商品的部分,也就是用户购买商品时候访问的接口,添加获取当前的合约调用的用户地址。用log的方式查看。

如下是添加的测试代码,用于观察。

    @SneakyThrows
    @PostMapping("{address}")
    public CommonResponse buyCommodity(@PathVariable String address, @RequestBody ShopBuyCommodityInputBO commodityInputBO){
        if (StringUtils.isBlank(address)){
            return CommonResponse.fail("400", new RuntimeException("当前用户无效"));
        }
        // 获取当前用户的私钥,并使用其创建加密套件及密钥对
        UserKey userKey = userKeyService.selectByUserAddress(address);
        CryptoSuite cryptoSuite = new CryptoSuite(CryptoType.ECDSA_TYPE);
        CryptoKeyPair cryptoKeyPair = cryptoSuite.getKeyPairFactory().createKeyPair(EncryptionUtils.decrypt(userKey.getUserPrivateKey()));
        // 购买商品操作
        TransactionResponse transactionResponse = shopService.buyCommodity(cryptoKeyPair, commodityInputBO);
        if (transactionResponse.getReceiptMessages().equals("Success")){
            log.info("======================================================");
            log.info("++++++++++++当前的合约调用者:" + transactionResponse.getTransactionReceipt().getFrom());
            log.info("++++++++++++当前的合约调用者:" + transactionResponse.getTransactionReceipt().getLogs().get(0).getTopics().get(1));
            return CommonResponse.ok("购买成功",address);
        }
        return CommonResponse.fail("400",new RuntimeException("购买失败"));
    }

调用此接口继续测试。

image-20230306023304521

3.查看当前的控制台输出情况。

image-20230306023714077

4.使用WeBASE-Front的Event的事件查看。

从日志和Event事件查看,合约的调用者都是0x17b1730bb77db57313ac59c20c0aaae2efbaeab6地址。

image-20230306023811570

根据以上的案例,可以理解私钥加载的使用场景。

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

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

相关文章

MVC模式和三层架构

MVC模式和三层架构 MVC模式三层架构MVC与三层架构的联系MVC与三层架构的异同 MVC模式 MVC&#xff08;Model View Controller&#xff09;是软件工程中的一种软件设计模式&#xff0c;它把软件系统分为模型、视图和控制器三个基本部分。用一种业务逻辑、数据、界面显示分离的方…

路由器+Gdbserver+IDA Pro远程调试

最近在复现路由器漏洞&#xff0c;也踩了不少坑&#xff0c;记录一下&#xff0c;希望能对需要的人有一些帮助。使用的路由器型号为RT-AC68U&#xff0c;ARM架构&#xff0c;小端序&#xff0c;Linux内核版本2.6.36&#xff0c;很老&#xff0c;主要的时间也花费在找能支持这个…

2023年最新版kali linux安装教程

一、前期准备 前排提醒&#xff0c;文末有绿色版安装包免费领取&#xff01; 二、VMware虚拟机配置 1、打开vmware&#xff0c;点击创建新的虚拟机 2、选择自定义(高级)选项&#xff0c;点击下一步 3、继续下一步 4、选择【稍后安装操作系统】&#xff0c;然后点击下一步 …

【Mybatis】使用mybatis框架连接mysql数据库详细步骤

和我之前写的通过导入jdbc驱动jar包来连接mysql数据库而言&#xff0c;用mybatis来说可以有很多好处呀&#xff0c;首先mybatis&#xff0c;就是对jdbc的优化方案对吧&#xff0c;&#xff0c;jdbc的硬编码和一些繁琐的操作在使用mybatis的时候我就彻底抛掷脑后了哈哈哈。 同时…

MySQL数据库下载及安装教程(最最新版)

MySQL数据库下载及安装教程&#xff08;最最新版&#xff09; 一、下载mysql数据库二、安装Mysql三、验证是否安装成功&#xff08;一&#xff09;、命令提示符cmd窗口验证&#xff08;二&#xff09;、MySQL控制台验证 一、下载mysql数据库 进入MySQL官方网站&#xff08;htt…

SVG实现中国地图

1.SVG是什么&#xff1f; svg 是Scalable Vector Graphics的缩写&#xff0c;指可伸缩矢量图形&#xff0c;可以用于绘制复杂不规则的控件。 svg绘制原理&#xff0c;就是利用了Path绘制图形。 1&#xff09;svg利用xml定义图形。在xml中就包晗了绘制Path所需的数据。 2&…

创建数据库中,超详细常用的MySQL命令(含解析、图解与全部代码)

目录 系统命令行 MySQL命令行 数据库命令 数据表命令 建表并导入数据 表的其他操作 系统命令行 以下是在系统命令行&#xff0c;已管理员身份运行的情况下&#xff0c;MySQL的一些命令 1.这两条是关闭MySQL服务与开启MySQL服务的命令 net stop MySQL net start MySQL80…

Mysql启动不了怎么回事

mysql 服务无法启动是什么原因&#xff1f; mysql服务无法启动的原因有很多&#xff1a;可能端口被占用&#xff1b;可能my.cnf配置了错误的参数&#xff1b;也有可能没有初始数据库&#xff0c;还有可能是其他原因。大多数原因都可以通过先注销掉原有的服务、重新装载服务、之…

ASIC-WORLD Verilog(5)基础语法下篇

写在前面 在自己准备写一些简单的verilog教程之前&#xff0c;参考了许多资料----asic-world网站的这套verilog教程即是其一。这套教程写得极好&#xff0c;奈何没有中文&#xff0c;在下只好斗胆翻译过来&#xff08;加了自己的理解&#xff09;分享给大家。 这是网站原文&…

k8s中pod使用详解

一、前言 在之前k8s组件一篇中,我们谈到了pod这个组件,了解到pod是k8s中资源管理的最小单位,可以说Pod是整个k8s对外提供服务的最基础的个体,有必要对Pod做深入的学习和探究。 二、再看k8s架构图 为了加深对k8s中pod的理解,再来回顾下k8s的完整架构 三、pod特点 结合上面这…

YOLOv5算法原理与网络结构

YOLOv5算法原理与网络结构 1.1 YOLOv5算法 YOLOv5算法共有4种网络结构&#xff0c;分别是YOLOv5s、YOLOv5m、YOLOv5l和YOLOv5x&#xff0c;这四种网络结构在宽度和深度上不同&#xff0c;原理上基本一样&#xff0c;接下来以 YOLOv5s 为例介绍 YOLOv5网络结构。 图1 YOLOv5网…

基于Java+SpringBoot+Vue前后端分离手机销售商城系统设计和实现

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

软件AutoID Network Navigator设置基恩士扫码枪的使用教程

AutoID Network Navigator可以用来对扫码枪的ip和各参数进行调整 1.设置前的准备 扫码枪的默认ip是192.168.100.1&#xff0c;所以需要先把电脑IP更改为192.168.100.xxx 2.搜索扫码枪 更改电脑IP后打开软件点击绿色的号 选择以太网 出现局域网设置弹窗&#xff0c;若为你设…

为什么说网络安全行业是 IT 行业最后的红利?

一、为什么选择网络安全&#xff1f; 这几年随着我国《国家网络空间安全战略》《网络安全法》《网络安全等级保护 2.0》等一系列政策/法规/标准的持续落地&#xff0c;网络安全行业地位、薪资随之水涨船高。 未来 3-5 年&#xff0c;是安全行业的黄金发展期&#xff0c;提前踏…

《汇编语言》- 读书笔记 - 第1章-基础知识

《汇编语言》- 读书笔记 - 第1章-基础知识 1.1 机器语言1.2 汇编语言的产生1.3 汇编语言的组成1.4 存储器1.5 指令和数据1.6 存储单元1.7 CPU对存储器的读写1.8 地址总线主流CPU的寻址能力 1.9 数据总线1.10 控制总线检测点 1.11.11 内存地址空间(概述)1.12 主板1.13 接口卡1.1…

【python基础教程】csv文件的写入与读取

✅作者简介&#xff1a;大家好我是hacker707,大家可以叫我hacker &#x1f4c3;个人主页&#xff1a;hacker707的csdn博客 &#x1f525;系列专栏&#xff1a;python基础教程 &#x1f4ac;推荐一款模拟面试、刷题神器&#x1f449;点击跳转进入网站 csv文件读写 csv的简单介绍…

MAC怎么获取文件路径 MAC获取文件路径的四种方法

MAC怎么获取文件路径介绍 方法一&#xff1a;最简单的方法 右键文件或者文件夹&#xff0c;选择显示简介 2在弹出来的窗口中找到位置&#xff0c;即为路径&#xff0c;在mac 10.10之前的系统是正常的路径&#xff0c;10.10开始是小箭头代替/显示&#xff0c;注意&#xff0c…

Git操作不规范,战友提刀来相见。

年终奖都没了&#xff0c;还要扣我绩效&#xff0c;门都没有&#xff0c;哈哈。 这波骚Git操作我也是第一次用&#xff0c;担心闪了腰&#xff0c;所以不仅做了备份&#xff0c;也做了笔记&#xff0c;分享给大家。 文末留言抽奖&#xff0c;聊聊你的年终奖。 问题描述 小A和…

2023年最新Python安装详细教程

目录 一、python官网 二、在官网的Downloads栏目&#xff0c;选择对应的操作系统 三、进入windows对应的页面&#xff0c;选择python版本 (1)选择python的稳定发布版本Stable Releases (2)下载python的安装程序Windows Installer 四、运行安装python的安装程序Windows Install…

【郭东白架构课 模块二:创造价值】25|节点四:架构规划之需求确认

你好&#xff0c;我是郭东白。 上节课我们讲了架构规划这个环节的第一个部分&#xff0c;也就是统一语义。那么这节课我们就来讲第二个部分——需求确认。 需求确认与统一语义的过程是密不可分的。需求确认是在统一语义赋能之下进行的&#xff0c;所以两者并不是先后顺序的关系…