news 2026/3/2 20:43:18

JavaWeb后端阶段项目,从小白到高级开发,收藏这篇就足够了

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JavaWeb后端阶段项目,从小白到高级开发,收藏这篇就足够了

项目工程

一、环境搭建

application.properties文件配置:

spring.application.name=Spring1 #驱动类名称 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver #数据库连接的url spring.datasource.url=jdbc:mysql://localhost:3306/mybatis #连接数据库的用户名 spring.datasource.username=root #连接数据库的密码 spring.datasource.password=123456 #配置mybatis日志,指定输出到控制台 mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl #开启mybatis驼峰命名自动映射开关(例:a_column --->aColumn) mybatis.configuration.map-underscore-to-camel-case=true el-case=true

导入结构

controller(实体类)

mapper(接口)

pojo(实体类)

service(接口)

配置xml映射文件,编写SQL语句(在resources文件夹下新建)

和Mapper接口名一样,在resources下创建包时不能用 “.” 来分层,只能用 “/”,但是显现出的效果相同

约束直接从官方文档拷贝

在Mybatis中文网进入“入门”,找到配置SQL语句的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="Mapper接口的全类名"> </mapper>

二、开发规范

Restful特点

Rest是风格,是约定方式,约定不是规定,可以打破

描述模块的功能通常使用复数,也就是加s的格式来描述,表示此类资源,而非单个资源。如:user、emps等

前后端交互统一响应结果Result:(放在pojo包下)

@Data @NoArgsConstructor @AllArgsConstructor public class Result{ private Integer code; ==响应码,1代标成功,0代表失败 private String msg; ==响应信息,描述字符串 private Object data; ==返回的数据 public static Result success(){ ==增删改 响应成功 return new Result(1,"success",null); } public static Result success(Object data){ ==查询 响应成功 return new Result(1,"success",data); } public static Result error(String msg){ ==响应失败 return new Result(0,msg,null); } }

三、前后端联调

四、部门管理

(一)查询部门信息

在controller层下的DeptController类下编写

@Slf4j //省略Logger对象 @RestController //交给ioc容器管理,成为bean对象 public class DeptController { //@RequestMapping("/depts") //通过其value属性来指定请求路径,method属性来指定请求方式,下面为简化版 //有Get,Post,Put,Delete @GetMapping("/depts") public Result list() { log.info("查询全部数据");//输出日志 //调用Service查询数据 List<Dept> deptList = deptService.list(); return Result.success(deptList);//传递返回的值 } } ====以下为SLf4j的源码==== Example: @Slf4j public class LogExample { } will generate: public class LogExample { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExample.class); }

在Service层的DeptService接口下创建 list() 方法,并在具体的实现类中重写 list() 方法,创建mapper对象并调用其方法、
接口

/** * 部门管理 */ public interface DeptService { /* * 查询全部部门数据*/ List<Dept> list(); } //

实现类

@Service public class DeptServiceImpl implements DeptService { @Autowired private DeptMapper deptMapper;//调用Dao层从数据库获取数据 @Override public List<Dept> list() { return deptMapper.list(); } }

在Dao层实现从数据库获取数据,用注解方式进行调用

/** * 部门管理 */ @Mapper public interface DeptMapper { /* * 查询全部部门方法 * */ @Select("select * from dept") List<Dept> list(); }
(二)删除部门

controller层:

/** * 删除部门 * @return */ // @DeleteMapping("/depts/{id}") @DeleteMapping("/{id}") public Result delete(@PathVariable Integer id){ //使用@PathVariable注解去获取@RequestMapping中{}中传进来的值,并绑定到处理方法定一的形参上 //应用时,在@RequestMapping请求路径中,将需要传递的参数用花括号{}括起来, //然后通过@PathVariable("参数名称")获取URL中对应的参数值。 log.info("根据id删除部门:{}",id);//一个大括号就是一个参数占位符 deptService.delete(id); //调用Service删除部门 return Result.success(); }

service层
接口

/** * 根据id删除部门 * * @param id */ void delete(Integer id); //

实现类

@Override public void delete(Integer id) { deptMapper.delete(id); }

dao层

@Delete("delete from mybatis.dept where id = #{id}") void delete(Integer id);

PS:postman测试时记得把请求方式换成DELETE

(三)新增部门

五、员工管理

基本数据封装对象:PageBean

package com.itheima.pojo; import java.util.List; public class PageBean { private Long total; private List rows; public Long getTotal() { return total; } public void setTotal(Long total) { this.total = total; } public List getRows() { return rows; } public void setRows(List rows) { this.rows = rows; } }
补充:

@RequestParam,@PathVariable注解的区别:

  1. @RequestParam
    从前端获取参数,其中的 defaultValue 可设置默认值

  2. @PathVariable
    通过请求路径指定参数,其中 defaultValue() 设置默认值

    请求路径:/depts/{id}
  3. @DateTimeFormat
    其中的pattern可以设置日期的格式,如

    @DateTimeFormat (pattern = "yyyy-MM-dd")
  4. @RequestBody
    @RequsetParam 可以接受post请求,但是请求的contentType 为 form-data格式,而@RequestBody接受的请求方式application/json;charset=UTF-8

(一)分页查询员工列表
1、原始方法

controller层

@Slf4j //@RequestMapping("/emps") @RestController public class EmpController { @Autowired private EmpService empService; @GetMapping("/emps") public Result empSelect(@RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10")Integer pageSize){ //设置默认值 // if(page==null)page = 1; // if(pageSize==null)pageSize=10; log.info("员工信息分页查询第{}页{}条",page,pageSize); PageBean bean = empService.empSelect(page,pageSize); return Result.success(bean); } }

service层(在service层完成对数据的封装)

dao层

@Mapper public interface EmpMapper { @Select("select *from emp limit #{page},#{pageSize}") public List<Emp> empSelect(Integer page, Integer pageSize); @Select("select count(*) from emp") public Long empTotal(); }
2、PageHelper插件
  1. 配置pom.xml文件

    <!--PageHelper分页插件--> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.4.6</version> </dependency>
  2. EmpMapper

    /** * 员工信息查询 * @return */ @Select("select * from emp") public List<Emp> list();
  3. EmpServicelmp

    @Override public PageBean empSelect(Integer page, Integer pageSize) { //1.设置分页参数 PageHelper.startPage(page,pageSize); //2.执行查询 List<Emp> empList = empMapper.list(); Page<Emp> p =(Page<Emp>) empList;//强制转换成Page对象 //3.封装pagebean对象 PageBean bean = new PageBean(p.getTotal(),p.getResult()); return bean; }
3、条件分页查询(动态SQL)

SQL语句:

select * from emp where name like concat('%',?,'%') and gender = ? and entrydate between ? and ? order by update_time desc;

controller层

(补全参数)

public class EmpController { @Autowired private EmpService empService; @GetMapping("/emps") public Result empSelect(@RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10")Integer pageSize, String name, Short gender, @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin, @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end){ log.info("员工信息分页查询第{}页{}条",page,pageSize); PageBean bean = empService.empSelect(page,pageSize); return Result.success(bean); } }

service层

(改造empSelect方法)

接口

public interface EmpService { PageBean empSelect(Integer page, Integer pageSize,String name, Short gender, LocalDate begin, LocalDate end); }

实现类

@Service public class EmpServiceImpl implements EmpService { @Autowired private EmpMapper empMapper; @Override public PageBean empSelect(Integer page, Integer pageSize, String name, Short gender, LocalDate begin, LocalDate end) { //1.设置分页参数 PageHelper.startPage(page,pageSize); //2.执行查询 List<Emp> empList = empMapper.list(name,gender,begin ,end); Page<Emp> p =(Page<Emp>) empList;//强制转换成Page对象 //3.封装pagebean对象 PageBean bean = new PageBean(p.getTotal(),p.getResult()); return bean; } }

dao层

(改造list方法)

mapper

public List<Emp> list(String name, Short gender, LocalDate begin, LocalDate end);

EmpMapper映射文件

<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.itheima.mapper.EmpMapper"> <select id="list" resultType="com.itheima.pojo.Emp"> select * from emp <where> <if test="name!=null"> name like concat('%',#{name},'%') </if> <if test="gender!=null"> and gender = #{gender} </if> <if test="begin!=null and end!=null"> and entrydate between #{begin} and #{end} </if> </where> order by update_time desc </select> </mapper>
(二)批量删除员工数据

contraller

@DeleteMapping("/emps/{ids}") public Result Delete(@PathVariable ArrayList<Integer> ids){ log.info("批量删除,ids:{}",ids); empService.Delete(ids); return Result.success(); }

service

dao

(三)新增员工数据

controller

@PostMapping("/emps") public Result save(@RequestBody Emp emp){ log.info("新增员工,{}",emp); empService.Insert(emp); return Result.success(); }

service

dao

@Insert("Insert Into emp (username, name, gender, image, job, entrydate, dept_id, create_time, update_time) " + "VALUES (#{username},#{name},#{gender},#{image},#{job},#{entrydate},#{deptId},#{createTime},#{updateTime})") void Insert(Emp emp);
(四)文件上传

将本地图片、视频、音频上传到服务器供其他用户浏览或下载

1.本地存储

代码实现:

  1. 在服务器本地磁盘上创建images目录,用来存储上传的文件(例:E盘创建images目录)
  2. 使用MultipartFile类提供的API方法,把临时文件转存到本地磁盘目录下

MultipartFile 常见方法:

把前端文件复制到resources文件夹下的static中

controller

package com.itheima.controller; import com.itheima.pojo.Result; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.IOException; import java.util.UUID; @Slf4j //日志注解 @RestController public class UploadController { @PostMapping("/upload") public Result upload(String username, Integer age, MultipartFile image) throws IOException { //也可以用@RequestParm注解指定对应参数,如:@RequestParm("image") MultipartFile file log.info("文件上传,{},{},{}",username,age,image); //1.获取原始文件名 String imageName = image.getOriginalFilename(); //2.构成新文件名 int index = imageName.lastIndexOf('.'); UUID uuid = UUID.randomUUID(); String newName = uuid.toString()+imageName.substring(index); //3.将文件存储在服务器的磁盘目录中:E:\javaprogram\SaveImages image.transferTo(new File("E:\\javaprogram\\SaveImages\\"+newName));//该方法将接收到的文件转存到指定磁盘目录下 log.info("新文件名,{}",newName); return Result.success(); } }

其中,文件默认不超过1M

在properties文件中修改限制

#配置单个文件最大上传大小 spring.servlet.multipart.max-file-size = 10MB #配置单个请求最大上传大小(一次请求能上传的总文件大小) sprin.servlet.multipart.max-file-size = 100MB
2.阿里云存储OSS

第三方服务思路

SDK:Software Development Kit,软件开发工具包,包括辅助软件开发的依赖(jar包)、代码示例等

Bucket:存储空间是用户用于存储对象(Object,就是文件)的容器,所有的对象都必须隶属于某个存储空间。

  1. 开通OSS服务之后,就可以进入到阿里云对象存储的控制台



  1. 点击左侧的 “Bucket列表”,创建一个Bucket



AccessKey:

AccessKey ID:
AccessKey Secret:

入门程序

引入依赖项

<dependency> <groupId>com.aliyun.oss</groupId> <artifactId>aliyun-sdk-oss</artifactId> <version>3.17.4</version> </dependency> <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.3.1</version> </dependency> <dependency> <groupId>javax.activation</groupId> <artifactId>activation</artifactId> <version>1.1.1</version> </dependency> <!-- no more than 2.3.3--> <dependency> <groupId>org.glassfish.jaxb</groupId> <artifactId>jaxb-runtime</artifactId> <version>2.3.3</version> </dependency>

上传文件流

以下代码用于将文件流上传到目标存储空间examplebucket中exampledir目录下的exampleobject.txt文件。

1.修改Endpoint

2.填写bucketName

3.指定保存至阿里云时的名称 objectName

4.指定将要上传的文件的本地路径 filePath

5.配置id和secret

import com.aliyun.oss.ClientException; import com.aliyun.oss.OSS; import com.aliyun.oss.common.auth.*; import com.aliyun.oss.OSSClientBuilder; import com.aliyun.oss.OSSException; import com.aliyun.oss.model.PutObjectRequest; import com.aliyun.oss.model.PutObjectResult; import java.io.File; public class Demo { public static void main(String[] args) throws Exception { // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。 String endpoint = "https://oss-cn-hangzhou.aliyuncs.com"; // 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。 EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider(); // 填写Bucket名称,例如examplebucket。 String bucketName = "examplebucket"; // 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。 String objectName = "exampledir/exampleobject.txt"; // 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。 // 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件。 String filePath= "D:\\localpath\\examplefile.txt"; // 创建OSSClient实例。 OSS ossClient = new OSSClientBuilder().build(endpoint, credentialsProvider); try { // 创建PutObjectRequest对象。 PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, new File(filePath)); // 如果需要上传时设置存储类型和访问权限,请参考以下示例代码。 // ObjectMetadata metadata = new ObjectMetadata(); // metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString()); // metadata.setObjectAcl(CannedAccessControlList.Private); // putObjectRequest.setMetadata(metadata); // 上传文件。 PutObjectResult result = ossClient.putObject(putObjectRequest); } catch (OSSException oe) { System.out.println("Caught an OSSException, which means your request made it to OSS, " + "but was rejected with an error response for some reason."); System.out.println("Error Message:" + oe.getErrorMessage()); System.out.println("Error Code:" + oe.getErrorCode()); System.out.println("Request ID:" + oe.getRequestId()); System.out.println("Host ID:" + oe.getHostId()); } catch (ClientException ce) { System.out.println("Caught an ClientException, which means the client encountered " + "a serious internal problem while trying to communicate with OSS, " + "such as not being able to access the network."); System.out.println("Error Message:" + ce.getMessage()); } finally { if (ossClient != null) { ossClient.shutdown(); } } } }

由于oss进行了更新,所以Id和Secret的配置参照该文章:

后端之路——阿里云OSS云存储-CSDN博客

集成

UploadController

1.接收上传的图片

2.存储图片(OSS)

3.返回访问图片的url

  1. 引入阿里云OSS工具类(示例代码改)
    新建utils包

    package com.itheima.utils; import com.aliyun.oss.OSS; import com.aliyun.oss.OSSClientBuilder; import com.aliyun.oss.common.auth.CredentialsProviderFactory; import com.aliyun.oss.common.auth.EnvironmentVariableCredentialsProvider; import org.springframework.web.multipart.MultipartFile; import java.io.*; import java.util.UUID; /** * 阿里云 OSS 工具类 */ @Component //解释:utils不属于三层,交给IOC容器管理直接加@Component public class AliOSSUtils { private String endpoint = "https://oss-cn-beijing.aliyuncs.com"; private String bucketName = "bucket122333"; /** * 实现上传图片到OSS */ public String upload(MultipartFile file) throws Exception { // 获取上传的文件的输入流 InputStream inputStream = file.getInputStream(); // 避免文件覆盖 String originalFilename = file.getOriginalFilename(); String fileName = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf(".")); //上传文件到 OSS EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider(); OSS ossClient = new OSSClientBuilder().build(endpoint, credentialsProvider); ossClient.putObject(bucketName, fileName, inputStream); //文件访问路径 String url = endpoint.split("//")[0] + "//" + bucketName + "." + endpoint.split("//")[1] + "/" + fileName; // 关闭ossClient ossClient.shutdown(); return url;// 把上传到oss的路径返回 } }
  2. 上传图片接口开发

controller

package com.itheima.controller; import com.itheima.pojo.Result; import com.itheima.utils.AliOSSUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.IOException; import java.util.UUID; @Slf4j //日志注解 @RestController public class UploadController { @Autowired private AliOSSUtils aliOSSUtils; // 阿里云OSS存储 @PostMapping("/upload") public Result upload(MultipartFile image) throws Exception { log.info("文件上传,文件名{}",image.getOriginalFilename()); //调用阿里云OSS工具类进行文件上传 String url = aliOSSUtils.upload(image); log.info("文件上传成功,文件url{}",url); return Result.success(url); } }
(五)修改员工数据
1.回显

controller

@GetMapping("/{id}") public Result search(@PathVariable Integer id){ log.info("根据id查询员工,id:{}",id); Emp emp = empService.Search(id); return Result.success(emp); }

service

dao

@Select("select * from emp where id = #{id}") Emp Search(Integer id);
2.修改数据

controller

@PutMapping public Result update(@RequestBody Emp emp){ log.info("根据id修改员工数据,id:{}",emp.getId()); empService.update(emp); return Result.success(); }

service

dao

六、配置文件

(一)参数配置化(properties)

问题分析:AliOSSUtils文件下的第三方信息硬编码在代码中,不便于维护和管理

private String endpoint = "https://oss-cn-beijing.aliyuncs.com"; //地址 private String bucketName = "bucket122333"; //库名

问题解决:在application.properties文件中配制阿里云信息

#自定义阿里云配置信息 aliyun.oss.endpoint = https://oss-cn-beijing.aliyuncs.com aliyun.oss.bucketName = bucket122333

@Value 注解:用于外部配置的属性注入,具体用法如下

@Value("${配置文件中的key}")
@Component //解释:utils不属于三层,交给IOC容器管理直接加@Component public class AliOSSUtils { @Value("${aliyun.oss.endpoint}") private String endpoint = "https://oss-cn-beijing.aliyuncs.com"; @Value("${aliyun.oss.bucketName}") private String bucketName = "bucket122333";
(二)yml配置文件

SpringBoot提供了多种属性配置方式

常见配置文件格式:

yml基本语法

yml和properties对比

spring: application: name: Spring1 #数据库连接信息 datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mybatis username: root password: 123456 #密码无法连接数据库时用单引号引起来 #文件上传配置 servlet: multipart: max-file-size: 10MB max-request-size: 100MB #Mybatis配置 mybatis: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true #阿里云OSS配置 aliyun: oss: bucketName: bucket122333 endpoint: https://oss-cn-beijing.aliyuncs.com
spring.application.name=Spring1 #驱动类名称 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver #数据库连接的url spring.datasource.url=jdbc:mysql://localhost:3306/mybatis #连接数据库的用户名 spring.datasource.username=root #连接数据库的密码 spring.datasource.password=123456 #配置mybatis日志,指定输出到控制台 mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl #开启mybatis驼峰命名自动映射开关(例:a_column --->aColumn) mybatis.configuration.map-underscore-to-camel-case=true #配置单个文件最大上传大小 spring.servlet.multipart.max-file-size = 10MB #配置单个请求最大上传大小(一次请求能上传的总文件大小) spring.servlet.multipart.max-request-size = 100MB #配置阿里云OSS aliyun.oss.endpoint = https://oss-cn-beijing.aliyuncs.com aliyun.oss.bucketName = bucket122333
(三)@ConfigurationProperties

可以将配置文件中配置项的值自动注入到对象的属性中(取代@Value)

前提条件:

@Component //解释:utils不属于三层,交给IOC容器管理直接加@Component @Data @ConfigurationProperties(prefix = "aliyun.oss" ) public class AliOSSUtils { // @Value("${aliyun.oss.endpoint}") private String endpoint ; // @Value("${aliyun.oss.bucketName}") private String bucketName ;

可选操作:解决报红

引入configuration依赖,自动识别被@ConfigurationProperties注解标识的bean对象,在配置文件中配置时自动提示与该bean对象属性名相对应的配置项名字

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> </dependency>

如图所示

@ConfigurationProperties 和 @Value 的对比

七、基础登录取功能

(一)登录基本操作

controller(其中判断用户名和密码是否正确的

操作放在该层)

@Slf4j @RestController public class LoginController { @Autowired private EmpService empService; @PostMapping public Result login(@RequestBody Emp emp){ log.info("员工登录:{}",emp); Emp e = empService.login(emp); if(e!=null){ return Result.success(); }else { return Result.error("用户名或密码错误"); } } }

service

dao

/** * 根据用户名和密码查询员工 * @param emp * @return */ @Select("select * from emp where username = #{username} and password = #{password}") Emp getByUsernameAndPassword(Emp emp);
(二)会话技术
(1)Cookie

1.Cookie:HTTP请求头包含存储先前通过与所述服务器发送的HTTP cookies Set-Cookie 头

2.Set-Cookie:所述Set-Cookie HTTP响应头被用于从服务器向代理发送 cookie

存储在浏览器本地,请求之后返回cookie值,下次访问时从浏览器本地发送cookie

(2)Sessions

存储在服务器端,请求后从服务端查询是否存在相应值,但是服务器集群无法使用

(3)JWT令牌(主流)


配置xml文件

<!--JWT令牌--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>

在测试类中添加测试方法生成令牌

(要是报错ClassNotFoundException,添加依赖jaxb-api)

<!--jaxb-api--> <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.3.1</version> </dependency>
@Test //生成JWT令牌 public void testGenJwt(){ Map<String, Object> claims = new HashMap<>(); claims.put("id",1); claims.put("name","tom"); String jwt = Jwts.builder() .signWith(SignatureAlgorithm.HS256, "itheima")//用于设置数字签名的算法,例如:HS256(256位密钥) .setClaims(claims)//自定义内容(载荷) .setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000))//设置有效期为1小时 .compact();//获取成字符串 System.out.println(jwt); }
@Test public void testParseJWT(){ Map<String, Object> claims = Jwts.parser() .setSigningKey("itheima")//指定签名密钥 .parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoidG9tIiwiaWQiOjEsImV4cCI6MTcyOTQzMTk0MX0.uZw_C-9qhHFakqoLyG3Jh-MRQrwSmHCN2kfgQBdUhSY")//解析生成的jwt令牌(2024/10/20过期) .getBody(); System.out.println(claims); }
(三)使用JWT令牌完成登录校验

思路

1.引入JWT令牌工具类

package com.itheima.utils; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import java.util.Date; import java.util.Map; public class JwtUtils { private static String signKey = "itheima"; private static Long expire = 43200000L; /** * 生成JWT令牌 * @param claims JWT第二部分负载 payload 中存储的内容 * @return */ public static String generateJwt(Map<String, Object> claims){ String jwt = Jwts.builder() .addClaims(claims) .signWith(SignatureAlgorithm.HS256, signKey) .setExpiration(new Date(System.currentTimeMillis() + expire)) .compact(); return jwt; } /** * 解析JWT令牌 * @param jwt JWT令牌 * @return JWT第二部分负载 payload 中存储的内容 */ public static Claims parseJWT(String jwt){ Claims claims = Jwts.parser() .setSigningKey(signKey) .parseClaimsJws(jwt) .getBody(); return claims; } }

2.修改登录controller代码

@Slf4j @RestController public class LoginController { @Autowired private EmpService empService; @PostMapping("/login") public Result login(@RequestBody Emp emp){ log.info("员工登录:{}",emp); Emp e = empService.login(emp); if(e!=null){ //登录成功,生成令牌,下发令牌 Map<String,Object> jwt = new HashMap<>(); //在jwt令牌生成中加上ID,用户名,姓名 jwt.put("ID",e.getId()); jwt.put("username",e.getUsername()); jwt.put("Name",e.getName()); String s = JwtUtils.generateJwt(jwt); return Result.success(s); }else { //登录失败,返回 return Result.error("用户名或密码错误"); } } }
(四)统一拦截
(1)Filter过滤器

基本操作:

  1. 定义Filter:定义一个类实现Filter接口

    PS:导入的Filter是javax.servlet.Filter

  2. 配置Filter:在Filter类上加上@WebFilter注解,配置拦截资源的路径(urlPatterns),启动类加上@ServletComponentScan开启Servlet组件支持

    定义和配置Filter:

    package com.itheima.Filter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import java.io.IOException; @WebFilter(urlPatterns = "/*") //拦截所有请求 public class DemoFilter implements Filter { @Override //初始化方法,只调用一次,默认实现 public void init(FilterConfig filterConfig) throws ServletException { //System.out.println("init初始化方法执行了"); Filter.super.init(filterConfig); } @Override //拦截到请求都会调用 public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("拦截到了请求"); } @Override //销毁方法,只调用一次,默认实现 public void destroy() { //System.out.println("destroy销毁方法执行了"); Filter.super.destroy(); } }

    配置启动类:

    @ServletComponentScan //配置Servlet组件支持 @SpringBootApplication public class Spring1Application { public static void main(String[] args) { SpringApplication.run(Spring1Application.class, args); } }

过滤器执行流程:

Filter的拦截路径:可以根据需求配置不同的拦截资源路径

拦截路径urlPatterns值含义
拦截具体路径/login只有访问/login路径时,才会被拦截
目录拦截/emps/*访问/emps下的所有资源时,会被拦截
拦截所有/*访问所有资源,都会被拦截

过滤器链:一个web应用中可以配置多个过滤器,形成一个过滤器链。

访问流程:filter1放行前–》放行–》filter2放行前–》放行–》访问资源–》filter2放行后–》filter1放行后

abc过滤器(" /* ")

@WebFilter(urlPatterns = "/*") public class AbcFilter implements Filter { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("abc拦截到了请求"); //放行 filterChain.doFilter(servletRequest,servletResponse); System.out.println("abc放行后执行逻辑"); } }

demo过滤器(" /login ")

@WebFilter(urlPatterns = "/login")//拦截所有请求 public class DemoFilter implements Filter { @Override //拦截到请求都会调用 public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("demo拦截到了请求"); //放行 filterChain.doFilter(servletRequest,servletResponse); System.out.println("demoF拦截请求放行"); } }

顺序:注解配置的Filter,优先级按照过滤器类名(字符串)的自然排序

登录校验功能实现:

流程图:

步骤:

阿里巴巴fastjosn:手动把Result转换成json数据(版本号改成2.0.51可用)

<!--阿里巴巴fastjosn--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> </dependency>

代码实现:

@Slf4j @WebFilter(urlPatterns = "/*") public class LoginCheckFilter implements Filter { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) servletRequest;//强转为http获取请求参数 HttpServletResponse resp = (HttpServletResponse) servletResponse;//强转为http获取响应参数 //> 1- 获取url String url = req.getRequestURL().toString(); log.info("请求的url:{}",url); //> 2- 判断请求url中是否包含login,如果包含,放行 if(url.contains("login")){ log.info("登录操作,放行"); filterChain.doFilter(servletRequest,servletResponse); return; } //> 3- 获取请求头(token)中令牌 见api文件 String jwt = req.getHeader("token"); //> 4- 判断令牌是否存在,如果不存在返回错误结果 if(!StringUtils.hasLength(jwt)){ log.info("令牌为空,失败"); Result notLogin = Result.error("NOT_LOGIN"); //手动转化 对象--JSON-------》阿里fastJOSN // 这里的JSONObject是com.alibaba.fastjson.JSONObject String notl = JSONObject.toJSONString(notLogin); resp.getWriter().write(notl);//直接响应给浏览器 return; } //> 5- 解析token,如果解析失败返回错误结果 try { JwtUtils.parseJWT(jwt); } catch (Exception e) { //解析失败 e.printStackTrace(); log.info("jwt令牌解析失败"); Result notLogin = Result.error("NOT_LOGIN"); String notl = JSONObject.toJSONString(notLogin); resp.getWriter().write(notl);//直接响应给浏览器 return; } //> 6- 放行 log.info("令牌有效,放行"); filterChain.doFilter(servletRequest,servletResponse); } }
(2)Interceptor拦截器

基本操作:

  1. 定义拦截器:定义一个类实现Interceptor接口

  2. 注册拦截器

定义拦截器

@Component //交给ioc容器管理 public class LoginCheckInterceptor implements HandlerInterceptor { @Override //目标资源方法运行前运行,返回true:放行 false:不放行 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("preHandle........"); return true; } @Override //目标资源方法运行后运行 public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("postHandle///////"); } @Override //视图渲染完毕后运行,最后运行 public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("afterCompletion........"); } }

注册拦截器(新建配置类包)

@Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private LoginCheckInterceptor interceptor; @Override //注册拦截器 public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(interceptor).addPathPatterns("/**").excludePathPatterns("/login") //需要拦截的资源 //不需要拦截的资源 } }
拦截路径含义举例
/*一级路径能匹配/depts,/emps,/login,不能匹配/depts/1
/**任意级路径能匹配任意级路径
/depts/*/depts下的一级路径能匹配/depts/1,不能匹配/depts/1/2,/depts
/depts/**/depts下的任意级路径能匹配/depts/1,/depts/1/2,/depts,不能匹配/emps/1

拦截器、过滤器执行流程:

拦截器和过滤器之间的区别:

八、异常处理

异常抛出过程:

九、事务管理

(一)回顾事务

概念:事务是一组操作的集合,它是一个不可分割的工作单位,这些操作要么同时成功,要么同时失败

操作

完善删除部门操作

@Override public void delete(Integer id) { //1.删除部门 deptMapper.delete(id); //2.根据部门id删除部门下的员工信息 empMapper.DeleteByDeptId(id); }
/** * 根据部门id删除部门下员工 * @param id */ @Delete("delete from emp where dept_id = #{id}") void DeleteByDeptId(Integer id); }
(二)Spring事务管理

执行多次数据访问操作的方法上添加@Transactional注解

开启事务管理日志:

logging: level: org.springframework.jdbc.support.JdbcTransactionManager:debug
@Transactional//事务注解 @Override public void delete(Integer id) { //1.删除部门 deptMapper.delete(id); int i = 1/0;//模拟异常 //2.根据部门id删除部门下的员工信息 empMapper.DeleteByDeptId(id); }
(三)事务进阶
1.rollbackFor

默认情况下,只有出现RuntimeException才会回滚异常。rollbackFor属性用于控制出现何种异常,回滚事务

设置@Transactional的rollbackFor值

//出现任何异常都回滚 @Transactional(rollbackFor = Exception.class)//事务注解 @Override public void delete(Integer id) { //1.删除部门 deptMapper.delete(id); // int i = 1/0;//模拟异常 //2.根据部门id删除部门下的员工信息 empMapper.DeleteByDeptId(id); }
2.propagation(事务传播行为)

事务的传播行为:指的是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制

例如

//a方法 @Transactional public void a(){ //... userService.b(); //... }
//b方法 @Transactional public void b(){ //... }

此时的b方法是否需要开启一个新的事务?

事务的常见的传播行为

属性值含义
REQUIRED【默认值】需要事务,有则加入,无则创建新事务
REQUIRES_NEW需要新事务,无论有无,总是创建新事务
SUPPORTS支持事务,有则加入,无则在无事务的状态中运行
NOT_SUPPORTED不支持事务,在无事务的状态下运行,如果当前存在已有事务,则挂起当前事务
MANDATORY必须有事务,否则抛出异常
NEVER必须没事务,否则抛出异常

补充代码:

解散部门

需求:解散部门时,无论是成功还是失败,都要记录操作日志

步骤:

  1. 解散部门:删除部门、删除部门下的员工
  2. 记录日志到数据库表中

(1)记录日志

@Transactional(rollbackFor = Exception.class)//事务注解 @Override public void delete(Integer id) { //1.删除部门 deptMapper.delete(id); // int i = 1/0;//模拟异常 //2.根据部门id删除部门下的员工信息 empMapper.DeleteByDeptId(id); //记录解散部门的操作 DeptLog deptLog = new DeptLog(); deptLog.setCreateTime(LocalDateTime.now()); deptLog.setDescription("执行了解散部门的操作,此次解散的是"+id+"号部门"); deptLogService.insert(deptLog); }

(2)根据需求优化代码(try-catch-finally)

@Transactional(rollbackFor = Exception.class)//事务注解 @Override public void delete(Integer id) { //1.删除部门 try { deptMapper.delete(id); // int i = 1/0;//模拟异常 //2.根据部门id删除部门下的员工信息 empMapper.DeleteByDeptId(id); } finally { //记录解散部门的操作 DeptLog deptLog = new DeptLog(); deptLog.setCreateTime(LocalDateTime.now()); deptLog.setDescription("执行了解散部门的操作,此次解散的是" + id + "号部门"); deptLogService.insert(deptLog); } }

由于默认值使得insert方法中的事务传播行为是 加入已有事务,所以当删除部门报错后,记录日志的操作会被一同回滚。

(3)修改 DeptLogServiceImpl 类下的 insert 方法的事务传播行为 (**REQUIRES_NEW**新建一个事务)

@Transactional(propagation = Propagation.REQUIRES_NEW) @Override public void insert(DeptLog deptLog) { deptLogMapper.insert(deptLog); }

十、AOP基础

概念:面向切面编程、面向方向编程,其实就是面向特定方法编程。

场景:案例部分功能运行较慢,定位执行耗时较长的业务方法,此时需要统计每一个业务方法的执行耗时

  1. 获取方法开始时间
  2. 运行原始方法
  3. 获取方法运行结束时间,计算执行耗时

实现:动态代理是面向切面编程最主流的实现。而SpringAOP是Spring框架的高级技术,旨在管理bean对象的过程中,主要通过底层的都太代理机制,对特定的方法进行编程。

(一)快速入门:

统计各个业务层方法执行耗时

  1. 导入依赖:在pom.xml中导入AOP的依赖

    <!--AOP依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
  2. 编写AOP程序:针对于特定方法根据业务需要进行编程

    package com.itheima.aop; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; @Slf4j @Component @Aspect public class TimeAspect { @Around("execution(* com.itheima.service.*.*(..))") //切入点表达式 public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable{ //1.记录开始时间 long begin = System.currentTimeMillis(); //2.调用原始方法运行 Object result = joinPoint.proceed(); //3.记录结束时间,计算方法执行耗时 long end = System.currentTimeMillis(); log.info(joinPoint.getSignature()+"方法执行耗时:{}ms",end - begin);//getSignature可以拿到方法名 return result; } }

    AOP应用场景:记录操作日志、权限控制、事务管理等

    优势:代码无侵入、减少重复代码、提高开发效率、维护方便

(二)核心概念

执行过程:程序运行时会自动为目标对象生成一个代理对象,在代理对象中对目标对象的功能进行增强(在不修改源代码的前提下增加了功能),即通知内的代码进行拼接,最后注入的时候不再注入目标对象而是代理对象,调用方法的时候调用的则是代理对象的方法

(三)通知类型以及顺序
各种通知类型
  1. @Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行
  2. @Before:前置通知,此注解标注的通知方法在目标方法前被执行
  3. @After:后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行
  4. @AfterReturning:返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行
  5. @AfterThrowing:异常后通知,此注解标注的通知方法发生异常后执行

正常情况下采用

@Before("execution(路径)") public void before(JoinPoint joinPoint){ log.info("before ..."); }
公共表达式@Pointcut

其中,Spring中提供了@Pointcut注解,即公共切入点表达式,用新构造的空方法替代execution,如下所示

@Slf4j @Component @Aspect public class MyAspect1 { //切入点方法(公共的切入点表达式) @Pointcut("execution(* com.itheima.service.*.*(..))") private void pt(){ } //前置通知(引用切入点) @Before("pt()") public void before(JoinPoint joinPoint){ log.info("before ..."); } //环绕通知 @Around("pt()") public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { log.info("around before ..."); //调用目标对象的原始方法执行 Object result = proceedingJoinPoint.proceed(); //原始方法在执行时:发生异常 //后续代码不在执行 log.info("around after ..."); return result; } //后置通知 @After("pt()") public void after(JoinPoint joinPoint){ log.info("after ..."); } //返回后通知(程序在正常执行的情况下,会执行的后置通知) @AfterReturning("pt()") public void afterReturning(JoinPoint joinPoint){ log.info("afterReturning ..."); } //异常通知(程序在出现异常的情况下,执行的后置通知) @AfterThrowing("pt()") public void afterThrowing(JoinPoint joinPoint){ log.info("afterThrowing ..."); } }

也可以修改公共切入点表达式方法的属性(如private改成public),便可以在其他aop类下使用该表达式的路径

通知顺序

默认情况下,当有多个切面的切入点都匹配到了目标方法,目标方法运行时,多个通知方法都会被执行,并且按照切面类类名的字母顺序,before字母排名靠前的先执行,after字母排名靠后的先执行

Spring提供了@Order( )注解加在切面类上来控制顺序

(四)切入点表达式

常见形式:

(1)execution(…)

根据方法的签名来匹配

格式:execution(访问修饰符 返回值 包名.类名.方法名(方法参数) throws 异常)
@Before("execution(public void com.itherma.service.impl.DeptServiceImpl.delete(java.lang.Integer))") public void before(JoinPoint joinPoint){ }

通配符号:

同时匹配两个方法:

这里需要匹配的是DeptService下的两个方法

List<Dept> list(); 和 void delete(Integer id);
@Pointcut("execution(* com.itheima.service.DeptService.list()) ||" + "execution(* com.itheima.service.DeptService.delete())") private void pointcut(){ }
(2)@annotation(…)

根据注释匹配,用于匹配标识有特定注解的方法

@Before("@annotation(com.itheima.anno.Log)") public void before(){ }

步骤:

  1. 自定义注解Log

    @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD)//作用于方法上 public @interface MyLog { }
  2. 在对应方法上加上自定义注解(起到标识作用)

    @Log @Override public List<Dept> list() { List<Dept> deptList = deptMapper.list(); return deptList; } @Log @Override public void delete(Integer id) { deptMapper.delete(id); }
  3. 改写切入点表达式

    匹配DeptServiceImpl中的 list() 和 delete(Integer id)方法 //@Pointcut("execution(* com.itheima.service.DeptService.list()) || execution(* com.itheima.service.DeptService.delete(java.lang.Integer))") @Pointcut("@annotation(com.itheima.aop.Log)") private void pt(){}
注解和反射(补充内容,之前学得不深入)

基础格式:

@元注解 public @interface Test{ 定义内容 }

(一)元注解负责解释自定义注解,决定了我们自定义注解的应用范围,java定义了4个标准的meta-annotation类型

  1. @Target({ElementType.xxx, ElementType.xxx})

    表示自定义注解的作用范围

  2. @Retention(RetentionPolicy.xxx)

    表示自定义注解的生命周期

  3. @Documented

    说明该注解将被包含在javadoc中

  4. @Inherited

    表示子类可以继承父类中的该注解

(二)定义内容

  1. 其中的每一个方法实际上是声明了一个配置参数
  2. 方法的名称就是参数的名称
  3. 返回值类型就是参数的类型
  4. 可以通过default来声明参数的默认值
  5. 如果只有一个成员参数一般参数名为value
//自定义注解 @Target({ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation{ //注解的参数:参数类型 + 参数名(); String name() default ""; int age() default 0; int id() default -1; //-1表示不存在 }
//使用该注解的类/方法 public class test{ //注解可以显示赋值,若没有默认值,我们就必须给注解赋值 @MyAnnotation(name = "小明") public void test(){ } }

(三)反射(获取注解的途径)

Java反射机制的反射类:Class,Method,Field,Constructor

常用方法getClass,getinterfaces,getMethods,getDeclaredField,getDeclaredConstructor,getAnnotarion,Class.forName(“字符串”),newInstance

用反射操作

public class T { private int age; public int getAge() { return age; } public void setAge(int age) { this.age = age; } public T() { } public T(int age) { this.age = age; } public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException { Class c1 = Class.forName("com.test53_Annotation.T"); //通过无参构造器(本质)创建对象 T my1 =(T) c1.newInstance(); System.out.println(my1); //通过反射获取构造器并创建对象 Constructor constructor= c1.getDeclaredConstructor(int.class); T my2 = (T) constructor.newInstance(10); System.out.println(my2); //通过反射获取方法 T my3 =(T) c1.newInstance(); Method MYmethod = c1.getMethod("setAge", int.class); //invoke:激活 //给方法传递(对象,“方法的值”)没有值填null MYmethod.invoke(my3,10086); System.out.println(my3.getAge()); //通过反射操作属性 T my4 = (T)c1.newInstance(); Field MYage = c1.getDeclaredField("age"); MYage.setAccessible(true);//关闭权限检测,可以访问private修饰的字段,其他class,method同理 MYage.set(my4,999); System.out.println(my4.age); } }

通过反射获取注解信息

import lombok.Data; import java.lang.annotation.*; import java.lang.reflect.Field; public class Test1 { public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException { //获取类的对象 Class c1 = Class.forName("com.test53_Annotation.Student"); Annotation[] annotations = c1.getAnnotations(); //通过反射获取注解 for (Annotation a:annotations ) { System.out.println(a); } //通过反射获取Table注解的值 Table table = (Table) c1.getAnnotation(Table.class); System.out.println(table.value()); //通过反射获取Field注解的值 Field f1 = c1.getDeclaredField("name"); f1.setAccessible(true);//修改权限 MField mf = f1.getAnnotation(MField.class); System.out.println(mf.columnName()+" "+mf.type()+" "+mf.length()); } } @Data @Table(value = "db_mybatis") class Student{ @MField(columnName = "id",type = "int",length = 10) private int id; @MField(columnName = "age",type = "int",length = 3) private int age; @MField(columnName = "name",type = "varchar",length = 5) private String name; public Student(int id, int age, String name) { this.id = id; this.age = age; this.name = name; } public Student(){ } } @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @interface Table{ String value();//表名 } @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @interface MField { String columnName();//列名 String type();//类型 int length();//长度 }
(五)连接点

在Spring中用JoinPoint抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名,方法名,方法参数等

//公共切入点表达式 @Pointcut("execution(* com.itheima.service.DeptService.*(..))") private void pt(){} @Before("pt()") public void before(JoinPoint joinPoint){ log.info("MyAspect8 ... before ..."); } @Around("pt()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { log.info("MyAspect8 around before ..."); //1. 获取 目标对象的类名 . String className = joinPoint.getTarget().getClass().getName();//Target:目标 log.info("目标对象的类名:{}", className); //2. 获取 目标方法的方法名 . String methodName = joinPoint.getSignature().getName();//Signature:全称是Method Signature,方法签名,包括方法名称、参数列表等 log.info("目标方法的方法名: {}",methodName); //3. 获取 目标方法运行时传入的参数 . Object[] args = joinPoint.getArgs();//Args:Arguments的缩写,方法参数 log.info("目标方法运行时传入的参数: {}", Arrays.toString(args)); //4. 放行 目标方法执行 . Object result = joinPoint.proceed(); //5. 获取 目标方法运行的返回值 . log.info("目标方法运行的返回值: {}",result); log.info("MyAspect8 around after ..."); return result; }
(六)AOP记录操作日志

将案例中增删改相关接口的操作日志记录到数据库中

包括操作人,操作时间,执行方法的全类名,执行方法名,方法运行时参数,返回值,方法执行时长

具体步骤:

  1. 创建自定义注解类 @Log

    @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Log { }
  2. 创建AOP切面类

    @Component @Slf4j @Aspect public class LogAspect { @Autowired private HttpServletRequest httpServletRequest; //HTTPServletRequest是Tomcat收到请求后创建的, // 然后传给Spring的Servlet @Autowired private OperateLogMapper operateLogMapper; @Pointcut("@annotation(com.itheima.anno.Log)")//注解配置切入点表达式 public void pt(){ } @Around("pt()")//使用环绕通知 public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable { //获取操作人id,即获取jwt令牌 String jwt = httpServletRequest.getHeader("token"); Claims claims = JwtUtils.parseJWT(jwt); Integer operateUser =(Integer) claims.get("ID"); //获取操作时间 LocalDateTime operateTime = LocalDateTime.now(); //操作类名 String className = joinPoint.getTarget().getClass().getName(); //操作方法名 String methodName = joinPoint.getSignature().getName(); //操作方法参数 Object[] args = joinPoint.getArgs(); String methodParams = Arrays.toString(args);//把集合元素变成字符串 //操作耗时 //记录开始时间 long begin = System.currentTimeMillis(); //调用原始方法运行 Object result = joinPoint.proceed(); //方法返回值 String returnValue = JSONObject.toJSONString(result); //记录结束时间,计算方法执行耗时 long end = System.currentTimeMillis(); Long costTime = end - begin; //记录操作日志 OperateLog operateLog = new OperateLog(null,operateUser,operateTime,className,methodName,methodParams,returnValue,costTime); operateLogMapper.insert(operateLog); log.info("AOP记录操作日志:{}",operateLog); return result; } }
  3. 在对应增删改方法上加上@Log注解

十一、SpringBoot底层原理讲解

(一)配置优先级
(二)bean的管理
(三)第三方bean

因为第三方文件是只读文件,所以不能直接在类上加上@Component注解,非自定义注解就需要用@Bean注解

(四)SpringBoot原理

spring基本框架是Spring Framework,但是其配置、依赖繁琐,所以官方推出了springboot框架来简化基于Spring框架的开发。

SpringBoot提供了起步依赖,自动配置功能则简化了框架在使用时bean的声明、bean的配置

1. 起步依赖

而在SpringBoot依赖中只需要引入一个依赖

原理就是maven的依赖传递

2. 自动配置

概念:SpringBoot的自动配置就是当Spring容器启动后,一些配置类、bean对象就自动存入到 IOC 容器中,不需要我们手动去声明,从而简化了开发,省去了繁琐的配置操作

十二、Maven高级

(一)分模块设计与开发

将项目按照功能拆分成若干子模块,方便项目的管理维护、拓展,也方便模块之间的相互调用,资源共享

步骤:

  1. 创建maven模块 tilas-pojo,存放实体类

  2. 创建maven模块 tilas-utils,存放相关工具类

    导入对应依赖至新建模块中,刷新依赖

    再在原来程序的xml文件中把新建的模块导入依赖,刷新依赖

    <!--tlias-pojo包--> <dependency> <groupId>com.itheima</groupId> <artifactId>tlias-pojo</artifactId> <version>1.0-SNAPSHOT</version> </dependency>

注意:分模块开发需要先针对模块功能进行设计,再进行编码。不会先将工程开发完毕再拆分

(二)继承与聚合

继承与聚合

继承
1.继承关系

概念:继承描述的是两个工程之间的关系,与java中的继承类似,子工程可以继承父工程中的配置信息(依赖),常见于关系的继承,把子工程公共的依赖配置在父工程下

实现:

<parent> ...... <!--父工程的坐标--> </parent>
  1. 创建maven模块tlias-parent,设置打包方式pom(默认jar),同时让其继承spring-boot-starter-parent工程(与java相同单继承机制,此处是因为tlias-web-management模块继承了spring-boot-starter-parent)

    <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.5</version> </parent> <groupId>com.itheima</groupId> <artifactId>tlias-parent</artifactId> <version>1.0-SNAPSHOT</version> <packaging>pom</packaging> <!--修改打包方式,用packing-->
  2. 在子工程的pom.xml文件中配置继承关系

    <parent> <groupId>com.itheima</groupId> <artifactId>tlias-parent</artifactId> <version>1.0-SNAPSHOT</version> <relativePath>../tlias-parent/pom.xml</relativePath><!-- ../表示退一级目录 --> </parent>
  3. 在父工程中配置子工程共有依赖

注意事项:

  1. relativePath指定父工程的pom文件的相对位置(如果不指定,将从本地仓库/远程仓库查找该工程)

  2. 如果父子工程都配置了同一个依赖的不同版本,以子工程的为准

  3. 结构说明,实例案例是拆分项目,实际开发可能是第二种

补充说明:

2.版本锁定

前提条件:不是所有模块有相同的依赖,部分依赖相同时,通过版本锁定来控制其版本号相同(注意:管理并不是导入,管理之后还需要在子工程中导入相关依赖,只是不需要添加版本号,需要单独列出)

在maven中,可以在父工程的pom文件中通过来统一管理版本依赖

此时,子工程引入依赖时,无需指定版本号,父工程统一管理。变更依赖版本只需要在父工程中统一修改

父工程:

<dependencyManagement> <dependencies> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> </dependencies> </dependencyManagement>

子工程:

<dependencies> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> </dependency> </dependencies>

自定义属性/引用属性

<properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <!--自定义属性--> <lombok.version>1.18.24</lombok.version> </properties> <dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> <!--${}替代--> </dependency> </dependencies>
聚合

打包:maven生命周期中的packge

(若本地仓库没有相应依赖模块,需要在maven生命周期中选择install安装到本地仓库)

传统打包方式:打包某个工程时,需要先安装其所依赖的工程以及其父工程,再对项目进行打包,非常繁琐

maven中可以通过设置当前聚合工程所包含的子模块名称

聚合工程中所包含的模块,在构建时,会自动根据模块间的依赖关系设置构建顺序,与modules中的顺序无关

<!--聚合--> <modules> <module>../tlias-pojo</module> ../表示退一层级(这里表示parent的xml,需要退一层找parent同级的模块) <module>../tlias-utils</module> <module>../tlias-web-management</module> </modules>
(三)私服

介绍:私服是一种特殊的远程仓库,它是架设在局域网内的仓库服务,用于代理位于外部的中央仓库,用于解决团队内部的资源共享和资源同步问题

依赖的查找顺序:本地仓库 -》私服 -》中央仓库

一般开发中私服不需要我们自己搭建

<groupId>com.itheima</groupId> <artifactId>tlias-parent</artifactId> <version>1.0-SNAPSHOT</version> //这里就是项目的版本 <packaging>pom</packaging>
私服配置说明

访问私服:http://192.168.150.101:8081

访问密码:admin/admin

使用私服,需要在maven的settings.xml配置文件中,做如下配置:

1.需要在 servers 标签中,配置访问私服的个人凭证(访问的用户名和密码)

<server> <id>maven-releases</id> <username>admin</username> <password>admin</password> </server> <server> <id>maven-snapshots</id> <username>admin</username> <password>admin</password> </server>

2.在 mirrors 中只配置我们自己私服的连接地址(如果之前配置过阿里云,需要直接替换掉)

<mirror> <id>maven-public</id> <mirrorOf>*</mirrorOf> <url>http://192.168.150.101:8081/repository/maven-public/</url> </mirror>

3.需要在 profiles 中,增加如下配置,来指定snapshot快照版本的依赖,依然允许使用

<profile> <id>allow-snapshots</id> <activation> <activeByDefault>true</activeByDefault> </activation> <repositories> <repository> <id>maven-public</id> <url>http://192.168.150.101:8081/repository/maven-public/</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>true</enabled> </snapshots> </repository> </repositories> </profile>

4.如果需要上传自己的项目到私服上,需要在项目的pom.xml文件中,增加如下配置,来配置项目发布的地址(也就是私服的地址)

<distributionManagement> <!-- release版本的发布地址 --> <repository> <id>maven-releases</id> <url>http://192.168.150.101:8081/repository/maven-releases/</url> </repository> <!-- snapshot版本的发布地址 --> <snapshotRepository> <id>maven-snapshots</id> <url>http://192.168.150.101:8081/repository/maven-snapshots/</url> </snapshotRepository> </distributionManagement>

发布项目,直接运行 deploy 生命周期即可 (发布时,建议跳过单元测试)

启动本地私服
解压: apache-maven-nexus.zip

进入目录: apache-maven-nexus\nexus-3.39.0-01\bin

启动服务:双击 start.bat

访问服务:localhost:8081

私服配置说明:将上述配置私服信息的 192.168.150.101 改为 localhost

说真的,这两年看着身边一个个搞Java、C++、前端、数据、架构的开始卷大模型,挺唏嘘的。大家最开始都是写接口、搞Spring Boot、连数据库、配Redis,稳稳当当过日子。

结果GPT、DeepSeek火了之后,整条线上的人都开始有点慌了,大家都在想:“我是不是要学大模型,不然这饭碗还能保多久?”

先给出最直接的答案:一定要把现有的技术和大模型结合起来,而不是抛弃你们现有技术!掌握AI能力的Java工程师比纯Java岗要吃香的多。

即使现在裁员、降薪、团队解散的比比皆是……但后续的趋势一定是AI应用落地!大模型方向才是实现职业升级、提升薪资待遇的绝佳机遇!

如何学习AGI大模型?

作为一名热心肠的互联网老兵,我决定把宝贵的AI知识分享给大家。 至于能学习到多少就看你的学习毅力和能力了 。我已将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。

因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取

2025最新版CSDN大礼包:《AGI大模型学习资源包》免费分享**

一、2025最新大模型学习路线

一个明确的学习路线可以帮助新人了解从哪里开始,按照什么顺序学习,以及需要掌握哪些知识点。大模型领域涉及的知识点非常广泛,没有明确的学习路线可能会导致新人感到迷茫,不知道应该专注于哪些内容。

我们把学习路线分成L1到L4四个阶段,一步步带你从入门到进阶,从理论到实战。

L1级别:AI大模型时代的华丽登场

L1阶段:我们会去了解大模型的基础知识,以及大模型在各个行业的应用和分析;学习理解大模型的核心原理,关键技术,以及大模型应用场景;通过理论原理结合多个项目实战,从提示工程基础到提示工程进阶,掌握Prompt提示工程。

L2级别:AI大模型RAG应用开发工程

L2阶段是我们的AI大模型RAG应用开发工程,我们会去学习RAG检索增强生成:包括Naive RAG、Advanced-RAG以及RAG性能评估,还有GraphRAG在内的多个RAG热门项目的分析。

L3级别:大模型Agent应用架构进阶实践

L3阶段:大模型Agent应用架构进阶实现,我们会去学习LangChain、 LIamaIndex框架,也会学习到AutoGPT、 MetaGPT等多Agent系统,打造我们自己的Agent智能体;同时还可以学习到包括Coze、Dify在内的可视化工具的使用。

L4级别:大模型微调与私有化部署

L4阶段:大模型的微调和私有化部署,我们会更加深入的探讨Transformer架构,学习大模型的微调技术,利用DeepSpeed、Lamam Factory等工具快速进行模型微调;并通过Ollama、vLLM等推理部署框架,实现模型的快速部署。

整个大模型学习路线L1主要是对大模型的理论基础、生态以及提示词他的一个学习掌握;而L3 L4更多的是通过项目实战来掌握大模型的应用开发,针对以上大模型的学习路线我们也整理了对应的学习视频教程,和配套的学习资料。

二、大模型经典PDF书籍

书籍和学习文档资料是学习大模型过程中必不可少的,我们精选了一系列深入探讨大模型技术的书籍和学习文档,它们由领域内的顶尖专家撰写,内容全面、深入、详尽,为你学习大模型提供坚实的理论基础(书籍含电子版PDF)

三、大模型视频教程

对于很多自学或者没有基础的同学来说,书籍这些纯文字类的学习教材会觉得比较晦涩难以理解,因此,我们提供了丰富的大模型视频教程,以动态、形象的方式展示技术概念,帮助你更快、更轻松地掌握核心知识

四、大模型项目实战

学以致用,当你的理论知识积累到一定程度,就需要通过项目实战,在实际操作中检验和巩固你所学到的知识,同时为你找工作和职业发展打下坚实的基础。

五、大模型面试题

面试不仅是技术的较量,更需要充分的准备。

在你已经掌握了大模型技术之后,就需要开始准备面试,我们将提供精心整理的大模型面试题库,涵盖当前面试中可能遇到的各种技术问题,让你在面试中游刃有余。


因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取

2025最新版CSDN大礼包:《AGI大模型学习资源包》免费分享

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!