微服务OAuth 2.1认证授权可行性方案(Spring Security 6)
文章目录
- 一、背景
- 二、微服务架构介绍
- 三、认证服务器
- 1. 数据库创建
- 2. 新建模块
- 3. 导入依赖和配置
- 4. 安全认证配置类
- 四、认证服务器测试
- 1. AUTHORIZATION_CODE(授权码模式)
- 1. 获取授权码
- 2. 获取JWT
- 2. CLIENT_CREDENTIALS(客户端凭证模式)
- 五、Gateway
- 1. 引入依赖
- 2. 添加白名单文件
- 3. 全局过滤器
- 4. 获取远程JWKS
- 5. 校验JWT
- 6. 测试(如何携带JWT)
- 六、后记
一、背景
Oauth2停止维护,基于OAuth 2.1 和 OpenID Connect 1.0的Spring Authorization Server模块独立于SpringCloud。
本文开发环境如下:
| Version | |
|---|---|
| Java | 17 |
| SpringCloud | 2023.0.0 |
| SpringBoot | 3.2.1 |
| Spring Authorization Server | 1.2.1 |
| Spring Security | 6.2.1 |
| mysql | 8.2.0 |
https://spring.io/projects/spring-security#learn
https://spring.io/projects/spring-authorization-server#learn
二、微服务架构介绍
一个认证服务器(也是一个微服务),专门用于颁发JWT。
一个网关(也是一个微服务),用于白名单判断和JWT校验。
若干微服务。
本文的关键在于以下几点:
- 搭建认证服务器
- 网关白名单判断
- 网关验证JWT
- 认证服务器如何共享公钥,让其余微服务有JWT自校验的能力。

三、认证服务器
这里是官方文档https://spring.io/projects/spring-authorization-server#learn
基本上跟着Getting Started写完就可以。
1. 数据库创建
新建一个数据库xc_users。
然后执行jar里自带的三个sql。

