-
基于远程开发环境部署Flask与开发者空间GaussDB的实践应用构建在线TODO清单(保姆级指南实战案例)案例介绍本案例将演示如何在华为开发者空间的远程云开发环境中部署 Flask 应用,并与 GaussDB 数据库 对接,实现一个基础的 TODO 清单 Web 应用。通过该实践,开发者能够掌握以下技能:在云开发环境中快速搭建 Flask 项目;配置并连接 GaussDB 数据库;使用 Flask ORM 完成数据库建表与数据操作;实现一个可运行的 TODO 清单应用。这种结合方式能够帮助开发者快速上手 Web 开发,并体验华为开发者空间提供的一站式开发环境。案例内容本案例将演示如何在华为开发者空间的远程云开发环境中部署 Flask 应用,并与 GaussDB 数据库 对接,实现一个基础的 TODO 清单 Web 应用。通过该实践,开发者能够掌握以下技能:在云开发环境中快速搭建 Flask 项目;配置并连接 GaussDB 数据库;使用 Flask ORM 完成数据库建表与数据操作;实现一个可运行的 TODO 清单应用。这种结合方式能够帮助开发者快速上手 Web 开发,并体验华为开发者空间提供的一站式开发环境。一、概述在现代 Web 应用开发中,快速迭代与云端部署 已成为常态。传统的本地开发环境虽然灵活,但在协作效率、资源配置以及数据库运维方面往往存在一定的局限。为了解决这些问题,越来越多的开发者开始借助 远程开发环境 与 云数据库服务 来完成应用构建。Flask 作为一个轻量化的 Python Web 框架,因其简单、灵活、可扩展的特性,广泛应用于原型验证和小型应用开发。而华为开发者空间所提供的 GaussDB 云数据库 与 云开发环境,为开发者提供了一体化的开发体验:无需繁琐配置,即可在云端完成代码编写、数据库对接和应用部署。本案例将通过构建一个 TODO 清单应用,演示如何在远程云开发环境中部署 Flask,并与 GaussDB 结合,实现一个完整的从环境搭建到应用落地的流程。通过该实践,开发者不仅能够熟悉 Flask 与数据库交互的核心方法,还能深入体验基于华为云生态的高效开发模式。1.1 案例介绍Flask 是一个轻量级 Python Web 框架,以简洁、灵活著称,非常适合快速构建小型应用或原型系统。本案例结合 Flask 与 GaussDB,通过远程开发环境完成从环境搭建到应用部署的全流程操作。最终效果:在浏览器访问 Flask 服务;可在页面中创建和查看 TODO 任务;数据持久化存储在 GaussDB 数据库中。1.2 适用对象中小企业希望快速验证产品原型;个人开发者尝试云端开发与数据库应用;高校学生学习 Flask 与数据库交互实践。1.3 案例时间预计总时长:约 40 分钟。1.4 案例流程1.在华为开发者空间申请云开发环境;2.配置 Flask 运行环境并安装依赖包;3.申请并绑定 GaussDB 数据库实例;4.在 Flask 中配置数据库连接;5。启动 Flask Web 服务并完成 TODO 清单应用测试。1.5 资源总览资源名称规格费用使用时长云开发环境鲲鹏通用计算增强型 kc1免费40分钟GaussDB 数据库4 vCPUs / 16G免费40分钟公网 EIP5Mbit/s0.8元/GB按流量计费二、案例准备工作2.1 配置云开发环境1.登录华为开发者空间;2.选择创建远程云开发环境;3.创建开发环境创建远程开发环境时需要填写以下信息:环境名称(必填):支持字母、数字和下划线,长度不超过 15 个字符,且不能以数字开头。开放端口(可选):默认为 22 端口,可额外开放 8080-8089 范围内的端口。默认账号(必填):默认为 developer,可自定义,需为 4~16 位小写字母,禁止使用系统保留用户名(如 root、agent)。密码(必填):需为 8~32 位字符,且至少包含 大写字母、小写字母、数字、特殊字符(~!@#$%^*-_=+?) 中的三种类型。4.将服务开机5.安装 CLI 工具包hdspace.exe 是一款面向开发者的命令行工具(CLI),用于在 PC 端对 开发者空间云开发环境 进行创建与管理。借助该工具,开发者不仅可以快速建立与云开发环境的隧道连接,还能实现远程操作,例如:上传/下载文件编写与编辑代码编译与执行程序下载方式在 Web 端进入已创建的云开发环境,点击 【远程连接】 → 【立即下载】,即可将 hdspace.exe 工具包下载到本地。6.配置环境变量以 Windows 11 为例,配置步骤如下:在任务栏搜索框输入 “环境变量”,或在 控制面板 → 系统和安全 → 系统 → 高级系统设置 中找到 环境变量 入口。在弹出的窗口中,找到 系统变量 下的 Path,点击 编辑。在 Path 配置界面中,点击 新建,输入 hdspace.exe 所在目录的路径。确认保存设置并退出。完成上述操作后,用户即可在 任意命令行窗口(如 CMD、PowerShell)中直接使用 hdspace.exe 命令。安装成功截图如下常用命令如下含义命令云开发环境顶层帮助hdspace devenv --help开发者空间配置帮助hdspace config --help开发者空间版本帮助hdspace version --help云开发环境列表帮助hdspace devenv list --help创建云开发环境帮助hdspace devenv create --help启动云开发环境帮助hdspace devenv start –help关闭云开发环境帮助hdspace devenv close --help删除云开发环境帮助hdspace devenv delete --help建立隧道帮助hdspace devenv start-tunnel --help7.获取并配置 AK/SK在使用 CLI 工具操作云开发环境前,需要完成 身份认证。这依赖于 AK(Access Key) 与 SK(Secret Key):AK(Access Key):访问密钥的唯一 ID,用于标识用户身份,通常可在网络传输中公开。SK(Secret Key):秘密密钥,仅用户与服务端持有,用于生成请求签名,确保请求合法性并防止未授权访问。配置 AK/SK 等价于在 CLI 工具中完成身份认证,从而使开发者能够通过命令行安全地管理和操作云开发环境。8.获取 AK/SK在下载的文件中可找到 AK/SK,格式类似如下:配置 AK/SK在命令行中执行以下命令:hdspace config根据提示依次输入 AK 和 SK:输入 AK 后回车输入 SK(注意:SK 输入时不会显示回显),并再次输入确认配置完成后,CLI 工具即可正常使用身份认证信息访问云开发环境。9.查看可用云开发环境信息2.2 领取GaussDB数据库目前可以免费领取GaussDB在线试用版(2025年 06月 21日 - 2025年 12月 31日)。当报名申请审核通过后,进入 开发者空间工作台。此时页面会1.显示 GaussDB 免费试用 提示,点击 立即开通 即可完成开通操作。若需远程访问数据库实例,必须为其绑定弹性公网 EIP(EIP 需单独购买,按需计费,价格约为 0.33 元/小时)。在控制台中,点击目标数据库实例名称,即可进入 GaussDB 基本信息 页面:2.登录GaussDB数据库回到云数据库控制台,登录数据库:填写密码后测试成功:3.成功登录页面如下4.点击绑定弹性公网IP,就可远程使用。三、在线TODO清单应用搭建3.1 项目背景功能分析这是一个基于Flask框架和GaussDB数据库构建的在线TODO清单网站,提供简洁美观的界面和完整的任务管理功能。功能特点📝 添加任务:创建新的待办事项,支持标题和详细描述✅ 完成任务:一键标记任务为已完成/未完成🗑️ 删除任务:轻松移除不需要的任务📋 任务列表:按创建时间倒序显示所有任务💬 消息提示:操作反馈及时显示📱 响应式设计:适配各种屏幕尺寸🎨 现代UI:采用Tailwind CSS构建的简洁美观界面项目结构GaussDB/ ├── app.py # 主应用程序文件 ├── requirements.txt # 项目依赖 ├── .env # 环境变量配置 ├── .gitignore # Git忽略规则 ├── templates/ # HTML模板目录 │ └── index.html # 主页面模板 └── README.md # 项目说明文档 3.2 GaussDB数据库样例数据如下: (1, '学习GaussDB基础', '了解GaussDB的基本概念和架构', FALSE), (2, '创建TODO应用', '使用Flask和GaussDB构建简单的TODO应用', FALSE), (3, '配置数据库连接', '确保应用能够正确连接到GaussDB', TRUE), (4, '测试SQL执行', '验证各种SQL语句在GaussDB中的执行情况', FALSE), (5, '解决权限问题', '修复创建表和序列时的权限错误', FALSE); 完整的GaussDB数据库执行命令如下:-- GaussDB schema 权限问题解决方案 -- 1. 检查当前连接使用的schema SELECT current_schema(); -- 2. 查看可用的schema列表 SELECT nspname FROM pg_namespace; -- 3. 尝试切换到public schema(如果有权限) SET search_path TO public; -- 4. 在public schema中创建todo表 CREATE TABLE public.todo ( id INT NOT NULL PRIMARY KEY, title VARCHAR(100) NOT NULL, description TEXT, completed BOOLEAN NOT NULL DEFAULT FALSE, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); -- 5. 插入样例数据 INSERT INTO public.todo (id, title, description, completed) VALUES (1, '学习GaussDB基础', '了解GaussDB的基本概念和架构', FALSE), (2, '创建TODO应用', '使用Flask和GaussDB构建简单的TODO应用', FALSE), (3, '配置数据库连接', '确保应用能够正确连接到GaussDB', TRUE), (4, '测试SQL执行', '验证各种SQL语句在GaussDB中的执行情况', FALSE), (5, '解决权限问题', '修复创建表和序列时的权限错误', FALSE); -- 6. 验证插入结果 SELECT * FROM public.todo; -- 使用说明: -- 1. 执行此脚本前,请确保您有权限访问和使用public schema -- 2. 如果切换schema失败,请联系数据库管理员授予适当的权限 -- 3. 如果仍然无法创建表,可能需要管理员在特定schema中为您创建表结构 这份脚本主要用于解决 GaussDB 中 schema 权限不足 导致的建表问题。流程包括:首先检查当前连接所使用的 schema,并查看可用的 schema 列表;若有权限则切换到 public schema,在其中创建 todo 表并插入示例数据;最后通过查询验证插入结果。使用时需确保用户具备 public schema 的访问与操作权限,如仍遇到权限限制,则需联系数据库管理员授予相应权限或由管理员在目标 schema 下预建表结构。3.3 todo清单构建创建并使用一个简单的 TODO 清单表,以实现任务管理的基本功能。主要步骤包括:选择合适的 schema、创建表结构、插入示例数据以及验证数据。选择或切换 schema通过 current_schema() 查看当前连接的 schema,使用 SET search_path TO public; 切换到 public schema(前提是具有权限)。创建 TODO 表表结构设计包括 id(主键)、title、description、completed 状态、created_at 和 updated_at 时间戳等字段,支持记录任务详情及状态跟踪。插入示例数据向 todo 表中插入若干任务条目,用于后续的测试和展示。验证与查询通过 SELECT * FROM public.todo; 检查数据是否成功写入,并可根据 completed 状态或其他字段进行筛选和排序,实现基本的任务管理功能。这种方法不仅便于快速搭建实验性 TODO 应用,也为后续通过应用程序(如 Flask)对 GaussDB 的访问提供了数据基础。增加数据删除数据更新数据GaussDB数据库信息在本节中,我们通过 GaussDB 成功构建了一个简单的 TODO 清单表,实现了任务的增删改查操作。首先,确保使用具有权限的 schema(如 public),然后创建包含任务 ID、标题、描述、状态和时间戳等字段的表结构。接着,通过插入示例数据完成初始填充,并通过查询验证数据的正确性。同时展示了增、删、改操作的实际效果,使得用户能够直观了解 GaussDB 对任务数据的管理能力。整体流程为后续基于应用程序(如 Flask)构建完整的 TODO 应用打下了坚实的数据基础,也体现了 GaussDB 在日常开发中灵活、高效的数据库操作能力。四.心得通过本案例的实践,我们可以获得以下几点重要心得与经验:4.1 云开发环境的优势与体验快速搭建与即开即用华为开发者空间提供的云开发环境无需复杂的本地配置,只需创建环境并启动,即可开始编码与调试。这对于中小团队或个人开发者尤其方便,能够节省环境配置时间,并避免“环境不一致”问题。一体化的开发体验在同一平台下完成代码开发、数据库管理、依赖安装和远程调试,极大提升了开发效率。同时,CLI 工具 hdspace.exe 提供了命令行管理、文件上传下载、隧道连接等功能,使得远程操作更加灵活便捷。云端协作与可扩展性通过云开发环境,团队成员可以共享同一开发环境,实现代码与数据的一体化管理。环境资源可按需扩展,例如增加计算或存储能力,非常适合原型验证和小型项目开发。4.2 GaussDB 数据库的使用体会权限管理与 schema 问题GaussDB 默认 schema 权限可能会影响建表和数据操作。通过切换到 public schema 或联系管理员授予权限,可以顺利完成数据库建表、插入数据和查询操作。这提醒开发者在云数据库使用中,应充分了解权限体系与 schema 管理策略。数据操作便捷无论是通过 SQL 脚本插入样例数据,还是在 Flask 应用中进行增删改查,GaussDB 的执行效率和稳定性表现良好。在实际应用中,开发者可以快速验证数据操作,保证前端应用的响应速度和数据一致性。云数据库安全与远程访问绑定弹性公网 EIP 后可实现远程访问,结合 AK/SK 配置可确保操作安全。开发者在生产环境中应注意安全策略,例如限制 IP、启用访问控制和加密连接,确保数据安全。4.3 Flask 与数据库结合的实践经验轻量级快速开发Flask 框架以简洁著称,非常适合快速构建原型应用。在本案例中,通过少量代码就完成了 TODO 清单的页面展示、任务增删改查以及数据持久化,体现了 Flask 的灵活性。ORM 与数据库交互在实际开发中,可以使用 Flask 的 ORM(如 SQLAlchemy)进行数据库操作,使数据操作更加直观、可维护。通过本案例,我们掌握了基本的 ORM 映射与查询方法,为后续复杂应用打下基础。前后端分离与响应式设计本案例采用 Tailwind CSS 构建界面,简洁美观且自适应不同设备。结合 Flask 提供的数据接口,可进一步扩展为前后端分离的 Web 应用,实现更丰富的功能。4.4 总结与经验分享小型应用原型验证本案例演示了如何在 40 分钟内完成一个完整的 TODO 清单应用,从环境搭建到数据库对接,再到应用部署,充分体现了云开发环境的高效性和可操作性。注意权限与配置在使用云数据库时,应提前了解 schema 权限、AK/SK 配置及公网访问策略,避免在开发过程中因权限问题导致操作受限。可拓展性与实践价值本案例可以作为其他小型 Web 应用的模板,例如博客系统、在线笔记、任务管理工具等。开发者可在此基础上进一步扩展功能,例如用户登录、任务分类、提醒机制等。云开发与本地开发结合尽管云开发环境方便快捷,但在处理大规模数据或复杂计算时,本地开发环境仍有其优势。建议开发者根据项目需求灵活选择开发方式,云端和本地相结合,以提高开发效率。通过本案例,开发者不仅掌握了 Flask 与 GaussDB 的基础使用方法,也积累了远程云开发环境实践经验,为后续更复杂 Web 应用开发奠定了坚实基础。我正在参加【案例共创】第6期 开发者空间-基于云开发环境和GaussDB构建应用 https://bbs.huaweicloud.com/forum/thread-0229189398343651003-1-1.html
-
物化视图后续版本是否可以支持自动刷新
-
案例介绍 “旅游小助手”微信小程序是一款帮助旅游者,在途中随时拍照后关联一首此景点的唐诗宋词,增长旅游者的文化素养。 开发工具是使用一个微信小程序开发工具IDE,申请了APP ID,可以专注于编写应用程序,最后在平台上仿真模拟,再上传发布。本案例在微信小程序开发工具上开发了应用功能部分,并与开发者空间提供的免费GaussDB数据库实例对接,完成一个小程序开发。华为开发者空间是为全球开发者打造的专属开发者空间,致力于为每位开发者提供一台云开发环境、一套开发工具和云上存储空间,汇聚昇腾、鸿蒙、鲲鹏、GaussDB、欧拉等华为各项根技术的开发工具资源,并提供配套案例指导开发者 从开发编码到应用调测,基于华为根技术生态高效便捷的知识学习、技术体验、应用创新。 资源总览本案例预计花费0元。资源名称规格单价(元)时长(分钟)华为开发者空间 - 云开发环境鲲鹏通用计算增强型 kc1 | 2vCPUs | 4G | euler,22免费60华为开发者空间 - 生态版GaussDB单副本集中式版 | 4 vCPUs | 16G | HCE OS 64bit (200GB)免费60微信开发工具平台stable 1.06.202504020免费 案例适用对象和开发时间1.1 适用对象企业个人开发者高校学生1.2 案例时间本案例总时长预计1个小时。案例方案1.3任务分析小程序连接华为云GaussDB数据库,用于存储识别结果或用户数据,由小程序进行用户交互,把数据存放到数据库。1.4 技术方案 使用华为云数据库服务SDK,通过API网关或云函数访问实现增删改查基本操作。首先要创建华为云数据库实例获取连接配置信息编写数据访问层代码,安全考虑,数据库连接信息不能暴露在前端,需要使用HTTPS安全连接,实现访问权限控制。集成方式通过云函数封装数据库操作小程序调用云函数接口。以下是云数据库步骤:1、需要创建数据库表结构,2、配置华为云环境,3、测试数据库连接。方案还要增加安全增强,添加连接池管理,实现参数化查询防注入,添加操作日志记录功能。1.5 方案还要性能优化:连接复用和查询优化错误增加重试机制,包括:需要添加日志管理功能、实现数据库迁移脚本、添加性能监控。其中,日志管理需求:操作审计日志、错误详细记录和查询性能日志;迁移需求: 版本化数据库变更、回滚支持和环境一致性;添加监控指标:连接池状态、查询耗时和错误率。1.6 集成方案需要集成到主流程添加错误处理中间件实现请求追踪1.7 监控扩展告警规则日志分级敏感信息过滤 测试方案部分包括单元测试中间件、集成测试API流程和模拟错误场景1.8 部署准备环境变量配置、启动脚本优化、健康检查接口并且需要集成健康检查到主服务添加API测试用例实现自动化测试流程1.9健康检查集成方案添加/health端点服务启动自检就绪/存活探针最后进行API测试,数据操作测试错误场景覆盖性能基准测试,1.10 CI/CD准备测试脚本配置覆盖率报告质量门禁,同时编写部署文档配置CI/CD流水线压力测试,检查README内容完整性确认部署步骤准确性验证示例命令有效性。注意后续优化,添加更多使用场景示例补充故障排查指南完善API文档细节。 1.11 交付完成打包完整项目生成文档 附录小程序代码结构d:/企业/鸿蒙生态/ ├── app.js ├── app.json├── app.wxss └── pages/ └── index/ ├── index.js ├── index.json ├── index.wxml └── index.wxss项目文件目录结构我正在参加【案例共创】第6期 开发者空间-基于云开发环境和GaussDB构建应用 cid:link_3
-
余年方十七,习编程已有数载,虽非初学,然深知学无止境,技艺之精进,如逆水行舟,不进则退。 技艺渐精 昔日初学时,常为类型错误而苦恼,今已能熟练运用多种语言。ts之强大,Python之优雅,前端三剑客之灵动,后端框架之稳健,皆有所涉猎。数据结构与算法,亦略有心得,排序查找,动态规划,虽不敢言精通,然基本功底尚算扎实。 项目实战 近年来,陆续完成数个项目:网站开发、移动应用、数据分析工具等。每一次从需求分析到架构设计,从编码实现到测试部署,皆亲力亲为。虽过程艰辛,然收获颇丰。始知纸上谈兵易,实际开发难。用户体验、性能优化、代码维护,诸般考量,方显编程之深奥。 开源贡献 偶有小作,贡献于开源社区,虽微不足道,然能为他人所用,心甚慰藉。GitHub上星标虽少,然每一个fork,每一个issue,皆是对余之鞭策与鼓励。 技术视野 关注前沿技术,人工智能、区块链、云计算等,皆有所了解。深知技术日新月异,昨日之新技术,今日或已过时。唯有保持学习之心,方能不被时代所抛弃。 反思与展望 回首来路,虽有所成,然亦深感不足。代码质量有待提升,系统设计能力尚需磨练,团队协作经验亦显匮乏。他日若能进入知名企业,与高手切磋,定能更上一层楼。 夫编程者,匠心独运之艺也。既要有扎实之基础,又需具创新之思维。余虽年少,然志在千里,愿以代码改变世界,以技术服务社会。作者写此文时正值十七岁生日前夕,虽习编程数年,然经验尚浅,见解有限,往有不足之处,请在评论区指出。案例简介:本案例选择bun + Next.js作为示例编写了学生成绩管理系统,并借助开发者空间云开发环境提供的免费 GaussDB数据库和HCE2.0开发环境进行本地部署Next.js生态组件、轻松部署上云,直观地展示在实际应用开发中为开发者带来的便利。一、概述1.1 案例介绍Bun + Next.js + tRPC 是一个现代化的全栈 TypeScript Web 开发技术栈,可以快速开发高性能和可维护的网站。Bun 作为超快的 JavaScript 运行时和包管理器,Next.js 负责处理前端和后端开发中的复杂部分,tRPC 提供端到端类型安全的 API 层。在数据库操作方面,本技术栈支持直接使用原生 SQL 语句进行数据库交互,无需额外的 ORM 抽象层,既保证了查询性能的最优化,又提供了最大的灵活性。让开发者可以专注于编写业务逻辑,而无需重复造轮子。本案例借助开发者空间云开发环境部署 Next.js 项目,通过 tRPC 构建类型安全的 API 接口,并直接使用 SQL 语句与开发者空间提供的免费 GaussDB 数据库实例对接,完成一个现代化 Web 应用构建。通过实际操作,让大家深入了解如何利用 Bun + Next.js + tRPC + 原生 SQL 技术栈开发并部署一个高性能的全栈 Web 应用。华为开发者空间,是为全球开发者打造的专属开发者空间,致力于为每位开发者提供一台 云开发环境、一套开发工具和云上存储空间,汇聚昇腾、鸿蒙、鲲鹏、GaussDB、欧拉 等华为各项根技术的开发工具资源,并提供配套案例指导开发者 从开发编码到应用调 测,基于华为根技术生态高效便捷的知识学习、技术体验、应用创新。1.2 适用对象企业个人开发者初高中学生和高校学生1.3 案例时间本案例总时长预计120分钟。1.4 案例流程本案例将通过三个主要步骤,带领大家在华为云开发者空间上构建一个完整的 Bun + Next.js + tRPC + 原生 SQL 全栈应用:第一步:申请华为云开发者空间的 GaussDB 数据库注册并登录华为云开发者空间申请免费的 GaussDB 数据库实例获取数据库连接配置信息第二步:进行华为云开发者空间的云开发环境进行 Bun + Next.js + tRPC 安装与配置创建云开发环境实例安装 Bun 运行时环境初始化 Next.js TypeScript 项目集成 tRPC 框架,配置端到端类型安全的 API设置数据库连接和环境变量第三步:启用 Next.js Web 服务,适配 GaussDB,在客户端测试原生 SQL 的建表和数据操作编写原生 SQL 建表脚本通过 tRPC 封装数据库操作接口启动 Next.js 开发服务器在客户端测试数据库的增删改查功能验证端到端类型安全和性能表现通过这三个步骤,您将完整体验从环境搭建到应用部署的全流程,深入理解 Bun + Next.js + tRPC + 原生 SQL 技术栈在华为云开发者空间上的强大能力。1.5 资源总览本案例预计花费0.8元。资源名称规格单价(元)时长(分钟)华为开发者空间 - 云开发环境鲲鹏通用计算增强型 kc1 2vCPUs 4G免费40华为开发者空间 - 生态版GaussDB单副本集中式版 4 vCPUs 16G HCE OS 64bit (200GB)免费40弹性公网IP按流量计费 5Mbit/s0.8元/GB40二、案例准备工作2.1 配置云开发环境根据案例《开发者空间 - 云开发环境使用指导》,请查看下面链接,配置云开发环境,并通过xshell等链接工具登录云开发环境。开发者空间 - 云开发环境使用指导2.2 领取GaussDB数据库免费领取GaussDB在线试用版(2025年 06月 21日 - 2025年 12月 31日)。华为开发者空间-GaussDB云数据库领取与使用指导注:部署的Django项目需要对接GaussDB,因此GaussDB需要绑定EIP,参考上述指导中领取部分第(5)步bunx create-next-app@latest2.3 验证云开发环境与GaussDB互通通过xshell或windows命令终端登录云开发环境,使用ping [ip]测试GaussDB能否连接,如下是通的2.4 新建数据库及用户修改dn:password_encryption_type字段为1。0表示采用md5方式对密码加密。1表示采用sha256和md5两种方式分别对密码加密,2表示采用sha256方式对密码加密,3表示采用sm3方式对密码加密。MD5加密算法安全性低,存在安全风险,不建议使用。点击右上角登录数据库管理界面创建数据库使用如下图配置。通过右上角登录GaussDB的SQL操作界面,用如下SQL创建Django链接的登录用户。create user myuser with sysadmin password 'GaussDB@123'; 三、Bun + Next.js + Prisma 安装配置指南3.1 安装 Bun# 全局安装 bun sudo npm install -g bun # 验证安装 bun --version # 检查安装位置 which bun3.2 创建 Next.js 项目# 交互式创建next.js项目 bunx create-next-app@latest看到如下输出即为成功创建Next.js项目3.3测试Next.js是否安装成功# 查看项目结构 ls -la # 启动开发服务器测试 bun dev # 在另一个终端测试(如果需要) curl http://localhost:3000 3.4 安装trpc步骤 1: 安装依赖bun add @trpc/server @trpc/client @trpc/react-query @trpc/next @tanstack/react-query zod superjson依赖说明@trpc/server - 服务器端 tRPC 核心@trpc/client - 客户端 tRPC 核心@trpc/react-query - React Query 集成@trpc/next - Next.js 集成@tanstack/react-query - 数据获取和缓存zod - 类型验证superjson - 序列化(支持 Date、BigInt 等)步骤 2: 创建服务器端配置创建 app/lib/trpc.tsimport { initTRPC } from '@trpc/server'; import superjson from 'superjson'; import { ZodError } from 'zod'; const t = initTRPC.create({ transformer: superjson, errorFormatter({ shape, error }) { return { ...shape, data: { ...shape.data, zodError: error.cause instanceof ZodError ? error.cause.issues : null, }, }; }, }); export const createTRPCRouter = t.router; export const publicProcedure = t.procedure; 配置说明transformer: superjson - 使用 SuperJSON 进行序列化errorFormatter - 自定义错误格式化,支持 Zod 验证错误createTRPCRouter - 创建路由的工厂函数publicProcedure - 公开的 API 过程(无需认证)步骤 3: 创建路由tRPC 路由(Router)是服务端定义 API 端点和业务逻辑的核心组件,它通过类型安全的方式组织和管理所有的远程过程调用,包括查询(query)、变更(mutation)和订阅(subscription),并自动生成 TypeScript 类型定义供客户端使用,实现了从后端到前端的完整类型推断和 API 结构管理。创建主路由 app/lib/routers/_app.tsimport { createTRPCRouter } from '../trpc'; export const appRouter = createTRPCRouter({ }); export type AppRouter = typeof appRouter; 步骤 4: 创建 API 路由处理创建 app/api/trpc/[trpc]/route.tsimport { fetchRequestHandler } from '@trpc/server/adapters/fetch'; import { appRouter } from '../../../lib/routers/_app'; const handler = (req: Request) => fetchRequestHandler({ endpoint: '/api/trpc', req, router: appRouter, createContext: () => ({}), }); export { handler as GET, handler as POST }; 说明使用 Next.js App Router 的 API 路由fetchRequestHandler 处理 HTTP 请求createContext 创建请求上下文(当前为空对象)步骤 5: 创建客户端配置创建 utils/trpc.tsimport { createTRPCReact } from '@trpc/react-query'; import { type AppRouter } from '../app/lib/routers/_app'; export const trpc = createTRPCReact<AppRouter>(); 创建独立客户端 utils/trpc-client.tsimport { createTRPCProxyClient, httpBatchLink } from '@trpc/client'; import superjson from 'superjson'; import { type AppRouter } from '../app/lib/routers/_app'; export const trpcClient = createTRPCProxyClient<AppRouter>({ links: [ httpBatchLink({ url: '/api/trpc', transformer: superjson, }), ], }); 步骤 6: 设置提供者创建 app/providers.tsx'use client'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { httpBatchLink } from '@trpc/client'; import React, { useState } from 'react'; import superjson from 'superjson'; import { trpc } from '../utils/trpc'; export function TRPCProvider({ children }: { children: React.ReactNode }) { const [queryClient] = useState(() => new QueryClient()); const [trpcClient] = useState(() => trpc.createClient({ links: [ httpBatchLink({ url: '/api/trpc', transformer: superjson, }), ], }) ); return ( <trpc.Provider client={trpcClient} queryClient={queryClient}> <QueryClientProvider client={queryClient}>{children}</QueryClientProvider> </trpc.Provider> ); } tRPC Provider 是一个配置组件,用于在客户端应用中设置 tRPC 客户端实例,它负责配置 API 端点、管理请求状态和缓存,并提供端到端的 TypeScript 类型安全,让前端能够直接调用后端函数而无需手动定义 API 接口类型。步骤 7: 集成到应用更新 app/layout.tsximport type { Metadata } from "next"; import { Geist, Geist_Mono } from "next/font/google"; import "./globals.css"; import { TRPCProvider } from "./providers"; const geistSans = Geist({ variable: "--font-geist-sans", subsets: ["latin"], }); const geistMono = Geist_Mono({ variable: "--font-geist-mono", subsets: ["latin"], }); export const metadata: Metadata = { title: "Create Next App", description: "Generated by create next app", }; export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( <html lang="en"> <body className={`${geistSans.variable} ${geistMono.variable} antialiased`} > <TRPCProvider>{children}</TRPCProvider> </body> </html> ); } 3.5 连接和初始化数据库并编写相关方法步骤1:安装sql相关依赖bun add pg dotenv bun add -d @types/bun @types/pg tsxdotenv 是一个零依赖的模块,它可以从 .env 文件中加载环境变量到 process.env 中。步骤2:设置数据库连接创建 .env# GaussDB 数据库配置 DB_HOST=your-gaussdb-host.com DB_PORT=3306 DB_USER=your-username DB_PASSWORD=your-password DB_NAME=your-database-name # 可选配置 DB_MAX_CONNECTIONS=20 说明DB_HOST就是你数据库IPDB_PORT为数据库端口默认是8000DB_USER和DB_PASSWORD就是你刚刚创建的用户的密码步骤3:编写数据库相关代码创建 scripts/init-database.ts 用来初始化数据库import { Client } from 'pg'; import dotenv from 'dotenv'; dotenv.config(); //这里连接到数据库,如果连接失败就去检查你的.env文件 const client = new Client({ host: process.env.DB_HOST || 'localhost', port: parseInt(process.env.DB_PORT || '5432'), database: process.env.DB_NAME || 'student_management', user: process.env.DB_USER || 'postgres', password: process.env.DB_PASSWORD || 'password', }); //这里开始初始化数据库 async function initDatabase() { try { console.log('连接数据库...'); await client.connect(); console.log('数据库连接成功'); // 创建学生表 console.log('创建学生表...'); await client.query(` CREATE TABLE IF NOT EXISTS students ( id SERIAL PRIMARY KEY, student_id VARCHAR(20) UNIQUE NOT NULL, name VARCHAR(100) NOT NULL, gender VARCHAR(10) CHECK (gender IN ('男', '女')), birth_date DATE, class_name VARCHAR(50), phone VARCHAR(20), email VARCHAR(100), address TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); `); // 创建课程表 console.log('创建课程表...'); await client.query(` CREATE TABLE IF NOT EXISTS courses ( id SERIAL PRIMARY KEY, course_code VARCHAR(20) UNIQUE NOT NULL, course_name VARCHAR(100) NOT NULL, credits DECIMAL(3,1) NOT NULL, teacher_name VARCHAR(100), description TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); `); // 创建成绩表 console.log('创建成绩表...'); await client.query(` CREATE TABLE IF NOT EXISTS grades ( id SERIAL PRIMARY KEY, student_id INTEGER REFERENCES students(id) ON DELETE CASCADE, course_id INTEGER REFERENCES courses(id) ON DELETE CASCADE, semester VARCHAR(20) NOT NULL, score DECIMAL(5,2) CHECK (score >= 0 AND score <= 100), grade VARCHAR(2) CHECK (grade IN ('A+', 'A', 'A-', 'B+', 'B', 'B-', 'C+', 'C', 'C-', 'D+', 'D', 'F')), exam_date DATE, remarks TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE(student_id, course_id, semester) ); `); // 插入测试数据 console.log('插入测试数据...'); // 学生数据 实在想不出来什么好名字了。。。就张三李四吧,为啥都是高三呢因为作者高三了 await client.query(` INSERT INTO students (student_id, name, gender, birth_date, class_name, phone, email) VALUES ('2024001', '张三', '男', '2006-03-15', '高三(1)班', '13800138001', 'zhangsan@example.com'), ('2024002', '李四', '女', '2006-07-22', '高三(1)班', '13800138002', 'lisi@example.com'), ('2024003', '王五', '男', '2006-01-10', '高三(1)班', '13800138003', 'wangwu@example.com'), ON CONFLICT (student_id) DO NOTHING; `); // 课程数据 让高中生学高数是不是很好玩 await client.query(` INSERT INTO courses (course_code, course_name, credits, teacher_name, description) VALUES ('MATH101', '数学', 4.0, '陈老师', '高等数学基础课程'), ('CHI101', '语文', 4.0, '刘老师', '语文基础课程'), ('ENG101', '英语', 4.0, '王老师', '英语基础课程'), ('PHY101', '物理', 3.5, '李老师', '物理基础课程'), ('CHE101', '化学', 3.5, '张老师', '化学基础课程'), ('BIO101', '生物', 3.0, '赵老师', '生物基础课程') ON CONFLICT (course_code) DO NOTHING; `); // 成绩数据 await client.query(` INSERT INTO grades (student_id, course_id, semester, score, grade, exam_date) VALUES (1, 1, '2024-春季', 85.5, 'B+', '2024-06-15'), (1, 2, '2024-春季', 92.0, 'A', '2024-06-16'), (1, 3, '2024-春季', 78.5, 'C+', '2024-06-17'), (2, 1, '2024-春季', 95.0, 'A+', '2024-06-15'), (2, 2, '2024-春季', 88.5, 'B+', '2024-06-16'), (2, 3, '2024-春季', 91.0, 'A', '2024-06-17'), (3, 1, '2024-春季', 72.0, 'C', '2024-06-15'), (3, 2, '2024-春季', 85.0, 'B+', '2024-06-16'), (3, 3, '2024-春季', 79.5, 'C+', '2024-06-17') ON CONFLICT (student_id, course_id, semester) DO NOTHING; `); // 创建索引 console.log('创建索引...'); await client.query(` CREATE INDEX IF NOT EXISTS idx_students_student_id ON students(student_id); CREATE INDEX IF NOT EXISTS idx_students_class_name ON students(class_name); CREATE INDEX IF NOT EXISTS idx_courses_course_code ON courses(course_code); CREATE INDEX IF NOT EXISTS idx_grades_student_id ON grades(student_id); CREATE INDEX IF NOT EXISTS idx_grades_course_id ON grades(course_id); CREATE INDEX IF NOT EXISTS idx_grades_semester ON grades(semester); `); console.log('数据库初始化完成!'); // 显示统计 const stats = await client.query(` SELECT (SELECT COUNT(*) FROM students) as student_count, (SELECT COUNT(*) FROM courses) as course_count, (SELECT COUNT(*) FROM grades) as grade_count; `); console.log(`学生数量: ${stats.rows[0].student_count}`); console.log(`课程数量: ${stats.rows[0].course_count}`); console.log(`成绩记录: ${stats.rows[0].grade_count}`); } catch (error) { console.error('数据库初始化失败:', error); throw error; } finally { await client.end(); console.log('数据库连接已关闭'); } } if (require.main === module) { initDatabase() .then(() => { console.log('数据库初始化完成!'); process.exit(0); }) .catch((error) => { console.error('初始化失败:', error); process.exit(1); }); } export { initDatabase }; 创建 types/database.ts 类型文件// 学生信息 export interface Student { id: number; student_id: string; name: string; gender: '男' | '女'; birth_date: Date | null; class_name: string | null; phone: string | null; email: string | null; address: string | null; created_at: Date; updated_at: Date; } // 课程信息 export interface Course { id: number; course_code: string; course_name: string; credits: number; teacher_name: string | null; description: string | null; created_at: Date; updated_at: Date; } // 成绩信息 export interface Grade { id: number; student_id: number; course_id: number; semester: string; score: number | null; grade: 'A+' | 'A' | 'A-' | 'B+' | 'B' | 'B-' | 'C+' | 'C' | 'C-' | 'D+' | 'D' | 'F' | null; exam_date: Date | null; remarks: string | null; created_at: Date; updated_at: Date; } // 成绩详情(包含学生和课程信息) export interface GradeWithDetails { id: number; student_id: number; course_id: number; semester: string; score: number | null; grade: string | null; exam_date: Date | null; remarks: string | null; student_name: string; student_id_code: string; course_name: string; course_code: string; teacher_name: string | null; created_at: Date; updated_at: Date; } // 学生成绩统计 export interface StudentGradeStats { student_id: number; student_name: string; student_id_code: string; class_name: string | null; total_courses: number; average_score: number; total_credits: number; gpa: number; } // 课程成绩统计 export interface CourseGradeStats { course_id: number; course_name: string; course_code: string; teacher_name: string | null; total_students: number; average_score: number; highest_score: number; lowest_score: number; pass_rate: number; } // 查询参数 export interface StudentQueryParams { page?: number; limit?: number; search?: string; class_name?: string; gender?: '男' | '女'; } export interface GradeQueryParams { page?: number; limit?: number; student_id?: number; course_id?: number; semester?: string; min_score?: number; max_score?: number; } // 创建学生参数 export interface CreateStudentParams { student_id: string; name: string; gender: '男' | '女'; birth_date?: Date; class_name?: string; phone?: string; email?: string; address?: string; } // 更新学生参数 export interface UpdateStudentParams { name?: string; gender?: '男' | '女'; birth_date?: Date; class_name?: string; phone?: string; email?: string; address?: string; } // 创建成绩参数 export interface CreateGradeParams { student_id: number; course_id: number; semester: string; score?: number; grade?: string; exam_date?: Date; remarks?: string; } // 更新成绩参数 export interface UpdateGradeParams { score?: number; grade?: string; exam_date?: Date; remarks?: string; } // 创建课程参数 export interface CreateCourseParams { course_code: string; course_name: string; credits: number; teacher_name?: string; description?: string; } // 更新课程参数 export interface UpdateCourseParams { course_name?: string; credits?: number; teacher_name?: string; description?: string; } 创建lib/database.ts提供相关数据库操作方法import { Pool, PoolClient } from 'pg'; import dotenv from 'dotenv'; import { Student, Course, Grade, GradeWithDetails, StudentGradeStats, CourseGradeStats, StudentQueryParams, GradeQueryParams, CreateStudentParams, UpdateStudentParams, CreateGradeParams, UpdateGradeParams, CreateCourseParams, UpdateCourseParams, } from '../types/database'; dotenv.config(); class DatabaseService { private pool: Pool; constructor() { this.pool = new Pool({ host: process.env.DB_HOST || 'localhost', port: parseInt(process.env.DB_PORT || '5432'), database: process.env.DB_NAME || 'student_management', user: process.env.DB_USER || 'postgres', password: process.env.DB_PASSWORD || 'password', max: 20, idleTimeoutMillis: 30000, connectionTimeoutMillis: 2000, }); // 监听连接错误 this.pool.on('error', (err) => { console.error('数据库连接池错误:', err); }); } // 获取数据库连接 async getClient(): Promise<PoolClient> { return await this.pool.connect(); } // 关闭连接池 async close(): Promise<void> { await this.pool.end(); } // 学生相关方法 async getStudents(params: StudentQueryParams = {}): Promise<Student[]> { const { page = 1, limit = 10, search, class_name, gender } = params; const offset = (page - 1) * limit; let query = 'SELECT * FROM students WHERE 1=1'; const values: any[] = []; let paramIndex = 1; if (search) { query += ` AND (name ILIKE $${paramIndex} OR student_id ILIKE $${paramIndex})`; values.push(`%${search}%`); paramIndex++; } if (class_name) { query += ` AND class_name = $${paramIndex}`; values.push(class_name); paramIndex++; } if (gender) { query += ` AND gender = $${paramIndex}`; values.push(gender); paramIndex++; } query += ` ORDER BY created_at DESC LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`; values.push(limit, offset); const client = await this.getClient(); try { const result = await client.query(query, values); return result.rows; } finally { client.release(); } } async getStudentById(id: number): Promise<Student | null> { const client = await this.getClient(); try { const result = await client.query('SELECT * FROM students WHERE id = $1', [id]); return result.rows[0] || null; } finally { client.release(); } } async getStudentByStudentId(studentId: string): Promise<Student | null> { const client = await this.getClient(); try { const result = await client.query('SELECT * FROM students WHERE student_id = $1', [studentId]); return result.rows[0] || null; } finally { client.release(); } } async createStudent(params: CreateStudentParams): Promise<Student> { const client = await this.getClient(); try { const result = await client.query(` INSERT INTO students (student_id, name, gender, birth_date, class_name, phone, email, address) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING * `, [ params.student_id, params.name, params.gender, params.birth_date, params.class_name, params.phone, params.email, params.address, ]); return result.rows[0]; } finally { client.release(); } } async updateStudent(id: number, params: UpdateStudentParams): Promise<Student | null> { const client = await this.getClient(); try { const fields = Object.keys(params).filter(key => params[key as keyof UpdateStudentParams] !== undefined); if (fields.length === 0) return null; const setClause = fields.map((field, index) => `${field} = $${index + 2}`).join(', '); const values = [id, ...fields.map(field => params[field as keyof UpdateStudentParams])]; const result = await client.query(` UPDATE students SET ${setClause}, updated_at = CURRENT_TIMESTAMP WHERE id = $1 RETURNING * `, values); return result.rows[0] || null; } finally { client.release(); } } async deleteStudent(id: number): Promise<boolean> { const client = await this.getClient(); try { const result = await client.query('DELETE FROM students WHERE id = $1', [id]); return (result.rowCount || 0) > 0; } finally { client.release(); } } // 课程相关方法 async getCourses(): Promise<Course[]> { const client = await this.getClient(); try { const result = await client.query('SELECT * FROM courses ORDER BY course_code'); return result.rows; } finally { client.release(); } } async getCourseById(id: number): Promise<Course | null> { const client = await this.getClient(); try { const result = await client.query('SELECT * FROM courses WHERE id = $1', [id]); return result.rows[0] || null; } finally { client.release(); } } async createCourse(params: CreateCourseParams): Promise<Course> { const client = await this.getClient(); try { const result = await client.query(` INSERT INTO courses (course_code, course_name, credits, teacher_name, description) VALUES ($1, $2, $3, $4, $5) RETURNING * `, [ params.course_code, params.course_name, params.credits, params.teacher_name, params.description, ]); return result.rows[0]; } finally { client.release(); } } async updateCourse(id: number, params: UpdateCourseParams): Promise<Course | null> { const client = await this.getClient(); try { const fields = Object.keys(params).filter(key => params[key as keyof typeof params] !== undefined); if (fields.length === 0) return null; const setClause = fields.map((field, index) => `${field} = $${index + 2}`).join(', '); const values = [id, ...fields.map(field => params[field as keyof typeof params])]; const result = await client.query(` UPDATE courses SET ${setClause}, updated_at = CURRENT_TIMESTAMP WHERE id = $1 RETURNING * `, values); return result.rows[0] || null; } finally { client.release(); } } // 成绩相关方法 async getGrades(params: GradeQueryParams = {}): Promise<GradeWithDetails[]> { const { page = 1, limit = 10, student_id, course_id, semester, min_score, max_score } = params; const offset = (page - 1) * limit; let query = ` SELECT g.*, s.name as student_name, s.student_id as student_id_code, c.course_name, c.course_code, c.teacher_name FROM grades g JOIN students s ON g.student_id = s.id JOIN courses c ON g.course_id = c.id WHERE 1=1 `; const values: any[] = []; let paramIndex = 1; if (student_id) { query += ` AND g.student_id = $${paramIndex}`; values.push(student_id); paramIndex++; } if (course_id) { query += ` AND g.course_id = $${paramIndex}`; values.push(course_id); paramIndex++; } if (semester) { query += ` AND g.semester = $${paramIndex}`; values.push(semester); paramIndex++; } if (min_score !== undefined) { query += ` AND g.score >= $${paramIndex}`; values.push(min_score); paramIndex++; } if (max_score !== undefined) { query += ` AND g.score <= $${paramIndex}`; values.push(max_score); paramIndex++; } query += ` ORDER BY g.created_at DESC LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`; values.push(limit, offset); const client = await this.getClient(); try { const result = await client.query(query, values); return result.rows; } finally { client.release(); } } async getGradeById(id: number): Promise<GradeWithDetails | null> { const client = await this.getClient(); try { const result = await client.query(` SELECT g.*, s.name as student_name, s.student_id as student_id_code, c.course_name, c.course_code, c.teacher_name FROM grades g JOIN students s ON g.student_id = s.id JOIN courses c ON g.course_id = c.id WHERE g.id = $1 `, [id]); return result.rows[0] || null; } finally { client.release(); } } async createGrade(params: CreateGradeParams): Promise<Grade> { const client = await this.getClient(); try { const result = await client.query(` INSERT INTO grades (student_id, course_id, semester, score, grade, exam_date, remarks) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING * `, [ params.student_id, params.course_id, params.semester, params.score, params.grade, params.exam_date, params.remarks, ]); return result.rows[0]; } finally { client.release(); } } async updateGrade(id: number, params: UpdateGradeParams): Promise<Grade | null> { const client = await this.getClient(); try { const fields = Object.keys(params).filter(key => params[key as keyof UpdateGradeParams] !== undefined); if (fields.length === 0) return null; const setClause = fields.map((field, index) => `${field} = $${index + 2}`).join(', '); const values = [id, ...fields.map(field => params[field as keyof UpdateGradeParams])]; const result = await client.query(` UPDATE grades SET ${setClause}, updated_at = CURRENT_TIMESTAMP WHERE id = $1 RETURNING * `, values); return result.rows[0] || null; } finally { client.release(); } } async deleteGrade(id: number): Promise<boolean> { const client = await this.getClient(); try { const result = await client.query('DELETE FROM grades WHERE id = $1', [id]); return (result.rowCount || 0) > 0; } finally { client.release(); } } // 统计方法 async getStudentGradeStats(studentId: number): Promise<StudentGradeStats | null> { const client = await this.getClient(); try { const result = await client.query(` SELECT s.id as student_id, s.name as student_name, s.student_id as student_id_code, s.class_name, COUNT(g.id) as total_courses, AVG(g.score) as average_score, SUM(c.credits) as total_credits, AVG(CASE WHEN g.grade = 'A+' THEN 4.3 WHEN g.grade = 'A' THEN 4.0 WHEN g.grade = 'A-' THEN 3.7 WHEN g.grade = 'B+' THEN 3.3 WHEN g.grade = 'B' THEN 3.0 WHEN g.grade = 'B-' THEN 2.7 WHEN g.grade = 'C+' THEN 2.3 WHEN g.grade = 'C' THEN 2.0 WHEN g.grade = 'C-' THEN 1.7 WHEN g.grade = 'D+' THEN 1.3 WHEN g.grade = 'D' THEN 1.0 WHEN g.grade = 'F' THEN 0.0 ELSE NULL END) as gpa FROM students s LEFT JOIN grades g ON s.id = g.student_id LEFT JOIN courses c ON g.course_id = c.id WHERE s.id = $1 GROUP BY s.id, s.name, s.student_id, s.class_name `, [studentId]); return result.rows[0] || null; } finally { client.release(); } } async getCourseGradeStats(courseId: number): Promise<CourseGradeStats | null> { const client = await this.getClient(); try { const result = await client.query(` SELECT c.id as course_id, c.course_name, c.course_code, c.teacher_name, COUNT(g.id) as total_students, AVG(g.score) as average_score, MAX(g.score) as highest_score, MIN(g.score) as lowest_score, (COUNT(CASE WHEN g.score >= 60 THEN 1 END) * 100.0 / COUNT(g.id)) as pass_rate FROM courses c LEFT JOIN grades g ON c.id = g.course_id WHERE c.id = $1 GROUP BY c.id, c.course_name, c.course_code, c.teacher_name `, [courseId]); return result.rows[0] || null; } finally { client.release(); } } } // 创建单例实例 const databaseService = new DatabaseService(); export default databaseService; 步骤4:初始化数据库在package.json的scripts中添加 "db:init": "tsx scripts/init-database.ts", 然后执行bun run db:init看到输出数据库初始化完成!即为成功初始化初始化后可以在华为云的数据库管理界面看到有三张表被创建了!3.6 编写业务代码步骤1:trpc路由创建app/lib/routers/course.tsimport { z } from 'zod'; import { createTRPCRouter, publicProcedure } from '../trpc'; import databaseService from '../../../lib/database'; export const courseRouter = createTRPCRouter({ // 获取所有课程 getAll: publicProcedure .query(async () => { return await databaseService.getCourses(); }), // 根据ID获取课程 getById: publicProcedure .input(z.number()) .query(async ({ input }) => { return await databaseService.getCourseById(input); }), // 创建课程 create: publicProcedure .input(z.object({ course_code: z.string(), course_name: z.string(), credits: z.number(), teacher_name: z.string().optional(), description: z.string().optional(), })) .mutation(async ({ input }) => { return await databaseService.createCourse(input); }), // 更新课程 update: publicProcedure .input(z.object({ id: z.number(), data: z.object({ course_name: z.string().optional(), credits: z.number().optional(), teacher_name: z.string().optional(), description: z.string().optional(), }), })) .mutation(async ({ input }) => { return await databaseService.updateCourse(input.id, input.data); }), // 获取课程成绩统计 getGradeStats: publicProcedure .input(z.number()) .query(async ({ input }) => { return await databaseService.getCourseGradeStats(input); }), }); 创建app/lib/routers/grade.tsimport { z } from 'zod'; import { createTRPCRouter, publicProcedure } from '../trpc'; import databaseService from '../../../lib/database'; export const gradeRouter = createTRPCRouter({ // 获取所有成绩 getAll: publicProcedure .input(z.object({ page: z.number().optional(), limit: z.number().optional(), student_id: z.number().optional(), course_id: z.number().optional(), semester: z.string().optional(), min_score: z.number().optional(), max_score: z.number().optional(), }).optional()) .query(async ({ input }) => { return await databaseService.getGrades(input); }), // 根据ID获取成绩 getById: publicProcedure .input(z.number()) .query(async ({ input }) => { return await databaseService.getGradeById(input); }), // 创建成绩 create: publicProcedure .input(z.object({ student_id: z.number(), course_id: z.number(), semester: z.string(), score: z.number().optional(), grade: z.string().optional(), exam_date: z.date().optional(), remarks: z.string().optional(), })) .mutation(async ({ input }) => { return await databaseService.createGrade(input); }), // 更新成绩 update: publicProcedure .input(z.object({ id: z.number(), data: z.object({ score: z.number().optional(), grade: z.string().optional(), exam_date: z.date().optional(), remarks: z.string().optional(), }), })) .mutation(async ({ input }) => { return await databaseService.updateGrade(input.id, input.data); }), // 删除成绩 delete: publicProcedure .input(z.number()) .mutation(async ({ input }) => { return await databaseService.deleteGrade(input); }), // 获取学生成绩 getByStudent: publicProcedure .input(z.object({ student_id: z.number(), semester: z.string().optional(), })) .query(async ({ input }) => { return await databaseService.getGrades({ student_id: input.student_id, semester: input.semester, }); }), // 获取课程成绩 getByCourse: publicProcedure .input(z.object({ course_id: z.number(), semester: z.string().optional(), })) .query(async ({ input }) => { return await databaseService.getGrades({ course_id: input.course_id, semester: input.semester, }); }), }); 创建app/lib/routers/student.tsimport { z } from 'zod'; import { createTRPCRouter, publicProcedure } from '../trpc'; import databaseService from '../../../lib/database'; export const studentRouter = createTRPCRouter({ // 获取所有学生 getAll: publicProcedure .input(z.object({ page: z.number().optional(), limit: z.number().optional(), search: z.string().optional(), class_name: z.string().optional(), gender: z.enum(['男', '女']).optional(), }).optional()) .query(async ({ input }) => { return await databaseService.getStudents(input); }), // 根据ID获取学生 getById: publicProcedure .input(z.number()) .query(async ({ input }) => { return await databaseService.getStudentById(input); }), // 根据学号获取学生 getByStudentId: publicProcedure .input(z.string()) .query(async ({ input }) => { return await databaseService.getStudentByStudentId(input); }), // 创建学生 create: publicProcedure .input(z.object({ student_id: z.string(), name: z.string(), gender: z.enum(['男', '女']), birth_date: z.date().optional(), class_name: z.string().optional(), phone: z.string().optional(), email: z.string().email().optional(), address: z.string().optional(), })) .mutation(async ({ input }) => { return await databaseService.createStudent(input); }), // 更新学生 update: publicProcedure .input(z.object({ id: z.number(), data: z.object({ name: z.string().optional(), gender: z.enum(['男', '女']).optional(), birth_date: z.date().optional(), class_name: z.string().optional(), phone: z.string().optional(), email: z.string().email().optional(), address: z.string().optional(), }), })) .mutation(async ({ input }) => { return await databaseService.updateStudent(input.id, input.data); }), // 删除学生 delete: publicProcedure .input(z.number()) .mutation(async ({ input }) => { return await databaseService.deleteStudent(input); }), // 获取学生成绩统计 getGradeStats: publicProcedure .input(z.number()) .query(async ({ input }) => { return await databaseService.getStudentGradeStats(input); }), }); 更新主路由文件app/lib/routers/_app.tsimport { createTRPCRouter } from '../trpc'; import { studentRouter } from './student'; import { courseRouter } from './course'; import { gradeRouter } from './grade'; export const appRouter = createTRPCRouter({ student: studentRouter, course: courseRouter, grade: gradeRouter, }); export type AppRouter = typeof appRouter; 步骤2:编写前端页面app/page.tsx'use client'; import { useState } from 'react'; import { trpc } from "../utils/trpc"; import Navigation from './components/Navigation'; import StudentManager from './components/StudentManager'; import CourseManager from './components/CourseManager'; import GradeManager from './components/GradeManager'; import ReportManager from './components/ReportManager'; function StatCard({ title, value, icon }: { title: string; value: string | number; icon: string }) { return ( <div className="bg-white rounded-lg shadow p-6"> <div className="flex items-center"> <div className="flex-1"> <p className="text-sm text-black">{title}</p> <p className="text-2xl font-bold text-black">{value}</p> </div> <div className="text-3xl">{icon}</div> </div> </div> ); } function SimpleTable({ title, data, columns }: { title: string; data: any[]; columns: { key: string; label: string }[]; }) { if (!data || data.length === 0) { return ( <div className="bg-white rounded-lg shadow p-6"> <h3 className="text-lg font-semibold mb-4 text-black">{title}</h3> <p className="text-black">暂无数据</p> </div> ); } return ( <div className="rounded-lg shadow p-6 bg-white"> <h3 className="text-lg font-semibold mb-4 text-black">{title}</h3> <div className="overflow-x-auto"> <table className="w-full"> <thead> <tr className="border-b"> {columns.map((col) => ( <th key={col.key} className="text-left py-2 px-4 font-medium text-black"> {col.label} </th> ))} </tr> </thead> <tbody> {data.slice(0, 5).map((row, index) => ( <tr key={index} className="border-b hover:bg-gray-50 text-black"> {columns.map((col) => ( <td key={col.key} className="py-2 px-4 text-black"> {row[col.key] || '-'} </td> ))} </tr> ))} </tbody> </table> </div> {data.length > 5 && ( <p className="text-sm text-gray-500 mt-2">显示前5条,共{data.length}条</p> )} </div> ); } export default function HomePage() { const [currentPage, setCurrentPage] = useState('dashboard'); const students = trpc.student.getAll.useQuery(); const courses = trpc.course.getAll.useQuery(); const grades = trpc.grade.getAll.useQuery({ limit: 10 }); const allGrades = trpc.grade.getAll.useQuery(); const totalStudents = students.data?.length || 0; const totalCourses = courses.data?.length || 0; const totalGrades = allGrades.data?.length || 0; const validGrades = allGrades.data?.filter(grade => grade.score !== null && grade.score !== undefined) || []; const avgScore = validGrades.length > 0 ? (validGrades.reduce((sum, grade) => sum + (parseFloat(String(grade.score)) || 0), 0) / validGrades.length).toFixed(1) : '0.0'; const showPage = () => { switch (currentPage) { case 'students': return <StudentManager />; case 'courses': return <CourseManager />; case 'grades': return <GradeManager />; case 'reports': return <ReportManager />; default: return ( <div className="space-y-6"> <div className="grid grid-cols-2 md:grid-cols-4 gap-4"> <StatCard title="学生总数" value={totalStudents} icon="👥" /> <StatCard title="课程总数" value={totalCourses} icon="📚" /> <StatCard title="成绩记录" value={totalGrades} icon="📊" /> <StatCard title="平均分" value={avgScore} icon="⭐" /> </div> <div className="grid grid-cols-1 lg:grid-cols-2 gap-6"> <SimpleTable title="学生列表" data={students.data || []} columns={[ { key: 'name', label: '姓名' }, { key: 'student_id', label: '学号' }, { key: 'class_name', label: '班级' } ]} /> <SimpleTable title="课程列表" data={courses.data || []} columns={[ { key: 'course_name', label: '课程名称' }, { key: 'teacher_name', label: '教师' }, { key: 'credits', label: '学分' } ]} /> </div> <SimpleTable title="最新成绩" data={grades.data || []} columns={[ { key: 'student_name', label: '学生' }, { key: 'course_name', label: '课程' }, { key: 'score', label: '分数' }, { key: 'semester', label: '学期' } ]} /> </div> ); } }; return ( <div className="min-h-screen bg-gray-50"> <Navigation currentPage={currentPage} onPageChange={setCurrentPage} /> <main className="max-w-7xl mx-auto px-4 py-8"> {showPage()} </main> <footer className="bg-white border-t mt-12 py-6"> <div className="max-w-7xl mx-auto px-4 text-center text-gray-500 text-sm"> <p>学生成绩管理系统 作者dylan su</p> </div> </footer> </div> ); } app/components/Navigation.tsx'use client'; import { useState } from 'react'; interface NavigationProps { currentPage: string; onPageChange: (page: string) => void; } export default function Navigation({ currentPage, onPageChange }: NavigationProps) { const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); const menuItems = [ { id: 'dashboard', label: '仪表板' }, { id: 'students', label: '学生管理' }, { id: 'courses', label: '课程管理'}, { id: 'grades', label: '成绩管理' }, { id: 'reports', label: '统计报表'}, ]; return ( <nav className="bg-white shadow-sm border-b"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="flex justify-between h-16"> <div className="flex items-center"> <div className="flex-shrink-0 flex items-center"> <span className="text-xl font-bold text-gray-900">学生管理系统</span> </div> </div> <div className="hidden md:flex items-center space-x-8"> {menuItems.map((item) => ( <button key={item.id} onClick={() => onPageChange(item.id)} className={`flex items-center px-3 py-2 rounded-md text-sm font-medium transition-colors ${ currentPage === item.id ? 'bg-blue-100 text-blue-700' : 'text-gray-600 hover:text-gray-900 hover:bg-gray-50' }`} > {item.label} </button> ))} </div> <div className="md:hidden flex items-center"> <button onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)} className="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-blue-500" > <span className="sr-only">打开主菜单</span> {isMobileMenuOpen ? ( <svg className="block h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /> </svg> ) : ( <svg className="block h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" /> </svg> )} </button> </div> </div> </div> {isMobileMenuOpen && ( <div className="md:hidden"> <div className="px-2 pt-2 pb-3 space-y-1 sm:px-3 bg-white border-t"> {menuItems.map((item) => ( <button key={item.id} onClick={() => { onPageChange(item.id); setIsMobileMenuOpen(false); }} className={`flex items-center w-full px-3 py-2 rounded-md text-base font-medium transition-colors ${ currentPage === item.id ? 'bg-blue-100 text-blue-700' : 'text-gray-600 hover:text-gray-900 hover:bg-gray-50' }`} > {item.label} </button> ))} </div> </div> )} </nav> ); } app/components/StudentManager.tsx'use client'; import { useState } from 'react'; import { trpc } from '../../utils/trpc'; export default function StudentManager() { const [showForm, setShowForm] = useState(false); const [editingId, setEditingId] = useState<number | null>(null); const [form, setForm] = useState({ student_id: '', name: '', gender: '男' as '男' | '女', class_name: '', phone: '', email: '' }); const students = trpc.student.getAll.useQuery(); const createStudent = trpc.student.create.useMutation({ onSuccess: () => { students.refetch(); resetForm(); } }); const updateStudent = trpc.student.update.useMutation({ onSuccess: () => { students.refetch(); resetForm(); } }); const deleteStudent = trpc.student.delete.useMutation({ onSuccess: () => students.refetch() }); const resetForm = () => { setForm({ student_id: '', name: '', gender: '男', class_name: '', phone: '', email: '' }); setEditingId(null); setShowForm(false); }; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (editingId) { updateStudent.mutate({ id: editingId, data: form }); } else { createStudent.mutate(form); } }; const handleEdit = (student: any) => { setEditingId(student.id); setForm({ student_id: student.student_id, name: student.name, gender: student.gender, class_name: student.class_name || '', phone: student.phone || '', email: student.email || '' }); setShowForm(true); }; const handleDelete = (id: number) => { if (confirm('确定删除这个学生?')) { deleteStudent.mutate(id); } }; return ( <div className="space-y-6 text-black"> <div className="flex justify-between items-center"> <h1 className="text-2xl font-bold">学生管理</h1> <button onClick={() => setShowForm(true)} className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600" > 添加学生 </button> </div> <div className="bg-white rounded-lg shadow p-6"> <h2 className="text-lg font-semibold mb-4">学生列表</h2> {students.isLoading ? ( <p>加载中...</p> ) : students.error ? ( <p className="text-red-500">加载失败</p> ) : ( <div className="overflow-x-auto"> <table className="w-full"> <thead> <tr className="border-b"> <th className="text-left py-2 px-4">学号</th> <th className="text-left py-2 px-4">姓名</th> <th className="text-left py-2 px-4">性别</th> <th className="text-left py-2 px-4">班级</th> <th className="text-left py-2 px-4">操作</th> </tr> </thead> <tbody> {students.data?.map((student) => ( <tr key={student.id} className="border-b hover:bg-gray-50"> <td className="py-2 px-4">{student.student_id}</td> <td className="py-2 px-4">{student.name}</td> <td className="py-2 px-4">{student.gender}</td> <td className="py-2 px-4">{student.class_name || '-'}</td> <td className="py-2 px-4"> <button onClick={() => handleEdit(student)} className="text-blue-600 hover:text-blue-800 mr-2" > 编辑 </button> <button onClick={() => handleDelete(student.id)} className="text-red-600 hover:text-red-800" > 删除 </button> </td> </tr> ))} </tbody> </table> </div> )} </div> {showForm && ( <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center"> <div className="bg-white rounded-lg p-6 w-full max-w-md"> <h2 className="text-lg font-semibold mb-4"> {editingId ? '编辑学生' : '添加学生'} </h2> <form onSubmit={handleSubmit} className="space-y-4"> <div> <label className="block text-sm font-medium mb-1">学号</label> <input type="text" value={form.student_id} onChange={(e) => setForm({...form, student_id: e.target.value})} className="w-full px-3 py-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500" required /> </div> <div> <label className="block text-sm font-medium mb-1">姓名</label> <input type="text" value={form.name} onChange={(e) => setForm({...form, name: e.target.value})} className="w-full px-3 py-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500" required /> </div> <div> <label className="block text-sm font-medium mb-1">性别</label> <select value={form.gender} onChange={(e) => setForm({...form, gender: e.target.value as '男' | '女'})} className="w-full px-3 py-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500" > <option value="男">男</option> <option value="女">女</option> </select> </div> <div> <label className="block text-sm font-medium mb-1">班级</label> <input type="text" value={form.class_name} onChange={(e) => setForm({...form, class_name: e.target.value})} className="w-full px-3 py-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500" /> </div> <div> <label className="block text-sm font-medium mb-1">电话</label> <input type="text" value={form.phone} onChange={(e) => setForm({...form, phone: e.target.value})} className="w-full px-3 py-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500" /> </div> <div> <label className="block text-sm font-medium mb-1">邮箱</label> <input type="email" value={form.email} onChange={(e) => setForm({...form, email: e.target.value})} className="w-full px-3 py-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500" /> </div> <div className="flex gap-2 pt-4"> <button type="submit" className="flex-1 bg-blue-500 text-white py-2 rounded hover:bg-blue-600" disabled={createStudent.isPending || updateStudent.isPending} > {createStudent.isPending || updateStudent.isPending ? '保存中...' : '保存'} </button> <button type="button" onClick={resetForm} className="flex-1 bg-gray-500 text-white py-2 rounded hover:bg-gray-600" > 取消 </button> </div> </form> </div> </div> )} </div> ); } app/components/ReportManager.tsx'use client'; import { trpc } from '../../utils/trpc'; export default function ReportManager() { const students = trpc.student.getAll.useQuery(); const courses = trpc.course.getAll.useQuery(); const grades = trpc.grade.getAll.useQuery(); // 计算统计数据 const totalStudents = students.data?.length || 0; const totalCourses = courses.data?.length || 0; const totalGrades = grades.data?.length || 0; // 计算平均分 - 只计算有效的成绩,处理字符串类型的分数 const validGrades = grades.data?.filter(grade => grade.score !== null && grade.score !== undefined) || []; const avgScore = validGrades.length > 0 ? (validGrades.reduce((sum, grade) => sum + (parseFloat(String(grade.score)) || 0), 0) / validGrades.length).toFixed(1) : '0.0'; // 分数分布 - 只使用有效的成绩,处理字符串类型的分数 const scoreDistribution = validGrades.length > 0 ? { excellent: validGrades.filter(g => parseFloat(String(g.score)) >= 90).length, good: validGrades.filter(g => parseFloat(String(g.score)) >= 80 && parseFloat(String(g.score)) < 90).length, average: validGrades.filter(g => parseFloat(String(g.score)) >= 70 && parseFloat(String(g.score)) < 80).length, poor: validGrades.filter(g => parseFloat(String(g.score)) < 70).length } : { excellent: 0, good: 0, average: 0, poor: 0 }; return ( <div className="space-y-6 text-black"> <h1 className="text-2xl font-bold">统计报表</h1> <div className="grid grid-cols-2 md:grid-cols-4 gap-4"> <div className="bg-white rounded-lg shadow p-6"> <div className="flex items-center"> <div className="flex-1"> <p className="text-sm text-black">学生总数</p> <p className="text-2xl font-bold text-black">{totalStudents}</p> </div> <div className="text-3xl">👥</div> </div> </div> <div className="bg-white rounded-lg shadow p-6"> <div className="flex items-center"> <div className="flex-1"> <p className="text-sm text-black">课程总数</p> <p className="text-2xl font-bold text-black">{totalCourses}</p> </div> <div className="text-3xl">📚</div> </div> </div> <div className="bg-white rounded-lg shadow p-6"> <div className="flex items-center"> <div className="flex-1"> <p className="text-sm text-black">成绩记录</p> <p className="text-2xl font-bold text-black">{totalGrades}</p> </div> <div className="text-3xl">📊</div> </div> </div> <div className="bg-white rounded-lg shadow p-6"> <div className="flex items-center"> <div className="flex-1"> <p className="text-sm text-black">平均分</p> <p className="text-2xl font-bold text-black">{avgScore}</p> </div> <div className="text-3xl">⭐</div> </div> </div> </div> <div className="bg-white rounded-lg shadow p-6"> <h2 className="text-lg font-semibold mb-4">分数分布</h2> <div className="grid grid-cols-2 md:grid-cols-4 gap-4"> <div className="text-center"> <div className="text-2xl font-bold text-green-600">{scoreDistribution.excellent}</div> <div className="text-sm text-gray-600">优秀 (90+)</div> </div> <div className="text-center"> <div className="text-2xl font-bold text-blue-600">{scoreDistribution.good}</div> <div className="text-sm text-gray-600">良好 (80-89)</div> </div> <div className="text-center"> <div className="text-2xl font-bold text-yellow-600">{scoreDistribution.average}</div> <div className="text-sm text-gray-600">中等 (70-79)</div> </div> <div className="text-center"> <div className="text-2xl font-bold text-red-600">{scoreDistribution.poor}</div> <div className="text-sm text-gray-600">需努力 (<70)</div> </div> </div> </div> <div className="bg-white rounded-lg shadow p-6"> <h2 className="text-lg font-semibold mb-4">课程平均分</h2> {courses.isLoading ? ( <p>加载中...</p> ) : ( <div className="space-y-3"> {courses.data?.map((course) => { const courseGrades = grades.data?.filter(g => g.course_id === course.id) || []; const avg = courseGrades.length > 0 ? (courseGrades.reduce((sum, g) => sum + (parseFloat(String(g.score)) || 0), 0) / courseGrades.length).toFixed(1) : '0.0'; return ( <div key={course.id} className="flex justify-between items-center py-2 border-b"> <span>{course.course_name}</span> <span className="font-semibold">{avg}分</span> </div> ); })} </div> )} </div> </div> ); } app/components/GradeManager.tsx'use client'; import { useState } from 'react'; import { trpc } from '../../utils/trpc'; export default function GradeManager() { const [showForm, setShowForm] = useState(false); const [editingId, setEditingId] = useState<number | null>(null); const [form, setForm] = useState({ student_id: 0, course_id: 0, score: 0, semester: '' }); const grades = trpc.grade.getAll.useQuery(); const students = trpc.student.getAll.useQuery(); const courses = trpc.course.getAll.useQuery(); const createGrade = trpc.grade.create.useMutation({ onSuccess: () => { grades.refetch(); resetForm(); } }); const updateGrade = trpc.grade.update.useMutation({ onSuccess: () => { grades.refetch(); resetForm(); } }); const deleteGrade = trpc.grade.delete.useMutation({ onSuccess: () => grades.refetch() }); const resetForm = () => { setForm({ student_id: 0, course_id: 0, score: 0, semester: '' }); setEditingId(null); setShowForm(false); }; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (editingId) { updateGrade.mutate({ id: editingId, data: form }); } else { createGrade.mutate(form); } }; const handleEdit = (grade: any) => { setEditingId(grade.id); setForm({ student_id: grade.student_id, course_id: grade.course_id, score: grade.score, semester: grade.semester }); setShowForm(true); }; const handleDelete = (id: number) => { if (confirm('确定删除这个成绩?')) { deleteGrade.mutate(id); } }; const getGradeLevel = (score: number) => { if (score >= 90) return { text: '优秀', color: 'text-green-600' }; if (score >= 80) return { text: '良好', color: 'text-blue-600' }; if (score >= 70) return { text: '中等', color: 'text-yellow-600' }; return { text: '需努力', color: 'text-red-600' }; }; return ( <div className="space-y-6 text-black"> <div className="flex justify-between items-center"> <h1 className="text-2xl font-bold">成绩管理</h1> <button onClick={() => setShowForm(true)} className="bg-purple-500 text-white px-4 py-2 rounded hover:bg-purple-600" > 录入成绩 </button> </div> <div className="bg-white rounded-lg shadow p-6"> <h2 className="text-lg font-semibold mb-4">成绩列表</h2> {grades.isLoading ? ( <p>加载中...</p> ) : grades.error ? ( <p className="text-red-500">加载失败</p> ) : ( <div className="overflow-x-auto"> <table className="w-full"> <thead> <tr className="border-b"> <th className="text-left py-2 px-4">学生</th> <th className="text-left py-2 px-4">课程</th> <th className="text-left py-2 px-4">分数</th> <th className="text-left py-2 px-4">等级</th> <th className="text-left py-2 px-4">学期</th> <th className="text-left py-2 px-4">操作</th> </tr> </thead> <tbody> {grades.data?.map((grade) => { const level = getGradeLevel(grade.score || 0); return ( <tr key={grade.id} className="border-b hover:bg-gray-50"> <td className="py-2 px-4">{grade.student_name}</td> <td className="py-2 px-4">{grade.course_name}</td> <td className="py-2 px-4">{grade.score}</td> <td className={`py-2 px-4 ${level.color}`}>{level.text}</td> <td className="py-2 px-4">{grade.semester}</td> <td className="py-2 px-4"> <button onClick={() => handleEdit(grade)} className="text-blue-600 hover:text-blue-800 mr-2" > 编辑 </button> <button onClick={() => handleDelete(grade.id)} className="text-red-600 hover:text-red-800" > 删除 </button> </td> </tr> ); })} </tbody> </table> </div> )} </div> {showForm && ( <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center"> <div className="bg-white rounded-lg p-6 w-full max-w-md"> <h2 className="text-lg font-semibold mb-4"> {editingId ? '编辑成绩' : '录入成绩'} </h2> <form onSubmit={handleSubmit} className="space-y-4"> <div> <label className="block text-sm font-medium mb-1">学生</label> <select value={form.student_id} onChange={(e) => setForm({...form, student_id: parseInt(e.target.value)})} className="w-full px-3 py-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500" required > <option value="">选择学生</option> {students.data?.map((student) => ( <option key={student.id} value={student.id}> {student.name} ({student.student_id}) </option> ))} </select> </div> <div> <label className="block text-sm font-medium mb-1">课程</label> <select value={form.course_id} onChange={(e) => setForm({...form, course_id: parseInt(e.target.value)})} className="w-full px-3 py-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500" required > <option value="">选择课程</option> {courses.data?.map((course) => ( <option key={course.id} value={course.id}> {course.course_name} ({course.course_code}) </option> ))} </select> </div> <div> <label className="block text-sm font-medium mb-1">分数</label> <input type="number" min="0" max="100" value={form.score} onChange={(e) => setForm({...form, score: parseInt(e.target.value)})} className="w-full px-3 py-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500" required /> </div> <div> <label className="block text-sm font-medium mb-1">学期</label> <input type="text" value={form.semester} onChange={(e) => setForm({...form, semester: e.target.value})} className="w-full px-3 py-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="如:2024春季" required /> </div> <div className="flex gap-2 pt-4"> <button type="submit" className="flex-1 bg-purple-500 text-white py-2 rounded hover:bg-purple-600" disabled={createGrade.isPending || updateGrade.isPending} > {createGrade.isPending || updateGrade.isPending ? '保存中...' : '保存'} </button> <button type="button" onClick={resetForm} className="flex-1 bg-gray-500 text-white py-2 rounded hover:bg-gray-600" > 取消 </button> </div> </form> </div> </div> )} </div> ); } app/components/CourseManager.tsx'use client'; import { useState } from 'react'; import { trpc } from '../../utils/trpc'; export default function CourseManager() { const [showForm, setShowForm] = useState(false); const [editingId, setEditingId] = useState<number | null>(null); const [form, setForm] = useState({ course_code: '', course_name: '', credits: 0, teacher_name: '', description: '' }); const courses = trpc.course.getAll.useQuery(); const createCourse = trpc.course.create.useMutation({ onSuccess: () => { courses.refetch(); resetForm(); } }); const updateCourse = trpc.course.update.useMutation({ onSuccess: () => { courses.refetch(); resetForm(); } }); const resetForm = () => { setForm({ course_code: '', course_name: '', credits: 0, teacher_name: '', description: '' }); setEditingId(null); setShowForm(false); }; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (editingId) { updateCourse.mutate({ id: editingId, data: form }); } else { createCourse.mutate(form); } }; const handleEdit = (course: any) => { setEditingId(course.id); setForm({ course_code: course.course_code, course_name: course.course_name, credits: course.credits, teacher_name: course.teacher_name || '', description: course.description || '' }); setShowForm(true); }; return ( <div className="space-y-6 text-black"> <div className="flex justify-between items-center"> <h1 className="text-2xl font-bold">课程管理</h1> <button onClick={() => setShowForm(true)} className="bg-green-500 text-white px-4 py-2 rounded hover:bg-green-600" > 添加课程 </button> </div> <div className="bg-white rounded-lg shadow p-6"> <h2 className="text-lg font-semibold mb-4">课程列表</h2> {courses.isLoading ? ( <p>加载中...</p> ) : courses.error ? ( <p className="text-red-500">加载失败</p> ) : ( <div className="overflow-x-auto"> <table className="w-full"> <thead> <tr className="border-b"> <th className="text-left py-2 px-4">课程代码</th> <th className="text-left py-2 px-4">课程名称</th> <th className="text-left py-2 px-4">学分</th> <th className="text-left py-2 px-4">教师</th> <th className="text-left py-2 px-4">操作</th> </tr> </thead> <tbody> {courses.data?.map((course) => ( <tr key={course.id} className="border-b hover:bg-gray-50"> <td className="py-2 px-4">{course.course_code}</td> <td className="py-2 px-4">{course.course_name}</td> <td className="py-2 px-4">{course.credits}</td> <td className="py-2 px-4">{course.teacher_name || '-'}</td> <td className="py-2 px-4"> <button onClick={() => handleEdit(course)} className="text-blue-600 hover:text-blue-800" > 编辑 </button> </td> </tr> ))} </tbody> </table> </div> )} </div> {showForm && ( <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center"> <div className="bg-white rounded-lg p-6 w-full max-w-md"> <h2 className="text-lg font-semibold mb-4"> {editingId ? '编辑课程' : '添加课程'} </h2> <form onSubmit={handleSubmit} className="space-y-4"> <div> <label className="block text-sm font-medium mb-1">课程代码</label> <input type="text" value={form.course_code} onChange={(e) => setForm({...form, course_code: e.target.value})} className="w-full px-3 py-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500" required /> </div> <div> <label className="block text-sm font-medium mb-1">课程名称</label> <input type="text" value={form.course_name} onChange={(e) => setForm({...form, course_name: e.target.value})} className="w-full px-3 py-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500" required /> </div> <div> <label className="block text-sm font-medium mb-1">学分</label> <input type="number" value={form.credits} onChange={(e) => setForm({...form, credits: parseInt(e.target.value)})} className="w-full px-3 py-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500" required /> </div> <div> <label className="block text-sm font-medium mb-1">教师</label> <input type="text" value={form.teacher_name} onChange={(e) => setForm({...form, teacher_name: e.target.value})} className="w-full px-3 py-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500" /> </div> <div> <label className="block text-sm font-medium mb-1">描述</label> <textarea value={form.description} onChange={(e) => setForm({...form, description: e.target.value})} className="w-full px-3 py-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500" rows={3} /> </div> <div className="flex gap-2 pt-4"> <button type="submit" className="flex-1 bg-green-500 text-white py-2 rounded hover:bg-green-600" disabled={createCourse.isPending || updateCourse.isPending} > {createCourse.isPending || updateCourse.isPending ? '保存中...' : '保存'} </button> <button type="button" onClick={resetForm} className="flex-1 bg-gray-500 text-white py-2 rounded hover:bg-gray-600" > 取消 </button> </div> </form> </div> </div> )} </div> ); } 3.6 运行项目执行bun dev然后看到终端返回打开http://localhost:3000即可看到项目运行的状况4.1 页面展示4.1.1仪表盘这是学生管理系统的仪表板页面,作为系统的主控制面板,它通过四个关键数据卡片(学生总数6人、课程总数10门、成绩记录10条、平均分77.0)快速展示系统整体状况,同时以表格形式分别列出了学生基本信息、课程设置和最新成绩记录,让管理员能够一目了然地掌握当前教学管理的核心数据和最新动态。4.1.2 学生管理这是学生管理系统的学生管理页面,专门用于学生信息的维护和管理。页面顶部设有"添加学生"按钮用于新增学生记录,主体部分以表格形式展示了6名学生的详细信息,包括学号(2024001-2024006)、姓名、性别和所属班级,每行末尾都提供"编辑"和"删除"操作按钮,方便管理员对学生信息进行实时的增删改查操作。4.1.3 课程管理这是学生管理系统的课程管理页面,用于管理学校的课程信息。页面顶部提供"添加课程"按钮用于新增课程,主体部分以表格形式展示了10门课程的详细信息,包括:核心课程:数学、语文、英语、物理、化学、生物等主要学科每门课程都配有相应的任课教师和学分设置课程信息包含:课程代码(如MATH101、CHI101等标准化编码)课程名称学分(2.0-4.0学分不等)任课教师编辑操作按钮4.1.4 成绩管理这是学生管理系统的成绩管理页面,用于记录和管理学生的考试成绩。页面功能包括:主要功能录入成绩:页面右上角提供"录入成绩"按钮,用于添加新的成绩记录成绩查看:以表格形式展示所有学生的成绩信息成绩信息展示页面显示了3名学生(张三、李四、王五)在2024春季学期的各科成绩:学生成绩概况:张三:语文92分(优秀)、英语78.5分(中等)、数学85.5分(良好)李四:数学95分(优秀)、语文88.5分(良好)、英语91分(优秀)王五:数学72分(中等)、语文85分(良好)、英语79.5分(中等)每条成绩记录都提供"编辑"和"删除"操作,方便教师对成绩进行修改和维护。整个界面设计清晰,便于教务人员进行成绩管理工作。4.1.5 统计报表5.1总结本项目基于华为开发者空间的云开发环境,采用 Bun + Next.js + tRPC 现代化全栈技术栈,成功构建了一个功能完善的学生成绩管理系统。系统包含课程管理和成绩管理两大核心模块,实现了标准化的课程编码体系和科学的成绩评价机制。通过 Bun 超快运行时提升构建效率,Next.js 处理前后端复杂逻辑,tRPC 确保端到端类型安全,原生 SQL 直接对接华为 GaussDB 数据库实现最优查询性能。项目充分利用华为开发者空间提供的免费数据库实例和完整开发工具链,体验了从开发编码到应用调测的全流程云开发模式。系统界面简洁直观,支持课程信息维护、成绩录入编辑、智能等级评定等功能,为学校教务管理提供了高效便捷的数字化解决方案。通过实际开发实践,深入了解了现代全栈开发最佳实践和华为根技术生态的强大能力,为未来进一步集成昇腾AI、鸿蒙等技术打下了坚实基础。至此本案例关于在开发者空间–远程开发环境中部署Bun + Next.js + tRPC与开发者空间生态版GaussDB实践操作并编写学生成绩管理系统完毕。我正在参加【案例共创】第6期 开发者空间-基于云开发环境和GaussDB构建应用(https://bbs.huaweicloud.com/forum/thread-0229189398343651003-1-1.html)
-
活动介绍:为让云服务产品更精准贴合实际场景、更全面覆盖使用需求,我们特发起本次友好开发者招募活动。无论你是熟悉业务的技术达人,还是刚接触产品的新手用户,只要你有想法、有经验,都欢迎加入进来 —— 分享你的使用心得、补充未覆盖的场景细节、提出内容优化建议。一旦您的建议经过专家评审团的认可与采纳,将会获得开发者定制礼品!我们期待着与您一起,共同打造更加优质、高效的云服务体验。活动时间2025.8.23-2025.9.30活动流程:01 领取云数据库GaussDB在线试用版02 按照实践案例深度使用体验03 在活动贴下跟帖评论运行结果04 提交开发者工单,反馈产品优化建议第1步:实验前的准备实名登录:(已注册并实名可跳过)华为云账号实名认证,点击这里。(已设置可跳过)登录后设置社区昵称,点我设置。第2步:领取云数据库GaussDB在线试用版:(1) 点击申请链接:cid:link_2(2) 填写申请信息:(3) 欢迎加入微信群交流: 第3步:动手操作吧 (1) 实践案例查看地址:cid:link_3(2) 完成体验后截图在评论区(3) 任务过程中,遇到操作问题可在本微信群进行交流(4) 遇到产品功能问题,请提交问题工单(必须是开发者工单)(5) 完成问卷调查。奖品设置奖项设置获奖要求获奖名额激励礼品建议排名奖被采纳建议数≥3条5每人价值200元开发者礼包1份高价值建议奖被评选为高价值需求5每人价值200元开发者礼包1份说明:1. 每条被采纳建议累计1积分,被采纳建议数统计截止时间为2025年9月30日24点;2. 奖品数量有限,按照时间先后顺序,先到先得;3. 如礼品库存不足将存在替换成等价值礼品的可能;4. 开发者礼包举例:序号礼包名称介绍1200元开发者礼包1开发者定制双肩包(黑武士款)开发者定制渔夫帽(2025款)开发者定制鼠标垫大号(2025款)2200元开发者礼包2开发者定制冲锋衣(M-3XL)开发者定制渔夫帽(2025款)开发者定制鼠标垫大号(2025款)活动说明(1) 建议提交时需要在标题中以“【GaussDB体验】”为建议标题开头,比如【GaussDB体验】建议增加XX/优化XX/XX操作失败等。(2) 建议内容仅针对云数据库GaussDB应用实践活动,非该活动的建议内容不参与此活动。(3) 提交的建议要求建议对云产品功能及优化改进有积极作用,建议内容需要表述清晰,有明确的建议方案,最好有操作截图或链接等能进一步详细描述。(4) 若出现积分相同且排名一致的情况,根据先到先得原则进行发放。(5) 同一用户在同一页面(文档)提出的同一类用户体验问题(包括但不限于错别字、语句不通顺、视觉体验等),在被采纳后仅算作一条积分。(6) 兑换礼品以仓库现有礼品为准,不可以指定,如遇商品缺货,将随机换成其他等价值礼品发放。
-
用户级修改参数 ALTER USER使用方法:https://support.huaweicloud.com/sqlreference-dws/dws_06_0149.html例:alter user mmsuser set enable_fast_query_shipping=off;用户级修改参数,该用户新建连接立即生效,不需要重启,单存量连接不生效。可以通过审计日志查看用户登入登出时间,查看连接创建时间,通常使用pid进行关联:select * from pgxc_query_audit('2025-08-15 00:00:00','2025-08-15 06:30:00') where session_id like '%对应的pid%' and detail_info not in ('START TRANSACTION','ROLLBACK','COMMIT') and operation_type ='login_logout' order by begintime;结论:用户级修改参数如果未生效,用户重新登录即可。
-
华为全连接大会在哪里报名呀,一直密切关注华为的技术创新和行业动态,非常期待能够线下参加2025华为全连接大会,与大家深入交流学习,希望能获得参会资格
-
面对MySQL、PostgreSQL、Redis、MongoDB、TiDB等众多选择,一次错误的数据库选型足以拖垮一个产品甚至一家公司。本文将从性能、扩展性、成本、适用场景四大维度,进行一场客观、深度的剖析,助您做出最理性的技术决策。 一、五大数据库核心维度对比 二、深度剖析与场景化选择 1. MySQL:互联网业务的可靠基石 痛点解决:满足绝大多数Web应用对结构化数据存储和事务一致性的基本需求。其成熟的生态、广泛的社区支持和稳定的表现是其最大优势。 典型场景:用户中心、订单管理、博客/CMS系统。当业务量增长后,需要通过读写分离和分库分表来缓解压力,但这会极大增加应用复杂度和运维负担。 2. PostgreSQL:复杂业务的瑞士军刀 痛点解决:当MySQL在复杂查询、自定义数据类型、窗口函数、外键约束等方面无法满足需求时,PG是完美的升级选择。它支持JSONB,甚至在某种程度上可以替代MongoDB。 典型场景:地理位置处理(PostGIS)、财务系统、科学数据、需要复杂ACID事务的业务。 3. Redis:速度与激情的代名词 痛点解决:彻底解决数据库热点数据访问延迟过高的问题,将响应时间从毫秒级降至微秒级。 典型场景: 缓存:数据库热点数据缓存,减轻后端压力。 排行榜:利用`ZSET`实现实时排名。 秒杀系统:利用原子操作和高并发能力控制库存。 消息队列:使用`List`实现简单的异步任务队列。 注意:Redis的数据存储在内存中,成本较高,且持久化方案需要精心设计以防数据丢失。 4. MongoDB:灵活模式的敏捷先锋 痛点解决:应对需求频繁变更、数据结构不固定的场景。其灵活的文档模型(BSON)允许快速迭代,无需像关系数据库一样频繁执行`ALTER TABLE`操作。 典型场景:物联网(IoT)传感器数据、用户行为日志、商品画像、内容评论系统。其原生分片功能使得横向扩展变得相对简单。 5. TiDB: Scale 困境的终极答案 痛点解决:当MySQL分库分表后的运维复杂度、跨分片事务、全局有序查询等问题无法解决时,TiDB提供了“终极方案”。它兼容MySQL协议,支持无限水平扩展,同时保证分布式事务的ACID特性。 典型场景: 替换分库分表MySQL集群:应用无需改动即可接入,获得弹性扩展能力。 HTAP场景:一套系统同时处理在线交易(OLTP)和实时分析(OLAP),省去复杂的ETL流程。三、决策树:三步锁定目标数据库 graph TDA[开始] --> B{是否需要事务?}B -->|是| C{读写比例?}C -->|读多写少| D[考虑Redis/MongoDB]C -->|均衡/写多| E{数据结构是否固定?}E -->|结构化| F[MySQL/PostgreSQL]E -->|非结构化| G[MongoDB]B -->|否| H[纯KV存储→Redis]F --> I{是否需要GIS/JSON?}I -->|是| J[PostgreSQL]I -->|否| K[MySQL]G --> L{是否需要Advanced Aggregation?}L -->|是| M[MongoDB Atlas]L -->|否| N[简化版MongoDB]🔍 决策路径示例场景1:电商平台商品目录(SKU数量超亿)→ 选择MongoDB(灵活文档模型+分片扩展)场景2:银行交易流水(强一致要求)→ 选择PostgreSQL(支持事务级唯一约束)场景3:直播弹幕系统(百万级QPS)→ 选择Redis集群(极低延迟+简单数据结构) 四、总结 数据库选型没有银弹,业务场景是唯一的衡量标准。 对于绝大多数中小型项目,MySQL依然是性价比最高的起点。 当遇到复杂业务逻辑和高级SQL功能时,PostgreSQL值得优先考虑。 Redis作为缓存和加速层的价值无可替代,是架构中必不可少的组件。 面对高度灵活的数据模型,MongoDB可以显著提升开发效率。 当数据规模和并发量达到巨头级别,TiDB这类分布式数据库是解决Scale问题的未来方向。 技术决策者应避免过早优化和过度设计,结合团队技术栈、业务发展阶段和长期规划,选择最适合当前、并能平滑演进到下一阶段的数据库解决方案。
-
华为全联接大会有什么期待
-
GaussDB(DWS)时序表建表最佳实践cid:link_0HStore简介cid:link_1列存储优势cid:link_6小CU问题cid:link_2CU合并cid:link_7提升数据聚簇性cid:link_3HStore表的背景cid:link_4HStore 表的特点cid:link_8HStore 的视图与函数cid:link_9GaussDB(DWS) 锁管理详解cid:link_10表锁cid:link_11其他锁cid:link_5用户自定义锁cid:link_12查看锁等待cid:link_13GaussDB(DWS)有哪些锁https://bbs.huaweicloud.com/forum/thread-0294191082093980066-1-1.html
-
常规锁:常规锁主要用于业务访问数据库对象的加锁,保护并发操作的对象,保持数据一致性;常见的常规锁有表锁(relation)和行锁(tuple)。 表锁:当对表进行DDL、DML操作时,会对操作的对象表加锁,在事务结束释放; 行锁:使用select for share语句时持有该模式锁,后台会对tuple加5级锁;使用select for update, delete, update 等操作时,后台会对tuple加7级锁(ExclusiveLock)。 轻量级锁:轻量级锁主要用于数据库内部共享资源访问的保护,比如内存结构、共享内存分配控制等。常规锁按照粒度通常可分为1~8个等级,DWS在8.2.1(及以上)版本中新增了第9级锁(UPDATE EXCLUSIVE)。ACCESS SHARE与ACCESS EXCLUSIVE锁冲突例子:session 1 在事务内对表进行truncate,且 lockwait_timeout参数设置为10s;session 2 查询该表,此时会一直等到session 1 释放锁,直到等锁超时;ROW SHARE(行锁冲突的例子):并发insert/update/copy;session 1在事务内对有主键约束的行存表进行更新;session 2 对同一主键的行进行更新,会一直等待session 1释放锁,直到行锁超时。
-
locktype 列表示锁类型,包括表锁、事务锁、扩展锁、自定义锁等; relation 列表示表的oid,如果是表锁,relation列会显示表的oid; transactionid 表示事务号,如果是事务锁,transactionid列会显示session的事务号; mode列表示锁级别,级别1-8级; pid 列表示session的线程号; granted 列表示是否持有锁,‘t’表示持有锁,‘f'表示等待锁;持有表t1的3级锁RowExclusiveLock,表t1的oid是16384; 持有session2的事务锁,事务号是2415630; 持有记录(1,1)的记录锁; 等待session1事务结束释放事务锁,granted为’f',申请 session1的事务号对应的5级锁(ShareLock),与 session1 持有 7 级事务锁冲突,需要锁等待。
-
用户自定义锁也叫咨询锁(advisory lock),用户可以通过调用GaussDB(DWS) 提供的函数来自定义锁,自定义锁按照作用范围分为两类: 1) 事务级自定义锁 pg_advisory_xact_lock(key bigint) pg_advisory_xact_lock(key1 int, key2 int) pg_advisory_xact_lock_shared(key bigint) pg_advisory_xact_lock_shared(key1 int, key2 int) pg_try_advisory_xact_lock(key bigint) pg_try_advisory_xact_lock(key1 int, key2 int) pg_try_advisory_xact_lock_shared(key bigint) pg_try_advisory_xact_lock_shared(key1 int, key2 int) 2) session级自定义锁 pg_advisory_lock(key bigint) pg_advisory_lock(key1 int, key2 int) pg_advisory_lock_shared(key bigint) pg_advisory_lock_shared(key1 int, key2 int) pg_try_advisory_lock(key bigint) pg_try_advisory_lock(key1 int, key2 int) pg_try_advisory_lock_shared(key bigint) pg_try_advisory_lock_shared(key1 int, key2 int) 带shared后缀的相关函数会申请5级锁ShareLock,不带 shared后缀的会申请7级锁ExclusiveLock; 带try 标识的相关函数表示尝试申请锁,如果申请不到,直接返回,不需锁等待。
-
1) 事务锁 写事务会获取一个事务号,并且会以这个事务号申请一个事务锁,锁级别是7级锁ExclusiveLock。 事务锁用于控制记录的并发修改,比如,两个事务先后修改同一条记录,在前一个事务未结束之前,后一个事务会等待在前一个事务的事务锁上。 2) 记录锁 当出现并发更新冲突时,冲突的事务会申请记录锁,锁级别是 7 级锁ExclusiveLock。 记录锁主要是提高等待事务的优先级,在更新事务结束后,让持有记录锁的事务第一个被唤醒。 3) 扩展锁 文件扩展时,会申请扩展锁,扩展锁的锁级别是7级锁ExclusiveLock。 表、索引、fsm、vm等文件扩展时都会申请扩展锁。 4) 分区锁 分区锁是专门针对分区表的,分区锁的意义与表锁差不多,锁级别从 1-8 都有。
-
GaussDB(DWS)支持的表锁级别很多,原生PG支持从最低的1级到最高的8级,另外GaussDB(DWS)在8.2.1(及以上)版本中还增加了9级锁,各锁级别含义如下: 1级锁,AccessShareLock SELECT语句申请AccessShareLock,只与8级锁冲突,只会阻塞DDL等语句; 2级锁,RowShareLock SELECT FOR SHARE/UPDATE语句申请RowShareLock,与7/8级锁冲突; 3级锁,RowExclusiveLockINSERT/UPDATE/DELET语句申请RowExclusiveLock,与 6-8级锁冲突; 4级锁,ShareUpdateExclusiveLockVACUUM/ANALYZE语句申请ShareUpdateExclusiveLock,与 5-8级锁冲突; 5级锁,ShareLock CREATE INDEX语句申请ShareLock,与4/6/7/8级锁冲突,与5级锁不冲突,同一个表的多个CREATE INDEX可以同时执行不阻塞; 6级锁,ShareRowExclusiveLock 在 GaussDB(DWS) 中,ShareRowExclusiveLock 目前只在 ALTER SEQUENCE 中用到,阻塞表的增删改以及更高级别操作,该锁与3-8级锁冲突; 7级锁,ExclusiveLock VACUUM FULL,MERGE PARTITION等语句申请ExclusiveLock级锁,ExclusiveLock 只与SELECT兼容,与2-8级锁冲突; 除了表锁外,事务锁,扩展锁、记录锁都是使用的ExclusiveLock,后面会进行详细介绍; 8级锁,AccessExclusiveLock DDL 等语句会申请AccessExclusiveLock,包括 ALTER TABLE,DROP TABLE,TRUNCATE,REINDEX,VACUUM FULL等,8级锁与所有锁都冲突; 9级锁,UpdateExclusiveLock 仅autovacuum中使用,为了支持与autoanalyze操作并行。
上滑加载中
推荐直播
-
Skill 构建 × 智能创作:基于华为云码道的 AI 内容生产提效方案2026/03/25 周三 19:00-20:00
余伟,华为云软件研发工程师/万邵业(万少),华为云HCDE开发者专家
本次直播带来两大实战:华为云码道 Skill-Creator 手把手搭建专属知识库 Skill;如何用码道提效 OpenClaw 小说文本,打造从大纲到成稿的 AI 原创小说全链路。技术干货 + OPC创作思路,一次讲透!
回顾中 -
码道新技能,AI 新生产力——从自动视频生成到开源项目解析2026/04/08 周三 19:00-21:00
童得力-华为云开发者生态运营总监/何文强-无人机企业AI提效负责人
本次华为云码道 Skill 实战活动,聚焦两大 AI 开发场景:通过实战教学,带你打造 AI 编程自动生成视频 Skill,并实现对 GitHub 热门开源项目的智能知识抽取,手把手掌握 Skill 开发全流程,用 AI 提升研发效率与内容生产力。
回顾中 -
“虾”路相逢 脑洞无穷-龙虾Show& Talk2026/04/16 周四 15:00-18:00
叶文彬-华为云运营顾问、陈晶杰-华为云开发者专家、李成-华为云AI架构师、张剑-HCDE开发者专家
以OpenClaw为代表的AI智能体引发全民狂热,这场狂欢背后,是AI从"对话工具"向"数字员工"的范式跃迁,是用户渴望能自主执行任务、真正"干活"的AI代理。华为云已正式开启Openclaw体验计划,支持用户将龙虾部署在华为云上,与本地隐私数据进行隔离,如何让每一位用户优雅的“养虾”?欢迎收看本次直播
即将直播
热门标签