SpringBoot限制接口访问频率 - 这些错误千万不能犯

最近在基于SpringBoot做一个面向普通用户的系统,为了保证系统的稳定性,防止被恶意攻击,我想控制用户访问每个接口的频率。为了实现这个功能,可以设计一个annotation,然后借助AOP在调用方法之前检查当前ip的访问频率,如果超过设定频率,直接返回错误信息。

常见的错误设计

在开始介绍具体实现之前,我先列举几种我在网上找到的几种常见错误设计。

1. 固定窗口

有人设计了一个在每分钟内只允许访问1000次的限流方案,如下图01:00s-02:00s之间只允许访问1000次,这种设计最大的问题在于,请求可能在01:59s-02:00s之间被请求1000次,02:00s-02:01s之间被请求了1000次,这种情况下01:59s-02:01s间隔0.02s之间被请求2000次,很显然这种设计是错误的。

jm36ts

2. 缓存时间更新错误

我在研究这个问题的时候,发现网上有一种很常见的方式来进行限流,思路是基于redis,每次有用户的request进来,就会去以用户的ip和request的url为key去判断访问次数是否超标,如果有就返回错误,否则就把redis中的key对应的value加1,并重新设置key的过期时间为用户指定的访问周期。核心代码如下:

// core logic
int limit = accessLimit.limit();
long sec = accessLimit.sec();
String key = IPUtils.getIpAddr(request) + request.getRequestURI();
Integer maxLimit =null;
Object value =redisService.get(key);
if(value!=null && !value.equals("")) {
    maxLimit = Integer.valueOf(String.valueOf(value));
}
if (maxLimit == null) {
    redisService.set(key, "1", sec);
} else if (maxLimit < limit) {
    Integer i = maxLimit+1;
    redisService.set(key, i.toString(), sec);
} else {
	throw new BusinessException(500,"请求太频繁!");
}

