1 Star 0 Fork 36

Andrew-Y / 基于区块链技术的电子合同管理系统后台

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
MIT

RuoYi-Vue-Plus平台简介

RuoYi-Vue-Plus 是基于 RuoYi-Vue 针对 分布式集群 场景升级(不兼容原框架)

功能介绍 使用技术 文档地址 特性注意事项
当前框架 RuoYi-Vue-Plus RuoYi-Vue-Plus文档 重写RuoYi-Vue全方位升级(不兼容原框架)
satoken分支 RuoYi-Vue-Plus-satoken satoken分支地址 使用satoken重构权限鉴权(仅供学习不推荐上生产)
单体分支 RuoYi-Vue-Plus-fast fast分支地址 单体应用结构
原框架 RuoYi-Vue RuoYi-Vue官网 定期同步需要的功能
前端开发框架 Vue、Element UI Element UI官网
后端开发框架 SpringBoot SpringBoot官网
容器框架 Undertow Undertow官网 基于 Netty 的高性能容器
权限认证框架 Spring Security、Jwt SpringSecurity官网 支持多终端认证系统
关系数据库 MySQL MySQL官网 适配 8.X 最低 5.7
缓存数据库 Redis Redis官网 适配 6.X 最低 4.X
数据库框架 Mybatis-Plus Mybatis-Plus文档 快速 CRUD 增加开发效率
数据库框架 p6spy p6spy官网 更强劲的 SQL 分析
多数据源框架 dynamic-datasource dynamic-ds文档 支持主从与多种类数据库异构
序列化框架 Jackson Jackson官网 统一使用 jackson 高效可靠
网络框架 Feign、OkHttp3 Feign官网 接口化管理 HTTP 请求
Redis客户端 Redisson Redisson文档 支持单机、集群配置
分布式限流 Redisson Redisson文档 全局、请求IP、集群ID 多种限流
分布式锁 Lock4j Lock4j官网 注解锁、工具锁 多种多样
分布式幂等 Lock4j Lock4j文档 基于分布式锁实现
文件存储 Minio Minio文档 本地存储
文件存储 七牛、阿里、腾讯 OSS使用文档 云存储
监控框架 SpringBoot-Admin SpringBoot-Admin文档 全方位服务监控
校验框架 Validation Validation文档 增强接口安全性、严谨性
Excel框架 Alibaba EasyExcel EasyExcel文档 性能优异 扩展性强
文档框架 Knife4j Knife4j文档 美化接口文档
工具类框架 Hutool、Lombok Hutool文档 减少代码冗余 增加安全性
代码生成器 适配MP、Knife4j规范化代码 Hutool文档 一键生成前后端代码
部署方式 Docker Docker文档 容器编排 一键部署业务集群
国际化 SpringMessage SpringMVC文档 Spring标准国际化方案

本系统演示视频

https://www.bilibili.com/video/BV1s44y1E7sm

基于区块链技术的电子合同管理系统

本系统后台使用框架为ruoyi-vue-plus:https://github.com/JavaLionLi/RuoYi-Vue-Plus

该框架为前后端分离框架,后端使用SpringBoot,前端使用Vue

小程序UI框架为Lin-UI:https://github.com/TaleLin/lin-ui

区块链为Hyperledger Fabric,部署框架为minifabric:https://github.com/hyperledger-labs/minifabric

阿里OSS做对象存储:https://help.aliyun.com/product/31815.html

IPFS做入链对象存储:https://ipfs.io/

itext实现pdf盖章:https://www.tutorialspoint.com/itext/itext_setting_position_of_image.htm

Hyperledger Fabric 官方手册:https://hyperledger-fabric.readthedocs.io/zh_CN/release-2.2/network/network.html

一些重要配置以及代码逻辑

如何基于itext实现pdf盖章

详见PDFUtils声明与调用

