加班把数据库重构完毕

加班把数据库重构完毕

本文的数据库重构是基于 clickhouse 时序非关系型的数据库。该数据库适合存储股票数据,速度快,一般查询都是 ms 级别,不需要异步查询更新界面 ui。

达到目标效果:数据表随便删除,重新拉数据以及指标计算,十多年的数据,整一个过程 5-6 分钟即可,速度远超通达信。因为每个季度数据回除权,所以旧的数据是有问题的,现在再也不怕删数据重新拉取重新计算了。

为啥要重构?

  1. 以前日行情数据和指标数值是分开两个表的,后面继续研究 clickhouse 数据库,发现根本不需要多表存储,因为 clickhouse 是列存储方式,所以宽表并不会影响查询速度。

  2. 以前数据经常出现不完整情况,指标数据计算会发生日级别的断层。

  3. 以前数据重复插入的时候,查出来经常需要去重,增加了消耗。

  4. 以前很害怕数据重新拉取和计算,因为经常出现数据不完整问题,都不敢删重新来过,不然又要停机查问题了,现在随便删随便重新计算,彻底解决了这个问题。

关键设计

把所有的股票的日行情数据和指标数据存储在一个表

理由:

  1. 可以多个股票同时查询。
  2. 可以多个股票同一个时间段同时查询。
  3. 可以选择性查询某部分字段,不需要跨表,从而提高效率。
  4. 可以完成数据的完整性和自动去重。

疑问:

  1. 有的同学疑惑,所有日行情数据和指标数据放一个表会不会增加查询速度。
    答案:不会,这是因为 clickhouse 为快速处理这大数据问题效率慢设计好了。

  2. 如何设置排序值?
    答案:因为我们把所有股票数据以及指标放在了同一个表中,所以需要把 date 和 code 两个字段作为键值。

如何避免重复插入,查询数据是使用最新的数据?

  1. clickhouse 数据库并不擅长单列更新的,所以我们要更新某列的时候,原则是:先把要更新的行查出来,然后计算指标数据,填充完后,直接插回去即可,所以每一行需要添加一个 version 版本号,数据库会自动去重保存最新的版本号数据,旧数据数据库会自动删除。

  2. 由于采取的策略是查询数据出来,计算指标填充完重新插回去,所以我们使用的引擎策略是,ReplacingMergeTree,这个的意思是 clickhouse 数据库会自动去重。

  3. 查询,由于插入新的行的时候,如果有重复行 clickhouse 数据库是在后台不知何时才会自动触发去掉旧数据的,所以查询的使用要加个小技巧,要以版本号进行排序,然后取最新的一条, ORDER BY version DESC,LIMIT 1 BY code,date。具体的见代码。

  4. 创建表的关键。 引擎: ENGINE=ReplacingMergeTree(version) 以版本号作为去重标准,保留最新版本号的数据
    主键: PRIMARY KEY(javaHash(code), date) ,由于所有日行情数据放一个表,所以以 code,date 两个字段确定一行数据。
    排序值: ORDER BY(javaHash(code), date),以 code 和 date 作为排序,有了解过 clickhouse 数据库的同学就会知道,这两个字段决定了 clickhouse 的数据存储方式。

福利

如何同学也使用 clickhouse 数据库用来存储股票数据,或者还未建立数据库来存储数据的,建议你使用 clickhouse 用来存储,别用 MySql,场景不一样,MySql 适合业务型的,clickhouse 天生就是为数据分析而产生的。所以在查询速度上,clickhouse 是碾压 MySql 的。

可以直接使用我的代码,是经过不断测试趋于完善的了,没 bug 了。

我的重构代码:

import time

import pandahouse as ph
import pandas as pd
from clickhouse_driver import Client

'''
pandahouse 是通过http url 链接,端口号是8123
'
''
connection = dict(database="stock",
                  host="http://localhost:8123",
                  user='default',
                  password='sykent')

'''
clickhouse_driver 是通过TCP链接,端口号是9000
'
''

DB = 'stock'
# settings = {'max_threads': 5}
client = Client(database=f'{DB}',
                host='127.0.0.1',
                port='9000',
                user='default',
                password='sykent',
                # settings=settings
                )
