茶叶产业结合区块链技术的实战项目
一. 项目背景
1. 茶叶产业现状
茶叶作为一种传统饮品,在全球范围内享有广泛的消费市场。根据市场研究,茶叶产业的市场规模持续增长,尤其是在中国、印度和其他茶叶生产国。现代消费者对茶叶的需求不仅限于口感和品质,越来越重视产品的来源和生产过程。
2. 市场规模
全球茶叶市场的规模在不断扩大,预计在未来几年将继续保持增长。根据市场调研机构的报告,2022年全球茶叶市场规模已经达到数百亿美元,预计到2027年将突破数千亿美元。这一增长受益于健康意识的提高、对天然饮品的偏好以及新兴市场的消费能力提升。
3. 常见问题
- 假冒伪劣产品:茶叶行业面临着假冒伪劣产品的挑战。一些不法商家利用消费者对品牌和质量的认知不足,出售劣质茶叶。这不仅损害了消费者的利益,也影响了整个行业的声誉。
- 信息不透明:许多消费者对茶叶的来源、生产工艺和质量标准缺乏清晰的信息,导致他们难以做出明智的消费决策。这种信息的不对称使得消费者对购买的茶叶质量产生疑虑,影响了市场的健康发展。
- 质量标准不一:不同地区和品牌的茶叶质量标准不一,缺乏统一的行业规范,导致市场混乱。消费者在购买时难以判断产品的真实质量。
二. 区块链的作用与局限性
1. 茶叶安全与区块链的作用
- 追溯能力:
- 区块链技术提供的追溯能力确实可以帮助快速定位问题来源。如果发现某批茶叶存在安全隐患,可以通过区块链系统追踪到具体的生产、加工和运输环节,迅速识别出问题所在。
- 透明记录:
- 所有信息都在区块链上记录,确保了数据的透明性和不可篡改性。这意味着消费者和相关方都可以随时查看茶叶的来源和加工信息,从而加强对产品的信任。
2. 区块链的局限性
虽然区块链能在追溯和信息透明性上提供支持,但它本身并不能直接解决产品质量问题。以下是一些局限性:
- 质量控制在源头:
- 区块链记录的是已经发生的事件。如果生产商提供的茶叶本身存在质量问题,区块链无法替代质量控制措施。解决质量问题需要从源头加强监管和标准制定。
- 后续措施依赖人工:
- 一旦发现问题,虽然区块链能帮助定位,但后续的处理(如召回、赔偿等)依赖于相关企业或监管机构的行动,而这并非区块链技术本身所能直接控制。
- 需要完整的生态系统:
- 有效的溯源系统需要产业链各方的合作,包括生产商、加工商、物流公司和监管机构。如果某一方不遵循标准或不记录信息,整体的溯源效果将受到影响。
3. 总结
因此,区块链在食品安全方面的作用主要体现在追溯和透明度上,但解决质量问题仍需要综合的质量管理和监管措施。区块链提供的工具可以有效支持这些措施,但不是解决方案的全部。
区块链保障数据安全,而不是溯源的商品安全。
三. 项目需求分析
1. 用户角色:茶农、加工商、物流公司和消费者
2. 功能需求:
- 茶叶信息登记
- 生产和加工记录
- 物流跟踪
- 消费者查询系统
3. 架构设计
4. 数据模型
- 区块表:记录区块号,用于区块更新
- 产品表:记录产品信息
- 溯源码表:记录溯源码信息
- 上链数据表:记录上传到区块链的信息
四. 技术选型
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', type: 'POST', 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("");
Gson gson = new GsonBuilder() .disableHtmlEscaping() .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
|
新建数据库连接,导入数据库文件
成功导入
2. 前往首页
1
| http://localhost:8007/index
|
3. 新增商品
我们使用上次实训5的生成的钱包地址
1 2 3 4 5 6 7 8 9
| { "code": "1", "msg": "生成钱包成功", "o": { "publicKey": "2e58fdca4a243486b9ecfd9287e3c56258eaee31761025ebc4acbe636e5c767b453b5465b755b22c1cb572c32e7e98241193b7e8dbe6ed2b6d3eb81668883772", "privateKey": "ced6cc51ee0d2063211564e53012aaac4c0b4978e8cc22a11ed998de4e4bcc3b", "address": "0xed6d1bcb771e59765ab65f43b9406669428af057" } }
|
4. 新增溯源码
5. 查看溯源码
点击新增流程(注:要记得先启动挖矿,到后面才能成功上链)
6. 下载图片
需要手机和电脑在同一网络下才能扫码查看
在线扫描二维码网站https://cli.im/text
访问一下,成功查到产品信息