Skip to content

SpringBoot练手项目 TodoTask App

介绍

在学习完瑞吉外卖后一直没有进行实战练习,故此借助周末时间开发一套简单的 TodoTask AppAPI,原型是参考时光系App的,我们只开发App需要用到的API接口,不开发具体的客户端,项目以练习SpringBoot框架为主要目的。

业务流程

  • 1、用户注册,注册成功后用用户密码去登录,登陆成功后返回token,后续所有请求都需要在headers中携带token进行验证

  • 2、用户可以创建多个任务,并且可以更改当前任务状态(完结、未完成),可以设置任务执行时间,可以关联分类,可以关联多张照片,在创建任务时如果传入数据包含了子任务则自动创建对应的子任务

  • 3、一个任务可以包含多个子任务,用户可以针对一个任务创建多个子任务,用户可以更改子任务状态(完结、未完成),如果一个任务的子任务全部都完成了,默认完成该任务

  • 4、一个任务可以关联多个分类,一个分类也可以关联多个任务

创建数据库

API列表

  • 用户注册接口

  • 编辑用户信息

  • 用户登录接口

  • 创建任务(如果填写了子任务则在创建任务的同时创建好子任务)接口

  • 创建子任务接口

  • 编辑任务接口

  • 编辑子任务接口

  • 删除任务(同步删除所有子任务)接口

  • 删除子任务接口

  • 图片公共上传接口

  • 公共图片下载接口

  • 分页查询任务列表接口

  • 根据任务id分页查询子任务接口

  • 根据分类id查询所有任务接口

  • 根据任务id查询所有的分类接口

创建项目

勾选必要的依赖,这里我们使用的SpringBoot版本是2.7.12,待会我们在pom文件中修改版本

创建项目后需要确认的三件事

1、setting\maven查看使用的仓库是不是本地仓库

2、setting\maven\runner查看运行的java版本

3、project structure查看jdk版本

项目初始化

修改pom文件

我们修改SpringBoot版本为2.4.5pom.xml

xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.atguigu.task</groupId>
    <artifactId>task-app</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>task-app</name>
    <description>todo task App接口服务</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

修改启动类

引入Slf4j包并且添加项目启动提示

com.atguigu.task.taskapp.TaskAppApplication

java
package com.atguigu.task.taskapp;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@Slf4j
@SpringBootApplication
public class TaskAppApplication {

    public static void main(String[] args) {
        SpringApplication.run(TaskAppApplication.class, args);
        log.info("项目启动成功....");
    }
}

整合mybatis-plus并且测试

引入数据库驱动依赖以及mybatis-plus依赖

xml
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.2.0</version>
</dependency>
xml
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.17</version>
</dependency>
xml
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.23</version>
</dependency>

配置application.yml

yml
server:
  port: 3000
spring:
  application:
    name: task_app
  datasource:
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/task_app?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
      username: root
      password: 123456
mybatis-plus:
  configuration:
    #在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      id-type: AUTO

用户模块

SpringBoot使用jwt登录,引入jwt依赖

pom.xml

xml
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

添加jwt工具类

com.atguigu.task.taskapp.utils.JwtUtil

java
package com.atguigu.task.taskapp.utils;

import com.atguigu.task.taskapp.entity.User;
import io.jsonwebtoken.*;
import lombok.extern.slf4j.Slf4j;

import java.util.Date;
import java.util.UUID;

@Slf4j
public class JwtUtil {
    // 设置 token 过期时间(单位:毫秒):目前为 6000 秒
    private static final long ttl = 6000 * 1000;
    // Signature 签名
    private static final String signature = "Zhao-Chao";


    public static String createToken(User user) {
        JwtBuilder jwtBuilder = Jwts.builder();

        return jwtBuilder
                .setHeaderParam("typ", "JWT")
                .setHeaderParam("alg", "HS256")
                .setId(UUID.randomUUID().toString())
                .setSubject(user.getUserName())
                .setExpiration(new Date(System.currentTimeMillis() + ttl))
                .setIssuer("Wu-Yikun")
                .setIssuedAt(new Date())
                .claim("userId", user.getId())
                .claim("role", "user")
                .signWith(SignatureAlgorithm.HS256, signature)
                .compact();
    }