sql = 'SET max_partitions_per_insert_block = 200'
client.execute(sql)
"""
表名
"
""
STOCK_DAILY_TABLE = 'stock_daily_price_v2'
INDUSTRY_DAILY_TABLE = 'industry_daily_v2'
INDUSTRY_CONSTITUENT_STOCK_TABLE = 'industry_constituent_stock_v2'
MARKET_DAILY_TABLE = 'market_daily_v2'


def stock_daily(
        pool_code,
        start_time,
        end_time,
        use_col=None
) -> pd.DataFrame:
    """
    查询股票某个时间段日线数据
    :param pool_code: 股票代码池 list() ['000001', '000002'] 或者 '000001'
    :param start_time: 开始时间
    :param end_time: 结束时间
    :param use_col: 使用的列 list() ['open', 'close'],不传则使用全部列
    "
""
    return __query_daily_related(
        STOCK_DAILY_TABLE,
        pool_code,
        start_time,
        end_time,
        use_col
    )


def stock_daily_http(
        pool_code,
        start_time,
        end_time,
        use_col=None
) -> pd.DataFrame:
    """
    查询股票某个时间段日线数据
    :param pool_code: 股票代码池 list() ['000001', '000002'] 或者 '000001'
    :param start_time: 开始时间
    :param end_time: 结束时间
    :param use_col: 使用的列 list() ['open', 'close'],不传则使用全部列
    "
""
    return __query_daily_related_http(
        STOCK_DAILY_TABLE,
        pool_code,
        start_time,
        end_time,
        use_col
    )


def stock_daily_on_date(
        pool_code,
        date_time,
        use_col=None
) -> pd.DataFrame:
    """
    查询股票某日日线数据
    :param pool_code: 股票代码池 list() ['000001', '000002'] 或者 '000001'
    :param date_time: 日期
    :param use_col: 使用的列 list() ['open', 'close'],不传则使用全部列
    "
""
    return stock_daily(
        pool_code,
        date_time,
        date_time,
        use_col
    )


def industry_daily(
        pool_code,
        start_time,
        end_time,
        use_col=None
) -> pd.DataFrame:
    """
    查询行业某个时间段日线数据
    :param 参照stock_daily
    "
""
    return __query_daily_related(
        INDUSTRY_DAILY_TABLE,
        pool_code,
        start_time,
        end_time,
        use_col
    )


def industry_daily_on_date(
        pool_code,
        date_time,
        use_col=None
) -> pd.DataFrame:
    """
    查询行业某日日线数据
    :param 参照stock_daily_on_date
    "
""
    return industry_daily(
        pool_code,
        date_time,
        date_time,
        use_col
    )


def all_industry_daily_on_date(
        date_time,
        use_col=None
) -> pd.DataFrame:
    """
    查询所有板块的某个日期的rps
    :param date_time:
    :param use_col:
    :return:
    "
""
    if use_col is None:
        sql = f"""
        SELECT *
        FROM {DB}.{INDUSTRY_DAILY_TABLE}
        WHERE date == '{date_time}'
        ORDER BY version DESC
        LIMIT 1 BY code,date
        "
""
    else:
        columns = 'date,code,' + ','.join(use_col) + ',version'
        sql = f"""
        SELECT {columns}
        FROM {DB}.{INDUSTRY_DAILY_TABLE}
        WHERE date == '{date_time}'
        ORDER BY version DESC
        LIMIT 1 BY code,date
        "
""
    df = from_table(sql)

    if df.empty:
        return df
    else:
        df.drop(columns='date', inplace=True)
        return df


def market_daily(
        pool_code,
        start_time,
        end_time,
        use_col=None
) -> pd.DataFrame:
    """
    查询大盘指数某个时间段日线数据
    :param 参照stock_daily
    "
""
    return __query_daily_related(
        MARKET_DAILY_TABLE,
        pool_code,
        start_time,
        end_time,
        use_col
    )


def market_daily_on_date(
        pool_code,
        date_time,
        use_col=None
) -> pd.DataFrame:
    """
    查询大盘指数某日日线数据
    :param 参照stock_daily_on_date
    "
""
    return market_daily(
        pool_code,
        date_time,
        date_time,
        use_col
    )


def board_constituent_stock(
        code
) -> pd.DataFrame:
    """
    板块成分股
    :param code: 板块代码
    :return:
    "
