基于Spring事件驱动模式实现业务解耦

事件驱动模式

举个例子?

大部分软件或者APP都有会有会员系统,当我们注册为会员时,商家一般会把我们拉入会员群、给我们发优惠券、推送欢迎语什么的。
image
值得注意的是:

  1. 注册成功后才会产生后面的这些动作;
  2. 注册成功后的这些动作没有先后执行顺序之分;
  3. 注册成功后的这些动作的执行结果不能互相影响;

传统写法

public Boolean doRegisterVip(){
	//1、注册会员
	registerVip();
	//2、入会员群
	joinMembershipGroup();
	//3、发优惠券
	issueCoupons();
	//4、推送消息
	sendWelcomeMsg();
}

这样的写法将所有的动作都耦合在doRegisterVip方法中,首先执行效率低下,其次耦合度太高,最后不好扩展。那么如何优化这种逻辑呢?

事件驱动模式原理介绍?

Spring的事件驱动模型由三部分组成:

事件:用户可自定义事件类和相关属性及行为来表述事件特征,Spring4.2之后定义事件不需要再显式继承ApplicationEvent类,直接定义一个bean即可,Spring会自动通过PayloadApplicationEvent来包装事件。

事件发布者:在Spring中可通过ApplicationEventPublisher把事件发布出去,这样事件内容就可以被监听者消费处理。

事件监听者:ApplicationListener,监听发布事件,处理事件发生之后的后续操作。

原理图如下:
image

代码实现

1. 定义基本元素

事件发布者:EventEngine.java、EventEngineImpl.java

package com.example.event.config;

/**
 * 事件引擎
 */
public interface EventEngine {

    /**
     * 发送事件
     *
     * @param event 事件
     */
    void publishEvent(BizEvent event);
}
package com.example.event.config;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;

import org.springframework.util.CollectionUtils;

/**
 * 事件引擎实现类
 */
public class EventEngineImpl implements EventEngine {

    /**
     * 异步执行器。也系统需要自行定义线程池
     */
    private Executor bizListenerExecutor;

    /**
     * 是否异步,默认为false
     */
    private boolean async;

    /**
     * 订阅端 KEY是TOPIC,VALUES是监听器集合
     */
    private Map<String, List<BizEventListener>> bizSubscribers = new HashMap<>(16);

    @Override
    public void publishEvent(BizEvent event) {
        List<BizEventListener> listeners = bizSubscribers.get(event.getTopic());
        if (CollectionUtils.isEmpty(listeners)) {
            return;
        }
        for (BizEventListener bizEventListener : listeners) {
            if (bizEventListener.decide(event)) {
                //异步执行的话,放入线程池
                if (async) {
                    bizListenerExecutor.execute(new EventSubscriber(bizEventListener, event));
                } else {
                    bizEventListener.onEvent(event);
                }

            }
        }
    }

    /**
     * Setter method for property <tt>bizListenerExecutor</tt>.
     *
     * @param bizListenerExecutor value to be assigned to property bizListenerExecutor
     */
    public void setBizListenerExecutor(Executor bizListenerExecutor) {
        this.bizListenerExecutor = bizListenerExecutor;
    }

    /**
     * Setter method for property <tt>bizSubscribers</tt>.
     *
     * @param bizSubscribers value to be assigned to property bizSubscribers
     */
    public void setBizSubscribers(Map<String, List<BizEventListener>> bizSubscribers) {
        this.bizSubscribers = bizSubscribers;
    }

    /**
     * Setter method for property <tt>async</tt>.
     *
     * @param async value to be assigned to property async
     */
    public void setAsync(boolean async) {
        this.async = async;
    }
}

事件:BizEvent.java

package com.example.event.config;

import java.util.EventObject;

/**
 * 业务事件
 */
public class BizEvent extends EventObject {

    /**
     * Topic
     */
    private final String topic;

    /**
     * 业务id
     */
    private final String bizId;

    /**
     * 数据
     */
    private final Object data;

