当前位置: 首页 > news >正文

微服务的编程测评系统15-头像上传-OSS

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 1. 头像上传
    • 1.1 引入oss
    • 1.2 集成oss到项⽬
    • 1.3 文件上传功能
    • 1.4 测试
    • 1.5 头像上传
  • 2. 用户答题功能
    • 2.1 表结构设计
  • 总结


前言

1. 头像上传

1.1 引入oss

要先创建AcessKey,头像那里点击AcessKey
搜索Oss对象存储
可以点击免费试用,过期了就买新的
在这里插入图片描述

点击试用教程

在这里插入图片描述

点击20GB3个月的,立即试用

在这里插入图片描述
点击新手秘籍

在这里插入图片描述

然后是创建Bucket

在这里插入图片描述
然后进入bucket
在这里插入图片描述

然后是点击新建目录,就可以创建我们要的目录了

在这里插入图片描述

1.2 集成oss到项⽬

在这里插入图片描述
找到下面的sdk下载

在这里插入图片描述
点击sdk示例,选择java
在这里插入图片描述

<dependency><groupId>com.aliyun.oss</groupId><artifactId>aliyun-sdk-oss</artifactId><version>3.17.4</version>
</dependency>

先创建oj-common-file

如果使用的是Java 9及以上的版本,则需要添加以下JAXB相关依赖。

<dependency><groupId>javax.xml.bind</groupId><artifactId>jaxb-api</artifactId><version>2.3.1</version>
</dependency>
<dependency><groupId>javax.activation</groupId><artifactId>activation</artifactId><version>1.1.1</version>
</dependency>
<!-- no more than 2.3.3-->
<dependency><groupId>org.glassfish.jaxb</groupId><artifactId>jaxb-runtime</artifactId><version>2.3.3</version>
</dependency>

还有

        <dependency><groupId>com.ck</groupId><artifactId>oj-common-core</artifactId><version>${oj-common-core.version}</version></dependency><dependency><groupId>com.ck</groupId><artifactId>oj-common-redis</artifactId><version>${oj-common-redis.version}</version></dependency><dependency><groupId>com.ck</groupId><artifactId>oj-common-security</artifactId><version>${oj-common-security.version}</version></dependency>

然后是配置访问凭证——》只有有这个凭证的人才可以使用,就按照官网上面的来操作就可以了–>可以后面用nacos配置

注意要在:在 RAM 控制台,创建使用永久 AccessKey 访问的 RAM 用户,保存 AccessKey,然后为该用户授予 AliyunOSSFullAccess 权限。–》生成AccessKey

然后是初始化了

在这里插入图片描述
这个就是初始化示例

endpoint就是我们创建bucket的地域
我们是成都
在这里插入图片描述
就是oss-cn-chengdu.aliyuncs.com

region就是cn-chengdu

这个最好也写在nacos上
credentialsProvider这里是配置访问凭证
我们用的不是环境变量,是nacos配置的访问凭证

    // 创建 OSS 客户端实例OSS ossClient = OSSClientBuilder.create()

这个是最重要的

但是这个应该是交给spring管理的,我们就不用最后还关闭了

file下创建config包
OSSProperties


@Data
@Component
@ConfigurationProperties(prefix = "file.oss")
public class OSSProperties {private String endpoint;//地域private String region;//地域private String accessKeyId;private String accessKeySecret;private String bucketName;//bucket名字/*** 路径前缀,加在 endPoint 之后,就是目录*/private String pathPrefix;  //ojtest,bucket的目录名
}

@ConfigurationProperties(prefix = “file.oss”)的意思就是把nacos上的配置读取到这个类


