单点登录概述

单点登录(Single Sign On)简称为 SSO,是比较流行的企业业务整合的解决方案之一。SSO是指在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。其主要优势有:简化密码管理、提高关键登录流程效率、减少密码疲劳、减少技术服务需求等。单点登录(SSO)的实现并不是只使用某个技术即可,它是结合多种技术综合实现的产物,比如当前分布式架构的公司实现单点登录常用的技术为Spring Security + oauth2 + JWT 等

单点登录流程

Spring Security

概述

Spring security 是一个强大的和高度可定制的身份验证和访问控制框架,即实现了认证授权功能。Spring security核心就是拦截所有进入系统的请求,然后校验每个请求是否能够访问它所期望的资源,Spring Security对Web资源的保护就是通过执行FilterChain来实现,而其中认证管理器(AuthenticationManager)和决策管理器(AccessDecisionManager)分别进行认证和授权核心功能的处理

springsecurity执行流程

核心原理

处理流程

过滤器链相互配合实现springsecurity功能

关键过滤器及其作用:

  • SecurityContextPersistenceFilter :这个Filter是整个拦截过程的入口和出口(也就是第一个和最后一个拦截 器),会在请求开始时从配置好的 SecurityContextRepository 中获取 SecurityContext,然后把它设置给 SecurityContextHolder。在请求完成后将 SecurityContextHolder 持有的 SecurityContext 再保存到配置好的 SecurityContextRepository,同时清除 securityContextHolder 所持有的 SecurityContext
  • UsernamePasswordAuthenticationFilter :用于处理来自表单提交的认证。该表单必须提供对应的用户名和密码,其内部还有登录成功或失败后进行处理的 AuthenticationSuccessHandler 和 AuthenticationFailureHandler,这些都可以根据需求做相关改变; FilterSecurityInterceptor: 是用于保护web资源的,使用AccessDecisionManager对当前用户进行授权访问,前 面已经详细介绍过了
  • ExceptionTranslationFilter: 能够捕获来自 FilterChain 所有的异常,并进行处理。但是它只会处理两类异常: AuthenticationException 和 AccessDeniedException,其它的异常它会继续抛出

用户认证

用户认证流程

认证过程:

  1. 用户提交用户名、密码被SecurityFilterChain中的 UsernamePasswordAuthenticationFilter 过滤器获取到, 封装为请求Authentication,通常情况下是UsernamePasswordAuthenticationToken这个实现类
  2. 然后过滤器将Authentication提交至认证管理器(AuthenticationManager)进行认证
  3. 认证成功后, AuthenticationManager 身份管理器返回一个被填充满了信息的(包括上面提到的权限信息, 身份信息,细节信息,但密码通常会被移除) Authentication 实例
  4. SecurityContextHolder 安全上下文容器将第3步填充了信息的 Authentication ,通过 SecurityContextHolder.getContext().setAuthentication(…)方法,设置到其中。 可以看出AuthenticationManager接口(认证管理器)是认证相关的核心接口,也是发起认证的出发点,它 的实现类为ProviderManager。而Spring Security支持多种认证方式,因此ProviderManager维护着一个 List 列表,存放多种认证方式,最终实际的认证工作是由 AuthenticationProvider完成的。咱们知道web表单的对应的AuthenticationProvider实现类为 DaoAuthenticationProvider,它的内部又维护着一个UserDetailsService负责UserDetails的获取。最终 AuthenticationProvider将UserDetails填充至Authentication

用户授权

授权流程

Spring Security可以通过 http.authorizeRequests() 对web请求进行授权保护。Spring Security使用标准Filter建立了对web请求的拦截,最终实现对资源的授权访问

OAuth2

概述

OAuth(开放授权)是一个开放标准,允许用户授权第三方应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方应用或分享他们数据的所有内容。很多大公司如Google,Yahoo,Microsoft等都提供了OAUTH认证服务,这些都足以说明OAUTH标准逐渐成为开放资源授权的标准。 Oauth协议目前发展到2.0版本,1.0版本过于复杂,2.0版本已得到广泛应用

oAuth协议特点

  • 简单:不管是OAuth服务提供者还是应用开发者,都很易于理解与使用
  • 安全:没有涉及到用户密钥等信息,更安全更灵活
  • 开放:任何服务提供商都可以实现OAuth,任何软件开发商都可以使用OAuth

