首页 文章 精选 留言 我的

精选列表

搜索[工作],共10006篇文章
优秀的个人博客,低调大师

它是如何工作的?

松哥最近辅导了几个小伙伴秋招,有小伙伴在面小红书时遇到这个问题,这个问题想回答全面还是有些挑战,松哥结合之前的一篇旧文和大伙一起来聊聊。 一 无状态登录 1.1 什么是有状态 有状态服务,即服务端需要记录每次会话的客户端信息,从而识别客户端身份,根据用户身份进行请求的处理,典型的设计如 Tomcat 中的 Session。例如登录:用户登录后,我们把用户的信息保存在服务端 session 中,并且给用户一个 cookie 值,记录对应的 session,然后下次请求,用户携带 cookie 值来(这一步有浏览器自动完成),我们就能识别到对应 session,从而找到用户的信息。这种方式目前来看最方便,但是也有一些缺陷,如下: 服务端保存大量数据,增加服务端压力 服务端保存用户状态,不支持集群化部署 1.2 什么是无状态 微服务集群中的每个服务,对外提供的都使用 RESTful 风格的接口。而 RESTful 风格的一个最重要的规范就是:服务的无状态性,即: 服务端不保存任何客户端请求者信息 客户端的每次请求必须具备自描述信息,通过这些信息识别客户端身份 那么这种无状态性有哪些好处呢? 客户端请求不依赖服务端的信息,多次请求不需要必须访问到同一台服务器 服务端的集群和状态对客户端透明 服务端可以任意的迁移和伸缩(可以方便的进行集群化部署) 减小服务端存储压力 1.3 如何实现无状态 无状态登录的流程: 首先客户端发送账户名/密码到服务端进行认证 认证通过后,服务端将用户信息加密并且编码成一个 token(一般是 JWT),返回给客户端 以后客户端每次发送请求,都需要携带认证的 token 服务端对客户端发送来的 token 进行解密,判断是否有效,并且获取用户登录信息 二 JWT 2.1 JWT 简介 JWT,全称是 Json Web Token, 是一种 JSON 风格的轻量级的授权和身份认证规范,可实现无状态、分布式的 Web 应用授权: JWT 作为一种规范,并没有和某一种语言绑定在一起,常用的 Java 实现是 GitHub 上的开源项目 jjwt,地址如下:https://github.com/jwtk/jjwt 2.2 JWT 数据格式 JWT 包含三部分数据: Header:头部,通常头部有两部分信息: 声明类型,这里是JWT 加密算法,自定义 我们会对头部进行 Base64Url 编码(可解码),得到第一部分数据。 Payload:载荷,就是有效数据,在官方文档中(RFC7519),这里给了7个示例信息: iss (issuer):表示签发人 exp (expiration time):表示token过期时间 sub (subject):主题 aud (audience):受众 nbf (Not Before):生效时间 iat (Issued At):签发时间 jti (JWT ID):编号 这部分也会采用 Base64Url 编码,得到第二部分数据。 Signature:签名,是整个数据的认证信息。一般根据前两步的数据,再加上服务的的密钥secret(密钥保存在服务端,不能泄露给客户端),通过 Header 中配置的加密算法生成。用于验证整个数据完整和可靠性。 生成的数据格式如下图: 注意,这里的数据通过 . 隔开成了三部分,分别对应前面提到的三部分,另外,这里数据是不换行的,图片换行只是为了展示方便而已。 2.3 JWT 交互流程 流程图: 步骤翻译: 应用程序或客户端向授权服务器请求授权 获取到授权后,授权服务器会向应用程序返回访问令牌 应用程序使用访问令牌来访问受保护资源(如 API) 因为 JWT 签发的 token 中已经包含了用户的身份信息,并且每次请求都会携带,这样服务的就无需保存用户信息,甚至无需去数据库查询,这样就完全符合了 RESTful 的无状态规范。 2.4 JWT 存在的问题 说了这么多,JWT 也不是天衣无缝,由客户端维护登录状态带来的一些问题在这里依然存在,举例如下: 续签问题,这是被很多人诟病的问题之一,传统的 cookie+session 的方案天然的支持续签,但是 jwt 由于服务端不保存用户状态,因此很难完美解决续签问题,如果引入 redis,虽然可以解决问题,但是 jwt 也变得不伦不类了。 注销问题,由于服务端不再保存用户信息,所以一般可以通过修改 secret 来实现注销,服务端 secret 修改后,已经颁发的未过期的 token 就会认证失败,进而实现注销,不过毕竟没有传统的注销方便。 密码重置,密码重置后,原本的 token 依然可以访问系统,这时候也需要强制修改 secret。 基于第 2 点和第 3 点,一般建议不同用户取不同 secret。 对于上面提到的这些问题,在目前具体的项目实践中,更多的是通过引入 Redis 来解决这些问题。 三 实战 说了这么久,接下来我们就来看看这个东西到底要怎么用? 3.1 环境搭建 首先我们来创建一个 Spring Boot 项目,创建时需要添加 Spring Security 依赖,创建完成后,添加 jjwt 依赖,完整的 pom.xml 文件如下: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> </dependency> 然后在项目中创建一个简单的 User 对象实现 UserDetails 接口,如下: public class User implements UserDetails { private String username; private String password; private List<GrantedAuthority> authorities; public String getUsername() { return username; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } //省略getter/setter } 这个就是我们的用户对象,先放着备用,再创建一个 HelloController,内容如下: @RestController public class HelloController { @GetMapping("/hello") public String hello() { return "hello jwt !"; } @GetMapping("/admin") public String admin() { return "hello admin !"; } } HelloController 很简单,这里有两个接口,设计是 /hello 接口可以被具有 user 角色的用户访问,而 /admin 接口则可以被具有 admin 角色的用户访问。 3.2 JWT 配置 先准备一个 JWT 操作的工具类: public class JwtUtils { public static final Integer EXPIRE_TIME = 7 * 24 * 60 * 60; public static final Integer REDIS_TOKEN_EXPIRE_TIME = 30 * 60; public static final String TOKEN_ISSUER = "javaboy"; /** * 生成 JWT 字符串 * * @return */ public static String createJWT() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); String jws = Jwts.builder() //设置头部信息(相当于 JWT 字符串第一部分的内容) .header().add("type", "JWT") .and() //相当于 JWT 字符串第二部分的内容 //签名密钥 .signWith(getPrivateKey()) //设置过期时间 .expiration(java.util.Date.from(java.time.Instant.now().plusSeconds(EXPIRE_TIME))) //设置用户名 .subject(authentication.getName()) //设置用户权限,相当于把用户权限转为字符串 admin,user,xxx .claim("authorities", authentication.getAuthorities().stream().map(a -> a.getAuthority()).collect(Collectors.joining(","))) //令牌签发时间 .issuedAt(new Date()) .issuer(TOKEN_ISSUER) .compact(); return jws; } public static PrivateKey getPrivateKey() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { ClassPathResource resource = new ClassPathResource("private_key.pem"); byte[] privateKeyBytes = FileCopyUtils.copyToByteArray(resource.getInputStream()); String key = new String(privateKeyBytes, StandardCharsets.UTF_8); String privateStr = key .replace("-----BEGIN PRIVATE KEY-----", "") .replaceAll(System.lineSeparator(), "") .replace("-----END PRIVATE KEY-----", ""); byte[] privateKeyDecodedBytes = Base64.getDecoder().decode(privateStr); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyDecodedBytes); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PrivateKey privateKey = keyFactory.generatePrivate(keySpec); return privateKey; } public static RSAPublicKey getPublicKey() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { ClassPathResource resource = new ClassPathResource("public_key.pem"); byte[] publicKeyBytes = FileCopyUtils.copyToByteArray(resource.getInputStream()); String key = new String(publicKeyBytes, StandardCharsets.UTF_8); String publicStr = key .replace("-----BEGIN PUBLIC KEY-----", "") .replaceAll(System.lineSeparator(), "") .replace("-----END PUBLIC KEY-----", ""); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); byte[] publicKeyBase64Bytes = Base64.getDecoder().decode(publicStr); X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyBase64Bytes); PublicKey rsaPublicKey = keyFactory.generatePublic(publicKeySpec); return (RSAPublicKey) rsaPublicKey; } public static Authentication verifyToken(String token) { try { Claims claims = Jwts.parser() .verifyWith(getPublicKey()).build() .parseSignedClaims(token).getPayload(); String username = claims.getSubject(); // user,admin,xxx String authorities = claims.get("authorities", String.class); // 解析用户权限 List<SimpleGrantedAuthority> list = Arrays.stream(authorities.split(",")).map(s -> new SimpleGrantedAuthority(s)).collect(Collectors.toList()); return UsernamePasswordAuthenticationToken.authenticated(username, null, list); } catch (Exception e) { //令牌校验失败 return null; } } } 接下来,在用户登录成功的时候,生成 JWT 字符串,并存入到 Redis 中,如下: @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests(a -> a.anyRequest().authenticated()) .csrf(c -> c.disable()) .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .formLogin(f -> f .successHandler((req, resp, auth) -> { //登录成功,生成 JWT 字符串 try { String token = JwtUtils.createJWT(); //将 JWT 字符串保存到 redis 中 redisTemplate.opsForValue().set(auth.getName() + ":" + token, auth.getName()); // 设置过期时间 redisTemplate.expire(auth.getName() + ":" + token, JwtUtils.REDIS_TOKEN_EXPIRE_TIME, TimeUnit.SECONDS); resp.setContentType("application/json;charset=utf-8"); RespBean respBean = new RespBean(200, token, null); resp.getWriter().write(new ObjectMapper().writeValueAsString(respBean)); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } catch (InvalidKeySpecException e) { throw new RuntimeException(e); } }) .permitAll()) .logout(l -> l.logoutSuccessHandler((req, resp, auth) -> { // 删除 redis 中的 token String key = auth.getName() + ":" + req.getHeader("Authorization").replace("Bearer ", ""); System.out.println("key = " + key); redisTemplate.delete(key); resp.getWriter().write("logout success"); }).logoutUrl("/logout").permitAll()); http.addFilterAfter(new JwtFilter(redisTemplate), SecurityContextHolderFilter.class); return http.build(); } 最后再来一个校验的过滤器,在用法发送请求的时候,校验 JWT 字符串,如下: public class JwtFilter extends GenericFilterBean { StringRedisTemplate redisTemplate; public JwtFilter(StringRedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) { RespBean result = null; HttpServletRequest req = (HttpServletRequest) servletRequest; HttpServletResponse resp = (HttpServletResponse) servletResponse; resp.setContentType("application/json;charset=utf-8"); try { //如果是登录请求,则直接放行 if (req.getRequestURI().equals("/login")) { filterChain.doFilter(servletRequest, servletResponse); return; } //获取请求头中的token String token = req.getHeader("Authorization"); if (token == null) { result = new RespBean(500, "令牌为空,请求失败", null); PrintWriter out = resp.getWriter(); out.write(new ObjectMapper().writeValueAsString(result)); } else { //验证token token = token.replace("Bearer ", ""); //验证token是否有效 //校验通过,则返回认证后的 Auhtentication 对象,否则返回 null Authentication authentication = JwtUtils.verifyToken(token); String s = redisTemplate.opsForValue().get(authentication.getName() + ":" + token); if (s == null) { // 令牌失效 result = new RespBean(501, "令牌失效,请重新登录", null); resp.getWriter().write(new ObjectMapper().writeValueAsString(result)); } else { //令牌有效,检查令牌过期时间,如果临近过期,则为令牌续期 Long expire = redisTemplate.getExpire(authentication.getName() + ":" + token); if (expire != null && expire < 10 * 60) { //说明令牌还有 10 分钟过期,则重新设置新的过期时间 redisTemplate.expire(authentication.getName() + ":" + token, Duration.ofSeconds(JwtUtils.REDIS_TOKEN_EXPIRE_TIME)); } SecurityContextHolder.getContext().setAuthentication(authentication); filterChain.doFilter(servletRequest, servletResponse); } } } catch (Exception e) { } finally { // 清除上下文 SecurityContextHolder.clearContext(); } } } 关于这个过滤器,我说如下几点: 首先从请求头中提取出 Authorization 字段,这个字段对应的 value 就是用户的 token。 将提取出来的 token 字符串调用 JwtUtils.verifyToken 方法进行验证,将验证结果放到当前的 SecurityContext 中,然后执行过滤链使请求继续执行下去。 3.3 Spring Security 配置 最后,在 Spring Security 中配置这个过滤器。 http.addFilterAfter(new JwtFilter(redisTemplate), SecurityContextHolderFilter.class); 搞定! 3.4 测试 做完这些之后,我们的环境就算完全搭建起来了,接下来启动项目然后在 POSTMAN 中进行测试,如下: 登录成功后返回的字符串就是经过 base64url 转码的 token,一共有三部分,通过一个 . 隔开,我们可以对第一个 . 之前的字符串进行解码,即 Header,如下: 再对两个 . 之间的字符解码,即 payload: 可以看到,我们设置信息,由于 base64 并不是加密方案,只是一种编码方案,因此,不建议将敏感的用户信息放到 token 中。 接下来再去访问 /hello 接口,注意认证方式选择 Bearer Token,Token 值为刚刚获取到的值,如下: 可以看到,访问成功。 四 总结 这就是 JWT 结合 Spring Security 的一个简单用法。

