2020.03.23 INIT
中间换了工作,好久没写了
2021.05.08 新增:页面预览、功能实现样例(含后端接口、前端页面、配置绑定)
2021.05.13 新增:首页增加访问人数统计功能
2021.05.19 新增:用户信息查询接口、页面开发
2021.06.10 新增:生成图片功能
2021.06.18 新增:文本处理工具下载页面
2021.06.30 优化:静态图片、生成图片的备注支持中文
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
前言
1、概述
此前一直想把测试工作过程中的一些通用的工具、方法、FAQ,集成为一个通用的测试工具。
也尝试过用python的Tkinter做的GUI(有兴趣的话,见: https://www.cnblogs.com/chenyuebai/p/7150382.html )
虽然能用,但缺点也很明显:不美观、扩展性差、需要打包下载。
因此,想搭个Web-Browser、前后端分离的测试平台,把测试的一些能力集成上去,顺便学习下前后端开发知识。
功能范围大概包含:
(1)测试数据构造:日常测试、配合联调,需要多次重复的构造测试数据(接口、落库表、redis、收发kafka消息...);针对高频操作沉淀出TOP场景,落到测试平台中。供组内同学,或者诉求方使用
(2)常用查询:直接查询库表,一是查询环境需要频繁切换,二是很多字段都是枚举值,需要二次查询,效率低;在这里把一些通用的查询操作固化下来,代码中将枚举值转义,易于理解
(3)常用链接地图:支持区分环境,含公司通用的研发/测试平台地址、小工具地址、FAQ等
(4)接口MOCK管理
2、项目结构
(1)后端:python3 + django + mysql
(2)前端:Vue + Element-UI + vue-template + Js
3、放几张预览:
(1)首页及菜单
(2)测试数据构造:
(3)小工具集:
(4)常用链接:
进入正题。
一、环境准备
1、Python 3.6.1
2、Django 2.2.6 pip安装即可
3、mysql 5.7.1 + Connector/J 5.1.48
(1)安装:mysql安装时需记录用户名、密码
(2)配置时间、字符集:
sql cmd: set global time_zone = '+8:00'; #修改mysql全局时区为北京时间,即我们所在的东8区 set time_zone = '+8:00'; #修改当前会话时区 flush privileges; #立即生效
installPathmy.ini:
在[client]节点下添加 default-character-set=utf8
在[mysqld]节点下添加 (注:collation是排序方式) character-set-server=utf8 collation-server=utf8_general_ci
restart
SHOW VARIABLES LIKE 'char%';
(3)本地启动mysql服务:
CMD: net start mysql_service_ch
(4)数据库迁移
manage.py makemigrations
manage.py migrate
(5)创建超级用户
manage.py createsuperuser
(6)设置后台管理界面为中文
setting.py中: LANGUAGE_CODE = 'zh-hans' TIME_ZONE = 'Asia/Shanghai'
前端开发环境搭建,参考:
https://www.cnblogs.com/goldlong/p/8027997.html
二、创建项目、应用
1、创建项目
django-admin.py startproject MySite运行服务:manage.py runserver 本地访问:http://127.0.0.1:8000/
ps:若希望外部机器(同一网络内)访问本机的Django服务 可:manage.py runserver 0.0.0.0:8080
并将setting.py中,ALLOWED_HOSTS = ['*', ]
2、创建应用
manage.py startapp Post
3、目录说明
4、配置数据库信息
由于选择的是mysql,需要配置数据库信息
MySite/__init__.py:
import pymysql
pymysql.install_as_MySQLdb()
MySite/setting.py:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'mysite',
'USER':'root',
"PASSWORD":"passwd123456",
"HOST":"127.0.0.1",
"PORT":"3306"
}
}
至此,环境基本搭建完成;
三、功能实现
大概思路
--> 前端浏览器访问/点击,触发http请求
--> 后端 MySiteurls.pyurlpatterns中寻找路由信息 --> 调用views.py中的视图函数 --> 视图函数调用viewImpl中具体的业务代码 --> 数据库处理 --> 组装结果 --> 返回结果至前端
--> 前端页面渲染展示
以查询某权益当前可用天数功能为例(涉及具体业务,部分代码有删改):
1、后端接口开发
(1)接口业务逻辑实现:viewImpl.py
def get*****VaildDay(self,request): Logger.info("request.POST = %s" % request.POST) # 记录访问信息 utils.SysRecord().visitRecord(request) response = {} validDay = "" tableTemplate = "********" sqlTemplate = '''********''' # 解析、校验入参 userId = request.POST.get("userId") env = request.POST.get("env") strTools = WorkTools.StringFactory() if (strTools.isNull(userId) == False) or (strTools.isNull(env) == False): Logger.error("校验参数失败,存在入参为空") response["code"] = "1" response["message"] = "校验参数失败,存在入参为空;userId、env必填" return JsonResponse(data=response, json_dumps_params={'ensure_ascii': False}) # 获取分表 tableName = utils.TableEditor().getSubTableName(tableTemplate, userId, "1") # 获取当前时间 currentTime = WorkTools.SystemTools().get_current_time() # 组装并执行sql sql = sqlTemplate.replace("tableName_template", tableName).replace("userId_template", userId).replace( "currentTime_template", currentTime) Logger.info("组装sql完成,sql = %s" % sql) mysqlConnectInfo = sysConfig.MYSQL["equity_%s" % env] mysql = WorkTools.MysqlTools(mysqlConnectInfo["host"], mysqlConnectInfo["port"], mysqlConnectInfo["db"],mysqlConnectInfo["username"], mysqlConnectInfo["password"]) sqlConnect = mysql.connectMysqlDatabase() validCountDecimal = mysql.executeSqlLine(sqlConnect, sql)["data"] validCount = validCountDecimal[0][0] try: # if (validCount == None) or (validCount == "None"): validDay = 0 else: validCountStr = str(validCount.quantize(Decimal('0'))) validDay = int(validCountStr) / 100 response["code"] = "0" response["message"] = "success.查询【*****当前可用天数】成功" response["userId"] = userId response["validDay"] = validDay response["env"] = env Logger.info("get*****VaildDay success.userId = %s" % userId) except Exception as e: executeResult = str(e) + " " + traceback.format_exc() Logger.error("计算********权益当前可用天数失败: %s详细信息请见日志" % validCountDecimal) response["code"] = "1" response["message"] = str(e) + " " + traceback.format_exc() return JsonResponse(data=response, json_dumps_params={'ensure_ascii': False})
(2)view层配置:views.py
#司机优先抢货权益-查询司机优先抢货权益当前可用天数 @csrf_exempt def get*****VaildDay(request): return viewImpl.Equity().get*****VaildDay(request)
(3)url层路由配置:urls.py
urlpatterns = [ # 前端分离 url('home', TemplateView.as_view(template_name="index.html")), #权益 path("api/equity/get*****VaildDay", views.get*****VaildDay, name="get*****VaildDay"), ]
2、前端页面开发
前端页面以前用原生JS写过一些,也是面向百度编码的水平。。且写出来的页面不是很美观,不如找现成的轮子,又快又好看。
听前端同事安利了Vue+Element-UI,熟悉了两天,写写简单页面够用了
进入正题:
(1)静态页面开发:equity.vue 有删改,仅供参考
<template> <div class="equity-****"> <h4>查询测试数据:****当前可用天数</h4> <el-form-item label="选择环境"> <el-radio-group v-model="form.requestResource"> <el-radio label="dev" /> <el-radio label="qa" /> </el-radio-group> </el-form-item> <el-form-item label="userId"> <el-input v-model="form.requestUserId" style=" 25%;" type="number"/> </el-form-item> <el-form-item> <el-button type="primary" @click="get*****VaildDay()">查询</el-button> <el-button @click="onCancel">取消</el-button> </el-form-item> <el-form-item> <el-row> <el-table :data="validDay" style=" 40%" border> <el-table-column prop="userId" label="userId" min-width="60"> <template scope="scope"> {{ scope.row.userId }} </template> </el-table-column> <el-table-column prop="validDay" label="当前可用天数" min-width="40"> <template scope="scope"> {{ scope.row.validDay }} </template> </el-table-column> </el-table> </el-row> </el-form-item> </div> </template> <script> import { ge*******VaildDay } from '@/api/equity' export default { name: 'equity-*****', data() { return { form: { }, validDay:[] } }, methods: { onCancel() { this.$message({ message: 'cancel!', type: 'warning' }) }, ge*******VaildDay() { this.listLoading = true var params = new URLSearchParams();//要使用这种类型的数据 params.append('userId', this.form.requestUserId); params.append('env', this.form.requestResource); this.myparms=params; ge*******VaildDay(this.myparms).then(response => { console.log("ge*******VaildDay response = ",response) // for debug var msg = response.message // var detail = response this.showSuccess("提示",msg) //生成数组 var validDayInfo = new Object() validDayInfo.userId = response.userId validDayInfo.validDay = response.validDay var validDayInfoArray=new Array() validDayInfoArray[0] = validDayInfo this.validDay = validDayInfoArray }) }, showSuccess(title,msg) { this.$alert(msg, title, { confirmButtonText: '确定', callback: action => { // this.$message({ // type: 'info', // message: `action: ${ action }` // }); } }); } } } </script> <style scoped> .line{ text-align: center; } </style>
(2)调用后端接口:equity.js
import request from '@/utils/request' import { MessageBox, Message } from 'element-ui' export function ge*******VaildDay(data) { Message({ message: "请求已发出,请稍等" || 'Info', type: 'Info', // duration: 5 * 1000 }) return request({ url: '/equity/ge*******VaildDay', method: 'post', data }) }
请求是基于request.js发出的,模板中已封装好,修改一下相关的状态码校验即可,不再赘述。
前端开发完成后,cmd执行npm run build,如果代码OK,就能够看到所有的代码会被webpack自动打包到dist目录下了,至此前端开发完成;
3、前后端绑定:settings.py
(1)贴一些关键配置
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [ # 配置Django项目的模板搜索路径 os.path.join(BASE_DIR, r"vue-admindist"), ], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] # Add for vuejs 配置一下静态文件的搜索路径 STATICFILES_DIRS = [ os.path.join(BASE_DIR, r"vue-admindiststatic"), ]
4、启动服务、访问
(1)启动后端服务:manage.py runserver 0.0.0.0:8080
(2)访问: http://{本地IP}:8080/home 代码没有问题的话,就可以进入首页了;
(3)找到自己所属的菜单,点击即可使用上述开发的功能了
页面:
接口信息:
四、功能迭代
1、首页增加访问人数统计功能
(1)表设计
CREATE TABLE `visit_record` ( `id` int(11) NOT NULL AUTO_INCREMENT, `api` varchar(200) NOT NULL COMMENT '访问接口', `param` varchar(200) DEFAULT '' COMMENT '访问接口参数', `ip` varchar(100) DEFAULT 'default' COMMENT '访问者ip', `user_id` varchar(100) DEFAULT 'default' COMMENT '访问者id', `user_name` varchar(100) DEFAULT 'default' COMMENT '访问者名', `env` varchar(100) NOT NULL COMMENT '环境类型:dev,qa', `create_time` datetime NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1654 DEFAULT CHARSET=utf8;
(2)utils.py visitRecord实现:记录访问信息落表
class SysRecord(): # 记录系统访问次数,落库 def visitRecord(self,request): userId = "default" userName = "default" if not request.POST.urlencode(): param = "default" else: param = request.POST.urlencode() if not request.POST.get("env"): env = "default" else: env = request.POST.get("env") api = request.path ip = DjangoTools().getRequestIp(request) createTime = WorkTools.SystemTools().get_current_time() sqlTemplate = """INSERT INTO `mysite`.`visit_record` (`api`, `param`, `ip`, `user_id`, `user_name`, `env`, `create_time`) VALUES ( 'api_template', 'param_template', 'ip_template', 'user_id_template', 'user_name_template', 'env_template', 'create_time_template');""" # 组装并执行sql sql = sqlTemplate.replace("api_template",api).replace("param_template",param).replace("ip_template",ip).replace("user_id_template",userId).replace("user_name_template",userName).replace("env_template",env).replace("create_time_template",createTime) Logger.info("visitRecord 记录访问信息:组装sql完成,sql = %s" % sql) try: mysqlConnectInfo = sysConfig.MYSQL["mysql_own"] Logger.info("mysqlConnectInfo = %s" % mysqlConnectInfo) mysql = WorkTools.MysqlTools(mysqlConnectInfo["host"], mysqlConnectInfo["port"], mysqlConnectInfo["db"],mysqlConnectInfo["username"], mysqlConnectInfo["password"]) sqlConnect = mysql.connectMysqlDatabase() mysql.executeSqlLine(sqlConnect, sql) except Exception as e: ret = str(e) + " " + traceback.format_exc() Logger.error("visitRecord 记录访问信息失败,api=%s,param=%s,env=%s,ip=%s,userId=%s,userName=%s,createTime=%s" % (api, param, env, ip, userId, userName,createTime))
(3)页面&接口入口处增加统计:
viewImpl.py中,对页面或对外提供的接口,调用visitRecord()记录访问信息,如在查询用户信息接口中增加:
class Users(): def queryUserInfo(self,request): # 记录访问信息 utils.SysRecord().visitRecord(request) # 以下省略
(4)后端提供访问次数查询接口
viewImpl.py
class Dashboard(): def queryTotalVisitNum(self,request):# 记录访问信息 utils.SysRecord().visitRecord(request) # response = {} # 组装并执行sql sql = '''select count(*) from visit_record;''' mysqlConnectInfo = sysConfig.MYSQL["mysql_own"] mysql = WorkTools.MysqlTools(mysqlConnectInfo["host"], mysqlConnectInfo["port"], mysqlConnectInfo["db"],mysqlConnectInfo["username"], mysqlConnectInfo["password"]) sqlConnect = mysql.connectMysqlDatabase() totalVisitNum = mysql.executeSqlLine(sqlConnect, sql)["data"][0][0] try: # 组装结果返回 response["code"] = "0" response["message"] = "success.查询累计访问量完成" response["totalVisitNum"] = str(totalVisitNum) Logger.info("queryUserInfo success.id = %s" % id) except Exception as e: executeResult = str(e) + " " + traceback.format_exc() Logger.error("queryUserInfo:查查询累计访问量失败:%s"%executeResult) response["code"] = "1" response["message"] = executeResult return JsonResponse(data=response, json_dumps_params={'ensure_ascii': False})
views.py
# 首页dashboard #查询累计访问量 @csrf_exempt def queryTotalVisitNum(request): return viewImpl.Dashboard().queryTotalVisitNum(request)
urls.py
# 首页dashboard path("api/dashboard/queryTotalVisitNum", views.queryTotalVisitNum, name="queryTotalVisitNum"),
(3)前端页面开发
dashboard.js 中配置接口
import request from '@/utils/request' import { MessageBox, Message } from 'element-ui' export function queryTotalVisitNum(data) { return request({ url: '/dashboard/queryTotalVisitNum', method: 'post', data }) }
dashboardinedx.vue stye太多不贴了
<template> <el-row :gutter="40" class="panel-group"> <div class="dashboard-container"> <div class="dashboard-text">******测试</div> <div class="dashboard-text">你好: {{ name }}</div> </div> <el-col :xs="12" :sm="12" :lg="6" class="card-panel-col"> <div class="card-panel"> <div class="card-panel-icon-wrapper icon-people"> <svg-icon icon-class="user" class-name="card-panel-icon" /> </div> <div class="card-panel-description"> <div class="card-panel-text"> 累计访问 </div> <div class="dashboard-text">{{ totalVisitNum }}</div> </div> </div> </el-col> </el-row> </template> <script> import { mapGetters } from 'vuex' import { queryTotalVisitNum } from '@/api/dashboard' export default { name: 'Dashboard', computed: { ...mapGetters([ 'name' ]) }, data() { return { totalVisitNum:this.queryTotalVisitNum() } }, methods: { queryTotalVisitNum() { this.listLoading = true queryTotalVisitNum().then(response => { console.log("queryTotalVisitNum response = ",response) // for debug var msg = response.message var totalVisitNum = response.totalVisitNum this.totalVisitNum = totalVisitNum }) } } } </script> <style lang="scss" scoped></style>
OK,至此就完成了,看下效果: