zoukankan      html  css  js  c++  java
  • python3 + Django + Mysql + Vue + Element-UI 学习笔记:从0搭建前后端分离的测试工具平台

    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>
    View Code

    (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,至此就完成了,看下效果:

    陈月白 http://www.cnblogs.com/chenyuebai
  • 相关阅读:
    【数据结构】算法 Tree Sub Structure 树的子结构
    【数据结构】算法 Binary Search Tree find Kth largest Node 二叉搜索树的第k大节点
    【数据结构】算法 Count Complete Tree Nodes 完全二叉树的节点个数
    【数据结构】算法 合并K个有序链表Merge k Sorted Lists
    JVisualVM 插件安装中出现网络问题
    [ISSUE] Logback TimeBasedRollingPolicy not rolling every day.
    【数据结构】算法 计算数组中的逆序对
    JQ为元素添加class
    时间戳转换成日期
    JS拖拽
  • 原文地址:https://www.cnblogs.com/chenyuebai/p/12397281.html
Copyright © 2011-2022 走看看