Hyperledger Fabric 1.0 从零开始(十)——智能合约(参阅:Hyperledger Fabric Chaincode for Operators——实操智能合约

Hyperledger Fabric 1.0 从零开始(十一)——CouchDB(参阅:Hyperledger Fabric CouchDB as the State Database——使用CouchDB

上述两章,最近网上各路大神文章云集,方案多多,因为最近工作太忙太忙,我暂时就先不赘述了,可以优先参考深蓝大大的文章HyperLedger Fabric ChainCode开发——shim.ChaincodeStubInterface用法

这章先捡大家都比较在意的java sdk应用方案贴出来,很多朋友都找我要过,我主要是把注释都写进去了,用法简单了说了下,一般情况下会java开发的都能看懂。

年前实在太忙。

JAVA-SDK

9.1基本介绍

官方在Fabric1.0之后就推荐使用SDK来实现交互的操作,原本在0.6上的restapi已经被废弃。JAVA-SDK可以参考github。官方给的SDK的DEMO很难入手,注释稀少内容还很杂,所幸感谢github上有位朋友(具体地址实在是找不到了,以后会补上)稍加整理了一下,直接重写了官方的DEMO,让像我这样的新人更容易入手。

本次在官方SDK和大神重新的DEMO的基础上做了一次整理,能够更加清晰简单的入门并使用JAVA-SDK来进行交互。

9.2架构

相信看到这里的朋友应该都对Fabric已经有了足够的了解,至少是应用层上已经可以实现分布式账本的各项功能,sdk也是在这样的基础进行讲述。

首先看下针对JAVA-SDK所写的辅助工程目录

关于Fabric,我们知道一个channel中可以创建多个chaincode,而一个chaincode需要指定对应orderer和peer。

所以,在此建立了一个bean目录来存放自定义的chaincode、orderer和peer对象。这几个对象都包含各自最基本的访问属性。

具体对应关系如下:

bean.Chaincode - Fabric创建的chaincode信息,涵盖所属channel等信息

bean.Orderers  - Fabric创建的orderer信息,涵盖单机和集群两种方案

bean.Peers     - Fabric创建的peer信息,包含有cli、org、ca、couchdb等节点服务器关联启动服务信息集合

ChaincodeManager - 智能合约操作总控制器

FabricConfig - 智能合约操作总参数配置器

FabricOrg - 联盟组织对象

FabricUser - 联盟用户对象

FabricStore - 联盟存储配置对象

9.3具体实现

直接通过代码来看实现

9.3.1、Chaincode代码

 package cn.aberic.fabric.bean;

 /**
* Fabric创建的chaincode信息,涵盖所属channel等信息
*
* @author aberic
*
* @date 2017年10月18日 - 下午2:07:42
* @email abericyang@gmail.com
*/
public class Chaincode { /** 当前将要访问的智能合约所属频道名称 */
private String channelName; // ffetest
/** 智能合约名称 */
private String chaincodeName; // ffetestcc
/** 智能合约安装路径 */
private String chaincodePath; // github.com/hyperledger/fabric/xxx/chaincode/go/example/test
/** 智能合约版本号 */
private String chaincodeVersion; // 1.0
/** 执行智能合约操作等待时间 */
private int invokeWatiTime = 100000;
/** 执行智能合约实例等待时间 */
private int deployWatiTime = 120000; public String getChannelName() {
return channelName;
} public void setChannelName(String channelName) {
this.channelName = channelName;
} public String getChaincodeName() {
return chaincodeName;
} public void setChaincodeName(String chaincodeName) {
this.chaincodeName = chaincodeName;
} public String getChaincodePath() {
return chaincodePath;
} public void setChaincodePath(String chaincodePath) {
this.chaincodePath = chaincodePath;
} public String getChaincodeVersion() {
return chaincodeVersion;
} public void setChaincodeVersion(String chaincodeVersion) {
this.chaincodeVersion = chaincodeVersion;
} public int getInvokeWatiTime() {
return invokeWatiTime;
} public void setInvokeWatiTime(int invokeWatiTime) {
this.invokeWatiTime = invokeWatiTime;
} public int getDeployWatiTime() {
return deployWatiTime;
} public void setDeployWatiTime(int deployWatiTime) {
this.deployWatiTime = deployWatiTime;
} }

9.3.2、Orderers代码

 package cn.aberic.fabric.bean;

 import java.util.ArrayList;
import java.util.List; /**
* Fabric创建的orderer信息,涵盖单机和集群两种方案
*
* @author aberic
*
* @date 2017年10月18日 - 下午1:56:48
* @email abericyang@gmail.com
*/
public class Orderers { /** orderer 排序服务器所在根域名 */
private String ordererDomainName; // anti-moth.com
/** orderer 排序服务器集合 */
private List<Orderer> orderers; public Orderers() {
orderers = new ArrayList<>();
} public String getOrdererDomainName() {
return ordererDomainName;
} public void setOrdererDomainName(String ordererDomainName) {
this.ordererDomainName = ordererDomainName;
} /** 新增排序服务器 */
public void addOrderer(String name, String location) {
orderers.add(new Orderer(name, location));
} /** 获取排序服务器集合 */
public List<Orderer> get() {
return orderers;
} /**
* 排序服务器对象
*
* @author aberic
*
* @date 2017年10月18日 - 下午2:06:22
* @email abericyang@gmail.com
*/
public class Orderer { /** orderer 排序服务器的域名 */
private String ordererName;
/** orderer 排序服务器的访问地址 */
private String ordererLocation; public Orderer(String ordererName, String ordererLocation) {
super();
this.ordererName = ordererName;
this.ordererLocation = ordererLocation;
} public String getOrdererName() {
return ordererName;
} public void setOrdererName(String ordererName) {
this.ordererName = ordererName;
} public String getOrdererLocation() {
return ordererLocation;
} public void setOrdererLocation(String ordererLocation) {
this.ordererLocation = ordererLocation;
} } }

9.3.3、Peers代码

 package cn.aberic.fabric.bean;

 import java.util.ArrayList;
import java.util.List; /**
* Fabric创建的peer信息,包含有cli、org、ca、couchdb等节点服务器关联启动服务信息集合
*
* @author aberic
*
* @date 2017年10月18日 - 下午1:49:03
* @email abericyang@gmail.com
*/
public class Peers { /** 当前指定的组织名称 */
private String orgName; // Org1
/** 当前指定的组织名称 */
private String orgMSPID; // Org1MSP
/** 当前指定的组织所在根域名 */
private String orgDomainName; //org1.example.com
/** orderer 排序服务器集合 */
private List<Peer> peers; public Peers() {
peers = new ArrayList<>();
} public String getOrgName() {
return orgName;
} public void setOrgName(String orgName) {
this.orgName = orgName;
} public String getOrgMSPID() {
return orgMSPID;
} public void setOrgMSPID(String orgMSPID) {
this.orgMSPID = orgMSPID;
} public String getOrgDomainName() {
return orgDomainName;
} public void setOrgDomainName(String orgDomainName) {
this.orgDomainName = orgDomainName;
} /** 新增排序服务器 */
public void addPeer(String peerName, String peerEventHubName, String peerLocation, String peerEventHubLocation, String caLocation) {
peers.add(new Peer(peerName, peerEventHubName, peerLocation, peerEventHubLocation, caLocation));
} /** 获取排序服务器集合 */
public List<Peer> get() {
return peers;
} /**
* 节点服务器对象
*
* @author aberic
*
* @date 2017年11月11日 - 下午6:56:14
* @email abericyang@gmail.com
*/
public class Peer { /** 当前指定的组织节点域名 */
private String peerName; // peer0.org1.example.com
/** 当前指定的组织节点事件域名 */
private String peerEventHubName; // peer0.org1.example.com
/** 当前指定的组织节点访问地址 */
private String peerLocation; // grpc://110.131.116.21:7051
/** 当前指定的组织节点事件监听访问地址 */
private String peerEventHubLocation; // grpc://110.131.116.21:7053
/** 当前指定的组织节点ca访问地址 */
private String caLocation; // http://110.131.116.21:7054
/** 当前peer是否增加Event事件处理 */
private boolean addEventHub = false; public Peer(String peerName, String peerEventHubName, String peerLocation, String peerEventHubLocation, String caLocation) {
this.peerName = peerName;
this.peerEventHubName = peerEventHubName;
this.peerLocation = peerLocation;
this.peerEventHubLocation = peerEventHubLocation;
this.caLocation = caLocation;
} public String getPeerName() {
return peerName;
} public void setPeerName(String peerName) {
this.peerName = peerName;
} public String getPeerEventHubName() {
return peerEventHubName;
} public void setPeerEventHubName(String peerEventHubName) {
this.peerEventHubName = peerEventHubName;
} public String getPeerLocation() {
return peerLocation;
} public void setPeerLocation(String peerLocation) {
this.peerLocation = peerLocation;
} public String getPeerEventHubLocation() {
return peerEventHubLocation;
} public void setPeerEventHubLocation(String eventHubLocation) {
this.peerEventHubLocation = eventHubLocation;
} public String getCaLocation() {
return caLocation;
} public void setCaLocation(String caLocation) {
this.caLocation = caLocation;
} public boolean isAddEventHub() {
return addEventHub;
} public void addEventHub(boolean addEventHub) {
this.addEventHub = addEventHub;
} } }

9.3.4、FabricUser代码

 package cn.aberic.fabric;

 import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Set; import org.bouncycastle.util.encoders.Hex;
import org.hyperledger.fabric.sdk.Enrollment;
import org.hyperledger.fabric.sdk.User; import io.netty.util.internal.StringUtil; /**
* 联盟用户对象
*
* @author aberic
*
* @date 2017年9月7日 - 下午4:36:53
* @email abericyang@gmail.com
*/
class FabricUser implements User, Serializable { private static final long serialVersionUID = 5695080465408336815L; /** 名称 */
private String name;
/** 规则 */
private Set<String> roles;
/** 账户 */
private String account;
/** 从属联盟 */
private String affiliation;
/** 组织 */
private String organization;
/** 注册操作的密�? */
private String enrollmentSecret;
/** 会员id */
private String mspId;
/** 注册登记操作 */
Enrollment enrollment = null; // �?要在测试env中访�? /** 存储配置对象 */
private transient FabricStore keyValStore;
private String keyValStoreName; public FabricUser(String name, String org, FabricStore store) {
this.name = name;
this.keyValStore = store;
this.organization = org;
this.keyValStoreName = toKeyValStoreName(this.name, org); String memberStr = keyValStore.getValue(keyValStoreName);
if (null != memberStr) {
saveState();
} else {
restoreState();
}
} /**
* 设置账户信息并将用户状�?�更新至存储配置对象
*
* @param account
* 账户
*/
public void setAccount(String account) {
this.account = account;
saveState();
} @Override
public String getAccount() {
return this.account;
} /**
* 设置从属联盟信息并将用户状�?�更新至存储配置对象
*
* @param affiliation
* 从属联盟
*/
public void setAffiliation(String affiliation) {
this.affiliation = affiliation;
saveState();
} @Override
public String getAffiliation() {
return this.affiliation;
} @Override
public Enrollment getEnrollment() {
return this.enrollment;
} /**
* 设置会员id信息并将用户状�?�更新至存储配置对象
*
* @param mspID
* 会员id
*/
public void setMspId(String mspID) {
this.mspId = mspID;
saveState();
} @Override
public String getMspId() {
return this.mspId;
} @Override
public String getName() {
return this.name;
} /**
* 设置规则信息并将用户状�?�更新至存储配置对象
*
* @param roles
* 规则
*/
public void setRoles(Set<String> roles) {
this.roles = roles;
saveState();
} @Override
public Set<String> getRoles() {
return this.roles;
} public String getEnrollmentSecret() {
return enrollmentSecret;
} /**
* 设置注册操作的密钥信息并将用户状态更新至存储配置对象
*
* @param enrollmentSecret
* 注册操作的密�?
*/
public void setEnrollmentSecret(String enrollmentSecret) {
this.enrollmentSecret = enrollmentSecret;
saveState();
} /**
* 设置注册登记操作信息并将用户状�?�更新至存储配置对象
*
* @param enrollment
* 注册登记操作
*/
public void setEnrollment(Enrollment enrollment) {
this.enrollment = enrollment;
saveState();
} /**
* 确定这个名称是否已注�?
*
* @return 与否
*/
public boolean isRegistered() {
return !StringUtil.isNullOrEmpty(enrollmentSecret);
} /**
* 确定这个名字是否已经注册
*
* @return 与否
*/
public boolean isEnrolled() {
return this.enrollment != null;
} /** 将用户状态保存至存储配置对象 */
public void saveState() {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
oos.flush();
keyValStore.setValue(keyValStoreName, Hex.toHexString(bos.toByteArray()));
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
} /**
* 从键值存储中恢复该用户的状�??(如果找到的话)。如果找不到,什么也不要做�??
*
* @return 返回用户
*/
private FabricUser restoreState() {
String memberStr = keyValStore.getValue(keyValStoreName);
if (null != memberStr) {
// 用户在键值存储中被找到,因此恢复状�?��??
byte[] serialized = Hex.decode(memberStr);
ByteArrayInputStream bis = new ByteArrayInputStream(serialized);
try {
ObjectInputStream ois = new ObjectInputStream(bis);
FabricUser state = (FabricUser) ois.readObject();
if (state != null) {
this.name = state.name;
this.roles = state.roles;
this.account = state.account;
this.affiliation = state.affiliation;
this.organization = state.organization;
this.enrollmentSecret = state.enrollmentSecret;
this.enrollment = state.enrollment;
this.mspId = state.mspId;
return this;
}
} catch (Exception e) {
throw new RuntimeException(String.format("Could not restore state of member %s", this.name), e);
}
}
return null;
} public static String toKeyValStoreName(String name, String org) {
System.out.println("toKeyValStoreName = " + "user." + name + org);
return "user." + name + org;
} }

9.3.5、FabricOrg代码

 package cn.aberic.fabric;

 import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.spec.InvalidKeySpecException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set; import org.apache.log4j.Logger;
import org.hyperledger.fabric.sdk.Peer;
import org.hyperledger.fabric.sdk.User;
import org.hyperledger.fabric_ca.sdk.HFCAClient; import cn.aberic.fabric.bean.Orderers; /**
* 联盟组织对象
*
* @author aberic
*
* @date 2017年9月7日 - 下午4:35:40
* @email abericyang@gmail.com
*/
class FabricOrg { private static Logger log = Logger.getLogger(FabricOrg.class); /** 名称 */
private String name;
/** 会员id */
private String mspid;
/** ca 客户端 */
private HFCAClient caClient; /** 用户集合 */
Map<String, User> userMap = new HashMap<>();
/** 本地节点集合 */
Map<String, String> peerLocations = new HashMap<>();
/** 本地排序服务集合 */
Map<String, String> ordererLocations = new HashMap<>();
/** 本地事件集合 */
Map<String, String> eventHubLocations = new HashMap<>();
/** 节点集合 */
Set<Peer> peers = new HashSet<>();
/** 联盟管理员用户 */
private FabricUser admin;
/** 本地 ca */
private String caLocation;
/** ca 配置 */
private Properties caProperties = null; /** 联盟单节点管理员用户 */
private FabricUser peerAdmin; /** 域名名称 */
private String domainName; public FabricOrg(cn.aberic.fabric.bean.Peers peers, Orderers orderers, FabricStore fabricStore, String cryptoConfigPath)
throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException, IOException {
this.name = peers.getOrgName();
this.mspid = peers.getOrgMSPID();
for (int i = 0; i < peers.get().size(); i++) {
addPeerLocation(peers.get().get(i).getPeerName(), peers.get().get(i).getPeerLocation());
addEventHubLocation(peers.get().get(i).getPeerEventHubName(), peers.get().get(i).getPeerEventHubLocation());
setCALocation(peers.get().get(i).getCaLocation());
}
for (int i = 0; i < orderers.get().size(); i++) {
addOrdererLocation(orderers.get().get(i).getOrdererName(), orderers.get().get(i).getOrdererLocation());
}
setDomainName(peers.getOrgDomainName()); // domainName=tk.anti-moth.com // Set up HFCA for Org1
// setCAClient(HFCAClient.createNewInstance(peers.getCaLocation(), getCAProperties())); setAdmin(fabricStore.getMember("admin", peers.getOrgName())); // 设置该组织的管理员 File skFile = Paths.get(cryptoConfigPath, "/peerOrganizations/", peers.getOrgDomainName(), String.format("/users/Admin@%s/msp/keystore", peers.getOrgDomainName())).toFile();
File certificateFile = Paths.get(cryptoConfigPath, "/peerOrganizations/", peers.getOrgDomainName(),
String.format("/users/Admin@%s/msp/signcerts/Admin@%s-cert.pem", peers.getOrgDomainName(), peers.getOrgDomainName())).toFile();
log.debug("skFile = " + skFile.getAbsolutePath());
log.debug("certificateFile = " + certificateFile.getAbsolutePath());
setPeerAdmin(fabricStore.getMember(peers.getOrgName() + "Admin", peers.getOrgName(), peers.getOrgMSPID(), findFileSk(skFile), certificateFile)); // 一个特殊的用户,可以创建通道,连接对等点,并安装链码
} public String getName() {
return name;
} /**
* 获取联盟管理员用户
*
* @return 联盟管理员用户
*/
public FabricUser getAdmin() {
return admin;
} /**
* 设置联盟管理员用户
*
* @param admin
* 联盟管理员用户
*/
public void setAdmin(FabricUser admin) {
this.admin = admin;
} /**
* 获取会员id
*
* @return 会员id
*/
public String getMSPID() {
return mspid;
} /**
* 设置本地ca
*
* @param caLocation
* 本地ca
*/
public void setCALocation(String caLocation) {
this.caLocation = caLocation;
} /**
* 获取本地ca
*
* @return 本地ca
*/
public String getCALocation() {
return this.caLocation;
} /**
* 添加本地节点
*
* @param name
* 节点key
* @param location
* 节点
*/
public void addPeerLocation(String name, String location) {
peerLocations.put(name, location);
} /**
* 添加本地组织
*
* @param name
* 组织key
* @param location
* 组织
*/
public void addOrdererLocation(String name, String location) {
ordererLocations.put(name, location);
} /**
* 添加本地事件
*
* @param name
* 事件key
* @param location
* 事件
*/
public void addEventHubLocation(String name, String location) {
eventHubLocations.put(name, location);
} /**
* 获取本地节点
*
* @param name
* 节点key
* @return 节点
*/
public String getPeerLocation(String name) {
return peerLocations.get(name);
} /**
* 获取本地组织
*
* @param name
* 组织key
* @return 组织
*/
public String getOrdererLocation(String name) {
return ordererLocations.get(name);
} /**
* 获取本地事件
*
* @param name
* 事件key
* @return 事件
*/
public String getEventHubLocation(String name) {
return eventHubLocations.get(name);
} /**
* 获取一个不可修改的本地节点key集合
*
* @return 节点key集合
*/
public Set<String> getPeerNames() {
return Collections.unmodifiableSet(peerLocations.keySet());
} /**
* 获取一个不可修改的本地节点集合
*
* @return 节点集合
*/
public Set<Peer> getPeers() {
return Collections.unmodifiableSet(peers);
} /**
* 获取一个不可修改的本地组织key集合
*
* @return 组织key集合
*/
public Set<String> getOrdererNames() {
return Collections.unmodifiableSet(ordererLocations.keySet());
} /**
* 获取一个不可修改的本地组织集合
*
* @return 组织集合
*/
public Collection<String> getOrdererLocations() {
return Collections.unmodifiableCollection(ordererLocations.values());
} /**
* 获取一个不可修改的本地事件key集合
*
* @return 事件key集合
*/
public Set<String> getEventHubNames() {
return Collections.unmodifiableSet(eventHubLocations.keySet());
} /**
* 获取一个不可修改的本地事件集合
*
* @return 事件集合
*/
public Collection<String> getEventHubLocations() {
return Collections.unmodifiableCollection(eventHubLocations.values());
} /**
* 设置 ca 客户端
*
* @param caClient
* ca 客户端
*/
public void setCAClient(HFCAClient caClient) {
this.caClient = caClient;
} /**
* 获取 ca 客户端
*
* @return ca 客户端
*/
public HFCAClient getCAClient() {
return caClient;
} /**
* 向用户集合中添加用户
*
* @param user
* 用户
*/
public void addUser(FabricUser user) {
userMap.put(user.getName(), user);
} /**
* 从用户集合根据名称获取用户
*
* @param name
* 名称
* @return 用户
*/
public User getUser(String name) {
return userMap.get(name);
} /**
* 向节点集合中添加节点
*
* @param peer
* 节点
*/
public void addPeer(Peer peer) {
peers.add(peer);
} /**
* 设置 ca 配置
*
* @param caProperties
* ca 配置
*/
public void setCAProperties(Properties caProperties) {
this.caProperties = caProperties;
} /**
* 获取 ca 配置
*
* @return ca 配置
*/
public Properties getCAProperties() {
return caProperties;
} /**
* 设置联盟单节点管理员用户
*
* @param peerAdmin
* 联盟单节点管理员用户
*/
public void setPeerAdmin(FabricUser peerAdmin) {
this.peerAdmin = peerAdmin;
} /**
* 获取联盟单节点管理员用户
*
* @return 联盟单节点管理员用户
*/
public FabricUser getPeerAdmin() {
return peerAdmin;
} /**
* 设置域名名称
*
* @param doainName
* 域名名称
*/
public void setDomainName(String domainName) {
this.domainName = domainName;
} /**
* 获取域名名称
*
* @return 域名名称
*/
public String getDomainName() {
return domainName;
} /**
* 从指定路径中获取后缀为 _sk 的文件,且该路径下有且仅有该文件
*
* @param directorys
* 指定路径
* @return File
*/
private File findFileSk(File directory) {
File[] matches = directory.listFiles((dir, name) -> name.endsWith("_sk"));
if (null == matches) {
throw new RuntimeException(String.format("Matches returned null does %s directory exist?", directory.getAbsoluteFile().getName()));
}
if (matches.length != 1) {
throw new RuntimeException(String.format("Expected in %s only 1 sk file but found %d", directory.getAbsoluteFile().getName(), matches.length));
}
return matches[0];
} }

9.3.6、FabricStore代码

 package cn.aberic.fabric;

 import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Serializable;
import java.io.StringReader;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.Security;
import java.security.spec.InvalidKeySpecException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties; import org.apache.commons.io.IOUtils;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.hyperledger.fabric.sdk.Enrollment; /**
* 联盟存储配置对象
*
* @author aberic
*
* @date 2017年9月7日 - 下午4:36:19
* @email abericyang@gmail.com
*/
class FabricStore { private String file;
/** 用户信息集合 */
private final Map<String, FabricUser> members = new HashMap<>(); public FabricStore(File file) {
this.file = file.getAbsolutePath();
} /**
* 设置与名称相关的值
*
* @param name
* 名称
* @param value
* 相关值
*/
public void setValue(String name, String value) {
Properties properties = loadProperties();
try (OutputStream output = new FileOutputStream(file)) {
properties.setProperty(name, value);
properties.store(output, "");
output.close();
} catch (IOException e) {
System.out.println(String.format("Could not save the keyvalue store, reason:%s", e.getMessage()));
}
} /**
* 获取与名称相关的值
*
* @param 名称
* @return 相关值
*/
public String getValue(String name) {
Properties properties = loadProperties();
return properties.getProperty(name);
} /**
* 加载配置文件
*
* @return 配置文件对象
*/
private Properties loadProperties() {
Properties properties = new Properties();
try (InputStream input = new FileInputStream(file)) {
properties.load(input);
input.close();
} catch (FileNotFoundException e) {
System.out.println(String.format("Could not find the file \"%s\"", file));
} catch (IOException e) {
System.out.println(String.format("Could not load keyvalue store from file \"%s\", reason:%s", file, e.getMessage()));
}
return properties;
} /**
* 用给定的名称获取用户
*
* @param 名称
* @param 组织
*
* @return 用户
*/
public FabricUser getMember(String name, String org) {
// 尝试从缓存中获取User状�??
FabricUser fabricUser = members.get(FabricUser.toKeyValStoreName(name, org));
if (null != fabricUser) {
return fabricUser;
}
// 创建User,并尝试从键值存储中恢复它的状�??(如果找到的话)�?
fabricUser = new FabricUser(name, org, this);
return fabricUser;
} /**
* 用给定的名称获取用户
*
* @param name
* 名称
* @param org
* 组织
* @param mspId
* 会员id
* @param privateKeyFile
* @param certificateFile
*
* @return user 用户
*
* @throws IOException
* @throws NoSuchAlgorithmException
* @throws NoSuchProviderException
* @throws InvalidKeySpecException
*/
public FabricUser getMember(String name, String org, String mspId, File privateKeyFile, File certificateFile)
throws IOException, NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException {
try {
// 尝试从缓存中获取User状�??
FabricUser fabricUser = members.get(FabricUser.toKeyValStoreName(name, org));
if (null != fabricUser) {
System.out.println("尝试从缓存中获取User状�?? User = " + fabricUser);
return fabricUser;
}
// 创建User,并尝试从键值存储中恢复它的状�??(如果找到的话)�?
fabricUser = new FabricUser(name, org, this);
fabricUser.setMspId(mspId);
String certificate = new String(IOUtils.toByteArray(new FileInputStream(certificateFile)), "UTF-8");
PrivateKey privateKey = getPrivateKeyFromBytes(IOUtils.toByteArray(new FileInputStream(privateKeyFile)));
fabricUser.setEnrollment(new StoreEnrollement(privateKey, certificate));
return fabricUser;
} catch (IOException e) {
e.printStackTrace();
throw e;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
throw e;
} catch (NoSuchProviderException e) {
e.printStackTrace();
throw e;
} catch (InvalidKeySpecException e) {
e.printStackTrace();
throw e;
} catch (ClassCastException e) {
e.printStackTrace();
throw e;
}
} /**
* 通过字节数组信息获取私钥
*
* @param data
* 字节数组
*
* @return 私钥
*
* @throws IOException
* @throws NoSuchProviderException
* @throws NoSuchAlgorithmException
* @throws InvalidKeySpecException
*/
private PrivateKey getPrivateKeyFromBytes(byte[] data) throws IOException, NoSuchProviderException, NoSuchAlgorithmException, InvalidKeySpecException {
final Reader pemReader = new StringReader(new String(data));
final PrivateKeyInfo pemPair;
try (PEMParser pemParser = new PEMParser(pemReader)) {
pemPair = (PrivateKeyInfo) pemParser.readObject();
}
PrivateKey privateKey = new JcaPEMKeyConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME).getPrivateKey(pemPair);
return privateKey;
} static {
try {
Security.addProvider(new BouncyCastleProvider());
} catch (Exception e) {
e.printStackTrace();
}
} /**
* 自定义注册登记操作类
*
* @author yangyi47
*
*/
static final class StoreEnrollement implements Enrollment, Serializable { private static final long serialVersionUID = 6965341351799577442L; /** 私钥 */
private final PrivateKey privateKey;
/** 授权证书 */
private final String certificate; StoreEnrollement(PrivateKey privateKey, String certificate) {
this.certificate = certificate;
this.privateKey = privateKey;
} @Override
public PrivateKey getKey() {
return privateKey;
} @Override
public String getCert() {
return certificate;
}
} }

9.3.7、FabricConfig代码

 package cn.aberic.fabric;

 import java.io.File;

 import org.apache.log4j.Logger;

 import cn.aberic.fabric.bean.Chaincode;
import cn.aberic.fabric.bean.Orderers;
import cn.aberic.fabric.bean.Peers; public class FabricConfig { private static Logger log = Logger.getLogger(FabricConfig.class); /** 节点服务器对象 */
private Peers peers;
/** 排序服务器对象 */
private Orderers orderers;
/** 智能合约对象 */
private Chaincode chaincode;
/** channel-artifacts所在路径:默认channel-artifacts所在路径/xxx/WEB-INF/classes/fabric/channel-artifacts/ */
private String channelArtifactsPath;
/** crypto-config所在路径:默认crypto-config所在路径/xxx/WEB-INF/classes/fabric/crypto-config/ */
private String cryptoConfigPath;
private boolean registerEvent = false; public FabricConfig() {
// 默认channel-artifacts所在路径 /xxx/WEB-INF/classes/fabric/channel-artifacts/
channelArtifactsPath = getChannlePath() + "/channel-artifacts/";
// 默认crypto-config所在路径 /xxx/WEB-INF/classes/fabric/crypto-config/
cryptoConfigPath = getChannlePath() + "/crypto-config/";
} /**
* 默认fabric配置路径
*
* @return D:/installSoft/apache-tomcat-9.0.0.M21-02/webapps/xxx/WEB-INF/classes/fabric/channel-artifacts/
*/
private String getChannlePath() {
String directorys = ChaincodeManager.class.getClassLoader().getResource("fabric").getFile();
log.debug("directorys = " + directorys);
File directory = new File(directorys);
log.debug("directory = " + directory.getPath()); return directory.getPath();
// return "src/main/resources/fabric/channel-artifacts/";
} public Peers getPeers() {
return peers;
} public void setPeers(Peers peers) {
this.peers = peers;
} public Orderers getOrderers() {
return orderers;
} public void setOrderers(Orderers orderers) {
this.orderers = orderers;
} public Chaincode getChaincode() {
return chaincode;
} public void setChaincode(Chaincode chaincode) {
this.chaincode = chaincode;
} public String getChannelArtifactsPath() {
return channelArtifactsPath;
} public void setChannelArtifactsPath(String channelArtifactsPath) {
this.channelArtifactsPath = channelArtifactsPath;
} public String getCryptoConfigPath() {
return cryptoConfigPath;
} public void setCryptoConfigPath(String cryptoConfigPath) {
this.cryptoConfigPath = cryptoConfigPath;
} public boolean isRegisterEvent() {
return registerEvent;
} public void setRegisterEvent(boolean registerEvent) {
this.registerEvent = registerEvent;
} }

9.3.8、ChaincodeManager代码

 package cn.aberic.fabric;

 import static java.nio.charset.StandardCharsets.UTF_8;

 import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.spec.InvalidKeySpecException;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException; import org.apache.log4j.Logger;
import org.hyperledger.fabric.sdk.BlockEvent;
import org.hyperledger.fabric.sdk.BlockListener;
import org.hyperledger.fabric.sdk.ChaincodeID;
import org.hyperledger.fabric.sdk.Channel;
import org.hyperledger.fabric.sdk.HFClient;
import org.hyperledger.fabric.sdk.ProposalResponse;
import org.hyperledger.fabric.sdk.QueryByChaincodeRequest;
import org.hyperledger.fabric.sdk.SDKUtils;
import org.hyperledger.fabric.sdk.TransactionProposalRequest;
import org.hyperledger.fabric.sdk.exception.CryptoException;
import org.hyperledger.fabric.sdk.exception.InvalidArgumentException;
import org.hyperledger.fabric.sdk.exception.ProposalException;
import org.hyperledger.fabric.sdk.exception.TransactionException;
import org.hyperledger.fabric.sdk.security.CryptoSuite; import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException; import cn.aberic.fabric.bean.Chaincode;
import cn.aberic.fabric.bean.Orderers;
import cn.aberic.fabric.bean.Peers; public class ChaincodeManager { private static Logger log = Logger.getLogger(ChaincodeManager.class); private FabricConfig config;
private Orderers orderers;
private Peers peers;
private Chaincode chaincode; private HFClient client;
private FabricOrg fabricOrg;
private Channel channel;
private ChaincodeID chaincodeID; public ChaincodeManager(FabricConfig fabricConfig)
throws CryptoException, InvalidArgumentException, NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException, IOException, TransactionException {
this.config = fabricConfig; orderers = this.config.getOrderers();
peers = this.config.getPeers();
chaincode = this.config.getChaincode(); client = HFClient.createNewInstance();
log.debug("Create instance of HFClient");
client.setCryptoSuite(CryptoSuite.Factory.getCryptoSuite());
log.debug("Set Crypto Suite of HFClient"); fabricOrg = getFabricOrg();
channel = getChannel();
chaincodeID = getChaincodeID(); client.setUserContext(fabricOrg.getPeerAdmin()); // 也许是1.0.0测试版的bug,只有节点管理员可以调用链码
} private FabricOrg getFabricOrg() throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException, IOException { // java.io.tmpdir : C:\Users\yangyi47\AppData\Local\Temp\
File storeFile = new File(System.getProperty("java.io.tmpdir") + "/HFCSampletest.properties");
FabricStore fabricStore = new FabricStore(storeFile); // Get Org1 from configuration
FabricOrg fabricOrg = new FabricOrg(peers, orderers, fabricStore, config.getCryptoConfigPath());
log.debug("Get FabricOrg");
return fabricOrg;
} private Channel getChannel()
throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException, IOException, CryptoException, InvalidArgumentException, TransactionException {
client.setUserContext(fabricOrg.getPeerAdmin());
return getChannel(fabricOrg, client);
} private Channel getChannel(FabricOrg fabricOrg, HFClient client)
throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException, IOException, CryptoException, InvalidArgumentException, TransactionException {
Channel channel = client.newChannel(chaincode.getChannelName());
log.debug("Get Chain " + chaincode.getChannelName()); // channel.setTransactionWaitTime(chaincode.getInvokeWatiTime());
// channel.setDeployWaitTime(chaincode.getDeployWatiTime()); for (int i = 0; i < peers.get().size(); i++) {
File peerCert = Paths.get(config.getCryptoConfigPath(), "/peerOrganizations", peers.getOrgDomainName(), "peers", peers.get().get(i).getPeerName(), "tls/server.crt")
.toFile();
if (!peerCert.exists()) {
throw new RuntimeException(
String.format("Missing cert file for: %s. Could not find at location: %s", peers.get().get(i).getPeerName(), peerCert.getAbsolutePath()));
}
Properties peerProperties = new Properties();
peerProperties.setProperty("pemFile", peerCert.getAbsolutePath());
// ret.setProperty("trustServerCertificate", "true"); //testing
// environment only NOT FOR PRODUCTION!
peerProperties.setProperty("hostnameOverride", peers.getOrgDomainName());
peerProperties.setProperty("sslProvider", "openSSL");
peerProperties.setProperty("negotiationType", "TLS");
// 在grpc的NettyChannelBuilder上设置特定选项
peerProperties.put("grpc.ManagedChannelBuilderOption.maxInboundMessageSize", 9000000);
channel.addPeer(client.newPeer(peers.get().get(i).getPeerName(), fabricOrg.getPeerLocation(peers.get().get(i).getPeerName()), peerProperties));
if (peers.get().get(i).isAddEventHub()) {
channel.addEventHub(
client.newEventHub(peers.get().get(i).getPeerEventHubName(), fabricOrg.getEventHubLocation(peers.get().get(i).getPeerEventHubName()), peerProperties));
}
} for (int i = 0; i < orderers.get().size(); i++) {
File ordererCert = Paths.get(config.getCryptoConfigPath(), "/ordererOrganizations", orderers.getOrdererDomainName(), "orderers", orderers.get().get(i).getOrdererName(),
"tls/server.crt").toFile();
if (!ordererCert.exists()) {
throw new RuntimeException(
String.format("Missing cert file for: %s. Could not find at location: %s", orderers.get().get(i).getOrdererName(), ordererCert.getAbsolutePath()));
}
Properties ordererProperties = new Properties();
ordererProperties.setProperty("pemFile", ordererCert.getAbsolutePath());
ordererProperties.setProperty("hostnameOverride", orderers.getOrdererDomainName());
ordererProperties.setProperty("sslProvider", "openSSL");
ordererProperties.setProperty("negotiationType", "TLS");
ordererProperties.put("grpc.ManagedChannelBuilderOption.maxInboundMessageSize", 9000000);
ordererProperties.setProperty("ordererWaitTimeMilliSecs", "300000");
channel.addOrderer(
client.newOrderer(orderers.get().get(i).getOrdererName(), fabricOrg.getOrdererLocation(orderers.get().get(i).getOrdererName()), ordererProperties));
} log.debug("channel.isInitialized() = " + channel.isInitialized());
if (!channel.isInitialized()) {
channel.initialize();
}
if (config.isRegisterEvent()) {
channel.registerBlockListener(new BlockListener() { @Override
public void received(BlockEvent event) {
// TODO
log.debug("========================Event事件监听开始========================");
try {
log.debug("event.getChannelId() = " + event.getChannelId());
log.debug("event.getEvent().getChaincodeEvent().getPayload().toStringUtf8() = " + event.getEvent().getChaincodeEvent().getPayload().toStringUtf8());
log.debug("event.getBlock().getData().getDataList().size() = " + event.getBlock().getData().getDataList().size());
ByteString byteString = event.getBlock().getData().getData(0);
String result = byteString.toStringUtf8();
log.debug("byteString.toStringUtf8() = " + result); String r1[] = result.split("END CERTIFICATE");
String rr = r1[2];
log.debug("rr = " + rr);
} catch (InvalidProtocolBufferException e) {
// TODO
e.printStackTrace();
}
log.debug("========================Event事件监听结束========================");
}
});
}
return channel;
} private ChaincodeID getChaincodeID() {
return ChaincodeID.newBuilder().setName(chaincode.getChaincodeName()).setVersion(chaincode.getChaincodeVersion()).setPath(chaincode.getChaincodePath()).build();
} /**
* 执行智能合约
*
* @param fcn
* 方法名
* @param args
* 参数数组
* @return
* @throws InvalidArgumentException
* @throws ProposalException
* @throws InterruptedException
* @throws ExecutionException
* @throws TimeoutException
* @throws IOException
* @throws TransactionException
* @throws CryptoException
* @throws InvalidKeySpecException
* @throws NoSuchProviderException
* @throws NoSuchAlgorithmException
*/
public Map<String, String> invoke(String fcn, String[] args)
throws InvalidArgumentException, ProposalException, InterruptedException, ExecutionException, TimeoutException, NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException, CryptoException, TransactionException, IOException {
Map<String, String> resultMap = new HashMap<>(); Collection<ProposalResponse> successful = new LinkedList<>();
Collection<ProposalResponse> failed = new LinkedList<>(); /// Send transaction proposal to all peers
TransactionProposalRequest transactionProposalRequest = client.newTransactionProposalRequest();
transactionProposalRequest.setChaincodeID(chaincodeID);
transactionProposalRequest.setFcn(fcn);
transactionProposalRequest.setArgs(args); Map<String, byte[]> tm2 = new HashMap<>();
tm2.put("HyperLedgerFabric", "TransactionProposalRequest:JavaSDK".getBytes(UTF_8));
tm2.put("method", "TransactionProposalRequest".getBytes(UTF_8));
tm2.put("result", ":)".getBytes(UTF_8));
transactionProposalRequest.setTransientMap(tm2); Collection<ProposalResponse> transactionPropResp = channel.sendTransactionProposal(transactionProposalRequest, channel.getPeers());
for (ProposalResponse response : transactionPropResp) {
if (response.getStatus() == ProposalResponse.Status.SUCCESS) {
successful.add(response);
} else {
failed.add(response);
}
} Collection<Set<ProposalResponse>> proposalConsistencySets = SDKUtils.getProposalConsistencySets(transactionPropResp);
if (proposalConsistencySets.size() != 1) {
log.error("Expected only one set of consistent proposal responses but got " + proposalConsistencySets.size());
} if (failed.size() > 0) {
ProposalResponse firstTransactionProposalResponse = failed.iterator().next();
log.error("Not enough endorsers for inspect:" + failed.size() + " endorser error: " + firstTransactionProposalResponse.getMessage() + ". Was verified: "
+ firstTransactionProposalResponse.isVerified());
resultMap.put("code", "error");
resultMap.put("data", firstTransactionProposalResponse.getMessage());
return resultMap;
} else {
log.info("Successfully received transaction proposal responses.");
ProposalResponse resp = transactionPropResp.iterator().next();
byte[] x = resp.getChaincodeActionResponsePayload();
String resultAsString = null;
if (x != null) {
resultAsString = new String(x, "UTF-8");
}
log.info("resultAsString = " + resultAsString);
channel.sendTransaction(successful);
resultMap.put("code", "success");
resultMap.put("data", resultAsString);
return resultMap;
} // channel.sendTransaction(successful).thenApply(transactionEvent -> {
// if (transactionEvent.isValid()) {
// log.info("Successfully send transaction proposal to orderer. Transaction ID: " + transactionEvent.getTransactionID());
// } else {
// log.info("Failed to send transaction proposal to orderer");
// }
// // chain.shutdown(true);
// return transactionEvent.getTransactionID();
// }).get(chaincode.getInvokeWatiTime(), TimeUnit.SECONDS);
} /**
* 查询智能合约
*
* @param fcn
* 方法名
* @param args
* 参数数组
* @return
* @throws InvalidArgumentException
* @throws ProposalException
* @throws IOException
* @throws TransactionException
* @throws CryptoException
* @throws InvalidKeySpecException
* @throws NoSuchProviderException
* @throws NoSuchAlgorithmException
*/
public Map<String, String> query(String fcn, String[] args) throws InvalidArgumentException, ProposalException, NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException, CryptoException, TransactionException, IOException {
Map<String, String> resultMap = new HashMap<>();
String payload = "";
QueryByChaincodeRequest queryByChaincodeRequest = client.newQueryProposalRequest();
queryByChaincodeRequest.setArgs(args);
queryByChaincodeRequest.setFcn(fcn);
queryByChaincodeRequest.setChaincodeID(chaincodeID); Map<String, byte[]> tm2 = new HashMap<>();
tm2.put("HyperLedgerFabric", "QueryByChaincodeRequest:JavaSDK".getBytes(UTF_8));
tm2.put("method", "QueryByChaincodeRequest".getBytes(UTF_8));
queryByChaincodeRequest.setTransientMap(tm2); Collection<ProposalResponse> queryProposals = channel.queryByChaincode(queryByChaincodeRequest, channel.getPeers());
for (ProposalResponse proposalResponse : queryProposals) {
if (!proposalResponse.isVerified() || proposalResponse.getStatus() != ProposalResponse.Status.SUCCESS) {
log.debug("Failed query proposal from peer " + proposalResponse.getPeer().getName() + " status: " + proposalResponse.getStatus() + ". Messages: "
+ proposalResponse.getMessage() + ". Was verified : " + proposalResponse.isVerified());
resultMap.put("code", "error");
resultMap.put("data", "Failed query proposal from peer " + proposalResponse.getPeer().getName() + " status: " + proposalResponse.getStatus() + ". Messages: "
+ proposalResponse.getMessage() + ". Was verified : " + proposalResponse.isVerified());
} else {
payload = proposalResponse.getProposalResponse().getResponse().getPayload().toStringUtf8();
log.debug("Query payload from peer: " + proposalResponse.getPeer().getName());
log.debug("" + payload);
resultMap.put("code", "success");
resultMap.put("data", payload);
}
}
return resultMap;
} }

