Skip to content

API 开发指南

作者:唐亚峰 | battcn
字数统计:1.3k 字

学习目标

掌握 Wemirr Platform 后端 API 开发的标准流程和最佳实践

开发流程

设计接口 → 创建实体 → 编写 Mapper → 实现 Service → 编写 Controller → 测试验证

完整示例

以「文章管理」为例,演示完整的 CRUD 开发流程。

1. 数据库设计

sql
CREATE TABLE `t_article` (
    `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
    `title` varchar(200) NOT NULL COMMENT '标题',
    `content` text COMMENT '内容',
    `author` varchar(50) DEFAULT NULL COMMENT '作者',
    `status` tinyint DEFAULT '0' COMMENT '状态:0-草稿 1-发布',
    `view_count` int DEFAULT '0' COMMENT '浏览量',
    `tenant_id` bigint DEFAULT NULL COMMENT '租户ID',
    `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    `created_by` bigint DEFAULT NULL COMMENT '创建人',
    `updated_by` bigint DEFAULT NULL COMMENT '更新人',
    PRIMARY KEY (`id`),
    KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB COMMENT='文章表';

2. 创建实体类

java
package com.wemirr.demo.entity;

import com.baomidou.mybatisplus.annotation.*;
import com.wemirr.framework.db.entity.SuperEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;

@Data
@EqualsAndHashCode(callSuper = true)
@TableName("t_article")
public class Article extends SuperEntity<Long> {

    /** 标题 */
    private String title;

    /** 内容 */
    private String content;

    /** 作者 */
    private String author;

    /** 状态:0-草稿 1-发布 */
    private Integer status;

    /** 浏览量 */
    private Integer viewCount;

    /** 租户ID */
    private Long tenantId;
}

3. 创建 DTO/VO

java
// DTO - 数据传输对象(接收前端参数)
package com.wemirr.demo.dto;

import jakarta.validation.constraints.*;
import lombok.Data;

@Data
public class ArticleDTO {
    
    @NotBlank(message = "标题不能为空")
    @Size(max = 200, message = "标题最多200个字符")
    private String title;

    @NotBlank(message = "内容不能为空")
    private String content;

    @Size(max = 50, message = "作者最多50个字符")
    private String author;

    @Min(value = 0, message = "状态值无效")
    @Max(value = 1, message = "状态值无效")
    private Integer status;
}

// VO - 视图对象(返回前端数据)
package com.wemirr.demo.vo;

import lombok.Data;
import java.time.LocalDateTime;

@Data
public class ArticleVO {
    private Long id;
    private String title;
    private String content;
    private String author;
    private Integer status;
    private String statusName;
    private Integer viewCount;
    private LocalDateTime createTime;
    private String createdByName;
}

// PageQuery - 分页查询参数
package com.wemirr.demo.dto;

import com.wemirr.framework.db.page.PageRequest;
import lombok.Data;
import lombok.EqualsAndHashCode;

@Data
@EqualsAndHashCode(callSuper = true)
public class ArticlePageQuery extends PageRequest {
    
    /** 标题(模糊查询) */
    private String title;
    
    /** 状态 */
    private Integer status;
    
    /** 作者 */
    private String author;
}

4. 创建 Mapper

java
package com.wemirr.demo.mapper;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.wemirr.framework.db.mybatisplus.ext.SuperMapper;
import com.wemirr.demo.entity.Article;
import com.wemirr.demo.dto.ArticlePageQuery;
import com.wemirr.demo.vo.ArticleVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

@Mapper
public interface ArticleMapper extends SuperMapper<Article> {
    
    /**
     * 分页查询文章列表
     */
    IPage<ArticleVO> selectArticlePage(IPage<?> page, @Param("query") ArticlePageQuery query);
}
xml
<!-- mapper/ArticleMapper.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wemirr.demo.mapper.ArticleMapper">

    <select id="selectArticlePage" resultType="com.wemirr.demo.vo.ArticleVO">
        SELECT 
            a.id,
            a.title,
            a.content,
            a.author,
            a.status,
            CASE a.status WHEN 0 THEN '草稿' WHEN 1 THEN '已发布' END AS status_name,
            a.view_count,
            a.create_time,
            u.nick_name AS created_by_name
        FROM t_article a
        LEFT JOIN sys_user u ON a.created_by = u.id
        <where>
            <if test="query.title != null and query.title != ''">
                AND a.title LIKE CONCAT('%', #{query.title}, '%')
            </if>
            <if test="query.status != null">
                AND a.status = #{query.status}
            </if>
            <if test="query.author != null and query.author != ''">
                AND a.author LIKE CONCAT('%', #{query.author}, '%')
            </if>
        </where>
        ORDER BY a.create_time DESC
    </select>

</mapper>

5. 创建 Service

java
// Service 接口
package com.wemirr.demo.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.wemirr.framework.db.mybatisplus.ext.SuperService;
import com.wemirr.demo.entity.Article;
import com.wemirr.demo.dto.ArticleDTO;
import com.wemirr.demo.dto.ArticlePageQuery;
import com.wemirr.demo.vo.ArticleVO;

public interface ArticleService extends SuperService<Article> {
    
    /**
     * 分页查询
     */
    IPage<ArticleVO> page(ArticlePageQuery query);
    
    /**
     * 获取详情
     */
    ArticleVO getDetail(Long id);
    
    /**
     * 创建文章
     */
    void create(ArticleDTO dto);
    
    /**
     * 更新文章
     */
    void update(Long id, ArticleDTO dto);
    
    /**
     * 发布文章
     */
    void publish(Long id);
}
java
// Service 实现
package com.wemirr.demo.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.wemirr.framework.db.mybatisplus.ext.SuperServiceImpl;
import com.wemirr.framework.commons.exception.CheckedException;
import com.wemirr.demo.entity.Article;
import com.wemirr.demo.dto.ArticleDTO;
import com.wemirr.demo.dto.ArticlePageQuery;
import com.wemirr.demo.vo.ArticleVO;
import com.wemirr.demo.mapper.ArticleMapper;
import com.wemirr.demo.service.ArticleService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class ArticleServiceImpl extends SuperServiceImpl<ArticleMapper, Article> 
    implements ArticleService {
    
    @Override
    public IPage<ArticleVO> page(ArticlePageQuery query) {
        return baseMapper.selectArticlePage(query.buildPage(), query);
    }
    
    @Override
    public ArticleVO getDetail(Long id) {
        Article article = getById(id);
        if (article == null) {
            throw CheckedException.notFound("文章不存在");
        }
        // 增加浏览量
        lambdaUpdate()
            .eq(Article::getId, id)
            .setSql("view_count = view_count + 1")
            .update();
        return BeanUtil.copyProperties(article, ArticleVO.class);
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void create(ArticleDTO dto) {
        Article article = BeanUtil.copyProperties(dto, Article.class);
        article.setViewCount(0);
        if (article.getStatus() == null) {
            article.setStatus(0); // 默认草稿
        }
        save(article);
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void update(Long id, ArticleDTO dto) {
        Article article = getById(id);
        if (article == null) {
            throw CheckedException.notFound("文章不存在");
        }
        BeanUtil.copyProperties(dto, article);
        updateById(article);
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void publish(Long id) {
        boolean updated = lambdaUpdate()
            .eq(Article::getId, id)
            .set(Article::getStatus, 1)
            .update();
        if (!updated) {
            throw CheckedException.badRequest("发布失败");
        }
    }
}

6. 创建 Controller

java
package com.wemirr.demo.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.wemirr.framework.commons.entity.Result;
import com.wemirr.framework.db.log.annotation.SysLog;
import com.wemirr.demo.dto.ArticleDTO;
import com.wemirr.demo.dto.ArticlePageQuery;
import com.wemirr.demo.service.ArticleService;
import com.wemirr.demo.vo.ArticleVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.springframework.web.bind.annotation.*;

@Tag(name = "文章管理")
@RestController
@RequestMapping("/articles")
@RequiredArgsConstructor
public class ArticleController {

    private final ArticleService articleService;

    @Operation(summary = "分页查询")
    @GetMapping
    @SaCheckPermission("article:list")
    public Result<IPage<ArticleVO>> page(ArticlePageQuery query) {
        return Result.success(articleService.page(query));
    }

    @Operation(summary = "获取详情")
    @GetMapping("/{id}")
    @SaCheckPermission("article:view")
    public Result<ArticleVO> getById(@PathVariable Long id) {
        return Result.success(articleService.getDetail(id));
    }

    @Operation(summary = "新增文章")
    @PostMapping
    @SaCheckPermission("article:add")
    @SysLog(value = "新增文章")
    public Result<Void> create(@RequestBody @Valid ArticleDTO dto) {
        articleService.create(dto);
        return Result.success();
    }

    @Operation(summary = "更新文章")
    @PutMapping("/{id}")
    @SaCheckPermission("article:edit")
    @SysLog(value = "更新文章")
    public Result<Void> update(@PathVariable Long id, @RequestBody @Valid ArticleDTO dto) {
        articleService.update(id, dto);
        return Result.success();
    }

    @Operation(summary = "删除文章")
    @DeleteMapping("/{id}")
    @SaCheckPermission("article:delete")
    @SysLog(value = "删除文章")
    public Result<Void> delete(@PathVariable Long id) {
        articleService.removeById(id);
        return Result.success();
    }

    @Operation(summary = "发布文章")
    @PostMapping("/{id}/publish")
    @SaCheckPermission("article:publish")
    @SysLog(value = "发布文章")
    public Result<Void> publish(@PathVariable Long id) {
        articleService.publish(id);
        return Result.success();
    }
}

常用注解

校验注解

注解说明
@NotNull不能为 null
@NotBlank不能为空白字符串
@NotEmpty集合不能为空
@Size长度/大小范围
@Min / @Max最小/最大值
@Pattern正则匹配
@Email邮箱格式

权限注解

java
// Sa-Token 权限注解
@SaCheckPermission("article:add")      // 需要指定权限
@SaCheckRole("admin")                   // 需要指定角色
@SaCheckLogin                           // 需要登录

// 组合权限
@SaCheckPermission(value = {"article:add", "article:edit"}, mode = SaMode.OR)

日志注解

java
@SysLog(value = "操作描述", type = LogType.OPERATION)

Swagger 注解

java
@Tag(name = "模块名称")
@Operation(summary = "接口描述")
@Parameter(description = "参数描述")
@Schema(description = "字段描述")

API 文档

项目集成了 SpringDoc(Swagger 3),启动后访问:

下一步