财务报表初版总结


财务报表项目v1版本已上线,在此总结下。




项目简介


财务报表是财务对于当天的支出收入信息进行汇总给财务总监审核,审核通过后给 Boss 看的一个报表。

项目前端分为 pc 端和 手机微信端,微信端是接入到企业号的应用,对于后端而言,没有多大区别。

根据用户不同分的权限也不同:
财务员工:录入,提交审核。
财务总监:审核,审核通过 or 驳回重新录入。
Boss:查看(财务总监审核通过的)报表。

项目工作

项目采用 Django + SQLAlchemy 数据库用的 MySQL

本来这样的一个小项目,可以用更轻量级的 Flask 或者刚好练练手 sanic 的,但是当时比较急,就用了比较快捷及比较熟悉些的 Django。现在看来选型并不是很成功,尤其是其间 SQLAlchemy 遇到了一些坑。但是Django的确更省事。如果有时间可以考虑重构一下。

相关:
对数据进行加密存库
接口采用 RESTful API风格 及 token 认证

部署:
supervisor + gunicorn + nginx

日志:
logbook

实现方式

认证管理

用户首次登录会以 cookie (微信端) 或者 json (pc 端) 形式返回用户 token ,用户带着 token 请求接口,如 token 过期或者认证失败将会返回401。

权限管理

不同身份有不同的权限, 在 user 表中添加一个身份字段,不同值代表不同身份。

wechat 网页授权登录

企业微信开发者文档

一个标准的OAuth2认证模式。

因为微信端有身份判断,所以需要获取到当前登录的用户信息。
根据开发者文档,除了需要传一些必要的字段之外,需要传一个 redirect url 也就是 授权成功后的跳转页面 和 state 字段(自定义值)。 微信授权后直接带着code 字段及传入的 state 值 (拼接在 url 中返回)去请求 redirect url,而要获取用户信息的话需要这个 code 字段和 access token 。 也就是说我们需要拿到这个 code。

这里我们跟前端的处理方式是这样的:
1> 前端传一个redirect url 到后端wechat-login 接口
2> 后端记录 redirect url 并为其生成一个 uuid 作为对应关系保存于数据库。
3> 后端将用 uuid 和一个新的redirect url (即后端的wechat-auth 接口)拼接为 wechat 的认证url。
4> wechat 返回新的带有 state 和 code 的 url 请求wechat-auth 接口,在 wechat-auth 接口中获取 code 值及 uuid 对应的原前端要重定向的 url,根据 code 获取用户信息,放到 cookie 中重定向到前端要重定向的 url。也就是说,最后在后端完成重定向,重定向到前端想要去的页面,重定向到的页面可以从 cookie 中获得后端设置的用户信息。

ps: Django 中设置 cookie 及重定向

1
2
3
4
5
from django.http import HttpResponseRedirect
res = HttpResponseRedirect(res_url)
res.set_cookie("COOKIE_TOKEN_KEY", "xxx")
res.set_cookie("COOKIE_USER_KEY","xxxx")
return res

加密

MySQL 有一套加密方式,示例:

1
2
3
insert into users(test) values(AES_ENCRYPT('teststr','salt'));  

select AES_DECRYPT(test,'salt') from users

相对应的 SQLAlchemy 也可以加密,而且加密后为二进制需要转换为十六进制存储
使用 SQLAlchemy 时用 func 然后使用相应的函数就可以了。
16进制转换: func.hex func.unhex
加解密:
func.aes_encrypt(value, key)
func.aes_decrypt(value, key)

SQLAlchemy 遇到的问题

SQLAlchemy 遇到了很多坑,因为之前只是用 SQLAlchemy 写一些脚本,真正用到 web 项目很少, 这次又是强行放到 Django 上,所以因为不够熟悉所以出了一些问题。

主要问题:

1.问题描述:

一个接口报错后,全部接口都不能用了,主要是因为一个接口报错后,它就停留在报错的事务,没有进行手动回滚,所以导致其它的接口都没办法工作了。

解决方案:
try catch 到异常后 进行 rollback 。这样解决了一个报错其他都不能用的问题,但是弊端是无法显式的看到报错,只能在日志中查看报错信息。

2.问题描述
每天早晨来到公司后发现 连接断开了,造成这种情况的原因可能是:
1> 数据库重启了,导致连接池断开。因为在测试环境所以不太稳定。
2> 连接池连接的空闲时间超过(配置时间),断开了。

总之就是因为连接池断开后没有自动重连导致的,从网上搜了下解决方案,暂时解决了问题。

解决方案:

禁用SQLAlchemy提供的数据库连接池,只需要在调用create_engine是指定连接池为NullPool,
SQLAlchemy就会在执行session.close()后立刻断开数据库连接

create_engine()函数和连接池相关的参数有:
-pool_recycle, 默认为-1, 推荐设置为7200, 即如果connection空闲了7200秒, 自动重新获取, 以防止connection被db server关闭.
-pool_size=5, 连接数大小,默认为5,正式环境该数值太小,需根据实际情况调大
-max_overflow=10, 超出pool_size后可允许的最大连接数,默认为10, 这10个连接在使用过后, 不放在pool中, 而是被真正关闭的.
-pool_timeout=30, 获取连接的超时阈值, 默认为30秒

参考: 这位小兄弟的博客中的这篇连接池的介绍

连接池的问题,感觉自己理解的还是不够到位,还需要进一步学习。
暂时就这些,如果有需要补充的再次补充。