使用 Rust 和 DDD 构建 API 服务器

Introduction 介绍

I tried implementing an API server using Rust and the Axum framework.
我尝试使用 Rust 和 Axum 框架实现 API 服务器。

Target Audience 本文受众

  • Those who want to implement an API server with Rust.
    那些想要用 Rust 实现 API 服务器的人。
  • Those who want to implement DDD with Rust.
    那些想要用 Rust 实现 DDD (
    Domain-driven design领域驱动设计)的人。

Not Covered 本文不包括

  • Basic Rust syntax. 基本 Rust 语法。
  • Basic concepts of DDD. DDD 的基本概念。
  • How to use specific crates.
    如何使用特定的板条箱。

Dependency Direction 依赖方向

ddd

The architecture we are creating has the dependency relationship as shown above. Keeping this dependency relationship in mind while reading the article will enhance your understanding. It’s important to note that the infrastructure layer does not depend on the application layer.
我们正在创建的架构具有如上所示的依赖关系。阅读本文时牢记这种依赖关系将增强您的理解。需要注意的是,基础设施层不依赖于应用程序层。

Implementation Begins 实施开始

Specifying Requirements 明确要求

This time, we decided to create a system for universities to manage clubs.
这次,我们决定创建一个大学管理俱乐部的系统。

  • Ability to add members. 能够添加成员。
  • Fourth-year students cannot be added.
    无法添加四年级学生。
  • Ability to remove members.
    能够删除成员。
  • Owners cannot be removed.
    无法删除所有者。
  • Fourth-year students graduate.
    四年级学生毕业。
  • Clubs require a minimum of 3 members to operate.
    俱乐部需要至少 3 名会员才能运营。
  • Clubs have a maximum capacity.
    俱乐部有最大容纳人数。
  • Clubs require a representative.
    俱乐部需要一名代表。
  • Individuals aged 20 and above can join social gatherings.
    20岁及以上的个人可以参加社交聚会。
  • Only third-year students can become club representatives.
    只有三年级学生才能成为俱乐部代表。

Domain Layer 领域层

Let’s start by creating the domain layer. The Circle aggregate consists of two entities: Circle, which serves as the aggregate root, and Member, representing members within the aggregate.
让我们从创建域层开始。 Circle 聚合由两个实体组成: Circle ,用作聚合根,以及 Member ,代表聚合内的成员。

pub struct Circle {pub id: CircleId, // Circle ID (Value Object)pub name: String,pub capacity: usize,pub owner: Member,pub members: Vec<Member>,
}
pub struct Member {pub id: MemberId, // Member ID (Value Object)pub name: String,pub age: usize,pub grade: Grade,pub major: Major,
}

We’re using Value Objects for IDs. I won't explain the difference between Entity and Value Object this time, so if you're interested, please look it up.
我们使用 Value Objects 作为 ID。这次我就不解释 Entity 和 Value Object 的区别了,有兴趣的话可以查一下。

use std::fmt;
use std::hash::{Hash, Hasher};#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CircleId(usize);impl CircleId {pub fn gen() -> Self {Self(rand::random::<usize>())}
}impl std::convert::From<usize> for CircleId {fn from(id: usize) -> Self {Self(id)}
}impl Hash for CircleId {fn hash<H: Hasher>(&self, state: &mut H) {self.0.hash(state);}
}impl fmt::Display for CircleId {fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {write!(f, "{}", self.0)}
}impl std::convert::From<CircleId> for usize {fn from(circle_id: CircleId) -> usize {circle_id.0}
}

Here’s the Rust code implementing methods for the domain layer, focusing on the Circle aggregate and the Member entity.
下面是领域层实现方法的 Rust 代码,重点关注 Circle 聚合和 Member 实体。

