文章目录
基础知识:
1maven是一款管理项目的工具
a.生命周期的管理,编译,测试,打包等等
b.依赖管理,插件管理
c.分模块构建
2.maven的坐标:用来引入依赖的
通过groupId+artifactId+version三者合一去仓库寻找依赖
3.maven的仓库
本地仓库:个人电脑上的仓库
私服: 第三方公司搭建的服务器
中央仓库: maven团队维护的
问题引入:
1.右侧爆红:
1.通过工具删除下载失败的资源(之前发过,但是需要编辑文件,修改文件中的路径是自己本地仓库的路径)
2.保持网络通畅(你们需要拔掉网线)
3.添加坐标配置,刷新,重新下载即可
2.atl+insert 快捷导入找不到依赖
一、私服搭建
1. 为什么搭建私服
问题:
1.我们公司开发了jar包,其他项目组如何使用?
2.公司不给我们开放网络,我们怎么下载依赖?
小结:
为什么搭建私服:
方便我们公司自己管理依赖。
nexus软件的安装
步骤:
1.解压
2.执行文件
解压:
私服配置文件中的端口号:
双击或者鼠标右击,管理员执行
启动成功:
2. 私服的使用
访问首页
浏览器输入地址:http://localhost:8081/nexus
登录
默认用户名和密码:
用户名:admin
秘密法:admin123
登录成功
仓库介绍
私服中仓库类型的介绍:
group:组,所有仓库的入口
hosted:宿主仓库,用于管理非中央仓库的依赖
proxy: 代理,中央仓库的代理
virtual: maven1版本的支持,不用。
仓库介绍:
Public Repositories: 仓库组,所有仓库的统一入口。
3rd party: 宿主仓库,用于存放中央仓库没有并且是第三方公司开发的jar。
Apache Snapshots: 代理仓库,apache仓库的代理
central: 代理仓库,中央仓库的代理
Central M1 shadow : maven1版本的支持,不管
Releases: 宿主仓库,用来存放我们自己开发的依赖的发行版
Snapshots: 宿主仓库,用来存放我们自己开大的依赖的快照版
发行版(正式发布会):解决了快照版出现的bug,不会频繁的更新。
快照版(抢先体验):一些bug,会频繁的更新bug
仓库类型:
私服中各种仓库 和 仓库之间的关系
快照版和发行版:
小结:
常见仓库类型:
group:组,所有仓库的入口
hosted:宿主仓库,用于管理非中央仓库的依赖
proxy: 代理,中央仓库的代理
私服中的常见仓库:
Public Repositories: 仓库组,所有仓库的统一入口。
3rd party: 宿主仓库,用于存放中央仓库没有并且是第三方公司开发的jar。
central: 代理仓库,中央仓库的代理
Releases: 宿主仓库,用来存放我们自己开发的依赖的发行版
Snapshots: 宿主仓库,用来存放我们自己开大的依赖的快照版
发行版(正式发布会):解决了快照版出现的bug,不会频繁的更新。
快照版(抢先体验):一些bug,会频繁的更新bug
从私服下载资源
演示步骤:
1.删除本地仓库中mysql依赖。
2.修改私服中central代理的远程仓库为阿里云
3.修改maven的settings配置文件中,配置私服为我们自己搭建的私服
4.打开工程,引入被删除的mysql依赖,
1.删除本地仓库中mysql依赖。
2.修改私服中central代理的远程仓库为阿里云
阿里云私服地址: http://maven.aliyun.com/nexus/content/groups/public
3.修改maven的settings配置文件中,配置私服为我们自己搭建的私服
4.打开工程,引入被删除的mysql依赖,
问题:
如果配置了阿里云,也连接了网络,但是就是下载不成功。
将资源上传到私服
第三方宿主仓库 (3rd party)
用于存放阿里云没有但是是第三方开发的jar包。
演示:
1.自定义一个依赖,打成jar包,代表第三方公司的依赖
2.上传到3rd party
3.开启一个工程,引入这个依赖
4.查看引入情况即可
演示:
1.自定义一个依赖,打成jar包,代表第三方公司的依赖
2.上传到3rd party
3.开启一个工程,引入这个依赖
4.查看引入情况即可
自定义快照版仓库
演示步骤:
1.自定开启maven工程,编写一个mylist工具类。并且打成jar包
2.在maven工程的settings.xml中配置远程仓库的登录用户名和密码
3.在maven的pom.xml中配置远程仓库的地址
4.deploy命令进行上传
5.再另起工程, 通过坐标引入
1.自定开启maven工程,编写一个mylist工具类。并且打成jar包
2.在maven工程的pom.xml中配置远程仓库的地址
<distributionManagement>
<!--当前项目版本后缀为:RELEASES的上传目录-->
<repository>
<id>releases</id>
<name>Internal Releases</name>
<url>http://localhost:8081/nexus/content/repositories/releases/</url>
</repository>
<!--当前项目版本后缀为:SNAPSHOT上传目录-->
<snapshotRepository>
<id>snapshots</id>
<name>Internal Snapshots</name>
<url>http://localhost:8081/nexus/content/repositories/snapshots/</url>
</snapshotRepository>
</distributionManagement>
3.在maven的settings.xml中配置用户名和密码
<server>
<!-- 仓库的唯一标识, -->
<id>snapshots</id>
<username>admin</username>
<password>admin123</password>
</server>
<server>
<id>releases</id>
<username>admin</username>
<password>admin123</password>
</server>
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210623110438764.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L21tbW1tQ0pQ,size_16,color_FFFFFF,t_70
小结:
maven的settings.xml中配置用户名和密码时,仓库的唯一标识必须和maven的pom.xml的仓库的唯一标识一致。
问题1:
此处有一个问题:maven命令执行不成功。
因为我们maven的命令是依赖于maven的插件,本地仓库有插件,但是maven会去校验远程仓库是否有对应的插件。很明显,我们远程私服是自己刚搭建的,没有插件,所以命令执行失败。
解决办法:我们先删除本地的一个插件,然后在直接去执行命令,maven会自动校验远程,并且进行下载。
问题2:
pom.xml中配置如下内容
<repositories>
<repository>
<!-- 私服的id -->
<id>nexus</id>
<!-- 自己的私服的公共入口 -->
<url>http://192.168.14.69:8081/nexus/content/groups/public/</url>
<releases>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
</releases>
<snapshots>
<!-- 允许从快照版仓库下载资源 -->
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
</snapshots>
</repository>
</repositories>
自定义发行版仓库
上传成功
其他公共私服地址
4、http://maven.aliyun.com/nexus/content/groups/public/ 阿里云 (强力推荐)
5.https://mirrors.huaweicloud.com/repository/maven/ (华为云)
7、http://repository.jboss.org/nexus/content/groups/public
二、maven高级
依赖范围
环境:
测试环境:test包下的代码
编译环境:书写java代码的环境。
运行环境:war+tomcat环境
依赖范围:
provide:作用于编译时和测试时,不做用于运行时。 例如servlet。因为tomcat已经提供了。
runtime: 作用于运行时和测试时,但是不作用于编译时。例如mysql驱动。
test:只需要作用于测试时。 例如junit
compile:默认值,作用于运行时,编译时,测试时。
绝大多数情况,依赖范围用的不好,只是造成资源的浪费,并不会有其他影响。
但是对于servlet的依赖范围,provide是必须要添加的,要不然会报错。
依赖传递
依赖传递:
如果A依赖于B,当我们引入依赖A的时候,maven会自动的引入A所依赖的B.
例如:
spring-context 依赖于spring-core等等。
所以我们引入spring-context,会自动的引入spring-core。
演示:
创建2个工程demo_01和demo_02,
demo_01依赖其他的jar包
demo_02依赖于demo_01.所以,根据传递依赖,demo_02也会依赖于其他jar包
依赖可选
A依赖于B,当我们引入A的时候,maven会自动的引入A所依赖的B。
对于我们来说,不一定需要B。所以如何解决。
1.依赖可选,A在引入B的时候,添加声明,B依赖是可选的。其他工程引入A的时候,就不会引入B了。
2.依赖排除,工程在引入A的时候,添加声明,不需要B依赖。所以就不会引入B依赖。
演示:
1.创建工程Demo_A,引入依赖Junit,配置依赖可选
2.创建工程Demo_B,引入工程Demo_A. 根据依赖传递,Demo_B应该自动的引入Junit.
1.1.创建工程Demo_A,引入依赖Junit,配置依赖可选,B工程引入A
2.A工程配置依赖可选
依赖排除
演示:
Demo_A工程直接引入junit,
Demo_B工程引入依赖Demo_A,Demo_B在引入Demo_A的时候,声明需要排除的依赖。
依赖冲突
Demo_A,Demo_B,Demo_C
Demo_A引入spring-context(5.0.6),依赖传递spring-core
Demo_B引入spring-core(4.1.2),
Demo_C引入Demo_A和Demo_B,Demo_C同时依赖spring-core的2个版本,版本冲突。
maven解决版本冲突的原则:
1.路径最短者优先。
2.路径相同,先声明者优先。
路径最短者优先演示:(ctrl+alt+u) 查看maven的依赖路径
路径相同先声明者优先
将Demo_A的依赖切换成spring-core的5.0.6的版本
三、ssm工程改造成分层构建
maven的继承和聚合是2个不一样的概念,此处我们分开讲,但是企业开发都是一起使用的。
maven的继承
为什么需要继承
java中的继承:用于抽取共性代码的
maven的继承:用于抽取公共配置的,例如:依赖。
maven的继承:
1.用于抽取公共配置
2.方便所有子工程的依赖版本的统一维护
3.父工程中,没有java代码。
maven继承的演示
步骤:
1.创建父工程Demo_parent,声明打包方式为pom.(idea工具做好了)
2.创建子工程Demo_child_A,通过标签声明父工程(idea工具做好了)
3.父工程引入依赖,mysql依赖
步骤:
1.创建父工程Demo_parent,声明打包方式为pom.(idea工具做好了)
2.创建子工程Demo_child_A,通过标签声明父工程(idea工具做好了)
此时继承相关配置,idea全部都自动配置好了
父工程打pom包
子工程中需要声明父工程
3.父工程引入依赖,mysql依赖
继承的一些应用
父工程中:
1.针对于所有子工程需要的依赖,才去<dependencies>中声明,方便所有子工程的继承。
2.非所有子工程需要的依赖,我们在<dependencyManagement>声明,只会锁定版本,不会真的引入
哪个子工程需要,再次编写坐标(不包含version)配置引入
3.所有依赖的版本统一在<properties>中声明。
4.插件也是上述管理操作
父工程配置:
<?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.itheima</groupId>
<artifactId>maven_parent</artifactId>
<!-- 父工程必须打pom包 -->
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<!-- 声明子模块-->
<modules>
<module>maven_child1</module>
<module>maven_child_user</module>
</modules>
<!--
maven中继承的实际应用:
强调:
1.maven的继承是用来抽取公共的配置的
2.父工程中没有java代码,只有一个pom.xml
继承的实际使用细节:
1.<dependencies>中,只会声明一些所有子工程都需要的依赖,例如:junit,或者一些其他工具包
2.对于非所有子模块需要的依赖,我们统一声明到<dependencyManagement>标签中,
用来锁定版本,然后子工程要用,再去引入即可。
3.针对于所有的依赖,或者插件,或者模块的版本号,我们统一的在<properties>中进行声明,统一管理。
-->
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
</dependencies>
<!-- 依赖管理,只是用来锁定依赖的版本的,不会真的去引入依赖。一般都是用在父工程中的-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- 依赖版本的统一管理-->
<mysql.version>5.1.38</mysql.version>
<junit.version>4.12</junit.version>
</properties>
<build>
<plugins>
<!-- java编译插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<!-- tomcat7插件,命令: mvn tomcat7:run -DskipTests -->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<uriEncoding>utf-8</uriEncoding>
<port>8080</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
</project>
子工程配置
maven的聚合(多模块开发)
maven的聚合:多模块开发。
当我们的工程比较庞大的时候,此时我们一个开发者负责一个模块。
而我们之前的工程,所有的代码全部放在一起。此时也就意味着我哪怕负责单个模块,也需要将所有的代码拉取到本地。而且如果不小心动了其他模块的代码呢。
所以在针对于上述这些情况,我们针对我们的工程进行分模块构建,
例如:用户模块单独做成一个工程,订单模块单独做成一个工程,
A组负责用户模块的开发
B组负责订单模块的开发。
好处:
1.方便企业开发的分工合作
2.方便分布式项目的管理
说白了:maven的聚合就是将一个工程,拆分成多个工程进行管理。
如何拆分:
1.纵向拆分:
按照项目的层次结构进行拆分。service层作为一个模块,dao层作为一个模块。
2.横向拆分:
按照功能模块进行拆分,用户模块,订单模块,物流模块,支付模块
上述继承时,已经使用了聚合了。
父工程中自动添加了子模块的声明:
小结:
企业开发模块和继承一起使用
ssm工程分层构建
步骤:
1.按照层次结构去拆分:
1.ssm_parent: 父工程
2.ssm_web: web工程,包含了controller层
3.ssm_service: service层
4.ssm_dao:dao层
5.ssm_pojo:实体层
2. 设置工程的打包方式
父工程打pom包,web工程打war包,其他工程打jar包
3. 配置模块和模块之间的依赖关系
4. 移植代码
5.测试即可
步骤:
1.按照层次结构去拆分:
1.ssm_parent: 父工程
2.ssm_web: web工程,包含了controller层
3.ssm_service: service层
4.ssm_dao:dao层
5.ssm_pojo:实体层
2.设置工程的打包方式
父工程打pom包,web工程打war包,其他工程打jar包
3.配置模块和模块之间的依赖关系
dao依赖于pojo
service依赖于dao
web依赖于service
web工程需要的代码
4.移植代码
父工程的pom.xml,依赖管理偷懒
<?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.itcast</groupId>
<artifactId>ssm_parent</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>ssm_pojo</module>
<module>ssm_dao</module>
<module>ssm_service</module>
<module>ssm_web</module>
</modules>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- 父工程声明所有子模块的版本号-->
<ssm.version>1.0-SNAPSHOT</ssm.version>
</properties>
<dependencies>
<!-- 驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
<!-- mybaits-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- spring和mybatis的整合依赖,spring5中,必须要求此包的版本是1.3.0以上才行 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
<!-- spring的依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
<!-- 阿里巴巴德鲁伊连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.9</version>
</dependency>
<!-- spring整合test-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
<!-- spring的jdbc相关-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
<!-- 日志 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.1</version>
</dependency>
<!-- springmvc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
<!--servlet依赖-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<!-- jstl-->
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<!-- 声明所有的子模块-->
<dependency>
<groupId>com.itcast</groupId>
<artifactId>ssm_pojo</artifactId>
<version>${ssm.version}</version>
</dependency>
<dependency>
<groupId>com.itcast</groupId>
<artifactId>ssm_dao</artifactId>
<version>${ssm.version}</version>
</dependency>
<dependency>
<groupId>com.itcast</groupId>
<artifactId>ssm_service</artifactId>
<version>${ssm.version}</version>
</dependency>
<dependency>
<groupId>com.itcast</groupId>
<artifactId>ssm_web</artifactId>
<version>${ssm.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<!-- java编译插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<!-- tomcat7插件,命令: mvn tomcat7:run -DskipTests -->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<uriEncoding>utf-8</uriEncoding>
<port>8080</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
</project>
5.测试即可
注意:
此处必须点击install,因为我们模块的依赖,是依赖的本地仓库中的模块,所以当我们代码复制之后,要把最新的代码安装包本地仓库,然后才能进行打包。要不然打的war包,里面没有java文件的。
小结;
一定要注意我们修改了某个模块的代码,一定要将模块通过install更新到本地仓库
四、纯注解开发(了解)
核心: 配置类代替配置文件
步骤:
1.创建一个配置类SpringConfig.java,替代applicationContext.xml
2.创建一个配置类SpringMVCConfig.java,代替springmvc.xml
3.创建一个配置类MyBatisConfig.java,替代applicationContext_mybaits.xml
4.创建一个配置类WebProjectInitConfig来替代web.xml配置
5.在pom.xml中配置插件,表示war包不需要web.xml配置
SpringConfig.java
package com.itheima.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration //声明当前类是一个配置类
//配置注解扫描,并且配置controller注解的排除
@ComponentScan(basePackages = "com.itheima",
excludeFilters = @ComponentScan.Filter(Controller.class))
@EnableAspectJAutoProxy //开启注解aop
@EnableTransactionManagement //开启注解事务
public class SpringConfig {
}
SpringMVCConfig.java
package com.itheima.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
@Configuration
@ComponentScan("com.itheima.controller")
@EnableWebMvc //开启注解驱动
public class SpringMvcConfig extends WebMvcConfigurerAdapter {
@Bean
public InternalResourceViewResolver internalResourceViewResolver(){
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/jsp/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
//释放静态资源
/**
* 释放静态资源。
*
* 原理:在tomcat中,静态资源是由tomcat带的缺省的servlet来处理的。
*
*/
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
MyBatisConfig.java
package com.itheima.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import javax.sql.DataSource;
@Configuration
@PropertySource("classpath:jdbc.properties") //导入外部配置
@MapperScan("com.itheima.dao") //包扫描方式创建mapper对象
public class MyBatisConfig {
@Value("${jdbc.username}") //读配置外部配置进行依赖注入
private String username;
@Value("${jdbc.password}")
private String password;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.driverClass}")
private String driverClassName;
@Bean
public DataSource dataSource(){
DruidDataSource dataSource = new DruidDataSource();
//设置数据库连接池参数
dataSource.setDriverClassName(driverClassName);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource){
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
//设置数据源
sqlSessionFactoryBean.setDataSource(dataSource);
//设置别名映射
sqlSessionFactoryBean.setTypeAliasesPackage("com.itheima.pojo");
//设置<settings>相关配置
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
configuration.setMapUnderscoreToCamelCase(true);
sqlSessionFactoryBean.setConfiguration(configuration);
SqlSessionFactory sqlSessionFactory = null;
try {
sqlSessionFactory = sqlSessionFactoryBean.getObject();
} catch (Exception e) {
e.printStackTrace();
}
return sqlSessionFactory;
}
}
WebProjctionInitConfig.java
package com.itheima.config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
public class ProjectInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class,MyBatisConfig.class}; //spring的配置类
}
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
//添加过滤器
//servletContext.addFilter();
super.onStartup(servletContext);
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class}; //springMVC的配置类
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"}; // 配置dispatcherServlet的映射路径
}
}
pom.xml中声明不需要web.xml打war包
<!-- 声明打包时,不需要web.xml -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.4</version>
<configuration>
<!-- 声明不需要web.xml-->
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
总结
1.私服的搭建,操作一遍,理解私服中各种仓库的含义。
2.maven的高级特性:
a.依赖范围
b.依赖传递: A依赖B,工程引入依赖A,那么工程也会引入依赖B
c.解决传递依赖
1).依赖可选 <optional>true</...>
2).依赖排除 <exclusions></exclusions>
d.依赖冲突: A依赖mysql,B也依赖mysql,C引入A和B,所以C会依赖mysql的2个版本。冲突!!!
maven解决冲突的原则:
1.最短路径者优先
2.路径相同,先声明者优先
3.maven的继承和聚合:
maven继承:用于抽取相同配置的。一般用于管理所依赖和插件的版本的。
maven的聚合:将工程拆分成多个模块,方便企业开发的分工合作,方便分布式项目的模块之间的管理。
企业开发:继承和聚合都是一起使用的。
一定要注意我们修改了某个模块的代码,一定要将模块通过install更新到本地仓库
五、travel案例核心亮点
1、模块构建
【1】依赖关系
travel-parent: 父工程
travel-commons: 通用模块
travel-core: 框架集成
travel-dao: dao层
travel-service: 业务层
travel-web-platform: controller层
构建模块间依赖
模块间的依赖管理如上图
1.父工程统一声明所有子模块的版本
2.根据图构建模块之间的依赖关系
【2】模块职能
1、统一定义jar和plugin的插件版本
2、定义管理其他模块
3、定义依赖声明
4、定义deploy的私服信息
1、mybatis的配置支持
2、mybatis的主键生成策略interceptor的定义
3、mybatis的主键生成策略支持工具类
4、spring的配置文件
5、spring-mvc的配置文件
1、constant常量定义
2、exception异常定义
3、mybatis-plugin插件定义
5、工具类
1、mapper层接口
2、pojo层对象
3、自定义mapperExt的支持
1、主体业务的实现
后台项目-管理平台
1、projectInitCofing的初始化配置文件
2、统一响应web层
1、projectInitCofing的初始化配置文件
2、统一响应网关路由web层
3、登录interceptors的定义
2、vo和pojo转换概念
vo:view Object。表现层对象。用来封装和浏览器进行交互的数据的。
pojo: 普通无规则对象。
dto: data transfer object. 数据传输对象。
po: 普通的持久化对象
-----------------------------------------------
为何需要这么划分实体?
问题:数据表有20个字段。
pojo用于对应表,java中的数据持久化到数据库。
页面展示:需要分页信息,pojo能封装吗? 所以针对页面需要展示的数据,我们需要创建实体(VO)来封装。
数据传输:但是我传输用户数据只需要传输5个字段。使用pojo可以传,但是其他15个字段在进行json字符串转换的时候也会转换,所以传输数据时这些数据也会传递,但是无意义。所以浪费字眼,影响性能,所以需要dto,只声明数据传输时需要的字段。
-----------------------------------------------
web层需要使用Vo类型,
dao层需要使用pojo类型。
service涉及到vo和pojo的转换
3、mybatis逆向工程
步骤:
0.idea中安装plugins,lombok插件
1.需要在travel-dao层引入mybatis-generator相关的依赖和插件
2.需要mybatis-generator的核心配置
3.需要在travel-commons中引入mybatis-generator需要的工具类
4.先在travel-parent中执行install命令,然后再在travel-dao层执行generator插件。
导入插件
<plugins>
<!--代码生成器:mybatis-generator-maven-plugin-->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<dependencies>
<!--代码生成器:mysql依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!--代码生成器:mybatis-generator-core依赖-->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.5</version>
</dependency>
<!--代码生成器:自定义插件依赖-->
<dependency>
<groupId>com.itheima</groupId>
<artifactId>travel_commons</artifactId>
<version>${travel.version}</version>
</dependency>
</dependencies>
<executions>
<execution>
<id>Generate MyBatis Artifacts</id>
<phase>package</phase>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<configuration>
<!--允许移动生成的文件 -->
<verbose>true</verbose>
<!-- 是否覆盖 -->
<overwrite>true</overwrite>
<!-- 自动生成的配置 -->
<configurationFile>
src/main/resources/mybatis-generator.xml
</configurationFile>
</configuration>
</plugin>
</plugins>
mybatis-generator的核心配置
mybatis的接口放在了mapper包下,需要改mybatis配置扫描的包
注意:此配置抄写过来,一般需要手动改写的配置
1.mysql驱动的位置
2.mysql连接配置
3.生成的实体位置
4.mapper接口的位置
5.工具类的位置
6.表明和实体名的映射
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!--导入mysql的驱动-->
<classPathEntry
location="E:\maven\apache-maven-3.5.2-bin\repository\mysql\mysql-connector-java\5.1.38\mysql-connector-java-5.1.38.jar"/>
<!-- context 是逆向工程的主要配置信息 -->
<!-- id:起个名字 -->
<!-- targetRuntime:设置生成的文件适用于那个 mybatis 版本 -->
<context id="default" targetRuntime="MyBatis3">
<!-- 数据库注释,数据库注释到实体的乱码 -->
<property name="javaFileEncoding" value="UTF-8"/>
<!-- 插件-->
<!-- 帮助实体类生成toString方法的 -->
<plugin type="com.itheima.plugin.ToStringPlugin"/>
<!-- 帮助生成的实体类,采用lombok注解 -->
<plugin type="com.itheima.plugin.LombokPlugin"/>
<!-- 序列化-->
<plugin type="org.mybatis.generator.plugins.SerializablePlugin"/>
<!-- 根据数据库列的注释,生成的实体的属性也会自动添加注释 -->
<commentGenerator type="com.itheima.plugin.CommentGenerator">
<property name="suppressAllComments" value="false"/>
</commentGenerator>
<!--optional,旨在创建class时,对注释进行控制-->
<!--<commentGenerator>-->
<!--<property name="suppressDate" value="false" />-->
<!--<!– 是否去除自动生成的注释 true:是 : false:否 –>-->
<!--<property name="suppressAllComments" value="true" />-->
<!--</commentGenerator>-->
<!--jdbc的数据库连接-->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://127.0.0.1:3306/spring-travel"
userId="root"
password="root" />
<!--非必须,类型处理器,在数据库类型和java类型之间的转换控制-->
<javaTypeResolver>
<!-- 默认情况下数据库中的 decimal,bigInt 在 Java 对应是 sql 下的 BigDecimal 类 -->
<!-- 不是 double 和 long 类型 -->
<!-- 使用常用的基本类型代替 sql 包下的引用类型 -->
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<!-- targetPackage:生成的实体类所在的包 -->
<!-- targetProject:生成的实体类所在的硬盘位置 -->
<javaModelGenerator targetPackage="com.itheima.pojo"
targetProject="src/main/java">
<property name="javaFileEncoding" value="UTF-8"/>
<!-- 是否允许子包 -->
<property name="enableSubPackages" value="false" />
<!-- 是否对modal添加构造函数 -->
<property name="constructorBased" value="false" />
<!-- 是否清理从数据库中查询出的字符串左右两边的空白字符 -->
<property name="trimStrings" value="true" />
<!-- 建立modal对象是否不可改变 即生成的modal对象不会有setter方法,只有构造方法 -->
<property name="immutable" value="false" />
</javaModelGenerator>
<!-- targetPackage 和 targetProject:生成的 mapper 文件的包和位置 -->
<sqlMapGenerator targetPackage="sqlMapper"
targetProject="src/main/resources">
<!-- 针对数据库的一个配置,是否把 schema 作为字包名 -->
<property name="enableSubPackages" value="false" />
</sqlMapGenerator>
<!-- targetPackage 和 targetProject:生成的 interface 文件的包和位置 XMLMAPPER:生成XML方式,ANNOTATEDMAPPER:生成注解方式-->
<javaClientGenerator type="ANNOTATEDMAPPER"
targetPackage="com.itheima.mapper" targetProject="src/main/java">
<!-- 针对 oracle 数据库的一个配置,是否把 schema 作为字包名 -->
<property name="enableSubPackages" value="false" />
</javaClientGenerator>
<!-- 所有表与实体名的映射 -->
<table tableName="tab_affix" domainObjectName="Affix">
<generatedKey column="id" sqlStatement="MySql" identity="true"/>
</table>
<table tableName="tab_category" domainObjectName="Category" >
<generatedKey column="id" sqlStatement="MySql" identity="true"/>
</table>
<table tableName="tab_favorite" domainObjectName="Favorite">
<generatedKey column="id" sqlStatement="MySql" identity="true"/>
</table>
<table tableName="tab_seller" domainObjectName="Seller">
<generatedKey column="id" sqlStatement="MySql" identity="true"/>
</table>
<table tableName="tab_user" domainObjectName="User">
<generatedKey column="id" sqlStatement="MySql" identity="true"/>
</table>
<table tableName="tab_route" domainObjectName="Route">
<generatedKey column="id" sqlStatement="MySql" identity="true"/>
</table>
</context>
</generatorConfiguration>
引入工具类和依赖
travel-commons中引入mybatis-generator需要的工具类和依赖
<dependencies>
<!-- log4j2驱动依赖 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-web</artifactId>
</dependency>
<!--工具包-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
</dependency>
<!--jackson依赖包-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
</dependencies>
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210623112104434.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L21tbW1tQ0pQ,size_16,color_FFFFFF,t_70
4.先在travel-parent中执行install命令,然后再在travel-dao层执行generator插件。
逆向工程原理
mapper接口和接口需要的sql语句的提供类:
实体和查询条件的组装类。
Mybatis逆向工程的mapper接口详解:
选择性的(Selective):
条件查询(ByExample):
测试类:
package com.itheima.mapper;
import com.itheima.config.SpringConfig;
import com.itheima.pojo.Affix;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import static org.junit.Assert.*;
@RunWith(SpringJUnit4ClassRunner.class) //配置spring的运行器,这个运行器会帮我们创建spring容器
@ContextConfiguration(classes = SpringConfig.class) // 配置spring配置类或者配置文件的位置。
@WebAppConfiguration // 模拟web环境进行测试,spring会创建web项目需要的对象,例如,servletContext,session...
public class AffixMapperTest {
@Autowired //自动注入
private AffixMapper affixMapper;
@Test
public void insert() {
Affix affix = Affix.builder()
.id(123123123l)
.businessId(123123123l)
.businessType("route_big1")
.suffix(".png")
.fileName("abc.png")
.pathUrl("route_big1/abc.png")
.build();
affixMapper.insert(affix);
}
@Test
public void selectByPrimaryKey() {
Affix affix = affixMapper.selectByPrimaryKey(1l);
System.out.println(affix);
}
}
package com.itheima.mapper;
import com.itheima.config.SpringConfig;
import com.itheima.pojo.User;
import com.itheima.pojo.UserExample;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import java.util.List;
import static org.junit.Assert.*;
@RunWith(SpringJUnit4ClassRunner.class) //配置spring的运行器,这个运行器会帮我们创建spring容器
@ContextConfiguration(classes = SpringConfig.class) // 配置spring配置类或者配置文件的位置。
@WebAppConfiguration // 模拟web环境进行测试,spring会创建web项目需要的对象,例如,servletContext,session...
public class UserMapperTest {
@Autowired
private UserMapper userMapper;
@Test
public void selectByExample() {
//创建条件组装对象
UserExample userExample = new UserExample();
//组装条件
userExample.createCriteria()
.andSexEqualTo("1")
.andUsernameLike("%zhao%");
List<User> userList = userMapper.selectByExample(userExample);
userList.forEach(user->{
System.out.println(user);
});
}
@Test
public void updateByPrimaryKeySelective() {
User user = User.builder()
.id(571847474723635248l)
.username("liuyan")
.password("123123")
.build();
userMapper.updateByPrimaryKeySelective(user);
}
@Test
public void updateByPrimaryKey() {
User user = User.builder()
.id(82202595013509120l)
.username("liuyan")
.password("123123")
.build();
userMapper.updateByPrimaryKey(user);
}
}
4、mybatis分页插件
mybatis分页插件底层通过拦截器,在executor执行sql之前,对sql语句进行拦截。然后修改sql,分别进行count查询和limit分页查询。
mybatis分页插件的使用
1.因为这个插件需要和mybatis做集成,travel-core模块引入依赖分页插件依赖
2.在MyBatisConfig里面创建一个分页插件拦截器对象,将这个拦截器配置给mybatis
3.在service中执行mapper方法之前,通过PageHelper.setPageNum(pageNum,pageSize),mybatis在执行mapper方法之前,就会做拦截,自动进行分页查询。
1.因为这个插件需要和mybatis做集成,travel-core模块引入依赖分页插件依赖
<!-- 分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
</dependency>
2.在MyBatisConfig里面创建一个分页插件拦截器对象,将这个拦截器配置给mybatis
/**
* @Description 分页插件
*/
@Bean
public PageInterceptor initPageInterceptor(){
PageInterceptor pageInterceptor = new PageInterceptor();
Properties properties = new Properties();
//方言,mysql方言。
properties.setProperty("helperDialect", "mysql");
//该参数默认为false
//设置为true时,会将RowBounds第一个参数offset当成pageNum页码使用
properties.setProperty("offsetAsPageNum", "true");
//使用RowBounds分页会进行count查询。
properties.setProperty("rowBoundsWithCount", "true");
pageInterceptor.setProperties(properties);
return pageInterceptor;
}
3.在service中执行mapper方法之前,通过PageHelper.setPageNum(pageNum,pageSize),mybatis在执行mapper方法之前,就会做拦截,自动进行分页查询。
package com.itheima.service.impl;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.itheima.mapper.FavoriteMapper;
import com.itheima.mapper.RouteMapper;
import com.itheima.mapperExt.RouteMapperExt;
import com.itheima.pojo.Favorite;
import com.itheima.pojo.FavoriteExample;
import com.itheima.pojo.Route;
import com.itheima.req.FavoriteVo;
import com.itheima.req.RouteVo;
import com.itheima.req.UserVo;
import com.itheima.service.FavoriteService;
import com.itheima.utils.BeanConv;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.http.HttpSession;
import java.util.List;
@Service
public class FavoriteServiceImpl implements FavoriteService {
@Autowired
private FavoriteMapper favoriteMapper;
@Autowired
private RouteMapper routeMapper;
//扩展mapper
@Autowired
private RouteMapperExt routeMapperExt;
@Autowired
private HttpSession session;
@Override
public boolean isFavorited(FavoriteVo favoriteVo) {
//session中获取用户
UserVo userVo =(UserVo)session.getAttribute("user");
//查询中间表tab_favorite是否存在数据即可
FavoriteExample favoriteExample = new FavoriteExample();
favoriteExample.createCriteria()
.andUserIdEqualTo(userVo.getId())
.andRouteIdEqualTo(favoriteVo.getRouteId());
List<Favorite> favoriteList = favoriteMapper.selectByExample(favoriteExample);
return favoriteList.size()==1;
}
@Override
@Transactional
public int addFavorite(FavoriteVo favoriteVo) {
UserVo userVo = (UserVo)session.getAttribute("user");
//1.向中间表插入一条数据
Favorite favorite = Favorite.builder()
.routeId(favoriteVo.getRouteId())
.userId(userVo.getId())
.build();
favoriteMapper.insert(favorite);
// int i = 1/0;//演示事务
//2.修改需要收藏的旅游线路的attention_count字段值+1
// routeMapper.
routeMapperExt.updateAttentionCountByPrimaryKey(favoriteVo.getRouteId());
//3.重新查询收藏后的次数
Route route = routeMapper.selectByPrimaryKey(favoriteVo.getRouteId());
return route.getAttentionCount();
}
@Override
public PageInfo<RouteVo> findMyFavorite(FavoriteVo favoriteVo) {
//通知mybatis,使用分页技术
PageHelper.startPage(favoriteVo.getPageNum(),favoriteVo.getPageSize());
//1.查询当前用户收藏的旅游线路的总数量
//2.当前用户当前页码数显示的旅游线路信息。
//对于mybatis分页查询来说,此处只要编写一条用于查询所有数据的sql即可。
UserVo userVo = (UserVo)session.getAttribute("user");
List<Route> routeList = routeMapperExt.findMyFavorite(userVo.getId());
// routeList.forEach(route -> {
// System.out.println("分页数据:"+route);
// });
//mybatis提供好了pageBean
PageInfo<Route> pageInfo = new PageInfo<>(routeList);
// System.out.println(pageInfo.getTotal());
// System.out.println(pageInfo.getList());
//属性拷贝
PageInfo<RouteVo> voPageInfo = new PageInfo<>();
//只能拷贝简单属性,total,但是list属于复杂属性,拷贝不过去。
BeanConv.toBean(pageInfo,voPageInfo);
//集合进行单独拷贝
List<RouteVo> routeVos = BeanConv.toBeanList(pageInfo.getList(), RouteVo.class);
voPageInfo.setList(routeVos);
return voPageInfo;
}
}
扩展mapper,只需要查询所有即可
package com.itheima.mapperExt;
import com.itheima.pojo.Route;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import java.util.List;
public interface RouteMapperExt {
@Update("UPDATE tab_route SET attention_count = attention_count+1 WHERE id = #{routeId}")
public void updateAttentionCountByPrimaryKey(@Param("routeId") long routeId);
@Select("SELECT r.* FROM (SELECT * FROM tab_favorite WHERE user_id=#{uid}) temp LEFT JOIN tab_route r ON temp.route_id= r.id")
public List<Route> findMyFavorite(@Param("uid") long uid);
}
单元测试:调用方法,断点查询service的结果好控制台sql语句
小结:
1.分页插件和mybatis做集成
a.引入依赖
b.创建分页拦截器,pageInterceptor,并且配置给mybatis
2.如何使用
a.通过PageHelper.startPage(pageNum,pageSize); 通知mybatis做分页查询
b.执行的sql只需要查询所有即可。
service返回值PageInfo类型
我们service需要PageInfo类型的返回值,所以需要属性拷贝。
但是注意:此处不能先拷贝集合,必须先拷贝pageInfo,然后再拷贝集合。
@Override
public PageInfo<RouteVo> findMyFavorite(FavoriteVo favoriteVo) {
//通知mybatis,使用分页技术
PageHelper.startPage(favoriteVo.getPageNum(),favoriteVo.getPageSize());
//1.查询当前用户收藏的旅游线路的总数量
//2.当前用户当前页码数显示的旅游线路信息。
//对于mybatis分页查询来说,此处只要编写一条用于查询所有数据的sql即可。
UserVo userVo = (UserVo)session.getAttribute("user");
List<Route> routeList = routeMapperExt.findMyFavorite(userVo.getId());
// routeList.forEach(route -> {
// System.out.println("分页数据:"+route);
// });
//mybatis提供好了pageBean
PageInfo<Route> pageInfo = new PageInfo<>(routeList);
// System.out.println(pageInfo.getTotal());
// System.out.println(pageInfo.getList());
//属性拷贝
PageInfo<RouteVo> voPageInfo = new PageInfo<>();
//只能拷贝简单属性,total,但是list属于复杂属性,拷贝不过去。
BeanConv.toBean(pageInfo,voPageInfo);
//集合进行单独拷贝
List<RouteVo> routeVos = BeanConv.toBeanList(pageInfo.getList(), RouteVo.class);
voPageInfo.setList(routeVos);
return voPageInfo;
}
mybatis的分页插件原理
mybatis分页插件底层通过拦截器,在executor执行sql之前,对sql语句进行拦截。然后修改sql,分别进行count查询和limit分页查询。
Page类型:
属性拷贝原理:
5、雪花算法
雪花算法的介绍
雪花算法:用来生成唯一标识的规则。
为什么要用雪花算法?
我们数据库表的主键采用的是主键自增。实际企业开发不用,第一,id容易泄露。第二,不适用于分表分库环境。
所以基于上述问题:我们需要唯一标识。早期有很多中方案。
中心化生成: 依赖于一个核心,如何这个核心出问题了,就不行了。
redis中心
基于sequence区间的。
去中心化生成: 不依赖于一个核心。直接依赖于jar包。
uuid: 随机,不重复
雪花算法: 随机,总体是自增的数字
相对于uuid来说:对于数据库索引的维护是非常有帮助的。
雪花算法的原理:
集成到mybatis中
集成原理:
通过mybatis拦截器,在executor执行sql(insert)之前,获取实体,
设置实体的id的值为雪花算法生成的值。然后再去执行sql
集成步骤:
0.配置机房id和机器id,创建雪花算法对象
1.创建雪花算法拦截器对象,将雪花算法对象交给拦截器
2.将这个拦截器对象配置给mybatis。
1.拷贝雪花算法工具类到core模块中
package com.itheima.travel.config;
/**
* Twitter_Snowflake<br>
* SnowFlake的结构如下(每部分用-分开):<br>
* 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 <br>
* 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0<br>
* 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截)
* 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序IdWorker类的startTime属性)。41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br>
* 10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId<br>
* 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号<br>
* 加起来刚好64位,为一个Long型。<br>
* SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右。
*/
public class SnowflakeIdWorker {
// ==============================Fields===========================================
/** 开始时间截 (2020-08-28) */
private final long twepoch = 1598598185157L;
/** 机器id所占的位数 */
private final long workerIdBits = 5L;
/** 数据标识id所占的位数 */
private final long datacenterIdBits = 5L;
/** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */
private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
/** 支持的最大数据标识id,结果是31 */
private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
/** 序列在id中占的位数 */
private final long sequenceBits = 12L;
/** 机器ID向左移12位 */
private final long workerIdShift = sequenceBits;
/** 数据标识id向左移17位(12+5) */
private final long datacenterIdShift = sequenceBits + workerIdBits;
/** 时间截向左移22位(5+5+12) */
private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
/** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */
private final long sequenceMask = -1L ^ (-1L << sequenceBits);
/** 工作机器ID(0~31) */
private long workerId;
/** 数据中心ID(0~31) */
private long datacenterId;
/** 毫秒内序列(0~4095) */
private long sequence = 0L;
/** 上次生成ID的时间截 */
private long lastTimestamp = -1L;
//==============================Constructors=====================================
/**
* 构造函数
* @param workerId 工作ID (0~31)
* @param datacenterId 数据中心ID (0~31)
*/
public SnowflakeIdWorker(long workerId, long datacenterId) {
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
}
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}
// ==============================Methods==========================================
/**
* 获得下一个ID (该方法是线程安全的)
* @return SnowflakeId
*/
public synchronized long nextId() {
long timestamp = timeGen();
//如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
if (timestamp < lastTimestamp) {
throw new RuntimeException(
String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
}
//如果是同一时间生成的,则进行毫秒内序列
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & sequenceMask;
//毫秒内序列溢出
if (sequence == 0) {
//阻塞到下一个毫秒,获得新的时间戳
timestamp = tilNextMillis(lastTimestamp);
}
}
//时间戳改变,毫秒内序列重置
else {
sequence = 0L;
}
//上次生成ID的时间截
lastTimestamp = timestamp;
//移位并通过或运算拼到一起组成64位的ID
long id = ((timestamp - twepoch) << timestampLeftShift) //
| (datacenterId << datacenterIdShift) //
| (workerId << workerIdShift) //
| sequence;
return id;
}
/**
* 阻塞到下一个毫秒,直到获得新的时间戳
* @param lastTimestamp 上次生成ID的时间截
* @return 当前时间戳
*/
protected long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
/**
* 返回以毫秒为单位的当前时间
* @return 当前时间(毫秒)
*/
protected long timeGen() {
return System.currentTimeMillis();
}
//==============================Test=============================================
/** 测试 */
public static void main(String[] args) {
SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0);
for (int i = 0; i < 1000; i++) {
long id = idWorker.nextId();
System.out.println(id);
}
}
}
2.拷贝雪花算法拦截器到core模块的interceptors包中
package com.itheima.travel.interceptor;
import com.itheima.travel.config.SnowflakeIdWorker;
import com.itheima.travel.utils.EmptyUtil;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.springframework.beans.factory.annotation.Autowired;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Map;
import java.util.Properties;
/**
* @Description 主键雪花算法
*/
@Intercepts({@Signature(type = Executor.class, method = "update", args ={MappedStatement.class,Object.class})})
@Log4j2
public class PrimaryKeyInterceptor implements Interceptor {
//主键生成策略
private SnowflakeIdWorker snowflakeIdWorker;
//主键标识
private String primaryKey ;
public PrimaryKeyInterceptor() {
}
public PrimaryKeyInterceptor(SnowflakeIdWorker snowflakeIdWorker) {
this.snowflakeIdWorker = snowflakeIdWorker;
}
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object[] args = invocation.getArgs();
if (args == null || args.length != 2) {
return invocation.proceed();
} else {
MappedStatement ms = (MappedStatement) args[0];
// 操作类型
SqlCommandType sqlCommandType = ms.getSqlCommandType();
// 只处理insert操作
if (!EmptyUtil.isNullOrEmpty(sqlCommandType) && sqlCommandType == SqlCommandType.INSERT) {
if (args[1] instanceof Map) {
// 批量插入
List list = (List) ((Map) args[1]).get("list");
for (Object obj : list) {
setProperty(obj, primaryKey, snowflakeIdWorker.nextId());
}
} else {
setProperty(args[1], primaryKey, snowflakeIdWorker.nextId());
}
}
}
return invocation.proceed();
}
@Override
public Object plugin(Object o) {
return Plugin.wrap(o, this);
}
@Override
public void setProperties(Properties properties) {
//指定主键
primaryKey = properties.getProperty("primaryKey");
if (EmptyUtil.isNullOrEmpty(primaryKey)){
primaryKey="id";
}
}
/**
* 设置对象属性值
*
* @param obj 对象
* @param property 属性名称
* @param value 属性值
*/
private void setProperty(Object obj, String property, Object value)
throws NoSuchFieldException,
IllegalAccessException,
InvocationTargetException {
Field field = obj.getClass().getDeclaredField(property);
if (!EmptyUtil.isNullOrEmpty(field)) {
field.setAccessible(true);
Object val = field.get(obj);
if (EmptyUtil.isNullOrEmpty(val)) {
BeanUtils.setProperty(obj, property, value);
}
}
}
public void setSnowflakeIdWorker(SnowflakeIdWorker snowflakeIdWorker) {
this.snowflakeIdWorker = snowflakeIdWorker;
}
}
3.配置机房id和机器id,创建雪花算法对象
jdbc.properties添加配置
jdbc.username=root
jdbc.password=root
jdbc.url=jdbc:mysql://localhost:3306/spring-travel
jdbc.driverClass=com.mysql.jdbc.Driver
snow.workerId=20
snow.datacenterId=15
在MyBatisConfig.java中读取配置
4.在MyabtisConfig.java创建雪花算法拦截器对象,将雪花算法对象交给拦截器
@Bean
public PrimaryKeyInterceptor primaryKeyInterceptor() {
//创建雪花算法对象
SnowflakeIdWorker snowflakeIdWorker = new SnowflakeIdWorker(workerId,datacenterId);
//创建拦截器对象
PrimaryKeyInterceptor primaryKeyInterceptor = new PrimaryKeyInterceptor(snowflakeIdWorker);
Properties properties = new Properties();
properties.setProperty("primaryKey", "id");
primaryKeyInterceptor.setProperties(properties);
return primaryKeyInterceptor;
}
5.将这个拦截器对象配置给mybatis。
6.mybatis的逆向工程中,去掉主键的自增。重新生成
- 运行项目插入用户数据测试是否成功
6、Swagger2构建测试接口文档
我们测试时,需要根据接口文档进行测试。
带来2个问题:
1.我们需要时刻的维护接口文档
2.如果忘记维护,会导致测试时,测试不成功,因为接口文档的描述和实际代码不一致
我们使用Swagger2进行自动的构建接口文档,
spring针对swagger2进行了整合,springfox
通过注解扫描,扫描controller包下的controller类,动态的生成接口文档,我们直接测试即可。
说白了,swagger2动态帮助我们维护接口文档。
集成swagger2
1.引入依赖
2.创建swagger2的配置类,检查swagger2的配置
3.在controller和handler中添加swagger2的相关注解,辅助swagger生成接口文档
4.浏览器测试。
1.引入依赖
pom.xml中声明依赖:
<!--swagger2支持-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${swagger2}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${swagger2}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-spring-webmvc</artifactId>
<version>${swagger2}</version>
</dependency>
2.创建swagger2的配置类,检查swagger2的配置
创建swaggerConfig.java,检查controller的扫描路径是不是自己的
@Configuration
//开启swagger2的webmvc支持
@EnableSwagger2WebMvc
public class SwaggerConfig {
@Bean
public Docket createRestApi() {
// 构建API文档 文档类型为swagger2
return new Docket(DocumentationType.SWAGGER_2)
.select()
// 配置 api扫描路径
.apis(RequestHandlerSelectors.basePackage("com.itheima.travel.controller"))
// 指定路径的设置 any代表所有路径
.paths(PathSelectors.any())
// api的基本信息
.build().apiInfo(new ApiInfoBuilder()
// api文档名称
.title("SSM黑马旅游Swagger接口文档")
// api文档描述
.description("SSM黑马旅游,描述信息......")
.contact(new Contact("黄星成", "http://www.itcatst.com", "58948528@qq.com"))
// api文档版本
.version("1.0") // 版本
.build());
}
}
在springmvc的配置中,palteformSpringMvcConfig.java中配置扫描和资源释放
@Configuration //声明当前类是一个配置。
public class PlatformSpringMvcConfig extends SpringMVCConfig {
/**
* @Description 登录拦截
*/
@Bean("loginInterceptor")
public LoginInterceptor loginInterceptor(){
return new LoginInterceptor();
}
@Autowired
private LoginInterceptor loginInterceptor;
/**
* 资源路径 映射
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
/**
* 支持webjars
*/
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
/**
* 支持swagger
*/
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
super.addResourceHandlers(registry);
}
/**
* @Description 拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 登录拦截
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")
.excludePathPatterns(
"/**/user/**",
"/**/seller/**",
"/**/category/**",
"/**/webjars/**",
"/**/swagger-ui.html",
"/**/swagger-resources/**",
"/**/v2/**"
);
}
}
3.在controller和handler中添加swagger2的相关注解,辅助swagger生成接口文档
常用swagger2注解
@Api:用在请求的类上,表示对类的说明
tags="说明该类的作用,可以在UI界面上看到的注解"
value="该参数没什么意义,在UI界面上也看到,所以不需要配置"
@ApiOperation:用在请求的方法上,说明方法的用途、作用
value="说明方法的用途、作用"
notes="方法的备注说明"
@ApiImplicitParams:用在请求的方法上,表示一组参数说明
@ApiImplicitParam:用在@ApiImplicitParams注解中,指定一个请求参数的各个方面
name:参数名
value:参数的汉字说明、解释
required:参数是否必须传
paramType:参数放在哪个地方
· header --> 请求参数的获取:@RequestHeader
· query --> 请求参数的获取:@RequestParam
· path(用于restful接口)--> 请求参数的获取:@PathVariable
· div(不常用)
· form(不常用)
dataType:参数类型,默认String,其它值dataType="Integer"
defaultValue:参数的默认值
@ApiResponses:用在请求的方法上,表示一组响应
@ApiResponse:用在@ApiResponses中,一般用于表达一个错误的响应信息
code:数字,例如400
message:信息,例如"请求参数没填好"
response:抛出异常的类
@ApiModel:用于响应类上,表示一个返回响应数据的信息
(这种一般用在post创建的时候,使用@RequestBody这样的场景,
请求参数无法使用@ApiImplicitParam注解进行描述的时候)
@ApiModelProperty:用在属性上,描述响应类的属性
4.浏览器测试http://127.0.0.1:8080/swagger-ui.html
注意:springmvc的配置文件添加注解@Configuration
7、redisson缓存
redisson缓存介绍
1.什么样的数据适合做缓存? 不经常发生改变的数据都可以做缓存
2.基于缓存,数据查询的流程? 优先查缓存,缓存有,直接返回,如果没有,从数据库获取,然后放入缓存。
3.如果缓存的数据发生了更新? 同步删除缓存数据。redis:基于内存,将数据保存到内存中。 key-value格式保存数据的。
jedis:只是一个基本的redis命令的封装。不用
redisson:是一个分布式的java操作redis的框架,底层采用的是Netty框架(异步非阻塞的),而且redisson内部提供了很多redis使用时的应用级解决方案:
作用:
分布式锁 -解决缓存击穿
限流器(令牌桶算法) -解决缓存穿透
布隆过滤器,分布式对象。。。。
redisson的github地址:https://github.com/redisson/redisson/wiki
spring集成redisson
spring集成redisson的核心:将redissonClient对象交给spring管理即可
redisson的参考api
spring集成redisson的步骤:
1.引入redisson的依赖
2.外部properties配置,配置redis的信息
3.springConfig中读取外部redis相关配置,创建redissonClient对象即可。
4.直接在DemoController中依赖注入redissonClient对象,注入成功,表示集成成功
1.travel-core引入redisson的依赖,父工程中版本做好声明。
<!--redisson 依赖-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
</dependency>
2.外部properties配置,配置redis的信息
# redis
redis.nodes=redis://127.0.0.1:6379
redis.connectTimeout=5000
redis.connectPoolSize=64
redis.connectionMinimumidleSize=64
redis.maxtotal=500
redis.timeout=4000
redis.retryInterval=1500
3.springConfig中读取外部redis相关配置,创建redissonClient对象即可。
/**
* rsson操作redis配置类
**/
@Configuration
@Log4j2
public class RedissonConfig {
/**
* redis连接地址
*/
@Value("${redis.nodes}")
private String nodes;
/**
* 获取连接超时时间
*/
@Value("${redis.connectTimeout}")
private int connectTimeout;
/**
* 最小空闲连接数
*/
@Value("${redis.connectPoolSize}")
private int connectPoolSize;
/**
* 最小连接数
*/
@Value("${redis.connectionMinimumidleSize}")
private int connectionMinimumidleSize;
/**
* 等待数据返回超时时间
*/
@Value("${redis.timeout}")
private int timeout;
/**
* 刷新时间
*/
@Value("${redis.retryInterval}")
private int retryInterval;
@Bean(value = "redissonClient",destroyMethod="shutdown")
public RedissonClient config() {
log.info("=====初始化RedissonClient开始======");
String[] nodeList = nodes.split(",");
Config config = new Config();
//单节点
if (nodeList.length == 1) {
config.useSingleServer().setAddress(nodeList[0])
.setConnectTimeout(connectTimeout)
.setConnectionMinimumIdleSize(connectionMinimumidleSize)
.setConnectionPoolSize(connectPoolSize)
.setTimeout(timeout);
//集群节点
} else {
config.useClusterServers().addNodeAddress(nodeList)
.setConnectTimeout(connectTimeout)
.setRetryInterval(retryInterval)
.setMasterConnectionMinimumIdleSize(connectionMinimumidleSize)
.setMasterConnectionPoolSize(connectPoolSize)
.setSlaveConnectionMinimumIdleSize(connectionMinimumidleSize)
.setSlaveConnectionPoolSize(connectPoolSize)
.setTimeout(3000);
}
log.info("=====初始化RedissonClient完成======");
return Redisson.create(config);
}
}
4.直接在DemoController中依赖注入redissonClient对象,注入成功,表示集成成功
缓存业务分析
步骤:
0.引入常量类,此常量类定义了缓存的key.
1.优先查询缓存,有直接返回
2.如果没有,再查询数据库,将数据保存到redis中
图片做缓存
package com.itheima.travel.service.impl;
@Service
public class AffixServiceImpl implements AffixService {
@Autowired
private AffixMapper affixMapper;
@Autowired
private RedissonClient redissonClient;
@Override
public List<AffixVo> findAffixByBusinessId(AffixVo affixVo) {
//1.优先查询缓存
//获取通用对象桶
RBucket<List<AffixVo>> bucket = redissonClient.getBucket(RedisConstant.AFFIXSERVICE_FINDAFFIXBYBUSINESSID + affixVo.getBusinessId());
List<AffixVo> affixVos = bucket.get();
if(!EmptyUtil.isNullOrEmpty(affixVos)){
System.out.println("***************走了缓存拿图片");
return affixVos;
}
//2.如果没有查询数据,并且保存到缓存中
System.out.println("***************从数据库获取图片");
//组装查询的条件
AffixExample affixExample = new AffixExample();
affixExample.createCriteria()
.andBusinessIdEqualTo(affixVo.getBusinessId());
List<Affix> affixList = affixMapper.selectByExample(affixExample);
//属性拷贝
affixVos = BeanConv.toBeanList(affixList, AffixVo.class);
//存储缓存,缓存数据存储半小时
bucket.set(affixVos,1800, TimeUnit.SECONDS);
return affixVos;
}
}
分类信息做缓存
package com.itheima.travel.service.impl;
@Service
@Log4j2
public class CategoryServiceImpl implements CategoryService {
@Autowired
private CategoryMapper categoryMapper;
@Autowired
private RedissonClient redissonClient;
@Override
public List<CategoryVo> findAllCategories() {
//1.从缓存查询
RBucket<List<CategoryVo>> bucket = redissonClient.getBucket(RedisConstant.CATEGORYSERVICE_FINDALLCATEGORY);
List<CategoryVo> categoryVoList = bucket.get();
if(!EmptyUtil.isNullOrEmpty(categoryVoList)){
log.debug("****************************8所有的类别信息查询缓存");
return categoryVoList;
}
log.debug("****************************8所有的类别信息查询数据库");
//2.缓存没有从数据库查
//没有任何条件
List<Category> categoryList = categoryMapper.selectByExample(null);
//属性拷贝
categoryVoList = BeanConv.toBeanList(categoryList, CategoryVo.class);
//保存到缓存
bucket.set(categoryVoList);
return categoryVoList;
}
}
收藏模块查询做缓存
@Override
public PageInfo<RouteVo> findMyFavoriteByPage(int pageNum, int pageSize) {
//1.查询数据的总数量
// select count(*) from tab_route where ....
//2.分页查询收藏的旅游线路
// select * from tab_route where ... limit (pageNum-1)*pageSize,pageSize
//3.封装到PageBean返回
/**
* 分页查询我收藏的旅游线路完整sql
* -- 当前用户收藏的旅游线路的分页
select r.* from tab_favorite f,tab_route r where f.route_id=r.id and f.user_id= '3022260735877126' limit 3,3
*/
//1.告知mybatis进行分页查询
PageHelper.startPage(pageNum,pageSize);
//2.查询所有即可
User user = (User) session.getAttribute(session.getId());
//这个list中,包含了分页的旅游线路信息 和 count数据
//list是什么类型? 真实的类型是Page类型
List<Route> list = favoriteMapperExt.findMyFavoriteByPage(user.getId());
/**
* 此处2个问题?
* 第一个,为什么不能先做集合的拷贝。total不正确
* 第二个,pageInfo的total属性的值哪里来的呢?
*/
//集合属性拷贝
// List<RouteVo> routeVoList = BeanConv.toBeanList(list, RouteVo.class);
//
// PageInfo<RouteVo> voPageInfo = new PageInfo<>(routeVoList);
//此处,total属性来自于list(Page)的total,list来自list(Page)本身
PageInfo<Route> pageInfo = new PageInfo<>(list);
System.out.println("分页查询的结果:"+pageInfo);
//属性拷贝
PageInfo<RouteVo> voPageInfo = new PageInfo<>();
//只能拷贝total属性,不能拷贝list。
BeanConv.toBean(pageInfo,voPageInfo);
//针对list进行拷贝
List<RouteVo> routeVoList = BeanConv.toBeanList(pageInfo.getList(), RouteVo.class);
voPageInfo.setList(routeVoList);
//旅游线路的图片信息走缓存
for(RouteVo routeVo:routeVoList){
//旅游线路的id封装成AffixVo类型的businessId中
List<AffixVo> affixVos = affixService.findAffixByBusinessId(AffixVo.builder().businessId(routeVo.getId()).build());
//赋值给旅游线路
routeVo.setAffixVoList(affixVos);
}
return voPageInfo;
}
旅游线路模块查询缓存
此模块和收藏模块一模一样
8、异常的统一处理
在travel-platform-web模块中的controller中,添加用于统一异常处理的BasicController
package com.itheima.travel.web;
/**
* @ClassName BasicController.java
* @Description 基础controller处理异常
*/
@RestControllerAdvice
public class BasicController {
@ExceptionHandler
public ResponseWrap<Boolean> ExceptionHandler(Exception e){
ProjectException projectException = null;
if (e instanceof ProjectException){
projectException= (ProjectException) e;
}else {
projectException = new ProjectException("10000","未定义异常");
}
return ResponseWrap.<Boolean>builder()
.code(projectException.getCode())
.msg(projectException.getMessage())
.operationTime(new Date())
.data(false)
.build();
}
}
9、跨域拦截器
跨域介绍
跨域的定义:
受到浏览器的同源策略的影响,如果发起ajax请求的服务器和ajax要请求的服务器的端口,域名,协议只要有一个不一样,浏览器都认为是跨域请求,那么浏览器为了保护服务器资源,哪怕服务给出响应了,但是浏览器会拒绝接受。
如何解决跨域呢?
1.jsonp,通过<srcipt>标签去引入资源,然后解析资源,再获取资源。
2.cross作为http协议的扩展,纳入到了http协议中。
cross的原理:服务器将需要进行跨域请求的客户端纳入收信任的范围,那么浏览器就会进行跨域请求了
cross底层原理:
简单请求:(只携带固定的那么几个请头)
浏览器发送请求时,会携带一个请求头:origin:就是发出ajax请求的域名
服务器接受到请求,会在响应中携带响应头。
Access-Control-Allow-Origin : 配置了服务器受信任的客户端
access-control-allow-methods: 允许跨域的请求方式
access-control-allow-credentials: 允许cookie跨域
Access-Control-Allow-Headers : 允许其他可以跨域的头部
复杂请求:(不局限于固定的几个请求)
浏览器会发2次请求,
第一次请求,会询问服务器,当前客户端发起的请求是否允许跨域。
第二次请求,浏览器发出跨域请求,进行访问资源。
配置跨域
1.给所有的controller方法添加@CrossOrigin,开启跨域
2.通过跨域拦截器,给所有的请求设置响应头。
1.给所有的controller方法添加@CrossOrigin,开启跨域
2.通过跨域拦截器,给所有的请求设置响应头。
跨域拦截器:
package com.itheima.travel.interceptors;
/**
* @Description:
*/
public class CrossInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
//解决跨域问题
String origin = request.getHeader("Origin");
//服务器设置允许跨域的 域名。
response.setHeader("Access-Control-Allow-Origin", origin);
response.setHeader("access-control-allow-methods", "*");
//是否支持cookie跨域
response.setHeader("access-control-allow-credentials", "true");
//指定头部跨域传递accessToken
response.setHeader("Access-Control-Allow-Headers", "Content-Type");
return true;
}
}
springmvc需要配置跨域拦截器 travel-web-platform 模块中的platformSpringMvcConfig.java配置跨域拦截器
10、其他
系统概述
旅游为用户提供一个方便的电子网络平台。该系统分前台官网部分和后台系统管理部分。旅行社通过后台管理系统将旅游信息发布在该平台上,并对整个旅游线路进行有效的控制、管理和统计;消费者通过系统官网方便快捷的选择或关注需要的旅游方案,享受旅行社或平台提供的各种旅游服务。
用户特性
本软件的最终用户将是官网用户(旅游者),旅行社管理人员
官网用户:只要求有基本的电脑操作知识,互联网知识即可。
旅行社管理人员:要求了解基本的电脑操作知识,经过一定时间的使用培训即可
概要设计
1、业务架构
层级划分
控制层:使用springmvc做响应的同时使用拦截器处理
业务层:主体业务分为后台管理中心与官方网站,其中后台管理主要负责商家及电商用户管理、角色管理、资源管理和线路管理,官方网站:主要负责主体业务对客户的呈现
基础模块:图片服务、日志服务、
2、技术架构
3、数据库概要设计
表设计:
1.分析业务中的实体。
2.分析实体之间的关系。(e-r图)
3.创建表和表关系
a.一个实体一张表
b.实体有哪些属性,表就有哪些字段。
c.表关系设计。
注意:要满足第三范式。
【1】业务表
表名 | 描述 |
---|---|
tab_affix | 图片表【附件】 |
tab_category | 分类表 |
tab_favorite | 收藏表 |
tab_route | 路线表 |
tab_seller | 商家表 |
tab_user | 用户表 |
####
【2】业务表关系
表名 | 描述 |
---|---|
tab_affix | 多个图片属于同一个线路 |
tab_category | 一个分类下面可以有多条线路 |
tab_favorite | 多条收藏属于同一个用户 |
tab_route | 一条线路有多个图片、被关注多次、属于某个商家、属于某个分类 |
tab_seller | 一个商家可以发布多条线路 |
tab_user | 一个用户可以关注多条线路 |
4、接口概要设计
【1】用户模块
接口名称 | 接口描述 |
---|---|
registerUser | 注册接口 |
loginUser | 登录接口 |
logoutUser | 退出接口 |
isLogin | 是否登录接口【隐藏功能】 |
【2】收藏模块
接口名称 | 接口描述 |
---|---|
findMyFavorite | 我的收藏 |
addFavorited | 添加收藏 |
isFavorited | 是否收藏 |
【3】分类模块
接口名称 | 接口描述 |
---|---|
findAllCategory | 查询所有分类 |
【4】线路模块
接口名称 | 接口描述 |
---|---|
findRouteByPage | 多条件线路查询分页 |
findRouteById | 线路详情 |
findFavoriteRankRouteByPage | 按照收藏次数分页查询旅游线路信息 |
5、模块功能开发举例
【1.1】功能描述
1、在register.html页面上,当用户填写完基本信息后,点击注册按钮时,为用户创建账号
2、注册页面点击“立即登录”跳转login.html页面
【1.2】加载方法
1、首页中右上角,如果当前用户处于【未登录】状态,显示“注册”,点击“注册”可以跳转到注册页面
2、用户登录页面,右下方点击立刻注册可以跳转到注册页面
【1.3】页面操作
功能操作 | 功能操作描述 | 重要度 |
---|---|---|
注册 | 点击【注册】,创建当前用户 | 高 |
立即登录 | 点击【立即登录】,跳转登录页面 | 高 |
【1.4】约束条件
1、注册页面,提交表单时,需要的约束条件
字段 | 是否必须 | 是否唯一 |
---|---|---|
用户名 | Y | Y |
密码 | Y | N |
Y | Y | |
姓名 | Y | N |
手机 | Y | Y |
性别 | Y | N |
生日 | Y | N |
【1.5】页面跳转
1、注册成功,自动完成登录过程,跳转首页
2、注册失败,本页面不做跳转,返回错误提示信息
【1.6】接口开发详情
请求路径
http://127.0.0.1:8080/user/register
请求方式
请求方式:post
Content-Type : application/json
方法信息
接口类信息:com.itheima.travel.web.UserController
方法名:registerUser
请求参数
字段 | 类型 | 长度 | 注释 | 是否为空 |
---|---|---|---|---|
realName | String | 200 | 真实姓名 | Y |
telephone | String | 12 | 电话 | N |
sex | String | 200 | 性别 | N |
username | String | 100 | 用户名称 | N |
password | String | 32 | 密码 | N |
birthday | Date | 0 | 生日 | Y |
String | 100 | 邮箱 | N |
请求json字符串
{
"birthday": "2020-11-03T06:52:08.320Z",
"email": "59948528@qq.com",
"password": "pass",
"realName": "束XX",
"sex": "1",
"telephone": "15156403088",
"username": "shu"
}
返回信息
类型:布尔类型
{
"code": "200",
"msg": "操作成功",
"operationTime": "2020-11-06 03:17:22",
"data": true,
"webSite": null
}
业务逻辑
1、保存用户信息到数据库
2、注册成功后,自动登录,把用户信息放入session中,并且跳转首页
…(img-q2gLgjiB-1624416768127)]
表名 | 描述 |
---|---|
tab_affix | 多个图片属于同一个线路 |
tab_category | 一个分类下面可以有多条线路 |
tab_favorite | 多条收藏属于同一个用户 |
tab_route | 一条线路有多个图片、被关注多次、属于某个商家、属于某个分类 |
tab_seller | 一个商家可以发布多条线路 |
tab_user | 一个用户可以关注多条线路 |
4、接口概要设计
【1】用户模块
接口名称 | 接口描述 |
---|---|
registerUser | 注册接口 |
loginUser | 登录接口 |
logoutUser | 退出接口 |
isLogin | 是否登录接口【隐藏功能】 |
【2】收藏模块
接口名称 | 接口描述 |
---|---|
findMyFavorite | 我的收藏 |
addFavorited | 添加收藏 |
isFavorited | 是否收藏 |
【3】分类模块
接口名称 | 接口描述 |
---|---|
findAllCategory | 查询所有分类 |
【4】线路模块
接口名称 | 接口描述 |
---|---|
findRouteByPage | 多条件线路查询分页 |
findRouteById | 线路详情 |
findFavoriteRankRouteByPage | 按照收藏次数分页查询旅游线路信息 |
5、模块功能开发举例
【1.1】功能描述
1、在register.html页面上,当用户填写完基本信息后,点击注册按钮时,为用户创建账号
2、注册页面点击“立即登录”跳转login.html页面
【1.2】加载方法
1、首页中右上角,如果当前用户处于【未登录】状态,显示“注册”,点击“注册”可以跳转到注册页面
2、用户登录页面,右下方点击立刻注册可以跳转到注册页面
【1.3】页面操作
功能操作 | 功能操作描述 | 重要度 |
---|---|---|
注册 | 点击【注册】,创建当前用户 | 高 |
立即登录 | 点击【立即登录】,跳转登录页面 | 高 |
【1.4】约束条件
1、注册页面,提交表单时,需要的约束条件
字段 | 是否必须 | 是否唯一 |
---|---|---|
用户名 | Y | Y |
密码 | Y | N |
Y | Y | |
姓名 | Y | N |
手机 | Y | Y |
性别 | Y | N |
生日 | Y | N |
【1.5】页面跳转
1、注册成功,自动完成登录过程,跳转首页
2、注册失败,本页面不做跳转,返回错误提示信息
【1.6】接口开发详情
请求路径
http://127.0.0.1:8080/user/register
请求方式
请求方式:post
Content-Type : application/json
方法信息
接口类信息:com.itheima.travel.web.UserController
方法名:registerUser
请求参数
字段 | 类型 | 长度 | 注释 | 是否为空 |
---|---|---|---|---|
realName | String | 200 | 真实姓名 | Y |
telephone | String | 12 | 电话 | N |
sex | String | 200 | 性别 | N |
username | String | 100 | 用户名称 | N |
password | String | 32 | 密码 | N |
birthday | Date | 0 | 生日 | Y |
String | 100 | 邮箱 | N |
请求json字符串
{
"birthday": "2020-11-03T06:52:08.320Z",
"email": "59948528@qq.com",
"password": "pass",
"realName": "束XX",
"sex": "1",
"telephone": "15156403088",
"username": "shu"
}
返回信息
类型:布尔类型
{
"code": "200",
"msg": "操作成功",
"operationTime": "2020-11-06 03:17:22",
"data": true,
"webSite": null
}
业务逻辑
1、保存用户信息到数据库
2、注册成功后,自动登录,把用户信息放入session中,并且跳转首页