崛起於Springboot2.X + 區塊鏈單節點mysql實現交易記錄(34)

《SpringBoot2.X心法總綱》html

        (本篇博客已於2019-08-28優化更新)java

      簡介:隨着今年愈來愈多的區塊鏈項目更多的落地,咱們本身公司也想準備着手區塊鏈方向發展,因此領導給咱們技術部要求也要落地一個區塊鏈的項目,其實以前一兩個月也接觸了Hyperledger這個框架,固然我本身的博客分類中也有對Hyperledger框架的大概集成以及搭建,可是我接觸幾個月的時間裏,不知道做用是什麼?讀過好多相似這個框架有關的書,但直覺告訴我,他們對於現階段的我,沒用!包括《區塊鏈技術進階與實戰》《區塊鏈開發實戰 hyperledger fabric 關鍵技術與案例分析》《深度探索區塊鏈 Hyperledger技術與應用》《Hyperledger Fabric 開發實戰》,最後一本只是看了看,不過這書寫的都是大同小異,雷同之處太多。而後我就在想,這個框架學習來要好久吧,畢竟本身要搞這個,我也是個java,沒有接觸過這方面的,即使上一個公司的區塊鏈項目已經落地,可是技術沒有接觸過,只不過當時用的是python,那個產品是InsurBox,不過那也是一個相似於挖礦的小遊戲,可是徹底跟公司業務不一樣。  需求:公司是作一個相似基金股票之類的平臺,用戶買賣股票或者基金的話,或者體現,支付那些交易記錄都要用區塊鏈實現。
     後來我本身又想了想,本身在沒人帶,徹底靠本身,網上根本沒有教材的狀況下,很能短期熟練的運用Hyperledger這本技術框架,因此我忽然又想到了另一種方案,既然Hyperledger是結合區塊鏈的思想寫出來的,那麼咱們的爲何不能用區塊鏈的思想直接用java寫出來,爲何還要花費那麼長的時間去學習那樣的技術。因此我結合公司業務以及區塊鏈的思想,本身整出了一套簡單的區塊鏈交易記錄。   
     區塊鏈思想:         
        一、去中心化【作不到共有鏈,這是一個關於股票基金的產品,不能共享!】   
        二、信息不可篡改【能夠作到,安全加密】      
        三、多節點運行,容許1/3的節點最大程度被攻擊而不破壞區塊 【能夠作到】      
        四、分佈式數據庫【能夠作到】  
     技術棧:redis+mysql+lombok+freemaker,下一篇將使用mongodb替代mysql,因此目前只是一個簡單的區塊鏈交易記錄,由於自我感受也很差,因此也但願你們指出來以後多作更改!目前只是單節點,因此不存在多節點查詢的算法,下一篇替換mongodb將會多節點。

                

一、添加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>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>

二、mysql表結構

      2.1 交易表 t_point_deal 目前只用一個記錄結果代替交易過程,畢竟保密。python

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_blockmysql

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

三、實體類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;
    }
}

四、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

五、啓動類Application

      方法上添加掃描包註解web

@MapperScan(basePackages={"com.dtb.trade"})

六、加密工具類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;
    }
}

七、controller層

      7.1 查詢交易記錄,根據t_point_deal 中的買方id查詢redis

      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;
    }
}

八、service層

      8.1 RedisService 這個redis我刪除了多餘的方法spring

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 記帳核心方法sql

@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;
     }

}

九、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.一、這個rest接口代碼邏輯是先從mysql block區塊中判斷有無區塊,若是沒有那麼生成創世區塊,若是有的話,那麼在原有區塊繼續添加交易記錄,直到區塊中的交易記錄數量達到限制,在從新下一個區塊繼續。mongodb

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

    10.二、根據買方id(t_point_deal 中的字段)查詢她全部的交易記錄

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

    10.三、若是本身要測試的話,那麼能夠在t_point_deal表中隨意添加幾條記錄,最後執行接口,就能夠看到區塊信息了,如圖:

    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>

      執行查詢如圖:

 

      目前不足之處是:不能多節點運行,並執行拜占庭算法等,以及非關係行數據庫運用。

相關文章
相關標籤/搜索