前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >「手把手」 Spring Boot 实现 TODO 项目

「手把手」 Spring Boot 实现 TODO 项目

作者头像
Jimmy_is_jimmy
发布2024-02-21 11:10:30
1480
发布2024-02-21 11:10:30
举报
文章被收录于专栏:call_me_Rcall_me_R

我们使用 Spring Boot 从零开始实现一个 TODO 项目,实现的项目,不包含真实上线的流程。

开发环境

  • MacBook Air - Sonoma 14.2
  • IntelliJ IDEA 2021.2.2
  • Google Chrome - 版本 120.0.6099.129(正式版本) (arm64)
  • Navicat Premium - 16.0.12
  • Postman - Version 8.12.1

项目搭建

我们先创建项目。

New Project
  • Atifact 填写 - todo-service
  • Group 填写 - com.jimmy
  • Java 选择版本 17

Go next.

  • Spring Boot 选择 3.2.1

Dependencies 选择如下:

  • Spring Web
  • Lombok
  • Spring Data JPA
  • MySQL Driver
添加 mysql

Navicat Premium 中新建数据库:

  • 数据库名:todo_service
  • 字符集:utf8mb4
  • 排序规则:utf8mb4_general_ci

And then.

在生成的项目中 src/main/resources/application.properties 文件中,添加下面的内容:

代码语言:javascript
复制
spring.datasource.url=jdbc:mysql://localhost:3306/todo_service
spring.datasource.username=root
spring.datasource.password=

spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect
spring.jpa.hibernate.ddl-auto=update
运行项目

执行项目运行,控制台没有报错,项目运行成功。

创建 Controller

我们简单创建一个 Controller 的案例 Demo,来了解过程。

创建 controller 的 Demo

在项目 src/main/java/com/jimmy.todoservice 下,创建包,其名为 controller

And then.

在包 controller 下,创建类,其名为 Demo,写入如下的代码:

代码语言:javascript
复制
package com.jimmy.todoservice.controller;

@RestController
@RequestMapping("api")
public class Demo {
  @GetMapping("/hello")
  public String sayHello() {
    return "Hello World!";
  }
}
验证

运行项目后,在 postman 上执行接口:

代码语言:javascript
复制
method [GET]
url [http://localhost:8080/api/hello]

能够正确输出 Hello World! 的信息。

数据写入 MySql

真实的项目,我们需要有自己的数据库来存储数据。这里,我们选择了 MySql

添加数据库表映射

项目进入 src/main/java/com.jimmy.todoservice 下创建包 entity

And then.

然后在包 entity 下创建类 Demo,然后填写下面的内容:

代码语言:javascript
复制
package com.jimmy.todoservice.entity;

@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "demo")
public class Demo {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  @Column(name = "name", nullable = false)
  private String name;
}

此时,如果我们执行项目,则会创建名为 demo 的数据表。我们可以进入 Navicat Premium 中数据库 todo_service 下查看表 demo,该表内有两个字段,分别为 idname

添加对应的 TDO

我们创建了 entity,下面我们创建相关的 tdo,方便前端数据的写入。

我们在 src/main/java/com.jimmy.todoservice 下创建包,名为 dto

And then.

在包 dto 下面创建类,名为 DemoDto,添加下面的内容:

代码语言:javascript
复制
package com.jimmy.todoservice.dto;

@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class DemoDto {
  private Long id;
  private String name;
}
添加对应的 Repository 来与数据库建立联系

src/main/java/com.jimmy.todoservice 下创建包,名为 repository

And then.

在包 repository 下创建接口类文件,名为 DemoRepository,添加下面的内容:

代码语言:javascript
复制
package com.jimmy.todoservice.repository;

public interface DemoRepository extends JpaRepository<Demo, Long> {
}
创建对应的服务 service

src/main/java/com.jimmy.todoservice 下创建包,名为 service

And then.

在包 service 中创建接口类 DemoService,并添加下面的内容:

代码语言:javascript
复制
package com.jimmy.todoservice.service;

public interface DemoService {
  // Add demo item
  DemoDto addDemoItem(DemoDto demoDto);
}

在包 service 下面创建包 impl

And then.

service/impl 下创建类 DemoServiceImpl,并添加下面的内容:

代码语言:javascript
复制
package com.jimmy.todoservice.service.impl;

@Service
@AllArgsConstructor
public class DemoServiceImpl implements DemoService {

  private DemoRepository demoRepository;

  @Override
  public DemoDto addDemoItem(DemoDto demoDto) {
    return null;
  }
}

在完善上面 DemoServiceImpl.java 文件之前,我们先添加 modelMapper,用于 dtoentity 数据的转换。

添加 modelMapper

我们在 pom.xml 中,添加下面的依赖:

代码语言:javascript
复制
<!-- https://mvnrepository.com/artifact/org.modelmapper/modelmapper -->
<dependency>
    <groupId>org.modelmapper</groupId>
    <artifactId>modelmapper</artifactId>
    <version>3.2.0</version>
</dependency>

安装上面的依赖后,在入口文件 TodoServiceApplication 中添加下面的内容:

代码语言:javascript
复制
@Bean
public ModelMapper modelMapper() {
  ModelMapper modelMapper = new ModelMapper();
  modelMapper.getConfiguration()
          .setMatchingStrategy(MatchingStrategies.STRICT); // https://stackoverflow.com/questions/58838964/modelmapper-failed-to-convert-java-lang-string-to-java-lang-long
  return modelMapper;
}

整个文件 TodoServiceApplication.java 的内容(移除了 import 引入)如下:

代码语言:javascript
复制
package com.jimmy.todoservice;

@SpringBootApplication
public class TodoServiceApplication {

  // 使用 model mapper
  @Bean
  public ModelMapper modelMapper() {
    ModelMapper modelMapper = new ModelMapper();
    modelMapper.getConfiguration()
            .setMatchingStrategy(MatchingStrategies.STRICT); // https://stackoverflow.com/questions/58838964/modelmapper-failed-to-convert-java-lang-string-to-java-lang-long
    return modelMapper;
  }

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

}

