7.写文章、AOP记录日志、修正文章归档bug

写文章需要 三个接口:

  1. 获取所有文章类别

  2. 获取所有标签

  3. 发布文章

1和2

1. 所有文章分类

1.1 接口说明

接口url:/categorys

请求方式:GET

请求参数:无

返回数据:

{
    "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":"编程语言"}
    ]
}

这个比较简单,代码如下

CategoryController:
package com.example.blog.controller;

import com.example.blog.entity.Category;
import com.example.blog.service.CategoryService;
import com.example.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 categories()
    {
        return categoryService.findAll();
    }
}
CategoryServiceImpl:
package com.example.blog.service.impl;

import com.example.blog.dao.mapper.CategoryMapper;
import com.example.blog.entity.Category;
import com.example.blog.service.CategoryService;
import com.example.blog.vo.CategoryVo;
import com.example.blog.vo.Result;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
public class CategoryServiceImpl implements CategoryService
{

    @Autowired
    private CategoryMapper categoryMapper;

    @Override
    public Result findAll() 
    {
        List<Category> categories = categoryMapper.selectList(null);
        return Result.success(copyList(categories));
    }

    private List<CategoryVo> copyList(List<Category> categories) 
    {
        List<CategoryVo> categoryVoList = new ArrayList<>();
        for(Category category:categories)
        {
            categoryVoList.add(copy(category));
        }
        return categoryVoList;
    }

    private CategoryVo copy(Category category)
    {
        CategoryVo categoryVo = new CategoryVo();
        BeanUtils.copyProperties(category,categoryVo);
        return categoryVo;
    }
}

所有文章标签:

2.1 接口说明

接口url:/tags

请求方式:GET

请求参数:无

返回数据:

{
    "success": true,
    "code": 200,
    "msg": "success",
    "data": [
        {
            "id": 5,
            "tagName": "springboot"
        },
        {
            "id": 6,
            "tagName": "spring"
        },
        {
            "id": 7,
            "tagName": "springmvc"
        },
        {
            "id": 8,
            "tagName": "11"
        }
    ]
}
TagsController:
package com.example.blog.controller;

import com.example.blog.service.TagService;
import com.example.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("/tags")
public class TagsController
{
    @Autowired
    private TagService tagService;

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

}
TagServiceImpl:
package com.example.blog.service.impl;

import com.baomidou.mybatisplus.extension.api.R;
import com.example.blog.dao.mapper.TagMapper;
import com.example.blog.entity.Tag;
import com.example.blog.service.TagService;
import com.example.blog.vo.Result;
import com.example.blog.vo.TagVo;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@Service
public class TagServiceImpl implements TagService
{

    @Autowired
    private TagMapper tagMapper;

    @Override
    public Result findAll()
    {
        List<Tag> tags = tagMapper.selectList(null);
        return Result.success(copyList(tags));
    }

    public List<TagVo> copyList(List<Tag> tags)
    {
        List<TagVo> tagVoList = new ArrayList<>();
        for(Tag tag:tags)
        {
            tagVoList.add(copy(tag));
        }
        return tagVoList;
    }

    public TagVo copy(Tag tag)
    {
        TagVo tagVo = new TagVo();
        BeanUtils.copyProperties(tag,tagVo);
        return tagVo;
    }
}

发布文章

接口url:/articles/publish

请求方式:POST

请求参数:

参数名称参数类型说明
titlestring文章标题
idlong文章id(编辑有值)
bodyobject({content: "ww", contentHtml: "<p>ww</p>↵"})文章内容
category{id: 2, avatar: "/category/back.png", categoryName: "后端"}文章类别
summarystring文章概述
tags[{id: 5}, {id: 6}]文章标签

返回数据:

{
    "success": true,
    "code": 200,
    "msg": "success",
    "data": {"id":12232323}
}

 这里需要封装一个前端参数ArticleParam

package com.example.blog.vo.params;

import com.example.blog.vo.ArticleBodyVo;
import com.example.blog.vo.CategoryVo;
import com.example.blog.vo.TagVo;
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 ArticleParam
{
    @JsonSerialize(using = ToStringSerializer.class)
    private Long id;

    private ArticleBodyParam body;

    private CategoryVo category;

    private String summary;

    private List<TagVo> tags;/*文章标签*/

    private String title;
}
ArticleBodyParam:
package com.example.blog.vo.params;

