1.前言

学习了SSM框架后练手,写这个博客大概花了一个多星期。博客基本功能都有实现,后台代码写地比较简单。前端页面仿造Hexo博主的博客页面(传送门:https://jerryc.me/),由于本人前端技术有限,只能写一个大概的页面。

2.技术总结

前端:bootstrap+layui
(bootstrap主要用于实现响应式,layui写后台管理系统页面)
后台:SpringMVC+Spring+Mybatis
(Maven搭建环境)
数据库:Mysql

3.主要功能

添加文章、管理文章、显示/隐藏文章、添加标签分类、管理标签分类、评论文章、评论管理、分享文章、友链的管理、个人资料更新

4.数据库设计

文章表
在这里插入图片描述
分类表
在这里插入图片描述
标签表
在这里插入图片描述
标签-文章映射表
在这里插入图片描述
评论表
在这里插入图片描述
友链表
在这里插入图片描述
用户表
在这里插入图片描述

5.项目结构

在这里插入图片描述

6.部分页面功能

(1)博客首页

在这里插入图片描述
首页文章列表分页功能用的是layui的分页模块

HTML代码:
在这里插入图片描述
JS代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
<script>
window.onload = function() {
loadData(); //请求文章数据
getPage(); //分页操作

}
var page = 1; //设置首页页码
var limit = 5; //设置一页显示的条数
var count; //总条数

function loadData() {
$.ajax({
type : "post",
url : "PostController/findByPageDesc",//对应controller的URL
async : false,
dataType : 'json',
data : {
"curr" : page,
"nums" : limit,
},
success : function(msg) {
count = msg.count; //设置总条数
var html = [];
var post = msg.data;

for (var i = 0; i < post.length; i++) {
html.push('<div class=" articleboder">'
+ '<div id="'
+ post[i].id
+'" class="col-sm-6 col-xs-12 articleimg" onclick="javascript:article(this)">'
+ '<img src="./upload/'+post[i].img+'"/></div>'
+ '<div class="col-sm-6 col-xs-12 articleintro">'
+ '<div class="hidden-xs articlehidden"></div>'
+ '<div class="visible-xs articlevisible"></div>'
+ '<div id="'
+ post[i].id
+ '" class="title" onclick="javascript:article(this)">'
+ post[i].title
+ '</div>'
+ '<div class="articledate">'
+ ' <span><i class="fa fa-calendar" aria-hidden="true"></i><a>'
+ post[i].timeString
+ '</a></span>'
+ ' <span><a>|</a></span>'
+ ' <span><i class="fa fa-inbox article-meta__icon" aria-hidden="true"></i><a>'
+ post[i].typeString
+ '</a></span>'
+ '</div>'
+ '<div class="intro">'
+ post[i].summary
+ '</div>'
+ '</div>'
+ '</div>');
}
$(".content").empty().append(html);
}
});
}

function getPage() {
layui.use('laypage', function() {
var laypage = layui.laypage;
//执行一个laypage实例
laypage.render({
elem : 'demo2', //注意,这里的 demo2 是 ID,不用加 # 号
count : count, //数据总数,从服务端得到
limit : limit, //每页条数设置
theme : '#1E9FFF',
jump : function(obj, first) {
//obj包含了当前分页的所有参数,比如:
console.log(obj.curr); //得到当前页,以便向服务端请求对应页的数据。
console.log(obj.limit); //得到每页显示的条数
page = obj.curr; //改变当前页码
limit = obj.limit;
//首次不执行
if (!first) {
loadData(); //加载数据
}
}
});
});
}
</script>

后台代码:

1
2
3
4
5
6
7
8
9
10
11
12
@RequestMapping("/findByPageDesc")
public @ResponseBody String findByPageDesc(Integer curr, Integer nums) throws JsonProcessingException{
int pagenum=(curr - 1)*nums;
List<Post> posts = postService.findByPageDesc(pagenum, nums);
JSONObject object = new JSONObject();
object.put("code", 0);
object.put("msg", "");
object.put("count", postService.count());
object.put("data", posts);
System.out.println(object.toJSONString());
return object.toJSONString();
}

(2)随机颜色大小标签

在这里插入图片描述
HTML代码:
在这里插入图片描述
JS代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<script>
//分页
window.onload = function() {
loadData(); //请求数据
label();
}

function loadData() {
$.ajax({
type : "post",
url : "TagController/selectAll",//对应controller的URL
async : false,
dataType : 'json',
success : function(msg) {
var html = [];
var tag = msg.data;
for (var i = 0; i < tag.length; i++) {
html.push('<a href="label_detail.jsp?tagid=' + tag[i].id
+ '">' + tag[i].tag + '</a>');
}
$(".content").empty().append(html);
}
});
}

function label() {
$(document).ready(function() {
var obj = $("#wrap a");//获取a标签中的数据
function rand(num) {
//parseInt();将字符串转为整数
//Math.random();生成随机数
return parseInt(Math.random() * num + 1);
}

function randomcolor() {
var str = Math.ceil(Math.random() * 16777215).toString(16);
if (str.length < 6) {
str = "0" + str;
}
return str;
}

for (len = obj.length, i = len; i--;) {
obj[i].style.left = rand(600) + "px";//标签左右间距
obj[i].style.top = rand(400) + "px";//标签上下间距
obj[i].className = "color" + rand(5);
obj[i].style.zIndex = rand(5);//设置元素的堆叠顺序
obj[i].style.fontSize = rand(5) + 18 + "px";//随机字体大小这里是18-23
obj[i].style.color = "#" + randomcolor();//字体颜色
obj[i].style.padding = rand(15) + "px";
}
});
}
</script>

后台返回标签的Json字符串

(3)写文章

在这里插入图片描述
文章内容使用的是百度的ueditor,百度ueditor下载JSP版本,把解压后的文件放进项目目录里,查看官方文档进行配置。

(4)文章管理页面

在这里插入图片描述
文章管理页面用的是layui的table模块,有编辑、删除和显示(隐藏)三个功能。
表格数据渲染:
HTML代码:

<table id="test" lay-filter="test"></table>

JS代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script type="text/html" id="toolbarDemo">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm" lay-event="getCheckData">获取选中行数据</button>
<button class="layui-btn layui-btn-sm" lay-event="getCheckLength">获取选中数目</button>
<button class="layui-btn layui-btn-sm" lay-event="isAll">验证是否全选</button>
</div>
</script>

<script type="text/html" id="barDemo">
<a class="layui-btn layui-btn-sm layui-bg-blue" lay-event="edit">编辑</a>
<a class="layui-btn layui-btn-sm layui-btn-danger" lay-event="del">删除</a>
</script>

<script id="switchTpl" type="text/html">
<input type="checkbox" name="display" value = {{d.display}} lay-skin="switch" lay-text="显示|隐藏" lay-filter="display" {{ d.display == '1' ? 'checked' : '' }}>
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
<script src="${pageContext.request.contextPath}/resources/layui/layui.all.js"></script>
<script>
//JavaScript代码区域
layui.use('element', function(){
var element = layui.element;
});

//显示或隐藏文章
layui.use('form',function(){
var form = layui.form
form.on('switch(display)', function(obj){
//根据业务判断是开启还是关闭
var state = obj.elem.checked?0:1;
//取数据(根据索引table.cache里面的行数据)
var index = obj.othis.parents('tr').attr("data-index");
var id = tableData[index].id;
$.ajax({
url: '../PostController/display?id='+id+'&display='+state,
type: "post",
dataType:"json",
success: function(suc) {
if(suc.code === 1) {
if(suc.msg === 0){
layer.msg("文章已隐藏", {
icon: 6
});
}else if(suc.msg === 1){
layer.msg("文章已显示", {
icon: 6
});
}
} else {
layer.msg("设置失败,请稍后再试!", {
icon: 5
});
}
}
});
});
});

var tableData;

layui.use('table', function(){
var table = layui.table;

table.render({
elem: '#test'
,url:'../PostController/findByPage'
,method:'post'
,limits : [5,10,15,20]
,limit : 10
,request: {
pageName: 'curr' ,//页码的参数名称,默认:page
limitName: 'nums' //每页数据量的参数名,默认:limit
}
,toolbar: '#toolbarDemo' //开启头部工具栏,并为其绑定左侧模板
,defaultToolbar: ['filter', 'exports', 'print', { //自定义头部工具栏右侧图标。如无需自定义,去除该参数即可
title: '提示'
,layEvent: 'LAYTABLE_TIPS'
,icon: 'layui-icon-tips'
}]
,title: '用户数据表'
,cols: [[
{type: 'checkbox', fixed: 'left'}
,{field:'id', title:'ID', width:70, unresize: true, sort: true}
,{field:'title', title:'标题',width:328}
,{field:'typeString', title:'分类',width:158}
,{field:'clickhit', title:'点击数',width:125, sort: true}
,{field:'replyhit', title:'评论数',width:125, sort: true}

,{field:'timeString', title:'发表时间',width:188, sort: true}

,{field:'display', title:'显示状态',width:120,templet:"#switchTpl"}

,{fixed: '', title:'操作', fixed: 'right', width:132, toolbar: '#barDemo'}
]]
,page: true
,id:"tableIns"
,done:function(){
tableData = table.cache.tableIns;
}
});

//头工具栏事件
table.on('toolbar(test)', function(obj){
var checkStatus = table.checkStatus(obj.config.id);
switch(obj.event){
case 'getCheckData':
var data = checkStatus.data;
layer.alert(JSON.stringify(data));
break;
case 'getCheckLength':
var data = checkStatus.data;
layer.msg('选中了:'+ data.length + ' 个');
break;
case 'isAll':
layer.msg(checkStatus.isAll ? '全选': '未全选');
break;

//自定义头工具栏右侧图标 - 提示
case 'LAYTABLE_TIPS':
layer.alert('这是工具栏右侧自定义的一个图标按钮');
break;
};
});

//监听行工具事件
table.on('tool(test)', function(obj){
var data = obj.data
,layEvent = obj.event; //获得 lay-event 对应的值
console.log(obj)
switch(layEvent){
case 'del':
var delIndex = layer.confirm('真的删除"' + data.title + '"吗?', function(delIndex) {
$.ajax({
url: '../PostController/delete?id='+data.id,
type: "post",
dataType:"json",
success: function(suc) {
if(suc.code === 1) {
obj.del(); //删除对应行(tr)的DOM结构,并更新缓存
layer.close(delIndex);
console.log(delIndex);
layer.msg("删除成功", {
icon: 1
});
} else {
layer.msg("删除失败", {
icon: 5
});
}
}
});
layer.close(delIndex);
});
break;
case 'edit':
/* $.ajax({
url: '../PostController/editPost?id='+data.id,
type: "get"

}); */
window.location.href="../PostController/editPost?id="+data.id;
break;
}
});
});
</script>

后台查询、删除、显示(隐藏)文章的Controller省略

(5)图片上传功能和回显

在这里插入图片描述
在这里插入图片描述
“我的友链”和“基本资料”页面都有图片上传,用的是layui的文件上传功能。图书上传成功后,前端页面会回显图片。
HTML代码:
在这里插入图片描述
JS代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<script src="${pageContext.request.contextPath}/resources/layui/layui.all.js"></script>
<script>
//JavaScript代码区域
layui.use('element', function(){
var element = layui.element;

});

window.onload= function () {
loadImg();
}

layui.use('upload', function(){
var upload = layui.upload;
//普通图片上传
upload.render({
elem: '#test1'
,url: '../UserController/uploadImg'
,accept: 'images'
,acceptMime: 'image/*'
,size: '1024*5'
,before: function(obj){
//预读本地文件示例,不支持ie8
obj.preview(function(index, file, result){
$('#demo1').attr('src', result); //图片链接(base64)
});
}
,done: function(res, input){
layer.msg('头像上传成功',{icon:6});
console.log(res); //如:{"code":0 ,"msg":"","url":"http://cdn.abc.com/123.jpg"'}
document.getElementById("myimg").innerHTML=res.name;

}
});
});

function loadImg(){
$.ajax({
type:"post",
url:"../UserController/photo",//对应controller的URL
dataType: 'json',
success:function(msg){
document.getElementById('demo1').src=msg.name;
}
});
}
</script>

(6)页面加载动画

加载动画并不是真的会等待页面数据加载完成后才隐藏,只是等待1000毫秒后隐藏。
在这里插入图片描述
HTML代码:
在这里插入图片描述
JS代码:

1
2
3
4
5
6
7
<script>
window.onload = function() {
setTimeout(function(){
siteLoading.classList.remove('active')
},1000);
}
</script>

CSS样式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
.wrapper {
height: 200px;
width: 200px;
border: 1px solid #fff;
/* 将圆形动画定位到正中 */
position: relative;
}

.wrapper::before,
.wrapper::after{
content: '';
height: 10px;
width: 10px;
background-color: black;
border-radius: 100%;
/* 将圆形动画定位到正中 */
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
margin: auto;
animation: dada 2s linear infinite;
}

.wrapper::after {
animation-delay: 1s;
}

@keyframes dada {
0% {
height: 0px;
width: 0px;
opacity: 1;
}
100% {
height: 100px;
width: 100px;
opacity: 0;
}
}

.loading {
display: none;
background-color: #fff;
position: fixed;
top: 0;
left: 0;
height: 100%;
width: 100%;
z-index: 999;
justify-content: center;
align-items: center;
}

.loading.active {
display: flex;
}