    /**
     * @param topic 事件topic,用于区分事件类型
     * @param bizId 业务ID,标识这一次的调用
     * @param data  事件传输对象
     */
    public BizEvent(String topic, String bizId, Object data) {
        super(data);
        this.topic = topic;
        this.bizId = bizId;
        this.data = data;
    }

    /**
     * Getter method for property <tt>topic</tt>.
     *
     * @return property value of topic
     */
    public String getTopic() {
        return topic;
    }

    /**
     * Getter method for property <tt>id</tt>.
     *
     * @return property value of id
     */
    public String getBizId() {
        return bizId;
    }

    /**
     * Getter method for property <tt>data</tt>.
     *
     * @return property value of data
     */
    public Object getData() {
        return data;
    }
}

事件监听者:EventSubscriber.java

package com.example.event.config;

/**
 * 事件监听者。注意:此时已经没有线程上下文,如果需要请修改构造函数,显示复制上下文信息
 */
public class EventSubscriber implements Runnable {

    /**
     * 业务监听器
     **/
    private BizEventListener bizEventListener;

    /**
     * 业务事件
     */
    private BizEvent bizEvent;

    /**
     * @param bizEventListener 事件监听者
     * @param bizEvent         事件
     */
    public EventSubscriber(BizEventListener bizEventListener, BizEvent bizEvent) {
        super();
        this.bizEventListener = bizEventListener;
        this.bizEvent = bizEvent;
    }

    @Override
    public void run() {
        bizEventListener.onEvent(bizEvent);
    }
}

2. 其他组件

业务事件监听器:BizEventListener.java

package com.example.event.config;

import java.util.EventListener;

/**
 * 业务事件监听器
 *
 */
public interface BizEventListener extends EventListener {

    /**
     * 是否执行事件
     *
     * @param event 事件
     * @return
     */
    public boolean decide(BizEvent event);

    /**
     * 执行事件
     *
     * @param event 事件
     */
    public void onEvent(BizEvent event);
}

事件引擎topic:EventEngineTopic.java

package com.example.event.config;

/**
 * 事件引擎topic,用于区分事件类型
 */
public class EventEngineTopic {
    /**
     * 入会员群
     */
    public static final String JOIN_MEMBERSHIP_GROUP = "joinMembershipGroup";

    /**
     * 发优惠券
     */
    public static final String ISSUE_COUPONS = "issueCoupons";

    /**
     * 推送消息
     */
    public static final String SEND_WELCOME_MSG = "sendWelcomeMsg";

}

3. 监听器实现

优惠券处理器:CouponsHandlerListener.java

package com.example.event.listener;

import com.example.event.config.BizEvent;
import com.example.event.config.BizEventListener;
import org.springframework.stereotype.Component;

/**
 * 优惠券处理器
 */
@Component
public class CouponsHandlerListener implements BizEventListener {

    @Override
    public boolean decide(BizEvent event) {
        return true;
    }

    @Override
    public void onEvent(BizEvent event) {
        System.out.println("优惠券处理器:十折优惠券已发放");
    }
}

会员群处理器:MembershipHandlerListener.java

package com.example.event.listener;

import com.example.event.config.BizEvent;
import com.example.event.config.BizEventListener;
import org.springframework.stereotype.Component;

/**
 * 会员群处理器
 */
@Component
public class MembershipHandlerListener implements BizEventListener {
    @Override
    public boolean decide(BizEvent event) {
        return true;
    }

    @Override
    public void onEvent(BizEvent event) {
        System.out.println("会员群处理器:您已成功加入会员群");
    }
}

消息推送处理器:MsgHandlerListener.java

package com.example.event.listener;

import com.example.event.config.BizEvent;
import com.example.event.config.BizEventListener;
import org.springframework.stereotype.Component;

/**
 * 消息推送处理器
 */
@Component
public class MsgHandlerListener implements BizEventListener {

    @Override
    public boolean decide(BizEvent event) {
        return true;
    }

