VS2013写window服务,实现定时短信功能

本文详细介绍了如何在Windows环境下实现监控数据库中特定数值,并在数值减少到预设值时,通过短信提醒用户的功能。通过创建Windows服务,确保服务随系统启动并稳定运行,不受其他影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

好久没来了,说要写博的,但人懒……一直没写过。最近做一个功能,这样的可能以后很少用到,时间久不用,可能还会忘记,还是写篇记录下来吧。

公司业务里,有一项需求,需要监控数据库里的某些数值,当数值减少到预设值时,就通过短信提醒用户,这种功能做在IIS上似乎不太稳定,如果IIS重启之类的,就经常会出现一些问题。后来就想,能否做成windows的服务,只要服务器开着,这项服务就随系统开启启动,不受其他影响。

随后就实现了这个功能。

 

第一步,新建一个windowsService服务

在windows桌面下,有windows服务,项目名称自定义吧。我就改成公司项目的名称了Fanso2o_MonitoringActivity

 

第二步,先写业务吧,单击切换到代码视图吧。

 

第三步,业务处理

 

哎呀,截图太麻烦了,直接上代码。

  1 public partial class Service1 : ServiceBase
  2     {
  3         //因为业务不多,就直接写在这里面了。如果业务多,最好还是另外封装。        
  4         public static List<UserMonitor> user = new List<UserMonitor>();
  5 
  6         //创建一个定时执行检查的全局对象。我的时间是写在配置文件里的,可以修改的,此处直接上数字了。
  7         public static readonly Timer timer = new Timer(1800000);
  8         public Service1()
  9         {
 10             InitializeComponent();
 11         }
 12         /// <summary>
 13         /// 服务启动,执行定时检查任务。
 14         /// </summary>
 15         /// <param name="args"></param>
 16         protected override void OnStart(string[] args)
 17         {
 18             //将业务方法添加到定时执行的对象事件中,
 19             timer.Elapsed += new ElapsedEventHandler(Time);
 20             timer.AutoReset = true;
 21             timer.Enabled = true;
 22         }
 23 
 24         /// <summary>
 25         /// 服务关闭
 26         /// </summary>
 27         protected override void OnStop()
 28         {
 29             timer.Close();
 30             timer.Dispose();
 31 
 32         }
 33 
 34         /// <summary>
 35         /// 服务重启
 36         /// </summary>
 37         protected override void OnContinue()
 38         {
 39             timer.Start();
 40             base.OnContinue();
 41         }
 42 
 43         /// <summary>
 44         /// 服务暂停
 45         /// </summary>
 46         protected override void OnPause()
 47         {
 48             timer.Stop();
 49             base.OnPause();
 50         }
 51 
 52         /// <summary>
 53         /// 执行业务的方法
 54         /// </summary>
 55         /// <param name="source"></param>
 56         /// <param name="e"></param>
 57         public static async void Time(object source, System.Timers.ElapsedEventArgs e)
 58         {
 59             await MonitoringActivity();
 60         }
 61 
 62         /// <summary>
 63         /// 具体业务的异步方法
 64         /// </summary>
 65         /// <returns></returns>
 66         static async Task<bool> MonitoringActivity()
 67         {
 68             return await Task.Run<bool>(() =>
 69             {
 70                //具体的业务方法,根据自己的需要写吧。
 71                 var defaultSmsService = new DefaultSmsService();
 72                 var activityMonitorDao = new MongoCommonDao<ActivityMonitor, string>();
 73                 var groupDao = new MongoCommonDao<CompanyResourceGroup, string>();
 74                 var resourceDao = new MongoCommonDao<CompanyResource, string>();
 75                 var monitorList = activityMonitorDao.Set.FindAll().ToList();
 76 
 77                 var group =
 78                     groupDao.Set.Find(Query<CompanyResourceGroup>.In(x => x.Id,
 79                         monitorList.Select(c => c.ActivityGroupid)))
 80                         .SetFields(new string[] { "Title", "Amount", "GetAmount" }).ToList();
 81                 monitorList.ForEach(x =>
 82                 {
 83                     x.MonitorPerson.ForEach(c =>
 84                     {
 85                         if (user.All(y => y.Name != c.MonitorName && y.Phone != c.Phone))
 86                         {
 87                             var u = new UserMonitor
 88                             {
 89                                 Phone = c.Phone,
 90                                 Name = c.MonitorName,
 91                                 CompanyGroup = new List<CompanyResourceGroup>(),
 92                                 count = 0,
 93                                 SenCount = -1
 94                             };
 95                             user.Add(u);
 96                         }
 97                         var g = group.Find(h => h.Id == x.ActivityGroupid);
 98                         if (g != null && g.Residue <= 50)
 99                         {
100                             var index = user.FindIndex(h => h.Name == c.MonitorName && h.Phone == c.Phone);
101                             if (index>-1&&user[index].CompanyGroup.All(h => h.Id != x.ActivityGroupid))
102                             {
103                                 user[index].count++;
104                                 user[index].CompanyGroup.Add(g);
105                             }
106                         }
107                         else if (g != null && g.Residue > 50)
108                         {
109                             var index = user.FindIndex(h => h.Name == c.MonitorName && h.Phone == c.Phone);
110                             if (index > -1 && user[index].CompanyGroup.Any(h => h.Id == x.ActivityGroupid))
111                             {
112                                 user[index].CompanyGroup.RemoveAll(h => h.Id == x.ActivityGroupid);
113                                 user[index].count--;
114                             }
115                         }
116                     });
117                 });
118                 if (user.Any())
119                 {
120                     var smsStr = new List<SmsModel>();
121                     user.ForEach(x =>
122                     {
123                         if (x.count > 0 && x.SenCount < x.count)
124                         {
125                             var m = new SmsModel();
126                             m.PhoneList = x.Phone;
127                             m.SmsStr.Add(x.count.ToString());
128                             var str = new StringBuilder();
129                             var num = x.CompanyGroup.Count > 3 ? 3 : x.CompanyGroup.Count;
130                             for (int i = 0; i < num; i++)
131                             {
132                                 str.Append(x.CompanyGroup[i].Title + ",");
133                             }
134                             m.SmsStr.Add(str.ToString());
135                             x.SenCount = x.count;
136 
137                             //发送短信的具体方法
138                             var s = defaultSmsService.SendValidateCode(m.PhoneList, m.SmsStr);
139                             Console.WriteLine(s);
140                         }
141                     });
142                 }
143                 return true;
144             });
145         }
146     }
147 
148     /// <summary>
149     /// 一些用到的类
150     /// </summary>
151     public class UserMonitor
152     {
153         public string Name { get; set; }
154         public string Phone { get; set; }
155         public int count { get; set; }
156         public int SenCount { get; set; }
157         public List<CompanyResourceGroup> CompanyGroup { get; set; }
158 
159         public UserMonitor()
160         {
161             this.CompanyGroup = new List<CompanyResourceGroup>();
162             this.Name = string.Empty;
163             this.Phone = string.Empty;
164             this.count = 0;
165             this.SenCount = -1;
166         }
167     }
168 
169     /// <summary>
170     /// 一些用到的类
171     /// </summary>
172     public class SmsModel
173     {
174         public string PhoneList { get; set; }
175         public List<string> SmsStr { get; set; }
176 
177         public SmsModel()
178         {
179             PhoneList = string.Empty;
180             SmsStr = new List<string>();
181         }
182     }
windows服务的具体业务代码

 

