个人博客项目笔记_07

写文章

写文章需要 三个接口:

  1. 获取所有文章类别

  2. 获取所有标签

  3. 发布文章

1. 所有文章分类

1.1 接口说明

接口url:/categorys

请求方式:GET

请求参数:

参数名称 参数类型 说明

返回数据:

json 复制代码
{
    "success":true,
 	"code":200,
    "msg":"success",
    "data":
    [
        {"id":1,"avatar":"/category/front.png","categoryName":"前端"},	
        {"id":2,"avatar":"/category/back.png","categoryName":"后端"},
        {"id":3,"avatar":"/category/lift.jpg","categoryName":"生活"},
        {"id":4,"avatar":"/category/database.png","categoryName":"数据库"},
        {"id":5,"avatar":"/category/language.png","categoryName":"编程语言"}
    ]
}

1.2 Controller

java 复制代码
package com.cherriesovo.blog.controller;

import com.cherriesovo.blog.service.CategoryService;
import com.cherriesovo.blog.vo.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("categorys")
public class CategoryController {

    @Autowired
    private CategoryService categoryService;

    @GetMapping
    public Result listCategory() {
        return categoryService.findAll();
    }
}

1.3 Service

java 复制代码
public interface CategoryService {
    Result findAll();   //类别查询
}
java 复制代码
@Service
public class CategoryServiceImpl implements CategoryService {
    public CategoryVo copy(Category category){
        CategoryVo categoryVo = new CategoryVo();
        BeanUtils.copyProperties(category,categoryVo);
        return categoryVo;
    }
    public List<CategoryVo> copyList(List<Category> categoryList){
        List<CategoryVo> categoryVoList = new ArrayList<>();
        for (Category category : categoryList) {
            categoryVoList.add(copy(category));
        }
        return categoryVoList;
    }
    @Override
    public Result findAll() {
        //SELECT * FROM category;
        List<Category> categories = this.categoryMapper.selectList(new LambdaQueryWrapper<>());
        return Result.success(copyList(categories));
    }
}

2. 所有文章标签

2.1 接口说明

接口url:/tags

请求方式:GET

请求参数:

参数名称 参数类型 说明

返回数据:

json 复制代码
{
    "success": true,
    "code": 200,
    "msg": "success",
    "data": [
        {
            "id": 5,
            "tagName": "springboot"
        },
        {
            "id": 6,
            "tagName": "spring"
        },
        {
            "id": 7,
            "tagName": "springmvc"
        },
        {
            "id": 8,
            "tagName": "11"
        }
    ]
}

2.2 Controller

java 复制代码
@RestController
@RequestMapping("tags")
public class TagsController {

    @Autowired
    private TagService tagsService;
    
    @GetMapping
    public Result findAll(){
        return tagsService.findAll();
    }

}

2.3 Service

java 复制代码
public interface TagService {
    Result findAll();   //查询所有的文章标签
}

TagServiceImpl:

java 复制代码
	@Override
    public Result findAll() {
        //SELECT * FROM tag;
        List<Tag> tags = this.tagMapper.selectList(new LambdaQueryWrapper<>());
        return Result.success(copyList(tags));
    }

3. 发布文章

3.1 接口说明

接口url:/articles/publish

请求方式:POST

请求参数:

参数名称 参数类型 说明
title string 文章标题
id long 文章id(编辑有值)
body object({content: "ww", contentHtml: " ww ↵"}) 文章内容
category {id: 2, avatar: "/category/back.png", categoryName: "后端"} 文章类别
summary string 文章概述
tags [{id: 5}, {id: 6}] 文章标签

返回数据:

json 复制代码
{
    "success": true,
    "code": 200,
    "msg": "success",
    "data": {"id":12232323}
}

3.2 Controller

java 复制代码
package com.cherriesovo.blog.vo.params;

import com.cherriesovo.blog.vo.CategoryVo;
import com.cherriesovo.blog.vo.TagVo;
import lombok.Data;

import java.util.List;

@Data
public class ArticleParam {

    private Long id;

    private ArticleBodyParam body;

    private CategoryVo category;

    private String summary;

    private List<TagVo> tags;

    private String title;
}
java 复制代码
package com.cherriesovo.blog.vo.params;

import lombok.Data;

@Data
public class ArticleBodyParam {

    private String content;

    private String contentHtml;

}
java 复制代码
//json数据进行交互
@RestController
@RequestMapping("articles")
public class ArticleController {
    /*
    * 发布文章
    * */
    @PostMapping("publish")
    public Result publish(@RequestBody ArticleParam articleParam){
        return articleService.publish(articleParam);
    }
}

3.3 Service

java 复制代码
public interface ArticleService {
    //文章发布
    Result publish(ArticleParam articleParam);
}

ArticleServiceImpl:

