用户注册

作为一个现代化电商平台,什么最重要呢?of course 是用户,广大用户群体是支持我们可持续发展的基石,顾客是上帝, 虽然在当今上帝已经不被重视了,特别是很多的平台对于老用户就是恨不得赶紧Out...但是用户量是一切的基础,那我们就开始创建我们的上帝吧!

创建数据库


数据库的部分,我在这里就不多讲了,大家需要的话可以直接去传送门 抓取脚本expensive-shop.sql.

生成UserMapper


参考上节内容:传送门

编写业务逻辑


首先,我们先来分析一下要注册一个用户,我们系统都需要做哪些动作?

  • validate

    • input string(校验输入我们需要通过两个角度处理)

      • FrontEnd valid

      前端校验是为了降低我们服务器端压力而做的一部分校验,这部分校验可以拦截大多数的错误请求。

      • Backend valid

      后端校验是为了防止某些不法小伙伴绕开前端从而直接访问我们的api造成数据请求服务器错误,或者前端小伙伴程序有bug...无论是哪一种可能性,都有可能造成严重的后果。

    • email & mobile invalid

    因为本人没有追求email / 短信发送服务器,所以这一步就pass,小伙伴们可以自行研究哈。

  • control
    • create user

      校验通过后,就可以进行创建用户的动作了。

      接下来,我们就可以来实际编码实现业务了,我们使用最基本的分层架构,在之前我们已经通过Mybatis Generator工具生成了基本的pojo,mapper,对于简单的操作我们只需要再编写servicecontroller层就可以完成我们的开发工作了。

编写user service


mscx-shop-service中创建com.liferunner.service.IUserService接口,包含2个方法findUserByUserNamecreateUser,如下:

public interface IUserService {

    /**
* 根据用户名查询用户是否存在
*
* @param username
* @return
*/
Users findUserByUserName(String username); /**
* 创建用户
*
* @param userRequestDTO 用户请求dto
* @return 当前用户
*/
Users createUser(UserRequestDTO userRequestDTO) throws Exception;
}

接着,我们需要具体实现这个接口类,如下:

@Service
@Slf4j
public class UserServiceImpl implements IUserService {
private final String FACE_IMG = "https://avatars1.githubusercontent.com/u/4083152?s=88&v=4"; // 构造器注入
private final UsersMapper usersMapper;
private final Sid sid; @Autowired
public UserServiceImpl(UsersMapper usersMapper, Sid sid) {
this.usersMapper = usersMapper;
this.sid = sid;
} @Override
public Users findUserByUserName(String username) {
// 构建查询条件
Example example = new Example(Users.class);
val condition = example.createCriteria()
.andEqualTo("username", username);
return this.usersMapper.selectOneByExample(example);
} @Transactional(propagation = Propagation.REQUIRED)
@Override
public Users createUser(UserRequestDTO userRequestDTO) throws Exception {
log.info("======begin create user : {}=======", userRequestDTO);
val user = Users.builder()
.id(sid.next()) //生成分布式id
.username(userRequestDTO.getUsername())
.password(MD5GeneratorTools.getMD5Str(userRequestDTO.getPassword()))
.birthday(DateUtils.parseDate("1970-01-01", "yyyy-MM-dd"))
.nickname(userRequestDTO.getUsername())
.face(this.FACE_IMG)
.sex(SexEnum.secret.type)
.createdTime(new Date())
.updatedTime(new Date())
.build();
this.usersMapper.insertSelective(user);
log.info("======end create user : {}=======", userRequestDTO);
return user;
}
}

这里有几处地方有必要说明一下:

UserServiceImpl#findUserByUserName 说明

  • tk.mybatis.mapper.entity.Example 通过使用Example来构建mybatis的查询参数,如果有多个查询条件,可以通过example.createCriteria().addxxx逐一添加。