    @Override
    public void onEvent(BizEvent event) {
        System.out.println("消息推送处理器:欢迎成为会员!!!");
    }
}

事件驱动引擎配置:EventEngineConfig.java

package com.example.event.listener;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import com.example.event.config.BizEventListener;
import com.example.event.config.EventEngine;
import com.example.event.config.EventEngineImpl;
import com.example.event.config.EventEngineTopic;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;

/**
 * 事件驱动引擎配置
 */
@Configuration
public class EventEngineConfig {
    /**
     * 线程池异步处理事件
     */
    private static final Executor EXECUTOR = new ThreadPoolExecutor(20, 50, 10, TimeUnit.MINUTES,
        new LinkedBlockingQueue(500), new CustomizableThreadFactory("EVENT_ENGINE_POOL"));

    @Bean("eventEngineJob")
    public EventEngine initJobEngine(CouponsHandlerListener couponsHandlerListener,
        MembershipHandlerListener membershipHandlerListener,
        MsgHandlerListener msgHandlerListener) {
        Map<String, List<BizEventListener>> bizEvenListenerMap = new HashMap<>();
        //注册优惠券事件
        bizEvenListenerMap.put(EventEngineTopic.ISSUE_COUPONS, Arrays.asList(couponsHandlerListener));
        //注册会员群事件
        bizEvenListenerMap.put(EventEngineTopic.JOIN_MEMBERSHIP_GROUP, Arrays.asList(membershipHandlerListener));
        //注册消息推送事件
        bizEvenListenerMap.put(EventEngineTopic.SEND_WELCOME_MSG, Arrays.asList(msgHandlerListener));

        EventEngineImpl eventEngine = new EventEngineImpl();
        eventEngine.setBizSubscribers(bizEvenListenerMap);
        eventEngine.setAsync(true);
        eventEngine.setBizListenerExecutor(EXECUTOR);
        return eventEngine;
    }
}

4. 测试类

TestController.java

package com.example.event.controller;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import javax.annotation.Resource;

import com.example.event.config.BizEvent;
import com.example.event.config.EventEngine;
import com.example.event.config.EventEngineTopic;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * 测试
 */
@RestController
@RequestMapping("/test")
public class TestController {

    @Resource(name = "eventEngineJob")
    private EventEngine eventEngine;

    @GetMapping("/doRegisterVip")
    public String doRegisterVip(@RequestParam(required = true) String userName,
        @RequestParam(required = true) Integer age) {
        Map<String, Object> paramMap = new HashMap<>(16);
        paramMap.put("userName", userName);
        paramMap.put("age", age);
        //1、注册会员,这里不实现了
        System.out.println("注册会员成功");
        //2、入会员群
        eventEngine.publishEvent(
            new BizEvent(EventEngineTopic.JOIN_MEMBERSHIP_GROUP, UUID.randomUUID().toString(), paramMap));
        //3、发优惠券
        eventEngine.publishEvent(
            new BizEvent(EventEngineTopic.ISSUE_COUPONS, UUID.randomUUID().toString(), paramMap));
        //4、推送消息
        eventEngine.publishEvent(
            new BizEvent(EventEngineTopic.SEND_WELCOME_MSG, UUID.randomUUID().toString(), paramMap));
        return "注册会员成功";
    }
}

项目代码结构

image

调用接口

http://localhost:8080/test/doRegisterVip?userName=zhangsan&age=28

image

本文转载于网络 如有侵权请联系删除

