基于Java+MySQL实现(Web)可扩展的程序在线评测系统

发布于:2025-07-14 ⋅ 阅读:(33) ⋅ 点赞:(0)

可扩展的程序在线评测系统研究与设计

摘要

程序语言课程是计算机相关专业的核心教学内容之一,要提高程序语言设计能力必须通过大量的实践练习与交流。在传统的学习过程中,往往通过人工方式对程序的源代码进行评测,遇到问题,不易于学习交流。本系统目的便是构建一个基于B/S的,能够方便扩展系统功能的在线评测系统。同时在基础框架之上扩展出几个模块,以增强程序学习者之间的学习交流,简化程序的评测过程,提高程序学习的效率。

本系统结合当前优秀的开源框架,构建一个相对易于扩展的web在线评测系统。系统总体框架实现和技术主要为Spring,Hibernate,MySQL,Lucene等主流技术。采用Spring MVC来控制主要的业务逻辑,Hibernate实现数据的ORM映射,MySQL实现系统数据的存储。前端使用JSP进行显示,用jQuery进行行为的控制,前后端之间Ajax传输JSON格式数据。

本论文阐释了如何可扩展系统,jQuery插件开发,Ajax服务的构建,易用的DAO模块设计,基于角色的权限控制等适应于本系统的基础应用。这些基础应用的设计最大好处就是能加快系统开发,提高系统的扩展性及灵活性。最后在这些技术的基础之上扩展了三个模块,分别是,基础管理模块,在线评测模块和论坛模块。

关键字:可扩展 SpringMVC Hibernate Ajax RBAC

一、前  言

1.1 研究目的

程序语言课程是计算机相关专业的核心教学内容之一,要提高程序语言设计能力必须通过大量的实践练习和交流,在传统的教学过程中,往往通过人工方式对程序的源代码进行评测,这种人工评测方式存在效率不高,容易误判并且缺乏一个可以共套交流的平台。本毕业设计课题提出一个基于B / S模式的可扩展在线程序语言评测系统,对系统的设计和主要的实现技术进行分析和探讨。

程序设计类课程,具有实践性强的特点。它不但要求学生掌握基础的理论知识,更重要的是要求学生不断提高自身的编程实践能力。为了方便学生进行学习和联系,提高学生动手编程的实践能力,开发一个自动化,智能化的评测系统成为需要。随着现代信息化的发展,软硬件的不断升级,使得开发这样一个评测系统成为可能。

Online Judge System(以后简称OJS),则是指一个在线的裁判系统,它可对程序源代码在线进行编译和执行,并通过预先设计的测试数据来检验程序源代码的正确性。

1.2 研究意义

EPOJS(Extensible Program Online Judge System)是一个学习的好帮手,开发这样一个系统是具有一定的实际意义的,下面分点阐述。

1.2.1 在线学习程序设计语言

无论是什么样的用户,只要想学习C程序设计语言,就可以在本系统上面实现在线编辑程序,执行程序并查看执行结果,通过测试的情况反馈使用者的学习情况。

1.2.2 学员间交流

现有的OJ系统,没有论坛系统功能,学员之间不利于沟通。因此,本系统也扩展了论坛系统功能,便于用户在论坛上面进行沟通。

1.2.3 基于角色的权限控制

使用RBAC来进行权限的管理与控制,使得权限分配更加集中和灵活。设计这样的权限控制模型,主要是利于扩展。对于最原始的系统来说,也许只有一种角色类型,就是普通用户。但是对于特定的模块,可能就需要新的角色,如在在线评测模块中,为了对教学进行支持,定义了学生和教师的角色。

1.3 国内外研究概况

在线测评系统发展到现在已经比较常见了,无论是其体系结构还是运行模式都已经相对成熟了。现行的许多大学校园中,都拥有自己的在线测评系统,但大都具有类似的工作模式,主要体现在以下几个方面:

第一方面,由于各大高校越来越重视ACM竞赛,很多高校变进行了各自的程序语言在线测评系统的开发和应用。主要目的就是要提供一个平台供学生们提高程序设计水平。

第二方面,许多的程序语言在线测评系统并没有大胆的和教学结合在一起(当然也有,比如北京大学在线测评系统),应用面不够广。ACM/ICPC 带动了演算算法程序设计的风气,世界上许多大专院校的咨询系所,仿照 ACM/ICPC 的比赛模式,纷纷自行开发出即时线上比赛系統,能夠自动批改、评分、计时、统计。学生不必齐聚一堂,就可以相互切磋程式设计技巧。比赛结束之后,便将比赛题目列为题库,并开放线上批改程序的功能,供学生赛后练习。这样的系统大家一般称之为「Online Judge System 」,或直接称为「 Online Judge (OJ) 」。下面就国内和国外的OJ系统进行简介。

1.3.1 国内研究现状

1.浙江大学 Online Judge(ZOJ)
国内最早也是最有名气的OJ,有很多高手在上面做题。特点是数据比较刁钻,经常会有想不到的边界数据,很能考验思维的全面性。
2.北京大学 Online Judge(POJ)
建立较晚,但题目加得很快,现在题数和ZOJ不相上下,特点是举行在线比赛比较多,数据比ZOJ上的要弱,有时候同样的题同样的程序,在ZOJ上WA,在POJ上就能AC。

1.3.2 国外研究现状

1.西班牙Valladolid大学 Online Judge(UVA)
世界上最大最有名的OJ,题目巨多而且巨杂,数据也很刁钻,全世界的顶尖高手都在上面。据说如果你能在UVA上AC一千道题以上,就尽管向IBM、微软什么的发简历吧,绝对不会让你失望的。
2.俄罗斯Ural立大学 Online Judge(URAL)
也是一个老牌的OJ,题目不多,但题题经典。

1.4 项目开发必要性

本系统所面向的用户是广大的学生、教师和普通用户,为学生提供良好的学习平台,为老师提供一个功能完备的练习、测试及相关统计信息。

本系统是对可扩展系统的研究和设计,扩展的EPOJS为计算机相关专业的学生提供一个很好的学习和交流平台。其根本需求是提高学生学习程序设计的兴趣和学习的质量,提高教师的管理效能。对于传统的在线测评系统,并没有结合到教学中去,也没有相应的交流平台,不利于技术之间的交流。同时,所提供的统计信息不够丰富,针对性不强。

本系统旨在解决部分的需求,研究并设计一个可扩展的框架,对多种程序设计语言进行可扩展支持。而本次毕业设计将重点扩展Java语言的在线测评模块和论坛模块。论坛系统用于学生,教师以及其他使用本系统的用户进行技术交流。

1.5 系统需求

1.5.1 外部行为需求

本系统涉及到的用户角色有普通用户,学生,教师,超级管理员,超级管理员是具有所有权限的,也就是可以使用系统的所有功能。系统要求对于不同的用户角色,可以进行权限的分配。

从外部来看,本系统主要提供以下几个功能:

  • ·用户可以选题进行解答,在线进行评测,获得结果,查看相关的统计信息。
  • ·学生可以参加某个教师的课程,完成老师布置的作业,查询作业情况。
  • ·教师可以创建课程和题目,布置作业,查看学生的作业情况,统计信息。
  • ·用户可以使用内部论坛系统。

下面我们来看一看可扩展的在线测评系统顶层用例图:

图 1-5-1 系统顶层用例图

上图描述了系统的主要功能,更进一步地,各个角色的功能是由权限来控制的,所以对于角色的功能,在这里就不再赘述了。

1.5.2 内部特性需求

内部特性需求主要是作为开发者角度去看这个问题,为了得到更好的扩展性或是其他性能方面的要求,定义了以下需求:

  • 系统必须是能够比较方便的进行功能模块的扩展,如扩展在线聊天模块。
  • 必须能够实现基于角色的权限控制模式。
  • 系统可以运行在Windows和Linux上面,即能够跨平台。

1.6 系统开发环境

1.6.1 客户端软件

操作系统: windows,Linux Desktop

浏览器:   Chrome(推荐),Firefox, IE9及以上版本

1.6.2 服务端软件

  • 操作系统:   windows server, Linux server
  • WEB服务器:Tomcat7.0
  • 数据库:     MySQL5.0
  • JDK/JRE:    jdk1.7
  • C编译器:  g3.4.5

1.6.3 开发工具

  • 开发平台: Eclipse3.7
  • 构建工具: Ant
  • 版本控制: GitHub-分布式代码管理
  • UML建模:Astah Community 6.6
  • 测试框架: JUnit4
  • 应用框架: Spring,Hibenate,jQuery

二、相关技术概述

在本次项目中,所使用的均是主流的开源技术,易于获取,网上的学习资源丰富,关于该类开源框架的论坛和社区也不少,并且在企业中,这些技术都应用得非常广泛,这也是本项目选用这些技术的根本原因。下面就来简单介绍一下这些开源的技术框架。

2.1 Spring

作为在Java领域最为成功的开源软件之一,Spring在Java EE 开发中,使用者众多。2002年,伴随着Rod Johnson的《Expert One-on-One J2EE Design and Development》一书出版而发布的Spring框架(也就是当年的interface21),经过几年的发展,现在已经逐渐成熟起来,Spring带来的崭新开发理念,也早已伴随着它的广泛应用而“飞入寻常百姓家”。

2.1.1 简介

