适用于后端小白的:SpringBoot+Java17+Maven+MyBatis+git+gitee项目初始化流程

如果你是一个SpringBoot小白,想了解一下关于SpringBoot项目的基本项目创建流程,可以先看看这篇文档,或许对你会有一些帮助。

本文档主要使用IDEA 2024 版本的ide工具开发,因为不同版本的IDEA,UI设计不同,其他版本的idea可能有些页面与本文的中的图片示例不一样,所以,你需要注意一下你的版本问题。你可以选择下载一个与我的版本相同的版本,也可以选择看完本文的“项目初始化”章节之后,去找与你版本相似/相同的教程创建项目,当然你要是本来就熟练使用IDEA的话当我没说。

1 创建SpringBoot项目

1.1 打开IDEA的界面

1.1.1 1.1.2 两种方法二选其一

1.1.1 打开IDEA的欢迎界面

在这里插入图片描述

1.1.2 使用IDEA打开一个任意的项目

在这里插入图片描述

1.2 打开“新建项目”对话框

同样也是两个方法二选其一

1.2.1 单击“新建项目”按钮

在这里插入图片描述

1.2.2 选择:“文件”→“新建”→“项目”

在这里插入图片描述
在这里插入图片描述

1.3 在idea集成的spring创建工具中输入基本的项目信息

这里我打算给我的项目名词取名为c-zuan

位置存到专门存放项目的文件

创建git仓库

语言选择java

类型(构建工具)选择Maven

组(项目的唯一标识)输入我自己的域名(反写)

工件(与项目同名)

软件包名词与组相同

Java版本选择17(你也可以选择别的)

打包方式选择jar

在这里插入图片描述

1.4 选择添加项目运行需要的最基本的依赖

Spring Web:springboot开发web项目必须的依赖

MySQL Driver:MySQL 数据库的驱动依赖

MyBatis Framework:MyBatis依赖

Lombok:Lombok依赖,提供很多注解,简化代码开发

在这里插入图片描述

1.5 注意:

单机创建之后idea会自动在往上下载很多依赖等、插件,你需要耐心等待一会儿才可以进行下一步操作

全部加载完成之后页面展示内容是这个样子的

在这里插入图片描述

2 使用git管理项目

2.1 首次提交项目

因为前文中在创建项目的时候勾选了创建git仓库,所以目前本项目已经有了git仓库,如果在前面的操作中没有勾选创建git仓库的按钮,那么在这里你是没有git仓库的,你需要自行创建git仓库,如果不会创建git仓库,请移步其他文档,学习如何创建git仓库;或者选择跳过“使用git管理项目”章节,前往下一章

在这里插入图片描述

在git面板中可以看到刚刚的提交记录

在这里插入图片描述

2.2 在“master”分支中新建一个开发分支“develop”

在这里插入图片描述

在弹出的对话框中输入“develop”

在这里插入图片描述

新建分支之后git会自动跳转到新的分支“develop”

在这里插入图片描述

如果没有自动跳转的话,右击develop选择“签出”即可切换到develop分支

签出(checkout):笔者认为这应该是翻译插件的一个错误翻译导致的,将其翻译为“切换分支”更合理

在这里插入图片描述

2.3 创建远程仓库

打开gitee :https://gitee.com/

需要登录账号,如果没有账号,请先注册一个账号。

点击右上角“加号” 并选择“创建仓库”

在这里插入图片描述

输入仓库名,路径会自动生成一个与仓库同名路径,当然你也可以自行修改为其他的路径,可以选择“开源”/“私有”

单击“创建” 即可完成创建

在这里插入图片描述

在这里插入图片描述

2.4 使用idea连接远程仓库

2.4.1 推送master分支

切换到master分支

在这里插入图片描述

选择“推送”

在这里插入图片描述

单击“定义远程”

在这里插入图片描述

回到gitee仓库 单击复制按钮 将远程仓库路径复制到粘贴板

在这里插入图片描述

将远程仓库路径粘贴到 URL文本框,名称一般都默认叫“origin”可以不用改,除非你有特殊需求。

在这里插入图片描述

单击“推送”按钮

在这里插入图片描述

回到远程仓库页面,刷新页面,可以发现,仓库已更新。我们的项目已被正确的推送。

在这里插入图片描述

2.4.2 推送develop分支

与推送master分支步骤相同,只不过省去了定义远程仓库的步骤,因为在同一个项目中idea会默认我们只用一个远程仓库,而试试也正是如此,所以不用重新定义远程。直接点击推送,然后回到远程仓库页面刷新页面,即可看到新推送的分支

切勿忘记推送前将分支切换(签出)到“develop”分支,不切换可能也不影响推送,但是还算要养成好习惯,操作哪个分支就切换到哪个分支(如果合并分支的话要切换到“要合并到”的分支上,而不是“被合并的”分支),再进行操作。

在这里插入图片描述

3 连接数据库

3.1 设置application.properties配置文件

如果有好奇心重的朋友可能项目创建完成之后就已经尝试运行这个项目的了,但是会发现运行不起来,有一个报错。例,如下报错:

Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
2024-09-01T15:13:42.680+08:00 ERROR 16256 --- [c-zuan-test] [           main] o.s.b.d.LoggingFailureAnalysisReporter   : 

***************************
APPLICATION FAILED TO START
***************************

Description:

Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.

Reason: Failed to determine a suitable driver class


Action:

Consider the following:
  If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.
  If you have database settings to be loaded from a particular profile you may need to activate it (no profiles are currently active).


进程已结束,退出代码为 1

报错指出,未能配置DataSourceDataSource 是用于获取数据库连接的对象。

接下来你需要再项目的:src/main/resources/application.properties文件下输入以下配置属性。

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/c_zuan
spring.datasource.username=root
spring.datasource.password=123123

spring.datasource.driver-class-name :是数据库的驱动,只要你使用的是MySQL,就照抄我案例中的路径即可,如果是其他数据库,那么请自行去网络上寻找相应的驱动路径。

spring.datasource.url :是你要连接的数据库的路径地址,jdbc:mysql 是MySQL的协议,localhost:3306 是数据库所在的地址以及端口号,如果localhost 是本地的意思,我的数据库与我的后端项目再统一设备上,所以可以使用localhost 如果你要连接远程服务器的地址,请将localhost 更换为你的服务器的公网ip。

spring.datasource.username :数据库的用户名。

spring.datasource.password :数据库的密码。

将以上信息配置正确之后偶再次启动项目就会发现, 项目已经成功启动。

3.2 写一个简单的数据访问方法测试数据库是否已经正确连接

3.2.1 编写访问数据库的访问接口类

再在路径src/main/java/top/honlnk/czuan下新建一个mapper

src/main/java/top/honlnk/czuan/mapper 路径下新建一个名叫TestMapper的接口类。

输入以下测试代码

package top.honlnk.czuan.mapper;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import java.util.List;
import java.util.Map;

@Mapper
public interface TestMapper {
    @Select("select * from words")
    List<Map<String, Object>> testSelect();
}

在这里插入图片描述

但是ide会以红色字体提示我们一个错误,这个的报错原因是我们还没有绑定数据源,说人话就是idea还不知道我们用的是哪一个数据库里的数据,刚刚写的配置表只是告诉了这个项目,要用哪个数据库,驱动是什么,用户名密码是什么。但是idea还不知道,我们还需要让idea也知道这些信息。

3.2.2 添加数据源

单击这个小图标,打开数据库侧边栏。

在这里插入图片描述

在这里插入图片描述

在“数据源和驱动程序”页面中输入以下信息,点击“确定”按钮,即可完成添加数据源。

在这里插入图片描述

再次回到TestMapper 的接口类中,就会发现,哪个报错一已经消失了。

在这里插入图片描述

3.2.3 编写测试类

接下来要编写一个测试类来运行以下这个数据库查询方法

打开src/test/java/top/honlnk/czuan/CZuanTestApplicationTests.java

CZuanTestApplicationTests.java 类中编写以下代码

package top.honlnk.czuan;

import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import top.honlnk.czuan.mapper.TestMapper;

@Slf4j
@SpringBootTest
class CZuanTestApplicationTests {

    @Autowired
    public TestMapper testMapper;

    @Test
    void contextLoads() {
        List<Map<String, Object>> list = testMapper.testSelect();
        log.info("单词表中所有的数据: {}", list);
    }

}

在这里就用到了创建项目时添加的Lombok依赖,使用@Slf4j注解,即可正确引用此依赖,在下文中的log.info() 方法就是使用了Lombok依赖的方法,这是在控制台中打印一个日志。

log.info("testMapper.testSelect(): {}", list); 会把在数据库中访问到的数据打印在控制台。

在控制台中看到输出这这两条信息,证明你的数据库连接非常正确。

在这里插入图片描述

3.3 提交并推送所作的操作。

值得注意的是,一定要注意你的当前分支,不要提交到master上面

在这里插入图片描述

打开git面板即可看到新增了一条提交记录

在这里插入图片描述

4 创建实体类(pojo)

在上面的测试类中,有这样一段代码:List<Map<String, Object>> list = testMapper.testSelect();mapper 中由于是Select * ,所以,这个查询把整个表格的内容都获取出来了,既然这样,就必须要定义一个如此复杂的类型List<Map<String, Object>> 来接收参数,这样写即麻烦又不规范,所以我们需要定义一些实体类,来作为各层之间传递参数的载体(标准)。

根据笔者的习惯:会把实体类定义为三种:DTO entity/BO VO

entity/BO(实体):通常代表业务领域中的对象,直接映射数据库的表,一个entity实体类代表一个数据表。对数据库进行增删改查,以及处理部分业务逻辑时会用到这个。

