博客设计文档
设计详情
- filter 拦截器来进行会话管理
- 多线程的单例模式来控制对数据库的连接
- 通过json来进行前后端的交接 用序列化和反序列化前后端数据
- 创建一个单独的类来进行异常处理 并且将异常原因返回给前端
- 设计一个统一的前后端响应交接抽象类,通过其他servlet来重写抽象类的方法,提高代码的复用率
- 设计一个JSONResponse类作为后端响应数据的模板,从而更好的实现前后端数据的交接
前端设计
- Json + ajax
- success为true则返回正确的页面 为false则返回我们设置的报错信息
调用一个接口来统一返回成功或者失败的接口
成功
- 没有报错,返回为true,响应正确的前端页面
失败
- 返回为false, 响应报错的后端的信息
- 报错信息就是我们自己所设置的报错信息
用户登录接口
请求
- POST “/login”
- Content-Type: x-www-form-urlencoded
- username=abc&password=123
响应
- Content-Type : application/json
- 跳转页面的内容
获取文章列表
请求
- GET “/articleList”
- Content-Type: text/html; charset=UTF-8
- 获取到Session,强转user,获取到user的id
响应
- Content-Type: text/html; charset=UTF-8
- 获取到文章列表
发表文章
请求
- GET “/articleAdd”
- Content-Type: text/html; charset=UTF-8
- 获取到文章标题和文章内容
响应
- Content-Type: text/html; charset=UTF-8
- 发表文章成功
获取文章详情
请求
- GET “/articleDetail”
- Content-Type: text/html; charset=UTF-8
- 根据文章id 获取文章详情
响应
- Content-Type: text/html; charset=UTF-8
- 返回文章
修改文章
请求
- POST “/articleUpdate”
- Content-Type: text/html; charset=UTF-8
响应
- Content-Type: text/html; charset=UTF-8
- 返回文章
删除文章
请求
- GET “/articleUpdate”
- Content-Type: text/html; charset=UTF-8
响应
- Content-Type: text/html; charset=UTF-8
- 刷新文章列表
代码设计
1.数据库设计
drop database if exists servlet_blog;
create database servlet_blog character set utf8mb4;
use servlet_blog;
create table user (
id int primary key auto_increment,
username varchar(20) not null unique,
password varchar(20) not null,
nickname varchar(20),
sex bit,
birthday date ,
head varchar(20)
);
create table article(
id int primary key auto_increment,
title varchar(20) not null ,
content mediumtext not null,
create_time timestamp default now(),
view_count int default 0,
user_id int,
foreign key(user_id) references user(id)
);
-- 主外键关联的表,默认创建的表的主外键是restrict严格模式,具有从连关系
-- 如果从表有数据关联到某一行数据,那x不可以删
-- 真实的设计上是不删除物理,在每一个表上设计上一个字段,表示是否有效
insert into user(username, password) values ('a', '1');
insert into user(username, password) values ('b', '2');
insert into user(username, password) values ('c', '3');
insert into article(title, content, user_id) values ('堆排序', 'public****', 2);
insert into article(title, content, user_id) values ('冒泡排序', 'public****', 2);
insert into article(title, content, user_id) values ('选择排序', 'public****', 2);
insert into article(title, content, user_id) values ('插入排序', 'public****', 1);
insert into article(title, content, user_id) values ('归并排序', 'public****', 1);
insert into article(title, content, user_id) values ('快速排序', 'public****', 1);
2. 数据库类
JSONResponse 这个类不会被添加到数据库中,只是为了做前后端数据的交接
package org.example.model;
/**
* http响应json数据, 前后端统一约定的json格式
* 响应状态码都是200 进入ajax 的success来使用
* { success : true, data:xxx }
* { success : false, code : xxx}
* */
// 使用注解的方式获取 get 和 set 方法
public class JSONResponse {
// 业务操作是否成功
private boolean success;
// 业务操作的消息码 一般来说 出现错误时的错误码才有意义,用于定位问题
private String code;
// 业务操作的错误消息 给用户看的信息
private String message;
// 业务数据, 业务操作成功时,给前端ajax success 方法使用,解析行用json数据,渲染网页
private Object data;
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}
3.数据库连接设置
package org.example.servlet;
import org.example.dao.ArticleDao;
import org.example.model.Article;
import org.example.model.User;
import org.example.util.JSONUtil;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.InputStream;
@WebServlet("/articleAdd")
public class ArticleAddServlet extends AbstractBaseServlet {
@Override
protected Object process(HttpServletRequest req, HttpServletResponse resp) throws Exception {
HttpSession session = req.getSession(false);
User user = (User) session.getAttribute("user");
// 请求数据类型是application/json 需要反序列化
InputStream inputStream = req.getInputStream();
Article article = JSONUtil.deserialize(inputStream, Article.class);
article.setUser_id(user.getId());
return ArticleDao.insert(article);
}
}
4.关于dao层代码的设计
对用户的登录
package org.example.dao;
import org.example.MyException.AppException;
import org.example.model.User;
import org.example.util.DBUtil;
import java.sql.*;
import java.util.Date;
public class LoginDAO {
public User query (String username) {
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
connection = DBUtil.getConnection();
String sql = "select * from user where username=?";
try {
statement = connection.prepareStatement(sql);
statement.setString(1, username);
resultSet = statement.executeQuery();
if (resultSet.next()) {
User user = new User();
user.setId(resultSet.getInt("id"));
user.setName(resultSet.getString("username"));
user.setPassword(resultSet.getString("password"));
user.setNickname(resultSet.getString("nickname"));
//返回值是 时分秒 java.sql.Date
//getTimestamp java.sql.Timestamp 年月日时分秒
//获取的时数据库中时间类型 sql.Date
java.sql.Date birthday = resultSet.getDate("birthday");
if (birthday != null) {
// 需要转化类型 将sql.Date 装换成 util.Date
user.setBirthday(new Date(birthday.getTime()));
}
user.setSex(resultSet.getBoolean("sex"));
user.setHead(resultSet.getString("head"));
return user;
}
} catch (SQLException e) {
throw new AppException("DAOLOG001", "查询用户错误", e);
} finally {
DBUtil.Close(connection, statement, resultSet);
}
return null;
}
}
对文章的获取 添加 删除 修改
对文章列表的获取
package org.example.dao;
import org.example.MyException.AppException;
import org.example.model.Article;
import org.example.util.DBUtil;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class ArticleDao {
public static int insert(Article article) {
Connection connection = null;
PreparedStatement statement = null;
connection = DBUtil.getConnection();
String sql = "insert into article (title, content, user_id)" +
"values (?, ?, ?)";
try {
statement = connection.prepareStatement(sql);
statement.setString(1, article.getTitle());
statement.setString(2, article.getContent());
statement.setInt(3, article.getUser_id());
return statement.executeUpdate();
} catch (SQLException throwables) {
throw new AppException("ARTINSERT005", "新增出错", throwables);
} finally {
DBUtil.Close(connection, statement, null);
}
}
public static Article query(int parseInt) {
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
connection = DBUtil.getConnection();
String sql = "select id,title,content from article where id = ?";
try {
statement = connection.prepareStatement(sql);
statement.setInt(1, parseInt);
resultSet = statement.executeQuery();
if (resultSet.next()) {
Article article = new Article();
article.setId(parseInt);
article.setTitle(resultSet.getString("title"));
article.setContent(resultSet.getString("content"));
return article;
}
return null;
} catch (SQLException throwables) {
throw new AppException("ARTDETAIL", "查询文章详情失败", throwables);
} finally {
DBUtil.Close(connection, statement, resultSet);
}
}
public static int updateArticle(Article article) {
Connection connection = null;
PreparedStatement statement = null;
connection = DBUtil.getConnection();
String sql = "update article set content = ? ,title = ? where id = ? ;";
try {
statement = connection.prepareStatement(sql);
statement.setString(1, article.getContent());
statement.setString(2, article.getTitle());
statement.setInt(3, article.getId());
int num = statement.executeUpdate();
return num;
} catch (SQLException throwables) {
throw new AppException("UPDATEART007", "修改失败", throwables);
} finally {
DBUtil.Close(connection, statement, null);
}
}
public List<Article> queryByuUser_id(Integer user_id) {
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
List<Article> list = new ArrayList<>();
connection = DBUtil.getConnection();
String sql = "select * from article where user_id = ?";
//String sql = "select id, title from article where user_id=?";
try {
statement = connection.prepareStatement(sql);
statement.setInt(1, user_id);
resultSet = statement.executeQuery();
while (resultSet.next()) {
Article article = new Article();
article.setId(resultSet.getInt("id"));
article.setContent(resultSet.getString("content"));
article.setTitle(resultSet.getString("title"));
article.setUser_id(resultSet.getInt("user_id"));
article.setView_count(resultSet.getInt("view_count"));
java.sql.Date date = resultSet.getDate("create_time");
if (date != null) {
article.setCreate_time(new Date(date.getTime()));
}
list.add(article);
}
return list;
} catch (SQLException e) {
throw new AppException("ART001", "获取文章列表失败");
} finally {
DBUtil.Close(connection, statement, resultSet);
}
}
public static int delete(String[] split) {
Connection connection = null;
PreparedStatement statement = null;
connection = DBUtil.getConnection();
StringBuilder sql = new StringBuilder("delete from article where id in (");
sql.append("?");
for (int i = 1; i < split.length; i++) {
sql.append(",");
sql.append("?");
}
sql.append(")");
try {
statement = connection.prepareStatement(sql.toString());
for (int i = 0; i < split.length; i++) {
statement.setInt(i+1, Integer.parseInt(split[i]));
}
return statement.executeUpdate();
} catch (SQLException throwables) {
throw new AppException("ARTDELE001", "文章删除出错", throwables);
} finally {
DBUtil.Close(connection, statement, null);
}
}
}
5.对于前后端数据的序列化和反序列化的设计
package org.example.util;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.io.InputStream;
// JSON 是前后端传输请求的数据格式
// 表单数据类型 x-www-form-urlencoded
public class JSONUtil {
private static final ObjectMapper MAPPER = new ObjectMapper();
// 静态方法
/**
* JSON序列化 ,将Java对象序列化成JSON字符串
* @param o java对象
* @return json字符串
* */
public static String serialize (Object o) {
try {
// 将java对象序列化后写入
return MAPPER.writeValueAsString(o);
} catch (JsonProcessingException e) {
e.printStackTrace();
throw new RuntimeException("json序列化失败" + o);
}
}
/**
* 反序列化 将JSON字符串转化成JAVA对象
* 使用泛型 来确定JAVA对象
* @param is 输入流
* @param tClass 确定要反序列化的类型
* @return 反序列化的对象
*/
public static <T> T deserialize (InputStream is, Class<T> tClass) {
try {
// 读取内容后将其反序列化成java对象
return MAPPER.readValue(is, tClass);
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("json反序列化失败" + tClass.getName());
}
}
}
6. Servelt类需要继承的抽象类
package org.example.servlet;
import org.example.MyException.AppException;
import org.example.model.JSONResponse;
import org.example.util.JSONUtil;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public abstract class AbstractBaseServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 设置请求体的编码格式
req.setCharacterEncoding("UTF-8");
// 设置响应体的编码格式
resp.setCharacterEncoding("UTF-8");
// 设置响应体的数据类型 浏览器采取那种方式去执行
resp.setContentType("application/json");
//Session会话管理:除登录和注册接口,其他都需要登录后访问
//req.getServletPath(); 获取请求的服务路径
JSONResponse JSresp = new JSONResponse();
try {
//调用子类重写的方法
Object data = process(req, resp);
// 子类的process 方法操作成功,没有抛异常,表示业务执行成功
JSresp.setSuccess(true);
JSresp.setData(data);
} catch (Exception e){
//异常如何处理 JDBC的异常SQLException JSON处理的异常
//自定义异常来返回异常处理的方式
// 传入前端的异常信息
e.printStackTrace();
String code = "UNKNOWN";
String s = "服务器未知错误";
if (e instanceof AppException) {
code = ((AppException) e).getCode();
s = e.getMessage();
}
// 可以不用设置 boolean的基本数据类型false
JSresp.setSuccess(false);
JSresp.setCode(code);
JSresp.setMessage(s);
}
PrintWriter p = resp.getWriter();
// 调用自己写的序列化方法 序列化JSresp对象
p.println(JSONUtil.serialize(JSresp));
p.flush();
p.close();
}
// protected : 子类可以用
protected abstract Object process(HttpServletRequest req,
HttpServletResponse resp) throws Exception;
}
7.servelt层代码
1.登录
package org.example.servlet;
import org.example.MyException.AppException;
import org.example.dao.LoginDAO;
import org.example.model.User;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
@WebServlet("/login")
public class LoginServlet extends AbstractBaseServlet{
@Override
protected Object process(HttpServletRequest req, HttpServletResponse resp) throws Exception {
String username = req.getParameter("username");
String password = req.getParameter("password");
LoginDAO dao = new LoginDAO();
User user = dao.query(username);
if (user == null) {
throw new AppException("LOG002", "用户不存在");
}
if (!user.getPassword().equals(password)) {
throw new AppException("LOG002", "用户名或者密码错误");
}
/*if ("abc".equals(username)) {
// 模拟登录成功
return null;
//resp.sendRedirect("jsp/articleList.jsp");
} else {
throw new AppException("LOGIN001", "用户名或密码错误");
}*/
HttpSession session = req.getSession();
session.setAttribute("user", user);
return null;
}
}
2.获取文章列表
package org.example.servlet;
import org.example.MyException.AppException;
import org.example.dao.ArticleDao;
import org.example.model.Article;
import org.example.model.User;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.List;
@WebServlet("/articleList")
public class ArticleListServlet extends AbstractBaseServlet{
@Override
protected Object process(HttpServletRequest req, HttpServletResponse resp) throws Exception {
// 获取session
HttpSession session = req.getSession(false);
// 如果没有 返回null
if (session == null) {
throw new AppException("ART002", "session为空, 用户未登录");
}
User user = (User) session.getAttribute("user");
if (user == null) {
throw new AppException("ART002", "session不为空, 但没有user");
}
Integer user_id = user.getId();
ArticleDao dao = new ArticleDao();
List<Article> articles = dao.queryByuUser_id(user_id);
return articles;
}
}
- 获取文章详情
package org.example.servlet;
import org.example.dao.ArticleDao;
import org.example.model.Article;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/articleDetail")
public class ArticleDetail extends AbstractBaseServlet{
@Override
protected Object process(HttpServletRequest req, HttpServletResponse resp) throws Exception {
String id = req.getParameter("id");
Article article = ArticleDao.query(Integer.parseInt(id));
return article;
}
}
- 发布文章
package org.example.servlet;
import org.example.dao.ArticleDao;
import org.example.model.Article;
import org.example.model.User;
import org.example.util.JSONUtil;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.InputStream;
@WebServlet("/articleAdd")
public class ArticleAddServlet extends AbstractBaseServlet {
@Override
protected Object process(HttpServletRequest req, HttpServletResponse resp) throws Exception {
HttpSession session = req.getSession(false);
User user = (User) session.getAttribute("user");
// 请求数据类型是application/json 需要反序列化
InputStream inputStream = req.getInputStream();
Article article = JSONUtil.deserialize(inputStream, Article.class);
article.setUser_id(user.getId());
return ArticleDao.insert(article);
}
}
- 修改文章
package org.example.servlet;
import org.example.dao.ArticleDao;
import org.example.model.Article;
import org.example.util.JSONUtil;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
@WebServlet("/articleUpdate")
public class ArticleUpdateServlet extends AbstractBaseServlet {
@Override
protected Object process(HttpServletRequest req, HttpServletResponse resp) throws Exception {
// 获取请求中的输入流
InputStream inputStream = req.getInputStream();
// 反序列化
Article article = JSONUtil.deserialize(inputStream, Article.class);
int num = ArticleDao.updateArticle(article);
return num;
}
}
- 删除文章
package org.example.servlet;
import org.example.dao.ArticleDao;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/articleDelete")
public class ArticleDeleteServlet extends AbstractBaseServlet{
@Override
protected Object process(HttpServletRequest req, HttpServletResponse resp) throws Exception {
// 未进行会话管理
String strid = req.getParameter("ids");
int num = ArticleDao.delete(strid.split(","));
return num;
}
}
8.自定义异常对异常的处理类
package org.example.MyException;
/**
* 自定义异常类: 业务代码抛自定义异常和其他异常
* 自定义异常返回给的错误码,其他异常返回其他异常状态码
* */
public class AppException extends RuntimeException {
private String code;
public AppException (String code,String message) {
// super(message);
// this.code = code;
this(code, message, null);
}
public AppException (String code, String message, Throwable cause) {
super(message, cause);
this.code = code;
}
public String getCode() {
return code;
}
}
9.fitter拦截器
利用fitter拦截器来做用户的会话管理
package org.example.filter;
import org.example.model.JSONResponse;
import org.example.util.JSONUtil;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
// 匹配路径 指的是匹配所有方法路径
// 进行会话管理
/**
* 配置用户统一会话管理的过滤器,配配置有请求路径
* 服务器 资源: /login 不用session检验 其他都需要 返回401
* 前端资源: /jsp/校验Session ,不通过重定向到登录页面
* /js/, /static/, /view/ 全都不校验
* */
@WebFilter("/*")
public class LoginFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
/**
* 每次http请求匹配到过滤器路径时,会执行过滤器的doFilter
* 如果往下执行,是调用FilterChain.doFilter(request, response)
* 否则自行处理响应
* */
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
String servletPath = req.getServletPath();
// 不需要登录允许访问:
if (servletPath.startsWith("/js/") || servletPath.startsWith("/static/")
|| servletPath.startsWith("/view/") || servletPath.equals("/login")) {
chain.doFilter(request, response);
} else {
// 获取session对象
HttpSession session = req.getSession();
// 验证用户是否登录 , 如果没有登录, hi需要根据前端或后端做不同的处理
if (session == null || session.getAttribute("user") == null) {
// 前端重定向到登录页面
// 后端返回401状态码
if (servletPath.startsWith("/jsp/")) {
// 重定向到登陆页面
// 绝对路径
resp.sendRedirect(basePath(req) + "/view/login.html");
} else {
// 401 状态码
resp.setStatus(401);
resp.setContentType("application/json; charset=UTF-8");
JSONResponse jsonResponse = new JSONResponse();
jsonResponse.setCode("NOLOGIN");
jsonResponse.setMessage("用户没有登录");
PrintWriter pw = resp.getWriter();
// 序列化
pw.println(JSONUtil.serialize(jsonResponse));
pw.flush();
pw.close();
}
} else {
chain.doFilter(request, response);
}
}
}
/**
* 根据http请求,动态获取访问路径
* */
public static String basePath(HttpServletRequest request) {
// http
String schema = request.getScheme();
// 主机ip或域名
String host = request.getServerName();
// 端口
int port = request.getServerPort();
String contextPath = request.getContextPath();
return schema + "://" + host + ":" + port + contextPath;
}
@Override
public void destroy() {
}
}