Projeto Espantalho - (dia04)

índice

Projeto Espantalho

8. Tratamento de exceção unificado na estrutura SpringMVC

Com relação ao tratamento unificado de exceções:

9. Solicitar verificação de parâmetro

10. Registre-se para o teste de página inicial

11. Concluindo o trabalho após o registro bem-sucedido

12. Resumo do estágio


Projeto Espantalho

 

8. Tratamento de exceção unificado na estrutura SpringMVC

Um mecanismo de tratamento de exceção unificado é fornecido na estrutura SpringMVC (claro, ele também pode ser usado diretamente na estrutura SpringBoot), de modo que cada exceção precisa ser tratada apenas uma vez, mesmo se uma certa exceção ocorrer em várias solicitações, não há necessidade para lidar com isso repetidamente! O núcleo é que quando o desenvolvedor chama um método que pode lançar uma exceção, no controlador, a exceção é lançada diretamente novamente, então SpringMVC irá capturar o objeto de exceção correspondente ao chamar o método do controlador, e se você desenvolver. método para lidar com exceções, então a estrutura SpringMVC irá chamar automaticamente este método para lidar com as exceções!

Com relação ao tratamento unificado de exceções:

  • Por padrão, este método só pode atuar em solicitações relacionadas na classe do controlador atual. Por exemplo, se o método for escrito UserController, ele só pode atuar UserControllerem cada solicitação processada no processo. Se aparecer na execução de outros métodos de controlador Exceções não será manipulado! Em relação a este problema, existem duas soluções alternativas:

    • Escreva o método para tratar exceções na classe base da classe do controlador, e cada classe do controlador pode herdar desta classe base;

    • Defina o método de tratamento de exceções em qualquer classe, e adicione @ControllerAdviceou @RestControllerAdviceanote antes da declaração desta classe , cada classe controladora não precisa herdar desta classe;

  • @ExceptionHandlerAs anotações devem ser adicionadas para tratar exceções uniformemente ;

  • Deve usar publicpermissões;

  • O tipo do valor de retorno pode se referir ao princípio de design do valor de retorno do método que trata a solicitação;

  • O nome do método pode ser personalizado;

  • A lista de parâmetros do método precisa adicionar pelo menos o parâmetro do tipo de exceção para representar o objeto de exceção capturado pelo framework. Quanto ao tipo de exceção do parâmetro, é necessário ser capaz de representar qualquer exceção a ser tratada; outro parâmetros também podem ser adicionados à lista de parâmetros do método, mas, você só pode adicionar alguns parâmetros permitidos pela estrutura SpringMVC, como HttpServletRequest, HttpServletResponseetc., não tão aleatórios quanto o método de processamento da solicitação!

Você pode cn.tedu.straw.portal.controllercriar uma GlobalExceptionHandlerclasse no pacote do projeto para tratamento unificado de exceções, adicioná- @RestControllerAdvicela antes da declaração da classe , para que o método de tratamento de exceções nesta classe possa atuar em todo o projeto e adicionar métodos para tratar exceções nesta classe:

package cn.tedu.straw.portal.controller;
​
import cn.tedu.straw.portal.service.ex.ClassDisabledException;
import cn.tedu.straw.portal.service.ex.InsertException;
import cn.tedu.straw.portal.service.ex.InviteCodeException;
import cn.tedu.straw.portal.service.ex.PhoneDuplicateException;
import cn.tedu.straw.portal.vo.R;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
​
@RestControllerAdvice
public class GlobalExceptionHandler {
​
    @ExceptionHandler
    public R handleException(Throwable e) {
        if (e instanceof InviteCodeException) {
            return R.failure(12, e);
        } else if (e instanceof ClassDisabledException) {
            return R.failure(13, e);
        } else if (e instanceof PhoneDuplicateException) {
            return R.failure(14, e);
        } else if (e instanceof InsertException) {
            return R.failure(15, e);
        } else {
            return R.failure(9999, e);
        }
    }
​
}

Para facilitar o gerenciamento unificado dos códigos de erro e aumentar a legibilidade do código, esses códigos de erro devem ser declarados como constantes estáticas. Ao mesmo tempo, para facilitar a declaração e o gerenciamento dessas constantes Restáticas , as interfaces internas estáticas podem ser usado na classe para declarar:

package cn.tedu.straw.portal.vo;
​
import lombok.Data;
import lombok.experimental.Accessors;
​
@Data
@Accessors(chain=true)
public class R {
​
    private Integer state;
    private String message;
​
    public static R ok() {
        return new R().setState(State.OK);
    }
​
    public static R failure(Integer state, String message) {
        return new R().setState(state).setMessage(message);
    }
​
    public static R failure(Integer state, Throwable e) {
        return failure(state, e.getMessage());
    }
​
    public static interface State {
        int OK = 0;
        int ERR_INVITE_CODE = 4001;
        int ERR_CLASS_DISABLED = 4002;
        int ERR_PHONE_DUPLICATE = 4003;
        int ERR_INSERT = 4004;
        int ERR_UNKNOWN = 9999;
    }
​
}

Então, ao lidar com exceções, o código de erro usa estas constantes estáticas:

package cn.tedu.straw.portal.controller;
​
import cn.tedu.straw.portal.service.ex.ClassDisabledException;
import cn.tedu.straw.portal.service.ex.InsertException;
import cn.tedu.straw.portal.service.ex.InviteCodeException;
import cn.tedu.straw.portal.service.ex.PhoneDuplicateException;
import cn.tedu.straw.portal.vo.R;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
​
@RestControllerAdvice
public class GlobalExceptionHandler {
​
    @ExceptionHandler
    public R handleException(Throwable e) {
        if (e instanceof InviteCodeException) {
            return R.failure(R.State.ERR_INVITE_CODE, e);
        } else if (e instanceof ClassDisabledException) {
            return R.failure(R.State.ERR_CLASS_DISABLED, e);
        } else if (e instanceof PhoneDuplicateException) {
            return R.failure(R.State.ERR_PHONE_DUPLICATE, e);
        } else if (e instanceof InsertException) {
            return R.failure(R.State.ERR_INSERT, e);
        } else {
            return R.failure(R.State.ERR_UNKNOWN, e);
        }
    }
​
}

9. Solicitar verificação de parâmetro

Para o desenvolvimento do lado do servidor, todos os parâmetros de solicitação enviados pelo cliente devem ser considerados não confiáveis . Por exemplo, o "nome do usuário" pode ter 1 letra ou outro formato básico está incorreto (comprimento, caracteres de composição). O problema é que mesmo se o o próprio cliente tem um mecanismo de verificação, não é confiável. Afinal, o cliente pode ser adulterado ou o cliente que não é navegador também pode ter a versão que o usuário usa não atualizada, o que faz com que o formato do parâmetro de solicitação fique incorreto. problema! Portanto, o servidor deve verificar a validade desses parâmetros na primeira vez que receber os parâmetros de solicitação!

Nota: Mesmo que todos os parâmetros sejam verificados no lado do servidor, a verificação no lado do cliente deve existir! O objetivo principal é interceptar a maioria das solicitações erradas para reduzir a pressão no lado do servidor!

Ao implementar a inspeção do lado do servidor, você pode usá-la hibernate-validationpara implementá-la. No momento, ela foi integrada spring-boot-starter-validation, então primeiro adicione a dependência ao projeto:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

Em relação a este mecanismo de verificação, sua abordagem é verificar as propriedades de um objeto. Algumas anotações podem ser adicionadas para indicar as regras de verificação antes das propriedades que precisam ser verificadas. Anotações comumente usadas são:

  • @NotNull: Não é permitido não ter valor, ou seja, não é permitido ter null;

  • @NotEmpty: Não é permitido ser um valor de string vazia, ou seja, o comprimento da string deve ser maior que 0;

  • @NotBlank: Espaço em branco não é permitido, ou seja, a string deve conter caracteres diferentes de espaços em branco, por exemplo, " "também está errado;

  • @Pattern: A expressão regular usada durante a verificação pode ser definida nos parâmetros de anotação;

  • @Size: Verifique se o comprimento do valor da string está dentro de um determinado intervalo;

  • outro……

Por exemplo, você pode Useradicionar anotações relacionadas à validação antes dos atributos da classe, por exemplo, usernameadicionar anotações de validação antes dos atributos:

@NotBlank(message = "用户名不允许为空!")
@Size(min = 4, max = 16, message = "用户名必须是4~16个字符!")
private String username;

Em seguida, na classe do controlador, na lista de parâmetros do método para processar a solicitação, adicione @Validou @Validationanote antes do objeto a ser verificado e, em seguida, adicione o BindingResultparâmetro. No corpo do método da solicitação, julgue o BindingResultparâmetro para obter a verificação resultado:

​// http://localhost:8080/portal/user/student/register?inviteCode=JSD2003-111111&nickname=Hello&phone=13800138002&password=1234
@RequestMapping("/student/register")
public R studentRegister(String inviteCode,
    @Validated User user, BindingResult bindingResult) {
    if (inviteCode == null || inviteCode.length() < 4 || inviteCode.length() > 20) {
        throw new ParameterValidationException("邀请码必须是4~20个字符!");
    }
    if (bindingResult.hasErrors()) {
        String errorMessage = bindingResult
                .getFieldError().getDefaultMessage();
        log.debug("validation has error : {}", errorMessage);
        throw new ParameterValidationException(errorMessage);
    }

    userService.registerStudent(user, inviteCode);
    return R.ok();
}

Em relação à verificação acima:

  • O objeto verificado deve ser 1 objeto;

  • O resultado da verificação do pacote BindingResultdeve ser declarado após o parâmetro verificado;

  • A estrutura de verificação não pode completar todos os requisitos de verificação.Se algumas regras de verificação não puderem ser implementadas pela estrutura de verificação, você mesmo pode escrever as regras de verificação;

  • Se houver um erro no processo de verificação, e a BindingResultmensagem de erro de recebimento não for utilizada no controlador , ela será lançada.Também BindExceptioné possível tratar essa exceção diretamente no código de tratamento de exceção unificado;

  • O código de demonstração acima estará envolvido Re GlobalExceptionHandleressas duas classes e outros conteúdos relacionados.

10. Registre-se para o teste de página inicial

Para evitar que o Spring Security intercepte solicitações assíncronas, você precisa personalizar a classe de configuração, herdar WebSecurityConfigurerAdapter, reescrever protected void configure(HttpSecurity http)e chamar o csrf().disable()método do objeto de parâmetro , para que a solicitação AJAX possa ser enviada normalmente na página da web!

 

package cn.tedu.straw.portal.security;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import sun.security.pkcs11.P11Util;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
    }
}

O front end usará o Vue para acessar os elementos da página, usará AJAX para enviar solicitações assíncronas e processar os resultados. O uso básico dessas duas estruturas de front-end é demonstrado da seguinte maneira:

Código da página de teste:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>学生注册</title>
    <script src="jquery.min.js"></script>
    <script src="vue.js"></script>
    <style>
        .err { color: red; }
    </style>
</head>
<body>
<h1>学生注册</h1>
<div id="register">
    <form id="form-register" v-on:submit.prevent="register" action="/portal/user/student/register">
        <table>
            <tr>
                <td>邀请码</td>
                <td><input name="inviteCode"><span class="err" v-text="inviteCodeMessage"></span></td>
            </tr>
            <tr>
                <td>手机号码</td>
                <td><input name="phone"><span class="err" v-text="phoneMessage"></span></td>
            </tr>
            <tr>
                <td>昵称</td>
                <td><input name="nickname"></td>
            </tr>
            <tr>
                <td>密码</td>
                <td><input name="password"></td>
            </tr>
            <tr>
                <td>确认密码</td>
                <td><input name="confirmPassword"></td>
            </tr>
            <tr>
                <td>&nbsp;</td>
                <td><input value="用户注册" type="submit"></td>
            </tr>
        </table>
    </form>
</div>
<script>
    // 创建Vue数据模型
    let app = new Vue({
        el: '#register',
        data:{
            inviteCodeMessage: null,
            phoneMessage: null
        },
        methods: {
            register: function () {
                // alert("准备注册!");
                app.inviteCodeMessage = null;
                app.phoneMessage = null;
                $.ajax({
                    url: '/portal/user/student/register',
                    data: $('#form-register').serialize(),
                    type: 'post',
                    dataType: 'json',
                    success: function(json) {
                        console.log(json);
                        if (json.state == 2000) {
                            alert("注册成功!");
                        } else if (json.state == 4001 || json.state == 4002) {
                            app.inviteCodeMessage = json.message;
                        } else if (json.state == 4003) {
                            app.phoneMessage = json.message;
                        } else {
                            alert("注册失败!" + json.message);
                        }
                    }
                });
            }
        }
    });
</script>
</body>
</html>

Em relação à página real, você pode baixá-la em https://robinliu.3322.org:8888/download/demo/straw_v1.4.zip , copiar a página baixada e os arquivos relacionados para o projeto src / main / resources / static Just sob a pasta (se a pasta estática não existir, você mesmo pode criá-la).

