├── .gitignore ├── LICENSE ├── README.md ├── app ├── __init__.py ├── utility.py └── views.py ├── config ├── img └── 效果图.png ├── requirements.txt └── run.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/* 2 | *.pyc 3 | *.pyo 4 | .env 5 | .venv/* 6 | .gitignore 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2017, iakisey 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 开篇 2 | 3 | * 实现功能:通过测试公众号模版消息推送,能够实时获知服务器的状态。 4 | 5 | * ideal 来源于 方糖气球的 [Server酱](http://sc.ftqq.com/3.version)。 博主没有开源出来,就自己造个轮子用。 6 | 7 | * 代码基于python3的tornado实现的,实现方法很简单,语言只是工具。 8 | 9 | * [项目地址](https://github.com/iakisey/ServerMsgPush), Follower, Fork, Stars, Issues 啥都可以 🤣 10 | 11 | # 具体过程 12 | 13 | * 登陆[微信公众平台测试号](http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login) 14 | 15 | * 获取测试号信息(appid, appsecret) 16 | 17 | * 新增测试模版, 获取 template_id 18 | 19 | ``` 20 | exc_name: {{keyword1.DATA}} 21 | exc_value: {{keyword2.DATA}} 22 | filename: {{keyword3.DATA}} 23 | lineno: {{keyword4.DATA}} 24 | name: {{keyword5.DATA}} 25 | line: {{keyword6.DATA}} 26 | ``` 27 | 28 | * 获取关注者信息 openid 29 | 30 | * config 31 | 32 | ``` 33 | [base] 34 | port = 8000 35 | appid = 1 # 测试号信息 36 | appsecret = 2 # 测试号信息 37 | token_url = https://api.weixin.qq.com/cgi-bin/token? 38 | maintainer = ['a', 'b'] # 要推送人员的openid,可从关注者选 39 | [template] 40 | id = 1 # 模版id 41 | send = https://api.weixin.qq.com/cgi-bin/message/template/send?access_token= 42 | url = https://github.com/iakisey # 在微信上点击模版消息时所跳转的URL 43 | ``` 44 | 45 | * 效果图 46 | 47 |  48 | 49 | * 代码片段 50 | 51 | ``` 52 | @update_token 53 | def send_msg(openid, url, a, b, c, d, e, f): 54 | data = json.dumps({ 55 | 'touser': openid, 56 | 'template_id': config['template']['exception_id'], 57 | 'url': url, 58 | 'data': { 59 | 'keyword1': { 60 | 'value': a, 61 | 'color': '#173177' 62 | }, 63 | 'keyword2': { 64 | 'value': b, 65 | 'color': '#173177' 66 | }, 67 | 'keyword3': { 68 | 'value': c, 69 | 'color': '#173177' 70 | }, 71 | 'keyword4': { 72 | 'value': d, 73 | 'color': '#173177' 74 | }, 75 | 'keyword5': { 76 | 'value': e, 77 | 'color': '#173177' 78 | }, 79 | 'keyword6': { 80 | 'value': f, 81 | 'color': '#173177' 82 | }, 83 | } 84 | }).encode() 85 | url = config['template']['send'] 86 | url += config['base']['access_token'] 87 | with urllib.request.urlopen(url, data) as f: 88 | print('send_msg result: {}'.format(f.read().decode())) 89 | 90 | def output_wechat(): 91 | from . import config 92 | exc_type, exc_value, exc_tb = sys.exc_info() 93 | exc_type_msg = exc_type.__name__ if exc_type else exc_type 94 | exc_tbs = sorted( 95 | [e for e in traceback.extract_tb(exc_tb)], 96 | key=lambda e: len(e.filename)) 97 | exc_tb = exc_tbs[0] if exc_tbs else None 98 | exc_tb = exc_tb if exc_tb else None 99 | for user in eval(config['base']['maintainer']): 100 | send_msg( 101 | user, 102 | config['template']['url'], 103 | exc_type_msg, 104 | str(exc_value) if exc_value else None, 105 | *exc_tb 106 | ) if exc_type_msg or exc_value or exc_tb else None 107 | ``` 108 | -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- 1 | from configparser import ConfigParser 2 | 3 | from tornado.ioloop import IOLoop 4 | from tornado.httpserver import HTTPServer 5 | from tornado.options import parse_command_line 6 | from tornado.web import Application, RequestHandler 7 | 8 | 9 | class Application(Application): 10 | def __init__(self): 11 | settings = dict( 12 | autoreload=True, 13 | default_handler_class=BaseHandler 14 | ) 15 | super().__init__(None, **settings) 16 | 17 | def route(self, url): 18 | def register(handler): 19 | self.add_handlers('.*$', [(url, handler)]) 20 | return handler 21 | return register 22 | 23 | 24 | class BaseHandler(RequestHandler): 25 | def write_error(self, status_code, **kwargs): 26 | output_wechat() if 'exc_info' in kwargs and \ 27 | not str(status_code).startswith('4') else None 28 | self.finish('