Likt's Blog

天空一直都在,是云来了又去。

0%

溯源项目实战

一、实验目的

  1. 掌握springboot基本框架:dao层、service层和controller层;
  2. 前后端综合分析产品上链流程;
  3. 项目开发基本框架;

二、实验环境

  • Windows 11
  • IDEA java、maven、springboot
  • navicat
  • postman

三、实验步骤

一)项目背景

  1. 茶叶在全球范围内享有广泛的消费市场,产业市场规模持续增长,现代消费者对茶叶的需求不仅限于口感和品质,越来越重视产品的来源和生产过程。
  2. 常见问题:假冒伪劣产品;信息不透明;质量标准不一。

二)区块链的作用与局限性

  1. 茶叶安全与区块链的作用
    • 追溯能力
    • 透明记录:确保了数据的透明性和不可篡改性
  2. 区块链的局限性
    • 质量控制在源头
    • 后续措施依赖人工
    • 需要完整的生态系统
  3. 总结
    • 区块链在食品安全方面的作用主要体现在追溯和透明度上,但解决质量问题仍需要综合的质量管理和监管措施。
    • 区块链提供的工具可以有效支持这些措施,但不是解决方案的全部。
    • 区块链保障数据安全,而不是溯源商品的安全。

三)项目需求分析

  1. 用户角色:茶农、加工商、物流公司和消费者。

  2. 功能需求:

    • 茶叶信息登记
    • 生产和加工记录
    • 物流跟踪
    • 消费者查询系统
  3. 架构设计

    前端展示层 html css javascript jquery
    服务层 聚合服务
    存储层 mysql数据库、区块文件
  4. 数据模型

    区块表:id,blockIndex,currentBlockin记录区块号,用于区块更新

    产品表:product_name,product_desc,creat_time,address

    溯源码表:记录溯源码信息

    code,create_time,product_name,address,img_url(二维码链接)

    上链记录表:from,to,content,hash_no,block_index,create_time,code,product_name,

四)技术选型

  1. 开发工具
    • 代码开发工具:IDEA
    • 数据库可视化工具:navicat
    • 接口测试工具:postman
  2. 系统环境
    • jdk11
    • mysql
  3. 前端技术:html+css+javascript,jquery
  4. 后端技术:spring boot,mybatis

