0%

区块链实训6

茶叶产业结合区块链技术的实战项目

一. 项目背景

1. 茶叶产业现状

茶叶作为一种传统饮品,在全球范围内享有广泛的消费市场。根据市场研究,茶叶产业的市场规模持续增长,尤其是在中国、印度和其他茶叶生产国。现代消费者对茶叶的需求不仅限于口感和品质,越来越重视产品的来源和生产过程。

2. 市场规模

全球茶叶市场的规模在不断扩大,预计在未来几年将继续保持增长。根据市场调研机构的报告,2022年全球茶叶市场规模已经达到数百亿美元,预计到2027年将突破数千亿美元。这一增长受益于健康意识的提高、对天然饮品的偏好以及新兴市场的消费能力提升。

3. 常见问题

  • 假冒伪劣产品:茶叶行业面临着假冒伪劣产品的挑战。一些不法商家利用消费者对品牌和质量的认知不足,出售劣质茶叶。这不仅损害了消费者的利益,也影响了整个行业的声誉。
  • 信息不透明:许多消费者对茶叶的来源、生产工艺和质量标准缺乏清晰的信息,导致他们难以做出明智的消费决策。这种信息的不对称使得消费者对购买的茶叶质量产生疑虑,影响了市场的健康发展。
  • 质量标准不一:不同地区和品牌的茶叶质量标准不一,缺乏统一的行业规范,导致市场混乱。消费者在购买时难以判断产品的真实质量。

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

1. 茶叶安全与区块链的作用

  1. 追溯能力
    • 区块链技术提供的追溯能力确实可以帮助快速定位问题来源。如果发现某批茶叶存在安全隐患,可以通过区块链系统追踪到具体的生产、加工和运输环节,迅速识别出问题所在。
  1. 透明记录
    • 所有信息都在区块链上记录,确保了数据的透明性和不可篡改性。这意味着消费者和相关方都可以随时查看茶叶的来源和加工信息,从而加强对产品的信任。

2. 区块链的局限性

虽然区块链能在追溯和信息透明性上提供支持,但它本身并不能直接解决产品质量问题。以下是一些局限性:

  1. 质量控制在源头
    • 区块链记录的是已经发生的事件。如果生产商提供的茶叶本身存在质量问题,区块链无法替代质量控制措施。解决质量问题需要从源头加强监管和标准制定。
  1. 后续措施依赖人工
    • 一旦发现问题,虽然区块链能帮助定位,但后续的处理(如召回、赔偿等)依赖于相关企业或监管机构的行动,而这并非区块链技术本身所能直接控制。
  1. 需要完整的生态系统
    • 有效的溯源系统需要产业链各方的合作,包括生产商、加工商、物流公司和监管机构。如果某一方不遵循标准或不记录信息,整体的溯源效果将受到影响。

3. 总结

因此,区块链在食品安全方面的作用主要体现在追溯和透明度上,但解决质量问题仍需要综合的质量管理和监管措施。区块链提供的工具可以有效支持这些措施,但不是解决方案的全部。

区块链保障数据安全,而不是溯源的商品安全。

三. 项目需求分析

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

2. 功能需求:

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

3. 架构设计

image-20241102145505211

4. 数据模型

  1. 区块表:记录区块号,用于区块更新

image-20241102145744871

  1. 产品表:记录产品信息

image-20241102145823267

image-20241102145834063

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

image-20241102145850546

image-20241102145856491

  1. 上链数据表:记录上传到区块链的信息

image-20241102145920479

image-20241102145927584

四. 技术选型

1. 开发工具

  • 代码开发工具:IntelliJ IDEA
  • 数据库可视化工具:navicat
  • 接口测试工具:postman

2. 系统环境

jdk11,mysql8.0.26

3. 前端技术

html+css+JavaScript,jquery

4. 后端技术

springboot,mybatis

五. 代码实现

项目地址:https://gitee.com/daitoulin/suyuan_project.git

1. 配置文件

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
#项目端口号
server.port=8007

spring.datasource.url=jdbc:mysql://localhost:3306/suyuan?useUnicode=true&useSSL=false&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

### datasource-pool
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.minimum-idle=10
spring.datasource.hikari.maximum-pool-size=30
spring.datasource.hikari.auto-commit=true
spring.datasource.hikari.idle-timeout=30000
spring.datasource.hikari.pool-name=HikariCP
spring.datasource.hikari.max-lifetime=900000
spring.datasource.hikari.connection-timeout=10000
spring.datasource.hikari.connection-test-query=SELECT 1
spring.datasource.hikari.validation-timeout=1000
spring.servlet.multipart.max-file-size=100MB
spring.servlet.multipart.max-request-size=100MB

mybatis.mapper-locations=classpath:/mybatis-mapper/*Mapper.xml
mybatis.configuration.map-underscore-to-camel-case=true


node.ip=192.168.132.1

spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.mode=HTML

2. 启动类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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")
@EnableScheduling
public class SuyuanApplication {

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

}

注:

@MapperScan(“com.example.suyuan.dao”)
扫描这个包下的文件,才能让mybatis正常运行

@EnableScheduling
启动定时任务功能

3. 上链接口完整链路

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>

对应路由代码块

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. 环境配置

git clone一下项目文件,然后右键pom文件添加maven项目拉取依赖

修改配置文件application.properties

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
#项目端口号
server.port=8007

spring.datasource.url=jdbc:mysql://localhost:3306/suyuan?useUnicode=true&useSSL=false&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

### datasource-pool
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.minimum-idle=10
spring.datasource.hikari.maximum-pool-size=30
spring.datasource.hikari.auto-commit=true
spring.datasource.hikari.idle-timeout=30000
spring.datasource.hikari.pool-name=HikariCP
spring.datasource.hikari.max-lifetime=900000
spring.datasource.hikari.connection-timeout=10000
spring.datasource.hikari.connection-test-query=SELECT 1
spring.datasource.hikari.validation-timeout=1000
spring.servlet.multipart.max-file-size=100MB
spring.servlet.multipart.max-request-size=100MB

mybatis.mapper-locations=classpath:/mybatis-mapper/*Mapper.xml
mybatis.configuration.map-underscore-to-camel-case=true


node.ip=192.132.43.96

spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.mode=HTML

新建数据库连接,导入数据库文件

image-20241102145553190

成功导入

image-20241102145728813

2. 前往首页

1
http://localhost:8007/index

image-20241102150954461

3. 新增商品

我们使用上次实训5的生成的钱包地址

1
2
3
4
5
6
7
8
9
{
"code": "1",
"msg": "生成钱包成功",
"o": {
"publicKey": "2e58fdca4a243486b9ecfd9287e3c56258eaee31761025ebc4acbe636e5c767b453b5465b755b22c1cb572c32e7e98241193b7e8dbe6ed2b6d3eb81668883772",
"privateKey": "ced6cc51ee0d2063211564e53012aaac4c0b4978e8cc22a11ed998de4e4bcc3b",
"address": "0xed6d1bcb771e59765ab65f43b9406669428af057"
}
}

image-20241102155327689

image-20241102155336368

4. 新增溯源码

image-20241102155541482

5. 查看溯源码

image-20241102155649663

image-20241102155707562

点击新增流程(注:要记得先启动挖矿,到后面才能成功上链)

image-20241102155741777

6. 下载图片

需要手机和电脑在同一网络下才能扫码查看

image-20241102160312068

在线扫描二维码网站https://cli.im/text

image-20241102160409314

image-20241102160734445

访问一下,成功查到产品信息

image-20241102161109642