三次输错密码后,系统是怎么做到不让我继续尝试的?

故事背景

忘记密码这件事,相信绝大多数人都遇到过,输一次错一次,错到几次以上,就不允许你继续尝试了。

但当你尝试重置密码,又发现新密码不能和原密码重复:

相信此刻心情只能用一张图形容:

虽然,但是,密码还是很重要的,顺便我有了一个问题:三次输错密码后,系统是怎么做到不让我继续尝试的?

我想了想,有如下几个问题需要搞定

  1. 是只有输错密码才锁定,还是账户名和密码任何一个输错就锁定?
  2. 输错之后也不是完全冻结,为啥隔了几分钟又可以重新输了?
  3. 技术栈到底麻不麻烦?

去网上搜了搜,也问了下ChatGPT,找到一套解决方案:SpringBoot+Redis+Lua脚本。
这套方案也不算新,很早就有人在用了,不过难得是自己想到的问题和解法,就记录一下吧。

顺便回答一下上面的三个问题:

  1. 锁定的是IP,不是输入的账户名或者密码,也就是说任一一个输错3次就会被锁定
  2. Redis的Lua脚本中实现了key过期策略,当key消失时锁定自然也就消失了
  3. 技术栈同SpringBoot+Redis+Lua脚本

那么自己动手实现一下

前端部分

首先写一个账密输入页面,使用很简单HTML加表单提交

<!DOCTYPE html>
<html>
<head>
	<title>登录页面</title>
	<style>
		body {
			background-color: #F5F5F5;
		}
		form {
			width: 300px;
			margin: 0 auto;
			margin-top: 100px;
			padding: 20px;
			background-color: white;
			border-radius: 5px;
			box-shadow: 0 0 10px rgba(0,0,0,0.2);
		}
		label {
			display: block;
			margin-bottom: 10px;
		}
		input[type="text"], input[type="password"] {
			border: none;
			padding: 10px;
			margin-bottom: 20px;
			border-radius: 5px;
			box-shadow: 0 0 5px rgba(0,0,0,0.1);
			width: 100%;
			box-sizing: border-box;
			font-size: 16px;
		}
		input[type="submit"] {
			background-color: #30B0F0;
			color: white;
			border: none;
			padding: 10px;
			border-radius: 5px;
			box-shadow: 0 0 5px rgba(0,0,0,0.1);
			width: 100%;
			font-size: 16px;
			cursor: pointer;
		}
		input[type="submit"]:hover {
			background-color: #1C90D6;
		}
	</style>
</head>
<body>
	<form action="http://localhost:8080/login" method="get">
		<label for="username">用户名</label>
		<input type="text" id="username" name="username" placeholder="请输入用户名" required>
		<label for="password">密码</label>
		<input type="password" id="password" name="password" placeholder="请输入密码" required>
		<input type="submit" value="登录">
	</form>
</body>
</html>

效果如下:

后端部分

技术选型分析

首先我们画一个流程图来分析一下这个登录限制流程

从流程图上看,首先访问次数的统计与判断不是在登录逻辑执行后,而是执行前就加1了;
其次登录逻辑的成功与失败并不会影响到次数的统计;
最后还有一点流程图上没有体现出来,这个次数的统计是有过期时间的,当过期之后又可以重新登录了。

那为什么是Redis+Lua脚本呢?

Redis的选择不难看出,这个流程比较重要的是存在一个用来计数的变量,这个变量既要满足分布式读写需求,还要满足全局递增或递减的需求,那Redis的incr方法是最优选了。
那为什么需要Lua脚本呢?流程上在验证用户操作前有些操作,如图:

这里至少有3步Redis的操作,get、incr、expire,如果全放到应用里面来操作,有点慢且浪费资源。

Lua脚本的优点如下:

  • 减少网络开销。可以将多个请求通过脚本的形式一次发送,减少网络时延。
  • 原子操作。Redis会将整个脚本作为一个整体执行,中间不会被其他请求插入。因此在脚本运行过程中无需担心会出现竞态条件,无需使用事务。
  • 复用。客户端发送的脚本会永久存在redis中,这样其他客户端可以复用这一脚本,而不需要使用代码完成相同的逻辑。

最后为了增加功能的复用性,我打算使用Java注解的方式实现这个功能。

代码实现

项目结构如下

配置文件

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.11</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>LoginLimit</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>LoginLimit</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!-- Jedis -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>
        <!--切面依赖 -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
        </dependency>
        <!-- commons-lang3 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
        <!-- guava -->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>23.0</version>
        </dependency>
        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

application.properties

## Redis配置
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=
spring.redis.timeout=1000
## Jedis配置
spring.redis.jedis.pool.min-idle=0
spring.redis.jedis.pool.max-idle=500
spring.redis.jedis.pool.max-active=2000
spring.redis.jedis.pool.max-wait=10000
  

注解部分

LimitCount.java
package com.example.loginlimit.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 次数限制注解
 * 作用在接口方法上
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LimitCount {
    /**
     * 资源名称,用于描述接口功能
     */
    String name() default "";

    /**
     * 资源 key
     */
    String key() default "";

    /**
     * key prefix
     *
     * @return
     */
    String prefix() default "";

    /**
     * 时间的,单位秒
     * 默认60s过期
     */
    int period() default 60;

    /**
     * 限制访问次数
     * 默认3次
     */
    int count() default 3;
}
核心处理逻辑类:LimitCountAspect.java
package com.example.loginlimit.aspect;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Objects;

import javax.servlet.http.HttpServletRequest;

import com.example.loginlimit.annotation.LimitCount;
import com.example.loginlimit.util.IPUtil;
import com.google.common.collect.ImmutableList;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

@Slf4j
@Aspect
@Component
public class LimitCountAspect {

    private final RedisTemplate<String, Serializable> limitRedisTemplate;

    @Autowired
    public LimitCountAspect(RedisTemplate<String, Serializable> limitRedisTemplate) {
        this.limitRedisTemplate = limitRedisTemplate;
    }

    @Pointcut("@annotation(com.example.loginlimit.annotation.LimitCount)")
    public void pointcut() {
        // do nothing
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        HttpServletRequest request = ((ServletRequestAttributes)Objects.requireNonNull(
            RequestContextHolder.getRequestAttributes())).getRequest();

        MethodSignature signature = (MethodSignature)point.getSignature();
        Method method = signature.getMethod();
        LimitCount annotation = method.getAnnotation(LimitCount.class);
        //注解名称
        String name = annotation.name();
        //注解key
        String key = annotation.key();
        //访问IP
        String ip = IPUtil.getIpAddr(request);
        //过期时间
        int limitPeriod = annotation.period();
        //过期次数
        int limitCount = annotation.count();

        ImmutableList<String> keys = ImmutableList.of(StringUtils.join(annotation.prefix() + "_", key, ip));
        String luaScript = buildLuaScript();
        RedisScript<Number> redisScript = new DefaultRedisScript<>(luaScript, Number.class);
        Number count = limitRedisTemplate.execute(redisScript, keys, limitCount, limitPeriod);
        log.info("IP:{} 第 {} 次访问key为 {},描述为 [{}] 的接口", ip, count, keys, name);
        if (count != null && count.intValue() <= limitCount) {
            return point.proceed();
        } else {
            return "接口访问超出频率限制";
        }
    }

    /**
     * 限流脚本
     * 调用的时候不超过阈值,则直接返回并执行计算器自加。
     *
     * @return lua脚本
     */
    private String buildLuaScript() {
        return "local c" +
            "\nc = redis.call('get',KEYS[1])" +
            "\nif c and tonumber(c) > tonumber(ARGV[1]) then" +
            "\nreturn c;" +
            "\nend" +
            "\nc = redis.call('incr',KEYS[1])" +
            "\nif tonumber(c) == 1 then" +
            "\nredis.call('expire',KEYS[1],ARGV[2])" +
            "\nend" +
            "\nreturn c;";
    }

}

获取IP地址的功能我写了一个工具类IPUtil.java,代码如下:

package com.example.loginlimit.util;

import javax.servlet.http.HttpServletRequest;

public class IPUtil {

    private static final String UNKNOWN = "unknown";

    protected IPUtil() {

    }

    /**
     * 获取 IP地址
     * 使用 Nginx等反向代理软件, 则不能通过 request.getRemoteAddr()获取 IP地址
     * 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,
     * X-Forwarded-For中第一个非 unknown的有效IP字符串,则为真实IP地址
     */
    public static String getIpAddr(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
    }

}

另外就是Lua限流脚本的说明,脚本代码如下:

  private String buildLuaScript() {
        return "local c" +
            "\nc = redis.call('get',KEYS[1])" +
            "\nif c and tonumber(c) > tonumber(ARGV[1]) then" +
            "\nreturn c;" +
            "\nend" +
            "\nc = redis.call('incr',KEYS[1])" +
            "\nif tonumber(c) == 1 then" +
            "\nredis.call('expire',KEYS[1],ARGV[2])" +
            "\nend" +
            "\nreturn c;";
    }

这段脚本有一个判断, tonumber(c) > tonumber(ARGV[1])这行表示如果当前key 的值大于了limitCount,直接返回;否则调用incr方法进行累加1,且调用expire方法设置过期时间。

最后就是RedisConfig.java,代码如下:

package com.example.loginlimit.config;

import java.io.IOException;
import java.io.Serializable;
import java.time.Duration;
import java.util.Arrays;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

@Configuration
public class RedisConfig extends CachingConfigurerSupport {

    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.port}")
    private int port;

    @Value("${spring.redis.password}")
    private String password;

    @Value("${spring.redis.timeout}")
    private int timeout;

    @Value("${spring.redis.jedis.pool.max-idle}")
    private int maxIdle;

    @Value("${spring.redis.jedis.pool.max-wait}")
    private long maxWaitMillis;

    @Value("${spring.redis.database:0}")
    private int database;

    @Bean
    public JedisPool redisPoolFactory() {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxIdle(maxIdle);
        jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
        if (StringUtils.isNotBlank(password)) {
            return new JedisPool(jedisPoolConfig, host, port, timeout, password, database);
        } else {
            return new JedisPool(jedisPoolConfig, host, port, timeout, null, database);
        }
    }

    @Bean
    JedisConnectionFactory jedisConnectionFactory() {
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
        redisStandaloneConfiguration.setHostName(host);
        redisStandaloneConfiguration.setPort(port);
        redisStandaloneConfiguration.setPassword(RedisPassword.of(password));
        redisStandaloneConfiguration.setDatabase(database);

        JedisClientConfiguration.JedisClientConfigurationBuilder jedisClientConfiguration = JedisClientConfiguration
            .builder();
        jedisClientConfiguration.connectTimeout(Duration.ofMillis(timeout));
        jedisClientConfiguration.usePooling();
        return new JedisConnectionFactory(redisStandaloneConfiguration, jedisClientConfiguration.build());
    }

    @Bean(name = "redisTemplate")
    @SuppressWarnings({"rawtypes"})
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        //使用 fastjson 序列化
        JacksonRedisSerializer jacksonRedisSerializer = new JacksonRedisSerializer<>(Object.class);
        // value 值的序列化采用 fastJsonRedisSerializer
        template.setValueSerializer(jacksonRedisSerializer);
        template.setHashValueSerializer(jacksonRedisSerializer);
        // key 的序列化采用 StringRedisSerializer
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());

        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    //缓存管理器
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager.RedisCacheManagerBuilder
            .fromConnectionFactory(redisConnectionFactory);
        return builder.build();
    }

    @Bean
    @ConditionalOnMissingBean(StringRedisTemplate.class)
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    public KeyGenerator wiselyKeyGenerator() {
        return (target, method, params) -> {
            StringBuilder sb = new StringBuilder();
            sb.append(target.getClass().getName());
            sb.append(method.getName());
            Arrays.stream(params).map(Object::toString).forEach(sb::append);
            return sb.toString();
        };
    }

    @Bean
    public RedisTemplate<String, Serializable> limitRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Serializable> template = new RedisTemplate<>();
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}

class JacksonRedisSerializer<T> implements RedisSerializer<T> {
    private Class<T> clazz;
    private ObjectMapper mapper;

    JacksonRedisSerializer(Class<T> clazz) {
        super();
        this.clazz = clazz;
        this.mapper = new ObjectMapper();
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    }

    @Override
    public byte[] serialize(T t) throws SerializationException {
        try {
            return mapper.writeValueAsBytes(t);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public T deserialize(byte[] bytes) throws SerializationException {
        if (bytes.length <= 0) {
            return null;
        }
        try {
            return mapper.readValue(bytes, clazz);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
}

LoginController.java
package com.example.loginlimit.controller;

import javax.servlet.http.HttpServletRequest;

import com.example.loginlimit.annotation.LimitCount;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
public class LoginController {

    @GetMapping("/login")
    @LimitCount(key = "login", name = "登录接口", prefix = "limit")
    public String login(
        @RequestParam(required = true) String username,
        @RequestParam(required = true) String password, HttpServletRequest request) throws Exception {
        if (StringUtils.equals("张三", username) && StringUtils.equals("123456", password)) {
            return "登录成功";
        }
        return "账户名或密码错误";
    }

}
LoginLimitApplication.java
package com.example.loginlimit;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class LoginLimitApplication {

    public static void main(String[] args) {
        SpringApplication.run(LoginLimitApplication.class, args);
    }

}

演示一下效果

上面这套限流的逻辑感觉用在小型或中型的项目上应该问题不大,不过目前的登录很少有直接锁定账号不能输入的,一般都是弹出一个验证码框,让你输入验证码再提交。我觉得用我这套逻辑改改应该不成问题,核心还是接口尝试次数的限制嘛!刚好我还写过SpringBoot生成图形验证码的文章:SpringBoot整合kaptcha实现图片验证码功能,哪天再来试试这套逻辑~

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

相关文章

  • 得益于5G iPhone,中国消费者推动苹果在中国实现第二季度新高

    南华早报总部位于加州的Cupertino周三表示,在5GiPhone的强劲需求以及首次购买Mac电脑和iPad的推动下,苹果在包括香港和台湾在内的大中华区的销售额在截至3月的季度几乎翻了一番。该地区收入增长87.5%,至177亿美元,是苹果第二季度在大中华区收入最高的一年。在此期间,包括农历新年假期在内,苹果的全球收入占了近20%。苹果首席执行官蒂姆•库克(TimCook)在与分析师的电话会议上表示,苹果"对中国消费者对iPhone12系列的反应感到特别高兴"。他补充说,本季度中国城市最畅销的两款机型来自该公司。去年10月推出的iPhone12系列是苹果推出的首款5G手机。这些型号被中国消费者寄予厚望,他们已经接受了国内智能手机制造商提供的5G设备。研究公司IDC的数据显示,2020年中国共出货5G手机1.675亿部,占市场智能手机出货量的一半以上。库克还指出,所有产品类别都实现了两位数的强劲增长。"大约三分之二的购买Mac和iPad的人是第一次购买。因此,我们正在中国吸引一些新客户,这对我们非常重要,"他说。投资公司Wedbush证券董事总经理

  • 使用 IIS 在 Windows 上部署 Python Web 应用

    虽然Linux受到了绝大多数互联网公司的青睐,但是Windows服务器在中小企业的服务器选择中依然还是首选。 一方面和日常使用的Windows操作系统的使用逻辑相似,上手很轻松;另一方面,其功能和性能,对于中小企业来说,完全够用,运维还相对简单。最关键的是,图形化的界面,熟悉的操作逻辑,让没有经验的人也能快速完成。在MrDoc交流群里,经常有朋友询问,如何在Windows上部署MrDoc。在Windows上部署像MrDoc这样典型的PythonWeb应用,主要有两种方式:一是通过Apache进行部署;二则是通过IIS进行部署;关于通过Apache在Windows上部署PythonWeb应用程序的指南,州的先生之前已经分享过一篇文档:使用Apache在Windows上部署PythonWeb应用 今天,我们来学习一下使用IIS在Windows上部署PythonWeb应用。所需环境WindowsPython3IIS准备源码在这里我们使用MrDoc作为项目示例,所以首先需要将MrDoc的代码下载到本地并进行初始化:复制#使用Git工具克隆MrDoc源码 gitclonehttps://gite

  • iOS自定义视图:阅读并同意注册协议 【用户协议及隐私政策入口】

    1.1使用富文本API`NSLinkAttributeName`实现超链接功能1.2使用按钮处理点击事件实现超链接功能2.1设置约束的最大值2.2设置约束的最小值前言1.1使用富文本APINSLinkAttributeName实现超链接功能iOS《用户协议及隐私政策》弹框:1、包含超链接属性、demo支持中英文切换 2、文章地址:https://kunnan.blog.csdn.net/article/details/103902362 3、《用户协议及隐私政策》弹框的实现步骤: 3.1、自定义TextView,采用富文本属性进行内容设置attributedText(包括下划线NSUnderlineStyleSingle、超链接NSLinkAttributeName、颜色NSForegroundColorAttributeName等信息) 3.2、实现代理方法textView:shouldInteractWithURL:inRange,处理点击超链接的回调(打开对应URLWebview) 4、demo下载地址:https://download.csdn.net/download/u01

  • 前端基础-HTML(meta标签)

    meta标签1.编码示意图这种情况就是乱码,是因为我们输入的中文,往计算机中保存的时候,最终都要转成2进制的数据形式,也就是说有一个编码的过程,在保存文件的时候默认使用的是ANSI编码格式,浏览器显示文件中内容的时候,还需要将2进制的数据转换成文字形式显示出来,也就是说还有解码的过程,浏览器被指定为utf-8格式来解码,也就是说编码和解码不一致所造成的乱码解决乱码示意图保存编码图示我们加上meta标签反而会乱码的原因,是因为meta标签可以指定浏览器解析文件的编码格式,不加meta标签反而会正常,是因为我们不指定解码格式,浏览器会自动检测编码格式,再以对应的解码方式进行解码。字符集的核心点就是如果设置了以什么字符集进行读取,那么在保存的时候也需要设置成对应的字符集<metacharset="utf-8"><!--告知浏览器使用utf-8的编码格式来解析页面-->复制知识小百科:字符集ansi:不同的国家和地区制定了不同的标准,由此产生了GB2312、GBK、Big5、Shift_JIS等各自的编码标准。这些使用1至4个字节来代表一个字符的各种

  • JavaScript中的this基本问题

    在函数中this到底取何值,是在函数真正被调用执行的时候确定下来的,函数定义的时候确定不了。 执行上下文环境: **定义**:执行函数的时候,会产生一个上下文的对象,里面保存变量,函数声明和this。 **作用**:用来保存本次运行时所需要的数据当你在代码中使用了this,这个this的值就直接从执行的上下文中获取了,而不会从作用域链中搜寻。关于this的取值,大体上可以分为以下几种情况:情况一:全局&调用普通函数在全局环境中,this永远指向window。console.log(this===window);//true复制普通函数在调用时候(注意不是构造函数,前面不加new),其中的this也是指向window。但是如果在严格模式下调用的话会报错:varx=1; functionfirst(){ console.log(this);//undefined console.log(this.x);//UncaughtTypeError:Cannotreadproperty'x'ofundefined } first();复制情况二:构造函数所谓的构造函数就

  • 马化腾:腾讯要帮助中国加快数字化,主要靠小程序(附演讲)

    整理|DavidZ4月12日上午,腾讯创始人马化腾在互联网+峰会上发表演讲,用一个目标、三个角色、五个领域和七种工具来解释接下来腾讯想在数字经济中发挥的具体作用。一个目标是说腾讯要成为各行业的数字化助手,先自己做减法再帮合作伙伴做加法。围绕这个目标,马化腾说腾讯要做三件事:做连接、做工具、做生态。连接就是各行业物理世界和比特世界的接口,或者说API;工具则是说希望腾讯成为各行业最完备的数字化转型工具箱,或者说SDK;而生态是以开放协作的理念提供新型基础设施。五个领域则是指民生政务、生活消费、生产服务、生命健康和生态环保,腾讯希望在这五大领域助力数字化转型和升级。去年下半年开始,腾讯也在阿里提出的新零售领域加速布局智慧零售。但区别在于,马化腾强调腾讯自己不会做零售,而是去成为零售业数字化的助手,为合作伙伴提供智慧零售解决方案。具体来说,腾讯跟沃尔玛合作通过微信实现扫码付,同永辉超市合作在小程序平台上直接开店。另外,腾讯还联合永辉投资了家乐福,联手京东用16亿元入股了步步高零售。这些动作可能意味着,腾讯希望在零售业做出一些成果,从而为其它行业做示范作用,进一步扩大腾讯作为连接器和用户生态的

  • 处理机进程调度模拟

    一、进程调度无论是在批处理还是分时系统中,用户进程数一般都多于处理机数、这将导致它们互相争夺处理机。另外,系统进程也同样需要使用处理机。这就要求进程调度程序按一定的策略,动态地把处理机分配给处于就绪队列中的某一个进程,以使之执行。进程调度属于处理机调度。处理机调度分为三个层次:高级调度:(High-LevelScheduling)又称为长程调度、作业调度,它决定把外存上处于后备队列中的作业调入内存运行,为他们创建进程、分配必要的资源,放入就绪队列低级调度:(Low-LevelScheduling)又称为短程调度、进程调度,它决定把就绪队列的某进程获得处理机,并由分派程序将处理机分配给被选中的进程中级调度:(Intermediate-LevelScheduling)又称为在虚拟存储器中引入,在内、外存对换区进行进程对换,把外存上那些已经预备运行条件的就绪进程再重新调入内存,放入就绪队列。二、常用调度算法模拟首先创建一个进程控制块(PCB)的类:1packagecontrolblock; 2 3/** 4*进程控制块 5* 6*@authorwz 7* 8*@date2015年11月10日

  • centos7 kill程序进程_centos杀死进程命令

    大家好,又见面了,我是你们的朋友全栈君 经过搜集和整理相关的linux杀死进程的材料,在这里本人给大家推荐本篇文章,希望大家看后会有不少收获。1.kill作用:根据进程号杀死进程用法:kill[信号代码]进程ID举例:[root@localhost~]#psauxf|grephttpd注意:kill-9来强制终止退出举例[root@localhost~]#psaux|grepgaim或者[root@localhost~]#pgrep-lgaim5031gaim5031gaim[root@localhost~]#kill-95031特殊用法:kill-STOP[pid]发送SIGSTOP(17,19,23)停止一个进程,而并不linux杀死进程。kill-CONT[pid]发送SIGCONT(19,18,25)重新开始一个停止的进程。kill-KILL[pid]发送SIGKILL(9)强迫进程立即停止,并且不实施清理操作。kill-9-1终止你拥有的全部进程。2.killall作用:通过程序的名字,直接杀死所有进程用法:killall正在运行的程序名举例:[root@localhostb

  • 不要为了虚荣心而让孩子过早的学习人工智能

      版权申明:本文为博主窗户(ColinCai)原创,欢迎转帖。如要转贴,必须注明原文网址   http://www.cnblogs.com/Colin-Cai/p/8910099.html   作者:窗户   QQ/微信:6679072   E-mail:6679072@qq.com复制   这个题目有点攻击性的意思,看的人不大舒服。最开始的时候,我题目在《反对孩子学人工智能》、《坚决反对孩子学人工智能》之间犹豫。后来一想,是否攻击性过强,于是妥协一下,准备把题目拟为《孩子是否学人工智能须斟酌》。最后还是觉得,如此不鲜明的题目不是我的风格,索性把话都说出来,于是拟了这么个标题。我曾经多次想写有一定批判性的话题,在这篇文章中可能会把以前想到的一些写一下。不是故意要当标题党,也不是借故要标新立异。   首先,我们来看看什么是人工智能。   IT祖师爷Turing就对人工智能就有很多的构想,其中最有名的就是图灵测试(Turingtest),这是一种判别机器是否有智能的方法,在可预见的未来之内,通过图灵测试依然是人工智能努力的目标。简单的说,从图灵测试的角度来说,人工智能就是对于问

  • 《如何高效学习》读书笔记

    《如何高效学习》  学习上的投资会给你的生活带来巨大的益处,前提是你能真正应用那些花时间学来的知识。如果只学习而没有实际的应用,就是在浪费生命。   整体性学习小结 基础(1)结构──关于某个学科的知识之间联系的总和(2)模型──将信息压缩成最基本的单元,模型是结构的种子(3)高速公路──不同结构之间的联系,有助于创造性的思考 五个步骤(1)获取──通过感官获取信息(2)理解──明白信息的表面意思(3)拓展──与其他信息建立联系(4)纠错──剔除错误联系(5)应用──将知识应用到各种情境中去 五种分类(1)随意信息──事实、日期、列表、规则以及某种顺序(2)观点信息──信息的唯一目的就是支持或者反对某种观点(3)过程信息──它是指某种技能的信息(4)具体信息──容易视觉化的信息,一般是与实际紧密联系的信息(5)抽象信息──信息不容易有自己的经验 点击查看原图 点击下载思维导图  

  • Ember.js 的视图层

    本指导会详尽阐述Ember.js视图层的细节。为想成为熟练Ember开发者准备,且包含了对于入门Ember不必要的细节。 Ember.js有一套复杂的用于创建、管理并渲染连接到浏览器DOM上的层级视图的系统。视图负责响应诸如点击、拖拽以及滚动等的用户事件,也在视图底层数据变更时更新DOM的内容。 视图层级通常由求值一个Handlebars模板创建。当模板求值后,会添加子视图。当那些子视图求值后,会添加它们的子视图,如此递推,直到整个层级被创建。 即使你并没有在Handlebars模板中显式地创建子视图,Ember.js内部仍使用视图系统更新绑定的值。例如,每个Handlebars表达式{{value}}幕后创建一个视图,这个视图知道当值变更时如何更新绑定值。 你也可以在应用运行时用Ember.ContainerView类对视图层级做出修改。一个容器视图暴露一个可以手动修改的子视图实例数组,而非模板驱动。 视图和模板串联工作提供一套用于创建任何你梦寐以求的用户界面的稳健系统。最终用户应从诸如当渲染和事件传播是的计时事件之类的复杂东西中隔离开。应用开发者应可以一次性把他们的UI描述成Han

  • 独立2D游戏开发工具GGELUA入门教程

    B大的ggelua码云项目: https://gitee.com/baidwwy/GGELUA   GGE相对于EM有什么优势? 01.比HGE更好的引擎(Galaxy2D)02.比Bass更好的音效(Fmod)03.比官方lua更好的脚本(luajit)04.比EM更好的编辑器(Sublimetext3)05.比EM更好的服务端(HPSocket)06.比EM更好的地图编辑器(Tiled)07.比EM更好的代码管理(随意require,核心代码需要才加载)08.比EM更好的资源管理(只管载入,自动释放)09.支持所有国外lua模块10.支持易语言静态编译的DLL(不一定得黑月)11.除编译器和Start.dll(代码载入)外,所有源码开源. 待编辑~  

  • eclipse使用maven搭建SSM框架,编写spring配置文件时,发现有些标签不提示

    1、首先eclispe安装了springtools插件,或者你引入了命名空间 2、spring配置全都正确,但是tx:Advice标签没提示 最后才发现Maven从中央仓库下载jar包的时候,网络不能中断,不然会造成下载到本地仓库的jar缺失。 解决方法:删除本地仓库的jar,重新从中央仓库下载,这里我用的是阿里云的镜像 <mirrors> <mirror> <!--Thissendseverythingelseto/public--> <id>nexus-aliyun</id> <mirrorOf>*</mirrorOf> <name>Nexusaliyun</name> <url>http://maven.aliyun.com/nexus/content/groups/public</url> </mirror> </mirrors>复制  

  • C语言基础知识总结大全(干货)

    C语言是当代人学习及生活中的必备基础知识,应用十分广泛,下面为大家带来C语言基础知识梳理总结,C语言零基础入门绝对不是天方夜谭!   算法结构: 一、顺序结构、选择结构、循环结构;二、循环结构又分为while型、until型、for循环结构;程序流程图; 结构化程序设计方法: (1)自顶向下;(2)逐步细化;(3)模块化设计;(4)结构化编码。 数据类型: 常量:常量包括字面常量、直接常量和符号常量; 变量:C语言规定标志符只能由字母、数字和下划线三种字符组成,且第一个字符必须是字母或者下划线;必须压迫先定义后使用;每一个变量被定义以确定类型后,在编译时就能为其分配相应的存储单元; 整数类型:整数常量有十进制、八进制和十六进制;“%d” 整形变量:数据在内存中存放形式是以二进制形式存放;有int型、shortint型和longint型,无符号整型变量的范围是-32768—32767,有符号型为0~65535.通常把long定义为32位,把short定义为16位,int可以是32位也可以为16位,这都主要取决于机器字长。 实型常量的表示方法:(1)十进制,0.0;(2)指数形式

  • java基础之IO

    待续.... JavaIO总结 JavaIO读写总结   Java-io学习总结:输入 Java学习总结之JavaIO系统(一) javaIO详解总结 Java中IO总结   java流类图结构  一:File类 ①   java.io.file类是专门对文件进行操作的类,只能对文件本身进行操作,不能对文件内容进行操作。 ②  File类是“文件和目录路径名的抽象表示”,而不是指文件内容(IO)。 ③  File类定义了一些与平台无关的操作方法,如:删除文件、创建文件、重命名等。 ④   Java目录被看成一个特殊文件,List()方法可以返回目录中所有的子目录和文件。 ⑤    在Unix下路径分隔符为(/),在windows下为(\),在java中可以正确的处理不同系统的分隔符。 二:File类常用的构造方法与常用方法 构造方法:     publicFile

  • 人脑+电脑:通过文档管理让个人能力大大拓展

        "人脑+电脑": 现在的电脑已经是“PC+手机+平板+云服务器”四者的结合体,不再是单指PC了。 如何充分发挥两个脑的协同作用,需要个人不断深入思考,才能让“个人能力的边界”不断扩大。 当然,利用个人能力和"组织别人以利用别人能力"都是扩大的方法,都值得思考。   1.在互联网时代, 不进行文档管理的老板、主管的“发展天花板”是容易推测的。 因为: 要将某个事物考虑清楚, 需要收集的信息量是惊人的, 思考的中间产物是需要落地到文档的, 决策输出的产物更是需要不断细化的。   2.没有很强的文档管理能力行吗?  当然行,但很受限于“个人的脑能力”, 只有“人脑+电脑”有机搭配使用, 这种的能力才有可能是很大到难于想像的。   3.“电脑”这个名字起得好! 现在的电脑已经是“PC+手机+平板+云服务器”四者的结合体, 不再是单指PC了。     通过文档管理是否能让个人能力大大拓展?  你还怀疑吗?   最好坚信这一点, 并立即、马上去想怎样管理好Word、Excel、PDF、PP

  • 【2019年07月22日】A股最便宜的股票

      查看更多A股最便宜的股票:androidinvest.com/CNValueTop/ 便宜指数=PE+PB+股息+ROE,四因子等权,数值越大代表越低估。 本策略只是根据最新的数据来选股,完全无人工参与的过程,所以并不能对接下来的利润或业绩做预测,因此请结合个股基本面一起来看。 如有任何疑问,请留言咨询。如果喜欢本推荐,也请多帮忙转发和分享,十分感谢。 新钢股份(SH600782)-便宜指数:14.37-扣非市盈率PE:2.66-市净率PB:0.77-股息收益率:4.21%-ROE:34.09%-新钢股份的历史市盈率PE走势图 南钢股份(SH600282)-便宜指数:12.52-扣非市盈率PE:3.89-市净率PB:0.88-股息收益率:9.20%-ROE:26.49%-南钢股份的历史市盈率PE走势图 三钢闽光(SZ002110)-便宜指数:12.15-扣非市盈率PE:3.61-市净率PB:1.07-股息收益率:15.78%-ROE:41.60%-三钢闽光的历史市盈率PE走势图 柳钢股份(SH601003)-便宜指数:12.06-扣非市盈率PE:3.49-市净率PB:1

  • CAD超级填充:如何将图块作为填充图案?

    CAD填充命令是CAD常用命令之一,其可以用图案、实体、渐变色对封闭区域或选定对象进行填充。可是如果想要用图块作为CAD填充图案的话该怎么办呢?浩辰CAD软件中的超级填充命令,不仅可以用图块、光栅图像作为CAD填充图案,还可以选择边界创建区域覆盖,本文就和小编一起来详细了解一下吧! CAD超级填充:图块填充实例 1、启动浩辰CAD软件,在菜单栏依次点击【扩展工具】—【绘图工具】—【超级填充】。如下图所示: 2、执行命令后,会弹出【超级填充】对话框,点击【块填充】。如下图所示: 3、此时会弹出【超级填充–插入】对话框。 (1)块。点击【块】按钮后,会弹出【选择块】对话框(此对话框可以选择当前图纸的内部块),在列表中选择需要插入的块,点击【确定】。如下图所示: (2)文件:点击【文件】按钮后,会弹出【选择参照文件】对话框,找到并选中需要插入的外部块文件,点击【打开】。如下图所示: 说明:如果不勾选【超级填充–插入】对话框中的【在屏幕上指定参数】,则可以直接在对话框中输入相关的插入参数值;如果勾选,则系统将会在单击"确定"后要求用户在命令行输入或用光标指定。 4、根据提示在图纸中指

  • 父子组件传值,非父子组件传值 实例方法/事件封装(2018/12/11)

    .一、父组件给子组件传值(当子组件在父组件中当做标签使用的时候通过自定义属性进行传值,接受的时候通过this.props进行接受) 思路: 父组件引入子组件,在子组件的标签中添加自定义属性 然后将this.state中的属性传给子组件的标签 子组件通过this.props接收父组件传过来的属性,添加到虚拟dom中,再通过对虚拟dom的渲染在页面上显示出来   this.props是用来接受外部属性 one.js(这是子组件)   importReact,{Component}from"react" classoneextendsComponent{      render(){          console.log(this.props) //{info:“1111”}           let{inf

  • .Net Core中的配置Configuration使用以及源码解析

    在以前的.NetFramework程序中,我们的很多配置都会写到App.config或者Web.config,然后通过系统提供的System.Configuration.ConfigurationManager去获取相应的配置,但是在.NetCore我们有了新的配置获取方式,并且不只是支持config文件,默认实现了ini,xml,json等一系列文件类型的获取方式,并且他们的获取方式是统一的,它做到了不同的配置源,统一的获取方式。 使用IConfiguration来获取配置信息 新建一个Asp.NetCoreMVC应用,这里你也可以建控制台应用,只是网站应用方便演示,两者没有区别。唯一不同的地方是网站已经为我们创建了ConfigurationBuilder对象,并且注入到了容器中,同时也添加了一些默认的配置。 在项目里添加一个config01.json文件,内容如下: { "EnableCache":true, "Email":{ "From":"from@email.com", "To":"to@email.com" }, "Type":{ "Assembly":{ "Names

  • 洛谷 P1447 [NOI2010] 能量采集

    题目描述 https://www.luogu.com.cn/problem/P1447 栋栋有一块长方形的地,他在地上种了一种能量植物,这种植物可以采集太阳光的能量。在这些植物采集能量后,栋栋再使用一个能量汇集机器把这些植物采集到的能量汇集到一起。 栋栋的植物种得非常整齐,一共有\(n\)列,每列有\(m\)棵,植物的横竖间距都一样,因此对于每一棵植物,栋栋可以用一个坐标\((x,y)\)来表示,其中\(x\)的范围是\(1\)至\(n\),\(y\)的范围是\(1\)至\(m\),表示是在第\(x\)列的第\(y\)棵。 由于能量汇集机器较大,不便移动,栋栋将它放在了一个角上,坐标正好是\((0,0)\)。 能量汇集机器在汇集的过程中有一定的能量损失。如果一棵植物与能量汇集机器连接而成的线段上有\(k\)棵植物,则能量的损失为\(2k+1\)。例如,当能量汇集机器收集坐标为\((2,4)\)的植物时,由于连接线段上存在一棵植物\((1,2)\),会产生\(3\)的能量损失。注意,如果一棵植物与能量汇集机器连接的线段上没有植物,则能量损失为\(1\)。现在要计算总的能量损失。 题意 就

相关推荐

推荐阅读