Skip to content

Commit 8aa4617

Browse files
authored
Merge pull request #133 from lrsgzs/master
feat: C# IPC 实现逻辑
2 parents 50e65b8 + e1107cf commit 8aa4617

52 files changed

Lines changed: 3728 additions & 1477 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,4 +193,5 @@ cython_debug/
193193
/data/audio
194194
/data/TEMP
195195
/data/CSES
196+
/typings
196197
/data/images

CONTRIBUTING.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,17 @@ uv sync
7878
uv run main.py
7979
```
8080

81+
#### 5. C# IPC 相关开发
82+
83+
您需要 .NET 9.0 来生成 .NET 程序集的 Python 存根(用于 IDE 提示)。如果您选择放弃 IDE 提示,无需安装 .NET 9.0。
84+
程序集存放在 `data/dlls` 目录。下面是存根生成方法:
85+
86+
```bash
87+
powershell ./scripts/generate-stubs.ps1
88+
```
89+
90+
如果您正在使用 Linux 发行版,请先安装 [PowerShell 7](https://github.com/PowerShell/PowerShell),并将上方命令中的 `powershell` 替换为 `pwsh`
91+
8192
### 贡献准则
8293

8394
**您为 SecRandom 贡献的功能须遵循以下准则:**

README.md

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,26 @@
4040
4141
## 📖 目录
4242

43-
- [🎯 为什么选择公平抽取](#-为什么选择公平抽取)
44-
- [🌟 核心亮点](#-核心亮点)
45-
- [📥 下载](#-下载)
46-
- [📸 软件截图](#-软件截图)
47-
- [🙏 贡献者](#-贡献者和特别感谢)
48-
- [💝 捐献支持](#-捐献支持)
49-
- [📞 联系方式](#-联系方式)
50-
- [📄 官方文档](#-官方文档)
51-
- [✨ Star历程](#-star历程)
43+
- [SecRandom - 公平随机抽取系统](#secrandom---公平随机抽取系统)
44+
- [📖 目录](#-目录)
45+
- [🎯 为什么选择公平抽取](#-为什么选择公平抽取)
46+
- [🌟 核心亮点](#-核心亮点)
47+
- [🎯 智能公平抽取系统](#-智能公平抽取系统)
48+
- [🎨 现代化用户体验](#-现代化用户体验)
49+
- [🚀 强大功能集](#-强大功能集)
50+
- [💻 系统兼容性](#-系统兼容性)
51+
- [📥 下载](#-下载)
52+
- [🌐 官方下载页面](#-官方下载页面)
53+
- [📸 软件截图](#-软件截图)
54+
- [🙏 贡献者和特别感谢](#-贡献者和特别感谢)
55+
- [第三方依赖与代码](#第三方依赖与代码)
56+
- [PythonNET-Stubs-Generator](#pythonnet-stubs-generator)
57+
- [💝 捐献支持](#-捐献支持)
58+
- [爱发电支持](#爱发电支持)
59+
- [📞 联系方式](#-联系方式)
60+
- [📄 官方文档](#-官方文档)
61+
- [贡献指南与 Actions 构建工作流](#贡献指南与-actions-构建工作流)
62+
- [✨ Star历程](#-star历程)
5263

5364
## 🎯 为什么选择公平抽取
5465

@@ -139,6 +150,20 @@
139150

140151
<!-- ALL-CONTRIBUTORS-LIST:END -->
141152

153+
## 第三方依赖与代码
154+
155+
本项目使用了以下第三方代码:
156+
157+
### PythonNET-Stubs-Generator
158+
- **路径**`vendors/pythonnet-stub-generator/`
159+
- **来源**[MHDante/pythonnet-stub-generator](https://github.com/MHDante/pythonnet-stub-generator)
160+
- **许可证**:MIT License
161+
- **版权**
162+
- Copyright (c) 2019 Robert McNeel & Associates
163+
- Copyright (c) 2022 Dante Camarena
164+
- **状态**:修改了编译目标平台为 .NET 9.0
165+
- *注:原始 MIT License 文本保留在 `vendors/pythonnet-stub-generator/LICENSE.md` 中。*
166+
142167
## 💝 捐献支持
143168

144169
如果您觉得 SecRandom 对您有帮助,欢迎支持我们的开发工作!
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
import asyncio
2+
import threading
3+
from typing import Optional
4+
from loguru import logger
5+
6+
from app.tools.path_utils import get_data_path
7+
8+
CSHARP_AVAILABLE = False
9+
10+
try:
11+
# 导入 Python.NET
12+
from pythonnet import load
13+
load("coreclr", runtime_config=get_data_path("dlls", "dotnet.runtimeconfig.json"))
14+
15+
# 加载 .NET CoreCLR 程序集
16+
import clr
17+
clr.AddReference("ClassIsland.Shared.IPC")
18+
clr.AddReference("SecRandom4Ci.Interface")
19+
20+
# 导入程序集
21+
from System import Action
22+
from ClassIsland.Shared.Enums import TimeState
23+
from ClassIsland.Shared.IPC import IpcClient, IpcRoutedNotifyIds
24+
from ClassIsland.Shared.IPC.Abstractions.Services import IPublicLessonsService
25+
from dotnetCampus.Ipc.CompilerServices.GeneratedProxies import GeneratedIpcFactory
26+
from SecRandom4Ci.Interface.Services import ISecRandomService
27+
from SecRandom4Ci.Interface.Models import CallResult, Student
28+
29+
CSHARP_AVAILABLE = True
30+
except:
31+
logger.warning("无法加载 Python.NET,将会回滚!")
32+
33+
34+
if CSHARP_AVAILABLE:
35+
class CSharpIPCHandler:
36+
"""C# dotnetCampus.Ipc 处理器,用于连接 ClassIsland 实例"""
37+
_instance: Optional["CSharpIPCHandler"] = None
38+
39+
def __new__(cls):
40+
if cls._instance is None:
41+
cls._instance = super().__new__(cls)
42+
cls._instance._initialized = False
43+
return cls._instance
44+
45+
@classmethod
46+
def instance(cls):
47+
"""获取单例实例"""
48+
if cls._instance is None:
49+
cls._instance = cls()
50+
return cls._instance
51+
52+
def __init__(self):
53+
"""
54+
初始化 C# IPC 处理器
55+
"""
56+
self.ipc_client: Optional[IpcClient] = None
57+
self.client_thread: Optional[threading.Thread] = None
58+
self.is_running = False
59+
60+
def start_ipc_client(self) -> bool:
61+
"""
62+
启动 C# IPC 客户端
63+
64+
Returns:
65+
启动成功返回True,失败返回False
66+
"""
67+
if self.is_running:
68+
return True
69+
70+
try:
71+
self.client_thread = threading.Thread(target=self._run_client, daemon=False)
72+
self.client_thread.start()
73+
self.is_running = True
74+
return True
75+
except Exception as e:
76+
logger.error(f"启动 C# IPC 客户端失败: {e}")
77+
return False
78+
79+
def stop_ipc_client(self):
80+
"""停止 C# IPC 客户端"""
81+
self.is_running = False
82+
if self.client_thread and self.client_thread.is_alive():
83+
self.client_thread.join(timeout=1)
84+
85+
def send_notification(
86+
self,
87+
class_name,
88+
selected_students,
89+
draw_count=1,
90+
settings=None,
91+
settings_group=None
92+
) -> bool:
93+
"""发送提醒"""
94+
95+
if settings:
96+
display_duration = settings.get("notification_display_duration", 5)
97+
else:
98+
display_duration = 5
99+
100+
randomService = GeneratedIpcFactory.CreateIpcProxy[ISecRandomService](
101+
self.ipc_client.Provider, self.ipc_client.PeerProxy)
102+
result = self.convert_to_call_result(class_name, selected_students, draw_count, display_duration)
103+
randomService.NotifyResult(result)
104+
105+
return True
106+
107+
def is_breaking(self) -> bool:
108+
"""是否处于下课时间"""
109+
lessonSc = GeneratedIpcFactory.CreateIpcProxy[IPublicLessonsService](
110+
self.ipc_client.Provider, self.ipc_client.PeerProxy)
111+
state = lessonSc.CurrentState in [getattr(TimeState, "None"), TimeState.PrepareOnClass, TimeState.Breaking, TimeState.AfterSchool]
112+
logger.debug(f"获取到的 ClassIsland 时间状态: {lessonSc.CurrentState} 是否下课: {state}")
113+
return state
114+
115+
@staticmethod
116+
def convert_to_call_result(class_name: str, selected_students, draw_count: int, display_duration=5.0) -> CallResult:
117+
result = CallResult()
118+
result.ClassName = class_name
119+
result.DrawCount = draw_count
120+
result.DisplayDuration = display_duration
121+
for student in selected_students:
122+
cs_student = Student()
123+
cs_student.StudentId = student[0]
124+
cs_student.StudentName = student[1]
125+
cs_student.Exists = student[2]
126+
result.SelectedStudents.Add(cs_student)
127+
return result
128+
129+
def _on_class_test(self):
130+
lessonSc = GeneratedIpcFactory.CreateIpcProxy[IPublicLessonsService](
131+
self.ipc_client.Provider, self.ipc_client.PeerProxy)
132+
logger.debug(f"上课 {lessonSc.CurrentSubject.Name} 时间: {lessonSc.CurrentTimeLayoutItem}")
133+
134+
def _run_client(self):
135+
"""运行 C# IPC 客户端"""
136+
137+
async def client():
138+
"""异步客户端"""
139+
140+
self.ipc_client = IpcClient()
141+
self.ipc_client.JsonIpcProvider.AddNotifyHandler(IpcRoutedNotifyIds.OnClassNotifyId, Action(lambda: self._on_class_test()))
142+
143+
task = self.ipc_client.Connect()
144+
await loop.run_in_executor(None, lambda: task.Wait())
145+
146+
while self.is_running:
147+
await asyncio.sleep(1)
148+
149+
self.ipc_client = None
150+
151+
# 启动新的 asyncio 事件循环
152+
loop = asyncio.new_event_loop()
153+
asyncio.set_event_loop(loop)
154+
loop.run_until_complete(client())
155+
loop.close()
156+
else:
157+
class CSharpIPCHandler:
158+
"""C# dotnetCampus.Ipc 处理器,用于连接 ClassIsland 实例"""
159+
_instance: Optional["CSharpIPCHandler"] = None
160+
161+
def __new__(cls):
162+
if cls._instance is None:
163+
cls._instance = super().__new__(cls)
164+
cls._instance._initialized = False
165+
return cls._instance
166+
167+
@classmethod
168+
def instance(cls):
169+
"""获取单例实例"""
170+
if cls._instance is None:
171+
cls._instance = cls()
172+
return cls._instance
173+
174+
def __init__(self):
175+
"""
176+
初始化 C# IPC 处理器
177+
"""
178+
self.ipc_client = None
179+
self.client_thread = None
180+
self.is_running = False
181+
182+
def start_ipc_client(self) -> bool:
183+
"""
184+
启动 C# IPC 客户端
185+
186+
Returns:
187+
启动成功返回True,失败返回False
188+
"""
189+
return False
190+
191+
def stop_ipc_client(self):
192+
"""停止 C# IPC 客户端"""
193+
pass
194+
195+
def send_notification(
196+
self,
197+
class_name,
198+
selected_students,
199+
draw_count=1,
200+
settings=None,
201+
settings_group=None
202+
) -> bool:
203+
"""发送提醒"""
204+
return False
205+
206+
def is_breaking(self) -> bool:
207+
"""是否处于下课时间"""
208+
return False
209+
210+
@staticmethod
211+
def convert_to_call_result(class_name: str, selected_students, draw_count: int, display_duration=5.0) -> object:
212+
return object
213+
214+
def _on_class_test(self):
215+
pass
216+
217+
def _run_client(self):
218+
"""运行 C# IPC 客户端"""
219+
pass

app/common/extraction/extract.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from qfluentwidgets import *
1212

1313
from app.Language.obtain_language import get_content_name_async
14+
from app.common.IPC_URL.csharp_ipc_handler import CSharpIPCHandler
1415
from app.common.extraction.cses_parser import CSESParser
1516
from app.tools.path_utils import *
1617
from app.tools.settings_access import readme_settings_async
@@ -37,11 +38,7 @@ def _is_non_class_time() -> bool:
3738
)
3839
logger.debug(f"是否启用了ClassIsland数据源: {use_class_island_source}")
3940
if use_class_island_source:
40-
class_island_break_status = readme_settings_async(
41-
"time_settings", "current_class_island_break_status"
42-
)
43-
logger.debug(f"ClassIsland数据源当前课间状态: {class_island_break_status}")
44-
return bool(class_island_break_status)
41+
return CSharpIPCHandler.instance().is_breaking()
4542
else:
4643
current_day_of_week = _get_current_day_of_week()
4744
class_times = _get_class_times_by_day(current_day_of_week)

app/common/notification/notification_service.py

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from app.Language.obtain_language import get_any_position_value
1111
from app.tools.settings_access import readme_settings_async
1212
from app.common.IPC_URL.url_ipc_handler import URLIPCHandler
13+
from app.common.IPC_URL.csharp_ipc_handler import CSharpIPCHandler
1314

1415

1516
class NotificationContentWidget(QWidget):
@@ -863,15 +864,15 @@ def __init__(self):
863864
self.ipc_handler = URLIPCHandler("SecRandom", "secrandom")
864865
self._initialized = True
865866

866-
def send_to_classisland(
867+
def send_to_classisland2(
867868
self,
868869
class_name,
869870
selected_students,
870871
draw_count=1,
871872
settings=None,
872873
settings_group=None,
873874
):
874-
"""发送通知到ClassIsland
875+
"""发送通知到ClassIsland(旧版)
875876
876877
Args:
877878
class_name: 班级名称
@@ -938,6 +939,42 @@ def send_to_classisland(
938939
class_name, selected_students, draw_count, settings, settings_group
939940
)
940941

942+
def send_to_classisland(
943+
self,
944+
class_name,
945+
selected_students,
946+
draw_count=1,
947+
settings=None,
948+
settings_group=None,
949+
):
950+
"""发送通知到ClassIsland
951+
952+
Args:
953+
class_name: 班级名称
954+
selected_students: 选中的学生列表 [(学号, 姓名, 是否存在), ...]
955+
draw_count: 抽取的学生数量
956+
settings: 通知设置参数
957+
settings_group: 设置组名称
958+
"""
959+
960+
try:
961+
cs_ipc = CSharpIPCHandler.instance()
962+
status = cs_ipc.send_notification(class_name, selected_students, draw_count, settings, settings_group)
963+
if status:
964+
logger.info("成功发送通知到ClassIsland,结果未知")
965+
else:
966+
logger.info("因错误回退到SecRandom通知服务")
967+
self._show_secrandom_notification(
968+
class_name, selected_students, draw_count, settings, settings_group
969+
)
970+
except Exception as e:
971+
logger.exception("发送通知到ClassIsland时出错: {}", e)
972+
# 如果发生异常,回退到SecRandom通知服务
973+
logger.info("因错误回退到SecRandom通知服务")
974+
self._show_secrandom_notification(
975+
class_name, selected_students, draw_count, settings, settings_group
976+
)
977+
941978
def _show_secrandom_notification(
942979
self,
943980
class_name,

0 commit comments

Comments
 (0)