目录
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 是由百度 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+"/";
%>
富文本编辑器测试
此外,为了加载网页中的静态资源,如图片,js,css,需要对servlet进行配置:
根据自己的服务器选择不同包下的DefaultServlet
contextConfigLocation
classpath:spring-core.xml
org.springframework.web.context.ContextLoaderListener
Blog
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:spring-mvc.xml
Blog
/
default
org.apache.catalina.servlets.DefaultServlet
debug
0
listings
false
1
default
*.jpg
default
*.js
default
*.css
default
*.png
default
*.woff2
default
*.woff3
default
*.ttf
default
*.woff
default
*.gif
default
*.map
default
*.html
启动服务器后如下图,就说明配置成功,接下来就可以对UEditor进行编程了。
jsp页面:新增页面:add_ueditor 详情页:ueditor_detail
controller方法:add addContent detail
运行流程:
/add -> add方法 ->add_ueditor.jsp ->提交按钮->/addContent->detail
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";
}
}
页面中新增两个按钮,点击提交按钮调用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+"/";
%>
新增博客
<%--自定义js--%>
在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";
}
用$获取到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+"/";
%>
博客详情
${content}
网页界面:
后台打印输入:
博客内容一般较大,肯定不能用常规数据类型进行存储,因此可以使用MySQL提供的blog类型进行存储,根据具体需求选择不同的类型。
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;
}
}
定义一个接口获取全部tag
public interface TagMapper {
public List getAllTag();
}
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 selectAllTag(){
return tagMapper.getAllTag();
}
}
从t_tag表中查询所有数据,做一个映射
与标签开发过程一样,分别进行配置。
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;
}
}
public interface CategoryMapper {
public List getAllCategory();
}
@Service
public class CategoryService {
@Autowired
private CategoryMapper categoryMapper;
public List selectAllCategory(){
return categoryMapper.getAllCategory();
}
}
查询t_category表中的数据
首先创建对应的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 +
'}';
}
}
返回类型采用resultMap类型,这里使用到了一个类型转换的处理器类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 {
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());
}
}
同样设计二级映射,方便归类,注入标签与类别的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 categories = categoryService.selectAllCategory();
//获取标签数据
List 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+"/";
%>
新增博客
通过select标签可以进行类别与标签的选择。
博客编辑好了之后,下一步就需要进行数据的提交。
后台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
在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字符串与对象的相互转换,因为在插入数据库过程中,将其转换为对象操作起来更加方便,通过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 fromJson(String json, Class 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) {
}
}
}
}
}
主要定义两个方法,一个新增文章,另一个通过id号查询文章数据。
public interface ArticleMapper {
public int addArticle(Article article);
public Article getArticleById(Integer id);
}
@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);
}
}
调用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","处理失败");
}
映射文件中进行ResultMap的配置以及SQL语句的书写,注意配置过程中每一个属性映射的时候要保证前后端名称一致,否则就会报错。
insert into t_article (categoryId,title,content,description,status,author,createTime)
values (#{categoryId},#{title},#{content},#{description},#{status},#{author},#{createTime})
内容展示的jsp如下,分别获取到博客的名称,作者,内容,分类等等信息,其中要注意的是发表时间以及分类,因为通过Date获取的时间并不是一个格式化时间,需要对它进行转换,另一个是分类从数据库读取的是分类号,而不是类名,同时也要做相应的转换。
<%@ 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+"/";
%>
博客系统
博客系统
${article.title}
${article.content}
时间格式化
在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。
insert into t_article (categoryId,title,content,description,status,author,createTime)
values (#{categoryId},#{title},#{content},#{description},#{status},#{author},#{createTime})
最后部署至服务器可以看见初步效果
报错汇总
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名不一致导致的