目录

  • 一.HRM认证授权方案
    • 1.HRM认证方案分析
      • 1.1 问题分析
      • 1.2 解决方案
  • 二.认证服务
    • 1.认证服务搭建
    • 2.认证服务集成Security+Oauth2+JWT
    • 3.定义登录流程
      • 3.1.数据库设计
      • 3.2.定义UserDetailService
      • 3.3.为system编写Feign接口
      • 3.4.测试获取Token
  • 三.资源服务
  • 四.服务之间临时授权
    • 1.问题分析
    • 2.编码实战
      • 1.导入Http依赖
      • 2.封装Http工具
      • 3.添加拦截器
      • 4.配置客户端详情
  • 五.前端登录逻辑
    • 1.VUE前端登录
      • 1.1.获取Token
      • 1.2.请求头添加Token
      • 1.3.zuul导致请求头信息转发失败
    • 2.门户站点登录
      • 2.1.登录逻辑
      • 2.2.拦截Ajax请求添加Token
      • 2.3.多个前段站点测试
  • 六.Token无感刷新
    • 1.问题描述
    • 2. 编码实战
      • 2.1.添加请求响应拦截器
      • 2.2.后台刷新
      • 2.3.后台url放行
  • 七.注册逻辑修改

一.HRM认证授权方案

1.HRM认证方案分析

1.1 问题分析

项目采用微服务架构,使用前后端分离方案,项目分为门户网站(分为职位,课程,用户等等)端和后台管理端,整个项目的用户包括:平台方用户(后台),入驻机构方用户(后台),互联网用户(门户) , 而后台的用户表为t_employee(数据库hrm-system) ,门户用户表为t_login_info(数据库hrm-usercenter),且项目实现了微服务分库方案,如何实现系统的认证授权方案?

1.2 解决方案

方案一:客户端做认证时除了传入账户,密码,再传入一个标记如client=system即标记为系统管理登录,门户网站登录传入client=website即标记为前台登录,然后通过自定义认证Filter判断请求中的client标记来调用不用的UserDetailService加载不同库中的用户,只是该方案需要自定义AuthenticationFilter,以及多个AuthenticationProvider以及多个UserDetailsService 。

方案二:前台做前台的登录逻辑,后台做后台的登录逻辑,即校验用户名密码即可,在登录逻辑中如果登录成功就使用简化模式/或者客户端模式 向认证服务器发送一个获取Token的请求,得到Token之后,把Token响应给浏览器,但是这种模式有一个问题,就是如何去加载用户的权限呢?

方案三:后台管理系统和前台门户系统统一授权,那么应该如何解决门户系统用户和后台管理系统用户表表不一致,且数据库也不是同一个的问题?解决方案是:将t_employee(数据库hrm-system) 和 t_user(数据库hrm-usercenter) 抽像一个表,如: t_login(hrm-auth),t_login中字段为前/后台用户认证需要的公共字段,如:username,password,另外需要多加一个字段user_type来标识不同的用户类型,然后在UserDeailService中根据类型加载不同数据库的认证信息实现认证 , 而前端登录直接向认证中心请求Token即可

最终采用方案:方案三

二.认证服务

1.认证服务搭建

省略…见“SpringSecurity-微服务授权”

2.认证服务集成Security+Oauth2+JWT

省略…见“SpringSecurity-微服务授权”

3.定义登录流程

3.1.数据库设计

把前台门户用户表(hrm-user.t_user)和后台管理用户表(hrm-system.t_employee)用作登录的字段如:username,password抽取到公共表,hrm-auth.t_login

1.登录表 hrm-auth.t_login

Id : 主键
Username :账户
Password : 密码
Type : 用户类型,后台用户 / 前台用户
enabled  :账户启用
account_non_expired :账户过期
credentials_non_expired  :密码过期
account_non_locked   :账户锁定

抽象一个公共的登录表:t_login,这个表里面是所有的employee+sso里面的用户数据的登录信息,另外密码要进行BCryptPasswordEncoder加密

2.后台员工表 hrm-system.t_employee 主要字段

Id : 主键
login_id : 登录表id外键
...

t_employee表外键关联t_login的id

3.前台门户用户表hrm-user.t_vip_user

Id : 主键
login_id :登录表id外键
...

t_sso表外键关联t_login的id

3.2.定义UserDetailService

需要注意的是,我们已经对登录表做了抽象,所以根据用户名去hrm-auth.t_login表中查询认证信息,如果是系统用户需要加载用户的权限列表。

