Blog Platform Image Upload and Category Management Implementation

Bug Fixes

Article Archive Query

SELECT 
  FROM_UNIXTIME(create_date/1000,'%Y') AS year, 
  FROM_UNIXTIME(create_date/1000,'%m') AS month,
  COUNT(*) AS count 
FROM ms_article 
GROUP BY year, month

File Upload Functionality

API Specification

Endpoint: /upload Method: POST Parameters:

Parameter Type Description
image file Uploaded file

Response Format:

{
  "success": true,
  "code": 200,
  "msg": "success",
  "data": "https://static.cherr.com/aa.png"
}

Maven Dependency

<dependency>
  <groupId>com.qiniu</groupId>
  <artifactId>qiniu-java-sdk</artifactId>
  <version>[7.13.0, 7.13.99]</version>
</dependency>

Upload Controller Implementation

The controller generates unique filenames using UUID to prevent conflicts:

  1. UUID.randomUUID().toString() creates a universal unique identifier
  2. StringUtils.substringAfterLast(originalFilename, ".") extracts file extension
  3. Combines both to form "unique-id.extension" filename
@RestController
@RequestMapping("upload")
public class FileUploadController {
    
    @Autowired
    private CloudStorageUtil cloudStorage;
    
    @PostMapping
    public Result handleUpload(@RequestParam("image") MultipartFile uploadedFile) {
        String originalName = uploadedFile.getOriginalFilename();
        String uniqueFileName = generateUniqueName(originalName);
        
        boolean status = cloudStorage.storeFile(uploadedFile, uniqueFileName);
        if (status) {
            return Result.success(CloudStorageUtil.baseUrl + uniqueFileName);
        }
        return Result.fail(20001, "Upload failed");
    }
    
    private String generateUniqueName(String fileName) {
        return UUID.randomUUID().toString() + "." + 
               StringUtils.substringAfterLast(fileName, ".");
    }
}

Cloud Storage Configuration

Application Properties

spring.servlet.multipart.max-request-size=20MB
spring.servlet.multipart.max-file-size=2MB

Storage Utility Class

@Component
public class CloudStorageUtil {
    
    public static final String baseUrl = "http://sbf25hzn6.hb-bkt.clouddn.com/";
    
    @Value("")
    private String accessKey;
    
    @Value("")
    private String secretKey;
    
    private static final String BUCKET_NAME = "cherriesovo-blog";
    
    public boolean storeFile(MultipartFile file, String fileName) {
        Configuration config = new Configuration(Region.huabei());
        UploadManager manager = new UploadManager(config);
        
        try {
            byte[] fileData = file.getBytes();
            Auth authentication = Auth.create(accessKey, secretKey);
            String token = authentication.uploadToken(BUCKET_NAME);
            
            Response response = manager.put(fileData, fileName, token);
            JSON.parseObject(response.bodyString(), DefaultPutRet.class);
            return true;
        } catch (Exception exception) {
            exception.printStackTrace();
        }
        return false;
    }
}

Navigation and Content Classification

Category Listing

API Details

Endpoint: /categorys/detail Method: GET

Response Example:

{
  "success": true,
  "code": 200,
  "msg": "success",
  "data": [
    {
      "id": 1,
      "avatar": "/static/category/front.png",
      "categoryName": "Frontend",
      "description": "Frontend development topics"
    }
  ]
}

Data Transfer Object

@Data
public class CategoryDto {
    private Long id;
    private String avatar;
    private String categoryName;
    private String description;
}

Controller Layer

@RestController
@RequestMapping("categorys")
public class CategoryController {
    
    @GetMapping("detail")
    public Result getAllCategories() {
        return categoryService.retrieveAllWithDetails();
    }
}

Service Implementation

@Override
public Result retrieveAllWithDetails() {
    List<Category> categories = categoryMapper.selectList(new LambdaQueryWrapper<>());
    return Result.success(transformToDtoList(categories));
}

Tag Management

API Details

Endpoint: /tags/detail Method: GET

Response Example:

{
  "success": true,
  "code": 200,
  "msg": "success",
  "data": [
    {
      "id": 5,
      "tagName": "springboot",
      "avatar": "/static/tag/java.png"
    }
  ]
}

