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

czq
2991692032 4 weeks ago
parent 937672f571
commit 9d56e0550d

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

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

@ -14,7 +14,7 @@
<div class="filter-row"> <div class="filter-row">
<div class="semester-selector"> <div class="semester-selector">
<span class="filter-label">学期</span> <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-1"></el-option>
<el-option label="2023-2024学年第二学期" value="2023-2"></el-option> <el-option label="2023-2024学年第二学期" value="2023-2"></el-option>
</el-select> </el-select>
@ -217,7 +217,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'; 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 { scheduleApi } from '@/api';
import { useUserStore } from '@/stores'; import { useUserStore } from '@/stores';
import { Plus, ArrowLeft, ArrowRight } from '@element-plus/icons-vue'; import { Plus, ArrowLeft, ArrowRight } from '@element-plus/icons-vue';
@ -280,8 +280,8 @@ const courseRules = {
const courseDetailVisible = ref(false); const courseDetailVisible = ref(false);
const selectedCourse = ref<any>(null); const selectedCourse = ref<any>(null);
const isOwner = computed(() => { const isOwner = computed(() => {
if (!selectedCourse.value || !userStore.user) return false; if (!selectedCourse.value || !userStore.userInfo) return false;
return selectedCourse.value.userId === userStore.user.id; 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", "baseUrl": ".", "paths": { "@/*": ["src/*"] }, /* 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",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"], "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"],
"exclude": [ "exclude": [
"src/**/*_test.vue", "src/**/*_test.vue",

@ -31,3 +31,14 @@ build/
### VS Code ### ### VS Code ###
.vscode/ .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> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId> <artifactId>spring-boot-starter-actuator</artifactId>
</dependency> </dependency>
<!-- 阿里云OSS -->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.15.0</version>
</dependency>
</dependencies> </dependencies>
<build> <build>

@ -5,8 +5,11 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 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 org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.io.File;
@Configuration @Configuration
public class WebMvcConfig implements WebMvcConfigurer { public class WebMvcConfig implements WebMvcConfigurer {
@Autowired @Autowired
@ -28,6 +31,9 @@ public class WebMvcConfig implements WebMvcConfigurer {
"/posts/{id}", // 帖子详情(另一种匹配方式) "/posts/{id}", // 帖子详情(另一种匹配方式)
"/categories", // 分类列表 "/categories", // 分类列表
// 静态资源访问
"/api/files/**",
// Swagger文档相关 // Swagger文档相关
"/swagger-resources/**", "/swagger-resources/**",
"/v3/api-docs/**", "/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 @Override
public void addCorsMappings(CorsRegistry registry) { public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") registry.addMapping("/**")

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

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