设计模式学习_工厂方法模式

工厂方法设计模式 Factory Pattern

定义以及抽象层面

  • 工厂方法定义了一种更好地创造对象的方式。但是它让工厂子类来决定怎么创造以及创造哪一个对象。也就是说,工厂模式将创造对象的过程延迟给子类来进行。

它以这两种东西组成:

1. 我们想要创造的对象。

2.我们用来创造这些对象的工厂。

抽象层面的图所示:

在这里插入图片描述
# 我们可以看到:

1. 左边代表的是各个我们需要创建的对象。

2. 右边代表的是我们用什么(工厂)来创建,当然了,根据什么规则来创建这些对象,我们也可以写在工厂中。

具体案例层面以及代码实现

# 现在,我们用可以实现的代码形式来说明:

重要的图如下:

在这里插入图片描述

  • 也就是说,业务场景如下:我们需要创造一个可以画出形状的图形:可以是画出长方形,正方形或者圆形。我们利用工厂类来有逻辑地创造这些类。
  1. 接口 形状 Shape.java

    1
    2
    3
    4
    5
    package com.company.inter;

    public interface Shape {
    void draw();
    }
  2. 具体的实现子类.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    package com.company.impl;

    import com.company.inter.Shape;

    public class Circle implements Shape {
    @Override
    public void draw() {
    System.out.println("画出来的是圆形图案...");
    }
    }
1
2
3
4
5
6
7
8
9
10
package com.company.impl;

import com.company.inter.Shape;

public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("画出来的是长方形图案...");
}
}
1
2
3
4
5
6
7
8
9
10
package com.company.impl;

import com.company.inter.Shape;

public class Square implements Shape {
@Override
public void draw() {
System.out.println("画出来的是正方形图案...");
}
}
  1. 创造指定对象的工厂.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    package com.company.factory;

    import com.company.impl.Circle;
    import com.company.impl.Rectangle;
    import com.company.impl.Square;
    import com.company.inter.Shape;

    public class ShapeFactory {
    public Shape getShape(String specificShapeName) {
    //定义如何生成对象的逻辑
    if (specificShapeName.equals("Rectangle")) {
    return new Rectangle();
    } else if (specificShapeName.equals("Square")) {
    return new Square();
    }else if (specificShapeName.equals("Circle")) {
    return new Circle();
    }
    return null;
    }
    }
  2. 测试的主函数Main.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    package com.company;

    import com.company.factory.ShapeFactory;
    import com.company.inter.Shape;

    public class Main {

    public static void main(String[] args) {
    ShapeFactory shapeFactory = new ShapeFactory();

    //通过指定不同的名字 来创造不同的对象(依照具体的业务场景来定义不同的对象创建规则)
    Shape s1 = shapeFactory.getShape("Circle");
    s1.draw();

    Shape s2 = shapeFactory.getShape("Square");
    s2.draw();
    }
    }

效果图

在这里插入图片描述

Java技巧_配置视图解析器

SpringBoot配置MVC视图解析

  • 首先确保自己创建的是SpringBoot项目

项目结构图如下:
在这里插入图片描述
接下来正式开始配置:

1. 在SpringBoot的配置文件中

1
2
spring.mvc.view.prefix=classpath:/static/
spring.mvc.view.suffix=.html

(主要看图配置!)

2. Controller层写代码跳转即可

1
2
3
4
5
6
7
8
@RequestMapping("/user")
@Controller
public class User{
@GetMapping("/index")
public String toIndex(){
return "index";
}
}

注意,这里不能用@RestController注解

Spring Security学习笔记

安全保证框架

Shiro与Spring security

他们之间很像,除了名字、类名不一样。

Spring Security

在这里插入图片描述

  1. 可以实现定制化身份认证 Authentication

  2. 权限控制 Access of Control

权限:

  • 功能权限

  • 访问权限

  • 菜单权限

Spring Security 用于简化过滤器&拦截器

在这里插入图片描述

  • WebSecurityConfigurerAdapter:自定义Security策略(适配器模式)

  • AuthenticationManagerBuilder:自定义认证策略(建造者模式)

AOP概念

我们不用改变原来项目的业务代码,而是在项目中加入config。帮我们去做一些事情

入门案例

  • 使用工具idea,新建一个Spring initize项目(只勾选一个web即可)

  • 使用SpringBoot 2.2.1版本来使用: pom.xml文件中修改

  • 添加springboot-security依赖

修改controller 体验security

在controller包下新增TestController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/test")
public class TestController {

@GetMapping("/hello")
public String add(){
return "Hello, Spring Security!";
}
}

修改默认端口(避免占用)

在application.properties中

1
server.port = 8181

启动项目并测试

1
localhost:8181/test/hello

系统会出现这个界面:
在这里插入图片描述
Spring Security会强制让我们先登录。
在这里插入图片描述

  1. 默认的用户名:user

  2. 控制台(如图)会出现我们的默认密码Using generated security password

在正确输入后,会出现下图效果:
在这里插入图片描述

SpringSecurity 基本原理(过滤链)

Spring Security框架本质上就是一套的过滤器链 FilterChain

也就是说,有很多的过滤器Filter,执行到具体方法时,就会进入过滤器,只有过滤器对其进行过滤放行,才能进入到下一个过滤器。

常见三个过滤器

FilterSecurityInterceptor

FilterSecurityInterceptor是一个方法级的权限过滤器,具体有doFilter方法。

首先看之前的过滤器是否执行,如果执行,才执行自己的过滤器。

ExceptionTranslationFilter

ExceptionTranslationFilter是一个处理权限过程中,出现的异常问题的过滤器。依据每个不同的异常,做不同的处理。

UsernamePasswordAuthenticationFilter

/login且使用POST请求过来的表单做一个用户名密码校验。

SpringSecurity 过滤器的加载过程

使用Spring Security配置过滤器

如果使用SpringBoot项目的话,自动帮我们集成以下的这些代码。

  • 如果不用SpringBoot项目集成SpringSecurity的话,需要写一个DelegationFilterProxy过滤器。

而这个DelegationFilterProxy的doFilter方法中,有一个init初始化方法,这个初始化方法中,用于获得FilterChainProxy,这个Proxy中有一个doFilterInternal方法,这个方法中有一个List<Filter>很多个过滤器,并通过迭代的方式getFilters获得这些所有的过滤器。

SpringSecurity 中重要的接口

UserDetailsService 用户细节信息接口

在实际开发中,我们的账号和密码,并不是Spring Security所默认的user和默认生成密码。

而都是从数据库中查询出来的。

因此,这个接口很方便我们进行自定义逻辑业务开发。

实现这个UserDetailsService接口即可,在这个实现方法中,写查数据库的方法。

  1. 创建一个类,继承UsernamePasswordAuthenticationFilter过滤器(重写三个方法)

  2. 创建一个类,重新UserDetailsService接口,编写查询数据过程,并返回User对象(这个对象由Security提供)

PasswordEncoder 密码加密接口

在上一个UserDetailsService中我们需要返回一个Spring Security框架中的User对象,在这个对象中的密码,必须是要返回加密后的密码。而不能是明文。

Web项目中 认证&授权 思路

认证 Authentication: 就是用户在登录Web中,利用自己的用户名与密码,进行用户认证。

设置登录的用户名与密码

  1. 通过 application.properties 配置文件进行配置

  2. 通过配置类

  3. 通过自定义编写实现类,实现UserDetialsService,返回这个User对象即可

1. 通过配置文件

在这里插入图片描述
配置代码如下:

1
2
spring.security.user.name = user
spring.security.user.password = 123456

2. 通过自定义配置类

  1. 添加注解@configuration

  2. 继承WebSecurityConfigurerAdapter类

  3. 重写这个类的方法(如下图)
    在这里插入图片描述

在重写这个方法时,我们利用auth来配置用户信息,对于密码需要加密。

而且,要记得加一个@bean注解用于验证PasswordEncoder映射,否则会报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//利用这个auth来设置登录的用户信息
// 密码需要加密
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
String passwordEncoded = bCryptPasswordEncoder.encode("123456");
auth.inMemoryAuthentication().withUser("user").password(passwordEncoded).roles("admin");
}

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

现在我们可以用这个用户名user 密码123456去登录了。而这个user用户,具有的角色是:admin

3. 利用UserDetailsService接口配置(开发经常用)

  1. 创建配置类,auth使用UserDetailsService(注入一个UserDetailsService类)

  2. 编写实现类,返回User对象,这个对象由用户名、密码以及权限

创建配置类SecurityUserServiceConfig

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
package com.vincent.securitydemo.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class SecurityUserServiceConfig extends WebSecurityConfigurerAdapter {

@Autowired
private UserDetailsService userDetailsService;

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
//1. 使用UserDetailsService
//2. 使用返回的PasswordEncoder @Bean加密
}

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

编写实现类MyUserDetailsService

编写一个实现类MyUserDetailsService去实现UserDetailsService(我们的配置类需要),重写这个loadUserByUsername方法加载用户信息。

注意:@Service(“userDetailsService”)

