创建一个完整的企业级项目案例:一个简单的员工管理系统。前端使用Vue 3,后端使用C++(基于Drogon框架)。
项目结构
employee-management/
├── frontend/ # Vue前端
│ ├── src/
│ │ ├── components/
│ │ ├── views/
│ │ ├── api/
│ │ ├── App.vue
│ │ └── main.js
│ └── package.json
└── backend/ # C++后端
├── src/
│ ├── main.cpp
│ ├── EmployeeController.h
│ ├── EmployeeController.cpp
│ ├── Employee.h
│ └── Employee.cpp
├── CMakeLists.txt
└── config.json
后端实现(C++ with Drogon)
1. CMakeLists.txt
cmake
cmake_minimum_required(VERSION 3.16)
project(employee_backend)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Drogon REQUIRED)
find_package(jsoncpp REQUIRED)
add_executable(employee_server
src/main.cpp
src/Employee.cpp
src/EmployeeController.cpp
)
target_link_libraries(employee_server
Drogon::Drogon
jsoncpp
)
2. config.json
json
{
"listeners": [
{
"address": "0.0.0.0",
"port": 8080,
"https": false
}
],
"app": {
"document_root": "./",
"static_files_headers": [
{
"uri": "/",
"headers": {
"Access-Control-Allow-Origin": "*"
}
}
]
},
"db_clients": []
}
3. Employee.h
cpp
#ifndef EMPLOYEE_H
#define EMPLOYEE_H
#include <string>
#include <json/json.h>
class Employee {
private:
int id;
std::string name;
std::string department;
std::string position;
double salary;
public:
Employee() : id(0), salary(0.0) {}
Employee(int id, const std::string& name, const std::string& department,
const std::string& position, double salary)
: id(id), name(name), department(department), position(position), salary(salary) {}
// Getters
int getId() const { return id; }
std::string getName() const { return name; }
std::string getDepartment() const { return department; }
std::string getPosition() const { return position; }
double getSalary() const { return salary; }
// Setters
void setId(int id) { this->id = id; }
void setName(const std::string& name) { this->name = name; }
void setDepartment(const std::string& department) { this->department = department; }
void setPosition(const std::string& position) { this->position = position; }
void setSalary(double salary) { this->salary = salary; }
// JSON序列化
Json::Value toJson() const {
Json::Value json;
json["id"] = id;
json["name"] = name;
json["department"] = department;
json["position"] = position;
json["salary"] = salary;
return json;
}
// JSON反序列化
static Employee fromJson(const Json::Value& json) {
Employee emp;
if (json.isMember("id")) emp.setId(json["id"].asInt());
if (json.isMember("name")) emp.setName(json["name"].asString());
if (json.isMember("department")) emp.setDepartment(json["department"].asString());
if (json.isMember("position")) emp.setPosition(json["position"].asString());
if (json.isMember("salary")) emp.setSalary(json["salary"].asDouble());
return emp;
}
};
#endif // EMPLOYEE_H
4. EmployeeController.h
cpp
#ifndef EMPLOYEE_CONTROLLER_H
#define EMPLOYEE_CONTROLLER_H
#include <drogon/HttpController.h>
#include <vector>
#include <mutex>
#include "Employee.h"
using namespace drogon;
class EmployeeController : public HttpController<EmployeeController> {
private:
std::vector<Employee> employees;
std::mutex employeesMutex;
int nextId;
// 模拟数据库操作
void initSampleData();
public:
EmployeeController() : nextId(1) {
initSampleData();
}
METHOD_LIST_BEGIN
// 定义路由
ADD_METHOD_TO(EmployeeController::getAllEmployees, "/api/employees", Get, Options);
ADD_METHOD_TO(EmployeeController::getEmployee, "/api/employees/{1}", Get, Options);
ADD_METHOD_TO(EmployeeController::createEmployee, "/api/employees", Post, Options);
ADD_METHOD_TO(EmployeeController::updateEmployee, "/api/employees/{1}", Put, Options);
ADD_METHOD_TO(EmployeeController::deleteEmployee, "/api/employees/{1}", Delete, Options);
METHOD_LIST_END
// API处理方法
void getAllEmployees(const HttpRequestPtr& req,
std::function<void(const HttpResponsePtr&)>&& callback);
void getEmployee(const HttpRequestPtr& req,
std::function<void(const HttpResponsePtr&)>&& callback,
int id);
void createEmployee(const HttpRequestPtr& req,
std::function<void(const HttpResponsePtr&)>&& callback);
void updateEmployee(const HttpRequestPtr& req,
std::function<void(const HttpResponsePtr&)>&& callback,
int id);
void deleteEmployee(const HttpRequestPtr& req,
std::function<void(const HttpResponsePtr&)>&& callback,
int id);
};
#endif // EMPLOYEE_CONTROLLER_H
5. EmployeeController.cpp
cpp
#include "EmployeeController.h"
#include <json/json.h>
void EmployeeController::initSampleData() {
employees.push_back(Employee(1, "张三", "技术部", "高级工程师", 15000));
employees.push_back(Employee(2, "李四", "市场部", "市场经理", 12000));
employees.push_back(Employee(3, "王五", "人事部", "HR主管", 10000));
nextId = 4;
}
void EmployeeController::getAllEmployees(
const HttpRequestPtr& req,
std::function<void(const HttpResponsePtr&)>&& callback) {
// 处理CORS预检请求
if (req->getMethod() == Options) {
auto resp = HttpResponse::newHttpResponse();
resp->addHeader("Access-Control-Allow-Origin", "*");
resp->addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
resp->addHeader("Access-Control-Allow-Headers", "Content-Type");
callback(resp);
return;
}
std::lock_guard<std::mutex> lock(employeesMutex);
Json::Value jsonArray(Json::arrayValue);
for (const auto& emp : employees) {
jsonArray.append(emp.toJson());
}
auto resp = HttpResponse::newHttpJsonResponse(jsonArray);
resp->addHeader("Access-Control-Allow-Origin", "*");
callback(resp);
}
void EmployeeController::getEmployee(
const HttpRequestPtr& req,
std::function<void(const HttpResponsePtr&)>&& callback,
int id) {
if (req->getMethod() == Options) {
auto resp = HttpResponse::newHttpResponse();
resp->addHeader("Access-Control-Allow-Origin", "*");
resp->addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
resp->addHeader("Access-Control-Allow-Headers", "Content-Type");
callback(resp);
return;
}
std::lock_guard<std::mutex> lock(employeesMutex);
auto it = std::find_if(employees.begin(), employees.end(),
[id](const Employee& emp) { return emp.getId() == id; });
if (it != employees.end()) {
auto resp = HttpResponse::newHttpJsonResponse(it->toJson());
resp->addHeader("Access-Control-Allow-Origin", "*");
callback(resp);
} else {
Json::Value error;
error["error"] = "Employee not found";
auto resp = HttpResponse::newHttpJsonResponse(error);
resp->setStatusCode(k404NotFound);
resp->addHeader("Access-Control-Allow-Origin", "*");
callback(resp);
}
}
void EmployeeController::createEmployee(
const HttpRequestPtr& req,
std::function<void(const HttpResponsePtr&)>&& callback) {
if (req->getMethod() == Options) {
auto resp = HttpResponse::newHttpResponse();
resp->addHeader("Access-Control-Allow-Origin", "*");
resp->addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
resp->addHeader("Access-Control-Allow-Headers", "Content-Type");
callback(resp);
return;
}
auto json = req->getJsonObject();
if (!json) {
Json::Value error;
error["error"] = "Invalid JSON";
auto resp = HttpResponse::newHttpJsonResponse(error);
resp->setStatusCode(k400BadRequest);
resp->addHeader("Access-Control-Allow-Origin", "*");
callback(resp);
return;
}
std::lock_guard<std::mutex> lock(employeesMutex);
Employee emp = Employee::fromJson(*json);
emp.setId(nextId++);
employees.push_back(emp);
auto resp = HttpResponse::newHttpJsonResponse(emp.toJson());
resp->setStatusCode(k201Created);
resp->addHeader("Access-Control-Allow-Origin", "*");
callback(resp);
}
void EmployeeController::updateEmployee(
const HttpRequestPtr& req,
std::function<void(const HttpResponsePtr&)>&& callback,
int id) {
if (req->getMethod() == Options) {
auto resp = HttpResponse::newHttpResponse();
resp->addHeader("Access-Control-Allow-Origin", "*");
resp->addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
resp->addHeader("Access-Control-Allow-Headers", "Content-Type");
callback(resp);
return;
}
auto json = req->getJsonObject();
if (!json) {
Json::Value error;
error["error"] = "Invalid JSON";
auto resp = HttpResponse::newHttpJsonResponse(error);
resp->setStatusCode(k400BadRequest);
resp->addHeader("Access-Control-Allow-Origin", "*");
callback(resp);
return;
}
std::lock_guard<std::mutex> lock(employeesMutex);
auto it = std::find_if(employees.begin(), employees.end(),
[id](const Employee& emp) { return emp.getId() == id; });
if (it != employees.end()) {
Employee updatedEmp = Employee::fromJson(*json);
updatedEmp.setId(id);
*it = updatedEmp;
auto resp = HttpResponse::newHttpJsonResponse(updatedEmp.toJson());
resp->addHeader("Access-Control-Allow-Origin", "*");
callback(resp);
} else {
Json::Value error;
error["error"] = "Employee not found";
auto resp = HttpResponse::newHttpJsonResponse(error);
resp->setStatusCode(k404NotFound);
resp->addHeader("Access-Control-Allow-Origin", "*");
callback(resp);
}
}
void EmployeeController::deleteEmployee(
const HttpRequestPtr& req,
std::function<void(const HttpResponsePtr&)>&& callback,
int id) {
if (req->getMethod() == Options) {
auto resp = HttpResponse::newHttpResponse();
resp->addHeader("Access-Control-Allow-Origin", "*");
resp->addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
resp->addHeader("Access-Control-Allow-Headers", "Content-Type");
callback(resp);
return;
}
std::lock_guard<std::mutex> lock(employeesMutex);
auto it = std::find_if(employees.begin(), employees.end(),
[id](const Employee& emp) { return emp.getId() == id; });
if (it != employees.end()) {
employees.erase(it);
Json::Value success;
success["message"] = "Employee deleted successfully";
auto resp = HttpResponse::newHttpJsonResponse(success);
resp->addHeader("Access-Control-Allow-Origin", "*");
callback(resp);
} else {
Json::Value error;
error["error"] = "Employee not found";
auto resp = HttpResponse::newHttpJsonResponse(error);
resp->setStatusCode(k404NotFound);
resp->addHeader("Access-Control-Allow-Origin", "*");
callback(resp);
}
}
6. main.cpp
cpp
#include <drogon/drogon.h>
#include "EmployeeController.h"
int main() {
// 设置日志级别
drogon::app().setLogLevel(trantor::Logger::kWarn);
// 添加CORS支持
drogon::app().registerPostHandlingAdvice([](const drogon::HttpRequestPtr& req,
const drogon::HttpResponsePtr& resp) {
resp->addHeader("Access-Control-Allow-Origin", "*");
resp->addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
resp->addHeader("Access-Control-Allow-Headers", "Content-Type");
});
// 加载配置文件
drogon::app().loadConfigFile("config.json");
// 注册控制器
auto controller = std::make_shared<EmployeeController>();
drogon::app().registerController(controller);
// 启动服务器
drogon::app().run();
return 0;
}
前端实现(Vue 3)
1. package.json
json
{
"name": "employee-frontend",
"version": "1.0.0",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"vue": "^3.3.0",
"axios": "^1.4.0",
"element-plus": "^2.3.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.2.0",
"vite": "^4.3.0"
}
}
2. vite.config.js
javascript
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
server: {
port: 3000,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
}
})
3. index.html
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>员工管理系统</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
4. src/main.js
javascript
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
const app = createApp(App)
app.use(ElementPlus)
app.mount('#app')
5. src/api/employee.js
javascript
import axios from 'axios'
const API_BASE_URL = '/api'
const api = axios.create({
baseURL: API_BASE_URL,
timeout: 5000,
headers: {
'Content-Type': 'application/json'
}
})
export const employeeApi = {
// 获取所有员工
getAllEmployees() {
return api.get('/employees')
},
// 获取单个员工
getEmployee(id) {
return api.get(`/employees/${id}`)
},
// 创建员工
createEmployee(employee) {
return api.post('/employees', employee)
},
// 更新员工
updateEmployee(id, employee) {
return api.put(`/employees/${id}`, employee)
},
// 删除员工
deleteEmployee(id) {
return api.delete(`/employees/${id}`)
}
}
6. src/App.vue
vue
<template>
<div id="app">
<el-container>
<el-header>
<h1>员工管理系统</h1>
</el-header>
<el-main>
<EmployeeList />
</el-main>
</el-container>
</div>
</template>
<script>
import EmployeeList from './components/EmployeeList.vue'
export default {
name: 'App',
components: {
EmployeeList
}
}
</script>
<style>
#app {
font-family: 'Arial', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
}
.el-header {
background-color: #409EFF;
color: white;
display: flex;
align-items: center;
padding: 0 20px;
}
.el-header h1 {
margin: 0;
font-size: 24px;
}
.el-main {
padding: 20px;
}
</style>
7. src/components/EmployeeList.vue
vue
<template>
<div class="employee-list">
<!-- 操作栏 -->
<div class="toolbar">
<el-button type="primary" @click="showAddDialog">
<el-icon><Plus /></el-icon>
添加员工
</el-button>
</div>
<!-- 员工表格 -->
<el-table
:data="employees"
style="width: 100%"
border
stripe
v-loading="loading"
>
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="name" label="姓名" width="120" />
<el-table-column prop="department" label="部门" width="150" />
<el-table-column prop="position" label="职位" width="150" />
<el-table-column prop="salary" label="薪资" width="120">
<template #default="scope">
¥{{ scope.row.salary.toLocaleString() }}
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="scope">
<el-button size="small" @click="showEditDialog(scope.row)">
编辑
</el-button>
<el-button
size="small"
type="danger"
@click="deleteEmployee(scope.row.id)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 添加/编辑对话框 -->
<el-dialog
v-model="dialogVisible"
:title="dialogTitle"
width="500px"
@close="resetForm"
>
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-width="100px"
>
<el-form-item label="姓名" prop="name">
<el-input v-model="form.name" placeholder="请输入姓名" />
</el-form-item>
<el-form-item label="部门" prop="department">
<el-select v-model="form.department" placeholder="请选择部门">
<el-option label="技术部" value="技术部" />
<el-option label="市场部" value="市场部" />
<el-option label="人事部" value="人事部" />
<el-option label="财务部" value="财务部" />
<el-option label="行政部" value="行政部" />
</el-select>
</el-form-item>
<el-form-item label="职位" prop="position">
<el-input v-model="form.position" placeholder="请输入职位" />
</el-form-item>
<el-form-item label="薪资" prop="salary">
<el-input-number
v-model="form.salary"
:min="0"
:max="1000000"
:step="1000"
placeholder="请输入薪资"
/>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitForm">
确定
</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script>
import { ref, reactive, onMounted, computed } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
import { employeeApi } from '../api/employee'
export default {
name: 'EmployeeList',
components: {
Plus
},
setup() {
const employees = ref([])
const loading = ref(false)
const dialogVisible = ref(false)
const formRef = ref(null)
const editingId = ref(null)
const form = reactive({
name: '',
department: '',
position: '',
salary: 0
})
const rules = {
name: [
{ required: true, message: '请输入姓名', trigger: 'blur' },
{ min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' }
],
department: [
{ required: true, message: '请选择部门', trigger: 'change' }
],
position: [
{ required: true, message: '请输入职位', trigger: 'blur' }
],
salary: [
{ required: true, message: '请输入薪资', trigger: 'blur' }
]
}
const dialogTitle = computed(() => {
return editingId.value ? '编辑员工' : '添加员工'
})
// 获取所有员工
const fetchEmployees = async () => {
loading.value = true
try {
const response = await employeeApi.getAllEmployees()
employees.value = response.data
} catch (error) {
ElMessage.error('获取员工列表失败:' + error.message)
} finally {
loading.value = false
}
}
// 显示添加对话框
const showAddDialog = () => {
editingId.value = null
resetForm()
dialogVisible.value = true
}
// 显示编辑对话框
const showEditDialog = (employee) => {
editingId.value = employee.id
form.name = employee.name
form.department = employee.department
form.position = employee.position
form.salary = employee.salary
dialogVisible.value = true
}
// 提交表单
const submitForm = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid) => {
if (valid) {
try {
const employeeData = {
name: form.name,
department: form.department,
position: form.position,
salary: form.salary
}
if (editingId.value) {
await employeeApi.updateEmployee(editingId.value, employeeData)
ElMessage.success('员工信息更新成功')
} else {
await employeeApi.createEmployee(employeeData)
ElMessage.success('员工添加成功')
}
dialogVisible.value = false
await fetchEmployees()
} catch (error) {
ElMessage.error('操作失败:' + error.message)
}
}
})
}
// 删除员工
const deleteEmployee = async (id) => {
try {
await ElMessageBox.confirm(
'确定要删除该员工吗?',
'警告',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
)
await employeeApi.deleteEmployee(id)
ElMessage.success('员工删除成功')
await fetchEmployees()
} catch (error) {
if (error !== 'cancel') {
ElMessage.error('删除失败:' + error.message)
}
}
}
// 重置表单
const resetForm = () => {
form.name = ''
form.department = ''
form.position = ''
form.salary = 0
if (formRef.value) {
formRef.value.resetFields()
}
}
// 页面加载时获取数据
onMounted(() => {
fetchEmployees()
})
return {
employees,
loading,
dialogVisible,
formRef,
form,
rules,
dialogTitle,
showAddDialog,
showEditDialog,
submitForm,
deleteEmployee,
resetForm
}
}
}
</script>
<style scoped>
.employee-list {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}
.toolbar {
margin-bottom: 20px;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
}
</style>
编译和运行
安装vcpkg:
bash
# 克隆vcpkg仓库
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
# 运行bootstrap脚本
.\bootstrap-vcpkg.bat
# 集成到Visual Studio(可选)
.\vcpkg integrate install
后端编译运行:
bash
.\vcpkg install drogon:x64-windows nlohmann-json:x64-windows
# 或者使用jsoncpp(Drogon默认依赖,已自动安装)
.\vcpkg install jsoncpp:x64-windows
# 编译
cd backend
mkdir build && cd build
cmake ..
make
# 运行
./employee_server
前端运行:
bash
cd frontend
npm install
npm run dev
项目特点
- 完整的企业级架构:前后端分离,RESTful API设计
- 规范的代码结构:清晰的目录组织,模块化设计
- 完整的CRUD操作:增删改查功能齐全
- 错误处理:包含完善的错误处理和用户提示
- 线程安全:后端使用互斥锁保证数据安全
- CORS支持:处理跨域请求
- 表单验证:前端表单验证规则
- UI组件库:使用Element Plus提供专业的UI体验
这个项目虽然规模不大,但包含了企业级项目的核心要素,可以作为实际项目的基础模板进行扩展。