
钱币收藏这个圈子信息不透明、交流渠道散一直是个老问题。早些年大家混论坛、泡贴吧后来转战微信群但找一枚特定版式的钱币往往要在十几个群里反复刷屏图片模糊、价格混乱、真假难辨。我前年做了一款基于Spring Boot的钱币收藏交流系统把藏品管理、求购匹配、社区讨论和交易记录放到一起多少解决了这些痛点。这篇文章就从需求拆解、技术选型、数据库设计到核心功能实现把我实际操作中的完整思路和踩过的坑一次性讲清楚。项目本身不复杂但胜在覆盖面全——用户体系、藏品信息发布、图片存储、全文检索、社区互动、交易闭环几乎把Spring Boot生态里常用的东西都过了一遍。如果你正准备做类似的领域性交流平台或者正在为毕业设计选型这篇文章的参考价值会很高。1. 项目整体思路与需求拆解1.1 钱币收藏交流系统到底要解决什么问题先说需求。钱币收藏和一般电商有本质区别——它既是商品交易又是知识交流还带很强的圈子属性。一枚古钱币的价值判断涉及版别、品相、包浆、铸造工艺新手和老手对同一枚钱币的认知可能天差地别。所以系统不能只做“挂商品-下单”这种简单流程至少要覆盖三个层次藏品展示与查询、藏友间的信息交换、以及围绕钱币知识的社区讨论。我最初和几个玩钱币的朋友聊需求他们反馈最多的痛点是信息孤岛。有人手里有重复的版别想出有人满世界找某个特定年号的版式但双方碰不到一起。微信群里的消息刷得太快论坛的搜索功能又基本是摆设。所以我给系统的定位是以藏品为核心数据以交流为价值纽带以交易为最终闭环。用户先建自己的藏品库再从藏品库发起求购或出售同时可以发帖讨论版别真假。这样一来系统里的每一笔交易都有藏品数据做背书每一场讨论都和实物对应得上信息价值就沉淀下来了。1.2 功能模块设计的取舍逻辑功能拆解时我参考了闲鱼和钱币天堂的做法但做了不少减法。第一版功能清单列了二十多个模块最后砍到九个核心模块用户管理、藏品管理、求购信息、出售信息、系统内留言沟通、收藏夹、帖子社区、系统管理、消息通知。砍掉什么很关键。比如在线拍卖这个功能开发成本高不说还牵扯保证金、出价倒计时、防恶意竞拍对个人项目来说性价比太低。再比如在线支付接第三方支付平台需要企业资质个人开发者很难搞定。我把交易设计成“线上信息撮合、线下自行结算”——买卖双方在系统内沟通达成意向实际付款走私下渠道。这样做虽然不闭环但对收藏圈这种低频高客单价、重度依赖实物验货的交易场景反而是最务实的方案。技术层面Spring Boot 在这个项目里承担的是完整后端服务角色。选它而不是传统的SSM框架核心原因是自动装配和起步依赖能省掉大量配置文件。我记得以前用SSM写一个分页查询要配置SqlSessionFactory、MapperScannerConfigurer、分页插件光是XML就得写几十行Spring Boot 加一个pagehelper-spring-boot-starter就完事。这是实打实的效率提升对中小型项目尤其明显。1.3 技术选型从Spring Boot到周边生态的搭配思路我的技术栈是 Spring Boot 2.7.18 MyBatis-Plus MySQL 8.0 Redis Vue 2 Element UI。这套组合在当时是最稳的。Spring Boot 2.7.18 是2.x系列的收尾版本继承了2.x的稳定性又修了大量CVE漏洞比3.x更成熟。3.x 强制要求JDK 17我在开发机上装的是JDK 8长期维护的老项目也依赖JDK 8的运行时直接上3.x要连带升级一堆依赖风险不值当。前后端分离是我一开始就定的方案。我自己的体会是钱币展示对图片呈现要求高前端要经常调布局、做放大镜、做对比视图如果走Thymeleaf模板引擎前后端耦合太深改一次样式要重启后端服务。Vue 2 加 Element UI 做后台管理界面移动端用的是 Vant一套后端API两边复用省了不少事。这里提一句数据库选型。MySQL在读写性能和运维成本之间非常平衡。因为涉及到价格查询和版别检索我建了不少联合索引也验证过EXPLAIN的执行计划8.0的优化器表现比5.7更聪明。Redis用来做验证码存储、Token黑名单、热门帖子缓存。本来想把藏品浏览量也放Redis里异步落库后来觉得数据量没大到那个程度就只在登录态和验证码两处用到了缓存。2. 数据库设计与核心表结构2.1 核心表结构钱币属性怎么建模更合理钱币这个领域有个特点描述维度极其不统一。按品类分有古钱币、机制币、外币、纪念币按属性分有年代、年号、面值、材质、版别、品相评级如NGC/PCGS分数、尺寸、重量。如果用一张表强行统一所有属性字段会爆炸而且大量字段对特定品类是空的。我采用的方式是“主表加扩展表”。主字段放所有钱币都具备的属性名称、图片、品类、年代描述、材质、尺寸、重量、品相描述、售价、库存数量、所属用户、发布时间。特殊属性放进coin_extra_field表用KV结构存储键是属性名值是属性值。查询时先用主表过滤品类和价格区间再对扩展表做JSON解析。这种设计牺牲了一点查询效率但换来了极强的扩展性——以后新增“铸造厂家”这种属性不需要改表结构。另一种设计是直接用MySQL 8.0的JSON字段。coin_spec字段存一个JSON对象配合虚拟列做索引查询起来也很方便。我实际测试过在几千条数据量下两种方案性能差距可以忽略。但考虑到有些Java代码处理KV结构更直观我最终选了扩展表方案。2.2 命名规范与索引设计数据库表我统一用业务前缀加下划线命名方式比如sys_user、coin_info、deal_wanted、bbs_post、message_item。主键用bigint自增但对外接口不直接暴露ID因为容易被遍历爬取数据。我在ID生成策略上用了MyBatis-Plus的ASSIGN_ID生成雪花ID大数类型前端JavaScript处理时要注意精度丢失——这是经典的坑后面在问题排查里细说。索引设计上我发现几个高频查询路径需要重点关注藏品列表按品类和品相筛选用coin_info表的联合索引(category, grade_level)求购匹配按“用户状态”过滤索引(user_id, status)帖子按板块和时间排序索引(board_id, publish_time)收藏夹按用户查关联藏品在user_favorite表建(user_id, coin_id)唯一索引实测下来最要命的是帖子列表的深分页问题。数据量到几万条以后LIMIT 100000, 20这种写法会越查越慢。我后来改成基于ID的分页方式——客户端记住上一页最后一条帖子的ID查询时用WHERE id ? ORDER BY id DESC LIMIT 20响应时间从900毫秒降到40毫秒以内。这个优化是肉眼可见的。2.3 交易与交流链路的关系设计求购和出售我分成了两张表deal_wanted求购信息和deal_offer出售信息而不是合并成一张交易表。原因是两者的字段差异很大求购侧重“目标版式和期望价格区间”出售侧重“实际品相和实拍图片”。合并会导致大量空字段。沟通记录单独建了message_item表保存买卖双方的站内信。每条消息关联一个biz_id这个字段指向求购ID或出售ID再用biz_type区分场景。这样的设计让消息列表页可以同时展示“求购咨询”和“出售沟通”两类会话。页面上再按biz_id分组把同一个交易意图的往来消息聚合成一个会话卡片方便用户回溯上下文。收藏夹表字段最少但也最容易设计失误。一开始我只存了user_id和coin_id后来发现用户想知道“我什么时候收藏的”“收藏时价格多少”于是加了create_time和collect_price_snapshot。价格快照是个很实用的字段——藏品降价时用户可以对比收藏时和现在的价格。3. 核心功能实现与关键逻辑3.1 用户体系与Spring Security集成用户系统我用的是 Spring Security JWT 的方案。为什么不用传统Session因为前后端分离部署后前端静态资源放在Nginx上后端API跑在独立端口Session跨域要处理Cookie的同源策略麻烦得很。JWT把用户信息编码进Token前端存在localStorage每次请求在Authorization头带过来后端无状态校验天然免疫跨域问题。Spring Security的配置是这类项目的重头戏。我自定义了一个JwtAuthenticationFilter继承OncePerRequestFilter在请求进入Controller之前先解析JWT。解析成功就把UserId塞进SecurityContextHolder后续业务代码直接从上下文取当前用户。未带Token或Token过期的请求返回统一的401 JSON结构而不是重定向到登录页。有一点必须提醒Spring Security 6/5.7 之后的写法变化很大。我项目里用的是5.7之前的写法WebSecurityConfigurerAdapter还能用。如果你照着新版本的教程看会发现authorizeRequests()已经被废弃了要换成authorizeHttpRequests()。网上教程版本混乱代码跑不起来八成是这里出了问题。我是锁死2.7.18版本的依赖才避免了这类兼容性折磨。登录接口我做了一个细节连续五次密码错误锁定账号十五分钟。代码逻辑很简单在Redis里存一个login_fail_count:{username}每次校验失败加一超过阈值直接拒绝登录。这个功能看起来不起眼但对收藏类平台特别有意义——因为藏品信息有经济价值账号被盗意味着藏品数据泄露甚至被恶意篡改多一层防护总归是好事。3.2 藏品发布与图片处理细节藏品发布是整个系统使用频率最高的入口。表单字段包括基础信息、属性扩展、图片上传、价格设定。图片上传我用了本地存储方案没有上云。理由很现实个人项目没有对象存储的免费额度又不想为了一个毕设级别项目去申请云资源本地存储完全够用。上传接口接收MultipartFile校验文件类型和后缀一致性。这里有个经验不能只校验Content-Type因为请求头可以伪造。我同时校验图片魔数——读取文件前几个字节判断是不是真正的JPEG或PNG。JPEG的魔数以FFD8FF开头PNG以89504E47开头。这种校验能挡住绝大多数伪造图片的恶意上传。另外设置了单张图片5MB的上限超过直接拒绝——压缩处理链路没必要为超大原图白白消耗CPU。存储路径按日期分目录格式是/upload/2025/06/17/文件名用UUID重命名原始文件名一律丢弃。这里涉及一个安全细节如果保留原始文件名攻击者可能上传带路径穿越字符的文件名比如../../etc/passwd。使用UUID彻底杜绝了这个问题。图片的访问URL存数据库前端直接当静态资源引用。3.3 求购匹配与价格参考的逻辑设计求购匹配是系统的亮点功能。用户发布一条求购信息系统自动在已上架藏品里搜索匹配项并把匹配结果推送给发布人。匹配逻辑不是简单等值匹配而是规则匹配品类必须一致版式描述做模糊匹配价格区间有交集求购期望价的上限大于等于出售报价的下限品相等级不低于求购要求。这里有个细节版式描述模糊匹配到底用什么方案。我有三个选择MySQL的LIKE %关键字%、全文索引、以及搜索引擎。实测下来数据量在十万条以内时MySQL全文索引的MATCH...AGAINST性能已经不错但中文分词效果很一般。我最终用了一个折中方案——发布求购信息时让用户选几个预定义标签如“乾隆通宝”“雍正通宝”“宣统元宝”匹配时直接精确查标签字段再辅以关键字LIKE查询。这种方式实现简单、匹配准确率也高比上Lucene或Elasticsearch性价比高太多。价格参考模块我做了另一个设计基于历史成交记录计算同品类、同品相区间的平均价格和价格分布。每次浏览藏品详情页时接口返回“近三个月同类藏品成交均价”和“当前价高于/低于均价的比例”。这个功能极大提升了用户对藏品报价的信心。数据来源是交易完成后记录的成交价买家确认收到货并评价后成交记录才正式写入价格库。3.4 社区帖子与消息通知的实时性处理社区模块本质是一个简化版论坛。板块包括“新手入门”“真假鉴定”“版别讨论”“市场行情”。发帖时支持插入藏品引用——在编辑器中通过藏品搜索框找到某枚钱币插入后帖子下方会渲染出一个藏品卡片点击能跳转到藏品详情页。这个设计把社区讨论和藏品数据紧密串联讨论的真实感强了很多。消息通知我用了WebSocket加Redis的轻量组合。用户在帖子下回复、交易对象发送新消息、求购匹配到目标藏品系统都实时推送通知。连接的建立采用Token鉴权——WebSocket连接建立时从URL参数取Token校验通过后绑定到用户ID。服务端用了一个ConcurrentHashMap维护在线Session集合按用户ID存放在线状态。单机部署下这种方案完全够用如果以后要横向扩展可以改用Redis的Pub/Sub做消息广播。通知未读数我存在Redis里每次推送时给用户的计数器加一前端轮询拉到数字显示在导航栏的小红点上。已读清空采用“标记到某时间点为止全部已读”的策略而不是逐条更新状态这样数据库的压力小很多。3.5 MyBatis-Plus分页插件与复杂查询联动分页查询是这类信息管理系统的地基。我用的是MyBatis-Plus内置的分页插件配置方法很简单Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }配置好之后业务代码里只需要构造Page对象传给Mapper方法PageCoinInfoVO page new Page(current, size); LambdaQueryWrapperCoinInfo wrapper new LambdaQueryWrapper(); wrapper.eq(CoinInfo::getUserId, userId) .orderByDesc(CoinInfo::getPublishTime); coinInfoMapper.selectPage(page, wrapper);底层自动拼接LIMIT语句返回结果集中包含总数、总页数这些分页元数据前端分页组件直接用。需要留神的是多表联查时分页插件对JOIN查询的COUNT语句生成偶尔会有问题比如COUNT(DISTINCT ...)不被支持。我的经验是复杂统计类查询不要走分页插件的自动COUNT自己写一个独立的COUNT查询或者使用SqlParser注解跳过优化。这里再提一个实际开发中的优化点。藏品列表页的查询条件组合非常多品类、材质、价格区间、品相、年代、排序方式。如果每个组合都建索引索引数量会失控。我固定了几条“查询模板”模板的WHERE条件是固定的只变化参数值这样索引命中率最高。4. 安全防护与性能优化实操4.1 全局XSS过滤与文件上传安全标题里提到了“全局过滤器处理上传PDF文件时的XSS攻击”这个场景我在开发后期确实遇到了。收藏圈有个习惯藏友会把收藏证书做成PDF传到系统里展示。PDF文件本身不是HTML不需要做HTML转义但PDF元数据里可以嵌入恶意链接或JavaScript。系统里有一个PDF预览功能注入的恶意脚本可能在预览页面执行。我的处理方式是写一个全局过滤器专门拦截上传操作。过滤器的职责分两层第一层在HTTP请求入口使用XssFilter对表单参数做HTML转义和白名单校验第二层对上传的PDF文件做二进制扫描——解析PDF中的URI链接过滤掉javascript:协议和包含事件属性的片段。这个方案算不上绝对安全但对个人项目来说已经能把攻击面压到很低的水平。XSS过滤的核心是转义和编码校验。我自定义了一个继承HttpServletRequestWrapper的包装类重写getParameter、getHeader等方法对敏感字符做替换换成lt;换成gt;换成quot;。同时保留白名单标签像帖子正文需要支持加粗和链接如果一刀切全部转义排版功能就废了需要做分场景处理。4.2 接口幂等性与并发扣减问题交易意向匹配后用户可能对同一件藏品发起购买申请。如果不加控制两个人同时下单会导致一件藏品被重复承诺。这个问题的根源是库存扣减的并发安全性。方案有几种悲观锁SELECT FOR UPDATE最可靠但并发性能差乐观锁用版本号字段Update时校验版本号冲突则重试Redis分布式锁适合集群部署场景。我选了乐观锁简单且性能好。藏品表加一个version字段扣减时执行UPDATE coin_info SET stock stock - 1, version version 1 WHERE id ? AND version ? AND stock 0受影响行数为0说明并发冲突业务层返回“藏品已被抢购”的提示。配合事务注解Transactional能保证扣减操作的原子性。这种控制在毕业设计级别的答辩里是个很好的加分点因为很多人的项目根本没考虑并发问题。4.3 Redis缓存与热点数据降级策略藏品详情的访问热度分布极不平均热门品种比如“袁大头三年”的详情页占了总浏览量的三成。我加了Redis缓存首次查询从MySQL读取写入缓存过期时间设为一小时后续请求直接由缓存返回响应时间基本在10毫秒以内。这里有个一致性的坑用户修改了藏品信息后缓存里还是旧数据。解决思路是“修改即删缓存”——更新数据库后主动删除对应Redis键下次查询重建缓存。这样牺牲了一点缓存命中率但保证数据最终一致。删除和更新之间理论上还是有极小概率的并发脏读但对这个业务场景完全可以接受。降级策略方面我做了两层防御。第一层是缓存空值——查询不到的藏品ID也缓存一个null标记防止恶意遍历不存在的ID反复击穿数据库。第二层是热点KEY检测——如果某个藏品的访问量在短时间内暴增可能被推荐到首页主动延长该KEY的过期时间避免缓存雪崩。5. 开发部署阶段的坑与排查实录5.1 Spring Boot版本选择与依赖冲突版本问题是Spring Boot新手最容易栽的跟头。网上教程的时间跨度从2018年到现在依赖版本差异巨大——有的教程用spring-boot-starter-parent2.3.4有的用3.1.5。我项目里遇到的一个典型冲突是MyBatis-Plus 3.5.3 与 Spring Boot 2.4 之后的配置方式不兼容导致分页插件不生效。排查这类问题没有捷径核心方法是看依赖树。在IDEA的Maven面板里选择“Show Dependencies”能看到每个依赖的实际版本和引入路径。冲突时重点检查spring-boot-starter-web传递的spring-webmvc版本以及MyBatis-Plus对mybatis-spring的版本要求。我最终把Spring Boot锁定在2.7.18、MyBatis-Plus锁定在3.5.3.1两个版本的组合经过大量项目验证稳定性是有保障的。另一个经常被坑的是Jackson序列化和LocalDateTime的兼容问题。JDK 8的LocalDateTime默认序列化格式是一串数组前端根本看不懂。我配了全局的JacksonCustomizer统一输出yyyy-MM-dd HH:mm:ss格式。这类问题特别典型——后端测试一切正常前端一对接就发现时间格式不对定位起来又费时又恼火。5.2 前端精度丢失与ID类型问题这个问题在5.2节提过但值得展开讲。MyBatis-Plus的雪花ID是Long类型长度19位数字而JavaScript的Number类型安全整数范围只有53位2^53 - 1大约是9千万亿19位数字早就超了。前端拿到后后几位会被截断成0导致点击“编辑”时请求了错误的ID。这个坑一旦遇到表现非常隐蔽——列表加载正常、详情也正常就是编辑保存时提示“数据不存在或已删除”。因为页面跳转时ID已经悄悄变了。解决方案是我在后端接口里统一把ID转成字符串返回前端前端交互全程用字符串ID提交时后端再用Long.parseLong()转回来。这个方法朴实但有效强烈建议所有用雪花ID的项目都这么做。5.3 热更新调试与Quick Debug技巧开发过程中频繁重启Spring Boot应用确实影响效率。我配置了spring-boot-devtools依赖默认开启自动重启。改完Java代码后IDEA里按CtrlF9编译应用会检测到类文件变化自动重启。实测起来DevTools的自动重启比我手动重启快得多——它只重启应用上下文不像IDEA重启JVM那么重。另一个调试技巧是使用spring-boot-maven-plugin的远程调试功能。启动命令加上mvn spring-boot:run -Dspring-boot.run.jvmArguments-Xdebug -Xrunjdwp:transportdt_socket,servery,suspendn,address5005IDEA里配置Remote JVM Debug连接localhost:5005就能在本地代码里打断点了。提这个是因为部署到服务器后有些Bug只在远程环境复现本地正常。有远程调试通道至少能在生产前多一层兜底。5.4 从零反编译线上Jar排查问题开发中遇到一个比较经典的场景线上部署的Jar包和本地代码对不上怎么通过反编译定位线上实际运行的代码逻辑。我用的工具是 JD-GUI 加 Luyten先用javap -c看字节码指令不够直观直接用JD-GUI反编译成Java源码。反编译后的代码虽然和原始代码有出入比如泛型信息丢失、Lambda表达式还原成匿名内部类但业务逻辑和字符串常量基本能看出来。排查流程是先解压Jar包拿到BOOT-INF/classes目录确认存在被修改的ClassName.class文件再用JD-GUI打开对照。有一次线上页面加载空白本地代码却无法复现最后发现发布时打包的Jar是旧版本。通过反编译对比方法签名和日志字符串快速确认了线上代码和SVN上某个历史版本一致从而锁定问题出在构建部署环节而不是业务逻辑。这种能力虽然不常用但真正遇到时能救命。5.5 问题速查表我遇到过的典型Bug整理一张我实际踩坑的问题速查表按出现频率从高到低排列问题现象可能原因解决思路前端提交表单后出现400错误后端日期字段和前端格式不一致或JSON参数名不匹配用JsonFormat统一日期格式前端字段名与DTO属性严格对应图片上传提示“文件不是有效图片”通过魔数校验失败伪造后缀的真实文件用文件头字节判别真实类型不要信任后缀和Content-Type登录后部分接口仍然401Spring Security的放行路径配置遗漏检查permitAll()路径是否覆盖登录、注册、刷新Token、验证码等接口商品修改后列表还是旧数据Redis缓存未删除缓存与数据库不一致在更新业务代码后主动删除对应Redis键避免缓存穿透MyBatis-Plus查到预期外的结果条件构造器NULL值未处理导致SQL条件被忽略使用StringUtils.hasText()判断空值避免NULL条件拼错帖子列表翻到后面越来越慢深分页问题LIMIT offset, size在大数据量下性能退化改用游标分页或记录上一页最大值ID避免大偏移量查询定时任务执行了多次集群部署时多个实例同时触发引入分布式锁如ShedLock或部署时只允许单实例执行任务这张表不是全部但覆盖了80%的开发中高频问题。遇到类似情况时可以优先对照排查省去很多无效调试时间。6. 项目扩展思路与部署建议6.1 从单体到分布式的演进路线目前的单体架构在用户量到一定程度后会出现瓶颈。一个自然的演进路线是把文件上传从Tomcat本地目录抽离到独立的对象存储服务把消息推送单独拆出一个推送服务把藏品检索引擎换掉。我在热词里看到“minio加入到springboot”这个搜索。如果自己部署MinIO做图片存储Spring Boot整合方式其实很标准引入minioJava SDK配置MinioClient连接服务的地址、AccessKey、SecretKey再用PresignedPutObjectArgs生成预签名上传URL。前端拿到URL后直接把文件上传到MinIO后端只负责存URL路径压力瞬间小很多。迁移时把旧图片用mc mirror命令同步过去数据库里的URL前缀批量替换整个迁移过程可以滚动完成。消息推送如果数据量上升到需要多实例水平WebSocket的Session列表就不能继续用本地内存了。改造方案是把Session注册信息扔进Redis推送时根据标识查出目标实例再通过Redis的Pub/Sub做跨实例转发。这个改动不算小但结构上是清晰的。6.2 部署环境Docker打包与Nginx配置要点部署用Docker确实省心。我的Dockerfile设计是基础镜像用eclipse-temurin:8-jre把Maven打包好的Jar复制进去用EXPOSE 8080声明端口然后java -jar启动。重点是要把外部配置分离出来——数据库连接、Redis地址、文件上传路径这些写在application-prod.yml里构建时用-r参数指定Spring Profile。打包命令mvn clean package -DskipTests docker build -t coin-collector:1.0.0 . docker run -d -p 8080:8080 \ -v /opt/coin/upload:/app/upload \ -e SPRING_PROFILES_ACTIVEprod \ --restartalways \ coin-collector:1.0.0前端的Nginx配置核心点是静态资源缓存和API反向代理。静态资源加expires 7dAPI请求代理到后端服务地址并配置client_max_body_size 10m放宽上传限制。前端路由用history模式时Nginx要配置try_files $uri $uri/ /index.html否则刷新页面会404。最后说个容易被忽视的点为了不让服务器因系统重启后服务丢失所有中间件MySQL、Redis、项目应用都通过systemd或Docker容器的restart: always策略守护。我在项目上线后的第二周遇到过一次服务器自动重启后服务全部中断的情况因为当时服务是用nohup起在后台的重启后残留进程全没了。加上自动重启策略之后这类运维问题就再也没出现过。6.3 我对这个项目的一点复盘心得整个项目从设计到上线大概花了六周业余时间。回头复盘有几点想留给做类似项目的朋友。第一先想清楚核心闭环再动手写代码。我这个项目最开始就是吃了“功能越加越多”的亏。第一版草图里包含了拍卖、在线支付、直播带货后来调研一圈发现收藏圈用户根本不信赖线上的“快速交易”他们更在意的是“能不能找到对的人聊对的话”。砍掉那些花哨功能后系统反而更好用了。第二数据库设计要预留扩展性但不要过度设计。我用扩展表存储藏品特殊属性这个设计在开发初期看起来很冗余但真到了用户反馈“能不能记录铸造厂”“能不能记录边齿类型”时好处就体现出来了——不用改表只要在前端表单加两个字段就行。第三Spring Boot作为核心框架它的生态完整度让个人开发者可以把精力集中在业务逻辑上。配置简化、自动装配、起步依赖这些特性对中小型项目来说是实打实的生产力。选对版本、理解自动装配原理、学会看依赖树排查问题这三件事比多学十个新技术点有用得多。这个项目后续我打算继续完善。短期计划是加入Excel导入导出功能让藏友能把自己的藏品清单批量导入再做一个基于收藏偏好的推荐算法根据用户浏览和收藏行为推荐可能感兴趣的藏品。长期来看如果用户量和内容量增长上来会考虑把检索部分换掉增加更专业的中文分词能力。做这类领域性项目最大的乐趣就在于你永远知道下一个版本要怎么做得更好。