电商项目part06 微服务网关整合OAuth2.0授权中心

微服务网关整合 OAuth2.0 思路分析

网关整合 OAuth2.0 有两种思路,一种是授权服务器生成令牌, 所有请求统一在网关层验证,判断权
限等操作;另一种是由各资源服务处理,网关只做请求转发。 比较常用的是第一种,把API网关作
为OAuth2.0的资源服务器角色,实现接入客户端权限拦截、令牌解析并转发当前登录用户信息给微
服务,这样下游微服务就不需要关心令牌格式解析以及OAuth2.0相关机制了。
网关在认证授权体系里主要负责两件事:
(1)作为OAuth2.0的资源服务器角色,实现接入方访问权限拦截。
(2)令牌解析并转发当前登录用户信息(明文token)给微服务

微服务拿到明文token(明文token中包含登录用户的身份和权限信息)后也需要做两件事:
(1)用户授权拦截(看当前用户是否有权访问该资源)
(2)将用户信息存储进当前线程上下文(有利于后续业务逻辑随时获取当前用户信息)

大致流程

在这里插入图片描述

授权中心

<!-- spring security oauth2-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>

数据库 表

DROP TABLE IF EXISTS `oauth_client_details`;
CREATE TABLE `oauth_client_details`  (
  `client_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `resource_ids` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `client_secret` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `scope` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `authorized_grant_types` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `web_server_redirect_uri` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `authorities` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `access_token_validity` int(11) NULL DEFAULT NULL,
  `refresh_token_validity` int(11) NULL DEFAULT NULL,
  `additional_information` varchar(4096) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `autoapprove` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`client_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

在这里插入图片描述
在这里插入图片描述

实现非对称加密

第一步:生成jks 证书文件
我们使用jdk自动的工具生成
命令格式
keytool
-genkeypair 生成密钥对
-alias jwt(别名)
-keypass 123456(别名密码)
-keyalg RSA(生证书的算法名称,RSA是一种非对称加密算法)
-keysize 1024(密钥长度,证书大小)
-validity 365(证书有效期,天单位)
-keystore D:/jwt/jwt.jks(指定生成证书的位置和证书名称)
-storepass 123456(获取keystore信息的密码)
-storetype (指定密钥仓库类型)
使用 “keytool -help” 获取所有可用命令

keytool ‐genkeypair ‐alias jwt ‐keyalg RSA ‐keysize 2048 ‐keystore D:/jwt/jwt.jks

在这里插入图片描述
将生成的jwt.jks文件cope到授权服务器的resource目录下
在这里插入图片描述
查看公钥信息

keytool ‐list ‐rfc ‐‐keystore jwt.jks | openssl x509 ‐inform pem ‐pubkey

在这里插入图片描述

第二步:授权服务中增加jwt的属性配置类

/**
 * @vlog: 高于生活,源于生活
 * @desc: 类的描述: jwt 证书配置
 * @author: smlz
 * @version: 1.0
 */
@Data
@ConfigurationProperties(prefix = "my.jwt")
public class JwtCAProperties {
    /**
     * 证书名称
     */
    private String keyPairName;
    /**
     * 证书别名
     */
    private String keyPairAlias;
    /**
     * 证书私钥
     */
    private String keyPairSecret;
    /**
     * 证书存储密钥
     */
    private String keyPairStoreSecret;
}

yml中添加jwt配置

tuling:
 jwt:
  keyPairName: jwt.jks
  keyPairAlias: jwt
  keyPairSecret: 123123
  keyPairStoreSecret: 123123

第三步:修改AuthServerConfig的配置,支持非对称加密

@Configuration
@EnableAuthorizationServer
@EnableConfigurationProperties(value = JwtCAProperties.class)
public class MyAuthServerConfig extends AuthorizationServerConfigurerAdapter {
	
    
    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private DataSource dataSource;

    @Autowired
    private MyUserDetailService myUserDetailService;



//修改JwtTokenStoreConfig的配置,支持非对称加密

    @Autowired
    private JwtCAProperties jwtCAProperties;

 	/**
     * 方法实现说明:我们颁发的token通过jwt存储
     * @author:smlz
     * @return:
     * @exception:
     * @date:2020/1/21 21:49
     */
    @Bean
    public TokenStore tokenStore(){
        return new JwtTokenStore(jwtAccessTokenConverter());
    }


	@Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        //jwt的密钥
        converter.setKeyPair(keyPair());
        return converter;
    }

    @Bean
    public KeyPair keyPair() {
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource(jwtCAProperties.getKeyPairName()), jwtCAProperties.getKeyPairSecret().toCharArray());
        return keyStoreKeyFactory.getKeyPair(jwtCAProperties.getKeyPairAlias(), jwtCAProperties.getKeyPairStoreSecret().toCharArray());
    }

	//扩展JWT中的存储内容
    @Bean
    public MyTokenEnhancer myTokenEnhancer() {
        return new MyTokenEnhancer();
    }


	/**
     * 方法实现说明:认证服务器能够给哪些 客户端颁发token  我们需要把客户端的配置 存储到
     * 数据库中 可以基于内存存储和db存储
     * @author:smlz
     * @return:
     * @exception:
     * @date:2020/1/15 20:18
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(clientDetails());
    }

    /**
     * 方法实现说明:用于查找我们第三方客户端的组件 主要用于查找 数据库表 oauth_client_details
     * @author:smlz
     * @return:
     * @exception:
     * @date:2020/1/15 20:19
     */
    @Bean
    public ClientDetailsService clientDetails() {
        return new JdbcClientDetailsService(dataSource);
    }

    /**
     * 方法实现说明:授权服务器的配置的配置
     * @author:smlz
     * @return:
     * @exception:
     * @date:2020/1/15 20:21
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {

        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(myTokenEnhancer(),jwtAccessTokenConverter()));

        endpoints.tokenStore(tokenStore()) //授权服务器颁发的token 怎么存储的
                .tokenEnhancer(tokenEnhancerChain)
                .userDetailsService(myUserDetailService) //用户来获取token的时候需要 进行账号密码
                .authenticationManager(authenticationManager);
    }
 
    /**
     * 方法实现说明:授权服务器安全配置
     * @author:smlz
     * @return:
     * @exception:
     * @date:2020/1/15 20:23
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        //第三方客户端校验token需要带入 clientId 和clientSecret来校验
        security .checkTokenAccess("isAuthenticated()")
                 .tokenKeyAccess("isAuthenticated()");//来获取我们的tokenKey需要带入clientId,clientSecret

        security.allowFormAuthenticationForClients();
    }

第四步:扩展JWT中的存储内容
MyTokenEnhancer

/**
 * @vlog: 高于生活,源于生活
 * @desc: 类的描述:jwt自定义增强器(根据自己的业务需求添加非敏感字段)
 * @author: yuyang
 * @version: 1.0
 */