public class PDFUtils {
public static byte[] signPdf(String pdfUrl, String sealUrl) throws DocumentException, IOException {
        Document document = null;
        PdfStamper stamper = null;
        PdfReader reader = null;
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        byte[] data = null;
        try {
            // pdf文件路径
            URL url = new URL(pdfUrl);
            // 读取pdf文件
            reader = new PdfReader(url);
            // 获取页数
            int pageCount = reader.getNumberOfPages();
            // 生成副本
            stamper = new PdfStamper(reader, outputStream);
            // 生成document
            document = new Document(reader.getPageSize(1));
            // 获取页面宽度
            float width = document.getPageSize().getWidth();
            // 获取页面高度
            float height = document.getPageSize().getHeight();
            System.out.println("width = " + width + ", height = " + height);
            // 读取seal
            Image sealImage = Image.getInstance(sealUrl);
            // 根据域的大小缩放印章
            sealImage.scaleToFit(150, 150);
            // 设置印章位置
            sealImage.setAbsolutePosition(width - 150 - 50, 50);
            // 在最后一页盖章
            PdfContentByte sign = stamper.getOverContent(pageCount);
            sign.addImage(sealImage);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (document != null) {
                document.close();
            }
            if (stamper != null) {
                stamper.close();
            }
            if (reader != null) {
                data = outputStream.toByteArray();
                reader.close();
            }
            return data;
        }
    }

}

如何使用SpringBoot连接线上区块链,调用链码

  1. 首先搭建minifab在云服务器上,然后将minifab生成的“peerOrganizations"和”ordererOrganizations“文件复制到本地(文件路径:vars/keyfiles),创建一个名叫crypto-config的父目录保存(此处可自定义,需修改application.yml文件对照自己保存的路径)
  2. 然后将minifab生成的网络配置文件也保存到本地(文件路径:vars/profiles),里面有各种语言的连接配置文件,按需所求

连接Fabric相关代码

application.yml
--- # Fabric 超级账本gateway连接配置项
fabric:
  # 网络配置文件路径
  networkConfigPath: mychannel_connection_for_javasdk.yaml
  # 用户证书路径
  certificatePath: crypto-config/peerOrganizations/bc1.henriport.com/users/Admin@bc1.henriport.com/msp/signcerts/Admin@bc1.henriport.com-cert.pem
  # 用户私钥路径
  privateKeyPath: crypto-config/peerOrganizations/bc1.henriport.com/users/Admin@bc1.henriport.com/msp/keystore/priv_sk
  # 链码名字
  contractName: contract
  # 通道名字
  channelName: mychannel
  # mspId
  mspId: bc1-henriport-com
  # 用户
  user: admin
FabricProperties
@Data
@Component
@ConfigurationProperties(prefix = "fabric")
public class FabricProperties {

    /**
     * 网络配置文件路径
     */
    private String networkConfigPath;

    /**
     * 用户证书路径
     */
    private String certificatePath;

    /**
     * 用户私钥路径
     */
    private String privateKeyPath;

    /**
     * 链码名字
     */
    private String contractName;

    /**
     * 通道名字
     */
    private String channelName;

    /**
     * mspId
     */
    private String mspId;

    /**
     * 用户
     */
    private String user;

}
FabricUtils
 @Component
public class FabricUtils {
    @Resource
    private FabricProperties fabricProperties;

    private Network network;

    public Network getNetwork() {
        return this.network;
    }

    private Contract contract;

    public Contract getContract() {
        return this.contract;
    }