package cn.itsource.hrm.details;
import cn.itsource.hrm.client.SystemFeignClient;
import cn.itsource.hrm.domain.Login;
import cn.itsource.hrm.domain.Permission;
import cn.itsource.hrm.service.ILoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;//加載用戶在數據庫中的 認證信息和 授權信息
@Component
public class UserDetailServiceImpl implements UserDetailsService {@Autowiredprivate ILoginService loginService ;@Autowiredprivate SystemFeignClient systemFeignClient;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//加載數據庫中的的用戶Login login = loginService.selectByUsername(username);if(login == null)throw new UsernameNotFoundException("账户不存在");//加載用戶權限List<SimpleGrantedAuthority> authorities = new ArrayList<>();if(login.getType().intValue() == Login.TYPE_SYSTEM){//后台用户,获取权限表达式列表List<String> userPermissions = systemFeignClient.listByLoginId(login.getId());//把用戶的權限封裝成 UserDetail需要的 List<SimpleGrantedAuthority>  結構userPermissions.forEach(sn-> {authorities.add(new SimpleGrantedAuthority(sn));});}//封裝userDetails對象UserDetails userDetails = new org.springframework.security.core.userdetails.User(username,login.getPassword(),authorities);return userDetails;}
}

其他的授权服务配置,数据库等拷贝之前的代码即可,一抹一样。

3.3.为system编写Feign接口

如果登录用户是后台用户,我们需要为该用户查询权限列表,权限表在Hrm-system库中,所以要为system服务编写Feign接口,然后auth认证服务集成Feign,通过Feign接口查询当前用户所拥有的的权限列表

…省略…

3.4.测试获取Token
/oauth/token?client_id=webapp&client_secret=secret&grant_type=password&code=KsqVIC&redirect_uri=http://www.baidu.com&username=yhptest1&password=123


postmain发送post请求

三.资源服务

省略,跟以前一抹一样配置

可以把oauth2资源服务的配置抽取到公共的模块中
服务之间的授权,可以把feign的拦截器抽取到一个公共模块

四.服务之间临时授权

1.问题分析

客户端向认证中心发起请求获取Token,由于加载权限的时候权限列表在系统管理服务,这就意味着认证中心需要通过Feign调用系统管理,如果系统管理做了授权,那么认证中心就必须带着Token去访问,那这里就会产生一个问题,本来认证中心需要加载权限生成Token,而加载权限的时候又必须先传入一个Token过去,这不就矛盾了吗?如何解决?
方案一:我们可以考虑把权限表和认证表放在同一个库中,这样认证中心就不需要通过Feign远程调用系统服务了
方案二:我们可以在认证服务调用系统服务的时候通过拦截器拦截Feign,然后使用客户端模式生成临时的Token去访问系统服务,加载到权限再为用户生成Token。

2.编码实战

如何生成临时的Token?使用客户端模式向认证服务器发送请求Http即可如下:

http://localhost:1110/oauth/token?client_id=system&client_secret=123&grant_type=client_credentials
1.导入Http依赖
<dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>4.5.5</version>
</dependency>
<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.73</version>
</dependency>
2.封装Http工具
package cn.itsource.hrm.util;
import com.alibaba.fastjson.JSON;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.util.Map;public class HttpUtil {//发送Post请求,注意:参数使用?方式带着URL后面public static Map<String,String> sendPost(String url) {// 获得Http客户端(可以理解为:你得先有一个浏览器;注意:实际上HttpClient与浏览器是不一样的)CloseableHttpClient httpClient = HttpClientBuilder.create().build();// 创建Post请求HttpPost httpPost = new HttpPost(url);// 响应模型CloseableHttpResponse response = null;try {// 由客户端执行(发送)Post请求response = httpClient.execute(httpPost);// 从响应模型中获取响应实体HttpEntity responseEntity = response.getEntity();if (responseEntity != null) {return JSON.parseObject(EntityUtils.toString(responseEntity),Map.class);}} catch (Exception e) {e.printStackTrace();} finally {try {// 释放资源if (httpClient != null) {httpClient.close();}if (response != null) {response.close();}} catch (IOException e) {e.printStackTrace();}}return null;}
}
3.添加拦截器
@Component
@Slf4j
public class OAuth2FeignRequestInterceptor implements RequestInterceptor {private static String TEMPTOKENURL = "http://localhost:1110/oauth/token?client_id=%s&client_secret=%s&grant_type=client_credentials";//请求头中的tokenprivate final String AUTHORIZATION_HEADER = "Authorization";@Overridepublic void apply(RequestTemplate requestTemplate) {//requestTemplate:feign底层用来发请求的http客户端//使用客户端模式生成一个临时的TokenMap<String, String> tokenMap = HttpUtil.sendPost(String.format(TEMPTOKENURL, "temp", "123"));ValidUtils.assertNotNull(tokenMap,"服务调用失败[临时Token获取失败]");log.info("Feign拦截器添加请求头:{}",tokenMap);//3.添加到Feign的请求头requestTemplate.header(AUTHORIZATION_HEADER,"Bearer "+tokenMap.get("access_token"));}
}
4.配置客户端详情

