1. 项目背景与核心需求
钱币收藏作为一项兼具文化价值和投资属性的爱好,近年来在国内发展迅速。传统的线下交流方式受限于地域和时间,难以满足藏友间即时分享、专业鉴定的需求。去年我接手了一个钱币收藏交流平台的项目,核心目标是打造一个集藏品展示、交流社区、在线鉴定、交易撮合于一体的Web应用。
这个系统需要解决三个核心痛点:
- 藏品信息管理混乱:藏友常通过Excel或纸质记录,难以系统化管理
- 专业鉴定门槛高:普通藏家缺乏可靠的鉴定渠道
- 交易风险大:线下交易缺乏保障机制
2. 技术选型与架构设计
2.1 前后端分离架构
采用B/S架构,前端使用Vue.js+Element UI,后端基于Spring Boot。这种组合的优势在于:
- 开发效率:Vue的组件化开发+Spring Boot的自动配置
- 性能:前后端分离减轻服务器压力
- 可维护性:清晰的接口定义降低耦合度
实际开发中发现,Element UI的表格组件对钱币图片展示不够友好,后来通过自定义卡片式布局解决了这个问题
2.2 数据库设计要点
MySQL表结构设计考虑了钱币的特殊属性:
sql复制CREATE TABLE `coin` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL COMMENT '钱币名称',
`dynasty` varchar(50) NOT NULL COMMENT '朝代',
`material` varchar(20) NOT NULL COMMENT '材质',
`face_value` varchar(20) DEFAULT NULL COMMENT '面值',
`mint_year` varchar(10) DEFAULT NULL COMMENT '铸造年份',
`diameter` decimal(5,2) DEFAULT NULL COMMENT '直径(mm)',
`weight` decimal(5,2) DEFAULT NULL COMMENT '重量(g)',
`grade` varchar(20) DEFAULT NULL COMMENT '品相等级',
`description` text COMMENT '详细描述',
`owner_id` bigint NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
特别设计了多角度图片存储表:
sql复制CREATE TABLE `coin_image` (
`id` bigint NOT NULL AUTO_INCREMENT,
`coin_id` bigint NOT NULL,
`image_url` varchar(255) NOT NULL,
`angle` varchar(20) NOT NULL COMMENT '拍摄角度(正/反/边齿等)',
`is_authentic` tinyint DEFAULT '0' COMMENT '鉴定结果',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3. 核心功能实现
3.1 钱币鉴定流程实现
鉴定功能采用工作流引擎设计:
- 用户提交鉴定申请
- 系统自动匹配在线鉴定师
- 多专家背靠背鉴定
- 结果仲裁机制
关键代码示例:
java复制@PostMapping("/authenticate")
public Result authenticate(@RequestBody AuthenticateRequest request) {
// 1. 校验用户权限
if (!userService.hasPermission(request.getUserId(), "authenticate")) {
return Result.error("无鉴定权限");
}
// 2. 分配鉴定师
List<Expert> experts = expertService.matchExperts(
request.getCoinType(),
request.getExpectedLevel()
);
// 3. 创建鉴定任务
AuthenticateTask task = new AuthenticateTask();
task.setCoinId(request.getCoinId());
task.setExperts(experts);
taskService.createTask(task);
// 4. 通知鉴定师
notificationService.notifyExperts(experts, task);
return Result.success(task.getId());
}
3.2 高并发场景优化
在钱币拍卖功能中遇到性能瓶颈,通过以下方案优化:
- Redis缓存热门钱币数据
- 使用Redisson实现分布式锁
- 消息队列削峰
java复制public boolean placeBid(Long coinId, Long userId, BigDecimal price) {
String lockKey = "bid_lock:" + coinId;
RLock lock = redissonClient.getLock(lockKey);
try {
// 获取分布式锁(等待3秒,持有10秒)
if (lock.tryLock(3, 10, TimeUnit.SECONDS)) {
// 检查当前最高价
BigDecimal currentMax = redisTemplate.opsForValue()
.get("coin_max_bid:" + coinId);
if (currentMax != null && price.compareTo(currentMax) <= 0) {
return false;
}
// 更新竞价
redisTemplate.opsForValue().set(
"coin_max_bid:" + coinId,
price,
1, TimeUnit.HOURS
);
// 记录竞价历史
mqTemplate.convertAndSend(
"bid.queue",
new BidMessage(coinId, userId, price)
);
return true;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
return false;
}
4. 安全设计与实践
4.1 钱币图片防盗链
钱币高清图片是核心资产,我们实现了:
- 动态URL签名
- 访问频率限制
- 水印叠加
Nginx配置示例:
nginx复制location ~* ^/protected-images/ {
valid_referers none blocked server_names ~\.example\.com;
if ($invalid_referer) {
return 403;
}
# 动态生成访问令牌
secure_link $arg_md5,$arg_expires;
secure_link_md5 "$secure_link_expires$uri$remote_addr secret";
if ($secure_link = "") {
return 403;
}
if ($secure_link = "0") {
return 410;
}
# 限制下载速度
limit_rate 500k;
}
4.2 敏感操作审计
所有钱币状态变更操作记录详细日志:
java复制@Aspect
@Component
public class OperationLogAspect {
@Autowired
private OperationLogService logService;
@Pointcut("@annotation(com.example.collect.anno.OperationLog)")
public void operationPointCut() {}
@Around("operationPointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
OperationLog annotation = method.getAnnotation(OperationLog.class);
String operation = annotation.value();
Long userId = SecurityUtils.getCurrentUserId();
Object[] args = joinPoint.getArgs();
String params = JsonUtils.toJson(args);
long beginTime = System.currentTimeMillis();
try {
Object result = joinPoint.proceed();
long time = System.currentTimeMillis() - beginTime;
// 异步记录日志
CompletableFuture.runAsync(() -> {
OperationLogEntity log = new OperationLogEntity();
log.setUserId(userId);
log.setOperation(operation);
log.setParams(params);
log.setTime(time);
log.setStatus(1);
logService.save(log);
});
return result;
} catch (Exception e) {
long time = System.currentTimeMillis() - beginTime;
CompletableFuture.runAsync(() -> {
OperationLogEntity log = new OperationLogEntity();
log.setUserId(userId);
log.setOperation(operation);
log.setParams(params);
log.setTime(time);
log.setStatus(0);
log.setErrorMsg(e.getMessage());
logService.save(log);
});
throw e;
}
}
}
5. 项目部署与监控
5.1 容器化部署方案
使用Docker Compose编排服务:
yaml复制version: '3.8'
services:
app:
image: collect-app:1.0
build: .
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
- REDIS_HOST=redis
- MYSQL_HOST=mysql
depends_on:
- redis
- mysql
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: collect
volumes:
- mysql_data:/var/lib/mysql
ports:
- "3306:3306"
redis:
image: redis:6.2
ports:
- "6379:6379"
volumes:
- redis_data:/data
prometheus:
image: prom/prometheus
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
grafana:
image: grafana/grafana
ports:
- "3000:3000"
volumes:
- grafana_data:/var/lib/grafana
volumes:
mysql_data:
redis_data:
grafana_data:
5.2 性能监控配置
Spring Boot Actuator + Prometheus配置:
yaml复制management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
metrics:
export:
prometheus:
enabled: true
tags:
application: collect-system
对应的Grafana监控看板包含:
- JVM内存/线程监控
- 接口响应时间P99
- 数据库连接池使用率
- Redis命中率
- 关键业务指标(日活、鉴定通过率等)
6. 开发中的经验总结
6.1 钱币数据标准化
初期没有统一数据标准导致的问题:
- 同一朝代不同用户使用不同名称(如"清" vs "清朝")
- 材质描述不规范("银" vs "925银")
- 品相评级标准不统一
解决方案:
- 建立基础数据字典表
- 开发数据清洗工具
- 在前端实现自动补全和校验
java复制public class CoinDataValidator {
private static final Map<String, String> DYNASTY_MAPPING = Map.of(
"清", "清朝",
"明", "明朝",
// 其他映射关系...
);
private static final Set<String> MATERIALS = Set.of(
"金", "银", "铜", "铁", "镍", "铝", "合金"
);
public static Coin normalize(Coin coin) {
// 规范化朝代
String dynasty = DYNASTY_MAPPING.getOrDefault(
coin.getDynasty(),
coin.getDynasty()
);
coin.setDynasty(dynasty);
// 校验材质
if (!MATERIALS.contains(coin.getMaterial())) {
throw new IllegalArgumentException("不支持的材质类型");
}
// 标准化重量单位
if (coin.getWeight() > 1000) { // 可能是以克为单位的输入
coin.setWeight(coin.getWeight() / 1000);
}
return coin;
}
}
6.2 图片处理优化
钱币图片处理的三个关键点:
- 自动旋转校正(很多用户上传的图片方向不正确)
- 多尺寸生成(列表页缩略图200x200,详情页中等尺寸800x800,原图保留)
- 特征提取(用于相似钱币推荐)
使用Thumbnailator实现图片处理:
java复制public class ImageProcessor {
public static void generateVariants(String originalPath, String outputDir, String filename)
throws IOException {
// 原始图片备份
File original = new File(originalPath);
FileUtils.copyFile(original, new File(outputDir, "original_" + filename));
// 中等尺寸(详情页)
Thumbnails.of(originalPath)
.size(800, 800)
.keepAspectRatio(true)
.toFile(new File(outputDir, "medium_" + filename));
// 缩略图(列表页)
Thumbnails.of(originalPath)
.size(200, 200)
.keepAspectRatio(true)
.toFile(new File(outputDir, "thumb_" + filename));
// 灰度图(用于特征提取)
Thumbnails.of(originalPath)
.size(500, 500)
.keepAspectRatio(true)
.outputFormat("jpg")
.imageType(BufferedImage.TYPE_BYTE_GRAY)
.toFile(new File(outputDir, "gray_" + filename));
}
public static void autoRotate(String imagePath) throws IOException {
BufferedImage image = ImageIO.read(new File(imagePath));
Metadata metadata = ImageMetadataReader.readMetadata(new File(imagePath));
for (Directory directory : metadata.getDirectories()) {
for (Tag tag : directory.getTags()) {
if ("Orientation".equals(tag.getTagName())) {
int orientation = Integer.parseInt(tag.getDescription().split(" ")[0]);
if (orientation > 1) {
BufferedImage corrected = rotateImage(image, orientation);
ImageIO.write(corrected, "jpg", new File(imagePath));
}
break;
}
}
}
}
private static BufferedImage rotateImage(BufferedImage image, int orientation) {
// 实现旋转逻辑...
}
}
7. 项目演进方向
当前系统已经实现了基础功能,后续计划:
- 引入区块链技术为稀有钱币生成数字证书
- 开发AR查看功能,实现钱币3D展示
- 构建钱币知识图谱,提供智能问答
- 接入权威鉴定机构API,提升鉴定公信力
在技术架构上,计划将单体应用逐步拆分为微服务:
- 用户服务
- 钱币元数据服务
- 鉴定服务
- 交易服务
- 内容服务
采用Spring Cloud Alibaba技术栈,通过Nacos实现服务发现和配置管理,Sentinel做流量控制。