一、项目目标
这次的项目想要实现一个简易的博客功能,包括用户登录、注册,发表新文章,显示文章详情,以及显示文章列表的功能。
二、所需支持
由于前端所需内容皆以大致打包整合完毕,所以只需负责后端的功能实现。
在开始项目前,首先配置好Maven、Mysql、tomcat等配置。
三、具体实现
前端
使用了jquery框架中的ajax与form表单
form表单:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
<script type="text/javascript" src ="../static/jquery/jquery-1.12.4.js"></script>
<script type="text/javascript" src="../js/app.js"></script>
</head>
<body>
<h2>用户登录</h2>
<form id="login_form" method="post" action="../login" enctype="application/x-www-form-urlencoded">
<input id = "username" type="text" name="username" placeholder="请输入用户名"><br><br>
<input id = "password" type="password" name="password" placeholder="请输入密码"><br><br>
<input type="submit" value="登录">
</form>
</body>
</html>
利用form表单完成了前端的登录页面。
ajax:
$(function () {//页面加载完成之后执行function代码
//jquery,使用$("#id")通过元素id获取某个页面元素
$("#login_form").submit(function () {
//ajax自己发请求
$.ajax({
url: "../login",//请求的服务路径
type: "post",//请求方法
//contentType:""请求的数据类型 请求头Content_type,默认表单格式
//dataType:"",//响应的数据类型:不适用默认为表单提交的格式,json需要指定
data:$("#login_form").serialize(),//请求的数据:序列化表单的数据
dataType:"json",
success: function (r) {//响应体json字符串,会解析为方法参数
if(r.success){
//前端页面url直接跳转某个路径
window.location.href ="../jsp/articleList.jsp";
}else{
alert("错误码: "+r.code+ "\n错误消息: " +r.message)
}
}
})
//统一不执行默认表单提交
return false;
})
})
使用Ajax的最大优点,就是能在不更新整个页面的前提下维护数据。这使得Web应用程序更为迅捷地回应用户动作,并避免了在网络上发送那些没有改变的信息。
后端
1、数据库
利用Mysql语句完成了后端所需要的数据库建表
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 comment '账号',
password varchar(20) not null,
nickname varchar(20),
sex bit,
birthday date,
head varchar(50)
);
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)
);
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) value ('快速排序', 'public ...', 1);
insert into article(title, content, user_id) value ('冒泡排序', 'public ...', 1);
insert into article(title, content, user_id) value ('选择排序', 'public ...', 1);
insert into article(title, content, user_id) value ('归并排序', 'public ...', 2);
insert into article(title, content, user_id) value ('插入排序', 'public ...', 2);
-- 主外键关联的表,默认创建的主外键约束是restrict严格模式
-- 比如从表有数据关联到主表某一行数据X,那X不能删
-- 真实的设计上是不删除物理物理,在每一张表上设计一个字段,表示是否有效
select id, username, password, nickname, sex, birthday, head from user where username='a';
select id, title from article where user_id=1;
2、Util类
2.1 JSONUtil
Json是一种数据格式,保存在请求体与响应体内,JSONUtil可以实现Java字符串转换成JSON字符串的过程(序列化)以及反序列化操作。
2.2 DBUtil
DBUtil是一个工具类可以提供数据库JDBC操作。
由于项目中需要抛出各种异常,所以我们建立一个自定义异常类来进行统一异常处理.
3、自定义异常
public class AppException extends RuntimeException {
//给前端返回的json字符串中,保存错误码
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;
}
}
处理完这些后,我们就可以开始着手Servlet服务了
4、Servlet服务
4.1 Servlet父类
我们先写了一个父类Servlet,类似接收http请求的一个入口,后面的其他Servlet服务全部继承于父类,运用了模板模式的设计模式,可以完成统一业务的不同实现。
在父类Servlet中,我们要设置请求体的编码格式和响应体的编码与数据类型,然后进行自定义异常的抛出,最后完成统一的数据封装。
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()获取请求服务路径
//TODO
JSONResponse json = new JSONResponse();
try{
//调用子类重写的方法
Object data = process(req, resp);
//子类的process方法执行完没有抛异常,表示业务执行成功
json.setSuccess(true);
json.setData(data);
}catch(Exception e){
//异常如何处理?JDBC的异常SQLException,JSON处理的异常,自定义异常返回错误消息
e.printStackTrace();
//json.setSuccess(false)不用设置了,因为new的时候就是
String code = "UNKNOWN";
String s = "未知的错误";
if(e instanceof AppException){
code = ((AppException) e).getCode();
s = e.getMessage();
}
json.setCode(code);
json.setMessage(s);
}
PrintWriter pw = resp.getWriter();
pw.println(JSONUtil.serialize(json));
pw.flush();
pw.close();
}
protected abstract Object process(HttpServletRequest req,
HttpServletResponse resp) throws Exception;
}
4.2 用户登录
有了前端的用户登陆页面后,我们首要实现的目标就是使用户可以登陆进去,并且可以识别当密码错误与用户名错误时抛出不同的异常。为了实现这个功能,我们还需要一个登录DAO来链接数据库中的用户信息来作为支持。
@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");
User user = LoginDAO.query(username);
if(user == null)
throw new AppException("LOG002", "用户不存在");
if(!user.getPassword().equals(password))
throw new AppException("LOG003", "用户名或密码错误");
//登录成功,创建session
HttpSession session = req.getSession();
session.setAttribute("user", user);
return null;
}
}
4.3 发表新文章
完成了登录功能后,继续我们就需要完成发表新文章功能。同样需要一个文章DAO来连接到数据库中的文章信息。
@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 is = req.getInputStream();
Article a = JSONUtil.deserialize(is, Article.class);
a.setUserId(user.getId());
int num = ArticleDAO.insert(a);
return null;
}
}
4.4 修改文章
当发表文章后觉得不满意,我们肯定还需要进行修改功能。
@WebServlet("/articleUpdate")
public class ArticleUpdateServlet extends AbstractBaseServlet {
@Override
protected Object process(HttpServletRequest req, HttpServletResponse resp) throws Exception {
InputStream is = req.getInputStream();
Article a = JSONUtil.deserialize(is, Article.class);
int num = ArticleDAO.update(a);
return null;
}
}
4.5 删除文章
如果不需要某个文章,可以执行删除功能
@WebServlet("/articleDelete")
public class ArticleDeleteServlet extends AbstractBaseServlet{
@Override
protected Object process(HttpServletRequest req, HttpServletResponse resp) throws Exception {
String ids = req.getParameter("ids");
int num = ArticleDAO.delete(ids.split(","));
return null;
}
}
4.6 文章列表
为了是操作方便,我们还需要能够展示出文章列表并且可以选中执行。也是为了能够是用户的安全性更强。
@WebServlet("/articleList")
public class ArticleListServlet extends AbstractBaseServlet{
@Override
protected Object process(HttpServletRequest req, HttpServletResponse resp) throws Exception {
//Huoqusession,没有就返回null
HttpSession session = req.getSession(false);
if(session == null)
throw new AppException("ART002","用户没有登陆,不允许访问");
//获取登陆时创建的Session用户纤细
User user = (User) session.getAttribute("user");
if(user == null)
throw new AppException("ART003","会话异常,请重新登录");
//用户已登录,并且保存了用户信息
List<Article> articles = ArticleDAO.queryByUserId(user.getId());
return articles;
}
}
5、富文本编辑器
一个项目就这样即将完成了,最后添加一个基于百度的富文本编辑器来实现图片的上传功能,为此我们需要先进行以下五步:
- 修改idea中tomcat配置的应用上下文路径,maven中的finalName
- 修改webapp/static/ueditor/ueditor.config.js,33行修改(应用上下文路径+服务路径)
- 实现后端接口(和第二步的服务路径一致)
- 修改config.json配置:上传图片到服务器本地的路径,及访问的主机ip,port,应用上下文路径
- idea运行时,需要配置tomcat:将tomcat/webapps路径下的项目都部署
@WebServlet("/ueditor")
public class UEditorServlet 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 {
URL url = UEditorServlet.class.getClassLoader()
.getResource("config.json");
//URL获取到时,都是编码后的字符串,使用时,需要先解码再使用
String path = URLDecoder.decode(url.getPath(), "UTF-8");
//框架提供的富文本编辑器上传功能
MyActionEnter enter = new MyActionEnter(req, path);
String exec = enter.exec();//执行
PrintWriter pw = resp.getWriter();
pw.println(exec);
pw.flush();
pw.close();
}
}