简化Java 企业应用开发是Spring的目标,可以这么说,Spring为开发者提供了一一站式的轻量级应用开发框架(或者说平台)。作为平台,Spring抽象了我们在许多应用中遇到的共性问题;同时,作为一个轻量级的应用开发框架,Spring有其自身的特点,通过这些特点,Spring充分体现了它的设计理念:在Java EE的应用开发中,支持POJO(Plain Old Java Objects, 简单的Java对象)和使用JavaBean的开发方式,使应用米娜想接口开发,充分支持OO(面向对象)的设计方法。

图 2-1 Spring总体架构图

简单来说,Spring是一个轻量级的()和面向切面()的框架。

1.轻量——从大小与开销两方面而言Spring都是轻量的。完整的Spring可以在一个大小只有1MB多的JAR文件里发布。并且Spring所需的处理开销也是微不足道的。此外,Spring是非侵入式的:地,Spring应用中的不依赖于Spring的特定类。

2.控制反转——Spring通过一种称作控制反转(IoC)的技术促进了松耦合。当应用了IoC,一个依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。你可以认为IoC与JNDI相反——不是从容器中查找依赖,而是容器在对象初始化时不等对象请求就主动将依赖传递给它。

3.面向切面——Spring提供了的丰富支持,允许通过分离应用的业务与级(例如审计(auditing)和(transaction)管理)进行的开发。只实现它们应该做的——完成业务逻辑——仅此而已。它们并不负责(甚至是意识)其它的系统级关注点,例如日志或支持。

4.容器——Spring包含并管理的配置和生命周期,在这个意义上它是一种容器,你可以配置你的每个bean如何被创建——基于一个可配置(prototype),你的bean可以创建一个单独的实例或者每次需要时都生成一个新的实例——以及它们是如何相互关联的。然而,Spring不应该被混同于传统的重量级的EJB容器,它们经常是庞大与笨重的,难以使用。

5.框架——Spring可以将简单的配置、组合成为复杂的应用。在Spring中,被声明式地组合,典型地是在一个XML文件里。Spring也提供了很多基础功能(、持久化框架集成等等),将应用逻辑的开发留给了你。

所有Spring的这些特征使你能够编写更干净、更可管理、并且更易于测试的代码。它们也为Spring中的各种模块提供了基础支持。

2.1.2 应用场景

通过前面的介绍,我们了解到Spring是一个轻量级框架。在Spring这个一站式的应用平台或框架中,其中各个模块除了依赖IoC和AOP之外,相互之间并没有很强的耦合性,Spring的最终目标是简化应用开发的编程模型。一方面,我们可以把Spring作为一个整体来使用,另一方面,也可以各取所需,把Spring的各个模块拿出来独立使用,这取决于我盟对Spring提供服务的具体需求。正因为如此,才大大地拓宽了Spring的应用场景。

在JavaEE企业应用的开发中,我们了解了使用Spring最为基本的应用场景,就是SSH架构来完成企业应用开发,取代传统的EJB开发模式,在SSH中,Struts作为Web UI, Spring作为中间件平台,Hibernate作为数据持久化工具(ORM-Object-Relation Mapping)来操作关系数据库。

在Spring的实现中,它的核心实现,如IoC的容器的实现,是直接依赖于JVM虚拟机的,也就是说在Java环境中,Spring IoC容器是可以独立使用的。对于Spring而言,如果要在.NET环境中使用,Spring团队也提供了Spring .NET的实现;在Android平台中也有支持。从这些应用场景来看,Spring设计时的轻量级特性,以及推崇POJO开发,所以使用起来非常灵活。

2.1.3 价值

在Spring的应用中,Spring团队为我们列举了Spring的核心价值,非常值得参考:

  • Spring是一个非侵入性(Non-invasive)框架,其目标是是应用程序代码对框架的依赖最小化,应用代码可以在没有Spring或者其它容器上面进行。
  • Spring提供了一个一致的编程模型,使应用可以直接使用POJO进行开发,从而与运行环境(如应用服务器)隔离开来。
  • Spring推动应用的设计风格向面向对象和面向接口编程转变,提高了代码的重用性和可测试性。
  • Spring改进了体系结构的选择,虽然作为应用平台,Spring可以帮助我们选择不同的技术实现,比如从Hibernate切换大其他ORM工具(如MyBatis),从Struts切换到Spring MVC,尽管我们通常不会这么做,但是我盟在技术方案的选择使用Spring作为应用平台,Spring至少为我们提供而来这种可能性,从而降低了平台锁定的风险。

2.2 Hibernate

Hibernate是当前主流的开源ORM(Object Relation Mapping-对象关系映射)框架。它对JDBC进行了非常轻量级的对象封装,使得Java可以随心所欲的使用对象思维来操纵。 Hibernate可以应用在任何使用JDBC的场合,既可以在Java的客户端使用,也可以在Servlet/JSP的Web应用中使用。

Hibernate不仅负责从Java类映射到数据库表(和从Java数据类型到SQL数据类型),但也提供了数据查询和检索。我们不用手工去编写繁琐的SQL语句和JDBC,Hibernate可以使用HQL来帮助我们完成这些更为底层的实现,从而快速开发。通过隐藏底层实现,作为一个开发人员,你可以使用纯面向对象的方式去思考问题,提高对问题空间的理解和抽象,Hibernate允许你这么做。同时,Hibernate更可以跨数据库平台,使用统一的HQL基本上能完成大部分的查询工作,Hibernate同样支持分页查询,我们知道,不同数据库的分页查询方式有所不同,而Hibernate屏蔽了这些细节,从而允许你使用统一的方式进行分页查询。

2.3 jQuery

jQuery是一个前端的轻量级JvaScript库,它以“Write Less, Do More”为设计理念。是继prototype之后又一个优秀的JavaScript。它兼容CSS3,还兼容各种。 jQuery使用户能更方便地处理HTML documents、events、实现动画效果,并且方便地为网站提供AJAX交互。jQuery还有一个比较大的优势是,它的文档说明很全,而且各种应用也说得很详细,同时还有许多成熟的可供选择。jQuery能够使用户的html页面保持代码和html内容分离,也就是说,不用再在html里面插入一堆js来调用命令了,只需定义id即可。

更为重要的是,jQuery是开源免费的,对于热爱javascript的开发人员来说,jQuery的源代码具有非常好的参考价值。简单的说,jQuery具有以下一些特点:

  • 动态特效
  • AJAX
  • 通过插件来扩展
  • 方便的工具 - 例如浏览器版本判断
  • 渐进增强
  • 链式调用
  • 多浏览器支持

2.4 Ajax

相信做过web开发的人员都不陌生,Ajax 就是“Asynronous JavaScript and XML”(异步JavaScript和XML)。在过去还是主要以XML作为数据传输格式,但是XML太过庞大和复杂,逐渐被JSON数据格式取代。现在大多数Ajax都是以JSON作为数据传输的格式。

Ajax不是一种新的语言,而是一种用于创建更好更快以及交互性更强的 Web 应用程序的技术。通过 Ajax,可使用的对象来直接与服务器进行通信。通过这个对象,可在不重载页面的情况与服务器交换数据。Ajax 在浏览器与 Web 服务器之间使用异步数据传输(HTTP 请求),这样就可使网页从服务器请求少量的信息,而不是整个页面。

如前面提到的,Ajax可以大大提高用户与web应用程序的交互速度,对于之更心一小部分显示文档的需求,Ajax技术缩短了文档传送和文档呈现所需要的时间。Ajax能够极大地改善用Web用户的体验。

为了将Ajax单独抽象出来,提高系统的模块化程度,提供系统的可维护性和扩展性,也是为了最小化满足系统的Ajax需求,设计了适合于本系统的 Ajax服务。本服务是以模块作为服务划分的,要访问本服务,就必须指定模块和对应的服务名称。系统在初始化的时候会注册相应的模块及其有关Ajax的服务。

2.5 WEB中的RBAC概述

在web系统中,权限的控制有其特性。在web系统中,我们在web页面中进行的每一个动作对应于浏览器做出的反应都是一一对应的,如果结合后端的服务,那么用户在前端所做的动作,每次发出的请求都是与后端服务意义对应的。而请求本质上就是一个URL的访问。由于在一个单独的web系统中,URL是唯一的,因此,设计web中的权限模型方案就可以以URL为突破口。本系统所使用的权限模型方案正是基于站内URL唯一的特性设计的。以某一个URL作为一个权限的代表。在web中进行权限的控制有两个方面的内容,一是权限的显示问题,另一个是权限的拦截问题。所谓权限的显示,就是说在web页面中,系统会根据用户的角色来判定你是否具备某个权限或不具备某个权限,如果具备,就会提供一个操作界面给你,如果不存在就不会提供这样的在操作界面。所谓权限的拦截,就是说,可能在web页面中没有显示,但是这个用户直接在浏览器上面输入一个自己没有权限访问的URL,那么后台服务器应该能够对这个请求进行拦截,对没有权限的操作进行阻塞。

图 2-5-1 web权限控制活动图

权限控制请求流程描述:

  1. 用户使用URL发起请求。
  2. 服务端使用CheckPrivilegeInterceptor拦截到该请求,解析该URL,分析出权限,如果有权限访问就正常返回期望页面并跳到(3),如果没有权限就直接跳到(4)。
  3. 根据用户的角色信息,拿到用户的所有权限,对于链接,使用自定义的a标签,实现对权限的控制,一旦没有操作对应URL的权限就不显示该标签,最后返回一个正常页面。
  4. 结束。

2.6 分页概述