@Slf4j
@Configuration
@EnableConfigurationProperties(OSSProperties.class)//Component注解的bean不好找,只能这样了
public class OSSConfig {@Autowiredprivate OSSProperties prop;public OSS ossClient;@Beanpublic OSS ossClient() throws ClientException {DefaultCredentialProvider credentialsProvider = CredentialsProviderFactory.newDefaultCredentialProvider(prop.getAccessKeyId(), prop.getAccessKeySecret());// 创建ClientBuilderConfigurationClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();clientBuilderConfiguration.setSignatureVersion(SignVersion.V4);// 使用内网endpoint进行上传ossClient = OSSClientBuilder.create().endpoint(prop.getEndpoint()).credentialsProvider(credentialsProvider).clientConfiguration(clientBuilderConfiguration).region(prop.getRegion()).build();return ossClient;}@PreDestroypublic void closeOSSClient() {ossClient.shutdown();}
}

这样就可以了

加上 @PreDestroy,那么在销毁这个对象的时候,就会自动执行这个方法了

nacos配置

file:max-time: 3test: trueoss:endpoint: oss-cn-chengdu.aliyuncs.comregion: cn-chengduaccessKeyId: 你的accessKeyIdaccessKeySecret: 你的accessKeySecretbucketName: 你的bucketNamepathPrefix: ojtest/

1.3 文件上传功能

在这里插入图片描述

在这里插入图片描述
我们使用的主要是这个

我们可以分装成一个service
来调用

@Getter
@Setter
public class OSSResult {private String name;/*** 对象状态:true成功,false失败*/private boolean success;
}

@Slf4j
@Service
@RefreshScope
public class OSSService {@Autowiredprivate OSSProperties prop;@Autowiredprivate OSSClient ossClient;@Autowiredprivate RedisService redisService;@Value("${file.max-time}")private int maxTime;@Value("${file.test}")private boolean test;public OSSResult uploadFile(MultipartFile file) throws Exception {if (!test) {checkUploadCount();}InputStream inputStream = null;try {String fileName;if (file.getOriginalFilename() != null) {fileName = file.getOriginalFilename().toLowerCase();} else {fileName = "a.png";}String extName = fileName.substring(fileName.lastIndexOf(".") + 1);inputStream = file.getInputStream();return upload(extName, inputStream);} catch (Exception e) {log.error("OSS upload file error", e);throw new ServiceException(ResultCode.FAILED_FILE_UPLOAD);} finally {if (inputStream != null) {inputStream.close();}}}private void checkUploadCount() {Long userId = ThreadLocalUtil.get(Constants.USER_ID, Long.class);Long times = redisService.getCacheMapValue(CacheConstants.USER_UPLOAD_TIMES_KEY, String.valueOf(userId), Long.class);if (times != null && times >= maxTime) {throw new ServiceException(ResultCode.FAILED_FILE_UPLOAD_TIME_LIMIT);}redisService.incrementHashValue(CacheConstants.USER_UPLOAD_TIMES_KEY, String.valueOf(userId), 1);if (times == null || times == 0) {long seconds = ChronoUnit.SECONDS.between(LocalDateTime.now(),LocalDateTime.now().plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0));redisService.expire(CacheConstants.USER_UPLOAD_TIMES_KEY, seconds, TimeUnit.SECONDS);}}private OSSResult upload(String fileType, InputStream inputStream) {// key pattern: file/id.xxx, cannot start with /String key = prop.getPathPrefix() + ObjectId.next() + "." + fileType;ObjectMetadata objectMetadata = new ObjectMetadata();objectMetadata.setObjectAcl(CannedAccessControlList.PublicRead);PutObjectRequest request = new PutObjectRequest(prop.getBucketName(), key, inputStream, objectMetadata);PutObjectResult putObjectResult;try {putObjectResult = ossClient.putObject(request);} catch (Exception e) {log.error("OSS put object error: {}", ExceptionUtil.stacktraceToOneLineString(e, 500));throw new ServiceException(ResultCode.FAILED_FILE_UPLOAD);}return assembleOSSResult(key, putObjectResult);}private OSSResult assembleOSSResult(String key, PutObjectResult putObjectResult) {OSSResult ossResult = new OSSResult();if (putObjectResult == null || StrUtil.isBlank(putObjectResult.getRequestId())) {ossResult.setSuccess(false);} else {ossResult.setSuccess(true);ossResult.setName(FileUtil.getName(key));}return ossResult;}
}

uploadFile就是上传文件
checkUploadCount是检查上传次数,文件上传次数是有限制的,防止一直上传,钱不够用了

