erge branch 'master' of https://github.com/RichZDS/BOSS
inti
This commit is contained in:
2026-01-10 22:17:02 +08:00
4 changed files with 128 additions and 64 deletions

View File

@@ -32,25 +32,28 @@ public class WebConfig implements WebMvcConfigurer {
*/ */
@PostConstruct @PostConstruct
public void init() { public void init() {
String projectRoot = System.getProperty("user.dir");
File configuredDir = new File(fileStoragePath);
if (!configuredDir.isAbsolute()) {
configuredDir = new File(projectRoot, fileStoragePath);
}
if (configuredDir.exists()) {
staticResourcePath = configuredDir.getAbsolutePath();
return;
}
try { try {
// 尝试获取classpath下的static目录
Resource resource = resourceLoader.getResource("classpath:/static/"); Resource resource = resourceLoader.getResource("classpath:/static/");
try { File staticDir = resource.getFile();
File staticDir = resource.getFile(); staticResourcePath = staticDir.getAbsolutePath();
staticResourcePath = staticDir.getAbsolutePath(); } catch (IOException e) {
} catch (IOException e) { File targetStaticDir = new File(projectRoot, "target/classes/static");
// 如果在jar包中使用项目根目录 if (targetStaticDir.exists()) {
String projectRoot = System.getProperty("user.dir"); staticResourcePath = targetStaticDir.getAbsolutePath();
File staticDir = new File(projectRoot, "src/main/resources/static"); } else {
if (!staticDir.exists()) { staticResourcePath = new File(projectRoot, "src/main/resources/static").getAbsolutePath();
staticDir = new File(projectRoot, "target/classes/static");
}
staticResourcePath = staticDir.getAbsolutePath();
} }
} catch (Exception e) {
// 降级方案:使用项目根目录
String projectRoot = System.getProperty("user.dir");
staticResourcePath = new File(projectRoot, "src/main/resources/static").getAbsolutePath();
} }
} }
@@ -78,4 +81,3 @@ public class WebConfig implements WebMvcConfigurer {
.setCachePeriod(3600); .setCachePeriod(3600);
} }
} }

View File

@@ -189,14 +189,16 @@ public class ResumeController {
String extension = ""; String extension = "";
int lastDotIndex = originalFilename.lastIndexOf("."); int lastDotIndex = originalFilename.lastIndexOf(".");
if (lastDotIndex > 0) { if (lastDotIndex > 0) {
extension = originalFilename.substring(lastDotIndex); extension = originalFilename.substring(lastDotIndex).toLowerCase();
} }
// 验证文件类型允许PDF和Word文档 // 验证文件类型允许PDF和Word文档
String contentType = file.getContentType(); String contentType = file.getContentType();
if (contentType != null && !contentType.equals("application/pdf") boolean isAllowedContentType = "application/pdf".equals(contentType)
&& !contentType.equals("application/msword") || "application/msword".equals(contentType)
&& !contentType.equals("application/vnd.openxmlformats-officedocument.wordprocessingml.document")) { || "application/vnd.openxmlformats-officedocument.wordprocessingml.document".equals(contentType);
boolean isAllowedExtension = ".pdf".equals(extension) || ".doc".equals(extension) || ".docx".equals(extension);
if (!isAllowedContentType && !isAllowedExtension) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "仅支持PDF和Word文档格式"); throw new BusinessException(ErrorCode.PARAMS_ERROR, "仅支持PDF和Word文档格式");
} }

View File

