编码技巧——事务提交后执行

2023-10-26

日常开发中,一些诸如"先读后写"、"先写A再写B"、"先写A再执行B"的场景,一般都会用到事务;这里的事务指的是本地事务,如果涉及RPC,一般我们通过异步补偿来保证最终一致性;本篇例举2个使用事务"先写A再执行B"的场景;

1. 订单场景

(1)处理支付回调

这里主要是收到支付系统的结果回调后执行的逻辑,包括参数校验,业务订单校验,幂等处理,订单(成功/失败)状态更新;注意这里涉及"先查再写"的场景,外层使用了@Transaction注解,让Spring的TransactionSynchronizationManager来管理事务;

    /**
     * 处理支付回调
     *
     * @param payResultReqDTO
     * @return
     */
    @Transactional
    @Override
    public boolean handlePayResult(PayResultReqDTO payResultReqDTO) {
        log.warn("[paySystem callback]handlePayResult [payResultReqDTO={}]", JSON.toJSONString(payResultReqDTO));
        try {
            // 基础参数校验
            boolean checkPayResultReq = checkPayResultReq(payResultReqDTO);
            if (!checkPayResultReq) {
                return false;
            }
            // 判断支付状态
            String tradeStatus = payResultReqDTO.getTradeStatus();
            // tradeStatus不是终态,返回处理失败
            if (!OrderConstants.TRADE_STATUS_SUCCESS.equals(tradeStatus) && !OrderConstants.TRADE_STATUS_FAIL.equals(tradeStatus)) {
                log.warn("[paySystem callback]tradeStatus is not 0000/0002! payResultReqDTO={}", JSON.toJSONString(payResultReqDTO));
                return false;
            }
            // 查询本地订单
            MemberOrderDO orderDO = memberOrderDAO.selectByOrderNo(payResultReqDTO.getCpOrderNumber());
            if (orderDO == null) {
                log.warn("[paySystem callback]order not found in DB! payResultReqDTO={}", JSON.toJSONString(payResultReqDTO));
                return false;
            }
            // (已经处理过的)支付成功或失败不处理
            if (MemberOrderStatusEnum.PAID.getStatus().equals(orderDO.getStatus()) || MemberOrderStatusEnum.PAY_FAILED.getStatus().equals(orderDO.getStatus())) {
                log.warn("[paySystem callback]memberOrder has already been handled. [payResultReqDTO={}] status={}", JSON.toJSONString(payResultReqDTO), orderDO.getStatus());
                return true;
            }
            try {
                if (OrderConstants.TRADE_STATUS_SUCCESS.equals(tradeStatus)) {
                    // 处理支付金额回调,若实际支付金额未取到,则取订单金额
                    String payAmount = StringUtils.isBlank(payResultReqDTO.getTradeAmount()) ? payResultReqDTO.getOrderAmount() : payResultReqDTO.getTradeAmount();
                    return handleMemberOrderAfterPaySuc(orderDO, payResultReqDTO.getOrderNumber(), payAmount, payResultReqDTO.getPayTime(), payResultReqDTO.getDiscount(), buildPayExtraInfo(payResultReqDTO.getPayExtInfo()));
                } else if (OrderConstants.TRADE_STATUS_FAIL.equals(tradeStatus)) {
                    // 支付失败
                    return handleMemberOrderAfterPayFail(orderDO);
                }
            } catch (Exception e) {
                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
                log.error("[SERIOUS_DB][paySystem callback]handlePayResult error rollback! [payResultReqDTO={}] e:{}", JSON.toJSONString(payResultReqDTO), e);
            }
        } catch (Exception e) {
            log.error("[SERIOUS_BUSINESS][paySystem callback]handlePayResult error! [payResultReqDTO={}] e:{}", JSON.toJSONString(payResultReqDTO), e);
        }
        return false;
    }

(2)订单更新和消息投递(消息事务)

