【开源项目--稻草】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.htmlreturn new ModelAndView("login");}
}
步骤3:
要对login.html进行放行
要配置登录时的各种信息
要配置登出时的各种信息
SecurityConfig类中编写
@Overrideprotected 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. 用户注册
每个网站都需要用户注册的功能
页面如下图
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类中重写接口的方法
在方法中排定业务逻辑顺序
@Overridepublic 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 {
@AutowiredUserMapper userMapper;@Overridepublic UserDetails getUserDetails(String username) {// 此方法代码略}
@AutowiredClassroomMapper classroomMapper;@AutowiredUserRoleMapper userRoleMapper;
BCryptPasswordEncoder passwordEncoder=new BCryptPasswordEncoder();
@Overridepublic 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:
编写测试类
@AutowiredIUserService userService;
@Testpublic 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 注册功能的收尾
@AutowiredIUserService 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添加插件
编写html文件
static文件夹下创建一个测试Vue的页面vue.html
这个页面项目中不使用,就是测使用
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><!-- 添加VUE的支持 --><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>//使用Vuelet app=new Vue({el:"#app",data:{message:"Vue发送的文字!",content:"输入框的内容"},methods:{hello:function(){console.log(this.content);//this.content=this.content+"!";}}});
</script>
</html>
Vue功能的强大之处在于信息实时同步的双向绑定
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><!--v-on:submit.prevent表示绑定一个方法 ,.prevent阻止表单提交 --><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><!-- /.form-box --></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);}}});}}
});