ArticleServiceImpl共需要经历如下步骤:

  1. 创建一个 Article 对象,并设置其属性,最后将文章对象插入到数据库中。

    java 复制代码
    		Article article = new Article();
            article.setAuthorId(sysUser.getId());
            article.setCategoryId(articleParam.getCategory().getId());
            article.setCreateDate(System.currentTimeMillis());
            article.setCommentCounts(0);
            article.setSummary(articleParam.getSummary());	//摘要
            article.setTitle(articleParam.getTitle());
            article.setViewCounts(0);
            article.setWeight(Article.Article_Common);
            //设置了文章的 bodyId 属性为 -1L。通常情况下,-1L 通常被用作一个特殊的标记,表示某个值无效或未设置
            article.setBodyId(-1L);	//内容id
            //插入之后会自动生成一个文章id
            this.articleMapper.insert(article);
  2. 将文章id与标签id进行关联------获取文章的标签列表,遍历标签列表,对每个标签执行以下操作:

    1. 创建一个 ArticleTag 对象,并设置其文章ID和标签ID。
    2. 将 ArticleTag 对象插入到数据库中(article_tag表)。
    java 复制代码
    List<TagVo> tags = articleParam.getTags();
            if (tags != null) {
                for (TagVo tag : tags) {
                    ArticleTag articleTag = new ArticleTag();
                    articleTag.setArticleId(article.getId());
                    articleTag.setTagId(tag.getId());
                    this.articleTagMapper.insert(articleTag);
                }
            }
  3. 文章内容存储(article_body表)

    java 复制代码
    		ArticleBody articleBody = new ArticleBody();
            articleBody.setContent(articleParam.getBody().getContent());
            articleBody.setContentHtml(articleParam.getBody().getContentHtml());
            articleBody.setArticleId(article.getId());
            articleBodyMapper.insert(articleBody);
  4. 更新article表中的body属性

    java 复制代码
    		article.setBodyId(articleBody.getId());
            articleMapper.updateById(article);
  5. 设置返回值

    java 复制代码
    		ArticleVo articleVo = new ArticleVo();
            articleVo.setId(article.getId());
            return Result.success(articleVo);
java 复制代码
@Override
    @Transactional
    public Result publish(ArticleParam articleParam) {
        /*
        * 1、发布文章目的是构建article对象
        * 2、作者id------当前的登录用户
        * 3、要将标签加入到关联列表
        * 4、body 内容存储  要的是bodyId
        * */
        //此接口要加入到登录拦截中,否则会造成空指针异常
        SysUser sysUser = UserThreadLocal.get();

        Article article = new Article();
        article.setAuthorId(sysUser.getId());
        article.setCategoryId(articleParam.getCategory().getId());
        article.setCreateDate(System.currentTimeMillis());
        article.setCommentCounts(0);
        article.setSummary(articleParam.getSummary());	//摘要
        article.setTitle(articleParam.getTitle());
        article.setViewCounts(0);
        article.setWeight(Article.Article_Common);
        //设置了文章的 bodyId 属性为 -1L。通常情况下,-1L 通常被用作一个特殊的标记,表示某个值无效或未设置
        article.setBodyId(-1L);	//内容id
        //插入之后会自动生成一个文章id
        this.articleMapper.insert(article);

        List<TagVo> tags = articleParam.getTags();
        if (tags != null) {
            for (TagVo tag : tags) {
                ArticleTag articleTag = new ArticleTag();
                articleTag.setArticleId(article.getId());
                articleTag.setTagId(tag.getId());
                this.articleTagMapper.insert(articleTag);
            }
        }
        //body内容存储(article_body表)
        ArticleBody articleBody = new ArticleBody();
        articleBody.setContent(articleParam.getBody().getContent());
        articleBody.setContentHtml(articleParam.getBody().getContentHtml());
        articleBody.setArticleId(article.getId());
        articleBodyMapper.insert(articleBody);

        //更新article表中的body
        article.setBodyId(articleBody.getId());
        articleMapper.updateById(article);
        //设置返回值
        ArticleVo articleVo = new ArticleVo();
        articleVo.setId(article.getId());
        return Result.success(articleVo);
    }
java 复制代码
package com.cherriesovo.blog.dao.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.cherriesovo.blog.dao.pojo.ArticleTag;

public interface ArticleTagMapper  extends BaseMapper<ArticleTag> {
}
java 复制代码
package com.cherriesovo.blog.dao.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.cherriesovo.blog.dao.pojo.ArticleBody;

public interface ArticleBodyMapper extends BaseMapper<ArticleBody> {
}
java 复制代码
package com.cherriesovo.blog.vo;

import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import java.util.List;

@Data
public class ArticleVo {
    //一定要记得加 要不然 会出现精度损失
    @JsonSerialize(using = ToStringSerializer.class)
    private Long id;

    private String title;

    private String summary;

