web开发——代码编写流程简述

        简介:本篇文章从前端向后端发送请求开始,到后端向前端做出响应的流程整个流程进行了一个简述,并对会出现的一些问题提供了解决办法。本篇文章中使用了vue框架,没有使用java框架。

       从VUE开始,在VUE中,我们使用axios发送请求,axios对发送请求的方式进行了封装,内部使用的还是原来的发送方式。在main.js文件中配置我们发送请求的目的IP地址、端口、项目名,在VUE文件中发送请求就只需要后端配置的某一服务的地址。请求方式有get和post两种请求,get请求可以发送少量数据,获得大量数据,将要发送的数据拼接到请求地址后,书写格式为"?变量名=值[&变量名=值...]";post请求方式不是把数据拼在地址后,而是作为第二个参数传入,发送的内容很多时,比如在教务管理系统中完成添加一个学生,一个学生的信息有多条,不可能全部都以键值对的形式拼接,这样实现起来不方便,而且容易出错。可以将一个学生作为一个js对象,将学生的信息填入对象中,这样我们在传参的时候就只需要将对象作为参数即可。

get请求发送举例:

// resp是接收到的响应
this.$http.get("api/SH?mark=getDorms&building=" + this.building).then(resp => {
    if (resp.data.code == 200) {
        // 业务逻辑代码
    }
})

post请求发送举例:

// this.form包含了用户在界面中输入的账号和密码,resp是接收到的响应
this.$http.post("Login", jsonToString(this.form)).then(resp => {
    // 根据相应的数据, 做出下一步动作
    // 201是作者自定义的账号或密码有误状态码
    if (resp.data.code == 201) {
        this.$message({
            message: resp.data.message,
            type: 'warning'
        })
    } else if (resp.data.code == 200) {
        this.$message({
            message: resp.data.message,
            type: 'success'
        })
        // 登录成功后, 将当前登陆的用户信息存储到浏览器sessionStorage对象中, 关闭浏览器后会销毁
        sessionStorage.setItem("id", resp.data.data.id)
        sessionStorage.setItem("account", resp.data.data.account)
        sessionStorage.setItem("name", resp.data.data.name)
        sessionStorage.setItem("gender", resp.data.data.gender)
        sessionStorage.setItem("token", resp.data.data.token)
        this.$router.push("/Main")
    }
})

       在发送一个请求后,后端并不会接收到我们所发的请求,三个原因:①前后端分别部署在不同的服务器上,存在跨域安全问题,跨域默认是一种不安全的操作;②前端向后端发送请求成功,但是存在js与java对象格式不匹配的问题;③解码格式不匹配。对以上三个问题的都需要一一处理:①跨域问题可以在前端处理,也可以在后端处理,需要将指定的跨域访问设置为信息的,允许前端向他请求的后端进行访问;②本片文章中提供一个简易的js对象格式转为java对象格式,这只是一个临时的处理方法,在深入学习前端后会有其他更好的处理方式。初学的读者可以先用该方法转换格式;③在后端设置解码的格式。

js对象格式转换为java对象格式:

// 问题2解决办法,前端方法
// 将json对象序列化为键=值&键=值
function jsonToString(jsonobj) {
    // console.log(jsonobj)
    var str = "";
    for (var s in jsonobj) {
        str += s + "=" + jsonobj[s] + "&";
    }
    return str.substring(0, str.length - 1);
}

       上一段中我们提到了一些问题,也讲述了解决办法,现在又有一个新的问题,那就是这些配置代码应该写在哪,怎么写。首先我们要知道在实际项目中,一个项目所提供的服务应该有很多个,所以我们不可能在每一个服务里都编写一次处理跨域问题、字符集设置的代码,那样会使得我们的代码显得很臃肿,其次他也不符合java面向对象的思想。后端接受请求的两个很重要的部分:Servlet服务和Filter过滤器。其中Servlet服务中编写编写我们要执行的业务逻辑代码;Filter过滤器中完成对指定的Servlet服务过滤功能,满足条件时才会执行Servlet服务,其中Filter在配置时可以用"/*"表示为所有的Servlet添加此过滤器。

跨域问题处理,后端方法:

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

// 解决跨域问题的过滤器,后端方法
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

        //过滤链,让程序向下执行,下一个可能是过滤器也可能是servlet
        filterChain.doFilter(servletRequest, servletResponse);
    }
}

字符集设置,后端方法:

import javax.servlet.*;
import java.io.IOException;

