SpringBoot聚合项目:达内知道(三)-数据库用户登录、学生注册

1.用户数据详解

  下面我们要实现数据库中的用户登录,但是必须要明确数据库中保存用户信息和其资格的格式:

在数据库中,两张表的关系有:

  1.一对多(多对一)

  2.多对多:

  上面的用户角色权限结构中,用户和角色就是多对多,角色对权限也是多对多,凡是多对多的关系,必须有一个中间表来保存他们的关系。

  3.一对一(少见)

  • user表和role表:一个用户可以有多个角色,一个角色可以对应多个用户,为多对多的关系,需要使用中间表user_role进行连接,通过user_id和role_id进行查询

  • role表和permission表:一个角色可以有多个权限,一个权限可以对应多个角色,为多对多的关系,需要使用中间表role_permission进行连接,通过role_id和permission_id进行查询

  • 用户表、角色表、权限表联合起来查询,至少需要5张表

  • 注意:id=11代表学生,id=3代表老师,密码888888;

  • 邀请码确定班级

  • 由于用了Spring-Security框架, enabled、locked为针对框架在表中设置的参数

5表联查权限代码:事先可以在数据库可视化工具进行测试,测试成功后,再复制到IDEA中

 SELECT p.id, p.name
 FROM user u
 LEFT JOIN user_role ur       ON u.id=ur.user_id
 LEFT JOIN role r             ON r.id=ur.role_id
 LEFT JOIN role_permission rp ON rp.role_id=r.id
 LEFT JOIN permission p       ON p.id=rp.permission_id
 WHERE u.id=11

查询结果:

在UserMapper接口中定义两个方法,代码如下:

 package cn.tedu.knows.portal.mapper;
 
 import cn.tedu.knows.portal.model.Permission;
 import cn.tedu.knows.portal.model.User;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import org.apache.ibatis.annotations.Select;
 import org.springframework.stereotype.Repository;
 
 import java.util.List;
 
 /**
 * <p>
     * Mapper 接口
     * </p>
 *
 * @author tedu.cn
 * @since 2021-08-23
 */
 @Repository //写了Repository就不用在Autowired后面写required=false了
 public interface UserMapper extends BaseMapper<User> { //继承mybatis-plus提供的BaseMapper
     //根据用户id查询所有权限的方法(复制测试好的SQL语句)
     @Select("SELECT p.id,p.name" +
             " FROM user u" +
             " LEFT JOIN user_role ur ON u.id=ur.user_id" +
             " LEFT JOIN role r ON r.id=ur.role_id" +
             " LEFT JOIN role_permission rp ON rp.role_id=r.id" +
             " LEFT JOIN permission p ON p.id=rp.permission_id" +
             " WHERE u.id=#{id}")//此处要变为可变的id,即数据库中存在用户的id
     List<Permission> findUserPermissionsById(Integer id);//返回多个对象,用List
 
     //根据用户名查询用户对象 *:效率低,不推荐使用
     @Select("select * from user where username=#{username}")
     User findUserByUsername(String username);//返回单个对象,用对象对应的类型接收
 }
 

这两个方法都是登录时需要的,为了保证程序正确运行,推荐大家测试一下,尤其是5表联查,测试代码如下:

 @Autowired
 UserMapper userMapper;
 //测试UserMapper可用
 @Test
 public void testUse(){
     //用户名查用户
     User user=userMapper.findUserByUsername("st2");
     System.out.println(user);
     //根据用户id查询权限
     List<Permission> ps=userMapper.findUserPermissionsById(user.getId());
     for(Permission p:ps){
         System.out.println(p);
 
    }
 }

输出结果:

 2021-08-24 21:43:02.160  INFO 13220 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
 2021-08-24 21:43:02.525 INFO 13220 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
 2021-08-24 21:43:02.535 DEBUG 13220 --- [           main] c.t.k.p.m.UserMapper.findUserByUsername : ==> Preparing: select * from user where username=?
 2021-08-24 21:43:02.568 DEBUG 13220 --- [           main] c.t.k.p.m.UserMapper.findUserByUsername : ==> Parameters: st2(String)
 2021-08-24 21:43:02.640 DEBUG 13220 --- [           main] c.t.k.p.m.UserMapper.findUserByUsername : <==     Total: 1
 User(id=11, username=st2, nickname=李四同学, password={bcrypt}$2a$10$ELGiEhKyLlO9r3.WVOkHDe16JTCKCErcABhElD5CF7ZwQ.Hm6sVRW, sex=保密, birthday=null, phone=null, classroomId=null, createtime=2021-03-13T22:36:59, enabled=1, locked=0, type=0, selfIntroduction=null)
 2021-08-24 21:43:02.659 DEBUG 13220 --- [           main] c.t.k.p.m.U.findUserPermissionsById     : ==> Preparing: SELECT p.id,p.name FROM user u LEFT JOIN user_role ur ON u.id=ur.user_id LEFT JOIN role r ON r.id=ur.role_id LEFT JOIN role_permission rp ON rp.role_id=r.id LEFT JOIN permission p ON p.id=rp.permission_id WHERE u.id=?
 2021-08-24 21:43:02.660 DEBUG 13220 --- [           main] c.t.k.p.m.U.findUserPermissionsById     : ==> Parameters: 11(Integer)
 2021-08-24 21:43:02.688 DEBUG 13220 --- [           main] c.t.k.p.m.U.findUserPermissionsById     : <==     Total: 4
 Permission(id=1, name=/index.html, desc=null)
 Permission(id=2, name=/question/create, desc=null)
 Permission(id=3, name=/question/uploadMultipleFile, desc=null)
 Permission(id=4, name=/question/detail, desc=null)

 

2.实现数据库登录

  现在我们登录需要使用UserDetails类实现,UserDetails译作:用户详情,是Spring-Security提供的一个类型,专门用于Spring-Security框架保存用户信息。我们要做的登录方法也是Spring-Security框架规定好的,所以我们只需要编写框架需要我们编写的内容,这个内容由Spring-Security框架提供了一个接口表现,这个接口叫UserDetailsService,其中提供了一个方法loadUserByUsername,参数是用户名,返回值是UserDetails对象。

  我们在service包下的impl包下创建UserDetailsServiceImpl类,编写代码如下:

 package cn.tedu.knows.portal.service.impl;
 
 import cn.tedu.knows.portal.mapper.UserMapper;
 import cn.tedu.knows.portal.model.Permission;
 import cn.tedu.knows.portal.model.User;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.security.core.userdetails.UserDetailsService;
 import org.springframework.security.core.userdetails.UsernameNotFoundException;
 import org.springframework.stereotype.Component;
 
 import java.util.List;
 
 @Component //注意不要忘记这个(以组件扫描的形式将对象存到Spring容器中) 实现接口 重写方法 改s为username,见名知意
 public class UserDetailServiceImpl implements UserDetailsService {
     @Autowired //自动装配:从Spring容器中将UserMapper对象取出来
     private UserMapper userMapper;
     @Override //点登录,自动将用户名传过来,然后与查询到的密码进行匹配
     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
         //1.根据用户名获得用户信息
         User user = userMapper.findUserByUsername(username);
         //2.判断用户名是否存在
         if(user == null){
             return null;//不存在时直接结束方法
        }
         //3.查询当前用户所有权限
         List<Permission> ps = userMapper.findUserPermissionsById(user.getId());
         //4.通过权限的List中存储的对象获取权限名转化为String[],用于后续作为auth,设置访问资格
         String[] auth = new String[ps.size()];
         int i = 0;//数组起始下标
         for (Permission p:ps) {
             auth[i++]=p.getName();
        }
         //5.构建UserDetails对象用于返回
         UserDetails u = org.springframework.security.core.userdetails
                .User.builder()
                 //设置当前用户的用户名
                .username(user.getUsername())
                 //设置当前用户的密码
                .password(user.getPassword())
                 //设置当前用户的所有权限/资格,auth对应上面的访问权限
                .authorities(auth)
                 //Spring-Security支持用户的锁定和禁用,这里也顺便提一下:表中设计两列:enabled、locked
                 //设置锁定,()里为true表示锁定,false表示不锁定 表中默认为0,此处设置为1
                .accountLocked(user.getLocked()==1)
                 //设置禁用,()里为true表示禁用,false表示不禁用,可用 表中默认为1,此处设置为0
                .disabled(user.getEnabled()==0)
                .build();
         //6.返回UserDetails对象,千万别忘了返回!!!!
         return u;
    }
 }
 

security/SecurityConfig类修改配置:

 package cn.tedu.knows.portal.security;
 
 import cn.tedu.knows.portal.service.impl.UserDetailServiceImpl;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
 import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
 
 @Configuration
 //提示键:EGMS 启动SpringBoot的权限管理功能,设置prePostEnabled = true,此处必须要设置
 @EnableGlobalMethodSecurity(prePostEnabled = true)
 public class SecurityConfig extends WebSecurityConfigurerAdapter {//继承WebSecurityConfigureAdapter
     @Autowired
     private UserDetailServiceImpl userDetailService;
     @Override //重写configure方法
     protected void configure(AuthenticationManagerBuilder auth) throws Exception {
         //在用户点击登录时,会触发这个方法,这个方法会调用我们编写的
         //loadUserByUsername方法,根据用户输入的用户名查询用户详情。
         //至于登录是否成功,以及验证登录过程,都在框架中封装了,我们只能在页面上看到结果。
         auth.userDetailsService(userDetailService);
    }
 }

2.1 设置可访问内容

  一般的网站,有些页面是可以不登录就能访问的,那么我们先把我们要开发的首页进行设置,不登录就能访问,也是在SecurityConfig类中进行设置,但是重写不同的方法:

 //设置当前Spring-Security框架的放行规则
 @Override
 protected void configure(HttpSecurity http) throws Exception {
     http.authorizeRequests()   //对请求进行授权设置
        .antMatchers(          //设置匹配路径
             "/index_student.html", //学生首页
                 "/js/*",  //当前目录下的所有文件
                 "/css/*",  //当前目录下的所有文件
                 "/img/**",  //当前目录及子目录下的所有文件
                 "/bower_components/**"  //当前目录及子目录下的所有文件
        ).permitAll()           //上述路径全部允许直接访问
        .anyRequest().authenticated() //其它请求需要登录之后才能访问
        .and().formLogin();     //登录使用表单认证
 }

2.2 自定义登录页

  Spring-Security提供的登录页效果不理想,我们想将我们编写的登录页替换掉原有的,也是在SecurityConfig中进行配置,编写如下配置:

 //设置当前Spring-Security框架的放行规则
 @Override
 protected void configure(HttpSecurity http) throws Exception {
     http.authorizeRequests()  //对请求进行授权设置
        .antMatchers(  //匹配路径
             "/index_student.html", //学生首页
             "/js/*",    //当前目录下的所有文件
             "/css/*",   //当前目录下的所有文件
             "/img/**",  //当前目录及子目录下的所有文件
             "/bower_components/**",    //当前目录及子目录下的所有文件
             "/login.html", //放行自定义登录页面路径:达内登录页面
        ).permitAll()  //上述路径全部允许直接访问
        .anyRequest().authenticated() //其它请求需要登录之后才能访问
        .and().formLogin() //登录使用表单验证
        .loginPage("/login.html") //设置自定义登录页面路径:达内登录页面
        .loginProcessingUrl("/login") //设置处理登录的路径
        .failureUrl("/login.html?error") //设置登陆失败路径,页面内置登录失败信息,提示“账号或密码错误”
        .defaultSuccessUrl("/index_student.html") //设置登录成功的页面:学生首页
        .and().logout()  //设置登出
        .logoutUrl("/logout")  //设置登出路径
        .logoutSuccessUrl("/login.html?logout")  //设置登出后显示登录页面,提示“已经登出系统”
        .and().csrf().disable(); //关闭防跨域攻击,不关闭无法登录(只要设置了自定义登录页面,就必须设置这个)            
 }

 测试结果:

(1)登录成功,响应index_student.html首页内容

(2)用户名或密码错误

(3)登录后无权限访问的页面

(4)登出成功页面

3.实现学生注册

注册页面:

注册流程:

  1.学生获得邀请码访问注册页,填写表单信息

  2.学生将表单提交到控制器,控制器接收表单信息

  3.控制器调用业务逻辑层方法,将用户提交的表单发送到业务逻辑层

  4.业务逻辑层进行各种验证,密码加密,实例化User对象,调用Mapper进行新增操作

  5.UserMapper的新增方法是Mybatis Plus提供的!不用写代码。

  6.返回信息到控制器,控制器返回注册结果到页面

注意:先完成同步注册,后修改为异步注册

2.1 注册准备

  首先注册页面一定是不需要登录就能访问的,将SecurityConfig类的放行列表中添加注册页:

 .antMatchers(          //设置匹配路径
     "/index_student.html", //学生首页
             "/js/*",    //当前目录下的所有文件
             "/css/*",   //当前目录下的所有文件
             "/img/**",  //当前目录及子目录下的所有文件
             "/bower_components/**",    //当前目录及子目录下的所有文件
             "/login.html", //放行自定义登录页面路径
             "/register.html", //放行注册页面
             "/register" //放行注册路径
        ).permitAll()  //上述路径全部允许直接访问
        .anyRequest().authenticated() //其它请求需要登录之后才能访问
        .and().formLogin() //登录使用表单验证
        .loginPage("/login.html") //设置自定义登录页面路径
        .loginProcessingUrl("/login") //设置处理登录的路径
        .failureUrl("/login.html?error") //设置登陆失败路径,页面内置登录失败信息
        .defaultSuccessUrl("/index_student.html") //设置登录成功的页面
        .and().logout()  //设置登出
        .logoutUrl("/logout")  //设置登出路径
        .logoutSuccessUrl("/login.html?logout")  //设置登出后显示登录页面
        .and().csrf().disable(); //关闭防跨域攻击,不关闭无法登录(只要设置了自定义登录页面,就必须设置这个)
 

  我们接收表单信息需要创建一个封装表单数据的类,创建vo包,包中创建RegisterVo的类,类中匹配表单的每个属性代码如下:

 package cn.tedu.knows.portal.vo;
 
 import lombok.Data;
 
 @Data
 public class RegisterVo {
     private String inviteCode;
     private String phone;
     private String nickname;
     private String password;
     private String confirm;
 }
 

  我们的表单信息会发送到控制器,我们编写一个控制器类方法类接收它:在controller包中新建一个SystemController类,代码如下:

 package cn.tedu.knows.portal.controller;
 
 import cn.tedu.knows.portal.vo.RegisterVo;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RestController;
 
 @RestController
 @Slf4j //由lombok提供,进行日志记录
 public class SystemController {
     //注册方法是post请求
     @PostMapping("/register") //注册路径
     public String registerStudent(RegisterVo registerVo){
         log.debug("收到注册信息:{}",registerVo);//{}表示占位符,可使用多个占位符,后面依次赋值
         return "ok";
    }
 }
 

  重启服务,进行注册,访问路径:http://localhost:8888/register.html,浏览器页面显示:ok,控制台输出注册信息:

 收到注册信息:RegisterVo(inviteCode=JSD1912-876840, phone=12345678901, nickname=xiaocui, password=123456, confirm=123456)

1.3 java三层结构

在企业开发中,java代码实现业务时最少有三层结构:

  • Controller:控制层

    • 从前端接收用户信息

    • 向前端发送信息

    • 除此之外的所有工作通过调用业务逻辑层完成

  • Service:业务逻辑层

    • 除控制层和数据访问层之外所有其它工作,由业务层完成

      • 例如对信息正确性的甄别、复杂业务的工作梳理以及信息的收集

  • Mapper:数据访问层

    • 只负责和数据库进行沟通,完成增删改查方法

1.4 使用QueryWrapper

  我们之前完成过一次使用Mybatis Plus提供的方法进行全查的测试:

 List<Tag> tags=tagMapper.selectList(null);

  上面的代码执行了全查操作,selectList(null)表示无条件查询,我们可以向selectList的()中添加条件实现按条件查询,而无需编写sql语句。

QueryWrapper类型的对象可以设置条件,并当做查询参数。下面这个测试,就演示如何使用它:

 package cn.tedu.knows.portal;
 
 import cn.tedu.knows.portal.mapper.ClassroomMapper;
 import cn.tedu.knows.portal.model.Classroom;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 
 //SpringBoot测试类中必须写这个注解
 @SpringBootTest
 public class TestWrapper {
     //从Spring容器中取出ClassroomMapper
     @Autowired
     ClassroomMapper classroomMapper;
     @Test
     //测试查询班级
     public void testRoom(){
         //实例化QueryWrapper对象
         QueryWrapper<Classroom> query = new QueryWrapper<>();
         //设置条件:判断是否有要查询字段中的内容   参数1:查询字段 参数2:要查询的内容
         query.eq("invite_code","JSD2001-706246");
         //按这个条件执行查询
         Classroom classroom = classroomMapper.selectOne(query);//查询得到一个结果
         System.out.println(classroom);
    }
 }
 

输出结果:

上面测试中我们使用了eq方法,这个方法的含义是设置条件:判断是否相等,除了这个方法之外,常用的其他方法有:

  • lt(less than):小于

  • le(less equals):小于等于

  • gt(great than):大于

  • ge(great equals):大于等于

  • ne(not equals):不等于

1.5 自定义异常

  我们马上要编写注册代码,在注册代码编写过程中,如果邀请码错误或者手机号已经被注册,我们要怎么反馈给用户呢?解决方案是使用异常机制

  如果发生了上述问题,就抛出一个异常对象,表示出了什么问题这个异常对象的类型不能是系统本身提供的,应该是一个我们自定义的异常,表示在我们编写业务过程中发生的错误问题。于是,我们在cn.tedu.knows.portal路径下创建一个exception包,包中新建一个ServiceException类,这个类的代码从提供给大家的ServiceException.txt文件中复制即可。

 package cn.tedu.knows.portal.exception;
 
 /**
  * 自定义异常:都是一些构造方法的重载,只是参数列表不同
  */
 public class ServiceException extends RuntimeException{
     private int code = 500;
 
     public ServiceException() { }
 
     public ServiceException(String message) {
         super(message);
    }
 
     public ServiceException(String message, Throwable cause) {
         super(message, cause);
    }
 
     public ServiceException(Throwable cause) {
         super(cause);
    }
 
     public ServiceException(String message, Throwable cause,
                             boolean enableSuppression, boolean writableStackTrace) {
         super(message, cause, enableSuppression, writableStackTrace);
    }
 
     public ServiceException(int code) {
         this.code = code;
    }
 
     public ServiceException(String message, int code) {
         super(message);
         this.code = code;
    }
 
     public ServiceException(String message, Throwable cause,
                             int code) {
         super(message, cause);
         this.code = code;
    }
 
     public ServiceException(Throwable cause, int code) {
         super(cause);
         this.code = code;
    }
 
     public ServiceException(String message, Throwable cause,
                             boolean enableSuppression, boolean writableStackTrace, int code) {
         super(message, cause, enableSuppression, writableStackTrace);
         this.code = code;
    }
 
     public int getCode() {
         return code;
    }
 
 }

1.6 编写注册的业务逻辑层

  注册业务是对User的新增,所以要找User对应的业务逻辑层代码。业务逻辑层由一个接口和一个实现类组成(解耦),要先写接口,再写对应的实现类。User对应的接口是IUserService,对应的实现类是UserServiceImpl。

我们新编写一个业务应该从接口开始编写,在IUserService中添加一个注册方法:

 package cn.tedu.knows.portal.service;
 
 import cn.tedu.knows.portal.model.User;
 import cn.tedu.knows.portal.vo.RegisterVo;
 import com.baomidou.mybatisplus.extension.service.IService;
 
 /**
  * <p>
  * 服务类
  * </p>
  *
  * @author tedu.cn
  * @since 2021-08-23
  */
 public interface IUserService extends IService<User> {
     //学生注册的方法,参数RegisterVo
     void registerStudent(RegisterVo registerVo);
 
 }
 

下面要去实现注册方法,在接口上Ctrl+Alt+B跳到对应实现类,在UserServiceImpl类中编写代码:

 package cn.tedu.knows.portal.service.impl;
 
 import cn.tedu.knows.portal.exception.ServiceException;
 import cn.tedu.knows.portal.mapper.ClassroomMapper;
 import cn.tedu.knows.portal.mapper.UserRoleMapper;
 import cn.tedu.knows.portal.model.Classroom;
 import cn.tedu.knows.portal.model.User;
 import cn.tedu.knows.portal.mapper.UserMapper;
 import cn.tedu.knows.portal.model.UserRole;
 import cn.tedu.knows.portal.service.IUserService;
 import cn.tedu.knows.portal.vo.RegisterVo;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
 import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.stereotype.Service;
 
 import java.time.LocalDateTime;
 
 /**
  * <p>
  * 服务实现类
  * </p>
  *
  * @author tedu.cn
  * @since 2021-08-23
  */
 @Service //作用1:保存到Spring容器中;作用2:语义化,代表业务逻辑层
 public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
     //Ctrl+Alt+B 可以从接口直接跳转到它的实现类(唯一实现类)
     //注册用户必须使用userMapper进行最后的新增,获得user信息
     @Autowired
     UserMapper userMapper;
 
     //要验证邀请码是否正确,需要利用ClassroomMapper进行查询班级邀请码
     @Autowired
     private ClassroomMapper classroomMapper;
 
     //新增学生之后,还要新增学生和role表的关系,要使用UserRoleMapper
     @Autowired
     private UserRoleMapper userRoleMapper;
 
     @Override //重写接口中的方法
     public void registerStudent(RegisterVo registerVo) {
         //1.根据邀请码查询班级信息
         QueryWrapper<Classroom> query = new QueryWrapper<>();
         query.eq("invite_code",registerVo.getInviteCode());
         Classroom classroom = classroomMapper.selectOne(query);//一个邀请码对应一个班级,返回一个对象
         //2.如果班级不存在,抛出异常
         if(classroom==null){
             //抛出异常
             throw new ServiceException("邀请码错误!");//throw有return的作用,下面代码不再执行
        }
         //3.根据用户输入的手机号查询用户信息
         User user = userMapper.findUserByUsername(registerVo.getPhone());//手机号是用户名,利用手机号进行注册
         //4.如果用户存在,抛出异常
         if(user!=null){
             throw new ServiceException("手机号已经被注册!");
        }
         //5.利用bcrypt加密算法对密码进行加密
         PasswordEncoder encoder = new BCryptPasswordEncoder();
         String pwd = "{bcrypt}" + encoder.encode(registerVo.getPassword());//利用bcrypt算法对注册密码进行加密
         //验证密码在前端页面已经处理
         //6.实例化User对象,进行相关属性的赋值
         User u = new User();
         u.setUsername(registerVo.getPhone());//注意:是用phone进行注册的,phone就是用户名
         u.setPassword(pwd);//用加密后的密码作为用户密码
         u.setNickname(registerVo.getNickname());
         u.setClassroomId(classroom.getId());//从班级中获得班级id
         u.setCreatetime(LocalDateTime.now());//获取当前时间:年月日时分秒
         u.setEnabled(1);//可用
         u.setLocked(0);//不锁定
         u.setType(0);//学生类型为0
         //7.将用户新增到数据库
         //返回受影响的行数:增删改 执行后,主键id自增,自动赋值到user表的id中,因此不用赋值user的id属性
         int num = userMapper.insert(u);
         //num是增删改操作对数据库产生影响的行数,新增一般是1,如果返回0表示新增失败
         if(num==0){//或者num!=1
             throw new ServiceException("数据库异常");
        }
         //8.新增用户和角色的关系
         UserRole userRole = new UserRole();
         //UserRole有两个属性:user_id、role_id
         userRole.setUserId(u.getId());//注意:此处是u而非user
         userRole.setRoleId(2);//学生角色为2
         num = userRoleMapper.insert(userRole);
         //num是增删改操作对数据库产生影响的行数,新增一般是1,如果返回0表示新增失败
         if(num==0){
             throw new ServiceException("数据库异常");
        }
    }
 }
 

1.7 控制层调用业务逻辑层方法

  上面编写完了业务逻辑层代码,我们需要在SystemController类的注册方法中调用它才行,修改registerStudent方法代码为:

 @Autowired
 private IUserService userService;
 //注册方法,是post请求
 @PostMapping("/register")
 public String registerStudent(RegisterVo registerVo){
     log.debug("收到注册信息:{}",registerVo);
     //Ctrl+Alt+T可以快速添加try-catch结构
     try {
         //调用业务逻辑层方法实现注册功能
         userService.registerStudent(registerVo);
         return "ok";
    } catch (ServiceException e) {
         //e.printStackTrace()是将错误信息输出到控制台
         //可以保留,但是也要知道,控制台的错误信息是我们主动输出的
         e.printStackTrace();
         return e.getMessage();
    }
 }

  编写完毕,可以重启服务,进行测试!

1.8 异步实现注册

  我们需要使用Vue+Axios实现异步注册,下面进行register.html页面内容的修改,主要是Vue绑定和js代码的引用、axios的引用等。

在register.html页面的head标签最后位置引入Axios框架:ax+Tab

 <!--引入axios框架-->
 <script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.min.js"></script>

register.html 32行附近:

 <!--d-block显示内容,尽管display:none也没用,但要给d-block添加属性,控制其何时显示-->
 <div id="error" class="alert alert-danger" style="display: none"
              v-bind:class="{'d-block':hasError}"><!--hasError:true显示,false不显示-->
  <i class="fa fa-exclamation-triangle"></i>
  <span v-text="message">邀请码错误!</span><!--v-text="message"等价于{{message}}-->
 </div>

register.html从40行开始的注册表单:

 <!--v-on:submit.prevent="register"中
  .prevent:意思是在当前form表单提交时阻止原有的提交效果
  ="register":意思是运行vue中定义的register方法
 -->
 <form action="/register" method="post"
       v-on:submit.prevent="register">
   <div class="form-group has-icon">
     <input type="text" name="inviteCode" class="form-control" placeholder="请输入邀请码" required="required" v-model="inviteCode"><!--v-model vue绑定-->
     <span class="fa fa-barcode form-control-icon"></span>
   </div>
   <div class="form-group has-icon">
     <input type="tel" name="phone" class="form-control" placeholder="请输入手机号"
       pattern="^\d{11}$" required="required" v-model="phone"><!--v-model vue绑定-->
     <span class=" fa fa-phone form-control-icon"></span>
   </div>
 
   <div class="form-group has-icon">
     <input type="text" name="nickname" class="form-control" placeholder="请设置昵称,字数为2-20之间" pattern="^.{2,20}$" required="required" v-model="nickname"><!--v-model vue绑定-->
     <span class="fa fa-user form-control-icon"></span>
   </div>
 
   <div class="form-group has-icon">
     <input type="password" name="password" class="form-control" placeholder="设置密码6-20个字母、数字、下划线" required="required" pattern="^\w{6,20}$" v-model="password"><!--v-model vue绑定-->
     <span class="fa fa-lock form-control-icon"></span>
   </div>
   <div class="form-group has-icon">
     <input type="password" name="confirm" class="form-control" placeholder="请再次输入密码" required="required" v-model="confirm"><!--v-model vue绑定-->
     <span class="fa fa-lock form-control-icon"></span>
   </div>
   <button type="submit" class="btn btn-primary btn-block btn-flat" >注册</button>
 </form>

页面末尾添加引用:

 </body>
 <script src="js/utils.js"></script>
 <script src="js/register.js"></script>
 </html>

register.js 文件微调:34行

 if(r.data == "ok"){/*与SystemController中实现注册后返回的“ok”对应*/

编写完毕之后,重启服务进行测试!

 

posted @ 2021-08-24 22:36  Coder_Cui  阅读(652)  评论(0编辑  收藏  举报