springboot集成security(认证)

1. 依赖

        <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>
        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.1</version>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

加入 spring-boot-starter-security 依赖之后,不需要任何配置,启动项目,就会出现一个登录界面:

  • 任何访问请求都会被拦截到 /login,并且要求认证后才能访问,请求方式是 Get

  • 默认 usernameuser,每次启动都会在控制台输出一个 128Byte 的 password

  • 可通过配置文件修改默认的 usernamepassword (静态用户,适用于内部网络认证)

    spring:
      # default login url: /login
      security:
        user:
          name: dev
          # default size: 128 Byte
          password: admin
    

2. 自定义登录逻辑


1. 数据库查询

表、实体类:

@Data
public class User {
    private Integer id;
    private String name;
    private String password;
}

配置文件:

# application name
spring:
  # mysql
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/spbt?serverTimezone=Asia/Shanghai
    username: root
    password: admin
# web service port
server:
  port: 8088
mybatis-plus:
  type-aliases-package: com.chenjy.security_demo.dto
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

mapper:

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.chenjy.security_demo.mapper.UserMapper">
    <select id="getUserByName" parameterType="string" resultType="user">
        select
               id,
               name,
               password
        from
             user
        <where>
            <if test="name != null and name != ''">
                name = #{name}
            </if>
        </where>
    </select>
</mapper>
@Mapper
public interface UserMapper {
    User getUserByName(String name);
    List<User> getUserByName();
}

启动类加上 @MapperScan 注解


service:

public interface UserService {
    User getUserByName(String name);
    List<User> getUserByName();
}
@Service
public class UserServiceImpl implements UserService {
    @Resource
    private UserMapper userMapper;

    @Override
    public User getUserByName(String name) {
        return userMapper.getUserByName(name);
    }

    @Override
    public List<User> getUserByName() {
        return userMapper.getUserByName();
    }
}

2. security认证

要自定义 security 认证逻辑,就需要定义一个服务对象来实现 UserDetailsService 接口,并重写 loadUserByUsername 方法。


1. loadUserByUsername

loadUserByUsername

  • security 唯一的认证方法
  • 查询用户失败,会抛出 UsernameNotFoundException
@Component
public class UserDetailsServiceImpl implements UserDetailsService {
    @Resource
    private UserService userService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        com.chenjy.security_demo.dto.User user = userService.getUserByName(username);
        if (user == null) {
            throw  new UsernameNotFoundException("用户名错误");
        }
        // 匹配用户密码
        // org.springframework.security.core.userdetails.User
         User res = new User(username, user.getPassword(), AuthorityUtils.createAuthorityList());
        return res;
    }
}
  • org.springframework.security.core.userdetails.User

  • loadUserByUsername 需要返回一个 UserDetails 的实现类,可以直接使用 User,其有两个构造器:

    public User(String username, String password, Collection<? extends GrantedAuthority> authorities)
    public User(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) 
    
    • username 用户名
    • password 用户密码
    • authorities 权限集合
  • security 内部会自动进行密码匹配

这时候直接启动项目,会报错:

*java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"*

这是因为 security 5.X 需要提供一个PasswordEncorder的实例


2. PasswordEncorder(不加密)

security 内部进行密码匹配的时候,一定要进行加密和解密处理。要求 IOC 容器中,必须要存在一个 PasswordEncorder 对象,提供加密和解密逻辑。

  • 可以直接客户端明文,数据库解密来匹配验证
  • 也可以客户端加密,数据库直接密文来匹配验证

因为我的数据库密码并未进行加密,所以这里,我们就直接返回字符串进行明文比对就行了。

@Component
public class MyPasswordEncoder implements PasswordEncoder {
    /**
     * @Description 收集页面的密码
     * @param rawPassword
     * @return String
    */
    @Override
    public String encode(CharSequence rawPassword) {
        return rawPassword.toString();
    }

    /**
     * @Description 匹配逻辑
     * @param rawPassword 明文,页面收集的密码
     * @param encodedPassword 密文,存储在数据源中的密码
     * @return boolean
    */
    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        return rawPassword.equals(encodedPassword);
    }
}

encode 对页面收集到的密码进行加密,然后将加密好的密文交给 matches,与数据库密码进行比对。


3. MD5加密数据库密码

自定义加密工具类

public class PwdEncode {

    /**
     * @Description 收集
     * @param pwd
     * @return String
    */
    public static String encode(String pwd) {
        MessageDigest md5 = null;
        try {
            md5 = MessageDigest.getInstance("MD5");
        } catch (Exception e) {
            System.out.println(e.toString());
            e.printStackTrace();
            return "";
        }
        char[] charArray = pwd.toCharArray();
        byte[] byteArray = new byte[charArray.length];

        for (int i = 0; i < charArray.length; i++) {
            byteArray[i] = (byte) charArray[i];
        }
        byte[] md5Bytes = md5.digest(byteArray);
        StringBuffer hexValue = new StringBuffer();
        for (int i = 0; i < md5Bytes.length; i++) {
            int val = ((int) md5Bytes[i]) & 0xff;
            if (val < 16){
                hexValue.append("0");
            }
            hexValue.append(Integer.toHexString(val));
        }
        return hexValue.toString();
    }

    /**
     * @Description 解密
     * 一次加密之后,需要两次解密
     * @param pwd
     * @return String
    */
    public static String decrypt(String pwd) {
        char[] a = pwd.toCharArray();
        for (int i = 0; i < a.length; i++) {
            a[i] = (char) (a[i] ^ 't');
        }
        String s = new String(a);
        return s;
    }

    public static void main(String[] args) {
        String s = "123456";
        System.out.println("原始:" + s);
        System.out.println("MD5后:" + encode(s));
        System.out.println("加密的:" + decrypt(s));
        System.out.println("解密的:" + decrypt(decrypt(s)));
    }
}

然后修改数据库密码。


4. PasswordEncorder(加密)

@Component
public class MyPasswordEncoder implements PasswordEncoder {
    /**
     * @Description 收集页面的密码
     * @param rawPassword
     * @return String
    */
    @Override
    public String encode(CharSequence rawPassword) {
        return rawPassword.toString();
    }

    /**
     * @Description 匹配逻辑
     * @param rawPassword 明文,页面收集的密码
     * @param encodedPassword 密文,存储在数据源中的密码
     * @return boolean
    */
    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        return PwdEncode.encode(rawPassword.toString()).equals(encodedPassword);
    }
}

注意: MD5解密很简单——https://www.cmd5.com/,所以不推荐使用MD5加密。


5. BCryptPasswordEncoder

鉴于MD5加密的不安全性,所以建议使用 security 自带的加密工具类 —— BCryptPasswordEncoder

  • 同一明文,加密两次,输出不同

        public static void main(String[] args) {
            String s = "123456";
            BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();;
            System.out.println("原始: " + s);
            System.out.println("加密1: " + encoder.encode(s));
            System.out.println("加密1: " + encoder.encode(s));
        }
    
  • 可使用 matches 验证明文与密文是否相同:

    System.out.println(encoder.matches(s, encoder.encode(s)));
    

BCryptPasswordEncoder使用bcrypt算法对密码进行加密,同时会为密码加上“盐”,开发者不需要自己加“盐”,即使相同的明文字段生成的加密字符串也不同。匹配时,从密文中取出“盐”,用该盐值加密明文和最终密文作对比。


BCryptPasswordEncoder的默认强度为10,开发者可以根据自己服务器的速度进行调整,以确保密码验证的时间约为1秒(官方建议)

BCryptPasswordEncoder encoder_pro = new BCryptPasswordEncoder(15);
System.out.println("加密2: " + encoder_pro.encode(s));

注册 BCryptPasswordEncoder

  • 取消MyPasswordEncoder的 @Component 注解

  • 新建一个配置类,注册 BCryptPasswordEncoder

    @Configuration
    public class SecurityConf {
        @Bean
        public BCryptPasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    }
    

6. 认证流程(图)


3. 自定义登录界面


1. 界面

依赖:

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

resources/templates/login.html:

<!DOCTYPE html>
<html lang="en" xmlns:th="https://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>登录</title>
</head>
<body>
<div class="main">
  <div class="title">
    <span>密码登录</span>
  </div>

  <div class="title-msg">
    <span>请输入登录账户和密码</span>
  </div>

  <form class="login-form" method="post" novalidate th:action="@{/myLogin}">
    <!--输入框-->
    <div class="input-content">
      <!--autoFocus-->
      <div>
        <input type="text"
               autocomplete="off"
               placeholder="用户名"
               name="name"
               required/>
      </div>

      <div style="margin-top: 16px">
        <input type="password"
               autocomplete="off"
               placeholder="登录密码"
               name="password"
               required
               maxlength="32"/>
      </div>
    </div>

    <!--登入按钮-->
    <div style="text-align: center">
      <button type="submit" class="enter-btn" >登录</button>
    </div>
  </form>

</div>
</body>
<style>
  body{
    background: #426258;
  }
  *{
    padding: 0;
    margin: 0;
  }

  .main {
    padding-left: 25px;
    padding-right: 25px;
    padding-top: 15px;
    width: 350px;
    height: 350px;
    background: #FFFFFF;
    /*以下css用于让登录表单垂直居中在界面,可删除*/
    position: absolute;
    top: 50%;
    left: 50%;
    margin: -175px auto 0 -175px;
  }

  .title {
    width: 100%;
    height: 40px;
    line-height: 40px;
  }

  .title span {
    font-size: 18px;
    color: #353f42;
  }

  .title-msg {
    width: 100%;
    height: 64px;
    line-height: 64px;
  }

  .title:hover{
    cursor: default	;
  }

  .title-msg:hover{
    cursor: default	;
  }

  .title-msg span {
    font-size: 12px;
    color: #707472;
  }

  .input-content {
    width: 100%;
    height: 120px;
  }

  .input-content input {
    width: 330px;
    height: 40px;
    border: 1px solid #dad9d6;
    background: #ffffff;
    padding-left: 10px;
    padding-right: 10px;
  }

  .enter-btn {
    width: 350px;
    height: 40px;
    color: #fff;
    background: #0bc5de;
    line-height: 40px;
    text-align: center;
    border: 0px;
  }

  .enter-btn:hover {
    cursor:pointer;
    background: #1db5c9;
  }
</style>

</html>

注意: security 处理登录请求的方式一定是 POST

resources/templates/main.html:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
      xmlns:shiro="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>主页</title>
</head>
<body>
<div class="con">
    <h1><span style="color: #67865d">登陆成功</span></h1>
    <br>
    <div class="component">
    </div>
    <br>
</div>
</body>
<style>
    body {
        background-color: #c1d3d0;
    }
    .con {
        text-align: center;
    }
    .component div {
        font-size: 32px;
    }
    .component span {
        color: #FFFFFF;
        font-size: 32px;
    }
</style>

2. security配置类

extends WebSecurityConfigurerAdapter 并重写 configure(HttpSecurity http)


1. 配置默认登录界面

@Configuration
public class SecurityConf extends WebSecurityConfigurerAdapter {
    /**
     * @Description 注册BCryptPasswordEncoder到容器
     * @return BCryptPasswordEncoder
     */
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * @Description 重写父类型中的配置逻辑
     * @param http 基于http协议的security配置对象,包含所有security相关配置逻辑
     * @throws Exception 配置出错会抛出异常
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 配置请求相关内容
        http
                .formLogin()
                .loginPage("/myLogin") // 登录页面地址,默认 /login, 这里的地址要由 controller 接口地址决定
                .usernameParameter("name") // 登录页面用户名字段
                .passwordParameter("password"); // 登录页面用户密码字段

        // 关闭csrf
        http.csrf().disable();
    }
}

然后启动:

  • 可发现,并不会拦截请求
  • 一旦自定义配置,所有的默认认证流程会全部清空

2. 配置拦截与放行

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 配置请求相关内容
        http
                .formLogin()
                .loginPage("/myLogin") // 登录页面地址,默认 /login, 这里的地址要由 controller 接口地址决定
                .usernameParameter("name") // 登录页面用户名字段
                .passwordParameter("password") // 登录页面用户密码字段
                .and()
                .authorizeRequests()
                .antMatchers("/myLogin")
                .permitAll() // /myLogin 不需要认证,可直接访问
                .anyRequest()
                .authenticated(); // 其他请求都需要认证

        // 关闭csrf
        http.csrf().disable();
    }
  • formLogin 配置登录表单
  • loginPage 配置登录界面
  • usernameParameter 登录页面用户字段名
  • passwordParameter 登录页面用户密码字段名
  • and and
  • authorizeRequests 配置拦截和放行的url
  • antMatchers 匹配多个路径,用 , 隔开
  • permitAll 前面匹配的路径全部放行
  • anyRequest 其他路径
  • authenticated 匹配的路径需要认证

注意:

  • 拦截一定要写在放行之后
  • 如果不配置 usernameParameterpasswordParameter,那么请求参数名必须是 usernamepassword,不然 security 无法识别

3. 登录成功后的处理方案

security 默认访问成功后跳转到 “/” ,现在修改配置,使其跳转到 main


方案一: 使用 successForwardUrl,请求转发 —— POST 请求。

    @RequestMapping("main")
    public String main() {
        return "main";
    }
.successForwardUrl("/main") // 登录成功跳转页面

方案二: 使用 successForwardUrl,响应重定向(重新请求) —— GET 请求。

.defaultSuccessUrl("http://127.0.0.1:8088/main") // 响应重定向(GET)

注意1: 因为 successForwardUrl 是重新发起请求,所以需要传入一个绝对地址才能成功定向。

注意2: 最好传入一个参数 alwaysUse (代表一定使用这个重定向地址),防止出现错误。

.defaultSuccessUrl("http://127.0.0.1:8088/main", true) 

方案三: 使用 successHandler,自定义认证成功后的请求处理逻辑(转发、重定向都可自定义)。

successForwardUrldefaultSuccessUrl 内部就是用的 successHandler 的实现类,进行控制成功后交给哪个方法处理。

自定义请求成功处理器: implements AuthenticationSuccessHandler

@Data
@AllArgsConstructor
@NoArgsConstructor
public class SuccessHandler implements AuthenticationSuccessHandler {
    private String url;
    public boolean isRedirect;
    
    /**
     * @Description
     * @param request 请求对象  请求转发——request.getRequestDispatcher(url).forward(request, response);
     * @param response 响应对象 重定向——response.sendRedirect(url);
     * @param authentication 认证成功后的对象,包含用户名和权限信息(防止信息泄露,所以没带密码)
    */
    @Override
    public void onAuthenticationSuccess( HttpServletRequest request,
            HttpServletResponse response,
            Authentication authentication) throws IOException, ServletException {
        if (isRedirect) {
            response.sendRedirect(url);
        } else {
            request.getRequestDispatcher(url).forward(request, response);
        }
    }
}
.successHandler(new SuccessHandler("/main", false)) // 使用自定义请求处理控制逻辑

4. 登陆失败后的处理方案

登陆失败后,默认跳转到 /login?error,现在自定义一个登录失败的处理。


方案一: 使用 failureForwardUrl,登录失败请求转发 —— POST

    @RequestMapping("fail")
    public String fail(Model model) {
        model.addAttribute("fail");
        return "login";
    }
.failureForwardUrl("/fail") // 登录失败转发
<span th:if="${fail} == 'true'" style="color: red">登陆失败</span>

方案二: 使用 failureForwardUrl,响应重定向。

.failureUrl("/fail") // 响应重定向
...
.antMatchers("/myLogin", "/fail")
.permitAll() // /myLogin,/fail 不需要认证,可直接访问

重定向是重新发请求, 所以需要在权限配置中放行 /fail


方案三: 使用 failureForwardUrl,自定义处理器。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class FailureHandler implements AuthenticationFailureHandler {
    private String url;
    private boolean isRedirect;

    @Override
    public void onAuthenticationFailure(HttpServletRequest request,
                                        HttpServletResponse response,
                                        AuthenticationException exception) throws IOException, ServletException {
        if (isRedirect) {
            response.sendRedirect(url);
        } else {
            request.getRequestDispatcher(url).forward(request, response);
        }
    }
}
.failureHandler(new FailureHandler("/fail", true)) //
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 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
发出的红包

打赏作者

364.99°

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值