相关文章

  • tensorfolw很多的函数不能使用,具体解决方法:

    经过挣扎,我安装好了tensorflow,但是运行还是出现警告:YourCPUsupportsinstructionsthatthisTensorFlowbinarywasnotcompiledtouse:AVX2就是说我不能完全使用AVX2原来代码:importtensorflowastf#导入TensorFlow的包 s=tf.constant([[1,2,3],[4,5,6]])#这里定义了一个Tensor print("s",s)复制解决方案:忽略警告就好了,于是我们在原来代码加入新的部分:importos os.environ['TF_CPP_MIN_LOG_LEVEL']='2'复制所以新的代码为:importtensorflowastf#导入TensorFlow的包 importos os.environ['TF_CPP_MIN_LOG_LEVEL']='2' s=tf.constant([[1,2,3],[4,5,6]])#这里定义了一个Tensor print(&

  • 考研失利,调剂环境工程。我还能做算法吗?

    专注于AI算法面经与踩坑经验分享! 考研失利大家好,我是灿视。今天周一,早上来公司第一件事就是整理周报。在整理周报的时候,小我n届的学弟给我发信息。学弟本科是计算机专业,今年考的就是那所华五软工,结果都知道了,炸了!调剂在出分之后,学弟也跟我保持了一段时间的密切沟通,我也会主动询问他关于调剂的一些情况,其中我给他的一些建议就是,“不一定非要CS或者软工的专业,如果老师做的偏信息处理相关的课程,那也是可以的。”学弟是安徽人,在我们安徽人眼里,除了清北,那么科大无疑就是最好的高校了。学弟在科大网站上,翻了往年的调剂简章,发现有一些如稀土专业,环境专业,是有调剂名额的,学弟也就抱着死马当活马医去报名了调剂系统。果然,貌似除了CS,软工、自动化等热门专业,其它专业好像就是比较冷清,学弟的分数还不低,进了调剂复试。据学弟说,他在复试的时候,主要就是介绍了下它的毕设,是一个基于深度学习的项目,为什么主要介绍这个项目呢?学弟的意思是,这是他在“炸鱼”!当前人工智能还是比较先进的东西,传统学科相对落后,并且AI交叉各领域是一个大趋势,做科研的老师,不会错过与人工智能进行交叉的机会的。果然,在学弟复试结

  • 《Prometheus监控实战》第8章 监控应用程序

    第8章监控应用程序首先,考虑的一些高级设计模式和原则8.1应用程序监控入门应用程序开发中存在一种常见的反模式,即把监控和其他运维功能(如安全性)视为应用程序的增值组件而非核心功能。但监控(和安全性)应该是应用程序的核心功能。如果你要为应用程序构建规范或用户故事,则请把对应用程序每个组件的监控包含进去。不构建指标或监控将存在严重的业务和运营风险,这将导致无法识别或诊断故障无法衡量应用程序的运行性能无法衡量应用程序或组件的业务指标以及成功与否,例如跟踪销售数据或交易价值另一种常见的反模式是监控力度不足,我们始终建议你尽全力监控应用程序。人们经常会抱怨数据太少,但很少会担心数据太多注意:在存储容量的限制范围内,因超出容量而导致监控停止工作显然是不可取的。一种有效的方法是首先关注并修改保留时间,以便在减少存储的同时又不丢失有用的信息第三点需要注意的是,如果你使用多个环境(例如开发、测试、预生产和生产),那么请确保为监控配置提供标签,以便明确数据来自哪个特定环境,这样就可以对监控和指标进行分区8.1.1从哪里开始开始为应用添加监控,一个不错的选择是程序的入口和出口。例如测量请求和响应的数量和时间

  • CSS3背景与渐变

    一、CSS3背景图像区域background-clip(指定背景绘制区域)ackground-clip:border-box/padding-box/content-box;/*没有padding的时候,content-box和padding-box效果一样*/ 兼容性:IE9+、FireFox、Chrome、Safari、Opera 二、CSS3背景图像定位background-position(背景定位) background-position:px/%...; background-origin(设置元素背景图片的原始起始位置) background-origin:padding-box|border-box|content-box; 兼容性:IE9+、FireFox4+、Chrome、Safari5+、Opera 三、CSS3背景图像大小background-size(指定背景图片大小)background-size:px/%/cover/contain;/*cover:把背景图片扩展至足够大,以使背景图片完全覆盖区域(即完全不留白)contain:把图像扩展至最大尺寸,以使其

  • 使用Xtrabackup对MySQL做主从复制

    使用Xtrabackup对MySQL做主从复制说明xtrabackupmysqldump对于导出10G以下的数据库或几个表,还是适用的,而且更快捷。一旦数据量达到100-500G,无论是对原库的压力还是导出的性能,mysqldump就力不从心了。Percona-Xtrabackup备份工具,是实现MySQL在线热备工作的不二选择,可进行全量、增量、单表备份和还原。(但当数据量更大时,可能需要考虑分库分表,或使用LVM快照来加快备份速度了)。 2.2版本xtrabackup能对InnoDB和XtraDB存储引擎的数据库非阻塞地备份,innobackupex通过perl封装了一层xtrabackup,对MyISAM的备份通过加表读锁的方式实现。2.3版本xtrabackup命令直接支持MyISAM引擎。Xtrabackup优势:无需停止数据库进行InnoDB热备增量备份MySQL流压缩到传输到其它服务器能比较容易地创建主从同步备份MySQL时不会增大服务器负载replication为什么要做主从复制?我想这是要在实施以前要想清楚的问题。是为了实现读写分离,减轻主库负载或数据分析?为了数据安

  • 基础练习 十六进制转十进制

    问题描述  从键盘输入一个不超过8位的正的十六进制数字符串,将它转换为正的十进制数后输出。   注:十六进制数中的10~15分别用大写的英文字母A、B、C、D、E、F表示。样例输入FFFF样例输出65535 思路:         设十六进制位数为n,十六进制的第i位乘以10的n- i次方。使用函数pow比较简单。pow函数包含在头文件cmath(C中是math.h)中,c++中提供很多种pow的重载形式,Tpow(Tx,Ty)表示x的y次方,T可以为int、double、float、longdouble。 #include<cstdio> #include<cmath> #include<cstring> intmain() { intlen,i,t; longlongintN=0; charw[8]; gets(w); len=strlen(w); for(i=0;w[i]!='\0';i++) { switch(w[i]) { case'A':t=10;break; case'B'

  • 作业调度框架 Quartz.NET 2.0 beta 发布

    经过整整1年多时间的开发,Quartz.NET2.0发布了beta版,对应于JavaQuartz的2.1版本,下载地址http://quartznet.sourceforge.net/download.html。整个版本相对于1.0版本进行了大量的修改,单元测试的代码更友好(重构了更多的接口),API是基于泛型和.NET3.5SP1之后的特性,例如DateTimeOffset。这是Quartz.NET有史以来最大的、最值得兴奋的一个版本。该版本除了在性能上有所提升外,增加了如下新特性:Scheduler.Clear()提供方便用于清除所有任务、触发器和日程的方法Scheduler.ScheduleJobs((IDictionary>triggersAndJobs,booleanreplace)方法可批量增加任务和触发器Scheduler.UnscheduleJobs(IListtriggerKeys)方法提供批量取消任务的Scheduler.DeleteJobs(IListjobKeys),不用说,这是批量删除任务的Scheduler.CheckExists(JobKeyjobK

  • 2-11

  • NYOJ-596-谁是最好的Coder

    原题链接 谁是最好的Coder 时间限制:1000 ms | 内存限制:65535 KB 难度:0 描述 计科班有很多Coder,帅帅想知道自己是不是综合实力最强的coder。 帅帅喜欢帅,所以他选了帅气和编程水平作为评选标准。 每个同学的综合得分是帅气程度得分与编程水平得分的和。 他希望你能写一个程序帮他一下。   输入数据有多组。输入一个数n,代表计科班的总人数。接下来有n行数,一行数有两个数a,b。其中a代表该同学的编程水平,b代表该同学的帅气程度。n=0表示输入结束。输出每组数据占一行,输出所有同学中综合得分最高的分数。样例输入 5 910 711 16 57 35 2 73 76 0复制 样例输出 19 13复制   1#include<iostream> 2usingnamespacestd; 3intmain() 4{ 5intn,a,b,sum; 6while(1) 7{ 8sum=0; 9cin>>n; 10if(n==0) 11break; 12while(n--) 13{

  • Fiori前台开发和ABAP的不同点总结

    Fiori知识体系: html和css,属于网页的定义,不是编程语言,相对容易掌握 Javascript,是一门编程语言,在浏览器环境下运行,相对有难度,需要系统学习,b站上有很多视频 Fiori用xml(html)定义页面,用Javascript控制页面的逻辑,并与后台gateway通信 ABAP开发人员不习惯的地方 GUI的数据请求 Fiori的数据请求 qq互助群:

  • Eclipse设置字体大小

    打开Window->Perferences->General->Apprearance->ColorsandFonts->Basic->TextFont->Edit进行设置。

  • ASP.Net Core 中使用Zookeeper搭建分布式环境中的配置中心系列一:使用Zookeeper.Net组件演示基本的操作

    前言:马上要过年了,祝大家新年快乐!在过年回家前分享一篇关于Zookeeper的文章,我们都知道现在微服务盛行,大数据、分布式系统中经常会使用到Zookeeper,它是微服务、分布式系统中必不可少的分布式协调框架。它的作用体现在分布式系统中解决了配置中心的问题,以及解决了在分布式环境中不同进程之间争夺资源的问题,也就是分布式锁的功能以及分布式消息队列功能等等。所以在微服务的环境中Zookeeper是现在很多公司首选的分布式协调框架,包括我之前的公司也在使用Zookeeper。说了这么多,没别的就是想说一下Zookeeper的重要性,废话不多说,进入正题。本篇博客只是演示在.NetCore环境中如何使用Zookeeper组件进行基本的增删改查和一些注意的要点,如果对Zookeeper还不是太了解的话,建议认认真真、仔仔细细地阅读该文章:http://www.cnblogs.com/sunddenly/p/4033574.html  否则可能下面演示的你会看不懂。   一、Zookeeper基本概念快速介绍 概念: Zookeeper是一个开源的分布式协调框架

  • 折叠树

    用户设置了折叠树按钮后,预览报表时没有动态树效果。 实现动态折叠树必须是以数据分析预览(op=view),而使用分页预览,可以看到没有实现动态折叠树效果,如下图: 聚合报表不支持动态折叠树,所以在聚合报表中设置了动态折叠树、数据分析预览时没有动态树效果。 决策报表中新增报表块不支持控件设置,所以在决策报表中无法达到动态折叠树的效果。

  • TCP/IP 的介绍

    TCP/IP是因特网的通信协议,TransmissionControlProtocol/InternetProtocol的简写,中译名为传输控制协议/因特网互联协议,又名网络通讯协议,是Internet最基本的协议、Internet国际互联网络的基础,由网络层的IP协议和传输层的TCP协议组成 TCP/IP通信协议TCP/IP通信协议是对计算机必须遵守的规则的描述,只有遵守这些规则,计算机之间才能进行通信。 浏览器与服务器都在使用TCP/IP协议浏览器与服务器使用TCP/IP协议来链接因特网。浏览器使用TCP/IP协议进入服务器,服务器使用TCP/IP协议来发送HTML到浏览器。您的E-Mail使用TCP/IP协议您的电子邮件也通过TCP/IP协议来发送和接收邮件。因特网地址是TCP/IP协议因特网地址比如“42.120.45.233”就是一个TCP/IP协议。TCP/IPTCP/IP(中译名为传输控制协议/因特网互联协议)是用于因特网(Internet)的通信协议计算机通信协议是对那些计算机必须遵守以便彼此通信的的规则的描述什么是TCP/IP? TCP/IP是供已连接因特网的计算机进

  • 优酷播放器demo

    1<!DOCTYPEhtml> 2<htmllang="en-US"> 3 4<head> 5<metahttp-equiv="Content-Type"content="text/html;charset=UTF-8"> 6<!--<metaname="viewport"content="initial-scale=1.0,user-scalable=no,minimum-scale=1.0,maximum-scale=1.0"/>--> 7<metacontent="width=640,user-scalable=0"name="viewport"/> 8</script> 9<style> 10html, 11body, 12*{ 13padding:0; 14margin:0; 15} 16 17html, 18body{ 19height:100%; 20} 21 22#wrap, 23#player{ 24height:100%; 25} 26</style&g

  • Vue使用lib-flexible,将px转化为rem

    1.下载lib-flexible 我使用的是vue-cli+webpack,所以是通过npm来安装的 npmilib-flexible--save复制 2.引入lib-flexible 在main.js中引入lib-flexible import'lib-flexible/flexible'复制 3.安装px2rem-loader npminstallpx2rem-loader复制 4.配置px2rem-loader 在build文件中找到util.js,将px2rem-loader添加到cssLoaders中,将下面代码加进cssLoaders方法中 constpx2remLoader={ loader:'px2rem-loader', options:{ remUint:75 } }复制   同时,在generateLoaders方法中添加px2remLoader functiongenerateLoaders(loader,loaderOptions){ constloaders=[cssLoader,px2remLoader] if(options

  • 前端框架 前端库

    日期库:dayjs  https://www.npmjs.com/package/dayjs       https://www.tongbiao.xyz/

  • iframe添加点击事件

    <!DOCTYPEHTML><html><head><metahttp-equiv="Content-Type"content="text/html;charset=utf-8"/><title>jquery</title><styletype="text/css"></style><scriptsrc="https://cdn.bootcss.com/jquery/3.2.1/jquery.js"></script><scripttype="text/javascript"></script></head><iframesrc="http://code.artcare.com/page/light-pulses/"width="100%"height="100%"scrolling="no"frameborder="0"id="iFrame"></iframe><inputtype="text"id=

  • (组合游戏)SG函数与SG定理详解

    文章目录前言什么是组合游戏必胜点和必败点的概念Sprague-Grundy(SG)定理SG函数 前言   好久没写博客了,上一篇博客还是去年实训写的,一是因为寒假,二是因为随着难度的加深,学一个算法的时间也变长了很多(蒟蒻专有buff)。当然,最重要的还是因为自己懒~   后面会继续努力的。(这csdn的markdown编辑器又改版了越来越难用了) 转载请注明转自bestsort的博客 好了,进入主题,说一下SG函数和SG定理吧 什么是组合游戏 在竞赛中,组合游戏的题目一般有以下特点 题目描述一般为AAA,BBB2人做游戏 AAABBB交替进行某种游戏规定的操作,每操作一次,选手可以在有限的操作(操作必须合法)集合中任选一种。 对于游戏的任何一种可能的局面,合法的操作集合只取决于这个局面本身,不取决于其它因素(跟选手,以前的所有操作无关) 如果当前选手无法进行合法的操作,则为负 举个例子现在有一个数0,小明小红2人每次可以轮流在当前数加1~3,谁先凑到21谁就赢 这个描述就符合上面的条件: 小明小红(满足1) 每次轮流在当前数上加1~3(满足2) 当前能进

  • pytest: error: unrecognized arguments: --html=report.html

      使用piplist来检查,如果你已经pytest-html安装了。如果不是,请安装  pipinstallpytest-html。 pytest-html 是pytest的插件,不属于pytest库。

  • MongoDB-安装

    下载后放置到D盘Mongo文件夹中(下载路径:http://www.mongodb.org/downloads)1.打开DOS命令。 win+R=>>CMD2.切换盘符。 cd/dD:\Mongo\bin3.安装。 mongod--dbpath"D:\Mongo\Data\db"--logpath"D:\Mongo\Data\log.log"--auth--serviceName"MongoDB"--install--dbpath数据库保存路径--logpath日志保存路径--auth登录权限验证启用--serviceName服务名称--installWindows服务形式安装4.启动服务。 netstartMongoDB5.暂停服务。 netstopMongoDB6.删除服务。 scdeleteMongoDB   如果现在不努力,以后会活的更累吧。

相关推荐

推荐阅读