Maison > Java > javaDidacticiel > Quelles sont les techniques de vérification des paramètres SpringBoot ?

Quelles sont les techniques de vérification des paramètres SpringBoot ?

WBOY
Libérer: 2023-05-12 08:19:24
avant
1471 Les gens l'ont consulté

1. Utiliser les annotations de validation

Spring Boot fournit des annotations de validation intégrées, qui peuvent vous aider à valider les champs de saisie simplement et rapidement, comme vérifier les champs nuls ou vides, appliquer des limites de longueur et utiliser expressions régulières Modèles de validation d'expression ainsi que validation des adresses e-mail. Spring Boot 提供了内置的验证注解,可以帮助简单、快速地对输入字段进行验证,例如检查 null 或空字段、强制执行长度限制、使用正则表达式验证模式以及验证电子邮件地址。

一些最常用的验证注释包括:

  • @NotNull:指定字段不能为空。

  • @NotEmpty:指定列表字段不能为空。

  • @NotBlank:指定字符串字段不得为空或仅包含空格。

  • @Min@Max:指定数字字段的最小值和最大值。

  • @Pattern:指定字符串字段必须匹配的正则表达式模式。

  • @Email:指定字符串字段必须是有效的电子邮件地址。

具体用法参考下面例子:

public class User {
    @NotNull
    private Long id;

    @NotBlank
    @Size(min = 2, max = 50)
    private String firstName;

    @NotBlank
    @Size(min = 2, max = 50)
    private String lastName;

    @Email
    private String email;

    @NotNull
    @Min(18)
    @Max(99)
    private Integer age;

    @NotEmpty
    private List<String> hobbies;

    @Pattern(regexp = "[A-Z]{2}\d{4}")
    private String employeeId;
Copier après la connexion

2.使用自定义验证注解

虽然 Spring Boot 的内置验证注释很有用,但它们可能无法涵盖所有情况。如果有特殊参数验证的场景,可以使用 Spring 的 JSR 303 验证框架创建自定义验证注释。自定义注解可以让你的的验证逻辑更具可重用性和可维护性。

假设我们有一个应用程序,用户可以在其中创建帖子。每个帖子都应该有一个标题和一个正文,并且标题在所有帖子中应该是唯一的。虽然 Spring Boot 提供了用于检查字段是否为空的内置验证注释,但它没有提供用于检查唯一性的内置验证注释。在这种情况下,我们可以创建一个自定义验证注解来处理这种情况。

首先,我们创建自定义约束注解UniqueTitle

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UniqueTitleValidator.class)
public @interface UniqueTitle {
    String message() default "Title must be unique";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}
Copier après la connexion

接下来,我们创建一个PostRepository接口,目的是从数据库中检索帖子:

public interface PostRepository extends JpaRepository<Post, Long> {
    Post findByTitle(String title);
}
Copier après la connexion

然后我们需要定义验证器类 UniqueTitleValidator,如下所示:

@Component
public class UniqueTitleValidator implements ConstraintValidator<UniqueTitle, String> {

    @Autowired
    private PostRepository postRepository;

    @Override
    public boolean isValid(String title, ConstraintValidatorContext context) {
        if (title == null) {
            return true;
        }
        return Objects.isNull(postRepository.findByTitle(title));
    }
}
Copier après la connexion

UniqueTitleValidator实现了ConstraintValidator接口,它有两个泛型类型:第一个是自定义注解UniqueTitle,第二个是正在验证的字段类型(在本例中为String). 我们还自动装配了PostRepository 类以从数据库中检索帖子。

isValid()方法通过查询 PostRepository 来检查 title 是否为 null 或者它是否是唯一的。如果 title 为 null 或唯一,则验证成功,并返回 true。

定义了自定义验证注释和验证器类后,我们现在可以使用它来验证 Spring Boot 应用程序中的帖子标题:

public class Post {
    @UniqueTitle
    private String title;

    @NotNull
    private String body;
}
Copier après la connexion

我们已将 @UniqueTitle 注释应用于 Post 类中的 title 变量。验证此字段时,这将触发 UniqueTitleValidator 类中定义的验证逻辑。