// redis related
    public boolean set(final String key, Object value, Long expireTime) {
        boolean result = false;
        try {
            ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
            operations.set(key, value);
            redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

这里面很大的问题,就是每次都会更新key的缓存过期时间,这样相当于变相延长了每个计数周期, 可能我们想控制用户一分钟内只能访问5次,但是如果用户在前一分钟只访问了三次,后一分钟访问了三次,在上面的实现里面,很可能在第6次访问的时候返回错误,但这样是有问题的,因为用户确实在两分钟内都没有超过对应的访问频率阈值。

关于key的刷新这块,可以参看redis官方文档,每次refreh都会更新key的过期时间。
EEB8ry

基于滑动窗口的正确设计

指定时间T内,只允许发生N次。我们可以将这个指定时间T,看成一个滑动时间窗口(定宽)。我们采用Redis的zset基本数据类型的score来圈出这个滑动时间窗口。在实际操作zset的过程中,我们只需要保留在这个滑动时间窗口以内的数据,其他的数据不处理即可。

WKdTZ9

比如在上面的例子里面,假设用户的要求是60s内访问频率控制为3次。那么我永远只会统计当前时间往前倒数60s之内的访问次数,随着时间的推移,整个窗口会不断向前移动,窗口外的请求不会计算在内,保证了永远只统计当前60s内的request。

为什么选择Redis zset ?

为了统计固定时间区间内的访问频率,如果是单机程序,可能采用concurrentHashMap就够了,但是如果是分布式的程序,我们需要引入相应的分布式组件来进行计数统计,而Redis zset刚好能够满足我们的需求。

Redis zset(有序集合)中的成员是有序排列的,它和 set 集合的相同之处在于,集合中的每一个成员都是字符串类型,并且不允许重复;而它们最大区别是,有序集合是有序的,set 是无序的,这是因为有序集合中每个成员都会关联一个 double(双精度浮点数)类型的 score (分数值),Redis 正是通过 score 实现了对集合成员的排序。

Redis 使用以下命令创建一个有序集合:

ZADD key score member [score member ...]

这里面有三个重要参数,

  • key:指定一个键名;
  • score:分数值,用来描述  member,它是实现排序的关键;
  • member:要添加的成员(元素)。

当 key 不存在时,将会创建一个新的有序集合,并把分数/成员(score/member)添加到有序集合中;当 key 存在时,但 key 并非 zset 类型,此时就不能完成添加成员的操作,同时会返回一个错误提示。

在我们这个场景里面,key就是用户ip+request uri,score直接用当前时间的毫秒数表示,至于member不重要,可以也采用和score一样的数值即可。

限流过程是怎么样的?

整个流程如下:

  1. 首先用户的请求进来,将用户ip和uri组成key,timestamp为value,放入zset
  2. 更新当前key的缓存过期时间,这一步主要是为了定期清理掉冷数据,和上面我提到的常见错误设计2中的意义不同。
  3. 删除窗口之外的数据记录。
  4. 统计当前窗口中的总记录数。
  5. 如果记录数大于阈值,则直接返回错误,否则正常处理用户请求。

e0tcMj

基于SpringBoot和AOP的限流

这一部分主要介绍具体的实现逻辑。

定义注解和处理逻辑

首先是定义一个注解,方便后续对不同接口使用不同的限制频率。

/**  
 * 接口访问频率注解,默认一分钟只能访问5次  
 */  
@Documented  
@Target(ElementType.METHOD)  
@Retention(RetentionPolicy.RUNTIME)  
public @interface RequestLimit {  
  
    // 限制时间 单位:秒(默认值:一分钟)  
    long period() default 60;  
  
    // 允许请求的次数(默认值:5次)  
    long count() default 5;  
  
}

在实现逻辑这块,我们定义一个切面函数,拦截用户的request,具体实现流程和上面介绍的限流流程一致,主要涉及到redis zset的操作。


@Aspect
@Component
@Log4j2
public class RequestLimitAspect {

    @Autowired
    RedisTemplate redisTemplate;

    // 切点
    @Pointcut("@annotation(requestLimit)")
    public void controllerAspect(RequestLimit requestLimit) {}

    @Around("controllerAspect(requestLimit)")
    public Object doAround(ProceedingJoinPoint joinPoint, RequestLimit requestLimit) throws Throwable {
        // get parameter from annotation
        long period = requestLimit.period();
        long limitCount = requestLimit.count();

        // request info
        String ip = RequestUtil.getClientIpAddress();
        String uri = RequestUtil.getRequestUri();
        String key = "req_limit_".concat(uri).concat(ip);

        ZSetOperations zSetOperations = redisTemplate.opsForZSet();

        // add current timestamp
        long currentMs = System.currentTimeMillis();
        zSetOperations.add(key, currentMs, currentMs);

        // set the expiration time for the code user
        redisTemplate.expire(key, period, TimeUnit.SECONDS);

        // remove the value that out of current window
        zSetOperations.removeRangeByScore(key, 0, currentMs - period * 1000);

        // check all available count
        Long count = zSetOperations.zCard(key);

        if (count > limitCount) {
            log.error("接口拦截:{} 请求超过限制频率【{}次/{}s】,IP为{}", uri, limitCount, period, ip);
            throw new AuroraRuntimeException(ResponseCode.TOO_FREQUENT_VISIT);
        }

        // execute the user request
        return  joinPoint.proceed();
    }

}

使用注解进行限流控制

这里我定义了一个接口类来做测试,使用上面的annotation来完成限流,每分钟允许用户访问3次。

@Log4j2  
@RestController  
@RequestMapping("/user")  
public class UserController {    

    @GetMapping("/test")  
    @RequestLimit(count = 3)  
    public GenericResponse<String> testRequestLimit() {  
        log.info("current time: " + new Date());  
        return new GenericResponse<>(ResponseCode.SUCCESS);  
    }  
  
}

我接着在不同机器上,访问该接口,可以看到不同机器的限流是隔离的,并且每台机器在周期之内只能访问三次,超过后,需要等待一定时间才能继续访问,达到了我们预期的效果。

2023-05-21 11:23:15.733  INFO 99636 --- [nio-8080-exec-1] c.v.c.a.api.controller.UserController    : current time: Sun May 21 11:23:15 CST 2023
2023-05-21 11:23:21.848  INFO 99636 --- [nio-8080-exec-3] c.v.c.a.api.controller.UserController    : current time: Sun May 21 11:23:21 CST 2023
2023-05-21 11:23:23.044  INFO 99636 --- [nio-8080-exec-4] c.v.c.a.api.controller.UserController    : current time: Sun May 21 11:23:23 CST 2023
2023-05-21 11:23:25.920 ERROR 99636 --- [nio-8080-exec-5] c.v.c.a.annotation.RequestLimitAspect    : 接口拦截:/user/test 请求超过限制频率【3次/60s】,IP为0:0:0:0:0:0:0:1
2023-05-21 11:23:28.761 ERROR 99636 --- [nio-8080-exec-6] c.v.c.a.annotation.RequestLimitAspect    : 接口拦截:/user/test 请求超过限制频率【3次/60s】,IP为0:0:0:0:0:0:0:1
2023-05-21 11:24:12.207  INFO 99636 --- [io-8080-exec-10] c.v.c.a.api.controller.UserController    : current time: Sun May 21 11:24:12 CST 2023
2023-05-21 11:24:19.100  INFO 99636 --- [nio-8080-exec-2] c.v.c.a.api.controller.UserController    : current time: Sun May 21 11:24:19 CST 2023
2023-05-21 11:24:20.117  INFO 99636 --- [nio-8080-exec-1] c.v.c.a.api.controller.UserController    : current time: Sun May 21 11:24:20 CST 2023
2023-05-21 11:24:21.146 ERROR 99636 --- [nio-8080-exec-3] c.v.c.a.annotation.RequestLimitAspect    : 接口拦截:/user/test 请求超过限制频率【3次/60s】,IP为192.168.31.114
2023-05-21 11:24:26.779 ERROR 99636 --- [nio-8080-exec-4] c.v.c.a.annotation.RequestLimitAspect    : 接口拦截:/user/test 请求超过限制频率【3次/60s】,IP为192.168.31.114
2023-05-21 11:24:29.344 ERROR 99636 --- [nio-8080-exec-5] c.v.c.a.annotation.RequestLimitAspect    : 接口拦截:/user/test 请求超过限制频率【3次/60s】,IP为192.168.31.114

欢迎关注公众号【码老思】,只讲最通俗易懂的原创技术干货。

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

相关文章

  • 一篇文章带你了解Python的迭代知识

    点击上方“Go语言进阶学习”,进行关注回复“Go语言”即可获赠从入门到进阶共10本电子书今日鸡汤偶然值林叟,谈笑无还期。一、前言大家好,我是Go进阶者。如果给定一个list或tuple,可以通过for循环来遍历这个list或tuple,这种遍历称为迭代(Iteration)。二、案例在Python中,迭代是通过for…in来完成的,而很多语言比如C或者Java,迭代list是通过下标完成的。比如Java代码:for(i=0;i<list.length;i++){ n=list[i]; }复制注: 可以看出,Python的for循环抽象程度要高于Java的for循环,因为Python的for循环不仅可以用在list或tuple上,还可以作用在其他可迭代对象上。list这种数据类型虽然有下标,但很多其他数据类型是没有下标的,但是,只要是可迭代对象,无论有无下标,都可以迭代,比如dict就可以迭代。d={'a':1,'b':2,'c':3} forkeyind: print(key)复制注:因为dict的存储不是按照list的

  • 软件工程中的部署管道(CI/CD)

    软件工程团队中的管道是一组自动化的流程,使开发人员和DevOps专业人员能够可靠,高效地编译,构建并将代码部署到生产计算平台。没有硬性规定可以说明管道需要什么样的内容以及必须使用的工具,但是管道最常见的组件是:构建自动化/持续集成,测试自动化和部署自动化。管道通常由一组工具组成,这些工具通常分为以下几类:源代码控制构建工具容器化配置管理监控方式软件交付管道的主要目标是自动化,在管道的任何步骤之中或之间都无需手工步骤或进行任何更改。手动执行这些无聊且重复的任务时,确实会发生人为错误,并且最终会因部署不足而影响可交付成果的能力以及潜在的SLA。部署管道部署管道是从版本控制中获取代码并以自动化方式将其提供给应用程序用户的过程。当一组开发人员从事项目或功能时,他们需要可靠且有效的方式来构建,测试和部署其工作。从历史上看,这将是一个手动过程,涉及很多沟通和很多人为错误。典型的部署流程的阶段如下: 部署管道版本控制通常,从事代码工作的软件开发人员会将所做的更改提交到源代码管理中(例如github)。提交源代码管理后,将启动部署管道的第一阶段,该阶段将触发代码编译,单元测试,代码分析和安装程序创建。

  • 电力时钟应用介绍

    电力时钟系统又叫做变电站时钟系统,用卫星标准时间作基准参考,提供高可靠性、高冗余度的时间基准信号,并采用先进的锁相技术,使守时电路输出的时间同步信号精密同步在GPS/外部B码时间基准上,输出短期和长期稳定度都十分优良的高精度同步信号。电力时钟系统采用精准的测频与智能驯服算法,使锁定的晶振/铷钟频率信号与GPS卫星/北斗卫星/外部B码时间基准保持精密同步。由于装置输出的1PPS等时间信号是内置振荡器的分频秒信号输出,同步于GPS/北斗信号但并不受GPS/北斗秒脉冲信号跳变带来的影响,相当于UTC时间基准的复现。采用了“智能学习算法”的GPS时钟,在驯服晶振过程中能够不断“学习”晶振的运行特性,并将这些参数存入板载存储器中。当外部B码时间基准出现异常或不可用时,装置能够自动切换到内部守时状态,并依据板主板中对晶体或者铷钟特性进行补偿,使守时电路继续提供高可靠性的时间信息输出,同时避免了因晶体振荡器老化造成的频偏对守时指标的影响。SYN4505型电力时钟具有智能状态切换功能,能够智能判别两路外部B码时间基准信号的稳定性和优劣,并提供多种时间基准配置方法。当外部送来的主外部时间基准(B码输入1

  • 编程坑太多,Map 集合怎么也有这么多坑?一不小心又踩了好几个!

    上一篇List踩坑文章中,我们提到几个比较容易踩坑的点。作为List集合好兄弟Map,我们也是天天都在使用,一不小心也会踩坑。今天我就来总结这些常见的坑,再捞自己一手,防止后续同学再继续踩坑。本文设计知识点如下:不是所有的Map都能包含null这个踩坑经历还是发生在实习的时候,那时候有这样一段业务代码,功能很简单,从XML中读取相关配置,存入Map中。代码示例如下:那时候正好有个小需求,需要改动一下这段业务代码。改动的过程中,突然想到HashMap并发过程可能导致死锁的问题。于是改动了一下这段代码,将HashMap修改成了ConcurrentHashMap。美滋滋提交了代码,然后当天上线的时候,就发现炸了。。。应用启动过程发生NPE问题,导致应用启动失败。根据异常日志,很快就定位到了问题原因。由于XML某一项配置问题,导致读取元素为null,然后元素置入到ConcurrentHashMap中,抛出了空指针异常。这不科学啊!之前HashMap都没问题,都可以存在null,为什么它老弟ConcurrentHashMap就不可以?翻阅了一下ConcurrentHashMap#put方法的源码

  • Java NIO-12.NIO和IO

    学习了JavaNIO和IOAPI之后,就有了一个问题: 什么时候用IO,什么时候用NIO? 本文将试着阐明JavaNIO和IO之间使用上的区别,以及它们是如何影响到你的代码设计的。JavaNIO和IO之间的主要区别IONIO面向流面向缓冲区阻塞IO非阻塞IO选择器下面的表格总结了JavaNIO和IO的区别。表格后面对更多的细节进行说明。IONIO面向流面向缓冲区阻塞IO非阻塞IO选择器面向流与面向缓冲区第一个大的区别就是IO是面向流的,而NIO是面向缓冲区的。什么意思呢? JavaIO是面向流的,就是说从流中一次性读取一个或者多个字节。无论读取出来的数据怎样使用,它们都不会被缓存。此外,流中的数据也不能被前后移动。如果需要前后移动流中的数据,就需要先将它们存在缓冲区中。 JavaNIO的面向缓冲区方式有点不同。数据被读到一个稍后才使用的缓冲区。缓冲区中的数据能根据需要前后移动。这样在处理中提供了很大的灵活性。但是,也需要检查缓冲区中的数据是否包含了需要处理的所有数据。此外,往缓冲区中读取更多的数据时,需要确认没有覆盖掉还未处理的数据。阻塞和非阻塞IOJavaIO中的各种流是阻塞的。这意

  • Jmockdata随机模拟 Java 数据插件

       Jmockdta是一款实现模拟JAVA类型或对象的实例化并随机初始化对象的数据的工具框架。单元测试的利器。 Theplug-inofJmockdatawhatthroughrandomalgorithmmockjavadata. Jmockdata插件通过随机算法模拟Java数据.具体介绍和用法可以参考如下:模拟数据入口方法JMockData.mock(JmockDataWrapper)被模拟数据必须继承JmockDataWrapper经过它的包装被模拟的数据最好是plainbean,只提供getter,setter,has,is方法的才可以被模拟框架默认实现了40个元数据类型的数据模拟器包括: short.class,Short.class,short[].class,Short[].class,int.class,Integer.class,int[].class,Integer[].class,long.class,Long.class,long[].class,Long[].class,float.class,Float.class,float[].class,Float

  • 增删改查的查之简单查询

    导读 软件测试人员在工作使用SQL语言中的查询是使用得最多的,而查询也是SQL语言中最复杂的,很多测试人员只使用到其中最简单的查询1.数据库的使用现在在任何项目中都有数据的存在,那么在测试过程中查看数据库中的数据是必不可少的步骤,那什么情况下测试人员会查看数据库呢?比如有一个测试场景是注册新用户,用户在前端页面上添加了一个新用户,点击提交后,弹出提示用户注册成功。这时预期结果中就应该包含查询数据库:查询user表中新增一条数据,数据字段的信息与注册信息一致;查询password表中新增一条数据,字段信息显示正确,其中密码字段为加密后的字符串。再比如有一个测试场景是一个用户向另一个用户转账100元,用户在前端页面发起转账,界面显示转账成功。预期结果中查询数据库的信息应为:交易流水表中新增一条数据,数据字段的信息显示正确,其中转账金额为100元,手续费为0元;查询用户账户表,发起用户的账户余额为XXX元,接收用户的账户余额为XXX元。这些语句包含在测试用例中的预期结果中,而在执行测试时就需要根据用例所描述的信息去查询数据库。软件测试人员在执行测试时使用最多的语句就是查询(SELECT)语句

  • ASP.NET 路由

    ASP.NET路由使您可以使用不必映射到网站中特定文件的URL。由于URL不必映射到文件,所以可以在Web应用程序中使用URL,这些URL是描述性的用户操作,因此更易于被用户理解。在一个不使用路由的ASP.NET应用程序中,对URL的传入请求通常映射到磁盘上的物理文件,如.aspx文件。在ASP.NET路由中,您可以定义URL模式,该模式包含在处理URL请求时使用的值的占位符。在运行时,应用程序名称后面的URL部分根据您所定义的URL模式分析为离散值。ASP.NET路由不同于其他URL重写方案。URL重写通过在将请求发送到网页之前实际更改URL来处理传入请求。此外,URL重写通常没有相应的API来创建基于模式的URL。在URL重写中,如果更改了URL模式,则必须手动更新包含原始URL的所有超链接。由于ASP.NET路由可以从URL提取值,所以处理传入请求时不更改URL。如果必须创建一个URL,则将参数值传递到为您生成URL的方法中。若要更改URL模式,请在某位置更改该模式,您在应用程序中创建的基于该模式的所有链接将自动使用新模式。定义的URL模式称作“路由”。在路由中,您可以指定占位符

  • ggplot2优雅的绘制镶嵌条形图

    ❝本节来介绍如何使用「ggplot2」来绘制镶嵌条形图,下面通过一个小例子来展示 ❞加载R包library(tidyverse) library(camcorder) library(ggtext) 复制导入数据incl_gen_2019<-read_tsv("incl_gen_2019.xls")%>% mutate(OECD=rowMeans(select(.,3:last_col())))%>% rename(provisions=1)%>% add_row(provisions="Allprovisions",!!!colMeans(.[-1],na.rm=TRUE))%>% pivot_longer(2:last_col(),names_to="country")%>% mutate(year=2019,.before=1) 复制incl_gen_1999<-read_tsv("data1990.xls")%>% mutate(OECD=rowMea

  • 腾讯云证书监控SSLPod公共参数调用方式

    公共参数是用于标识用户和接口签名的参数,如非必要,在每个接口单独的接口文档中不再对这些参数进行说明,但每次请求均需要携带这些参数,才能正常发起请求。 公共参数的具体内容会因您使用的签名方法版本不同而有所差异。 使用签名方法v3的公共参数签名方法v3(有时也称作TC3-HMAC-SHA256)相比签名方法v1(有些文档可能会简称签名方法),更安全,支持更大的请求包,支持POSTJSON格式,性能有一定提升,推荐使用该签名方法计算签名。完整介绍详见签名方法v3。 注意:接口文档中的示例由于目的是展示接口参数用法,简化起见,使用的是签名方法v1GET请求,如果依旧想使用签名方法v1请参考下文章节。 使用签名方法v3时,公共参数需要统一放到HTTPHeader请求头部中,如下表所示: 参数名称 类型 必选 描述 Action String 是 HTTP请求头:X-TC-Action。操作的接口名称。取值参考接口文档中输入参数公共参数Action的说明。例如云服务器的查询实例列表接口,取值为DescribeInstances。 Region String - HTTP请求头:X-

  • Python自动化测试 (九)urllib2 发送HTTP Request

    urllib2是Python自带的标准模块,用来发送HTTPRequest的。 类似于.NET中的, HttpWebRequest类   urllib2的优点 Pythonurllib2发出的HTTPRequest,能自动被Fiddler截获,方便了调试。 Python可以自动处理Cookie   urllib2的缺点 Pythonurllib2发出的httpRequest,中的header会被修改成“首字母大写”, 比如你的代码里写的header是:content-TYPE=application/x-www-form-urlencoded, 会被修改为Content-Type=application/x-www-form-urlencoded   实例一, Get方法,并且自定义header   #-*-coding:UTF-8-*- importurllib2 request=urllib2.Request("http://www.baidu.com/") request.add_header('

  • 【政治经济学】《马克思主义政治经济学概论》概念梳理

    一、导论 生产方式 生产方式:是指社会生活所必需的物质资料的某得方式。在生产过程中形成的人与自然界和人与人之间的相互关系的体系。 其中人与自然之间的关系是生产力。是生产的自然属性。生产力指的是人类利用自然和改造自然、进行物质资料生产的能力。它主要包括三个要素:劳动者、劳动资料和劳动对象 人与人之间的关系是生产关系。生产关系又分为狭义和广义两个层次。 狭义的生产方式是指直接生产过程中形成的经济关系,重要的是生产资料的所有制关系。 广义的生产关系是指再生产(连续不断地社会生产)中的经济关系,包括生产、分配、交换、消费四个相互联系的环节 所以,生产方式包括生产力和生产关系。 生产力与生产关系的关系:生产关系受生产力决定,同时又对生产力具有反作用,生产关系的总和构成社会的经济基础。生产力和生产关系是社会生产过程中两个不可分割的方面, 生产力代表生产的物质内容 生产关系则是生产的社会形式 一定生产力的发展水平和发展阶段,决定着一定的生产关系。 生产力的发展变化和发展需求,决定着生产关系的产生、发展和更替变化。 当生产关系适应生产力的发展水平和发展阶段时,他就积极地推动生产力的

  • SQL设置SQLServer最大连接数查询语句

    设置最大连接数 下面的T-SQL语句可以配置SQLServer允许的并发用户连接的最大数目。 execsp_configure'showadvancedoptions',1execsp_configure'userconnections',100第一句用以表示显示sp_configure 系统存储过程高级选项,使用userconnections时,要求showadvancedoptions值为1。 第二句配置最大连接数为100,0表示不限制,但不表示无限,后面将谈谈。 也可以在企业管理器中配置,在企业管理器中,可以在实例上点右键->“属性”->“连接”里面更改。 需要重新启动SQLServer,该值才会生效。 @@max_connections select@@max_connections它总是返回32767,它并不是指上面设置的userconnections,实际上它表示userconnections最大可设置为多少。由于它的最大值是32767,那么userconnections为0时,最大

  • 原型链相关

    1.创建对象有几种方法 复制 //第一种 varobject1={name:'fang'}; varobject2=newObject({name:'fang'}); console.log(object1,object2);  //Object{name:"fang"}Object{name:"fang"} //第二种 varM=function(name){this.name=name}; varobject3=newM('fang'); console.log(object3);  //M{name:"fang"} //第三种 varobj={name:'fang'}; varobject4=Object.create(obj); console.log(object4);  //Object{} 2.原型、构造函数、实例、原型链 构造函数:用来在创建对象时初始化对象。特点:构造函数名一般为大写字母开头;与new运算符一起使用来实例化对象 原型:构造函数在创建的过程中,系统自动创建出来与构造函数相关联的一个空的对象。可以由构造函数.prototype来访问到。 原型

  • 关于视典系统问题的反馈方案

    测试调整8-20 一、系统BUG 1、明细打印弹窗报错:如果实收金额有为0的项目,则明细打印报错,如图:    2、门诊检查:输入配镜处方保存后自动生成了配镜病历,但是在这个自动生成的配镜病历中,提取的信息多了一部分,如下图红框所示,红框以下的都提取了历史数据,并非今日的公共检查数据,所以不应该自动提取这些历史档案数据     二、功能修改  1、退费:退款方式,增加代收款、代金券; 定金分期:退款方式增加充值卡退款、代收款、代金券 说明:金额也需要按照指定的退款方式中相应的减掉      2、收费详单、定金详单: 1)当有退款的时候,在相应的详单下面生成一条新的退的详单,数量显示实际退掉的数量,其中金额为负数,如“-320”     2)收费详单、定金详单的收费种类下拉,增加空白选项,即全选。同时这2个页面,把参数1和参数2合并为一列“项目详情”,把除了项目名称以外的字段参数都显示出来,用斜线/来分隔  3)、开单和查询开单:开单的详单内容和查询开单的详单内容,参数

  • Filter、Listener、Interceptor、Controller in a Request

    从以下程序运行Log 可以看出在一个Request 执行过程中 MyListener>>requestInitialized >>> MyFilter>>>doFilter  >>>  MyInterceptor1>>>preHandle >>>   Controllermethod  >>> MyInterceptor1>>>postHandle  >>> MyInterceptor1>>>afterCompletion  >>>  MyListener>>requestDestroyed       &nb

  • Linux命令记录-环境准备(一)

    准备工作 1.配置好网络:原始查看ipaddr/ipa2.使用ifconfig:yuminstall-ynat-tools3.使用wget:yuminstall-ywget4.替换默认源(163): http://mirrors.163.com/.help/centos.html 1.源文件备份:mv/etc/yum.repos.d/CentOS-Base.repo/etc/yum.repos.d/CentOS-Base.repo.backup 2.再下载Centos,并且将其改成默认源文件: wgethttp://mirrors.163.com/.help/CentOS7-Base-163.repo mvCentOS7-Base-163.repo/etc/yum.repos.d/CentOS-Base.repo 3.运行命令生成缓存 yumcleanall yummakecache5.安装vim:yuminstall-yvim         积极竞争    &n

  • 拉取分支代码,修改代码之后再上传到该分支

    首先新建文件夹,在文件夹里右键gitbashhere, gitclone地址,用vscode打开代码,修改好代码之后, 首先gitbranch-a查看远程分支,gitcheckouttest切换到test分支,gitbranch查看当前分支, gitpull,gitadd.,gitcommit-m"提交代码到test分支",gitpushorigintest

  • Sql 中常用日期转换Convert(Datetime)

    CONVERT(data_type,expression[,style]) convert(varchar(10),字段名,转换格式) 说明:此样式一般在时间类型(datetime,smalldatetime)与字符串类型(nchar,nvarchar,char,varchar)相互转换的时候才用到. 语句结果 SELECTCONVERT(varchar(100),GETDATE(),0)071520094:06PM SELECTCONVERT(varchar(100),GETDATE(),1)07/15/09 SELECTCONVERT(varchar(100),GETDATE(),2)09.07.15 SELECTCONVERT(varchar(100),GETDATE(),3)15/07/09 SELECTCONVERT(varchar(100),GETDATE(),4)15.07.09 SELECTCONVERT(varchar(100),GETDATE(),5)15-07-09 SELECTCONVERT(varchar(100),GETDATE(),6)1507

  • 详解Java泛型type体系整理

    一直对jdk的ref使用比较模糊,早上花了点时间简单的整理了下,也帮助自己理解一下泛型的一些处理。 java中class,method,field的继承体系 java中所有对象的类型定义类Type 说明: Type:TypeisthecommonsuperinterfaceforalltypesintheJavaprogramminglanguage.Theseincluderawtypes,parameterizedtypes,arraytypes,typevariablesandprimitivetypes. 使用 一般我们不直接操作Type类型,所以第一次使用会对这个比较陌生,相对内部的一些概念。 根据Type类型分类,整理了一个type->class的转换过程,同理也包括处理GenericType。支持多级泛型处理。 Java代码 1privatestaticClassgetClass(Typetype,inti){ 2if(typeinstanceofParameterizedType){//处理泛型类型 3returngetGenericClass((Param

  • Python问题记录

    问题一: can't find '__main__' module in '.' 卡在PyCharm软件很久,刚开始安装的Python3.2,PyCharm无法运行;今天发现版本错了,更换成Python3.7.输入HelloWorld。结果,软件提示: can't find '__main__' module in '.' 百度后发现,很多新手都遇见了此问题,解决起来很简单:Run/DebugConfigurations处ScriptPathon:要具体到某一个.py文件。  借用一下别人的截图(原文在此):   问题二:io.UnsupportedOperation:notwritable open函数,有r(只读)、w(只写)模式,默认是r,需要指定为w才能写入。 可以查看此文章学习:python操作文件  

相关推荐

推荐阅读