当前位置: 首页 > news >正文

WPF 数据验证

WPF提供了能与数据绑定系统紧密协作的验证功能。提供了两种方法用于捕获非法值:

1、可在数据对象中引发错误。

可以在设置属性时抛出异常,通常WPF会忽略所有在设置属性时抛出的异常,但可以进行配置,从而显示更有帮助的可视化指示。另一种选择是在自定义的数据类中实现 INotifyDataErrorInfo 或 IDataErrorInfo 接口,从而可得到指示错误的功能而不会抛出异常。

2、可在绑定级别上定义验证。

这种方法可获得使用相同验证的灵活性,而不必考虑使用的是哪个控件。更好的是,因为是在不同类中定义验证,可以很容易的在存储类似数据类型的多个绑定中重用验证。

错误模板

错误模板使用的是装饰层,装饰层是位于普通窗口内容之上的绘图层。使用装饰层,可添加可视化装饰来指示错误,而不用替换控件背后的控件模板或改变窗口的布局。文本框的标准错误模板通过在相应文本框的上面添加红色的Border元素来指示发生了错误。可使用错误模板添加其他细节。

<Style TargetType="{x:Type TextBox}"><Setter Property="Validation.ErrorTemplate"><Setter.Value><ControlTemplate><DockPanel LastChildFill="True"><TextBlock DockPanel.Dock="Right" Foreground="Red" FontSize="14" FontWeight="Bold" ToolTip="{Binding ElementName=adornerPlaceholder, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">*</TextBlock><Border BorderBrush="Green" BorderThickness="1"><AdornedElementPlaceholder Name="adornerPlaceholder"></AdornedElementPlaceholder></Border></DockPanel></ControlTemplate></Setter.Value></Setter><Style.Triggers><Trigger Property="Validation.HasError" Value="true"><Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/></Trigger></Style.Triggers>
</Style>

在数据对象中引发错误

我这里分别尝试了 IDataErrorInfo 与 INotifyDataErrorInfo 接口,这俩需要分别对应使用 <DataErrorValidationRule/> 与 <NotifyDataErrorValidationRule/> 。

IDataErrorInfo 接口定义了Error字段,这个字段在结构有多个字段时,难以映射,于是在[]下标下实现逻辑。

INotifyDataErrorInfo 接口需要实现 HasError,GetErrors 两个函数,并需要在属性的set 访问器内实现校验逻辑,并管理Error信息。

总体来说,在数据对象中引发错误不是一个好选择,验证逻辑硬编码在数据对象中,不利于复用。

在绑定级别上定义验证

在绑定级别上定义验证是针对于数据类型自定义验证规则,可以对同一种数据类型进行复用。自定义验证规则需要继承自 ValidationRule 类,需要实现 Validate 函数,在其中完成自定义验证内容。

public class PriceRule : ValidationRule
{private decimal min = 0;private decimal max = decimal.MaxValue;public decimal Min{get => min;set => min = value;}public decimal Max{get => max;set => max = value;}public override ValidationResult Validate(object value, CultureInfo cultureInfo){decimal price = 0;try{if (((string)value).Length > 0){price = Decimal.Parse((string)value, NumberStyles.Any, cultureInfo);}}catch{return new ValidationResult(false, "Illegal characters.");}if (price < min || price > max){return new ValidationResult(false, "Not in the range " + Min + " to " + Max + ".");}else{return new ValidationResult(true, null);}}
}

验证多个值

如果需要执行对两个或更多个绑定值的验证,可以通过 BindingGroup 来实现,将需要校验的多个控件放置于同一个容器中,在容器级别应用验证规则,需要通过事件主动触发验证,通常是子组件失去焦点时。

<Grid Grid.Row="3" Grid.ColumnSpan="2" DataContext="{Binding Path=Person}" TextBoxBase.LostFocus="Grid_LostFocus"><Grid.ColumnDefinitions><ColumnDefinition/><ColumnDefinition/></Grid.ColumnDefinitions><Grid.RowDefinitions><RowDefinition/><RowDefinition/></Grid.RowDefinitions><Grid.BindingGroup><BindingGroup x:Name="personValidation"><BindingGroup.ValidationRules><local:PersonRule/></BindingGroup.ValidationRules></BindingGroup></Grid.BindingGroup><TextBlock Grid.Row="0" Grid.Column="0">Person ID:</TextBlock><TextBox Grid.Row="0" Grid.Column="1" Text="{Binding Path=ID}" /><TextBlock Grid.Row="1" Grid.Column="0">Person Name:</TextBlock><TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Path=Name}"/>
</Grid>
public class Person : ViewModelBase
{private string name = string.Empty;public string Name { get=>name; set=> SetProperty(ref name, value); }private string id = string.Empty;public string ID { get => id; set => SetProperty(ref id, value); }
}
public class PersonRule : ValidationRule
{public override ValidationResult Validate(object value, CultureInfo cultureInfo){BindingGroup bindingGroup = (BindingGroup)value;Person? person = bindingGroup.Items[0] as Person;string name = (string)bindingGroup.GetValue(person, "Name");string id = (string)bindingGroup.GetValue(person, "ID");if ((name == "") && (id == "")){return new ValidationResult(false, "A Person requires a ID or Name.");}else{return new ValidationResult(true, null);}}
}

下面贴出完整的测试代码:

MainWindow.xaml

<Window x:Class="TestValidation.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:local="clr-namespace:TestValidation"mc:Ignorable="d"Title="MainWindow" Height="450" Width="800"><Border Padding="7" Margin="7" Background="LightSteelBlue"><Grid x:Name="myGrid"><Grid.Resources><Style TargetType="{x:Type TextBox}"><Setter Property="Validation.ErrorTemplate"><Setter.Value><ControlTemplate><DockPanel LastChildFill="True"><TextBlock DockPanel.Dock="Right" Foreground="Red" FontSize="14" FontWeight="Bold" ToolTip="{Binding ElementName=adornerPlaceholder, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">*</TextBlock><Border BorderBrush="Green" BorderThickness="1"><AdornedElementPlaceholder Name="adornerPlaceholder"></AdornedElementPlaceholder></Border></DockPanel></ControlTemplate></Setter.Value></Setter><Style.Triggers><Trigger Property="Validation.HasError" Value="true"><Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/></Trigger></Style.Triggers></Style></Grid.Resources><Grid.ColumnDefinitions><ColumnDefinition Width="Auto"/><ColumnDefinition/></Grid.ColumnDefinitions><Grid.RowDefinitions><RowDefinition Height="*"/><RowDefinition Height="*"/><RowDefinition Height="*"/><RowDefinition Height="2*"/></Grid.RowDefinitions><TextBlock Grid.Row="0" Grid.Column="0">PersonAge:</TextBlock><TextBox Grid.Row="0" Grid.Column="1" DataContext="{Binding Path=PersonAge}"><TextBox.Text><Binding Path="Age" NotifyOnValidationError="true"><Binding.ValidationRules><DataErrorValidationRule/></Binding.ValidationRules></Binding></TextBox.Text></TextBox><TextBlock Grid.Row="1" Grid.Column="0">PersonName:</TextBlock><TextBox Grid.Row="1" Grid.Column="1" DataContext="{Binding Path=PersonName}"><TextBox.Text><Binding Path="Name" NotifyOnValidationError="true"><Binding.ValidationRules><NotifyDataErrorValidationRule/></Binding.ValidationRules></Binding></TextBox.Text></TextBox><TextBlock Grid.Row="2" Grid.Column="0">PersonPrice:</TextBlock><TextBox Grid.Row="2" Grid.Column="1" DataContext="{Binding Path=PersonPrice}"><TextBox.Text><Binding Path="Price" NotifyOnValidationError="true"><Binding.ValidationRules><local:PriceRule Min="0"/></Binding.ValidationRules></Binding></TextBox.Text></TextBox><Grid Grid.Row="3" Grid.ColumnSpan="2" DataContext="{Binding Path=Person}" TextBoxBase.LostFocus="Grid_LostFocus"><Grid.ColumnDefinitions><ColumnDefinition/><ColumnDefinition/></Grid.ColumnDefinitions><Grid.RowDefinitions><RowDefinition/><RowDefinition/></Grid.RowDefinitions><Grid.BindingGroup><BindingGroup x:Name="personValidation"><BindingGroup.ValidationRules><local:PersonRule/></BindingGroup.ValidationRules></BindingGroup></Grid.BindingGroup><TextBlock Grid.Row="0" Grid.Column="0">Person ID:</TextBlock><TextBox Grid.Row="0" Grid.Column="1" Text="{Binding Path=ID}" /><TextBlock Grid.Row="1" Grid.Column="0">Person Name:</TextBlock><TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Path=Name}"/></Grid></Grid></Border>
</Window>

MainWindow.xaml.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;namespace TestValidation;public class ViewModelBase : INotifyPropertyChanged
{public event PropertyChangedEventHandler? PropertyChanged;protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null){PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));}protected virtual bool SetProperty<T>(ref T member, T value, [CallerMemberName] string? propertyName = null){if (EqualityComparer<T>.Default.Equals(member, value)){return false;}member = value;OnPropertyChanged(propertyName);return true;}
}
public class PersonAge : ViewModelBase, IDataErrorInfo
{public string this[string columnName]{get{if(columnName == "Age"){if(Age < 0 || Age > 150){return "Age must not be less than 0 or greater than 150.";}}return null;}}private int age;public int Age { get => age; set => SetProperty(ref age, value); }public string Error => null;
}
public class PersonName : ViewModelBase, INotifyDataErrorInfo
{private string name = string.Empty;public string Name {get => name;set{bool valid = true;foreach (char c in value){if (!char.IsLetterOrDigit(c)){valid = false;break;}}if(!valid){List<string> errors = new List<string> ();errors.Add("The Name can only contain letters and numbers.");SetErrors("Name", errors);}else{ClearErrors("Name");}SetProperty(ref name, value);}}private Dictionary<string, List<string>> errors = new Dictionary<string, List<string>>();private void SetErrors(string propertyName, List<string> propertyErrors){errors.Remove(propertyName);errors.Add(propertyName, propertyErrors);if (ErrorsChanged != null)ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));}private void ClearErrors(string propertyName){errors.Remove(propertyName);if(ErrorsChanged != null)ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));}public bool HasErrors{get { return errors.Count > 0; }}public event EventHandler<DataErrorsChangedEventArgs>? ErrorsChanged;public IEnumerable GetErrors(string? propertyName){if(!string.IsNullOrEmpty(propertyName) && errors.ContainsKey(propertyName)){return errors[propertyName];}return new List<string>();}
}
public class PersonPrice : ViewModelBase, INotifyPropertyChanged
{private decimal price = 0;public decimal Price{get => price; set{SetProperty(ref price, value);}}
}public class PriceRule : ValidationRule
{private decimal min = 0;private decimal max = decimal.MaxValue;public decimal Min{get => min;set => min = value;}public decimal Max{get => max;set => max = value;}public override ValidationResult Validate(object value, CultureInfo cultureInfo){decimal price = 0;try{if (((string)value).Length > 0){price = Decimal.Parse((string)value, NumberStyles.Any, cultureInfo);}}catch{return new ValidationResult(false, "Illegal characters.");}if (price < min || price > max){return new ValidationResult(false, "Not in the range " + Min + " to " + Max + ".");}else{return new ValidationResult(true, null);}}
}public class Person : ViewModelBase
{private string name = string.Empty;public string Name { get=>name; set=> SetProperty(ref name, value); }private string id = string.Empty;public string ID { get => id; set => SetProperty(ref id, value); }
}
public class PersonRule : ValidationRule
{public override ValidationResult Validate(object value, CultureInfo cultureInfo){BindingGroup bindingGroup = (BindingGroup)value;Person? person = bindingGroup.Items[0] as Person;string name = (string)bindingGroup.GetValue(person, "Name");string id = (string)bindingGroup.GetValue(person, "ID");if ((name == "") && (id == "")){return new ValidationResult(false, "A Person requires a ID or Name.");}else{return new ValidationResult(true, null);}}
}
public partial class MainWindow : Window
{public MainWindow(){InitializeComponent();myGrid.DataContext = this;}public PersonAge PersonAge { get; set; } = new PersonAge();public PersonName PersonName { get; set; } = new PersonName();public PersonPrice PersonPrice { get; set; } = new PersonPrice();public Person Person { get; set; } = new Person();private void Grid_LostFocus(object sender, RoutedEventArgs e){personValidation.CommitEdit();}
}

相关文章:

WPF 数据验证

WPF提供了能与数据绑定系统紧密协作的验证功能。提供了两种方法用于捕获非法值&#xff1a; 1、可在数据对象中引发错误。 可以在设置属性时抛出异常&#xff0c;通常WPF会忽略所有在设置属性时抛出的异常&#xff0c;但可以进行配置&#xff0c;从而显示更有帮助的可视化指示…...

IDEA的maven想显示层级关系,而非平级

新版和旧版的IDEA的位置不一样&#xff0c;2023.2.1的版本在右上角的“” 这个位置 如图所示&#xff1a; 然后点击按模块分组&#xff1a;...

(八)k8s实战-身份认证与权限

一、认证 User AccountsService Accounts Service Account 自动化&#xff1a; Service Account Admission ControllerToken ControllerService Account Controller 1、Service Account Admission Controller 通过 Admission Controller 插件来实现对 pod 修改&#xff0c…...

数学建模:TOPSIS分析

&#x1f506; 文章首发于我的个人博客&#xff1a;欢迎大佬们来逛逛 TOPSIS分析法 算法流程 假设有m个评价对象&#xff0c;n个评价指标&#xff0c;首先需要进行指标的正向化&#xff1a; 极大型极小型单点型区间型 然后对正向化后的矩阵进行标准化&#xff0c;得到 Z Z Z…...

【Qt学习】10 利用QSharedMemory实现单例运行

问题 让应用程序只有一个运行实例 QSharedMemory除了可以完成进程间通信&#xff0c;还可以实现应用程序单例化。 解法 首先&#xff0c;看看QSharedMemory的几个函数&#xff1a; 1、QSharedMemory(const QString &key, QObject *parent Q_NULLPTR)构造函数 该构造函数…...

FPGA应用于图像处理

FPGA应用于图像处理 FPGA&#xff08;Field-Programmable Gate Array&#xff09;直译过来就是现场可编程门阵列。是一种可以编程的逻辑器件&#xff0c;具有高度的灵活性&#xff0c;可以根据具体需求就像编程来实现不同的功能。 FPGA器件属于专用的集成电流中的一种半定制电…...

vscode python 无法引入上层目录解决

在vscode 中.vscode 配置如下 { // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid830387 “version”: “0.2.0”, “configurati…...

[开发|java] java list 取某个属性最大的项

示例代码: import java.util.*;class Person {private String name;private int age;public Person(String name, int age) {this.name name;this.age age;}public int getAge() {return age;} }public class Main {public static void main(String[] args) {List<Person…...

关闭浏览器的跨域校验

首发博客地址 问题描述 当你访问资源失败&#xff0c;并遇到以下类似提示时&#xff1a; Access to script at 资源路径 from origin null has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, isolated-app, chrom…...

USRP 简介,对于NI软件无线电你所需要了解的一切

什么是 USRP 通用软件无线电外设( USRP ) 是由 Ettus Research 及其母公司National Instruments设计和销售的一系列软件定义无线电。USRP 产品系列由Matt Ettus领导的团队开发&#xff0c;被研究实验室、大学和业余爱好者广泛使用。 大多数 USRP 通过以太网线连接到主机&…...

RTE_Driver驱动框架和Keil下开发需要支持的xxx_DFP软件包分析

1.RTE_Driver驱动框架 RTE_Driver代表"Run-Time Environment Driver"&#xff0c;是Keil MDK&#xff08;Microcontroller Development Kit&#xff09;中的一个概念。Keil MDK是一种用于嵌入式系统开发的集成开发环境&#xff0c;提供了开发、编译、调试等一系列工具…...

ImportError: Cannot load dynamic library. Did you compile LSD?

1、问题描述 >>> import pylsd2 Traceback (most recent call last):File "<stdin>", line 1, in <module>File "/data/data/wangzy-p-wangzy-p3-volume-pvc-0fee40a7-7013-49b4-8cfb-b4ab0394165b/.conda/envs/paddle/lib/python3.8/sit…...

音频应用编程

目录 ALSA 概述alsa-lib 简介sound 设备节点alsa-lib 移植编写一个简单地alsa-lib 应用程序一些基本概念打开PCM 设备设置硬件参数读/写数据示例代码之PCM 播放示例代码值PCM 录音 使用异步方式PCM 播放示例-异步方式PCM 录音示例-异步方式 使用poll()函数使用poll I/O 多路复用…...

软件测试/测试开发丨Python 学习笔记 之 链表

点此获取更多相关资料 本文为霍格沃兹测试开发学社学员学习笔记分享 原文链接&#xff1a;https://ceshiren.com/t/topic/26458 链表与数组的区别 复杂度分析 时间复杂度数组链表插入删除O(n)O(1)随机访问O(1)O(n) 其他角度分析 内存连续&#xff0c;利用CPU的机制&#xff0…...

Matlab 使用经验分享(常用函数介绍;矩阵常见计算)

Matlab 使用经验分享 大家好&#xff01;最近有很多朋友询问我关于 Matlab 的使用&#xff0c;于是我决定写一篇博客来分享一下我的经验。对于数学和编程爱好者来说&#xff0c;Matlab 是一个非常有用的工具。我自己在数学实验和数学建模竞赛中也经常使用它。那么&#xff0c;…...

软件工程(十七) 行为型设计模式(三)

1、观察者模式 简要说明 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新 速记关键字 联动,广播消息 类图如下 基于上面的类图,我们来实现一个监听器。类图中的Subject对应我们的被观察对象接口(IObservable),…...

在抖音中使用语聚AI,实现自动回复用户视频评论、私信问答

您可以通过集简云数据流程&#xff0c;将语聚AI助手集成到抖音视频评论、抖音私信&#xff0c;实现自动回复用户视频评论、私信问答&#xff0c;大大提升账号互动与运营效率。 效果如下&#xff1a; 自动化流程&#xff1a; ● 抖音普通号评论对接语聚AI&#xff08;点击可一…...

pyqt5-快捷键QShortcut

import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import *""" 下面示例揭示了&#xff0c;当关键字绑定的控件出现的时候&#xff0c;快捷键才管用&#xff0c; 绑定的控件没有出现的时候快捷键无效 """…...

匿名函数( lambda 表达式)

在 C 中&#xff0c;匿名函数也被称为 lambda 表达式。C11 引入了 lambda 表达式&#xff0c;使得在需要函数对象&#xff08;函数符&#xff09;的地方可以使用匿名函数来代替。 lambda 表达式的基本语法如下&#xff1a; [capture list] (parameter list) -> return typ…...

基于SSM的汽车维修管理系统——LW模板

摘要 随着人们生活水平的不断提高&#xff0c;私家车的数量正在逐年攀升。这带动了汽车维修行业的发展。越来越多的汽车维修厂如雨后春笋般涌现。同时&#xff0c;维修厂的业务操作产生了庞大的数据&#xff0c;这给汽车维修厂工作人员的数据管理提出了新的要求&#xff0c;他们…...

【项目实战】通过多模态+LangGraph实现PPT生成助手

PPT自动生成系统 基于LangGraph的PPT自动生成系统&#xff0c;可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析&#xff1a;自动解析Markdown文档结构PPT模板分析&#xff1a;分析PPT模板的布局和风格智能布局决策&#xff1a;匹配内容与合适的PPT布局自动…...

在Ubuntu中设置开机自动运行(sudo)指令的指南

在Ubuntu系统中&#xff0c;有时需要在系统启动时自动执行某些命令&#xff0c;特别是需要 sudo权限的指令。为了实现这一功能&#xff0c;可以使用多种方法&#xff0c;包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法&#xff0c;并提供…...

C# 类和继承(抽象类)

抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...

智能仓储的未来:自动化、AI与数据分析如何重塑物流中心

当仓库学会“思考”&#xff0c;物流的终极形态正在诞生 想象这样的场景&#xff1a; 凌晨3点&#xff0c;某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径&#xff1b;AI视觉系统在0.1秒内扫描包裹信息&#xff1b;数字孪生平台正模拟次日峰值流量压力…...

在WSL2的Ubuntu镜像中安装Docker

Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包&#xff1a; for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...

关于easyexcel动态下拉选问题处理

前些日子突然碰到一个问题&#xff0c;说是客户的导入文件模版想支持部分导入内容的下拉选&#xff0c;于是我就找了easyexcel官网寻找解决方案&#xff0c;并没有找到合适的方案&#xff0c;没办法只能自己动手并分享出来&#xff0c;针对Java生成Excel下拉菜单时因选项过多导…...

libfmt: 现代C++的格式化工具库介绍与酷炫功能

libfmt: 现代C的格式化工具库介绍与酷炫功能 libfmt 是一个开源的C格式化库&#xff0c;提供了高效、安全的文本格式化功能&#xff0c;是C20中引入的std::format的基础实现。它比传统的printf和iostream更安全、更灵活、性能更好。 基本介绍 主要特点 类型安全&#xff1a…...

【51单片机】4. 模块化编程与LCD1602Debug

1. 什么是模块化编程 传统编程会将所有函数放在main.c中&#xff0c;如果使用的模块多&#xff0c;一个文件内会有很多代码&#xff0c;不利于组织和管理 模块化编程则是将各个模块的代码放在不同的.c文件里&#xff0c;在.h文件里提供外部可调用函数声明&#xff0c;其他.c文…...

GB/T 43887-2024 核级柔性石墨板材检测

核级柔性石墨板材是指以可膨胀石墨为原料、未经改性和增强、用于核工业的核级柔性石墨板材。 GB/T 43887-2024核级柔性石墨板材检测检测指标&#xff1a; 测试项目 测试标准 外观 GB/T 43887 尺寸偏差 GB/T 43887 化学成分 GB/T 43887 密度偏差 GB/T 43887 拉伸强度…...

AT模式下的全局锁冲突如何解决?

一、全局锁冲突解决方案 1. 业务层重试机制&#xff08;推荐方案&#xff09; Service public class OrderService {GlobalTransactionalRetryable(maxAttempts 3, backoff Backoff(delay 100))public void createOrder(OrderDTO order) {// 库存扣减&#xff08;自动加全…...