    private Integer commentCounts;

    private Integer viewCounts;

    private Integer weight;
    /**
     * 创建时间
     */
    private String createDate;

    private String author;

    private ArticleBodyVo body;

    private List<TagVo> tags;

    private CategoryVo category;

}
java 复制代码
package com.cherriesovo.blog.dao.pojo;

import lombok.Data;

@Data
public class ArticleTag {

    private Long id;

    private Long articleId;

    private Long tagId;
}

当然登录拦截器中,需要加入发布文章的配置:

WebMVCConfig:

java 复制代码
 @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //拦截test接口,后续实际遇到需要拦截的接口时,在配置为真正的拦截接口
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/test")
                .addPathPatterns("/comments/create/change")
                .addPathPatterns("/articles/publish");
    }

3.4 测试

4. AOP日志

定义一个自定义注解 LogAnnotation,用于在方法上添加日志相关的注解信息:

  • @Target(ElementType.METHOD):这个注解指定了 LogAnnotation 注解可以被应用于方法上。
  • @Retention(RetentionPolicy.RUNTIME):这个注解指定了 LogAnnotation 注解在运行时可见。
  • @Documented:这个注解指定了 LogAnnotation 注解将被包含在 Javadoc 中。
  • String module() default "";:这个注解定义了一个 module 属性,用于指定日志的模块,默认值为空字符串。
  • String operator() default "";:这个注解定义了一个 operator 属性,用于指定执行操作的操作者,默认值为空字符串。

这个自定义注解可以用于方法上,用于标记需要记录日志的方法,并且可以通过 moduleoperator 属性指定日志的模块和操作者。

Javadoc 是 Java 语言中用于生成 API 文档的工具。它能够根据源代码中的特定标记,自动生成与代码相关的文档。Javadoc 工具会扫描 Java 源代码中特定格式的注释,并根据这些注释生成 HTML 格式的 API 文档。这些注释通常以 /** 开头,以 */ 结尾,位于类、方法、字段等代码元素的前面。Javadoc 工具会解析这些注释中的标签和内容,并生成易于阅读和导航的 API 文档。)

java 复制代码
package com.cherriesovo.blog.common.aop;

import java.lang.annotation.*;

/**
 * 日志注解
 */
//TYPE代表可以放在类上面,METHOD代表可以放在方法上
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogAnnotation {

    String module() default "";

    String operator() default "";
}

LogAspect是一个使用了 Spring AOP的日志切面类:

  • @Aspect:这个注解标识了这个类是一个切面类,用于定义通知和切点的关系。

  • @Pointcut("@annotation(com.cherriesovo.blog.common.aop.LogAnnotation)"):这个注解定义了一个切点 logPointCut(),它表示当目标方法上存在 com.cherriesovo.blog.common.aop.LogAnnotation 注解时,这个切点会匹配到。

  • public void logPointCut() { }:这个方法定义了切点的具体内容,但方法体为空,因为它只是用于标识切点,实际的逻辑在通知方法中实现。

  • @Around("logPointCut()"):这个注解表示环绕通知,它表示在目标方法执行前后都会执行通知逻辑。

  • public Object around(ProceedingJoinPoint point) throws Throwable { }:这个方法是环绕通知的具体实现。在目标方法执行前记录开始时间,在执行后记录结束时间,并记录日志。

  • private void recordLog(ProceedingJoinPoint joinPoint, long time) { }:这个方法用于记录日志,它获取了目标方法的签名、注解信息、方法参数、请求信息等,并使用日志记录器将这些信息输出到日志中。

    • ProceedingJoinPoint 是 Spring AOP 中的一个接口,它提供了对连接点(Join Point)进行操作的功能。在面向切面编程中,连接点表示程序执行过程中的特定点,比如方法的调用或异常的处理等。

      ProceedingJoinPointJoinPoint 的子接口,在 Spring AOP 中,它专门用于表示可以执行的连接点,例如在环绕通知中,通过调用 proceed() 方法可以执行目标方法。

      通常,在环绕通知中,我们会将 ProceedingJoinPoint 对象作为参数传递给通知方法,在通知方法中可以通过调用 proceed() 方法来继续执行目标方法,也可以获取连接点的信息,如方法签名、参数等。

整个类的作用是,当目标方法被调用时,记录下方法的执行时间、方法的输入参数、请求的 IP 地址等信息,并将这些信息输出到日志中,以便进行日志记录和监控。

java 复制代码
package com.cherriesovo.blog.common.aop;

import com.alibaba.fastjson.JSON;
import com.cherriesovo.blog.utils.HttpContextUtils;
import com.cherriesovo.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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

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

/**
 * 日志切面
 */
@Aspect //切面 定义了通知和切点的关系
@Component
@Slf4j
public class LogAspect {
    //切点
    @Pointcut("@annotation(com.cherriesovo.blog.common.aop.LogAnnotation)")
    public void logPointCut() {
    }