use crate::domain::aggregate::member::Member;
use crate::domain::aggregate::value_object::circle_id::CircleId;use super::value_object::grade::Grade;
use anyhow::Error;#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Circle {pub id: CircleId, // Circle ID (Value Object)pub name: String,pub capacity: usize,pub owner: Member,pub members: Vec<Member>,
}impl Circle {// Method for creating a new circlepub fn new(name: String, owner: Member, capacity: usize) -> Result<Self, Error> {// Only 3rd graders can be ownersif owner.grade != Grade::Third {return Err(Error::msg("Owner must be 3rd grade"));}// Circle must have at least 3 membersif capacity < 3 {return Err(Error::msg("Circle capacity must be 3 or more"));}Ok(Circle {id: CircleId::gen(),name,owner,capacity,members: vec![],})}// Method for reconstructing a circlepub fn reconstruct(id: CircleId,name: String,owner: Member,capacity: usize,members: Vec<Member>,) -> Self {Circle {id,name,owner,capacity,members,}}// Method for updating a circlepub fn update(&mut self, name: Option<String>, capacity: Option<usize>) {if let Some(name) = name {self.name = name;}if let Some(capacity) = capacity {self.capacity = capacity;};}// Method to check if the circle is fullfn is_full(&self) -> bool {self.members.len() + 1 >= self.capacity}// Method to check if the circle can operatefn is_runnable(&self) -> bool {self.members.len() + 1 >= 3}// Method to check if a member can join a social gatheringfn is_drinkable_alcohol(member: &Member) -> bool {member.is_adult()}// Method to add a member to the circlepub fn add_member(&mut self, member: Member) -> Result<(), Error> {// Can't add member if circle is fullif self.is_full() {return Err(Error::msg("Circle member is full"));}// 4th graders can't join circlesif member.grade == Grade::Fourth {return Err(Error::msg("4th grade can't join circle"));}self.members.push(member);Ok(())}// Method to remove a member from the circlepub fn remove_member(&mut self, member: &Member) -> Result<(), Error> {// Owner can't be removedif self.owner.id == member.id {return Err(Error::msg("Owner can't be removed"));}self.members.retain(|m| m.id != member.id);Ok(())}// Method to graduate 4th graderspub fn graduate(&mut self) {self.members.retain(|m| m.grade != Grade::Fourth);}
}
impl Member {// Method for creating a new memberpub fn new(name: String, age: usize, grade: Grade, major: Major) -> Self {Member {id: MemberId::gen(),name,age,grade,major,}}// Method for reconstructing a memberpub fn reconstruct(id: MemberId, name: String, age: usize, grade: Grade, major: Major) -> Self {Member {id,name,age,grade,major,}}// Method to check if the member is 20 or olderpub fn is_adult(&self) -> bool {self.age >= 20}
}

This code includes methods for creating, reconstructing, updating, and managing members and circles in the domain layer.
该代码包括用于在领域层中创建、重建、更新和管理成员和圈子的方法。

Interfaces 接口

We create interfaces to expose the domain’s behavior externally. We’ll create an interface for manipulating the Circle aggregate.
我们创建接口来向外部公开域的行为。我们将创建一个用于操作 Circle 聚合的接口。

pub trait CircleRepositoryInterface {fn find_circle_by_id(&self, circle_id: &CircleId) -> Result<Circle, Error>;fn create(&self, circle: &Circle) -> Result<(), Error>;fn update(&self, circle: &Circle) -> Result<Circle, Error>;fn delete(&self, circle: &Circle) -> Result<(), Error>;
}

trait in Rust is similar to an interface in other languages.
Rust 中的 trait 与其他语言中的 interface 类似。

Infrastructure Layer 基础设施层

The infrastructure layer handles persistence. It doesn’t matter where the data is stored; it could be Firestore, Postgres, etc. For this example, we’ll store data in memory.
基础设施层处理持久性。数据存储在哪里并不重要;它可以是 Firestore、Postgres 等。在本例中,我们将数据存储在内存中。

In the infrastructure layer, we implement the interfaces from the domain layer.
在基础设施层,我们实现领域层的接口。