3.在服务器端验证

除了前端或者客户端做了验证意外,服务器端验证输入是至关重要的。它可以确保在处理或存储任何恶意或格式错误的数据之前将其捕获,这对于应用程序的安全性和稳定性至关重要。

假设我们有一个允许用户创建新帐户的 REST 端点。端点需要一个包含用户用户名和密码的 JSON 请求体。为确保输入有效,我们可以创建一个 DTO(数据传输对象)类并将验证注释应用于其字段:

public class UserDTO {

    @NotBlank
    private String username;

    @NotBlank
    private String password;
}
Copier après la connexion

我们使用@NotBlank注解来确保usernamepassword字段不为空或 null。

接下来,我们可以创建一个控制器方法来处理 HTTP POST 请求并在创建新用户之前验证输入:

@RestController
@RequestMapping("/users")
@Validated
public class UserController {
    @Autowired
    private UserService userService;

    @PostMapping
    public ResponseEntity<String> createUser(@Valid @RequestBody UserDTO userDto) {
        userService.createUser(userDto);
        return ResponseEntity.status(HttpStatus.CREATED).body("User created successfully");
    }
}
Copier après la connexion

我们使用 Spring 的@Validated注解来启用方法级验证,我们还将 @Valid 注释应用于 userDto 参数以触发验证过程。

4.提供有意义的错误信息

当验证失败时,必须提供清晰简洁的错误消息来描述出了什么问题以及如何修复它。

这是一个示例,如果我们有一个允许用户创建新用户的 RESTful API。我们要确保姓名和电子邮件地址字段不为空,年龄在 18 到 99 岁之间,除了这些字段,如果用户尝试使用重复的“用户名”创建帐户,我们还会提供明确的错误消息或“电子邮件”。

为此,我们可以定义一个带有必要验证注释的模型类 User,如下所示:

public class User {

    @NotBlank(message = "用户名不能为空")
    private String name;

    @NotBlank(message = "Email不能为空")
    @Email(message = "无效的Emaild地址")
    private String email;

    @NotNull(message = "年龄不能为空")
    @Min(value = 18, message = "年龄必须大于18")
    @Max(value = 99, message = "年龄必须小于99")
    private Integer age;
}
Copier après la connexion

我们使用 message属性为每个验证注释提供了自定义错误消息。

接下来,在我们的 Spring 控制器中,我们可以处理表单提交并使用 @Valid 注释验证用户输入:

@RestController
@RequestMapping("/users")
public class UserController {
    @Autowired
    private UserService userService;

    @PostMapping
    public ResponseEntity<String> createUser(@Valid @RequestBody User user, BindingResult result) {
        if (result.hasErrors()) {
            List<String> errorMessages = result.getAllErrors().stream()
                    .map(DefaultMessageSourceResolvable::getDefaultMessage)
                    .collect(Collectors.toList());
            return ResponseEntity.badRequest().body(errorMessages.toString());
        }

        // save the user to the database using UserService
        userService.saveUser(user);

        return ResponseEntity.status(HttpStatus.CREATED).body("User created successfully");
    }
}
Copier après la connexion

我们使用 @Valid 注释来触发 User 对象的验证,并使用 BindingResult

Certaines des annotations de validation les plus couramment utilisées incluent : 🎜
  • 🎜@NotNull : le champ spécifié ne peut pas être nul. 🎜
  • 🎜@NotEmpty : Le champ de liste spécifié ne peut pas être vide. 🎜
  • 🎜@NotBlank : Spécifie que le champ de chaîne ne doit pas être vide ou contenir uniquement des espaces. 🎜
  • 🎜@Min et @Max : Spécifiez les valeurs minimales et maximales pour les champs numériques. 🎜
  • 🎜@Pattern : Spécifie le modèle d'expression régulière auquel le champ de chaîne doit correspondre. 🎜
  • 🎜@Email : Spécifie que le champ de chaîne doit être une adresse e-mail valide. 🎜
