在线学习平台-项目技术点-后台
目录
1、主键生成策略
1.1自动增长-AUTO INCREMENT
1.2UUID
1.3Redis生成ID
2、MyBatis-plus
2.1自动填充
2.2悲观锁、乐观锁
2.3性能分析插件
3.@ResponseBody和@RequestBody
4.es6语法
4.1let变量和const变量
4.2解构赋值(数组和对象解构
4.3模板字符串`
4.4简写:声明对象和定义方法
4.5对象拓展运算符(拷贝和合并对象
4.6箭头函数
5.Vue.js
5.1v-bind单向绑定和v-model双向绑定
5.2v-on绑定事件
5.3条件渲染v-if和v-show
5.4列表渲染v-for
5.5组件
5.6Vue生命周期
5.7路由
1)vue.js路由例子
2)项目中整合的router/index.js
小疑问:字段name和meta的区别?
5.8axios
6.Node.js、NPM包管理器、Bable
6.1Node.js
6.2NPM包管理器
6.3Bable
6.4Webpack
7.跨域问题
7.1跨域形成
7.2跨域解决
8.路由
9.Nginx
9.1请求转发
1)配置nginx代理
2)配置开发环境-修改config/dev.env.js
3)测试请求接口
10.EasyExcel
10.1特点
10.2写入和读出
1)引入依赖
2)创建实体类
3)测试EasyExcel的写
编辑
4)测试EasyExcel的读
11.阿里云视频点播
11.1API和SDK的区别
11.2视频点播SDK-测试
1)前置-安装添加maven仓库的配置和依赖到pom
2)初始化对象-创建SDK工具类
3)根据视频ID获取视频地址
4)根据视频ID获取视频播放凭证
5)上传视频
6)删除视频
12.微服务SpringCloud
12.1概念
1)微服务
2)SpringCloud
3)Nacos注册中心
-服务注册
4)Feign服务调用
-配置
-在调用端创建interface,使用注解指定调用服务名称,定义调用的方法路径
-在调用端的VideoServiceImpl中调用client中的方法
5)Hystrix熔断器
-配置
-VlodClient的实现类
13.统计分析
13.1定时任务-Cron
1)在启动类添加注解
2)创建日期工具类
3)创建定时任务类
13.2ECharts图表整合
1)Demo测试
2)安装ECharts
3)整合前端代码
14.SpringCloud-GateWay网关
14.1网关基本概念
14.2SpringCloudGateway核心概念
14.3创建api-gateway模块(网关服务)
14.4网关解决跨域问题
15.SpringSecurity
15.1框架介绍
15.2认证与授权实现思路
16.Nacos配置中心
16.1介绍与作用
16.2读取Nacos配置中心的配置文件
16.3补充:springboot配置文件加载顺序
报错及解决
1.P130,获取课程发布信息接口报错
原因:xml文件未编译:maven默认加载机制造成问题
maven加载时候,把java文件夹里面.java类型文件进行编译,如果其他类型文件,不会加载
解决
1、复制xml到target目录中
2、把xm1文件放到resources目录
3、推荐使用-通过配置实现
- pom.xml
<!-- 项目打包时会将java目录中的*.xml文件也进行打包 -->
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
- 项目application. properties
#配置mapper xml文件的路径
mybatis-plus.mapper-locations=classpath:com/guli/edu/mapper/xml/*.xml
2.java文件夹或resources文件夹未加入springboot
解决方法:右击模块,选择open module setting
3、P244
1、主键生成策略
1.1自动增长-AUTO INCREMENT
优点:主键自动+1
缺点:分表,下一个表的ID需要知道上一个表的尾ID+1
@TableId(type=IdType.AUTO)
1.2UUID
优点:每次生成随机唯一的值
缺点:排序不方便
@TableId(type=IdType.UUID)
1.3Redis生成ID
性能优于数据库时,利用Reids单线程,原子操作INCR和INCRBY实现累加。
1.4MP自带策略-snowflake算法生成19位的ID
//mp自带策略,生成19位值,数字类型,如long
@TableId(type=IdType.ID_WORKER)
//字符串类型,如String
@TableId(type=IdType.ID_WORKER_STR)
2、MyBatis-plus
2.1自动填充
记录的创建时间、修改时间,可以在新增、修改时自动填充
(1)数据库表中添加自动填充字段
(2)实体上添加注解
@Data
public class User {
......
//新增时,自动填充
@TableField(fill = FieldFill.INSERT)
private Date createTime;
//@TableField(fill = FieldFill.UPDATE)
@TableField(fill = FieldFill.INSERT_UPDATE)//新增/修改时自动填充
private Date updateTime;
}
(3)实现元对象处理器接口
注意:元对象metaObject指数据库的create_time/update_time字段,
不要忘记添加 @Component 注解
package com.atguigu.mybatisplus.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(MyMetaObjectHandler.class);
@Override//新增时,调用该方法
public void insertFill(MetaObject metaObject) {//metaObject为元对象
LOGGER.info("start insert fill ....");
//设置对象字段、日期、元对象字段
this.setFieldValByName("createTime", new Date(), metaObject);
this.setFieldValByName("updateTime", new Date(), metaObject);
}
@Override//修改时,调用该方法
public void updateFill(MetaObject metaObject) {
LOGGER.info("start update fill ....");
this.setFieldValByName("updateTime", new Date(), metaObject);
}
}
(4)测试,调用mp新增方法时,日期字段自动填充
2.2悲观锁、乐观锁
- 悲观锁:串行,每次访问数据时都会先加锁,一旦线程获得锁,其它尝试获取锁的线程都会被阻塞,直到锁被释放。常见的实现方式是数据库中的行级锁、表级锁或行级锁等。
- 在高并发、数据竞争激烈的场景中使用,如金融交易、库存管理等。这类场景下,冲突发生的可能性很高,所以加锁确保数据的一致性。
- 容易产生死锁(Deadlock)
- 乐观锁:并行,并发操作冲突的可能性较小,因此不会主动加锁,而是进行数据版本检查来决定是否提交操作 。
- 适用于读操作多、写操作少的场景,如一些阅读类应用、CMS系统等。因为这些场景下,冲突发生的概率较低,乐观锁可以提高系统的并发性。
乐观锁实现方式:
- 取出记录时,获取当前version
- 更新时,带上这个version
- 执行更新时, set version = newVersion where version = oldVersion
- 如果version不对,就更新失败
(1)数据库中添加version字段
(2)实体类添加version字段
@Version
@TableField(fill = FieldFill.INSERT)
private Integer version;
(3)元对象处理器接口添加version的insert默认值
@Override
public void insertFill(MetaObject metaObject) {
......
//设置version字段默认值为“1”
this.setFieldValByName("version", 1, metaObject);
}
(4)在 MybatisPlusConfig 中注册 Bean
package com.atguigu.mybatisplus.config;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@EnableTransactionManagement
@Configuration //配置类
@MapperScan("com.atguigu.mybatis_plus.mapper")
public class MybatisPlusConfig {
/**
* 乐观锁插件
*/
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
}
(5)测试乐观锁可以修改成功/失败
/**
* 测试 乐观锁插件
*/
@Test
public void testOptimisticLocker() {
//查询
User user = userMapper.selectById(1L);
//修改数据
user.setName("Helen Yao");
user.setEmail("helen@qq.com");
//执行更新-乐观锁成功
userMapper.updateById(user);//version更新为“2”
}
/**
* 测试乐观锁插件 失败
*/
@Test
public void testOptimisticLockerFail() {
//查询
User user = userMapper.selectById(1L);
//修改数据
user.setName("Helen Yao1");
user.setEmail("helen@qq.com1");
//模拟取出数据后,数据库中version实际数据比取出的值大,即已被其它线程修改并更新了version
user.setVersion(user.getVersion() - 1);
//执行更新-无法执行
userMapper.updateById(user);
}
2.3性能分析插件
性能分析拦截器,用于输出每条 SQL 语句及其执行时间。开发环境使用,超过指定时间,停止运行。有助于发现问题。
(1)参数说明
参数:maxTime: SQL 执行最大时长,超过自动停止运行,有助于发现问题。
参数:format: SQL是否格式化,默认false。
(2)在 MpConfig 中配置
package com.example.demomp1.config;
import com.baomidou.mybatisplus.extension.plugins.PerformanceInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
/**
*
* MyBatisPlus插件配置
*/
@Configuration
@MapperScan("com.example.demomp1.mapper")
public class MpConfig {
/**
* SQL 执行性能分析插件
* 开发环境使用,线上不推荐。 maxTime 指的是 sql 最大执行时长
* 开发环境:dev
* 测试环境:test
* 生产环境:prod
*/
@Bean
@Profile({"dev","test"})// 设置 dev test 环境开启
public PerformanceInterceptor performanceInterceptor() {
PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
performanceInterceptor.setMaxTime(200);//ms,超过此处设置的ms则sql不执行
performanceInterceptor.setFormat(true);
return performanceInterceptor;
}
}
(3)application.properties中设置dev环境
#环境设置:dev、test、prod
spring.profiles.active=dev
(4)运行测试,Time:15ms
改小MaxTime为5ms后,运行测试
3.@ResponseBody和@RequestBody
- @ResponseBody:封装返回数据的格式为json
- @RequestBody:需使用post提交,提交json格式的数据封装到对象里,才能取得到值
4.es6语法
ECMAScript 和 JavaScript 的关系是,前者是后者的规格,后者是前者的一种实现
4.1let变量和const变量
1)let声明变量
1.局部 2.只可声明一次
<script>
//let语法
//let与var区别
// 1.let定义的变量是局部的
{
var a = 10
let b = 20
}
console.log(a) //10
console.log(b) //Uncaught ReferenceError: b is not defined
// 2.let只能声明一次变量
var a1 = 10
var a1 = 11
let b1 = 20
let b1 = 21
console.log(a1)
console.log(b1) //Uncaught SyntaxError: Identifier 'b1' has already been declared
</script>
2)const声明变量
1.必须初始化 2.不可变
<script>
//const声明变量
// 1.必须初始化
//const a //const01.html:4 Uncaught SyntaxError: Missing initializer in const declaration
// 2.只读不可变
const b = 10
b = 11 //Uncaught TypeError: Assignment to constant variable.
</script>
4.2解构赋值(数组和对象解构
数组:[ ],对象:{ }
<script>
//1、数组解构
// 传统
let a = 1, b = 2, c = 3
console.log(a, b, c)
// ES6[]
let [x, y, z] = [1, 2, 3]
console.log(x, y, z)
//2、对象解构
let user = { name: 'Helen', age: 18 }
// 传统
let name1 = user.name
let age1 = user.age
console.log(name1, age1)
// ES6{}
let { name, age } = user//注意:结构的变量必须是user中的属性
console.log(name, age)
</script>
4.3模板字符串`
用反引号 `,除了作为普通字符串,还可以用来定义多行字符串,还可以在字符串中加入变量和表达式。
1、字符串换行 2、${}引入变量和表达式 3、${}引入函数
// 1、多行字符串
let string1 = `Hey,
can you stop angry now?`
console.log(string1)
// Hey,
// can you stop angry now?
// 2、字符串插入变量和表达式。变量名写在 ${} 中,${} 中可以放入 JavaScript 表达式。
let name = "Mike"
let age = 27
let info = `My Name is ${name},I am ${age+1} years old next year.`
console.log(info)
// My Name is Mike,I am 28 years old next year.
// 3、字符串中调用函数
function f(){
return "have fun!"
}
let string2 = `Game start,${f()}`
console.log(string2); // Game start,have fun!
4.4简写:声明对象和定义方法
// 1.声明对象
const age = 12
const name = "Amy"
// 传统
const person1 = {age: age, name: name}
console.log(person1)
// ES6
const person2 = {age, name}
console.log(person2) //{age: 12, name: "Amy"}
// 2.定义方法
// 传统
const person1 = {
sayHi:function(){
console.log("Hi")
}
}
person1.sayHi();//"Hi"
// ES6:去掉function
const person2 = {
sayHi(){
console.log("Hi")
}
}
person2.sayHi() //"Hi"
4.5对象拓展运算符(拷贝和合并对象
拓展运算符(...)用于取出参数对象所有可遍历属性然后拷贝到当前对象
// 1、拷贝对象
let person1 = {name: "Amy", age: 15}
let someone = { ...person1 }
console.log(someone) //{name: "Amy", age: 15}
// 2、合并对象
let age = {age: 15}
let name = {name: "Amy"}
let person2 = {...age, ...name}
console.log(person2) //{age: 15, name: "Amy"}
4.6箭头函数
箭头函数提供了一种更加简洁的函数书写方式。
基本语法是:参数 => 函数体. 箭头函数多用于匿名函数的定义
// 当箭头函数没有参数或者有多个参数,要用 () 括起来。
// 当箭头函数函数体有多行语句,用 {} 包裹起来,表示代码块,
// 当只有一行语句,并且需要返回结果时,可以省略 {} , 结果会自动返回。
var f3 = (a,b) => {
let result = a+b
return result
}
console.log(f3(6,2)) // 8
// 箭头函数-前面代码相当于:
var f4 = (a,b) => a+b
// 传统
var f1 = function(a){
return a
}
console.log(f1(1))
// ES6
var f2 = a => a
console.log(f2(1))
5.Vue.js
Vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统
这里的核心思想就是没有繁琐的DOM操作,例如jQuery中,我们需要先找到div节点,获取到DOM对象,然后进行一系列的节点操作
5.1v-bind单向绑定和v-model双向绑定
- v-bind指令单向数据绑定:这个指令一般用在标签属性里面,获取值
- v-model只能给表单类,也就是具有value属性的元素进行数据双向绑定,如text、radio、checkbox、selected。(v-model是捕获用户的输入值,如果没有value,捕获不了,所以这个流向没有意义,v-model就是收集value值)
1)v-bind
v-bind 特性被称为指令。指令带有前缀 v-
除了使用插值表达式{{}}进行数据渲染,也可以使用 v-bind指令,它的简写的形式就是一个冒号(:)
<body>
<div id="app">
<!-- 如果要将模型数据绑定在html属性中,则使用 v-bind 指令
此时title中显示的是模型数据
-->
<h1 :title="content">
{{message}}
</h1>
</div>
<script src="vue.min.js"></script>
<script>
new Vue({
el: '#app',
data: {
message: "你好!",
content: "v-bind绑定的title,鼠标悬浮可见"
}
})
</script>
</body>
2)v-model
<body>
<div id="app">
<!-- v-bind:value只能进行单向的数据渲染 -->
v-bind:
<input type="text" v-bind:value="student.name">
<br>
v-model:
<!-- v-model 可以进行双向的数据绑定 -->
<input type="text" v-model="student.name">
<p>您要查询的是:{{student.name}}</p>
</div>
<script src="vue.min.js"></script>
<script>
new Vue({
el: '#app',
data: {
student: {
name: "黎明"
}
}
})
</script>
</body>
5.2v-on绑定事件
<body>
<div id="app">
<!-- v-on 指令绑定事件,click指定绑定的事件类型,事件发生时调用vue中methods节点中定义的方法 -->
<!-- v-on:click 简写-> @click -->
<button v-on:click="search()">点击查询地址</button>
<a :href="result.address">CSND地址</a>
</div>
<script src="vue.min.js"></script>
<script>
new Vue({
el: '#app',
data: {
//查询结果
result: {}
},
//methods定义方法
methods: {
search() {
console.log("查询。。。。")
this.result = {
name: "秋名山小白",
address: "https://blog.csdn.net/qq_62015542?spm=1011.2415.3001.5343"
}
}
}
})
</script>
</body>
5.3条件渲染v-if和v-show
v-if和v-show的区别:
v-if
是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。v-if
也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。- 相比之下,
v-show
就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。 - 一般来说,
v-if
有更高的切换开销,而v-show
有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用v-show
较好;如果在运行时条件很少改变,则使用v-if
较好。
<input type="checkbox" v-model="ok">同意许可协议
<!-- v:if条件指令:还有v-else、v-else-if 切换开销大 -->
<h1 v-if="ok">if:Lorem ipsum dolor sit amet.</h1>
<h1 v-else>no</h1>
<!-- v:show 条件指令 初始渲染开销大 -->
<h1 v-show="ok">show:Lorem ipsum dolor sit amet.</h1>
<h1 v-show="!ok">no</h1>
5.4列表渲染v-for
<body>
<div id="app">
<ul>
<!-- 如果想获取索引,则使用index关键字,注意,圆括号中的index必须放在后面 -->
<li v-for="(user, index) in userList">{{index}}{{user.id}}-{{user.username}}-{{user.age}}</li>
</ul>
</div>
<script src="vue.min.js"></script>
<script>
new Vue({
el: '#app',
data: {
userList: [
{ id: 1, username: 'helen', age: 18 },
{ id: 2, username: 'peter', age: 28 },
{ id: 3, username: 'andy', age: 38 }
]
}
})
</script>
</body>
5.5组件
组件(Component)是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。组件系统让我们可以用独立可复用的小组件来构建大型应用,几乎任意类型的应用的界面都可以抽象为一个组件树。
1)局部组件
<body>
<div id="app">
<!-- 使用定义的局部组件 -->
<Navbar></Navbar>
</div>
<script src="vue.min.js"></script>
<script>
new Vue({
el: '#app',
// 定义局部组件,这里可以定义多个局部组件
components: {
//组件的名字
'Navbar': {
//组件的内容
template: '<ul><li>首页</li><li>学员管理</li></ul>'
}
}
})
</script>
</body>
2)全局组件
1.创建Navbar.js,定义全局组件
// 定义全局组件
Vue.component('Navbar', {
template: '<ul><li>首页</li><li>学员管理</li><li>讲师管理</li></ul>'
})
2.先引入vue.min.js,后引入Navbar.js,并使用全局组件
<div id="app">
<Navbar></Navbar>
</div>
<script src="vue.min.js"></script>
<script src="components/Navbar.js"></script>
<script>
var app = new Vue({
el: '#app'
})
</script>
5.6Vue生命周期
//===创建时的四个事件
beforeCreate() { // 第一个被执行的钩子方法:实例被创建出来之前执行
console.log(this.message) //undefined
this.show() //TypeError: this.show is not a function
// beforeCreate执行时,data 和 methods 中的 数据都还没有没初始化
},
created() { // 第二个被执行的钩子方法
console.log(this.message) //床前明月光
this.show() //执行show方法
// created执行时,data 和 methods 都已经被初始化好了!
// 如果要调用 methods 中的方法,或者操作 data 中的数据,最早,只能在 created 中操作
},
beforeMount() { // 第三个被执行的钩子方法
console.log(document.getElementById('h3').innerText) //{{ message }}
// beforeMount执行时,模板已经在内存中编辑完成了,尚未被渲染到页面中
},
mounted() { // 第四个被执行的钩子方法
console.log(document.getElementById('h3').innerText) //床前明月光
// 内存中的模板已经渲染到页面,用户已经可以看见内容
},
//===运行中的两个事件
beforeUpdate() { // 数据更新的前一刻
console.log('界面显示的内容:' + document.getElementById('h3').innerText)
console.log('data 中的 message 数据是:' + this.message)
// beforeUpdate执行时,内存中的数据已更新,但是页面尚未被渲染
},
updated() {
console.log('界面显示的内容:' + document.getElementById('h3').innerText)
console.log('data 中的 message 数据是:' + this.message)
// updated执行时,内存中的数据已更新,并且页面已经被渲染
}
注意:created在页面渲染之前执行,mounted在页面渲染之后执行。可通过debugger调试
<script>
new Vue({
el: '#app',
data: {
},
created() {
debugger
console.log("created在页面渲染之前执行。。")
},
mounted() {
debugger
console.log("mounted在页面渲染之后执行。。")
}
})
</script>
5.7路由
1)vue.js路由例子
Vue.js 路由允许我们通过不同的 URL 访问不同的内容。
通过 Vue.js 可以实现多视图的单页Web应用(single page web application,SPA)。
Vue.js 路由需要载入 vue-router 库
1.引入vue.min.js和vue.router.min.js
2.编写html文件,使用router-link to 指定链接和router-view展示位置
3.编写js文件,定义路由组件,创建 router 实例并挂载
<body>
<div id="app">
<h1>Hello App!</h1>
<p>
<!-- 使用 router-link 组件来导航. -->
<!-- 通过传入 `to` 属性指定链接. -->
<!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
<router-link to="/">首页</router-link>
<router-link to="/student">会员管理</router-link>
<router-link to="/teacher">讲师管理</router-link>
</p>
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</div>
<!-- 引入js -->
<script src="vue.min.js"></script>
<script src="vue-router.min.js"></script>
<script>
// 1. 定义(路由)组件。
// 可以从其他文件 import 进来
const Welcome = { template: '<div>欢迎</div>' }
const Student = { template: '<div>student list</div>' }
const Teacher = { template: '<div>teacher list</div>' }
// 2. 定义路由
// 每个路由应该映射一个组件。
const routes = [
{ path: '/', redirect: '/welcome' }, //设置默认指向的路径
{ path: '/welcome', component: Welcome },
{ path: '/student', component: Student },
{ path: '/teacher', component: Teacher }
]
// 3. 创建 router 实例,然后传 `routes` 配置
const router = new VueRouter({
routes // (缩写)相当于 routes: routes
})
// 4. 创建和挂载根实例。
// 从而让整个应用都有路由功能
const app = new Vue({
el: '#app',
router
})
// 现在,应用已经启动了!
</script>
</body>
2)项目中整合的router/index.js
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
export const constantRouterMap = [
{ path: '/login', component: () => import('@/views/login/index'), hidden: true },
{ path: '/404', component: () => import('@/views/404'), hidden: true },
{
path: '/',
component: Layout,
redirect: '/dashboard',
name: '后台首页',
hidden: true,
children: [{
path: 'dashboard',
component: () => import('@/views/dashboard/index')
}]
},
{
path: '/edu/teacher',
component: Layout,
redirect: '/edu/teacher/list',
name: '讲师管理',
meta: { title: '讲师管理', icon: 'example' },
children: [
{
path: 'list',
name: '讲师列表',
component: () => import('@/views/edu/teacher/list'),
meta: { title: '讲师列表', icon: 'table' }
},
{
path: 'save',
name: '添加讲师',
component: () => import('@/views/edu/teacher/save'),
meta: { title: '添加讲师', icon: 'tree' }
},
{
path: 'edit/:id',
name: '编辑讲师',
component: () => import('@/views/edu/teacher/save'),
meta: { title: '编辑讲师', noCache: true },
hidden: true
}
]
},
{
path: 'external-link',
component: Layout,
children: [
{
path: 'https://panjiachen.github.io/vue-element-admin-site/#/',
meta: { title: 'External Link', icon: 'link' }
}
]
},
{ path: '*', redirect: '/404', hidden: true }
]
export default new Router({
// mode: 'history', //后端支持可开
scrollBehavior: () => ({ y: 0 }),
routes: constantRouterMap
})
小疑问:字段name和meta的区别?
name
字段:- 用途:
name
字段通常用于给路由一个唯一的名字,这个名字可以在编程时用来引用或导航到这个路由。 - 示例:在这个例子中,
name: '讲师管理'
意味着这个路由被命名为“课程分类管理”。 - 使用场景:
name
字段在编程时特别有用,比如使用 Vue Router 的router.push({ name: '讲师管理' })
方法来导航到这个路由,而不是使用路径字符串。
- 用途:
meta
字段:- 用途:
meta
字段是一个对象,用于存储自定义的元信息(metadata),这些信息可以是任何你需要的数据,比如标题、图标、权限要求等。 - 示例:在这个例子中,
meta: { title: '讲师管理', icon: 'example' }
定义了两个元信息:title
和icon
。 - 使用场景:
meta
字段常用于动态设置页面标题、显示页面图标、检查权限等。例如,你可以根据meta.title
动态设置页面头部(header)的标题,或者根据meta.requiresAuth
来决定是否允许用户访问该页面。
- 用途:
5.8axios
axios是独立于vue的一个项目,基于promise用于浏览器和node.js的http客户端
- 在浏览器中可以帮助我们完成 ajax请求的发送
- 在node.js中可以向远程接口发送请求
1.引入引入vue.min.js和axios.min.js
2.调用接口
注意:测试时需要开启后端服务器,并且后端开启跨域访问权限
//自己编写data.json数据,模拟后端返回接口数据
{
"code": 200,
"message": "成功",
"data": {
"items": [
{
"name": "lucy",
"age": 20
},
{
"name": "jack",
"age": 18
},
{
"name": "tom",
"age": 39
}
]
}
}
//axios.html
<body>
<div id="app">
<table border="1">
<tr>
<td>年龄</td>
<td>姓名</td>
</tr>
<tr v-for="item in memberList">
<td>{{item.age}}</td>
<td>{{item.name}}</td>
</td>
</tr>
</table>
</div>
<script src="vue.min.js"></script>
<script src="axios.min.js"></script>
<script>
new Vue({
el: '#app',
data: { //data定义变量和初始值
memberList: []//数组
},
//渲染前执行
created() {
this.getList()
},
methods: {
//创建方法,查询所有用户信息
getList() {
//使用axios发送ajax请求
//axios.提交方式("请求接口路径").then(箭头函数).catch(箭头函数)
axios.get('data.json')
.then(response => { //请求成功执行then方法,返回数据
console.log(response)
this.memberList = response.data.data.items
})
.catch(error => { //请求失败执行then方法
console.log(error)
})
}
}
})
</script>
</body>
6.Node.js、NPM包管理器、Bable
6.1Node.js
简单的说 Node.js 就是运行在服务端的 JavaScript。
如果你是一个前端程序员,你不懂得像PHP、Python或Ruby等动态编程语言,然后你想创建自己的服务,那么Node.js是一个非常好的选择。
Node.js 是运行在服务端的 JavaScript,如果你熟悉Javascript,那么你将会很容易的学会Node.js。
当然,如果你是后端程序员,想部署一些高性能的服务,那么学习Node.js也是一个非常好的选择。
6.2NPM包管理器
NPM全称Node Package Manager,是Node.js包管理工具,是全球最大的模块生态系统,里面所有的模块都是开源免费的;也是Node.js的包管理工具,相当于前端的Maven
使用npm管理项目
1、创建文件夹npm
2、项目初始化
#建立一个空文件夹,在命令提示符进入该文件夹 执行命令初始化
npm init
#按照提示输入相关信息,如果是用默认值则直接回车即可。
#name: 项目名称
#version: 项目版本号
#description: 项目描述
#keywords: {Array}关键词,便于用户搜索到我们的项目
#最后会生成package.json文件,这个是包的配置文件,相当于maven的pom.xml
#我们之后也可以根据需要进行修改。
#如果想直接生成 package.json 文件,那么可以使用命令
npm init -y
2、修改npm镜像
#经过下面的配置,以后所有的 npm install 都会经过淘宝的镜像地址下载
npm config set registry https://registry.npm.taobao.org
#查看npm配置信息
npm config list
3、npm install命令的使用
#使用 npm install 安装依赖包的最新版,
#模块安装的位置:项目目录\node_modules
#安装会自动在项目目录下添加 package-lock.json文件,这个文件帮助锁定安装包的版本
#同时package.json 文件中,依赖包会被添加到dependencies节点下,类似maven中的 <dependencies>
npm install jquery
#npm管理的项目在备份和传输的时候一般不携带node_modules文件夹
npm install #根据package.json中的配置下载依赖,初始化项目
#如果安装时想指定特定的版本
npm install jquery@2.1.x
#devDependencies节点:开发时的依赖包,项目打包到生产环境的时候不包含的依赖
#使用 -D参数将依赖添加到devDependencies节点
npm install --save-dev eslint
#或
npm install -D eslint
#全局安装
#Node.js全局安装的npm包和工具的位置:用户目录\AppData\Roaming\npm\node_modules
#一些命令行工具常使用全局安装的方式
npm install -g webpack
4、其它命令
#更新包(更新到最新版本)
npm update 包名
#全局更新
npm update -g 包名
#卸载包
npm uninstall 包名
#全局卸载
npm uninstall -g 包名
6.3Bable
Babel是一个广泛使用的转码器,可以将ES6代码转为ES5代码,从而在现有环境执行执行。
这意味着,你可以现在就用 ES6 编写程序,而不用担心现有环境是否支持。
6.4Webpack
Webpack 是一个前端资源加载/打包工具。它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。
从图中我们可以看出,Webpack 可以将多种静态资源 js、css、less 转换成一个静态文件,减少了页面的请求。
7.跨域问题
7.1跨域形成
Access to XMLHttpRequest at 'http://localhost:8001/eduservice/user/login' from origin 'http://localhost:9528' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
通过一个地址去访间另外一个地址,这个过程中如果有三个地方任何一个不一样:
- 访问协议:http https
- ip地址:192.168.1.1 172.11.11.11
- 端口号:9528 8001
7.2跨域解决
方法一:在Controller类上加上注解 @CrossOrigin
方法二:网关
8.路由
==只判断值相等
===判断值和类型均相等
<el-table-column label="头衔" width="80">
<template slot-scope="scope">
{{ scope.row.level===1?'高级讲师':'首席讲师' }}
</template>
</el-table-column>
9.Nginx
反向代理服务器
- 请求转发:得到客户端请求,根据请求转发到具体服务器中(路径匹配)
- 负载均衡:把请求均分到不同服务器
- 动静分离:把Java代码存放Tomcat服务器,静态资源存放在静态服务器
9.1请求转发
1)配置nginx代理
在nginx.conf中配置对应的微服务服务器地址即可
注意:最好修改默认的 80端口到81
http {
server {
listen 81;
......
}
......
server {
#监听端口
listen 9001;
#主机
server_name localhost;
#匹配路径
location ~ /eduservice/ {
proxy_pass http://localhost:8001;
}
location ~ /eduoss/ {
proxy_pass http://localhost:8002;
}
}
}
配置后,需要把Nginx重新启动
2)配置开发环境-修改config/dev.env.js
3)测试请求接口
10.EasyExcel
10.1特点
- EasyExcel是阿里巴巴开源的一个excel处理框架,以使用简单、节省内存著称。EasyExcel能大大减少占用内存的主要原因是在解析Excel时没有将文件数据一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析。
- EasyExcel采用一行一行的解析模式,并将一行的解析结果以观察者的模式通知处理(AnalysisEventListener)。
10.2写入和读出
1)引入依赖
<dependencies>
<!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.1.1</version>
</dependency>
</dependencies>
<!--xls-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.17</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.17</version>
</dependency>
2)创建实体类
设置表头和添加的数据字段
import com.alibaba.excel.annotation.ExcelProperty;
/**
* 实体类对象对应Excel
*/
//设置表头和添加的数据字段
public class DemoData {
//设置表头名称
@ExcelProperty("学生编号")
private int sno;
//设置表头名称
@ExcelProperty("学生姓名")
private String sname;
public int getSno() {
return sno;
}
public void setSno(int sno) {
this.sno = sno;
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
@Override
public String toString() {
return "DemoData{" +
"sno=" + sno +
", sname='" + sname + '\'' +
'}';
}
}
3)测试EasyExcel的写
import com.alibaba.excel.EasyExcel;
import java.util.ArrayList;
import java.util.List;
/**
* 测试EasyExcel的写
*/
public class TestEasyExcelWrite {
public static void main(String[] args) {
// 1)将java代码写入Excel
// 1.设置要写入的文件夹地址和excel名称
String fileName = "D:\\guli_1010\\write.xlsx";
// 2.调用easyexcel里面的方法实现写操作
//write方法两个参数:第一个参数文件路径名称,第二个参数实体类class
EasyExcel.write(fileName, DemoData.class).sheet("写入方法一").doWrite(data());
}
//循环设置要添加的数据,最终封装到list集合中
private static List<DemoData> data() {
List<DemoData> list = new ArrayList<DemoData>();
for (int i = 0; i < 10; i++) {
DemoData data = new DemoData();
data.setSno(i);
data.setSname("张三"+i);
list.add(data);
}
return list;
}
}
4)测试EasyExcel的读
读取操作需先设置监听器类
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
//创建读取excel监听器
public class ExcelListener extends AnalysisEventListener<DemoData> {
//创建list集合封装最终的数据
List<DemoData> list = new ArrayList<DemoData>();
//一行一行去读取excle内容
@Override
public void invoke(DemoData user, AnalysisContext analysisContext) {
//输入每行内容
System.out.println("***"+user);
list.add(user);
}
//读取excel表头信息
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
System.out.println("表头信息:"+headMap);
}
//读取完成后执行
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
}
}
测试读取Excel代码
import com.alibaba.excel.EasyExcel;
/**
* 测试EasyExcel的读
*/
public class TestEasyExcelRead {
public static void main(String[] args) {
// 1)将Excel的文档读出来
// 1.设置要读出的文件夹地址和excel名称
String fileName = "D:\\guli_1010\\write.xlsx";
// 2.指定读用哪个class去读,然后读取第一个sheet,并设置监听器。 文件流会自动关闭
EasyExcel.read(fileName, DemoData.class, new ExcelListener()).sheet().doRead();
}
}
11.阿里云视频点播
11.1API和SDK的区别
- API:固定的请求地址+请求参数。
- 阿里云提供固定的地址,只需要调用这个固定的地址,向地址传递参数,实现功能。
- 可以通过httpClient技术调用API地址,而不需要通过浏览器。
- SDK:封装的API,更方便使用,可以调用阿里云提供类或者接口里面的方法实现视频功能(类似EasyExcel)
11.2视频点播SDK-测试
视频点播(VOD)-阿里云帮助中心
因为上传视频可以进行加密,加密之后,使用加密之后地址不能进行视频播放。
在数据库存储不存地址,而是存储视频id
1)前置-安装添加maven仓库的配置和依赖到pom
maven仓库
<repositories>
<repository>
<id>sonatype-nexus-staging</id>
<name>Sonatype Nexus Staging</name>
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
依赖
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.3.3</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-vod</artifactId>
<version>2.15.5</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.2</version>
</dependency>
注意:缺少aliyun-sdk-vod-upload的依赖
-需要之间导入官方文档的jar包视频点播SDK及对应Demo源码的下载地址_视频点播(VOD)-阿里云帮助中心
-使用mvn - install手动安装(切记安装位置是仓库需要手动移动到repository对应文件中)
命令行进入阿里云zip有jar包的lib目录,执行以下代码
mvn install:install-file -DgroupId=com.aliyun -DartifactId=aliyun-sdk-vod-upload -Dversion=1.4.15 -Dpackaging=jar -Dfile=aliyun-java-vod-upload-1.4.15.jar
-DgroupId=com.aliyun,指你本地respository仓库种需要放依赖的位置
安装完后,需要配置依赖
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-sdk-vod-upload</artifactId>
<version>1.4.15</version>
</dependency>
2)初始化对象-创建SDK工具类
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.profile.DefaultProfile;
/**
* 阿里云SDK 初始化对象
*/
public class InitObject {
public static DefaultAcsClient initVodClient(String accessKeyId, String accessKeySecret) throws ClientException {
String regionId = "cn-shanghai"; // 点播服务接入区域
DefaultProfile profile = DefaultProfile.getProfile(regionId, accessKeyId, accessKeySecret);
DefaultAcsClient client = new DefaultAcsClient(profile);
return client;
}
}
3)根据视频ID获取视频地址
//1 根据视频iD获取视频播放地址
public static void getPlayUrl() throws Exception{
//创建初始化对象
DefaultAcsClient client = InitObject.initVodClient("LTAI4FvvVEWiTJ3GNJJqJnk7", "9st82dv7EvFk9mTjYO1XXbM632fRbG");
//创建获取视频地址request和response
GetPlayInfoRequest request = new GetPlayInfoRequest();
GetPlayInfoResponse response = new GetPlayInfoResponse();
//向request对象里面设置视频id
request.setVideoId("474be24d43ad4f76af344b9f4daaabd1");
//调用初始化对象里面的方法,传递request,获取数据
response = client.getAcsResponse(request);
List<GetPlayInfoResponse.PlayInfo> playInfoList = response.getPlayInfoList();
//播放地址
for (GetPlayInfoResponse.PlayInfo playInfo : playInfoList) {
System.out.print("PlayInfo.PlayURL = " + playInfo.getPlayURL() + "\n");
}
//Base信息
System.out.print("VideoBase.Title = " + response.getVideoBase().getTitle() + "\n");
}
4)根据视频ID获取视频播放凭证
//2 根据视频iD获取视频播放凭证
public static void getPlayAuth() throws Exception{
DefaultAcsClient client = InitObject.initVodClient("LTAI5tLYLEuvvB6jrnmk1cDR", "KnBSfb9B8oT6bmcntogyzMkt5xDYQo");
//创建获取视频播放凭证
GetVideoPlayAuthRequest request = new GetVideoPlayAuthRequest();
GetVideoPlayAuthResponse response = new GetVideoPlayAuthResponse();
request.setVideoId("40e9448aa35e71ef95126732b78e0102");
//调用初始化对象的方法得到凭证
response = client.getAcsResponse(request);
System.out.println("playAuth:"+response.getPlayAuth());
}
5)上传视频
public static void main(String[] args) throws Exception {
String accessKeyId = "LTAI5tLYLEuvvB6jrnmk1cDR";
String accessKeySecret = "KnBSfb9B8oT6bmcntogyzMkt5xDYQo";
String title = "6 - What If I Want to Move Faster - upload by sdk"; //上传之后文件名称
//本地要上传的文件路径和名称
String fileName = "D:\\BaiduNetdiskDownload\\在线教育--谷粒学苑\\项目资料\\1-阿里云上传测试视频\\6 - What If I Want to Move Faster.mp4";
//上传视频的方法
UploadVideoRequest request = new UploadVideoRequest(accessKeyId, accessKeySecret, title, fileName);
/* 可指定分片上传时每个分片的大小,默认为2M字节 */
request.setPartSize(2 * 1024 * 1024L);
/* 可指定分片上传时的并发线程数,默认为1,(注:该配置会占用服务器CPU资源,需根据服务器情况指定)*/
request.setTaskNum(1);
UploadVideoImpl uploader = new UploadVideoImpl();
UploadVideoResponse response = uploader.uploadVideo(request);
if (response.isSuccess()) {
//上传成功后,获得的视频ID
System.out.print("VideoId=" + response.getVideoId() + "\n");
} else {
/* 如果设置回调URL无效,不影响视频上传,可以返回VideoId同时会返回错误码。其他情况上传失败时,VideoId为空,此时需要根据返回错误码分析具体错误原因 */
System.out.print("VideoId=" + response.getVideoId() + "\n");
System.out.print("ErrorCode=" + response.getCode() + "\n");
System.out.print("ErrorMessage=" + response.getMessage() + "\n");
}
6)删除视频
//根据视频id删除阿里云视频
@DeleteMapping("removeAlyVideo/{id}")
public R removeAlyVideo(@PathVariable String id) {
try {
//初始化对象
DefaultAcsClient client = InitObject.initVodClient(ConstantVodUtils.ACCESS_KEY_ID, ConstantVodUtils.ACCESS_KEY_SECRET);
//创建删除视频request对象
DeleteVideoRequest request = new DeleteVideoRequest();
//向request设置视频id
request.setVideoIds(id);
//调用初始化对象的方法实现删除
client.getAcsResponse(request);
return R.ok();
}catch(Exception e) {
e.printStackTrace();
throw new GuliException(20001,"删除视频失败");
}
}
12.微服务SpringCloud
12.1概念
1)微服务
- 微服务是架构风格
- 把一个项目拆分成独立的多个服务多个服务是独立运行,每个服务占用独立进程
目前微服务的开发框架,最常用的有以下四个:
Spring Cloud:http://projects.spring.io/spring-cloud(现在非常流行的微服务架构)
Dubbo:http://dubbo.io
Dropwizard:http://www.dropwizard.io (关注单个微服务的开发)
Consul、etcd&etc.(微服务的模块)
2)SpringCloud
- springcloud并不是一种技术,是很多技术总称,很多框架集合
- springcloud里面有很多框架(技术),使用springcloud里面这些框架实现微服务操作
- 使用springcloud,需要依赖技术springboot
Spring Cloud相关基础服务组件
- 服务发现——Netflix Eureka (Nacos)
- 服务调用——Netflix Feign
- 熔断器——Netflix Hystrix
- 服务网关——Spring Cloud GateWay
- 分布式配置——Spring Cloud Config (Nacos)
- 消息总线 —— Spring Cloud Bus (Nacos)
3)Nacos注册中心
Nacos是以服务为主要服务对象的中间件,Nacos支持所有主流的服务发现、配置和管理。
Nacos = Spring Cloud Eureka + Spring Cloud Config
Nacos 可以与 Spring, Spring Boot, Spring Cloud 集成,并能代替 Spring Cloud Eureka, Spring Cloud Config
通过 Nacos Server 和 spring-cloud-starter-alibaba-nacos-discovery 实现服务的注册与发现。
Nacos主要提供以下四大功能:
1. 服务发现和服务健康监测
2. 动态配置服务
3. 动态DNS服务
4. 服务及其元数据管理
-服务注册
添加依赖、配置信息和Nacos客户端注解
<!--服务注册-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
#配置application.properties
# nacos服务地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
//在客户端微服务启动类中添加注解
@EnableDiscoveryClient
4)Feign服务调用
Spring Cloud Feign帮助我们定义和实现依赖服务接口的定义。在Spring Cloud feign的实现下,只需要创建一个接口并用注解方式配置它,即可完成服务提供方的接口绑定,简化了在使用Spring Cloud Ribbon时自行封装服务调用客户端的开发量。
-配置
<!--服务调用-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
//在调用端的启动类添加注解
@EnableFeignClients
-在调用端创建interface,使用注解指定调用服务名称,定义调用的方法路径
/**
* Feigin服务调用接口
* 调用端创建interface,使用注解指定调用服务名称,定义调用的方法路径
*/
@FeignClient(name = "service-vod",fallback = VodFileDegradeFeignClient.class) //调用的服务名称
@Component
public interface VodClient {
//定义调用的方法路径
//根据视频id删除阿里云视频
//@PathVariable注解一定要指定参数名称,否则出错
@DeleteMapping("/eduvod/video/removeAlyVideo/{id}")
public R removeAlyVideo(@PathVariable("id") String id);
}
-在调用端的VideoServiceImpl中调用client中的方法
//注入vodClient
@Autowired
private VodClient vodClient;
//删除小节,删除对应阿里云视频
@DeleteMapping("{id}")
public R deleteVideo(@PathVariable String id) {
//根据小节id获取视频id,调用方法实现视频删除
EduVideo eduVideo = videoService.getById(id);
String videoSourceId = eduVideo.getVideoSourceId();
//判断小节里面是否有视频id
if(!StringUtils.isEmpty(videoSourceId)) {
//根据视频id,远程调用实现视频删除
R result = vodClient.removeAlyVideo(videoSourceId);
if(result.getCode() == 20001) {
throw new GuliException(20001,"删除视频失败,熔断器...");
}
}
//删除小节
videoService.removeById(id);
return R.ok();
}
5)Hystrix熔断器
SpringCloud使用Hystrix组件提供断路器、资源隔离与自我修复功能。
当某些服务不稳定的时候,使用这些服务的用户线程将会阻塞,如果没有隔离机制,系统随时就有可能会挂掉,从而带来很大的风险。
-配置
<!--hystrix依赖,主要是用 @HystrixCommand -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--ribben依赖,主要是用于负载均衡-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
#开启熔断机制
feign.hystrix.enabled=true
# 设置hystrix超时时间,默认1000ms
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=6000
-VlodClient的实现类
/**
* VodClient的实现类
* 用于出错后熔断
*/
@Component
public class VodFileDegradeFeignClient implements VodClient {
//出错之后会执行
@Override
public R removeAlyVideo(String id) {
return R.error().message("删除视频出错了");
}
@Override
public R deleteBatch(List<String> videoIdList) {
return R.error().message("删除多个视频出错了");
}
}
13.统计分析
13.1定时任务-Cron
定时任务:在固定时侯自动执行程序比如闹钟。
使用Cron表达式,制定定时任务规则
在线生成cron表达式:http://cron.qqe2.com/
1)在启动类添加注解
@EnableScheduling //开启定时任务
2)创建日期工具类
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
/**
* 日期操作工具类
*
* @author qy
* @since 1.0
*/
public class DateUtil {
private static final String dateFormat = "yyyy-MM-dd";
/**
* 格式化日期
*
* @param date
* @return
*/
public static String formatDate(Date date) {
SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
return sdf.format(date);
}
/**
* 在日期date上增加amount天 。
*
* @param date 处理的日期,非null
* @param amount 要加的天数,可能为负数
*/
public static Date addDays(Date date, int amount) {
Calendar now =Calendar.getInstance();
now.setTime(date);
now.set(Calendar.DATE,now.get(Calendar.DATE)+amount);
return now.getTime();
}
public static void main(String[] args) {
System.out.println(DateUtil.formatDate(new Date()));
System.out.println(DateUtil.formatDate(DateUtil.addDays(new Date(), -1)));
}
}
3)创建定时任务类
import com.atguigu.staservice.service.StatisticsDailyService;
import com.atguigu.staservice.utils.DateUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* 定时任务类
*/
@Component
public class ScheduledTask {
@Autowired
private StatisticsDailyService statisticsDailyService;
//每隔5s执行一次
//@Scheduled(cron = "0/5 * * * * ? ")
public void task1(){
System.out.println("自动任务");
}
//每天一点执行,把前一天数据查询添加
@Scheduled(cron = "0 0 1 * * ? ")
public void task2(){
statisticsDailyService
.registerCount(DateUtil.formatDate(DateUtil.addDays(new Date(),-1)));
}
}
13.2ECharts图表整合
官方网站:https://echarts.baidu.com/
1)Demo测试
引入ECharts js文件->定义图表区域->渲染图表
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<!-- 引入 ECharts 文件 -->
<script src="echarts.min.js"></script>
</head>
<body>
<!-- 为ECharts准备一个具备大小(宽高)的Dom -->
<div id="main" style="width: 600px;height:400px;"></div>
</body>
<script type="text/javascript">
// 基于准备好的dom,初始化echarts实例
var myChart = echarts.init(document.getElementById('main'));
// 指定图表的配置项和数据
var option = {
title: {
text: 'ECharts 入门示例'
},
tooltip: {},
legend: {
data: ['销量']
},
xAxis: {
data: ["衬衫", "羊毛衫", "雪纺衫", "裤子", "高跟鞋", "袜子"]
},
yAxis: {},
series: [{
name: '销量',
type: 'bar',
data: [5, 20, 36, 10, 10, 20]
}]
};
// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option);
</script>
</html>
2)安装ECharts
npm install --save echarts@4.1.0
3)整合前端代码
<template>
<div class="app-container">
<!--表单-->
<el-form :inline="true" class="demo-form-inline">
<el-form-item>
<el-select v-model="searchObj.type" clearable placeholder="请选择">
<el-option label="学员登录数统计" value="login_num"/>
<el-option label="学员注册数统计" value="register_num"/>
<el-option label="课程播放数统计" value="video_view_num"/>
<el-option label="每日课程数统计" value="course_num"/>
</el-select>
</el-form-item>
<el-form-item>
<el-date-picker
v-model="searchObj.begin"
type="date"
placeholder="选择开始日期"
value-format="yyyy-MM-dd" />
</el-form-item>
<el-form-item>
<el-date-picker
v-model="searchObj.end"
type="date"
placeholder="选择截止日期"
value-format="yyyy-MM-dd" />
</el-form-item>
<el-button
:disabled="btnDisabled"
type="primary"
icon="el-icon-search"
@click="showChart()">查询</el-button>
</el-form>
<div class="chart-container">
<div id="chart" class="chart" style="height:500px;width:100%" />
</div>
</div>
</template>
<script>
import echarts from 'echarts'
import staApi from '@/api/sta'
export default {
data() {
return {
searchObj:{},
btnDisabled:false,
xData:[],
yData:[]
}
},
methods:{
//生成图表-获取数据
showChart() {
staApi.getDataSta(this.searchObj)
.then(response => {
console.log('*****************'+response)
this.yData = response.data.numDataList
this.xData = response.data.date_calculatedList
//调用下面生成图表的方法,改变值
this.setChart()
})
},
//官方方法
setChart() {
// 基于准备好的dom,初始化echarts实例
this.chart = echarts.init(document.getElementById('chart'))
// console.log(this.chart)
// 指定图表的配置项和数据
var option = {
title: {
text: '数据统计'
},
tooltip: {
trigger: 'axis'
},
dataZoom: [{
show: true,
height: 30,
xAxisIndex: [
0
],
bottom: 30,
start: 10,
end: 80,
handleIcon: 'path://M306.1,413c0,2.2-1.8,4-4,4h-59.8c-2.2,0-4-1.8-4-4V200.8c0-2.2,1.8-4,4-4h59.8c2.2,0,4,1.8,4,4V413z',
handleSize: '110%',
handleStyle: {
color: '#d3dee5'
},
textStyle: {
color: '#fff'
},
borderColor: '#90979c'
},
{
type: 'inside',
show: true,
height: 15,
start: 1,
end: 35
}],
// x轴是类目轴(离散数据),必须通过data设置类目数据
xAxis: {
type: 'category',
data: this.xData
},
// y轴是数据轴(连续数据)
yAxis: {
type: 'value'
},
// 系列列表。每个系列通过 type 决定自己的图表类型
series: [{
// 系列中的数据内容数组
data: this.yData,
// 折线图
type: 'line'
}]
}
this.chart.setOption(option)
}
}
}
</script>
14.SpringCloud-GateWay网关
14.1网关基本概念
API 网关出现的原因是微服务架构的出现,不同的微服务一般会有不同的网络地址,而外部客户端可能需要调用多个服务的接口才能完成一个业务需求,如果让客户端直接与各个微服务通信,会有以下的问题:
(1)客户端会多次请求不同的微服务,增加了客户端的复杂性。
(2)存在跨域请求,在一定场景下处理相对复杂。
(3)认证复杂,每个服务都需要独立认证。
(4)难以重构,随着项目的迭代,可能需要重新划分微服务。例如,可能将多个服务合并成一个或者将一个服务拆分成多个。如果客户端直接与微服务通信,那么重构将会很难实施。
(5)某些微服务可能使用了防火墙 / 浏览器不友好的协议,直接访问会有一定的困难。
以上这些问题可以借助 API 网关解决。API 网关是介于客户端和服务器端之间的中间层,所有的外部请求都会先经过 API 网关这一层。也就是说,API的实现方面更多的考虑业务逻辑,而安全、性能、监控可以交由 API 网关来做,这样既提高业务灵活性又不缺安全性
14.2SpringCloudGateway核心概念
(1)路由。路由是网关最基础的部分,路由信息有一个ID、一个目的URL、一组断言和一组Filter组成。如果断言路由为真,则说明请求的URL和配置匹配
(2)断言。Java8中的断言函数。Spring Cloud Gateway中的断言函数输入类型是Spring5.0框架中的ServerWebExchange。Spring Cloud Gateway中的断言函数允许开发者去定义匹配来自于http request中的任何信息,比如请求头和参数等。
(3)过滤器。一个标准的Spring webFilter。Spring cloud gateway中的filter分为两种类型的Filter,分别是Gateway Filter和Global Filter。过滤器Filter将会对请求和响应进行修改处理
14.3创建api-gateway模块(网关服务)
(1)在pom.xml引入依赖
<dependencies>
<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>
</dependencies>
(2)编写application.properties配置文件
配置对应需要覆盖网关的模块
# 服务端口
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
#服务路由名小写
#spring.cloud.gateway.discovery.locator.lower-case-service-id=true
#设置路由id
spring.cloud.gateway.routes[0].id=service-acl
#设置路由的uri
spring.cloud.gateway.routes[0].uri=lb://service-acl
#设置路由断言,代理servicerId为auth-service的/auth/路径
spring.cloud.gateway.routes[0].predicates= Path=/*/acl/**
#配置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/**
#配置service-ucenter服务
spring.cloud.gateway.routes[2].id=service-ucenter
spring.cloud.gateway.routes[2].uri=lb://service-ucenter
spring.cloud.gateway.routes[2].predicates= Path=/ucenterservice/**
#配置service-ucenter服务
spring.cloud.gateway.routes[3].id=service-cms
spring.cloud.gateway.routes[3].uri=lb://service-cms
spring.cloud.gateway.routes[3].predicates= Path=/cmsservice/**
spring.cloud.gateway.routes[4].id=service-msm
spring.cloud.gateway.routes[4].uri=lb://service-msm
spring.cloud.gateway.routes[4].predicates= Path=/edumsm/**
spring.cloud.gateway.routes[5].id=service-order
spring.cloud.gateway.routes[5].uri=lb://service-order
spring.cloud.gateway.routes[5].predicates= Path=/orderservice/**
spring.cloud.gateway.routes[6].id=service-order
spring.cloud.gateway.routes[6].uri=lb://service-order
spring.cloud.gateway.routes[6].predicates= Path=/orderservice/**
spring.cloud.gateway.routes[7].id=service-oss
spring.cloud.gateway.routes[7].uri=lb://service-oss
spring.cloud.gateway.routes[7].predicates= Path=/eduoss/**
spring.cloud.gateway.routes[8].id=service-statistic
spring.cloud.gateway.routes[8].uri=lb://service-statistic
spring.cloud.gateway.routes[8].predicates= Path=/staservice/**
spring.cloud.gateway.routes[9].id=service-vod
spring.cloud.gateway.routes[9].uri=lb://service-vod
spring.cloud.gateway.routes[9].predicates= Path=/eduvod/**
spring.cloud.gateway.routes[10].id=service-edu
spring.cloud.gateway.routes[10].uri=lb://service-edu
spring.cloud.gateway.routes[10].predicates= Path=/eduuser/**
14.4网关解决跨域问题
(1)创建配置类
@Configuration
public class CorsConfig {
@Bean
public CorsWebFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedMethod("*");
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
}
15.SpringSecurity
15.1框架介绍
Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。
(1)用户认证
进入用户登录时候,输入用户名和密码,查询数据库,输入用户名和密码是否正确,如果正确的
话,认证成功了
(2)用户授权
登录了系统,登录用户可能是不同的角色,比如现在登录的用户是管理员,管理员操作所有功能,
比如登录用户普通用户,操作功能肯定比管理员少很多
Spring Security其实就是用filter,多请求的路径进行过滤。
(1)如果是基于Session,那么Spring-security会对cookie里的sessionid进行解析,找到服务器存储的sesion信息,然后判断当前用户是否符合请求的要求。
(2)如果是token,则是解析出token,然后将当前请求加入到Spring-security管理的权限信息中去
15.2认证与授权实现思路
如果系统的模块众多,每个模块都需要就行授权与认证,所以我们选择基于token的形式进行授权与认证。
用户根据用户名密码认证成功,然后获取当前用户角色的一系列权限值,并以用户名为key,权限列表为value的形式存入redis缓存中,根据用户名相关信息生成token返回,
浏览器将token记录到cookie中,每次调用api接口都默认将token携带到header请求头中,Spring-security解析header头获取token信息,解析token获取当前用户名,根据用户名就可以从redis中获取权限列表,这样Spring-security就能够判断当前请求是否有权限访问。
16.Nacos配置中心
16.1介绍与作用
(1)Nacos介绍
Nacos 可以与 Spring, Spring Boot, Spring Cloud 集成,并能代替 Spring Cloud Eureka, Spring Cloud Config。通过 Nacos Server 和 spring-cloud-starter-alibaba-nacos-config 实现配置的动态变更。
(2)应用场景
在系统开发过程中,开发者通常会将一些需要变更的参数、变量等从代码中分离出来独立管理,以独立的配置文件的形式存在。目的是让静态的系统工件或者交付物(如 WAR,JAR 包等)更好地和实际的物理运行环境进行适配。配置管理一般包含在系统部署的过程中,由系统管理员或者运维人员完成。配置变更是调整系统运行时的行为的有效手段。
如果微服务架构中没有使用统一配置中心时,所存在的问题:
- 配置文件分散在各个项目里,不方便维护
- 配置内容安全与权限
- 更新配置后,项目需要重启
nacos配置中心:系统配置的集中管理(编辑、存储、分发)、动态更新不重启、回滚配置(变更管理、历史版本管理、变更审计)等所有与配置相关的活动。
16.2读取Nacos配置中心的配置文件
(1)在service中引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
(2)创建bootstrap.properties配置文件
#配置中心地址
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
#spring.profiles.active=dev
# 该配置影响统一配置中心中的dataId
spring.application.name=service-statistics
(3)把项目之前的application.properties内容注释,启动项目查看效果
16.3补充:springboot配置文件加载顺序
其实yml和properties文件是一样的原理,且一个项目上要么yml或者properties,二选一的存在。推荐使用yml,更简洁。
(1)加载顺序
这里主要是说明application和bootstrap的加载顺序。
- bootstrap.yml(bootstrap.properties)先加载
- application.yml(application.properties)后加载
- bootstrap.yml 用于应用程序上下文的引导阶段。bootstrap.yml 由父Spring ApplicationContext加载。
- 父ApplicationContext 被加载到使用 application.yml 的之前。
(2)配置区别
bootstrap.yml 和application.yml 都可以用来配置参数。
- bootstrap.yml 可以理解成系统级别的一些参数配置,这些参数一般是不会变动的。
- application.yml 可以用来定义应用级别的。