显示用户信息面板
index.html以及很多页面上都有这个用户信息面板
开发用户信息面板
为了方便显示这个面板中的信息,我们新建一个UserVo的值对象类
我们先完成提问数量和收藏数量的查询和编写即可
所以新建一个UserVo类代码如下
@Data
//支持连缀书写
@Accessors(chain = true)
public class UserVo {
private Integer id;
private String username;
private String nickname;
//两个面板中显示的数据
//问题数量
private int questions;
//收藏数量
private int collections;
}
我们先从数据访问层查询这个用户开始
UserMapper接口中添加一个查询User基本信息的方法
@Select("select id,username,nickname from user " +
" where username=#{username}")
UserVo findUserVoByUsername(String username);
测试代码如下
@Autowired
UserMapper userMapper;
@Test
public void testUser(){
UserVo user=userMapper.findUserVoByUsername("st2");
System.out.println(user);
}
为了学习使用QueryWrapper来查询数量,我们在IQuestionService中编写一个根据用户id获得问题数量的方法
代码如下
Integer countQuestionsByUserId(Integer userId);
在QuestionServiceImpl类中实现这个方法
@Override
public Integer countQuestionsByUserId(Integer userId) {
//使用QueryWrapper查询数量的方法
QueryWrapper<Question> query=new QueryWrapper<>();
query.eq("user_id",userId);
query.eq("delete_status",0);
Integer count=questionMapper.selectCount(query);
//别忘了返回
return count;
}
上面查询的问题数量,实际上是为了让UserVo获得信息的准备工作
而UserVo的创建赋值需要在UserService中
所以完成IUserService接口中方法的编写代码如下
//查询当前登录用户信息面板的方法
UserVo currentUserVo();
这个接口方法的实现
UserServiceImpl类代码如下
@Autowired
IQuestionService questionService;
@Override
public UserVo currentUserVo() {
//获得登录用户名
String username=currentUsername();
//获得当前对象基本信息
UserVo user=userMapper.findUserVoByUsername(username);
Integer questions=questionService
.countQuestionsByUserId(user.getId());
user.setQuestions(questions);
//用户收藏数信息未做!!!
return user;
}
也可以进行测试
完成了业务逻辑层的编写下面开始编写控制层
UserController类中调用代码如下
//显示用户信息面板的控制层方法
@GetMapping("/me")
public R<UserVo> me(){
UserVo user=userService.currentUserVo();
return R.ok(user);
}
已经完成控制层的编码,下面就剩html和js了
首先编写index.html页面中vue信息的绑定
代码如下
<!--个人信息-->
<div id="userApp" class="container-fluid font-weight-light">
<div class="card">
<h5 class="card-header" v-text="user.nickname">陈某</h5>
<div class="card-body">
<div class="list-inline mb-1 ">
<a class="list-inline-item mx-3 my-1 text-center">
<div><strong>10</strong></div>
<div>回答</div>
</a>
<a class="list-inline-item mx-3 my-1 text-center" href="personal/myQuestion.html">
<div>
<strong v-text="user.questions">10</strong>
</div>
<div>提问</div>
</a>
<a class="list-inline-item mx-3 my-1 text-center" href="personal/collect.html">
<div>
<strong v-text="user.collections">10</strong>
</div>
<div>收藏</div>
</a>
<a class="list-inline-item mx-3 my-1 text-center" href="personal/task.html">
<div><strong>10</strong></div>
<div>任务</div>
</a>
</div>
</div>
</div>
</div>
下面开始写js文件
但是为了避免写完js文件在回到index.html文件中添加引用,我们先在index.html中写好引用即可
</body>
<script src="js/utils.js"></script>
<script src="js/index.js"></script>
<script src="js/tags_nav.js"></script>
<script src="js/user_info.js"></script>
</html>
在static文件夹中的js文件夹中创建user_info.js文件来绑定html文件中的内容
user_info.js代码如下
let userApp = new Vue({
el: "#userApp",
data: {
user: {}
},
methods: {
loadCurrentUser: function () {
$.ajax({
url: "/v1/users/me",
method: "get",
success: function (r) {
console.log(r)
if (r.code==OK) {
userApp.user=r.data;
}else{
console.log(r.message);
}
}
});
}
},
created: function () {
//页面加载完毕后立即调用loadCurrentUser方法
this.loadCurrentUser();
}
});
完成用户信息面板的复用
步骤1:
将我们刚刚编写的index.html页面的用户信息面板的div设置为TH模板
<div id="userApp" th:fragment="user_info"
class="container-fluid font-weight-light">
步骤2:
在create.html文件中找到对应的位置,套用模板
<div class="col-4 pb-2 ">
<div th:replace="index::user_info">
</div>
<!--热点数据代码略-->
</div>
步骤3:
create.html文件末尾引入js的支持
</body>
<script src="../js/utils.js"></script>
<script src="../js/tags_nav.js"></script>
<script src="../js/createQuestion.js"></script>
<script src="../js/user_info.js"></script>
</html>
稻草问答讲师回复首页
学生发布问题已经基本完成
下面是讲师回复
但是讲师回复的前提是讲师也要登录
讲师的登录显示的是一个和index.html页面很相似的内容的另一个页面
是index_teacher.html
显示讲师首页
复制static文件夹下的index_teacher.html到templates下
如果也开发一个控制器来显示讲师的主页
在HomeController中编写代码如下
//临时显示讲师主页的控制器方法
@GetMapping("/index_teacher.html")
public ModelAndView indexTeacher(){
return new ModelAndView("index_teacher");
}
编写成功后,我们虽然可以顺利访问老师的主页
但是从需求上讲,访问老师还是学生的首页是由登录用户的身份决定的
不能随意指定
所以我们要编写一个功能:按登录用户的身份显示不同的主页
步骤1:
因为我们现在将登录操作和权限管理交由了Spring-Security来处理
所以我们在完成本功能时仍然需要操作很多Spring-Security的API
在UserMapper中添加一个方法,按用户id查询这个用户的所有身份
以便保存掉Spring-Security中
代码如下
//按用户id查询用户的所有角色
@Select("select r.id,r.name " +
"from user u " +
"left join user_role ur on u.id=ur.user_id " +
"left join role r on r.id=ur.role_id " +
"where u.id=#{userId}")
List<Role> findUserRolesById(Integer id);
测试一下
@Autowired
UserMapper userMapper;
@Test
public void roles(){
List<Role> list=userMapper.findUserRolesById(1);
for(Role role:list){
System.out.println(role);
}
}
上面的信息查询出来是为了保存到Spring-Security的权限管理中的
我们在UserServiceImpl类中设置过登录用户的权限,现在要去这个方法中重构一下
添加角色信息,UserServiceImpl的getUserDetails方法
代码如下
@Override
public UserDetails getUserDetails(String username) {
//根据用户名获得用户对象
User user=userMapper.findUserByUsername(username);
//判断用户对象是否为空
if(user==null) {
//如果为空直接返回null
return null;
}
//如果不为空根据用户的id查询这个用户的所有权限
List<Permission> permissions=
userMapper.findUserPermissionsById(user.getId());
//将权限List中的权限转成数组方便赋值
String[] auths=new String[permissions.size()];
for(int i=0;i<auths.length;i++){
auths[i]=permissions.get(i).getName();
}
//读取用户的所有角色
List<Role> roles=userMapper.findUserRolesById(user.getId());
int j=auths.length;
//扩容上面的数组
auths= Arrays.copyOf(auths,
auths.length+roles.size());
//向数组内容中赋值
for(Role r:roles){
auths[j]=r.getName();
j++;
}
//创建UserDetails对象,并为他赋值
UserDetails ud= org.springframework.security.core.userdetails
.User.builder()
.username(user.getUsername())
.password(user.getPassword())
.accountLocked(user.getLocked()==1)//写==1是判断锁定
.disabled(user.getEnabled()==0)//写==0是判断不可用
.authorities(auths).build();
//最后返回UserDetails对象
return ud;
}
运行完上面的代码
Spring-Security的权限管理的字符串包含了权限相关的内容和角色相关的内容
以一个学生登录为例
这个数组的内容是:auths={"/index.html","/question/create",
“/question/uploadMultipleFile”,"/question/detail",“ROLE_STUDENT”}
下面我们就来实现根据不同的角色使HomeController中显示主页的控制器代码,返回不同的主页
HomeController类index方法代码如下
//声明两个常亮以便判断用户的角色
static final GrantedAuthority STUDENT =
new SimpleGrantedAuthority("ROLE_STUDENT");
static final GrantedAuthority TEACHER =
new SimpleGrantedAuthority("ROLE_TEACHER");
//显示首页
@GetMapping("/index.html")
//@AuthenticationPrincipal 注解后面跟Spring-Security的User类型参数
//表示需要Spring-Security将当前登录用户的权限信息赋值给User对象
//以便我们在方法中验证他的权限或身份
public ModelAndView index(
@AuthenticationPrincipal User user){
// 根据Spring-Security提供的用户判断权限,绝对返回哪个页面
if(user.getAuthorities().contains(STUDENT)){
return new ModelAndView("index");
}else if(user.getAuthorities().contains(TEACHER)){
return new ModelAndView("index_teacher");
}
return null;
}
复用讲师首页的内容
在index_teacher.html页面中做一些修改
首先Th的命名空间
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
使用模板替换导航栏
<!--引入标签的导航栏-->
<div class="container-fluid" th:replace="index::tags_nav">
<!--中间代码略-->
</div>
使用模板替换用户信息面板
<!--个人信息-->
<div th:replace="index::user_info"
class="container-fluid font-weight-light">
<!--中间代码略-->
</div>
引入依赖的js文件
</body>
<script src="js/utils.js"></script>
<script src="js/tags_nav.js"></script>
<script src="js/user_info.js"></script>
</html>
显示讲师问题列表
讲师首页显示的问题是学生向当前登录讲师提问的问题
所以和学生首页查询当前登录学生的提问不同,需要编写新的Sql语句查询讲师首页问题列表
QuestionMapper中添加新的方法代码如下
@Repository
public interface QuestionMapper extends BaseMapper<Question> {
@Select("SELECT q.* " +
" FROM question q" +
" LEFT JOIN user_question uq " +
" ON q.id=uq.question_id" +
" WHERE uq.user_id=#{userId} OR q.user_id=#{userId}" +
" ORDER BY q.createtime DESC")
List<Question> findTeachersQuestions(Integer userId);
}
可以测试一下
@Autowired
QuestionMapper questionMapper;
@Test
public void teacherQuestions(){
List<Question> list=
questionMapper.findTeachersQuestions(3);
for(Question question:list){
System.out.println(question);
}
}
然后开始编写业务逻辑层的接口
IQuestionService
//分页查询当前登录的老师问题的方法
PageInfo<Question> getQuestionsByTeacherName(
String username,Integer pageNum,Integer pageSize
);
编写接口的实现
QuestionServiceImpl
@Override
public PageInfo<Question> getQuestionsByTeacherName(
String username, Integer pageNum, Integer pageSize) {
if(pageNum == null)
pageNum=1;
if(pageSize == null)
pageSize=8;
//根据用户名查询用户对象
User user=userMapper.findUserByUsername(username);
//设置分页查询
PageHelper.startPage(pageNum,pageSize);
List<Question> questions=
questionMapper.findTeachersQuestions(user.getId());
//别忘了,要将问题列中的标签字符串转成标签的List
for(Question q: questions){
List<Tag> tags=tagNamesToTags(q.getTagNames());
q.setTags(tags);
}
return new PageInfo<Question>(questions);
}
控制层的调用
QuestionController
@GetMapping("/teacher")
@PreAuthorize("hasRole('ROLE_TEACHER')")
public R<PageInfo<Question>> teachers(
//声明权限是为了获得用户名的
@AuthenticationPrincipal User user,
Integer pageNum){
if(pageNum == null)
pageNum = 1;
Integer pageSize=8;
//调用业务逻辑层的方法
PageInfo<Question> pageInfo=questionService
.getQuestionsByTeacherName(
user.getUsername(),pageNum,pageSize
);
return R.ok(pageInfo);
}
index_teacher.html页面也可以使用th模板来复用问题列表
定义模板
在index.html页面中找到定义id为QuestionApp的div
修改代码如下
<div class="container-fluid" id="questionsApp"
th:fragment="questions_app">
套用模板
在index_teacher.html页面中找到对应的div编写复用代码
<div class="container-fluid"
th:replace="index::questions_app">
最后完成js文件的编写
可以复制index.js修改名称为index_teacher.js
修改ajax的调用路径即可
/*
显示登录讲师的问题列表
*/
let questionsApp = new Vue({
el:'#questionsApp',
data: {
questions:[],
pageInfo:{},
style:"fa-tasks",
navTitle:"我的任务"
},
methods: {
loadQuestions:function (pageNum) {
if(!pageNum){ //如果pageNum为空,默认页码为1
pageNum=1;
}
$.ajax({
url: '/v1/questions/teacher',
method: "GET",
data:{pageNum:pageNum},
success: function (r) {
console.log("成功加载数据");
console.log(r);
if(r.code === OK){
questionsApp.questions = r.data.list;
//调用计算持续时间的方法
questionsApp.updateDuration();
//调用显示所有按标签呈现的图片
questionsApp.updateTagImage();
questionsApp.pageInfo=r.data;
}
}
});
},
updateTagImage:function(){
let questions = this.questions;
for(let i=0; i<questions.length; i++){
//获得当前问题对象的所有标签的集合(数组)
let tags = questions[i].tags;
//js代码中特有的写法if(tags)
//相当于判断tags非空
if(tags){
//获取当前问题的第一个标签对应的图片文件路径
let tagImage = 'img/tags/'+tags[0].id+'.jpg';
console.log(tagImage);
//将这个文件路径保存到tagImage属性用,以便页面调用
questions[i].tagImage = tagImage;
}
}
},
updateDuration:function () {
let questions=this.questions;
for(let i=0;i<questions.length;i++){
//获得问题中的创建时间属性(毫秒数)
let createtime=new Date(questions[i].createtime).getTime();
//获得当前时间的毫秒数
let now=new Date().getTime();
//计算时间差(秒)
let durtaion=(now-createtime)/1000;
if(durtaion<60){
// 显示刚刚
//duration这个名字可以随便起,只要保证和页面上取的一样就行
questions[i].duration="刚刚";
}else if(durtaion<60*60){
// 显示XX分钟
questions[i].duration=
(durtaion/60).toFixed(0)+"分钟前";
}else if (durtaion<60*60*24){
//显示XX小时
questions[i].duration=
(durtaion/60/60).toFixed(0)+"小时前";
}else{
//显示XX天
questions[i].duration=
(durtaion/60/60/24).toFixed(0)+"天前";
}
}
}
},
created:function () {
console.log("执行了方法");
this.loadQuestions(1);
}
});
最后引用js依赖
</body>
<script src="js/utils.js"></script>
<script src="js/tags_nav.js"></script>
<script src="js/user_info.js"></script>
<script src="js/index_teacher.js"></script>
</html>
还可以尝试修改"我的问答""我的任务"的绑定
这里不强制要求