管理系统总结(前端:Vue-cli, 后端Jdbc连接mysql数据库,项目部署tomcat里)

根据所学的知识, 写一个管理系统, 顺便总结一些知识点

准备:

前端用vue-cli的框架, 后端用jdbc连接数据库, 项目部署tomcat服务器来完成交互

●前端的vue-cli框架搭建可以看 点击跳转 的第二小结
●后端的tomcat在idea里的相关的配置与集成,可以看 点击跳跃

一、 前段搭建

在完成点击跳转Vue-cli 项目的搭建之后, 咱去组件官网直接 顺组件 (复制组件), 这里我用的是Element-ui官网的组件,官网点击右边: 点击完成跳转

1. 登录组件

在这里插入图片描述

2. 管理页面

在这里插入图片描述

3.异步请求发送后端, 验证账号

首先使用xios来发送, 因为原生态的ajax代码繁琐
<1>首先要引入axios.js文件或者安装axios的脚手架

$ npm install axios
这里就引入xios.js的组件来使用axios发送请求

<2>下载完成后, 在node_moudules里导入到main.js的全局配置文件里面
在这里插入图片描述

<3>导入后就能直接用axios语法来发送请求
发送get请求

				this.$http.get("http://localhost:8080/webBack_5.14/login?account=jwq&password=111").then(function(resp){
					//业务逻辑代码
				})

发送post请求

				this.$http.post("http://localhost:8080/webBack_5.14/login",this.account).then((resp){
				//业务逻辑代码
				})

4.异步请求发送后端 数据转化json

由于json是一种轻量级的数据格式, 所以前后端传输数据时候, 经常用json格式来传输

在前端可以写一个方法来转化数据格式:

在这里插入图片描述

请求里做参数转json格式,直接发送json的请求
在这里插入图片描述

5.防止通过浏览器地址恶意访问

如果仅仅是通过登录框验证密码还是不够安全, 还可以通过URL地址来直接进行访问, 就会跳过登录的页面

如下:
在这里插入图片描述

直接输入了网站URL的地址, 就直接访问进来了, 根本没有通过账户密码的验证

所以要将一个验证信息放到浏览器里—>(sessionStorage)

sessionStorage

他是浏览器里可以存储数据的容器, 在关闭浏览器时就会清除

因此将用户信息存储到sessionStorage(会话储存) 里 , 在进入具体操作的网页网页时候可以先进行判断, 如果有会话存储里有账号, 那么就可以进行访问, 不然那就回到登录的页面, 这样验证就更加符合需求,

在这里插入图片描述

而且此时的sessionStorage还可以将登录人的信息, 账号放到页面上显示, 就好像京东这些购物软件, 将用户人的登录信息展示到我的这一页面,就可以将操作人的信息放到sessionStorage里面, 随后在哪个页面想要获取渲染在前端时, 就可以随时获取, 非常方便

如下:
在这里插入图片描述
在这里插入图片描述

具体操作

在登录成功后, 将account的数据放入到sessionStorage里面,
在之后每一次请求处理访问登录的请求外的其他请求, 都要验证account

<1>放入account
在这里插入图片描述
在这里插入图片描述
<2>添加web会话跟踪技术
这是在router对象的那个index.js文件里配置
在这里插入图片描述
这样每一次请求的跳转就有了条件, 经过验证, 满足条件才会跳转, 让浏览器的安全性有了基础保障
在这里插入图片描述

提高安全性(session不安全), 使用token来验证

session缺点

  1. 浏览器里的数据时可以随时改的, 就会让一些不法分子走漏洞, 所以在前端验证并不是万全之策
  2. 用户认证之后,服务端做认证记录,如果认证的记录被保存在内存
    中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。
    因此要引入另一种安全的高效的验证操作, token 验证方式

不了解token的可以看这里 点击跳转

具体操作

首先在登录成功之后, 要在后端生成token, 再将token连随响应数据一并发送给前端, 前端得到token之后, 在接下来的每一次发送请求都会将token携带到请求里, 到后端来验证token是否正确, 正确就继续执行操作, 不正确就要返回 token不对的异常信息

生成token:
首先要导入jar包
在这里插入图片描述
由JWT的方式生成token
想了解JWT的可以 点击去看看JWT

JWT的工具类

public class JWTUtil {