优秀的个人博客,低调大师

了解学习 JSX 的工作方式

过去半年基本都是使用VUE来开发项目,也有了一定的经验了,后续将会推出一套前后端完整的基于VUE的管理系统。从今天开始学习 React ,首先就从定义React组件的JSX开始。那什么是JSX呢?与JavaScript有啥关系?本文就来一起学习。 JSX到底是什么? JSX,即 JavaScript XML,JavaScript 的语法扩展,允许在 JavaScript 文件中编写 HTML (这是个人内心抗拒的原因)。不必通过 JavaScript 对象创建、修改或者拼接 HTML ,可以使用类似 XML 的语法创建元素,编译运行时生成相应的 DOM 元素。 那它看起来怎么样?请看下面的代码: const jsx = <div>Hello React</div>; 如何使用JSX? 那么如何在实践中使用JSX呢?JSX是灵活的,不管哪种编程方式,都需要遵循一定的规则。下面就来逐一介绍。 在JSX中使用表达式 假设希望在HTML模板中使用JavaScript变量,使用花括号 {},可以插值变量以获得动态值: const title = "DevPoint"; const expression = <h1>Hello {title}</h1>; 这里 {title} 将在 h1 元素中替换为 DevPoint 。当然,不仅可以嵌入JavaScript变量,还可以使用更复杂的表达式: const a = 1; const b = 1; const expression = ( <code> {a} + {b} = {a + b} </code> ); 这将输出1+1=2,在处理数组时,表达式也通常用于生成HTML元素列表。假设有一个动态菜单,从API接收数据: { "menu": [ { "name": "Home", "link": "/" }, { "name": "About", "link": "/about" }, { "name": "Contact", "link": "/contact" } ] } 然后,可以使用JSX表达式映射整个数组: const menu = <ul>{menu.map((item) => console.log(item))}</ul>; 在这里,使用JSX表达式中的 Array.map 方法将每个元素记录到控制台,但是如何显示列表元素呢? const menu = ( <ul> {menu.map((item) => ( <li> <a href={item.link}>{item.name}</a> </li> ))} </ul> ); 可以简单地从函数返回所需的元素,这相当于在下面显式写出 return 关键字: const menu = ( <ul> {menu.map((item) => { return ( <li> <a href={item.link}>{item.name}</a> </li> ); })} </ul> ); 表达式还可用于创建条件语句,或使用三元运算符根据变量的值返回不同的HTML元素,如下: { name && <h1>Hello {name}</h1>; } { /* 这里是注释 */} { name ? <h1>Hello {name}</h1> : null; } 如上所见,JSX中的注释也用于表达式中。如果要在一行前面写//的话,它也会被打印到最终的HTML文件中,因为它被解释为自由流文本。因此,在使用注释时需要使用表达式。 在JSX中使用属性 正如上面所看到的,也可以像平常一样在JSX元素中使用HTML属性。还可以对属性使用JSX表达式,或者如果使用的是静态值,则可以使用常规HTML中的引号来定义它们: <a href={item.link} target="_blank"> {item.name} </a>; 在JSX中,这些称为 props,需要注意的一点是,当涉及到使用破折号的属性时,JSX 使用驼峰大小写。例如,如果要添加 aria 属性,将执行以下操作: <div aria-label="Label"></div> <div aria-checked="true"></div> <div tab-index="0"></div> <div ariaLabel="Label"></div> <div ariaChecked="true"></div> <div tabIndex="0"></div> 在JSX中使用属性时还有一个特殊的例外,那就是使用HTML类。由于单词 class 在JavaScript中是一个保留关键字,因此需要改用 className: <div class="carousel"></div> <div className="carousel"></div> 始终关闭JSX标记 当使用自动关闭标记或使用纯粹用于传递视觉信息的元素且这些元素没有任何子元素时,请确保始终关闭标签,否则,将会出现错误: {/* 错误的方式 */} <br> <img> <div> {/* 正确的方式 */} <br /> <img /> <div className="loader" /> 返回JSX 需要指出的另一条重要规则是,一次只能返回一个JSX元素。请看下面的 HTML 元素: <span className="tooltip">...</span> <div className="tooltip-backdrop" /> 这个例子是无效的,因为试图返回多个相邻的JSX元素,为了修复它,需要将其包装在父级中: <span className="tooltip-container"> <span className="tooltip">...</span> <div className="tooltip-backdrop" /> </span> 或者,如果不想用不必要的HTML元素污染DOM树,还可以使用一个称为片段的特殊标记: <React.Fragment> <span className="tooltip">...</span> <div className="tooltip-backdrop" /> </React.Fragment> {/* 或者 */} <Fragment> <span className="tooltip">...</span> <div className="tooltip-backdrop" /> </Fragment> {/* 或者 */} <> <span className="tooltip">...</span> <div className="tooltip-backdrop" /> </> 以上三个示例将生成相同的代码,都能正常运行,并且不会向HTML文件中添加额外的DOM元素。 存储JSX JSX元素也可以存储在变量中,并传递给其它JSX元素使用。但是,很可能会涉及到将JSX元素存储在函数中的情况: const Slider = () => ( <div>这是一个组件</div> ); <Slider /> 此外,建议将函数大写,否则,它将被视为HTML元素,例外的情况是使用对象方法,例如: const component = { slider() { return <div>这是一个组件</div> } }; <component.slider /> 通过使用函数,还可以传递可用于HTML元素的插件: const Slider = ({ size }) => ( <div className={size}>这是一个可以控制尺寸的组件</div> ); <Slider size="large" /> <Slider size="medium" /> <Slider size="small" /> 如上所见,不仅限于使用HTML属性的名称,还可以使用自定义名称。 JSX生成成什么? 那么JSX究竟生成了什么呢?浏览器无法理解JSX语法,因此 React 需要将其编译为浏览器能够理解的 JavaScript 函数调用。这些JavaScript函数调用最终将创建和更新必要的HTML元素。以下面的代码片段为例,它将生成以下函数调用: const jsx = <div>Welcome to React</div>; React.createElement("div", null, "Welcome to React"); const jsx = <div className="carousel" />; React.createElement("div", { className: "carousel" }); 如果 JSX 元素有子元素,那么这些子元素也将被编译为 React.createElement 调用,并最终生成在 JavaScript 包中,如下: React.createElement(component, props, ...children); 同样,如果对组件使用小写,则这些组件将被视为HTML元素,因此请确保将它们大写: <component />; React.createElement("component"); <Component />; React.createElement(Component); <component.slider />; React.createElement(component.slider); 总结 JSX 可以简化代码,使其更易于使用,同时也使其更具可读性。为了回顾本教程的重要部分,关于 JSX 与 JavaScript 的连接,以下是一些注意事项: JSX 是 JavaScript 的语法扩展,代表 JavaScript XML。 React 可以使用或不使用 JSX 编写,尽管如此,强烈建议使用 JSX。 JSX 支持使用大括号来使用表达式。 JSX 中的属性是用驼峰命名法编写的。 JSX 元素必须始终关闭。如果不涉及子元素,则元素必须是闭合(成对)的。 一次只能返回一个 JSX,如果需要返回多个元素,请使用片段。 函数可以返回 JSX 元素。 JSX 生成为 React.createElement 函数调用。