    public Long incrementHashValue(final String key, final String hKey, long delta) {return redisTemplate.opsForHash().increment(key, hKey, delta);}
    public static final String USER_UPLOAD_TIMES_KEY = "user:upload:times";

我们这里用的是incrementHashValue来对上传次数进行计数,用的是hash的办法,hash一共有两个key,一个是内存keyUSER_UPLOAD_TIMES_KEY ,一个是外存key:userId
其实和以前String类型的计数是差不多的
delta表示增加的数是多少,就表示加多少
uploadFile中test是一个开关,表示要不要无限上传图片
MultipartFile是接收前端上传的文件类
它的getInputStream方法就可以获取到InputStream了
如果OSS文件存在,则上传的数据会覆盖该文件的内容;如果OSS文件不存在,则会新建该文件。
我们上传文件到oss,但是如果文件名重复了,那就不好了,以前人的头像就没了
所以我们第一要拿到原本文件名的后缀,然后给它新建一个文件名,保证不重复

upload就是上传文件方法了

upload中prop.getPathPrefix()就是oj-test的目录

ObjectId.next()就是生成一个不一样的文件名,也是hutool的,生成的是字符串

assembleOSSResult就是来处理一下发送是否成功,然后对应应该返回的OssResult应该是什么

1.4 测试

我们这里上传文件分为两个接口
一个是上传文件到oss
一个是把返回的唯一标识文件名存到数据库中
如果两个在一起操作,就会速度很慢,防止一个接口5s搞不赢就超时了

OSSResult中的name就是文件名就是唯一标识

@RestController
@RequestMapping("/file")
@Tag(name = "文件上传接口")
@Slf4j
public class FileController extends BaseController {@Autowiredprivate IFileService fileService;@PostMapping("/upload")public R<OSSResult> upload(@RequestBody MultipartFile file) {return R.ok(fileService.upload(file));}
}
@Slf4j
@Configuration
public class OSSConfig {

注意这里应该删掉@EnableConfigurationProperties(OSSProperties.class)
应该这个注解就是一个注册,而@component也是一个注册,这样就会双重注册,所以就不行

在这里插入图片描述

注意了这里测试的接口应该是form-data,然后是file类型
记得上传文件

在OSSService中
这里改一下

    @Autowiredprivate OSS ossClient;

注意我们还要开启oss的读取权限
在这里插入图片描述
在这里插入图片描述
这样就可以了

在这里插入图片描述
测试发现也是存储成功了

1.5 头像上传

存储成功了,我们还要查询头像才行
而且图片文件名还要存在mysql,这样就可以一一对应了,和用户
而且我们说过的,上传oss和存数据库应该分开的,因为上传oss很耗时间

先写后端

    @PutMapping("/head-image/update")public R<Void> updateHeadImage(@RequestBody UserUpdateDTO userUpdateDTO) {return toR(userService.updateHeadImage(userUpdateDTO));}
    @Overridepublic int updateHeadImage(UserUpdateDTO userUpdateDTO) {Long userId = ThreadLocalUtil.get(Constants.USER_ID, Long.class);if (userId == null) {throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);}User user = userMapper.selectById(userId);if (user == null) {throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);}user.setHeadImage(userUpdateDTO.getHeadImage());//更新用户缓存userCacheManager.refreshUser(user);tokenService.refreshLoginUser(user.getNickName(),user.getHeadImage(),ThreadLocalUtil.get(Constants.USER_KEY, String.class));return userMapper.updateById(user);}

这样就成功了

前端上传头像我们用的是el-upload
在这里插入图片描述
就用第一个就可以了

                <div v-if="!isDisabled()"><el-upload :action="uploadUrl" :headers="headers":on-error="handleUploadError" :on-success="handleUploadSuccess" :show-file-list="false"><el-button type="info">{{ userDetailForm.headImage ? '更换头像' : '上传头像' }}</el-button><template #tip> </template></el-upload></div>

在这里插入图片描述

所以action就是配置上传文件的接口,

//http://127.0.0.1:19090/friend/file/upload
const uploadUrl = ref("/dev-api/file/upload")

所以这个意思就是上传本地文件之后,自动调用/file/upload
在这里插入图片描述
注意这个action发送的请求不是axios发送的请求,所以我们以前弄的axios的请求拦截器配置的请求头就不会对这个upload生效了
所以我们要用:headers="headers"来配置请求头

const headers = ref({Authorization: "Bearer " + getToken(),
})

所以说呢,这个action的请求和响应都必须我们自己来处理,不能用原来的拦截器
上传头像之后,还要存入唯一标识
在这里插入图片描述
这两个就可以处理上传成功和失败的情况了
:on-error=“handleUploadError” :on-success=“handleUploadSuccess”

handleUploadSuccess方法的res就是响应结果,就是R,不用再次data才能得到R

async function handleUploadSuccess(res) {if (res.code !== 1000) {ElMessage.error(res.msg)} else {const userUpdateDTO = reactive({headImage : res.data.name})await updateHeadImageService(userUpdateDTO)getUserDetail()ElMessage.success("头像上传成功")window.location.reload();}
}

window.location.reload();是刷新页面的意思,这里刷新一下的原因是右上角的头像没有更新

但是存入了oss,怎么获取oss中的图片呢
怎么加载到前端呢—》一个下载资源的地址
在这里插入图片描述
下载的url就在这里,发现前缀都是固定的

所以我们在后端获取detail和info的时候就可以加上前缀url,这样就可以给前端加载了