我们要为临时Token准备一个客户端详情配置

解释:temp作为临时的Token,可以在authorities配置默认权限

五.前端登录逻辑

1.VUE前端登录

1.1.获取Token

方案一:

前端登录可以使用“password”模式直接向认证中心发请求,但是这样一来就需要在参数中拼接秘钥secrect,如下:

//拼接获取tokne的参数
var param = "username="+this.ruleForm2.account+"&password="+this.ruleForm2.checkPass+"&client_id=system&client_secret=123&grant_type=password";
//请求参数的内容格式为普通方式,否则默认使用JSON格式提交参数
var config={headers: {'Content-Type': 'application/x-www-form-urlencoded'}
};
//发起请求 http://localhost:1020/hrm/auth/auth/oauth/token?
this.$http.post("/auth/oauth/token",param,config).then(res=>{//获取到Tokenvar token = res.data.access_token;var refresh_token = res.data.refresh_token;//把token存储起来sessionStorage.setItem("U-TOKEN",token);sessionStorage.setItem("R-TOKEN",refresh_token);sessionStorage.setItem("user",'{"username":"'+this.ruleForm2.account+'"}');//修改登录成功后跳转到首页this.$router.push({ path: '/echarts' });this.logining = false;return;
}).catch(error => {this.$message.error(error.message);this.logining = false;return;
});

方案二:
如果您觉得不安全,我们可以先只带着用户名,密码,clientId请求后台的登录逻辑,在后台登录逻辑中拼接好秘钥向认证中心获取Token

发送登录请求,额外多加了一个clientId,主要是用来获取令牌时会用到,不同的客户端登录可以指定不通的clientId

//拼接获取tokne的参数
var param = {username:this.ruleForm2.account,password:this.ruleForm2.checkPass,type:1
};
//发起请求 http://localhost:1020/hrm/  auth/auth/oauth/token?
this.$http.post("/auth/login/user",param).then(res=>{//获取到Tokenif(res.data.success){console.log(res.data);var token = res.data.resultObj.access_token;var refresh_token = res.data.resultObj.refresh_token;//把token存储起来sessionStorage.setItem("U-TOKEN",token);sessionStorage.setItem("R-TOKEN",refresh_token);sessionStorage.setItem("user",'{"username":"'+this.ruleForm2.account+'"}');//修改登录成功后跳转到首页this.$router.push({ path: '/echarts' });this.logining = false;return;}else{this.$message.error(res.data.message);}
}).catch(error => {this.$message.error(error.message);this.logining = false;return;
});

编写后台登录逻辑,

//系统登录
@RequestMapping(value="/user")
public AjaxResult login(@RequestBody Login login){AccessTokenVo accessTokenVo = loginService.getOauthToken(login);return AjaxResult.me().setResultObj(accessTokenVo);
}
//获取密码模式的Token
public static final String URL_TOKEN_PASSWORD = "http://localhost:1100/oauth/token?client_id=%s&client_secret=%s&grant_type=password&username=%s&password=%s";
public static final String URL_TOKEN_REFRESH = "http://localhost:1100/oauth/token?client_id=%s&client_secret=%s&grant_type=refresh_token&refresh_token=%s";@Autowired
private Oauth2ClientDetailsProperties properties;@Override
public AccessTokenVo getOauthToken(Login login) {//根据传入的type,得到对应的客户端配置Oauth2ClientDetails clientDetails = properties.getClientDetials(login.getType());//1.拼接一个获取oath2的token的url:密码模式String url = String.format(URL_TOKEN_PASSWORD, clientDetails.getClientId(),clientDetails.getSecret(), login.getUsername(), login.getPassword());//2.发送请求获取tokenMap<String, String> map = HttpUtil.sendPost(url);//3.封装结果String tokenJson = JSON.toJSONString(map);return JSON.parseObject(tokenJson,AccessTokenVo.class );
}
@Component
@ConfigurationProperties(prefix = "oauth2.client")
@Data
public class Oauth2ClientDetailsProperties {private Oauth2ClientDetails website;private Oauth2ClientDetails system;public Oauth2ClientDetails getClientDetials(Integer type) {ParamsAssert.isNotNull(type, ErrorCode.CODE_302_LOGIN_TYPE_NOT_NULL);switch (type.intValue()){case Login.TYPE_SYSTEM : return system;case Login.TYPE_WEBSITE : return website;}ParamsAssert.isNotNull(type, ErrorCode.CODE_302_LOGIN_TYPE_NOT_NULL);return null;}
}
@Data
public class Oauth2ClientDetails {private String clientId;private String secret;
}
oauth2:client:website:clientId: websitesecret: 123system:clientId: systemsecret: 123

从配置中根据clientId获取秘钥Secrect,拼接成完整的URL向认证中心获取Token,把结果返回给浏览器。

1.2.请求头添加Token

Main.js : axios请求拦截,每次请求都在请求头携带token,资源服务会对请求头中的token进行校验.

axios.interceptors.request.use(config => {//如果已经登录了,每次都把token作为一个请求头传递过程if (sessionStorage.getItem('U-TOKEN')) {// 让每个请求携带token--['X-Token']为自定义key 请根据实际情况自行修改config.headers['Authorization'] = "Bearer "+sessionStorage.getItem('U-TOKEN')}return config
}, error => {// Do something with request errorPromise.reject(error)
});
1.3.zuul导致请求头信息转发失败
zuul:sensitive-headers:     #把zuul忽略敏感请求头的配置覆盖为空

2.门户站点登录

2.1.登录逻辑

注意:我们前端有多个站点组成,如果要实现单点登录,就需要使用Cookies跨域实现登录之后Token在多个站点中都可以使用

<script src="/js/axios.common.js"></script>
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" src="js/jquery.cookie.js"></script>
 var param = {username:this.formParams.phone,password:this.formParams.password,type:0};//发起请求 http://localhost:1020/hrm/  auth/auth/oauth/token?this.$http.post("/auth/login/user",param).then(res=>{//获取到Tokenif(res.data.success) {var token = res.data.resultObj.access_token;var refresh_token = res.data.resultObj.refresh_token;$.cookie('U-TOKEN',token,{expires:7,path:'/',domain:'hrm.com',secure:false});$.cookie('R-TOKEN',refresh_token,{expires:7,path:'/',domain:'hrm.com',secure:false});sessionStorage.setItem("user", '{"username":"' + this.formParams.phone + '"}');//修改登录成功后跳转到首页alert("登录成功");location.href="http://course.hrm.com:6002/list.html";return;}else{alert("登录失败:"+ajaxResult.message);
}}).catch(error => {alert("登录失败:"+ajaxResult.message);return;});
2.2.拦截Ajax请求添加Token

Axios.common.js

axios.interceptors.request.use(config => {var token = $.cookie('U-TOKEN');if (token && token.length >= 0){config.headers['Authorization'] = "Bearer "+token;}return config
}, error => {Promise.reject(error)
})
2.3.多个前段站点测试

1.配置hosts

127.0.0.1 course.hrm.com
127.0.0.1 user.hrm.com

使用不同的域名访问用户站点和课程站点,事项一次登录,多个前段站点都有token

2.后台配置zuul跨域

六.Token无感刷新

1.问题描述

当Token失效,请求会返回401错误,我们先应用程序可以自动刷新Token,无需用户重新登录,除非refresh_token 也过期了,我们认为用户登录信息过期需要重新登录。

我们可以在后端实现无感刷新,添加Oauth2的异常处理器,如果是401Token失效异常,自动发送Token刷新请求,把新的Token通过cookie写到客户端,然后重新转发请求,由于我们是前后端分离项目,前端使用的是sessionStorage存储Token,这种方案实现起来很长麻烦,所有我们采用前端自动刷新方案。

前端实现无感刷新的思路是在Axios响应结果的时候判断异常如果401 Token过期,我们就主动发送一个请求到后端刷新Token,然后把新的Token保存起来替换旧的Token,然后使用新的Token重新发起请求即可。

