崛起于Springboot2.X之区块链单节点mysql实现交易记录(34)

简介:随着今年越来越多的区块链项目更多的落地,我们自己公司也想准备着手区块链方向发展,所以领导给我们技术部要求也要落地一个区块链的项目,其实之前一两个月也接触了Hyperledger这个框架,当然我自己的博客分类中也有对Hyperledger框架的大概集成以及搭建,但是我接触几个月的时间里,懵懂,不知道作用是什么?读过好多类似这个框架有关的书,但直觉告诉我,他们对于现阶段的我,没用!包括《区块链技术进阶与实战》《区块链开发实战 hyperledger fabric 关键技术与案例分析》《深度探索区块链 Hyperledger技术与应用》《Hyperledger Fabric 开发实战》,最后一本只是看了看,没有具体细看,不过这书写的都是大同小异,雷同之处太多。然后我就在想,这个框架学习来要很久吧,毕竟自己要搞这个,我也是个java,没有接触过这方面的,即便上一个公司的区块链项目已经落地,但是技术没有接触过,只不过当时用的是python,那个产品是InsurBox,不过那也是一个类似于挖矿的小游戏,但是完全跟公司业务不同。

需求:公司是做一个类似基金股票之类的平台,用户买卖股票或者基金的话,或者体现,支付那些交易记录都要用区块链实现。

后来我自己又想了想,自己在没人带,完全靠自己,网上根本没有教材的情况下,很能短时间熟练的运用Hyperledger这本技术框架,所以我突然又想到了另外一种方案,既然Hyperledger是结合区块链的思想写出来的,那么我们的为什么不能用区块链的思想直接用java写出来,为什么还要花费那么长的时间去学习那样的技术。所以我结合公司业务以及区块链的思想,自己整出了一套简单的区块链交易记录。

区块链思想:1、去中心化   【做不到共有链,这是一个关于股票基金的产品,不能共享!】

                  2、信息不可篡改  【可以做到,安全加密】

                  3、多节点运行,允许1/3的节点最大程度被攻击而不破坏区块 【可以做到】

                  4、分布式数据库 。 【可以做到】

综合以上所述,个人觉得,也没必要完全利用Hyperledger,所以这个技术还是慢慢学习吧,暂且不用了,成本太高。

 

  redis+mysql+lombok  下一篇将使用mongodb替代mysql,所以目前只是一个简单的区块链交易记录,因为自我感觉也不好,所以也希望大家指出来之后多做更改!目前只是单节点,所以不存在多节点查询的算法,下一篇替换mongodb将会多节点

1cf0e8003ad19cfa3864f241b8446c0b0e6.jpg

1、添加pom文件依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.3.2</version>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>


<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.47</version>
</dependency>
<dependency>
    <groupId>commons-collections</groupId>
    <artifactId>commons-collections</artifactId>
    <version>3.2.2</version>
</dependency>
2、mysql表结构

 2.1 交易表 t_point_deal 目前只用一个记录结果代替交易过程,毕竟保密,

