SQLAIchemy 异步DBManager封装-01入门理解

news2024/11/25 20:35:02

前言

SQLAlchemy 是一个强大的 Python SQL 工具包和对象关系映射(ORM)系统,是业内比较流行的ORM,设计非常优雅。随着其2.0版本的发布,SQLAlchemy 引入了原生的异步支持,这极大地增强了其在处理高并发和异步I/O场景下的能力。通过结合像greenlet、gevent这样的协程库,SQLAlchemy 使得异步数据库操作成为可能,从而提高了应用程序的性能和响应速度。

这里我将基于SQLAlchemy的异步支持,封装一些常用的增删改查(CRUD)操作到 https://github.com/HuiDBK/py-tools 中,以便在项目开发中更加便捷地使用。

Github: https://github.com/sqlalchemy/sqlalchemy

2.0文档:https://docs.sqlalchemy.org/en/20/index.html

简单使用

封装前,先简单介绍下如何使用 SQLAIchemy。

具体细节可以参考官网文档:https://docs.sqlalchemy.org/en/20/orm/quickstart.html

安装依赖

pip install sqlalchemy[asyncio]==2.0.20
pip install aiomysql==0.2.0

这里安装了 sqlalchemy 2.0版本,以及 aiomysql 异步数据库驱动,进行演示。

创建异步数据库引擎

from sqlalchemy.ext.asyncio import create_async_engine  

# db_uri = "{protocol}://{user}:{password}@{host}:{port}/{db}"

db_engine = create_async_engine("mysql+aiomysql://root:123456@127.0.0.1:3306/demo")

声明数据库表映射模型

from sqlalchemy import String
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column


class BaseOrmTable(DeclarativeBase):
    """SQLAlchemy Base ORM Model"""

    __abstract__ = True

    id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True, comment="主键ID")


class UserTable(BaseOrmTable):
    """用户表"""

    __tablename__ = "user"
    username: Mapped[str] = mapped_column(String(30), default="", comment="用户昵称")
    password: Mapped[str] = mapped_column(String(30), default="", comment="用户密码")
    phone: Mapped[str] = mapped_column(String(11), default="", comment="手机号")
    email: Mapped[str] = mapped_column(String(30), default="", comment="邮箱")
    avatar: Mapped[str] = mapped_column(String(100), default="", comment="头像")

简单db操作


from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column

# db_uri = "{protocol}://{user}:{password}@{host}:{port}/{db}"

db_engine = create_async_engine("mysql+aiomysql://root:123456@127.0.0.1:3306/hui-demo")

Session = async_sessionmaker(db_engine)

async def create_tables():
    # 根据映射创建库表
    async with db_engine.begin() as conn:
        await conn.run_sync(BaseOrmTable.metadata.create_all)


async def main():
    await create_tables()

    async with Session.begin() as session:
        # 添加用户
        new_user = UserTable(username='hui', email='huidbk@163.com')
        session.add(new_user)
        await session.flush()   # 刷新table 对象属性,获取新增的id
        print(new_user.id)
        print("add user", new_user.__dict__)

        # 获取用户
        user = await session.get(UserTable, new_user.id)
        print("get user", user.__dict__)

        # 更新用户
        user.email = 'hui@163.com'
        await session.merge(user)
        print("updated user", user.__dict__)

        # 删除用户
        await session.delete(user)


if __name__ == '__main__':
    # 运行主函数
    asyncio.run(main())

常用DB操作封装

SQLAlchemyManager

class SQLAlchemyManager(metaclass=SingletonMetaCls):
    DB_URL_TEMPLATE = "{protocol}://{user}:{password}@{host}:{port}/{db}"

    def __init__(
            self,
            host: str = "localhost",
            port: int = 3306,
            user: str = "",
            password: str = "",
            db_name: str = "",
            pool_size: int = 30,
            pool_pre_ping: bool = True,
            pool_recycle: int = 600,
            log: Union[logging.Logger] = None,
    ):
        self.host = host
        self.port = port
        self.user = user
        self.password = password
        self.db_name = db_name
        self.pool_size = pool_size
        self.pool_pre_ping = pool_pre_ping
        self.pool_recycle = pool_recycle
        self.log = log or logger

        self.db_engine: AsyncEngine = None
        self.async_session_maker: async_sessionmaker = None

    def get_db_url(self, protocol: str = "mysql+aiomysql"):
        db_url = self.DB_URL_TEMPLATE.format(
            protocol=protocol, user=self.user, password=self.password, host=self.host, port=self.port, db=self.db_name
        )
        return db_url
     
    def init_db_engine(self, protocol: str):
        """
        初始化db引擎
        Args:
            protocol: 驱动协议类型

        Returns:
            self.db_engine
        """
        db_url = self.get_db_url(protocol=protocol)
        self.log.info(f"init_db_engine => {db_url}")
        self.db_engine = create_async_engine(
            url=db_url, pool_size=self.pool_size, pool_pre_ping=self.pool_pre_ping, pool_recycle=self.pool_recycle
        )
        self.async_session_maker = async_sessionmaker(bind=self.db_engine, expire_on_commit=False)
        return self.db_engine
        
    def init_mysql_engine(self, protocol: str = "mysql+aiomysql"):
        """
        初始化mysql引擎
        Args:
            protocol: 驱动协议类型

        Returns:
            self.db_engine
        """
        return self.init_db_engine(protocol=protocol) 

SQLAlchemyManager 主要封装一些数据库账户配置信息、连接池信息。

pool_size(连接池大小): 指定连接池中允许保持的最大连接数。当应用程序需要访问数据库时,连接池会维护一定数量的数据库连接,以便快速地响应请求。通常情况下,pool_size 的值应该根据应用程序的并发访问量和数据库的性能来进行调整。

pool_pre_ping(预检查连接): 指定是否在数据库连接被使用前对连接进行预检查。预检查可以确保连接处于活动状态,并且可以自动重新连接到数据库服务器,以防止连接由于长时间空闲而失效。启用预检查可以提高应用程序对数据库的可靠性和稳定性。

pool_recycle(连接回收时间): 指定数据库连接在被重新使用之前的最大空闲时间。当连接空闲时间超过 pool_recycle 设置的值时,连接将被关闭并重新创建,以防止连接长时间处于空闲状态而导致的连接问题。pool_recycle 的值通常设置为一个较小的时间间隔,以确保连接能够及时地得到回收和重建,从而提高连接的健壮性和性能。

init_db_engine 方法则是初始化数据库引擎,内部根据数据库配置信息

  • 构造异步的数据库引擎 db_engine
  • 维护一个 async_session_maker 数据库会话工厂

BaseORMTable 映射库表封装

from datetime import datetime
from sqlalchemy import func
from sqlalchemy.ext.asyncio import AsyncAttrs
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column


class BaseOrmTable(AsyncAttrs, DeclarativeBase):
    """SQLAlchemy Base ORM Model"""

    __abstract__ = True

    id: Mapped[int] = mapped_column(primary_key=True, comment="主键ID")
    
    def __repr__(self):
        return str(self.to_dict())

    def to_dict(self, alias_dict: dict = None, exclude_none=True) -> dict:
        """
        数据库模型转成字典
        Args:
            alias_dict: 字段别名字典 eg: {"id": "user_id"}, 把id名称替换成 user_id
            exclude_none: 默认排查None值
        Returns: dict
        """
        alias_dict = alias_dict or {}
        if exclude_none:
            return {
                alias_dict.get(c.name, c.name): getattr(self, c.name)
                for c in self.__table__.columns if getattr(self, c.name) is not None
            }
        else:
            return {
                alias_dict.get(c.name, c.name): getattr(self, c.name, None)
                for c in self.__table__.columns
            }


class TimestampColumns(AsyncAttrs, DeclarativeBase):
    """时间戳相关列"""
    __abstract__ = True

    created_at: Mapped[datetime] = mapped_column(default=datetime.now, comment="创建时间")

    updated_at: Mapped[datetime] = mapped_column(default=datetime.now, onupdate=datetime.now, comment="更新时间")

    deleted_at: Mapped[datetime] = mapped_column(nullable=True, comment="删除时间")


class BaseOrmTableWithTS(BaseOrmTable, TimestampColumns):
    __abstract__ = True

