diff --git a/src/main/java/com/cxx/food/controller/FileController.java b/src/main/java/com/cxx/food/controller/FileController.java new file mode 100644 index 0000000..265b4f7 --- /dev/null +++ b/src/main/java/com/cxx/food/controller/FileController.java @@ -0,0 +1,65 @@ +package com.cxx.food.controller; + +import com.cxx.food.dto.file.ChunkInfoDto; +import com.cxx.food.dto.file.ChunkResultDto; +import com.cxx.food.dto.file.FileInfoDto; +import com.cxx.food.dto.file.FileRecordDto; +import com.cxx.food.service.FileService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +@RestController +@RequestMapping("/file") +@Tag(name = "文件管理") +public class FileController { + @Resource + private FileService fileService; + + @Operation(summary = "上传文件") + @PostMapping(value = "", headers = "content-type=multipart/form-data") + public FileRecordDto uploadFile(@RequestPart("file") MultipartFile file, + @RequestParam("path") String path, + @RequestParam("md5") String md5) { + return fileService.uploadFile(file, path, md5); + } + + @Operation(summary = "上传文件块") + @PostMapping("/chunk") + public Boolean uploadChunk(ChunkInfoDto chunkInfo, + String path) { + return fileService.uploadChunk(chunkInfo, path); + } + + @Operation(summary = "验证当前文件块是否上传") + @GetMapping("/chunk") + public ChunkResultDto checkChunk(@RequestParam String identifier, + @RequestParam String filename, + @RequestParam String path) { + return fileService.checkChunk(identifier, filename, path); + } + + @Operation(summary = "删除当前已上传的文件块") + @DeleteMapping("/chunk") + public Boolean deleteChunk(@RequestParam String identifier, + @RequestParam String path) { + return fileService.deleteChunk(identifier, path); + } + + @Operation(summary = "合并文件") + @PostMapping("/chunk/merge") + public FileRecordDto mergeFile(@RequestParam String filename, + @RequestParam String path) { + return fileService.mergeFile(filename, path); + } + + @Operation(summary = "获取文件夹目录") + @GetMapping("/catalog") + public List getFolderInfo(@RequestParam String folderName) { + return fileService.getFolderInfo(folderName); + } +} \ No newline at end of file diff --git a/src/main/java/com/cxx/food/controller/SessionController.java b/src/main/java/com/cxx/food/controller/SessionController.java index 3c97247..eef908f 100644 --- a/src/main/java/com/cxx/food/controller/SessionController.java +++ b/src/main/java/com/cxx/food/controller/SessionController.java @@ -6,10 +6,7 @@ import com.cxx.food.service.SessionService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/session") @@ -21,8 +18,14 @@ public class SessionController { @Operation(summary = "创建会话") @PostMapping("") @SaIgnore - public SessionDto createSession(@RequestParam("name") String name, - @RequestParam("password") String password) { - return sessionService.create(name, password); + public SessionDto create(@RequestParam("username") String username, + @RequestParam("password") String password) { + return sessionService.create(username, password); + } + + @Operation(summary = "退出会话") + @DeleteMapping("") + public Boolean abort(@RequestParam("username") String username) { + return sessionService.abort(username); } } \ No newline at end of file diff --git a/src/main/java/com/cxx/food/controller/UserController.java b/src/main/java/com/cxx/food/controller/UserController.java new file mode 100644 index 0000000..10fe922 --- /dev/null +++ b/src/main/java/com/cxx/food/controller/UserController.java @@ -0,0 +1,27 @@ +package com.cxx.food.controller; + +import cn.dev33.satoken.annotation.SaIgnore; +import com.cxx.food.dto.ReadView; +import com.cxx.food.dto.UserDto; +import com.cxx.food.dto.WriteView; +import com.cxx.food.service.UserService; +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.web.bind.annotation.*; + +@RestController +@RequestMapping("/user") +@Tag(name = "用户管理") +public class UserController { + @Resource + private UserService userService; + + @Operation(summary = "注册用户") + @PostMapping("") + @SaIgnore + public Boolean createSession(@JsonView(WriteView.class) @RequestBody UserDto userDto) { + return userService.register(userDto); + } +} \ No newline at end of file diff --git a/src/main/java/com/cxx/food/dao/FoodDao.java b/src/main/java/com/cxx/food/dao/FoodDao.java index 12f0fc1..b6854e8 100644 --- a/src/main/java/com/cxx/food/dao/FoodDao.java +++ b/src/main/java/com/cxx/food/dao/FoodDao.java @@ -15,22 +15,24 @@ public interface FoodDao { IPage querySummaryByPage(IPage page, @Param("query") FoodQueryVo foodQueryVo); - List querySummary(@Param("query") FoodQueryVo foodQueryVo); + List querySummary(@Param("query") FoodQueryVo foodQueryVo, + @Param("user") String user); List queryRecordById(@Param("id") Long id); List queryRecord(@Param("startDate") String startDate, - @Param("endDate") String endDate); + @Param("endDate") String endDate, + @Param("user") String user); - StatsDto queryStats(); + StatsDto queryStats(@Param("user") String user); - List queryRecordStats(); + List queryRecordStats(@Param("user") String user); - List queryCategoryStats(); + List queryCategoryStats(@Param("user") String user); - List queryRankStats(); + List queryRankStats(@Param("user") String user); - List queryCategory(); + List queryCategory(@Param("user") String user); void insertMaterial(@Param("foodId") Long foodId, @Param("list") List list); diff --git a/src/main/java/com/cxx/food/dto/SessionDto.java b/src/main/java/com/cxx/food/dto/SessionDto.java index 1d33b24..969a792 100644 --- a/src/main/java/com/cxx/food/dto/SessionDto.java +++ b/src/main/java/com/cxx/food/dto/SessionDto.java @@ -1,6 +1,7 @@ package com.cxx.food.dto; import cn.dev33.satoken.stp.SaTokenInfo; +import com.fasterxml.jackson.annotation.JsonView; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.Setter; @@ -10,5 +11,8 @@ import lombok.Setter; public class SessionDto { @Schema(description = "令牌") private SaTokenInfo saToken; + + @Schema(description = "用户信息") + private UserDto userInfo; } diff --git a/src/main/java/com/cxx/food/dto/UserDto.java b/src/main/java/com/cxx/food/dto/UserDto.java new file mode 100644 index 0000000..17df47b --- /dev/null +++ b/src/main/java/com/cxx/food/dto/UserDto.java @@ -0,0 +1,73 @@ +package com.cxx.food.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonView; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.Setter; + +import java.util.Date; +import java.util.List; + +@Getter +@Setter +@JsonView(PlainView.class) +public class UserDto { + @Schema(description = "id") + @JsonView(ReadView.class) + private Long id; + + @Schema(description = "用户名称") + @NotBlank(message = "用户名称不能为空") + private String username; + + @Schema(description = "密码") + @NotBlank(message = "密码不能为空") + @JsonView(WriteView.class) + private String password; + +// @Schema(description = "性别") +// @JsonView(ReadView.class) +// private Integer gender; +// +// @Schema(description = "身份证号") +// @JsonView(ReadView.class) +// private String idNumber; +// +// @Schema(description = "手机号码") +// @JsonView(ReadView.class) +// private String phoneNumber; +// +// @Schema(description = "电子邮件") +// @JsonView(ReadView.class) +// private String email; +// +// @Schema(description = "生日") +// @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") +// private Date birthDate; + + @Schema(description = "头像") + @JsonView(ReadView.class) + private String avatar; + +// @Schema(description = "地区") +// @JsonView(ReadView.class) +// private List area; +// +// @Schema(description = "地址") +// @JsonView(ReadView.class) +// private String address; +// +// @Schema(description = "职业") +// @JsonView(ReadView.class) +// private String job; +// +// @Schema(description = "标签") +// @JsonView(ReadView.class) +// private List tags; +// +// @Schema(description = "描述") +// @JsonView(ReadView.class) +// private String description; +} diff --git a/src/main/java/com/cxx/food/dto/file/ChunkInfoDto.java b/src/main/java/com/cxx/food/dto/file/ChunkInfoDto.java new file mode 100644 index 0000000..2cd38af --- /dev/null +++ b/src/main/java/com/cxx/food/dto/file/ChunkInfoDto.java @@ -0,0 +1,22 @@ +package com.cxx.food.dto.file; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.web.multipart.MultipartFile; + +@Getter +@Setter +public class ChunkInfoDto { + /** + * 当前文件块,从1开始 + */ + private Integer chunkNumber; + /** + * 文件名 + */ + private String filename; + /** + * 块内容 + */ + private transient MultipartFile multipartFile; +} diff --git a/src/main/java/com/cxx/food/dto/file/ChunkResultDto.java b/src/main/java/com/cxx/food/dto/file/ChunkResultDto.java new file mode 100644 index 0000000..51ec08e --- /dev/null +++ b/src/main/java/com/cxx/food/dto/file/ChunkResultDto.java @@ -0,0 +1,20 @@ +package com.cxx.food.dto.file; + +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +public class ChunkResultDto { + /** + * 是否跳过上传(已上传的可以直接跳过,达到秒传的效果) + */ + private boolean isSkipUpload; + + /** + * 已经上传的文件块编号,可以跳过,断点续传 + */ + private List uploadedChunkList; +} diff --git a/src/main/java/com/cxx/food/dto/file/FileInfoDto.java b/src/main/java/com/cxx/food/dto/file/FileInfoDto.java new file mode 100644 index 0000000..9aeea70 --- /dev/null +++ b/src/main/java/com/cxx/food/dto/file/FileInfoDto.java @@ -0,0 +1,17 @@ +package com.cxx.food.dto.file; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Getter; +import lombok.Setter; + +import java.util.Date; + +@Getter +@Setter +public class FileInfoDto { + private String name; + private String type; + private String size; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Date createDate; +} diff --git a/src/main/java/com/cxx/food/dto/file/FileRecordDto.java b/src/main/java/com/cxx/food/dto/file/FileRecordDto.java new file mode 100644 index 0000000..8c08ca7 --- /dev/null +++ b/src/main/java/com/cxx/food/dto/file/FileRecordDto.java @@ -0,0 +1,14 @@ +package com.cxx.food.dto.file; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class FileRecordDto { + private Long id; + private String name; + private String md5; + private String path; + private String url; +} diff --git a/src/main/java/com/cxx/food/entity/FileRecord.java b/src/main/java/com/cxx/food/entity/FileRecord.java new file mode 100644 index 0000000..d9f44d4 --- /dev/null +++ b/src/main/java/com/cxx/food/entity/FileRecord.java @@ -0,0 +1,24 @@ +package com.cxx.food.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.cxx.framework.data.AbstractEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("file_record") +public class FileRecord extends AbstractEntity { + @TableField("name") + private String name; + + @TableField("md5") + private String md5; + + @TableField("path") + private String path; + + @TableField("url") + private String url; +} diff --git a/src/main/java/com/cxx/food/entity/Record.java b/src/main/java/com/cxx/food/entity/Record.java index 533d38f..1647154 100644 --- a/src/main/java/com/cxx/food/entity/Record.java +++ b/src/main/java/com/cxx/food/entity/Record.java @@ -16,7 +16,7 @@ public class Record extends AbstractIdEntity { private Long foodId; @TableField("person") - private Long person; + private String person; @TableField("date") private Date date; diff --git a/src/main/java/com/cxx/food/mapper/FileRecordMapper.java b/src/main/java/com/cxx/food/mapper/FileRecordMapper.java new file mode 100644 index 0000000..e6c1a1f --- /dev/null +++ b/src/main/java/com/cxx/food/mapper/FileRecordMapper.java @@ -0,0 +1,9 @@ +package com.cxx.food.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.cxx.food.entity.FileRecord; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface FileRecordMapper extends BaseMapper { +} \ No newline at end of file diff --git a/src/main/java/com/cxx/food/service/FileService.java b/src/main/java/com/cxx/food/service/FileService.java new file mode 100644 index 0000000..3cf0d3d --- /dev/null +++ b/src/main/java/com/cxx/food/service/FileService.java @@ -0,0 +1,48 @@ +package com.cxx.food.service; + +import com.cxx.food.dto.file.ChunkInfoDto; +import com.cxx.food.dto.file.ChunkResultDto; +import com.cxx.food.dto.file.FileInfoDto; +import com.cxx.food.dto.file.FileRecordDto; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +public interface FileService { + FileRecordDto uploadFile(MultipartFile file, String path, String md5); + + /** + * 上传文件块 + * @param chunkInfo 文件块信息 + * @param path 上传路径 + * @return 是否上传成功 + */ + Boolean uploadChunk(ChunkInfoDto chunkInfo, String path); + + /** + * 检查文件块信息 + * @param identifier 唯一标识 + * @param filename 文件名 + * @param path 上传文件夹路径 + * @return 检查结果 + */ + ChunkResultDto checkChunk(String identifier, String filename, String path); + + /** + * 删除文件块 + * @param identifier 唯一标识 + * @param path 上传文件夹路径 + * @return 是否删除成功 + */ + Boolean deleteChunk(String identifier, String path); + + /** + * 合并文件 + * @param filename 文件名 + * @param path 上传文件夹路径 + * @return 是否合并成功 + */ + FileRecordDto mergeFile(String filename, String path); + + List getFolderInfo(String folderName); +} diff --git a/src/main/java/com/cxx/food/service/SessionService.java b/src/main/java/com/cxx/food/service/SessionService.java index 4f05d46..3baf12b 100644 --- a/src/main/java/com/cxx/food/service/SessionService.java +++ b/src/main/java/com/cxx/food/service/SessionService.java @@ -4,5 +4,7 @@ package com.cxx.food.service; import com.cxx.food.dto.SessionDto; public interface SessionService { - SessionDto create(String name, String password); + SessionDto create(String username, String password); + + Boolean abort(String username); } diff --git a/src/main/java/com/cxx/food/service/UserService.java b/src/main/java/com/cxx/food/service/UserService.java new file mode 100644 index 0000000..5b0fa86 --- /dev/null +++ b/src/main/java/com/cxx/food/service/UserService.java @@ -0,0 +1,9 @@ +package com.cxx.food.service; + + +import com.cxx.food.dto.SessionDto; +import com.cxx.food.dto.UserDto; + +public interface UserService { + Boolean register(UserDto userDto); +} diff --git a/src/main/java/com/cxx/food/service/impl/FileServiceImpl.java b/src/main/java/com/cxx/food/service/impl/FileServiceImpl.java new file mode 100644 index 0000000..e3e06c0 --- /dev/null +++ b/src/main/java/com/cxx/food/service/impl/FileServiceImpl.java @@ -0,0 +1,204 @@ +package com.cxx.food.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.cxx.food.constant.FileConstant; +import com.cxx.food.dto.file.ChunkInfoDto; +import com.cxx.food.dto.file.ChunkResultDto; +import com.cxx.food.dto.file.FileInfoDto; +import com.cxx.food.dto.file.FileRecordDto; +import com.cxx.food.entity.FileRecord; +import com.cxx.food.mapper.FileRecordMapper; +import com.cxx.food.service.FileService; +import com.cxx.food.util.FileUtils; +import com.cxx.food.util.UploadFileUtil; +import com.cxx.framework.web.CustomException; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.FilenameUtils; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +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.nio.file.Paths; +import java.util.*; + + +@Service +@Slf4j +public class FileServiceImpl implements FileService { + @Value("${file.path}") + private String fileRootPath; + + @Resource + private FileRecordMapper fileRecordMapper; + + @Override + public FileRecordDto uploadFile(MultipartFile multipartFile, String path, String md5) { + UploadFileUtil.checkDirIsExist(getFilePath(path)); + + String filename = multipartFile.getOriginalFilename(); + String newPath = path + File.separator + md5 + "." + FilenameUtils.getExtension(filename); + File newFile = new File(getFilePath(newPath)); + + saveFile(multipartFile, newFile); + + // 这里的url需要加上 WebMvcConfig 中设置的前缀 + String url = FileConstant.FILE_URL_PREFIX + File.separator + newPath; + return getFileRecord(filename, md5, path, url); + } + + @Override + public Boolean uploadChunk(ChunkInfoDto chunkInfo, String path) { + MultipartFile file = chunkInfo.getMultipartFile(); + + try { + // 1. 校验路径是否存在 + UploadFileUtil.checkDirIsExist(getFilePath(path)); + // 2. 获取文件块路径 + Path chunkPath = Paths.get(UploadFileUtil.generateChunkPath(getFilePath(path), chunkInfo)); + // 3. 将文件块写入指定路径 + Files.write(chunkPath, file.getBytes()); + } catch (IOException exception) { + log.error("文件上传块失败: {}", exception.getMessage()); + throw new CustomException("文件上传块失败: " + exception.getMessage()); + } + + return true; + } + + @Override + public ChunkResultDto checkChunk(String identifier, String filename, String path) { + ChunkResultDto chunkResult = new ChunkResultDto(); + List uploadedChunkList = new ArrayList<>(); + + String folder = getFilePath(path) + File.separator + identifier; + String file = folder + File.separator + filename; + + // 判断文件夹是否存在 + if (UploadFileUtil.fileExists(folder)) { + // 先判断整个文件是否已经上传过了,如果是,则告诉前端跳过上传,实现秒传 + if (UploadFileUtil.fileExists(file)) { + chunkResult.setSkipUpload(true); + chunkResult.setUploadedChunkList(uploadedChunkList); + log.info("完整文件已存在,直接跳过上传,实现秒传"); + } else { + chunkResult.setSkipUpload(false); + // 获取已经上传的文件块 + chunkResult.setUploadedChunkList(UploadFileUtil.getUploadedChunkList(folder, filename)); + } + } else { + chunkResult.setSkipUpload(false); + chunkResult.setUploadedChunkList(uploadedChunkList); + } + + return chunkResult; + } + + @Override + public Boolean deleteChunk(String identifier, String path) { + String folder = getFilePath(path) + File.separator + identifier; + log.info("开始删除文件: " + folder); + + // 判断文件夹是否存在 + if (UploadFileUtil.fileExists(folder)) { + UploadFileUtil.deleteDirectory(folder); + } else { + throw new CustomException("文件: " + folder + "不存在"); + } + + return true; + } + + @Override + public FileRecordDto mergeFile(String filename, String path) { + String uuid = UUID.randomUUID().toString().replace("-", ""); + String newFilename = uuid + "." + FilenameUtils.getExtension(filename); + try { + UploadFileUtil.mergeFile(newFilename, getFilePath(path)); + } catch (IOException exception) { + throw new CustomException("合并文件: " + filename + " 失败"); + } + + String newPath = path + File.separator + newFilename; + String url = FileConstant.FILE_URL_PREFIX + File.separator + newPath; + return getFileRecord(filename, uuid, path, url); + } + + @Override + public List getFolderInfo(String folderName) { + File folder = new File(folderName); + List fileInfoList = new ArrayList<>(); + if (folder.exists() && folder.isDirectory()) { + File[] files = folder.listFiles(); + if (files != null) { + List folderInfoList = new ArrayList<>(); + List fileInfoSubList = new ArrayList<>(); + for (File file : files) { + FileInfoDto fileInfo = new FileInfoDto(); + fileInfo.setName(file.getName()); + if (file.isDirectory()) { + fileInfo.setType("文件夹"); + fileInfo.setSize("-"); + folderInfoList.add(fileInfo); + } else { + fileInfo.setType(FileUtils.getFileType(file.getName())); + fileInfo.setSize(FileUtils.formatSize(file.length())); + fileInfoSubList.add(fileInfo); + } + fileInfo.setCreateDate(new Date(file.lastModified())); + } + fileInfoList.addAll(folderInfoList); + fileInfoList.addAll(fileInfoSubList); + } + } + return fileInfoList; + } + + private void saveFile(MultipartFile multipartFile, File file) { + // 如果文件不存在 + if (!file.exists()) { + try { + // 3. 如果不存在则创建文件 + multipartFile.transferTo(file); + } catch (IOException e) { + throw new CustomException("文件上传失败:{}", e.getMessage()); + } + } + } + + private FileRecordDto getFileRecord(String filename, String md5, String path, String url) { + FileRecord fileRecord = new FileRecord(); + fileRecord.setName(filename); + fileRecord.setMd5(md5); + fileRecord.setPath(path); + fileRecord.setUrl(url); + fileRecord.setId(saveDatabase(fileRecord, md5)); + + // 4. 返回文件信息 + FileRecordDto fileRecordDto = new FileRecordDto(); + BeanUtils.copyProperties(fileRecord, fileRecordDto); + + return fileRecordDto; + } + + private Long saveDatabase(FileRecord fileRecord, String md5) { + LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery(FileRecord.class); + FileRecord dbFileRecord = fileRecordMapper.selectOne(queryWrapper.eq(FileRecord::getMd5, md5)); + if (Objects.isNull(dbFileRecord)) { + fileRecordMapper.insert(fileRecord); + return fileRecord.getId(); + } else { + return dbFileRecord.getId(); + } + } + + private String getFilePath(String path) { + return fileRootPath + path; + } +} diff --git a/src/main/java/com/cxx/food/service/impl/FoodServiceImpl.java b/src/main/java/com/cxx/food/service/impl/FoodServiceImpl.java index 9576b5c..6eb70bb 100644 --- a/src/main/java/com/cxx/food/service/impl/FoodServiceImpl.java +++ b/src/main/java/com/cxx/food/service/impl/FoodServiceImpl.java @@ -1,5 +1,6 @@ package com.cxx.food.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,22 @@ public class FoodServiceImpl implements FoodService { public Boolean addRecord(RecordDto recordDto) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); Recipe recipe = recipeMapper.selectOne(queryWrapper.eq(Recipe::getName, recordDto.getName())); + long recipeId; if (recipe == null) { - throw new CustomException("该菜谱不存在"); + Recipe newRecipe = new Recipe(); + newRecipe.setName(recordDto.getName()); + newRecipe.setCategory("其他"); + newRecipe.setRecommendRate(0); + newRecipe.setRemark(""); + recipeMapper.insert(newRecipe); + recipeId = newRecipe.getId(); + } else { + recipeId = recipe.getId(); } Record record = new Record(); - record.setFoodId(recipe.getId()); + record.setFoodId(recipeId); + record.setPerson((String) StpUtil.getLoginId()); BeanUtils.copyProperties(recordDto, record); recordMapper.insert(record); @@ -164,7 +175,7 @@ public class FoodServiceImpl implements FoodService { @Override public List querySummary(FoodQueryVo foodQueryVo) { - return foodDao.querySummary(foodQueryVo); + return foodDao.querySummary(foodQueryVo, StpUtil.getLoginIdAsString()); } @Override @@ -184,7 +195,7 @@ public class FoodServiceImpl implements FoodService { @Override public List queryRecord(String startDate, String endDate) { - return foodDao.queryRecord(startDate, endDate); + return foodDao.queryRecord(startDate, endDate, StpUtil.getLoginIdAsString()); } @Override @@ -195,26 +206,26 @@ public class FoodServiceImpl implements FoodService { @Override public List queryCategory() { - return foodDao.queryCategory(); + return foodDao.queryCategory(StpUtil.getLoginIdAsString()); } @Override public StatsDto queryStats() { - return foodDao.queryStats(); + return foodDao.queryStats(StpUtil.getLoginIdAsString()); } @Override public List queryRecordStats() { - return foodDao.queryRecordStats(); + return foodDao.queryRecordStats(StpUtil.getLoginIdAsString()); } @Override public List queryCategoryStats() { - return foodDao.queryCategoryStats(); + return foodDao.queryCategoryStats(StpUtil.getLoginIdAsString()); } @Override public List queryRankStats() { - return foodDao.queryRankStats(); + return foodDao.queryRankStats(StpUtil.getLoginIdAsString()); } } diff --git a/src/main/java/com/cxx/food/service/impl/SessionServiceImpl.java b/src/main/java/com/cxx/food/service/impl/SessionServiceImpl.java index b74c020..a0b2d1e 100644 --- a/src/main/java/com/cxx/food/service/impl/SessionServiceImpl.java +++ b/src/main/java/com/cxx/food/service/impl/SessionServiceImpl.java @@ -6,6 +6,9 @@ import cn.dev33.satoken.stp.StpUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.cxx.food.dto.SessionDto; +import com.cxx.food.dto.UserDto; +import com.cxx.food.entity.User; +import com.cxx.food.mapper.UserMapper; import com.cxx.food.service.SessionService; import com.cxx.framework.web.CustomException; import jakarta.annotation.Resource; @@ -15,13 +18,42 @@ import org.springframework.stereotype.Service; @Service public class SessionServiceImpl implements SessionService { + @Resource + private UserMapper userMapper; @Override - public SessionDto create(String name, String password) { + public SessionDto create(String username, String password) { + LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery(User.class).eq(User::getUsername, username); + User user = userMapper.selectOne(queryWrapper); + if (user == null) { + throw new CustomException("该用户不存在"); + } + + if (!BCrypt.checkpw(password, user.getPassword())) { + throw new CustomException("密码不正确"); + } + + UserDto userDto = new UserDto(); + BeanUtils.copyProperties(user, userDto); + SessionDto sessionDto = new SessionDto(); - StpUtil.login(645808454946885L); + StpUtil.login(user.getId()); sessionDto.setSaToken(StpUtil.getTokenInfo()); + sessionDto.setUserInfo(userDto); return sessionDto; } + + @Override + public Boolean abort(String username) { + LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery(User.class).eq(User::getUsername, username); + User user = userMapper.selectOne(queryWrapper); + if (user == null) { + throw new CustomException("该用户不存在"); + } + + StpUtil.logout(user.getId()); + + return Boolean.TRUE; + } } diff --git a/src/main/java/com/cxx/food/service/impl/UserServiceImpl.java b/src/main/java/com/cxx/food/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..c18a40b --- /dev/null +++ b/src/main/java/com/cxx/food/service/impl/UserServiceImpl.java @@ -0,0 +1,34 @@ +package com.cxx.food.service.impl; + + +import cn.dev33.satoken.secure.BCrypt; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.cxx.food.dto.UserDto; +import com.cxx.food.entity.User; +import com.cxx.food.mapper.UserMapper; +import com.cxx.food.service.UserService; +import com.cxx.framework.web.CustomException; +import jakarta.annotation.Resource; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Service; + + +@Service +public class UserServiceImpl implements UserService { + @Resource + private UserMapper userMapper; + + @Override + public Boolean register(UserDto userDto) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + if (userMapper.exists(queryWrapper.eq(User::getUsername, userDto.getUsername()))) { + throw new CustomException("该用户已注册"); + } + + User user = new User(); + BeanUtils.copyProperties(userDto, user); + user.setPassword(BCrypt.hashpw(userDto.getPassword(), BCrypt.gensalt())); + + return userMapper.insert(user) == 1; + } +} diff --git a/src/main/java/com/cxx/food/util/FileUtils.java b/src/main/java/com/cxx/food/util/FileUtils.java new file mode 100644 index 0000000..cf6113d --- /dev/null +++ b/src/main/java/com/cxx/food/util/FileUtils.java @@ -0,0 +1,28 @@ +package com.cxx.food.util; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class FileUtils { + public static String getFileType(String fileName) { + int lastIndex = fileName.lastIndexOf('.'); + if (lastIndex != -1) { + return fileName.substring(lastIndex + 1); + } + return "未知类型"; + } + + public static String formatSize(long size) { + if (size < 1024) { + return size + " B"; + } else if (size < 1024 * 1024) { + return String.format("%.2f KB", (double) size / 1024); + } else if (size < 1024 * 1024 * 1024) { + return String.format("%.2f MB", (double) size / (1024 * 1024)); + } else { + return String.format("%.2f GB", (double) size / (1024 * 1024 * 1024)); + } + } + + +} diff --git a/src/main/java/com/cxx/food/util/UploadFileUtil.java b/src/main/java/com/cxx/food/util/UploadFileUtil.java new file mode 100644 index 0000000..d3e66cc --- /dev/null +++ b/src/main/java/com/cxx/food/util/UploadFileUtil.java @@ -0,0 +1,173 @@ +package com.cxx.food.util; + +import cn.hutool.core.io.file.FileNameUtil; +import com.cxx.food.dto.file.ChunkInfoDto; +import lombok.extern.slf4j.Slf4j; + +import java.io.File; +import java.io.IOException; +import java.nio.file.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; + +/** + * @Author: chenxiuxiang + * @Date: 2023/7/24 14:49 + * @Description: 上传文件工具类 + */ +@Slf4j +public class UploadFileUtil { + private static final String FILE_SEPARATOR = "_"; + + /** + * 校验文件夹是否存在 + * + * @param path 路径 + */ + public static void checkDirIsExist(String path) { + File dir = new File(path); + if (!dir.exists() && !dir.isDirectory()) { + boolean mkdir = dir.mkdirs(); + } + } + + /** + * 获取文件路径 + * + * @param path 上传文件夹路径 + * @param chunkInfo 分片文件信息 + * @return 文件路径 + */ + public static String generateChunkPath(String path, ChunkInfoDto chunkInfo) { + return path + File.separator + chunkInfo.getFilename() + FILE_SEPARATOR + chunkInfo.getChunkNumber(); + } + + public static List getUploadedChunkList(String folder, String filename) { + List uploadedChunkList = new ArrayList<>(); + + // 获取文件夹下所有的文件 + try (Stream list = Files.list(Paths.get(folder))) { + // 去除需要合并的文件 + list.filter(path -> !path.getFileName().toString().equals(filename)) + // 循环遍历文件 将已经上传的文件序号添加至列表中 + .forEach(path -> { + String chunkPath = path.getFileName().toString(); + int index = chunkPath.lastIndexOf(FILE_SEPARATOR); + uploadedChunkList.add(Integer.valueOf(chunkPath.substring(index + 1))); + }); + } catch (IOException e) { + throw new RuntimeException(e); + } + + return uploadedChunkList; + } + + /** + * 文件合并 + */ + public static void mergeFile(String filename, String path) throws IOException { + // 判断文件是否存在 + String file = path + File.separator + filename; + if (fileExists(file)) { + deleteFile(file); + } + + // 不存在的话,进行合并 + Files.createFile(Paths.get(file)); + + // 获取文件夹下所有的文件 + try (Stream list = Files.list(Paths.get(path))) { + // 保留后缀包含分隔符的 + list.filter(chunkPath -> FileNameUtil.getSuffix(chunkPath.getFileName().toString()).contains(FILE_SEPARATOR)) + // 按照文件名排序 + .sorted((o1, o2) -> { + String p1 = o1.getFileName().toString(); + String p2 = o2.getFileName().toString(); + int i1 = p1.lastIndexOf(FILE_SEPARATOR); + int i2 = p2.lastIndexOf(FILE_SEPARATOR); + return Integer.valueOf(p1.substring(i1 + 1)) + .compareTo(Integer.valueOf(p2.substring(i2 + 1))); + }) + // 循环写入到文件中 + .forEach(chunkPath -> { + try { + // 以追加的形式写入文件 + Files.write(Paths.get(file), Files.readAllBytes(chunkPath), StandardOpenOption.APPEND); + // 合并后删除该块 + Files.delete(chunkPath); + } catch (IOException exception) { + log.error("写入文件失败: " + exception.getMessage()); + throw new RuntimeException(exception); + } + }); + } + } + + /** + * 根据文件的全路径名判断文件是否存在 + */ + public static boolean fileExists(String file) { + Path path = Paths.get(file); + return Files.exists(path, LinkOption.NOFOLLOW_LINKS); + } + + /** + * 删除目录(文件夹)以及目录下的文件 + * + * @param sPath 被删除目录的文件路径 + * @return 目录删除成功返回true,否则返回false + */ + public static boolean deleteDirectory(String sPath) { + // 如果sPath不以文件分隔符结尾,自动添加文件分隔符 + if (!sPath.endsWith(File.separator)) { + sPath = sPath + File.separator; + } + File dirFile = new File(sPath); + // 如果dir对应的文件不存在,或者不是一个目录,则退出 + if (!dirFile.exists() || !dirFile.isDirectory()) { + return false; + } + + boolean flag = true; + // 删除文件夹下的所有文件(包括子目录) + File[] files = dirFile.listFiles(); + for (File file : Objects.requireNonNull(files)) { + // 删除子文件 + if (file.isFile()) { + flag = deleteFile(file.getAbsolutePath()); + } + // 删除子目录 + else { + flag = deleteDirectory(file.getAbsolutePath()); + } + if (!flag) { + break; + } + } + if (!flag) { + return false; + } + + log.info("文件删除成功"); + // 删除当前目录 + return dirFile.delete(); + } + + /** + * 删除单个文件 + * + * @param sPath 被删除文件的文件名 + * @return 单个文件删除成功返回true,否则返回false + */ + public static boolean deleteFile(String sPath) { + boolean flag = false; + File file = new File(sPath); + // 路径为文件且不为空则进行删除 + if (file.isFile() && file.exists()) { + flag = file.delete(); + } + return flag; + } +} \ No newline at end of file diff --git a/src/main/resources/mapper/FoodMapper.xml b/src/main/resources/mapper/FoodMapper.xml index 3dbcf06..af56bf1 100644 --- a/src/main/resources/mapper/FoodMapper.xml +++ b/src/main/resources/mapper/FoodMapper.xml @@ -92,6 +92,7 @@ LEFT JOIN recipe AS b ON a.food_id = b.id WHERE a.date =]]> STR_TO_DATE(#{startDate}, '%Y-%m-%d') AND a.date STR_TO_DATE(#{endDate}, '%Y-%m-%d') + AND a.person = #{user} ORDER BY date DESC @@ -132,7 +133,9 @@ a.category = #{query.category} + AND a.create_by = #{user} + ORDER BY a.update_time DESC; @@ -155,6 +160,7 @@ SELECT category AS name, COUNT(id) AS value FROM recipe + WHERE create_by = #{user} GROUP BY category ORDER BY value DESC; @@ -165,6 +171,7 @@ FROM recipe a LEFT JOIN record b ON a.id = b.food_id + WHERE a.create_by = #{user} GROUP BY a.id, a.name ORDER BY value DESC; @@ -172,6 +179,7 @@