项目整体结构
配置文件:web.xml, spring.xml, springmvc.xml, mybatis.xml
包:controller, dto, service, dao, domain
web.xml
<!-- 注册ServletContextListener监听器。当ServletContext被创建时,该监听器会根据spring.xml创建出root ioc容器 -->
<listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</context-param>
<!-- 将所有请求交给DispatcherServlet处理。在类中会根据springmvc.xml创建出servlet ioc容器(该容器是root ioc容器的子容器。寻找bean时会先在该容器中寻找,若没找到则委托root容器寻找。) -->
<servlet>
<servlet-name>app-servlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app-servlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
spring.xml
<!-- 将数据库连接池交给容器管理 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver" />
<property name="jdbcUrl" value="..." />
<property name="user" value="root" />
<property name="password" value="xiemingrui" />
</bean>
<!-- 将mybatis的SqlSessionFactory交给容器管理 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="mapperLocations" value="classpath:com/xmr/dao/*Mapper.xml"/>
<property name="configLocation" value="classpath:mybatis.xml" />
</bean>
<!-- 将mybatis创建的代理类(即DAO)交给容器管理(dao包中放的只是接口而不是DAO,MapperScannerConfigurer则是将mybatis通过动态代理创建的DAO交给容器) -->
<bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.xmr.dao" />
</bean>
springmvc.xml
<!-- 将controller, service类交给容器管理 -->
<context:component-scan base-package="com.xmr.controller"/>
<context:component-scan base-package="com.xmr.service"/>
<!-- 配置静态资源,使得这些url不会被mapping到DispatcherServlet中处理 -->
<!-- <mvc:annotation-driven/> -->
<!-- <mvc:resources mapping="/res/**" location="/res/"/> -->
mybatis.xml
<!-- 配置延迟加载 -->
<configuration>
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
</configuration>
controller:
--将从request中获取的参数封装成DTO(Data Transfer Object)
--将DTO作为参数,调用service层的方法,进行业务处理,获取service层返回的DTO
--将service层返回的DTO,放进request的attribute中,forward给jsp去展示
service:
--将从controller层接收到的DTO封装成DO(Domain Object)
--将DO作为参数,调用dao层的方法,操作数据库,获取dao层返回的DO
--将dao层返回的DO,封装为DTO,返回给controller层
dao:
--接收DO作为参数,操作数据库,返回DO
String管理各个类的过程:
1. 当Context被创建时,触发org.springframework.web.context.ContextLoaderListener监听器中相应方法的执行。该方法会根据spring.xml配置文件创建出一个ioc容器(ApplicationContext),容器中包含了dataSource、sqlSessionFactory、
mapperScannerConfigurer这三个bean,而mapperScannerConfigurer
2. 当Tomcat启动时,会创建org.springframework.web.servlet.DispatcherServlet类的单例,并执行该Servlet的init方法(因为配置了load-on-startup)。init方法中会根据springmvc.xml配置文件创建出一个ioc容器(ApplicationContext),该容器中包含了com.xmr.controller/service包下的加了注解的相应Controller bean和Service bean。
3. 每个bean默认的scope为singleton,所以在创建ApplicationContext时,就会触发这些bean单例的创建。在创建mapperScannerConfigurer bean的单例时,其会扫描com.xmr.dao包下的所有DAO接口,并生成这些接口的代理对象,将这些对象放入容器中。在创建Controller bean时,因为Controller bean中依赖了一系列的Service bean,spring会去容器中寻找这些Service bean注入,而Service bean中又依赖了一系列的DAO bean,spring会去容器中找到这些DAO bean注入,最终完成Controller单例的创建。
4. 当接收到请求时,Tomacat调用DispatcherServlet的service方法。其service方法中会根据请求的url,从容器中取出相应的Controller实例,调用其相应方法处理请求。
项目前台功能:
1. 获取申报入口查询表单
controller:
--无接收参数
--调用service层的方法,获取仅包含了国家名的CountryDTO数组;调用service层的方法,获取仅包含了省份名的ProvinceDTO数组
--将service层返回的两个DTO数组,放进request的attribute中,forward给jsp,供用户从中选择相应出发中转国家和到达省份。
service:
--无接收参数
--调用countries的dao方法,获取全部的CountryDO;调用provinces的dao方法,获取全部的ProvinceDO
--将dao层返回的DO,封装为仅包含国家名的CountryDTO、仅包含省份名的ProvinceDTO,返回给controller层
countries的dao:
--返回全部的CountryDO
provinces的dao
--返回全部的ProvinceDO
2. 申报入口查询
===============================================================================
插:ReportTypeQueryDTO
ArrayList<String> countries;
String queryResult;
===============================================================================
controller:
--从request中获得始发地和中转地国家,封装进reportTypeQueryDTO的countries中。
--将DTO作为参数,调用service层的方法,获取含有queryResult的DTO。
--将从service层获取的DTO,放进request的attribute中,forward给jsp去展示
service:
--将从controller层接收到的DTO中的国家名封装成country DO数组。
--将每个country DO作为参数,调用countries表的dao方法,获取到完整的country DO。
统计所有country DO的type字段。若包含>0个type 1国家,则在queryResult中设置为核酸检测入口;若包含0个type 1国家,>0个type 2国家,则在queryResult中设置为健康申报入口;若包含0个type 1国家和0个type 2国家,则在queryResult中设置为可无需任何申报。
--将DTO返回给controller层
dao:
--接收country DO作为参数,查询countries表,返回完整的country DO。
3. 获取日常健康申报表单
controller:查看HttpSession中key为user的键值对,值是否为空。
若为空,则forward到登录界面jsp;若不为空,则forward到日常健康申报表单jsp。
4. 日常健康申报
===============================================================================
插:DailyReportDO
UserDO userDO;
int state;
Date date;
插:DailyReportDTO
HashMap<String, String> report;
boolean hint;
===============================================================================
controller:
--将从request中获取的参数封装进DailyReportDTO
--将DTO作为参数,调用service层的方法,对健康申报进行记录,获取到返回的DTO
--若返回的DTO中的hint为空,则申报成功重定向到申报成功页面;否则,将DTO放进request的attribute中,forward给申报表单jsp展示错误信息。
service:
--检查DTO中的数据正确性。若有问题,则在hint中设置相关提示并返回;若无问题,则根据DTO中的report,判断出健康状态state;从HttpSession中取出userDO;获取当前时间。封装dailyReportDO。
--将dailyReportDO作为参数,调用daily_report的dao方法,存储健康申报记录。
--将DTO返回给controller层。
dao:
--接收dailyReportDO,将其存储进数据库
5. 出示健康申报二维码
===============================================================================
插:QRCodeDTO String path; String hint;
插:QRCodeManager
public class QRCodeManager {
private static Map<String, QRCodeInfo>
qrCodeMap = new ConcurrentHashMap<>();
static {
ScheduledThreadPoolExecutor pool = new
ScheduledThreadPoolExecutor(1);
scheduledThreadPoolExecutor.scheduleAtFixedRate(
new Runnable() {
@Override
public void run() {
checkOutDateQRCode();
}
}, 1, 1, TimeUnit.HOURS);
}
private static void startQRCodeOutdateCheckThread(){..}
}
qrCodeMap中key为随机生成的字符串,value为QRCodeInfo类,该类包含二维码颜色信息、UserInfoDO对象、二维码图片路径、创建时间、最大存活时间、二维码类别信息(健康申报类型二维码)。
使用ScheduledThreadPoolExecutor定时执行重复任务。每小时执行一次checkOutDateSession方法,该方法会对qrCodeMap进行遍历,用系统的当前时间减去每个QRCodeInfo中记录的创建时间,若差值超过了最大存活时间,则从qrCodeMap中删除该二维码记录,并去到相应路径下删除二维码图片。
===============================================================================
controller:
--无接收参数
--调用service层的方法,获取service层返回的QRCodeDTO
--将DTO放进request的attribute中。若DTO中的hint为空,则forward到二维码展示页面进行二维码展示;否则,forward到错误提示页面展示错误信息。
service:
--无接收参数
--将从HttpSession中获取的UserDO作为参数(为空则在hint中设置相关提示并返回),调用daily_report的dao方法,获取到该用户的所有健康申报记录(DailyReportDO数组)。从DailyReportDO数组中获取到过去14天的记录。若无法获取到14天前的记录,则判定为红码;若14天记录中有出现不健康状态,则判定为红码;若14天记中有相邻两条记录间隔超过24小时,判定为红码;其余为绿码。
获取UserInfoDO:以UserDO作为参数调用user_info表的dao,获取到UserInfoDO。若获取到的UserInfoDO为空,则在hint中设置相关提示并返回。
使用‘url?qrCode=随机字符串'这串字符串生成二维码图片存储在相应路径中(文件名为那串随机字符串)。
向QRCodeManager中插入一条key为随机字符串,value为QRCodeInfo对象的键值对。
--将二维码图片的路径封装进QRCodeDTO,返回给controller。
dao:
--接收UserDO作为参数,查询该用户的所有健康申报记录,返回DailyReportDO数组。
user_info表的dao:
--接收UserDO作为参数,查询该用户的实名认证信息,返回UserInfoDO。
6. 获取注册表单
直接forward到注册表单jsp
7. 注册
===============================================================================
插:RegisterDTO
String userName;
String password;
String hint;
===============================================================================
controller:
--将从request中获取到的账号、密码封装进RegisterDTO
--将RegisterDTO作为参数,调用service层的方法,将信息存进user表中,并获取service层返回的RegisterDTO
--若service层返回的RegisterDTO中的hint为空,则注册成功重定向到注册成功页面;否则,将RegisterDTO放进request的attribute中,forward给注册表单jsp展示错误信息。
service:
--检查从controller层接收到的RegisterDTO。若用户名重复,则在RegisterDTO中设置相应hint返回;若无问题,则封装成UserDO
--以UserDO作为参数,调用user表的dao方法,将数据存入数据库中
--将DTO返回给controller层
dao:
--接收UserDO作为参数,将相应数据存储进数据库
8. 获取登录表单
直接forward到登录表单jsp
9. 登录
===============================================================================
插:LoginDTO
String userName;
String password;
String hint;
===============================================================================
controller:
--将从request中获取到的账号、密码封装进LoginDTO
--将LoginDTO作为参数,调用service层的方法,获取service层返回的LoginDTO
--若service层返回的LoginDTO中的hint为空,则登录成功重定向到主页;否则,将LoginDTO放进request的attribute中,forward给登录表单jsp展示错误信息。
service:
--将从controller层接收到的LoginDTO中的userName封装成UserDO
--以该UserDO为参数,调用user表的dao,查找用户,获取到完整的UserDO。若获取到的UserDO为空或UserDO中的密码和LoginDTO中的密码不一致,则在LoginDTO的hint中设置相应提示信息;
--将LoginDTO返回给controller层
user表的dao:
--接收UserDO作为参数,查询数据库,返回完整的UserDO
10. 获取实名认证表单
controller:查看HttpSession中key为user的键值对,值是否为空。
若为空,则forward到登录表单jsp;若不为空,则forward到实名认证表单jsp。
11. 实名认证
===============================================================================
插:UserInfoDTO
...some personal information...
String hint;
===============================================================================
controller:
--将从request中获取到的实名认证信息封装成UserInfoDTO。为获取到的图片生成独一无二的id字符串作为图片的文件名,存储在以该用户userName命名的文件夹下(userName从HttpSession中的UserDO中获得)。DTO中对应记录的则是图片的路径。
--将DTO作为参数,调用service层的方法,获取service层返回的DTO
--若service层返回的UserInfoDTO中的hint为空,则提交成功重定向到提交成功页面;否则,将UserInfoDTO放进request的attribute中,forward给实名认证表单jsp展示错误信息。
service:
--检查从controller层接收到的UserInfoDTO。如有问题,则设置相应hint返回;若无问题,则加上checked=0和user_id后封装成UserInfoDO
--从HttpSessin中获取到UserDO,以UserDO为参数调用user_info的dao获取到UserInfoDO。若UserInfoDO不为空,表示该用户之前提交过实名认证数据,则调用相应dao将原有数据改为当前的UserInfoDO中的数据(checked字段设为0,需重新审核);若UserInfoDO为空,则表示该用户并未提交过实名认证数据,则调用相应dao添加该UserInfoDO进数据库。
--将UserInfoDTO返回给controller层
dao:
--接收UserInfoDO作为参数,操作数据库,将数据存进数据库中或修改原有数据
12. 获取核酸检测申报表单
controller:查看HttpSession中key为user的键值对,值是否为空。
若为空,则forward到登录界面jsp;若不为空,则forward到核酸检测申报表单jsp。
13. 核酸检测申报
controller:
--从request处获取到核酸检测图片和检测日期信息。为检测图片生成独一无二的id字符串作为图片的文件名,存储在以该用户userName命名的文件夹下(userName从HttpSession中的UserDO中获得)。将图片路径和检测日期封装成DTO
--将DTO作为参数,调用service层的方法,存储申报信息,获取service层返回的DTO
--若service层返回的DTO中的hint为空,则提交成功重定向到提交成功页面;否则,将DTO放进request的attribute中,forward给实名认证表单jsp展示错误信息。
service:
--检查从controller层接收到的DTO。若有问题,则在hint中设置相关提示返回。若无问题,则加上checked=0和user_id后将其封装成DO
--将UserDO作为参数,调用nat_report表的dao方法,查询是否存在该用户的核酸检测申报记录。若有,则对其进行修改;若无,则将DO插入。
--将DTO返回给controller层
dao:
--将接收到的DO存储进数据库或对已存数据进行修改
14. 出示核酸检测二维码
controller:
--无接收参数
--调用service层的方法,获取service层返回的QRCodeDTO
--将DTO放进request的attribute中。若DTO中的hint为空,则forward到二维码展示页面进行二维码展示;否则,forward到错误提示页面展示错误信息。
service:
--无接收参数
--将从HttpSession中获取的UserDO作为参数(为空则在hint中设置相关提示并返回),调用nat_report的dao方法,获取到对应该用户的唯一一条核酸检测申报记录。若查询不到对应记录,则为红码;若查询到的记录的checked=0,则为红码;若checked=1但记录的日期与当前时间的差大于5天,则为红码;若checked=1且记录的日期与当前时间的差小于5天,则为绿码。
获取UserInfoDO:以UserDO作为参数调用user_info表的dao,获取到UserInfoDO。若获取到的UserInfoDO为空,则在hint中设置相关提示并返回。
使用‘url?qrCode=随机字符串'这串字符串生成二维码图片存储在相应路径中(文件名为那串随机字符串)。
向QRCodeManager中插入一条key为随机字符串,value为QRCodeInfo对象的键值对。
--将二维码图片的路径封装进QRCodeDTO,返回给controller。
dao:
--接收UserDO作为参数,查询该用户的所有健康申报记录,返回NATReportDO。
user_info表的dao:
--接收UserDO作为参数,查询该用户的实名认证信息,返回UserInfoDO。
项目后台功能:
1. 查询二维码
微信扫描二维码后,将会从二维码图片中解析出‘url?qrCode=字符串'并访问。
服务端处从request中获取到qrCode参数中的字符串。在QRCodeManager中查找是否存在以该字符串为key的二维码。若存在,则获取该key对应的QRCodeInfo对象,将里面的信息封装成DTO,forward给jsp显示。展示的信息有,二维码颜色信息、二维码类型信息、实名认证信息,其中实名认证信息中包含了其实名是否通过审核的信息;若不存在,则返回相应提示。
2. 发送提醒邮件
使用ScheduledThreadPoolExecutor去定时重复执行任务。每隔一段时间就去数据库中查找出每个用户的最后一次申报记录,用当前时间减去该用户最后一次申报的时间,若超过20小时,则发送提醒邮件给该用户。
因为提交的Runnable类需要执行连接数据库进行查询的操作,其需要依赖Service bean。因此,我们将该Runnable类交给spring管理,让spring去注入相关依赖。具体做法即,将该类注解为Component,并配置springmvc.xml去扫描到该类,使得该类得以被放进容器中。当容器(即AppliationContext)被创建时,将会去创建该类的单例对象,并将其依赖的Service bean注入。
为了启动该定时重复执行的任务,我们创建了一个Context监听器,并将其在配置文件中进行注册。当Context创建时,将会触发监听器中的相应方法。该方法会创建一个ScheduledThreadPoolExecutor对象,并调用其scheduleAtFixedRate(myRunnable, t1, t2)方法。其中,myRunnable对象由applicationContext.getBean("myRunnable")方法从容器中取出。
Runnable的run方法将会调用相应的Service层方法(不需参数),以获取到每个用户的最后一次申报记录的DailyReportDTO,并找出超过20小时未进行新的申报的用户,对他们发邮件进行提醒。对于被调用的service层方法,其会调用daily_report表的相应dao方法(无需参数),以获取到每隔用户的最后一次申报记录的DailyReportDO,并将其封装为DailyReportDTO返回给run方法。对于被调用的dao方法,其会对数据库进行查询,返回相应的DailyReportDO。
3. 对实名认证、核酸检测证明进行审核
获取到user_info表和nat_report表中所有checked=0的记录。审核好后提交,改变对应记录的checked字段。
4. 对需进行申报的国家及其所需的申报类型进行更新
获取到countries表中所有的记录。修改好后提交,改变对应国家的type字段。