admin管理员组文章数量:1616697
框架搭建
Intellij IDEA + springboot + maven
- 新建项目Spring Initializer…
- 引入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 // 指定日志文件位置
异常处理
- 定义错误页面
- 404
- 500
- error
- 全局处理异常
@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的获取方法如代码中所示。
页面处理
- 静态页面导入project
- 使用thymeleaf布局
- 错误页面美化
使用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”)
表示受什么依赖
版权声明:本文标题:一个博客项目-1 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://m.elefans.com/xitong/1728741673a1171134.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论