一.运维平台之用户资源

参考 https://github.com/rfjer/autoAdmin/


创建数据库
CREATE DATABASE `devops` /*!40100 DEFAULT CHARACTER SET utf8 */;


安装工具   
pip install Django==2.2.1
pip install coreapi-cli
pip install djangorestframework
pip install django-apscheduler 
pip install tencentcloud-sdk-python
pip install django-filter
pip install requests -i https://pypi.douban.com/simple/ 
pip install pymysql
pip install django-rest-swagger
pip install django-cors-headers
pip install djangorestframework-jwt


创建项目 
django-admin startproject devops


创建用户app
python manage.py startapp users


在devops 目录下创建 apps 目录,并把 users 目录移动到apps目录下


settings.py 文件中添加
import sys 
sys.path.insert(0,os.path.join(BASE_DIR, "apps"))  # 注意此行一定要在BASE_DIR的后面,不然就会报错


在 ALLOWED_HOSTS 行的后面添加
AUTH_USER_MODEL = 'users.User'


INSTALLED_APPS 中添加
    'rest_framework',
    'rest_framework.authtoken',
    'users',
    'corsheaders',
    'django_apscheduler',
    'django_filters',

    
在 INSTALLED_APPS 模块的后面添加
CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_ALLOW_ALL = True
CORS_ORIGIN_WHITELIST = (
    'http://127.0.0.1:8000',
)


在 MIDDLEWARE 中注释掉下面这行 
#'django.middleware.csrf.CsrfViewMiddleware',


在 MIDDLEWARE 中 'django.contrib.sessions.middleware.SessionMiddleware', 下面添加一行
'corsheaders.middleware.CorsMiddleware',


修改数据库连接
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'devops',
        'USER': 'root',
        'PASSWORD': 'chengce243',
        'HOST': '127.0.0.1',
        'PORT': 3306,
        'OPTIONS':{
            'init_command':"SET sql_mode='STRICT_TRANS_TABLES'",
            'charset':'utf8mb4',
        },
    }
}


settings.py中加入DOMAIN,让自动生成邮件域名

DOMAIN = "@admin.com"


在最后添加如下
from rest_framework.settings import api_settings

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'devops.paginations.Pagination',
    'PAGE_SIZE': 10,
    'DEFAULT_FILTER_BACKENDS': (
        'django_filters.rest_framework.DjangoFilterBackend',
    ),
    'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
}

## qcloud
QCLOUD_SECRETID = "AKIDFaq5EbGxiQx22qzUL2XtCJJ7j9NQuyTq"
QCLOUD_SECRETKEY = "LTP8fz72lOr8D8reFerunUyGQN7f4LTP"


users/models.py
from django.db import models
# Create your models here.
from django.contrib.auth.models import AbstractUser, Group

class User(AbstractUser):
    name = models.CharField("姓名", max_length=32, null=True, help_text="姓名")
    phone = models.CharField("电话", max_length=11, null=True, help_text="手机号")

    class Meta:
        verbose_name = "用户"
        ordering = ["id"]
        db_table = 'auth_user'

    def __str__(self):
        return self.username
        

users/views.py
from django.contrib.auth import get_user_model
from rest_framework import viewsets, mixins, permissions
from rest_framework.response import Response
from .serializers import UserSerializer, UserRegSerializer

from .filters import UserFilter

User = get_user_model()

class UserRegViewset(mixins.CreateModelMixin,
                     mixins.UpdateModelMixin,
                     viewsets.GenericViewSet):
    """
    create:
    创建用户
    
    update:
    修改密码
    """
    queryset = User.objects.all()
    serializer_class = UserRegSerializer
 

class UsersViewset(viewsets.ModelViewSet):
    """
    retrieve:
    获取用户信息
    list:
    获取用户列表
    update:
    更新用户信息
    delete:
    删除用户
    """
    queryset = User.objects.all()
    serializer_class = UserSerializer
    filter_class = UserFilter
    filter_fields = ("username",)
    extra_perms_map = {
        "GET": ["users.show_user_list"]
    }

    def get_queryset(self):
        queryset = super(UsersViewset, self).get_queryset()
        queryset = queryset.order_by("id")
        queryset = queryset.exclude(is_superuser=True)
        return queryset
    
    
