大模型开发(十四):使用OpenAI Chat模型 + Google API实现一个智能收发邮件的AI应用程序

news2024/12/24 9:35:31

全文共1.2w余字,预计阅读时间约24~40分钟 | 满满干货(附代码),建议收藏!

本文目标:将Gmail API接入Chat模型,编写一个智能收发邮件的AI应用程序
在这里插入图片描述

代码下载点这里

一、背景

大模型应用开发从谷歌云入手进行学习和AI应用开发,是门槛最低、效率最高的方法。谷歌云上不仅有成百上千的各类应用的API,而且拥有统一的API制式和权限管理方法,调用过程非常方便,一个账号进行一次项目认证即可使用海量的API,外加详细完整的开发者文档,更是极大程度降低了上手使用API的门槛。

同时,谷歌云是功能完备的应用开发平台,如果不仅是尝试使用API进行前期的探索,而是希望真正意义上的完成企业级应用开发,也完全可以在谷歌云上进行。谷歌云不仅提供了完整的在线应用开发与发布流程,而且提供了(相对)廉价、稳定的云服务,开发者开发的应用程序可以直接在云端运行,并享受谷歌云提供的一整套应急、维护流程,以及实时可视化监控页面。

但是一个真实存在的问题是:国内还是存在访问限制,需要使用魔法,本文的目标是将Gmail API接入Chat模型,编写一个智能收发邮件的AI应用程序。

二、Gmail API的OAuth授权

首先第一步,要调用谷歌的API,肯定是要完成授权。具体谷歌云Google Cloud与谷歌云API库的介绍,及如何完成Gmail API的OAuth授权过程,如果您了解的话可以跳过这一步,直接进入下面代码内容,如不清楚如何操作的,请看下面链接:

Gmail API的OAuth授权过程的成功实现非常重要,使用谷歌云的其他API来构建更加复杂的AI应用程序,其中大多数都需要进行OAuth授权,如果已经完成了授权,则该凭证也是可以应用于其他API调用的,完成授权也是进行AI应用程序开发的前提。

AI应用开发:Gmail API如何实现OAuth授权

三、借助Gmail API构建智能邮件收发应用程序

3.1 在Chat模型中添加查阅邮件功能

先尝试将Gmail API接入Chat模型中。在这三篇文章中:

大模型开发(十一):Chat Completions模型的Function calling功能详解

大模型开发(十二):Function calling 流程优化并实现多轮对话任务

大模型开发(十三):Function calling调用外部工具API,实现实时天气查询

已经跑通了优化后的Function calling功能执行流程,对于开发一个AI应用程序,在确定了基本功能实现的目标之后,总共分四步进行:

  1. 测试该外部函数功能是否具备可行性
  2. 验证大语言模型是否具备解读外部函数结果和准确翻译外部函数参数的能力
  3. 据此创建能够调用外部工具API外部函数,并编写非常详细函数说明
  4. 将定义好的外部函数带入run_conversation函数或者chat_with_model函数测试对话效果。

所以首先尝试通过外部函数赋予Chat模型查阅最近一封邮件的功能,具体实现过程如下:

  • Step 1:测试外部函数功能可行性

先测试能否通过调用Gmail API查阅最近得一封邮件信息,包括发件人、日期和邮件内容,代码如下:

# 从本地文件中加载凭据
creds = Credentials.from_authorized_user_file('token.json')

# 创建 Gmail API 客户端
service = build('gmail', 'v1', credentials=creds)

# 列出用户的一封最新邮件
results = service.users().messages().list(userId='me', maxResults=1).execute()
messages = results.get('messages', [])

# 遍历邮件
for message in messages:
    # 获取邮件的详细信息
    msg = service.users().messages().get(userId='me', id=message['id']).execute()

    # 获取邮件头部信息
    headers = msg['payload']['headers']

    # 提取发件人、发件时间
    From, Date = "", ""
    for h in headers:
        name = h['name']
        if name.lower() == 'from':
            From = h['value']
        if name.lower() == 'date':
            Date = h['value']

    # 提取邮件正文
    if 'parts' in msg['payload']:
        part = msg['payload']['parts'][0]
        if part['mimeType'] == 'text/plain':
            data = part['body']["data"]
        else:
            data = msg['payload']['body']["data"]
    else:
        data = msg['payload']['body']["data"]
        
    data = data.replace("-","+").replace("_","/")
    decoded_data = base64.b64decode(data)
    str_text = str(decoded_data, "utf-8")
    msg_str = email.message_from_string(str_text)

    if msg_str.is_multipart():
        text = msg_str.get_payload()[0]  
    else:
        text = msg_str.get_payload()
    
    print('From: {}'.format(From[:8]))
    print('Date: {}'.format(Date))
    print('Content: {}'.format(text))