认证流程

oAuth认证成功后返回token,通过token访问服务器资源

其中OAauth2.0包括以下角色:

  1. Resource owner(资源拥有者):拥有该资源的最终用户,他有访问资源的账号密码
  2. Resource server(资源服务器):受保护资源所在的服务器,如果请求包含正确的访问令牌,就可以访问受保护的资源
  3. Client(客户端):请求访问资源的客户端,可以是浏览器、移动设备或者服务器,客户端会携带访问令牌访问资源服务器上的资源
  4. Authorization server(认证服务器):负责认证客户端身份的服务器,如果客户端认证通过,会给客户端发放访问资源服务器的令牌

授权模式

oAuth2.0目前支持四种授权模式(其中授权码模式和密码模式是企业当中最常用的模式):

  • Authorization Code(授权码模式):正宗的OAuth2的授权模式,客户端先将用户导向认证服务器,认证用户成功后获取授权码,然后进行授权,最后根据授权码获取访问令牌
  • Implicit(隐藏式):和授权码模式相比,取消了获取授权码的过程,直接获取访问令牌
  • Password(密码模式):客户端直接向用户获取用户名和密码,之后向认证服务器获取访问令牌
  • Client Credentials(客户端凭证模式):客户端直接通过客户端认证(比如client_id和client_secret)从认证服务器获取访问令牌

注意: 为了防止令牌被滥用,任何授权模式都必须在第三方应用申请令牌之前先进行系统备案,说明自己的身份,然后会拿到两个身份识别码:客户端 ID(client ID)客户端密钥(client secret)。没有备案过的第三方应用,是不会拿到令牌的

授权码模式

是指应用先申请一个授权码,然后再用这个授权码获取令牌

流程:

  • 客户端将用户导向认证服务器的授权页面
  • 用户在认证服务器页面登录并授权
  • 认证服务器返回授权码给客户端
  • 客户端将授权码传递给客户端所在的后端服务(也可以是自己的认证服务器),由后端服务在后端请求认证服务器获取令牌,并返回给客户端

密码模式

如果用户信任应用,应用可以直接携带用户的用户名和密码,直接申请令牌

流程:

  • 客户端要求用户提供用户名和密码
  • 客户端携带用户名和密码,访问授权服务器
  • 授权服务器验证用户身份之后,直接返回令牌

应用场景

  • 原生app授权:app登录请求后台接口,为了安全认证,所有请求都带token信息。如登录验证、 请求后台数据
  • 前后端分离单页面应用:前后端分离框架,前端请求后台数据,需要进行oauth2安全认证
  • 第三方应用授权登录,比如QQ,微博,微信的授权登录

JWT

概述

JSON Web Token(JWT)是一个开放标准协议,在用户和服务器之间作为 JSON 对象安全地传输信息,因为此信息是经过数字签名,所以可通过验证保证传输过程安全可靠,另外由于oauth2协议生成的token无法满足需求,所以一般在开发过程中,我们往往采用的是jwt生成token,主要原因

  • jwt安全性比较高,加密算法众多而且加上签名可以发现token被改写
  • jwt可以自定义属性信息
  • jwt存储在客户端,不用存放在内存,减少了服务器的压力

JWT 认证 VS session认证

首先需要说明 JSON Web Token 是可以用于认证的,那么就先来对比一下 JSON Web Token 认证和 传统的 session 认证的区别,传统的 session 认证是有状态的,也就是说我们需要在服务端保存用户的认证信息,如果服务端重新或者换一台服务器,那么这个认证就失效了,并且传统的 session 的认证方式扩展起来不是那么的容易。

基于 JSON Web Token 的鉴权机制类似于 http 协议,是一种无状态的,服务器不需要保存用户的认证信息或者会话信息,这也就意味着 JWT 认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利,也是由于这个特性,JWT 在微服务架构中应用广泛。

JWT基本原理

JWT运行原理

JWT执行流程:

  1. 客户端使用账号和密码请求登录接口
  2. 登录成功后服务器使用签名密钥生成JWT,并返回JWT给客户端
  3. 客户端再次向服务端请求其他接口时,请求头会带上JWT
  4. 服务器接收到JWT后验证签名的有效性,对客户端做出相应的响应