    /**
     * 校验token
     * @param token
     * @return
     */
    public static boolean parseToken(String token) {
        if (token == null) {
            return false;
        }
        JwtParser jwtParser = Jwts.parser();
        try {
            jwtParser.setSigningKey(signature).parseClaimsJws(token);
        } catch (Exception e) {
            return false;
        }
        return true;
    }

    /**
     * 提取token信息
     * @param token
     */
    public static Claims tokenToOut(String token) {
        Claims claims = Jwts.parser()
                .setSigningKey(signature)
                .parseClaimsJws(token)
                .getBody();
        return claims;
    }

}

com.atguigu.task.taskapp.entity.User

java
package com.atguigu.task.taskapp.entity;

import lombok.Data;

import java.io.Serializable;

@Data
public class User implements Serializable {
    private static final long serialVersionUID = 1L;

    // 主键,用户id
    private long id;

    // 用户名称
    private String userName;

    // 手机号码
    private String phone;

    // 性别
    private Integer sex;

    // 身份证
    private String idNumber;

    // 头像
    private String avatar;

    // 状态0表示禁用 1表示启用
    private Integer status;

    // 密码
    private String password;
}

com.atguigu.task.taskapp.mapper.UserMapper

java
package com.atguigu.task.taskapp.mapper;

import com.atguigu.task.taskapp.entity.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper extends BaseMapper<User> {

}

com.atguigu.task.taskapp.service.UserService

java
package com.atguigu.task.taskapp.mapper;

import com.atguigu.task.taskapp.entity.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper extends BaseMapper<User> {

}

com.atguigu.task.taskapp.service.impl.UserServiceImpl

java
package com.atguigu.task.taskapp.service.impl;

import com.atguigu.task.taskapp.entity.User;
import com.atguigu.task.taskapp.mapper.UserMapper;
import com.atguigu.task.taskapp.service.UserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}

用户登录注册以及修改用户信息 com.atguigu.task.taskapp.controller.UserController

java
package com.atguigu.task.taskapp.controller;

import com.atguigu.task.taskapp.common.R;
import com.atguigu.task.taskapp.dto.UserDto;
import com.atguigu.task.taskapp.entity.User;
import com.atguigu.task.taskapp.service.UserService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.DigestUtils;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;

import java.util.HashMap;
import java.util.Map;

import static com.atguigu.task.taskapp.utils.JwtUtil.createToken;

@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;


    /**
     * 用户注册
     * @param request
     * @param user
     * @return
     */
    @PostMapping("/register")
    public R<String> register(HttpServletRequest request, @RequestBody User user){

        log.info("user{}",user);

        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        // 通过传入的userName查询是否可以匹配到数据
        queryWrapper.eq(user.getUserName() != null,User::getUserName,user.getUserName());

        User user1 = userService.getOne(queryWrapper);

        // 如果用户已存在直接返回已注册信息
        if(user1 != null){
            return R.error("该用户已注册,请直接登录");
        }

        // 进行密码md5加密
        String password = user.getPassword();
        password = DigestUtils.md5DigestAsHex(password.getBytes());

        user.setPassword(password);

        userService.save(user);

        return R.success("注册成功");
    }

    /**
     *
     * @param request
     * @param user
     * @return
     */
    @PostMapping("/login")
    public R<User> login(HttpServletRequest request, @RequestBody User user){
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(user!=null,User::getUserName,user.getUserName());
        User user1 = userService.getOne(queryWrapper);

        // 用户是否存在
        if(user1==null){
            return R.error("用户不存在");
        }

        // 获取md5加密后密码
        String password = DigestUtils.md5DigestAsHex(user.getPassword().getBytes());

        // 比较密码是否一致
        if(!user1.getPassword().equals(password)){
            return R.error("密码不一致");
        }

        // 账号禁用
        if(user1.getStatus() == 0){
            return R.error("账号已经禁用");
        }

        // 登录成功,生成token
        String token = createToken(user1.getUserName());

        return R.success(user1).add("token",token);
    }

}

实现请求拦截功能

com.atguigu.task.taskapp.TaskAppApplication

java
package com.atguigu.task.taskapp;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;

@Slf4j
@SpringBootApplication
@ServletComponentScan
public class TaskAppApplication {

    public static void main(String[] args) {
        SpringApplication.run(TaskAppApplication.class, args);
        log.info("项目启动成功....");
    }
}