    /**
     * jwt 生成 token
     */
    public static String token (Admin admin){
        String token = "";
        try {
             //过期时间 为 1970.1.1 0:0:0 至 过期时间 当前的毫秒值 + 有效时间
            Date expireDate = new Date(new Date().getTime() +3600*1000);
             //秘钥及加密算法
            Algorithm algorithm = Algorithm.HMAC256("ZCEQIUBFKSJBFJH2020BQWE");
           //设置头部信息
            Map<String,Object> header = new HashMap<>();
            header.put("typ","JWT");
            header.put("alg","HS256");
            //携带 id,账号信息,生成签名
            token = JWT.create()
                    .withHeader(header)
                    .withClaim("id",admin.getId())
                    .withClaim("account",admin.getAccount())
                    .withExpiresAt(expireDate)
                    .sign(algorithm);
        }catch (Exception e){
            e.printStackTrace(); return null;
        }
        return token;
    }

    /**
     * 验证 token 的方法
     * @param token
     * @return
     */
    public static boolean verify(String token){
        try {
         //验签
            Algorithm algorithm = Algorithm.HMAC256("ZCEQIUBFKSJBFJH2020BQWE");
            JWTVerifier verifier = JWT.require(algorithm).build();
            DecodedJWT jwt = verifier.verify(token);
            return true;
        } catch (Exception e) {//当传过来的 token 如果有问题,抛出异常
            return false;
        }
    }

    /**
     * 获得 token 中 playload 部分数据,按需使用
     * @param token
     * @return
     */
    public static DecodedJWT getTokenInfo(String token){
        return JWT.require(Algorithm.HMAC256("ZCEQIUBFKSJBFJH2020BQWE")).build().verify(token);
    }
}

将token信息封装到admin对象里, 一并返回前端
在这里插入图片描述
前端将信息放入浏览器的会话存储里
在这里插入图片描述
请求发送时,后端验证token

先想想, 后端在接到前端请求时, 要验证token,可以写在一个过滤器里面, 除了登录请求外都要走这个过滤器, 那么问题来了, 如何使得这个过滤器除了登录请求外都走这个过滤器呢?
<1>过滤器的配置

我们可以通过给走这个过滤器的请求添加一层级的路径, 具体如下图
在这里插入图片描述
在这里插入图片描述

这样就可以区分经过验证token过滤器与不验证token过滤器的请求了, 不验证就直接写请求地址, 验证就加上一层级的/token

6.添加请求拦截器

由于我们的每一次请求都要将token信息发送给后端, 索性就放在响请求对象里的请求头的区域
那我们是不是每次请求都要设置请求头啊, 麻烦不麻烦
后端有没有将每次请求拦截下来的东西, 再设置请求头的内容, 你还别说还真有, 响应拦截器
这是

这是在每次请求时,将token放在请求的请求头区域里, 到后端浏览器调用
在这里插入图片描述

7. 响应拦截器

在前端处理后端响应回来的状态码时, 每一个组件对相同部分状态码总是同一个业务流程, 如: 500 状态码时候, 就会弹出服务器异常的信息, 那这样会繁琐很多, 使得代码冗余

在每次接收响应时, 可以根据状态码来做统一给用户做出响应结果, 可以将那些状态码放入响应拦截器里

在这里插入图片描述

8.Main组件添加子组件,来展示学生

  1. 首先定义组件

在Main组件点击学生管理, 就会到达studentList组件
在这里插入图片描述

List组件的生命周期函数mounted(页面加载完成调用), 写一个接收后端响应数据到Vue对象里的studentList的方法
在这里插入图片描述

  1. 注册组件

在router对象性里注册组件, 由于此时的组件是在Main组件里展示, 所以就要在Main组件下定义子组件

在这里插入图片描述

  1. 在Main组件(父组件)添加来展示不同组件

在这里插入图片描述

时间转化问题

由于后端从数据库从数据库接收到学生的注册信息, 会默认将数据库的Data类型转化long类型, 在渲染到前端, 就会是时间戳的格式, 如下图:
数据库里时间:
在这里插入图片描述
渲染到前端:
在这里插入图片描述

所以要格式化student类里oper_time(学生注册时间)的属性, 可以添加注解@JsonFornmat(),
这样在转json时候就会格式化, 响应到前端就会是 年月日 时分秒的格式
在这里插入图片描述

9.引入添加学生的功能

首先,在element-ui里引入功能的组件样式, 然后给组件添加事件, 事件就是打开一个类似于登录的表单, 点击保存之后, 向后端发送一个请求, 通过调取控制层,业务层,数据层将数据保存到数据库里

保存学生的表单,在选择专业时候, 专业不能是固定的, 不然以后更改专业还得改代码, 代码灵活度太低, 所以专业数据就要从数据库获取

点击调取openAddDiolog()方法, 将新增学生的弹窗设置可见(就展示出来)
在这里插入图片描述