UserServiceImpl#createUser 说明

  • @Transactional(propagation = Propagation.REQUIRED),开启事务,选择事务传播级别为REQUIRED,表示必须要有一个事务存在,如果调用者不存在事务,那本方法就自己开启一个新的事物,如果调用方本身存在一个活跃的事务,那本方法就加入到它里面(同生共死)。
  • org.n3r.idworker.Sid, 这个是一个开源的 分布式ID生成器组件,传送门, 后期有机会的话,会专门写一个id生成器文章。
  • MD5GeneratorTools 是用来对数据进行MD5加密的工具类,大家可以在源码中下载。也可以直接使用java.security.MessageDigest 直接加密实现,总之密码不能明文存储就行了。
  • SexEnum 这个是一个表述性别类型的枚举,在我们编码的规范中,尽量要求不要出现Magic number,就是开发界常说的魔术数字(即1,2,300...)
  • 这里的日志打印,可能有人会问为什么你没有声明类似:private final static Logger logger = LoggerFactory.getLogger(UserServiceImpl.class); ,这是因为我们在开始的时候,我们引入了lombok依赖,不记得的同学可以参考传送门。在这里依赖中,它继承了很多的日志组件,我们只需要使用一个注解lombok.extern.slf4j.Slf4j来开启日志,使用log.info..就可以了。
  • UserRequestDTO 又是个什么鬼?在我们开发的过程中,很可能会有大批量的参数需要传递,这时我们如果使用xxx#(String aa,Integer bb,Boolean cc...)会让我们烦不胜数,而且看着也不美观,这时候我们就可以选择创建一个新对象来帮助我们传递数据,那么也就是我们的UserRequestDTO对象,所谓的DTO就是Data Transfer Object的首字母缩写,顾名思义,它是用来传递数据对象用的。

编写user controller


同样在mscx-shop-api中,创建com.liferunner.api.controller.UserController,实现用户创建。

@RestController
@RequestMapping(name = "/users")
@Slf4j
@Api(tags="用户管理")
public class UserController { @Autowired
private IUserService userService; @ApiOperation("校验是否重名")
@GetMapping("/validateUsername")
public JsonResponse validateUsername(@RequestParam String username) {
// 判断用户名是否非法
if (StringUtils.isBlank(username))
return JsonResponse.errorMsg("用户名不能为空!");
if (null != userService.findUserByUserName(username))
return JsonResponse.errorMsg("用户名已存在!");
// 用户名可用
return JsonResponse.ok();
} @ApiOperation("创建用户")
@PostMapping("/create")
public JsonResponse createUser(@RequestBody UserRequestDTO userRequestDTO) {
try {
if (StringUtils.isBlank(userRequestDTO.getUsername()))
return JsonResponse.errorMsg("用户名不能为空");
if (null != this.userService.findUserByUserName(userRequestDTO.getUsername())) {
return JsonResponse.errorMsg("用户名已存在!");
}
if (StringUtils.isBlank(userRequestDTO.getPassword()) ||
StringUtils.isBlank(userRequestDTO.getConfimPassword()) ||
userRequestDTO.getPassword().length() < 8) {
return JsonResponse.errorMsg("密码为空或长度小于8位");
}
if (!userRequestDTO.getPassword().equals(userRequestDTO.getConfimPassword()))
return JsonResponse.errorMsg("两次密码不一致!");
val user = this.userService.createUser(userRequestDTO);
if (null != user)
return JsonResponse.ok(user);
} catch (Exception e) {
log.error("创建用户失败,{}", userRequestDTO);
}
return JsonResponse.errorMsg("创建用户失败");
}
}

UserController#validateUsername(username) 说明

  • JsonResponse对象是为了方便返回给客户端一个统一的格式而封装的数据对象。
@Data
@NoArgsConstructor
@AllArgsConstructor
public class JsonResponse { // 定义jackson对象
private static final ObjectMapper MAPPER = new ObjectMapper();
// 响应业务状态
private Integer status;
// 响应消息
private String message;
// 响应中的数据
private Object data; public static JsonResponse build(Integer status, String msg, Object data) {
return new JsonResponse(status, msg, data);
} public static JsonResponse ok(Object data) {
return new JsonResponse(data);
} public static JsonResponse ok() {
return new JsonResponse(null);
} public static JsonResponse errorMsg(String msg) {
return new JsonResponse(500, msg, null);
} public static JsonResponse errorMap(Object data) {
return new JsonResponse(501, "error", data);
} public static JsonResponse errorTokenMsg(String msg) {
return new JsonResponse(502, msg, null);
} public static JsonResponse errorException(String msg) {
return new JsonResponse(555, msg, null);
} public static JsonResponse errorUserQQ(String msg) {
return new JsonResponse(556, msg, null);
} public JsonResponse(Object data) {
this.status = 200;
this.message = "OK";
this.data = data;
} public Boolean isOK() {
return this.status == 200;
}
}