com.atguigu.task.taskapp.filter.LoginCheckFilter

java
package com.atguigu.task.taskapp.filter;

import com.alibaba.fastjson.JSON;
import com.atguigu.task.taskapp.common.R;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.AntPathMatcher;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

import static com.atguigu.task.taskapp.utils.JwtUtil.parseToken;
import static com.atguigu.task.taskapp.utils.JwtUtil.tokenToOut;

/**
 * 检测当前访问用户是否已完成了登录
 */
@WebFilter(filterName = "loginCheckFilter",urlPatterns = "/*")
@Slf4j
public class LoginCheckFilter implements Filter{
    // 路径匹配器
    public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        /**
         * 1、获取本次请求的url
         * 2、判断本次请求是否需要处理
         * 3、如果不需要处理则直接放行
         * 4、判断登录状态,从header中获取token信息,并进行校验
         * 5、如果token失效则返回登陆结果
         */

        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        // 设置字符集编码,避免返回中文时乱码
        response.setContentType("text/html;charset=UTF-8");

        // 从头部信息中获取token令牌
        String token = request.getHeader("token");

        // 获取本次请求地址
        String requestURI = request.getRequestURI();

        log.info("拦截到请求:{}",requestURI);

        // 放行白名单
        String[] urls = new String[]{
                "/user/login",
                "/user/register"
                //"/backend/**"
        };

        // 判断请求地址是否在白名单中
        boolean check = check(urls,requestURI);

        // 白名单放行
        if(check){
            log.info("本次请求: {},白名单不需要处理",requestURI);
            filterChain.doFilter(request,response);
            return;
        }

        log.info("token: {}",token);

        // token不存在直接返回登陆失败
        if(token == null){
            response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
            return;
        }

        try {
            // 校验token
            boolean checkToken = parseToken(token);
            Claims claims = tokenToOut(token);
            Integer userId = (Integer) claims.get("userId");
            String userName = claims.getSubject();

            // 校验token通过
            if(!checkToken){
                log.info("token校验失败");
                response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
                return;
            }

            log.info("用户已登陆,用户id为{},用户名称为{}",userId,userName);
            filterChain.doFilter(request,response);
            return;
        } catch (Exception e) {
            // token已过期
            if(e.getMessage().contains("JWT expired")){
                response.getWriter().write(JSON.toJSONString(R.error("token已过期,请重新登录")));
            }
        }

    }

    /**
     * 路径匹配函数
     * @param urls 传入的路径字符串数组
     * @param requestURI 当前的请求地址
     * @return
     */
    public boolean check(String[] urls,String requestURI){
        for(String url: urls){
            boolean match = PATH_MATCHER.match(url,requestURI);
            if(match){
                return true;
            }
        }
        return false;
    }
}

分类模块

每一个任务都可以关联多个分类,每个分类也可以关联多个任务,接下来我们需要创建分类模块的增删改查

com.atguigu.task.taskapp.entity.Category

java
package com.atguigu.task.taskapp.entity;

import lombok.Data;

@Data
public class Category {
    private static final long serialVersionUID = 1L;

    // 主键,用户id
    private long id;

    // 用户名称
    private String categoryName;
}

com.atguigu.task.taskapp.mapper.CategoryMapper

java
package com.atguigu.task.taskapp.mapper;

import com.atguigu.task.taskapp.entity.Category;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface CategoryMapper extends BaseMapper<Category> {
}
java
package com.atguigu.task.taskapp.service;

import com.atguigu.task.taskapp.entity.Category;
import com.baomidou.mybatisplus.extension.service.IService;

public interface CategoryService extends IService<Category> {
}

com.atguigu.task.taskapp.service.impl.CategoryServiceImpl

java
package com.atguigu.task.taskapp.service.impl;

import com.atguigu.task.taskapp.entity.Category;
import com.atguigu.task.taskapp.mapper.CategoryMapper;
import com.atguigu.task.taskapp.service.CategoryService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;

@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {
}

com.atguigu.task.taskapp.controller.CategoryController

java
package com.atguigu.task.taskapp.controller;

import com.atguigu.task.taskapp.common.R;
import com.atguigu.task.taskapp.entity.Category;
import com.atguigu.task.taskapp.service.CategoryService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;

