项目简介
在线教育课程管理系统是一个基于Spring Boot + Vue.js的全栈项目,旨在为教育机构提供一个完整的在线课程管理解决方案。系统支持课程管理、学员管理、教师管理、视频点播、作业批改等核心功能。
技术栈
后端技术
- Spring Boot 2.x
- Spring Security
- MyBatis-Plus
- Redis
- MySQL
- JWT认证
- 阿里云OSS
- 阿里云视频点播
前端技术
- Vue.js 2.x
- Element UI
- Axios
- Vuex
- Vue Router
- ECharts
核心功能模块
1. 课程管理
- 课程信息维护
- 课程章节管理
- 课程视频上传
- 课程发布与下架
2. 讲师管理
- 讲师信息维护
- 讲师资质审核
- 讲师课程关联
3. 学员管理
- 学员注册登录
- 学习记录追踪
- 课程购买
- 学习进度管理
4. 视频点播
- 视频上传转码
- 视频加密播放
- 断点续播
- 播放统计
5. 订单管理
- 课程订单
- 支付集成
- 订单统计
- 退款处理
系统架构
├── 表现层(Vue.js前端)
│ ├── 用户界面
│ ├── 状态管理
│ └── 路由控制
├── 应用层(Spring Boot后端)
│ ├── 控制器
│ ├── 业务逻辑
│ └── 数据访问
└── 数据层
├── MySQL
├── Redis
└── 阿里云存储
数据库设计
主要数据表包括:
-- 课程表
CREATE TABLE edu_course (
id BIGINT PRIMARY KEY,
title VARCHAR(100),
teacher_id BIGINT,
price DECIMAL(10,2),
lesson_num INTEGER,
cover VARCHAR(255),
buy_count BIGINT,
status VARCHAR(10),
create_time DATETIME
);
-- 讲师表
CREATE TABLE edu_teacher (
id BIGINT PRIMARY KEY,
name VARCHAR(50),
intro TEXT,
career VARCHAR(100),
level INTEGER,
avatar VARCHAR(255),
sort INTEGER
);
-- 用户表
CREATE TABLE edu_member (
id BIGINT PRIMARY KEY,
username VARCHAR(50),
password VARCHAR(255),
nickname VARCHAR(50),
mobile VARCHAR(11),
avatar VARCHAR(255),
is_disabled BOOLEAN
);
关键技术实现
1. 视频点播服务
@Service
public class VodServiceImpl implements VodService {
@Override
public String uploadVideo(MultipartFile file) {
try {
// 初始化VOD客户端
DefaultAcsClient client = initVodClient();
// 上传视频
CreateUploadVideoResponse response = uploadVideo(client, file);
// 返回视频ID
return response.getVideoId();
} catch (Exception e) {
throw new CustomException(20001, "视频上传失败");
}
}
}
2. JWT认证
@Component
public class JwtUtils {
private static final long EXPIRE = 1000 * 60 * 60 * 24;
private static final String SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";
public static String createToken(String id, String nickname) {
return Jwts.builder()
.setSubject("edu-user")
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
.claim("id", id)
.claim("nickname", nickname)
.signWith(SignatureAlgorithm.HS256, SECRET)
.compact();
}
}
性能优化
- Redis缓存:
@Cacheable(value = "course", key = "#id")
public CourseVO getCourseInfo(String id) {
Course course = courseMapper.selectById(id);
return convertToCourseVO(course);
}
- MyBatis-Plus分页:
public IPage<Course> pageCourse(long current, long limit) {
Page<Course> page = new Page<>(current, limit);
return baseMapper.selectPage(page, null);
}
部署方案
- 前端部署:
- Nginx反向代理
- CDN加速
- 后端部署:
- Docker容器化
- SpringCloud微服务架构
项目总结
本项目采用前后端分离架构,实现了在线教育平台的核心功能。通过使用主流的技术栈,保证了系统的可扩展性和维护性。在开发过程中,重点关注了以下几个方面:
- 系统安全性
- 代码可维护性
- 性能优化
- 用户体验
未来展望
- 引入微服务架构
- 添加直播课程功能
- 优化移动端适配
- 引入AI智能推荐
- 完善数据分析功能
在线教育系统核心模块详解
一、课程管理模块
1. 数据模型设计
-- 课程基本信息表
CREATE TABLE edu_course (
id BIGINT PRIMARY KEY,
title VARCHAR(100) COMMENT '课程标题',
teacher_id BIGINT COMMENT '讲师ID',
subject_id BIGINT COMMENT '课程分类ID',
subject_parent_id BIGINT COMMENT '课程分类父级ID',
price DECIMAL(10,2) COMMENT '课程价格',
lesson_num INTEGER COMMENT '总课时',
cover VARCHAR(255) COMMENT '课程封面',
buy_count BIGINT COMMENT '销售数量',
view_count BIGINT COMMENT '浏览数量',
status VARCHAR(10) COMMENT '课程状态',
description TEXT COMMENT '课程简介',
create_time DATETIME,
update_time DATETIME
);
-- 课程章节表
CREATE TABLE edu_chapter (
id BIGINT PRIMARY KEY,
course_id BIGINT COMMENT '课程ID',
title VARCHAR(100) COMMENT '章节标题',
sort INTEGER COMMENT '排序',
create_time DATETIME,
update_time DATETIME
);
-- 课程小节表
CREATE TABLE edu_video (
id BIGINT PRIMARY KEY,
course_id BIGINT COMMENT '课程ID',
chapter_id BIGINT COMMENT '章节ID',
title VARCHAR(100) COMMENT '节点标题',
video_source_id VARCHAR(100) COMMENT '云端视频资源ID',
duration FLOAT COMMENT '视频时长(秒)',
size BIGINT COMMENT '视频大小(字节)',
sort INTEGER COMMENT '排序',
free BOOLEAN COMMENT '是否免费',
create_time DATETIME,
update_time DATETIME
);
2. 核心功能实现
2.1 课程信息管理
@Service
public class CourseServiceImpl implements CourseService {
@Autowired
private CourseMapper courseMapper;
@Transactional
public String saveCourseInfo(CourseInfoVO courseInfoVO) {
// 1. 保存课程基本信息
Course course = new Course();
BeanUtils.copyProperties(courseInfoVO, course);
courseMapper.insert(course);
// 2. 保存课程描述信息
CourseDescription description = new CourseDescription();
description.setDescription(courseInfoVO.getDescription());
description.setCourseId(course.getId());
descriptionMapper.insert(description);
return course.getId();
}
@Cacheable(value = "course", key = "#id")
public CourseInfoVO getCourseInfo(String id) {
// 获取课程信息
Course course = courseMapper.selectById(id);
// 获取课程描述
CourseDescription description = descriptionMapper.selectById(id);
// 组装数据
CourseInfoVO courseInfoVO = new CourseInfoVO();
BeanUtils.copyProperties(course, courseInfoVO);
courseInfoVO.setDescription(description.getDescription());
return courseInfoVO;
}
}
2.2 章节管理
@Service
public class ChapterServiceImpl implements ChapterService {
@Autowired
private ChapterMapper chapterMapper;
@Autowired
private VideoMapper videoMapper;
public List<ChapterVO> getChapterVideoByCourseId(String courseId) {
// 1. 根据课程id查询章节列表
QueryWrapper<Chapter> chapterWrapper = new QueryWrapper<>();
chapterWrapper.eq("course_id", courseId);
chapterWrapper.orderByAsc("sort");
List<Chapter> chapters = chapterMapper.selectList(chapterWrapper);
// 2. 根据课程id查询小节列表
QueryWrapper<Video> videoWrapper = new QueryWrapper<>();
videoWrapper.eq("course_id", courseId);
videoWrapper.orderByAsc("sort");
List<Video> videos = videoMapper.selectList(videoWrapper);
// 3. 组装数据
List<ChapterVO> chapterVOList = new ArrayList<>();
chapters.forEach(chapter -> {
ChapterVO chapterVO = new ChapterVO();
BeanUtils.copyProperties(chapter, chapterVO);
// 过滤出属于当前章节的小节
List<VideoVO> videoVOList = videos.stream()
.filter(video -> video.getChapterId().equals(chapter.getId()))
.map(video -> {
VideoVO videoVO = new VideoVO();
BeanUtils.copyProperties(video, videoVO);
return videoVO;
})
.collect(Collectors.toList());
chapterVO.setChildren(videoVOList);
chapterVOList.add(chapterVO);
});
return chapterVOList;
}
}
2.3 视频管理
@Service
public class VideoServiceImpl implements VideoService {
@Autowired
private VodClient vodClient;
@Transactional
public void saveVideoInfo(VideoInfoForm videoInfoForm) {
// 1. 上传视频到云端
String videoSourceId = vodClient.uploadVideo(videoInfoForm.getFile());
// 2. 保存视频信息
Video video = new Video();
BeanUtils.copyProperties(videoInfoForm, video);
video.setVideoSourceId(videoSourceId);
videoMapper.insert(video);
}
@Transactional
public void removeVideo(String id) {
// 1. 查询云端视频id
Video video = videoMapper.selectById(id);
String videoSourceId = video.getVideoSourceId();
// 2. 删除云端视频
vodClient.removeVideo(videoSourceId);
// 3. 删除数据库记录
videoMapper.deleteById(id);
}
}
二、讲师管理模块
1. 数据模型设计
CREATE TABLE edu_teacher (
id BIGINT PRIMARY KEY,
name VARCHAR(50) COMMENT '讲师姓名',
intro TEXT COMMENT '讲师简介',
career VARCHAR(100) COMMENT '职业资质',
level INTEGER COMMENT '讲师级别',
avatar VARCHAR(255) COMMENT '头像',
sort INTEGER COMMENT '排序',
status BOOLEAN COMMENT '状态',
create_time DATETIME,
update_time DATETIME
);
2. 核心功能实现
2.1 讲师列表与条件查询
@Service
public class TeacherServiceImpl implements TeacherService {
@Autowired
private TeacherMapper teacherMapper;
public IPage<Teacher> pageTeacherCondition(long current, long limit, TeacherQuery teacherQuery) {
Page<Teacher> page = new Page<>(current, limit);
QueryWrapper<Teacher> wrapper = new QueryWrapper<>();
// 动态SQL
String name = teacherQuery.getName();
Integer level = teacherQuery.getLevel();
String begin = teacherQuery.getBegin();
String end = teacherQuery.getEnd();
if (!StringUtils.isEmpty(name)) {
wrapper.like("name", name);
}
if (level != null) {
wrapper.eq("level", level);
}
if (!StringUtils.isEmpty(begin)) {
wrapper.ge("create_time", begin);
}
if (!StringUtils.isEmpty(end)) {
wrapper.le("create_time", end);
}
wrapper.orderByDesc("sort");
return teacherMapper.selectPage(page, wrapper);
}
}
2.2 讲师资质审核
@Service
public class TeacherAuditServiceImpl implements TeacherAuditService {
@Autowired
private TeacherMapper teacherMapper;
@Transactional
public void auditTeacher(String teacherId, Integer status, String comment) {
Teacher teacher = teacherMapper.selectById(teacherId);
// 更新审核状态
teacher.setStatus(status);
teacherMapper.updateById(teacher);
// 记录审核日志
TeacherAuditLog log = new TeacherAuditLog();
log.setTeacherId(teacherId);
log.setStatus(status);
log.setComment(comment);
auditLogMapper.insert(log);
// 发送审核结果通知
if (status == 1) {
notifyService.sendAuditPassNotification(teacher);
} else {
notifyService.sendAuditRejectNotification(teacher, comment);
}
}
}
三、学员管理模块
1. 数据模型设计
-- 学员信息表
CREATE TABLE edu_member (
id BIGINT PRIMARY KEY,
openid VARCHAR(128) COMMENT '微信openid',
mobile VARCHAR(11) COMMENT '手机号',
password VARCHAR(255) COMMENT '密码',
nickname VARCHAR(50) COMMENT '昵称',
sex INTEGER COMMENT '性别',
age INTEGER COMMENT '年龄',
avatar VARCHAR(255) COMMENT '用户头像',
sign VARCHAR(100) COMMENT '用户签名',
is_disabled BOOLEAN COMMENT '是否禁用',
create_time DATETIME,
update_time DATETIME
);
-- 学习记录表
CREATE TABLE edu_study_record (
id BIGINT PRIMARY KEY,
course_id BIGINT COMMENT '课程ID',
member_id BIGINT COMMENT '会员ID',
video_id BIGINT COMMENT '视频ID',
watch_time INTEGER COMMENT '观看时长(秒)',
progress FLOAT COMMENT '观看进度',
last_watch_time DATETIME COMMENT '最后观看时间',
create_time DATETIME,
update_time DATETIME
);
2. 核心功能实现
2.1 学员注册登录
@Service
public class MemberServiceImpl implements MemberService {
@Autowired
private MemberMapper memberMapper;
@Autowired
private PasswordEncoder passwordEncoder;
public void register(RegisterVO registerVO) {
// 1. 验证手机号是否已注册
QueryWrapper<Member> wrapper = new QueryWrapper<>();
wrapper.eq("mobile", registerVO.getMobile());
Integer count = memberMapper.selectCount(wrapper);
if (count > 0) {
throw new CustomException(20001, "手机号已被注册");
}
// 2. 创建会员
Member member = new Member();
member.setMobile(registerVO.getMobile());
member.setNickname(registerVO.getNickname());
member.setPassword(passwordEncoder.encode(registerVO.getPassword()));
member.setIsDisabled(false);
member.setAvatar("https://edu-default-avatar.oss-cn-beijing.aliyuncs.com/default.jpg");
memberMapper.insert(member);
}
public String login(LoginVO loginVO) {
// 1. 根据手机号获取会员信息
QueryWrapper<Member> wrapper = new QueryWrapper<>();
wrapper.eq("mobile", loginVO.getMobile());
Member member = memberMapper.selectOne(wrapper);
// 2. 校验密码
if (member == null || !passwordEncoder.matches(loginVO.getPassword(), member.getPassword())) {
throw new CustomException(20001, "手机号或密码错误");
}
// 3. 生成token
String token = JwtUtils.createToken(member.getId(), member.getNickname());
return token;
}
}
2.2 学习进度管理
@Service
public class StudyRecordServiceImpl implements StudyRecordService {
@Autowired
private StudyRecordMapper studyRecordMapper;
public void saveStudyRecord(StudyRecordVO studyRecordVO) {
String memberId = JwtUtils.getMemberIdByJwtToken(request);
// 1. 查询是否存在学习记录
QueryWrapper<StudyRecord> wrapper = new QueryWrapper<>();
wrapper.eq("course_id", studyRecordVO.getCourseId());
wrapper.eq("member_id", memberId);
wrapper.eq("video_id", studyRecordVO.getVideoId());
StudyRecord record = studyRecordMapper.selectOne(wrapper);
if (record == null) {
// 2. 创建新的学习记录
record = new StudyRecord();
record.setCourseId(studyRecordVO.getCourseId());
record.setMemberId(memberId);
record.setVideoId(studyRecordVO.getVideoId());
record.setWatchTime(studyRecordVO.getWatchTime());
record.setProgress(studyRecordVO.getProgress());
studyRecordMapper.insert(record);
} else {
// 3. 更新学习记录
record.setWatchTime(studyRecordVO.getWatchTime());
record.setProgress(studyRecordVO.getProgress());
record.setLastWatchTime(new Date());
studyRecordMapper.updateById(record);
}
}
public Map<String, Object> getStudyProgress(String courseId) {
String memberId = JwtUtils.getMemberIdByJwtToken(request);
// 1. 获取课程的所有视频
List<Video> videoList = videoMapper.selectByCourseId(courseId);
// 2. 获取已学习的视频记录
QueryWrapper<StudyRecord> wrapper = new QueryWrapper<>();
wrapper.eq("course_id", courseId);
wrapper.eq("member_id", memberId);
List<StudyRecord> recordList = studyRecordMapper.selectList(wrapper);
// 3. 计算学习进度
int totalVideos = videoList.size();
int finishedVideos = (int) recordList.stream()
.filter(record -> record.getProgress() >= 0.9)
.count();
Map<String, Object> result = new HashMap<>();
result.put("total", totalVideos);
result.put("finished", finishedVideos);
result.put("progress", totalVideos == 0 ? 0 : (finishedVideos * 100 / totalVideos));
return result;
}
}
2.3 学习数据统计
@Service
public class StudyStatisticsServiceImpl implements StudyStatisticsService {
@Autowired
private StudyRecordMapper studyRecordMapper;
public Map<String, Object> getStudyStatistics(String memberId, String courseId) {
Map<String, Object> statistics = new HashMap<>();
// 1. 总学习时长
Integer totalTime = studyRecordMapper.sumWatchTime(memberId, courseId);
statistics.put("totalTime", totalTime);
// 2. 最近7天的学习记录
List<DailyStudyVO> dailyStudy = studyRecordMapper.getDailyStudyData(memberId, courseId);
statistics.put("dailyStudy", dailyStudy);
// 3. 课程完成率
Double progress = studyRecordMapper.getCourseProgress(memberId, courseId);
statistics.put("progress", progress);
// 4. 获取学习时段分布
List<TimeSlotVO> timeSlots = studyRecordMapper.getStudyTimeSlots(memberId, courseId);
statistics.put("timeSlots", timeSlots);
return statistics;
}
}
这些模块的实现涉及到了:
- 数据库设计
- 业务逻辑实现
- 缓存使用
- 事务管理
- 安全控制
- 性能优化
每个模块都需要考虑:
- 数据一致性
- 并发处理
- 异常处理
- 日志记录
- 权限控制
- 数据验证
通过这些模块的实现,可以为用户提供完整的在线学习体验。
视频点播与订单管理模块详解
一、视频点播模块
1. 数据模型设计
-- 视频信息表
CREATE TABLE edu_video (
id BIGINT PRIMARY KEY,
course_id BIGINT COMMENT '课程ID',
chapter_id BIGINT COMMENT '章节ID',
title VARCHAR(100) COMMENT '视频标题',
video_source_id VARCHAR(100) COMMENT '云端视频资源ID',
video_original_name VARCHAR(100) COMMENT '原始文件名',
play_count BIGINT COMMENT '播放次数',
duration FLOAT COMMENT '视频时长(秒)',
size BIGINT COMMENT '视频大小(字节)',
status TINYINT COMMENT '视频状态(0:未上传 1:转码中 2:正常)',
version INTEGER COMMENT '乐观锁版本号',
create_time DATETIME,
update_time DATETIME
);
-- 视频播放记录表
CREATE TABLE edu_video_play_log (
id BIGINT PRIMARY KEY,
member_id BIGINT COMMENT '会员ID',
video_id BIGINT COMMENT '视频ID',
ip VARCHAR(50) COMMENT '播放IP',
play_time INTEGER COMMENT '播放时长(秒)',
create_time DATETIME
);
2. 核心功能实现
2.1 视频上传与转码
@Service
public class VodServiceImpl implements VodService {
@Value("${aliyun.vod.accessKeyId}")
private String accessKeyId;
@Value("${aliyun.vod.accessKeySecret}")
private String accessKeySecret;
@Async
public String uploadVideo(MultipartFile file) {
try {
// 1. 创建VOD客户端
DefaultAcsClient client = initVodClient();
// 2. 获取文件信息
String fileName = file.getOriginalFilename();
String title = fileName.substring(0, fileName.lastIndexOf("."));
InputStream inputStream = file.getInputStream();
// 3. 创建上传请求
CreateUploadVideoRequest request = new CreateUploadVideoRequest();
request.setTitle(title);
request.setFileName(fileName);
// 4. 获取上传地址和凭证
CreateUploadVideoResponse response = client.getAcsResponse(request);
String videoId = response.getVideoId();
String uploadAddress = response.getUploadAddress();
String uploadAuth = response.getUploadAuth();
// 5. 执行上传
UploadVideoRequest uploadRequest = new UploadVideoRequest(uploadAddress, uploadAuth);
uploadRequest.setInputStream(inputStream);
UploadVideoImpl uploader = new UploadVideoImpl();
UploadVideoResponse uploadResponse = uploader.upload(uploadRequest);
// 6. 设置转码模板
SetDefaultTranscodeTemplateRequest transcodeRequest =
new SetDefaultTranscodeTemplateRequest();
transcodeRequest.setVideoId(videoId);
transcodeRequest.setTranscodeTemplateGroupId("your-template-group-id");
client.getAcsResponse(transcodeRequest);
return videoId;
} catch (Exception e) {
log.error("视频上传失败", e);
throw new CustomException(20001, "视频上传失败");
}
}
private DefaultAcsClient initVodClient() {
DefaultProfile profile = DefaultProfile.getProfile(
"cn-shanghai",
accessKeyId,
accessKeySecret
);
return new DefaultAcsClient(profile);
}
}
2.2 视频播放授权
@Service
public class VideoPlayServiceImpl implements VideoPlayService {
@Autowired
private RedisTemplate redisTemplate;
public VideoPlayAuthVO getPlayAuth(String videoId) {
try {
// 1. 创建VOD客户端
DefaultAcsClient client = initVodClient();
// 2. 创建请求
GetVideoPlayAuthRequest request = new GetVideoPlayAuthRequest();
request.setVideoId(videoId);
// 3. 获取播放凭证
GetVideoPlayAuthResponse response = client.getAcsResponse(request);
String playAuth = response.getPlayAuth();
// 4. 获取视频信息
GetVideoInfoResponse videoInfo = getVideoInfo(client, videoId);
// 5. 组装返回数据
VideoPlayAuthVO authVO = new VideoPlayAuthVO();
authVO.setPlayAuth(playAuth);
authVO.setVideoId(videoId);
authVO.setTitle(videoInfo.getVideo().getTitle());
authVO.setDuration(videoInfo.getVideo().getDuration());
return authVO;
} catch (Exception e) {
log.error("获取播放凭证失败", e);
throw new CustomException(20001, "获取播放凭证失败");
}
}
@Async
public void recordPlayLog(VideoPlayLogVO playLog) {
// 1. 记录播放日志
VideoPlayLog log = new VideoPlayLog();
BeanUtils.copyProperties(playLog, log);
playLogMapper.insert(log);
// 2. 更新播放次数
String key = "video:playCount:" + playLog.getVideoId();
redisTemplate.opsForValue().increment(key);
}
}
2.3 断点续播实现
@Service
public class VideoProgressServiceImpl implements VideoProgressService {
@Autowired
private RedisTemplate redisTemplate;
private final String PROGRESS_KEY = "video:progress:";
public void saveProgress(String memberId, String videoId, Float progress) {
String key = PROGRESS_KEY + memberId;
redisTemplate.opsForHash().put(key, videoId, progress);
}
public Float getProgress(String memberId, String videoId) {
String key = PROGRESS_KEY + memberId;
Object progress = redisTemplate.opsForHash().get(key, videoId);
return progress == null ? 0f : Float.valueOf(progress.toString());
}
@Scheduled(cron = "0 0 1 * * ?") // 每天凌晨1点执行
public void syncProgressToDb() {
Set<String> keys = redisTemplate.keys(PROGRESS_KEY + "*");
for (String key : keys) {
String memberId = key.substring(PROGRESS_KEY.length());
Map<Object, Object> progress = redisTemplate.opsForHash().entries(key);
// 批量更新到数据库
List<VideoProgress> progressList = new ArrayList<>();
progress.forEach((videoId, prog) -> {
VideoProgress vp = new VideoProgress();
vp.setMemberId(memberId);
vp.setVideoId(videoId.toString());
vp.setProgress(Float.valueOf(prog.toString()));
progressList.add(vp);
});
videoProgressMapper.batchUpdate(progressList);
}
}
}
二、订单管理模块
1. 数据模型设计
-- 订单表
CREATE TABLE edu_order (
id BIGINT PRIMARY KEY,
order_no VARCHAR(64) COMMENT '订单号',
course_id BIGINT COMMENT '课程ID',
course_title VARCHAR(100) COMMENT '课程名称',
member_id BIGINT COMMENT '会员ID',
nickname VARCHAR(50) COMMENT '会员昵称',
mobile VARCHAR(11) COMMENT '会员手机',
total_fee DECIMAL(10,2) COMMENT '订单金额',
pay_type TINYINT COMMENT '支付类型(1:微信 2:支付宝)',
status TINYINT COMMENT '订单状态(0:未支付 1:已支付)',
is_deleted BOOLEAN COMMENT '逻辑删除',
create_time DATETIME,
update_time DATETIME
);
-- 支付日志表
CREATE TABLE edu_pay_log (
id BIGINT PRIMARY KEY,
order_no VARCHAR(64) COMMENT '订单号',
pay_time DATETIME COMMENT '支付完成时间',
total_fee DECIMAL(10,2) COMMENT '支付金额',
transaction_id VARCHAR(64) COMMENT '交易流水号',
trade_state VARCHAR(20) COMMENT '交易状态',
pay_type TINYINT COMMENT '支付类型',
attr TEXT COMMENT '其他属性',
create_time DATETIME
);
2. 核心功能实现
2.1 订单创建
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private CourseService courseService;
@Autowired
private MemberService memberService;
@Transactional(rollbackFor = Exception.class)
public String createOrder(String courseId) {
// 1. 获取当前用户信息
String memberId = JwtUtils.getMemberIdByJwtToken(request);
Member member = memberService.getById(memberId);
// 2. 获取课程信息
CourseVO course = courseService.getCourseInfo(courseId);
// 3. 判断是否已购买
QueryWrapper<Order> wrapper = new QueryWrapper<>();
wrapper.eq("course_id", courseId);
wrapper.eq("member_id", memberId);
wrapper.eq("status", 1); // 已支付
Integer count = baseMapper.selectCount(wrapper);
if (count > 0) {
throw new CustomException(20001, "已购买该课程");
}
// 4. 创建订单
Order order = new Order();
order.setOrderNo(OrderNoUtil.getOrderNo()); // 生成订单号
order.setCourseId(courseId);
order.setCourseTitle(course.getTitle());
order.setMemberId(memberId);
order.setNickname(member.getNickname());
order.setMobile(member.getMobile());
order.setTotalFee(course.getPrice());
order.setStatus(0); // 未支付
baseMapper.insert(order);
return order.getOrderNo();
}
}
2.2 微信支付实现
@Service
public class WxPayServiceImpl implements WxPayService {
@Value("${wx.pay.appId}")
private String appId;
@Value("${wx.pay.mchId}")
private String mchId;
@Value("${wx.pay.mchKey}")
private String mchKey;
@Value("${wx.pay.notifyUrl}")
private String notifyUrl;
public Map createNative(String orderNo) {
try {
// 1. 根据订单号获取订单信息
Order order = orderService.getByOrderNo(orderNo);
// 2. 使用微信支付SDK创建支付参数
Map<String, String> params = new HashMap<>();
params.put("appid", appId);
params.put("mch_id", mchId);
params.put("nonce_str", WXPayUtil.generateNonceStr());
params.put("body", order.getCourseTitle());
params.put("out_trade_no", orderNo);
params.put("total_fee", order.getTotalFee().multiply(new BigDecimal("100")).longValue()+"");
params.put("spbill_create_ip", IpUtil.getIpAddr(request));
params.put("notify_url", notifyUrl);
params.put("trade_type", "NATIVE");
// 3. 发送请求到微信支付
HttpClient client = new HttpClient("https://api.mch.weixin.qq.com/pay/unifiedorder");
client.setXmlParam(WXPayUtil.generateSignedXml(params, mchKey));
client.setHttps(true);
client.post();
// 4. 获取返回结果
String xml = client.getContent();
Map<String, String> resultMap = WXPayUtil.xmlToMap(xml);
// 5. 封装返回结果
Map map = new HashMap<>();
map.put("orderNo", orderNo);
map.put("totalFee", order.getTotalFee());
map.put("resultCode", resultMap.get("result_code"));
map.put("codeUrl", resultMap.get("code_url")); // 二维码地址
return map;
} catch (Exception e) {
log.error("微信支付二维码生成失败", e);
throw new CustomException(20001, "微信支付二维码生成失败");
}
}
@Transactional(rollbackFor = Exception.class)
public void notifyPayResult(HttpServletRequest request) {
try {
// 1. 获取回调结果
InputStream inputStream = request.getInputStream();
String xml = IOUtils.toString(inputStream, "UTF-8");
Map<String, String> notifyMap = WXPayUtil.xmlToMap(xml);
// 2. 验证签名
if (WXPayUtil.isSignatureValid(notifyMap, mchKey)) {
// 3. 验证支付结果
if ("SUCCESS".equals(notifyMap.get("result_code"))) {
// 4. 处理订单
String orderNo = notifyMap.get("out_trade_no");
orderService.updateOrderStatus(orderNo, notifyMap);
// 5. 记录支付日志
PayLog payLog = new PayLog();
payLog.setOrderNo(orderNo);
payLog.setPayTime(new Date());
payLog.setTotalFee(new BigDecimal(notifyMap.get("total_fee")).divide(new BigDecimal("100")));
payLog.setTransactionId(notifyMap.get("transaction_id"));
payLog.setTradeState(notifyMap.get("result_code"));
payLog.setPayType(1); // 微信支付
payLog.setAttr(JSON.toJSONString(notifyMap));
payLogMapper.insert(payLog);
}
}
} catch (Exception e) {
log.error("微信支付回调处理失败", e);
throw new CustomException(20001, "微信支付回调处理失败");
}
}
}
2.3 订单状态管理
@Service
public class OrderStatusServiceImpl implements OrderStatusService {
@Autowired
private RedisTemplate redisTemplate;
private final String ORDER_TIMEOUT_KEY = "order:timeout:";
@Transactional(rollbackFor = Exception.class)
public void updateOrderStatus(String orderNo, Map<String, String> payResult) {
// 1. 更新订单状态
Order order = orderService.getByOrderNo(orderNo);
order.setStatus(1); // 已支付
orderService.updateById(order);
// 2. 清除订单超时记录
redisTemplate.delete(ORDER_TIMEOUT_KEY + orderNo);
// 3. 发送订单完成事件
OrderCompleteEvent event = new OrderCompleteEvent(this, order);
applicationEventPublisher.publishEvent(event);
}
@Scheduled(cron = "0 */1 * * * ?") // 每分钟执行一次
public void checkOrderTimeout() {
// 1. 获取所有超时订单key
Set<String> keys = redisTemplate.keys(ORDER_TIMEOUT_KEY + "*");
for (String key : keys) {
String orderNo = key.substring(ORDER_TIMEOUT_KEY.length());
Order order = orderService.getByOrderNo(orderNo);
// 2. 检查订单是否已超过30分钟
if (order != null && order.getStatus() == 0) {
long createTime = order.getCreateTime().getTime();
if (System.currentTimeMillis() - createTime > 30 * 60 * 1000) {
// 3. 取消订单
cancelOrder(orderNo);
}
}
}
}
@Transactional(rollbackFor = Exception.class)
public void cancelOrder(String orderNo) {
// 1. 更新订单状态
Order order = orderService.getByOrderNo(orderNo);
order.setStatus(2); // 已取消
orderService.updateById(order);
// 2. 清除订单超时记录
redisTemplate.delete(ORDER_TIMEOUT_KEY + orderNo);
// 3. 发送订单取消事件
OrderCancelEvent event = new OrderCancelEvent(this, order);
applicationEventPublisher.publishEvent(event);
}
}
2.4 订单统计分析
@Service
public class OrderStatisticsServiceImpl implements OrderStatisticsService {
public Map<String, Object> getOrderStatistics(String begin, String end) {
Map<String, Object> statistics = new HashMap<>();
// 1. 订单总数和金额
Map<String, Object> totalMap = orderMapper.getTotalStatistics(begin, end);
statistics.put("totalOrders", totalMap.get("count"));
statistics.put("totalAmount", totalMap.get("amount"));
// 2. 各状态订单数量
List<Map<String, Object>> statusList = orderMapper.getOrderStatusStatistics(begin, end);
statistics.put("statusStatistics", statusList);
// 3. 每日订单统计
List<Map<String, Object>> dailyList = orderMapper.getDailyOrderStatistics(begin, end);
statistics.put("dailyStatistics", dailyList);
// 4. 支付方式分布
List<Map<String, Object>> payTypeList = orderMapper.getPayTypeStatistics(begin, end);
statistics.put("payTypeStatistics", payTypeList);
return statistics;
}
public Map<String, Object> getCourseOrderStatistics(String courseId) {
Map<String, Object> statistics = new HashMap<>();
// 1. 课程订单总数和金额
Map<String, Object> totalMap = orderMapper.getCourseOrderStatistics(courseId);
statistics.put("totalOrders", totalMap.get("count"));
statistics.put("totalAmount", totalMap.get("amount"));
// 2. 课程每日订单统计
List<Map<String, Object>> dailyList = orderMapper.getCourseDailyOrderStatistics(courseId);
statistics.put("dailyStatistics", dailyList);
// 3. 购买用户画像分析
Map<String, Object> userProfile = orderMapper.getCourseUserProfile(courseId);
statistics.put("userProfile", userProfile);
return statistics;
}
}
3. 关键技术点
-
视频点播模块:
- 使用阿里云VOD服务
- 视频转码与加密
- 播放鉴权
- 断点续播
- 播放统计
-
订单管理模块:
- 订单号生成
- 微信支付集成
- 支付结果异步通知
- 订单状态管理
- 订单超时处理
- 统计分析
4. 性能优化
- 视频播放优化:
@Configuration
public class VideoPlayConfig {
@Bean
public LoadingCache<String, String> playAuthCache() {
return CacheBuilder.newBuilder()
.maximumSize(10000)
.expireAfterWrite(1, TimeUnit.HOURS)
.build(new CacheLoader<String, String>() {
@Override
public String load(String videoId) throws Exception {
return vodService.getPlayAuth(videoId);
}
});
}
}
- 订单并发处理:
@Configuration
public class OrderConfig {
@Bean
public RateLimiter orderRateLimiter() {
return RateLimiter.create(100.0); // 每秒处理100个订单
}
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer().setAddress("redis://localhost:6379");
return Redisson.create(config);
}
}
这些模块的实现需要注意:
-
安全性
- 视频防盗链
- 支付安全
- 订单数据安全
-
可靠性
- 视频上传重试
- 支付结果确认
- 订单状态一致性
-
性能
- 视频加载优化
- 订单并发处理
- 缓存使用
-
可维护性
- 代码模块化
- 异常处理
- 日志记录

被折叠的 条评论
为什么被折叠?