相比Session服务器就不用保存任何 session 数据了,这种通信无状态方式则更容易实现扩展

JWT数据结构

JWT在线调试工具: JSON Web Tokens - jwt.io

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NzMzMTkwNjEsInVzZXJfbmFtZSI6IlgxMjQwNjc2IiwiYXV0aG9yaXRpZXMiOlsiSm9iX0FkbWluIiwiU1lTX0FETUlOIiwiQ09SRV9BRE1JTiIsIlhYWF9BRE1JTiIsIkRldmVsb3Blcl9NaWxlc3RvbmVfUmVsYXRlZF9BUFAiLCJQWFhYX0FETUlOIiwiTlhYWF9BRE1JTiIsIlJYWFhfQURNSU4iXSwianRpIjoiNjcyMTEyYmEtYjhiYi00NWMyLTg2ZWMtOWVlNWUyZDk4MDQwIiwiY2xpZW50X2lkIjoiWFhYLWdhdGV3YXktbG9jYWwiLCJzY29wZSI6WyJzZXJ2ZXIiXX0.7NPLWeFJCL1MV0Sn4DhAievzL6bN6BqUrTiRUydnUHg

JWT实际上就是一个字符串"Header.Payload.Signature",中间使用 . 分割成三个部分:Header(头部),PayLoad(负载),Signature(签名)

Header

Header部分是一个JSON对象,描述JWT的元数据

{"alg": "HS256","typ": "JWT"
}
  • alg属性:表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256)
  • typ属性表示这个令牌(token)的类型(type),JWT 令牌固定写为JWT

最后使用 base64 加密算法转成字符串

PayLoad

Payload也是一个JSON对象,作为JWT主体内容,里面存放实际需要传递的一些有效信息,如下:

{"exp": 1673319061,"user_name": "X1240676","authorities": ["Job_Admin", "SYS_ADMIN", "CORE_ADMIN", "XXX_ADMIN",     "Developer_Milestone_Related_APP", "PXXX_ADMIN", "NXXX_ADMIN", "RXXX_ADMIN"],"jti": "672112ba-b8bb-45c2-86ec-9ee5e2d98040","client_id": "XXX-gateway-local","scope": ["server"]
}

JSON Web Token 标准定义中定义了以下 7 个字段:

  • iss(issuer): 签发人

  • exp (expiration time): 过期时间

  • sub (subject): 主题

  • aud (audience): 受众

  • nbf (Not Before): 生效时间

  • iat (Issued At): 签发时间

  • jti (JWT ID): 编号

除官方字段外还可以定义私有字段,最后使用 base64 加密算法转成字符串

Signature

Signature 部分是对前两部分的签名,防止数据篡改

对header和payload进行非对称加密获得签名

Header和Payload都是使用 Base64 进行编码的,Signature 需要使用编码后的 header 和 payload 以及我们提供的一个密钥secret(该密钥只有服务器才知道,不能泄露给用户),然后使用 header 中指定的签名算法(HS256)进行签名

HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret
)

计算得到签名以后,将Header、Payload、Signature 通过.拼接形成最终的JWt返回给客户端

JWT 的使用方式

客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息Authorization字段里面。

Authorization: Bearer <token>

应用场景

  • Authentication(鉴权)这是使用JWT最常见的情况。 一旦用户登录,每个后续请求都将包含JWT,允许用户访问该令牌允许的路由,服务和资源。 单点登录是当今广泛使用JWT的一项功能,因为它的开销很小,并且能够轻松地跨不同域使用
  • Information Exchange(信息交换)JSON Web Tokens是在各方之间安全传输信息的好方式。 因为JWT可以签名:例如使用公钥/私钥对,所以可以确定发件人是他们自称的人。 此外,由于使用标头和有效载荷计算签名,因此您还可以验证内容是否未被篡改

注意事项

JWT 默认不加密,由于其中包含了一些有效信息,若一旦泄露,任何人都可以获得该令牌的所有权限,所以不要将敏感数据写入 JWT。同时为了减少不法盗用,我们可以:

  • 将JWT 的有效期设置短一点,另外对于一些重要的权限,使用时应该再次对用户进行认证
  • 不应该使用 HTTP 协议明码传,而是使用 HTTPS 协议传输

