写点什么

Micronaut 教程(二):分布式跟踪、JWT 安全和 AWS Lambda 部署

  • 2018-12-15
  • 本文字数:10489 字

    阅读完需:约 34 分钟

Micronaut教程(二):分布式跟踪、JWT安全和AWS Lambda部署

关键要点

  • Micronaut 提供了与 Zipkin 和 Jaeger 等多种分布式跟踪解决方案的无缝集成。

  • 框架提供了几种“开箱即用”的安全解决方案,例如基于 JWT 的认证。

  • Micronaut 提供了“令牌传播”之类的功能,用以简化微服务之间的安全通信。

  • 因为内存占用少,Micronaut 能够运行在功能即服务(FaaS)无服务器环境中。


在本系列的第一篇文章中,我们使用基于 JVM 的Micronaut框架开发并部署了三个微服务。在第二篇文章中,我们将为应用程序添加几个功能:分布式跟踪、JWT 安全性和无服务器功能。此外,我们也将介绍 Micronaut 提供的用户输入验证功能。

分布式跟踪

将系统分解为更小、更细粒度的微服务可以带来多种好处,但也会给生产环境的监控系统增加复杂性。


你应该假设你的网络将会受到恶意实体的骚扰,它们时刻准备着随心所欲地释放它们的愤怒。

——Sam Newman,《构建微服务》


Micronaut 与 Jaeger 和 Zipkin 原生集成——它们都是顶级的开源分布式跟踪解决方案。


Zipkin 是一种分布式跟踪系统,用于收集时序数据,这些数据可用于解决微服务架构中的延迟问题。它负责收集和查找这些数据。


启动 Zipkin 的简单方法是通过 Docker:


$ docker run -d -p 9411:9411 openzipkin/zipkin
复制代码


这个应用程序由三个微服务组成,也就是我们在第一篇文章中开发的三个微服务(gateway、inventory、books)。


我们需要对这三个微服务做出修改。


修改 build.gradle,加入跟踪依赖项:


build.gradle
compile "io.micronaut:micronaut-tracing"
复制代码


将以下依赖项添加到 build.gradle 中,这样就可以将跟踪数据发送到 Zipkin。


build.gradle
runtime 'io.zipkin.brave:brave-instrumentation-http' runtime 'io.zipkin.reporter2:zipkin-reporter' compile 'io.opentracing.brave:brave-opentracing'
复制代码


配置跟踪选项:


src/main/resources/application.yml
tracing: zipkin: http: url: http://localhost:9411 enabled: true sampler: probability: 1
复制代码


设置 tracing.zipkin.sample.probability = 1,意思是我们要跟踪所有的请求。在生产环境中,你可能希望设置较低的百分比。


在测试时禁用跟踪:


src/test/resources/application-test.yml
tracing: zipkin: enabled: false
复制代码


只需要很少的配置更改,就可以将分布式跟踪集成到 Micronaut 中。

运行应用程序

现在让我们运行应用程序,看看分布式跟踪集成是否能够正常运行。在第一篇文章中,我们集成了 Consul,用于实现服务发现。因此,在启动微服务之前需要先启动 Zipkin 和 Consul。在微服务启动好以后,它们将在 Consul 服务发现中进行注册。当我们发出请求时,它们会向 Zipkin 发送数据。


Gradle 提供了一个 flag(-parallel)用来启动微服务:


./gradlew -parallel run
复制代码


你可以通过 cURL 命令向三个微服务发起请求:


$ curl http://localhost:8080/api/books[{"isbn":"1680502395","name":"Release It!","stock":3},{"isbn":"1491950358","name":"Building Microservices","stock":2}]
复制代码


然后,你可以通过http://localhost:9411来访问Zipkin UI。

JWT 安全性

Micronaut 提供了多种开箱即用的安全选项,你可以使用基本的身份验证、基于会话的身份验证、JWT 身份验证、Ldap 身份验证,等等。JSON Web Token(JWT)是一种开放的行业标准(RFC 7519)用于在参与方之间声明安全。