导入组件

因为一个组件里写太多东西, 不方便改动, 那么就可以通过导入组件的方式将新增学生的内容写在另一个.vue组件里, 之后通过导入就可以了
Add组件:
在这里插入图片描述
dialogFormVisible:这是对话框的属性, 表示的是一个值, 这个值如果是true表示对话框可见, 如果false那就表示对话框是不可见, 可以通过调用之前的openAddDiolog()的方法来给他赋值

在List组件里定义Add组件在这里插入图片描述

点击新增就会调用openAddDiolog方法, 将新增学生弹窗改成可见在这里插入图片描述

解决增加学生表单里的专业问题

在弹窗里的内容加载完成之后, 就调用生命周期函数, 将数据库里的专业信息展示到表单里
在这里插入图片描述

在这里插入图片描述

二、后端搭建

后端使用的是idea写代码工具, 集成了tomcat服务器

1. 项目构建

后端结构: 如下
在这里插入图片描述

2. 封装连接数据库的代码

由于要有许多的增删改查, 与数据库相关的操作, 所以就封装一下, 以后跟方便用
<1>首先先在src目录下新建一个. properties的文件
在这里插入图片描述
<2>新建一个工具类
工具类将加载驱动, 获取Connection连接对象, 释放资源一些操作封装成静态方法, 用的时候直接拿

就叫做JdbcUtil的类

public class JdbcUtil {

    private static String dirver = null;
    private static String url = null;
    private static String username = null;
    private  static String password = null;

