WEB综合案例
学习目标:
目标1:能够说出案例的系统架构和技术架构
目标2:能够说出案例的大致需求
目标3:完成案例工程结构和页面结构的搭建
目标4:完成企业模块相关功能的业务开发
目标5:完成部门模块相关功能的业务开发
目标6:完成用户模块相关功能的业务的开发
1. 概述
1.1 案例介绍
黑马面面是一款面向程序员的面试刷题系统,服务于学员培训学习完毕后的复习问题,通过大量刷题,提高企业面试题的熟知度,辅助学员顺利完成求职面试。
注意:我们在本课程中只做黑马面面系统的一部分,对有些功能进行了微缩改造,
黑马面面本原始系统又分为三个子系统:后台系统(题目录入),前台系统(答题),手机端(在本课程中不涉及),以下是各子系统核心的功能介绍
后台系统:试题的录入
前台系统:会员刷题
手机端:会员刷题(常用/主流)
1.2 系统架构
接下来在做之前,我们就要说说这套案例制作的时候应该采用怎样的一种结构进行搭建?同时采用什么样的技术进行实现。那么首先我们先聊一聊整个项目的系统架构。
- 对于整个系统来说,它分成两块,一个是后台系统,一个是前台系统。那么我们在访问后台系统的时候,是通过浏览器来进行访问,最终把我们的数据存入到我们的数据库端。
- 记得一点,我们从后台系统录入的题目数据,最终会被前台系统使用,所以说前后台系统,他们在数据这一端上来说是进行共享的。也就是前后台系统用的基础数据是同一组。
- 那么前台系统是通过手机端来进行刷这个题,那么我们在这里边呢,不做手机端的,我们也做浏览器的,这就是它的一个整体的结构,你要先了解。
- 那么对于后台系统来说,开发的时候,我们采用三层架构的形式开发,分为表现、业务、数据。
- 表现层负责数据的收集以及回显,业务层负责业务逻辑处理,数据层负责与数据库打交道。
- 那么对于前台系统来说呢,它仍然是这样的,只不过他们之间用的技术有差别。那么都有哪些差别呢?
- 接下来咱们就要来说一下技术架构!
1.3 技术架构
对于后台系统与前台系统,我们分成五个层面来介绍他们的产品。分别是页面端的技术,也就是我们的前端技术了,以及controller、service、Dao、DB。
详情见下图:
- AdminLTE:一个前端框架,提供了很多友好的主题样式,动态功能效果,可直接使用,非常方便
- 类似于Element-UI
- zTree:一个前端插件,提供了树状结构组件
- POI:数据报表工具,可用于报表导出
1.4 需求分析
-
刷题是整个项目的核心功能,那么试题一定是我们的核心。
- 对一道题来说,体型会多种多样,这次我们以最复杂的选择题来演示。选择题一般由题目与选项构成,题目与选项是一对多的关系。
- 在上图中,我们以线和圆点来表述它们之间的关系。没有圆点的是“一”方,有圆点的是“多”方。
- 继续看,试题一定有归属的学科,比如你Java的同学做python的题,其实意义不大对吧。
- 题目和学科能直产生一对多的关系吗?一个学科下其实分了很多的东西,比如Java下分Java基础、JavaWeb等等,所以在学科和试题之间,需要有个目录。(相当于每道题都有一个小分类和大分类)
- 假如你现在想去一个企业,是不想想看看这个企业以前都出些什么面试题呀。那就需要一个企业的模块了。一个企业与试题之间,也是一对多的关系。
-
试题是谁录入系统呢?
- 需要有用户模块,那肯定是操作系统的人,对于这个用户来说,并不是所有人都能录入,所以需要约定一个部门。
- 然后,那是部门中所有人都需要录入么?这样就涉及到了一个权限的问题了,我们说你这个用户有一种角色,就能拥有录入试题的权限!所以在用户与角色之间形成一个多对多的关系。
- 这个人分配角色了就能录试题了吗?不,还需要一个叫模块的东西。就是这个系统中一共有多少种操作?在我们系统中有一个模块叫做录入试题的模块,有一个模块叫审核试题的模块,是这个角色能操作这个模块儿,所以这个用户才能执行这项操作。模块与角色之间也是一个多对多的关系。
- 录入完了就能直接用么?不能,万一你录的题有问题呢?所以一定要有一个审核机制。对于所有的操作,我们都需要有一个日志来记录了,所以还要有一个日志的东西。
-
接下来要开始答题 ,那谁来答题呢?,会员。
- 所以我们要有一个会员的模块。会员就直接做题吗?做题应该是以试卷的形式呈现。作为一个会员,登录以后,你要去做一套卷子,而不是做一道题,当然你说能不能做单个题,可以,可以把单个题理解为这个试卷就一道题。会员与试卷是一对多的关系。
- 那试卷就与我们的试题直接产生关系么,不需要。我们每一个试卷生成以后。都需要把这个题给做出来,你做出来以后,除了有题目以外,试卷中还得有你做题的答案。
- 所以说试卷中会保存一个试卷的答题明细,这个地方试卷对答题明细是一个一对多的关系。其实答题明细中本身就有试题的ID,因此我们这里用试卷与答题明细对试题进行关联。
-
总结一下,左边这块是属于后台系统,负责保障录入试题的。右边这块属于前台系统,负责学员的刷题功能。
-
相关的表都有固定前缀
- ss:是项目的系统(system)相关的表,也就是试题角色用户等表
- st:是试题相关的表
- tr:是前台相关的一些表
- 表
1.5 课程计划安排
2. 环境搭建
2.1 工程结构搭建
创建工程的要求,及注意点:
-
创建maven工程(web工程)
-
导入项目依赖的坐标(资源)
-
补全目录结构
|-----src
|-------main
|------------java
|------------resources
|------------webapp
|-------test
|-------------java
|-------------resources
- 创建三层架构开发的包层次结构(main/java下边的包结构)
domain
dao
service
web
controller
filters
utils
factory
-
创建模块:mm
-
将pom.xml里边的内容先清空,只留根元素和项目本身的坐标信息
-
在pom.xml文件中添加相关坐标:
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <!--mybatis_--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.3</version> </dependency> <!--分页插件--> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.1.2</version> </dependency> <!--mysql--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.46</version> </dependency> <!--druid数据源--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.21</version> </dependency> <!--junit--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!-- servlet3.0 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <!--jsp--> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.3.3</version> <scope>provided</scope> </dependency> <!--bean-utils--> <dependency> <groupId>commons-beanutils</groupId> <artifactId>commons-beanutils</artifactId> <version>1.9.4</version> </dependency> <!--apache工具包--> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.9</version> </dependency> <!--jstl--> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!--jackson--> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.9.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.9.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.0</version> </dependency> <!--文件上传--> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.1</version> </dependency> <!--POI--> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>4.0.1</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>4.0.1</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml-schemas</artifactId> <version>4.0.1</version> </dependency> </dependencies> <build> <plugins> <!--tomcat插件--> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.1</version> <configuration> <port>80</port> <path>/</path> </configuration> </plugin> </plugins> </build>
-
创建相关目录结构,包结构,如下
删除:webapp\WEB-INF 下的三个配置文件,包括web.xml,我们用注解的方式添加servlet路径
-
创建一个启动配置,然后启动初始项目,运行查看
-
数据库:“资料\工程资源文件\数据库相关\itheima_mm.sql”
2.2 页面结构搭建
管理后台一般有着固定的页面构建模式,我们可以进行快速构建
-
AdminLTE是一款建立在bootstrap和jquery之上的开源模板主题工具,其中内置了多个模板页面,可以用于快速创建响应式Html5网站,并免去了书写大量的 CSS 与 JS 的工作
-
黑马程序员研究院对AdminLTE进行了汉化,并改良了个别功能,方便学员学习使用
-
从今日课程资料中找到:“资料\模块页面\案例结构页面”,将这个文件夹下所有内容----------->copy到项目的webapp目录下,如果有文件需要覆盖则选择覆盖
-
启动项目
-
点击登录
-
我们打开main.jsp发现他的结构如下:这是由AdminLTE构建的网站后台的整体页面布局
3. 企业模块
我们选择一个单表的增删改查功能来进行入门,熟悉开发的模式和流程,因此选择企业模块:
要对企业信息做CRUD,我们需要知道要操作企业的那些字段,
3.1 数据层开发
- 创建实体:com.itheima.domain.store.Company(Company是属于后台管理的包,所以放到store下了)
public class Company {
private String id;
private String name;
private Date expirationDate;
private String address;
private String licenseId;
private String representative;
private String phone;
private String companySize;
private String industry;
private String remarks;
private Integer state;
private String city;
setter/getter省略....
}
- 创建dao:com.itheima.dao.store.CompanyDao
public interface CompanyDao {
int save(Company company); //增删改的返回值是受影响的行数
int delete(Company company);
int update(Company company);
Company findById(String id);
List<Company> findAll();
}
-
从今日课程资料中找到:dao层资源文件---------拷贝配置文件下的资源(先copy如下三个文件)到项目java\resources目录下
-
在项目resources目录下创建一个目录:com\itheima\dao\store,然后把CompanyDao.xml配置文件放到该目录中
- 注意:目录一级一级创建,不能直接创建com.itheima.dao.store(因为这里是目录,不是包名)
- 映射文件需要与对应的dao的目录一般是一致的,所以创建了这个目录结构
-
从今日课程资料中找到:“资料\dao层资源文件\工具类”----------拷贝工具类下的资源到项目中的utils包和factory包下;注意别放错位置
- TransactionUtil.java:事务工具类
- MapperFactory.java:sqlSession初始化,获取sqlSession,获取代理类
3.2 业务层开发
-
业务层基础功能:
- 增
- 删
- 改
- 查单个
- 查全部
- 分页查(分页插件)
-
接下来,我们依次来实现:
- 创建业务层接口:com.itheima.service.store.CompanyService
public interface CompanyService {
/**
* 添加
* @param company
* @return 注意增删改在业务层的返回值都改为void了,因为这些操作的结果一般不会往表现层传递
*/
void save(Company company);
/**
* 删除
* @param company
* @return
*/
void delete(Company company);
/**
* 修改
* @param company
* @return
*/
void update(Company company);
/**
* 查询单个
* @param id 查询的条件(id)
* @return 查询的结果,单个对象
*/
Company findById(String id);
/**
* 查询全部的数据
* @return 全部数据的列表对象
*/
List<Company> findAll();
/**
* 分页查询数据
* @param page 页码
* @param size 每页显示的数据总量
* @return
*/
PageInfo findAll(int page,int size);
}
- 创建业务层实现类:com.itheima.service.store.impl.CompanyServiceImpl
public class CompanyServiceImpl implements CompanyService {
@Override
public void save(Company company) {
SqlSession sqlSession = null;
try{
//1.获取SqlSession (获取sqlsession和代理接口,都通过MapperFactory来处理)
sqlSession = MapperFactory.getSqlSession();
//2.获取Dao
CompanyDao companyDao = MapperFactory.getMapper(sqlSession,CompanyDao.class);
//id使用UUID的生成策略来获取
String id = UUID.randomUUID().toString();
company.setId(id);
//3.调用Dao层操作
companyDao.save(company);
//4.提交事务 (事务相关的操作,我们都使用事务工具类的方法)
TransactionUtil.commit(sqlSession);
}catch (Exception e){
TransactionUtil.rollback(sqlSession);
throw new RuntimeException(e);
//记录日志
}finally {
try {
TransactionUtil.close(sqlSession);
}catch (Exception e){
e.printStackTrace();
}
}
}
@Override
public void delete(Company company) {
SqlSession sqlSession = null;
try{
//1.获取SqlSession
sqlSession = MapperFactory.getSqlSession();
//2.获取Dao
CompanyDao companyDao = MapperFactory.getMapper(sqlSession,CompanyDao.class);
//3.调用Dao层操作
companyDao.delete(company);
//4.提交事务
TransactionUtil.commit(sqlSession);
}catch (Exception e){
TransactionUtil.rollback(sqlSession);
throw new RuntimeException(e);
//记录日志
}finally {
try {
TransactionUtil.close(sqlSession);
}catch (Exception e){
e.printStackTrace();
}
}
}
@Override
public void update(Company company) {
SqlSession sqlSession = null;
try{
//1.获取SqlSession
sqlSession = MapperFactory.getSqlSession();
//2.获取Dao
CompanyDao companyDao = MapperFactory.getMapper(sqlSession,CompanyDao.class);
//3.调用Dao层操作
companyDao.update(company);
//4.提交事务
TransactionUtil.commit(sqlSession);
}catch (Exception e){
TransactionUtil.rollback(sqlSession);
throw new RuntimeException(e);
//记录日志
}finally {
try {
TransactionUtil.close(sqlSession);
}catch (Exception e){
e.printStackTrace();
}
}
}
@Override
public Company findById(String id) {
SqlSession sqlSession = null;
try{
//1.获取SqlSession
sqlSession = MapperFactory.getSqlSession();
//2.获取Dao
CompanyDao companyDao = MapperFactory.getMapper(sqlSession,CompanyDao.class);
//3.调用Dao层操作
return companyDao.findById(id);
}catch (Exception e){
throw new RuntimeException(e);
//记录日志
}finally {
try {
TransactionUtil.close(sqlSession);
}catch (Exception e){
e.printStackTrace();
}
}
}
@Override
public List<Company> findAll() {
SqlSession sqlSession = null;
try{
//1.获取SqlSession
sqlSession = MapperFactory.getSqlSession();
//2.获取Dao
CompanyDao companyDao = MapperFactory.getMapper(sqlSession,CompanyDao.class);
//3.调用Dao层操作
return companyDao.findAll();
}catch (Exception e){
throw new RuntimeException(e