@RestController
@Slf4j
@RequestMapping("/category")
public class CategoryController {

    @Autowired
    private CategoryService categoryService;

    /**
     * 添加分类
     * @param request
     * @param category
     * @return
     */
    @PostMapping("/add")
    public R<String> add(HttpServletRequest request, @RequestBody Category category){

        categoryService.save(category);

        return R.success("新增成功");
    }

    /**
     * 编辑分类
     * @param request
     * @param category
     * @return
     */
    @PostMapping("/edit")
    public R<String> edit(HttpServletRequest request,@RequestBody Category category){
        categoryService.updateById(category);

        return R.success("保存成功");
    }

    /**
     * 删除一条分类
     * @param id
     * @return
     */
    @DeleteMapping("/delete")
    public R<String> delete(@RequestParam long id){
        log.info("删除id:{}",id);
        categoryService.removeById(id);

        return R.success("删除成功");
    }

    /**
     * 分页查询分类列表
     * @param page
     * @param pageSize
     * @param name
     * @return
     */
    @GetMapping("/page")
    public R<Page> page(int page,int pageSize,String name){
        log.info("page = {},pageSize={},name={}",page,pageSize,name);

        // 构造分页器
        Page pageInfo = new Page(page,pageSize);

        // 构造条件构造器
        LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();
        // 添加一个过滤条件
        queryWrapper.like(StringUtils.hasText(name),Category::getCategoryName,name);

        categoryService.page(pageInfo,queryWrapper);

        return R.success(pageInfo);
    }
}

任务模块

任务模块是核心模块,业务流程如下:

创建任务的同时可以创建子任务以及关联多个分类,后台在接收到任务时,要将对应的子任务同步入库。

分别创建子任务表和任务分类中间表模块

子任务模块

com.atguigu.task.taskapp.entity.SubTask

java
package com.atguigu.task.taskapp.entity;

import lombok.Data;

import java.time.LocalDateTime;
import java.util.Date;

@Data
public class SubTask {
    private static final long serialVersionUID = 1L;

    // 主键,用户id
    private long id;

    // 任务执行时间
    private Date date;

    // 任务内容
    private String content;

    // 创建时间
    private LocalDateTime createTime;

    // 修改时间
    private LocalDateTime updateTime;

    // 创建用户id
    private long userId;

    // 任务id
    private long taskId;

    // 任务名称
    private String taskName;

    // 用户名称
    private String userName;

    // 任务状态 0未完成1已完成
    private Integer status;
}

com.atguigu.task.taskapp.mapper.SubTaskMapper

java
package com.atguigu.task.taskapp.mapper;

import com.atguigu.task.taskapp.entity.SubTask;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface SubTaskMapper extends BaseMapper<SubTask> {
}

com.atguigu.task.taskapp.service.SubTaskService

java
package com.atguigu.task.taskapp.service;

import com.atguigu.task.taskapp.entity.SubTask;
import com.baomidou.mybatisplus.extension.service.IService;

public interface SubTaskService extends IService<SubTask> {
}

com.atguigu.task.taskapp.service.impl.SubTaskServiceImpl

java
package com.atguigu.task.taskapp.service.impl;

import com.atguigu.task.taskapp.entity.SubTask;
import com.atguigu.task.taskapp.mapper.SubTaskMapper;
import com.atguigu.task.taskapp.service.SubTaskService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;

@Service
public class SubTaskServiceImpl extends ServiceImpl<SubTaskMapper, SubTask> implements SubTaskService {
}

任务分类模块

com.atguigu.task.taskapp.entity.TaskCategory

java
package com.atguigu.task.taskapp.entity;

import lombok.Data;

@Data
public class TaskCategory {
    private static final long serialVersionUID = 1L;

    // 主键,用户id
    private long id;

    // 任务id
    private long taskId;

    // 分类id
    private long categoryId;
}

com.atguigu.task.taskapp.mapper.TaskCategoryMapper

java
package com.atguigu.task.taskapp.mapper;

import com.atguigu.task.taskapp.entity.TaskCategory;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface TaskCategoryMapper extends BaseMapper<TaskCategory> {
}

com.atguigu.task.taskapp.service.TaskCategoryService

java
package com.atguigu.task.taskapp.service;