🎜Veuillez vous référer à l'exemple suivant pour une utilisation spécifique : 🎜
# messages.properties
user.name.required=Name is required.
user.email.invalid=Invalid email format.
user.age.invalid=Age must be a number between 18 and 99.
Copier après la connexion
Copier après la connexion
🎜2. Utilisez des annotations de validation personnalisées🎜🎜Bien que les annotations de validation intégrées de Spring Boot soient utiles, elles peuvent ne pas couvrir toutes les situations. S'il existe des scénarios de validation de paramètres spéciaux, vous pouvez utiliser le cadre de validation JSR 303 de Spring pour créer des annotations de validation personnalisées. Les annotations personnalisées peuvent rendre votre logique de validation plus réutilisable et maintenable. 🎜🎜Supposons que nous ayons une application permettant aux utilisateurs de créer des publications. Chaque message doit avoir un titre et un corps de texte, et le titre doit être unique pour tous les messages. Bien que Spring Boot fournisse des annotations de validation intégrées pour vérifier si un champ est vide, il ne fournit pas d'annotations de validation intégrées pour vérifier l'unicité. Dans ce cas, nous pouvons créer une annotation de validation personnalisée pour gérer cette situation. 🎜🎜Tout d'abord, nous créons une annotation de contrainte personnalisée UniqueTitle : 🎜
user.name.required=名称不能为空.
user.email.invalid=无效的email格式.
user.age.invalid=年龄必须在18到99岁之间.
Copier après la connexion
Copier après la connexion
🎜Ensuite, nous créons une interface PostRepository dans le but de récupérer les publications de la base de données : 🎜
public class User {
    @NotNull(message = "{user.id.required}")
    private Long id;

    @NotBlank(message = "{user.name.required}")
    private String name;

    @Email(message = "{user.email.invalid}")
    private String email;

    @NotNull(message = "{user.age.required}")
    @Min(value = 18, message = "{user.age.invalid}")
    @Max(value = 99, message = "{user.age.invalid}")
    private Integer age;
}
Copier après la connexion
Copier après la connexion
🎜Ensuite Nous devons définir la classe de validateur UniqueTitleValidator comme suit : 🎜
@Configuration
public class AppConfig {
    @Bean
    public MessageSource messageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        messageSource.setBasename("messages");
        messageSource.setDefaultEncoding("UTF-8");
        return messageSource;
    }

    @Bean
    public LocalValidatorFactoryBean validator() {
        LocalValidatorFactoryBean validatorFactoryBean = new LocalValidatorFactoryBean();
        validatorFactoryBean.setValidationMessageSource(messageSource());
        return validatorFactoryBean;
    }
}
Copier après la connexion
Copier après la connexion
🎜UniqueTitleValidator implémente l'interface ConstraintValidator, qui a deux types génériques : Le premier est l'annotation personnalisée UniqueTitle et la seconde est le type de champ en cours de validation (dans ce cas String). Nous avons également câblé automatiquement la classe PostRepository pour récupérer les publications. à partir de la base de données. 🎜🎜La méthode isValid() vérifie si title est nul ou s'il est unique en interrogeant PostRepository. Si title est nul ou unique, la validation réussit et true est renvoyé. 🎜🎜Une fois l'annotation de validation personnalisée et la classe de validation définies, nous pouvons désormais l'utiliser pour valider les titres des articles dans notre application Spring Boot : 🎜
public class User {
    @NotBlank(groups = Default.class)
    private String firstName;

    @NotBlank(groups = Default.class)
    private String lastName;

    @Email(groups = EmailNotEmpty.class)
    private String email;

    // getters and setters omitted for brevity
    public interface EmailNotEmpty {}
    public interface Default {}
}
Copier après la connexion
Copier après la connexion
🎜Nous avons appliqué l'annotation @UniqueTitle à variable title dans la classe >Post. Lors de la validation de ce champ, cela déclenche la logique de validation définie dans la classe UniqueTitleValidator. 🎜🎜3. Vérification côté serveur🎜🎜En plus de la vérification sur le front-end ou sur le client, il est crucial de vérifier l'entrée côté serveur. Il garantit que toutes les données malveillantes ou mal formées sont détectées avant d'être traitées ou stockées, ce qui est essentiel pour la sécurité et la stabilité des applications. 🎜🎜Supposons que nous ayons un point de terminaison REST qui permet aux utilisateurs de créer de nouveaux comptes. Le point de terminaison attend un corps de requête JSON contenant le nom d'utilisateur et le mot de passe de l'utilisateur. Pour garantir que l'entrée est valide, nous pouvons créer une classe DTO (Data Transfer Object) et appliquer des annotations de validation à ses champs : 🎜
@RestController
@RequestMapping("/users")
@Validated
public class UserController {
    public ResponseEntity<String> createUser(
            @Validated({org.example.model.ex6.User.EmailNotEmpty.class}) @RequestBody User userWithEmail,
            @Validated({User.Default.class}) @RequestBody User userWithoutEmail)
    {
        // Create the user and return a success response
       
    }
}
Copier après la connexion
Copier après la connexion
🎜 Nous utilisons l'annotation @NotBlank pour garantir que username et le champ mot de passe n'est ni vide ni nul. 🎜🎜Ensuite, nous pouvons créer une méthode de contrôleur pour gérer la requête HTTP POST et valider l'entrée avant de créer un nouvel utilisateur : 🎜
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = EndDateAfterStartDateValidator.class)
public @interface EndDateAfterStartDate {
    String message() default "End date must be after start date";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}
Copier après la connexion
Copier après la connexion
🎜Nous utilisons l'annotation @Validated de Spring pour activer la validation au niveau de la méthode, nous Appliquez également l'annotation @Valid au paramètre userDto pour déclencher le processus de validation. 🎜🎜4. Fournissez des messages d'erreur significatifs 🎜🎜 Lorsque la validation échoue, un message d'erreur clair et concis doit être fourni décrivant ce qui n'a pas fonctionné et comment y remédier. 🎜🎜 Ceci est un exemple si nous avons une API RESTful qui permet aux utilisateurs de créer de nouveaux utilisateurs. Nous veillons à ce que les champs de nom et d'adresse e-mail ne soient pas vides et soient âgés de 18 à 99 ans. En plus de ces champs, nous fournissons également un message d'erreur clair ou « adresse e-mail » si un utilisateur tente de créer un compte avec un "nom d'utilisateur" en double. 🎜🎜Pour ce faire, nous pouvons définir une classe de modèle User avec les annotations de validation nécessaires comme suit : 🎜
public class EndDateAfterStartDateValidator implements ConstraintValidator<EndDateAfterStartDate, TaskForm> {
    @Override
    public boolean isValid(TaskForm taskForm, ConstraintValidatorContext context) {
        if (taskForm.getStartDate() == null || taskForm.getEndDate() == null) {
            return true;
        }

        return taskForm.getEndDate().isAfter(taskForm.getStartDate());
    }
}
Copier après la connexion
Copier après la connexion
🎜Nous fournissons un message d'erreur personnalisé pour chaque annotation de validation en utilisant l'attribut message. 🎜🎜Ensuite, dans notre contrôleur Spring, nous pouvons gérer la soumission du formulaire et valider la saisie de l'utilisateur à l'aide de l'annotation @Valid : 🎜
@EndDateAfterStartDate
public class TaskForm {
    @NotNull
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate startDate;

    @NotNull
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate endDate;
}
Copier après la connexion
Copier après la connexion
🎜Nous utilisons l'annotation @Valid pour Déclenchez la validation sur l'objet User et utilisez l'objet BindingResult pour détecter les erreurs de validation. 🎜

5.将 i18n 用于错误消息

如果你的应用程序支持多种语言,则必须使用国际化 (i18n) 以用户首选语言显示错误消息。

以下是在 Spring Boot 应用程序中使用 i18n 处理错误消息的示例

首先,在资源目录下创建一个包含默认错误消息的 messages.properties 文件

# messages.properties
user.name.required=Name is required.
user.email.invalid=Invalid email format.
user.age.invalid=Age must be a number between 18 and 99.
Copier après la connexion
Copier après la connexion

