前后端分离项目-后端接口入门开发手记

一、创建项目

1. 新建maven项目

未打开任何项目的,直接点击New Project,已经打开其他项目的,依次点击File -> New -> Project -> Maven,点击Next,新建Maven项目(这里的Project SDK我们使用的是1.8版本)

在这里插入图片描述
然后我们配置Maven项目如下(具体含义可以自行查找)

  • Name,项目名称
  • Location,项目本地存放路径
  • GroupId,Maven组一般是网站倒写,也可以自己命名,如cn.zhangsan
  • ArtifactId,组中的模块儿名
  • Version,该Maven项目的版本号

在这里插入图片描述

点击Finnish后(如果已经打开了其他项目,会弹出Open Project的窗口,选择This Window会关闭当前项目,打开创建的项目,选择New Window会在新的窗口打开创建的项目),初始的Maven项目就创建好了
在这里插入图片描述

2. 搭建SpringBoot

使用SpringBoot很简单,我们只需要在pom.xml中添加SpringBoot依赖即可,最终如下

  • parent,将SpringBoot设置为当前项目的父级,也就是将我们的项目继承SpringBoot
  • properties,我们一般将依赖版本号写在这里,下面直接引用,方便管理版本
  • dependencies,依赖列表
  • dependency,依赖项
<?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 http://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.2</version>
    </parent>

    <groupId>com.jl15988</groupId>
    <artifactId>we-shopping</artifactId>
    <version>1.0</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
</project>

加载Maven依赖需要点击右上角的小图标,这样才能将依赖引入我们的项目
在这里插入图片描述

3. 创建接口

src\main\java下新建包com.jl15988.shopping(包名往往与组名相同,或组名+模块名,不过不能有大写),然后创建WeShoppingApplication(这里命名使用的是项目名+Application)类,添加如下代码

package com.jl15988.shopping;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author Jalon
 * @since 2023/8/1 10:56
 **/
@SpringBootApplication
public class WeShoppingApplication {
    
    

    public static void main(String[] args) {
    
    
        SpringApplication.run(WeShoppingApplication.class);
    }
}

然后在com.jl15988.shopping下创建controller包,在controller包下创建HelloController类,并添加如下代码

  • @RestController,注解,用以表明当前是一个控制器类
  • @GetMapping,声明此方法是一个Get请求
package com.jl15988.shopping.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author Jalon
 * @since 2023/8/1 10:59
 **/
@RestController
public class HelloController {
    
    

    @GetMapping("/hello")
    public String hello() {
    
    
        return "Hello World!";
    }
}

然后我们就可以启动项目了,可以直接点击WeShoppingApplication中的绿色三角启动项目(启动过项目的可以直接点击右上角的绿色小三角)
在这里插入图片描述

然后浏览器访问localhost:8080/hello,效果图如下(如果有其他项目或程序占用8080端口可能会报错)

在这里插入图片描述

二、开发接口

1. 创建数据库

使用navicat创建数据库we_shopping

在这里插入图片描述

然后执行sql语句创建数据表

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user`  (
  `user_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
  `username` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
  `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
  `nick` varchar(26) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `create_time` datetime(0) NULL DEFAULT NULL,
  `update_time` datetime(0) NULL DEFAULT NULL,
  PRIMARY KEY (`user_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户' ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

2. 添加代码生成工具

首先在项目中引入一下依赖

<!-- 数据库 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
<!-- mybatis -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.3.2</version>
</dependency>
<!-- lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
<!-- mybatis-plus -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3.1</version>
</dependency>
<!-- 代码生成 -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.5.3.1</version>
</dependency>
<!-- 代码生成引擎模板 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
    <version>3.1.0</version>
</dependency>

然后在com.jl15988.shopping下创建CodeGenerator类,然后添加如下内容

package com.jl15988.shopping;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.rules.DbColumnType;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import com.baomidou.mybatisplus.generator.fill.Column;

import java.sql.Types;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * @author Jalon
 * @since 2023/8/1 11:35
 **/
public class CodeGenerator {
    
    

    // 数据库地址
    private static final String URL = "jdbc:mysql://localhost:3306/we_shopping?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai";

    // 数据库用户名
    private static final String USERNAME = "root";

    // 数据库密码
    private static final String PASSWORD = "123456";

    // 生成代码的包名
    private static final String PACKAGE = "com.jl15988.shopping";

    // 作者
    private static final String AUTHOR = "Jalon";

    // 过滤的数据库名前缀
    private static final String[] PREFIXS = {
    
    "sys_"};

    public static void main(String[] args) {
    
    
        String projectPath = System.getProperty("user.dir");

        FastAutoGenerator.create(URL, USERNAME, PASSWORD)
                .globalConfig(builder -> {
    
    
                    builder.author(AUTHOR) // 设置作者
//                            .enableSwagger() // 开启 swagger 模式
                            .outputDir(projectPath + "/src/main/java") // 输出目录
                            .disableOpenDir(); //
                })
                .dataSourceConfig(builder -> builder.typeConvertHandler((globalConfig, typeRegistry, metaInfo) -> {
    
    
                    int typeCode = metaInfo.getJdbcType().TYPE_CODE;
                    if (typeCode == Types.SMALLINT) {
    
    
                        // 自定义类型转换
                        return DbColumnType.INTEGER;
                    }
                    return typeRegistry.getColumnType(metaInfo);

                }))
                .packageConfig(builder -> {
    
    
                    builder.parent(PACKAGE) // 设置父包名
//                            .moduleName("") // 设置父包模块名
                            .entity("domain.entity")
                            .pathInfo(Collections.singletonMap(OutputFile.xml, projectPath + "/src/main/resources/mapper")); // 设置mapperXml生成路径
                })
                .strategyConfig((scanner, builder) -> {
    
    
                    List<String> tables = getTables(scanner.apply("请输入表名,多个英文逗号分隔,所有表生成输入 all"));
                    builder.addTablePrefix(PREFIXS) // 过滤前缀
                            .addInclude(tables) // 增加表匹配
                            // controller配置
                            .controllerBuilder()
                            .enableRestStyle() // 添加@RestController
                            .enableHyphenStyle() // 驼峰转连字符
                            // 实体类配置
                            .entityBuilder()
                            .enableFileOverride() // 生成覆盖
                            .enableLombok() //添加lombok
                            .addTableFills(new Column("create_time", FieldFill.INSERT))
                            .disableSerialVersionUID() // 禁用生成 serialVersionUID
                            .idType(IdType.ASSIGN_ID) // 当用户未输入时,采用雪花算法生成一个适用于分布式环境的全局唯一主键
                            .build();
                })
                // 引擎模板,默认的是Velocity引擎模板
//                .templateEngine(new BeetlTemplateEngine()) // Beetl引擎模板
                .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板
                .execute();
    }

    // 处理 all 情况
    protected static List<String> getTables(String tables) {
    
    
        return "all".equals(tables) ? Collections.emptyList() : Arrays.asList(tables.split(","));
    }
}

然后点击那个绿色的小三角启动这个类中的main方法,然后在控制台输入all即可自动生成我们所需要的各种类,最终如下图(这里忽略application.yml)
在这里插入图片描述

记录一下,优化到了此处

2. 编写接口

然后在项目resources下创建application.yml文件,内容如下(注意修改自己的数据库名称和密码)

server:
  port: 8080
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/we_shopping?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
mybatis:
  mapper-locations: classpath:/mapper/*Mapper.xml

然后在com.jl15988.shopping包下创建entity包,然后创建User类如下

package com.myblog.entity;

import lombok.Data;

import java.util.Date;

/**
 * @author Jaon
 * @datetime 2021/10/29 16:13
 */
@Data
public class User {
    
    

    private Long id;

    /**
     * 用户名
     */
    private String username;

    /**
     * 密码
     */
    private String password;

    /**
     * 昵称
     */
    private String nick;

    /**
     * 邮箱
     */
    private String email;

    /**
     * 创建时间
     */
    private Date createTime;

    /**
     * 更新时间
     */
    private Date updateTime;
}

在com.myblog包下创建mapper包,然后创建UserMapper接口,如下

package com.myblog.mapper;

import com.myblog.entity.User;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

/**
 * @author Jaon
 * @datetime 2021/10/29 16:09
 */
@Mapper
public interface UserMapper {
    
    

    List<User> list();
}

在resources下创建mapper包,然后创建UserMapper.xml如下(这里写的sql语句是将数据库user表中所有条目查询出来,如果查询全部字段值,不推荐使用*号,推荐手写全部字段值查询)

<?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.myblog.mapper.UserMapper">
    <select id="list" resultType="com.myblog.entity.User">
        SELECT id, username, nick, email, create_time createTime, update_time updateTime
        FROM t_user
    </select>
