news 2026/10/18 12:06:02

SpringBoot+以太坊的项目评审系统:智能合约存证开发实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot+以太坊的项目评审系统:智能合约存证开发实战

简介:一份面向计算机相关专业(软件工程、计科、区块链、人工智能等)学生的基于SpringBoot与以太坊的项目评审系统毕业设计资源包,覆盖课题设计、服务端开发、智能合约编写与部署交付全流程,既适合作为毕业设计、课程设计或实训项目的完整参考,也方便入门者按文档逐步复现。压缩包共208个文件、约771KB,内含75个GIF过程演示、51个Java类、23个JS脚本、22个FTL模板与9个CSS样式,并辅以以太坊智能合约(.sol与编译产物)、SQL初始化脚本和部署文档,便于快速还原项目环境、理清前后端与合约交互。当前已有110人学习下载,属于小范围流通的高分毕业设计资源。项目源码经过完整功能测试,答辩评审得分95分,结构清晰、扩展性强,既可直接用于课题交付,也可按需做二次开发;配套的过程演示、数据脚本与部署说明,能显著降低环境搭建与排错成本,帮助读者高效完成项目。

1. 为什么项目评审系统要碰以太坊:毕设选题值不值得做

做毕设选“基于SpringBoot+以太坊的项目评审系统”这个题目,很多人其实是冲着区块链三个字来的。框架学习和页面开发都不难,难的是把以太坊真正接进业务里,让答辩的时候有东西可讲、有数据可验证。这套系统把评审业务搬上链:项目的申报、专家打分、最终评审结论都会生成一条存证交易,保证数据一旦落定不可篡改。它解决的问题不是“做一个系统”,而是“证明评审过程可信”。适合三类人:想用最少成本把区块链写进毕业设计的人、想给公司内部评审流程加审计能力的开发者、以及需要在简历里同时展示SpringBoot后端和智能合约两项技能的人。

我帮你把这条技术路线拆到底。从架构选型、智能合约编写、SpringBoot集成,到部署顺序和常见问题,全部按可复现的标准写清楚。你照着做完,手里会有一套能跑、能演示、能回答追问的完整作品。

2. 分层架构与数据模型:链上存证、链下业务怎么配合

2.1 为什么让SpringBoot管业务、让以太坊管存证

项目评审系统如果所有数据都上链,会非常难用。区块链的每一次写入都要消耗Gas,测试网虽然免费但网络确认有延迟,区块浏览器的查询速度也远不如关系型数据库。常见的正确做法是“链下业务、链上存证”:SpringBoot负责管理项目信息、专家名单、评审打分这些日常操作,数据落在MySQL里;等到一条评审记录被最终确认,系统把这条记录的摘要哈希写到以太坊上,拿到交易哈希和区块号。

这样的分层有几个实际好处。第一,业务功能开发和普通Java Web项目没有区别,学习成本低。第二,上链的数据量很小,一辈子也就几千条,Gas费用在测试网可以忽略。第三,答辩时你可以清楚地说出“链上存的是指纹,链下存的是原文件”,这句话本身就展示了设计思考。链上存证的核心逻辑是:原数据放在MySQL,把原数据的SHA-256摘要写进合约,然后用合约里的摘要和MySQL里的摘要做比对,两边一致就说明没有被篡改。

2.2 评审上链的系统模块划分与核心表结构

系统按角色划分的话,典型模块有四个:项目管理、专家管理、评审打分、存证查询。项目管理负责申报项目的基本资料维护;专家管理负责维护评审专家名单和分配关系;评审打分是各类评审系统最复杂的部分,要支持打分项、权重、总分计算;存证查询是区块链部分最直观的功能,输入一个项目编号或者一次评审的ID就能看到链上的交易信息。

表结构设计时,我给了一张“评审记录”表做主表,另外两张基础表是项目和专家。评审记录表里的关键字段包括:评审编号、项目ID、专家ID、各项得分、总分、评审时间,以及两个区块链相关字段:交易哈希和区块高度。