    //获取数据库的外部文件
    //这里放在静态代码块里, 在第一次new对象时候, 就需要加载驱动
    static {
        try {
        //通过字节输入流来读取对应的properties文件
        InputStream in = JdbcUtil.class.getClassLoader().getResourceAsStream("db.properties");
        //再将读取到的文件放入到jdk里的properties对象里
        Properties pop = new Properties();
        //来获取外部的db.properties数据
        pop.load(in);

        //对应属性赋对象方法
        dirver = pop.getProperty("driver");
        url = pop.getProperty("url");
        username = pop.getProperty("username");
        password = pop.getProperty("password");

        //加载驱动
            Class.forName(dirver);
        } catch (IOException | ClassNotFoundException e) {
            System.out.println("java获取Connection对象错误");
            e.printStackTrace();
        }
    }
    //获取连接对象
    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(url, username, password);
    }
    //释放连接对象的资源
    public void relese(Connection con, Statement sta, ResultSet res){
        if (res!=null){
            try {
                res.close();
            } catch (SQLException throwables) {
                System.out.println("sql接收数据集合ResultSet关闭异常");
                throwables.printStackTrace();
            }
        }
        if (sta!=null){
            try {
                sta.close();
            } catch (SQLException throwables) {
                System.out.println("sql接收数据集合ResultSet关闭异常");

                throwables.printStackTrace();
            }
        }
        if (con!=null){
            try {
                res.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
}

3. 登录判断账号

<1>与数据库交互的Dao层代码展示


LoginDao

public class LoginDao {
    public Admin checkAccount(String account,String passWord) throws SQLException {
        Connection connection = null;
        PreparedStatement ps =null;
        ResultSet resultSet = null;
        Admin admin = null;
        JdbcUtil jdbcUtil = new JdbcUtil();

		//直接通过类调取方法来获取连接对象
        connection = jdbcUtil.getConnection();
    	//  ps = connection.prepareStatement("select account password from admin where account="+"\'"+account+"\'");
        ps = connection.prepareStatement("select id, account from admin where account=? and password=?");
        ps.setObject(1, account);
        ps.setObject(2, passWord);
        resultSet = ps.executeQuery();

        //如果有结果, 说明有该账号
        while(resultSet.next()){
            admin = new Admin();
            admin.setId(resultSet.getInt("id"));
            admin.setAccount(resultSet.getString("account"));
            break;
        }
        //释放连接数据库资源
        jdbcUtil.relese(connection,ps,resultSet);

        return admin;
    }
}

在这里插入图片描述

这三个地方需要注意一下
<1>第一部分 在判断登录这个Dao里, 此方法不能仅仅判断账号密码是否正确, 还要获取返回的那个admin对象

在实际项目中, 难道后天不需要账户信息吗?
就拿淘宝例子来说把, 是不是当你买个东西之后,再次登录时候就给你推类似的一些东西, 这里就是后天已经记录了你买的东西, 所以登录时候就知道给你推相似的商品了

<2>不能直接将用户的账号注入到SQL里查找

 ps = connection.prepareStatement("select account password from admin where account="+"\'"+account+"\'");

这一行代码, 可能会让一些 偷奸耍滑 的人钻空子, 你想想, 直接获取到的账号信息放入sql语句查找,

如果有一个用户输入account: 1==1, 那你此处sql的where条件不就形同虚设了嘛, 随便进入管理系统, 那你就等着被炒鱿鱼把

所以要用格式占位控制符来代替

        ps = connection.prepareStatement("select id, account from admin where account=? and password=?");
        ps.setObject(1, account);
        ps.setObject(2, passWord);

这不就肥肠完美么


<2>业务处理层LoginService

public class LoginServlet extends HttpServlet {

    @Override
    public void service(ServletRequest req, ServletResponse resp) throws ServletException, IOException {
        String account = req.getParameter("account");
        String password = req.getParameter("password");
        CommonRes commonRes = null;
        try{
            LoginDao loginDao = new LoginDao();
            Admin admin = loginDao.checkAccount(account, password);
            System.out.println(admin);
            if (admin!=null){
                //生成token
                String token = JWTUtil.token(admin);
                admin.setToken(token);
                commonRes = new CommonRes(200,admin,"登录成功");
            }else{
                commonRes = new CommonRes(201,"账号或密码错误");
            }
        }catch(Exception e){
            e.printStackTrace();
            commonRes = new CommonRes(500,"服务器异常");
        }
        //将common结果转化成jason格式返回
        resp.setContentType("text/html;charset=UTF-8");
        Writer writer = resp.getWriter();
        ObjectMapper mapper = new ObjectMapper();
        String jsonstr = mapper.writeValueAsString(commonRes);
        writer.write(jsonstr);
    }

代码解读
在这里插入图片描述
由于前端用的是axios的请求发送框架, 所以数据格式在经过服务器内部的一系列处理之后, 就会变成json的键值对格式,
所以后端可以直接 用 req.getParameter(“account”);获取对应参数值

        String account = req.getParameter("account");
        String password = req.getParameter("password");

3. 解决前后端编码不一致的问题问题

在前端向后端发送带参数的请求, 后端获取时, 浏览器默认发送的是ISO8859-1格式的编码 , 而这种编码格式是不能解析中文的, 所以就有乱码的出现
也就是你在request,getParameter(“参数名”)时候, 会是乱码

所以在获取参数之前,要对请求 设置编码
request.setContetnType("text/html;charset=utf-8");, 在请求头设置编码
同理, 后端服务器给前端做出响应的时候也应该设置响应编码, 否则会出现前端接收响应时, 响应的是乱码问题
response.setContetnType("text/html;charset=utf-8")

由于每次接收请求与响应数据都要设置编码格式, 所以就可以将上面两行代码放到过滤器里面

5. 解决跨域问题

跨域是指从一个域名的网页去请求另一个域名的资源。比如从www.baidu.com 页面去请求 www.google.com 的资源。但是一般情况下不能这么做,它是由浏览器的同源策略造成

所以可以告诉浏览器可以访问, 可以在响应头里设置

我从后端来解决这个问题

由此我们可以写过一个过滤器来实现

public class CorsFilter implements Filter {
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
            throws IOException, ServletException {
        HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;
        HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
        //允许携带Cookie时不能设置为* 否则前端报错
        httpResponse.setHeader("Access-Control-Allow-Origin", httpRequest.getHeader("origin"));//允许所有请求跨域
        httpResponse.setHeader("Access-Control-Allow-Methods", "*");//允许跨域的请求方法GET, POST, HEAD 等
        httpResponse.setHeader("Access-Control-Allow-Headers", "*");//允许跨域的请求头
        httpResponse.setHeader("Access-Control-Allow-Credentials", "true");//是否携带cookie

        filterChain.doFilter(servletRequest, servletResponse);
    }
}

并且在web.xml里配置哪些业务经过 过滤器

    <filter>
    <filter-name>corsFilter</filter-name>
    <filter-class>com.jwq.webback.filter.CorsFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>corsFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

6. 后端对返回结果进行封装

由于每次对后端结果进行返回时候,只能一个一个返回, 所以此时可以封装一个返回的结果集

commonRes

public class CommonRes {
    private int code;
    private Object data;
    private String message;

    public CommonRes(int code, Object data, String message) {
        this.code = code;
        this.data = data;
        this.message = message;
    }

    public CommonRes(int code, String message) {
        this.code = code;
        this.message = message;
    }

    //在转化json格式要通过反射机制用set, get方法来取值
    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

代码解析
在这里插入图片描述
注意: 这里必须是有set,get方法, 因为在后面的json返回格式, 必须要通过反射机制转化json格式

7. 后端对返回结果进行转化json格式后进行响应

<1>首先要导入jar包
在这里插入图片描述
<2>在使用mapper对象里的writeValuesAsString()对响应结果进行json格式的处理

        Writer writer = resp.getWriter();
        ObjectMapper mapper = new ObjectMapper();
        //commonRes这是上面说的返回结果集对象,放入mapper对象的
        //方法里,转json格式
        String jsonstr = mapper.writeValueAsString(commonRes);
        writer.write(jsonstr);

<3>json响应结果查看

在这里插入图片描述
注意: 这里的message响应的是中文, 所以要设置响应头编码

可以设置一个过滤器, 将请求编码与响应编码一起设置了

public class EncodFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        servletRequest.setCharacterEncoding("utf-8");
        HttpServletResponse response = (HttpServletResponse)servletResponse;
        response.setContentType("text/html;charset=UTF-8");
        //让进入到过滤器里的请求, 离开当前的过滤器, 继续向后执行, 后一个可能是过滤器,也可以是servlet
        filterChain.doFilter(servletRequest,servletResponse);
    }
}


8. List组件展示学生操作的处理代码(Contral层, service层, dao层)

由于学生相关的操作用的是一个service处理层, 在相同的get或者post请求下, 后端就要区分不同请求了,

可以在前端发送请求时候给一个标记, mark
在这里插入图片描述

后端获取参数进行, 进行判断
在这里插入图片描述


展示学生的service业务层(暂时Contral与service层不分离)

    /**
     * 展示学生的方法
     * @return
     * @throws SQLException
     */
    public List outStudent() throws SQLException {
        StudentDao studentDao = new StudentDao();
        List<Student> students = studentDao.outStudent();
        return students;
    }

dao层

   /**
     * 查询所有学生的方法
     * @return
     * @throws SQLException
     */
    public List<Student> outStudent() throws SQLException {
        List<Student> majors= new ArrayList<>();
        JdbcUtil jdbcUtil = new JdbcUtil();
        Connection connection = jdbcUtil.getConnection();
        PreparedStatement ps = connection.prepareStatement("SELECT\n" +
                "s.`num`,\n" +
                "s.name,\n" +
                "s.gender,\n" +
                "s.address,\n" +
                "s.phone,\n" +
                "m.name mname,\n" +
                "a.account aname,\n" +
                "s.oper_time FROM student s\n" +
                "LEFT JOIN major m\n" +
                "  ON s.majorid = m.id\n" +
                "LEFT JOIN admin a\n" +
                "  ON s.adminid = a.id");
        ResultSet resultSet = ps.executeQuery();

        while(resultSet.next()){
            Student major = new Student();
            major.setNum(resultSet.getInt("num"));
            major.setName(resultSet.getString("name"));
            major.setGender(resultSet.getString("gender"));
            major.setAddress(resultSet.getString("address"));
            major.setPhone(resultSet.getString("phone"));
            major.setMname(resultSet.getString("mname"));
            major.setAname(resultSet.getString("aname"));
            major.setOperTime(resultSet.getTimestamp("oper_time"));//年月日时分秒
            majors.add(major);
        }
        //释放资源
        jdbcUtil.relese(connection,ps,resultSet);
        return majors;
    }

9. 新增学生操作的处理代码(Contral层, service层, dao层)

10. 发送数据库的专业的代码

响应专业的业务层

    /**
     * 展示专业的方法
     * @return
     * @throws SQLException
     */
    public List outMajor() throws SQLException {
        StudentDao studentDao = new StudentDao();
        List<Major> majors = studentDao.outMajor();
        System.out.println(majors);
        return majors;
    }

dao层

    /**
     * 返回学生专业的方法
     * @return
     * @throws SQLException
     */
    public List<Major> outMajor() throws SQLException {
        List<Major> majors= new ArrayList<>();
        JdbcUtil jdbcUtil = new JdbcUtil();
        Connection connection = jdbcUtil.getConnection();
        PreparedStatement ps = connection.prepareStatement("select id, name from major");
        ResultSet resultSet = ps.executeQuery();

        while(resultSet.next()){
            Major major = new Major();
            major.setNum(resultSet.getString("id"));
            major.setName(resultSet.getString("name"));
            majors.add(major);
        }

        //释放资源
        jdbcUtil.relese(connection,ps,resultSet);
        return majors;
    }

在这里插入图片描述

  • 7
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lion Coder

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值