数据库
数据库的设计
系统中需要设计几张表?
用户表:用户Id 用户名 密码 是否作者
博客表:标题 内容 发布时间 作者ID(与用户表关联)
不能被数据库或编程语言中的基本数据类型表示的,都可以考虑定义成一个类或者一张表。
数据库的创建
# 创建数据库
drop database if exists blog_db;
# 排序时忽略大小写
create database blog_db character set utf8mb4 collate utf8mb4_general_ci;
#选择数据库
use blog_db;
#创建表
drop table if exists user;
create table user(
id bigint primary key auto_increment comment '用户ID,自增',
username varchar(50) unique not null comment '用户名',
password varchar(50) not null comment '密码'
);
#博客表
drop table if exists blog;
create table blog(
id bigint primary key auto_increment comment 'ID,自增',
title varchar(1024) not null comment '标题',
content text not null comment '内容',
createTime datetime not null comment '发布时间',
userId bigint not null
);
# 初始化两个用户
insert into user values(null,'魈','123456');
insert into user values(null,'迪卢克','123456');
#初始化两篇文章
insert into blog values(null,'单元测试','内容',now(),1);
insert into blog values(null,'单元测试2','内容',now(),2);
准备工作
1.创建工程
2.设置编码和其他项设置
3.引入依赖
<dependencies>
<!-- servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!--数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<!-- json解析 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
</dependencies>
创建表
根据数据库中的表,创建对应的Java类。
用户表
public class User {
private Long id;
private String username;
//删除文章进行权限判断时添加
//private boolean isAuthor;
@JsonIgnore //不参与Json序列化
private String password;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
//public void setAuthor(boolean author) {
// isAuthor = author;
//}
//public boolean isAuthor() {
// return isAuthor;
//}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
博客表
public class Blog {
private Long id;
private String title;
private String content;
//发布日期,使用当前的系统时间
private Timestamp createTime;
private Long userId;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
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 Timestamp getCreateTime() {
return createTime;
}
public void setCreateTime(Timestamp creatTime) {
this.createTime = creatTime;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
@Override
public String toString() {
return "Blog{" +
"id=" + id +
", title='" + title + '\'' +
", content='" + content + '\'' +
", creatTime=" + createTime +
", userId=" + userId +
'}';
}
}
封装工具类
1.与数据库建立连接的方法需要用到很多次,所以将其抽出来单独封装成一个类,减少重复编码。
public class DBUtils {
//定义数据源
private static DataSource dataSource;
private static final String URL = "jdbc:mysql://localhost:3306/blog_db?characterEncoding=utf8&useSSL=false";
private static final String USER = "root";
private static final String PASSWORD = "123456";
//完成初始化
static {
MysqlDataSource mysqlDataSource = new MysqlDataSource();
mysqlDataSource.setURL(URL);
mysqlDataSource.setUser(USER);
mysqlDataSource.setPassword(PASSWORD);
dataSource = mysqlDataSource;
}
//构造方法私有化
private DBUtils (){}
//获取数据库连接
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
public static void close (ResultSet resultSet, PreparedStatement statement,Connection connection){
//依次关闭并释放资源
if(resultSet != null){
try {
resultSet.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if(statement != null){
try {
statement.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if(connection != null){
try {
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
}
2.封装校验要传入的字符串是否为空的工具类。
public class StringUtils {
/**
* 校验要传入的字符串是否为空
* @param value
* @return true 空 <br/> false 非空
*/
public static boolean isEmpty(String value){
if(value == null || value.equals("")){
return true;
}
return false;
}
}
3.由于博客系统中的主要功能都需要用户登录,所以可以把登录校验抽取出来,做成一个方法。
public class UserUtils {
public static User checkUserLoginStatus (HttpServletRequest request){
//判断request是否为null
if(request == null){
return null;
}
//获取session
HttpSession session = request.getSession(false);
if(session == null){
return null;
}
//获取用户信息
User user = (User) session.getAttribute(AppConfig.USER_SESSION_KEY);
return user;
}
}
封装公共类
1.定义操作成功或者失败的统一返回格式。当状态码为1000时,定义为操作失败。当状态码为0时,定义操作成功。
public class AppResult<T> {
// 状态码
private int code;
// 描述信息
private String message;
// 相关的数据
private T data;
// 三个参数的构造方法
public AppResult(int code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
/**
* 操作成功
* @return
*/
public static AppResult success() {
return new AppResult(0, "操作成功", null);
}
public static AppResult success (String message) {
return new AppResult(0, message, null);
}
public static <T> AppResult success (T data) {
return new AppResult(0, "操作成功", data);
}
public static <T> AppResult success (String message, T data) {
return new AppResult(0, message, data);
}
/**
* 操作失败
* @return
*/
public static AppResult failed () {
return new AppResult(1000, "操作失败", null);
}
public static AppResult failed (String message) {
return new AppResult(1000, message, null);
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
2.定义全局的配置变量,保存用户session信息的key值。
/**
* 定义全局的配置变量
*/
public class AppConfig {
public static final String USER_SESSION_KEY = "USER_SESSION_KEY";
}
登录功能
约定前后端交互接口
[请求]
POST /login
Content-Type : application/x-www-form-urlencoded
username=test&password=123456
[响应]
{
"code":0,
"message" : "登录成功",
"data" : null
}
服务端实现
1.用户输入用户名和密码并提交到服务器;
2.服务器接收到用户数据,并校验是否为null;
3.根据用户名去数据库中查询用户信息;
4.如果可以查到用户对象,判断数据库中查到的密码与用户输入的密码是否相等;
5.返回登录是否成功。
1.数据库访问的实现(UserDao):提供一个selectByUsername的数据库访问方法,根据用户名去数据库中查询用户信息。
public class UserDao {
/**
* 根据用户名查询用户信息
* @param username
* @return User对象
*/
public User selectByUsername (String username) {
// 非空校验
if (StringUtils.isEmpty(username)) {
return null;
}
// 进行数据库操作, 定义数据库访问的相关对象
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
// 1. 获取数据库连接
connection = DBUtils.getConnection();
// 2. 定义SQL语句
String sql = "select id, username, password from user where username = ?";
// 3. 对SQL进行预处理
statement = connection.prepareStatement(sql);
// 4. 设置占位符的值
statement.setString(1, username);
// 5. 执行SQL,获取结果
resultSet = statement.executeQuery();
// 6. 构造对象
if (resultSet.next()) {
User user = new User();
user.setId(resultSet.getLong(1));
user.setUsername(resultSet.getString(2));
user.setPassword(resultSet.getString(3));
// 返回结果
return user;
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 释放资源
DBUtils.close(resultSet, statement, connection);
}
// 没有查询到结果,返回null
return null;
}
}
2.Servlet实现,用于用户登录。
此时用户要提交数据,需要重写doPost 方法。
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
// 创建DAO
private UserDao userDao = new UserDao();
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 设置编码集
req.setCharacterEncoding("UTF-8");
resp.setContentType("application/json; charset=utf-8");
// 接收参数
String username = req.getParameter("username");
String password = req.getParameter("password");
// 非空校验
if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
// 使用统一的数据返回格式
AppResult appResult = AppResult.failed("用户名和密码不能为空");
String json = objectMapper.writeValueAsString(appResult);
resp.getWriter().write(json);
return;
}
// 校验通过,调用DAO
User user = userDao.selectByUsername(username);
if (user == null) {
// 使用统一的数据返回格式
AppResult appResult = AppResult.failed("用户名密码不正确");
resp.getWriter().write(objectMapper.writeValueAsString(appResult));
return;
}
// 校验密码是否正确
if (!user.getPassword().equals(password)) {
AppResult appResult = AppResult.failed("用户名密码不正确");
resp.getWriter().write(objectMapper.writeValueAsString(appResult));
return;
}
// 保存用户信息到session中
HttpSession session = req.getSession(true);
// 使用全局定义的SESSION_KEY
session.setAttribute(AppConfig.USER_SESSION_KEY, user);
// 使用同一个数据返回格式
AppResult appResult = AppResult.success("登录成功");
resp.getWriter().write(objectMapper.writeValueAsString(appResult));
// 打印日志
System.out.println("登录成功 : " + user.getId());
}
}
服务端测试
通过postman测试接口是否正确。使用表单提交的方式来测试。
1.在不输入用户名的情况下进行测试,返回了用户名和密码不能为空,符合预期。
2.输入错误的用户名或密码,返回了用户名和密码不正确,符合预期。
3.输入正确的用户名和密码,返回了登录成功,测试完成。
客户端实现(blog_login)
在前端页面,使用ajax来提交数据,根据服务端返回的数据,在回调方法中处理前端逻辑。当获取到返回的状态码为0时,让界面从登录界面(blog_login)跳转到博客的列表页(blog_list),代表恩登录成功。当登录失败时弹窗,提示用户错误信息。
<div class="row">
<input type="button" class="btn_submit" id="btn_login_submit" value="提交">
</div>
核心代码: 当form表单没有指定action时,那么数据就会提交到当前的URL。为了不执行表单的提交动作,input type 改为button,表示他是一个普通的按钮。这样就会执行给按钮绑定的事件。
<script>
//在页面加载完成之后再执行JS
$(function(){
//为登陆按钮绑定事件
$('#btn_login_submit').click(function(){
//1.获取输入的用户名并校验
let usernameEl = $('#username');
if (!usernameEl.val()){
alert('用户名不能为空');
//让用户名输入框获取焦点
usernameEl.focus();
return;
}
//2.获取输入密码并做校验
let passwordEl = $('#password');
if(!passwordEl.val()){
alert('请输入密码');
passwordEl.focus();
return;
}
//构造发送的数据
let postData = {
username : usernameEl.val(),
password : passwordEl.val()
};
//发送ajax
$.ajax({
//请求的方法
type : 'post',
url : 'login',
contentType : 'application/x-www-form-urlencoded',
data : postData,
//成功回调
success : function(respData){
if(respData.code == 0){
//登录成功,跳转到blog_list_html界面
location.assign('blog_list.html');
}else{
//登录失败
alert(respData.message);
}
},
error : function(){
console.log('访问出现问题')
}
});
});
})
</script>
客户端测试
1.不输入用户名点击提交时,显示用户名不能为空,符合预期。
2.只输入用户名不输入密码,显示请输入密码,符合预期。
3.输入正确的用户名和密码,跳转到博客列表页。
获取当前用户的登录信息
服务端实现
当用户登录成功之后,已经把用户信息保存到了session中。用户发送请求到指定的URL,服务器从session中取出用户信息,转换成JSON字符串并返回。
@WebServlet("/user")
public class UserServlet extends HttpServlet {
private ObjectMapper objectMapper = new ObjectMapper();
private UserDao userDao = new UserDao();
private BlogDao blogDao = new BlogDao();
//获取用户信息
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置编码格式
req.setCharacterEncoding("UTF-8");
resp.setContentType("application/json; charset=utf-8");
//定义统一返回的数据类型
AppResult appResult;
//获取session对象
HttpSession session = req.getSession(false);
//判断session
if(UserUtils.checkUserLoginStatus(req) == null){
//设置HTTP的状态码
resp.setStatus(403);
//错误描述
appResult = AppResult.failed("用户没有登录,请登陆后再试");
//表示用户没有登录
resp.getWriter().write(objectMapper.writeValueAsString(appResult));
//中断代码的流程
return;
}
user = (User) session.getAttribute(AppConfig.USER_SESSION_KEY);
jsonString = objectMapper.writeValueAsString(AppResult.success(user));
//返回json
resp.getWriter().write(jsonString);
}
}
服务端测试
1.当用户没有登陆时,通过postman发送GET请求,返回了用户没有登录,请登录后再试,并将状态码置为了403,符合预期。
2.当用户登录成功之后再发送GET请求,返回了作者信息。注意,用户的敏感信息,比如密码,在json序列化时就应当排除这个字段。只需要在给password这个属性加入注解即可。
序列化:把Java对象转换成可以在网络上传输的数据类型。比如java->json。
客户端实现
首先要为相应的标签起个名字或ID,然后引入依赖,在资源页面加载成功之后直接发送请求,获取用户信息,在回调方法里面处理响应。
核心代码:
<script>
$(function(){
//直接发送ajax请求,获取当前登录的用户信息
$.ajax({
//请求的方法
type : 'get',
url : 'user',
success : function(respData){
if(respData.code == 0){
//请求成功
let user = respData.data;
//设置用户名
$('#h3_list_username').html(user.username);
}else{
//失败
}
},
error : function(){
console.log('访问出现问题.')
},
statusCode : {
//可以为不同的HTTP状态码定义不同的方法
403 : function() {
//打开日志并跳转到登录界面
console.log('用户无权访问,强制跳转到登录界面.');
location.assign('blog_login.html');
}
}
});
})
</script>
获取所有的的文章列表
约定前后端接口
[请求]
GET/blog
[响应]
{
"code": 0,
"message": "操作成功",
"data": [
{
"blogid": 2,
"title": "单元测试2",
"content": "内容",
"createTime":"xxxx-xx-xx xx:xx:xx" ,
"userId": 2
},
{
"blogid": 1,
"title": "单元测试",
"content": "内容",
"createTime": "xxxx-xx-xx xx:xx:xx",
"userId": 1
}
]
}
服务端实现
根据以上的响应格式,对于DAO中的SQL,select语句查询出来的是一个集合,用Java的List对象包装结果集。
1.操作数据库(Blog Dao),获取所有文章。
public class BlogDao {
/**
* 获取所有文章
*/
public List<Blog> selectAll(){
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
connection = DBUtils.getConnection();
//定义SQL
String sql = "select id, title, content, createTime, userId from blog order by createTime desc";
statement = connection.prepareStatement(sql);
resultSet = statement.executeQuery();
//遍历结果集
List<Blog> blogList = null;
while (resultSet.next()){
//判断是否为空
if(blogList == null){
blogList = new ArrayList<>();
}
//获取数据
Blog blog= new Blog();
blog.setId(resultSet.getLong(1));
blog.setTitle(resultSet.getString(2));
blog.setContent(resultSet.getString(3));
//判断长度是否大于100
if(blog.getContent().length() > 100){
//调用String对象的截取字符串的方法
blog.setContent(blog.getContent().substring(0,100) + "...");
}
blog.setCreateTime(resultSet.getTimestamp(4));
blog.setUserId(resultSet.getLong(5));
//把blog对象加到集合中
blogList.add(blog);
}
//返回结果集
return blogList;
} catch (SQLException e) {
e.printStackTrace();
}finally {
DBUtils.close(resultSet,statement,connection);
}
return null;
}
}
注意这里有一个BUG: 当写入的内容太长,会使得列表里的博客长度太长,因此此时需要只显示一部分,其余用省略号代替。
2.Servlet实现,用于获取文章列表。
重写doGet方法。
@WebServlet("/blog")
public class BlogServlet extends HttpServlet {
//用于数据库访问
private BlogDao blogDao = new BlogDao();
//转换json
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置编码格式
req.setCharacterEncoding("UTF-8");
resp.setContentType("application/json; charset=utf-8");
//定义返回的统一对象
AppResult<List<Blog>> appResult;
//校验用户登录状态
if(UserUtils.checkUserLoginStatus(req) == null){
//设置HTTP的状态码
resp.setStatus(403);
//错误描述
appResult = AppResult.failed("用户没有登录,请登陆后再试");
//表示用户没有登录
resp.getWriter().write(objectMapper.writeValueAsString(appResult));
//中断代码的流程
return;
}
//调用DAO
List<Blog> blogs = blogDao.selectAll();
// 为空时
if (blogs == null) {
blogs = new ArrayList<>();
}
// 序列化
jsonString = objectMapper.writeValueAsString(AppResult.success(blogs));
// 返回JSON
resp.getWriter().write(jsonString);
}
}
服务端测试
当登录之后发送GET请求,返回了博客的详情,符合预期。
客户端实现(blog_list)
核心代码:
<script>
$(function(){
//发送请求,获取文章列表
$.ajax({
type : 'get',
url : 'blog',
//回调
success : function(respData){
//根据自定义状态码处理
if(respData.code == 0){
//成功时,构造文章列表
buildArticleList(respData.data);
} else {
//出现错误
alert (respData.message);
}
},
error : function(){
//打印日志
console.log('访问出现错误。');
},
statusCode : {
403 : function(){
//未登录,强制跳转到登录界面
location.assign('blog_login.html');
}
}
});
// 构建文章列表
function buildArticleList(data) {
// 当文章列表为空时
if (!data || data.length == 0) {
let htmlString = '<h3>没有文章, 快发布一篇吧.</h3>'
$('.container-right').html(htmlString);
return;
}
data.forEach(blog => {
let htmlString = '<div class="blog-content"> '
+ ' <div class="blog-title"> '
+ blog.title
+ ' </div> '
+ ' <div class="blog-datetime"> '
+ formatDate(blog.createTime)
+ ' </div> '
+ ' <div class="content"> '
+ blog.content
+ ' </div> '
+ ' <div class="aEl"> '
+ ' <a href="./blog_details.html?blogId=' + blog.id + '">查看全文 >> </a> '
+ ' </div> '
+ ' <hr> '
+ ' </div> ';
// 追加到列表区域
$('.container-right').append(htmlString);
});
}
</script>
注意:这里需要解决日期的显示问题,需要将日期格式化。
function formatDate(time) {
var date = new Date(time);
var year = date.getFullYear(),
month = date.getMonth() + 1,//月份是从0开始的
day = date.getDate(),
hour = date.getHours(),
min = date.getMinutes(),
sec = date.getSeconds();
var newTime = year + '-' +
(month < 10 ? '0' + month : month) + '-' +
(day < 10 ? '0' + day : day) + ' ' +
(hour < 10 ? '0' + hour : hour) + ':' +
(min < 10 ? '0' + min : min) + ':' +
(sec < 10 ? '0' + sec : sec);
return newTime;
}
客户端测试
查看文章详情
点击查看全文的按钮会跳转到blog_details页面。在这个页面里需要查询到两个信息,一个是指定的文章信息,一个是指定文章的作者信息。
服务端实现
1.根据文章Id去查询文章详情(Blog Dao),要去查询数据库。
public class BlogDao {
/**
* 根据博客Id查询博客信息
* @param id 博客Id
* @return 博客记录
*/
public Blog selectById(Long id){
//非空校验
if(id == null || id <= 0){
return null;
}
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
connection = DBUtils.getConnection();
//定义SQL
String sql = "select id, title, content, createTime, userId from blog where id = ?";
statement = connection.prepareStatement(sql);
//替换占位符
statement.setLong(1,id);
resultSet = statement.executeQuery();
//遍历结果集
if (resultSet.next()){
//获取数据
Blog blog= new Blog();
blog.setId(resultSet.getLong(1));
blog.setTitle(resultSet.getString(2));
blog.setContent(resultSet.getString(3));
blog.setCreateTime(resultSet.getTimestamp(4));
blog.setUserId(resultSet.getLong(5));
//返回结果
return blog;
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
DBUtils.close(resultSet,statement,connection);
}
return null;
}
}
2.根据作者Id查询作者的信息(User Dao),也要去查询数据库。
public class UserDao {
/**
* 根据用户ID查询用户信息
* @param id 用户Id
* @return 对应的用户记录
*/
public User selectById (Long id){
// 非空校验
if (id == null || id <= 0) {
return null;
}
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet= null;
try {
connection = DBUtils.getConnection();
//定义SQL
String sql = "select id, username, password from user where id = ?";
statement = connection.prepareStatement(sql);
statement.setLong(1,id);
//获取结果集
resultSet= statement.executeQuery();
//处理结果集
if(resultSet.next()){
User user = new User();
user.setId(resultSet.getLong(1));
user.setUsername(resultSet.getString(2));
user.setPassword(resultSet.getString(3));
//返回结果
return user;
}
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
DBUtils.close(resultSet,statement,connection);
}
return null;
}
}
3.Servlet实现查看文章的详情
这里可以有两种实现方式,第一种是重新创建一个新的Servlet,并接收blogId参数,返回对应的博客内容。第二种是修改现有的获取文章列表里实现过的doGet方法,并对是否传入了blogId参数进行判断,然后执行相应的逻辑。我们来实现第二种方式。
@WebServlet("/blog")
public class BlogServlet extends HttpServlet {
//用于数据库访问
private BlogDao blogDao = new BlogDao();
//转换json
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置编码格式
req.setCharacterEncoding("UTF-8");
resp.setContentType("application/json; charset=utf-8");
//定义返回的统一对象
AppResult<List<Blog>> appResult;
//校验用户登录状态
if(UserUtils.checkUserLoginStatus(req) == null){
//设置HTTP的状态码
resp.setStatus(403);
//错误描述
appResult = AppResult.failed("用户没有登录,请登陆后再试");
//表示用户没有登录
resp.getWriter().write(objectMapper.writeValueAsString(appResult));
//中断代码的流程
return;
}
// 获取blogId参数
String blogId = req.getParameter("blogId");
// 定义返回的JSON变量
String jsonString = null;
// 判断参数是否为空
if (StringUtils.isEmpty(blogId)) {
// 没有传blogId 获取博客列表
List<Blog> blogs = blogDao.selectAll();
// 为空时
if (blogs == null) {
blogs = new ArrayList<>();
}
// 序列化
jsonString = objectMapper.writeValueAsString(AppResult.success(blogs));
} else {
// 传入了blogId 根据blogId去查询指定的博客详情
Blog blog = blogDao.selectById(Long.valueOf(blogId));
// 序列化
jsonString = objectMapper.writeValueAsString(AppResult.success(blog));
}
// 返回JSON
resp.getWriter().write(jsonString);
}
}
4.Servlet设置作者信息:先通过blogId获取到博客信息,再通过博客Blog中的userId字段,去用户表中查询出对应的用户信息。 这里也有两种方式,我们通过修改UserServlet的doGet方法来实现。
@WebServlet("/user")
public class UserServlet extends HttpServlet {
private ObjectMapper objectMapper = new ObjectMapper();
private UserDao userDao = new UserDao();
private BlogDao blogDao = new BlogDao();
//获取用户信息
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置编码格式
req.setCharacterEncoding("UTF-8");
resp.setContentType("application/json; charset=utf-8");
//定义统一返回的数据类型
AppResult appResult;
//获取session对象
HttpSession session = req.getSession(false);
//判断session
if(UserUtils.checkUserLoginStatus(req) == null){
//设置HTTP的状态码
resp.setStatus(403);
//错误描述
appResult = AppResult.failed("用户没有登录,请登陆后再试");
//表示用户没有登录
resp.getWriter().write(objectMapper.writeValueAsString(appResult));
//中断代码的流程
return;
}
// 获取传的参数blogId
String blogId = req.getParameter("blogId");
// 返回的JSON
String jsonString = null;
User user = null;
// 不传blogId时从session中获取当前登录的用户信息
if (StringUtils.isEmpty(blogId)) {
// 获取当前登录的用户
user = (User) session.getAttribute(AppConfig.USER_SESSION_KEY);
// 返回JSON
jsonString = objectMapper.writeValueAsString(AppResult.success(user));
} else {
// 查询博客信息
Blog blog = blogDao.selectById(Long.valueOf(blogId));
// 查询到的结果为空
if (blog == null) {
resp.getWriter().write(objectMapper.writeValueAsString(AppResult.failed("没有找到对应博客")));
return;
}
// 根据userId查询用户,这个用户就是当前博客的作者
user = userDao.selectById(blog.getUserId());
// 判断User是否为空
if (user == null) {
resp.getWriter().write(objectMapper.writeValueAsString(AppResult.failed("用户不存在")));
return;
}
// 返回作者
jsonString = objectMapper.writeValueAsString(AppResult.success(user));
}
// 返回结果
resp.getWriter().write(jsonString);
}
}
服务端测试
1.当指定博客Id并发送GET请求时,返回了博客的详细信息以及作者的Id,符合预期。
2.当指定博客Id并发送GET请求时,返回了发布该博客的作者信息。
客户端实现(blog_details)
前端可以通过JS中的location对象获取浏览器地址栏query string 中的参数列表。只保留页面的结构,并起一个ID,在ajax成功回调中,设置对应ID的内容。
核心代码:
<script>
$(function () {
//获取当前博客的详细信息
$.ajax({
//请求方法
type: 'get',
//请求路径
url: 'blog' + location.search,
//回调
success: function (respData) {
if (respData.code == 0) {
let blog = respData.data;
//成功,为页面中的相应标签赋值
$('#div_details_title').html(blog.title);
$('#div_details_createTime').html(formatDate(blog.createTime));
// $('#div_details_content').html(blog.content);
//格式化内容
editormd.markdownToHTML('div_details_content', { markdown: blog.content });
} else {
//错误
alter(respData.message);
}
},
error: function () {
//打印日志
console.log('访问出现错误');
},
statusCode: {
403: function () {
//强制跳转到登陆界面
location.assign('blog_login.html');
}
}
});
//获取作者详情信息
$.ajax({
type: 'get',
url: 'user' + location.search,
//回调
success: function (respData) {
if (respData.code == 0) {
let user = respData.data;
//成功,设置作者名
$('#h_datails_username').html(user.username);
} else {
//错误
alter(respData.message);
}
},
error: function () {
//打印日志
console.log('访问出现错误');
},
statusCode: {
403: function () {
//强制跳转到登陆界面
location.assign('blog_login.html');
}
}
});
})
</script>
注意,这里有一个bug: 通过MarkDown编辑之后详情界面仍然显示的是字符串格式而不是MarkDown格式,因此需要格式化。
客户端测试
通过点击不同的帖子,在显示不同的作者信息时,显示不同的作者信息。
发布文章
1.提供一个插入数据的方法;
2.Servlet对提交的参数做非空校验,并且从session中获取用户;
3.前端发送post请求,把用户页面上的数据提交到Servlet;
4.成功之后,前端跳转到blog_list.html界面,展示出博客列表。
约定前后端交互接口
[请求]
POST/blog
Content-type : application/x-www-form-urlencoded
title=标题&content=正文...
[响应]
"code": 0,
"message":发布成功,
服务端实现
1.提供插入数据的方法
public class BlogDao {
/**
* 发布新的博客
* @param blog
* @return 受影响的行数
*/
public int insert(Blog blog){
//非空校验
if(blog == null || StringUtils.isEmpty(blog.getTitle()) || StringUtils.isEmpty(blog.getContent()) ||
blog.getUserId() == null || blog.getCreateTime() == null){
return 0;
}
//定义操作数据库过程中使用的对象
Connection connection = null;
PreparedStatement statement = null;
try {
connection = DBUtils.getConnection();
//定义sql
String sql = "insert into blog values(null, ?, ?, ?, ?)";
//预处理sql
statement = connection.prepareStatement(sql);
statement.setString(1,blog.getTitle());
statement.setString(2,blog.getContent());
statement.setTimestamp(3,blog.getCreateTime());
statement.setLong(4,blog.getUserId());
//执行sql
int row = statement.executeUpdate();
return row;
} catch (SQLException e) {
e.printStackTrace();
}finally {
DBUtils.close(null,statement,connection);
}
return 0;
}
}
2.Servlet实现:这里是提交参数,通过重写doPost方法实现。
@WebServlet("/blog")
public class BlogServlet extends HttpServlet {
/**
* 发布博客
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置编码格式
req.setCharacterEncoding("UTF-8");
resp.setContentType("application/json; charset=utf-8");
//获取当前登陆的用户信息
User user = UserUtils.checkUserLoginStatus(req);
//校验用户登录状态
if(user == null){
//设置HTTP的状态码
resp.setStatus(403);
//表示用户没有登录
resp.getWriter().write(objectMapper.writeValueAsString(AppResult.failed("用户没有登录,请登陆后再试")));
//中断代码的流程
return;
}
//接收用户提交的参数
String title = req.getParameter("title");
String content = req.getParameter("content");
if(StringUtils.isEmpty(title) || StringUtils.isEmpty(content)){
resp.getWriter().write(objectMapper.writeValueAsString(AppResult.failed("标签或正文不能为空")));
return;
}
//构造blog对象
Blog blog = new Blog();
blog.setTitle(title); //标题
blog.setContent(content);//内容
blog.setCreateTime(new Timestamp(System.currentTimeMillis()));//发布时间
blog.setUserId(user.getId());//当前用户就是作者
//调用DAO,写入数据库
int row = blogDao.insert(blog);
if(row <= 0){
resp.getWriter().write(objectMapper.writeValueAsString(AppResult.failed("保存文章失败,请联系管理员。")));
return;
}
//返回成功
resp.getWriter().write(objectMapper.writeValueAsString((AppResult.success("发布成功"))));
}
}
服务端测试
1.当没有填写文章的title或内容时,返回了标签或正文不能为空,符合预期。
2.填写完标签和内容,返回了发布成功,并在Blog表中查到了插入的数据。
客户端实现(blog_edit)
前端要集成一个编辑器插件。要定义样式和插件的JS依赖。初始化编辑器:editormd会在页面中找到指定ID的DIV,在DIV中完成编辑器的初始化。在DIV中添加一个文本域,编辑器会自动把用户输入的内容设置在到这个文本域。这个文本域本身就是一个表单标签,可以通过.val()的形式拿到当中的值。
<!-- 创建一个div,起一个id -->
<div id="blog_edit">
<textarea id="text_edit_content" style="display : none;"></textarea>
</div>
首先给提交按钮绑定事件,在事件内先校验title和content的值,校验通过之后构造要发送的数据,发送ajax请求,然后进行回调,发布成功则跳转到博客列表页面,失败则给用户显示错误信息。
核心代码:
<!-- 初始化编辑器 -->
<script type="text/javascript">
$(function () {
var editor = editormd("blog_edit", {
width: "100%",
height: "100%",
// theme : "dark",
// previewTheme : "dark",
// editorTheme : "pastel-on-dark",
codeFold: true,
//syncScrolling : false,
saveHTMLToTextarea: true, // 保存 HTML 到 Textarea
searchReplace: true,
//watch : false, // 关闭实时预览
htmlDecode: "style,script,iframe|on*", // 开启 HTML 标签解析,为了安全性,默认不开启
// toolbar : false, //关闭工具栏
// previewCodeHighlight : false, // 关闭预览 HTML 的代码块高亮,默认开启
emoji: true,
taskList: true,
tocm: true, // Using [TOCM]
tex: true, // 开启科学公式TeX语言支持,默认关闭
flowChart: true, // 开启流程图支持,默认关闭
sequenceDiagram: true, // 开启时序/序列图支持,默认关闭,
placeholder: '开始创作...', // 占位符
path: "./editor.md/lib/"
});
// 绑定按钮的点击事件
$('#submit').click(function() {
// 获取用户输入
let titleEl = $('#title');
if (!titleEl.val()) {
alert('请输入文章标题');
titleEl.focus();
return;
}
let contentEl = $('#text_edit_content');
if (!contentEl.val()) {
alert('请输入文章内容');
return;
}
// 构选发送的数据
let postData = {
title : titleEl.val(),
content: contentEl.val()
}
// 提交请求
$.ajax ({
// 请求方法
type : 'post',
url: 'blog',
contentType : 'application/x-www-form-urlencoded',
data : postData,
// 回调
success : function(respData) {
if (respData.code == 0) {
alert('发布成功.');
// 发送成功,跳转到blog_list.html
location.assign('blog_list.html');
} else {
alert(respData.message);
}
},
error : function () {
// 打印日志
console.log('访问出现错误。');
},
statusCode : {
403 : function () {
// 未登录,强制跳转到登录页面
location.assign('blog_login.html');
}
}
});
});
});
</script>
客户端测试
1.当只写博客标题而不写内容时,页面弹出请输入文章。
2.写完博客点击提交,弹出发布成功,然后跳转到博客列表页面。
删除文章
服务端实现
1.首先要定义SQL语句,然后提供相应的接口 。
public class BlogDao {
/**
* 删除一条博客
* @param id
* @return
*/
public int deleteById (Long id) {
// 非空校验
if (id == null || id <= 0) {
return -1;
}
Connection connection = null;
PreparedStatement statement = null;
try {
connection = DBUtils.getConnection();
String sql = "delete from blog where id = ?";
statement = connection.prepareStatement(sql);
// 替换占位符
statement.setLong(1, id);
// 执行SQL
int row = statement.executeUpdate();
return row;
} catch (SQLException e) {
e.printStackTrace();
} finally {
DBUtils.close(null , statement, connection);
}
return -1;
}
}
2.修改UserServlet中的doGet方法中获取用户信息的代码:
通过传入的参数blodId查询文章详情,然后获取到Blog对象中的userId的值,通过这个值和和当前登录的UserId作比较,相同则是作者,不同则不是。
@WebServlet("/user")
public class UserServlet extends HttpServlet {
private ObjectMapper objectMapper = new ObjectMapper();
private UserDao userDao = new UserDao();
private BlogDao blogDao = new BlogDao();
//获取用户信息
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置编码格式
req.setCharacterEncoding("UTF-8");
resp.setContentType("application/json; charset=utf-8");
//定义统一返回的数据类型
AppResult appResult;
//获取session对象
HttpSession session = req.getSession(false);
//判断session
if(UserUtils.checkUserLoginStatus(req) == null){
//设置HTTP的状态码
resp.setStatus(403);
//错误描述
appResult = AppResult.failed("用户没有登录,请登陆后再试");
//表示用户没有登录
resp.getWriter().write(objectMapper.writeValueAsString(appResult));
//中断代码的流程
return;
}
// 获取传的参数blogId
String blogId = req.getParameter("blogId");
// 返回的JSON
String jsonString = null;
User user = null;
// 不传blogId时从session中获取当前登录的用户信息
if (StringUtils.isEmpty(blogId)) {
// 获取当前登录的用户
user = (User) session.getAttribute(AppConfig.USER_SESSION_KEY);
// 返回JSON
jsonString = objectMapper.writeValueAsString(AppResult.success(user));
} else {
// 查询博客信息
Blog blog = blogDao.selectById(Long.valueOf(blogId));
// 查询到的结果为空
if (blog == null) {
resp.getWriter().write(objectMapper.writeValueAsString(AppResult.failed("没有找到对应博客")));
return;
}
// 根据userId查询用户,这个用户就是当前博客的作者
user = userDao.selectById(blog.getUserId());
// 判断User是否为空
if (user == null) {
resp.getWriter().write(objectMapper.writeValueAsString(AppResult.failed("用户不存在")));
return;
}
// 判断当前登录的用户是不是文章作者
User currentUser = (User) session.getAttribute(AppConfig.USER_SESSION_KEY);
if (currentUser.getId() == blog.getUserId()) {
// 表示当前登录用户就是作者
user.setAuthor(true);
}
// 返回作者
jsonString = objectMapper.writeValueAsString(AppResult.success(user));
}
// 返回结果
resp.getWriter().write(jsonString);
}
}
3.Servlet实现删除博客,重写doDelete方法。这是再次判断是否是作者本人,否则无权删除文章。
@WebServlet("/blog")
public class BlogServlet extends HttpServlet {
//用于数据库访问
private BlogDao blogDao = new BlogDao();
//转换json
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 设置编码格式
req.setCharacterEncoding("UTF-8");
resp.setContentType("application/json; charset=utf-8");
// 获取参数
String blogId = req.getParameter("blogId");
// 非空校验
if (StringUtils.isEmpty(blogId)) {
resp.getWriter().write(objectMapper.writeValueAsString(AppResult.failed("没有找到文章编号")));
return;
}
// 获取当前登录的用户
// 3. 判断session
User user = UserUtils.checkUserLoginStatus(req);
if (user == null) {
String jsonString = objectMapper.writeValueAsString(AppResult.failed("用户没有登录, 请登录后再试"));
// 表示没用户没有登录
resp.getWriter().write(jsonString);
// 设置HTTP的状态码
resp.setStatus(403);
// 中断代码的流程
return;
}
// 获取文章详情
Blog blog = blogDao.selectById(Long.valueOf(blogId));
// 校验文章是否存在
if (blog == null ) {
resp.getWriter().write(objectMapper.writeValueAsString(AppResult.failed("文章不存在")));
return;
}
// 判断是不是作者
if (user.getId() != blog.getUserId()) {
resp.getWriter().write(objectMapper.writeValueAsString(AppResult.failed("无权删除别人的文章")));
return;
}
// 调用DAO,进行删除操作
int row = blogDao.deleteById(Long.valueOf(blogId));
if (row != 1) {
resp.getWriter().write(objectMapper.writeValueAsString(AppResult.failed("删除失败")));
return;
}
// 删除成功
resp.getWriter().write(objectMapper.writeValueAsString(AppResult.success("删除成功")));
}
}
服务端测试
1.当删除别人的文章,返回了无权删除别人的文章,符合预期。
2.当删除自己的文章之后,返回了删除成功,此时blog表中也删除了这篇文章。
客户端实现
前端根据author是否为true来初始化一个删除按钮,并绑定事件。删除文章之后跳转到列表页面。
核心代码:
<script>
$(function () {
//获取作者详情信息
$.ajax({
type: 'get',
url: 'user' + location.search,
//回调
success: function (respData) {
if (respData.code == 0) {
let user = respData.data;
//成功,设置作者名
$('#h_datails_username').html(user.username);
//生成删除按钮
let htmlStr = '<a href="javascript:void(0);">删除</a>';
//把html转成jQuery对象并追加到页面上
let deleteEl = $(htmlStr);
//获取到要追加元素的父标签
$('.opts').append(deleteEl);
//绑定事件
deleteEl.click(deleteBlog);
} else {
//错误
alter(respData.message);
}
},
error: function () {
//打印日志
console.log('访问出现错误');
},
statusCode: {
403: function () {
//强制跳转到登陆界面
location.assign('blog_login.html');
}
}
});
//删除文章事件
function deleteBlog() {
if (!confirm('是否删除?')) {
return;
}
//发送请求
$.ajax({
type: 'delete',
url: 'blog' + location.search,
//回调
success: function (respData) {
if (respData.code == 0) {
//跳转到列表页
location.assign('blog_list.html')
} else {
//错误
alter(respData.message);
}
},
error: function () {
//打印日志
console.log('访问出现错误');
},
statusCode: {
403: function () {
//强制跳转到登陆界面
location.assign('blog_login.html');
}
}
});
}
})
</script>
客户端测试
在文章详情页,点击删除文章,弹出是否删除,点击确认文章被删除,符合预期。
注销登录
服务端实现
在服务器把用户对应的session销毁。
@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 设置编码格式
req.setCharacterEncoding("UTF-8");
resp.setContentType("application/json; charset=utf-8");
//获取session
HttpSession session = req.getSession(false);
//判断session是否有效
if(session != null){
//销毁session
session.invalidate();
}
resp.getWriter().write(objectMapper.writeValueAsString(AppResult.success("注销成功")));
}
}
服务端测试
客户端实现
在文章列表页面生成注销按钮,当点击注销之后,页面跳转到登录界面。
核心代码:
<script>
$(function(){
// 为注销按钮绑定事件
$('#a_list_logout').click(function () {
// 发送请求
$.ajax({
type : 'get',
url : 'logout',
success : function (respData) {
if (respData.code == 0) {
// 成功,跳转到登录页面
location.assign('blog_login.html');
} else {
// 失败
alert(respData.message);
}
},
error : function() {
console.log('访问出现问题.');
}
});
});
})
</script>
总结:
1.前端怎么发送数据;
2.后端怎么接收数据;
3.前后端交互过程中参数的解析方式 (前后端要约定好);
4.后端接收到参数之后要做哪些校验;
5.数据库如何操作;
6.后端返回什么样的结果给前端;
7.前端拿到服务器的响应之后做什么样的处理;
继续加油~