这个参数要与配置类中,@Autowire自动装配的名字一样。不然找不到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.vincent.securitydemo.service;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.List;

@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {

@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
// 手动代替查询数据库操作
List<GrantedAuthority> authorityList = AuthorityUtils.commaSeparatedStringToAuthorityList("role");

return new User("user", new BCryptPasswordEncoder().encode("123456"), authorityList);
}
}
  • 注意,我们返回的User对象有三个参数:用户名,加密后的密码以及一个Collection表示具有的权限集合

加入数据库操作 到MyUserDetailsService实现类中

整合MybatisPlus进入到这一步,实现具体的数据库操作。

引入相关依赖

  1. 引入MybatisPlus依赖

  2. 引入MySQL依赖

  3. 引入工具类Lombok 方便实体类注解

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
<!--web项目依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!--security依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

<!--pojo注解生成get、set方法-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>

<!--mybatis依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.1</version>
</dependency>

<!--MySQL依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

<!--测试依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>

处理数据库中的User表

  1. id 自增数字主键

  2. username varchar类型

  3. password varchar类型

处理Mapper 继承Mybatis+ 给我们写好的基本Mapper即可

  1. 创建mapper包 UserMapper接口 extends BaseMapper 泛型
1
2
3
4
5
6
7
8
9
10
package com.vincent.securitydemo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.vincent.securitydemo.entity.User;

@Repository
public interface UserMapper extends BaseMapper<User> {

}

重写之前MyUserDetailsService实现类中的方法 通过数据库查询

  1. Service实现类中 注入UserMapper对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {

@Autowired
private UserMapper userMapper;

@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {

// 数据库查询语句 用于认证用户
//并最终返回Security框架中的User对象
return ...;
}
}
  1. 利用Mybatis-plus中的QueryWrapper帮我们规定查询满足的条件 并通过UserMapper中的查询方法,注意判断根据用户名查询出来的User对象(我们定义的Entity)是否为空

  2. 最终返回Spring Security框架自己的User对象

注意看代码中的注释内容!!!

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
package com.vincent.securitydemo.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.vincent.securitydemo.entity.User;
import com.vincent.securitydemo.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.List;

@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {

@Autowired
private UserMapper userMapper;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//数据库查询语句 用于认证用户
//参数中的s 即代表用户提交表单中的用户名
//现在我们要根据用户名 去查询对应的数据库中是否存在这个数据

QueryWrapper<User> wrapper = new QueryWrapper<>(); //条件构造器 通过这个构造器去做查询 类似UserExample
wrapper.eq("username", username);
User user = userMapper.selectOne(wrapper);

if (user == null) { //认证失败 没有存在的用户
throw new UsernameNotFoundException("用户名不存在");
}

//注意我们返回的不是自己的实体类User
//而是security框架给我们提供的User类
List<GrantedAuthority> authorityList = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
return new org.springframework.security.core.userdetails.User(user.getUsername(), new BCryptPasswordEncoder().encode(user.getPassword()), authorityList);
}
}
  1. 这一步很关键 我们要在SpringBoot启动类中 加一个注解@MapperScan 启动Mapper 不然的话无法识别我们的Mapper

SecuritydemoApplication.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.vincent.securitydemo;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.vincent.securitydemo.mapper")
public class SecuritydemoApplication {
public static void main(String[] args) {
SpringApplication.run(SecuritydemoApplication.class, args);
}

}
  1. application.properties 配置文件中 配置数据库信息

对于本SpringSecurity测试的SpringBoot版本是SpringBoot 2.2.1 Release

因此,在引入数据库驱动时,如果使用的是MySQL8.0驱动且加入时区

引入时,应使用:com.mysql.cj.jdbc.Driver

1
2
3
4
5
6
7
8
9
10
11
12
13
server.port = 8181

#可直接通过配置文件 来配置用户信息
#spring.security.user.name=user
#spring.security.user.password=123456

#数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url = jdbc:mysql://localhost:3306/securitydemo?characterEncoding=utf-8&serverTimezone=GMT%2B8
spring.datasource.username = root
spring.datasource.password = 123456

spring.jpa.show-sql = true

自定义登录页面&设置路径不需要认证

在实际的业务开发中,我们需要自己的好看的登录页面。因此接下来我们将指定登录的页面。

在实际的业务开发中,有些Controller是不需要进行验证用户权限的。我们也进行设置。

主要思路就是,在 SecurityConfig 配置类中,配置一下就可以了。

值得注意的是,现在我们还是用到configure方法,但是里面的参数不是AuthenticationManagerBuilder auth,而是HttpSecurity http。

我们通过http.formLogin() 进行一系列的设置

  1. 设置默认登录页面的路径

  2. 设置默认登录的请求路径(SpringSecurity自动帮我们实现)

  3. 设置默认登录成功跳转的路径

  4. 设置哪些路径不需要认证,直接通过

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
package com.vincent.securitydemo.config;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class SecurityUserServiceConfig extends WebSecurityConfigurerAdapter {

@Autowired
private UserDetailsService userDetailsService;

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//配置用户认证
auth
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}

@Override
protected void configure(HttpSecurity http) throws Exception {
//完成与HTTP请求的配置设置
http.formLogin()
.loginPage("/login.html")
//自定义自己编写的登录页面 参数是地址
.
loginProcessingUrl("/user/login")
//定义登录访问的请求路径 但是这个具体的方法过程由SpringSecurity实现

.failureUrl("/error.html")
//定义登录错误跳转的页面 参数是地址

.defaultSuccessUrl("index.html").permitAll()
//定义默认登录成功后 跳转到的路径

.and().authorizeRequests().antMatchers("/", "/test/hello", "/user/login").permitAll()
.anyRequest().authenticated()
//授权通过,这些路径是不需要认证,直接让它过!

.and().csrf().disable(); //关闭CSRF认证方式
}

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

现在我们只需要,一个登录页面,一个处理登录成功的controller

  • 注意我们这里的action值,必须跟config中配置的请求路径相同,而且name只能是用户名和密码

resources/static/login.html

1
2
3
4
5
<form action="/user/login" method="post">
用户名:<input type="text" name="username"> <br>
密码: <input type="password" name="password">
<input type="submit" value="登录">
</form>

resources/static/error.html

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>错误页面</title>
</head>
<body>
<h1>抱歉,登录错误!</h1>
</body>
</html>

resources/static/index.html

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>主页面</title>
</head>
<body>
<h1>登录成功,这是主界面!</h1>
</body>
</html>

controller/TestController.java

1
2
3
4
@GetMapping("/index")
public String toIndex(){
return "Hello, Index!";
}

基于角色或者权限的访问控制

1. hasAuthority方法

对于这个方法,如果用户具有指定的权限,则返回True,否则返回False.

# 在config中声明只有哪些权限可以通过这个路径请求
在这里插入图片描述

1
2
//只有具有admin权限 才能访问这个/test/index这个路径(同时在Service中加入admin)
.antMatchers("/test/index").hasAuthority("admin")

# Service中加入这些权限

1
2
//在Service中加入admin权限
List<GrantedAuthority> authorityList = AuthorityUtils.commaSeparatedStringToAuthorityList("admin");

在这里插入图片描述
当出现上述图片时,代表403权限不够!

2. hasAnyAuthority

  • 当某个请求对于声明的角色中,任意一个角色都可以通过时使用。

比如对于学生的管理,教师Teacher与管理员Admin都可以访问这些接口路径。

1
.antMatchers("/test/index").hasAnyAuthority("admin,teacher")

则表示,当权限为teacher或者admin…等等时(用逗号隔开即可)都允许通过!

3. hasRole

  • 基本用法不变,但是源码显示:它会将我们声明的角色名xxx变成ROLE_+xxx。

因此,我们在Service中,声明具有的角色时,应该手动添加为ROLE_xxx。

#配置类

1
.antMatchers("/test/index").hasRole("salesman")

#Service声明具有的权限

1
List<GrantedAuthority> authorityList = AuthorityUtils.commaSeparatedStringToAuthorityList("admin,Role_salesman");

4. hasAnyRole

用法与hasAnyAutority一致。

自定义无权限403页面

  • 在配置类中,配置自定义403页面即可。

在这里插入图片描述

1
http.exceptionHandling().accessDeniedPage("/unauth.html");

如果满足无权限条件的话,会跳转到这个页面。
在这里插入图片描述

SpringSecurity 认证&授权 常见的注解

  • 注解的作用,就是简化开发。

@Secured(“ROLE_XXX, ROLE_YYY”)

这个注解表示:用户具有某个角色,可以根据这个角色来访问相应的接口。

使用这个注解时,我们要打开注解功能:

  1. 添加注解到Springboot启动类或者Config配置类上:

    1
    @EnableGlobalMethodSecurity(securedEnabled = true)
  2. 在Controller的方法上面,使用这个注解,自动为我们判断角色权限。

1
2
3
4
5
@GetMapping("/deleteUser")
@Secured("ROLE_admin")
public String deleteUser(){
return "通过权限,这是删除页面!";
}

表示这个方法,必须要具有admin角色才能进入。

@PreAuthorize

  • 这个是在方法执行之前进行校验。
  1. 在启动类相同位置,逗号,开启prePostEnabled = true

    1
    @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
  2. 在相应方法上,加入注解**@PreAuthorize()**

    1
    2
    3
    4
    5
    @GetMapping("/updateUser")
    @PreAuthorize("hasRole('ROLE_admin')")
    public String updateUser(){
    return "通过权限,这是更改页面!";
    }

在这里插入图片描述
可以看到,这里可以选择之前的四种:

  1. hasRole
  2. hasAnyRole
  3. hasAuthority
  4. hasAnyAuthority
  • 注意涉及到Role时,手动添加ROLE_XXX即可。

@PostAuthorize

  • 这种方式用得频率比较少,代表:执行方法之后,再进行校验一般适用于,带有返回值的校验方式!

  • 也就是说,方法是一定会执行的。*

只不过是,方法执行后,遇到了403权限错误。

1
2
3
4
5
6
@GetMapping("/afterMethodVerify")
@PostAuthorize("hasRole('ROLE_admin')")
public String afterMethodVerify(){
System.out.println('一定会执行的!');
return "通过权限,这是目标页面!";
}

Java适应前端JSON参数_万物皆可Map

来源于之前做过的ECharts引入饼图数据:

点我回顾ECharts

我们前端图表data中有两个参数:

  1. name 代表饼图每一部分的名称

  2. value 代表饼图每一部分的数据量(自动根据这个数据量更改比例)

因此我们前端需要复杂处理:name与value

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//同步请求数据
$.ajax({
url: "${request.getContexPath()}/meal/showData",
type: "POST",
dataType: "JSON",
async: false,
// 回调函数
success: function (res){
data = res //这里具体看链接中 引入的饼图ECharts
},
error: function (error){
console.log(error)
}
})

data中是这样的:

1
data:[ { "name": "bookName", value: "20" }]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  @ResponseBody
  @RequestMapping("/showData")
  public List<Map<String,Object>> showData(
Model model,
HttpServletRequest request){
// 查询所有订单信息 List<OrderItem> list = orderItemService.list();

//map: productId(Integer类型) 次数(Integer类型) Map<Integer,Integer> map = new HashMap<>();

//遍历所有待处理的list //利用map的键值唯一性统计各(具体item对象)数量 list.forEach(i -> {
//如果存在对应的pid 作为键 那么就累加sum
//否则(第一次检索) 赋值为默认1 map.merge(i.getPid(), 1, Integer::sum); }); //查询商品名字返回结果集
//值用Object更加的灵活 List<Map<String,Object>> res = new ArrayList<>(); //map映射转换为 集合set Set<Map.Entry<Integer, Integer>> entries = map.entrySet();
//遍历这个集合 entries.forEach(entry -> { Map<String,Object> map1 = new HashMap<>();
//获取到集合中的每一个key值 Integer key = entry.getKey();
//根据这个key值(也就是pid) 去找对应的product对象 Product product = productService.get(key);
//将需要两个属性 put进去 //我们需要前端data的name:val value:value map1.put("name", product.getName()); map1.put("value", entry.getValue()); res.add(map1); }); return res; }

设计模式学习_装饰器模式

装饰器模式 Decorator Pattern

  • 试想有一个平安夜苹果,我们用一个精美的盒子将其装入,同时我们可以按照自己的需要,在最外层继续装精美的盒子(例子不太恰当)。

而这就是装饰器模式,本身的工作原理。

定义

1. 装饰器模式,允许我们像现有的对象添加新的功能同时不改变其设计结构。

2. 是作为现有类的一个包装,即本质上,还是这个类

3. 动态扩展或者撤销类的功能

同时,装饰器模式可以解决,一般继承Inheritance的缺陷。

我们知道,当一个类继承另一个类是,它必须具备这个类的所有功能(包括属性与方法)。

这样是有缺陷的,比如:

我们有一个叫做Beverage 饮料的类 是一个抽象类(代表我们不能像接口一样 去实例化)

其中,Beverage有价格属性以及得到价格的方法

现在Beverage有两个子类:1. 咖啡 2. 茶

现在我们按照一般的思路来添加功能,就是直接在Beverage类中添加(比如咖啡是否加牛奶)

但是,试想一下,如果有牛奶这个属性,对于茶来说,就可能显得没必要了(抱歉如果你喜欢这么搭配,无意冒犯)

  • 也就是说,部分子类有时,没必要拥有父类的所有信息。

所以,我们的装饰器模式就是来解决这样的问题:如何在不更改已有代码的同时,更高效地添加对象的功能。

UML图示

在这里插入图片描述

从这个图中,我们需要看到最重要的概念:

  1. 对于装饰器来说,它即具有饮料成员变量,同时自己又是一个饮料

很好解释:对于奶茶店里的珍珠,首先它要知道自己加到哪杯饮料上去,其次加到那杯饮料过后,它们共同组成了一杯新的饮料。

这样,一层一层的包裹,可以动态地延展系统中对象的功能。

代码实现

  1. 这里我们有Milk与Coffee两种饮料,分别卖5元与10元

  2. 这里我们有可选选项Sugar,加糖多加1元

首先我们要有抽象类Beverage.java

1
2
3
4
5
6
package com.company.entity;

public abstract class Beverage {
public abstract int getPrice();
}

然后我们有两个继承Beverage的Milk.java与Coffee.java

1
2
3
4
5
6
7
8
9
10
11
package com.company.entity;

public class Milk extends Beverage{

//假设菜单上规定 牛奶一杯5元
@Override
public int getPrice() {
return 5;
}
}

1
2
3
4
5
6
7
8
9
10
package com.company.entity;

public class Coffee extends Beverage{

//假设菜单上规定 咖啡一杯10元(注意,咖啡与牛奶都是基础选项)
@Override
public int getPrice() {
return 10;
}
}

接下来我们要有,一个抽象类AddOnDecorator.java

1
2
3
4
5
6
7
8
package com.company.decorator;

import com.company.entity.Beverage;

public abstract class AddOnDecorator extends Beverage {
public abstract int getPrice();
}

最后我们要有自己的可选选项SugarDecorator.java去继承这个抽象类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.company.decorator;

import com.company.entity.Beverage;

public class SugarDecorator extends AddOnDecorator {

//被包裹的内层 不管之前加了什么
// (可以是咖啡来加糖 也可以是牛奶来加糖)
Beverage beverage;

public SugarDecorator(Beverage beverage){
this.beverage = beverage;
}

//这里糖是可选选项
// 当你给已有的饮料加糖时 加上糖后
// 仍然是饮料 还可以加其他的东西
@Override
public int getPrice() {
return this.beverage.getPrice() + 1;
}

}

这里的关键代码是:

1
2
3
4
5
Beverage beverage;
...

return this.beverage.getPrice() + 1;

就相当于给里层的饮料,加了一层价格1元但是里层的饮料还加了什么我们不在乎。

因为,不管加了什么可选选项,它同时就是一杯饮料。

最后,我们用Main.java运行即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.company;

import com.company.decorator.SugarDecorator;
import com.company.entity.Beverage;
import com.company.entity.Coffee;

public class Main {

public static void main(String[] args) {

Coffee coffee = new Coffee();
System.out.println("如果只点一杯咖啡,价格是:" + coffee.getPrice() + "元" );

System.out.println("...");

System.out.println("咖啡太苦了,准备加糖...");

SugarDecorator sugarDecorator = new SugarDecorator(coffee);

System.out.println("加完糖后,整个饮料的价格是:" + sugarDecorator.getPrice());

}
}

在这里插入图片描述

毕业答辩_重点图表

E-R 图

E-R图也称实体-联系图(Entity Relationship Diagram),提供了表示实体类型、属性和联系的方法,用来描述现实世界的概念模型。

  1. 矩形框代表实体

  2. 菱形框代表联系

  3. 实线上的1:1 1:m m:n分别代表一对一、一对多以及多对多关系

  4. 椭圆框代表实体的各个属性,如果是主键则加上下划线。

在这里插入图片描述

时序图

时序图(Sequence Diagram),又名序列图、循序图,是一种UML交互图。它通过描述对象之间发送消息的时间顺序显示多个对象之间的动态协作。它可以表示用例的行为顺序,当执行一个用例行为时,其中的每条消息对应一个类操作或状态机中引起转换的触发事件。

在这里插入图片描述

时序图由以下部分构成:

  1. 角色

角色通常是人(图中的教师),当然也可以是系统或者子系统

  1. 对象

对象通常位于时序图的顶部,一般直接写类名即可

  1. 生命线

对象下面的虚线即称为生命线,而消息在两条生命线之间进行传递

  1. 控制焦点
    控制焦点,一般指的是对象下的矩形框,即对象操作的声明周期

  2. 消息
    在两条相邻的生命线中,消息在这之间进行传递,消息传递的本质就是方法的调用

数据流图

数据流图(Data Flow Diagram):简称DFD,它从数据传递和加工角度,以图形方式来表达系统的逻辑功能、数据在系统内部的逻辑流向和逻辑变换过程,是结构化系统分析方法的主要表达工具及用于表示软件模型的一种图示方法。

在这里插入图片描述

值得注意的是,数据流图不是传统的流程图或框图,数据流也不是控制流。数据流图是从数据的角度来描述一个系统,而框图是从对数据进行加工的工作人员的角度来描述系统

也就是说,数据流图的根本,就是用不同层级来描述数据是怎样流动的,从哪里到哪里,中间经过了什么加工处理。

数据流图,分为顶层与第一层、第二层

在这里插入图片描述

UML用例图

用例图是用来描述系统功能的技术,表示一个系统中用例与参与者及其关系的图。

也就是说,用例图展示的是,系统为不同参与者提供的不同(用例)的功能。
在这里插入图片描述

  1. 参与者一般可以是系统内部的不同角色(图中的管理员)、或者也可以是系统外部的参与角色

  2. 一个用例是一个基本单元(图中的个人信息管理、教师管理等等)

  3. 用例与用例之间具备一些关系(比如图中虚线表示的的包含关系等等)

毕业答辩_总体注意事项

答辩需要注意的事情

封面一定保持校正一致

  1. 大数据学院加上

  2. 校外指导教师不能少

  3. 日期已正确 勿修改

在这里插入图片描述

摘要

  1. 研究什么东西(涉及到论文题目的解析)

  2. 用的什么技术(SpringMVC+Spring+Mybatis 凡是论文中出现的英文名 要知道是什么)

  3. 关键词语:要体现论文的题目 言简意赅 突出业务核心 不要冗余

英文摘要

检查单词之间是否有空格 首字母大写

目录

一定要删掉目录中的目录。

以及英文摘要与绪论之间
在这里插入图片描述

页眉

记得要带编号

在这里插入图片描述

绪论 | 前言

切记,不要内容太少了。要保证字数要有一页!!!

技术介绍:
1. Tomcat(具体是干什么的?)
2. Spring…(常见的后台框架,以及简单的运作原理)
3. Ajax以及前端框架Bootstrap等等(记得概念

  • 注意,格式:如果要么是Mybatis就全文全部都是Mybatis!不要来个mybatis

系统分析

  1. 可行性(XX可行性分析等等)

  2. 功能分析(几个角色)

  3. 用例分析:(提供用例图(执行人以及执行功能)、增加用例说明(业务逻辑)

用例图

  • 要了解用例图的意义,以及其中的基本组成成分。
    在这里插入图片描述
  • 对用例图,要辅佐于一定的用例说明,否则无法根据业务进行代码编写。*

在这里插入图片描述

功能结构图

# 注意结构图,要条状清晰,整体对齐
在这里插入图片描述

系统设计

数据库设计

实体图

实体图不是E-R图

一般来说,写了几个实体图,就要有对应的几个E-R图

每个字段要知道什么意思!
在这里插入图片描述

E-R图

注意,要搞清楚一对多的关系!(检查数据库,表与表之间的关系。

要对应实体图!

一对多,一对一一般不做多的表,多对多要建立新的表。

时序图

在这里插入图片描述

系统实现

代码讲解

注意找到对应的后台代码

以登录举例:(如果老师问,就在对应的页面操作!)

  1. 运行代码,按F12,调出控制台
    在这里插入图片描述
  1. 点击对应到后台action的事件,看看触发了什么请求方法名?

在这里插入图片描述
login 那么在对应后台的Controller中找这个方法!

可以使用快捷键:Ctrl + Shift + R 全局搜索这个RequestMapping

在这里插入图片描述

  • 讲代码的时候,不要乱&按照逻辑来讲,尽量编写代码的时候(见词生意

==遇到不会的,那就点到为止==

尽量不要贴前端代码,代码不要贴太多,不要超过一页!

在这里插入图片描述

系统测试

要了解常见的测试方法:黑盒测试,白盒测试。

最好附上,系统测试表。

总结

  1. 具体开发个什么东西?
  2. 开发的过程中,用到什么工具?
  3. 开发过程中,遇到什么具体的问题?
  4. 未来维护中,怎么解决所遇到的问题?或者整体是否有更好的架构工具!?

参考文献

姓名,参考来源,时间。

不要有其他的特殊符号,中间一般只有逗号与点
在这里插入图片描述
注意,如果英文参考文献,单词之间间隔过大,可以通过以下办法调整:

  1. 选中这段英文参考文献

  2. 右键段落(Office 2010为例)

  3. 中文版式,中选择允许西文在单词中间换行

在这里插入图片描述

致谢

检查,不要抄别人的致谢!

遗留重点

  1. 再次熟悉代码

  2. 熟悉常见的图(用例图,数据流图,E-R图等等以及其中的概念)

  3. 提前检查环境,数据库+后台+前端页面

设计模式学习_观察者模式

观察者模式 Observer Pattern

这是一种很好解释:气象监测总部WeatherStation与我们的手机APP PhoneDisplay之间的关系。

我们看天气的时候,数据都是依照最新的数据变化而变化的,而变化的数据,来源于具体发送数据的仪器(比如天气传感器 Weather Sensor)

也就是说,我们可以利用这种模式,来更好地在数据变化后,让所有显示器设备更新数据变化消息。

关键概念

1. 观察者模式定义了一种“一对多”的关系。

2. 当这个关系中的“一”,数据发生变化时,其中所依赖的“多”将会自动地进行变化。

在这里插入图片描述
解释:

  1. 这里ISubject与IObserver如何体现一对多关系?

  2. 为什么PhoneDisplay与TvDisplay要有WeatherStation呢?

对于第一个问题,我们在每个ISubject中,附上一个List<IObserver> observers列表。

对于第二个问题,回答是:我们的显示屏要显示数据变化,而这些数据来源始终是WeatherStation。

因此,update函数其实要用到WeatherStation的数据。

代码思路&实现:

  • 很简单,如果某一个时刻,WeatherStation中的数据(可以是成员变量)变化了,那么我们就执行notify()来通知List中的每一个Observer。

接下来,我们用代码来实现:

抽象接口层面:

  1. ISubject.java

    1
    2
    3
    4
    5
    6
    7
    8
    package com.company.inter;

    public interface ISubject {
    void add(IObserver iObserver);
    void remove(IObserver iObserver);
    void notifyObserver();
    }

  2. IObserver.java

    1
    2
    3
    4
    5
    package com.company.inter;

    public interface IObserver {
    void update();
    }

具体实现层面

  1. WeatherStation

    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
    package com.company.entity;

    import com.company.inter.IObserver;
    import com.company.inter.ISubject;

    import java.util.ArrayList;
    import java.util.List;

    public class WeatherStation implements ISubject {

    //被注册进来的所有观察者
    List<IObserver> observers = new ArrayList<>();

    //温度变量 当温度变量发生变化时 通知所有观察者
    Integer temperature = 18;

    //将观察者注册进来 其中就是用list add进来
    @Override
    public void add(IObserver iObserver) {
    this.observers.add(iObserver);
    }

    @Override
    public void remove(IObserver iObserver) {
    observers.remove(iObserver);
    }

    //在温度变量发生变化后 每一个观察者通过update方法来得到最新数据
    @Override
    public void notifyObserver() {
    //通知每一个观察者
    for(IObserver iObserver: this.observers){
    iObserver.update();
    }
    }

    public Integer getTemperature(){
    return this.temperature;
    }

    //模拟实现数据变化
    public void setTemperature(Integer temperature){
    this.temperature = temperature;
    }
    }
  2. PhoneDisplay.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    package com.company.entity;

    import com.company.inter.IObserver;

    public class PhoneDisplay implements IObserver{

    WeatherStation weatherStation;

    //我们需要得到变化后的数据信息 因此需要通过构造方法 <绑定> 一个被观察者
    public PhoneDisplay(WeatherStation weatherStation){
    this.weatherStation = weatherStation;
    }

    //通过这个被观察者的public get方法 我们才可以知道最新的数据信息
    @Override
    public void update() {
    System.out.println("数据发生变化,最新温度为:" +
    this.weatherStation.getTemperature());
    }
    }
  3. TvDisplay.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    package com.company.entity;

    import com.company.inter.IObserver;

    public class TvDisplay implements IObserver{

    WeatherStation weatherStation;

    //我们需要得到变化后的数据信息 因此需要通过构造方法 <绑定> 一个被观察者
    public TvDisplay(WeatherStation weatherStation){
    this.weatherStation = weatherStation;
    }

    //通过这个被观察者的public get方法 我们才可以知道最新的数据信息
    @Override
    public void update() {
    System.out.println("数据发生变化,最新温度为:" +
    this.weatherStation.getTemperature());
    }
    }

主函数:

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
package com.company;

import com.company.entity.PhoneDisplay;
import com.company.entity.TvDisplay;
import com.company.entity.WeatherStation;

public class Main {

public static void main(String[] args) {
WeatherStation weatherStation = new WeatherStation();

//手机显示器实时绑定天气预报站台
PhoneDisplay phoneDisplay = new PhoneDisplay(weatherStation);
//电视机显示器实时绑定天气预报站台
TvDisplay tvDisplay = new TvDisplay(weatherStation);

//天气预报站台将手机显示器注册进来
weatherStation.add(phoneDisplay);
//天气预报站台将电视机显示器注册进来
weatherStation.add(tvDisplay);

System.out.println("天气正常,一直都是:" +
String.valueOf(weatherStation.getTemperature()) + "摄氏度.");


System.out.println("某一时刻,数据突然发生变化...");
weatherStation.setTemperature(-20);

System.out.println("天气预报站台准备广播消息!Broadcasting...");

weatherStation.notifyObserver();

System.out.print("手机显示器这边显示:");
phoneDisplay.update();

System.out.print("电视机显示器这边显示:");
tvDisplay.update();
}
}

效果图

在这里插入图片描述

设计模式学习_策略模式

策略者模式 Strategy Pattern

使用背景

  • 定义: 我们将算法Algorithm与使用者Client分离,让这些使用者自由地选择策略。

想象这样一个场景:

有一个基类Duck,它具有两个方法:void eat() 与 void fly()

eat代表吃 fly代表飞行

现在有两个subclass 子类:WildDuck 与 CityDuck

  1. 它们在eat的行为上一摸一样

  2. 它们在fly的行为上不一样

试想,如果我们用Java来实现:

整个UML图可能是这样:

在这里插入图片描述

其中,- 代表这是一个私有方法,且void代表它无返回值。

前面,我们提到:这两种鸭子的eat行为是一样的,而fly的行为不一样。

那么,在这种单重继承的情况下,如果eat行为一样,我们只能重复的复制粘贴代码。

  • 想象一下,当子类的数目在需求增大的过程中越来越多?这样的代码不具有高重用性与可维护性。

使用策略者模式解决问题

  1. 在上述问题中,eat与fly可以代表两种算法或者策略

  2. 使用者就是WildDuck以及CityDuck

我们希望对于不同的使用者,可以任意使用策略。

解决方案:

我们将同一种策略,封装成接口Interface,然后采用不同的实现策略(即不同的实现类)

同时,将这些接口,注入到我们的使用者中。

使用具体的方法时,我们使用这些使用者的所注入进来的实现类的具体方法。

直接上UML图:(图不规范,见谅。)

在这里插入图片描述

从这个图中,我们可以看到:

  1. Duck类具有两个接口:IEatBehavior与IFlyBehavior

  2. 每个接口都有若干个实现类(策略)

  3. 当子类WildDuck与CityDuck继承Duck时,具有父类的所有资源。

  • 那么如何用代码实现呢?

代码实现:

  1. 首先,我们要有一个基类 Duck.java以及它的子类 WildDuck.java与CityDuck.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    package com.company.entity;

    import com.company.inter.IEatBehavior;
    import com.company.inter.IFlyBehavior;

    public class Duck {
    IEatBehavior iEatBehavior;

    IFlyBehavior iFlyBehavior;

    public Duck(IEatBehavior iEatBehavior, IFlyBehavior iFlyBehavior) {
    this.iEatBehavior = iEatBehavior;
    this.iFlyBehavior = iFlyBehavior;
    }

    public void eat(){
    this.iEatBehavior.eat();
    }

    public void fly(){
    this.iFlyBehavior.fly();
    }

    }

可以看到,这里有两个接口,分别代表两种策略(Eat与Fly)

# WildDuck.java

1
2
3
4
5
6
7
8
9
10
11
package com.company.entity;

import com.company.inter.IEatBehavior;
import com.company.inter.IFlyBehavior;

public class WildDuck extends Duck{
public WildDuck(IEatBehavior iEatBehavior, IFlyBehavior iFlyBehavior) {
super(iEatBehavior, iFlyBehavior);
}
}

# CityDuck.java

1
2
3
4
5
6
7
8
9
10
11
package com.company.entity;

import com.company.inter.IEatBehavior;
import com.company.inter.IFlyBehavior;

public class CityDuck extends Duck{
public CityDuck(IEatBehavior iEatBehavior, IFlyBehavior iFlyBehavior) {
super(iEatBehavior, iFlyBehavior);
}
}

  1. 定义两种接口

# iEatBehavior.java

1
2
3
4
5
6
package com.company.inter;

public interface IEatBehavior {
public void eat();
}

# iFlyBehavior.java

1
2
3
4
5
6
package com.company.inter;

public interface IFlyBehavior {
public void fly();
}

  1. 定义这些接口的不同策略实现类

# SimpleEatBehaviorImpl.java

1
2
3
4
5
6
7
8
9
10
package com.company.impl;

import com.company.inter.IEatBehavior;

public class SimpleEatBehaviorImpl implements IEatBehavior {
@Override
public void eat() {
System.out.println("吃:采用正常吃饭的策略!");
}
}

# SimpleFlyBehaviorImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
package com.company.impl;

import com.company.inter.IFlyBehavior;

public class SimpleFlyBehaviorImpl implements IFlyBehavior {

@Override
public void fly() {
System.out.println("飞:采用正常飞的策略!");
}
}

# JetFlyBehaviorImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
package com.company.impl;

import com.company.inter.IFlyBehavior;

public class JetFlyBehaviorImpl implements IFlyBehavior {

@Override
public void fly() {
System.out.println("飞:采用喷气式飞行的策略!");
}
}

  • 那么目前为止,我们已经将这些策略与使用者分离了。

使用的时候,我们只需要让他们使用不同的策略即可。

# Main.java

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
package com.company;

import com.company.entity.CityDuck;
import com.company.entity.WildDuck;
import com.company.impl.JetFlyBehaviorImpl;
import com.company.impl.SimpleEatBehaviorImpl;
import com.company.impl.SimpleFlyBehaviorImpl;
import com.company.inter.IEatBehavior;
import com.company.inter.IFlyBehavior;

public class Main {

public static void main(String[] args) {

//对于城市Duck来说 Eat采用的是simpleEat策略 Fly也采用的是simpleFly策略
IEatBehavior iSimpleEatBehavior = new SimpleEatBehaviorImpl();
IFlyBehavior iSimpleFlyBehavior = new SimpleFlyBehaviorImpl();
CityDuck cityDuck = new CityDuck(iSimpleEatBehavior, iSimpleFlyBehavior);
System.out.println("城市鸭子采用的策略:");
cityDuck.eat();
cityDuck.fly();

//对于城市Duck来说 Eat采用的是simpleEat策略(直接重用) Fly采用的是jetFly策略
IFlyBehavior iJetFlyBehavior = new JetFlyBehaviorImpl();
WildDuck wildDuck = new WildDuck(iSimpleEatBehavior, iJetFlyBehavior);
System.out.println("接下来是野鸭子采用的策略:");
wildDuck.eat();
wildDuck.fly();

}
}

效果图:

在这里插入图片描述

VUE学习笔记-4-VUE小技巧

axios请求 包括Get请求与Post请求(注意是异步)

因为该方法是异步,因此无法准确知道元素的执行顺序。

有可能axios方法后面的方法会提前执行,也有可能会延后执行。

Get方法

1
2
3
4
5
6
7
8
9
var _this = this
var _data = this.$data
axios.get(main.requestAddress + '/book/showAllBooks', {
params: {'pn': _data.queryParams.currentPageNum}
}).then(function (response) {
//从后端返回给前端的内容(code、message、data...等等)
}).catch(function (error) {

});

params:{ ‘data’: data} 是指以键值对的方式 往后台传输数据

因此,后台需要用@RequestParam()标签

注意,在main.js中定义一个变量 var requestAddress=”192.xxx.xxx.xxx”

同时导出这个main.js

最后使用的时候记得 import main from ‘main.js的路径’

请务必注意!

使用axios请求时,如果需要用到在回调函数中用到该网页的内容。

必须在axios.get/post方法外 定义一个

1
var _this = this

注意外面的this与回调函数的this不一样!!!

Post方法

POST方法比较复杂,涉及到数据绑定

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
<el-form :rules="loginFormRules" ref="loginForm" 
:model="loginForm" label-position="right"
label-width="auto" show-message>

<span class="login-title">欢迎登录</span>
<div style="margin-top: 5px"></div>

<el-form-item label="用户名" prop="username">
<el-col :span="22">
<el-input type="text" v-model="loginForm.username"></el-input>
</el-col>
</el-form-item>

<el-form-item label="密码" prop="password">
<el-col :span="22">
<el-input type="password" v-model="loginForm.password"></el-input>
</el-col>
</el-form-item>

<el-form-item>
<el-button type="primary" @click="loginSubmit('loginForm')">登录</el-button>
<el-button type="primary" @click="dialogVisible=true">注册</el-button>
</el-form-item>

</el-form>

ref=”loginForm” 与 :model=”loginForm” 都是指绑定data中的loginForm

rules=”loginFormRules” 指的是绑定自定义规则

v-model=”loginForm.username”指的是 该值双向绑定loginForm里面的username属性因此,一定记得prop=loginForm.prop

点击登录按钮时,loginForm作为参数传进去!

1. loginForm里面的参数->对应的所有prop

写在return里面即可:

1
2
3
4
loginForm: {
username: '',
password: ''
}

2. loginForm对应的规则,也写在return里面

1
2
3
4
5
6
7
8
loginFormRules: {
username:[
{required: true, message: '账号不可为空', trigger: 'blur'}
],
password: [
{required: true, message: '密码不可为空', trigger: 'blur'}
]
}

require表示必须填,触发blur后,会提示message!

这样,在JS中,可以将该表单loginForm与它对应的参数输入规则绑定在一起验证!

同时也可以自定义验证规则!
在data(){这里} 中写

1
2
3
4
5
6
7
const validateUserName = (rule, value, callback) => {
if(value === 0){
callback(new Error('这里是错误提示'))
}else{
callback() //验证成功,通过自定义规则!
}
}

其中value是对应输入框的值!

callback用来回调

提示对方错误信息

还是

让对方通过

这样的话,可以在规则中,声明走自定义的规则方法:

1
2
3
4
5
loginFormRules: {
username:[
{required: true, validator: validateUserName,, trigger: 'blur'}
]
}

其他自定义规则 看官方文档

具体登录+验证方法如下:

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
loginSubmit(formName) {
let _this = this
// 为表单绑定验证功能
this.$refs[formName].validate((valid) => {
if (valid) {
// 使用 vue-router 路由到指定页面,该方式称之为编程式导航
this.$axios.post(main.requestAddress + `/user/login`, _this.loginForm)
.then(res => {
_this.$data.message = res.data.message
//若数据库查询失败 则返回重新登录信息
if (res.data.data === null) {
_this.openLoginFailMessage();
return;
}
sessionStorage.setItem("token", true);
sessionStorage.setItem("username", res.data.data.username);
_this.$router.push("/index");//路由跳转至 path=> /index 具体看自己的路由配置
})
.catch(Error => {
console.log(Error)
})
} else {
return false;
}
});
}

全局路由配置(基于session的前端权限)

1.首先搞清楚,除了登录、404..页面,其他的涉及到必须要先登录的业务数据页面都是要授权认证的!

2.授权认证,必须要经过成功登录

3.每次页面进行跳转都要经过权限认证过程!

配置路由,除了登录页面,其他都要认证

我们给相应涉及业务数据的页面,比如主页面index,加上需要拦截的头信息:

注意!登录页面是不需要加的!

1
2
3
4
5
6
7
8
{
meta: {
requireAuth: true //如果来该页面 需要验证
},
path: '/index',
name: '业务逻辑',
component: ()=>import('../views/ShowAll')
}

然后,在页面拦截时,我们需要判断要去的页面是否需要授权?

也就是requireAuth是否是’true’?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//添加全局路由拦截
router.beforeEach((to, from, next) => {
if (to.meta.requireAuth) {
//如果去的页面需要认证 那么就开始认证
if (sessionStorage.getItem("token") == 'true') {
next(); //放行
} else {
alert('请您登陆...');
//否则下一步也放行 但放行到登录页面
next({
path: '/login'
})
}
} else { //如果不需要授权 比如直接去登录页面
if (sessionStorage.getItem("token") == 'true' && to.path != '/login') {
next('/index')
} else {
next()
}
}
})
  1. 注意这里的sessionStorage.getItem(“token”)取出来的值是字符串类型

  2. 这里的 && to.path!=’/login’ 是用户从index主界面 到login登录页面 而不想已经验证但还是跳到index页面的情况

  3. 如果不指定

1
next({path: '/login'}) 

指定去登录页面(前提是登录页面的path是’/login’)

那么next()就代表允许去想要去的页面

  1. 因为每次我们要看sessionStorage.getItem(“token”)里面值是不是’true’。因此,我们需要在登录成功的时候!添加对应的token信息

登录成功添加可“允许通过”的路由信息(Token,Session,Cookies均可)

1
sessionStorage.setItem("token", true);

过滤路由显示菜单

如果在菜单中不想让某些通用模块出现(比如登录、注册、注销等功能)

可以在对应路由信息中,添加属性: hidden: true

  • 在页面中的el-submenu 以及el-submenu-item中 去for循环遍历时,加一个v-if=”!item.hidden”

注意,v-if 与 v-for 是不能同时出现在属性中 可以将v-for 提前一个标签!

Vue 项目中加入常用插件

加入插件

通常,我们通过vue/cli来创建相应的vue项目

1
vue create vue_demo
  • 注意,如果不想要VUE严格的eslint语法规则。

可以在创建的时候,取消勾选Fomatter。

若需要VUEX全局状态管理,可以勾选。

若需要路由router,则可以选择勾选。

…在经过漫长的cmd后

不用关闭当前git bash或者命令行控制工具Command

我们首先进入这个项目,直接利用npm配置一些插件。

1
2
3
4
cd vue_demo
vue add vuetify //下载vuetify移动自适应插件
vue add element //引入elementUI 并且在接下来选择时Fully import还是按需引入
vue add axios //将ajax封装好的axios

不出意外的话,在具体的项目目录中,可以看到多出一个文件夹plugins。

这里装着我们安装进去的插件。

在配置之后,我们可以用VS code来打开这个文件夹,并且

1
npm run serve

运行项目

VUE中添加Echarts可视化图表

npm 安装echarts插件

1
npm install echarts --save

如果npm的安装速度较慢,可以使用cnpm淘宝镜像

1
2
3
4
5
6

//安装淘宝镜像
npm install -g cnpm --registry=https://registry.npm.taobao.org

//从淘宝镜像下载
cnpm install echarts -S

当完成npm安装后,echarts插件出现在 node_modules 目录下

全局引入ECHARTS

main.js 文件

1
2
3
4
// 引入echarts
import * as echarts from 'echarts'

Vue.prototype.$echarts = echarts

初始化图表

1
<div  ref="chartColumn" style="{width: '300px', height: '300px'}"></div>
  1. ref属性是为了JS代码中能引用这个图表

  2. <div>属性之间默认隔行 如果想要多个图表在同一行中显示 添加属性:style="display: inline-block"

  3. 如果想要动态改变 图表的高度与宽度 可以利用VUE的动态绑定

JS画图动态渲染(柱状图与饼状图)

饼状图

将画图抽象成一个函数 drawLine() 这个函数放在mounted() 生命周期函数中

1
this.drawLine();

图标参数JSON数据 (定义在data中的return{})

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
optionCircle:{
title: {
text: '实时热门图书',
subtext: '站点动态统计',
left: 'center'
},
tooltip: {
trigger: 'item'
},
legend: {
orient: 'vertical',
left: 'left',
},
series: [
{
name: '访问来源',
type: 'pie',
radius: '50%',
data: [],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
}

正常情况下,是看不到任何数据的 但是能看到你的饼状图标题

接下来,我们要在data中从后台动态添加数据

这里我选择 AXIOS 异步刷新数据

JS drawLine() 函数

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
drawLine(){
// 基于准备好的dom,初始化echarts实例
//通过ref来找到图表
let myChartCircle = this.$echarts.init(this.$refs.chartCircle);

//动态从数据库中取数据
//画饼图
const _this = this
axios.get(
main.requestAddress +
'/book/showPopularBooks').then(function (response) {
let list = response.data.data
let data = _this.$data.optionCircle.series[0].data

for(let i =0; i<list.length; i++){
data.push({
name: list[i].bookname,
value: list[i].borrowNum
})
}
myChartCircle.setOption(_this.$data.optionCircle);
}).catch(function (error) {
alert(error);
});
}

可以看到,其实真正的饼状图数据是:

“name”代表饼图的名称 “value”代表对应name的饼图数据

值得注意的是,这种push方法是加入新的JSON对象的方法

1
2
3
4
5
data.push({
key1: value1(可以来自后台),
key2: value2(可以来自后台),
...
});

效果图如下:

在这里插入图片描述

柱状图

与饼状图相似,先是配置图标参数,渲染图表,最后是取数据

配置图表参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
optionColumn:{
title: {
text: '实时借书用户'
},
tooltip: {
},
legend: {
},
xAxis: {
data: []
},
yAxis: {},
series: [{
name: '借阅次数',
type: 'bar',
data: []
}]
}

渲染图表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let myChartColumn = this.$echarts.init(this.$refs.chartColumn)
//使用刚指定的配置项和数据显示图表。
//画柱状图
axios.get(
main.requestAddress +
'/book/showPopularUsers').then(function (response) {
let list = response.data.data
let nameArray = _this.$data.optionColumn.xAxis.data
let numArray = _this.$data.optionColumn.series[0].data
for(let i=0; i<list.length; i++){
nameArray.push(list[i].username);
numArray.push(list[i].totalBorrowNum);
}
myChartColumn.setOption(_this.$data.optionColumn);

}).catch(function (error) {
alert(error);
});

可以看到,对于柱状图,我们要配置两个

  1. xAxis 即X轴的数组中的数据

  2. series[0]中的data数组 代表从左往右依次的 棱柱所代表的value

效果图如下

在这里插入图片描述

VUEX 存值与取值区域

在VUE中,如果有些数据我们不太需要后台频繁的查询数据

我们就可以将这些数据存放在VUEX中

声明store的index.js

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
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

//导出之后 外部可以使用
export default new Vuex.Store({
state:{
//放全局变量 比如viewCounter是浏览量统计器
viewCounter = 1
},
mutations:{
//setter方法区域
setViewCounter(state, viewCounter){
state.viewCounter = viewCounter
}
},
getters:{
//getter方法区域
getViewCounter: state => state.viewCounter
},
action:{},
modules:{}
})

在需要存值、取值的地方 调用方法

取值getViewCounter

1
this.$store.getters.getViewCounter

存值setViewCounter方法

1
this.$store.commit('setViewCounter', newValue);

WEB项目实现文件下载与上传

SSM项目+非MultipartFile类

  • 在这种类型的项目中,我们需要在相应的Spring MVC.xml配置文件中,去配置一个Bean

代码如下:

1
2
3
4
5
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="104857600"/>
<property name="maxInMemorySize" value="4096"/>
<property name="defaultEncoding" value="UTF-8"></property>
</bean>

这种,我们就可以在Controller层中,使用@RequestParam(“file”) MultipartFile file了。

如果前端是Form表单的话,则前端代码如下:

1
2
3
4
5
<form class="form-horizontal " action="/UploadServlet" id="upload" enctype="multipart/form-data" method="post">
<input class="form-control" type="file">
<button type="submit" class="btn btn-success btn-sm">上传</button>
<div> ${ message } </div>
</form>

这样,当我们点击上传后,即会跳转到对应的action,我们用@PostMapping来接收即可。

后台代码如下:

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
67
68
69
70
71

// 上传文件存储目录
private static final String UPLOAD_DIRECTORY = "upload";

// 上传配置
private static final int MEMORY_THRESHOLD = 1024 * 1024 * 3; // 3MB
private static final int MAX_FILE_SIZE = 1024 * 1024 * 40; // 40MB
private static final int MAX_REQUEST_SIZE = 1024 * 1024 * 50; // 50MB

@RequestMapping(value = "/UploadServlet", method = RequestMethod.POST)
public String upload(
ModelAndView modelAndView,
HttpServletRequest request,
HttpServletResponse response) throws IOException, ServletException {


// 配置上传参数
DiskFileItemFactory factory = new DiskFileItemFactory();
// 设置内存临界值 - 超过后将产生临时文件并存储于临时目录中
factory.setSizeThreshold(MEMORY_THRESHOLD);
// 设置临时存储目录
factory.setRepository(new File(System.getProperty("java.io.tmpdir")));

ServletFileUpload upload = new ServletFileUpload(factory);

// 设置最大文件上传值
upload.setFileSizeMax(MAX_FILE_SIZE);

// 设置最大请求值 (包含文件和表单数据)
upload.setSizeMax(MAX_REQUEST_SIZE);

// 中文处理
upload.setHeaderEncoding("UTF-8");

// 构造临时路径来存储上传的文件
// 这个路径相对当前应用的目录
String uploadPath = request.getServletContext().getRealPath("./") + File.separator + UPLOAD_DIRECTORY;

// 如果目录不存在则创建
File uploadDir = new File(uploadPath);
if (!uploadDir.exists()) {
uploadDir.mkdir();
}

try {
List<FileItem> formItems = upload.parseRequest(request);
if (formItems != null && formItems.size() > 0) {
// 迭代表单数据
for (FileItem item : formItems) {
// 处理不在表单中的字段
if (!item.isFormField()) {
String fileName = new File(item.getName()).getName();
String filePath = uploadPath + File.separator + fileName;
File storeFile = new File(filePath);

// 在控制台输出文件的上传路径
String filePathSaved = uploadPath + File.separator;

// 保存文件到硬盘
item.write(storeFile);
teacherServer.saveFilePath(filePathSaved, fileName);
request.setAttribute("message", formItems.get(0).getName() + "文件上传成功!");
}
}
}
} catch (Exception ex) {
request.setAttribute("message", "错误信息: " + "文件上传失败,请检查是否文件为空!");

}
return "/teacher/tea_add_paper";
}

注意到return这里,我们需要配置相应的视图解析器,不然我们不知道return哪个地方去显示。

1
2
3
4
5
<!--配置试图解析-->
<bean class = "org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/"></property>
<property name="suffix" value=".jsp"></property>
</bean>

在这里插入图片描述
注意:webapp 就是我们配置的根目录,并且配置的视图后缀为.jsp

  • 这样我们就能在,相应的视图中,看到message的内容了

SpringBoot项目+JSP+MultipartFile(非AJAX异步)

首先,我们需要在SpringBoot的配置文件application.yml或者application.properties中配置:

application.properties

1
2
3
spring.servlet.multipart.max-request-size=10MB

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

然后我们,在controller中写相应语句:

  • 我们仍然需要配置视图解析器ViewResolver:

在application.properties中:

1
2
3
spring.mvc.view.prefix=/

spring.mvc.view.suffix=.jsp

仍与上方图片中的项目结构图一致!

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

private static final String UPLOAD_DIRECTORY = "upload";

@PostMapping("/upload")
@ResponseBody
public String upload(@RequestParam("file") MultipartFile file, HttpServletRequest request) {
if (file.isEmpty()) {
request.setAttribute("message", "对不起,检测到文件为空!");
return "/teacher/tea_add_paper";
}

String fileName = file.getOriginalFilename();
String templatePath = request.getServletContext().getRealPath("./") + File.separator + UPLOAD_DIRECTORY;

File dest = new File(filePath + fileName);
try {
file.transferTo(dest);
request.setAttribute("message", "文件上传成功!");
return "/teacher/tea_add_paper";
} catch (IOException e) {
System.out.println(e)
}

request.setAttribute("message", "文件上传失败!");
return "/teacher/tea_add_paper";
}

接下来,就准备写前端页面请求:

1
2
3
4
5
6
7
8
<form method="post" action="/uploading" enctype="multipart/form-data">
<input type="file" name="file">
<br/>
<input type="submit" value="提交给服务器">
<div>
${message}
</div>
</form>

SpringBoot+VUE+ElementUI前后端分离

前提是解决跨域问题,并且直接通过IP地址访问,将这个访问的ip地址存在VUEX中或者Main.js中即可。

1. VUE前端页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<el-upload
class="uploadDemo"
ref="upload"
accept=".doc"
action="https://jsonplaceholder.typicode.com/posts/"
:on-preview="handlePreview"
:on-remove="handleRemove"
:http-request="httpRequest"
:auto-upload="false"
>
<el-button slot="trigger" size="small" type="primary"
>选取文件</el-button
>
<el-button
style="margin-left: 10px"
size="small"
type="success"
@click="submitUpload"
>上传到服务器</el-button
>
<div slot="tip" class="el-upload__tip">
只能上传.doc后缀文件,且不超过40MB
</div>
</el-upload>
  1. 写两个函数

一个是按钮点击函数,另一个是上传文件的表单具体提交(axios请求)的函数

  • 提交按钮函数
    1
    2
    3
    submitUpload() {
    this.$refs.upload.submit(); //我们通过el-upload的引用来找到 并让他提交
    },

注意,我们这里看到他的action位置,并不是我们的后台地址。

也就是说,我们通过先把自己的文件传给他的action,再由他的action服务器地址,传给我们的后台服务器。

最后,提交表单后,会走绑定属性httpRequest的方法,即:

  • 表单提交函数
    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
    //最后上传走这个方法!
    httpRequest(param) {
    let fileObj = param.file; // 相当于input里取得的files
    let data = new FormData(); // FormData 对象
    data.append("file", fileObj); // 文件对象 后台用@RequestParam来接收
    const config = { headers: { "Content-Type": "multipart/form-data" } };

    var _this = this;
    //在这里我将后台的服务器地址 存进了VUEX中
    //并通过相应的getters 来获取
    this.$axios
    .post(
    this.$store.getters.getRequestAddress + `/uploading`,
    data,
    config
    )
    .then((res) => {
    let data = res.data;
    console.log(data);
    if (data.data === 1) {
    _this.sendMessage(data.message, "success");
    } else {
    _this.sendMessage(data.message, "error");
    }
    })
    .catch((Error) => {
    _this.sendMessage("请求失败!", "error");
    });
    },

发送消息的方法,记得引入ElementUI

1
2
3
4
5
6
7
//弹窗发送消息 以传入的参数而定
sendMessage(messageContent, messageType) {
this.$message({
message: messageContent,
type: messageType,
});
},

然后就是后台接收(如果是SSM项目,在SpringMVC配置文件中配置Bean

如果是SpringBoot项目,记得在application.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
//elementUI 上传方法

private static final String UPLOAD_DIRECTORY = "upload";

@PostMapping(value = "/uploading")
@ResponseBody
public TSMResult uploadFile(@RequestParam("file") MultipartFile file, HttpServletRequest request, TSMResult tsmResult) {

if (file.isEmpty()) {
tsmResult.setData(0);
tsmResult.setMessage("文件不可为空!");
return tsmResult;
}
// 获取文件全名a.py
String fileName = file.getOriginalFilename();
// 文件上传路径<br> String templatePath = "E:/file/template/"
String templatePath = request.getServletContext().getRealPath("./") + File.separator + UPLOAD_DIRECTORY;

System.out.println("文件路径:" + templatePath);

// 获取文件的后缀名
String suffixName = fileName.substring(fileName.lastIndexOf("."));

//获取文件名
String prefixName = fileName.substring(0, fileName.lastIndexOf("."));
// 解决中文问题,liunx 下中文路径,图片显示问题
//fileName = UUID.randomUUID() + suffixName;
File dest0 = new File(templatePath);
//prefixName + File.separator +
File dest = new File(dest0, fileName);


//文件上传-覆盖
try {
// 检测是否存在目录
if (!dest0.getParentFile().exists()) {
dest0.getParentFile().mkdirs();
//检测文件是否存在
}
if (!dest.exists()) {
dest.mkdirs();
}
file.transferTo(dest);

//上传成功后 将上传路径上传到数据库中
String filePathSaved = templatePath + File.separator;
teacherServer.saveFilePath(filePathSaved, fileName);

tsmResult.setData(1);
tsmResult.setMessage("文件上传成功!");
return tsmResult;
} catch (Exception e) {
tsmResult.setData(0);
tsmResult.setMessage("服务器错误,请稍后再试!");
return tsmResult;
}
}
1
teacherServer.saveFilePath(filePathSaved, fileName);

这个代表,我们将这个上传的文件名字,以及对应的上传路径,写进数据库中

对应服务层代码:

1
2
3
4
public int saveFilePath(String filePathSaved, String fileName) {

return sourceMapper.saveFilePath(filePathSaved, fileName);
}

对应mapper代码:

1
2
3
@Insert("insert into source(source, papername) values (#{filePath},#{fileName})")
int saveFilePath(@Param("filePath") String filePath, @Param("fileName") String fileName);

可以建立一个Source实体类,引入Lombok

1
2
3
private Integer id;
private String filePath;
private String fileName;

并且数据库也建立一个source表,其中字段名与这个Source实体类的属性名一模一样(避免Mybatis的字段映射ResultMap)

实现文件下载

# 在实现文件的上传后,我们肯定会有相应的下载功能。

即从上传目录中的文件中,去下载对应的文件。

  • 需求:我们将文件的信息以及路径,存进数据库了,也就代表:

  • 我们“只能下载那些已经保存在数据库中的文件”

JSP实现文件下载(且动态检测文件是否可下载)

  1. 前端页面动态生成一个锚点(Bootstrap可加上按钮样式类)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 1. 利用Ajax异步获取到分好页的论文(PageHelper插件)
    var list = result.data.list

    // 2. 利用$.each来遍历这些数据 对每个数据进行筛选
    $.each(list, function (index, item) {
    //3. 首先获取到即将下载的论文名(鉴于我们上传时 是将论文的文件名存进数据库的)
    var papername = item.papername

    //4. 为每一项“模拟按钮” 动态加上类 方便我们后期动态remove掉
    var download = $("<a></a>").addClass(papername + " btn btn-danger btn-sm").append($("<span></span>").addClass("glyphicon glyphicon-download")).append("下载").attr("href", "/DownloadServlet?paperName=" + item.papername);
    });

注意到,我们这里的路径是:/DownloadServlet?paperName=xxx

因此,我们肯定是以get的请求方式

  1. 后台controller层代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19

    private static final String UPLOAD_DIRECTORY = "upload";

    @RequestMapping(value = "/DownloadServlet")
    public ResponseEntity<byte[]> fileDownload(
    HttpServletRequest request,
    @RequestParam("paperName") String fileName,
    Model model) throws Exception {
    fileName = new String(fileName.getBytes("ISO-8859-1"), "UTF-8"); //后台接受中文数据编码
    fileName = fileName + ".doc";

    String filePath = request.getServletContext().getRealPath("./") + File.separator + UPLOAD_DIRECTORY + "\\" + fileName;
    File file = new File(filePath);
    HttpHeaders headers = new HttpHeaders();
    String downloadFile = new String(fileName.getBytes("utf-8"), "iso-8859-1");
    headers.setContentDispositionFormData("attachment", downloadFile);
    headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
    return new ResponseEntity<byte[]>(FileUtils.readFileToByteArray(file), headers, HttpStatus.CREATED);
    }

其实,上述代码是不够完整的,因为如果数据库中没有这个以.doc为后缀(后缀写死了)的文件,就会报错。

因此,我们需要在前端下载之前就进行一次查询:

如果该文件的文件名存到数据库里了,才给予下载(即才动态添加下载的锚点样式)

  • 完整代码:写在$.each遍历中:
    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

    // 1. 利用Ajax异步获取到分好页的论文(PageHelper插件)
    var list = result.data.list

    // 2. 利用$.each来遍历这些数据 对每个数据进行筛选
    $.each(list, function (index, item) {
    //3. 首先获取到即将下载的论文名(鉴于我们上传时 是将论文的文件名存进数据库的)
    var papername = item.papername

    //4. 为每一项“模拟按钮” 动态加上类 方便我们后期动态remove掉
    var download = $("<a></a>").addClass(papername + " btn btn-danger btn-sm").append($("<span></span>").addClass("glyphicon glyphicon-download")).append("下载").attr("href", "/DownloadServlet?paperName=" + item.papername);

    //5. 查询该文件是否可下载
    $.ajax({
    url: "isExistsFilename",
    data: {"paperName": item.papername},
    type: "GET",
    dataType: "JSON",
    success: function (result) {
    //数据库存储了文件信息的论文 才可以下载
    //如果为false表示不可下载 则根据对应的papername来remove样式
    if(result.data == false){
    $("." + item.papername).remove();
    }
    },
    error: function (error) {
    console.log(error)
    }
    });
    });

查询的控制层代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RequestMapping(value = "/isExistsFilename", method = RequestMethod.GET)
@ResponseBody
public TSMResult isExistsFilename(
@RequestParam("paperName") String paperName) {
//处理中文
Boolean isExists = false;
try {
paperName = new String(paperName.getBytes("ISO-8859-1"), "UTF-8");
paperName = paperName + ".doc";
Source source = adminServer.isExistsPaperFile(paperName);
isExists = (source == null) ? false : true;
} catch (Exception e) {
System.out.println("检测到异常!");
e.printStackTrace();
}
return new TSMResult(200, "查询成功", isExists);
}

从代码中的三目运算符可以看出:

1
isExists = (source == null) ? false : true;
  • 如果这个文件为空则会true,否则为false

  • 也就是说,如果文件不为空,则为false,表示不可下载

服务层代码:

1
2
3
public Source isExistsPaperFile(String paperName) {
return sourceMapper.selectPaperFile(paperName);
}

具体Source类看上方代码即可。

Mapper代码:

1
2
@Select("select* from source where papername = #{paperName}")
Source selectPaperFile(@Param("paperName") String paperName);

实现通用JS代码全局引用

ElementUI的Message消息提示、Notify通知等等

我们希望在弹出消息或者通知(Notify)时

  • 根据我们自定义的参数,来相应提示用户
  1. 提示用户更改信息成功

  2. 这个提示类型是success类型

只需调用:

1
this.$sendMessage('这是消息内容', 'info/success/error/warning')

info 是灰色 success是绿色 error是红色 warning是黄色

创建通用功能的utils.js

  • 定义多个function 最后导出即可

注意,这里有一个知识点:对于默认导出的函数,使用时函数名外不需要{xxx}

即直接: import XXX from ‘xxx/xxx’

而如果有多个函数,想要按需引入的话,需要加上{}

按需引入(包括默认导出函数):

1
import { fun1, fun2 } from 'xxx/xxx'
1
2
3
4
5
6
7
8
9
10
11
12
13
//弹窗发送消息 以传入的参数而定
export function sendMessage(messageContent, messageType) {
this.$message({
message: messageContent,
type: messageType,
});
}

export function alertTest() {
alert('这是测试方法!')
}

export default sendMessage

main.js中引入并使用

1
2
3
import { sendMessage,loadingAnimation } from './assets/js/utils'
Vue.prototype.$sendMessage = sendMessage
Vue.prototype.$alertTest = alertTest

这样我们就可以,直接在vue项目中:使用this.$sendMessage来使用相应方法了。

HTML中引入VUE并使用ElementUI样式

head标签引入ElementUI的CSS、JS以及VUE的JS

1
2
3
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/element-ui/lib/index.js"></script>

初始VUE(2.0版本)

  1. 页面非body标签 标记
1
2
3
<div id="app"> 

</div>
  1. vue挂载上去(可以使用template写HTML页面 如果已经有内容了就去掉template
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    new Vue({
    el: '#app',
    data() {
    return {

    }
    },
    methods: {

    },
    created() {

    },
    mounted() {

    }
    })