在实际应用中,数据量通常很大很大,不可能一次性给用户显示全部的数据,那么我们就可以控制每次只给用户显示一部分的数据,也就是我们常说的分页功能,分页包括两种方式,一种是基于数据库分页的查询,另一种是基于内存中的数据分页。前者使用非常广泛,后者通常是一些小数据量的分页,因为需要把数据都load到内存中去。后面会给这两种方式进行讲解。

2.6.1 分页前端显示

在前端,在许多场景中都需要对数据进行分页,在网上也有很多javascript分页的库。有些是纯javascript的,有些是基于jQuery的,有免费的也有收费的。但是,网上这些分页工具大都功能太国语强大,在本系统中,根本不需要那么多的功能,在本系统中只需要进行简单的分页就行,甚至不需要什么Ajax分页,基于这个目的,于是在jQuery的基础上设计了一个分页的插件(具体实现可以在GitHub上面找到:https://github.com/cianfree/cdesign/tree/master/pagingplugin)。

目录结构:

图 2-6-1 分页插件目录结构

先从调用形式说起:

<html>
<head>
<script type="text/javascript" src="jquery.js"/>
<!-- 引入自定义的分页插件 -->
<link href=" css/mypaging.css" type="text/css" rel="stylesheet"/>
<script type="text/javascript" src=" jquery.mypaging.js"/>
<script type="text/javascript">
	$(function(){
		jQuery("#pagingDiv").myPaging({
			currentPage: 5, // 当前页
			pageCount: 30, // 总页数
			pageSize: 10, // 每页显示条数
			totalRecord: 300, // 总记录数目
			showSize: 10, // 分页栏显示的页码个数
			callback: function(currentPage, pageSize){
				// 回调函数,当点击页码的时候就会执行这个函数
			}
		});
	})
</script>
</head>
<body>
<div id="pagingDiv"></div>
</body>
</html>

效果图:

图 2-6-2 分页插件演示

2.6.2 基于数据库查询的分页

基于数据库的查询的分页在实际应用中很广泛,系统通常都会有很多的数据,所以每次只能给用户显示一小部分的数据。

这种方式的查询需要依赖于数据库底层的实现,像现在的数据库,基本上都不同,在mysql中我们使用limit进行分页查询;在Oracle中的分页查询比较复杂,形式有好几种,但是都涉及到多个子查询;SQLServer中分页查询可以使用SELECT TOP 和 NOT IN组合实现。

在本系统中,因为使用了Hibernate开源框架,Hibernate具备跨数据库的能力,使用的HQL提供了统一的分页查询方式,屏蔽掉了数据库的底层差异。在后面的详细设计中,也会有对查询方式的分页进一步设计成更好使用的查询帮助类QueryHelper,并且使用的是Hibernate的实现。

2.6.3 基于内存的数据分页

基于内存的查询方式用得比较少,但是在本项目中也有使用到,因此也专门设计了一个内存查询的工具类MemoryPaging,允许用户定义比较器。关于分页查询在后面的详细设计中还会提到,这里就不多做说明了。

三、基础架构设计与实现

3.1 可扩展架构设计与实现

3.1.1 可扩展架构设计

1.系统流程

在Java web中,WEB具有一定的规范,应用服务器会先读取部署描述符web.xml进行系统的部署,突破口就在这里。

可扩展的架构设计方案如下:

  1. 应用服务器读取web.xml,初始化监听器。
  2. 应用服务器通过反射调用监听器的初始化方法。
  3. 在监听器的初始化方法中,读取项目的模块配置文件,修改与Spring相关的配置并保存。
  4. 启动Spring在web环境中的容器,此时读取到的是最新的配置文件。
  5. 根据模块配置文件,刷新系统权限数据,并更新数据库。
  6. 执行每个模块监听器的初始化方法,初始化每个模块。
  7. 启动完毕。

下面是启动流程顺序图:

图 3-1-1 系统启动流程图

2.扩展需要的资源

上面介绍了系统的启动流程,接下来介绍系统是如何进行扩展的。在进行扩展操作之前,必须先准备好扩展模块的资源,要扩展模块的资源包含下面几个方面:

  • 模块编译好的class文件
  • 页面显示的资源文件[jsp, js, css, images等]
  • 项目配置文件[spring,hibernate,privileges]

关于spring, hibernate, privilege配置文件的格式如下(不作强制要求):

建议命名格式:

  • Spring: applicationContext-[模块名称].xml
  • Hibernate: hibernate.cfg-[模块名称].xml
  • Privileges: privilege-[模块名称].xml

1).applicationContext-[模块名称].xml文件

该文件和普通的Spring配置文件一致,主要配置相关的bean以及搜索的包路径。可以作为Spring的配置文件导入到框架的Spring核心配置文件中,请看下面一个Spring文件的示例:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xmlns:p="http://www.springframework.org/schema/p"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xsi:schemaLocation="
	http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
	http://www.springframework.org/schema/context
	http://www.springframework.org/schema/context/spring-context-3.0.xsd
	http://www.springframework.org/schema/mvc
	http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">

	<!-- 搜索的包 -->
	<context:component-scan base-package="edu.zhku"/>
	
	<!-- 导入hibernate配置文件 -->
	<import resource="frame/applicationContext-hibernate.xml"/>
	
	<!-- 导入web层相关的配置 -->
	<import resource="frame/applicationContext-web.xml"/>
	
	<mvc:annotation-driven/>
	<!-- 启用Spring注解 -->	
	<context:annotation-config/>
</beans>

2).hibernate.cfg-[模块名称].xml

该配置文件主要是配置相关的模块实体类映射文件,样例文件如下:

<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
	<session-factory>
		<!-- 配置映射文件 -->
		<mapping resource="edu/zhku/fr/domain/User.hbm.xml" />
	</session-factory>
</hibernate-configuration>

3).privilege-[模块名称].xml

该文件非常重要,它描述了在本模块中的所有功能,是实现权限控制的必要条件,每一个功能亦称之为一个权限。

<?xml version="1.0" encoding="UTF-8"?>
<privs>
	<!-- 一个模块作为一个权限组 -->
	<top-privilege name="系统管理" action="systemManage">
		<!-- 第二级别显示在下拉菜单中 -->
		<priv name="用户管理" action="user/userList">
			<!-- 第三级别的是在页面中出现的,一般只有三级 -->
			<sub-priv name="用户列表" action="user/userList" />
           // 其他权限配置
		</priv>
	</top-privilege>
</privs>

3.开始扩展

准备好了上面的步骤之后,接下来就是把该模块添加到原有的系统中去了,只要做以下几个步骤即可。

1).修改module-config.xml

示例(配置基础应用模块):

<?xml version="1.0" encoding="UTF-8"?>
<!--说明
	module: 表示一个模块
	module[name]: 模块名称,这个是唯一的
	module[deploy]: 表示是否要在安装系统的时候安装本模块
	module[depends]: 表示本模块依赖于其他的哪些模块,分隔符可以是 ';' ',' ':' '-' ' ' 
	module>>hibernate-cfg: 表示该模块对应的Hibernate配置文件路径,只能接收一个文件,
因为其他配置都可以通过import方式导入
	module>>spring-cfg: 表示该模块对应的 Spring配置文件路径,只能接收一个文件,因为其他
配置都可以通过import方式导入
	module>>privilege-cfg: 表示该模块对应的权限数据配置文件,只能接收一个文件,因为其他
配置都可以通过import方式导入
	module>>listeners:	 表示该模块下的监听器
			属性:
				class: 	代表该监听器对应的类,该类必须要有一个public的空构造函数
				deploy: 表示是否部署,即是否在web.xml中添加了该监听器,true表示添加了,那
么在模块初始化的时候就不会在执行该监听器了,否则就会反射创建对象,
并执行contextInitialized方法来初始化本模块所需要的数据。
   module>>roles: 模块系统固有角色
 -->
<modules>
	<module name="base" deploy="true">
       <!-- 对应模块的配置文件位置 -->
		<hibernate-cfg>classpath:base/hibernate.cfg-base.xml</hibernate-cfg>
		<spring-cfg>classpath:base/applicationContext-base.xml</spring-cfg>
		<privilege-cfg>classpath:base/privileges-base.xml</privilege-cfg>
       <!-- 对应模块启动监听器,可以没有,看模块的需求  -->
		<listeners>
			<listener class="edu.zhku.base.listener.BaseContextLoaderListener" 
deploy="false"/>
		</listeners>
       <roles>
			<role name="学生用户" description="POJ特有角色,作为学生拥有教学相关的任务"/>
		</roles>
	</module>
</modules>

2).copy资源

将classes复制到主系统的classes下面,同时把配置文件也复制在下面,目录结构与classes目录保持一致。最后把页面相关资源[jsp, html, js, css, image等]复制到WebContent下面,目录结构相对于WebContent要一致。

3).重新启动系统即可

3.1.2 可扩展架构实现

前面介绍了架构的运行流程,接下来就是详细讨论如何实现这么一个架构了,本架构的实现主要依赖与Spring和web应用服务器。

1.可扩展架构核心类图

图 3-1-2 扩展架构核心类图

表 3-1-1 扩展架构核心组件描述

组件

组件职责

ContextLoaderListener

Spring核心监听器,启动SpringMVC。

FrameLoaderListener

系统核心监听器,模块加载,初始化相关配置,如初始化ConfigCenter,Log,准备系统所需数据等。

ModuleService

