diff --git a/home-service/pom.xml b/home-service/pom.xml index 6bc88e1..808bfdd 100644 --- a/home-service/pom.xml +++ b/home-service/pom.xml @@ -38,6 +38,13 @@ cn.hutool hutool-crypto + + + + ws.schild + jave-all-deps + 3.5.0 + diff --git a/home-service/src/main/java/com/cxx/home/config/AListProperties.java b/home-service/src/main/java/com/cxx/home/config/AListProperties.java new file mode 100644 index 0000000..8bdad33 --- /dev/null +++ b/home-service/src/main/java/com/cxx/home/config/AListProperties.java @@ -0,0 +1,15 @@ +package com.cxx.home.config; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@ConfigurationProperties(prefix = "alist") +@Getter +@Setter +public class AListProperties { + private String username; + private String password; +} diff --git a/home-service/src/main/java/com/cxx/home/config/RestClientConfig.java b/home-service/src/main/java/com/cxx/home/config/RestClientConfig.java new file mode 100644 index 0000000..7ebb4ec --- /dev/null +++ b/home-service/src/main/java/com/cxx/home/config/RestClientConfig.java @@ -0,0 +1,25 @@ +package com.cxx.home.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.web.client.RestClient; + +@Configuration +public class RestClientConfig { + final String baseUrl = "http://192.168.1.7:5244/"; + + @Bean + public RestClient aListClient() { + + return RestClient.builder() + // 基础RURL + .baseUrl(baseUrl) + // 默认请求头 请求体为JSON格式 + .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + // 默认请求头 响应体为JSON格式 + .defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE) + .build(); + } +} diff --git a/home-service/src/main/java/com/cxx/home/controller/AuthController.java b/home-service/src/main/java/com/cxx/home/controller/AuthController.java new file mode 100644 index 0000000..08d95da --- /dev/null +++ b/home-service/src/main/java/com/cxx/home/controller/AuthController.java @@ -0,0 +1,28 @@ +package com.cxx.home.controller; + +import cn.dev33.satoken.stp.StpUtil; +import com.cxx.common.ReadView; +import com.cxx.common.WriteView; +import com.cxx.common.dto.StatsChartDto; +import com.cxx.home.dto.anniversary.AnniversaryDto; +import com.cxx.home.service.AnniversaryService; +import com.fasterxml.jackson.annotation.JsonView; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/auth") +@Tag(name = "认证") +public class AuthController { + @Operation(summary = "查询在线人数") + @GetMapping("/online") + public List queryOnline() { + return StpUtil.searchSessionId("", 0, -1, true); + } +} + \ No newline at end of file diff --git a/home-service/src/main/java/com/cxx/home/controller/FileController.java b/home-service/src/main/java/com/cxx/home/controller/FileController.java index 8717e38..5a84566 100644 --- a/home-service/src/main/java/com/cxx/home/controller/FileController.java +++ b/home-service/src/main/java/com/cxx/home/controller/FileController.java @@ -4,6 +4,7 @@ import com.cxx.home.dto.file.ChunkInfoDto; import com.cxx.home.dto.file.ChunkResultDto; import com.cxx.home.dto.file.FileInfoDto; import com.cxx.home.dto.file.FileRecordDto; +import com.cxx.home.service.AListService; import com.cxx.home.service.FileService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -20,6 +21,9 @@ public class FileController { @Resource private FileService fileService; + @Resource + private AListService aListService; + @Operation(summary = "上传文件") @PostMapping(value = "", headers = "content-type=multipart/form-data") public FileRecordDto uploadFile(@RequestPart("file") MultipartFile file, @@ -28,6 +32,13 @@ public class FileController { return fileService.uploadFile(file, path, md5); } + @Operation(summary = "上传aList文件") + @PostMapping(value = "/alist", consumes = "multipart/form-data") + public void uploadAListFile(@RequestParam("file") MultipartFile file, + @RequestParam("filePath") String filePath) { + aListService.uploadFile(file, filePath); + } + @Operation(summary = "上传文件块") @PostMapping("/chunk") public Boolean uploadChunk(ChunkInfoDto chunkInfo, diff --git a/home-service/src/main/java/com/cxx/home/dto/alist/AListLogin.java b/home-service/src/main/java/com/cxx/home/dto/alist/AListLogin.java new file mode 100644 index 0000000..371a280 --- /dev/null +++ b/home-service/src/main/java/com/cxx/home/dto/alist/AListLogin.java @@ -0,0 +1,11 @@ +package com.cxx.home.dto.alist; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class AListLogin { + private String username; + private String password; +} diff --git a/home-service/src/main/java/com/cxx/home/service/AListService.java b/home-service/src/main/java/com/cxx/home/service/AListService.java new file mode 100644 index 0000000..465ca28 --- /dev/null +++ b/home-service/src/main/java/com/cxx/home/service/AListService.java @@ -0,0 +1,7 @@ +package com.cxx.home.service; + +import org.springframework.web.multipart.MultipartFile; + +public interface AListService { + void uploadFile(MultipartFile file, String filePath); +} diff --git a/home-service/src/main/java/com/cxx/home/service/impl/AListServiceImpl.java b/home-service/src/main/java/com/cxx/home/service/impl/AListServiceImpl.java new file mode 100644 index 0000000..80a90e2 --- /dev/null +++ b/home-service/src/main/java/com/cxx/home/service/impl/AListServiceImpl.java @@ -0,0 +1,99 @@ +package com.cxx.home.service.impl; + +import com.cxx.framework.web.CustomException; +import com.cxx.home.config.AListProperties; +import com.cxx.home.dto.alist.AListLogin; +import com.cxx.home.service.AListService; +import com.cxx.home.util.VideoCompressor; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.FileSystemResource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Service; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestClient; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; + +@Service +@Slf4j +public class AListServiceImpl implements AListService { + + @Resource + private RestClient aListClient; + + @Resource + private AListProperties aListProperties; + + private String getAccessToken() { + AListLogin aListLogin = new AListLogin(); + aListLogin.setUsername(aListProperties.getUsername()); + aListLogin.setPassword(aListProperties.getPassword()); + + Map response = aListClient.post() + .uri("api/auth/login") + .body(aListLogin) + .retrieve() + .body(Map.class); + + if (response == null) { + throw new CustomException("AList登录失败 请联系管理员"); + } + + HashMap data = (HashMap) response.get("data"); + return (String) data.get("token"); + } + + @Override + public void uploadFile(MultipartFile file, String filePath) { + try { + String token = getAccessToken(); + + MultiValueMap body = new LinkedMultiValueMap<>(); + + File tempFile = convertMultipartFileToFile(file); + log.info("临时文件地址:{}", tempFile.toPath()); + + log.info("开始压缩文件:{}", tempFile.getName()); + File compressFile = VideoCompressor.compressTo720p(tempFile); + log.info("完成压缩文件:{}", tempFile.getName()); + + log.info("压缩文件地址:{}", tempFile.toPath()); + + body.add("file", new FileSystemResource(compressFile)); + + aListClient.put() + .uri("api/fs/form") + .header(HttpHeaders.AUTHORIZATION, token) + .header("File-Path", filePath) + .contentType(MediaType.MULTIPART_FORM_DATA) + .body(body) + .retrieve() + .body(Map.class); + + Files.deleteIfExists(compressFile.toPath()); + Files.deleteIfExists(tempFile.toPath()); + } catch (Exception e) { + log.error("文件上传失败{}", e.getMessage()); + throw new RuntimeException("文件上传失败", e); + } + } + + private File convertMultipartFileToFile(MultipartFile file) throws IOException { + String suffix = (file.getOriginalFilename() != null && file.getOriginalFilename().contains(".")) + ? file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".")) + : ".tmp"; + + Path tempFile = Files.createTempFile(System.currentTimeMillis() + "", suffix); + file.transferTo(tempFile); + return tempFile.toFile(); + } +} diff --git a/home-service/src/main/java/com/cxx/home/service/impl/FoodServiceImpl.java b/home-service/src/main/java/com/cxx/home/service/impl/FoodServiceImpl.java index 2cbd97e..5315dd5 100644 --- a/home-service/src/main/java/com/cxx/home/service/impl/FoodServiceImpl.java +++ b/home-service/src/main/java/com/cxx/home/service/impl/FoodServiceImpl.java @@ -1,5 +1,6 @@ package com.cxx.home.service.impl; +import cn.dev33.satoken.stp.StpUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.Wrappers; @@ -90,12 +91,21 @@ public class FoodServiceImpl implements FoodService { public Boolean addFoodRecord(FoodRecordDto foodRecordDto) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); FoodRecipe foodRecipe = foodRecipeMapper.selectOne(queryWrapper.eq(FoodRecipe::getName, foodRecordDto.getName())); + long recipeId; if (foodRecipe == null) { - throw new CustomException("该菜谱不存在"); + FoodRecipe newRecipe = new FoodRecipe(); + newRecipe.setName(foodRecordDto.getName()); + newRecipe.setCategory("其他"); + newRecipe.setRecommendRate(0); + newRecipe.setRemark(""); + foodRecipeMapper.insert(newRecipe); + recipeId = newRecipe.getId(); + } else { + recipeId = foodRecipe.getId(); } FoodRecord foodRecord = new FoodRecord(); - foodRecord.setFoodId(foodRecipe.getId()); + foodRecord.setFoodId(recipeId); BeanUtils.copyProperties(foodRecordDto, foodRecord); foodRecordMapper.insert(foodRecord); diff --git a/home-service/src/main/java/com/cxx/home/util/VideoCompressor.java b/home-service/src/main/java/com/cxx/home/util/VideoCompressor.java new file mode 100644 index 0000000..2881cd7 --- /dev/null +++ b/home-service/src/main/java/com/cxx/home/util/VideoCompressor.java @@ -0,0 +1,153 @@ +package com.cxx.home.util; + +import lombok.extern.slf4j.Slf4j; +import ws.schild.jave.Encoder; +import ws.schild.jave.MultimediaObject; +import ws.schild.jave.encode.AudioAttributes; +import ws.schild.jave.encode.EncodingAttributes; +import ws.schild.jave.encode.VideoAttributes; + +import java.io.File; + +/** + * 视频码率压缩工具类 + * 只压缩码率,保持原始分辨率和宽高比 + */ +@Slf4j +public class VideoCompressor { + + // 支持的视频格式 + private static final String[] SUPPORTED_FORMATS = { + ".mp4", ".avi", ".mov", ".mkv", ".flv", + ".wmv", ".webm", ".mpeg", ".mpg", ".3gp" + }; + + // 码率压缩参数 + private static final int TARGET_VIDEO_BITRATE = 1500000; // 1.5 Mbps + private static final int TARGET_AUDIO_BITRATE = 128000; // 128 kbps + private static final String VIDEO_CODEC = "libx264"; + private static final String AUDIO_CODEC = "aac"; + private static final String OUTPUT_FORMAT = "mp4"; + private static final String OUTPUT_SUFFIX = "_compressed.mp4"; + + // 码率判断阈值 + private static final int MAX_BITRATE_THRESHOLD = 2500000; // 2.5 Mbps + private static final int MIN_BITRATE_THRESHOLD = 1000000; // 1 Mbps + + /** + * 压缩视频码率,保持原始分辨率和宽高比 + * + * @param inputFile 输入视频文件 + * @return 压缩后的视频文件,如果不需要压缩则返回原文件 + * @throws Exception 压缩过程中可能出现的异常 + */ + public static File compressTo720p(File inputFile) throws Exception { + if (!isSupportedVideoFile(inputFile)) { + log.info("文件格式不支持或文件不存在,直接返回源文件"); + return inputFile; + } + + // 判断是否需要压缩码率 + if (!needBitrateCompression(inputFile)) { + return inputFile; + } + + File outputFile = createOutputFile(inputFile); + Encoder encoder = new Encoder(); + + // 配置编码参数 + EncodingAttributes encodingAttributes = createBitrateEncodingAttributes(); + + log.info("开始压缩视频: {} -> {}", inputFile.getName(), outputFile.getName()); + encoder.encode(new MultimediaObject(inputFile), outputFile, encodingAttributes); + log.info("视频压缩完成: {}", outputFile.getName()); + + return outputFile; + } + + /** + * 判断视频是否需要压缩码率 + */ + public static boolean needBitrateCompression(File videoFile) throws Exception { + MultimediaObject media = new MultimediaObject(videoFile); + + // 获取视频基本信息 + double fileSizeMB = videoFile.length() / 1048576.0; + double duration = media.getInfo().getDuration() / 1000.0; + int originalBitrate = media.getInfo().getVideo().getBitRate(); + + // 获取视频分辨率 + int width = media.getInfo().getVideo().getSize().getWidth(); + int height = media.getInfo().getVideo().getSize().getHeight(); + + log.info("视频分析: 分辨率 {}x{}, 大小 {}MB, 时长 {}秒, 码率 {}bps", width, height, fileSizeMB, duration, originalBitrate); + + // 如果原始码率超过阈值,就需要压缩 + boolean needsCompression = originalBitrate > MAX_BITRATE_THRESHOLD; + + if (needsCompression) { + log.info("视频码率({}bps)超过阈值,需要压缩", originalBitrate); + } else { + log.info("视频码率({}bps)在合理范围内,无需压缩", originalBitrate); + } + + return needsCompression; + } + + /** + * 创建码率压缩编码配置 + */ + private static EncodingAttributes createBitrateEncodingAttributes() { + // 音频配置 + AudioAttributes audio = new AudioAttributes(); + audio.setCodec(AUDIO_CODEC); + audio.setBitRate(TARGET_AUDIO_BITRATE); + + // 视频配置 + VideoAttributes video = new VideoAttributes(); + video.setCodec(VIDEO_CODEC); + video.setBitRate(TARGET_VIDEO_BITRATE); + + // 编码设置 + EncodingAttributes attributes = new EncodingAttributes(); + attributes.setOutputFormat(OUTPUT_FORMAT); + attributes.setAudioAttributes(audio); + attributes.setVideoAttributes(video); + + return attributes; + } + + /** + * 检查文件是否为支持的视频格式 + */ + private static boolean isSupportedVideoFile(File file) { + if (file == null || !file.exists()) { + return false; + } + + String filename = file.getName().toLowerCase(); + for (String format : SUPPORTED_FORMATS) { + if (filename.endsWith(format)) { + return true; + } + } + return false; + } + + /** + * 创建输出文件对象 + */ + private static File createOutputFile(File inputFile) { + String baseName = getFileBaseName(inputFile.getName()); + String outputFilename = baseName + OUTPUT_SUFFIX; + return new File(inputFile.getParent(), outputFilename); + } + + /** + * 获取文件名(不含扩展名) + */ + private static String getFileBaseName(String filename) { + int dotIndex = filename.lastIndexOf('.'); + return (dotIndex == -1) ? filename : filename.substring(0, dotIndex); + } +} \ No newline at end of file diff --git a/home-service/src/main/resources/application.yml b/home-service/src/main/resources/application.yml index d422948..030daba 100644 --- a/home-service/src/main/resources/application.yml +++ b/home-service/src/main/resources/application.yml @@ -6,7 +6,7 @@ spring: name: @project.artifactId@ version: @project.version@ profiles: - active: dev + active: docker mail: host: smtp.163.com port: 465 @@ -36,3 +36,7 @@ web-starter: aes: secret-key: "sG5p7t9wBdKnPqRsUvYx2D5F8H9JmQ=2" + +alist: + username: admin + password: 19940822Cxx diff --git a/home-service/src/main/resources/mapper/FoodMapper.xml b/home-service/src/main/resources/mapper/FoodMapper.xml index f681063..dff3f48 100644 --- a/home-service/src/main/resources/mapper/FoodMapper.xml +++ b/home-service/src/main/resources/mapper/FoodMapper.xml @@ -106,16 +106,17 @@