OK,我们返回上面的 service/impl/DemoServiceImpl.java 文件。

完善 DemoSeriveImpl

我们引入 modelMapper,整个文件的内容(移除了 import 引入)如下:

代码语言:javascript
复制
package com.jimmy.todoservice.service.impl;

@Service
@AllArgsConstructor
public class DemoServiceImpl implements DemoService {

  private DemoRepository demoRepository;

  private ModelMapper modelMapper;

  @Override
  public DemoDto addDemoItem(DemoDto demoDto) {

    Demo demo = modelMapper.map(demoDto, Demo.class);

    Demo savedDemo = demoRepository.save(demo);

    DemoDto savedDemoDto = modelMapper.map(savedDemo, DemoDto.class);

    return savedDemoDto;
  }
}
添加对应的 controller 操作

我们在之前的 src/main/java/com.jimmy.totoservice/controller/Demo.java 文件内,添加下面的 add 接口操作。整个文件的内容(移除了 import 引入)如下:

代码语言:javascript
复制
package com.jimmy.todoservice.controller;

@RestController
@RequestMapping("api")
@AllArgsConstructor
public class Demo {
  private DemoService demoService;

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

  @PostMapping("/add")
  public ResponseEntity<DemoDto> addName(@RequestBody DemoDto demoDto) {
    DemoDto savedDemoDto = demoService.addDemoItem(demoDto);
    return new ResponseEntity<>(savedDemoDto, HttpStatus.OK);
  }
}
验证

我们运行项目起来。在 postman 上执行下面的接口:

代码语言:javascript
复制
method [POST]
url [http://localhost:8080/api/add]
body -> {"name": "jimmy"}

查看返回的写入数据库的结果。

我们打开 Navicat Premium 查看 todo_service 数据库中表 demo 写入了新数据。

信息返回

我们统一处理返回的信息。

公共返回文件

我们在 src/main/java/com.jimmy.todoservice 下新建包 common,然后在其下面新建类 ResultData,内容如下:

代码语言:javascript
复制
package com.jimmy.todoservice.common;

@Data
public class ResultData<T> {
  private String code;
  private String message;
  private T data;
  // 扩展字段,比如接口的请求时间
  private Long accessTimestamp;
//  private String path; // TODO: 获取请求的路径

  // 构造函数
  public ResultData() {
    this.accessTimestamp = System.currentTimeMillis();
  }

  // 成功返回
  public static <T> ResultData<T> success(T data) {
    ResultData<T> resultData = new ResultData<>();
    resultData.setCode("10000");
    resultData.setMessage(("请求成功!"));
    resultData.setData(data);
    return resultData;
  }

  // 失败返回
  public static <T> ResultData<T> fail(String code, String message) {
    ResultData<T> resultData = new ResultData<>();
    resultData.setCode(code);
    resultData.setMessage(message);
    return resultData;
  }
}
Demo

然后,我们更改 Get 接口请求,在文件 controller/Demo 下更改:

代码语言:javascript
复制
@GetMapping("/get/{id}")
public ResultData<DemoDto> getItem(@PathVariable("id") Long id) {
  DemoDto demoDto = demoService.getDemoItem(id);
  return ResultData.success(demoDto);
}
验证

启动项目,在 postman 上进行验证:

代码语言:javascript
复制
method [GET]
url [http://localhost:8080/api/get/1]

添加 security - 注册和登录

我们引入 security 进行验证。

安装依赖

在项目根目录的 pom.xml 文件中添加下面的依赖引用:

代码语言:javascript
复制
<!-- security -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

此时,运行项目,在浏览器中打开链接 http://localhost:8080 会自动跳转到登陆的页面。账号为默认 user,密码是随机生成的,可见于控制台 Using generated security password: 后的一串字符串。

自定义用户名和密码

当然,我们也可以自定义用户名和密码,我们在文件 src/main/resources/application.properties 中添加:

代码语言:javascript
复制
spring.security.user.name=jimmy 
spring.security.user.password=123456

重新启动项目后,我们可以通过用户名/密码 jimmy/123456 来登陆。

用户注册

下面,我们实现一个系统用户注册。

首先,我们先配置 spring security config 配置类。在 com.jimmy.todoservice/config 下添加下面的内容:

代码语言:javascript
复制
package com.jimmy.todoservice.config;

@Configuration
@EnableMethodSecurity
@AllArgsConstructor
public class SpringSecurityConfig {

  @Bean
  public static PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
  }

  @Bean
  SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
    httpSecurity.csrf((csrf) -> csrf.disable())
            .authorizeHttpRequests((authorize) -> {
              authorize.requestMatchers("/api/auth/**").permitAll();
              authorize.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll();
              authorize.anyRequest().authenticated();
            }).httpBasic(Customizer.withDefaults());

    return httpSecurity.build();
  }

  @Bean
  public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
    return authenticationConfiguration.getAuthenticationManager();
  }

}

我们在 com.jimmy.todoservice/entity 下添加用户类 User,内容如下:

代码语言:javascript
复制
package com.jimmy.todoservice.entity;

@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "users")
public class User {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  private String name;
  @Column(nullable = false, unique = true)
  private String username;
  @Column(nullable = false, unique = true)
  private String email;
  @Column(nullable = false)
  private String password;
}

然后在 com.jimmy.todoservice/dto 下添加 RegisterDto 类:

代码语言:javascript
复制
package com.jimmy.todoservice.dto;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class RegisterDto {
  private String name;
  private String username;
  private String email;
  private String password;
}

com.jimmy.todoservice/repository 下添加类 UserRepository,内容如下:

代码语言:javascript
复制
package com.jimmy.todoservice.repository;

public interface UserRepository extends JpaRepository<User, Long> {

  Optional<User> findByUsernameOrEmail(String username, String email);

}

然后在 com.jimmy.todoservice/service 下添加接口类 AuthService ,内容如下:

代码语言:javascript
复制
package com.jimmy.todoservice.service;

public interface AuthService {

  String register(RegisterDto registerDto);

  String login(LoginDto loginDto);
}

这里我把登陆的接口也罗列出来了。

