【开源项目--稻草】Day03

【开源项目--稻草】Day03

  • 1. 续Spring-Security
    • 1.1 自定义登录界面
  • 2. 用户注册
    • 2.1 将注册页面显示
    • 2.2 编写控制器进行测试
    • 2.3 编写注册业务逻辑
    • 2.4 注册功能的收尾
  • 3. VUE
    • 3.1 VUE的基本使用
      • 3.1.1 什么是VUE
    • 3.2 使用VUE+Ajax完善稻草问答的注册功能

1. 续Spring-Security

1.1 自定义登录界面

如果想在用户登录时用我们自己的登录页面代替Spring-Security提供的登录页面

需要进行如下配置

步骤1:

登录页面是视图模板引擎生成的,所以需要引入Thymeleaf的依赖

子项目的pom.xml文件

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

步骤2:

将static文件夹中的login.html
复制到templates文件夹下
需要注意
现在login.html提交的路径是/login
用户名和密码输入框的name是username和password
这两个名字也是Spring-Security约定的不要改!!
我们需要写一个控制器来访问显示这个页面
这个控制器不输于任何实体类,新建一个SystemController

@RestController
public class SystemController {
    // 显示登录页面的方法
    @GetMapping("/login.html")
    public ModelAndView loginForm(){
        //ModelAndView("login");对应的是resources/templates/login.html
        return new ModelAndView("login");
    }
}

步骤3:
要对login.html进行放行

要配置登录时的各种信息

要配置登出时的各种信息

SecurityConfig类中编写

 @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()//对当前全部请求进行授权
                .antMatchers(
                        "/index.html",
                        "/img/*",
                        "/js/*",
                        "/css/*",
                        "/bower_components/**",
                        "/login.html"
                )//设置路径
                .permitAll()//允许全部请求访问上面定义的路径
                //其它路径需要全部进行表单登录验证
                .anyRequest().authenticated().and()
                .formLogin()
                .loginPage("/login.html")
                .loginProcessingUrl("/login")
                .failureUrl("/login.html?error")
                .defaultSuccessUrl("/index.html")
                .and().logout()
                .logoutUrl("/logout")
                .logoutSuccessUrl("/login.html?logout");
    }

方法说明:

1.csrf().disable():关闭防跨域攻击功能,不关闭容易发生错误
2.loginPage:指定登录页面路径
3.loginProcessingUrl:指定表单提交的路径
4.failureUrl:指定登录失败时的路径
5.defaultSuccessUrl:指定登录成功时的路径
6.logout():表示开始配置登出时的内容
7.logoutUrl:指定出的路径(当页面有这个请求时,Spring-Security去执行用户登出操作)
8.logoutSuccessUrl:指定登出成功之后显示的页面

2. 用户注册

每个网站都需要用户注册的功能
页面如下图
【开源项目--稻草】Day03_第1张图片
1.获得邀请码(开发过程是从数据库获得,运营时向老师索取)
2.通过登录页上的注册连接显示注册页面
3.向服务器请求注册页并显示到浏览器
4.注册页面填写信息并提交表单
5.服务器接收到表单信息,控制层调用业务逻辑层执行注册操作
6.业务层执行连库操作新增之前验证邀请码
7.邀请码验证通过在执行数据库新增操作
8.返回新增操作的运行结果
9.根据结果反馈到控制层,有异常就报异常
10.控制器将注册结果信息使用JSON返回给浏览器
11.浏览器中局部刷新页面,将注册结果显示给用户

2.1 将注册页面显示

步骤1:

复制static文件夹中的register.html页面到templates文件夹

步骤2:

编写控制器SystemController类中添加方法

	// 显示注册页面的方法
    @GetMapping("/register.html")
    public ModelAndView register(){
        return new ModelAndView("register");
    }

步骤3:

SecurityConfig类中放行register.html

http.csrf().disable()
                .authorizeRequests()//对当前全部请求进行授权
                .antMatchers(
                        "/index.html",
                        "/img/*",
                        "/js/*",
                        "/css/*",
                        "/bower_components/**",
                        "/login.html",
                        "/register.html"  //放行在这个!!!!!!
                )//设置路径
                .permitAll()//允许全部请求访问上面定义的路径
                //其它路径需要全部进行表单登录验证
                .anyRequest().authenticated().and()
                .formLogin()
                .loginPage("/login.html")
                .loginProcessingUrl("/login")
                .failureUrl("/login.html?error")
                .defaultSuccessUrl("/index.html")
                .and().logout()
                .logoutUrl("/logout")
                .logoutSuccessUrl("/login.html?logout");

2.2 编写控制器进行测试

我们先编写一个简单的控制器代码接收表单的信息,并返回内容
在页面上显示

步骤1:

表单提交的5个属性创建一个Vo类接收代码如下

@Data
public class RegisterVo implements Serializable {
    private String inviteCode;
    private String phone;
    private String nickname;
    private String password;
    private String confirm;
}

步骤2:

上面步骤1中的实体类能接收表单发送过来的信息

但是我们控制器处理完成后,想返回Json格式的对象给JS,也需要一个实体类

这个实体类最好能够通用于所有业务

现在行业中流行使用一个"R"类来返回JSON格式信息

这个R类中主要包含3个属性

1.状态码

2.状态消息

3.实体(控制器查询出的任何内容)

创建R类(代码无需掌握,会使用即可)

@Data
@Accessors(chain = true)
public class R<T> implements Serializable {/** 200 OK - [GET]:服务器成功返回用户请求的数据 */
    public static final int OK = 200;/** 201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。 */
    public static final int CREATED = 201;/** 202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务) */
    public static final int ACCEPTED = 202;/** 204 NO CONTENT - [DELETE]:用户删除数据成功。 */
    public static final int NO_CONTENT = 204;/** 400 INVALID REQUEST - [POST/PUT/PATCH]:
    用户发出的请求有错误,服务器没有进行新建或修改数据的操作。*/
    public static final int INVALID_REQUEST = 400;/** 401 Unauthorized - [*]:
    表示用户没有权限(令牌、用户名、密码错误)。 */
    public static final int UNAUTHORIZED = 401;/** 403 Forbidden - [*] 
    表示用户得到授权(与401错误相对),但是访问是被禁止的。*/
    public static final int FORBIDDEN = 403;/** 404 NOT FOUND - [*]:
    用户发出的请求针对的是不存在的记录,服务器没有进行操作。 */
    public static final int NOT_FOUND = 404;/** 410 Gone -[GET]:
    用户请求的资源被永久删除,且不会再得到的。*/
    public static final int GONE = 410;/** 422 Unprocesable entity - [POST/PUT/PATCH] 
    当创建一个对象时,发生一个验证错误。 */
    public static final int UNPROCESABLE_ENTITY = 422;/** 500 INTERNAL SERVER ERROR - [*]:
     服务器发生错误,用户将无法判断发出的请求是否成功。 */
    public static final int INTERNAL_SERVER_ERROR = 500;private int code;
    private String message;
    private T data;/**
     * 服务器成功返回用户请求的数据
     * @param message 消息
     */
    public static R ok(String message){
        return new R().setCode(OK).setMessage(message);
    }/**
     * 服务器成功返回用户请求的数据
     * @param data 数据
     */
    public static R ok(Object data){
        return new R().setMessage("OK").setCode(OK).setData(data);
    }/**
     * 用户新建或修改数据成功。
     */
    public static R created(String message){
        return new R().setCode(CREATED).setMessage(message);
    }/**
     * 表示一个请求已经进入后台排队(异步任务)
     */
    public static R accepted(String message){
        return new R().setCode(ACCEPTED).setMessage(message);
    }/**
     * 用户删除数据成功
     */
    public static R noContent(String message){
        return new R().setCode(NO_CONTENT).setMessage(message);
    }/**
     * 用户发出的请求有错误,服务器没有进行新建或修改数据的操作。
     */
    public static R invalidRequest(String message){
        return new R().setCode(INVALID_REQUEST).setMessage(message);
    }/**
     * 表示用户没有权限(令牌、用户名、密码错误)
     */
    public static R unauthorized(String  message){
        return new R().setCode(UNAUTHORIZED).setMessage(message);
    }/**
     * 登录以后,但是没有足够权限
     */
    public static R forbidden(){
        return new R().setCode(FORBIDDEN).setMessage("权限不足!");
    }/**
     * 用户发出的请求针对的是不存在的记录,服务器没有进行操作。
     */
    public static R notFound(String message){
        return new R().setCode(NOT_FOUND).setMessage(message);
    }/**
     * 用户请求的资源被永久删除,且不会再得到的。
     */
    public static R gone(String message){
        return new R().setCode(GONE).setMessage(message);
    }/**
     * 当创建一个对象时,发生一个验证错误。
     */
    public static R unproecsableEntity(String message){
        return new R().setCode(UNPROCESABLE_ENTITY)
          .setMessage(message);
    }/**
     * 将异常消息复制到返回结果中
     */
    public static R failed(ServiceException e){
        return new R().setCode(e.getCode())
          .setMessage(e.getMessage());
    }/**
     * 服务器发生错误,用户将无法判断发出的请求是否成功。
     */
    public static R failed(Throwable e){
        return new R().setCode(INTERNAL_SERVER_ERROR)
          .setMessage(e.getMessage());
    }
}

步骤3:

上面两个步骤分别解决了控制器的参数和返回值的问题,

下面我们就测试一个控制器代码,观察是否能够获得参数信息,并返回

SystemController中编写代码

@RestController
@Slf4j//启动日志功能!
public class SystemController {//省略其它方法....@PostMapping("/register")
    public R registerStudent(RegisterVo registerVo){
        System.out.println(registerVo);
        log.debug("得到信息为:{}",registerVo);
        return R.created("注册成功!");
    }
}

步骤4:

配置/register请求的放行

SecurityConfig代码中

 http.csrf().disable()
                .authorizeRequests()//对当前全部请求进行授权
                .antMatchers(
                        "/index.html",
                        "/img/*",
                        "/js/*",
                        "/css/*",
                        "/bower_components/**",
                        "/login.html",
                        "/register.html",
                        "/register"  //放行注册业务!!!!!!
                )//设置路径
                .permitAll()//允许全部请求访问上面定义的路径
                //其它路径需要全部进行表单登录验证
                .anyRequest().authenticated().and()
                .formLogin()
                .loginPage("/login.html")
                .loginProcessingUrl("/login")
                .failureUrl("/login.html?error")
                .defaultSuccessUrl("/index.html")
                .and().logout()
                .logoutUrl("/logout")
                .logoutSuccessUrl("/login.html?logout");

步骤5:

配置日志等级

在控制器的代码中我们启用了日志

需要配置debug等级才能显示在控制台

application.properties文件

logging.level.cn.tedu.straw.portal.mapper=trace
logging.level.cn.tedu.straw.portal=debug

2.3 编写注册业务逻辑

步骤1:

注册业务逻辑属于User表

所以在IUserService接口中新建注册方法

// 用户注册的方法(现在是针对学生注册)
void registerStudent(RegisterVo registerVo);

步骤2:

在IUserService的实现类UserServiceImpl类中重写接口的方法

在方法中排定业务逻辑顺序

 @Override
    public void registerStudent(RegisterVo registerVo) {
        //判断registerVo非空
        
        //根据输入的邀请码查询班级,验证邀请码有效性//验证数据库中是否已经注册过输入的用户名(手机号)//User对象的赋值(将表单中的值和一些默认值确定后)//执行User新增//验证新增结果//将新增的用户赋予学生的角色(新增user_role的关系表)//验证关系表新增结果}

步骤3:

编写判定以及验证时需要的自定义业务异常类

这个类和R类相同也不需要掌握代码,只需要掌握用法

package cn.tedu.straw.portal.service;import cn.tedu.straw.portal.vo.R;public class ServiceException extends RuntimeException{
    private int code = R.INTERNAL_SERVER_ERROR;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;
    }/** 400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作。*/
    public static ServiceException invalidRequest(String message){
        return new ServiceException(message, R.INVALID_REQUEST);
    }/** 404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作。 */
    public static ServiceException notFound(String message){
        return new ServiceException(message, R.NOT_FOUND);
    }/** 410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。*/
    public static ServiceException gone(String message){
        return new ServiceException(message, R.GONE);
    }/** 422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。 */
    public static ServiceException unprocesabelEntity(String message){
        return new ServiceException(message, R.UNPROCESABLE_ENTITY);
    }
}

步骤4:

完成UserServiceImpl类的编写

@Service
@Slf4j//添加了log4j的支持
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {@Autowired
    UserMapper userMapper;
    @Override
    public UserDetails getUserDetails(String username) {
       // 此方法代码略
    }@Autowired
    ClassroomMapper classroomMapper;
    @Autowired
    UserRoleMapper userRoleMapper;BCryptPasswordEncoder passwordEncoder=
            new BCryptPasswordEncoder();@Override
    public void registerStudent(RegisterVo registerVo) {
        //判断registerVo非空
        if(registerVo==null){
            //如果信息是空则发生异常
            //这里的异常逻辑是我们编写的项目发生的,不是系统异常
            //所以这里以及以后的方法中都需要抛出自定义的异常
            throw ServiceException.unprocesabelEntity("表单数据为空");
        }
        //根据输入的邀请码查询班级,验证邀请码有效性
        QueryWrapper<Classroom> queryWrapper=new QueryWrapper<>();
        queryWrapper.eq("invite_code",registerVo.getInviteCode());
        Classroom classroom=classroomMapper.selectOne(queryWrapper);
        log.debug("邀请码对应的班级为:{}",classroom);
        if(classroom==null){
            throw ServiceException.unprocesabelEntity("邀请码错误!");
        }
        //验证数据库中是否已经注册过输入的用户名(手机号)
        //用户名查询用户对象
        User u=userMapper.findUserByUsername(registerVo.getPhone());
        if(u!=null){
            //用户已存在
            throw ServiceException.unprocesabelEntity("手机号已经注册!");
        }
        //User对象的赋值(将表单中的值和一些默认值确定后)
        User user=new User();
        user.setUsername(registerVo.getPhone());
        user.setPhone(registerVo.getPhone());
        user.setNickname(registerVo.getNickname());
        //用户输入的是明文密码,数据库保存的是带算法ID的加密结果!
        user.setPassword("{bcrypt}"+
                passwordEncoder.encode(registerVo.getPassword()));
        user.setClassroomId(classroom.getId());
        user.setCreatetime(LocalDateTime.now());
        user.setEnabled(1);
        user.setLocked(0);
        //执行User新增
        int num=userMapper.insert(user);
        //验证新增结果
        if(num!=1) {
            throw new ServiceException("服务器忙,稍后再试");
        }
        //将新增的用户赋予学生的角色(新增user_role的关系表)
        UserRole userRole=new UserRole();
        userRole.setUserId(user.getId());
        userRole.setRoleId(2);
        num=userRoleMapper.insert(userRole);
        //验证关系表新增结果
        if(num!=1) {
            throw new ServiceException("服务器忙,稍后再试");
        }
    }
}

步骤5:

编写测试类

@Autowired
    IUserService userService;@Test
    public void testUserService(){
        RegisterVo registerVo=new RegisterVo();
        registerVo.setPhone("13333113131");
        registerVo.setNickname("大树");
        registerVo.setInviteCode("JSD1912-876840");
        registerVo.setPassword("123456");
        registerVo.setConfirm("123456");
        userService.registerStudent(registerVo);
        System.out.println("complate!");
    }

2.4 注册功能的收尾

    @Autowired
    IUserService userService;@PostMapping("/register")
    public R registerStudent(RegisterVo registerVo){
        System.out.println(registerVo);
        log.debug("得到信息为:{}",registerVo);
        try{
            userService.registerStudent(registerVo);
            return R.created("注册成功!");
        }catch (ServiceException e){
            log.error("注册失败",e);
            return R.failed(e);
        }}

3. VUE

3.1 VUE的基本使用

3.1.1 什么是VUE

也是一个js为基础的前端框架

提供了一套前端信息和服务器信息交互的一种方式

这种方式要比以前的信息交互方式简单

一般情况下,程序要结合JQuery的ajax操作和Vue的功能完成前后端信息交互

使用准备

Idea添加插件
【开源项目--稻草】Day03_第2张图片
编写html文件

static文件夹下创建一个测试Vue的页面vue.html

这个页面项目中不使用,就是测使用

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
    
    <script src="bower_components/vue/dist/vue.js">script>
head>
<body>
<div id="app">
    <p v-text="message">VUE演示p>
    <input type="text" v-model="content">
    <button type="button" v-on:click="hello">按钮button>
div>
body>
<script>
    //使用Vue
    let app=new Vue({
        el:"#app",
        data:{
            message:"Vue发送的文字!",
            content:"输入框的内容"
        },
        methods:{
            hello:function(){
                console.log(this.content);
                //this.content=this.content+"!";
            }
        }
    });
script>
html>

Vue功能的强大之处在于信息实时同步的双向绑定

【开源项目--稻草】Day03_第3张图片

3.2 使用VUE+Ajax完善稻草问答的注册功能

修改register.html代码

DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="renderer" content="webkit">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <title>稻草FAQ注册title>
  <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.min.css">
  <link rel="stylesheet" href="bower_components/font-awesome/css/font-awesome.min.css">
  <link rel="stylesheet" href="bower_components/plugins/css/vueAlert.css">
  <link rel="stylesheet" href="css/login.css">
  <script src="bower_components/jquery/dist/jquery.min.js" >script>
  <script src="bower_components/bootstrap/dist/js/bootstrap.min.js" >script>
  <script src="bower_components/vue/dist/vue.js">script>
head>
<body class="bg-light"><div class="container-fluid">
  <div class="row">
    <div class="mx-auto mt-5" style="width: 400px">
      <div class="alsrtInfo" style="display: none">
        <div class="profPrompt_test" >div>
      div>
      <h2 class="text-center">达内稻草问答系统h2>
      <div class="bg-white p-4" id="app"><p class="text-center"><b>注册新用户b>p><div id="error" class="alert alert-danger" style="display: none">
          <i class="fa fa-exclamation-triangle">i> <span >邀请码错误!span>
        div>
        
        <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">
            <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" >
            <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" >
            <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"  >
            <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">
            <span class="fa fa-lock form-control-icon">span>
          div>
          <button type="submit" class="btn btn-primary btn-block btn-flat" >注册button>
        form><a href="login.html" class="text-center d-inline-block mt-3">已有账号,立即登录a>
      div>
      
    div>
  div>
div>
body>
<script src="js/utils.js">script>
<script src="js/register.js">script>
html>

修改static/js/register.js代码

let app = new Vue({
    el:'#app',
    data:{
        inviteCode:'',
        phone:'',
        nickname:'',
        password:'',
        confirm:''
    },
    methods:{
        register:function () {
            console.log('Submit');
            let data = {
                inviteCode: this.inviteCode,
                phone: this.phone,
                nickname: this.nickname,
                password: this.password,
                confirm: this.confirm
            }
            console.log(data);
            if(data.password !== data.confirm){
                return;
            }
            $.ajax({
                url:"/register",
                method: "POST",
                data: data,
                success: function (r) {
                    console.log(r);
                    if(r.code == CREATED){
                        console.log("注册成功");
                        console.log(r.message);
                    }else{
                        console.log(r.message);
                    }
                }
            });
        }
    }
});

你可能感兴趣的:(开源项目学习,开源)