以上是对于企业当中应用实战的知识铺垫,接下来我们进入企业实战叭~

​​​​​​​

国际化解决方案-非前后端分离模式

一.传统的jquery前端页面解决方案
jquery-i18前端页面国际化执行流程
1.首先需要引入jquery.js,jquery.i18n.properties.js,并且i18n放在jquery.js后面引入,并加入自定义的language.js代码
2.resource/static/assets/i18n创建不同语言文件,包括messages_zh_CN.properties/messages_zh_TW.properties/messages_en.properties等
3.在html页面中设置标签的class=i18n,data-properties="xxx" data-ptype="xxx",
    首先language.js会加载对应的语言文件
    然后根据页面对应的data-ptype找到data-properties
    最后利用jquery.i18n.properties.js的$.i18n.prop进行赋值,示例如下:
    <!--html-->
    <div class="i18n" data-properties="htmlmsg" data-ptype="html"></div>
    <!--text-->
    <div class="i18n" data-properties="hellomsg1" data-ptype="text"></div>
    
    
二.后端处理国际化
1. 新增语言拦截器localeChangeInterceptor,通过它来获取前台传递过来的语言参数

具体实现流程:
    a. LocaleConfig 配置类中自定义@Bean LocaleChangeInterceptor(配置类+@Bean就是一种:把bean加载到ioc容器的一种机制,并且覆盖底层的配置类)
    b. LocaleChangeInterceptor通过它的子类CdpCustomLocaleChangeInterceptor的preHandle方法来获取语言参数
       -- preHandle方法获取语言参数逻辑
            首先从request获取默认'DEFAULT_PARAM_NAME = "locale"'值,若为空,则从headerName中取值,
            若headerName也为空,则从cookies取值。
       -- 获取到newLocale语言值后,使用LocaleResolver进行解析,其中LocaleResolver是LocaleConfig配置类自定义的bean,其中若无法解析,则默认设置为zh-CN
    c. 编写获取语言资源服务LocaleMessageService,提供获取资源对应字段值、资源所有值等方法
       --其中 messageSource是LocaleConfig配置类自定义的bean,并通过ExposedResourceBundleMessageSource初始设置了语言资源路径以及编码,并提供获取资源key等方法
    d. WebMvcConfig 添加语言拦截器,绑定LocaleConfig 配置类中自定义LocaleChangeInterceptor
    
    实际应用
    e. Controller控制层调用LocaleMessageService提供的功能返回对应语言所需的资源信息
    f. CdpExceptionHandlerAspect类以面向切面的方式统一处理项目中异常错误信息的国际化

language.js参考代码

/**
 * cookie操作
 * https://github.com/FloweringVivian/jquery.i18n.properties
 */
var getCookie = function(name, value, options) {
    if (typeof value != 'undefined') { // name and value given, set cookie
        options = options || {};
        if (value === null) {
            value = '';
            options.expires = -1;
        }
        var expires = '';
        if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) {
            var date;
            if (typeof options.expires == 'number') {
                date = new Date();
                date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000));
            } else {
                date = options.expires;
            }
            expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE
        }
        var path = options.path ? '; path=' + options.path : '';
        var domain = options.domain ? '; domain=' + options.domain : '';
        var s = [cookie, expires, path, domain, secure].join('');
        var secure = options.secure ? '; secure' : '';
        var c = [name, '=', encodeURIComponent(value)].join('');
        
        var cookie = [c, expires, path, domain, secure].join('');
        document.cookie = cookie;
    } else { // only name given, get cookie
        var cookieValue = null;
        if (document.cookie && document.cookie != '') {
            var cookies = document.cookie.split(';');
            for (var i = 0; i < cookies.length; i++) {
                var cookie = jQuery.trim(cookies[i]);
                // Does this cookie string begin with the name we want?
                if (cookie.substring(0, name.length + 1) == (name + '=')) {
                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                    break;
                }
            }
        }
        return cookieValue;
    }
};

/**
 * 获取浏览器语言类型
 * @return {string} 浏览器国家语言
 */
var getNavLanguage = function(){
    if(navigator.appName == "Netscape"){
        var navLanguage = navigator.language;
        return navLanguage;
    }
    return false;
}