use anyhow::Error;use crate::domain::{aggregate::{circle::Circle,member::Member,value_object::{circle_id::CircleId, grade::Grade, major::Major, member_id::MemberId},},interface::circle_repository_interface::CircleRepositoryInterface,
};use super::db::Db;#[derive(Clone, Debug)]
pub struct CircleRepository {db: Db,
}impl CircleRepository {pub fn new() -> Self {Self { db: Db::new() }}
}impl CircleRepositoryInterface for CircleRepository {fn find_circle_by_id(&self, circle_id: &CircleId) -> Result<Circle, Error> {match self.db.get::<CircleData, _>(&circle_id.to_string())? {Some(data) => Ok(Circle::try_from(data)?),None => Err(Error::msg("Circle not found")),}}fn create(&self, circle: &Circle) -> Result<(), Error> {match self.db.get::<CircleData, _>(&circle.id.to_string())? {Some(_) => Err(Error::msg("Circle already exists")),None => {self.db.set(circle.id.to_string(), &CircleData::from(circle.clone()))?;Ok(())}}}fn update(&self, circle: &Circle) -> Result<Circle, Error> {match self.db.get::<CircleData, _>(&circle.id.to_string())? {Some(_) => self.db.set(circle.id.to_string(), &CircleData::from(circle.clone())).and_then(|_| self.db.get::<CircleData, _>(&circle.id.to_string())).map(|data| match data {Some(data) => Circle::try_from(data),None => Err(Error::msg("Failed to convert circle data")),})?,None => Err(Error::msg("Circle not found")),}}fn delete(&self, circle: &Circle) -> Result<(), Error> {match self.db.get::<CircleData, _>(&circle.id.to_string())? {Some(_) => self.db.remove(circle.id.to_string()),None => Err(Error::msg("Circle not found")),}}
}#[derive(serde::Deserialize, serde::Serialize)]
struct CircleData {id: usize,name: String,owner: MemberData,capacity: usize,members: Vec<MemberData>,
}impl std::convert::From<Circle> for CircleData {fn from(circle: Circle) -> Self {CircleData {id: circle.id.into(),name: circle.name,owner: MemberData::from(circle.owner),capacity: circle.capacity,members: circle.members.into_iter().map(MemberData::from).collect(),}}
}impl std::convert::TryFrom<CircleData> for Circle {type Error = Error;fn try_from(data: CircleData) -> Result<Self, Self::Error> {Ok(Circle::reconstruct(CircleId::from(data.id),data.name,Member::reconstruct(MemberId::from(data.owner.id),data.owner.name,data.owner.age,Grade::try_from(data.owner.grade)?,Major::from(data.owner.major.as_str()),),data.capacity,data.members.into_iter().map(Member::try_from).collect::<Result<Vec<Member>, Error>>()?,))}
}#[derive(serde::Deserialize, serde::Serialize)]
struct MemberData {id: usize,name: String,age: usize,grade: usize,major: String,
}impl std::convert::From<Member> for MemberData {fn from(value: Member) -> Self {Self {id: value.id.into(),name: value.name,age: value.age,grade: value.grade.into(),major: value.major.into(),}}
}impl std::convert::TryFrom<MemberData> for Member {type Error = Error;fn try_from(value: MemberData) -> Result<Self, Self::Error> {Ok(Member::reconstruct(MemberId::from(value.id),value.name,value.age,Grade::try_from(value.grade)?,Major::from(value.major.as_str()),))}
}

We use XxxData to represent values retrieved from the database. In this case, it's CircleData and MemberData. We implement the TryFrom trait to convert these database types into domain layer types using the reconstruct method, allowing us to decouple the application layer from the infrastructure layer.
我们使用 XxxData 来表示从数据库检索的值。在本例中,它是 CircleData 和 MemberData 。我们实现 TryFrom 特征,使用 reconstruct 方法将这些数据库类型转换为域层类型,从而使我们能够将应用程序层与基础设施层解耦。

Exactly, abstracting away the database implementation details is crucial in Domain-Driven Design (DDD). This abstraction ensures that changes in the underlying database technology (like switching from a local in-memory database to Firebase or PostgreSQL) don’t affect the application layer. By depending on abstractions rather than concrete implementations, the application layer remains decoupled from the infrastructure layer.
确切地说,抽象出数据库实现细节在领域驱动设计(DDD)中至关重要。这种抽象确保底层数据库技术的更改(例如从本地内存数据库切换到 Firebase 或 PostgreSQL)不会影响应用程序层。通过依赖抽象而不是具体实现,应用程序层保持与基础设施层的解耦。

While the implementation of the database isn’t directly related to DDD concepts, it’s essential for ensuring that the domain logic can operate independently of specific database choices. If you’re interested, feel free to delve deeper into the database implementation details.
虽然数据库的实现与 DDD 概念没有直接关系,但它对于确保域逻辑可以独立于特定数据库选择进行操作至关重要。如果您有兴趣,请随意深入研究数据库实现细节。