创建一些基础的 ORM 类,以便后续的映射类可以继承并且共享一些公有属性和方法。

  1. BaseOrmTable 类:

    1. 定义了一个基础的 ORM 模型类,继承了 AsyncAttrsDeclarativeBase。这样做使得 BaseOrmTable 类具有了异步属性访问的能力,为异步编程提供便利,特别是在异步环境中访问具有延迟加载或者异步加载特性的属性。
    2. 提供了一个 to_dict 方法,用于将数据库模型转换为字典。它支持通过参数 alias_dict 指定字段别名,并且可以选择是否排除值为 None 的属性。
  2. TimestampColumns 类:

    1. 定义了一个包含时间戳相关列的抽象基类。这些列通常在很多数据库表中都会有,用于记录数据的创建时间、更新时间和删除时间。
    2. 这些列被设置为默认值,比如 created_atupdated_at 默认使用 datetime.now 函数来自动记录当前时间,deleted_at 则允许为空,用于标记数据的删除时间(可用作于逻辑删除)
  3. BaseOrmTableWithTS 类:

    1. 继承了 BaseOrmTableTimestampColumns,实际上是一个组合类,集成了基础的 ORM 功能和时间戳相关的列。
    2. 这个类进一步封装了 BaseOrmTableTimestampColumns,使得后续的映射类只需要继承这个类,就能够拥有基础的 ORM 功能和时间戳相关的列。

通过这种封装,你可以在后续的数据库映射类中更加专注于业务逻辑的实现,而不需要重复编写基础的 ORM 功能和时间戳相关的列,提高了代码的重用性和可维护性。

DBManager 数据库通用操作封装

前置封装说明

from typing import Any, List, Type, TypeVar, Union
from py_tools.connections.db.mysql import BaseOrmTable
from py_tools.meta_cls import SingletonMetaCls
    

# 泛指 BaseOrmTable 所有子类实例对象类型   
T_BaseOrmTable = TypeVar("T_BaseOrmTable", bound=BaseOrmTable)
T_Hints = TypeVar("T_Hints")  # 用于修复被装饰的函数参数提示,让IDE有类型提示


def with_session(method) -> T_Hints:
    """
    兼容事务会话
    Args:
        method: orm 的 crud

    Notes:
        方法中没有带事务连接则,则构造

    Returns:
    """

    @functools.wraps(method)
    async def wrapper(db_manager, *args, **kwargs):
        session = kwargs.get("session") or None
        if session:
            return await method(db_manager, *args, **kwargs)
        else:
            async with db_manager.transaction() as session:
                kwargs["session"] = session
                return await method(db_manager, *args, **kwargs)

    return wrapper

这里我提供了一个 with_session 装饰器,用于在需要数据库会话(事务)的数据库操作方法中自动开启事务,由于 sqlaichemy 官方推荐每个数据库操作都手动开启事务会话(自动提交),装饰器的设计没有时则构造,有则共享,这样不但可以减少冗余 async with db_manager.transaction() as session 的代码,也可以兼容多个操作共享同一个 session 有问题时进行事务回滚。

由于给方法加了通用的装饰器导致一些版本的IDE无法识别方法真实的签名,使用时会出现不知道方法的入参是什么,对于开发者来说是极其不方便的。

使用 typing 的 TypeVar 自定义类型来构造一个通用的泛型来当作函数返回的类型,进而修复。

from typing import TypeVar
T_Hints = TypeVar("T_Hints")  # 用于修复被装饰的函数参数提示,让IDE有类型提示


def with_session(method) -> T_Hints:
    ...

这里PyCharm 2023.2.4 版本升级到 2024.1 就有提示了,IDE修复了,可以不用 T_Hints 了。

一些旧版本构造 sqlaichemy 的库表对象时也会出现不知道类对象属性入参提示,升级到最新版本都解决了。

from contextlib import asynccontextmanager


class DBManager(metaclass=SingletonMetaCls):
    DB_CLIENT: SQLAlchemyManager = None
    orm_table: Type[BaseOrmTable] = None

    @classmethod
    def init_db_client(cls, db_client: SQLAlchemyManager):
        cls.DB_CLIENT = db_client
        return cls.DB_CLIENT

    @classmethod
    @asynccontextmanager
    async def transaction(cls):
        """事务上下文管理器"""
        async with cls.DB_CLIENT.async_session_maker.begin() as session:
            yield session

    @classmethod
    @asynccontextmanager
    async def connection(cls) -> AsyncIterator[AsyncConnection]:
        """数据库引擎连接上下文管理器"""
        async with cls.DB_CLIENT.db_engine.begin() as conn:
            yield conn

  • init_db_client 方法用于初始化数据库客户端(引擎)。
  • transaction 则是简单的通过 contextlib 中 asynccontextmanager 封装一个异步的上下文管理器方便简洁的开启一个数据库会话(事务)进行数据库相关操作。
  • connection 数据库引擎连接上下文管理器。
  • orm_table 是具体继承 DBManager 的子类进行指定的,用于操作具体的库表(orm_table)。
  • DBManager 通过 SingletonMetaCls 元类实现单例模式。具体单例模式可以了解 https://juejin.cn/post/7272006755265380367 这篇文章有详细的介绍。

DB添加操作封装

    
class DBManager(metaclass=SingletonMetaCls):
    DB_CLIENT: SQLAlchemyManager = None
    orm_table: Type[BaseOrmTable] = None

    @with_session
    async def bulk_add(
            self,
            table_objs: List[Union[T_BaseOrmTable, dict]],
            *,
            orm_table: Type[BaseOrmTable] = None,
            flush: bool = False,
            session: AsyncSession = None
    ) -> List[T_BaseOrmTable]:
        """
        批量插入
        Args:
            table_objs: orm映射类实例列表
                eg.[UserTable(username="hui", age=18), ...] or [{"username": "hui", "age": 18}, ...]
            orm_table: orm表映射类
            flush: 刷新对象状态,默认不刷新
            session: 数据库会话对象,如果为 None,则通过装饰器在方法内部开启新的事务

        Returns:
            成功插入的对象列表
        """
        orm_table = orm_table or self.orm_table
        if all(isinstance(table_obj, dict) for table_obj in table_objs):
            # 字典列表转成orm映射类实例列表处理
            table_objs = [orm_table(**table_obj) for table_obj in table_objs]
    
        session.add_all(table_objs)
        if flush:
            await session.flush(table_objs)
    
        return table_objs
    
    @with_session
    async def add(
            self,
            table_obj: [T_BaseOrmTable, dict],
            *,
            orm_table: Type[BaseOrmTable] = None,
            session: AsyncSession = None
     ) -> int:
        """
        插入一条数据
        Args:
            table_obj: orm映射类实例对象, eg. UserTable(username="hui", age=18) or {"username": "hui", "age": 18}
            orm_table: orm表映射类
            session: 数据库会话对象,如果为 None,则通过装饰器在方法内部开启新的事务

        Returns: 新增的id
            table_obj.id
        """
        orm_table = orm_table or self.orm_table
        if isinstance(table_obj, dict):
            table_obj = orm_table(**table_obj)
            
        session.add(table_obj)
        await session.flush(objects=[table_obj])  # 刷新对象状态,获取新增的id
        return table_obj.id

这里就是用 session.add 与 add_all 方法封装了数据库添加、批量添加的操作,封装的点主要在于除了 orm_table 实例对象入参还支持字典入参,内部还是转换成库表映射类实例来操作,最后通过 session.flush 方法,单个添加返回新增的主键id,批量添加则是返回实例对象列表。

设计的方法中有一个 * 号是参数的分隔符,它的作用是将其前面的参数声明为位置参数,而将 * 后面的参数声明为关键字参数,* 号后面的参数入参只能使用关键字形式的入参,我在很多的开源库中都看到了这样的设计,可以把一些函数语义连贯、常用必传的参数设置为位置参数,其他的则是关键字参数。这样可以明确参数的作用、提高函数的可读性、防止参数错误等。

具体看下使用案例:

import asyncio

from sqlalchemy import String
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column

from py_tools.connections.db.mysql import BaseOrmTableWithTS, BaseOrmTable, DBManager, SQLAlchemyManager


class UserTable(BaseOrmTableWithTS):
    """用户表"""

    __tablename__ = "user"
    username: Mapped[str] = mapped_column(String(30), default="", comment="用户昵称")
    password: Mapped[str] = mapped_column(String(30), default="", comment="用户密码")
    phone: Mapped[str] = mapped_column(String(11), default="", comment="手机号")
    email: Mapped[str] = mapped_column(String(30), default="", comment="邮箱")
    avatar: Mapped[str] = mapped_column(String(100), default="", comment="头像")


async def create_tables():
    # 根据映射创建库表(异步)
    # async with db_engine.begin() as conn:
    #    await conn.run_sync(BaseOrmTable.metadata.create_all)
    
    async with DBManager.connection() as conn:
        await conn.run_sync(BaseOrmTable.metadata.create_all)


async def init_orm_manager():
    db_client = SQLAlchemyManager(
        host="127.0.0.1",
        port=3306,
        user="root",
        password="123456",
        db_name="hui-demo",
    )
    db_client.init_mysql_engine()
    DBManager().init_db_client(db_client)


async def manager_crud():
    user = {"username": "hui", "email": "huidbk.163.com"}
    user_id = await DBManager().add(table_obj=user, orm_table=UserTable)
    print("user_id", user_id)

    users = [
        {"username": "zack", "email": "zack.163.com"},
        {"username": "wang", "email": "wang.163.com"}
    ]
    add_users = await DBManager().bulk_add(table_objs=users, orm_table=UserTable)
    add_user_ids = [user.id for user in add_users]
    print("add_user_ids", add_user_ids)


async def main():
    await create_tables()

    # await normal_crud()

    await init_orm_manager()

    await manager_crud()


if __name__ == '__main__':
    # 运行主函数
    asyncio.run(main())

在程序启动时初始化好DBManager 的 DB_CLIENT 就可以直接使用封装的方法,主要就是 DB_CLIENT 作为类属性,后面DBManager 实例与子类实例对象都可以共享这个数据库引擎。但我这里还是不推荐上面的写法,DBManager 是一些通用的DB操作,而具体一些业务操作还是单独封装一些DB业务Manager类来进行会比较好,更利于扩展维护与复用。


class UserManager(DBManager):
    orm_table = UserTable

    async def get_name_by_email(self, email):
        username = await self.query_one(cols=["username"], conds=[self.orm_table.email == email], flat=True)
        return username


async def manager_crud():

    # demo 2 (推荐)
    user = UserTable(username="hui-test01", email="hui-test01.163.com")
    user_id = await UserManager().add(table_obj=user)
    print("user_id", user_id)

    users = [
        UserTable(username="hui-test02", email="hui-test02.163.com"),
        UserTable(username="hui-test03", email="hui-test03.163.com"),
    ]
    add_users = await UserManager().bulk_add(table_objs=users)
    add_user_ids = [user.id for user in add_users]
    print("add_user_ids", add_user_ids)
    
    username = await UserManager().get_name_by_email(email="huidbk.163.com")
    print("username", username)


>>> out
user_id 4
add_user_ids [5, 6]
username hui

这里 UserManager 单独封装的 get_name_by_email 的方法就是业务中常用查询操作通过邮件获取用户名称,这里就是举一个简单的例子,具体DB业务具体封装而不是全部写在逻辑层,这样别人要用的时候就不用重新组织条件参数、上下文,而是简单传递业务参数进行复用获取数据。

UserManager 调用 add、bulk_add 等方法时也不用像 DBManager 指定 orm_table 参数,使用起来更简洁。具体是因为 UserManager 类指定了 类属性 orm_table = UserTable,再封装时有一句 orm_table = orm_table or self.orm_table 意思就是优先选择入参的orm_table,没有则是 self.orm_table (具体实例对象的orm_table)。这样写也体现出 封装、继承的灵活性。

这里也引出了另一个封装方法 query_one 查询单条数据。由于介绍了一些Demo如果把所有的封装方法混合到一起篇幅就太长,故而我准备分成三篇进行分别介绍,这样也更好阅读。

  1. SQLAIchemy 异步DBManager封装-01入门理解
  2. SQLAIchemy 异步DBManager封装-02熟悉掌握
  3. SQLAIchemy 异步DBManager封装-03得心应手

Github源代码

源代码已上传到了Github,里面也有具体的使用Demo,欢迎大家一起体验、贡献。

HuiDBK/py-tools: 打造 Python 开发常用的工具,让Coding变得更简单 (github.com)

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

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

