javaweb——>个人博客项目
具体代码参考:java_blog
目录
一.简单介绍
1.功能简介
实现一个简易的博客功能,包括用户登录、,发表新文章,显示文章详情,显示文章列表,显示文章列表接口访问量(使用到多线程,暂时未做)。
2.使用的技术
二.项目准备
1.需要的资源
2.创建Web项目
3.数据库设计
<1>用户表
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 (50) comment '头像'
);
insert into user(username,password) values
('张三','1001'),
('李四','1002'),
('王五','1003');
<2>文章表
create table article(
id int primary key auto_increment,
title varchar (20) not null comment'标题',
content mediumtext not null comment '正文' ,
create_time timestamp default now(),
view_count int default 0,
user_id int ,
foreign key (user_id) references user(id)
);
insert into article( title, content, user_id) values
('快速排序','public...',1),
('冒泡排序','public...',1),
('选择排序','public...',1),
('归并排序','public...',2),
('插入排序','public...',2);
三.项目开发
1.前置知识
<1>Filter过滤器
Filter:拦截请求,过滤响应
<2>简单Ajax和json介绍
(1). Ajax
B.json的序列化和反序列化
参考:序列化和反序列化
2.具体开发(后端服务)
<1>设计数据库实体类和返回的JSONResponse对象
(1)用户类
@Getter
@Setter
@ToString
public class User {
private Integer id;
private String username;
private String password;
private String nickname;//昵称
private Boolean sex;
private Date birthday;
private String head;//头像
}
(2)文章类
@Setter
@Getter
@ToString
public class Article {
private Integer id;
private String title;
private String content;
private Date createTime;
private Integer viewCount;
private Integer userId;
}
(3)JSONResponse对象
/**
* http响应json数据,前后端统一约定的格式
* 前端响应的状态码都是200,进入ajax的success来使用
* 操作成功:(success:true, data :xxx)
* 操作失败:(success:false code:xxx, message:xxx)
*/
@Setter
@Getter
@ToString
public class JSONResponse {
//业务操作是否成功
private boolean success;
//业务操作的消息码,一般来说,出现错误的错误码才有意义
private String code;
//业务操作的错误信息,给用户看的信息
private String message;
//业务数据:业务操作成功时,给前端ajax,success方法使用,解析响应json数据,渲染网页信息
private Object data;
}
<2>设计自定义异常以及Servlet实现的父类
(1)自定义异常
package org.example.exception;
/**
* 自定义异常类:业务代码抛自定义异常或者其他异常
* 自定义异常返回给定的错误码,其他异常返回其他错误码
*/
public class APPexception extends RuntimeException{
//给前端返回json字符串中,保存错误码
private String code;
//message父类自带,返回josn字符串中,保存错误信息给用户用
public APPexception(String code,String message) {
super(message);
this.code=code;
}
public APPexception(String code,String message, Throwable cause) {
super(message, cause);
this.code=code;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
}
(2)Servlet实现的父类以及异常的处理
package org.example.servlet;
import org.example.exception.APPexception;
import org.example.modle.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");
//获取当前URL,校验是否是登录或者注册,如果不是,进行session校验
//session会话管理,除登录和注册接口,其他都需要登录后访问
//通过req.getServletPath();获取请求服务路径
JSONResponse json=new JSONResponse();
//调用子类重写的方法
try{
Object data=process(req, resp);
//子类的process执行完全没有抛异常,表示业务执行成功
json.setSuccess(true);
json.setData(data);
}catch (Exception e){
e.printStackTrace();
//异常如何处理
//自定义异常返回错误信息
//json.setSuccess(false)不用设置了,因为new的时候初始化就是
String code="UNKNOWN";
String s="未知错误";
if(e instanceof APPexception){
//将异常信息取出赋给s
code=((APPexception)e).getCode();
s=e.getMessage();
json.setCode(code);
json.setMessage(s);
}
}
PrintWriter pr=resp.getWriter();
//将json信息打印
pr.println(JSONUtil.serialize(json));
pr.flush();
pr.close();
}
protected abstract Object process(HttpServletRequest req, HttpServletResponse resp) throws Exception;
}
<3>设计数据库连接工具类
private static final String URL="jdbc:mysql://localhost:3306/servlet_blog?" +
"user=root&password=sn20000904&useUnicode=true&characterEncoding=UTF-8";
//数据库连接池
private static final DataSource DS=new MysqlDataSource();
//静态代码块初始化DS
static {
((MysqlDataSource)DS).setUrl(URL);
}
(1).获取数据库连接
//获取数据库连接
public static Connection getConnection(){
try {
return DS.getConnection();
} catch (SQLException e) {
e.printStackTrace();
//抛自定义异常
throw new APPexception("SQL001","获取数据库连接异常",e);
}
}
(2)资源释放
//资源释放
public static void close(Connection c, Statement s, ResultSet r){
try {
if(r!=null)
r.close();
if(s!=null)
s.close();
if(c!=null)
c.close();
} catch (SQLException e) {
throw new APPexception("SQL002","数据库释放资源出错",e);
}
}
public static void close(Connection c, Statement s){
close(c,s ,null );
}
<4>设计JSON与java对象的序列化和反序列化方法
package org.example.util;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.io.InputStream;
public class JSONUtil {
//定义一个ObjectMapper类,主要实现java类和json对象之间的转换
private static final ObjectMapper MAPPER = new ObjectMapper();
/**
* JSON序列化,将java对象序列化为json字符串
*
* @param o java对象
* @return json字符串
*/
//序列化,返回json字符串
public static String serialize(Object o) {
try {
return MAPPER.writeValueAsString(o);
} catch (JsonProcessingException e) {
e.printStackTrace();
throw new RuntimeException("json序列化失败" + o);
}
}
/**
* 反序列化操作
*
* @param is 输入流
* @param clazz 指定要反序列化的类型
* @param <T>
* @return 反序列化对象
*/
//反序列化(使用输入流InputStream获取输入字符串)
public static <T> T deserialize(InputStream is, Class<T> clazz) {
try {
return MAPPER.readValue(is, clazz);
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("json反序列化失败" + clazz.getName());
}
}
}
<5>设计Filter过滤器工具
package org.example.filter;
import org.example.modle.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;
//import javax.servlet.annotation.WebFilter;
/**
* 配置用户统一会话管理的过滤器,匹配所有请求路径
* 服务端资源:/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(false);
//验证用户是否登录,如果没有登录,还需要根据前端和后端做不同的处理
if(session==null||session.getAttribute("user")==null){
//前端重定向到登陆页面
if(servletPath.startsWith("/jsp/")){
//使用绝对路径,重定向
resp.sendRedirect(basePath(req)+"/view/login.html");
}else {//后端返回401状态码
resp.setStatus(401);
//设置请求体的编码
resp.setCharacterEncoding("UTF-8");
//为响应体设置数据类型(浏览器要采取什么方式执行)
resp.setContentType("application/json");
JSONResponse json=new JSONResponse();
json.setCode("LOG000");
json.setMessage("用户没有登录,不允许访问");
PrintWriter pw=resp.getWriter();
pw.println(JSONUtil.serialize(json));
pw.flush();
pw.close();
}
}else {//敏感资源,但已登录,允许继续执行
chain.doFilter(request, response);
}
}
}
/**
* 根据http请求,动态的获取访问路径(服务路径之前的部分)
*/
public static String basePath(HttpServletRequest req){
String schema=req.getScheme();//获取http
String host=req.getServerName();//主机ip或域名
int port=req.getServerPort();//服务器端口号
String contextPath=req.getContextPath();//获取应用上下文路径
return schema+"://"+host+":"+port+contextPath;
}
@Override
public void destroy() {
}
}
<6>业务功能的具体实现
用户登录、,发表新文章,显示文章详情,显示文章列表,删除文章,修改文章,显示文章列表接口访问量(使用到多线程,暂时未做)。
源代码参考:java_blog
Servlet相关操作位于servlet包下,数据库查询操作位于DAO包下
四.项目总结
1.使用的技术和功能
<1>使用的技术
Servlet,MySQL,jackson,ajax,UEditor
<2>功能
1.已完成:用户登录、,发表新文章,显示文章详情,显示文章列表,删除文章,修改文章
Servlet模板方法:结合jackson序列化响应的统一格式,结合自定义异常完成统一的异常处理
敏感资源的查看,使用到Session以及Filter过滤器
扩展:
Filter过滤器完成用户会话的统一管理
富文本编辑器实现博客文章的展示,以及图片上传
2.未完成:使用到多线程
数据库连接池,通过双重校验锁的单例模式来完成
文章列表接口的统计访问量:(内存实现)在文章列表接口中,定义一个变量来保存访问量,在定义一个获取访问量的接口,请求时返回访问量的内容
<3>项目执行的流程
(1)请求网页
(2)js发送ajax请求
(3)Tomcat
(4)Servlet