下面是注册用户重点👇

我们实现注册接口,在 com.jimmy.todoservice/service/impl 下添加下面的内容:

代码语言:javascript
复制
package com.jimmy.todoservice.service.impl;

@Service
@AllArgsConstructor
public class AuthServiceImpl implements AuthService {
  private UserRepository userRepository;

  @Override
  public String register(RegisterDto registerDto) {
    User user = new User();
    user.setName(registerDto.getName());
    user.setUsername(registerDto.getUsername());
    user.setEmail(registerDto.getEmail());
    user.setPassword(registerDto.getPassword());

    userRepository.save(user);
    return null;
  }

  @Override
  public String login(LoginDto loginDto) {
    // 登陆验证

    return null;
  }
}

这里我们只是简单的将用户信息,密码还是明文,写入到数据库中。

这个时候,我们在 com.jimmy.todoservice/controller 下添加类 AuthController ,内容如下:

代码语言:javascript
复制
package com.jimmy.todoservice.controller;

@AllArgsConstructor
@RestController
@RequestMapping("/api/auth")
public class AuthController {

  private AuthService authService;

  // 注册接口
  @PostMapping("/register")
  public String register(@RequestBody RegisterDto registerDto) {
    String response = authService.register(registerDto);
    System.out.println(response);
    return "register";
  }

  // 登陆接口
  @PostMapping("/login")
  public String login(@RequestBody LoginDto loginDto) {
    return "login";
  }
}

运行项目,我们在 Postman 上直接调用注册接口,进行注册。

代码语言:javascript
复制
[POST] http://localhost:8080/api/auth/register
[BODY] {
    "name": "嘉明",
    "username": "jimmy",
    "email": "reng99@outlook.com",
    "password": "123456"
}

执行后,通过 Navicat 进入数据库查看,发现内容写入。

嗯,这里刚才也说了,我们的用户密码是明文写入,这样很不安全,我们更改下注册类 com.jimmy.todoservive/service/impl/AuthServiceImpl.java,更改后内容如下:

代码语言:javascript
复制
package com.jimmy.todoservice.service.impl;

@Service
@AllArgsConstructor
public class AuthServiceImpl implements AuthService {
  private PasswordEncoder passwordEncoder; // 密码加密
  private UserRepository userRepository;

  @Override
  public String register(RegisterDto registerDto) {
    User user = new User();
    user.setName(registerDto.getName());
    user.setUsername(registerDto.getUsername());
    user.setEmail(registerDto.getEmail());
    user.setPassword(passwordEncoder.encode(registerDto.getPassword()));

    userRepository.save(user);
    return null;
  }

  @Override
  public String login(LoginDto loginDto) {
    // 登陆验证

    return null;
  }
}

重新运行项目,在 Navicat 上删除存在的数据。我们在 Postman 上直接调用注册接口,进行注册。

代码语言:javascript
复制
[POST] http://localhost:8080/api/auth/register
[BODY] {
    "name": "jimmy",
    "username": "jimmy",
    "email": "reng99@outlook.com",
    "password": "123456"
}

执行后,通过 Navicat 进入数据库查看,发现内容写入,此时的密码是加密的。

用户登录

我们在 com.jimmy.todoservice/security 下添加类 CustomUserDetailsService 来重写 security 的方法 loadUserByUsername,初步的内容如下:

代码语言:javascript
复制
package com.jimmy.todoservice.security;

@Service
@AllArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {

  private UserRepository userRepository;

  @Override
  public UserDetails loadUserByUsername(String usernameOrEmail) throws UsernameNotFoundException {
    System.out.println("username or email");
    System.out.println(usernameOrEmail);
    // 从数据库中查询
    User user = userRepository.findByUsernameOrEmail(usernameOrEmail, usernameOrEmail)
            .orElseThrow(() -> new UsernameNotFoundException("该用户不存在"));
    System.out.println(user);
    return null;
  }
}

我们在 com.jimmy.todoservice/dto 下添加类 LoginDto,内容如下:

代码语言:javascript
复制
package com.jimmy.todoservice.dto;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class LoginDto {
  private String usernameOrEmail;
  private String password;
}

我们在上面的 controller 包下面的类 AuthController 已经添加了登陆的 api 了。我们执行下看看:

代码语言:javascript
复制
[POST] http://localhost:8080/api/auth/login
[BODY] {
    "usernameOrEmail": "reng99@outlook.com",
    "password": "123456"
}

重启项目后,我们并不能进行登陆,只是 return "login";

下面我们来更改👇

更改com.jimmy.todoservice/controller/AuthController.java

代码语言:javascript
复制
// 登陆接口
@PostMapping("/login")
public String login(@RequestBody LoginDto loginDto) {
  String token = authService.login(loginDto);
  return token;
}

更改 com.jimmy.todoservice/service/impl/AuthServiceImpl.java

代码语言:javascript
复制
@Override
public String login(LoginDto loginDto) {

  UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
          loginDto.getUsernameOrEmail(),
          loginDto.getPassword()
  );
  // 登陆验证
  try {
    Authentication authentication = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
    SecurityContextHolder.getContext().setAuthentication(authentication);
  } catch (AuthenticationException e) {
    e.printStackTrace();
  }

  return "token successfully return";
}

更改 com.jimmy.todoservice/security/CustomUserDetailsService.java 内容:

代码语言:javascript
复制
package com.jimmy.todoservice.security;

@Service
@AllArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {

  private UserRepository userRepository;

  @Override
  public UserDetails loadUserByUsername(String usernameOrEmail) throws UsernameNotFoundException {
    // 从数据库中查询
    User user = userRepository.findByUsernameOrEmail(usernameOrEmail, usernameOrEmail)
            .orElseThrow(() -> new UsernameNotFoundException("该用户不存在"));

//    Set<GrantedAuthority> authorities = null; // null 会报错,不允许空
    List<GrantedAuthority> authorities = new ArrayList<>();
    authorities.add(new SimpleGrantedAuthority("ROLE_USER")); // 模拟,写死

    return new org.springframework.security.core.userdetails.User(
            usernameOrEmail,
            user.getPassword(),
            authorities
    );
  }
}