public class MyTokenEnhancer implements TokenEnhancer {
    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {

        MemberDetails memberDetails = (MemberDetails) authentication.getPrincipal();

        final Map<String, Object> additionalInfo = new HashMap<>();

        final Map<String, Object> retMap = new HashMap<>();

        //这里暴露memberId到Jwt的令牌中,后期可以根据自己的业务需要 进行添加字段
        additionalInfo.put("memberId",memberDetails.getUmsMember().getId());
        additionalInfo.put("nickName",memberDetails.getUmsMember().getNickname());
        additionalInfo.put("integration",memberDetails.getUmsMember().getIntegration());

        retMap.put("additionalInfo",additionalInfo);

        ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(retMap);
        
        return accessToken;
    }
}

配置SpringSecurity

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyUserDetailService myUserDetailService;

    /**
     * 方法实现说明:用于构建用户认证组件,需要传递userDetailsService和密码加密器
     * @author:smlz
     * @param auth
     * @return:
     * @exception:
     * @date:2019/12/25 14:31
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(myUserDetailService).passwordEncoder(passwordEncoder());
    }


    /**
     * 设置前台静态资源不拦截
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/assets/**", "/css/**", "/images/**");
    }



    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

// 密码模式需要这个bean
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
            // oauth2 密码模式需要拿到这个bean
        return super.authenticationManagerBean();
    }

}

UserDetailService

@Slf4j
@Component
public class MyUserDetailService implements UserDetailsService {

    /**
     * 方法实现说明:用户登陆
     * @author:smlz
     * @param userName 用户名密码
     * @return: UserDetails
     * @exception:
     * @date:2020/1/21 21:30
     */

    @Autowired
    private MemberMapper memberMapper;

    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {

        if(StringUtils.isEmpty(userName)) {
            log.warn("用户登陆用户名为空:{}",userName);
            throw new UsernameNotFoundException("用户名不能为空");
        }

        Member member = getByUsername(userName);

        if(null == member ) {
            log.warn("根据用户名没有查询到对应的用户信息:{}",userName);
        }

        log.info("根据用户名:{}获取用户登陆信息:{}",userName,member );

        MemberDetails memberDetails = new MemberDetails(member );

        return memberDetails;
    }

    /**
     * 方法实现说明:根据用户名获取用户信息
     * @author:smlz
     * @param username:用户名
     * @return: UmsMember 会员对象
     * @exception:
     * @date:2020/1/21 21:34
     */
    public Member getByUsername(String username) {
        MemberExample example = new MemberExample();
        example.createCriteria().andUsernameEqualTo(username);
        List<Member> memberList = memberMapper.selectByExample(example);
        if (!CollectionUtils.isEmpty(memberList)) {
            return memberList.get(0);
        }
        return null;
    }
}

