插件开发版|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…...
基于算法竞赛的c++编程(28)结构体的进阶应用
结构体的嵌套与复杂数据组织 在C中,结构体可以嵌套使用,形成更复杂的数据结构。例如,可以通过嵌套结构体描述多层级数据关系: struct Address {string city;string street;int zipCode; };struct Employee {string name;int id;…...

调用支付宝接口响应40004 SYSTEM_ERROR问题排查
在对接支付宝API的时候,遇到了一些问题,记录一下排查过程。 Body:{"datadigital_fincloud_generalsaas_face_certify_initialize_response":{"msg":"Business Failed","code":"40004","sub_msg…...

3.3.1_1 检错编码(奇偶校验码)
从这节课开始,我们会探讨数据链路层的差错控制功能,差错控制功能的主要目标是要发现并且解决一个帧内部的位错误,我们需要使用特殊的编码技术去发现帧内部的位错误,当我们发现位错误之后,通常来说有两种解决方案。第一…...

如何在看板中体现优先级变化
在看板中有效体现优先级变化的关键措施包括:采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中,设置任务排序规则尤其重要,因为它让看板视觉上直观地体…...

跨链模式:多链互操作架构与性能扩展方案
跨链模式:多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈:模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展(H2Cross架构): 适配层…...

第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明
AI 领域的快速发展正在催生一个新时代,智能代理(agents)不再是孤立的个体,而是能够像一个数字团队一样协作。然而,当前 AI 生态系统的碎片化阻碍了这一愿景的实现,导致了“AI 巴别塔问题”——不同代理之间…...

【配置 YOLOX 用于按目录分类的图片数据集】
现在的图标点选越来越多,如何一步解决,采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集(每个目录代表一个类别,目录下是该类别的所有图片),你需要进行以下配置步骤&#x…...
Vite中定义@软链接
在webpack中可以直接通过符号表示src路径,但是vite中默认不可以。 如何实现: vite中提供了resolve.alias:通过别名在指向一个具体的路径 在vite.config.js中 import { join } from pathexport default defineConfig({plugins: [vue()],//…...

华为OD机试-最短木板长度-二分法(A卷,100分)
此题是一个最大化最小值的典型例题, 因为搜索范围是有界的,上界最大木板长度补充的全部木料长度,下界最小木板长度; 即left0,right10^6; 我们可以设置一个候选值x(mid),将木板的长度全部都补充到x,如果成功…...
tomcat指定使用的jdk版本
说明 有时候需要对tomcat配置指定的jdk版本号,此时,我们可以通过以下方式进行配置 设置方式 找到tomcat的bin目录中的setclasspath.bat。如果是linux系统则是setclasspath.sh set JAVA_HOMEC:\Program Files\Java\jdk8 set JRE_HOMEC:\Program Files…...