跳转至

任务 CRUD 业务实现

把前面学的分层、校验、统一响应、MyBatis-Plus 串起来,实现任务的增删改查。

接口一览

方法 路径 作用
GET /tasks?current=1&size=10&status=0 分页查询当前用户的任务
POST /tasks 新建任务
PUT /tasks/{id} 修改任务
DELETE /tasks/{id} 删除任务(逻辑删除)

业务实现

完整的 Service 实现——注意每个方法里的归属校验

package com.javaglm.task.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.javaglm.task.common.BizException;
import com.javaglm.task.dto.TaskRequest;
import com.javaglm.task.entity.Task;
import com.javaglm.task.mapper.TaskMapper;
import com.javaglm.task.service.TaskService;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;

/**
 * 任务服务实现(第 28 章)。
 * 继承 ServiceImpl<TaskMapper, Task>:自动拥有 CRUD,Mapper 也自动注入。
 * 业务逻辑写在这里(校验归属、组装数据),Controller 保持"瘦"。
 */
@Service
public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task> implements TaskService {

    @Override
    public IPage<Task> pageTasks(long current, long size, Long userId, Integer status) {
        LambdaQueryWrapper<Task> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Task::getUserId, userId);
        if (status != null) {
            wrapper.eq(Task::getStatus, status);
        }
        wrapper.orderByDesc(Task::getCreateTime);
        return this.page(new Page<>(current, size), wrapper);
    }

    @Override
    public Task createTask(TaskRequest req, Long userId) {
        Task task = new Task();
        task.setTitle(req.getTitle());
        task.setDescription(req.getDescription());
        task.setStatus(req.getStatus() == null ? 0 : req.getStatus());
        task.setUserId(userId);
        task.setCreateTime(LocalDateTime.now());
        task.setUpdateTime(LocalDateTime.now());
        this.save(task);
        return task;
    }

    @Override
    public Task updateTask(Long id, TaskRequest req, Long userId) {
        Task task = this.getById(id);
        if (task == null || !task.getUserId().equals(userId)) {
            throw new BizException(404, "任务不存在或无权操作");
        }
        task.setTitle(req.getTitle());
        task.setDescription(req.getDescription());
        if (req.getStatus() != null) {
            task.setStatus(req.getStatus());
        }
        task.setUpdateTime(LocalDateTime.now());
        this.updateById(task);
        return task;
    }

    @Override
    public void deleteTask(Long id, Long userId) {
        Task task = this.getById(id);
        if (task == null || !task.getUserId().equals(userId)) {
            throw new BizException(404, "任务不存在或无权操作");
        }
        this.removeById(id);   // @TableLogic:实际执行逻辑删除
    }
}

关键点:

  • pageTasks:用 LambdaQueryWrapperuser_id = ?(只查自己的)、可选 status、按创建时间倒序,调 this.page(...) 分页。
  • createTask:组装 Task 对象(userId 来自 JWT,不是前端传的),this.save(task) 插入。
  • updateTask:先 getById 查出来,校验 userId 归属(防越权改别人任务),再更新。
  • deleteTask:同样校验归属,this.removeById 逻辑删除。

Controller

接参数、调 Service、包 Result:

package com.javaglm.task.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.javaglm.task.common.Result;
import com.javaglm.task.dto.TaskRequest;
import com.javaglm.task.entity.Task;
import com.javaglm.task.service.TaskService;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;

/**
 * 任务接口(第 28 章):增删改查 + 分页。
 * 注意 userId 来自 JWT 拦截器放进请求上下文的值(不是前端传的,防伪造)。
 */
@RestController
@RequestMapping("/tasks")
public class TaskController {

    private final TaskService taskService;

    public TaskController(TaskService taskService) {
        this.taskService = taskService;
    }

    @GetMapping
    public Result<IPage<Task>> list(
            @RequestParam(defaultValue = "1") long current,
            @RequestParam(defaultValue = "10") long size,
            @RequestParam(required = false) Integer status,
            HttpServletRequest request) {
        Long userId = (Long) request.getAttribute("userId");
        return Result.success(taskService.pageTasks(current, size, userId, status));
    }

    @PostMapping
    public Result<Task> create(@RequestBody @Valid TaskRequest req,
                               HttpServletRequest request) {
        Long userId = (Long) request.getAttribute("userId");
        return Result.success(taskService.createTask(req, userId));
    }

    @PutMapping("/{id}")
    public Result<Task> update(@PathVariable Long id,
                               @RequestBody @Valid TaskRequest req,
                               HttpServletRequest request) {
        Long userId = (Long) request.getAttribute("userId");
        return Result.success(taskService.updateTask(id, req, userId));
    }

    @DeleteMapping("/{id}")
    public Result<?> delete(@PathVariable Long id, HttpServletRequest request) {
        Long userId = (Long) request.getAttribute("userId");
        taskService.deleteTask(id, userId);
        return Result.success();
    }
}

注意 userId 的来源:

Long userId = (Long) request.getAttribute("userId");

不是前端传的,而是 JWT 拦截器(第 29 章)解析 token 后放进请求上下文的。这样绝对防伪造——前端没法假装自己是别人。

完整调用链

sequenceDiagram
    participant F as 前端
    participant I as JwtInterceptor
    participant C as TaskController
    participant S as TaskServiceImpl
    participant DB as MySQL
    F->>I: GET /tasks (带 Authorization)
    I->>I: 解析 token,取出 userId
    I->>C: 放行,userId 存入 request
    C->>S: pageTasks(current,size,userId,status)
    S->>DB: SELECT ... WHERE user_id=? AND deleted=0 LIMIT
    DB-->>S: 数据
    S-->>C: IPage<Task>
    C-->>F: Result.success(page)

用 Postman 测试

  1. 先调 POST /auth/login 拿到 token;
  2. 后续请求 Header 加 Authorization: Bearer <token>
  3. POST /tasks,body:{"title":"学Java","status":0}
  4. GET /tasks 看列表。

:octicons-arrow-left-16: 上一章:数据库设计实战 | 下一章:JWT 认证