UserController#createUser(UserRequestDTO) 说明

  • 如上文所讲,需要先做各种校验
  • 成功则返回JsonResponse
  • 细心的同学可能看到了上文中有几个注解@Api(tags="用户管理"),@ApiOperation("创建用户"),这个是Swagger 的注解,我们会在下一节和大家详细探讨,以及如何生成off-line docs

测试API


在我们每次修改完成之后,都尽可能的mvn clean install一次,因为我们隶属不同的project,如果不重新安装一次,偶尔遇到的问题会让人怀疑人生的。

...
[INFO] expensive-shop ..................................... SUCCESS [ 1.220 s]
[INFO] mscx-shop-common ................................... SUCCESS [ 9.440 s]
[INFO] mscx-shop-pojo ..................................... SUCCESS [ 2.020 s]
[INFO] mscx-shop-mapper ................................... SUCCESS [ 1.564 s]
[INFO] mscx-shop-service .................................. SUCCESS [ 1.366 s]
[INFO] mscx-shop-api ...................................... SUCCESS [ 4.614 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 20.739 s
[INFO] Finished at: 2019-11-06T14:53:55+08:00
[INFO] ------------------------------------------------------------------------

当看到上述运行结果之后,就可以启动我们的应用就行测试啦~

UserController#validateUsername(username) 测试

测试API的方式有很多种,比如curl localhost:8080/validateUsername,在比如使用超级流行的Postman也是完全ok的,我这里用的是之前在第一篇中和大家所说的一个插件Restful Toolkit(可以实现和postman一样的简单效果,同时还能帮助我们生成一部分测试信息),当我们应用启动之后,效果如下图,

我们可以看到,插件帮我们生成了几个测试方法,比如我们点击validateUsername,下方就会生成当前方法是一个包含username参数的GET方法,demoData是插件默认给我们生成的测试数据。可以随意修改。

点击Send:



可以看到请求成功了,并且返回我们自定义的JSON格式数据。

UserController#createUser(UserRequestDTO) 测试

接着我们继续测试用户注册接口,请求如下:



可以看到,当我们选择create方法时,插件自动帮我们设置请求类型为POST,并且RequestBody的默认值也帮助我们生成了,我只修改了默认的usernamepassword值,confimPassword的默认值我没有变动,那按照我们的校验逻辑,它应该返回的是return JsonResponse.errorMsg("两次密码不一致!");这一行,点击Send:



修改confimPassword12345678,点击Send:



可以看到,创建用户成功,并且将当前创建的用户返回到了我们请求客户端。那么我们继续重复点击创建,会怎么样呢?继续Send:



可以看到,我们的验证重复用户也已经生效啦。

下节预告


下一节我们将学习如何使用Swagger自动生成API接口文档给前端,以及如果没有外部网络的情况下,或者需要和第三方平台对接的时候,我们如何生成离线文档给到第三方。

gogogo!

[springboot 开发单体web shop] 3. 用户注册实现的更多相关文章

  1. [springboot 开发单体web shop] 1. 前言介绍和环境搭建

    前言介绍和环境搭建 简述 springboot 本身是为了做服务化用的,我们为什么要反其道使用它来开发一份单体web应用呢? 在我们现实的开发工作中,还有大量的业务系统使用的是单体应用,特别是对于中小 ...

  2. [springboot 开发单体web shop] 4. Swagger生成Javadoc

    Swagger生成JavaDoc 在日常的工作中,特别是现在前后端分离模式之下,接口的提供造成了我们前后端开发人员的沟通 成本大量提升,因为沟通不到位,不及时而造成的[撕币]事件都成了日常工作.特别是 ...

  3. [springboot 开发单体web shop] 5. 用户登录及首页展示

    用户登录及前端展示 用户登录 在之前的文章中我们实现了用户注册和验证功能,接下来我们继续实现它的登录,以及登录成功之后要在页面上显示的信息. 接下来,我们来编写代码. 实现service 在com.l ...

  4. [springboot 开发单体web shop] 6. 商品分类和轮播广告展示

    商品分类&轮播广告 因最近又被困在了OSGI技术POC,更新进度有点慢,希望大家不要怪罪哦. 上节 我们实现了登录之后前端的展示,如: 接着,我们来实现左侧分类栏目的功能. ## 商品分类|P ...

  5. [springboot 开发单体web shop] 2. Mybatis Generator 生成common mapper

    Mybatis Generator tool 在我们开启一个新项目的研发后,通常要编写很多的entity/pojo/dto/mapper/dao..., 大多研发兄弟们都会抱怨,为什么我要重复写CRU ...

  6. [springboot 开发单体web shop] 7. 多种形式提供商品列表

    上文回顾 上节 我们实现了仿jd的轮播广告以及商品分类的功能,并且讲解了不同的注入方式,本节我们将继续实现我们的电商主业务,商品信息的展示. 需求分析 首先,在我们开始本节编码之前,我们先来分析一下都 ...

  7. [springboot 开发单体web shop] 8. 商品详情&评价展示

    上文回顾 上节 我们实现了根据搜索关键词查询商品列表和根据商品分类查询,并且使用到了mybatis-pagehelper插件,讲解了如何使用插件来帮助我们快速实现分页数据查询.本文我们将继续开发商品详 ...

  8. 开发单体web shop] 6. 商品分类和轮播广告展示

    目录 商品分类&轮播广告 商品分类|ProductCategory 需求分析 开发梳理 编码实现 轮播广告|SlideAD 需求分析 开发梳理 编码实现 福利讲解 源码下载 下节预告 商品分类 ...

  9. Springboot开发web项目

    当前,Spring毫无疑问已经成为java后台对象管理标准框架,除了通过IOC能够管理我们的自定义对象的生命周期之外还提供了众多功能繁复的可配置功能模块.但同时带来了复杂的配置项,这对初学者而言简直是 ...