//设置语言类型: 默认为中文

var i18nLanguage = "zh-CN";

//设置一下网站支持的语言种类
var webLanguage = ['zh-CN','zh-TW', 'en'];

var moduleFolderPath = "";//模块文件夹

// 执行页面i18n方法
var execI18n = function(){

if (getCookie("_cdp_lang_")) {
        i18nLanguage = getCookie("_cdp_lang_");
    } else {
        // 获取浏览器语言
        var navLanguage = getNavLanguage();
        if (navLanguage) {
            // 判断是否在网站支持语言数组里
            var charSize = $.inArray(navLanguage, webLanguage);
            if (charSize > -1) {
                i18nLanguage = navLanguage;
                // 存到缓存中
                getCookie("_cdp_lang_",navLanguage);
            };
        } else{
            console.log("not navigator");
            return false;
        }
    }
    /* 需要引入 i18n 文件*/
    if ($.i18n == undefined) {
        console.log("请引入i18n js 文件")
        return false;
    };

/*
    这里需要进行i18n的翻译
     */
    jQuery.i18n.properties({
        name : 'messages', //资源文件名称
        //path : '/dev/assets/i18n/', //资源文件路径
        path : '/demo/assets/i18n/' + (moduleFolderPath == "" ? "" : (moduleFolderPath + "/") ),
        mode : 'map', //用Map的方式使用资源文件中的值 'both'
        language : i18nLanguage,
        callback : function() {//加载成功后设置显示内容

var insertEle = $(".i18n");
            insertEle.each(function() {
                var properties = $.trim($(this).attr('data-properties'));
                if(properties){
                    var pType = $(this).attr('data-ptype');
                    var pTypeArr = pType.split('/');
                    var propertiesArr = properties.split('/');
                
                    //获取前端i18n标签,根据类型从语言文件获取对应的属性值
                    for(var i=0;i<pTypeArr.length;i++){

if($.trim(pTypeArr[i]) == 'html'){

$(this).html($.i18n.prop($.trim(propertiesArr[i])));

}else if($.trim(pTypeArr[i]) == 'text'){

$(this).text($.i18n.prop($.trim(propertiesArr[i])));

}else if($.trim(pTypeArr[i]) == 'title'){

$(this).attr('title', $.i18n.prop($.trim(propertiesArr[i])));

}else if($.trim(pTypeArr[i]) == 'alt'){

$(this).attr('alt', $.i18n.prop($.trim(propertiesArr[i])));

}else if($.trim(pTypeArr[i]) == 'placeholder'){

$(this).attr('placeholder', $.i18n.prop($.trim(propertiesArr[i])));

}else if($.trim(pTypeArr[i]) == 'value'){

$(this).val($.i18n.prop($.trim(propertiesArr[i])));

};

};
                };
            });
        }
    });
        
}

function setI18nLanguageCookie(){
    // 将国际化语言值存到缓存cookies中
    getCookie("_lang_" , i18nLanguage);
}

/*页面执行加载执行*/
$(function(){

/*执行I18n翻译*/
    execI18n();
});