五)代码

  1. git clone https://gitee.com/daitoulin/suyuan_project.git

  2. 分析suyuan_project项目框架:component主键-有定时功能,templates文件夹中存放网页框架。

    image-20241106002022044

  3. 配置文件application.properties,修改mysql数据库密码,node.ip可以写任意一个启动节点的IP;增加开启模板引擎,需要引入spring-boot-starter-thymeleaf包才能使用,HTML模板;

    1
    2
    3
    4
    #找到数据库对应的*Mapper.xml文件
    mybatis.mapper-locations=classpath:/mybatis-mapper/*Mapper.xml
    #开启驼峰命名
    mybatis.configuration.map-underscore-to-camel-case=true
    1
    2
    3
    #开启模板引擎,需要引入spring-boot-starter-thymeleaf包才能使用
    spring.thymeleaf.prefix=classpath:/templates/
    spring.thymeleaf.mode=HTML
  4. 关于新增HTML页面:templates文件夹中新建block.html,在htmlcontroller中添加一个,项目要重启才能生效,可以通过http://localhost:8007/block对新建的页面进行访问。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <!--block.html文件-->
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>区块</title>
    </head>
    <body>
    <div>hello likt!welcome to my block</div>
    </body>
    </html>
    1
    2
    3
    4
    @RequestMapping("/block")
    public String block(){
    return "block";
    }//htmlcontroller中新增一个方法

    image-20241102164106441

  5. 启动引导节点、两个block_contract节点

    image-20241103205640695

  6. 访问http://localhost:8007/index

  7. 使用配对的钱包地址和私钥

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    {
    "code": "1",
    "msg": "生成钱包成功",
    "o": {
    "publicKey": "13aae02db917ba3cf26eb5201f55d9fe094be2ad8df09437918c3d278adde0af77ae39ae6a1575d592536287a78b58594638951bd7cc3f04153381f0a0fa6618",
    "privateKey": "8676da2a245a145b1cc7e158d6b2221e892478c386dc0dcc28b9ed6772b08975",
    "address": "0x4dd32f368a54e39ee7da24d6eb5ebecf7d062eb5"
    }
    }

  8. 新增产品

    image-20241102154719521

  9. 新增溯源码,输入私钥

    image-20241102155016624

  10. 查看溯源码

  11. 新增流程1

    image-20241102155336364

  12. 新增流程2

    image-20241102165312487

  13. 打开navicat,链接blockchain.db可以查看到pending表中的两个上链信息

    image-20241102165428448

  14. 两个block_contract节点开始挖矿/starMining

  15. 从block_contract的browser文件夹进入index.html,进入挖矿后台,挖到交易信息

    image-20241103211910291

    image-20241103215331921

    image-20241103215638544

  16. 增加流程时,打开开发者工具,查看到toChain的信息

    image-20241103215545204

  17. 当新增成功,在开发者工具中可以查看到toChain,依次对应代码中的上链接口。

    image-20241103220100980

  18. 使用postman,toChain接口,进行新增流程如下图

    image-20241103220359152

  19. 可以从区块号93中看到刚刚新增流程操作均上链成功

    image-20241103220408896

  20. 下载图片,使用草料二维码工具,对其进行扫描,获取到链接,可以看到溯源结果如下

    image-20241103233624269

  21. 点击其中的流程,查看详细信息。

    image-20241103233654860

  22. 导入suyuan.sql新建数据库,查看数据模型—-四个数据表如下

    ①t_block区块表:记录区块号,用于区块更新

    image-20241106230508022

    ②t_chain_data上链数据表:记录上传到区块链的信息

    image-20241106230601267

    ③t_code溯源码表:记录溯源码信息

    image-20241106230622413

    ④t_product产品表:记录产品信息

    image-20241106230649961

  23. 项目启动类:SuyuanApplication.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    package com.example.suyuan;

    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.scheduling.annotation.EnableScheduling;

    @SpringBootApplication
    @MapperScan("com.example.suyuan.dao")//扫描这个包下的文件,才能让mybatis正常运行
    @EnableScheduling//启动定时任务功能
    public class SuyuanApplication {

    public static void main(String[] args) {
    SpringApplication.run(SuyuanApplication.class, args);
    }

    }

  24. 上链接口完整任务

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    <div class="modal">
    <div class="mask">
    </div>
    <div class="modelBody">
    <div>新增流程</div>
    <div class="formItem"><label>流程名:</label> <input id="processName" name="processName" aria-label="流程名 " placeholder="流程名"></div>
    <div class="formItem"><label>流程内容:</label> <input id="content" name="content" aria-label="流程内容 " placeholder="流程内容"></div>
    <div class="btnBox"><button class="btn danger" onclick="closeModal()">取消</button><button class="btn primary" onclick="handleConfirm()">提交</button></div>
    </div>
    </div>



    <script>
    let loading = false
    function handleConfirm() {
    if(loading) return
    loading = true
    const processName = $('#processName').val()
    const content = $('#content').val()
    $.ajax({
    url: '/toChain', // API 的 URL 地址
    type: 'POST', // 请求类型,可以是 GET, POST, PUT, DELETE 等
    contentType: 'application/json',
    data: JSON.stringify({
    processName,
    content,
    privateKey,
    code
    }),
    success: function(data) {
    // 在这里处理从服务器返回的数据
    const { code, msg } = data
    if(code === '1') {
    closeModal()
    alert('新增成功')
    } else {
    alert(msg)
    }
    loading = false
    },
    error: function(jqXHR, textStatus, errorThrown) {
    // 处理请求失败的情况
    console.error('Error: ' + textStatus + ' ' + errorThrown);
    loading = false
    }
    });
    }
    </script>
  25. ChainController.java控制层

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    @RequestMapping("/toChain")
    public ResponseEntity<JSONObject> toChain(@RequestBody ChainDataBo chainDataBo) throws Exception {
    JSONObject jo = new JSONObject();

    if ("".equals(chainDataBo.getCode()) || chainDataBo.getCode() == null){
    jo.setCode("-1");
    jo.setMsg("溯源码不能为空");
    return new ResponseEntity<JSONObject>(jo, HttpStatus.OK);
    }

    if ("".equals(chainDataBo.getPrivateKey()) || chainDataBo.getPrivateKey() == null){
    jo.setCode("-1");
    jo.setMsg("钱包私钥不能为空");
    return new ResponseEntity<JSONObject>(jo, HttpStatus.OK);
    }

    if ("".equals(chainDataBo.getContent()) || chainDataBo.getContent() == null){
    jo.setCode("-1");
    jo.setMsg("内容不能为空");
    return new ResponseEntity<JSONObject>(jo, HttpStatus.OK);
    }

    if ("".equals(chainDataBo.getProcessName()) || chainDataBo.getProcessName() == null){
    jo.setCode("-1");
    jo.setMsg("流程名不能为空");
    return new ResponseEntity<JSONObject>(jo, HttpStatus.OK);
    }

    TCode tCode = codeDao.queryByCode(chainDataBo.getCode());


    TChainData tChainData = new TChainData();
    tChainData.setFrom(tCode.getAddress());
    tChainData.setTo("system");
    tChainData.setContent(chainDataBo.getContent());
    tChainData.setCreateTime(DateUtils.getTime());
    tChainData.setCode(tCode.getCode());
    tChainData.setProductName(tCode.getProductName());
    tChainData.setProcessName(chainDataBo.getProcessName());
    tChainData.setChainStatus("0");
    tChainData.setBlockIndex("");

    //String jsonStr = new Gson().toJson(tChainData.toString());
    Gson gson = new GsonBuilder()
    .disableHtmlEscaping() // 禁用 HTML 转义
    .create();
    String jsonStr = gson.toJson(tChainData);


    BigInteger pri = new BigInteger(chainDataBo.getPrivateKey(), 16);

    TradeObject tradeObject = new TradeObject();
    tradeObject.setFrom(tCode.getAddress());
    tradeObject.setTo("system");
    tradeObject.setType("1");
    tradeObject.setContent(jsonStr);
    tradeObject.setJsoncreatetime(DateUtils.getTime());
    tradeObject.setObjToString(tradeObject.toString());

    Sign.SignatureData signatureData = EthUtils.signMessage(tradeObject.toString(),pri);
    String sign = EthUtils.getSignStr(signatureData);
    tradeObject.setSign(sign);

    String hashNo = PendingUtils.genTradeNo(tradeObject);
    tChainData.setHashNo(hashNo);
    //保存上链数据到数据库
    chainDataDao.save(tChainData);

    //上链发送交易
    String url = "http://" + ip + ":8001/data/trade";
    restTemplate.postForEntity(url, tradeObject, TradeObject.class);

    jo.setCode("1");
    jo.setMsg("提交交易成功");
    return new ResponseEntity<JSONObject>(jo, HttpStatus.OK);
    }

四、实验结果

  1. 新增产品、新增溯源码、新增流程、下载图片、扫描溯源码、对产品进行溯源

    image-20250112171412372

  2. 利用开发者工具进行跟踪

    image-20250112171421740

  3. navicat连接block_chain.db查看数据上链信息

    image-20250112172040715

五、总结

  1. 关于溯源码扫描查看流程进行溯源,需要手机和电脑在同一网络下才能扫码查看,或者直接电脑扫描查看。

  2. 如果想重置区块链需要把所有节点项目的db和ws删除,仅删除一个节点的db和ws是无效的,一旦开启,两个对等节点会完成数据的对等共享,必须删除所有节点项目的相关内容。

  3. 本次实验启动溯源项目节点,启动引导节点、两个block_contract节点【两个分节点开始挖矿starMining】(至少两个节点,否则不能通过验证)。出现点击了“提交”新建流程,但是不显示新建成功的,应该是有节点没有开启;具体情况可以根据进入开发者工具进行查看分析。

  4. t_code表中img_url即溯源码对应的链接,对应generateQRCode生成二维码的接口,对应前端溯源码下载图片;后通过草料二维码工具,对产品信息进行溯源。

  5. springboot项目的安全性:【dao层、service层和controller层】

    • model层也叫pojo层或者entity层。一般数据库的一张表对应一个pojo层,并且表中所有字段都在pojo层都一一对应。

    • dao层也叫mapper层,数据持久层。对数据库进行持久化操作,他的方法是针对数据库操作的,基本用到的就是增删改查。它只是个接口,只有方法名字,具体实现在mapper.xml中。

    • service层叫业务逻辑层,存放业务逻辑处理,不直接对数据库进行操作,有接口和接口实现类,提供controller层调用的方法。创建两个文件,一个存放接口类,一个存放接口实现类。

    • controller层叫控制器层,负责前后端交互,接受前端请求,调用service层,接收service层返回的数据,最后返回具体的页面和数据到客户端。同样,也需要两个文件,接口类和接口实现类。

    ①想要访问数据库并且操作,只能通过dao层向数据库发送sql语句,将这些结果通过接口传给service层。
    ②想要处理数据,要先向dao层请求数据,对dao层传过来的数据进行加工处理,将这些处理好的数据通过接口传给controller层。
    ③客户想要查询或修改数据时,要先向service层请求数据,收集service层传过来的数据,将这些数据通过接口显示给客户,一般通过html等方法给客户。

-------------本文结束感谢您的阅读-------------