随机推荐

  1. 认识MongoDB复制集

    ​ 从这一篇开始,我们要踏上MongoDB进阶之路啦,想想还有点小开心呢.一筐猪镇楼. ​ 引入复制集 我们先来想一个场景,如果本地项目使用MongoDB,都是下载,安装,连接一条龙服务.这实际也就是 ...

  2. 使用Ingress来负载分发微服务

    目录 使用Ingress来负载分发微服务  Demo规划  准备Demo并完成部署  创建部署(Deployment)资源  创建服务(Service)资源  创建Ingress资源并配置转发规则  ...

  3. Maven私服Nexus的搭建

    # Maven私服Nexus的搭建 ## 私服存在的合理性 Maven中的依赖是从服务器仓库中下载的,Maven的仓库只有两大类: - 1) 本地仓库 - 2) 远程仓库,其中在远程仓库中又分成了3种 ...

  4. B/S 端构建的基于 WebGL 3D 可视化档案馆管理系统

    前言 档案管理系统是通过建立统一的标准以规范整个文件管理,包括规范各业务系统的文件管理的完整的档案资源信息共享服务平台,主要实现档案流水化采集功能.为企事业单位的档案现代化管理,提供完整的解决方案,档 ...

  5. 如何决定使用 HashMap 还是 TreeMap?

    问:如何决定使用 HashMap 还是 TreeMap? 介绍 TreeMap<K,V>的Key值是要求实现java.lang.Comparable,所以迭代的时候TreeMap默认是按照 ...

  6. e课表项目第二次冲刺周期第九天

    昨天完成了什么? 昨天,我查找了相关的资料,将数据库根据我们的课程信息进行了重新的设计,并将数据能够连上数据库,即在添加课程的界面,可以将添加的课程的信息,存储到数据库中,并且存储到课程表中,并注明是 ...

  7. js通过方法返回对象的注意点

    问题:js通过方法返回一个字面量对象和返回一个提前已经定义好的字面量对象有区别吗? 答案:有 我们先来看看第一种情况,fun1方法返回一个提前没定义的字面量对象,然后通过调用方法返回三个对象,分别是o ...

  8. PHP next

    1.函数的作用:返回数组当前元素位置的下一个元素 2.函数的参数: @param array &$array 3. 例子一:数组拷贝时,内部指针的位置也一起拷贝 <?php $arr1 ...

  9. 掌握git基本功

    前言 最近想把代码传到GitHub上,结果我发现的demo的npm全是本地安装,上穿到GitHub要死要死,几百M,然后我就搜了下怎么不上传node_modules弄了半天也没成功,于是准备静下心学一 ...

  10. [Luogu2593] [ZJOI2006]超级麻将

    题目地址 :https://www.luogu.org/problemnew/show/P2593. 无脑DP(虽说是抄的额) #include <iostream> #include & ...