单点登录: 企业微服务架构中实现方案-上篇相关推荐

  1. 如何在微服务架构中实现安全性?

    点击上方"方志朋",选择"置顶公众号" 技术文章第一时间送达! 作者 | Chris Richardson 网络安全已成为每个企业都面临的关键问题.几乎每天都有 ...

  2. 骚年快答 | 微服务架构中的BFF到底是啥?

    [答疑解惑]| 作者 / Edison Zhou 这是恰童鞋骚年的第263篇原创内容 昨天的骚年快答<技术中台与业务中台都是啥玩意>一文中留下一个问题:BFF是啥?为啥在API网关和业务中 ...

  3. 【有料】微服务架构中的BFF到底是啥?

    在<技术中台与业务中台都是啥玩意>一文中留下一个问题:BFF是啥?为啥在API网关和业务中台之间加入了一层BFF?考虑到在实际工作中,我的大部分同事都问过这个问题,这里我也总结一下进行答复 ...

  4. 网站如何经过身份验证_如何在微服务架构中实现安全性?

    首先为自己打个广告,我目前在某互联网公司做架构师,已经有5年经验,每天都会写架构师系列的文章,感兴趣的朋友可以关注我和我一起探讨,同时需要架构师资料的可以私信我免费送 作者 | Chris Richa ...

  5. 微服务架构中的BFF到底是啥?

    一.从一个MyShop开始说起 为了讲清BFF是个啥,这里引用我在波波老师的课程<Spring Boot与K8s云原生应用开发>中学到的一个案例,来跟大家分享一下,并尽力说清楚BFF是啥, ...

  6. 认证鉴权与API权限控制在微服务架构中的设计与实现

    引言: 本文系<认证鉴权与API权限控制在微服务架构中的设计与实现>系列的第一篇,本系列预计四篇文章讲解微服务下的认证鉴权与API权限控制的实现. 1. 背景 最近在做权限相关服务的开发, ...

  7. Java生鲜电商平台-SpringCloud微服务架构中网络请求性能优化与源码解析

    Java生鲜电商平台-SpringCloud微服务架构中网络请求性能优化与源码解析 说明:Java生鲜电商平台中,由于服务进行了拆分,很多的业务服务导致了请求的网络延迟与性能消耗,对应的这些问题,我们 ...

  8. Java生鲜电商平台-SpringCloud微服务架构中分布式事务解决方案

    Java生鲜电商平台-SpringCloud微服务架构中分布式事务解决方案 说明:Java生鲜电商平台中由于采用了微服务架构进行业务的处理,买家,卖家,配送,销售,供应商等进行服务化,但是不可避免存在 ...

  9. 认证鉴权与API权限控制在微服务架构中的设计与实现(一)

    作者: [Aoho's Blog] 引言: 本文系<认证鉴权与API权限控制在微服务架构中的设计与实现>系列的第一篇,本系列预计四篇文章讲解微服务下的认证鉴权与API权限控制的实现. 1. ...

  10. Spring Cloud与微服务学习总结(6)——认证鉴权与API权限控制在微服务架构中的设计与实现(四)

    本文转载自(http://blueskykong.com/2017/10/26/security4/) 1. 前文回顾 首先还是照例对前文进行回顾.在第一篇 认证鉴权与API权限控制在微服务架构中的设 ...

最新文章

  1. Python常用6个技术网站汇总分享!
  2. 0x07.基本算法 — 贪心
  3. Mybatis【一对多、多对一、多对多】知识要点
  4. 教你如何让电脑的ADSL宽带连接开机自动拨号
  5. DCMTK:类DcmOther64bitVeryLong的测试程序
  6. c语言二维数组省略号,LaTex 常用语法
  7. 听力技巧-4大难点讲析
  8. 洛谷——(100分)P1590 失踪的7
  9. 安卓电视 TV端的webview网页 按键控制和一些小问题
  10. pdf添加水印的方法
  11. Win10电脑只有一个C盘怎么分区分盘?
  12. 小米手机的专用计算机连接软件,小米手机怎么连接电脑?这些方法值得收藏!...
  13. 三次握手 resend
  14. 洛谷 P1538 迎春舞会之数字舞蹈
  15. 推荐一本好书《 Java程序员 上班那点事儿》
  16. vscode报错http://127.0.0.1:5500/11.html 找不到应用程序
  17. python 白噪声检验-时间序列 平稳性检验 白噪声 峰度 偏度
  18. Hadoop Streaming 实战: 实用Partitioner类KeyFieldBasedPartitioner
  19. 【mindspore】mindspore实现手写数字识别
  20. 6.Python之函数

热门文章

  1. 蓝桥杯 连续区间数(抖机灵做法)
  2. Programming Languages PartA Week4学习笔记——SML函数式编程
  3. python 绕过 反爬
  4. 计算机程序设计专业大学排名,全国计算机专业大学排名一览表
  5. 【vn.py】SpreadTrading价差交易
  6. html百度换皮肤,windows7系统下怎么给百度网页更换皮肤
  7. Win32反汇编(三)深层次的了解各种转移指令:IF语句有符号与无符号跳转
  8. 【目标检测】11、Region Proposal by Guided Anchoring
  9. led大屏按实际尺寸设计画面_微间距LED大屏幕拼接显示系统设计方案
  10. Unity - Timeline 之 Timeline window(Timeline窗口)