users/serializers.py
from rest_framework import serializers
from django.contrib.auth.models import Group
from django.contrib.auth import get_user_model
from django.conf import settings
User = get_user_model()

#控制具体动作有哪些
class UserSerializer(serializers.ModelSerializer):
    """
    用户序列化类
    """
    #控制的你是返回的格式:
    last_login  = serializers.DateTimeField(format="%Y-%m-%d %H:%M:%S",
                                            label="上次登录时间",
                                            help_text="上次登录时间")

    # 控制往前端返回哪些字段:
    class Meta:
        model = User
        fields = ("id", "username", "name", "phone", "email", "is_active", "last_login")
    #想修改哪些字段可在此方法中控制:
    def update(self, instance, validated_data):
        instance.phone = validated_data.get("phone", instance.phone)
        instance.is_active = validated_data.get("is_active", instance.is_active)
        instance.save()
        return instance


class UserRegSerializer(serializers.ModelSerializer):
    """
    用户注册序列化类
    """
    #把密码处理成密文
    password = serializers.CharField(style={"input_type": "password"},
                                     label="密码",
                                     write_only=True,
                                     help_text="密码")
   #验证
    def validate(self, attrs):
        attrs["is_active"] = False #默认创建完的用户不可直接登录
        #给用户自动加email地址
        attrs["email"] = "{}{}".format(attrs['username'], settings.DOMAIN)
        return attrs
    #此create可不要,但不要的结果是密码直接存进去了
    def create(self, validated_data):
        instance = super(UserRegSerializer, self).create(validated_data=validated_data)
        instance.set_password(validated_data["password"])
        instance.save()
        return instance

    def update(self, instance, validated_data):
        password =  validated_data.get("password", None)
        if password:
            instance.set_password(password)
            instance.save()
        return instance
    class Meta:
        model = User
        #序列化时返回的数据
        fields = ("username", "password", "name", "id", "phone")

        
users/router.py
from rest_framework.routers import DefaultRouter
from .views import UsersViewset, UserRegViewset

user_router = DefaultRouter()
user_router.register(r'userreg', UserRegViewset) 
user_router.register(r'users', UsersViewset) 


users/urls.py
from django.conf.urls import url, include
from rest_framework.routers import DefaultRouter
from rest_framework.documentation import include_docs_urls
from users.router import router as user_router
# from resources.router import router as resources_router
router = DefaultRouter()
router.registry.extend(user_router.registry)
# router.registry.extend(resources_router.registry)


from rest_framework.authtoken.views import obtain_auth_token
urlpatterns = [
    url(r'^api/', include(router.urls)),
    url(r'^api-auth/', include('rest_framework.urls')),
    url(r'^docs/', include_docs_urls("51reboot接口文档")),
    url(r'^api-token-auth/', obtain_auth_token)
]


users/filters.py
import django_filters

from django.contrib.auth import get_user_model
from django.db.models import Q

User = get_user_model()

class UserFilter(django_filters.rest_framework.FilterSet):
    """
    用户过滤类
    """
    username = django_filters.CharFilter(method='search_username')

    def search_username(self, queryset, name, value):
        return queryset.filter(Q(name__icontains=value)|Q(username__icontains=value))

    class Meta:
        model = User
        fields = ['username']

        
devops/urls.py
from django.conf.urls import url, include
from rest_framework.routers import DefaultRouter
from rest_framework.documentation import include_docs_urls
from rest_framework_jwt.views import obtain_jwt_token

router = DefaultRouter()

from users.router import user_router

router.registry.extend(user_router.registry)

urlpatterns = [
    url(r'^api/', include(router.urls)),
    url(r'^', include(router.urls)),
    url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
    url(r'^api-token-auth/', obtain_jwt_token),
    url(r'^docs/', include_docs_urls("开源运维平台"))
]
        

说明:        
上述中是以api开头的全部交给router.urls去处理,
因为到时我前端和后端可能放在一台服务器上,然后调动的时候只要不是api开头的全部交给nginx去处理,
以api开头则交给django去处理。
        
devops/paginations.py    
from rest_framework.pagination import PageNumberPagination

class Pagination(PageNumberPagination):
    def get_page_size(self, request):
        try:
            page_size = int(request.query_params.get("page_size", -1))
            if page_size >= 0:
                return page_size
        except:
            pass
        return self.page_size

        
在 users下的 的 __init__.py  文件新增内容     
import pymysql
pymysql.version_info = (1, 4, 6, 'final', 0)  # change mysqlclient version
pymysql.install_as_MySQLdb()


数据库同步  
python manage.py makemigrations
python manage.py migrate


当执行 python manage.py makemigrations 的时候,报错如下:
File "C:\python\lib\site-packages\django\db\backends\mysql\operations.py", line 146, in last_executed_query
  query = query.decode(errors='replace')
AttributeError: 'str' object has no attribute 'decode'

解决办法:
C:\python\lib\site-packages\django\db\backends\mysql\operations.py 文件146行
将 decode 改为 encode 

    def last_executed_query(self, cursor, sql, params):
        # With MySQLdb, cursor objects have an (undocumented) "_executed"
        # attribute where the exact query sent to the database is saved.
        # See MySQLdb/cursors.py in the source distribution.
        query = getattr(cursor, '_executed', None)
        if query is not None:
            query = query.encode(errors='replace')
        return query
        

创建超级管理员    
python manage.py createsuperuser


创建测试用户的数据
python manage.py shell           
                               
from django.contrib.auth import get_user_model                                               
User = get_user_model()                                                                      
def create_user(name): 
    for i in range(1,20): 
        username = "{}-{}".format(name,i) 
        User.objects.create_user(username, "{}@admin.com".format(username),"12345678") 
        
        
create_user("rock")
create_user("panda")                                                                         
create_user("wd") 

清空数据(不想要时可用此命令清空数据)
User.objects.all().delete()    


启动项目
python manage.py runserver 0.0.0.0:8000


vue环境搭建:

参考 https://www.cnblogs.com/dbslinux/p/13180500.html


下载并安装git:  https://www.git-scm.com/download/win
            
      
vue开发环境搭建 
下载二进制包   https://nodejs.org/zh-cn/


在cmd中如下显示说明安装成功
C:\Users\EDUTECH> node -v
v14.15.4


新建 web 目录,devops文件夹是后端代码,和web目录是同级

到 web目录 目录下 
git clone https://github.com/gxhsh/vueAdmin-template.git


安装nodejs:  
D:\ops\web\vue-admin-template> npm install --registry=https://registry.npm.taobao.org


运行
D:\ops\web\vue-admin-template> npm run dev 
 

取消权限 src/permission.js 
import router from './router'
// import store from './store'
import NProgress from 'nprogress' // Progress 进度条
import 'nprogress/nprogress.css'// Progress 进度条样式
// import { Message } from 'element-ui'
// import { getToken } from '@/utils/auth' // 验权

// const whiteList = ['/login'] // 不重定向白名单
router.beforeEach((to, from, next) => {
  NProgress.start()
  next()
  // if (getToken()) {
  //   if (to.path === '/login') {
  //     next({ path: '/' })
  //   } else {
  //     if (store.getters.roles.length === 0) {
  //       store.dispatch('GetInfo').then(res => { // 拉取用户信息
  //         next()
  //       }).catch(() => {
  //         store.dispatch('FedLogOut').then(() => {
  //           Message.error('验证失败,请重新登录')
  //           next({ path: '/login' })
  //         })
  //       })
  //     } else {
  //       next()
  //     }
  //   }
  // } else {
  //   if (whiteList.indexOf(to.path) !== -1) {
  //     next()
  //   } else {
  //     next('/login')
  //     NProgress.done()
  //   }
  // }
})