2. 编码实战

2.1.添加请求响应拦截器

这里判断了如果是401的响应代表Token过期,重新组装参数刷新Token,然后重新设置到localStorage,然后调用 axios.request 继续执行请求 , 需要注意的是刷新Token的请求必须是同步的,因为我们需要拿到Token后才能继续发请求。

//处理Token刷新========================================================================
axios.interceptors.response.use(config => {return config
},error => {if (error && error.response) {console.log(error);switch (error.response.status) {case 401: return doRequest(error);case 403: return doNoPerHandler(error);}}Promise.reject(error)
});
function doNoPerHandler(error) {alert("没访问权限");
}
async function doRequest (error) {try {//获取新的Tokenconst data = await getNewToken();var token = data.data.resultObj.access_token;var refresh_token = data.data.resultObj.refresh_token;sessionStorage.setItem("U-TOKEN",token);sessionStorage.setItem("R-TOKEN",refresh_token);//继续执行上一次失败的请求const res = await axios.request(error.config);return res;} catch(err) {alert("登录失效,请重新登录");sessionStorage.clear();router.replace({ path:"/login" });return err;}
}//刷新Token
async function getNewToken() {var refreshToken = sessionStorage.getItem('R-TOKEN');if(refreshToken){return await axios({url: '/auth/login/refresh?refreshToken='+refreshToken+"&type=1",method: 'post',headers: {'Content-Type':'application/x-www-form-urlencoded'}})}else{alert("登录失效,请重新登录");sessionStorage.clear();router.replace({ path:"/login" });}
}//处理Token刷end=========================================
2.2.后台刷新
//系统刷新
@RequestMapping(value="/refresh")
public AjaxResult refresh(@RequestParam("refreshToken") String refreshToken,@RequestParam("type") Integer type){AccessTokenVo accessTokenVo = loginService.refresh(refreshToken,type);return AjaxResult.me().setResultObj(accessTokenVo);
}
@Override
public AccessTokenVo refresh(String refreshToken,Integer type) {//根据传入的type,得到对应的客户端配置Oauth2ClientDetails clientDetails = properties.getClientDetials(type);//1.拼接一个获取oath2的token的url:密码模式String url = String.format(URL_TOKEN_REFRESH, clientDetails.getClientId(),clientDetails.getSecret(),refreshToken);//2.发送请求获取tokenMap<String, String> map = HttpUtil.sendPost(url);//3.封装结果String tokenJson = JSON.toJSONString(map);return JSON.parseObject(tokenJson,AccessTokenVo.class );
}
2.3.后台url放行
.antMatchers("/login","/login/user","/login/refresh").permitAll()

七.注册逻辑修改

1.加密方式修改

vipUser.setPassword(passwordEncoder.encode(password));

2.保持登录表数据

//1.加密方式
//2.同步一个t_login到认证中心
AjaxResult ajaxResult = authFeignClient.save(new Login(mobile, vipUser.getPassword(), Login.TYPE_WEBSITE));
ParamsAssert.isTrue(ajaxResult.isSuccess(), ErrorCode.CODE_206_REGISTER_LOGIN_SAVE_ERROR);

3.编写认证中心Feign接口
4.编写认证中心保持方法

@RequestMapping(value="/save",method= RequestMethod.POST)
public AjaxResult save(@RequestBody Login login){loginService.insert(login);return AjaxResult.me();
}
@Override
public boolean insert(Login login) {//根据用户的类型,获取 clientIdOauth2ClientDetails clientDetails = properties.getClientDetials(login.getType());login.setClientId(clientDetails.getClientId());return super.insert(login);
}

注意:访问要用域名

HRM认证授权方案_新版相关推荐

  1. 基于.NetCore3.1搭建项目系列 —— 认证授权方案之Swagger加锁

    1 开始 在之前的使用Swagger做Api文档中,我们已经使用Swagger进行开发接口文档,以及更加方便的使用.这一转换,让更多的接口可以以通俗易懂的方式展现给开发人员.而在后续的内容中,为了对a ...

  2. 基于.NetCore3.1系列 —— 认证授权方案之授权揭秘 (下篇)

    一.前言 回顾:基于.NetCore3.1系列 -- 认证授权方案之授权揭秘 (上篇) 在上一篇中,主要讲解了授权在配置方面的源码,从添加授权配置开始,我们引入了需要的授权配置选项,而不同的授权要求构 ...

  3. 认证授权方案之授权揭秘 (上篇)

    一.前言 回顾:认证授权方案之授权初识 从上一节中,我们在对授权系统已经有了初步的认识和使用,可以发现,asp.net core为我们提供的授权策略是一个非常强大丰富且灵活的认证授权方案,能够满足大部 ...

  4. 认证授权方案之JwtBearer认证

    1.前言 回顾:认证方案之初步认识JWT 在现代Web应用程序中,即分为前端与后端两大部分.当前前后端的趋势日益剧增,前端设备(手机.平板.电脑.及其他设备)层出不穷.因此,为了方便满足前端设备与后端 ...

  5. 认证授权方案之授权初识

    1.前言 回顾:认证授权方案之JwtBearer认证 在上一篇中,我们通过JwtBearer的方式认证,了解在认证时,都是基于Claim的,因此我们可以通过用户令牌获取到用户的Claims,在授权过程 ...

  6. 开源软件加密授权方案_身份验证和授权作为开源解决方案服务

    开源软件加密授权方案 通过实施身份验证和授权(a&a)机制为所有用户数据设计集中式服务. 我将分享我的经验并最终确定解决方案的结论. 该设计包括客户端(Web应用程序)和服务器(A&A ...

  7. 分布式系统认证方案_分布式系统认证需求_Spring Security OAuth2.0认证授权---springcloud工作笔记135

    技术交流QQ群[JAVA,C++,Python,.NET,BigData,AI]:170933152 分布式认证需求 分布式系统的每个服务都会有认证.授权的需求,如果每个服务都实现一套认证授权逻辑会非 ...

  8. jwt token注销_【原创精选】OAuth 2.0+JWT+spring security完成认证授权-生产级-附带源码...

    前言导读 分析一下为什么要用OAuth2和JWT来做 1. **单点登录(SSO)**方案单击登录方案是最常见的解决方案,但单点登录需要每个与用户交互的服务都必须与认证服务进行通信,这不但会造成重复, ...

  9. Spring Security OAuth2.0_实现分布式认证授权_集成测试_Spring Security OAuth2.0认证授权---springcloud工作笔记155

    技术交流QQ群[JAVA,C++,Python,.NET,BigData,AI]:170933152 然后前面我们已经把分布式微服务的,认证授权全部集成了,然后我们来测试. 启动资源微服务order微 ...

最新文章

  1. 【干货】搭建社区运营团队的一些经验和“血的教训”
  2. 转载:flash 跨域 crossdomain.xml
  3. php伪静态后不能访问html,php伪静态后html不能访问怎么办
  4. mysql 同一天多条记录只取第一条_MySQL面试高频100问(二)
  5. window+VS+OpenCV编译libfacedetection库进行人脸检测
  6. 软件设计师考试 | 第四章 操作系统知识 | 文件管理
  7. 三菱伺服自动调谐_自动化领域最值得关注的十大伺服电机
  8. Java基础学习,一些零散的笔记之抽象类与接口
  9. 10.15 iptables filter表小案例;10.16—10.18 iptables nat
  10. 不能随便给他人登录微信小程序
  11. Android中的RAM、ROM、SD卡以及各种内存的区别
  12. centos 加密解密
  13. 高校手机签到系统——zxing.net生成二维码(补充)
  14. python运维小工具_Python实现跨平台运维小神器
  15. 简单五子棋,加入存储,读取功能
  16. Linux下串口编程总结
  17. 从DS1302电路设计总结的晶振电路设计规范
  18. oracle++spm,ORACLE 11G 使用SPM来调整SQL语句的执行计划
  19. vmware加速方法总结
  20. [转]deepin系统添加开机运行命令、软件自启动方法

热门文章

  1. c语言制作二元一次方程组的根,编程计算二元一次方程的根
  2. 腾讯云Linux服务器 centos7 Lampp环境搭建 vsftp搭建 ssl证书安装 所遇到的问题
  3. 激活函数,优化技术和损失函数
  4. LIGO探测到两个“瘦子”黑洞形成的引力波
  5. 兄弟连兄弟会培训高级管理人员
  6. HP-UX 11.31 安装RAC 添加共享磁盘的问题
  7. 滴滴php面试题,面经:三次滴滴面试通过,一些真实细节分享给大家
  8. Oriented Response Networks 论文笔记
  9. 水滴筹赴美上市:以“善良”为名的生意经
  10. STM32f1之简单控制继电器模块(附源码)