【智能排班系统】Hibernate Validator 参数校验

2024-09-01 03:28

本文主要是介绍【智能排班系统】Hibernate Validator 参数校验,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

🎯导读:本文档介绍了参数校验的重要性及其在软件开发中的作用,强调了数据完整性、安全性、用户体验、系统稳定性及开发效率等方面的关键价值。文档详细阐述了Hibernate Validator这一流行的Java验证框架的使用方法,展示了如何利用其内置注解(如@NotNull、@Size、@Email等)来对输入数据进行有效性检查。此外,还探讨了自定义校验规则的开发方式,以及如何通过分组校验来适应不同的业务场景需求。通过集成Hibernate Validator,开发者可以显著提升应用程序的质量与用户体验。

文章目录

  • 参数校验
  • Hibernate Validator 简介
  • 依赖
  • 基础使用
  • 常用校验注解
    • 字段校验注解
    • 其他注解
  • 分组校验
    • 定义分组
    • 使用
  • 自定义校验
    • 定义注解
    • 校验器实现
    • 使用
    • 排班系统实现(以添加节日为例)
      • 代码位置
      • 参数类
      • 接口
      • 统一异常处理
      • 测试
  • 嵌套校验

参数校验

参数校验是指在接收输入数据时,对传入的参数进行验证,以确保它们符合预期的格式、范围和有效性。这种校验对于保证软件的稳定性和安全性至关重要。以下是参数校验的一些关键作用和意义:

  1. 数据完整性:
    1. 参数校验可以帮助确保接收到的数据符合预期的格式和结构。例如,通过校验确保日期格式正确、数值在合理范围内、字符串长度合适等。
    2. 这样可以防止因输入数据错误而导致的程序异常或错误行为。
  2. 安全性:
    1. 校验可以防止恶意或意外的数据注入,如 SQL 注入、XSS 攻击等。通过严格的校验规则,可以降低这些安全风险。
    2. 正确的校验机制能够帮助过滤掉不安全的输入,保护系统免受攻击。
  3. 用户体验:
    1. 在用户界面上,及时反馈错误信息给用户,帮助他们更快地纠正输入错误,改善用户体验。
    2. 清晰的错误提示可以使用户更容易理解和操作系统,减少因输入错误造成的困惑和沮丧感。
  4. 系统稳定性:
    1. 通过在校验阶段捕获潜在的问题,可以提前处理错误情况,避免后续处理逻辑中出现未预见的异常。
    2. 这有助于确保系统在面对各种输入时都能保持一致的行为,提高系统的可靠性和稳定性。
  5. 开发效率:
    1. 自动化的参数校验减少了手工检查的需要,简化了开发过程,降低了出错的可能性。
    2. 开发者可以专注于核心业务逻辑,而不必担心基础的数据验证问题。
  6. 易于维护:
    1. 明确的校验规则使得代码更易于理解和维护。当需要调整业务逻辑或数据格式时,可以在一处修改校验逻辑即可。
    2. 这有助于保持代码的清晰和整洁,便于团队协作。

Hibernate Validator 简介

参数校验最直接的方式就是在方法中实现具体的逻辑来判断参数是否合理,但是如果每个方法都要这样判断,未免太过复杂,Hibernate Validator 定义了一些常用的校验注解可以帮助我们快速搞定一些常见的常数校验,如非空、长度限制、邮件是否合格……

Hibernate Validator 是一个强大的开源库,用于实现 Java Bean Validation(JSR 303 和 JSR 380)规范。它是 Java 应用程序中最广泛使用的验证框架之一。Hibernate Validator 提供了一套丰富的约束注解,允许开发者轻松地对实体对象中的属性进行约束定义,确保它们满足特定的业务规则。通过使用如 @NotNull@Size@Pattern 等内置注解,开发者可以方便地检查对象的状态,确保数据的完整性和一致性。

Hibernate Validator 不仅支持标准的 JSR 规范中定义的约束,还提供了一些额外的注解,如 @Length@ScriptAssert,使得验证逻辑更加灵活和强大。此外,它还支持自定义约束注解的开发,允许根据具体的应用需求来扩展验证功能。通过集成 Hibernate Validator,开发者可以在运行时自动执行验证逻辑,减少手工编写验证代码的工作量,并提高代码的可读性和可维护性。

Hibernate Validator 的应用场景非常广泛,从简单的 Web 表单数据验证到复杂的业务规则检查,都可以看到它的身影。无论是前端还是后端开发,Hibernate Validator 都能帮助开发者确保数据的准确性和有效性,从而提升软件的质量和用户体验。

依赖

在父工程添加如下依赖来管理版本

<hibernate-validator.version>6.2.5.Final</hibernate-validator.version><dependency><groupId>org.hibernate.validator</groupId><artifactId>hibernate-validator</artifactId><version>${hibernate-validator.version}</version>
</dependency>

sss-commonsss-enterprisesss-aggregation模块添加都如下依赖,你可能会疑惑,sss-enterprise模块不是已经导入了sss-common了吗,为什么还要重复引用。亲测不引用,参数会生效,这个bug我找了一个下午o(╥﹏╥)o

<dependency><groupId>org.hibernate.validator</groupId><artifactId>hibernate-validator</artifactId>
</dependency>

基础使用

首先给参数类的字段添加注解,例如给name定义非空检验

@Data
@TableName("festival")
@NoArgsConstructor
@AllArgsConstructor
public class FestivalEntity extends BaseEntity implements Serializable {private static final long serialVersionUID = 1L;/*** 节日名称*/@NotBlank(message = "节日名称不能为空")private String name;/*** 起始日期*/@NotNull(message = "起始日期不能为空")private Date startDate;/*** 截止日期*/@NotNull(message = "截止日期不能为空")private Date endDate;/*** 门店id*/@JsonSerialize(using = ToStringSerializer.class)private Long storeId;/*** 0:农历 1:新历*/private int type;
}

接在给Controller的方法添加校验注解,@Validated要添加在@RequestBody前面,这样接收到参数festival之后,就会按照所设定规则对里面的字段值进行校验

@PostMapping("/save")
public R save(@Validated @RequestBody FestivalEntity festival, HttpServletRequest httpServletRequest) {long storeId = Long.parseLong(JwtUtil.getStoreId(httpServletRequest.getHeader("token")));festival.setStoreId(storeId);festivalService.save(festival);return R.ok();
}

常用校验注解

字段校验注解

【布尔类】

  • **@AssertTrue**布尔表达式必须为true。
  • **@AssertFalse**布尔表达式必须为false。
@AssertTrue(message = "你必须同意服务条款")
private boolean agreeToTerms;@AssertFalse(message = "您必须不是机器人")
private boolean isRobot;

【数字类】

  • **@Min**数值必须大于等于指定的最小值。
  • **@Max**数值必须小于等于指定的最大值。
  • **@DecimalMin**十进制数必须大于等于指定的最小值。
  • **@DecimalMax**十进制数必须小于等于指定的最大值。
  • **@Digits**检查数字的整数部分和小数部分的位数是否不超过指定的值。
  • **@Range**数字或日期必须在指定范围内。
  • **@Negative**数字必须是负数。
  • **@NegativeOrZero**数字必须是负数或零。
  • **@Positive**数字必须是正数。
  • **@PositiveOrZero**数字必须是正数或零。
// 确保年龄至少为 18 岁
@Min(value = 18)
private int age;
// 确保考试分数不能超过 100 分
@Max(value = 100)
private int score;// 确保价格至少为 10.00。inclusive = true 表示包括 10.00 在内
@DecimalMin(value = "10.00", inclusive = true)
private BigDecimal price;
// 确保折扣率不能超过 0.99。inclusive = false 表示不包括 0.99
@DecimalMax(value = "0.99", inclusive = false)
private BigDecimal discountRate;@Digits(integer = 10, fraction = 2, message = "交易金额的整数部分不能超过10位数,小数部分不能超过2位数")
private BigDecimal amount;@Digits(integer = 5, fraction = 2, message = "手续费的整数部分不能超过5位数,小数部分不能超过2位数")
private BigDecimal fee;@Range(min = 18, max = 100, message = "年龄必须在18到100岁之间")
private int age;@Negative(message = "温度必须低于零度")
private double temperature;@NegativeOrZero(message = "海拔高度必须是负数或零")
private int altitude;@Positive(message = "体重必须是正数")
private double weight;@PositiveOrZero(message = "身高必须是正数或零")
private double height;

【日期类】

  • **@Past**日期必须在过去。
  • **@Future**日期必须在未来。
  • @PastOrPresent:用于验证日期是否在过去或当前日期。
  • @FutureOrPresent:用于验证日期是否在未来或当前日期。
@Past(message = "出生日期必须在过去")
private LocalDate birthDate;
@Future(message = "预约日期必须在未来")
private LocalDate appointmentDate;
@PastOrPresent
private LocalDate lastLogin;
@FutureOrPresent
private LocalDate nextAppointment;

【字符串类】

  • **@NotBlank**字符串不能为null且去除空白后长度必须大于零。
  • **@Length**字符串长度必须在指定范围内。
  • **@Email**字符串必须是有效的电子邮件地址。
  • **@Pattern**字符串必须匹配正则表达式。
  • **@CreditCardNumber**字符串必须是一个有效的信用卡号。
@NotBlank(message = "评论内容不能为空")
@Length(min = 5, max = 200, message = "评论内容长度必须在5到200个字符之间")
private String content;
@Email(message = "电子邮件地址无效")
private String email;
@NotBlank(message = "密码不能为空")
@Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d]{8,}$", message = "密码必须包含字母和数字,且长度至少为8个字符")
private String password;
@CreditCardNumber(message = "信用卡号无效")
private String creditCardNumber;

【通用】

  • **@NotEmpty**字符串、集合、数组等不能为null且长度必须大于零。
  • **@NotNull**字段或属性不能为null。
  • **@Null**字段或属性必须为null。
  • **@Size**用于字符串、集合、数组等,检查大小是否在指定范围内。
@NotNull(message = "用户名不能为空")
private String username;@NotNull(message = "密码不能为空")
private String password;@Size(min = 3, max = 20, message = "用户名长度必须在3到20个字符之间")
private String username;
@Size(min = 1, message = "至少需要选择一项兴趣")
private List<String> interests;@NotEmpty(message = "用户名不能为空")
private String username;

其他注解

  • **@Constraint**用于创建自定义约束。
  • **@ConstraintValidator**用于创建自定义约束的验证器。
  • **@ConstraintViolation**用于描述约束违反的情况。
  • **@ConstraintDescriptor**用于描述约束元数据。
  • **@ValidationGroups**允许你对验证逻辑进行分组,从而控制哪些约束在何时被应用。
  • **@Valid**应用于对象,验证对象的所有属性。
  • **@Validated**通常用于框架集成,比如Spring,以启用方法参数验证。

分组校验

同一个字段,不同方法中可能对其有不同的要求。例如名字这个字段,添加用户的时候要求该字段非空,但修改用户信息的时候,如果不修改名字,该字段可以为空。

为了满足上面的需求,我们需要定义不同的小组来进行校验隔离

定义分组

新增分组

/*** @Author dam* @create 2024/8/31 10:32*/
public interface AddGroup {
}

修改分组

/*** @Author dam* @create 2024/8/31 10:32*/
public interface UpdateGroup {
}

使用

在使用注解的时候,使用groups属性来定义即可

@NotNull(message = "起始日期不能为空", groups = {AddGroup.class})
private Date startDate;@NotNull(message = "起始日期不能为空", groups = {AddGroup.class, UpdateGroup.class})
private Date startDate;

自定义校验

自定义校验规则是为了让我们可以定义一些项目通用,但是Hibernate Validator不具备的校验方法。下面的例子会实现一个校验 传入参数 是否被 指定数组 所包含,例如参数是被在{1,2,3,4,5}当中

定义注解

import com.dam.valid.validator.TypeValidator;import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;/*** 注解:校验是否在所包含的数字中** @Author dam* @create 2024/8/31 10:48*/
@Documented
@Constraint(validatedBy = {TypeValidator.class})
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface TypeAnno {//--------------- 必须包含字段 ----------------String message() default "字段必须是0或1";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};//--------------- 自定义字段 ----------------/*** 用来接收所包含的值**/int[] values() default {};
}
  • @Documented:当你生成Javadoc文档时,该注解将被记录下来,方便其他开发者查阅
  • @Constraint(validatedBy = {TypeValidator.class}):表示这是一个验证约束注解,并指定了一个验证器类 TypeValidator,用于执行具体的验证逻辑。 validatedBy属性用来指定一个或多个验证器类,这些类负责实现具体的验证逻辑
  • @Target:定义此注解可以应用的目标元素类型。
    • ElementType.METHOD:方法。
    • ElementType.FIELD:字段。
    • ElementType.CONSTRUCTOR:构造函数。
    • ElementType.PARAMETER:方法或构造函数的参数。
  • @Retention(RetentionPolicy.RUNTIME):定义了注解的保留策略,这里设置为 RUNTIME,意味着该注解将在编译时保留,并且可以在运行时通过反射访问。

校验器实现

import com.dam.valid.annotations.TypeAnno;import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;/*** 校验器:校验是否在所包含的数字中** @Author dam* @create 2024/8/31 10:52*/
public class TypeValidator implements ConstraintValidator<TypeAnno, Integer> {/*** 存储类型*/private Set<Integer> typeSet = new HashSet<>();@Overridepublic void initialize(TypeAnno constraintAnnotation) {for (int value : constraintAnnotation.values()) {typeSet.add(value);}}/*** 校验字段是否有效** @param value 要校验的值* @param context* @return*/@Overridepublic boolean isValid(Integer value, ConstraintValidatorContext context) {System.out.println("触发校验");return typeSet.contains(value);}
}

使用

/*** 0:农历 1:新历*/
@TypeAnno(values = {0, 1}, groups = {AddGroup.class}, message = "节日日期类型只能是农历(0)、新历(1)")
private int type;

排班系统实现(以添加节日为例)

代码位置

在这里插入图片描述

参数类

import com.baomidou.mybatisplus.annotation.TableName;
import com.dam.model.entity.BaseEntity;
import com.dam.valid.annotations.TypeAnno;
import com.dam.valid.groups.AddGroup;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Date;/*** 门店节日表** @author dam* @email 1782067308@qq.com* @date 2023-03-13 16:42:08*/
@Data
@TableName("festival")
@NoArgsConstructor
@AllArgsConstructor
public class FestivalEntity extends BaseEntity implements Serializable {private static final long serialVersionUID = 1L;/*** 节日名称*/@NotBlank(message = "节日名称不能为空")private String name;/*** 起始日期*/@NotNull(message = "起始日期不能为空", groups = {AddGroup.class})private Date startDate;/*** 截止日期*/@NotNull(message = "截止日期不能为空", groups = {AddGroup.class})private Date endDate;/*** 门店id*/@JsonSerialize(using = ToStringSerializer.class)private Long storeId;/*** 0:农历 1:新历*/@TypeAnno(values = {0, 1}, groups = {AddGroup.class}, message = "节日日期类型只能是农历(0)、新历(1)")private int type;
}

接口

/*** 保存*/
@PostMapping("/save")
@OperationLog(title = FestivalController.title, businessType = BusinessTypeEnum.INSERT, detail = "新增节日")
@PreAuthorize("hasAuthority('bnt.festival.add')")
public R save(@Validated({AddGroup.class}) @RequestBody FestivalEntity festival, HttpServletRequest httpServletRequest) {long storeId = Long.parseLong(JwtUtil.getStoreId(httpServletRequest.getHeader("token")));festival.setStoreId(storeId);festivalService.save(festival);return R.ok();
}

统一异常处理

当请求参数未能通过 Spring MVC 的数据绑定和校验时,会抛出 MethodArgumentNotValidException。该方法捕捉此类异常,并将校验错误信息整理后返回给客户端。

import com.dam.model.enums.ResultCodeEnum;
import com.dam.model.result.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;import java.util.HashMap;
import java.util.Map;@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {/*** 参数校验异常* @param exception* @return*/@ExceptionHandler(value = MethodArgumentNotValidException.class)@ResponseBodypublic R handleValidException(MethodArgumentNotValidException exception) {// 创建一个 Map 用来存储每个字段的错误信息Map<String, String> map = new HashMap<>();// 获取数据校验的错误结果BindingResult bindingResult = exception.getBindingResult();// 遍历所有的 FieldError 对象,将每个字段的错误信息存入 map 中bindingResult.getFieldErrors().forEach(fieldError -> {// 获取错误信息String message = fieldError.getDefaultMessage();// 获取字段名String field = fieldError.getField();// 将字段名和错误信息放入 mapmap.put(field, message);});// 记录错误日志log.error("数据校验出现问题{}, 异常类型{}", exception.getMessage(), exception.getClass());// 获取错误信息并格式化成字符串StringBuilder sb = new StringBuilder();int id = 1;for (String key : map.keySet()) {// 格式化错误信息,每条错误信息前加上编号sb.append(id++).append(") ").append(key).append("->").append(map.get(key)).append("<br/>");}// 打印错误信息到控制台System.out.println(sb.toString());// 返回包含错误代码和错误信息的 R 对象return R.error(ResultCodeEnum.ARGUMENT_VALID_ERROR.getCode(), ResultCodeEnum.ARGUMENT_VALID_ERROR.getMessage() + ":<br/>" + sb.toString());}
}

测试

使用ApiFox软件来进行接口测试

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

定义请求头参数

在这里插入图片描述

发送请求

在这里插入图片描述

嵌套校验

嵌套校验指在进行数据验证时,不仅仅检查顶层对象的属性是否符合预期,同时也深入到对象的子对象或者集合中去验证它们的属性是否满足特定规则的过程。这种校验方式常见于复杂的数据结构中,比如对象的属性可能是一个列表,而列表中的每个元素本身又是一个对象;或者是对象的某个属性是一个具有多个字段的另一个对象。这时需要使用@Valid来修饰该属性

import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List;// 订单实体类
public class Order {@NotNull(message = "用户信息不能为空")@Validprivate User user;@NotEmpty(message = "订单中至少需要包含一个产品")@Validprivate List<Product> products;// Getters and Setterspublic User getUser() {return user;}public void setUser(User user) {this.user = user;}public List<Product> getProducts() {return products;}public void setProducts(List<Product> products) {this.products = products;}
}// 用户实体类
public class User {@NotNull(message = "用户名不能为空")private String name;// Getters and Setterspublic String getName() {return name;}public void setName(String name) {this.name = name;}public Address getAddress() {return address;}public void setAddress(Address address) {this.address = address;}
}

这篇关于【智能排班系统】Hibernate Validator 参数校验的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1125810

相关文章

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

基于人工智能的图像分类系统

目录 引言项目背景环境准备 硬件要求软件安装与配置系统设计 系统架构关键技术代码示例 数据预处理模型训练模型预测应用场景结论 1. 引言 图像分类是计算机视觉中的一个重要任务,目标是自动识别图像中的对象类别。通过卷积神经网络(CNN)等深度学习技术,我们可以构建高效的图像分类系统,广泛应用于自动驾驶、医疗影像诊断、监控分析等领域。本文将介绍如何构建一个基于人工智能的图像分类系统,包括环境

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,

Andrej Karpathy最新采访:认知核心模型10亿参数就够了,AI会打破教育不公的僵局

夕小瑶科技说 原创  作者 | 海野 AI圈子的红人,AI大神Andrej Karpathy,曾是OpenAI联合创始人之一,特斯拉AI总监。上一次的动态是官宣创办一家名为 Eureka Labs 的人工智能+教育公司 ,宣布将长期致力于AI原生教育。 近日,Andrej Karpathy接受了No Priors(投资博客)的采访,与硅谷知名投资人 Sara Guo 和 Elad G

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

如何在页面调用utility bar并传递参数至lwc组件

1.在app的utility item中添加lwc组件: 2.调用utility bar api的方式有两种: 方法一,通过lwc调用: import {LightningElement,api ,wire } from 'lwc';import { publish, MessageContext } from 'lightning/messageService';import Ca

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

【区块链 + 人才服务】可信教育区块链治理系统 | FISCO BCOS应用案例

伴随着区块链技术的不断完善,其在教育信息化中的应用也在持续发展。利用区块链数据共识、不可篡改的特性, 将与教育相关的数据要素在区块链上进行存证确权,在确保数据可信的前提下,促进教育的公平、透明、开放,为教育教学质量提升赋能,实现教育数据的安全共享、高等教育体系的智慧治理。 可信教育区块链治理系统的顶层治理架构由教育部、高校、企业、学生等多方角色共同参与建设、维护,支撑教育资源共享、教学质量评估、