一、实验目的
- 掌握springboot基本框架:dao层、service层和controller层;
- 前后端综合分析产品上链流程;
- 项目开发基本框架;
二、实验环境
- Windows 11
- IDEA java、maven、springboot
- navicat
- postman
三、实验步骤
一)项目背景
- 茶叶在全球范围内享有广泛的消费市场,产业市场规模持续增长,现代消费者对茶叶的需求不仅限于口感和品质,越来越重视产品的来源和生产过程。
- 常见问题:假冒伪劣产品;信息不透明;质量标准不一。
二)区块链的作用与局限性
- 茶叶安全与区块链的作用
- 追溯能力
- 透明记录:确保了数据的透明性和不可篡改性
- 区块链的局限性
- 质量控制在源头
- 后续措施依赖人工
- 需要完整的生态系统
- 总结
- 区块链在食品安全方面的作用主要体现在追溯和透明度上,但解决质量问题仍需要综合的质量管理和监管措施。
- 区块链提供的工具可以有效支持这些措施,但不是解决方案的全部。
- 区块链保障数据安全,而不是溯源商品的安全。
三)项目需求分析
用户角色:茶农、加工商、物流公司和消费者。
功能需求:
- 茶叶信息登记
- 生产和加工记录
- 物流跟踪
- 消费者查询系统
架构设计
前端展示层 html css javascript jquery 服务层 聚合服务 存储层 mysql数据库、区块文件 数据模型
区块表: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,
四)技术选型
- 开发工具
- 代码开发工具:IDEA
- 数据库可视化工具:navicat
- 接口测试工具:postman
- 系统环境
- jdk11
- mysql
- 前端技术:html+css+javascript,jquery
- 后端技术:spring boot,mybatis
五)代码
git clone https://gitee.com/daitoulin/suyuan_project.git分析suyuan_project项目框架:component主键-有定时功能,templates文件夹中存放网页框架。
![image-20241106002022044]()
配置文件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=true1
2
3#开启模板引擎,需要引入spring-boot-starter-thymeleaf包才能使用
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.mode=HTML关于新增HTML页面:templates文件夹中新建block.html,在htmlcontroller中添加一个,项目要重启才能生效,可以通过http://localhost:8007/block对新建的页面进行访问。
1
2
3
4
5
6
7
8
9
10
11<!--block.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
public String block(){
return "block";
}//htmlcontroller中新增一个方法![image-20241102164106441]()
启动引导节点、两个block_contract节点
![image-20241103205640695]()
使用配对的钱包地址和私钥
1
2
3
4
5
6
7
8
9
10{
"code": "1",
"msg": "生成钱包成功",
"o": {
"publicKey": "13aae02db917ba3cf26eb5201f55d9fe094be2ad8df09437918c3d278adde0af77ae39ae6a1575d592536287a78b58594638951bd7cc3f04153381f0a0fa6618",
"privateKey": "8676da2a245a145b1cc7e158d6b2221e892478c386dc0dcc28b9ed6772b08975",
"address": "0x4dd32f368a54e39ee7da24d6eb5ebecf7d062eb5"
}
}新增产品
![image-20241102154719521]()
新增溯源码,输入私钥
![image-20241102155016624]()
查看溯源码
新增流程1
![image-20241102155336364]()
新增流程2
![image-20241102165312487]()
打开navicat,链接blockchain.db可以查看到pending表中的两个上链信息
![image-20241102165428448]()
两个block_contract节点开始挖矿/starMining
从block_contract的browser文件夹进入index.html,进入挖矿后台,挖到交易信息
![image-20241103211910291]()
![image-20241103215331921]()
![image-20241103215638544]()
增加流程时,打开开发者工具,查看到toChain的信息
![image-20241103215545204]()
当新增成功,在开发者工具中可以查看到toChain,依次对应代码中的上链接口。
![image-20241103220100980]()
使用postman,toChain接口,进行新增流程如下图
![image-20241103220359152]()
可以从区块号93中看到刚刚新增流程操作均上链成功
![image-20241103220408896]()
下载图片,使用草料二维码工具,对其进行扫描,获取到链接,可以看到溯源结果如下
![image-20241103233624269]()
点击其中的流程,查看详细信息。
![image-20241103233654860]()
导入suyuan.sql新建数据库,查看数据模型—-四个数据表如下
①t_block区块表:记录区块号,用于区块更新
![image-20241106230508022]()
②t_chain_data上链数据表:记录上传到区块链的信息
![image-20241106230601267]()
③t_code溯源码表:记录溯源码信息
![image-20241106230622413]()
④t_product产品表:记录产品信息
![image-20241106230649961]()
项目启动类:
SuyuanApplication.java1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18package 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;
//扫描这个包下的文件,才能让mybatis正常运行
//启动定时任务功能
public class SuyuanApplication {
public static void main(String[] args) {
SpringApplication.run(SuyuanApplication.class, args);
}
}上链接口完整任务
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>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
public ResponseEntity<JSONObject> toChain( 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);
}
四、实验结果
新增产品、新增溯源码、新增流程、下载图片、扫描溯源码、对产品进行溯源
![image-20250112171412372]()
利用开发者工具进行跟踪
![image-20250112171421740]()
navicat连接block_chain.db查看数据上链信息
![image-20250112172040715]()
五、总结
关于溯源码扫描查看流程进行溯源,需要手机和电脑在同一网络下才能扫码查看,或者直接电脑扫描查看。
如果想重置区块链需要把所有节点项目的db和ws删除,仅删除一个节点的db和ws是无效的,一旦开启,两个对等节点会完成数据的对等共享,必须删除所有节点项目的相关内容。
本次实验启动溯源项目节点,启动引导节点、两个block_contract节点【两个分节点开始挖矿starMining】(至少两个节点,否则不能通过验证)。出现点击了“提交”新建流程,但是不显示新建成功的,应该是有节点没有开启;具体情况可以根据进入开发者工具进行查看分析。
t_code表中img_url即溯源码对应的链接,对应generateQRCode生成二维码的接口,对应前端溯源码下载图片;后通过草料二维码工具,对产品信息进行溯源。
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等方法给客户。























