.
675 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 超星考试客户端工具
2 |
3 |
4 |
5 | 
6 | 
7 | 
8 | 
9 | 
10 |
11 |
12 |
13 | ---
14 |
15 | > 🚀 **本项目旨在为考试客户端(如学习通)提供本地题库、AI搜题、界面增强等多种实用功能。**
16 |
17 | ---
18 |
19 |
20 | 📑 目录
21 |
22 | - [项目介绍](#项目介绍)
23 | - [主要功能](#主要功能)
24 | - [使用说明](#使用说明)
25 | - [常见问题](#常见问题)
26 | - [时间日历](#时间日历)
27 | - [赞赏](#赞赏)
28 | - [注意事项](#注意事项)
29 | - [配置说明](#配置说明)
30 | - [贡献与反馈](#贡献与反馈)
31 | - [免责声明](#免责声明)
32 |
33 |
34 |
35 | ---
36 |
37 | ## 项目介绍
38 | 本项目是一个专为考试客户端(如学习通)设计的辅助工具,集成了本地题库管理、AI智能搜题、窗口防护与界面增强等多项实用功能,提升考试答题效率与体验。
39 |
40 | ---
41 |
42 | ## 主要功能
43 |
44 | ### 题库管理与搜索
45 | - **本地题库导入**:自动读取`tiku.txt`文件,支持大批量题目管理。
46 | - **关键词高亮搜索**:输入关键词后,所有匹配项高亮显示,支持回车键跳转下一个结果。
47 | - **题库内容只读保护**:防止误操作修改题库内容。
48 |
49 | ### AI 智能搜题
50 | - **多AI平台支持**:可选[讯飞星火](https://aiui.xfyun.cn/console)或[Deepseek](https://www.deepseek.com)。
51 | - **一键AI问答**:输入问题后,AI自动返回答案,支持多线程防止界面卡顿。
52 | - **AI答案一键输入**:AI答案可一键自动输入到目标输入框(需英文输入法)。
53 |
54 | ### 界面与交互增强
55 | - **窗口置顶与防录屏/截屏**:调用`SetWindowDisplayAffinity`,窗口始终置顶且无法被录屏/截屏工具捕获。
56 | - **窗口透明度调节**:右键一键切换(0.2/0.5),Ctrl+滚轮精细调节(0.1~1.0)。
57 | - **字体大小调节**:Alt+滚轮随时调整题库/AI答案字体大小。
58 | - **窗口快速隐藏/显示**:F3一键隐藏到屏幕边缘,再次按下恢复。
59 | - **窗口自由拖动**:Ctrl+鼠标左键拖动窗口到任意位置。
60 | - **ESC/F1-F12快捷退出**:ESC或任意F1-F12键可快速关闭程序(可自定义)。
61 |
62 | ### 其他实用功能
63 | - **在线更新检测**:自动检测新版本并提示更新。
64 | - **多线程处理**:AI问答、输入等操作均采用多线程,保证界面流畅不卡顿。
65 | - **详细注释与易用配置**:代码注释详细,`config.yaml`配置简单明了,便于二次开发。
66 |
67 | ---
68 |
69 | ## 使用说明
70 |
71 | ### 环境准备
72 | 1. **安装Python**:确保已安装Python 3.7+。
73 | 2. **安装依赖**:在命令行中运行以下命令安装所需依赖:
74 | ```bash
75 | pip install tkinter requests pyyaml pynput websocket-client
76 | ```
77 | 3. **准备文件**:在程序运行目录下创建`tiku.txt`(题库)和`config.yaml`(配置文件)。
78 |
79 | ### 配置AI平台
80 | 1. **讯飞星火**:
81 | - 在`config.yaml`中填写`appid`、`api_key`、`api_secret`。
82 | - 申请地址:[讯飞星火](https://aiui.xfyun.cn/console)。
83 | 2. **Deepseek**:
84 | - 在`config.yaml`中填写`api_key`和`model`。
85 | - 可直接使用预设,建议更换为自己的密钥。
86 |
87 | ### 启动程序
88 | 1. **运行程序**:在命令行中运行`demo1.py`。
89 | 2. **首次启动**:程序会自动检测配置和题库文件。
90 | 3. **主界面功能**:
91 | - 题库搜索:输入关键词,回车跳转下一个结果。
92 | - AI切换:点击"AI"按钮切换到AI搜题界面。
93 | - 快捷输入:在输入框中输入内容,点击"输入"按钮自动输入。
94 |
95 | ### 常用快捷键与操作
96 | - **F3**:窗口隐藏/恢复
97 | - **Ctrl+鼠标左键**:拖动窗口
98 | - **右键**:切换透明度
99 | - **Ctrl+滚轮**:调整透明度
100 | - **Alt+滚轮**:调整字体大小
101 | - **ESC/F1-F12**:快速退出
102 | - **回车**:题库搜索下一个
103 |
104 | ---
105 |
106 | ## 常见问题
107 |
108 | - **Q: 启动报错"缺少config.yaml文件"?**
109 | - A: 请确保`config.yaml`在程序同目录下,参考示例配置文件填写。
110 |
111 | - **Q: 题库无法加载或搜索?**
112 | - A: 请确保`tiku.txt`存在且为UTF-8编码,每行一题。
113 |
114 | - **Q: AI搜题无响应?**
115 | - A: 检查网络连接、API密钥是否正确,或更换AI平台。
116 |
117 | - **Q: 窗口无法被录屏/截屏?**
118 | - A: 仅主窗口受保护,输入法弹窗等仍可能被录屏,技术有限无法完全防护。
119 |
120 | - **Q: 如何自定义快捷键?**
121 | - A: 可在`demo1.py`中修改相关绑定代码,注释详细易于调整。
122 |
123 | ---
124 |
125 | ## 时间日历
126 | | 日期 | 事件 |
127 | | ------------ | ------------------------------------------------------------ |
128 | | 2024.12.26 | 项目开始,创建代码仓库 |
129 | | 2024.12.27 | 创建README和GPL-3.0 License,demo1.py实现透明度、快捷退出等 |
130 | | 2024.12.28 | 解决截屏/录屏,题库导入与高亮搜索 |
131 | | 2024.12.29 | 添加一键输入功能 |
132 | | 2024.12.30 | 完成AI功能(讯飞星火),项目基本完成 |
133 | | 2025.1.1 | 添加Alt+滚轮调整字体大小 |
134 | | 2025.1.6 | 添加窗口可移动(Ctrl+鼠标左键) |
135 | | 2025.1.7 | 添加config文件,AI功能更易配置 |
136 | | 2025.2.13 | 添加Deepseek AI |
137 | | 2025.2.25 | 添加前置文件查找、详细注释 |
138 | | 2025.2.27 | 多线程处理防止堵塞 |
139 | | 2025.4.22 | 在线更新、查找下一个功能 |
140 |
141 | ---
142 |
143 | ## 赞赏
144 |
145 |
146 |
147 |
148 | ---
149 |
150 | ## 注意事项
151 | > ⚠️ **请确保运行目录下有`tiku.txt`和`config.yaml`文件**
152 | - AI逻辑代码在`AI_ask`函数中
153 | - 讯飞星火AI需自行申请密钥
154 | - 预留AI可直接使用,建议更换为自己的
155 | - 其他AI请自行修改代码
156 | - 隐藏(F3)时窗口透明度降到最低,拉成细条放左侧
157 | - 输入法内容仍可能被录屏/截屏,能力有限无法解决,如有方案欢迎[issues](https://github.com/SJYssr/CX_EXAM_python/issues/1)留言
158 | - 如需要破解复制粘贴功能&&篡改猴相关功能,请移步[cef_cx_copy_tool](https://github.com/SJYssr/cef_cx_copy_tool)
159 |
160 | ---
161 |
162 | ## 配置说明
163 | - `config.yaml`文件:
164 | - `type=0` 未设置AI
165 | - `type=1` 讯飞星火AI
166 | - `type=2` DeepseekAI
167 |
168 | ---
169 |
170 | ## 贡献与反馈
171 | 欢迎提交 [Issues](https://github.com/SJYssr/CX_EXAM_python/issues) 反馈问题或建议,或直接 Fork/PR 参与开发。
172 |
173 | ---
174 |
175 | ## 免责声明
176 | > **本代码仅用于学习讨论,禁止用于盈利或违法用途。**
177 |
178 | - 遵循 [GPL-3.0 License](https://github.com/SJYssr/CX_EXAM_python/blob/main/LICENSE) 协议:
179 | - 允许开源/免费使用、引用、修改、衍生
180 | - 禁止闭源商业发布、销售及盈利
181 | - 基于本代码的程序**必须**同样遵守GPL-3.0协议
182 | - 他人或组织使用本代码进行的任何违法行为与本人无关
183 |
184 | ---
--------------------------------------------------------------------------------
/config.yaml:
--------------------------------------------------------------------------------
1 | AI_set:
2 | type: 1
3 | # AI类型,0为不配置ai,1为讯飞星火AI,2为deepseekAI
4 | PATH:
5 | path: D:\学习通考试客户端\考试客户端
6 | SPARK:
7 | Spark_url: wss://spark-api.xf-yun.com/v1.1/chat
8 | api_key: 5b2302f3ac2295e56bd13f587d7ffa6e
9 | api_secret: YWY0MWJhNTM4MGU3NTJkZDJiMDM0ZjZl
10 | appid: 1b69309b
11 | domain: lite
12 | # ;讯飞星火ai配置说明
13 | # ;appid=
14 | # ;api_secret=
15 | # ;api_key=
16 | # ;appid、api_secret、api_key三个服务认证信息请前往开放平台控制台查看(https://console.xfyun.cn/services/bm35)
17 | #
18 | # ;Spark_url= wss://spark-api.xf-yun.com/v3.5/chat Max环境的地址
19 | # ;Spark_url = wss://spark-api.xf-yun.com/v4.0/chat 4.0Ultra环境的地址
20 | # ;Spark_url = wss://spark-api.xf-yun.com/v3.1/chat Pro环境的地址
21 | # ;Spark_url = wss://spark-api.xf-yun.com/v1.1/chat Lite环境的地址
22 | #
23 | # ;domain=generalv3.5 Max版本
24 | # ;domain = 4.0Ultra 4.0Ultra 版本
25 | # ;domain = generalv3 Pro版本
26 | # ;domain = lite Lite版本址
27 | deepseek:
28 | api_key:
29 | model: deepseek-chat
30 | # ;model = deepseek-reasoner
31 | # ;api充值地址(https://www.deepseek.com)
32 | # ;如果是第三方接入deepseek,请修改函数call_deepseek_api中的url
33 | # ;自用的申请地址(https://www.api4gpt.com/register?aff=uZgA),接口url(https://one.api4gpt.com/v1/chat/completions)
34 |
--------------------------------------------------------------------------------
/demo1.py:
--------------------------------------------------------------------------------
1 | # _*_coding : UTF_8 _*_
2 | # author : SJYssr
3 | # Date : 2024/12/26 下午10:17
4 | # ClassName : demo1.py
5 | # Github : https://github.com/SJYssr
6 | import os
7 | import tkinter as tk
8 | from ctypes import windll, wintypes
9 | from tkinter import messagebox
10 |
11 | import requests
12 | import yaml
13 | from pynput.keyboard import Controller
14 |
15 | import _thread as thread
16 | import time
17 | import base64
18 | import datetime
19 | import hashlib
20 | import hmac
21 | import json
22 | from urllib.parse import urlparse
23 | import ssl
24 | from datetime import datetime
25 | from time import mktime
26 | from urllib.parse import urlencode
27 | from wsgiref.handlers import format_date_time
28 | import websocket
29 |
30 |
31 |
32 |
33 |
34 | class FileNotFoundError(Exception):
35 | pass
36 |
37 |
38 | def jian_cha_wen_jian():
39 | config_name = "config.yaml"
40 | currect_dir = os.getcwd()
41 | if config_name not in os.listdir(currect_dir):
42 | raise FileNotFoundError("缺少config.yaml文件,请检查")
43 |
44 |
45 | try:
46 | jian_cha_wen_jian()
47 | except FileNotFoundError as e:
48 | messagebox.showinfo("提示", str(e))
49 | exit()
50 |
51 | # 加载YAML配置
52 | with open('config.yaml', 'r', encoding='utf-8') as f:
53 | config = yaml.safe_load(f)
54 |
55 | # 读取配置
56 | type = config['AI_set']['type']
57 | if type == 0:
58 | messagebox.showinfo("AI设置", "当前未设置AI")
59 | elif type == 1:
60 | messagebox.showinfo("AI设置", "正在使用SparkAI")
61 | spark_config = config['SPARK']
62 | appid = spark_config['appid']
63 | api_secret = spark_config['api_secret']
64 | api_key = spark_config['api_key']
65 | Spark_url = spark_config['Spark_url']
66 | domain = spark_config['domain']
67 | elif type == 2:
68 | messagebox.showinfo("AI设置", "正在使用DeepseekAI")
69 | deepseek_config = config['deepseek']
70 | deepseek_api_key = deepseek_config['api_key']
71 | deepseek_model = deepseek_config['model']
72 | else:
73 | messagebox.showinfo("AI设置", "请检查config.yaml文件中的AI_set配置项")
74 |
75 | # 定义 SetWindowDisplayAffinity 函数
76 | SetWindowDisplayAffinity = windll.user32.SetWindowDisplayAffinity
77 | SetWindowDisplayAffinity.argtypes = [wintypes.HWND, wintypes.DWORD]
78 | SetWindowDisplayAffinity.restype = wintypes.BOOL
79 |
80 |
81 | def keep_on_top():
82 | """将窗口始终保持在最上层"""
83 | # 设置窗口属性,使其始终保持在最上层
84 | root.attributes("-topmost", True)
85 | # 获取当前处于前台的窗口句柄
86 | hwnd = windll.user32.GetForegroundWindow()
87 | # 设置显示亲和性为 0x0000001,表示窗口将始终显示在所有其他窗口之上
88 | dwAffinity = 0x00000011 # 设置显示亲和性为 0x00000011
89 | # 调用 SetWindowDisplayAffinity 函数,将窗口的显示亲和性设置为 dwAffinity
90 | SetWindowDisplayAffinity(hwnd, dwAffinity)
91 | # 每秒钟(1000毫秒)调用一次 keep_on_top 函数,以确保窗口始终在最上层
92 | root.after(1000, keep_on_top) # 每秒钟检查一次
93 |
94 |
95 | def change_opacity(event):
96 | """CTRL+滚轮改变窗口透明度"""
97 | # 声明全局变量 current_opacity 和 is_small,以便在函数内部修改它们
98 | global current_opacity, is_small
99 | # 如果窗口处于小窗口模式(is_small 为 True),则不执行任何操作
100 | if is_small != False:
101 | return
102 | else:
103 | if event.delta > 0: # 滚轮向上滚动
104 | current_opacity += 0.1
105 | else: # 滚轮向下滚动
106 | current_opacity -= 0.1
107 | current_opacity = max(0.1, min(current_opacity, 1.0)) # 限制透明度在 0.1 到 1.0 之间
108 | root.attributes("-alpha", current_opacity)
109 |
110 |
111 | def change_opacity0(event):
112 | """右键改变窗口透明度0.2/0.5"""
113 | # 声明全局变量 current_opacity 和 is_small,以便在函数内部修改它们
114 | global current_opacity, is_small
115 | # 检查窗口是否处于小窗口模式,如果是则直接返回,不执行后续操作
116 | if is_small != False:
117 | return
118 | # 如果当前透明度是0.2,则将其改为0.5
119 | if current_opacity == 0.2:
120 | current_opacity = 0.5
121 | # 否则,将透明度改为0.2
122 | else:
123 | current_opacity = 0.2
124 | # 应用新的透明度
125 | root.attributes("-alpha", current_opacity)
126 |
127 |
128 | def close_window(event):
129 | """关闭窗口"""
130 | root.destroy()
131 |
132 |
133 | def change_weight(event):
134 | """键盘按下事件处理函数"""
135 | global is_small, current_opacity # 声明全局变量 is_small 和 current_opacity
136 | if is_small: # 如果当前窗口是小的
137 | root.geometry("300x533+0+380") # 将窗口大小改为 300x533
138 | root.attributes("-alpha", current_opacity) # 设置窗口透明度为之前的透明度
139 | else: # 如果当前窗口是大的
140 | root.geometry("5x910+0+0") # 将窗口大小改为 5x910
141 | current_opacity = root.attributes("-alpha") # 保存当前的透明度
142 | root.attributes("-alpha", 0.1) # 设置窗口透明度为 0.1
143 | is_small = not is_small # 切换窗口大小状态
144 |
145 |
146 | def load_file_content():
147 | """读取 tiku.txt 文件内容到文本框中"""
148 | try:
149 | # 尝试以只读模式打开名为 'tiku.txt' 的文件,并指定编码为 'utf-8'
150 | with open('tiku.txt', 'r', encoding='utf-8') as file:
151 | # 读取文件的全部内容
152 | content = file.read()
153 | # 将读取的内容插入到文本框的起始位置('1.0' 表示第一行第一列)
154 | text_box.insert('1.0', content)
155 | # 设置文本框为不可编辑状态
156 | text_box.config(state='disabled') # 状态为不可编辑
157 | except FileNotFoundError:
158 | # 如果文件未找到,捕获 FileNotFoundError 异常
159 | # 在文本框中插入提示信息
160 | text_box.insert('1.0', "未找到tiku.txt,请确保文件夹中有此文件")
161 | # 设置文本框为不可编辑状态
162 | text_box.config(state='disabled') # 状态为不可编辑
163 |
164 |
165 | # 添加全局变量来跟踪搜索状态
166 | current_search_index = 0
167 | search_results = []
168 |
169 |
170 | def highlight_search():
171 | """高亮显示文本框中匹配搜索框内容项,并支持单个跳转"""
172 | global current_search_index, search_results
173 |
174 | # 获取搜索框中的搜索词
175 | search_term = search_entry.get()
176 | if not search_term:
177 | return
178 |
179 | # 移除所有现有高亮标记
180 | text_box.tag_remove('highlight', '1.0', 'end')
181 | text_box.tag_remove('current_highlight', '1.0', 'end')
182 |
183 | # 收集所有匹配项的位置
184 | search_results = []
185 | start = '1.0'
186 | while True:
187 | start = text_box.search(search_term, start, stopindex='end')
188 | if not start:
189 | break
190 | end = f"{start}+{len(search_term)}c"
191 | search_results.append((start, end))
192 | start = end
193 |
194 | if not search_results:
195 | return
196 |
197 | # 重置当前索引
198 | current_search_index = 0
199 |
200 | # 高亮显示第一个匹配项
201 | start, end = search_results[current_search_index]
202 | text_box.tag_add('current_highlight', start, end)
203 | text_box.tag_config('current_highlight', background='yellow')
204 |
205 | # 确保当前高亮项可见
206 | text_box.see(start)
207 |
208 |
209 | def next_search_result():
210 | """跳转到下一个搜索结果"""
211 | global current_search_index, search_results
212 | if not search_results:
213 | return
214 |
215 | # 移除当前高亮
216 | text_box.tag_remove('current_highlight', '1.0', 'end')
217 |
218 | # 移动到下一个结果
219 | current_search_index = (current_search_index + 1) % len(search_results)
220 | start, end = search_results[current_search_index]
221 |
222 | # 高亮显示新的当前项
223 | text_box.tag_add('current_highlight', start, end)
224 | text_box.see(start)
225 |
226 |
227 | def text_input():
228 | """主界面输入功能"""
229 | # 获取输入框中的文本
230 | input_text = input_entry.get()
231 | if not input_text: # 如果输入框为空,则跳过此函数
232 | return
233 |
234 | def input_thread():
235 | keyboard = Controller()
236 | time.sleep(5)
237 | keyboard.type(input_text)
238 | time.sleep(1)
239 | input_entry.delete(0, 'end') # 清空输入框
240 |
241 | # 启动新线程执行输入操作
242 | thread.start_new_thread(input_thread, ())
243 |
244 |
245 | def ai_text_input():
246 | """AI界面输入功能"""
247 | # 从输入框获取用户输入的文本
248 | input_text = ai_input_entry.get()
249 | if not input_text: # 如果输入框为空,则跳过此函数
250 | return
251 |
252 | def input_thread():
253 | keyboard = Controller()
254 | time.sleep(5)
255 | keyboard.type(input_text)
256 | time.sleep(1)
257 | ai_input_entry.delete(0, 'end') # 清空输入框
258 |
259 | # 启动新线程执行输入操作
260 | thread.start_new_thread(input_thread, ())
261 |
262 |
263 | def switch_to_ai_search():
264 | """切换到AI搜索界面"""
265 | main_frame.pack_forget() # 隐藏主界面框架
266 | ai_frame.pack(fill="both", expand=True) # 显示AI搜索界面框架
267 | search_frame.pack_forget() # 隐藏搜索框架
268 | ai_text_box.config(state='disabled') # 禁止编辑AI回答文本框
269 |
270 |
271 | def switch_to_main():
272 | """切换回主界面"""
273 | ai_frame.pack_forget() # 隐藏AI搜索界面框架
274 | search_frame.pack(side="top", fill="x") # 显示搜索框架
275 | main_frame.pack(fill="both", expand=True) # 显示主界面框架
276 |
277 |
278 | def change_text_size(event):
279 | """调整文本框内字体大小"""
280 | global text_size
281 | text_size = max(10, min(text_size, 12)) # 限制字体大小在1到12之间
282 | if main_frame.winfo_ismapped():
283 | """调整题库文本大小"""
284 | if event.delta > 0: # 滚轮向上滚动
285 | text_size += 1
286 | else: # 滚轮向下滚动
287 | text_size -= 1
288 | text_box.config(font=("Arial", text_size))
289 | elif ai_search_frame.winfo_ismapped():
290 | """调整AI回答文本大小"""
291 | if event.delta > 0: # 滚轮向上滚动
292 | text_size += 1
293 | else: # 滚轮向下滚动
294 | text_size -= 1
295 | ai_text_box.config(font=("Arial", text_size))
296 |
297 |
298 | def start_move(event):
299 | global x, y
300 | x = event.x
301 | y = event.y
302 |
303 |
304 | def stop_move(event):
305 | root.geometry(f"+{event.x_root - x}+{event.y_root - y}")
306 |
307 |
308 | def AI_ask():
309 | """AI搜索调用的函数"""
310 | ai_search_button.config(state='disabled')
311 | ai_text_box.config(state='normal')
312 | ai_text_box.delete('1.0', tk.END)
313 | if type == 1:
314 | ai_text_box.config(state='disabled')
315 | else:
316 | ai_text_box.insert(tk.END, "正在思考中,请稍候...")
317 | ai_text_box.config(state='disabled')
318 |
319 | def run_ai():
320 | try:
321 | if type == 1:
322 | wsParam = Ws_Param(appid, api_key, api_secret, Spark_url)
323 | websocket.enableTrace(False)
324 | wsUrl = wsParam.create_url()
325 | query = ai_search_entry.get()
326 | ws = websocket.WebSocketApp(wsUrl, on_message=on_message, on_error=on_error, on_close=on_close,
327 | on_open=on_open)
328 | ws.appid = appid
329 | ws.query = query
330 | ws.domain = domain
331 | ws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE})
332 | elif type == 2:
333 | response = call_deepseek_api(deepseek_api_key, ai_search_entry.get())
334 | root.after(0, update_ai_text, response)
335 | except Exception as e:
336 | root.after(0, update_ai_text, f"发生错误: {str(e)}")
337 | finally:
338 | root.after(0, lambda: ai_search_button.config(state='normal'))
339 |
340 | thread.start_new_thread(run_ai, ())
341 |
342 |
343 | # Spark消息处理--------------------------------------------
344 |
345 | def update_ai_text(content):
346 | ai_text_box.config(state='normal')
347 | ai_text_box.delete('1.0', tk.END)
348 | ai_text_box.insert(tk.END, content)
349 | ai_text_box.config(state='disabled')
350 |
351 |
352 | class Ws_Param(object):
353 | # 初始化
354 | def __init__(self, APPID, APIKey, APISecret, gpt_url):
355 | self.APPID = APPID
356 | self.APIKey = APIKey
357 | self.APISecret = APISecret
358 | self.host = urlparse(gpt_url).netloc
359 | self.path = urlparse(gpt_url).path
360 | self.gpt_url = gpt_url
361 |
362 | # 生成url
363 | def create_url(self):
364 | # 生成RFC1123格式的时间戳
365 | now = datetime.now()
366 | date = format_date_time(mktime(now.timetuple()))
367 |
368 | # 拼接字符串
369 | signature_origin = "host: " + self.host + "\n"
370 | signature_origin += "date: " + date + "\n"
371 | signature_origin += "GET " + self.path + " HTTP/1.1"
372 |
373 | # 进行hmac-sha256进行加密
374 | signature_sha = hmac.new(self.APISecret.encode('utf-8'), signature_origin.encode('utf-8'),
375 | digestmod=hashlib.sha256).digest()
376 |
377 | signature_sha_base64 = base64.b64encode(signature_sha).decode(encoding='utf-8')
378 |
379 | authorization_origin = f'api_key="{self.APIKey}", algorithm="hmac-sha256", headers="host date request-line", signature="{signature_sha_base64}"'
380 |
381 | authorization = base64.b64encode(authorization_origin.encode('utf-8')).decode(encoding='utf-8')
382 |
383 | # 将请求的鉴权参数组合为字典
384 | v = {
385 | "authorization": authorization,
386 | "date": date,
387 | "host": self.host
388 | }
389 | # 拼接鉴权参数,生成url
390 | url = self.gpt_url + '?' + urlencode(v)
391 | # 此处打印出建立连接时候的url,参考本demo的时候可取消上方打印的注释,比对相同参数时生成的url与自己代码生成的url是否一致
392 | return url
393 |
394 |
395 | # 收到websocket错误的处理
396 | def on_error(ws, error):
397 | ai_text_box.after(0, update_ai_text, error)
398 |
399 |
400 | # 收到websocket关闭的处理
401 | def on_close(ws):
402 | ai_text_box.after(0, update_ai_text, f'##连接已关闭##')
403 |
404 |
405 | # 收到websocket连接建立的处理
406 | def on_open(ws):
407 | thread.start_new_thread(run, (ws,))
408 |
409 |
410 | def run(ws, *args):
411 | data = json.dumps(gen_params(appid=ws.appid, query=ws.query, domain=ws.domain))
412 | ws.send(data)
413 |
414 |
415 | # 收到websocket消息的处理
416 | def on_message(ws, message):
417 | data = json.loads(message)
418 | ai_text_box.config(state='normal')
419 | if data['header']['code'] != 0:
420 | root.after(0, update_ai_text, f"请求错误: {data['header']['code']}")
421 | ws.close()
422 | else:
423 | content = data["payload"]["choices"]["text"][0]["content"]
424 | root.after(0, ai_text_box.insert(tk.END, content))
425 | if data["payload"]["choices"]["status"] == 2:
426 | ws.close()
427 | ai_text_box.config(state='disabled')
428 |
429 |
430 | def gen_params(appid, query, domain):
431 | """
432 | 通过appid和用户的提问来生成请参数
433 | """
434 |
435 | data = {
436 | "header": {
437 | "app_id": appid,
438 | "uid": "1234",
439 | # "patch_id": [] #接入微调模型,对应服务发布后的resourceid
440 | },
441 | "parameter": {
442 | "chat": {
443 | "domain": domain,
444 | "temperature": 0.5,
445 | "max_tokens": 4096,
446 | "auditing": "default",
447 | }
448 | },
449 | "payload": {
450 | "message": {
451 | "text": [{"role": "user", "content": query}]
452 | }
453 | }
454 | }
455 | return data
456 |
457 |
458 | # deepseek消息处理
459 | def call_deepseek_api(deepseek_api_key, prompt):
460 | global deepseek_model
461 | url = "https://api.deepseek.com/v1/chat/completions" # 请根据实际API文档替换URL
462 | headers = {
463 | "Authorization": f"Bearer {deepseek_api_key}",
464 | "Content-Type": "application/json"
465 | }
466 | data = {
467 | "model": f"{deepseek_model}", # 根据实际模型名修改
468 | "messages": [
469 | {"role": "user", "content": prompt}
470 | ],
471 | "temperature": 0.7
472 | }
473 |
474 | try:
475 | response = requests.post(url, headers=headers, json=data)
476 | response.raise_for_status() # 自动触发HTTP错误
477 | return response.json()['choices'][0]['message']['content']
478 |
479 | except requests.exceptions.RequestException as e:
480 | # 返回网络/HTTP相关异常
481 | return e
482 | except KeyError as e:
483 | # 返回JSON解析错误
484 | return e
485 | except Exception as e:
486 | # 捕获其他未知异常
487 | return e
488 |
489 |
490 | # --------------------------------------------------------
491 |
492 | # 界面部件
493 | root = tk.Tk()
494 | root.geometry("300x533+0+380") # 设置窗口大小和位置
495 | root.attributes("-alpha", 0.5) # 设置窗口透明度为 0.5
496 | root.configure(bg='white') # 设置窗口背景颜色为白色
497 | root.overrideredirect(True) # 隐藏窗口边框
498 |
499 | # 创建顶部框架用于放置搜索框和搜索按钮(主界面)
500 | search_frame = tk.Frame(root)
501 | search_frame.pack(side="top", fill="x")
502 |
503 | search_entry = tk.Entry(search_frame)
504 | search_entry.pack(side="left", fill="x", expand=True, padx=5, pady=5)
505 | search_entry.configure(foreground='gray')
506 |
507 | ai_button = tk.Button(search_frame, text="AI", command=switch_to_ai_search)
508 | ai_button.pack(side="right", padx=5, pady=5)
509 | ai_button.configure(foreground='gray')
510 |
511 | search_button = tk.Button(search_frame, text="搜索", command=highlight_search)
512 | search_button.pack(side="right", padx=5, pady=5)
513 | search_button.configure(foreground='gray')
514 |
515 | # 创建主界面框架和文本框(主界面)
516 | main_frame = tk.Frame(root)
517 | main_frame.pack(fill="both", expand=True)
518 |
519 | text_frame = tk.Frame(main_frame)
520 | text_frame.pack(fill="both", expand=True)
521 |
522 | text_box = tk.Text(text_frame, wrap='word')
523 | text_box.pack(side="left", fill="both", expand=True)
524 | text_box.configure(foreground='gray')
525 |
526 | # 创建底部框架用于放置输入框和输入按钮(主界面)
527 | bottom_frame = tk.Frame(main_frame)
528 | bottom_frame.pack(side="bottom", fill="x")
529 |
530 | input_entry = tk.Entry(bottom_frame)
531 | input_entry.pack(side="left", fill="x", expand=True, padx=5, pady=5)
532 | input_entry.configure(foreground='gray')
533 |
534 | submit_button = tk.Button(bottom_frame, text="输入", command=text_input)
535 | submit_button.pack(side="right", padx=5, pady=5)
536 | submit_button.configure(foreground='gray')
537 |
538 | # 创建顶部框架用于放置搜索框和搜索按钮(AI搜索界面)
539 | ai_frame = tk.Frame(root)
540 | ai_search_frame = tk.Frame(ai_frame)
541 | ai_search_frame.pack(side="top", fill="x")
542 |
543 | ai_search_entry = tk.Entry(ai_search_frame)
544 | ai_search_entry.pack(side="left", fill="x", expand=True, padx=5, pady=5)
545 | ai_search_entry.configure(foreground='gray')
546 |
547 | back_button = tk.Button(ai_search_frame, text="返回", command=switch_to_main)
548 | back_button.pack(side="right", padx=5, pady=5)
549 | back_button.configure(foreground='gray')
550 |
551 | ai_search_button = tk.Button(ai_search_frame, text="AI搜索", command=AI_ask)
552 | ai_search_button.pack(side="right", padx=5, pady=5)
553 | ai_search_button.configure(foreground='gray')
554 |
555 | # 创建AI搜索界面的文本框用于显示AI的回答
556 | ai_text_box = tk.Text(ai_frame, wrap="word")
557 | ai_text_box.pack(fill="both", expand=True)
558 | ai_text_box.configure(foreground='gray')
559 |
560 | # 创建底部框架用于放置输入框和输入按钮(AI搜索界面)
561 | ai_bottom_frame = tk.Frame(ai_frame)
562 | ai_bottom_frame.pack(side="bottom", fill="x")
563 |
564 | ai_input_entry = tk.Entry(ai_bottom_frame)
565 | ai_input_entry.pack(side="left", fill="x", expand=True, padx=5, pady=5)
566 | ai_input_entry.configure(foreground='gray')
567 |
568 | ai_submit_button = tk.Button(ai_bottom_frame, text="输入", command=ai_text_input)
569 | ai_submit_button.pack(side="right", padx=5, pady=5)
570 | ai_submit_button.configure(foreground='gray')
571 |
572 | # 绑定鼠标事件
573 | root.bind("", start_move)
574 | root.bind("", stop_move)
575 | # 绑定键盘按下事件
576 | root.bind("", change_weight)
577 | # 绑定鼠标右键点击事件
578 | root.bind("", change_opacity0) # 表示鼠标右键
579 | # 绑定滚轮事件,仅当按下Ctrl键时生效
580 | root.bind("", change_opacity)
581 | # 绑定滚轮事件,仅当按下ALT键时生效
582 | root.bind("", change_text_size)
583 | # 绑定关闭窗口事件
584 | root.bind("", close_window)
585 | # 绑定搜索导航事件
586 | root.bind("", lambda e: next_search_result())
587 | current_opacity = 0.5 # 初始透明度设置为 0.5
588 | text_size = 10 # 初始文字大小为 10
589 | is_small = False # 初始窗口大小为 300x500
590 | keep_on_top() # 启动保持最上层功能
591 | # 加载文件内容到文本框中
592 | load_file_content()
593 | root.mainloop()
594 |
--------------------------------------------------------------------------------
/tiku.txt:
--------------------------------------------------------------------------------
1 | 切换omm用户
2 | su - omm
3 |
4 | 启动数据库
5 | gs_om -t start
6 |
7 | 停止数据库
8 | gs_om -t stop
9 |
10 | 登录postgres数据库
11 | gsql -d postgres -p 26000 -r
12 |
13 | 登录student数据库
14 | gsql -d studentdb -h 192.168.100.91 -U student -p 26000 -W student@ustb2020 -r
15 |
16 | 创建用户
17 | CREATE USER 用户名 IDENTIFIED BY 密码;
18 |
19 | 赋予用户系统权限
20 | ALTER USER 用户名 SYSADMIN;
21 |
22 | 创建数据库
23 | CREATE DATABASE 数据库名;
24 |
25 | 删除用户
26 | DROP USER 用户名;
27 |
28 | 删除数据库
29 | DROP DATABASE 数据库名;
30 |
31 | 创建表
32 | CREATE TABLE 表名(参数1 数据类型, 参数2 数据类型, 参数3 数据类型....);
33 |
34 | 表中插入数据
35 | INSERT INTO 表名(参数1 数据类型, 参数2 数据类型, 参数3 数据类型....) VALUES(值1, 值2, 值3.....);
36 | 或者
37 | INSERT INTO 表名 VALUES(值1, 值2, 值3.....);
38 |
39 | 查询表中数据
40 | SELECT * FROM 表名;
41 |
42 | 删除表
43 | DROP TABLE 表名;
44 |
45 |
46 | 常见元命令:
47 | \l:列出数据库中所有的数据库,包括名称、所有者、字符集编码以及使用权限等。
48 | \d:列出当前数据库下所有的数据库对象,包括:表、视图和序列。如果后面跟有具体的表名或索引名,则显示该表或索引的详细信息。
49 | \db:列出所有可用的表空间。
50 | \dn:列出所有的模式(名称空间)。
51 | \du\dg:列出所有数据库角色。
52 | \dt:列出数据库中的表。
53 | \dt+:以扩展方式显示数据库中所有的表
54 | \di:列出所有的索引。
55 | \ds:列出所有的序列。
56 | \dv:列出所有的视图。
57 | \df:列出所有的函数
58 | \timing on\off:显示每条SQL语句的执行时间(以毫秒为单位)。
59 | \echo [string]:把字符串写到标准输出。
60 | \conninfo:查询当前连接的数据库的信息。
61 | \c 数据库:更换连接的数据库
62 | \c – 用户名:切换用户。
63 | \h:获取SQL的帮助
64 | \?:获取gsql元命令的帮助
65 | \! Os_command 执行操作系统的命令(例如:\!ls -l)
66 | \i file.sql 执行sql语句
67 | \q:退出gsql。
--------------------------------------------------------------------------------