一,点击左边菜单栏的背景色
1,效果预览
2,将 curNav 和当前的点击对象添加到modal。
mv.addObject("curNav","uhome");
mv.addObject("curNav","uinfo");
mv.addObject("curNav","upassword");
mv.addObject("curNav","ucomment");
3,前台去判断 curNav 是否存在且是当前的点击对象,是就添加背景色,不是就维持原来的样式。
<li class="<#if curNav?? && curNav =='uhome'>user-menu-nav-cur<#else>user-menu-nav</#if>">我的课程<span>></span></li>
二,学习记录的加载
1,只有登录的用户才有学习记录。
2,通过当前登录用户的 userId, 获取 user_course_section表 t 中当前登录的用户的学习记录,对数据做关联查询,关联course c 表,section s 表。查询对应的课程的名称和对应的章节的名称,通过t表的 courseId,t表的 sectionId 建立与 course表,courseSection表的关联。
<select id="queryUserCourse" parameterType="com.langshan.onlineschoollangshan.domain.UserCourseSection" resultMap="bean_map">
SELECT t.id,t.courseId,t.sectionId,
c.name as courseName,s.name as sectionName,
t.updateAt
FROM user_course_section t,course c,course_section s
WHERE t.courseId = c.id
AND t.sectionId = s.id
AND t.userId = #{userId}
</select>
3,返回的学习记录的 list 数据加载到modal中。前台去遍历就可以了。
@RequestMapping("uhome")
public ModelAndView uhome(){
ModelAndView mv = new ModelAndView("zY/user_home");
mv.addObject("curNav","uhome");
//获取当前登录用户的id
Long userId = ShiroContext.getSessionUser().getId();
//做关联查询 查出对应的课程名称和章节的名称
UserCourseSection userCourseSection = new UserCourseSection();
userCourseSection.setUserId(userId);
List<UserCourseSection> list = userCourseSectionService.queryUserCourse(userCourseSection);
mv.addObject("studyRecord",list);
return mv;
}
4,查询对应字段的意义
查询 t表 courseId:展示课程名称时,可以点击请求到课程详情页面。
<a href="${base}/course/${item.courseId!}">${item.courseName!}</a>
查询 t表 sectionId:展示小节名称时,点击可以请求到课程学习页面。
<a href="${base}/learn/${item.sectionId!}">
<span class="continue-btn"style="margin-left: 50px">继续学习</span>
</a>
t表的updateAt:展示最近一次更新学习记录表数据的时间。最近一次学习该课程小节的时间。
<span>${item.updateAt?string('yyyy-MM-dd')}</span>
5,关联查询要在返回的 resultMap 中 domain 中添加对应的字段,否则数据查询不出来。
三,学习记录的创建或更新 :学习页面与学习记录表的开发
1,学习记录表记录的是哪个登录的用户 userId,学习哪个课程 courseId,哪个小节的数据 sectionId,所以
当登录的用户
点击哪个课程
开始学习,学习课程的小节
到这个页面,学习记录的数据才会被创建
2,先根据当前登录的用户的 id 和小节数据的 courseId去查学习记录表。(章节数据共用一个数据库中的表,章节数据创建在同一个课程下,所以每一条章节的数据都有courseId)
为了避免查询学习记录时受到垃圾数据的影响,通过 id 降序排序,只查一条数据
<select id="queryUserCourseSection" parameterType="com.langshan.onlineschoollangshan.domain.UserCourseSection" resultMap="bean_map">
SELECT
<include refid="all_columns" />
FROM user_course_section
WHERE userId = #{userId}
AND courseId = #{courseId}
ORDER BY id DESC
LIMIT 0,1
</select>
4,如果查得到学习记录说明该用户曾学过该课程,就去更新现学的对应的小节的数据。如果该用户没有该课程的学习记录,就去创建这条学习记录。设置不为空的字段 status 和 rate。
public void mergeUserCourseSection(CourseSection courseSection){
//去查登录用户的学习记录
if (ShiroContext.isLogin()){
Long userId = ShiroContext.getSessionUser().getId();
UserCourseSection userCourseSection = new UserCourseSection();
userCourseSection.setUserId(userId);
userCourseSection.setCourseId(courseSection.getCourseId());
UserCourseSection curUserCourseSection = userCourseSectionService.queryUserCourseSection(userCourseSection);
if (curUserCourseSection == null){
//如果数据库中没有获取到该登录用户的课程学习记录
//创建学习记录
userCourseSection.setSectionId(courseSection.getId());
userCourseSection.setStatus(0L);
userCourseSection.setRate(0L);
userCourseSectionService.create(userCourseSection);
}else {
//获取到学习记录,更新现学的小节的数据
curUserCourseSection.setSectionId(courseSection.getId());
userCourseSectionService.update(curUserCourseSection);
}
}
创建的是传入数据库中查询为空的对象。
更新的是从数据库中可以获取到学习记录的,更新现学的小节的数据。
名字长容易搞混
5,调用 service 中的create方法,update方法,设置updateAt,把当前时间设置为创建/更新学习记录的时间。这样,去加载学习记录的时间才有数据。
public void create(UserCourseSection entity){
entity.setCreateAt(new Date());
entity.setUpdateAt(new Date());
entityDao.create(entity);
}
public void update(UserCourseSection entity){
entity.setCreateAt(new Date());
entity.setUpdateAt(new Date());
entityDao.update(entity);
}
在学习第四部分之前:
点击请求到课程的详情的controller
课程详情的controller返回一个页面
课程详情的controller,将sectionId对应的章节数据加载到modal中,在页面中遍历出来,点击请求到 learn controller
返回 learn页面通过sectionId获取对应小节数据的videoUrl,播放视频
四,课程详情 controller 和 user_course_section表的开发:
对于已经登录的用户如果学习过该课程,打开课程的详情页面
点击开始学习
应播放对应的小节数据的videoUrl
CourseSection courseSection = null;
if (ShiroContext.isLogin()){
//登录的用户去查学习记录表,查得到就把对应的小节数据加载到modal,learn页面根据小节的数据的videoUrl去播放
UserCourseSection userCourseSection = new UserCourseSection();
userCourseSection.setUserId(ShiroContext.getSessionUser().getId());
userCourseSection.setCourseId(courseId);
UserCourseSection resultUserCourseSection = userCourseSectionService.queryUserCourseSection(userCourseSection);
if (resultUserCourseSection != null){
CourseSection curCourseSection = courseSectionService.getById(resultUserCourseSection.getSectionId());
mv.addObject("curCourseSection",curCourseSection);
}
2,如果用户没有登录,或者没有学习过该课程,就默认将遍历出来的第1章第1节的数据加载到model中。
Map<Long, chapterSectionBean> chapterMap = courseSectionService.queryChapterSection(courseId);
mv.addObject("chapterMap",chapterMap);
if (courseSection == null){
Iterator<chapterSectionBean> it = chapterMap.values().iterator();
if (it.hasNext()) {
chapterSectionBean firstChapter = it.next();
if (firstChapter != null) {
mv.addObject("curSection", firstChapter.getSections().get(0));
}
}
}
如何得知该用户有没有学习过该课程?queryUserCourseSection方法,传入登录用户的id和当前课程的id,查询数据库学习记录表的数据。
<select id="queryUserCourseSection" parameterType="com.langshan.onlineschoollangshan.domain.UserCourseSection" resultMap="bean_map">
SELECT
<include refid="all_columns" />
FROM user_course_section
WHERE userId = #{userId}
AND courseId = #{courseId}
ORDER BY id DESC
LIMIT 0,1
</select>
总结:如果查询登录用户的学习记录表,学习过该课程那么有返回的学习记录。就将对应的sectionId的章节数据加载到modal,前台去遍历小节数据的id并请求learn页面,再根据对应小节的videoUrl去播放,如果没有登录或者没有学习记录的用户,就默认学习课程的第一章第一节的数据。
如果登录的用户第一次学习该课程下的小节,学习记录也会被创建到数据库,并在我的课程页面加载出来。
五,修改个人信息
1,通过菜单栏的 a标签请求uinfo Controller返回一个页面,实现页面结构。通过当前登录用户的id,getById去数据库查询对应的用户的数据,加载到modal。
前台去遍历数据,处理图片数据。(有详细的讲处理图片的笔记,这里不讲了)
<a href="${base}/uinfo">
<li class="<#if curNav?? && curNav =='uinfo'>user-menu-nav-cur<#else>user-menu-nav</#if>">个人信息<span>></span></li>
</a>
@RequestMapping("uinfo")
public ModelAndView uinfo(){
ModelAndView mv = new ModelAndView("zY/user_info");
mv.addObject("curNav","uinfo");
//获取修改前的数据
Long userId = ShiroContext.getSessionUser().getId();
AuthUser authUser = authUserService.getById(userId);
if (authUser != null){
mv.addObject("authUser",authUser);
}
return mv;
}
2,把修改后的数据ajaxSubmit提交到后台。个人数据更新成功或失败都通过id传入信息。
function doSubmit() {
$('#uinfoForm').ajaxSubmit({
dataType:'json',
success:function (resp) {
if (resp.errcode == 0){
_os.alertMsg("个人数据修改成功")
}else {
_os.alertMsg("个人数据修改失败")
}
}
})
}
3,后台根据当前用户的id去更新数据。
@RequestMapping("saveInfo")
@ResponseBody
public String saveInfo(AuthUser authUser,String userHeader){
//对图片数据的处理
if (StringUtils.isNotEmpty(userHeader)){
Long id = attachmentService.createAttachment("userHeader",userHeader);
authUser.setHeader(id.toString());
}
//修改后的个人的数据通过当前用户的id去更新
Long id = ShiroContext.getSessionUser().getId();
authUser.setId(id);
authUserService.update(authUser);
return JsonView.successJson();
}
4,mapper中去掉username动态的sql,username对于登录时要一致,注册时不可重复,查看时不可修改。
六,修改密码信息
1,通过菜单栏的a标签请求 upassword Controller返回一个页面,实现页面结构。
<a href="${base}/upassword">
<li class="<#if curNav?? && curNav =='upassword'>user-menu-nav-cur<#else>user-menu-nav</#if>">修改密码<span>></span></li>
</a>
@RequestMapping("upassword")
public ModelAndView upassword(){
ModelAndView mv = new ModelAndView("zY/user_password");
mv.addObject("curNav","upassword");
return mv;
}
2,前端:提交前旧密码不为空。新密码和确认密码要相同。(与注册时一样。)否则传入失败信息,数据回滚。
3个密码都经过64位的hash编码并设置到输入框,再提交到后台。
在点击提交的函数中处理,不要页面加载完成以后就处理。
function doSubmit() {
let username = '${user.username}'
let oldPassword = $('#oldPassword').val()
let password = $('#password').val()
let rePassword = $('#rePassword').val()
if (_os.isEmpty(oldPassword)) {
_os.alertMsg("旧密码不能为空")
return;
}
if (password != rePassword) {
_os.alertMsg("新密码于确认密码要相同")
return;
}
oldPassword = sha256.hmac(username, oldPassword)
$('#oldPassword').val(oldPassword)
password = sha256.hmac(username, password)
$('#password').val(password)
rePassword = sha256.hmac(username, rePassword)
$('#rePassword').val(rePassword)
$('#passwordForm').ajaxSubmit({
dataType: 'json',
success: function (resp) {
if (resp.errcode == 0) {
_os.alertMsg("修改成功")
} else {
_os.alertMsg(resp.message)
}
}
})
}
3,Controller中判断前端提交过来的3个密码不为空。
获取当前登录用户的数据库中的信息。
旧密码的密文与数据库的密文一致。新密码与确认密码一致。就将新密码的密文保存到数据库。
@RequestMapping("savePassword")
@ResponseBody
public String savePassword(String oldPassword,String password,String rePassword){
if(StringUtils.isEmpty(oldPassword)|| StringUtils.isEmpty(password)||StringUtils.isEmpty(rePassword)){
return JsonView.failureJson("密码不能为空");
}
AuthUser authUser = ShiroContext.getSessionUser();
authUser = this.authUserService.getById(authUser.getId());
oldPassword = HashUtil.hmacSha256(oldPassword,authUser.getSalt());
//旧密码的密文与数据库的密文一致。
if(authUser.getPassword().equals(oldPassword)){
//新密码与确认密码一致。
if(password.equals(rePassword)){
//将新密码的密文保存到数据库。
password = HashUtil.hmacSha256(password,authUser.getSalt());
authUser.setPassword(password);
this.authUserService.update(authUser);
return JsonView.successJson();
}else{
return JsonView.failureJson("新密码和确认密码不一致");
}
}else{
return JsonView.failureJson("旧密码输入错误");
}
}
4,如果改密码改忘了,直接去数据库重置密码,文档中有。
七,发给我的评论
通过菜单栏的 a标签请求ucomment Controller返回一个页面。
获取当前登录用户的用户名,查询course comment表的toUsernane,查询发给我的评论。
<a href="${base}/ucomment">
<li class="<#if curNav?? && curNav =='ucomment'>user-menu-nav-cur<#else>user-menu-nav</#if>">我的评论<span>></span></li>
</a>
@RequestMapping("ucomment")
public ModelAndView ucomment(){
ModelAndView mv = new ModelAndView("zY/user_comment");
mv.addObject("curNav","ucomment");
String username = ShiroContext.getSessionUsername();
List commentList = courseCommentService.queryUserCourseComment(username);
mv.addObject("commentList",commentList);
return mv;
}
2,queryUserCourseComment方法,查询课程评论表中,发给我的评论。
<select id="queryUserCourseComment" parameterType="java.lang.String" resultMap="bean_map">
SELECT
<include refid="all_columns" />
FROM course_comment
WHERE to_username = #{username}
ORDER BY id DESC
</select>
3,实现页面结构
4,引用的内容也显示出来,先判断被引用的内容存在且不为空才显示。
<#if item.refContent?? && item.refContent != ''>
<div style="padding: 5px; background-color: #f6f6f6;">
<span>${item.refContent!}</span>
</div>
</#if>