fix: 修复TypeScrip类型错误和用户store属性引用

czq
2991692032 4 weeks ago
parent 937672f571
commit 9d56e0550d

@ -162,7 +162,7 @@
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { ElMessage, ElMessageBox, FormInstance } from 'element-plus';
import { ElMessage, ElMessageBox, type FormInstance } from 'element-plus';
import { resourceApi } from '@/api';
import { Upload, Document, Picture, Files, Folder, Grid, Reading, Promotion } from '@element-plus/icons-vue';
@ -345,10 +345,7 @@ const submitEdit = async () => {
if (valid) {
submitting.value = true;
try {
const res = await resourceApi.updateResource(currentEditId.value, editForm);
if (res.code === 200) {
try { const res = await resourceApi.updateResource(currentEditId.value!, editForm); if (res.code === 200) {
ElMessage.success('资源信息更新成功');
editDialogVisible.value = false;
fetchMyResources();

@ -132,7 +132,7 @@
<script setup lang="ts">
import { ref, reactive, onMounted, computed } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { ElMessage, ElMessageBox, FormInstance } from 'element-plus';
import { ElMessage, ElMessageBox, type FormInstance } from 'element-plus';
import { resourceApi } from '@/api';
import { useUserStore } from '@/stores';
import { Download, Star, More, View, Files, Document } from '@element-plus/icons-vue';
@ -163,10 +163,7 @@ const editRules = {
};
//
const isOwner = computed(() => {
if (!resource.value || !userStore.user) return false;
return resource.value.userId === userStore.user.id;
});
const isOwner = computed(() => { if (!resource.value || !userStore.userInfo) return false; return resource.value.userId === userStore.userInfo.id;});
const isImage = computed(() => {
if (!resource.value) return false;

@ -14,7 +14,7 @@
<div class="filter-row">
<div class="semester-selector">
<span class="filter-label">学期</span>
<el-select v-model="currentSemester" placeholder="选择学期">
<el-select v-model="currentSemester" placeholder="选择学期" style="min-width: 220px;">
<el-option label="2023-2024学年第一学期" value="2023-1"></el-option>
<el-option label="2023-2024学年第二学期" value="2023-2"></el-option>
</el-select>
@ -217,7 +217,7 @@
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue';
import { ElMessage, ElMessageBox, FormInstance } from 'element-plus';
import { ElMessage, ElMessageBox, type FormInstance } from 'element-plus';
import { scheduleApi } from '@/api';
import { useUserStore } from '@/stores';
import { Plus, ArrowLeft, ArrowRight } from '@element-plus/icons-vue';
@ -280,8 +280,8 @@ const courseRules = {
const courseDetailVisible = ref(false);
const selectedCourse = ref<any>(null);
const isOwner = computed(() => {
if (!selectedCourse.value || !userStore.user) return false;
return selectedCourse.value.userId === userStore.user.id;
if (!selectedCourse.value || !userStore.userInfo) return false;
return selectedCourse.value.userId === userStore.userInfo.id;
});
//

@ -1,15 +1,4 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
{ "extends": "@vue/tsconfig/tsconfig.dom.json", "compilerOptions": { "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", "baseUrl": ".", "paths": { "@/*": ["src/*"] }, /* Linting */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, "noUncheckedSideEffectImports": true },
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"],
"exclude": [
"src/**/*_test.vue",

@ -31,3 +31,14 @@ build/
### VS Code ###
.vscode/
### 敏感配置文件 ###
application-local.yml
application-dev.yml
application-prod.yml
.env
*.env
config/application-local.yml
src/main/resources/application-local.yml
src/main/resources/application-dev.yml
src/main/resources/application-prod.yml

@ -127,6 +127,13 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 阿里云OSS -->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.15.0</version>
</dependency>
</dependencies>
<build>

@ -5,8 +5,11 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.io.File;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
@ -28,6 +31,9 @@ public class WebMvcConfig implements WebMvcConfigurer {
"/posts/{id}", // 帖子详情(另一种匹配方式)
"/categories", // 分类列表
// 静态资源访问
"/api/files/**",
// Swagger文档相关
"/swagger-resources/**",
"/v3/api-docs/**",
@ -38,6 +44,18 @@ public class WebMvcConfig implements WebMvcConfigurer {
);
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 配置上传文件的访问路径
String uploadPath = new File("uploads").getAbsolutePath();
registry.addResourceHandler("/api/files/**")
.addResourceLocations("file:" + uploadPath + File.separator);
// 直接映射到uploads/resources目录
registry.addResourceHandler("/api/resources/**")
.addResourceLocations("file:" + new File("uploads/resources").getAbsolutePath() + File.separator);
}
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")

@ -12,6 +12,7 @@ import com.unilife.model.entity.Resource;
import com.unilife.model.entity.User;
import com.unilife.model.vo.ResourceVO;
import com.unilife.service.ResourceService;
import com.unilife.utils.OssService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -43,8 +44,13 @@ public class ResourceServiceImpl implements ResourceService {
@Autowired
private CategoryMapper categoryMapper;
@Autowired
private OssService ossService;
// 文件存储路径实际项目中应该配置在application.yml中
private static final String UPLOAD_DIR = "uploads/resources/";
// OSS存储目录
private static final String OSS_DIR = "resources";
@Override
@Transactional
@ -67,28 +73,15 @@ public class ResourceServiceImpl implements ResourceService {
}
try {
// 创建上传目录(如果不存在)
File uploadDir = new File(UPLOAD_DIR);
if (!uploadDir.exists()) {
uploadDir.mkdirs();
}
// 生成唯一文件名
String originalFilename = file.getOriginalFilename();
String fileExtension = originalFilename != null ? originalFilename.substring(originalFilename.lastIndexOf(".")) : "";
String newFilename = UUID.randomUUID().toString() + fileExtension;
String filePath = UPLOAD_DIR + newFilename;
// 保存文件
Path path = Paths.get(filePath);
Files.write(path, file.getBytes());
// 将文件上传到阿里云OSS
String fileUrl = ossService.uploadFile(file, OSS_DIR);
// 创建资源记录
Resource resource = new Resource();
resource.setUserId(userId);
resource.setTitle(createResourceDTO.getTitle());
resource.setDescription(createResourceDTO.getDescription());
resource.setFileUrl(filePath);
resource.setFileUrl(fileUrl); // 存储OSS文件URL
resource.setFileSize(file.getSize());
resource.setFileType(file.getContentType());
resource.setCategoryId(createResourceDTO.getCategoryId());
@ -103,7 +96,7 @@ public class ResourceServiceImpl implements ResourceService {
data.put("resourceId", resource.getId());
return Result.success(data, "资源上传成功");
} catch (IOException e) {
} catch (Exception e) {
log.error("文件上传失败", e);
return Result.error(500, "文件上传失败");
}
@ -128,7 +121,7 @@ public class ResourceServiceImpl implements ResourceService {
.id(resource.getId())
.title(resource.getTitle())
.description(resource.getDescription())
.fileUrl(resource.getFileUrl())
.fileUrl(resource.getFileUrl()) // 直接返回OSS URL
.fileSize(resource.getFileSize())
.fileType(resource.getFileType())
.userId(resource.getUserId())
@ -239,6 +232,17 @@ public class ResourceServiceImpl implements ResourceService {
return Result.error(403, "无权限删除此资源");
}
// 删除OSS中的文件
try {
String fileUrl = resource.getFileUrl();
if (fileUrl != null && fileUrl.startsWith("http")) {
ossService.deleteFile(fileUrl);
}
} catch (Exception e) {
log.error("删除OSS文件失败", e);
// 继续执行,不影响数据库记录的删除
}
// 删除资源(逻辑删除)
resourceMapper.delete(resourceId);
@ -257,15 +261,33 @@ public class ResourceServiceImpl implements ResourceService {
// 增加下载次数
resourceMapper.incrementDownloadCount(resourceId);
// 返回文件URL
// 处理文件URL生成临时访问链接
String fileUrl = resource.getFileUrl();
// 提取对象名称并生成临时访问URL有效期1小时
String objectName = ossService.getObjectNameFromUrl(fileUrl);
if (objectName != null) {
// 生成有效期为1小时的临时访问URL
fileUrl = ossService.generatePresignedUrl(objectName, 3600 * 1000);
}
// 返回文件URL和文件名
Map<String, Object> data = new HashMap<>();
data.put("fileUrl", resource.getFileUrl());
data.put("fileName", resource.getTitle());
data.put("fileUrl", fileUrl);
data.put("fileName", resource.getTitle() + getFileExtension(fileUrl));
data.put("fileType", resource.getFileType());
return Result.success(data, "获取下载链接成功");
}
// 获取文件扩展名
private String getFileExtension(String filePath) {
int lastDot = filePath.lastIndexOf(".");
if (lastDot > 0) {
return filePath.substring(lastDot);
}
return "";
}
@Override
@Transactional
public Result likeResource(Long resourceId, Long userId) {

@ -1,25 +1,23 @@
package com.unilife.utils;
import com.aliyun.oss.OSS;
import com.aliyun.oss.model.DeleteObjectRequest;
import com.aliyun.oss.model.OSSObject;
import com.aliyun.oss.model.GeneratePresignedUrlRequest;
import com.aliyun.oss.model.PutObjectRequest;
import com.unilife.config.OssConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;
@Service
@Slf4j
@Component
public class OssService {
@Autowired
@ -30,94 +28,112 @@ public class OssService {
/**
* OSS
* @param file
* @param dir
* @return 访URL
*/
public String uploadFile(MultipartFile file) throws IOException {
// 生成文件名
String originalFilename = file.getOriginalFilename();
String extension = "";
if (originalFilename != null && originalFilename.contains(".")) {
extension = originalFilename.substring(originalFilename.lastIndexOf("."));
}
public String uploadFile(MultipartFile file, String dir) {
String bucketName = ossConfig.getBucketName();
String urlPrefix = ossConfig.getUrlPrefix();
// 按日期组织文件夹
String dateFolder = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
String fileName = dateFolder + "/" + UUID.randomUUID().toString() + extension;
try (InputStream inputStream = file.getInputStream()) {
// 创建上传请求
PutObjectRequest putObjectRequest = new PutObjectRequest(
ossConfig.getBucketName(),
fileName,
inputStream
);
// 执行上传
ossClient.putObject(putObjectRequest);
// 返回文件URL
String fileUrl = ossConfig.getUrlPrefix() + fileName;
log.info("文件上传成功: {}", fileUrl);
return fileUrl;
} catch (Exception e) {
log.error("文件上传失败: {}", e.getMessage(), e);
throw new RuntimeException("文件上传失败: " + e.getMessage());
try {
// 获取文件原始名称
String originalFilename = file.getOriginalFilename();
// 获取文件后缀
String suffix = "";
if (originalFilename != null && originalFilename.contains(".")) {
suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
}
// 构建OSS存储路径目录/日期/随机UUID.后缀
String dateDir = new SimpleDateFormat("yyyy/MM/dd").format(new Date());
String filename = UUID.randomUUID().toString().replaceAll("-", "") + suffix;
String objectName = dir + "/" + dateDir + "/" + filename;
// 获取文件输入流
InputStream inputStream = file.getInputStream();
// 上传文件到OSS
ossClient.putObject(bucketName, objectName, inputStream);
// 关闭输入流
inputStream.close();
// 返回文件访问URL
return urlPrefix + objectName;
} catch (IOException e) {
log.error("上传文件到OSS失败: ", e);
throw new RuntimeException("上传文件失败");
}
}
/**
* OSS
* OSS
* @param fileUrl URL
*/
public void deleteFile(String fileUrl) {
try {
// 从URL中提取对象名称
String objectName = getObjectNameFromUrl(fileUrl);
if (objectName != null) {
DeleteObjectRequest deleteObjectRequest = new DeleteObjectRequest(
ossConfig.getBucketName(),
objectName
);
ossClient.deleteObject(deleteObjectRequest);
log.info("文件删除成功: {}", fileUrl);
}
} catch (Exception e) {
log.error("文件删除失败: {}", e.getMessage(), e);
String bucketName = ossConfig.getBucketName();
String urlPrefix = ossConfig.getUrlPrefix();
if (fileUrl.startsWith(urlPrefix)) {
String objectName = fileUrl.substring(urlPrefix.length());
ossClient.deleteObject(bucketName, objectName);
}
}
/**
* URL访
* OSS
* @param fileUrl URL
* @return
*/
public String generatePresignedUrl(String fileUrl) {
try {
String objectName = getObjectNameFromUrl(fileUrl);
if (objectName == null) {
return fileUrl;
}
public InputStream getFileStream(String fileUrl) {
String bucketName = ossConfig.getBucketName();
String urlPrefix = ossConfig.getUrlPrefix();
if (fileUrl.startsWith(urlPrefix)) {
String objectName = fileUrl.substring(urlPrefix.length());
OSSObject ossObject = ossClient.getObject(bucketName, objectName);
return ossObject.getObjectContent();
}
return null;
}
// 设置URL过期时间为1小时
Date expiration = new Date(System.currentTimeMillis() + 3600 * 1000);
/**
* 访URLbucket
* @param objectName
* @param expiration
* @return URL
*/
public String generatePresignedUrl(String objectName, long expiration) {
String bucketName = ossConfig.getBucketName();
try {
// 计算过期时间
Date expirationDate = new Date(System.currentTimeMillis() + expiration);
GeneratePresignedUrlRequest generatePresignedUrlRequest = new GeneratePresignedUrlRequest(
ossConfig.getBucketName(),
objectName
);
generatePresignedUrlRequest.setExpiration(expiration);
// 创建请求
GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, objectName);
request.setExpiration(expirationDate);
URL url = ossClient.generatePresignedUrl(generatePresignedUrlRequest);
// 生成URL
URL url = ossClient.generatePresignedUrl(request);
return url.toString();
} catch (Exception e) {
log.error("生成预签名URL失败: {}", e.getMessage(), e);
return fileUrl;
log.error("生成临时访问URL失败: ", e);
throw new RuntimeException("生成临时访问URL失败");
}
}
/**
* URL
* URL
* @param fileUrl URL
* @return
*/
public String getObjectNameFromUrl(String fileUrl) {
if (fileUrl == null || !fileUrl.startsWith(ossConfig.getUrlPrefix())) {
return null;
String urlPrefix = ossConfig.getUrlPrefix();
if (fileUrl != null && fileUrl.startsWith(urlPrefix)) {
return fileUrl.substring(urlPrefix.length());
}
return fileUrl.substring(ossConfig.getUrlPrefix().length());
return null;
}
}

@ -24,8 +24,6 @@ spring:
redis:
port: 6379
host: 127.0.0.1
profiles:
active: local
knife4j:
enable: true
openapi:
@ -61,4 +59,4 @@ aliyun:
accessKeyId: ${ALIYUN_OSS_ACCESS_KEY_ID:your-access-key-id}
accessKeySecret: ${ALIYUN_OSS_ACCESS_KEY_SECRET:your-access-key-secret}
bucketName: ${ALIYUN_OSS_BUCKET_NAME:your-bucket-name}
urlPrefix: ${ALIYUN_OSS_URL_PREFIX:https://your-bucket-name.oss-region.aliyuncs.com/}
urlPrefix: ${ALIYUN_OSS_URL_PREFIX:https://your-bucket-name.oss-region.aliyuncs.com/}spring.profiles.active=local

Loading…
Cancel
Save