diff --git a/biji-houdaun/src/main/java/com/test/bijihoudaun/common/advice/GlobalExceptionHandler.java b/biji-houdaun/src/main/java/com/test/bijihoudaun/common/advice/GlobalExceptionHandler.java new file mode 100644 index 0000000..54a99b0 --- /dev/null +++ b/biji-houdaun/src/main/java/com/test/bijihoudaun/common/advice/GlobalExceptionHandler.java @@ -0,0 +1,71 @@ +package com.test.bijihoudaun.common.advice; + +import com.test.bijihoudaun.common.exception.BaseException; + +import com.test.bijihoudaun.common.response.R; +import com.test.bijihoudaun.common.response.ResultCode; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.validation.BindException; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.multipart.MaxUploadSizeExceededException; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * 全局异常处理器 + */ +@RestControllerAdvice +public class GlobalExceptionHandler { + + /** + * 处理业务异常 + */ + @ExceptionHandler(BaseException.class) + public R handleBaseException(BaseException e, HttpServletRequest request) { + return R.fail(e.getResultCode().getCode(), e.getMessage()); + } + + /** + * 处理文件大小超出限制异常 + */ + @ExceptionHandler(MaxUploadSizeExceededException.class) + public R handleFileSizeLimitExceeded() { + return R.fail("文件大小超过限制"); + } + + /** + * 处理参数校验异常 + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + public R> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { + List errors = e.getBindingResult().getFieldErrors() + .stream() + .map(FieldError::getDefaultMessage) + .collect(Collectors.toList()); + return R.fail(ResultCode.VALIDATE_FAILED.getCode(), errors.get(0)); + } + + /** + * 处理绑定异常 + */ + @ExceptionHandler(BindException.class) + public R> handleBindException(BindException e) { + List errors = e.getBindingResult().getFieldErrors() + .stream() + .map(FieldError::getDefaultMessage) + .collect(Collectors.toList()); + return R.fail(ResultCode.VALIDATE_FAILED.getCode(), errors.get(0)); + } + + /** + * 处理其他异常 + */ + @ExceptionHandler(Exception.class) + public R handleException(Exception e) { + return R.fail(ResultCode.FAILED.getCode(), "系统繁忙,请稍后再试"); + } +} diff --git a/biji-houdaun/src/main/java/com/test/bijihoudaun/common/exception/BaseException.java b/biji-houdaun/src/main/java/com/test/bijihoudaun/common/exception/BaseException.java new file mode 100644 index 0000000..44d05c9 --- /dev/null +++ b/biji-houdaun/src/main/java/com/test/bijihoudaun/common/exception/BaseException.java @@ -0,0 +1,32 @@ +package com.test.bijihoudaun.common.exception; + +import com.test.bijihoudaun.common.response.ResultCode; +import lombok.Getter; + +/** + * 基础业务异常类 + */ +@Getter +public class BaseException extends RuntimeException { + private final ResultCode resultCode; + + public BaseException(ResultCode resultCode) { + super(resultCode.getMsg()); + this.resultCode = resultCode; + } + + public BaseException(ResultCode resultCode, String message) { + super(message); + this.resultCode = resultCode; + } + + public BaseException(ResultCode resultCode, Throwable cause) { + super(resultCode.getMsg(), cause); + this.resultCode = resultCode; + } + + public BaseException(ResultCode resultCode, String message, Throwable cause) { + super(message, cause); + this.resultCode = resultCode; + } +} diff --git a/biji-houdaun/src/main/java/com/test/bijihoudaun/common/response/R.java b/biji-houdaun/src/main/java/com/test/bijihoudaun/common/response/R.java new file mode 100644 index 0000000..41a1b4d --- /dev/null +++ b/biji-houdaun/src/main/java/com/test/bijihoudaun/common/response/R.java @@ -0,0 +1,87 @@ +package com.test.bijihoudaun.common.response; + +import lombok.Data; +import org.springframework.http.HttpStatus; +import org.springframework.http.ProblemDetail; + +import java.io.Serializable; +import java.net.URI; + +/** + * 统一返回结果类 + * @param 数据类型 + */ +@Data +public class R implements Serializable { + private static final long serialVersionUID = 1L; + + private Integer code; // 状态码 + private String msg; // 消息 + private T data; // 数据 + + // 成功返回 + public static R success() { + return success(null); + } + + public static R success(T data) { + R r = new R<>(); + r.setCode(ResultCode.SUCCESS.getCode()); + r.setMsg(ResultCode.SUCCESS.getMsg()); + r.setData(data); + return r; + } + + // 分页成功返回 + public static R pageSuccess(long total, T data) { + PageR r = new PageR<>(); + r.setCode(ResultCode.SUCCESS.getCode()); + r.setMsg(ResultCode.SUCCESS.getMsg()); + r.setData(data); + r.setTotal(total); + return r; + } + + // 失败返回 + public static R fail() { + return fail(ResultCode.FAILED); + } + + public static R fail(ResultCode resultCode) { + return fail(resultCode.getCode(), resultCode.getMsg()); + } + + public static R fail(String msg) { + return fail(ResultCode.FAILED.getCode(), msg); + } + + public static R fail(Integer code, String msg) { + R r = new R<>(); + r.setCode(code); + r.setMsg(msg); + return r; + } + + // 转换为ProblemDetail + public static ProblemDetail toProblemDetail(ResultCode resultCode, HttpStatus status) { + ProblemDetail problemDetail = ProblemDetail.forStatus(status); + problemDetail.setType(URI.create("https://api.test.com/errors/" + resultCode.name().toLowerCase())); + problemDetail.setTitle(resultCode.getMsg()); + problemDetail.setDetail(resultCode.getMsg()); + problemDetail.setProperty("code", resultCode.getCode()); + return problemDetail; + } + + // 分页返回结果类 + private static class PageR extends R { + private long total; + + public long getTotal() { + return total; + } + + public void setTotal(long total) { + this.total = total; + } + } +} diff --git a/biji-houdaun/src/main/java/com/test/bijihoudaun/common/response/ResultCode.java b/biji-houdaun/src/main/java/com/test/bijihoudaun/common/response/ResultCode.java new file mode 100644 index 0000000..661d198 --- /dev/null +++ b/biji-houdaun/src/main/java/com/test/bijihoudaun/common/response/ResultCode.java @@ -0,0 +1,51 @@ +package com.test.bijihoudaun.common.response; + +import lombok.Getter; + +/** + * 返回状态码枚举 + */ +@Getter +public enum ResultCode { + // 基础状态码 + SUCCESS(200, "操作成功"), + FAILED(500, "操作失败"), + VALIDATE_FAILED(400, "参数校验失败"), + UNAUTHORIZED(401, "未授权"), + FORBIDDEN(403, "禁止访问"), + NOT_FOUND(404, "资源不存在"), + + // 参数相关错误 + PARAM_IS_INVALID(1001, "参数无效"), + PARAM_IS_BLANK(1002, "参数为空"), + PARAM_TYPE_BIND_ERROR(1003, "参数类型错误"), + PARAM_NOT_COMPLETE(1004, "参数缺失"), + + // 加密相关错误 + ENCRYPTION_FAILED(2001, "加密失败"), + DECRYPTION_FAILED(2002, "解密失败"), + KEY_GENERATION_FAILED(2003, "密钥生成失败"), + INVALID_KEY_FORMAT(2004, "密钥格式无效"), + + // 编码相关 + BASE64_ENCODE_FAILED(4001, "Base64编码失败"), + BASE64_DECODE_FAILED(4002, "Base64解码失败"), + + + + // ID生成相关 + ID_GENERATION_FAILED(3001, "ID生成失败"), + UUID_GENERATION_FAILED(3002, "UUID生成失败"), + SNOWFLAKE_PARAM_INVALID(3003, "雪花ID参数无效"), // 用于workerId/datacenterId越界 + CLOCK_BACKWARD_ERROR(3004, "系统时钟回拨异常"), // 处理时钟回拨场景 + SEQUENCE_OVERFLOW(3005, "ID序列号溢出"); // 序列号超过4095时的异常 + + + private final Integer code; + private final String msg; + + ResultCode(Integer code, String msg) { + this.code = code; + this.msg = msg; + } +} diff --git a/biji-houdaun/src/main/java/com/test/bijihoudaun/config/WebConfig.java b/biji-houdaun/src/main/java/com/test/bijihoudaun/config/WebConfig.java new file mode 100644 index 0000000..c1d4018 --- /dev/null +++ b/biji-houdaun/src/main/java/com/test/bijihoudaun/config/WebConfig.java @@ -0,0 +1,26 @@ +package com.test.bijihoudaun.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedOrigins("*") + .allowedMethods("*") + .allowedHeaders("*") + .maxAge(3600); // 预检请求缓存时间 + ; + } + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + registry.addResourceHandler("/uploads/**") + .addResourceLocations("file:uploads/"); + } +} \ No newline at end of file diff --git a/biji-houdaun/src/main/java/com/test/bijihoudaun/controller/ImageController.java b/biji-houdaun/src/main/java/com/test/bijihoudaun/controller/ImageController.java new file mode 100644 index 0000000..96145ec --- /dev/null +++ b/biji-houdaun/src/main/java/com/test/bijihoudaun/controller/ImageController.java @@ -0,0 +1,33 @@ +package com.test.bijihoudaun.controller; + + +import com.test.bijihoudaun.entity.Image; +import com.test.bijihoudaun.service.ImageService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; + +@RestController +@RequestMapping("/api/images") +public class ImageController { + + @Autowired + private ImageService imageService; + + @PostMapping + public ResponseEntity uploadImage( + @RequestParam Integer userId, + @RequestParam(required = false) Integer markdownId, + @RequestParam("file") MultipartFile file) { + + try { + Image image = imageService.uploadImage(userId, markdownId, file); + return ResponseEntity.ok(image); + } catch (IOException e) { + return ResponseEntity.status(500).build(); + } + } +} \ No newline at end of file diff --git a/biji-houdaun/src/main/java/com/test/bijihoudaun/controller/MarkdownController.java b/biji-houdaun/src/main/java/com/test/bijihoudaun/controller/MarkdownController.java new file mode 100644 index 0000000..9e9d978 --- /dev/null +++ b/biji-houdaun/src/main/java/com/test/bijihoudaun/controller/MarkdownController.java @@ -0,0 +1,41 @@ +package com.test.bijihoudaun.controller; + +import com.test.bijihoudaun.entity.MarkdownFile; +import com.test.bijihoudaun.service.MarkdownFileService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/markdown") +public class MarkdownController { + + @Autowired + private MarkdownFileService markdownFileService; + + @PostMapping + public ResponseEntity createMarkdown( + @RequestParam Integer userId, + @RequestParam String title, + @RequestParam String fileName, + @RequestBody String content) { + + MarkdownFile file = markdownFileService.createMarkdownFile( + userId, title, fileName, content); + + return ResponseEntity.ok(file); + } + + @PutMapping("/{id}") + public ResponseEntity updateMarkdown( + @PathVariable Integer id, + @RequestBody String content) { + + MarkdownFile file = markdownFileService.updateMarkdownContent(id, content); + + if (file != null) { + return ResponseEntity.ok(file); + } + return ResponseEntity.notFound().build(); + } +} diff --git a/biji-houdaun/src/main/java/com/test/bijihoudaun/entity/Image.java b/biji-houdaun/src/main/java/com/test/bijihoudaun/entity/Image.java new file mode 100644 index 0000000..e1be22e --- /dev/null +++ b/biji-houdaun/src/main/java/com/test/bijihoudaun/entity/Image.java @@ -0,0 +1,32 @@ +package com.test.bijihoudaun.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +@TableName("image") +public class Image { + @TableId(type = IdType.AUTO) + private Integer id; + private Integer userId; + private Integer markdownId; + + @TableField("original_name") + private String originalName; + + @TableField("stored_name") + private String storedName; + + private String url; + private Integer size; + + @TableField("content_type") + private String contentType; + + private LocalDateTime createdAt; +} \ No newline at end of file diff --git a/biji-houdaun/src/main/java/com/test/bijihoudaun/entity/MarkdownFile.java b/biji-houdaun/src/main/java/com/test/bijihoudaun/entity/MarkdownFile.java new file mode 100644 index 0000000..e50aad5 --- /dev/null +++ b/biji-houdaun/src/main/java/com/test/bijihoudaun/entity/MarkdownFile.java @@ -0,0 +1,25 @@ +package com.test.bijihoudaun.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +@TableName("markdown_file") +public class MarkdownFile { + @TableId(type = IdType.AUTO) + private Integer id; + private Integer userId; + private String title; + + @TableField("file_name") + private String fileName; + + private String content; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; +} diff --git a/biji-houdaun/src/main/java/com/test/bijihoudaun/entity/User.java b/biji-houdaun/src/main/java/com/test/bijihoudaun/entity/User.java new file mode 100644 index 0000000..b96a0ba --- /dev/null +++ b/biji-houdaun/src/main/java/com/test/bijihoudaun/entity/User.java @@ -0,0 +1,20 @@ +package com.test.bijihoudaun.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +@TableName("user") +public class User { + @TableId(type = IdType.AUTO) + private Integer id; + private String username; + private String password; + private String email; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; +} diff --git a/biji-houdaun/src/main/java/com/test/bijihoudaun/interceptor/XSSInterceptor.java b/biji-houdaun/src/main/java/com/test/bijihoudaun/interceptor/XSSInterceptor.java new file mode 100644 index 0000000..6f089eb --- /dev/null +++ b/biji-houdaun/src/main/java/com/test/bijihoudaun/interceptor/XSSInterceptor.java @@ -0,0 +1,26 @@ +package com.test.bijihoudaun.interceptor; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HtmlUtil; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.web.servlet.HandlerInterceptor; + +import java.util.Arrays; +import java.util.Map; + +public class XSSInterceptor implements HandlerInterceptor { + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + // 清理请求参数中的XSS内容 + Map parameterMap = request.getParameterMap(); + parameterMap.forEach((key, values) -> { + String[] newValues = Arrays.stream(values) + .map(v -> StrUtil.isBlank(v) ? v : HtmlUtil.filter(v)) + .toArray(String[]::new); + request.setAttribute("filtered_" + key, newValues); + }); + return true; + } +} diff --git a/biji-houdaun/src/main/java/com/test/bijihoudaun/mapper/ImageMapper.java b/biji-houdaun/src/main/java/com/test/bijihoudaun/mapper/ImageMapper.java new file mode 100644 index 0000000..6a91a43 --- /dev/null +++ b/biji-houdaun/src/main/java/com/test/bijihoudaun/mapper/ImageMapper.java @@ -0,0 +1,14 @@ +package com.test.bijihoudaun.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.test.bijihoudaun.entity.Image; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface ImageMapper extends BaseMapper { + // 自定义方法:根据用户ID获取图片列表 + // List findByUserId(Integer userId); + + // 自定义方法:根据Markdown文件ID获取关联图片 + // List findByMarkdownId(Integer markdownId); +} \ No newline at end of file diff --git a/biji-houdaun/src/main/java/com/test/bijihoudaun/mapper/MarkdownFileMapper.java b/biji-houdaun/src/main/java/com/test/bijihoudaun/mapper/MarkdownFileMapper.java new file mode 100644 index 0000000..d9c1901 --- /dev/null +++ b/biji-houdaun/src/main/java/com/test/bijihoudaun/mapper/MarkdownFileMapper.java @@ -0,0 +1,11 @@ +package com.test.bijihoudaun.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.test.bijihoudaun.entity.MarkdownFile; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface MarkdownFileMapper extends BaseMapper { + // 自定义方法:根据用户ID获取文件列表 + // List findByUserId(Integer userId); +} diff --git a/biji-houdaun/src/main/java/com/test/bijihoudaun/mapper/UserMapper.java b/biji-houdaun/src/main/java/com/test/bijihoudaun/mapper/UserMapper.java new file mode 100644 index 0000000..0415a3c --- /dev/null +++ b/biji-houdaun/src/main/java/com/test/bijihoudaun/mapper/UserMapper.java @@ -0,0 +1,12 @@ +package com.test.bijihoudaun.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.test.bijihoudaun.entity.User; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface UserMapper extends BaseMapper { + // 自定义查询方法示例 + // @Select("SELECT * FROM user WHERE username = #{username}") + // User findByUsername(String username); +} diff --git a/biji-houdaun/src/main/java/com/test/bijihoudaun/service/ImageService.java b/biji-houdaun/src/main/java/com/test/bijihoudaun/service/ImageService.java new file mode 100644 index 0000000..3d51bd6 --- /dev/null +++ b/biji-houdaun/src/main/java/com/test/bijihoudaun/service/ImageService.java @@ -0,0 +1,42 @@ +package com.test.bijihoudaun.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.test.bijihoudaun.entity.Image; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.List; + +public interface ImageService extends IService { + /** + * 上传图片 + * @param userId 用户ID + * @param markdownId Markdown文件ID(可选) + * @param file 图片文件 + * @return 上传的图片对象 + * @throws IOException 文件操作异常 + */ + Image uploadImage(Integer userId, Integer markdownId, MultipartFile file) throws IOException; + + /** + * 删除图片 + * @param id 图片ID + * @param userId 用户ID(用于权限验证) + * @return 是否删除成功 + */ + boolean deleteImage(Integer id, Integer userId); + + /** + * 获取用户的图片列表 + * @param userId 用户ID + * @return 图片列表 + */ + List getUserImages(Integer userId); + + /** + * 获取Markdown文件关联的图片 + * @param markdownId Markdown文件ID + * @return 图片列表 + */ + List getMarkdownImages(Integer markdownId); +} \ No newline at end of file diff --git a/biji-houdaun/src/main/java/com/test/bijihoudaun/service/MarkdownFileService.java b/biji-houdaun/src/main/java/com/test/bijihoudaun/service/MarkdownFileService.java new file mode 100644 index 0000000..2ab8aca --- /dev/null +++ b/biji-houdaun/src/main/java/com/test/bijihoudaun/service/MarkdownFileService.java @@ -0,0 +1,33 @@ +package com.test.bijihoudaun.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.test.bijihoudaun.entity.MarkdownFile; + +import java.util.List; + +public interface MarkdownFileService extends IService { + /** + * 创建Markdown文件 + * @param userId 用户ID + * @param title 文件标题 + * @param fileName 文件名 + * @param content 文件内容 + * @return 创建的文件对象 + */ + MarkdownFile createMarkdownFile(Integer userId, String title, String fileName, String content); + + /** + * 更新Markdown内容 + * @param id 文件ID + * @param content 新内容 + * @return 更新后的文件对象 + */ + MarkdownFile updateMarkdownContent(Integer id, String content); + + /** + * 获取用户的所有Markdown文件 + * @param userId 用户ID + * @return 文件列表 + */ + List getUserFiles(Integer userId); +} \ No newline at end of file diff --git a/biji-houdaun/src/main/java/com/test/bijihoudaun/service/UserService.java b/biji-houdaun/src/main/java/com/test/bijihoudaun/service/UserService.java new file mode 100644 index 0000000..d475f47 --- /dev/null +++ b/biji-houdaun/src/main/java/com/test/bijihoudaun/service/UserService.java @@ -0,0 +1,29 @@ +package com.test.bijihoudaun.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.test.bijihoudaun.entity.User; + +public interface UserService extends IService { + /** + * 用户注册 + * @param username 用户名 + * @param password 密码 + * @param email 邮箱 + * @return 注册成功的用户 + */ + User register(String username, String password, String email); + + /** + * 用户登录 + * @param username 用户名 + * @param password 密码 + * @return 登录成功的用户 + */ + User login(String username, String password); + + /** + * 用户删除 + * @param id 用户id + */ + void deleteUser(Integer id); +} diff --git a/biji-houdaun/src/main/java/com/test/bijihoudaun/service/impl/ImageServiceImpl.java b/biji-houdaun/src/main/java/com/test/bijihoudaun/service/impl/ImageServiceImpl.java new file mode 100644 index 0000000..033ebe9 --- /dev/null +++ b/biji-houdaun/src/main/java/com/test/bijihoudaun/service/impl/ImageServiceImpl.java @@ -0,0 +1,100 @@ +package com.test.bijihoudaun.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.test.bijihoudaun.entity.Image; +import com.test.bijihoudaun.mapper.ImageMapper; +import com.test.bijihoudaun.service.ImageService; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +@Service +public class ImageServiceImpl + extends ServiceImpl + implements ImageService { + + @Value("${file.upload-dir}") + private String uploadDir; + + @Override + public Image uploadImage(Integer userId, Integer markdownId, MultipartFile file) throws IOException { + // 创建上传目录 + Path uploadPath = Paths.get(uploadDir); + if (!Files.exists(uploadPath)) { + Files.createDirectories(uploadPath); + } + + // 生成唯一文件名 + String originalFilename = file.getOriginalFilename(); + String extension = originalFilename.substring(originalFilename.lastIndexOf(".")); + String storedName = UUID.randomUUID() + extension; + + // 保存文件 + Path filePath = uploadPath.resolve(storedName); + Files.copy(file.getInputStream(), filePath); + + // 创建图片实体 + Image image = new Image(); + image.setUserId(userId); + image.setMarkdownId(markdownId); + image.setOriginalName(originalFilename); + image.setStoredName(storedName); + image.setUrl("/uploads/" + storedName); + image.setSize((int) file.getSize()); + image.setContentType(file.getContentType()); + image.setCreatedAt(LocalDateTime.now()); + + this.save(image); + return image; + } + + @Override + public boolean deleteImage(Integer id, Integer userId) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("id", id) + .eq("user_id", userId); + + Image image = this.getOne(queryWrapper); + if (image == null) { + return false; + } + + try { + // 删除文件系统中的图片 + Path filePath = Paths.get(uploadDir, image.getStoredName()); + Files.deleteIfExists(filePath); + + // 删除数据库记录 + this.removeById(id); + return true; + } catch (IOException e) { + throw new RuntimeException("删除图片失败", e); + } + } + + @Override + public List getUserImages(Integer userId) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("user_id", userId) + .orderByDesc("created_at"); + return this.list(queryWrapper); + } + + @Override + public List getMarkdownImages(Integer markdownId) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("markdown_id", markdownId) + .orderByDesc("created_at"); + return this.list(queryWrapper); + } +} \ No newline at end of file diff --git a/biji-houdaun/src/main/java/com/test/bijihoudaun/service/impl/MarkdownFileServiceImpl.java b/biji-houdaun/src/main/java/com/test/bijihoudaun/service/impl/MarkdownFileServiceImpl.java new file mode 100644 index 0000000..31d5610 --- /dev/null +++ b/biji-houdaun/src/main/java/com/test/bijihoudaun/service/impl/MarkdownFileServiceImpl.java @@ -0,0 +1,51 @@ +package com.test.bijihoudaun.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.test.bijihoudaun.entity.MarkdownFile; +import com.test.bijihoudaun.mapper.MarkdownFileMapper; +import com.test.bijihoudaun.service.MarkdownFileService; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.List; + +@Service +public class MarkdownFileServiceImpl + extends ServiceImpl + implements MarkdownFileService { + + @Override + public MarkdownFile createMarkdownFile(Integer userId, String title, String fileName, String content) { + MarkdownFile file = new MarkdownFile(); + file.setUserId(userId); + file.setTitle(title); + file.setFileName(fileName); + file.setContent(content); + file.setCreatedAt(LocalDateTime.now()); + file.setUpdatedAt(LocalDateTime.now()); + + this.save(file); + return file; + } + + @Override + public MarkdownFile updateMarkdownContent(Integer id, String content) { + MarkdownFile file = this.getById(id); + if (file != null) { + file.setContent(content); + file.setUpdatedAt(LocalDateTime.now()); + this.updateById(file); + } + return file; + } + + @Override + public List getUserFiles(Integer userId) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("user_id", userId) + .orderByDesc("updated_at"); + return this.list(queryWrapper); + } +} \ No newline at end of file diff --git a/biji-houdaun/src/main/java/com/test/bijihoudaun/service/impl/UserServiceImpl.java b/biji-houdaun/src/main/java/com/test/bijihoudaun/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..96fa5f7 --- /dev/null +++ b/biji-houdaun/src/main/java/com/test/bijihoudaun/service/impl/UserServiceImpl.java @@ -0,0 +1,54 @@ +package com.test.bijihoudaun.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.test.bijihoudaun.entity.User; +import com.test.bijihoudaun.mapper.UserMapper; +import com.test.bijihoudaun.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; + +@Service +public class UserServiceImpl extends ServiceImpl implements UserService { + + @Autowired + private UserMapper userMapper; + + @Override + public User register(String username, String password, String email) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(User::getUsername, username); + if (this.count(queryWrapper) > 0) { + return null; + } + queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(User::getEmail, email); + if (this.count(queryWrapper) > 0) { + return null; + } + User user = new User(); + user.setUsername(username); + user.setPassword(password); + user.setEmail(email); + user.setCreatedAt(LocalDateTime.now()); + userMapper.insert(user); + return user; + } + + @Override + public User login(String username, String password) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(User::getUsername, username) + .eq(User::getPassword, password); + return userMapper.selectOne(queryWrapper); + } + + @Override + public void deleteUser(Integer id) { + userMapper.deleteById(id); + } +} \ No newline at end of file diff --git a/biji-houdaun/src/main/java/com/test/bijihoudaun/util/EntityCopier.java b/biji-houdaun/src/main/java/com/test/bijihoudaun/util/EntityCopier.java new file mode 100644 index 0000000..0572319 --- /dev/null +++ b/biji-houdaun/src/main/java/com/test/bijihoudaun/util/EntityCopier.java @@ -0,0 +1,49 @@ +package com.test.bijihoudaun.util; + +import cn.hutool.core.bean.BeanUtil; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * 实体复制工具类 + */ +public class EntityCopier { + + /** + * 复制实体属性到另一个实体 + * @param source 源实体 + * @param target 目标实体 + * @param 源类型 + * @param 目标类型 + */ + public static void copyEntityToEntity(S source, T target) { + BeanUtil.copyProperties(source, target); + } + + /** + * 复制实体属性到目标类的新实例 + * @param source 源实体 + * @param targetClass 目标类 + * @param 源类型 + * @param 目标类型 + * @return 目标类的新实例 + */ + public static T copyEntityToClass(S source, Class targetClass) { + return BeanUtil.copyProperties(source, targetClass); + } + + /** + * 复制实体列表到目标类列表 + * @param sourceList 源列表 + * @param targetClass 目标类 + * @param 源类型 + * @param 目标类型 + * @return 目标类的新列表 + */ + public static List copyList(List sourceList, Class targetClass) { + return sourceList.stream() + .map(source -> copyEntityToClass(source, targetClass)) + .collect(Collectors.toList()); + } +} diff --git a/biji-houdaun/src/main/java/com/test/bijihoudaun/util/RandomString.java b/biji-houdaun/src/main/java/com/test/bijihoudaun/util/RandomString.java new file mode 100644 index 0000000..a576631 --- /dev/null +++ b/biji-houdaun/src/main/java/com/test/bijihoudaun/util/RandomString.java @@ -0,0 +1,16 @@ +package com.test.bijihoudaun.util; + +public class RandomString { + + // 生成指定长度的随机字符串 + public static String generateRandomString(int length) { + String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + StringBuilder result = new StringBuilder(); + + for (int i = 0; i < length; i++) { + int randomIndex = (int) (Math.random() * chars.length()); + result.append(chars.charAt(randomIndex)); + } + return result.toString(); + } +} diff --git a/biji-houdaun/src/main/java/com/test/bijihoudaun/util/SnowflakeIdGenerator.java b/biji-houdaun/src/main/java/com/test/bijihoudaun/util/SnowflakeIdGenerator.java new file mode 100644 index 0000000..ad78d04 --- /dev/null +++ b/biji-houdaun/src/main/java/com/test/bijihoudaun/util/SnowflakeIdGenerator.java @@ -0,0 +1,122 @@ +package com.test.bijihoudaun.util; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class SnowflakeIdGenerator { + + // 起始时间戳(2023-01-01 00:00:00) + private static final long EPOCH = 1672531200000L; + + // 机器ID位数 + private static final long WORKER_ID_BITS = 5L; + // 数据中心ID位数 + private static final long DATACENTER_ID_BITS = 5L; + // 最大机器ID (0-31) + private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS); + // 最大数据中心ID (0-31) + private static final long MAX_DATACENTER_ID = ~(-1L << DATACENTER_ID_BITS); + // 序列号位数 + private static final long SEQUENCE_BITS = 12L; + + // 机器ID左移位数 + private static final long WORKER_ID_SHIFT = SEQUENCE_BITS; + // 数据中心ID左移位数 + private static final long DATACENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS; + // 时间戳左移位数 + private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATACENTER_ID_BITS; + // 序列号掩码(4095) + private static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS); + + private final long workerId; + private final long datacenterId; + + private long sequence = 0L; + private long lastTimestamp = -1L; + + /** + * 构造函数 + * + * @param workerId 机器ID + * @param datacenterId 数据中心ID + */ + public SnowflakeIdGenerator( + @Value("${worker.id:0}") long workerId, + @Value("${datacenter.id:0}") long datacenterId) { + + // 校验机器ID范围 + if (workerId > MAX_WORKER_ID || workerId < 0) { + throw new IllegalArgumentException( + String.format("Worker ID 必须在 0 到 %d 之间", MAX_WORKER_ID)); + } + + // 校验数据中心ID范围 + if (datacenterId > MAX_DATACENTER_ID || datacenterId < 0) { + throw new IllegalArgumentException( + String.format("Datacenter ID 必须在 0 到 %d 之间", MAX_DATACENTER_ID)); + } + + this.workerId = workerId; + this.datacenterId = datacenterId; + } + + /** + * 生成雪花ID + * + * @return 雪花ID + */ + public synchronized long nextId() { + long timestamp = currentTime(); + + // 处理时钟回拨 + if (timestamp < lastTimestamp) { + throw new RuntimeException( + String.format("时钟回拨拒绝生成ID。当前时间: %d, 最后时间: %d", + timestamp, lastTimestamp)); + } + + // 同一毫秒内生成ID + if (lastTimestamp == timestamp) { + sequence = (sequence + 1) & SEQUENCE_MASK; + // 当前毫秒序列号用完 + if (sequence == 0) { + timestamp = nextMillis(lastTimestamp); + } + } + // 新毫秒重置序列号 + else { + sequence = 0L; + } + + lastTimestamp = timestamp; + + // 组合ID各部分 + return ((timestamp - EPOCH) << TIMESTAMP_SHIFT) + | (datacenterId << DATACENTER_ID_SHIFT) + | (workerId << WORKER_ID_SHIFT) + | sequence; + } + + /** + * 生成雪花ID,转化成字符串类型 + * + * @return 雪花ID-字符串类型 + */ + public synchronized String nextIdStr() { + return Long.toString(nextId()); + } + + // 等待下一毫秒 + private long nextMillis(long lastTimestamp) { + long timestamp = currentTime(); + while (timestamp <= lastTimestamp) { + timestamp = currentTime(); + } + return timestamp; + } + + // 获取当前毫秒时间戳 + private long currentTime() { + return System.currentTimeMillis(); + } +} \ No newline at end of file diff --git a/biji-houdaun/src/main/java/com/test/bijihoudaun/util/UuidV7.java b/biji-houdaun/src/main/java/com/test/bijihoudaun/util/UuidV7.java new file mode 100644 index 0000000..bdf6bbe --- /dev/null +++ b/biji-houdaun/src/main/java/com/test/bijihoudaun/util/UuidV7.java @@ -0,0 +1,26 @@ +package com.test.bijihoudaun.util; + +import com.fasterxml.uuid.Generators; +import com.fasterxml.uuid.impl.TimeBasedGenerator; + +/** + * uuidV7 + */ +public class UuidV7 { + /** + * 生成uuidV7,生成的uuid有- + */ + public static String uuid() { + TimeBasedGenerator generator = Generators.timeBasedGenerator(); + return generator.generate().toString(); + } + + /** + * 获取uuidV7,生成的uuid无- + */ + public static String uuidNoHyphen() { + return uuid().replace("-", ""); + } + + +} diff --git a/biji-houdaun/src/main/resources/application.yml b/biji-houdaun/src/main/resources/application.yml index b64c2fb..4aff118 100644 --- a/biji-houdaun/src/main/resources/application.yml +++ b/biji-houdaun/src/main/resources/application.yml @@ -7,6 +7,10 @@ spring: multipart: max-file-size: 10MB # ???????5MB max-request-size: 10MB # ???????5MB +file: + upload-dir: uploads + + #?? server: port: 8083 @@ -19,3 +23,9 @@ worker: # ????ID (0~31) datacenter: id: 1 + +# MyBatis-Plus?? +mybatis-plus: + mapper-locations: classpath:mapper/*.xml + configuration: + map-underscore-to-camel-case: true diff --git a/mydatabase.db b/mydatabase.db index e69de29..9e1c3e8 100644 Binary files a/mydatabase.db and b/mydatabase.db differ diff --git a/sql/data.sql b/sql/data.sql new file mode 100644 index 0000000..2a0d708 --- /dev/null +++ b/sql/data.sql @@ -0,0 +1,36 @@ +-- 用户表 +CREATE TABLE IF NOT EXISTS user ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT NOT NULL UNIQUE, + password TEXT NOT NULL, + email TEXT, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP +); + +-- Markdown文件表 +CREATE TABLE IF NOT EXISTS markdown_file ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + title TEXT NOT NULL, + file_name TEXT NOT NULL, + content TEXT NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE CASCADE + ); + +-- 图片表 +CREATE TABLE IF NOT EXISTS image ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + markdown_id INTEGER, + original_name TEXT NOT NULL, + stored_name TEXT NOT NULL UNIQUE, + url TEXT NOT NULL, + size INTEGER NOT NULL, + content_type TEXT NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE CASCADE, + FOREIGN KEY (markdown_id) REFERENCES markdown_file(id) ON DELETE SET NULL + ); \ No newline at end of file