欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

[springboot 开发单体web shop] 3. 用户注册实现

程序员文章站 2022-05-31 21:16:43
[TOC] 用户注册 作为一个现代化电商平台,什么最重要呢?of course 是用户,广大用户群体是支持我们可持续发展的基石, , 虽然在当今上帝已经不被重视了,特别是很多的平台对于老用户就是恨不得赶紧Out...但是用户量是一切的基础,那我们就开始创建我们的上帝吧! 创建数据库 数据库的部分,我 ......

用户注册

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

创建数据库


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

生成usermapper


参考上节内容:

编写业务逻辑


首先,我们先来分析一下要注册一个用户,我们系统都需要做哪些动作?
[springboot 开发单体web shop] 3. 用户注册实现

  • 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一样的简单效果,同时还能帮助我们生成一部分测试信息),当我们应用启动之后,效果如下图,
[springboot 开发单体web shop] 3. 用户注册实现

我们可以看到,插件帮我们生成了几个测试方法,比如我们点击validateusername,下方就会生成当前方法是一个包含username参数的get方法,demodata是插件默认给我们生成的测试数据。可以随意修改。
点击send:
[springboot 开发单体web shop] 3. 用户注册实现
可以看到请求成功了,并且返回我们自定义的json格式数据。

usercontroller#createuser(userrequestdto) 测试

接着我们继续测试用户注册接口,请求如下:
[springboot 开发单体web shop] 3. 用户注册实现
可以看到,当我们选择create方法时,插件自动帮我们设置请求类型为post,并且requestbody的默认值也帮助我们生成了,我只修改了默认的usernamepassword值,confimpassword的默认值我没有变动,那按照我们的校验逻辑,它应该返回的是return jsonresponse.errormsg("两次密码不一致!");这一行,点击send:
[springboot 开发单体web shop] 3. 用户注册实现
修改confimpassword12345678,点击send:
[springboot 开发单体web shop] 3. 用户注册实现
可以看到,创建用户成功,并且将当前创建的用户返回到了我们请求客户端。那么我们继续重复点击创建,会怎么样呢?继续send:
[springboot 开发单体web shop] 3. 用户注册实现
可以看到,我们的验证重复用户也已经生效啦。

下节预告


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