阿里云oss
阿里巴巴提供的云存储服务。解决海量数据存储与弹性扩容
未达到指定容量,是不收取费用的
进入阿里云官网注册使用即可
需要实名注册,并申请OSS存储服务,最后创建一个bucket
你最好充1块钱
为什么要许可证
实际开发中,上面的Bucket仓库,不是所有开发者都可以看的
程序员需要用java调用Api访问仓库存取,这时使用许可证即可调用阿里提供的API
使用OSS我们需要的一些信息从哪里获取,地域节点是我们之后会用到的
创建子模块
1、引入依赖
<!--阿里云OSS依赖-->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.9.1</version>
</dependency>
<!--日期工具栏依赖-->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.10.6</version>
</dependency>
2、配置文件
server:
port: 8002 #微服务端口号
spring:
application:
name: service-oss #服务名
profiles:
active: dev #环境
#阿里云OSS配置,可以用官方文档的代码配置,也可以直接配置到文件中,因为这些值是不变的
aliyun:
oss:
file:
endpoint: oss-cn-XXXXX.XXXXX.com #你的地域节点,我上面提到过,这些信息全部可以从仓库控制台得到
keyid: XXXXXXXXXXXXXXXXXXXXXXX #你的证书id
keysecret: XXXXXXXXXXXXXXXXXXXXXXXXXXX #你的密钥
bucketname: XXXXXX #你的仓库名,可以在阿里官网控制台创建,或者使用java代码创建
3、创建启动类,配置其不进行数据库扫描,因为我们这个微服务不用数据库但spring boot 默认扫描url数据源,你可以为其配置数据源,或者用和我相同的办法,不扫描即可
package com.yzpnb.oss;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
//exclude表示不包含,DataSourceAutoConfiguration.class表示数据源自动配置的类,也就是不包含它,不进行数据源自动配置
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@ComponentScan(basePackages = {"com.yzpnb"})//自动扫描,扫描的包下必须有子目录
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
4、创建java类,保存配置文件中的信息(大名鼎鼎的java配置,虽然配置稍微麻烦一点,但分模块管理,解耦合,维护开发真的香)
package com.yzpnb.oss.utils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class ConstandApplicationUtils implements InitializingBean {
//接口的作用是spirng扫描到类时,执行@Value初始化完变量后,会执行afterPropertiesSet方法。
//读取配置文件内容
@Value("${aliyun.oss.file.endpoint}")
private String endpoint;
@Value("${aliyun.oss.file.keyid}")
private String keyId;
@Value("${aliyun.oss.file.keysecret}")
private String keySecret;
@Value("${aliyun.oss.file.bucketname}")
private String bucketName;
//定义一些公开静态常量,用这些常量获取值,或者只提供get方法,而不要提供set方法
public static String ENDPOINT;
public static String KEY_ID;
public static String KEY_SECRET;
public static String BUCKET_NAME;
@Override
public void afterPropertiesSet() throws Exception {
ENDPOINT=endpoint;
KEY_ID=keyId;
KEY_SECRET=keySecret;
BUCKET_NAME=bucketName;
}
}
1、编写controller 添加上传头像的接口,其中==MultipartFile==可以直接获取上传的文件对象
package com.yzpnb.oss.controller;
import com.yzpnb.common_utils.Result;
import com.yzpnb.oss.service.OssService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@RestController
@RequestMapping("/ossservice/")
@CrossOrigin//解决跨域
public class OssController {
@Autowired
private OssService ossService;
//上传头像
@PostMapping("uploadFile")
public Result uploadFile(MultipartFile file){
//1、获取上传的文件 MultipartFile 可以直接获取上传的文件
//2、返回上传到oss的路径
String url=ossService.uploadFileAvatar(file);
return Result.ok().data("url",url);
}
}
2、编写对于service接口
package com.yzpnb.oss.service;
import org.springframework.web.multipart.MultipartFile;
public interface OssService {
/**上传头像,返回上传后文件的路径*/
String uploadFileAvatar(MultipartFile file);
}
3、service实现类
package com.yzpnb.oss.service.impl;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.yzpnb.oss.service.OssService;
import com.yzpnb.oss.utils.ConstandApplicationUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
@Service
public class OssServiceImpl implements OssService {
/**上传头像,返回上传后文件的路径*/
@Override
public String uploadFileAvatar(MultipartFile file) {/**记住,MultipartFile很重要*/
/**1、获取我们工具类中的值*/
// Endpoint以杭州为例,其它Region请按实际情况填写。
String endpoint = ConstandApplicationUtils.ENDPOINT;
// 阿里云主账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM账号进行API访问或日常运维,请登录 https://ram.console.aliyun.com 创建RAM账号。
String accessKeyId = ConstandApplicationUtils.KEY_ID;
String accessKeySecret = ConstandApplicationUtils.KEY_SECRET;
String bucketName = ConstandApplicationUtils.BUCKET_NAME;
// 上传文件到OSS时需要指定包含文件后缀在内的完整路径,例如abc/efg/123.jpg。
String objectName = file.getOriginalFilename();
/**2、获取OSS的实例*/
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
/**3、获取输入流然后上传*/
//获取InputStream输入流
InputStream inputStream=null;
try {
inputStream=file.getInputStream();
} catch (IOException e) {
e.printStackTrace();
}
// 上传内容到指定的存储空间(bucketName)并保存为指定的文件名称(objectName)。
ossClient.putObject(bucketName, objectName, inputStream);
/**4、关闭OSS*/
// 关闭OSSClient。
ossClient.shutdown();
/**返回图片url,阿里云存储url生成规则为https://仓库名.地域节点/文件名*/
return "https://"+bucketName+"."+endpoint+"/"+objectName;
}
}
/**
* UUID.randomUUID用于随机生成一个uuid值,他们用-连接,比如w443-s456
* toString将其转换为字符串
* replace("-","") 将字符串中的-换为空白
* */
String uuid= UUID.randomUUID().toString().replace("-","");
// 上传文件到OSS时需要指定包含文件后缀在内的完整路径,例如abc/efg/123.jpg。
/**将uuid拼接到文件名前面,不要拼接到后面,否则后缀名就不对了*/
String objectName = uuid+file.getOriginalFilename();
/**
* UUID.randomUUID用于随机生成一个uuid值,他们用-连接,比如w443-s456
* toString将其转换为字符串
* replace("-","") 将字符串中的-换为空白
* */
String uuid= UUID.randomUUID().toString().replace("-","");
/**
* 上传文件时,会根据我们文件名创建文件夹,例如2020/5/13/123.jpg
* 那么它会根据路径创建2020文件夹然后创建5文件夹,然后创建13文件夹
* 最后将123.jpg放在13文件夹中
* 所以我们只需要将年月日拼接到路径中即可
* 我们这里不使用原生的new Data()的方式
* 我们使用前面引入过的joda-time工具类
* */
String dateTime=new DateTime().toString("yyyy/MM/dd");//自动将日期按照我指定的格式装换为字符串
// 上传文件到OSS时需要指定包含文件后缀在内的完整路径,例如abc/efg/123.jpg。
/**将uuid拼接到文件名后面*/
String objectName = dateTime+"/"+uuid+file.getOriginalFilename();
Nginx作用
请求转发
负载均衡
动静分离
请求转发:
写50个微服务,每个微服务都有自己独立的端口,怎么办,用户访问时每次都要输入不同端口么?
放一个网关不就好了,网关对外提供一个端口,然后网关帮我们访问50个微服务
那网关怎么知道我们要用哪个微服务呢?
你在设定微服务的请求路径时,每个都按照特有的名字进行设置不就好了,让网关根据你的路径转发到合适的端口中
也就是说:我们请求Nginx对外提供的指定端口,然后它帮我们将请求转发到其它端口的微服务中
负载均衡:
如果访问量非常大怎么办?
那就多台服务器一起分担访问量大的微服务啊!多台服务器统称为集群哦!
那怎么均衡的让每台服务器都处理一样的并发任务,而不是有的处理的多,而有的一个都处理不到呢?
先在有很多均衡负载的算法啦!Nginx就实现了很多均衡负载的算法哦!它会把请求轮换着分发到不同服务器哦!非常的雨露均沾
动静分离:
把java代码和静态资源,分开存放,保存在不同的服务器中
如果80端口被占用而导致启动失败,请参考博客https://blog.csdn.net/ytm15732625529/article/details/79058372 或者使用我所使用的方法(推荐)
关闭nginx
server {
listen 9001;# 配置你的nginx端口
server_name localhost;#服务名地址
location ~ /eduservice/ { #当我们请求的路径中包含/eduservice/时就将请求转发到http://localhost:8001端口
proxy_pass http://localhost:8001;
}
location ~ /ossservice/ { #当我们请求的路径中包含/ossservice/时就将请求转发到http://localhost:8002端口
proxy_pass http://localhost:8002;
}
}
<template>
<div class="app-container">
<el-form label-width="120px">
<el-form-item label="讲师名称">
<el-input v-model="teacher.name"/>
</el-form-item>
<el-form-item label="讲师排序">
<el-input-number v-model="teacher.sort" controls-position="right" min="0"/>
</el-form-item>
<el-form-item label="讲师头衔">
<el-select v-model="teacher.level" clearable placeholder="请选择">
<!--
数据类型一定要和取出的json中的一致,否则没法回填
因此,这里value使用动态绑定的值,保证其数据类型是number
-->
<el-option :value="1" label="高级讲师"/>
<el-option :value="2" label="首席讲师"/>
</el-select>
</el-form-item>
<el-form-item label="讲师资历">
<el-input v-model="teacher.career"/>
</el-form-item>
<el-form-item label="讲师简介">
<el-input v-model="teacher.intro" :rows="10" type="textarea"/>
</el-form-item>
<!-- 讲师头像 -->
<el-form-item label="讲师头像">
<!-- 头衔缩略图 -->
<pan-thumb :image="teacher.avatar"/>
<!-- 文件上传按钮 单击后imagecropperShow变为true,这控制上传弹窗开启-->
<el-button type="primary" icon="el-icon-upload" @click="imagecropperShow=true">更换头像
</el-button>
<!--
v-show:是否显示上传组件
:key:类似于id,如果一个页面多个图片上传控件,可以做区分
:url:后台上传的url地址
@close:关闭上传组件
@crop-upload-success:上传成功后的回调 -->
<image-cropper
v-show="imagecropperShow"
:width="300"
:height="300"
:key="imagecropperKey"
:url="BASE_API+'/ossservice/uploadFile'"
field="file"
@close="close"
@crop-upload-success="cropSuccess"/>
</el-form-item>
<!--保存按钮 -->
<el-form-item>
<el-button :disabled="saveBtnDisabled" type="primary" @click="saveOrUpdate">保存</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import eduTeacher from '@/api/teacher/eduTeacher.js'
import imageCropper from '@/components/ImageCropper/index.vue'
import panThumb from '@/components/PanThumb/index.vue'
export default{
data(){
return{
teacher:{
},
imagecropperShow:false,//上传弹窗组件是否显示
imagecropperKey:0,//唯一标识
BASE_API:process.env.VUE_APP_BASE_API,//将http://localhost:9001赋值给BASE_API
saveBtnDisabled:false,//保存按钮是否禁用
}
},
created() {
if(this.$route.params && this.$route.params.id){
this.selectById(this.$route.params.id);//如果路由中有参数并且参数是id值,则调用selectById方法,查询数据
}
},
methods:{
/* *
上传组件
*/
/* 关闭上传弹窗 */
close(){
this.imagecropperShow=false;
this.imagecropperKey++;
},
/* 上传成功*/
cropSuccess(data){
this.imagecropperShow=false;
this.teacher.avatar=data.url;
this.imagecropperKey++;
},
/* 根据id获取数据*/
selectById(id){
eduTeacher.selectById(id)
.then(response =>{
this.teacher=response.data.eudTeacher;//将数据存储到teacher中
})
.catch(error=>{
})
},
/* 添加方法 */
save(){
eduTeacher.save(this.teacher)
.then(response =>{
//提示信息
console.log("成功")
//回到列表显示
this.$router.push({name:'teacher'})
})
},
/* 修改方法*/
updateById(){
eduTeacher.updateById(this.teacher)
.then(response=>{
//回到列表显示
this.$router.push({name:'teacher'})
})
.catch(error=>{
})
},
/* 修改和添加,为了可以让修改和添加用同一个页面 */
saveOrUpdate(){
if(this.teacher.id){/* 如果teacher对象中有id值,说明是我们请求后的数据,执行的应该是修改 */
this.updateById();
}
else{/* 如果没有,说明我们仅仅添加数据,执行的应该是添加 */
this.save();
}
},
},
components:{
imageCropper,
panThumb,
},
}
</script>
<style>
</style>
此工具是阿里巴巴提供的开源工具,用来读取excel表格中数据,而且是一行一行读,速度快,浪费资源少
1、引入依赖
<!--easyExcel-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.2.3</version>
</dependency>
package com.yzpnb.eduservice.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import java.io.Serializable;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
*
* 课程科目
*
*
* @author testjava
* @since 2020-05-14
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value="EduSubject对象", description="课程科目")
public class EduSubject implements Serializable {
private static final long serialVersionUID=1L;
@ApiModelProperty(value = "课程类别ID")
@TableId(value = "id", type = IdType.ID_WORKER_STR)
private String id;
@ApiModelProperty(value = "类别名称")
private String title;
@ApiModelProperty(value = "父ID")
private String parentId;
@ApiModelProperty(value = "排序字段")
private Integer sort;
@ApiModelProperty(value = "创建时间")
@TableField(fill= FieldFill.INSERT)
private Date gmtCreate;
@ApiModelProperty(value = "更新时间")
@TableField(fill= FieldFill.INSERT_UPDATE)
private Date gmtModified;
}
package com.yzpnb.eduservice.entity.excel;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
@Data
public class SubjectExcel {
@ExcelProperty(index = 0)
private String oneSubjectName;
@ExcelProperty(index=1)
private String twoSubjectName;
}
package com.yzpnb.eduservice.service.impl;
import com.alibaba.excel.EasyExcel;
import com.yzpnb.eduservice.entity.EduSubject;
import com.yzpnb.eduservice.entity.excel.SubjectExcel;
import com.yzpnb.eduservice.listener.SubjectExcelListener;
import com.yzpnb.eduservice.mapper.EduSubjectMapper;
import com.yzpnb.eduservice.service.EduSubjectService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
/**
*
* 课程科目 服务实现类
*
*
* @author testjava
* @since 2020-05-14
*/
@Service
public class EduSubjectServiceImpl extends ServiceImpl<EduSubjectMapper, EduSubject> implements EduSubjectService {
/**根据excel表格添加课程分类*/
@Override
public void insertByExcel(MultipartFile file, EduSubjectService eduSubjectService) {
try{
EasyExcel.read(file.getInputStream(), SubjectExcel.class,new SubjectExcelListener(eduSubjectService)).sheet().doRead();
} catch (IOException e) {
e.printStackTrace();
}
}
}
package com.yzpnb.eduservice.listener;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.yzpnb.eduservice.entity.EduSubject;
import com.yzpnb.eduservice.entity.excel.SubjectExcel;
import com.yzpnb.eduservice.service.EduSubjectService;
import com.yzpnb.service_base_handler.CustomExceptionHandler;
import org.springframework.beans.factory.annotation.Autowired;
public class SubjectExcelListener extends AnalysisEventListener<SubjectExcel> {
/**
* 监听器不可以交给spirng管理,因为我们要通过new来使用它的实例,所以我们就不能自动注入我们需要的接口service
* 所以添加有参构造手动获取service
* 最后补充无参构造,保证监听器可以被new
* */
private EduSubjectService eduSubjectService;//service接口对象
public SubjectExcelListener() {//无参构造
}
public SubjectExcelListener(EduSubjectService eduSubjectService) {//有参构造
this.eduSubjectService = eduSubjectService;
}
@Autowired
EduSubject eduSubject;//放一个公用对象,免得一直new来new去
@Override//一行一行读
public void invoke(SubjectExcel subjectExcel, AnalysisContext analysisContext) {
if(subjectExcel == null){//如果文件没有内容,这实例为null
throw new CustomExceptionHandler(20001,"文件内容为空");
}
/**添加一级分类 我们需要它添加完的id值*/
eduSubject=this.existsOneSubject(subjectExcel.getOneSubjectName());//将方法返回值赋值给obj
if(eduSubject==null){//调用当前(this表示当前类对象)类方法,如果为null表示没有重复数据
eduSubject=new EduSubject();//一定要新建对象
eduSubject.setParentId("0");//所有一级分类的parent_id都是0
eduSubject.setTitle(subjectExcel.getOneSubjectName());//将当前从excel读取的一行内容中,去第一列的值也就是一级目录名字给title
eduSubjectService.save(eduSubject);//添加操作,完成后,信息都会自动保存到对象中
}
/**
* 走到这里说明已经成功添加了一级分类目录或者查出了数据库已经存在的对象
* 那么现在eduSubject对象,存储的就是当前一级分类对象,通过get方法即可获取我们想要的信息
* 而我们二级分类的parent_id是一级父分类的id,所有我们获取这个一级分类的id
*/
String id=eduSubject.getId();//获取id值,
/**添加二级分类*/
if(this.existsTwoSubject(subjectExcel.getTwoSubjectName(),id)==null){
eduSubject=new EduSubject();//一定要new对象,否则id等值还是一级分类的
eduSubject.setParentId(id);//将刚刚存储的一级分类的id值给parent_id
eduSubject.setTitle(subjectExcel.getTwoSubjectName());
eduSubjectService.save(eduSubject);
}
}
//判断一级分类不能重复添加
private EduSubject existsOneSubject(String name){
QueryWrapper<EduSubject> queryWrapper=new QueryWrapper<>();
//根据当前课程名称查询,如果查出来,表示数据库有重复数据
queryWrapper.eq("title",name).eq("parent_id","0");
//根据条件获取数据并返回
return eduSubjectService.getOne(queryWrapper);
}
//判断二级分类不能重复添加
private EduSubject existsTwoSubject(String name,String parentId){
QueryWrapper<EduSubject> queryWrapper=new QueryWrapper<>();
//根据当前课程名称和id(二级分类每个课程id不同)查询
queryWrapper.eq("title",name).eq("parent_id",parentId);
//根据条件获取数据并返回
return eduSubjectService.getOne(queryWrapper);
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
}
}
package com.yzpnb.eduservice.controller;
import com.yzpnb.common_utils.Result;
import com.yzpnb.eduservice.service.EduSubjectService;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
/**
*
* 课程科目 前端控制器
*
*
* @author testjava
* @since 2020-05-14
*/
@RestController
@RequestMapping("/eduservice/edu-subject")
public class EduSubjectController {
@Autowired
private EduSubjectService eduSubjectService;
/**
* 查询
*/
/**
* 添加
*/
//1、根据excel表格插入数据,获取用户上传的excel文件,读取数据
@PostMapping("insertExcel")
@ApiOperation("上传excel文件添加课程")
public Result insertByExcel(MultipartFile file){
eduSubjectService.insertByExcel(file,eduSubjectService);
return Result.ok();
}
}