目次
プロジェクトデータ処理フロー---プロジェクトデータ処理フローを理解する
ユーザーインターフェイス->コントローラーレイヤー->ビジネスレイヤー->永続レイヤー
UserControllerでリクエストを処理し、URLにアクセスできるかどうかをテストする簡単なメソッドを追加します。
かかしプロジェクト
プロジェクトデータ処理フロー---プロジェクトデータ処理フローを理解する
プロジェクトでは、ユーザーが送信したリクエストを処理するとき、ユーザーリクエストデータの方向は次のようになります。
ユーザーインターフェイス->コントローラーレイヤー->ビジネスレイヤー->永続レイヤー
上記の層の分業は次のとおりです。
- ユーザーインターフェイス: 表示データを再テストし、ユーザー操作エントリを提供し、要求を送信し、サーバー側の応答結果を取得します。
- コントローラ層: 要求の受信と応答結果の送信を担当します。
- ビジネス層:ビジネスプロセスとビジネスロジックを担当し、データのセキュリティ( データはビジネスによって設定されたルールに従って生成または変更する必要があります)と整合性()を確保する必要があります。
- 永続層:データのアクセスと操作、追加、削除、変更、検査を担当します
プロジェクトを開発する場合、開発シーケンスは次のようになります。
永続層->ビジネス層->コントローラー層->ユーザーインターフェイス
在开发项目时,开发顺序应该是:
持久层 ----> 业务层 ----> 控制器层 ----> 用户界面
学生登録-永続層
ユーザー登録は、テーブル挿入データ内のユーザーデータの本質であり、ユーザー名、電話番号、または一意のフィールドを保護するために、データクエリを挿入する前にクエリによって実行する必要があります
MyBatisPlusを使用しているため、通常のデータの追加、削除、変更が完了した後は、必要な機能を自分で開発する必要はありません。
MyBatisPlus的使用存在一些争议,
主要表现为:方法的调用可能比较麻烦,
例如可能
需要使用到QueryWrapper来封装WHERE子句的条件,另外,执行效率可能略低。
学生登録-ビジネス層
「学生は登録する前に既知の招待コード(データシートに記録されている)を入力する必要があり、招待に応じて学生をさまざまなクラスに割り当てることができる」というルールがあるため、最初に次のことを確認する必要があります。学生が登録していることを確認できます招待コードは正しい時間に入力されていますか?」
まず、ノートサーバーからclass_infoテーブルのSQLスクリプトをダウンロードし、MySQLコンソールにログインし、strawデータパスを使用して、次のコマンドを使用してSQLスクリプトをインポートします。
<
source 脚本文件的路径
>
注:スクリプトファイルのパスは、スクリプトファイルをコンソールウィンドウに直接ドラッグすることで自動的に取得できます。取得したパスが引用符で囲まれている場合は、手動で引用符を削除する必要source
があります。パスの前に空白が必要です。、およびコマンドを使用して分割しないでください。記号は終了を意味します。
新しいデータベースが使用されると、新しいデータテーブルがプロジェクトで使用する必要のあるファイルを生成するためにストロージェネレーターが必要になります。直接実行しCodeGenerator
、テーブル名を入力class_info
し、実行が完了した後、関連ファイル(4つのJavaのフォルダ、SQLステートメントを構成するための1 XMLファイル)をコピーして生成されたわら発電class_info
にわらポータル終了後、サブモジュールプロジェクトの削除ストロージェネレータで生成されたばかりのファイル。
「学生登録」の実行中に、異常な理由がある可能性があります。
- 間違った招待コード
- クラスが無効になりました
- 電話番号はすでに使用されています
- ユーザーデータの挿入に失敗しました
プロジェクトでそれを見つけます。例外をスローする必要がある場合は、RuntimeExceptionの子孫を使用することをお勧めします。通常、例外をカスタマイズします。例外をカスタマイズする方法について
- 1つの例外をカスタマイズし、例外で属性を宣言します。属性の値が異なる場合、それは異なるタイプのエラーを意味します。
- それぞれが1つのエラーに対応するいくつかの例外をカスタマイズします。
現在のプロジェクトでは、常に上記の2番目のアプローチが使用されます。最初に、「カスタムビジネス例外」という表現が簡単なカスタム例外基本クラス例外を作成する必要があります。
「cn.tedu.portal.service.ex」パッケージで作成
ServiceException:
例外クラスは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);
}
}
次に、上記の4種類のエラーに対応する例外を作成します。4つの例外はすべて、上記で作成したものから継承する必要がありますServiceException
。
招待コードエラー例外
public class InviteCodeException extends ServiceException{
//构造方法
}
クラスが異常に無効化されました
public class ClassDisabledException extends ServiceException{
//构造方法
}
電話番号はすでに使用されています、エラーは異常です
public class PhoneDuplicateException extends ServiceException{
//构造方法
}
ユーザーデータエラー例外の挿入に失敗しました
public class InsertException extends ServiceException{
//构造方法
}
インターフェースでビジネスの抽象的な方法を定義する
ビジネス層を開発するときは、最初にインターフェイスでビジネスの抽象メソッドを定義してから、実装クラスでメソッドを実装する必要があります。
今回は「登録」関数を開発する必要があり、宣言された抽象メソッドは次のようになります。
<
void registerStudent( User user ,String inviteCode);
>
ビジネス方法の戻り値タイプの設計について:
关于业务方法的返回值类型设计:
仅以<操作成功为前提来设计返回值类型! >不需要考虑操作失败问题<当操作失败时,都会抛出某种异常的对象!>
抽象メソッドが設計されたら、抽象メソッドを実装クラスに実装する必要があります。
@Autowired
ClassInfoMapper classInfoMapper;
@Autowired
UserMapper userMapper;
public void seristerStudent(User user,String inviteCode){
//[由于当前项目的设计规则是 :"学生账号通过 手机号码注册,登录",必须保证手机号码的唯一]
<
//根据 参数inviteCode 邀请码 调用ClassInfoMapper 对象的selectOne() 方法 查询class_info表
//判断用户是否为空
//是 : 表示没有找到有效的验证码,不允许注册,抛出异常
InviteCodeException
>
//从 以上查询到的班级信息 获取enabled ,判断是否0
//是 :表示该班级已禁用,不允许注册该班级的学生账号,抛出异常
ClassDisabledException
//从参数user中取出手机号码
//调用 UserMapper 对象的 selectOne() 方法 <持久层的方法>,根据手机号码查询学生账号信息
//判断查询结果是否为null
//是 : **找到学生信息,表示手机号码已经被占用,则不允许注册....抛出异常
PhoneDuplicateException
// ---抛出异常,表示手机号码被占用----
// >>没有找到了学生信息,表示手机号码没有被占用,则允许注册....
//^^确保参数 User 中的信息全部是有效的
//--取出User 中的密码, 调用私有的encode( )方法进行加密 进行加密 ,并将加密后的密码封装回到User中
// - classId : 通过验证邀请码时得到的结果
// - createdTime : 当前时间, LocaleDateTime.now()
// - enabled : 1 允许使用
// - locked : 0 ,不锁定
// - type : 0 表示学生
//调用 UserMapper( 持久层的方法 ) 对象的 insert( ) 方法,根据参数user 插入数据,获取返回值
//判断返回值 < 受影响的行数 > 是否不为1
//是 : 受影响的行数不是1 则插入用户数据失败 ,抛出异常
InsertException
if(){
return / throw
}
}
パスワードを暗号化する方法
@Autowired
PasswordEncoder passwordEncoder
private String encode(String rawPassword){
String encodePassword = passwordEncoder.encode(rawPassword);
return encodePassword;
}
ビジネスコードを作成するときは、例外をスローすることに基づいて判断するようにしてください。
特定のコードの実装:
package cn.tedu.straw.portal.service.impl;
import cn.tedu.straw.portal.mapper.ClassInfoMapper;
import cn.tedu.straw.portal.mapper.UserMapper;
import cn.tedu.straw.portal.model.ClassInfo;
import cn.tedu.straw.portal.model.User;
import cn.tedu.straw.portal.service.IUserService;
import cn.tedu.straw.portal.service.ex.ClassDisabledException;
import cn.tedu.straw.portal.service.ex.InsertException;
import cn.tedu.straw.portal.service.ex.InviteCodeException;
import cn.tedu.straw.portal.service.ex.PhoneDuplicateException;
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.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
/**
* <p>
* 服务实现类
* </p>
*
* @author tedu.cn
* @since 2020-07-14
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Autowired
private ClassInfoMapper classInfoMapper;
@Autowired
private UserMapper userMapper;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public void registerStudent(User user, String inviteCode) {
// 【由于当前项目设计的规则是“学生账号通过手机号码注册、登录”,必须保证手机号码唯一】
// 调用ClassInfoMapper对象的selectOne()方法,根据参数inviteCode邀请码,查询class_info表
QueryWrapper<ClassInfo> classQueryWrapper = new QueryWrapper<>();
classQueryWrapper.eq("invite_code", inviteCode);
ClassInfo classInfo = classInfoMapper.selectOne(classQueryWrapper);
// 判断查询结果是否为空
if (classInfo == null) {
// 是:表示没有找到有效的邀请码,不允许注册,抛出InviteCodeException
throw new InviteCodeException();
}
// 从以上查询到的班级信息中取出enabled,判断是否为0
if (classInfo.getEnabled() == 0) {
// 是:表示该班级已禁用,不允许注册该班级的学生账号,抛出ClassDisabledException
throw new ClassDisabledException();
}
// 从参数user中取出手机号码
String phone = user.getPhone();
// 调用UserMapper对象的selectOne()方法,根据手机号码查询学生账号信息
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.eq("phone", user.getPhone());
User result = userMapper.selectOne(userQueryWrapper);
// 判断查询结果是否不为null
if (result != null) {
// 是:找到了学生信息,表示手机号码已经被占用,则不允许注册,抛出PhoneDuplicateException
throw new PhoneDuplicateException();
}
// 没有找到学生信息,表示手机号码没有被占用,则允许注册……
// 确保参数user中的数据全部是有效的
// - 取出参数user中的密码,调用私有的encode()方法进行加密,并将加密后的密码封装回到user中
String rawPassword = user.getPassword();
String encodePassword = encode(rawPassword);
user.setPassword(encodePassword);
// - classId:此前验证邀请码时得到的结果
user.setClassId(classInfo.getId());
// - createdTime:当前时间,LocalDateTime.now()
user.setCreatedTime(LocalDateTime.now());
// - enabled:1,允许使用
user.setEnabled(1);
// - locked:0,不锁定
user.setLocked(0);
// - type:0,表示学生
user.setType(0);
// 调用UserMapper对象的insert()方法,根据参数user插入数据,获取返回值
int rows = userMapper.insert(user);
// 判断返回值(受影响的行数)是否不为1
if (rows != 1) {
// 是:受影响的行数不是1,则插入用户数据失败,抛出InsertException
throw new InsertException();
}
}
/**
* 执行密码加密
*
* @param rawPassword 原密码
* @return 根据原密码执行加密得到的密文
*/
private String encode(String rawPassword) {
String encodePassword = passwordEncoder.encode(rawPassword);
return encodePassword;
}
}
完了後、src / test / javaでcn.tedu.straw.portal
パッケージservice
サブパケットを作成し、このパッケージでUserServiceTests
上記のメソッドをテストするためのテストクラスを作成します。
package cn.tedu.straw.portal.service;
import cn.tedu.straw.portal.model.User;
import cn.tedu.straw.portal.service.ex.ServiceException;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
@Slf4j
public class UserServiceTests {
@Autowired
IUserService userService;
@Test
void registerStudent() {
try {
User user = new User();
user.setPhone("13800138002");
user.setNickname("小王同学");
user.setPassword("1234");
String inviteCode = "JSD2003-005803";
userService.registerStudent(user, inviteCode);
log.debug("register student > OK.");
} catch (ServiceException e) {
log.debug("register student > failure.");
log.debug("cause : {}", e.getClass());
}
}
}
操作の失敗は、スローされた例外によって処理されます
<
操作失败以异常抛出来处理,
用户名错误异常: UserNotFoundException
密码错误异常 PasswordNotMatchException
通过不同的错误,抛出不同的异常,在一个方法中处理登录失败操作!
User login() throws UserNotFoundException,PasswordNotMatchException;
>
<
try{
User user = login();
//执行成功!
}catch( UserNotFoundException ){
//用户名错误!
}catch( PasswordNotMatchException ){
//密码错误!
}
>
学生登録---コントローラー
現在のプロジェクトにはSpringSecurityフレームワークが統合されているため、デフォルトでは、現在のサイトのすべてのリクエストにはログイン後にのみアクセスでき(具体的には、ログインを回避するために特定のリクエストを設定する方法については後で説明します)、アプリケーションに追加できます。プロパティ一時的なユーザー名とパスワード:
spring.security.user.name=root
spring.security.user.password=$2a$10$tsM03ULkiifEpSCWtQ5Mq.yrLZIPKVr5vHwU1FGjtT9B1vPlswa.C
上記のパスワードの値がされ1234
、元のパスワードがbcryptのアルゴリズムによって暗号化されるとして使用。BcryptPasswordEncoding
パスワードの暗号化は、自動的に現在のプロジェクトにインストールされている。春のセキュリティフレームワークが自動的にパスワードを使用してページ上でユーザーが入力したパスワードを暗号化しますしたがって、1234
対応する暗号テキストは上記の構成ファイルで構成されます。他のパスワードを一時的に使用する必要がある場合は、最初に単体テストで暗号文を生成してから、上記のspring.security.user.password
属性に暗号文を構成できます。
UserControllerでリクエストを処理し、URLにアクセスできるかどうかをテストする簡単なメソッドを追加します。
// http://localhost:8080/portal/user/student/register?
//inviteCode=JSD2003-111111&nickname=Hello&phone=13800138002
@RequestMapping("/student/register")
public String studentRegister() {
return "studentRegister";
}
JSONデータ
実際にリクエストを処理する場合、クライアントへの最終的な応答はJSONデータである必要があります。JSONデータに確実に応答できるようにするには、最初にcn.tedu.straw.portalに「R」クラス(結果を表す)を作成します。 .voパッケージ:
package cn.tedu.straw.portal.vo;
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Accessors(chain=true)
public class R {
private Integer state;
private String message;
}
次に、コントローラーで要求を処理する方法を調整します。
package cn.tedu.straw.portal.controller;
import cn.tedu.straw.portal.model.User;
import cn.tedu.straw.portal.service.IUserService;
import cn.tedu.straw.portal.service.ex.ClassDisabledException;
import cn.tedu.straw.portal.service.ex.InsertException;
import cn.tedu.straw.portal.service.ex.InviteCodeException;
import cn.tedu.straw.portal.service.ex.PhoneDuplicateException;
import cn.tedu.straw.portal.vo.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>
* 前端控制器
* </p>
*
* @author tedu.cn
* @since 2020-07-14
*/
@RestController
@RequestMapping("/portal/user")
public class UserController {
@Autowired
private IUserService userService;
// http://localhost:8080/portal/user/student/register?inviteCode=JSD2003-111111&nickname=Hello&phone=13800138002&password=1234
@RequestMapping("/student/register")
public R studentRegister(User user, String inviteCode) {
try {
userService.registerStudent(user, inviteCode);
return new R().setState(1).setMessage("注册成功!");
} catch (InviteCodeException e) {
return new R().setState(2).setMessage("注册失败!邀请码错误!");
} catch (ClassDisabledException e) {
return new R().setState(3).setMessage("注册失败!班级已经被禁用!");
} catch (PhoneDuplicateException e) {
return new R().setState(4).setMessage("注册失败! 手机号码已经被注册!");
} catch (InsertException e) {
return new R().setState(5).setMessage("注册失败!服务器忙,请稍后再次尝试!");
}
}
}
完了したら、プロジェクトを再起動し、ブラウザを開いてログインし、上記のURLを使用してテストします。