请注意,用法主要都依赖于ChaincodeManager这个智能合约管理器,建议以单例的形式生成该对象。

插入数据调用 manager.invoke(fcn, arguments)

查询数据调用 manager.query(fcn, arguments)

第一个参数是方法名,第二个参数是智能合约中的args字符串数组

切记不要用invoke来执行查询操作,一来没必要,二来该操作会生成数据集,且数据集也会发送给排序服务器,得不偿失。

Hyperledger Fabric 1.0 从零开始(十二)——fabric-sdk-java应用的更多相关文章

  1. Hyperledger Fabric 1.0 从零开始(二)——环境构建(公网)

    1:环境构建 在本文中用到的宿主机环境是Centos ,版本为Centos.x86_647.2,通过Docker 容器来运行Fabric的节点,版本为v1.0.因此,启动Fabric网络中的节点需要先 ...

  2. Hyperledger Fabric 1.0 从零开始(一)

    在HyperLedger/Fabric发布0.6的时候,公司就已经安排了一个团队研究这一块,后来也请IBM的专家组过来培训了一批人,不幸的是,这批人后来全走了,然后1.0就发布了.自从2017年7月H ...

  3. Hyperledger Fabric 1.0 从零开始(三)——环境构建(内网/准离线)

    有公网环境的服务器可以直接看 Hyperledger Fabric 1.0 从零开始(二)--环境构建(公网) ,本篇内容与上篇相似,只不过环境搭建需要在内网下,也就是网络被限制的情况下. 1:环境构 ...

  4. Hyperledger Fabric 1.0 从零开始(一)——吐槽

    在HyperLedger/Fabric发布0.6的时候,公司就已经安排了一个团队研究这一块,后来也请IBM的专家组过来培训了一批人,不幸的是,这批人后来全走了,然后没过多久1.0就发布了.自从2017 ...

  5. Hyperledger Fabric 1.0 从零开始(十二)——fabric-sdk-java应用【补充】

    在 Hyperledger Fabric 1.0 从零开始(十二)--fabric-sdk-java应用 中我已经把官方sdk具体改良办法,即使用办法发出来了,所有的类及文件都是完整的,在文章的结尾也 ...

  6. Hyperledger Fabric 1.0 从零开始(八)——Fabric多节点集群生产部署

    6.1.平台特定使用的二进制文件配置 该方案与Hyperledger Fabric 1.0 从零开始(五)--运行测试e2e类似,根据企业需要,可以控制各节点的域名,及联盟链的统一域名.可以指定单独节 ...

  7. Hyperledger Fabric 1.0 从零开始(十三)——orderer分布式方案

    简述 在搭建HyperLedger Fabric环境的过程中,我们会用到一个configtx.yaml文件(可参考Hyperledger Fabric 1.0 从零开始(八)——Fabric多节点集群 ...

  8. 利用WPF建立自己的3d gis软件(非axhost方式)(十二)SDK中的导航系统

    原文:利用WPF建立自己的3d gis软件(非axhost方式)(十二)SDK中的导航系统 先下载SDK:https://pan.baidu.com/s/1M9kBS6ouUwLfrt0zV0bPew ...

  9. Hyperledger Fabric 1.0 从零开始(五)——运行测试e2e

    3:运行测试e2e 3.1.运行fabric-samples的问题说明 该问题说明能够解决6.1.平台特定使用的二进制文件配置第一步的问题.可以选择继续阅读该说明,或者等参考到6.1小节时再反向阅读本 ...

随机推荐

  1. Hibernate中使用@Lob 注解保存String[] 问题

    Hibernate中使用@Lob 注解保存String[] 问题 在Hibernate注解中怎样你想保存一个字段为String数组类型.假设你想尝试保存为clob类型的话,普通情况下为定义为: @En ...

  2. C3P0 APPARENT DEADLOCK

    一,c3p0执行一段时间后报错例如以下 W 07-26_00:58:27 ThreadPoolAsynchronousRunner.java 608 com.mchange.v2.async.Thre ...

  3. spring mvc对静态资源的访问

    如果我们的项目使用的是springmvc,在web.xml中会有一段这的配置. <servlet> <servlet-name>springMvc</servlet-na ...

  4. springmvc返回json字符串中文乱码问题

    问题: 后台代码如下: @RequestMapping("menuTreeAjax") @ResponseBody /** * 根据parentMenuId获取菜单的树结构 * @ ...

  5. Github开源项目(企业信息化基础平台)

    JEEPlatform 一款企业信息化开发基础平台,可以用于快速构建企业后台管理系统,集成了OA(办公自动化).SCM(供应链系统).ERP(企业资源管理系统).CMS(内容管理系统).CRM(客户关 ...

  6. 如何修改maven默认仓库(即repository)的路径

    如何修改maven默认仓库(即repository)的路径 1  在maven的安装目录下,修改Eclipse(或IntelliJ IDEA)的MAVEN的存储位置,点击Browser按钮,选择set ...

  7. 【java提高】---ArrayList源码

    ArrayList源码 一.定义 public class ArrayList<E> extends AbstractList<E> implements List<E& ...

  8. 浏览器兼容性--IE11以及Edge等下载文件的中文名出现乱码,前后端解决方案

    项目中有用到文件下载功能,之前在处理下载时对IE浏览器下文件下载名进行过处理,测试也没有问题,但是功能上线后,业务反馈IE11文件下载文件名依然乱码.打印User-Agent字符串如下: IE11 U ...

  9. 4.python迭代器生成器装饰器

    容器(container) 容器是一种把多个元素组织在一起的数据结构,容器中的元素可以逐个地迭代获取,可以用in, not in关键字判断元素是否包含在容器中.通常这类数据结构把所有的元素存储在内存中 ...

  10. vue+express之前后台分离博客

    说来惭愧,这么久没有更新过博客了,写个项目练练手吧,打算写一个vue+express的博客  可能这个时间说长不长说短不短  写到哪就是哪吧  我采用的是前后台分离  express采用的是mvc,但 ...