技术架构
四端同源是怎么做到的
只有一个答案:判据全在服务端,四个端都只是它的显示器。下面是这句话落到工程上的样子。
技术选型
- 服务端
- Spring Boot 4 · Spring Data JPA · Redis · MySQL 8 · JDK 25
- 网页与管理台
- Next.js 16 App Router · shadcn/ui · Tailwind CSS 4 · TanStack Query
- 小程序
- 微信原生框架(非 uni-app / Taro)· TypeScript 逻辑层
- 存储
- MySQL 8 · Redis · 对象存储(客户端直传)
按业务域纵向切分
不是按 controller / service / repository 分层——那样一个目录会装近百个文件,改一处业务要同时打开四个目录。现在改树洞只需要打开一个目录。
- ugc61
动态、话题、评论、点赞收藏分享、搜索
- user56
资料、关注、隐私、签到、举报、角色
- activity34
活动、场次、报名、活动评论
- message34
系统消息、私聊额度、WebSocket、订阅消息
- form33
问卷表单、题目、CP 匹配
- course27
课程、教师、教室、课程评价
- showcase27
广告位、表情、诗词、轮播、能力标签
- campus21
学校、学院、校区、地点
- student20
学生身份认证(邮箱 / 照片 / 答题)
- treehole19
匿名树洞
- score18
微校分、充值、兑换、支付
- idle17
闲置交易
- organization15
社团组织与身份
- tracking13
打开事件与行为埋点
- wechat9
公众号消息接入与回调
- attachment6
附件(多域共用,独立成域)
右上角的数字是该域的文件数,条形按最大的那个域等比缩放。跨域共用的东西只放 shared,且必须真的跨域。
浏览器里的那两个端
「学生在浏览器里也能用」「校级管理员自己就能用起来」——这两句光靠文字说都没有说服力。
网页版
与小程序共用同一套后端、同一批账号与内容。

新鲜事:桌面三栏,右上角是浏览范围 
动态详情与楼中楼评论 
私信:WebSocket 实时,掉线有轮询兜底 
公众号扫码登录:与小程序归一成同一个账号
运营管理台
审核、运营配置、题库、配对、订单都在这里。

数据概览:校级管理员看到的是本校的数字 
学生认证审核:AI 初筛给的理由摆在显眼处,人做最后一判(证件照已打码) 
内容审核:删除是软删,勾「含已删除」可回看并一键恢复 
CP 配对工作台:待匹配与已配对一目了然,人来拍板
接口契约
这五条是写死的。四个端各自实现一次客户端,靠的就是它们不变。
01HTTP 状态码表意
成功就是 2xx 且响应体直接是数据,没有外层包裹;失败是对应的状态码,响应体固定为
{error, message}。分支判断只看error常量,永远不比对message——文案会改,还会随语言变。02401 分三态
TOKEN_EXPIRED触发单飞刷新后重放原请求;TOKEN_MISSING与TOKEN_INVALID直接回登录态。三态不分开的话,一次过期就会把用户踢回登录页。03游标分页
{start, limit}换{elements, total, hasMore, nextStart}。start是不透明游标,续页只能用上一页给的nextStart——自己算start + limit会串页。04空字段被省略
服务端 JSON 不下发 null。所以读任何可空字段之前先回答一句:这个键不存在的时候算什么?把它写进代码注释里,比记在脑子里可靠。
05错误文案有语言
服务端按
Accept-Language返回对应语言的错误文案。所以客户端固定发自己界面的语言,而不是透传浏览器的——否则中文界面里会冒出英文提示。
认证与令牌
学生侧
自建不透明令牌存在 Redis 里。访问令牌 4 小时且每次请求滑动续期,刷新令牌 7 天且一次性使用。同一用户重复登录会吊销之前的令牌。
两个登录入口
小程序走微信 code 登录;网页走公众号扫码——开会话拿带参二维码,用户扫码顺带关注公众号,浏览器轮询拿令牌。两条路凭 unionId 归一成同一个账号,先扫码和先小程序两个方向都有集成测试钉着。
管理侧
与学生是两套完全独立的账号体系,令牌互不通用。访问令牌是 30 分钟的 JWT,服务端登记 jti 所以能即时吊销;刷新令牌不透明、7 天、用完即焚——因此客户端的单飞刷新是必需品而不是优化。
四个端各自的取舍
网页必须走服务端代理
服务端只给管理端接口注册了 CORS,学生侧接口一个跨源头都不发(小程序不是浏览器环境,给学生侧开 CORS 只会扩大攻击面)。所以浏览器直连一律被同源策略拦下,请求统一经过 Next 的路由处理器转发。
WebSocket 不经代理
路由处理器不转发协议升级,所以 WebSocket 直连服务端。浏览器又不能给 WS 请求自定义头,令牌只能走查询参数——服务端的令牌过滤器支持这一路。没配 WS 地址时整段跳过,列表本来就有轮询兜底。
图片直传对象存储
服务端只签发一份有时效的表单凭据,文件从客户端直接传到存储,不经过应用服务器。省带宽,也免得一个大图把连接池占满。
小程序按分包切
主包只放高频页面,课程、CP、摄影、树洞、地图、活动、闲置、积分各自成包——首次打开只下载主包。
部署需要准备什么
系统本身自部署。真正的前置条件是这几个外部账号,其中两个的申请周期比写代码长。
一台服务器
跑应用、MySQL 8、Redis。JDK 25。
对象存储
存图片与视频,需要配好跨域规则与访问域名。
小程序账号
学生端入口。
认证服务号
网页扫码登录需要带参二维码权限;与小程序绑同一个开放平台账号才能归一账号。
商户号
只有要做充值才需要。
发信邮箱
邮箱认证的验证码从这里发出。
所有凭据从环境变量注入,缺一个应用在启动期就会失败并指出缺哪一项——不会带着未解析的占位符跑起来。