import lombok.Data;

//发布文章所需要的前端参数 文章内容
//body: {content: "cxz", contentHtml: "<p>cxz</p>↵"}
@Data
public class ArticleBodyParam
{
    private String content;

    private String contentHtml;
}

返回的数据是文章的id,注意是一个对象

ArticleController:
package com.example.blog.controller;

import com.example.blog.service.ArticleService;
import com.example.blog.vo.Result;
import com.example.blog.vo.params.ArticleParam;
import com.example.blog.vo.params.PageParams;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RequestMapping("/articles")
@RestController
public class ArticleController
{

    @Autowired
    private ArticleService articleService;

    @PostMapping("/publish")
    public Result publish(@RequestBody ArticleParam articleParam)
    {
        return articleService.publish(articleParam);
    }
}
该接口需要加入到登录拦截器中,不然获得到的用户的id可能为空,article的成员AuthorId也就赋值不了。
    /*新增登录拦截器*/
    @Override
    public void addInterceptors(InterceptorRegistry registry)
    {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/comments/create/change")
                .addPathPatterns("/articles/publish")
                .excludePathPatterns("/login")
                .excludePathPatterns("/register");
    }


ArticleServiceImpl:

代码逻辑:

1.构建Article对象,并添加对应属性
2.作者id 从当前的登录用户获取 (该登录接口需要加入到登录拦截器中,不然获得到的id可能为空)
3.标签 要将标签加入到关联列表中(ms_article_tag),需要先插入文章到数据库中,让数据库生成文章id,再循环遍历插入到数据库表中
4.将文章内容存储到ms_article_body表中,插入之后还需要把BodyId返回给article对象,让article对象更新自己表中的BodyId
package com.example.blog.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.blog.dao.mapper.ArticleBodyMapper;
import com.example.blog.dao.mapper.ArticleMapper;
import com.example.blog.dao.mapper.ArticleTagMapper;
import com.example.blog.dao.mapper.CategoryMapper;
import com.example.blog.dos.Archives;
import com.example.blog.entity.*;
import com.example.blog.service.*;
import com.example.blog.utils.UserThreadLocal;
import com.example.blog.vo.*;
import com.example.blog.vo.params.ArticleBodyParam;
import com.example.blog.vo.params.ArticleParam;
import com.example.blog.vo.params.ArticleTag;
import com.example.blog.vo.params.PageParams;
import org.joda.time.DateTime;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.sql.Date;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class ArticleServiceImpl implements ArticleService
{

    @Autowired
    private ArticleMapper articleMapper;

    @Autowired
    private TagService tagService;

    @Autowired
    private SysUserService sysUserService;

    @Autowired
    private ArticleBodyMapper articleBodyMapper;

    @Autowired
    private CategoryService categoryService;

    @Autowired
    private ArticleTagMapper articleTagMapper;

    /**
     * 文章发布
     * @param articleParam
     * @return
     */
    @Override
    public Result publish(ArticleParam articleParam)
    {
        /**
         * 1.发布文章 目的是构建Article对象,并添加对应属性
         * 2.作者id 从当前的登录用户获取 (该接口需要加入到登录拦截器中,不然获得到的id可能为空)
         * 3.标签 要将标签加入到 关联列表中(ms_article_tag),需要先插入文章到数据库中,让数据库生成文章id
         * 4.body 文章内容存储,插入之后还需要把articleBodyId返回给article对象
         *
         */
        //1.
        Article article = new Article();
        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(articleParam.getCategory().getId());

        //2.
        SysUser sysUser = UserThreadLocal.get();
        article.setAuthorId(sysUser.getId());
        //3.
        //文章插入到数据库之后,就会生成一个文章id
        articleMapper.insert(article);
        Long articleId = article.getId();

        List<TagVo> tags = articleParam.getTags();
        if (null != tags)
        {
            for(TagVo tagVo:tags)
            {
                ArticleTag articleTag = new ArticleTag();
                articleTag.setTagId(tagVo.getId());
                articleTag.setArticleId(articleId);
                articleTagMapper.insert(articleTag);
            }
        }
        //4.内容存储
        ArticleBody articleBody = new ArticleBody();
        ArticleBodyParam articlebodyParam = articleParam.getBody();
        articleBody.setContent(articlebodyParam.getContent());
        articleBody.setContentHtml(articlebodyParam.getContentHtml());
        articleBody.setArticleId(articleId);
        articleBodyMapper.insert(articleBody);
        article.setBodyId(articleBody.getId());
        //插入articleBodyId之后,需要进行article在数据库中的更新
        articleMapper.updateById(article);

        Map<String,String> map = new HashMap<>();
        //这里也有精度损失的问题,需要在ArticleVo类中的id设置序列化
        map.put("id",articleId.toString());

        return Result.success(map);
    }
}

AOP记录日志:

首先添加一个注解,加上此注解的方法就是一个切点方法

package com.example.blog.common.aop;


import java.lang.annotation.*;

//ElementType.TYPE代表可以放在类上面;ElementType.METHOD代表可以放在方法上
@Target({ElementType.TYPE, ElementType.METHOD})

/**
 * Annotations are to be recorded in the class file by the compiler and
 * retained by the VM at run time, so they may be read reflectively.
 *
 * @see java.lang.reflect.AnnotatedElement
 */
@Retention(RetentionPolicy.RUNTIME)

//标记注解,用于描述其它类型的注解应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。
@Documented

public @interface LogAnnotation 
{
    String module() default "";

    String operator() default "";
}

添加切面

package com.example.blog.common.aop;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.api.R;
import com.example.blog.utils.HttpContextUtils;
import com.example.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
@Aspect //切面(通知+切点)
@Slf4j
public class LogAspect
{
    //切点就是该注解,这个注解加在哪里,哪里就是切点
    @Pointcut("@annotation(com.example.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,以及对应切点方法的详情(方法名、参数)
        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("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]);
        log.info("params:{}",params);

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


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

记录调用该接口的ip地址

package com.example.blog.utils;

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

import javax.servlet.http.HttpServletRequest;

public class HttpContextUtils
{
    public static HttpServletRequest getHttpServletRequest()
    {
        return ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
    }
}
package com.example.blog.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;

/****
 *
 * Project Name:spring-boot-seckill
 * <p>从http请求中获取ip地址<br>
 *
 * @ClassName: IPUtils
 * @date 2019年1月3日  下午6:30:02
 *
 * @author youqiang.xiong
 * @version 1.0
 * @since
 * @see
 */
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;
    }
}

比如我需要查询文章列表的日志:

    /**
     * 分页查询 article数据库表
     * @param pageParams
     * @return
     */
    @Override
    //加上此注解 代表要对此接口记录日志
    @LogAnnotation(module="文章",operator="获取文章列表")
    public Result listArticle(PageParams pageParams)
    {
        Page<Article> page = new Page<>(pageParams.getPage(), pageParams.getPageSize());
        LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();/*查询器*/
        queryWrapper.orderByDesc(Article::getWeight);/*是否置顶进行排序*/
        queryWrapper.orderByDesc(Article::getCreateDate);/*根据创建时间进行降序排序 order by create_date desc*/
        Page<Article> articlePage = articleMapper.selectPage(page, queryWrapper);/*等同于编写一个普通list查询,mybatis-plus自动替你分页*/
        List<Article> records = articlePage.getRecords();
        List<ArticleVo> articleVoList = copyList(records,true,true);
        return Result.success(articleVoList);
    }

修正文章归档bug;

ArticleServiceImpl:

    /**
     * 文章归档
     * @return
     */
    @Override
    public Result listArchives()
    {
        /**
         * 对每年每月的文章进行分组,并统计它们的数量
         */
        /* select year(create_date) as year,month(create_date) as month,count(*) as count from ms_article
        group by year,month*/
        List<Archives> archivesList = articleMapper.listArchives();
        return Result.success(archivesList);
    }
articleMapper.xml:
后台用的是System.currentTimeMillis()方法设置的时间,从数据库获取需要FROM_UNIXTIME(create_date/1000,'%Y')获取对应年份,月份(毫秒值除以1000,也就是获取它的秒)
<?xml version="1.0" encoding="UTF-8" ?>
<!--MyBatis配置文件-->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.example.blog.dao.mapper.ArticleMapper">

    <select id="listArchives" resultType="com.example.blog.dos.Archives">
       select FROM_UNIXTIME(create_date/1000,'%Y') as year, FROM_UNIXTIME(create_date/1000,'%m') as month,count(*) as count from ms_article group by year,month
    </select>
</mapper>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值