第三部分:前端HTML页面
3.1 主页面导航
由于CGI程序动态生成HTML内容,前端样式已经在template.c中以内联CSS方式实现。为了更好的开发体验,可以使用独立的CSS文件。
3.2 静态页面(可选)
如果希望将前端与后端分离,可以创建独立的HTML文件并通过AJAX调用API。以下是前端页面的示例:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>学生管理系统</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Microsoft YaHei', sans-serif; background: #f0f2f5; }
.header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; }
.container { max-width: 1200px; margin: 20px auto; padding: 0 20px; }
.card { background: white; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.1); padding: 20px; margin-bottom: 20px; }
.form-group { margin-bottom: 15px; }
.form-group label { display: block; margin-bottom: 5px; font-weight: bold; }
.form-group input, .form-group select { width: 100%; padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; }
button { background: #667eea; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; }
button:hover { background: #5a67d8; }
table { width: 100%; border-collapse: collapse; }
th, td { padding: 12px; text-align: left; border-bottom: 1px solid #eee; }
th { background: #f5f5f5; }
.btn-edit { background: #28a745; margin-right: 5px; }
.btn-delete { background: #dc3545; }
.btn-small { padding: 4px 10px; font-size: 12px; }
.filter-bar { margin-bottom: 20px; display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); justify-content: center; align-items: center; z-index: 1000; }
.modal-content { background: white; border-radius: 8px; padding: 20px; width: 500px; max-width: 90%; }
.close { float: right; cursor: pointer; font-size: 24px; }
</style>
</head>
<body>
<div class="header">
<div class="container">
<h1>📚 学生管理系统</h1>
<p>管理学生信息、成绩和班级</p>
</div>
</div>
<div class="container" id="app">
<div class="filter-bar">
<select id="classFilter">
<option value="">全部班级</option>
</select>
<button onclick="loadStudents()">筛选</button>
<button onclick="showAddModal()" style="background:#28a745;">+ 添加学生</button>
</div>
<div class="card">
<h2>学生列表</h2>
<div id="studentTable"></div>
</div>
</div>
<!-- 添加/编辑模态框 -->
<div id="studentModal" class="modal">
<div class="modal-content">
<span class="close" onclick="closeModal()">×</span>
<h2 id="modalTitle">添加学生</h2>
<form id="studentForm">
<input type="hidden" id="studentId" name="id">
<div class="form-group">
<label>学号:</label>
<input type="text" id="studentNo" name="student_no" required>
</div>
<div class="form-group">
<label>姓名:</label>
<input type="text" id="studentName" name="name" required>
</div>
<div class="form-group">
<label>性别:</label>
<select id="studentGender" name="gender">
<option value="男">男</option>
<option value="女">女</option>
</select>
</div>
<div class="form-group">
<label>出生日期:</label>
<input type="date" id="studentBirth" name="birth_date">
</div>
<div class="form-group">
<label>班级:</label>
<select id="studentClass" name="class_id"></select>
</div>
<div class="form-group">
<label>家长电话:</label>
<input type="text" id="studentPhone" name="phone">
</div>
<div class="form-group">
<label>家庭地址:</label>
<input type="text" id="studentAddress" name="address">
</div>
<button type="button" onclick="saveStudent()">保存</button>
<button type="button" onclick="closeModal()">取消</button>
</form>
</div>
</div>
<script>
// 加载班级列表
function loadClasses() {
fetch('/cgi-bin/class.cgi?action=list')
.then(response => response.json())
.then(data => {
const classSelect = document.getElementById('classFilter');
const classModal = document.getElementById('studentClass');
data.data.forEach(cls => {
const option = document.createElement('option');
option.value = cls.id;
option.textContent = cls.name;
classSelect.appendChild(option.cloneNode(true));
classModal.appendChild(option);
});
});
}
// 加载学生列表
function loadStudents() {
const classId = document.getElementById('classFilter').value;
let url = '/cgi-bin/student.cgi?action=list&format=json';
if (classId) url += '&class_id=' + classId;
fetch(url)
.then(response => response.json())
.then(data => {
renderStudentTable(data.data);
});
}
// 渲染学生表格
function renderStudentTable(students) {
const container = document.getElementById('studentTable');
if (!students || students.length === 0) {
container.innerHTML = '<p style="text-align:center; padding:40px;">暂无数据</p>';
return;
}
let html = '<table>';
html += '<thead><tr><th>学号</th><th>姓名</th><th>性别</th><th>班级</th><th>电话</th><th>操作</th></tr></thead>';
html += '<tbody>';
students.forEach(s => {
html += '<tr>';
html += `<td>${s.student_no}</td>`;
html += `<td>${s.name}</td>`;
html += `<td>${s.gender}</td>`;
html += `<td>${s.class_name}</td>`;
html += `<td>${s.phone || ''}</td>`;
html += `<td>
<button class="btn-small" onclick="viewStudent(${s.id})">查看</button>
<button class="btn-small btn-edit" onclick="editStudent(${s.id})">编辑</button>
<button class="btn-small btn-delete" onclick="deleteStudent(${s.id})">删除</button>
</td>`;
html += '</tr>';
});
html += '</tbody></table>';
container.innerHTML = html;
}
// 显示添加模态框
function showAddModal() {
document.getElementById('modalTitle').innerText = '添加学生';
document.getElementById('studentForm').reset();
document.getElementById('studentId').value = '';
document.getElementById('studentModal').style.display = 'flex';
}
// 编辑学生
function editStudent(id) {
fetch(`/cgi-bin/student.cgi?action=detail&id=${id}&format=json`)
.then(response => response.json())
.then(data => {
const s = data.data;
document.getElementById('modalTitle').innerText = '编辑学生';
document.getElementById('studentId').value = s.id;
document.getElementById('studentNo').value = s.student_no;
document.getElementById('studentName').value = s.name;
document.getElementById('studentGender').value = s.gender;
document.getElementById('studentBirth').value = s.birth_date || '';
document.getElementById('studentClass').value = s.class_id;
document.getElementById('studentPhone').value = s.phone || '';
document.getElementById('studentAddress').value = s.address || '';
document.getElementById('studentModal').style.display = 'flex';
});
}
// 保存学生
function saveStudent() {
const id = document.getElementById('studentId').value;
const data = {
student_no: document.getElementById('studentNo').value,
name: document.getElementById('studentName').value,
gender: document.getElementById('studentGender').value,
birth_date: document.getElementById('studentBirth').value,
class_id: document.getElementById('studentClass').value,
phone: document.getElementById('studentPhone').value,
address: document.getElementById('studentAddress').value
};
const url = id ? '/cgi-bin/student.cgi?action=edit' : '/cgi-bin/student.cgi?action=add';
fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams(data)
}).then(() => {
closeModal();
loadStudents();
alert(id ? '修改成功' : '添加成功');
});
}
// 删除学生
function deleteStudent(id) {
if (confirm('确定要删除该学生吗?')) {
fetch(`/cgi-bin/student.cgi?action=delete&id=${id}`)
.then(() => {
loadStudents();
alert('删除成功');
});
}
}
// 关闭模态框
function closeModal() {
document.getElementById('studentModal').style.display = 'none';
}
// 页面加载
window.onload = () => {
loadClasses();
loadStudents();
};
</script>
</body>
</html>
第四部分:编译与部署
4.1 开发环境详解
4.1.1 Linux开发环境搭建(Ubuntu/Debian)
在开始编译之前,我们需要搭建完整的开发环境。以下是详细的安装步骤:
步骤1:安装GCC编译器套件
# 更新软件包列表
sudo apt update
# 安装build-essential包(包含gcc、g++、make等编译工具)
sudo apt install build-essential -y
# 验证GCC安装
gcc --version
# 预期输出:gcc (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0
步骤2:安装MySQL开发库
MySQL开发库提供了C语言连接MySQL所需的头文件和库文件:
# 安装MySQL客户端开发库
sudo apt install libmysqlclient-dev -y
# 安装MySQL服务器(如果需要本地数据库)
sudo apt install mysql-server -y
# 验证MySQL头文件位置
ls /usr/include/mysql/
# 应该看到mysql.h、mysql_version.h等文件
步骤3:安装Apache Web服务器
# 安装Apache2
sudo apt install apache2 -y
# 启用CGI模块
sudo a2enmod cgi
sudo a2enmod rewrite
# 重启Apache服务
sudo systemctl restart apache2
# 检查Apache状态
sudo systemctl status apache2
4.1.2 项目目录结构规划
在开始编写代码之前,规划好项目目录结构:
student-management-system/
├── src/ # 源代码目录
│ ├── common.h # 公共头文件
│ ├── db.c # 数据库操作函数
│ ├── utils.c # 工具函数
│ ├── template.c # HTML模板函数
│ └── student.c # 主程序
├── include/ # 头文件目录(可选)
├── lib/ # 编译生成的库文件(可选)
├── build/ # 编译输出目录
├── cgi-bin/ # CGI程序部署目录
├── html/ # 静态HTML文件目录
├── config/ # 配置文件目录
│ └── database.conf # 数据库配置文件
├── logs/ # 日志目录
├── Makefile # 编译配置文件
└── README.md # 项目说明文档
4.2 Makefile编写详解
Makefile是自动化编译的核心工具,它定义了编译规则和依赖关系。以下是完整的Makefile:
# ============================================================
# Makefile - 学生管理系统编译配置
# ============================================================
# 编译器设置
CC = gcc
CFLAGS = -Wall -g -O2 -I/usr/include/mysql
LDFLAGS = -lmysqlclient
# 目录设置
SRC_DIR = src
BUILD_DIR = build
CGI_DIR = /usr/lib/cgi-bin
# 源文件列表
SOURCES = $(SRC_DIR)/student.c \
$(SRC_DIR)/db.c \
$(SRC_DIR)/utils.c \
$(SRC_DIR)/template.c
# 目标文件列表
OBJECTS = $(BUILD_DIR)/student.o \
$(BUILD_DIR)/db.o \
$(BUILD_DIR)/utils.o \
$(BUILD_DIR)/template.o
# 最终生成的可执行文件
TARGET = $(BUILD_DIR)/student.cgi
# ============================================================
# 默认目标:编译主程序
# ============================================================
all: $(TARGET)
# 链接目标文件生成可执行文件
$(TARGET): $(OBJECTS)
@mkdir -p $(BUILD_DIR)
$(CC) $(OBJECTS) -o $(TARGET) $(LDFLAGS)
@echo "✓ 编译完成: $(TARGET)"
# 编译student.c
$(BUILD_DIR)/student.o: $(SRC_DIR)/student.c $(SRC_DIR)/common.h
@mkdir -p $(BUILD_DIR)
$(CC) $(CFLAGS) -c $< -o $@
# 编译db.c
$(BUILD_DIR)/db.o: $(SRC_DIR)/db.c $(SRC_DIR)/common.h
$(CC) $(CFLAGS) -c $< -o $@
# 编译utils.c
$(BUILD_DIR)/utils.o: $(SRC_DIR)/utils.c $(SRC_DIR)/common.h
$(CC) $(CFLAGS) -c $< -o $@
# 编译template.c
$(BUILD_DIR)/template.o: $(SRC_DIR)/template.c $(SRC_DIR)/common.h
$(CC) $(CFLAGS) -c $< -o $@
# ============================================================
# 安装:将编译好的程序部署到CGI目录
# ============================================================
install: $(TARGET)
sudo cp $(TARGET) $(CGI_DIR)/
sudo chmod 755 $(CGI_DIR)/student.cgi
@echo "✓ 部署完成: $(CGI_DIR)/student.cgi"
# ============================================================
# 清理:删除编译产生的文件
# ============================================================
clean:
rm -rf $(BUILD_DIR)/*.o $(TARGET)
@echo "✓ 清理完成"
# ============================================================
# 完全清理并重新编译
# ============================================================
rebuild: clean all
# ============================================================
# 运行测试
# ============================================================
test:
@echo "开始测试..."
@echo "请访问: http://localhost/cgi-bin/student.cgi?action=list"
# ============================================================
# 帮助信息
# ============================================================
help:
@echo "可用命令:"
@echo " make - 编译项目"
@echo " make install- 部署到CGI目录"
@echo " make clean - 清理编译文件"
@echo " make rebuild- 完全重新编译"
@echo " make test - 显示测试信息"
4.3 编译过程详解
4.3.1 编译前的准备工作
# 1. 创建项目目录结构
mkdir -p student-management-system/{src,build,html,config,logs}
cd student-management-system
# 2. 将所有源代码放入src目录
# 将前文的所有.c和.h文件保存到src目录
# 3. 创建Makefile
# 将上面的Makefile内容保存到项目根目录
# 4. 确保MySQL服务正在运行
sudo systemctl start mysql
sudo systemctl status mysql
4.3.2 执行编译
# 执行编译
make
# 编译过程输出示例:
# mkdir -p build
# gcc -Wall -g -O2 -I/usr/include/mysql -c src/student.c -o build/student.o
# gcc -Wall -g -O2 -I/usr/include/mysql -c src/db.c -o build/db.o
# gcc -Wall -g -O2 -I/usr/include/mysql -c src/utils.c -o build/utils.o
# gcc -Wall -g -O2 -I/usr/include/mysql -c src/template.c -o build/template.o
# gcc build/student.o build/db.o build/utils.o build/template.o -o build/student.cgi -lmysqlclient
# ✓ 编译完成: build/student.cgi
4.3.3 解决常见编译错误
错误1:mysql.h: No such file or directory
# 原因:MySQL开发库未安装
# 解决方案:
sudo apt install libmysqlclient-dev
# 验证头文件位置
find /usr -name "mysql.h" 2>/dev/null
错误2:undefined reference to `mysql_init'
# 原因:链接器找不到MySQL库
# 解决方案:检查Makefile中的LDFLAGS是否包含-lmysqlclient
# 或者手动指定库路径
gcc -o student.cgi *.o -L/usr/lib/x86_64-linux-gnu -lmysqlclient
错误3:collect2: error: ld returned 1 exit status
# 原因:多个源文件中有重复的main函数定义
# 解决方案:确保只有student.c中有main函数
# 其他.c文件中不应包含main函数
错误4:权限被拒绝
# 原因:没有写入build目录的权限
# 解决方案:
mkdir -p build
chmod 755 build
4.4 部署配置详解
4.4.1 CGI目录配置
Apache的CGI程序需要放置在特定目录并具有执行权限:
# 1. 部署CGI程序
sudo make install
# 或手动复制
sudo cp build/student.cgi /usr/lib/cgi-bin/
sudo chmod 755 /usr/lib/cgi-bin/student.cgi
# 2. 验证文件已正确放置
ls -la /usr/lib/cgi-bin/student.cgi
# 输出示例:-rwxr-xr-x 1 root root 98765 May 1 10:30 student.cgi
4.4.2 Apache虚拟主机配置
# /etc/apache2/sites-available/student.conf
<VirtualHost *:80>
ServerName student.local
DocumentRoot /var/www/html
# CGI配置
ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/
<Directory "/usr/lib/cgi-bin">
Options +ExecCGI
AddHandler cgi-script .cgi
Require all granted
</Directory>
# 静态文件目录
Alias /static /var/www/student/static
<Directory "/var/www/student/static">
Options Indexes FollowSymLinks
Require all granted
</Directory>
# 日志配置
ErrorLog ${APACHE_LOG_DIR}/student_error.log
CustomLog ${APACHE_LOG_DIR}/student_access.log combined
</VirtualHost>
4.4.3 启用站点配置
# 1. 启用虚拟主机
sudo a2ensite student.conf
# 2. 禁用默认站点(可选)
sudo a2dissite 000-default.conf
# 3. 检查配置语法
sudo apachectl configtest
# 预期输出:Syntax OK
# 4. 重启Apache
sudo systemctl restart apache2
4.4.4 数据库配置
# 1. 创建配置文件(避免硬编码密码)
cat > config/database.conf << 'EOF'
DB_HOST=localhost
DB_USER=root
DB_PASS=your_password
DB_NAME=student_db
EOF
# 2. 设置配置文件权限(仅所有者可读)
chmod 600 config/database.conf
# 3. 导入数据库表结构
mysql -u root -p < database.sql