事务默认传递,因此订单状态更新的两个方法也被事务包裹,这里关注方法handleMemberOrderAfterPaySuc()和handleMemberOrderAfterPayFail;方法内主要包含业务订单状态的更新,以及将订单结果入库本地消息表(消息事务),然后再投递出去给下游系统;


    /**
     * 支付成功后执行操作
     */
    private boolean handleMemberOrderAfterPaySuc(MemberOrderDO memberOrderDO, String payOrderNo, String payAmount, String payTime, String deductAmount, PayExtraInfo payExtraInfo) {
        fillMemberOrderParam(memberOrderDO, payOrderNo, payAmount, payTime, deductAmount, payExtraInfo);
        memberOrderDAO.updatePayResult(memberOrderDO);
        Map<String, String> msgBody = buildPaySucMsgBody(memberOrderDO);
        MessageDeliverDO messageDeliverDO = buildMessageDeliverDO(memberOrderDO.getOrderNo(), MessageDeliverOrderTypeEnum.MEMBER_ORDER.getType(), msgBody);
        // 如果是代扣支付单则更新代扣单状态
        if (MemberOrderTypeEnum.WITHHOLD_ORDER.getType().equals(memberOrderDO.getOrderType())) {
            withholdDAO.updateOrderStatus(memberOrderDO.getOrderNo(), WithholdStatusEnum.PAY_SUC.getStatus());
        }
        messageDeliverDAO.insert(messageDeliverDO);
        // 优惠活动资格使用
        activityService.makeBenefitUsed(memberOrderDO.getOpenid(), memberOrderDO.getOrderNo());
        // 投递订单消息RMQ
        postProcessHandleMemberOrder(memberOrderDO);
        return true;
    }

    /**
     * 支付失败后执行操作
     */
    private boolean handleMemberOrderAfterPayFail(MemberOrderDO memberOrderDO) {
        memberOrderDO.setPayStatus(PayStatusEnum.PAY_FAILED.getStatus());
        memberOrderDO.setStatus(MemberOrderStatusEnum.PAY_FAILED.getStatus());
        memberOrderDAO.updatePayResult(memberOrderDO);
        // 如果是代扣支付单则更新代扣单状态
        if (MemberOrderTypeEnum.WITHHOLD_ORDER.getType().equals(memberOrderDO.getOrderType())) {
            withholdDAO.updateOrderStatus(memberOrderDO.getOrderNo(), WithholdStatusEnum.PAY_FAIL.getStatus());
            log.warn("withhold_pay_fail,[openid={} orderNo={}]", memberOrderDO.getOpenid(), memberOrderDO.getOrderNo());
        }
        // 订单状态是否为支付失败,若订单状态为支付失败,则处理微信纯签约,签约失败但支付成功的异常情况
        if (PayStatusEnum.PAY_FAILED.getStatus().equals(memberOrderDO.getPayStatus()) && StringUtils.isNotBlank(memberOrderDO.getAgreementNo())) {
            // 处理蓝卡微信纯签约逻辑
            processWeChatMiniAgreement(memberOrderDO);
        }
        // 优惠活动资格恢复
        activityService.makeBenefitAvailable(memberOrderDO.getOpenid(), memberOrderDO.getOrderNo());
        // 支付失败 不会向core发RMQ
        postProcessHandleMemberOrder(memberOrderDO);
        return true;
    }

(3)后置处理-投递消息

这里先入库本地消息表,然后再投递消息;注意这里使用TransactionSynchronizationManager这个类,并且使用了"提交后再执行"afterCommit'的;原因是:事务会传递到这里,如果不使用afterCommit,投递消息服务的调用(DUBBO调用消息中间件)也会被包裹在外层大事务中,所有的操作ready之后才会一起commit,而对于RPC来说,已经被执行了;

