índice
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
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 atuarUserController
em 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
@ControllerAdvice
ou@RestControllerAdvice
anote antes da declaração desta classe , cada classe controladora não precisa herdar desta classe;
-
-
@ExceptionHandler
As anotações devem ser adicionadas para tratar exceções uniformemente ; -
Deve usar
public
permissõ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
,HttpServletResponse
etc., não tão aleatórios quanto o método de processamento da solicitação!
Você pode cn.tedu.straw.portal.controller
criar uma GlobalExceptionHandler
classe no pacote do projeto para tratamento unificado de exceções, adicioná- @RestControllerAdvice
la 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 R
está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-validation
para 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 ternull
; -
@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 User
adicionar anotações relacionadas à validação antes dos atributos da classe, por exemplo, username
adicionar 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 @Valid
ou @Validation
anote antes do objeto a ser verificado e, em seguida, adicione o BindingResult
parâmetro. No corpo do método da solicitação, julgue o BindingResult
parâ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
BindingResult
deve 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
BindingResult
mensagem de erro de recebimento não for utilizada no controlador , ela será lançada.TambémBindException
é 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
R
eGlobalExceptionHandler
essas 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> </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ótulov-bind:class="{'d-block': hasError}"
, o que significa que o valorhasError
do nomed-block
no estilo CSS é controlado através das propriedades do Vue . Issod-block
significadisplay block
que 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ótulov-text="errorMessage"
, indicando que o texto exibido no rótulo está vinculado aoerrorMessage
atributo Vue ; -
Linha 34: para o
<form>
rótulo adicionadoid="form-register"
ev-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 umregister
método (função) Vue , queprevent
representa 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ótulov-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 user
design 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 é BCrpyt
implementado 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 UserServiceImpl
classe, 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
@Slf4j
anotaçõ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.