本期引言:
本文将介绍如何使用SpringBoot框架构建一个简单的JDBC连接数据库项目。在这个项目中,我们将使用MySQL作为数据库,通过SpringBoot框架实现数据的增删改查操作。本文将涵盖以下内容:
- 配置项目所需的环境和工具
- 创建SpringBoot项目
- 添加Maven依赖
- 配置数据源和JDBC连接
- 实现数据的增删改查操作
- 运行项目并进行测试
本文旨在为初学者提供一个简单易懂的SpringBoot JDBC连接数据库的项目构建教程,通过本文的学习,读者可以了解到如何使用SpringBoot框架来连接数据库,以及如何实现常见的数据操作功能。
1 安装IDEA DOCKER插件
操作对象IDEA
1.1.1 打开IDEA,选择设置;
1.1.2 搜索Docker并安装,安装好后重新启动IDEA。
1.2 使用IDEA连接实验容器
1.2.1 选择远程开发
1.2.2 点击新建连接。
1.2.3 本文使用深信服产业教育中心实验平台,开启实验,点击实验靶标,将IP地址和SSH映射端口填入IDEA连接中。读者可以选择linux或者Docker镜像作为远程调试的实验环境。
1.2.4 项目目录选择/app,点击下载IDE并连接。
1.2.5 通过上述操作,会下载对应的GATEWAY,连接Docker容器。
1.2.6 点击连接,连接成功后,会显示下述内容。
1.2.7 双击APP,会打开新的远程连接IDEA。
至此远程连接成功。
1.3 创建Spring Boot项目
1.3.1 创建Mavne项目
Spring Boot项目通常采用标准的Maven项目结构,可以包含以下文件和目录:
- src/main/java: 存放项目的主要Java源代码
- src/test/java: 存放测试代码
- src/main/resources: 存放应用程序的配置文件、静态文件和其他资源
- src/test/resources: 存放测试资源文件
- pom.xml : Maven构建配置文件,用于定义项目依赖、构建插件、编译选项等
- application.properties Spring Boot应用程序的配置文件,包括数据库配置、服务器端口、日志级别等
- static/ 和 templates/ 目录: 存放静态文件和Thymeleaf模板文件,用于构建Web应用程序
根据MVC设计模式: - 模型(Model):模型表示应用程序的数据和业务逻辑,它是应用程序的核心部分。在Web应用程序中,模型通常由Java对象组成,这些对象封装了应用程序中的数据和相关的业务逻辑。控制器(Controller)可以从模型中获取数据,并将其呈现给用户界面。
- 视图(View):视图表示应用程序的用户界面,即用户看到的内容。视图通常是由HTML、CSS、JavaScript等Web技术组成的,并且通常是根据模型的数据动态生成的。在Spring Boot的MVC模式中,视图通常由模板引擎(如Thymeleaf、Freemarker等)处理,模板引擎将模型中的数据填充到模板中,生成最终的HTML页面。
- 控制器(Controller):控制器是应用程序的中心协调器,它处理来自用户界面的输入和模型的输出,将它们组合成一个完整的应用程序。控制器负责处理HTTP请求并调用模型和视图来生成响应。在Spring Boot的MVC模式中,控制器通常使用注解(如@RequestMapping、@GetMapping等)来处理请求,并且可以使用Spring提供的注解来注入依赖项和管理状态。
1.2.3 本文使用深信服产业教育中心实验平台,开启实验,点击实验靶标,将IP地址和SSH映射端口填入IDEA连接中。读者可以选择linux或者Docker镜像作为远程调试的实验环境。
1.2.4 项目目录选择/app,点击下载IDE并连接。
1.2.5 通过上述操作,会下载对应的GATEWAY,连接Docker容器。
1.2.6 点击连接,连接成功后,会显示下述内容。
1.2.7 双击APP,会打开新的远程连接IDEA。
至此远程连接成功。
1.3 创建Spring Boot项目
1.3.1 创建Mavne项目
Spring Boot项目通常采用标准的Maven项目结构,可以包含以下文件和目录:
- src/main/java: 存放项目的主要Java源代码
- src/test/java: 存放测试代码
- src/main/resources: 存放应用程序的配置文件、静态文件和其他资源
- src/test/resources: 存放测试资源文件
- pom.xml : Maven构建配置文件,用于定义项目依赖、构建插件、编译选项等
- application.properties Spring Boot应用程序的配置文件,包括数据库配置、服务器端口、日志级别等
- static/ 和 templates/ 目录: 存放静态文件和Thymeleaf模板文件,用于构建Web应用程序
根据MVC设计模式: - 模型(Model):模型表示应用程序的数据和业务逻辑,它是应用程序的核心部分。在Web应用程序中,模型通常由Java对象组成,这些对象封装了应用程序中的数据和相关的业务逻辑。控制器(Controller)可以从模型中获取数据,并将其呈现给用户界面。
- 视图(View):视图表示应用程序的用户界面,即用户看到的内容。视图通常是由HTML、CSS、JavaScript等Web技术组成的,并且通常是根据模型的数据动态生成的。在Spring Boot的MVC模式中,视图通常由模板引擎(如Thymeleaf、Freemarker等)处理,模板引擎将模型中的数据填充到模板中,生成最终的HTML页面。
- 控制器(Controller):控制器是应用程序的中心协调器,它处理来自用户界面的输入和模型的输出,将它们组合成一个完整的应用程序。控制器负责处理HTTP请求并调用模型和视图来生成响应。在Spring Boot的MVC模式中,控制器通常使用注解(如@RequestMapping、@GetMapping等)来处理请求,并且可以使用Spring提供的注解来注入依赖项和管理状态。
完整项目结构如下
src/
├── main/
│ ├── java/ #Java后台代码
│ │ └── com.example/
│ │ ├── controller/
│ │ │ └── HomeController.java #控制器,处理前端页面的请求 @GetMapping等
│ │ ├── dao/
│ │ │ ├── UserDao.java #数据库操作接口
│ │ │ └── UserDaoImpl.java #数据库操作类
│ │ ├── model/
│ │ │ └── User.java #模型层,数据库字段对应类
│ │ ├── service/
│ │ │ ├── UserService.java #业务逻辑层接口,接收控制层数据,传递给DAO层。
│ │ │ └── UserServiceImpl.java #业务逻辑层类
│ │ └── utils/
│ │ └── JdbcUtils.java #数据库连接文件
│ └── resources/ #前端代码
│ ├── static/
│ │ ├── css/
│ │ │ └── style.css
│ │ └── js/
│ │ └── app.js
│ ├── templates/
│ │ └── index.html
│ └── application.properties
lib/ #依赖库目录
├── mysql-connector-java-8.0.26.jar #MySQL数据库驱动
pom.xml #Maven项目配置文件
target/ #编译后的文件目录
├── classes/
│ ├── com/
│ │ └── example/
│ │ ├── controller/
│ │ │ └── HomeController.class
│ │ ├── dao/
│ │ │ ├── UserDao.class
│ │ │ └
pom.xml的作用
是Maven项目用于定义项目的配置和依赖项。
该文件位于项目的根目录中,是一个XML文件,包含有关项目的信息,例如其名称、版本和依赖项。
用于定义项目的依赖项。它指定了项目所依赖的库和框架,Maven使用此信息下载必要的依赖项并构建项目。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/1.0.0 http://maven.apache.org/xsd/maven-1.0.0.xsd">
<modelVersion>1.0.0</modelVersion>
<groupId>com.sangfor</groupId>
<artifactId>helloSql</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<java.version>17</java.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- Spring Framework -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.13</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.13</version>
</dependency>
<!-- MySQL Connector -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
<!-- Thymeleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<version>2.6.3</version>
</dependency>
<!-- JUnit 5 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.5.5</version>
</dependency>
</dependencies>
</project>
groupId和artifactId元素标识项目,version元素指定项目的版本。dependencies元素列出了项目的依赖项,这些依赖项是Spring Core和Spring Web库。
application.properties的作用
在Spring Boot项目中,application.properties是一个配置文件,用于配置应用程序。它位于项目的src/main/resources目录中。该文件包含键值对,用于定义应用程序的属性,例如服务器端口、数据库连接详细信息和日志设置。
# Database connection settings
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/sangfor?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true
spring.datasource.username=root
spring.datasource.password=root
# Hibernate settings
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
spring.jpa.show-sql=true
# Tomcat server settings
server.port=8080
server.servlet.context-path=/
Maven项目中target目录的作用
在Maven项目中,target目录是编译后的类和资源文件所在的目录,位于项目的根目录下。
当运行mvn package命令时,Maven将编译源代码并将其打包成JAR或WAR文件,该文件将放置在target目录中。
target目录还包含构建过程中生成的其他文件,如测试报告和Javadoc。
1.3.2 后端代码
HomeController.java
package com.sangfor.controller;
import com.sangfor.model.User;
import com.sangfor.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Controller
public class HomeController {
@Autowired
private UserService userService;
@GetMapping("/")
public String home() {
return "index";
}
@GetMapping("/findAll")
public List<User> findAll() {
User user = new User();
return userService.findAll(user);
}
@GetMapping("/findById/{id}")
public User findById(@PathVariable int id) {
User user = new User();
user.setId(id);
return userService.findById(user);
}
@PostMapping("/save")
public void save(@RequestBody User user) {
userService.save(user);
}
@PostMapping("/update")
public void update(@RequestBody User user) {
userService.update(user);
}
@PostMapping("/delete/{id}")
public void delete(@PathVariable int id) {
userService.delete(id);
}
}
@Controller是Spring框架中的一个注解,用于标识一个类是控制器层的组件。控制器层是MVC架构中的一部分,负责处理用户请求并返回响应。@Controller注解通常与@RequestMapping注解一起使用,后者用于将请求映射到控制器方法。
HomeController类被标记为控制器层组件,并使用@RequestMapping注解将根路径映射到home()方法。该方法返回一个名为index的视图名称,该视图将在/resources/templates目录下查找名为index.html的模板文件。
HomeController类使用@Autowired注解将UserService实例自动注入到userService字段中,SpringBoot框架的@Autowired类似于UserService userService = new userService();
UserDao.java
package com.sangfor.dao;
import java.util.List;
import com.sangfor.model.User;
public interface UserDao {
public List<User> findAll(User user);
public User findById(User user);
public void save(User user);
public void update(User user);
public void delete(int id);
}
UserDaoImpl.java
package com.sangfor.dao;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import com.sangfor.model.User;
import com.sangfor.utils.JdbcUtils;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@Repository
@RestController
public class UserDaoImpl implements UserDao {
@Autowired
JdbcUtils jdbc;
public List<User> findAll(@RequestBody User user) {
List<User> userList = new ArrayList<>();
try {
Connection conn = jdbc.getConnection();
String sql = "SELECT * FROM user";
PreparedStatement ps = conn.prepareStatement(sql);
ResultSet rs = ps.executeQuery();
while (rs.next()) {
user.setId(rs.getInt("id"));
user.setName(rs.getString("name"));
user.setAge(rs.getInt("age"));
user.setEmail(rs.getString("email"));
userList.add(user);
}
rs.close();
ps.close();
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
return userList;
}
public User findById(@RequestBody User user) {
try {
JdbcUtils jdbc = new JdbcUtils();
Connection conn = jdbc.getConnection();
String sql = "SELECT * FROM user WHERE id=?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setInt(1, user.getId());
ResultSet rs = ps.executeQuery();
if (rs.next()) {
user = new User();
user.setId(rs.getInt("id"));
user.setName(rs.getString("name"));
user.setAge(rs.getInt("age"));
user.setEmail(rs.getString("email"));
}
rs.close();
ps.close();
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
return user;
}
public void save(@RequestBody User user) {
try {
Connection conn = jdbc.getConnection();
String sql = "INSERT INTO user(name, age, email) VALUES(?,?,?)";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, user.getName());
ps.setInt(2, user.getAge());
ps.setString(3, user.getEmail());
ps.execute();
ps.close();
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
public void update(User user) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = jdbc.getConnection();
String sql = "UPDATE user SET name=?, age=?, email=? WHERE id=?";
ps = conn.prepareStatement(sql);
ps.setString(1, user.getName());
ps.setInt(2, user.getAge());
ps.setString(3, user.getEmail());
ps.setInt(4, user.getId());
ps.executeUpdate();
rs = ps.executeQuery();
} catch (SQLException e) {
e.printStackTrace();
}
}
public void delete(@RequestBody int id) {
try {
Connection conn = jdbc.getConnection();
String sql = "DELETE FROM user WHERE id=?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setInt(1, id);
ps.executeUpdate();
ResultSet rs = ps.executeQuery();
rs.close();
ps.close();
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
@Repository是Spring框架中的一个注解,用于标识一个类是数据访问层的组件。数据访问层是MVC架构中的一部分,负责与数据库进行交互。
@RestController是Spring框架中的一个注解,用于标识一个类是RESTful风格的控制器层组件。与@Controller注解不同,@RestController注解会自动将控制器方法的返回值转换为JSON格式的响应。
REST(Representational State Transfer)是一种Web服务的架构风格,是一种客户端和服务器之间的通信协议。 RESTful风格指的是使用REST原则设计和开发的Web服务。它强调使用HTTP协议中的GET、POST、PUT和DELETE等请求方法来进行资源的操作,通过URI(统一资源标识符)来唯一标识资源,使用MIME类型来标识传输的数据类型,并且不保持任何客户端的状态,即所谓的“无状态”设计。这种设计方式可以使得Web服务的接口简单、灵活、可扩展和可维护。 在RESTful风格的Web服务中,客户端可以通过HTTP请求方法和URI来请求资源的操作,服务器会响应相应的状态码和数据,以此实现客户端和服务器之间的数据交互。RESTful风格的Web服务通常使用JSON格式来进行数据交换,但也可以使用XML或其他格式。
User.java
package com.sangfor.model;
public class User {
private int id;
private String name;
private int age;
private String email;
public User() {}
public User(int id, String name, int age, String email) {
this.id = id;
this.name = name;
this.age = age;
this.email = email;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
UserService.java
package com.sangfor.service;
import java.util.List;
import com.sangfor.model.User;
public interface UserService {
public List<User> findAll(User user);
public User findById(User user);
public void save(User user);
public void update(User user);
public void delete(int id);
}
UserServiceImpl.java
package com.sangfor.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.sangfor.dao.UserDao;
import com.sangfor.model.User;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public List<User> findAll(User user) {
return userDao.findAll(user);
}
@Override
public User findById(User user) {
return userDao.findById(user);
}
@Override
public void save(User user) {
userDao.save(user);
}
@Override
public void update(User user) {
userDao.update(user);
}
@Override
public void delete(int id) {
userDao.delete(id);
}
}
@Override是Java中的一个注解,用于标识一个方法是覆盖父类或实现接口中的方法。当一个方法被标记为@Override时,编译器会检查该方法是否确实覆盖了父类或接口中的方法。如果没有覆盖成功,编译器会报错。
JdbcUtils.java
package com.sangfor.utils;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.beans.factory.annotation.Value;
@RestController
public class JdbcUtils {
@Value("${spring.datasource.url}")
private String url;
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.datasource.password}")
private String password;
static {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public Connection getConnection() throws SQLException {
return DriverManager.getConnection(url, username, password);
}
public static void close(ResultSet rs, PreparedStatement ps, Connection conn) {
try {
if (rs != null) {
rs.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (ps != null) {
ps.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (conn != null) {
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
@Value是Spring框架中的一个注解,用于将属性文件或环境变量中的值注入到Spring bean中。它可以用于注入简单的值,如字符串和数字,也可以用于注入更复杂的值,如数组和列表。
@Value注解将属性文件或环境变量中的application.properties属性的值注入到 @Value("${spring.datasource.url}")到URL字段中。
Application
package com.sangfor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
1.3.3 前端代码
前端代码使用ajax将json数据传递到后端代码中,实现前后端分离。
index.html
<!DOCTYPE html>
<html>
<head>
<title>Java-SQL</title>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script src="js/app.js"></script>
<link rel="stylesheet" type="text/css" href="css/style.css">
</head>
<body>
<div class="container">
<h1 class="title">Java-SQL</h1>
<form>
<div class="form-group">
<label for="id">ID:</label>
<input type="text" id="id" name="id">
</div>
<div class="form-group">
<label for="name">Name:</label>
<input type="text" id="name" name="name">
</div>
<div class="form-group">
<label for="email">Email:</label>
<input type="text" id="email" name="email">
</div>
<div class="button-group">
<button type="button" οnclick="addUser()">Add User</button>
<button type="button" οnclick="updateUser()">Update User</button>
<button type="button" οnclick="deleteUser()">Delete User</button>
<button type="button" οnclick="getUserById()">Get User By ID</button>
<button type="button" οnclick="getAllUsers()">Get All Users</button>
</div>
</form>
<div id="result"></div>
</div>
</body>
</html>
style.css
/* reset */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
/* global */
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: 1.5;
color: #24292e;
background-color: #f6f8fa;
}
.container {
width: 500px;
margin: 0 auto;
padding: 30px;
border-radius: 3px;
background-color: #fff;
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.2);
}
.title {
margin-bottom: 30px;
font-size: 32px;
font-weight: 400;
color: #24292e;
text-align: center;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: 600;
}
input[type="text"] {
width: 100%;
height: 36px;
padding: 6px 12px;
border: 1px solid #d1d5da;
border-radius: 3px;
background-color: #fff;
font-size: 16px;
line-height: 1.5;
color: #24292e;
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}
input[type="text"]:focus {
border-color: #0366d6;
outline: 0;
box-shadow: 0 0 0 3px rgba(3, 102, 214, 0.3);
}
.button-group {
display: flex;
justify-content: center;
align-items: center;
margin-top: 20px;
}
button {
margin: 0 10px;
padding: 10px 20px;
border-radius: 3px;
background-color: #f6f8fa;
color: #fff;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: background-color 0.15s ease-in-out;
}
button:hover {
background-color: #f6f8fa;
}
#result {
margin-top: 20px;
font-size: 16px;
line-height: 1.5;
color: #586069;
}
.button-group {
text-align: center;
margin-top: 20px;
}
.button-group button {
display: inline-block;
width: 150px;
height: 50px;
margin: 10px;
background-color: #24292e;
border: none;
border-radius: 5px;
color: white;
font-size: 16px;
cursor: pointer;
}
app.js
function addUser() {
var user = {
id: $("#id").val(),
name: $("#name").val(),
email: $("#email").val()
};
$.ajax({
url: "/save",
type: "POST",
contentType: "application/json",
data: JSON.stringify(user),
success: function(result) {
alert("添加成功!");
$("#result").html(result);
}
});
}
function updateUser() {
var user = {
id: $("#id").val(),
name: $("#name").val(),
email: $("#email").val()
};
$.ajax({
url: "/update/" + user.id,
type: "PUT",
contentType: "application/json",
data: JSON.stringify(user),
success: function(result) {
alert("修改成功!");
$("#result").html(result);
}
});
}
function deleteUser() {
var id = $("#id").val();
$.ajax({
url: "/delete/" + id,
type: "DELETE",
success: function(result) {
alert("删除成功!");
$("#result").html(result);
}
});
}
function getUserById() {
var id = $("#id").val();
$.ajax({
url: "/findById/" + id,
type: "GET",
success: function(result) {
alert("查询成功!");
$("#result").html(result);
}
});
}
function getAllUsers() {
$.ajax({
url: "/findAll",
type: "GET",
success: function(result) {
alert("查询成功!");
$("#result").html(result);
}
});
}
执行
执行后可以通过前端页面对数据库数据进行增删改查。
通过搭建springboot项目掌握:
1、idea远程编译代码
2、maven快速搭建springboot和maven项目结构
3、掌握springboot项目运行的基本流程和常见语法
4、使用jdbc连接和操作数据库
5、使用Json传递数据,了解前后端代码解耦方式
本期作者
迟忠旸:深信服竞赛负责人,深信服安全服务认证专家(SCSE-S),产业教育中心资深讲师
曾任职于中国电子科技网络信息安全有限公司,担任渗透测试工程师、安全讲师,多次为政府部门、大中型企业提供网络安全培训;具有丰富的内、外网渗透测试和红蓝对抗实战经验,擅长CTF、Web安全渗透测试、内网安全渗透测试、红蓝对抗等多个方向课程。