基于java开发教室预约Web

Class Int—教室资源管理系统 一,需求介绍 1,1 项目前景 近年来,随着高校不断的扩招,在校师生人数不断增加,各级高校都迫切需要提高工作质量和工作效率

本文包含相关资料包-----> 点击直达获取<-------

Class Int—教室资源管理系统

一、需求介绍

1.1 项目前景

近年来,随着高校不断的扩招,在校师生人数不断增加,各级高校都迫切需要提高工作质量和工作效率。计算机信息处理技术发展的同时,也带动了网络技术的飞速发展,所有这些技术都为包括信息采集、信息处理、信息传递、信息共享功能的高度自动化的办公系统提供了强大的技术支持和开发平台,使得他们的开发和应用速率得到了很大的提供,各个系统的开发都成为可能。

教室是高等学校的教学管理中的重要资源,授课必须选择相关的教室才可进行。每学期开学前,各大高校都会组织老师安排指令性的教学任务,但与此同时,教室也要负责组织学校的各种其他活动,如讲座、培训等工作。教室活动的安排的增多,就需要对教室资源进行合理地安排,使教室的资源得到充分利用,这就是教室管理工作的主要任务。进入二十一世纪,互联网技术迅猛发展,很多高校都将教室管理进行网络化,就是在网络上进行相关的教室管理操作。通过网络,可以实现教室管理的快捷和方便。教室管理网络化的出现,构建了一个网上平台,供教室使用者和教室管理者交流。在教室使用者方便的查询和使用教室信息的同时,教室管理者也可以通过计算机对教室信息实现最有效率的管理,也就降低了人工处理的成本,管理人员也会更加轻松地从事相关的管理工作。

高校教室信息量大,动态变化频繁,要求可以精确、及时的对变化进行相应调整,所以教室管理是一个复杂的过程,管理员需要设定专门的数据库,并及时地更新和完善相关的教室信息库,要达到这个目的,最简单的方法就是建立教室管理系统。通过这个系统,可以大大提高高校信息化建设,可以在提高教室管理工作的效率和质量的同时,为学校制定相关的决策提供必要的依据。

本文从小组开发的并投入使用的荟庐报告厅微信借用小程序入手,尝试从小到大,从学院到学校,从几个教室到全校教室,进行“Class Int——基于互联网的教室资源管理系统”的项目搭建。

教室的使用具有一定的计划性和流动性,即一个班在相应的时间里,所上的课程和使用的教室是固定的,但是不同的课程在不同的时间里会使用不同的教室,这与教务处的课程安排是密切相关的。

为了能够更系统的,更有序的,更合理的,更有效地进行教室管理,有必要利用计算机来处理各种信息,这也就需要一个更有效的教室管理系统。

运用软件工程的基本原理和方法应用,对多媒体教室管理系统进行需求分析、系统架构、模块划分等提出具体的解决方案。

本系统是对教室的使用情况进行管理,为用户提供了一套操作简单、使用可靠、界面友好、易于管理和使用的处理工具。本系统对教室使用情况进行统一处理,避免数据存取、数据处理的重复,提高工作效率,减少了系统数据处理的复杂性。本系统不仅使管理人员从繁重的工作中解脱出来,而且提高了教室管理的效率,提高了教室管理的科学性,方便了用户查询、管理人员进行管理。

本系统是基于多媒体教室管理工作的需求、结合学生需求开发的多媒体教室管理系统,因此该系统结构清晰,简单实用,可以满足教学的需求。操作人员一般不用培训就能使用该系统。通过这样的教室管理系统,可以做到教室的集中化、规范化管理,实现准确、快速查询统计功能,从而减少教学人员的工作量,大大缩短了师生预约教室的时间。将全面提升教室管理的信息化管理水平,提高学校管理人员、教学人员的工作效率,降低学校的管理成本,提升教室的使用率,为学生进行科研设计、校内活动提供便利,为学校创造更大的社会效益。

1.2 需求分析

本系统的最终用户为在校师生,我们根据从学校方面取得的图表资料、文字资料以及其他细节方面的信息,根据我们日常生活中的经验,根据我们所做的其他询问和调查,得出用户的下列实际要求:

1.2.1 学校的组织机构情况

与教室管理相关的学校的组织机构有:学生、教师和教室。学校的所有日常工作都是主要围绕着这三大部分进行的。

学生方面,一个学校下设若干学院,如软件学院、VR 现代产业学院、信管学院等;一个学院下设若干专业,如软件学院下设三个专业:软件工程、物联网工程等;一个专业有若干班级,如软件学院的 2019 级软件工程专业下设六个班级:软件 191 班、软件 192 班、软件 193 班、软件 194 班、软件 195 班、软件 196 班、;一个班级有若干同学。

教师方面,一个学校下设若干学院,如软件学院、VR 现代产业学院、信管学院等;一个学院下设若干专业,如软件学院下设三个专业:软件工程、物联网工程、软件工程(中外合作);一个专业有若干教师。

课程方面,一个学校下设若干学院,如软件学院、VR 现代产业学院、信管学院等;一个学院下设若干专业,如软件学院下设三个专业:软件工程、物联网工程、软件工程(中外合作);一个专业开设有若干课程,如软件工程专业开设有数据库系统原理、面向对象建模与分析等。

教室方面,一个学校有若干教学楼,一个教学楼有若干楼层,一个楼层有若干教室。

一个教师可以开设若干门课程,一门课程可以由多个老师来教授。一个教室在不同的时段可以上不同的课程。

1.2.2 调查相关部门的业务活动情况

教务处:

教务处需要处理借教室申请信息,使用的数据是电子版的教室、教师和学生信息,对提出的借用教室申请采用手动的加工和处理,最后给借教室的人输出的是批准或拒绝借用教室的信息,即一个电子的教室使用条。

1.2.3 用户对系统的要求

信息要求:

由于系统的使用主体是教师和学生,因此对系统的信息要求可分为以下几个方面:

  • 教师信息教师的基本信息,主要包括教师的教师编号,教师姓名,所属院系,职称,身份证号等;

  • 学生信息

学生的基本信息,主要包括学生的学生编号,学生姓名,所属院系,职务,身份证号等;

  • 教室信息

教室的基本信息,主要包括教室的教室编号,教学楼号,楼层号,多媒体设备配备情况等;

  • 教学楼信息教学楼的基本信息,主要包括教学楼名称,教学楼编号等;

  • 课程信息课程的基本信息,主要包括课程名称、课程时间段等;

  • 预约信息表预约的相关信息,包括预约人、预约时间、预约描述、联系方式等;处理要求:学校现存系统存在的问题:

教务系统

我们从自身体验出发,并通过问卷调查了许多老师同学,认为学校现存的借用教室的程序过于繁琐,浪费时间,并且给教务处老师带来了很大的工作量,仅能在工作时间进行审核。

现在我校教务平台可以查阅每个教室当天各个时段的使用情况:“有课”、 “无课”。但是,每一页显示的时间过短、借用时间不灵活、借用限制多、审核时间长、不能查询任意教室在任意时段的使用情况、不能通过手机操作,完全不利于用户查询和借用。

物业楼管

由于教务系统的各种限制,使得近半数老师学生更倾向于向楼管人员口头进行登记借用的方式,相比于教务系统,有审核快、时间灵活的优势,同时也带来冲突、需要当面借用等一系列缺点。

鉴于以上存在的各种问题,给学校的主体――教师和学生,带来了很大的不便,使得现存的教务系统不能更好地服务于教师和同学,也不利于教务处审核的老师。因此我们认为有必要设计新系统,完善上述各种功能。

系统应当完成以下的信息处理:

教室查询

学生或者老师通过这个功能,可以通过手机 APP、小程序等客户端查询相关教学楼相关教室的信息以及该教室在每天任一时段的使用情况,例如有课、举办讲座、举行活动等等。这个功能以便使大家能更好地了解教室及其使用情况。

教室借用

学生或者老师通过这个功能,可以通过手机 APP、小程序等客户端借教室,即获得教室在某段时间的使用权,办讲座,开展社团活动等等。

借用审核

教务处老师通过这个功能,可以通过手机 APP、小程序等客户端实时接收借用申请并进行审核。

安全性要求:

  • 系统应设置访问用户的标识以鉴别是否是合法用户,并要求合法用户设置其密码,保证用户身份不被盗用;
  • 系统应对不同的数据设置不同的访问级别,限制访问用户可查询和处理数据的类别和内容;
  • 系统应对不同用户设置不同的权限,区分不同的用户,该系统的用户主要可以分为以下几类:

  • 普通用户:该类用户主要由学生、教职工等组成。用户可以使用微信授权使用本系统客户端小程序,正常使用教室的查看、借用、分享、调整、续约、举报等功能。同时对于部分高级用户(部分被授权的老师、学生),提供数据导出、举报查看等功能。

  • 审核管理人员:该类用户主要组成为学校教务处信息管理科相关管理人员。在拥有普通用户、高级用户所有权限的同时,可以对所有借用申请进行审核,也可以发布公告。

  • 系统管理员:主要操作信息管理系统,承担数据登入登出,修改,备份等工作,确保信息不泄漏、不丢失,采取一切可能的技术手段和管理措施,保护网络中的信息安全。

  • 运维人员:软件的测试维护人员。针对在系统的日常运营中可能出现的服务器过载,宕机,软件本身出现的 bug 等问题进行预防和解决。

  • 合作人员:平台的合作伙伴,该软件可能需要经学校网络管理中心同意,调取微信用户对应的个人信息,如学号、姓名等,用于破坏教室追责、实名认证等。