最后,我们重新启动项目。在 Postman 上请求:

代码语言:javascript
复制
[POST] http://localhost:8080/api/auth/login
[BODY] {
    "usernameOrEmail": "reng99@outlook.com",
    "password": "123456"
}

能够成功返回信息,验证通过✅。

至此,我们可以把 src/main/resources/application.properties 文件内设定的账号密码移除:

代码语言:javascript
复制
spring.security.user.name=jimmy // -
spring.security.user.password=123456 // -

整合 JWT

TODO 项目,整合 JWT。

安装依赖

在项目根目录中,文件 pom.xml 中添加依赖:

代码语言:javascript
复制
<!--   jwt     -->
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-jackson -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>

安装上面的依赖。

Jwt 生成和校验

我们在 src/main/resources/application.properties 内添加内容,如下:

代码语言:javascript
复制
# jwt - sha256 -> jimmy https://emn178.github.io/online-tools/sha256.html https://www.unitconverters.net/time/millisecond-to-day.htm
app.jwt-secret=930a68a51a2db950f58fd3b0b5f1d76f56afaa16e12a418d71ca6c25f2390424
app.jwt-expiration-milliseconds=604800000

上面添加了私钥和过期时间

然后,我们在 src/main/java/com.jimmy.todoservice/security 中添加下面三个文件:

JwtTokenProvider.class 生成 token,校验 token

代码语言:javascript
复制
package com.jimmy.todoservice.security;

@Component
public class JwtTokenProvider {

  // 密钥
  @Value("${app.jwt-secret}")
  private String jwtSecret;

  // 过期时间
  @Value("${app.jwt-expiration-milliseconds}")
  private long jwtExpirationDate;

  // 生成 JWT token
  public String generateToken(Authentication authentication) {
    String username = authentication.getName();

    Date currentDate = new Date();

    Date expireDate = new Date(currentDate.getTime() + jwtExpirationDate);

    String token = Jwts.builder()
            .setSubject(username)
            .setIssuedAt(new Date())
            .setExpiration(expireDate)
            .signWith(key())
            .compact();

    return token;
  }

  private Key key() {
    return Keys.hmacShaKeyFor(
            Decoders.BASE64URL.decode(jwtSecret)
    );
  }

  // 从 token 中获取用户名
  public String getUsername(String token) {
    Claims claims = Jwts.parserBuilder()
            .setSigningKey(key())
            .build()
            .parseClaimsJws(token)
            .getBody();

    String username = claims.getSubject();

    return username;
  }

  // 验证 JWT token
  public boolean validateToken(String token) {
    // 注意: token should not be empty
    Jwts.parserBuilder()
            .setSigningKey(key())
            .build()
            .parse(token);

    return true;
  }
}

JwtAuthenticationEntryPoint.class 重写认证的入口:

代码语言:javascript
复制
package com.jimmy.todoservice.security;

@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { // 认证入口文件
  @Override
  public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
    response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
  }
}

JwtAuthenticationFilter.class 对每个请求进行过滤:

代码语言:javascript
复制
package com.jimmy.todoservice.security;

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter { // 对请求进行过滤
  private JwtTokenProvider jwtTokenProvider;

  private UserDetailsService userDetailsService;

  // 判断 token 是否为空
  private boolean isTokenEmpty(String token) {
    return token == null || token.trim() == "";
  }

  // 从请求中,获取 token
  private String getTokenFromRequest(HttpServletRequest request) {
    String bearerToken = request.getHeader("Authorization");

    if(StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
      return bearerToken.substring(7, bearerToken.length());
    }

    return null;
  }

  @Override
  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    // Get JWT token form HTTP request
    String token = getTokenFromRequest(request);

    // Validate Token
    if(!isTokenEmpty(token) && StringUtils.hasText(token) && jwtTokenProvider.validateToken(token)) {
      // 从 token 中获取用户名
      String username = jwtTokenProvider.getUsername(token);

      // 获取 security 的用户信息
      UserDetails userDetails = userDetailsService.loadUserByUsername(username);

      UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
              userDetails,
              null,
              userDetails.getAuthorities()
      );

      authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

      SecurityContextHolder.getContext().setAuthentication(authenticationToken);
    }

    filterChain.doFilter(request, response);
  }
}

那么,我们接下来在登陆接口中生成 token。我们进入文件 com.jimmy.todoservice/service/impl/AuthServiceImpl.java 中改写登陆的接口实现:

代码语言:javascript
复制
@Override
public String login(LoginDto loginDto) {
  // 登陆验证
  UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
          loginDto.getUsernameOrEmail(),
          loginDto.getPassword()
  );

  Authentication authentication = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
  SecurityContextHolder.getContext().setAuthentication(authentication);

  // 生成 token
  String token = jwtTokenProvider.generateToken(authentication);

  return token;
}

然后,重启项目。通过 Postman 请求:

代码语言:javascript
复制
[POST] http://localhost:8080:/api/auth/login
[BODY] {
    "usernameOrEmail": "reng99@outlook.com",
    "password": "123456"
}

Postman 中接口返回 token 信息。验证通过✅

Jwt 校验 Demo

我们在上面已经完成了 Jwt 的校验。那么,下面,我们来实现其他接口需要 jwt 校验的案例。

我们先来实现一个获取用户列表的 api,我们添加的内容如下👇

我们在 com.jimmy.todoservice/dto 下添加类 UserDto:

代码语言:javascript
复制
package com.jimmy.todoservice.dto;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class UserDto {
  private String name;
  private String username;
  private String email;
  private String password;
}

然后在 com.jimmy.todoservice/service 包下添加接口类 UserService:

代码语言:javascript
复制
package com.jimmy.todoservice.service;

public interface UserService {
  // 获取用户列表数据
  List<UserDto> getAllUsers();
}

com.jimmy.todoservice/service/impl 下添加该接口实现类 UserServiceImpl

代码语言:javascript
复制
package com.jimmy.todoservice.service.impl;

@Service
@AllArgsConstructor
public class UserServiceImpl implements UserService {
  private UserRepository userRepository;
  private ModelMapper modelMapper;

  @Override
  public List<UserDto> getAllUsers() {
    List<User> users = userRepository.findAll();
    return users.stream().map((user) -> modelMapper.map(user, UserDto.class))
            .collect(Collectors.toList());
  }
}

接着,我们在 com.jimmy.service/controller 包下添加类 UserController

代码语言:javascript
复制
package com.jimmy.todoservice.controller;

@AllArgsConstructor
@RestController
@RequestMapping("/api/users")
public class UserController {
  private UserService userService;

  // 获取所有用户列表
  @GetMapping
  public ResponseEntity<List<UserDto>> getAllUsers() {
    List<UserDto> users = userService.getAllUsers();
    return new ResponseEntity<>(users, HttpStatus.OK);
  }
}

至此,我们启动项目,通过 Postman 进行接口调用:

代码语言:javascript
复制
[GET] http://localhost:8080/api/users

在没有添加 token 得情况下会出现 401,添加了登陆的 token 就会获取到用户的列表。

但是,接口并没有通过。

我们来重新整改下之前配置的内容,下面👇

更改 com.jimmy.todoservice/config/SpringSecurityConfig.java:

代码语言:javascript
复制
package com.jimmy.todoservice.config;

@Configuration
@EnableMethodSecurity
@AllArgsConstructor
public class SpringSecurityConfig {

  private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
  private JwtAuthenticationFilter jwtAuthenticationFilter;

  @Bean
  public static PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
  }

  @Bean
  SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
    httpSecurity.csrf((csrf) -> csrf.disable())
            .authorizeHttpRequests((authorize) -> {
              authorize.requestMatchers("/api/auth/**").permitAll();
              authorize.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll();
              authorize.anyRequest().authenticated();
            }).httpBasic(Customizer.withDefaults());

    // 添加内容 -> 认证, jwt 
    httpSecurity.exceptionHandling(exception -> exception
            .authenticationEntryPoint(jwtAuthenticationEntryPoint));

    httpSecurity.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

    return httpSecurity.build();
  }

  @Bean
  public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
    return authenticationConfiguration.getAuthenticationManager();
  }

}

更改 com.jimmy.todoservice/security/JwtAuthenticationFilter.java 文件:

代码语言:javascript
复制
package com.jimmy.todoservice.security;


@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter { // 对请求进行过滤
  private JwtTokenProvider jwtTokenProvider;

  private UserDetailsService userDetailsService;
  
  // 添加内容 -> 构造函数
  public  JwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider, UserDetailsService userDetailsService) {
    this.jwtTokenProvider = jwtTokenProvider;
    this.userDetailsService = userDetailsService;
  }

  // 判断 token 是否为空
  private boolean isTokenEmpty(String token) {
    return token == null || token.trim() == "";
  }

  // 从请求中,获取 token
  private String getTokenFromRequest(HttpServletRequest request) {
    String bearerToken = request.getHeader("Authorization");

    if(StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
      return bearerToken.substring(7, bearerToken.length());
    }

    return null;
  }

  @Override
  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    // Get JWT token form HTTP request
    String token = getTokenFromRequest(request);

    // Validate Token
    if(!isTokenEmpty(token) && StringUtils.hasText(token) && jwtTokenProvider.validateToken(token)) {
      // 从 token 中获取用户名
      String username = jwtTokenProvider.getUsername(token);

      // 获取 security 的用户信息
      UserDetails userDetails = userDetailsService.loadUserByUsername(username);

      UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
              userDetails,
              null,
              userDetails.getAuthorities()
      );

      authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

      SecurityContextHolder.getContext().setAuthentication(authenticationToken);
    }

    filterChain.doFilter(request, response);
  }
}

重新运行项目。

通过 Postman 测试,能够成功返回登陆接口的 token 信息。并且带 token 信息访问用户的列表接口,能够返回用户列表信息数据;不带 token 访问用户列表接口,则返回 401

角色表增删改查

TODO 项目,进行角色限制。

初始内容

在上面添加 security - 注册和登录小节中,我们在 com.jimmy.todoservice/security/CustomUserDetailsService.java 模拟了角色授权。

代码语言:javascript
复制
List<GrantedAuthority> authorities = new ArrayList<>();
    authorities.add(new SimpleGrantedAuthority("ROLE_USER")); // 模拟,写死

下面,我们新建表来进行关联。

在进行关联之前,先对角色表进行增删改除 - 这个也是本文的重点。用户表和角色表的关联放在下一篇文章。

添加 entity,在 com.jimmy.todoservice/entity 包下添加类 Role

代码语言:javascript
复制
package com.jimmy.todoservice.entity;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "roles")
public class Role {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  private String name;
}

添加 repository,在 com.jimmy.todoservice/repository 包下添加类 RoleRepository

代码语言:javascript
复制
package com.jimmy.todoservice.repository;

public interface RoleRepository extends JpaRepository<Role, Long> {
  Role findByName(String name);
}

添加 dto,在 com.jimmy.todoservice/dto 包下添加类 RoleDto:

代码语言:javascript
复制
package com.jimmy.todoservice.dto;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class RoleDto {
  private String name;
}

添加 service,在 com.jimmy.todoservice/service 包下添加接口类 RoleService:

代码语言:javascript
复制
package com.jimmy.todoservice.service;

public interface RoleService {
  // 添加角色
  RoleDto addRole(RoleDto roleDto);

  // 获取角色
  RoleDto getRole(Long id);

  // 获取列表
  List<RoleDto> getAllRoles();

  // 删除角色
  RoleDto deleteRole(Long id);
}

添加服务实现,在 com.jimmy.todoservice/service/impl 包下添加对应的实现类 RoleServiceImpl,下面是文件骨架内容:

代码语言:javascript
复制
package com.jimmy.todoservice.service.impl;

@Service
@AllArgsConstructor
public class RoleServiceImpl implements RoleService {
  
  @Override
  public RoleDto addRole(RoleDto roleDto) {
    return null;
  }

  @Override
  public RoleDto getRole(Long id) {
    return null;
  }

  @Override
  public List<RoleDto> getAllRoles() {
    return null;
  }

  @Override
  public RoleDto deleteRole(Long id) {
    return null;
  }
}
角色的添加

com.jimmy.todoservice/service/impl/RoleServiceImpl.java 中更改 addRole 方法:

代码语言:javascript
复制
private RoleRepository roleRepository;

private ModelMapper modelMapper;

@Override
public RoleDto addRole(RoleDto roleDto) {
  Role role = modelMapper.map(roleDto, Role.class);
  Role savedRole = roleRepository.save(role);

  RoleDto savedRoleDto = modelMapper.map(savedRole, RoleDto.class);
  return savedRoleDto;
}

我们添加对应的 controller,在 com.jimmy.todoservice/controller 下添加类 RoleController:

代码语言:javascript
复制
package com.jimmy.todoservice.controller;

@RestController
@RequestMapping("/api/roles")
@AllArgsConstructor
public class RoleController {
  private RoleService roleService;

  // 添加角色
  @PostMapping
  public ResponseEntity<RoleDto> addRole(@RequestBody RoleDto roleDto) {
    RoleDto savedRoleDto = roleService.addRole(roleDto);
    return new ResponseEntity<>(savedRoleDto, HttpStatus.CREATED);
  }
}

然后,我们启动项目。通过 Postman 请求接口:

代码语言:javascript
复制
[POST] http://localhost:8080/api/roles
[BODY] {
  "name": "ADMIN"
}

注意:前提要登陆,带上 token 请求上面的接口

请求成功后,通过 Navicat 查看 roles 表中,数据已经正确写入。

获取角色列表

com.jimmy.todoservice/service/impl/RoleServiceImpl.java 中更改 getAllRoles 方法:

代码语言:javascript
复制
@Override
public List<RoleDto> getAllRoles() {
  List<Role> roles = roleRepository.findAll();
  return roles.stream().map(role -> modelMapper.map(role, RoleDto.class))
          .collect(Collectors.toList());
}

我们添加对应的 controller,在 com.jimmy.todoservice/controller/RoleController.java 中添加接口如下:

代码语言:javascript
复制
// 获取角色列表
@GetMapping
public ResponseEntity<List<RoleDto>> getAllRoles() {
  List<RoleDto> roles = roleService.getAllRoles();
  return new ResponseEntity<>(roles, HttpStatus.OK);
}

重新运行项目。在 Postman 中请求下面的接口:

代码语言:javascript
复制
[GET] http://localhost:8080/api/roles

注意:前提要登陆,带上 token 请求上面的接口

请求成功,放回数据列表。

获取指定 ID 角色

我们先添加个错误提示先,在 com.jimmy.todoservice/exception 下添加类 ResourceNotFoundException:

代码语言:javascript
复制
package com.jimmy.todoservice.exception;

@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException{
  public ResourceNotFoundException(String message) {
    super(message);
  }
}

更改 com.jimmy.todoservice/service/impl/RoleServiceImpl.java 文件下的 getRole 方法:

代码语言:javascript
复制
@Override
public RoleDto getRole(Long id) {
  Role role = roleRepository.findById(id)
          .orElseThrow(() -> new ResourceNotFoundException("资源找不到"));
  return modelMapper.map(role, RoleDto.class);
}

接着编写 controller,在 com.jimmy.todoservice/controller/RoleController.java 文件中添加接口:

代码语言:javascript
复制
// 获取指定 id 的角色
@GetMapping("/{id}")
public ResponseEntity<RoleDto> getRole(@PathVariable("id") Long roleId) {
  RoleDto roleDto = roleService.getRole(roleId);
  return new ResponseEntity<>(roleDto, HttpStatus.OK);
}

此时启动项目,通过 Postman 调用接口:

代码语言:javascript
复制
[GET] http://localhost:8080/api/roles/1

注意:前提要登陆,带上 token 请求上面的接口

成功返回信息。

这个时候,我们返回的信息没有 id 字段。那是因为我们在 RoleDto 中没有编写 id,我们改写下 com.jimmy.todoservice/dto/RoleDto.java 文件内容:

代码语言:javascript
复制
package com.jimmy.todoservice.dto;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class RoleDto {
  private long id;
  private String name;
}

然后重新启动项目,调用接口:

代码语言:javascript
复制
[GET] http://localhost:8080/api/roles/1

此时有 id 的字段信息返回。

更改指定 ID 角色

在此之前,我们添加个 description 字段在 entity 中,这步忽略。读者自行更改。

我们在 com.jimmy.todoservice/service/RoleService.java 上添加实现的方法:

代码语言:javascript
复制
// 更新角色
RoleDto updateRole(Long id, RoleDto roleDto);

然后在 com.jimmy.todoservice/service/impl/RoleServiceImpl.java 内添加 updateRole 方法的实现:

代码语言:javascript
复制
@Override
public RoleDto updateRole(Long id, RoleDto roleDto) {
  Role role = roleRepository.findById(id)
          .orElseThrow(() -> new ResourceNotFoundException("资源找不到"));

  if(roleDto != null && roleDto.getName() != null && !roleDto.getName().isEmpty()) {
    role.setName(roleDto.getName());
  }
  if(roleDto != null && roleDto.getDescription() != null && !roleDto.getDescription().isEmpty()) {
    role.setDescription(roleDto.getDescription());
  }

  Role updatedRole = roleRepository.save(role);

  return modelMapper.map(updatedRole, RoleDto.class);
}

接着,我们添加相关的 controller,在 com.jimmy.todoservice/controller/RoleController.java 上添加接口:

代码语言:javascript
复制
// 更新指定 id 的角色
@PutMapping("/{id}")
public ResponseEntity<RoleDto> updateRole(@PathVariable("id") Long roleId, @RequestBody RoleDto roleDto) {
  RoleDto savedRoleDto = roleService.updateRole(roleId, roleDto);
  return new ResponseEntity<>(savedRoleDto, HttpStatus.OK);
}

启动项目,在 Postman 上测试接口:

代码语言:javascript
复制
[PUT] http://localhost:8080/api/roles/1
[BODY] {
  "description": "管理员"
}

注意:前提要登陆,带上 token 请求上面的接口

进入 Navicat 中查看记录被成功修改。

删除指定 ID 角色

我们在 com.jimmy.todoservice/service/RoleService.java 上添加实现的方法:

代码语言:javascript
复制
// 删除角色
void deleteRole(Long id);

然后在 com.jimmy.todoservice/service/impl/RoleServiceImpl.java 内添加 deleteRole 方法的实现:

代码语言:javascript
复制
@Override
public void deleteRole(Long id) {
  Role role = roleRepository.findById(id)
          .orElseThrow(() -> new ResourceNotFoundException("资源找不到"));
  roleRepository.deleteById(id);
}

接着,我们添加相关的 controller,在 com.jimmy.todoservice/controller/RoleController.java 上添加接口:

代码语言:javascript
复制
// 删除指定 id 的角色
@DeleteMapping("/{id}")
public  ResponseEntity<String> deleteRole(@PathVariable("id") Long roleId) {
  roleService.deleteRole(roleId);
  return ResponseEntity.ok("删除角色成功!");
}

启动项目,在 Postman 上测试接口:

代码语言:javascript
复制
[DELETE] http://localhost:8080/api/roles/1

注意:前提要登陆,带上 token 请求上面的接口

进入 Navicat 查看相关的记录已经被删除。

如果读者理解了角色的增删改查,那么 TODO 的增删改查大同小异

角色和用户关联

下面,我们将角色和用户进行关联。

建立表关联

用户表建立外键,外键为角色表的 ID

更改 com.jimmy.todoservice/entity/User.java 的文件:

代码语言:javascript
复制
package com.jimmy.todoservice.entity;

@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "users")
public class User {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  private String name;
  @Column(nullable = false, unique = true)
  private String username;
  @Column(nullable = false, unique = true)
  private String email;
  @Column(nullable = false)
  private String password;

  // User 关联 Role, 多对多关联
  @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
  @JoinTable(name = "users_roles", // 关联表
          joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"), // 指定关联到当前实体的外键列
          inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id") // 指定到关联的另一个实体的外键列(哪个实体呢?可以通过下面的 Set<Role> 中推断出是 roles 实体)
  )
  private Set<Role> roles;
}

我们重新启动项目之后,可以通过 Navicat 查看到数据库中多出了一个表 users_roles

实操

我们来操作下,给用户添加一个角色关联。

在文件 com.jimmy.todoservice/service/UserService.java 中,添加内容:

代码语言:javascript
复制
// 用户关联角色
void assignRoleToUser(Long userId, Set<Long> roleId);

然后在文件 com.jimmy.todoservice/service/impl/UserServiceImpl.java 中进行实现,添加下面的代码:

代码语言:javascript
复制
@Override
public void assignRoleToUser(Long userId, Set<Long> roleIds) {
  User user = userRepository.findById(userId).orElseThrow(() -> new ResourceNotFoundException("用户找不到"));
  Set<Role> roles = new HashSet<>(roleRepository.findAllById(roleIds));
  user.setRoles(roles);
  userRepository.save(user);
}

最后,添加对应 controller,在文件 com.jimmy.todoservice/controller/UserController.java 中添加对应的接口,如下:

代码语言:javascript
复制
// 给用户设定角色
@PutMapping("/{id}")
public ResponseEntity<String> assignRoleToUser(@PathVariable("id") Long userId, @RequestBody Set<Long> roleIds) {
  userService.assignRoleToUser(userId, roleIds);
  return new ResponseEntity<>("成功返回", HttpStatus.OK);
}

假设角色表中我们已经有了数据 ID23,用户表中有数据 ID8

我们通过 Postman 发起请求:

代码语言:javascript
复制
[PUT] http://localhost:8080/api/users/8
[BODY] [2, 3]

注意,我们得添加登陆的 token 凭证

执行成功后,我们可以通过 Navacat 中,数据库的中间表 users_roles 中写入数据。

整合角色权限到 security

将角色的权限整合到 Security 中。

前言

在之前的章节中,我们在 com.jimmy.todoservice/security/CustomUserDetailsService.java 中模拟,写死了角色。如下:

代码语言:javascript
复制
//    Set<GrantedAuthority> authorities = null; // null 会报错,不允许空
    List<GrantedAuthority> authorities = new ArrayList<>();
    authorities.add(new SimpleGrantedAuthority("ROLE_USER")); // 模拟,写死

那么,本小节讲解,如何应用用户关联的角色到鉴权中。

获取角色的 roles

我们改写前言中的代码内容如下:

代码语言:javascript
复制
//    Set<GrantedAuthority> authorities = null; // null 会报错,不允许空
//    List<GrantedAuthority> authorities = new ArrayList<>();
//    authorities.add(new SimpleGrantedAuthority("ROLE_USER")); // 模拟,写死
    Set<GrantedAuthority> authorities = user.getRoles().stream()
            .map((role) -> new SimpleGrantedAuthority(role.getName()))
            .collect(Collectors.toSet());
设定接口的角色权限

我们更改 com.jimmy.todoservice/config/SpringSecurityConfig.java 的文件内容如下:

代码语言:javascript
复制
package com.jimmy.todoservice.config;

@Configuration
@EnableMethodSecurity
@AllArgsConstructor
public class SpringSecurityConfig {

  private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
  private JwtAuthenticationFilter jwtAuthenticationFilter;

  @Bean
  public static PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
  }

  @Bean
  SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
    httpSecurity.csrf((csrf) -> csrf.disable())
            .authorizeHttpRequests((authorize) -> {
              authorize.requestMatchers(HttpMethod.GET, "/api/roles/**").hasAnyAuthority("ADMIN", "USER"); // + 添加,[GET] 接口只有角色 ADMIN 或者 USER 有权限访问
              authorize.requestMatchers(HttpMethod.PUT, "/api/roles/**").hasAuthority("ADMIN"); // + 添加,[PUT] 接口只有角色是 ADMIN  有权限访问 
              authorize.requestMatchers("/api/auth/**").permitAll();
              authorize.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll();
              authorize.anyRequest().authenticated();
            }).httpBasic(Customizer.withDefaults());

    // 认证, jwt
    httpSecurity.exceptionHandling(exception -> exception
            .authenticationEntryPoint(jwtAuthenticationEntryPoint));

    httpSecurity.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

    return httpSecurity.build();
  }

  @Bean
  public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
    return authenticationConfiguration.getAuthenticationManager();
  }

}
验证

我们添加带角色为 USER 的用户,用起 token 凭证发起请求访问接口

代码语言:javascript
复制
[PUT] http://localhost:8080/api/roles/2
{
    "description": "管理员"
}

访问不了,报错 401

当用其 token 访问接口

代码语言:javascript
复制
[GET] http://localhost:8080/api/roles

则能成功获取到角色列表。

接下来就是实现 TODOCURD 了,请参考小节角色表增删改查

项目部署到服务器 - 练手

我们先拿一个 Demo 的项目来部署,并没有数据库。GET 接口 /api/demo 只是返回一个字符串的信息 Hello world!

我们将参考到的文章 - java jar 包发布

假设我们已经打包好了文件 **.jar,比如 todo-service-0.0.1-SNAPSHOT.jar

服务器登陆

假设我们知道了服务器的地址,账号(用户名),密码,我们可以通过下面的命令行进行登陆:

代码语言:javascript
复制
1. ssh 账号@1*.1**.8*.1**
2. 根据提示输入密码

成功登陆。

安装 java

因为我们是 java 服务,所以我们安装相关的包。假设这里的服务器中可用 yum 管理包。

代码语言:javascript
复制
sudo yum update // 升级
sudo yum install java-17-openjdk // 安装相关的 java 的版本
安装 nginx 并将配置

同理,安装 nginx

安装成功后,一般情况下,我们通过 whereis nginx 就可以查看到 config 的配置文件路径。如果我们找不到的话,我们可以通过 sudo nginx -t 查看其所在位置,比如:

代码语言:javascript
复制
> sudo nginx -t

nginx: the configuration file /opt/homebrew/etc/nginx/nginx.conf syntax is ok

nginx: configuration file /opt/homebrew/etc/nginx/nginx.conf test is successful

由此可以知道 config 文件在 /opt/homebrew/etc/nginx/ 文件夹下。

好,那么我们来配置下服务。

进入相关的 **.config 之后,我们添加下面的内容:

代码语言:javascript
复制
# service - from -jimmy

upstream api {

    server 127.0.0.1:6000;

    keepalive 2000;

}

server {
    # 其他内容忽略
  
    # service - from -jimmy
    location /api {

        proxy_pass http://api;

    }
}

保存后,我们可以通过 sudo nginx -t 来查看配置语法是否正确。

上传 jar 包,并启动

我们通过下面的命令行上传 jar 包:

代码语言:javascript
复制
scp -r /path/to/target/demo-0.0.1-SNAPSHOT.jar 账号@1*.1**.8*.1**:/usr/local/nginx/ // 同步本地信息到远程服务器

这里我们把 jar 包放在了文件夹 /usr/local/nginx/ 下。

那么,我们来启动该服务,并指定运行的端口是 6000,这个要和上面配置的 configupstream api 配置的 server 的端口有关。

进入 /usr/local/nginx/ 后,我们可以通过方法一,如下:

代码语言:javascript
复制
java -jar todo-service-0.0.1-SNAPSHOT.jar --server.port=6000

来启动服务,这个方式,在关闭掉控制台后,服务会中断。

方法一,局限。那么,我们使用方法二 nohup java -jar -Dserver.port=6000 todo-service-0.0.1-SNAPSHOT.jar > output-demo-0.0.1-SNAPST.txt &,服务常驻内存,关闭控制台也不会影响。

此时,通过访问 [GET] https://domain.com/api/demo 接口,则正确返回字符串数据。

那么,方法二,如果我们要关闭服务怎么办?

我们可以使用下面的方法:

代码语言:javascript
复制
# 1. 使用 ps 命令行查找 java 进程的 PID
ps aux | grep java

# 使用 kill 命令行终止 java 进程,假设 java 进程 ID 是 12345
kill -9 12345
# -9 参数表示强制终止进程

【完✅】

谢谢你看到这里🌹

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2024-02-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 开发环境
  • 项目搭建
    • New Project
      • 添加 mysql
        • 运行项目
        • 创建 Controller
          • 创建 controller 的 Demo
            • 验证
            • 数据写入 MySql
              • 添加数据库表映射
                • 添加对应的 TDO
                  • 添加对应的 Repository 来与数据库建立联系
                    • 创建对应的服务 service
                      • 添加 modelMapper
                        • 完善 DemoSeriveImpl
                          • 添加对应的 controller 操作
                            • 验证
                            • 信息返回
                              • 公共返回文件
                                • Demo
                                  • 验证
                                  • 添加 security - 注册和登录
                                    • 安装依赖
                                      • 自定义用户名和密码
                                        • 用户注册
                                          • 用户登录
                                          • 整合 JWT
                                            • 安装依赖
                                              • Jwt 生成和校验
                                                • Jwt 校验 Demo
                                                • 角色表增删改查
                                                  • 初始内容
                                                    • 角色的添加
                                                      • 获取角色列表
                                                        • 获取指定 ID 角色
                                                          • 更改指定 ID 角色
                                                            • 删除指定 ID 角色
                                                            • 角色和用户关联
                                                              • 建立表关联
                                                                • 实操
                                                                • 整合角色权限到 security
                                                                  • 前言
                                                                    • 获取角色的 roles
                                                                      • 设定接口的角色权限
                                                                        • 验证
                                                                        • 项目部署到服务器 - 练手
                                                                          • 服务器登陆
                                                                            • 安装 java
                                                                              • 安装 nginx 并将配置
                                                                                • 上传 jar 包,并启动
                                                                                相关产品与服务
                                                                                数据库
                                                                                云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
                                                                                领券
                                                                                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档