也就是说,极端情况,消息先发出去,订单状态和本地消息表才入库,而在入库之前,下游可能已经收到了这条发出去的消息通知,这是不允许的!例如下游系统收到消息后调用消息回执接口,而这时消息还没有入库本地消息表,就会出现回执失败;

    /**
     * 会员订单后置处理
     */
    private void postProcessHandleMemberOrder(MemberOrderDO memberOrderDO) {
        if (MemberOrderStatusEnum.PAID.getStatus().equals(memberOrderDO.getStatus())) {
            // fixme 要求一定要更新订单表和消息入库 才能发消息 极端情况RPC发消息调用瞬间 core回调 但是DB事务还没有提交完成 RPC应该剥离事务
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
                @Override
                public void afterCommit() {
                    // 支付成功发消息
                    Map<String, String> msgBody = buildPaySucMsgBody(memberOrderDO);
                    boolean sendMsgResult = notifyService.sendPayMsg(msgBody);
                    if (sendMsgResult) {
                        log.warn("send paySucMsg to core sus.[orderNo={} msg={}]", memberOrderDO.getOrderNo(), JSON.toJSONString(msgBody));
                    } else {
                        log.warn("send paySucMsg to core failed.[orderNo={} msg={}]", memberOrderDO.getOrderNo(), JSON.toJSONString(msgBody));
                    }
                    if (StringUtils.isNotBlank(memberOrderDO.getAgreementNo())) {
                        // 支付成功后,查询是否有需要投递的签约信息,用于处理微信纯签约,新用户购买,在收到支付回调之后,需要进行处理
                        MessageDeliverDO messageDeliverDO = messageDeliverDAO.queryByOrderAndType(memberOrderDO.getAgreementNo(), MessageDeliverOrderTypeEnum.AGREEMENT.getType());
                        if (messageDeliverDO != null) {
                            // 发送签约信息
                            Map<String, String> signedMsgBody = JSON.parseObject(messageDeliverDO.getMsgBody(), Map.class);
                            boolean sendSignedMsgResult = notifyService.sendSignMsg(signedMsgBody);
                            log.warn("pay_suc_send_signSucMsg_to_core_sus.[orderNo={} res={} msg={}]", memberOrderDO.getOrderNo(), sendSignedMsgResult, JSON.toJSONString(sendSignedMsgResult));
                        }
                    }
                }
            });
        }
    }

2. 物料更新并通知下游

开发中遇到的一个滥用事务的问题,原本的起义是,希望在活动计划物料审核通过后(落库后),将物料同步给下游(RMQ),并且调用通知服务告知相关人员;因此,在代码中使用事务@Transaction包裹业务逻辑,包括:(1)按照计划id查询计划、(2)查询后校验、(3)校验通过后更新DB中计划的状态、(4)更新成功后调用消息投递服务同步下游、(5)同时调用通知服务发送短信邮件给相关人员;乍一看好像没有问题,"先查后写"确实需要事务包裹,并且如果通知服务调用失败,我们也可以允许DB更新回滚,运营可通过再次操作完成审核

但是——

遇到的问题描述下:部分RPC发生异常(消息投递服务调用成功,但是通知服务发送短信邮件调用失败),导致事务回滚,因此DB未更新,但部分RPC(消息投递服务调用成功)已经执行出去了;

原因很明显,就是我们的RPC是无法会滚的,尤其是当涉及多个RPC操作,后面的RPC失败并不会导致前面的RPC回滚(除非显示的调用RPC的回滚方法,不过一般是没有的),只有本地的DB操作回滚了;并且,抛出的RPC异常,并不能确保本地RPC调用一定是失败了,也可能是超时了,但是远程的服务已经被执行成功了;

结论就是:在事务@Transaction中,尽量避免RPC操作;应当将RPC剥离出事务,如执行完本地DB事务后再去执行RPC,如果RPC失败,我们可以通过重试模板甚至是异常日志告警+手动重试的方式补偿,而RPC接口一般都是幂等的,重复的调用也不会有问题;

