頭部登錄狀態
shiro標籤的引用
由於shiro標籤不是html的原生標籤,所有我們需要先引入一個額外的依賴,shiro的標籤庫(thymeleaf的拓展標籤)。
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
依賴添加好之後,然後,我們需要在com.fly.config.ShiroConfig
中初始化一下,注入對應的Bean, 頁面才能渲染出來
//用於thymeleaf模板使用shiro標籤,shiro方言標籤
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
然後在需要使用shiro標籤的html 文件的頭部添加
<html xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
添加好之後,就可以使用<shiro:user></shiro:user>
將要權限控制的內容包起來,當然shiro 標籤還有很多
用戶信息存到session中
用戶登錄成功之後需要將用戶的信息保存的session中。我們只需要在用戶認證的方法中com.fly.shiro.OAuth2Realm
類的doGetAuthenticationInfo
方法中加上如下語句:
// 將登陸信息放在session
SecurityUtils.getSubject().getSession().setAttribute("profile",profile);
經過如下如上設置我們就實現了登錄頭部狀態的控制
完善個人信息
用戶中心
用戶中心主要就兩個,我發的貼和我收藏的貼
我發的帖子,在這裏插入代碼片com.homework.controller.CenterController#center
查詢條件只有用戶id
QueryWrapper<Post> wrapper = new QueryWrapper<Post>().eq("user_id", getProfileId())
.orderByDesc("created");
IPage<Map<String, Object>> pageData = postService.pageMaps(page, wrapper);
request.setAttribute("pageData", pageData);
我的收藏
IPage<Map<String, Object>> pageData = userCollectionService.
pageMaps(page, new QueryWrapper<UserCollection>()
.eq("user_id", getProfileId()).orderByDesc("created"));
postService.join(pageData, "post_id");
request.setAttribute("pageData", pageData);
基本設置
- tab 切換回顯的問題,一個頁面有多個tab,如何讓在選中tab 之後刷新不丟失原來的tab選中選項呢?答案是在url 後面加上#,這相當於標籤的效果。
當前tab頁標籤定義:
在static/mods/user.js
有如下語句:
//顯示當前tab
if(location.hash){
element.tabChange('user', location.hash.replace(/^#/, ''));
}
element.on('tab(user)', function(){
var othis = $(this), layid = othis.attr('lay-id');
if(layid){
location.hash = layid;
}
});
我們在templates/common/static.html
放入瞭如下代碼,並修改下信息
<script th:inline="javascript" th:if="${session.profile != null}">
layui.cache.page = '';
layui.cache.user = {
username: [[${session.profile.username}]]
,uid: [[${session.profile.id}]]
,avatar: [[${session.profile.avatar}]]
,experience: 0
,sex: [[${session.profile.gender}]]
};
layui.config({
version: "3.0.0",
base: '/mods/' //這裏實際使用時,建議改成絕對路徑
}).extend({
fly: 'index'
}).use('fly');
</script>
通過上面代碼,我們把初始化layui的部分js代碼,頁面中很多class或id 的div 就擁有了特定的監聽或其他。其中就報貨截取url 獲取#後面的標籤用於tab 回顯功能,還有頭像的上傳功能封裝等。
加上了上面代碼之後你會發現經常會有個異常的彈框,那是瀏覽器控制檯發現去訪問/message/nums
的鏈接,在index.js 文件中找到 新消息通知,按照接口要求我們修改地址爲/user/message/nums
並在添加該接口
@ResponseBody
@PostMapping("/message/nums")
public Object getMessNums() {
Map<Object, Object> result = new HashMap<>();
result.put("status", 0);
result.put("count", 3);
return result;
}
- 頭像
頭像上傳接口com.fly.controller.CenterController#upload
,
頭像上傳核心代碼
String orgName = file.getOriginalFilename();
log.info("上傳文件名爲:" + orgName);
// 獲取後綴名
String suffixName = orgName.substring(orgName.lastIndexOf("."));
log.info("上傳的後綴名爲:" + suffixName);
// 文件上傳後的路徑
String filePath = Constant.uploadDir;
if ("avatar".equalsIgnoreCase(type)) {
fileName = "/avatar/avatar_" + getProfileId() + suffixName;
} else if ("post".equalsIgnoreCase(type)) {
fileName = "post/post_" + DateUtil.format(new Date(), DatePattern.PURE_DATETIME_MS_FORMAT) + suffixName;
}
File dest = new File(filePath + fileName);
// 檢查目錄是否存在
if (!dest.getParentFile().exists()) {
dest.getParentFile().mkdir();
}
//上傳文件
file.transferTo(dest);
log.info("上傳成功之後文件的路徑={}", dest.getPath());
目前上傳的圖片我們是到了一個指定目錄,然後nginx或者tomcat是可以讀取這個目錄的,所以可以通過url來訪問,一般來說我們把圖片上傳到雲存儲服務上。這裏先這樣弄了。
頭像上傳之後,更新shiro 中的頭像信息
AccountProfile profile = getProfile();
profile.setAvatar(url);
圖片上傳之後更新圖像信息
- 密碼
密碼重置接口com.fly.controller.CenterController#resetPwd
接口代碼比較簡單:
@ResponseBody
@PostMapping("/resetPwd")
public R restPwd(String nowpass, String pass) {
//查詢用戶
User user = userService.getById(getProfileId());
if (user == null || !nowpass.equals(user.getPassword())) {
return R.failed("密碼不正確");
}
user.setPassword(pass);
boolean result = userService.updateById(user);
return R.ok(result);
}
前端頁面在 /user/setting.html
中
發表,編輯博客
發表和編輯博客是同一個頁面,前端頁面展示
ajax 請求代碼:
$(function() {
layui.use('form', function() {
var form = layui.form;
//監聽提交
form.on('submit(post)', function (data) {
$.ajax({
url: '/user/post',
type: "POST",
data: data.field,
success: function (res) {
if (res.code == 0) {
layer.msg("操作成功");
setTimeout(function () {
location.href="/post/" + res.data;
}, 1000);
} else {
layer.msg(res.msg);
}
}
});
return false;
});
});
});
後臺接口在com.fly.controller.PostController
類中:
@ResponseBody
@PostMapping("/user/post")
public R postArticle(@Valid Post post, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return R.failed(bindingResult.getFieldError().getDefaultMessage());
}
// 新增文章
if (post.getId() == null) {
post.setUserId(getProfileId());
post.setModified(new Date());
post.setCreated(new Date());
post.setCommentCount(0);
post.setEditMode(Constant.EDIT_HTML_MODEL);
post.setLevel(0);
post.setRecommend(false);
post.setViewCount(0);
post.setVoteDown(0);
post.setVoteUp(0);
post.setStatus(Constant.NORMAL_STATUS);
} else {
Post tempPost = postService.getById(post.getId());
if (tempPost.getUserId().equals(getProfileId())) {
return R.failed("不是自己的帖子");
}
}
postService.saveOrUpdate(post);
// TODO: 2018/12/13 給所有訂閱人發送消息
return R.ok(post.getId());
}
顯示渲染博客
我們原先的顯示博客內容是通過如下標籤來顯示的
<div class="detail-body photos" th:text="${post.content}"></div>
但這樣顯示出來的內容明顯和我們預覽的不一樣,還需要經過layui的渲染,所以我們要加上一段js代碼。在body後面加上(templates/post/index.html:253)
<script>
layui.use(['fly','face'],function () {
var $ = layui.$
,fly=layui.fly;
// 如果你是採用模板自帶的編輯器,你需要開啓以下語句來解析
$('.detail-body').each(function () {
var othis = $(this), html = othis.html();
othis.html(fly.content(html));
});
博文回顯
用戶編輯完博客之後,點擊提交保存之後就可以 調用/user/post
進行博文回顯,博客的地址com.fly.controller.PostController#index
, 博文回顯主要博文,用戶,分類以及評論信息,核心代碼如下:
Map<String, Object> post = postService.getMap(new QueryWrapper<Post>().eq("id", id));
userService.join(post, "user_id");
categoryService.join(post, "category_id");
Assert.notNull(post, "該文章已被刪除");
req.setAttribute("post", post);
req.setAttribute("currentCategoryId", post.get("category_id"));
Page<Comment> page = new Page<>();
page.setCurrent(current);
page.setSize(size);
IPage<Map<String, Object>> pageData = commentService.pageMaps(page, new QueryWrapper<Comment>()
.eq("post_id", id)
.orderByDesc("created"));
userService.join(pageData, "user_id");
commentService.join(pageData, "parent_id");
req.setAttribute("pageData", pageData);
前端頁面在 templates/post/index.html
部分代碼如下:
頁面效果如下:
用戶主頁
博客評論功能
用戶評論表:
CREATE TABLE `comment` (
`id` bigint(32) NOT NULL AUTO_INCREMENT COMMENT '主鍵ID',
`content` longtext NOT NULL COMMENT '評論的內容',
`parent_id` bigint(32) DEFAULT NULL COMMENT '回覆的評論ID',
`post_id` bigint(32) NOT NULL COMMENT '評論的內容ID',
`user_id` bigint(32) NOT NULL COMMENT '評論的用戶ID',
`vote_up` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '“頂”的數量',
`vote_down` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '“踩”的數量',
`level` tinyint(2) unsigned NOT NULL DEFAULT '0' COMMENT '置頂等級',
`status` tinyint(2) DEFAULT NULL COMMENT '評論的狀態',
`created` datetime NOT NULL COMMENT '評論的時間',
`modified` datetime DEFAULT NULL COMMENT '評論的更新時間',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;
後端接口代碼在在這裏插入代碼片
@ResponseBody
@PostMapping("/user/post/comment")
public R commentAdd(@Valid Comment comment, BindingResult bindingResult) {
Post post = postService.getById(comment.getPostId());
Assert.isTrue(post != null, "該帖子已被刪除");
comment.setUserId(getProfileId());
comment.setCreated(new Date());
comment.setModified(new Date());
comment.setStatus(Constant.NORMAL_STATUS);
// TODO 記錄動作
// TODO 通知作者
commentService.save(comment);
return R.ok(null);
}
前端頁面在 templates/post/index.html
提交評論代碼如下:
配置異步請求登錄過濾器
在我們shiroConfig中,我們配置了非ajax的請求直接跳轉到登錄頁面,但是受限的ajax請求則不能處理。
如未登錄狀態下直接評論文檔,我們應該給出 請先登錄 的提示。
在shiro中有很多過濾器。其中org.apache.shiro.web.filter.authc.UserFilter
在此我們繼承UserFilter然後重寫redirectToLogin方法。
public class AuthFilter extends UserFilter {
@Override
protected void redirectToLogin(ServletRequest servletRequest, ServletResponse response) throws IOException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
// 異步請求要先登錄
String header = request.getHeader("X-Requested-With");
if (header != null && "XMLHttpRequest".equals(header)) {
Subject subject = SecurityUtils.getSubject();
if (!subject.isAuthenticated()) {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().print(JSONUtil.toJsonStr(R.failed("請先登錄!")));
} else {
super.redirectToLogin(servletRequest, response);
}
}
}
}
然後在com.fly.config.ShiroConfig
中注入AuthFilter 的Bean
@Bean
public AuthFilter authFilter(){
return new AuthFilter();
}
參考代碼:
https://github.com/XWxiaowei/FlyBlog/tree/v5-collection-center