相关文章

codeforce #925 (div3) 题解

D. Divisible Pairs 给出数组 a a a,如果二元组 ( i , j ) (i,j) (i,j)满足 a i a j m o d x 0 & & a i − a j m o d y 0 a_i a_j mod x 0 \&\& a_i - a_j mod y 0 ai​aj​modx0&&ai​−aj​mody0,则beauty。其中 i &…

阿里云服务器项目部署docker-compose+vue+redis+nginx+minio+springboot

1 阿里云服务器项目部署-单机部署 docker-compose 1.1 搭建背景 服务器 阿里云服务器 前端 vue 后端 springboot 服务 redis 、nginx、minio 都做单机模式部署,不做集群部署 博客内容参考了其他博文&#xff0c;会贴出来 1.2 <重要>端口开放前提说明 任何开放的端…

2024年华中杯数学建模竞赛全攻略:ABC题思路解析+代码实现+数据集+论文撰写+全程答疑

引言 &#xff08;比赛后所有题目资料在文末获取呀&#xff09; 华中杯数学建模竞赛是数学建模领域的一项重要赛事&#xff0c;它不仅考验参赛者的数学建模能力&#xff0c;还考验了编程技能、数据分析能力和论文撰写能力。为了帮助参赛者更好地准备2024年的竞赛&#xff0c;本…

编程填空题:麻烦的位运算

闲着没事干可以做做 可以看到&#xff0c;那个函数直接return了&#xff0c;也就是说&#xff0c;得找到一个表达式&#xff0c;直接求出结果 简单分析一下&#xff1a; 其第i位为1当且仅当n的右边i位均为1 也就是说&#xff0c;前i-1位有0&#xff0c;第i位就是0 也就是说…

二维数组之前缀和中篇

在此之前&#xff0c;可以先看看这篇二维数组之二维前缀和-首篇。 和为K的子数组 给你一个整数数组 nums 和一个整数 k &#xff0c;请你统计并返回 该数组中和为 k 的子数组的个数 。子数组是数组中元素的连续非空序列。 示例 1&#xff1a; 输入&#xff1a;nums [1,1,…

cesium加载倾斜影像数据(模拟雨、雪、雾、无人机飞行、测距、箭头标绘、电子围栏等)

实现效果如下&#xff1a; 功能菜单如下&#xff1a; 加载倾斜影像核心代码&#xff1a; var palaceTileset new Cesium.Cesium3DTileset({url: http://127.0.0.1:9002/tileset.json,//控制切片视角显示的数量&#xff0c;可调整性能maximumScreenSpaceError: 0.1,maximumNum…

c++的学习之路:25、map与set

摘要 本文中说一下map与set的使用 目录 摘要 一、关联式容器 二、键值对 三、map 1、map的介绍 2、map的使用 1、map的模板参数说明&#xff1a; 2、map的构造 3、map的迭代器 4、map的容量与元素访问 5、map中元素的修改 6、代码使用 ​编辑 三、总结 四、se…

51单片机入门_江协科技_33~34_OB记录的自学笔记_LED呼吸灯与PWM直流马达调速

33. 直流电机驱动(PWM) 33.1. 直流电机介绍 •直流电机是一种将电能转换为机械能的装置。一般的直流电机有两个电极&#xff0c;当电极正接时&#xff0c;电机正转&#xff0c;当电极反接时&#xff0c;电机反转 •直流电机主要由永磁体&#xff08;定子&#xff09;、线圈&…

winform 入门篇 -- 第15章 表格视图

表格控件 表格视图 DataGridView &#xff0c;即表格控件提行多行多列的表格状的数据展示 演示: 以表格控件来展示学生数据。。 每个单元格 都可以进行独立的编写 &#xff08;与上节得不同&#xff09; 基本操作: 1 添加一个表格控件 DataGridView 2 设置列数、列名 属…

HarmonyOS Next 视频弹幕功能

视频弹幕功能 介绍 本示例介绍如何使用ohos.danmakuflamemaster和ohos.gsyvideoplayer开发支持视频弹幕的播放器。可以自定义弹幕样式、占据屏幕宽度&#xff0c;发送弹幕&#xff0c;开关弹幕视图。 效果图预览 使用说明 点击播放按钮&#xff0c;进行视频播放&#xff0c…

