【我的2022下半年回顾-1】-SpringBoot
前言
2022过去了,迎来崭新的一年,在过去的半年里,我不仅在课内,还在课外通过各种方式了解和学习到了不少新的语言、框架、技术等等。平时急于产出和学习,没能好好的整理所学,遂在这个寒假,从这篇文章开始,以一系列文章作为对这段时间所学所作进行一个系统性回顾整理的过程,希望给自己查漏补缺,并能给一些正在或将要学习其中某些内容的朋友们一点启发。
这篇文章,将浅层概述Springboot的原理,配置,架构等,一步步讲解,采用常规的java技术栈,手把手完成一个简易的个人信息系统后端(注册、登录、改密、增和改个人信息)
导航
发文顺序从上到下,顺序大致按我接触它们的时间点排的,可能个别文章会提早或延后
- Springboot【✅】
- Java EE/ Jakata EE
- 网络安全框架 (Java)
- Spring Security
- Sa-Token
- Vue.js
- Web UI
- Mybatis
- Auto.js
- 浏览器3D引擎(轻量级): Three.js
- Uniapp / Unicloud 开发
- 微信小程序开发
- 前端深度学习引擎: Tensorflow.js
- 其他
- 利用Git管理源代码
- 利用maven管理java项目
- 利用npm管理项目
- Node.js配置和管理
- Sqlite: 内存中的轻数据库
- 软件过程和项目管理浅谈
- 打造自己的前端组件库
- 软件测试工具
- 文档撰写利器
正文
Spring是什么?SpringBoot呢?
不作赘述,简单了解一下:
苦于早期Javaee EJB的笨重繁琐,Spring框架诞生了,便捷的注解等特性使java应用的开发难度大大降低,Spring迅速风靡全球。SpringBoot是建立在Spring基础上的一个开源框架,提供了默认的Spring环境,对Spring的配置和开发进行了再一步简化,使得程序员更专注于业务代码,渐渐成为java微服务开发的利器之一。
创建SpringBoot项目
在创建项目之前,建议在你的开发环境中安装java8+和maven,并选择一款适合你的Java IDE,并为你的IDE配置好jdk和maven。我选择的是Intellij IDEA和VsCode(偶尔),VsCode须安装Java Extension Pack和Spring Boot Extension Pack。
maven命令行创建(不推荐)
- cmd下cd进入你准备存放项目的目录(我更喜欢在资源管理器地址栏直接cmd :-))
-
mvn archetype:generate -DgroupId=com.evanpatchouli.demo -DartifactId=SpringBootDemo_mvn -DinteractiveMode=false
你将会得到这样的目录↓(-DartifactId:项目文件名)mvn第一步结果 - 接着就是在pom.xml加入各种springboot依赖使这个基础的java项目变成springboot项目
- 这样就完成了,是不是很繁琐,最后开发的时候还是要回到IDE,那直接用IDE创建项目何乐而不为呢?所以不推荐
VsCode(推荐)
VsCode好处在于,美观、易用、免费和轻量(没加装太多插件的前提下)
-
Ctrl + Shift + P > Spring Initializr > 选择Maven
Spring初始化 - 选择SpringBoot版本,如果你需要使用新出的SpringBoot 3.x,请确保你的jdk17+
- 选择语言,Java > GroupId > Artifact Id > jdk版本:你使用的
注意GroupId和Artifact Id和上面maven不同,GroupId对应com.evanpatchouli,ArifactId对应demo,包着项目的目录会在最后一步让你选择 -
接着这步是提前配备一些工具,有不少都是常用的,可以按需选择。我准备在示例里写个常规的web项目,选择了以下这5个,支持了SpringMVC和MySql,并选用其余的快速开发
选择工具 -
到这里,就创建成功了!你将会得到这样的目录↓
vscode_success - 注意了,还没完,打开终端:
mvn help:system
,看看有没有报错。比如我在第2步选择了springboot 2.7.8,但实际上我的maven镜像是腾讯的,他们仓库里面貌似没有这么高的版本(你问我那为啥还选2.7.8?最新的Spring Initializr给我的选项最低就是它了...),我就把pom.xml里面的换成了2.3.7 RELEASE,或者你去maven官方仓库下对应版本的jar放到本地仓库也行。接着又爆了个错:Failed to calculate Effective POM
,我才发现vscode的maven配置里settings.xml的s漏打了,所以请务必确保确保你的环境配置正确。 - 尝试运行一下,先进行一些配置,配置端口和mysql,因为我启用了mysql
# 应用名称
spring.application.name=demo
# 应用服务 WEB 访问端口
server.port=8080
# MySQL数据库驱动:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据源名称
spring.datasource.name=defaultDataSource
# 数据库连接地址
spring.datasource.url=jdbc:mysql://localhost:3306/speingdemo?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT
# 数据库用户名&密码:
spring.datasource.username=root
spring.datasource.password=root
不出意外的话,显示Started DemoApplication in 9.041 seconds (JVM running for 9.802)就成功了
IntelliJ IDEA(推荐)
在Java开发上,IDEA是一款极好的IDE,我通常会选择IDEA来开发SpringBoot
需要注意的是,版本较旧的IDEA可能不支持较新版本的SpringBoot,比如我现在用的是2019的,在发布SpringBoot 3.0 那天我捣鼓了半天才意识到我的IDEA不支持最新版的Spring
- 新建项目 > Spring Initializr ,如果default的你打不开,可以换个网络、看看一下idea的proxy、还有maven镜像路径是不是https的,或者可以使用阿里的:https://start.aliyun.com
idea初始化项目 - 剩余步骤和vscode里介绍的差不离,记得Type选maven,不要选maven.pom
-
创建完成后,等它加载依赖,初次加载依赖可能要不少时间,耐心等待,等上方工具栏这玩意出来,就说明OK了,可以运行
SpringInit_success_idea
Initializr 网页创建
- https://start.spring.io
- https://start.aliyun.com
- 创建好会下载项目.zip。阿里云的甚至能选择生成工具和架构的示例代码,有兴趣可以看看
SpringMVC是什么?
-
概念: MVC,全称Model-View-Controller(模型、视图、控制器),我摆一张springmvcspringmvc
前端(携带数据)请求指定路径的接口(Controller),接口接受数据,进行业务处理,业务代码操作Mapper接口操作数据库,将结果最终返回到Controller上,对业务结果做一个结构化的封装再返回给前端,前端拿到响应结果后,在页面上进行渲染。
手写springmvc:简易账号系统
示例项目将采用前后端分离(Vue+SpringBoot)开发、整合发布的模式
- 完善application.properties,为mvc配置静态资源路径(static目录);如果要将视图层放在templates下,则必须采用thymeleaf,jsf等这类技术写前端。
#视图层控制
spring.mvc.static-path-pattern=/**
-
创建符合mvc形式的文件结构
springmvc目录结构
创建实体类
- 创建实体类User,用lombok的注解代替样板代码,手写一个2参的构造函数
package com.evanpatchouli.demo.model.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
int id;
private String name;
private String pwd;
public User(String name, String pwd){
this.name = name;
this.pwd = pwd;
}
}
Mapper层
- 写Mapper层,插入和查询数据
@Mapper
注解(Mybatis)声明这属于Mapper层
@Repository
是Spring JPA的一个注解,写上可以避免爆未注入Bean的错(虽然不影响运行)
示例项目里将采用Mybatis注解编写sql语句,xml写法很容易找到,我就不拿来用了。格式:@Xxx(value = "sql语句")
("value="可省略)
(Mybatis注解sql的好处在于快,动态sql有专门的方案,不输xml)
package com.evanpatchouli.demo.model.mapper;
import com.evanpatchouli.demo.model.entity.User;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;
@Mapper
@Repository
public interface UserMapper {
@Insert(value = "insert into user(name,pwd) values (#{name},#{pwd})")
void insert(User user);
@Select("select * from user where name=#{name} and pwd=#{pwd}")
User select(@Param("name") String name, @Param("pwd") String pwd);
}
Service层
- 编写Service层,调用Mapper
@Service声明这属于Service层,为其注册Bean
@Autowired帮我们将参数自动注入Mapper接口
package com.evanpatchouli.demo.controller.service;
import com.evanpatchouli.demo.model.entity.User;
import com.evanpatchouli.demo.model.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public void register(User user){
userMapper.insert(user);
};
public User select(String name, String pwd){
return userMapper.select(name, pwd);
}
}
- 封装一个返回体(非必要)
封装返回体的方式有很多,自定义泛型等等,平常我更喜欢直接new一个哈希Map作为返回体
Restful接口返回的数据结构也没有规范标准,具体不赘述,总之要保证数据,信息,成功与否传达清晰
示例项目中,为了结构简明,我定义了几个返回值为Map的静态方法和常用Http状态码枚举类
Result:
package com.evanpatchouli.demo.util;
import java.util.HashMap;
import java.util.Map;
public class Result {
public static Map error() {
Map error = new HashMap();
error.put("code",null);
error.put("type",null);
error.put("info",null);
return error;
}
public static Map result(){
Map result = new HashMap();
result.put("status",null);
result.put("error",error());
result.put("data",null);
result.put("msg",null);
return result;
}
public static Map ok(){
Map result = result();
result.replace("status",HttpStatus.OK.code);
result.replace("error",null);
result.replace("data",null);
result.replace("msg","success");
return result;
}
public static Map ok(String msg){
Map result = result();
result.replace("status",HttpStatus.OK.code);
result.replace("error",null);
result.replace("msg",msg);
result.replace("data",null);
return result;
}
public static Map ok(Object o, String msg){
Map result = result();
result.replace("status",HttpStatus.OK.code);
result.replace("error",null);
result.replace("msg",msg);
result.replace("data",o);
return result;
}
public static Map ResetContent(){
Map result = result();
result.replace("status",HttpStatus.ResetContent.code);
Map error = error();
error.replace("type","ResetContent");
error.replace("info","ResetContent");
error.replace("code",null);
result.replace("error",error);
result.replace("msg","error");
return result;
}
public static Map ResetContent(int code, String msg){
Map result = result();
result.replace("status",HttpStatus.ResetContent.code);
Map error = error();
error.replace("type","ResetContent");
error.replace("info","ResetContent");
error.replace("code",code);
result.replace("error",error);
result.replace("msg",msg);
return result;
}
public static Map Unauthorized(){
Map result = result();
result.replace("status",HttpStatus.Unauthorized.code);
Map error = error();
error.replace("type","Unauthorized");
error.replace("info","Unauthorized");
error.replace("code",null);
result.replace("error",error);
result.replace("msg","error");
return result;
}
public static Map Unauthorized(int code, String msg){
Map result = result();
result.replace("status",HttpStatus.Unauthorized.code);
Map error = error();
error.replace("type","Unauthorized");
error.replace("info","Unauthorized");
error.replace("code",code);
result.replace("error",error);
result.replace("msg",msg);
return result;
}
public static Map NotFound(){
Map result = result();
result.replace("status",HttpStatus.NotFound.code);
Map error = error();
error.replace("type","NotFound");
error.replace("info","NotFound");
error.replace("code",null);
result.replace("error",error);
result.replace("msg","error");
return result;
}
public static Map NotFound(int code, String msg){
Map result = result();
result.replace("status",HttpStatus.NotFound.code);
Map error = error();
error.replace("type","NotFound");
error.replace("info","NotFound");
error.replace("code",code);
result.replace("error",error);
result.replace("msg",msg);
return result;
}
public static Map Forbidden(){
Map result = result();
result.replace("status",HttpStatus.Forbidden.code);
Map error = error();
error.replace("type","Forbidden");
error.replace("info","Forbidden");
error.replace("code",null);
result.replace("error",error);
result.replace("msg","error");
return result;
}
public static Map Forbidden(int code, String msg){
Map result = result();
result.replace("status",HttpStatus.Forbidden.code);
Map error = error();
error.replace("type","Forbidden");
error.replace("info","Forbidden");
error.replace("code",code);
result.replace("error",error);
result.replace("msg",msg);
return result;
}
public static Map error(String typename, int code, String msg){
Map result = result();
result.replace("status",HttpStatus.valueOf(typename));
Map error = error();
error.replace("type",typename);
error.replace("info",typename);
error.replace("code",code);
result.replace("error",error);
result.replace("msg",msg);
return result;
}
public static Map error(String typename, int code, String msg, Object o){
Map result = error(typename, code, msg);
result.replace("data",o);
return result;
}
}
HttpStatus:
package com.evanpatchouli.demo.util;
public enum HttpStatus {
OK(200),
ResetContent(205),
Unauthorized(401),
Forbidden(403),
NotFound(404);
public final int code;
HttpStatus(int code) {
this.code = code;
}
}
也可以不按这个写,直接在Controller里new一个HashMap拿来用,比如:
// 我采取的是一种符合RFC规范的rest接口返回结构,但rest去遵守RFC,往往会比较难确定用哪个Http状态码,比如去查资料各路大佬也是对密码错误这种情况的返回码争论不休,有说200的,有说401的,有用403的,有选409的,有用422的,也有303的...细看他们阐述的理由似乎都很有道理
// {
// "status": 404,
// "error": {
// "code": 74,
// "type": "NotFound",
// "info": "The account you want to login do not exist!"
// },
// "msg": "error",
// "data": null,
// }
// 不遵守RFC的rest接口,status统一200,虽然显得"不规范",但不用头疼纠结该返回什么Http状态码,有的大厂就在这样使用,比如 META(Facebook)的
// {
// "status": 200, //甚至直接不写
// "type": "OAuthException",
// "message": "(#803) Some of the aliases you requested do not exist: foo.bar"
// }
Map result = new HashMap();
Map error = new HashMap();
error.put("code",1); //错误(业务)编号,通常和Info一起存放在一个表,或者Set或者Map或者枚举类等里面,现在我这里还没有做这个Map
error.put("type","NotFound"); //Not Found,通常和它对应的数字码存在Map,枚举等等
error.put("Info","请求的资源不存在"); //先直接写了
result.put("status", 404); //http数字码
result.put("error", error);
result.put("data", null); //如果请求源需要返回数据,就放在这里
Controller层(api):登陆注册接口
- 编写Controller层接口
@CrossOrigin
: 前后端分离时,允许请求源跨域名访问接口
@RestController
: 这代表每个接口的返回值都将被转化成JSON返回给请求源,适合前后端分离;如果是不分离开发的,采用@Controller
,想返回JSON时在相应的方法上加@ResponseBody
@RequestMapping
指定笼统的url请求路径,不限制请求方式
@GetMapping
和@PostMapping
代表GET和POST,PUT等等以此类推,可用以实现同一路径分化
在这个用户接口里,用了@PathVariable
和@RequestParam
两种方式传参,两种放到一起来比较一下使用上的不同:虽然处理数据的方式如出一辙,但在URL上可以明显感受到不同,PathVaribale是指向不同的路径(资源),做初步的分化,RequestParam往往是对这个路径上资源进行操作所需要的参数,比如这里的model=user指向user资源,action=register指向注册这个操作,而注册user则需要name和pwd这两个参数。(极度的restful接口风格)
@RequestParam可以设置参数是否必须传和默认值,格式:@RequestParam(required = false, defaultValue = "666") String pwd
,defaultValue必须是字符串,如果你的参数类型是int, boolean等,它会自动转换,不用你手动转换类型
实际开发中,可以不用将他们俩掺和着写,分成两个不同路径的函数就行。稍后章节还会展示另一种接受数据的方式
注册时,先查询此号有无
登陆时,核对一下
如果model没对上user,或action也没对上,返回404 NotFound
package com.evanpatchouli.demo.controller;
import com.evanpatchouli.demo.controller.service.UserService;
import com.evanpatchouli.demo.model.entity.User;
import com.evanpatchouli.demo.util.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@CrossOrigin
@RestController
@RequestMapping("/api")
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/{model}/{action}") // http://localhost:8080/api/register
public Map register(@PathVariable String model, @PathVariable String action, @RequestParam String name, @RequestParam String pwd){
if(model.equals("user")){
User user = new User();
switch (action){
case "register" :
user = userService.select(name, pwd);
if(user != null){
return Result.ResetContent(1,"账号已存在");
}
user = new User(name, pwd);
userService.register(user);
user = userService.select(name, pwd);
return Result.ok(user, "注册成功");
case "login" :
user = userService.select(name, pwd);
if(user != null){
return Result.ok(user,"登录成功");
}
return Result.Unauthorized(1,"密码错误");
}
}
return Result.NotFound();
}
}
- 测试一下接口,postman,apipost都可以
测试用例:
(1)成功注册
(2)注册同样的账号
(3)登录正确的账号
(4)输入错误的密码,登录失败
(5)访问不存在的资源
测试结果没有问题,所以到这一步,基本的注册登录模块已经完成了。
修改密码接口
- 增添一个修改密码的接口
Mapper :
@Update("update user set pwd=#{pwd} where id=#{id}")
void updatePwd(@Param("id") int id,@Param("pwd") String pwd);
Service:
public void updatePwd(int id, String pwd) { userMapper.updatePwd(id, pwd); }
Controller:
接口传参加上@RequestParam(required = false) String pwd2
case "updatePwd" :
user = userService.select(name, pwd);
if(user != null){
userService.updatePwd(user.getId(),pwd2);
return Result.ok("修改成功");
}
return Result.Forbidden(1,"密码错误");
测试:
(1)修改密码,原密码错误
(2)正确修改密码
到这里,一个极简版的账号模块就完成了。
- 添加个人信息和对应的接口
先在数据库里创建userinfo表,单独存储用户个人信息
/*
Navicat MySQL Data Transfer
Source Server : localhost_3306
Source Server Version : 50739
Source Host : localhost:3306
Source Database : springdemo
Target Server Type : MYSQL
Target Server Version : 50739
File Encoding : 65001
Date: 2023-01-10 16:18:17
*/
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for `userinfo`
-- ----------------------------
DROP TABLE IF EXISTS `userinfo`;
CREATE TABLE `userinfo` (
`id` int(11) NOT NULL,
`nick` varchar(255) DEFAULT NULL,
`phone` varchar(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of userinfo
-- ----------------------------
这里我将展示另一种传递接收数据的方式,新的Controller:
新建了一个接口和增改了第一个接口,先看新增的:函数入参里面除了上面提到的pathvaribale, requestparam,多了一个@RequestBody
,这和前面两个不一样,pvar和rparm都是写在url路径上的,RequestBody代表的是请求的正文,传输数据体的实体,你可以为它指定类型为一个自定义的类或者Map,这个类包含了你需要的数据字段,发送请求的正文按照你的格式写,接口接收时就能映射属性值到你自定义的类上去,好处是url上就看不到了。这个实体就叫dto。
Controller
package com.evanpatchouli.demo.controller;
import com.evanpatchouli.demo.controller.service.UserService;
import com.evanpatchouli.demo.model.entity.InfoDto;
import com.evanpatchouli.demo.model.entity.User;
import com.evanpatchouli.demo.util.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@CrossOrigin
@RestController
@RequestMapping("/api")
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/{model}/{action}") // http://localhost:8080/api/
public Map userCount(@PathVariable String model, @PathVariable String action, @RequestParam(required = false) String name, @RequestParam(required = false) String pwd, @RequestParam(required = false, defaultValue = "ni") String pwd2, @RequestParam(required = false, defaultValue = "0") int id){
if(model.equals("user")){
User user = new User();
switch (action){
case "register" :
user = userService.select(name, pwd);
if(user != null){
return Result.ResetContent(1,"账号已存在");
}
user = new User(name, pwd);
userService.register(user);
user = userService.select(name, pwd);
return Result.ok(user, "注册成功");
case "login" :
user = userService.select(name, pwd);
if(user != null){
return Result.ok(user,"登录成功");
}
return Result.Unauthorized(1,"密码错误");
case "query":
user = userService.selectById(id);
if(user != null){
return Result.ok(user, "success");
}
break;
case "updatePwd" :
user = userService.select(name, pwd);
if(user != null){
userService.updatePwd(user.getId(),pwd2);
return Result.ok("修改成功");
}
return Result.Forbidden(1,"密码错误");
case "info" :
Map userWithInfo = userService.getUserWithInfo(id);
if(userWithInfo != null){
return Result.ok(userWithInfo, "携带Info的user");
}
break;
default:
break;
}
}
return Result.NotFound();
}
@PostMapping("/userInfo/{action}")// http://localhost:8080/api/userInfo/
public Map userInfo(@PathVariable String action, @RequestParam(required = false, defaultValue = "0") int id, @RequestBody(required = false) InfoDto infoDto){
switch (action) {
case "insert":{
if(infoDto==null){
return Result.ResetContent();
}
if(userService.selectById(infoDto.getId())==null){
break;
}
userService.insertInfo(infoDto);
Map info = userService.selectInfo(infoDto.getId());
if(info != null){
return Result.ok(info, "success");
}
break;
}
case "query":{
Map info = userService.selectInfo(id);
if(info != null){
return Result.ok(info, "success");
}
break;
}
case "alter":{
if(infoDto==null){
return Result.ResetContent();
}
if(userService.selectById(infoDto.getId())==null){
break;
}
Map info = userService.updateInfo(infoDto);
if(infoDto !=null){
return Result.ok(info, "success");
}
break;
}
}
return Result.NotFound();
}
}
InfoDto:
package com.evanpatchouli.demo.model.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class InfoDto {
private int id;
private String nick;
private String phone;
}
Mapper新增:
注意最后一个接口,进行了一个连接查询,返回类型是Map,因为Mybatis是根据字段建立映射关系的,所以返回类型不一定非要是一个实体类,因为我不想专门创建一个有个人信息的User2,也不想在原User里面增加个人信息属性,这样前面那个select出来的user会携带null的个人信息,这很难受,所以我使用Map接受。
// 根据用户id获取用户
@Select("select * from user where id=#{id}")
User selectById(int id);
// 插入用户信息到userinfo表
@Insert("insert into userinfo(id,nick,phone) values (#{id},#{nick},#{phone})")
void insertInfo(InfoDto infoDto);
// 更新用户信息
@Update(value = "update userinfo set nick=#{nick},phone=#{phone} where id=#{id}")
void updateInfo(InfoDto infoDto);
// 根据用户id获取单独的info信息
@Select("select * from userinfo where id=#{id}")
Map selectInfo(int id);
// 获取用户时,携带上个人信息
@Select("select * from user u,userinfo i where u.id=#{id} and u.id=i.id")
Map selectUserWithInfo(int id);
Service新增:
public User selectById(int id){ return userMapper.selectById(id); }
public void insertInfo(InfoDto infoDto) { userMapper.insertInfo(infoDto); }
public Map selectInfo(int id) { return userMapper.selectInfo(id); }
public Map updateInfo(InfoDto infoDto) {
userMapper.updateInfo(infoDto);
return userMapper.selectInfo(infoDto.getId());
}
public Map getUserWithInfo(int id){
return userMapper.selectUserWithInfo(id);
}
测试用例:
(1)插入一条info,用户id1
(2)查询刚才的info
(3)修改刚才的info
(4)查询携带info的user,id=1
测试没有问题,到这里,一个极简版的个人信息系统的后端就完成了。如果清楚了上面所讲的,恭喜成功入门springboot,已经有能力去完成一些业务简单的后端了,殊途同归。
后记
这个示例项目里,目前,创建账号后,是没有对应的info的,如果直接拿来给前端用,前端直接调取携带info的user接口,是返回404 NotFound的。对此,后端可以在创建账号时,顺便往info里查一条默认的记录,或者前端完成注册并登录后,强制要求必须先完善个人信息。
使用dto之前,应当检查其属性是否都不为空,我在这提供一个检查dto的静态方法
package com.evanpatchouli.demo.util;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.HashSet;
import java.util.Set;
public class DtoUtil {
public static Boolean hasNoBlank(Object obj, Set<String> ign){
if(obj==null){ // obj是你要检查的dto
return false;
}
if(ign==null){ // ign是一个哈希Set,调用方法前把你要忽略的字段(比如类型为int的、即使为空也无所谓、接口重用有时候需要有时候不需要,另行检测等)丢进ign,检查的时候跳过它们
ign = new HashSet<>();
}
Field[] fields = obj.getClass().getDeclaredFields();
Boolean rs = true;
for (Field field : fields) {
field.setAccessible(true);
Object fieldValue = null;
String fieldName = "";
try {
fieldValue = field.get(obj); //得到属性值
//Type fieldType = field.getGenericType();//得到属性类型
fieldName = field.getName(); // 得到属性名
//System.out.println("属性类型:" + fieldType + ",属性名:" + fieldName + ",属性值:" + fieldValue);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
if(ign.contains(fieldName)){
System.out.println(fieldName+" 属性忽略检测");
continue;
}
if (fieldValue == null || fieldValue.equals("")) { //只要有一个属性值不为null 就返回false 表示对象不为null
System.out.println("对象有空值");
rs = false;
break;
}
}
return rs;
}
}
项目源码
比起上面的代码稍有补充和改动
https://github.com/Evanpatchouli/SpringBoot_Demo_evanpatchouli