DTO(Data Transfer Object,数据传输对象):通常用于客户端与服务端或者服务端与服务端之间传递数据。

VO(View Object,视图对象):通常用于传输客户端要展示的数据。虽然DTO可以用作表示服务端向客户端传递数据,但是我更习惯将服务端向客户端单向传输数据单独用一类管理起来,这样对我设计接口文档会有一些帮助。

4.1 创建DTO

src/main/java/top/honlnk/czuan 路径下创建一个包pojo (对象)

src/main/java/top/honlnk/czuan/pojo 路径下创建一个叫dto的包

src/main/java/top/honlnk/czuan/pojo/dto 路径下创建一个叫LoginDTO的Java类

虽然还没一正式编写项目,但是90%的项目都有登录功能,所以可以先创建一个用作登录的实体类。

LoginDTO中写入这些代码

package top.honlnk.czuan.pojo.dto;

import lombok.Data;

@Data
public class LoginDTO {

    private String account;

    private String password;

    private String captchaCode;

    private Boolean isAutoLogin;
}

@Data 注解与上文中的@Slf4j 一样,同样是来之Lombok中的注解,它可以简化很多的代码。

account :账号

password :密码

captchaCode :人机验证码

isAutoLogin :是否自动登录

4.2 创建VO

src/main/java/top/honlnk/czuan/pojo 路径下创建一个叫vo的包

src/main/java/top/honlnk/czuan/pojo/vo 路径下创建一个叫LoginVO的Java类

LoginVO中写入这些代码

package top.honlnk.czuan.pojo.vo;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class LoginVO {

    private String uname;

    private String email;

    private String account;

    private String token;
}

@Builder :用于快捷构建复杂的对象实例,使代码更整洁易于维护

@AllArgsConstructor :生成一个无参构造函数

@NoArgsConstructor:生成一个包含所有参数的构造函数

4.3 创建entity

src/main/java/top/honlnk/czuan/pojo 路径下创建一个叫entity的包

src/main/java/top/honlnk/czuan/pojo/entity 路径下创建一个叫User的Java类

User中写入这些代码

package top.honlnk.czuan.pojo.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDate;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private Integer id;
    private String account;
    private String uname;
    private String password;
    private String eMail;
    private String readPower;
    private String addPower;
    private String delPower;
    private String revisePower;
    private String powerPower;
    private String reviewPower;
    private LocalDate createdAt;
    private LocalDate updatedAt;
    private String createdBy;
    private String updatedBy;
}

另外,我还创建了一个叫Words的实体类,并将刚刚编写的mapper测试类修改为标准的开发格式,项目测试同样能正常通过。

    @Test
    void contextLoads() {
        List<Words> list = testMapper.testSelect();
        log.info("单词表中所有的数据: {}", list);
    }
@Mapper
public interface TestMapper {
    @Select("select * from words")
    List<Words> testSelect();
}

4.4 提交git

与上述步骤一样

5 创建持久层(Mapper)

只有在MyBatis环境中使用Mapper表示持久层才比较合理,如果是在其他环境中,可能需要其他的名词来表示从持久层,或者直接使用Repository 表示持久层

由于前文的连接数据库章节中,已经创建了mapper包,所以在这里就不需要创建了。

5.1 创建UserMapper接口类

src/main/java/top/honlnk/czuan/mapper 路径下创建一个名叫UserMapper的接口类

UserMapper 中编写

package top.honlnk.czuan.mapper;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import top.honlnk.czuan.pojo.entity.User;

@Mapper
public interface UserMapper {
    
    void addUser(User user);
    
    @Select("select uname, account, e_mail, created_at, created_by from users where account = #{account}")
    User getUserByAccount(String account);
}

addUser()方法:添加用户,这个方法计划使用XML配置文件映射SQL语句

getUserByAccount()方法:根据账号查询用户信息,这个SQL功能较为简单,所以直接使用注解映射SQL语句,这样开发,语法简洁、高效。

5.2 创建UserMapper.xml配置文件

在MyBatis中,复杂的数据库访问方法通常会映射到一个XML文件中映射SQL语句,在这里面编写代码,可扩展度更高,能够实现的功能更复杂。

如果是简单的逻辑,则不需要使用XML文件映射,而是直接在方法的上面直接引入一个注解,然后再括号里直接写入需要的SQL语句。

XML 映射文件的名称与Mapper接口文件名称需要一致,并且将XML映射文件和Mapper接口放置在相同的包下,也就是所谓的同包同名。

由于在spring boot的工程化项目中 java这个文件夹下存放的都是java源代码,所以要想完成这个需求,因此,就只能在resources这个文件夹下建立一个名字相同的包。

5.2.1 创建同路径包文件:

在resource包下右键单击,打开快捷菜单 ⇒ 选择 “新建” ⇒ 单击“目录” 输入目录名:top/honlnk/czuan/mapper

这样可以快速创建三个嵌套目录

在这里插入图片描述

注意的是:千万不要以英文符号的.(点)间隔,如果是新版本的idea会有提示,如果是旧版本的话可能根本不会有提示

这样创建出来的文件就是一个名叫 top.honlnk.czuan的文件,并不为我们需要的 “同包同名”。
在这里插入图片描述

在这里插入图片描述

5.2.2 创建UserMapper.xml文件

在刚刚新建的目录下右键单击 选择“新建” ⇒ 单击“文件”输入:UserMapper.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="top.honlnk.czuan.mapper.UserMapper">
<insert id="addUser">
    INSERT INTO users
    <trim prefix="(" suffix=")" suffixOverrides=",">
        <if test="account != null and account != ''">account,</if>
        <if test="uname != null and uname != ''">uname,</if>
        <if test="password != null and password != ''">password,</if>
        <if test="eMail != null and eMail != ''">e_mail,</if>
        <if test="readPower != null and readPower != ''">read_power,</if>
        <if test="addPower != null and addPower != ''">add_power,</if>
        <if test="delPower != null and delPower != ''">del_power,</if>
        <if test="revisePower != null and revisePower != ''">revise_power,</if>
        <if test="powerPower != null and powerPower != ''">power_power,</if>
        <if test="reviewPower != null and reviewPower != ''">review_power,</if>
        <if test="createdAt != null and createdAt != ''">created_at,</if>
        <if test="updateAt != null and updateAt != ''">updated_at,</if>
        <if test="createdBy != null and createdBy != ''">created_by,</if>
        <if test="updateBy != null and updateBy != ''">update_by,</if>
    </trim>
    VALUES
    <trim prefix="(" suffix=")" suffixOverrides=",">
        <if test="account != null and account != ''">#{account},</if>
        <if test="uname != null and uname != ''">#{uname},</if>
        <if test="password != null and password != ''">#{password},</if>
        <if test="eMail != null and eMail != ''">#{eMail},</if>
        <if test="readPower != null and readPower != ''">#{readPower},</if>
        <if test="addPower != null and addPower != ''">#{addPower},</if>
        <if test="delPower != null and delPower != ''">#{delPower},</if>
        <if test="revisePower != null and revisePower != ''">#{revisePower},</if>
        <if test="powerPower != null and powerPower != ''">#{powerPower},</if>
        <if test="reviewPower != null and reviewPower != ''">#{reviewPower},</if>
        <if test="createdAt != null and createdAt != ''">#{createdAt},</if>
        <if test="updateAt != null and updateAt != ''">#{updatedAt},</if>
        <if test="createdBy != null and createdBy != ''">#{createdBy},</if>
        <if test="updateBy != null and updateBy != ''">#{updateBy},</if>
    </trim>
</insert>


</mapper>

5.2.3 对代码的解释:

以下代码,是MyBatis 的配置文件中的固定代码,其中:namespace 属性需要设置成与你要映射的文件位置。

<?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="top.honlnk.czuan.mapper.UserMapper">
</mapper>

以下代码,是映射的sql语句,其中:id 的值是你要映射的方法名,这里要映射的是addUser方法
<trim> 标签:
prefix suffix 用于指定前缀和后缀。
suffixOverrides 用于处理逗号,确保最后一个逗号不会出现在最终的 SQL 语句中。
<if> 标签:
检查每个字段是否不为空,如果非空则加入到列名和值列表中。

<insert id="addUser">
    INSERT INTO users
    <trim prefix="(" suffix=")" suffixOverrides=",">
        <if test="account != null and account != ''">account,</if>
        <if test="uname != null and uname != ''">uname,</if>
        <if test="password != null and password != ''">password,</if>
        <if test="eMail != null and eMail != ''">e_mail,</if>
        <if test="readPower != null and readPower != ''">read_power,</if>
        <if test="addPower != null and addPower != ''">add_power,</if>
        <if test="delPower != null and delPower != ''">del_power,</if>
        <if test="revisePower != null and revisePower != ''">revise_power,</if>
        <if test="powerPower != null and powerPower != ''">power_power,</if>
        <if test="reviewPower != null and reviewPower != ''">review_power,</if>
        <if test="createdAt != null and createdAt != ''">created_at,</if>
        <if test="updateAt != null and updateAt != ''">updated_at,</if>
        <if test="createdBy != null and createdBy != ''">created_by,</if>
        <if test="updateBy != null and updateBy != ''">update_by,</if>
    </trim>
    VALUES
    <trim prefix="(" suffix=")" suffixOverrides=",">
        <if test="account != null and account != ''">#{account},</if>
        <if test="uname != null and uname != ''">#{uname},</if>
        <if test="password != null and password != ''">#{password},</if>
        <if test="eMail != null and eMail != ''">#{eMail},</if>
        <if test="readPower != null and readPower != ''">#{readPower},</if>
        <if test="addPower != null and addPower != ''">#{addPower},</if>
        <if test="delPower != null and delPower != ''">#{delPower},</if>
        <if test="revisePower != null and revisePower != ''">#{revisePower},</if>
        <if test="powerPower != null and powerPower != ''">#{powerPower},</if>
        <if test="reviewPower != null and reviewPower != ''">#{reviewPower},</if>
        <if test="createdAt != null and createdAt != ''">#{createdAt},</if>
        <if test="updateAt != null and updateAt != ''">#{updatedAt},</if>
        <if test="createdBy != null and createdBy != ''">#{createdBy},</if>
        <if test="updateBy != null and updateBy != ''">#{updateBy},</if>
    </trim>
</insert>

5.2.4 配置

sql编写完成还需要进行一些简单的配置

比如:在返回数据的时候 需要根据字段名返回,恰巧这个字段采用的是下划线的命名方式(数据库的设计标准),但是在java代码中,定义实体类却用的是小驼峰命名(java代码开发的设计标准),这样直接映射的话,会发现根本就映射不到对应的属性中。

所以需要一个一个mybatis的配置,让系统自动帮我们映射到对应的属性中去。

在配置文件 application.properties 中输入代码mybatis.configuration.map-underscore-to-camel-case=true即可解决这一问题。

另外,项目运行的时候还需要保存一些,日志,如果你需要在日志中,保存你的数据库访问记录的话,可以使用这个配置,让数据库访问的具体信息打印在控制台,或者映射到日志文件。

mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

就就像这一 直接写在这个位置即可。

在这里插入图片描述

5.2.5 测试

编写一个测试类测试代码编写是否有错误

    @Test
    void testUserMapper() {
        userMapper.addUser(User.builder()
                .account("lisi")
                .uname("李四")
                .password("123456")
                .eMail("lisi@czuan.top")
                .build());
        log.info("添加用户成功");
    }
    @Test
    void testGetUserByAccount() {
        String account = "alice123";

        User user = userMapper.getUserByAccount(account);
        log.info("根据账号查询到的用户信息: {}", user);
    }

注意:在测试getUserByAccount方法的时候

@Select("select uname, account, e_mail, created_at, created_by from users where account = #{account}")中的e_mail在返回数据时,就涉及到了上面配置过的mybatis.configuration.map-underscore-to-camel-case=true属性。

因为如果不进行这项配置的话,那么e_mail读取出来的属性是null

5.3 提交git

6 创建服务层(Service)

6.1 创建包以及文件

服务层的重要性不言而喻,没有任何一个正经项目是可以离开服务层的,在这里就不过多赘述,直接切入正题。

在路径src/main/java/top/honlnk/czuan下新建一个名叫service的包

src/main/java/top/honlnk/czuan/service 路径下新建一个名叫UserService的接口类。

写入以下代码

package top.honlnk.czuan.service;
import top.honlnk.czuan.pojo.entity.User;
public interface UserService {
    void addUser(User user);
    User getUserByAccount(String account);
}

src/main/java/top/honlnk/czuan/service 路径下新建一个名叫impl 的包。

src/main/java/top/honlnk/czuan/service/impl 路径下新建一个名叫UserServiceImpl 类。

写入以下代码

package top.honlnk.czuan.service.impl;

import org.springframework.stereotype.Service;
import top.honlnk.czuan.pojo.entity.User;
import top.honlnk.czuan.service.UserService;

@Service
public class UserServiceImpl implements UserService {
    @Override
    public void addUser(User user) {

    }

    @Override
    public User getUserByAccount(String account) {
        return null;
    }
}

6.2 连接服务层与持久层

@Service
public class UserServiceImpl implements UserService {

    private static final Logger log = LoggerFactory.getLogger(UserServiceImpl.class);
    @Autowired
    private UserMapper userMapper;

    @Override
    public void addUser(User user) {
        user.setCreatedBy("system");
        user.setUpdateBy("system");
        userMapper.addUser(user);
        log.info("添加用户成功");
    }

    @Override
    public User getUserByAccount(String account) {
        User user = userMapper.getUserByAccount(account);
        log.info("根据账号查询到的用户信息: {}", user);
        return user;
    }
}

6.3 创建服务层的扩建模块

首先来说明一些所谓的“扩展模块”是用来干什么的

在服务层处理数据的时候难免会遇到很多复杂的逻辑,需要些很多代码,但,如果把特别多的代码都写在同一个包下面的话会让代码变得难以维护。根据笔者的习惯,会在服务层创建一个名叫module 在这个里面编写复杂的逻辑代码。

src/main/java/top/honlnk/czuan/service/impl 路径下创建一个名叫 module的包。

src/main/java/top/honlnk/czuan/service/impl/module 路径下创建一个名叫TestSIModule 的类(当然如果你想的话,也可以叫TestServiceImplModule)

写入以下代码

package top.honlnk.czuan.service.impl.module;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class TestSIModule {
    public String test() {
        log.info("这个方法啥也不做,就单纯测试");

        String test = test2();
        log.info("我老弟给我的返回值是: {}", test);
        return test;
    }

    private String test2() {
        log.info("我听我大哥的,我也啥也不干");
        return "test2";
    }
}

在需要使用的地方调用这些方法

比如:在UserServiceImpl 中写入以下在代码

 @Override
    public void addUser(User user) {
        // 在添加用户之前假设有一个特别复杂的逻辑需要处理
        TestSIModule testSIModule = new TestSIModule();

        String result = testSIModule.test();
        log.info("测试结果为: {}", result);

        User.builder()
                .createdBy("testAccount")
                .updatedBy("testAccount")
                .build();
        userMapper.addUser(user);
        log.info("添加用户成功");
    }

6.4 测试

    @Autowired
    private UserService userService;

    @Test
    void testGetUserByAccount2() {
        String account = "zhangfei";

        User user = userService.getUserByAccount(account);
        log.info("根据账号查询到的用户信息——service: {}", user);
    }

    @Test
    void testAddUser() {
        User user = User.builder()
                .account("zhangsanfeng")
                .uname("张三丰")
                .password("123456")
                .eMail("zhangsanfeng@czuan.top")
                .build();
        userService.addUser(user);
        log.info("添加用户成功——service");
    }

6.5 提交git

7 创建控制层(Controller)

在web项目中,控制层的重要程度一点也不亚于服务层,并且一个基本的控制层相对简单。

7.1 创建包以及文件

src/main/java/top/honlnk/czuan 目录下创建一个名叫controller 的包

src/main/java/top/honlnk/czuan/controller 目录下创建一个名叫UserController

写入代码

当然些这段代码的前提是要创建个AddUserDTO 类,在前面已经写过如何创建,就不做过多的赘述了。

package top.honlnk.czuan.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import top.honlnk.czuan.pojo.dto.AddUserDTO;
import top.honlnk.czuan.pojo.entity.User;
import top.honlnk.czuan.service.UserService;

@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;

    @PostMapping("/add")
    public String addUser(@RequestBody AddUserDTO addUserDTO) {

        log.info("addUserDTO: " + addUserDTO);

        User user = User.builder()
                .account(addUserDTO.getAccount())
                .uname(addUserDTO.getUname())
                .password(addUserDTO.getPassword())
                .eMail(addUserDTO.getEmail())
                .readPower(addUserDTO.getReadPower())
                .addPower(addUserDTO.getAddPower())
                .delPower(addUserDTO.getDelPower())
                .revisePower(addUserDTO.getRevisePower())
                .powerPower(addUserDTO.getPowerPower())
                .reviewPower(addUserDTO.getReviewPower())
                .build();
        log.info("user: " + user);
        userService.addUser(user);
        return "success";
    }
}

7.2 测试

使用任意接口测试工具测试即可

这里使用的是ApiFox

在这里插入图片描述

7.3 提交git

8 创建实体类(pojo)

在上面的测试类中,有这样一段代码:List<Map<String, Object>> list = testMapper.testSelect();mapper 中由于是Select * ,所以,这个查询把整个表格的内容都获取出来了,既然这样,就必须要定义一个如此复杂的类型List<Map<String, Object>> 来接收参数,这样写即麻烦又不规范,所以我们需要定义一些实体类,来作为各层之间传递参数的载体(标准)。

根据笔者的习惯:会把实体类定义为三种:DTO entity/BO VO

entity/BO(实体):通常代表业务领域中的对象,直接映射数据库的表,一个entity实体类代表一个数据表。对数据库进行增删改查,以及处理部分业务逻辑时会用到这个。

DTO(Data Transfer Object,数据传输对象):通常用于客户端与服务端或者服务端与服务端之间传递数据。

VO(View Object,视图对象):通常用于传输客户端要展示的数据。虽然DTO可以用作表示服务端向客户端传递数据,但是我更习惯将服务端向客户端单向传输数据单独用一类管理起来,这样对我设计接口文档会有一些帮助。

8.1 创建DTO

src/main/java/top/honlnk/czuan 路径下创建一个包pojo (对象)

src/main/java/top/honlnk/czuan/pojo 路径下创建一个叫dto的包

src/main/java/top/honlnk/czuan/pojo/dto 路径下创建一个叫LoginDTO的Java类

虽然还没一正式编写项目,但是90%的项目都有登录功能,所以可以先创建一个用作登录的实体类。

LoginDTO中写入这些代码

package top.honlnk.czuan.pojo.dto;

import lombok.Data;

@Data
public class LoginDTO {

    private String account;

    private String password;

    private String captchaCode;

    private Boolean isAutoLogin;
}

@Data 注解与上文中的@Slf4j 一样,同样是来之Lombok中的注解,它可以简化很多的代码。

account :账号

password :密码

captchaCode :人机验证码

isAutoLogin :是否自动登录

8.2 创建VO

src/main/java/top/honlnk/czuan/pojo 路径下创建一个叫vo的包

src/main/java/top/honlnk/czuan/pojo/vo 路径下创建一个叫LoginVO的Java类

LoginVO中写入这些代码

package top.honlnk.czuan.pojo.vo;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class LoginVO {

    private String uname;

    private String email;

    private String account;

    private String token;
}

@Builder :用于快捷构建复杂的对象实例,使代码更整洁易于维护

@AllArgsConstructor :生成一个无参构造函数

@NoArgsConstructor:生成一个包含所有参数的构造函数

8.3 创建entity

src/main/java/top/honlnk/czuan/pojo 路径下创建一个叫entity的包

src/main/java/top/honlnk/czuan/pojo/entity 路径下创建一个叫User的Java类

User中写入这些代码

package top.honlnk.czuan.pojo.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDate;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private Integer id;
    private String account;
    private String uname;
    private String password;
    private String eMail;
    private String readPower;
    private String addPower;
    private String delPower;
    private String revisePower;
    private String powerPower;
    private String reviewPower;
    private LocalDate createdAt;
    private LocalDate updatedAt;
    private String createdBy;
    private String updatedBy;
}

另外,我还创建了一个叫Words的实体类,并将刚刚编写的mapper测试类修改为标准的开发格式,项目测试同样能正常通过。

    @Test
    void contextLoads() {
        List<Words> list = testMapper.testSelect();
        log.info("单词表中所有的数据: {}", list);
    }
@Mapper
public interface TestMapper {
    @Select("select * from words")
    List<Words> testSelect();
}

8.4 提交git

与上述步骤一样

9 创建持久层(Mapper)

只有在MyBatis环境中使用Mapper表示持久层才比较合理,如果是在其他环境中,可能需要其他的名词来表示从持久层,或者直接使用Repository 表示持久层

由于前文的连接数据库章节中,已经创建了mapper包,所以在这里就不需要创建了。

9.1 创建UserMapper接口类

src/main/java/top/honlnk/czuan/mapper 路径下创建一个名叫UserMapper的接口类

UserMapper 中编写

package top.honlnk.czuan.mapper;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import top.honlnk.czuan.pojo.entity.User;

@Mapper
public interface UserMapper {
    
    void addUser(User user);
    
    @Select("select uname, account, e_mail, created_at, created_by from users where account = #{account}")
    User getUserByAccount(String account);
}

addUser()方法:添加用户,这个方法计划使用XML配置文件映射SQL语句

getUserByAccount()方法:根据账号查询用户信息,这个SQL功能较为简单,所以直接使用注解映射SQL语句,这样开发,语法简洁、高效。

9.2 创建UserMapper.xml配置文件

在MyBatis中,复杂的数据库访问方法通常会映射到一个XML文件中映射SQL语句,在这里面编写代码,可扩展度更高,能够实现的功能更复杂。

如果是简单的逻辑,则不需要使用XML文件映射,而是直接在方法的上面直接引入一个注解,然后再括号里直接写入需要的SQL语句。

XML 映射文件的名称与Mapper接口文件名称需要一致,并且将XML映射文件和Mapper接口放置在相同的包下,也就是所谓的同包同名。

由于在spring boot的工程化项目中 java这个文件夹下存放的都是java源代码,所以要想完成这个需求,因此,就只能在resources这个文件夹下建立一个名字相同的包。

9.2.1 创建同路径包文件:

在resource包下右键单击,打开快捷菜单 ⇒ 选择 “新建” ⇒ 单击“目录” 输入目录名:top/honlnk/czuan/mapper

这样可以快速创建三个嵌套目录

在这里插入图片描述

注意的是:千万不要以英文符号的.(点)间隔,如果是新版本的idea会有提示,如果是旧版本的话可能根本不会有提示

这样创建出来的文件就是一个名叫 top.honlnk.czuan的文件,并不为我们需要的 “同包同名”。

在这里插入图片描述

在这里插入图片描述

9.2.2 创建UserMapper.xml文件

在刚刚新建的目录下右键单击 选择“新建” ⇒ 单击“文件”输入:UserMapper.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="top.honlnk.czuan.mapper.UserMapper">
<insert id="addUser">
    INSERT INTO users
    <trim prefix="(" suffix=")" suffixOverrides=",">
        <if test="account != null and account != ''">account,</if>
        <if test="uname != null and uname != ''">uname,</if>
        <if test="password != null and password != ''">password,</if>
        <if test="eMail != null and eMail != ''">e_mail,</if>
        <if test="readPower != null and readPower != ''">read_power,</if>
        <if test="addPower != null and addPower != ''">add_power,</if>
        <if test="delPower != null and delPower != ''">del_power,</if>
        <if test="revisePower != null and revisePower != ''">revise_power,</if>
        <if test="powerPower != null and powerPower != ''">power_power,</if>
        <if test="reviewPower != null and reviewPower != ''">review_power,</if>
        <if test="createdAt != null and createdAt != ''">created_at,</if>
        <if test="updateAt != null and updateAt != ''">updated_at,</if>
        <if test="createdBy != null and createdBy != ''">created_by,</if>
        <if test="updateBy != null and updateBy != ''">update_by,</if>
    </trim>
    VALUES
    <trim prefix="(" suffix=")" suffixOverrides=",">
        <if test="account != null and account != ''">#{account},</if>
        <if test="uname != null and uname != ''">#{uname},</if>
        <if test="password != null and password != ''">#{password},</if>
        <if test="eMail != null and eMail != ''">#{eMail},</if>
        <if test="readPower != null and readPower != ''">#{readPower},</if>
        <if test="addPower != null and addPower != ''">#{addPower},</if>
        <if test="delPower != null and delPower != ''">#{delPower},</if>
        <if test="revisePower != null and revisePower != ''">#{revisePower},</if>
        <if test="powerPower != null and powerPower != ''">#{powerPower},</if>
        <if test="reviewPower != null and reviewPower != ''">#{reviewPower},</if>
        <if test="createdAt != null and createdAt != ''">#{createdAt},</if>
        <if test="updateAt != null and updateAt != ''">#{updatedAt},</if>
        <if test="createdBy != null and createdBy != ''">#{createdBy},</if>
        <if test="updateBy != null and updateBy != ''">#{updateBy},</if>
    </trim>
</insert>


</mapper>

9.2.3 对代码的解释:

以下代码,是MyBatis 的配置文件中的固定代码,其中:namespace 属性需要设置成与你要映射的文件位置。

<?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="top.honlnk.czuan.mapper.UserMapper">
</mapper>

以下代码,是映射的sql语句,其中:id 的值是你要映射的方法名,这里要映射的是addUser方法
<trim> 标签:
prefix suffix 用于指定前缀和后缀。
suffixOverrides 用于处理逗号,确保最后一个逗号不会出现在最终的 SQL 语句中。
<if> 标签:
检查每个字段是否不为空,如果非空则加入到列名和值列表中。

<insert id="addUser">
    INSERT INTO users
    <trim prefix="(" suffix=")" suffixOverrides=",">
        <if test="account != null and account != ''">account,</if>
        <if test="uname != null and uname != ''">uname,</if>
        <if test="password != null and password != ''">password,</if>
        <if test="eMail != null and eMail != ''">e_mail,</if>
        <if test="readPower != null and readPower != ''">read_power,</if>
        <if test="addPower != null and addPower != ''">add_power,</if>
        <if test="delPower != null and delPower != ''">del_power,</if>
        <if test="revisePower != null and revisePower != ''">revise_power,</if>
        <if test="powerPower != null and powerPower != ''">power_power,</if>
        <if test="reviewPower != null and reviewPower != ''">review_power,</if>
        <if test="createdAt != null and createdAt != ''">created_at,</if>
        <if test="updateAt != null and updateAt != ''">updated_at,</if>
        <if test="createdBy != null and createdBy != ''">created_by,</if>
        <if test="updateBy != null and updateBy != ''">update_by,</if>
    </trim>
    VALUES
    <trim prefix="(" suffix=")" suffixOverrides=",">
        <if test="account != null and account != ''">#{account},</if>
        <if test="uname != null and uname != ''">#{uname},</if>
        <if test="password != null and password != ''">#{password},</if>
        <if test="eMail != null and eMail != ''">#{eMail},</if>
        <if test="readPower != null and readPower != ''">#{readPower},</if>
        <if test="addPower != null and addPower != ''">#{addPower},</if>
        <if test="delPower != null and delPower != ''">#{delPower},</if>
        <if test="revisePower != null and revisePower != ''">#{revisePower},</if>
        <if test="powerPower != null and powerPower != ''">#{powerPower},</if>
        <if test="reviewPower != null and reviewPower != ''">#{reviewPower},</if>
        <if test="createdAt != null and createdAt != ''">#{createdAt},</if>
        <if test="updateAt != null and updateAt != ''">#{updatedAt},</if>
        <if test="createdBy != null and createdBy != ''">#{createdBy},</if>
        <if test="updateBy != null and updateBy != ''">#{updateBy},</if>
    </trim>
</insert>

9.2.4 配置

sql编写完成还需要进行一些简单的配置

比如:在返回数据的时候 需要根据字段名返回,恰巧这个字段采用的是下划线的命名方式(数据库的设计标准),但是在java代码中,定义实体类却用的是小驼峰命名(java代码开发的设计标准),这样直接映射的话,会发现根本就映射不到对应的属性中。

所以需要一个一个mybatis的配置,让系统自动帮我们映射到对应的属性中去。

在配置文件 application.properties 中输入代码mybatis.configuration.map-underscore-to-camel-case=true即可解决这一问题。