Micronaut 提供了开箱即用的用于生成、签名、加密和验证 JWT 令牌的功能。


我们将把 JWT 身份验证集成到我们的应用程序中。

修改 gateway 微服务,让它支持 JWT

gateway 微服务将负责生成和传播 JWT 令牌。


修改 build.gradle,为每个微服务(gateway、inventory 和 books)添加 micronaut-security-jwt 依赖项:


gateway/build.gradle
compile "io.micronaut:micronaut-security-jwt" annotationProcessor "io.micronaut:micronaut-security"
复制代码


修改 application.yml:


gateway/src/main/resources/application.ymlmicronaut:    application:        name: gateway    server:        port: 8080    security:        enabled: true        endpoints:            login:                enabled: true            oauth:                enabled: true        token:            jwt:                enabled: true               signatures:                   secret:                       generator:                           secret: pleaseChangeThisSecretForANewOne            writer:                header:                   enabled: true            propagation:                enabled: true                service-id-regex: "books|inventory"
复制代码


我们做了几个重要的配置变更:


  • micronaut.security.enable = true 启用了安全,并默认为每个端点提供安全保护。

  • micronaut.security.endpoints.login.enable = true 启用了/login 端点,我们将用它进行身份验证。

  • micronaut.security.endpoints.oauth.enable = true 启用了/oauth/access_tokenendpoint 端点,在令牌过期时,我们可以使用它来获取新的 JWT 访问令牌。

  • micronaut.security.jwt.enable = true 启用了 JWT 功能。

  • 我们让应用程序启用签名的 JWT。更多的签名和加密选项,请参阅 JWT 令牌生成文档。

  • micronaut.security.token.propagation.enabled = true 表示启用了令牌传播。这是一种在微服务架构中简化 JWT 或其他令牌安全机制的功能。

  • micronaut.security.writer.header.enabled = ture 启用了一个令牌写入器,它将为开发人员在 HTTP 标头中写入 JWT 令牌。

  • micronaut.security.token.propagation.service-id-regex 设置了一个正则表达式,用于匹配需要进行令牌传播的服务。我们匹配了应用程序中的其他两个服务。


你可以使用 @Secured 注解来配置 Controller 或 Controller Action 级别的访问。


使用 @Secured(“isAuthenticated()”)注解 BookController.java,只允许经过身份验证的用户访问。同时记得使用 @Secured(“isAuthenticated()”)注解 inventory 和 books 微服务的 BookController 类。


/login 端点被调用时,会尝试通过任何可用的 AuthenticationProvider 对用户进行身份验证。为了简单起见,我们将允许两个用户访问,他们是福尔摩斯和华生。创建 SampleAuthenticationProvider:


gateway/src/main/java/example/micronaut/SampleAuthenticationProvider.java
package example.micronaut;
import io.micronaut.context.annotation.Requires; import io.micronaut.context.env.Environment; import io.micronaut.security.authentication.AuthenticationFailed; import io.micronaut.security.authentication.AuthenticationProvider; import io.micronaut.security.authentication.AuthenticationRequest; import io.micronaut.security.authentication.AuthenticationResponse; import io.micronaut.security.authentication.UserDetails; import io.reactivex.Flowable; import org.reactivestreams.Publisher;
import javax.inject.Singleton; import java.util.ArrayList; import java.util.Arrays;
@Requires(notEnv = Environment.TEST) @Singleton public class SampleAuthenticationProvider implements AuthenticationProvider {
@Override public Publisher<AuthenticationResponse> authenticate(AuthenticationRequest authenticationRequest) { if (authenticationRequest.getIdentity() == null) { return Flowable.just(new AuthenticationFailed()); } if (authenticationRequest.getSecret() == null) { return Flowable.just(new AuthenticationFailed()); } if (Arrays.asList("sherlock", "watson").contains(authenticationRequest.getIdentity().toString()) && authenticationRequest.getSecret().equals("elementary")) { return Flowable.just(new UserDetails(authenticationRequest.getIdentity().toString(), new ArrayList<>())); } return Flowable.just(new AuthenticationFailed()); } }
复制代码

修改 inventory 和 books,让它们支持 JWT

对于 inventory 和 books,除了添加 micronaut-security-jwt 依赖项并使用 @Secured 注解控制器之外,我们还需要修改 application.yml,以便能够验证在 gateway 中生成和签名的 JWT 令牌。


修改 application.yml:


inventory/src/main/resources/application.yml
micronaut: application: name: inventory server: port: 8081 security: enabled: true token: jwt: enabled: true signatures: secret: validation: secret: pleaseChangeThisSecretForANewOne
复制代码


请注意,我们使用与 gateway 配置中相同的秘钥,这样就可以验证由 gateway 微服务签名的 JWT 令牌。

运行安全的应用程序

在启动了 Zipkin 和 Consul 之后,你就可以同时启动这三个微服务。Gradle 提供了一个方便的 flag(-parallel):


./gradlew -parallel run
复制代码


你可以运行 cURL 命令,然后会收到 401 错误,表示未授权!


$ curl -I http://localhost:8080/api/books HTTP/1.1 401 UnauthorizedDate: Mon, 1 Oct 2018 18:44:54 GMT transfer-encoding: chunked connection: close
复制代码


我们需要先登录,并获得一个有效的 JWT 访问令牌:


$ curl -X "POST" "http://localhost:8080/login" \-H 'Content-Type: application/json; charset=utf-8' \-d $'{ "username": "sherlock", "password": "password" }' {"username":"sherlock","access_token":"eyJhbGciOiJIUzI1NiJ9.eyJzdWI iOiJzaGVybG9jayIsIm5iZiI6MTUzODQxMjQwOSwicm9sZXMiOltdLCJpc3MiOiJnYX Rld2F5IiwiZXhwIjoxNTM4NDE2MDA5LCJpYXQiOjE1Mzg0MTI0MDl9.1W4CXbN1bJgM CQlCDKJtm7zHWzyZeIr1rHpTuDy6h0","refresh_token":"eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ zaGVybG9jayIsIm5iZiI6MTUzODQxMjQwOSwicm9sZXMiOltdLCJpc3MiOiJnYXRld2 F5IiwiaWF0IjoxNTM4NDEyNDA5fQ.l72msZKwHmYeLs7T0vKtRxu7_DZr62rPCILNmC 7UEZ4","expires_in":3600,"token_type":"Bearer"}
复制代码


Micronaut 提供了开箱即用的 RFC 6750 Bearer Token 规范支持。我们可以使用从/login 响应标头中获得的 JWT 来调用/api/books 端点。


curl "http://localhost:8080/api/books" \ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzaGVybG9jayIsIm5iZiI6MTUzODQxMjQwOS wicm9sZXMiOltdLCJpc3MiOiJnYXRld2F5IiwiZXhwIjoxNTM4NDE2MDA5LCJpYXQiO jE1Mzg0MTI0MDl9.1W4CXbN1bJgMCQlCDKJtm7zHWz-yZeIr1rHpTuDy6h0'[{"isbn":"1680502395","name":"Release It!","stock":3}, {"isbn":"1491950358","name":"Building Microservices","stock":2}]
复制代码

Serverless

我们将添加一个部署到 AWS Lambda 的功能来验证 books 的 ISBN。


mn create-function example.micronaut.isbn-validator
复制代码


注意:我们使用了 Micronaut CLI 提供的 create-function 命令。

验证

我们将创建一个单例来处理 ISBN 10 验证。


创建一个封装操作的接口:


package example.micronaut;
import javax.validation.constraints.Pattern;
public interface IsbnValidator { boolean isValid(@Pattern(regexp = "\\d{10}") String isbn);}
复制代码


Micronaut 的验证基于标准框架JSR 380,也称为 Bean Validation 2.0。


Hibernate Validator是这个标准的参考实现。


将以下代码段添加到 build.gradle 中:


isbn-validator/build.gradle
compile "io.micronaut.configuration:micronaut-hibernatevalidator"
复制代码


创建一个实现了 IsbnValidator 的单例。


isbn-validator/src/main/java/example/micronaut/DefaultIsbnValidator.java
package example.micronaut;
import io.micronaut.validation.Validated; import javax.inject.Singleton; import javax.validation.constraints.Pattern;
@Singleton @Validated public class DefaultIsbnValidator implements IsbnValidator {
/** * must range from 0 to 10 (the symbol X is used for 10), and must be such that the sum of all the ten digits, each multiplied by its (integer) weight, descending from 10 to 1, is a multiple of 11. * @param isbn 10 Digit ISBN * @return whether the ISBN is valid or not. */ @Override public boolean isValid(@Pattern(regexp = "\\d{10}") String isbn) { char[] digits = isbn.toCharArray(); int accumulator = 0; int multiplier = 10; for (int i = 0; i < digits.length; i++) { char c = digits[i]; accumulator += Character.getNumericValue(c) * multiplier; multiplier--; } return (accumulator % 11 == 0); }}
复制代码


与之前的代码清单一样,你要为需要验证的类添加 @Validated 注解。


创建单元测试:


isbn-validator/src/test/java/example/micronaut/IsbnValidatorTest.java
package example.micronaut;
import io.micronaut.context.ApplicationContext; import io.micronaut.context.DefaultApplicationContext; import io.micronaut.context.env.Environment; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException;import javax.validation.ConstraintViolationException;import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue;
public class IsbnValidatorTest {
private static ApplicationContext applicationContext;
@BeforeClass public static void setupContext() { applicationContext = new DefaultApplicationContext(Environment.TEST).start(); } @AfterClass public static void stopContext() { if (applicationContext!=null) { applicationContext.stop(); } }
@Rule public ExpectedException thrown = ExpectedException.none();
@Test public void testTenDigitValidation() { thrown.expect(ConstraintViolationException.class); IsbnValidator isbnValidator = applicationContext.getBean(IsbnValidator.class); isbnValidator.isValid("01234567891"); }
@Test public void testControlDigitValidationWorks() { IsbnValidator isbnValidator = applicationContext.getBean(IsbnValidator.class); assertTrue(isbnValidator.isValid("1491950358")); assertTrue(isbnValidator.isValid("1680502395")); assertFalse(isbnValidator.isValid("0000502395")); }}
复制代码


如果我们尝试使用十一位数字字符串调用该方法,就会抛出 javax.validation.ConstraintViolationException。

函数的输入和输出

这个函数将接受单个参数(ValidationRequest,它是一个封装了 ISBN 的 POJO)。


isbn-validator/src/main/java/example/micronaut/IsbnValidationRequest.java
package example.micronaut;
public class IsbnValidationRequest { private String isbn; public IsbnValidationRequest() { } public IsbnValidationRequest(String isbn) { this.isbn = isbn; } public String getIsbn() { return isbn; }
public void setIsbn(String isbn) { this.isbn = isbn; }}
复制代码


并返回单个结果(ValidationResponse,一个封装了 ISBN 和一个指示 ISBN 是否有效的布尔值的 POJO)。


isbn-validator/src/main/java/example/micronaut/IsbnValidationResponse.java
package example.micronaut;
public class IsbnValidationResponse { private String isbn; private Boolean valid; public IsbnValidationResponse() { }
public IsbnValidationResponse(String isbn, boolean valid) { this.isbn = isbn; this.valid = valid; } public String getIsbn() { return isbn; } public void setIsbn(String isbn) { this.isbn = isbn; } public Boolean getValid() { return valid; } public void setValid(Boolean valid) { this.valid = valid; }}
复制代码

函数测试

当我们运行 create-function 命令时,Micronaut 会在 src/main/java/example/micronaut 目录创建一个 IsbnValidatorFunction 类。修改它,让它实现 java.util.Function 接口。