接下来,为每种支持的语言创建一个 messages_xx.properties 文件,例如,中文的 messages_zh_CN.properties

user.name.required=名称不能为空.
user.email.invalid=无效的email格式.
user.age.invalid=年龄必须在18到99岁之间.
Copier après la connexion
Copier après la connexion

然后,更新您的验证注释以使用本地化的错误消息

public class User {
    @NotNull(message = "{user.id.required}")
    private Long id;

    @NotBlank(message = "{user.name.required}")
    private String name;

    @Email(message = "{user.email.invalid}")
    private String email;

    @NotNull(message = "{user.age.required}")
    @Min(value = 18, message = "{user.age.invalid}")
    @Max(value = 99, message = "{user.age.invalid}")
    private Integer age;
}
Copier après la connexion
Copier après la connexion

最后,在 Spring 配置文件中配置 MessageSource bean 以加载 i18n 消息文件

@Configuration
public class AppConfig {
    @Bean
    public MessageSource messageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        messageSource.setBasename("messages");
        messageSource.setDefaultEncoding("UTF-8");
        return messageSource;
    }

    @Bean
    public LocalValidatorFactoryBean validator() {
        LocalValidatorFactoryBean validatorFactoryBean = new LocalValidatorFactoryBean();
        validatorFactoryBean.setValidationMessageSource(messageSource());
        return validatorFactoryBean;
    }
}
Copier après la connexion
Copier après la connexion

现在,当发生验证错误时,错误消息将根据随请求发送的“Accept-Language”标头以用户的首选语言显示。

6.使用分组验证

验证组是 Spring Boot 验证框架的一个强大功能,允许您根据其他输入值或应用程序状态应用条件验证规则。

现在有一个包含三个字段的User类的情况下:firstNamelastNameemail。我们要确保如果 email 字段为空,则 firstNamelastName 字段必须非空。否则,所有三个字段都应该正常验证。

为此,我们将定义两个验证组:EmailNotEmptyDefaultEmailNotEmpty 组将包含当 email 字段不为空时的验证规则,而 Default 组将包含所有三个字段的正常验证规则。

创建带有验证组的 User

public class User {
    @NotBlank(groups = Default.class)
    private String firstName;

    @NotBlank(groups = Default.class)
    private String lastName;

    @Email(groups = EmailNotEmpty.class)
    private String email;

    // getters and setters omitted for brevity
    public interface EmailNotEmpty {}
    public interface Default {}
}
Copier après la connexion
Copier après la connexion

请注意,我们在User类中定义了两个接口,EmailNotEmptyDefault。这些将作为我们的验证组。

接下来,我们更新Controller使用这些验证组

@RestController
@RequestMapping("/users")
@Validated
public class UserController {
    public ResponseEntity<String> createUser(
            @Validated({org.example.model.ex6.User.EmailNotEmpty.class}) @RequestBody User userWithEmail,
            @Validated({User.Default.class}) @RequestBody User userWithoutEmail)
    {
        // Create the user and return a success response
       
    }
}
Copier après la connexion
Copier après la connexion

我们已将@Validated注释添加到我们的控制器,表明我们想要使用验证组。我们还更新了 createUser 方法,将两个 User 对象作为输入,一个在 email 字段不为空时使用,另一个在它为空时使用。

@Validated 注释用于指定将哪个验证组应用于每个 User 对象。对于 userWithEmail 参数,我们指定了 EmailNotEmpty 组,而对于 userWithoutEmail 参数,我们指定了 Default 组。

进行这些更改后,现在将根据“电子邮件”字段是否为空对“用户”类进行不同的验证。如果为空,则 firstNamelastName 字段必须非空。否则,所有三个字段都将正常验证。

7.对复杂逻辑使用跨域验证

如果需要验证跨多个字段的复杂输入规则,可以使用跨字段验证来保持验证逻辑的组织性和可维护性。跨字段验证可确保所有输入值均有效且彼此一致,从而防止出现意外行为。

假设我们有一个表单,用户可以在其中输入任务的开始日期和结束日期,并且我们希望确保结束日期不早于开始日期。我们可以使用跨域验证来实现这一点。

