1. 项目背景与技术选型
企业员工培训系统是现代企业数字化转型的重要组成部分。随着远程办公和混合工作模式的普及,一套高效、灵活的员工培训平台能够显著提升企业人力资源管理的效率。Python作为后端开发语言,凭借其简洁语法和丰富的生态库,成为构建此类系统的理想选择。
在技术栈选择上,我们采用前后端分离架构:
- 前端:Vue.js 3.x + Element Plus
- 后端:Django 4.2 / Flask 2.3
- 开发工具:PyCharm Professional 2023.2
- 数据库:PostgreSQL 15
关键决策:选择Django还是Flask取决于项目规模。Django自带Admin、ORM等全套工具,适合快速开发标准化系统;Flask更轻量,适合需要高度定制的场景。本案例以Django为主进行讲解,但核心逻辑同样适用于Flask。
2. 开发环境配置
2.1 Python环境搭建
推荐使用pyenv管理多版本Python:
bash复制# 安装pyenv
curl https://pyenv.run | bash
# 安装Python 3.10.6
pyenv install 3.10.6
pyenv global 3.10.6
# 验证安装
python -V
pip -V
2.2 PyCharm专业版配置
- 创建新项目时选择"Django"模板
- 配置项目解释器为上述pyenv环境
- 安装必备插件:
- Django Support
- Vue.js
- Database Tools
- REST Client
2.3 前端环境准备
bash复制# 安装Node.js 18.x
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt-get install -y nodejs
# 安装Vue CLI
npm install -g @vue/cli
# 创建Vue项目
vue create training-system-frontend
3. 数据库设计与模型层实现
3.1 数据库Schema设计
python复制# models.py
from django.db import models
from django.contrib.auth.models import User
class Department(models.Model):
name = models.CharField(max_length=100)
code = models.CharField(max_length=20, unique=True)
class Employee(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
department = models.ForeignKey(Department, on_delete=models.SET_NULL, null=True)
position = models.CharField(max_length=100)
hire_date = models.DateField()
class Course(models.Model):
title = models.CharField(max_length=200)
description = models.TextField()
duration = models.PositiveIntegerField() # 分钟数
is_required = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
class TrainingRecord(models.Model):
employee = models.ForeignKey(Employee, on_delete=models.CASCADE)
course = models.ForeignKey(Course, on_delete=models.CASCADE)
completion_date = models.DateTimeField(null=True, blank=True)
score = models.PositiveIntegerField(null=True, blank=True)
STATUS_CHOICES = [
('P', 'Pending'),
('I', 'In Progress'),
('C', 'Completed'),
]
status = models.CharField(max_length=1, choices=STATUS_CHOICES, default='P')
3.2 Django Admin定制
python复制# admin.py
from django.contrib import admin
from .models import Department, Employee, Course, TrainingRecord
class EmployeeAdmin(admin.ModelAdmin):
list_display = ('user', 'department', 'position')
search_fields = ('user__username', 'position')
list_filter = ('department',)
admin.site.register(Department)
admin.site.register(Employee, EmployeeAdmin)
admin.site.register(Course)
admin.site.register(TrainingRecord)
4. REST API开发
4.1 Django REST Framework配置
python复制# settings.py
INSTALLED_APPS = [
...
'rest_framework',
'rest_framework.authtoken',
]
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
]
}
4.2 序列化与视图
python复制# serializers.py
from rest_framework import serializers
from .models import Course, TrainingRecord
class CourseSerializer(serializers.ModelSerializer):
class Meta:
model = Course
fields = '__all__'
class TrainingRecordSerializer(serializers.ModelSerializer):
course = CourseSerializer(read_only=True)
class Meta:
model = TrainingRecord
fields = '__all__'
# views.py
from rest_framework import viewsets
from .models import Course, TrainingRecord
from .serializers import CourseSerializer, TrainingRecordSerializer
class CourseViewSet(viewsets.ModelViewSet):
queryset = Course.objects.all()
serializer_class = CourseSerializer
class TrainingRecordViewSet(viewsets.ModelViewSet):
serializer_class = TrainingRecordSerializer
def get_queryset(self):
return TrainingRecord.objects.filter(employee__user=self.request.user)
4.3 URL路由配置
python复制# urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from . import views
router = DefaultRouter()
router.register(r'courses', views.CourseViewSet)
router.register(r'records', views.TrainingRecordViewSet)
urlpatterns = [
path('api/', include(router.urls)),
path('api-auth/', include('rest_framework.urls')),
]
5. 前端Vue实现
5.1 项目结构
code复制src/
├── assets/
├── components/
│ ├── CourseCard.vue
│ ├── NavBar.vue
│ └── TrainingProgress.vue
├── router/
│ └── index.js
├── store/
│ └── index.js
├── views/
│ ├── Dashboard.vue
│ ├── Courses.vue
│ └── Profile.vue
└── App.vue
5.2 核心组件实现
vue复制<!-- CourseCard.vue -->
<template>
<el-card class="course-card">
<div slot="header">
<span>{{ course.title }}</span>
<el-tag v-if="course.is_required" type="danger">必修</el-tag>
</div>
<div class="course-desc">{{ course.description }}</div>
<div class="course-footer">
<span>时长: {{ duration }}分钟</span>
<el-button
type="primary"
size="small"
@click="startCourse"
>
开始学习
</el-button>
</div>
</el-card>
</template>
<script>
export default {
props: {
course: {
type: Object,
required: true
}
},
computed: {
duration() {
return Math.ceil(this.course.duration / 60)
}
},
methods: {
startCourse() {
this.$emit('start', this.course.id)
}
}
}
</script>
5.3 Vuex状态管理
javascript复制// store/index.js
import { createStore } from 'vuex'
import axios from 'axios'
export default createStore({
state: {
courses: [],
records: []
},
mutations: {
SET_COURSES(state, courses) {
state.courses = courses
},
SET_RECORDS(state, records) {
state.records = records
}
},
actions: {
async fetchCourses({ commit }) {
const response = await axios.get('/api/courses/')
commit('SET_COURSES', response.data)
},
async fetchRecords({ commit }) {
const response = await axios.get('/api/records/')
commit('SET_RECORDS', response.data)
}
}
})
6. 系统部署方案
6.1 生产环境准备
bash复制# 安装必要依赖
sudo apt update
sudo apt install -y nginx postgresql python3-pip
# 创建数据库用户
sudo -u postgres createuser -P training_user
sudo -u postgres createdb -O training_user training_system
# 安装Python依赖
pip install gunicorn psycopg2-binary
6.2 Gunicorn配置
python复制# gunicorn.conf.py
bind = "0.0.0.0:8000"
workers = 3
worker_class = "gevent"
timeout = 120
6.3 Nginx配置
nginx复制# /etc/nginx/sites-available/training_system
server {
listen 80;
server_name yourdomain.com;
location / {
proxy_pass http://localhost:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /static/ {
alias /path/to/your/static/files/;
}
location /media/ {
alias /path/to/your/media/files/;
}
}
7. 实际开发中的经验分享
- Django信号的使用:当员工完成培训课程时,自动发送通知邮件
python复制# signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.core.mail import send_mail
from .models import TrainingRecord
@receiver(post_save, sender=TrainingRecord)
def send_completion_email(sender, instance, created, **kwargs):
if instance.status == 'C' and not created:
subject = f"培训完成通知: {instance.course.title}"
message = f"尊敬的{instance.employee.user.username}, 您已完成课程学习!"
send_mail(
subject,
message,
'noreply@yourcompany.com',
[instance.employee.user.email],
fail_silently=False,
)
- Vue性能优化:对于课程列表使用虚拟滚动
vue复制<template>
<el-table
:data="courses"
style="width: 100%"
height="calc(100vh - 180px)"
row-key="id"
:row-height="80"
>
<!-- 列定义 -->
</el-table>
</template>
- 数据库查询优化:避免N+1查询问题
python复制# 错误做法
records = TrainingRecord.objects.filter(employee=request.user.employee)
for record in records:
print(record.course.title) # 每次循环都会查询数据库
# 正确做法
records = TrainingRecord.objects.select_related('course').filter(
employee=request.user.employee
)
- 前端安全实践:处理JWT令牌
javascript复制// axios拦截器
axios.interceptors.request.use(config => {
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
})
axios.interceptors.response.use(
response => response,
error => {
if (error.response.status === 401) {
localStorage.removeItem('token')
router.push('/login')
}
return Promise.reject(error)
}
)
8. 常见问题解决方案
- 跨域问题:Django后端需要配置CORS
python复制# settings.py
INSTALLED_APPS = [
...
'corsheaders',
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
...
]
CORS_ALLOWED_ORIGINS = [
"http://localhost:8080",
"https://yourdomain.com"
]
- 静态文件收集:生产环境需要运行
bash复制python manage.py collectstatic
- Vue路由模式:使用history模式需要Nginx额外配置
nginx复制location / {
try_files $uri $uri/ /index.html;
}
- 数据库连接池:使用django-db-geventpool优化并发
python复制# settings.py
DATABASES = {
'default': {
'ENGINE': 'django_db_geventpool.backends.postgresql_psycopg2',
'HOST': 'localhost',
'NAME': 'training_system',
'USER': 'training_user',
'PASSWORD': 'yourpassword',
'OPTIONS': {
'MAX_CONNS': 20,
}
}
}
- 异步任务处理:使用Celery处理耗时操作
python复制# tasks.py
from celery import shared_task
from django.core.mail import send_mail
@shared_task
def send_bulk_emails(recipients, subject, message):
send_mail(
subject,
message,
'noreply@yourcompany.com',
recipients,
fail_silently=False,
)