isbn-validator/src/main/java/example/micronaut/IsbnValidatorFunction.java
package example.micronaut;
import io.micronaut.function.FunctionBean;import java.util.function.Function; import javax.validation.ConstraintViolationException;
@FunctionBean("isbn-validator") public class IsbnValidatorFunction implements Function<IsbnValidationRequest, IsbnValidationResponse> {
private final IsbnValidator isbnValidator;
public IsbnValidatorFunction(IsbnValidator isbnValidator) { this.isbnValidator = isbnValidator; }
@Override public IsbnValidationResponse apply(IsbnValidationRequest req) { try { return new IsbnValidationResponse(req.getIsbn(), isbnValidator.isValid(req.getIsbn())); } catch(ConstraintViolationException e) { return new IsbnValidationResponse(req.getIsbn(),false); } }}
复制代码


上面的代码做了几件事:


  • 使用 @FunctionBean 注解了一个返回函数的方法。

  • 你可以在函数中使用 Micronaut 的编译时依赖注入。我们通过构造函数注入了 IsbnValidator。


函数也可以作为 Micronaut 应用程序上下文的一部分运行,这样方便进行测试。应用程序已经在类路径中包含了用于测试的 function-web 和 HTTP 服务器依赖项:


isbn-validator/build.gradle
testRuntime "io.micronaut:micronaut-http-server-netty" testRuntime "io.micronaut:micronaut-function-web"
复制代码


要在测试中调用函数,需要修改 IsbnValidatorClient.java


isbn-validator/src/test/java/example/micronaut/IsbnValidatorClient.java
package example.micronaut;
import io.micronaut.function.client.FunctionClient; import io.micronaut.http.annotation.Body; import io.reactivex.Single;import javax.inject.Named;
@FunctionClient public interface IsbnValidatorClient { @Named("isbn-validator") Single<IsbnValidationResponse> isValid(@Body IsbnValidationRequest isbn);}
复制代码


同时修改 IsbnValidatorFunctionTest.java。我们需要测试不同的场景(有效的 ISBN、无效的 ISBN、超过 10 位的 ISBN 和少于 10 位的 ISBN)。


isbn-validator/src/test/java/example/micronaut/IsbnValidatorFunctionTest.java
package example.micronaut;
import io.micronaut.context.ApplicationContext; import io.micronaut.runtime.server.EmbeddedServer; import org.junit.Test; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue;
public class IsbnValidatorFunctionTest {
@Test public void testFunction() { EmbeddedServer server = ApplicationContext.run(EmbeddedServer.class);
IsbnValidatorClient client = server.getApplicationContext().getBean(IsbnValidatorClient.class);
assertTrue(client.isValid(new IsbnValidationRequest("1491950358")).blockingGet().getValid()); assertTrue(client.isValid(new IsbnValidationRequest("1680502395")).blockingGet().getValid()); assertFalse(client.isValid(new IsbnValidationRequest("0000502395")).blockingGet().getValid()); assertFalse(client.isValid(new IsbnValidationRequest("01234567891")).blockingGet().getValid()); assertFalse(client.isValid(new IsbnValidationRequest("012345678")).blockingGet().getValid()); server.close();}
}
复制代码

部署到 AWS Lambda

假设你拥有 Amazon Web Services(AWS)帐户,那么就可以转到 AWS Lambda 并创建一个新功能。


选择 Java 8 运行时。名称为 isbn-validator,并创建一个新的角色表单模板。角色名称为 lambda_basic_execution。



运行./gradlew shadowJar 生成一个 Jar 包。


shadowJar 是 Gradle ShadowJar 插件提供的一个任务。


$ du -h isbn-validator/build/libs/isbn-validator-0.1-all.jar 11M isbn-validator/build/libs/isbn-validator-0.1-all.jar
复制代码


上传 JAR,并指定 Handler。


io.micronaut.function.aws.MicronautRequestStreamHandler 
复制代码


我只分配了 256Mb 内存,超时时间为 25 秒。


从另一个微服务中调用函数

我们将在 gateway 微服务中使用这个 lambda。修改 gateway 微服务中的 build.gradle,添加 micronaut-function-client:


