SpringBoot+Mybatis Plus开发博客系统

一.下载layui社区的模版

https://fly.layui.com/store/FlyTemplate/

二.springboot热部署

参考(https://blog.csdn.net/panruola/article/details/87890234
2.1导入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
</dependency>

2.2Compiler,勾选 “Make project automatically”
在这里插入图片描述
3.3.快捷键 Shift+Ctrl+Alt+/ ,选择 “Registry” ,选中打勾 “compiler.automake.allow.when.app.running”
在这里插入图片描述

三.导入到springboot项目中(抽取片段)

我们可以抽取功能的片段:比如comment.html中公共的css样式(title不一样,我们可以通过参数传过来)

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head th:fragment="commentStyle">
    <meta charset="utf-8">
    <title th:text="${title}">基于 layui 的极简社区页面模版</title>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <meta name="keywords" content="fly,layui,前端社区">
    <meta name="description" content="Fly社区是模块化前端UI框架Layui的官网社区,致力于为web开发提供强劲动力">
    <link rel="stylesheet" th:href="@{/static/layui/css/layui.css}">
    <link rel="stylesheet" th:href="@{/static/css/global.css}">
</head>
<body>

引入:

<!--引入公共头部样式-->
<head th:replace="~{inc/comment::commentStyle(title='首页')}">

四.controller中防止非整形访问该URL会报异常,所以通过id:\\d*来只允许整形通过

    /*
    博客详细页面
    id:\d*只允许传的参数为整形
     */
    @GetMapping("/post/{id:\\d*}")
    public String detail(@PathVariable("id") Long id){
        return "post/detail";
    }

五.导航栏的信息通过项目启动就存进容器中,这样项目一运行就查询数据库中的数据

1.创建一个类实现ApplicationRunner和ServletContextAware类

  1. ApplicationRunner用于在项目运行就执行
  2. ServletContextAware用于获取容器上下文

2.运行项目查询数据库中数据,并存进上下文

package com.wcy.eblog.config;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.wcy.eblog.entity.MCategory;
import com.wcy.eblog.service.MCategoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.context.ServletContextAware;

import javax.servlet.ServletContext;
import java.util.List;

/**
 * ApplicationRunner 容器在启动的时候就会执行
 * ServletContextAware获取上下文容器
 * 把分类(导航栏)存进上下文容器中 (便于速度更快)
 */
@Component
public class ContextStartUp implements ApplicationRunner, ServletContextAware {
    @Autowired
    private MCategoryService mCategoryService;

    ServletContext servletContext;
    @Override
    public void run(ApplicationArguments args) throws Exception {
        //查询status=0的所有分类导航栏 若为1则不查询
        List<MCategory> mCategoryList = mCategoryService.list(new QueryWrapper<MCategory>().eq("status",0));
        //把查询到的数据存进容器中
        servletContext.setAttribute("mCategoryList", mCategoryList);
    }

    @Override
    public void setServletContext(ServletContext servletContext) {
        this.servletContext=servletContext;
    }
}

3.thymeleaf获取容器上下文的方法

            <li th:each="category:${#servletContext.getAttribute('mCategoryList')}">
                <a th:href="@{'/catalog/'+${category.id}}">[[${category.name}]]</a>
            </li>

六.thymeleaf处理时间显示成几小时前

              <!--对时间处理成几小时前-->
              <span th:datetime="${#dates.format(postVo.created, 'yyyy-MM-dd HH:mm')}" class="time"></span>
              <script src="https://cdn.bootcss.com/timeago.js/3.0.2/timeago.js"></script>
              <script>

                  // 自动更新
                  var timeagoInstance = timeago();// 实例
                  timeagoInstance.render(document.querySelectorAll('.time'),'zh_CN');
              </script>

七.注意layui分页的JS需要等页面加载完毕才执行

问题:防止layui分页消失,不会出现分页按钮
解决:

放到JQ的加载完毕才会执行的方法

$(function(){
           
           
 });

八.实现博客七天回复量排行榜(redis实现)

1.实现逻辑:

  1. 查询数据库中前七天的博客以及博客的评论数
  2. 通过redis的zset有序集合key存关于天数的(博客创建的那一天 有序map),然后zset里面的key存博客id之类的,分数存评论数
    比如:2020-7-2日有哪些博客创建了并且评论了,那么就会存进这个有序集合的key里面(见下图)

在这里插入图片描述
4. 因为web端需要展示博客的标题、评论数,所有还需要通过一个hash来存储这些数据。
5. 这些都需要设置一个过期时间 超出七天则清除
相关命令:
在这里插入图片描述
在这里插入图片描述

2.对应的service逻辑方法:

    //初始化本周热议
    @Override
    public void initWeekRank() {
        //1.查询七天内的博客
        //DateUtil.offset(new Date(), DateField.DAY_OF_MONTH,-7)获取当前时间的第前七天的时间
        List<MPost> mPosts = mPostMapper.selectList(new QueryWrapper<MPost>()
                .ge("created", DateUtil.offset(new Date(), DateField.DAY_OF_MONTH,-7))
                .gt("comment_count",0)//评论数大于0
        );
        //2.存进redis中
        for(MPost mPost:mPosts){
            String key="eblog:rank:time:"+DateUtil.format(mPost.getCreated(),"yyyy-MM-dd");
            //2.1//存进zset中
            redisUtil.zSet(key,mPost.getId(),mPost.getCommentCount());
            //2.2.计算当前时间与博客创建时间差  7-(18-14)
            long time=(7-DateUtil.between(new Date(),mPost.getCreated(), DateUnit.DAY))*24*60*60;
            //2.3设置key消失时间
            boolean expire = redisUtil.expire(key, time);
            //2.4由于前端需要显示标题、评论数等信息
            hashCachePostIdAndTitle(mPost);
        }

        //3.计算某七天的评论数并逆序排序
        zUnionPost7DaysForWeekRand();
    }

    //计算七天内的评论数
    private void zUnionPost7DaysForWeekRand() {
        String newKey="eblog:rank:7week";
        List<String> list=new ArrayList<>();
        for(int i=-7;i<0;i++){
            //这七天的key存进list里面 用户求并集
            String timeKey="eblog:rank:time:"+ DateUtil.format(DateUtil.offsetDay(new Date(),i),"yyyy-MM-dd");
            list.add(timeKey);
        }
        String key="eblog:rank:time:"+ DateUtil.format(new Date(),"yyyy-MM-dd");
        redisUtil.zUnionAndStore(key,list,newKey);//timeKey
    }

    //把博客信息存进redis中
    private void hashCachePostIdAndTitle(MPost mPost) {
        //通过博客的ID来存储键
        String key="eblog:rank:post:"+mPost.getId();
        //计算当前时间与博客创建时间差  7-(18-14)
        long time=(7-DateUtil.between(new Date(),mPost.getCreated(), DateUnit.DAY))*24*60*60;
        //存进redis中并设置过期时间
        redisUtil.hset(key,"id",mPost.getId(),time);
        redisUtil.hset(key,"title",mPost.getTitle(),time);
        redisUtil.hset(key,"commentCount",mPost.getCommentCount(),time);
    }

3.一旦用户评论或删除评论就修改缓存中评论的数目,并且重新更新缓存中的并集以及博客信息

    /**
     * 增加或减少redis这篇博客的评论数
     * @param postId
     * @param isincr
     */
    @Override
    public void incrCommentWeekRank(Long postId, Boolean isincr) {
        String key="eblog:rank:time:"+DateUtil.format(new Date(),"yyyy-MM-dd");
        redisUtil.zIncrementScore(key,postId,isincr?1:-1);//判断是+1还是-1
        zUnionPost7DaysForWeekRand();//重新更新七天的并集排行榜
        MPost mPost = this.getById(postId);
        this.hashCachePostIdAndTitle(mPost);//重新更新这篇博客信息
    }

九.博客访问量

1.比如查看某篇博客就更新缓存的访问量

    @GetMapping("/post/{id:\\d*}")
    public String detail(@PathVariable("id") Long id,
    @RequestParam(name = "current",required = false,defaultValue = "1") Long current
            ,@RequestParam(name = "size",required = false,defaultValue = "2") Long size){
        //返回博客信息
        PostVo postVo=mPostService.findPostById(id);

        //实时更新缓存中访问量
        this.mPostService.putViewCount(postVo);
        //返回评论信息 1.分页 2.博客id 3.用户id 4.排序
        IPage<CommentVo> commentVoIPage=mCommentService.pageingComment(new Page(current,size),id,null,"created");
        req.setAttribute("PostVo",postVo);
        req.setAttribute("CommentVo",commentVoIPage);
        return "post/detail";
    }

2.更新缓存访问量,给实体的访问量重新赋值,这样传回前端访问量就自动修改了
通过标题八缓存的博客信息来获取访问量,无则使用实体的

    /**
     *更新缓存中访问量
     * @param postVo
     */
    @Override
    public void putViewCount(PostVo postVo) {
        //1.从缓存中拿出访问量 如果缓存中没有直接获取实体的访问量
        String key="eblog:rank:post:"+postVo.getId();
        Integer viewCount = (Integer)redisUtil.hget(key, "viewCount");
        if(viewCount!=null){
            postVo.setViewCount(viewCount+1);
        }else{
            postVo.setViewCount(postVo.getViewCount()+1);
        }
        //2.更新缓存数据
        redisUtil.hset(key,"viewCount",postVo.getViewCount());
    }

十.redis不能存储Long类型

redis不能存储Long类型,会把Long类型转换为Integer类型,所以如果想要获取到long类型,就需要:

   Integer id = (Integer)redisUtil.hget(key, "id");//博客ID
   Long idLong = Long.valueOf(id.longValue());

十一.springboot使用定时任务:

1.开启定时任务

//开启定时任务
@EnableScheduling
public class EblogApplication {

    public static void main(String[] args) {
        SpringApplication.run(EblogApplication.class, args);
        System.out.println("http://localhost:8088");
    }

}

2.使用 @Scheduled(fixedRate = 60601000)注解 (每一小时执行一次)
参数配置:
fixedRate = 101000 固定每10秒执行一次
fixedDelay = 10
1000 延迟每10秒执行一次
cron = “0 0 2 * * ?” //每天凌晨两点执行 https://qqe2.com/cron可配置cron
3.cron的更多配置。
每隔5秒执行一次:*/5 * * * * ?
每隔1分钟执行一次:0 */1 * * * ?

每天23点执行一次:0 0 23 * * ?

每天凌晨1点执行一次:0 0 1 * * ?

每月1号凌晨1点执行一次:0 0 1 1 * ?

每月最后一天23点执行一次:0 0 23 L * ?

每周星期天凌晨1点实行一次:0 0 1 ? * L

在26分、29分、33分执行一次:0 26,29,33 * * * ?

每天的0点、13点、18点、21点都执行一次:0 0 0,13,18,21 * * ?

十二.springboot实现定时更新访问量

Set keys = redisTemplate.keys(“eblog:rank:post:*”);//查询有eblog:rank:post:所有key

/**
 * 定时更新访问量
 */
@Component
public class ViewCountSyncTask {
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private RedisTemplate<String,Object> redisTemplate;
    @Autowired
    private MPostService mPostService;
    /**
     * @Scheduled进行定时任务
     * fixedRate = 10*1000 固定每10秒执行一次
     * fixedDelay = 10*1000 延迟每10秒执行一次
     * cron = "0 0 2 * * ?" //每天凌晨两点执行     https://qqe2.com/cron可配置cron
     *
     */
    @Scheduled(fixedRate = 60*60*1000)
    public void viewCountTast(){
        //1.获取缓存中访问量
        Set<String> keys = redisTemplate.keys("eblog:rank:post:*");//查询有eblog:rank:post:所有key
        List<MPost> posts=new ArrayList<>();
        for(String key:keys){
            //查看缓存中是否有访问量
            if(redisUtil.hHasKey(key,"viewCount")){
                Integer id = (Integer)redisUtil.hget(key, "id");//博客ID
                Long idLong = Long.valueOf(id.longValue());
                Integer viewCount = (Integer)redisUtil.hget(key, "viewCount");//博客访问量
                MPost post=new MPost();
                post.setId(idLong);
                post.setViewCount(viewCount);
                posts.add(post);

                //更新之后这些访问量缓存里面的数据需要清空(清空的原因:
                // 如果不删除 下次执行这个方法就又会查出很多viewCount这个缓存 以至于上方的list会很大
                redisUtil.hdel(key,"viewCount");
            }
        }

        //2.判断是否需要更新到数据库
        if(posts.isEmpty()) return;

        //3.批量更新数据库
        boolean isUpdate = mPostService.updateBatchById(posts);
        if(isUpdate){
            System.out.println("------------------访问量更新数据库成功--------------------");
        }
    }
}

十三.springboot使用Hibernate Validator校验pojo的某些字段

1.spring-boot-starter-web包里面有hibernate-validator包,不需要引用hibernate validator依赖。
2.比如邮箱验证(不能为空和格式)

import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
    @Email
    @NotBlank(message = "邮箱不能为空")
    private String email;

3.添加hibernate validator的工具类

package com.wcy.eblog.util;

import lombok.Data;
import org.hibernate.validator.HibernateValidator;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * Hibernate Validator校验工具类
 */
public class ValidationUtil {

    /**
     * 开启快速结束模式 failFast (true)
     */
    private static Validator validator = Validation.byProvider(HibernateValidator.class).configure().failFast(false).buildValidatorFactory().getValidator();
    /**
     * 校验对象
     *
     * @param t bean
     * @param groups 校验组
     * @return ValidResult
     */
    public static <T> ValidResult validateBean(T t,Class<?>...groups) {
        ValidResult result = new ValidationUtil().new ValidResult();
        Set<ConstraintViolation<T>> violationSet = validator.validate(t,groups);
        boolean hasError = violationSet != null && violationSet.size() > 0;
        result.setHasErrors(hasError);
        if (hasError) {
            for (ConstraintViolation<T> violation : violationSet) {
                result.addError(violation.getPropertyPath().toString(), violation.getMessage());
            }
        }
        return result;
    }
    /**
     * 校验bean的某一个属性
     *
     * @param obj          bean
     * @param propertyName 属性名称
     * @return ValidResult
     */
    public static <T> ValidResult validateProperty(T obj, String propertyName) {
        ValidResult result = new ValidationUtil().new ValidResult();
        Set<ConstraintViolation<T>> violationSet = validator.validateProperty(obj, propertyName);
        boolean hasError = violationSet != null && violationSet.size() > 0;
        result.setHasErrors(hasError);
        if (hasError) {
            for (ConstraintViolation<T> violation : violationSet) {
                result.addError(propertyName, violation.getMessage());
            }
        }
        return result;
    }
    /**
     * 校验结果类
     */
    @Data
    public class ValidResult {

        /**
         * 是否有错误
         */
        private boolean hasErrors;

        /**
         * 错误信息
         */
        private List<ErrorMessage> errors;

        public ValidResult() {
            this.errors = new ArrayList<>();
        }
        public boolean hasErrors() {
            return hasErrors;
        }

        public void setHasErrors(boolean hasErrors) {
            this.hasErrors = hasErrors;
        }

        /**
         * 获取所有验证信息
         * @return 集合形式
         */
        public List<ErrorMessage> getAllErrors() {
            return errors;
        }
        /**
         * 获取所有验证信息
         * @return 字符串形式
         */
        public String getErrors(){
            StringBuilder sb = new StringBuilder();
            for (ErrorMessage error : errors) {
                sb.append(error.getPropertyPath()).append(":").append(error.getMessage()).append(" ");
            }
            return sb.toString();
        }

        public void addError(String propertyName, String message) {
            this.errors.add(new ErrorMessage(propertyName, message));
        }
    }

    @Data
    public class ErrorMessage {

        private String propertyPath;

        private String message;

        public ErrorMessage() {
        }

        public ErrorMessage(String propertyPath, String message) {
            this.propertyPath = propertyPath;
            this.message = message;
        }
    }

}

4.controller层使用

    @PostMapping("/regist")
    @ResponseBody
    public Result doregist( MUser user,String repass,String vercode){
        //验证表单信息是否符合
        ValidationUtil.ValidResult validResult = ValidationUtil.validateBean(user);
        if (validResult.hasErrors()){
            return Result.fail(validResult.getErrors());
        }
}

二.添加分组检验:如果未传入分组 则不进行验证
1.对应的bean中

    /**
     * 邮件
     */
    @NotBlank(message = "邮箱不能为空",groups = {Update.class,Save.class})
    @Email(message = "邮箱格式不正确",groups = {Update.class,Save.class})
    private String email;
    
    //保存的时候校验分组
    public interface Save {
    }

    //更新的时候校验分组
    public interface Update {
    }

2.controller中验证传入分组

        ValidationUtil.ValidResult validResult = ValidationUtil.validateBean(user, User.Save.class);
        if(validResult.hasErrors()){
            return Result.fail(validResult.getErrors());
        }

十四.使用MP的插入方法小心主键会使用它自己的生成策略

1.配置文件

mybatis-plus:
  mapper-locations: classpath*:/mapper/**Mapper.xml
  type-aliases-package: com.wcy.eblog.*
  global-config:
    db-config:
      id-type: auto #使用mybatis-plus的插入时主键使用数据库自增

2.实体类ID上加上@TableId(value = “id”,type = IdType.AUTO)使用数据库的自增

    @TableId(value = "id",type = IdType.AUTO)
    private Long id;

十五.不小心用了MP的主键生成策略,如何把主键序列重写修改

1,删除原有主键:

ALTER TABLE `table_name` DROP `id`;

2,添加新主键字段:

ALTER TABLE `table_name` ADD `id` INT( 11 ) NOT NULL FIRST;

3,设置新主键:

ALTER TABLE `table_name` MODIFY COLUMN `id` INT( 11 ) NOT NULL AUTO_INCREMENT,ADD PRIMARY KEY(id);

十六.springboot中使用shiro

shirod重要的三部分

  • 1.获取subject(用户)
  • 2.shiro有自己的session,无http中的session无关
  • 3.判断是否认证
  • 4.获取token
  • 5.是否有某个角色
  • 6.是否有某个权限
  • 7.注销
    在这里插入图片描述
    1.导入依赖
        <!--shiro权限框架-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>
        <!--thymeleaf中使用shiro的标签-->
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>

2.自定义XXXRealm 继承AuthorizingRealm用于授权和认证
授权的设置:
在这里插入图片描述

@Component
public class AccountRealm extends AuthorizingRealm {
    @Autowired
    private MUserService mUserService;
    //模拟授权 实际应该查库
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
                AccountProfile profile= (AccountProfile) principalCollection.getPrimaryPrincipal();
        //模拟给用户9赋予Admin权限
        if(profile.getId()== 9){
            SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
            info.addRole("admin");
            return info;
        }
        return null;
    }

    /**
     * 用于用户登录认证
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        UsernamePasswordToken token= (UsernamePasswordToken) authenticationToken;
        String email = token.getUsername();
        //查询数据库进行操作
        AccountProfile profile=mUserService.login(token.getUsername(),String.valueOf(token.getPassword()));
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(profile, token.getCredentials(), getName());
        return info;
    }
}

3.登录的controller

    @PostMapping("login")
    @ResponseBody
    public Result doLogin(String email,String password,String vercode){
        if(StringUtils.isEmpty(email)){
            return Result.fail("请填写邮箱");
        }
        if(StringUtils.isEmpty(password)){
            return Result.fail("请填写密码");
        }
        if(StringUtils.isEmpty(vercode)){
            return Result.fail("验证码不能为空");
        }
        String kapchat = (String)req.getSession().getAttribute(Constants.KAPTCHA_SESSION_KEY);
        if(!vercode.toUpperCase().equals(kapchat.toUpperCase())){
            return Result.fail("验证码错误");
        }
        UsernamePasswordToken token=new UsernamePasswordToken(email, DigestUtil.md5Hex(password));
        try{
            //使用shiro进行登录
            SecurityUtils.getSubject().login(token);
        }catch (AuthenticationException e) {
            //进行登录会产生下面的异常
            if (e instanceof UnknownAccountException) {
                return Result.fail("用户不存在");
            } else if (e instanceof LockedAccountException) {
                return Result.fail("用户被禁用");
            } else if (e instanceof IncorrectCredentialsException) {
                return Result.fail("密码错误");
            } else {
                return Result.fail("用户认证失败");
            }
        }
        return Result.success().action("/");
    }

4.shiro的配置类

/**
 * 类似于拦截器
 */
@Slf4j
@Configuration
public class ShiroConfig {

    @Bean
    public SecurityManager securityManager(AccountRealm accountRealm){

        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(accountRealm);

        log.info("------------------>securityManager注入成功");

        return securityManager;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {

        ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
        filterFactoryBean.setSecurityManager(securityManager);
        // 配置登录的url和登录成功的url
        filterFactoryBean.setLoginUrl("/login");
        filterFactoryBean.setSuccessUrl("/user/center");
        // 配置未授权跳转页面
        filterFactoryBean.setUnauthorizedUrl("/error/403");

        Map<String, String> hashMap = new LinkedHashMap<>();

//        hashMap.put("/res/**", "anon");
//
//        hashMap.put("/user/home", "auth");
//        hashMap.put("/user/set", "auth");
//        hashMap.put("/user/upload", "auth");
//        hashMap.put("/user/index", "auth");
//        hashMap.put("/user/public", "auth");
//        hashMap.put("/user/collection", "auth");
//        hashMap.put("/user/mess", "auth");
//        hashMap.put("/msg/remove/", "auth");
//        hashMap.put("/message/nums/", "auth");
//
//        hashMap.put("/collection/remove/", "auth");
//        hashMap.put("/collection/find/", "auth");
//        hashMap.put("/collection/add/", "auth");
//
//        hashMap.put("/post/edit", "auth");
//        hashMap.put("/post/submit", "auth");
//        hashMap.put("/post/delete", "auth");
//        hashMap.put("/post/reply/", "auth");
//
//        hashMap.put("/websocket", "anon");
        hashMap.put("/login", "anon");
        filterFactoryBean.setFilterChainDefinitionMap(hashMap);

        return filterFactoryBean;

    }

    /**
     * 用于thymeleaf模板与shiro的标签
     * @return
     */
    @Bean
    public ShiroDialect shiroDialect() {
        return new ShiroDialect();
    }
}

5.thymeleaf使用标签:
1.引入:

<html xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">

2.标签:

guest标签
  <shiro:guest>
  </shiro:guest>
  用户没有身份验证时显示相应信息,即游客访问信息。

user标签
  <shiro:user>  
  </shiro:user>
  用户已经身份验证/记住我登录后显示相应的信息。

authenticated标签
  <shiro:authenticated>  
  </shiro:authenticated>
  用户已经身份验证通过,即Subject.login登录成功,不是记住我登录的。

notAuthenticated标签
  <shiro:notAuthenticated>
  
  </shiro:notAuthenticated>
  用户已经身份验证通过,即没有调用Subject.login进行登录,包括记住我自动登录的也属于未进行身份验证。

principal标签
  <shiro: principal/>
  
  <shiro:principal property="username"/>
  相当于((User)Subject.getPrincipals()).getUsername()。

lacksPermission标签
  <shiro:lacksPermission name="org:create">
 
  </shiro:lacksPermission>
  如果当前Subject没有权限将显示body体内容。

hasRole标签
  <shiro:hasRole name="admin">  
  </shiro:hasRole>
  如果当前Subject有角色将显示body体内容。

hasAnyRoles标签
  <shiro:hasAnyRoles name="admin,user">
   
  </shiro:hasAnyRoles>
  如果当前Subject有任意一个角色(或的关系)将显示body体内容。

lacksRole标签
  <shiro:lacksRole name="abc">  
  </shiro:lacksRole>
  如果当前Subject没有角色将显示body体内容。

hasPermission标签
  <shiro:hasPermission name="user:create">  
  </shiro:hasPermission>
  如果当前Subject有权限将显示body体内容

十七.thymeleaf中标签的属性上如何shiro标签

问题:
<shiro:principal property=“avatar”/>只能只能作用于标签的内容上,不能作用于属性上。
解决:
1.我们可以随便设置一个div标签,然后把值给div
2.再通过js获取div的值,然后赋值src或href等等标签属性上面

      <div style="display: none" id="div_avatar"><shiro:principal property="avatar"/></div>
      <img src="" id="img_avatar"/>
      <script>
          $(function () {
             var avatar=$('#div_avatar').html();
             $('#img_avatar').attr("src",avatar)
             console.log(avatar)
          })
     </script>

十八.thymeleaf中判断list是否为空

不为空:

th:if="${not #lists.isEmpty(listData)}"

为空:

th:if="${#lists.isEmpty(listData)}"

十九.批量删除文件(忽略后缀)

    public static void deleteFileSuff(File file,String fileName){
        //批量删除忽略后缀的文件
        String[] list = file.list();
        for(String temp:list){
            String name=temp.substring(0,temp.indexOf("."));
            String nameSuffix=temp.substring(temp.indexOf("."));
            if(name.equals(fileName)){
                File file1=new File(file.getAbsolutePath()+"/"+fileName+nameSuffix);
                file1.delete();
            }
        }
    }

二十.springboot上传图片

1.yml配置:${user.dir}是获取容器src路径的绝对路径

#文件下载路径  ${user.dir}可以获取容器即src路径的绝对路径   记得最后的/
file:
  upload:
    dir: ${user.dir}/upload/

2.配置类;一但发起/upload请求就会映射到${user.dir}/upload/路径下面,我们只需要再返回图片的路径就可以了

@Configuration
public class MyMvcConfig implements WebMvcConfigurer{
    @Value("${file.upload.dir}")
    private String uploadPath;//获取配置文件的路径 这样可以随时配置图片上传路径

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        //资源映射  用于图片显示
        registry.addResourceHandler("/upload/**").addResourceLocations("file:"+uploadPath);
    }

}

3.比如返回路径为:/upload/user/avatar"+newName 这样前端展示这个url就可以了。

return Result.success("/upload/user/avatar"+newName);

二十二.layui流和layui的模板引擎

一个类似于流的形式的页面
在这里插入图片描述
1.首先使用layui的模板引擎定义一个显示的item
其中使用了layui的时间工具类 这里面没法使用thymeleaf的语法

<ul class="mine-view jie-row" id="fabu">

            <script id="lpl_fabu" type="text/html" th:inline="javascript">
              <li>
                <a class="jie-title" th:href="@{/post/post/{{d.id}}}" target="_blank">{{d.title}}</a>
                <i>{{layui.util.toDateString(d.created, 'yyyy-MM-dd HH:mm:ss')}}</i>
                <a class="mine-edit" href="/jie/edit/8116">编辑</a>
                <em>{{d.viewCount}}阅/{{d.commentCount}}答</em>
              </li>
            </script>

          </ul>

2.js的代码

    $(function () {
        layui.use(['flow','laytpl','util'], function(){
            var $ = layui.jquery; //不用额外加载jQuery,flow模块本身是有依赖jQuery的,直接用即可。
            var flow = layui.flow;
            var laytpl = layui.laytpl;
            var util = layui.util;
            //流加载的容器
            flow.load({
                elem: '#fabu', //指定列表容器 放在哪个容器里面
                isAuto:false  //不自动加载
                ,done: function(page, next){ //到达临界点(默认滚动触发),触发下一页
                    var lis = [];

                    //以jQuery的Ajax请求为例,请求下一页数据(注意:page是从2开始返回)
                    $.get('/user/publish?page='+page, function(res){
                        //假设你的列表返回在data集合中 通过layui的循环获取数据
                        layui.each(res.data.records, function(index, item){
                            //通过layui的自定义数据模板 lpl_fabu.innerHTML是模板引擎的ID并显示html
                            laytpl(lpl_fabu.innerHTML).render(item, function(html){
                                //放进数组中
                                lis.push(html);
                            });
                        });

                        //执行下一页渲染,第二参数为:满足“加载更多”的条件,即后面仍有分页
                        //pages为Ajax返回的总页数,只有当前页小于总页数的情况下,才会继续出现加载更多
                        next(lis.join(''), page < res.data.pages);
                    });
                }
            });
        });
    })

二十三.对于多表查询如果涉及到四个表以上不推荐使用left join

通过内部查询的方式来查询设计多表的查询

SELECT
            m.*, (
                SELECT
                    username
                FROM
                    m_user
                WHERE
                    id = m.from_user_id
            ) AS fromUserName,
            (
                SELECT
                    title
                FROM
                    m_post
                WHERE
                    id = m.post_id
            ) AS blogTitle,
            (
                SELECT
                    content
                FROM
                    m_comment
                WHERE
                    id = m.comment_id
            ) AS commentContent
        FROM
            m_user_message m
        WHERE
            to_user_id=#{id}
        ORDER BY created DESC

二十四.shiro中对ajax的自定义过滤器

1.自定义过滤器

public class AuthFilter extends UserFilter {
    //重写没有登录重定向的方法
    @Override
    protected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
        HttpServletRequest httpServletRequest= (HttpServletRequest) request;
        //X-Requested-With: XMLHttpRequest
        String header = httpServletRequest.getHeader("X-Requested-With");
        //1.判断是否是ajax请求
        if(header != null && "XMLHttpRequest".equals(header)){
            boolean authenticated = SecurityUtils.getSubject().isAuthenticated();
            //登录回超时
            if(!authenticated){
                response.setContentType("application/json;charset=UTF-8");//设置字符集
                JSONObject jsonObject=new JSONObject();
                jsonObject.put("msg","回话超时,请重新登录");
                response.getWriter().print(jsonObject);
            }
        }else {
            super.redirectToLogin(request, response);
        }
    }
}

