summary - AvengerEug/treadpit GitHub Wiki
-
一. 前端
- 1.1 Css/Scss
- 1.2 Java Script
-
1.3 Vue.js/Nuxt.js
- 1.3.1 vue.js生命周期
- 1.3.2 nuxt.js 生命周期
- 1.3.3 computed(计算属性)
- 1.3.4 v-show和v-if
- 1.3.5 v-for
- 1.3.6 this.$set/Vue.set 响应式属性
- 1.3.7 自定义指令
- 1.3.8 vue-router base属性
- 1.3.9 Nuxt v-for嵌套v-if的坑
- 1.3.10 .native添加原生事件
- 1.3.11 nuxt.js重定向
- 1.3.12 vue深度监听属性
- 1.3.13 vue脚手架3.0版本之前的路由配置
- 1.3.14 前端生成excel表格
- 1.3.15 限制输入字符的语句在linux中会失效
- 1.3.15 vue.js嵌套路由 子路由path的定义
- 1.3.16 Nuxt.js 官网提供的自定义loading组件. 并将该组件定义在nuxt.config.js的loading 选项中
- 1.3.17 vue.js父子组件加载顺序
- 1.3.18 vue.js .sync修饰符
- 1.4 ElementUI
- 1.5 npm
- 1.6 Html
- 1.7 ES6语法
-
二. 后端
-
2.1 Java basic
- 2.1.1 Double引发的Null Pointer Exception
- 2.1.2 强转类型前提
- 2.1.3 基础数据类型相等的判断
- 2.1.4 Split方法
- 2.1.5 异常中try, finally, catch中return的顺序
- 2.1.6 Servlet中没有暴露出无参构造器(手动编写了带参构造器)
- 2.1.7 根据oss url获取远程图片, 并转成base64
- 2.1.8 动态代理抛出实际真实异常
- 2.1.9 多线程基础
- 2.1.10 抽象类和接口的区别
- 2.1.11 Overload和Override的区别
- 2.1.12 ArrayList,LinkedList,Vector
- 2.1.13 HashMap, HashTable, ConcurrentHashMap
- 2.1.14 创建Class对象的几种方法
- 2.1.15 ArrayList.asList()的坑
- 2.1.16 不要在 foreach 循环里进行元素的 remove/add 操作
- 2.1.17 JVM 类加载器
- 2.2 Spring Cloud
-
2.3 Spring Boot
- 2.3.1 @RequestParam 类型映射
- 2.3.2 @RequestMapping 方法映射关系
- 2.3.3 @RequestBody注解接收Post请求ContentType为application/x-www-form-urlencoded格式的数据
- 2.3.4 @RequestMapping支持多种请求方法
- 2.3.5 @RequestMapping指定request和response的contentType
- 2.3.6 SpringBoot使用对象来接收query参数
- 2.3.7 Spring mvc 全局异常处理注解@ExceptionHandler
- 2.3.8 Spring获取ioc容器上下文的两种方式
- 2.3.9 Spring核心
- 2.3.10 Spring bean生命周期
- 2.3.11 SpringBoot是job注解和异步调用注解生效前提
- 2.3.12 Springboot yml文件配置的坑
- 2.3.13 @Autowired和@Resource的区别
- 2.3.14 SpringBoot默认包扫描路径
- 2.4 Mybatis
- 2.5 MySql
- 2.6 Elasticsearch
- 2.7 Redis
- 2.8 Maven
- 2.9 Git
- 2.10 设计模式与应用
-
2.1 Java basic
- 三. DevOps
-
四. Linux
-
4.1 常用命令(常忘)
- 4.1.1 给文件添加可执行权限
- 4.1.2 删除当前文件夹内所有内容
- 4.1.3 压缩当前文件夹为zip包
- 4.1.4 解压缩zip压缩包到当前目录
- 4.1.5 查看某个文件的大小
- 4.1.6 列出文件夹下面第一级每个文件的大小(包括文件夹和文件)
- 4.1.7 查找文件命令
- 4.1.8 查找某个端口被占用
- 4.1.9 查看linux各进程内存使用情况
- 4.1.10 Linux 启动项目后台保留策略
- 4.1.11 cp -r 命令的坑
- 4.1.12 清除所有log文件(内存不够时)
- 4.1.13 压缩成tar.gz压缩包
- 4.1.14 解压缩tar.gz包
- 4.1.15 Linux文件权限查看及无权限解决方案
-
4.1 常用命令(常忘)
- 五. Http
- 六. IDEA
- 七. 阿里云oss
浏览器的渲染机制是从上至下, 当有多个样式同时应用到同一个dom元素时, 默认使用最后一个样式。(不考虑手动设置权重的case)
1. 前提: 标签之间是嵌套关系
2. 文字颜色、字体大小、字体、行高、文字有关的属性都可被继承
3. 特殊点:
a标签不能实现字体颜色的集成, 字体大小可被继承
h1标签不可以继承字体大小, 继承过来会做一些 `计算`.
| 类别 | !important | 行内样式 | id选择器 | 类选择器 | 标签选择器 | 默认样式 |
|---|---|---|---|---|---|---|
| 对应权重 | 1000以上 | 1000 | 100 | 10 | 1 | 0 |
对于所有的选择器, 都会统计权重, 哪个权重大, 使用哪个(若权重一样, 则应用最后一个)
- backgroud: url('xxxx'); 的样式必须写在最前面。 eg: background-size: 100%; 的参数才会生效, 因为它是override机制。 同时可以在background: url('xxx') 接属性。 eg: background: url('xxx') no-repeat 100% 100%; 即一次性设置完。
- 使用background-image: url('xxx') 的方式, 这样则不需要考虑前后顺序
- 需要使用backgroundImage属性:
<div class="supply-index" :style="{backgroundImage: 'url(' + attributeObj.image + ')'}"></div>注意: 使用的是backgroundImage, 而不是background-image
width: 100%;
top: 50%;
transform: translateY(-50%);- 其实top: 50%, 已经是将该dom元素垂直居中了, 但是因为要内容居中, 所以需要Y轴移动元素内容高度的50%, 即transform: translateY(-50%)
- RGBA来标识颜色, 第四个参数可以增加透明度
- 常用块级元素: div, h1-h6, p, ul, li
- 特点:
- 独占一行
- 可以设置宽高
- 嵌套情况下, 子元素的宽度默认与父类一致
- 常用行级元素: span, a, strong, em, del, ins
- 特点:
- 在一行上显示
- 不能直接设置宽和高(若实在想设置宽和高, 可以设置成行内块元素)
- 元素的宽和高就是内容撑开宽和高
- 常用行级元素: input, img
- 特点:
- 在一行上显示
- 可以设置宽高
- 定义: 浏览器默认文本大小为16px, 行高是基于基线与基线之间的距离 = 文字高度 + 上下边距(padding)
- 定义: 标准文档流: 自上而下, 自左向右, 块元素独占一行, 行元素在一行上显示, 碰到父边框后换行
- 使用方式:
- 注意事项: 若两个对象的key一致, 那么会覆盖value, key不会重复
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop
//获取当前window窗体的高度: document.documentElement.clientHeight
window.addEventListener('scroll', function(){}, true) //注册滚动条滚动事件
document.documentElment //获取的是html元素arr.splice(0, 1) // => 表示从下标为0开始删除一个元素,
arr.splice(0, 1, 'test') // => 则对arr数组下标为0开始添加一个test元素- 处理数组的结果, 前者会返回数组的每一个元素对应的下标, 后者会返回每一个元素的value
- 首先addEventListener添加的事件是不会被覆盖的 其次若有层级关系(会冒泡), 则遵循以下规则: true的执行顺序在false之前 true和true之间: 外层的先触发 false和false之间: 内层的先触发


