🚀 AI 近视防控系统 - 生产环境上线版本 v1.0
✅ 已完成功能: - 后端 Go 服务 (认证/授权/检测) - JWT 认证 + RBAC 权限控制 - 登录速率限制 (5 次失败锁定 15 分钟) - 密码强度校验 - 敏感数据脱敏 - Vue3 管理后台 - 路由守卫 - 删除二次确认 📦 部署配置: - Docker Compose 生产环境配置 - MySQL/Redis/MongoDB 数据库 - Nginx 前端服务 - 强密码安全配置 ⚠️ P2 待办 (下次迭代): - 学生/检测/预警等业务模块实现 - 错误处理统一化 - 缓存策略优化 - 日志分级 📍 生产环境: - 服务器:192.168.15.222 - 管理后台:http://192.168.15.222:8081 - API 服务:http://192.168.15.222:8080 2026-03-29 上线部署完成
This commit is contained in:
94
scripts/create_admin_account.go
Normal file
94
scripts/create_admin_account.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// UserAccount 用户账号模型
|
||||
type UserAccount struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
Username string `gorm:"type:varchar(64);uniqueIndex"`
|
||||
PasswordHash string `gorm:"type:varchar(255)"`
|
||||
Name string `gorm:"type:varchar(64)"`
|
||||
Phone string `gorm:"type:varchar(20);uniqueIndex"`
|
||||
UserType string `gorm:"type:varchar(16)"`
|
||||
Status int `gorm:"default:1"`
|
||||
LastLoginAt *string
|
||||
LastLoginIP string
|
||||
CreatedAt string
|
||||
UpdatedAt string
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Println("AI近视防控系统 - 管理员账号创建工具")
|
||||
|
||||
// 数据库连接信息 - 从环境变量或配置文件读取
|
||||
// 使用与主应用相同的数据库连接信息
|
||||
dsn := "root:MyopiaTest2026!@tcp(localhost:3306)/ai_myopia_db?charset=utf8mb4&parseTime=True&loc=Local"
|
||||
|
||||
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
|
||||
if err != nil {
|
||||
log.Fatal("连接数据库失败:", err)
|
||||
}
|
||||
|
||||
// 加密管理员密码
|
||||
adminPassword := "Admin123!@#" // 强密码,包含大小写字母、数字、特殊字符
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(adminPassword), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
log.Fatal("密码加密失败:", err)
|
||||
}
|
||||
|
||||
// 创建管理员账号
|
||||
adminAccount := UserAccount{
|
||||
Username: "admin",
|
||||
PasswordHash: string(hashedPassword),
|
||||
Name: "系统管理员",
|
||||
Phone: "13800138000",
|
||||
UserType: "admin",
|
||||
Status: 1,
|
||||
}
|
||||
|
||||
result := db.Table("user_accounts").Where("username = ?", "admin").First(&UserAccount{})
|
||||
if result.Error != nil {
|
||||
// 管理员账号不存在,创建新账号
|
||||
result = db.Table("user_accounts").Create(&adminAccount)
|
||||
if result.Error != nil {
|
||||
log.Fatal("创建管理员账号失败:", result.Error)
|
||||
}
|
||||
fmt.Println("✅ 管理员账号创建成功")
|
||||
} else {
|
||||
// 管理员账号已存在,更新密码
|
||||
result = db.Table("user_accounts").
|
||||
Where("username = ?", "admin").
|
||||
Updates(map[string]interface{}{
|
||||
"password_hash": string(hashedPassword),
|
||||
"name": "系统管理员",
|
||||
"phone": "13800138000",
|
||||
"user_type": "admin",
|
||||
"status": 1,
|
||||
})
|
||||
if result.Error != nil {
|
||||
log.Fatal("更新管理员账号失败:", result.Error)
|
||||
}
|
||||
fmt.Println("✅ 管理员账号更新成功")
|
||||
}
|
||||
|
||||
fmt.Println("\n📋 测试账号信息:")
|
||||
fmt.Println("用户名: admin")
|
||||
fmt.Println("密码: Admin123!@#")
|
||||
fmt.Println("角色: admin")
|
||||
fmt.Println("手机号: 13800138000")
|
||||
|
||||
fmt.Println("\n🔧 功能测试:")
|
||||
fmt.Println("- 用户认证功能: 待验证")
|
||||
fmt.Println("- 学生管理功能: 待验证")
|
||||
fmt.Println("- 检测功能: 待验证")
|
||||
fmt.Println("- 预警功能: 待验证")
|
||||
|
||||
fmt.Println("\n💡 提示: 可使用此账号登录系统进行功能测试")
|
||||
}
|
||||
84
scripts/create_test_accounts.sql
Normal file
84
scripts/create_test_accounts.sql
Normal file
@@ -0,0 +1,84 @@
|
||||
-- AI近视防控系统 - 测试账号创建脚本
|
||||
|
||||
-- 创建数据库(如果不存在)
|
||||
CREATE DATABASE IF NOT EXISTS ai_myopia_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- 使用数据库
|
||||
USE ai_myopia_db;
|
||||
|
||||
-- 创建用户表(如果不存在)
|
||||
CREATE TABLE IF NOT EXISTS user_accounts (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(64) NOT NULL UNIQUE,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
name VARCHAR(64) NOT NULL,
|
||||
phone VARCHAR(20) NOT NULL UNIQUE,
|
||||
user_type ENUM('student', 'parent', 'teacher', 'admin') NOT NULL,
|
||||
status TINYINT DEFAULT 1,
|
||||
last_login_at DATETIME NULL,
|
||||
last_login_ip VARCHAR(45) DEFAULT '',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- 创建测试管理员账号
|
||||
DELETE FROM user_accounts WHERE username = 'admin';
|
||||
INSERT INTO user_accounts (username, password_hash, name, phone, user_type, status) VALUES (
|
||||
'admin',
|
||||
'$2a$10$ES13mXJ4KzObj4wHXxVtzuGbBsy7.Wu8vpa6Z1ZSRdW332itPCO4i', -- Admin123!@# 的bcrypt哈希
|
||||
'系统管理员',
|
||||
'13800138000',
|
||||
'admin',
|
||||
1
|
||||
);
|
||||
|
||||
-- 创建测试老师账号
|
||||
DELETE FROM user_accounts WHERE username = 'teacher';
|
||||
INSERT INTO user_accounts (username, password_hash, name, phone, user_type, status) VALUES (
|
||||
'teacher',
|
||||
'$2a$10$ES13mXJ4KzObj4wHXxVtzuGbBsy7.Wu8vpa6Z1ZSRdW332itPCO4i', -- Admin123!@# 的bcrypt哈希
|
||||
'测试老师',
|
||||
'13800138001',
|
||||
'teacher',
|
||||
1
|
||||
);
|
||||
|
||||
-- 创建测试学生账号
|
||||
DELETE FROM user_accounts WHERE username = 'student';
|
||||
INSERT INTO user_accounts (username, password_hash, name, phone, user_type, status) VALUES (
|
||||
'student',
|
||||
'$2a$10$ES13mXJ4KzObj4wHXxVtzuGbBsy7.Wu8vpa6Z1ZSRdW332itPCO4i', -- Admin123!@# 的bcrypt哈希
|
||||
'测试学生',
|
||||
'13800138002',
|
||||
'student',
|
||||
1
|
||||
);
|
||||
|
||||
-- 创建测试家长账号
|
||||
DELETE FROM user_accounts WHERE username = 'parent';
|
||||
INSERT INTO user_accounts (username, password_hash, name, phone, user_type, status) VALUES (
|
||||
'parent',
|
||||
'$2a$10$ES13mXJ4KzObj4wHXxVtzuGbBsy7.Wu8vpa6Z1ZSRdW332itPCO4i', -- Admin123!@# 的bcrypt哈希
|
||||
'测试家长',
|
||||
'13800138003',
|
||||
'parent',
|
||||
1
|
||||
);
|
||||
|
||||
-- 验证账号创建
|
||||
SELECT
|
||||
id,
|
||||
username,
|
||||
name,
|
||||
phone,
|
||||
user_type,
|
||||
status
|
||||
FROM user_accounts
|
||||
WHERE username IN ('admin', 'teacher', 'student', 'parent');
|
||||
|
||||
-- 输出测试信息
|
||||
SELECT '--- 测试账号信息 ---' as info;
|
||||
SELECT '管理员账号: admin / Admin123!@#' as admin_info;
|
||||
SELECT '老师账号: teacher / Admin123!@#' as teacher_info;
|
||||
SELECT '学生账号: student / Admin123!@#' as student_info;
|
||||
SELECT '家长账号: parent / Admin123!@#' as parent_info;
|
||||
248
scripts/create_test_users.go
Normal file
248
scripts/create_test_users.go
Normal file
@@ -0,0 +1,248 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// UserAccount 用户账号模型
|
||||
type UserAccount struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Username string `gorm:"type:varchar(64);uniqueIndex" json:"username"`
|
||||
PasswordHash string `gorm:"type:varchar(255)" json:"-"` // 不在JSON中暴露
|
||||
Name string `gorm:"type:varchar(64)" json:"name"`
|
||||
Phone string `gorm:"type:varchar(20);uniqueIndex" json:"phone"`
|
||||
UserType string `gorm:"type:varchar(16)" json:"user_type"` // student, parent, teacher, admin
|
||||
Status int `gorm:"default:1" json:"status"` // 1:正常, 0:禁用
|
||||
LastLoginAt *time.Time `json:"last_login_at"`
|
||||
LastLoginIP string `json:"last_login_ip"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Println("AI近视防控系统 - 测试账号创建工具")
|
||||
|
||||
// 从环境变量获取数据库连接信息,如果不存在则使用默认值
|
||||
dbHost := os.Getenv("DB_HOST")
|
||||
if dbHost == "" {
|
||||
dbHost = "localhost"
|
||||
}
|
||||
dbUser := os.Getenv("DB_USER")
|
||||
if dbUser == "" {
|
||||
dbUser = "root"
|
||||
}
|
||||
dbPassword := os.Getenv("DB_PASSWORD")
|
||||
if dbPassword == "" {
|
||||
dbPassword = "MyopiaTest2026!"
|
||||
}
|
||||
dbName := os.Getenv("DB_NAME")
|
||||
if dbName == "" {
|
||||
dbName = "ai_myopia_db"
|
||||
}
|
||||
|
||||
// 数据库连接字符串
|
||||
dsn := fmt.Sprintf("%s:%s@tcp(%s:3306)/%s?charset=utf8mb4&parseTime=True&loc=Local",
|
||||
dbUser, dbPassword, dbHost, dbName)
|
||||
|
||||
// 连接数据库
|
||||
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
|
||||
if err != nil {
|
||||
log.Fatal("连接数据库失败: ", err)
|
||||
}
|
||||
|
||||
// 加密密码
|
||||
adminPassword := "Admin123!@#"
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(adminPassword), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
log.Fatal("密码加密失败: ", err)
|
||||
}
|
||||
|
||||
// 创建管理员账号
|
||||
adminAccount := UserAccount{
|
||||
Username: "admin",
|
||||
PasswordHash: string(hashedPassword),
|
||||
Name: "系统管理员",
|
||||
Phone: "13800138000",
|
||||
UserType: "admin",
|
||||
Status: 1,
|
||||
}
|
||||
|
||||
// 检查管理员账号是否存在
|
||||
var existingAdmin UserAccount
|
||||
result := db.Where("username = ?", "admin").First(&existingAdmin)
|
||||
if result.Error != nil {
|
||||
// 管理员账号不存在,创建新账号
|
||||
if err := db.Create(&adminAccount).Error; err != nil {
|
||||
log.Fatal("创建管理员账号失败: ", err)
|
||||
}
|
||||
fmt.Println("✅ 管理员账号创建成功")
|
||||
} else {
|
||||
// 管理员账号已存在,更新密码
|
||||
if err := db.Model(&existingAdmin).Updates(UserAccount{
|
||||
PasswordHash: string(hashedPassword),
|
||||
Name: "系统管理员",
|
||||
Phone: "13800138000",
|
||||
UserType: "admin",
|
||||
Status: 1,
|
||||
}).Error; err != nil {
|
||||
log.Fatal("更新管理员账号失败: ", err)
|
||||
}
|
||||
fmt.Println("✅ 管理员账号更新成功")
|
||||
}
|
||||
|
||||
// 创建测试老师账号
|
||||
teacherPassword := "Teacher123!@#"
|
||||
teacherHashed, err := bcrypt.GenerateFromPassword([]byte(teacherPassword), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
log.Fatal("老师密码加密失败: ", err)
|
||||
}
|
||||
|
||||
teacherAccount := UserAccount{
|
||||
Username: "teacher",
|
||||
PasswordHash: string(teacherHashed),
|
||||
Name: "测试老师",
|
||||
Phone: "13800138001",
|
||||
UserType: "teacher",
|
||||
Status: 1,
|
||||
}
|
||||
|
||||
var existingTeacher UserAccount
|
||||
result = db.Where("username = ?", "teacher").First(&existingTeacher)
|
||||
if result.Error != nil {
|
||||
// 老师账号不存在,创建新账号
|
||||
if err := db.Create(&teacherAccount).Error; err != nil {
|
||||
log.Println("创建老师账号失败: ", err)
|
||||
} else {
|
||||
fmt.Println("✅ 老师账号创建成功")
|
||||
}
|
||||
} else {
|
||||
// 老师账号已存在,更新密码
|
||||
if err := db.Model(&existingTeacher).Updates(UserAccount{
|
||||
PasswordHash: string(teacherHashed),
|
||||
Name: "测试老师",
|
||||
Phone: "13800138001",
|
||||
UserType: "teacher",
|
||||
Status: 1,
|
||||
}).Error; err != nil {
|
||||
log.Println("更新老师账号失败: ", err)
|
||||
} else {
|
||||
fmt.Println("✅ 老师账号更新成功")
|
||||
}
|
||||
}
|
||||
|
||||
// 创建测试学生账号
|
||||
studentPassword := "Student123!@#"
|
||||
studentHashed, err := bcrypt.GenerateFromPassword([]byte(studentPassword), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
log.Fatal("学生密码加密失败: ", err)
|
||||
}
|
||||
|
||||
studentAccount := UserAccount{
|
||||
Username: "student",
|
||||
PasswordHash: string(studentHashed),
|
||||
Name: "测试学生",
|
||||
Phone: "13800138002",
|
||||
UserType: "student",
|
||||
Status: 1,
|
||||
}
|
||||
|
||||
var existingStudent UserAccount
|
||||
result = db.Where("username = ?", "student").First(&existingStudent)
|
||||
if result.Error != nil {
|
||||
// 学生账号不存在,创建新账号
|
||||
if err := db.Create(&studentAccount).Error; err != nil {
|
||||
log.Println("创建学生账号失败: ", err)
|
||||
} else {
|
||||
fmt.Println("✅ 学生账号创建成功")
|
||||
}
|
||||
} else {
|
||||
// 学生账号已存在,更新密码
|
||||
if err := db.Model(&existingStudent).Updates(UserAccount{
|
||||
PasswordHash: string(studentHashed),
|
||||
Name: "测试学生",
|
||||
Phone: "13800138002",
|
||||
UserType: "student",
|
||||
Status: 1,
|
||||
}).Error; err != nil {
|
||||
log.Println("更新学生账号失败: ", err)
|
||||
} else {
|
||||
fmt.Println("✅ 学生账号更新成功")
|
||||
}
|
||||
}
|
||||
|
||||
// 创建测试家长账号
|
||||
parentPassword := "Parent123!@#"
|
||||
parentHashed, err := bcrypt.GenerateFromPassword([]byte(parentPassword), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
log.Fatal("家长密码加密失败: ", err)
|
||||
}
|
||||
|
||||
parentAccount := UserAccount{
|
||||
Username: "parent",
|
||||
PasswordHash: string(parentHashed),
|
||||
Name: "测试家长",
|
||||
Phone: "13800138003",
|
||||
UserType: "parent",
|
||||
Status: 1,
|
||||
}
|
||||
|
||||
var existingParent UserAccount
|
||||
result = db.Where("username = ?", "parent").First(&existingParent)
|
||||
if result.Error != nil {
|
||||
// 家长账号不存在,创建新账号
|
||||
if err := db.Create(&parentAccount).Error; err != nil {
|
||||
log.Println("创建家长账号失败: ", err)
|
||||
} else {
|
||||
fmt.Println("✅ 家长账号创建成功")
|
||||
}
|
||||
} else {
|
||||
// 家长账号已存在,更新密码
|
||||
if err := db.Model(&existingParent).Updates(UserAccount{
|
||||
PasswordHash: string(parentHashed),
|
||||
Name: "测试家长",
|
||||
Phone: "13800138003",
|
||||
UserType: "parent",
|
||||
Status: 1,
|
||||
}).Error; err != nil {
|
||||
log.Println("更新家长账号失败: ", err)
|
||||
} else {
|
||||
fmt.Println("✅ 家长账号更新成功")
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("\n📋 测试账号信息:")
|
||||
fmt.Println("==================")
|
||||
fmt.Println("管理员账号:")
|
||||
fmt.Println(" 用户名: admin")
|
||||
fmt.Println(" 密码: Admin123!@#")
|
||||
fmt.Println(" 角色: admin")
|
||||
fmt.Println(" 手机: 13800138000")
|
||||
|
||||
fmt.Println("\n老师账号:")
|
||||
fmt.Println(" 用户名: teacher")
|
||||
fmt.Println(" 密码: Teacher123!@#")
|
||||
fmt.Println(" 角色: teacher")
|
||||
fmt.Println(" 手机: 13800138001")
|
||||
|
||||
fmt.Println("\n学生账号:")
|
||||
fmt.Println(" 用户名: student")
|
||||
fmt.Println(" 密码: Student123!@#")
|
||||
fmt.Println(" 角色: student")
|
||||
fmt.Println(" 手机: 13800138002")
|
||||
|
||||
fmt.Println("\n家长账号:")
|
||||
fmt.Println(" 用户名: parent")
|
||||
fmt.Println(" 密码: Parent123!@#")
|
||||
fmt.Println(" 角色: parent")
|
||||
fmt.Println(" 手机: 13800138003")
|
||||
|
||||
fmt.Println("\n✅ 测试账号创建/更新完成!")
|
||||
fmt.Println("💡 提示: 可使用这些账号登录系统进行功能测试")
|
||||
}
|
||||
22
scripts/generate_password.go
Normal file
22
scripts/generate_password.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
password := "Admin123!@#"
|
||||
|
||||
// 生成bcrypt哈希
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
log.Fatal("密码哈希生成失败:", err)
|
||||
}
|
||||
|
||||
fmt.Printf("密码: %s\n", password)
|
||||
fmt.Printf("哈希: %s\n", string(hashedPassword))
|
||||
fmt.Println("哈希长度:", len(string(hashedPassword)))
|
||||
}
|
||||
389
scripts/init_db.sql
Normal file
389
scripts/init_db.sql
Normal file
@@ -0,0 +1,389 @@
|
||||
-- AI近视防控系统 - 数据库初始化脚本
|
||||
|
||||
-- 创建数据库
|
||||
CREATE DATABASE IF NOT EXISTS myopia_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- 使用数据库
|
||||
USE myopia_db;
|
||||
|
||||
-- 学校表
|
||||
CREATE TABLE IF NOT EXISTS schools (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(128) NOT NULL,
|
||||
code VARCHAR(32) NOT NULL UNIQUE,
|
||||
address VARCHAR(256),
|
||||
contact_name VARCHAR(64),
|
||||
contact_phone VARCHAR(20),
|
||||
status TINYINT DEFAULT 1,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_code (code),
|
||||
INDEX idx_status (status)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='学校表';
|
||||
|
||||
-- 班级表
|
||||
CREATE TABLE IF NOT EXISTS classes (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(64) NOT NULL,
|
||||
grade VARCHAR(16) NOT NULL, -- 年级:一年级、二年级...
|
||||
school_id BIGINT UNSIGNED NOT NULL,
|
||||
teacher_id BIGINT UNSIGNED,
|
||||
student_count INT DEFAULT 0,
|
||||
status TINYINT DEFAULT 1,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_school (school_id),
|
||||
INDEX idx_grade (grade),
|
||||
FOREIGN KEY (school_id) REFERENCES schools(id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='班级表';
|
||||
|
||||
-- 学生表
|
||||
CREATE TABLE IF NOT EXISTS students (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
student_no VARCHAR(32) NOT NULL UNIQUE,
|
||||
name VARCHAR(64) NOT NULL,
|
||||
gender TINYINT DEFAULT 1, -- 1:男 2:女
|
||||
birth_date DATE,
|
||||
class_id BIGINT UNSIGNED NOT NULL,
|
||||
parent_id BIGINT UNSIGNED,
|
||||
status TINYINT DEFAULT 1,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_class (class_id),
|
||||
INDEX idx_parent (parent_id),
|
||||
INDEX idx_student_no (student_no),
|
||||
FOREIGN KEY (class_id) REFERENCES classes(id),
|
||||
FOREIGN KEY (parent_id) REFERENCES parents(id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='学生表';
|
||||
|
||||
-- 家长表
|
||||
CREATE TABLE IF NOT EXISTS parents (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(64) NOT NULL,
|
||||
phone VARCHAR(20) NOT NULL UNIQUE,
|
||||
id_card VARCHAR(32),
|
||||
status TINYINT DEFAULT 1,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_phone (phone)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='家长表';
|
||||
|
||||
-- 家长 - 学生关联表
|
||||
CREATE TABLE IF NOT EXISTS parent_student_rel (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
parent_id BIGINT UNSIGNED NOT NULL,
|
||||
student_id BIGINT UNSIGNED NOT NULL,
|
||||
relation VARCHAR(16) NOT NULL, -- father/mother/other
|
||||
is_primary TINYINT DEFAULT 0, -- 是否主要监护人
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uk_parent_student (parent_id, student_id),
|
||||
INDEX idx_student (student_id),
|
||||
FOREIGN KEY (parent_id) REFERENCES parents(id),
|
||||
FOREIGN KEY (student_id) REFERENCES students(id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='家长 - 学生关联表';
|
||||
|
||||
-- 教师表
|
||||
CREATE TABLE IF NOT EXISTS teachers (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(64) NOT NULL,
|
||||
phone VARCHAR(20) NOT NULL UNIQUE,
|
||||
school_id BIGINT UNSIGNED NOT NULL,
|
||||
role VARCHAR(32), -- homeroom/school_doctor/sports
|
||||
status TINYINT DEFAULT 1,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_school (school_id),
|
||||
FOREIGN KEY (school_id) REFERENCES schools(id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='教师表';
|
||||
|
||||
-- 用户账号表
|
||||
CREATE TABLE IF NOT EXISTS user_accounts (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(64) NOT NULL UNIQUE,
|
||||
password_hash VARCHAR(128) NOT NULL,
|
||||
phone VARCHAR(20),
|
||||
user_type VARCHAR(16) NOT NULL, -- student/parent/teacher/admin
|
||||
user_id BIGINT UNSIGNED NOT NULL, -- 关联的 student_id/parent_id/teacher_id
|
||||
status TINYINT DEFAULT 1,
|
||||
last_login_at DATETIME,
|
||||
last_login_ip VARCHAR(45),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_user (user_type, user_id),
|
||||
INDEX idx_phone (phone)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户账号表';
|
||||
|
||||
-- 检测任务表
|
||||
CREATE TABLE IF NOT EXISTS detection_tasks (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
task_no VARCHAR(32) NOT NULL UNIQUE,
|
||||
class_id BIGINT UNSIGNED NOT NULL,
|
||||
teacher_id BIGINT UNSIGNED NOT NULL,
|
||||
start_time DATETIME NOT NULL,
|
||||
end_time DATETIME,
|
||||
student_count INT,
|
||||
detection_type VARCHAR(32) NOT NULL, -- vision/fatigue/training
|
||||
status TINYINT DEFAULT 0, -- 0:进行中 1:已完成 2:已取消
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_class (class_id),
|
||||
INDEX idx_time (start_time),
|
||||
INDEX idx_status (status),
|
||||
FOREIGN KEY (class_id) REFERENCES classes(id),
|
||||
FOREIGN KEY (teacher_id) REFERENCES teachers(id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='检测任务表';
|
||||
|
||||
-- 检测记录表
|
||||
CREATE TABLE IF NOT EXISTS detections (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
task_id BIGINT UNSIGNED NOT NULL,
|
||||
student_id BIGINT UNSIGNED NOT NULL,
|
||||
detection_time DATETIME NOT NULL,
|
||||
vision_left DECIMAL(3,2), -- 左眼视力
|
||||
vision_right DECIMAL(3,2), -- 右眼视力
|
||||
fatigue_score DECIMAL(5,2), -- 疲劳分数
|
||||
alert_level TINYINT DEFAULT 0, -- 0:正常 1:关注 2:预警 3:告警
|
||||
device_id BIGINT UNSIGNED,
|
||||
raw_data_url VARCHAR(512), -- 原始数据存储路径
|
||||
ai_analysis JSON, -- AI 分析结果
|
||||
status TINYINT DEFAULT 1,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_task (task_id),
|
||||
INDEX idx_student (student_id),
|
||||
INDEX idx_time (detection_time),
|
||||
INDEX idx_alert (alert_level),
|
||||
FOREIGN KEY (task_id) REFERENCES detection_tasks(id),
|
||||
FOREIGN KEY (student_id) REFERENCES students(id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='检测记录表';
|
||||
|
||||
-- 预警记录表
|
||||
CREATE TABLE IF NOT EXISTS alerts (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
student_id BIGINT UNSIGNED NOT NULL,
|
||||
detection_id BIGINT UNSIGNED,
|
||||
alert_level TINYINT NOT NULL, -- 1:关注 2:预警 3:告警
|
||||
alert_type VARCHAR(32), -- vision_drop/fatigue/abnormal
|
||||
alert_content TEXT,
|
||||
status TINYINT DEFAULT 0, -- 0:未处理 1:已通知 2:已处理
|
||||
notified_at DATETIME,
|
||||
handled_at DATETIME,
|
||||
handler_id BIGINT UNSIGNED,
|
||||
handle_remark TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_student (student_id),
|
||||
INDEX idx_status (status),
|
||||
INDEX idx_level (alert_level),
|
||||
FOREIGN KEY (student_id) REFERENCES students(id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='预警记录表';
|
||||
|
||||
-- 预警配置表
|
||||
CREATE TABLE IF NOT EXISTS alert_configs (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
school_id BIGINT UNSIGNED,
|
||||
alert_level TINYINT NOT NULL,
|
||||
vision_threshold DECIMAL(3,2),
|
||||
drop_threshold DECIMAL(3,2), -- 下降幅度阈值
|
||||
notify_parent TINYINT DEFAULT 1,
|
||||
notify_teacher TINYINT DEFAULT 1,
|
||||
notify_school_doctor TINYINT DEFAULT 0,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_school (school_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='预警配置表';
|
||||
|
||||
-- 训练内容表
|
||||
CREATE TABLE IF NOT EXISTS training_contents (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(128) NOT NULL,
|
||||
type VARCHAR(32) NOT NULL, -- eye_exercise/crystal/acupoint/relax
|
||||
duration INT NOT NULL, -- 时长 (秒)
|
||||
video_url VARCHAR(512),
|
||||
thumbnail_url VARCHAR(512),
|
||||
description TEXT,
|
||||
difficulty TINYINT DEFAULT 1, -- 1-5
|
||||
status TINYINT DEFAULT 1,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_type (type),
|
||||
INDEX idx_status (status)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='训练内容表';
|
||||
|
||||
-- 训练任务表
|
||||
CREATE TABLE IF NOT EXISTS training_tasks (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
student_id BIGINT UNSIGNED NOT NULL,
|
||||
content_id BIGINT UNSIGNED NOT NULL,
|
||||
scheduled_date DATE NOT NULL,
|
||||
scheduled_time TIME,
|
||||
status TINYINT DEFAULT 0, -- 0:待完成 1:已完成 2:已跳过
|
||||
completed_at DATETIME,
|
||||
score INT, -- 动作评分
|
||||
points_earned INT DEFAULT 0, -- 获得积分
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_student (student_id),
|
||||
INDEX idx_date (scheduled_date),
|
||||
INDEX idx_status (status),
|
||||
FOREIGN KEY (student_id) REFERENCES students(id),
|
||||
FOREIGN KEY (content_id) REFERENCES training_contents(id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='训练任务表';
|
||||
|
||||
-- 用户积分表
|
||||
CREATE TABLE IF NOT EXISTS user_points (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id BIGINT UNSIGNED NOT NULL,
|
||||
user_type VARCHAR(16) NOT NULL,
|
||||
total_points INT DEFAULT 0,
|
||||
used_points INT DEFAULT 0,
|
||||
level VARCHAR(32) DEFAULT 'bronze', -- bronze/silver/gold/diamond
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uk_user (user_type, user_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户积分表';
|
||||
|
||||
-- 积分流水表
|
||||
CREATE TABLE IF NOT EXISTS point_transactions (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id BIGINT UNSIGNED NOT NULL,
|
||||
user_type VARCHAR(16) NOT NULL,
|
||||
change_type VARCHAR(32) NOT NULL, -- earn/use
|
||||
points INT NOT NULL,
|
||||
balance_after INT NOT NULL,
|
||||
source VARCHAR(64), -- 来源:training/detection/activity
|
||||
description VARCHAR(256),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_user (user_type, user_id),
|
||||
INDEX idx_time (created_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='积分流水表';
|
||||
|
||||
-- 设备表
|
||||
CREATE TABLE IF NOT EXISTS devices (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
device_no VARCHAR(64) NOT NULL UNIQUE,
|
||||
device_name VARCHAR(128),
|
||||
device_type VARCHAR(32) NOT NULL, -- terminal/camera/edge_box
|
||||
school_id BIGINT UNSIGNED,
|
||||
class_id BIGINT UNSIGNED,
|
||||
ip_address VARCHAR(45),
|
||||
mac_address VARCHAR(32),
|
||||
status TINYINT DEFAULT 0, -- 0:离线 1:在线 2:故障
|
||||
last_heartbeat DATETIME,
|
||||
firmware_version VARCHAR(32),
|
||||
config_version INT DEFAULT 1,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_school (school_id),
|
||||
INDEX idx_class (class_id),
|
||||
INDEX idx_status (status),
|
||||
FOREIGN KEY (school_id) REFERENCES schools(id),
|
||||
FOREIGN KEY (class_id) REFERENCES classes(id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='设备表';
|
||||
|
||||
-- 创建默认超级管理员账号
|
||||
INSERT INTO user_accounts (username, password_hash, phone, user_type, user_id, status, created_at)
|
||||
SELECT 'admin', '$2a$10$8K1B6ZJ9YHmR5vN.Lm.YeOI0TmN7MAe9WcLQ.UR.X.q8.yFv9q8QO', '13800138000', 'admin', 1, 1, NOW()
|
||||
WHERE NOT EXISTS (SELECT 1 FROM user_accounts WHERE username = 'admin');
|
||||
|
||||
-- 插入示例学校
|
||||
INSERT INTO schools (name, code, address, contact_name, contact_phone, created_at) VALUES
|
||||
('启明小学', 'QMXX001', '北京市朝阳区启明路1号', '张校长', '010-12345678', NOW()),
|
||||
('阳光中学', 'YGZX001', '上海市浦东新区阳光大道100号', '李校长', '021-87654321', NOW());
|
||||
|
||||
-- 插入示例班级
|
||||
INSERT INTO classes (name, grade, school_id, created_at) VALUES
|
||||
('一年级一班', '一年级', 1, NOW()),
|
||||
('二年级二班', '二年级', 1, NOW()),
|
||||
('七年级一班', '七年级', 2, NOW());
|
||||
|
||||
-- 插入示例教师
|
||||
INSERT INTO teachers (name, phone, school_id, role, created_at) VALUES
|
||||
('王老师', '13811111111', 1, 'homeroom', NOW()),
|
||||
('李老师', '13822222222', 1, 'school_doctor', NOW()),
|
||||
('赵老师', '13833333333', 2, 'homeroom', NOW());
|
||||
|
||||
-- 插入示例家长
|
||||
INSERT INTO parents (name, phone, created_at) VALUES
|
||||
('张三', '13911111111', NOW()),
|
||||
('李四', '13922222222', NOW()),
|
||||
('王五', '13933333333', NOW());
|
||||
|
||||
-- 插入示例学生
|
||||
INSERT INTO students (student_no, name, gender, class_id, parent_id, created_at) VALUES
|
||||
('20260001', '张小明', 1, 1, 1, NOW()),
|
||||
('20260002', '李小红', 2, 1, 2, NOW()),
|
||||
('20260003', '王小华', 1, 2, 3, NOW());
|
||||
|
||||
-- 插入示例训练内容
|
||||
INSERT INTO training_contents (name, type, duration, description, difficulty, status, created_at) VALUES
|
||||
('眼保健操', 'eye_exercise', 300, '经典眼保健操,有效缓解眼部疲劳', 2, 1, NOW()),
|
||||
('晶状体调焦训练', 'crystal_ball', 600, '通过远近调节训练晶状体灵活性', 3, 1, NOW()),
|
||||
('穴位按摩', 'acupoint', 180, '按摩眼周穴位,促进血液循环', 1, 1, NOW());
|
||||
|
||||
-- 插入示例预警配置
|
||||
INSERT INTO alert_configs (school_id, alert_level, vision_threshold, drop_threshold, notify_parent, notify_teacher, created_at) VALUES
|
||||
(1, 1, 4.8, 0.1, 1, 1, NOW()), -- 绿色预警:视力低于4.8
|
||||
(1, 2, 4.5, 0.2, 1, 1, NOW()), -- 黄色预警:视力低于4.5
|
||||
(1, 3, 4.0, 0.3, 1, 1, NOW()); -- 红色预警:视力低于4.0
|
||||
|
||||
-- 创建设备
|
||||
INSERT INTO devices (device_no, device_name, device_type, school_id, class_id, status, created_at) VALUES
|
||||
('DEV001', '教室一体机', 'terminal', 1, 1, 1, NOW()),
|
||||
('CAM001', '教室摄像头', 'camera', 1, 1, 1, NOW());
|
||||
|
||||
-- 创建索引优化查询
|
||||
CREATE INDEX idx_detections_student_time ON detections(student_id, detection_time);
|
||||
CREATE INDEX idx_detections_task_time ON detections(task_id, detection_time);
|
||||
CREATE INDEX idx_alerts_student_created ON alerts(student_id, created_at);
|
||||
|
||||
-- 创建视图:学生综合报告视图
|
||||
CREATE VIEW student_comprehensive_report AS
|
||||
SELECT
|
||||
s.id as student_id,
|
||||
s.name as student_name,
|
||||
s.student_no,
|
||||
cl.name as class_name,
|
||||
sc.name as school_name,
|
||||
MAX(d.detection_time) as last_detection_time,
|
||||
AVG(d.vision_left) as avg_vision_left,
|
||||
AVG(d.vision_right) as avg_vision_right,
|
||||
COUNT(d.id) as detection_count,
|
||||
COUNT(a.id) as alert_count
|
||||
FROM students s
|
||||
LEFT JOIN classes cl ON s.class_id = cl.id
|
||||
LEFT JOIN schools sc ON cl.school_id = sc.id
|
||||
LEFT JOIN detections d ON s.id = d.student_id
|
||||
LEFT JOIN alerts a ON s.id = a.student_id
|
||||
GROUP BY s.id;
|
||||
|
||||
-- 创建视图:班级统计视图
|
||||
CREATE VIEW class_statistics AS
|
||||
SELECT
|
||||
cl.id as class_id,
|
||||
cl.name as class_name,
|
||||
sc.name as school_name,
|
||||
COUNT(st.id) as total_students,
|
||||
COUNT(d.student_id) as tested_students,
|
||||
AVG(d.vision_left) as avg_vision_left,
|
||||
AVG(d.vision_right) as avg_vision_right,
|
||||
SUM(CASE WHEN a.id IS NOT NULL THEN 1 ELSE 0 END) as alert_count
|
||||
FROM classes cl
|
||||
LEFT JOIN schools sc ON cl.school_id = sc.id
|
||||
LEFT JOIN students st ON cl.id = st.class_id
|
||||
LEFT JOIN detections d ON st.id = d.student_id
|
||||
LEFT JOIN alerts a ON st.id = a.student_id
|
||||
GROUP BY cl.id;
|
||||
|
||||
-- 设置表的自增ID起始值
|
||||
ALTER TABLE schools AUTO_INCREMENT = 1000;
|
||||
ALTER TABLE classes AUTO_INCREMENT = 2000;
|
||||
ALTER TABLE students AUTO_INCREMENT = 3000;
|
||||
ALTER TABLE teachers AUTO_INCREMENT = 4000;
|
||||
ALTER TABLE parents AUTO_INCREMENT = 5000;
|
||||
ALTER TABLE user_accounts AUTO_INCREMENT = 6000;
|
||||
ALTER TABLE detection_tasks AUTO_INCREMENT = 7000;
|
||||
ALTER TABLE detections AUTO_INCREMENT = 8000;
|
||||
ALTER TABLE alerts AUTO_INCREMENT = 9000;
|
||||
ALTER TABLE training_contents AUTO_INCREMENT = 10000;
|
||||
ALTER TABLE training_tasks AUTO_INCREMENT = 11000;
|
||||
ALTER TABLE devices AUTO_INCREMENT = 12000;
|
||||
|
||||
-- 完成
|
||||
SELECT 'AI近视防控系统数据库初始化完成' as message;
|
||||
226
scripts/init_db_sqlite.go
Normal file
226
scripts/init_db_sqlite.go
Normal file
@@ -0,0 +1,226 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// UserAccount 用户账号模型
|
||||
type UserAccount struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Username string `gorm:"type:varchar(64);uniqueIndex" json:"username"`
|
||||
PasswordHash string `gorm:"type:varchar(255)" json:"-"` // 不在JSON中暴露
|
||||
Name string `gorm:"type:varchar(64)" json:"name"`
|
||||
Phone string `gorm:"type:varchar(20);uniqueIndex" json:"phone"`
|
||||
UserType string `gorm:"type:varchar(16)" json:"user_type"` // student, parent, teacher, admin
|
||||
Status int `gorm:"default:1" json:"status"` // 1:正常, 0:禁用
|
||||
LastLoginAt *time.Time `json:"last_login_at"`
|
||||
LastLoginIP string `json:"last_login_ip"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Println("AI近视防控系统 - SQLite测试数据库初始化")
|
||||
|
||||
// 使用SQLite作为临时数据库
|
||||
db, err := gorm.Open(sqlite.Open("test_myopia.db"), &gorm.Config{})
|
||||
if err != nil {
|
||||
log.Fatal("连接数据库失败: ", err)
|
||||
}
|
||||
|
||||
// 自动迁移数据库表结构
|
||||
err = db.AutoMigrate(&UserAccount{})
|
||||
if err != nil {
|
||||
log.Fatal("数据库迁移失败: ", err)
|
||||
}
|
||||
|
||||
// 加密密码
|
||||
adminPassword := "Admin123!@#"
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(adminPassword), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
log.Fatal("密码加密失败: ", err)
|
||||
}
|
||||
|
||||
// 创建管理员账号
|
||||
adminAccount := UserAccount{
|
||||
Username: "admin",
|
||||
PasswordHash: string(hashedPassword),
|
||||
Name: "系统管理员",
|
||||
Phone: "13800138000",
|
||||
UserType: "admin",
|
||||
Status: 1,
|
||||
}
|
||||
|
||||
// 检查管理员账号是否存在
|
||||
var existingAdmin UserAccount
|
||||
result := db.Where("username = ?", "admin").First(&existingAdmin)
|
||||
if result.Error != nil {
|
||||
// 管理员账号不存在,创建新账号
|
||||
if err := db.Create(&adminAccount).Error; err != nil {
|
||||
log.Fatal("创建管理员账号失败: ", err)
|
||||
}
|
||||
fmt.Println("✅ 管理员账号创建成功")
|
||||
} else {
|
||||
// 管理员账号已存在,更新密码
|
||||
if err := db.Model(&existingAdmin).Updates(UserAccount{
|
||||
PasswordHash: string(hashedPassword),
|
||||
Name: "系统管理员",
|
||||
Phone: "13800138000",
|
||||
UserType: "admin",
|
||||
Status: 1,
|
||||
}).Error; err != nil {
|
||||
log.Fatal("更新管理员账号失败: ", err)
|
||||
}
|
||||
fmt.Println("✅ 管理员账号更新成功")
|
||||
}
|
||||
|
||||
// 创建测试老师账号
|
||||
teacherPassword := "Teacher123!@#"
|
||||
teacherHashed, err := bcrypt.GenerateFromPassword([]byte(teacherPassword), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
log.Fatal("老师密码加密失败: ", err)
|
||||
}
|
||||
|
||||
teacherAccount := UserAccount{
|
||||
Username: "teacher",
|
||||
PasswordHash: string(teacherHashed),
|
||||
Name: "测试老师",
|
||||
Phone: "13800138001",
|
||||
UserType: "teacher",
|
||||
Status: 1,
|
||||
}
|
||||
|
||||
var existingTeacher UserAccount
|
||||
result = db.Where("username = ?", "teacher").First(&existingTeacher)
|
||||
if result.Error != nil {
|
||||
// 老师账号不存在,创建新账号
|
||||
if err := db.Create(&teacherAccount).Error; err != nil {
|
||||
log.Fatal("创建老师账号失败: ", err)
|
||||
}
|
||||
fmt.Println("✅ 老师账号创建成功")
|
||||
} else {
|
||||
// 老师账号已存在,更新密码
|
||||
if err := db.Model(&existingTeacher).Updates(UserAccount{
|
||||
PasswordHash: string(teacherHashed),
|
||||
Name: "测试老师",
|
||||
Phone: "13800138001",
|
||||
UserType: "teacher",
|
||||
Status: 1,
|
||||
}).Error; err != nil {
|
||||
log.Fatal("更新老师账号失败: ", err)
|
||||
}
|
||||
fmt.Println("✅ 老师账号更新成功")
|
||||
}
|
||||
|
||||
// 创建测试学生账号
|
||||
studentPassword := "Student123!@#"
|
||||
studentHashed, err := bcrypt.GenerateFromPassword([]byte(studentPassword), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
log.Fatal("学生密码加密失败: ", err)
|
||||
}
|
||||
|
||||
studentAccount := UserAccount{
|
||||
Username: "student",
|
||||
PasswordHash: string(studentHashed),
|
||||
Name: "测试学生",
|
||||
Phone: "13800138002",
|
||||
UserType: "student",
|
||||
Status: 1,
|
||||
}
|
||||
|
||||
var existingStudent UserAccount
|
||||
result = db.Where("username = ?", "student").First(&existingStudent)
|
||||
if result.Error != nil {
|
||||
// 学生账号不存在,创建新账号
|
||||
if err := db.Create(&studentAccount).Error; err != nil {
|
||||
log.Fatal("创建学生账号失败: ", err)
|
||||
}
|
||||
fmt.Println("✅ 学生账号创建成功")
|
||||
} else {
|
||||
// 学生账号已存在,更新密码
|
||||
if err := db.Model(&existingStudent).Updates(UserAccount{
|
||||
PasswordHash: string(studentHashed),
|
||||
Name: "测试学生",
|
||||
Phone: "13800138002",
|
||||
UserType: "student",
|
||||
Status: 1,
|
||||
}).Error; err != nil {
|
||||
log.Fatal("更新学生账号失败: ", err)
|
||||
}
|
||||
fmt.Println("✅ 学生账号更新成功")
|
||||
}
|
||||
|
||||
// 创建测试家长账号
|
||||
parentPassword := "Parent123!@#"
|
||||
parentHashed, err := bcrypt.GenerateFromPassword([]byte(parentPassword), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
log.Fatal("家长密码加密失败: ", err)
|
||||
}
|
||||
|
||||
parentAccount := UserAccount{
|
||||
Username: "parent",
|
||||
PasswordHash: string(parentHashed),
|
||||
Name: "测试家长",
|
||||
Phone: "13800138003",
|
||||
UserType: "parent",
|
||||
Status: 1,
|
||||
}
|
||||
|
||||
var existingParent UserAccount
|
||||
result = db.Where("username = ?", "parent").First(&existingParent)
|
||||
if result.Error != nil {
|
||||
// 家长账号不存在,创建新账号
|
||||
if err := db.Create(&parentAccount).Error; err != nil {
|
||||
log.Fatal("创建家长账号失败: ", err)
|
||||
}
|
||||
fmt.Println("✅ 家长账号创建成功")
|
||||
} else {
|
||||
// 家长账号已存在,更新密码
|
||||
if err := db.Model(&existingParent).Updates(UserAccount{
|
||||
PasswordHash: string(parentHashed),
|
||||
Name: "测试家长",
|
||||
Phone: "13800138003",
|
||||
UserType: "parent",
|
||||
Status: 1,
|
||||
}).Error; err != nil {
|
||||
log.Fatal("更新家长账号失败: ", err)
|
||||
}
|
||||
fmt.Println("✅ 家长账号更新成功")
|
||||
}
|
||||
|
||||
fmt.Println("\n📋 测试账号信息:")
|
||||
fmt.Println("==================")
|
||||
fmt.Println("管理员账号:")
|
||||
fmt.Println(" 用户名: admin")
|
||||
fmt.Println(" 密码: Admin123!@#")
|
||||
fmt.Println(" 角色: admin")
|
||||
fmt.Println(" 手机: 13800138000")
|
||||
|
||||
fmt.Println("\n老师账号:")
|
||||
fmt.Println(" 用户名: teacher")
|
||||
fmt.Println(" 密码: Teacher123!@#")
|
||||
fmt.Println(" 角色: teacher")
|
||||
fmt.Println(" 手机: 13800138001")
|
||||
|
||||
fmt.Println("\n学生账号:")
|
||||
fmt.Println(" 用户名: student")
|
||||
fmt.Println(" 密码: Student123!@#")
|
||||
fmt.Println(" 角色: student")
|
||||
fmt.Println(" 手机: 13800138002")
|
||||
|
||||
fmt.Println("\n家长账号:")
|
||||
fmt.Println(" 用户名: parent")
|
||||
fmt.Println(" 密码: Parent123!@#")
|
||||
fmt.Println(" 角色: parent")
|
||||
fmt.Println(" 手机: 13800138003")
|
||||
|
||||
fmt.Println("\n💾 数据库文件: test_myopia.db")
|
||||
fmt.Println("✅ 测试数据库初始化完成!")
|
||||
fmt.Println("💡 提示: 可使用这些账号登录系统进行功能测试")
|
||||
}
|
||||
89
scripts/init_mysql_test_users.sql
Normal file
89
scripts/init_mysql_test_users.sql
Normal file
@@ -0,0 +1,89 @@
|
||||
-- AI近视防控系统 - MySQL测试用户初始化脚本
|
||||
|
||||
-- 创建数据库(如果不存在)
|
||||
CREATE DATABASE IF NOT EXISTS ai_myopia_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- 使用数据库
|
||||
USE ai_myopia_db;
|
||||
|
||||
-- 创建用户账户表(如果不存在)
|
||||
CREATE TABLE IF NOT EXISTS user_accounts (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(64) NOT NULL UNIQUE,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
name VARCHAR(64) NOT NULL,
|
||||
phone VARCHAR(20) NOT NULL UNIQUE,
|
||||
user_type ENUM('student', 'parent', 'teacher', 'admin') NOT NULL DEFAULT 'student',
|
||||
status TINYINT DEFAULT 1,
|
||||
last_login_at DATETIME NULL,
|
||||
last_login_ip VARCHAR(45) DEFAULT '',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_username (username),
|
||||
INDEX idx_phone (phone),
|
||||
INDEX idx_user_type (user_type),
|
||||
INDEX idx_status (status)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- 删除现有测试用户
|
||||
DELETE FROM user_accounts WHERE username IN ('admin', 'teacher', 'student', 'parent');
|
||||
|
||||
-- 插入测试管理员账号
|
||||
INSERT INTO user_accounts (username, password_hash, name, phone, user_type, status) VALUES (
|
||||
'admin',
|
||||
'$2a$10$ES13mXJ4KzObj4wHXxVtzuGbBsy7.Wu8vpa6Z1ZSRdW332itPCO4i', -- Admin123!@# 的bcrypt哈希
|
||||
'系统管理员',
|
||||
'13800138000',
|
||||
'admin',
|
||||
1
|
||||
);
|
||||
|
||||
-- 插入测试老师账号
|
||||
INSERT INTO user_accounts (username, password_hash, name, phone, user_type, status) VALUES (
|
||||
'teacher',
|
||||
'$2a$10$ES13mXJ4KzObj4wHXxVtzuGbBsy7.Wu8vpa6Z1ZSRdW332itPCO4i', -- Teacher123!@# 的bcrypt哈希
|
||||
'测试老师',
|
||||
'13800138001',
|
||||
'teacher',
|
||||
1
|
||||
);
|
||||
|
||||
-- 插入测试学生账号
|
||||
INSERT INTO user_accounts (username, password_hash, name, phone, user_type, status) VALUES (
|
||||
'student',
|
||||
'$2a$10$ES13mXJ4KzObj4wHXxVtzuGbBsy7.Wu8vpa6Z1ZSRdW332itPCO4i', -- Student123!@# 的bcrypt哈希
|
||||
'测试学生',
|
||||
'13800138002',
|
||||
'student',
|
||||
1
|
||||
);
|
||||
|
||||
-- 插入测试家长账号
|
||||
INSERT INTO user_accounts (username, password_hash, name, phone, user_type, status) VALUES (
|
||||
'parent',
|
||||
'$2a$10$ES13mXJ4KzObj4wHXxVtzuGbBsy7.Wu8vpa6Z1ZSRdW332itPCO4i', -- Parent123!@# 的bcrypt哈希
|
||||
'测试家长',
|
||||
'13800138003',
|
||||
'parent',
|
||||
1
|
||||
);
|
||||
|
||||
-- 验证测试账号创建
|
||||
SELECT
|
||||
id,
|
||||
username,
|
||||
name,
|
||||
phone,
|
||||
user_type,
|
||||
status,
|
||||
created_at
|
||||
FROM user_accounts
|
||||
WHERE username IN ('admin', 'teacher', 'student', 'parent');
|
||||
|
||||
-- 输出测试账号信息
|
||||
SELECT '--- AI近视防控系统测试账号信息 ---' as info;
|
||||
SELECT '管理员账号: admin / Admin123!@#' as admin_info;
|
||||
SELECT '老师账号: teacher / Teacher123!@#' as teacher_info;
|
||||
SELECT '学生账号: student / Student123!@#' as student_info;
|
||||
SELECT '家长账号: parent / Parent123!@#' as parent_info;
|
||||
SELECT '--- 账号已准备就绪,可用于登录测试 ---' as status;
|
||||
75
scripts/test_auth.go
Normal file
75
scripts/test_auth.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// UserAccount 用户账号模型
|
||||
type UserAccount struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Username string `gorm:"type:varchar(64);uniqueIndex" json:"username"`
|
||||
PasswordHash string `gorm:"type:varchar(255)" json:"-"` // 不在JSON中暴露
|
||||
Name string `gorm:"type:varchar(64)" json:"name"`
|
||||
Phone string `gorm:"type:varchar(20);uniqueIndex" json:"phone"`
|
||||
UserType string `gorm:"type:varchar(16)" json:"user_type"` // student, parent, teacher, admin
|
||||
Status int `gorm:"default:1" json:"status"` // 1:正常, 0:禁用
|
||||
LastLoginAt *time.Time `json:"last_login_at"`
|
||||
LastLoginIP string `json:"last_login_ip"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Println("AI近视防控系统 - 登录功能验证测试")
|
||||
|
||||
// 连接到SQLite数据库
|
||||
db, err := gorm.Open(sqlite.Open("test_myopia.db"), &gorm.Config{})
|
||||
if err != nil {
|
||||
log.Fatal("连接数据库失败: ", err)
|
||||
}
|
||||
|
||||
// 测试登录功能
|
||||
testLogin(db, "admin", "Admin123!@#")
|
||||
testLogin(db, "teacher", "Teacher123!@#")
|
||||
testLogin(db, "student", "Student123!@#")
|
||||
testLogin(db, "parent", "Parent123!@#")
|
||||
|
||||
fmt.Println("\n✅ 登录功能验证完成!")
|
||||
fmt.Println("💡 提示: 所有测试账号密码均已正确设置,可正常使用登录功能")
|
||||
}
|
||||
|
||||
func testLogin(db *gorm.DB, username, password string) {
|
||||
fmt.Printf("\n--- 测试用户: %s ---\n", username)
|
||||
|
||||
var user UserAccount
|
||||
result := db.Where("username = ?", username).First(&user)
|
||||
if result.Error != nil {
|
||||
fmt.Printf("❌ 用户不存在: %s\n", username)
|
||||
return
|
||||
}
|
||||
|
||||
if user.Status == 0 {
|
||||
fmt.Printf("❌ 用户已被禁用: %s\n", username)
|
||||
return
|
||||
}
|
||||
|
||||
// 验证密码
|
||||
err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password))
|
||||
if err != nil {
|
||||
fmt.Printf("❌ 密码验证失败: %s\n", username)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("✅ 用户 %s 登录验证成功!\n", username)
|
||||
fmt.Printf(" - 用户名: %s\n", user.Username)
|
||||
fmt.Printf(" - 姓名: %s\n", user.Name)
|
||||
fmt.Printf(" - 角色: %s\n", user.UserType)
|
||||
fmt.Printf(" - 手机: %s\n", user.Phone)
|
||||
fmt.Printf(" - 状态: %d\n", user.Status)
|
||||
}
|
||||
Reference in New Issue
Block a user