这个是可扩展的核心组件,管理模块的部署和相关资源的清理和构建。系统启动的时候调用,主要有一下几个方法,init进行模块初始化,initModuleContext进行模块上下文初始化,clear进行系统的资源清理,freshPrivilege进行系统权限数据的更新,contextDestory进行系统上细纹资源的清理。本组件是实现扩展的核心。

ConfigCenter

保存配置信息,以便其他组件获取相关的配置信息,系统启动时候初始化。

Key

为配置体系服务,提供key-value,作为ConfigCenter的key值,当系统没有指定相关的配置时,使用key中的默认值。

Log

轻量级的日志服务组件,若没有指定日志配置文件,将自动加载默认配置文件classpath:log.cf。

Module

是模块配置的一个抽象,在module-config.xml中进行了配置,ModuleManager会读取这个配置并转化成Module对象,然后根据模块部署的情况进行系统初始化处理。

DaoSupport

数据访问组件接口,实现对数据的访问,使用的是ORM框架,子类是使用Hibernate的实现类。

QueryHelper

查询帮助组件接口,支持条件查询,分页查询和排序。

PageBean

保存分页查询的结果Bean,其中包含结果信息,总页数,当前页,总记录数目等分页相关的信息。

2.权限模型设计

这个主题在下一节中进行描述,见

3.2 RBAC权限模型设计与实现

前面所提到的扩展架构其实要对权限相关数据进行初始化。那么本节就权限模型的设计和实现进行阐述。

不同职责的人员,对于系统操作的权限应该是不同的。可以对“组”进行权限分配。对于一个大企业的业务系统来说,如果要求管理员为其下员工逐一分配系统操作权限的话,是件耗时且不够方便的事情。所以,系统中就提出了对“组”进行操作的概念,将权限一致的人员编入同一组,然后对该组进行权限分配。

权限管理系统应该是可扩展的。它应该可以加入到任何带有权限管理功能的系统中。就像是组件一样的可以被不断的重用,而不是每开发一套管理系统,就要针对权限管理部分进行重新开发。

满足业务系统中的功能权限。传统业务系统中,存在着两种权限管理,其一是功能权限的管理,而另外一种则是资源权限的管理,在不同系统之间,功能权限是可以重用的,而资源权限则不能。

那么本系统将设计以下权限控制模型:

权限 ----> 权限组 ---> 角色 ---> 用户组

下面对这个模型进行简单的介绍:

1.权限

在系统中,权限通过 模块+动作 来产生,模块就是整个系统中的一个子模块,可能对应一个菜单,动作也就是整个模块中(在B/S系统中也就是一个页面的所有操作,比如“浏览、添加、修改、删除”等)。将模块与之组合可以产生此模块下的所有权限。

2.权限组

为了更方便的权限的管理,另将一个模块下的所有权限组合一起,组成一个“权限组”,也就是一个模块管理权限,包括所有基本权限操作。比如一个权限组(用户管理),包括用户的浏览、添加、删除、修改、审核等操作权限,一个权限组也是一个权限。

3.角色

权限的集合,角色与角色之间属于平级关系,可以将基本权限或权限组添加到一个角色中,用于方便权限的分配。

4.用户组

将某一类型的人、具有相同特征人组合一起的集合体。通过对组授予权限(角色),快速使一类人具有相同的权限,来简化对用户授予权限的繁琐性、耗时性。用户组的划分,可以按职位、项目或其它来实现。用户可以属于某一个组或多个组。

3.2.1 领域模型分析

要设计基于角色的权限控制模型,用户便是使用权限的主体。这里的领域模型包括:用户,角色和权限。

用户可以有多个角色,一个角色也可以有多个用户,一个角色包含0个或多个权限,一个权限可以属于多个角色,下面是这三者之间的类图。

图 3-2-1 权限领域模型关系

从上面可以看到,用户并不直接与权限关联,而是通过角色划分,权限分配到角色上,角色具备一组权限。但这是粗粒度上面的划分,仅仅是控制到功能上面的权限控制,并没有控制到字段的权限控制。这与Oracle的权限不同,Oracle权限可以控制到某行数据的某个字段,是细粒度的权限模型。

3.2.2 基础数据操作

要实现权限的分配和存储,就必须进行相关数据的存取,本系统使用MySQL数据库存储相关的信息,使用Hibernate实现数据的访问。组件之间的类图关系如下(关于DAO数据访问请参照):

图 3-2-3 RBAC数据访问类图

表 3-2-2 组件描述

组件

功能描述

DaoSuport

数据访问接口组件,见

HibernateDaoSupport

数据访问Hibernate支持,抽象类,被设计用于继承,见

PrivilegeService

权限模型数据访问服务接口

RoleSerice

角色模型数据访问服务接口

UserService

用户模型数据访问服务接口

3.2.3 RBAC实现

前面介绍了一些基础,知道了整个系统的权限控制流程,那么现在来具体谈谈前后端是怎么进行权限控制的。

正如[2.5 web中的RBAC概述]所描述的那样,WEB中的权限控制包含两个方面的内容。其一,控制权限的显示;其二,请求的拦截,并检测权限。对于其一,采用自定义标签以及自定义标签函数实现对权限的显示控制;对于其二,采用Spring的拦截器进行请求的拦截,检测权限以决定返回的页面。下面整体来看看这个类图设计。

图 3-2-4 WEB权限控制类图

表 3-2-3 WEB权限控制组件描述

组件

功能描述

AbstractBasicTag

封装了标签的基本属性,用于继承

AbstractEventTag

封装了标签的基本事件属性,用于继承

AbstractTag

提供Servlet规范中,Tag接口的默认实现,用于继承

AnchorTag

权限显示控制的核心标签

Validator

相关验证,如是否有权限,是否为管理员等

CheckPrivilegeFilter

请求过滤器,专用于权限的检测,后台的权限拦截就是靠这个类了,前端的请求都会被这个类拦截,如果通过权限检测就返回对应页面,没有权限就重定向到没有权限提示页面

关于自定义标签以及自定义函数的使用方法在这里就不再赘述了。

权限控制流程如下:

图 3-2-5 权限控制过程图

在上图中,CheckPrivilegeFilter作为一个核心的权限控制器,起到了请求转发的作用。它会根据用户的情况进行权限检测以返回相应的页面,当返回正常页面时,便会同步加载所有的自定义标签,在自定义标签内部,会进行权限的检测,如果没有权限就不会显示该标签。如果该请求对应的用户没有操作的权限,就会返回一个没有权限的页面,告知用户没有操作权限,这个页面是可以进行配置的。具体的配置可以在web.xml中添加初始化参数。

3.3 JSONLIB设计与实现

在本系统中,所有的Ajax请求所使用的数据格式都是JSON,当前确实有很多JSON相关的开源工具,如json-lib,Gson,Jackson(Spring就是使用这个)。

但是,这些开源工具都功能太过强大,就本项目而言,只需要最基本的功能即可,第三包中很多功能特性根本不需要,为了减少第三方包的依赖,决定自行设计一个简单的,适合于本项目的jsonlib。