@@ -27,6 +27,12 @@ public class FileService {
*/ */
private String basePath; private String basePath;
/**
* 文件存储路径(相对于项目根目录)
*/
@Value("${file.storage.path:src/main/resources/static}")
private String storagePath;
/** /**
* 文件访问基础URL用于构建访问地址 * 文件访问基础URL用于构建访问地址
*/ */
@@ -44,36 +50,38 @@ public class FileService {
*/ */
@PostConstruct @PostConstruct
public void init() { public void init() {
try { this.basePath = resolveBasePath();
// 获取src/main/resources/static目录的绝对路径 initStorageDirectory();
Resource resource = resourceLoader.getResource("classpath:/static/"); log.info("文件存储路径已初始化: {}", basePath);
File staticDir; }
try { private String resolveBasePath() {
// 尝试获取资源文件的实际路径 String projectRoot = System.getProperty("user.dir");
staticDir = resource.getFile(); Path configuredPath = Paths.get(storagePath);
} catch (IOException e) { Path resolvedPath = configuredPath.isAbsolute()
// 如果在jar包中运行无法直接获取File则使用项目根目录下的路径 ? configuredPath
String projectRoot = System.getProperty("user.dir"); : Paths.get(projectRoot).resolve(configuredPath);
staticDir = new File(projectRoot, "src/main/resources/static");
if (Files.exists(resolvedPath)) {
// 如果项目根目录下不存在尝试使用target/classes/static return resolvedPath.toAbsolutePath().toString();
if (!staticDir.exists()) {
staticDir = new File(projectRoot, "target/classes/static");
}
}
this.basePath = staticDir.getAbsolutePath();
// 确保目录存在
initStorageDirectory();
} catch (Exception e) {
log.error("初始化文件存储目录失败", e);
// 降级方案使用项目根目录下的static目录
String projectRoot = System.getProperty("user.dir");
this.basePath = new File(projectRoot, "src/main/resources/static").getAbsolutePath();
initStorageDirectory();
} }
try {
Resource resource = resourceLoader.getResource("classpath:/static/");
File staticDir = resource.getFile();
if (staticDir.exists()) {
return staticDir.getAbsolutePath();
}
} catch (IOException e) {
log.warn("无法从classpath解析静态目录尝试降级路径: {}", e.getMessage());
}
Path targetPath = Paths.get(projectRoot, "target/classes/static");
if (Files.exists(targetPath)) {
return targetPath.toAbsolutePath().toString();
}
return resolvedPath.toAbsolutePath().toString();
} }
/** /**
@@ -216,4 +224,3 @@ public class FileService {
return filePath.toAbsolutePath().toString(); return filePath.toAbsolutePath().toString();
} }
} }

View File

@@ -40,6 +40,12 @@
</a-upload> </a-upload>
<div v-if="uploading" style="color: #1890ff"> <div v-if="uploading" style="color: #1890ff">
<a-spin size="small" /> 上传中... <a-spin size="small" /> 上传中...
<a-progress
v-if="uploadProgress > 0"
:percent="uploadProgress"
size="small"
style="margin-top: 8px"
/>
</div> </div>
<div v-if="formState.attachmentUrl" style="margin-top: 8px"> <div v-if="formState.attachmentUrl" style="margin-top: 8px">
<a-space> <a-space>
@@ -47,7 +53,8 @@
<FileTextOutlined /> <FileTextOutlined />
查看附件 查看附件
</a> </a>
<a @click="formState.attachmentUrl = ''" style="color: #ff4d4f">删除</a> <span style="color: #999">{{ uploadFileName }}</span>
<a @click="clearAttachment" style="color: #ff4d4f">删除</a>
</a-space> </a-space>
</div> </div>
<div v-if="!formState.attachmentUrl && !uploading" style="color: #999; font-size: 12px"> <div v-if="!formState.attachmentUrl && !uploading" style="color: #999; font-size: 12px">
@@ -73,7 +80,7 @@
import NavBar from '@/components/NavBar.vue'; import NavBar from '@/components/NavBar.vue';
import { reactive, ref, onMounted, computed } from 'vue'; import { reactive, ref, onMounted, computed } from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { addResume, updateResume, getResumeVoById, uploadResumeFile } from '@/api/api/resumeController'; import { addResume, updateResume, getResumeVoById } from '@/api/api/resumeController';
import { message, Upload } from 'ant-design-vue'; import { message, Upload } from 'ant-design-vue';
import { UploadOutlined, FileTextOutlined } from '@ant-design/icons-vue'; import { UploadOutlined, FileTextOutlined } from '@ant-design/icons-vue';
@@ -83,6 +90,8 @@ const router = useRouter();
const isEdit = computed(() => !!route.params.id); const isEdit = computed(() => !!route.params.id);
const loading = ref(false); const loading = ref(false);
const uploading = ref(false); const uploading = ref(false);
const uploadProgress = ref(0);
const uploadFileName = ref('');
const formState = reactive({ const formState = reactive({
resumeTitle: '', resumeTitle: '',
@@ -106,6 +115,7 @@ const loadData = async () => {
const res = await getResumeVoById({ id }); const res = await getResumeVoById({ id });
if (res.code === 0 && res.data) { if (res.code === 0 && res.data) {
Object.assign(formState, res.data); Object.assign(formState, res.data);
uploadFileName.value = extractFileName(formState.attachmentUrl);
} else { } else {
message.error(res.message || '加载简历失败'); message.error(res.message || '加载简历失败');
} }
@@ -114,13 +124,21 @@ const loadData = async () => {
} }
}; };
const extractFileName = (url: string) => {
if (!url) return '';
const parts = url.split('/');
return parts[parts.length - 1] || '';
};
const beforeUpload = (file: File) => { const beforeUpload = (file: File) => {
const isPdf = file.type === 'application/pdf'; const isPdf = file.type === 'application/pdf';
const isWord = file.type === 'application/msword' || const isWord = file.type === 'application/msword' ||
file.type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'; file.type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
const extension = file.name.split('.').pop()?.toLowerCase();
const isAllowedExtension = extension === 'pdf' || extension === 'doc' || extension === 'docx';
const isLt10M = file.size / 1024 / 1024 < 10; const isLt10M = file.size / 1024 / 1024 < 10;
if (!isPdf && !isWord) { if (!isPdf && !isWord && !isAllowedExtension) {
message.error('仅支持PDF和Word文档格式'); message.error('仅支持PDF和Word文档格式');
return Upload.LIST_IGNORE; return Upload.LIST_IGNORE;
} }
@@ -132,27 +150,62 @@ const beforeUpload = (file: File) => {
}; };
const handleUpload = async (options: any) => { const handleUpload = async (options: any) => {
const { file } = options; const { file, onError, onSuccess } = options;
uploading.value = true; uploading.value = true;
uploadProgress.value = 0;
try { try {
const formData = new FormData(); const formData = new FormData();
formData.append('file', file); formData.append('file', file);
const res = await uploadResumeFile(formData); const baseUrl = import.meta.env.VITE_API_BASE_URL || '/api';
if (res.code === 0 && res.data) { const xhr = new XMLHttpRequest();
formState.attachmentUrl = res.data; xhr.open('POST', `${baseUrl}/resume/upload`, true);
message.success('文件上传成功'); xhr.withCredentials = true;
} else { xhr.upload.onprogress = (event) => {
message.error(res.message || '文件上传失败'); if (event.lengthComputable) {
} uploadProgress.value = Math.round((event.loaded / event.total) * 100);
}
};
xhr.onload = () => {
try {
const response = JSON.parse(xhr.responseText);
if (response.code === 0 && response.data) {
formState.attachmentUrl = response.data;
uploadFileName.value = extractFileName(response.data);
message.success('文件上传成功');
onSuccess?.(response, file);
} else {
message.error(response.message || '文件上传失败');
onError?.(response);
}
} catch (e) {
message.error('文件上传失败');
onError?.(e);
} finally {
uploadProgress.value = 0;
uploading.value = false;
}
};
xhr.onerror = () => {
message.error('文件上传失败');
onError?.(new Error('Upload failed'));
uploadProgress.value = 0;
uploading.value = false;
};
xhr.send(formData);
} catch (error: any) { } catch (error: any) {
console.error('上传失败:', error); console.error('上传失败:', error);
message.error(error.message || '文件上传失败'); message.error(error.message || '文件上传失败');
} finally { uploadProgress.value = 0;
uploading.value = false; uploading.value = false;
} }
}; };
const clearAttachment = () => {
formState.attachmentUrl = '';
uploadFileName.value = '';
};
const onFinish = async (values: any) => { const onFinish = async (values: any) => {
loading.value = true; loading.value = true;
try { try {