目录
UEditor
博客内容提交与展示功能测试
Controller开发
新增博客页面add_ueditor.jsp
博客详情界面detail.jsp
博客新增和展示详情功能开发
博客存储
博客标题开发
标签POJO类
TagMapper
TagService
映射文件TagMapper.xml
博客类别开发
类别POJO类
CategoryMapper
CategoryService
CategoryMapper.xml
博客内容开发
类别POJO类
ArticleMapper.xml
ConvertBlobTypeHandler
ArticleController
部署至服务器
文章数据提交
后台JS处理
ArticleController
文章数据保存至数据库
Json数据转换
ArticleMapper
ArticleService
ArticleController
ArticleMapper.xml
部署至服务器
博客内容展示
article_detail.jsp
时间格式化
分类ID与类名转换
报错汇总
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 1 path $
UEditor
UEditor 是由百度 web 前端研发部开发所见即所得富文本web编辑器,具有轻量、可定制、注重用户体验等特点。
首先去https://github.com/fex-team/ueditor下载UEditor,然后将文件拷贝至项目中。
接下来通过一个Demo对UEditor进行测试。
通过Controller的TestDemo进行测试。
@Controller
public class TestController {
@RequestMapping("/ueTest")
public String test(){
return "ueditortest";
}
}
新建一个ueditorTest.jsp,在body中调用js
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fm" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>富文本编辑器测试</title>
</head>
<body class="gray-bg">
<script name="content" type="text/plain">这里写你的初始化内容</script>
<script type="text/javascript" src="<%=basePath%>js/ueditor1_4_3/ueditor.config.js"></script>
<script type="text/javascript" src="<%=basePath%>js/ueditor1_4_3/ueditor.all.js"></script>
<!-- 实例化编辑器 -->
<script type="text/javascript">
var ue = UE.getEditor('container');
</script>
</body>
</html>
此外,为了加载网页中的静态资源,如图片,js,css,需要对servlet进行配置:
根据自己的服务器选择不同包下的DefaultServlet
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value> classpath:spring-core.xml </param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>Blog</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>Blog</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- 静态资源的加载-->
<servlet>
<servlet-name>default</servlet-name>
<!--jetty容器-->
<!-- <servlet-class>org.eclipse.jetty.servlet.DefaultServlet</servlet-class>-->
<!--Tomcat容器-->
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>listings</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.jpg</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.js</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.css</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.png</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.woff2</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.woff3</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.ttf</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.woff</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.gif</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.map</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
</web-app>
启动服务器后如下图,就说明配置成功,接下来就可以对UEditor进行编程了。
博客内容提交与展示功能测试
jsp页面:新增页面:add_ueditor 详情页:ueditor_detail
controller方法:add addContent detail
运行流程:
/add -> add方法 ->add_ueditor.jsp ->提交按钮->/addContent->detail
Controller开发
package com.tulun.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/test")
public class TestController {
private String context ;
@RequestMapping("/add")
public String test(){
return "add_ueditor";
}
//点击保存按钮,将内容提交到后台
@RequestMapping("/addContent")
public String addContent(String description,String content) {
System.out.println("content:"+content);
System.out.println("description:"+description);
context = content;
return "redirect:/test/detail";
}
//详情页面
@RequestMapping("/detail")
public String detail(Model model){
model.addAttribute("content", context);
return "ueditor_detail";
}
}
新增博客页面add_ueditor.jsp
页面中新增两个按钮,点击提交按钮调用js中的saveArticle方法进行提交
<%@ page language="java" contentType="text/html; charset=UTF-8" import="java.util.*" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fm" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%>
<% String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=GB18030">
<link href="<%=basePath%>css/bootstrap.min.css" rel="stylesheet">
<link href="<%=basePath%>css/style.css" rel="stylesheet">
<title>新增博客</title>
</head>
<body>
<div class="ibox-content">
<form method="get" class="form-horizontal">
<div class="hr-line-dashed"></div>
<div class="form-group">
<label class="col-sm-2 control-label">内容:</label>
<div class="col-sm-10">
<script type="text/plain"></script>
</div>
</div>
<div class="form-group">
<div class="col-sm-4 col-sm-offset-2">
<button class="btn btn-primary" type="button" onclick="saveArticle()">保存内容</button>
<button class="btn btn-white" type="button" onclick="cancelSaveArticle()">取消</button>
</div>
</div>
</form>
</div>
<!-- Mainly scripts -->
<script src="<%=basePath%>js/jquery-2.1.1.min.js"></script>
<script src="<%=basePath%>js/bootstrap.min.js"></script>
<%--自定义js--%>
<script src="<%=basePath%>js/article/add_article_test.js"></script>
<!-- 配置文件 -->
<script type="text/javascript" src="<%=basePath%>js/ueditor1_4_3/ueditor.config.js"></script>
<!-- 编辑器源码文件 -->
<script type="text/javascript" src="<%=basePath%>js/ueditor1_4_3/ueditor.all.js"></script>
<script type="text/javascript" charset="utf-8" src="<%=basePath%>js/ueditor1_4_3/lang/zh-cn/zh-cn.js"></script>
<script src="<%=basePath%>js/plugins/chosen/chosen.jquery.js"></script>
<script type="text/javascript">
$(document).ready(function () {
var ue = UE.getEditor('editor');
})
// 初始化多选框
$(".chosen-select").chosen({
max_selected_options:5,
no_results_text:"没有找到",
allow_single_deselect:true
});
</script>
</body>
</html>
在js文件中对saveArticle进行编写,获取到编辑器的内容,通过post方式进行提交,提交成功就跳转至detail进行显示,失败就返回add界面重新进行添加。
// 保存文章
function saveArticle(){
var arr = [];
//获取输入的内容 getContent() (包含和页面相关的标签)
arr.push(UE.getEditor('editor').getContent());
var content = arr.join("\n");
// alert(content);
// 简介 (getContentTxt() 获取纯文本内容,并且只截取前10个字)
var description = UE.getEditor('editor').getContentTxt().substring(0,10);
// 保存文章
$.ajax({
type : "POST",
url : '../test/addContent', //提交内容用这个URL
data : "content="+content+"&description="+description,
success : function(data) {
if(data.resultCode != 'success'){
//成功了就显示详情
console.log("sssssss"+content);
window.location.href = "../test/detail";
autoCloseAlert(data.errorInfo,1000);
return false;
}else{
alert("失败哦");
// 重新添加
window.location.href = "../test/add";
}
}
});
}
//取消后跳转到add
function cancelSaveArticle(){
window.location.href = "../test/add";
}
博客详情界面detail.jsp
用$获取到content的值,然后显示在页面上。
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<link rel="stylesheet" href="<%=basePath%>css/demo/style.css" type="text/css" media="all">
<title>博客详情</title>
</head>
<body class="home blog ui-c3">
<div class="content">
<article class="article-content">
${content}
</article>
</div>
</body>
</html>
网页界面:
后台打印输入:
博客新增和展示详情功能开发
- 博客新增页面:该页面主要用于新增一篇博客,其中包含了博客类别(可选择已有类别)、博客标签(可选择已有标签)、标题以及内容。
- 展示详情:标题、标签、类别、内容、作者以及发布时间
博客存储
博客内容一般较大,肯定不能用常规数据类型进行存储,因此可以使用MySQL提供的blog类型进行存储,根据具体需求选择不同的类型。
- .tinyblog:仅256个字符
- .blog 最大限制65k字节
- .mediumblog:限制16M字节
- .logblog:限制4G
博客标题开发
标签POJO类
package com.tulun.model;
/**
* Description :
* Created by Resumebb
* Date :2021/4/27
*/
public class Tag {
private Integer id;
private String tagName;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getTagName() {
return tagName;
}
public void setTagName(String tagName) {
this.tagName = tagName;
}
}
TagMapper
定义一个接口获取全部tag
public interface TagMapper {
public List<Tag> getAllTag();
}
TagService
package com.tulun.service;
import com.tulun.dao.TagMapper;
import com.tulun.model.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* Description :
* Created by Resumebb
* Date :2021/4/27
*/
@Service
public class TagService {
@Autowired
private TagMapper tagMapper;
public List<Tag> selectAllTag(){
return tagMapper.getAllTag();
}
}
映射文件TagMapper.xml
从t_tag表中查询所有数据,做一个映射
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.tulun.dao.TagMapper">
<select resultType="com.tulun.model.Tag">
select * from t_tag
</select>
</mapper>
博客类别开发
与标签开发过程一样,分别进行配置。
类别POJO类
package com.tulun.model;
/**
* Description :
* Created by Resumebb
* Date :2021/4/27
*/
public class Category {
private Integer id;
private String categoryName;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getCategoryName() {
return categoryName;
}
public void setCategoryName(String categoryName) {
this.categoryName = categoryName;
}
}
CategoryMapper
public interface CategoryMapper {
public List<Category> getAllCategory();
}
CategoryService
@Service
public class CategoryService {
@Autowired
private CategoryMapper categoryMapper;
public List<Category> selectAllCategory(){
return categoryMapper.getAllCategory();
}
}
CategoryMapper.xml
查询t_category表中的数据
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.tulun.dao.CategoryMapper">
<select resultType="com.tulun.model.Category">
select * from t_category
</select>
</mapper>
博客内容开发
首先创建对应的POJO类,所有元素均与数据库一一对应
类别POJO类
package com.tulun.model;
import java.util.Date;
/**
* Description :
* Created by Resumebb
* Date :2021/4/27
*/
public class Article {
private Integer id;
private Integer categoryId;
private String title;
private String content;
private String decription;
private Integer status;
private String author;
private Date createTime;
private Integer showCount;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getCategoryId() {
return categoryId;
}
public void setCategoryId(Integer categoryId) {
this.categoryId = categoryId;
}
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 getDecription() {
return decription;
}
public void setDecription(String decription) {
this.decription = decription;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public Integer getShowCount() {
return showCount;
}
public void setShowCount(Integer showCount) {
this.showCount = showCount;
}
@Override
public String toString() {
return "Article{" +
"id=" + id +
", catagoryId=" + categoryId +
", title='" + title + '\'' +
", content='" + content + '\'' +
", decription='" + decription + '\'' +
", status=" + status +
", author='" + author + '\'' +
", createTime=" + createTime +
", showCount=" + showCount +
'}';
}
}
ArticleMapper.xml
返回类型采用resultMap类型,这里使用到了一个类型转换的处理器类ConvertBlobTypeHandler
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.tulun.dao.ArticleMapper">
<resultMap type="com.tulun.model.Article">
<result column="content" property="content" typeHandler="com.tulun.util.ConvertBlobTypeHandler"></result>
</resultMap>
</mapper>
ConvertBlobTypeHandler
该工具类主要用于将博客中的blog类型转换为String类型。
package com.tulun.util;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import java.io.ByteArrayInputStream;
import java.io.UnsupportedEncodingException;
import java.sql.*;
/**
* @desc Blob转换String处理器,解决MyBatis存储blob字段后,出现乱码的问题
*/
public class ConvertBlobTypeHandler extends BaseTypeHandler<String> {
private static final String DEFAULT_CHARSET = "utf-8";
@Override
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
Blob blob = rs.getBlob(columnName);
if (null == blob) {
return null;
}
byte[] returnValue = null;
returnValue = blob.getBytes(1, (int) blob.length());
try {
// ###把byte转化成string
return new String(returnValue, DEFAULT_CHARSET);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("Blob Encoding Error!");
}
}
@Override
public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
Blob blob = rs.getBlob(columnIndex);
if (null == blob) {
return null;
}
byte[] returnValue = null;
returnValue = blob.getBytes(1, (int) blob.length());
try {
// ###把byte转化成string
return new String(returnValue, DEFAULT_CHARSET);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("Blob Encoding Error!");
}
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
Blob blob = cs.getBlob(columnIndex);
if (null == blob) {
return null;
}
byte[] returnValue = null;
returnValue = blob.getBytes(1, (int) blob.length());
try {
// ###把byte转化成string
return new String(returnValue, DEFAULT_CHARSET);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("Blob Encoding Error!");
}
}
@Override
public void setNonNullParameter(PreparedStatement ps, int parameterIndex, String parameter, JdbcType jdbcType)
throws SQLException {
ByteArrayInputStream bis = null;
try {
// ###把String转化成byte流
bis = new ByteArrayInputStream(parameter.getBytes(DEFAULT_CHARSET));
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("Blob Encoding Error!");
}
ps.setBinaryStream(parameterIndex, bis, parameter.length());
}
}
ArticleController
同样设计二级映射,方便归类,注入标签与类别的Service实例,通过实例调用select**方法,获取到的对象存储于List中,通过Model类的addAttribute方法进行添加。
package com.tulun.controller;
import com.tulun.model.Category;
import com.tulun.model.Tag;
import com.tulun.service.CategoryService;
import com.tulun.service.TagService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.List;
/**
* Description :
* Created by Resumebb
* Date :2021/4/27
*/
@Controller
@RequestMapping("/article")
public class ArticleController {
@Autowired
private TagService tagService;
@Autowired
private CategoryService categoryService;
@RequestMapping("/add")
public String add(Model model) {
//获取类别数据
List<Category> categories = categoryService.selectAllCategory();
//获取标签数据
List <Tag> tags = tagService.selectAllTag();
model.addAttribute("categoryList", categories);
model.addAttribute("tagList", tags);
return "article/add_article";
}
}
新增页面jsp开发
<%@ page language="java" contentType="text/html; charset=UTF-8" import="java.util.*" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fm" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="renderer" content="webkit">
<link href="<%=basePath%>css/bootstrap.min.css" rel="stylesheet">
<link href="<%=basePath%>font-awesome/css/font-awesome.css" rel="stylesheet">
<!-- Data Tables -->
<link href="<%=basePath%>css/plugins/dataTables/dataTables.bootstrap.css" rel="stylesheet">
<link href="<%=basePath%>css/animate.css" rel="stylesheet">
<link href="<%=basePath%>css/style.css" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="<%=basePath%>js/flavr/flavr/css/animate.css" />
<link rel="stylesheet" type="text/css" href="<%=basePath%>js/flavr/flavr/css/flavr.css" />
<link href="<%=basePath%>css/plugins/datapicker/datepicker3.css" rel="stylesheet">
<link href="<%=basePath%>css/plugins/chosen/chosen.css" rel="stylesheet">
<style>
.input-upload{ position: relative;}
.input-upload input[type="file"]{ position: absolute; left: 0px; top: 0px; width: 72px; height: 35px; opacity:.0;filter:alpha(opacity=00);}
</style>
<title>新增博客</title>
</head>
<body>
<div >
<div class="gray-bg dashbard-1">
<%--<div class="row wrapper border-bottom white-bg page-heading">--%>
<%--</div>--%>
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-lg-12">
<div class="ibox float-e-margins">
<div class="ibox-content">
<form method="get" class="form-horizontal">
<div class="form-group">
<label class="col-sm-2 control-label">类别:</label>
<div class="col-sm-4">
<select class="form-control m-b" >
<option value="-1">无</option>
<c:forEach items="${categoryList}" var="category">
<option value="${category.id}">${category.categoryName}</option>
</c:forEach>
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">博客标题</label>
<div class="col-sm-10">
<input type="text" class="form-control" placeholder="请填写博客名">
</div>
</div>
<div class="hr-line-dashed"></div>
<div class="form-group">
<label class="col-sm-2 control-label">博客内容:</label>
<div class="col-sm-10">
<script type="text/plain"></script>
</div>
</div>
<div class="hr-line-dashed"></div>
<div class="form-group">
<label class="col-sm-2 control-label">标签:</label>
<div class="col-sm-10">
<select data-placeholder="请选择标签" class="chosen-select" multiple style="width:450px;" tabindex="5">
<c:forEach items="${tagList}" var="tag">
<option value="${tag.id}" hassubinfo="true">${tag.tagName}</option>
</c:forEach>
</select>
</div>
</div>
<div class="form-group">
<div class="col-sm-4 col-sm-offset-2">
<button class="btn btn-primary" type="button" onclick="saveArticle()">保存</button>
<button class="btn btn-white" type="button" onclick="cancelSaveArticle()">取消</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Mainly scripts -->
<script src="<%=basePath%>js/jquery-2.1.1.min.js"></script>
<script src="<%=basePath%>js/bootstrap.min.js"></script>
<script src="<%=basePath%>js/plugins/metisMenu/jquery.metisMenu.js"></script>
<script src="<%=basePath%>js/plugins/slimscroll/jquery.slimscroll.min.js"></script>
<script src="<%=basePath%>js/hplus.js"></script>
<script src="<%=basePath%>js/plugins/pace/pace.min.js"></script>
<script src="<%=basePath%>js/validation.js"></script>
<%--自定义js--%>
<script src="<%=basePath%>js/article/add_article.js"></script>
<script type="text/javascript" src="<%=basePath%>js/flavr/flavr/js/flavr.min.js"></script>
<script type="text/javascript" src="<%=basePath%>js/flavr/base.js"></script>
<script type="text/javascript" src="<%=basePath%>js/zTree_v3/js/jquery.ztree.all-3.5.js"></script>
<script src="<%=basePath%>js/plugins/datapicker/bootstrap-datepicker.js"></script>
<script type="text/javascript" src="<%=basePath%>js/ueditor1_4_3/ueditor.config.js"></script>
<script type="text/javascript" src="<%=basePath%>js/ueditor1_4_3/ueditor.all.js"></script>
<script type="text/javascript" charset="utf-8" src="<%=basePath%>js/ueditor1_4_3/lang/zh-cn/zh-cn.js"></script>
<script type="text/javascript" src="<%=basePath%>js/ajaxfileupload.js"></script>
<script src="<%=basePath%>js/plugins/chosen/chosen.jquery.js"></script>
<script type="text/javascript" src="<%=basePath%>js/imageUtils.js"></script>
<script type="text/javascript">
$(document).ready(function () {
var UEDITOR_HOME_URL = "/js/ueditor1_4_3/";
var ue = UE.getEditor('editor');
})
$(".chosen-select").chosen({
max_selected_options:5,
no_results_text:"û���ҵ�",
allow_single_deselect:true
});
</script>
</body>
</html>
部署至服务器
通过select标签可以进行类别与标签的选择。
文章数据提交
博客编辑好了之后,下一步就需要进行数据的提交。
后台JS处理
后台js对数据进行解析,其中的每一项都需要选中信息或填入信息,如果没有信息,就利用ajax进行弹窗提示,时长为500ms。之后再通过param进行数据加密,防止数据明文显示被抓包。如果保存成功将重定位至list。
// 保存文章
function saveArticle(){
var param = {};
// 收集参数 校验
//$("#categoryId").val()
var categoryId = $("#categoryId").val();
if(categoryId == '-1'){
autoCloseAlert("请选择栏目",500);
return false;
}
param["categoryId"] = categoryId;
var title = $("#title").val();
if(isEmpty(title)){
autoCloseAlert("请输入标题",500);
return false;
}
param["title"] = title;
var arr = [];
arr.push(UE.getEditor('editor').getContent());
var content = arr.join("\n");
// 简介
var description = UE.getEditor('editor').getContentTxt().substring(0,500);
// 标签
var tagId = $(".chosen-select").val();
// alert(tagId);
if(!isEmpty(tagId)){
var ids = (tagId+"").split("\,");
var tagArray = [];
for(var i=0;i<ids.length;i++){
tagObj = {"id":ids[i]};
// alert(tagObj.id);
tagArray.push(tagObj);
}
param["tagList"] = tagArray;
console.info(tagArray);
}else{
autoCloseAlert("请输入标签",500);
return false;
}
// alert('param='+encodeURI(encodeURI(JSON.stringify(param))));
// 保存文章
$.ajax({
type : "POST",
url : '../article/addContent',
data : 'param='+encodeURI(encodeURI(JSON.stringify(param)))+"&content="+encodeURI(encodeURI(content)).replace(/\&/g, "%26").replace(/\+/g, "%2B")+"&description="+encodeURI(encodeURI(description)),
success : function(data) {
if(data.resultCode != 'success'){
autoCloseAlert(data.errorInfo,1000);
return false;
}else{
// 调到列表页
window.location.href = "../article/list";
}
}
});
}
function cancelSaveArticle(){
window.location.href = "../article/list";
}
ArticleController
在ArticleController中新增addContent方法,利用URLDecoder进行数据的解密。
@RequestMapping("/addContent")
@ResponseBody
public Result addContent(String param, String content, String description) throws UnsupportedEncodingException {
String param1 = URLDecoder.decode(param, "utf-8");
String content1 = URLDecoder.decode(content, "utf-8");
String description1 = URLDecoder.decode(description, "utf-8");
System.out.println("content:"+content);
System.out.println("description:"+description);
System.out.println("content1:"+content1);
System.out.println("description1:"+description1);
return new Result("success","处理成功");
}
这里用到一个Result类,它是用于存储返回结果的,属性主要包含操作结果,错误信息,以及附属对象。
package com.tulun.model;
import org.apache.log4j.Logger;
/**
* 封装统一的结果集给前端
*/
public class Result {
// 操作结果
private String resultCode;
// 错误信息
private String errorInfo;
// 附属对象
private Object object;
public Result(String resultCode, String errorInfo) {
super();
this.resultCode = resultCode;
//this.errorInfo = "错误";
this.errorInfo = errorInfo;
System.out.println(errorInfo);
}
public Result(String resultCode, String errorInfo, Object object) {
super();
this.resultCode = resultCode;
this.errorInfo = errorInfo;
this.object = object;
}
public String getResultCode() {
return resultCode;
}
public void setResultCode(String resultCode) {
this.resultCode = resultCode;
}
public String getErrorInfo() {
return errorInfo;
}
public void setErrorInfo(String errorInfo) {
this.errorInfo = errorInfo;
}
public Object getObject() {
return object;
}
public void setObject(Object object) {
this.object = object;
}
@Override
public String toString() {
return "Result{" +
"resultCode='" + resultCode + '\'' +
", errorInfo='" + errorInfo + '\'' +
", object=" + object +
'}';
}
}
接下来部署到服务器进行打印测试:
上面为加密信息,下面为解密信息 ,可以看出加密解密成功实现。
文章数据保存至数据库
Json数据转换
该工具类的作用是实现Json字符串与对象的相互转换,因为在插入数据库过程中,将其转换为对象操作起来更加方便,通过Set方法就可以进行设置。
package com.tulun.util;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
public class JsonUtil {
public static Gson getGson() {
return new Gson();
}
/**
* 将对象转为JSON字符串(忽略NULL值)
*
* @param src
* @return
*/
public static String toJson(Object src) {
return getGson().toJson(src);
}
/**
* 将对象转为JSON字符串(不忽略NULL值)
*
* @param src
* @param serializeNulls
* @return
*/
public static String toJson(Object src, boolean serializeNulls) {
if (serializeNulls)
return new GsonBuilder().serializeNulls().create().toJson(src);
return toJson(src);
}
/**
* 将JSON字符串转为对象
*
* @param json
* @param classOfT
* @return
* @throws JsonSyntaxException
*/
public static <T> T fromJson(String json, Class<T> classOfT)
throws JsonSyntaxException {
return getGson().fromJson(json, classOfT);
}
/**
* 从请求体中读取客户端发送的JSON串
*
* @param stream
* 输入流
* @return String 类型,接收到的JSON串
*/
public static String readStringFromRequestBody(InputStream stream) {
StringBuffer sb = new StringBuffer();
char[] buf = new char[2048];
int len = -1;
try {
InputStreamReader reader = new InputStreamReader(stream, "UTF-8");
while ((len = reader.read(buf)) != -1) {
sb.append(new String(buf, 0, len));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
}
}
}
return sb.toString();
}
/**
* 回写响应
* @param json
* @param response
*/
public static void writeString(String json, HttpServletResponse response) {
ServletOutputStream os = null;
try {
os = response.getOutputStream();
os.write(json.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally{
if(null != os){
try {
os.close();
} catch (IOException e) {
}
}
}
}
}
ArticleMapper
主要定义两个方法,一个新增文章,另一个通过id号查询文章数据。
public interface ArticleMapper {
public int addArticle(Article article);
public Article getArticleById(Integer id);
}
ArticleService
@Service
public class ArticleService {
@Autowired
ArticleMapper articleMapper;
public int addArticle(Article article){
return articleMapper.addArticle(article);
}
public Article getArticleById(Integer id){
return articleMapper.getArticleById(id);
}
}
ArticleController
调用JsonUtil类将字符串转换为Article类,通过set方法进行传参,根据Service的返回结果进行判断,i>0就插入成功,后台打印信息,数据库的数据也插入成功。
@RequestMapping("/addContent")
@ResponseBody
public Result addContent(String param, String content, String description) throws UnsupportedEncodingException {
String param1 = URLDecoder.decode(param, "utf-8");
String content1 = URLDecoder.decode(content, "utf-8");
String description1 = URLDecoder.decode(description, "utf-8");
//将String字符串解析为对象
Article article = JsonUtil.fromJson(param1, Article.class);
article.setContent(content1);
article.setDescription(description1);
article.setStatus(0);
article.setCreateTime(new Date());
article.setAuthor("杜甫");
System.out.println(article);
int i = articleService.addArticle(article);
if(i>0){
return new Result("success","处理成功");
}
return new Result("fail","处理失败");
}
ArticleMapper.xml
映射文件中进行ResultMap的配置以及SQL语句的书写,注意配置过程中每一个属性映射的时候要保证前后端名称一致,否则就会报错。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.tulun.dao.ArticleMapper">
<resultMap type="com.tulun.model.Article">
<result column="id" property="id"></result>
<result column="categoryId" property="categoryId"></result>
<result column="title" property="title"></result>
<result column="content" property="content"></result>
<result column="description" property="description"></result>
<result column="status" property="status"></result>
<result column="author" property="author"></result>
<result column="createTime" property="createTime"></result>
<result column="showCount" property="showCount"></result>
<result column="content" property="content" typeHandler="com.tulun.util.ConvertBlobTypeHandler"></result>
</resultMap>
<insert parameterType="com.tulun.model.Article">
insert into t_article (categoryId,title,content,description,status,author,createTime)
values (#{categoryId},#{title},#{content},#{description},#{status},#{author},#{createTime})
</insert>
<select parameterType="int" resultMap="articleMap">
select * from t_article where id = #{id}
</select>
</mapper>
部署至服务器
博客内容展示
内容展示的jsp如下,分别获取到博客的名称,作者,内容,分类等等信息,其中要注意的是发表时间以及分类,因为通过Date获取的时间并不是一个格式化时间,需要对它进行转换,另一个是分类从数据库读取的是分类号,而不是类名,同时也要做相应的转换。
article_detail.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html><head>
<link rel="stylesheet" href="<%=basePath%>css/demo/style.css" type="text/css" media="all">
<link href="<%=basePath%>js/ueditor1_4_3/third-party/SyntaxHighlighter/shCoreDefault.min.css" rel="stylesheet" type="text/css" />
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=11,IE=10,IE=9,IE=8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0, minimum-scale=1.0, maximum-scale=1.0">
<meta name="baidu-site-verification" content="emEdenaBVA">
<meta http-equiv="Cache-Control" content="no-siteapp">
<title> 博客系统</title>
</head>
<body class="home blog ui-c3">
<section class="container">
<header class="header">
<div class="logo_right"><span class="glyphicon glyphicon-search degfy_search"></span></div>
<div class="logo_left"></div>
<h1 class="logo"><a href="">博客系统</a></h1>
<div class="widget_head"></div>
</header>
<div class="content-wrap">
<div class="content">
<header class="article-header">
<h1 class="article-title">${article.title}</h1>
<ul class="article-meta">
<li>作者:${article.author} 发布于:${article.time}</li>
<li>分类:<a rel="category tag" data-original-title="" title="">${article.categoryName}</a></li>
</ul>
</header>
<article class="article-content">
${article.content}
</article>
<div class="article-tags">
标签:
<c:forEach items="${article.id}" var="tag">
<a href="javascript:void(0)" rel="tag" data-original-title="" title="">${tag.tagName}</a>
</c:forEach>
</div>
</div>
</section>
<div ></div>
<script type="text/javascript" src="http://libs.baidu.com/jquery/1.9.1/jquery.min.js"></script>
<script src="//cdn.bootcss.com/jquery.lazyload/1.9.1/jquery.lazyload.js"></script>
</body>
</html>
时间格式化
在Article的POJO类中新增time属性,通过SimpleDateFormat类格式化时间,然后通过get方法进行返回。
public String getTime() {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return simpleDateFormat.format(createTime);
}
分类ID与类名转换
在Article的POJO类中新增categoryName属性,通过数据库的联合查询来返回一个categoryName。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.tulun.dao.ArticleMapper">
<resultMap type="com.tulun.model.Article">
<result column="id" property="id"></result>
<result column="categoryId" property="categoryId"></result>
<result column="title" property="title"></result>
<result column="content" property="content"></result>
<result column="description" property="description"></result>
<result column="status" property="status"></result>
<result column="author" property="author"></result>
<result column="createTime" property="createTime"></result>
<result column="categoryName" property="categoryName"></result>
<result column="showCount" property="showCount"></result>
<result column="content" property="content" typeHandler="com.tulun.util.ConvertBlobTypeHandler"></result>
</resultMap>
<insert parameterType="com.tulun.model.Article">
insert into t_article (categoryId,title,content,description,status,author,createTime)
values (#{categoryId},#{title},#{content},#{description},#{status},#{author},#{createTime})
</insert>
<select parameterType="int" resultMap="articleMap">
select a.*,c.categoryName categoryName from t_article a, t_category c where a.categoryId = c.id and a.id = #{id}
</select>
</mapper>
最后部署至服务器可以看见初步效果
报错汇总
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 1 path $
原因是POJO类的与mapper.xml中select标签上的id名不一致导致的