插件开发版|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…...

python/java环境配置
环境变量放一起 python: 1.首先下载Python Python下载地址:Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个,然后自定义,全选 可以把前4个选上 3.环境配置 1)搜高级系统设置 2…...
线程与协程
1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指:像函数调用/返回一样轻量地完成任务切换。 举例说明: 当你在程序中写一个函数调用: funcA() 然后 funcA 执行完后返回&…...
基于数字孪生的水厂可视化平台建设:架构与实践
分享大纲: 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年,数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段,基于数字孪生的水厂可视化平台的…...

DIY|Mac 搭建 ESP-IDF 开发环境及编译小智 AI
前一阵子在百度 AI 开发者大会上,看到基于小智 AI DIY 玩具的演示,感觉有点意思,想着自己也来试试。 如果只是想烧录现成的固件,乐鑫官方除了提供了 Windows 版本的 Flash 下载工具 之外,还提供了基于网页版的 ESP LA…...
leetcodeSQL解题:3564. 季节性销售分析
leetcodeSQL解题:3564. 季节性销售分析 题目: 表:sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...

如何理解 IP 数据报中的 TTL?
目录 前言理解 前言 面试灵魂一问:说说对 IP 数据报中 TTL 的理解?我们都知道,IP 数据报由首部和数据两部分组成,首部又分为两部分:固定部分和可变部分,共占 20 字节,而即将讨论的 TTL 就位于首…...

关键领域软件测试的突围之路:如何破解安全与效率的平衡难题
在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件,这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下,实现高效测试与快速迭代?这一命题正考验着…...
管理学院权限管理系统开发总结
文章目录 🎓 管理学院权限管理系统开发总结 - 现代化Web应用实践之路📝 项目概述🏗️ 技术架构设计后端技术栈前端技术栈 💡 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 🗄️ 数据库设…...

Unity UGUI Button事件流程
场景结构 测试代码 public class TestBtn : MonoBehaviour {void Start(){var btn GetComponent<Button>();btn.onClick.AddListener(OnClick);}private void OnClick(){Debug.Log("666");}}当添加事件时 // 实例化一个ButtonClickedEvent的事件 [Formerl…...

ui框架-文件列表展示
ui框架-文件列表展示 介绍 UI框架的文件列表展示组件,可以展示文件夹,支持列表展示和图标展示模式。组件提供了丰富的功能和可配置选项,适用于文件管理、文件上传等场景。 功能特性 支持列表模式和网格模式的切换展示支持文件和文件夹的层…...