com.amazonaws:aws-java-sdk-lambda dependencies:
build.gradle
compile "io.micronaut:micronaut-function-client" runtime 'com.amazonaws:aws-java-sdk-lambda:1.11.285'
复制代码


修改 src/main/resources/application.yml:


src/main/resources/application.yml
aws: lambda: functions: vat: functionName: isbn-validator qualifer: isbn region: eu-west-3 # Paris Region
复制代码


创建一个接口:


src/main/java/example/micronaut/IsbnValidator.java
package example.micronaut;
import io.micronaut.http.annotation.Body;import io.reactivex.Single;
public interface IsbnValidator { Single<IsbnValidationResponse> validateIsbn(@Body IsbnValidationRequest req); }
复制代码


创建一个 @FunctionClient:


src/main/java/example/micronaut/FunctionIsbnValidator.java
package example.micronaut;
import io.micronaut.context.annotation.Requires; import io.micronaut.context.env.Environment; import io.micronaut.function.client.FunctionClient; import io.micronaut.http.annotation.Body; import io.reactivex.Single;import javax.inject.Named;
@FunctionClient @Requires(notEnv = Environment.TEST) public interface FunctionIsbnValidator extends IsbnValidator { @Override @Named("isbn-validator") Single<IsbnValidationResponse> validateIsbn(@Body IsbnValidationRequest req);}
复制代码


关于上面这些代码有几点值得注意:


  • FunctionClient 注解可以在接口上应用引入通知(introduction advice),这样接口定义的方法就会成为远程函数的调用者。

  • 使用函数名 isbn-validator,与 application.yml 定义的一样。


最后一步是修改 gateway 的 BookController,让它调用函数。


src/main/java/example/micronaut/BooksController.java
package example.micronaut;
import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Get; import io.micronaut.security.annotation.Secured; import io.reactivex.Flowable;
import java.util.List;
@Secured("isAuthenticated()")@Controller("/api") public class BooksController { private final BooksFetcher booksFetcher; private final InventoryFetcher inventoryFetcher; private final IsbnValidator isbnValidator; public BooksController(BooksFetcher booksFetcher, InventoryFetcher inventoryFetcher, IsbnValidator isbnValidator) { this.booksFetcher = booksFetcher; this.inventoryFetcher = inventoryFetcher; this.isbnValidator = isbnValidator; }
@Get("/books") Flowable<Book> findAll() { return booksFetcher.fetchBooks() .flatMapMaybe(b -> isbnValidator.validateIsbn(new IsbnValidationRequest(b.getIsbn())) .filter(IsbnValidationResponse::getValid) .map(isbnValidationResponse -> b) ) .flatMapMaybe(b -> inventoryFetcher.inventory(b.getIsbn()) .filter(stock -> stock > 0) .map(stock -> { b.setStock(stock); return b; }) ); }}
复制代码


我们通过构造函数注入了 IsbnValidator。调用远程函数对程序员来说是透明的。

结论

下面的图片说明了我们在这一系列文章中开发的应用程序:


  • 我们有三个微服务(一个 Java 服务、一个 Groovy 服务和一个 Kotlin 服务)。

  • 这些微服务使用 Consul 进行服务发现。

  • 这些微服务使用 Zipkin 作为分布式跟踪服务。

  • 我们添加了第四个微服务,一个部署到 AWS Lambda 的功能。

  • 微服务之间的通信是安全的。每个请求在 Authorization Http 标头中包含一个 JWT 令牌就可以通过网络。JWT 令牌通过内部请求自动传播。


关于 Micronaut 的更多内容,请访问官方网站

关于作者


Sergio del AmoCaballero 是一名手机应用程序(iOS、Android,后端由 Grails/Micronaut 驱动)开发者。自 2015 年起,Sergio del Amo 为 Groovy 生态系统和微服务维护着一个新闻源Groovy Calamari


查看英文原文Micronaut Tutorial: Part 2: Easy Distributed Tracing, JWT Security and AWS Lambda Deployment