use std::{collections::HashMap,sync::{Arc, RwLock},
};#[derive(Clone, Debug)]
pub struct Db {db: Arc<RwLock<HashMap<String, String>>>,
}impl Db {pub fn new() -> Self {Self {db: Arc::new(RwLock::new(HashMap::new())),}}pub fn get<D, K>(&self, key: K) -> anyhow::Result<Option<D>>whereK: AsRef<str>,D: serde::de::DeserializeOwned,{let db = self.db.read().map_err(|e| anyhow::anyhow!("Error reading from database: {:?}", e))?;match db.get(key.as_ref()) {Some(value) => {let deserialized_value = serde_json::from_str(value).map_err(|e| anyhow::anyhow!("Error deserializing value: {:?}", e))?;Ok(Some(deserialized_value))}None => Ok(None),}}pub fn keys(&self) -> Vec<String> {let db = self.db.read().expect("read data from db");db.keys().cloned().collect()}pub fn remove<K>(&self, key: K) -> anyhow::Result<()>whereK: AsRef<str>,{let mut db = self.db.write().map_err(|e| anyhow::anyhow!("Error writing to database: {:?}", e))?;db.remove(key.as_ref()).ok_or_else(|| anyhow::anyhow!("Key not found in database"))?;Ok(())}pub fn set<S, K>(&self, key: K, value: &S) -> anyhow::Result<()>whereK: Into<String>,S: serde::ser::Serialize,{let value = serde_json::to_string(value)?;let mut db = self.db.write().map_err(|e| anyhow::anyhow!("Error writing to database: {:?}", e))?;db.insert(key.into(), value);Ok(())}
}

Application Layer 应用层

In the application layer, we use entities and value objects to achieve use cases and delegate processing to repositories (infrastructure layer). We request processing from repositories without directly depending on the infrastructure layer. This is known as the Dependency Inversion Principle, where we rely on abstractions rather than concrete implementations to realize use cases. In this context, we’ll implement the use case of creating a circle.
在应用程序层,我们使用实体和值对象来实现用例并将处理委托给存储库(基础设施层)。我们请求存储库进行处理,而不直接依赖于基础设施层。这称为依赖倒置原则,我们依靠抽象而不是具体实现来实现用例。在这种情况下,我们将实现创建圆圈的用例。

use anyhow::Result;
use serde::Deserialize;use crate::domain::{aggregate::{circle::Circle,member::Member,value_object::{grade::Grade, major::Major},},interface::circle_repository_interface::CircleRepositoryInterface,
};#[derive(Debug, Deserialize)]
pub struct CreateCircleInput {pub circle_name: String,pub capacity: usize,pub owner_name: String,pub owner_age: usize,pub owner_grade: usize,pub owner_major: String,
}impl CreateCircleInput {pub fn new(circle_name: String,capacity: usize,owner_name: String,owner_age: usize,owner_grade: usize,owner_major: String,) -> Self {CreateCircleInput {circle_name,capacity,owner_name,owner_age,owner_grade,owner_major,}}
}#[derive(Debug, Deserialize)]
pub struct CreateCircleOutput {pub circle_id: usize,pub owner_id: usize,
}pub struct CreateCircleUsecase<T>
whereT: CircleRepositoryInterface,
{circle_repository: T,
}impl<T> CreateCircleUsecase<T>
whereT: CircleRepositoryInterface,
{pub fn new(circle_repository: T) -> Self {CreateCircleUsecase { circle_repository }}pub fn execute(&mut self,circle_circle_input: CreateCircleInput,) -> Result<CreateCircleOutput> {let grade = Grade::try_from(circle_circle_input.owner_grade)?;let major = Major::from(circle_circle_input.owner_major.as_str());let owner = Member::new(circle_circle_input.owner_name,circle_circle_input.owner_age,grade,major,);let owner_id = owner.id;let circle = Circle::new(circle_circle_input.circle_name,owner,circle_circle_input.capacity,)?;self.circle_repository.create(&circle).map(|_| CreateCircleOutput {circle_id: usize::from(circle.id),owner_id: usize::from(owner_id),})}
}

Use Case I/O (Input/Output): We define the I/O of the use case as CreateCircleInput and CreateCircleOutput.
用例 I/O(输入/输出):我们将用例的 I/O 定义为 CreateCircleInput 和 CreateCircleOutput 。

CreateCircleUsecase Struct: CreateCircleUsecase is a generic struct that receives something implementing the CircleRepositoryInterface trait as its field.
CreateCircleUsecase 结构: CreateCircleUsecase 是一个通用结构,它接收实现 CircleRepositoryInterface 特征的内容作为其字段。