    //通知类,环绕通知
    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long beginTime = System.currentTimeMillis();	//开始时间
        //执行方法
        Object result = point.proceed();
        //执行时长(毫秒)
        long time = System.currentTimeMillis() - beginTime;
        //保存日志
        recordLog(point, time);
        return result;
    }

    //记录日志
    private void recordLog(ProceedingJoinPoint joinPoint, long time) {
        //获取了目标方法的签名信息
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //通过签名获取目标方法
        Method method = signature.getMethod();
        //获取了目标方法上的 LogAnnotation 注解,以便获取注解中的信息
        LogAnnotation logAnnotation = method.getAnnotation(LogAnnotation.class);
        log.info("=====================log start================================");
        log.info("module:{}",logAnnotation.module());	//输出日志中的模块信息
        log.info("operation:{}",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]);	//参数列表转换为 JSON 格式的字符串,这里只获取了第一个参数
        log.info("params:{}",params);	//输出请求的参数信息

        //获取request 设置IP地址
        HttpServletRequest request = HttpContextUtils.getHttpServletRequest();	获取当前的HttpServletRequest对象
        log.info("ip:{}", IpUtils.getIpAddr(request));	//输出请求的 IP 地址


        log.info("excute time : {} ms",time);	//输出方法的执行时间
        log.info("=====================log end================================");
    }

}
java 复制代码
package com.cherriesovo.blog.utils;

import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

public class HttpContextUtils {
    /*用于获取当前线程的 HttpServletRequest 对象,通过 RequestContextHolder.getRequestAttributes() 获取到当前请求的属性对		 象,然后将其转换为 ServletRequestAttributes,再调用 getRequest() 方法获取到 HttpServletRequest 对象。*/
    public static HttpServletRequest getHttpServletRequest(){
        return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
    }
}
java 复制代码
package com.cherriesovo.blog.utils;

import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

//IP 地址获取工具类 IpUtils,用于从 HTTP 请求中获取客户端的真实 IP 地址
public class IpUtils {

    private static Logger logger = LoggerFactory.getLogger(IpUtils.class);

    /**
     * 获取IP地址
     * 使用Nginx等反向代理软件, 则不能通过request.getRemoteAddr()获取IP地址
     * 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,X-Forwarded-For中第一个非unknown的有效IP字符串,则为真实IP地址
     */
    public static String getIpAddr(HttpServletRequest request) {
        String ip = null;
        try {
            ip = request.getHeader("x-forwarded-for");
            if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("Proxy-Client-IP");
            }
            if (StringUtils.isEmpty(ip) || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("WL-Proxy-Client-IP");
            }
            if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("HTTP_CLIENT_IP");
            }
            if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("HTTP_X_FORWARDED_FOR");
            }
            if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getRemoteAddr();
            }
        } catch (Exception e) {
            logger.error("IPUtils ERROR ", e);
        }
        // 使用代理,则获取第一个IP地址
        if (StringUtils.isEmpty(ip) && ip.length() > 15) {
            if (ip.indexOf(",") > 0) {
                ip = ip.substring(0, ip.indexOf(","));
            }
        }
        return ip;
    }
}
      ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
    } catch (Exception e) {
        logger.error("IPUtils ERROR ", e);
    }
    // 使用代理,则获取第一个IP地址
    if (StringUtils.isEmpty(ip) && ip.length() > 15) {
        if (ip.indexOf(",") > 0) {
            ip = ip.substring(0, ip.indexOf(","));
        }
    }
    return ip;
}

}

复制代码
相关推荐
E___V___E1 小时前
MySQL数据库入门到大蛇尚硅谷宋红康老师笔记 高级篇 part 2
数据库·笔记·mysql
爱学习的小王!4 小时前
nvm安装、管理node多版本以及配置环境变量【保姆级教程】
经验分享·笔记·node.js·vue
陈志化4 小时前
JMeter----笔记
笔记·jmeter
HollowKnightZ5 小时前
论文阅读笔记:Gated CRF Loss for Weakly Supervised Semantic Image Segmentation
论文阅读·笔记
xzal126 小时前
青少年编程都有哪些比赛可以参加
笔记·青少年编程
典龙3306 小时前
如何使用springboot项目如何实现小程序里面商品的浏览记录功能案例
spring boot
StickToForever6 小时前
第4章 信息系统架构(二)
经验分享·笔记·学习·职场和发展
阿噜噜小栈7 小时前
Cursor 无限续杯
经验分享·笔记
customer087 小时前
【开源免费】基于SpringBoot+Vue.JS个人博客系统(JAVA毕业设计)
java·vue.js·spring boot·后端·开源
qq_459238497 小时前
SpringBoot整合Redis和Redision锁
spring boot·redis·后端