From f1e758934437838ee2e78f2b597a70dfd3f08d24 Mon Sep 17 00:00:00 2001 From: cc <124141@qq.com> Date: Tue, 1 Apr 2025 19:49:15 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=B7=B2=E7=9F=A5=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DATABASE_SETUP.md | 328 ++++++++++++++ README.md | 81 ++++ api-app.js | 255 +++++++++++ api-package.json | 16 + build-script.sh | 11 + database_setup.sql | 33 ++ entry/src/main/ets/common/DatabaseService.ets | 415 ++++++++++++++++++ entry/src/main/ets/pages/TransitionPage.ets | 260 +++++++++++ 8 files changed, 1399 insertions(+) create mode 100644 DATABASE_SETUP.md create mode 100644 README.md create mode 100644 api-app.js create mode 100644 api-package.json create mode 100755 build-script.sh create mode 100644 database_setup.sql create mode 100644 entry/src/main/ets/common/DatabaseService.ets create mode 100644 entry/src/main/ets/pages/TransitionPage.ets diff --git a/DATABASE_SETUP.md b/DATABASE_SETUP.md new file mode 100644 index 0000000..e8844c6 --- /dev/null +++ b/DATABASE_SETUP.md @@ -0,0 +1,328 @@ +# 智慧教室管理系统数据库与API配置指南 + +本文档详细介绍如何在宝塔面板上配置MySQL数据库和Node.js API服务,以支持智慧教室管理系统的HarmonyOS应用。 + +## 一、宝塔面板安装与配置 + +### 1.1 安装宝塔面板 + +如果尚未安装宝塔面板,请参考[宝塔官方文档](https://www.bt.cn/bbs/thread-19376-1-1.html)进行安装。 + +### 1.2 安装必要软件 + +在宝塔面板中安装以下软件: +- Nginx (用于Web服务器) +- MySQL (用于数据库) +- PHP (如需使用phpMyAdmin) +- Node.js (用于API服务) + +## 二、MySQL数据库配置 + +### 2.1 创建数据库 + +1. 在宝塔面板左侧找到"数据库"选项 +2. 点击"添加数据库" +3. 填写以下信息: + - 数据库名称:`hongm` + - 用户名:`hongm` + - 密码:`JsKJeG7CX2WnyArt` (或自定义安全密码) + - 访问权限:选择"所有人" (开发环境)或指定IP (生产环境) +4. 点击"提交"创建数据库 + +### 2.2 导入数据结构 + +1. 在数据库列表找到刚创建的`hongm`数据库 +2. 点击"管理"进入phpMyAdmin +3. 选择"SQL"标签 +4. 复制并执行以下SQL语句: + +```sql +-- 创建用户信息表 +CREATE TABLE IF NOT EXISTS UserText ( + account VARCHAR(20) PRIMARY KEY, + nickname VARCHAR(50) NOT NULL, + email VARCHAR(100), + phone VARCHAR(20), + photo VARCHAR(255) DEFAULT NULL, + category VARCHAR(20) NOT NULL, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- 创建用户密码表 +CREATE TABLE IF NOT EXISTS UserPassword ( + account VARCHAR(20) PRIMARY KEY, + password VARCHAR(50) NOT NULL, + FOREIGN KEY (account) REFERENCES UserText(account) +); + +-- 插入示例用户数据 +INSERT INTO UserText (account, nickname, email, phone, category) VALUES +('2', '张三', 'student@qq.com', '17267383831', 'student'), +('9222', '李华', 'student123@qq.com', '12345678901', 'student'), +('0', '教师demo', 'teach@qq.com', NULL, 'teacher'); + +-- 插入密码(所有密码都是'1') +INSERT INTO UserPassword (account, password) VALUES +('2', '1'), +('9222', '1'), +('0', '1'); +``` + +## 三、Node.js API服务配置 + +### 3.1 创建网站 + +1. 在宝塔面板左侧找到"网站"选项 +2. 点击"添加站点" +3. 填写以下信息: + - 域名:`api.yourdomain.com` (或您的服务器IP地址) + - 备注:`智慧教室管理系统API服务` + - PHP版本:纯静态 +4. 点击"提交"创建网站 + +### 3.2 上传API服务代码 + +1. 在网站目录中创建`app.js`文件 +2. 将以下代码复制到`app.js`中: + +```javascript +const express = require('express'); +const mysql = require('mysql2'); +const cors = require('cors'); +const bodyParser = require('body-parser'); + +const app = express(); +const port = 3000; + +// 启用CORS和JSON解析 +app.use(cors()); +app.use(bodyParser.json()); + +// 数据库连接配置 +const dbConfig = { + host: 'localhost', + port: 3306, // MySQL默认端口,根据您的配置可能需要修改 + user: 'hongm', + password: 'JsKJeG7CX2WnyArt', + database: 'hongm' +}; + +// 创建数据库连接池 +const pool = mysql.createPool(dbConfig); +const promisePool = pool.promise(); + +// 验证用户登录API +app.post('/api/login', async (req, res) => { + try { + const { account, password } = req.body; + + // 查询密码 + const [passwordRows] = await promisePool.query( + 'SELECT password FROM UserPassword WHERE account = ?', + [account] + ); + + if (passwordRows.length === 0) { + return res.status(401).json({ success: false, message: '账号不存在' }); + } + + const storedPassword = passwordRows[0].password; + + if (password !== storedPassword) { + return res.status(401).json({ success: false, message: '密码错误' }); + } + + // 查询用户信息 + const [userRows] = await promisePool.query( + 'SELECT * FROM UserText WHERE account = ?', + [account] + ); + + if (userRows.length === 0) { + return res.status(401).json({ success: false, message: '用户信息不存在' }); + } + + const user = userRows[0]; + + // 返回成功登录信息和用户数据 + res.json({ + success: true, + message: '登录成功', + user: { + account: user.account, + nickname: user.nickname, + email: user.email, + phone: user.phone, + photo: user.photo || 'http://139.155.155.67:2342/images/default_avatar.png', + category: user.category + } + }); + } catch (error) { + console.error('登录错误:', error); + res.status(500).json({ success: false, message: '服务器错误' }); + } +}); + +// 获取用户信息API +app.get('/api/user/:account', async (req, res) => { + try { + const { account } = req.params; + + // 查询用户信息 + const [userRows] = await promisePool.query( + 'SELECT * FROM UserText WHERE account = ?', + [account] + ); + + if (userRows.length === 0) { + return res.status(404).json({ success: false, message: '用户不存在' }); + } + + const user = userRows[0]; + + // 返回用户数据 + res.json({ + success: true, + user: { + account: user.account, + nickname: user.nickname, + email: user.email, + phone: user.phone, + photo: user.photo || 'http://139.155.155.67:2342/images/default_avatar.png', + category: user.category + } + }); + } catch (error) { + console.error('获取用户信息错误:', error); + res.status(500).json({ success: false, message: '服务器错误' }); + } +}); + +// 更新用户邮箱API +app.put('/api/user/:account/email', async (req, res) => { + try { + const { account } = req.params; + const { email } = req.body; + + // 验证邮箱格式 + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(email)) { + return res.status(400).json({ success: false, message: '邮箱格式无效' }); + } + + // 更新邮箱 + const [result] = await promisePool.query( + 'UPDATE UserText SET email = ? WHERE account = ?', + [email, account] + ); + + if (result.affectedRows === 0) { + return res.status(404).json({ success: false, message: '用户不存在' }); + } + + res.json({ success: true, message: '邮箱更新成功' }); + } catch (error) { + console.error('更新邮箱错误:', error); + res.status(500).json({ success: false, message: '服务器错误' }); + } +}); + +// 启动服务器 +app.listen(port, () => { + console.log(`API服务运行在端口 ${port}`); +}); +``` + +### 3.3 安装依赖并启动服务 + +1. 通过SSH连接到服务器,进入网站目录: + ```bash + cd /www/wwwroot/your_website_directory + ``` + +2. 初始化项目并安装依赖: + ```bash + npm init -y + npm install express mysql2 cors body-parser + ``` + +3. 安装PM2并启动服务: + ```bash + npm install -g pm2 + pm2 start app.js --name "classmg-api" + pm2 startup + pm2 save + ``` + +### 3.4 配置Nginx反向代理 (可选) + +为了使API更安全和易于访问,可以配置Nginx反向代理: + +1. 在宝塔面板中,找到您的网站,点击"设置" +2. 点击"反向代理",添加新的反向代理 +3. 填写以下信息: + - 名称:`API反向代理` + - 目标URL:`http://127.0.0.1:3000` + - 发送域名:勾选 +4. 点击"提交" + +现在您可以通过 `http://your_domain/api/` 访问API服务。 + +### 3.5 配置防火墙 + +确保服务器防火墙允许访问3000端口(如果直接使用Node.js)或80/443端口(如果使用Nginx反向代理)。 + +## 四、HarmonyOS应用配置 + +### 4.1 修改API地址 + +在HarmonyOS应用的`DatabaseService.ets`文件中,确保API地址正确配置: + +```typescript +// 定义API服务配置 +const API_CONFIG: ApiConfig = { + // 修改为您的实际服务器地址 + baseUrl: 'http://your_server_ip:3000/api', + timeout: 10000 +}; +``` + +### 4.2 构建并测试应用 + +1. 在DevEco Studio中构建应用 +2. 在模拟器或真机上测试登录功能 +3. 尝试使用示例账号登录: + - 账号:`2`或`9222`或`0` + - 密码:`1` + +## 五、生产环境安全建议 + +1. 启用HTTPS:为API服务配置SSL证书 +2. 实现更安全的密码存储:使用加盐哈希而不是明文存储 +3. 添加JWT认证:实现令牌机制进行会话管理 +4. 限制请求频率:防止暴力破解攻击 +5. 日志记录:记录所有API调用以便审计 +6. 定期备份数据库:防止数据丢失 + +## 六、故障排除 + +### 6.1 API连接问题 + +如果应用无法连接到API: +1. 检查服务器IP和端口是否正确 +2. 确认防火墙设置允许连接 +3. 验证API服务是否正在运行 (`pm2 status`) +4. 检查服务器日志 (`pm2 logs classmg-api`) + +### 6.2 数据库问题 + +如果API服务无法连接到数据库: +1. 确认数据库用户名和密码正确 +2. 检查数据库用户权限 +3. 验证数据库服务是否正在运行 + +### 6.3 降级模式 + +应用内已实现降级模式,当无法连接到API服务时,将使用本地模拟数据: +- 账号:`2`、`9222`、`0` +- 密码:`1` \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..5d64cd8 --- /dev/null +++ b/README.md @@ -0,0 +1,81 @@ +# 智慧教室管理系统 + +## 项目概述 + +智慧教室管理系统是一款基于HarmonyOS/ArkTS开发的应用,旨在提供智能化的教室管理解决方案。系统包含多个功能模块,用于管理教室状态、课程信息和用户权限等。 + +## 功能特点 + +- **用户管理**: 支持教师和学生两种用户类型,提供差异化的功能体验 +- **教室监控**: 实时监控教室温度、湿度、人数等数据 +- **课程数据**: 展示课程信息、教师信息和学生出勤情况 +- **个人设置**: 用户可以修改个人信息和系统偏好设置 + +## 技术架构 + +- **前端**: HarmonyOS/ArkTS +- **后端**: MySQL数据库 +- **通信**: HTTP API + +## 系统要求 + +- HarmonyOS设备 +- 网络连接 +- 支持HarmonyOS 3.0及以上版本 + +## 数据库配置 + +系统使用MySQL数据库存储用户信息和系统数据。数据库配置如下: + +- **数据库地址**: 139.155.155.67:25342 +- **数据库名**: hongm +- **用户名**: hongm +- **密码**: JsKJeG7CX2WnyArt + +### 数据库初始化 + +导入`database_setup.sql`文件以创建必要的表和初始数据。主要的数据表包括: + +- **UserText**: 存储用户个人信息 +- **UserPassword**: 存储用户登录信息 + +## 使用指南 + +### 登录系统 + +1. 在登录页面输入您的账号和密码(默认密码: 1) +2. 系统会根据账号自动识别用户类型: + - 账号包含"2"的识别为学生 + - 账号包含"0"的识别为教师 +3. 登录成功后将显示过渡页面,然后进入系统主页 + +### 系统导航 + +系统包含三个主要页面: + +- **首页**: 显示教室监控和综合上课数据 +- **上课**: 提供课程相关功能 +- **设置**: 管理个人信息和系统设置 + +### 个人信息设置 + +在设置页面,用户可以查看和修改个人信息: + +- **头像**: 显示用户头像 +- **账号**: 显示当前登录账号(不可修改) +- **昵称**: 显示用户昵称(不可修改) +- **邮箱**: 可修改,需符合邮箱格式 +- **电话**: 显示联系电话(不可修改) + +## 开发信息 + +- **开发团队**: 922213102班鸿蒙第一组 +- **版本**: 1.0.0 + +## 备注 + +- 用户密码默认为"1" +- 系统现有示例用户: + - 学生: 账号"2",昵称"张三" + - 学生: 账号"9222",昵称"李华" + - 教师: 账号"0",昵称"教师demo" \ No newline at end of file diff --git a/api-app.js b/api-app.js new file mode 100644 index 0000000..57d6d6f --- /dev/null +++ b/api-app.js @@ -0,0 +1,255 @@ +const express = require('express'); +const mysql = require('mysql2'); +const cors = require('cors'); +const bodyParser = require('body-parser'); +const fs = require('fs'); +const path = require('path'); + +// 创建Express应用 +const app = express(); +const port = 3000; + +// 启用CORS和JSON解析 +app.use(cors()); +app.use(bodyParser.json()); + +// 创建logs目录(如果不存在) +const logsDir = path.join(__dirname, 'logs'); +if (!fs.existsSync(logsDir)) { + fs.mkdirSync(logsDir); +} + +// 简单日志函数 +function logToFile(message) { + const logFile = path.join(logsDir, 'api.log'); + const timestamp = new Date().toISOString(); + fs.appendFileSync(logFile, `${timestamp} - ${message}\n`); + console.log(`${timestamp} - ${message}`); +} + +// 数据库连接配置 +const dbConfig = { + host: 'localhost', + port: 25342, // 修改为正确的MySQL端口 + user: 'hongm', // 替换为您的数据库用户名 + password: 'hongm', // 替换为您的数据库密码 + database: 'hongm', + waitForConnections: true, + connectionLimit: 10, + queueLimit: 0 +}; + +// 创建数据库连接池 +const pool = mysql.createPool(dbConfig); +const promisePool = pool.promise(); + +// 检查数据库连接 +async function checkDbConnection() { + try { + const connection = await promisePool.getConnection(); + logToFile('数据库连接成功'); + connection.release(); + return true; + } catch (error) { + logToFile(`数据库连接失败: ${error.message}`); + return false; + } +} + +// 在启动时测试数据库连接 +checkDbConnection(); + +// 创建一个简单的首页响应 +app.get('/', (req, res) => { + res.send('智能教室管理系统API服务正在运行'); +}); + +// 创建一个图片目录路由 +app.use('/images', express.static(path.join(__dirname, 'images'))); + +// API: 用户登录验证 +app.post('/api/login', async (req, res) => { + try { + logToFile(`收到登录请求: ${JSON.stringify(req.body)}`); + const { account, password } = req.body; + + if (!account || !password) { + return res.status(400).json({ + success: false, + message: '账号和密码不能为空' + }); + } + + // 查询密码 + const [passwordRows] = await promisePool.query( + 'SELECT password FROM UserPassword WHERE account = ?', + [account] + ); + + if (passwordRows.length === 0) { + logToFile(`登录失败: 账号 ${account} 不存在`); + return res.status(401).json({ + success: false, + message: '账号不存在' + }); + } + + const storedPassword = passwordRows[0].password; + + if (password !== storedPassword) { + logToFile(`登录失败: 账号 ${account} 密码错误`); + return res.status(401).json({ + success: false, + message: '密码错误' + }); + } + + // 查询用户信息 + const [userRows] = await promisePool.query( + 'SELECT * FROM UserText WHERE account = ?', + [account] + ); + + if (userRows.length === 0) { + logToFile(`登录异常: 账号 ${account} 存在但用户信息丢失`); + return res.status(500).json({ + success: false, + message: '用户信息不存在' + }); + } + + const user = userRows[0]; + logToFile(`登录成功: 账号 ${account}, 用户名 ${user.nickname}`); + + // 返回成功登录信息和用户数据 + res.json({ + success: true, + message: '登录成功', + user: { + account: user.account, + nickname: user.nickname, + email: user.email || '', + phone: user.phone || '', + photo: user.photo || 'http://139.155.155.67:2342/images/default_avatar.png', + category: user.category + } + }); + } catch (error) { + logToFile(`登录错误: ${error.message}`); + res.status(500).json({ + success: false, + message: '服务器错误', + error: error.message + }); + } +}); + +// API: 获取用户信息 +app.get('/api/user/:account', async (req, res) => { + try { + const { account } = req.params; + logToFile(`获取用户信息请求: 账号 ${account}`); + + // 查询用户信息 + const [userRows] = await promisePool.query( + 'SELECT * FROM UserText WHERE account = ?', + [account] + ); + + if (userRows.length === 0) { + logToFile(`获取用户信息失败: 账号 ${account} 不存在`); + return res.status(404).json({ + success: false, + message: '用户不存在' + }); + } + + const user = userRows[0]; + logToFile(`获取用户信息成功: 账号 ${account}, 用户名 ${user.nickname}`); + + // 返回用户数据 + res.json({ + success: true, + user: { + account: user.account, + nickname: user.nickname, + email: user.email || '', + phone: user.phone || '', + photo: user.photo || 'http://139.155.155.67:2342/images/default_avatar.png', + category: user.category + } + }); + } catch (error) { + logToFile(`获取用户信息错误: ${error.message}`); + res.status(500).json({ + success: false, + message: '服务器错误', + error: error.message + }); + } +}); + +// API: 更新用户邮箱 +app.put('/api/user/:account/email', async (req, res) => { + try { + const { account } = req.params; + const { email } = req.body; + logToFile(`更新用户邮箱请求: 账号 ${account}, 新邮箱 ${email}`); + + // 验证邮箱格式 + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(email)) { + logToFile(`更新邮箱失败: 邮箱格式无效 ${email}`); + return res.status(400).json({ + success: false, + message: '邮箱格式无效' + }); + } + + // 检查用户是否存在 + const [userCheck] = await promisePool.query( + 'SELECT account FROM UserText WHERE account = ?', + [account] + ); + + if (userCheck.length === 0) { + logToFile(`更新邮箱失败: 账号 ${account} 不存在`); + return res.status(404).json({ + success: false, + message: '用户不存在' + }); + } + + // 更新邮箱 + const [result] = await promisePool.query( + 'UPDATE UserText SET email = ? WHERE account = ?', + [email, account] + ); + + if (result.affectedRows === 0) { + logToFile(`更新邮箱失败: 账号 ${account} 无法更新`); + return res.status(500).json({ + success: false, + message: '更新邮箱失败' + }); + } + + logToFile(`更新邮箱成功: 账号 ${account}, 新邮箱 ${email}`); + res.json({ + success: true, + message: '邮箱更新成功' + }); + } catch (error) { + logToFile(`更新邮箱错误: ${error.message}`); + res.status(500).json({ + success: false, + message: '服务器错误', + error: error.message + }); + } +}); + +// 启动服务器 +app.listen(port, () => { + logToFile(`API服务运行在端口 ${port}`); +}); \ No newline at end of file diff --git a/api-package.json b/api-package.json new file mode 100644 index 0000000..87d9d67 --- /dev/null +++ b/api-package.json @@ -0,0 +1,16 @@ +{ + "name": "classmg-api", + "version": "1.0.0", + "description": "智能教室管理系统API服务", + "main": "app.js", + "scripts": { + "start": "node app.js", + "dev": "nodemon app.js" + }, + "dependencies": { + "body-parser": "^1.20.2", + "cors": "^2.8.5", + "express": "^4.18.2", + "mysql2": "^3.6.1" + } +} \ No newline at end of file diff --git a/build-script.sh b/build-script.sh new file mode 100755 index 0000000..b979a4f --- /dev/null +++ b/build-script.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +echo "Cleaning project..." +rm -rf build/ +rm -rf build-profile/ + +echo "Building project..." +/Applications/DevEco-Studio.app/Contents/tools/node/bin/node /Applications/DevEco-Studio.app/Contents/tools/hvigor/bin/hvigorw.js --mode module -p module=entry@default -p product=default -p requiredDeviceType=phone clean + +echo "Rebuilding project..." +/Applications/DevEco-Studio.app/Contents/tools/node/bin/node /Applications/DevEco-Studio.app/Contents/tools/hvigor/bin/hvigorw.js --mode module -p module=entry@default -p product=default -p requiredDeviceType=phone assembleHap --analyze=normal --parallel --incremental --daemon diff --git a/database_setup.sql b/database_setup.sql new file mode 100644 index 0000000..3fe8c26 --- /dev/null +++ b/database_setup.sql @@ -0,0 +1,33 @@ +-- Create the UserText table to store user information +CREATE TABLE IF NOT EXISTS UserText ( + account VARCHAR(20) PRIMARY KEY, + nickname VARCHAR(50) NOT NULL, + email VARCHAR(100), + phone VARCHAR(20), + photo VARCHAR(255) DEFAULT NULL, + category VARCHAR(20) NOT NULL, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- Create the UserPassword table to store user account and password +CREATE TABLE IF NOT EXISTS UserPassword ( + account VARCHAR(20) PRIMARY KEY, + password VARCHAR(50) NOT NULL, + FOREIGN KEY (account) REFERENCES UserText(account) +); + +-- Insert sample users as specified in the requirements +-- Student users +INSERT INTO UserText (account, nickname, email, phone, category) VALUES +('2', '张三', 'student@qq.com', '17267383831', 'student'), +('9222', '李华', 'student123@qq.com', '12345678901', 'student'); + +-- Teacher user +INSERT INTO UserText (account, nickname, email, phone, category) VALUES +('0', '教师demo', 'teach@qq.com', NULL, 'teacher'); + +-- Insert passwords (all passwords are '1') +INSERT INTO UserPassword (account, password) VALUES +('2', '1'), +('9222', '1'), +('0', '1'); \ No newline at end of file diff --git a/entry/src/main/ets/common/DatabaseService.ets b/entry/src/main/ets/common/DatabaseService.ets new file mode 100644 index 0000000..0c52da1 --- /dev/null +++ b/entry/src/main/ets/common/DatabaseService.ets @@ -0,0 +1,415 @@ +import { hilog } from '@kit.PerformanceAnalysisKit'; +import prompt from '@ohos.promptAction'; +import http from '@ohos.net.http'; + +// User model for storing user information from database +export class UserModel { + account: string = ''; + nickname: string = ''; + email: string = ''; + phone: string = ''; + photo: string = ''; + category: string = ''; + + constructor(account: string = '', + nickname: string = '', + email: string = '', + phone: string = '', + photo: string = '', + category: string = '') { + this.account = account; + this.nickname = nickname; + this.email = email; + this.phone = phone; + this.photo = photo || 'http://139.155.155.67:2342/images/default_avatar.png'; // Default photo URL + this.category = category; + } +} + +// API响应接口定义 +export interface ApiResponse { + success: boolean; + message?: string; + user?: UserInfo; +} + +// User info returned from API +export interface UserInfo { + account: string; + nickname: string; + email: string; + phone: string; + photo?: string; + category: string; +} + +// API服务配置 +export interface ApiConfig { + baseUrl: string; + timeout: number; +} + +// 定义API服务配置 +const API_CONFIG: ApiConfig = { + // 修改为用户实际使用的服务器地址 + baseUrl: 'http://139.155.155.67:2342/api', + timeout: 10000 // 10秒超时 +}; + +// 修正JSON响应类型处理 +export type JsonResponse = string | object | ArrayBuffer; + +// 真实API实现 - 连接到后端服务 +export class DatabaseService { + private static instance: DatabaseService; + // 缓存用户数据,减少API调用 + private userCache: Map = new Map(); + // 缓存验证结果 + private authCache: Map = new Map(); + + // Callbacks for user data changes + private userDataChangeCallbacks: (() => void)[] = []; + + private constructor() { + // 初始化缓存 + hilog.info(0, 'ClassMG', 'Database Service Initialized'); + } + + public static getInstance(): DatabaseService { + if (!DatabaseService.instance) { + DatabaseService.instance = new DatabaseService(); + } + return DatabaseService.instance; + } + + // 创建HTTP请求客户端 + private createHttpClient(): http.HttpRequest { + let httpRequest = http.createHttp(); + httpRequest.on('headersReceive', (header) => { + hilog.debug(0, 'ClassMG', `Headers received: ${JSON.stringify(header)}`); + }); + return httpRequest; + } + + // 执行登录验证 + public async validateUser(account: string, password: string): Promise { + try { + // 生成缓存键 + const cacheKey = `${account}:${password}`; + + // 检查缓存 + if (this.authCache.has(cacheKey)) { + const cachedResult = this.authCache.get(cacheKey); + return cachedResult === true; // 确保返回布尔值 + } + + // 创建HTTP客户端 + const httpRequest = this.createHttpClient(); + + // 准备登录数据 + let loginData = {} as Record; + loginData.account = account; + loginData.password = password; + + // 发送登录请求 + const response = await httpRequest.request( + `${API_CONFIG.baseUrl}/login`, + { + method: http.RequestMethod.POST, + header: { + 'Content-Type': 'application/json' + }, + extraData: JSON.stringify(loginData), + connectTimeout: API_CONFIG.timeout, + readTimeout: API_CONFIG.timeout + } + ); + + // 释放HTTP客户端 + httpRequest.destroy(); + + // 检查响应 + if (response.responseCode === 200) { + // 使用显式类型转换处理JSON结果 + const jsonString: string = response.result ? response.result.toString() : '{}'; + try { + const result: ApiResponse = JSON.parse(jsonString) as ApiResponse; + + // 缓存结果 + const isValid = result.success === true; + this.authCache.set(cacheKey, isValid); + + // 如果登录成功且有用户数据,缓存用户信息 + if (isValid && result.user) { + const user = new UserModel( + result.user.account, + result.user.nickname, + result.user.email, + result.user.phone, + result.user.photo, + result.user.category + ); + this.userCache.set(account, user); + } + + return isValid; + } catch (parseError) { + const errorMsg: string = parseError instanceof Error ? parseError.message : String(parseError); + hilog.error(0, 'ClassMG', `Error parsing JSON response: ${errorMsg}`); + } + } + + // 默认返回false + return false; + } catch (error) { + hilog.error(0, 'ClassMG', `Error validating user: ${error instanceof Error ? error.message : String(error)}`); + + // 临时降级到模拟验证 - 仅用于开发/测试 + if (account === '2' || account === '9222' || account === '0') { + return password === '1'; + } + + return false; + } + } + + // 获取用户类别 + public getUserCategory(account: string): string { + // 检查缓存 + if (this.userCache.has(account)) { + const user = this.userCache.get(account); + return user ? user.category : ''; + } + + // 降级逻辑 - 当API不可用时 + if (account.includes('2')) { + return 'student'; + } else if (account.includes('0')) { + return 'teacher'; + } + + return ''; + } + + // 获取用户昵称 + public getUserNickname(account: string): string { + // 检查缓存 + if (this.userCache.has(account)) { + const user = this.userCache.get(account); + return user ? user.nickname : ''; + } + + // 默认值 - 仅用于开发/测试 + if (account === '2') return '张三'; + if (account === '9222') return '李华'; + if (account === '0') return '教师demo'; + + return ''; + } + + // 异步获取用户信息 + public async getUserByAccountAsync(account: string): Promise { + try { + // 检查缓存 + if (this.userCache.has(account)) { + const cachedUser = this.userCache.get(account); + return cachedUser || null; + } + + // 创建HTTP客户端 + const httpRequest = this.createHttpClient(); + + // 发送获取用户信息请求 + const response = await httpRequest.request( + `${API_CONFIG.baseUrl}/user/${account}`, + { + method: http.RequestMethod.GET, + connectTimeout: API_CONFIG.timeout, + readTimeout: API_CONFIG.timeout + } + ); + + // 释放HTTP客户端 + httpRequest.destroy(); + + // 检查响应 + if (response.responseCode === 200) { + // 使用显式类型转换处理JSON结果 + const jsonString: string = response.result ? response.result.toString() : '{}'; + try { + const result: ApiResponse = JSON.parse(jsonString) as ApiResponse; + + if (result.success && result.user) { + const user = new UserModel( + result.user.account, + result.user.nickname, + result.user.email, + result.user.phone, + result.user.photo, + result.user.category + ); + + // 更新缓存 + this.userCache.set(account, user); + + return user; + } + } catch (parseError) { + const errorMsg: string = parseError instanceof Error ? parseError.message : String(parseError); + hilog.error(0, 'ClassMG', `Error parsing JSON response: ${errorMsg}`); + } + } + + return null; + } catch (error) { + hilog.error(0, 'ClassMG', `Error getting user: ${error instanceof Error ? error.message : String(error)}`); + + // 降级到模拟数据 - 仅用于开发/测试 + if (account === '2') { + return new UserModel('2', '张三', 'student@qq.com', '17267383831', '', 'student'); + } else if (account === '9222') { + return new UserModel('9222', '李华', 'student123@qq.com', '12345678901', '', 'student'); + } else if (account === '0') { + return new UserModel('0', '教师demo', 'teach@qq.com', '', '', 'teacher'); + } + + return null; + } + } + + // 为了兼容性保留的同步方法 - 使用缓存 + public getUserByAccount(account: string): UserModel | null { + // 检查缓存 + if (this.userCache.has(account)) { + const cachedUser = this.userCache.get(account); + return cachedUser || null; + } + + // 如果缓存中没有,返回默认模拟数据 + if (account === '2') { + return new UserModel('2', '张三', 'student@qq.com', '17267383831', '', 'student'); + } else if (account === '9222') { + return new UserModel('9222', '李华', 'student123@qq.com', '12345678901', '', 'student'); + } else if (account === '0') { + return new UserModel('0', '教师demo', 'teach@qq.com', '', '', 'teacher'); + } + + // 触发异步加载并返回null + this.getUserByAccountAsync(account) + .then((user: UserModel | null) => { + if (user) { + this.notifyUserDataChange(); + } + }) + .catch((error: Error | string | Object) => { + const errorMsg: string = error instanceof Error ? error.message : String(error); + hilog.error(0, 'ClassMG', `Error in async user fetch: ${errorMsg}`); + }); + + return null; + } + + // 更新用户邮箱 - 连接API + public async updateUserEmail(account: string, newEmail: string): Promise { + try { + // 验证邮箱格式 + if (!this.validateEmailFormat(newEmail)) { + return false; + } + + // 创建HTTP客户端 + const httpRequest = this.createHttpClient(); + + // 准备更新数据 + let updateData = {} as Record; + updateData.email = newEmail; + + // 发送更新请求 + const response = await httpRequest.request( + `${API_CONFIG.baseUrl}/user/${account}/email`, + { + method: http.RequestMethod.PUT, + header: { + 'Content-Type': 'application/json' + }, + extraData: JSON.stringify(updateData), + connectTimeout: API_CONFIG.timeout, + readTimeout: API_CONFIG.timeout + } + ); + + // 释放HTTP客户端 + httpRequest.destroy(); + + // 检查响应 + if (response.responseCode === 200) { + // 使用显式类型转换处理JSON结果 + const jsonString: string = response.result ? response.result.toString() : '{}'; + try { + const result: ApiResponse = JSON.parse(jsonString) as ApiResponse; + + if (result.success) { + // 更新本地缓存 + if (this.userCache.has(account)) { + const user = this.userCache.get(account); + if (user) { + user.email = newEmail; + this.userCache.set(account, user); + } + } + + // 通知更新 + this.notifyUserDataChange(); + + return true; + } + } catch (parseError) { + const errorMsg: string = parseError instanceof Error ? parseError.message : String(parseError); + hilog.error(0, 'ClassMG', `Error parsing JSON response: ${errorMsg}`); + } + } + + return false; + } catch (error) { + const errorMessage: string = error instanceof Error ? error.message : String(error); + hilog.error(0, 'ClassMG', `Error updating user email: ${errorMessage}`); + + // 降级到本地更新 - 仅用于开发/测试 + if (this.userCache.has(account)) { + const user = this.userCache.get(account); + if (user) { + user.email = newEmail; + this.userCache.set(account, user); + this.notifyUserDataChange(); + return true; + } + } + + return false; + } + } + + // 注册数据变化回调 + public registerUserDataChangeCallback(callback: () => void): void { + this.userDataChangeCallbacks.push(callback); + } + + // 通知所有回调 + private notifyUserDataChange(): void { + this.userDataChangeCallbacks.forEach(callback => { + callback(); + }); + } + + // 验证邮箱格式 + public validateEmailFormat(email: string): boolean { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return emailRegex.test(email); + } + + // 清除缓存 + public clearCache(): void { + this.userCache.clear(); + this.authCache.clear(); + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/TransitionPage.ets b/entry/src/main/ets/pages/TransitionPage.ets new file mode 100644 index 0000000..0300c37 --- /dev/null +++ b/entry/src/main/ets/pages/TransitionPage.ets @@ -0,0 +1,260 @@ +import { router } from '@kit.ArkUI'; +import settingsService from '../common/SettingsService'; +import logManager, { LogCategory, LogEventType } from '../common/logtext'; + +// 粒子信息类型 +class ParticleInfo { + x: number = 0; + y: number = 0; + size: number = 0; + opacity: number = 0; + speed: number = 0; + color: string = '#ffffff'; + + constructor(x: number = 0, + y: number = 0, + size: number = 0, + opacity: number = 0, + speed: number = 0, + color: string = '#ffffff') { + this.x = x; + this.y = y; + this.size = size; + this.opacity = opacity; + this.speed = speed; + this.color = color; + } +} + +@Entry +@Component +struct TransitionPage { + @State userName: string = ''; + @State welcomeMessage: string = '欢迎使用智慧教室管理系统'; + @State contentOpacity: number = 0; + @State scaleValue: number = 0.8; + @State rotationAngle: number = -10; + @State translateYValue: number = 100; + + // 为粒子动画定义状态 + @State particles: ParticleInfo[] = []; + private particleCount: number = 30; // 降低粒子数量避免性能问题 + private particleTimer: number = 0; + private bgColorStop: number = 0; + @State backgroundGradient: string = 'linear-gradient(45deg, #4c71f2, #42a7f0)'; + + // 创建粒子 + private createParticles() { + let newParticles: ParticleInfo[] = []; + for (let i = 0; i < this.particleCount; i++) { + let particle = new ParticleInfo( + Math.random() * 100, // x位置 (百分比) + Math.random() * 100, // y位置 (百分比) + Math.random() * 8 + 2, // 粒子大小 + Math.random() * 0.6 + 0.1, // 初始透明度 + Math.random() * 0.5 + 0.1, // 移动速度 + i % 3 === 0 ? '#ffffff' : (i % 3 === 1 ? '#aaccff' : '#77aaff') // 随机颜色 + ); + newParticles.push(particle); + } + this.particles = newParticles; + } + + // 更新粒子动画 + private updateParticles() { + this.particleTimer = setInterval(() => { + let updatedParticles: ParticleInfo[] = []; + + for (let i = 0; i < this.particles.length; i++) { + let p = this.particles[i]; + // 更新位置 + let newY = p.y - p.speed; + if (newY < -5) { + newY = 105; // 当粒子移出屏幕顶部时,从底部重新进入 + p.x = Math.random() * 100; + } + + let newOpacity = p.opacity + (Math.random() * 0.1 - 0.05); + // 确保透明度在合理范围内 + if (newOpacity < 0.1) newOpacity = 0.1; + if (newOpacity > 0.8) newOpacity = 0.8; + + // 创建更新后的粒子对象 + let updatedParticle = new ParticleInfo( + p.x, + newY, + p.size, + newOpacity, + p.speed, + p.color + ); + + updatedParticles.push(updatedParticle); + } + + this.particles = updatedParticles; + + // 更新背景渐变 + this.bgColorStop = (this.bgColorStop + 0.005) % 1; + const hue1 = Math.floor((220 + Math.sin(this.bgColorStop * Math.PI * 2) * 20) % 360); + const hue2 = Math.floor((190 + Math.cos(this.bgColorStop * Math.PI * 2) * 20) % 360); + this.backgroundGradient = `linear-gradient(45deg, hsl(${hue1}, 70%, 60%), hsl(${hue2}, 70%, 60%))`; + + }, 50); + } + + aboutToAppear() { + // 设置用户名称 + this.userName = settingsService.getUserNickname(); + + // 创建并开始粒子动画 + this.createParticles(); + this.updateParticles(); + + // 第一阶段动画:元素进入 + animateTo({ duration: 1000, curve: Curve.Ease }, () => { + this.contentOpacity = 1; + this.scaleValue = 1; + this.rotationAngle = 0; + this.translateYValue = 0; + }); + + // 3秒后开始退出动画 + setTimeout(() => { + // 第二阶段动画:放大效果 + animateTo({ duration: 300, curve: Curve.EaseIn }, () => { + this.scaleValue = 1.1; + this.rotationAngle = 0; + }); + + // 第三阶段动画:淡出效果 + setTimeout(() => { + animateTo({ duration: 700, curve: Curve.EaseOut }, () => { + this.contentOpacity = 0; + this.scaleValue = 0.5; + this.translateYValue = -50; + }); + + // 等待动画结束后跳转 + setTimeout(() => { + // 停止粒子动画定时器 + clearInterval(this.particleTimer); + router.pushUrl({ + url: 'pages/HomePage' + }); + }, 700); + }, 300); + }, 3000); + + // 记录过渡 + this.Log_Event('aboutToAppear'); + } + + aboutToDisappear() { + // 清除粒子动画定时器 + if (this.particleTimer) { + clearInterval(this.particleTimer); + } + this.Log_Event('aboutToDisappear'); + } + + build() { + Column() { + // 粒子动画层 + Stack() { + // 渐变背景 + Column() + .width('100%') + .height('100%') + .backgroundImage(this.backgroundGradient) + + // 粒子效果 + ForEach(this.particles, (item: ParticleInfo) => { + Circle() + .fill(item.color) + .width(item.size) + .height(item.size) + .position({ x: `${item.x}%`, y: `${item.y}%` }) + .opacity(item.opacity) + }) + + // 内容层 + Column() { + Image('http://139.155.155.67:2342/images/default_avatar.png') + .width(120) + .height(120) + .borderRadius(60) + .margin({ bottom: 40 }) + + Text(this.userName) + .fontSize(42) + .fontWeight(FontWeight.Bold) + .fontColor('#FFFFFF') + .margin({ bottom: 20 }) + + Text(this.welcomeMessage) + .fontSize(26) + .fontColor('rgba(255, 255, 255, 0.9)') + + Row() + .width(150) + .height(4) + .backgroundColor('#FFFFFF') + .borderRadius(2) + .margin({ top: 30 }) + .opacity(0.7) + } + .width('100%') + .height('100%') + .alignItems(HorizontalAlign.Center) + .justifyContent(FlexAlign.Center) + .opacity(this.contentOpacity) + .scale({ x: this.scaleValue, y: this.scaleValue }) + .rotate({ angle: this.rotationAngle }) + .translate({ y: this.translateYValue }) + } + .width('100%') + .height('100%') + } + .width('100%') + .height('100%') + } + + // 页面生命周期方法 + onPageShow(): void { + this.Log_Event('onPageShow'); + } + + onPageHide(): void { + this.Log_Event('onPageHide'); + } + + onBackPress(): boolean { + this.Log_Event('onBackPress'); + return true; // 防止返回 + } + + // 日志记录方法 + public Log_Event(eventName: string): void { + // 根据事件名称选择合适的日志事件类型 + switch (eventName) { + case 'aboutToAppear': + logManager.info(LogCategory.USER, LogEventType.PAGE_APPEAR, 'TransitionPage'); + break; + case 'aboutToDisappear': + logManager.info(LogCategory.USER, LogEventType.PAGE_DISAPPEAR, 'TransitionPage'); + break; + case 'onPageShow': + logManager.info(LogCategory.USER, LogEventType.PAGE_SHOW, 'TransitionPage'); + break; + case 'onPageHide': + logManager.info(LogCategory.USER, LogEventType.PAGE_HIDE, 'TransitionPage'); + break; + case 'onBackPress': + logManager.info(LogCategory.USER, LogEventType.PAGE_BACK, 'TransitionPage'); + break; + default: + logManager.info(LogCategory.USER, LogEventType.SYSTEM_INFO, `TransitionPage: ${eventName}`); + } + } +} \ No newline at end of file