从零开始搭建博客02----发表博客个人中心

简介: 由于shiro标签不是html的原生标签,所有我们需要先引入一个额外的依赖,shiro的标签库(thymeleaf的拓展标签)。

头部登录状态

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 标签还有很多

9528c4ca80de55c9d9e7f74e2e968292_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ1MzQ4MDg=,size_16,color_FFFFFF,t_70.png

用户信息存到session中

用户登录成功之后需要将用户的信息保存的session中。我们只需要在用户认证的方法中com.fly.shiro.OAuth2Realm 类的doGetAuthenticationInfo 方法中加上如下语句:

//        将登陆信息放在session
        SecurityUtils.getSubject().getSession().setAttribute("profile",profile);

经过如下如上设置我们就实现了登录头部状态的控制

421c13c8bb1b88216668f823deaa5723_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ1MzQ4MDg=,size_16,color_FFFFFF,t_70.png


完善个人信息

用户中心

用户中心主要就两个,我发的贴和我收藏的贴

2c7a135e2ce5456aac98bf2c4ae03378_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ1MzQ4MDg=,size_16,color_FFFFFF,t_70.png

我发的帖子,在这里插入代码片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);

基本设置

1.tab 切换回显的问题,一个页面有多个tab,如何让在选中tab 之后刷新不丢失原来的tab选中选项呢?答案是在url 后面加上#,这相当于标签的效果。

e98d6e1d447daa43ec6f3a9ede8ae584_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ1MzQ4MDg=,size_16,color_FFFFFF,t_70.png

当前tab页标签定义:

3045d77ee4f4387b3e9d1cfffbfa4003_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ1MzQ4MDg=,size_16,color_FFFFFF,t_70.png

在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;
    }

4.头像

头像上传接口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);

图片上传之后更新图像信息

d59925c5842925dc659aa0900c9c3700_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ1MzQ4MDg=,size_16,color_FFFFFF,t_70.png

5.密码

密码重置接口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>

1

但这样显示出来的内容明显和我们预览的不一样,还需要经过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 部分代码如下:

f58bd3f5bc9bea295baeeb4e0f03a7e1_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ1MzQ4MDg=,size_16,color_FFFFFF,t_70.png

页面效果如下:

c6ecb49154e9db6603ed21a6397a91a5_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ1MzQ4MDg=,size_16,color_FFFFFF,t_70.png


53dc56744e932c6d1edcbf2b9ed099ef_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ1MzQ4MDg=,size_16,color_FFFFFF,t_70.png

用户主页

博客评论功能

用户评论表:

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 提交评论代码如下:

8f165a2911250e4d8e27c131ca490400_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ1MzQ4MDg=,size_16,color_FFFFFF,t_70.png

ajax 代码如下:

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));
        });
        var form = layui.form;
        //监听提交
        form.on('submit(comment)', function (data) {
            $.ajax({
                url:'/user/post/comment',
                data: data.field,
                type:'POST',
                success: function (res) {
                    if (res.code == 0) {
                        layer.msg('操作成功');
                        setTimeout(function () {
                            location.reload();
                        }, 1000);
                    } else {
                        layer.msg(res.msg);
                    }
                }
            });
            return false;
        });
    });

配置异步请求登录过滤器

在我们shiroConfig中,我们配置了非ajax的请求直接跳转到登录页面,但是受限的ajax请求则不能处理。

如未登录状态下直接评论文档,我们应该给出 请先登录 的提示。

在shiro中有很多过滤器。其中org.apache.shiro.web.filter.authc.UserFilter

83023edb3ca3af27ac2f5a87640bdf94_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ1MzQ4MDg=,size_16,color_FFFFFF,t_70.png

在此我们继承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();
    }

b6c7454920e7463efd5577d6009a5643_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ1MzQ4MDg=,size_16,color_FFFFFF,t_70.png

博客收藏功能

由于我们收藏按钮是通过js动态生成的,所以我们先在html中定义好存放收藏按钮的div,id 为LAY_jieAdmin

在static/mods/jie.js 中
    var asyncRender = function () {
        var div = $('.fly-admin-box'), jieAdmin = $('#LAY_jieAdmin');
        //查询帖子是否收藏
        if (jieAdmin[0] && layui.cache.user.uid != -1) {
            $.post("/user/post/collection/find",
                {postId: div.data('id')}, function (res) {
                    console.log("--------------")
                    jieAdmin.append('<span class="layui-btn layui-btn-xs jie-admin ' + (res.data.collection ? 'layui-btn-danger' : '') + '" type="collect" data-type="' + (res.data.collection ? 'remove' : 'add') + '">' + (res.data.collection ? '取消收藏' : '收藏') + '</span>');
                });
        }
    }();

