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标准国际化方案 |
本系统后台使用框架为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
详见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;
}
}
}
- 首先搭建minifab在云服务器上,然后将minifab生成的“peerOrganizations"和”ordererOrganizations“文件复制到本地(文件路径:vars/keyfiles),创建一个名叫crypto-config的父目录保存(此处可自定义,需修改application.yml文件对照自己保存的路径)
- 然后将minifab生成的网络配置文件也保存到本地(文件路径:vars/profiles),里面有各种语言的连接配置文件,按需所求
连接Fabric相关代码
--- # 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
@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;
}
@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);
}
}
}
调用链码相关代码
/**
* 修改审核状态
*
* @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);
}
@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);
}
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();
}
}
}
}
}
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"
./minifab up -n simple -l go -v 1.0 -r true -o bc1.henriport.com
./minifab restart
./minifab explorerup
./minifab explorerdown
./minifab down
./minifab cleanup
更多命令可通过如下命令查看
./minifab -h
请看链码文件->main.go,仅供参考
-
根据例子自定义自己的链码
-
将go.mod、go.sum、main.go文件打包新建名为"go"的文件夹
-
在\minifabric-main\vars\chaincode\目录下创建自己自定义链码的文件夹,比如"contract"
-
将"go"文件夹复制到"contract"文件夹下
-
回到\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"'
- 代理被拒绝
==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