一文掌握fastapi微服务开发

news2024/11/19 3:18:16

目录

一、概述

1.1 微服务

1.1.1 微服务的优势

1.1.2 微服务的缺点

1.2 为何使用Python开发微服务

1.3 FastAPI概述

二、开发

2.1 安装FastAPI

2.1.1 安装虚拟环境

2.1.2 创建虚拟环境

2.1.3 激活虚拟环境

2.1.4 安装FastAPI

2.2 FastAPI简单使用

2.2.1 查询

2.2.2 添加

2.2.3 修改

2.2.4 删除

2.3 代码组织

2.4 使用PostgreSQL数据库

2.4.1 安装PostgreSQL数据库

2.4.2 在FastAPI中连接PostgreSQL数据库

2.5 微服务中的数据管理

三、小结


一、概述

1.1 微服务

如果你是一名Python Web开发人员,那么肯定听说过微服务这个名词,并且希望通过Python来构建微服务。那么到底什么是微服务呢?

微服务(Microservice)是一种构建高可伸缩应用程序的架构,是一种将大型单一应用程序分解为专门针对特定服务、功能的单个应用程序的方法。举例来说,假如我们需要给自己的家进行装修,我们以前的做法就是找一家全包的装修公司将家里的水电、门窗、家具等全部交给这家装修公司,这家装修公司跟我们签订合同以后就统筹来安排所有的装修细节,我们后续的对接直接跟这家装修公司沟通就行了。装修时,所有的材料费都由这家装修公司统一采购和支出。这种方式在web开发领域就是典型的单片体系结构,每个业务逻辑(门窗装修、水电装修、家具装修)都驻留在同一个应用程序中(同一家装修公司负责),并且使用相同的数据库(所有装修的采购和财务由公司统一管理)。微服务架构则与这种单片体系结构不同,在微服务体系结构中,应用程序被分解为几个独立的服务,这些服务在不同的进程中运行。同样以装修为例,如果我们对这家公司的整体装修水平是认同的,但是,对他们打的家具不满意,我们想用别家专门做家具的来给我们打一套家具,那么我们就可以把打家具这项装修任务摘出去,交给另一家专门做家具的公司。很显然,我们完全可以将各个装修子任务都采用这种方式交给专门的装修公司。水电的交给专门做水电的、家具的交给专门做家具的、门窗的交给专门做门窗的。这种“细分”的方式能够让我们得到更满意的装修成果,毕竟术业有专攻,这种方式最终的性价比会更高。但是有个问题,全部任务分散以后我们必须规划好各个任务的进度和接口对接。例如,对于装修来说,我们一般是先装修水电再装修门窗家具,这里就有一个时间调度的安排。另外,水电的一些接口位置也必须提前规划好,因为后面设计门窗时也要考虑水电的位置安排。总之,这些各个分散的独立服务之间需要有效“沟通”才能真正发挥微服务的作用。微服务对于应用程序的不同功能有一个不同的数据库,根据每个服务的性质,微服务使用HTTP、AMQP或类似TCP的二进制协议相互通信,也可以使用RabbitMQ、Kafka或Redis等消息队列执行服务间通信。

1.1.1 微服务的优势

  • 松耦合

松散耦合的应用程序意味着可以使用最适合它们的技术来构建不同的服务。因此,开发团队可以根据每个子服务最适合的技术来开发。

  • 易控制

术业有专攻,微服务可以使整个应用程序更容易理解和控制。

  • 易扩展

使用微服务可以使应用程序扩展变得更容易,因为如果其中一个服务需要高GPU使用率,那么只有包含该服务的服务器需要高GPU,而其他服务器可以在普通服务器上运行。这一点在当今的人工智能时代尤其重要,因为我们经常需要部署一些基于AI这种高性能计算的微服务。

1.1.2 微服务的缺点

微服务并不是万能的,它也有一些不足。

  • 数据库同步

由于不同的服务使用不同的数据库,因此涉及多个微服务的数据库事务提交需要保证一致性。

  • 微服务拆分

在第一次尝试时很难实现服务的完美分割,到底哪些功能应该单独拆出来组成一个微服务才是最佳的,这些都需要迭代测试。

  • 通信速度

由于微服务之间使用网络交互进行通信,这使得应用程序容易因为网络延迟和服务速度慢而变慢。

1.2 为何使用Python开发微服务

Python是构建微服务的完美工具,因为它具有强大的社区、易于学习的曲线和大量的第三方库。

Python是一种面向对象的、解释型的、通用的、开源的脚本编程语言,它之所以非常流行,主要有三点原因:

  • Python 简单易用,学习成本低,看起来非常优雅干净;
  • Python 标准库和第三库众多,功能强大,既可以开发小工具,也可以开发企业级应用;
  • Python 站在了人工智能和大数据的风口上,流行度广。

举个简单的例子来说明一下 Python 的简单。比如要实现某个功能,C语言可能需要 100 行代码,而 Python 可能只需要几行代码,因为C语言什么都要得从头开始编写,而 Python 已经内置了很多常见功能,我们只需要导入包,然后调用一个函数即可。简单就是 Python 的巨大魅力之一,是它的杀手锏。正因为python的简洁,很多著名的web应用也开始陆续的采用python进行开发,例如豆瓣和知乎。但是,Python也有缺陷,就是它的速度相比其它语言要慢。虽然Python的速度问题一直被人诟病,但是由于Python引入了异步编程,因此近来出现了性能与GO和Node.js同等的web框架:Fastapi。

1.3 FastAPI概述

FastAPI是近几年基于Python推出的一款高性能web框架,使用Python 3.6+并基于标准的Python进行构建。它的优势如下:

  • 快速:可与NodeJSGo比肩的极高性能(归功于 Starlette 和 Pydantic),使其超越django和flask成为最快的python web框架之一。

  • 高效编码:提高功能开发速度约200%至300%。

  • 更少 bug:减少约40%的人为(开发者)错误。
  • 智能:极佳的编辑器支持。处处皆可自动补全,减少调试时间。
  • 简单:设计的易于使用和学习。
  • 简短:使代码重复最小化。
  • 健壮:生产可用级别的代码。还有自动生成的交互式文档。

具体可参考FastAPI官方文档。

本教程将使用python web框架FastAPI来构建一个微服务应用。相关环境说明如下所示:

编程语言:Python 3.6.1

操作系统:Windows 7

二、开发

2.1 安装FastAPI

2.1.1 安装虚拟环境

虚拟环境是Python解释器的一个副本环境,在这个环境中可以安装其它第三方Python包,在虚拟环境中安装的Python包不会影响全局环境中的包。打个比方,如果我同时接手两个团队的活:团队A和团队B,A团队的项目依赖了人工智能的tensorflow库(非常流行的深度学习库),B团队的项目也依赖了tensorflow,但是不巧的是A团队使用的是tensorflow 1版本,而B团队使用的是tensorflow 2版本,两个版本的兼容性非常差,那么两个项目同时在我自己的电脑上操作我该安装哪个库呢。假设两个库能同时安装在电脑上,具体执行的时候Python解释器也不知道到底该调用哪个tensorflow库。有没有什么办法把两个库在同一台电脑上隔离开,需要用哪个库就切换到哪个,这样,我就可以在一台电脑上同时开发两个不同环境的项目了。这个解决办法就是虚拟环境。

简单理解,虚拟环境就像一个容器,在某个虚拟环境中安装的python包可以独立于其它环境。

Windows平台下的虚拟环境需要使用第三方工具virtualenv来创建,打开命令终端,输入下面的命令即可完成安装:

pip install virtualenv

安装完成后检查是否成功安装,继续输入命令如下:

virtualenv --version

效果如下图所示:

此时会输出virtualenv的版本号,上面的版本号为20.4.2。

2.1.2 创建虚拟环境