在本项目中,主要的任务是要把java对象转化成javascript可识别的json字符串。对于从字符串转换成java对象的需求并不是很大,因此在设计的时候更多精力是放在了java转json字符串的问题上面(关于本项目可在github上面浏览:https://github.com/cianfree/cdesign/tree/master/jsonlib)。

对于本jsonlib的设计需求,应该能够自定义解析的方式,提供一定的灵活性,以下是jsonlib的设计类图。

图 3-3-1 JSONLIB类图

从上面的类图中我们可以看到,以Json为核心类,JsonWriter和JsonReader为核心接口。下面对这些组件进行详细描述:

表 3-3-1 jsonlib组件描述

组件

功能描述

Json

进行json数据格式转换,可以进行JsonWriter和JsonReader的注册,也就是允许开发者自定义java对象的Reader和Writer,提供了扩展性。

JsonReader

把一个java对象转成一个json字符串,接口,开发者可以自定义Reader,以便实现特殊java对象的转换。

JsonWriter

把一个json字符串转成一个java对象,接口,开发者可以自定义Writer,实现特殊对象的转换。

JsonUtils

Json相关的工具类,提供基础数据的处理。

JSONLIB系统内部处理流程:

图 3-3-2 java对象转json字符串

图 3-3-3 json字符串转java对象

可以看到,jsonlib被设计得非常简单,与此同时也给了开发者很大的自由度,开发者可以灵活的进行扩展JsonLib的功能,这已经能够满足本项目的需求。

3.4 Ajax基础服务设计与实现

本次设计的方案是使用一个核心的Servlet作为Ajax请求的分发器,将前端发出的请求经过解析分发给特定的模块中具体的服务进行处理,最终返回的是json字符串数据。也就是说本项目是依赖于前面Jsonlib项目的。(关于本项目可在github上面浏览:https://github.com/cianfree/cdesign/tree/master/cdajax)。

图 3-4-1 Ajax服务类设计

表3-4-1 Ajax组件职责说明

组件

功能描述

AjaxDispatcher

Ajax核心分发器,根据请求解析出处理该ajax服务的模块和对应模块内的服务

AjaxModuleManager

注册系统ajax模块和获取相应的ajax服务

KVList

存储请求相关的信息,key-value,request,Session,Applciation;但不包含文件相关的信息。

AjaxModule

标识一个类是Ajax组件类,是一个Annotation

AjaxMethod

表示一个方法是Ajax服务方法,是一个Annotation

具体代码实现就不说了,可以参考GitHub上面的项目源代码,下面介绍Ajax的启动流程和处理流程。

图 3-4-2 Ajax服务启动流程

图3-4-3 Ajax请求处理流程

3.5 日志服务设计与实现

本系统并不打算使用第三方的日志工具,以自己设计了一个简单的日志记录工具,可以通过配置文件进行简单的配置(关于本项目可在github上面浏览:https://github.com/cianfree/cdesign/tree/master/cdlog)。

可配置的选项如下:

表 3-5-1 日志配置项

配置项

描述

log.level

日志输出级别,五个级别[debug, info, warn, error, fatal],默认是fatal,即把fatal以下的日志全部输出。

log.target

日志输出目标,可选项[console, dir],即可以输出到控制台或文件中去,默认是console

log.target.dir

日志输出目录,可选项:当前应用目录[user.dir],使用绝对的物理路径[如:/home/arvin/logs]

log.date.pattern

日期格式化,默认是yyyy-MM-dd HH:mm:ss

日志工具总体类图,如图4-5-1所示:

图4-5-1 日志工具类图

配置文件示例:log.cf (classpath路径下才可生效)

# 日志输出级别,五个级别[debug, info, warn, error, fatal]
log.level=fatal
# 日志输出目标,可选项[console, dir]
log.target=console
# 日志输出目录, 可选项: 当前应用根目录[user.dir], 自定义绝对路径[如D:/poj/log]
log.target.dir=user.dir
# 日期格式化
log.date.pattern=yyyy-MM-dd HH:mm:ss

3.6 数据访问设计与实现

在本系统中,选择了Hibernate作为数据持久化方案,关于Hibernate的相关技术这里不再赘述,请查看2.2 Hibernate。

对于数据的持久化,最基本的莫过于增删改查了,也就是平常所说的CRUD。另外,分页功能也是非常的常用,如果对每个实体类都相应的设计一个Dao接口,不仅费时费力,而且使用起来也非常不方便,扩展性和重用性都很差。基于此,在本系统中,对数据操作进行进一步的封装,使得更加方便扩展和使用。以下便是DAO的抽象设计:

图 3-6-1 DAO类图

经过这样的设计之后,关于DAO的实现就变得非常简单,而且扩展性能也好,重用性非常高。由于逻辑简单在本系统中没有特别使用Dao层,直接使用了Service层,即没有设DAO,例如我们进行User类的数据访问,只需设计一个对应的UserService及其实现类UserServiceImpl即可。经过上面的设计之后,UserService和UserServiceImple就变得非常简单,请看下面的例子。

UserService:

public interface UserService extends DaoSupport<User> {
    // 这里可以定义关于User的特殊业务处理方法
}

UserServiceImpl:

public class UserServiceImpl extends HibernateDaoSupport<User> 
implements UserService {
    // 这里给定特殊实现
}

在本系统中,DaoSupport的继承体系如下:

图 3-6-2 DAO继承体系

3.7 分页设计与实现

3.7.1 前端分页插件设计

设计该分页插件的具体实现思路就是,在给定的div上面根据分页相关参数添加页码按钮,显示当前页码,添加页面按钮的响应事件,如上一页,第一页,首页,末页。当点击的时候就会返回当前页和每页显示的距离数目。

在显示分页信息的时候,有以下几种情况:

表 3-7-1 分页显示情况表

经过仔细分析得知,该分页插件的难点在于如何计算显示页码的开始和结束下标,以下是结算开始和结束下标的算法:

// 计算begIndex和endIndex
var indexAdapterFlag = false;
function indexAdapter() {
	// showSize=10: 偶数,左4=10/2-1, 中间选中, 右5=10/2
	// showSize=5:  奇数,左2=(5-1)/2, 中间一个,右2=(5-1)/2
	indexAdapterFlag = true;
	// 计算showSize
	var showSize = getShowSize();
	var leftSize,	// 在当前页左边显示几个页码 
		rightSize;	// 在当前页右边应该显示几个页码
	if(showSize % 2 == 0) {
		leftSize = showSize/2 -1;
		rightSize = leftSize + 1;
	} else {
		leftSize = (showSize - 1) / 2;
		rightSize = leftSize;
	}
	if(getCurrentPage() - leftSize <= 0) {
		begIndex = 1;
		rightSize += (leftSize - getCurrentPage() + 1);
	} else {
		begIndex = getCurrentPage() - leftSize;
	}
	if(getCurrentPage() + rightSize > getPageCount()) {
		endIndex = getPageCount();
		begIndex -= (getCurrentPage()+rightSize - getPageCount());
	} else {
		endIndex = getCurrentPage() + rightSize;
	}
	if(begIndex <= 0) begIndex = 1;
	if(endIndex > getPageCount()) endIndex = getPageCount();
}

3.7.2 基于数据库的分页

在分页设计中,抽象出一个对象用于记录一次查询的分页结果,包含一些分页的基本信息,如当前页,下一页,数据,总条数,每页显示条数等。这个对象叫做PageBean,关于查询的分页,主要通过QueryHelper来进行的,QueryHelper是支持链式调用的,使用示例如下:

PageBean pb = this.userService.paging(
    this.getCurrentPage(currentPage), //
    this.getPageSize(pageSize), //
    this.queryHelper
        .setClass(User.class)
        .setAlias("u")
        .addCondiction(!Validator.isEmpty(account),
            "u.account LIKE ?", "%" + account + "%")//
        .addOrderProperty("u.buildTime", false));

关于QueryHelper的接口定义如下:

public interface QueryHelper {
	// 设置针对谁的查询
	QueryHelper setClass(Class<?> clazz);
	// 设置别名
	QueryHelper setAlias(String alias);
	/**
	 * 给where子句添加条件同时指定参数
	 * @param condiction	条件,如username like ? 
	 * @param params	对应的参数
	 * @return	返回本对象,用于支持链式调用
	 */
	public QueryHelper addCondiction(String condiction, Object... params);
	/**
	 * 如果第一个参数为true,则添加该条件到Where子句中,前面可能就是一个boolean表达式,只有满足一定条件的时候才会添加查询条件
	 * 
	 * @param append	是否要添加查询条件
	 * @param condiction	要添加的查询条件
	 * @param params	对应查询占位符的参数
	 * @return	返回本对象,用于支持链式调用
	 */
	public QueryHelper addCondiction(boolean append, String condiction, Object... params);

	/**
	 * 拼接order by子句
	 * @param propertyName	要进行排序的属性名称,当然是指数据库中的字段名称
	 * @param asc	ASC为true表示升序排列,false表示降序排列[DESC]
	 * @return 返回本对象,用于支持链式调用
	 */
	public QueryHelper addOrderProperty(String propertyName, boolean asc);
	/**
	 * 如果append为true,则拼接
	 * @param append	指定是否要添加排序属性,如果条件判断为true就添加
	 * @param propertyName	要排序的属性名
	 * @param asc	true表示升序,false表示降序
	 * @return	返回本对象,用于支持链式调用
	 */
	public QueryHelper addOrderProperty(boolean append, String propertyName, boolean asc);
	/**
	 * 获取生成的用于查询数据列表的查询语句
	 * @return 返回查询列表的查询语句,可能是HQL,也可能是SQL语句,具体看是怎么实现的
	 */
	public String getListQueryString();
	/**
	 * 获取用于查询数据列表的数量查询语句
	 * @return 返回查询语句
	 */
	public String getCountQueryString();
	/**
	 * 获取查询参数
	 */
	public List<Object> getParameters();
}

在本次开发中,使用的是Hibernate实现的QueryHelper,在这里就不对HibernateQueryHelper的源代码进行讲解了。

3.7.3 基于内存的分页

基于内存的分页使用的是一个静态类MemoryPaging,能够对一个集合中的对象进行分页,并返回一个PageBean对象,该类有两个静态方法用户分页,一个是不需要进行排序,另一个用户需要设定排序规则:

没有排序规则的分页

PageBean paging(
int currentPage, // 当前页
int pageSize,  // 每页数据条数
Collection recordList) // 要进行分页的记录

具有排序规则的分页

PageBean paging(
int currentPage, // 当前页
int pageSize,  // 每页数据条数
Collection recordList, // 要进行分页的记录
Comparator sorter) // 排序规则

算法描述

该内存的分页比较简单,只需要根据当前页和每页大小就能计算出从第几条数据开始,把那一页的数据保存起来返回一个PageBean,PageBean对象内部就会对相关的分页数据进行计算。

public static final PageBean paging(int currentPage, int pageSize, Collection recordList) {
    List list = new ArrayList();
    currentPage = getCurrentPage(currentPage); // 适应性当前页
    pageSize = getPageSize(pageSize);
    int index = 1;
    int begIndex = (currentPage - 1) * pageSize + 1;
    int endIndex = (begIndex + pageSize) - 1;
    endIndex = endIndex > recordList.size() 
                     ? recordList.size() 
                     : endIndex; 
    for(Object obj : recordList) { 
    // 遍历内存的数据集,把begIndex和endIndex之间取出
       if(index >= begIndex && index <= endIndex) {
            list.add(obj);
        }
        ++ index;
    }
    return new PageBean(
                 currentPage, 
                 pageSize, 
                 getRecordCount(recordList), // 获取记录总数
                 list);
    }
public static final PageBean paging(int currentPage, int pageSize, Collection recordList, Comparator sorter) {
        List list = new ArrayList(recordList);
        Collections.sort(list, sorter); // 根据给定的排序规则排序
        return paging(currentPage, pageSize, list);
    }

四、模块分析

4.1 模块划分

在本次项目开发中,本系统将构建一个基础应用模块和扩展的两个模块。如下图:

图 4-1-1 系统模块图

4.2 模块功能

在第四节中讨论了系统基础架构设计,现在可以进一步设计系统所需要的功能的,经过前面的分析,本次开发需要扩展出三个模块,接下来对这三个扩展模块进行详细说明。

4.2.1 基础应用扩展模块

本模块是所有其他扩展模块的基础,是本系统框架的第一个扩展模块。提供了系统的相关通用的管理功能,如用户管理,角色管理,个人信息管理。下面对该模块的功能进行详细的描述。

图 4-2-1 基础应用扩展模块功能

4.2.2 在线评测扩展模块

在线评测系统扩展模块是本次系统开发的核心功能模块之一,功能稍微复杂一些,主要提供在线评测和辅辅助教学功能。

图 4-2-2 在线评测模块功能

4.2.3 在线论坛扩展模块

在线论坛扩展模块是为了提供一个平台让大家可以相互之间进行交流,论坛的功能和当前网络上面的论坛没有太多的区别,相对比较简单,请看本系统论坛的功能明细:

图 4-2-3 在线扩展模块功能

4.3 领域模型分析

4.3.1 系统参与者

本系统为可扩展的在线测评系统,在可扩展的框架之上,本次扩展了三个模块,分别是基础管理模块,在线评测模块和论坛模块。系统外部参与者主要有:

  • 超级管理员
  • 普通用户
  • 学生
  • 教师

图 4-3-1 顶层用例

4.3.2 参与者权限分配

在本系统中,所谓的参与者,其实从根本上来说还是用户,因为使用的是基于角色的权限控制,所以在本系统中只有用户的概念,其余的都是定义的角色。本系统中超级管理员是没有权限限制的。学生,教师是系统自定义的角色,超级管理员可以给角色分配权限。下面就不同的角色进行权限的分配。

普通用户权限初定义

图 4-3-2 普通用户功能

学生用户权限初定义

图 4-3-3 学生用户功能

教师用户权限初定义

图 4-3-4 教师用户功能

五、模块详细设计与实现

5.1 系统体系架构

5.1.1 系统体系架构

本项目的扩展模块均使用SSH(SpringMVC+Spring+Hibernate)体系架构进行设计和实现。在进行web系统开发的时候,使用一个良好的体系结构能提高web系统的扩展性和可维护性,同时能提高系统开发的效率,利于项目任务划分。本项目就web系统开发所面临的问题,结合当前流行的web开源框架Spring,Hibernate,将SSH应用到项目中来。

图 5-1-1 SSH应用到项目系统架构图

5.1.2 职责分配

1.SpringMVC负责WEB层

SpringMVC的核心分发器接收到前端的表单提交的请求,然后通过映射关系,分发给Controller进行请求的处理,所有的Controller都使用Annotation进行标识,要处理请求的方法也经过Annotation进行标识,SpringMVC启动的时候就会处理这些映射关系。

2.Spring负责业务层的管理

Service提供了统一的调用接口,封装了数据的持久化操作,实现业务逻辑,并集成了Hibernate,利用Spring的声明式事务处理来统一管理Hibernate的事务,对数据的ACID属性进行有效控制。

3.Hibernate负责数据持久化

Hibernate通过一组xxx.hbm.xml文件和POJO对象,建立起对象-关系映射,使得与数据库中的表一一对应,并且能方便的屏蔽掉数据库之间的差异,提供一个HQL进行统一查询,方便数据库的迁移。本项目更是在此基础之上,构建了更为灵活的数据访问,见 。

5.1.3 结合SSH的MVC模型

MVC模式在UI设计中使用得非常普遍,这个模式的特点是:分离了模型,视图,控制器三种角色,将业务从UI设计中独立出来,封装到模型和控制器的设计上面去,使得它们之间相互解耦,可以独立扩展而不彼此依赖。

从整体上看,在使用SpringMVC的时候,需要在部署描述符(web.xml)中配置DispatcherServlet,这可以看成是一个前端控制器的具体实现,另外需要在Bean中配置Web请求和Controller(控制器)的关系,可以使用xml和Annotation两种方式进行关联,最后还要处理各种视图的展现方式。在具体使用Controller的时候,会看到ModuleAndView数据的生成,还能看到ModuleAndView把数据交给相应的View(视图)来进行呈现。

在SSH的基础上,为了更好的扩展,设计了更为灵活的数据访问,现在我们就来看看扩展的SSH是如何实现MVC这种设计模式的。

首先,来看SpringMVC的处理流程是如何的:

图 5-1-2 SpringMVC工作流处理

经过前面的介绍,大致了解了SpringMVC,接下来看看SSH的一个整体工作流程是如何的。

图 5-1-3 MVC序列图

5.1.4 SSH工作流程说明

应用服务器(如Tomcat)启动时,应用服务器会去读取部署描述符web.xml,初始化相关资源,如初始化Spring核心容器DispatcherServlet,DispathcerServlet就会读取applicationContext.xml来初始化SpringMVC环境。记录请求与Controller的对应信息等。

当Web浏览器向服务器发起请求的时候,DispatcherServlet就会根据请求信息找到相应的Controller,执行Controller的对应处理方法处理本次请求,此时Controller就是去调用业务层的组件Service,Service通过Hibernate访问数据库,对数据进行相关的业务处理,然后把请求的结果数据封装到一个ModuleAndView的类中。ModuleAndView就会把数据提交给Spring,让Spring找到相应的视图解析器去展现数据给用户,本系统使用的就是jsp来显示。JSP动态进行数据的渲染,最后把结果返回Web浏览器。

5.2 基础应用扩展模块

5.2.1 领域模型分析

基础应用扩展模块的功能就好像基础设施一般,没有它不行,所有的其他扩展模块都要依赖于本模块。本模块主要是做一些管理的工作,如用户的管理,角色的管理,角色的授权,个人信息的管理,这些都是基础性服务。

关于本模块的功能,详见。

从上面的的分析可知,本模块涉及到的领域模型有:用户,角色,权限,以下是这三者的类图。

图 5-2-1 领域模型类图

5.2.2 视图与控制层设计

视图通常是与某个Controller联系在一起的,用户发起请求,后台经过数据处理返回用户一个页面。从分析可以进行以下的界面设计。所有的Controller都继承与BaseController,BaseController提供公共方法以及注入主要的组件,以便提供子类使用,简化代码,提高重用。

本模块有四个核心Controller,分别是HomeController,PersonalController,RoleController和UserController。它们的职责如下:

表 5-2-1 基础应用扩展模块控制器职责分配

控制器

职责

HomeController

主页面相关跳转控制,主页,登录,注销,没有权限页面,简介页面

PersonalController

个人设置相关控制器,如更新个人信息,修改密码

RoleController

角色信息管理控制器,如角色CRUD以及角色授权

UserController

用户相关控制器,如用户的CRUD,用户注册,发送激活邮件,初始化密码等

控制器是由Spring的IoC容器进行管理的,因此使用起来非常的方便,你可以直接在方法中声明相关的参数,如HttpServletRequest,HttpSession,ModuleMap等,Spring就会自动帮你注入这些参数,比起Struts来说这可要灵活许多。

表 5-2-2 基础应用扩展模块Controller映射

控制器

动作

RequestMapping

出口

HomeController

主页面

index.html;home.html

home.jsp

HomeController

无权限

noPrivilege.html

noPrivipege.jsp

HomeController

简介

introduction.html

introduction.jsp

HomeController

注销

logout.html

logout.jsp

PersonalController

查看个人信息

personalInfo.html

editUserInfoUI.jsp

PersonalController

更新个人信息

updateUserInfo.html

personalInfo.html

PersonalController

修改密码页面

personalPassword.html

personalInfo.html

PersonalController

修改密码

updateUserPassword.html

updateUserPwdSuccess.jsp;editUserPasswordUI.jsp

RoleController

角色列表

roleList.html

roleList.jsp

RoleController

编辑角色

editRoleUI.html

editRoleUI.jsp

RoleController

保存角色

saveRole.html

roleList.html

RoleController

更新角色

updateRole.html

roleList.html

RoleController

角色授权

privTreeUI.html

roleList.html

UserController

用户登录界面

loginUI.html

loginUI.jsp

UserController

登录

login.html

home.jsp

UserController

注册页面

regUI.html

regUI.jsp

UserController

注册

reg.html

regSuccess.jsp;regUI.jsp

UserController

用户激活

activate.html

home.jsp;resendmail.jsp

UserController

用户列表

userList.html

userList.jsp

UserController

保存用户

saveUser.html

userList.html

UserController

更新用户

updateUser.html

userList.html

5.2.3 业务组件设计

在SSH系统架构中,模型组件负责处理系统的业务逻辑,这些Service被Spring的IoC容器管理着,方便的处理依赖关系,并且所有的业务组件都是以接口方式出现,是完全面向接口编程,扩展性好。

表 5-2-3 组件职责

业务组件接口

组件默认实现

职责描述

PrivilegeService

PrivilegeServiceImpl

权限相关的业务处理

RoleService

RoleServiceImpl

角色相关业务处理

UserService

UserServiceImpl

用户相关业务处理

MailService

MailServiceImpl

邮件操作组件,主要是发送激活邮件

5.3 在线测评扩展模块

5.3.1 领域模型分析

本扩展模块是本系统的重要功能之一,详细的功能分析见 4.2 .2 。

从上面的功能分析中可以抽象出该模块具有以下几个领域模型:题目,课程,解题结果,作业,作业结果。他们之间的关联关系如图 6-3-1:

图 5-3-1 在线评测领域模型

5.3.2 视图与控制层设计

与前面一样,本模块同样还是使用SSH架构,在本模块中包含以下几个Controller:ProbleamController,WorkoutController,HomeworkController,CourseController。各个控制器的职责分配如下:

表 5-3-1 Controller职责描述

控制器

职责

ProblemController

题目管理控制器

HomeworkController

作业管理控制器

CourseController

课程管理控制器

根据SpringMVC处理流程,可以确定Controller和页面之间的调用关系,如表 5-3-2:

表 5-3-2 在线评测模块Controller映射

Controller

动作

RequestMapping

出口

PojCoreController

题目列表

problemList.html

problemList.jsp

PojCoreController

编辑题目

editProblemUI.html

editProblemUI.jsp

PojCoreController

保存题目

saveProblem.html

problemList.html

PojCoreController

更新题目

updateProblem.html

problemList.html

PojCoreController

删除题目

deleteProblem.html

problemList.html

PojCoreController

解题页面

solveProblemUI.html

solveProblemUI.jsp;problemList.html

CourseController

课程列表

courseList.html

courseList.jsp

CourseController

课程信息

viewCourse.html

viewCourse.jsp

CourseController

编辑课程

editCourseUI.html

editCourseUI.jsp

CourseController

保存课程

saveCourse.html

courseList.html

CourseController

更新课程

updateCourse.html

courseList.html

CourseController

删除课程

removeCourse.html

courseList.html

CourseController

参加课程

joinCourse.html

viewCourse.html

CourseController

退出课程

quitCourse.html

viewCourse.html

CourseController

课程学生列表

courseStudentList.html

courseStudentList.jsp

CourseController

删除某课程学生

deleteCourseStudent.html

courseStudentList.html

CourseController

学生用户列表(全站)

studentList.html

studentList.jsp

CourseController

添加学生到课程

addStudentToCourse.html

courseStudentList.html

HomeworkController

作业列表

courseHomeworkList.html

courseHomeworkList.jsp

HomeworkController

添加作业

addCourseHomework.html

courseHomeworkList.html

HomeworkController

删除作业

removeCourseHomework.html

courseHomeworkList.html

HomeworkController

某道作业作答情况

studentSolveList.html

studentSolveList.jsp

HomeworkController

某学生的作业情况

studentHomeworkList.html

studentHomeworkList.jsp

HomeworkController

做作业

solveHWUI.html

solveHWUI.jsp

5.3.3 业务组件设计

本模块除了基本的Service组件之外,还有Ajax组件,Ajax组件是不受Spring控制的,是一个独立的Ajax服务。详见。下面介绍各大组件的职责,见表 5-3-3:

表 5-3-3 在线评测组件职责

组件接口

组件默认实现

组件职责

ProblemService

ProblemServiceImpl

题目业务处理

WorkoutService

WorkoutServiceImpl

普通在线评测业务处理

CourseService

CourseServiceImpl

课程业务处理

HomeworkService

HomeworkServiceImpl

作业业务处理

SolveHWService

SolveHWServiceImpl

作业的业务处理

POJAjaxModule

ajax模块服务组件

5.3.4 源代码评测设计

源代码评测是在线评测的基础,可以对程序语言进行配置,提供对异常代码的检测机制。

源代码评测设计类图

如图5-3-2:

图 5-3-2 源代码评测类设计

组件描述

表 5-3-4 源代码评测组件

组件

职责描述

HandlerStatus

枚举类型,记录执行的结果

SourceHandler

处理代码的服务端评测,核心接口,不同语言提供不同的Handler

SourseHandlerManager

代码评测Handler的管理,因为可能有不同语言,需要一个管理器进行管理,通过读取spring-languages.xml初始化,可自定义。

Language

标识程序语言类

ECDetector

恶意代码检测核心接口,提供可以扩展的代码检测机制

ECDetectorManager

管理恶意代码的检测器,如注册恶意代码检测器,读取默认配置文件detectors.properties,用户也可以自定义配置文件

5.4 在线论坛扩展模块

5.4.1 领域模型分析

在线论坛扩展模块是为了提供用户交流用的,关于本模块的功能,详见。

从模块功能上面进行分析,可以总结出以下几个领域模型:板块,主题,回复,用户。图 5-4-1 是本模块的领域模型类图。

图 5-4-1 论坛模块领域模型

5.4.2 视图与控制层设计

本模块采用的还是SSH,所以处理流程都是一样的,在本模块中包含以下两个核心的Controller:ForumController和ForumManagerController,他们的职责如表 5-4-1:

表 5-4-1 Controller职责描述

控制器

职责

ForumController

用户使用论坛控制器,主要从用户角度看

ForumManagerController

论坛管理控制器,从论坛管理角度看

根据SpringMVC处理流程,可以确定Controller和页面之间的调用关系,如表 5-4-2:

表 5-4-2 论坛模块Controller映射

控制器

动作

RequestMapping

出口

ForumManagerController

板块列表

forumList.html

forumList.jsp

ForumManagerController

编辑板块

editForumUI.html

editForumUI.jsp

ForumManagerController

保存板块

saveForum.html

forumList.html

ForumManagerController

更新板块

updateForum.html

forumList.html

ForumManagerController

板块上移

moveUp.html

forumList.html

ForumManagerController

板块下移

moveDown.html

forumList.html

ForumManagerController

删除板块

deleteForum.html

forumList.html

ForumController

主题展示

forumShow.html

forumShow.jsp

ForumController

编辑主题

editTopicUI.html

editTopicUI.jsp

ForumController

保存主题

saveTopic.html

forumShow.html

ForumController

删除主题

deleteTopic.html

forumShow.html

ForumController

回复列表

topicShow.html

topicShow.jsp

ForumController

回复页面

replyTopicUI.html

replyTopicUI.jsp

ForumController

回复主题

replyTopic.html

topicShow.html

ForumController

移动主题

moveTopic.html

forumShow.html

ForumController

转普通帖

ordinaryTopic.html

forumShow.html

ForumController

转置顶帖

topTopic.html

forumShow.html

ForumController

转精华帖

creamTopic.html

forumShow.html

5.4.3 业务组件设计

和前面的一致,对于Controller来说,所有的组件都是接口类型,在本模块中包含下面的组件,如表 5-4-3:

表 5-4-3 论坛模块组件表

业务组件接口

组件默认实现

职责描述

ForumService

ForumServiceImpl

板块相关的业务处理

TopicService

TopicServiceImpl

主题相关业务处理

ReplyService

ReplyServiceImpl

回复相关业务处理

六、系统测试

系统的登录界面是用户使用本系统的唯一入口点,无论是具备什么角色的用户都通过该界面登录系统。

图 6-1 系统登录主界面

作为普通用户也能够进行注册,但是注册只能默认是普通用户,不能够选择,如果要转换自己的角色,需要向系统管理员沟通。

图 6-2 系统注册页面

6.1 基础应用模块测试

基础应用模块是系统的基础功能,首先使用超级管理员登录,登录后的界面如下:

图 6-1-1 Home界面

鼠标移至系统管理 用户管理,见出现以下界面:

图 6-1-2 用户列表

超级管理员可以查看用户的列表信息,但是超级管理员不能删除自己和其他超级管理员,管理员具备非超级用户的增删改查。点击新建按钮出现创建用户的界面如下:

图 6-1-3 创建用户

接着点击提交,就会返回用户列表页面,并可以看到对应的操作选项,如下图:

图 6-1-4 添加用户后用户列表

点击初始化密码,将会给一个提示框,如下图:

图 6-1-5 初始化密码提示

先来检查普通用户所具有的权限,将鼠标移至系统管理角色管理,得到角色列表如下图:

图 6-1-6 角色列表

可以看到,由于普通用户是系统固有的权限,不能删除,不过此时并没有配置相关的权限,单击权限设计进入普通用户的权限设置页面如下:

图 6-1-7 普通用户权限树

可以看到,当前普通用户没有任何权限,那么现在单击退出系统,使用张三这个用户登录系统,由于张三是普通用户,并且普通用户这个角色当前还没有配置任何可用的权限,所以张三登录之后应该是没有什么可以操作的,如下图:

图 6-1-8 普通用户可操作的选项

可以看到,他没有在线评测和在线论坛的使用权限,但是有实用工具的权限,因为这个是本系统设定的逻辑,实用工具并没有纳入权限的控制之中。那么现在重新使用超级管理员登录,并授予普通用户以下权限:

图 6-1-9 授权

本次演示授予普通用户使用论坛和个人信息设计的权限,点击提交并退出系统,再次使用张三这个用户登录系统,此时再来看:

图6-1-10 新的权限

6.2 在线评测模块测试

在线评测模块是本系统中一个比较重要的扩展模块之一,先来测试普通用户情况下最简单的在线评测使用。使用超级用户登录,并部署POJ模块。登录系统之后,将鼠标滑至【在线评测】,在显示的下拉列表中选中【题目列表】,得到的结果如下:

图 6-2-1 问题列表

由于当前并没有任何题目,所以列表为空,现在点击右下角的新建按钮,出现下面的画面。

图 6-2-2 新建题目

上面的数据是要添加的数据,单击保存,就会跳转到题目列表页面,如下图,会显示刚添加的HelloWorld题目。

图 6-2-3 显示添加的题目

在右边的操作中,我们可以看到可以进行删除,修改和做题操作,删除和修改这边就不再进行演示了,单击【做题】按钮,将出现以下界面。

图 6-2-4 做题页面

左上角的面板是用来显示题目的基本信息,左下角的面板是用来显示代码的执行情况,右边的面板是用来编辑源代码的。本例是要在控制台中显示HelloWorld,编辑好源代码之后,单击提交代码:

图 6-2-5 系统提示是否提交

系统会提交是否要进行提交,点击确定之后,就会出现下面的界面。

图 6-2-6 等待后台执行结果

这里其实是使用Ajax将代码发送到后台进行执行,然后把结果返回给客户端,实现无刷新的更新数据,最后把结果显示在左下角的面板中,如下图。

图 6-2-7 运行结果

当我们做完题目之后,就可以查看自己的做题结果了,把鼠标移动到在线测评,在下拉列表中选择我的成绩,就会显示我的做题列表。

图 6-2-8 我的成绩列表

其中还有重做的操作选项,如果开始的时候做错了,你可以选择继续做。

6.3 在线论坛模块测试

在线论坛模块旨在提供一个站内用户的交流平台,提高程序学习的气氛和效率。为了便于演示所有的功能,我们使用超级管理员登录系统。登录系统之后,将鼠标移动到论坛系统,单击论坛管理,如下:

图 6-3-1 板块列表

系统刚上线的时候,还没有设置任何的板块,现在点击新建按钮,页面展示如下:

图 6-3-2 添加板块

输入相关信息,然后点击保存,系统会跳转到板块列表页面,再来看时就能看到刚添加的板块信息了,如下图:

图 6-3-3 显示添加板块

我们可以看到右边的操作中,有上移和下移的操作,这个是用来控制板块显示的位置的,先添加几个演示数据,再来看。

图 6-3-4 板块列表

现在,我点击C/C++操作栏中的[上移],就会得到下面的结果,如图7-3-5。

图 6-3-5 板块上移操作

现在点击JavaEE板块对应操作栏中的下移操作,就会得到如下结果。

图 6-3-6 JavaEE板块下移

接下来测试分页的功能,现在继续添加板块,然后得到下面的结果,总共添加了14个板块,每页显示10条板块。

图6-3-7 分页

点击按钮【2】或者是点击下一页,就会得到下面的结果。

图 6-3-8 分页

以上测试主要演示了板块的管理功能,接下来要演示的是论坛的使用,把鼠标移到论坛系统上面,然后点击进入论坛,就会得到下面的结果。

图6-3-9 板块及其相关信息列表

单击其中一个板块,如数据库,进入该板块的主题列表如下:

图 6-3-10 板块的主题列表

由于该板块刚创建,因此没有主题,那么现在就可以单击【发新帖】按钮,来发表新的主题,单击按钮,结果如下:

图 6-3-11 发表新主题

填写好主题的内容之后,就可以单击提交按钮进行提交,提交之后会跳转到主题的列表页面,默认是普通主题帖,请看下图。

图 6-3-12 查看主题列表

单击该新主题,进入到该主题下的回复列表中,当然现在是没有任何回复内容的,因为是刚创建的,那么我们可以进行主题的回复。

图 6-3-13 主题展示

单击提交按钮,就会刷新当前页面,并把刚才的回复内容显示出来。

图6-3-14 回复列表

相信你也注意到了右上角的一些按钮,这个按钮的功能也很简单,首先来看【回复】按钮,这个按钮就是回复当前主题,和快速回复一样,只不过是使用独立的回复页面,如下图。

图 6-3-15 独立的回复页面

再来看第二个按钮【移动到其它板块】,从左上角的导航中可以看到,当前主题实在【数据库】这个板块中的,现在我单击【移动到其它板块】按钮,将会出现下图:

图 6-3-16 移动板块页面

选中JavaEE,然后单击提交按钮,就把该主题移动到JavaEE这个板块中去了,如下图:

图 6-3-17 移动到其他板块的结果

可以看到左上角变了,变成了JavaEE,也就是板块移动成功了。接下来是精华,置顶和普通三个按钮,这三个按钮的作用分别是把当前帖子变成精华帖显示,变成置顶帖显示和普通帖显示,默认情况下,帖子是普通帖,单击置顶按钮,可以看到下面的结果。

图 6-3-18 置顶帖子

可以看到已经把该帖子变成了置顶帖子,另外两个按钮我就不再演示了,大同小异。我们在创建几个新的主题,看看置顶帖的效果,一般情况下,月先创建的帖子会放在最后面,也就是,默认情况下按照时间排序。

图 6-3-19 帖子的顺序

可以看到,默认情况下是按照时间的降序进行排列的,而且,置顶帖永远是在最上面的,即便是时间比较早创建也不受影响。点击进入第二个新帖,然后把它转成精华帖,精华帖同样会受到时间的排序。

图 6-3-20 转成精华帖

七、总结

经过一个多学期的艰苦奋斗,最终将毕业设计之可扩展的在线评测系统顺利完成,系统基本上已经达到了预期的效果。从最初学习和总结大学一直以来的知识,到问题领域的分析,技术的选型,架构的设计,代码的重构,架构的重构,模块开发,最后进行系统测试。这么一步一步走过来,经历了很多的心酸,但也获益匪浅。

本次毕业设计综合了很多知识,无论是开源框架,还是自定义的开发架构都很好地运用到了本次系统的开发中来。是我感受最深的还是自己设计的一些组件和基础架构,如Ajax后端服务,这个是我参考了公司所开发的架构进行的一种简化。也许在别人眼中不值一提,但这是我经过努力思考的结果,在其中我成长了很多。

在实际的开发中,我总会不断的出现一些新的想法,不断对自己现有的架构进行调整和重构。一开始使用的是Struts2 Spring Hibernate,后来换成了SpringMVC Spring Hibernate这是比较大的变动。

经过这次的毕业设计,我最兴奋的并不是能不能最终实现这个系统,我所在意的是,我能否把自己的想法体现到程序中来,否则我自己不会设计那么多的基础架构,网上完全有这种东西,我自己设计也是不断参考现有的,以便进行简化,开发最低限度适合系统的工具,同时降低对第三方框架和工具的依赖。

系统虽然能够实现基础期望功能,但仍然有缺陷。我自己设计的基础架构我也知道有很多的缺陷在里面,但是鉴于时间关系,并没有继续在自设计架构和工具上进一步优化设计,我想在以后的学习和工作中会进一步优化现在的工具,为我所用。实现扩展架构是基于Spring进行设计的,对Spring的依赖较大。目前并没有设计成其他技术的扩展,业务层只能使用Spring,但是显示层的技术选择还是有一定的空间,这也是得益于Sping框架本身的设计。另外,本系统并没有继续开发自动部署的工具,使得在进行模块导入的时候要花点时间。

最后,总结一下本次毕业设计,几个月以来,虽然也会忙于工作,但总会抽出一些时间做毕业设计,遇到问题变先自己充分进行研究,想不明白的时候就直接去看开源框架的源代码,在不明白就去问百度,谷歌,如果还是不明白就问老师了,不过这种情况还真是少见。实习的时候,曾经我的一个技术主管对我说,我这种学习路线固然扎实,但是花的时间太多了,很多东西没必要刨根问底。虽然他说得有理,但是也许他不知道这就是我的乐趣吧。几个月来,终于看到了自己努力的成果。

八、参考文献

[1]计文柯. Spring技术内幕 [M] 北京: 机械工业出版社, 2012.
[2][美]David Flanagan. JavaScript: The Definitive Guide [M] O’REILLY, 2011.
[3][美]Ross Harmes Dustin Diaz. Pro JavaScript Design Patern [M].TELECOM PRESS,2009.
[4]陶国荣. jQuery权威指南 [M] 北京: 机械工业出版社, 2012.
[5][美]Joshua Bloch. Effective Java [M] 北京: 机械工业出版社, 2011.
[6]南阳理工学院在线评测系统[EB/OL]. http://acm.nyist.net/JudgeOnline/problemset.php.
[7]宜宾大学在线评测系统[EB/LO]. http://quanblog.com/oj.
[8]基于RBAC模型的权限管理系统的设计和实现[EB/OL]. http://hi.baidu.com/injava/item/137586d196bfe6e7b2f77775.
[9]郭理,秦怀斌,梁斌. 基于RBAC的高校Web服务平台权限设计 [J]. 微计算机信息, 2011, (2).
[10]jQuery Pagination Plugin [EB/LO]. 
http://archive.plugins.jquery.com/project/pagination.
[11]Spring Documentation[EB/LO]. http://www.springsource.org/documentation.
[12][美]Erich Gamma Richard Helm Ralph Johnson[M]. 设计模式 北京: 机械工业出版社, 2011.
[13]高性能WEB应用开发指南[EB/LO]. http://developer.51cto.com/art/201104/257581.htm.
[14]jQuery最佳实战[EB/LO]. http://www.open-open.com/bbs/view/1318473226718.
[15]Java Object Memory Structure[EB/LO]. http://www.codeinstructions.com/2008/12/java-objects-memory-structure.html.

网站公告

今日签到

点亮在社区的每一天
去签到