第二部分:C语言CGI程序开发
2.1 开发环境准备
Linux环境:
# 安装gcc编译器
sudo apt install gcc make
# 安装MySQL开发库
sudo apt install libmysqlclient-dev
# 安装Apache Web服务器(或Nginx)
sudo apt install apache2
sudo a2enmod cgi # 启用CGI模块
sudo systemctl restart apache2
编译命令模板:
# 编译CGI程序
gcc -o student.cgi student.c -lmysqlclient -I/usr/include/mysql
# 将编译好的程序放到CGI目录
sudo cp student.cgi /usr/lib/cgi-bin/
sudo chmod 755 /usr/lib/cgi-bin/student.cgi
2.2 公共头文件
// common.h - 公共头文件,包含数据库连接、宏定义等
#ifndef COMMON_H
#define COMMON_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mysql/mysql.h>
// ========== 数据库配置宏 ==========
#define DB_HOST "localhost"
#define DB_USER "root"
// 安全提示:生产环境应从配置文件读取,不应硬编码
#define DB_PASS "123456"
#define DB_NAME "student_db"
// ========== HTTP响应头宏 ==========
// HTML内容类型响应头
#define HTTP_HTML "Content-Type: text/html; charset=utf-8\n\n"
// JSON内容类型响应头(用于API风格)
#define HTTP_JSON "Content-Type: application/json; charset=utf-8\n\n"
// 重定向响应头
#define HTTP_REDIRECT(url) "Location: " url "\n\n"
// ========== 方法宏 ==========
// 获取环境变量值的辅助宏
#define GET_ENV(name) getenv(name) ? getenv(name) : ""
// ========== 函数声明 ==========
MYSQL* db_connect();
void db_close(MYSQL *conn);
void url_decode(char *src, char *dest);
void html_escape(const char *src, char *dest);
char* get_post_data();
#endif
2.3 数据库操作函数
// db.c - 数据库操作函数实现
#include "common.h"
/**
* 数据库连接函数
*
* 功能:建立与MySQL数据库的连接
* 返回:成功返回MYSQL*指针,失败返回NULL并输出错误信息
*
* 说明:此函数会初始化连接句柄、设置字符集、进行身份认证
*/
MYSQL* db_connect() {
MYSQL *conn = mysql_init(NULL);
if (conn == NULL) {
fprintf(stderr, "mysql_init() failed\n");
return NULL;
}
// 设置字符集为utf8mb4,确保中文正确存储和显示
mysql_options(conn, MYSQL_SET_CHARSET_NAME, "utf8mb4");
// 连接数据库(主机、用户、密码、数据库名、端口、socket、标志)
if (mysql_real_connect(conn, DB_HOST, DB_USER, DB_PASS,
DB_NAME, 0, NULL, 0) == NULL) {
fprintf(stderr, "mysql_real_connect() failed: %s\n", mysql_error(conn));
mysql_close(conn);
return NULL;
}
return conn;
}
/**
* 数据库关闭函数
*
* 功能:释放数据库连接资源
*/
void db_close(MYSQL *conn) {
if (conn) {
mysql_close(conn);
}
}
/**
* 执行SQL查询
*
* 功能:执行给定的SQL语句
* 返回:0成功,-1失败
*/
int db_query(MYSQL *conn, const char *sql) {
if (mysql_query(conn, sql)) {
fprintf(stderr, "Query failed: %s\nSQL: %s\n", mysql_error(conn), sql);
return -1;
}
return 0;
}
2.4 URL解码与HTML转义函数
// utils.c - 工具函数实现
#include "common.h"
#include <ctype.h>
/**
* URL解码函数
*
* 功能:将URL编码的字符串解码为原始字符串
* 例如:%E5%BC%A0%E4%B8%89 → 张三
*
* 原理:URL编码中,%后跟两位十六进制数表示一个字节
*/
void url_decode(char *src, char *dest) {
char *p = src;
char *q = dest;
while (*p) {
if (*p == '%' && isxdigit(*(p+1)) && isxdigit(*(p+2))) {
// 解析十六进制数转换为字符
char hex[3] = {*(p+1), *(p+2), '\0'};
*q++ = (char)strtol(hex, NULL, 16);
p += 3;
} else if (*p == '+') {
// '+'在URL编码中代表空格
*q++ = ' ';
p++;
} else {
*q++ = *p++;
}
}
*q = '\0';
}
/**
* HTML转义函数
*
* 功能:将HTML特殊字符转换为实体,防止XSS攻击
* 例如:< → < > → > & → & " → "
*/
void html_escape(const char *src, char *dest) {
while (*src) {
switch (*src) {
case '<': strcpy(dest, "<"); dest += 4; break;
case '>': strcpy(dest, ">"); dest += 4; break;
case '&': strcpy(dest, "&"); dest += 5; break;
case '"': strcpy(dest, """); dest += 6; break;
case '\'': strcpy(dest, "'"); dest += 5; break;
default: *dest++ = *src; break;
}
src++;
}
*dest = '\0';
}
/**
* 获取POST数据
*
* 功能:从标准输入读取POST请求的数据
*
* 说明:环境变量CONTENT_LENGTH指示POST数据长度
* 需要读取对应长度的数据
*/
char* get_post_data() {
char *post_data = NULL;
char *content_length_str = getenv("CONTENT_LENGTH");
if (content_length_str != NULL) {
int content_length = atoi(content_length_str);
if (content_length > 0) {
post_data = (char*)malloc(content_length + 1);
if (post_data) {
fread(post_data, 1, content_length, stdin);
post_data[content_length] = '\0';
}
}
}
return post_data;
}
/**
* 解析查询字符串
*
* 功能:将key1=value1&key2=value2格式的字符串解析为键值对
*/
void parse_query_string(char *query_string, char *params[][2], int *count) {
char *token;
char *saveptr;
*count = 0;
if (query_string == NULL || *query_string == '\0') return;
token = strtok_r(query_string, "&", &saveptr);
while (token != NULL && *count < 50) {
char *eq_pos = strchr(token, '=');
if (eq_pos != NULL) {
*eq_pos = '\0';
strncpy(params[*count][0], token, 99);
url_decode(eq_pos + 1, params[*count][1]);
(*count)++;
}
token = strtok_r(NULL, "&", &saveptr);
}
}
2.5 HTML模板函数
// template.c - HTML页面生成函数
#include "common.h"
/**
* 输出HTML页面头部
*
* 功能:生成HTML文档的开始部分,包括<!DOCTYPE>、<html>、<head>和
* 引用的CSS/JS文件
*/
void print_html_header() {
printf("<!DOCTYPE html>\n");
printf("<html lang=\"zh-CN\">\n");
printf("<head>\n");
printf(" <meta charset=\"UTF-8\">\n");
printf(" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n");
printf(" <title>学生管理系统</title>\n");
printf(" <style>\n");
printf(" * { margin: 0; padding: 0; box-sizing: border-box; }\n");
printf(" body { font-family: 'Microsoft YaHei', sans-serif; background: #f0f2f5; }\n");
printf(" .header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; }\n");
printf(" .container { max-width: 1200px; margin: 20px auto; padding: 0 20px; }\n");
printf(" .card { background: white; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.1); padding: 20px; margin-bottom: 20px; }\n");
printf(" .form-group { margin-bottom: 15px; }\n");
printf(" .form-group label { display: block; margin-bottom: 5px; font-weight: bold; }\n");
printf(" .form-group input, .form-group select { width: 100%; padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; }\n");
printf(" button { background: #667eea; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; }\n");
printf(" button:hover { background: #5a67d8; }\n");
printf(" table { width: 100%; border-collapse: collapse; }\n");
printf(" th, td { padding: 12px; text-align: left; border-bottom: 1px solid #eee; }\n");
printf(" th { background: #f5f5f5; }\n");
printf(" .btn-edit { background: #28a745; margin-right: 5px; }\n");
printf(" .btn-delete { background: #dc3545; }\n");
printf(" .btn-small { padding: 4px 10px; font-size: 12px; }\n");
printf(" .filter-bar { margin-bottom: 20px; display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }\n");
printf(" </style>\n");
printf("</head>\n");
printf("<body>\n");
}
/**
* 输出页面头部导航
*/
void print_navbar() {
printf("<div class=\"header\">\n");
printf(" <div class=\"container\">\n");
printf(" <h1>📚 学生管理系统</h1>\n");
printf(" <p>欢迎使用学生管理系统,管理学生信息、成绩和班级</p>\n");
printf(" </div>\n");
printf("</div>\n");
}
/**
* 输出HTML页面尾部
*/
void print_html_footer() {
printf("</body>\n");
printf("</html>\n");
}
/**
* 输出错误页面
*/
void print_error_page(const char *message) {
print_html_header();
print_navbar();
printf("<div class=\"container\">\n");
printf(" <div class=\"card\" style=\"text-align:center; color:#dc3545;\">\n");
printf(" <h2>操作失败</h2>\n");
printf(" <p>%s</p>\n", message);
printf(" <a href=\"/cgi-bin/student.cgi?action=list\"><button>返回首页</button></a>\n");
printf(" </div>\n");
printf("</div>\n");
print_html_footer();
}
2.6 学生管理核心逻辑
// student.c - 学生管理CGI主程序
#include "common.h"
#include <stdlib.h>
#include <string.h>
// 函数声明
void handle_list(MYSQL *conn, char *class_id);
void handle_detail(MYSQL *conn, char *id);
void handle_add(MYSQL *conn);
void handle_edit(MYSQL *conn);
void handle_delete(MYSQL *conn, char *id);
void show_form_add();
void show_form_edit(MYSQL *conn, int id);
void render_student_table(MYSQL_RES *res);
/**
* 主函数 - CGI程序入口
*
* 执行流程:
* 1. 获取环境变量REQUEST_METHOD,判断请求类型(GET/POST)
* 2. 解析查询参数(action、id等)
* 3. 连接数据库
* 4. 根据action参数调用对应的处理函数
* 5. 输出HTTP响应头(Content-Type)
* 6. 执行具体业务逻辑并输出HTML内容
*/
int main() {
MYSQL *conn;
char *method = GET_ENV("REQUEST_METHOD");
char *query_string = GET_ENV("QUERY_STRING");
char action[50] = "list";
char id[10] = "";
char class_id[10] = "";
// 解析查询参数
if (query_string != NULL && strlen(query_string) > 0) {
char *token;
char query_copy[512];
strncpy(query_copy, query_string, 511);
query_copy[511] = '\0';
token = strtok(query_copy, "&");
while (token != NULL) {
if (strncmp(token, "action=", 7) == 0) {
strncpy(action, token + 7, 49);
action[49] = '\0';
} else if (strncmp(token, "id=", 3) == 0) {
strncpy(id, token + 3, 9);
id[9] = '\0';
} else if (strncmp(token, "class_id=", 9) == 0) {
strncpy(class_id, token + 9, 9);
class_id[9] = '\0';
}
token = strtok(NULL, "&");
}
}
// 连接数据库
conn = db_connect();
if (conn == NULL) {
printf(HTTP_HTML);
printf("<html><body><h1>数据库连接失败</h1></body></html>\n");
return 1;
}
// 输出HTTP响应头
printf(HTTP_HTML);
// 根据action分发请求
if (strcmp(action, "list") == 0) {
handle_list(conn, class_id);
} else if (strcmp(action, "detail") == 0) {
handle_detail(conn, id);
} else if (strcmp(action, "add") == 0) {
if (strcmp(method, "POST") == 0) {
handle_add(conn);
} else {
show_form_add();
}
} else if (strcmp(action, "edit") == 0) {
if (strcmp(method, "POST") == 0) {
handle_edit(conn);
} else if (strlen(id) > 0) {
show_form_edit(conn, atoi(id));
}
} else if (strcmp(action, "delete") == 0 && strlen(id) > 0) {
handle_delete(conn, id);
} else {
handle_list(conn, NULL);
}
db_close(conn);
return 0;
}
/**
* 处理学生列表查询
*
* 功能:查询学生信息(可选班级筛选),并渲染为表格
*/
void handle_list(MYSQL *conn, char *class_id) {
char sql[1024];
MYSQL_RES *res;
MYSQL_ROW row;
// 构建SQL查询(支持班级筛选)
if (class_id != NULL && strlen(class_id) > 0) {
snprintf(sql, sizeof(sql),
"SELECT s.id, s.student_no, s.name, s.gender, "
"c.name as class_name, s.phone "
"FROM students s "
"LEFT JOIN classes c ON s.class_id = c.id "
"WHERE s.class_id = %s "
"ORDER BY s.id", class_id);
} else {
snprintf(sql, sizeof(sql),
"SELECT s.id, s.student_no, s.name, s.gender, "
"c.name as class_name, s.phone "
"FROM students s "
"LEFT JOIN classes c ON s.class_id = c.id "
"ORDER BY s.id");
}
if (db_query(conn, sql) != 0) {
print_error_page("查询数据失败");
return;
}
res = mysql_store_result(conn);
print_html_header();
print_navbar();
// 输出筛选栏
printf("<div class=\"container\">\n");
printf(" <div class=\"filter-bar\">\n");
printf(" <form method=\"GET\" action=\"/cgi-bin/student.cgi\" style=\"display:flex; gap:10px;\">\n");
printf(" <input type=\"hidden\" name=\"action\" value=\"list\">\n");
printf(" <select name=\"class_id\">\n");
printf(" <option value=\"\">全部班级</option>\n");
// 查询班级列表用于筛选下拉框
MYSQL_RES *class_res;
MYSQL_ROW class_row;
if (db_query(conn, "SELECT id, name FROM classes") == 0) {
class_res = mysql_store_result(conn);
while ((class_row = mysql_fetch_row(class_res))) {
printf(" <option value=\"%s\"", class_row[0]);
if (class_id != NULL && strcmp(class_id, class_row[0]) == 0) {
printf(" selected");
}
printf(">%s</option>\n", class_row[1]);
}
mysql_free_result(class_res);
}
printf(" </select>\n");
printf(" <button type=\"submit\">筛选</button>\n");
printf(" <a href=\"/cgi-bin/student.cgi?action=add\">\n");
printf(" <button type=\"button\" style=\"background:#28a745;\">+ 添加学生</button>\n");
printf(" </a>\n");
printf(" </form>\n");
printf(" </div>\n");
// 输出学生表格
printf(" <div class=\"card\">\n");
printf(" <h2>学生列表</h2>\n");
printf(" <table>\n");
printf(" <thead>\n");
printf(" <tr><th>学号</th><th>姓名</th><th>性别</th><th>班级</th><th>电话</th><th>操作</th></tr>\n");
printf(" </thead>\n");
printf(" <tbody>\n");
while ((row = mysql_fetch_row(res))) {
printf(" <tr>");
printf("<td>%s</td>", row[1] ? row[1] : "");
printf("<td>%s</td>", row[2] ? row[2] : "");
printf("<td>%s</td>", row[3] ? row[3] : "");
printf("<td>%s</td>", row[4] ? row[4] : "");
printf("<td>%s</td>", row[5] ? row[5] : "");
printf("<td>");
printf("<a href=\"/cgi-bin/student.cgi?action=detail&id=%s\">", row[0]);
printf("<button class=\"btn-small\">查看</button></a>\n");
printf("<a href=\"/cgi-bin/student.cgi?action=edit&id=%s\">", row[0]);
printf("<button class=\"btn-small btn-edit\">编辑</button></a>\n");
printf("<a href=\"/cgi-bin/student.cgi?action=delete&id=%s\" onclick=\"return confirm('确定删除?')\">", row[0]);
printf("<button class=\"btn-small btn-delete\">删除</button></a>\n");
printf("</td>");
printf("</tr>\n");
}
printf(" </tbody>\n");
printf(" </table>\n");
printf(" </div>\n");
printf("</div>\n");
mysql_free_result(res);
print_html_footer();
}
/**
* 处理学生详情查看
*
* 功能:根据学生ID查询详细信息,包括基本信息和成绩
*/
void handle_detail(MYSQL *conn, char *id) {
char sql[1024];
MYSQL_RES *res;
MYSQL_ROW row;
// 查询学生基本信息
snprintf(sql, sizeof(sql),
"SELECT s.student_no, s.name, s.gender, s.birth_date, "
"c.name as class_name, s.phone, s.address "
"FROM students s "
"LEFT JOIN classes c ON s.class_id = c.id "
"WHERE s.id = %s", id);
if (db_query(conn, sql) != 0) {
print_error_page("查询学生信息失败");
return;
}
res = mysql_store_result(conn);
row = mysql_fetch_row(res);
if (row == NULL) {
mysql_free_result(res);
print_error_page("学生不存在");
return;
}
print_html_header();
print_navbar();
printf("<div class=\"container\">\n");
printf(" <div class=\"card\">\n");
printf(" <h2>学生详细信息</h2>\n");
printf(" <table style=\"width:100%;\">\n");
printf(" <tr><th style=\"width:120px;\">学号:</th><td>%s</td></tr>\n", row[0] ? row[0] : "");
printf(" <tr><th>姓名:</th><td>%s</td></tr>\n", row[1] ? row[1] : "");
printf(" <tr><th>性别:</th><td>%s</td></tr>\n", row[2] ? row[2] : "");
printf(" <tr><th>出生日期:</th><td>%s</td></tr>\n", row[3] ? row[3] : "");
printf(" <tr><th>班级:</th><td>%s</td></tr>\n", row[4] ? row[4] : "");
printf(" <tr><th>家长电话:</th><td>%s</td></tr>\n", row[5] ? row[5] : "");
printf(" <tr><th>家庭地址:</th><td>%s</td></tr>\n", row[6] ? row[6] : "");
printf(" </table>\n");
printf(" <div style=\"margin-top:20px;\">\n");
printf(" <a href=\"/cgi-bin/student.cgi?action=edit&id=%s\"><button>编辑信息</button></a>\n", id);
printf(" <a href=\"/cgi-bin/student.cgi?action=list\"><button>返回列表</button></a>\n");
printf(" </div>\n");
printf(" </div>\n");
mysql_free_result(res);
// 查询成绩信息
snprintf(sql, sizeof(sql),
"SELECT subject, score, exam_date, semester FROM scores WHERE student_id = %s", id);
if (db_query(conn, sql) == 0) {
res = mysql_store_result(conn);
int score_count = mysql_num_rows(res);
if (score_count > 0) {
printf(" <div class=\"card\">\n");
printf(" <h2>考试成绩</h2>\n");
printf(" <table>\n");
printf(" <thead><tr><th>科目</th><th>分数</th><th>考试日期</th><th>学期</th></tr></thead>\n");
printf(" <tbody>\n");
while ((row = mysql_fetch_row(res))) {
printf(" <tr>");
printf("<td>%s</td><td>%s</td><td>%s</td><td>%s</td>",
row[0] ? row[0] : "", row[1] ? row[1] : "",
row[2] ? row[2] : "", row[3] ? row[3] : "");
printf("</tr>\n");
}
printf(" </tbody>\n");
printf(" </table>\n");
printf(" </div>\n");
}
mysql_free_result(res);
}
printf("</div>\n");
print_html_footer();
}
/**
* 添加学生表单页面
*
* 功能:显示添加学生的HTML表单
*/
void show_form_add() {
MYSQL *conn = db_connect();
print_html_header();
print_navbar();
printf("<div class=\"container\">\n");
printf(" <div class=\"card\">\n");
printf(" <h2>添加学生</h2>\n");
printf(" <form method=\"POST\" action=\"/cgi-bin/student.cgi?action=add\">\n");
printf(" <div class=\"form-group\">\n");
printf(" <label>学号:</label>\n");
printf(" <input type=\"text\" name=\"student_no\" required>\n");
printf(" </div>\n");
printf(" <div class=\"form-group\">\n");
printf(" <label>姓名:</label>\n");
printf(" <input type=\"text\" name=\"name\" required>\n");
printf(" </div>\n");
printf(" <div class=\"form-group\">\n");
printf(" <label>性别:</label>\n");
printf(" <select name=\"gender\">\n");
printf(" <option value=\"男\">男</option>\n");
printf(" <option value=\"女\">女</option>\n");
printf(" </select>\n");
printf(" </div>\n");
printf(" <div class=\"form-group\">\n");
printf(" <label>出生日期:</label>\n");
printf(" <input type=\"date\" name=\"birth_date\">\n");
printf(" </div>\n");
printf(" <div class=\"form-group\">\n");
printf(" <label>班级:</label>\n");
printf(" <select name=\"class_id\">\n");
// 查询班级列表
if (conn && db_query(conn, "SELECT id, name FROM classes") == 0) {
MYSQL_RES *res = mysql_store_result(conn);
MYSQL_ROW row;
while ((row = mysql_fetch_row(res))) {
printf(" <option value=\"%s\">%s</option>\n", row[0], row[1]);
}
mysql_free_result(res);
}
printf(" </select>\n");
printf(" </div>\n");
printf(" <div class=\"form-group\">\n");
printf(" <label>家长电话:</label>\n");
printf(" <input type=\"text\" name=\"phone\">\n");
printf(" </div>\n");
printf(" <div class=\"form-group\">\n");
printf(" <label>家庭地址:</label>\n");
printf(" <input type=\"text\" name=\"address\">\n");
printf(" </div>\n");
printf(" <button type=\"submit\">保存</button>\n");
printf(" <a href=\"/cgi-bin/student.cgi?action=list\"><button type=\"button\">取消</button></a>\n");
printf(" </form>\n");
printf(" </div>\n");
printf("</div>\n");
if (conn) db_close(conn);
print_html_footer();
}
/**
* 处理添加学生请求
*
* 功能:接收POST数据,验证并插入数据库
*/
void handle_add(MYSQL *conn) {
char *post_data = get_post_data();
char params[30][2][100] = {0};
int param_count = 0;
char sql[2048];
char escaped_name[200], escaped_no[200], escaped_phone[200], escaped_addr[200];
if (post_data == NULL) {
print_error_page("未收到表单数据");
free(post_data);
return;
}
// 解析POST数据
parse_query_string(post_data, params, ¶m_count);
// 提取字段值
char student_no[50] = "";
char name[50] = "";
char gender[10] = "";
char birth_date[20] = "";
char class_id[10] = "";
char phone[50] = "";
char address[200] = "";
for (int i = 0; i < param_count; i++) {
if (strcmp(params[i][0], "student_no") == 0)
strcpy(student_no, params[i][1]);
else if (strcmp(params[i][0], "name") == 0)
strcpy(name, params[i][1]);
else if (strcmp(params[i][0], "gender") == 0)
strcpy(gender, params[i][1]);
else if (strcmp(params[i][0], "birth_date") == 0)
strcpy(birth_date, params[i][1]);
else if (strcmp(params[i][0], "class_id") == 0)
strcpy(class_id, params[i][1]);
else if (strcmp(params[i][0], "phone") == 0)
strcpy(phone, params[i][1]);
else if (strcmp(params[i][0], "address") == 0)
strcpy(address, params[i][1]);
}
// 验证必填字段
if (strlen(student_no) == 0 || strlen(name) == 0 || strlen(gender) == 0 || strlen(class_id) == 0) {
print_error_page("请填写必填字段");
free(post_data);
return;
}
// 转义特殊字符防止SQL注入
mysql_real_escape_string(conn, escaped_no, student_no, strlen(student_no));
mysql_real_escape_string(conn, escaped_name, name, strlen(name));
mysql_real_escape_string(conn, escaped_phone, phone, strlen(phone));
mysql_real_escape_string(conn, escaped_addr, address, strlen(address));
// 构建INSERT语句
snprintf(sql, sizeof(sql),
"INSERT INTO students (student_no, name, gender, birth_date, class_id, phone, address) "
"VALUES ('%s', '%s', '%s', %s, %s, '%s', '%s')",
escaped_no, escaped_name, gender,
strlen(birth_date) > 0 ? birth_date : "NULL",
class_id, escaped_phone, escaped_addr);
if (db_query(conn, sql) != 0) {
print_error_page("添加失败,学号可能重复");
} else {
// 添加成功,重定向到列表页
printf("Location: /cgi-bin/student.cgi?action=list\n\n");
}
free(post_data);
}