public class EncodingFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("启动服务器时加载, 过滤器初始化");
    }

    // 过滤操作
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // 接收编码
        servletRequest.setCharacterEncoding("utf-8");
        // 响应
        servletResponse.setContentType("text/html;charset=utf-8");
        //过滤链,让程序向下执行,下一个可能是过滤器也可能是servlet
        filterChain.doFilter(servletRequest,servletResponse);
    }
}

        Servlet服务和Filter过滤器都有两种配置方式,注解配置web.xml文件中配置。注解配置时Servlet服务使用"@WebServlet",Filter过滤器使用"@WebFilter"。注解配置方式配置过滤器时,一定要注意过滤器的类名,当有多个过滤器时,如果都是用注解方式配置,系统容默认会按照字典序来调用过滤器,所以在使用注解方式配置过滤器时一定要注意首先应该执行拿一些过滤器,如解决跨域问题的过滤器。当然在配置过滤器时我们可能会觉得考虑字典序很麻烦,那么就应该在web.xml文件中进行配置。在web.xml文件中的过滤器将会自上而下的配置顺序依次执行,而且一定优先于注解方式配置的过滤器,所以我们可以把一些必须先行执行的过滤器按照一定的顺序配置到web.xml文件中。在web.xml中我们还可以定义数据,在过滤器中可以获取到这些数据,比如在web.xml文件中定义字符编码格式,在过滤器中就可以动态获取编码格式完成字符集设置。
    

在web.xml中配置Servlet和Filter:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!--  配置Servlet服务  -->
    <servlet>
        <servlet-name>Login</servlet-name>
        <servlet-class>com.flash.dormserver.servlet.LoginServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>Login</servlet-name>
        <url-pattern>/Login</url-pattern>
    </servlet-mapping>

    <!--  配置Filter过滤器  -->
    <filter>
        <filter-name>Cors</filter-name>
        <filter-class>com.flash.dormserver.filter.CorsFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>Cors</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <filter>
        <filter-name>Encoding</filter-name>
        <filter-class>com.flash.dormserver.filter.EncodingFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>Encoding</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

       在Servlet服务中执行玩相应的业务逻辑后,需要向前端做出响应,响应的格式也应该有一个统一的规定,这样前端对于数据的处理也更加的方便。一个响应应包含状态码code(如:200表示正常,500表示后端异常)、响应数据data(没有数据响应,可以为null)、响应说明message。其中状态码由公司规定,所以状态码并不固定。刚才提到前端与后端的对象格式,编码格式不同,以java的格式向前端做出响应也存在同样的问题,所以我们同样也需要规定编码格式和对响应的数据格式格式转换,响应编码格式设置与接收请求编码格式放在同一个过滤器中,对象格式转换,通过maven下载json使用。

maven中配置地址—java对象格式转js对象格式:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.14.2</version>
</dependency>

servlet服务代码举例:

import com.fasterxml.jackson.databind.ObjectMapper;
import com.flash.dormserver.model.Admin;
import com.flash.dormserver.model.Result;
import com.flash.dormserver.dao.AdminManagerDao;
import com.flash.dormserver.util.JWTUtil;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.SQLException;

// "/Login"为此服务配置的地址
@WebServlet(urlPatterns = "/Login")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // post接受到汉字不能正确处理,是一堆乱码,可以给定字符集来处理该问题
        System.out.println("doPost");
        String account = req.getParameter("account");
        String password = req.getParameter("password");
//        System.out.println(account);
//        System.out.println(password);

        // 调用其他的java程序处理  例如调用dao层 与数据库交互
        AdminManagerDao loginDao = new AdminManagerDao();
        Admin admin;
        Result result;
        // 响应
        PrintWriter writer = resp.getWriter();
        try {
            admin = loginDao.loginMessage(account, password);
            if (admin != null) {
                admin.setToken(JWTUtil.getToken(admin));
                result = new Result(200, "登录成功!", admin);
            } else {
                result = new Result(201, "账号或密码错误!");
            }
            // new ObjectMapper().writeValueAsString(result)将result对象转换为前端可识别的对象,会调用我们所写的类的get方法
            writer.write(new ObjectMapper().writeValueAsString(result));
        } catch (SQLException | ClassNotFoundException throwables) {
            throwables.printStackTrace();
            result = new Result(500, "未知异常!");
            writer.write(new ObjectMapper().writeValueAsString(result));
        }
    }
}

    前端接收到消息后通过状态码对响应做出一个判断,正常(code=200)则完成前端对应的业务逻辑代码,后端异常(code=500)也应该向用户提示错误信息。对于前端来说需要像后端发送各式各样的请求,每次接到响应后都需要对状态进行判断,正确则执行不同的业务逻辑代码,如果是500异常,执行的业务逻辑代码可能完全相同,我们不可能在每一次接收到响应后都对状态码为500时写一模一样的报错代码。前端也有前端的相应的处理方法,类似于后端的过滤器,前端有他自己的拦截器,对响应进行拦截的方法成为响应拦截器,编写在main.js中,在响应拦截器中可以对状态码500做出统一处理。