""
    sql = f"""
    SELECT *
    FROM {DB}.{INDUSTRY_CONSTITUENT_STOCK_TABLE}
    WHERE industry_code == '{code}'
    "
""
    return from_table(sql)


# @timing_decorator
def to_table(data, table):
    if data.empty:
        return 0

        # 获取columns 如果不包含 'date',重置index
    if 'date' not in data.columns:
        data.reset_index(inplace=True)

    data.insert(data.shape[1], 'version', int(time.time()))
    columns = ', '.join(data.columns)
    sql = f'INSERT INTO {table} ({columns}) VALUES'
    client.execute(sql, data.values.tolist())
    return data.shape[0]


# @timing_decorator
def to_table_common(data, table):
    columns = ', '.join(data.columns)
    sql = f'INSERT INTO {table} ({columns}) VALUES'
    client.execute(sql, data.values.tolist())
    return data.shape[0]


# @timing_decorator
def from_table(sql) -> pd.DataFrame:
    last_time = time.time()
    try:
        result = client.query_dataframe(sql)
    except Exception as e:
        print(e)
        result = pd.DataFrame()
    print("db-> 耗时: {}  sql: {}".format((time.time() - last_time) * 1000, sql))
    return result


def from_table_http(sql):
    """
    查询表
    :param sql:
    :return: dataframe
    "
""
    last_time = time.time()
    df = ph.read_clickhouse(sql, connection=connection)
    print("db-> 耗时: {}  sql: {}".format((time.time() - last_time) * 1000, sql))
    return df


def __creat_daily_related_table(table_name, **kwargs):
    """
    创建日行情相关的表
    注意:一定需要date,code这两列,作为排序值
    :param table_name: 表名
    :param kwargs: 列名
    :return:
    "
""
    columns_str = ''
    for key, value in kwargs.items():
        columns_str = columns_str + f'{key} {value},'
    columns_str = columns_str[:len(columns_str) - 1]
    # 自动添加列名 version 用于插入更新数据
    columns_str = columns_str + ',version Int64'
    if 'code' not in columns_str or 'date' not in columns_str:
        raise Exception('not column code date!!')

    sql = f"""
    CREATE TABLE if NOT EXISTS {table_name}({columns_str})
    ENGINE=ReplacingMergeTree(version)
    PRIMARY KEY(javaHash(code), date)
    ORDER BY(javaHash(code), date)
    "
""
    print('创建表sql:', sql)
    client.execute(sql)


def __creat_common_table(table_name, order_by=None, **kwargs):
    """
    创建通用的表,默认使用 ReplacingMergeTree,并自动添加列 version 用于插入更新数据,
    而且去重的时候,只会保留version最大的数据
    :param table_name: 表名
    :param order_by: 排序字段
    :param kwargs: 列名
    "
""

    columns_str = ''
    for key, value in kwargs.items():
        columns_str = columns_str + f'{key} {value},'
    columns_str = columns_str[:len(columns_str) - 1]
    # 自动添加列名 version 用于插入更新数据
    columns_str = columns_str + ',version Int64'
    sql = f"""
    CREATE TABLE if NOT EXISTS {table_name}({columns_str})
    ENGINE=ReplacingMergeTree(version)
    "
""
    if order_by is not None:
        sql = sql + f' ORDER BY{order_by}'
    print('创建表sql:', sql)
    client.execute(sql)


def __drop_table(table_name):
    """
    删除表
    :param table_name:
    :return:
    "
""
    sql = f'DROP TABLE IF EXISTS {table_name}'
    client.execute(sql)
    print('删除表sql:', sql)


def __query_daily_related(
        table,
        pool_code,
        start_time,
        end_time,
        use_col=None
) -> pd.DataFrame:
    """
    查询股票相关的表
     eg:query_daily_related(['000001', '000002'], '2021-01-01', '2022-09-30')
    :param pool_code: 股票池 数据类型 list eg:'[000001', '000002']
    :param start_time: 开始时间
    :param end_time: 结束时间
    :param use_col: list 需要返回的列,默认返回 'date,code' 并设置 date 为 index
    :return:
    如果 start_time == end_time 则认为是查询某一天的数据
    version 为最新的数据,以此来去重
    "
