Spring Boot Security 基本使用一

前言

众所周知,如果要对Web资源进行保护,最好的办法莫过于Filter,要想对方法调用进行保护,最好的办法莫过于AOP。Spring Security核心是一系列Filter,对资源进行过滤,这篇文档写的蛮不错的: https://my.oschina.net/u/865921/blog/159849.
Spring Security包括两块内容,Authentication(认证)和 Authorization(授权,也叫访问控制)。简单来说,认证即你是谁,授权即你有用什么权限。”认证”是认证主体的过程,通常是指可以在应用程序中执行操作的用户、设备或其它系统。”授权”是指是否允许已认证的主体执行某一项操作。

Spring Boot Security 和Spring Secutrity的关系

在Spring Security框架中,主要包含两个jar,即spring-security-web依赖和spring-security-config依赖。代码如下:

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
    <version>4.2.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>4.2.2.RELEASE</version>
</dependency>

SpringBoot对Spring Secutiry做了封装,仅仅是做了封装,并没有改动什么东西。需要引入spring-boot-starter-security起步依赖:

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

简单的权限控制

新建一个springboot项目,只要在pom文件里引入spring-boot-starter-security起步依赖,就使用默认的配置自动开启了安全校验功能。启动项目时,会生成一个默认的用户(user)和密码,密码会打印在命令行里。访问项目时,会先跳出一个登陆页面。

如果想要自定义用户名和密码,可以采用下面配置:spring.security.user.name 和spring.security.user.password.
官方文档参考
默认密码

进一步探究

初步体验了spring boot security,框架已经做了大量的工作,设置什么都不需要做,仅仅简单的把依赖引入进来,像是打开了一个开关,就开启了安全校验功能.然而实际实际开发中应用场景要比这复杂的多.例如需要能够创建用户和权限,要求程序能够从数据库中读取用户,完成认证和访问控制.就需要做一些适应性改造.在这个过程中,仅仅为了使用的话,把握几个核心问题,背后的原理可以日后探究.
第一个问题:框架是如何从数据库里拿到用户信息?
第二个问题:框架是如何拿到用户访问的api所对应的角色的?
第三个问题:框架是如何判断登录用户的角色有没有权限访问这个api的呢?

先看一张图,了解权限控制的整个流程:

流程图

准备工作

使用mysql数据库,使用jpa做持久层交互。不知道如何配置的可以参考我的这篇博文:地址
需要创建5张表用于存储用户(user),角色(role),权限(permission),用户角色关系表(user_role),角色权限关系表(role_permission).
表结构:
表结构

  • 实体类父类:
    @SuppressWarnings("serial")
    @MappedSuperclass
    @Data
    public abstract class AbstractEntity implements Serializable {
      @Id
      @GeneratedValue(strategy = GenerationType.IDENTITY)
      private Long id;
     
      @Column(name = "created_date", columnDefinition = "datetime")
      @CreatedDate
      private Date createdDate;
     
      @Column(name = "update_date", columnDefinition = "datetime")
      @LastModifiedDate
      private Date updateDate;
    
  • 创建用户类:
    @SuppressWarnings("serial")
    @Entity
    @Data
    @EqualsAndHashCode(callSuper = false)
    @NoArgsConstructor
    @AllArgsConstructor
    @Table(name = "user")
    @EntityListeners(AuditingEntityListener.class)
    public class User extends AbstractEntity {
     
      @NotNull
      @Column(name = "name", columnDefinition = "varchar(60) not null comment 'name'")
      private String name;
     
      @Column(name = "age", columnDefinition = "tinyint(1) not null comment 'age'")
      private Integer age;
     
      @Column(name = "password", columnDefinition = "varchar(60) not null comment 'password'")
      private String password;
     
      @NotNull
      @Column(name = "phone_number", columnDefinition = "varchar(11) not null comment 'phone_number'", unique = true)
      private String phoneNumber;
    	```
    
  • 创建角色类
    @SuppressWarnings("serial")
    @Entity
    @Data
    @EqualsAndHashCode(callSuper = false)
    @NoArgsConstructor
    @AllArgsConstructor
    @Table(name = "role")
    @EntityListeners(AuditingEntityListener.class)
    public class Role extends AbstractEntity {
     
      @NotNull
      @Column(name = "name", columnDefinition = "varchar(100) not null comment 'name'")
      private String name;
     }
    
  • 创建权限类
    @SuppressWarnings("serial")
    @Entity
    @Data
    @EqualsAndHashCode(callSuper = false)
    @NoArgsConstructor
    @AllArgsConstructor
    @Table(name = "permission")
    @EntityListeners(AuditingEntityListener.class)
    public class Permission extends AbstractEntity {
     
      @NotNull
      @Column(name = "name", columnDefinition = "varchar(100) not null comment 'name'")
      private String name;
     
      @Column(name = "url", columnDefinition = "varchar(255) not null comment 'resource path'")
      private String url;
     
      @Column(name = "pid", columnDefinition = "int not null comment 'pid'")
      private Integer pid;
    }
    
  • 创建用户角色表
    @SuppressWarnings("serial")
    @Entity
    @Data
    @EqualsAndHashCode(callSuper = false)
    @NoArgsConstructor
    @AllArgsConstructor
    @Table(name = "user_role")
    @EntityListeners(AuditingEntityListener.class)
    public class UserRole extends AbstractEntity {
     
      @NotNull
      @Column(name = "user_id", columnDefinition = "int not null comment 'user id'")
      private Long userId;
     
      @NotNull
      @Column(name = "role_id", columnDefinition = "int not null comment 'role id'")
      private Long roleId;
    }
    
  • 创建角色权限表
    @SuppressWarnings("serial")
    @Entity
    @Data
    @EqualsAndHashCode(callSuper = false)
    @NoArgsConstructor
    @AllArgsConstructor
    @Table(name = "role_permission")
    @EntityListeners(AuditingEntityListener.class)
    public class RolePermission extends AbstractEntity {
     
      @NotNull
      @Column(name = "role_id", columnDefinition = "int not null comment 'role id'")
      private Long roleId;
     
      @NotNull
      @Column(name = "permission_id", columnDefinition = "int not null comment 'permission id'")
      private Long permissionId;
    }
    
  • UserDetails接口是实现Spring Security的核心接口。getUsername是UserDetails里的方法,也可以返回别的信息,如邮箱,手机号码等。getAuthorities返回权限信息。有些人习惯直接使User实体类实现UserDetails接口,这样在这个类里面不仅要写users表的属性,还要重写UserDetails的方法,耦合度较高。User实体类原本只用来与数据库形成ORM映射,现在却要为框架提供其他功能。新建一个UserDto类,实现UserDetails接口
    @SuppressWarnings("serial")
    @Data
    @EqualsAndHashCode(callSuper = false)
    @NoArgsConstructor
    @AllArgsConstructor
    public class UserDto extends DeepflowAbstractDto implements UserDetails {
     
      private String name;
     
      private String password;
     
      private Integer age;
     
      private String phoneNumber;
     
      private List<RoleDto> authorities;
     
      @Override
      public Collection<RoleDto> getAuthorities() {
        return authorities;
      }
     
      @Override
      public String getPassword() {
        return password;
      }
     
      @Override
      public String getUsername() {
        return name;
      }
     
      @Override
      public boolean isAccountNonExpired() {
        return true;
      }
     
      @Override
      public boolean isAccountNonLocked() {
        return true;
      }
     
      @Override
      public boolean isCredentialsNonExpired() {
        return true;
      }
     
      @Override
      public boolean isEnabled() {
        return true;
      }
    }
    
  • 同样的原因,创建一个RoleDto实现GrantedAuthority接口
    @SuppressWarnings("serial")
    @Data
    @EqualsAndHashCode(callSuper = false)
    @NoArgsConstructor
    @AllArgsConstructor
    public class RoleDto extends DeepflowAbstractDto implements GrantedAuthority {
     
      private String name;
     
      @Override
      public String getAuthority() {
        return name;
      }
     }
    
  • 及创建对这些表进行CRUR操作的repository,service类
  • 创建登录的controller 和 访问用户的api
    @RestController
    public class MainController {
     
    	  @GetMapping("/")
    	  public ModelAndView root() {
    	    return new ModelAndView("index");
    	  }
    	 
    	  @GetMapping("/index")
    	  public ModelAndView index() {
    	    return new ModelAndView("index");
    	  }
    	 
    	  @RequestMapping(value = "/login", method = { RequestMethod.GET, RequestMethod.POST })
    	  public ModelAndView login(HttpServletRequest request) {
    	    return new ModelAndView("login");
    	  }
    	 
    	  @PostMapping("/login-error")
    	  public ModelAndView loginError(Model model) {
    	    ModelAndView modelAndView = new ModelAndView("login");
    	    modelAndView.addObject("loginError", true);
    	    return modelAndView;
    	  }
    	 
    	  @GetMapping("/401")
    	  public ModelAndView accessDenied() {
    	    return new ModelAndView("401");
    	  }
      }
    
    @RestController
    @RequestMapping("/api/users")
    public class UserController {
    
      @Inject
      private UserService userService;
    
      @GetMapping("/{id}")
      public UserDto getUser(@PathVariable Long id) {
        return userService.findOne(id);
      }
    
      @GetMapping("/phonenumber")
      public List<UserDto> getUser(@RequestParam("phonenumber") List<String> phoneNumber) {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        System.out.print(auth);
        return userService.findUserByPhoneNumbers(phoneNumber);
      }
    
      @PostMapping()
      public UserDto create(@RequestBody UserDto userDto) {
        return userService.create(userDto);
      }
    
      @PutMapping()
      public UserDto update(@RequestBody UserDto userDto) {
        return userService.update(userDto);
      }
    
      @GetMapping("/list")
      public List<UserDto> findAll(@RequestParam(name = "search", required = false) String search, Pageable pageable) {
        return userService.search(search, pageable);
      }
    }
    
  • 创建处理页面
    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>登录</title>
    </head>
    <body>
        <p th:if="${loginError}" class="error">用户名或密码错误</p>
        <form th:action="@{/login}" method="post">
            <label for="username">用户名</label>:
            <input type="text" id="username" name="username" autofocus="autofocus" />
            <br/>
            <label for="password">密 码</label>:
            <input type="password" id="password" name="password" />
            <br/>
            <input type="submit" value="登录" />
        </form>
        <p><a href="/index" th:href="@{/index}"></a></p>
    </body>
    </html>
    
  • 401.html
    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>401 page</title>
    </head>
    <body>
        <div>
            <div>
                <h2>权限不够</h2>
                <p>拒绝访问!</p>
            </div>
        </div>
    </body>
    </html>
    
  • 启动项目后,需要往数据库里插入一些测试数据
    INSERT INTO user (id, name, password, age, phone_number) VALUES (1,'user','e10adc3949ba59abbe56e057f20f883e', 20, "15026773423"); 
    INSERT INTO user (id, name, password, age, phone_number) VALUES (2,'admin','e10adc3949ba59abbe56e057f20f883e', 21, "15026772323"); 
    INSERT INTO role (id, name) VALUES (1,'USER');
    INSERT INTO role (id, name) VALUES (2,'ADMIN');
    INSERT INTO permission (id, url, name, pid) VALUES (1,'/api/users/phonenumber','phonenumber',0);
    INSERT INTO permission (id, url, name, pid) VALUES (2,'/api/users','admin',0);
    INSERT INTO permission (id, url, name, pid) VALUES (3,'/api/users/list','admin',0);
    INSERT INTO user_role (user_id, role_id) VALUES (1, 1);
    INSERT INTO user_role (user_id, role_id) VALUES (2, 1);
    INSERT INTO user_role (user_id, role_id) VALUES (2, 2);
    INSERT INTO role_permission (role_id, permission_id) VALUES (1, 1);
    INSERT INTO role_permission (role_id, permission_id) VALUES (2, 1);
    INSERT INTO role_permission (role_id, permission_id) VALUES (2, 2);
    INSERT INTO role_permission (role_id, permission_id) VALUES (2, 3);
    

编写权限代码(解决三个问题)

编写权限代码需要解决几个问题:

第一个问题:框架是如何从数据库里拿到用户信息?

----重写UserDetailsService接口,重写loadUserByUsername类,这个类的作用是什么呢?这部分是由自己写代码拿到用户信息再返回给框架的。根据用户输入的用户名查找出该用户,并且根据用户id,查找出用户绑定的角色。
```
@Component
public class DeepflowUserDetailsService implements UserDetailsService {

  @Inject
  private UserService userService;

  @Inject
  private UserRoleService userRoleService;

  @Inject
  private RoleService roleService;

  @Override
  public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
    UserDto userDto = userService.findUserByName(userName);
    if (null != userDto) {
      List<Long> roleIds = userRoleService.findByUserId(userDto.getId())//
          .stream()//
          .map(UserRoleDto::getRoleId)//
          .collect(Collectors.toList());
      List<RoleDto> roleDtos = roleService.findByIds(roleIds);
      userDto.setAuthorities(roleDtos);
    }

    return userDto;
  }
}
 ```

第二个问题:框架是如何拿到用户访问的api所对应的角色的?

----继承FilterInvocationSecurityMetadataSource接口,重写getAttributes方法 .
```
@Component
public class DeepflowSecuritySourceService implements FilterInvocationSecurityMetadataSource {

  @Inject
  private RolePermissionService rolePermissionService;

  @Inject
  private RoleService roleService;

  @Inject
  private PermissionService permissionService;

  /**
   * 每一个资源所需要的角色 Collection<ConfigAttribute>决策器会用到
   */
  private static HashMap<String, Collection<ConfigAttribute>> map = null;

  /**
   * 返回请求的资源需要的角色
   */
  @Override
  public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
    if (map == null) {
      loadResourceDefine();
    }
    // object 中包含用户请求的request 信息
    HttpServletRequest request = ((FilterInvocation) o).getHttpRequest();
    for (Iterator<String> it = map.keySet().iterator(); it.hasNext();) {
      String url = it.next();
      if (new AntPathRequestMatcher(url).matches(request)) {
        return map.get(url);
      }
    }

    return null;
  }

  @Override
  public Collection<ConfigAttribute> getAllConfigAttributes() {
    return null;
  }

  @Override
  public boolean supports(Class<?> aClass) {
    return true;
  }

  /**
   * 初始化 所有资源 对应的角色
   */
  public void loadResourceDefine() {
    map = new HashMap<>(16);
    // 权限资源 和 角色对应的表 也就是 角色权限 中间表
    rolePermissionService.findAll()//
        .stream()//
        .forEach(rolePermisson -> {
          // 某个资源 可以被哪些角色访问
          RoleDto roleDto = roleService.findOne(rolePermisson.getRoleId());
          PermissionDto permissionDto = permissionService.findOne(rolePermisson.getPermissionId());
          String url = permissionDto.getUrl();
          String roleName = roleDto.getName();
          ConfigAttribute role = new SecurityConfig(roleName);

          if (map.containsKey(url)) {
            map.get(url).add(role);
          } else {
            List<ConfigAttribute> list = new ArrayList<>();
            list.add(role);
            map.put(url, list);
          }
        });

  }
}

```

第三个问题:框架是如何判断登录用户的角色有没有权限访问这个api的呢?

–继承AccessDecisionManager接口,重写decide方法。这里可以自己去定义用户的角色是否有访问api的权限。如果没有权限,则跳转到401页面
```
@Component
public class DeepflowAccessDecisionManager implements AccessDecisionManager {

  /**
   * 通过传递的参数来决定用户是否有访问对应受保护对象的权限
   *
   * @param authentication
   *          包含了当前的用户信息,包括拥有的权限。这里的权限来源就是前面登录时UserDetailsService中设置的authorities。
   * @param object
   *          就是FilterInvocation对象,可以得到request等web资源
   * @param configAttributes
   *          configAttributes是本次访问需要的权限
   */
  @Override
  public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
      throws AccessDeniedException, InsufficientAuthenticationException {
    if (configAttributes == null || configAttributes.size() <= 0) {
      return;
    } else {
      String needRole;
      for (Iterator<ConfigAttribute> iter = configAttributes.iterator(); iter.hasNext();) {
        needRole = iter.next().getAttribute();

        for (GrantedAuthority ga : authentication.getAuthorities()) {
          if (needRole.trim().equals(ga.getAuthority().trim())) {
            return;
          }
        }
      }
      throw new AccessDeniedException("当前访问没有权限");
    }

  }

  /**
   * 表示此AccessDecisionManager是否能够处理传递的ConfigAttribute呈现的授权请求
   */
  @Override
  public boolean supports(ConfigAttribute configAttribute) {
    return true;
  }

  /**
   * 表示当前AccessDecisionManager实现是否能够为指定的安全对象(方法调用或Web请求)提供访问控制决策
   */
  @Override
  public boolean supports(Class<?> aClass) {
    return true;
  }
}

```
  • 面自定义的权限验证策略如何起作用呢?
    ----继承AbstractSecurityInterceptor类
    @Component
    public class DeepflowFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
    
     @Autowired
     private FilterInvocationSecurityMetadataSource securityMetadataSource;
    
     @Autowired
     public void setDeepflowAccessDecisionManager(DeepflowAccessDecisionManager deepflowAccessDecisionManager) {
       super.setAccessDecisionManager(deepflowAccessDecisionManager);
     }
    
     @Override
     public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
         throws IOException, ServletException {
    
       FilterInvocation fi = new FilterInvocation(servletRequest, servletResponse, filterChain);
       invoke(fi);
     }
    
     public void invoke(FilterInvocation fi) throws IOException, ServletException {
    
       InterceptorStatusToken token = super.beforeInvocation(fi);
       try {
         // 执行下一个拦截器
         fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
       } finally {
         super.afterInvocation(token, null);
       }
     }
    
     @Override
     public Class<?> getSecureObjectClass() {
       return FilterInvocation.class;
     }
    
     @Override
     public SecurityMetadataSource obtainSecurityMetadataSource() {
    
       return this.securityMetadataSource;
     }
    
    }
    
    
  • 核心配置(SecurityConfig):自定义一些web security 安全设置
    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
      @Inject
      private DeepflowUserDetailsService userService;
    
      @Inject
      public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    
        // 校验用户
        auth.userDetailsService(userService).passwordEncoder(new PasswordEncoder() {
    
          // 对密码进行加密
          @Override
          public String encode(CharSequence charSequence) {
            System.out.println(charSequence.toString());
            return DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
          }
    
          // 对密码进行判断匹配
          @Override
          public boolean matches(CharSequence charSequence, String s) {
            String encode = DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
            boolean res = s.equals(encode);
            return res;
          }
        });
    
      }
    
      @Override
      protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            // 匹配的这些资源允许所有人访问
            .antMatchers("/login", "/login-error", "/401", "/css/**", "/js/**").permitAll()
            .antMatchers("/api/roles/**").hasAnyRole("ADMIN")
            .anyRequest().authenticated() // 剩下所有的请求,都要进行授权访问校验
            .and()
            .formLogin()//
            .loginPage("/login") // 指令表单登录页面,否则使用默认的
            .failureUrl("/login-error") // 同上
            .and()//
            .exceptionHandling()//
            .accessDeniedPage("/401")//
            .and()//
            .logout()//
            .logoutUrl("/logout")//
            .logoutSuccessUrl("/login");
      }
    
    }
    
    

测试

项目启起来后,可以使用admin/123456 , user/123456,分别访问:http://localhost:8080/api/users/list和http://localhost:8080/api/users/phonenumber?phonenumber=15026773462效果

附:
参考文档: link (https://my.oschina.net/u/865921/blog/159849)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
《Vue和SpringBoot打造假日旅社管理系统》课程,将讲解如何使用Vue和SpringBoot开发这个项目,手把手演示开发流程!附赠源码、文档、数据库脚本等全部资料,提供售后答疑。 课程简介本课程讲解的是《基于 Vue 和 SpringBoot 的假日旅社管理系统》,该系统支持民宿档案、民宿新闻、民宿预定、民宿评论这四大核心业务,适用于乡村民宿企业的民宿预定业务。系统给每个民宿档案提供一个唯一标识,对民宿预定、评论等各个环节进行快速批量的数据采集,确保游客及时掌握景区民宿的真实数据,方便游客进行民宿预定。另外系统还包括员工管理、组织机构管理、文件管理、权限管理功能,给旅社企业提供更个性化的民宿管理模式。假日旅社管理系统采用了基于角色的访问控制,角色和菜单关联,一个角色可以配置多个菜单权限;然后再将用户和角色关联,一位用户可以赋予多个角色。这样用户就可以根据角色拿到该有的菜单权限,更方便旅社企业的管理人员进行权限管控。   软件技术选型前端Vue:Vue 是构建前端界面的核心框架,本系统采用 2.6.14 版本。View UI:基于 Vue.js2.0 的组件库,本系统采用 4.7.0 版本。后端Spring Boot:构建系统核心逻辑的后端框架,本系统采用 2.7.0 版本。MyBatis / MyBatis Plus:后端连接数据库的框架,本系统采用 3.5.2 版本。数据库MySQL:本项目的主数据库,本系统采用 8.0.29 版本。Redis:本系统采用基于 Windows 版本的 Redis,用于图形验证码和用户菜单权限的临时存储,采用了 5.0.14 版本。开发环境VsCode:项目前端的开发工具,使用版本为 1.68.0。IntelliJ IDEA :项目后端的开发工具,使用版本为 2021.3.2。Jdk:Java 的开发环境,使用版本为 17.0.3.1。Maven:后端项目的打包工具,使用版本为 3.6.2。NodeJs:前端项目的开发环境,使用版本为 16.13.0。 软件架构分析基于 Vue 和 SpringBoot 的假日旅社管理系统包括了系统基础模块、民宿档案模块、民宿新闻模块、民宿预定模块、民宿评论模块这五大功能模块,其架构如下图所示。  接下来,分别对五大模块进行详细介绍。系统基础模块系统基础模块,是用于支撑假日旅社管理系统的正常运行,这个模块包括了登陆注册模块、员工部门管理、菜单权限管理等。假日旅社管理系统支持用户使用账号、密码和图形验证码登陆,操作界面如下图所示。  假日旅社管理系统支持用户使用手机号、姓名、密码和图形验证码注册,操作界面如下图所示。 用户成功进入系统后,可进入到基于 Vue 和 SpringBoot 的假日旅社管理系统的首页,首页展示了当前登陆的地址、现在的时间和用户配置的常用模块,如下图所示。 接着用户点击左侧的用户管理,即可进入用户管理模块,用户管理模块的首页如下图所示。 用户可以在这个模块对系统登陆用户的档案进行维护,包括添加新用户、删除用户、编辑用户、根据姓名/部门查询用户。用户可以进入部门管理模块,管理旅社的部门数据,如下图所示。 同理用户可以进入到菜单管理模块,对系统的菜单进行管理,菜单管理模块的操作界面如下图所示。 民宿档案模块第二个模块是民宿档案模块,民宿档案就是用来管理民宿的数据,民宿档案包括民宿的名称、面积、房号、房间类型、早餐情况、价格、备注等,以下是民宿档案模块的主界面。用户可以点击顶部的“新增”按钮,进入民宿档案添加界面,添加民宿档案数据,如下图所示。 其中房间类型为下拉框单项选择,如下图所示。还有早餐情况也是下拉单选,如下图所示。 用户可以对现有的民宿档案数据进行编辑更新,只需点击每一行民宿档案数据的“编辑”按钮,即可进入民宿档案数据的编辑界面,如下图所示。 用户也可以对不需要的民宿数据进行删除操作,用户点击删除时,系统会弹出二次确认弹框,如下图所示。  民宿新闻模块第三个模块是民宿新闻模块,民宿新闻就是用来管理民宿的新闻资讯,包含的功能如下所示。 民宿新闻包括民宿的名称、面积、房号、房间类型、早餐情况、价格、备注等,以下是民宿新闻模块的主界面,其中的图片仅供测试样例使用。用户可以点击顶部的“新增”按钮,进入民宿新闻添加界面,添加民宿新闻数据,如下图所示。 新闻描述字段采用了 ueditor 富文本编辑器,这是由百度 web 前端研发部开发的所见即所得的开源富文本编辑器,具有轻量、可定制、用户体验优秀等特点,基于 MIT 开源协议,所有源代码可自由修改和使用。 用户可以对现有的民宿新闻数据进行编辑更新,只需点击每一行民宿新闻数据的“编辑”按钮,即可进入民宿新闻数据的编辑界面,如下图所示。 民宿预定模块第四个模块是民宿预定模块,旅客可以在民宿预定模块中预定民宿,达到旅客的住宿目的,民宿预定模块包含的功能如下所示。民宿预定包括了预定民宿 ID、预定民宿名称、预定日期、下单时间、下单人 ID、下单人姓名、价格、是否付款、下单备注等字段,旅客首先进入民宿档案模块,可以看到每一行民宿数据都有一个预约按钮,如下图所示。 如用户点击 1 幢 102 民宿的预约按钮后,会弹出预约确认框,需要输入预约的日期,日期表单默认选择今日,如下图所示。 旅客需要点击“确认预约”按钮,完成预约操作,系统给与“预约成功”提示,如下图所示。 预约成功后,旅客可以从民宿预定模块中进行查询,如下图所示。 最后旅客进行付款操作,点击每一行右侧的付款按钮,如下图所示。支付完成后,系统将预定单的支付状态改为付款,预定流程结束,如下图所示。 民宿评论模块 第五个模块是民宿预定模块,旅客可以在民宿预定结束后评论民宿,以帮助更多的人了解民宿,民宿评论模块包含的功能如下所示。 民宿评论包括了民宿名称、民宿 ID、评论时间、评论内容、评论人 ID、评论人姓名等字段,旅客首先进入民宿档案模块,可以看到每一行民宿数据都有一个评论按钮,如下图所示。 旅客点击评论后,系统给与弹框反馈,如下图所示。  用户输入评论内容后,点击确认评论按钮,即可完成评论操作,如下图所示。  旅客评论后,即可在民宿评论模块中查看此评论数据,如下图所示。 也可以在民宿模块中,双击民宿数据查看评论信息,如下图所示。 项目总结本软件是基于 Vue 和 SpringBoot 的假日旅社管理系统,包含了民宿档案、民宿新闻、民宿预定、民宿评论这四个功能模块。 开发本系统的目的,就是为了帮助旅游景点的民宿企业提高民宿管理效率,降低人力成本,让旅游景点的民宿企业获得更多的经济效益。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值