</mapper>

在com.myblog包下创建service包,然后创建UserService接口如下

package com.myblog.service;

import com.myblog.entity.User;

import java.util.List;

/**
 * @author Jaon
 * @datetime 2021/10/29 16:11
 */
public interface UserService {
    
    

    List<User> list();
}

在service下创建impl包,创建UserServiceImpl类并实现UserService接口如下

package com.myblog.service.impl;

import com.myblog.entity.User;
import com.myblog.mapper.UserMapper;
import com.myblog.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @author Jaon
 * @datetime 2021/10/29 16:11
 */
@Service
public class UserServiceImpl implements UserService {
    
    

    @Autowired
    UserMapper userMapper;

    @Override
    public List<User> list() {
    
    
        return userMapper.list();
    }
}

在controller下创建UserController如下

package com.myblog.controller;

import com.myblog.entity.User;
import com.myblog.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * @author Jaon
 * @datetime 2021/10/29 16:22
 */
@RestController
@RequestMapping("/user")
public class UserController {
    
    

    @Autowired
    UserService userService;

    @GetMapping("/list")
    public List<User> list() {
    
    
        return userService.list();
    }
}

最后在数据库造一条数据,重启项目,访问localhost:8080/user/list

如数据库里

在这里插入图片描述

访问结果(我这里使用了CSDN的插件,所以自动格式化了返回数据)

在这里插入图片描述

a. 时间格式化

如图,后端返回的时间格式与我们实际想要的格式不符合,所以我们需要转换以下格式,可以直接在实体类属性上添加@JsonFormat注解

/**
 * 创建时间
 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;

/**
 * 更新时间
 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;

b. 隐藏数据为空的属性

如图,我们所看到的返回数据中有值为null的参数,在某些情况下,我们可能会隐藏值为null的参数,我们可以使用@JsonInclude注解来实现

@Data
@JsonInclude(value = JsonInclude.Include.NON_NULL)
public class User {
    
    
    /* 省略代码 */
}

3. 实现跨域

如果是前后端分离的项目,此时如果我们创建一个前端项目来访问当前接口是访问不通的,因为地址或端口不同造成了跨域请求,此时我们需要后端开放跨域请求,如图

在这里插入图片描述

在com.myblog包下创建config包,并创建WebMvcConfig类,如下

package com.myblog.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author Jaon
 * @datetime 2021/11/1 9:54
 */
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    
    

    @Override
    public void addCorsMappings(CorsRegistry registry) {
    
    
        // 允许所有的路径可以跨域
        registry.addMapping("/**")
                // 允许所有来源都可以跨域
                .allowedOriginPatterns("*")
                // 允许跨域的请求
                .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
                // 允许证书凭证(如果这里设置为true,设置来源为所有只能使用allowedOriginPatterns)
                .allowCredentials(true)
                // 跨域时间3600秒
                .maxAge(3600)
                // 允许所以头标题
                .allowedHeaders("*");
    }
    }
}

然后我们重启项目,重新访问可以发现跨域报错消失了,并且能够返回数据,如图

在这里插入图片描述

4. 全局异常处理

作为后端,就算是报错也需要返回异常信息来提示前端,但是在我们的代码中,controller层与service层之间传递数据不可能局限于一个非常统一的数据类型或者service根本就没有返回值,这就造成了有某些异常信息不能直接从service层返回到前端,如果使用try-catch来捕获就太过于麻烦了,所以我们定义全局异常处理来统一捕获异常并返回给前端

如图,我们写了一个非常简单并且肯定能够报错的代码

在这里插入图片描述

然后我们去访问该地址,会发现报了一个500的错,并在控制台打印了错误信息

在这里插入图片描述

在这里插入图片描述

然后我们在com.myblog包下创建handler包,并创建GlobalExceptionHandler类,如下

package com.myblog.handler;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
 * @author Jaon
 * @datetime 2021/11/1 11:27
 */
@RestControllerAdvice
public class GlobalExceptionHandler {
    
    

    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(Exception.class)
    public String handleException(Exception e) {
    
    
        return e.getMessage();
    }
}

然后重启项目再次访问地址,发现返回信息变了,这说明我们捕获到了异常,并返回给了前端,但是控制台是没有报错信息的

在这里插入图片描述

a. 报错日志