首先,我们定义一个自定义验证注解EndDateAfterStartDate

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = EndDateAfterStartDateValidator.class)
public @interface EndDateAfterStartDate {
    String message() default "End date must be after start date";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}
Copier après la connexion
Copier après la connexion

然后,我们创建验证器EndDateAfterStartDateValidator

public class EndDateAfterStartDateValidator implements ConstraintValidator<EndDateAfterStartDate, TaskForm> {
    @Override
    public boolean isValid(TaskForm taskForm, ConstraintValidatorContext context) {
        if (taskForm.getStartDate() == null || taskForm.getEndDate() == null) {
            return true;
        }

        return taskForm.getEndDate().isAfter(taskForm.getStartDate());
    }
}
Copier après la connexion
Copier après la connexion

最后,我们将EndDateAfterStartDate注释应用于我们的表单对象TaskForm

@EndDateAfterStartDate
public class TaskForm {
    @NotNull
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate startDate;

    @NotNull
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate endDate;
}
Copier après la connexion
Copier après la connexion

现在,当用户提交表单时,验证框架将自动检查结束日期是否晚于开始日期,如果不是,则提供有意义的错误消息。

8.对验证错误使用异常处理

可以使用异常处理ExceptionHandler来统一捕获和处理验证错误。

以下是如何在 Spring Boot 中使用异常处理来处理验证错误的示例:

@RestControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
                                                                  HttpHeaders headers, HttpStatus status,
                                                                  WebRequest request) {
        Map<String, Object> body = new LinkedHashMap<>();
        body.put("timestamp", LocalDateTime.now());
        body.put("status", status.value());

        // Get all errors
        List<String> errors = ex.getBindingResult()
                .getFieldErrors()
                .stream()
                .map(x -> x.getDefaultMessage())
                .collect(Collectors.toList());

        body.put("errors", errors);

        return new ResponseEntity<>(body, headers, status);
    }
}
Copier après la connexion

在这里,我们创建了一个用 @RestControllerAdvice 注解的 RestExceptionHandler 类来处理我们的 REST API 抛出的异常。然后我们创建一个用 @ExceptionHandler 注解的方法来处理在验证失败时抛出的 MethodArgumentNotValidException

在处理程序方法中,我们创建了一个 Map 对象来保存错误响应的详细信息,包括时间戳、HTTP 状态代码和错误消息列表。我们使用 MethodArgumentNotValidException 对象的 getBindingResult() 方法获取所有验证错误并将它们添加到错误消息列表中。

最后,我们返回一个包含错误响应详细信息的ResponseEntity对象,包括作为响应主体的错误消息列表、HTTP 标头和 HTTP 状态代码。

有了这个异常处理代码,我们的 REST API 抛出的任何验证错误都将被捕获并以结构化和有意义的格式返回给用户,从而更容易理解和解决问题。

9.测试你的验证逻辑

需要为你的验证逻辑编写单元测试,以帮助确保它正常工作。

@DataJpaTest
public class UserValidationTest {

    @Autowired
    private TestEntityManager entityManager;

    @Autowired
    private Validator validator;

    @Test
    public void testValidation() {
        User user = new User();
        user.setFirstName("John");
        user.setLastName("Doe");
        user.setEmail("invalid email");

        Set<ConstraintViolation<User>> violations = validator.validate(user);

        assertEquals(1, violations.size());
        assertEquals("must be a well-formed email address", violations.iterator().next().getMessage());
    }
}
Copier après la connexion

我们使用 JUnit 5 编写一个测试来验证具有无效电子邮件地址的“用户”对象。然后我们使用 Validator 接口来验证 User 对象并检查是否返回了预期的验证错误。

10.考虑客户端验证

客户端验证可以通过向用户提供即时反馈并减少对服务器的请求数量来改善用户体验。但是,不应依赖它作为验证输入的唯一方法。客户端验证很容易被绕过或操纵,因此必须在服务器端验证输入,以确保安全性和数据完整性。

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:yisu.com
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal