【SpringCloud-Seata客户端源码分析01】

文章目录

  • 启动seata客户端
    • 1.导入依赖
    • 2.自动装配
  • 发送请求的核心方法
  • 客户端开启事务的核心流程
  • 服务端分布式事务的处理机制

启动seata客户端

1.导入依赖

<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.12.RELEASE</version></parent><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><!-- 选择SpringCloudAlibaba版本的时候,一定要参考官网的建议,否则会有问题 --><spring-cloud.version>Hoxton.SR12</spring-cloud.version><spring-cloud-alibaba.version>2.2.9.RELEASE</spring-cloud-alibaba.version></properties><dependencyManagement><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${spring-cloud.version}</version><type>pom</type><scope>import</scope></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>${spring-cloud-alibaba.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><dependencies><!-- nacos服务注册与发现 --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!--引入seata--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-seata</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>

客户端启动流程图
在这里插入图片描述

2.自动装配

自动装配的核心类
在这里插入图片描述
在SeataAutoConfiguration我们找到对应注入的类GlobalTransactionScanner,通过名称我们应该推算出,他应该是对应@GlobalTransaction进行扫描,然后注入到容器
我们先看下GlobalTransactionScanner类继承的类
在这里插入图片描述
创建的代理类

  protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {if (!this.doCheckers(bean, beanName)) {return bean;} else {try {synchronized(PROXYED_SET) {//如果代理已经存在直接返回bean对象if (PROXYED_SET.contains(beanName)) {return bean;} else {this.interceptor = null;//判断是否是TCC模式if (TCCBeanParserUtils.isTccAutoProxy(bean, beanName, this.applicationContext)) {TCCBeanParserUtils.initTccFenceCleanTask(TCCBeanParserUtils.getRemotingDesc(beanName), this.applicationContext);this.interceptor = new TccActionInterceptor(TCCBeanParserUtils.getRemotingDesc(beanName));ConfigurationCache.addConfigListener("service.disableGlobalTransaction", new ConfigurationChangeListener[]{(ConfigurationChangeListener)this.interceptor});} else {Class<?> serviceInterface = SpringProxyUtils.findTargetClass(bean);Class<?>[] interfacesIfJdk = SpringProxyUtils.findInterfaces(bean);//判断是否是被注解@globalTransactional或者@GlobalLock代理的类if (!this.existsAnnotation(new Class[]{serviceInterface}) && !this.existsAnnotation(interfacesIfJdk)) {return bean;}//创建globalTransactionalInterceptor拦截器if (this.globalTransactionalInterceptor == null) {this.globalTransactionalInterceptor = new GlobalTransactionalInterceptor(this.failureHandlerHook);ConfigurationCache.addConfigListener("service.disableGlobalTransaction", new ConfigurationChangeListener[]{(ConfigurationChangeListener)this.globalTransactionalInterceptor});}this.interceptor = this.globalTransactionalInterceptor;}LOGGER.info("Bean[{}] with name [{}] would use interceptor [{}]", new Object[]{bean.getClass().getName(), beanName, this.interceptor.getClass().getName()});if (!AopUtils.isAopProxy(bean)) {bean = super.wrapIfNecessary(bean, beanName, cacheKey);} else {AdvisedSupport advised = SpringProxyUtils.getAdvisedSupport(bean);//获取拦截器Advisor[] advisor = this.buildAdvisors(beanName, this.getAdvicesAndAdvisorsForBean((Class)null, (String)null, (TargetSource)null));Advisor[] var8 = advisor;int var9 = advisor.length;for(int var10 = 0; var10 < var9; ++var10) {Advisor avr = var8[var10];int pos = this.findAddSeataAdvisorPosition(advised, avr);advised.addAdvisor(pos, avr);}}PROXYED_SET.add(beanName);return bean;}}} catch (Exception var14) {throw new RuntimeException(var14);}}}

事务管理器TM,资源管理器RM初始化实在实例化之后进行这个是在GlobalTransactionScanner
继承的InitializingBean的afterPropertiesSet方法中实现InitializingBean接口的使用
实现InitializingBean接口的bean,在Spring容器初始化并设置所有bean属性后,会调用其afterPropertiesSet()方法。这通常用于在bean的属性全部设置完毕后需要进行的一些自定义初始化工作,例如验证属性或建立资源连接。

public void afterPropertiesSet() {//判断是否开启事务if (this.disableGlobalTransaction) {if (LOGGER.isInfoEnabled()) {LOGGER.info("Global transaction is disabled.");}//添加监听器  ConfigurationCache.addConfigListener("service.disableGlobalTransaction", new ConfigurationChangeListener[]{this});} else {//CAS将初始化置换成Trueif (this.initialized.compareAndSet(false, true)) {//初始化客户端this.initClient();}}}

初始化客户端的代码

private void initClient() {if (LOGGER.isInfoEnabled()) {LOGGER.info("Initializing Global Transaction Clients ... ");}if ("my_test_tx_group".equals(this.txServiceGroup)) {LOGGER.warn("the default value of seata.tx-service-group: {} has already changed to {} since Seata 1.5, please change your default configuration as soon as possible and we don't recommend you to use default tx-service-group's value provided by seata", "my_test_tx_group", "default_tx_group");}if (!StringUtils.isNullOrEmpty(this.applicationId) && !StringUtils.isNullOrEmpty(this.txServiceGroup)) {//初始化TM客户端TMClient.init(this.applicationId, this.txServiceGroup, accessKey, secretKey);if (LOGGER.isInfoEnabled()) {LOGGER.info("Transaction Manager Client is initialized. applicationId[{}] txServiceGroup[{}]", this.applicationId, this.txServiceGroup);}//初始化RM客户端RMClient.init(this.applicationId, this.txServiceGroup);if (LOGGER.isInfoEnabled()) {LOGGER.info("Resource Manager is initialized. applicationId[{}] txServiceGroup[{}]", this.applicationId, this.txServiceGroup);}if (LOGGER.isInfoEnabled()) {LOGGER.info("Global Transaction Clients are initialized. ");}this.registerSpringShutdownHook();} else {throw new IllegalArgumentException(String.format("applicationId: %s, txServiceGroup: %s", this.applicationId, this.txServiceGroup));}}

TM和RM底层都是用的Netty进行的通讯
TM的初始化

    public static void init(String applicationId, String transactionServiceGroup, String accessKey, String secretKey) {TmNettyRemotingClient tmNettyRemotingClient = TmNettyRemotingClient.getInstance(applicationId, transactionServiceGroup, accessKey, secretKey);tmNettyRemotingClient.init();}

RM的初始化

    public static void init(String applicationId, String transactionServiceGroup) {RmNettyRemotingClient rmNettyRemotingClient = RmNettyRemotingClient.getInstance(applicationId, transactionServiceGroup);rmNettyRemotingClient.setResourceManager(DefaultResourceManager.get());rmNettyRemotingClient.setTransactionMessageHandler(DefaultRMHandler.get());rmNettyRemotingClient.init();}

发送请求的核心方法

在这里插入图片描述

 public Object invoke(final MethodInvocation methodInvocation) throws Throwable {//利用AOP获取代理类Class<?> targetClass = methodInvocation.getThis() != null ? AopUtils.getTargetClass(methodInvocation.getThis()) : null;//获取代理的方法Method specificMethod = ClassUtils.getMostSpecificMethod(methodInvocation.getMethod(), targetClass);if (specificMethod != null && !specificMethod.getDeclaringClass().equals(Object.class)) {Method method = BridgeMethodResolver.findBridgedMethod(specificMethod);//判断此方法是否被@GlobalTranscation修饰GlobalTransactional globalTransactionalAnnotation = (GlobalTransactional)this.getAnnotation(method, targetClass, GlobalTransactional.class);//判断此方法是否被注解@GlobalLock修饰GlobalLock globalLockAnnotation = (GlobalLock)this.getAnnotation(method, targetClass, GlobalLock.class);boolean localDisable = this.disable || degradeCheck && degradeNum >= degradeCheckAllowTimes;if (!localDisable) {if (globalTransactionalAnnotation != null || this.aspectTransactional != null) {AspectTransactional transactional;if (globalTransactionalAnnotation != null) {transactional = new AspectTransactional(globalTransactionalAnnotation.timeoutMills(), globalTransactionalAnnotation.name(), globalTransactionalAnnotation.rollbackFor(), globalTransactionalAnnotation.rollbackForClassName(), globalTransactionalAnnotation.noRollbackFor(), globalTransactionalAnnotation.noRollbackForClassName(), globalTransactionalAnnotation.propagation(), globalTransactionalAnnotation.lockRetryInterval(), globalTransactionalAnnotation.lockRetryTimes());} else {transactional = this.aspectTransactional;}//被@GlobalTranstional修饰的方法进入此方法return this.handleGlobalTransaction(methodInvocation, transactional);}//被@GlobalLock修饰的方法进入此类if (globalLockAnnotation != null) {return this.handleGlobalLock(methodInvocation, globalLockAnnotation);}}}return methodInvocation.proceed();}

事务的核心方法TransactionalTemplate的excute方法

 public Object execute(TransactionalExecutor business) throws Throwable {//获取事务信息TransactionInfo txInfo = business.getTransactionInfo();//如果为空直接抛异常if (txInfo == null) {throw new ShouldNeverHappenException("transactionInfo does not exist");} else {//创建或者获取全局事务标志xidGlobalTransaction tx = GlobalTransactionContext.getCurrent();//获取事务的传播机制Propagation propagation = txInfo.getPropagation();SuspendedResourcesHolder suspendedResourcesHolder = null;try {Object var6;switch(propagation) {case NOT_SUPPORTED:if (this.existingTransaction(tx)) {suspendedResourcesHolder = tx.suspend();}var6 = business.execute();return var6;case REQUIRES_NEW:if (this.existingTransaction(tx)) {suspendedResourcesHolder = tx.suspend();tx = GlobalTransactionContext.createNew();}break;case SUPPORTS:if (this.notExistingTransaction(tx)) {var6 = business.execute();return var6;}case REQUIRED:break;case NEVER:if (this.existingTransaction(tx)) {throw new TransactionException(String.format("Existing transaction found for transaction marked with propagation 'never', xid = %s", tx.getXid()));}var6 = business.execute();return var6;case MANDATORY:if (this.notExistingTransaction(tx)) {throw new TransactionException("No existing transaction found for transaction marked with propagation 'mandatory'");}break;default:throw new TransactionException("Not Supported Propagation:" + propagation);}//如果事务的xid为空,则重新创建一个新的if (tx == null) {tx = GlobalTransactionContext.createNew();}//获取全局锁配置GlobalLockConfig previousConfig = this.replaceGlobalLockConfig(txInfo);try {//开启全局事务this.beginTransaction(txInfo, tx);Object rs;Object ex;try {rs = business.execute();} catch (Throwable var17) {ex = var17;this.completeTransactionAfterThrowing(txInfo, tx, var17);throw var17;}this.commitTransaction(tx);ex = rs;return ex;} finally {this.resumeGlobalLockConfig(previousConfig);this.triggerAfterCompletion();this.cleanUp();}} finally {if (suspendedResourcesHolder != null) {tx.resume(suspendedResourcesHolder);}}}}

客户端开启事务的核心流程

在这里插入图片描述

    private void beginTransaction(TransactionInfo txInfo, GlobalTransaction tx) throws ExecutionException {try {//执行开启事务前的操作this.triggerBeforeBegin();//开启事务tx.begin(txInfo.getTimeOut(), txInfo.getName());//执行开启事务后的操作this.triggerAfterBegin();} catch (TransactionException var4) {throw new ExecutionException(tx, var4, Code.BeginFailure);}}
 public void begin(int timeout, String name) throws TransactionException {//判断当前的角色是不是Launcherif (this.role != GlobalTransactionRole.Launcher) {//不是判断当前的xid是否为nullthis.assertXIDNotNull();if (LOGGER.isDebugEnabled()) {LOGGER.debug("Ignore Begin(): just involved in global transaction [{}]", this.xid);}} else {//不是判断当前的xid是否为nullthis.assertXIDNull();String currentXid = RootContext.getXID();if (currentXid != null) {throw new IllegalStateException("Global transaction already exists, can't begin a new global transaction, currentXid = " + currentXid);} else {//开启事务,请求后台服务,返回xidthis.xid = this.transactionManager.begin((String)null, (String)null, name, timeout);//将事务状态置为开启this.status = GlobalStatus.Begin;//绑定xidRootContext.bind(this.xid);if (LOGGER.isInfoEnabled()) {LOGGER.info("Begin new global transaction [{}]", this.xid);}}}}

服务端分布式事务的处理机制

在这里插入图片描述

 protected void doGlobalBegin(GlobalBeginRequest request, GlobalBeginResponse response, RpcContext rpcContext)throws TransactionException {/*** 调用core.begin开启事务,并持久化* 给response设置全局事务编号XID*/response.setXid(core.begin(rpcContext.getApplicationId(), rpcContext.getTransactionServiceGroup(),request.getTransactionName(), request.getTimeout()));if (LOGGER.isInfoEnabled()) {LOGGER.info("Begin new global transaction applicationId: {},transactionServiceGroup: {}, transactionName: {},timeout:{},xid:{}",rpcContext.getApplicationId(), rpcContext.getTransactionServiceGroup(), request.getTransactionName(), request.getTimeout(), response.getXid());}}
    public String begin(String applicationId, String transactionServiceGroup, String name, int timeout)throws TransactionException {//key1:创建全局会话,这里面已经创建了全局事务IdGlobalSession session = GlobalSession.createGlobalSession(applicationId, transactionServiceGroup, name,timeout);MDC.put(RootContext.MDC_KEY_XID, session.getXid());// 添加事务生命周期监听器session.addSessionLifecycleListener(SessionHolder.getRootSessionManager());//2、 开启事务,这里有事务持久话的一个机制session.begin();// transaction start event//  发送事务开启事件MetricsPublisher.postSessionDoingEvent(session, false);// 返回全局事务IDreturn session.getXid();}
    @Overridepublic void begin() throws TransactionException {this.status = GlobalStatus.Begin;this.beginTime = System.currentTimeMillis();this.active = true;for (SessionLifecycleListener lifecycleListener : lifecycleListeners) {// 在这里处理lifecycleListener.onBegin(this);}}
private void writeSession(LogOperation logOperation, SessionStorable sessionStorable) throws TransactionException {// 将事务信息写入数据库if (!transactionStoreManager.writeSession(logOperation, sessionStorable)) {if (LogOperation.GLOBAL_ADD.equals(logOperation)) {throw new GlobalTransactionException(TransactionExceptionCode.FailedWriteSession,"Fail to store global session");} else if (LogOperation.GLOBAL_UPDATE.equals(logOperation)) {throw new GlobalTransactionException(TransactionExceptionCode.FailedWriteSession,"Fail to update global session");} else if (LogOperation.GLOBAL_REMOVE.equals(logOperation)) {throw new GlobalTransactionException(TransactionExceptionCode.FailedWriteSession,"Fail to remove global session");} else if (LogOperation.BRANCH_ADD.equals(logOperation)) {throw new BranchTransactionException(TransactionExceptionCode.FailedWriteSession,"Fail to store branch session");} else if (LogOperation.BRANCH_UPDATE.equals(logOperation)) {throw new BranchTransactionException(TransactionExceptionCode.FailedWriteSession,"Fail to update branch session");} else if (LogOperation.BRANCH_REMOVE.equals(logOperation)) {throw new BranchTransactionException(TransactionExceptionCode.FailedWriteSession,"Fail to remove branch session");} else {throw new BranchTransactionException(TransactionExceptionCode.FailedWriteSession,"Unknown LogOperation:" + logOperation.name());}}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/34992.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【DS Solutions】一个反欺诈产品的进化,Stripe Radar

Stripe Radar 是 Stripe 提供的一项防欺诈服务&#xff0c;它利用机器学习技术来帮助商家检测和阻止信用卡欺诈行为。这篇文章是Stripe公司关于其反欺诈解决方案Stripe Radar的构建过程的介绍。文章从Stripe的防欺诈团队工程师的角度出发&#xff0c;详细讲述了Stripe Radar的工…

车辆数据的提取、定位和融合 精确车辆定位(其三.一 共十二篇)随机复合

第一篇&#xff1a; System Introduction 第二篇&#xff1a;State of the Art 第三篇&#xff1a;localization 第四篇&#xff1a;Submapping and temporal weighting 第五篇&#xff1a;Mapping of Point-shaped landmark data 第六篇&#xff1a;Clustering of landma…

礼让,不是一昧地退让,而是表达我们的素养、品德

礼 / 让&#xff0c;发心是文明相处&#xff0c;互助互让&#xff0c;是君子之交

UnityShader SDF有向距离场简单实现

UnityShader SDF有向距离场简单实现 前言项目场景布置连连看画一个圆复制一个圆计算修改shader参数 鸣谢 前言 突然看到B站的一个教程&#xff0c;还不错&#xff0c;记录一下 项目 场景布置 使用ASE连连看&#xff0c;所以先要导入Amplify Shader Editor 连连看 画一个…

面试-JMM的内存可见性

1.JAVA内存模型 分析&#xff1a; 由于JVM运行程序的实体是线程&#xff0c;而每个线程创建时&#xff0c;JVM都会 为其创建一个工作内存(栈空间),用于存储线程私有的数据。而java内存模型中规定所有变量都存储在主内存中。主内存是共享内存区域&#xff0c;所有线程都可以访问…

Python-PDF文件密码破解小工具

背景 经常从网络上下载的PDF笔记被加了密&#xff0c;在自己学习的过程中想要添加书签却因为没有密码无法添加&#xff0c;所以通过Python实现一个解密小工具&#xff0c;亲测大多数密码都可以破解。 代码 import os import tkinter as tk from tkinter import filedialog #…

你还不知道Modbus RTU???

1. 什么是Modbus RTU Modbus RTU&#xff08;Remote Terminal Unit&#xff09;是Modbus通信协议的一种变种&#xff0c;用于串行通信。它是一种常见的工业控制系统通信协议&#xff0c;通常用于采集传感器数据、控制执行器和监控设备状态。Modbus RTU采用二进制编码&#xff0…

【深度学习】实现基于MNIST数据集的TensorFlow/Keras深度学习案例

基于TensorFlow/Keras的深度学习案例 实现基于MNIST数据集的TensorFlow/Keras深度学习案例0. 什么是深度学习&#xff1f;1. TensorFlow简介2. Keras简介3. 安装TensorFlow前的注意事项4. 安装Anaconda3及搭建TensorFlow环境1&#xff09; 下载安装Anaconda Navigator2&#xf…

go语言day06 数组 切片

数组 : 定长且元素类型一致,在索引逻辑上连续存储,数组的内存地址是存储的第一个元素的内存地址 几种创建方式: 仅声明 var nums [ 3 ] int 声明并赋值 var nums [ 2 ] string {"武沛齐","alex"} 声明并按下标赋值 var nums [ 3 ] int {0:88,2:1 } 省略…

ffmpeg+nginx+video实现rtsp流转hls流,web页面播放

项目场景&#xff1a; 最近调试海康摄像头需要将rtsp流在html页面播放,因为不想去折腾推拉流&#xff0c;所以我选择ffmpeg转hls流&#xff0c;nginx转发&#xff0c;html直接访问就好了 1.首先要下载nginx和ffmpeg 附上下载地址&#xff1a; nginx nginx news ffmpeg htt…

HttpServletRequest・getContentLeng・getContentType区别

getContentLength()&#xff1a; 获取客户端发送到服务器的HTTP请求主体内容的字节数&#xff08;长度&#xff09; 如果请求没有正文内容&#xff08;如GET&#xff09;&#xff0c;或者请求头中没有包含Content-Length字段&#xff0c;则该方法返回 -1 getContentType()&am…

eclipse中svn从分支合并到主干及冲突解决

1、将分支先commit&#xff0c;然后再update&#xff0c;然后再clean一下&#xff0c;将项目多余的target都清理掉。 2、将branches切换到trunk 3、工程上右键-》Team-》合并&#xff08;或Merge&#xff09; 4、默认选项&#xff0c;点击Next 5、有未提交的改动&#xff0c;…

文献阅读:通过双线性建模来破译神经元类型连接的遗传密码

文献介绍 文献题目 Deciphering the genetic code of neuronal type connectivity through bilinear modeling 研究团队 Mu Qiao&#xff08;美国加州理工学院&#xff09; 发表时间 2024-06-10 发表期刊 eLife 影响因子 7.7 DOI 10.7554/eLife.91532.3 摘要 了解不同神经元…

打造爆款秘籍:阿里巴巴国际站测评补单优势全攻略

在阿里巴巴国际站&#xff0c;买家复购率和其他销售指标是衡量产品市场潜力和销售成功与否的关键指标。当系统评估出产品具有巨大的市场潜力时&#xff0c;它会相应地增加对产品的流量支持&#xff1b;反之&#xff0c;如果潜力不足&#xff0c;产品的排名将会受到影响&#xf…

使用 Spring Boot 3.x 与图形学技术,添加电子印章防伪特征

使用 Spring Boot 3.x 与图形学技术,添加电子印章防伪特征 在电子办公和无纸化办公日益普及的今天,电子印章的使用越来越广泛。然而,如何确保电子印章的安全性和防伪能力成为了一个亟待解决的问题。本文将通过 Spring Boot 3.x 和图形学技术,深入探讨如何为电子印章添加防…

Redis-实战篇-什么是缓存-添加redis缓存

文章目录 1、什么是缓存2、添加商户缓存3、前端接口4、ShopController.java5、ShopServiceImpl.java6、RedisConstants.java7、查看Redis Desktop Manager 1、什么是缓存 缓存就是数据交换的缓冲区&#xff08;称为Cache&#xff09;&#xff0c;是存贮数据的临时地方&#xff…

01数字电子技术基础

第一节课&#xff1a;introduction 导论 决定了这门课的学习方法、学习内容、一个大概的把握、虽不是具体的技术&#xff0c;不是细节&#xff0c;但是这是一节思想 每门课都重要&#xff0c;但侧重点不同。 学习前人的思想和营养&#xff0c;为自己所用。 1.课程性质&#x…

【Text2SQL 论文】MAGIC:为 Text2SQL 任务自动生成 self-correction guideline

论文&#xff1a;MAGIC: Generating Self-Correction Guideline for In-Context Text-to-SQL ⭐⭐⭐ 莱顿大学 & Microsoft, arXiv:2406.12692 一、论文速读 DIN-SQL 模型中使用了一个 self-correction 模块&#xff0c;他把 LLM 直接生成的 SQL 带上一些 guidelines 的 p…

前端:HTML、CSS、JavaScript 代码注释 / 注释与代码规范

一、HTML 行内注释 HTML注释是在HTML代码中添加说明和解释的一种方法&#xff0c;这些注释不会被浏览器渲染或显示在页面上&#xff0c;而是被浏览器忽略。HTML注释对于代码的可读性、可维护性和团队协作非常重要。 1.1、HTML注释的语法 HTML注释的语法是以<!--开始&…

大学生必备!GitHub星标破千的matlab教程(从新手到骨灰级玩家)

MATLAB&#xff08;Matrix Laboratory&#xff09;是MathWorks公司推出的用于算法开发、数据可视化、数据分析以及数值计算的高级技术计算语言和交互式环境的商业数学软件。 MATLAB具有数值分析、数值和符号计算、工程与科学绘图、数字图像处理、财务与金融工程等功能&#xf…