SpringBoot(合集)
- 微服务阶段
javase:OOP
mysql:持久化
html+css+js+jquery+框架
javaweb:独立开发MVC三层架构的网站
SSM:框架;简化我们的开发流程,配置也开始较为复杂
war:tomcat运行
Spring再简化:SpringBoot -jar:内嵌tomcat;微服务·架构!
服务越来越多:springcloud
- SpringBoot学习路线
SpringBoot
- 是什么
- 如何配置编写yaml
- 自动装配的原理
- 集成web开发:业务的核心
- 分布式开发:Dubbo+zookeeper
- swagger:接口文档
- 任务调度
- SpringSecurity:Shiro
SpringCould
- 微服务
- springcould入门
- Resful风格
- Eureka
- Ribbon
- Feign
- HyStrix
- Zuul路由网关
- SpringCloud config:git
- 原理初探
自动配置:
pom.xml
- spring-boot-dependencies:核心依赖在父工程中
- 我们写或引入一些Springboot依赖的时候,不需要指定版本,就因为由这些版本仓库
启动器:
```xml
org.springframework.boot spring-boot-starter 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- **启动器:**Springboot的启动场景
- 比如spring-boot-starter-web,他会帮我们导入web环境的所有依赖!
- springboot会将
- 注解
- ```java
@SpringBootApplication:标注这个类是一个springboot的应用:启动类下的所有资源被导入
- SpringBootConfiguration:Springboot配置
- @Configuration:Spring的配置类
- @Component:说明这也是一个Spring的组件
- @EnableAutoConfiguration:自动配置
- @AutoConfigurationPackage:自动配置包
- @Import(AutoConfigurationPackages.Registrat.class):自动配置"包注册"
- @Import(AutoConfigurationImportSelector.class):自动配置导入选择
//获取所有配置
List<String> configurations=getCandidateConfigurations(annotationMetadata,attribuates);总结:springboot所有的自动配置都是在启动的时候扫面并加载:spring.factories所有的自动配置类都在这里,但不一定生效,要判断条件是否成立,只有导入对应的star,就有对应的启动器,有了启动器,自动配置就会生效,然后就配置成功。
步骤
- springboot启动,从类路径下/META-INF/spring.factories获取指定的值;
- 将这些自动配置的类导入容器,自动装配生效
- 整合javaEE,解决方案和自动配置的东西都在spring-boot-autoconfig-2.2.0.RELEASE.JAR中
- 他会把所有需要导入的组件,以类名的方式返回,这些组件就会被添加到容器
- 了解主启动类怎么运行
开始执行run()方法
- 推断应用类型是普通项目还是Web项目
- 查找并加载所有可用初始化器,设置带initializers属性中
- 找出所有的应用程序监听器,设置到listeners属性中
- 推断并设置main方法的定义类,找到运行的主类
- yaml语法
语法结构:key: 空格 value
1 | #对象 |
- 给属性赋值的几种方式
1 | server: |
1 |
|
- JSR303校验
1 | 被注释元素必须为空 |
- 多环境切换及配置文件位置
配置文件可以存放的位置:
- file:./config/
- file:./
- classpath:/config/
- classpath:/
1 | #多个properties文件,配置文件的指定 |
1 | #yaml文件中环境的切换 |
- 导入静态资源
- 在springboot中,我们可以使用以下方式处理静态资源
- webjars localhost:8080/webjars/
- public,static,/**,resources localhost:8080/
- 优先级:resource>static(默认)>public
- Thymeleaf模板引擎
templates目录下的所有页面,只能通过controller来跳转
实现过程:
- 导入依赖
1 | <!--导入模板引擎依赖--> |
- Thymeleaf的使用
1 |
|
- 配置静态资源目录
1 |
|
- MVC配置原理
- 自定义视图解析器
1 |
|
- 扩展springmvc
1 | @Configuration |
- 国际化
首页配置:所有页面静态资源都需要thymeleaf接管
页面国际化:
- 配置i18n文件
- 如果需要在项目中进行按钮自动切换,需要自定义组件LocalResolver
- 需要将自己写的组件配置到spring容器中@Bean
- #{}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21//自定义国际化
public class MyLocalResolver implements LocaleResolver {
//解析请求
public Locale resolveLocale(HttpServletRequest request) {
//获取参数中的请求连接
String language=request.getParameter("l");
Locale local=Locale.getDefault();//如果没有使用默认
if(!StringUtils.isEmpty(language)){
String[] split=language.split(language);
local=new Locale(split[0],split[1]);//国家。地区
}
return local;
}
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14//自定义视图解析器
public class MymvcController implements WebMvcConfigurer {
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
}
//注入容器
public LocaleResolver MyLocalResolver(){
return new MyLocalResolver();
}
}1
2
3
4
5
6
7
8#关闭模板引擎的缓存
spring:
thymeleaf:
cache: false
#国际化配置文件的文件位置
message.basename: i18n.login
mvc:
data-format: yyyy-MM-dd
- 拦截器(Aop)
使用场景:
- 登陆验证
- 权限验证(校验token)
- 日志记录(用户ip,访问时间)
- 处理cookie,本地化,国际化,主题等
- 性能监控,监控处理时长
- 通用行为:读取cookie得到用户信息并将用户对象放入请求,方便后续流程使用,还有如提取Locale,Theme信息等,只要是多个处理器都需要的即可使用拦截器实现
主要方法:
(1)preHandle:该方法在调用Controller方法或获取静态资源前被调用(静态资源包括html、js等)。
(2)postHandle:该方法在调用Controller方法或获取静态资源后,但是视图还没有被渲染前调用。
(3)afterCompletion:该方法在视图渲染后进行调用,主要用来清除资源。
实现过程:
- 实现HandlerInterceptor中的方法
1 | //自定义拦截器 |
- 编写拦截器的配置类文件,实现
WebMvcConfigurer
接口中的方法,配置类上添加@Configuration
注解
1 |
|
- 过滤器
使用场景:
过滤敏感词汇,防止sql注入
设置字符编码
URL级别的权限访问控制
压缩响应信息
权限控制、用户登录(非前端后端分离场景)
实现过程:实现
Filter
接口,启动类
上添加@ServletComponentScan
注解
1 |
|
- 拦截器和过滤器的区别
拓展
:Servlet和Controller的区别
使用Servlet可以收集来自网页表单的用户输入,还可以动态创建网页。DispatcherServlet是SpringMVC中唯一的Servlet,Servlet容器(Tomcat)把所有的请求都转发到DispatcherServlet,然后通过HandlerMapping把请求路由到具体的Controller中。因此,Controller只是一个普通的Java Bean。
执行顺序:tomcat—>filter—>Servlet—>Interceptor—>controller
区别:
实现原理不同
- Filter是基于函数回调实现的
- Interceptor是基于反射实现的
触发时机不同
- 过滤器Filter是在请求进入Tomcat等容器后,servlet处理之前进行调用的。
- 拦截器Interceptor是在请求进入servlet后,执行Controller之前进行调用的
使用范围不同
- 依赖于Tomcat等容器,所以它只能在web程序中使用
- 由Spring容器进行管理,并不依赖Tomcat等容器,既可以应用在web程序中,也可以应用在非web程序中
拦截范围不同
- 过滤器Filter几乎可以拦截所有进入容器的请求
- 拦截器Interceptor只会对Controller请求或访问static目录下的静态资源请求起作用
初始化时机不同
- 过滤器Filter是随着Tomcat等web容器启动时而进行初始化。
- 拦截器Interceptor时随着spring启动而进行初始化
使用场景:二者相比拦截器功能更强大些,Filter能做的事情,它都能做,而且可以在请求前,请求后执行,比较灵活。
- Filter主要用来设置字符编码、过滤敏感词汇和URL级别的简单权限控制
- 拦截器更适合记录比较详细的信息或比较复杂的权限管理
- 上传
储存方式:
- 搭建Nginx静态资源服务器
- OSS对象存储
上传类型:
本地文件上传:普通io流
网页资源上传:URL,HttpURLConnection,InputStream
1
2
3URL url=new URL(url)
HttpURLConnection coon=(HttpURLConnection)url.openConnection();
InputStream in=coon.getOutputStream();
- 下载
- 服务端设置
1 | response.setHeader("Content-Type","application/octet-stream"); |
- 示例代码
1 | //网络端资源文件下载到本地 |
- 学习目标
- JDBC
- Mybatis
- Druid
- Shiro
- Spring Security
- 异步任务,邮件发送,定时任务
- Swagger
- Dubbo+Zookeeper
- JDBC
配置文件:
1 | Spring: |
- Druid
1 | #整合Druid https://www.cnblogs.com/hellokuangshen/p/12497041.html |
- Mybatis
```xml
org.mybatis.spring.boot mybatis-spring-boot-starter 2.1.1 1
2
3
4
5
6
7
8
9
10
11
12
13
2. ```java
//编写mapper接口
@Mapper
@Repository
public interface UserMapper {
@Select("select * from EmployeeMap")//例:注解方式
List<EmployeeMap> queryUserList();
user queryUserById(String name);
int addUser(user user);
int updateUser(user user);
int deleteUser(String name);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39<!--编写sql语句抽取到xml文件中-->
<mapper namespace="com.kuang.mapper.EmployeeMapper">
<!--<cache/>开启缓存-->
<resultMap id="EmployeeMap" type="Employee">
<id property="id" column="eid"/>
<result property="lastName" column="last_name"/>
<result property="email" column="email"/>
<result property="gender" column="gender"/>
<result property="birth" column="birth"/>
<association property="eDepartment" javaType="Department">
<id property="id" column="did"/>
<result property="departmentName" column="dname"/>
</association>
</resultMap>
<select id="getEmployees" resultMap="EmployeeMap">
select e.id as eid,last_name,email,gender,birth,d.id as did,d.department_name as dname
from department d,employee e
where d.id = e.department
</select>
<insert id="save" parameterType="Employee">
insert into employee (last_name,email,gender,department,birth)
values (#{lastName},#{email},#{gender},#{department},#{birth});
</insert>
<select id="get" resultType="Employee">
select * from employee where id = #{id}
</select>
<delete id="delete" parameterType="int">
delete from employee where id = #{id}
</delete>
</mapper>```yaml
#连接数据库
spring:
datasource:username: root password: 123456 #?serverTimezone=UTC解决时区的报错 url: jdbc:mysql://localhost:3306/ssmbuild?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8 driver-class-name: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource #Spring Boot 默认是不注入这些属性值的,需要自己绑定 #druid 数据源专有配置 initialSize: 5 minIdle: 5 maxActive: 20 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 validationQuery: SELECT 1 FROM DUAL testWhileIdle: true testOnBorrow: false testOnReturn: false poolPreparedStatements: true #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入 #如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority #则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j filters: stat,wall,log4j maxPoolPreparedStatementPerConnectionSize: 20 useGlobalDataSourceStat: true connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
#整合mybatis
mybatis:#实体类存在的位置
type-aliases-package: com.jilf.demo.pojo
#扫描sql抽取xml的位置
mapper-locations: classpath:mybatis/mapper/*.xml1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
### - MybatisPlus
#### - 配置文件
```properties
#开启日志,方便调试
mybatis-plus.configuration.log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mybatis-plus.mapper-locations=classpath:com/atguigu/eduservice/mapper/xml/*.xml
#返回json的全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
<!--项目打包时会将java目录中的*.xml文件也进行打包-->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
- pom依赖
1 | <!--导入依赖--> |
- 代码生成器
1 | //代码生成器 |
1 | //UserMapper继承BaseMapper |
1 |
|
- 命名特点:表中字段create_time,实体类中createTime
- 自动填充
不用set,用mp方法实现
在实体类的属性上添加注解@TableField(fill=FieldFill.INSERT)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class User {
//value="字段",可字段和属性的映射,即使不符合命名特点
//type,设置主键类型和主键生成策略
//主键自动填充,需要设置主键自增
private long id;
private String name;
private Integer age;
private String email;
//添加时自动填充
private Date createTime;
//修改时自动填充
private Date updateTime;
}实现MetaObjectHandler中的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17//实现MetaObjectHandler中的方法
public class MymetaObjectHandler implements MetaObjectHandler {
//使用mp实现添加操作,该方法执行
public void insertFill(MetaObject metaObject) {
//字段 要修改的值
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("updateTime",new Date(),metaObject);
}
//使用mp修改操作,该方法执行
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime",new Date(),metaObject);
}
}
- 乐观锁
作用:
主要解决 丢失更新
若不考虑事务隔离性,产生读问题:脏读,不可重复读,幻读(虚读)
实现方式:
- 取出记录时,获得当前version
- 更新时,带上这个version
- 执行更新时,set version=newVersion where version=oldVersion
- 如果version不对,就更新失败
实现过程:
数据库中添加version字段
1
AlTER TABLE 'user' add column 'version' INT
实体类添加@Version注解
1
2
3
private Integer version;元对象处理接口添加version的inser默认值
1
2
3
4
5//在MymetaObjectHandler中
public void insertFill(MetaObject metaObject){
this.setFieldValByName("version",1,metaObject);
}```java
//在MyBatisPlus的接口中注册Bean,创建配置类;插件固定
@EnableTransactionManagement
@Configuration
@MapperScan(“扫描mapper包”)
public class MybatisPlusConfig{/** *乐观锁插件 */ @Bean public OptimisticLockerInterceptor optimisticLockInterceptor(){ return new OptimisticLockerInterceptor(); }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#### - 悲观锁
- 串行
#### - mp的简单查询
```java
@Test//根据id查询
public void testSelectById(){
User user= userMapper.selectById(1);
System.out.println(user);
}
@Test//通过多个id,批量查询
public void testSelectBatchIds(){
List<User> users=userMapper.selectBatchIds(Arrays.asList(1L,2L,3L));
users.forEach(System.out::println);
}
@Test//通过map封装条件查询,批量查询
public void testSelectByMap(){
HashMap<String,Object> map= new HashMap<>();
map.put("name","zzzzz");
map.put("age",20);
List<User> users=userMapper.selectByMap(map);
for (User user:users) {
System.out.println(user);
}
- MyBatisPlus实现分页
- 配置分页插件
1 |
|
- 构建Page对象并测试分页
1 | //分页操作 |
- mp实现逻辑删除
表中添加逻辑删除字段,对应实体类添加属性,属性添加注解**@TableLogic**
配置逻辑删除插件
1
2
3
4//逻辑删除插件
public ISqlInjector sqlInjector(){
return new LogicSqlInjector();
}1
2
3
4
5mybatis:
global-config: #逻辑删除默认值为0,1.不设置也可以
db-config:
logic-delete-value: 1
logic-not-delete-value: 01
2
3
4
5//逻辑删除测试文件
public void testSqlInjector(){
int result=userMapper.deleteById(4L);
System.out.println(result);
}
- 配置性能分析插件
1 | //SQL执行性能分析插件,开发环境使用,线上不推荐,maxTime指的是sql最大执行时长 |
- 条件查询
1 |
|
- 插件配置类
1 |
|
- 自动填充处理类
1 | //自动填充 |
- SpringSecurity(安全)
简介:针对Spring boot的安全框架,对于安全控制,仅需引入spring-boot-starter-security模块。进行少量配置,即可实现强大的安全管理!
几个重要的类:
- WebSecurityConfigurerAdapter:自定义Security策略
- AuthenticationManagerBuillder:自定义认证策略
- @EnableWebSecurity:开启WebSecurity模式,@Enablelxxxx开启某个功能
Spring Security的两个主要目标是“认证”和”授权”(访问控制)
- “认证”:(Authentication)
- “授权”:(Authorization)
常见的权限控制模块:
- 功能权限
- 访问权限
- 菜单权限
- 基本用法
创建项目添加依赖
1
2
3
4
5
6
7
8
9
10
11<!--导包-->
<!--security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--security-thymeleaf整合包-->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
<version>3.0.4.RELEASE</version>添加hello接口
1
2
3
4
5
6
7
8
public class HelloController{
public String hello(){
return "hello";
}
}启动项目测试:访问hello接口,默认security账户名为
user
,密码控制台
随机生成
- 配置用户名和密码
若开发者不满意默认生成,可在application.properties
中配置默认用户名
和密码
1 | spring.security.user.name=sang |
- 基于内存的认证
通过继承WebSecurityConfigurerAdapter,重写
configure(AuthenticationManagerBuilder auth)
,实现基于内存的认证1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();//不对称加密
}
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin").password("123").roles("admin","user")
.and()
.withUser("sang").password("1234").roles("user");
}
}
- HttpSecurity
根据实际情况自定义角色管理功能,重写WebSecurityConfigurerAdapter的另一个方法
1 | package com.jilf.test.config; |
- 自定义登陆页面
- 在resource下创建static文件夹,将登录表单页面的css,js,img放在static下
- 重写configure(WebSecurity web)放行静态资源
1 |
|
- 整合Swagger
-2.0版本
1 | <!--swagger--> |
1 | //配置类 |
-3.0版本(推荐)
引入依赖
1
2
3
4
5
6
7
8
9
10<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
<!--在启动类或配置类上添加@EnableOpenApi注解-->
<!--引入该依赖就已经可以访问swagger接口文档,路径地址swagger-ui/index.html-->
<!--推荐springboot2.6一下整合-->
<!--2.6以上需要配置文件添加:spring.mvc.pathmatch.matching-strategy: ant_path_matcher>配置信息(自定义)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class Swagger3Config {
// 这个需要在配置文件中配置是否开启Swagger
private Boolean SwaggerEnable;
public Docket createRestApi() {
return new Docket(DocumentationType.OAS_30)
.apiInfo(apiInfo())// 添加文档信息
.select()
// 选择要显示的包 如果是下面的写法则会将所有的接口都会展示出来 无论你有没有写注解
.apis(RequestHandlerSelectors.basePackage("com.example.demo.controller"))
// 而这种写法则是将所有的有注解的接口展示出来
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
.paths(PathSelectors.any())
.build()
// 是否启用Swagger
.enable(SwaggerEnable);
}
/**
* 文档信息
* @return ApiInfo
*/
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("这是swagger3的接口文档")
.description("更多请咨询服务开发者mt1020")
.contact(new Contact("mt1020", "浙江省嘉兴市", "3174393"))
.version("1.0")
.build();
}
}
- 统一返回结果类
1 | //统一返回结果的类 |
- 带条件的分页查询
1 |
|
- 全局统一异常处理类
1 |
|
- 自定义异常处理类
- 创建自定义异常类
1 |
|
- 业务中需要的位置抛出GuliException
1 | try{ |
- 添加异常处理方法
1 |
|
- 统一日志处理
配置日志级别:OFF,FATAL,ERROR,WARN,INFO,DEBUG,ALL
默认情况下,spring boot从控制台打印出来日志级别只有INFO及以上级别,可以配置日志级别
1
2
3#设置日志级别
logging.level.root=WARN
#这种方式只能将日志打印在控制台配置Logback日志
1
2#删除: logging.level.root=WARN
#mybatis-plus.configuration.log-impl: org.apache.ibatis.logging.stdout.StdOutImpl在resource中创建logback-spring.xml
https://blog.csdn.net/datastructure18/article/details/120919329 如果程序运行出现异常,把异常信息输出到文件中 - 在异常处理类上添加@Slf4j注解 - 在异常方法中输出异常语句:log.error(e.getMessage());
- ES6前端技术
1 | // var 声明的变量没有局部作用域 |
- OSS云存储
https://www.aliyun.com/product/oss?spm=5176.19720258.J_3207526240.36.e9392c4aCHGaCR
导入依赖
1
2
3
4
5
6
7
8
9
10
11
12<!--阿里云oss依赖-->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.1.0</version>
</dependency>
<!--日期工具栏依赖-->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version></version>
</dependency>配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15#服务端口
server.port=8002
#服务名
spring.application.name=service-oss
#环境设置
spring.profiles.active=dev
#阿里云OSS
#不用的服务器,地址不同
aliyun.oss.file.endpoint=oss-cn-hongkong.aliyuncs.com
aliyun.oss.file.keyid=xxxxxxxxxxxxxxxxxxxxx
aliyun.oss.file.keysecret=xxxxxxxxxxxxxxxxxxxxx
#bucket可以在控制台创建,也可以用java代码创建
aliyun.oss.file.bucketname=xxxxxxxxxxx创建工具类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class ConstantPropertiesUtils implements InitializingBean {//初始化接口,重写afterPropertiesSet()方法
//读取配置文件
// Endpoint
private String endpoint;
// 阿里云账号AccessKey
private String accessKeyId;
private String accessKeySecret;
// 填写Bucket名称
private String bucketName;
//定义静态常量
public static String END_POINT;
public static String ACCESS_KEYID;
public static String ACCESS_KEYSECRET;
public static String BUCKET_NAME;
public void afterPropertiesSet() throws Exception {
END_POINT=endpoint;
ACCESS_KEYID=accessKeyId;
ACCESS_KEYSECRET=accessKeySecret;
BUCKET_NAME=bucketName;
}
}
- EasyExcel
应用场景:数据导入,导出,异构系统之间的数据传输
```xml
com.alibaba easyexcel 2.1.1 <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>${poi.version}</version> </dependency> <!--xlsx--> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>${poi.version}</version> </dependency>
1
2
3
4
5
6
7
8
9
10
11
2. ```java
<!--封装实体-->
@Data
public class DemoData {
@ExcelProperty("学生编号")
private Integer sno;
@ExcelProperty("学生姓名")
private String name;
}
```java
public class test { public static void main(String[] args) { //写法一: //写入文件夹地址和excel文件名称 String filename="D://write.xlsx"; EasyExcel.write(filename,DemoData.class).sheet("学生列表").doWrite(getData()); //写法二: //需要手动关闭流 String fileName = "F:\\112.xlsx"; // 这里 需要指定写用哪个class去写 ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build(); WriteSheet writeSheet = EasyExcel.writerSheet("写入方法二").build(); excelWriter.write(data(), writeSheet); /// 千万别忘记finish 会帮忙关闭流 excelWriter.finish(); } private static ListgetData(){ List list=new ArrayList<>(); for(int i=0; i<10; i++){ DemoData demoData=new DemoData(); demoData.setSno(i); demoData.setName("张三"+i); list.add(demoData); } return list; } } 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
4. ```java
//测试读操作
@RestController
@CrossOrigin
@RequestMapping("/eduservice/subject")
public class EduSubjectController {
@Autowired
private EduSubjectService subjectService;
//添加课程分类
//获取上传过来的文件,把文件内容读出来
@PostMapping("addSubject")
public R addSubject(MultipartFile file){
//上传过来的文件
subjectService.saveSubject(file,subjectService);
return R.ok();
}
}```java
@Service
public class EduSubjectServiceImpl extends ServiceImpl<EduSubjectMapper, EduSubject> implements EduSubjectService {@Override//添加课程分类 public void saveSubject(MultipartFile file,EduSubjectService subjectService) { try{ //文件输入流 InputStream in=file.getInputStream(); //调用方法进行读取 EasyExcel.read(in, SubjectData.class, new SubjectExcelListener(subjectService)).sheet().doRead(); }catch (Exception e){ e.printStackTrace(); } }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
- ```java
public class SubjectExcelListener extends AnalysisEventListener<SubjectData> {
//因为SubjectListener不能交给spring进行管理,需要自己new,不能注入其他对象
//不能实现数据库操作
public EduSubjectService subjectService;
public SubjectExcelListener() { }
public SubjectExcelListener(EduSubjectService subjectService) {
this.subjectService = subjectService;
}
@Override
public void invoke(SubjectData subjectData, AnalysisContext analysisContext) {
if (subjectData == null){
throw new GuliException(20001,"文件数据为空");
}
//一行一行读,每次读取两个值,第一个值为一级分类,第二个值为二级分类
//判断一级分类是否重复
EduSubject existOneSubject=this.existOneSubject(subjectService,subjectData.getOneSubjectName());
if(existOneSubject == null){//没有一级分类,进行添加
existOneSubject=new EduSubject();
existOneSubject.setParentId("0");
existOneSubject.setTitle(subjectData.getOneSubjectName());//一级分类名称
subjectService.save(existOneSubject);
}
String pid=existOneSubject.getId();
//添加二级分类
EduSubject existTwoSubject=this.existOneSubject(subjectService,subjectData.getTwoSubjectName(),pid);
if(existTwoSubject == null){
existTwoSubject = new EduSubject();
existTwoSubject.setParentId(pid);
existTwoSubject.setTitle(subjectData.getTwoSubjectName());//二级分类名称
subjectService.save(existTwoSubject);
}
}
//判断一级分类不重复添加
private EduSubject existOneSubject(EduSubjectService subjectService,String name ){
QueryWrapper<EduSubject> wrapper=new QueryWrapper<>();
wrapper.eq("title",name);
wrapper.eq("parent_id",'0');
EduSubject onesubject=subjectService.getOne(wrapper);
return onesubject;
}
//判断二级分类不重复添加
private EduSubject existOneSubject(EduSubjectService subjectService,String name,String pid ){
QueryWrapper<EduSubject> wrapper=new QueryWrapper<>();
wrapper.eq("title",name);
wrapper.eq("parent_id",pid);
EduSubject twosubject=subjectService.getOne(wrapper);
return twosubject;
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
}
}```java
@Data
public class SubjectData {@ExcelProperty(index = 0) private String oneSubjectName; @ExcelProperty(index = 1) private String twoSubjectName;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
### - 富文本编辑器
参考:[首页 - Vue Element Admin (gitee.io)](https://panjiachen.gitee.io/vue-element-admin/#/dashboard)
1. 复制脚本库:将脚本库复制到static目录下(在vue-element-admin-master的static路径下)
2. 配置html变量
- 在webpack.dev.conf.js中添加配置,
- 使在html页面中可以使用这里定义的Base_URL变量
```javascript
new HtmlWedpackPlugin({
……,
templateParameters: {
BASE_URL:config.dev.assetsPublicPath+config.dev.assetsSubDirectory
}
})
引入脚本:在guli-admin/index.html中引入js脚本
1
2<script src=<%= BASE_URL %>/tinymce4.7.5/tinymce.min.js></script>
<script src=<%= BASE_URL %>/tinymce4.7.5/langs/zh_CN.js></script>
组件使用:
复制组件:src/components/Tinymce
引入组件:课程信息中引入Tinymce
1
2
3
4
5
6
7<script>
import Tinymce from '@/components/Tinymce'
export default {
components:{Tinymce},
}
</script>组件模板
1
2
3<el-form-item label="课程简介">
<tinymce :height="300" />
</el-form-item>组件样式
1
2
3
4
5<style scoped>
.tinymce-container {
line-height: 29px;
}
<style>
- Git
安装:https://registry.npmmirror.com/binary.html?path=git-for-windows/v2.36.1.windows.1/
配置
1
2
3
4
5#配置用户·和邮箱
git config --global user.name "Jilfoyle"
git config --global user.email "918703864@qq.com"
#查询配置信息
git config --global --listgit基本理论:
工作区域:工作目录(working),暂存区(Stage/Index),资源库(Repository),远程仓库(Remote)
项目搭建
1
2git init #本地初始化
git clone url #远程clonegit文件操作
- 文件
- nginx
1 | #常用命令 |
- 打包操作
1 | <!--1.项目打包时会将java目录中的*.xml文件也进行打包--> |
- 添加jar包到本地maven仓库
1 | #例:添加aliyun-java-vod-upload-1.4.14.jar |
- 微服务
1 | 1.微服务是架构风格 |
- SpringCloud
1 | SpringCloud是一系列框架的集合,它利用Spring Boot的简化了分布式基础设施的开发,如服务发现。服务注册,配置中心,消息总线,负载均衡,熔断器,数据监控等,都可以用SpringBoot的开发风格做到一键启动和部署 |
- SpringCloud基础服务组件
- 服务发现—Nacos
- 服务调用—Netflix Feign
- 熔断器—Netflix Hystrix
- 服务网关—Spring Cloud GateWay
- 分布式配置—Spring Cloud Config(Nacos)
- 消息总线—Spring Cloud Bus(Nacos)
- SpringCloud小版本
- SNAPSHOT:快照版本,随时可能修改
- M:MileStone,M1表示里程碑版本,一般同时标注PRE,表示预览版
- SR:Service Release,SR1表示第一个正式版本,一般同时标注GA,表示稳定版本
- Nacos下载和安装
地址:https://github.com/alibaba/nacos/releases
版本:nacos-server-1.1.4.tar.gz或nacos-server-1.1.4.zip解压任意目录
- 服务注册
引入依赖
1
2
3
4
5
6
7
8
9
10
11<!--引入依赖-->
<!--服务注册-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--服务调用-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>在要注册的服务配置文件application中配置Nacos地址
1
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
在启动类添加**@EnableDiscoveryClient**注解
- 服务调用
在调用端的服务启动类上添加**@EnableFeignClients**注解
创建接口,在接口上添加注解**@Component,@FeignClient(“服务名字”)**
1
2
3
4
5
6
7
8//@PathVariable注解一定要指定参数名称,否则出错
public interface Vodclient{
//定义调用的方法路径
//完整路径
public R removeAlyVideo(; String id)
}在需要调用的controller中注入Vodclient实现远程调用
- SpringCloud调用接口流程
1 | 接口化请求调用--call-->(1)Feign --Hystrix熔断处理机制--> (2)Hystrix --请求通过,则通过Ribbon负载均衡器,挑选合适的服务提供端-->(3)Ribbon--请求包转换成Http Client客户端框架做真实的http通信请求-->(4)HttpClient/OkHttp |
- 整合熔断器
添加熔断器依赖
1
2
3
4
5
6
7
8
9
10<!--负载均衡-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<!--hystrix依赖,主要是用 @HystrixCommand -->
<dependency>
<groupId>org.springframework.cloud</groupId>
</dependency>在调用端配置文件中开启熔断器
1
2#开启熔断机制
feign.hystrix.enabled=true在创建interface之后,还需要创建interface的实现类,在实现类中实现方法,出错了输出内容
1
2
3
4
5
6
7
8
9
10
11
12
13
public class VodFileDegradeFeignClient implements VodClient {
//远程调用出错会执行
//删除一个视频
public R removeAlyVideo(String id) {
return R.error().message("删除视频出错");
}
//批量删除视频
public R deleteBatch(List<String> videoList) {
return R.error().message("删除多个视频出错");
}
}在接口上添加注解
1
- Redis
特点:
- 基于Key-value进行存储的
- 支持多种数据结构:string(字符串),list(列表),hash(哈希),set(集合),zset(有序集合)
- 支持持久化,通过内存存储,也可以存到硬盘里
- 支持过期时间,支持事务,消息订阅
- 读取速度110000次/s,写的速度是81000次/s
- 所有操作都是原子性的,同时支持对几个操作全并后的原子性执行
场景:
- 经常查询,不经常修改,不是特别重要的数据放到redis中缓存
- Redis设置
redis.config设置:
1 | # bind 127.0.0.1 1.注释 |
先启动redis-server
,再启动redis-cli
- Springboot整合redis
引入依赖
1
2
3
4
5
6
7
8
9
10
11
12<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--spring2.x集成redis所需的common-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency>redis配置
1
2
3
4
5
6# redis配置
spring.redis.host=127.0.0.1
spring.redis.lettuce.pool.max-active=20
spring.redis.lettuce.pool.max-wait=-1
spring.redis.lettuce.pool.max-idle=5
spring.redis.lettuce.pool.min-idle=0创建配置类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65package com.atguigu.servicebase;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
public class RedisConfig extends CachingConfigurerSupport {
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setConnectionFactory(factory);
//key序列化方式
template.setKeySerializer(redisSerializer);
//value序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
//value hashmap序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题),过期时间600秒
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
}开启缓存注解:
(1)
@Cacheable
:根据方法对其返回结果进行缓存,下次请求时,如果缓存存在,则直接读取缓存返回数据,若缓存不存在,则执行方法,并把结果存入缓存中,一般用在
查询方法
上属性/方法名 解释 value 缓存名,必填,它指定了你的缓存存放在哪块命名空间 cacheNames 与value差不多,二选一即可 key 可选属性,可以使用SpEL标签自定义缓存的key (2)缓存@CachePut
使用该注解标志的方法,每次都会执行,并将结果存入指定的缓存中。其他方法可以直接从响应的缓存中读取缓存数据,而不需要再去查询数据库。一般用在
新增方法
上。查看源码,属性值如下:
属性/方法名 解释 value 缓存名,必填,它指定了你的缓存存放在哪块命名空间 cacheNames 与value差不多,二选一即可 key 可选属性,可以使用SpEL标签自定义缓存的key (3)缓存@CacheEvict
使用该注解标志的方法,会清空指定的缓存。一般用在
更新或者删除
方法上查看源码,属性值如下:
属性/方法名 解释 value 缓存名,必填,它指定了你的缓存存放在哪块命名空间 cacheNames 与value差不多,二选一即可 key 可选属性,可以使用SpEL标签自定义缓存的key allEntries 是否清空所有缓存,默认为false.如果指定为true,则方法调用后将立即清空所有缓存 beforeInvocation 是否在方法执行前就清空,默认为 false。如果指定为 true,则在方法执行前就会清空缓存
- 邮件发送
导入依赖
1
2
3
4
5
6<!--邮件发送-->
<!--javax.mail-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>配置文件
1
2
3
4
5#javax.mail配置
spring.mail.username=918703864@qq.com
spring.mail.password=oiidxvtzb
spring.mail.host=smtp.qq.com
spring.mail.properties.mail.smtl.ssl.enable=true简单邮件测试
1
2
3
4
5SimpleMailMessage Message = new SimpleMailMessage();
Message.setSubject("你家大狗子");
Message.setText("okkk");
Message.setTo("目标邮箱");
Message.setFrom("发送端邮箱");复杂邮件测试
1
2
3
4
5
6
7
8
9
10
11
12//一个复杂的邮件
MimeMessage mailMessage=mailSender.createMimeMessage();
//组装
MimeMessageHelper helper=new MimeMessageHelper(mailMessage,true,"utf-8");
helper.setSubject("党中央发起的第一次警告!!!");
helper.setText("内容",true); //true解析内容为html
//附件
helper.addAttachment("附件名称",new File("附件路径"));
helper.setTo("a918703864@foxmail.com");
helper.setFrom("918703864@qq.com");
//复杂邮件
mailSender.send(mailMessage);
- 异步任务
- 启动类上添加
@EnableAsync
注解 - 异步方法上添加
@Async
- 定时任务
- 在启动类上添加注解:
@EnableScheduling
- 创建定时任务类:在这个类里面使用表达式设置什么时候去执行
- 类上
@Component
- 方法上
@Scheduled(cron="0/5 * * * * ?"):
每五秒执行一次,参考:https://www.pppet.net
- 类上
- canal数据同步
1.应用场景
在前面的统计分析功能中,我们采取了服务调用获取统计数据,这样耦合度高,效率相对较低,目前我采取另一种实现方式,通过实时同步数据库表的方式实现,例如我们要统计每天注册与登录人数,我们只需把会员表同步到统计库中,实现本地统计就可以了,这样效率更高,耦合度更低,Canal就是一个很好的数据库同步工具。canal是阿里巴巴旗下的一款开源项目,纯Java开发。基于数据库增量日志解析,提供增量数据订阅&消费,目前主要支持了MySQL。
2.Canal环境搭建
开启mysql服务:service mysql start
(1)开启binlog功能·是否来开启
1 | mysql>show variables like 'log_bin'; |
(2)如果显示状态为OFF表示该功能未开启,开启binlog功能
1 | 1,修改 mysql 的配置文件 my.cnf |
(3)在mysql里面添加以下的相关用户和权限
1 | CREATE USER 'canal'@'%' IDENTIFIED BY 'canal'; |
3.下载安装Canal服务
下载地址:https://github.com/alibaba/canal/releases
(1)下载并上传到服务器后,放到目录中,解压文件
1 | cd /usr/local/canal |
(2)修改配置文件:vi conf/example/instance.properties
1 | #需要改成自己的数据库信息 |
1 | 注意: |
(3) 进入bin目录下启动:sb bin/startup.sh
4.项目中引入canal
引入依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.otter</groupId>
<artifactId>canal.client</artifactId>
</dependency>
</dependencies>创建application.properties配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14# 服务端口
server.port=10000
# 服务名
spring.application.name=canal-client
# 环境设置:dev、test、prod
spring.profiles.active=dev
# mysql数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/guli?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root编写canal客户端类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208import com.alibaba.otter.canal.client.CanalConnector;
import com.alibaba.otter.canal.client.CanalConnectors;
import com.alibaba.otter.canal.protocol.CanalEntry.*;
import com.alibaba.otter.canal.protocol.Message;
import com.google.protobuf.InvalidProtocolBufferException;
import org.apache.commons.dbutils.DbUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.net.InetSocketAddress;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
public class CanalClient {
//sql队列
private Queue<String> SQL_QUEUE = new ConcurrentLinkedQueue<>();
private DataSource dataSource;
/**
* canal入库方法
*/
public void run() {
CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress("192.168.44.132",11111), "example", "", "");
int batchSize = 1000;
try {
connector.connect();
connector.subscribe(".*\\..*");
connector.rollback();
try {
while (true) {
//尝试从master那边拉去数据batchSize条记录,有多少取多少
Message message = connector.getWithoutAck(batchSize);
long batchId = message.getId();
int size = message.getEntries().size();
if (batchId == -1 || size == 0) {
Thread.sleep(1000);
} else {
dataHandle(message.getEntries());
}
connector.ack(batchId);
//当队列里面堆积的sql大于一定数值的时候就模拟执行
if (SQL_QUEUE.size() >= 1) {
executeQueueSql();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (InvalidProtocolBufferException e) {
e.printStackTrace();
}
} finally {
connector.disconnect();
}
}
/**
* 模拟执行队列里面的sql语句
*/
public void executeQueueSql() {
int size = SQL_QUEUE.size();
for (int i = 0; i < size; i++) {
String sql = SQL_QUEUE.poll();
System.out.println("[sql]----> " + sql);
this.execute(sql.toString());
}
}
/**
* 数据处理
*
* @param entrys
*/
private void dataHandle(List<Entry> entrys) throws InvalidProtocolBufferException {
for (Entry entry : entrys) {
if (EntryType.ROWDATA == entry.getEntryType()) {
RowChange rowChange = RowChange.parseFrom(entry.getStoreValue());
EventType eventType = rowChange.getEventType();
if (eventType == EventType.DELETE) {
saveDeleteSql(entry);
} else if (eventType == EventType.UPDATE) {
saveUpdateSql(entry);
} else if (eventType == EventType.INSERT) {
saveInsertSql(entry);
}
}
}
}
/**
* 保存更新语句
*
* @param entry
*/
private void saveUpdateSql(Entry entry) {
try {
RowChange rowChange = RowChange.parseFrom(entry.getStoreValue());
List<RowData> rowDatasList = rowChange.getRowDatasList();
for (RowData rowData : rowDatasList) {
List<Column> newColumnList = rowData.getAfterColumnsList();
StringBuffer sql = new StringBuffer("update " + entry.getHeader().getTableName() + " set ");
for (int i = 0; i < newColumnList.size(); i++) {
sql.append(" " + newColumnList.get(i).getName()
+ " = '" + newColumnList.get(i).getValue() + "'");
if (i != newColumnList.size() - 1) {
sql.append(",");
}
}
sql.append(" where ");
List<Column> oldColumnList = rowData.getBeforeColumnsList();
for (Column column : oldColumnList) {
if (column.getIsKey()) {
//暂时只支持单一主键
sql.append(column.getName() + "=" + column.getValue());
break;
}
}
SQL_QUEUE.add(sql.toString());
}
} catch (InvalidProtocolBufferException e) {
e.printStackTrace();
}
}
/**
* 保存删除语句
*
* @param entry
*/
private void saveDeleteSql(Entry entry) {
try {
RowChange rowChange = RowChange.parseFrom(entry.getStoreValue());
List<RowData> rowDatasList = rowChange.getRowDatasList();
for (RowData rowData : rowDatasList) {
List<Column> columnList = rowData.getBeforeColumnsList();
StringBuffer sql = new StringBuffer("delete from " + entry.getHeader().getTableName() + " where ");
for (Column column : columnList) {
if (column.getIsKey()) {
//暂时只支持单一主键
sql.append(column.getName() + "=" + column.getValue());
break;
}
}
SQL_QUEUE.add(sql.toString());
}
} catch (InvalidProtocolBufferException e) {
e.printStackTrace();
}
}
/**
* 保存插入语句
*
* @param entry
*/
private void saveInsertSql(Entry entry) {
try {
RowChange rowChange = RowChange.parseFrom(entry.getStoreValue());
List<RowData> rowDatasList = rowChange.getRowDatasList();
for (RowData rowData : rowDatasList) {
List<Column> columnList = rowData.getAfterColumnsList();
StringBuffer sql = new StringBuffer("insert into " + entry.getHeader().getTableName() + " (");
for (int i = 0; i < columnList.size(); i++) {
sql.append(columnList.get(i).getName());
if (i != columnList.size() - 1) {
sql.append(",");
}
}
sql.append(") VALUES (");
for (int i = 0; i < columnList.size(); i++) {
sql.append("'" + columnList.get(i).getValue() + "'");
if (i != columnList.size() - 1) {
sql.append(",");
}
}
sql.append(")");
SQL_QUEUE.add(sql.toString());
}
} catch (InvalidProtocolBufferException e) {
e.printStackTrace();
}
}
/**
* 入库
* @param sql
*/
public void execute(String sql) {
Connection con = null;
try {
if(null == sql) return;
con = dataSource.getConnection();
QueryRunner qr = new QueryRunner();
int row = qr.execute(con, sql);
System.out.println("update: "+ row);
} catch (SQLException e) {
e.printStackTrace();
} finally {
DbUtils.closeQuietly(con);
}
}
}创建启动类
1
2
3
4
5
6
7
8
9
10
11
12
13
public class CanalApplication implements CommandLineRunner {
private CanalClient canalClient;
public static void main(String[] args) {
SpringApplication.run(CanalApplication.class, args);
}
public void run(String... strings) throws Exception {
//项目启动,执行canal客户端监听
canalClient.run();
}
}
- Gateway网关
作用:对相同模块自动实现负载均衡,轮询
,权重
,最少请求时间
- 全局性流控
- 日志统计
- 防止SQL注入
- 防止Web攻击
- 屏蔽工具扫面
- 黑白IP名单
- 证书/加解密处理
- 服务级别流控
- 服务降级与熔断
- 路由与负载均衡,灰度策略
- 服务过滤,聚合与发现
- 权限验证与用户等级策略
- 业务规则与参数校验
- 多级缓存策略
功能特征:
- 基于Spring Framework5,Project Reactor和Spring Boot2.0进行构建
- 动态路由,能够匹配任何请求属性
- 支持路径重写
- 集成Spring Cloud服务发现功能(Nacos,Eruka)
- 可集成流控降级功能(Sentinel,Hystrix)
- 可以对路由器指定易于编写的
Predicate(断言)
和Filter(过滤器)
创建微服务模块
引入依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27<dependency>
<groupId>com.atguigu</groupId>
<artifactId>common_utils</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--gson-->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<!--服务调用-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>创建配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29#服务端口
server.port=8222
#服务名
spring.application.name=service-gateway
#nacos服务地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
#使用服务发现路由
spring.cloud.gateway.discovery.locator.enabled=true
#设置路由id
spring.cloud.gateway.routes[0].id=service-cms
#设置路由的uri
spring.cloud.gateway.routes[0].uri=lb://service-cms
#设置路由断言,代理serviceId为auth-service的/auth/路径
spring.cloud.gateway.routes[0].predicates=Path=/educms/**
#配置service-edu服务
spring.cloud.gateway.routes[1].id=service-edu
spring.cloud.gateway.routes[1].uri=lb://service-edu
spring.cloud.gateway.routes[1].predicates=Path=/eduservice/**
#http://localhost/order-service/order/add
#http://localhost/order/add
spring.cloud.gateway.routes[2].filters.StripPrefix=1//转发之前去掉第一层路径
spring.cloud.gateway.roters[2].predicates.cookie=键,正则表达式
spring.cloud.gateway.router[2].predicates.Header=X-Request-Id,\d+ 根据请求头匹配
spring.cloud.gateway.router[2].predicates.Host启动类上添加服务发现注解
@EnableDiscoveryClient
通过网关地址访问
- 注册业务
实现方式:
单一服务器模式
使用session对象广播机制实现—》session复制(默认30分钟session过期)
使用cookie+redis实现—》
- 在项目任意模块进行登录,登录之后,把数据放到两个地方
- redis,
在key
:生成唯一随机值(ip,用户id等等),在value
:用户数据 - cookie:把redis里面生成key值放到cookie里面
- redis,
- 访问项目中的其他模块,发送请求带着cookie进行发送,获取cookie值,拿着cookie做事情
- 获取cookie,到redis进行查询,根据key进行查询,如果查询数据存在,登录
- 在项目任意模块进行登录,登录之后,把数据放到两个地方
使用token实现
在项目某个模块进行登录,登录之后,按照规则生成字符传,把登录之后的用户信息包含到生成的字符串里面,
返回字符串
- 可以把字符串通过cookie返回
- 把字符串通过地址栏返回
在去访问其他模块,每次访问在地址栏带着生成字符串,在访问模块里面获取地址栏字符串,根据字符串获取用户信息,如果可以获取到,就登录
- JWT令牌
JWT生成字符串组成:
- JWT头信息
- 有效荷载 包含主体信息(用户信息)
- 签名哈希,防伪标志
使用:
引入依赖
1
2
3
4
5
6
7<dependencies>
<!--JWT-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
</dependencies>创建工具类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85package com.atguigu.oss.commonutils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
/**
* @author helen
* @since 2019/10/16
*/
public class JwtUtils {
public static final long EXPIRE = 1000 * 60 * 60 * 24;//token过期时间
public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";//密钥
public static String getJwtToken(String id, String nickname){
String JwtToken = Jwts.builder()
.setHeaderParam("typ", "JWT")
.setHeaderParam("alg", "HS256")
.setSubject("guli-user")//分类
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRE))//设置过期时间
//设置token主体部分,存储用户信息
.claim("id", id)
.claim("nickname", nickname)
.signWith(SignatureAlgorithm.HS256, APP_SECRET)
.compact();
return JwtToken;
}
/**
* 判断token是否存在与有效
* @param jwtToken
* @return
*/
public static boolean checkToken(String jwtToken) {
if(StringUtils.isEmpty(jwtToken)) return false;
try {
Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 判断token是否存在与有效
* @param request
* @return
*/
public static boolean checkToken(HttpServletRequest request) {
try {
String jwtToken = request.getHeader("token");
if(StringUtils.isEmpty(jwtToken)) return false;
Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 根据token获取会员id
* @param request
* @return
*/
public static String getMemberIdByJwtToken(HttpServletRequest request) {
String jwtToken = request.getHeader("token");
if(StringUtils.isEmpty(jwtToken)) return "";
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
Claims claims = claimsJws.getBody();
return (String)claims.get("id");
}
}
- MD5加密工具类
1 | package com.atguigu.oss.commonutils; |
- 验证码
1 | package com.atguigu.oss.commonutils; |
- 验证码请求接口
1 | public String getVerifyCode(HttpServletResponse response,HttpServletRequest request) { |
- (前端)定时器
1 | <script> |
- 登录业务
- 登录流程
- 调用接口登录返回
token
字符串 - 将返回的返回的
token
放到cookie
里面 - 创建前端拦截器,判断
cookie
里面是否有token
字符串- 若有,把
token
放到header
(请求头)
- 若有,把
- 根据
token
值,调用接口,根据token
获取用户信息,为了首页
面显示 - 在首页面中根据
cookie
显示用户信息
1 | //登录的方法 |
1 | import axios from 'axios' |
- 微信扫码登录的应用
OAuth2
:特定问题解决方案
- 开放系统间授权
- 分布式访问问题
微信扫码登录注册流程:
- 注册https://open.weixin.qq.com
- 邮箱激活
- 完善开发者资料
- 完善开发者资质认证:准备营业执照,1-2个工作日审批,300元
- 创建网站应用:提交审核,7个工作日审批
- 熟悉微信登录流程
- Springboot工具类
- 断言
- 断言是一个逻辑判断,用于检查不应该发生的情况
- Assert 关键字在 JDK1.4 中引入,可通过 JVM 参数
-enableassertions
开启 - SpringBoot 中提供了 Assert 断言工具类,通常用于数据合法性检查
1 | // 要求参数 object 必须为非空(Not Null),否则抛出异常,不予放行 |
- 对象、数组、集合
- ObjectUtils
- 获取对象的基本信息
1 | // 获取对象的类名。参数为 null 时,返回字符串:"null" |
- 判断工具
1 | // 判断数组是否为空 |
- 其他工具方法
1 | // 向参数数组的末尾追加新元素,并返回一个新数组 |
- StringUtils
- 字符串判断工具
1 | // 判断字符串是否为 null,或 ""。注意,包含空白符的字符串为非空 |
- 字符串操作工具
1 | // 查找并替换指定子串 |
- 路径相关工具方法
1 | // 解析路径字符串,优化其中的 “..” |
- CollectionUtils
- 集合判断工具. 我是程序汪
1 | // 判断 List/Set 是否为空 |
- 集合操作工具。 Java项目分享 最新整理全集
1 | // 将 Array 中的元素都添加到 List/Set 中 |
- 文件、资源、IO 流
- FileCopyUtils
- 输入
1 | // 从文件中读入到字节数组中 |
- 输出
1 | // 从字节数组到文件 |
- ResourceUtils
- 从资源路径获取文件
1 | // 判断字符串是否是一个合法的 URL 字符串。 |
- Resource
1 | // 文件系统资源 D:\... |
1 | // 判断资源是否存在 |
- StreamUtils
- 输入
1 | void copy(byte[] in, OutputStream out) |
- 输出
1 | byte[] copyToByteArray(InputStream in) |
- 反射、AOP
- ReflectionUtils
- 获取方法
1 | // 在类中查找指定方法 |
- 执行方法
1 | // 执行方法 |
- 获取字段
1 | // 在类中查找指定属性 |
- 设置字段
1 | // 获取 target 对象的 field 属性值 |
- AopUtils
- 判断代理类型
1 | // 判断是不是 Spring 代理对象 |
- 获取被代理对象的 class
1 | // 获取被代理的目标 class |
- AopContext
- 获取当前对象的代理对象
1 | Object currentProxy() |
- 第三方整合
- 阿里云播放器
- 获取播放地址
参考文档:https://help.aliyun.com/document_detail/61064.html
前面的 03-使用服务端SDK 介绍了如何获取非加密视频的播放地址。直接使用03节的例子获取加密视频播放地址会返回如下错误信息
Currently only the AliyunVoDEncryption stream exists, you must use the Aliyun player to play or set the value of ResultType to Multiple.
目前只有AliyunVoDEncryption流存在,您必须使用Aliyun player来播放或将ResultType的值设置为Multiple。
因此在testGetPlayInfo测试方法中添加 ResultType 参数,并设置为true
1 | privateParams.put("ResultType","Multiple"); |
此种方式获取的视频文件不能直接播放,必须使用阿里云播放器播放
- 集成播放器
引入脚本
1
2<link rel="stylesheet" href="https://g.alicdn.com/de/prismplayer/2.8.1/skins/default/aliplayer-min.css" />
<script charset="utf-8" type="text/javascript" src="https://g.alicdn.com/de/prismplayer/2.8.1/aliplayer-min.js"></script>初始化视频播放器
1
2
3
4
5
6
7
8
9
10
11
12
13
14<body>
<div class="prism-player" id="J_prismPlayer"></div>
<script>
var player = new Aliplayer({
id: 'J_prismPlayer',
width: '100%',
autoplay: false,
cover: 'http://liveroom-img.oss-cn-qingdao.aliyuncs.com/logo.png',
//播放配置
},function(player){
console.log('播放器创建好了。')
});
</script>
</body>视频地址播放
在Aliplayer的配置参数中添加如下属性
1
2//播放方式一:支持播放地址播放,此播放优先级最高,此种方式不能播放加密视频
source:"视频播放地址"启动浏览器运行,测试视频的播放
播放凭证播放
阿里云播放器支持通过播放凭证自动换取播放地址进行播放,接入方式更为简单,且安全性更高。播放凭证默认时效为100秒(最大为3000秒),只能用于获取指定视频的播放地址,不能混用或重复使用。如果凭证过期则无法获取播放地址,需要重新获取凭证。
1
2
3encryptType:'1',//如果播放加密视频,则需设置encryptType=1,非加密视频无需设置此项
vid : '视频id',
playauth : '视频授权码',注意:播放凭证有过期时间,默认值:100秒 。取值范围:100~3000
设置播放凭证的有效期
在获取播放凭证的测试用例中添加如下代码
1
request.setAuthInfoTimeout(200L)
在线视频播放地址:https://player.alicdn.com/aliplayer/setting/setting.html
- 微信支付
引入依赖
1
2
3
4
5
6
7
8
9
10
11<dependencies>
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
</dependencies>
- 功能实现
- windows端口查询
windows:查看端口状态 netstat -ano
1 | //获取路由中id值 |
- oss对象存储
1 | # coding=utf8 |
1 |
|
1 | public class OssServiceApplication extends SpringBootServletInitializer { |
- 查询ip地址
1 | //java工具类实现 |
1 | # python 实现 |
1 | from binding.python.ip2Region import Ip2Region |