自动化点击工具

python 复制代码
import tkinter as tk
from tkinter import messagebox, simpledialog, ttk
import pyautogui
import time
import threading
import json
import os
from pynput import mouse, keyboard
from datetime import datetime, timedelta


class AutoClickerApp:
    def __init__(self, root):
        self.root = root
        self.root.title("全能自动化点击工具")
        self.root.geometry("800x650")
        self.root.resizable(True, True)

        # 基础设置
        self.font = ('SimHei', 10)
        self.style = ttk.Style()
        self.style.configure('TLabelframe.Label', font=self.font)
        self.style.configure('TButton', font=self.font)
        self.style.configure('TLabel', font=self.font)
        self.style.configure('TEntry', font=self.font)
        self.style.configure('TCombobox', font=self.font)
        self.style.configure('Treeview', rowheight=25, font=self.font)

        # 参数设置
        self.double_click_threshold = 0.3  # 双击阈值(秒)
        self.pyautogui_delay = 0  # 鼠标移动动画时间(设置为0取消动画)
        self.last_event_time = None

        # 数据存储
        self.events = []  # 存储事件格式:(类型, x, y, 延迟, 附加数据)
        self.running = False
        self.recording = False
        self.tasks = []  # 存储定时任务

        # 监听器
        self.mouse_listener = None
        self.keyboard_listener = None

        # 创建界面
        self.create_widgets()
        self.load_config_list()
        self.refresh_task_configs()

    def create_widgets(self):
        # 主框架 - 使用网格布局
        self.main_frame = ttk.Frame(self.root)
        self.main_frame.pack(fill='both', expand=True, padx=10, pady=10)

        # 左侧 - 事件记录区域
        left_frame = ttk.Frame(self.main_frame)
        left_frame.grid(row=0, column=0, sticky='nsew', padx=5, pady=5)

        # 事件列表框架
        frame_list = ttk.LabelFrame(left_frame, text="记录的事件列表")
        frame_list.pack(fill='both', expand=True, padx=5, pady=5)

        # 事件列表树
        self.tree = ttk.Treeview(frame_list, columns=('序号', '类型', '位置', '延迟', '内容'), show='headings')
        self.tree.heading('#1', text='序号', anchor='w')
        self.tree.heading('#2', text='类型', anchor='w')
        self.tree.heading('#3', text='位置', anchor='w')
        self.tree.heading('#4', text='延迟(秒)', anchor='w')
        self.tree.heading('#5', text='内容', anchor='w')
        self.tree.pack(side='left', fill='both', expand=True)

        scrollbar = ttk.Scrollbar(frame_list, orient='vertical', command=self.tree.yview)
        scrollbar.pack(side='right', fill='y')
        self.tree.config(yscrollcommand=scrollbar.set)

        # 控制按钮框架
        btn_frame = ttk.Frame(left_frame)
        btn_frame.pack(fill='x', padx=5, pady=5)

        self.btn_record = ttk.Button(btn_frame, text="开始记录", command=self.start_recording, width=12)
        self.btn_record.pack(side='left', padx=3)

        self.btn_stop = ttk.Button(btn_frame, text="停止记录", command=self.stop_recording, state='disabled', width=12)
        self.btn_stop.pack(side='left', padx=3)

        self.btn_edit = ttk.Button(btn_frame, text="编辑延迟", command=self.edit_delay, state='disabled', width=12)
        self.btn_edit.pack(side='left', padx=3)

        self.btn_edit_all = ttk.Button(btn_frame, text="编辑全部延迟", command=self.edit_all_delay, state='disabled',
                                       width=12)
        self.btn_edit_all.pack(side='left', padx=3)

        self.btn_clear = ttk.Button(btn_frame, text="清除所有", command=self.clear_events, width=12)
        self.btn_clear.pack(side='left', padx=3)

        # 执行设置框架
        execute_frame = ttk.LabelFrame(left_frame, text="执行设置")
        execute_frame.pack(fill='x', padx=5, pady=5)

        ttk.Label(execute_frame, text="循环次数:").pack(side='left', padx=5)
        self.loop_var = tk.StringVar(value="1")
        ttk.Entry(execute_frame, textvariable=self.loop_var, width=5).pack(side='left', padx=5)
        ttk.Label(execute_frame, text="次").pack(side='left')

        self.btn_run = ttk.Button(execute_frame, text="开始执行", command=self.start_execution, width=12)
        self.btn_run.pack(side='right', padx=5)

        # 状态显示
        self.status = ttk.Label(left_frame, text="就绪(执行中按Q键退出)", foreground="blue",
                                font=(self.font[0], 10, 'bold'))
        self.status.pack(pady=5)

        # 配置管理框架
        config_frame = ttk.LabelFrame(left_frame, text="配置管理")
        config_frame.pack(fill='x', padx=5, pady=5)

        ttk.Label(config_frame, text="配置名称:").pack(side='left', padx=5)
        self.config_name = tk.StringVar()
        ttk.Entry(config_frame, textvariable=self.config_name, width=20).pack(side='left', padx=5)
        ttk.Button(config_frame, text="保存配置", command=self.save_config, width=10).pack(side='left', padx=5)

        ttk.Label(config_frame, text="加载配置:").pack(side='left', padx=15)
        self.config_list = ttk.Combobox(config_frame, width=20)
        self.config_list.pack(side='left', padx=5)
        ttk.Button(config_frame, text="加载", command=self.load_config, width=10).pack(side='left', padx=5)

        # 右侧 - 定时任务区域
        right_frame = ttk.Frame(self.main_frame)
        right_frame.grid(row=0, column=1, sticky='nsew', padx=5, pady=5)

        # 定时任务框架
        task_frame = ttk.LabelFrame(right_frame, text="定时任务设置")
        task_frame.pack(fill='both', expand=True, padx=5, pady=5)

        # 任务列表树
        self.task_tree = ttk.Treeview(task_frame, columns=('序号', '时间', '配置', '状态'), show='headings')
        self.task_tree.heading('#1', text='序号', anchor='w')
        self.task_tree.heading('#2', text='执行时间', anchor='w')
        self.task_tree.heading('#3', text='配置名称', anchor='w')
        self.task_tree.heading('#4', text='状态', anchor='w')
        self.task_tree.pack(side='left', fill='both', expand=True)

        task_scrollbar = ttk.Scrollbar(task_frame, orient='vertical', command=self.task_tree.yview)
        task_scrollbar.pack(side='right', fill='y')
        self.task_tree.config(yscrollcommand=task_scrollbar.set)

        # 任务控制按钮
        task_btn_frame = ttk.Frame(right_frame)
        task_btn_frame.pack(fill='x', padx=5, pady=5)

        self.btn_add_task = ttk.Button(task_btn_frame, text="添加任务", command=self.add_task, width=12)
        self.btn_add_task.pack(side='left', padx=3)

        self.btn_del_task = ttk.Button(task_btn_frame, text="删除任务", command=self.delete_task, state='disabled',
                                       width=12)
        self.btn_del_task.pack(side='left', padx=3)

        self.btn_start_tasks = ttk.Button(task_btn_frame, text="启动任务", command=self.start_tasks, state='disabled',
                                          width=12)
        self.btn_start_tasks.pack(side='left', padx=3)

        self.btn_stop_tasks = ttk.Button(task_btn_frame, text="停止任务", command=self.stop_tasks, state='disabled',
                                         width=12)
        self.btn_stop_tasks.pack(side='left', padx=3)

        # 任务时间设置
        time_frame = ttk.Frame(right_frame)
        time_frame.pack(fill='x', padx=5, pady=5)

        ttk.Label(time_frame, text="执行时间:").pack(side='left', padx=5)
        self.time_var = tk.StringVar(value=datetime.now().strftime("%H:%M:%S"))
        ttk.Entry(time_frame, textvariable=self.time_var, width=10).pack(side='left', padx=5)
        ttk.Label(time_frame, text="格式: HH:MM:SS").pack(side='left', padx=5)

        # 任务配置选择
        config_task_frame = ttk.Frame(right_frame)
        config_task_frame.pack(fill='x', padx=5, pady=5)

        ttk.Label(config_task_frame, text="配置:").pack(side='left', padx=5)
        self.task_config_list = ttk.Combobox(config_task_frame, width=20)
        self.task_config_list.pack(side='left', padx=5)
        ttk.Button(config_task_frame, text="刷新配置", command=self.refresh_task_configs, width=10).pack(side='left',
                                                                                                         padx=5)

        # 任务状态显示
        self.task_status = ttk.Label(right_frame, text="定时任务就绪", foreground="blue",
                                     font=(self.font[0], 10, 'bold'))
        self.task_status.pack(pady=5)

        # 网格权重设置,使界面可伸缩
        self.main_frame.columnconfigure(0, weight=1)
        self.main_frame.columnconfigure(1, weight=1)
        self.main_frame.rowconfigure(0, weight=1)

        # 绑定任务树选择事件
        self.task_tree.bind("<<TreeviewSelect>>", lambda e: self.btn_del_task.config(
            state='normal' if self.task_tree.selection() else 'disabled'))

    def start_recording(self):
        if self.recording or self.running:
            messagebox.showwarning("提示", "请先停止当前操作")
            return

        self.pre_record_pos = pyautogui.position()
        self.events = []
        self.recording = True
        self.last_event_time = None

        # 初始化键盘缓冲区
        self.key_buffer = []
        self.last_key_time = None

        self.mouse_listener = mouse.Listener(on_click=self.on_mouse_click)
        self.keyboard_listener = keyboard.Listener(
            on_press=self.on_key_press,
            on_release=self.on_key_release
        )
        self.mouse_listener.start()
        self.keyboard_listener.start()

        self.btn_record.config(state='disabled')
        self.btn_stop.config(state='normal')
        self.btn_edit.config(state='disabled')
        self.btn_edit_all.config(state='disabled')
        self.status.config(text="正在记录(点击/按键,按Q或Esc结束)")

    def stop_recording(self):
        if not self.recording:
            return

        self.recording = False

        # 处理未提交的键盘缓冲区
        self.flush_key_buffer()

        # 移除最后一次鼠标点击事件(保留键盘事件)
        if self.events:
            last_event_type = self.events[-1][0]
            if last_event_type in ['left_click', 'right_click', 'double_click']:
                self.events.pop()

        if self.mouse_listener:
            self.mouse_listener.stop()
        if self.keyboard_listener:
            self.keyboard_listener.stop()

        if hasattr(self, 'pre_record_pos'):
            pyautogui.moveTo(self.pre_record_pos)

        self.btn_record.config(state='normal')
        self.btn_stop.config(state='disabled')
        self.btn_edit.config(state='normal' if self.events else 'disabled')
        self.btn_edit_all.config(state='normal' if self.events else 'disabled')
        self.btn_run.config(state='normal' if self.events else 'disabled')
        self.status.config(text=f"记录完成,共 {len(self.events)} 个事件")
        self.update_tree()

    def on_mouse_click(self, x, y, button, pressed):
        if not self.recording or not pressed:
            return

        # 处理未提交的键盘缓冲区
        self.flush_key_buffer()

        current_time = time.time()
        delay = current_time - self.last_event_time if self.last_event_time else 0
        event_type = self.get_mouse_type(button)

        # 处理双击
        if event_type == 'left_click' and len(self.events) > 0 and \
                current_time - self.last_event_time < self.double_click_threshold:
            self.events[-1] = ('double_click', x, y, delay, None)
        else:
            self.events.append((event_type, x, y, delay, None))

        self.last_event_time = current_time
        self.update_tree()

    def on_key_press(self, key):
        if not self.recording:
            return

        try:
            if key.char.lower() == 'q':
                if self.running:
                    self.stop_execution()
                elif self.recording:
                    self.stop_recording()
                return
        except AttributeError:
            pass

        if key == keyboard.Key.esc and self.recording:
            self.stop_recording()
            return

        current_time = time.time()

        # 处理特殊键
        key_name = self.get_key_name(key)

        # 如果是修饰键(Shift、Ctrl等),不记录
        if key_name in ['shift', 'ctrl', 'alt', 'cmd']:
            return

        # 如果距离上次按键时间较长,提交之前的缓冲区
        if self.last_key_time and (current_time - self.last_key_time > 0.5):
            self.flush_key_buffer()

        self.key_buffer.append(key_name)
        self.last_key_time = current_time

    def on_key_release(self, key):
        # 检测组合键(如Ctrl+C)
        key_name = self.get_key_name(key)

        # 如果是修饰键释放,检查是否有组合键
        if key_name in ['shift', 'ctrl', 'alt', 'cmd']:
            # 提交当前缓冲区作为组合键
            if self.key_buffer:
                self.flush_key_buffer()

    def flush_key_buffer(self):
        if not self.key_buffer:
            return

        current_time = time.time()
        delay = current_time - self.last_event_time if self.last_event_time else 0

        # 将键盘缓冲区内容作为一个完整的键盘输入事件
        key_content = ''.join(self.key_buffer)
        self.events.append(('key_input', None, None, delay, key_content))

        self.last_event_time = current_time
        self.key_buffer = []
        self.update_tree()

    def get_mouse_type(self, button):
        return {
            mouse.Button.left: 'left_click',
            mouse.Button.right: 'right_click'
        }.get(button, 'unknown')

    def get_key_name(self, key):
        try:
            return key.char  # 普通字符键
        except:
            # 特殊键处理
            key_str = str(key).replace('Key.', '')
            return {
                'space': ' ',
                'enter': '\n',
                'tab': '\t',
                'backspace': '⌫',
                'delete': '⌦',
                'up': '↑',
                'down': '↓',
                'left': '←',
                'right': '→',
            }.get(key_str, key_str)

    def update_tree(self):
        self.tree.delete(*self.tree.get_children())
        for idx, (typ, x, y, delay, data) in enumerate(self.events, 1):
            pos = f"({int(x)}, {int(y)})" if typ.startswith('left') or typ.startswith('right') else "键盘"
            content = data if typ == 'key_input' else ''
            self.tree.insert('', 'end', values=(
                idx,
                '双击' if typ == 'double_click' else typ.replace('_click', '单击'),
                pos,
                f"{delay:.2f}",
                content
            ))

    def edit_delay(self):
        try:
            selected = self.tree.selection()[0]
            idx = int(self.tree.item(selected)['values'][0]) - 1
            current_delay = self.events[idx][3]
            new_delay = simpledialog.askfloat(
                "编辑延迟",
                f"事件 #{idx + 1} 延迟(秒)",
                initialvalue=current_delay,
                minvalue=0.0,
                maxvalue=3600.0
            )
            if new_delay is not None:
                self.events[idx] = (self.events[idx][0], self.events[idx][1],
                                    self.events[idx][2], new_delay, self.events[idx][4])
                self.update_tree()
        except:
            messagebox.showinfo("提示", "请选择要编辑的事件")

    def edit_all_delay(self):
        if not self.events:
            messagebox.showinfo("提示", "没有事件可编辑")
            return

        avg_delay = sum(event[3] for event in self.events) / len(self.events)
        new_delay = simpledialog.askfloat(
            "编辑全部延迟",
            "设置所有事件的延迟时间(秒)",
            initialvalue=avg_delay,
            minvalue=0.0,
            maxvalue=3600.0
        )

        if new_delay is not None:
            if len(self.events) > 0:
                first_event = self.events[0]
                self.events = [(event[0], event[1], event[2], new_delay, event[4]) for event in self.events]
                self.events[0] = (first_event[0], first_event[1], first_event[2], first_event[3], first_event[4])
            self.update_tree()
            self.status.config(text=f"已将所有事件延迟设置为 {new_delay:.2f}s")

    def start_execution(self):
        if self.running or not self.events:
            messagebox.showwarning("提示", "请先记录事件或停止当前任务")
            return

        try:
            loop = int(self.loop_var.get())
            if loop < 0:
                raise ValueError
        except:
            messagebox.showerror("错误", "请输入有效的循环次数(0为无限循环)")
            return

        self.running = True
        self.btn_run.config(state='disabled')
        self.btn_record.config(state='disabled')
        self.status.config(text="准备中...3秒后开始(按Q键退出)")
        self.root.update()
        time.sleep(3)

        self.execution_thread = threading.Thread(target=self.execute_events, daemon=True)
        self.execution_thread.start()

    def execute_events(self):
        try:
            loop = int(self.loop_var.get()) or float('inf')
            for _ in range(loop):
                if not self.running:
                    break
                for event in self.events:
                    if not self.running:
                        break
                    self.perform_event(event)
            self.status.config(text="执行完成")
        except Exception as e:
            self.status.config(text=f"错误:{str(e)}")
        finally:
            self.running = False
            self.btn_run.config(state='normal')
            self.btn_record.config(state='normal')

    def perform_event(self, event):
        typ, x, y, delay, data = event
        self.status.config(text=f"执行中:{typ} 延迟{delay:.2f}s(按Q键退出)")
        self.root.update()
        time.sleep(delay)  # 等待延迟

        if typ.startswith('left') or typ == 'double_click':
            try:
                pyautogui.moveTo(x, y, duration=0)
                if typ == 'double_click':
                    pyautogui.doubleClick()
                else:
                    pyautogui.click()
            except pyautogui.FailSafeException:
                self.stop_execution()
                messagebox.showwarning("安全中断", "鼠标移到屏幕边缘")
        elif typ == 'key_input':
            # 处理键盘输入
            pyautogui.typewrite(data)

    def stop_execution(self):
        self.running = False
        self.status.config(text="已停止")
        self.btn_run.config(state='normal')
        self.btn_record.config(state='normal')

    def clear_events(self):
        if messagebox.askyesno("确认", "确定清除所有记录?"):
            self.events = []
            self.update_tree()
            self.btn_edit.config(state='disabled')
            self.btn_edit_all.config(state='disabled')
            self.btn_run.config(state='disabled')
            self.status.config(text="记录已清除")

    # 配置管理
    def save_config(self):
        name = self.config_name.get().strip()
        if not name:
            messagebox.showerror("错误", "配置名称不能为空")
            return

        config = {
            "events": self.events,
            "loop": self.loop_var.get()
        }
        config_dir = "configs"
        if not os.path.exists(config_dir):
            os.makedirs(config_dir)

        try:
            with open(f"{config_dir}/{name}.json", "w", encoding="utf-8") as f:
                json.dump(config, f, ensure_ascii=False, indent=2)
            self.load_config_list()
            self.refresh_task_configs()
            messagebox.showinfo("成功", f"配置 '{name}' 已保存")
        except Exception as e:
            messagebox.showerror("错误", f"保存失败:{str(e)}")

    def load_config_list(self):
        config_dir = "configs"
        self.config_list['values'] = [f[:-5] for f in os.listdir(config_dir) if f.endswith(".json")]

    def load_config(self):
        name = self.config_list.get()
        if not name:
            messagebox.showwarning("提示", "请选择配置文件")
            return

        config_dir = "configs"
        path = f"{config_dir}/{name}.json"
        if not os.path.exists(path):
            messagebox.showerror("错误", "配置文件不存在")
            return

        try:
            with open(path, "r", encoding="utf-8") as f:
                config = json.load(f)
                self.events = config["events"]
                self.loop_var.set(config["loop"])
                self.update_tree()
                self.btn_edit.config(state='normal')
                self.btn_edit_all.config(state='normal')
                self.btn_run.config(state='normal')
                self.status.config(text=f"已加载配置:{name}")
        except Exception as e:
            messagebox.showerror("错误", f"加载失败:{str(e)}")

    # 定时任务管理
    def refresh_task_configs(self):
        config_dir = "configs"
        self.task_config_list['values'] = [f[:-5] for f in os.listdir(config_dir) if f.endswith(".json")]

    def add_task(self):
        time_str = self.time_var.get()
        config_name = self.task_config_list.get()

        if not time_str or not config_name:
            messagebox.showwarning("提示", "请选择执行时间和配置")
            return

        try:
            hour, minute, second = map(int, time_str.split(':'))
            now = datetime.now()
            task_time = now.replace(hour=hour, minute=minute, second=second, microsecond=0)

            if task_time <= now:
                task_time += timedelta(days=1)

            task = {
                'time': task_time,
                'config': config_name,
                'status': '等待中'
            }
            self.tasks.append(task)
            self.update_task_tree()
            self.btn_start_tasks.config(state='normal' if self.tasks else 'disabled')

            self.task_status.config(text=f"已添加任务:{config_name} @ {time_str}")
        except:
            messagebox.showerror("错误", "时间格式错误,请使用 HH:MM:SS 格式")

    def delete_task(self):
        try:
            selected = self.task_tree.selection()[0]
            idx = int(self.task_tree.item(selected)['values'][0]) - 1
            del self.tasks[idx]
            self.update_task_tree()
            self.btn_start_tasks.config(state='normal' if self.tasks else 'disabled')
            self.btn_del_task.config(state='disabled' if not self.task_tree.selection() else 'normal')
            self.task_status.config(text="已删除选中任务")
        except:
            messagebox.showinfo("提示", "请选择要删除的任务")

    def update_task_tree(self):
        self.task_tree.delete(*self.task_tree.get_children())
        for idx, task in enumerate(self.tasks, 1):
            time_str = task['time'].strftime("%H:%M:%S")
            self.task_tree.insert('', 'end', values=(
                idx,
                time_str,
                task['config'],
                task['status']
            ))

    def start_tasks(self):
        if not self.tasks:
            messagebox.showwarning("提示", "没有任务可执行")
            return

        if self.running:
            messagebox.showwarning("提示", "请先停止当前执行的任务")
            return

        self.running_tasks = True
        self.btn_start_tasks.config(state='disabled')
        self.btn_stop_tasks.config(state='normal')
        self.btn_add_task.config(state='disabled')
        self.btn_del_task.config(state='disabled')

        self.task_thread = threading.Thread(target=self.run_tasks, daemon=True)
        self.task_thread.start()
        self.task_status.config(text="定时任务已启动")

    def stop_tasks(self):
        self.running_tasks = False
        self.btn_start_tasks.config(state='normal' if self.tasks else 'disabled')
        self.btn_stop_tasks.config(state='disabled')
        self.btn_add_task.config(state='normal')
        self.btn_del_task.config(state='disabled' if not self.task_tree.selection() else 'normal')
        self.task_status.config(text="定时任务已停止")

    def run_tasks(self):
        while self.running_tasks and self.tasks:
            now = datetime.now()
            executed_tasks = []

            for task in self.tasks:
                if task['status'] == '等待中' and task['time'] <= now:
                    task['status'] = '执行中'
                    self.update_task_tree()

                    config_dir = "configs"
                    path = f"{config_dir}/{task['config']}.json"
                    try:
                        with open(path, "r", encoding="utf-8") as f:
                            config = json.load(f)
                            self.events = config["events"]
                            self.loop_var.set(config["loop"])

                            self.running = True
                            self.status.config(text=f"执行定时任务:{task['config']}")
                            self.root.update()

                            loop = int(self.loop_var.get()) or float('inf')
                            for _ in range(loop):
                                if not self.running or not self.running_tasks:
                                    break
                                for event in self.events:
                                    if not self.running or not self.running_tasks:
                                        break
                                    self.perform_event(event)

                            task['status'] = '已完成'
                    except Exception as e:
                        task['status'] = '执行失败'
                        self.task_status.config(text=f"任务执行失败:{str(e)}")

                    executed_tasks.append(task)
                    self.update_task_tree()

            for task in executed_tasks:
                self.tasks.remove(task)

            if not self.tasks:
                self.running_tasks = False
                self.root.after(0, self.task_status.config, {"text": "所有定时任务已完成"})
                self.root.after(0, self.btn_start_tasks.config, {"state": 'disabled'})
                self.root.after(0, self.btn_stop_tasks.config, {"state": 'disabled'})
                self.root.after(0, self.btn_add_task.config, {"state": 'normal'})
                break

            time.sleep(1)


if __name__ == "__main__":
    root = tk.Tk()
    app = AutoClickerApp(root)
    root.mainloop()
相关推荐
一个Potato12 分钟前
Python面试总结
开发语言·python
无证驾驶梁嗖嗖12 分钟前
ubuntu22鼠键失灵恢复记录笔记chatgpt解决
运维
无敌最俊朗@21 分钟前
**HTTP/HTTPS基础** - URL结构(协议、域名、端口、路径、参数、锚点) - 请求方法(GET、POST) - 请求头/响应头 - 状态码含义
爬虫·python·网络协议·http·https
带鱼吃猫40 分钟前
Linux系统:ext2文件系统的核心概念和结构
linux·运维·服务器
qwer555881 小时前
linux-----------------库制作与原理(下)
linux·运维·服务器
vortex51 小时前
Bash fork 炸弹 —— :(){ :|:& };:
运维·服务器·开发语言·网络安全·bash
ephemerals__2 小时前
【Linux】简易版Shell实现(附源码)
linux·运维·chrome
xiaohanbao092 小时前
day29 python深入探索类装饰器
开发语言·python·学习·机器学习·pandas
CryptoRzz2 小时前
股票数据源对接技术指南:印度尼西亚、印度、韩国
数据库·python·金融·数据分析·区块链
烦躁的大鼻嘎2 小时前
【Linux】ELF与动静态库的“暗黑兵法”:程序是如何跑起来的?
linux·运维·服务器·c++·vscode·ubuntu