未打开任何项目的,直接点击New Project,已经打开其他项目的,依次点击File -> New -> Project -> Maven
,点击Next,新建Maven项目(这里的Project SDK我们使用的是1.8版本)
点击Finnish后(如果已经打开了其他项目,会弹出Open Project的窗口,选择This Window会关闭当前项目,打开创建的项目,选择New Window会在新的窗口打开创建的项目),初始的Maven项目就创建好了
使用SpringBoot很简单,我们只需要在pom.xml中添加SpringBoot依赖即可,最终如下
<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.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.4.2version>
parent>
<groupId>com.jl15988groupId>
<artifactId>we-shoppingartifactId>
<version>1.0version>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
dependencies>
project>
加载Maven依赖需要点击右上角的小图标,这样才能将依赖引入我们的项目
在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
类,并添加如下代码
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端口可能会报错)
使用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;
首先在项目中引入一下依赖
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>1.3.2version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.5.3.1version>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-generatorartifactId>
<version>3.5.3.1version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-freemarkerartifactId>
<version>3.1.0version>
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)
然后在项目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表中所有条目查询出来,如果查询全部字段值,不推荐使用*号,推荐手写全部字段值查询)
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的插件,所以自动格式化了返回数据)
如图,后端返回的时间格式与我们实际想要的格式不符合,所以我们需要转换以下格式,可以直接在实体类属性上添加@JsonFormat注解
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
/**
* 更新时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
如图,我们所看到的返回数据中有值为null的参数,在某些情况下,我们可能会隐藏值为null的参数,我们可以使用@JsonInclude注解来实现
@Data
@JsonInclude(value = JsonInclude.Include.NON_NULL)
public class User {
/* 省略代码 */
}
如果是前后端分离的项目,此时如果我们创建一个前端项目来访问当前接口是访问不通的,因为地址或端口不同造成了跨域请求,此时我们需要后端开放跨域请求,如图
在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("*");
}
}
}
然后我们重启项目,重新访问可以发现跨域报错消失了,并且能够返回数据,如图
作为后端,就算是报错也需要返回异常信息来提示前端,但是在我们的代码中,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();
}
}
然后重启项目再次访问地址,发现返回信息变了,这说明我们捕获到了异常,并返回给了前端,但是控制台是没有报错信息的
一个后端项目,报错日志是非常重要的,对于后期维护和问题解决起到了决定性作用,所以全局异常处理没有打印报错信息是一个不太好的现象。我们可以使用@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();
}
}
在我们的业务逻辑中,可能需要自定义异常来抛出,由全局异常处理类来处理,这个时候考虑到返回的状态码和异常信息的不同,我们需要自定义异常类来配合全局异常处理
在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表中全部信息,然后再次请求,可以看到前端收到了我们想要的结果
对于前后端分离的项目,后端返回数据往往需要统一规范,来配合前端数据统一处理
在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());
}
然后我们访问一下接口,可以看到返回格式变了
至此,我们的入门接口开发就算是完成了