跳到正文
微校 WeSmile

技术架构

四端同源是怎么做到的

只有一个答案:判据全在服务端,四个端都只是它的显示器。下面是这句话落到工程上的样子。

技术选型

服务端
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 实时,掉线有轮询兜底
    私信:WebSocket 实时,掉线有轮询兜底
  • 公众号扫码登录:与小程序归一成同一个账号
    公众号扫码登录:与小程序归一成同一个账号

运营管理台

审核、运营配置、题库、配对、订单都在这里。

  • 数据概览:校级管理员看到的是本校的数字
    数据概览:校级管理员看到的是本校的数字
  • 学生认证审核:AI 初筛给的理由摆在显眼处,人做最后一判(证件照已打码)
    学生认证审核:AI 初筛给的理由摆在显眼处,人做最后一判(证件照已打码)
  • 内容审核:删除是软删,勾「含已删除」可回看并一键恢复
    内容审核:删除是软删,勾「含已删除」可回看并一键恢复
  • CP 配对工作台:待匹配与已配对一目了然,人来拍板
    CP 配对工作台:待匹配与已配对一目了然,人来拍板

接口契约

这五条是写死的。四个端各自实现一次客户端,靠的就是它们不变。

  1. 01HTTP 状态码表意

    成功就是 2xx 且响应体直接是数据,没有外层包裹;失败是对应的状态码,响应体固定为 {error, message}。分支判断只看 error 常量,永远不比对 message——文案会改,还会随语言变。

  2. 02401 分三态

    TOKEN_EXPIRED 触发单飞刷新后重放原请求;TOKEN_MISSINGTOKEN_INVALID 直接回登录态。三态不分开的话,一次过期就会把用户踢回登录页。

  3. 03游标分页

    {start, limit}{elements, total, hasMore, nextStart}start 是不透明游标,续页只能用上一页给的 nextStart——自己算 start + limit 会串页。

  4. 04空字段被省略

    服务端 JSON 不下发 null。所以读任何可空字段之前先回答一句:这个键不存在的时候算什么?把它写进代码注释里,比记在脑子里可靠。

  5. 05错误文案有语言

    服务端按 Accept-Language 返回对应语言的错误文案。所以客户端固定发自己界面的语言,而不是透传浏览器的——否则中文界面里会冒出英文提示。

认证与令牌

  • 学生侧

    自建不透明令牌存在 Redis 里。访问令牌 4 小时且每次请求滑动续期,刷新令牌 7 天且一次性使用。同一用户重复登录会吊销之前的令牌。

  • 两个登录入口

    小程序走微信 code 登录;网页走公众号扫码——开会话拿带参二维码,用户扫码顺带关注公众号,浏览器轮询拿令牌。两条路凭 unionId 归一成同一个账号,先扫码和先小程序两个方向都有集成测试钉着。

  • 管理侧

    与学生是两套完全独立的账号体系,令牌互不通用。访问令牌是 30 分钟的 JWT,服务端登记 jti 所以能即时吊销;刷新令牌不透明、7 天、用完即焚——因此客户端的单飞刷新是必需品而不是优化。

四个端各自的取舍

部署需要准备什么

系统本身自部署。真正的前置条件是这几个外部账号,其中两个的申请周期比写代码长。

所有凭据从环境变量注入,缺一个应用在启动期就会失败并指出缺哪一项——不会带着未解析的占位符跑起来。