router.afterEach(() => {
  NProgress.done() // 结束Progress
})


重新指向路由 config/index.js
第17行修改如下
port: 8080


开发环境配置成本机 config/dev.env.js
BASE_API: '"http://127.0.0.1:8000"',
  
  
线上环境配置 config/prod.env.js
BASE_API: '"http://127.0.0.1:8000"',


去掉不需要的路由 router/index.js
//  {
//    path: '/example',
//    component: Layout,
//    redirect: '/example/table',
//    name: 'Example',
//    meta: { title: 'Example', icon: 'example' },
//    children: [
//      {
//        path: 'table',
//        name: 'Table',
//        component: () => import('@/views/table/index'),
//        meta: { title: 'Table', icon: 'table' }
//      },
//      {
//        path: 'tree',
//        name: 'Tree',
//        component: () => import('@/views/tree/index'),
//        meta: { title: 'Tree', icon: 'tree' }
//      }
//    ]
//  },
//
//  {
//    path: '/form',
//    component: Layout,
//    children: [
//      {
//        path: 'index',
//        name: 'Form',
//        component: () => import('@/views/form/index'),
//        meta: { title: 'Form', icon: 'form' }
//      }
//    ]
//  },


用户展示,添加前端路由 router/index.js
  {
    path: '/users',
    component: Layout,
    name: 'users',
    meta: { title: '用户管理', icon: 'example' },
    children: [
      {
        path: 'user',
        name: 'user',
        component: () => import('@/views/users/user.vue'),   
        meta: { title: '用户', icon: 'table' }
      }
    ]
  },
  
  
前端视图 views/users/user.vue
<template>
  <div class="app-container">
    <el-row>
      <el-col :span="12">
        <el-input v-model="params.username" placeholder="搜索用户名" @keyup.enter.native="handleSearch">
          <el-button slot="append" icon="el-icon-search" @click="handleSearch"/>
        </el-input>
      </el-col>
      <el-col :span="12" align="right" style="padding-right:20px;">
        <el-button type="primary" @click="addUserVisible=true">添加用户</el-button>
      </el-col>
    </el-row>
    <el-table
      :data="userList"
      stripe
      style="width: 100%">
      <el-table-column
        prop="username"
        label="username"/>
      <el-table-column
        prop="name"
        label="姓名"/>
      <el-table-column
        prop="phone"
        label="电话"/>
      <el-table-column
        prop="email"
        label="email"/>
      <el-table-column
        prop="is_active"
        label="状态">
        <template slot-scope="scope">
          <el-switch v-model="scope.row.is_active" @change="handleUserStatusChange(scope.row)"/>
        </template>
      </el-table-column>
      <el-table-column
        prop="last_login"
        label="last_login"/>
      <el-table-column
        fixed="right"
        label="操作"
        width="100">
        <template slot-scope="scope">
          <el-button type="text" size="small" @click="handleModify(scope.row)">修改</el-button>
        </template>
      </el-table-column>
    </el-table>
    <el-row v-show="total>10" type="flex" justify="center" style="padding-top:20px;">
      <el-pagination
        :total="total"
        layout="prev, pager, next"
        background
        @current-change="handleChange" />
    </el-row>
    <AddUserForm v-model="addUserVisible" @fetch="handleFetch" />
    <ModifyUserForm v-model="modifyUserVisible" :user-id="userId" @fetch="handleFetch" />
  </div>
