Skip to content

Commit cb57555

Browse files
添加NewsFlash插件:金融消息推送插件
功能特性: - 支持多消息源配置(HTTP JSON、RSS、WebSocket) - 状态栏实时显示最新消息,支持两行滚动 - 消息前缀自定义,正文颜色跟随主程序 - 未读消息红色数字提醒 - 右键菜单:查看全部消息、标记已读、设置 - Setting界面管理订阅源 - 点击消息打开浏览器查看详情 - Tooltip显示完整消息内容 技术实现: - C++ MFC插件开发 - WinHTTP实现HTTP请求 - yyjson解析JSON数据 - 简易RSS XML解析 - 后台线程轮询消息源 - 消息缓存和去重机制
1 parent d012909 commit cb57555

20 files changed

Lines changed: 2515 additions & 0 deletions

Plugins/NewsFlash/DataManager.cpp

Lines changed: 798 additions & 0 deletions
Large diffs are not rendered by default.

Plugins/NewsFlash/DataManager.h

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
#pragma once
2+
#include <string>
3+
#include <vector>
4+
#include <map>
5+
#include <memory>
6+
#include <mutex>
7+
#include <ctime>
8+
#include "resource.h"
9+
10+
#define g_data CDataManager::Instance()
11+
12+
// 消息项结构
13+
struct NewsItem
14+
{
15+
std::wstring id; // 消息唯一ID
16+
std::wstring title; // 消息标题
17+
std::wstring content; // 消息内容
18+
std::wstring source_name; // 消息源名称
19+
std::wstring source_prefix; // 消息源前缀(如 [金十])
20+
std::wstring url; // 消息链接
21+
std::wstring time_str; // 时间字符串
22+
time_t timestamp; // 时间戳
23+
bool is_read; // 是否已读
24+
25+
NewsItem() : timestamp(0), is_read(false) {}
26+
};
27+
28+
// 消息源配置
29+
struct NewsSource
30+
{
31+
std::wstring id; // 消息源唯一ID
32+
std::wstring name; // 消息源名称
33+
std::wstring prefix; // 显示前缀(如 [金十])
34+
std::wstring url; // API URL
35+
std::wstring type; // 类型:http_json, rss等
36+
int interval; // 轮询间隔(秒)
37+
bool enabled; // 是否启用
38+
time_t last_update; // 上次更新时间
39+
40+
// JSON解析配置
41+
std::wstring json_data_path; // 数据路径,如 "data.items"
42+
std::wstring json_id_field; // ID字段名
43+
std::wstring json_title_field; // 标题字段名
44+
std::wstring json_time_field; // 时间字段名
45+
std::wstring json_url_field; // URL字段名
46+
47+
NewsSource() : interval(10), enabled(true), last_update(0) {}
48+
};
49+
50+
// 设置数据
51+
struct SettingData
52+
{
53+
std::vector<NewsSource> sources; // 消息源列表
54+
int max_display_length; // 最大显示长度
55+
int max_history_count; // 最大历史记录数
56+
bool show_time; // 是否显示时间
57+
58+
SettingData() : max_display_length(100), max_history_count(100), show_time(true) {}
59+
};
60+
61+
class CDataManager
62+
{
63+
private:
64+
CDataManager();
65+
~CDataManager();
66+
67+
public:
68+
static CDataManager& Instance();
69+
70+
void LoadConfig(const std::wstring& config_dir);
71+
void SaveConfig() const;
72+
const CString& StringRes(UINT id);
73+
void DPIFromWindow(CWnd* pWnd);
74+
int DPI(int pixel);
75+
float DPIF(float pixel);
76+
int RDPI(int pixel);
77+
HICON GetIcon(UINT id);
78+
79+
// 消息管理
80+
void StartFetching();
81+
void StopFetching();
82+
void FetchAllSources();
83+
void FetchSource(const NewsSource& source);
84+
85+
// 获取最新消息
86+
std::vector<std::shared_ptr<NewsItem>> GetLatestNews(int count = 2);
87+
std::vector<std::shared_ptr<NewsItem>> GetUnreadNews();
88+
std::vector<std::shared_ptr<NewsItem>> GetReadNews();
89+
90+
// 标记已读
91+
void MarkAsRead(const std::wstring& news_id);
92+
void MarkAllAsRead();
93+
94+
// 获取消息详情
95+
std::shared_ptr<NewsItem> GetNewsById(const std::wstring& id);
96+
97+
// HTTP请求(简单实现)
98+
bool HttpGet(const std::wstring& url, std::wstring& response);
99+
100+
// JSON解析辅助(简单实现)
101+
bool ParseJsonNews(const std::wstring& json, const NewsSource& source,
102+
std::vector<std::shared_ptr<NewsItem>>& news_list);
103+
// RSS解析辅助(简单实现)
104+
bool ParseRssNews(const std::wstring& xml, const NewsSource& source,
105+
std::vector<std::shared_ptr<NewsItem>>& news_list);
106+
107+
SettingData m_setting_data;
108+
109+
// 文本颜色(由主程序通过扩展信息传递),0 表示未设置
110+
COLORREF m_label_text_color{ 0 };
111+
COLORREF m_value_text_color{ 0 };
112+
113+
private:
114+
void LoadDefaultSources();
115+
void AddTestData();
116+
void SaveNewsCache();
117+
void LoadNewsCache();
118+
static UINT FetchThreadProc(LPVOID pParam);
119+
120+
private:
121+
static CDataManager m_instance;
122+
std::wstring m_config_path;
123+
std::map<UINT, CString> m_string_table;
124+
std::map<UINT, HICON> m_icons;
125+
int m_dpi{ 96 };
126+
127+
// 消息数据
128+
std::vector<std::shared_ptr<NewsItem>> m_all_news;
129+
std::mutex m_news_mutex;
130+
131+
// 后台线程
132+
CWinThread* m_fetch_thread;
133+
bool m_stop_fetching;
134+
std::map<std::wstring, time_t> m_last_news_id; // 记录每个源的最后一条消息ID
135+
};
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
#include "pch.h"
2+
#include "EditSourceDlg.h"
3+
#include "resource.h"
4+
5+
IMPLEMENT_DYNAMIC(CEditSourceDlg, CDialogEx)
6+
7+
CEditSourceDlg::CEditSourceDlg(NewsSource& source, CWnd* pParent)
8+
: CDialogEx(IDD_EDIT_SOURCE_DIALOG, pParent)
9+
, m_source(source)
10+
{
11+
}
12+
13+
CEditSourceDlg::~CEditSourceDlg()
14+
{
15+
}
16+
17+
void CEditSourceDlg::DoDataExchange(CDataExchange* pDX)
18+
{
19+
CDialogEx::DoDataExchange(pDX);
20+
}
21+
22+
BEGIN_MESSAGE_MAP(CEditSourceDlg, CDialogEx)
23+
END_MESSAGE_MAP()
24+
25+
BOOL CEditSourceDlg::OnInitDialog()
26+
{
27+
CDialogEx::OnInitDialog();
28+
29+
// 初始化类型下拉框
30+
InitTypeCombo();
31+
32+
// 名称
33+
SetDlgItemText(IDC_SOURCE_NAME, m_source.name.c_str());
34+
// 前缀
35+
SetDlgItemText(IDC_SOURCE_PREFIX, m_source.prefix.c_str());
36+
// URL
37+
SetDlgItemText(IDC_SOURCE_URL, m_source.url.c_str());
38+
// 轮询间隔
39+
if (m_source.interval <= 0)
40+
m_source.interval = 60;
41+
SetDlgItemInt(IDC_SOURCE_INTERVAL, m_source.interval, FALSE);
42+
// 启用
43+
CheckDlgButton(IDC_SOURCE_ENABLED, m_source.enabled ? BST_CHECKED : BST_UNCHECKED);
44+
45+
// JSON 映射
46+
SetDlgItemText(IDC_JSON_DATA_PATH, m_source.json_data_path.c_str());
47+
SetDlgItemText(IDC_JSON_ID_FIELD, m_source.json_id_field.c_str());
48+
SetDlgItemText(IDC_JSON_TITLE_FIELD, m_source.json_title_field.c_str());
49+
SetDlgItemText(IDC_JSON_TIME_FIELD, m_source.json_time_field.c_str());
50+
SetDlgItemText(IDC_JSON_URL_FIELD, m_source.json_url_field.c_str());
51+
52+
return TRUE;
53+
}
54+
55+
void CEditSourceDlg::InitTypeCombo()
56+
{
57+
CComboBox* pCombo = (CComboBox*)GetDlgItem(IDC_SOURCE_TYPE);
58+
if (pCombo == nullptr)
59+
return;
60+
61+
// 下拉项文本为中文,内部类型使用固定字符串
62+
int indexHttpJson = pCombo->AddString(L"HTTP JSON \u63a5\u53e3");
63+
pCombo->SetItemDataPtr(indexHttpJson, (void*)L"http_json");
64+
65+
int indexRss = pCombo->AddString(L"RSS \u8ba2\u9605\u6e90");
66+
pCombo->SetItemDataPtr(indexRss, (void*)L"rss");
67+
68+
int indexWs = pCombo->AddString(L"WebSocket \u63a8\u9001");
69+
pCombo->SetItemDataPtr(indexWs, (void*)L"websocket");
70+
71+
// 根据当前 source.type 选择
72+
std::wstring type = m_source.type;
73+
if (type.empty())
74+
type = L"http_json";
75+
76+
int count = pCombo->GetCount();
77+
for (int i = 0; i < count; ++i)
78+
{
79+
const wchar_t* pType = (const wchar_t*)pCombo->GetItemDataPtr(i);
80+
if (pType != nullptr && type == pType)
81+
{
82+
pCombo->SetCurSel(i);
83+
return;
84+
}
85+
}
86+
87+
// 默认选择 HTTP JSON
88+
pCombo->SetCurSel(indexHttpJson);
89+
}
90+
91+
void CEditSourceDlg::OnOK()
92+
{
93+
CString text;
94+
95+
// 名称
96+
GetDlgItemText(IDC_SOURCE_NAME, text);
97+
m_source.name = (LPCTSTR)text;
98+
99+
// 前缀
100+
GetDlgItemText(IDC_SOURCE_PREFIX, text);
101+
m_source.prefix = (LPCTSTR)text;
102+
103+
// URL
104+
GetDlgItemText(IDC_SOURCE_URL, text);
105+
m_source.url = (LPCTSTR)text;
106+
107+
// 轮询间隔
108+
BOOL bTrans = FALSE;
109+
UINT interval = GetDlgItemInt(IDC_SOURCE_INTERVAL, &bTrans, FALSE);
110+
if (!bTrans || interval == 0)
111+
interval = 60;
112+
m_source.interval = (int)interval;
113+
114+
// 启用
115+
m_source.enabled = (IsDlgButtonChecked(IDC_SOURCE_ENABLED) == BST_CHECKED);
116+
117+
// 类型
118+
CComboBox* pCombo = (CComboBox*)GetDlgItem(IDC_SOURCE_TYPE);
119+
if (pCombo != nullptr)
120+
{
121+
int sel = pCombo->GetCurSel();
122+
if (sel >= 0)
123+
{
124+
const wchar_t* pType = (const wchar_t*)pCombo->GetItemDataPtr(sel);
125+
if (pType != nullptr)
126+
m_source.type = pType;
127+
}
128+
}
129+
if (m_source.type.empty())
130+
m_source.type = L"http_json";
131+
132+
// JSON 映射
133+
GetDlgItemText(IDC_JSON_DATA_PATH, text);
134+
m_source.json_data_path = (LPCTSTR)text;
135+
136+
GetDlgItemText(IDC_JSON_ID_FIELD, text);
137+
m_source.json_id_field = (LPCTSTR)text;
138+
139+
GetDlgItemText(IDC_JSON_TITLE_FIELD, text);
140+
m_source.json_title_field = (LPCTSTR)text;
141+
142+
GetDlgItemText(IDC_JSON_TIME_FIELD, text);
143+
m_source.json_time_field = (LPCTSTR)text;
144+
145+
GetDlgItemText(IDC_JSON_URL_FIELD, text);
146+
m_source.json_url_field = (LPCTSTR)text;
147+
148+
CDialogEx::OnOK();
149+
}

Plugins/NewsFlash/EditSourceDlg.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#pragma once
2+
#include "afxdialogex.h"
3+
#include "DataManager.h"
4+
5+
// 编辑订阅源对话框
6+
class CEditSourceDlg : public CDialogEx
7+
{
8+
DECLARE_DYNAMIC(CEditSourceDlg)
9+
10+
public:
11+
CEditSourceDlg(NewsSource& source, CWnd* pParent = nullptr);
12+
virtual ~CEditSourceDlg();
13+
14+
enum { IDD = IDD_EDIT_SOURCE_DIALOG };
15+
16+
protected:
17+
virtual void DoDataExchange(CDataExchange* pDX);
18+
virtual BOOL OnInitDialog();
19+
virtual void OnOK();
20+
21+
DECLARE_MESSAGE_MAP()
22+
23+
private:
24+
void InitTypeCombo();
25+
26+
private:
27+
NewsSource& m_source;
28+
};

0 commit comments

Comments
 (0)