Tag DTO

@Data
public class TagDto {
    private Long id;
    private String tagName;
    private String avatar;
}

Tag Controller

@RestController
@RequestMapping("tags")
public class TagController {
    
    @GetMapping("detail")
    public Result getAllTags() {
        return tagService.retrieveAllWithTagDetails();
    }
}

Tag Service

@Override
public Result retrieveAllWithTagDetails() {
    LambdaQueryWrapper<Tag> wrapper = new LambdaQueryWrapper<>();
    List<Tag> tags = tagMapper.selectList(wrapper);
    return Result.success(convertToTagDtoList(tags));
}

Category-Based Article Filtering

API Specification

Endpoint: /category/detail/{id} Method: GET Parameters:

Parameter Type Description
id path Category identifier

Controller Implementation

@GetMapping("detail/{id}")
public Result getCategoryById(@PathVariable("id") Long categoryId) {
    return categoryService.findByIdWithDetails(categoryId);
}

Service Logic

@Override
public Result findByIdWithDetails(Long id) {
    Category category = categoryMapper.selectById(id);
    CategoryDto dto = convertToDto(category);
    return Result.success(dto);
}

Enhanced Article Pagination

Modified pagination service to support category filtering:

@Override
public List<ArticleDto> paginateArticles(ArticleQueryParams params) {
    Page<Article> page = new Page<>(params.getPageNum(), params.getPageSize());
    LambdaQueryWrapper<Article> wrapper = new LambdaQueryWrapper<>();
    
    if (params.getCategoryId() != null) {
        wrapper.eq(Article::getCategoryId, params.getCategoryId());
    }
    
    wrapper.orderByDesc(Article::getPriority, Article::getCreatedDate);
    Page<Article> resultPage = articleMapper.selectPage(page, wrapper);
    
    List<Article> articles = resultPage.getRecords();
    return transformToArticleDtoList(articles, true, false, true);
}

Query Parameters Structure

@Data
public class ArticleQueryParams {
    private int pageNum = 1;
    private int pageSize = 10;
    private Long categoryId;
    private Long tagId;
}

Tag-Based Article Filtering

API Details

Endpoint: /tags/detail/{id} Method: GET Parameters:

Parameter Type Description
id path Tag identifier

Controller Code

@GetMapping("detail/{id}")
public Result getTagById(@PathVariable("id") Long tagId) {
    return tagService.findTagWithDetails(tagId);
}

Service Implementation

@Override
public Result findTagWithDetails(Long id) {
    Tag tag = tagMapper.selectById(id);
    TagDto dto = convertToTagDto(tag);
    return Result.success(dto);
}

Extended Article Query Logic

Enhanced article service to support tag-based filtering through junction table:

@Override
public List<ArticleDto> paginateArticles(ArticleQueryParams params) {
    Page<Article> page = new Page<>(params.getPageNum(), params.getPageSize());
    LambdaQueryWrapper<Article> wrapper = new LambdaQueryWrapper<>();
    
    if (params.getCategoryId() != null) {
        wrapper.eq(Article::getCategoryId, params.getCategoryId());
    }
    
    List<Long> articleIds = new ArrayList<>();
    if (params.getTagId() != null) {
        LambdaQueryWrapper<ArticleTag> tagWrapper = new LambdaQueryWrapper<>();
        tagWrapper.eq(ArticleTag::getTagId, params.getTagId());
        
        List<ArticleTag> articleTags = articleTagMapper.selectList(tagWrapper);
        for (ArticleTag relation : articleTags) {
            articleIds.add(relation.getArticleId());
        }
        
        if (!articleIds.isEmpty()) {
            wrapper.in(Article::getId, articleIds);
        }
    }
    
    wrapper.orderByDesc(Article::getPriority, Article::getCreatedDate);
    Page<Article> resultPage = articleMapper.selectPage(page, wrapper);
    
    List<Article> articles = resultPage.getRecords();
    return transformToArticleDtoList(articles, true, false, true);
}

Tags: blog-development file-upload cloud-storage category-management tag-filtering

Posted on Sun, 14 Jun 2026 16:28:07 +0000 by sampledformat