2018-12-15 09:002046
用户头像

发布了 731 篇内容, 共 447.5 次阅读, 收获喜欢 2001 次。

关注

评论 1 条评论

发布
暂无评论
发现更多内容

从研发效能的视角谈“故障复盘”

博文视点Broadview

《手写 Mybatis》第7步:SQL执行器的定义和实现

小傅哥

小傅哥 mybatis 面试经验 源码学习 手写Mybatis

面试突击44:volatile 有什么用?

王磊

Java Java面试题 java常见面试题

云图说 | 华为云医疗智能体EIHealth,AI赋能基因组研究

华为云开发者联盟

华为云 云图说 EIHealth 医疗智能体 基因组

维权思考

成周

显卡只是为游戏而生吗?GPU服务器了解一下

Finovy Cloud

GPU服务器 GPU算力

thinkphp5的消息队列详细教程

CRMEB

Java培训 判空的新写法

@零度

java8 JAVA开发 判空写法

springsecurity从当前请求对象中获取用户信息

急需上岸的小谢

5月月更

MongoDB 入门教程系列之一:开发环境搭建以及 Node.js 和 Java 的读写访问

汪子熙

数据库 mongodb 分布式 分布式数据库mongodb 5月月更

What?构造的查询语句会导致堆栈溢出

华为云开发者联盟

neo4j 图数据库 堆栈溢出 查询语句 查询语言

OpenHarmony 3.1 Beta版本关键特性解析——分布式DeviceProfile

OpenHarmony开发者

OpenHarmony OpenHarmony 3.1 Release DeviceProfile

【C语言】计算器

謓泽

5月月更

Python 操作 Excel,从 xlwings 模块开始

梦想橡皮擦

5月月更

多平台快速开发的UI框架

源字节1号

跟我学Python图像处理丨获取图像属性、兴趣ROI区域及通道处理

华为云开发者联盟

Python OpenCV 图像处理 图像 ROI区域

内存不超过5M,datop 在识别冷热内存及跨 numa 访存有多硬核?| 龙蜥技术

OpenAnolis小助手

cpu 内存 datop 轻量级 muma

动手实操丨RC522射频卡模块与IC卡完成充值消费查询的技术实现思路

华为云开发者联盟

stm32 RC522射频卡模块 IC卡 RC522

DevOps系列之 —— 持续规划与设计(一)敏捷项目管理理念与方法实践

若尘

DevOps 5月月更

数字化时代,国内SaaS行业的发展态势

小炮

SaaS

OpenHarmony 3.1 Beta版本关键特性解析——ArkUI开发框架容器类API的介绍与使用

OpenHarmony开发者

OpenHarmony ArKUI 3.0 OpenHarmony 3.1 Release

网站开发进阶(二十五)JS实现将html表格导出为excel文件

No Silver Bullet

5月月更 表格导出

[Day36]-[二叉树]-在每个树行中找最大值

方勇(gopher)

LeetCode 二叉树 数据结构算法

ONES 收购 SegmentFault 思否,共建高质量开发者社区

万事ONES

项目管理 程序员 研发管理 ONES

WebAssembly技术_加载ffmpeg在Web端调用解码

DS小龙哥

5月月更

Jeff Dean:深度学习的黄金十年

OneFlow

人工智能 深度学习 软件 硬件

大数据培训Flink怎样保证数据是一致性的

@零度

大数据 flink

云原生 on nLive:云上 Nebula Graph

NebulaGraph

数据库 图数据库 原生云

Redis 内存优化在 vivo 的探索与实践

vivo互联网技术

数据库 redis 性能优化 内存优化

Pipy for Next Web:静态内容服务与缓存加速

Flomesh

CDN加速 Pipy Headless CMS

web前端培训Vite的原理源码解析

@零度

前端开发 vite

Micronaut教程(二):分布式跟踪、JWT安全和AWS Lambda部署_语言 & 开发_Sergio del Amo Caballero_InfoQ精选文章