java项目技术解析

java面试经验

lombok

Lombok是一个实用的Java类库,可以通过简单的注解来简化和消除一些必须有但显得很臃肿的Java代码。

@Data

提供了更综合的生成代码功能(@Getter + @Setter +

@ToString + @EqualsAndHashCode)

@Data //getter方法、setter方法、toString方法、hashCode方法、equals方法

@NoArgsConstructor //无参构造

@AllArgsConstructor//全参构造

个人博客系统

基于Spring Boot、MyBatis和MySQL构建个人博客系统是一个较为流行的技术组合,适用于快速开发和部署一个轻量级且高效的个人博客平台。下面是这个系统的技术设计方案的详细描述:

1. 系统架构设计

前端架构:
  • 使用Vue.js或React.js等现代前端框架,实现单页面应用(SPA)。

  • 通过Axios或Fetch API与后端接口进行数据交互。

  • --异步加载数据--

    1. 引入Axios文件
    1. 使用Axios发送请求,并获取响应结果,官方提供的api很多

  • 利用Element UI组件库来加速界面开发

后端架构
  • Spring Boot: 作为应用程序框架,用于创建独立的、生产级别的 Spring 应用程序。
  • MyBatis: 作为持久层框架,与MySQL数据库交互,用于数据持久化。

在mapper包下创建一个接口 UserMapper ,这是一个持久层接口(Mybatis的持久层接口规范一般都叫XxxMapper)。

@Mapper
public interface UserMapper {
//查询所有用户数据
@Select("select id, name, age, gender, phone from user")
public List<User> list();
}

@Mapper注解:表示是mybatis中的Mapper接口

程序运行时:框架会自动生成接口的实现类对象(代理对象),并给交Spring的IOC容器管理

@Select注解:代表的就是select查询,用于书写select查询语句

  • MySQL: 作为关系数据库管理系统,存储博客内容、用户信息等数据。
  • Docker: 容器化后端应用,便于部署和扩展。

用Docker部署Java项目-CSDN博客

把准备好的Dockerfile和jar包发送到服务器中的同一个文件夹中。准备创建Docker镜像

创建Docker镜像,一定要在当前目录中,输入命令:

              docker build -t xxx .

输入命令创建 Docker容器:

                docker run -p 8080:8080 text

  • Nginx: 作为反向代理服务器,处理静态资源和服务器间的请求转发。--///

nginx学习,看这一篇就够了:下载、安装。使用:正向代理、反向代理、负载均衡。常用命令和配置文件,很全-CSDN博客

jdbc是

JDBC: ( Java DataBase Connectivity ),就是使用Java语言操作关系型数据库的一套API。

本质:

sun公司官方定义的一套操作所有关系型数据库的规范,即接口。

各个数据库厂商去实现这套接口,提供数据库驱动jar包。

我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类。

jdbc和mybaits对比

技术对比

  1. 数据库连接四要素(驱动、链接、用户名、密码),都配置在springboot默认的配置文application.properties中

  2. 查询结果的解析及封装,由mybatis自动完成映射封装,我们无需关注

  3. 在mybatis中使用了数据库连接池技术,从而避免了频繁的创建连接、销毁连接而带来的资源浪费

数据库连接池

数据库连接池是个容器,负责分配、管理数据库连接(Connection)

程序在启动时,会在数据库连接池(容器)中,创建一定数量的Connection对象

允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个

释放空闲时间超过最大空闲时间的连接,来避免因为没有释放连接而引起的数据库连接遗漏

数据库连接池的好处:

  1. 资源重用

  2. 提升系统响应速度

  3. 避免数据库连接遗漏

实现

官方(sun)提供了数据库连接池标准(javax.sql.DataSource接口)

功能:获取连接

public Connection getConnection() throws SQLException;

第三方组织必须按照DataSource接口实现

现在使用更多的是:Hikari、Druid (性能更优越)

Hikari(追光者) [默认的连接池]

Druid(德鲁伊)

Druid连接池是阿里巴巴开源的数据库连接池项目

功能强大,性能优秀,是Java语言最好的数据库连接池之一

登录校验

HTTP协议是无状态协议

所谓无状态,指的是每一次请求都是独立的,下一次请求并不会携带上一次请求的数据。而浏览器与服

务器之间进行交互,基于HTTP协议也就意味着现在我们通过浏览器来访问了登陆这个接口,实现了登陆

的操作,接下来我们在执行其他业务操作时,服务器也并不知道这个员工到底登陆了没有。因为HTTP协

议是无状态的,两次请求之间是独立的,所以是无法判断这个员工到底登陆了没有。

会话跟踪技术有两种:

  1. Cookie(客户端会话跟踪技术)

数据存储在客户端浏览器当中

  1. Session(服务端会话跟踪技术)

