admin管理员组

文章数量:1616423

框架搭建

Intellij IDEA + springboot + maven

  1. 新建项目Spring Initializer…
  2. 引入SpringBoot模块
web
Thymeleaf: 前端页面模板
JPA:数据
MySQL:数据库驱动
Aspects:Spring AOP
DevTools:开发工具

配置文件
application.properties与application.yml

spring:
  thymeleaf:
    mode: HTML
  profiles:
    active: dev
spring:
  datasource:  // 数据库相关配置
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/blog?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
    username: root
    password: root

  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true

logging:
  level: //打印日志级别
    root: info
    com.lrm: debug  // 个性化设置包
  file: log/blog-dev.log  // 指定日志文件位置

异常处理

  1. 定义错误页面
  • 404
  • 500
  • error
  1. 全局处理异常
@ControllerAdvice  // 拦截标注有注解的控制器
public class ControllerExceptionHandler {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @ExceptionHandler(Exception.class) // 标识可以做异常处理
    public ModelAndView exceptionHandler(HttpServletRequest request, Exception e) {

        // 记录
        logger.error("Request URL : {}, Exception : {}", request.getRequestURL(), e);

        if(AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) {
            throw new NotFoundException();
        }
        ModelAndView mv = new ModelAndView();
        mv.addObject("url", request.getRequestURI());
        mv.addObject("exception", e);
        mv.setViewName("error/error");
        return mv;
    }
}

日志处理

  • 记录日志内容
    • 请求url
    • 访问者ip
    • 调用方法
    • 参数args
    • 返回内容

这块内容用到了aspects,即AOP。

在项目下新建包aspect,新建LogAspect.java

package com.lmr.blog.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;

@Aspect  // 切面
@Component // 开启组件扫描
public class LogAspect {

    private final Logger logger  = LoggerFactory.getLogger(this.getClass());


    @Pointcut("execution(* com.lmr.blog.web.*.*(..))")  //该注解声明这是一个切面,括号内容规定切面拦截哪些类
    public void log() {

    }

    @Before("log()")
    public void doBefore(JoinPoint joinPoint) {
    
    	// 获取request
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        String url = request.getRequestURL().toString();
        String ip = request.getRemoteAddr();
        //类型名+方法
        String classMethod = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        RequestLog requestLog = new RequestLog(url, ip, classMethod, args);

        logger.info("Request : {}", requestLog);
    }

    @After("log()")
    public void doAfter() {
//        logger.info("--------------doAfter---------------");
    }

    @AfterReturning(returning = "result", pointcut = "log()")
    public void doAfterReturn(Object result) {
        logger.info("Result: {}", result);
    }

    private class RequestLog {
        private String url;
        private String ip;
        private String classMethod;
        private Object[] args;

        public RequestLog(String url, String ip, String classMethod, Object[] args) {
            this.url = url;
            this.ip = ip;
            this.classMethod = classMethod;
            this.args = args;
        }

        @Override
        public String toString() {
            return "{" +
                    "url='" + url + '\'' +
                    ", ip='" + ip + '\'' +
                    ", classMethod='" + classMethod + '\'' +
                    ", args=" + Arrays.toString(args) +
                    '}';
        }
    }
}

代码中有很多注解:
@Aspect // 切面
@Component // 开启组件扫描
二者是必须的

@Pointcut("execution(* com.lmr.blog.web.*.*(..))")
该注解声明这是一个切面,括号内容规定切面拦截哪些类,表示拦截web下所有类,web是一个目录

@Before("log()")
在注解之前执行
@After("log()")
在注解之后执行

这里构建了一个集成类RequestLog,将log记录的内容包裹进去,url,ip,classMethod,args的获取方法如代码中所示。

页面处理

  1. 静态页面导入project
  2. 使用thymeleaf布局
  3. 错误页面美化

使用thymeleaf布局

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3/1999/xhtml">
<head th:fragment="head(title)">
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title th:replace="${title}">博客详情</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr/semantic-ui/2.2.4/semantic.min.css">
    <link rel="stylesheet" href="../static/css/typo.css" th:href="@{/css/typo.css}">
    <link rel="stylesheet" href="../static/css/animate.css" th:href="@{/css/animate.css}">
    <link rel="stylesheet" href="../static/lib/prism/prism.css" th:href="@{/lib/prism/prism.css}">
    <link rel="stylesheet" href="../static/lib/tocbot/tocbot.css" th:href="@{/lib/tocbot/tocbot.css}">
    <link rel="stylesheet" href="../static/css/me.css" th:href="@{/css/me.css}">
</head>
<body>
<!--导航-->
<nav th:fragment="menu(n)" class="ui inverted attached segment m-padded-tb-mini m-shadow-small" >
    <div class="ui container">
        <div class="ui inverted secondary stackable menu">
            <h2 class="ui teal header item">Blog</h2>
            <a href="#" class="active m-item item m-mobile-hide" th:classappend="${n==1} ? 'active'"><i class="mini home icon"></i>首页</a>
            <a href="#" class="m-item item m-mobile-hide" th:classappend="${n==2} ? 'active'"><i class="mini idea icon"></i>分类</a>
            <a href="#" class="m-item item m-mobile-hide" th:classappend="${n==3} ? 'active'"><i class="mini tags icon"></i>标签</a>
            <a href="#" class="m-item item m-mobile-hide" th:classappend="${n==4} ? 'active'"><i class="mini clone icon"></i>归档</a>
            <a href="#" class="m-item item m-mobile-hide" th:classappend="${n==5} ? 'active'"><i class="mini info icon"></i>关于我</a>
            <div class="right m-item item m-mobile-hide">
                <div class="ui icon inverted transparent input m-margin-tb-tiny">
                    <input type="text" placeholder="Search....">
                    <i class="search link icon"></i>
                </div>
            </div>
        </div>
    </div>
    <a href="#" class="ui menu toggle black icon button m-right-top m-mobile-show">
        <i class="sidebar icon"></i>
    </a>
</nav>

<!--底部footer-->
<footer th:fragment="footer" class="ui inverted vertical segment m-padded-tb-massive">
    <div class="ui center aligned container">
        <div class="ui inverted divided stackable grid">
            <div class="three wide column">
                <div class="ui inverted link list">
                    <div class="item">
                        <img src="../static/images/wechat.jpg" th:src="@{/images/wechat.jpg}" class="ui rounded image" alt="" style="width: 110px">
                    </div>
                </div>
            </div>
            <div class="three wide column">
                <h4 class="ui inverted header m-text-thin m-text-spaced " >最新博客</h4>
                <div class="ui inverted link list">
                    <a href="#" class="item m-text-thin">用户故事(User Story)</a>
                    <a href="#" class="item m-text-thin">用户故事(User Story)</a>
                    <a href="#" class="item m-text-thin">用户故事(User Story)</a>
                </div>
            </div>
            <div class="three wide column">
                <h4 class="ui inverted header m-text-thin m-text-spaced ">联系我</h4>
                <div class="ui inverted link list">
                    <a href="#" class="item m-text-thin">Email:lirenmi@163</a>
                    <a href="#" class="item m-text-thin">QQ:865729312</a>
                </div>
            </div>
            <div class="seven wide column">
                <h4 class="ui inverted header m-text-thin m-text-spaced ">Blog</h4>
                <p class="m-text-thin m-text-spaced m-opacity-mini">这是我的个人博客、会分享关于编程、写作、思考相关的任何内容,希望可以给来到这儿的人有所帮助...</p>
            </div>
        </div>
        <div class="ui inverted section divider"></div>
        <p class="m-text-thin m-text-spaced m-opacity-tiny">Copyright © 2016 - 2017 Lirenmi Designed by Lirenmi</p>
    </div>
</footer>

<th:block th:fragments="script">
    <script src="https://cdn.jsdelivr/npm/jquery@3.2/dist/jquery.min.js"></script>
    <script src="https://cdn.jsdelivr/semantic-ui/2.2.4/semantic.min.js"></script>
    <script src="//cdn.jsdelivr/npm/jquery.scrollto@2.1.2/jquery.scrollTo.min.js"></script>

    <script src="../static/lib/prism/prism.js" th:src="@{/lib/prism/prism.js}"></script>
    <script src="../static/lib/tocbot/tocbot.min.js" th:src="@{/lib/tocbot/tocbot.min.js}"></script>
    <script src="../static/lib/qrcode/qrcode.min.js" th:src="@{/lib/qrcode/qrcode.min.js}"></script>
    <script src="../static/lib/waypoints/jquery.waypoints.min.js" th:src="@{/lib/waypoints/jquery.waypoints.min.js}"></script>
</th:block>
</body>

</html>

分别加上

th:fragment=“head(title)”
th:replace="_fragments :: head(~{::title})"
th:fragment=“menu(1)”
th:replace="_fragments :: menu(1)"
th:fragment=“footer”
th:replace="_fragments :: footer"

这样就可以使用_fragments.html文件中定义的样式了。

设计与规范

  • 实体设计
    • 实体类
      • 博客blog
      • 博客分类Type
      • 博客标签Tag
      • 博客评论Comment
      • 用户User
    • 实体关系

      创建5个实体类,分别生成构造方法,getter and setter,toString等。

以blog.java为例:

package com.lmr.blog.po;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * Created by limi on 2017/10/14.
 */
@Entity
@Table(name = "t_blog")
public class Blog {

    @Id
    @GeneratedValue
    private Long id;

    private String title;
    private String content;
    private String firstPicture;
    private String flag;
    private Integer views;
    private boolean appreciation;
    private boolean shareStatement;
    private boolean commentabled;
    private boolean published;
    private boolean recommend;
    @Temporal(TemporalType.TIMESTAMP)
    private Date createTime;
    @Temporal(TemporalType.TIMESTAMP)
    private Date updateTime;

    @ManyToOne
    private Type type;

    @ManyToMany(cascade = {CascadeType.PERSIST})
    private List<Tag> tags = new ArrayList<>();


    @ManyToOne
    private User user;

    @OneToMany(mappedBy = "blog")
    private List<Comment> comments = new ArrayList<>();

    public Blog() {
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getFirstPicture() {
        return firstPicture;
    }

    public void setFirstPicture(String firstPicture) {
        this.firstPicture = firstPicture;
    }

    public String getFlag() {
        return flag;
    }

    public void setFlag(String flag) {
        this.flag = flag;
    }

    public Integer getViews() {
        return views;
    }

    public void setViews(Integer views) {
        this.views = views;
    }

    public boolean isAppreciation() {
        return appreciation;
    }

    public void setAppreciation(boolean appreciation) {
        this.appreciation = appreciation;
    }

    public boolean isShareStatement() {
        return shareStatement;
    }

    public void setShareStatement(boolean shareStatement) {
        this.shareStatement = shareStatement;
    }

    public boolean isCommentabled() {
        return commentabled;
    }

    public void setCommentabled(boolean commentabled) {
        this.commentabled = commentabled;
    }

    public boolean isPublished() {
        return published;
    }

    public void setPublished(boolean published) {
        this.published = published;
    }

    public boolean isRecommend() {
        return recommend;
    }

    public void setRecommend(boolean recommend) {
        this.recommend = recommend;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public Date getUpdateTime() {
        return updateTime;
    }

    public void setUpdateTime(Date updateTime) {
        this.updateTime = updateTime;
    }

    public Type getType() {
        return type;
    }

    public void setType(Type type) {
        this.type = type;
    }

    public List<Tag> getTags() {
        return tags;
    }

    public void setTags(List<Tag> tags) {
        this.tags = tags;
    }


    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }


    public List<Comment> getComments() {
        return comments;
    }

    public void setComments(List<Comment> comments) {
        this.comments = comments;
    }

    @Override
    public String toString() {
        return "Blog{" +
                "id=" + id +
                ", title='" + title + '\'' +
                ", content='" + content + '\'' +
                ", firstPicture='" + firstPicture + '\'' +
                ", flag='" + flag + '\'' +
                ", views=" + views +
                ", appreciation=" + appreciation +
                ", shareStatement=" + shareStatement +
                ", commentabled=" + commentabled +
                ", published=" + published +
                ", recommend=" + recommend +
                ", createTime=" + createTime +
                ", updateTime=" + updateTime +
                '}';
    }
}

注解:
@Id
声明实体
@Table(name = “t_blog”)
会在数据库中生成相应表
@Id
主键
@GeneratedValue
自动生成

@Temporal(TemporalType.TIMESTAMP)
private Date createTime;
括号中代表Date样式(包含日期和时间)

OneToMany等4种类型
其中OneToMany的主键上需要声明(mappedBy=“name”)
表示受什么依赖

本文标签: 项目博客