看下输出结果:

image-20230727160514201

看下邮箱状态:

image-20230727160611804

上述代码的核心流程是通过service.users().messages().list(userId=‘me’, maxResults=1).execute()获取了最近一封邮件的信息,并最终保留在msg中。

更多关于Gmail API返回结果信息可参考Gmail API官网功能介绍:https://developers.google.com/gmail/api/guides

  • Step 2:验证模型能否解读Gmail API返回结果

Gmail API 在不处理的情况下,返回的结果是这样的:

image-20230727160830560

包括发件人信息、收件时间和邮件正文信息等各种详细的信息,为了满足更多不同问题的回答,考虑直接让Chat模型解读msg结果,通过如下流程测试Chat模型是否熟悉Gmail API这一功能及其返回结果的解读方式,代码如下:

import os
import openai
openai.api_key = os.getenv("OPENAI_API_KEY")

response = openai.ChatCompletion.create(
  model="gpt-4-0613",
  messages=[
    {"role": "system", "content": "这是我的Gmail邮箱最近一封邮件的内容:%s" % msg},
    {"role": "system", "content": "邮件内容是由Gmail API获取"},
    {"role": "user", "content": "请问我的Gmail最近一封邮件是谁发送的,具体内容是什么?"}
  ]
)
response.choices[0].message['content']

看下输出结果:

image-20230727161135323

Chat模型能够非常好的理解msg中所包含的信息。而后在设计外部函数和Chat模型的通信流程时,即可直接让外部函数对大语言模型直接输出msg结果。

  • Step 3:创建外部函数

按照Step 1中的代码流程,创建一个能够返回最近一封邮件信息的msg对象,并输出为JSON格式:

def get_latest_email(userId):
    """
    查询Gmail邮箱中最后一封邮件信息
    :param userId: 必要参数,字符串类型,用于表示需要查询的邮箱ID,\
    注意,当查询我的邮箱时,userId需要输入'me';
    :return:包含最后一封邮件全部信息的对象,该对象由Gmail API创建得到
    """
    # 从本地文件中加载凭据
    creds = Credentials.from_authorized_user_file('token.json')
    
    # 创建 Gmail API 客户端
    service = build('gmail', 'v1', credentials=creds)
    
    # 列出用户的一封最新邮件
    results = service.users().messages().list(userId=userId, maxResults=1).execute()
    messages = results.get('messages', [])

    # 遍历邮件
    for message in messages:
        # 获取邮件的详细信息
        msg = service.users().messages().get(userId='me', id=message['id']).execute()
        
    return json.dumps(msg)
functions_list = [get_latest_email]

在编写完函数之后,为了确保最终的对话函数能顺利运行,先手动测试auto_functions能否根据函数说明正确编写functions参数,以及测试下Chat模型能否正确识别functions参数格式要求,并将用户需求顺利解读和翻译为函数可以接受的参数形式,先引入functions参数自动编写函数:

def auto_functions(functions_list):
    """
    Chat模型的functions参数编写函数
    :param functions_list: 包含一个或者多个函数对象的列表;
    :return:满足Chat模型functions参数要求的functions对象
    """
    def functions_generate(functions_list):
        # 创建空列表,用于保存每个函数的描述字典
        functions = []
        
        def chen_ming_algorithm(data):
            """
            陈明算法函数,该函数定义了一种特殊的数据集计算过程
            :param data: 必要参数,表示带入计算的数据表,用字符串进行表示
            :return:陈明函数计算后的结果,返回结果为表示为JSON格式的Dataframe类型对象
            """
            df_new = pd.read_json(data)
            res = np.sum(df_new, axis=1) - 1
            return res.to_json(orient='records')
        
        chen_ming_function_description = inspect.getdoc(chen_ming_algorithm)
        
        chen_ming_function_name = chen_ming_algorithm.__name__
        
        chen_ming_function = {"name": "chen_ming_algorithm",
                              "description": "用于执行陈明算法的函数,定义了一种特殊的数据集计算过程",
                              "parameters": {"type": "object",
                                             "properties": {"data": {"type": "string",
                                                                     "description": "执行陈明算法的数据集"},
                                                           },
                                             "required": ["data"],
                                            },
                             }

        
        # 对每个外部函数进行循环
        for function in functions_list:
            # 读取函数对象的函数说明
            function_description = inspect.getdoc(function)
            # 读取函数的函数名字符串
            function_name = function.__name__

            user_message1 = '以下是某函数说明:%s。' % chen_ming_function_description +\
                            '根据这个函数的函数说明,请帮我创建一个function对象,用于描述这个函数的基本情况。这个function对象是一个JSON格式的字典,\
                            这个字典有如下5点要求:\
                            1.字典总共有三个键值对;\
                            2.第一个键值对的Key是字符串name,value是该函数的名字:%s,也是字符串;\
                            3.第二个键值对的Key是字符串description,value是该函数的函数的功能说明,也是字符串;\
                            4.第三个键值对的Key是字符串parameters,value是一个JSON Schema对象,用于说明该函数的参数输入规范。\
                            5.输出结果必须是一个JSON格式的字典,只输出这个字典即可,前后不需要任何前后修饰或说明的语句' % chen_ming_function_name
            
            
            assistant_message1 = json.dumps(chen_ming_function)
            
            user_prompt = '现在有另一个函数,函数名为:%s;函数说明为:%s;\
                          请帮我仿造类似的格式为当前函数创建一个function对象。' % (function_name, function_description)

            response = openai.ChatCompletion.create(
                              model="gpt-4-0613",
                              messages=[
                                {"role": "user", "name":"example_user", "content": user_message1},
                                {"role": "assistant", "name":"example_assistant", "content": assistant_message1},
                                {"role": "user", "name":"example_user", "content": user_prompt}]
                            )
            functions.append(json.loads(response.choices[0].message['content']))
        return functions
    
    max_attempts = 3
    attempts = 0

    while attempts < max_attempts:
        try:
            functions = functions_generate(functions_list)
            break  # 如果代码成功执行,跳出循环
        except Exception as e:
            attempts += 1  # 增加尝试次数
            print("发生错误:", e)
            if attempts == max_attempts:
                print("已达到最大尝试次数,程序终止。")
                raise  # 重新引发最后一个异常
            else:
                print("正在重新运行...")
    return functions

看一下执行结果:

image-20230727162659259

functions参数编写完全没问题,接下来测试Chat模型能否顺利创建满足格式的参数:

response = openai.ChatCompletion.create(
        model="gpt-4-0613",
        messages=[{"role": "user", "content": '请帮我查下我Gmail邮箱中最后一封邮件信息'}],
        functions=functions,
        function_call="auto",  
    )

看下结果:

image-20230728083657580

  • Step 4:验证对话效果

最后来测试下对话效果,仍然导入之前写的自动执行外部函数调用的Chat对话模型函数多轮对话函数

def run_conversation(messages, functions_list=None, model="gpt-4-0613"):
    """
    能够自动执行外部函数调用的Chat对话模型
    :param messages: 必要参数,字典类型,输入到Chat模型的messages参数对象
    :param functions_list: 可选参数,默认为None,可以设置为包含全部外部函数的列表对象
    :param model: Chat模型,可选参数,默认模型为gpt-4
    :return:Chat模型输出结果
    """
    # 如果没有外部函数库,则执行普通的对话任务
    if functions_list == None:
        response = openai.ChatCompletion.create(
                        model=model,
                        messages=messages,
                        )
        response_message = response["choices"][0]["message"]
        final_response = response_message["content"]
        
    # 若存在外部函数库,则需要灵活选取外部函数并进行回答
    else:
        # 创建functions对象
        functions = auto_functions(functions_list)
        # 创建外部函数库字典
        available_functions = {func.__name__: func for func in functions_list}

        # first response
        response = openai.ChatCompletion.create(
                        model=model,
                        messages=messages,
                        functions=functions,
                        function_call="auto")
        response_message = response["choices"][0]["message"]

        # 判断返回结果是否存在function_call,即判断是否需要调用外部函数来回答问题
        if response_message.get("function_call"):
            # 需要调用外部函数
            # 获取函数名
            function_name = response_message["function_call"]["name"]
            # 获取函数对象
            fuction_to_call = available_functions[function_name]
            # 获取函数参数
            function_args = json.loads(response_message["function_call"]["arguments"])
            # 将函数参数输入到函数中,获取函数计算结果
            function_response = fuction_to_call(**function_args)

            # messages中拼接first response消息
            messages.append(response_message)  
            # messages中拼接函数输出结果
            messages.append(
                {
                    "role": "function",
                    "name": function_name,
                    "content": function_response,
                }
            )  
            # 第二次调用模型
            second_response = openai.ChatCompletion.create(
                model=model,
                messages=messages,
            )  
            # 获取最终结果
            final_response = second_response["choices"][0]["message"]["content"]
        else:
            final_response = response_message["content"]
    
    return final_response
