本文介绍SSM整合项目:CRM客户关系管理系统。
1. 项目介绍
客户关系管理系统正是为企业建立一个客户信息收集、管理、分析和利用的信息系统。以客户数据的管理为核心,记录企业在市场营销和销售过程中和客户发生的各种交互行为以及各类有关的活动状态,提供各类的数据模型,为后期的分析和决策提供了大力的支持。
本项目需要掌握以下技能点:
- JavaSE:
- Java基本语法
- 集合
- IO流
- 多线程等等
- JavaWeb:
- HTML
- CSS
- JavaScript
- JQuery
- AJAX
- Servlet
- JSP
- 数据库:
- MySQL
- 框架:
- Spring
- SpringMVC
- MyBatis
- 工具:
- Maven
- Git
- JDK动态代理
总的来说,项目是一套完整的功能。除了需要掌握上面的技能之外,还需要有一个全局的意识,这是非技术层面的,而是项目逻辑层面的。需要合理地分配代码逻辑,以便后期维护。
其实上面的代码技能是最基本的,而项目软件则是充满灵魂的代码,所以说,这部分项目是为了给代码注入灵魂,学习项目开发过程中的思想。
1.1 技术架构
首先具体介绍一下,本项目需要用到的技术。在描述技术架构的时候,应该按照项目开发过程中的逻辑来陈述。即将技术以实际需求的形式来展示。
和三层架构类似,一般情况下,项目开发分为四层:视图层(View)、控制层(Controller)、业务层(Service)和持久层(Dao/Mapper)。各层的技术分别如下所示:
视图层:目的是展示数据,和用户交互
HTML、CSS、JavaScript、Jquery(封装了js)、BootStrap(封装了html、css、js,类似的还有ext、vue、easyUI等等)
控制层:目的是接收请求,并根据用户请求来选择不同的业务代码来处理具体请求,并将处理结果返回给视图层。即控制业务处理流程。
Servlet、JSP、SpringMVC(封装了Servlet,类似的还有webwork、struts1、struts2等等)
业务层:目的是处理业务逻辑,为用户提供服务。
JavaSE
持久层:目的是操作数据库,处理数据
MySQL、JDBC、MyBatis(类似的还有Hibernate)
其中,为管理整体的逻辑,需要将上述四层整合起来(也可被称为整合层),需要用到Spring框架技术(类似的还有ejb、corba等等)。
1.2 软件开发生命周期
本小节回顾一下软件开发的生命周期(可以参考一下本科阶段的《软件工程》这门课程),主要介绍软件开发的几个阶段。
一般情况下,软件公司中和软件开发相关的有如下几个组织:
- 市场部:负责拉项目
- 产品部:负责设计项目功能
- 研发部(美工、程序员、DBA):负责开发项目
- 测试部:负责测试
- 实施部:负责部署
- 运维部:负责后期运行维护
软件开发的生命周期有如下几个阶段:
- 招标(甲方写标书,乙方投标竞争拿到项目)
- 可行性分析(是否能做、是否有效益等等)
- 需求分析(产品经理分析并设计大概的需求)
- 分析与设计(宏观分析如何功能实现;代码分层、功能类、数据表等设计;选择什么软硬件服务器等等;架构师、技术总监、项目经理负责)
- 搭建开发环境(添加所需要的jar包、配置文件、静态页面、公共工具类等等;项目经理负责)
- 编码实现(编码实现功能;程序员负责)
- 测试(黑盒测试、白盒测试等等;测试人员负责)
- 试运行(试运行;实施部门人员负责)
- 上线正式运行(正式上线交付;实施部门人员负责)
- 运维(后期运行维护;运维人员负责)
- 文档编写(贯穿上述各个阶段)
2. 核心业务介绍(需求分析)
2.1 项目简介
本项目是客户关系管理系统(Customer Relationship Management),是一个企业级应用,为企业内部使用,要求不是很高,可以采用SSM框架实现。主要管理客户关系相关的资源。如贸易公司、保险公司等。在市场、销售、服务等各个环节中维护客户关系。CRM项目的宗旨:增加新客户、留住老客户,把已有客户转化为忠诚客户。
本CRM项目是给进出口贸易公司来使用的。
2.2 核心业务
主要有以下几类功能:
系统管理功能(保证业务管理功能正常、安全地运行)
比如用户登录、权限验证、退出等等;另外还有系统设置等等。
业务管理功能(处理业务数据)
比如添加用户、添加贸易信息等等。主要有以下几个功能:
- 市场活动(添加、删除活动等等,为了拉客户)
- 线索(根据举办的市场活动,筛选出来潜在客户)
- 客户和联系人(存储上面的潜在客户)
- 交易(发展潜在客户,使其成为真正客户)
- 售后回访(为客户提供售后服务,客服与其维持联系;系统应该主动提醒客服,不要忘记联系重要客户)
- 统计图表(统计各个阶段的数据情况)
3. 物理模型设计
3.1 数据表
CRM项目用到的数据表:
用户表:tbl_user
主要用于系统功能,主要用于登录。
数据字典值:tbl_dic_val
主要用于系统功能,主要用于下拉列表的选项值。
数据字典类型表:tbl_dic_type
主要用于系统功能,因为有多个下拉列表,每个下拉列表的选项值不同。为了区分,为每个下拉列表设置一个类型。本张表就是为了存储下拉类型。这样,选取了特定的下拉类型,就能从
tbl_dic_type
中选择出特定类型的值来显示。市场活动表:tbl_activity
主要用于业务功能,存储开展的市场活动。
市场活动备注表:tbl_activity_remark
主要用于业务功能,对市场活动进行备注评价意见等等。
线索表:tbl_clue
主要用于业务功能,开展的市场活动主要是为了拉取客户,因此本张表主要是存储潜在客户的相关信息。
线索备注表:tbl_clue_remark
主要用于业务功能,对线索进行备注评价意见等等。
线索和市场活动关联关系表:tbl_clue_activity_relation
主要用于业务功能,二者属于多对多的关系,一场活动可和产生多个线索,一个线索可由多个活动产生(一个人参加了多场活动,且都留下了联系方式)
客户表:tbl_customer
主要用于业务功能,线索表示潜在的客户,可发展为真正的客户。
客户备注表:tbl_customer_remark
主要用于业务功能,对客户进行备注评价意见等等。
联系人:tbl_contacts
主要用于业务功能,联系人由潜在的客户发展而来。
联系人备注表:tbl_contacts_remark
主要用于业务功能,对联系人进行备注评价意见等等。
联系人和市场活动关联关系表:tbl_contacts_activity_relation
主要用于业务功能,二者属于多对多的关系,联系人和线索类似,都由活动产生。
交易表:tbl_tran
主要用于业务功能,客户有了交易意愿,进行交易
交易备注表:tbl_tran_remark
主要用于业务功能,对业务进行备注评价意见等等。
交易历史表:tbl_tran_history
主要用于业务功能,对交易的谈判历程等进行记录。
任务表:tbl_task(暂时不做这个功能)
主要用于业务功能,给客户使用,客服依据此表,进行回访。
数据表中的主键:推荐使用和业务无关的字段做主键,因为业务数据可能会变,主键也就跟着变,这是不合理的,所以尽量将其不作为主键。
主键字段的类型和长度由主键值的生成方式来决定,而主键值的生成方式主要有:
- 自增(借助数据库自增主键生成机制),即数值型,长度由数据量决定。(效率较低,在实际开发中使用较少)
- assign,程序员手动生成主键值,写程序生成唯一的值。常见的算法有:hi/low(数值型,长度由数据量决定,使用较少),UUID(字符串型,长度为32位,使用较多),雪花算法等等。
- 共享主键:两张表的数据有关联,且一一对应,因此两张表共享同一个主键。(使用较少)
- 联合主键:由多个字段的类型和长度决定。(使用较少)
数据表中的外键:外键是用来确定表和表之间的关系。表和表之间的关系:
一对多
一张表A中的一条记录对应另一张表B中的多条记录。另一张表B中的一条记录只能对应一张表A中的一条记录。表A就是父表,表B就是子表。比如一个班级,有多个学生,但是一个学生只属于一个班级。
添加数据时,先添加父表记录,再添加子表记录;删除数据时,先删除子表记录,在删除父表记录;查询数据时,可能会进行关联查询(内连接、外连接、等值连接、全连接)。
一对一
一张表A中的一条记录只能对应另一张表B中的一条记录。另一张表B中的一条记录页只能对应一张表A中的一条记录。比如一个公民对应一个驾照。两表采用共享主键或者唯一外键。
多对多
一张表A中的一条记录对应另一张表B中的多条记录。另一张表B中的一条记录页也能对应一张表A中的多条记录。比如学生表、课程表,即一个学生可选择多门课程,一门课程也被多门学生选择。选课表采用学生表和课程表的两个主键作为联合主键。
数据表的日期和时间字段:日期和时间是比较特殊的数据类型,都有特定的格式,数据库中的类型为:
- date:只有日期(年月日)
- time:只有时间(时分秒)
- datatime:日期加时间
Java程序和SQL中的日期时间格式是不通的,所以需要工具类转换,一般情况下都可定义为定长字符串,此时就无需再进行转换了,因为Java和SQL中的字符串是想通的。等到操作时间的时候再格式化。
具体的数据表可到网盘中下载。
4. 搭建开发环境
4.1 IDEA创建项目工程
一般情况下,创建一个新的空Project,用于管理各个模块Module。模块Module是用于具体的功能代码,同类的业务实现放在同一个Module中。这样一个完整的项目(Project)由各个子业务(Module)共同组成。本项目比较简单,只有一个模块。
SSM项目,在创建Module的时候,直接选择Maven中的webapp即可,自动导入Jar包,比较方便。
其中,GroupId可以认为是项目名(Project),ArtifactId可以认为是模块名(Module)
IDEA创建的Maven中的webapp项目,自带的目录结构不全,需要手动补全,如src/main/java
、src/main/resources
、src/test/java
、src/test/resources
。
并修改目录属性。
也要注意,设置项目的编码方式。
4.2 添加jar包依赖
MySQL驱动
1
2
3
4
5
6<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>Druid连接池
1
2
3
4
5
6<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>MyBatis框架
1
2
3
4
5
6<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>Spring依赖
(核心依赖已由SpringMVC间接导入)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<!-- Spring事务依赖 -->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-tx -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.14</version>
</dependency>
<!-- Spring事务依赖(JDBC数据库) -->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.14</version>
</dependency>SpringMVC依赖
1
2
3
4
5
6<!-- SpringMVC依赖(已经包括了Spring核心依赖) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.18</version>
</dependency>Spring AOP依赖
1
2
3
4
5
6<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.15</version>
</dependency>Spring与MyBatis整合依赖
1
2
3
4
5
6<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>添加项目对JSP、Servlet、JSTL等的支持
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25<!-- servlet依赖 -->
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<!-- jsp依赖 -->
<!-- https://mvnrepository.com/artifact/javax.servlet.jsp/jsp-api -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
<scope>provided</scope>
</dependency>
<!-- JSTL -->
<!-- https://mvnrepository.com/artifact/javax.servlet/jstl -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>Jackson插件依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.13.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-annotations -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.13.1</version>
</dependency>poi依赖(用于导出excel等办公文件)
1
2
3
4
5
6<!-- https://mvnrepository.com/artifact/org.apache.poi/poi -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.17</version>
</dependency>fileupload依赖(用于文件上传)
1
2
3
4
5
6<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>Log4j依赖(日志)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.17.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.17.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-jcl -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-jcl</artifactId>
<version>2.17.1</version>
</dependency>
4.3 添加配置文件
创建配置文件,用于后续的各项配置。
mybatis(
mybatis-config.xml
)因为采用了数据库连接池,所以本配置文件就是为了集成所有的SQL映射文件,以及MyBatis的基本配置。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<configuration>
<!-- MyBatis主配置文件,因为采用了数据库连接池,所以数据库连接信息在datasource中 -->
<!-- 本文件的作用就是提供一些基础配置,以及对SQL映射文件进行统一管理 -->
<!-- 指定properties文件的位置,从类路径根下开始找 -->
<properties resource="jdbc.properties"></properties>
<!-- 控制MyBatis的全局行为 -->
<!-- 在主配置文件的configuration标签内,加入如下标签 -->
<settings>
<!-- 控制输出日志,输出为标准控制台输出 -->
<setting name="logImpl" value="STDOUT_LOGGING"></setting>
</settings>
<!-- 映射文件 -->
<!-- <mappers>-->
<!-- <mapper resource="org/hianian/dao/UserDao.xml"/>-->
<!-- </mappers>-->
</configuration>spring(
applicationContext-datasource.xml
、applicationContext.xml
)applicationContext-datasource.xml
文件主要关于数据库,即整合Mybatis(负责扫描mapper):- 配置数据源
- 配置SqlSessionFactory
- mapper注解扫描器配置
- 配置事务管理器
- 配置事务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 将数据库的配置信息写在一个独立的配置文件中,将该配置文件引入到本配置文件中用于数据域对象读取 -->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<!-- 声明数据源DataSource对象以及连接对象Connection,作用是连接数据库,代替MyBatis主配置文件中的数据库连接 -->
<bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="maxActive" value="${jdbc.maxActive}"></property>
</bean>
<!-- 声明MyBatis所提供的SqlSessionFactoryBean类,这个类内部会创建SqlSessionFactory对象。 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 通过之前的MyBatis主配置文件知道,创建SqlSessionFactory对象需要数据库连接信息以及主配置文件 -->
<!-- 连接数据库就是上面的myDataSource,即下面的属性 -->
<property name="dataSource" ref="myDataSource"></property>
<!-- 主配置文件就是resources/mybatis-config.xml -->
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
</bean>
<!-- MyBatis扫描器,创建Dao对象 -->
<!-- 声明Dao对象,使用SqlSession的getMapper(.class)方法来创建对象 -->
<!-- 这个声明类对象不需要id(不需要手动调用),MapperScannerConfigurer会在内部调用getMapper()生成每个dao接口的代理对象。 -->
<!-- 因为需要SqlSession对象,以及接口的class名。所以需要将这两个信息赋给属性 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 指定SqlSessionFactory对象的id,会自动通过SqlSessionFactory对象创建SqlSession对象 -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
<!-- 指定代理对象的接口的class (只需要指定dao接口所在包名即可,会自动扫描接口类型)-->
<!-- MapperScannerConfigurer会扫描指定包中所有的接口,把每个接口都执行一次getMapper()方法,得到每个接口生成的dao对象 -->
<!-- 创建好的dao对象放入到Spring容器中。对象名称就是Dao接口的首字母小写 -->
<property name="basePackage" value="org.hianian.dao"></property>
</bean>
</beans>applicationContext.xml
是Spring总配置文件,用于导入其他Spring子配置文件、扫描Service等等。1
2
3
4
5
6
7
8
9
10
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 将数据库连接池对象导入本文件 -->
<import resource="applicationContext-datasource.xml" />
<!-- 声明service对象,为用户提供服务 -->
</beans>这里为了方便,将Spring配置文件分为上述若干个子文件。
sprinbmvc(
applicationContext-mvc.xml
)该文件主要负责扫描Controller。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- springmvc的配置文件,用于声明Controller和其他web相关的对象-->
<!-- 声明组件扫描器,创建Controller对象 -->
<context:component-scan base-package="org.hianian.controller"></context:component-scan>
<!-- 声明视图解析器,方便资源之间的路径书写 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 声明视图文件的前缀 -->
<property name="prefix" value="/WEB-INF/jsp/"></property>
<!-- 声明视图文件的后缀,即扩展名 -->
<property name="suffix" value=".jsp"></property>
</bean>
<!-- 添加注解驱动(方便返回Object等数据类型以及处理静态资源) -->
<mvc:annotation-driven></mvc:annotation-driven>
</beans>注意,这里的视图解析器是随便写的,后续可能会返回修改。
web.xml
该文件是web项目的核心配置,和上述框架没关系。负责web项目启动的时候一些配置、加载框架配置文件(加载applicationContext.xml监听器、SpringMVC核心调度器)、字符过滤器等等。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!-- 配置Spring监听器,在Tomcat启动时,就创建Spring容器 -->
<!-- Spring相关配置 -->
<!-- 注册Spring监听器 ,并指定自定义的配置文件位置(就是说,创建容器的时候,扫描哪个文件,创建里面的对象)-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- SpringMVC相关配置-->
<!-- 中央调度器 -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>pom.xml
在该文件中设置maven对配置文件的编译选项,即将资源文件编译到指定的类路径目录下面。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29<build>
<resources>
<resource>
<!-- 告诉指定的目录 -->
<directory>src/main/java</directory>
<!-- 拷贝的资源文件类型 -->
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<!-- 设置过滤器,这里不用设置了,上面相当于设置了 -->
<filtering>false</filtering>
</resource>
<!-- 避免上述操作覆盖默认设置,这里将默认设置的目录添加 -->
<resource>
<!-- 告诉指定的目录 -->
<directory>src/main/resources</directory>
<!-- 拷贝的资源文件类型 -->
<includes>
<include>*.*</include>
</includes>
<!-- 设置过滤器,这里不用设置了,上面相当于设置了 -->
<filtering>false</filtering>
</resource>
</resources>
</build>
4.4 添加资源文件
资源文件主要是前端页面以及涉及到的图片、js等等。资源文件可根据不同的功能存放在不同的文件夹下。注意,整个项目分为系统功能和业务功能两部分。
注意,一般情况下,资源文件和WEB-INF
平级,都在webapp目录下,而WEB-INF
里面是不可通过URL访问到。(对于一些重要的页面,需要把页面放到WEB-INF
下面,通过其他页面处理请求Controller(验证账号密码)来转发到该页面,注意,这里不是重定向,因为重定向是用户重新发起请求,而WEB-INF是安全的,不允许外部访问)。
- 为了更加安全,统一将需要保护的页面资源文件放置到
WEB-INF
里面的某个目录下,如pages
。 - 对于js、css等公开数据,可直接放到
webapp
下面。因为这些资源都是页面引用的,如果放到WEB-INF
下面,就会导致在引用的时候,还需要通过Controller来访问,比较麻烦。
目录结构如下所示【为了方便,后续是根据系统功能和业务功能进行建包的】:
5. 功能实现
一个项目比较复杂,有很多个功能。但是应该先做哪些呢?先做没有依赖的功能,也就是独立的功能。首先是业务功能是依赖于系统功能的。因为业务功能需要账号,因此需要先做系统功能。
5.1 系统功能
5.1.1 首页功能
需求分析:访问项目名:
http://127.0.0.1:8080/crm
直接跳转到首页index.html
。分析与设计:由客户端发起请求,Controller接收请求后,通过映射,发现是请求首页,所以直接转发到index.jsp,该JSP在Tomcat运行后生成html,返回给客户端。
编码实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
public class IndexController {
// 注意,因为是转发视图,所以返回值类型是String
// 注意,要修改上面中央调度器url。
public String index(){
// 转发到index.jsp
// 注意,在springmvc配置文件中设置视图解析器
return "index";
}
}将已经设置好的
index.html
资源修改为index.jsp
,其实就是修改一下扩展名即可。注意,还要修改二者的编码方式,因为IDEA默认的html编码方式ISO8859,而对jsp编码方式为UTF-8。所以在修改扩展名之前,一定要先将html文件编码修改为UTF-81
2
3
4
5
6
7
8
9
10
11<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<script type="text/javascript">
document.location.href = "settings/qx/user/login.html";
</script>
</body>
</html>测试:因为项目原型,index.jsp里面的内容是请求登录页面。而我们将登录页面已经放置到了WEB-INF中,因此此时请求转发到index.jsp中,随即请求登录页面。此时还需要对登录页面请求进行Controller控制。
注意,什么时候新建Controller,什么时候在Controller中新建方法?
这取决于,如果两个方法访问的资源是否在同一个目录下,如果在同一个目录下,那么就新建方法。如果不在同一个目录下吗,就新建Controller。即,一个Controller的所有方法,处理后的请求返回的资源均在同一个目录下。
新建的Controller如下所示:
1 |
|
前端修改后的页面为:
1 | <%@ page contentType="text/html;charset=UTF-8" language="java" %> |
注意,返回的页面是服务器编译后的内容,而URL路径则是发生的请求,即Controller中返回的地址。所以说,浏览器中的URL其实和视图解析器没有关系,Controller中的RequestMapping也和视图解析器没关系。
还要注意一点的是,因为我们在前面将所有请求都映射到SpringMVC的中央调度器了,但是此时,也就意味着请求图片、js等也是如此【还需要另外的Controller】。显然这样效率较低,也没必要。因此,需要将其余的请求,也就是找不到Controller【显然图片就不需要Controller】的请求进行特殊处理,参考SpringMVC讲义,有两种方式,一种利用所在服务器的默认处理Handler,一种是利用框架自带的Handler【优先选用这种】。在springmvc配置文件中添加如下配置。
1 | <mvc:annotation-driven></mvc:annotation-driven> |
5.1.2 登录功能
需求分析:用户在登录页面,输入用户名和密码,点击“登录”按钮或者回车,完成用户登录的功能。
- 用户名和密码不能为空。【表单验证】
- 用户名或者密码错误,用户已过期,用户状态被锁定,ip受限等都不能登录成功。【查询数据库】
- 登录成功之后,所以业务页面显示当前用户的名称。【将验证时后台查出来的数据放入到作用域中】
- 实现10天记住密码。【判断是否点击了记住密码,如果有,则将账号密码存储在Cookie中,保存在本机】
- 登录成功之后,跳转到业务主页面。
- 登录失败,页面不跳转,提示信息。
分析与设计:用户在登录页面输入账号和密码,点击登录或者回车键,发送请求到后台Controller【注意,这里是异步请求,因为如果验证失败,那么就在本页面显示账号或密码不正确(局部刷新);如果验证成功,那么就进入到业务页面(全局刷新),因此当既有可能局部刷新也有可能是全局刷新时,只能是异步请求】。那么此时,客户端只能接受到JSON字符串,对于异步刷新是可以的(验证失败,指明原因),但是全局刷新该怎么实现呢?(需要利用异步请求的接收结果来重新发起请求。)
总体来说,客户端发送请求;后台Controller接收请求,并将参数封装为对象,调用Service层处理业务并传参;Service层根据业务调用Dao来获取数据,并根据参数来比对数据,处理业务,最终将结果返回给Controller(上面的流程图的11步应该在Service操作);Controller根据将业务处理结果返回给客户端;客户端根据结果来渲染页面。
注意,前面提到过,对于Controller以及方法,如果方法的返回值的视图在一个目录下,那么这些方法就在一个Controller类中。而对于Service、Mapper(Dao)、实体类等,这些则是依据针对处理的数据表,如果处理的数据属于一张表,那么就在一个类中,否则则新建类。
编码实现:
首先,由于tbl_user表中的字段太多,所以采用MyBatis逆向工程生成实体类、Dao接口以及映射文件最基本的代码。而上面的queryUserByLoginActAndPwd(map)方法需要自己实现。
注意,对于一些常用代码,可抽取成工具类;对于一些规定的编码值,比如登录成功为1,登录失败为0,这些值不能写死,因为后续可能会修改这些值,肯定不能一一修改,应该将其定义为常量类中的常量属性,直接调用即可。
在需求3中,一共有四个作用域:pageContext、request、session、application,那么该将数据放到哪个作用域中呢?
- pageContext:用于在同一个页面不同标签之间传递数据,即从一个标签传递另一个标签【自定义标签底层处理数据】
- request:从发出请求到收到响应之间传递数据,即同一次请求中传递数据。
- session:在一个浏览器不同次请求之间传递数据。【基于Cookie,当Cookie对象消失后,那么session会话也就消失了】,即同一个用户在一段时间内的数据。
- application:服务器层面共享数据,从服务器启动到服务器关闭,即所有用户之间共享的数据。
因此,在同一个用户,一段时间内共享数据,需要放置到session会话域中。
5.1.3 安全退出
注意,这里的退出是安全退出,简单的关闭浏览器等这种方式在非安全环境下并不是安全的【cookie没有清除,session没断开(对服务器压力较大)】。
因此,应该安全退出:清空cookie,销毁session
需求分析:用户在任意的业务页面,点击“退出”按钮,弹出确认退出的模态窗口;用户在确认退出的模态窗口,点击“确定”按钮,完成安全退出的功能:
- 安全退出,清空cookie,销毁session
- 退出完成后,跳转到首页
注意,因为要服务器清空cookie、销毁session,并且跳转到首页【会跳转到登录页面】,所以需要请求Controller。而Controller最终返回的视图是登录页面,所以,和前面的Controller应该是一个,即在里面添加方法即可。
注意,在最终要返回的首页。虽然最终通过请求转发和重定向都能实现,但是请求转发不会改变浏览器中的原始URL,而重定向则会改变。因此,这里应该是重定向比较合适。
代码如下所示:
1 |
|
5.1.4 登录验证
其实前面可以看到,完全可以直接输入controller中的路径来进行访问,而无需登录就可以进入业务页面。这其实是不安全的,需要验证。
需求分析:登录验证,用户访问任何资源文件,都需要进行登录验证。
- 只有登录成功的用户才能访问业务资源。
- 没有登录成功的用户访问业务资源,跳转到登录页面。
因为是对所有资源都进行验证,显然需要一个全局的限制,即过滤器【原生态,功能不够强大】,或者是框架提供的拦截器【对过滤器进行封装之后的工具类,SpringMVC中的HandlerInterceptor接口】。
因为登录成功后,在session中添加了对象数据,因此,只要获取到session对象,判断是否有该数据即可。代码如下所示:
1 |
|
在配置文件中,声明拦截器:
1 | <!-- 配置拦截器 --> |
5.2 业务功能——市场活动
页面切割技术:默认情况下,一个浏览器页面只能显示一个html页面;为了灵活,可以在一个浏览器页面中显示多个html页面,即页面切割技术:
- <frameset>标签和<frame>标签
- <div>标签和<iframe>标签【常用】
5.2.1 创建市场活动
需求分析:创建市场活动:用户在市场活动主页面,点击“创建”按钮,弹出创建市场活动的模态窗口;用户在创建市场活动的模态窗口填写表单,点击“保存”按钮,完成市场创建活动的功能。
- 所有者是动态的(下拉列表,即在显示市场活动主页面时,就从数据库中查询出所有用户并且显示在创建的模态窗口中)。
- 所有者和名称不能为空。
- 如果开始日期和结束日期都不为空,则结束日期不能比开始日期小。
- 成本只能为非负整数。
- 创建成功之后,关闭模态窗口,刷新市场活动列,显示第一页数据,保持每页显示条数不变。
- 创建失败,提示信息创建失败,模态窗口不关闭,市场活动列表也不刷新。
为了方便,前端项目原型使用了模态窗口。
创建市场活动,首先要显示市场活动以及所有者,即从数据库中遍历所需的数据表,将其显示到前端。
显示所有者:
新建保存市场活动:因为模态窗口是主页面的一部分,即需要异步请求。另外,因为需要日期,所以为了统一格式,采用前端日历插件(如bootstrap-datetimepicker)。前端插件使用步骤:
- 引入开发包:.js,.css。下载开发包,拷贝到项目的Webapp目录下,将开发包引入到调用者jsp文件中。
- 创建容器,如input、div等用于显示插件的功能。
- 当容器加载完成之后,对容器调用工具函数,即使用插件的功能。
5.2.2 查询市场活动
这里在点击市场活动时,显示出了主页面。但是此时其实需要显示当前已有的市场活动的,这是就需要检索数据库。另外,所有市场活动,有时候太多,没必要在一个页面中显示,此时就需要分页显示。另外,由于数量太多,此时还需要提供条件查询功能。同时,也许要提供用户自定义显示每页的市场活动条目数。
总体来说,本功能,首先需要检索数据库,查询出所有的市场活动,交给浏览器。而后,浏览器在前端接收到数据之后,似乎可根据用户请求来分页显示、条件查询以及自定义每页的市场活动条目数。【这是自己认为的,但是其他人认为,其实每次都查询数据库就行,而且查询的就是指定页数的数据【条件】,而不是全部的数据;这样客户端浏览器压力较小。为什么要分页查询呢,这是因为分页查询的数据量较小,浏览器可以很容易显示,而如果全部查询之后,一方面,如果数据量较大,显然服务器和客户端占用内存都较大。】
需求分析:查询市场活动:
- 当市场活动主页面加载完成之后,显示所有数据的第一页。
- 用户在市场活动主页面填写查询条件,点击“查询”按钮,显示所有符合条件的数据的第一页,并保持每页显示条数不变。
- 实现翻页功能:
分页可由分页插件【bs_pagination】来实现。
- 在市场活动主页面,显示市场活动列表和记录的总条数;
- 默认每页显示条数:10。
5.2.3 删除市场活动
需求分析:删除市场活动:用户在市场活动主页面,选择要删除的市场活动,点击“删除”按钮,弹出确认窗口;用户点击“确定”按钮,完成删除市场活动的功能。
- 每次至少删除一次市场活动
- 可以批量删除市场活动
- 删除成功之后,刷新市场活动列表,显示第一页数据,保持每页显示条数不变
- 删除失败,提示信息,列表不刷新
注意,要有全选按钮。
补充,因为后续还有针对某个市场活动的备注,所以删除市场活动的同时,也要删除备注表中该市场活动的所有备注记录。
5.2.4 修改市场活动
需求分析:修改市场活动:用户在市场活动主页面,选择要修改的市场活动,点击“修改”按钮,弹出修改市场活动的模态窗口。用户在修改市场活动的模态窗口填写表单,点击“更新”按钮,完成修改市场活动的功能。
- 每次能且只能修改一条市场活动
- 所有者 是动态的
- 表单验证(同创建市场活动)
- 修改成功之后,关闭模态窗口,刷新市场活动列表,保持页号和每页显示条数都不变
- 修改失败,提示信息,模态窗口不关闭,列表也不刷新
注意,修改市场活动可以分成两部分:
- 一部分是根据选中的记录查询数据库在模态窗口显示详细信息。
- 另一部分是针对显示的详细信息,进行修改,更新数据库。
5.2.5 批量导出市场活动
需求分析:批量导出市场活动:用户在市场活动主页面,点击“批量导出”按钮,把所有市场活动生成一个Excel文件,弹出文件下载的对话框。用户选择要保存的目录,完成导出市场活动的功能。
- 导出成功之后,页面不刷新。
注意,这里是批量导出,即需要导出全部数据。流程如下所示:
- 给“批量导出”按钮添加单击事件,发送导出请求
- 查询所有的市场活动
- 创建一个excel文件,并且把市场活动写到excel文件中【无需写入磁盘】
- 把生成的excel文件输出到浏览器。【文件下载,注意,文件下载请求只能是同步请求,因为响应信息是文件,而AJAX解析不了。另外,即使是同步请求,而我们将其显示在下载窗口,因此不会覆盖掉原始页面】
- 用户选择保存目录即可。
文件生成和下载需要插件,如Apache-poi、iText等等,【因为Java的IO只能生成没有格式的文件】
关于办公文档插件使用的基本思想:插件把办公文档的所有元素(比如行、列、字体等等)封装成普通的Java类,程序员通过操作这些类来达到操作办公文档元素的目的。因此,只需要把插件提供的类弄明白即可。这些插件是Jar包,因此可在Maven项目中添加依赖。
poi插件的类:
- 文件——>HSSFWorkbook
- 页——>HSSFSheet
- 行——>HSSFRow
- 列——>HSSFCell
- 样式——>HSSFCellStyle
服务器本地生成文件后。用户点击下载来请求服务器传输文件。这里就是Controller返回值类型为文件类型,可不通过框架来传输,可自己传输,即返回值为void,通过response输出流来返回文件。测试代码如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 /**
* 不借助框架,自己来返回文件流
*/
public void fileDownload(HttpServletResponse response) throws IOException {
// 1.设置响应内容类型,这些文件类型,可查阅协议规定,contentType
response.setContentType("application/octet-stream;charset=UTF-8");
// 2.通过输出流写入文件
// 字符流只能写文本文件,需要字节流
OutputStream writer = response.getOutputStream();
// 浏览器接收到响应信息后,默认是在显示窗口打开响应信息。因此需要设置不让他在显示页面打开【即使打不开,也要调用相关软件】。
// 只有实在打不开的情况,才会激活文件下载程序。
// 我们需要直接让其激活文件下载程序。
// 设置响应头信息
response.addHeader("Content-Disposition", "attachment;filename=myStudent.xlsx");
// 读取要传输的文件,并输出
InputStream inputStream = new FileInputStream("D:\\others\\work\\work_2022_0507\\ip.xlsx");
byte[] buff = new byte[256];
int len = 0;
while((len=inputStream.read(buff)) != -1){
writer.write(buff, 0, len);
}
// 关闭流,对于writer不需要关闭,因为是response创建的,由tomcat自行关闭
inputStream.close();
writer.flush();
}
5.2.6 选择导出市场活动
需求分析:选择导出市场活动:用户在市场活动主页面,选择要导出的市场活动,点击“选择导出”按钮,把所有选择的数据生成一个excel文件,弹出文件下载的对话框。用户选择要保存的目录,完成选择导出市场活动的功能。
- 每次至少选择导出一条记录
- 导出成功之后,页面不刷新
其实这里的选择导出和前面的全部导出类似,只不过,这里有了条件,可根据复选框中的id,向后台传参,查询这些id的市场记录,然后再封装成文件即可。
5.2.7 导入市场活动
需求分析:导入市场活动:用户在市场活动主页面,点击“导入”按钮,弹出导入市场活动的模态窗口。用户在导入市场活动的模态窗口选择要上传的文件,点击“导入”按钮,完成导入活动的功能。
- 只支持.xls
- 文件大小不超过5MB
- 导入成功之后,提示成功导入记录条数,关闭模态窗口,刷新市场活动列表,显示第一页数据,保持每页显示条数不变
- 导入失败,提示信息,模态窗口不关闭,列表也不刷新。
注意,这里虽然可能是插入多条数据,但是因为是多条数据一起插入,即一条SQL语句,所以也无需使用事务。
导入市场活动也需要文件上传。从文件中解析数据,插入到数据库中。
文件上传的表单必须满足三个条件,缺一不可
- 表单组件标签只能用:<input type=”file”>,自动添加单击事件并弹出文件选择框。
- 请求方式只能用:post【参数放在请求体中(可以提交多种格式数据),而get放在请求头中(而且只能文本数据)】
- form表单的编码格式只能用:
multipart/form-data
。【根据HTTP协议的规定,浏览器每次向后台提交参数的时候,都会对参数进行统一编码;默认编码格式为urlencoded
(这种格式只能对文本数据进行编码,即将所有其他类型数据都转换为字符串,然后统一编码)】。enctype=”multipart/form-data”ajax发送文件案例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51 // 给导入按钮添加单击事件
$("#importActivityBtn").click(function () {
// 收集参数,即表单value,注意是文件,所以需要特定的方式拿到文件
var activityFileName = $("#activityFile").val();
// 利用文件名验证是否符合格式要求
var subffix = activityFileName.substr(activityFileName.lastIndexOf(".") + 1).toLocaleLowerCase();
if(subffix != "xls") {
alert("仅支持xls文件");
return;
}
// 拿到文件
var activityFile = $("#activityFile")[0].files[0];
// 验证文件大小,字节
if(activityFile.size > 5 * 1024 * 1024) {
alert("文件不能超过5MB");
return;
}
// 发送异步请求
// 注意,文件需要用FormData对象来传输数据,模拟键值对向后台提交参数
var formData = new FormData();
formData.append("activityFile", activityFile);
$.ajax({
url: 'workbench/activity/fileUpload.do',
data: formData,
processData: false, // 设置提交参数之前,是否统一编码成字符串,为后续URL编码格式做准备
contentType: false, // 设置提交参数之前,是否把所有的参数统一按urlencode编码,
type: 'post',
dataType: 'json',
success: function (data) {
if(data.code == "1") {
alert(data.message);
// 关闭窗口
$("#importActivityModal").modal("hide");
// 刷新列表
queryActivityByConditionForPage(1, $("#demo_page").bs_pagination('getOption', 'rowsPerPage'));
} else {
alert(data.message);
$("#importActivityModal").modal("show");
}
}
});
});
});注意,因为传输的数据是文件,所以后台Controller必须将数据封装成文件。而SpringMVC框架提供了封装后的数据类型MultipartFile,即SpringMVC框架会自动从请求体中将数据封装成该类对象。但是此行为需要一个文件上传解析器,而又因为此行为发生的次数比较少,因此该解析器对象需要在SpringMVC配置文件中手动配置。
1
2
3
4
5
6 <!-- 配置文件上传解析器 -->
<!-- id必须为multipartResolver -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="#{1024*1024*5}" />
<property name="defaultEncoding" value="utf-8" />
</bean>服务器获取到文件之后,需要对文件进行解析,获取到每一条数据,然后将其插入到数据库。解析文件依然用到前面的
apache-poi
插件。
5.2.8 查询市场活动明细
需求分析:用户在市场活动主页面,点击市场活动名称超链接,跳转到明细页面,完成查看市场活动明细的功能。在市场活动明细页面,展示:
- 市场活动的基本信息
- 该市场活动下所有的备注信息
本功能涉及到市场活动表和市场活动备注表两张表。一条市场活动可以有多条备注。但是一条备注只能针对的是一条市场活动。
5.2.9 添加市场活动备注
需求分析:用户在市场活动明细页面,输入备注内容,点击“保存”按钮,完成添加市场备注的功能。
- 备注内容不能为空
- 添加成功之后,清空输入框,刷新备注列表【前端页面追加一条即可,无需后台查询】
- 添加失败,提示信息,输入框不清空,列表也不刷新
5.2.10 删除市场活动备注
需求分析:用户在市场活动明细页面,点击“删除”市场活动备注的图标,完成删除市场活动备注的功能。
- 删除成功之后,刷新备注列表
- 删除失败,提示信息,备注列表不刷新
5.2.11 修改市场活动备注
需求分析:用户在市场活动明细页面,点击“修改”市场活动备注的图标,弹出修改市场备注的模态窗口;用户在修改市场活动备注的模态窗口,填写表单,点击“更新”按钮,完成修改市场活动备注的功能。
- 备注内容不能为空
- 修改成功之后,关闭模态窗口,刷新备注列表
- 修改失败,提示信息,模态窗口不关闭,列表也不刷新
5.3 业务功能——线索
5.3.1 创建线索
需求分析:用户在线索主页面,点击“创建”按钮,弹出创建线索的模态窗口;用户在创建线索的模态窗口,填写表单,点击“保存”按钮,完成创建线索的功能。
- 所有者、称呼、线索状态、线索来源是动态的【下拉列表】
- 表单验证
- 创建成功之后,关闭模态窗口,刷新线索列表,显示第一页数据,保持每页显示条数不变
- 创建失败,提示信息,模态窗口不关闭,列表也不刷新
5.3.2 查询线索(略)
5.3.3 查看线索明细(略)
5.3.4 删除线索(略)
5.3.5 修改线索(略)
5.3.6 关联市场活动(重点)
一条线索可由多条市场活动获得,一条市场活动可产生多条线索。