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

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 - 将无符号十六进制字符串解析为有符号长整型

    我有一堆十六进制字符串 其中之一是 d1bc4f7154ac9edb 这是 3333702275990511909 的十六进制值 如果执行 Long toHexString d1bc4f7154ac9edb 这与您得到的十六进制相同 现在
  • java 拖放

    我尝试熟悉java中的拖放 但我发现的所有教程都是 让我生气 我想要的只是从 JList 包含在名为 UserPanel 的自制 JPanel 中 拖动 PublicUserLabel 并将其放入从 JTabbedPanel 继承的自制类中
  • Java 错误和警告列表

    在哪里 如何获得所有 java 和 javac 的错误和警告消息的列表 This http mindprod com jgloss compileerrormessages html我认为页面是您所需要的
  • 二元运算符 >=、-、* 的错误操作数类型

    我无法弄清楚如何修复代码中不断出现的这些错误 import java util Scanner public class Unit02Prog1 public static void main String args Scanner inp
  • Android Studio 与 Google Play 服务的编译问题

    我正在运行 Android Studio 0 8 4 并在 Android Studio 0 8 2 上尝试过此操作 我正在运行 Java JDK 1 8 0 11 并尝试使用 JDK 1 8 0 05 每当我尝试构建我的 android
  • 传递自定义类型查询参数

    如何接受自定义类型查询参数 public String detail QueryParam request final MYRequest request 上面的行在启动服务器时出现错误 jersey server model ModelV
  • 在 doxygen 中使用 @see 或 @link

    我之前用 Javadoc 记录并使用了标签 see link or see foo and link foo 在我的描述中链接到其他课程 现在我尝试了doxygen 似乎这些标签不兼容 如果我运行 doxygen 完整的标签将被简单地解释为
  • java:为什么主线程等待子线程完成

    我有一个简单的java程序 主线程 main 创建并启动另一个线程t class T extends Thread Override public void run while true System out println Inside
  • 如何检查单词是否在wordNet中

    我开始了解wordNet直到我知道我找到了synonymous对于一个特定的词 现在我有一个文件 我想使用标记化该文本n gram例如 String s I like to wear tee shirt 使用后n gram这将是 I lik
  • 在 Eclipse 中删除空块之前的新行

    我更喜欢奥尔曼式 http en wikipedia org wiki Brace style Allman style大括号 例如 if foo magical prancing unicorn stuff 而不是 if foo unma
  • 生成一定长度的所有排列

    假设我们有一个字母表 abcdefghiklimnop 如何以有效的方式以五个一组的形式重复该字母表来递归生成排列 几天来我一直在为此苦苦挣扎 任何反馈都会有帮助 本质上这与 生成给定字符串的所有排列 https stackoverflow
  • JFrame 在连续运行代码时冻结

    我在使用时遇到问题JFrame 它会冻结 连续运行代码 下面是我的代码 点击时btnRun 我调用了该函数MainLoop ActionListener btnRun Click new ActionListener Override pu
  • java绕中心旋转矩形

    我想围绕其中心点旋转一个矩形 它应该保留在应该绘制的位置并在该空间中旋转 这是我的代码 AffineTransform transform new AffineTransform transform rotate Math toRadian
  • javadoc 子集/java 库组织

    我自己从来没有运行过javadoc 无论是在命令行还是ant 的 javadoc 任务 http ant apache org manual Tasks javadoc html 我将使用 ant 我需要为我编写的库生成 javadoc 问
  • Hybris:如何在impex中导入zip文件中的媒体?

    我知道我们可以导入未像这样压缩的图像 siteResource jar com project initialdata constants ProjectInitialDataConstants projectinitialdata imp
  • 添加 char 和 int

    据我了解 字符是一个字符 即一个字母 一个digit 标点符号 制表符 空格或类似的东西 因此 当我这样做时 char c 1 System out println c 输出 1 正是我所期望的 那么为什么当我这样做时 int a 1 ch
  • 向Java类库添加函数

    我使用的 Java 类库在很多方面都不完整 有很多类我认为应该内置其他成员函数 但是 我不确定添加这些成员函数的最佳实践 让我们调用不足的基类A class A public A long arbitrary arguments publi
  • 如何使用 AffineTransform.quadrantRotate 旋转位图?

    我想旋转一个bitmap关于它的中心点 然后将其绘制成更大的图形上下文 位图是40x40 pixels 图形上下文是500x500 pixels 这就是我正在做的 BufferedImage bi new BufferedImage 500
  • 如果 Modelmapper 中的整个属性为空,如何排除它们

    ModelMapper 是否 http modelmapper org http modelmapper org 支持什么排除属性 如果该值为空 我刚刚找到了 PropertyMap 但这对我来说是一种限制 因为我必须描述我想要的特定属性
  • 为什么/何时应该使用泛型方法?

    学习Java的时候遇到过通用方法 public

随机推荐

  • 【自学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 处理支付