数据存储在储在服务端

  1. 令牌技术

我刚才在介绍流程的时候,用了 3 个自动:

  • 服务器会 自动 的将 cookie 响应给浏览器。

  • 浏览器接收到响应回来的数据之后,会 自动 的将 cookie 存储在浏览器本地。

  • 在后续的请求当中,浏览器会 自动 的将 cookie 携带到服务器端。

优缺点

优点:HTTP协议中支持的技术(像Set-Cookie 响应头的解析以及 Cookie 请求头数据的携带,都是浏览器自动进行的,是无需我们手动操作的)

缺点:

移动端APP(Android、IOS)中无法使用Cookie

不安全,用户可以自己禁用Cookie

Cookie不能跨域

区分跨域的维度:

协议

IP/协议

端口

只要上述的三个维度有任何一个维度不同,那就是跨域操作

Session

优缺点

优点:Session是存储在服务端的,安全

缺点:

服务器集群环境下无法直接使用Session

移动端APP(Android、IOS)中无法使用Cookie

用户可以自己禁用Cookie

Cookie不能跨域

PS:Session 底层是基于Cookie实现的会话跟踪,如果Cookie不可用,则该方案,也就失效了。

令牌技术

如果通过令牌技术来跟踪会话,我们就可以在浏览器发起请求。在请求登录接口的时候,如果登录成功,我就可以生成一个令牌,令牌就是用户的合法身份凭证。接下来我在响应数据的时候,我就可以直接将令牌响应给前端。

接下来我们在前端程序当中接收到令牌之后,就需要将这个令牌存储起来。这个存储可以存储在cookie 当中,也可以存储在其他的存储空间(比如:localStorage)当中。

接下来,在后续的每一次请求当中,都需要将令牌携带到服务端。携带到服务端之后,接下来我们就需要来校验令牌的有效性。如果令牌是有效的,就说明用户已经执行了登录操作,如果令牌是无效的,就说明用户之前并未执行登录操作

优缺点

优点:

支持PC端、移动端

解决集群环境下的认证问题

减轻服务器的存储压力(无需在服务器端存储)

缺点:需要自己实现(包括令牌的生成、令牌的传递、令牌的校验)

JWT全称:JSON Web Token

定义了一种简洁的、自包含的格式,用于在通信双方以json数据格式安全的传输信息。由于数字签名的存在,这些信息是可靠的。

简洁:是指jwt就是一个简单的字符串。可以在请求参数或者是请求头当中直接传递。

自包含:指的是jwt令牌,看似是一个随机的字符串,但是我们是可以根据自身的需求在jwt

令牌中存储自定义的数据内容。如:可以直接在jwt令牌中存储用户的相关信息。

简单来讲,jwt就是将原始的json数据格式进行了安全的封装,这样就可以直接基于jwt在通信双方安全的进行信息传输了。

JWT的组成:

(JWT令牌由三个部分组成,三个部分之间使用英文的点来分割)

第一部分:Header(头), 记录令牌类型、签名算法等。 例如:

{"alg":"HS256","type":"JWT"}

第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。 例如:

{"id":"1","username":"Tom"}

第三部分:Signature(签名),防止Token被篡改、确保安全性。将header、payload,并加

入指定秘钥,通过指定签名算法计算而来。

签名的目的就是为了防jwt令牌被篡改,而正是因为jwt令牌最后一个部分数字签名的存在,所以整个jwt 令牌是非常安全可靠的。一旦jwt令牌当中任何一个部分、任何一个字符被篡改了,整个令牌在校验的时候都会失败,所以它是非常安全可靠的。

转为字符串

JWT是如何将原始的JSON格式数据,转变为字符串的呢?

其实在生成JWT令牌时,会对JSON格式的数据进行一次编码:进行base64编码

Base64:是一种基于64个可打印的字符来表示二进制数据的编码方式。既然能编码,那也就意味着也能解码。所使用的64个字符分别是A到Z、a到z、 0- 9,一个加号,一个斜杠,加起来就是64个字符。任何数据经过base64编码之后,最终就会通过这64个字符来表示。当然还有一个符号,那就是等号。等号它是一个补位的符号

需要注意的是Base64是编码方式,而不是加密方式。

登录认证流程

JWT令牌最典型的应用场景就是登录认证:

  1. 在浏览器发起请求来执行登录操作,此时会访问登录的接口,如果登录成功之后,我们需要生成一个jwt令牌,将生成的 jwt令牌返回给前端。

  2. 前端拿到jwt令牌之后,会将jwt令牌存储起来。在后续的每一次请求中都会将jwt令牌携带到服务端。

  3. 服务端统一拦截请求之后,先来判断一下这次请求有没有把令牌带过来,如果没有带过来,直接拒绝访问,如果带过来了,还要校验一下令牌是否是有效。如果有效,就直接放行进行请求的处理。

生成和校验

先引入JWT的依赖:

在引入完JWT来赖后,就可以调用工具包中提供的API来完成JWT令牌的生成和校验

工具类:Jwts

@Test
public void genJwt(){
Map<String,Object> claims = new HashMap<>();
claims.put("id",1);
claims.put("username","Tom");
String jwt = Jwts.builder()
 .setClaims(claims) //自定义内容(载荷) 
 .signWith(SignatureAlgorithm.HS256, "itheima") //签名算法
 .setExpiration(new Date(System.currentTimeMillis() +
24*3600*1000)) //有效期
 .compact();
System.out.println(jwt);
}

效验

@Test
public void parseJwt(){
Claims claims = Jwts.parser()
 .setSigningKey("itheima")//指定签名密钥(必须保证和生成令牌时使用
相同的签名密钥)

.parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwiZXhwIjoxNjcyNzI5NzMw
fQ.fHi0Ub8npbyt71UqLXDdLyipptLgxBUg_mSuGJtXtBk")
 .getBody();
System.out.println(claims);
}
实现步骤:
  1. 引入JWT工具类

在项目工程下创建com.itheima.utils包,并把提供JWT工具类复制到该包下

  1. 登录完成后,调用工具类生成JWT令牌并返回
JWT工具类
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)//指定令牌Token
 .getBody();
return claims;
 }
}

登录成功,生成JWT令牌并返回

