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),启动后访问:
- Swagger UI: http://localhost:5001/swagger-ui.html
- API 文档: http://localhost:5001/v3/api-docs