STM32 USB虚拟串口

电路原理图 usb部分 晶振部分 usb与单片机连接 配置信息 sys配置信息 rcc配置信息 usb配置信息 虚拟串口配置信息 时钟配置信息 项目配置信息 代码 包含文件 主函数代码 实验效果 修改接收波特率依然可以正常接收&#xff0c;也就是说单片机可以自动适应上位机的波特率设置。…

2023年图灵奖揭晓:Avi Wigderson的辉煌成就与深远影响

2023年图灵奖揭晓&#xff0c;你怎么看&#xff1f; 2023年图灵奖&#xff0c;最近刚刚颁给普林斯顿数学教授 Avi Wigderson&#xff01;作为理论计算机科学领域的领军人物&#xff0c;他对于理解计算中的随机性和伪随机性的作用&#xff0c;作出了开创性贡献。 方向三&#xf…

免费泛域名SSL如何申请,和通配符有什么区别

-----让我们明确什么是泛域名。所谓泛域名&#xff0c;是指使用星号&#xff08;*&#xff09;作为子域名的占位符&#xff0c;它可以匹配任意子域名。-----而通配符在域名中&#xff0c;它可以出现在主域名的任何位置&#xff0c;它可以用于主域名和子域名的保护。 主要应用场…

握手问题(蓝桥杯)

文章目录 握手问题【问题描述】答案&#xff1a;1204解题思路模拟 握手问题 【问题描述】 小蓝组织了一场算法交流会议&#xff0c;总共有 50 人参加了本次会议。在会议上&#xff0c;大家进行了握手交流。按照惯例他们每个人都要与除自己以外的其他所有人进行一次握手&#…

OceanBase 4.3 列存存储格式和列存索引存储格式

以 t1 表和索引为例子&#xff0c;下面两张图说明了存储层如何存储数据。 create table t1 (id1 int, id2 int, name varchar(10), salary int, primary key(id1, id2)) with column group (each column);create index idx (name) storing(salary) with column group(each co…

突破“三个九”!离子阱量子计算再创新高

如果把量子计算比作一场球赛&#xff0c;Quantinuum无疑又打了一记漂亮的好球。实际上&#xff0c;结合今年春季在量子体积、逻辑量子比特和布线问题等方面的进展&#xff0c;这个团队已经接近于完成一场完美的比赛。 3月&#xff0c;Quantinuum的研究人员证明了QCCD架构的可扩…

MYSQL08_页的概述、内部结构、文件头、文件尾、最大最小记录、页目录、区段表

文章目录 ①. 页的概述、大小②. 页的内部结构③. 第一部分 - 文件头④. 第一部分 - 文件尾⑤. 第二部分 - 空闲、用户记录、最大最小⑥. 第三部分 - 页目录⑦. 第三部分 - 页面头部⑧. 从数据页角度看B树⑨. 区、段和表、碎片区 ①. 页的概述、大小 ①. 数据库的存储结构&…

小行星碰撞

题目链接 小行星碰撞 题目描述 注意点 两个小行星相互碰撞&#xff0c;较小的小行星会爆炸如果两颗小行星大小相同&#xff0c;则两颗小行星都会爆炸每一颗小行星以相同的速度移动正负表示小行星的移动方向&#xff08;正表示向右移动&#xff0c;负表示向左移动&#xff09…

day81 session会话 文件上传

知识点&#xff1a; session 文件上传 一 session 1&#xff09;session&#xff1a;会话 在服务器端存储信息 指客户与服务器的会话 当用户通过浏览器访问服务器的某个页面时&#xff0c;在服务器开辟一个内存空间session 每个session 有唯一的id 2&#xff09;session过期 …

安全开发实战(3)--存活探测与端口扫描

目录 安全开发专栏 前言 存活探测 端口扫描 方式一: 1.3.1 One 1.3.2 Two 1.3.3 批量监测 方式二: 1.3.1 One 1.3.2 Two 1.3.3 Three 1.3.4 扫描ip地址,提取出开放端口和协议 ​编辑 1.3.5 批量扫描(最终完成版) 总结 安全开发专栏 安全开发实战​http://t.csd…