Impl CreateCircleUsecase: We implement two methods, new and execute, for the CreateCircleUsecase struct.
Impl CreateCircleUsecase:我们为 CreateCircleUsecase 结构实现两个方法, new 和 execute 。

  • The new Method: This method generates instances and receives something implementing the CircleRepositoryInterface trait, akin to a constructor in other languages. Here, we inject dependencies using an abstraction (trait) instead of a concrete implementation, adhering to the Dependency Inversion Principle.
    new 方法:此方法生成实例并接收实现 CircleRepositoryInterface 特征的内容,类似于其他语言中的构造函数。在这里,我们使用抽象(特征)而不是具体实现来注入依赖项,遵循依赖倒置原则。
  • The execute Method: This method executes the use case. It takes CreateCircleInput, creates a Circle Entity, and saves it to the repository. self refers to the instance of CreateCircleUsecase, allowing us to access the field with self.circle_repository as it implements the CircleRepositoryInterface trait, enabling the create method call.
    execute 方法:该方法执行用例。它采用 CreateCircleInput ,创建一个 Circle 实体,并将其保存到存储库中。 self 引用 CreateCircleUsecase 的实例,允许我们使用 self.circle_repository 访问该字段,因为它实现了 CircleRepositoryInterface 特征,从而启用 < b6>方法调用。

This setup ensures that the use case depends solely on the domain and avoids dependencies on the infrastructure layer.
此设置可确保用例仅依赖于域,并避免依赖于基础设施层。

Presentation Layer 表示层

In the Presentation Layer, we define endpoints, handle request reception, manage responses, and map values to pass to the Application Layer.
在表示层中,我们定义端点、处理请求接收、管理响应以及映射值以传递到应用程序层。

#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct CreateCircleRequestBody {pub circle_name: String,pub capacity: usize,pub owner_name: String,pub owner_age: usize,pub owner_grade: usize,pub owner_major: String,
}impl std::convert::From<CreateCircleRequestBody> for CreateCircleInput {fn from(CreateCircleRequestBody {circle_name,capacity,owner_name,owner_age,owner_grade,owner_major,}: CreateCircleRequestBody,) -> Self {CreateCircleInput::new(circle_name,capacity,owner_name,owner_age,owner_grade,owner_major,)}
}#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct CreateCircleResponseBody {pub circle_id: usize,pub owner_id: usize,
}impl std::convert::From<CreateCircleOutput> for CreateCircleResponseBody {fn from(CreateCircleOutput {circle_id,owner_id,}: CreateCircleOutput,) -> Self {CreateCircleResponseBody {circle_id,owner_id,}}
}pub async fn handle_create_circle(State(state): State<AppState>,Json(body): Json<CreateCircleRequestBody>,
) -> Result<Json<CreateCircleResponseBody>, String> {let circle_circle_input = CreateCircleInput::from(body);let mut usecase = CreateCircleUsecase::new(state.circle_repository);usecase.execute(circle_circle_input).map(CreateCircleResponseBody::from).map(Json).map_err(|e| e.to_string())
}#[derive(Clone)]
struct AppState {circle_repository: CircleRepository,
}fn router() -> Router<AppState> {Router::new().route("/circle", post(handle_create_circle))
}#[tokio::main]
async fn main() -> Result<(), ()> {let state = AppState {circle_repository: CircleRepository::new(),};let app = router().with_state(state);let listener = tokio::net::TcpListener::bind("127.0.0.1:3000").await.unwrap();println!("Listening on: {}", listener.local_addr().unwrap());axum::serve(listener, app).await.unwrap();Ok(())
}

Input/Output (I/O): CreateCircleRequestBody is a struct used to receive the request body. We implement From for CreateCircleRequestBody to construct CreateCircleInput. The response is implemented similarly.
输入/输出(I/O): CreateCircleRequestBody 是用于接收请求正文的结构体。我们为 CreateCircleRequestBody 实现 From 来构造 CreateCircleInput 。响应的实现类似。

AppState: AppState represents the application's state, provided by axum. It holds the repository and acts as a Dependency Injection (DI) container for resolving dependencies. While there's only one dependency in this case, it provides flexibility such as swapping CircleRepository with a mock like CircleRepositoryMock for testing purposes.
AppState: AppState 代表应用程序的状态,由axum提供。它保存存储库并充当用于解决依赖关系的依赖注入 (DI) 容器。虽然在这种情况下只有一个依赖项,但它提供了灵活性,例如将 CircleRepository 与 CircleRepositoryMock 等模拟交换以进行测试。

