springboot博客实战笔记02

发布于:2025-08-15 ⋅ 阅读:(17) ⋅ 点赞:(0)

一、评论功能:

注意要先登录之后才能进行评论,所有把评论加入到登录拦截器当中

@Override
    public void addInterceptors(InterceptorRegistry registry) {
        //拦截test接口,后续实际遇到需要拦截的接口时,在配置为真正的拦截接口
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/test").addPathPatterns("/comments/create/change");
    }
  • 在登录拦截器LoginInterceptor里面存入了用户信息:
  //登录成功 放行
        //我希望再controller中 直接获取用户的信息  怎么获取
        UserThreadLocal.put(sysUser);
  • 后续在评论的实现类里面可以用gei方法获取到 用户信息 充当评论人
 //这里直接在线程里面找到登录时候保存的用户信息
        SysUser sysUser = UserThreadLocal.get();

1.根据文章id查询评论

    @Override
    public Result commentsByArticleId(Long id) {
        /**
         * 1.根据文章id 查询评论列表 从comments表中查询-
         * 2.根据作者的id 查询作者的信息
         * 3.判断  如果 level = 1 要去查询它有没有子评论
         * 4.如果 有 根据评论id  进行查询(parent_id)
         */
        LambdaQueryWrapper<Comment> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Comment::getArticleId,id);
        queryWrapper.eq(Comment::getLevel,1);
        List<Comment> comments = commentMapper.selectList(queryWrapper);
        List<CommentVo> commentVoList = copyList(comments);



        return Result.success(commentVoList);
    }
private List<CommentVo> findCommentsByParentId(Long id) {

        LambdaQueryWrapper<Comment> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Comment::getParentId,id);
        queryWrapper.eq(Comment::getLevel,2);
        List<Comment> comments = commentMapper.selectList(queryWrapper);

        return copyList(comments);
    }

将pojo —> vo的方法,这里有个小巧思,分为level大于1和等于1的情况,当level等于1时,说明可能是楼主,会有子评论

private List<CommentVo> copyList(List<Comment> comments) {
        List<CommentVo> commentVoList = new ArrayList<>();
        for (Comment comment : comments) {
            commentVoList.add(copy(comment));
        }

        return  commentVoList;
    }

    private CommentVo copy(Comment comment) {
        CommentVo commentVo = new CommentVo();
        BeanUtils.copyProperties(comment,commentVo);
        commentVo.setId(String.valueOf(comment.getId()));
        //作者信息
        Long authorId = comment.getAuthorId();
        UserVo userVo = this.sysUserService.findUserVoById(authorId);
        commentVo.setAuthor(userVo);

        //子评论
        Integer level = comment.getLevel();
        if( 1 == level){
            Long id = Long.valueOf(comment.getId());
            List<CommentVo> commentVoList = findCommentsByParentId(id);
            commentVo.setChildrens(commentVoList);
        }
        //to User 给谁评论
        if(level >1){
            Long toUid = comment.getToUid();
            UserVo toUserVo = this.sysUserService.findUserVoById(toUid);
            commentVo.setToUser(toUserVo);
        }


        return commentVo;
    }

精度损失问题