业务处理完,就在service1中添加安装程序

第五步,在设置中,添加安装程序。会出现下图:

这两个是服务安装的一些组件。

第六步,分别在这两个组件上鼠标右键,选择属性,进行相应的设置。

displayName是服务显示在服务列表中的名字,可以根据需要填写。

serviceName是服务执行注册到系统中的名字,不可与其他服务重名。如果displayName没填写,那显示在服务列表中的,将是此属性中的名字。

starType是设置服务运行的方式,可以进行选择。

Account是服务的启动方式,也可以进行选择,LocalSystem是随系统启动而自动启动。

第七步,业务处理完毕后,就生成服务(最好用管理员权限运行VS2013,此处是个坑,害我检查了好久,我的不知道为什么,必需用管理员权限生成的,安装服务时,才不会报错,否则就有问题。)

第八步,服务生成完毕后,打开bin\Debug目录,新建两个文本文件,一个用来安装服务,另一个用来删除服务的。

           

 

Install.text里面放如下代码:

%SystemRoot%\Microsoft.NET\Framework\v4.0.30319\installutil.exe D:\C#\Fanso2o_MonitoringActivity\Fanso2o_MonitoringActivity\bin\Debug\Fanso2o_MonitoringActivity.exe
Net Start Fanso2o_MonitoringActivity
sc config Fanso2o_MonitoringActivity start= auto
pause

Uninstall.text里面放如下代码:

%SystemRoot%\Microsoft.NET\Framework\v4.0.30319\installutil.exe /u D:\C#\Fanso2o_MonitoringActivity\Fanso2o_MonitoringActivity\bin\Debug\Fanso2o_MonitoringActivity.exe
pause

 

这两段代码的具体含意,

%SystemRoot%\Microsoft.NET\Framework\v4.0.30319\installutil.exe就是调用.net的一个服务安装程序,来执行服务的安装和删除。【.net的安装程序,默认是装在系统盘下的那个位置,如果有改变,请更改为具体的路径】

此段代码后面跟着的是服务所在的文件夹的路径和服务的可执行文件。我是放在D盘下的,因此写D盘下的路径。可根据需要更改。

Net Start Fanso2o_MonitoringActivity  服务安装好后,立刻执行服务。

sc config Fanso2o_MonitoringActivity start= auto 设置服务的启动方式为自动。其实已经在代码里设置过了,这个地方没必要。

 

以上全部改好后,将Install.tex、Uninstall.text两个文件的后缀名改为bat,就可以执行服务的安装和删除了。

【注意一:执行此两个文件时,需要用管理员权限执行,否则就会报错。】

【注意二:服务安装后,请不要改动源代码重新生成,否则会删除不掉。如必需改动源代码,请先删除服务,删除后再改动生成,重新安装】

 

 

转载于:https://www.cnblogs.com/fong/p/5137685.html