Em relação à página de registro real, as posições que precisam ser ajustadas são:

  • Linha 30: <div>Adicionar o rótulo v-bind:class="{'d-block': hasError}", o que significa que o valor hasErrordo nome d-blockno estilo CSS é controlado através das propriedades do Vue . Isso d-blocksignifica display blockque o rótulo atual é exibido na forma de um bloco. É definido pelo Bootstrap e o valor é um valor booleano;

  • Linha 31: <span>Adicionar para o rótulo v-text="errorMessage", indicando que o texto exibido no rótulo está vinculado ao errorMessageatributo Vue ;

  • Linha 34: para o <form>rótulo adicionado id="form-register"e v-on:submit.prevent="register", para o parâmetro de solicitação de serialização de solicitação AJAX anterior, que indica um evento vinculado a formar um registermétodo (função) Vue , que preventrepresenta o método de envio original para evitar, neste <form>Todos os outros atributos originais no rótulo pode ser excluído;

  • Linha 37: Exclua o <input>rótulo v-model="inviteCode", caso contrário, um erro pode ser relatado;

  • Adicione sua própria <script>página de processamento de etiqueta:

<script>
    let app = new Vue({
        el: '#app',
        data: {
            hasError: false,
            errorMessage: null
        },
        methods: {
            register: function () {
                app.hasError = false;
                app.errorMessage = null;
                $.ajax({
                    url: '/portal/user/student/register',
                    data: $('#form-register').serialize(),
                    type: 'post',
                    dataType: 'json',
                    success: function (json) {
                        console.log(json);
                        if (json.state == 2000) {
                            alert("注册成功!");
                            // location.href = '某个页面';
                        } else {
                            app.hasError = true;
                            app.errorMessage = json.message;
                        }
                    }
                });
            }
        }
    });
</script>

11. Concluindo o trabalho após o registro bem-sucedido

No userdesign da tabela de dados atual , o campo da senha é char(68), mas o comprimento real da senha é de apenas 60 bits. Na verdade, antes que a senha seja salva {bcrpyt}, um prefixo deve ser adicionado antes do resultado da criptografia . A função deste prefixo é Para declarar o texto cifrado atual. O método de criptografia é BCrpytimplementado por meio de um algoritmo, e Spring Security pode chamar automaticamente o algoritmo de correspondência para verificação de senha de acordo com o tipo de algoritmo!

Portanto, na UserServiceImplclasse, adicione o prefixo de conclusão ao método de criptografia:

/**
 * 执行密码加密
 *
 * @param rawPassword 原密码
 * @return 根据原密码执行加密得到的密文
 */
private String encode(String rawPassword) {
    String encodePassword = passwordEncoder.encode(rawPassword);
    encodePassword = "{bcrypt}" + encodePassword;
    return encodePassword;
}

Devido à alteração das regras de senha, a senha original não é mais apropriada e existem ainda mais dados incorretos originalmente. Todos esses dados devem ser excluídos:

delete from user;

ou:

truncate user;

12. Resumo do estágio

Em relação à tecnologia utilizada:

  • SpringBoot ;

  • Lombok: faz com que os desenvolvedores não precisem mais declarar explicitamente Setters, Getters, toString () e outros códigos, e podem usar @Slf4janotações para gerar logs de maneira mais conveniente ;

  • Slf4j: registro de saída, personalizar o nível de registro, usar marcadores de posição para evitar a emenda repetida de strings durante a saída;

  • MyBatis Plus: Uma série de adições, exclusões e alterações de rotina foram concluídas, para que os desenvolvedores não tenham que escrever códigos relacionados por si mesmos, o que simplifica o desenvolvimento da camada de persistência. Claro, as funções concluídas do MyBatis Plus não podem atender a todas as necessidades, e mesmo alguns métodos podem não ser muito fáceis de usar, se os desenvolvedores precisarem personalizar outras funções de acesso a dados, eles também podem se referir ao uso de MyBatis para desenvolver novas funções de acesso a dados;

  • MyBatis Plus Generator: Usado para gerar arquivos automaticamente em alguns projetos, incluindo: classes de entidade, interfaces de camada de persistência, XML de camada de persistência, interfaces de camada de negócios, classes de implementação de camada de negócios e classes de controlador. É projetado automaticamente com base no campo dos dados tabela. Gerado;

  • Spring Security: a ser continuado;

  • Validação Spring: verificar a validade dos parâmetros do pedido;

  • Exceções personalizadas e tratamento unificado de exceções.

 

Acho que você gosta

Origin blog.csdn.net/c202003/article/details/107405853
Recomendado
Clasificación