其中layui.cache.user.uid 是在templates/common/static.html 中定义的。从上述代码中我们可以看出,js 代码根据后端的返回值动态的生成 收藏 or 取消收藏

后端接口如下:

@ResponseBody
    @PostMapping("/user/post/collection/find")
    public R collectionFind(String postId) {
        int count = userCollectionService.count(new QueryWrapper<UserCollection>()
                .eq("post_id", postId)
                .eq("user_id", getProfileId()));
        return R.ok(MapUtil.of("collection", count > 0));
    }

按钮点击效果的实现代码如下:

,collect: function(div){
      var othis = $(this), type = othis.data('type');
      fly.json('/user/post/collection/'+ type +'/', {
          postId: div.data('id')
      }, function(res){
        if(type === 'add'){
          othis.data('type', 'remove').html('取消收藏').addClass('layui-btn-danger');
        } else if(type === 'remove'){
          othis.data('type', 'add').html('收藏').removeClass('layui-btn-danger');
        }
      });
    }
  };

后端代码比较简单,参见:com.fly.controller.PostController 中的相关方法。

效果图:

c43f72bda53ba0fae930fc1da944ef69_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ1MzQ4MDg=,size_16,color_FFFFFF,t_70.png

参考代码:

https://github.com/XWxiaowei/FlyBlog/tree/v5-collection-center


相关文章
|
7月前
|
存储 运维 安全
隐语第二期学习内容随笔
数据要素在采集、存储等环节内外循环,数据持有方需确保内外循环中的数据安全与管控。信任焦虑源于数据权属等问题,依赖技术信任解决。隐私计算原则与开源隐语技术保障隐私安全。数据资产化驱动价值释放,技术信任促进流通,强调数据安全、隐私和信任的核心地位。
48 0
一键自动化博客发布工具,用过的人都说好(知乎篇)
使用一键自动化博客发布工具blog-auto-publishing-tools把博客发布到知乎上。
一键自动化博客发布工具,用过的人都说好(知乎篇)
|
7月前
|
存储 监控 安全
隐语第一期学习内容随笔
构建基于技术信任的数据可信流通体系,确保数据传输、处理、存储安全可控,防范持有权风险和越权使用导致的数据泄露、篡改。密态数据和密态天空计算强化数据安全,实现跨云互联。内外循环管理数据安全域,全程保障涉及身份确认、利益对齐等环节,依赖先进加密和跨域管控技术。
49 0
|
JavaScript 安全 前端开发
从零开始搭建个人网站博客
从零开始搭建个人网站博客
152 5
|
弹性计算 安全 关系型数据库
基于阿里云构建自己的博客(过程及心得体会)
本文介绍了如何使用免费领取的云服务器ECS、云数据库RDS MySQL Serverless 来构建自己的博客。首先,作者介绍了自己选择阿里云的原因和虚拟主机的优点。然后,详细介绍了如何配置阿里云虚拟主机以支持HTTPS和80端口等,最后在完成博客搭建后讲述了自己的心得体会。
文章发布博客平台整理汇总
文章发布博客平台整理汇总
69 0
|
前端开发 物联网 Java
我的第一篇博客 记录编程之路的初心与目标
大家好,我是一名即将步入大二的一名普通学生,现在就读于某双非二本的物联网工程专业的本科生。对于编程事业,我现在还显得比较稚嫩,但我仍然希望通过自己在各个途径的不断努力学习来改变自己乃至我整个家庭的命运。
98 0
|
人工智能 移动开发 大数据
程序人生 - 【官方指南】教你如何快速成为CSDN博客专家!
程序人生 - 【官方指南】教你如何快速成为CSDN博客专家!
208 0
程序人生 - 【官方指南】教你如何快速成为CSDN博客专家!
|
机器学习/深度学习 人工智能 监控
知乎CTO李大海:谢邀,来分享下内容社区的AI架构搭建与应用
谢邀!知乎 CTO 来分享下内容社区的 AI 架构搭建与应用。
198 0
知乎CTO李大海:谢邀,来分享下内容社区的AI架构搭建与应用
|
机器人 项目管理 开发者
阿里云开发者社区博文发布操作和规则说明
在开发者社区发布博文的几点注意事项。
阿里云开发者社区博文发布操作和规则说明