UserDetial类

public class MemberDetails implements UserDetails {
    private UmsMember umsMember;

    public MemberDetails(UmsMember umsMember) {
        this.umsMember = umsMember;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        //返回当前用户的权限
        return Arrays.asList(new SimpleGrantedAuthority("TEST"));
    }

    @Override
    public String getPassword() {
        return umsMember.getPassword();
    }

    @Override
    public String getUsername() {
        return umsMember.getUsername();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return umsMember.getStatus()==1;
    }

    public UmsMember getUmsMember() {
        return umsMember;
    }
}

接入网关gateway

认证过滤器AuthenticationFilter#filter中需要实现的逻辑

//1.过滤不需要认证的url,比如/oauth/**
//2. 获取token
//从请求头中解析 Authorization value: bearer xxxxxxx
//或者从请求参数中解析 access_token
//3. 校验token
// 拿到token后,通过公钥(需要从授权服务获取公钥)校验
// 校验失败或超时抛出异常
//4. 校验通过后,从token中获取的用户登录信息存储到请求头中

JWT依赖

<!‐‐添加jwt相关的包‐‐>
<dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt‐api</artifactId>
  <version>0.10.5</version>
</dependency>
<dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt‐impl</artifactId>
  <version>0.10.5</version>
  <scope>runtime</scope>
</dependency>
<dependency>
   <groupId>io.jsonwebtoken</groupId>
   <artifactId>jjwt‐jackson</artifactId>
   <version>0.10.5</version>
   <scope>runtime</scope>
</dependency>

编写GateWay的全局过滤器进行权限的校验拦截

@Component
@Slf4j
@EnableConfigurationProperties(value = NotAuthUrlProperties.class)
public class AuthorizationFilter implements GlobalFilter,Ordered,InitializingBean {
    @Autowired
    private RestTemplate restTemplate;
    /**
     * 请求各个微服务 不需要用户认证的URL
     */
    @Autowired
    private NotAuthUrlProperties notAuthUrlProperties;
	/**
     * jwt的公钥,需要网关启动,远程调用认证中心去获取公钥
     */
    private PublicKey publicKey;
	
	@Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        String currentUrl = exchange.getRequest().getURI().getPath();

        //1:不需要认证的url
        if(shouldSkip(currentUrl)) {
            //log.info("跳过认证的URL:{}",currentUrl);
            return chain.filter(exchange);
        }

        //log.info("需要认证的URL:{}",currentUrl);

        //第一步:解析出我们Authorization的请求头  value为: “bearer XXXXXXXXXXXXXX”
        String authHeader = exchange.getRequest().getHeaders().getFirst("Authorization");

        //第二步:解析请求,获取token,判断Authorization的请求头是否为空
        if(StringUtils.isEmpty(authHeader)) {
            log.warn("需要认证的url,请求头为空");
            throw new GateWayException(ResultCode.AUTHORIZATION_HEADER_IS_EMPTY);
        }

        //第三步 校验我们的jwt 若jwt不对或者超时都会抛出异常
        Claims claims = JwtUtils.validateJwtToken(authHeader,publicKey);

        //第四步 把从jwt中解析出来的 用户登陆信息存储到请求头中
        ServerWebExchange webExchange = wrapHeader(exchange,claims);

        return chain.filter(webExchange);

    }

	/**
     * 方法实现说明:把我们从jwt解析出来的用户信息存储到请求中
     * @author:smlz
     * @param serverWebExchange
     * @param claims
     * @return: ServerWebExchange
     * @exception:
     * @date:2020/1/22 12:12
     */
    private ServerWebExchange wrapHeader(ServerWebExchange serverWebExchange,Claims claims) {

        String loginUserInfo = JSON.toJSONString(claims);

        //log.info("jwt的用户信息:{}",loginUserInfo);

        String memberId = claims.get("additionalInfo",Map.class).get("memberId").toString();

        String nickName = claims.get("additionalInfo",Map.class).get("nickName").toString();

        //向headers中放文件,记得build
        ServerHttpRequest request = serverWebExchange.getRequest().mutate()
                .header("username",claims.get("user_name",String.class))
                .header("memberId",memberId)
                .header("nickName",nickName)
                .build();

        //将现在的request 变成 change对象
        return serverWebExchange.mutate().request(request).build();
    }


	/**
     * 方法实现说明:不需要授权的路径
     * @author:smlz
     * @param currentUrl 当前请求路径
     * @return:
     * @exception:
     * @date:2019/12/26 13:49
     */
    private boolean shouldSkip(String currentUrl) {
        //路径匹配器(简介SpringMvc拦截器的匹配器)
        //比如/oauth/** 可以匹配/oauth/token    /oauth/check_token等
        PathMatcher pathMatcher = new AntPathMatcher();
        for(String skipPath:notAuthUrlProperties.getShouldSkipUrls()) {
            if(pathMatcher.match(skipPath,currentUrl)) {
                return true;
            }
        }
        return false;
    }
	@Override
    public int getOrder() {
        return 0;
    }

    /**
     * 方法实现说明:网关服务启动 生成公钥
     * @author:smlz
     * @return:
     * @exception:
     * @date:2020/1/22 11:58
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        //初始化公钥
        this.publicKey = JwtUtils.genPulicKey(restTemplate);
    }

}

设置非拦截路径

/**
* @vlog: 高于生活,源于生活
* @desc: 类的描述:网关跳过认证的配置类
* @author: smlz
* @createDate: 2020/1/22 10:56
* @version: 1.0
*/
@Data
@ConfigurationProperties("my.gateway")
public class NotAuthUrlProperties {

    private LinkedHashSet<String> shouldSkipUrls;
}

//application.yml
my:
  gateway:
    shouldSkipUrls:
       - /oauth/**
       - /sso/**
       - /home/**
       - /portal/commentlist/**
       - /order/paySuccess/**
       - /pms/**
       - /static/qrcode/**

校验token逻辑

@Slf4j
public class JwtUtils {

    /**
     * 认证服务器许可我们的网关的clientId(需要在oauth_client_details表中配置)
     */
    private static final String CLIENT_ID = "api-gateway";

    /**
     * 认证服务器许可我们的网关的client_secret(需要在oauth_client_details表中配置)
     */
    private static final String CLIENT_SECRET = "mall";

    /**
     * 认证服务器暴露的获取token_key的地址
     */
    private static final String AUTH_TOKEN_KEY_URL = "http://mall-authcenter/oauth/token_key";

    /**
     * 请求头中的 token的开始
     */
    private static final String AUTH_HEADER = "bearer ";

    /**
     * 方法实现说明: 通过远程调用获取认证服务器颁发jwt的解析的key
     * @author:smlz
     * @param restTemplate 远程调用的操作类
     * @return: tokenKey 解析jwt的tokenKey
     * @exception:
     * @date:2020/1/22 11:31
     */
    private static String getTokenKeyByRemoteCall(RestTemplate restTemplate) throws GateWayException {

        //第一步:封装请求头
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        headers.setBasicAuth(CLIENT_ID,CLIENT_SECRET);
        HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(null, headers);

        //第二步:远程调用获取token_key
        try {

            ResponseEntity<Map> response = restTemplate.exchange(AUTH_TOKEN_KEY_URL, HttpMethod.GET, entity, Map.class);

            String tokenKey = response.getBody().get("value").toString();

            log.info("去认证服务器获取Token_Key:{}",tokenKey);

            return tokenKey;

        }catch (Exception e) {

            log.error("远程调用认证服务器获取Token_Key失败:{}",e.getMessage());

            throw new GateWayException(ResultCode.GET_TOKEN_KEY_ERROR);
        }
    }

    /**
     * 方法实现说明:生成公钥
     * @author:smlz
     * @param restTemplate:远程调用操作类
     * @return: PublicKey 公钥对象
     * @exception:
     * @date:2020/1/22 11:52
     */
    public static PublicKey genPulicKey(RestTemplate restTemplate) throws GateWayException {

        String tokenKey = getTokenKeyByRemoteCall(restTemplate);

        try{

            //把获取的公钥开头和结尾替换掉
            String dealTokenKey =tokenKey.replaceAll("\\-*BEGIN PUBLIC KEY\\-*", "").replaceAll("\\-*END PUBLIC KEY\\-*", "").trim();

            java.security.Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());

            X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(dealTokenKey));

            KeyFactory keyFactory = KeyFactory.getInstance("RSA");

            PublicKey publicKey = keyFactory.generatePublic(pubKeySpec);

            log.info("生成公钥:{}",publicKey);

            return publicKey;

        }catch (Exception e) {

            log.info("生成公钥异常:{}",e.getMessage());

            throw new GateWayException(ResultCode.GEN_PUBLIC_KEY_ERROR);
        }
    }

    public static Claims validateJwtToken(String authHeader,PublicKey publicKey) {
        String token =null ;
        try{
            token = StringUtils.substringAfter(authHeader, AUTH_HEADER);

            Jwt<JwsHeader, Claims> parseClaimsJwt = Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token);

            Claims claims = parseClaimsJwt.getBody();

            //log.info("claims:{}",claims);

            return claims;

        }catch(Exception e){

            log.error("校验token异常:{},异常信息:{}",token,e.getMessage());

            throw new GateWayException(ResultCode.JWT_TOKEN_EXPIRE);
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/90114.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python数据分析 | 各种图表对比总结

本期将带领大家一起对在数据可视化的过程中常用的一些图表进行下总结&#xff1a; 条形图 【适用场景】 适用场合是二维数据集&#xff08;每个数据点包括两个值x和y&#xff09;&#xff0c;但只有一个维度需要比较&#xff0c;用于显示一段时间内的数据变化或显示各项之间的…

深度学习:Sigmoid函数与Sigmoid层区别

深度学习&#xff1a;Sigmoid函数与Sigmoid层 1. Sigmoid神经网络层 vs. Sigmoid激活函数 在深度学习和神经网络中&#xff0c;“Sigmoid” 是一个常见的术语&#xff0c;通常用来表示两个相关但不同的概念&#xff1a;Sigmoid激活函数和Sigmoid神经网络层。这两者在神经网络…

Java 项目日志实例:综合应用

点击下方关注我&#xff0c;然后右上角点击...“设为星标”&#xff0c;就能第一时间收到更新推送啦~~~ 本文介绍 JCL(java common logging) 和 SLF4J 分别与 Log4j 结合使用的示例。 1 JCL Log4j 使用示例 1、JCL(java common logging) Log4j 介绍 使用 commons-logging 的 …

设计模式之工厂方法模式

目录 工厂方法模式 简介 优缺点 结构 使用场景 实现 1.抽象产品 2.具体产品 3.抽象工厂 4.具体工厂 5.调用 总结 抽象工厂模式 简介 结构 实现 区别 工厂方法模式 简介 提供一个用于创建对象的接口(工厂接口)&#xff0c;让其实现类(工厂实现类)决定实例化哪…

Mysql安装使用

Mysql下载: MySQL :: Download MySQL Community Server Mysql解压&#xff1a; 解压后在根目录新建data文件夹和新建my.ini文件 my.ini文件内容如下: 注意&#xff1a;记得修改目录位置 [mysqld] # 设置3306端口 port3306 # 设置mysql的安装目录 basedirD:\\mysql-5.7.30…

极狐GitLab 价值流管理之「总时间图」使用指南

本文来源&#xff1a;about.gitlab.com 作者&#xff1a;Haim Snir 译者&#xff1a;极狐(GitLab) 市场部内容团队 对于软件研发管理者来说&#xff0c;了解在整个研发过程中时间都耗费在了哪些地方&#xff0c;是进行交付价值优化的关键洞察。GitLab / 极狐GitLab 新的价值流分…

Python 数据分析——matplotlib 快速绘图

matplotlib采用面向对象的技术来实现&#xff0c;因此组成图表的各个元素都是对象&#xff0c;在编写较大的应用程序时通过面向对象的方式使用matplotlib将更加有效。但是使用这种面向对象的调用接口进行绘图比较烦琐&#xff0c;因此matplotlib还提供了快速绘图的pyplot模块。…

魏副业而战:闲鱼卖货做什么类目好?

我是魏哥&#xff0c;与其躺平&#xff0c;不如魏副业而战&#xff01; 做闲鱼&#xff0c;有人做高客单价的&#xff0c;也有人做低客单价的。 之前魏哥有做宠物产品&#xff0c;利润低&#xff0c;每单赚几元&#xff0c;做的很累&#xff0c;但做过一段时间后发现有很多复…

【分布式技术专题】「分布式ID系列」百度开源的分布式高性能的唯一ID生成器UidGenerator

UidGenerator是什么 UidGenerator是百度开源的一款分布式高性能的唯一ID生成器&#xff0c;更详细的情况可以查看官网集成文档 uid-generator是基于Twitter开源的snowflake算法实现的一款唯一主键生成器(数据库表的主键要求全局唯一是相当重要的)。要求java8及以上版本。 snow…

切换Debian的crontab的nano编辑器

Debian的crontab默认的编辑器是nano&#xff0c;用起来很不习惯,怎么才能转回vim呢? 用以下命令便可&#xff1a; #update-alternatives --config editor 出现以下所示的界面&#xff1a; 而后选择8使用/usr/bin/vim就能够了。 PS&#xff1a;若是你发现你的定时没有生效&…

【Linux网络】TCP UDP socket HTTP webSocket之间的区别

目录 一、OSI & TCP/IP模型 二、几者之间的关系 三、HTTP 四、Socket 五、WebSocket 5.1、WebSocket 优点 一、OSI & TCP/IP模型 首先我们要了解OSI七层模型&#xff0c;和预支对应的TCP/IP 四层的模型。 用下面的图可以看出&#xff0c;TCP UDP 工作在传输层&…

MyBatis与Spring的集成整合加优化分页功能

目录 一.为什么要将MyBatis和Spring整合&#xff1f;&#xff1f;&#xff1f; 二.配置环境 2.1 pom文件 2.2 xml文件 三.演示举例 四.Aop整合pageHelper 分页插件 今天的分享就到这啦&#xff01;&#xff01;&#xff01; 一.为什么要将MyBatis和Spring整合&#xff1f…

基于JSP+Servlet+Mysql停车场管理系统(含实训报告)

TOC 一、系统介绍 项目类型&#xff1a;Java web项目 项目名称&#xff1a;基于JSPServlet的停车场管理系统 项目架构&#xff1a;B/S架构 开发语言&#xff1a;Java语言 前端技术&#xff1a;HTML、CSS、JS、JQuery等技术 后端技术&#xff1a;JSP、Servlet、JDBC等技术…

Linux驱动开发(Day5)

思维导图&#xff1a; 不同设备号文件绑定&#xff1a;

codeforce 894

A. Gift Carpet &#xff08;模拟&#xff09; 题意&#xff1a; 给出n*m的矩阵&#xff0c;从左到右每列最多取一个字母&#xff0c;问能否取出"vika" 思路&#xff1a; 直接模拟。 const int N1e610; char g[25][25]; void solve(){int n,m; cin>>n>>…

2023谷歌开发者大会直播大纲「初稿」

听人劝、吃饱饭,奉劝各位小伙伴,不要订阅该文所属专栏。 作者:不渴望力量的哈士奇(哈哥),十余年工作经验, 跨域学习者,从事过全栈研发、产品经理等工作,现任研发部门 CTO 。荣誉:2022年度博客之星Top4、博客专家认证、全栈领域优质创作者、新星计划导师,“星荐官共赢计…

浅谈泛在电力物联网在电力设备状态在线监测中的应用

安科瑞 华楠 摘要&#xff1a;随着信息化水平的不断发展&#xff0c;泛在电力物联网的建设提上日程&#xff0c;这对提升变电站电力设备在线监测水平&#xff0c;推动智能电网发展具有重要的指导意义。对基于物联网的电力设备状态监测系统进行了研究&#xff0c;概括了泛在电力…

Python案例|Matplotlib库实现的数据分析

数据展示是数据分析和挖掘中的重要环节&#xff0c;通过图形的形式可以直观、清晰地呈现数据内在的规律。 本文所用数据采用上一篇案例实现后的数据表&#xff0c;数据存储在newbj_lianJia.csv文件中&#xff0c;具体代码如下。 import pandas as pd #导入库 import matplot…

电脑显示“Operating System not found”该怎么办?

“Operating System not found”是一种常见的电脑错误提示&#xff0c;这类错误会导致你无法成功启动Windows。那么电脑显示“Operating System not found”该怎么办呢&#xff1f; 方法1. 检查硬盘 首先&#xff0c;您可以测试硬盘是否存在问题。为此&#xff0c;您可以采取以…

【学习FreeRTOS】第19章——FreeRTOS低功耗模式Tickless

1.低功耗模式简介 很多应用场合对于功耗的要求很严格&#xff0c;比如可穿戴低功耗产品、物联网低功耗产品等一般MCU都有相应的低功耗模式&#xff0c;裸机开发时可以使用MCU的低功耗模式。FreeRTOS也提供了一个叫Tickless的低功耗模式&#xff0c;方便带FreeRTOS操作系统的应…