</template>
<script>
  import { getUserList, modifyUser } from '@/api/user'
  import AddUserForm from './components/addUserForm.vue'
  import ModifyUserForm from './components/modifyUser.vue'
  export default {
    name: 'UserList',
    components: {
      AddUserForm,
      ModifyUserForm
    },
    data() {
      return {
        userList: [],
        addUserVisible: false,
        modifyUserVisible: false,
        userId: 0,
        total: 0,
        params: {
          page: 1,
          username: ''
        }
      }
    },
    created() {
      this.fetchUserList()
    },
    methods: {
      fetchUserList() {
        getUserList(this.params).then(res => {
          this.userList = res.results
          this.total = res.count
        })
      },
      handleUserStatusChange(obj) {
        modifyUser(obj.id, { is_active: obj.is_active }).then(() => {
          this.$message({
            message: `修改 ${obj.name} 的状态成功`,
            type: 'success'
          })
        })
      },
      handleFetch() {
        this.fetchUserList()
      },
      handleModify(obj) {
        this.userId = obj.id
        this.modifyUserVisible = true
      },
      handleChange(val) {
        this.params.page = val
        this.fetchUserList()
      },
      handleSearch() {
        this.params.page = 1
        this.fetchUserList()
      }
    }
  }
</script>


浏览器装vue插件https://www.gugeapps.com/


获取后端数据 api/user.js
import request from '@/utils/request'

export function getUserList(params) {
  return request({
    url: '/api/users/',
    method: 'get',
    params
  })
}

export function getUser(userId) {
  return request({
    url: `/api/users/${userId}/`,
    method: 'get'
  })
}

export function modifyUser(id, data) {
  return request({
    url: `/api/users/${id}/`,
    method: 'patch',
    data
  })
}

export function addUser(data) {
  return request({
    url: `/api/userreg/`,
    method: 'post',
    data
  })
}


解决跨域参考github : django cors 


解决前端request拦截器utils/request.js
import axios from 'axios'
import { Message } from 'element-ui'
// import store from '../store'
// import { getToken } from '@/utils/auth'

// 创建axios实例
const service = axios.create({
  baseURL: process.env.BASE_API, // api的base_url
  timeout: 15000 // 请求超时时间
})

// request拦截器
// service.interceptors.request.use(config => {
//   if (store.getters.token) {
//     config.headers['X-Token'] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
//   }
//   return config
// }, error => {
//   // Do something with request error
//   console.log(error) // for debug
//   Promise.reject(error)
// })

// respone拦截器
service.interceptors.response.use(
  response => {
  /**
  * code为非20000是抛错 可结合自己业务进行修改
  */
    return response.data
    // const res = response.data
    // if (res.code !== 20000) {
    //   Message({
    //     message: res.message,
    //     type: 'error',
    //     duration: 5 * 1000
    //   })
    //
    //   // 50008:非法的token; 50012:其他客户端登录了;  50014:Token 过期了;
    //   if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
    //     MessageBox.confirm('你已被登出,可以取消继续留在该页面,或者重新登录', '确定登出', {
    //       confirmButtonText: '重新登录',
    //       cancelButtonText: '取消',
    //       type: 'warning'
    //     }).then(() => {
    //       store.dispatch('FedLogOut').then(() => {
    //         location.reload()// 为了重新实例化vue-router对象 避免bug
    //       })
    //     })
    //   }
    //   return Promise.reject('error')
    // } else {
    //   return response.data
    // }
  },
  error => {
    console.log('err' + error)// for debug
    Message({
      message: error.message,
      type: 'error',
      duration: 5 * 1000
    })
    return Promise.reject(error)
  }
)

export default service


添加用户 users/components/addUserForm.vue
<template>
  <div class="add-user-form">
    <el-dialog :visible.sync="visible" title="用户信息" @close="handleClose">
      <el-form ref="addUserForm" :model="form" :rules="rules" label-width="100px">
        <el-form-item label="登陆名:" prop="username">
          <el-input v-model="form.username" placeholder="请输入登陆名"/>
        </el-form-item>
        <el-form-item label="姓名:" prop="name">
          <el-input v-model="form.name" placeholder="请输入姓名"/>
        </el-form-item>
        <el-form-item label="密码:" prop="password">
          <el-input v-model="form.password" type="password" placeholder="请输入密码"/>
        </el-form-item>
        <el-form-item label="手机号:" prop="phone">
          <el-input v-model="form.phone" placeholder="请输入手机号"/>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="submitForm">立即创建</el-button>
          <el-button @click="resetForm">重置</el-button>
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button @click="handleClose">取 消</el-button>
        <el-button type="primary" @click="handleClose">确 定</el-button>
      </div>
    </el-dialog>
  </div>