前端响应拦截器:

// 添加响应拦截器,在main.js中完成配置
axios.interceptors.response.use((resp) => { //正常响应拦截
    if (resp.data.code == 500) {
        ElementUI.Message({
            message: resp.data.message,
            type: "error"
        })
        return
    }    
    if (resp.data.code == 401) {
        ElementUI.Message({
            message: resp.data.message,
            type: "warning"
        })
        router.replace("/Login")
        return
    }
    return resp
})

       接下来是安全处理有关的部分,对于很多网站来说,如果想使用网站的应用,我们都应该先完成登录功能,登录请求发送后,后端与数据库交互,验证成功后做出可以登陆的响应,包含数据有:[用户的个人信息(非保密的),还有一个令牌(token)]令牌作为该用户执行后续操作的通行证。token的生成方式我们也不用操心。通过maven导入JWT包,编写对应的类生成token,token内可以隐含一些标识用户的信息,例如用户的id和账号account,还可以规定令牌有效时间。

maven中配置地址—token:

<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.8.2</version>
</dependency>
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.flash.dormserver.model.Admin;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * 功能描述:JWT工具类
 */
public class JWTUtil {

    /**
     * 根据用户id和账号account,账号生成token
     *
     * @param admin
     * @return
     */
    public static String getToken(Admin admin) {
        String token = "";
        try {
            //过期时间 为1970.1.1 0:0:0 至 过期时间  当前的毫秒值 + 有效时间
            Date expireDate = new Date(new Date().getTime() + 2 * 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())// 携带用户id
                    .withClaim("account", admin.getAccount())// 携带用户账号account
                    .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);
    }
}

验证用户是否登录的过滤器:

import com.fasterxml.jackson.databind.ObjectMapper;
import com.flash.dormserver.model.Result;
import com.flash.dormserver.util.JWTUtil;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.PrintWriter;

// 注解方式配置过滤器
@WebFilter(urlPatterns = "/api/*")
public class AdminTokenFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // 获取到token
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String token = request.getHeader("token");
        // 验证token
        boolean verify = JWTUtil.verify(token);
        if (verify) {
            filterChain.doFilter(servletRequest, servletResponse);
        } else {
            // 不能让其继续向后执行, 向前端响应, 认证失败
            PrintWriter writer = servletResponse.getWriter();
            ObjectMapper objectMapper = new ObjectMapper();
            Result result = new Result(401, "身份验证失败, 请重新登录");
            String json = objectMapper.writeValueAsString(result);
            writer.print(json);
        }
    }
}

       在用户登陆后执行操作时每次向后端发送token,后端对token验证,如果验证通过则执行相应的业务逻辑,反之则不能让用户执行该操作,响应身份验证失败,提示用户重新登陆。用户登陆后,前端每次发送请求都需要携带token来对用户身份进行验证,我们不可能在每一个发送的请求后面都拼接一个token令牌,很麻烦,解决办法就是我们可以将其写入请求拦截器中做统一处理。后端将验证身份的代码编写到过滤器中,配置身份验证信息过滤器时,注意不能包含登录功能,否则会连登录请求一并拦截,因为未登录的用户一定没有token令牌。

前端请求拦截器:

//axios 添加请求拦截, 前端每向后端发送一次请求都会调用一次这个函数
axios.interceptors.request.use(config => {
    //为请求头对象,添加 Token 验证的 token 字段
    config.headers.token = sessionStorage.getItem('token')
    return config
})


       再回到前端,如果用户在未登录的情况下直接进入到教务管理系统(提前知道教务管理系统的网站,通过网站直接跳转到内容界面)中很显然也是不合理的,解决办法就是添加路由到行守卫,在每一次进行路由时,对token进行验证,没有登陆自然(即后端没有向前端发送token)。如果是未登录状态,则跳转至登录界面。

路由导航守卫:

// 路由导航守卫, 每当发生路由时, 都会自动调用此方法
// to:要访问的组件地址, from:从哪一个组建发起的路由, next:放行
rout.beforeEach((to, from, next) => {
    if (to.path == '/Login') {
        // 如果用户访问的登录页, 直接放行
        return next();
    } else {
        // 从浏览器中获取到信息, 判断是否已经登陆
        var token = window.sessionStorage.getItem("token");
        if (token == null) {
            // 走这里说明未登录直接跳转到Login界面
            return next("/Login");
        } else {
            // 走这里说明已经登陆
            next();
        }
    }
})

  • 28
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Black—slience

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

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

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

打赏作者

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

抵扣说明:

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

余额充值