handle_create_circle: This function receives the request and executes CreateCircleUsecase. The State(state) in the first argument retrieves the value from AppState, which is then injected into CreateCircleUsecase to call the execute method, as mentioned earlier. I/O is converted to desired formats accordingly.
handle_create_circle :该函数接收请求并执行 CreateCircleUsecase 。第一个参数中的 State(state) 从 AppState 检索值,然后将其注入到 CreateCircleUsecase 中以调用 execute 方法,如上所述早些时候。 I/O 相应地转换为所需的格式。

I tried implementing an API server using Rust, Axum, and Domain-Driven Design (DDD). While Rust adoption in business is still relatively low, I believe it will gain traction, especially in backend development. I encourage you to give it a try. Feel free to contribute by adding new use cases or aggregates to further aid learning.
我尝试使用 Rust、Axum 和领域驱动设计 (DDD) 来实现 API 服务器。虽然 Rust 在商业中的采用率仍然相对较低,但我相信它将获得牵引力,尤其是在后端开发方面。我鼓励你尝试一下。请随意添加新的用例或聚合来进一步帮助学习。


您可以在此处找到该项目的源代码。

learning-rust/src/hello.rs at io-uring · amacal/learning-rust · GitHub

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/819348.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何查看docker配置的镜像仓库

在Docker中&#xff0c;想要查看配置的镜像仓库&#xff08;注册表&#xff09;地址&#xff0c;可以通过查询Docker守护进程的配置来实现。在Linux系统中&#xff0c;Docker守护进程的配置文件通常位于 /etc/docker/daemon.json。该文件中可能会包含registry-mirrors配置项&am…

【Delphi 爬虫库 2】使用封装好的 JSON 解析库对 JSON 数据进行解析

当我们使用GET或POST方法获取到JSON数据时&#xff0c;我们需要对其进行解析。倘若我们使用现有的JSON解析库&#xff0c;这一过程将变得异常简单。在前文中&#xff0c;我们获得了一个翻译结果的JSON数据&#xff0c;具体如下所示 {“sessionUuid”:“translate_uuid171308666…

贪心算法先导

贪心算法其实就是没有什么规律可言&#xff0c;所以大家了解贪心算法 就了解它没有规律的本质就够了。 不用花心思去研究其规律&#xff0c; 没有思路就立刻看题解。 基本贪心的题目 有两个极端&#xff0c;要不就是特简单&#xff0c;要不就是死活想不出来。 学完贪心之后再…

算法训练营第41天|LeetCode 343.整数拆分 96.不同的二叉搜索树

LeetCode 343.整数拆分 题目链接&#xff1a; LeetCode 343.整数拆分 解题思路&#xff1a; 递推公式&#xff1a;一个数n是由1到n-1和另一个数的分解而来的&#xff0c;而另一个数是由更小的数分解而来的。 代码&#xff1a; class Solution { public:int integerBreak(…

Springboot+Vue项目-基于Java+MySQL的校园周边美食探索及分享平台系统(附源码+演示视频+LW)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &…

SU-03T语音识别

语音识别的由SU-03T、咪头、喇叭、还有一个CH340串口组成。SU-03T不需要代码的写入&#xff0c;直接可以进行配置就可以使用&#xff0c;极大降低了开发难度。 为客户提供超低成本的离线语 音识别方案&#xff0c;可广泛且快速应用于智能家居&#xff0c;各类智能小家电&#x…

【BlueDroid】【BLE】开关广播\扫描\配对\连接分析

1. 精讲蓝牙协议栈&#xff08;Bluetooth Stack&#xff09;&#xff1a;SPP/A2DP/AVRCP/HFP/PBAP/IAP2/HID/MAP/OPP/PAN/GATTC/GATTS/HOGP等协议理论 2. 欢迎大家关注和订阅&#xff0c;【精讲蓝牙协议栈】和【Android Bluetooth Stack】专栏会持续更新中.....敬请期待&#x…

如何实现YOLOv8保存目标检测后的视频文件

首先安装所需的库和依赖项&#xff0c;确保你已经安装了OpenCV和YOLOv8的相关库和依赖项。你可以使用pip或conda来安装它们。 其次加载YOLOv8模型&#xff0c;使用YOLOv8的训练权重文件和配置文件&#xff0c;加载模型并进行初始化。这可以通过使用适当的库函数来完成&…