另外,项目运行的时候还需要保存一些,日志,如果你需要在日志中,保存你的数据库访问记录的话,可以使用这个配置,让数据库访问的具体信息打印在控制台,或者映射到日志文件。

mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

就就像这一 直接写在这个位置即可。

在这里插入图片描述

9.2.5 测试

编写一个测试类测试代码编写是否有错误

    @Test
    void testUserMapper() {
        userMapper.addUser(User.builder()
                .account("lisi")
                .uname("李四")
                .password("123456")
                .eMail("lisi@czuan.top")
                .build());
        log.info("添加用户成功");
    }
    @Test
    void testGetUserByAccount() {
        String account = "alice123";

        User user = userMapper.getUserByAccount(account);
        log.info("根据账号查询到的用户信息: {}", user);
    }

注意:在测试getUserByAccount方法的时候

@Select("select uname, account, e_mail, created_at, created_by from users where account = #{account}")中的e_mail在返回数据时,就涉及到了上面配置过的mybatis.configuration.map-underscore-to-camel-case=true属性。

因为如果不进行这项配置的话,那么e_mail读取出来的属性是null

9.3 提交git

10 创建服务层(Service)

10.1 创建包以及文件

服务层的重要性不言而喻,没有任何一个正经项目是可以离开服务层的,在这里就不过多赘述,直接切入正题。

在路径src/main/java/top/honlnk/czuan下新建一个名叫service的包

src/main/java/top/honlnk/czuan/service 路径下新建一个名叫UserService的接口类。

写入以下代码

package top.honlnk.czuan.service;
import top.honlnk.czuan.pojo.entity.User;
public interface UserService {
    void addUser(User user);
    User getUserByAccount(String account);
}

src/main/java/top/honlnk/czuan/service 路径下新建一个名叫impl 的包。

src/main/java/top/honlnk/czuan/service/impl 路径下新建一个名叫UserServiceImpl 类。

写入以下代码

package top.honlnk.czuan.service.impl;

import org.springframework.stereotype.Service;
import top.honlnk.czuan.pojo.entity.User;
import top.honlnk.czuan.service.UserService;

@Service
public class UserServiceImpl implements UserService {
    @Override
    public void addUser(User user) {

    }

    @Override
    public User getUserByAccount(String account) {
        return null;
    }
}

10.2 连接服务层与持久层

@Service
public class UserServiceImpl implements UserService {

    private static final Logger log = LoggerFactory.getLogger(UserServiceImpl.class);
    @Autowired
    private UserMapper userMapper;

    @Override
    public void addUser(User user) {
        user.setCreatedBy("system");
        user.setUpdateBy("system");
        userMapper.addUser(user);
        log.info("添加用户成功");
    }

    @Override
    public User getUserByAccount(String account) {
        User user = userMapper.getUserByAccount(account);
        log.info("根据账号查询到的用户信息: {}", user);
        return user;
    }
}

10.3 创建服务层的扩建模块

首先来说明一些所谓的“扩展模块”是用来干什么的

在服务层处理数据的时候难免会遇到很多复杂的逻辑,需要些很多代码,但,如果把特别多的代码都写在同一个包下面的话会让代码变得难以维护。根据笔者的习惯,会在服务层创建一个名叫module 在这个里面编写复杂的逻辑代码。

src/main/java/top/honlnk/czuan/service/impl 路径下创建一个名叫 module的包。

src/main/java/top/honlnk/czuan/service/impl/module 路径下创建一个名叫TestSIModule 的类(当然如果你想的话,也可以叫TestServiceImplModule)

写入以下代码

package top.honlnk.czuan.service.impl.module;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class TestSIModule {
    public String test() {
        log.info("这个方法啥也不做,就单纯测试");

        String test = test2();
        log.info("我老弟给我的返回值是: {}", test);
        return test;
    }

    private String test2() {
        log.info("我听我大哥的,我也啥也不干");
        return "test2";
    }
}

在需要使用的地方调用这些方法

比如:在UserServiceImpl 中写入以下在代码

 @Override
    public void addUser(User user) {
        // 在添加用户之前假设有一个特别复杂的逻辑需要处理
        TestSIModule testSIModule = new TestSIModule();

        String result = testSIModule.test();
        log.info("测试结果为: {}", result);

        User.builder()
                .createdBy("testAccount")
                .updatedBy("testAccount")
                .build();
        userMapper.addUser(user);
        log.info("添加用户成功");
    }

10.4 测试

    @Autowired
    private UserService userService;

    @Test
    void testGetUserByAccount2() {
        String account = "zhangfei";

        User user = userService.getUserByAccount(account);
        log.info("根据账号查询到的用户信息——service: {}", user);
    }

    @Test
    void testAddUser() {
        User user = User.builder()
                .account("zhangsanfeng")
                .uname("张三丰")
                .password("123456")
                .eMail("zhangsanfeng@czuan.top")
                .build();
        userService.addUser(user);
        log.info("添加用户成功——service");
    }

10.5 提交git

11 创建控制层(Controller)

在web项目中,控制层的重要程度一点也不亚于服务层,并且一个基本的控制层相对简单。

11.1 创建包以及文件

src/main/java/top/honlnk/czuan 目录下创建一个名叫controller 的包

src/main/java/top/honlnk/czuan/controller 目录下创建一个名叫UserController

写入代码

当然些这段代码的前提是要创建个AddUserDTO 类,在前面已经写过如何创建,就不做过多的赘述了。

package top.honlnk.czuan.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import top.honlnk.czuan.pojo.dto.AddUserDTO;
import top.honlnk.czuan.pojo.entity.User;
import top.honlnk.czuan.service.UserService;

@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;

    @PostMapping("/add")
    public String addUser(@RequestBody AddUserDTO addUserDTO) {

        log.info("addUserDTO: " + addUserDTO);

        User user = User.builder()
                .account(addUserDTO.getAccount())
                .uname(addUserDTO.getUname())
                .password(addUserDTO.getPassword())
                .eMail(addUserDTO.getEmail())
                .readPower(addUserDTO.getReadPower())
                .addPower(addUserDTO.getAddPower())
                .delPower(addUserDTO.getDelPower())
                .revisePower(addUserDTO.getRevisePower())
                .powerPower(addUserDTO.getPowerPower())
                .reviewPower(addUserDTO.getReviewPower())
                .build();
        log.info("user: " + user);
        userService.addUser(user);
        return "success";
    }
}

11.2 测试

使用任意接口测试工具测试即可

这里使用的是ApiFox

在这里插入图片描述

11.3 提交git

12 创建统一响应结果(result)

在项目开发的过程中,传输数据时大多数情况下采用的是json格式。所以统一一个响应格式尤为重要。

你要问的为什么没有在接收数据的时候统一格式,其实这一步我们早就已经做过了,在创建实体类的DTO的时候我们就已经规定了格式——每个接口需要接收那些数据,并且符合json格式的标准。

12.1 创建result包

因为每个人/组织都有各自的开发规范,所以创建的包结构也各有不同,你可以向我一样在包的根目录下创建一个common 包(通用包/公共包),然后将我们要创建的result 包放在其下面;也可以直接将restult 包放在根目录下。

其实前文中创建的pojo包也可以放在common这个包下面,但是我更习惯把他放在外面,具体由个人习惯而定。使用idea可以快速的实现包的移动,即便你后期开发中想要 移动包的位置也会很方便,在这里就不多赘述了。

src/main/java/top/honlnk/czuan 路径下创建一个common/result

12.2 创建Result文件(基本响应)

src/main/java/top/honlnk/czuan/common/result 创建一个名叫Result 的普通响应格式文件。

写入一下代码

package top.honlnk.czuan.common.result;

import lombok.Data;

import java.io.Serializable;

/**
 * 后端统一返回结果
 * @param <T>
 */
@SuppressWarnings({"All"})
@Data
public class Result<T> implements Serializable {

    private Integer code; //编码:1成功,0和其它数字为失败
    private String msg; //返回信息
    private T data; //数据

    public static <T> Result<T> success() {
        Result<T> result = new Result<T>();
        result.msg = "success";
        result.code = 1;
        return result;
    }

    public static <T> Result<T> success(T object) {
        Result<T> result = new Result<T>();
        result.msg = "success";
        result.data = object;
        result.code = 1;
        return result;
    }

    public static <T> Result<T> error(String msg) {
        Result result = new Result();
        result.msg = msg;
        result.code = 0;
        return result;
    }

}

12.3 应用Result

在controller中调用一次查看是否可以正常使用

    @PostMapping("/add")
    public Result<String> addUser(@RequestBody AddUserDTO addUserDTO) {

        log.info("addUserDTO: " + addUserDTO);

        User user = User.builder()
                .account(addUserDTO.getAccount())
                .uname(addUserDTO.getUname())
                .password(addUserDTO.getPassword())
                .eMail(addUserDTO.getEmail())
                .readPower(addUserDTO.getReadPower())
                .addPower(addUserDTO.getAddPower())
                .delPower(addUserDTO.getDelPower())
                .revisePower(addUserDTO.getRevisePower())
                .powerPower(addUserDTO.getPowerPower())
                .reviewPower(addUserDTO.getReviewPower())
                .build();
        log.info("user: " + user);
        userService.addUser(user);
        return Result.success();
    }

    @GetMapping("/getUserByAccount")
    public Result<User> getUserByAccount(@RequestParam("account") String account) {
        User user = userService.getUserByAccount(account);
        return Result.success(user);
    }

12.4 创建PageResult文件(分页查询响应)

src/main/java/top/honlnk/czuan/common/result 创建一个名叫PageResult 的分页查询响应格式文件。

写入以下代码

package top.honlnk.czuan.common.result;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.List;

/**
 * 封装分页查询结果
 */
@SuppressWarnings({"All"})
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageResult implements Serializable {

    private long total; //总记录数

    private List records; //当前页数据集合

}

12.5 应用PageResult

应用PageResult比应用Result相对复杂一些

12.5.1 创建分页查询需要的DTO

package top.honlnk.czuan.pojo.dto;

import lombok.Data;

@Data
public class PageGetUserDTO {
    private int page;
    private int size;
    private String uname;
    private String account;
    private String readPower;
    private String addPower;
    private String delPower;
    private String revisePower;
    private String powerPower;
    private String reviewPower;
}

12.5.2 创建用于展示用户查询结果需要的VO

在这之前,我们创建的根据账号查询用户信息,返回结果用的是user类,这再实际开发中是很不规范的,所以我们需要专门创建一个用作客户端展示信息的VO

package top.honlnk.czuan.pojo.vo;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class GetUserInfoVO {
    private String uname;
    private String account;
    private String eMail;

    @JsonFormat(pattern = "yyyy/MM/dd")
    private LocalDate createdAt;
    private String createdBy;
}


12.5.3 创建分页查询需要的service

再些业务代码之前需要添加一个必要的依赖,需要在pom.xml文件中写入一下代码

<dependencies>
    ...
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>2.1.0</version> <!-- 值得注意的是,一定要注意版本,最好是使用最新版本,不然插件有可能会失效 -->
        </dependency>
    ...
</dependencies>

PageHelper 是一个辅助我们实现分页查询的插件;下一条sql会自动加入limit关键字分页。

另外根据PageHelper的要求,还需要用Page对象来接收查询的数据,并且再泛型<>中限制数据类型。

使用getTotal() 方法可以获取符合条件的总数,使用getResult() 方法可以获取规定的那一页的数据。

注意:service 返回数据的时候需要返回一个刚刚定义的PageResult 类型的数据,因为在控制层需要将这些数据存储在Result 中一并返回给前端。

    PageResult getUser(PageGetUserDTO pageGetUserDTO);
    @Override
    public PageResult getUser(PageGetUserDTO pageGetUserDTO) {
        log.info("查询用户信息,参数为: {}", pageGetUserDTO);
        PageHelper.startPage(pageGetUserDTO.getPage(), pageGetUserDTO.getSize());
        Page<User> user = userMapper.getUser(pageGetUserDTO);
        long total = user.getTotal();
        List<User> records = user.getResult();
        List<GetUserInfoVO> getUserInfoVOList = records.stream().map(item -> GetUserInfoVO.builder()
                .account(item.getAccount())
                .uname(item.getUname())
                .eMail(item.getEMail())
                .createdAt(item.getCreatedAt())
                .createdBy(item.getCreatedBy())
                .build()).toList();
        return new PageResult(total, getUserInfoVOList);
    }

12.5.4 创建分页查询需要的mapper

为了响应服务层userMapper.getUser() 方法接收的数据对象类型,所以在mapper层中,也需要返回一个Page类型的数据

    Page<User> getUser(PageGetUserDTO pageGetUserDTO);
<select id="getUser" resultType="top.honlnk.czuan.pojo.entity.User">
    SELECT
        *
    FROM users
    <where>
        <if test="account != null and account != ''">
            and account = #{account}
        </if>
        <if test="uname != null and uname != ''">
            and uname like concat('%', #{uname}, '%')
        </if>
        <if test="readPower != null and readPower != ''">
            and read_power = #{readPower}
        </if>
        <if test="addPower != null and addPower != ''">
            and add_power = #{addPower}
        </if>
        <if test="delPower != null and delPower != ''">
            and del_power = #{delPower}
        </if>
        <if test="revisePower != null and revisePower != ''">
            and revise_power = #{revisePower}
        </if>
        <if test="powerPower != null and powerPower != ''">
            and power_power = #{powerPower}
        </if>
        <if test="reviewPower != null and reviewPower != ''">
            and review_power = #{reviewPower}
        </if>

    </where>
    order by created_at desc
</select>

12.5.5 创建分页查询需要的controller

在控制层中需要接收一个PageGetUserDTO 类型的数据。

    @GetMapping("/getUser")
    public Result<PageResult> getUser(PageGetUserDTO pageGetUserDTO) {
        PageResult pageResult = userService.getUser(pageGetUserDTO);
        return Result.success(pageResult);
    }

12.5.6 测试

在这里插入图片描述

12.6 提交git

13 创建全局异常处理(exception)

每一个项目中,异常处理是必不可少的无论是因为系统抛出的报错,还是自定义的异常,异常处理都是必不可少的。

13.1 创建全局异常处理器(捕获异常)

src/main/java/top/honlnk/czuan/common/ 路径下创建个名为exception/handler 的嵌套包

src/main/java/top/honlnk/czuan/common/exception/handler路径下创建一个名为GlobalExceptionHandler 的类文件

写入代码

Exception() 方法可以捕获所有的异常,但是优先级最低

package top.honlnk.czuan.common.exception.handler;

import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import top.honlnk.czuan.common.result.Result;


@SuppressWarnings("All")
@Slf4j
@ControllerAdvice(basePackages = "top.honlnk.czuan.controller")
public class GlobalExceptionHandler {

    // 统一异常处理@ExceptionHandler,主要用于Exception
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public Result<String> Exception(HttpServletRequest request, Exception e) {
        log.error("全局异常捕获: ", e);
        return Result.error("系统异常");
    }
}


13.2 创建自定义异常处理(自定义异常)

src/main/java/top/honlnk/czuan/common/exception/路径下创建一个名为CustomException的类文件

写入以下代码

package top.honlnk.czuan.common.exception;

@SuppressWarnings("ALL")
public class CustomException extends RuntimeException{
    private String msg;

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public CustomException(String msg) {
        this.msg = msg;
    }
}


GlobalExceptionHandler 类中添加捕获自定义异常

CustomException() 方法可以捕获所有开发者自己定义的异常并返回自己想要的异常信息

    @ExceptionHandler(CustomException.class)
    @ResponseBody
    public Result<String> CustomException(HttpServletRequest request, CustomException e) {
        log.error("自定义异常: {}", e.getMsg());
        return Result.error(e.getMsg());
    }

13.3 应用自定义异常

在UserServiceImpl文件下,将分页查询用户的条件进行稍加限制

getUser() 方法中加入一个if判断判断pagesize 的值是否为0,如果为0,那么通过 throw new关键字 将错误信息抛出

    @Override
    public PageResult getUser(PageGetUserDTO pageGetUserDTO) {
        log.info("查询用户信息,参数为: {}", pageGetUserDTO);
        if (pageGetUserDTO.getPage() == 0 || pageGetUserDTO.getSize() == 0) {
            throw new CustomException("page或size为必选参数且值不能为0"); // page与size这两个参数不允许为0
        }
        ......
        ......
        return new PageResult(total, getUserInfoVOList);
    }

13.4 捕获指定异常信息

依然用刚刚的pagesize 举例,如果前端想后端发送请求的时候,将这两个值设置为0或者不写这两个值,都会默认为0,所以触发上面的自定义异常,但是如果写了这两个值但是没有正确赋值的话(比如一个空值或者一个字符串)那么就不会触发自定义异常,并且项目也不会正常运行。在运行时会触发一个叫MethodArgumentNotValidException 的异常。

在这里插入图片描述

捕获指定异常,并返回有用的报错信息:在GlobalExceptionHandler 中加入以下代码

@ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    public Result<String> exceptionHandler(MethodArgumentNotValidException e) {
        String message = e.getMessage();


        if (message.contains("Failed to convert property value")) {
            // 存储字段名
            StringBuilder fieldNameBuilder = new StringBuilder();

            // 正则表达式模式
            String pattern = "Failed to convert property value of type.*?for property '(.*?)'; For input string: \"(.*?)\"";
            Pattern r = Pattern.compile(pattern, Pattern.DOTALL);
            Matcher m = r.matcher(message);

            while (m.find()) {
                String fieldName = m.group(1);
                // 拼接字段名,中间用“、”隔开
                if (!fieldNameBuilder.isEmpty()) {
                    fieldNameBuilder.append("、");
                }
                fieldNameBuilder.append(fieldName);
            }
            // 最终的字段名字符串
            String fieldNames = fieldNameBuilder.toString();

            // 构造返回结果
            log.error("参数校验失败: {}", fieldNames + "参数类型格式不符合要求");
            return Result.error(fieldNames + "参数类型格式不符合要求");
        } else {
            return Result.error("参数校验失败" + e);
        }
    }

如果参数不正确,就会得到以下报错信息。

在这里插入图片描述

13.5 提交git

14 请求拦截。

加入请求蓝饥饿之后,前端在请求后端接口之前,会先进入创建的请求拦截类中,执行里面的代码。在这里面可以做很多必要的工作,比如解决跨域、添加路由前缀、JWT鉴权等等。

14.1 添加路由前缀 /api

为了不与前端的浏览器路由的路径重复,在实际开发的过程中,一般还需要给所有的接口前面拼接一级前端几乎不可能用到的路径名称,目的是为了避免二级路由重名带来的一系列前后端交互问题。

src/main/java/top/honlnk/czuan/common 创建一个名叫config 的包

src/main/java/top/honlnk/czuan/common/config 包下创建一个名叫WebMvcConfig的类

写入以下代码

package top.honlnk.czuan.common.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        // 指定controller统一的接口前缀
        configurer.addPathPrefix("/api", c -> c.isAnnotationPresent(RestController.class));
    }
}

14.2 在后端解决跨域问题

解决跨域的方法有很多,开发阶段,可以在前端解决跨域,项目部署阶段可以在Nginx解决跨域,但是在后端解决跨域也是一个非常不错的选择。如果你的项目需要的话,可以加入以下代码解决跨域问题。

同样是在WebMvcConfig文件下加入以下代码

addMapping("/api/**"):解决所有/api/路由开头的路由进行访问

allowedOrigins("*"):允许的来源为全部来源,也就是说,在任意地方请求都可以请求这些接口

allowedMethods("GET", "POST", "PUT", "DELETE"):允许的方法包括GET、POST、PUT和DELETE

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedOrigins("*")
                .allowedMethods("GET", "POST", "PUT", "DELETE");
    }

14.3 提交git

15 JWT登录鉴权

在实现类直线,需要添加两个包,分别是java-jwthutool-all

java-jwt:是jwt必不可少的包

hutool-all:是一个国产化的工具包,里面有很多工具可以帮助我们快速编码。

在pom.xml文件中加入以下代码

    </dependencies>
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>4.4.0</version>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.25</version>
        </dependency>
    </dependencies>

15.1 创建JWT工具类

src/main/java/top/honlnk/czuan/common目录下创建一个名叫utils 的包

src/main/java/top/honlnk/czuan/common/utils 目录下创建一个名叫JWTTokenUtil 的类

写入以下代码

package top.honlnk.czuan.common.utils;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import top.honlnk.czuan.pojo.entity.User;
import top.honlnk.czuan.service.UserService;

import java.util.Date;

@Slf4j
public class JWTTokenUtil {
    private static UserService staticUserService;

    @Resource
    private UserService userService;

    @PostConstruct
    public void serUserService() {
        staticUserService = userService;
    }

    /**
     * 生成token
     */
    public static String genToken(String userId, String password) {
        return JWT.create().withAudience(userId) // 将 user id 保存到 token 里面,作为载荷
                .withExpiresAt(DateUtil.offsetHour(new Date(), 6))  // 6小时后过期
                .sign(Algorithm.HMAC256(password)); // 以 password 作为 token 的密钥
    }


    /**
     * 获取当前登录的用户信息
     */
    public static User getCurrentUser() {
        String token;
        try {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
                token = request.getHeader("token");
            if (StrUtil.isBlank(token)) {
                    token = request.getParameter("token");
            }
            if (StrUtil.isBlank(token)) {
                log.error("获取当前登录用户信息失败:{}", token);
                return null;
            }

            // 解析token,获取用户信息
            String userId = JWT.decode(token).getAudience().get(0);
            return staticUserService.getUserById(Integer.valueOf(userId));
        } catch (Exception e) {
            log.error("获取当前登录用户信息失败", e);
            return null;
        }
    }
}

15.2 生成Token

15.2.1 编写登录有关的Controller Service 以及Mapper

因为生成Token只会发生在登录阶段,所以我们需要一个登录的例子作为“生成Token”的载体。

    @PostMapping("/login")
    public Result<LoginVO> login(@RequestBody LoginDTO loginDTO) {
        LoginVO loginVO = userService.login(loginDTO);
        return Result.success(loginVO);
    }
   LoginVO login(LoginDTO loginDTO);
    @Override
    public LoginVO login(LoginDTO loginDTO) {
        log.info("用户登录,参数为: {}", loginDTO);
        if (loginDTO.getAccount() == null || loginDTO.getAccount().isEmpty()) {
            throw new CustomException("账号不能为空");
        }
        if (loginDTO.getPassword() == null || loginDTO.getPassword().isEmpty()) {
            throw new CustomException("密码不能为空");
        }
//        if (loginDTO.getCaptchaCode() == null || loginDTO.getCaptchaCode().isEmpty()){
//            throw new CustomException("验证码不能为空");
//        }
        User user = userMapper.getUserByAccount(loginDTO.getAccount());
        if (user == null) {
            throw new CustomException("账号不存在");
        }
        if (user.getPassword().equals(loginDTO.getPassword())) {
        // TODO 在这里生成token
            return LoginVO.builder()
                    .account(user.getAccount())
                    .uname(user.getUname())
                    .eMail(user.getEMail())
                    .token("假设有个token")
                    .build();
        } else {
            throw new CustomException("密码错误");
        }
    }

15.2.2 将生成token的方法应用在登录功能中

在UserServiceImpl中将代码补充完成

    @Override
    public LoginVO login(LoginDTO loginDTO) {
        log.info("用户登录,参数为: {}", loginDTO);
        ......
        if (user.getPassword().equals(loginDTO.getPassword())) {
            // 生成token  将id转为String类型之后与password一同传入
            String token = JWTTokenUtil.genToken(user.getId().toString(), loginDTO.getPassword());
            return LoginVO.builder()
                    .account(user.getAccount())
                    .uname(user.getUname())
                    .eMail(user.getEMail())
                    .token(token)
                    .build();
        } else {
            throw new CustomException("密码错误");
        }
    }

15.3 创建JWT拦截器

刚刚创建的是一个JWT工具类,并不能实现拦截功能。

src/main/java/top/honlnk/czuan/common 路径下创建一个名叫interceptor 的包

src/main/java/top/honlnk/czuan/common/interceptor 路径下创建一个名叫JWTInterceptor 的类

写入以下代码

package top.honlnk.czuan.common.interceptor;

import cn.hutool.core.util.StrUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import top.honlnk.czuan.common.exception.CustomException;
import top.honlnk.czuan.pojo.entity.User;
import top.honlnk.czuan.service.UserService;

/**
 * JWT拦截器
 */

@Slf4j
@Component
public class JWTInterceptor implements HandlerInterceptor {

    @Resource
    private UserService userService;

    @Override
    public boolean preHandle(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler) {
        // 1. 获取请求头中的token
        String token = request.getHeader("token");
        if (StrUtil.isBlank(token)) {
            token = request.getParameter("token");
        }
        // 2. 判断是否为空,或者非法
        if (StrUtil.isBlank(token)) {
            throw new CustomException("无token,请重新登录");
        }

        // 3. 获取token中的用户信息
        String userId;
        User user;
        try {
            userId = JWT.decode(token).getAudience().get(0);
            // 4. 根据用户id查询用户信息
            user = userService.getUserById(Integer.valueOf(userId));
        } catch (Exception e) {
            String errMsg = "非法token,请重新登录";
            log.error("{},token={}", errMsg, token, e);
            throw new CustomException(errMsg);
        }
        if (user == null) {
            throw new CustomException("用户不存在,请重新登录");
        }
        // 5. 用户密码加签验证 token
        try {
            JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
            jwtVerifier.verify(token);
        } catch (Exception e) {
            String errMsg = "token已过期,请重新登录";
            log.error("{},token={}", errMsg, token, e);
            throw new CustomException(errMsg);
        }
        return true;
    }
}

15.3.1 如果你不考虑在前端做跨域的的话,还需要再preHandle方法的最上方加入一段代码

 @Override
    public boolean preHandle(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler) {
      
        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
            // 这是一个预检请求,不记录日志,直接返回200状态码
            response.setStatus(HttpStatus.OK.value());
            return true;
        }
        
        // 1. 获取请求头中的token
        String token = request.getHeader("token");
        if (StrUtil.isBlank(token)) {
            token = request.getParameter("token");
        }
        ······ ······
        ······ ······
}

预检请求(Preflight Request)

当你在Vue前端应用中尝试向不同源(即不同的域名、端口或协议)的Spring Boot后端API发送请求时,浏览器为了遵守CORS(跨源资源共享)策略,会首先发送一个预检请求(OPTIONS方法)。这个请求是为了确认实际请求是否会被服务器接受,特别是确认请求头中的自定义头是否会得到服务器的支持。

对于预检请求,通常不需要执行业务逻辑,只需返回一个成功响应即可。
并且这个预检请求,也不会真正的执行业务代码,所以不会影响我们的正常请求信息。
判断到是预检请求,直接以成功请求的方式放行即可

请添加图片描述

15.4 登录验证(请求拦截)

所谓登录验证,就是用户在进行必须与登录之后才能进行的操作的时候拦截并验证账号的登录状态,如果token无效则证明,视为账号没有登录,中断访问,拒绝用户访问服务端。

既然这也是个请求拦截,那么就要在我们先前创建的WebMvcConfig 中加入以下代码

这段代码会拦击所有符合定义的接口请求,然后去执行JWTInterceptor中的方法。

    /**
     * 加入自定义拦截器JWTInterceptor,设置拦截规则
     */
    @Resource
    private JWTInterceptor jwtInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(jwtInterceptor)
                .addPathPatterns("/api/**")
                .excludePathPatterns("/api/user/login");
                // .excludePathPatterns("/api/user/register"); // 注册接口:后期如果编写注册接口,再启用
                // .excludePathPatterns("/api/public/**");  // 公共接口:后期如果编写公共接口,再启用
    }

如果输入一个虚假的token:

在这里插入图片描述

如果不输入token

在这里插入图片描述

15.5 权限校验

权限校验就是在JWT的基础上扩展的内容。

在上面实现的JWT登录验证中,只是用到了账号和密码,如果是权限娇艳的话,加入权限即可,当前权限校验的方法多种多样,在这里我就随便举例一种方式。

15.5.1 在生成token的时候将权限字段也加入到里面

因为我在设计数据库的时候将所有的权限都以字段的方式设计在了用户表里面,所以我采取的策略是直接将每个字段加入到token里面。

JWTokenUtil 类中将getToken字段修改成这个样子

    public static String genToken(String userId, String password,
                                  String readPower, String addPower, String delPower,
                                  String revisePower, String powerPower, String reviewPower) {
        return JWT.create()
                .withAudience(userId) // 将 user id 保存到 token 里面,作为载荷
                .withExpiresAt(DateUtil.offsetHour(new Date(), 6))  // 6小时后过期
                .withClaim("password", password)  // 加入密码
                .withClaim("readPower", readPower)  // 加入读取权限
                .withClaim("addPower", addPower)  // 加入添加权限
                .withClaim("delPower", delPower)  // 加入删除权限
                .withClaim("revisePower", revisePower)  // 加入修改权限
                .withClaim("powerPower", powerPower)  // 加入权限管理权限
                .withClaim("reviewPower", reviewPower)  // 加入审核权限
                .sign(Algorithm.HMAC256(password));// 以 password 作为 token 的密钥
    }

修改了getToken,自然也要修改调用这个方法的类:在UserServiceImpl类中修改login 方法

写法有些略显笨拙,但是胜在好理解,如果基础比较牢固的兄弟可以自行修改写法,让编码变得更优雅。

    @Override
    public LoginVO login(LoginDTO loginDTO) {
        log.info("用户登录,参数为: {}", loginDTO);
        if (loginDTO.getAccount() == null || loginDTO.getAccount().isEmpty()) {
            throw new CustomException("账号不能为空");
        }
        if (loginDTO.getPassword() == null || loginDTO.getPassword().isEmpty()) {
            throw new CustomException("密码不能为空");
        }
//        if (loginDTO.getCaptchaCode() == null || loginDTO.getCaptchaCode().isEmpty()){
//            throw new CustomException("验证码不能为空");
//        }
        User user = userMapper.getUserByAccount(loginDTO.getAccount());
        if (user == null) {
            throw new CustomException("账号不存在");
        }
        if (user.getPassword().equals(loginDTO.getPassword())) {
            // 生成token
            String token = JWTTokenUtil.genToken(
                    user.getId().toString(),
                    loginDTO.getPassword(),
                    user.getReadPower(),
                    user.getAddPower(),
                    user.getDelPower(),
                    user.getRevisePower(),
                    user.getPowerPower(),
                    user.getReviewPower()
            );
            return LoginVO.builder()
                    .account(user.getAccount())
                    .uname(user.getUname())
                    .eMail(user.getEMail())
                    .token(token)
                    .build();
        } else {
            throw new CustomException("密码错误");
        }
    }

15.5.2 在解析token中的所有用户信息

JWTokenUtil 类中,将创建软件时写入的getCurrentUser 方法进行扩展。

public static User getCurrentUser() {
        String token;
        try {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            token = request.getHeader("token");

            if (StrUtil.isBlank(token)) {
                token = request.getParameter("token");
            }

            if (StrUtil.isBlank(token)) {
                log.error("获取当前登录用户信息失败:{}", token);
                return null;
            }

            // 解析 token,获取用户信息
            var decodedToken = JWT.decode(token);
            String userId = decodedToken.getAudience().get(0);
            String password = decodedToken.getClaim("password").asString();
            String readPower = decodedToken.getClaim("readPower").asString();
            String addPower = decodedToken.getClaim("addPower").asString();
            String delPower = decodedToken.getClaim("delPower").asString();
            String revisePower = decodedToken.getClaim("revisePower").asString();
            String powerPower = decodedToken.getClaim("powerPower").asString();
            String reviewPower = decodedToken.getClaim("reviewPower").asString();

            // 构建用户对象
            return User.builder()
                    .readPower(readPower)
                    .addPower(addPower)
                    .delPower(delPower)
                    .revisePower(revisePower)
                    .powerPower(powerPower)
                    .reviewPower(reviewPower)
                    .build();
                    
        } catch (Exception e) {
            log.error("获取当前登录用户信息失败", e);
            return null;
        }
    }

src/main/java/top/honlnk/czuan/controller 路径下创建一个名叫WordController 的类

为了减小文档的篇幅,就不过多阐述功能的实现,在这里就仅仅是基于控制层,展示用法。

写入以下代码

package top.honlnk.czuan.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import top.honlnk.czuan.common.exception.CustomException;
import top.honlnk.czuan.common.result.Result;
import top.honlnk.czuan.common.utils.JWTTokenUtil;
import top.honlnk.czuan.pojo.entity.User;

@Slf4j
@RestController
@RequestMapping("/word")
public class WordController {

    private User getUser() {
        return JWTTokenUtil.getCurrentUser();
    }

    @GetMapping("/getWord")
    public Result<String> getWord() {
        String readPower = getUser().getReadPower();
        log.info("getWord: {}", readPower);
        if (readPower.equals("1")) {
            return Result.success("您的权限可以阅读单词");
        } else {
            throw new CustomException("权限不足,您不可读取单词");
        }
    }

    @PostMapping("/addWord")
    public Result<String> addWord() {
        String addPower = getUser().getAddPower();
        log.info("addWord: {}", addPower);
        if (addPower.equals("1")) {
            return Result.success("您的权限可以修改单词");
        } else {
            throw new CustomException("权限不足,您不可修改单词");
        }
    }
}


在这段代码中,定义了两个接口分别是getWordaddWord 分别是单词读取权限和修改权限,在下面的实例中,此用户是没有修改权限的,智能读取,所以当他访问addWord 接口的时候会提示权限不足,不可修改单词。

在这里插入图片描述

在这里插入图片描述

15.6 提交git

16 创建配置常量

在这里,所谓常量就是一些项目中固定的字符串,比如刚上面例子中的:权限不足,您不可修改单词 如果这种字符串中有错别字,或者开发的过程中想要修改这些汉字的话在项目中找起来会很麻,所以笔者很推荐单独的用一个类管理起来,会节省大量的维护成本。

16.1 创建常量类

src/main/java/top/honlnk/czuan/common 路径下创建一个名叫constant 的包

src/main/java/top/honlnk/czuan/common/constant 路径下创建一个名叫WordConstant的类

写入以下代码

package top.honlnk.czuan.common.constant;

@SuppressWarnings("ALL")
public class WordConstant {
    // 拥有此权限
    public static final String ENABLE_POWER = "1";
    // 没有此权限
    public static final String DISABLE_POWER = "0";

    // 您的权限可以阅读单词
    public static final String ENABLE_READ_WORD = "您的权限可以阅读单词";
    // 您的权限可以添加单词
    public static final String ENABLE_ADD_WORD = "您的权限可以添加单词";
    // 您的权限可以删除单词
    public static final String ENABLE_DEL_WORD = "您的权限可以删除单词";
    // 您的权限可以修改单词
    public static final String ENABLE_REVISE_WORD = "您的权限可以修改单词";
    // 您的权限可以管理单词
    public static final String ENABLE_POWER_POWER = "您的权限可以管理权限";
    // 您的权限可以评论单词
    public static final String ENABLE_REVIEW_WORD = "您的权限可以审核单词";


    // 权限不足,您不可阅读单词
    public static final String DISABLE_READ_WORD = "权限不足,您不可阅读单词";
    // 权限不足,您不可添加单词
    public static final String DISABLE_ADD_WORD = "权限不足,您不可添加单词";
    // 权限不足,您不可删除单词
    public static final String DISABLE_DEL_WORD = "权限不足,您不可删除单词";
    // 权限不足,您不可修改单词
    public static final String DISABLE_REVISE_WORD = "权限不足,您不可修改单词";
    // 权限不足,您不可管理单词
    public static final String DISABLE_POWER_POWER = "权限不足,您不可管理权限";
    // 权限不足,您不可评论单词
    public static final String DISABLE_REVIEW_WORD = "权限不足,您不可评论单词";
}


16.2 应用常量类

WordController 类中的代码修改为:

package top.honlnk.czuan.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import top.honlnk.czuan.common.constant.WordConstant;
import top.honlnk.czuan.common.exception.CustomException;
import top.honlnk.czuan.common.result.Result;
import top.honlnk.czuan.common.utils.JWTTokenUtil;
import top.honlnk.czuan.pojo.entity.User;

@Slf4j
@RestController
@RequestMapping("/word")
public class WordController {

    private User getUser() {
        return JWTTokenUtil.getCurrentUser();
    }

    @GetMapping("/getWord")
    public Result<String> getWord() {
        String readPower = getUser().getReadPower();
        log.info("getWord: {}", readPower);
        if (readPower.equals(WordConstant.ENABLE_POWER)) {
            return Result.success(WordConstant.ENABLE_READ_WORD);
        } else {
            throw new CustomException(WordConstant.DISABLE_READ_WORD);
        }
    }

    @PostMapping("/addWord")
    public Result<String> addWord() {
        String addPower = getUser().getAddPower();
        log.info("addWord: {}", addPower);
        if (addPower.equals(WordConstant.ENABLE_POWER)) {
            return Result.success(WordConstant.ENABLE_ADD_WORD);
        } else {
            throw new CustomException(WordConstant.DISABLE_ADD_WORD);
        }
    }
}


16.3 提交git

将git提交成功之后合并分支

  1. 将分支切换到master

    右击master 选择Checkout 将分切换至master

    在这里插入图片描述

  2. develop 分支合并到master

    右击develop 选择 Merge'devilop'into'master' 将分支合并

    在这里插入图片描述

  3. 推送master 分支

    右击master 选择Push... 将分支推送到远程

    在这里插入图片描述

17 总结

好了,文档到此为止了,本文档总共讲述了

  1. 如何新建项目
  2. 如何使用git管理项目
  3. 如果使用远程git仓库
  4. 如何连接数据库
  5. 创建实体类
  6. 创建持久层
  7. 创建服务层
  8. 创建控制层
  9. 创建统一响应结果
  10. 创建全局异常处理
  11. 请求拦截
  12. JWT登录鉴权
  13. 创建配置常量

以上内容即为市面上常见的最基础的项目配置。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值