插件开发版|Authing 结合 APISIX 实现统一可配置 API 权限网关
当开发者在构建网站、移动设备或物联网应用程序时,API 网关作为微服务架构中不可或缺的控制组件,是流量的核心进出口。通过有效的权限管控,可以实现认证授权、监控分析等功能,提高 API 的安全性、可用性、拓展性以及优化 API 性能。在上一篇文章( Authing 结合 APISIX 实现统一可配置 API 权限网关【快速启动版】)中,演示了通过 Authing 权限管理 + APISIX 实现 API 的访问控制效果,本文将教你如何实现上述能力的具体实践方法。
01 关于 Authing
Authing 是国内首款以开发者为中心的全场景身份云产品,集成了所有主流身份认证协议,为企业和开发者提供完善安全的用户认证和访问管理服务。以「API First」作为产品基石,把身份领域所有常用功能都进行了模块化的封装,通过全场景编程语言 SDK 将所有能力 API 化提供给开发者。同时,用户可以灵活的使用 Authing 开放的 RESTful APIs 进行功能拓展,满足不同企业不同业务场景下的身份和权限管理需求。
02 关于 APISIX
Apache APISIX 是一个动态、实时、高性能的 API 网关,提供负载均衡、动态上游、灰度发布、服务熔断、身份认证、可观测性等丰富的流量管理功能。Apache APISIX 不仅支持插件动态变更和热插拔,而且拥有众多实用的插件。Apache APISIX 的 OpenID Connect 插件支持 OpenID Connect 协议,用户可以使用该插件让 Apache APISIX 对接 Authing 服务,作为集中式认证网关部署于企业中。
03 业务目标
通过 Authing 权限管理 + APISIX 实现 API 的访问控制
04 如何实现
本文所涉及到的代码已经上传到 Github
Python 插件:
https://github.com/fehu-asia/authing-apisix-python-agent
Java Adapter:
https://github.com/fehu-asia/authing-apisix-java-adapter
Java 插件:
https://github.com/fehu-asia/authing-apisix-java-agent
4.1 业务架构
系统整体包含了三大部分: Authing 服务集群、Authing 插件适配服务以及 APISIX 网关,本方案建立需要配置和开发的部分有四个部分,Authing API 权限结构配置、APISIX 插件和路由配置、APISIX 插件开发部署以及业务适配服务开发,其中业务适配服务包含了认证和授权的主要逻辑(使用单独服务承载),避免了插件的频繁更新和部署。
这里需要说明的是,之所以采用 Adapter 的方式来实现,是因为插件我们并不希望经常变动,但需求可能是无法避免的需要经常变动,所以我们将具体的鉴权逻辑放在 Adapter ,插件只实现请求转发和根据 Adapter 的返回结果决定是否放行,同时无状态的插件可以让我们实现更多的场景复用和能力扩展,例如进行鉴权结果的缓存实现,后续只需维护 Adapter 即可。
当然我们也可将具体的逻辑放在插件里。
注意,本教程只用于与 APISIX 和 Authing 进行集成,对于生产环境使用,您需要自行开发插件并保证其安全性及可用性等,本文档不承诺此插件可以用于生产环境。
- APISIX 基础环境搭建
git clone https://github.com/apache/apisix-docker.git
cd apisix-docker/example
docker-compose -p docker-apisix up -d
到这里可以使用 docker ps 查看 apisix docker 进程启动状态, 随后访问 localhost:9000 可以进入 dashboard 界面进行路由和插件的配置。
4.2 在 Authing 对 API 进行管理
登录 Authing 官网:www.authing.com ,进行以下操作:
- 4.2.1 创建应用
配置 Token 签名算法为 RS256 及校验 AccessToken 的方式为 none 。
- 4.2.2 创建用户
进入 Authing 控制台-用户管理-用户列表-点击创建用户后,可以根据不同方式(用户名、手机号、邮箱)创建测试用户,如下图所示:
- 4.2.3 创建 API
进入 Authing 控制台-权限管理-创建资源,可以选择创建树数据类型的资源,如下图所示:
- 4.2.4 创建策略
进入权限管理-数据资源权限-数据策略标签,可以点击创建策略来新建数据访问策略,如下图所示。策略包含了对应的权限空间中定义的数据以及操作,创建后能够基于此策略对不同对象(用户、角色、用户组等)进行授权管理。
- 4.2.5 API 授权
4.3 APISIX 路由和 SOCK 配置
- 4.3.1 SOCK 配置
APISIX 使用 unix sock 与插件进程通信,因此需要配置对应的 sock 端口:
需要将宿主机上的 sock 文件挂载到容器里,插件启动的时候会在宿主机上创建这个 sock 文件,此处需要注意的是,若 APISIX 是先于插件启动的,当插件启动后,则需要重启下 APISIX 容器,确保插件先于 APISIX 启动。
文件位置: /apisix-docker/example/docker-compose.yml apisix部分
apisix:image: apache/apisix:latestrestart: alwaysvolumes:- ./apisix_log:/usr/local/apisix/logs- ./apisix_conf/config.yaml:/usr/local/apisix/conf/config.yaml:ro- /tmp/runner.sock:/tmp/runner.sock
- 4.3.2 路由配置
X-API-KEY: /apisix/apisix-docker/example/apisix_conf/config.yaml
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{"uri": "/*","plugins": {"ext-plugin-pre-req": {"conf": [{"name": "authing_agent","value": "{\"url\": \"{适配服务的访问地址}\",\"user_pool_id\": \"{用户池ID}\",\"user_pool_secret\": \"{用户池密钥}\"}"}]}},"upstream": {"type": "roundrobin","nodes": {"httpbin.org:80": 1}}
}'
ext-plugin-pre-req 是需要启用的插件类型, 在配置 conf 中需要确定两个变量:
“name”: 插件名称
“value”: “{“url”: “适配服务的访问地址”,“user_pool_id”: “用户池ID”,“user_pool_secret”: “用户池密钥”}”
其中,访问地址格式为 {{domain}}:{{port}}/{{path}}
例如:
“{“url”: “http://192.168.1.123:8080/isAllow”,“user_pool_id”: “124u2353h2t24he2u349382u152”,“user_pool_secret”: “6435462313i5412njburh2u34”}”
4.4 APISIX 插件开发和部署
- 4.4.1 建立插件工程目录
git clone https://github.com/apache/apisix-python-plugin-runner.git
进入目录 make setup
进入目录 make install
进入目录并修改 apisix/plugins/rewrite.py 文件,将请求参数传递到 Authing
- 4.4.2 编写 Agent (python) 插件代码
可使用其他语言实现例如 Java、Go、Lua
之所以采用 Python 的原因是因为环境初始化比较简单,可以让开发者快速了解 APISIX 的插件的开发机制。
https://apisix.apache.org/docs/apisix/external-plugin/
from typing import Any
from apisix.runner.http.request import Request
from apisix.runner.http.response import Response
from apisix.runner.plugin.core import PluginBase
import json
import requests
import jsondef isAllow(request,config):return requests.request("POST", config.get("url"), headers={'Content-Type': 'application/json'}, data=json.dumps({"request": request,"pluginConfig": config}))class Rewrite(PluginBase):def name(self) -> str:return "authing_agent"def config(self, conf: Any) -> Any:return confdef filter(self, conf: Any, request: Request, response: Response):# 组装 Adapter 请求参数authing_request = {"uri": request.get_uri(),"method": request.get_method(),"args":request.get_args(),"headers":request.get_headers(),"request_id":request.get_id(),"host":request.get_var("host"),"remote_addr": request.get_remote_addr(),"configs": request.get_configs()}# 接收 Adapter 响应判断是否放行authing_response = isAllow(authing_request,eval(conf))if authing_response.text != "ok":response.set_status_code(authing_response.status_code)response.set_body(authing_response.text)
- 4.4.3 运行 Agent 插件
nohup make dev & #后台运行 agent 程序
4.5 适配器开发
- 4.5.1 通信接口设计
启动代理 Authing 服务(自行实现对应接口,以 springboot 为例,接口结构如下)
- 4.5.2 部分 JAVA 文件列出如下
IsAllowController.java
package cn.authing.apisix.adapter.controller;import cn.authing.apisix.adapter.entity.APISIXRquestParams;
import cn.authing.sdk.java.client.ManagementClient;
import cn.authing.sdk.java.dto.CheckPermissionDto;
import cn.authing.sdk.java.dto.CheckPermissionRespDto;
import cn.authing.sdk.java.dto.CheckPermissionsRespDto;
import cn.authing.sdk.java.model.ManagementClientOptions;
import cn.hutool.http.HttpStatus;
import cn.hutool.http.HttpUtil;
import com.google.gson.Gson;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSObject;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.jwk.source.RemoteJWKSet;
import com.nimbusds.jose.proc.BadJOSEException;
import com.nimbusds.jose.proc.JWSKeySelector;
import com.nimbusds.jose.proc.JWSVerificationKeySelector;
import com.nimbusds.jose.proc.SecurityContext;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.proc.ConfigurableJWTProcessor;
import com.nimbusds.jwt.proc.DefaultJWTProcessor;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StopWatch;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;import java.net.MalformedURLException;
import java.net.URL;
import java.text.ParseException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;/*** @author Gao FeiHu* @version 1.0.0* @date 2022.12.22* @email gaofeihu@authing.cn*/
@RestController
@Slf4j
public class IsAllowController {/*** 用户池 ID*/public static String ACCESS_KEY_ID = "";/*** 用户池密钥*/public static String ACCESS_KEY_SECRET = "";/*** Authing SDK* See* https://docs.authing.cn/v3/reference/*/ManagementClient managementClient;/*** 初始化 ManagementClient** @param ak 用户池 ID* @param aks 用户池密钥*/public void init(String ak, String aks) {log.info("init ManagementClient ......");try {// 保存用户池 ID 和密钥ACCESS_KEY_ID = ak;ACCESS_KEY_SECRET = aks;// 初始化ManagementClientOptions options = new ManagementClientOptions();options.setAccessKeyId(ak);options.setAccessKeySecret(aks);managementClient = new ManagementClient(options);} catch (Exception e) {e.printStackTrace();System.err.println("初始化 managementClient 失败,可能无法请求!");}}/*** 是否放行** @param apisixRquestParams 请求 body ,包含了 APISIX 插件的配置以及请求上下文* @param response HttpServletResponse* @return 200 OK 放行* 403 forbidden 禁止访问* 500 internal server error 请求错误 可根据实际需求放行或拒绝*/@PostMapping("/isAllow")public Object isAllow(@RequestBody APISIXRquestParams apisixRquestParams, HttpServletResponse response) {// 请求计时器StopWatch stopWatch = new StopWatch();stopWatch.start();// 请求 ID 与 APISIX 一致String requestID = apisixRquestParams.getRequest().getRequest_id();log.info("{} ==> 请求入参 : {} ", requestID, new Gson().toJson(apisixRquestParams));try {// 0. 若插件为多实例用于实现不同业务逻辑,此处可对应修改为多实例模式if (managementClient == null || !ACCESS_KEY_ID.equals(apisixRquestParams.getPluginConfig().get("user_pool_id"))) {init((String) apisixRquestParams.getPluginConfig().get("user_pool_id"), (String) apisixRquestParams.getPluginConfig().get("user_pool_secret"));}// 1. 拿到 accessTokenString authorization = (String) apisixRquestParams.getRequest().getHeaders().get("authorization");if (!StringUtils.hasLength(authorization)) {return result(response, stopWatch, requestID, HttpStatus.HTTP_UNAUTHORIZED, "HTTP_UNAUTHORIZED");}String accessToken = authorization;if (authorization.startsWith("Bearer")) {accessToken = authorization.split(" ")[1].trim();}log.info("{} ==> accessToken : {} ", requestID, accessToken);// 2. 解析 accessToken 拿到应用 ID 和用户 IDJWSObject parse = JWSObject.parse(accessToken);Map<String, Object> payload = parse.getPayload().toJSONObject();String aud = (String) payload.get("aud");String sub = (String) payload.get("sub");// 3. 校验 accessToken// 在线校验String result = onlineValidatorAccessToken(accessToken, aud);log.info("{} ==> accessToken 在线结果 : {} ", requestID, result);if (!result.contains("{\"active\":true")) {return result(response, stopWatch, requestID, HttpStatus.HTTP_UNAUTHORIZED, "HTTP_UNAUTHORIZED");}// // 离线校验
// if (null == offlineValidatorAccessToken(accessToken, aud)) {
// return result(response, stopWatch, requestID, HttpStatus.HTTP_UNAUTHORIZED, "HTTP_UNAUTHORIZED");
// }// 4. 获取到 APISIX 中的请求方法,对应 Authing 权限中的 actionString action = apisixRquestParams.getRequest().getMethod();// 5. 获取到 APISIX 中的请求路径String resource = apisixRquestParams.getRequest().getUri();// 6. 去 Authing 请求,判断是否有权限// TODO 可在此添加 Redis 对校验结果进行缓存CheckPermissionDto reqDto = new CheckPermissionDto();reqDto.setUserId(sub);reqDto.setNamespaceCode(aud);reqDto.setResources(Arrays.asList(resource.substring(1, resource.length())));reqDto.setAction(action);CheckPermissionRespDto checkPermissionRespDto = managementClient.checkPermission(reqDto);log.info(new Gson().toJson(checkPermissionRespDto));// 7. 由于我们是单个 resource 校验,所以只需要判断第一个元素即可List<CheckPermissionsRespDto> resultList = checkPermissionRespDto.getData().getCheckResultList();if (resultList.isEmpty() || resultList.get(0).getEnabled() == false) {return result(response, stopWatch, requestID, HttpStatus.HTTP_FORBIDDEN, "HTTP_FORBIDDEN");}return result(response, stopWatch, requestID, HttpStatus.HTTP_OK, "ok");} catch (Exception e) {e.printStackTrace();log.error("请求错误!", e);return result(response, stopWatch, requestID, HttpStatus.HTTP_INTERNAL_ERROR, e.getMessage());}}public String result(HttpServletResponse response, StopWatch stopWatch, String requestID, int status, String msg) {stopWatch.stop();log.info("{} ==> 请求耗时:{} , 请求出参 : http_status_code={},msg={} ", requestID, stopWatch.getTotalTimeMillis() + "ms", status, msg);response.setStatus(status);return msg;}public String onlineValidatorAccessToken(String accessToken, String aud) {HashMap<String, Object> paramMap = new HashMap<>();paramMap.put("token", accessToken);paramMap.put("token_type_hint", "access_token");paramMap.put("client_id", aud);return HttpUtil.post("https://api.authing.cn/" + aud + "/oidc/token/introspection", paramMap);}public JWTClaimsSet offlineValidatorAccessToken(String accessToken, String aud) {try {ConfigurableJWTProcessor<SecurityContext> jwtProcessor =new DefaultJWTProcessor<>();JWKSource<SecurityContext> keySource =null;keySource = new RemoteJWKSet<>(new URL("https://api.authing.cn/" + aud + "/oidc/.well-known/jwks.json"));JWSAlgorithm expectedJWSAlg = JWSAlgorithm.RS256;JWSKeySelector<SecurityContext> keySelector =new JWSVerificationKeySelector<>(expectedJWSAlg, keySource);jwtProcessor.setJWSKeySelector(keySelector);return jwtProcessor.process(accessToken, null);} catch (MalformedURLException e) {e.printStackTrace();} catch (ParseException e) {e.printStackTrace();} catch (BadJOSEException e) {e.printStackTrace();} catch (JOSEException e) {e.printStackTrace();} finally {return null;}}
}
APISIXRquestParams.java
package cn.authing.apisix.adapter.entity;import lombok.Data;
import lombok.ToString;import java.util.Map;/*** APISIX 请求实体类*/
@Data
@ToString
public class APISIXRquestParams {/*** APISIX 请求上下文*/APISIXRequest request;/*** 插件配置*/Map<String, Object> pluginConfig;}
APISIXRequest.java
package cn.authing.apisix.adapter.entity;import lombok.Data;
import lombok.ToString;import java.util.Map;@Data
@ToString
public class APISIXRequest {private String uri;private String method;private String request_id;private String host;private String remote_addr;private Map<String, Object> args;private Map<String, Object> headers;private Map<String, Object> configs;
}
4.6 访问测试
- 4.6.1 未认证
- 4.6.2 无权限
- 4.6.3 认证通过并成功访问
404 是因为上游服务没有这个接口,但认证和 API 鉴权已经通过
05 总结
如果您需要对 API 进行细颗粒度的管理可以通过本方案来实现,我们可以在 Adapter 实现更加细粒度的 API 访问控制以及更加场景化的权限方案。
相关文章:
插件开发版|Authing 结合 APISIX 实现统一可配置 API 权限网关
当开发者在构建网站、移动设备或物联网应用程序时,API 网关作为微服务架构中不可或缺的控制组件,是流量的核心进出口。通过有效的权限管控,可以实现认证授权、监控分析等功能,提高 API 的安全性、可用性、拓展性以及优化 API 性能…...
deepinlinux v20安装rust和tauri并配置vscode开发工具过程
rust 很快进入linux内核开发,作为高效后台语言值得学习 tauri是代替electron的跨平台框架,不打包浏览器内核,所以打包出来体积小 安装rust 命令 curl --proto https --tlsv1.2 -sSf https://sh.rustup.rs | sh 安装后看版本 rustc -V 看构…...
通俗易懂的机器学习——sklearn鸢尾花分类(KNN)
前言 KNN算法是机器学习中较为简单的入门算法,其主要思想是选取k个与待预测点相近的数据,观察他们的类别,本着离谁近就更像谁的思路对于待预测点进行预测,本文将针对使用sklearn进行KNN算法的使用进行详解 数据预处理 在正式开…...
操作系统引论
操作系统是管理硬件和软件的一种应用程序。操作系统是运行在计算机上最重要的一种软件,它管理计算机的资源和进程以及所有的硬件和软件。它为计算机硬件和软件提供了一种中间层,使应用软件和硬件进行分离,让我们无需关注硬件的实现࿰…...
优质 CS 读博 (PhD) 经验贴汇总
前言 如果你对这篇文章可感兴趣,可以点击「【访客必读 - 指引页】一文囊括主页内所有高质量博客」,查看完整博客分类与对应链接。 Advice for early-stage Ph.D. students 读博的核心是在研究上取得进展,只有在研究上取得一些进展ÿ…...
SpringCloud学习笔记 - @SentinelResource的fallbackblockHandler配置详解 - sentinel
1. sentinel服务负载均衡测试 sentinel默认开启了负载均衡的轮询模式,为了测试sentinel服务负载均衡的效果,需要先创建两个服务提供者和一个服务消费者。 1.1. 分别创建两个服务提供者-支付服务9003、9004 1. 添加pom依赖: 提供者只需要将…...
华为OD机试题 - 静态扫描最优成本(JavaScript)
最近更新的博客 2023新华为OD机试题 - 斗地主(JavaScript)2023新华为OD机试题 - 箱子之形摆放(JavaScript)2023新华为OD机试题 - 考古学家(JavaScript)2023新华为OD机试题 - 相同数字的积木游戏 1(JavaScript)2023新华为OD机试题 - 最多等和不相交连续子序列(JavaScri…...
mysql大数据量批量提交
DROP PROCEDURE IF EXISTS test.insert_bacth_commit_test1;CREATE PROCEDURE test.insert_bacth_commit_test1()begindeclare start_num int default 0; -- 初始设置起始行数declare end_num int default 5;-- 初始设施结束行数declare cnt_srouce int default 0; -- 定义源表…...
IP SAN组网配置
目录一、确认网络连接畅通二、服务器端ISCSI启动器配置1.以root身份登录2.验证是否已安装iSCSI启动器3.安装iSCSI启动器4.启动iSCSI服务5.给iSCSI启动器命名6.扫描目标器7.登录目标器8.将登录目标器行为设置为自启动三、主机多路径配置四、存储配置五、主机挂载背景:…...
面试7分看能力,3分靠嘴皮,剩下90分就靠这份Java面试八股文
有句话说的好,面试中7分靠能力,3分靠嘴皮刚开始面试的时候,介绍项目一般都是凸显我们技能的重中之重,它对一次面试的成败可以说具有决定性作用,这就涉及到我们的表达能力了,有人说我其实水平都在线…...
api接口如何对接?
对于很多产品小白或求职者而言,API接口是一个产品和研发领域的专业术语,大家可能在文章或者PRD中都已经有接触过API接口的概念。 实际上,接口的应用已经非常广泛和成熟,这个概念主要活跃在公司内部的各系统之间的衔接和对接以及公…...
毕业2年不到选择跳槽,居然拿到25K的薪资,简直了···
本人本科就读于某普通院校,毕业后通过同学的原因加入软件测试这个行业,角色也从测试小白到了目前的资深工程师,从功能测试转变为测试开发,并顺利拿下了某二线城市互联网企业的Offer,年薪 30W 。 选择和努力哪个重要&am…...
Java反序列化漏洞——CommonsCollections3链分析
一、原理CC1链中我们是通过调用Runtime.getRuntime.exec()来执行系统命令,而另一个方向我们可以通过TemplatesImpl加载字节码的类,通过调⽤其newTransformer() 方法,即可执⾏这段字节码的类构造器,我们在类构造器中加入恶意代码&a…...
英文论文(sci)解读复现【NO.5】让RepVGG再次变得更强大:一种量化感知方法
此前出了目标检测算法改进专栏,但是对于应用于什么场景,需要什么改进方法对应与自己的应用场景有效果,并且多少改进点能发什么水平的文章,为解决大家的困惑,此系列文章旨在给大家解读发表高水平学术期刊中的SCI论文&am…...
hive学习(仅供参考)
hive搭建Hive什么是hiveHive的优势和特点hive搭建解压、改名修改环境变量添加hive-site.xml将maven架包拷贝到hive替换一下gua包使环境变量生效初始化安装成功Hive 什么是hive 将结构化的数据文件映射为数据库表 提供类sql的查询语言HQL(Hive Query Language) Hive让更多的人…...
新生儿住月子中心20天患败血症 什么是败血症?有哪些危害
12月7日,四川眉山市民唐先生说,他刚出生的儿子在妇产医院分娩中心住了20天后感染了败血症。据唐先生介绍,哈子出院时各项指标正常。他在分娩中心住了半个月左右,孩子喝牛奶异常易怒,第二天开始发烧。当天,在…...
2023年美赛赛题A题赛题公布
问题A:遭受旱灾的植物群落背景不同种类的植物对压力的反应方式不同。例如,草原是相当的对干旱敏感。干旱发生的频率和严重程度各不相同。大量的观察表明,不同物种的数量在一个物种如何生长的过程中起着重要作用植物群落在连续几代的干旱周期中适应。在一…...
交互式前端开发最好用的WebGL框架
JavaScript是创建Web最有用的编程语言之一,尤其是在WebGL库的支持下。有了WebGL,可以很方便地使用 HTML5 Canvas 元素动态生成图形。因此,设计师和开发人员很容易创建流畅的2D和3D效果。WebGL是JavaScript API或基于OpenGL的库,它…...
【Java 面试合集】包装类的缓存问题
包装类的缓存问题1. 概述 嗨,大家好,【Java 面试合集】每日一题又来了。今天我们分享的内容是:包装类的缓存问题。 我们下面的案例以Integer 为例 2. 表现 public class TestCache {public static void main(String[] args) {Integer i 127…...
JAVA PYTHONGOLANG在STR LIST MAP 等数据结构的一些底层设计
一、列表和扩容机制 JAVA的列表主要分为list和vector,list是线程不安全的。list又主要分为ArrayList和LinkedList,ArrayList底层通过object数组实现,可以实现快速查找,LinkedList底层通过双向列表实现。java常用的列表实现类为ArrayList,ArrayList的主要源码如下: publi…...
SpringMVC处理ajax请求
RequestBodyRequestBody:将请求体中的内容和控制器方法的形参进行绑定。使用RequestBody注解将json格式请求参数转换为java对象。条件:1. 导入jackson依赖 (默认调用jackson功能实现的)2. 在springmvc的配置文件中设置开启<mvc:annotation-driven/>3. 在处理请…...
Spire.Office 8.2.2 for NET 开年之喜
Spire.Office for .NET对文档的操作包括打开,创建,修改,转换,打印,浏览 Word、Excel、PowerPoint 和 PDF 文档,以及将数据从数据源导出为常用的文档格式,如:Word,Excel&a…...
python中的.nc文件处理 | 04 利用矢量边界提取NC数据
利用矢量边界提取.nc数据 import osimport numpy as np import pandas as pd import matplotlib.pyplot as plt import cartopy.crs as ccrs import cartopy.feature as cfeature import seaborn as sns import geopandas as gpd import earthpy as et import xarray as xr # …...
使用 PyNeuraLogic 超越 Transformers
展示神经符号编程的力量neuro-symbolic1. 简介 在过去的几年里,我们看到了基于 Transformer 的模型的兴起,并在自然语言处理或计算机视觉等许多领域取得了成功的应用。在本文[1]中,我们将探索一种简洁、可解释和可扩展的方式来表达深度学习模…...
微信点金计划(服务商角度)
时间:2023/2/17 背景:微信在推出点金计划后,原本window.WeixinJSBridge.invoke方法的回调失效了,需要在微信支付服务商平台|平台开放更多能力,与服务商一起成长这里进行配置,配置流程跟着官方给…...
2023年美赛 MCM B题 重新构想马赛马拉岛
背景肯尼亚的野生动物保护区最初主要是为了保护野生动物和其他自然资源。肯尼亚议会于2013 年通过了《野生动物保护和管理法》,以提供更公平的资源共享,并允许进行替代的、以社 区为基础的管理工作[1].此后,肯尼亚增加了修正案,以…...
指标体系的应用与搭建
一、指标体系的介绍 体系泛指一定范围内同类事物按照一定的顺序或内在联系而组成的整体。指标体系也一样,指的是不同指标按照一定的顺序及内部联系而组成的整体。此外,在指标体系中,除了以应用为出发点搭建,还会加入使用指南&…...
固态继电器的五大优势
固态继电器的优点和五个关键优势,现代电气控制系统因二极管、晶体管和晶闸管等固态器件的发明而得到极大的增强。对于加热器和电机等大负载设备,固态继电器可能比传统的机械继电器具有巨大的优势。 虽然并非适用于所有情况,但它们具有许多吸引…...
特征检测之HOG特征算法详解及Opencv接口使用
1. HOG特征简介 特征描述符是图像或图像补丁的表示形式,它通过提取有用信息并丢弃无关信息来简化图像。 通常,特征描述符将大小W x H x 3(通道)的图像转换为长度为n的特征向量/数组。对于 HOG 特征描述符,输入图像的…...
一款好的低代码开发平台应该是什么样?
一款好的低代码开发平台应该是什么样? 以企业级应用构建来讲,完成一个应用复杂度随着技术的进步、需求的细化、业务要求的变化并不是逐渐降低,而是逐渐提升。用户想要有更好的体验,复杂度更是成倍提升。 基于此,低代码…...
网站制作熊猫建站/域名怎么注册
MySQL索引到底是干什么的?这个问题自己一直理解的很模糊,只知道它相当于书的目录,能加快数据检索速度。但是要深入一点去说,它为什么能加快数据检索速度,从哪能看出它加快了检索速度,说到这可能我就有点迷茫…...
做服装搭配直接售卖的网站/百度百度百度一下
当你收到这封信的时候,应该已经是2020年的岁末了吧。当然,前提是你能够平安地度过这十年,毕竟人生无常。不过依照你的生存能力,这些都应该不在话下吧?嘿嘿~ 想说的话很多,因此现在脑子很乱…...
烟台制作网站软件/河北网站推广公司
ArchLinux更改终端下的键盘布局更改终端下archlinux keyboardarchlinux wiki 上的直接引用cd 到 /usr/share/kbd/keymaps/i386/qwerty将默认键盘 (us.map.gz) 复制到新文件personal.map.gzgunzip解压新布局文件编辑 personal.map,例如:大小写成为 CTRL 设…...
做灯箱到哪个网站找业务/商务网站建设
在从数据库中读取数据时最好直接在select语句中加入选择条件,从而直接限制筛选出符合条件的数据,而不建议读出所有的数据然后用ABAP代码来过滤筛选。 例如,不建议使用: Select * from zflight. Check : zflight-airln ‘LF’ a…...
wordpress 账号 有效期/企业网络营销业务
本章是通过C实现邻接矩阵无向图。 目录1. 邻接矩阵无向图的介绍2. 邻接矩阵无向图的代码说明3. 邻接矩阵无向图的完整源码 转载请注明出处:如果天空不死 - 博客园 更多内容:数据结构与算法系列 目录 邻接矩阵无向图的介绍 邻接矩阵无向图是指通过邻接矩…...
卫浴网站怎么做/安徽seo人员
在使用高版本的Keil时往往复制粘贴中文注释的时候会出现乱码,这是因为中文的编码格式不对导致的,但是Keil这里的编码却又很奇特,编辑器是以ANSI编码方式来识别字符的,所需要修改编辑器的编码方式,注释是修改成GB2312编…...