前言
上篇谈到用bootstrap写几个简单的前端页面,当时说到那只是几个简单的静态页面,也就是拿来看的,不能与用户交互,用户点击按钮什么的也没什么反应。
这篇将会谈谈怎么补全对应的后端,从而让其成为“完整”的网站。我们从登录功能入手,因为登录功能不仅是诸多网站共同的功能需求,也是一个比较经典、具有代表性的且能够较好体现javaweb层次性的功能。(当然,最重要的还是因为这个功能简单,逻辑清晰,比较适合博主这种入门不久的萌新)
关于idea新建一个javaweb项目参见
idea基本web开发环境配置及新建javaweb项目
上篇:自己动手搭网站(四):用bootstrap写几个简单的页面
更多相关文章见:自己动手搭建网站系列总目录
目录
一、一些基本概念简介
jsp:(JavaServer Pages)故名思意,java服务页面,它的本质也是一个java类,也是servlet(servlet的本质也是Java类)。jsp页面可以嵌入Java语言,但是一般不会大量嵌入,只是用来搭建前端,比较普遍的方式是嵌入一些特殊标记或通过js来实现和后端servlet的交互。
下面是本次演示的登录功能的前端代码(login.jsp)
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta charset=UTF-8">
<title>微光落尘的个人空间</title>
<link rel="stylesheet" href="css/bootstrap.min.css">
<script src="js/jquery-3.5.1.min.js"></script>
<script src="js/bootstrap.min.js"></script>
<style>
.in_style{
background-color:rgba(30,144,255,0.1);
color:#1E90FF;
border:1px solid #1E90FF
}
body{
background:url("img/north-star-2869817_1920.jpg") no-repeat center center; /*加载背景图*/ /* 背景图不平铺 */
background-size:cover; /* 让背景图基于容器大小伸缩 */
background-attachment:fixed; /* 当内容高度大于图片高度时,背景图像的位置相对于viewport固定 */
background-color:#CCCCCC; /* 设置背景颜色,背景图加载过程中会显示背景色 */
}
.myBox{
background-color: rgba(30,144,255,0.1);
border:1px solid #1E90FF;
border-radius: 10px;
}
</style>
</head>
<body>
<div class="text-center">
<div class="container">
<div class="row" style="margin-top:30vh;">
<div class="col-sm-4 col-md-offset-8 myBox">
<div class="row" style="margin-bottom: 5vh">
<h1 style="color: rgba(253,252,252,0.98)">小尘空间传送门</h1>
</div>
<form id="form1" class="bs-example bs-example-form" role="form" action="${pageContext.request.contextPath}/login" method="post">
<%-- 登录失败提示信息--%>
<div class="info" style="color:red">${error}</div>
<div class="input-group" >
<span class="input-group-addon control-label in_style">时空节点</span>
<input type="text" id="username" name="username" class="form-control in_style" placeholder="">
</div>
<br>
<div class="input-group" style="margin-top:10px">
<span class="input-group-addon control-label in_style">开启秘钥</span>
<input type="password" name="password" class="form-control in_style" placeholder="">
</div>
<br>
<div class="input-group-btn" style="padding-top:3vh;padding-bottom: 1vh;text-align: end">
<input type="submit" id="login" class="btn btn-default" style="border:none;background-color: rgba(30,144,255,0.4);color: #1E90FF">点击传送</input>
</div>
</form>
</div>
</div>
</div>
</div>
</body>
</html>
servlet:(java服务小程序)用Java编写的服务器端小程序,由服务器调用和执行的Java类。要注意的是,servlet应继承HttpServlet类,重写doGet方法。方法内一般是接收前端jsp传来的数据,调用更下层的服务进行处理,最后返回给前端某些数据或切换路由,使用户看到不同的页面。
简单说下doGet方法的两个参数,当客户端向服务器发起请求时,服务器会创建一个代表请求的request对象,和一个代表响应的response对象。通过request对象可以获得客户端提交过来的信息,通过response对象可以将信息返回给客户端。它们的生存周期是从请求开始到客户端收到返回信息(请求结束)。
比如下面这个函数,服务于前端登录页面,通过req.getParameter()方法取得前端传递的参数,调用下层方法验证用户名和密码,根据验证结果执行不同的操作。若登录验证成功,将用户信息保存到session中,并使得页面跳转到首页;若验证失败,给前端一个失败信息,仍然将登录页面拉取到当前页面(刷新)
这里简单解释下session,所谓session,就是服务器为保存用户状态而创建的一个特殊对象,博主的理解是可以保存你从进入某个网站到离开那个网站的整个过程的状态信息(比如登录状态,购物车状态…);而上面说的request对象,一但请求完成,其就会被销毁,存放在其中的信息也就随之遗失。session的生存周期是可以设置的,tomcat中默认为30分钟。
需要注意的是,servlet需要在部署文件(web.xml)中声明,这样服务器(tomcat)才能对其进行调用。
<servlet-name>:为了映射取的一个名字,不能重复,但也就在这用用,相当于url-pattern和servlet-class间的一个中间变量。
<servlet-class>:servlet类完整路径,它的本质身份。
<url-pattern>:servlet在用户端的化名,很有用,tomcat这个“中介”知道servlet的真实身份,但是客户只能通过化名找“中介”请求服务,tomcat会操作servlet对客户的请求进行响应;在博主看来,所谓url就像是tomcat这个“中介”为了维持servlet的神秘性而展示给用户的一个代号,就像小说中的刺客代号一样。
一个完整的寻找servlet的过程为:url-pattern=>servlet-name=>servlet-class
下面是一个实例:
我的前端页面为login.jsp,我在前端输入用户名和错误的密码后页面变化如下:
可以看到url发生变化,最后部分就是servlet的url-pattern,而上面截取的后端代码中的req.getRequestDispatcher(“login.jsp”).forward(req, resp)
作用就是将登录页面拉取到当前位置,此时这个登录页面的url不是登录页面原本的url(login.jsp)而是servlet的url-pattern(login)。
二、登录功能的后端介绍
前面提到过,jsp页面一般是用来搭建用户视图的(前端页面),servlet接收前端传过来的数据,并调用更深层的方法进行处理,这里就简单谈下后端的层次。
在javaweb中,一般servlet只是简单的接收前端传来的信息,并调用更深层的方法进行处理,最终返回处理的结果信息给前端。而其调用的这层,则为service层,顾名思义,也就是服务层或业务层,复杂的业务逻辑一般会放到这层,其实这层的主要作用就是接收servlet传来的信息,并且调用dao层(数据访问层)获取数据库中某些必要的数据,进行逻辑判断,给出业务需求的结果,并将之返回给servlet,最终返回到客户端(不过一些简单的逻辑判断也可能直接被放在servlet层,虽然严格来说这并不合适,但实际开发…)。
//上述登录功能的service层关键代码
@Override
public User login(String userId, String password) {
Connection connection = null;
User user = null;
try {
connection = BaseDao.getConnection();
user = userdao.getUser(connection,userId);
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
BaseDao.closeResourse(connection,null,null);
}
return user;
}
最底层就是上面提到的Dao层(数据访问层),是直接和数据库打交道,操作数据库中数据的一层,通常包括对数据的增删改查等操作。
(不过严格地说起来,中间应该还有个jdbc,所谓jdbc,就是Java的数据库访问规范,它屏蔽了各种不同数据库的差异,可以为多种关系数据库提供统一访问,看起似乎没什么,但如果多了解一点数据库相关知识,就会觉得这玩意真方便,nb!)
顺便一说,若对每张表都编写一套增删改差方法未必有些麻烦,并且不符合编码的基本准则。一般,我们可以将这些功能的所公用的部分封装为一个对象,或者是类,此类是所有DAO的基类,可以称为:BaseDao。
此外,一般数据库中的每个表对应java中的一个基本类,数据库表中的字段就是类中的私有属性,每个属性都有gertter和setter方法。将之理解为简单的协助类就行,反正大家都是这么用的…
//上述登录功能的Dao层关键代码
@Override
public User getUser(Connection connection, String userId) throws SQLException {
PreparedStatement pstm = null;
ResultSet result = null;
User user = null;
if(connection != null){
String sql = "select * from user where user_id = ?";
Object[] params = {
userId};
result = BaseDao.execute(connection,sql,result,pstm,params);
if(result.next()){
user = new User();
user.setUserId(result.getString("user_id"));
user.setPassword(result.getString("password"));
}
BaseDao.closeResourse(null,result,pstm);
}
return user;
}
通过上面的介绍,我们可以得到javaweb处理用户请求的一个参考流程:
三、完整代码
前端(login.jsp)
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta charset=UTF-8">
<title>微光落尘的个人空间</title>
<link rel="stylesheet" href="css/bootstrap.min.css">
<script src="js/jquery-3.5.1.min.js"></script>
<script src="js/bootstrap.min.js"></script>
<style>
.in_style{
background-color:rgba(30,144,255,0.1);
color:#1E90FF;
border:1px solid #1E90FF
}
body{
background:url("img/north-star-2869817_1920.jpg") no-repeat center center; /*加载背景图*/ /* 背景图不平铺 */
background-size:cover; /* 让背景图基于容器大小伸缩 */
background-attachment:fixed; /* 当内容高度大于图片高度时,背景图像的位置相对于viewport固定 */
background-color:#CCCCCC; /* 设置背景颜色,背景图加载过程中会显示背景色 */
}
.myBox{
background-color: rgba(30,144,255,0.1);
border:1px solid #1E90FF;
border-radius: 10px;
}
</style>
</head>
<body>
<div class="text-center">
<div class="container">
<div class="row" style="margin-top:30vh;">
<div class="col-sm-4 col-md-offset-8 myBox">
<div class="row" style="margin-bottom: 5vh">
<h1 style="color: rgba(253,252,252,0.98)">小尘空间传送门</h1>
</div>
<form id="form1" class="bs-example bs-example-form" role="form" action="${pageContext.request.contextPath}/login" method="post">
<%-- 登录失败提示信息--%>
<div class="info" style="color:red">${error}</div>
<div class="input-group" >
<span class="input-group-addon control-label in_style">时空节点</span>
<input type="text" id="username" name="username" class="form-control in_style" placeholder="">
</div>
<br>
<div class="input-group" style="margin-top:10px">
<span class="input-group-addon control-label in_style">开启秘钥</span>
<input type="password" name="password" class="form-control in_style" placeholder="">
</div>
<br>
<div class="input-group-btn" style="padding-top:3vh;padding-bottom: 1vh;text-align: end">
<input type="submit" id="login" class="btn btn-default" style="border:none;background-color: rgba(30,144,255,0.4);color: #1E90FF">点击传送</input>
</div>
</form>
</div>
</div>
</div>
</div>
</body>
</html>
servlet(LoginServlet.java)
package com.shimmer.servlet.user;
import com.shimmer.pojo.User;
import com.shimmer.service.user.UserService;
import com.shimmer.service.user.UserServiceImpl;
import com.shimmer.until.Constants;
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 LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("login ============ ");
//获取用户名和密码
String username = req.getParameter("username");
String password = req.getParameter("password");
//调用service层方法,进行用户匹配
UserService userService = new UserServiceImpl();
User user = userService.login(username, password);
if (user != null && password.equals(user.getPassword()) ){
//登录成功
System.out.println("login success");
//将用户信息放入session
req.getSession().setAttribute(Constants.ADMIN_SESSION, user);
//页面跳转至index.jsp
resp.sendRedirect("personalCenter/personalCenter.jsp");
} else {
//页面跳转(login.jsp)带出提示信息--转发
System.out.println("login failure");
req.setAttribute("error", "用户名或密码错误");
req.getRequestDispatcher("login.jsp").forward(req, resp);
}
}
@Override
protected void doPost(HttpServletRequest req,HttpServletResponse resp) throws ServletException,IOException{
//自动生成的方法存根
doGet(req,resp);
}
}
部署文件(web.xml)
<servlet>
<servlet-name>LoginServlet</servlet-name>
<servlet-class>com.shimmer.servlet.user.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginServlet</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
service
UserService.java
package com.shimmer.service.user;
import com.shimmer.pojo.User;
public interface UserService {
public User login(String userId, String password);
}
UserServiceImpl.java
package com.shimmer.service.user;
import com.shimmer.dao.BaseDao;
import com.shimmer.dao.UserDao;
import com.shimmer.dao.UserDaoImpl;
import com.shimmer.pojo.User;
import org.junit.jupiter.api.Test;
import java.sql.Connection;
import java.sql.SQLException;
public class UserServiceImpl implements UserService{
private UserDao userdao;
public UserServiceImpl(){
userdao = new UserDaoImpl();
}
@Override
public User login(String userId, String password) {
Connection connection = null;
User user = null;
try {
connection = BaseDao.getConnection();
user = userdao.getUser(connection,userId);
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
BaseDao.closeResourse(connection,null,null);
}
return user;
}
}
Dao
UserDao.java
package com.shimmer.dao;
import com.shimmer.pojo.User;
import java.sql.Connection;
import java.sql.SQLException;
public interface UserDao {
public User getUser(Connection connection, String userId) throws SQLException;
}
UserDaoImpl.java
package com.shimmer.dao;
import com.shimmer.pojo.User;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class UserDaoImpl implements UserDao{
@Override
public User getUser(Connection connection, String userId) throws SQLException {
PreparedStatement pstm = null;
ResultSet result = null;
User user = null;
if(connection != null){
String sql = "select * from user where user_id = ?";
Object[] params = {
userId};
result = BaseDao.execute(connection,sql,result,pstm,params);
if(result.next()){
user = new User();
user.setUserId(result.getString("user_id"));
user.setPassword(result.getString("password"));
}
BaseDao.closeResourse(null,result,pstm);
}
return user;
}
}
BaseDao.java
package com.shimmer.dao;
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
//操作数据库的公共类
public class BaseDao {
private static String driver;
private static String url;
private static String username;
private static String password;
static {
Properties properties = new Properties();
//通过类加载器读取对应的资源
InputStream is = BaseDao.class.getClassLoader().getResourceAsStream("db.properties");
try {
properties.load(is);
} catch (IOException e) {
e.printStackTrace();
}
driver = properties.getProperty("driver");
url = properties.getProperty("url");
username = properties.getProperty("username");
password = properties.getProperty("password");
}
//获取数据库连接
public static Connection getConnection(){
Connection con = null;
try{
Class.forName(driver);
con = DriverManager.getConnection(url,username,password);
}catch (Exception e){
e.printStackTrace();
}
return con;
}
//编写查询公共方法
public static ResultSet execute(Connection con,String sql,ResultSet result,PreparedStatement prestm,Object[] params) throws SQLException {
//预编译的sql
prestm = con.prepareStatement(sql);
for(int i = 0;i < params.length;i++){
prestm.setObject(i+1,params[i]);
}
result = prestm.executeQuery();
return result;
}
//更新操作
public static int update(Connection con,String sql,ResultSet result,PreparedStatement prestm,Object[] params) throws SQLException {
int res = 0;
try {
//预编译的sql
prestm = con.prepareStatement(sql);
for(int i = 0;i < params.length;i++){
prestm.setObject(i+1,params[i]);
}
res = prestm.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
res = 0;
}
System.out.println(res);
return res;
}
//关闭资源
public static boolean closeResourse(Connection con,ResultSet result,PreparedStatement prestm){
boolean flag = true;
if(result != null){
try {
result.close();
result = null;
} catch (SQLException e) {
e.printStackTrace();
flag = false;
}
}
if(prestm != null){
try {
prestm.close();
prestm = null;
} catch (SQLException e) {
e.printStackTrace();
flag = false;
}
}
if(con != null){
try {
con.close();
con = null;
} catch (SQLException e) {
e.printStackTrace();
flag = false;
}
}
return flag;
}
}
Pojo
User.java
package com.shimmer.pojo;
public class User {
private String userId;
private String password;
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
数据库相关
db.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/blog_system?useUnicode=true&characterEncoding=utf-8&userSSL=false&serverTimezone=GMT%2B8
username=数据库的用户名
password=自己设置的数据库密码
数据库脚本
-- 创建数据库
create database if not exists blog_system;
use blog_system;
-- 创建用户表
create table user(
user_id varchar(20) primary key,
password varchar(20)
);
-- 向用户表中插入一条数据
insert into user(user_id,password) values('admin','test@123');