""

    # 如果传入的是单个code,转换成list
    if type(pool_code) is not list:
        code = pool_code
        pool_code = list()
        pool_code.append(code)
    # 时间不相等,查询时间段的数据
    if start_time != end_time:
        if use_col is None:
            sql = f"""
            SELECT *
            FROM {DB}.{table}
            WHERE date BETWEEN '{start_time}' AND '{end_time}'
            AND code IN {pool_code}
            ORDER BY version DESC
            LIMIT 1 BY code,date
            "
""
        else:
            columns = 'date,code,' + ','.join(use_col) + ',version'
            sql = f"""
            SELECT {columns}
            FROM {DB}.{table}
            WHERE date BETWEEN '{start_time}' AND '{end_time}'
            AND code IN {pool_code}
            ORDER BY version DESC
            LIMIT 1 BY code,date
            "
""
        df = from_table_http(sql)
        if df.empty:
            return df
        # 设置date为index,并排序
        df.set_index('date', inplace=True)
        df.sort_index(inplace=True)
    # 时间相等,查询某一天的数据
    else:
        if use_col is None:
            sql = f"""
            SELECT *
            FROM {DB}.{table}
            WHERE date == '{start_time}'
            AND code IN {pool_code}
            ORDER BY version DESC
            LIMIT 1 BY code,date
            "
""
        else:
            columns = 'date,code,' + ','.join(use_col) + ',version'
            sql = f"""
            SELECT {columns}
            FROM {DB}.{table}
            WHERE date == '{start_time}'
            AND code IN {pool_code}
            ORDER BY version DESC
            LIMIT 1 BY code,date
            "
""
        df = from_table_http(sql)
        if df.empty:
            return df
        df.drop(columns=['date'], inplace=True)
    # version 为更新插入使用,删除version列
    df.drop(columns=['version'], inplace=True)
    return df


def __query_daily_related_http(
        table,
        pool_code,
        start_time,
        end_time,
        use_col=None
) -> pd.DataFrame:
    """
    查询股票相关的表
     eg:query_daily_related(['000001', '000002'], '2021-01-01', '2022-09-30')
    :param pool_code: 股票池 数据类型 list eg:'[000001', '000002']
    :param start_time: 开始时间
    :param end_time: 结束时间
    :param use_col: list 需要返回的列,默认返回 'date,code' 并设置 date 为 index
    :return:
    如果 start_time == end_time 则认为是查询某一天的数据
    version 为最新的数据,以此来去重
    "
""

    # 如果传入的是单个code,转换成list
    if type(pool_code) is not list:
        code = pool_code
        pool_code = list()
        pool_code.append(code)
    # 时间不相等,查询时间段的数据
    if start_time != end_time:
        if use_col is None:
            sql = f"""
            SELECT *
            FROM {DB}.{table}
            WHERE date BETWEEN '{start_time}' AND '{end_time}'
            AND code IN {pool_code}
            ORDER BY version DESC
            LIMIT 1 BY code,date
            "
""
        else:
            columns = 'date,code,' + ','.join(use_col) + ',version'
            sql = f"""
            SELECT {columns}
            FROM {DB}.{table}
            WHERE date BETWEEN '{start_time}' AND '{end_time}'
            AND code IN {pool_code}
            ORDER BY version DESC
            LIMIT 1 BY code,date
            "
""
        df = from_table_http(sql)
        if df.empty:
            return df
        df.set_index('date', inplace=True)
        df.sort_index(inplace=True)
    # 时间相等,查询某一天的数据
    else:
        if use_col is None:
            sql = f"""
            SELECT *
            FROM {DB}.{table}
            WHERE date == '{start_time}'
            AND code IN {pool_code}
            ORDER BY version DESC
            LIMIT 1 BY code,date
            "
""
        else:
            columns = 'date,code,' + ','.join(use_col) + ',version'
            sql = f"""
            SELECT {columns}
            FROM {DB}.{table}
            WHERE date == '{start_time}'
            AND code IN {pool_code}
            ORDER BY version DESC
            LIMIT 1 BY code,date
            "
""
        df = from_table_http(sql)
        if df.empty:
            return df
        df.drop(columns=['date'], inplace=True)
    # version 为更新插入使用,删除version列
    df.drop(columns=['version'], inplace=True)
    return df


def stock_length(code):
    """
    查询股票上市最小日期
    :param code:
    :return:
    "
