笔者平常负责小组下午茶的组织(部门的小福利),每次购买点心后,需要先垫付费用并记录下来,等到季度末的时候再汇总给接口人统一报销。两个季度下来,总感觉一些地方需要改进:
作为一个搞自动化出生的技术控,面对这些问题简直不能忍,在合并完上个季度的报销费用后,我问自己:为什么不做个工具,既能解决问题,又能取悦自己呢?
对于小工具而言,主要需求如下:
自动备份和自动统计都比较容易,只要数据能录入到后台,这些都不是事儿;而要能随手记录,那就必须是移动端的应用(毕竟手机大家基本都是随身带,下单也多是用手机),且安卓和苹果都支持。经过这么一分析,做个【微信公众号】就是一个合适的轻量级解决方案啦 ^_^
当然,要作为部门记账报销用的工具,还得加一些小需求:
按照角色进行划分如下:
说明:使用数据库云服务来提升数据安全性是最直接有效的,考虑到尽量减少小工具的成本,这里选用了COS存储服务(COS每月有50G存储、10G流量的免费额度,完全满足需要)
完成了需求分析和系统设计,实现起来就是水到渠成了。
在‘云产品’中选择‘云服务器’,进入云主机即可按提示创建CVM云服务器
fdisk -l
fdisk /dev/vdb -- 这里有一系列交互式命令
mkfs.ext3 /dev/vdb1
mount /dev/vdb1 /data
cat /etc/fstab
echo '/dev/vdb1 /data ext3 defaults 0 0' >> /etc/fstab
yum install nginx
yum install uwsgi
yum install uwsgi-plugin-python
yum install python-devel
yum install MySQL-python
yum install memcached
yum install Python-memcached
上传安装包到云服务器,推荐使用FileZilla(上传下载都可以),windows和mac都支持,界面友好,非常方便。
网上的资料虽然不少,但实际配置起来难免踩坑,这里给出笔者的配置流程,供参考
本步骤主要是创建一个初始django项目,用于调试nginx+uwsgi+django,下午茶app的逻辑实现放在后面完成
// 此命令需要完成django安装后才能使用
django-admin.py startproject wx_website
/*
项目的目录结构如下,其中
apps目录存放下午茶对应的app工程
conf目录存放项目相关的配置文件
lib目录存放公共库
media、static、templates目录存放资源和模版文件
op目录存放网站操作脚本
*/
ll wx_website
drwxr-xr-x 6 root root 4096 Jan 16 22:20 apps
drwxr-xr-x 2 root root 4096 Feb 23 21:13 conf
drwxr-xr-x 2 root root 4096 Jan 2 02:05 lib
-rwxr-xr-x 1 root root 253 Nov 19 14:15 manage.py
-rw-r--r-- 1 root root 418 Nov 26 08:20 manage.pyc
drwxr-xr-x 2 root root 4096 Nov 15 22:26 media
drwxr-xr-x 2 root root 4096 Nov 16 08:06 op
drwxr-xr-x 6 root root 4096 Feb 10 17:05 static
drwxr-xr-x 3 root root 4096 Dec 25 15:34 templates
drwxr-xr-x 2 root root 4096 Mar 4 10:14 wx_website
// 调试模式启动,监听分配的公网ip 14.249.22.158上的8000端口
python manage.py runserver 14.249.22.158:8000
访问http://14.249.22.158:8000 看到Django欢迎页面,说明此步骤成功
IP‘14.249.22.158
为示例,请替换为CVM的公网IP
网上有很多例子是先配置uwsgi+django,再配置nginx+uwsgi,实际操作时很容易埋坑;这里直接给出完整的nginx+uwsgi配置,一次搞定
* 在conf目录下创建以下3个文件,分别为uwsgi和nginx的配置文件
ll conf
-rw-r--r-- 1 root root 253 Nov 19 14:15 uwsgi.ini
-rw-r--r-- 1 root root 664 Nov 19 14:15 uwsgi_params
-rw-r--r-- 1 root root 1157 Feb 23 21:13 wx_website_nginx.conf
uwsgi_params
:
uwsgi_param QUERY_STRING $query_string;
uwsgi_param REQUEST_METHOD $request_method;
uwsgi_param CONTENT_TYPE $content_type;
uwsgi_param CONTENT_LENGTH $content_length;
uwsgi_param REQUEST_URI $request_uri;
uwsgi_param PATH_INFO $document_uri;
uwsgi_param DOCUMENT_ROOT $document_root;
uwsgi_param SERVER_PROTOCOL $server_protocol;
uwsgi_param REQUEST_SCHEME $scheme;
uwsgi_param HTTPS $https if_not_empty;
uwsgi_param REMOTE_ADDR $remote_addr;
uwsgi_param REMOTE_PORT $remote_port;
uwsgi_param SERVER_PORT $server_port;
uwsgi_param SERVER_NAME $server_name;
uwsgi.ini
:
[uwsgi]
socket = /var/run/uwsgi/uwsgi.sock
master = true
# 步骤1中创建的wx_website项目的绝对路径
pythonpath = /data/website/wx_website
chdir = /data/website/wx_website
module = wx_website.wsgi
processes = 4
# 日志所在的目录一定要先创建好
daemonize = /var/log/uwsgi/uwsgi.log
plugins = python
wx_website_nginx.conf
:
worker_processes 4;
# 日志和pid所在的目录一定要先创建好
error_log /var/log/nginx/error.log;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] $status '
'"$request" $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
# 日志目录要先创建好
access_log /var/log/nginx/access.log main;
sendfile on;
upstream django {
# 对应uwsgi.ini中配置的socket文件路径
server unix:///var/run/uwsgi/uwsgi.sock;
# server 127.0.0.1:8000;
}
server {
listen 80;
server_name 14.249.22.158;
charset utf-8;
client_max_body_size 10M;
location /media {
alias /data/website/wx_website/media;
}
location /static {
alias /data/website/wx_website/static;
}
location / {
uwsgi_pass django;
include /data/website/wx_website/conf/uwsgi_params;
}
}
}
* 启动nginx
# step1:验证配置文件是否正确
nginx -t -c /data/website/wx_website/conf/wx_website_nginx.conf
# step2:步骤1显示successful后,启动nginx
nginx -t -c /data/website/wx_website/conf/wx_website_nginx.conf
# 后续修改nginx配置并验证成功后,用此命令使新配置生效
nginx -s reload
uwsgiserver.sh
文件如下:#!/bin/bash
# 这里为上文中uwsgi.ini的全路径
uwsgi_ini_path=/data/website/wx_website/conf/uwsgi.ini
if [ ! -n "$1" ]
then
echo "Usages: sh uwsgiserver.sh [start|stop|restart]"
exit 0
fi
if [ $1 = start ]
then
psid=`ps aux | grep "uwsgi" | grep -v "grep" | wc -l`
echo "psid:"$psid
if [ $psid -gt 4 ]
then
echo "uwsgi is running!"
exit 0
else
uwsgi $uwsgi_ini_path
echo "Start uwsgi service [OK]"
fi
elif [ $1 = stop ];then
killall -9 uwsgi
echo "Stop uwsgi service [OK]"
elif [ $1 = restart ];then
killall -9 uwsgi
uwsgi $uwsgi_ini_path
echo "Restart uwsgi service [OK]"
else
echo "Usages: sh uwsgiserver.sh [start|stop|restart]"
fi
这个sh脚本用于uwsgi的启停,后续开发过程中,它的使用频率会非常高
# 启动uwsgi
sh uwsgiserver.sh start
# 重启uwsgi
sh uwsgiserver.sh restart
最后,访问http://14.249.22.158/ 出现django欢迎页面,说明nginx+uwsgi+django配置成功
Tips:如果到最后一步,没有出现django欢迎页面,可以查看以下几个日志文件定位问题
/var/log/nginx/access.log
/var/log/nginx/error.log
/var/log/uwsgi/uwsgi.log
下午茶消费如何记录和报销等逻辑(下图灰色部分),不具备普遍参考性,就不详细介绍了,这里主要介绍微信公众号交互相关的内容。
python manage.py startapp happytea
wx_website/apps/
目录下:mv happytea/ apps/
ll /data/website/wx_website/apps/happytea
-rw-rw-r-- 1 root root 0 Jan 16 22:19 __init__.py
-rw-rw-r-- 1 root root 838 Feb 17 00:09 admin.py
-rw-rw-r-- 1 root root 1762 Feb 15 21:47 models.py
-rw-r--r-- 1 root root 162 Feb 12 13:25 urls.py
-rw-rw-r-- 1 root root 29199 Mar 24 22:10 views.py
wx_website/wx_website/settings.py
中添加appINSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django_crontab',
# 添加新创建的app
'apps.happytea'
)
wx_website/wx_website/urls.py
urlpatterns = [
url(r'^$', views.web_root_index),
url(r'^admin/', include(admin.site.urls)),
# 将/happytea/路径下请求路由到app的url定义文件中
url(r'^happytea/', include('apps.happytea.urls', namespace="happytea")),
]
wx_website/apps/happytea/urls.py
urlpatterns = [
# 将/happytea/wxmp/路径下的消息路由到 views中wxmp函数处理
url(r'^wxmp/$', views.wxmp, name='wxmp'),
]
微信公众平台相关的逻辑,主要有3个方面的内容(上图红框部分):
1. 公众号token的维护与更新
2. 验证消息是否鉴权通过
3. 微信xml消息解析与封装
通过微信公众平台的 开发文档 可以理解这些概念和协议,从而实现对应的处理逻辑。 不过从开发效率上看,如果引入开源的wechat_sdk库,可以将我们从这些非主干业务的开发工作中解放出来,节省大量的开发工作;而且开源库经过大量实际项目的运行,可靠性也非常高。
这里给大家推荐wechat-python-sdk,文档清晰易懂,接口调用简单,按照示例能快速上手。以本项目为例:
定义用于存储/获取的“token”和“时间戳”的函数
# 保存新的token和过期时间,覆盖原来保存的
def set_wx_token_func(newtoken, expires_at):
# 直接使用django的模型保存
token.update_token(newtoken, expires_at)
#
# 获取当前保存的token和过期时间(之前通过set_wx_token_func中保存的)
def get_wx_token_func():
last_token = token.get_token_by_wxid(wx_id)
return (str(last_token.token), last_token.expires_at)
# 公众号的配置信息
# access_token_getfunc和access_token_setfunc为前一步定义的两个函数
conf = WechatConf(token=apptoken, appid=appid, appsecret=appsecret,
encrypt_mode='normal', encoding_aes_key=appaeskey,
access_token_getfunc=get_wx_token_func,
access_token_setfunc=set_wx_token_func,
access_token_refreshfunc=None)
wechat = WechatBasic(conf=conf)
# 1,解析3个请求鉴权参数
signature = request.GET.get('signature')
timestamp = request.GET.get('timestamp')
nonce = request.GET.get('nonce')
# 2、通过sdk提供的check_signature接口验证消息是否鉴权通过
if not wechat.check_signature(signature, timestamp, nonce):
logger.info('check_signature fail')
return HttpResponseForbidden()
# 3、如果是http-get方法,说明是平台配置验证,返回echostr参数的值即可;
# 如果是http-post,则说明微信公众平台转发的用户命令消息,进一步处理
if request.method == 'GET':
echostr = request.GET.get('echostr')
logger.info('wx reg succ')
return HttpResponse(echostr)
elif request.method == 'POST':
return handler_wxmp_req(request.body)
* 解析微信转发的xml请求,提取发送用户id、消息类型、消息内容
# 1、解析xml消息
try:
wechat.parse_data(body)
except ParseError:
logger.error('parse wx_msg fail, msg: ' + body)
return HttpResponseBadRequest()
# 2、得到发送消息的用户id、消息类型、消息内容
userid = wechat.message.source # 用户id
msg = wechat.message
if isinstance(msg, TextMessage): # 消息类型
txt = wechat.message.content # 消息文本
* 将要回复的文本消息封装为微信公众号xml响应
# 解析命令文本,判断格式是否正确
(ret, expinfo) = parse_add_expense(txt)
if not ret:
content = '您输入的格式有误'
# 将返回的文本内容(content)封装为xml响应文本
# response_text方法返回的是一个xml文本
# 将此文本作为http-body返回给微信公众平台即可
return wechat.response_text(content=content)
按照上面的方法,就可以很方便的完成与微信公众平台相关的逻辑处理了。
django-app
的开发,可以参考官方文档,非常全面,这也是选择django框架的优点之一。这里仅仅截取一段简单‘用户状态查询’逻辑,便于理解下文的公众号操作示例
# 得到用户发送的文本(已完成微信xml请求的解析)
txt = wechat.message.content.strip()
# 根据请求中的用户id查询用户信息
user = User.get_user_by_openid(wechat.message.source)
# 如果命令文本是‘2’,说明是要查询用户状态
if txt == '2':
# 得到用户的状态信息文本
content = gen_user_info(user)
# 将要返回给用户的文本结果封装为xml响应文本
rspxml = wechat.response_text(content=content)
# 将xml响应文本作为http响应的body返回给公众平台
return HttpResponse(rspxml)
到这里,我们已经完成了服务器环境的搭建和公众号后台服务的开发。接下来就可以配置公众号,让搭建的后台服务来处理用户发送的命令了。
wechat-python-sdk
的WechatConf时传入的token参数相同http-get
请求。按照4.3中的处理逻辑,如果校验成功并返回了echostr,则公众号配置成功,后续用户在公众号中发送的消息,都会转发给我们的后台服务处理。TIPS:如果提交公众号的基础配置未成功 或 发送命令后未返回结果,请检查django逻辑处理的日志来定位问题
推荐pip方式
pip install qcloud_cos_v4
cos-sdk
的时候需要用到
# 导入cos-sdk
from qcloud_cos import CosClient
from qcloud_cos import UploadFileRequest
# 通过appid、secretid、secretkey,cos-region(bucket的地域)
# 创建一个CosClient实例
os_client = CosClient(settings.COS_APPID, settings.COS_SECRET_ID,
settings.COS_SECRET_KEY, settings.COS_REGION)
# 创建一个上传文件请求,参数为:bucket名称、路径、文件路径
# 注意参数的类型为unicode,不是string
cos_upload_req = UploadFileRequest(
u'happytea', u'/' + filename.decode('utf8'), file_path.decode('utf8'))
# 设置上传时如果存在同名同路径文件,是否允许覆盖
cos_upload_req.set_insert_only(0)
# 通过client完成上传,并得到上传响应对象
cos_upload_rsp = cos_client.upload_file(cos_upload_req)
# 判断响应(json对象)的‘code’值是否等于0
# 0:上传cos成功,非0:失败
if cos_upload_rsp['code'] != 0:
raise Exception('cos upload fail, info:{}'.format(cos_upload_rsp['message']))
这样,当cvm服务器出现我们监控的问题时,就可以通过短信、邮件马上得到通知了。
到这里,基于云服务的公众号开发就完成了,使用时效果如下
回想几年前,有朋友开发一个小型的业务系统,却因服务器购买、托管、网络等问题耗费了大量时间和精力,等系统开发完成,已错过了最佳的上线时间,实在让人唏嘘。
如今,无论是服务器、网络、云存储还是CDN,云服务的生态已经非常成熟,门槛低且成本小,我们何不放飞自己的梦想,在云中世界里尽情的飞翔呢!
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。