/* 举个栗子: 当前vue对象中有一个属性叫imageUrl, 但是因为它只是一个url,在动静分离的项目中,
如果要显示它必须还要在它前面添加oss host和ip, 所以此时可以使用计算属性对该属性进行处理, 计
算属性的作用比监听器更好, 因为它自带内存机制. 若this.imageUrl值没有变, 则每次获取计算属性
时都是从内存中获取
*/
computed: {
realImageUrl: function() {
return 'oss host : port ' + this.imageUrl
}
}- 在vue中computed的属性可以使用箭头函数, 第一个参数为vm(当前vue对象). eg:
computed: {
realImageUrl: vm => {
return 'oss host : port ' + this.imageUrl
}
}- 在nuxt中, computed的属性请使用普通函数, 如上述第一种. 若使用箭头函数的方式, 会不定时(有时能将当前vue对象依赖注入到vm参数,
有时却不能)的出现
imageUrl of undefined的错误
1. 首先要了解css中display: none; 该属性是将dom元素给隐藏且不占用文档流,但元素依然在网
页源码中可见(F12).
2. v-show指令的功能就是上述所说.
3. v-if指令的功能是将整个dom处于 '渲染-销毁' 这样的生命周期中.
4. 区别:
v-show: 不管初始值为false/true, 都会先渲染对应的dom元素。 对于需频繁控制dom元素的隐藏/显示的需求, 消耗性能较小.
v-if: 懒加载dom元素, 只有当value为true的时候才会对dom元素进行渲染, 若由true -> false, 则会对dom元素的所有
属性(绑定的事件, 响应式数据的绑定等)进行销毁. 当由false -> true, 则会重新绑定, 所以相比v-show而言,
性能消耗较大.- 先看以下示例(刚接触vue的学者渲染列表数据的经典写法)
<div v-for="(item, index) of items" :key="index">
<span>{{item.xxx}}</span>
</div>强烈建议不要使用上述的写法, 若你是做h5项目下拉式的分页功能倒无大碍. 若你是做web pc使用异步请求动态分页渲染列表
时, 它会让你见到```非常奇怪```的数据. 你会发现后面的几页数据(不刷新页面的前提下)始终会与前面一页的数据, 但重新
刷新页面时, 数据又正常了。
出现这样的case主要是因为vue为了减少性能消耗添加缓存的原因. 在vue中, 你可以为每一个dom元素绑定key属性, 改属性
是vue使用缓存的依据. 对于上面的例子, 当在web pc使用异步请求动态分页渲染列表的情景下, 若我们使用index作为当前
dom元素的key, 那么在渲染后面的页数时, 后面的index基本与上一页准备销毁的dom元素的key一致, vue为了尽可能高效
的渲染元素, 若发现销毁队列中的dom元素中与将要渲染的dom元素的key一致, 则会采用销毁掉列中的dom元素进行渲染.
所以, 在web pc使用异步请求动态分页渲染列表的情景下, 我们应该使用唯一标识作为dom元素的key, 这个唯一标识可以是
数据结构中的唯一值或者手动引入第三方uuid类库, 将uuid与dom元素的key绑定.
- 众所周知, vue的响应式数据是绑定在data方法中. 举个栗子:
<template>
<h1>the man's name is: {{man.name}}</h1>
<h2 v-if="man.age">the man's age is: {{man.age}}</h2>
<button @click="modifyAge">modify age</button>
</template>
export default {
data () {
return {
man: {
name: 'eugene'
}
}
},
methods: {
modifyAge () {
this.man.age = 13
}
},
mounted () {
this.man.age = 12
}
}
/*
1. 首先, 页面上不会显示 the man's age is 12。 因为age这个属性并不是响应式属性,
2. 其次, 就算点击 modify age button 页面也不会显示 the man's age is 13。 因
为this.$set/Vue.set方法动态添加响应式属性的前提是对象中的key不存在(在该栗子下,
虚拟dom树挂载到页面上时, mounted钩子方法就被调用了.), 否则该方法只做更新key对
应的value操作
3. 要在页面中显示the man's age is 12, 需要两个前提
A. man对象中无age属性
B. 在A的前提下调用this.$set(this.main, 'age', 12) 或 Vue.set(this.main, 'a
ge', 12)方法
*/- 场景: 在当前的vue实例中有一个名为currentUser的对象, 该对象只有一个id的属性, 现需要 给该对象添加一个name和age属性, 以至于在页面中能够支持响应式渲染。
方法1: 在初始化vue对象的时候给currentUser对象添加name和age属性 value均为空字符串
方法2: 使用Vue.set(this.currentUser, 'name', value) or this.$set(this.currentUser, 'name', value)
方法3: this.currentUser = Object.assign({}, this.currentUser, {
name: 'Eugene',
age: 23
})
//一定要将Object.assign创建一个新对象并重新赋值给this.currentUser对象, 而不要这样使用
Object.assign(this.currentUser. {
name: 'Eugene',
age: 23
})
// 因为Object.assign是将结果作为返回值返回-
注册全局自定义指令
Vue.directive('disabled', { bind: (el, binding) => { // bind钩子函数, vue挂载dom元素时会触发, 且只调用一次 }, update: (el, binding) => { // 虚拟dom树更新时, 该钩子函数会被调用 } }
最好别在bind钩子函数中操作其他dom元素, 因为它在虚拟dom树被创建时会被触发, 此时html的dom树还没有生成, 无法获取其他dom元素, 至于update钩子函数是否可以获取其他dom元素待确认 -
解析

添加该属性后 route会默认在每次跳转路由前把这个前缀给加上, 此时在浏览器中添加这个前缀或者不添加这个前缀都能match上路由
如下case:(若v-if 里面包含的标签中要使用的判断是否渲染dom元素的变量, eg如下的{{message.type}}要用到v-if的message, )
<div v-if="message">{{message.type}}</message>
一定要将前面的v-if改成v-show, 否则页面会在挂载(mounted钩子函数不会被执行)的时候失败。
具体错误如下: [nuxt] Error while initializing app DOMException: Failed to execute 'appendChild' on 'Node': This node type does not support this method.
(前提: message 在实例化vue 对象时 要为false)- 使用组件ui库, 若第三方ui库提供的组件中存在@click事件, 则直接使用@click会生效, 否者请加上.native
即 @click.native 表示使用原生的click事件
Nuxt.js 在asyncData方法中使用context上下文的redirect重定向时,
该方法有三个参数(statusCode, url, params)若只填一个url 则statusCode默认为302
若要传参数(只支持params的参数, 即锚点参数, 若存在query参数的话 重定向会失败), params参数是一个对象, key, value的格式
watch: {
属性: {
//self: this,
handler: function(newValue, oldValue) {
console.log(newValue)
},
deep: true
}
}- 通常在pc端写table组件时会用到, 一般prop为数组时, 这个会常用。
- 使用该方式对数组进行深度监听, 否则普通的监听数组内部数据进行改变时, 不会触发监听事件. 注: 在深度监听方式中, 如果要在里面处理当前vue对象, 最好别使用箭头函数, 因为此时的this为undefined, 通常会添加self:this 属性来获取当前vue对象, 并在函数里面使用self来获取vue对象.
- vue router插件必须在src目录下(第一层)
npm插件包: Track to this plug 插件包源码: Track to this repository 测试包: Track to this test repository
- 使用 return (/[\d]/.test(String.fromCharCode(ev.keyCode || ev.which))) || ev.which === 8 的方式限制输入字符, 在window环境上是可行的, 但是在linux系统下会失效
- vue.js 嵌套路由 chidren中的path不需要加斜杠(/)
{ path: '/user', component: () => import('具体的组件'), children: [ { path: 'list', name: 'user-index', component: () => import('具体的组件') } ] }
- 组件
<template> <el-container class="loading-container" v-show="loading"> <el-main class="loading-main" v-loading="true" element-loading-text="加载中" element-loading-spinner="el-icon-loading" element-loading-background="rgba(0, 0, 0, 0.3)"></el-main> </el-container> </template> <script> export default { data: () => ({ loading: false }), methods: { start () { // 可以理解成钩子函数, 路由更新时被调用 this.loading = true }, finish () { // 路由更新完毕(asyncData方法调用完成且页面加载完)是被调用 this.loading = false }, fail () { // 路由更新失败时调用 } } } </script>
- 配置
/* ** Customize the progress-bar color */ loading: '@/components/loading.vue', // 自定义loading组件的位置
- 从上往下查看
| 父组件 | 子组件 |
|---|---|
| beforeCreate | |
| created | |
| beforeMount | |
| beforeCreate | |
| created | |
| beforeMount | |
| mounted | |
| mounted | |
| beforeUpdate(可获取渲染之前的dom元素) | beforeUpdate |
| update(可获取最新的dom元素) | update |
| beforeDestroy(有vue实例) | |
| 子组件destroyed(无vue实例) | |
| 父组件beforeDestroy | |
| 父组件destroyed |
- 背景: 都知道在编写vue组件的时候只能使用一个v-model 完成双向数据绑定, 若想绑定多个双向数据绑定的变量呢?
- 解决方案: 使用.sync修饰符
- 父组件parent.vue
<children v-model="value" :customerAttr.sync="myValue" /> - 子组件chirldren.vue
methods: { notify (valueInner) { this.$emit("update:customerAttr", valueInner) } }
=> 当在子组件中调用了notify方法时, 则会同时更新父组件的myValue的值为子组件$emit中的valueInner
- 父组件parent.vue
npm 5.6之后是以package.lock.json的版本为主, 5.1之前package.lock.json基本上没啥用, 因为就算指定了版本但还是会install最新的
后面使用的是^符号(eg: ^2.0.0)的话, 如果依赖包更新了 那么会下载最新的依赖(前提, package.lock.json中没把版本定死),
若确定是使用某个依赖包的话, 那么使用~符号 eg: ~2.0.0 则只会下载2.0.0版本的依赖
- eg: 安装前端生成excel表的依赖 xlsx: 0.14.0
npm install --save [email protected]
npm install --save [email protected]
-
使用vue3.0版本之前的脚手架, 搭建webpack-simple模板
vue init webpack-simple export-excel-eug -
修改webpack.config.js文件, 配置别名 '@': resolve('src')
个人习惯, 习惯于使用@与根目录进行映射 -
撰写自定义组件
-
调用Vue.component api全局注册组件
-
修改webpack.config.js文件, 修改打包配置
* output配置: 1. 新增library="exportExcel" -> // 模块名, 其他类库使用require的方式引用的原因就是配置了这个 2. 新增libraryTarget="umd" -> // libraryTarget会生成不同umd的代码,可以只是commonjs标准的,也可以是指amd标准的,也可以只是通过script标签引入的 3. 新增umdNamedDefine="true" -> // 会对 UMD 的构建过程中的 AMD 模块进行命名。否则就使用匿名的 define 4. 修改filename为exportExcel.js -
修改package.json文件, 指定插件入口
"private": false, -> 需要发布, 因此需要将这个字段改为 false "main": "dist/exportExcelEug.js", -> 当在第三方使用类库, 使用 import ExportExcelEug from 'export-excel-eug'时, 会根据插件的package.json的main入口找文件library:指定的就是你使用require时的模块名 libraryTarget: 为了支持多种使用场景,我们需要选择合适的打包格式。常见的打包格式有 CMD、AMD、UMD,CMD只能在 Node 环境执行,AMD 只能在浏览器端执行,UMD 同时支持两种执行环境。显而易见,我们应该选择 UMD 格式。 有时我们想开发一个库,如lodash,underscore这些工具库,这些库既可以用commonjs和amd方式使用也可以用script方式引入。 这时候我们需要借助library和libraryTarget,我们只需要用ES6来编写代码,编译成通用的UMD就交给webpack了 umdNamedDefine:会对 UMD 的构建过程中的 AMD 模块进行命名。否则就使用匿名的 define -
由于插件的指定路口在dist打包目录下, 所以先把包打好再发布
-
遇到的坑
错误 原因 no_perms Private mode enable, only admin can publish this module 默认镜像非官方的, 需要重新设置.命令: npm config set registry http://registry.npmjs.org npm publish failed put 500 unexpected status code 401 没有登录,需要登录: npm login 即可 npm ERR! you do not have permission to publish “your module name”. Are you logged in as the correct user? 包名被占用, 需要重新命名. 命名之前最好先去npm官网查看包名是否被占用 You cannot publish over the previously published versions 每次发布时需要更新版本, 修改package.json文件的version字段即可. npm publish时经常报403 可以确认下注册的账号是否在邮箱中验证完毕 -
发布包的一句核心话就是: 将所有的插件(插件存在install方法)export出去, 并在使用组件项目入口使用Vue.use(插件)的方式
(Vue.use方法时会触发插件的install方法)生效.
-
背景: 当项目中有自己内部写好一些类库, 但是不想发布到npm公网中, 搭建私有仓库为不二选择。
-
搭建npm私有仓库的三种方式:
- cnpm搭建
- verdaccio搭建
- git
每种方式都自己的优劣势, 感兴趣可以自己研究。 -
npm + git搭建私有仓库
- 选择该方式原因
npm + git 搭建、集成速度快
- 创建私有git仓库(最好是创建一个npm私有仓库的group, 易于管理。 注:gitlab中能创建group以及私有仓库, github不能创建group, 且私有仓库需要付费)
假设在git上创建了一个group, 叫npm-pr, 私有仓库名为hello-world。 url为http://host/npm-pr/hello-world.git 或 git@host:npm-pr/hello-world.git - 初始化scope
npm init --scope=npm-pr
- push代码
上述4步算完成私有仓库的搭建
- 项目依赖类库, 从私有仓库中获取(package.json文件中配置)
// 采用ssh的方式拉取指定branch的依赖 "dependencies": { "@npm-pr/hello-world": "git+ssh://git@host:npm-pr/hello-world.git#branchName" }, // 采用https的方式拉取指定branch的依赖 "dependencies": { "@npm-pr/hello-world": "git+https://用户名:密码@host:npm-pr/hello-world.git#branchName" },
- 安装依赖
npm install @npm-pr/hello-world # 安装后的包路径为node_modules/npm-pr/hello-world 所以在对应前端框架引用插件入口出要注意文件的位置
- 选择该方式原因
- 添加meta,使IE用最新版本渲染页面
<meta http-equiv="X-UA-Compatible" content="IE=Edge">- HTML 页面, 在head标签的meta标签中设置charset='utf-8' 就能使浏览器以utf-8的标准去解码
<meta charset="UTF-8">-
首先, ES6中存在多模块的概念, 相当于java中的pacakge, 在使用它的时候需要import,
但是它不同与java的package的是, 它需要手动export出去, 即将包暴露出去 -
export default 只能暴露出一个接口. eg: xxx.js文件 export default class xxx { static a = 'attribute' static f = () => {console.log('f')} } xxx中包含许多属性和方法, 且需要static修饰.
使用时: import xxx from 'xxx文件的路径' 获取a属性: xxx.a 调用f方法: xxx.f()
-
export 通常是可以暴露多个接口, 但是在import时, 需要加上 {} 来import指定的接口 eg: xxx.js文件: const a = 'attribute' const f = () => {console.log('f')} export { a, f }
使用时: 获取a属性: import { a } from 'xxx文件路径' 获取f方法: import { f } from 'xxx文件路径' 同时获取a属性和f方法: import { a, f } from 'xxx文件路径'
Double amount = null;
amount += 123; --> Null pointer exception , 底层后调用 amount.valueOf() + 123 进而导致NullPointerException- Java不能直接使用(Long)的方式将integer类型强转成Long类型. 因为Integer和Long两个类没有继承关系。 -----> 只有存在继承关系的两个类才能使用括号的方式进行强转。
- 最好使用包装类型并使用equals方法进行判断, 拿Integer和Long类型举例,在(-128 ~ 127)的范围内,是从缓存中取数据, 当不在这个范围内时, 每次都是创建新对象. 栈内存中存放对象引用地址不一致, 使用 == 来判断相等时, 当然不一致。
- java split方法, 这种字符串切割 "a, b, c," 若单独的使用split(",")的方式
那么最后的数组长度为3而不是4, 因为这会调用split一个参数的重载方法, 可以指定长度 split(",", 4) 则指定长度为4
- 前提: 程序抛了异常, 并且try catch住了.
- 在catch中有return,finally中无return:
会先执行catch中的所有语句, 包括return后面的逻辑, 执行完之后不会立刻return, 而是去找有没有finally, 如果有则执行finally的语句, 再返回到catch中的return - catch和finally中都有return:
会先执行catch中的所有语句, 包括return后面的逻辑, 执行完之后不会立刻return, 再进finally, 此时发现 finally中有return, 那么就会走finally中的return逻辑, 从而catch中的return失效了, 即出口在finally了
- 访问时会报500, 因为系统会采用反射的机制调用无参构造器初始化当前servlet
- 使用ossClient的getObject方法获取图片的输入流
- 使用1024字节长度的方式, 读取输入流的信息, 并同时写进ByteArrayOutputStream输出流
ByteArrayOutputStream outStream = new ByteArrayOutputStream(); int len = 0; while ((len = ossObject.getObjectContent().read(buffer)) != -1) { outStream.write(buffer, 0, len); }
- 再将输出流转成byte数组 outStream.toByteArray()
- 使用jdk自带的Base64 encode编码方法, 将byte数组转成base64
- 使用字节限制每次读取的长度, 使用while循环保证能读取整个流
- 因为动态代理对象抛出的异常对象为顶级对象Throwable, 所以要获取真实对象的话, 需要调用异常的e.getCause()方法
- volatile关键字的作用大家也都明白, 大致就是提供多线程对临界区变量的可见性、一致性和有序性(JMM主要围绕这三个特性: Java内存模型)
在
实战Java并发程序基础一书中有提到, 当jvm采用server模式运行java程序时, 由于配置比较高大上(暂时先这么理解), 导致jvm有闲 情进而导致cpu有闲情对系统进行优化, 尽量让所有的临界区的变量变得可见。 - 上面的背景只是就先介绍到这里, 咱们看下面一段代码:
public class Thread9 { private static boolean ready; private static int number; public static class ReaderThread extends Thread { @Override public void run() { while(!ready); System.out.println("死循环结束, number = " + number); } } public static void main(String[] args) throws InterruptedException { new ReaderThread().start(); Thread.sleep(1000); number = 43; ready = true; System.out.println("主线程休息3秒"); Thread.sleep(3000); } } /* 针对如上的理解, 我们可以得知, 开启ReaderThread线程时, 在主线程睡眠1秒时, ReaderThread线程一直处于死循环中, 当主线程修改临界区number和ready的变量时, 由于ReaderThread线程一直处于死循环中, 压根没给jvm留出一丝的空闲 时间, 所以jvm没法对系统进行优化,尽量让所有的临界区的变量变得可见, 所以上述代码运行结果位: ===> 控制台输出 "主线程休息3秒" 并程序一直在运行中, 给人的感觉就是无任何反应 ==> 若想让线程能够执行完毕, 则只需要在ready变量添加volatile标识符进行修饰即可。 同时:还有一种能让人意想不到的方法, 在ReaderThread线程的run方法的死循环中, 添加System.out.print()输出语句, 输出任何信息都行, 你会发现我不添加volatile标识符, 线程也能正常结束!!!! 这是为什么呢??? ==> 原因就在于System.out.print()源码中有synchronized关键字, 对于jvm而言, 有synchronized关键字的是需要 获取和释放对象的锁的, 这些操作对于jvm而言是需要等待时间的, 而此时jvm发现我有闲情了, 那么它就会优化 系统代码, 尽量保证临界区的变量能够对所有线程可见, 所以线程就正常结束了。 */

-
抽象类
- 子类必须重写抽象类的
抽象方法 - 包含抽象方法的一定是抽象类
- 不能直接实例化对象
- 抽象方法只有声明,无方法体,方法权限不能为private
- 子类必须重写抽象类的
-
接口
- 可以同时实现多个接口
- 方法都是默认public修饰的,无方法体
- 接口中的变量默认都是public static final修饰的常量
- 实现接口的类一定要实现接口中的所有方法
-
共同点
- 都不能实例化对象
- 都有抽象方法
- 派生类都要重写或实现抽象方法
-
Overload(重载)
- 参数个数、类型不同
- 与返回值和函数修饰符无关
- 存在于同一个类中
-
Override(重写)
- 子类重写父类方法
- 方法名、参数名、参数个数、参数类型完全一致
- 子类方法权限不能小于父类, 方法权限 >= 父类
- 子类方法抛出的异常不能大于父类, 方法抛出异常 <= 父类
- final修饰的方法不能被重写
| 类别 | 底层实现 | 是否线程安全 | 是否可重复 | 是否有序 | 其他 |
|---|---|---|---|---|---|
| ArrayList | 线性表(数组) | 否 |
是 | 否 | 默认容量为10,每次扩容1.5倍 + 1,查询快,新增、删除慢 |
| LinkedList | 链表 | 否 |
是 | 无 | 对于新增、删除快,查询慢 |
| Vector | 线性表 | 是 |
是 | 是 | 同ArrayList |
| 类别 | 底层实现 | 是否线程安全 | 是否可重复 | 其他 |
|---|---|---|---|---|
| HashMap | --- | 否 |
否 | 可允许key或值为null,实现的是Map接口 |
| HashTable | --- | 是 |
是 | 不允许key或值为null, 实现的是Directory接口 |
| ConcurrentHashMap | -- | 是 |
是 | 与HashMap一致 |
-
根据类加载器
Class c = Class.forName("包.类名");
-
根据实例对象
String str = new String("test"); Class c = str.getClass();
-
根据类获取
Class c = String.class;
String strArr[] = new String[] {"a", "b"};
List list = Arrays.asList(strArr);
// => 此时执行list.add()方法会抛出UnsupportedOperationException异常, 因为返回的list类型为Arrays的内部类, 里面并没有重写add方法, 所以调用了父类的AbstractList add方法, 在AbstractList类中的add方法中抛出了UnsupportedOperationException异常并且执行strArr[0] = "h" list中的第一个元素也会改变 由于单线程的fail-fast机制, 当多个线程对fail-fast集合进行修改时, 可能会抛出ConcurrentModificationException
所以最好是通过迭代器 Iterator来操作, 利用迭代器的remove方法来进行删除
- 背景:
我们都知道java是跨平台的,但所谓的跨平台是指编译后的class字节码文件通过jvm能运行在不同的平台上,而jvm在 对应平台jdk的安装过程中就已经安装完成。那么运行一个普通的java程序(eg: 控制台输出Hello World)jvm在底层 做了哪些事呢? - javac命令:
javac命令的主要作用就是将.java后缀名文件编译成.class字节码文件, 在大多数IDE中, 这一步骤在run程序的时候 都帮我们完成了。 - java命令:
java命令就是将javac命令编译后的.class字节码文件运行起来。在此时, JVM将起着非常重要的作用。 首先, 一个普通无继承的类拥有四个类加载器: 1. 自身的classLoader: 2. AppClassLoader: 主要加载应用程序的类, 如自己编写的类、第三方jar包的类库。eg: maven中引入中的所有第三方jar包 3. ExtClassLoader: 能拿到它的引用,一般加载jdk安装目录下的jre/lib/ext文件下的所有jar包。 4. null(根类加载器): 在程序中拿不到它的引用,但是它实际存在,由c++编写, 根加载器一般加载比较重要的类. 比如jdk安装目录下的jre/lib/rt.jar类库(里面存放着jdk类库的字节码文件, 这就是我们能使用jdk api的原因) - 具体java应用程序class加载时间调用顺序如下图所示:
-
服务注册中心包括Eureka和zookeeper
-
选择Eureka作为服务注册中心原因如下:
- Eureka完全开源, 由Netflix公司生产环境三年的更新迭代,功能和性能上都非常稳定,且社区活跃
- 是SpringCloud首选推荐的服务注册与发现组件
- 与SpringCloud的其他组件 eg: Ribbon, Hystrix, Zuul等组件能无缝对接.
-
工作角色:
- 服务注册中心(register Service)
- 服务提供者(Provider Service)
- 服务消费者(Consumer Service)
-
选择Zuul作为路由网关的原因
- Zuul,Ribbon以及Eureka相结合,可以实现智能路由和负载均衡的功能
- 对外只暴露一个api端口, 由内部转发至具体服务
- 可以做用户身份认证和权限认证, 可以起到保护服务的作用
- 可以实时对请求进行日志输出
- 实现流量监控, 在高流量的情况下, 对服务进行降级
-
若在配置路由规则时,指定了请求的url那么zuul底层的ribbon就不会做负载均衡功能了
- 解析如下FeignClient定义的代码
@FeignClient(value="service-user", configuration = FeignConfig.class)
@RequestMapping(value = {"/users"})
public interface UserClient {
@RequestMapping(value = { "/{userId}/inner" }, method = RequestMethod.GET)
FeignMessage getByIdInner(@PathVariable(value = "userId") Long userId);
}
/*
1. @FeignClient(value="service-user", configuration = FeignConfig.class)
=> FeignClient的定义: 表明使用该client是,会找到service-user模块(在服务网eureka注册服务时指定的service.application.name配置)
并配置了FeignClient的配置(FeignConfig.class), 可以添加请求拦截器已经响应拦截器
2. @RequestMapping(value = {"/v1/users"})
=> 配置了FeignClient发送RPC请求时的requestMapping
3. @RequestMapping(value = { "/{userId}/inner" }, method = RequestMethod.GET)
FeignMessage getByIdInner(@PathVariable(value = "userId") Long userId);
=> 配置了当调用geByIdInner方法时的路径, 所以当在代码中调用
userClient.getByIdInner(1L)时. 底层会发送一个 method=GET, url=/users/1/inner 的HTTP Request,
返回值通过自定义的FeignMessage来接收(底层会做序列化和反序列化操作)
4. 最重要的一个点: 如何将userClient注入到Spring的IOC容器?
=> 在需要用到userClient的service的入口处添加
@EnableFeignClients(basePackages = {"UserClient的包路径"})
这样在服务启动时就会添加UserClient的实例至Spring IOC容器中
*/- 另外, FeignClient RPC底层使用的是HTTPClient, 在传递参数的时候必须要有个顺序, 所以会将Map转成LinkedHashMap
- SpringCloud集成swagger
- 需要添加swagger依赖(所有需要暴露swagger的服务都要添加, 所以可以统一加到root的pom.xml上)
<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.6.1</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.6.1</version> </dependency>
- 在api网关处添加swagger配置信息(添加Bean到Spring IOC容器中去)
@Configuration public class SwaggerResourcesConfiguration { @Primary @Bean public SwaggerResourcesProvider swaggerResourcesProvider() { return new SwaggerResourcesProvider() { @Override public List<SwaggerResource> get() { List resources = new ArrayList(); resources.add(createResource("service-user", "service-user", "1.0")); return resources; } }; } private SwaggerResource createResource(String name, String registeredEurekaServiceName, String version) { SwaggerResource swaggerResource = new SwaggerResource(); swaggerResource.setName(name); swaggerResource.setLocation("/" + registeredEurekaServiceName + "/v1/docs"); swaggerResource.setSwaggerVersion(version); return swaggerResource; } }
- 在ApiGateWay入口文件处添加@EnableSwagger2注解开启swagger功能
- Eureka: 服务注册与发现中心, 用来存储每个服务的对应的ip和端口
- Zuul: 路由网关, 微服务api请求统一路口, 类似于nginx方向代理, 需配置一套规则才能请求到具体的服务
- Ribbon: 负载均衡, 该组件存储在ApiGateWay中, 采用轮询算法, 依次请求服务的每一个实例
- Feigh: 服务间内部调用, 底层采用动态代理, 根据feignClient接口中mvc的一些注解, 组装http请求
- Hystrix: 熔断器, 防止因某个微服务崩溃而导致整个微服务雪崩。具体内部使用采用的是每个服务走自己的线程池
| 后台定义类型 | 前台传数据格式 |
|---|---|
| 数组类型 | value1, value2, value3 以逗号隔开 |
| List类型 | [value1, value2, value3] 以数组的形式 |
- @RequestMapping(value="test") -> 在启动项目时, 框架会自动映射成/test. 最好还是加上斜杠, 规范一些。
- 在通常的前后端分离项目中, 一般在前端框架使用的异步请求框架都会全局设置ContentType为application/json.
导致在接触第三方jar包回调时遇到不同的ContentType不知如何处理
在SpringBoot中的@RequestBody底层只有FormHttpMessageConverter支持解析application/x-www-form-urlencoded这种格式, 但它只能将请求体中的内容转成MultiValueMap对象。
- 注解中存在method属性, 是一个数组, 可以支持多种请求方式
eg: @RequestMapping( method = {RequestMethod.POST, RequestMethod.GET}, value = "/user-mapping")
- 注解中存在consumes和produces注解属性
前者指定request的ContentType, 后者指定response的ContentType, 属性都为数组, 支持多种类型
eg: @PostMapping( value = "/callback", consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"}, produces = {MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"}) //指定请求和响应的ContentType为application/x-www-form-urlencoded;charset=UTF-8 的方式
不需要@RequestParam注解, 直接添加对象类型的参数即可.- 该注解是方法级别的注解, 通常用来全局处理api请求时发生的异常 所以一般会写一把BaseController并让所有的Controller都继承它, 这样就能catch到所有Controller抛出的异常
- 通过WebApplicationContextUtil.getApplicationContext(ServletContext)的方式
WebApplicationContextUtil.getApplicationContext(request.getSession().getServletContext()) // 通常需要获取tomcat容器中的HttpServletRequest对象来获取上下文, // 虽然 HttpServletRequest 对象可以直接在方法里将该对象注入进去, 但是还是引入了比较重的对象, 不推荐该方式
- 通过ApplicationContextAware接口
1. 创建类并实现这个接口, 添加静态的ApplicationContext类型的对象。 2. 重写里面的方法setApplicationContext方法, 初始化ApplicationContext类型的对象 3. 将当前类注入到Spring IOC容器中. 这样, Ioc容器对象就是上述的 静态的ApplicationContext类型的对象. 想要具体的bean对象, 直接调用getBean方法即可. (注入到Spring IOC容器中的原因: spring启动时, 若有这样的类, 将会将上下文对象注入到 实现ApplicationContextAware接口的对象的ApplicationContext属性中去)
- 两种方式, 建议使用第二种。
- 单例模式: 所有注入ioc容器中的对象都是单例的
- Bean工厂方法
- 静态工厂方法
- 通过使用静态方法来获取实例
- 实例工厂方法:
- 通过使用非静态方法来获取实例
- 静态工厂方法
- Inject控制反转(常用)
- set方法
- 构造函数
- Aop
- Aspect(切面): 定义了切面是什么以及何时使用 Before——在方法调用之前调用通知 After——在方法完成之后调用通知,无论方法执行成功与否 After-returning——在方法执行成功之后调用通知 After-throwing——在方法抛出异常后进行通知 Around——通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为
- Join point(连接点): 业务方面的代码, 作为连接点
- Advice(引入): 引入允许我们向现有的类中添加方法或属性
- Pointcut(切点): 切点定义了“何处”需要执行code, 即表达式定义。 切点会匹配通知所要织入的一个或者多个连接点
- Annotation
- singleton: 单例模式, 默认。只存在一个实例
- propytype: 原型。每次注入属性时都是new一个新对象
- request: 针对每次http请求, 都会new一个新对象, 适用于WebApplicationContext环境
- session: 每次会话都会new一个新对象, 同一次会话共用一个实例
- global-session: 所有会话共用一个实例
- 异步注解: @Async 要使注解生效, 需要在入口处添加@EnableAsync注解
- job注解: @Scheduled
要使job定时器生效, 需要在入口处添加@EnableScheduling注解
-
在2.0.5.RELEASE版本中(其它版本没有测试)若使用key为no/yes时, load到内存中的key会发生变化 eg: 存在这样一个配置 product: no: product_01 yes: product_02
spring在将配置文件load到内存后, 使用@Value("${product.no}")的方式是会报错的: 报无这样的key 为啥呢? 因为spring会将key load成 product[false] 或 product[true]
- @Autowired: 默认按照byType的方式进行bean匹配, 是spring框架中的注解
- @Resource: 默认按照byName的方式进行bean匹配, 是jdk中自带的注解
-
默认包扫描路径为Springboot项目入口类所在包及子包,所以如果项目中会依赖一些common的jar包, 并且jar包中包含一些 springboot的注解时, 必须要保证依赖common包的路径能被springboot扫描注解时扫描到
eg:
假设一个springboot项目依赖一个jar包, 其中这个jar包会存在一个spring 上下文的工具类(一般用于因加载顺序无法 进行依赖注入属性时, 会用它来获取spring管理的bean, 通常是实现ApplicationContextAware接口)。 当在使用redis 作为mybatis的二级缓存时, 需要对实现mybatis Cache接口的类添加redisTemplate类型的对象, 并重写一些方法, 此时redisTemplate是无法依赖注入进去的。 此时就会通过获取spring上下文的工具类来获取bean, 所以此时若该工具类 存在实例化bean的相关注解时, 必须保证该类会在springboot的包扫描有效路径下。 不仅是在这种case下, 比如说在过滤器、拦截器中要注入一些属性时, 也是会注入失败的(因为过滤器或者拦截器加载的时 间是在spring上下文之前的), 此时必须要依赖spring上下文来获取bean对象。
- 若传入的参数为0, mybatis会将它当成
空字符串处理, 所以会查出name为空字符串的数据<select id="countApplicant" parameterType="int" resultType="long"> <![CDATA[ SELECT COUNT(1) FROM applicant WHERE name = #{parameter} ]]> </select>
-
$会存在sql注入的风险, #不会。 因为$ 是将数据和sql语句一起编译的 而#是先编译sql语句再将数据绑定上去, 即跟原生jdbc的问号占位符一样(?, ?, ?....) - 所以通常在模糊模糊查找时会添加bind标签将需要模糊查找的key预先编译好, 再直接用#将bind定义的变量keywordWrapper进行筛选即可,。
<bind name="keywordWrapper" value="keyword + '%'"/> <![CDATA[ SELECT * FROM user WHERE name LIKE #{keywordWrapper} ]]>
- parameterType和parameterMap
- parameterType指的是传递进去的参数类型, 基本数据类型以及pojo类型(map或类对象)
- parameterMap 一般很少用, 懒得研究
- resultMap和resultType
- resultType 返回基本数据类型
- resultMap 返回对象类型, 同时该对象需要在xml文件中配置model与db字段的映射关系
- 需要在字段中添加javaType类型, 指定具体的枚举类是什么, 否则直接使用typeHandler会抛出
Object does not represent an enum type的异常
<resultMap id="demoMap" type="map"> <result column="user_id" property="userId"/> <result column="status" property="status" javaType="枚举的具体类的class路径" typeHandler="org.apache.ibatis.type.EnumOrdinalTypeHandler"/> <result column="age" property="age"/> </resultMap>
- 必须重写setSqlSessionTemplate或者setSqlSessionFactory方法, 同时在方法中添加
@Autowire注解来开启mybatis功能(注入sqlSessionTemplate), 否则会抛初始化sqlSessionTemplate相关的异常
mysqldump -h host -u username -p database > target file
eg: 导出指定服务器(127.0.0.1)的test数据库到本地 test_local.sql 文件
mysqldump -h 127.0.0.1 -u root -p test > c:\test_local.sql
eg: 导出指定服务器(127.0.0.1)的test数据库的user表到本地 test_user.sql 文件
mysqldump -h 127.0.0.1 -u root -p test user > c:\test_user.sqlmysql --default-character-set=utf8 -h host -u username -p database > source file
eg: 将c盘根目录下test数据库的sql文件test_local.sql 导入本地test_local数据库
mysql --default-character-set=utf8 -u root -p test_local < c:\test_local.sql
或者登录到mysql(登录时需指定与sql文件同编码格式)并指定db 使用source命令
eg: mysql --default-character-set=utf8 -u root -p test_local
source c:\test_local.sql
eg: 将c盘根目录下的test_user.sql表导入本地test_local数据库user表
mysql --default-character-set=utf8 -u root -p test_local user < c:\test_user.sql
或者登录到mysql(登录时需指定与sql文件同编码格式)并指定db 使用source命令
eg: mysql --default-character-set=utf8 -u root -p test_local
source c:\test_local.sql
ps: --default-character-set=xxx 编码格式具体根据导出的db时选择的编码一致 A. 配置mysql bin目录的环境变量
B. 执行mysqld --initialize-insecure --user=mysql (若执行时报xxxx120.dll文件不存在, 则需下载vcredist_x64.exe并安装)
C. 执行mysqld -install -> 安装服务
D. 执行net start mysql -> 启动服务
E. 默认用户名是root 无密码- 语法:
注意:
DELIMITER $$ DROP PROCEDURE IF EXISTS testProc2$$ CREATE PROCEDURE testProc2() BEGIN DECLARE username INT; DECLARE done INT DEFAULT 0; -- 定义一个done变量, 用来判断cursor是否继续循环 DECLARE my_cur CURSOR FOR SELECT `name` FROM `info`; DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = true; -- 如果游标FETCH NEXT无数据了的话, 那么就会设置done为true OPEN my_cur; FETCH my_cur INTO username; WHILE(NOT done) DO INSERT INTO user_info(username) VALUES(username); FETCH my_cur INTO username; END WHILE; CLOSE my_cur; END; $$ DELIMITER ;
- 存储过程内定义的变量尽量不与需要操作的表的字段一致, 否则会将表中的字段的值填充到变量中.
- 一般使用存储过程的场景为:
- 需要频繁操作同一个sql语句. eg: 在权限表中, 每添加一个权限就需要将该权限与超级管理员关联起来. 此时可以创建一个将权限关联至超级管理员的存储过程
- 当更新表字段需要同一个表中的字段作为条件时(mysql会报错
#1093 - You can't specify target table 'xxxx' for update in FROM clause). 意思就是: 不能先select出同一表中的某些值,再update这个表(在同一语句中) 当然也可以采用嵌套子查询的方式解决, 即将select出来的结果当作一个视图, 再将视图作为条件更新表字段
DDL(DATA DEFINITAION LANGUAGE 数据定义语言): 使用范围: 对database, table有操作 eg: ALTER DROP CREATE
DML(DATA MANIPULATION LANGUAGE 数据操纵语言): 使用范围: 对数据的操作 update,create,insert,delete,存储过程, 视图等等
- 背景: 当整个团队集体开发某个需求时, 通常会选择一个人的db作为服务器db, 此时容易造成数据库连接池不够
解决方案:
-- 查看数据库最大连接数 SHOW variables LIKE '%max_connections%'; * 方案1: -- 重新设置全局最大链接数变量 SET GLOBAL max_connections=1024; 但,这种方式在重启mysql服务时就会失效 * 方案2: 修改mysql配置文件my.cnf,在[mysqld]段中添加或修改max_connections值: max_connections=1024
-
前提: 首先得找出哪些sql需要被优化
步骤: 1. 开启慢查询sql日志功能: SET GLOBAL slow_query_log = 1(可提前查看是否开启: SHOW VARIABLES LIKE '%slow_query_log%'); 2. 设置慢sql记录的阈值: SET GLOBAL long_query_time = 2;(查询超过两秒的sql都会记录到日志中去) 注意: 可能设置后执行 SHOW GLOBAL VARIABLES LIKE 'long_query_time'; 不会看到修改后的结果, 此时需重新开启一个连接才能看到 3. 设置日志存储位置: SET GLOBAL slow_query_log_file = 'c:\\mysql-slow.log';(或者使用默认的: SHOW VARIABLES LIKE '%slow_query_log_file%') 4. 查看慢查询日志 5. 统计有多少条慢sql: SHOW GLOBAL STATUS LIKE 'slow_queries'; -
使用Explain分析sql
语法: EXPLAIN SQL语句Explain的具体分析可参考: https://www.cnblogs.com/dwlovelife/p/11110215.html
1. es目录不能在 root下或root的子目录下
2. 需要添加JAVA_HOME环境变量 (/etc/profile文件)
3. 需要添加用户组, 并添加普通用户, 并给普通用户设置es文件夹的使用(可读)权限 chown -R 用户组:用户 es文件夹目录
4. 必须使用普通用户启动elasticsearch(使用'su 用户' 命令切换用户)
5. 根据官网提示, 因为elasticsearch是使用java编写的, 至少需要java1.8版本支持, 最好使用java1.8.0_131版本
6. 服务器内存小的机器, 需要修改elasticsearch的config/elasticsearch.yml文件修改对内存大小 -Xms128 -Xmx512m (其实就是启动java程序限制了堆内存最小值和最大值)
7. 修改config文件夹下的elasticsearch.yml文件 自定义配置文件中的cluster.name(建议直接去掉前面的注释), node.name(建议直接去掉前面的注释), netword.host
(建议使用0.0.0.0, 该配置会允许外网访问), http.port(建议直接去掉前面的注释)等配置
8. 配置运行时环境内存大小:
服务器运行内存比较小的 => 修改config文件夹下的jvm.options文件, 只指定最小堆内存-Xmx512m 最大堆内存设置去掉.
编辑 /etc/security/limits.conf文件, 修改(此文件修改后需要重新登录用户才会生效)
* soft nofile 65535 -> * soft nofile 65536 (限制线程同时打开文件数)
=> 注销重新登录后 输入命令验证是否生效:
ulimit -Hn, ulimit -Sn
* hard nproc 4096 -> * hard nproc 4096 (限制同时开启线程的最大数)
=> 注销重新登录后 输入命令验证是否生效:
ulimit -Hu, ulimit -Su
具体可参考 https://www.cnblogs.com/yesuuu/p/6962340.html
编辑/etc/sysctl.conf 追加以下内容: vm.max_map_count=655360 保存后运行 sysctl -p 命令检验是否添加成功
9. es搭建环境内容太小、线程数太小等常见问题: https://www.cnblogs.com/zhi-leaf/p/8484337.html
10. logstash版本要与elasticsearch版本一致 config set requirepass 1234- 一般不推荐这样做, 推荐直接修改配置文件
- 配置文件中,network中的修改 bind: 0.0.0.0
mvn install:install-file -Dfile=需要install到本地仓库的jar包路径 -DgroupId=jar包的groupId -DartifactId=jar包的artifactId -Dversion=jar包的版本 -Dpackaging=jar
eg: 手动安装```c:\common-auth-0.0.1-SNAPSHOT.jar```
mvn install:install-file -Dfile=c\common-auth-0.0.1-SNAPSHOT-core.jar -DgroupId=com.cloud -DartifactId=common-auth -Dversion=0.0.1-SNAPSHOT -Dpackaging=jar
在父(根)pom.xml文件添加如下配置:
<profiles>
<!-- 该配置文件主要作用是设置 默认使用的文件夹, 由activeByDefault标签决定, 随后也能支持在用maven命令build时指定环境信息
eg: mvn clean package -P dev 则会使用dev文件夹下的配置文件
-->
<profile>
<id>local</id>
<properties>
<conf.active>local</conf.active>
</properties>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<profile>
<id>dev</id>
<properties>
<conf.active>dev</conf.active>
</properties>
<activation>
<activeByDefault>false</activeByDefault>
</activation>
</profile>
</profiles>
<build>
<resources>
<resource>
<!-- 存放配置文件的目录, 将conf文件夹导出到内存 -->
<directory>src/main/resources</directory>
<excludes>
<exclude>conf</exclude>
</excludes>
</resource>
<resource>
<!-- 使用生效的目录 eg: src/main/resources/conf/local => 即使用local文件夹下的applicantion.yml/property 配置文件 -->
<directory>src/main/resources/conf/${conf.active}</directory>
</resource>
</resources>
</build>
---------------------------------------------------------------------
在子模块中的pom.xml文件添加如下配置:
<plugins>
<plugin>
<!-- springboot项目集成maven 插件. 注意:
如果在用maven构建多模块项目时,不要将此插件放到parent pom中,否则如果有sub module不是spring boot应用,
在打包时就会出错。只将该插件添加到是spring boot项目的子模块, ps: plugins标签在build标签里面 -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>- compile: 缺省值,适用于所有阶段,会随着项目一起发布。
- provided: 目标容器已经提供了这个artifact。所以在打包的时候该jar包不会被打进去。
- runtime,只在运行时使用,如JDBC驱动,适用运行和测试阶段。
- test,只在测试时使用,用于编译和运行测试代码。不会随项目发布。
- system,类似provided,需要显式提供包含依赖的jar,Maven不会在Repository中查找它。
<packaging>pom</packaging> -> 父类型都为pom类型
<packaging>jar</packaging> -> 内部调用或者作服务使用, 打成jar包
<packaging>war</packaging> -> 需要部署的项目- maven父项目指定了version版本号后, 在依赖的子项目中,加入parent标签, 并将父模块的groupId、artifactId、version、relativePath指定后, 若子模块没有指定版本号, 则会继承父类的版本号. 若指定了, 则使用自己的, 否则使用父类的
后台代码使用maven build代码混淆后, 最好是在spring bean 容器创建bean的时候指定名称, 并在注入时使用同样的名称,
尤其是配置类, 在为配置类创建bean时指定bean的name(eg: @Componet(value="test")), 在使用
@Resource注解时要指定name为test(eg: @Resource(name="test")),
否则可能会遇到这种情况: Bean named 'XXX' is expected to be of type 'TTT' but was actually of type 'TTT'
若使用@Autowired注入属性, 不会出现上述问题- git reset --hard HEAD
N 或 git reset --hard commitId - git reset --soft HEAD
N 或 git reset --soft commitId
区别:
1. --hard回退到指定版本, 并直接将当前版本与回退版本之间的commit给忽略掉(输入git status命令只会看到落后版本的提示)
2. --soft回退到指定版本, 会将当前版本与回退版本之间的commit给缓存到寄存区(输入git status命令可以看到所有回退的所有改动)
解释下 '~':
在git中HEAD表示的是当前版本, 而~表示的是包含指定版本(CommitId), 所以HEAD~的意思就是包含当前版本(CommitId)
但同时~后面可以接数字, 表示包含当前版本的前面N个版本, eg HEAD~2 表示包含当前版本的前面2个版本
git log --graph --pretty=oneline --abbrev-commit
在自动化部署时, 通常会将最新的commitId(前6位)作为镜像的tag, 若需要确定自己的代码有没有上环境时, 可以根据镜像的tag和commit来确认,
但git log命令呈现的东西太多, 提供给我们想要的信息(commitId)太少, 所以可以使用如下命令查看更多的commitId
git log --pretty=oneline, 当然也可以使用linux管道流关键字(grep)进行commit定位 git log --pretty=oneline | grep 112233
主要区别在于整个项目流水线:
1. 使用cherry-pick的方式将某个提交应用到当前分支和在当前分支修改代码提交代码一致.
2. 使用merge的方式在流水线会多一个分支.

背景: 合并commit有益于管理整个项目, 对于一个issue, 一个小模块应该是只包含一个commit, 这样能清晰的看到代码的改动 命令: git rebase -i commitId~ 或 git rebase -i HEAD~N 与回退版本的操作差不多, 选择需要合并的commitId即可 在rebase界面根据页面的提示操作即可 完成rebase操作后需要强制push代码, 所以此操作有风险, 一定要本地的代码是最新的.
首先我想操作的是想把某个被add进去的文件给reset回来, 结果操作成 git reset HEAD~ 了
最后执行的结果就是, 将我本地的改动和当前分支的最新的一次提交的改动给合并了. 导致分支的commit
回退到最新提交的上一个版本. 所以此时我本地的改动乱了.
为啥会出现这样的情况?
首先, HEAD关键字是分支上当前的指针, git reset HEAD 命令后面如果接的是文件, 效果是能和我的初衷一样的,
但是如果后面加的是~的话, 那么就会将某次提交的改动回退.而正巧 HEAD~ 和HEAD~1 是一样的(即
HEAD~默认会在~后面加一个数字1), 所以 git reset HEAD~n 比 git reset --soft commitId~/HEAD~n 更强.
因为前者是将commit回退 并将改动与本地的改动合并. 而后者是将commit回退, 但是将改动放入寄存区(git add后的区域)
如果要将改动与本地的改动合并的话 还要多执行一个 git reset . 操作.
- 原因: 记录是提交了, 但是author与git中的用户名和邮箱不一致
- 解决方案:
- git log 查看命令找到想要在Contribution生效的提交
- 执行git rebase -i -p commitId~ => 与合并commit类似, 找到一个范围
- 进入修改页面, 将需要修改author信息的commit前面的pick修改成edit或者e
- 保存退出(windows: Esc + : + wq + 回车)
- 重新设置author信息: git commit --amend --author="AvengerEug [email protected]"
包括用户名和邮箱 - 执行: git rebase --continue 完成当前commit信息的修改.
Notes: 若commitId和HEAD指针包含的commit较多
则需要重复执行 3-6步
- 若想支持以后的提交都能生效, 则需要全局配置git的用户名和密码
- 若想针对单个项目生效则在对应的仓库中执行如下代码:
git config user.name "xxxx"
git config user.email "[email protected]" - 具体可参考此
- 参考此项目
- 参考此项目
- 参考此项目
-
参考此项目
-
注意:
- 发布订阅模式中, 发布者和订阅者互不认识, 它们是通过中间件来交互的, eg: RabbitMq中的DirectExchange和 TopicExchange
- 观察者模式中, 发布者和订阅者是相互认识的, 他们可以直接交互, eg: RabbitMq中的FanoutExchange
-
下载镜像registry: docker pull registry => 默认在registry.hub.docker.com 中拉去镜像
-
运行镜像: docker run -itd -v /data/registry:/var/lib/registry -p 5555:5000 --restart=always -name registry registry:latest
命令解析: -itd: 在容器中打开一个伪终端进行交互操作, 并在后台运行 -v 把宿主机的 /data/registry目录绑定到容器的/var/lib/registry目录(该目录是registry容器中存放镜像文件的目录), 来实现数据持久化 -p: 映射端口, 宿主机的5555端口映射到容器的5000端口 --restart=always: 重启策略, 假如容器异常退出会自动重启 --name registry: 自定义容器名为registry registry:latest: 镜像名和tag名 -
运行registry镜像成功后, 可以在另外一台机器创建镜像并push到该仓库中
-
在另外一台机器中(最好是linux), 创建Dockerfile文件, 内容如下: FROM ubuntu AMD echo 'Hello docker!'
并在与Dockerfile文件同级目录下执行 docker build -t 47.100.26.16:5555/project/vue-docker:v1 . 来build镜像, 或者使用docker build -t 47.100.26.16:5555/project/vue-docker:v1 Dockerfile文件的路径来build镜像
-
执行完第4步的时候, 此时运行docker images命令 可以看到名称为47.100.26.16:5555/project/vue-docker tag为v1的镜像
-
此时在本地新建/etc/docker/daemon.json 文件, 若该文件中已存在, 则添加 "insecure-registries: ["远程registry1仓库的ip以及端口", "远程registry2仓库的ip以及端口", '等等, 以此类推']" 做此步骤的原因是, 若不添加 则会默认以https的请求去访问registry镜像仓库, 修改完之后需要重新加载daemon重启docker服务, 命令: sudo systemctl daemon-reload && sudo systemctl restart docker 重启之后 可以执行docker info命令查看刚刚的配置有没有生效, 若能看到配置则代表生效
-
此时可以执行push命令, 将仓库push到自己搭建的仓库了 命令: docker push 47.100.26.16:5555/project/vue-docker:v1
-
执行完第7步后, 可以选择在47.100.26.16机器上pull镜像或者在其他机器中pull镜像了. 命令: docker pull 47.100.26.16:5555/project/vue-docker:v1 注: 若是在47.100.26.16本机上pull的话, 则可以直接把ip改成127.0.0.1或者也可以重复第六步的步骤, 将47.100.26.16:5555配置进去 若是在没有将47.100.26.16:5555该配置配置到daemon.json文件的话, 需要额外配置一下才能拉取镜像
-
执行镜像 命令: docker run -d -p 8000:80 镜像名:tag 或者 docker run -d -p 8000:80 镜像Id
注意:
1. 47.100.26.16远程docker仓库的公网ip, 并要开启5555公网ip
2. 制作的镜像tag名必须为 镜像仓库的url可以访问的地址.
通常将它设置成 docker镜像仓库公网ip:公网端口/project/自定义镜像名:tag名
目的是在执行docker push/pull命令时, 能找到远程仓库镜像的地址(规则是这样)
背景: 需要php运行环境运行指定项目
- 克隆该项目到本地
- docker build -t docker-php:latest .
- docker run -it -d -p 8002:80 -v /root/php/resource:/var/www/html docker-php:latest
注意:
* clone上述指定项目时, 可以在直接使用作者提供的快速拉取和运行镜像的命令:
docker pull richarvey/nginx-php-fpm:latest & sudo docker run -d richarvey/nginx-php-fpm
这样就可以省略上述制作镜像的第二步.
* 解析下第三条命令:
-it: 在容器中打开一个伪终端进行交互操作
-d: 容器在后台运行
-v: 挂载宿主机目录 前者为宿主机挂载目录, 后者为容器中目录 eg: -v /root/php/resource:/var/www/html
至于为什么挂载到容器的/var/www/html目录, 由制作镜像时nginx的配置文件决定(公共资源目录: 可以通过
ngxin直接访问资源的路径).
正常在linux系统中, cp命令或者mv命令
eg: mv/(cp -r) ./test /root/test2 命令会将test整个文件夹放到 /root/test2 文件夹下,
但是 ADD命令是将 ./test文件夹下面的所有东西 放到/root/test2文件夹下
相当于 cp -r ./test/* /root/tes2
| 命令 | 描述 | 示例 | 注意事项 |
|---|---|---|---|
| FROM | 指定基础镜像 | FROM <镜像>:[TAG] | 无 |
| MAINTAINER | 镜像的维护者 | MAINTAINER '[email protected]' | 无 |
| COPY | 将宿主机的文件复制到镜像中去 | COPY 源文件(夹) 目标文件(夹) | 注意: 源文件路径必须与build同级,不能是其父级 |
| ADD | 将宿主机的文件添加到镜像中去 | ADD 源文件(夹) 目标文件(夹) | 注意: 源文件路径必须与build同级,不能是其父级 |
| WORKDIR | 指定工作目录, 在该命令下执行的文件操作 | WORKDIR /usr/local/src | 若在此命令后面执行了ADD或COPY命令,那么目标文件(夹)都会添加/usr/local/src前缀 |
| ENV | 指定环境变量, ENV = 或 ENV key value | ENV JAVA_HOME /usr/local/jdk 、 ENV PATH $PATH:$JAVA_HOME/bin/ | 无 |
| RUN | 后面接shell脚本的命令, 会按照shell脚本的格式解析 | RUN echo 'Hello docker' | 无 |
| CMD | 后面接shell脚本命令, 与RUN不同的是, 该CMD后面接的命令是在docker中执行的 | 无 | 无 |
- 背景: 在前后端分离的项目中, 难以避免在前端使用new Date()函数, 该函数获取的是当前电脑的时间,
加入当前电脑是UTC标准时间, 那么就会比我们正常的中国时间少8个小时。
所以, 根据这种情况, 会衍生出一种case: 就是在本地显示的时间是好的, 但是一上docker化的
dev或者qa或者生产环境的时候就会出现少了8个小时, 原因是dev、qa、生产环境是将nginx服务
器放在docker里, 会根据docker的时间区来获取时间戳的年月日, 进而出现本地环境和dev、qa、
生产环境出现不一样的情况 - 解决方案: (设置容器中的时间与当地的保持一致即可) 在制作docker的Dockerfile文件添加如下配置: ENV TZ=Asia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo '$TZ' > /etc/timezone
linux若分别以普通user启动jenkins.war, 那么会在/home/user/.jenkins/ 目录下建job目录.
若以root的身份启动的话, 则会在/root/.jenkins/ 目录下建job目录.
所以在后续需要启动jenkins时, 要注意使用root身份启动还是user身份启动, 因为他们读取的job目录是不一样的.
- 背景:
问: 什么情况下会不支持多环境同时部署?
答: 众所周知, maven项目的仓库设置的只有一个, 当在同一台搭建jenkins的电脑部署生产环境和测试环境的时,
使用的是同一个本地仓库, 生产环境一般部署是master分支, 测试环境部署测试环境的branch, 在maven项目build
的过程中, 一般都需要install jar包到本地仓库, 以便于打包的时候能找到依赖. 试问, 同时install不同branch的
jar包, 但jar包的groupId、artifactId、version都没有改变, 那肯定会出问题呀.
解决方案:
引入版本控制插件:
1. 在根项目pom文件中的build -> plugins标签中添加如下代码, 引入插件:
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>versions-maven-plugin</artifactId>
<version>2.3</version>
<configuration>
<generateBackupPoms>false</generateBackupPoms>
</configuration>
</plugin>
2. 在每个环境中部署前执行命令
mvn versions:set -DnewVersion=版本号
该命令的含义是根项目的pom文件的version设置成上面的 "版本号"
同时会将其它模块依赖另一个模块的version号都更新成上述的 "版本号"
(Notice: 子模块不要添加自己的version, 要完全依赖父模块的version), 否则pom文件会乱.
3. 每一个模块打包的版本都指定好了, 随后直接install & package就行了。
注意: 最好是每一个环境都有一个对应的backend项目, 否则在部署qa环境的时候, 运行了更新版本号的命令,
并且在install的过程中, 又需要同时部署生产环境, 那么此时又会重新设置每一个模块的版本号。
要解决这个问题则是每一个环境有一个对应的backend目录, 并对该banckend目录更新版本号进行打包
4. 打包完成后, 还要修改对应的制作镜像的Dockerfile文件, 因为要指定具体的目录拿具体打包好的jar包- 背景: 在多模块的springboot项目中, 可能会存在项目中统一以来的common类库(自己编写的), 在改common类库 会添加许多common的配置, eg: jdbc, log, mybatis mapping扫描包, mybatis别名配置等等. 所以 在模块打包时, 需要保证common的类库已经存在与maven 本地仓库了, 此时multijob的作用就体现了出来.
- multijob: multijob是一个插件, 它可以控制jenkins job的执行顺序, 串行还是并行.
- 实现步骤:
- 建立一个Multijob pipeline(外层, 主要是一个壳子, 该途径可以让job之间串行或者并行) 参数化构建.
添加参数, 一般都是branch name, 这样就能指定拉去哪个环境的代码了 - 在ADD BUILD STEP TAB下添加一些阶段(phase)性的job, 可以设置串行或者并行 串行即使用队列(sequence)的方式, 并将触发机制设置成successful 并行则使用并列(parallel)的方式, 并将触发机制设置成always 即在这可以设置一些job的执行顺序, 这样就可以解决一些项目依赖与另一个项目先启动的问题, eg: 要先启动eureka再启动其他服务, 因为其他服务要往eureka注册
- 建立一个Multijob pipeline(外层, 主要是一个壳子, 该途径可以让job之间串行或者并行) 参数化构建.
- 通过build step为downstream job phase的传递规则, downstream job 参数名必须与upstream
job定义的参数名一致, 这样在upstream job的multijob触发downstream job phase时,
downstream job才能接收到参数. - 通过post-build-action为trigger parameterized build on other projects的传递规则
通过设置parameters type为predefined paremters的方式, 以key=value的方式定义传递参数,
ps: 要获取upstream job传递的参数或者当前job自定义的参数时要使用${key}的方式
-
背景
在linux环境中启动一个程序想放入后台正常做法是使用nohup加&符号。 -
解决方案
需要使用如下命令: nohup java -jar xxx.jar > myLog.log 2>&1 & 将日志输出重定向到myLog.log文件中
- 背景:
在安装maven时, 配置环境变量配置到了~/.bashrc(~/.bash_profile)文件, 该文件的环境变量是针对于当前用户而言, 导致在jenkin build maven项目时提示命令找不到, 因为jenkins运行命令时, 他所处的用户组和用户是
- 首先这是一个定义变量的过程, ${}里面的ENV是变量名接收值, 相当于springboot的@requestParam(value = 'a', defaultValue="string") String b 中的a, 用来接收参数,而后面的local是默认值,
中间一定要加横线所以在执行脚本时设置参数值是这样表示的: ENV=test ./脚本名字那么此时脚本中的ENV变量就会变成test
- 使用双引号将整个命令包括起来, 使用单引号将awk后面的表达式括起来, 并在$x前添加转义符() eg: ssh -i xxxx.pem [email protected] "docker ps -a | grep vue-docker-v1 | awk '{print $1}'" 命令解析: 将docker信息包含vue-docker-v1的容器输出第一个参数(containerId), 这么做的原因通常是在部署时, 需要停止容器再重新打镜像.
- sed -i "s/需要修改的key/需要修改的内容/g" 目标文件 (后面加了一个g表示全局替换, 与正则中的g含义一致)
- eg: 将test111.txt文件名中的1全部替换成2
echo test111.txt | sed "s/1/2/g" | xargs mv test111.txt
- 替换文件名的命令还有rename命令, 但是该命令是批量修改文件名
eg: 将当前目录下的所有htm后缀的文件统一改成html后缀
rename "s/\.htm/\.html" *.htm
- 在server部分添加如下配置:
location /api/ {
proxy_pass http://127.0.0.1:8001/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
表示: 当遇到后缀到/api的路由时, 反向代理到 127.0.0.1:8001,
反响代理允许重新定义或者添加http请求头
语法: proxy_set_header field value;
- 在server部分添加如下配置:
location / {
try_files $uri $uri/ /index.html;
}
if (!-e $request_filename) {
rewrite ^/expo/.* /expo/index.html last;
}
----------------------------------------------
含义:
第一个配置为常见的vue.js单页面配置, 只需要添加这行配置, 并将打好的包放入ngxin访问的根目录下即可完成部署
第二个配置为配置多个vue.js单页面的配置:
解析:
条件: 若请求的资源不是一个文件
条件内语句块: 若请求uri中以expo为前缀, 则重定向到 uri为/expo/index.html
注: 1. 要保证build后的所有文件放在ngxin根目录下的expo文件夹下.
1. 静态资源要以expo文件夹为基准eg: 在vue脚手架3.0版本之前, 初始化项目并采用默认build的方式打包的index.html文件内容为
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
<meta name=viewport content="width=device-width,initial-scale=1">
<title>expo</title>
<link href=/static/css/app.2f09759cd2fab12147a7ee7a21efe173.css rel=stylesheet>
</head>
<body>
<div id=app></div>
<script type=text/javascript src=/static/js/manifest.28e76dc415eb565ab385.js></script>
<script type=text/javascript src=/static/js/vendor.3af6701d2adeecbcec08.js></script>
<script type=text/javascript src=/static/js/app.04c768f7c6f8cd515016.js></script>
</body>
</html>重点在于link标签的script标签, 默认为当前目录下的static文件夹下. 其实真正的规则为
${assetsPublicPath}/${assetsSubDirectory}/css(js)/xxxx.css(js)
而assetsPublicPath和assetsSubDirectory变量可以在config/index.js文件中配置,
所以在上述的配置中, 若请求的url为: 127.0.0.1/expo/test 那么nginx内部会重定向到 127.0.0.1/expo/index.html 即进入ngxin第二种路由: 此时index.html各种静态资源在ngxin可访问的根目录下, 此时会报404.
如果要访问expo/index.html 文件访问成功
- 首先在ngxin根目录下创建
expo文件夹 - 并修改vue.js配置文件即build后的index.html文件 如下图:
,
- 将build后的包整个丢进expo文件夹内
- 重新reload ngxin, 再次访问即可
- 拉取准备好的镜像包
git clone https://github.com/EugeneHuang9638/docker-nginx-basic-auth.git
- 执行命令
TAG=auth USERNAME=eug PASSWORD=pwd123 ./build.sh docker_build
各细节可跟踪至该repository
- eg: chmod +x build.sh
- rm -r *
- zip -r target.zip ./
- unzip target.zip -d ./ 或 unzip target.zip
- du -h 文件名
- du -ah --max-depth=1 文件夹名
- 基本: find / -name '文件名
- 通配符: find / -name '.conf'
- ps -ef | grep 80
- netstat -anp | grep 80
- top
- 指定某个线程: top | grep 进程名
- 背景: 通常要在后台执行会加上 & 符号, 但这样不稳定, 也许你关闭终端后, 通过这样的方式运行的程序就会挂掉.
- 目的: 要想脱离终端来执行程序的话应该使用 nohup命令, 三要素(nohup 运行的命令 & )
- 示例: 示例是运行一个打包好的springboot项目(后台启动项目, 并使用dev环境的配置)
nohup java -jar test.jar --spring.profiles.active=dev --server.port=8888 &
在执行 cp -r 源文件夹 目标文件夹命令时,
若目标文件夹中存在一个与源文件夹同名的文件夹的话, 那么它会将整个源文件夹copy到目标文件夹中同名文件夹的里面
eg: cp -r /root/test /root/info/test => 它会将/root/test copy到/root/info/test下,
所以/root/info/test 里面会多一个test文件夹.
ps: 它并不是将/root/test文件夹中的内容copy到/root/info/test中, 若要实现此需求, 请执行
cp -r /root/test/* /root/info/test- find . -name "*.log | xargs rm"
- 将当前目录压缩成test.tar.gz
tar -czf test.tar.gz ./*
- 解压缩test.tar.gz包 至root目录下
tar -zxvf test.tar.gz -c /root ``
- 如下图
- ContentType存在的意义: 在互联网中有成百上千种不同的数据类型, 它用来定义数据类型的格式.
- 在请求头中指定ContentType的意义: 告诉服务器使用什么格式以及编码来解析请求中的消息 eg: Content-Type: application/json;charset:utf-8;
- 常用的ContentType类型:
1. application/x-www-form-urlencoded 这种类型是将请求参数url编码后, 放到请求体中, 通常用于表单的提交. 通常会在后面添加 ;charset:utf-8 保证中文不乱码 2. application/json 使用这个类型,需要参数本身就是json格式的数据,参数会被直接放到请求实体里,不进行任何处理。服务端/客户端会按json格 式解析数据(约定好的情况下)get请求下会将key-value解析出来,放到URL的参数里面
- Ctrl + Alt + b
OSSClient ossClient = new OSSClient(endpoint, accessId, accessKey);
ObjectMetadata objectMetadata = new ObjectMetadata();
// 此段代码设置是否为私密
objectMetadata.setObjectAcl(CannedAccessControlList.Private);
ossClient.putObject(bucket, fileName, new ByteArrayInputStream(Base64.getDecoder().decode(imgBase64)), objectMetadata);- 原理: 根据需要请求的资源, 由后端对此资源进行临时授权(新增签名以及请求过期时间, 返回对该资源访问的完整url), 具体参考oss临时授权文档,
使用签名URL进行临时授权部分