""
    sql = f"""
    SELECT count()
    FROM {DB}.{STOCK_DAILY_TABLE}
    WHERE code == \'{code}\'
    "
""
    count = client.execute(sql)[0][0]
    print('stock_length sql:', sql, f'result count {count}')
    return count


def create_market_daily_table():
    """
    大盘数据表
    :return:
    "
""
    columns = {
        'date''Date',
        'code''String',
        'name''String',
        'open''Float32',
        'high''Float32',
        'low''Float32',
        'close''Float32',
        'volume''Float64',
        'amount''Float64',
        'change''Float32',
        'change_amount''Float32',
        'amplitude''Float32',
        'turnover''Float32'}
    __creat_daily_related_table(MARKET_DAILY_TABLE, **columns)


def create_stock_daily_table():
    """
    创建日行情数据表
    :return:
    "
""
    columns = {
        'date''Date',
        'code''String',
        'name''String',
        'open''Float32',
        'high''Float32',
        'low''Float32',
        'close''Float32',
        'change''Float32',
        'change_amount''Float32',
        'volume''Float64',
        'amount''Float64',
        'amplitude''Float32',
        'turnover''Float32',
        'amp05''Float32',
        'amp10''Float32',
        'amp20''Float32',
        'amp50''Float32',
        'amp120''Float32',
        'amp250''Float32',
        'ma05''Float32',
        'ma10''Float32',
        'ma20''Float32',
        'ma50''Float32',
        'ma120''Float32',
        'ma250''Float32',
        'rps05''Float32',
        'rps10''Float32',
        'rps20''Float32',
        'rps50''Float32',
        'rps120''Float32',
        'rps250''Float32', }
    __creat_daily_related_table(STOCK_DAILY_TABLE, **columns)


def create_industry_daily_table():
    """
    创建板块日行情
    :return:
    "
""
    columns = {
        'date''Date',
        'code''String',
        'name''String',
        'open''Float32',
        'high''Float32',
        'low''Float32',
        'close''Float32',
        'change''Float32',
        'change_amount''Float32',
        'volume''Float64',
        'amount''Float64',
        'amplitude''Float32',
        'turnover''Float32',
        'amp05''Float32',
        'amp10''Float32',
        'amp20''Float32',
        'amp50''Float32',
        'amp120''Float32',
        'amp250''Float32',
        'ma05''Float32',
        'ma10''Float32',
        'ma20''Float32',
        'ma50''Float32',
        'ma120''Float32',
        'ma250''Float32',
        'rps05''Float32',
        'rps10''Float32',
        'rps20''Float32',
        'rps50''Float32',
        'rps120''Float32',
        'rps250''Float32', }
    __creat_daily_related_table(INDUSTRY_DAILY_TABLE, **columns)


def create_industry_constituent_stock_table():
    """
    创建板块成分股
    :return:
    "
""
    columns = {
        'industry_code''String',
        'stock_code''String',
        'industry_name''String',
        'stock_name''String'}
    __creat_common_table(
        table_name=INDUSTRY_CONSTITUENT_STOCK_TABLE,
        order_by='(javaHash(industry_code), javaHash(stock_code))',
        **columns)


def create_all_table():
    # 创建日行情数据表
    create_stock_daily_table()
    # 创建板块日行情表
    create_industry_daily_table()
    # 创建板块成分股表
    create_industry_constituent_stock_table()
    # 创建大盘数据表
    create_market_daily_table()


def optimize(table_name):
    """
    手动触发数据表去重操作
    场景: 在更新表后,由于重复的ReplacingMergeTree是不定时触发的,
    所以可以强制调用触发。
    :param table_name:
    :return:
    "
""
    sql = f'optimize table stock.{table_name}'
    client.execute(sql)


def drop_all_table():
    __drop_table(STOCK_DAILY_TABLE)
    __drop_table(INDUSTRY_DAILY_TABLE)
    __drop_table(INDUSTRY_CONSTITUENT_STOCK_TABLE)
    __drop_table(MARKET_DAILY_TABLE)


def optimize_all():
    optimize(STOCK_DAILY_TABLE)
    optimize(INDUSTRY_DAILY_TABLE)
    optimize(INDUSTRY_CONSTITUENT_STOCK_TABLE)
    optimize(MARKET_DAILY_TABLE)