当数据库用的是分布式id的时候,前端会出现精度损失问题,导致找不到正确的id 此时需要在实体类中,添加相关注解,把id转为string,(分布式id 比较长,传到前端 会有精度损失,必须转为string类型,进行运输,就不会有问题了

    //防止前端精度损失 把id转为string
	@JsonSerialize(using = ToStringSerializer.class)
    private Long id;

二、发布文章

也需要登录过后才能发布文章,

 registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/test")
                .addPathPatterns("/comments/create/change")
                .addPathPatterns("/articles/publish");
 @Override
    public Result publish(ArticleParam articleParam) {
        /**
         * 1.发布文章  目的 构建Article对象
         * 2.作者id 当前的登录用户
         * 3.标签 要将标签加入到 关联表中
         * 4.body 内容存储
         *
         */
        SysUser sysUser = UserThreadLocal.get();
        //登录的用户id就是作者id 而登录的用户 可以去线程中找
        Article article = new Article();
        article.setAuthorId(Long.valueOf(sysUser.getId()));
        article.setWeight(Article.Article_Common);
        article.setViewCounts(0);
        article.setTitle(articleParam.getTitle());
        article.setSummary(articleParam.getSummary());
        article.setCommentCounts(0);
        article.setCreateDate(System.currentTimeMillis());
        article.setCategoryId(Long.valueOf(articleParam.getCategory().getId()));

        //插入之后会生成一个文章id
        this.articleMapper.insert(article);
        //tag
        List<TagVo> tags = articleParam.getTags();
        if(tags!=null){
            for (TagVo tag : tags) {
                Long articleId = Long.valueOf(article.getId());
                ArticleTag articleTag = new ArticleTag();
                articleTag.setTagId(Long.valueOf(tag.getId()));
                articleTag.setArticleId(articleId);
                articleTagMapper.insert(articleTag);
            }

        }
        //body
        ArticleBody articleBody = new ArticleBody();
        articleBody.setArticleId(Long.valueOf(article.getId()));
        articleBody.setContent(articleParam.getBody().getContent());
        articleBody.setContentHtml(articleParam.getBody().getContentHtml());
        articleBodyMapper.insert(articleBody);


        article.setBodyId(Long.valueOf(articleBody.getId()));
        articleMapper.updateById(article);


        //方法1 articleVo
//        ArticleVo articleVo = new ArticleVo();
//        articleVo.setId(article.getId());
//        return Result.success(articleVo);

        //方法2
        Map<String,String> map = new HashMap<>();
        map.put("id",article.getId().toString());//返回字符串避免精度损失问题
        return Result.success(map);
    }

三、AOP日志

对于 IoC 的一种补充,面向切面编程,简化程序的一种方式
IoC 简化代码量,AOP 实现解耦合的
让代码变得松散,灵活,更方便扩展和维护
日志输出和业务计算混合在一起,耦合在一起,不方便维护
解耦合,把日志输出和业务计算的代码进行分离
最终程序运行的时候,结果还要合到一起

1.在需要记录日志的controller上添加注解

    @PostMapping
    //加上此注解 代表要对此接口记录日志
    @LogAnnotation(module="文章",operator="获取文章列表")
    public Result listArticle(@RequestBody PageParams pageParams){

        return articleService.listArticle(pageParams);

    }

2.创建aop包,并且创建annotation类

在这里插入图片描述

在这里插入图片描述

package com.mszlu.blog.common.aop;


import java.lang.annotation.*;


//Type 代表可以放在类上面 Method代表可以放在方法上面
//ElementType.Type ElementType.METHOD
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogAnnotation {
    String module() default "";
    String operator() default "";
}

3.开花AOP创建LogAspect类

package com.mszlu.blog.common.aop;

import com.alibaba.fastjson.JSON;
import com.mszlu.blog.common.aop.LogAnnotation;
import com.mszlu.blog.utils.HttpContextUtils;
import com.mszlu.blog.utils.IpUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;

@Component//让spring识别到
@Aspect//切面 定义了通知和切点的关系
@Slf4j
public class LogAspect {

    @Pointcut("@annotation(com.mszlu.blog.common.aop.LogAnnotation)")
    public void pt(){}

    @Around("pt()")
    public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
        long beginTime = System.currentTimeMillis();
        //执行方法
        Object result = joinPoint.proceed();
        //执行时长(毫秒)
        long time = System.currentTimeMillis() - beginTime;
        //保存日志
        recordLog(joinPoint, time);
        return result;
    }
    private void recordLog(ProceedingJoinPoint joinPoint, long time) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        LogAnnotation logAnnotation = method.getAnnotation(LogAnnotation.class);
        log.info("=====================log start================================");
        log.info("module:{}",logAnnotation.module());
        log.info("operator:{}",logAnnotation.operator());

        //请求的方法名
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = signature.getName();
        log.info("request method:{}",className + "." + methodName + "()");

//        //请求的参数
        Object[] args = joinPoint.getArgs();
        String params = JSON.toJSONString(args[0]);
        log.info("params:{}",params);

        //获取request 设置IP地址
        HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
        log.info("ip:{}", IpUtils.getIpAddr(request));


        log.info("excute time : {} ms",time);
        log.info("=====================log end================================");
    }

}

四、上传图片


@RestController
@RequestMapping("upload")
public class UploadController {

    @Autowired
    private QiniuUtils qiniuUtils;

    @PostMapping()
    public Result upload(@RequestParam("image")MultipartFile file){
        //原始文件名称 比如aa.png
        String originalFilename = file.getOriginalFilename();

        String fileName = UUID.randomUUID()+toString()+"."+ StringUtils.substringAfterLast(originalFilename,".");
        //上传文件 上传在哪儿?
        //七牛云  云服务器 按量付费 幅度快 把图片发到离用户最近的服务器上
        //降低 我们自身应用服务器的宽带消耗

        boolean upload = qiniuUtils.upload(file,fileName);
        if(upload){
            return Result.success(QiniuUtils.url+fileName);
        }
        return Result.fail(20001,"上传失败");


     }

我们用的平台是七牛云 首先先要导入依赖:

 <dependency>
            <groupId>com.qiniu</groupId>
            <artifactId>qiniu-java-sdk</artifactId>
            <version>[7.7.0, 7.7.99]</version>
        </dependency>
  • QiniuUtils

@Component
public class QiniuUtils {

    public static  final String url = "http://t0oltruo7.hn-bkt.clouddn.com/";

    @Value("${qiniu.accessKey}")
    private  String accessKey;
    @Value("${qiniu.accessSecretKey}")
    private  String accessSecretKey;

    public  boolean upload(MultipartFile file,String fileName){

        //构造一个带指定 Region 对象的配置类
        Configuration cfg = new Configuration(Region.huanan());
        //...其他参数参考类注释
        UploadManager uploadManager = new UploadManager(cfg);
        //...生成上传凭证,然后准备上传
        String bucket = "danb24";
        //默认不指定key的情况下,以文件内容的hash值作为文件名
        try {
            byte[] uploadBytes = file.getBytes();
            Auth auth = Auth.create(accessKey, accessSecretKey);
            String upToken = auth.uploadToken(bucket);
            Response response = uploadManager.put(uploadBytes, fileName, upToken);
            //解析上传成功的结果
            DefaultPutRet putRet = JSON.parseObject(response.bodyString(), DefaultPutRet.class);
            return true;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return false;
    }
}

注意点

  • url就是你在七牛云创建的域名 “http://t0oltruo7.hn-bkt.clouddn.com/”;
  • accessKey和SecretKey在七牛云中获得 并且配置在在properties或者直接赋值(不推荐)
    qiniu.accessKey=NnneEoseKEAqkNNFuaMYWIBU7gn0xdVUbGuBjmGU
    qiniu.accessSecretKey=G5NBjBLd3OdttFet71AGxjbpahwduNpom_amLe1G
  • bucket 修改为自己的文件名
  • 也可以在配置文件中限制上传文件的值
    #上传文件总的最大值
    spring.servlet.multipart.max-request-size=20MB
    #单个文件的最大值
    spring.servlet.multipart.max-file-size=2MB

统一缓存处理(优化)

1.cache包下Cache

package com.mszlu.blog.common.cache;


import java.lang.annotation.*;


//
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Cache {

    long expire() default 1 * 60 * 1000;
    //缓存标识Key
    String name() default "";

}

2.cache包下CacheAspect

package com.mszlu.blog.common.cache;

import com.alibaba.fastjson.JSON;
import com.mszlu.blog.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.time.Duration;
//aop定义了一个切面  切面定义了切点和通知的关系
@Aspect
@Component
@Slf4j
public class CacheAspect {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;


    //切点
    @Pointcut("@annotation(com.mszlu.blog.common.cache.Cache)")
    public void pt(){}

    //环绕
    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp){
        try {
            Signature signature = pjp.getSignature();
            //类名
            String className = pjp.getTarget().getClass().getSimpleName();
            //调用的方法名
            String methodName = signature.getName();


            Class[] parameterTypes = new Class[pjp.getArgs().length];
            Object[] args = pjp.getArgs();
            //参数
            String params = "";
            for(int i=0; i<args.length; i++) {
                if(args[i] != null) {
                    params += JSON.toJSONString(args[i]);
                    parameterTypes[i] = args[i].getClass();
                }else {
                    parameterTypes[i] = null;
                }
            }
            if (StringUtils.isNotEmpty(params)) {
                //加密 以防出现key过长以及字符转义获取不到的情况
                params = DigestUtils.md5Hex(params);
            }
            Method method = pjp.getSignature().getDeclaringType().getMethod(methodName, parameterTypes);
            //获取Cache注解
            Cache annotation = method.getAnnotation(Cache.class);
            //缓存过期时间
            long expire = annotation.expire();
            //缓存名称
            String name = annotation.name();
            //先从redis获取
            String redisKey = name + "::" + className+"::"+methodName+"::"+params;
            String redisValue = redisTemplate.opsForValue().get(redisKey);
            if (StringUtils.isNotEmpty(redisValue)){
                log.info("走了缓存~~~,{},{}",className,methodName);
                return JSON.parseObject(redisValue, Result.class);
            }
            Object proceed = pjp.proceed();
            redisTemplate.opsForValue().set(redisKey,JSON.toJSONString(proceed), Duration.ofMillis(expire));
            log.info("存入缓存~~~ {},{}",className,methodName);
            return proceed;
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return Result.fail(-999,"系统错误");
    }

}

网站公告

今日签到

点亮在社区的每一天
去签到