Spring常用知识汇总

常用快捷键

CTRL+D 当前行内容快速向下复制
CTRL+F3 查找当前文件的所有引用
Alt+ins 快速生成setter、getter,构造方法
CTRL+ALT+L 代码格式化
CTRL+SHIFT+o 刷新Maven依赖
源码页面Ctrl + H可以看接口的层次结构
ctrl+alt+T 包围代码try-catch等

命名空间开启

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:context="http://www.springframework.org/schema/context"



xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd




http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd"
>
<context:property-placeholder location="jdbc.properties"/>

jdbc.properties文件

注意:如果出现以下报错:

Error querying database. Cause: java.sql.SQLException: Error setting driver on UnpooledDataSource. Cause: java.lang.ClassNotFoundException: Cannot find class: com.mysql.jdbc.Driver

MySQL JDBC驱动从8.0版本开始,驱动类名从 com.mysql.jdbc.Driver 更改为 com.mysql.cj.jdbc.Driver。如果您使用的是MySQL 8.0或更高版本的JDBC驱动,则需要在您的配置文件中将驱动类名更改为新的名称。

1
2
3
4
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_db1
jdbc.username=root
jdbc.password=123456

springboot版本问题

注意:由于版本问题,系统自带的版本可能低于当前支持的版本,会导致报错产生,如果你的版本报错,可以参照我下面的版本进行修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo3</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo3</name>
<description>demo3</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.5</version>
</dependency>

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>

</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

构建Config的时候出现无法解析jdbc.properties

这个问题原则上不影响使用,但是看起来让人不舒服,解决办法,在前面加上
classpath

1
2
3
4
5
@Configuration
@ComponentScan("Test.Config")
@PropertySource("classpath:jdbc.properties")
public class Config {
}

快速整合druid数据源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class jdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public DruidDataSource druidDataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}

这里要想不写improt导入,需要在头上写@Configuration,否则就需要在SpringConfig中加上@Import({jdbcConfig.class})

快速整合mybatis

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MybatisConfig {
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
// 定义bean,SqlSessionFactoryBean 用于产生SqlSessionFactory对象
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
//设置模型类的别名扫描
sqlSessionFactoryBean.setTypeAliasesPackage("Test.domain");
//设置数据源
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean;
}
//定义bean,返回MapperScannerConfigurer对象
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
mapperScannerConfigurer.setBasePackage("Test.Dao");
return mapperScannerConfigurer;
}
}

AOP需要的坐标

1
2
3
4
5
6
7
8
9
10
11
12

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>

<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>

因为spring-context中已经导入了spring-aop,所以不需要再单独导入spring-aop
导入AspectJ的jar包,AspectJ是AOP思想的一个具体实现,Spring有自己的AOP实现,但是相比于AspectJ来说比较麻烦,所以我们直接采用Spring整合ApsectJ的方式进行AOP开发。

编写测试类时出现@RunWith注解找不到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>

<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>

<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>

</dependencies>

上述代码中,第一个junit是初始化的时候自带的,经过测试想要解决上述问题,只需要删除第一个junit依赖,同时加上

1
2
3
4
5
6
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>compile</scope>
</dependency>

在加载外部配置文件jdbc.properties时出现下面的错误

明明配置文件 配置得没有错误,到运行的时候出现以下错误:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
严重: Caught exception while allowing TestExecutionListener [org.springframework.test.context.support.DependencyInjectionTestExecutionListener@7cbd213e] to prepare test instance [Test.AccountServiceTest@130d63be]
java.lang.IllegalStateException: Failed to load ApplicationContext
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:132)
at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:123)
at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:118)
at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:83)
at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:244)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:227)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:289)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:291)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:246)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:232)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55)
Caused by: org.springframework.beans.factory.BeanDefinitionStoreException: Failed to parse configuration class [Test.Config.SpringConfig]; nested exception is java.io.FileNotFoundException: class path resource [ jdbc.properties] cannot be opened because it does not exist
at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:189)
at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:319)
at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:236)
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:280)
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:96)
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:707)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:533)
at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:127)
at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:60)
at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.delegateLoading(AbstractDelegatingSmartContextLoader.java:275)
at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.loadContext(AbstractDelegatingSmartContextLoader.java:243)
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99)
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124)
... 26 more
Caused by: java.io.FileNotFoundException: class path resource [ jdbc.properties] cannot be opened because it does not exist <--------- 重点找到这个
at org.springframework.core.io.ClassPathResource.getInputStream(ClassPathResource.java:180)
at org.springframework.core.io.support.EncodedResource.getInputStream(EncodedResource.java:159)
at org.springframework.core.io.support.PropertiesLoaderUtils.fillProperties(PropertiesLoaderUtils.java:99)
at org.springframework.core.io.support.PropertiesLoaderUtils.fillProperties(PropertiesLoaderUtils.java:73)
at org.springframework.core.io.support.PropertiesLoaderUtils.loadProperties(PropertiesLoaderUtils.java:59)
at org.springframework.core.io.support.ResourcePropertySource.<init>(ResourcePropertySource.java:67)
at org.springframework.core.io.support.DefaultPropertySourceFactory.createPropertySource(DefaultPropertySourceFactory.java:37)
at org.springframework.context.annotation.ConfigurationClassParser.processPropertySource(ConfigurationClassParser.java:463)
at org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:280)
at org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:250)
at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:207)
at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:175)
... 38 more

简单解释以下就是 cannot be opened because it does not exist 不能打开jdbc.properties 因为它不存在 问题就在下面,因为没有空格,所以找不到,所以加上空格,就找到文件了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
@ComponentScan("Test")
@PropertySource("classpath: jdbc.properties")
@Import({jdbcConfig.class,MybatisConfig.class})
public class SpringConfig {
}

// @PropertySource("classpath:jdbc.properties") classpath:后面没有空格,千万记住
@Configuration
@ComponentScan("Test")
@PropertySource("classpath:jdbc.properties")
@Import({jdbcConfig.class,MybatisConfig.class})
public class SpringConfig {
}

POST请求中文乱码

解决方案:配置过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
protected Class<?>[] getRootConfigClasses() {
return new Class[0];
}

protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}

protected String[] getServletMappings() {
return new String[]{"/"};
}

//处理乱码问题
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("utf-8");
return new Filter[]{filter};
}
}

SpringMVC学习所需依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>untitled12</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>untitled12 Maven Webapp</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!--servlet-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!--springmvc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<!--json-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
</dependencies>
<build>
<finalName>untitled12</finalName>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.1</version>
<configuration>
<port>8081</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
</project>

springMVc访问页面404问题

我们通过注解形式配置的springMVC配置,初学者会出现静态资源加载出现404

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.config;

import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

import javax.servlet.Filter;

public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[0];
}

@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{springConfig.class};
}

@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
//解决前端请求数据传到后端出现乱码问题解决方式
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("utf-8");
return new Filter[]{filter};
}
}

上述代码中问题出现在getServletMappings这段代码,我们设置的是拦截所有资源交由springmvc来处理,其实在容器中又没有静态资源
所以控制台下面会报错[WARNING] No mapping for GET /Book.html 我们需要配置将静态页面给放行,不由它来处理就行
config包中SpringMvcSupport

1
2
3
4
5
6
7
8
9
10
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
registry.addResourceHandler("/js/**").addResourceLocations("/js/");
registry.addResourceHandler("/css/**").addResourceLocations("/css/");
registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
}
}

记住加载进springmvcconfig来管理
然后重启就行了

端口被占用导致前端资源找不到或者后台报错

通过下面的方法查找正在使用的端口,一般找没有前缀的TCP


使用命令行工具 netstat:
打开命令提示符(CMD),然后输入以下命令:
netstat -ano
这会列出当前系统中所有的网络连接和监听端口,以及相应的进程 ID(PID)。你可以在输出中查找你感兴趣的端口号,并查看对应的 PID。然后你可以使用任务管理器或其他工具,通过 PID 来找到占用该端口的进程。

使用资源监视器:
在 Windows 中,你也可以使用资源监视器来查看网络活动情况,包括端口占用情况。你可以按下 Win + R 打开运行窗口,输入 resmon,然后按 Enter 键来打开资源监视器。在资源监视器中,切换到网络选项卡,你将看到正在使用的端口及其相关信息。

IDEA中建包的时候如何才能把包分开

原先我们创建包时输入com.atguigu.web时,idea只会有一个包。那么如何变成层级结构的两个包呢?

🧑‍💻点击链接

tomcat插件以及端口等书写方式

1
2
3
4
5
6
7
8
9
10
11
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.1</version>
<configuration>
<port>8081</port>
<path>/</path>
</configuration>
</plugin>
</plugins>

在SpringBoot中访问路径显示404

第一种情况,Controller包应该和application.java同级,否则访问路径会显示404。

SpringBoot快速整合Mybatis的yml配置

添加依赖,版本可以自己改

1
2
3
4
5
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>

yml配置:
注意:
SpringBoot 版本低于2.4.3(不含),Mysql驱动版本大于8.0时,需要在url连接串中配置时区 jdbc:mysql://localhost:3306/springboot_db?serverTimezone=UTC,或在MySQL数据库端配置时区解决此问题

1
2
3
4
5
6
7
8
9
10
11
server:
port: 8081


spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/springboot_db?serverTimezone=UTC
username: root
password: 123456
type: com.alibaba.druid.pool.DruidDataSource

Lombok常见的注解和依赖

1
2
3
4
5
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<!--<version>1.18.12</version>-->
</dependency>

@Setter:为模型类的属性提供setter方法
@Getter:为模型类的属性提供getter方法
@ToString:为模型类的属性提供toString方法
@EqualsAndHashCode:为模型类的属性提供equals和hashcode方法
@Data:是个组合注解,包含上面的注解的功能
@NoArgsConstructor:提供一个无参构造函数
@AllArgsConstructor:提供一个包含所有参数的构造函数

1
2
3
4
5
6
7
8
9
10
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
}

分页拦截器的固定写法

在分页配置完成后,只有加上这段代码,才能实现分页功能。

1
2
3
4
5
6
7
8
9
10
11
@Configuration
public class ConfigPlus {

@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}

}

项目部署yml文件配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
server:
port: 8081
spring:
application:
name: reggie_take_out
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/reggie?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: 123456
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: ASSIGN_ID

SpringBoot公共字段的自动填充

1
2
3
4
5
6
7
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;


@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;

updatetime这边需要将INSERT_UPDATE,如果只添加update,则在新增操作的时候系统会报updatetime字段未初始化

文件上传与下载(阿里云oss与本地版)

文件上传介绍

  1. method=”post”,采用post方式提交数据
  2. enctype=”multipart/form-data”,采用multipart格式上传文件
  3. type=”file”,使用input的file控件上传

下面是前端的示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文件上传</title>
<!-- 引入样式 -->
<link rel="stylesheet" href="../../plugins/element-ui/index.css" />
<link rel="stylesheet" href="../../styles/common.css" />
<link rel="stylesheet" href="../../styles/page.css" />
</head>
<body>
<div class="addBrand-container" id="food-add-app">
<div class="container">
<el-upload class="avatar-uploader"
action="/common/upload"
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeUpload"
ref="upload">
<img v-if="imageUrl" :src="imageUrl" class="avatar"></img>
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</div>
</div>
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="../../plugins/vue/vue.js"></script>
<!-- 引入组件库 -->
<script src="../../plugins/element-ui/index.js"></script>
<!-- 引入axios -->
<script src="../../plugins/axios/axios.min.js"></script>
<script src="../../js/index.js"></script>
<script>
new Vue({
el: '#food-add-app',
data() {
return {
imageUrl: ''
}
},
methods: {
handleAvatarSuccess (response, file, fileList) {
this.imageUrl = `/common/download?name=${response.data}`
},
beforeUpload (file) {
if(file){
const suffix = file.name.split('.')[1]
const size = file.size / 1024 / 1024 < 2
if(['png','jpeg','jpg'].indexOf(suffix) < 0){
this.$message.error('上传图片只支持 png、jpeg、jpg 格式!')
this.$refs.upload.clearFiles()
return false
}
if(!size){
this.$message.error('上传文件大小不能超过 2MB!')
return false
}
return file
}
}
}
})
</script>
</body>
</html>

通过这个示例代码我们来写后端的代码

文件上传本地版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@RestController
@RequestMapping("/common")
@Slf4j
public class CommonController {

@Value("${reggie.path}")
private String basepath;

@PostMapping("/upload")
//file是个临时文件,我们在断点调试的时候可以看到,但是执行完整个方法之后就消失了
public Result<String> upload(MultipartFile file) {
log.info("获取文件:{}", file.toString());
//判断一下当前目录是否存在,不存在则创建
File dir = new File(basepath);
if (!dir.exists()) {
dir.mkdirs();
}

//获取一下传入的原文件名
String originalFilename = file.getOriginalFilename();
//我们只需要获取一下格式后缀,取子串,起始点为最后一个.
String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
//为了防止出现重复的文件名,我们需要使用UUID
String fileName = UUID.randomUUID() + suffix;
try {
//我们将其转存到我们的指定目录下
file.transferTo(new File(basepath + fileName));
} catch (IOException e) {
throw new RuntimeException(e);
}
//将文件名返回给前端,便于后期的开发
return Result.success(fileName);
}
}

文件转存的位置改为动态可配置

1
2
reggie:
path: E:\\reggie\\img\\

文件下载本地版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@GetMapping("/download")
public void download(String name, HttpServletResponse response) {
FileInputStream fis = null;
ServletOutputStream os = null;
try {
fis = new FileInputStream(basePath + name);
os = response.getOutputStream();
response.setContentType("image/jpeg");
int len;
byte[] buffer = new byte[1024];
while ((len = fis.read(buffer)) != -1)
os.write(buffer, 0, len);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (fis != null) {

try {
fis.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (os != null) {
try {
os.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}

文件上传OOS版:

由于要上传服务器所以这边的
ACCESS_KEY_ID
ACCESS_KEY_SECRET
直接在代码里面配置也可以在yml文件里面配置
对于官方的代码需要修改一下,官方的是在本地的环境中自动检测环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class AliOSSUtil {
// Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
private static final String ENDPOINT = "";
// 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
//EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
private static final String ACCESS_KEY_ID="";
private static final String ACCESS_KEY_SECRET="";
// 填写Bucket名称,例如examplebucket。
private static final String BUCKETNAME = "";
public static String uploadFile(String objectName, InputStream inputStream) throws Exception {
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(ENDPOINT,ACCESS_KEY_ID,ACCESS_KEY_SECRET );
String url = "";
try {
// 填写字符串。
String content = "Hello OSS,你好世界";

// 创建PutObjectRequest对象。
PutObjectRequest putObjectRequest = new PutObjectRequest(BUCKETNAME, objectName, inputStream);

// 如果需要上传时设置存储类型和访问权限,请参考以下示例代码。
// 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);
//url组成:https://bucket名称.区域节点/objectName
url="https://"+BUCKETNAME+"."+ENDPOINT.substring(ENDPOINT.lastIndexOf("/")+1)+"/"+objectName;
} 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();
}
}
return url;
}
}

调用就行了

1
2
3
4
5
6
7
8
9
@PostMapping("/upload")
public Result<String> upload(MultipartFile file) throws Exception {
log.info(file.toString());
String originalFilename = file.getOriginalFilename();
// 保证文件名字的唯一性,从而防止文件覆盖
String filename = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf("."));
String url = AliOSSUtil.uploadFile(filename, file.getInputStream());
return Result.success(url);
}

文件下载OOS版

这里我们使用阿里云官方给我们提供的流式下载,这里我们依然要修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package com.reggie_take_out.OSS;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.OSSObject;
import jakarta.servlet.ServletOutputStream;

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class AliOSSDownLoad {
// Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
private static final String ENDPOINT = "";
// 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
//EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
private static final String ACCESS_KEY_ID = "";
private static final String ACCESS_KEY_SECRET = "";
// 填写Bucket名称,例如examplebucket。
private static final String BUCKETNAME = "";

public static void downloadFile(String objectName, ServletOutputStream inputStream){

// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(ENDPOINT, ACCESS_KEY_ID, ACCESS_KEY_SECRET);
try {
// ossObject包含文件所在的存储空间名称、文件名称、文件元数据以及一个输入流。
OSSObject ossObject = ossClient.getObject(BUCKETNAME, objectName);

// 读取文件内容。
System.out.println("Object content:");
BufferedReader reader = new BufferedReader(new InputStreamReader(ossObject.getObjectContent()));
while (true) {
String line = reader.readLine();
if (line == null) break;

System.out.println("\n" + line);
}
// 数据读取完成后,获取的流必须关闭,否则会造成连接泄漏,导致请求无连接可用,程序无法正常工作。
reader.close();
// ossObject对象使用完毕后必须关闭,否则会造成连接泄漏,导致请求无连接可用,程序无法正常工作。
ossObject.close();

} 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 (Throwable 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();
}
}

}
}

调用就行了

1
2
3
4
@GetMapping("/download")
public void download(String name, HttpServletResponse response) throws IOException {
AliOSSDownLoad.downloadFile(name,response.getOutputStream());
}

然后前端需要改一点点就是将

1
2
3
handleAvatarSuccess (response, file, fileList) {
this.imageUrl = `/common/download?name=${response.data}`
},

/common/download?name= 删除掉,让完整的路径进来就行了

spring 整合mybatis所需要的依赖

这个是最新的依赖不会出现依赖易受攻击的警告

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.1.5</version>
</dependency>

邮箱验证功能问题

https://zhuanlan.zhihu.com/p/340704323
近两年国内各大云服务器厂商如阿里云、腾讯云、各种云纷纷加入了对25端口的封杀行列,最主要也是最重要的原因就是为了防堵垃圾邮件的发送。

电子邮件SMTP的协议标准本身就是非常脆弱的,协议的制定者在当时相对单纯的网络环境下制定了单纯的SMTP协议,其中最大的缺陷就是SMTP协议是开放性的,发信域和收信域之间缺乏认证机制

认证机制的缺乏导致了SMTP协议是包容的,当然也就包括了垃圾邮件。

就SMTP协议层面来说,垃圾邮件发送者只需要租用一台服务器,就可以使用任意域名任意发送垃圾邮件,这也就导致了电子邮件的世界垃圾邮件遍地。

为了解决这个问题不至于成为垃圾邮件发送者的帮凶,国内各大服务器厂商都开始封锁25端口,限制邮件的外发能力,并建议使用者使用465端口来代替25端口发送邮件

那么465端口真的能发送邮件吗,答案是能也不能,看使用者如何去理解。

这里有一个误区,许多不了解SMTP和电子邮件实现原理的人会误以为直接用465代替25端口就能实现邮件的最终发送,这是错误的。

为了说明问题,需要普及一点,一封email邮件的传递,从发件人发出到最终进入收件人的邮箱,是多方接力完成的,以下是邮件以此流转的简化过程:

1、发件人Foxmail/Outlook客户端 ====> 发信服务器(25端口或其他任意端口)
2、发件人所在发信服务器 ====> 收信服务器(25端口)
可以看到,一封邮件首先由Foxmail或Outlook客户端生成,然后进入第二步发件人邮箱服务器,使用25端口或其他任意端口都可以,邮件到这里并没有真正投递成功,你可以想象成寄快递,到目前为止你只是将包裹交给了快递公司,由快递公司负责将包裹投递给收件人。

发件人所在发信服务器收到邮件后,会查询收件人域名的MX记录解析到对应的收件IP,并将邮件投递到该IP地址的25端口,这一步的25端口是固定的雷打不动,SMTP协议就是这么规定的,除非你和收件方有特别的约定使用私有端口,否则你一定要连接大家约定俗成的25端口才可能将邮件真正投递出去。

以往25端口没有被封锁的年代,随便租用一台vps主机即可往外疯狂发送垃圾邮件,导致服务器主机商被人投诉不堪其扰,后来索性就一概将25端口封锁,宁可错杀不可错放

阿里云、腾讯云封锁25端口就是为了限制用户在邮件发送这一过程中扮演邮件最终投递者的角色。但如果你确实有正常邮件需要发送怎么办?厂商告诉你:OK,你不能直接使用25端口发送,请使用465或其他端口,将邮件交由第三方系统发送。即你可以寄快递,但请交给快递公司,让他们来寄送。如果你想做快递公司自己寄送,那么不欢迎你。

源码留存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@PostMapping("/sendMsg")
public Result<String> sendMsg(@RequestBody User user, HttpSession session) throws MessagingException {
String phone = user.getPhone();
if (!phone.isEmpty()) {
//随机生成一个验证码
String code = MailUtils.achieveCode();
log.info(code);
//这里的phone其实就是邮箱,code是我们生成的验证码
MailUtils.sendTestMail(phone, code);
//验证码存session,方便后面拿出来比对
session.setAttribute(phone, code);
return Result.success("验证码发送成功");
}
return Result.error("验证码发送失败");
}
@PostMapping("/login")
public Result<User> login(@RequestBody Map map, HttpSession session) {
log.info(map.toString());
//获取邮箱
String phone = map.get("phone").toString();
//获取验证码
String code = map.get("code").toString();
//从session中获取验证码
String codeInSession = session.getAttribute(phone).toString();
//比较这用户输入的验证码和session中存的验证码是否一致
if (code != null && code.equals(codeInSession)) {
//如果输入正确,判断一下当前用户是否存在
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
//判断依据是从数据库中查询是否有其邮箱
queryWrapper.eq(User::getPhone, phone);
User user = userService.getOne(queryWrapper);
//如果不存在,则创建一个,存入数据库
if (user == null) {
user = new User();
user.setPhone(phone);
userService.save(user);
user.setName("用户" + codeInSession);
}
//存个session,表示登录状态
session.setAttribute("user",user.getId());
//并将其作为结果返回
return Result.success(user);
}
return Result.error("登录失败");
}

Java 8的Stream API对集合进行处理

这段代码我在项目中经常使用,我觉得很有必要记录下来,后面的时候需要用到直接拿过来就行

1
2
3
4
5
6
7
8
List<ShoppingCart> shoppingCartList = orderDetailList.stream().map(x -> {
ShoppingCart shoppingCart = new ShoppingCart();
//将原订单详情里面的菜品信息重新复制到购物车对象中
BeanUtils.copyProperties(x, shoppingCart, "id");
shoppingCart.setUserId(userId);
shoppingCart.setCreateTime(LocalDateTime.now());
return shoppingCart;
}).collect(Collectors.toList());

一段一段的解释:
创建Stream:

1
orderDetailList.stream()

使用stream()方法从orderDetailList(假设是一个List<OrderDetail>或类似的结构)中创建一个Stream
转换每个元素:

1
.map(x -> { ... })

使用map函数对Stream中的每个元素(在这里用x表示)执行一个转换操作。转换操作是创建一个新的ShoppingCart对象,并将OrderDetail对象(x)中的属性复制到新的ShoppingCart对象中
收集结果:

1
.collect(Collectors.toList());

使用collect方法和Collectors.toList()收集器将Stream中的元素(在这里是转换后的ShoppingCart对象)收集到一个新的列表中。这个列表就是最终的结果,并赋值给shoppingCartList。

总结:常见的使用场景
Java 8 Stream API 的使用场景非常广泛,特别适用于对集合(如 List、Set、Map 等)进行复杂的数据处理。以下是一些常见的使用场景:

  • 数据转换(Mapping)和过滤(Filtering):当你需要将一个集合中的元素转换成另一种形式,并可能同时过滤掉一些元素时,Stream API 非常有用。例如,从一个用户列表中提取出所有活跃用户的姓名和电子邮件地址。
  • 数据聚合(Aggregation):需要对集合中的元素进行聚合操作时,如计算总和、平均值、最大值、最小值等,可以使用 Stream API 提供的 reduce、sum、average 等方法。
  • 排序(Sorting):如果你想对集合中的元素进行排序,可以使用 sorted 方法,它接受一个 Comparator。例如,根据价格对商品列表进行排序。
  • 分组(Grouping):使用 collect 方法配合 Collectors.groupingBy 可以轻松地将集合中的元素按照某个属性进行分组。例如,按照订单状态对订单列表进行分组。
  • 查找和匹配(Finding and Matching):Stream API 提供了 anyMatch、allMatch、noneMatch 方法用于检查集合中的元素是否满足某个条件,以及 findFirst、findAny 方法用于查找满足条件的第一个或任意一个元素。
  • 并行处理(Parallel Processing):通过调用 parallelStream() 而不是 stream(),你可以利用多核处理器并行处理集合中的元素。这在处理大数据集时可以提高性能。
  • 复杂数据处理:当数据处理逻辑非常复杂,涉及到多个步骤时,Stream API 的链式调用可以使代码更加清晰和易于维护。你可以将多个中间操作和终端操作组合在一起,形成一个流畅的处理管道。
  • 与数据库查询结合:在某些情况下,你可以使用 Stream API 来模拟数据库查询的某些功能,尤其是在内存中进行复杂的数据处理时。然而,请注意,对于大型数据集,直接使用数据库查询通常更加高效。
  • 函数式编程风格:Stream API 鼓励使用函数式编程风格,这有助于提高代码的可读性和可维护性。通过将数据和处理逻辑分离,你可以更容易地理解和测试代码。
  • 与其他Java 8特性结合:Stream API 可以与 Java 8 中的其他新特性(如 Lambda 表达式、方法引用、默认方法和接口中的静态方法等)结合使用,以进一步简化代码并提高性能。

SpringTask 使用方式

  • 导入maven坐标spring-context(已存在)
  • 启动类添加注解@EnableScheduling开启任务调度
  • 自定义定时任务类

举个例子:
每隔五秒触发一次

1
2
3
4
5
6
7
8
9
10
11
@Component
@Slf4j
public class MyTask {
/**
* 定时任务,每隔五秒触发一次
*/
@Scheduled(cron = "0/5 * * * * ?")
public void executeTask(){
log.info("定时任务开始执行:{}",new Date());
}
}

通过POI创建excel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class POITest {
/**
* 通过POI创建excel文件并且写入文件内容
*/
public static void write() throws Exception {
//内存中创建excel文件
XSSFWorkbook excel = new XSSFWorkbook();
//在excel文件中创建一个sheet页
XSSFSheet sheet = excel.createSheet("info");
//在sheet页中创建行对象,rownum编号从0开始
XSSFRow row = sheet.createRow(1);
//在行上面创建单元格,并且写入文件内容
row.createCell(1).setCellValue("姓名");
row.createCell(2).setCellValue("城市");

//再创建新行
row = sheet.createRow(2);
row.createCell(1).setCellValue("张三");
row.createCell(2).setCellValue("北京");
//再创建新行
row = sheet.createRow(3);
row.createCell(1).setCellValue("李四");
row.createCell(2).setCellValue("安徽");

//通过输出流将内存中的excel文件写入到磁盘
FileOutputStream out = new FileOutputStream(new File("D:\\info.xlsx"));
excel.write(out);
//关闭资源
out.close();
excel.close();
}


public static void main(String[] args) throws Exception {
write();
}


}

读取文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* 通过POI读取Excel文件中的内容
*
* @throws Exception
*/
public static void read() throws Exception {

FileInputStream fileInputStream = new FileInputStream(new File("D:\\info.xlsx"));//读取文件所在地址

XSSFWorkbook excel = new XSSFWorkbook(fileInputStream);
//读取Excel文件中的第一个Sheet页
XSSFSheet sheet = excel.getSheetAt(0);
//获取sheet页中最后一个行号
int lastRowNum = sheet.getLastRowNum();

for (int i = 1; i < lastRowNum; i++) {
//获得某一行
XSSFRow row = sheet.getRow(i);
//获得单元格对象
String cellValue = row.getCell(1).getStringCellValue();
String cellValue1 = row.getCell(2).getStringCellValue();
System.out.println(cellValue + " " + cellValue1);
}
excel.close();
fileInputStream.close();
}