技术栈:
- 后端:Spring + Springmvc +
mybatis(mybatis plus) - 后端脚手架:Spring Boot 2.2.2
模板: thymeleaf模板- 数据库:MySQL5.5
- 前端框架:vue 3
- 前端脚手架:vue-cli 3
- UI:
Semantic UI框架、element UI - 前端路由:vue-router
- 权限管理:shiro
- 缓存:redis
工具与环境:
IDEAeclipse- Maven 3
- JDK 8
Axure RP 8- lombok - 简化代码
1.2 功能规划
2、页面设计与开发
2.1 设计
页面规划:
前端展示:首页、详情页、分类、标签、归档、关于我
后台管理:模板页
2.2 页面开发
-
vue
-
iview
Semantic UI官网<mark>开始时候用semantic UI(各种问题),逐渐替换</mark>
Semantic UI中文官网
Semantic UI 中文 API -
webpack
-
背景图片资源
使用:canvas-ribbon.js -
古诗api - https://www.jinrishici.com/#
-
博客园 大佬 - https://www.cnblogs.com/gaoya666/category/1222505.html
2.3 插件集成
-
Pjax 异步加载 - github【vue-router 实现SPA
】
《Laravel-Pjax 使用》 - https://zhuanlan.zhihu.com/p/89629239
《jquery-pjax中文文档 - BSify》 - http://bsify.admui.com/jquery-pjax/
《如何将Pjax整合进网站,实现全站无刷新加载?》 - https://cloud.tencent.com/developer/article/1147511
《过渡页面 》 - https://www.jianshu.com/p/808a647dc324
(或者使用:history.js - https://github.com/balupton/history.js) -
openmoji - 免费开源表情符号
-
动画 animate.css
用 semantic 自带的 -
代码高亮 prism
异步加载 加上Prism.highlightAll();
http://www.mamicode.com/info-detail-1041544.html -
目录生成 Tocbot
editor.md 内置目录 -[toc]
-
《给博客添加夜间模式》 - https://imjad.cn/archives/code/add-night-mode-to-blog/
-
[文字]图标生成
http://www.network-science.de/ascii/
http://patorjk.com/software/taag/#p=display&f=Graffiti&t=Type%20Something%20 -
小图标 - www.easyicon.net
2.4 工具
- api 测试 - postman - https://www.postman.com/
- 开发:sts、vscode
- 环境:java8、node.js
2.5 页面原型
前端页面
登录
welcome页
后台页面
图片上传 - 参考博客园:https://upload.cnblogs.com/avatar/crop
3.1 构建与配置
1、引入Spring Boot模块:
- web
- Thymeleaf
JPAmybatis - plus- MySQL
- Aspects
- DevTools
2、application.yml配置
- 使用 thymeleaf 3
pom.xml:
<thymeleaf.version>3.0.2.RELEASE</thymeleaf.version>
<thymeleaf-layout-dialect.version>2.1.1</thymeleaf-layout-dialect.version>
application.yml:
spring:
thymeleaf:
mode: HTML
# 暂时没用
# 默认 HTML5 、 这里改html3也能用
- 数据库连接配置
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/blog?useUnicode=true&characterEncoding=utf-8
username: root
password: root
# 用 mybatis
jpa:
hibernate:
ddl-auto: update
show-sql: true
#server
server:
port: 80
servlet:
context-path: /
-
日志配置
application.yml:
logging:
level:
root: info
com.imcoding: debug
file: log/imcoding.log
logback-spring.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<!--包含Spring boot对logback日志的默认配置-->
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
<property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}/spring.log}"/>
<include resource="org/springframework/boot/logging/logback/console-appender.xml" />
<!--重写了Spring Boot框架 org/springframework/boot/logging/logback/file-appender.xml 配置-->
<appender name="TIME_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>${FILE_LOG_PATTERN}</pattern>
</encoder>
<file>${LOG_FILE}</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.%i</fileNamePattern>
<!--保留历史日志一个月的时间-->
<maxHistory>30</maxHistory>
<!-- Spring Boot默认情况下,日志文件10M时,会切分日志文件,这样设置日志文件会在100M时切分日志 -->
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>10MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="TIME_FILE" />
</root>
</configuration>
<!-- 1、继承Spring boot logback设置(可以在appliaction.yml或者application.properties设置logging.*属性) 2、重写了默认配置,设置日志文件大小在100MB时,按日期切分日志,切分后目录: my.2017-08-01.0 80MB my.2017-08-01.1 10MB my.2017-08-02.0 56MB my.2017-08-03.0 53MB ...... -->
- 生产环境与开发环境配置
- application-dev.yml
- application-pro.yml
3.2 异常处理
1、定义错误页面
404
500
error
2、全局处理异常
统一处理异常:
@ControllerAdvice
public class ControllerExceptionHandler {
private final Logger logger = LoggerFactory.getLogger(ControllerExceptionHandler.class);
/** * 异常处理 * @param request * @param e * @return */
@ExceptionHandler({Exception.class})
public ModelAndView handleException(HttpServletRequest request, Exception e) throws Exception {
logger.error("Request URL : {} , Exception : {}", request.getRequestURL(), e);
if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) {
throw e;
}
ModelAndView mav = new ModelAndView();
mav.addObject("url", request.getRequestURL());
mav.addObject("exception", e);
mav.setViewName("error/error");
return mav;
}
}
错误页面异常信息显示处理:
<div>
<div th:utext="'<!--'" th:remove="tag"></div>
<div th:utext="'Failed Request URL : ' + ${url}" th:remove="tag"></div>
<div th:utext="'Exception message : ' + ${exception.message}" th:remove="tag"></div>
<ul th:remove="tag">
<li th:each="st : ${exception.stackTrace}" th:remove="tag"><span th:utext="${st}" th:remove="tag"></span></li>
</ul>
<div th:utext="'-->'" th:remove="tag"></div>
</div>
3、资源找不到异常
@ResponseStatus(HttpStatus.NOT_FOUND)
public class NotFoundExcepiton extends RuntimeException {
public NotFoundExcepiton() {
}
public NotFoundExcepiton(String message) {
super(message);
}
public NotFoundExcepiton(String message, Throwable cause) {
super(message, cause);
}
}
3.4 页面处理
1、静态页面导入project
2、thymeleaf布局
- 定义fragment
- 使用fragment布局
3、错误页面美化
4、设计与规范
4.1 实体设计
实体类:
- 博客
Blog
- 博客分类
Type
- 博客标签
Tag
- 博客评论
Comment
- 用户
User
实体关系:(暂时先这样吧)
一张表一个 po 对象
Blog类:
Tag类:
Type类:
Comment类:
User类:
Role类
4.2 应用分层
4.3 约定
Service/DAO层命名约定:
- 获取单个对象的方法用
find
做前缀。Object - 获取多个对象的方法用
find
做前缀。Objects - 获取单个vo对象
findxxxinfo
。 - 获取统计值的方法用
count
做前缀。 - 插入的方法用
save
(推荐) 或做前缀。insert
- 删除的方法用
remove
(推荐) 或做前缀。delete
- 修改的方法用
update
做前缀。
如:
功能 | url | 请求方式 | 物理路径 | 转发 | 重定向 | controller | service | dao(根据 mp 看情况修改) |
---|---|---|---|---|---|---|---|---|
<mark>登录</mark>页面 | /login | /system/login | ~ | pageController.toLoginPage | ||||
user | ||||||||
用户<mark>登录</mark> | /user /<mark>login</mark> | POST | JsonResult | 前端处理 | userController.doLogin | shiro subject.login | ||
管理 页面 <mark>展示</mark> | /user 、/xxx | /system/<mark>user</mark>、/system/xxx | ~ | pageController.toSystemPage | Model add User | |||
管理 页面<mark>修改</mark> | /user /<mark>edit</mark> 、 /xxx/edit | /system/<mark>user_edit</mark> 、 /system/xxx_edit | ~ | pageController.toSystemPage<mark>Edit</mark> | Model add User | |||
用户<mark>列表</mark> | /user /<mark>list</mark>?pageCurrent=xx&&username=xx | GET | JsonResult | userController.find<mark>Page</mark>Object | userService.dofindPageObject | userDao.findPageObject | ||
用户<mark>校验/查重</mark> | /user /<mark>valid</mark>?<mark>id=xx</mark>&&xx | <mark>GET</mark> | JsonResult | userController.<mark>valid</mark>Object | userService.doValidObject | |||
用户<mark>添加</mark> | /user /<mark>{id}</mark>?xx | <mark>POST</mark> | JsonResult | userController.<mark>save</mark>Object | userService.doSaveObject | userDao.insertObject | ||
用户<mark>详情</mark> | /user /<mark>{id}</mark> | <mark>GET</mark> | JsonResult | userController.findObjectInfo | userService.dofindObjectInfo | |||
用户<mark>修改</mark> | /user /<mark>{id}</mark>?xx | <mark>PUT</mark> | JsonResult | userController.<mark>update</mark>Object | userService.doUpdateObject | userDao.updateObject | ||
用户<mark>删除</mark> | /user /<mark>{id}</mark> | <mark>DELETE</mark> | JsonResult | userController.<mark>remove</mark>Object | userService.doRemoveObject | userDao.deleteObject | ||
userState | ||||||||
用户状态(userState)<mark>列表</mark> | /us/list | GET | JsonResult | userStateController.findObjects | userStateService.doFindObjects | userStateDao.findObjects | ||
用户状态(userState)<mark>查询</mark> by userState-Id | /us/{id} | GET | JsonResult | userStateController.findObjectById | userStateService.doFindObjectById | userStateDao.findObjectById | ||
用户状态<mark>校验/查重</mark> | /us/valid?<mark>id=xx</mark>&&xxx… (添加校验 不传 id) | GET | JsonResult | userStateController.<mark>valid</mark>Object | userStateService.doValidObject | userStateDao.validObject | ||
用户状态(userState) <mark>添加</mark> | /us?xxx | POST | JsonResult | userStateController.saveObject | userStateService.doSaveObject | userStateService.insertObject | ||
用户状态(userState) <mark>修改</mark> | /us?xxx | PUT | JsonResult | userStateController.updateObject | userStateService.doUpdateObject | userStateService.updateObject | ||
用户状态(userState) <mark>删除</mark> | /us/{id} | DELETE | JsonResult | userStateController.removeObject | userStateService.doRemoveObject | userStateService.deleteObject | ||
avatar | ||||||||
图片<mark>查询</mark> | /avatar/<mark>{id}</mark> | <mark>GET</mark> | JsonResult | avatarController.<mark>find</mark>ObjectById | avatarService.doFindObjectById | avatarDao.findObjectById | ||
图片<mark>列表</mark> | /avatar/<mark>list</mark>?pageCurrent=xx&&name=xx | <mark>GET</mark> | JsonResult | avatarController.find<mark>Page</mark>Object | avatorService.doFindPageObject | avatarDao.findPageObject | ||
图片<mark>校验/查重</mark> 1.id以外 2.名字不能重复 | /avatar/<mark>valid</mark>?<mark>id=xx</mark>&&xx… | <mark>GET</mark> | JsonResult | avatarController.<mark>valid</mark>Object | avatorService.doValidObject | |||
图片<mark>上传</mark> | /avatar/load | POST | JsonResult | |||||
首页<mark>展示</mark> | / 、/index | /blog/article | ~ | pageController.toIndexPage |
状态码
状态码 | 说明 | 类型 |
---|---|---|
200 | 正常 | |
400 | 异常 |
5、后台管理功能实现
5.1 登录
1、构建登录页面和后台管理首页
2、UserService和UserRepository
3、LoginController实现登录
4、MD5加密
5、登录***
5.2 分类管理
1、分类管理页面
2、分类列表分页
{
"content":[
{"id":123,"title":"blog122","content":"this is blog content"},
{"id":122,"title":"blog121","content":"this is blog content"},
{"id":121,"title":"blog120","content":"this is blog content"},
{"id":120,"title":"blog119","content":"this is blog content"},
{"id":119,"title":"blog118","content":"this is blog content"},
{"id":118,"title":"blog117","content":"this is blog content"},
{"id":117,"title":"blog116","content":"this is blog content"},
{"id":116,"title":"blog115","content":"this is blog content"},
{"id":115,"title":"blog114","content":"this is blog content"},
{"id":114,"title":"blog113","content":"this is blog content"},
{"id":113,"title":"blog112","content":"this is blog content"},
{"id":112,"title":"blog111","content":"this is blog content"},
{"id":111,"title":"blog110","content":"this is blog content"},
{"id":110,"title":"blog109","content":"this is blog content"},
{"id":109,"title":"blog108","content":"this is blog content"}],
"last":false,
"totalPages":9,
"totalElements":123,
"size":15,
"number":0,
"first":true,
"sort":[{
"direction":"DESC",
"property":"id",
"ignoreCase":false,
"nullHandling":"NATIVE",
"ascending":false
}],
"numberOfElements":15
}
3、分类新增、修改、删除
5.3 标签管理
5.4 博客管理
1、博客分页查询
2、博客新增
3、博客修改
4、博客删除
6、前端展示功能实现
6.1 首页展示
1、博客列表
2、top分类
3、top标签
4、最新博客推荐
5、博客详情
1、Markdown 转换 HTML
- commonmark-java https://github.com/atlassian/commonmark-java
- pom.xml引用commonmark和扩展插件
<dependency>
<groupId>com.atlassian.commonmark</groupId>
<artifactId>commonmark</artifactId>
<version>0.10.0</version>
</dependency>
<dependency>
<groupId>com.atlassian.commonmark</groupId>
<artifactId>commonmark-ext-heading-anchor</artifactId>
<version>0.10.0</version>
</dependency>
<dependency>
<groupId>com.atlassian.commonmark</groupId>
<artifactId>commonmark-ext-gfm-tables</artifactId>
<version>0.10.0</version>
</dependency>
2、评论功能
- 评论信息提交与回复功能
- 评论信息列表展示功能
- 管理员回复评论功能
6.2 分类页
6.3 标签页
6.4 归档页
6.5 关于我
2.4 目录结构 (后面:命名约定为准)
/system/... 管理页面 /blog/... 游客页面 /article/... 文章页面 /user/list
/user/edit
/user/remove
/
请求、 对类的请求
templates
│
│ application-dev.yml // 配置文件
│
├─pages // 公共页面 - thymeleaf 模板路径
│ │ /login - 登录
│ │ / - 主页
│ │ /about - 关于我
│ │
│ ├─admin // 管理页面
│ │ md //编辑
│ │
│ ├─article // 文章页面
│ │ details/xxxx
│ │
│ └─fragments // _part of page
│ _topnav.html
/doLoginUI --> 登录
/doIndexUI --> /article --> / - 主页
/about - 关于我
/article/details/xxx - id=xxx文章路径
/doAdminUI --> 管理页面
/admin/md/?aritcleId=xxx - 修改id=xxx文章
/admin/
static
│
├─dist // 静态资源
│ ├─css
│ ├─js
│ ├─images // 图片
│ └─lib // 插件
3.3 日志处理
1、记录日志内容
- 请求 url
- 访问者 ip
- 调用方法 classMethod
- 参数 args
- 返回内容
2、记录日志类: (后期改为 log 类 存数据库)
@Aspect
@Component
public class LogAspect {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
/** * 定义切面 */
@Pointcut("execution(* com.imcoding.web.*.*(..))")
public void log() {
}
@Before("log()")
public void doBefore(JoinPoint joinPoint) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
String classMethod = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
ReqeustLog reqeustLog = new ReqeustLog(
request.getRequestURL().toString(),
request.getRemoteAddr(),
classMethod,
joinPoint.getArgs()
);
logger.info("Rquest ----- {}",reqeustLog);
}
@After("log()")
public void doAfter() {
//logger.info("---------- doAfter 2 ----------");
}
@AfterReturning(returning = "result",pointcut = "log()")
public void doAtfertRturning(Object result) {
logger.info("Return ------ {}",result );
}
private class ReqeustLog {
private String url;
private String ip;
private String classMethod;
private Object[] args;
public ReqeustLog(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 "ReqeustLog{" +
"url='" + url + '\'' +
", ip='" + ip + '\'' +
", classMethod='" + classMethod + '\'' +
", args=" + Arrays.toString(args) +
'}';
}
}
}