if __name__ == '__main__':
    count = stock_length('000001')
    print(count)

效果

  1. 重构的时候要用新的表,这样在重构的过程中不会影响旧数据的运行,稳定后就可以把新表替换旧表的逻辑了。
alt
  1. 新数据替换旧表,接回原来的 ui 使用中,这个过程其实也很简单,替换数据库的查询类即可。

行业板块面板 ui

alt

单个板块的可视化,板块成分股 ui

alt

个股的数据 ui

alt

本文由 mdnice 多平台发布

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

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

相关文章

elastic-job 完结篇

一 elastic-job 1.1 案例场景分析 1.设置4个分片,10秒执行一次。 分片弹性扩容缩容机制测试: 测试1:测试窗口1不关闭,再次运行main方法查看控制台日志,注意修改application.properties中的 server.port&#xf…

Vant 移动端UI 组件自动引入

Vue项目中安装Vant # Vue 3 项目,安装最新版 Vant npm i vant 组件按需引入配置 Vant按需引入- - -安装:unplugin-vue-components 插件 unplugin-vue-components 插件可以在Vue文件中自动引入组件(包括项目自身的组件和各种组件库中的组件&…

7.运算符

目录 一.算数运算符 1、算术运算符 2、比较运算符 1、等号()用来判断数字、字符串和表达式是否相等。 2、安全等于运算符(<>) 3、不等于运算符(<>或者!) 4、小于或等于运算符(<) 5、小于运算符(<) 6、IS NULL(IS NULL)&#xff0c;IS NOT NULL 运算…

2352 智能社区医院管理系统JSP【程序源码+文档+调试运行】

摘要 本文介绍了一个智能社区医院管理系统的设计和实现。该系统包括管理员、护工和医生三种用户&#xff0c;具有社区资料管理、药品管理、挂号管理和系统管理等功能。通过数据库设计和界面设计&#xff0c;实现了用户友好的操作体验和数据管理。经过测试和优化&#xff0c;系…

WorkPlus Meet:局域网内部使用的高效视频会议系统

随着全球化和远程办公的趋势&#xff0c;视频会议已成为现代企业和机构不可或缺的沟通工具。而现在&#xff0c;大多数政企单位或者涉密强的企业&#xff0c;都会使用局域网部署的音视频会议系统&#xff0c;提供更高的安全性和隐私保护。因为音视频会议中可能涉及到公司机密和…

程序员的护城河:职业发展的关键元素

目录 1. 技术深度与广度 2. 项目经验与实际操作 3. 沟通与团队协作 4. 持续学习与自我更新 5. 社区参与与开源贡献 6. 创新思维与解决问题的能力 7. 职业规划与自我管理 结语 在科技日新月异的今天&#xff0c;程序员的竞争已经不再仅仅依赖于技术水平&#xff0c;而是…

C++: 内存管理 (new / delete)

文章目录 一. C/C 内存分布二. C 语言中动态内存管理方式: malloc/calloc/realloc/free三. C内存管理方式1. new / delete 操作内置类型2. new / delete 操作自定义类型 四. operator new 与 operator delete 函数五. new 和 delete 的实现原理1. 内置类型2. 自定义类型 六. 定…

【中间件篇-Redis缓存数据库02】Redis高级特性和应用(慢查询、Pipeline、事务、Lua)

Redis高级特性和应用(慢查询、Pipeline、事务、Lua) Redis的慢查询 许多存储系统&#xff08;例如 MySQL)提供慢查询日志帮助开发和运维人员定位系统存在的慢操作。所谓慢查询日志就是系统在命令执行前后计算每条命令的执行时间&#xff0c;当超过预设阀值,就将这条命令的相关…

互联网大厂招兵买马开发鸿蒙应用,移动开发的春天又来了?

日前&#xff0c;美团拟开发鸿蒙系统APP的多个相关岗位正招聘开发人员引发业内关注。事实上&#xff0c;鸿蒙开发者已经成为京东、WPS、凤凰新闻、微博等互联网大厂争相招聘的人才&#xff0c;且招聘岗位众多。也就是说&#xff0c;这些公司正在加快鸿蒙化开发&#xff0c;为鸿…

