做文化系统的项目,最怕的不是需求复杂,而是大家嘴里说的“信息管理”根本不是同一件事。前阵子刚把 weixin130综合文化信息管理系统 交付上线,这个代号是我们内部立项时随手写的,自己都没想到一用就是大半年。这个系统本身不算重,但要把资讯、场馆预约、活动报名、非遗档案、数字资源全部揉进一个平台,还要和微信生态打通,坑是真的不少。这篇就以它为例拆开说说,给正准备接手类似项目的朋友当个参考。
先交代一下背景:客户是一家市级文化场馆,同时管着几个分馆和一批民间文艺团队。以前的信息散在各个地方,活动表在Excel里,场地预约靠微信聊天,非遗资料放在移动硬盘,公众号文章是宣传同事单独维护的。领导想看数据,只能让下面的人临时拉表。所以提需求时反复强调“综合”两个字。我当时理解,综合不是做一个巨型网站,而是用一套账号、一套数据、一套发布流程,把群众能看到的文化服务都管起来。
1. 项目背景与整体设计思路
1.1 文化业务到底“痛”在哪
文化信息化听起来温柔,实际上很琐碎。资讯发布审核、场馆预约占座、活动报名签到、非遗档案数字化、数字资源挂载,每一块单独拿出来都能做个系统,但群众和工作人员都希望在一处搞定。
群众侧的真实场景是:在公众号看到周末有个非遗体验活动,点进去想报名,结果发现报名表在不同链接里,或者干脆要打电话问前台。场馆侧更麻烦:多功能厅、排练室、展厅都有档期,有人想预约合唱排练,有人想借展厅办书画展,前台Excel记录经常撞车。而文化馆、图书馆、美术馆只要有一方数据不打通,群众就会被推来推去。
weixin130 要解决的,就是把这些分散的服务入口统一到微信里。群众不用装额外App,关注公众号、用H5页面就能浏览、预约、报名;后台管理人员在一个管理端里完成内容发布、审核和报表统计。这个定位决定了整个系统不能做成大而全的“智慧城市平台”,而是要轻重适中、快速落地。
1.2 方案选型:为什么先做公众号H5而不是小程序
早期讨论时,有人提议直接上小程序。我按住了这个冲动。小程序开发、审核、版本更新都比H5重,而文化类业务最大的特点是信息实时性要求不高、功能相对固定,没必要一上来就把摊子铺大。
我们最终选的是“管理后台 + 微信公众号H5”的组合。管理后台用 Java Spring Boot 提供接口,前端用 Vue 做单页管理;H5端用uni-app开发,服务端能渲染基本内容,复杂交互走微信网页授权。为什么这么选?因为用户本来就在微信里,打开公众号菜单就能进 H5,没有任何安装成本。H5 页面可以复用一套接口,将来要升级小程序,也只是多包一层壳的事,改动成本很小。
这里有一个关键判断:不要把“综合”理解成“在一个系统里装下所有功能”,而是要让多个端共用同一套数据和服务。我们设计的接口按资源维度划分,内容、场馆、活动、非遗、数字资源各成模块,权限上做统一认证,入口则有后台和C端两套。这样以后加APP、加小程序、加政务接口,都不用推倒重来。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心功能模块与数据模型设计
2.1 业务模块拆分与权限边界
模块拆分的唯一标准是“谁在用、用来干什么”。后台用户有三类:运营编辑、场馆管理员、系统管理员。编辑只负责内容类模块的增删改,场馆管理员只能维护各自场馆的档期和预约,系统管理员负责配置字典、角色和用户。没有把权限做得很复杂,因为文化单位人员流动不大,角色太细反而没人记得设置。
前台模块按群众的动作拆:
- 文化资讯:公告、活动预告、展览介绍,支持分类、置顶、定时发布。
- 场馆预约:按场馆、日期、时段生成可预约资源,群众选择时段后提交预约。
- 活动报名:发布的活动可以配置报名表单、名额上限、报名截止时间。
- 非遗档案:为项目和传承人建立档案,附带图文、音视频资料。
- 数字资源:电子书、纪录片、音频等资源的上传、在线预览和下载。
每个模块都有对应的列表页、详情页和管理表单,服务端通过统一的REST接口对外输出。这里要特别提醒一句:非遗传档案和普通资讯不一样,它需要长期维护,字段多、资料形式杂,最好从一开始就单独建表,别想着塞进“文章”表里加个类型字段,后面统计和排展会很痛苦。
2.2 数据库设计中的关键字段
数据库设计直接决定后面能不能跑顺。贴一张核心表的设计思路,这是项目中最值得反复打磨的部分:
| 模块 | 核心表 | 关键字段 | 设计理由 |
|---|---|---|---|
| 内容资讯 | content_article | category_id、is_top、status、publish_time、auditor_id | 支持分类、置顶、审核和定时发布 |
| 场馆预约 | venue_slot | venue_id、date、time_slot、max_count、booked_count | 按资源库存做并发控制,避免超卖 |
| 活动报名 | activity_order | activity_id、user_id、form_data、sign_status | 存JSON表单数据,兼容不同活动字段 |
| 非遗档案 | heritage_item | item_type、name、inheritor_id、cover_url、period | 区分项目与传承人,方便按主题检索 |
| 数字资源 | resource_file | title、file_url、cover_url、download_count、size | 文件和业务对象分离,便于统一处理水印 |
| 微信用户 | wechat_user | openid、unionid、nickname、phone、bind_status | 统一身份,H5、公众号、后续小程序共用 |
有几个字段是后来补的教训。比如 content_article 里我加了 source_type,用来区分是后台录入还是从旧系统迁移的,否则上线后数据出问题根本不知道从哪查起。venue_slot 里加了 lock_version 或原子更新条件,预约并发是文化场馆最容易被低估的问题。activity_order 里加了 unique_key,字段内容是 activity_id + user_id 拼出来的字符串,加唯一索引,防止同一个人反复提交报名。
2.3 微信用户体系与登录逻辑
微信生态下的系统,用户体系是最绕不开的一环。weixin130 用户登录没有做传统用户名密码,而是走微信公众号网页授权。用户第一次点菜单进入H5,后端拿到授权 code,换取 openid,再查 wechat_user 表。如果不存在就自动创建用户,同时引导绑定手机号;如果存在就直接返回登录态。
这里有一个容易忽略的细节:同一用户在不同公众号、不同小程序下的 openid 不一样,但如果这些应用属于同一开放平台账号,unionid 是一样的。所以表里一定要预留 unionid 字段。我们一开始只存 openid,后来想给App也开通入口,才发现没有 unionid 的话两边身份对不上。这个字段成本很低,但后面想补就很麻烦。
3. 关键环节的实现与部署要点
3.1 公众号服务端配置与Token校验
微信接入是所有H5功能的地基。在公众号后台配置服务器地址时,回调URL必须是 HTTPS 域名,且要先通过 token 校验。校验流程是微信服务器带 signature、timestamp、nonce、echostr 四个参数请求你的接口,你需要把 token、timestamp、nonce 按字典序排序后拼成字符串做 SHA1 哈希,和 signature 一致就把 echostr 原样返回。
这段逻辑用伪代码写,其实和语言无关:
python复制import hashlib
def check_signature(token, timestamp, nonce, signature):
tmp_list = sorted([token, timestamp, nonce])
tmp_str = "".join(tmp_list)
sha = hashlib.sha1(tmp_str.encode("utf-8")).hexdigest()
return sha == signature
我当时第一次部署时这个接口老报校验失败,排查半天发现是 security 配置把 /wechat/callback 这个路径拦截了,请求还没进业务代码就被返回 401。所以接入微信公众号回调,第一件事就是把回调地址放进白名单。另外,公众号后台还需要配置“网页授权域名”和“JS接口安全域名”,否则H5里调微信拍照、扫码能力会被拒绝。这几个配置是分散在不同菜单里的,新人很容易漏。
3.2 内容审核与素材发布流程
文化类内容不出问题还好,一出就是大事,所以审核流程不能省。我们的实现是“草稿—提交—审核—发布—定时发布”五态流转。编辑写完内容后点提交,状态变成待审核,审核人员能在后台看到全文预览和涉及素材列表,通过后状态变成已发布。如果设置了定时发布,服务端每秒扫一次待发布表,到时间就把状态改成发布,并把文章同步到微信素材库。
素材库这块有个细节:微信素材的 media_id 有效,但图文素材一旦做了修改,media_id 会变。所以不要在数据库里长期依赖 media_id,而是自己维护一个文件地址和媒体ID的映射表,每次发布前重新检查素材是否过期。图片我们统一做了压缩,长边控制在1080像素,既能保证清晰度,又能让H5页面加载快很多。视频不直接塞进正文,采用封面图+点击跳转的方式,减少首屏流量。
3.3 预约和报名的并发防超卖
文化场馆的活动报名,很多时候并没有真正高并发,但节假日抢票时还是会出现多人同时预约同一时段的情况。我们的办法很简单,不依赖复杂中间件,直接在数据库层做原子更新:
sql复制UPDATE venue_slot
SET booked_count = booked_count + 1
WHERE id = #{slotId}
AND booked_count < max_count;
执行这条 SQL 后,如果影响行数为 0,说明这个时段已经满了,直接告诉用户“已约满”;如果影响行数为 1,才继续插入预约记录。这个做法能避免“先查后写”带来的超卖,也省掉了引入Redis的运维成本。当然,如果单场活动真的有大几万人抢,还是建议把库存放到 Redis 里面用 DECR 操作。文化单位一般到不了这个量级,数据库锁足够了。
预约确认之后,我们还会给用户生成一个签到码,活动当天现场扫一扫,就能把订单状态从“已预约”改成“已签到”。这个功能上线后特别受欢迎,因为以前工作人员统计到场率要靠纸笔,现在后台实时能看到人数,还能给没到场的人发提醒。签到码用订单ID做个不可逆混淆,二维码里不放任何用户隐私,只放一段随机字符串。
4. 常见问题与排查技巧实录
4.1 高频故障速查表
项目上线后遇到的大部分问题,其实都集中在配置和数据上,把常见情况整理成一张表,能省很多抢救时间:
| 现象 | 常见原因 | 处理办法 |
|---|---|---|
| 公众号回调接口一直校验失败 | 路径被拦截、服务器时间不准、token不一致 | 先看服务器日志,确认请求有没有进业务方法,再比对加密串 |
| H5页面在微信里打不开 | 网页授权域名没有配置,或域名不是HTTPS | 检查公众号后台-设置-公众号设置-网页授权域名 |
| 图片上传后显示模糊 | 原图过大被前端压缩过度 | 统一压缩长边1080,质量75%,兼顾清晰度和流量 |
| 同一用户重复报名成功 | 报名表里没有唯一约束 | 给 activity_id+user_id 加唯一索引,并捕获插入异常 |
| 预约人数对不上 | 前端缓存了旧库存数据 | 预约接口返回当前剩余名额,让页面用服务端数据刷新 |
| 定时发布没有生效 | 服务器系统时间时区不对 | 统一使用 Asia/Shanghai 时区,数据库和代码都对齐 |
4.1 implies 4.2. The table is actually a subsection? Need number it 4.1; not "4.1" in table.
4.2 历史数据迁移与清洗
旧系统或 Excel 里的数据,迁移起来比想象中烦。光是整理活动时间字段就发现了三种格式:“2019.5.1”“2019年5月1日”“5月1日”。如果不先清洗,导入数据库后列表页按时间排序全是乱的。
我的建议是不要拿原始表直接导,先做一次清洗脚本,把所有日期统一成标准格式,分类字段映射成系统字典值,联系人字段如果是空就填“暂无”。清洗完的数据先导入临时表,后台里做一个“数据预览”页面,人工确认没问题后再切到正式表。weixin130 上线时迁移了两万多条历史数据,靠这个预览功能,业务人员自己就完成了核对,几乎没让我返工。
4.3 性能优化和备份策略
文化信息系统的并发不高,但图片和视频资源多,容易把磁盘和带宽吃满。静态资源全部走CDN,图片和视频直传OSS,数据库只存URL。H5列表页接口默认只返回基础字段,详情内容用单独的详情接口,这样列表打开速度能控制在1秒以内。
备份我做了两级:每天一次全量备份数据库,每六小时一次增量备份;OSS里的素材本身有版本回滚能力,所以不重复备份。特别提醒,不要只备份数据库不备份配置文件,公众号 AppSecret、数据库连接串这些配置如果丢了,恢复起来比数据丢失还糟。我把敏感配置放在环境变量里,同时保留一份加密的本地备份。
5. 实操体会与继续扩展的方向
做完整套系统,我最想提醒同行的一句话是:代码和框架都不是难点,真正的难点是让业务数据有“统一身份”。以前各个口子各管一摊,群众预约要登记好几次,开头说的“从每天十几个电话到每周看一张报表”,靠的就是把场馆、活动、人员全挂到同一套ID上。weixin130 这个项目最大的价值,不是系统本身有多先进,而是把所有文化资源的入口收拢了。
如果现在让我重新做一次,我会在一开始就把“数据字典”设计得更细。比如场馆类型、活动形式、受众分类,这些看起来像枚举的小东西,前期多定义几个,后面做统计和推荐会非常省力。另一个值得扩展的方向是活动签到和积分体系,签到数据沉淀下来之后,可以继续做文化志愿服务时长统计、活动偏好分析。系统上线不是结束,数据开始积累才是项目的真正开始。