因此使用了下方的代码,即提交事务后再执行RPC:

    /**
     * 审核计划并通知下游
     *
     * @param reqDTO
     * @return
     */
    @Transactional(rollbackFor = {Exception.class, BusinessException.class})
    @Override
    public long verify(InternalTestingPlanVerifyDTO reqDTO) {
			/* 前置查询DB并校验... */

			// 更新DB
            gameInternalTestingPlanApplyDAO.updateByPrimaryKeySelective(operateDomain);

            // 这里是后置的RPC操作 尽量从事务中剥离出来或使用异步线程执行
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
                @Override
                public void afterCommit() {
                    // 审核通过时-通知下游 物料通知
                    if (InternalTestingPlanStatusEnum.PASSED.getType().equals(verifyResultDTO.getVerifyResultStatus())) {
                        final Map<String, Object> body = BeanUtils.convertToMap(applyDO);
                        // 物料通知【RPC】
                        MessageDeliverTaskReqDTO materialModifyMessage = buildBaseMessageDeliverTaskReqDTO(planId, MessageSceneTypeEnum.INTESTINGPLAN_MATERIAL.getSceneType(), body);
                        messageDeliverService.deliver(materialModifyMessage);
                        log.warn("verify_pass_send_internalTestingPlan_material_message_suc.");
                    }
                    // 审核通过/不通过时-短信邮件提醒【RPC】
                    noticeService.notifyVerifyResult(reqDTO);
                }
            });
            return planId;
        } else {
            throw new BusinessException(ResultCodeEnum.BAD_PARAM, "invalid_planId_cannot_operate");
        }
    }

3. TransactionSynchronizationManager

TransactionSynchronizationManager是一个事务管理的核心类,通过TransactionSynchronizationManager我们可以管理当前线程的事务。而很多时候我们使用这个类是为了方便我们在事务结束或者开始之前实现一些自己的逻辑;如下:

@Service
@Transactional
@Slf4j
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private OrderService orderService;

    public User addUser(User user) {
        log.info("任务开始!");
        userRepository.save(user);
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {

            @Override
            public void beforeCommit(boolean readOnly) {
                log.info("事务开始提交");
            }

            @Override
            public void afterCommit() {
                log.info("事务提交结束");
                orderService.addOrder();
            }
        });
        log.info("任务已经结束!");
        return user;
    }

}

但是注意,由于事务默认是传递的,因此不要在afterCommit里面再去写一个事务包裹的调用,如下:

@Service
@Transactional
@Slf4j
public class OrderService {

    @Autowired
    private OrderRepository orderRepository;

    @Transactional
    public Order addOrder() {
        log.info("开始插入order数据");
        Order order = new Order();
        order.setMoney(100D);
        orderRepository.save(order);
        log.info("插入order数据结束");
        return order;
    }

}

因为原事务会传递到afterCommit里面的addOrder方法上,因此addOrder等不到原事务的提交,永远无法执行;可以打印一下原事务名和addOrder执行时的事务名验证:

String currentTransactionName = TransactionSynchronizationManager.getCurrentTransactionName();
log.info("currentTransactionName is {}",currentTransactionName);
// 控制台输出:
2022-01-05 23:28:28.669  INFO 12908 --- [main] dai.samples.jpa2.service.UserService     : currentTransactionName is dai.samples.jpa2.service.UserService.addUser
2022-01-05 23:28:28.673  INFO 12908 --- [main] dai.samples.jpa2.service.OrderService    : currentTransactionName is dai.samples.jpa2.service.UserService.addUser

可见,两个方法使用了相同的事务,但是需要注意的是addOrder方法是在afterCommit事务提交之后执行的,此时会导致addOrder中的JPA数据保存最终无法提交。所以我们需要使addOrder进入一个新的事务中,如在@Transactional注解中propagation参数用来控制事务的传播

@Transactional注解的propagation参数默认被设置为Propagation.REQUIRED传递,其逻辑是,如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。而上面的业务中我们并不希望其加入已有的事务中,所以单介绍上面的逻辑,假如希望JPA的数据保存到数据库中,需要在事务注解修改为@Transactional(propagation = Propagation.REQUIRES_NEW)参数;

然而在很多时候我们希望新加入的方法能够被同一个事务所管理,而使用Propagation.REQUIRES_NEW会导致当前操作脱离上一级事务的控制;所以在使用@Transactional(propagation = Propagation.REQUIRES_NEW)的时候一定要慎重,并且严格控制其被滥用;

