基于SSM的个人博客

1.前言

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

2.技术总结

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

3.主要功能

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

4.数据库设计

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

5.项目结构

在这里插入图片描述

6.部分页面功能

(1)博客首页

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

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

<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" οnclick="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" οnclick="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>

后台代码:

@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代码:

<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代码:

<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>
<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代码:

<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代码:

<script>
	window.onload = function() {
		setTimeout(function(){
			siteLoading.classList.remove('active')
		},1000);
}
</script>

CSS样式:

.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;
}
原文地址:https://www.cnblogs.com/does/p/13621481.html