Java面试题:描述Java 17中的密封接口及其用途

Java 17是继Java 11和Java 16之后的又一个长期支持&#xff08;LTS&#xff09;版本&#xff0c;它于2021年9月发布。在Java 17中&#xff0c;一个重要的新特性是密封接口&#xff08;Sealed Interfaces&#xff09;&#xff0c;这是对Java接口的增强&#xff0c;它允许接口有更…

【Git】Git的安装与常用命令

Git的安装与常用命令 一、Git的安装 &#xff08;一&#xff09;下载 官网下载&#xff1a;https://git-scm.com/downloads 镜像网站&#xff1a;https://registry.npmmirror.com/binary.html?pathgit-for-windows/ &#xff08;二&#xff09;安装 双击安装&#xff0c…

Elasticsearch分布式搜索

实用篇-ES-环境搭建 ES是elasticsearch的简称。我在SpringBoot学习 数据层解决方案 的时候&#xff0c;写过一次ES笔记&#xff0c;可以结合一起看一下。 之前在SpringBoot里面写的相关ES笔记是基于Windows的&#xff0c;现在我们是基于docker容器来使用&#xff0c;需要你们提…

安装jmeter和ant

安装jmeter和ant 安装java环境 安装jdk和jre 下载Java SE Development Kit 8 Java SE subscribers will receive JDK 8 updates until at least December 2030. 选择指定包进行安装&#xff0c;如windows 共享账号参考&#xff1a;Oracle官网 账号及密码 目前官网下载低…

springboot接口提高查询速度方法

接口想要提高查询速度&#xff0c;需要减少查询数据库的次数&#xff0c;需要把循环里面的查询提出来一次性查询完毕&#xff0c;然后通过java代码来获取响应的值。如下所示&#xff1a; List<OrderInfoHtVO> orderInfoList orderInfoService.getOrderInfoHtlist(query…

K12智慧校园-学工中心

1 系统概述 学工管理系统用于帮助学校学工部门负责拟定学院年度学生工作计划&#xff0c;提出年度学生工作思路及工作要点&#xff0c;并负责指导各系开展学生工作&#xff1b;负责学院的学风建设与校园文明督查&#xff1b;负责新生军训工作的组织、协调和安排&#xff1b;负…

顺序表 (头删 尾删 清空)

//头删 | 1 #include "head.h" | 1 #ifndef ww87 void head_del(p lp) | 2 int main(int argc, const char *argv[]) …

js纯前端实现语音播报,朗读功能(2024-04-15)

实现语音播报要有两个原生API 分别是【window.speechSynthesis】【SpeechSynthesisUtterance】 项目代码 // 执行函数 initVoice({text: 项目介绍,vol: 1,rate: 1 })// 函数 export function initVoice(config) {window.speechSynthesis.cancel();//播报前建议调用取消的函数…

[阅读笔记2][FLAN]FINETUNED LANGUAGE MODELS ARE ZERO-SHOT LEARNERS

接下来这篇是谷歌的FLAN&#xff0c;提出了指令微调这一新范式&#xff0c;在2022年发表。 这篇论文指出GPT3的zero-shot性能相比few-shot性能差太多了。他们发现如果对预训练模型进行指令微调能使zero-shot性能显著提升&#xff0c;下面右图显示指令微调后zero-shot比GPT3 few…

【zml】vp9 vp8

目录 问题 方案 知识点 研究过程 源码编译的可能 问题 所有 的机型 中&#xff0c;就海思芯片的有这个问题。应该是它的h264的编解码 问题&#xff0c;所以目前是让它以vp9在推流就没有问题。 但zlm对于vp9的录相是没有实现的。 所以目前现状是海思芯片的&#xff0c;就…

Ubuntu 22.04安装中文输入法

1. 安装 sudo apt install fcitx5 2. 管理已安装的语言 Setting->Region & Language->Manage Installed Language 在下图中点击“安装”&#xff0c;之后需要等一会 选择Fcitx 5 3. 添加输入法 Setting->Keyboard 点击chinese 选择你想要的输入法 重启一下&a…

安卓选择器

一、首先引入依赖库 //时间选择器implementation io.github.ShawnLin013:number-picker:2.4.13 二、自定义时间选择器 public class TimePickerCustom {private final BottomSheetDialog bottomDialog;private final NumberPicker year;private final NumberPicker month;pr…