Day 16 - Spring Boot 资料验证

在上一篇Day 15 - Spring Boot 注册与登入中,我们已经初步完成了注册与登入的基础功能,但我们要记得,Spring Boot 为了简化开发的流程提供了许多开箱即用的依赖,这篇我们就要介绍Spring Boot 提供关於资料验证的依赖,也就是Day 07 - Spring Boot 常用依赖中提到spring-boot-starter-validation,主要透过注释的方式进行,可以进一步简化我们的程序码,让我们更专注在业务逻辑上。

常用注释

当然,不免俗的要先看一下它提供的注释,以下列出一些常用的注释,可以不用背下来,但要记得有关於资料验证的功能,可以先查一下它有没有提供相关的注释。

  • @Valid : 注释在方法中要校验的参数上,用於启动校验,可搭配BindingResult 物件获取校验失败的讯息;也可以注释在成员属性上,用於进行嵌套验证。
  • @Validated : 对於@Valid 的封装,由Spring 提供的校验机制,比@Valid 多了分组功能,但必须配合@Valid 才能进行嵌套验证。
  • @Email : 被注释的属性必须是电子信箱格式
  • @Pattern : 被注释的属性必须符合指定的正则表示法
  • @Null : 被注释的属性必须为Null
  • @NotNull : 被注释的属性必须不为Null
  • @NotEmpty : 被注释的字串不可为空。
  • @NotBlank : 被注释的字串非null 且长度必须大於0。
  • @Length : 被注释的字串长度必须在指定的范围内。
  • @Min(value) : 被注释的属性必须为一个数字且大於或等於指定的最小值。
  • @Max(value) : 被注释的属性必须为一个数字且小於或等於指定的最大值。
  • @DecimalMin(value) : 被注释的属性必须为一个数字且大於或等於指定的最小值。
  • @DecimalMax(value) : 被注释的属性必须为一个数字且小於或等於指定的最大值。
  • @Size(max, min) : 被注释的属性必须为一个数字且在指定的范围内。
  • @Digits(integer, fraction) : 被注释的属性必须为一个数字且在可接收的范围内。
  • @Range : 被注释的属性必须在合适的范围内。
  • @AssertTrue : 被注释的属性必须为一个布林值且为true。
  • @AssertFalse : 被注释的属性必须为一个布林值且为false。
  • @Past : 被注释的属性必须为一个过去的日期
  • @Future : 被注释的属性必须为一个未来的日期

实作

新增依赖

<!-- Spring Validator -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

修改MemberAccount 实体类

package com.example.demo.entity;

import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
public class MemberAccount extends Base {

	private String id;

	@Email(message = "帐号必须是Email 格式")
	@NotBlank(message = "帐号不可为空")
	private String username;

	@NotBlank(message = "密码不可为空")
	@Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[\\w]{6,16}$", 
			 		 message = "密码必须为长度6~16位码大小写英文加数字")
	private String password;
	
	private String salt;

}

修改控制器层

@Controller
public class MemberAccountController {

	// 略...

	@RequestMapping(value = "/login", method = RequestMethod.POST)
	public String doLogin(
			@Valid @ModelAttribute MemberAccount memberAccount,
			HttpSession session, 
			RedirectAttributes redirectAttributes) {
		
		MemberAccountVO memberAccountVO = memberAccountService.login(memberAccount);
		if(memberAccountVO == null) {
			redirectAttributes.addFlashAttribute("MESSAGE", "帐号或密码错误");
			return "redirect:login";
		}
		session.setAttribute("member", memberAccountVO);	
		return "redirect:information";
	}

	// 略...
	
	@RequestMapping(value = "/register", method = RequestMethod.POST)
	public String doRegister(
			@Valid @ModelAttribute MemberAccountVO memberAccountVO,
			RedirectAttributes redirectAttributes) {

		Optional<String> optional = memberAccountService.register(memberAccountVO);
		String message = optional.orElse("注册成功");
		redirectAttributes.addFlashAttribute("MESSAGE", message);
		return "redirect:login";
	}
	
}

修改业务逻辑层的实作

可以看到注册的业务逻辑,少了前面对帐号及密码格式的判断,别小看这边只少了两行,当专案越来越大,搭配@Validated 为验证逻辑进行分组,可以减少各处的验证判断。

@Service
public class MemberAccountServiceImpl implements MemberAccountService {

	// 略...

	@Override
	public Optional<String> register(MemberAccountVO memberAccountVO) {
		// TODO Auto-generated method stub
		// 检查两次输入密是否相符
		if(!memberAccountVO.getPassword().equals(memberAccountVO.getCheckPassword())) return Optional.of("两次输入密码不相符");
		
		// 检查帐号是否重复注册
		MemberAccount data = memberAccountDao.findMemberAccountByUsername(memberAccountVO.getUsername());
		if(data != null) return Optional.of("该帐号已被使用");
		
		// 产生盐值
		String salt = UUID.randomUUID().toString().toUpperCase().replaceAll("-", "");
		
		// 密码加密
		String md5Password = getMd5Password(memberAccountVO.getPassword(), salt);

		// 新增MemberAccount 资料
		MemberAccount memberAccount = new MemberAccount();
		memberAccount.setUsername(memberAccountVO.getUsername());
		memberAccount.setPassword(md5Password);
		memberAccount.setSalt(salt);
		memberAccount.setCreate_by(memberAccountVO.getUsername());
		memberAccount.setUpdate_by(memberAccountVO.getUsername());
		Integer id = memberAccountDao.insert(memberAccount);
		if(id == 0) return Optional.of("新增会员帐号时发生错误");

		// 新增Member 资料
		Member member = new Member();
		member.setMa_id(String.valueOf(id));
		member.setName(memberAccountVO.getName());
		member.setCreate_by(memberAccountVO.getUsername());
		member.setUpdate_by(memberAccountVO.getUsername());
		Integer result = memberService.insert(member);
		if(result == 0) return Optional.of("新增会员资料时发生错误");
		
		return Optional.empty();
	}

	// 略...

}

Github

新增资料验证功能

参考网站

使用SpringBoot进行优雅的资料验证_部落格园精华区 - MdEditor
SpringBoot - 第二十章 | 资料验证(二)


<<:  Day 19: Security Hub 单一帐号/启用Org.後的布建

>>:  Day16 NiFi - 与 MongoDB 对接设定

那些被忽略但很好用的 Web API / Share

与你分享的快乐,胜过独自拥有 现代人看到有趣的网页、新闻、消息等等时,最常做的事情就是分享到社群帐...

非本科、半路转职的「软件科技职涯发展笔记」

from Unsplash 写了三十天的技术文章,最後一篇想谈谈「职涯发展」,毕竟这才是非本科转职...

[Day21] Scrum失败经验谈 – 没有价值的User story

User story:用一个简短的句子,描述用户的需求价值,也是大家所熟知的,身为一个「角色」,我想...

基本操作 - 下单

建立订单 from shioaji.constant import * # 股票 order = ...

Day27 - GitLab CI 如何让工作流程流水线跑快一点?之一 从 .gitlab-ci.yml 大部分解

在专案过程中,透过 GitLab CI 建立流水线,让研发过程中如编译、测试、打包、部署等工作都得以...