@RestController
@Slf4j
public class LoginController {
//依赖业务层对象
@Autowired
private EmpService empService;
@PostMapping("/login")
public Result login(@RequestBody Emp emp) {
//调用业务层:登录功能
Emp loginEmp = emp@RestController
@Slf4j
public class LoginController {
//依赖业务层对象
@Autowired
private EmpService empService;
@PostMapping("/login")
public Result login(@RequestBody Emp emp) {
//调用业务层:登录功能
Emp loginEmp = empService.login(emp)Service.login(emp);
//判断:登录用户是否存在
if(loginEmp !=null ){
//自定义信息
Map<String , Object> claims = new HashMap<>();
claims.put("id", loginEmp.getId());
claims.put("username",loginEmp.getUsername());
claims.put("name",loginEmp.getName());
//使用JWT工具类,生成身份令牌
String token = JwtUtils.generateJwt(claims);
return Result.success(token);
 }
return Result.error("用户名或密码错误");
 }
}
存储位置

服务器响应的JWT令牌存储在本地浏览器哪里了呢?

在当前案例中,JWT令牌存储在浏览器的本地存储空间local storage中了。 local storage是浏览器的本地存储,在移动端也是支持的

统一拦截

day12-34页

  1. Filter过滤器

  2. Interceptor拦截器

Filter

Filter表示过滤器,是 JavaWeb三大组件(Servlet、Filter、Listener)之一。

过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能

使用了过滤器之后,要想访问web服务器上的资源,必须先经过滤器,过滤器处理完毕之后,才可以访问对应的资源。过滤器一般完成一些通用的操作,比如:登录校验、统一编码处理、敏感字符处理等

下面我们通过Filter快速入门程序掌握过滤器的基本使用操作:

第1步,定义过滤器 :1.定义一个类,实现 Filter 接口,并重写其所有方法。

第2步,配置过滤器:Filter类上加 @WebFilter 注解,配置拦截资源的路径。引导类上加@ServletComponentScan 开启Servlet组件支持。

实现

在定义完Filter之后,Filter其实并不会生效,还需要完成Filter的配置,Filter的配置非常简单,只需要在Filter类上添加一个注解:@WebFilter,并指定属性urlPatterns,通过这个属性指

定过滤器要拦截哪些请求

@WebFilter(urlPatterns = "/*") //配置过滤器要拦截的请求路径( /* 表示拦
截浏览器的所有请求 )
public class DemoFilter implements Filter {
@Override //初始化方法, 只调用一次
public void init(FilterConfig filterConfig) throws
ServletException {
System.out.println("init 初始化方法执行了");
 }
@Override //拦截到请求之后调用, 调用多次
public void doFilter(ServletRequest request, ServletResponse
response, FilterChain chain) throws IOException, ServletException {
System.out.println("Demo 拦截到了请求...放行前逻辑");
//放行
chain.doFilter(request,response);
 }
@Override //销毁方法, 只调用一次
public void destroy() {
System.out.println("destroy 销毁方法执行了");
 }
}

init方法:过滤器的初始化方法。在web服务器启动的时候会自动的创建Filter过滤器对象,在创建过滤器对象的时候会自动调用init初始化方法,这个方法只会被调用一次。

doFilter方法:这个方法是在每一次拦截到请求之后都会被调用,所以这个方法是会被调用多次的,每拦截到一次请求就会调用一次doFilter()方法。

destroy方法: 是销毁的方法。当我们关闭服务器的时候,它会自动的调用销毁方法destroy,而这个销毁方法也只会被调用一次。

当我们在Filter类上面加了@WebFilter注解之后,接下来我们还需要在启动类上面加上一个注解@ServletComponentScan,通过这个@ServletComponentScan注解来开启SpringBoot项目对于Servlet组件的支持。

@ServletComponentScan
@SpringBootApplication
public class TliasWebManagementApplication {
public static void main(String[] args) {
SpringApplication.run(TliasWebManagementApplication.class,
args);
 }
}

注意事项:

在过滤器Filter中,如果不执行放行操作,将无法访问后面的资源。 放行操作:

chain.doFilter(request, response);

过滤器的拦截路径

拦截具体路径

/login

只有访问 /login 路径时,才会被拦截

目录拦截

/emps/*

访问/emps下的所有资源,都会被拦截

拦截所有

/*

访问所有资源,都会被拦截

Filter过滤器优先级

以注解方式配置的Filter过滤器,它的执行优先级是按时过滤器类名的

自动排序确定的,类名排名越靠前,优先级越高。

假如我们想让DemoFilter先执行,怎么办呢?答案就是修改类名。

登录步骤

基于上面的业务流程,我们分析出具体的操作步骤:

  1. 获取请求url

  2. 判断请求url中是否包含login,如果包含,说明是登录操作,放行

  3. 获取请求头中的令牌(token)

  4. 判断令牌是否存在,如果不存在,返回错误结果(未登录)

  5. 解析token,如果解析失败,返回错误结果(未登录)

  6. 放行

登录校验过滤器:LoginCheckFilter
@Slf4j
@WebFilter(urlPatterns = "/*") //拦截所有请求
public class LoginCheckFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest,
ServletResponse servletResponse, FilterChain chain) throws
IOException, ServletException {
//前置:强制转换为http协议的请求对象、响应对象 (转换原因:要使用子
类中特有方法)
HttpServletRequest request = (HttpServletRequest)
servletRequest;
HttpServletResponse response = (HttpServletResponse)
servletResponse;
//1.获取请求url
String url = request.getRequestURL().toString();
log.info("请求路径:{}", url); //请求路径:http://localhost:8080/login
//2.判断请求url中是否包含login,如果包含,说明是登录操作,放行
if(url.contains("/login")){
chain.doFilter(request, response);//放行请求
return;//结束当前方法的执行
 }
//3.获取请求头中的令牌(token)
String token = request.getHeader("token");
log.info("从请求头中获取的令牌:{}",token);
//4.判断令牌是否存在,如果不存在,返回错误结果(未登录)
if(!StringUtils.hasLength(token)){
log.info("Token不存在");
Result responseResult = Result.error("NOT_LOGIN");
//把Result对象转换为JSON格式字符串 (fastjson是阿里巴巴提供的
用于实现对象和json的转换工具类)
String json = JSONObject.toJSONString(responseResult);
response.setContentType("application/json;charset=utf-
8");
//响应
response.getWriter().write(json);
return;
 }
//5.解析token,如果解析失败,返回错误结果(未登录)
try {
JwtUtils.parseJWT(token);
 }catch (Exception e){
log.info("令牌解析失败!");
Result responseResult = Result.error("NOT_LOGIN");
//把Result对象转换为JSON格式字符串 (fastjson是阿里巴巴提供的用于实现对象和json的转换工具类)
String json = JSONObject.toJSONString(responseResult);
response.setContentType("application/json;charset=utf-
8");
//响应
response.getWriter().write(json);
return;
}
//6.放行
chain.doFilter(request, response);
 }
}

在上述过滤器的功能实现中,我们使用到了一个第三方json处理的工具包fastjson。我们要想使用,

需要引入如下依赖:

com.alibaba

fastjson

1.2.76

拦截器

下拦截器的基本使用。拦截器的使用步骤和过滤器类似,也分为两步:

  1. 定义拦截器

  2. 注册配置拦截器

自定义拦截器

自定义拦截器:实现HandlerInterceptor接口,并重写其所有方法

//自定义拦截器
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
//目标资源方法执行前执行。 返回true:放行 返回false:不放行
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle .... ");
return true; //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 .... ");
 }
}

注意:

preHandle方法:目标资源方法执行前执行。 返回true:放行 返回false:不放行

postHandle方法:目标资源方法执行后执行

afterCompletion方法:视图渲染完毕后执行,最后执行

注册配置拦截器

实现WebMvcConfigurer接口,并重写addInterceptors方法

@Configuration
public class WebConfig implements WebMvcConfigurer {
//自定义的拦截器对象
@Autowired
private LoginCheckInterceptor loginCheckInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册自定义拦截器对象
registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**
");//设置拦截器拦截的请求路径( /** 表示拦截所有请求)
 }
}
拦截路径

在注册配置拦截器的时候,我们要指定拦截器的拦截路径,

通过 addPathPatterns("要拦截路径") 方法,就可以指定要拦截哪些资源。

在入门程序中我们配置的是 /** ,表示拦截所有资源,而在配置拦截器时,不仅可以指定要拦截哪些资源,还可以指定不拦截哪些资源,只需要调用 excludePathPatterns("不拦截路径") 方法,指定哪些资源不需要拦截

@Configuration
public class WebConfig implements WebMvcConfigurer {
//拦截器对象
@Autowired
private LoginCheckInterceptor loginCheckInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册自定义拦截器对象
registry.addInterceptor(loginCheckInterceptor)
 .addPathPatterns("/**")//设置拦截器拦截的请求路径( /**
表示拦截所有请求)
 .excludePathPatterns("/login");//设置不拦截的请求路径
 }
}

 

浏览器请求拦截过程

当我们打开浏览器来访问部署在web服务器当中的web应用时,此时我们所定义的过滤器会拦截到这次请求。拦截到这次请求之后,它会先执行放行前的逻辑,然后再执行放行操作。而由于我们当前是基于springboot开发的,所以放行之后是进入到了spring的环境当中,也就是要来访问我们所定义的controller当中的接口方法。

Tomcat并不识别所编写的Controller程序,但是它识别Servlet程序,所以在Spring的Web环境中提供了一个非常核心的Servlet:DispatcherServlet(前端控制器),所有请求都会先进行到DispatcherServlet,再将请求转给Controller。

当我们定义了拦截器后,会在执行Controller的方法之前,请求被拦截器拦截住。执行preHandle() 方法,这个方法执行完成后需要返回一个布尔类型的值,如果返回true,就表示放行本次操作,才会继续访问controller中的方法;如果返回false,则不会放行(controller中的方法也不会执行)。

在controller当中的方法执行完毕之后,再回过来执行 postHandle() 这个方法以及afterCompletion() 方法,然后再返回给DispatcherServlet,最终再来执行过滤器当中放行后的这一部分逻辑的逻辑。执行完毕之后,最终给浏览器响应数据

以上就是拦截器的执行流程。通过执行流程分析,大家应该已经清楚了过滤器和拦截器之间的区别,其实它们之间的区别主要是两点:

接口规范不同:过滤器需要实现Filter接口,而拦截器需要实现HandlerInterceptor接口。

拦截范围不同:过滤器Filter会拦截所有的资源,而Interceptor只会拦截Spring环境中的资源。

总结先过滤在拦截

2. 数据库设计

数据库表结构:
  • 用户表: 存储用户信息,包括用户名、密码、邮箱、头像等。--阿里云oss--注解实现--参考官方SDK

--查询回显--

  • 博客文章表: 存储博客文章内容,包括标题、摘要、内容、发布时间等。
  • 评论表: 存储用户对博客文章的评论。
  • 分类表: 存储博客文章的分类,如技术、生活、读书等。
  • 标签表: 存储博客文章的标签,用于文章分类和搜索。

文件上传

前端

上传文件的原始form表单,要求表单必须具备以下三点(上传文件页面三要素):

表单必须有file域,用于选择要上传的文件

.

表单提交方式必须为POST

通常上传的文件会比较大,所以需要使用 POST 提交方式

.

表单的编码类型enctype必须要设置为:multipart/form-data

普通默认的编码格式是不适合传输大型的二进制数据的,所以在文件上传时,表单的编码格式必须设置为multipart/form-data

.

后端
1.存储为临时文件

首先在服务端定义这么一个controller,用来进行文件上传,然后在controller当中定义一个方法来处理 /upload 请求

在定义的方法中接收提交过来的数据 (方法中的形参名和请求参数的名字保持一致)

用户名:String name

年龄: Integer age

文件: MultipartFile image

Spring中提供了一个API:MultipartFile,使用这个API就可以来接收到上传的文件

如果表单项的名字和方法中形参名不一致

:使用@RequestParam注解进行参数绑定

public Result upload(String username,

Integer age,

@RequestParam("image") MultipartFile

file)

2.本地存储

代码实现:

  1. 在服务器本地磁盘上创建images目录,用来存储上传的文件(例:E盘创建images目录)

  2. 使用MultipartFile类提供的API方法,把临时文件转存到本地磁盘目录下

@Slf4j

@RestController

public class UploadController {

@PostMapping("/upload")

public Result upload(String username, Integer age, MultipartFile

image) throws IOException {

log.info("文件上传:{},{},{}",username,age,image);

//获取原始文件名

String originalFilename = image.getOriginalFilename();

//将文件存储在服务器的磁盘目录

image.transferTo(new File("E:/images/"+originalFilename));

return Result.success();

}

}

重复名处理

由于我们是使用原始文件名作为所上传文件

的存储名字,当我们再次上传一个名为1.jpg文件时,发现会把之前已经上传成功的文件覆盖掉。

解决方案:保证每次上传文件时文件名都唯一的(使用UUID获取随机文件名)

@Slf4j
@RestController
public class UploadController {
@PostMapping("/upload")
public Result upload(String username, Integer age, MultipartFile
image) throws IOException {
log.info("文件上传:{},{},{}",username,age,image);
//获取原始文件名
String originalFilename = image.getOriginalFilename();
//构建新的文件名
String extname =
originalFilename.substring(originalFilename.lastIndexOf("."));//文件
扩展名
String newFileName = UUID.randomUUID().toString()+extname;//
随机名+文件扩展名
//将文件存储在服务器的磁盘目录
image.transferTo(new File("E:/images/"+newFileName));
return Result.success();
 }
}

在SpringBoot中,文件上传时默认单个文件最大大小为1M

那么如果需要上传大文件,可以在application.properties进行如下配置:

#配置单个文件最大上传大小

spring.servlet.multipart.max-file-size=10MB

#配置单个请求最大上传大小(一次请求可以上传多个文件)

spring.servlet.multipart.max-request-size=100MB

缺点

如果直接存储在服务器的磁盘目录中,存在以下缺点:

不安全:磁盘如果损坏,所有的文件就会丢失

容量有限:如果存储大量的图片,磁盘空间有限(磁盘不可能无限制扩容)

无法直接访问

为了解决上述问题呢,通常有两种解决方案:

自己搭建存储服务器,如:fastDFS 、MinIO

使用现成的云服务,如:阿里云,腾讯云,华为云

3.阿里云OSS

day11-19页

登录阿里云->开通对象存储服务oss->创建bucket->获取AccessKey秘钥->参考官方SDK

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

import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
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.FileInputStream;
import java.io.InputStream;
public class AliOssTest {
public static void main(String[] args) throws Exception {
// Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
String endpoint = "oss-cn-shanghai.aliyuncs.com";
// 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您
创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。

String accessKeyId = "LTAI5t9MZK8iq5T2Av5GLDxX";
String accessKeySecret = "C0IrHzKZGKqU8S7YQcevcotD3Zd5Tc";
// 填写Bucket名称,例如examplebucket。
String bucketName = "web-framework01";
// 填写Object完整路径,完整路径中不能包含Bucket名称,例如
exampledir/exampleobject.txt。
String objectName = "1.jpg";
// 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。
// 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文
件流。
String filePath=
"C:\\Users\\Administrator\\Pictures\\1.jpg";
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint,
accessKeyId, accessKeySecret);
try {
InputStream inputStream = new FileInputStream(filePath);
// 创建PutObjectRequest对象。
PutObjectRequest putObjectRequest = new
PutObjectRequest(bucketName, objectName, inputStream);
// 设置该属性可以返回response。如果不设置,则返回的response为
空。
putObjectRequest.setProcess("true");
// 创建PutObject请求。
PutObjectResult result =
ossClient.putObject(putObjectRequest);
// 如果上传成功,则返回200。
System.out.println(result.getResponse().getStatusCode());
 } 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();
 }
 }
 }
}

在以上代码中,需要替换的内容为:

accessKeyId:阿里云账号AccessKey

accessKeySecret:阿里云账号AccessKey对应的秘钥

bucketName:Bucket名称

objectName:对象名称,在Bucket中存储的对象的名称

filePath:文件路径

在新增员工的时候,上传员工的图像,而之所以需要上传员工的图像,是因为将来我们需要在系统页面当中访问并展示员工的图像。而要想完成这个操作,需要做两件事:

  1. 需要上传员工的图像,并把图像保存起来(存储到阿里云OSS)

  2. 访问员工图像(通过图像在阿里云OSS的存储地址访问图像)

OSS中的每一个文件都会分配一个访问的url,通过这个url就可以访问到存储在阿里云上的图片。所以需要把url返回给前端,这样前端就可以通过url获取到图像。

步骤
  1. 引入阿里云OSS上传文件工具类(由官方的示例代码改造而来)
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.util.UUID;
@Component
public class AliOSSUtils {
private String endpoint = "https://oss-cnshanghai.aliyuncs.com";
private String accessKeyId = "LTAI5t9MZK8iq5T2Av5GLDxX";
private String accessKeySecret =
"C0IrHzKZGKqU8S7YQcevcotD3Zd5Tc";
private String bucketName = "web-framework01";
/**
* 实现上传图片到OSS
*/
public String upload(MultipartFile multipartFile) throws
IOException {
// 获取上传的文件的输入流
InputStream inputStream = multipartFile.getInputStream();
// 避免文件覆盖
String originalFilename =
multipartFile.getOriginalFilename();
String fileName = UUID.randomUUID().toString() +
originalFilename.substring(originalFilename.lastIndexOf("."));
//上传文件到 OSS
OSS ossClient = new OSSClientBuilder().build(endpoint,
accessKeyId, accessKeySecret);
ossClient.putObject(bucketName, fileName, inputStream);
//文件访问路径
String url = endpoint.split("//")[0] + "//" + bucketName +
"." + endpoint.split("//")[1] + "/" + fileName;
// 关闭ossClient
ossClient.shutdown();
return url;// 把上传到oss的路径返回
 }
}
  1. 修改UploadController代码:
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.IOException;
@Slf4j
@RestController
public class UploadController {
@Autowired
private AliOSSUtils aliOSSUtils;
@PostMapping("/upload")
public Result upload(MultipartFile image) throws IOException {
//调用阿里云OSS工具类,将上传上来的文件存入阿里云
String url = aliOSSUtils.upload(image);
//将图片上传完成后的url返回,用于浏览器回显展示
return Result.success(url);
 }
}

查询回显

day11-38

3. 业务逻辑实现

用户管理
  • 用户注册、登录、编辑资料、注销账户等功能。
  • 使用Spring Security或Apache Shiro实现用户权限管理和安全控制。--?
Shiro

是一个强大而灵活的开源安全框架,能够非常清晰的处理认证、授权、管理会话以及密码加密。

不跟任何的框架或者容器捆绑,可以独立运行。

四大核心功能介绍:

Authentication:身份认证/登录,验证用户是不是拥有相应的身份; Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限; Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普JavaSE环境的,也可以是如Web环境的; Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;

shiro的优点
  • shiro的代码更易于阅读,且使用更加简单;
  • shiro可以用于非web环境,不跟任何框架或容器绑定,独立运行;
shiro的缺点
  • 授权第三方登录需要手动实现;
Spring Security

一般流程为:

当用户登录时,前端将用户输入的用户名、密码信息传输到后台,后台用一个类对象将其封装起来,通常使用的是UsernamePasswordAuthenticationToken这个类。 程序负责验证这个类对象。验证方法是调用Service根据username从数据库中取用户信息到实体类的实例中,比较两者的密码,如果密码正确就成功登陆,同时把包含着用户的用户名、密码、所具有的权限等信息的类对象放到SecurityContextHolder(安全上下文容器,类似Session)中去。 用户访问一个资源的时候,首先判断是否是受限资源。如果是的话还要判断当前是否未登录,没有的话就跳到登录页面。 如果用户已经登录,访问一个受限资源的时候,程序要根据url去数据库中取出该资源所对应的所有可以访问的角色,然后拿着当前用户的所有角色一一对比,判断用户是否可以访问(这里就是和权限相关)。

spring-security的优点
  • spring-security对spring整合较好,使用起来更加方便;
  • 有更强大的spring社区进行支持;
  • 支持第三方的 oauth 授权,官方网站:spring-security-oauth
博客文章管理:
  • 发布新文章、编辑文章、删除文章。
  • 实现文章的增删改查(CRUD)操作。
  • 文章评论功能,实现评论的增删改查。
  • 文章分类和标签管理
互动交流
  • 实现用户对文章的点赞和收藏功能。
  • 实现评论的审核机制,避免恶意评论。
审核实现

调用百度API实现文本与图像审核功能

.

 添加依赖

下载工具类引入项目

 获取Access Token的代码,主要功能是获取调用API时必须在URL中带上的access_token参数。

//调用文本审核接口并取得结果

4. 安全性设计

  • 使用HTTPS协议保障数据传输安全。
  • 对用户密码进行加密存储(如使用bcrypt)。
  • 实现CSRF和XSS的防护措施。
  • jwt令牌登录校验+过滤器filter--实现?

5. 性能优化

  • 使用缓存(如Redis)来提高系统响应速度。

    --redis

1.配置redis信息(地址、端口、密码)

2.引入redisTemplate依赖

3.写redis工具类

4.使用工具类使用redis

  • 对数据库进行定期优化,如索引优化、查询优化等。

  • 使用分页和懒加载技术,减轻服务器压力。