现在我们假定有一个项目工程,该工程位于文件夹micropro中,我们现在的目标就是在这个micropro项目中创建python虚拟环境,这个虚拟环境是专门为micropro项目成立的。在命令行终端中通过cd命令进入项目目录中(假设micropro文件夹位于D盘目录下),如下图所示:

输入下面的命令用来创建名为venv的虚拟环境:

virtualenv venv

成功效果图如下所示:

这样我们就在micrppro文件夹下有了一个名为venv的子文件夹,它保存一个全新的虚拟环境,其中有一个私有的Python解释器位于micropro/venv/Scripts,在该虚拟环境中安装的python包会存放在micropro/venv/Lib路径下。

2.1.3 激活虚拟环境

Windows平台下激活虚拟环境的命令如下:

venv\Scripts\activate

成功激活之后,虚拟环境解释器的路径就被加入PATH中,但这种改变不是永久的,他只会影响当前的命令行终端。为了提醒用户已经激活了虚拟环境,如下图所示,会修改命令行的提示符,加入环境名:

像上述这种前面带()的命令行就说明已经在虚拟环境中了。

2.1.4 安装FastAPI

在虚拟环境中,我们使用下面的命令安装FastAPI:

pip install fastapi

由于FastAPI没有内置web服务,所以需要安装uvicorn才能运行web应用。uvicorn是一个ASGI服务器,它允许我们使用异步特性。

使用以下命令安装uvicorn:

pip install uvicorn

2.2 FastAPI简单使用

2.2.1 查询

本小节先简单介绍和熟悉下FastAPI的基本使用方法。

在新创建的micropro目录中创建一个新目录app和一个新文件main.py,然后在main.py中添加以下代码:

from fastapi import FastAPI

app = FastAPI()


@app.get('/')
async def index():
    return {"Real": "Python"}

上述代码首先导入并实例化FastAPI,然后注册根网址/,然后返回JSON。

我们可以使用uvicorn运行应用程序服务器:

uvicorn app.main:app

这里app.main表示使用app目录中的main.py文件,:app表示程序中定义的FastAPI实例名称。

成功启动后效果如下:

然后我们可以在浏览器中访问 http://127.0.0.1:8000,最终效果如下:

我们也可以访问http://127.0.0.1:8000/docs 来查看当前的所有访问路由并进行测试:

接下来,我们再深入扩展一下,在脚本中定义一个电影类Movie,代码如下所示:

from fastapi import FastAPI
from pydantic import BaseModel
from typing import List

app = FastAPI()
fake_movie_db = [{
    'name': 'Star Wars: Episode IX - The Rise of Skywalker',  #电影名
    'genres': ['Action', 'Adventure', 'Fantasy'], #题材
    'casts': ['Daisy Ridley', 'Adam Driver'] # 演员阵容
}]


class Movie(BaseModel):
    name: str
    genres: List[str]
    casts: List[str]


@app.get('/', response_model=List[Movie])
async def index():
    return fake_movie_db

上述代码中我们创建了一个新的类电影Movie,它从pydantic扩展了BaseModel。电影模型包含名称、题材和演员阵容。Pydantic内置了FastAPI,使得定义模型并对模型进行请求验证变得容易。我们在定义路由时额外使用了response_model=List[Movie],表明我们想要返回类Movie对应的实例对象列表,这里我们返回fake_movie_db。

我们重新运行服务器,然后访问 http://127.0.0.1:8000/docs ,可以看到如下效果:

我们可以看到示例响应部分中已经生成了电影模型的字段。

2.2.2 添加

接下来,我们添加路由,可以将电影添加到电影列表中。添加新的路由定义来处理POST请求:

@app.post('/', status_code=201)
async def add_movie(payload: Movie):
    movie = payload.dict()
    fake_movie_db.append(movie)
    return {'id': len(fake_movie_db) - 1}

重新启动服务器然后测试这个新的API。

我们先访问http://127.0.0.1:8000/docs,然后测试一下刚添加的api,输入数据如下图所示:

可以看到,系统成功返回了信息。我们成功的添加了一条电影信息。我们重新访问http://127.0.0.1:8000,查询效果如下图所示:

可以看到,我们刚添加的信息已经成功返回了。

2.2.3 修改

接下来,我们希望能够通过请求来修改电影列表中的数据,添加代码如下:

from fastapi import HTTPException 

@app.put('/{id}')
async def update_movie(id: int, payload: Movie):
    movie = payload.dict()
    movies_length = len(fake_movie_db)
    if 0 <= id < movies_length:
        fake_movie_db[id] = movie
        return None
    raise HTTPException(status_code=404, detail="Movie with given id not found")

上述代码中的id是我们的电影列表中某部电影的索引。我们重新启动服务器,来看下效果。提交数据如下所示:

然后我们重新访问http://127.0.0.1:8000,可以看到效果如下:

我们看到信息已经被修改了过来。

2.2.4 删除

最后我们定义删除接口,添加代码如下:

@app.delete('/{id}')
async def delete_movie(id: int):
    movies_length = len(fake_movie_db)
    if 0 <= id < movies_length:
        del fake_movie_db[id]
        return None
    raise HTTPException(status_code=404, detail="Movie with given id not found")

然后我们重新启动服务器,首先使用前面的Post接口添加3条数据,效果如下:

然后使用刚添加的删除接口,首先删除id=0的电影:

重新访问http://127.0.0.1:8000,效果如下:

可以看到,第一部电影已经删除掉了。

2.3 代码组织

通过2.2节我们熟悉了FastAPI基本的增删查改操作,前面我们的代码都是在一个脚本里编写的,这对于大型项目来说是不合适的,我们需要重新组织代码结构,使其更符合大型项目开发需求。

紧接着2.2节的内容。在应用程序中创建新文件夹api,并在api文件夹中创建新的movies.py。将所有与路由相关的代码从main.py移动到movies.py。调整后的movies.py如下所示:

#~/micropro/app/api/movies.py

from typing import List
from fastapi import Header, APIRouter
from fastapi import HTTPException

from app.api.models import Movie

fake_movie_db = [
    {
        'name': 'Star Wars: Episode IX - The Rise of Skywalker',
        'plot': 'The surviving members of the resistance face the First Order once again.',
        'genres': ['Action', 'Adventure', 'Fantasy'],
        'casts': ['Daisy Ridley', 'Adam Driver']
    }
]

movies = APIRouter()

@movies.get('/', response_model=List[Movie])
async def index():
    return fake_movie_db

@movies.post('/', status_code=201)
async def add_movie(payload: Movie):
    movie = payload.dict()
    fake_movie_db.append(movie)
    return {'id': len(fake_movie_db) - 1}

@movies.put('/{id}')
async def update_movie(id: int, payload: Movie):
    movie = payload.dict()
    movies_length = len(fake_movie_db)
    if 0 <= id < movies_length:
        fake_movie_db[id] = movie
        return None
    raise HTTPException(status_code=404, detail="Movie with given id not found")

@movies.delete('/{id}')
async def delete_movie(id: int):
    movies_length = len(fake_movie_db)
    if 0 <= id < movies_length:
        del fake_movie_db[id]
        return None
    raise HTTPException(status_code=404, detail="Movie with given id not found")

上述代码我们使用FastAPI中的APIRouter来注册新的API路由。另外,在api中创建一个新文件models.py,我们将在这个文件中保存我们定义的Pydantic模型。

接下来在main.py文件中注册这些新的路由:

#~/micropro/app/main.py
from fastapi import FastAPI
from app.api.movies import movies

app = FastAPI()
app.include_router(movies)

最终我们的应用结构如下:

最后,我们重新启动,查看下是否各项功能都正常。

2.4 使用PostgreSQL数据库

前面的案例中为了简单我们使用假的Python list来添加电影,在本小节我们将使用更实际的数据库来实现这个功能。本小节我们将使用PostgreSQL数据库。

2.4.1 安装PostgreSQL数据库

PostgreSQL是一个功能强大的开源对象关系型数据库系统,他使用和扩展了SQL语言,并结合了许多安全存储和扩展最复杂数据工作负载的功能。PostgreSQL的起源可以追溯到1986年,作为加州大学伯克利分校POSTGRES项目的一部分,并且在核心平台上进行了30多年的积极开发。PostgreSql提供了许多功能,旨在帮助开发人员构建应用程序,管理员保护数据完整性并且构建容错环境,并帮助你管理数据,无论数据集的大小。除了免费和开源之外,Postgre SQL还具有高度的可扩展性。 

进入下载页面:PostgreSQL: Downloads,由于我们使用windows系统,因此选择Windows对应的版本下载。下载完成后按照提示进行安装即可。安装完成后我们从开始菜单中找到pgAdmin4,这是PostgreSQL对应的数据库管理软件。

输入密码登陆pgAdmin4以后,我们创建一个名为movie_db数据库用于我们项目的数据管理。如下图所示:

最终效果如下:

2.4.2 在FastAPI中连接PostgreSQL数据库

为了能够在python中连接PostgreSQL数据库,我们需要依赖第三方库,使用下面的命令安装所需的库:

pip install 'databases[postgresql]'
pip install psycopg2

上述命令将同时安装sqlalchemy和asyncpg,这是后面使用PostgreSQL所必需的。

在api中创建一个新文件并将其命名为db.py,这个文件将包含REST API的实际数据库模型:

from sqlalchemy import (Column, Integer, MetaData, String, Table,
                        create_engine, ARRAY)

from databases import Database

DATABASE_URL = 'postgresql://movie_user:movie_password@localhost/movie_db'

engine = create_engine(DATABASE_URL)
metadata = MetaData()

movies = Table(
    'movies',
    metadata,
    Column('id', Integer, primary_key=True),
    Column('name', String(50)),
    Column('genres', ARRAY(String)),
    Column('casts', ARRAY(String))
)

database = Database(DATABASE_URL)

这里,DATABASE_URL是用于连接到PostgreSQL数据库的URL。这里movie_user是数据库用户的名称,movie_password是数据库用户的密码,movie_db是数据库的名称。默认的用户名为postgres,密码即为安装PostgreSQL时设置的密码。上述代码中创建了Movie对应的table。

更新main.py来连接数据库。main.py代码如下所示:

#~/micropro/app/main.py
from fastapi import FastAPI
from app.api.movies import movies
from app.api.db import metadata, database, engine

metadata.create_all(engine)

app = FastAPI()

@app.on_event("startup")
async def startup():
    await database.connect()

@app.on_event("shutdown")
async def shutdown():
    await database.disconnect()


app.include_router(movies)

FastAPI提供了一些事件处理程序,可以在应用程序启动时使用这些处理程序连接到我们的数据库,并在应用程序关闭时断开连接。

接下来我们更新movies.py文件,使其使用数据库而不是Python列表:

#~/micropro/app/api/movies.py

from typing import List
from fastapi import Header, APIRouter,HTTPException

from app.api.models import MovieIn, MovieOut
from app.api import db_manager

movies = APIRouter()

@movies.get('/', response_model=List[MovieOut])
async def index():
    return await db_manager.get_all_movies()

@movies.post('/', status_code=201)
async def add_movie(payload: MovieIn):
    movie_id = await db_manager.add_movie(payload)
    response = {
        'id': movie_id,
        **payload.dict()
    }
    return response


@movies.put('/{id}')
async def update_movie(id: int, payload: MovieIn):
    movie = await db_manager.get_movie(id)
    if not movie:
        raise HTTPException(status_code=404, detail="Movie not found")

    update_data = payload.dict(exclude_unset=True)
    movie_in_db = MovieIn(**movie)

    updated_movie = movie_in_db.copy(update=update_data)

    return await db_manager.update_movie(id, updated_movie)

@movies.delete('/{id}')
async def delete_movie(id: int):
    movie = await db_manager.get_movie(id)
    if not movie:
        raise HTTPException(status_code=404, detail="Movie not found")
    return await db_manager.delete_movie(id)

接下来添加db_manager.py文件用于操作数据库:

from app.api.models import MovieIn, MovieOut, MovieUpdate
from app.api.db import movies, database


async def add_movie(payload: MovieIn):
    query = movies.insert().values(**payload.dict())

    return await database.execute(query=query)

async def get_all_movies():
    query = movies.select()
    return await database.fetch_all(query=query)

async def get_movie(id):
    query = movies.select(movies.c.id==id)
    return await database.fetch_one(query=query)

async def delete_movie(id: int):
    query = movies.delete().where(movies.c.id==id)
    return await database.execute(query=query)

async def update_movie(id: int, payload: MovieIn):
    query = (
        movies
        .update()
        .where(movies.c.id == id)
        .values(**payload.dict())
    )
    return await database.execute(query=query)

最后更新models.py,以便可以将Pydantic模型与sqlalchemy表一起使用:

#~/micropro/api/models.py

from pydantic import BaseModel
from typing import List, Optional

class MovieIn(BaseModel):
    name: str
    genres: List[str]
    casts: List[str]


class MovieOut(MovieIn):
    id: int


class MovieUpdate(MovieIn):
    name: Optional[str] = None
    genres: Optional[List[str]] = None
    casts: Optional[List[str]] = None

这里MovieIn是用于将电影添加到数据库的基础类。当我们从数据库取出数据时我们需要使用MovieOut类。MovieUpdate类允许我们将模型中的值设置为可选的,以便在更新电影数据时只更新需要更新的字段。

最后,我们启动服务器,然后按照之前的方法添加数据,最后我们在pdAdmin4中查看有没有添加成功,效果如下图所示:

2.5 微服务中的数据管理

微服务中的数据库管理是一个难点。在构建整个应用时如何拆分数据?如何构建不同微服务的数据库?哪些数据是各个微服务共享的?这些在开发前都需要先仔细的考虑好。本小节给出一些基本的方案和优缺点介绍:

  • 一个微服务管理一个独立的数据库

如果希望微服务尽可能松耦合,那么为每个微服务提供一个独立的数据库是比较好的方案。每个微服务有一个不同的数据库允许我们独立地扩展不同的服务。涉及多个数据库的数据操作可以通过微服务之间良好的api实现。这种方式的缺点就是实现涉及多个微服务的业务数据操作比较麻烦,另外,网络开销的增加使得这种方式使用效率降低。

  • 共享数据库

如果有很多数据操作涉及多个微服务,那么最好使用共享数据库。这带来了高度一致的应用程序的好处,但也丧失了微服务体系结构带来的大部分好处。在一个微服务上工作的开发人员需要与其他服务中的模式更改相协调。

  • API组合

在涉及多个数据库的操作中,API组合充当API网关,并按所需顺序执行对其他微服务的API调用。最后,每个微服务的结果在内存中执行连接后返回给客户机。这种方法的缺点是在内存中进行这种大型数据的链接效率比较低。

总之,到底该选用哪种数据拆分方式并没有一个绝对完美的指导方案,需要根据项目类型、项目复杂度等综合来考量,也需要在试错和实践中来改进。用好微服务架构能够给整个项目带来超强性能的效果。

三、小结

本文针对FastAPI微服务开发作了简单介绍,想要快速上手FastAPI的读者可以通过本文快速掌握FastAPI框架特性,能够依样“画葫芦”的将相关功能揉进自己的项目中去。除了数据库相关操作以外,FastAPI最有特色的功能就是集成机器学习和深度学习模型,完成线上AI推理,有兴趣的读者可以参考我的另一篇博文。

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

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

相关文章

java注解以及如何利用反射获取注解的属性值

一、什么是注解 1.Annotation是从JDK5.0开始引入的新技术 2.Annotation的作用 : &#xff08;1&#xff09;不是程序本身&#xff0c;可以对程序作出解释(这一点和注释(comment)没什么区别)&#xff08;2&#xff09;可以被其他程序(比如:编译器等)读取 3.Annotation的格式…

【每日一题】【LeetCode】【第十一天】杨辉三角

解决之路 题目描述 测试案例&#xff08;部分&#xff09; 第一次 杨辉三角感觉还是挺经典的代码题目&#xff1f;之前大一学C语言好像写过一次。 不过&#xff0c;自己当时就不会写&#xff0c;这次自己先试试能不能想出解决方案。 输入数字是几&#xff0c;那就要输出几…

亚马逊云科技帮助Gemsouls在云上快速实现技术验证与部署

元宇宙热度居高不下&#xff0c;它所创造的虚拟世界进一步拉近了人与人之间的距离&#xff0c;用数字化的形式消除地理与空间上的隔阂。而高度拟真化的虚拟人与AI虚拟社交&#xff0c;是元宇宙落地的重要领域&#xff0c;打造以人工智能驱动的虚拟人社交平台已成为行业大势。 …

DNS 的一些基础知识,以及 DNS 转换域名的过程

DNS(Domain Name System)&#xff0c;主要作用是将域名转成 IP&#xff0c;本文主要讲解了 DNS 的一些基础知识&#xff0c;以及 DNS 转换域名的过程。DNS 是什么dig命令绝大多数网络通信都是基于 TCP/IP 协议&#xff0c;而 TCP/IP 协议的基础是 IP&#xff0c;所以网络中的计…

获取未来时间 一年或N年

需求 展示从本月初开始 一年的时间 或N年的时间 以便用户选择思路 一年12个月 是已知的 从本月到12月可以生成本年的每天数据从1月至上月可以生成所需得到最后一年的数据今年加最后一年的月份可拼接一年时间 所以中间年份是所需年分-1的数组数据中间年份都是1-12月 可以通过年份…

软件的生命周期和测试的生命周期的区分

软件的生命周期测试的生命周期软件测试贯穿于软件的整个生命周期在需求分析阶段测试人员的活测试人员需要站在用户角度分析&#xff1a;软件需求是否合理&#xff1b;站在技术角度分析&#xff1a;技术上是否可行&#xff0c;还有没有优化的空间&#xff1b;站在测试角度分析&a…

VALL-E:微软全新语音合成模型可以在3秒内复制任何人的声音

近日&#xff0c;微软研究人员宣布了一种新的语音合成AI模型 VALL-E&#xff0c;给出3秒样音就可以精确地模拟一个人的声音。一旦它学会了一个特定的声音&#xff0c;VALL-E可以复制说话者的情绪和语气&#xff0c;即使说话者本人从未说过的单词也可以模仿。 论文地址&#xf…

xlCompiler转换为程序的优秀工具

xlCompiler转换为程序的优秀工具 xlCompiler是将oxel文件转换为程序的优秀工具。无需担心删除oxel文件中使用的宏和公式。该工具将您所做的转换为Exe可执行文件&#xff0c;这比分发原始文件时要低得多。最终的文件不需要氧气来执行&#xff0c;您也会有一种在简单的氧气环境中…

D. Friendly Spiders(bfs+筛法)

传送门题意&#xff1a;给你n个蜘蛛&#xff0c;每个蜘蛛有自己的腿数&#xff0c;如果某两个蜘蛛可以直接联系&#xff0c;那么这两个蜘蛛的最大公约数就不为1&#xff0c;否则这两只蜘蛛就不能直接联系。现在给你两个蜘蛛的序号i,j问这两只蜘蛛是否可以直接或者通过其他的蜘蛛…

SDK安全专项评测证书

SDK安全专项行动”是中国信息通信研究院安全研究所大数据应用与安全创新实验室共同发起的&#xff0c;实验室紧跟信息技术发展趋势&#xff0c;依托数据安全、移动安全等领域深厚积累&#xff0c;通过前瞻研究和实践探索&#xff0c;形成了完整的SDK评测方案和指标体系。 SDK产…

【HCIA-openEuler】实验手册—05【openEuler软件管理】

文章目录一、实验介绍1、内容描述2、实验目的二、配置Yum源步骤1&#xff1a;进入yum repo目录步骤2&#xff1a;更改openEuler_x86_64.repo文件步骤3&#xff1a;刷新列出软件列表三、使用RPM命令管理软件1、RPM查询命令步骤1&#xff1a;执行以下命令&#xff0c;通过yum和rp…

华为DHCP实验配置

配置AR1为PC2分配地址 dhcp enable ip pool DHCP_PC2 network 192.168.20.200 mask 24 gateway-list 192.168.20.254 dns-list 192.168.20.100 excluded-ip-address 192.168.20.100 int g0/0/1 ip address 192.168.20.254 24 dhcp select global 配置AR1为PC3分配IP地址 dhcp e…

一个令人赞不绝口的代码 Debug 神器

在编写程序时&#xff0c;无论是对于初学者还是对于专业开发人员&#xff0c;都会花费大量时间来调试或修复源代码错误&#xff0c;也就是 Debug。 这个过程繁琐复杂&#xff0c;包括 Bug 复现和 Bug 定位等环节。如果有了自动化的 Debug 程序&#xff0c;就可以显著提高编程实…

Three.js一学就会系列:05 加载3D模型

系列文章目录 Three.js一学就会系列&#xff1a;01 第一个3D网站 Three.js一学就会系列&#xff1a;02 画线 Three.js一学就会系列&#xff1a;03 炫酷3D划线 Three.js一学就会系列&#xff1a;04 炫酷3D文字 文章目录系列文章目录前言一、核心代码讲解引入插件轨道控制器加载…

ES6 课程概述⑤

文章目录9-1. Fetch Api 概述9-2. 基本使用参数返回值9-3 **Response 对象**9-4 Request 对象9-5 Headers 对象9-6 文件上传10-1 迭代器背景知识JS 中的迭代器可迭代协议 与 for-of 循环可迭代协议for-of 循环展开运算符与可迭代对象生成器 (Generator)set 集合9-1. Fetch Api …

Unity 之 Addressable可寻址系统 -- 代码加载介绍

Unity 之 可寻址系统 -- 代码加载介绍 -- 进阶&#xff08;一&#xff09;一&#xff0c;可寻址系统代码加载1.1 回调形式1.2 异步等待1.3 面板赋值1.4 同步加载二&#xff0c;可寻址系统分标签加载2.1 场景搭建2.2 代码示例2.3 效果展示三&#xff0c;代码加载可寻址的解释概述…

算法第九期——DFS(深度优先搜索)的树上应用

树 树是一种特殊的图 。 特点&#xff1a; 若树有n个点,则有n-1条边。树有连通性但没有回路。从一个点出发可以到达任意一个&#xff0c;而且路径是唯一的。树的重心u&#xff08;最平衡的点&#xff09;: 以树上任意一个结点为根计算它的子树的结点数&#xff0c;如果结点…

二部图和匈牙利算法

1.二分图最大匹配 设G为二分图,若在G的子图M中,任意两条边都没有公共节点,那么称M为二分图G的一组匹配。在二分图中,包含边数最多的一组匹配称为二分图的最大匹配。 交替路:从一个未匹配点出发,依次经过非匹配边、匹配边、非匹配边…形成的路径叫交替路。 …

【算法】深度优先搜索 (DFS)

目录1.概述2.代码实现3.应用1.概述 &#xff08;1&#xff09;深度优先遍历 (Depth First Search, DFS)&#xff0c;是图的搜索算法之一&#xff0c;本质其实就是一个递归的过程&#xff0c;它就像是一棵树的前序遍历。 &#xff08;2&#xff09;DFS 从图中某个顶点 start 出…

游戏如何解决注入挂难题

游戏黑灰产的攻击角度除了常见的内存修改、模拟点击、破解等作弊手段&#xff0c;还有门槛相对较高的「专用插件类」。 专用插件类外挂是指针对特定游戏定制的外挂&#xff0c;其在实现方式上&#xff0c;类似插件&#xff0c;也称“定制挂”、“注入挂”。 游戏面临多样化的安…