基于C#+WPF编写的调用讯飞星火大模型工具

工具源码&#xff1a;https://github.com/lishuangquan1987/XFYun.SparkChat 工具效果截图&#xff1a; 支持流式输出: 其中ApiKey/ApiSecret/AppId需要自己到讯飞星火大模型官网去注册账号申请&#xff0c;免费的。 申请地址&#xff1a;https://xinghuo.xfyun.cn/ 注册之…

【OpenCV实现图像:用OpenCV图像处理技巧之白平衡算法2】

文章目录 概要Gray-world AlgotithmGround Truth Algorithm结论&#xff1a; 概要 随着数字图像处理技术的不断发展&#xff0c;白平衡算法成为了图像处理中一个关键的环节。白平衡的目标是校正图像中的颜色偏差&#xff0c;使得白色在图像中呈现真实的白色&#xff0c;从而提…

利用MSF设置代理

1、介绍&#xff1a; 通过MSF拿到一个机器的权限后&#xff0c;通过MSF搭建socks代理&#xff0c;然后通内网。 拿到目标权限&#xff0c;有很多方法&#xff0c;比如&#xff1a;①ms17-010 ②补丁漏洞 ③MSF生成后门 在此直接使用MSF生成后门 MSF中有三个代理模块&#x…

【ATTCK】MITRE Caldera - 测试数据泄露技巧

CALDERA是一个由python语言编写的红蓝对抗工具&#xff08;攻击模拟工具&#xff09;。它是MITRE公司发起的一个研究项目&#xff0c;该工具的攻击流程是建立在ATT&CK攻击行为模型和知识库之上的&#xff0c;能够较真实地APT攻击行为模式。 通过CALDERA工具&#xff0c;安全…

【函数讲解】pygmo中的函数 fast_non_dominated_sorting() + 利用支配关系,学习一个SVM分类器,将解分为两类

这个函数是用来执行非支配排序的&#xff0c;可以分层构建Pareto&#xff0c;并返回每一层的解以及每个解支配其他解的索引、解被其他解支配的次数、解所在的非支配层级。这个函数对这些解进行非支配排序&#xff0c;并返回四个数组&#xff1a;ndf, dl, dc, 和 ndr。 ndf (Non…

基于单片机设计的智能风扇(红外线无线控制开关调速定时)

一、项目介绍 在炎热的夏季&#xff0c;风扇成为人们室内生活中必不可少的电器产品。然而&#xff0c;传统的风扇控制方式存在一些不便之处&#xff0c;比如需要手动操作开关、无法远程控制和调速&#xff0c;以及缺乏定时功能等。为了解决这些问题&#xff0c;设计了一款基于…

红黑树-RBTree

目录 1. 红黑树的概念2. 红黑树的性质3. 结点的定义4. 结点的插入5. 整体代码 1. 红黑树的概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式…

长安链可验证数据库,保证数据完整性的可信存证方案

近日&#xff0c;长安链发布“可验证数据库”实现了链上链下协同存储及数据完整性保证&#xff0c;显著提升长安链存储能力的可扩展性。 可信存证是联盟链最典型的应用场景&#xff0c;被广泛应用在司法、工业、农业、贸易等领域。联盟链的存证应用主要分为两个阶段&#xff1…

通过easyexcel导出数据到excel表格

这篇文章简单介绍一下怎么通过easyexcel做数据的导出&#xff0c;使用之前easyui构建的歌曲列表crud应用&#xff0c;添加一个导出按钮&#xff0c;点击的时候直接连接后端接口地址&#xff0c;在后端的接口完成数据的导出功能。 前端页面完整代码 let editingId; let request…

【Python】一篇带你掌握数据容器之列表

目录 前言&#xff1a; 一、列表 1.列表的定义 2.列表的下标索引 3.列表的常用操作 &#xff08;1&#xff09;index方法&#xff1a;查找某元素的下标 (2)修改特定位置下标的元素 &#xff08;3&#xff09;insert&#xff08;下标&#xff0c;元素&#xff09;方法&a…

基于SpringBoot+Vue的在线学习平台系统

基于SpringBootVue的在线学习平台系统的设计与实现~ 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBootMyBatisVue工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 主页 用户界面 登录界面 管理员界面 摘要 本文设计并实现了一套基于Spri…