本项目所涉及的知识点:
后端:
1,MVC架构设计模式
2,Servlet技术
3,后端连接数据库的CRUD技术
4,后端Tomcat服务器的搭建
5,前后端通过JSON串实现数据交互
前端:
1,HTML,CSS,JavaScript前端三大件
2,VUE3框架
3,router路由
4,pinia共享数据,实现多组件之间的数据传递
5,axios代替原生ajax发送JSON
6,前端工程化
MVC设计模式
在我的上一篇满汉楼项目博客中就已经在使用MVC设计模式了,但是这里的MVC设计模式并不是Spring MVC,如下图所示:
MVC设计模式将整个项目分为了视图层,控制层和模型层三层。
1,视图层用于存放各前端文件,后续使用前端工程化技术就能彻底将前后端分离开。
2,Controller层是最重要的一个部分,因为这一层的存在才使得前后端能真正实现数据交互,在这一层中核心就使用了Servlet技术,用来接收客户端发送过来的数据并相应给服务端。
3,最后是Model层,在这一层中又细分为了服务层,DAO层,POJO层。
(1)服务层一般和Controller层进行对接,然后将业务发送给DAO层进行具体实现。
(2)而DAO层是直接与数据库连接的一层,在这一层中需要写sql语句与数据库实现交互
(3)POJO层中就是一些最基本的创建实体类,通俗来说就是setter, getter,构造器,无参构造器等。
注:在写的时候十分建议在一层中把接口和具体实现类分开,这样用户想要知道一个方法具体实现了什么业务只需要去看接口中的注释文档即可,而不用再去看具体的实现类了,可以说这是一种良好的开发规范。
Servlet技术
既然在刚刚讲MVC设计模式的时候提到了Servlet技术,在这里就解释一下。
在客户端向服务端请求数据的时候,请求的资源分为两种
一种是静态资源:即无需再程序运行时通过代码运行生成的资源,在程序运行之前就写好的资源,例如:html,css,js,img,音频文件和视频文件等
另一种是动态资源:即需要在程序运行时通过代码运行生成的资源,在程序运行之前无法确定的数据。例如向数据库中做CRUD操作就属于一种动态资源,这时请求资源就需要使用到Servlet技术。
servlet的开发流程如下:
1,创建JavaWeb项目,同时将tomcat添加为当前项目的依赖
2,重写service方法,service(HttpServletRequest req, HttpServletResponse resp)
3,在service方法中,定义业务处理代码
4,在web.xml中配置servlet对应的请求映射路径
<servlet>
<servlet-name>userServlet</servlet-name>
<servlet-class>com.ryy.servlet.userServlet</servlet-class>
</servlet>
<servlet-class>:告诉tomcat对应的要实例化的Servlet类
<servlet-name> :用于关联请求的映射路径
<servlet-mapping> //定义别名和请求映射路径的关系
<servlet-name>userServlet</servlet-name>
<url-pattern>/userServlet</url-pattern>
</servlet-mapping>
整个配置文件过程解析:tomcat会到<servlet-mapping>中去找到<url-pattern>,然后识别对应的<servlet-name>别名,再根据这个别名到<servlet>中对应别名,最后按<servlet-class>找到对应路径。
但是后期多用注解方式配置Servlet:
@WebServlet("/s1")
在使用url查找时输入/s1即能匹配
Tomcat技术
在刚才讲Servlet的时候,也是提到了tomcat,tomcat就是能够运行在本地的一个服务器软件,在这里我会详细讲其如何在IDEA中进行配置和运行:
tomcat的部署方式:
1,直接将编译好的项目放在webapps目录下
2,可将项目放在非webapps的其他目录下,在tomcat中通过配置文件指向App的实际路径
具体步骤:在tomcat的conf下创建Catalina/localhost目录,并在该目录下准备一个app.xml文件
文件中写: <Context path="/app" docBase="D:\mywebapps\app"/>
path:项目的访问路径,也是项目的上下文路径,就是在浏览器中输入的项目名称
docBase:项目在磁盘中的实际路径
在IDEA中添加tomcat依赖
1,src目录下存放JAVA代码
2,resources目录下存放一系列配置文件,例如我这里是JDBC的配置文件
3,文件-->项目结构-->模块-->添加Tomcat依赖-->对项目双击shift-->添加框架支持-->选择Java web6.0-->出现web目录,用于存放前端代码
4,Web-INF下的lib目录用于存放jar包
所有的jar包大家都可以去maven中自行搜索下载使用 https://mvnrepository.com/
在web.xml中的配置信息:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">
</web-app>
后端的各个工具类
JDBCUtil
package com.ryy.schedule.util;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
@SuppressWarnings("all")
public class JDBCUtil {
private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
private static DataSource dataSource;
//初始化连接池
static {
//可以帮我们读取.properties配置文件
Properties properties = new Properties();
InputStream resourceAsStream = JDBCUtil.class.getClassLoader().getResourceAsStream("jdbc.properties");
try {
properties.load(resourceAsStream);
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
//向外提供连接池的方法
public static DataSource getDataSource(){
return dataSource;
}
//向外提供连接的方法
public static Connection getConnection(){
Connection connection = threadLocal.get();
if(null == connection){
try {
connection = dataSource.getConnection();
} catch (SQLException e) {
throw new RuntimeException(e);
}
threadLocal.set(connection);
}
return connection;
}
//定义一个归还连接的方法(解除和ThreadLocal之间的关系联系)
public static void releaseConnection(){
Connection connection = threadLocal.get();
if(null != connection) {
threadLocal.remove();
//把连接设置回自动提交的连接
try {
connection.setAutoCommit(true);
//自动归还到连接池
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
}
MD5Util
该工具类是用来将用户的密码以MD5的形式进行加密然后存储到数据库中,主要是为了加密处理
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class MD5Util {
public static String encrypt(String strSrc){
try{
char hexChars[] = {'0','1','2','3','4','5','6','7','8','9',
'a','b','c','d','e','f'};
byte[] bytes = strSrc.getBytes();
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(bytes);
bytes = md.digest();
int j = bytes.length;
char[] chars = new char[j * 2];
int k = 0;
for(int i = 0; i < bytes.length; i++){
byte b = bytes[i];
chars[k++] = hexChars[b >>> 4 & 0xf];
chars[k++] = hexChars[b & 0xf];
}
return new String(chars);
}catch (NoSuchAlgorithmException e){
throw new RuntimeException("MD5加密出错");
}
}
}
WebUtil
该工具类中主要存放了一些读取前端传来的JSON串进行转换成对象的操作
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ryy.schedule.common.Result;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.text.SimpleDateFormat;
public class WebUtil {
private static ObjectMapper objectMapper;
static {
objectMapper = new ObjectMapper();
//设置JSON和Object转换的日期时间格式
objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
}
//从请求中获取JSON串并转换为Object
public static<T> T readJson(HttpServletRequest request, Class<T> clazz) { //Class<T> clazz参数的主要作用是在运行时提供类型信息,以便JSON库可以正确地执行反序列化操作
T t = null;
BufferedReader reader = null;
try{
//读取JSON串中每一行的信息
reader = request.getReader();
StringBuffer buffer = new StringBuffer();
String line = null;
//如果不为空就追加到字符串后面
while ((line = reader.readLine()) != null){
buffer.append(line);
}
t = objectMapper.readValue(buffer.toString(), clazz);
} catch (IOException e) {
throw new RuntimeException(e);
}
return t;
}
//将Result对象转换成JSON串并放入响应对象
public static void writeJson(HttpServletResponse resp, Result result) {
//告诉客户端响应给你的是一个JSON串
resp.setContentType("application/json;charset=UTF-8"); //在tomcat的conf目录的web.xml中搜索JSON找到的上下文路径
//将result对象转换为JSON串响应给客户端
try {
String json = objectMapper.writeValueAsString(result);
resp.getWriter().write(json);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
readJson就是用于读取传来的JSON串并将其转换为对象形式,writeJson就是用于将对象打包好以JSON串的格式发送给客户端。
DAO层,Controller层,Service层
一般这三层的逻辑就是Service层接收Controller层需要实现的方法,然后DAO层再接收Service层需要实现的方法,最后DAO层连接数据库进行crud操作
举例:前端发来需要进行删除日程的操作
//删除数据的函数
async function removeItem(index){
let sid = schedule.itemList[index].sid
let {data} = await request.get(`schedule/removeSchedule`, {params:{"sid": sid}})
if(data.code == 200){
showSchedule()
alert("删除成功")
}else{
alert("删除失败")
}
}
前端会向指定的Controller层的上下文路径发送请求(removeSchedule即为要执行的方法)
此时还没有removeSchedule的方法,需要在Controller层创建一个,然后交给Service层,再由Service层交给DAO层进行数据的删除操作
先创建一个服务层的对象以便等会能直接使用其调用服务层方法
private SysScheduleService scheduleService = new SysScheduleServiceImpl();
然后根据客户端传来的"sid"调用到服务层的方法,最后用JSON串的格式相应给客户端
protected void removeSchedule(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//接收请求中的sid参数
int sid = Integer.parseInt(req.getParameter("sid"));
//调用服务层方法 删除数据
scheduleService.removeSchedule(sid);
//响应成功信息
WebUtil.writeJson(resp, Result.ok(null));
}
来到服务层中:再进行对Dao层的调用,该Dao对象也是先前进行创建好的,类上面的创建Service对象
@Override
public Integer removeSchedule(int sid) {
return scheduleDao.removeSchedule(sid);
}
最后来到DAO层:向数据库发送删除的请求,并返回影响数据库的记录数,以便识别是否删除成功
@Override
public Integer removeSchedule(int sid) {
String sql = "delete from sys_schedule where sid=?";
return baseUpdate(sql, sid);
}
DAO层代码
BaseDAO
import com.ryy.schedule.util.JDBCUtil;
import java.lang.reflect.Field;
import java.sql.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@SuppressWarnings("all")
public class BaseDAO {
//公共的查询方法 返回的是单个对象
public <T> T baseQueryObjecct(Class<T> clazz, String sql, Object... args) {
T t = null;
Connection connection = JDBCUtil.getConnection();
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
int rows = 0;
try {
//准备语句对象
preparedStatement = connection.prepareStatement(sql);
//设置语句上的参数
for (int i = 0; i < args.length; i++) {
preparedStatement.setObject(i + 1, args[i]);
}
//执行,查询
resultSet = preparedStatement.executeQuery();
if (resultSet.next()) {
t = (T) resultSet.getObject(1);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != resultSet) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (null != preparedStatement) {
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
JDBCUtil.releaseConnection();
}
return t;
}
//公共的查询方法 返回的是对象的集合
public <T> List<T> baseQUery(Class clazz, String sql, Object... args) {
List<T> list = new ArrayList<>();
Connection connection = JDBCUtil.getConnection();
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
int rows = 0;
try {
//准备语句对象
preparedStatement = connection.prepareStatement(sql);
//设置语句上的参数
for (int i = 0; i < args.length; i++) {
preparedStatement.setObject(i + 1, args[i]);
}
//执行,查询
resultSet = preparedStatement.executeQuery();
ResultSetMetaData metaData = resultSet.getMetaData();
int columnCount = metaData.getColumnCount();
//将结果集通过反射封装成实体类对象
while (resultSet.next()) {
//用反射实例化对象
Object obj = clazz.getDeclaredConstructor().newInstance();
for (int i = 1; i <= columnCount; i++) {
String columnName = metaData.getColumnLabel(i);
Object value = resultSet.getObject(columnName);
//处理datetime类型字段和java.util.Data 转换问题
if (value.getClass().equals(LocalDateTime.class)) {
value = Timestamp.valueOf((LocalDateTime) value);
}
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);
field.set(obj, value);
}
list.add((T) obj);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != resultSet) {
try {
resultSet.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if (null != preparedStatement) {
try {
preparedStatement.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
JDBCUtil.releaseConnection();
}
return list;
}
//通用的增删改方法
public int baseUpdate(String sql, Object... args) {
//获取连接
Connection connection = JDBCUtil.getConnection();
PreparedStatement preparedStatement = null;
int rows = 0;
try {
//准备语句对象
preparedStatement = connection.prepareStatement(sql);
//设置语句上的参数
for (int i = 0; i < args.length; i++) {
preparedStatement.setObject(i + 1, args[i]);
}
//执行 增删改 executeUpdate
rows = preparedStatement.executeUpdate();
//释放资源(可选)
} catch (SQLException e) {
e.printStackTrace();
} finally {
if (null != preparedStatement) {
try {
preparedStatement.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
JDBCUtil.releaseConnection();
}
//返回的是影响数据库的记录数
return rows;
}
}
SysScheduleDao
import com.ryy.schedule.pojo.SysSchedule;
import java.util.List;
/**
* 介绍一下当前类或接口:。。。
* 作者:。。。
* 时间:。。。
*/
public interface SysScheduleDao {
/**
* 根据uid查看所有的日程
*/
List<SysSchedule> findItemListByUid(int uid);
/**
* 向数据库中添加一条空记录
* @param uid 根据用户的uid进行添加
*/
Integer addDefault(int uid);
/**
* 更新数据库中的日程
*/
Integer updateSchedule(SysSchedule schedule);
Integer removeSchedule(int sid);
}
SysUserDao
import com.ryy.schedule.pojo.SysUser;
/**
* 该接口用于定义针对表格的CRUD方法
* DAO层一般需要定义接口和实现类使得上层只需要知道方法是干什么的即可不需要知道方法的具体实现
*/
public interface SysUserDao {
/**
*向数据库中增加一条用户记录的方法
* @param sysUser 要增加的记录username和user_Pwd字段以SysUser实体类对象的形式接收
* @return 增加成功返回1 增加失败返回0
*/
int addSysUser(SysUser sysUser);
/**
* 根据用户名获得用户完整信息的方法
* @param username 要查询的用户名
* @return 如果找到了返回SysUser对象,找不到返回null
*/
SysUser findByUsername(String username);
}
SysScheduleDaoImpl
import com.ryy.schedule.dao.BaseDAO;
import com.ryy.schedule.dao.SysScheduleDao;
import com.ryy.schedule.pojo.SysSchedule;
import java.util.List;
public class SysScheduleDaoImpl extends BaseDAO implements SysScheduleDao {
@Override
public List<SysSchedule> findItemListByUid(int uid) {
String sql = "select sid,uid,title,completed from sys_schedule where uid=?";
List<SysSchedule> itemList = baseQUery(SysSchedule.class, sql, uid);
return itemList;
}
@Override
public Integer addDefault(int uid) {
String sql = "insert into sys_schedule values (DEFAULT,?,'请输入日程',0)";
return baseUpdate(sql, uid);
}
@Override
public Integer updateSchedule(SysSchedule schedule) {
String sql = "update sys_schedule set title=?, completed=? where sid=?";
return baseUpdate(sql, schedule.getTitle(), schedule.getCompleted(), schedule.getSid());
}
@Override
public Integer removeSchedule(int sid) {
String sql = "delete from sys_schedule where sid=?";
return baseUpdate(sql, sid);
}
}
SysUserDaoImpl
import com.ryy.schedule.dao.BaseDAO;
import com.ryy.schedule.dao.SysUserDao;
import com.ryy.schedule.pojo.SysUser;
import java.util.List;
public class SysUserDaoImpl extends BaseDAO implements SysUserDao {
@Override
public int addSysUser(SysUser sysUser) {
String sql = "insert into sys_user values(DEFAULT,?,?)";
return baseUpdate(sql,sysUser.getUsername(),sysUser.getUserPwd());
}
@Override
public SysUser findByUsername(String username) {
String sql = "select uid,username,user_pwd as userPwd from sys_user where username = ?";
List<SysUser> sysUserList = baseQUery(SysUser.class, sql, username);
return sysUserList != null && !sysUserList.isEmpty() ? sysUserList.getFirst() : null;
}
}
POJO层代码
此处不需要话大幅的篇章去写构造函数以及getter和setter方法,使用idea自带的lombok插件即可,jar包在上面已有截图
使用lombok帮助我们生成:getter,setter,全参构造,无参构造,equals,hashcode
使用步骤:
1 检查IDEA是否安装了lombok插件
2 检查是否勾选了 enable annotation processing
3 导入lombok的依赖
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@AllArgsConstructor
@NoArgsConstructor
@Data
public class SysSchedule implements Serializable {
private Integer sid;
private Integer uid;
private String title;
private Integer completed;
}
import lombok.*;
import java.io.Serializable;
import java.util.Objects;
/**
* 使用lombok帮助我们生成:getter,setter,全参构造,无参构造,equals,hashcode
* 使用步骤:
* 1 检查IDEA是否安装了lombok插件
* 2 检查是否勾选了 enable annotation processing
* 3 导入lombok的依赖
*
*/
@AllArgsConstructor //相当于添加了全参构造
@NoArgsConstructor //相当于添加了无参构造
@Data //getter,setter,equals,hashcode,toString
public class SysUser implements Serializable {
private Integer uid;
private String username;
private String userPwd;
}
Controller层代码
BaseController
service方法只需在这里实现,其他类对该层代码进行继承即可
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
@SuppressWarnings("all")
public class BaseController extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//判断此次请求是增?删?改?查? 在请求路径后面加上add,delete,update,find
String requestURI = req.getRequestURI(); //schedule/add
String[] split = requestURI.split("/");
String methodName = split[split.length-1];
//使用反射通过方法名获取下面的方法
Class aClass = this.getClass();
//获取方法
try {
Method declaredMethod = aClass.getDeclaredMethod(methodName, HttpServletRequest.class, HttpServletResponse.class);
//利用反射爆破破解方法的访问修饰符
declaredMethod.setAccessible(true);
//执行方法
declaredMethod.invoke(this, req, resp);
} catch (Exception e) {
e.printStackTrace();
}
}
}
SysScheduleController
import com.ryy.schedule.common.Result;
import com.ryy.schedule.pojo.SysSchedule;
import com.ryy.schedule.service.SysScheduleService;
import com.ryy.schedule.service.impl.SysScheduleServiceImpl;
import com.ryy.schedule.util.WebUtil;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 增加日程的请求 /schedule/add
* 查询日程的请求 /schedule/find
* 修改日程的请求 /schedule/update
* 删除日程的请求 /schedule/delete
* ... ...
*/
@WebServlet("/schedule/*")
public class SysScheduleController extends BaseController {
private SysScheduleService scheduleService = new SysScheduleServiceImpl();
protected void removeSchedule(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//接收请求中的sid参数
int sid = Integer.parseInt(req.getParameter("sid"));
//调用服务层方法 删除数据
scheduleService.removeSchedule(sid);
//响应成功信息
WebUtil.writeJson(resp, Result.ok(null));
}
protected void updateSchedule(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//接收请求体中的JSON串,转换为一个SysSchedule的对象
SysSchedule schedule = WebUtil.readJson(req, SysSchedule.class);
//调用服务层的方法,将信息更新进入数据库
scheduleService.updateSchedule(schedule);
WebUtil.writeJson(resp, Result.ok(null));
}
protected void addDefaultSchedule(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//接收请求中的uid参数
int uid = Integer.parseInt(req.getParameter("uid"));
//调用服务层方法,向数据库中增加一条空记录
scheduleService.addDefault(uid);
//响应给客户端
WebUtil.writeJson(resp, Result.ok(null));
}
protected void findAllSchedule(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//接收请求中的uid参数
int uid = Integer.parseInt(req.getParameter("uid"));
//查询用户的所有日程
List<SysSchedule> itemList = scheduleService.findItemListByUid(uid);
//将用户的所有日程放入一个Result对象
Map data = new HashMap();
data.put("itemList", itemList);
Result result = Result.ok(data);
//将Result对象转换为JSON相应给客户端
WebUtil.writeJson(resp, result);
}
}
SysUserController
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ryy.schedule.common.Result;
import com.ryy.schedule.common.ResultCodeEnum;
import com.ryy.schedule.pojo.SysUser;
import com.ryy.schedule.service.SysUserService;
import com.ryy.schedule.service.impl.SysUserServiceImpl;
import com.ryy.schedule.util.MD5Util;
import com.ryy.schedule.util.WebUtil;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@WebServlet("/user/*")
@SuppressWarnings("all")
public class SysUserController extends BaseController {
private SysUserService userService = new SysUserServiceImpl();
/**
* 注册时,接收要注册的用户名,校验用户名是否被占用的业务接口
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
protected void checkUsernameUsed(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//接收用户名
String username = req.getParameter("username");
//调用服务层业务处理方法查询该用户名是否有对应用户
SysUser sysUser = userService.findByUsername(username);
//如果有,响应:已占用
//如果没有,响应:可用
Result result = Result.ok(null);
if(null != sysUser){
result = Result.build(null, ResultCodeEnum.USERNAME_USED);
}
//将result对象转换为JSON串响应给客户端
WebUtil.writeJson(resp, result);
}
/**
* 接收用户登录请求,完成登录的业务接口
*
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
protected void login(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1,接收用户名和密码
// String username = req.getParameter("username");
// String userPwd = req.getParameter("userPwd");
SysUser sysUser = WebUtil.readJson(req, SysUser.class);
//2,调用服务层方法,根据用户名查询用户信息
SysUser loginUser = userService.findByUsername(sysUser.getUsername());
Result result = null;
if (null == loginUser) {
result = Result.build(null, ResultCodeEnum.USERNAME_ERROR);
}else if (!MD5Util.encrypt(sysUser.getUserPwd()).equals(loginUser.getUserPwd())) {
result = Result.build(null, ResultCodeEnum.PASSWORD_ERROR);
} else {
//登陆程序,将用户uid和username相应给客户端
Map data = new HashMap();
loginUser.setUserPwd(""); //将密码设置为空,这样后端相应给前端时就不会显示密码了
data.put("loginUser", loginUser);
result = Result.ok(data);
// //登陆成功之后,将登录的用户信息存入session域中,以便后期能从session域中获取用户信息
// HttpSession session = req.getSession();
// session.setAttribute("sysUser", loginUser);
// //4,跳转到首页
// resp.sendRedirect("/showSchedule.html");
}
//3 将登录结果相应给客户端
WebUtil.writeJson(resp, result);
}
/**
* 接收用户注册请求的业务处理方法(业务接口 不是java中的interface)
*
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
protected void regist(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1 接收客户端提交的参数(此时前端发送过来的是JSON串,不能再用getParameter的方法接收了)
//使用WebUtil中的读取JSON串的方法
SysUser registUser = WebUtil.readJson(req, SysUser.class);
//2 调用服务层方法,完成注册功能
//将参数放入一个SysUser对象中,在调用regist方法时传入
//SysUser sysUser = new SysUser(null, username, userPwd); 此时就不用再new SysUser对象了
int rows = userService.regist(registUser);
//3 根据注册结果(成功 失败)做页面跳转
Result result = Result.ok(null);
if(rows < 1) {
result = Result.build(null, ResultCodeEnum.USERNAME_USED); //设置响应状态码
}
WebUtil.writeJson(resp, result);
}
}
此处涉及一个很重要的知识:axios和原生ajax的区别
原生ajax:
//实例化一个xmlHttpRequest
var request = new xmlHttpRequest();
//设置xmlHttpRequest对象的回调函数
//request.readyState有1,2,3,4四个状态,4号状态就是接收到后端发送回来的信息
//request.status是响应状态码
request.onreadystatechange = function(){
if(request.readyState == 4 && request.status == 200){
alert("后端响应了")
}
}
//设置发送请求的方式和请求的资源路径
request.open("GET", "/hello? username=zhangsan")
request.send(); //发送请求
原生ajax的后端部分:
为解决响应信息格式不规范,处理方式不规范,后端响应回来的信息应当有一个统一的格式,前后端共同遵守
{"code":"", 业务状态码,本次请求的业务是否成功?若失败了,是什么原因失败了?
"message":"业务状态码的补充说明/描述"
"data":{} 本次响应的数据,诸如:成功/失败 List<Schedule>...}
具体步骤:
1,创建一个枚举类,里面放入一些自定义的响应码以及message信息解释
2,再创建一个类把"code","message","data"封装成一个result对象
3,导入Jackson的jar包,把result对象转换为JSON串相应给客户端
4,然后在JS代码部分接收JSON串:
var result = JSON.parse(request.responseText);
代码演示:
Result
/**
* {
* "code":"100/200/...",业务状态码,本次请求的业务是否成功?若失败了,是什么原因失败了?
* “message":"业务状态码的补充说明/描述"
* "data":{}本次相应的数据,诸如:成功/失败 List,Schedule>...
* }
*/
public class Result<T> {
private Integer code;
private String message;
private T data;
public Result(){}
//返回数据
protected static <T> Result<T> build(T data) {
Result<T> result = new Result<T>();
if(data != null)
result.setData(data);
return result;
}
//帮助快速构造result对象
public static<T> Result<T> build(T body, Integer code, String message) {
Result<T> result = build(body);
result.setCode(code);
result.setMessage(message);
return result;
}
//从枚举类中获取定义的响应状态码以及message给到result对象
public static<T> Result<T> build(T body, ResultCodeEnum resultCodeEnum) {
Result<T> result = build(body);
result.setCode(resultCodeEnum.getCode());
result.setMessage(resultCodeEnum.getMessage());
return result;
}
/**
* 操作成功
* @param data baseCategory1List
* @return
* @param <T>
*/
public static<T> Result<T> ok(T data) {
Result<T> result = build(data);
return build(data, ResultCodeEnum.SUCCESS);
}
public Result<T> message(String msg){
this.setMessage(msg);
return this;
}
public Result<T> code(Integer code){
this.setCode(code);
return this;
}
public Integer getCode() {
return code;
}
public void setCode(Integer 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;
}
}
ResultCodeEnum
/**
* 利用枚举类来列举各个响应状态码
*/
public enum ResultCodeEnum {
SUCCESS(200,"success"),
USERNAME_ERROR(501, "usernameError"),
PASSWORD_ERROR(503, "passwordError"),
NOTLOGIN(504, "notlogin"),
USERNAME_USED(505, "usernameUsed");
private Integer code;
private String message;
private ResultCodeEnum(Integer code, String message){
this.code = code;
this.message = message;
}
public Integer getCode() {
return code;
}
public String getMessage() {
return message;
}
}
使用axios技术
如果使用axois技术,我们在发送数据和接收数据时只需要使用await和async关键字即可
例如在注册页中:使用await向后端发送一个JSON串,然后接收时使用解构表达式获取Json串中的code响应状态码进行接下来的操作
//注册的方法
async function regist(){
//校验所有的输入框是否合法,不合法就不进行提交
let flag1 = await checkUsername() //此处如果不加await就会返回一个Promise对象,而不是true和false
let flag2 = await checkUserPwd()
let flag3 = await checkReUserPwd()
if(flag1 && flag2 && flag3){
let {data} = await request.post("user/regist", registUser) //此处是要看后端的哪一个路径下的方法是接收前端传过去的信息的,以JSON串形式发送数据
if(data.code == 200) {
//注册成功跳转登录页
alert("注册成功,快去登陆吧!")
router.push("/Login")
}else{
alert("抱歉,用户名被抢注了")
}
}else{
alert("校验不通过,请再次检查数据")
}
}
Service层代码
现阶段Service层主要只是起到一个桥梁的作用,承上controller层启下dao层
SysScheduleService
import com.ryy.schedule.pojo.SysSchedule;
import java.util.List;
public interface SysScheduleService {
List<SysSchedule> findItemListByUid(int uid);
Integer addDefault(int uid);
Integer updateSchedule(SysSchedule schedule);
Integer removeSchedule(int sid);
}
SysUserService
import com.ryy.schedule.pojo.SysUser;
/**
* 该接口定义了以sys_user表格为核心的业务处理功能
*/
public interface SysUserService {
/**
* 注册用户的方法
* @param sysUser 要注册的用户名和明文密码以SysUser对象的形式接收
* @return 注册成功返回1 注册失败返回0
*/
int regist(SysUser sysUser);
/**
* 根据用户名获得用户完整信息的方法
* @param username 要查询的用户名
* @return 如果找到了返回SysUser对象,找不到返回null
*/
SysUser findByUsername(String username);
}
SysScheduleServiceImpl
import com.ryy.schedule.dao.SysScheduleDao;
import com.ryy.schedule.dao.impl.SysScheduleDaoImpl;
import com.ryy.schedule.pojo.SysSchedule;
import com.ryy.schedule.service.SysScheduleService;
import java.util.List;
public class SysScheduleServiceImpl implements SysScheduleService {
private SysScheduleDao scheduleDao = new SysScheduleDaoImpl();
@Override
public List<SysSchedule> findItemListByUid(int uid) {
return scheduleDao.findItemListByUid(uid);
}
@Override
public Integer addDefault(int uid) {
return scheduleDao.addDefault(uid);
}
@Override
public Integer updateSchedule(SysSchedule schedule) {
return scheduleDao.updateSchedule(schedule);
}
@Override
public Integer removeSchedule(int sid) {
return scheduleDao.removeSchedule(sid);
}
}
SysUserServiceImpl
import com.ryy.schedule.dao.SysUserDao;
import com.ryy.schedule.dao.impl.SysUserDaoImpl;
import com.ryy.schedule.pojo.SysUser;
import com.ryy.schedule.service.SysUserService;
import com.ryy.schedule.util.MD5Util;
public class SysUserServiceImpl implements SysUserService {
private SysUserDao userDao = new SysUserDaoImpl();
@Override
public int regist(SysUser sysUser) {
//将用户的明文密码转换为密文密码
sysUser.setUserPwd(MD5Util.encrypt(sysUser.getUserPwd()));
//调用DAO层的方法 将SysUser信息存入数据库
return userDao.addSysUser(sysUser);
}
@Override
public SysUser findByUsername(String username) {
return userDao.findByUsername(username);
}
}
前端工程化简析
此处看到有很多工程文件
componets文件夹中就主要存放我们的页面工程
router文件夹中主要存放路由,即进行页面跳转的操作
store文件夹中主要存放pinia共享数据,使得各页面间能够实现数据的交互
utils中存放了相关axios技术,例如请求拦截器,响应拦截器等
App.vue:主要作用是进行全局显示,比如在这里我需要Header页面始终显示在其他三个页面的上方,以及各路由之间页面的自由切换,都需要在App.vue中进行配置
<script setup>
import Header from './components/Header.vue'
</script>
<template>
<div>
<Header></Header> <!--将头部始终显示在页面上-->
<hr>
<router-view></router-view> <!--显示接下来的路由切换页,即Login,Regist,ShowSchedule-->
</div>
</template>
<style scoped>
</style>
main.js中是用于进行全局配置的
例如全局配置路由router 和全局配置pinia
import { createApp } from 'vue'
import App from './App.vue'
import router from './router/router'
import pinia from './pinia.js'
const app = createApp(App)
app.use(pinia) //全局应用pinia
app.use(router)
app.mount('#app')
pinia.js的作用只是开启pinia的共享数据
//开启pinia
import {createPinia} from 'pinia'
let pinia = createPinia()
export default pinia
在前端部分,整体的思路就是为页面上布局的按钮进行函数事件的绑定,然后这些函数会与后端进行交互,最后返回响应的值,进行页面的改动或者利用路由进行页面的跳转
相应的前端代码我已经放在了文件中,大家可根据注释进行理解
最后要讲的就是过滤器Filter技术:
我们在登陆时会遇到浏览器同源禁止策略:即跨服务器请求数据了,当浏览器页面的端口号和后端服务器的端口号不一致时,浏览器会认为返回的数据不安全进而报错
解决方案:预检(每隔一段时间发送一次请求)
了护短会向服务端询问是否可以跨域,服务端会向客户端相应一些告知客户端可以跨域的信息,然后客户端才会正式向服务端发送请求
服务端的操作:创建一个跨域过滤器判断是否为预检请求
是:相应允许跨域的信息,请求到此为止
否:放行
import com.ryy.schedule.common.Result;
import com.ryy.schedule.util.WebUtil;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebFilter("/*")
public class CrosFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
System.out.println(request.getMethod());
HttpServletResponse response = (HttpServletResponse) servletResponse;
//跨域可以跨哪些域,*表示跨任何域
response.setHeader("Access-Control-Allow-Origin", "*");
//哪些方法允许跨域?
response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE, HEAD");
//预检请求的有效时间是多少?
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "access-control-allow-origin, authority," +
" content-type, version-info, X-Requested-With");
//如果是跨域请求,直接做出响应200
if(request.getMethod().equalsIgnoreCase("OPTIONS")){
WebUtil.writeJson(response, Result.ok(null));
}else{
//非预检请求,放行即可
filterChain.doFilter(servletRequest, servletResponse);
}
}
}