4. z
在项目中,每次处理用户提交的请求时,用户的请求数据的走向应该是:用户界面 --> 控制器层 --> 业务层 --> 持久层,以上各层的分工如下:
用户界面:负责显示数据、提供用户操作入口,并提交请求,获取服务器响应的结果;
控制器层:负责接收请求,并发出响应结果;
业务层:负责业务流程和业务逻辑,以保障数据的安全性(数据必须按照业务所设定的规则而产生或发生变化)和完整性;
持久层:负责数据访问,即增删改查。
在开发项目时,开发顺序应该是:持久层 --> 业务层 --> 控制器层 --> 用户界面。
5. 学生注册-持久层
用户注册的本质是向用户数据表中插入数据,然后,为了保证用户名或手机号或某字段唯一,还应该在插入数据之前通过查询进行检查。
由于使用了MyBatisPlus,常规的数据增删改查已经完成,可以不必自行开发所需要功能!
MyBatisPlus的使用存在一些争议,主要表现为:方法的调用可能比较麻烦,例如可能需要使用到QueryWrapper来封装WHERE子句的条件,另外,执行效率可能略低。
6. 学生注册-业务层
由于存在规则“学生注册时必须填写已知的邀请码(在数据表中有记录)才可以注册,将可以把学生根据邀请码分配到不同的班级”,所以,必须先保证“能够验证学生在注册时填写的邀请码是否正确”!
先从笔记服务器下载class_info表的SQL脚本,登录MySQL控制台,使用straw数据库后,通过命令导入该SQL脚本:
source 脚本文件的路径
注意:脚本文件的路径,可以直接将脚本文件拖拽到控制台窗口自动得到,如果得到的路径被添加了引号框住,需要手动删除引号,source与路径之前必须存在空白,且该命令不要使用分号表示结束。
由于使用了新的数据库,则需要通过straw-generator来生成新数据表在项目中需要使用到的各个文件!直接执行CodeGenerator,输入表名class_info,执行完成后,将straw-generator中生成的关于class_info表的相关文件(4个Java文件夹,1个配置SQL语句的XML文件)都复制到straw-portal子模块项目中,完成后,将straw-generator中刚刚生成的文件删除掉。
在执行“学生注册”时,可能出现异常的原因有:
邀请码错误;
班级已被禁用;
手机号码已被占用;
插入用户数据失败;
在项目中,当需要抛出异常时,推荐抛出RuntimeException的子孙类异常,通常,都会自定义异常来表示错误,关于如何自定义异常:
自定义1个异常,在异常中声明某个属性,该属性的值不同时,就表示不同类型的错误;
自定义若干个异常,每1种异常对应1种错误;
当前项目将始终使用以上第2种做法,首先,应该创建自定义异常的基类异常,便于表示“自定义业务异常”!则在cn.tedu.straw.portal.service.ex包中创建ServiceException:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5IrR5NRW-1595509211297)(image-20200715144725092.png)]
该异常类需要继承自RuntimeException,并添加与父类相同的构造方法:
package cn.tedu.straw.portal.service.ex; /** * 业务异常的基类 */ public class ServiceException extends RuntimeException { 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); } }
接下来,还是在cn.tedu.straw.portal.service.ex包中创建以上列举的4种错误对应的异常,这4个异常都必须继承自以上创建的ServiceException:
public class InviteCodeException extends ServiceException { // 构造方法…… } public class ClassDisabledException extends ServiceException { // 构造方法…… } public class PhoneDuplicateException extends ServiceException { // 构造方法…… } public class InsertException extends ServiceException { // 构造方法…… }
当开发业务层时,应该先在IUserService接口中定义业务的抽象方法,然后,再在实现类实现该方法。
本次需要开发“注册”功能,声明的抽象方法应该是:
void registerStudent(User user, String inviteCode);
关于业务方法的返回值类型的设计:仅以操作成功为前提来设计返回值类型!不需要考虑操作失败的问题,当操作失败时,都会抛出某种异常的对象!
当抽象方法已经设计好了,就应该在实现类中实现以上抽象方法:
@Autowired ClassInfoMapper classInfoMapper; @Autowired UserMapper userMapper; public void registerStudent(User user, String inviteCode) { // 【由于当前项目设计的规则是“学生账号通过手机号码注册、登录”,必须保证手机号码唯一】 // 调用ClassInfoMapper对象的selectOne()方法,根据参数inviteCode邀请码,查询class_info表 // 判断查询结果是否为空 // 是:表示没有找到有效的邀请码,不允许注册,抛出InviteCodeException // 从以上查询到的班级信息中取出enabled,判断是否为0 // 是:表示该班级已禁用,不允许注册该班级的学生账号,抛出ClassDisabledException // 从参数user中取出手机号码 // 调用UserMapper对象的selectOne()方法,根据手机号码查询学生账号信息 // 判断查询结果是否不为null // 是:找到了学生信息,表示手机号码已经被占用,则不允许注册,抛出PhoneDuplicateException // 没有找到学生信息,表示手机号码没有被占用,则允许注册…… // 确保参数user中的数据全部是有效的 // - 取出参数user中的密码,调用私有的encode()方法进行加密,并将加密后的密码封装回到user中 // - classId:此前验证邀请码时得到的结果 // - createdTime:当前时间,LocalDateTime.now() // - enabled:1,允许使用 // - locked:0,不锁定 // - type:0,表示学生 // 调用UserMapper对象的insert()方法,根据参数user插入数据,获取返回值 // 判断返回值(受影响的行数)是否不为1 // 是:受影响的行数不是1,则插入用户数据失败,抛出InsertException } @Autowired PasswordEncoder passwordEncoder; // 将密码进行加密的方法 private String encode(String rawPassword) { String encodePassword = passwordEncoder.encode(rawPassword); return encodePassword; }