参考:

TransactionSynchronizationManager.registerSynchronization使用中事务传播产生的问题

Spring 事务事件控制 解决业务异步操作解耦 TransactionSynchronizationManager

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

编码技巧——事务提交后执行 的相关文章

  • Java中有没有一种方法可以通过名称实例化一个类?

    我正在寻找问题 从字符串名称实例化一个类 https stackoverflow com questions 9854900 instantiate an class from its string name它描述了如何在有名称的情况下实例
  • 在 Java 中克隆对象 [3 个问题]

    这样做会调用Asub的clone方法吗 或者Asub深度克隆是否正确 如果没有的话 有没有办法通过这种方法对Asub进行深度克隆呢 abstract class Top extends TopMost protected Object cl
  • Mockito:如何通过模拟测试我的服务?

    我是模拟测试新手 我想测试我的服务方法CorrectionService correctPerson Long personId 实现尚未编写 但这就是它将执行的操作 CorrectionService将调用一个方法AddressDAO这将
  • 在内存中使用 byte[] 创建 zip 文件。 Zip 文件总是损坏

    我创建的 zip 文件有问题 我正在使用 Java 7 我尝试从字节数组创建一个 zip 文件 其中包含两个或多个 Excel 文件 应用程序始终完成 没有任何异常 所以 我以为一切都好 当我尝试打开 zip 文件后 Windows 7 出
  • Java 枚举与创建位掩码和检查权限的混淆

    我想将此 c 权限模块移植到 java 但是当我无法将数值保存在数据库中然后将其转换为枚举表示形式时 我很困惑如何执行此操作 在 C 中 我创建一个如下所示的枚举 public enum ArticlePermission CanRead
  • 动态选择端口号?

    在 Java 中 我需要获取端口号以在同一程序的多个实例之间进行通信 现在 我可以简单地选择一些固定的数字并使用它 但我想知道是否有一种方法可以动态选择端口号 这样我就不必打扰我的用户设置端口号 这是我的一个想法 其工作原理如下 有一个固定
  • 过滤两次 Lambda Java

    我有一个清单如下 1 2 3 4 5 6 7 和 预期结果必须是 1 2 3 4 5 6 7 我知道怎么做才能到7点 我的结果 1 2 3 4 5 6 我也想知道如何输入 7 我添加了i gt i objList size 1到我的过滤器
  • jQuery AJAX 调用 Java 方法

    使用 jQuery AJAX 我们可以调用特定的 JAVA 方法 例如从 Action 类 该 Java 方法返回的数据将用于填充一些 HTML 代码 请告诉我是否可以使用 jQuery 轻松完成此操作 就像在 DWR 中一样 此外 对于
  • Java 公历日历更改时区

    我正在尝试设置 HOUR OF DAY 字段并更改 GregorianCalendar 日期对象的时区 GregorianCalendar date new GregorianCalendar TimeZone getTimeZone GM
  • Spring Boot Data JPA 从存储过程接收多个输出参数

    我尝试通过 Spring Boot Data JPA v2 2 6 调用具有多个输出参数的存储过程 但收到错误 DEBUG http nio 8080 exec 1 org hibernate engine jdbc spi SqlStat
  • 当 OnFocusChangeListener 应用于包装的 EditText 时,TextInputLayout 没有动画

    不能比标题说得更清楚了 我有一个由文本输入布局包裹的 EditText 我试图在 EditText 失去焦点时触发一个事件 但是 一旦应用了事件侦听器 TextInputLayout 就不再对文本进行动画处理 它只是位于 editText
  • Java 和 Python 可以在同一个应用程序中共存吗?

    我需要一个 Java 实例直接从 Python 实例数据存储中获取数据 我不知道这是否可能 数据存储是否透明 唯一 或者每个实例 如果它们确实可以共存 都有其单独的数据存储 总结一下 Java 应用程序如何从 Python 应用程序的数据存
  • 尝试将 Web 服务部署到 TomEE 时出现“找不到...的 appInfo”

    我有一个非常简单的项目 用于培训目的 它是一个 RESTful Web 服务 我使用 js css 和 html 创建了一个客户端 我正在尝试将该服务部署到 TomEE 这是我尝试部署时遇到的错误 我在这里做错了什么 刚刚遇到这个问题 我曾
  • logcat 中 mSecurityInputMethodService 为 null

    我写了一点android应显示智能手机当前位置 最后已知位置 的应用程序 尽管我复制了示例代码 并尝试了其他几种解决方案 但似乎每次都有相同的错误 我的应用程序由一个按钮组成 按下按钮应该log经度和纬度 但仅对数 mSecurityInp
  • 关键字“table”附近的语法不正确,无法提取结果集

    我使用 SQL Server 创建了一个项目 其中包含以下文件 UserDAO java public class UserDAO private static SessionFactory sessionFactory static se
  • 使用反射覆盖最终静态字段是否有限制?

    在我的一些单元测试中 我在最终静态字段上的反射中遇到了奇怪的行为 下面是说明我的问题的示例 我有一个基本的 Singleton 类 其中包含一个 Integer public class BasicHolder private static
  • CamcorderProfile.videoCodec 返回错误值

    根据docs https developer android com reference android media CamcorderProfile html 您可以使用CamcorderProfile获取设备默认视频编解码格式 然后将其
  • 如果没有抽象成员,基类是否应该标记为抽象?

    如果一个类没有抽象成员 可以将其标记为抽象吗 即使没有实际理由直接实例化它 除了单元测试 是的 将不应该实例化的基类显式标记为抽象是合理且有益的 即使在没有抽象方法的情况下也是如此 它强制执行通用准则来使非叶类抽象 它阻止其他程序员创建该类
  • 双枢轴快速排序和快速排序有什么区别?

    我以前从未见过双枢轴快速排序 是快速排序的升级版吗 双枢轴快速排序和快速排序有什么区别 我在 Java 文档中找到了这个 排序算法是双枢轴快速排序 作者 弗拉基米尔 雅罗斯拉夫斯基 乔恩 本特利和约书亚 布洛赫 这个算法 在许多数据集上提供
  • Spring Rest 和 Jsonp

    我正在尝试让我的 Spring Rest 控制器返回jsonp但我没有快乐 如果我想返回 json 但我有返回的要求 完全相同的代码可以正常工作jsonp我添加了一个转换器 我在网上找到了用于执行 jsonp 转换的源代码 我正在使用 Sp

随机推荐

  • 【自学Linux】 Linux文件目录结构

    Linux文件目录结构 Linux文件目录结构教程 在 Linux 中 有一个很经典的说法 叫做一切皆文件 因此 我们在系统学习 Linux 之前 首先要了解 Linux 的文件目录结构 Linux 主要的目录有三大类 即根目录 usr 目
  • Tracy vue3 小笔记 1 - 如何使用 VUE, MVC, MVVM, Template, Vue 源码

    Vue Demo code 计数器那么安装和使用Vue这个JavaScript库有哪些方式呢 方式一 在页面中通过CDN的方式来引入 方式二 下载Vue的JavaScript文件 并且自己手动引入 方式三 通过npm包管理工具安装使用它 w
  • 智能输液系统(STM32+ESP8266-01S+阿里云+安卓APP)

    下位机 阿里云 APP 下位机流转上位机
  • 2021-08-19-leetcode-00001

    二分查找 704 给定一个 n 个元素有序的 升序 整型数组 nums 和一个目标值 target 写一个函数搜索 nums 中的 target 如果目标值存在返回下标 否则返回 1 278 你是产品经理 目前正在带领一个团队开发新的产品
  • Linux 学习笔记3 权限管理 定时任务 网络配置 进程、软件包管理

    权限管理 linux组的介绍 在linux中的每个用户必须属于一个组 不能独立于组外 在linux中每个文件有所有者 所在组 其它组的概念 1 所有者 2 所在组 3 其它组 4 改变用户所在的组 文件 目录所有者 一般为文件的创建者谁创建
  • kettle进阶之database join

    前言 这并不是一篇入门教学 且不是一篇高阶教学 仅仅针对kettle的database join 那么问题来了 kettle的database join是什么 简而言之 不同库之间sql语句传值 个人自定义 如有不当 请海涵 案例1 一条数
  • SQL Server 集合处理

    UNION ALL 返回两个结果集中所有的行 返回结果集中会存在重复行 UNION 返回两个结果集中去重的行 返回结果集中无重复行 INTERSECT 返回两个结果集都有的行 返回结果集中无重复行 EXCEPT 返回第一个结果集中有而第二个
  • vuex与生命周期的关系

    vue与生命周期的关系 问题呈现 获取到数据同时保存到vuex中 2 计算属性获取vuex中的值 3 循环输出 4 出现错误 问题解析 如果单独输出item questionSimpleInfo是可以输出整个对象的 但是输出其中某一个字段的
  • 论文阅读-Thinking in Frequency: Face Forgery Detection by Mining Frequency-aware Clues(F3Net基于频率感知线索的人脸)

    一 论文信息 题目 Thinking in Frequency Face Forgery Detection by Mining Frequency aware Clues 基于频率感知线索的人脸伪造检测 作者团队 会议 ECCV 2020
  • Markdown矩阵及公式语法编辑

    Markdown矩阵及公式语法编辑 详见链接 https cloud tencent com developer article 1402840
  • 9. xaml ComboBox控件

    1 运行图像 2 运行源码 a Xaml源码
  • C++中虚函数、虚指针和虚表详解

    关于虚函数的背景知识 用virtual关键字申明的函数叫做虚函数 虚函数肯定是类的成员函数 存在虚函数的类都有一个一维的虚函数表叫做虚表 每一个类的对象都有一个指向虚表开始的虚指针 虚表是和类对应的 虚表指针是和对象对应的 多态性是一个接口
  • Windows CMD 输出文本到文件,不加换行符

    gt test txt set p Hello
  • 共筑安全创新生态,持安科技加入麒麟软件安全生态联盟

    近日 麒麟软件安全生态联盟第二季度工作会议成功举行 零信任办公安全领域明星企业持安科技受邀参会 并参与授牌环节成为麒麟软件安全生态联盟会员单位 麒麟软件安全生态联盟授牌仪式 会上 联盟成员单位围绕操作系统安全事件与漏洞发展趋势 行业应用对操
  • Beego v2.0 编译后无法运行问题

    问题 beego版本 v2 0 1 通过bee pack be GOOS linux 打包后运行可执行程序报错 如下 panic err go command required not found exec go executable fi
  • Vue+Element-ui Table 列求和

    Vue Element ui Table 列求和 Vue代码 求和getSummaries 效果图 Vue代码
  • [GameFramework分析] Log(日志)

    文章目录 使用 分析 Unity脚本 Log LogScriptingDefineSymbols ScriptingDefineSymbols DefaultLogHelper 框架类 GameFrameworkLogLevel GameF
  • graylog日志分析管理系统入门教程

    日志分析系统可以实时收集 分析 监控日志并报警 当然也可以非实时的分析日志 splunk是功能强大且用起来最省心的 但是要收费 免费版有每天500M的限制 超过500M的日志就没法处理了 ELK系统是最常见的 缺点是配置麻烦一些 比较重量级
  • MySQL之InnoDB引擎(一)

    1 InnoDB介绍 InnoDB是一个通用的存储引擎 同时具备高可靠性与高性能的特性 除非用户指定存储引擎的类型 否则其作为MySQL Server的默认存储引擎 使用InnoDB存储引擎的优势包括如下几点 DML操作符合ACID模型 使
  • 编码技巧——事务提交后执行

    日常开发中 一些诸如 先读后写 先写A再写B 先写A再执行B 的场景 一般都会用到事务 这里的事务指的是本地事务 如果涉及RPC 一般我们通过异步补偿来保证最终一致性 本篇例举2个使用事务 先写A再执行B 的场景 1 订单场景 1 处理支付