优秀的个人博客,低调大师

几种常见负载均衡算法工作原理

轮询 顾名思义将请求按顺序轮流地分配到后端服务器上,它均衡地对待后端的每一台服务器,而不关心服务器实际的连接数和当前的系统负载。 优点 缺点 服务器请求数据相同 服务器压力不同,不适合根据服务器配置不同的情况 随机 通过系统的随机算法,根据后端服务器的列表大小值来随机选取其中的一台服务器进行访问。由概率统计理论可以得知,随着客户端调用服务端的次数增多,其实际效果越来越接近于平均分配调用量到后端的每一台服务器,也就是轮询的结果。 优点 缺点 使用简单 服务器压力不同,不适合根据服务器配置不同的情况 源地址哈希 源地址哈希的思想是根据获取客户端的IP地址,通过哈希函数计算得到的一个数值,用该数值对服务器列表的大小进行取模运算,得到的结果便是客服端要访问服务器的序号。采用源地址哈希法进行负载均衡,同一IP地址的客户端,当后端服务器列表不变时,它每次都会映射到同一台后端服务器进行访问。 优点 缺点 将来自同一IP地址的请求,同一会话期内,转发到相同的服务器;实现会话粘滞。 目标服务器宕机后,会话会丢失 加权轮询法 不同的后端服务器可能机器的配置和当前系统的负载并不相同,因此它们的抗压能力也不相同。给配置高、负载低的机器配置更高的权重,让其处理更多的请;而配置低、负载高的机器,给其分配较低的权重,降低其系统负载,加权轮询能很好地处理这一问题,并将请求顺序且按照权重分配到后端。 优点 缺点 根据权重,调节转发服务器的请求数目 使用相对复杂 加权随机法 与加权轮询法一样,加权随机法也根据后端机器的配置,系统的负载分配不同的权重。不同的是,它是按照权重随机请求后端服务器,而非顺序。 最小连接数法 最小连接数算法比较灵活和智能,由于后端服务器的配置不尽相同,对于请求的处理有快有慢,它是根据后端服务器当前的连接情况,动态地选取其中当前积压连接数最少的一台服务器来处理当前的请求,尽可能地提高后端服务的利用效率,将负责合理地分流到每一台服务器。 优点 缺点 根据服务器当前的请求处理情况,动态分配 算法实现相对复杂,需要监控服务器请求连接数