def chat_with_model(functions_list=None, 
                    prompt="你好呀", 
                    model="gpt-4-0613", 
                    system_message=[{"role": "system", "content": "你是以为乐于助人的助手。"}]):
    
    messages = system_message
    messages.append({"role": "user", "content": prompt})
    
    while True:           
        answer = run_conversation(messages=messages, 
                                    functions_list=functions_list, 
                                    model=model)
        
        
        print(f"模型回答: {answer}")

        # 询问用户是否还有其他问题
        user_input = input("您还有其他问题吗?(输入退出以结束对话): ")
        if user_input == "退出":
            break

        # 记录用户回答
        messages.append({"role": "user", "content": user_input})

先测试不带入外部函数get_latest_email时:

image-20230728084323222

再测试带入外部函数get_latest_email时:

image-20230728084651589

3.2 在Chat模型中添加发送邮件功能

  • Step 1:重新获取授权

上述创建的token其实只包含了阅读邮件的API授权,并未包含发送邮件API授权。因此这里首先需要进行API权限修改,即再次申请获取Gmail发送邮件API授权。使用之前的授权部分代码进行运行,并将send权限的授权文件保存在本地token_send.json文件中,代码如下:

SCOPES = ['https://www.googleapis.com/auth/gmail.send']

flow = InstalledAppFlow.from_client_secrets_file(
                'credentials-web.json', SCOPES)
creds = flow.run_local_server(port=8000, access_type='offline', prompt='consent')

with open('token_send.json', 'w') as token:
    token.write(creds.to_json())

如果这里不清楚怎么授权的,看Gmail API的OAuth授权部分

image-20230728085549688

**谷歌云API中一个(类)API在进行使用时会分根据API功能不同,需要进行多次授权。**不同之处就在于SCOPES变量的设置,SCOPES是一个包含多个地址的列表,而其中每个地址就代表着不同请求的发送地址,换而言之就代表着不同API权限,地址尾部为gmail.send则表示发送邮件的API,而尾部为gmail.readonly则表示只能阅读邮件。具体不同的API功能对应可以参考官方说明:https://developers.google.com/gmail/api/auth/scopes?hl=zh_CN

也可以一次性获得包含多个权限的授权文件,此时SCOPES可以按照如下方式进行定义,此时授权文件可以同时允许执行文件阅读和发送:

SCOPES = ['https://www.googleapis.com/auth/gmail.send','https://www.googleapis.com/auth/gmail.readonly']
  • Step 2:测试发送功能

完成授权之后,即可使用Gmail的发送邮件功能了,将授权文件改为token_send.json,具体代码实现流程如下:

from googleapiclient.discovery import build
from google.oauth2.credentials import Credentials
from email.mime.text import MIMEText
import base64

# 从本地文件中加载凭据
creds = Credentials.from_authorized_user_file('token_send.json')

# 创建 Gmail API 客户端
service = build('gmail', 'v1', credentials=creds)

def create_message(sender, to, subject, message_text):
    """创建一个MIME邮件"""
    message = MIMEText(message_text)
    message['to'] = to
    message['from'] = sender
    message['subject'] = subject
    raw_message = base64.urlsafe_b64encode(message.as_string().encode('utf-8')).decode('utf-8')
    return {
        'raw': raw_message
    }

def send_message(service, user_id, message):
    """发送邮件"""
    try:
        sent_message = service.users().messages().send(userId=user_id, body=message).execute()
        print(f'Message Id: {sent_message["id"]}')
        return sent_message
    except Exception as e:
        print(f'An error occurred: {e}')
        return None

# 创建邮件,发件人、收件邮箱、邮件主题和邮件内容
message = create_message('me', 'snowball950123@gmail.com', '测试', '测试Gmail API 的邮件发送功能')

# 发送邮件
send_message(service, 'me', message)

看下结果:

image-20230728090024864

MIME(Multipurpose Internet Mail Extensions,多用途互联网邮件扩展)是一种互联网标准,它扩展了原始的电子邮件规范(只能处理ASCII文本),使电子邮件能够支持其他类型的数据,如文本(包括非ASCII字符)、图像、音频、视频和应用程序数据

在Python中,email.mime模块提供了创建和修改MIME消息的类。这些类可以创建包含多种数据类型的复杂邮件消息,包含文本、HTML、附件和嵌入的图像等的邮件。可以查看创建的message对象:

image-20230728090314516

也可以通过sent_message = service.users().messages().send(userId=user_id, body=message).execute()方式调用Gmail API。

看下邮箱是否收到:

image-20230728090107421

  • Step 3:封装可被Chat模型调用的外部函数
def send_email(to, subject, message_text):
    """
    借助Gmail API创建并发送邮件函数
    :param to: 必要参数,字符串类型,用于表示邮件发送的目标邮箱地址;
    :param subject: 必要参数,字符串类型,表示邮件主题;
    :param message_text: 必要参数,字符串类型,表示邮件全部正文;
    :return:返回发送结果字典,若成功发送,则返回包含邮件ID和发送状态的字典。
    """
    
    creds_file='token_send.json'
    
    def create_message(to, subject, message_text):
        """创建一个MIME邮件"""
        message = MIMEText(message_text)
        message['to'] = to
        message['from'] = 'me'
        message['subject'] = subject
        raw_message = base64.urlsafe_b64encode(message.as_string().encode('utf-8')).decode('utf-8')
        return {
            'raw': raw_message
        }

    def send_message(service, user_id, message):
        """发送邮件"""
        try:
            sent_message = service.users().messages().send(userId=user_id, body=message).execute()
            print(f'Message Id: {sent_message["id"]}')
            return sent_message
        except Exception as e:
            print(f'An error occurred: {e}')
            return None

    # 从本地文件中加载凭据
    creds = Credentials.from_authorized_user_file(creds_file)

    # 创建 Gmail API 客户端
    service = build('gmail', 'v1', credentials=creds)

    message = create_message(to, subject, message_text)
    res = send_message(service, 'me', message)

    return json.dumps(res)
  • Step 3:测试下functions参数
functions = auto_functions(functions_list)
functions

image-20230728091143064

functions参数创建没问题,进一步测试Chat模型能否顺利创建函数所需参数:

response = openai.ChatCompletion.create(
        model="gpt-4-0613",
        messages=[{"role": "user", "content": '我想发送一个Gmail邮件,主要内容是:让小陈明天早上9点半来我办公室开会,商量一下我的100亿该怎么花'}],
        functions=functions,
        function_call="auto"
    )

response

看下返回结果:

image-20230728095249243

模型会根据语义创建邮件的主题和内容

  • Step 4:多轮对话验证

分别看下不调用外部函数send_email 和调用send_email时模型的处理:

image-20230728101052498

然后在邮箱中查看查收的邮件:

image-20230728101206448

至此,就将邮件发送功能也完整集成到Chat模型当中了。

3.3 在Chat模型中同时调用邮件发送和查询功能

在分别跑通了邮件发送功能之后,可以尝试在一个对话中同时调用这两个功能。毕竟大多数AI应用都是围绕某一方面应用的多功能集合,例如对于一个智能收发邮件助手的AI应用来说,收件和发件肯定是最基本的功能需求。

要在一个Chat模型中集成收件和发件两方面功能,只需要在functions_list同时添加get_latest_email和send_email即可,代码如下:

functions_list = [get_latest_email, send_email]
chat_with_model(functions_list=functions_list,
                system_message=[{"role": "system", "content": "小陈的邮箱地址是:snowball950123@gmail.com"}]

看下结果:

image-20230729094357803

image-20230729094428977

至此,就完成了在一个Chat对话中同时调用发件和收件功能。

四、总结

通过上面的实践能够发现,伴随着Chat模型中集成的外部工具API越来越多,整个应用也会看起来变得更加智能。而实际上也确实如此,在借助Function calling进行AI应用开发的过程中,外部函数库的功能范围是决定当前AI应用“智能”与否的关键。

但其实需要思考一个问题:创建一个包含外部工具API的外部函数的过程并不简单,一方面需要反复尝试来获取对应的API权限,另一方面也需要拥有一定的API调用知识,才能够顺利编写外部函数代码。仅仅是创建一个智能邮件收发的应用,就花费了不少的时间在API获取方法及函数编写方法。

但其实,这个开发过程或许是可以一定程度简化,简化的核心思路就是借助AI(Chat模型)来协助完成AI应用的开发

最后,感谢您阅读这篇文章!如果您觉得有所收获,别忘了点赞、收藏并关注我,这是我持续创作的动力。您有任何问题或建议,都可以在评论区留言,我会尽力回答并接受您的反馈。如果您希望了解某个特定主题,也欢迎告诉我,我会乐于创作与之相关的文章。谢谢您的支持,期待与您共同成长!

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

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

相关文章

Python爬虫的urlib的学习(学习于b站尚硅谷)

目录 一、页面结构的介绍  1.学习目标  2.为什么要了解页面&#xff08;html&#xff09;  3. html中的标签&#xff08;仅介绍了含表格、无序列表、有序列表、超链接&#xff09;  4.本节的演示 二、Urllib  1.什么是互联网爬虫&#xff1f;  2.爬虫核心  3.爬虫…

Sentinel 容灾中心的使用

Sentinel 容灾中心的使用 往期文章 Nacos环境搭建Nacos注册中心的使用Nacos配置中心的使用 熔断/限流结果 Jar 生产者 spring-cloud-alibaba&#xff1a;2021.0.4.0 spring-boot&#xff1a;2.6.8 spring-cloud-loadbalancer&#xff1a;3.1.3 sentinel&#xff1a;2021.0…

如何快速模拟一个后端 API

第一步&#xff1a;创建一个文件夹&#xff0c;用来存储你的数据 数据&#xff1a; {"todos": [{ "id": 1, "text": "学习html44", "done": false },{ "id": 2, "text": "学习css", "…

只会“点点点”,凭什么让开发看的起你?

众所周知&#xff0c;如今无论是大厂还是中小厂&#xff0c;自动化测试基本是标配了&#xff0c;毕竟像双 11、618 这种活动中庞大繁杂的系统&#xff0c;以及多端发布、多版本、机型发布等需求&#xff0c;但只会“写一些自动化脚本”很难胜任。这一点在招聘要求中就能看出来。…

Android 面试题 优化 (一)

&#x1f525; Android性能优化指标 &#x1f525; ​ &#x1f525; 包体积优化 &#x1f525; 安装包的大小会影响用户的安装率&#xff0c;如果一个包的太大&#xff0c;用户安装的意愿会大大降低。 经过包分析可以看到&#xff0c;安装包最大的部分是资源和第三方库&#…

奥迪A3:最新款奥迪A3内饰设计及智能科技应用

奥迪A3一直以来都是奥迪的入门级车型&#xff0c;但这并不意味着它在科技和内饰方面会有所退步。最新款奥迪A3的内饰设计和智能科技应用让人们再次惊叹奥迪的创新能力。 内饰设计 奥迪A3最新款的内饰设计引入了奥迪最新的设计元素&#xff0c;比如8.8英寸的中控显示屏&#xf…

山西电力市场日前价格预测【2023-07-30】

日前价格预测 预测明日&#xff08;2023-07-30&#xff09;山西电力市场全天平均日前电价为287.10元/MWh。其中&#xff0c;最高日前电价为309.20元/MWh&#xff0c;预计出现在09: 15。最低日前电价为252.82元/MWh&#xff0c;预计出现在24: 00。 价差方向预测 1&#xff1a;实…

Dendrogram | 今天是人见人爱、花见花开的环形Dendrogram!~(附完整代码)

1写在前面 好长时间没更新了&#xff0c;这周真的是天天都在手术室度过&#xff0c;常讲到的一句话就是苦的一比啊。&#x1fae0; 很久没有见过外面的世界了&#xff0c;世界那么大&#xff0c;我也想去看看&#xff01;~&#x1f602; 废话太多了&#xff0c;今天的教程是环形…

网络安全/信息安全(黑客技术)自学笔记

一、网络安全基础知识 1.计算机基础知识 了解了计算机的硬件、软件、操作系统和网络结构等基础知识&#xff0c;可以帮助您更好地理解网络安全的概念和技术。 2.网络基础知识 了解了网络的结构、协议、服务和安全问题&#xff0c;可以帮助您更好地解决网络安全的原理和技术…

从分片传输到并行传输之大文件传输加速技术

随着大文件的传输需求越来越多&#xff0c;传输过程中也会遇到很多困难&#xff0c;比如传输速度慢、文件安全性低等。为了克服这些困难&#xff0c;探讨各种大文件传输加速技术。其中&#xff0c;分片传输和并行传输是两种比较常见的技术&#xff0c;下面将对它们进行详细说明…

WEB:Web_python_template_injection

背景知识 python模板注入 ssit 题目 打开题目&#xff0c;发现页面提示&#xff0c;翻译为python模板注入 先测试是否存在注入 可以发现被执行了 先查看所有的子类 payload {{[].__class__.__base__.__subclasses__()}} 利用site.Printer的os模块执行命令 payload {{.__…

CAN转EtherNet/IP网关can协议破解服务

JM-EIP-CAN 是自主研发的一款 ETHERNET/IP 从站功能的通讯网关。该产品主要功能是将各种 CAN 总线和 ETHERNET/IP 网络连接起来。 本网关连接到 ETHERNET/IP 总线中做为从站使用&#xff0c;连接到 CAN 总线中根据节点号进行读写。 技术参数 ETHERNET/IP 技术参数 网关做为 …

C# Socket实际应用案例与属性详解

引言 Socket是一个在网络编程中非常常见和重要的概念&#xff0c;它提供了一种通信机制&#xff0c;使不同的计算机之间可以进行数据传输。本文将介绍C#中Socket的实际应用案例&#xff0c;并对Socket的常用属性进行详细解析。 文章目录 1. Socket的实际应用案例2. Socket的属…

CAS比较并交换具体实现细节

线程2读取内存值到工作内存中&#xff0c;设置一个预估值与读入的值相等&#xff0c;线程1也同样读入。 线程2进行操作&#xff0c;之后在写入内存前&#xff0c;将预估值与内存值作比较&#xff0c;看内存值是否修改过。 如果线程2比较完相同&#xff0c;则修改内存值为1&am…

开源了!最强原创图解八股文面试网来袭

强烈推荐 Github上业内新晋的一匹黑马—Java图解八股文面试网—Java2Top.cn&#xff0c;图解 Java 大厂面试题&#xff0c;深入全面&#xff0c;真的强烈推荐~ 这是一个二本逆袭阿里的大佬根据自己秋招上岸所看过的相关专栏&#xff0c;面经&#xff0c;课程&#xff0c;结合自…

211. 添加与搜索单词 - 数据结构设计---------------字典树

211. 添加与搜索单词 - 数据结构设计 原题链接&#xff1a;完成情况&#xff1a;解题思路&#xff1a;参考代码&#xff1a; 原题链接&#xff1a; 211. 添加与搜索单词 - 数据结构设计 https://leetcode.cn/problems/design-add-and-search-words-data-structure/descriptio…

tinkerCAD案例:19. Move Circuit Assembly 移动电路组件

tinkerCAD案例&#xff1a;19. Move Circuit Assembly 移动电路组件 In this tutorial, you’ll learn how to add the Move circuit assembly to a design. 在本教程中&#xff0c;您将学习如何将移动电路装配体添加到设计中。 When you assemble the Move circuit assembly…

【Docker】Docker应用部署之Docker容器安装Tomcat

目录 一、搜索镜像 二、拉取镜像 三、创建容器 四、测试使用 一、搜索镜像 docker search tomcat 二、拉取镜像 docker pull tomcat:版本 三、创建容器 首先在宿主机创建数据卷的目录 mkdir /root/tomcat # 创建目录 cd /root/tomcat # 进入目录 docker run -id -…

解决mysqld服务启动失败

1、进程 首先查看下mysql进程: ps -aux | grep mysql有进程号 2、所有者和所属组为mysql 查看/usr/local/MySQL/data/mysqld.pid所有者和所属组是否为mysql 原来是权限有问题&#xff0c;那么更改权限&#xff08;还需要加权限&#xff09;3、 重新启动服务

2023牛客暑期多校-J-Qu‘est-ce Que C‘est?(DP)

题意&#xff1a; 给定长度为n的数列,要求每个数都在的范围&#xff0c;且任意长度大于等于2的区间和都大于等于0&#xff0c;问方案数。。 思路&#xff1a; 首先要看出是dp题&#xff0c;用来表示遍历到第i位且后缀和最小为x的可行方案数&#xff08;此时的后缀可以只有最…