五.进行前后端接口设计(基于HTTP基础上进行扩展)
- 获取注册页面
- 请求:GET/register.html
- 响应:注册页面,页面上带有两个输入框(用户名和密码),还有一个提交按钮。
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>注册</title> </head> <body> <form action="register" method="post"> <input type="text" name="name" placeholder="请输入用户名"> <br/> <input type="password" name="password" placeholder="请输入密码"> <br/> <input type="submit" value="注册"> </form> </body> </html>
2.实现注册功能
- 请求:POST/register
- 响应:返回一个提示页面,告诉用户注册成功或失败,并能跳转到登陆页面。
package api; import model.User; import model.UserDao; import view.HtmlGenerator; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class RegisterServlet extends HttpServlet { // 浏览器是通过 POST 方法提交注册信息给服务器的. @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html; charset=utf-8"); // 1. 获取到前端提交的数据(用户名, 密码), 校验是否合法. String name = req.getParameter("name"); String password = req.getParameter("password"); if (name == null || "".equals(name) || password == null || "".equals(password)) { // 用户提交的数据有误. // 返回一个错误页面. (一段字符串, html 构成得到) //"用户名或者密码为空"是页面中的提示信息,"register.html"表示接下来要跳转到哪里去 String html = HtmlGenerator.getMessagePage("用户名或者密码为空", "register.html"); resp.getWriter().write(html); return; } // 2. 拿着用户名在数据库中查一下, 看看当前用户名是否已经存在. 如果存在, 认为注册失败(用户名不能重复) UserDao userDao = new UserDao(); User existUser = userDao.selectByName(name); if (existUser != null) { // existUser 非空, 说明该用户名已经存在. 就提示用户, 注册失败, 用户名重复 String html = HtmlGenerator.getMessagePage("用户名重复! 请换个名字!", "register.html"); resp.getWriter().write(html); return; } // 3. 根据前端提交的数据, 构造 User 对象并插入到数据库中. User user = new User(); user.setName(name); user.setPassword(password); userDao.add(user); // 4. 返回一个结果页面, 提示当前注册成功. String html = HtmlGenerator.getMessagePage("注册成功! 点击跳转到登陆页面!", "login.html"); resp.getWriter().write(html); } }
3.获取登陆页面
- 请求:GET/login.html
- 响应:返回一个登陆页面,包含两个输入框和一个登陆按钮。
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>登录</title> </head> <body> <form action="login" method="post"> <input type="text" name="name" placeholder="请输入用户名"> <br/> <input type="password" name="password" placeholder="请输入密码"> <br/> <input type="submit" value="登录"> </form> </body> </html>
4.实现登录功能
- 请求:POST/login
- 响应:返回一个提示页面,告诉用户登陆成功或失败。
package api; import model.User; import model.UserDao; import view.HtmlGenerator; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; public class LoginServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html; charset=utf-8"); // 1. 获取到用户名和密码. 并进行简单校验 String name = req.getParameter("name"); String password = req.getParameter("password"); if (name == null || "".equals(name) || password == null || "".equals(password)) { String html = HtmlGenerator.getMessagePage("用户名或密码为空","login.html"); resp.getWriter().write(html); return; } // 2. 数据库中查找, 看用户是否存在. // 3. 对比密码是否匹配 UserDao userDao = new UserDao(); User user = userDao.selectByName(name); if (user == null || !password.equals(user.getPassword())) { //用户不存在,提示错误 String html = HtmlGenerator.getMessagePage("用户名或密码错误","login.html"); resp.getWriter().write(html); return; } // 4. 匹配成功则认为登陆成功, 创建一个 Session HttpSession httpSession = req.getSession(true); httpSession.setAttribute("name",user); // 5. 返回一个登陆成功的提示页面 String html = HtmlGenerator.getMessagePage("登陆成功","article"); resp.getWriter().write(html); } }
5.获取文章列表
- 请求:GET/article
- 响应:返回文章的列表页(文章标题),点击标题进入详情页。
6.获取文章详细内容
- 请求:GET/article?article=1
- 响应:返回文章的详情页(文章内容)
7.新增/发布文章
- 请求:POST/article/title=()&content=()
- 响应:返回一个提示页面,告诉用户发布成功还是失败。
package api; import model.Article; import model.ArticleDao; import model.User; import model.UserDao; import view.HtmlGenerator; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; import java.util.List; public class ArticleServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html; charset=utf-8"); // 1. 验证用户是否已经登陆了. 如果尚未登陆, 就提示用户要先登陆 HttpSession httpSession = req.getSession(false); if (httpSession == null) { // 当前是未登录状态 String html = HtmlGenerator.getMessagePage("请先登陆", "login.html"); resp.getWriter().write(html); return; } User user = (User) httpSession.getAttribute("user"); // 2. 判断请求中是否存在 articleId 参数. String articleIdStr = req.getParameter("articleId"); if (articleIdStr == null) { // a) 没有这个参数就去执行获取文章列表操作 getAllArticle(user, resp); } else { // b) 有这个参数就去执行获取文章详情操作. getOneArticle(Integer.parseInt(articleIdStr), user, resp); } } private void getAllArticle(User user, HttpServletResponse resp) throws IOException { // 1. 查找数据库 ArticleDao articleDao = new ArticleDao(); List<Article> articles = articleDao.selectAll(); // 2. 构造页面 String html = HtmlGenerator.getArticleListPage(articles, user); resp.getWriter().write(html); } private void getOneArticle(int articleId, User user, HttpServletResponse resp) throws IOException { // 1. 查找数据库 ArticleDao articleDao = new ArticleDao(); Article article = articleDao.selectById(articleId); if (article == null) { // 文章未找到 String html = HtmlGenerator.getMessagePage("文章不存在", "article"); resp.getWriter().write(html); return; } // 为了验证换行消失的问题, 在这个环节把 Article 中的正文打出来, 看看是不是带换行. // System.out.println("article.content: " + article.getContent()); // 2. 根据作者id 找到作者信息, 进一步得到作者姓名 UserDao userDao = new UserDao(); User author = userDao.selectById(article.getUserId()); // 3. 构造页面 String html = HtmlGenerator.getArticleDetailPage(article, user, author); resp.getWriter().write(html); } // 实现新增文章的逻辑 @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("utf-8"); resp.setContentType("text/html; charset=utf-8"); // 1. 判定用户的登陆状态. 如果用户尚未登陆, 就要提示用户来登陆. HttpSession httpSession = req.getSession(false); if (httpSession == null) { String html = HtmlGenerator.getMessagePage("您尚未登陆", "login.html"); resp.getWriter().write(html); return; } User user = (User) httpSession.getAttribute("user"); // 2. 从请求中读取浏览器提交的数据(title, content), 并进行简单校验 String title = req.getParameter("title"); String content = req.getParameter("content"); if (title == null || "".equals(title) || content == null || "".equals(content)) { String html = HtmlGenerator.getMessagePage("提交的标题或者正文为空", "article"); resp.getWriter().write(html); return; } // 3. 把数据插入到数据库中. ArticleDao articleDao = new ArticleDao(); Article article = new Article(); article.setTitle(title); article.setContent(content); article.setUserId(user.getUserId()); articleDao.add(article); // 4. 返回一个插入成功的页面. String html = HtmlGenerator.getMessagePage("发布成功!", "article"); resp.getWriter().write(html); return; } }
8.删除文章
- 请求:GET/deleteArticle?article=1
- 响应:返回一个提示页面,告诉用户删除成功还是失败。
package api; import model.Article; import model.ArticleDao; import model.User; import view.HtmlGenerator; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; public class DeleteServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html; charset=utf-8"); // 1. 验证用户的登陆状态, 如果未登陆, 肯定不能删除. HttpSession httpSession = req.getSession(false); if (httpSession == null) { String html = HtmlGenerator.getMessagePage("您尚未登陆!", "login.html"); resp.getWriter().write(html); return; } User user = (User) httpSession.getAttribute("user"); // 2. 读取请求内容, 获取到要删除的文章 id String articleIdStr = req.getParameter("articleId"); if (articleIdStr == null || "".equals(articleIdStr)) { String html = HtmlGenerator.getMessagePage("要删除的文章 id 有误", "article"); resp.getWriter().write(html); return; } // 3. 根据文章 id 查找到该文章的作者. 当前用户如果就是作者, 才能删除, 否则删除失败. ArticleDao articleDao = new ArticleDao(); Article article = articleDao.selectById(Integer.parseInt(articleIdStr)); if (article.getUserId() != user.getUserId()) { String html = HtmlGenerator.getMessagePage("您只能删除自己的文章!", "article"); resp.getWriter().write(html); return; } // 4. 真正执行数据库删除操作 articleDao.delete(Integer.parseInt(articleIdStr)); // 5. 返回一个 "删除成功" 的页面. String html = HtmlGenerator.getMessagePage("删除成功!", "article"); resp.getWriter().write(html); } }
package view; import model.Article; import model.User; import java.util.List; public class HtmlGenerator { // 通过字符串拼接的形式, 构造出一个 html 格式的内容来. // 下面的代码实现起来非常麻烦. (HTML 嵌入到 Java 代码中) // 如果页面简单还好, 如果页面复杂, 那就非常不好整了. // 使用这种写法, 代码简单粗暴(不需要引入新的知识的) // 实际开发中还有更科学的写法: // 1. 服务器渲染页面, 把业务代码嵌入到 HTML (JSP, PHP); // 2. 服务器渲染页面, 把 HTML 嵌入到业务代码中, 不使用字符串拼接的方式, // 而是使用模板的方式. (FreeMarker) // 3. 前端渲染页面, 通过前后端分离的方式, 服务器只是返回简单数据, 由前端代码同过 JS // 构造页面内容. [主流] public static String getMessagePage(String message, String nextUrl) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("<html>"); stringBuilder.append("<head>"); stringBuilder.append("<meta charset=\"utf-8\">"); stringBuilder.append("<title>提示页面</title>"); stringBuilder.append("</head>"); stringBuilder.append("<body>"); stringBuilder.append("<h3>"); stringBuilder.append(message); stringBuilder.append("</h3>"); stringBuilder.append(String.format("<a href=\"%s\"> 点击这里进行跳转 </a>", nextUrl)); stringBuilder.append("</body>"); stringBuilder.append("</html>"); return stringBuilder.toString(); } // 按照字符串拼装的方式, 生成 html public static String getArticleListPage(List<Article> articles, User user) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("<html>"); stringBuilder.append("<head>"); stringBuilder.append("<meta charset=\"utf-8\">"); stringBuilder.append("<title>提示页面</title>"); stringBuilder.append("<style>"); // style 标签内部就是写 CSS 的逻辑 stringBuilder.append(".article {" + "color: #333;" + "text-decoration: none;" + // "display: inline-block;" + "width: 200px;" + "height: 50px;" + "}"); stringBuilder.append(".article:hover {" + "color: white;" + "background-color: orange;" + "}"); stringBuilder.append("body {" + "background-repeat: none;" + "background-position: 0 center;" + "}"); stringBuilder.append("</style>"); stringBuilder.append("</head>"); stringBuilder.append("<body>"); stringBuilder.append("<h3> 欢迎您! " + user.getName() + "</h3>"); stringBuilder.append("<hr>"); // 要有一个文章列表. 显示每个文章的标题. for (Article article : articles) { stringBuilder.append(String.format("<div style=\"width: 200px; height: 50px; line-height: 50px\"> <a class=\"article\" href=\"article?articleId=%d\"> %s </a>" + "<a href=\"deleteArticle?articleId=%d\"> 删除 </a></div>", article.getArticleId(), article.getTitle(), article.getArticleId())); } stringBuilder.append("<hr>"); stringBuilder.append(String.format("<div>当前共有博客 %d 篇</div>", articles.size())); // 在这里新增发布文章的区域 stringBuilder.append("<div> 发布文章 </div>"); stringBuilder.append("<div>"); stringBuilder.append("<form method=\"post\" action=\"article\">"); stringBuilder.append("<input type=\"text\" style=\"width: 500px; margin-bottom: 5px;\" name=\"title\" placeholder=\"请输入标题\">"); stringBuilder.append("<br>"); stringBuilder.append("<textarea name=\"content\" style=\"width: 500px; height: 300px;\"></textarea>"); stringBuilder.append("<br>"); stringBuilder.append("<input type=\"submit\" value=\"发布文章\">"); stringBuilder.append("</form>"); stringBuilder.append("</div>"); stringBuilder.append("</body>"); stringBuilder.append("</html>"); return stringBuilder.toString(); } public static String getArticleDetailPage(Article article, User user, User author) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("<html>"); stringBuilder.append("<head>"); stringBuilder.append("<meta charset=\"utf-8\">"); stringBuilder.append("<title>提示页面</title>"); stringBuilder.append("<style>"); // style 标签内部就是写 CSS 的逻辑 stringBuilder.append("a {" + "color: #333;" + "text-decoration: none;" + "display: inline-block;" + "width: 200px;" + "height: 50px;" + "}"); stringBuilder.append("a:hover {" + "color: white;" + "background-color: orange;" + "}"); stringBuilder.append("body {" + "background-repeat: none;" + "background-position: 0 center;" + "}"); stringBuilder.append("</style>"); stringBuilder.append("</head>"); stringBuilder.append("<body>"); stringBuilder.append("<h3> 欢迎您! " + user.getName() + "</h3>"); stringBuilder.append("<hr>"); stringBuilder.append(String.format("<h1>%s</h1>", article.getTitle())); stringBuilder.append(String.format("<h4>作者: %s</h4>", author.getName())); // 构造正文的地方. // HTML 中本来就不是用 \n 表示换行的. stringBuilder.append(String.format("<div>%s</div>", article.getContent() .replace("\n", "<br>"))); stringBuilder.append("</body>"); stringBuilder.append("</html>"); return stringBuilder.toString(); } }