一个后端项目,报错日志是非常重要的,对于后期维护和问题解决起到了决定性作用,所以全局异常处理没有打印报错信息是一个不太好的现象。我们可以使用@Slf4j来实现错误信息日志,如下

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    
    

    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(Exception.class)
    public String handleException(Exception e) {
    
    
        log.error("报错信息:{}", e.getMessage());
        return e.getMessage();
    }
}

b. 自定义异常

在我们的业务逻辑中,可能需要自定义异常来抛出,由全局异常处理类来处理,这个时候考虑到返回的状态码和异常信息的不同,我们需要自定义异常类来配合全局异常处理

在com.myblog包下创建common.lang包,并创建MyBlogException类,如下

package com.myblog.common.exception;

import lombok.Getter;

/**
 * @author Jaon
 * @datetime 2021/11/1 12:32
 */
@Getter
public class MyBlogException extends RuntimeException {
    
    

    private Integer code;

    private String msg;

    public MyBlogException(Integer code, String msg) {
    
    
        super(msg);
        this.code = code;
        this.msg = msg;
    }

    public MyBlogException(Integer code, String msg, Throwable throwable) {
    
    
        super(msg, throwable);
        this.code = code;
        this.msg = msg;
    }

    public MyBlogException(String msg) {
    
    
        super(msg);
        this.code = 403;
        this.msg = msg;
    }

    public MyBlogException(String msg, Throwable throwable) {
    
    
        super(msg, throwable);
        this.code = 403;
        this.msg = msg;
    }
}

然后我们就可以使用自定义的异常类来抛出异常了,如

修改list接口

@GetMapping("/list")
public List<User> list() {
    
    
    List<User> list = userService.list();
    if (list.size() <= 0) {
    
    
        // 如果数据库中没有用户信息则抛出异常
        throw new MyBlogException("当前没有用户信息");
    }
    return list;
}

在全局异常处理类中添加方法

@ExceptionHandler(MyBlogException.class)
public Map<String, Object> handleException(MyBlogException e) {
    
    
    log.error("报错信息:{}", e.getMessage());
    Map<String, Object> map = new HashMap<>();
    map.put("code", e.getCode());
    map.put("msg", e.getMsg());
    return map;
}

然后删除user表中全部信息,然后再次请求,可以看到前端收到了我们想要的结果

在这里插入图片描述

5. 返回数据统一封装

对于前后端分离的项目,后端返回数据往往需要统一规范,来配合前端数据统一处理

在common包下创建lang包,并创建Result类,如下

package com.myblog.common.lang;

import lombok.Data;

/**
 * @author Jaon
 * @datetime 2021/11/1 13:14
 */
@Data
public class Result {
    
    

    private Integer code;

    private String msg;

    private Object data;

    public static Result success() {
    
    
        return success(null);
    }

    public static Result success(Object data) {
    
    
        return success(200, data);
    }

    public static Result success(Integer code, Object data) {
    
    
        return common(code, "操作成功", data);
    }

    public static Result fail() {
    
    
        return fail(null);
    }

    public static Result fail(Integer code, String msg) {
    
    
        return common(code, msg, null);
    }

    public static Result fail(String msg) {
    
    
        return common(400, msg, null);
    }

    public static Result fail(Integer code, Object data) {
    
    
        return common(code, "操作失败", data);
    }

    public static Result common(Integer code, String msg, Object data) {
    
    
        Result result = new Result();
        result.setCode(code);
        result.setMsg(msg);
        result.setData(data);
        return result;
    }
}

然后将所有的返回替换掉

// 修改接口返回值
@GetMapping("/list")
public Result list() {
    
    
    List<User> list = userService.list();
    if (list.size() <= 0) {
    
    
        throw new MyBlogException("当前没有用户信息");
    }
    return Result.success(list);
}
// 修改异常处理类返回值
@ExceptionHandler(MyBlogException.class)
public Result handleException(MyBlogException e) {
    
    
    log.error("报错信息:{}", e.getMessage());
    return Result.fail(e.getCode(), e.getMsg());
}

@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(Exception.class)
public Result handleException(Exception e) {
    
    
    log.error("报错信息:{}", e.getMessage());
    return Result.fail(e.getMessage());
}

然后我们访问一下接口,可以看到返回格式变了

在这里插入图片描述

至此,我们的入门接口开发就算是完成了

猜你喜欢

转载自blog.csdn.net/jl15988/article/details/121079092