这一步官方并没有给出,大概因为可以使用内存存储,在简单demo省去了持久化。不建立数据库可能也是可行的,我没试过。
2. 新建模块
新建一个auth模块,作为认证服务器。
3. 导入依赖和配置
<dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-oauth2-authorization-server</artifactId></dependency>
server:servlet:context-path: /authport: 63070
spring:application:name: auth-serviceprofiles:active: devdatasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://192.168.101.65:3306/xc_users?serverTimezone=UTC&userUnicode=true&useSSL=false&username: rootpassword: 1009
4. 安全认证配置类
@Configuration
@EnableWebSecurity
public class AuthServerSecurityConfig {}
里面包含诸多内容,有来自Spring Security的,也有来自的Spring Authorization Server的。
UserDetailsService的实例,用于检索用户进行身份验证。
@Beanpublic UserDetailsService userDetailsService() {UserDetails userDetails = User.withUsername("lisi").password("456").roles("read").build();return new InMemoryUserDetailsManager(userDetails);}
- 密码编码器(可选,本文不用)
@Beanpublic PasswordEncoder passwordEncoder() {// 密码为明文方式return NoOpPasswordEncoder.getInstance();// 或使用 BCryptPasswordEncoder
// return new BCryptPasswordEncoder();}
- 协议端点的
Spring Security过滤器链
@Bean@Order(1)public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)throws Exception {OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);http.getConfigurer(OAuth2AuthorizationServerConfigurer.class).oidc(Customizer.withDefaults()); // Enable OpenID Connect 1.0http// Redirect to the login page when not authenticated from the// authorization endpoint.exceptionHandling((exceptions) -> exceptions.defaultAuthenticationEntryPointFor(new LoginUrlAuthenticationEntryPoint("/login"),new MediaTypeRequestMatcher(MediaType.TEXT_HTML)))// Accept access tokens for User Info and/or Client Registration.oauth2ResourceServer((resourceServer) -> resourceServer.jwt(Customizer.withDefaults()));return http.build();}
- 用于身份验证的
Spring Security过滤器链。
至于哪些要校验身份,哪些不用,根据自己需求写。
@Bean@Order(2)public SecurityFilterChain defaultFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests((authorize) ->authorize.requestMatchers(new AntPathRequestMatcher("/actuator/**")).permitAll().requestMatchers(new AntPathRequestMatcher("/login")).permitAll().requestMatchers(new AntPathRequestMatcher("/oauth2/**")).permitAll().requestMatchers(new AntPathRequestMatcher("/**/*.html")).permitAll().requestMatchers(new AntPathRequestMatcher("/**/*.json")).permitAll().requestMatchers(new AntPathRequestMatcher("/auth/**")).permitAll().anyRequest().authenticated()).formLogin(Customizer.withDefaults()).oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter())));return http.build();}
- 自定义验证转化器(可选)
private JwtAuthenticationConverter jwtAuthenticationConverter() {JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter();// 此处可以添加自定义逻辑来提取JWT中的权限等信息// jwtConverter.setJwtGrantedAuthoritiesConverter(...);return jwtConverter;}
- 用于管理客户端的
RegisteredClientRepository实例
@Beanpublic RegisteredClientRepository registeredClientRepository() {RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString()).clientId("XcWebApp")
// .clientSecret("{noop}XcWebApp").clientSecret("XcWebApp").clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC).authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE).authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN).authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS).redirectUri("http://www.51xuecheng.cn")
// .postLogoutRedirectUri("http://localhost:63070/login?logout").scope("all").scope(OidcScopes.OPENID).scope(OidcScopes.PROFILE).scope("message.read").scope("message.write").scope("read").scope("write").clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()).tokenSettings(TokenSettings.builder().accessTokenTimeToLive(Duration.ofHours(2)) // 设置访问令牌的有效期.refreshTokenTimeToLive(Duration.ofDays(3)) // 设置刷新令牌的有效期.reuseRefreshTokens(true) // 是否重用刷新令牌.build()).build();return new InMemoryRegisteredClientRepository(registeredClient);}
- 用于对访问令牌进行签名的实例
@Beanpublic JWKSource<SecurityContext> jwkSource() {KeyPair keyPair = generateRsaKey();RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();RSAKey rsaKey = new RSAKey.Builder(publicKey).privateKey(privateKey).keyID(UUID.randomUUID().toString()).build();JWKSet jwkSet = new JWKSet(rsaKey);return new ImmutableJWKSet<>(jwkSet);}private static KeyPair generateRsaKey() {KeyPair keyPair;try {KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");keyPairGenerator.initialize(2048);keyPair = keyPairGenerator.generateKeyPair();}catch (Exception ex) {throw new IllegalStateException(ex);}return keyPair;}
- 用于解码签名访问令牌的
JwtDecoder实例
@Beanpublic JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);}
- 用于配置
Spring Authorization Server的AuthorizationServerSettings实例
@Beanpublic AuthorizationServerSettings authorizationServerSettings() {return AuthorizationServerSettings.builder().build();}
这里可以设置各种端点的路径,默认路径点开builder()即可看到,如下
public static Builder builder() {return new Builder().authorizationEndpoint("/oauth2/authorize").deviceAuthorizationEndpoint("/oauth2/device_authorization").deviceVerificationEndpoint("/oauth2/device_verification").tokenEndpoint("/oauth2/token").jwkSetEndpoint("/oauth2/jwks").tokenRevocationEndpoint("/oauth2/revoke").tokenIntrospectionEndpoint("/oauth2/introspect").oidcClientRegistrationEndpoint("/connect/register").oidcUserInfoEndpoint("/userinfo").oidcLogoutEndpoint("/connect/logout");}
这里我必须吐槽一下,qnmd /.well-known/jwks.json,浪费我一下午。获取公钥信息的端点现在已经替换成了/oauth2/jwks。
四、认证服务器测试
基本上跟着Getting Started走就行。只不过端点的变动相较于Oauth2很大,还有使用方法上不同。
在配置RegisteredClient的时候,我们设置了三种GrantType,这里只演示两种AUTHORIZATION_CODE和CLIENT_CREDENTIALS。
1. AUTHORIZATION_CODE(授权码模式)
1. 获取授权码
用浏览器打开以下网址,
http://localhost:63070/auth/oauth2/authorize?client_id=XcWebApp&response_type=code&scope=all&redirect_uri=http://www.51xuecheng.cn
对应oauth2/authorize端点,后面的参数和当时设置RegisteredClient 保持对应就行。response_type一定是code。
进入到登陆表单,输入lisi - 456登陆。

选择all,同意请求。

url被重定向到http://www.51xuecheng.cn,并携带一个code,这就是授权码。
http://www.51xuecheng.cn/?code=9AexK_KFH1m3GiNBKsc0FU2KkedM2h_6yR-aKF-wPnpQT5USKLTqoZiSkHC3GUvt-56_ky-E3Mv5LbMeH9uyd-S1UV6kfJO6znqAcCAF43Yo4ifxTAQ8opoPJTjLIRUC
2. 获取JWT
使用apifox演示,postman,idea-http都可以。
向localhost:63070/auth服务的/oauth2/token端点发送Post请求,同时需要携带认证信息。
认证信息可以如图所填的方法,也可以放到Header中,具体做法是将客户端ID和客户端密码用冒号(:)连接成一个字符串,进行Base64编码放入HTTP请求的Authorization头部中,前缀为Basic 。比如
Authorization: Basic bXlDbGllbnRJZDpteUNsaWVudFNlY3JldA==


得到JWT

2. CLIENT_CREDENTIALS(客户端凭证模式)
不需要授权码,直接向localhost:63070/auth服务的/oauth2/token端点发送Post请求,同时需要携带认证信息。



五、Gateway
至于gateway基础搭建步骤和gateway管理的若干微服务本文不做指导。
相较于auth模块(也就是Authorization Server),gateway的角色是Resource Server。

1. 引入依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-oauth2-resource-server</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency>
2. 添加白名单文件
在resource下添加security-whitelist.properties文件。
写入以下内容
/auth/**=????
/content/open/**=??????????
/media/open/**=??????????
3. 全局过滤器
在全局过滤器中,加载白名单,然后对请求进行判断。
@Component
@Slf4j
public class GatewayAuthFilter implements GlobalFilter, Ordered {//白名单private static List<String> whitelist = null;static {//加载白名单try (InputStream resourceAsStream = GatewayAuthFilter.class.getResourceAsStream("/security-whitelist.properties");) {Properties properties = new Properties();properties.load(resourceAsStream);Set<String> strings = properties.stringPropertyNames();whitelist= new ArrayList<>(strings);} catch (Exception e) {log.error("加载/security-whitelist.properties出错:{}",e.getMessage());e.printStackTrace();}}@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {String requestUrl = exchange.getRequest().getPath().value();log.info("请求={}",requestUrl);AntPathMatcher pathMatcher = new AntPathMatcher();//白名单放行for (String url : whitelist) {if (pathMatcher.match(url, requestUrl)) {return chain.filter(exchange);}}}private Mono<Void> buildReturnMono(String error, ServerWebExchange exchange) {ServerHttpResponse response = exchange.getResponse();String jsonString = JSON.toJSONString(new RestErrorResponse(error));byte[] bits = jsonString.getBytes(StandardCharsets.UTF_8);DataBuffer buffer = response.bufferFactory().wrap(bits);response.setStatusCode(HttpStatus.UNAUTHORIZED);response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");return response.writeWith(Mono.just(buffer));}@Overridepublic int getOrder() {return 0;}
}
4. 获取远程JWKS
在yml配置中添加jwk-set-uri属性。
spring:security:oauth2:resourceserver:jwt:jwk-set-uri: http://localhost:63070/auth/oauth2/jwks
新建配置类,自动注入JwtDecoder。
@Configuration
public class JwtDecoderConfig {@Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}")String jwkSetUri;@Beanpublic JwtDecoder jwtDecoderLocal() {return NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build();}
}
5. 校验JWT
在全局过滤器中补全逻辑。
@Component
@Slf4j
public class GatewayAuthFilter implements GlobalFilter, Ordered {@Lazy@Autowiredprivate JwtDecoder jwtDecoderLocal;@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {String requestUrl = exchange.getRequest().getPath().value();log.info("请求={}",requestUrl);AntPathMatcher pathMatcher = new AntPathMatcher();//白名单放行for (String url : whitelist) {if (pathMatcher.match(url, requestUrl)) {return chain.filter(exchange);}}//检查token是否存在String token = getToken(exchange);log.info("token={}",token);if (StringUtils.isBlank(token)) {return buildReturnMono("没有携带Token,没有认证",exchange);}
// return chain.filter(exchange);try {Jwt jwt = jwtDecoderLocal.decode(token);// 如果没有抛出异常,则表示JWT有效// 此时,您可以根据需要进一步检查JWT的声明log.info("token有效期至:{}", formatInstantTime(jwt.getExpiresAt()));return chain.filter(exchange);} catch (JwtValidationException e) {log.info("token验证失败:{}",e.getMessage());return buildReturnMono("认证token无效",exchange);}}/*** 从请求头Authorization中获取token*/private String getToken(ServerWebExchange exchange) {String tokenStr = exchange.getRequest().getHeaders().getFirst("Authorization");if (StringUtils.isBlank(tokenStr)) {return null;}String token = tokenStr.split(" ")[1];if (StringUtils.isBlank(token)) {return null;}return token;}/*** 格式化Instant时间** @param expiresAt 在到期* @return {@link String}*/public String formatInstantTime(Instant expiresAt) {// 将Instant转换为系统默认时区的LocalDateTimeLocalDateTime dateTime = LocalDateTime.ofInstant(expiresAt, ZoneId.systemDefault());// 定义日期时间的格式DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");// 格式化日期时间并打印return dateTime.format(formatter);}}
6. 测试(如何携带JWT)
携带一个正确的JWT向gateway发送请求。
把JWT写到Header的Authorization字段中,添加前缀Bearer(用空格隔开),向gateway微服务所在地址发送请求。

gateway日志输出。

六、后记
颁发JWT都归一个认证服务器管理,校验JWT都归Gateway管理,至于授权,则由各个微服务自己定义。耦合性低、性能较好。
关于授权,可以接着这篇文章。
微服务OAuth 2.1认证授权Demo方案(Spring Security 6)
相关文章:
微服务OAuth 2.1认证授权可行性方案(Spring Security 6)
文章目录 一、背景二、微服务架构介绍三、认证服务器1. 数据库创建2. 新建模块3. 导入依赖和配置4. 安全认证配置类 四、认证服务器测试1. AUTHORIZATION_CODE(授权码模式)1. 获取授权码2. 获取JWT 2. CLIENT_CREDENTIALS(客户端凭证模式) 五、Gateway1.…...
Maui blazor ios 按设备类型设置是否启用safeArea
需求,新做了个app, 使用的是maui blazor技术,里面用了渐变背景,在默认启用SafeArea情况下,底部背景很突兀 由于现版本maui在SafeArea有点bug,官方教程的<ContentPage SafeAreafalse不生效,于…...
C#系列-使用 Minio 做图片服务器实现图片上传 和下载(13)
1、Minio 服务器下载和安装 要在本地安装和运行 MinIO 服务器,你可以按照以下 步骤进行操作: 1. 访问 MinIO 的官方网站:https://min.io/,然后 点击页面上的”Download”按钮。 2. 在下载页面上,选择适合你操作系统的 …...
生活篇——华为手机去除负一屏
华为手机去除如下图的恶心负一屏 打开华为的应用市场app 进入:我的-设置-国家/地区(改为俄罗斯)-进入智慧助手检查更新并更新智慧助手。 然后重复开始的操作,将地区改回中国,这样就没有负一屏了。...
2024牛客寒假算法基础集训营2-c Tokitsukaze and Min-Max XOR
来源 题目 Tokitsukaze 有一个长度为 n 的序列 a1,a2,…,an和一个整数 k。 她想知道有多少种序列 b1,b2,…,bm满足: 其中 ⊕\oplus⊕ 为按位异或,具体参见 百度百科:异或 答案可能很大,请输出 mod1e97 后的结果。 输入描述…...
C语言:指针的基础详解
目录 1. 内存 2. 取地址& 3. 指针变量 4. 解引用 4.1 *解引用 4.2 []解引用 4.3 ->解引用 5. 指针变量的大小 5.1 结论 6. 指针运算 7. void* 指针 8. const修饰指针 8.1 const修饰变量 8.2 const修饰指针变量 8.3 结论 9. 野指针 9.1 为什么会出现野指…...
PHP+vue+mysql校园学生社团管理系统574cc
运行环境:phpstudy/wamp/xammp等 开发语言:php 后端框架:Thinkphp 前端框架:vue.js 服务器:apache 数据库:mysql 数据库工具:Navicat/phpmyadmin 前台功能: 首页:展示社团信息和活动…...
VS Code中主程序C文件引用了另一个.h头文件,编译时报错找不到函数
目录 一、问题描述二、问题原因三、解决方法四、扩展五、通过CMake进行配置 一、问题描述 VS Code中主程序C文件引用了另一个.h头文件,编译时报错找不到函数 主程序 main.c #include <stdio.h> #include "sumaa.h"int main(int, char**){printf(&q…...
边缘计算:重塑数字世界的未来
引言 随着物联网(IoT)设备的激增和5G网络的普及,我们正站在一个计算模式的新纪元门槛上——边缘计算。这一技术范式将数据处理和分析推向网络的边缘,即设备或终端,为实时性要求较高的应用提供了前所未有的可能性。 目…...
2024 前端面试题 附录3
这里记录的是昨天和今天原篇的知识点补充 原篇地址: 2024 前端面试题(GPT回答 示例代码 解释)No.41 - No.60 2024 前端面试题(GPT回答 示例代码 解释)No.61 - No.100 2024 前端面试题(GPT回答 示例代…...
[Vue warn]: Duplicate keys detected: ‘1‘. This may cause an update error.
[Vue warn]: Duplicate keys detected: ‘1‘. This may cause an update error.——> Vue报错,key关键字不唯一: 解决办法:修改一下重复的id值!!!...
Docker-Learn(二)保存、导入、使用Docker镜像
1.保存镜像 根据上一节内容,将创建好镜像进行保存,需要退出当前的已经在运行的docer命令行中断里面,可以通过在终端里面输入指令exit或者按下键盘上的 ctrlD建退出: 回到自己的终端里面,输入指令: docker…...
第三百一十五回
文章目录 1. 概念介绍2. 基本用法3. 补充用法4. 内容总结 我们在上一章回中介绍了"再谈ListView中的分隔线",本章回中将介绍showMenu的用法.闲话休提,让我们一起Talk Flutter吧。 1. 概念介绍 我们在第一百六十三回中介绍了showMenu相关的内容…...
区块链(一): 以太坊基础知识
目录 什么是区块链?什么是以太坊?什么是加密货币?以太坊与比特币有什么不同?以太坊能做什么?什么是智能合约?以太坊社区以太坊白皮书 什么是区块链? 区块链是一个交易数据库,在网络…...
高级FPGA开发之基础协议PCIe
基础协议之PCIe部分 一、TLP包的包头 在PCIe的系统中,tlp包的包头的结构有许多部分是相似的,通过掌握这些常规的包头,能帮助理解在PCIe总线上各个设备之间如何进行数据的收发。 通用的字段 通用字段作用Fmt决定了包头是3DW还是3DWÿ…...
Vue核心基础1:数据代理
1 回顾Object.defineProperty方法 let str hello const person {name: 张三,age: 18 } Object.defineProperty(person, sex, {// value: 男,// enumerable: true, // 控制属性是否可以枚举,默认值是false// writable: true, // 控制属性是否可以被修改࿰…...
12 ABC串口接收原理与思路
1. 串口接收原理 基本原理:通过数据起始位判断要是否要开始接收的数据,通过采样的方式确定每一位数据是0还是1。 如何判断数据起始位到来:通过边沿检测电路检测起始信号的下降沿 如何采样:一位数据采多次,统计得到高…...
leetcode(二分查找)34.在排序数组中查找元素的第一个和最后一个位置(C++详细解释)DAY11
文章目录 1.题目示例提示 2.解答思路3.实现代码结果 4.总结 1.题目 给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。 如果数组中不存在目标值 target,返回 [-1, -1]。 你必须设计…...
算法刷题框架
前言:最近积累了一些算法题量,正在刷东神的算法笔记,监督自己记录下读后启发,顺便帮助道友们阅读 数据结构 这一部分老生常谈,数据的存储方式只有顺序存储和链式存储。 最基本的数组和链表对应这两者,栈…...
跟着cherno手搓游戏引擎【24】开启2D引擎前的项目总结(包括前置知识汇总)
前置技术: c动态链接和静态链接: 隐藏的细节:编译与链接_哔哩哔哩_bilibili 【底层】动态链接库(dll)是如何工作的?_哔哩哔哩_bilibili 预编译,编译,汇编,链接 预编译头文件: 为…...
rknn优化教程(二)
文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK,开始写第二篇的内容了。这篇博客主要能写一下: 如何给一些三方库按照xmake方式进行封装,供调用如何按…...
Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具
文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...
MySQL 8.0 OCP 英文题库解析(十三)
Oracle 为庆祝 MySQL 30 周年,截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始,将英文题库免费公布出来,并进行解析,帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...
[Java恶补day16] 238.除自身以外数组的乘积
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O(n) 时间复杂度…...
服务器--宝塔命令
一、宝塔面板安装命令 ⚠️ 必须使用 root 用户 或 sudo 权限执行! sudo su - 1. CentOS 系统: yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh2. Ubuntu / Debian 系统…...
中医有效性探讨
文章目录 西医是如何发展到以生物化学为药理基础的现代医学?传统医学奠基期(远古 - 17 世纪)近代医学转型期(17 世纪 - 19 世纪末)现代医学成熟期(20世纪至今) 中医的源远流长和一脉相承远古至…...
佰力博科技与您探讨热释电测量的几种方法
热释电的测量主要涉及热释电系数的测定,这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中,积分电荷法最为常用,其原理是通过测量在电容器上积累的热释电电荷,从而确定热释电系数…...
在Mathematica中实现Newton-Raphson迭代的收敛时间算法(一般三次多项式)
考察一般的三次多项式,以r为参数: p[z_, r_] : z^3 (r - 1) z - r; roots[r_] : z /. Solve[p[z, r] 0, z]; 此多项式的根为: 尽管看起来这个多项式是特殊的,其实一般的三次多项式都是可以通过线性变换化为这个形式…...
Ubuntu系统复制(U盘-电脑硬盘)
所需环境 电脑自带硬盘:1块 (1T) U盘1:Ubuntu系统引导盘(用于“U盘2”复制到“电脑自带硬盘”) U盘2:Ubuntu系统盘(1T,用于被复制) !!!建议“电脑…...
针对药品仓库的效期管理问题,如何利用WMS系统“破局”
案例: 某医药分销企业,主要经营各类药品的批发与零售。由于药品的特殊性,效期管理至关重要,但该企业一直面临效期问题的困扰。在未使用WMS系统之前,其药品入库、存储、出库等环节的效期管理主要依赖人工记录与检查。库…...