<think>好的,我现在需要帮用户解决他们提到的四个扩展功能:趋势图显示、报警功能、设备配置导入导出和用户权限管理。首先,我得逐一分析每个功能的需求和实现步骤,确保覆盖所有要点。 用户之前已经有一个基于PyScada的上位机软件,现在需要扩展。我需要回忆之前的对话,确保这次回答与之前的架构一致。比如,数据库设计已经存在devices、variables、history表,现在要新增alarms表,还需要考虑权限管理的表结构。 关于趋势图,用户提到用matplotlib嵌入到Tkinter界面。我需要想到如何在Tkinter中集成matplotlib的图表,可能需要使用FigureCanvasTkAgg。同时,要考虑数据从SQLite中获取,可能需要定时刷新图表,或者根据用户选择的时间范围查询数据。另外,要确保数据量大的时候性能不会太差,可能需要分页或采样。 报警功能需要设计数据库表alarms,包含触发条件、状态、时间等字段。报警触发逻辑是在数据更新时检查,所以需要在数据数据库的地方添加检查逻辑。报警通知可以用界面弹窗或者声音提示,但用户可能希望简单,所以先做界面提示。报警历史记录需要存储,并且可能需要确认机制,比如用户确认后标记为已处理。 设备配置导入导出用JSON格式,这需要将现有的设备配置(devices和variables表的数据)导出为JSON文件,以及从JSON导入时验证数据并插入数据库。需要考虑数据完整性,比如外键约束,导入时的事务处理,避免部分数据失败导致数据库不一致。 用户权限管理需要角色表、用户表、操作日志表。权限可以分为不同级别,如管理员、操作员、查看员。登录界面需要验证用户,并根据权限显示或隐藏功能。操作日志要记录关键操作,比如修改参数、控制开关,记录用户、时间、操作详情。这部分需要修改现有的UI,添加登录窗口,并在每个需要权限的操作前检查用户权限。 接下来,我需要考虑代码示例的正确性。比如,趋势图部分,使用matplotlib的示例是否正确,是否需要在Tkinter中正确布局。报警功能的数据表设计是否合理,触发逻辑是否在正确的位置。JSON导入导出是否处理了数据库事务,避免部分数据导入问题。权限管理中的密码存储是否安全,是否需要加密,或者用户可能只是简单的存储,暂时不考虑加密,但需要提醒用户。 还需要注意线程安全,比如数据采集和UI更新是否在同一个线程,特别是报警触发时如果涉及UI变化,需要用Tkinter的after方法或者队列机制。另外,扩展功能是否会影响原有系统的稳定性,需要测试建议。 最后,要确保回答结构清晰,分点说明每个功能实现步骤,提供代码示例,并提醒注意事项,比如性能、安全、数据验证等。同时,给出扩展建议,比如报警通知方式多样化,或者权限的细化管理。</think>我将为您分模块详细说明SCADA系统的扩展功能实现方案: 一、趋势图显示模块(Matplotlib集成) 1. 数据库增强 ```sql ALTER TABLE variables ADD COLUMN trend_enabled BOOLEAN DEFAULT 0; ``` 2. 界面改造方案 ```python # 在main_window.py中添加 from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg class TrendViewer(Toplevel): def __init__(self, var_id): super().__init__() self.figure = Figure(figsize=(6,4)) self.ax = self.figure.add_subplot(111) self.canvas = FigureCanvasTkAgg(self.figure, self) self.canvas.get_tk_widget().pack() # 加载历史数据 data = self.db.get_history(var_id, hours=24) self.ax.plot([x[0] for x in data], [x[1] for x in data]) self.canvas.draw() ``` 3. 数据获取方法 ```python # database.py新增 def get_history(self, var_id, hours=24): return self.conn.execute("""SELECT datetime(timestamp,'localtime'), value FROM history WHERE variable_id=? AND timestamp > datetime('now','-? hours') ORDER BY timestamp""", (var_id, hours)).fetchall() ``` 二、报警功能实现 1. 报警表设计(alarms.sql) ```sql CREATE TABLE alarms ( id INTEGER PRIMARY KEY, variable_id INTEGER, condition TEXT CHECK(condition IN ('>', '<', '==', '!=')), threshold REAL, message TEXT, active BOOLEAN DEFAULT 1, FOREIGN KEY(variable_id) REFERENCES variables(id) ); CREATE TABLE alarm_logs ( timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, alarm_id INTEGER, triggered_value REAL, acknowledged BOOLEAN DEFAULT 0, FOREIGN KEY(alarm_id) REFERENCES alarms(id) ); ``` 2. 报警检测逻辑 ```python # 在数据更新时触发检查 def update_variable(self, var_id, value): # 原有数据存储 self.db.update_variable(var_id, value) # 新增报警检查 alarms = self.db.get_active_alarms(var_id) for alarm in alarms: if eval(f"{value} {alarm['condition']} {alarm['threshold']}"): self.trigger_alarm(alarm['id'], value) def trigger_alarm(self, alarm_id, value): self.db.log_alarm(alarm_id, value) self.show_alarm_popup(alarm_id) # 显示报警弹窗 self.play_alarm_sound() # 播放报警音 ``` 三、设备配置导入/导出 1. JSON格式示例 ```json { "devices": [ { "name": "PLC-1", "ip": "192.168.1.100", "port": 502, "variables": [ { "name": "温度传感器", "address": 40001, "type": "holding" } ] } ] } ``` 2. 导入/导出核心代码 ```python import json class ConfigManager: def export_config(self, file_path): config = { "devices": [self._device_to_dict(d) for d in self.db.get_devices()] } with open(file_path, 'w') as f: json.dump(config, f, indent=2) def import_config(self, file_path): with open(file_path) as f: config = json.load(f) with self.conn: # 使用事务 for device in config['devices']: dev_id = self._create_device(device) for var in device['variables']: self._create_variable(dev_id, var) ``` 四、用户权限管理系统 1. 权限表设计 ```sql CREATE TABLE users ( id INTEGER PRIMARY KEY, username TEXT UNIQUE, password_hash TEXT, role TEXT CHECK(role IN ('admin', 'operator', 'viewer')) ); CREATE TABLE operation_logs ( timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, user_id INTEGER, action TEXT, details TEXT, FOREIGN KEY(user_id) REFERENCES users(id) ); ``` 2. 权限验证装饰器 ```python def require_role(required_role): def decorator(func): def wrapper(self, *args, **kwargs): if self.current_user.role != required_role: self.show_error("权限不足") return return func(self, *args, **kwargs) return wrapper return decorator # 使用示例 @require_role('admin') def delete_device(self, device_id): # 删除设备逻辑 self.log_operation(f"删除设备 {device_id}") ``` 开发注意事项: 1. 趋势图优化: - 使用`matplotlib.dates`优化时间轴显示 - 添加滚动缩放功能 - 实现数据采样(当数据点>10000时自动降采样) 2. 报警管理: - 实现报警确认机制 - 添加延时报警功能(持续触发超过N秒才记录) - 支持报警分级(普通/重要/紧急) 3. 配置导入: - 添加JSON schema验证 - 实现冲突处理(重复设备名称检测) - 支持增量导入 4. 权限系统: - 密码使用bcrypt加密存储 - 会话超时机制(15分钟无操作自动注销) - 操作日志审计功能(支持按时间/用户过滤) 扩展建议: 1. 趋势图可添加多变量对比显示 2. 报警支持短信/邮件通知集成 3. 配置版本控制(Git集成) 4. 实现基于角色的界面元素控制(动态菜单) 每个扩展模块约需增加300-500行代码,建议采用模块化开发方式逐个功能迭代实现
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值