1. 项目概述与核心需求解析一个在线教育平台的预约评价系统拆分下来其实要同时解决三端的问题学生端要快速找课、约课、课后反馈教师端要维护课程、统计评价管理端要完成审核和配置。题目里最开始叫预约评价系统后面又带了在线教育App小程序这就意味着整个系统不能只做一个单机Demo而是要考虑多端协同、移动端优先、甚至后续扩展到小程序的兼容性所以技术选型和组织架构从一开始就得想清楚。从需求反向推导这个项目最核心的场景有三个第一是课程预约流程学生看到课程列表查看详情选择时间提交预约第二是评价闭环学生完成课程后对教师和课程打分、留言数据沉淀下来反哺选课决策第三是管理和展示教师上传课程内容管理员审核课程上线学生端展示课程和教师的评价数据。技术方案上主端选择Android原生开发Java或Kotlin服务端用Spring Boot提供RESTful接口数据库用MySQL存储核心业务数据再加上Redis做缓存。小程序端后续如果要做接口可以直接复用只需要换一层UI实现这算是成本最低的多端兼容思路。2. 功能模块与业务场景拆解整个系统可以拆成三个角色维度每个角色看到的界面和能做的操作完全不一样从数据库设计层面就要把权限和角色字段设计好不然做到后面会非常痛苦。2.1 管理员端功能设计管理员的职责范围是整个平台的课程和用户。需要设计一个独立的管理后台用Web端实现会更方便管理员要能完成课程审批、教师账号审核、用户禁用与解封、课程分类管理这几件事。课程审核是其中最重要的一环教师在App端上传课程后课程并不会立刻出现在学生端的列表中而是先进入待审核状态。管理员需要检查课程标题、封面图、课程简介、价格策略、排课时间这些信息通过后才正式上架。2.2 教师端功能设计教师端是内容生产者功能设计要围绕课程的整个生命周期展开。课程创建、课程编辑、课表管理、学生预约列表、评价查看和数据统计这六块是一环扣一环的。创建课程时教师需要填写课程名称、所属科目分类、课程简介、封面图片、每节课时长、总课时数、上课方式以及是单次预约还是阶段课程。这个表单信息量很大App端需要做好分段交互避免一屏装下所有字段导致用户体验很差。然后通过一个日历视图管理排课比如每周一、周三晚上7点到8点可预约。排课有了学生才能预约。教师端还能看到学生预约列表开课前进行确认或取消操作。评价查看和数据统计则要和课程预约打通学生完成课程后评价教师端需要及时看到反馈。2.3 学生端功能设计学生端是用户量最大的部分功能设计的核心是让操作路径尽量短。学生打开App第一步是浏览课程第二步是预约第三步是上课/查看记录第四步是评价。课程列表采用信息流的形式展示每张卡片上要有课程封面、标题、教师姓名、评分星级、价格和可预约状态。学生点击进入课程详情页看到教师介绍、课程大纲、已学学员的评价列表然后选择合适的上课时间提交预约单。预约单要支持取消有截止时间限制课程完成后触发评价入口评分维度可以拆成课程质量、教师讲解、互动体验三项外加文字评价。2.4 通用功能与三方服务多端通用的功能还包括登录注册手机号短信验证码或账号密码、消息通知预约状态变更、开课提醒、支付课程费用结算MVP阶段可以跳过、文件上传课程封面、教师头像压缩后走OSS或服务器本地存储。选型时要考虑自建成本和第三方SDK之间的平衡。比如推送如果服务器资源有限直接轮询接口也可以用极光推送或厂商推送会更省事但前期接入成本也不低。3. 技术选型与系统架构落地技术选型的结论是所有后续开发的基础这里把整个技术栈的决策思路讲清楚方便直接照着落地。3.1 Android客户端架构选型Android端采用MVVM架构配合Google官方推荐的Jetpack组件这套组合是目前最稳妥的选择。Kotlin作为开发语言比Java在空安全、协程、扩展函数上省很多事新项目可以直接用Kotlin起步。UI层用ViewBinding或Compose二选一。Compose是趋势但对于团队不熟、工期紧的项目ViewBinding加XML可能更稳妥。网络层用Retrofit配合OkHttp和Gson这是最经典的组合。图片加载用Glide稳定成熟加载网络图片的缓存策略已经打磨得非常完善。数据本地化用Room用它缓存课程列表和用户信息提升弱网状态下的可用性。业务逻辑用ViewModel承载通过LiveData或Flow实现对UI层的感知更新。每个业务模块建议按功能分包不要按层分包比如把预约模块、课程模块、评价模块拆成独立Package每个内部再放activity/adapter/viewmodel这样随着业务膨胀更容易维护。// 一个ViewBinding ViewModel的简单示例 class CourseListActivity : AppCompatActivity() { private lateinit var binding: ActivityCourseListBinding private val viewModel: CourseViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding ActivityCourseListBinding.inflate(layoutInflater) setContentView(binding.root) viewModel.courseList.observe(this) { courses - // 刷新列表数据 courseAdapter.submitList(courses) } viewModel.loadCourses() } }3.2 后端服务端设计后端选择Spring Boot 2.x理由很简单生态丰富、招人容易、部署简单。Java开发的同学人手一套遇到问题社区答案也多。权限认证用JWTJSON Web Token实现无状态登录前端拿到token后放到请求头里传给后端后端通过拦截器校验。数据库用MySQL 5.7默认引擎InnoDB字符集utf8mb4。Redis用来存热点数据比如课程列表页的缓存、首页推荐位以及预约锁库存时的分布式锁。文件存储方面课程封面和头像走本地路径存储简单部署时直接放在服务器上的某个目录下通过nginx把静态目录映射出去。3.3 数据库核心表设计数据库表设计是整个项目地基这里给出最核心的几张表结构后续所有代码都围绕这些表展开。第一张是用户表字段包括id、手机号、密码加密存储、昵称、头像、角色、状态、创建时间、更新时间。角色字段用int类型区分比如1是学生2是教师3是管理员。第二张是教师信息表因为教师有额外的简介、教龄、擅长科目这些字段单独拆一张表存扩展信息避免用户表字段太冗余。第三张是课程表包含id、教师id、课程名称、课程封面、课程简介、分类id、价格、总课时、每节时长、上课方式、状态待审核/已上架/已下架、创建时间。第四张是排课表记录课程的可预约时间段包括课程id、上课日期、开始时间、结束时间、已预约人数、人数上限、状态。第五张是预约表记录学生预约课程的具体记录包含预约单号、用户id、排课id、课程id、预约状态已预约/已取消/已上课/已完成、创建时间、完成时间。第六张是评价表包含预约id、用户id、课程id、教师id、课程评分、讲解评分、互动评分、文字评价内容、创建时间。设计时要注意预约表和评价表是1对1的关系一条预约只对应一条评价也就是说学生只有完成课程后才能发起评价而且只能评价一次。3.4 接口协议约定前后端接口统一走RESTful风格返回格式要标准化。所有接口统一返回一个Result对象包含code、message、data三个字段。比如成功是code200业务异常是code400未登录是code401无权限是code403。接口路径设计按资源划分例如/api/course列表、/api/course/detail详情、/api/appointment/create创建预约、/api/appointment/cancel取消预约、/api/evaluation/submit提交评价。分页统一用page、pageSize参数返回的数据结构包含总条数、当前页数据和总页数。4. Android客户端关键模块实现客户端代码是绝大多数开发者精力投入最多的地方把预约流程和评价流程这两条主链路做好整个App的核心体验就立住了。4.1 登录注册模块实现登录注册是整个App的入口建议优先做简洁版。用户输入手机号获取短信验证码输入验证码后完成登录。短信验证码在开发阶段可以用固定验证码比如123456模拟上线前再接入真实短信网关。登录成功后后端返回JWT令牌和用户基础信息客户端存到SharedPreferences或DataStore里面。之后所有需要鉴权的接口都在请求头中携带token。请求拦截器要统一处理好token过期的情况当收到401响应时自动跳转到登录页并清空本地缓存。// Retrofit的Token拦截器写法 class AuthInterceptor : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val token SharedPreferencesUtil.getToken() val request chain.request().newBuilder() .header(Authorization, Bearer $token) .build() return chain.proceed(request) } }这个拦截器是整个网络模块的核心每加一个新的接口调用token都会自动携带不需要在业务代码里反复写Header否则每个接口都要传一遍token代码会非常冗余。4.2 课程列表与详情页展示课程列表页是学生端主场景一般用RecyclerView实现。性能和体验的重点在于图片加载的优化课程封面图要用Glide做尺寸适配和缓存还要处理加载失败时的占位图。列表分页采用常见的下拉刷新和上拉加载更多通过SwipeRefreshLayout配合RecyclerView的滑动监听实现。课程详情页要处理的信息更多除了基本信息还要展示教师资料、课程大纲、学员评价列表和可预约时间表。时间表建议用横向滑动的日期选择器加时间段列表选中某个时间段后展示实时剩余名额点击预约按钮再去提交。这里有一个关键细节时间段的状态要从后端实时获取因为可能存在多人同时预约导致名额变少不能直接使用本地缓存的数据否则会出现明明显示有名额但提交预约时失败的情况。4.3 预约流程的状态机设计预约是整个业务的核心链路状态机的设计一定要事先理清楚。预约单的状态流转如下待支付或待确认 - 已预约 - 已取消已预约 - 已上课 - 已完成已上课后触发评价入口完成评价后整个链路结束。Android端要处理好的场景主要是创建预约、取消预约、预约状态变更提醒。创建预约时提交课程id和排课id后端校验排课状态和剩余名额校验通过则生成一条预约记录。前端收到成功响应后跳转到预约成功页。取消预约要有时间截止规则比如开课前2小时允许取消超过时间则不允许。这个规则最好后端做硬校验前端只做展示提示因为用户修改本地时间绕过后端校验会导致数据不一致。4.4 评价模块实现评价模块的触发条件是预约状态等于已完成这个状态由后端在课程结束时间到达后自动更新。学生端首页做一个待评价入口或预约详情页展示评价按钮。评价页面包含三项打分和文字输入。评分用RatingBar控件设置为可半星或整星。三项评分分别映射到课程质量、教师讲解、互动体验三个维度提交时连同预约id一起POST到后端。前端需要做的基本校验是这三项评分不能为空、文字评价字数限制在500字以内。评价成功后列表里要能看到最新评价同时课程详情页的整体评分也应当动态更新。这个更新过程在后端完成前端只需要在评价成功后刷新详情页数据即可。5. 后端服务与接口实现要点后端代码的重点不在CRUD本身而是业务规则的实现和并发场景的处理。5.1 预约名额的并发控制预约接口一定是并发操作的重灾区。比如一个时段只有20个名额同时有30个学生点击预约如果代码写成先查后减那就会超卖预约人数超过上限。解决办法是在排课表中加一个已预约人数字段更新的时候用条件更新UPDATE schedule SET booked_count booked_count 1 WHERE id ? AND booked_count max_count这样的写法可以在单条SQL里完成判断和更新利用数据库本身的原子性避免了超卖问题。如果用的是Redis做分布式锁也可以实现但直接用数据库条件更新更简单也更容易维护。5.2 评价与预约的关系校验评价提交接口要严格校验预约状态和评价唯一性。学生只能评价自己名下的预约且必须满足预约状态已完成而且一条预约只能评价一次。校验逻辑放在Service层核心代码如下public Result submitEvaluation(EvaluationRequest request) { // 1. 校验预约是否存在 Appointment appointment appointmentMapper.selectById(request.getAppointmentId()); if (appointment null) { return Result.error(400, 预约记录不存在); } // 2. 校验预约归属 if (!appointment.getUserId().equals(request.getUserId())) { return Result.error(403, 无权评价该预约); } // 3. 校验预约状态 if (appointment.getStatus() ! APPOINTMENT_FINISHED) { return Result.error(400, 课程未完成暂不能评价); } // 4. 校验唯一性 if (evaluationMapper.existsByAppointmentId(request.getAppointmentId())) { return Result.error(400, 该预约已评价不能重复提交); } // 5. 业务数据入库 更新课程评分 evaluationMapper.insert(request); courseMapper.updateAverageScore(request.getCourseId()); return Result.ok(评价成功); }5.3 JWT鉴权与接口安全后端接口的鉴权通过Spring Boot拦截器实现。自定义一个拦截器在preHandle方法中读取请求头中的token解析并校验合法性再把用户信息放入ThreadLocal供后续业务使用。public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (token null || !token.startsWith(Bearer )) { response.setStatus(401); return false; } String jwt token.substring(7); // 校验jwt并解析用户信息 return true; } }需要说明的是接口安全不能只做登录校验还要做参数校验尤其是预约和评价场景后端的参数校验要覆盖前端所有可能漏掉的情况比如课程id不存在、排课id为空、评分为负数等等。把所有异常情况都拦截在后端是第一道防线。5.4 文件上传与静态资源处理课程封面和用户头像的上传接口前端用multipart/form-data格式POST到服务端。服务端接收后将文件写入磁盘上的指定目录并生成一个唯一的文件名时间戳加随机数然后将文件访问的URL返回给前端。PostMapping(/api/file/upload) public ResultString upload(RequestParam(file) MultipartFile file) { String originalFilename file.getOriginalFilename(); String ext originalFilename.substring(originalFilename.lastIndexOf(.)); String filename System.currentTimeMillis() _ UUID.randomUUID() ext; file.transferTo(new File(/data/upload/ filename)); return Result.ok(/static/ filename); }中间的坑是文件大小限制Spring Boot默认上传大小是1MB课程封面通常要调大到5MB。另一个坑是服务器重启后上传文件丢失需要定期备份或接入阿里云OSSMVP阶段用本地存储完全可以。6. 小程序端与Android端的联动实现小程序端是整个项目向更广阔用户群体延伸的关键环节在Android端功能稳定后再并行开发。6.1 小程序端技术选型小程序选择使用微信小程序原生框架它的核心逻辑和Android端有相似之处比如页面都是独立的wxml、wxss、js文件也有配套的组件库。如果团队熟悉Vue也可以选择uni-app用一套代码同时发布到微信小程序、支付宝小程序和相关平台。不过这里建议MVP阶段直接上原生小程序因为涉及的原生能力和兼容性问题更少团队上手的速度也更快。小程序的页面主要围绕课程列表、课程详情、预约操作和评价操作展开整体页面数量控制在十来个以内不用像Android那样构建复杂的本地缓存和内存管理逻辑。6.2 接口复用与权限管理小程序端完全复用后端接口登录流程换成微信授权登录加手机号绑定。用户在Android端已经登录过时小程序端要支持账号体系打通。简单方案是小程序端用微信登录生成一个临时code传给后端换取用户信息和token然后和Android端共用同一套用户体系。这里要考虑一个细节同一用户可能从Android端和小程序端两个入口登录需要保证数据的一致性和登录态的管理方式完全一致。否则用户在两端的预约记录会割裂后台看到的数据也会是独立的两套后患无穷。6.3 小程序端的页面交互优化小程序端的核心操作路径是首页列表 - 课程详情 - 选择时段 - 提交预约 - 我的预约 - 待评价 - 提交评价。整个路径的页面跳转要精简小程序用户习惯轻量操作太深的导航层级会导致流失率上升。首页的课程列表卡片做成简洁风格突出课程名、教师头像、评分和价格四个信息点击卡片跳详情。详情页面展示排课时间表用简单的标签布局替代横滑日历点击时间段后直接弹出确认预约的底部弹层减少页面跳转。7. 系统部署与联调自测项目的开发完成不等于项目结束部署和联调自测阶段有非常多平时不会注意到的问题会暴露出来这里梳理几个最容易踩坑的地方。7.1 前后端联调注意事项前后端联调的第一步是确认接口文档接口字段名、类型、嵌套结构完全对齐这一步省不了。联调过程中最容易出问题的是日期格式后端返回的时间戳或格式是UTC还是北京时间前端解析时有没有做时区换算这中间差8个小时很容易导致时段展示错乱。建议后端统一返回yyyy-MM-dd HH:mm:ss格式的字符串前端做展示时不做额外转换这样两边排查问题都直观。另一个常见问题是空值处理后端返回null字段前端直接展示或强转会导致崩溃前端要做好空对象保护后端在列表和详情接口中也要尽量返回空串代替null。真机调试时要特别注意Android网络权限配置开发时访问后端服务器如果后端是http明文协议Android 9及以上需要在AndroidManifest.xml中开启usesCleartextTraffic属性否则请求会被拦截掉。7.2 后端部署与环境配置后端部署建议直接用云服务器加宝塔面板部署成本低、操作直观。部署步骤安装JDK 1.8、安装MySQL、创建数据库并导入表结构、安装Redis、上传jar包、使用systemd配置进程守护。application.yml有几个关键配置要点数据库连接串的时区参数serverTimezoneAsia/Shanghai必须配置否则时间戳会显示成UTC时间。文件上传路径用绝对路径不要用相对路径避免jar包运行目录变化导致找不到文件。生产环境的日志级别调到INFO避免DEBUG刷屏占满磁盘。7.3 功能自测清单与压测建议功能自测建议至少覆盖下面这些核心场景学生注册登录、教师注册并完善信息、教师创建课程、管理员审核课程上架、学生浏览课程列表和详情、学生预约课程、教师查看预约列表、学生取消预约、课程时间结束、学生提交评价、教师查看评价结果、管理员查看整体评价数据。并发这块MVP阶段不需要上来就做分布式压测但要保证最基本的100并发请求情况下系统不挂。主要瓶颈在数据库和网络IO使用JMeter对核心接口做一轮简单的并发测试是有必要的重点看预约接口在100并发下是否出现超卖或报错。8. 常见问题与排查技巧实录开发过程中一定会遇到各种问题把最典型的几类整理出来这些问题以后在其他项目里大概率也会遇到。8.1 Android端常见问题第一个问题是图片加载不出来。遇到这种情况先检查URL是否能在浏览器里直接打开浏览器能打开说明后端静态资源没问题问题在前端。再看是否开启明文传输、URL是否拼接错误、域名是否在网络安全配置中允许访问。排查顺序是从外向内显示层、网络层、数据层。第二个问题是接口请求返回401或者登录失效。先检查token是否过期再看是否使用的是最新token排查请求头。建议在Retrofit的日志拦截器中把每个请求的完整URL和Headers打印出来一目了然。常见场景是用户在调试时切换账号旧token还留在本地存储中没有清理。第三个问题是RecyclerView列表更新后页面不刷新或闪现错乱。多半是DiffUtil没写或者notifyDataSetChanged大量调用导致的性能问题。建议列表数据使用ListAdapter配合DiffUtil让框架自动算出差集局部刷新这样既保证性能又避免闪烁。8.2 后端常见问题第一个问题是接口返回500日志显示SQL语法错误。基本是实体类字段和表字段名不一致导致检查MyBatis或JPA的驼峰映射配置是否正确。很多时候resultType写错或字段没映射报错还不是一次性的而是运行时才爆出来比较耗时。第二个问题是跨域导致前端请求失败。Spring Boot需要配置CORS过滤器允许前端开发服务器的地址跨域访问。配置时注意allowedOrigin不能写成*加allowCredentials同时使用这会触发浏览器安全限制。第三个问题是部署后Redis连接失败。大概率是服务器安全组端口没放行或者Redis绑定了127.0.0.1只允许本机访问。排查顺序是ping通不通、端口通不通、密码对不对、配置引用对不对。8.3 预约和评价的业务坑预约的超卖问题已经在前面讲过解决办法这里强调一点条件更新虽然能防超卖但是并发过高时还是可能出现更新失败的情况前端要做好失败提示和刷新逻辑让用户决定是换时段还是继续尝试。评价维度的数据统计问题也容易忽略。课程平均评分的更新如果每次评价都全表计算一次数据量大就会慢建议在课程表冗余一个average_score字段并加一个evaluation_count字段每次评价完成时对这两个字段做更新查询时直接返回避免聚合查询。9. 项目扩展方向与经验沉淀整个项目的MVP版本跑通之后可以考虑几个扩展方向这些方向能极大提升产品的完整度。第一个方向是消息推送。现在预约状态变更全靠用户主动刷新体验一般。接入推送服务后预约成功、开课提醒、课程被取消这些事件都可以实时推送到用户手机产品的粘性会有明显提升。第二个方向是统计分析。目前评价数据只是罗列可以做教师维度、课程维度、时间维度的数据可视化大屏按周、按月统计课程预约量、完成率、平均评分、热门课程排名等指标这些数据对运营决策非常有价值。第三个方向是课程内容形态的丰富目前预约评价系统只是连接学生和教师课程内容本身以线下或直播形式承载。后续可以做录播视频回放、课程资料下载、学习笔记等功能把在线教育的教学环节也纳入系统闭环。在项目推进过程中我自己的体会是不要去追求一步到位MVP阶段把核心链路跑通远比堆功能重要。先做预约和评价这两个闭环把数据结构设计扎实、接口规范统一、状态机逻辑清晰后续扩展功能只是在这个地基上垒砖。多端项目的核心痛点往往不在单个端的功能实现而在于接口规范的一致性和数据流转的正确性这两点从项目第一天就要定死标准。