2.shiroconfig配置类中注入自定义过滤器bean

    //把自己写的过滤器加入到容器中
    @Bean
    public AuthFilter authFilter(){
        return new AuthFilter();
    }

3.shiroFilterFactoryBean中配置自己的过滤器

//添加自定义的过滤器
        Map filterMap=new HashMap();
        //注意不要与内置的过滤器相同了
        filterMap.put("auth",authFilter());
        filterFactoryBean.setFilters(filterMap);


 //查看当前用户是否收藏了该博客  判断是否是ajax请求  ajax自己写一个过滤器 配置自己的过滤器
        hashMap.put("/collection/find/", "auth");
        hashMap.put("/collection/remove/", "auth");
        hashMap.put("/collection/add/", "auth");

二十五.java中使用hutool的断言

有时候在controller中,我们返回博客详情页面,可能出现博客以及不存在,那么我们就可以使用断言,如果不满足条件,就会抛出异常。
比如下方 当postVo==null 就会抛出异常 (注意是相反的哦)

        //返回博客信息
        PostVo postVo=mPostService.findPostById(id);
        Assert.isTrue(postVo!=null,"该文章已被删除");
基于springboot_ssm的个人博客源代码: 个人博客系统主要用于发表个人博客,记录个人生活日常,学习心得,技术分享等,供他人浏览,查阅,评论等。本系统结构如下: (1)博主端: 登录模块:登入后台管理系统:首先进入登录页面,需要输入账号和密码。它会使用Shiro进行安全管理,对前台输入的密 码进行加密运算,然后与数据库中的进行比较。成功后才能登入后台系统博客管理模块: 博客管理功能分为写博客博客信息管理。写博客是博主用来发表编写博客的,需要博客标题,然后选择博 客类型,最后将博客内容填入百度的富文本编辑器中,点击发布博客按钮即可发布博客博客类别管理模块:博主类别管理系统可以添加,修改和删除博客类型名称和排序序号。将会显示到首页的按日志类别区域。 游客可 以从这里查找相关的感兴趣的博客内容 评论信息管理模块:评论管理功能分为评论审核和评论信息管理两部分。评论审核是当有游客或自己发表了评论之后,博主需 要在后台管理系统中审核评论。若想将此评论显示在页面上则点击审核通过,否则点击审核不通过。 个人信息管理模块:修改博主的个人信息,可以修改昵称,个性签名,可以添加个人头像,修改个人简介; 系统管理功能模块:友情链接管理,修改密码,刷新系统缓存和安全退出,友情链接管理可以添加,修改,删除友情链接网址 (2)游客端: 查询博客: 查询具体哪一篇博客 查看博客内容: 查看博客内容 查看博主个人信息:查看博主个人简介 发表评论: 可以评论具体某篇博客 友情链接: 查看友情链接
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值