    @PostConstruct
    private void FabricUtils() {
        try {
            // 获取当前文件类路径
            String resourcePath = this.getClass().getResource("/").toURI().toString();
            System.out.println(resourcePath);
            X509Certificate certificate = readX509Certificate(Paths.get(new URI(resourcePath+fabricProperties.getCertificatePath())));
            PrivateKey privateKey = getPrivateKey(Paths.get(new URI(resourcePath+fabricProperties.getPrivateKeyPath())));
            Wallet wallet = Wallets.newInMemoryWallet();
            wallet.put(fabricProperties.getUser(), Identities.newX509Identity(fabricProperties.getMspId(), certificate, privateKey));
            //根据配置文件 获取Fabric网络连接对象
            Gateway.Builder builder = Gateway.createBuilder()
                .identity(wallet, "admin")
                .networkConfig(Paths.get(new URI(resourcePath+fabricProperties.getNetworkConfigPath())));
            //连接网关
            Gateway gateway = builder.connect();
            //获取通道
            network = gateway.getNetwork(fabricProperties.getChannelName());
            //获取合约对象
            contract = network.getContract(fabricProperties.getContractName());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static X509Certificate readX509Certificate(final Path certificatePath) throws IOException, CertificateException {
        try (Reader certificateReader = Files.newBufferedReader(certificatePath, StandardCharsets.UTF_8)) {
            return Identities.readX509Certificate(certificateReader);
        }
    }

    private static PrivateKey getPrivateKey(final Path privateKeyPath) throws IOException, InvalidKeyException {
        try (Reader privateKeyReader = Files.newBufferedReader(privateKeyPath, StandardCharsets.UTF_8)) {
            return Identities.readPrivateKey(privateKeyReader);
        }
    }
}

调用链码相关代码

Controller
/**
     * 修改审核状态
     *
     * @param id
     * @param type
     * @return
     */
    @ApiOperation("修改审核状态")
    @PreAuthorize("@ss.hasPermi('system:contract:edit')")
    @Log(title = "电子合同管理", businessType = BusinessType.UPDATE)
    @PutMapping("/state/{id}/{type}")
    public AjaxResult<Void> changeState(@NotNull(message = "主键不能为空")
                                        @PathVariable Long id,
                                        @NotNull(message = "type不能为空")
                                        @PathVariable Boolean type,
                                        String pdfUrl, String sealUrl) {
        return toAjax(iHeContractService.setStateById(id, type, pdfUrl, sealUrl) ? 1 : 0);
    }
ServiceImpl
   @Override
    public Boolean setStateById(Long id, Boolean type, String pdfUrl, String sealUrl) {
        String state = type ? "2" : "3";
        UpdateWrapper<HeContract> updateWrapper = new UpdateWrapper<>();
        // 如果审核通过
        try {
            if (type && pdfUrl != null && sealUrl != null) {
                // TODO 调用修改pdf方法,OSS文件重写
                byte[] data = PDFUtils.signPdf(pdfUrl, sealUrl);
                iSysOssService.updateFile(data, pdfUrl);
                // TODO 调用文件入ipfs方法
                String ipfsHash = IPFSUtils.upload(data);
                // 修改id对应的contract数据
                updateWrapper.eq("id", id)
                    .set("state", state)
                    .set("ipfs_hash", ipfsHash)
                    .set("is_link", 1);
                // TODO 调用数据入链方法,整体数据存入Fabric
                // 初始化
                Network network = fabricUtils.getNetwork();
                Contract contract = fabricUtils.getContract();
                // 获取contract
                HeContractVo heContractVo = queryById(id);
                byte[] addContractResult = contract.createTransaction("addContract")
                    .setEndorsingPeers(network.getChannel().getPeers(EnumSet.of(Peer.PeerRole.ENDORSING_PEER)))
                    .submit(
                        heContractVo.getId().toString(),
                        heContractVo.getTitle(),
                        heContractVo.getDescription(),
                        heContractVo.getType(),
                        ipfsHash,
                        heContractVo.getBelong().toString(),
                        heContractVo.getBelong().toString());
                log.info("addContract:"+new String(addContractResult, StandardCharsets.UTF_8));
            }
        } catch (DocumentException documentException) {
            documentException.printStackTrace();
            return false;
        } catch (IOException ioException) {
            ioException.printStackTrace();
            return false;
        } catch (ContractException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }
        return update(updateWrapper);
    }

如何使用SpringBoot连接本地IPFS,上传文件

IPFSUtils
public class IPFSUtils {
    /**
     * ipfs的服务器地址和端口
     */
    private static IPFS ipfs = new IPFS("/ip4/127.0.0.1/tcp/5001");

    /**
     * 将文件url转换为InputStream
     * @param url
     * @return
     */
    public static InputStream getFileInputSteam(String url){
          try {
            URL httpUrl = new URL(url);
            HttpURLConnection conn = (HttpURLConnection)httpUrl.openConnection();
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(5 * 1000);
            //通过输入流获取图片数据
            InputStream inStream = conn.getInputStream();
            return inStream;
        } catch (Exception e) {
           e.printStackTrace();
        }
        return null;
    }

    /**
     * 将文件url转换为byte[]
     * @param url
     * @return
     */
    public static byte[] getFileSteam(String url){
          try {
            URL httpUrl = new URL(url);
            HttpURLConnection conn = (HttpURLConnection)httpUrl.openConnection();
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(5 * 1000);
            //通过输入流获取图片数据
            InputStream inStream = conn.getInputStream();
            //得到图片的二进制数据
            byte[] btImg = readInputStream(inStream);
            return btImg;
        } catch (Exception e) {
           e.printStackTrace();
        }
        return null;
    }

     /**
   * 从输入流中获取数据
   * @param inStream 输入流
   * @return
   * @throws Exception
   */
   public static byte[] readInputStream(InputStream inStream) throws Exception{
     ByteArrayOutputStream outStream = new ByteArrayOutputStream();
     byte[] buffer = new byte[1024];
     int len = 0;
     while( (len=inStream.read(buffer)) != -1 ){
         outStream.write(buffer, 0, len);
     }
     inStream.close();
     return outStream.toByteArray();
   }

    /**
     * 整合,根据URL地址上传文件
     * @param url
     * @return
     * @throws IOException
     */
   public static String uploadByUrl(String url) throws IOException {
       InputStream fileInputSteam = getFileInputSteam(url);
       return upload(fileInputSteam);
   }

    /**
     * 根据文件路径上传文件
     * src/image/hello.jpg
     *
     * @param fileName
     * @return
     * @throws IOException
     */
    public static String upload(String fileName) throws IOException {
        NamedStreamable.FileWrapper file = new NamedStreamable.FileWrapper(new File(fileName));
        MerkleNode addResult = ipfs.add(file).get(0);
        return addResult.hash.toString();
    }

    /**
     * 根据inputstream上传文件
     * @param inputStream
     * @return
     * @throws IOException
     */
    public static String upload(InputStream inputStream) throws IOException {
        NamedStreamable.InputStreamWrapper file = new NamedStreamable.InputStreamWrapper(inputStream);
        MerkleNode addResult = ipfs.add(file).get(0);
        return addResult.hash.toString();
    }

    /**
     * 根据文件二进制内容上传文件
     *
     * @param data
     * @return
     * @throws IOException
     */
    public static String upload(byte[] data) throws IOException {
        NamedStreamable.ByteArrayWrapper file = new NamedStreamable.ByteArrayWrapper(data);
        MerkleNode addResult = ipfs.add(file).get(0);
        return addResult.hash.toString();
    }

    /**
     * 根据hash值下载文件
     *
     * @param hash
     * @return
     */
    public static byte[] download(String hash) {
        byte[] data = null;
        try {
            data = ipfs.cat(Multihash.fromBase58(hash));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return data;
    }

    /**
     * 替换文件?
     *
     * @param hash
     * @param destFile
     */
    public static void download(String hash, String destFile) {
        byte[] data = null;
        try {
            data = ipfs.cat(Multihash.fromBase58(hash));
        } catch (IOException e) {
            e.printStackTrace();
        }
        if (data != null && data.length > 0) {
            File file = new File(destFile);
            if (file.exists()) {
                file.delete();
            }
            FileOutputStream fos = null;
            try {
                fos = new FileOutputStream(file);
                fos.write(data);
                fos.flush();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }
    }
}

Minifabric使用文档

设置spec.yaml配置文件

cd /mnt/d/minifabric-main/
vim spec.yaml

fabric:
  cas:
  - "ca.bc1.henriport.com"
  - "ca.bc2.henriport.com"
  peers:
  - "p1.bc1.henriport.com"
  - "p1.bc2.henriport.com"
  orderers:
  - "orderer1.henriport.com"
  settings:
    ca:
      FABRIC_LOGGING_SPEC: DEBUG
    peer:
      FABRIC_LOGGING_SPEC: DEBUG
    orderer:
      FABRIC_LOGGING_SPEC: DEBUG
  ### use go proxy when default go proxy is restricted in some of the regions.
  ### the default goproxy
  # goproxy: "https://proxy.golang.org,direct"
  ### the goproxy in China area
  goproxy: "https://goproxy.cn,direct"
  ### set the endpoint address to override the automatically detected IP address
  # endpoint_address: 104.196.45.144
  ### set the docker network name to override the automatically generated name.
  netname: "mysite"
  ### set the extra optins for docker run command
  # container_options: "--restart=always --log-opt max-size=10m --log-opt max-file=3"

启动simple链码例子

./minifab up -n simple -l go -v 1.0 -r true -o bc1.henriport.com

重启fabric区块链

./minifab restart

启动区块链浏览器

./minifab explorerup

关闭区块链浏览器

./minifab explorerdown

关闭fabric区块链

./minifab down

删除fabric区块链

./minifab cleanup

更多命令可通过如下命令查看

./minifab -h

编写链码

请看链码文件->main.go,仅供参考

安装链码

  1. 根据例子自定义自己的链码

  2. 将go.mod、go.sum、main.go文件打包新建名为"go"的文件夹

  3. 在\minifabric-main\vars\chaincode\目录下创建自己自定义链码的文件夹,比如"contract"

  4. 将"go"文件夹复制到"contract"文件夹下

  5. 回到\minifabric-main\中启动区块链后输入如下代码

    ./minifab install -n contract -l go
    ./minifab approve
    ./minifab commit
    ./minifab initialize -p ''

    如果有go mod问题,回到"go"文件夹下输入以下命令

    go mod tidy
    go mod vendor

    高版本可用如下代码

    minifab ccup -n <chaincodename> -l <lang> -v 1.0

    详见:

    https://github.com/hyperledger-labs/minifabric/blob/main/docs/README.md#install-your-own-chaincode

测试用例

# 添加用户
./minifab invoke -p '"addUser","1", "张三", "1208471280957102", "120847124"'
./minifab invoke -p '"addUser","2", "李四", "128754812074", "197264192"'
./minifab invoke -p '"addUser","3", "王五", "128712354812074", "197264141292"'
# 通过用户id查询用户
./minifab query -p '"getUserById","1"'
# 修改用户
./minifab invoke -p '"updateUserById","3", "赵六", "128712354812074", "197264141292"'
# 添加合同
./minifab invoke -p '"addContract","1","租房", "租房合同", "个人", "jqoiwdnqowhjgoqwj", "1", "2"'
# 通过合同id查询合同
./minifab query -p '"getContractById","1"'
# 修改合同
./minifab invoke -p '"updateContractById","1", "租房2", "租房合同2", "非个人", "jqoiwdnqowhjgoqwj", "1", "3"'
# 删除合同
./minifab invoke -p '"deleteContractById","1"'

问题汇总

  1. 代理被拒绝

==Error: chaincode install failed with status: 500 - failed to invoke backing implementation of 'InstallChaincode': could not build chaincode: docker build failed: docker image build failed: docker build failed: Error returned from build: 1 "go: github.com/golang/protobuf@v1.3.2: Get "https://proxy.golang.org/github.com/golang/protobuf/@v/v1.3.2.mod": dial tcp 172.217.24.17:443: connect: connection refused== ———————————————— 版权声明:本文为CSDN博主「Jayo_o_0」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/weixin_42918620/article/details/120268692

// 到链码所在的目录下提前下载依赖包
cd fabric-samples/asset-transfer-basic/chaincode-go
//更改GO代理
go env -w GOPROXY=https://goproxy.io,direct
go env -w GO111MODULE=on
go mod vendor

之后重启网络部署 重新使用restart指令即可

更多参考资料

Hyperledger Fabric官方手册 https://hyperledger-fabric.readthedocs.io/en/latest/index.html

awesome-blockchain https://github.com/chaozh/awesome-blockchain-cn

快速带你上手Hyperledger Fabric环境搭建+开发测试 https://cloud.tencent.com/developer/article/1678190

登链社区 https://learnblockchain.cn/

minifabric https://github.com/hyperledger-labs/minifabric

HyperLedger Fabric ChainCode开发——shim.ChaincodeStubInterface用法 https://www.cnblogs.com/studyzy/p/7360733.html

Hyper ledger Fabric 2.3 部署链码中遇到的问题 https://blog.csdn.net/weixin_42918620/article/details/120268692

Fabric-sdk-java调试跑通 https://www.jianshu.com/p/1c7707ae3ffd

fabric-sdk-java https://github.com/hyperledger/fabric-sdk-java

Spring Fabric Gateway https://ecsoya.github.io/fabric/pages/gateway.html

fabric-sdk-java 简单示例 https://www.cnblogs.com/luyilan/p/15016703.html

Fabric2.0Java SDK实践-合约交易 https://blog.csdn.net/qq_28540443/article/details/104451259

Hyperledger Fabric架构及Java SDK实现 https://www.jianshu.com/p/d2c796849b42

【fabric】java sdk制作 Hyperledger Fabric Gateway SDK for Java https://blog.csdn.net/qq_38904077/article/details/108038061

常见Fabric错误场景(持续更新) https://www.jianshu.com/p/248530bf31b8

IPFS使用教程 | java对接IPFS的SDK https://blog.csdn.net/weixin_42793960/article/details/117441564

多节点IPFS搭建以及采用Java SDK通信 https://www.cnblogs.com/lyon-liu/p/14848958.html

ipfs - 命令行简介 http://cw.hubwiz.com/card/c/ipfs/1/1/1/

docker部署IPFS私有链 https://blog.csdn.net/cljdsc/article/details/110849702

赞赏一下

The MIT License (MIT) Copyright (c) 2019 RuoYi-Vue-Plus Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

简介

SpringBoot Vue Hyperledger Fabric 展开 收起
MIT
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
1
https://gitee.com/andrew-y/he_contract.git
git@gitee.com:andrew-y/he_contract.git
andrew-y
he_contract
基于区块链技术的电子合同管理系统后台
master

搜索帮助