优秀的个人博客,低调大师

智能枕头的功能及工作原理

释放压力 改善睡眠 这是一款骨传导音乐枕头,可以通过下载苹果版或者安卓版APP,将线插入手机耳机口,选好曲目开始播放,有四种关闭时间可以选择。而且这款枕头不需要充电或按电池,只要手机有电就可以了。这款枕头还能监测睡眠状态、评估睡眠质量。当你的头部接触到枕头,枕头感觉压力缓慢下沉,记忆材质细胞此事慢慢膨胀,在包裹住你的头颈的同时,吸收压力并释放压力。当到达你身体需要的高度时,材质细胞会自动填充稳固住形态与位置,让你放松感持续存在。 互通心意 感知内心 我们都知道,异地恋的孩纸们很辛苦,很多时候会被距离所打败。那么,下面不得不介绍这款传播爱意的枕头。这款智能情侣枕头是有两部分组成,,一款可检测脉搏的手环和一个内置了扬声器的枕头。两个人无论相离多远,便都可在睡前听到对方的心跳。不管是改善睡眠质量还是情侣间的传情,智能枕头的功能已经超乎了人们的想象,它不仅给人们带来了身体上的健康,更多的是精神上的慰藉。 遮挡阳光 追踪睡眠 智能枕头有一个内部的小袋可以用来放置手机,让你躺在床上听音乐时也不会受到耳机线的干扰,与此同时由于能将手机放置在小袋子里,它还能成为智能睡眠追踪器,从而实现更加精确的睡眠追踪。此外它的表面还设计了一个头套,可以让头部更暖和,也可以在白天睡觉时起到遮光的作用。 治疗鼾症 安心睡觉 枕边设备是整个智能枕头的“大脑”,集成了蓝牙模块、止鼾“聪明心”、本地算法中心、气泵/储气盒等功能模块。通优化算法,聪明心可以在用户使用三天内完成对用户鼾声特性的个性化学习,分辨和识别每个人不同的鼾声,并根据大喊着的鼾声分贝、频率等,对枕头发出“指令”识别的准确性可达到百分之九十以上。 智能枕头控制系统,包括声音传感器,用于采集鼾声;微振动传感器,用于采集环境噪声;鼾声介入装置;控制器,与所述微振动传感器、声音传感器、鼾声介入装置均通过导线连接;所述控制器接收所述微振动传感器探测的信号1,并根据所述第一信号判断控制所述声音传感器动作;所述控制器接收所述声音传感器探测的信号2,并根据所述信号2控制所述鼾声介入装置动作。本实用新型可以判断用户是否在睡眠状态,避免误判,多判,漏判等情况出现,同时可以与手机进行通信,将鼾声检测与介入结果,睡眠质量分析,深度-浅度睡眠周期分析等数据传递给手机,用户获得良好的产品体验。 就现在而言,智能枕头的设计水平、智能功能都还相当有限,未来随着相关技术的升级有望获得非常大的提升。而在细分领域发展方面,目前的智能枕头也比较局限,随着市场的进一步铺开,其种类与形式创新还有扩张空间。 据相关调查数据显示,截止2016年底,我国枕头市场规模已经突破1180亿元,其中智能枕头在枕头总体销量中所占比率正加速上升。当前的智能家居热潮还在延续,各种家庭智能产品依然在不断涌上市场。中国智能制造网认为,智能枕头作为未来智能生活的标配件,其发展前景十分值得期待。 同时,随着人工智能、物联网等前沿技术的发展,智能枕头有望结合智能家居系统实现融合发展,推动智能家居领域实现新增长,为人们的未来智能生活带来更佳体验。

优秀的个人博客,低调大师

android studio插件提升工作效率

SonarLint 代码质量管理插件 ButterKnife Zelezny ButterKnife 生成器,快速根据布局文件生成属性对象。 SelectorChapek 设计师给我们提供好了各种资源,每个按钮都要写一个selector是不是很麻烦?这么这个插件就为解决这个问题而生,你只需要做的是告诉设计师们按照规范命名就好了,其他一键搞定。 GsonFormat 根据json数据快速生成实体bean ParcelableGenerator Android中的序列化有两种方式,分别是实现Serializable接口和Parcelable接口,但在Android中是推荐使用Parcelable,只不过我们这种方式要比Serializable方式要繁琐,那么有了这个插件一切就ok了。 LeakCanary 良心企业Square最近刚开源的一个非常有用的工具,强烈推荐,帮助你在开发阶段方便的检测出内存泄露的问题,使用起来更简单方便,而且我们团队第一时间使用帮助我们发现了不少问题。 大部分内容摘自:http://stormzhang.com/android/2015/05/26/android-tools/ 本文转自秋楓博客园博客,原文链接:http://www.cnblogs.com/rwxwsblog/p/5383338.html,如需转载请自行联系原作者

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册