CREATE TABLE review_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, project_id BIGINT NOT NULL, expert_id BIGINT NOT NULL, score_tech INT, score_demo INT, score_innovation INT, total_score DECIMAL(6,2), data_hash VARCHAR(64) NOT NULL COMMENT '评审明细的SHA-256摘要', tx_hash VARCHAR(66) COMMENT '上链后的交易哈希', block_number BIGINT COMMENT '上链后的区块高度', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_review (tx_hash), UNIQUE KEY uk_project_expert (project_id, expert_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

字段的先后顺序是有讲究的。data_hash必须先算好才去上链,所以它在tx_hash之前出现。uk_review是交易哈希的唯一约束,防止同一条交易被重复接进库里。uk_project_expert是业务上的唯一约束,同一个专家对同一个项目只能有一次评审记录,这是后面做幂等控制的基础。

2.3 接入以太坊的两种方式:本地模拟链与测试网

开发阶段我强烈建议先用本地模拟链。本地模拟链是跑在自己机器上的以太坊开发环境,启动速度快,出块间隔可以调短到秒级,挖矿节点不会无缘无故断连。配合SpringBoot调试时,send()操作几乎瞬间返回,不用等真实的网络确认,这对反复改代码的人非常重要。

等本地跑通了,再切换到测试网。测试网的切换成本很低,只需要改一个RPC端点和账户私钥。这里有个常见误区:有些人一上来就接测试网,结果网络本身不稳定,一直超时,最后分不清是代码问题还是网络问题。我的习惯是“先本地把业务逻辑验完,再上测试网验证一次部署链路”,两步分开排错。测试网上的地址和本地模拟链的地址格式完全一样,代码里不需要有分支判断,唯一要改的是配置项。

2.4 部署文档里必须写清楚的四个部分

标题里强调“部署文档”,这一点值得多说几句。一份合格的部署文档至少要回答四件事:网络环境需要什么、依赖服务怎么启动、智能合约怎么部署、SpringBoot应用怎么配置。我见过不少项目资料齐全但部署文档写得像流水账,把启动命令抄一遍就算完事。真正的部署文档应该让人在断网状态下,照着你的步骤一步步操作,也能把系统跑起来。

网络环境部分要写明节点服务地址、端口开放情况;依赖服务部分写清楚MySQL的初始化脚本和默认账号密码;智能合约部分写清楚编译命令、部署命令、部署后的合约地址该粘贴到哪里;SpringBoot部分写清楚配置文件里的每一项作用。这四部分我会在后文逐步展开,每一处都会给你现成的命令和检查点。

3. 智能合约开发与部署:从Solidity代码到链上地址

3.1 合约目录结构与Solidity版本选择

以太坊智能合约的官方语言是Solidity,你的SpringBoot工程里可以开一个contracts目录专门放合约文件。我常用的目录结构是这样的:

src/main/resources/contracts/ ├── ProjectReviewStorage.sol └── README.md

Solidity版本方面,建议用0.8.x这个区间。0.8版本自带整数溢出检查和require的常用写法,对初学者来说能少踩很多坑。如果你在本地装了编译工具,把版本号写进pragma里就行。

// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract ProjectReviewStorage { address public immutable owner; address public manager; mapping(uint256 => ReviewRecord) private records; mapping(uint256 => bool) private exists; struct ReviewRecord { string dataHash; address operator; uint256 timestamp; uint256 blockNumber; } event ReviewStored(uint256 indexed reviewId, string dataHash, address indexed operator, uint256 blockNumber); constructor(address _manager) { owner = msg.sender; manager = _manager; } function setManager(address _newManager) external { require(msg.sender == owner, "only owner can set manager"); manager = _newManager; } function storeReview(uint256 _reviewId, string calldata _dataHash) external returns (uint256 blockNumber) { require(msg.sender == manager, "only manager can store review"); require(!exists[_reviewId], "review already stored"); require(bytes(_dataHash).length == 64, "dataHash must be 64 hex chars"); ReviewRecord storage r = records[_reviewId]; r.dataHash = _dataHash; r.operator = msg.sender; r.timestamp = block.timestamp; r.blockNumber = block.number; exists[_reviewId] = true; emit ReviewStored(_reviewId, _dataHash, msg.sender, block.number); return block.number; } function getStoredHash(uint256 _reviewId) external view returns (string memory) { require(exists[_reviewId], "review not exists"); return records[_reviewId].dataHash; } function isStored(uint256 _reviewId) external view returns (bool) { return exists[_reviewId]; } }

这段合约的核心逻辑并不复杂。storeReview要求调用者必须是manager地址,这是权限控制;exists这个映射用来防止同一条评审记录重复上链;dataHash要求是64位十六进制字符串,对应一个SHA-256摘要。getStoredHash是查询接口,SpringBoot后端拿到reviewId之后可以通过它把链上的哈希取回来比对。

3.2 部署合约前的准备:私钥、Gas参数与编译产物

Solidity合约不能直接交给SpringBoot运行,必须经过编译成字节码,再部署到区块链网络上,拿到一个合约地址。整个过程分三步:编译、部署、记录地址。很多人在这里翻车,把编译和部署混在一起,出错后不知道是编译器问题还是网络问题。我一般会分开执行,先在命令行编译成功,再写部署工具。

如果你用的是Maven或Gradle管理SpringBoot工程,可以在build.gradle里添加一个Java类作为部署入口。部署的时候需要三个关键参数:节点RPC地址、部署者私钥、manager地址。RPC地址在本地模拟链启动时会显示,测试网的RPC地址由节点服务商提供。私钥必须妥善保存,它对应的是你的区块链账户,泄露等于资金和合约管理权都交出去了。

一个常见的设计问题是:谁来做manager?毕设场景里Manager可以让某个固定地址来担任,也可以设计成让系统管理员修改。实际项目里,manager通常是后端服务使用的“服务账户”,后端持有这个账户的私钥,用它来调用storeReview。这样前端用户不需要直接和区块链交互,业务逻辑也保持在后端。

3.3 用Java工具类部署合约:最直接的启动方式

我这里提供一段核心的Java部署代码,逻辑是:连接节点、创建凭证、设置Gas参数、部署合约、打印地址。

public class ContractDeployer { public static void main(String[] args) throws Exception { String rpcUrl = "http://localhost:7545"; // 本地模拟链默认RPC String privateKey = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; Web3j web3j = Web3j.build(new HttpService(rpcUrl)); Credentials credentials = Credentials.create(privateKey); BigInteger gasPrice = Convert.toWei("20", Convert.Unit.GWEI).toBigInteger(); BigInteger gasLimit = BigInteger.valueOf(300000L); String managerAddress = credentials.getAddress(); ProjectReviewStorage contract = ProjectReviewStorage.deploy( web3j, credentials, gasPrice, gasLimit, managerAddress ).send(); System.out.println("合约地址: " + contract.getContractAddress()); System.out.println("部署区块: " + contract.getDeployBlock()); web3j.shutdown(); } }

这段部署工具里,gasPrice设为20 GWEI是测试网常见的起步值;本地模拟链对Gas价格并不敏感,但写成20 GWEI能模拟真实环境。gasLimit设成300000,是给合约构造函数和初始存储留的余量,很多合约因为Gas上限设太低,部署时直接报“out of gas”。managerAddress这里直接用了部署者地址,也就是说部署者自己就是manager,但实际系统里最好单独生成一个服务账户地址。

3.4 部署成功后的验证:交易回执里有哪些信息

部署成功后,控制台会打印合约地址。此时不要急着写SpringBoot代码,先做一轮验证:把合约地址拿到区块链浏览器上打开,能看到合约代码和交易记录才算真正的部署成功。如果没有区块链浏览器,也可以用Java代码直接查询部署交易的回执。

TransactionReceipt receipt = web3j.ethGetTransactionReceipt(txHash).send().get(); System.out.println("状态: " + receipt.getStatus()); System.out.println("区块号: " + receipt.getBlockNumber()); System.out.println("Gas使用: " + receipt.getGasUsed());

交易回执里的值值得解释一下。status为1表示交易成功,0表示失败;blockNumber说明这笔交易被打包进了哪个区块,是把区块链和业务数据关联起来的重要桥梁;gasUsed可以帮助你估算后续调用合约的Gas上限,比如看到部署用了20万Gas,那业务调用可以适当放宽。拿到合约地址后,建议把它写进SpringBoot的配置文件中并提交到文档里,不然重启后找不回来。

4. SpringBoot集成以太坊:初始化、上链服务与查询接口

4.1 Web3j依赖与节点连接配置

SpringBoot和以太坊之间常用Web3j这个库来通信。Web3j相当于一个Java版的区块链客户端,它把JSON-RPC请求封装成了对象方法,让你不用手动组装请求体。在pom.xml里引入依赖之后,配置一个Web3j实例就能开始工作。

<dependency> <groupId>org.web3j</groupId> <artifactId>core</artifactId> <version>4.9.8</version> </dependency>

版本号选4.x的稳定版本即可,不建议追最新版本,因为Web3j的部分API在升级后会有调整。配置方面,我习惯把节点地址和私钥放在application.yml里,方便不同环境切换。

web3j: rpc-url: http://localhost:7545 private-key: 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 contract: review-storage-address: 0x你的合约地址

接私钥放在配置文件里只是一种开发阶段的做法。生产环境不应该把私钥明文写在配置文件里,可以用环境变量或密钥管理服务注入。毕设场景下放在application.yml没问题,但答辩时如果被问到安全性,你要能说出“实际部署会用环境变量替换”这个方案。

4.2 合约加载与Credentials私钥管理

有了RPC地址和私钥,下一步就是把合约加载到SpringBoot里。加载合约需要四样东西:合约地址、Web3j对象、Credentials凭证、Gas参数。Credentials是从私钥创建出来的,它代表你的区块链身份。

@Configuration public class Web3jConfig { @Value("${web3j.rpc-url}") private String rpcUrl; @Value("${web3j.private-key}") private String privateKey; @Bean public Web3j web3j() { return Web3j.build(new HttpService(rpcUrl)); } @Bean public Credentials credentials() { return Credentials.create(privateKey); } @Bean public ProjectReviewStorage reviewStorageContract( Web3j web3j, Credentials credentials, @Value("${contract.review-storage-address}") String contractAddress) { return ProjectReviewStorage.load( contractAddress, web3j, credentials, DefaultGasProvider.GAS_PRICE, DefaultGasProvider.GAS_LIMIT ); } }

注意Credentials.create只接受不带0x前缀的私钥会报错,填错了它会直接抛异常。加载合约之后,ProjectReviewStorage对象就相当于一个本地代理,你调用它的storeReview方法,它会自动帮你构造交易并发送到链上。

4.3 评审结果上链服务:业务数据怎么变成交易

核心上链服务要做的事情是:接收一条评审记录,计算它的摘要,调用合约存入链上,然后把返回的交易哈希和区块号更新回数据库。这里计算摘要时要注意,不要用整个对象去算,而是拼接出固定格式的字符串再算。

@Service public class ReviewChainService { private static final Logger log = LoggerFactory.getLogger(ReviewChainService.class); private final ProjectReviewStorage contract; private final ReviewRecordMapper reviewRecordMapper; public ReviewChainService(ProjectReviewStorage contract, ReviewRecordMapper reviewRecordMapper) { this.contract = contract; this.reviewRecordMapper = reviewRecordMapper; } public void storeReview(ReviewRecord record) { if (StringUtils.hasText(record.getTxHash())) { throw new BusinessException("该评审已上链,请勿重复操作"); } String hash = calcHash(record); try { TransactionReceipt receipt = contract.storeReview( record.getId().longValue(), hash ).send(); ReviewRecord update = new ReviewRecord(); update.setId(record.getId()); update.setTxHash(receipt.getTransactionHash()); update.setBlockNumber(receipt.getBlockNumber().longValue()); reviewRecordMapper.updateBlockchainInfo(update); log.info("评审上链成功, id={}, txHash={}", record.getId(), receipt.getTransactionHash()); } catch (Exception e) { log.error("评审上链失败, id={}", record.getId(), e); throw new BusinessException("上链失败,请稍后重试"); } } private String calcHash(ReviewRecord record) { String raw = record.getProjectId() + "-" + record.getExpertId() + "-" + record.getScoreTech() + "-" + record.getScoreDemo() + "-" + record.getScoreInnovation() + "-" + record.getTotalScore(); return DigestUtils.sha256Hex(raw); } }

storeReview方法发送的是同步调用,会等到交易被打包并返回回执才继续执行。calcHash拼接了业务主键和各项得分,顺序不同会产生不同哈希,所以要定好规范。这里有一个容易忽略的点:storeReview的入参是uint256,Java里对应BigInteger,直接把Long传进去没问题,但如果record.getId()意外为null就会空指针,所以调用前要做非空校验。

4.4 查询存证接口:从tx_hash到评审明细的完整链路

查询接口是答辩时最容易演示的功能。用户在页面上输入评审ID,后端先查MySQL拿到业务数据,再从合约里查链上存的哈希,然后将两者比较,返回“一致”或“不一致”。

@RestController @RequestMapping("/api/review") public class ReviewVerifyController { private final ReviewRecordMapper reviewRecordMapper; private final ProjectReviewStorage contract; public ReviewVerifyController(ReviewRecordMapper reviewRecordMapper, ProjectReviewStorage contract) { this.reviewRecordMapper = reviewRecordMapper; this.contract = contract; } @GetMapping("/verify/{reviewId}") public Result verify(@PathVariable Long reviewId) throws Exception { ReviewRecord record = reviewRecordMapper.selectById(reviewId); if (record == null) { return Result.fail("评审记录不存在"); } if (record.getTxHash() == null) { return Result.fail("该评审未上链"); } String chainHash = contract.getStoredHash(reviewId.longValue()).send(); String localHash = calcHash(record); boolean match = chainHash.equalsIgnoreCase(localHash); VerifyVO vo = new VerifyVO(); vo.setReviewId(reviewId); vo.setLocalHash(localHash); vo.setChainHash(chainHash); vo.setTxHash(record.getTxHash()); vo.setBlockNumber(record.getBlockNumber()); vo.setMatch(match); return Result.ok(vo); } private String calcHash(ReviewRecord record) { // 必须与上链时完全一致 String raw = record.getProjectId() + "-" + record.getExpertId() + "-" + record.getScoreTech() + "-" + record.getScoreDemo() + "-" + record.getScoreInnovation() + "-" + record.getTotalScore(); return DigestUtils.sha256Hex(raw); } }

两个calcHash方法是同一套拼接规则,这就是链上链下对得上的前提。实际项目里我会把calcHash抽到一个工具类里,避免两处写出差异。getStoredHash是合约里的view方法,调用它不产生交易,也不消耗Gas,可以放心反复查询。

5. 部署与运行避坑:连不上的节点、丢掉的合约地址和重复上链

5.1 连接测试网失败:节点服务配置与本地兜底

现象:SpringBoot应用启动时报Connection refused或connect timed out,Web3j无法建立连接。
原因:节点RPC地址配置错误,或本地网络无法访问公共节点服务。
解决:先检查rpc-url是否能直接访问。用命令行工具直接访问RPC接口,能返回JSON就说明网络通。如果网络不通,毕设阶段别折腾,直接把配置切回本地模拟链,业务逻辑不会受影响。测试网在演示当天不稳定是常见的事,所以正式答辩前最后一天一定要再用本地模拟链做一次完整预演,防止现场翻车。

5.2 重启过后合约地址丢了:把地址变成可配置项

现象:系统第一次跑通,第二天启动后调用合约一直报合约不存在或调用失败。
原因:把合约地址硬编码在Service或Controller里,换电脑、换测试网后地址失效,或者代码重新部署后地址变了。
解决:把所有合约地址统一放在application.yml里,部署合约后把新地址写入配置,重启即可生效。我在3.3节里特意让你打印合约地址,就是希望你立刻把它落到配置文件中并用@Value注入。

5.3 调用合约一直超时:Gas参数与同步阻塞的排查

现象:storeReview调用后长时间不返回,控制台一直停在等待状态,最后超时。
原因:Gas价格设得太低,交易在节点里排队等打包;或者同步调用Block住了Tomcat线程,多条请求一起超时。
解决:本地模拟链把Gas Price调低甚至设为0没关系,测试网建议不低于20 GWEI。事务提交不要放在请求线程里,改成异步任务或MQ队列,让用户先看到“已受理”,上链完成再回调更新状态。这块不让用户干等,体验会好很多。

5.4 同一条评审被重复上链:唯一约束与幂等控制

现象:页面重复提交或者后端重试,库里出现两条相同业务ID的存证记录,合约exists映射也判断不了不同ID的数据。
原因:接口没有做幂等控制,前端也没有禁用按钮。
解决:数据库里tx_hash和project_id + expert_id都建唯一索引,代码里先查一次txHash是否为空,再做上链操作。同时在前端提交后把按钮置灰,双保险。合约里的exists[_reviewId]是链上防重,数据库唯一索引是链下防重,两层一起兜底才算完整。

5.5 评审权限失控:把以太坊地址和用户身份绑定

现象:随便一个人调用storeReview也能成功上链,或者后端服务换了一把私钥后调用被拒绝。
原因:合约里只判断manager地址,而后端用户表里没有维护地址和人的绑定关系。
解决:用户表增加wallet_address字段,管理员账号在初始化时把钱包地址登记进去。业务要求每个评审上链操作必须来自管理员,那么合约的manager就设置成管理员地址,后端持有的私钥和这个地址保持一致。这个设计在答辩时会被问到权限模型,值得提前想清楚。

6. 答辩前必做的三项验证与二次开发切入点

6.1 演示顺序:上链前、上链中、上链后

答辩演示不要从头登录到退出,那太冗长。我建议按“上链前、上链中、上链后”三步走。上链前:展示一个评审记录的完整内容,各项打分和总分都在MySQL里;上链中:点击“上链”按钮,后端实时打印日志,页面显示交易哈希和区块号;上链后:打开区块链浏览器展示这笔交易,再回到系统点击“验真”,对比结果一致。这个闭环展示的是完整链路,比讲一百页PPT都有说服力。

6.2 答辩追问怎么答:交易不可篡改、区块浏览器与回执

答辩老师大概率会问“凭什么说数据不可篡改”。这时候你别背概念,直接操作:在数据库里改一条评审分数,再点验真,系统返回“不一致”。一次操作胜过一套解释。还会问“链上存的到底是什么”,你的回答是:链上存的是摘要,不是原始数据。这既是成本考虑,也是隐私考虑。还会问“交易哈希能不能查”,你打开浏览器输入tx_hash就能看到交易详情,这比口头解释真实性高很多。

6.3 值得继续做的三个方向

这套系统跑通之后,有三条路可以继续深入。第一,把上链状态做成定时任务自动推送,申请、评审、公示三个阶段全部上链,形成完整的评审生命周期存证。第二,在管理端增加一个区块浏览器,把链上交易列表直接嵌入系统页面,用户不用跳出系统。第三,引入多签机制,重大评审结果需要两个管理员地址共同确认才上链,这是区块链应用里很经典的权限进化方向。我在自己做类似需求时,第二次迭代就加了多签,效果是评审流程的信任感明显提升,答辩的时候这也成了核心亮点。

做完这套系统后,我自己最深的感受是:区块链在毕设里不是一个空泛的理念,它是可以被精确验证的工程组件。与其让评审变复杂,不如只让它更可信。希望这篇笔记能帮你少走一些弯路,祝你的项目顺利跑通,答辩传来好消息。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/18 12:04:09

Claude Code子代理系统:打造可控的AI专家团队

子代理系统是我在 Claude Code 里用起来最上瘾、也最容易被误用的功能。前几篇聊了基本命令、上下文管理这些“单兵作战”技巧&#xff0c;今天这篇专门聊它内置的子代理&#xff08;subagent&#xff09;系统——说白了&#xff0c;就是让你不再对着一个 AI 对话窗口重复解释同…

作者头像 李华
网站建设 2026/10/18 12:03:18

RAG实战:检索增强生成如何解决大模型幻觉与知识库时效难题

做AI应用这几年&#xff0c;我一直觉得有个问题绕不开&#xff1a;大模型回答得再流畅&#xff0c;也没法保证它说的每一句都对。问一个产品上线时间&#xff0c;它可能一本正经地给出一个完全虚构的日期&#xff1b;问一份内部制度文件&#xff0c;它更是直接瞎编。这就是圈里…

作者头像 李华
网站建设 2026/10/18 12:01:22

Qwen3思考标记处理:从协议关闭到服务端剥离的完整实践

最近这一波大模型切换潮里&#xff0c;很多人是带着“能用就行”的心态把Qwen3接到业务里的。但真跑起来才发现&#xff0c;接入大模型和接入普通API完全是两码事。最典型的坑就是&#xff1a;同样一句提示词&#xff0c;响应结果里多出来一大段用think和/think包起来的文字&am…

作者头像 李华
网站建设 2026/10/18 12:00:02

Python构建名中医肿瘤治疗教学案例库:从结构化设计到智能推荐

名老中医治疗肿瘤的经验&#xff0c;一直是中医学术里最珍贵也最棘手传承的一部分。说它珍贵&#xff0c;是因为这些经验往往凝聚了名家几十年的临证智慧&#xff0c;是教材上找不到的活知识&#xff1b;说它棘手&#xff0c;是因为这些经验散落在海量的纸质医案、跟诊笔记里&a…

作者头像 李华
网站建设 2026/10/18 11:59:48

HMAC-SHA512算法详解:原理、实现与工程实践

1. 消息认证码到底是什么很多人第一次接触 Hmac 算法时&#xff0c;都会下意识把它归类到“加密算法”那一栏&#xff0c;然后开始纠结&#xff1a;它到底是用来加密数据还是解密数据的&#xff1f;密钥长度是多少&#xff1f;能不能像 AES 那样直接加解密文件&#xff1f;这个…

作者头像 李华
网站建设 2026/10/18 11:57:14

AI辅助期末复习:一周提20分的实操方法

期末出分那天&#xff0c;我盯着成绩单愣了好几秒——72分到92分&#xff0c;只用了一周。说实话&#xff0c;我自己都没想到AI能把复习效率拉高这么多。这一周我没有通宵&#xff0c;没有刷十套卷子&#xff0c;只是把“复习”这件事的每一个环节都重做了一遍&#xff0c;凡是…

作者头像 李华