</template>
<script>
  import { addUser } from '@/api/user'
  export default {
    name: 'AddUserForm',
    props: {
      value: {
        type: Boolean,
        default: false
      }
    },
    data() {
      return {
        visible: false,
        form: {
          username: '',
          name: '',
          password: '',
          phone: ''
        },
        rules: {
          username: [
            { required: true, message: '请输入用户名', trigger: 'blur' }
          ],
          name: [
            { required: true, message: '请输入用户姓名', trigger: 'blur' }
          ],
          password: [
            { required: true, message: '请输入密码', trigger: 'blur' }
          ],
          phone: [
            { required: true, message: '请输入联系电话', trigger: 'blur' }
          ]
        }
      }
    },
    watch: {
      value(val) {
        this.visible = val
      }
    },
    methods: {
      handleClose() {
        this.visible = false
        this.$emit('input', false)
        this.resetForm()
      },
      resetForm() {
        this.$refs.addUserForm.resetFields()
      },
      submitForm() {
        this.$refs.addUserForm.validate((valid) => {
          if (valid) {
            addUser(this.form).then(() => {
              this.$message({
                message: `创建用户 ${this.form.name} 成功`,
                type: 'success'
              })
              this.handleClose()
              this.$emit('fetch')
            })
          } else {
            console.log('error submit!!')
            return false
          }
        })
      }
    }
  }
</script>


用户修改功能 users/components/modifyUser.vue
<template>
  <div class="modify-user-form">
    <el-dialog :visible.sync="visible" :title="title" @close="handleClose">
      <el-form ref="modifyUserForm" :model="form" :rules="rules" label-width="100px">
        <el-form-item label="手机号:" prop="phone">
          <el-input v-model="form.phone" placeholder="请输入手机号"/>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="submitForm">修改</el-button>
          <el-button @click="resetForm">重置</el-button>
        </el-form-item>
      </el-form>
    </el-dialog>
  </div>
</template>
<script>
import { getUser, modifyUser } from '@/api/user'
export default {
  name: 'ModifyUser',
  props: {
    value: {
      type: Boolean,
      default: false
    },
    userId: {
      type: Number,
      default: 0
    }
  },
  data() {
    return {
      visible: false,
      userObj: null,
      form: {
        phone: ''
      },
      rules: {
        phone: [
          { required: true, message: '请输入联系电话', trigger: 'blur' }
        ]
      },
      uid: 0,
      title: ''
    }
  },
  watch: {
    value(val) {
      this.visible = val
    },
    userId(val) {
      if (val === 0) return
      this.uid = val
      this.fetchUser()
    }
  },
  methods: {
    handleClose() {
      this.visible = false
      this.$emit('input', false)
      this.userId = 0
      this.userObj = null
      this.resetForm()
      this.title = ''
    },
    resetForm() {
      this.$refs.modifyUserForm.resetFields()
      if (this.userId === 0) return
      if (this.userObj === null) return
      this.form.phone = this.userObj.phone
    },
    submitForm() {
      this.$refs.modifyUserForm.validate((valid) => {
        if (valid) {
          modifyUser(this.userId, this.form).then(() => {
            this.$message({
              message: `创建用户 ${this.userObj.name} 手机号成功`,
              type: 'success'
            })
            this.handleClose()
            this.$emit('fetch')
          })
        } else {
          console.log('error submit!!')
          return false
        }
      })
    },
    fetchUser() {
      getUser(this.uid).then(res => {
        this.title = `修改 ${res.name} 的信息`
        this.userObj = res
        this.form.phone = res.phone
      })
    }
  }
}
</script>



 

posted @ 2021-02-12 23:39  屠魔的少年  阅读(5)  评论(0)    收藏  举报