引言
教培机构面临的招生压力在近几年持续加剧:地推成本飙升、传统广告转化率下降、获客渠道越来越贵。核心原因是学员的注意力已经迁移到移动端,而很多机构的招生方式仍停留在"等学员上门"阶段。
本文从系统设计的角度,讨论教培机构在线招生场景下的几个关键技术模块:课程套餐的数据建模与权限授予、可视化微页面的组件化架构、多端页面生成策略,以及订单数据的统计查询实现。
一、课程套餐设计:数据模型与权限授予
1.1 单课销售的局限
单课销售模式下存在两个结构性问题:客单价低,学员学完即走,难以形成持续付费;学习碎片化,学员各买各的,无法形成系统性学习路径。
套餐将相关课程按学习路径打包销售,可以同时解决客单价和学习路径问题。
1.2 套餐数据模型
// 课程套餐实体publicclassCoursePackage{privateLongpackageId;privateStringname;// 套餐名称privateStringcoverUrl;// 套餐封面privateStringdescription;// 套餐介绍privateBigDecimaloriginalPrice;// 累计原价(展示用,用于体现优惠)privateBigDecimalsalePrice;// 套餐售价privateList<Long>courseIds;// 包含的课程列表privateIntegervalidDays;// 购买后有效期(天)privateBooleanhasCert;// 完成后是否颁发证书privateLocalDateTimesaleEndAt;// 限时优惠截止时间(可选)}关键字段说明:
originalPrice与salePrice分离,前端展示折扣信息时直接取两个字段计算,避免每次请求都重新累加单课价格。validDays控制套餐有效期,与单课有效期独立,支持套餐到期后单课权限仍保留的场景。saleEndAt为可选字段,支持限时促销,到期后自动恢复原价。
1.3 套餐购买后的权限授予
套餐购买成功后,需要为套餐内每门课程分别授权。这里涉及事务一致性:如果中途某门课程授权失败,已授权的需要回滚。
// 套餐购买后的权限授予@TransactionalpublicvoidgrantPackageAccess(LonguserId,LongpackageId,LongorderId){CoursePackagepkg=packageRepo.findById(packageId).orElseThrow();LocalDateTimeexpireAt=LocalDateTime.now().plusDays(pkg.getValidDays());// 为套餐内每门课程分别授权pkg.getCourseIds().forEach(courseId->{UserCourseAccessaccess=newUserCourseAccess();access.setUserId(userId);access.setCourseId(courseId);access.setOrderId(orderId);access.setExpireAt(expireAt);accessRepo.save(access);});}一个需要注意的边界情况:学员可能已经单独购买过套餐内的某门课程。此时需要判断已有权限的到期时间,取较晚者,避免覆盖更长的有效期。
// 处理已有权限的合并逻辑pkg.getCourseIds().forEach(courseId->{Optional<UserCourseAccess>existing=accessRepo.findByUserIdAndCourseId(userId,courseId);if(existing.isPresent()){// 取较晚的到期时间LocalDateTimenewExpireAt=existing.get().getExpireAt().isAfter(expireAt)?existing.get().getExpireAt():expireAt;existing.get().setExpireAt(newExpireAt);}else{UserCourseAccessaccess=newUserCourseAccess();access.setUserId(userId);access.setCourseId(courseId);access.setOrderId(orderId);access.setExpireAt(expireAt);accessRepo.save(access);}});二、可视化微页面:组件化架构设计
2.1 设计动机
教培机构的页面需求是高频且多样的:新课上架介绍页、节日促销落地页、招生活动报名页、讲师简介页等。如果每种页面都由开发单独编码,周期长且灵活性差。
微页面的核心思路是:将页面拆解为可复用的组件,通过 JSON 配置组合生成页面,前端渲染引擎根据 JSON 动态渲染,无需为每个页面单独写代码。
2.2 组件体系
系统定义以下基础组件类型:
| 组件类型 | 标识 | 用途 |
|---|---|---|
| Banner 图 | banner | 顶部主视觉图片,支持跳转链接 |
| 课程卡片 | course-card | 课程封面、标题、价格展示 |
| 套餐卡片 | package-card | 套餐内容、原价/现价、倒计时 |
| 讲师介绍 | lecturer | 讲师头像、简介、擅长方向 |
| 报名表单 | form | 姓名、手机号收集 |
| 倒计时模块 | countdown | 限时活动倒计时 |
| 图文区块 | richtext | 富文本内容展示 |
| 视频预览 | video | 嵌入课程试看视频 |
2.3 数据结构设计
微页面以 JSON 格式存储,核心是页面配置与组件配置的分离:
// 微页面数据结构interfaceMicroPage{pageId:stringtitle:stringcoverUrl:string// 分享时的封面图shareTitle:string// 分享标题shareDesc:string// 分享描述backgroundColor:stringcomponents:PageComponent[]}interfacePageComponent{componentId:stringtype:'banner'|'course-card'|'package-card'|'lecturer'|'form'|'countdown'|'richtext'|'video'order:number// 排序config:Record<string,any>// 各组件的配置项}// 示例:课程卡片组件配置interfaceCourseCardConfig{courseId:numbershowPrice:booleanshowOriginalPrice:booleanbuttonText:string// 按钮文案buttonAction:'buy'|'preview'|'enroll'}config字段使用Record<string, any>是一个权衡:不同组件的配置差异大,用强类型会导致接口膨胀。实际实现中可以通过 JSON Schema 对每种组件的 config 做校验,保证数据合法性。
2.4 多端渲染策略
同一份 JSON 配置需要适配多端渲染:
- PC 端:双栏布局,内容区 + 侧边栏引导,使用 CSS Grid 实现
- H5/移动端:单栏全幅布局,底部固定操作栏
- 小程序端:映射到小程序原生组件,如
rich-text、swiper等
前端渲染引擎的核心逻辑是:遍历components数组,根据type字段选择对应的渲染组件,将config作为 props 传入。新增组件类型时,只需注册新的渲染组件,无需修改引擎逻辑。
三、订单数据统计查询
3.1 统计查询设计
运营需要按课程或套餐维度查看销量和收入数据。这个查询的特点是聚合维度灵活、数据量大时需要分页。
// 订单统计查询(按课程/套餐汇总)@Query(""" SELECT o.item_type, o.item_id, o.item_name, COUNT(*) AS order_count, SUM(o.amount) AS total_revenue, AVG(o.amount) AS avg_order_value FROM orders o WHERE o.status = 'PAID' AND o.paid_at BETWEEN :startAt AND :endAt GROUP BY o.item_type, o.item_id, o.item_name ORDER BY total_revenue DESC """)List<OrderStatsVO>getOrderStatsByItem(@Param("startAt")LocalDateTimestartAt,@Param("endAt")LocalDateTimeendAt);当订单量达到百万级时,这个聚合查询需要优化:
paid_at字段建索引,避免全表扫描- 时间范围查询走索引覆盖,避免回表
- 如果数据量继续增长,考虑将统计结果预计算并缓存,用定时任务每小时刷新
3.2 核心统计指标
| 指标 | 计算方式 |
|---|---|
| 销售额 | SUM(amount) WHERE status = 'PAID' |
| 付费学员数 | COUNT(DISTINCT user_id) WHERE status = 'PAID' |
| 课程销量排行 | 按item_id分组,COUNT(*) DESC |
| 订单转化率 | 详情页 UV / 支付成功数 |
| 退款率 | 退款数 / 已支付数,按课程维度统计 |
转化率的分母(详情页 UV)来自前端埋点上报,需要单独的访问日志表存储,不能与订单表直接 JOIN。实际实现中通常通过定时任务离线计算,写入统计结果表。
四、总结
本文讨论了教培机构在线招生系统中几个关键模块的设计思路:
- 套餐权限授予需要处理事务一致性和已有权限合并两个边界情况
- 微页面组件化通过 JSON 配置驱动渲染,新增页面类型无需开发介入
- 订单统计查询在大数据量下需要索引优化和预计算策略
这几个模块的共同特点是:业务逻辑看似简单,但边界情况处理和数据一致性保障才是实现中的难点。在实际项目中,权限授予的事务边界、微页面 JSON 的 Schema 校验、统计查询的性能优化,都需要根据具体业务规模做针对性设计。