CREATE TABLE `t_point_deal` (
  `DEAL_ID` varchar(200) NOT NULL COMMENT '交易单id',
  `BUY_USER_ID` int(10) NOT NULL COMMENT '买方id',
  `BUY_ORDER_ID` bigint(10) unsigned NOT NULL,
  `SELL_USER_ID` int(11) NOT NULL COMMENT '卖方id',
  `SELL_ORDER_ID` bigint(10) unsigned NOT NULL,
  `POINT_ID` int(11) NOT NULL COMMENT '基金id号',
  `DEAL_DATE` datetime NOT NULL COMMENT '交易日期',
  `DEAL_NUM` int(11) unsigned NOT NULL COMMENT '交易数量',
  `DEAL_UNIT_PRICE` double(8,2) unsigned NOT NULL COMMENT '交易单价',
  `DEAL_PRICE` double(9,2) unsigned NOT NULL COMMENT '成交额',
  PRIMARY KEY (`DEAL_ID`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT

  2.2 区块表 t_block

CREATE TABLE `t_block` (
  `block_index` int(10) NOT NULL AUTO_INCREMENT COMMENT '区块索引号',
  `block_hash` varchar(100) NOT NULL COMMENT 'hash值',
  `block_stamp` varchar(40) NOT NULL COMMENT '时间戳',
  `pointDeals` mediumtext COMMENT '基金交易记录',
  `block_nonce` int(10) NOT NULL COMMENT '随机数',
  `previousHash` varchar(100) NOT NULL COMMENT '上一个区块hash值',
  PRIMARY KEY (`block_index`)
) ENGINE=MyISAM AUTO_INCREMENT=83 DEFAULT CHARSET=utf8

3、实体类Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PointDeal {

    private String dealId;
    private Integer buyUserId;
    private Long buyOrderId;
    private Integer sellUserId;
    private Long sellOrderId;
    private Integer pointId;
    private Date dealDate;
    private Integer dealNum;
    private Double dealUnitPrice;
    private Double dealPrice;
}
@Data
public class Block {

    /**

     * 区块索引号

     */

    private int index;

    /**

     * 当前区块的hash值,区块唯一标识

     */

    private String hash;

    /**

     * 生成区块的时间戳

     */

    private long timestamp;

    /**

     * 当前区块的交易集合

     */

    private List<PointDeal> pointDeals;

    private String data;

    /**

     * 工作量证明,计算正确hash值的次数

     */
    private int nonce;
    /**

     * 前一个区块的hash值

     */
    private String previousHash;

    public Block() {
        super();
    }

    public Block(int index, long timestamp, List<PointDeal> pointDeals, String data, int nonce, String previousHash, String hash) {
        super();
        this.index = index;
        this.timestamp = timestamp;
        this.pointDeals= pointDeals;
        this.nonce = nonce;
        this.previousHash = previousHash;
        this.hash = hash;
    }
}
4、application.properties配置
server.port=8092

#mysql:
spring.datasource.url=jdbc:mysql://localhost:3306/XX?characterEncoding=utf8&useSSL=false
spring.datasource.username=
spring.datasource.password=
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.max-idle=10
spring.datasource.max-wait=10000
spring.datasource.min-idle=5
spring.datasource.initial-size=5



mybatis.mapper-Locations=classpath:mapper/entity/*.xml
mybatis.type-aliases-package=com.dtb.trade.entity

spring.redis.database=0
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=
spring.redis.timeout=10000ms

spring.freemarker.allow-request-override=false
spring.freemarker.cache=true
spring.freemarker.check-template-location=true
spring.freemarker.charset=UTF-8
spring.freemarker.content-type=text/html
spring.freemarker.expose-request-attributes=false
spring.freemarker.expose-session-attributes=false
spring.freemarker.expose-spring-macro-helpers=false
spring.freemarker.suffix=.html
5、启动类Application

方法上添加扫描包注解

@MapperScan(basePackages={"com.dtb.trade"})
6、加密工具类SHA256
public class EncryptUtil {

    /**
     * 对字符串加密,加密算法使用MD5,SHA-1,SHA-256,默认使用SHA-256
     *
     * @param strSrc
     *            要加密的字符串
     * @param encName
     *            加密类型
     * @return
     */
    public static String Encrypt(String strSrc) {

        MessageDigest md = null;
        String strDes    = null;
        String encName   = "SHA-256";
        byte[] bt        = strSrc.getBytes();

        try {
            md = MessageDigest.getInstance(encName);
            md.update(bt);
            strDes = bytes2Hex(md.digest()); //to HexString
        } catch (NoSuchAlgorithmException e) {
            return null;
        }

        return strDes;
    }

    public static String bytes2Hex(byte[] bts) {

        String des = "";
        String tmp = null;

        for (int i = 0; i < bts.length; i++) {
            tmp = (Integer.toHexString(bts[i] & 0xFF));
            if (tmp.length() == 1) {
                des += "0";
            }
            des += tmp;
        }
        return des;
    }
}
7、controller层

    7.1 查询交易记录,根据t_point_deal 中的买方id查询

    7.2 更新交易记录到区块链上

@Controller
@Slf4j
public class TradeController {


    @Autowired
    TradeService tradeService;

    @Autowired
    RedisService redisService;


    //查询区块地址
    @GetMapping(value = "/block/{id}")
    public String getBlockAdress(@PathVariable("id")String id, ModelMap modelMap){
        //获取所有区
        List<Block> list = tradeService.getAllBlock();
        int userId  = Integer.valueOf(id);
        if (list == null){
            modelMap.put("msg","区块信息为空");
            modelMap.put("success",false);
            modelMap.put("code",210);
        }

        List<PointDeal> results = new ArrayList<>();
        for (Block block:list){
            List<PointDeal> pointDeals = JSONObject.parseArray(block.getData(),PointDeal.class);
            List<PointDeal> list1 = pointDeals.stream().filter(pointDeal -> pointDeal.getBuyUserId().equals(userId)).collect(Collectors.toList());
            if (pointDeals != null){
                results.addAll(list1);
            }
        }

        modelMap.put("page",results);
        return "/blockInfo";

    }


    //测试记账
    @GetMapping(value = "/block/test")
    public void test1(){
        List<PointDeal> list = tradeService.gainAllTrade();

        if (list.size()>0){
            //获取交易块
            Block block = null;
            String index = redisService.get("blockIndex");
            if (StringUtils.isEmpty(index)){
                //直接从数据库查询
                boolean isHave = tradeService.isBlock();
                if (!isHave){
                    //生成创世区块
                    tradeService.hyperledgerTwo(firstBlock(),list);
                }
                block = tradeService.getNewBlock();
                tradeService.hyperledger(block,list);

            }else {
                //查询区块信息
                block = tradeService.selectBlock(Integer.valueOf(index));
                if (block != null){
                    tradeService.hyperledger(block,list);
                }else {
                    System.out.println("区块出现异常");
                }
            }
        }

    }


    public static Block firstBlock(){

        Block block = new Block();

        String prev = "0000000000000000000000000000000000000000000000000000000000000000";
        int index   = 1;
        int nonce   = 1;
        long time   = System.currentTimeMillis();
        String hash = EncryptUtil.Encrypt(prev+index+nonce+time);
        List<PointDeal> list = new ArrayList<>();


        block.setPreviousHash(prev);
        block.setIndex(index);
        block.setNonce(nonce);
        block.setHash(hash);
        block.setTimestamp(time);
        block.setPointDeals(null);
        block.setPointDeals(list);

        return block;
    }
}
8、service层

    8.1 RedisService 这个redis我删除了多余的方法

package com.dtb.trade.service;

import com.alibaba.fastjson.JSONObject;
import org.apache.commons.collections.MapUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @Author:Mujiutian
 * @Description:
 * @Date: Created in 下午2:18 2018/7/13
 */
@Service
public class RedisService {

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    private String reidsKeyTitle = "pc_enterprise";

    public String getString(String key) {
        return stringRedisTemplate.opsForValue().get(changereidsKeyTitle(key));
    }

    /**
     * 向redis存入key和value
     * 如果key已经存在 则覆盖
     * @param key
     * @param value
     */
    public void set(String key, String value){
        ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue();
        opsForValue.set(changereidsKeyTitle(key), value);
    }
   /**
     * 向redis指定的db中存入key和value以及设置生存时间
     * 如果key已经存在 则覆盖
     * @param key
     * @param value
     * @param time    有效时间(默认时间单位为秒)
     * @param
     */
    public void set(String key, String value, Long time){
        ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue();
        // 默认时间单位为秒
        opsForValue.set(changereidsKeyTitle(key), value, time, TimeUnit.SECONDS);
    }

    /**
     * 通过key获取指定的value
     * @param key
     * @param
     * @return 没有返回null
     */
    public String get(String key) {
        return stringRedisTemplate.opsForValue().get(changereidsKeyTitle(key));
    }
}

    8.2 TradeService 记账核心方法

@Service
public class TradeService {

    @Autowired
    TradeDao tradeDao;

    @Autowired
    RedisService redisService;

    //区块拥有交易记录的最大个数
    private static final int blockMax = 5;

    //该区块已经拥有的交易记录个数
    private static int blockNum = 0;

    //获取定时刷新的交易记录
    public List<PointDeal> gainAllTrade(){

        Map<String,Object> map = new HashMap<>();
        String today = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
        map.put("start",today+" 00:00:00");
        map.put("end",today+" 23:59:59");
        return tradeDao.getAll(map);
    }

    //查询是否拥有数据块
    public boolean isBlock(){
        List<Block> list = tradeDao.isHave();
        if (list == null || list.isEmpty() || list.size()==0){
            return false;
        }
        return true;

    }

    //新增区块信息
    public boolean insertBlock(Block block){
        tradeDao.insertBlock(block);
        return true;
    }

    //更新区块信息
    public boolean updateBlock(Block block){
        tradeDao.updateBlock(block);
        return true;
    }

    //根据索引号查询区块信息
    public Block selectBlock(int index){
        return tradeDao.selectBlock(index);
    }

    //查询最新区块信息
    public Block getNewBlock(){
        Block block = tradeDao.getBlockIndex();

        block.setPointDeals(JSONObject.parseArray(block.getData(),PointDeal.class));

        return block;
    }

    //记账
    public boolean hyperledger(Block block,List<PointDeal> pointDeals){


        if (block.getPointDeals() != null){
            blockNum   = block.getPointDeals().size();
        }

        //准备记账的记忆记录个数
        int recordNum  = pointDeals.size();

        //欠缺,补足到最大区块个数
        int deficiency = blockMax - blockNum;

        //该区块是否能满足记账交易记录的个数,0刚好满足,正数绰绰有余,负数不满足,生成新的区块
        int surplusNum = deficiency-recordNum;

        if (blockNum < blockMax){
            //更新区块信息
            if (surplusNum >= 0){
                block.getPointDeals().addAll(pointDeals);
                block.setData(JSON.toJSONString(block.getPointDeals()));
                tradeDao.updateBlock(block);
            }else {
                //填补区块剩余
                for (int j =0;j<deficiency;j++){
                    block.getPointDeals().add(pointDeals.get(0));
                    pointDeals.remove(0);
                    //更新到数据库
                    block.setData(JSON.toJSONString(block.getPointDeals()));
                    tradeDao.updateBlock(block);
                    redisService.set("blockIndex",block.getIndex()+"");
                }
                //生成下一区块
                String nextPrev = block.getHash();
                int nextIndex   = block.getIndex()+1;
                int nextnonce   = block.getNonce();
                long nextTime   = System.currentTimeMillis();
                String nextHash = EncryptUtil.Encrypt(nextPrev+nextIndex+nextnonce+nextTime+block.getData());

                Block nextBlock = new Block();
                nextBlock.setTimestamp(nextTime);
                nextBlock.setPreviousHash(nextPrev);
                nextBlock.setHash(nextHash);
                nextBlock.setNonce(nextnonce);
                nextBlock.setIndex(nextIndex);
                hyperledgerTwo(nextBlock,pointDeals);
            }
        }else {
            //直接生成新区块
            String nextPrev      = block.getHash();
            int nextIndex        = block.getIndex()+1;
            int nextnonce        = block.getNonce();
            long nextTime        = System.currentTimeMillis();
            String nextHash      = EncryptUtil.Encrypt(nextPrev+nextIndex+nextnonce+nextTime+block.getData());
            List<PointDeal> list = new ArrayList<>();

            Block nextBlock = new Block();
            nextBlock.setTimestamp(nextTime);
            nextBlock.setPreviousHash(nextPrev);
            nextBlock.setHash(nextHash);
            nextBlock.setNonce(nextnonce);
            nextBlock.setIndex(nextIndex);
            nextBlock.setPointDeals(list);
            hyperledgerTwo(nextBlock,pointDeals);

        }

        return true;

    }

    //新区块记账
    public void hyperledgerTwo(Block block,List<PointDeal> pointDeals){

        int recordNum  = pointDeals.size();
        int deficiency = blockMax - blockNum;
        int surplusNum = deficiency-recordNum;

        if (surplusNum >= 0){
            //满足
            block.getPointDeals().addAll(pointDeals);
            block.setData(JSON.toJSONString(block.getPointDeals()));
            tradeDao.insertBlock(block);
            redisService.set("blockIndex",block.getIndex()+"");
        }else {
            //不满足
            for (int j =0;j<deficiency;j++){
                block.getPointDeals().add(pointDeals.get(0));
                pointDeals.remove(0);
            }
            block.setData(JSON.toJSONString(block.getPointDeals()));
            //更新到数据库
            tradeDao.insertBlock(block);

            //生成下一区块
            String nextPrev      = block.getHash();
            int nextIndex        = block.getIndex()+1;
            int nextnonce        = block.getNonce();
            long nextTime        = System.currentTimeMillis();
            String nextHash      = EncryptUtil.Encrypt(nextPrev+nextIndex+nextnonce+nextTime+block.getData());
            List<PointDeal> list = new ArrayList<>();

            Block nextBlock = new Block();
            nextBlock.setTimestamp(nextTime);
            nextBlock.setPreviousHash(nextPrev);
            nextBlock.setHash(nextHash);
            nextBlock.setNonce(nextnonce);
            nextBlock.setIndex(nextIndex);
            nextBlock.setPointDeals(list);

            hyperledgerTwo(nextBlock,pointDeals);
        }

    }

    //获取所有区块
    public List<Block> getAllBlock(){
        List<Block> blocks = tradeDao.isHave();
        if (blocks != null || blocks.size() > 0){
            return blocks;
        }
        return null;
     }

}
9、dao层
public interface TradeDao {



    @Select({
            "select * from t_point_deal where DEAL_DATE >= #{start} and DEAL_DATE <= #{end}"
    })
    @Results({
            @Result(column = "DEAL_ID",property = "dealId",jdbcType = JdbcType.VARCHAR),
            @Result(column = "BUY_USER_ID",property = "buyUserId",jdbcType = JdbcType.INTEGER),
            @Result(column = "BUY_ORDER_ID",property = "buyOrderId",jdbcType = JdbcType.BIGINT),
            @Result(column = "SELL_USER_ID",property = "sellUserId",jdbcType = JdbcType.INTEGER),
            @Result(column = "SELL_ORDER_ID",property = "sellOrderId",jdbcType = JdbcType.BIGINT),
            @Result(column = "POINT_ID",property = "pointId",jdbcType = JdbcType.INTEGER),
            @Result(column = "DEAL_DATE",property = "dealDate",jdbcType = JdbcType.DATE),
            @Result(column = "DEAL_NUM",property = "dealNum",jdbcType = JdbcType.INTEGER),
            @Result(column = "DEAL_UNIT_PRICE",property = "dealUnitPrice",jdbcType = JdbcType.DOUBLE),
            @Result(column = "DEAL_PRICE",property = "dealPrice",jdbcType = JdbcType.DOUBLE),
    })
    List<PointDeal> getAll(Map<String,Object> map);


    @Select({
            "select * from t_block"
    })
    @Results({
            @Result(column = "block_index",property = "index",jdbcType = JdbcType.INTEGER),
            @Result(column = "block_hash",property = "hash",jdbcType = JdbcType.VARCHAR),
            @Result(column = "block_stamp",property = "timestamp",jdbcType = JdbcType.TIMESTAMP),
            @Result(column = "pointDeals",property = "data",jdbcType = JdbcType.LONGVARCHAR),
            @Result(column = "block_nonce",property = "nonce",jdbcType = JdbcType.INTEGER),
            @Result(column = "previousHash",property = "previousHash",jdbcType = JdbcType.VARCHAR)
    })
    List<Block> isHave();

    @Select({
            "select * from t_block order by block_index desc limit 0,1"
    })
    @Results({
            @Result(column = "block_index",property = "index",jdbcType = JdbcType.INTEGER),
            @Result(column = "block_hash",property = "hash",jdbcType = JdbcType.VARCHAR),
            @Result(column = "block_stamp",property = "timestamp",jdbcType = JdbcType.TIMESTAMP),
            @Result(column = "pointDeals",property = "data",jdbcType = JdbcType.LONGVARCHAR),
            @Result(column = "block_nonce",property = "nonce",jdbcType = JdbcType.INTEGER),
            @Result(column = "previousHash",property = "previousHash",jdbcType = JdbcType.VARCHAR)
    })
    Block getBlockIndex();

    @Insert({
            "insert into t_block (block_hash,block_stamp,pointDeals,block_nonce,previousHash) values(#{hash},#{timestamp},#{data},#{nonce},#{previousHash})"
    })
    int insertBlock(Block block);

    @Update({
            "update t_block set pointDeals = #{data} where block_index = #{index}"
    })
    int updateBlock(Block block);


    @Select({
            "select * from t_block where block_index = #{index}"
    })
    @Results({
            @Result(column = "block_index",property = "index",jdbcType = JdbcType.INTEGER),
            @Result(column = "block_hash",property = "hash",jdbcType = JdbcType.VARCHAR),
            @Result(column = "block_stamp",property = "timestamp",jdbcType = JdbcType.TIMESTAMP),
            @Result(column = "pointDeals",property = "data",jdbcType = JdbcType.LONGVARCHAR),
            @Result(column = "block_nonce",property = "nonce",jdbcType = JdbcType.INTEGER),
            @Result(column = "previousHash",property = "previousHash",jdbcType = JdbcType.VARCHAR)
    })
    Block selectBlock(@Param("index")int index);
}
10、代码逻辑

    10.1 

@GetMapping(value = "/block/test")

这个rest接口代码逻辑是先从mysql block区块中判断有无区块,如果没有那么生成创世区块,如果有的话,那么在原有区块继续添加交易记录,直到区块中的交易记录数量达到限制,在重新下一个区块继续。

    10.2 

@GetMapping(value = "/block/{id}")

根据买方id(t_point_deal 中的字段)查询她所有的交易记录

    10.3 

如果自己要测试的话,那么可以在t_point_deal表中随意添加几条记录,最后执行接口,就可以看到区块信息了,如图:

97e08e923a901718e679894405d1a1b9b81.jpg

    10.4 查询的话,那么直接查询接口,然后在templates下创建新的blockInfo.html,因为是freemaker技术嘛

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>区块记录</title>
</head>
<body>

Tx:
<table>
    <thead>
    Block: <span></span><br/>
    Nonce: <span></span><br/>
    <tr>
        <th width="10%">交易id</th>
        <th width="10%">买方id</th>
        <th width="10%">卖方id</th>
        <th width="20%">交易日期</th>
        <th width="10%">交易数量</th>
        <th width="10%">交易单价</th>
        <th width="10%">成交额</th>
    </tr>
    </thead>
    <#if page?size gt 0>
    <tbody>
    <#list page as pointDeals>
    <tr>
        <td width="10%">${pointDeals.dealId!'_'}</td>
        <td width="10%">${pointDeals.buyUserId!'-'}</td>
        <td width="10%">${pointDeals.sellUserId!'_'}</td>
        <td width="20%">${pointDeals.dealDate?string('yyyy-MM-dd HH:mm:ss')!'_'}</td>
        <td width="10%">${pointDeals.dealNum!'_'}</td>
        <td width="10%">${pointDeals.dealUnitPrice!'_'}</td>
        <td width="10%">${pointDeals.dealPrice!'_'}</td>
    </tr>
    </#list>
    </tbody>
    </#if>
</table>
</body>
</html>

执行查询如图:

ddfd76d935604d62183c2574f494cc89fe6.jpg

 

目前不足之处是:不能多节点运行,并执行拜占庭算法等,以及非关系行数据库运用。

 

 

 

 

转载于:https://my.oschina.net/mdxlcj/blog/1928438

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值