sql优化

6. 部署与运维

Log4j是一款基于Java的开源日志组件,Log4j功能非常强大,我们可以将日志信息输出到控制台、文件、用户界面,也可以输出到操作系统的事件记录器和一些系统常驻进程

  • 使用监控工具(如Prometheus和Grafana)对系统性能进行监控。---\\

7. 开发工具和环境

  • IDE: IntelliJ IDEA
  • 代码规范: 使用Spring Boot提供的代码规范或自定义规范。
  • 项目管理: 使用Maven进行项目管理和依赖配置。 通过上述技术设计方案,可以构建一个具备基本博客功能、安全性高、易于维护的个人博客系统。在实际开发过程中,应根据具体需求调整和优化系统设计。

java里,HashMap的底层实现原理

    HashMap的底层数据结构是一个数组,数组中的每个元素称为桶(bucket),每个桶实际上是一个链表。当新元素插入到HashMap中时,首先根据键的哈希码来计算出桶的位置,然后将该元素添加到该桶所对应的链表中。     当我们在HashMap中查找元素时,它首先使用键的哈希码来计算出元素所在的桶的位置,然后遍历该桶对应的链表,查找键所对应的值。由于每个桶可能包含多个元素,因此查找的时间复杂度通常为O(1)。     需要注意的是,在添加元素时,如果哈希码相同但键不同,则会在同一桶中创建一个新的链表节点,这被称为哈希冲突。为了减少哈希冲突的数量,HashMap在每个桶上设置了一个阈值,当链表长度超过阈值时,会将链表转换为红黑树,以提高查找效率。 另外,为了保持HashMap的性能,当桶的数量达到一定程度时,HashMap会自动扩容。在扩容过程中,HashMap会重新计算每个元素在新数组中的位置,这个过程是比较耗时的,因此需要在实际使用时留足够的空间以减少扩容的频率。

1、简介:Java中的HashMap是一种基于哈希表的Map接口实现。它可以存储键值对,并且支持快速的插入、删除和查找操作。 2、使用的数据结构:HashMap的底层实现是数组+链表/红黑树。 3、具体实现原理: 1.创建结构:HashMap内部维护了一个Entry数组,每个Entry包含一个键值对。 2.插入值:HashMap根据键的哈希值计算出该键值对在数组中的位置。如果该位置已经有其他键值对了,则使用链表或红黑树来解决冲突。 3.查找值:HashMap会根据键的哈希值计算出该键值对在数组中的位置,然后遍历链表或红黑树,找到对应的键值对。 4.查找效率优化:Java 8中,当链表长度超过一定阈值时,会将链表转换为红黑树,以提高查找效率。 5.存储、访问效率优化:通过动态扩容、负载因子等特性提高效率。

HashSet如何检查重复

HashSet 通过哈希表实现,使用哈希算法将元素的值转换为哈希码并存储在哈希表中。当要添加一个新元素时,HashSet 会先计算该元素的哈希码,并查找哈希表中是否已经存在相同的哈希码。如果存在相同的哈希码,HashSet 就会调用该元素的 equals 方法进行比较,如果 equals 方法返回 true,HashSet 就认为该元素已经存在于集合中,不会将其添加到集合中。如果哈希码没有冲突,或者哈希码冲突但是 equals 方法返回 false,HashSet 就会将元素添加到集合中。因此,HashSet 检查重复是通过哈希码和 equals 方法实现的。

==和equals区别

==比较的是对象的地址是否相同,而equals是Object类的方法,如果不重写,则默认就是比较两个对象的地址,重写了就按重写后的规则来判断

在Java中,==运算符和equals()方法都可以用于比较两个对象。但是它们的比较方式是不同的: ==运算符比较的是两个对象的引用是否相等,即它们是否指向内存中的同一对象。 equals()方法比较的是两个对象的内容是否相等,即它们是否具有相同的属性值。

equals如何判断两个对象相同

在Java中,对象的 equals() 方法用于判断两个对象是否相等。默认情况下,equals() 方法只是比较两个对象的内存地址是否相同,即比较引用是否相同。如果要判断对象的内容是否相同,则需要重写 equals() 方法。

Spring三件套框架

Spring 三件套指的是 Spring 框架的三个核心模块,分别是 Spring Core、Spring AOP 和 Spring MVC。 Spring Core:提供了 IoC(Inverse of Control)容器,用于对象之间的解耦,通过容器自动将对象之间的依赖注入。同时,Spring Core 还提供了对 AspectJ 的集成,以及对基于注解的 Spring Bean 的支持。 Spring AOP:提供了基于 AOP(Aspect Oriented Programming)的编程方式,能够在不修改源代码的情况下,通过代理机制对对象进行增强,比如添加事务、日志、安全检查等功能。 Spring MVC:是 Spring 框架的 Web 模块,提供了 MVC(Model-View-Controller)模式的支持,用于处理 Web 请求和响应。Spring MVC 通过 DispatcherServlet、HandlerMapping、Controller 和 ViewResolver 等组件构成了完整的 MVC 模式的实现。

  • 19
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值