        if(StrUtil.isNotEmpty(loginUser.getHeadImage())){loginUserVO.setHeadImage(downLoadUrl+loginUser.getHeadImage());}
        if(StrUtil.isNotEmpty(userVO.getHeadImage())){userVO.setHeadImage(downLoadUrl+userVO.getHeadImage());}

其中downLoadUrl用nacos配置

export function updateHeadImageService(params = {}) {return service({url: "/user/head-image/update",method: "put",data: params,});
}

这样就成功了

2. 用户答题功能

2.1 表结构设计

在这里插入图片描述
左边是标题和内容
上边是执行代码
在这里插入图片描述
在这里插入图片描述
答题和判题是不一样的功能

答题在题库和竞赛都有
竞赛答题那里,应该要对用户总分和排名进行判断(竞赛结束)----》所以要对答题的代码,分数进行存储
我们要对答题记录进行保存—》也要保存代码,分数–》存最近一次记录

用户提交表

create table tb_user_submit(
submit_id bigint unsigned NOT NULL COMMENT '提交记录id',
user_id   bigint unsigned NOT NULL COMMENT '用户id',
question_id  bigint unsigned NOT NULL COMMENT '题目id',
exam_id bigint unsigned  COMMENT '竞赛id',
program_type tinyint NOT NULL COMMENT '代码类型 0 java   1 CPP',
user_code   text NOT NULL COMMENT '用户代码',
pass tinyint NOT NULL COMMENT '0:未通过  1:通过',
exe_message  varchar(500) NOT NULL COMMENT '执行结果',
score int NOT NULL DEFAULT '0' COMMENT '得分',
create_by    bigint unsigned not null  comment '创建人',
create_time  datetime not null comment '创建时间',
update_by    bigint unsigned  comment '更新人',
update_time  datetime comment '更新时间',
primary key(`submit_id`)
)

比赛的时候有examId,其他没有

总结

http://www.xdnf.cn/news/1353403.html

相关文章:

  • 高阶数据结构---ST表
  • kafaka知识要点
  • VLOOKUP专题训练
  • UE C++ 堆化
  • windows中bat脚本的一些操作(三)
  • 算法第五十五天:图论part05(第十一章)
  • 图论与最短路学习笔记
  • 【数据结构】跳表的概率模型详解与其 C 代码实现
  • 深度学习开篇
  • `strlen` 字符串长度函数
  • python 字典有序性的实现和OrderedDict
  • 计算机网络 各版本TLS握手的详细过程
  • 电脑零广告快响应提速(一)之卸载搜狗输入法使用RIME—东方仙盟
  • python re模块常用方法
  • MySQL详细介绍指南
  • 蓝牙aoa仓库管理系统功能介绍
  • [e3nn] 归一化 | BatchNorm normalize2mom
  • 【技术突破】动态目标误检率↓83.5%!陌讯多模态融合算法在智慧城管的实战优化
  • 基于电力电子变压器的高压脉冲电源方案复现
  • 使用 Certbot 申请 Apache 证书配置棘手问题
  • 【数据结构】计数排序:有时比快排还快的整数排序法
  • Ubuntu 操作系统深度解析:从入门到精通(2025 最新版)
  • Java JVM 超级详细指南
  • 在Linux环境中为Jupyter Lab安装Node.js环境
  • 云计算之云主机Linux是什么?有何配置?如何选?
  • JavaSpring+mybatis+Lombok,实现java架构[保姆教程]
  • Linux PCI 子系统:工作原理与实现机制深度分析
  • Bartender 5 Mac 多功能菜单栏管理
  • 【LeetCode】85. 最大矩形 (暴力枚举)
  • 嵌入式软件/硬件工程师面试题集