在线招标系统是当前企业采购和政府采购数字化转型的核心基础设施。传统招投标流程普遍存在纸质文件流转效率低下、信息透明度不足、人为干预风险高等痛点。我们基于SpringBoot框架开发的这套系统,实现了从项目发布、投标、开标到评标的全流程电子化管理。
系统最核心的价值在于:
系统采用前后端分离架构:
选择SpringBoot的核心考量:
招标业务的核心表关系:
sql复制CREATE TABLE `bidding_project` (
`id` bigint NOT NULL AUTO_INCREMENT,
`project_name` varchar(100) NOT NULL COMMENT '项目名称',
`project_code` varchar(50) NOT NULL COMMENT '项目编号',
`budget` decimal(15,2) DEFAULT NULL COMMENT '预算金额',
`publish_time` datetime DEFAULT NULL COMMENT '发布时间',
`deadline` datetime NOT NULL COMMENT '截止时间',
`status` tinyint NOT NULL COMMENT '状态:0-待发布 1-招标中 2-已截标 3-已完成',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_project_code` (`project_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
关键设计原则:
采用状态模式实现招标生命周期管理:
java复制public enum ProjectStatus {
DRAFT {
@Override
public boolean canPublish() { return true; }
},
PUBLISHED {
@Override
public boolean canBid() { return true; }
},
// 其他状态...
}
@Service
public class ProjectStateService {
@Transactional
public void changeStatus(Long projectId, ProjectStatus newStatus) {
Project project = getCheckedProject(projectId);
if (!project.getStatus().canTransferTo(newStatus)) {
throw new BusinessException("状态转换非法");
}
// 状态变更逻辑...
}
}
投标文件安全方案:
核心加密逻辑:
java复制public class FileEncryptor {
private static final String ALGORITHM = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding";
public EncryptedFile encrypt(MultipartFile file, PublicKey publicKey) {
byte[] fileKey = generateAESKey();
byte[] iv = generateIV();
// AES加密文件内容
byte[] encryptedContent = aesEncrypt(file.getBytes(), fileKey, iv);
// RSA加密AES密钥
byte[] encryptedKey = rsaEncrypt(fileKey, publicKey);
return new EncryptedFile(encryptedContent, encryptedKey, iv);
}
}
系统角色划分:
权限拦截实现:
java复制@PreAuthorize("hasRole('TENDERER') &&
@permissionService.canAccessProject(#projectId)")
@GetMapping("/projects/{projectId}")
public ProjectDetailVO getProjectDetail(@PathVariable Long projectId) {
//...
}
采用AOP实现操作日志:
java复制@Aspect
@Component
public class AuditLogAspect {
@AfterReturning(
pointcut = "@annotation(auditLog)",
returning = "result")
public void afterReturning(JoinPoint jp, AuditLog auditLog, Object result) {
String operation = auditLog.value();
HttpServletRequest request = getRequest();
AuditLogEntry entry = new AuditLogEntry();
entry.setUserId(SecurityUtils.getCurrentUserId());
entry.setOperation(operation);
entry.setIp(IpUtils.getIpAddr(request));
entry.setParams(JsonUtils.toJson(jp.getArgs()));
logService.save(entry);
}
}
采用Redis分布式锁防止重复投标:
java复制public BidResponse submitBid(BidRequest request) {
String lockKey = "bid_lock:" + request.getProjectId() + ":" + request.getCompanyId();
try {
boolean locked = redisLock.tryLock(lockKey, 10, TimeUnit.SECONDS);
if (!locked) {
throw new BusinessException("操作过于频繁");
}
// 投标核心逻辑
return doSubmitBid(request);
} finally {
redisLock.unlock(lockKey);
}
}
使用多级缓存加速评标查询:
缓存更新策略:
java复制@Cacheable(value = "evaluation", key = "#projectId")
public EvaluationResult getEvaluationResult(Long projectId) {
// 数据库查询逻辑
}
@CacheEvict(value = "evaluation", key = "#projectId")
public void updateEvaluation(Evaluation evaluation) {
// 更新逻辑
}
Docker Compose编排示例:
yaml复制version: '3'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
redis:
image: redis:6
ports:
- "6379:6379"
app:
build: .
ports:
- "8080:8080"
depends_on:
- mysql
- redis
Prometheus监控配置:
yaml复制management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
metrics:
export:
prometheus:
enabled: true
关键监控指标:
问题现象:大文件上传经常超时失败
解决方案:
核心合并逻辑:
java复制public void mergeChunks(String fileMd5, String fileName) throws IOException {
File tempDir = getTempDir(fileMd5);
File[] chunks = tempDir.listFiles();
try (FileOutputStream fos = new FileOutputStream(getDestFile(fileName))) {
for (int i = 0; i < chunks.length; i++) {
File chunk = new File(tempDir, i + ".tmp");
Files.copy(chunk.toPath(), fos);
}
}
// 验证合并后文件MD5
validateFileMd5(fileMd5, fileName);
}
问题现象:评标倒计时任务重复执行
解决方案:
改进后的任务调度:
java复制@Scheduled(cron = "0 0/5 * * * ?")
public void checkBiddingDeadline() {
String lockKey = "job:check_deadline";
try {
if (!redisLock.tryLock(lockKey, 30, TimeUnit.MINUTES)) {
return;
}
List<Project> projects = projectMapper.selectExpiredProjects();
projects.forEach(project -> {
try {
closeBidding(project.getId());
} catch (Exception e) {
log.error("关闭招标失败", e);
// 进入补偿流程
}
});
} finally {
redisLock.unlock(lockKey);
}
}
未来可引入:
增强可信度的方案:
实施示例:
solidity复制pragma solidity ^0.8.0;
contract Bidding {
struct Project {
string projectId;
uint256 deadline;
bool isClosed;
}
mapping(string => Project) public projects;
event ProjectClosed(string indexed projectId);
function closeBidding(string memory projectId) external {
require(!projects[projectId].isClosed, "Already closed");
require(block.timestamp >= projects[projectId].deadline, "Deadline not reached");
projects[projectId].isClosed = true;
emit ProjectClosed(projectId);
}
}