通过SSM框架实现CMS管理系统。
项目搭建
Maven聚合工程搭建
如果你还不了解maven可以看如下文章:
Maven 聚合工程
工程的整体结构:
edu_home: 做为父工程,其余子工程继承该父工程。
子工程之间的依赖关系:按层拆分
edu_pojo 依赖 common(通用的工具类)
edu_dao 依赖 edu_pojo
edu_service 依赖 edu_dao
edu_api(war工程) 依赖 edu_service
依赖关系建立原则:当前项目中要用到哪个项目的资源,那么当前项目就依赖要用到资源的项目(依赖分为:直接依赖和间接依赖)
:::tips 思考:如何实现按模块划分的聚合工程呢? :::
创建父工程和子工程(module),父工程的pom配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- 父工程 -->
<groupId>com.edu</groupId>
<artifactId>edu_home</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<!-- 聚合工程 -->
<modules>
<module>common</module>
<module>edu_pojo</module>
<module>edu_dao</module>
<module>edu_service</module>
<module>edu_api</module>
</modules>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
</project>
注意edu_api 是一个web项目,整体结构如下:
- 配置子工程的依赖关系
- edu_pojo 依赖 common(通用的工具类)
edu_pojo子工程如何依赖common子工程呢?其实就和我们依赖其他jar包一样,配置dependencies
<dependencies>
<!-- 引入common子工程 -->
<dependency>
<!-- 当前工程的group id -->
<groupId>com.edu</groupId>
<!-- 子工程名 -->
<artifactId>common</artifactId>
<!-- 版本 -->
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
edu_dao 依赖 edu_pojo
<dependencies> <dependency> <groupId>com.edu</groupId> <artifactId>edu_pojo</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies>
edu_service 依赖 edu_dao
<dependencies> <dependency> <groupId>com.edu</groupId> <artifactId>edu_dao</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies>
edu_api(war工程) 依赖 edu_service
<dependencies> <dependency> <groupId>com.edu</groupId> <artifactId>edu_service</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies>
至此,我们项目工程搭建已经完毕了:可以直接看edu_api 它的依赖关系
也可以直接运行:mvn install 如下日志 则我们的聚合工程搭建完毕
父工程的jar包依赖,其他所有的子工程够可以使用依赖的jar包
<properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <!-- 配置jar包的版本号 --> <spring.version>5.1.5.RELEASE</spring.version> <mybatis.version>3.5.4</mybatis.version> </properties> <!-- 锁定jar包的版本 --> <dependencyManagement> <dependencies> <!-- mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>${mybatis.version}</version> </dependency> <!-- spring mvc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <!-- sprin --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-expression</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${spring.version}</version> </dependency> </dependencies> </dependencyManagement> <dependencies> <!-- 持久层依赖 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.37</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.15</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13</version> <scope>test</scope> </dependency> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>4.1.6</version> </dependency> <!-- spring依赖坐标 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.13</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> </dependency> <!-- mybatis整合spring的依赖 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.1</version> </dependency> <!-- springmvc 的依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.2</version> <scope>provided</scope> </dependency> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.8</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.9.8</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.9.0</version> </dependency> <!-- Beanutils --> <dependency> <groupId>commons-beanutils</groupId> <artifactId>commons-beanutils</artifactId> <version>1.8.3</version> </dependency> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.1</version> </dependency> <!-- 解决跨域问题所需依赖 --> <dependency> <groupId>com.thetransactioncompany</groupId> <artifactId>cors-filter</artifactId> <version>2.5</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> </build>
OK,至此我们的项目已经初步搭建完毕了,下面开始进入编码阶段。
SSM整合
了解在实际项目中的整合ssm框架,以及聚合工程如何整合和拆分代码。
POJO层
创建实体类:
com.edu.pojo.Test
```java package com.edu.pojo;
public class Test { private Integer id; private String name; settter/getter…. }
:::tips
注意:所有子工程的包名统一:com.edu.xxx
:::
<a name="dGmQJ"></a>
### DAO层
由于dao层依赖POJO层,所有DAO层可以直接获取到Test的实例。
1. 创建dao接口:`TestMapper`
```java
package com.edu.dao;
import com.edu.pojo.Test;
import java.util.List;
public interface TestMapper {
List<Test> findAll();
}
创建映射配置文件:
com/edu/dao/TestMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.edu.dao.TestMapper"> <select id="findAll" resultType="test"> select * from account; </select> </mapper>
创建spring配置文件
applicationContext-dao.xml
完成spring整合mybatis的配置<?xml version="1.0" encoding="UTF-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd "> <!-- 1. 配置数据源 jdbc.properties --> <context:property-placeholder location="classpath:jdbc.properties"/> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <!-- 2. sqlSessionFactory --> <bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <!-- pojo实体类别名的配置 --> <property name="typeAliasesPackage" value="com.edu.pojo"/> </bean> <!-- 3. mapper映射扫描 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.edu.dao"/> </bean> </beans>
MyBatis的其他配置(驼峰命名、分页插件等等),需要我们创建一个mybatis的核心配置文件
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <settings> <!-- 开启驼峰命名 --> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings> </configuration>
Service层
service层调用dao层的mapper相关代码,所以要引入dao层的依赖,在构建项目工程的时候,我们已经依赖好了。
创建service接口和实现类
@Service public class TestServiceImpl implements TestService { @Autowired private TestMapper testMapper; @Override public List<Test> findAll() { return testMapper.findAll(); } }
service层的配置
applicationContext-service.xml
扫描Service层的service注解,引入DAO层子工程的配置子模块
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
">
<!-- IOC 注解扫描 service -->
<context:component-scan base-package="com.edu.service"/>
<!-- 引入dao子工程的子模块配置 -->
<import resource="classpath:applicationContext-dao.xml"/>
</beans>
Web/Api层
创建controller :
com.edu.controller
@RestController //组合了:@Controller和@ResponseBody @RequestMapping("/test") public class TestController { @Autowired //注入service private TestService testService; @RequestMapping("/findAll") public List<Test> findAll() { List<Test> list = testService.findAll(); return list; } }
配置springmvc核心配置文件
spring-mvc.xml
这里我们不在需要去配置视图解析器了,因为我们的项目是一个前后端分离的项目,只提供接口。
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 扫描controller层的IOC注解 -->
<context:component-scan base-package="com.edu.controller"/>
<!-- mvc注解增强:支持json读写 -->
<mvc:annotation-driven/>
<!-- 视图解析器 暂时不用配置,因为该项目是前后端分离的项目 -->
<!-- 静态资源放行 -->
<mvc:default-servlet-handler/>
</beans>
- spring核心配置文件
applicationContext.xml
在API层是真正加载spring的核心配置文件,所以我们需要把service的配置和dao层的配置都要引入到Api层的配置文件中,由于在service层引入了dao层的配置,所以在api层只需要将service层的配置引入即可,当服务器启动一起进行加载.
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 只需要 引入service层的配置 -->
<import resource="classpath:applicationContext-service.xml"/>
</beans>
web.xml
配置 ```xml <?xml version=”1.0” encoding=”UTF-8”?><filter-name>characterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param>
<filter-name>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern>
<servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-mvc.xml</param-value> </init-param> <load-on-startup>2</load-on-startup>
<servlet-name>dispatcherServlet</servlet-name> <url-pattern>/</url-pattern>
<!-- spring的监听器-加载applicationContext.xml -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!-- 配置跨域过滤器 -->
<filter>
<filter-name>corsFilter</filter-name>
<filter-class>com.thetransactioncompany.cors.CORSFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>corsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
ok,至此Api层的代码编写完成了,那么SSM也整合完毕,配置Tomcat服务器测试一下,我们写的接口。请求:`http://localhost:8080/edu_api/test/findAll`<br />返回json数据,那么就说明我们的项目搭建测试完毕。呼呼呼。。。。<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/375694/1611152578095-d2f37b68-11aa-4fab-9a09-4ae9348abda7.png#align=left&display=inline&height=195&margin=%5Bobject%20Object%5D&name=image.png&originHeight=390&originWidth=526&size=18688&status=done&style=none&width=263)
<a name="Vlpd0"></a>
## 功能开发
<a name="sFWbG"></a>
#### 多条件查询需求
> 根据多个条件进行查询,例如
> 1. 只根据名称查询、只根据状态查询
> 1. 根据名称和状态查询
> 1. 无条件查询-查询所有操作
前端传递参数不确定,后台需要进行逻辑判断。
1. 创建VO类,主要用于接收前台向后台发送过来的参数
```java
/**
* 前台传递参数的实体类
*/
public class CourseVo {
private String courseName;
private Integer status;
//getter/setter...
}
调用service方法,传递VO
@Service public class CourseServiceImpl implements CourseService { @Autowired private CourseMapper courseMapper; @Override public List<Course> findCourseByCondition(CourserVo courserVo) { List<Course> courses = courseMapper.findCourseByCondition(courserVo); return courses; } }
创建mapper映射,调用dao方法,执行SQL
<select id="findCourseByCondition" resultMap="courseMap" parameterType="courseVo"> select * from course <where> <if test="courseName != null and courseName != ''"> and course_name like concat('%',#{courseName},'%') </if> <if test="status != null and status != ''"> and status = #{status} </if> <if test="true"> and id_del != 1 </if> </where> </select>
返回响应的实体类:ResponseResult
/** * 响应的实体类 */ public class ResponseResult { private Boolean success; private String state; private String message; private Object content; //getter/setter... }
编写controller代码
注意参数VO需要添加@RequestBody注解,它会将json参数封装成实体Bean
@RestController
@RequestMapping("/course")
public class CourseController {
@Autowired
private CourseService courseService;
@PostMapping("/findAllCourse ")
public ResponseResult findAllCourse(@RequestBody CourserVo courserVo) {
List<Course> courses = courseService.findCourseByCondition(courserVo);
ResponseResult result = new ResponseResult(true, StateCode.SUCCESS.getCode(), StateCode.SUCCESS.getMsg(), courses);
return result;
}
}
图片上传需求
- springmvc 配置文件解析器
会将传过来的图片封装成Multipart
<!-- 文件解析器 注意 id的值必须是:multipartResolver -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="1048567"/>
</bean>
- controller接口去接收到图片信息,进行数据响应,fileName,filePath,创建map然后存储到响应实体类的content中
参数名称要与请求参数的key一致
@PostMapping("/courseUpload")
public ResponseResult fileUpload(@RequestParam("file") MultipartFile file, HttpServletRequest request) throws IOException {
//1. 判断文件是否为空
if (file.isEmpty()) {
throw new RuntimeException();
}
//2. 获取项目部署路径
//tomcat/webapp/edu_home/
String realPath = request.getServletContext().getRealPath("/");
//截取文件路径:tomcat/webapp/
String path = realPath.substring(0, realPath.indexOf("edu_api"));
//3. 获取文件的原名 jake.jpg
String originalFilename = file.getOriginalFilename();
//4. 生成新的文件名
long millis = System.currentTimeMillis();
//1231321213_.jpg
String fileName = millis + "_" + originalFilename.substring(originalFilename.lastIndexOf("."));
//5. 文件上传
//文件上传的目录
String uploadPath = path + "upload";
File filePath = new File(uploadPath, fileName);
//如果目录不存在 则进行创建
if (!filePath.getParentFile().exists()) {
filePath.getParentFile().mkdirs();
}
//图片就进行了真正的上传了
file.transferTo(filePath);
//6. 将文件名和文件路径返回进行响应
HashMap<String, String> map = new HashMap<>();
map.put("fileName", fileName);
//如何将地址动态写活呢?
map.put("filePath", "http://localhost:8080/upload/" + fileName);
ResponseResult result = new ResponseResult(true, StateCode.SUCCESS.getCode(), StateCode.SUCCESS.getMsg(), map);
return result;
}
:::tips
思考:如何将文件存储路径写成动态的呢?
:::
注意 在启动Tomcat服务器的时候,要把upload文件夹也要添加部署进去,这样才能访问upload目录下的文件
- 接收到图片信息保存到服务器
表与表之间关联的需求
例如:在课程表和老师表两张表,我们在添加课程的时候,也需要添加老师的信息,但是老师的信息需要课程的id,所以就需要先添加课程然后得到课程的id,再去将老师的记录添加到表中。
- 添加好Course课程信息,获取成功插入记录的ID值
- 封装Teacher主要封装CourseId,在将记录真正添加到表中
CourseController 编写代码
@PostMapping("/saveOrUpdateCourse") public ResponseResult saveOrUpdateCourse(@RequestBody CourseVo courseVo) throws InvocationTargetException, IllegalAccessException { courseService.saveCourseOrTeacher(courseVo); ResponseResult result = new ResponseResult(true, StateCode.SUCCESS.getCode(), StateCode.SUCCESS.getMsg(), null); return result; }
CourseService 编写代码
@Service public class CourseServiceImpl implements CourseService { @Autowired private CourseMapper courseMapper; @Override public List<Course> findCourseByCondition(CourseVo courseVo) { List<Course> courses = courseMapper.findCourseByCondition(courseVo); return courses; } @Override public void saveCourseOrTeacher(CourseVo courseVo) throws InvocationTargetException, IllegalAccessException { //1. 封装Course实体类 Course course = new Course(); //将courseVo中的属性值 赋给Course实体中 BeanUtils.copyProperties(course, courseVo); //补全信息:创建时间和更新时间 Date date = new Date(); course.setCreateTime(date); course.setUpdateTime(date); //2. 调用dao 保存课程 courseMapper.saveCourse(course); //3. 获取课程id 封装teacher实体 int courseId = course.getId(); Teacher teacher = new Teacher(); BeanUtils.copyProperties(teacher, courseVo); teacher.setCourseId(courseId); teacher.setCreateTime(date); teacher.setUpdateTime(date); teacher.setIsDel(0); //4. 调用dao 保存讲师信息 courseMapper.saveTeacher(teacher); } }
CourseMapper 编写代码 ```xml
<insert id="saveCourse" parameterType="course">
<!-- 获取新增成功之后的主键id -->
<selectKey resultType="int" order="AFTER" keyProperty="id">
select last_insert_id()
</selectKey>
insert into course(course_name,
brief,
preview_first_field,
preview_second_field,
course_img_url,
course_list_img,
sort_num,
price,
discounts,
sales,
discounts_tag,
course_description_mark_down,
create_time,
update_time)
values (#{courseName}, #{brief}, #{previewFirstField}, #{previewSecondField}, #{courseImgUrl},
#{courseListImg}, #{sortNum}, #{price}, #{discounts}, #{sales}, #{discountsTag},
#{courseDescriptionMarkDown},
#{createTime}, #{updateTime})
</insert>
<!-- 新增讲师信息 -->
<insert id="saveTeacher" parameterType="teacher">
INSERT INTO teacher(course_id,
teacher_name,
POSITION,
description,
create_time,
update_time)
VALUES (#{courseId}, #{teacherName}, #{position}, #{description}, #{createTime}, #{updateTime});
</insert>
<a name="6usKI"></a>
#### 多表关联查询需求
> 例如我们要查询课程信息及关联的老师信息
主要是DAO层的实现:<br />外链查询:
```xml
<!-- 查询课程信息 关联查询讲师信息 -->
<select id="findByCourseId" resultType="courseBo" parameterType="int">
select c.*, t.teacher_name, t.position, t.description
from course c
left join teacher t on c.id = t.course_id
where c.id = #{id}
</select>
嵌套查询:
课程->章节->课时
课程->章节: 1 -> N
章节->课时: 1 -> N
- 根据课程id查询具体的课程信息
- 获取课程的章节信息列表,循环拿到章节id,然后查询关联的课时信息列表
在一方的实体类中,添加多方的list
// 课时集合
private List<CourseLesson> lessonList;
public List<CourseLesson> getLessonList() {
return lessonList;
}
mapper映射文件
<resultMap id="courseSectionMap" type="courseSection">
<id property="id" column="id"/>
<result property="courseId" column="course_id"/>
<result property="sectionName" column="section_name"/>
<result property="description" column="description"/>
<result property="createTime" column="create_time"/>
<result property="updateTime" column="update_time"/>
<result property="isDe" column="is_de"/>
<result property="orderNum" column="order_num"/>
<result property="status" column="status"/>
<collection property="lessonList" ofType="courseLesson"
select="com.edu.dao.CourseLessonMapper.findBySectionId"
column="id">
</collection>
</resultMap>
<!-- 根据课程id查询课时 -->
<select id="findSectionAndLessonByCourseId" resultType="courseSectionMap" parameterType="int">
select *
from course_section
where course_id = #{id}
order by order_num
</select>
<!-- com.edu.dao.CourseLessonMapper -->
<select id="findBySectionId" resultType="courseLesson" parameterType="int">
select *
from course_lesson
where section_id = #{id}
</select>
trim标签的使用
<update id="updateCourse" parameterType="course">
update course
<!-- suffixOverrides会移除最后一个,逗号 -->
<trim prefix="set" suffixOverrides=",">
<if test="courseName != null and courseName != ''">
course_name = #{courseName},
</if>
<if test="brief != null and brief != ''">
brief=#{brief},
</if>
<if test="previewFirstField != null and previewFirstField != ''">
preview_first_field=#{previewFirstField},
</if>
<if test="previewSecondField != null and previewSecondField != ''">
preview_second_field=#{previewSecondField},
</if>
<if test="courseImgUrl != null and courseImgUrl != ''">
course_img_url=#{courseImgUrl},
</if>
<if test="courseListImg != null and courseListImg != ''">
course_list_img=#{courseListImg},
</if>
<if test="sortNum != null and sortNum != ''">
sort_num=#{sortNum},
</if>
<if test="price != null and price != ''">
price=#{price},
</if>
<if test="discounts != null and discounts != ''">
discounts=#{discounts},
</if>
<if test="sales != null and sales != '' or sales==0">
sales=#{sales},
</if>
<if test="discountsTag != null and discountsTag != ''">
discounts_tag=#{discountsTag},
</if>
<if test="courseDescriptionMarkDown != null and courseDescriptionMarkDown != ''">
course_description_mark_down=#{courseDescriptionMarkDown},
</if>
<if test="updateTime != null">
update_time=#{updateTime},
</if>
</trim>
<where>
<if test="id!=null and id != ''">
id=#{id}
</if>
</where>
</update>
分页需求
需要使用到PageHelper的mybatis插件
需要准备的分页数据:
- 总条数
- 每页显示多少条
- 总页数
- 当前页
- 分页数据
PageHelper配置
<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>4.1.6</version> </dependency>
mybatis配置文件中完成配置
<!-- mybatis插件配置 --> <plugins> <plugin interceptor="com.github.pagehelper.PageHelper"> <!-- 指定方言 --> <property name="dialect" value="mysql"/> </plugin> </plugins>
也可以在
applicationContext.xml
整合mybatis中进行配置<!-- 2. sqlSessionFactory --> <bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <!-- applicationContext配置mybatis插件 --> <property name="plugins"> <array> <bean class="com.github.pagehelper.PageHelper"> <property name="properties"> <value>helperDialect=mysql</value> </property> </bean> </array> </property> <!-- 引入mybatis的其他配置 --> <property name="configLocation" value="classpath:mybatis-config.xml"/> </bean> <!-- 3. mapper映射扫描 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.edu.dao"/> </bean>
在Service层使用PageHelper进行分页 :::tips 注意:PageHelper.startPage() 一定要在mapper调用之前,否则无法分页的 :::
@Service public class PromotionAdServiceImpl implements PromotionAdService { @Autowired private PromotionAdMapper promotionAdMapper; @Override public PageInfo<PromotionAd> findAllPromotionAdByPage(PromotionAdVo promotionAdVo) { PageHelper.startPage(promotionAdVo.getCurrentPage(), promotionAdVo.getPageSize()); List<PromotionAd> promotionAdList = promotionAdMapper.findAllPromotionAdByPage(); //分页信息以及分页数据 PageInfo<PromotionAd> pageInfo = new PageInfo<>(promotionAdList); return pageInfo; } }
注解的方式 解决日期格式的问题
可以使用如下的注解方式解决日期格式,而不用在自定义类型转换器了。
import org.springframework.format.annotation.DateTimeFormat; @DateTimeFormat(pattern = "yyyy-MM-dd") private Date startCreateTime; @DateTimeFormat(pattern = "yyyy-MM-dd") private Date endCreateTime;
权限系统
权限:权利和限制,在权限范围内做好自己的事情.
认证:验证用户名密码是否正确的过程
- 授权:对用户所能访问的资源进行控制
不用登录的用户有不同的权利,例如:财务经理针对系统中财务相关模块进行操作,人事经理针对系统中人事模块进行操作。
权限控制基本原理:
ACL(Assecc Control Lists )
ACL 是最早也是最基本的一种访问控制机制,它的原理非常简单:每一项资源,都配有一个列表,这个列表记录的就是哪些用户可以对这项资源执行CURD的操作,当系统视图访问这项资源时,会首先检查这个列表中是否有关于当前用户的访问权限,从而确定当前用户可否执行相应的操作。ACL是一种面向资源的访问控制模型,它的机制是围绕“资源”展开的
基于角色的访问控制RBAC (Role-Based Access Control)
RBAC 是把用户按角色进行归类,通过用户的角色确定用户能否针对某项资源进行某项操作,RBAC相当于ACL最大的优势就是它简化了用户与权限的管理,通过对用户进行分类,使得角色与权限关联起来,而用户与权限变成了间接关联。RBAC模型使得访问控制,特别是对用户的授权管理编程简单和易于维护,被广泛应用。
- 每个登录的用户,可以有多个角色
- 每个角色又可以拥有多个权限(菜单、资源访问)
例子:
:::tips
权限系统经典五张表:用户表、角色表、权限表、用户角色中间表、角色权限中间表。用户表和权限表没有直接关系,是通过角色表进行了间接关联
:::
下面已角色和菜单为例:
角色分配菜单管理:三个主要接口
- 显示了针对当前角色可以添加的菜单信息
- 主要是角色和菜单的关联:将选中的菜单信息和当前角色进行关联。
- 回显当前角色已经关联的菜单
- 查询所有的菜单节点信息。
菜单的自连接查询,菜单分为顶级菜单和子级菜单。
首先查询顶级的菜单,所有的顶级菜单parent_id都为-1
select * from menu where parent_id = -1
查询顶级菜单所关联的子级菜单:
select * from menu where parent_id = 1
基于mybatis嵌套查询完成:
<resultMap id="menuMap" type="Menu">
<id column="id" property="id"></id>
<result column="href" property="href"></result>
<result column="icon" property="icon"></result>
<result column="name" property="name"></result>
<result column="parent_id" property="parentId"></result>
<result column="description" property="description"></result>
<result column="order_num" property="orderNum"></result>
<result column="shown" property="shown"></result>
<result column="created_time" property="createdTime"></result>
<result column="updated_time" property="updatedTime"></result>
<result column="created_by" property="createdBy"></result>
<result column="updated_by" property="updatedBy"></result>
<collection property="subMenuList" ofType="Menu"
select="findAllMenu" column="id">
</collection>
</resultMap>
<!-- 查询所有父子关系的菜单 -->
<select id="findAllMenu" resultMap="menuMap" parameterType="int">
select *
from menu
where parent_id = #{id}
</select>
- 根据角色id查询已经关联的菜单信息
角色表和菜单表有一个中间表,通过中间表关联的roleId 找到menuId
<select id="findMenuIdByRoleId" resultType="java.lang.Integer" parameterType="int">
select menu_id
from role_menu_relation
where role_id = #{roleId}
</select>
- 为角色分配菜单列表
向角色菜单中间表添加记录,接收前端传递过来的菜单id列表,遍历菜单id.插入即可。 但是需要注意,如果该角色之前关联了某些菜单,而在前端操作了不关联了之前某个菜单,又关联了新的菜单,那么点击保存并没有在中间表中去掉之前的关联信息。也就是说我们在向角色菜单中间表添加关联关系时,通常会先把之前的关联关系清空,在添加关联关系。
例如:
- 角色A的原有菜单列表:【1,4,5,6,7,8,12】
- 在前端操作修改菜单列表:【1,5,7,8,11,12】
- 这时候如果后端直接插入到中间表,那么4和6的菜单关联关系并没有去掉 最终变成了:[1,4,5,6,7,8,11,12]
在插入之前,先删除之前的关联关系:
delete from 中间表 where role_id = ?
最终实现代码如下:
dao层的代码实现:
<!-- 根据roleId 删除中间表的关系 --> <delete id="deleteRoleContextMenu" parameterType="int"> delete from role_menu_relation where role_id = #{roleId} </delete> <!-- 关联角色的菜单,为角色分配菜单 --> <insert id="roleContextMenu" parameterType="Role_menu_relation"> insert into role_menu_relation values (null, #{menuId}, #{roleId}, #{createdTime}, #{updatedTime}, #{createdBy}, #{updatedBy}) </insert>
Service层的代码实现:
//事务管理 防止删除后 添加出现异常导致没有添加成功,而且关联关系也删除了 @Transactional(readOnly = false, propagation = Propagation.REQUIRED) @Override public void roleContextMenu(RoleMenuVo roleMenuVo) { //1. 先删除关联关系 roleMapper.deleteRoleContextMenu(roleMenuVo.getRoleId()); //2. 添加关联关系 List<Integer> menuIdList = roleMenuVo.getMenuIdList(); Date date = new Date(); for (Integer menuId : menuIdList) { Role_menu_relation relation = new Role_menu_relation(); relation.setRoleId(roleMenuVo.getRoleId()); relation.setMenuId(menuId); relation.setCreatedTime(date); relation.setUpdatedTime(date); relation.setCreatedBy("system"); relation.setUpdatedby("system"); roleMapper.roleContextMenu(relation); } }
测试:
查询角色管理的菜单信息
- 删除角色操作那么角色和菜单的关联表也要删除相同的角色信息
- 先清空和中间表的关联关系:用户和角色的中间表,角色和菜单的中间表
- 在进行角色表的删除
@Transactional(propagation = Propagation.REQUIRED) @Override public void deleteRole(Integer roleId) { //删除角色和菜单的关联关系 roleMapper.deleteRoleContextMenu(roleId); //删除角色和用户的关联关系 roleMapper.deleteRoleContextUser(roleId); //从角色表删除角色 roleMapper.deleteRole(roleId); }
- 用户登录之后查询角色授权的权限
- 根据用户id,从用户角色中间表查询角色id
根据角色id,查询角色的信息列表进行显示(一个用户有多个角色)
<!-- 查询用户拥有的角色 --> <select id="findUserRoleById" resultType="com.edu.pojo.Role" parameterType="int"> select * from roles r inner join user_role_relation urr on r.id = urr.role_id where user_id = #{id} </select>
给用户分配角色,需要移除之前的关联信息,添加新的角色信息
<!-- 删除用户角色中间表用户关联的数据 --> <delete id="deleteUserContextRole" parameterType="int"> delete from user_role_relation where user_id = #{userId} </delete> <!-- 建立用户和角色的关联关系 --> <insert id="insertUserContextRole" parameterType="User_Role_relation"> insert into user_role_relation values (null, #{userId}, #{roleId}, #{createdTime}, #{updatedTime}, #{createdBy}, #{updatedby}) </insert>
service层业务逻辑实现:注意要进行事务管理,因为涉及到了删除、和多个新增操作,如果出现异常没有统一事务管理会导致数据出现错误
/** * 给用户分配角色 * * @param userRoleVo */ @Transactional(propagation = Propagation.REQUIRED) @Override public void userContextRole(UserRoleVo userRoleVo) { //1. 移除之前关联的角色 userMapper.deleteUserContextRole(userRoleVo.getUserId()); //2. 关联新的角色 // 获取角色id 列表 List<Integer> roleIdList = userRoleVo.getRoleIdList(); if (roleIdList != null && roleIdList.size() > 0) { for (Integer roleId : roleIdList) { User_Role_relation relation = new User_Role_relation(); relation.setUserId(userRoleVo.getUserId()); relation.setRoleId(roleId); Date date = new Date(); relation.setCreatedTime(date); relation.setUpdatedTime(date); relation.setCreatedBy("system"); relation.setUpdatedby("system"); userMapper.insertUserContextRole(relation); } } }
获取用户拥有的权限:动态显示菜单列表
- 通过用户id查询用户角色中间表,查询到具体关联的角色。
- 根据角色查询角色菜单中间表和查询角色资源中间表。
DAO层的编写:
<!-- 查询用户拥有的角色 -->
<select id="findUserRoleById" resultType="com.edu.pojo.Role" parameterType="int">
select *
from roles r
inner join user_role_relation urr on r.id = urr.role_id
where user_id = #{id}
</select>
<!-- 根据角色id 查询顶级菜单 -->
<select id="findParentMenuByRoleId" resultType="Menu" parameterType="list">
select DISTINCT m.* from roles r inner join role_menu_relation rmr on r.id = rmr.role_id
inner join menu m on rmr.menu_id = m.id
where m.parent_id=-1 and r.id in
<foreach collection="list" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</select>
<!-- 根据顶级菜单去查询所分配的子级菜单 且只包括了有权限的所有子菜单id -->
<select id="findSubMenuByPid" resultType="Menu">
select * from menu where parent_id = #{pid} and id in
<foreach collection="contextSubIds" item="subId" open="(" separator="," close=")">
#{subId}
</foreach>
</select>
<!-- 查询有权限的所有子菜单id -->
<select id="findContextSubByRoleId" resultType="java.lang.Integer">
select distinct m.id from menu m join role_menu_relation rmr on m.id = rmr.menu_id
join roles r on rmr.role_id = r.id
where r.id in
<foreach collection="role_ids" item="roleId" open="(" separator="," close=")">
#{roleId}
</foreach>
and m.id not in
<foreach collection="pids" item="pid" open="(" separator="," close=")">
#{pid}
</foreach>
</select>
<!-- 获取用户关联的资源权限信息 -->
<select id="findUserContextResourceByRoleId" resultType="com.edu.pojo.Resource" parameterType="list">
select * from resource r inner join role_resource_relation rrr on r.id = rrr.resource_id
inner join roles r2 on rrr.role_id = r2.id
where r2.id in
<foreach collection="list" item="roleId" open="(" separator="," close=")">
#{roleId}
</foreach>
</select>
Service 层的编写:
@Override
public ResponseResult getUserPermission(Integer userId) {
//1. 根据用户id 查询用户所拥有的角色
List<Role> roles = userMapper.findUserRoleById(userId);
//2. 获取角色id 保存list集合中
ArrayList<Integer> roleIds = new ArrayList<>();
for (Role role : roles) {
roleIds.add(role.getId());
}
//3. 获取角色分配的顶级菜单信息
List<Menu> parentMenus = userMapper.findParentMenuByRoleId(roleIds);
List<Integer> pids = new ArrayList<>();
for (Menu parentMenu : parentMenus) {
pids.add(parentMenu.getId());
}
//5. 查询有权限的所有子菜单id
List<Integer> subIds = userMapper.findContextSubByRoleId(pids, roleIds);
//6. 查询所在父菜单 分配的子级菜单
for (Menu parentMenu : parentMenus) {
List<Menu> subMenus = userMapper.findSubMenuByPid(parentMenu.getId(), subIds);
parentMenu.setSubMenuList(subMenus);
}
//7. 查询所有的资源信息
List<Resource> resources = userMapper.findUserContextResourceByRoleId(roleIds);
HashMap<String, Object> map = new HashMap<>();
map.put("menuList", parentMenus);
map.put("resourceList", resources);
ResponseResult responseResult = new ResponseResult(true, StateCode.SUCCESS.getCode(), StateCode.SUCCESS.getMsg(), map);
return responseResult;
}
Controller层的编写:
@GetMapping("getUserPermission")
public ResponseResult getUserPermission(HttpServletRequest request) {
//获取请求头 设置的access_token信息,对比token获取session中存储的userId
String req_access_token = request.getHeader("Authorization");
HttpSession session = request.getSession();
String access_token = (String) session.getAttribute("access_token");
if (access_token.equals(req_access_token)) {//校验正确
Integer user_id = (Integer) session.getAttribute("user_id");
ResponseResult result = userService.getUserPermission(user_id);
return result;
} else {//校验错误
//响应错误
ResponseResult responseResult = new ResponseResult(false, "400", "token过期", null);
return responseResult;
}
}
MD5 加密密码
MD5加密,它是对信息进行摘要采集,再通过一定的位运算,最终获取加密后的MD5字符串
- 针对不同长度待加密的数据,字符串等等,其都可以返回一个固定长度的MD5加密字符串,通常32位的16进制字符串。
- 其加密过程几乎不可逆,除非维护一个庞大的key-value数据库进行碰撞破解
- 运算简便,且可实现方式多样,通过一定的处理方式也可以避免碰撞算法的破解(加盐:随机字符串)
- 对于一个固定的字符串、数字等等,MD5加密后的字符串是固定的,不管MD5加密多少次都是同样的效果
添加依赖:
<!-- MD5 加密相关 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.7</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.11</version>
</dependency>
MD5工具类:
/**
* MD5方法
*
* @param text 明文 123456
* @param key 密钥 lagou
* @return 密文
* @throws Exception
*/
public static String md5(String text, String key) throws Exception {
//加密后的字符串
String encodeStr = DigestUtils.md5Hex(text + key);
System.out.println("MD5加密后的字符串为:encodeStr=" + encodeStr);
return encodeStr;
}
/**
* MD5验证方法
*
* @param text 明文
* @param key 密钥
* @param md5 密文
* @return true/false
* @throws Exception
*/
public static boolean verify(String text, String key, String md5) throws Exception {
//根据传入的密钥进行验证
String md5Text = md5(text, key);
if (md5Text.equalsIgnoreCase(md5)) {
System.out.println("MD5验证通过");
return true;
}
return false;
}
登录需求
@RequestMapping("/login")
public ResponseResult login(User user, HttpServletRequest request) {
try {
User findUser = userService.findByPhone(user);
if (findUser != null) {
//登录成功
//存储session中 access_token
HttpSession session = request.getSession();
String access_token = UUID.randomUUID().toString();
session.setAttribute("access_token", access_token);
session.setAttribute("user_id", findUser.getId());
HashMap<String, Object> map = new HashMap<>();
map.put("access_token", access_token);
map.put("user_id", findUser.getId());
ResponseResult responseResult = new ResponseResult(true, StateCode.SUCCESS.getCode(), StateCode.SUCCESS.getMsg(), map);
return responseResult;
} else {
//登录失败
ResponseResult responseResult = new ResponseResult(true, StateCode.LOGIN_ERROR.getCode(), StateCode.LOGIN_ERROR.getMsg(), null);
return responseResult;
}
} catch (Exception e) {
e.printStackTrace();
ResponseResult responseResult = new ResponseResult(true, StateCode.LOGIN_ERROR.getCode(), StateCode.LOGIN_ERROR.getMsg(), "");
return responseResult;
}
}
后台项目部署
- 将sql导入到服务器中,通过数据库图形工具连接服务器
- 项目打包发布
在平时开发过程中,不同的环境中项目的相关配置也会有相关的不同,我们在不同的环境中部署就要手动修改为对应环境的配置,通过maven的相关配置来在打包时指定各个环境对应配置文件。
创建两个配置文件:development.properties 开发环境配置。product.properties生产环境配置
在之前配置的jdbc.properties
中进行修改,动态引用配置
jdbc.driver = com.mysql.jdbc.Driver
jdbc.url = ${jdbc.url}
jdbc.username = ${jdbc.username}
jdbc.password = ${jdbc.password}
maven配置:在DAO层的pom.xml
中进行配置
<profiles>
<profile>
<id>dev</id>
<properties>
<!-- 测试环境 -->
<env>development</env>
</properties>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<profile>
<id>prod</id>
<properties>
<!-- 正式环境 -->
<env>product</env>
</properties>
</profile>
</profiles>
<build>
<finalName>web</finalName>
<filters>
<filter>src/main/resources/filter/${env}.properties</filter>
</filters>
<resources>
<resource>
<directory>src/main/resources</directory>
<excludes>
<exclude>filter/*.properties</exclude>
</excludes>
<filtering>true</filtering>
</resource>
</resources>
</build>
这时候在maven的工具中就会多了一个 Profiles 来指定环境
项目打包
• 命令打包
打本地包 mvn -Pdev install 或者mvn install(因为本例activeByDefault配的为true)
打产品包 mvn -Pprod install
结果:src/main/resources/config/jdbc.properties根据 mvn -P 参数决定值
最终会在edu_api Web层的target中,可以看到其他的其子工程都打成了jar
• 使用idea打包
打包之后将其放在Tomcat的webapps/目录下即可
前端项目部署
3.2.1 修改配置文件
- 生产环境配置文件,配置后台URL
VUE_APP_NAME = Edu Boss
VUE_APP_TITLE = Lagou Edu Boss (Dev)
VUE_APP_STORAGE_PREFIX = lagou_edu_boss_dev
#VUE_APP_API_FAKE = /front
VUE_APP_API_FAKE = http://192.168.52.100:8080/ssm-web
#VUE_APP_API_BASE = /boss
VUE_APP_API_BASE = http://192.168.52.100:8080/ssm-web
- 自定义配置文件,配置打包相关信息
将下面内容拷贝到 vue.config.js
module.exports = {
publicPath: process.env.NODE_ENV === "production" ? "/edu-boss/" : "/",
indexPath: "index.html",
assetsDir: "static",
lintOnSave: process.env.NODE_ENV !== "production",
productionSourceMap: false,
devServer: {
open: true,
port: 8081
}
};
3.2.2 打包测试操作
- 打包命令
npm run build
- 在项目下会生成一个 dist 目录
- 在本地tomcat的webapps目录下,创建一个edu-boss文件夹,将dist目录中的文件拷贝到里面
- 测试: 启动本地tomcat ,访问前端项目 路径为:
http://localhost:8081/edu-boss/
3.3.3 发布前端项目
- 解压一个新的tomcat, 修改端口号
#解压
tar xvf apache-tomcat-8.5.50.tar
#改名
mv apache-tomcat-8.5.50 ui-tomcat
#修改端口号
cd ui-tomcat/conf/
vim server.xml
- 上传前端项目到 webapps
//上传 edu-boss.zip ,并解压
unzip edu-boss.zip
//删除edu-boss.zip
rm -rf edu-boss.zip
- 运行前端项目,并访问
./bin/startup.sh
//动态查看日志
tail -f logs/catalina.out
//访问
http://192.168.52.100:8081/edu-boss/
3.3 修改tomcat默认访问项目
- 使用notpad打开前端tomcat的配置文件 server.xml, 找到
Host
标签
- 在Host标签内加入
<Context path="" docBase="edu-boss" reloadable="true" debug="0" privileged="true">
</Context>
- 重新启动 并访问前端项目,这个时候只需要直接访问 8081即可
http://192.168.52.100:8081/
3.4 配置反向代理
- 使用notpad打开nginx的配置文件 nginx.conf
- 配置反向代理
#配置ssm项目 反向代理
upstream lagouedu{
server 192.168.52.100:8081;
}
server {
listen 80;
server_name www.edu-boss.com;
location / {
proxy_pass http://lagouedu; #转发的地址
index index.html index.htm;
}
}
- 修改本地hosts, 配置域名映射
- 访问 www.edu-boss.com