import com.atguigu.task.taskapp.entity.TaskCategory;
import com.baomidou.mybatisplus.extension.service.IService;

public interface TaskCategoryService extends IService<TaskCategory> {
}

com.atguigu.task.taskapp.service.impl.TaskCategoryServiceImpl

java
package com.atguigu.task.taskapp.service.impl;

import com.atguigu.task.taskapp.entity.TaskCategory;
import com.atguigu.task.taskapp.mapper.TaskCategoryMapper;
import com.atguigu.task.taskapp.service.TaskCategoryService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;

@Service
public class TaskCategoryServiceImpl extends ServiceImpl<TaskCategoryMapper, TaskCategory> implements TaskCategoryService {
}

任务模块

com.atguigu.task.taskapp.entity.Task

java
package com.atguigu.task.taskapp.entity;

import lombok.Data;

import java.time.LocalDateTime;
import java.util.Date;

@Data
public class Task {
    private static final long serialVersionUID = 1L;

    // 主键,任务id
    private long id;

    // 任务执行时间
    private Date date;

    // 任务内容
    private String content;

    // 创建时间
    private LocalDateTime createTime;

    // 修改时间
    private LocalDateTime updateTime;

    // 创建用户id
    private long userId;

    // 任务封面
    private String covers;

    // 备注
    private String remark;

    // 用户名称
    private String userName;

    // 任务状态 0未完成1已完成
    private Integer status;
}

com.atguigu.task.taskapp.dto.TaskDto

java
package com.atguigu.task.taskapp.dto;

import com.atguigu.task.taskapp.entity.Category;
import com.atguigu.task.taskapp.entity.SubTask;
import com.atguigu.task.taskapp.entity.Task;
import lombok.Data;

import java.util.ArrayList;
import java.util.List;

@Data
public class TaskDto extends Task {
    // 子任务列表
    private List<SubTask> subTaskList = new ArrayList<>();

    // 关联的分类列表
    private List<Category> categoryList = new ArrayList<>();
}

com.atguigu.task.taskapp.mapper.TaskMapper

java
package com.atguigu.task.taskapp.mapper;

import com.atguigu.task.taskapp.entity.Task;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface TaskMapper extends BaseMapper<Task> {
}

com.atguigu.task.taskapp.service.TaskService

java
package com.atguigu.task.taskapp.service;

import com.atguigu.task.taskapp.dto.TaskDto;
import com.atguigu.task.taskapp.entity.Task;
import com.baomidou.mybatisplus.extension.service.IService;

public interface TaskService extends IService<Task> {

    // 新增菜品,同时插入子任务
    public void saveWithSubtask(TaskDto taskDto);
}

com.atguigu.task.taskapp.service.impl.TaskServiceImpl

java
package com.atguigu.task.taskapp.service.impl;

import com.atguigu.task.taskapp.dto.TaskDto;
import com.atguigu.task.taskapp.entity.Category;
import com.atguigu.task.taskapp.entity.SubTask;
import com.atguigu.task.taskapp.entity.Task;
import com.atguigu.task.taskapp.entity.TaskCategory;
import com.atguigu.task.taskapp.mapper.TaskMapper;
import com.atguigu.task.taskapp.service.SubTaskService;
import com.atguigu.task.taskapp.service.TaskCategoryService;
import com.atguigu.task.taskapp.service.TaskService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@Service
public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task> implements TaskService {

    @Autowired
    private TaskCategoryService taskCategoryService;

    @Autowired
    private SubTaskService subTaskService;

    @Transactional
    @Override
    public void saveWithSubtask(TaskDto taskDto) {
        // 1、数据提交任务对象、子任务列表、当前关联的分类列表
        // 2、将任务对象数据入库
        // 3、将分类列表入库
        // 4、将子任务列表入库

        // 创建主任务
        Task task = new Task();

        task.setDate(taskDto.getDate());
        task.setContent(taskDto.getContent());
        task.setCreateTime(LocalDateTime.now());
        task.setUpdateTime(LocalDateTime.now());
        task.setUserName(taskDto.getUserName());
        task.setUserId(taskDto.getUserId());
        task.setCovers(taskDto.getCovers());
        task.setRemark(taskDto.getRemark());
        task.setStatus(taskDto.getStatus());

        // 保存主任务
        this.save(task);

        // 创建子任务列表
        List<SubTask> subTaskList = new ArrayList<>();
        // 抽取子任务
        subTaskList = taskDto.getSubTaskList();

        // 遍历subTaskList 设置taskId
        subTaskList = subTaskList.stream().map((item) -> {
            // 为每一项设置taskId
            item.setTaskId(task.getId());
            item.setCreateTime(LocalDateTime.now());
            item.setUpdateTime(LocalDateTime.now());
            return item;
        }).collect(Collectors.toList());

        List<TaskCategory> taskCategoryList = new ArrayList<>();

        // 遍历categoryList 填充taskCategoryList
        taskDto.getCategoryList().stream().map((item) -> {
            TaskCategory taskCategory = new TaskCategory();
            taskCategory.setTaskId(task.getId());
            taskCategory.setCategoryId(item.getId());
            taskCategoryList.add(taskCategory);

            return item;
        }).collect(Collectors.toList());

        log.info("taskCategoryList{}",taskCategoryList);
        log.info("subTaskList{}",subTaskList);

        // 保存子任务
         subTaskService.saveBatch(subTaskList);

        // 保存在任务分类表
         taskCategoryService.saveBatch(taskCategoryList);

    }

}

com.atguigu.task.taskapp.controller.TaskController

java
package com.atguigu.task.taskapp.controller;

import com.atguigu.task.taskapp.common.R;
import com.atguigu.task.taskapp.dto.TaskDto;
import com.atguigu.task.taskapp.entity.SubTask;
import com.atguigu.task.taskapp.entity.Task;
import com.atguigu.task.taskapp.entity.TaskCategory;
import com.atguigu.task.taskapp.service.SubTaskService;
import com.atguigu.task.taskapp.service.TaskCategoryService;
import com.atguigu.task.taskapp.service.TaskService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import static com.atguigu.task.taskapp.utils.JwtUtil.tokenToOut;

@Slf4j
@RestController
@RequestMapping("/task")
public class TaskController {

    @Autowired
    private TaskService taskService;

    @Autowired
    private SubTaskService subTaskService;

    @Autowired
    private TaskCategoryService taskCategoryService;

    /**
     * 提交任务
     * @param request
     * @param taskDto
     * @return
     */
    @PostMapping("/add")
    public R<String> add(HttpServletRequest request, @RequestBody TaskDto taskDto){
        try {
            taskService.saveWithSubtask(taskDto);

        }catch (Exception e){
            log.error("保存任务报错{}",e.getMessage());
            return R.error("保存失败,请稍后重试");
        }
        return R.success("保存成功");
    }

    /**
     * 任务列表
     * @param page
     * @param pageSize
     * @return
     */
    @GetMapping("/page")
    public R<Page> page(int page, int pageSize,HttpServletRequest request){
        // 获取token
        String token = request.getHeader("token");
        Claims claims = tokenToOut(token);
        // 获取当前用户id
        Integer userId = (Integer) claims.get("userId");

        // 构造分页器
        Page<Task> pageInfo = new Page(page,pageSize);

        // 构造一个taskDto的分页器,用来保存最终的结果
        Page<TaskDto> taskDtoPage = new Page<>();

        LambdaQueryWrapper<Task> queryWrapper = new LambdaQueryWrapper<>();

        // 设置查询条件
        queryWrapper.eq(Task::getUserId,userId);

        // 执行分页查询 为pageInfo赋值
        taskService.page(pageInfo,queryWrapper);

        // 将pageInfo中的分页信息拷贝到taskDtoPage,排除record不拷贝,
        // 因为taskDtoPage中record的每一项均需要扩展成TaskDto
        BeanUtils.copyProperties(pageInfo,taskDtoPage,"record");

        // 获取查询出来的records数据
        List<Task> records = pageInfo.getRecords();

        List<TaskDto> list = records.stream().map((item) -> {

            TaskDto taskDto = new TaskDto();

            // 将item拷贝到taskDto中共有的部分
            BeanUtils.copyProperties(item,taskDto);

            LambdaQueryWrapper<SubTask> subTaskLambdaQueryWrapper = new LambdaQueryWrapper<>();

            subTaskLambdaQueryWrapper.eq(SubTask::getTaskId,item.getId());

            List<SubTask> subTaskList = subTaskService.list(subTaskLambdaQueryWrapper);

            taskDto.setSubTaskList(subTaskList);

            return taskDto;

        }).collect(Collectors.toList());

        taskDtoPage.setRecords(list);

        return R.success(taskDtoPage);
    }

    /**
     * 完成任务以及子任务,更改任务状态的同时更新关联的子任务状态
     * @param request
     * @param task
     * @return
     */
    @PostMapping("/complete")
    public R<String> complete(HttpServletRequest request,@RequestBody Task task){

        Long id = task.getId();

        Task task1 = taskService.getById(id);

        if(task1 == null){
            return R.error("任务不存在");
        }

        // 将任务状态修改成完成状态
        task1.setStatus(task.getStatus());

        LambdaQueryWrapper<SubTask> subTaskLambdaQueryWrapper = new LambdaQueryWrapper<>();

        // 设置一个条件
        subTaskLambdaQueryWrapper.eq(id!=null,SubTask::getTaskId,id);

        List<SubTask> subTaskList = subTaskService.list(subTaskLambdaQueryWrapper);

        // 批量更新关联的子任务状态
        subTaskList = subTaskList.stream().map(item -> {
            item.setStatus(task.getStatus());
            return item;
        }).collect(Collectors.toList());

        taskService.updateById(task1);
        subTaskService.updateBatchById(subTaskList);

        log.info("subTaskList:{}",subTaskList);

        return R.success("更新成功");
    }

    /**
     * 分页根据category分页查询任务列表
     * @param page
     * @param pageSize
     * @param categoryId
     * @return
     */
    @GetMapping("/category/list")
    public R<Page> getListByCategoryId(int page,int pageSize,@RequestParam Long categoryId){

        try {
            // 最终返回的结果
            List<Task> taskList = new ArrayList<>();

            // 用来保存taskid集合
            List<Long> taskIdList = new ArrayList<>();

            // 构建分页查询器
            Page<Task> pageInfo = new Page<>(page,pageSize);

            // 创建条件构造器
            LambdaQueryWrapper<TaskCategory> queryWrapper = new LambdaQueryWrapper<>();
            // 设置条件
            queryWrapper.eq(categoryId!=null,TaskCategory::getCategoryId,categoryId);
            // 将指定分类关联数据查询出来
            List<TaskCategory> taskCategoryList = taskCategoryService.list(queryWrapper);

            log.info("taskCategoryList:{}",taskCategoryList);

            // 提取并填充taskIdList
            taskCategoryList.stream().map((item) -> {
                taskIdList.add(item.getTaskId());
                return item;

            }).collect(Collectors.toList());

            LambdaQueryWrapper<Task> taskLambdaQueryWrapper = new LambdaQueryWrapper<>();

            taskLambdaQueryWrapper.in(Task::getId,taskIdList);
            taskLambdaQueryWrapper.orderByAsc(Task::getUpdateTime);

            // 执行分页查询并填充pageInfo
            taskService.page(pageInfo,taskLambdaQueryWrapper);

            log.info("pageInfo:{}",pageInfo);

            return R.success(pageInfo);
        }catch (Exception e){
            log.info("e,{}",e);
            return R.error("查询失败,请稍后重试");
        }
    }
}

项目部署

添加项目部署脚本

sh
#!/bin/sh
echo =================================
echo  自动化部署脚本启动
echo =================================

echo 停止原来运行中的工程
APP_NAME=taskapp

tpid=`ps -ef|grep $APP_NAME|grep -v grep|grep -v kill|awk '{print $2}'`
if [ ${tpid} ]; then
    echo 'Stop Process...'
    kill -15 $tpid
fi
sleep 2
tpid=`ps -ef|grep $APP_NAME|grep -v grep|grep -v kill|awk '{print $2}'`
if [ ${tpid} ]; then
    echo 'Kill Process!'
    kill -9 $tpid
else
    echo 'Stop Success!'
fi

echo 准备从Git仓库拉取最新代码
cd /usr/local/taskapp

echo 开始从Git仓库拉取最新代码
git pull
echo 代码拉取完成

echo 开始打包
output=`mvn clean package -Dmaven.test.skip=true`

cd target

echo 启动项目
nohup java -jar task-app-0.0.1-SNAPSHOT &> taskapp.log &
echo 项目启动完成

上次更新于: