电商项目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.jkskeyPairAlias: jwtkeyPairSecret: 123123keyPairStoreSecret: 123123

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

@Configuration
@EnableAuthorizationServer
@EnableConfigurationProperties(value = JwtCAProperties.class)
public class MyAuthServerConfig extends AuthorizationServerConfigurerAdapter {@Autowiredprivate AuthenticationManager authenticationManager;@Autowiredprivate DataSource dataSource;@Autowiredprivate MyUserDetailService myUserDetailService;//修改JwtTokenStoreConfig的配置,支持非对称加密@Autowiredprivate JwtCAProperties jwtCAProperties;/*** 方法实现说明:我们颁发的token通过jwt存储* @author:smlz* @return:* @exception:* @date:2020/1/21 21:49*/@Beanpublic TokenStore tokenStore(){return new JwtTokenStore(jwtAccessTokenConverter());}@Beanpublic JwtAccessTokenConverter jwtAccessTokenConverter() {JwtAccessTokenConverter converter = new JwtAccessTokenConverter();//jwt的密钥converter.setKeyPair(keyPair());return converter;}@Beanpublic KeyPair keyPair() {KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource(jwtCAProperties.getKeyPairName()), jwtCAProperties.getKeyPairSecret().toCharArray());return keyStoreKeyFactory.getKeyPair(jwtCAProperties.getKeyPairAlias(), jwtCAProperties.getKeyPairStoreSecret().toCharArray());}//扩展JWT中的存储内容@Beanpublic MyTokenEnhancer myTokenEnhancer() {return new MyTokenEnhancer();}/*** 方法实现说明:认证服务器能够给哪些 客户端颁发token  我们需要把客户端的配置 存储到* 数据库中 可以基于内存存储和db存储* @author:smlz* @return:* @exception:* @date:2020/1/15 20:18*/@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {clients.withClientDetails(clientDetails());}/*** 方法实现说明:用于查找我们第三方客户端的组件 主要用于查找 数据库表 oauth_client_details* @author:smlz* @return:* @exception:* @date:2020/1/15 20:19*/@Beanpublic ClientDetailsService clientDetails() {return new JdbcClientDetailsService(dataSource);}/*** 方法实现说明:授权服务器的配置的配置* @author:smlz* @return:* @exception:* @date:2020/1/15 20:21*/@Overridepublic 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*/@Overridepublic void configure(AuthorizationServerSecurityConfigurer security) throws Exception {//第三方客户端校验token需要带入 clientId 和clientSecret来校验security .checkTokenAccess("isAuthenticated()").tokenKeyAccess("isAuthenticated()");//来获取我们的tokenKey需要带入clientId,clientSecretsecurity.allowFormAuthenticationForClients();}

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

/*** @vlog: 高于生活,源于生活* @desc: 类的描述:jwt自定义增强器(根据自己的业务需求添加非敏感字段)* @author: yuyang* @version: 1.0*/
public class MyTokenEnhancer implements TokenEnhancer {@Overridepublic 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 {@Autowiredprivate MyUserDetailService myUserDetailService;/*** 方法实现说明:用于构建用户认证组件,需要传递userDetailsService和密码加密器* @author:smlz* @param auth* @return:* @exception:* @date:2019/12/25 14:31*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(myUserDetailService).passwordEncoder(passwordEncoder());}/*** 设置前台静态资源不拦截* @param web* @throws Exception*/@Overridepublic void configure(WebSecurity web) throws Exception {web.ignoring().antMatchers("/assets/**", "/css/**", "/images/**");}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}// 密码模式需要这个bean@Beanpublic AuthenticationManager authenticationManagerBean() throws Exception {// oauth2 密码模式需要拿到这个beanreturn super.authenticationManagerBean();}}

UserDetailService

@Slf4j
@Component
public class MyUserDetailService implements UserDetailsService {/*** 方法实现说明:用户登陆* @author:smlz* @param userName 用户名密码* @return: UserDetails* @exception:* @date:2020/1/21 21:30*/@Autowiredprivate MemberMapper memberMapper;@Overridepublic 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;}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {//返回当前用户的权限return Arrays.asList(new SimpleGrantedAuthority("TEST"));}@Overridepublic String getPassword() {return umsMember.getPassword();}@Overridepublic String getUsername() {return umsMember.getUsername();}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic 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 {@Autowiredprivate RestTemplate restTemplate;/*** 请求各个微服务 不需要用户认证的URL*/@Autowiredprivate NotAuthUrlProperties notAuthUrlProperties;/*** jwt的公钥,需要网关启动,远程调用认证中心去获取公钥*/private PublicKey publicKey;@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {String currentUrl = exchange.getRequest().getURI().getPath();//1:不需要认证的urlif(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中放文件,记得buildServerHttpRequest 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;}@Overridepublic int getOrder() {return 0;}/*** 方法实现说明:网关服务启动 生成公钥* @author:smlz* @return:* @exception:* @date:2020/1/22 11:58*/@Overridepublic 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_keytry {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);}}
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

认识Mybatis的关联关系映射,灵活关联表对象之间的关系

目录 一、概述 ( 1 ) 介绍 ( 2 ) 关联关系映射 ( 3 ) 关联讲述 二、一对一关联映射 2.1 数据库创建 2.2 配置文件 2.3 代码生成 2.4 编写测试 三、一对多关联映射 四 、多对多关联映射 给我们带来的收获 一、概述 ( 1 ) 介绍 关联关系映射是指在数据库中&…

RH1288V3 - 初识物理服务器

如果你拥有一台物理服务器(不是云服务器) 个人比较推荐你用物理服务器&#xff0c;虽然性能会比云要来的差&#xff0c;但是不用每月交钱上。云服务固然方便&#xff0c;但是几个核的性能和一点存储&#xff0c;想做一个动漫网站固然要很多mp4这种影视资源&#xff0c;云服务器…

人工智能在现代招聘中的崛起:超越传统筛选的未来

引言 在过去的几十年里,招聘一直是企业的核心活动之一。传统的招聘流程依赖于人力资源专家手工筛选简历、面试候选人并进行背景调查。这种方法不仅耗时,而且可能受到人为偏见的影响。随着技术的进步,特别是人工智能(AI)的发展,招聘的面貌正在发生深刻的变化。人工智能在…

【Ubuntu20.04安装Nvidia驱动、CUDA和CUDNN】

Ubuntu20.04安装Nvidia驱动、CUDA和CUDNN 1 Nvidia驱动安装1.1 安装1.2 安装Nvidia可能会遇到的问题1.2.1 NVIDIA 驱动与 Nouveau 驱动不兼容1.2.2 ERROR: Unable to find the development tool cc 2 CUDA安装2.1 下载和安装2.2 配置CUDA环境 3 安装CUDNN4 切换CUDA版本 1 Nvid…

在 macOS 中安装 TensorFlow 1g

tensorflow 需要多大空间 pip install tensorflow pip install tensorflow Looking in indexes: https://pypi.douban.com/simple/ Collecting tensorflowDownloading https://pypi.doubanio.com/packages/1a/c1/9c14df0625836af8ba6628585c6d3c3bf8f1e1101cafa2435eb28a7764…

在其他python环境中使用jupyter notebook

1、切换到目标python环境 activate 目标python环境 2、安装notebook内核包 pip install ipykernel 3、加环境加入到notebook中 python -m ipykernel install 目标python环境 4、切换到base环境 activate base 5、打开目标项目的对应盘 如果&#xff0c;项目在c盘&…

DEFORMABLE DETR: DEFORMABLE TRANSFORMERS FOR END-TO-END OBJECT DETECTION 论文精度笔记

DEFORMABLE DETR DEFORMABLE DETR: DEFORMABLE TRANSFORMERS FOR END-TO-END OBJECT DETECTION 参考&#xff1a;AI-杂货铺-Transformer跨界CV又一佳作&#xff01;Deformable DETR&#xff1a;超强的小目标检测算法&#xff01; 摘要 摘要部分&#xff0c;作者主要说明了如…

Datawhale AI夏令营 - 用户新增预测挑战赛 | 学习笔记

任务1&#xff1a;跑通Baseline # 1. 导入需要用到的相关库 # 导入 pandas 库&#xff0c;用于数据处理和分析 import pandas as pd # 导入 numpy 库&#xff0c;用于科学计算和多维数组操作 import numpy as np # 从 sklearn.tree 模块中导入 DecisionTreeClassifier 类 # De…

开源容灾备份软件,开源cdp备份软件

数据的安全性和完整性面临着硬件问题、黑客攻击、人为错误等各种威胁。在这种环境下&#xff0c;开源容灾备份软件应运而生&#xff0c;通过提供自动数据备份和恢复&#xff0c;有效地保证了公司的数据安全。 一、开源容灾备份软件的定义和作用 开源容灾备份软件是一种基于开源…

全流程R语言Meta分析核心技术教程

详情点击链接&#xff1a;全流程R语言Meta分析核心技术教程 一&#xff0c;Meta分析的选题与检索 1、Meta分析的选题与文献检索 1)什么是Meta分析&#xff1f; 2)Meta分析的选题策略 3)精确检索策略&#xff0c;如何检索全、检索准 4)文献的管理与清洗&#xff0c;如何制定文…

Django基础5——ORM中间程序

文章目录 一、基本了解二、ORM基本操作2.1 连接数据库2.1.1 使用sqlite数据库2.1.2 使用MySQL数据库 2.2 对数据库操作2.2.1 增&#xff08;前端数据——>数据库&#xff09;2.2.2 查&#xff08;数据库——>前端展示&#xff09;2.2.3 改&#xff08;修改数据&#xff0…

C# Winfrom通过COM接口访问和控制Excel应用程序,将Excel数据导入DataGridView

1.首先要创建xlsx文件 2.在Com中添加引用 3. 添加命名空间 using ApExcel Microsoft.Office.Interop.Excel; --这样起个名字方面后面写 4.样例 //点击操作excelDataTable dt new DataTable();string fileName "D:\desktop\tmp\test.xlsx";ApExcel.Application exA…

软件测试知识点总结(一)

文章目录 前言一. 什么是软件测试二. 软件测试和软件调试的区别三. 软件测试和研发的区别四. 优秀的测试人员所应该具备的素质总结 前言 在现实生活中的很多场景下&#xff0c;我们都会进行测试。 比如买件衣服&#xff0c;我们需要看衣服是不是穿着好看&#xff0c;衣服材质如…

pyton\yolov8安装和基础使用,训练和预测

首先到官网下载yolov8&#xff0c;官方的地址&#xff0c;下载好压缩包后&#xff0c;解压到pycharm打开&#xff0c;我个人使用的是pycharm&#xff0c;接下来也是在pycharm里操作的。&#xff08;专业版pycharm&#xff09; yolov8的官方文档有说明&#xff0c;必须要有的环境…

“深度学习”学习日记:Tensorflow实现VGG每一个卷积层的可视化

2023.8.19 深度学习的卷积对于初学者是非常抽象&#xff0c;当时在入门学习的时候直接劝退一大班人&#xff0c;还好我坚持了下来。可视化时用到的图片&#xff08;我们学校的一角&#xff01;&#xff01;&#xff01;&#xff09;以下展示了一个卷积和一次Relu的变化 作者使…

【C51 GPIO的原理和内部结构】

51单片机项目基础篇 中篇&#xff1a;介绍GPIO1、认识GPIO2、GPIO 结构框图与工作原理2.1、P0端口结构框图与工作原理2.1.1、剖析组成 P0 口的每个单元的作用2.1.2、 P0 口做为 I/O 口及地址/数据总线使用时的具体工作过程 2.2、P1 端口结构框图与工作原理2.3、P2 端口结构框图…

Lamda表达式

为什么要使用lamda表达式 避免匿名内部类定义过多可以让你的代码看起来很简洁去掉了一堆没有意义的代码&#xff0c;只留下核心的逻辑 函数式接口 理解Functionalnterface (函数式接口)是学习Java8 lambda表达式的关键所在. 定义: 任何接口&#xff0c;如果只包含唯一一个…

多维时序 | MATLAB实现SABO-CNN-GRU-Attention多变量时间序列预测

多维时序 | MATLAB实现SABO-CNN-GRU-Attention多变量时间序列预测 目录 多维时序 | MATLAB实现SABO-CNN-GRU-Attention多变量时间序列预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 多维时序 | MATLAB实现SABO-CNN-GRU-Attention多变量时间序列预测。 模型描…

Java小白基础自学阶段(持续更新...)

引言 Java作为一门广泛应用于企业级开发的编程语言&#xff0c;对初学者来说可能会感到有些复杂。然而&#xff0c;通过适当的学习方法和资源&#xff0c;即使是小白也可以轻松掌握Java的基础知识。本文将提供一些有用的建议和资源&#xff0c;帮助小白自学Java基础。 学习步骤…

cpolar做一个内网穿透

因为不在公司&#xff0c;需要访问公司的数据库&#xff0c;所以做一个内网穿透 下载安装 下载地址&#xff1a; https://dashboard.cpolar.com/get-started 下载后是个压缩包&#xff0c;解压后傻瓜式安装 操作隧道 安装后打开Cpolar Web UI 登录账号&#xff0c;查看隧…