主要功能模块主要为以下几类:

  • 公共模块:任何用户均可以访问以下界面;
  • 主界面:系统主要功能界面;
  • 注册界面:用户需要注册账号后登录;
  • 登录界面:用户注册成功后,需要登录或者授权登陆系统才可以拥有访问其他页面的权限。

  • 用户模块:不同的用户对应访问不同的界面的权限;普通用户(学生、普通老师):

  • 教室详情模块:用户在该页面查看教室使用情况,以进一步进行借用预约、续约、等操作;

  • 预约详情模块:用户在该页面可以对自己的预约信息进行查看、修改等操作;

  • 聊天模块:用户在该页面可以对某个预约的发起人、系统客服发起私聊操作;

  • 帮助模块:用户在该页面可以查看系统的使用说明,对遇到的问题可以选择私聊系统客服、提交反馈、提交等操作;

  • 个人界面:用户在该页面可以查看自己的所有预约,同时对个人信息、权限等进行设置。

高级用户(被授权学生、被授权老师、相关教职工、物业楼管人员):除包含以上普通用户的所有模块以外,高级用户还拥有以下功能:

  • 数据导出界面:用户在该界面导出教室使用的基本情况;
  • 教室详情模块:在原有的基础上,该类用户可以查看申请者的实名信息;
  • 审核举报模块:用户在该界面可以查看举报信息,并进行相关扣分、意见反馈等操作。

管理员模块 :管理相关信息,不同的管理员类型对应访问不同的界面的权限;

  • 审核管理员用户(教务处相关老师):除包含高级用户的特有模块以外,高级用户还拥有以下功能:

  • 预约审核模块:用户在该界面查看并审核相关预约,并进行同意预约或者拒绝预约等操作;

  • 授权权限模块:审核管理员可以赋予指定用户高级用户权限,使其能够访问高级用户功能。

  • 系统管理员用户(系统开发维护人员):

  • 用户信息管理模块:可以对所有用户信息进行查看、修改、删除以及授予高级用户、管理员权限操作;

  • 备份恢复模块:可以对数据库信息进行备份、恢复操作。

完整性要求:

  • 各种信息记录的完整性,信息记录内容尽量不为空;
  • 各种数据间相互的联系的正确性;
  • 相同的数据在不同记录中的一致性。

1.2.4 确定系统的边界

经对前面的需求调查和初步的分析,确定由计算机完成的工作时对数据进行各种管理和处理,具体的工作内容见第二部分。由手工完成的工作主要有不能由计算机生成的,各种数据的更新,包括数据变化后的修改,数据的增加,失效数据或无用数据的删除等;以及系统的日常维护。

二、技术描述

2.1 系统相关技术介绍

2.1.1 系统开发相关技术

系统总体架构

前后端分离已成为互联网项目开发的业界标准使用方式,通过 nginx+tomcat 的方式,也可以中间加一个 nodejs 有效的进行解耦,并且前后端分离会为以后的大型分布式架构、弹性计算架构、微服务架构、多端化服务(多种客户端,例如:浏览器,车载终端,安卓,iOS 等等)打下坚实的基础。这个步骤是系统架构从猿进化成人的必经之路。核心思想是前端 HTML 页面通过 AJAX 调用后端的 API 接口并使用 JSON 数据进行交互。

  • Web 服务器:一般指像 Nginx 这类的服务器,他们一般只能解析静态资源;
  • 应用服务器:一般指像 Tomcat,Jetty,Resin 这类的服务器可以解析动态资源也可以解析静态资源,但解析静态资源的能力没有 Web 服务器好;一般都是只有 Web 服务器才能被外网访问,应用服务器只能内网访问。

以前的 JavaWeb 项目大多数都是 Java 程序员既搞前端,又搞后端。随着时代的发展,渐渐的许多大中小公司开始把前后端的界限分的越来越明确,前端工程师只管前端的事情,后端工程师只管后端的事情。正所谓术业有专攻,一个人如果什么都会,那么他毕竟什么都不精。大中型公司需要专业人才,小公司需要全才,但是对于个人职业发展来说,前后端需要分离。

早期主要使用 MVC 框架,Jsp+Servlet 的结构图如下:

图 1 Jsp+Servlet 结构图

所有的请求都被发送给作为控制器的 Servlet,它接受请求,并根据请求信息将它们分发给适当的 JSP 来响应。同时,Servlet 还根据 JSP 的需求生成 JavaBeans 的实例并输出给 JSP 环境。JSP 可以通过直接调用方法或使用 UseBean 的自定义标签得到 JavaBeans 中的数据。需要说明的是,这个 View 还可以采用 Velocity、Freemaker 等模板引擎。使用了这些模板引擎,可以使得开发过程中的人员分工更加明确,还能提高开发效率。

这种方式耦合性太强。那么,就算你用了 FreeMarker 等模板引擎,不能写 Java 代码。那前端也不可避免的要去重新学习该模板引擎的模板语法,无谓增加了前端的学习成本。

因此,我们需要前后端半分离模式,前端负责开发页面,通过接口(AJAX)获取数据,采用 Dom 操作对页面进行数据绑定,最终是由前端把页面渲染出来。

这也就是 AJAX 与 SPA 应用(单页应用)结合的方式,其结构图如下:

图 2 前后端分离结构图

步骤如下:

  • 浏览器请求,CDN 返回 HTML 页面;
  • HTML 中的 JS 代码以 AJAX 方式请求后台的 RESTFul 接口;
  • 接口返回 JSON 数据,页面解析 JSON 数据,通过 Dom 操作渲染页面;

后端提供的都是以 JSON 为数据格式的 API 接口供 Native 端使用,同样提供给 Web 的也是 JSON 格式的 API 接口。

那么意味着 Web 工作流程是:

  • 打开 Web,加载基本资源,如 CSS,JS 等;
  • 发起一个 AJAX 请求再到服务端请求数据,同时展示 loading;
  • 得到 JSON 格式的数据后再根据逻辑选择模板渲染出 DOM 字符串;
  • 将 DOM 字符串插入页面中 Web view 渲染出 DOM 结构;

首先,这种方式的优点是很明显的。前端不会嵌入任何后台代码,前端专注于 HTML、CSS、JS 的开发,不依赖于后端。自己还能够模拟 JSON 数据来渲染页面。发现 Bug,也能迅速定位出是谁的问题。

2.1.2 端开发相关技术

HTML5

HTML 是互联网上应用最广泛的标记语言。HTML 文件就是普通文本 +HTML 标记,而不同的 HTML 标记能表示不同的效果。(简单的说 HTML 是超文本标记语言)

HTML5 草案的前身名为 Web Applications 1.0,于 2004 年被 WHATWG 提出,于 2007 年被 W3C 接纳,并成立了新的 HTML 工作团队。

如果从狭义的角度来讲,HTML5 就是 HTML4 的新一代产品。而如果从广义的角度来讲,则是新一代的富客户端解决方案。

HTML5 的优势在于支持 Html5 的浏览器包括 Firefox(火狐浏览器),IE9 及其更高版本,Chrome(谷歌浏览器),Safari,Opera 等;国内的 遨游浏览器(Maxthon),以及基于 IE 或 Chromium(Chrome 的工程版或称实验版)所推出的 360 浏览器、搜狗浏览器、QQ 浏览器、猎豹浏览器等国产浏览器同样具备支持 HTML5 的能力。

需要注意的是,虽然很多浏览器目前已经能够支持 HTML5,但是显示效果仍旧存在差异性。

JavaScript

JavaScript 一种动态类型、弱类型、基于原型的客户端脚本语言,用来给HTML 网页增加动态功能。

动态:在运行时确定数据类型。变量使用之前不需要类型声明,通常变量的类型是被赋值的那个值的类型。弱类:计算时可以不同类型之间对使用者透明地隐式转换,即使类型不正确,也能通过隐式转换来得到正确的类型。

原型:新对象继承对象(作为模版),将自身的属性共享给新对象,模版对象称为原型。这样新对象实例化后不但可以享有自己创建时和运行时定义的属性,而且可以享有原型对象的属性。

JavaScript 由三部分组成:

  • ECMAScript(核心)

作为核心,它规定了语言的组成部分:语法、类型、语句、关键字、保留字、操作符、对象

  • DOM(文档对象模型)

DOM 把整个页面映射为一个多层节点结果,开发人员可借助 DOM 提供的 API,轻松地删除、添加、替换或修改任何节点。DOM 也有级别,分为 DOM1、DOM2、DOM3,拓展不少规范和新接口。

  • BOM (浏览器对象模型)

支持可以访问和操作浏览器窗口的浏览器对象模型,开发人员可以控制浏览器显示的页面以外的部分。

  • JavaScript 版本

JavaScript 语言是在 10 天时间内设计出来的,虽然语言的设计者水平非常NB,但谁也架不住“时间紧,任务重”,所以,JavaScript 有很多设计缺陷。

此外,由于 JavaScript 的标准——ECMAScript 在不断发展,最新版 ECMAScript 6 标准(简称 ES6)已经在 2015 年 6 月正式发布了,所以,讲到JavaScript 的版本,实际上就是说它实现了 ECMAScript 标准的哪个版本。

  • JavaScript框架

jQuery Mobile 是一个易于触摸的 Web UI 开发框架,可让您开发可在智能手机和平板电脑上使用的移动 Web 应用程序。jQueryMobile 框架建立在 jQuery 核心之上,并提供了许多功能,包括 HTML 和 XML 文档对象模型(DOM)遍历和操纵,处理事件,使用 AJAX 执行服务器通信以及网页的动画和图像效果。 。 移动框架本身是从 jQuery 核心单独下载的大约 12KB(压缩和压缩),压缩/压缩后约为 25KB。 与其他 jQuery 框架一样,jQueryMobile 是一个免费的双重许可(MIT 和 GPL)库。

尽管 jQuery Mobile 仍在 Alpha 中,但仍有一些演示和文档。 建议您查看的文档和演示相关信息 ,并期待在演示源代码下载部分 。

在撰写本文时,jQuery Mobile 框架是 Alpha 2 版本(v1.0a2)。 本守则为草案形式,可能会随时更改。 但是,现有框架非常可靠。 有了 alpha 版本中令人印象深刻的组件集,jQuery Mobile 有望成为开发移动 Web 应用程序的理想框架和工具集。

该框架易于使用。您可以主要使用很少或没有 JavaScript 的标记驱动来开发页面。

尽管 jQuery Mobile 利用最新 HTML5,CSS3 和 JavaScript,但并非所有移动设备都提供这种支持。 jQuery Mobile 的理念是同时支持高端和功能较弱的设备(例如不支持 JavaScript 的设备),并仍提供最佳体验。

jQuery Mobile 在设计时考虑了可访问性。 它支持可访问的富 Internet 应用程序(WAI-ARIA),以帮助使用辅助技术的残障游客访问网页。

jQuery Mobile 框架的整体大小相对较小,JavaScript 库为 12KB,CSS 为6KB,还有一些图标。

该框架还提供了一个主题系统,使您可以提供自己的应用程序样式。

当工具箱,例如 PhoneGap 的使用(参见相关信息 ),它使用网络技术来构建独立的应用程序,jQuery Mobile 框架可以帮助简化您的应用程序的开发。

2.1.3 后端开发相关技术

Spring

Spring 框架是 J2EE 应用开发的集成解决方案,提供了 IoC(控制反转)和 AOP(面向切面)两种核心机制,为应用程序内部各模块之间实现高内聚、低耦合提供了支持。IoC,又称“控制反转”,是一种根据配置实例化 Java 对象,管理对象生命周期,组织对象之间关系的设计思想。Spring 框架将纳入生命周期管理的 Java 对象称之为”Bean”,Spring 框架在启动时自动创建 Bean,并将 Bean 放到 Spring 的上下文中。如果某个 Bean 申明需要关联另外一个 Bean, Spring 框架自动建立 Bean 之间的关联。当某个 Bean 申明需要关联另外一个 Bean 时,可以申明关联另外一个 Bean 的接口,Spring 会自动从上下文中查找实现该接口的 Bean,从而建立两者之间的关联。在 IoC 机制的支持下,Spring 可以 J2EE 体系中各种技术集成起来,如图所示。

图 3 JSpring 结构图

这些技术包含 Web 开发技术(SpringWebMVC)、数据持久化技术(SpringORM)、缓存技术(SpringDataCache)、RESTFul 客户端(SpringRestTemplate)、安全技术(SpringSecurity)、服务注册发现和负载均衡(SpringCloud)。Spring 支持各种组件存在不同的第三方实现方案,这些第三方实现方案并可相互替换,开发者可根据场景选择最适合的实现方案,当需要修改实现方案时,仅需要对应用进行简单的配置,不需要对已完成的代码做任何改动。比如,数据缓存技术(SpringDataCache)存在将数据缓存到 Redis、缓存到 memcache、缓存到本地内存几种方案,开发者只需要调用缓存 API,而不需要关注具体实现。再比如,服务注册发现和负载均衡框架(SpringCloud)框架体系中,需要搭建服务注册中心,服务注册中心的实现技术有 etcd、consul、eureka、dubbo 等,这些实现技术来自不同的公司或开源组织,而开发者选择或切换技术实现时,仅需要简单的配置,无需修改代码。

AOP,又称面向切面编程。面向切面思想从面向对象思想基础上发展而来,用于将系统的核心功能和辅助功能解耦。Web 设计开发者在设计系统的某一功能模块时,除了要设计该功能本身的逻辑实现,还需要考虑其辅助功能,如记录日志、进行权限控制、对数据进行缓存、对调用方进行流量控制等等。Spring 将上述辅助功能看作“切面”,切面是一个独立的模块,调用者调用服务提供者的 API 的过程会透明触发切面的代码逻辑,切面负责对调用请求进行拦截、处理、过滤。

Spring Boot

SpringBoot 为基于 J2EE 架构的 Web 后端集成开发框架。SpringBoot 从

Spring 框架发展而来,在 Spring 框架的基础上,简化 Spring 框架的默认配置,如支持在应用程序中嵌入 Web 服务器实现可独立运行的 Web 应用,从而简化 Web 应用的部署。

Spring Data Java Persistent API

JavaPersistentAPI(Java 数据持久化 API)简称 JPA[28]。Java 是一种面向对象的编程语言,信息在 Java 应用内存中是以类和对象的形式组织的,对象拥有属性、方法和关联关系。而企业的生产运营数据通常由数据库管理,数据库按存储方式,可以分为关系型数据库、keyvalue 数据、列式数据库、图形数据库等。关系型数据库是企业生产应用的主流数据库,其按照表、字段、约束的形式组织数据结构,应用程序通过 SQL(结构化查询语言)操作关系型数据库的数据。

良好的系统架构设计应具备数据独立性特征,即数据结构的改变不影响上层的应用程序,数据独立性包含物理独立性和逻辑独立性两个方面。物理独立性表示数据磁盘等介质的存储结构的改变不影响应用程序,表现为底层数据库中间件的变动对应用程序透明,如将 Oracle 更换为 MySQL 或其他数据库。逻辑独立性表示数据逻辑结构的变化对应用程序透明,如增加表、增加字段。JPA 定义了

Java 应用程序和关系型数据库之间的接口,具体功能有:

  • 定义了对 Java 对象新增、修改、删除、查询接口,应用程序逻辑仅需要面
  • 向 JPA 编程。
  • 通过元数据定义 Java 对象、属性、关系和关系型数据库表、字段、约束之间的映射,将面向对象的 API 翻译成可由数据库执行的 SQL 语句。

JPA 实现了数据的物理独立性。如 JPA 提供了对不同关系数据库 dialect (方言)的支持,实现同一个 API 针对不同的关系数据库产品,翻译成不同的 SQL。如分页查询 A 表,每页 10 行,查询第 1 页的场景,针对 MySQL 生成的 SQL 是“select fromAlimit0,10”,而针对 Oracle 的语法却是“select from (select rownum rownum_ a.* from A a where rownum<=10) whererownum_>=1”。

JPA 实现了数据的逻辑独立性。关系数据库数据模型变动后,需要调整 Java 对象和表、字段、约束的映射的元数据映射,对上层应用代码透明。

JPA 按照接口和实现相分离的原则设计,具备较强的可扩展性,JPA 定义了一套 API 标准,由第三方团队实现此标准。应用程序的开发者可选择 JPA 的实现,更改 JPA 实现对上层应用代码无任何影响。

腾讯云分布式数据库 TDSQL-C

云原生数据库 TDSQL-C(Cloud Native Database TDSQL-C,TDSQL-C)是腾讯云自研的新一代高性能高可用的企业级分布式云数据库。融合了传统数据库、云计算与新硬件技术的优势,100% 兼容 MySQL 和 PostgreSQL,实现超百万级QPS 的高吞吐,128TB 海量分布式智能存储,保障数据安全可靠。

TDSQL-C 基于 MySQL5.7,相比于 MySQL,具有以下特点:定制内核

深度定制的数据库内核,实现诸多企业级特性和优化,服务公司内部用户和腾讯云百 TB 级别的外部用户,是支撑关键业务平稳运行的基石。

日志即数据库

可计算智能存储,由分布式存储系统自动管理数据的多副本,实现自动扩缩容,自动故障校验检测和修复。日志即数据库,真正实现了将 RedoLOG 下沉到存储层,将网络 IO 减少到最低。

面向服务的体系结构

架构基于现有的云服务如对象存储 COS,云硬盘 CBS,云服务器 CVM,云网络服务如私有网络 VPC,腾讯网关服务 TGW(Tencent Gateway)。

软件优化与新硬件相结合

通过基于 SPDK 和 RDMA 的零拷贝技术,减少了操作系统上下文切换以及数据在用户态和内核态之间拷贝引起的性能损耗,进一步优化了关键路径的系统性能,降低请求延迟。

2.2 系统功能的设计和划分

根据如上得到的用户需求,我们将本系统按照所完成的功能分成以下四个子系统:

2.2.1 用户信息子系统

用户点击登陆、注册后,用户需要填写相关信息,并提交登陆/注册/在验证成功后进入,并赋予相关权限。

用户和系统其他部分可以通过该系统对用户的部分个人信息进行读取访问和修改。

2.2.2 事件消息子系统

用户进入问题反馈、举报、设备报修、教室借用详情页面、私聊消息后,用户需要填写完整后提交数据库。在提交相关事件后,经过系统处理发送给相关人员进行进一步审核/核查并提交结果返回给用户。

2.2.3 教室处理子系统

用户进行教室借用时,该系统提取数据库中教室的相关数据,经过提取并格式化后传输给客户端并呈现在用户面前以便于进一步借用操作。

管理员更新教室时,可以通过上传学校的开课表,该系统通过分析、提取、格式化开课表内的相关数据后将更新的相关信息写入数据库。

2.2.4 日志消息子系统

用户和其他系统以及程序本身运行时所做的操作、产生的信息和错误都会被该系统所捕获,经过汇总处理和格式化后输出至相关数据库,便于管理运维人员进行系统维护。

经上述分析,我们已经得到了对于该系统的基本要求和系统模块的划分,综上,我们对教师查询子系统、教室借用子系统进行具体的数据库设计,在需求分析中形成的数据流图如下一部分所示。

2.3 数据流图

数据流图(Data Flow Diagram)简称 DFD,它从数据传递和加工角度,以图形方式来表达系统的逻辑功能、数据在系统内部的逻辑流向和逻辑变换过程,是结构化系统分析方法的主要表达工具及用于表示软件模型的一种图示方法。

为了表达处理过程的数据加工情况,需要采用层次结构的数据流图。按照系统的层次结构进行逐步分解,并以分层的数据流图反映这种结构关系,能清楚和理解整个系统。

2.3.1 顶层数据流图

图 4 顶层数据流图

2.3.2 中间层数据流图

图 5 中间层数据流图

2.3.3 底层教室处理系统数据流图

图 6 底层教室处理系统数据流图

2.3.4 底层消息处理系统数据流图

图 7 底层消息处理系统数据流图

2.3.5 底层中央监视系统数据流图

图 8 底层中央监视系统数据流图

2.4 数据词典

数据字典是系统中各类数据描述的集合,是进行详细的数据收集和数据分析所获得的主要成果。通常包括:数据项、数据结构、数据流、数据存储和处理过程五个部分。

数据字典是对数据流图的详细描述。

结合上一部分所做数据流图,对该系统的数据词典部分进行设计与分析。

用户信息数据词典

表1 数据词典描述·用户 ID

数据项编号 I 01 01
数据项名称 用户 ID
数据项字段 id
数据项描述 用于系统辨别用户
数据项类型 Int(15)
数据项备注 主键、自增

表2 数据词典描述·用户姓名

数据项编号 I 01 02
数据项名称 用户姓名
数据项字段 name
数据项描述 用于储存用户姓名
数据项类型 varchar(10)
数据项备注 非空

表 3 数据词典描述·用户用户名

数据项编号 I 01 03
数据项名称 用户用户名
数据项字段 user_name
数据项描述 用于储存用户用户名
数据项类型 varchar(20)
数据项备注 非空

表 4 数据词典描述·用户密码加密盐

数据项编号 I 01 04
数据项名称 用户密码加密盐
数据项字段 salt
数据项描述 用于用户密码加密
数据项类型 varchar(16)
数据项备注 非空,注册时随机生成 16 位字符串,对原始用户密码进行非对称矩阵加盐加密

表 5 数据词典描述·用户密码

数据项编号 I 01 05
数据项名称 用户密码
数据项字段 password
数据项描述 用于储存用户密码
数据项类型 varchar(255)
数据项备注 非空,用户经过非对称矩阵加盐加密算法后的密码

表 6 数据词典描述·用户绑定手机号

数据项编号 I 01 06
数据项名称 用户绑定手机号
数据项字段 phone
数据项描述 用于储存用户手机号
数据项类型 int(11)
数据项备注 便于信息登记以及密码找回

表 7 数据词典描述·用户绑定邮箱

数据项编号 I 01 07
数据项名称 用户绑定邮箱
数据项字段 email
数据项描述 用于储存用户邮箱
数据项类型 varchar(40)
数据项备注 便于密码找回

表 8 数据词典描述·用户 QQ openID

数据项编号 I 01 08
数据项名称 用户 QQ openID
数据项字段 QQ
数据项描述 用于系统第三方快速登录
数据项类型 varchar(255)
数据项备注

表 9 数据词典描述·用户微信 openID

数据项编号 I 01 09
数据项名称 用户微信 openID
数据项字段 wechat
数据项描述 用于系统第三方快速登录
数据项类型 varchar(255)
数据项备注

表 10 数据词典描述·用户苹果 openID

数据项编号 I 01 10
数据项名称 用户苹果 openID
数据项字段 apple
数据项描述 用于系统第三方快速登录
数据项类型 varchar(6255)
数据项备注

表 11 数据词典描述·用户一卡通号

数据项编号 I 01 11
数据项名称 用户一卡通号
数据项字段 stu_id
数据项描述 用于信息登记
数据项类型 int(10)
数据项备注 以财大为例,一卡通号为 10 位

表 12 数据词典描述·用户类型

数据项编号 I 01 12
数据项名称 用户类型
数据项字段 type
数据项描述 用于记录用户身份(学生/教师/审核员/管理员)
数据项类型 varchar(5)
数据项备注 非空

表 13 数据词典描述·用户高级权限

数据项编号 I 01 13
数据项名称 用户高级权限
数据项字段 high_permissions
数据项描述 用于记录用户是否具有高级权限
数据项类型 bit
数据项备注 非空,0 为 false,1 为 true,默认值为 0

表 14 数据词典描述·用户认证

数据项编号 I 01 14
数据项名称 用户认证
数据项字段 authentication
数据项描述 用于记录用户是否认证
数据项类型 bit
数据项备注 非空,0 为 false,1 为 true,默认值为 0

表15 数据词典描述·用户注册时间

数据项编号 I 01 15
数据项名称 用户注册时间
数据项字段 created_date
数据项描述 用于记录用户注册时间
数据项类型 datetime
数据项备注 非空,无具体意义,用于生成用户加密盐

表 16 数据词典描述·用户(密码)更新时间

数据项编号 I 01 16
数据项名称 用户(密码)更新时间
数据项字段 updated_date
数据项描述 用于记录用户上次修改密码时间
数据项类型 datetime
数据项备注 非空,用于判断用户自动登录是否有效

表 17 数据词典描述·用户上次登陆时间

数据项编号 I 01 17
数据项名称 用户上次登陆时间
数据项字段 login_date
数据项描述 用于记录用户上次登陆时间
数据项类型 datetime
数据项备注 非空,用于判断用户当前登录是否有效

表 18 数据词典描述·用户通知 id

数据项编号 I 01 18
数据项名称 用户通知 id
数据项字段 server_key
数据项描述 用于记录用户微信通知服务 key
数据项类型 datetime
数据项备注 用于向用户微信推送消息

表 19 数据词典描述·用户设备 ua

数据项编号 I 01 19
数据项名称 用户设备 ua
数据项字段 ua
数据项描述 用于记录用户设备
数据项类型 datetime
数据项备注 验证自动登陆是否有效

表 20 数据词典描述·用户信用

数据项编号 I 01 20
数据项名称 用户信用
数据项字段 credit
数据项描述 用于记录用户信用值
数据项类型 Int(3)
数据项备注 非空,用于记录用户信用值

表 21 数据词典描述·用户当前登陆令牌

数据项编号 I 01 21
数据项名称 用户当前登陆令牌
数据项字段 token
数据项描述 用于记录用户令牌
数据项类型 Int(3)
数据项备注 用于检测是否登陆有效

教室信息数据词典

表 22 数据词典描述·教室 ID

数据项编号 I 02 01
数据项名称 教室 ID
数据项字段 room_id
数据项描述 用于系统辨别、储存教室名称
数据项类型 varchar(5)
数据项备注 主键,例如 H104

表 23 数据词典描述·教室所属教学楼

数据项编号 I 02 02
数据项名称 教室所属教学楼
数据项字段 room_building
数据项描述 用于储存教室所属教学楼
数据项类型 varchar(10)
数据项备注 非空

表 24 数据词典描述·教室所属楼层

数据项编号 I 02 03
数据项名称 教室所属楼层
数据项字段 room_floor
数据项描述 用于储存教室所属楼层
数据项类型 varchar(4)
数据项备注 非空

表 25 数据词典描述·教室最大容纳人数

数据项编号 I 02 04
数据项名称 教室最大容纳人数
数据项字段 room_number
数据项描述 用于储存教室最大容纳人数
数据项类型 Int(3)
数据项备注 非空

表26 数据词典描述·教室类型

数据项编号 I 02 05
数据项名称 教室类型
数据项字段 room_type
数据项描述 用于辨别教室类型
数据项类型 varchar(10)
数据项备注 非空,例如:机房、多媒体教室、普通教室

事件消息信息数据词典

表 27 数据词典描述·事件 ID

数据项编号 I 03 01
数据项名称 事件 ID
数据项字段 event_id
数据项描述 用于系统辨别事件
数据项类型 int
数据项备注 主键、自增

表 28 数据词典描述·事件类型

数据项编号 I 03 02
数据项名称 事件类型
数据项字段 event_type
数据项描述 用于储存事件类型
数据项类型 varchar(10)
数据项备注 非空,例如:预约、反馈、举报等

表 29 数据词典描述·事件标题

数据项编号 I 03 03
数据项名称 事件标题
数据项字段 event_title
数据项描述 用于简述事件
数据项类型 varchar(20)
数据项备注 非空

表 30 数据词典描述·事件描述

数据项编号 I 03 04
数据项名称 事件描述
数据项字段 event_des
数据项描述 用于描述事件
数据项类型 varchar(255)
数据项备注

表 31 数据词典描述·事件开始时间

数据项编号 I 03 05
数据项名称 事件开始时间
数据项字段 event_s
数据项描述 用于储存事件开始时间
数据项类型 datetime
数据项备注 非空

表 32 数据词典描述·事件结束时间

数据项编号 I 03 06
数据项名称 事件结束时间
数据项字段 event_e
数据项描述 用于储存事件结束时间
数据项类型 datetime
数据项备注 非空

表 33 数据词典描述·发起用户 ID

数据项编号 I 03 07
数据项名称 发起用户 ID
数据项字段 user_id
数据项描述 记录事件发起人
数据项类型 int
数据项备注 外键,对应用户信息表中的 id

表 34 数据词典描述·涉及教室 ID

数据项编号 I 03 08
数据项名称 涉及教室 ID
数据项字段 wechat
数据项描述 用于记录事件涉及教室
数据项类型 int
数据项备注 外键,对应教室信息表中的 id

表 35 数据词典描述·事件已读

数据项编号 I 03 09
数据项名称 事件已读
数据项字段 read
数据项描述 用于记录事件是否已读
数据项类型 int
数据项备注

日志信息数据词典

表 36 数据词典描述·日志 ID

数据项编号 I 04 01
数据项名称 日志 ID
数据项字段 id
数据项描述 用于辨别系统日志
数据项类型 int
数据项备注 主键、自增

表 37 数据词典描述·日志详情

数据项编号 I 04 02
数据项名称 日志详情
数据项字段 formatted_message
数据项描述 控制台输出内容
数据项类型 text
数据项备注 非空

表 38 数据词典描述·日志标题

数据项编号 I 04 03
数据项名称 日志标题
数据项字段 logger_name
数据项描述 控制台输出标题
数据项类型 varchar(255)
数据项备注 非空

表 39 数据词典描述·日志等级

数据项编号 I 04 04
数据项名称 日志等级
数据项字段 level_string
数据项描述 用于储存该日志的等级
数据项类型 varchar(255)
数据项备注 非空,例如 info、warn、error

表 40 数据词典描述·出错线程名

数据项编号 I 04 05
数据项名称 出错线程名
数据项字段 thread_name
数据项描述 用于储存出错线程名
数据项类型 varchar(255)
数据项备注 尽可能获取,方便问题排查

表 41 数据词典描述·调用文件

数据项编号 I 04 06
数据项名称 调用文件
数据项字段 caller_filename
数据项描述 用于储存调用文件名称
数据项类型 varchar(255)
数据项备注 日志输出所在的 Java 文件,方便问题排查

表 42 数据词典描述·调用函数

数据项编号 I 04 07
数据项名称 调用函数
数据项字段 caller_class
数据项描述 用于储存调用函数名称
数据项类型 varchar(255)
数据项备注 日志输出所在函数,方便问题排查

表 43 数据词典描述·调用方法

数据项编号 I 04 08
数据项名称 调用方法
数据项字段 caller_method
数据项描述 用于储存调用方法
数据项类型 varchar(255)
数据项备注 日志输出所在方法,方便问题排查

表 44 数据词典描述·调用行

数据项编号 I 04 09
数据项名称 调用行
数据项字段 caller_line
数据项描述 用于储存调用行
数据项类型 int
数据项备注 日志输出所在行数,方便问题排查

2.5 数据结构定义

数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。本章将对系统主要的数据结构进行分析和定义。

表 45 数据结构定义·用户账户信息

数据结构编号 DS01
数据结构名称 用户账户信息
数据结构简述 用户登陆、注册信息描述
数据结构组成 I 01 01+I 01 02+I 01 03+I 01 04+I 01 07+I 01 08+I 01 01+I 01;12+I 01 13+I 01 14

表 46 数据结构定义·用户个人信息

数据结构编号 DS02
数据结构名称 用户个人信息
数据结构简述 用户个人资料描述
数据结构组成 I 01 05+I 01 06+I 01 09+I 01 10+I 01 11+I 01 15

表 47 数据结构定义·教室详情信息

数据结构编号 DS03
数据结构名称 教室详情信息
数据结构简述 教室名称、信息描述
数据结构组成 I 02 01+I 02 02+I 02 03+I 02 04+I 02 05

表 48 数据结构定义·事件详情信息

数据结构编号 DS04
数据结构名称 事件详情信息
数据结构简述 事件类型、名称等描述
数据结构组成 I 03 01+I 03 02+I 03 03+I 03 04+I 03 05+I 03 06+I 03 07+I 03;08

表 49 数据结构定义·日志信息

数据结构编号 DS05
数据结构名称 日志信息
数据结构简述 日志类型、名称等描述
数据结构组成 I 04 01+I 04 02+I 04 03+I 04 04+I 04 05+I 04 06+I 04 07+I 04;08+I 04 09

2.6 数据流定义

数据流是一组有序,有起点和终点的字节的数据序列。包括输入流和输出流。

数据流最初是通信领域使用的概念,代表传输中所使用的信息的数字编码信号序列。

本部分将对系统主要的几个数据流进行定义。

表 50 数据流定义·提交用户事件消息

数据流编号 D1
数据流名称 提交用户事件消息
数据流简述 用户提交预约申请、反馈、举报等事件消息
数据流来源 用户
数据流去向 事件消息系统
数据流组成 I 03 01+I 03 02+I 03 03+I 03 04+I 03 05+I 03 06+I 03 07+I 03;08

表 51 数据流定义·教室空闲数据

数据流编号 D2
数据流名称 教室空闲数据
数据流简述 系统将处理后的空闲教室数据
数据流来源 教室处理系统
数据流去向 用户
数据流组成 I 02 01+I 02 02+I 02 03+I 02 04+I 02 05

2.7 数据库设计与实现

在 B/S 结构的 Web 系统中,数据库是整个系统的数据中心,用户所需的信息都是通过中间控件调用数据库数据而得到的。数据库的设计在本系统中占有很大的比重,一个良好的数据库不但可以使系统以较优秀的性能运行,也可以简化开发难度,缩短开发周期。本系统采用的数据库是 MySQL 数据库,前文中提到了数据库设计的大体思路并给出了数据库的表结构和数据库表的关系表,下面就细节方面进一步细化。

概念数据模型的目标是统一业务概念,作为业务人员和技术人员之间沟通的桥梁,确定不同实体之间的最高层次的关系。概念数据模型是最终用户对数据存储的看法,反映了最终用户综合性的信息需求,它以数据类的方式描述企业级的数据需求,数据类代表了在业务环境中自然聚集成的几个主要类别数据。

概念模型设计阶段,主要处于系统分析的阶段,属性可以不完全描述,但也可以描述一些主要的属性。概念数据模型的内容包括重要的实体及实体之间的关系。在概念数据模型中不包括实体的属性,也不用定义实体的主键。这是概念数据模型和逻辑数据模型的主要区别。

图 6 数据库概念模型 E-R 图

逻辑数据模型的目标是尽可能详细的描述数据,但并不考虑数据在物理上如何来实现,也是逻辑模型与物理模型之间区别性的关键。例如在本系统数据库的逻辑模型中可能加入了由于系统设计需要的一些字段(属性),这些字段可能是在业务概念上不存在或不需要的。逻辑模型是概念模型从真实世界向计算机世界的转换,加入了系统设计的相关内容。逻辑数据建模不仅会影响数据库设计的方向,还间接影响最终数据库的性能和管理。如果在实现逻辑数据模型时投入得足够多,那么在物理数据模型设计时就可以有许多可供选择的方法。逻辑数据模型反映的是系统分析设计人员对数据存储的观点,是对概念数据模型进一步的分解和细化。逻辑数据模型是根据业务规则确定的,关于业务对象、业务对象的数据项及业务对象之间关系的基本蓝图。逻辑数据模型的内容包括所有的实体和关系,确定每个实体的属性,定义每个实体的主键,指定实体的外键,需要进行范式化处理。接着上面逻辑结构的设计,该系统的的逻辑结构,如下图所示。

图 7 数据库逻辑模型图

物理数据模型的目标是指定如何用具体的数据库模式来实现逻辑数据模型,以及真正的保存数据。

物理数据模型是在逻辑数据模型的基础上,考虑各种具体的技术实现因素,进行数据库体系结构设计,真正实现数据在数据库中的存储。

物理数据模型的内容包括确定所有的表和列,定义外键用于确定表之间的关系,基于用户的需求可能进行范式化等内容。在物理实现上的考虑,可能会导致物理数据模型和逻辑数据模型有较大的不同。

物理模型跟逻辑模型的区别就是,逻辑模型并不指出特定的数据存储,仅限于系统逻辑上的描述。物理模型是逻辑模型在具体存储介质上的表现,直接与具体的数据库管理系统或存储介质相关的数据模型。

物理模型给出了在数据库系统的字段名称,与具体数据库管理系统相关的数据类型的定义。而逻辑模型与具体的数据库管理系统或存储介质无关,仅为使用计算机系统概念中的一种逻辑结构。

图 8 数据库物理模型图

在数据库的设计当中,本系统尽量采用单表设计(即尽可能将近似数据存储在同一个数据表里)。相比于设计多个表,单表设计具有以下的好处:

从开发效率来看

联合查询是需要多个单查询进行逻辑组合才能完成的查询的工作,联合查询仅仅需要一个 SQL 就可以完成查询工作,即把业务逻辑放到了 SQL 中,由数据库来处理,相对来说开发效率会比较高。

从查询效率来看

查询的执行流程:连接数据库、传入 SQL、执行 SQL 语句、返回查询结果、断开连接;

无论是单查询还是联合查询,进行查询时都是需要进行上述流程的。传统的实现中,认为需要让数据库来完成更多的工作,这样做的原因在于网络通信、查询解析和优化是一件代价很高的事情。然而现在的众多数据库在设计上连接和断开连接都是轻量级的,返回一组小的查询结果也很高效。并且现在的网络速度与之前相比也快了很多,连接数据库、返回查询结果、断开连接的耗时不在是影响效率的主要原因。那么 SQL 的执行耗时成了关键,多个单查询的耗时根据情况不

同无法与联合查询的耗时进行对比,不过我们可以通过以下几个方面进行考虑:

缓存效率

数据库是存在缓存机制的,当一条 SQL 执行之后,再次执行相同的 SQL,数

据库会把缓存的结果返回出去,而不会重新查询数据库。单查询的可重用性较高,所以缓存效率相较之联合查询会更高。使用第三方 Redis 等缓存,key(组合更少更单一)和 value 使用也相应减少。

锁竞争

为了保证数据库的数据同步,在数据库进行读写时,数据库会用锁机制,限制其他连接对其操作。读写越快,数据库的并发性越高。由于联合查询查询速度比单个查询要慢很多,这样联合查询会增加锁的竞争关系,所以用单查询会更好些。

查询结果有效使用率

相较于联合查询,单查询的查询结果有效利用率要高很多,也就是说联合查询会浪费一些时间在查询无用的数据上。例如后台管理的列表界面,通常都会分页显示,关联查询的结果集,只有当前页的数据被使用,其他都是无用的,但数据库需要消耗额外资源得到全部结果集,再从中得到当前页数据。单表查询结果放 Redis 等缓存中使用效率更高。

大数量的表推荐使用单表,小数据量的表推荐使用组合查询。

单表 SQL 虽然设计难度大但是简单容易理解,而且做分库等改动较小。

综合以上三个模型的设计与分析,本系统的表设计归纳总结如下图:

图 9 数据库系统表设计图

2.8 主要系统功能模块设计与实现

2.8.1 构建 Web 入口

首先构建简单首页 index.html,作为入口。利用渐入渐出的加载效果实现仿 APP 式的加载。构建了 8 张启动图并随机启用,同时在前端和后端效验用户登陆状态。如果效验成功,则直接进入 app,否则继续判断。如果用户第一次使用该 app,则会跳转到权限验证页面,告知用户相关权限,待用户同意后,再跳转至登陆页面。

相关 js 数据处理代码如下:

js checkLogin() tools.ajaxGet("http://localhost:8080/getInfo", autoUser, function(res) { console.log(res) if(res.status === 0) { console.log("云端获取成功") registerJsonn(res.data) ajax = true; } else { console.log("云端认证失败") document.getElementById('frame').src = "login.html"; document.title = "笑约"; ajax = true; } })

2.8.2 构建登录前端页面

接下来是登陆页面的搭建。采用 js、JSP 为辅 CSS、HTML 为主,构建页面。

动态背景采用纯 CSS 实现,backdrop-filter 属性为一个元素后面区域添加模糊效果,使用 filter(滤镜) 属性,改变颜色,hue-rotate(deg) 给图像应用色相旋转,calc() 函数用于动态计算长度值,var() 函数调用自定义的 CSS 属性值 x,调用动画 animate,需要 10s 完成动画,linear 表示动画从头到尾的速度是相同的,infinite 指定动画应该循环播放无限次,动态计算动画延迟几秒播放,部分代码如下:

css .box .circle { position: absolute; background: rgba(255, 255, 255, 0.1); backdrop-filter: blur(5px); box-shadow: 0 25px 45px rgba(0, 0, 0, 0.1); border: 1px solid rgba(255, 255, 255, 0.5); border-right: 1px solid rgba(255, 255, 255, 0.2); border-bottom: 1px solid rgba(255, 255, 255, 0.2); border-radius: 50%; filter:hue-rotate(calc(var(--x) * 70deg)); animation:animate 10s linear infinite; animation-delay:calc(var(--x) * -1s); } @keyframes animate { 0%, 100% { transform: translateY(-50px); } 50% { transform: translateY(50px); } }

登录窗口代码,同时集成错误提示,实现一页多用,由前端 JavaScript 和后端 API 一同控制,部分代码展示:

```c++

登陆成功

```

其中,box2 为隐藏窗口。只有当登陆成功后,服务器 API 返回参数 success 并且 cookie 中的 username 和加密的 token 经过 JavaScript 校对后相对应,才能显示这个隐藏的 box2(即登陆成功),同时将登陆窗口 box1 隐藏。box3 为错误显示窗口,原理和作用同 box2。不一样的是,JavaScript 通过获取服务器返回的错误代码(详见后文),修改 box3 中的错误提示,达到提醒“账号密码错误”、“验证码错误”、“用户未登录”等效果。验证码通过腾讯云 API 实时生成,并将验证码结果存在后端中供登陆验证核对验证码。同时对验证码增加监听事件,局部刷新验证码,达到点击切换验证码的效果。

form 表单通过 AJAX 实现访问登陆接口进行登陆验证。

使用 type="checkbox"标签做为是否自动登陆的选择框,一起提交到后台Struts。

以下为部分 js 代码,实现一个网页多用,进行未登录提示、验证码错误提示、用户名密码错误提示、网页跳转,控制登陆窗口、成功窗口、报错窗口直接的显示以及隐藏:

```js } else if(msgcode.indexOf("zhuce") !== -1) { box1.style.display = 'none' box4.style.display = 'block' logintxt.innerjsp = '欢迎注册' } else if(msgcode.indexOf("tc") !== -1) {//退出登录 tc.onclick() } else{ //其他状态

//首先尝试进行自动登录
if(checkLogin()) { 
    jump("success") //自动登录成功
}
//自动登录失败,继续解析参数
if(msgcode === "uperror") { //如果为账号密码错误状态
    box1.style.display = 'none' 
    box3.style.display = 'block'
    errortext.innerjsp = "用户名或者密码错误!" 
    logintxt.innerjsp = '登陆失败' 
    leti = 6
    setInterval(function() { //倒计时
        i = i - 1
        errorreturntext.innerjsp = i + "秒后返回登陆" if(i === 0) {
            jump()
        }
    }, 1000)
} else if(msgcode === "icodeerror") { 
    //如果为游客状态,提示用户先登录 
    errorreturntext.innerjsp = 6 + "秒后前往登陆" 
    box1.style.display = 'none' 
    box3.style.display = 'block' 
    errortext.innerjsp = "验证码错误!" 
    logintxt.innerjsp = '操作失败' 
    leti = 6
    setInterval(function() {
        i = i - 1
        errorreturntext.innerjsp = i + "秒后返回登陆" if(i === 0) {
            jump()
        }
    }, 1000)
} elseif(msgcode === "youke") { //如果为游客状态,提示用户先登录 
    errorreturntext.innerjsp = 6 + "秒后前往登陆"

```

当登陆按钮被点击后,触发 form 表单通过 AJAX 提交 get 请求给 API 进行读取,包括 name、pwd 以及确验证码,实现获取相关信息,便于后续的操作。

js let captcha1 = new TencentCaptcha('2004603663', function () { tools.ajaxGet("http://localhost:8080/login", loginuser, function (res) { console.log(res) var jsonObject = JSON.parse(res.data) if (res.status === 0) { if (chck.value === "true") { setCookie("loginName", btoa(noTuiGe(jsonObject.username)), "7","/") //登陆用户名被编码保存,一方面防止中文存入 cookie 崩溃,一方面" 加密" setCookie(btoa(btoa(noTuiGe(jsonObject.username))), jiamitoken(jsonObject.username), "7","/") //登陆用户名被两次编码进行"加密",同时保存对应的加密 token } else { setCookie("loginName", btoa(noTuiGe(jsonObject.username)), "/") setCookie(btoa(btoa(noTuiGe(jsonObject.username))), jiamitoken(jsonObject.username), "/") } jump("success") } else jump("uperror") }) });

后端和数据库数据以及验证码进行验证。

```java @RequestMapping("login") public ResResult login(

@RequestParam String password,
@RequestParam String userName,

HttpServletResponseresponse,
HttpServletRequestrequest) { 
    ajax(response); 
    User user; asyncLogs.sendInfoAsync("用户登陆" + userName + "/" + password);         try{
        user = userRepository.findByUserName(userName);
        } catch(IncorrectResultSizeDataAccessException e) {                                     System.out.println(userName + "用户重复");                                              returnResResult.fail(ResultCode.USER_HAS_EXISTED);
        } catch(NullPointerException e) {
            System.out.println(userName + "用户不存在");                                         returnResResult.fail(ResultCode.USER_NOT_EXISTED);
        }
    if(user == null) {
        System.out.println(userName + "用户不存在");                                         returnResResult.fail(ResultCode.USER_NOT_EXISTED);
    }
    System.out.println(user); 
    if(!encryption.getSaltMD5(password, user.getSalt()).equals(user.getPassword()))              return ResResult.fail(ResultCode.USER_PWD_ERROR); 
JSONObject object = newJSONObject(true); 
object.put("status", "登陆成功"); 
object.put("username", user.getUserName()); 
object.put("name", user.getName()); 
user.setLoginDate(newDate()); 
user.setUa(request.getHeader("User-Agent")); 
asyncUserRepo.saveAsync(user); 
System.out.println("返回数据"); 
return ResResult.suc(object.toString());

}

```

登陆分为以下三种情况:

验证码错误 ,直接返回页面,并携带参数 msg=icerror 和 登陆前页面的 url(后文会提到),被前端 js 读取后,显示验证码错误提示信息,并 6 秒后返回登陆页面重试。

验证码正确 ,账户和密码也正确,登陆成功。同时,将登陆信息生成 token;将用户名直接写入 Cookie 便于前端读取显示到网页;将用户名、登陆时间、token 经 base64 加密后写入 Cookie,防止被修改,用于验证自动登录。最后携带参数 msg=success 和登陆前页面的 url 返回登陆页面,被前端 js 读取后,显示 XXX 用户登陆成功提示信息,并 6 秒后返回登陆前页面。如果登陆前页面不存在,则返回首页(index.jsp)。

其中,若选中自动登录,则 cookie 将会保存一个星期,以供自动登录。一周后,自动登录也将会随着 cookie 的消除而失效。

验证码正确 ,但账号和密码至少其一有误,登陆失败,携带参数 msg=icerror 和 登陆前页面的 url(后文会提到),被前端 js 读取后,显示账号密码错误提示信息,并 6 秒后返回登陆页面重试。

java if(realCode != null&& realCode.equals(userCode)) { if("zhangliang".equals(name) && "123".equals(pwd)) { session.setAttribute("isLogin", true); Cookie coo =newCookie("LOGINTOKEN", MyBase64.base64jiami("user=" + name+";time="+newSimpleDateFormat("yyyy/MM/dd-HH:mm:ss:SSS").format(newDate())+";自动登录:" + autologin)); Cookie coon = newCookie("ursename", name); if("true".equals(autologin)) { coon.setMaxAge(60 * 60 * 24 * 7); coo.setMaxAge(60 * 60 * 24 * 7);//设置cookie的生命周期 System.out.println("自动登录"); } response.addCookie(coo);//添加到response中 response.addCookie(coon); response.sendRedirect("loginPage.jsp?msg=success&from=" + fromUrl); } else{ response.sendRedirect("loginPage.jsp?msg=uperror&from=" + fromUrl); System.out.println("账号密码错误"); } } else{ System.out.println("验证码错误"); //request.getRequestDispatcher("/loginPage.jsp").forward(request, response); response.sendRedirect("loginPage.jsp?msg=icerror&from=" + fromUrl); }

其中,用户的敏感信息被加密为 token 后保存在浏览器中保证安全。同时后端还实现了注册接口,采用异步储存来加快速度。相关底层实现如下:

c++ @Async publicvoidsaveAsync(User user) { save(user); } publicvoidsave(User user) { System.out.println("异步储存"); userRepository.save(user); }

SpringData自带的Repository 接口:

CrudRepository 接口提供了最基本的对实体类的添删改查操作 -Tsave(Tentity);保存单个实体 -TfindOne(ID id);根据id查找实体 -voiddelete(ID/T/Iterable);根据Id删除实体,删除实体,批量删除 PagingAndSortingRepository提供了分页与排序功能 -<T,ID

extends Serializable>第一个参数传实体类,第二个参数传注解数据类型 -

Iterable findAll(Sortsort);排序 -Page findAll(Pageablepageable);

分页查询(含排序功能)JpaSpecificationExecutor提供了Specification(封装 JPA Criteria查询条件)的查询功能 - List findAll(Specification

spec); - Page findAll(Specification spec, Pageable pageable); -

List findAll(Specification spec, Sortsort);

这里值列出的是常用方法。

CrudRepository 中的 findAll() 方法要慎用。当数据库中数据量大,多线程脚本调用 findAll 方法,系统可能会宕机。

CrudRepository 中的 deletAll()方法要慎用。这是物理删除,现在企业一般采用逻辑删除。

PagingAndSortingRepository 和 JpaSpecificationExecutor 能满足大部分业务需求。

同时,在修改密码的接口上,提供的接口已无法满足使用,上面的方法虽然简单(不用写 SQL 语句),但它有最为致命的问题-----不支持复杂查询,其次是命名太长因此可以采用一下办法:

使用@Query 注解实现复杂查询,设置 nativeQuery=true 使查询支持原生sql

配合@Modifying 注解实现创建,修改,删除操作

SpringData 默认查询事件为只读事务,若要修改数据则需手动添加事务注解。

查询方法名一般以 find|read|get 开头,建议用 findfindByAccount : 通过 account 查询 Useraccount 是 User 的属性,拼接时首字母需大写。支持的关 键 词 有 很 多 比 如 Or,Between,isNull,Like,In 等 ,

findByEmailEndingWithAndCreatedDateLessThan: 查询在指定时间前注册,并以 xx 邮箱结尾的用户 And : 并且 EndingWith :以某某结尾 LessThan : 小于。注意 若有 User(用户表) Platform(用户平台表) 存在一对一的关系,且 User表中有 platformId 字段 SpringData 为了区分:findByPlatFormId 表示通过 platformId 字段查询 findByPlatForm_Id 表示通过 platform 实体类中 id 字段

查询表的设计,尽量做单表查询,以确保高并发场景减轻数据库的压力。例如这里提到的通过邮箱或者手机号模糊查询用户信息:

c++ @Query(value = "SELECT u FROM User u WHERE u.email LIKE %?1% OR u.phone LIKE %?2%") List<User> findByEmailAndIhpneLike(String email, String iphone);

2.8.3 构建软件首页

进入项目首页时,将会再次对用户登陆身份进行验证,同时向 API 请求个人信息:

c++ letuser = atob(getCookie("loginName")) //尝试获取用户名cookie letautoUser = { 'userName':user } let result; tools.ajaxGet("http://localhost:8080/getInfo", autoUser, function(res) { console.log(res) if(res.status === 0) { console.log("云端获取成功") registerJsonn(res.data) let name = getUserName(); userObj = getUser(name) if(userObj.type.indexOf("审核")!==-1) { document.getElementById("fun2").innerHTML="预约审核" document.getElementById("fun2e").innerHTML="Appointment review" document.getElementById("noEvent").innerHTML="审核拒绝" document.getElementById("noEvent1").style.display="inline-block"; document.getElementById("noEvent").classList.remove("kefu") document.getElementById("noEvent1").classList.remove("kefu") document.getElementById("noEvent").classList.add("shenheB") document.getElementById("noEvent1").classList.add("shenheB") } updateInfo() } })

AJAX 异步调用后端接口,获取到信息后写入到本地储存中以减少不必要的 API 请求,同时将信息动态显示在网页上,局部更新。

AJAX 即“AsynchronousJavascriptAndXML”(异步 JavaScript 和 XML),是指一种创建交互式网页应用的网页开发 技术。

AJAX 是一种浏览器通过 js 异步发起请求,局部更新页面的技术。

AJAX 请求的局部更新,浏览器地址栏不会发生变化 局部更新不会舍弃原来页面的内容

局部更新:就是页面上的某个组件 如 div 中的值进行了更新数据。

2.8.4 前后端 JSON 数据交互及标准 API 返回格式

JSON(JavaScript Object Notation) 是一种轻量级的数据交换格局。它基于ECMAScript的一个子集。 JSON选用完全独立于言语的文本格局,但是也使用了类似于C言语宗族的习气(包含C、C++、C#、Java、JavaScript、Perl、 Python等)。这些特性使json调试成为抱负的数据交换言语。 易于人阅览和编写,同时也易于机器解析和生成(一般用于提高网络传输速率)。

在学习 JavaScript 的过程中,我们接触了一种新的数据格式——JSON 数据格式。JSON 的全称是”JavaScript Object Notation”,意思是 JavaScript 对象表示法,它是一种基于文本,独立于语言的轻量级数据交换格式。 JSON 数据的书写格式是键(名称)/值对。 JSON 键值对是用来保存 JS 对象的一种方式,和 JS 对象的写法也大同小异,键/值对包括字段名称(在双引号中),后面写一个冒号,然后是值。 JSON 值可以是:字符串(在双引号中)、数组(在中括号中)、数字(整数或浮点数)、逻辑值(true 或 false)、对象(在大括号中)、 null。 JSON 结构有两种结构,就是对象和数组。通过这两种结构可以表示各种复杂的结构。 {"province": "Shanxi"} 可以理解为是一个包含 province 为 Shanxi 的对象, ["Shanxi","Shandong"]这是一个包含两个元素的数组 而 [{"province":"Shanxi"},{"province":"Shandong"}] 就表示包含两个对象的数组。当然了,也可以使用 {"province":["Shanxi","Shandong"]} 来简化上面的 JSON,这是一个拥有一个 name 数组的对象。

后端 JSON 的生成上,我们使用了 fastjson,实现对象快速转换 jsonobject fastjson 是阿里巴巴的开源 JSON 解析库,它可以解析 JSON 格式的字符串,支持将 Java Bean 序列化为 JSON 字符串,也可以从 JSON 字符串反序列化到JavaBean。

fastjson 的优点有以下:

  • 速度快

fastjson 相对其他 JSON 库的特点是快,从 2011 年 fastjson 发布 1.1.x 版本之后,其性能已经被其他 Java 实现的 JSON 库超越。

  • 使用广泛

fastjson 在宏网宏大量使用,在万台服务器上部署,fastjson 在业界被广泛接受。在 2012 年被中国读者大量使用为国产开源软件之一。

  • 测试完备

fastjson 有非常多的测试用例,在 1.2.11 版本中,测试用例超过 3321 个。每次发布进行回归测试,保证质量稳定。

  • 使用简单

fastjson 的 API 十分简洁。功能完备支持泛型,支持流处理超大文本,支持枚举,支持序列化和反序列化扩展。

前端的使用上,js 原生支持对 JSON 字符串和对象的相互转化。在 AJAX 异步请求 qpi 获取到返回信息后,提起其中的 JSON 数据并进一步提取,从而进一步操作页面。

c++ for(leti = keys.length-1; i >=0; i--) { let keyname = (keys[i].toString()) valus.push(json[keyname]) if(json[keyname]["type"] === "审核中") { div += '<li data-icon="false">\n' + ......................

后端还进一步对返回数据进行 API 标准化,使之成为标准的 JSON API 返回格式。

为了兼容多种类型的错误码,我们通过声明接口的方式解决,再由具体的业

务错误码类实现该接口。首先在包中添加 response 目录并新建返回码接口类。其次再定义一个业务错误码枚举类实现上述接口类。继续在其它 API 中添加包并新建 Result 返回包装类。其中提供了 SuccessfulResult 及 ErrorResult 方法用于接口调用成功或失败时的返回。

2.8.5 后端异步调用函数

Spring 异步线程池的接口类,其实质是 java.util.concurrent.Executor

Spring 已经实现的异常线程池:

  • SimpleAsyncTaskExecutor:不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程。
  • SyncTaskExecutor:这个类没有实现异步调用,只是一个同步操作。只适用于不需要多线程的地方
  • ConcurrentTaskExecutor:Executor 的适配类,不推荐使用。如果
  • ThreadPoolTaskExecutor 不满足要求时,才用考虑使用这个类

SimpleThreadPoolTaskExecutor:是 Quartz 的 SimpleThreadPool 的类。线程池同时被 quartz 和非 quartz 使用,才需要使用此类

ThreadPoolTaskExecutor : 最 常 使 用 , 推 荐 。 其 实 质 是 对 java.util.concurrent.ThreadPoolExecutor 的包装 Spring 对过@Async 定义异步任务

异步的方法有 3 种。最简单的异步调用,返回值为 void。带参数的异步调用,异步方法可以传入参数。异常调用返回 Future。

对于不依赖返回数据的相关数据库操作,我们都都将其改为异步多线程函数,并发运行,极大提升相应速度:

```java packagecom.classint.xiaoyue.Async;

importcom.classint.xiaoyue.data.event.Event; importcom.classint.xiaoyue.data.event.EventRepository; importorg.springframework.beans.factory.annotation.Autowired; importorg.springframework.scheduling.annotation.Async; importorg.springframework.stereotype.Component;

@Component public class AsyncEventRepo { @Autowired private EventRepository eventRepository; @Async public void saveEventAsync(Event eve) { save(eve); } public void save(Event eve) { eventRepository.save(eve); } @Async public void deleteAsync(long id) { delete(id); } public void delete(long id) { eventRepository.deleteEventById(id); } } ```

2.8.6 非对称加密算法

为保护用户数据,在后端密码的保存上,我们采用了基于矩阵的加盐加密算法:

利用待加密信息配合成熟算法通过一定规则构造出一个新的加密矩阵,将待加密信息经过此加密矩阵转换为密文数据,将密文数据与原始数据传输到密文接收方,密文接收方利用接收到的明文信息采用相同规则构造出解密矩阵,将密文数据解密,再将解密信息与明文信息比较,如对比一致,则采用该信息。本发明所述的方法不但可以保证信息传输的安全性,而且能有效检测出信息是否被篡改。

c++ privatestaticString md5Hex(String str) { try { MessageDigest md = MessageDigest.getInstance("MD5"); byte[] digest = md.digest(str.getBytes()); returnnewString(newHex().encode(digest)); } catch(Exception e) { e.printStackTrace(); System.out.println(e); return""; } }

所有密码数据均被加密后保存在数据库中,无法逆向进行破解。

即使在前端,我们也运用了不可逆非对称加盐加密算法来进行保密。一般信息也被层层编码储存在客户端浏览器中。

不可逆算法指加密过程中不需要使用密钥,输入明文后由系统直接经过加密算法处理成密文,这种加密后的数据是无法被解密的,只有重新输入明文,并再次经过同样不可逆的加密算法处理,得到相同的加密密文并被系统重新识别后,才能真正解密。这里使用不可逆加密算法 MD5。MD5(Message-DigestAlgorithm)是计算机安全领域广泛使用的散列函数(又称哈希算法、摘要算法)。主要用来确保消息的完整性和一致性。常见的应用场景有:密码保护、下载文件校验等。当攻击者知道算法是 md5 后,可以将实现准备好的常见明文密码的 md5 值来进行匹配暴力破解所以要进行"加盐"处理在密码的特定位置插入特定字符串后,再对修改的字符串进行 md5 运算同样的密码,当“盐”值不一样的时候,md5 的值差异非常大通过密码加盐,可以防止最初级的暴力破解,如果攻击者事先不知道“盐”值,破解的难度就会非常大。

```c++ let salt = "HELLOhtmlImZHANGliang@5201314,<.>/?;:'[china]" function strToBinary(str) { let result = []; let list = str.split(""); for (let i = 0; i < list.length; i++) { if (i !== 0) { result.push((salt[i % (salt.length - 1)]).charCodeAt(0).toString(2)); //加入转换成二进制的盐 } let item = list[i]; let binaryStr = item.charCodeAt(0).toString(3); //原字符转为 asc2 后转换为三进制 result.push(binaryStr); } return result.join("110"); }

function jiamipassword(text) { return md5(md5(strToBinary(text))) //md5 加密两次 } function jiamitoken(text) { return md5(text + "这是个验证用户的 token") } ```

2.8.7 项目云端部署及网络内容分发加速

项目搭建在腾讯云轻量云服务器上,并采用腾讯云网络分发加速。

腾讯云轻量应用服务器(Lighthouse)是新一代开箱即用、面向轻量业务场景的云服务器产品,助力中小企业和开发者便捷高效的在云端构建小型网站、博客、论坛、云盘以及各类开发测试和学习环境,相比传统云服务器更加简单易用,并通过基础云资源与热门开源软件的融合打包实现应用的一站式交付。

内容分发网络(ContentDeliveryNetwork,CDN),是在现有 Internet 中增加的一层新的网络架构,由遍布全球的高性能加速节点构成。这些高性能的服务节点都会按照一定的缓存策略存储您的业务内容,当您的用户向您的某一业务内容发起请求时,请求会被调度至最接近用户的服务节点,直接由服务节点快速响应,有效降低用户访问延迟,提升可用性。

CDN 有效地解决了目前互联网业务中网络层面的以下问题:

  • 用户与业务服务器地域间物理距离较远,需要进行多次网络转发,传输延时较高且不稳定。
  • 用户使用运营商与业务服务器所在运营商不同,请求需要运营商之间进行互联转发。
  • 业务服务器网络带宽、处理能力有限,当接收到海量用户请求时,会导致响应速度降低、可用性降低。

具体部署为:

首先需要将 Spring Boot 项目打包为 War 包:

war 是一个可以直接运行的 Web 模块,通常用于网站,打成包部署到容器中。

以 Tomcat 来说,将 war 包放置在其\webapps\目录下,然后启动 Tomcat,这个包就会自动解压,就相当于发布了。

war 包是 Sun 提出的一种 Web 应用程序格式,与 jar 类似,是很多文件的压缩包。war 包中的文件按照一定目录结构来组织。根据其根目录下包含有 HTML 和 JSP 文件,或者包含有这两种文件的目录,另外还有 WEB-INF 目录。通常在 WEB-INF 目录下含有一个 web.xml 文件和一个 classes 目录,web.xml 是这个应用的配置文件,而 classes 目录下则包含编译好的 servlet 类和 JSP,或者 servlet 所依赖的其他类(如 JavaBean)。通常这些所依赖的类也可以打包成 jar 包放在 WEB-INF 下的 lib 目录下。

简单来说,war 包是 JavaWeb 程序打的包,war 包里面包括写的代码编译成的 class 文件,依赖的包,配置文件,所有的网站页面,包括 HTML,JSP 等等。一个 war 包可以理解为是一个 Web 项目,里面是项目的所有东西。

SpringBoot 默认达成 jar 包,使用 SpringBoot 构想 Web 应用,默认使用内

置的 Tomcat。但考虑到项目需要集群部署或者进行优化时,就需要打成 war 包部署到外部的 Tomcat 服务器中。修改 pom.xml 文件将默认的 jar 方式改为 war, maven 中排除 spring-boot-starter-web 中的 Tomcat,添加打包依赖spring-boot-starter-tomcat。

继承org.springframework.boot.web.servlet.support.SpringBootServletInitializer,实现 configure 方法。使用 mvn 命令行打包,运行:mvn clean 和 mvn install。

最后将 war 包部署至 Tomcat 服务器。

参考文献

  • J2EE平台下快速WEB开发的研究与应用(武汉理工大学·李涛)
  • J2EE平台下快速WEB开发的研究与应用(武汉理工大学·李涛)
  • 基于Web下的远程教学系统的设计与实现(吉林大学·王骥)
  • J2EE平台下快速WEB开发的研究与应用(武汉理工大学·李涛)
  • 基于Java的Web应用设计与开发(西南石油学院·龚华)
  • 计算机基础系列课程网络CAI教学的研究与实践——现代远程教学系统基于Web的辅助教学平台(成都理工大学·袁爱新)
  • 基于PHP+MySQL的交互学习系统的设计与实现(吉林大学·刘博)
  • 一种基于UNIX的互联网站搭建方案的关键技术与WEB2.0(中国水利水电科学研究院·罗皓)
  • 高职礼仪课程教学平台的设计与实现(北京工业大学·杨菲)
  • 一种基于UNIX的互联网站搭建方案的关键技术与WEB2.0(中国水利水电科学研究院·罗皓)
  • 基于J2EE的远程教育平台的开发与实现(吉林大学·葛瑛)
  • J2EE平台下快速WEB开发的研究与应用(武汉理工大学·李涛)
  • 基于ASP.NET高校辅助教学视频点播系统的设计与实现(电子科技大学·吴耀东)
  • 远程教育教学支持系统的研究与实现(西北大学·詹涛)
  • 基于ASP.NET高校辅助教学视频点播系统的设计与实现(电子科技大学·吴耀东)

本文内容包括但不限于文字、数据、图表及超链接等)均来源于该信息及资料的相关主题。发布者:代码海岸 ,原文地址:https://m.bishedaima.com/yuanma/35880.html

相关推荐

发表回复

登录后才能评论