完整的电商平台后端API开发总结

对于开发一个Web项目来说,无论是电商还是其他品类的项目,注册与登录模块都是必不可少的;注册登录功能也是我们在日常生活中最长接触的,对于这个业务场景的需求与逻辑大概是没有什么需要详细介绍的,市面上常见的邮箱注册、手机注册、账号密码注册,其处理方式基本相同,我们这里使用账号密码注册的方式,实现整个平台的注册/登录功能;

让我们开始!

判断用户名是否存在

实现注册部分的代码,首先想到的就是,我们要对前端所发送过来的请求参数做验证,在有些项目中,会将请求参数的格式验证和合法性验证只写在前端校验,而后端只实现业务逻辑,我认为这是极其危险的编码习惯;当我们的项目放在线上的时候,就会有恶意用户绕过前端验证,直接访问我们的服务器,对线上业务造成破坏,因此前端验证是为了减轻一部分请求直接到达后端,但是相应的验证后端也要去做

那么,首先我们要实现的就是验证用户注册的用户名是否存在;我们首先来看一下用户表的结构及设计:

从图上我们可以看到用户表中常用的字段,我们用户表以用户的Id为主键,但注意:ID并不是自增长的,这与传统的Id设计不同,这里不是自增的原因是:当系统达到一定的体量时,用户数量激增,我们需要去做分布式集群,需要分库分表,这时自增的ID会给分库分表带来极大的困难,因为,出于日后系统优化的考虑,我们这里的数据库主键,不是自增的。

接口API(淘宝)开发

理清业务逻辑,看完数据库结构,我们着手开始编写业务代码;在整个项目代码的编写和接口的实现我们都遵循自底向上的方式,从数据库开始,实现数据的映射,业务实现,结果推送的流程,对应pojo映射---Service编写---Controller控制的过程。

那么,我们开始啦:首先,我们创建一个UserService接口,在接口中,我们编写我们第一个业务方法:

复制代码
/** * 判断用户名是否存在 */public boolean queryUsernameIsExist(String username);

我们传入一个userName,返回一个布尔值;有了接口之后,我们去实现这个方法:

我们在service工程中,新建一个Impl的包,在里面新建一个类UserServiceImpl,去实现UserService接口,并实现其中的方法;我们在这个方法中,需要操作User这个实体类,那么我们先把UserMapper引入进来:

复制代码
@Autowiredpublic UsersMapper usersMapper;

在方法中,我们使用Example这种使用条件查询的方式去做查询:

复制代码
@Transactional(propagation = Propagation.SUPPORTS)@Overridepublic boolean queryUsernameIsExist(String username) {
    Example userExample = new Example(Users.class);    Example.Criteria userCriteria = userExample.createCriteria();    userCriteria.andEqualTo("username",username);
    Users result  = usersMapper.selectOneByExample(userExample);
    return result == null ? false : true;}

在这个方法的实现中,我们使用了Example这种方式,Example映射一个实体类,获得一个example对象,为这个对象去添加相应的条件,Criteria对应的方法有很多,可以判断等于,大于,相似等各类条件,使用起来很方便,感兴趣的同学可以去阅读他的源码;这个方法返回一个Users对象,我们去判空,若为空,则用户名可用,若false,则用户名存在;

实现了Service之后,我们来编写Controller,我们在api工程中,新建一个Controller类。命名为PassportController,我们为他加上RestController注解,并加上路由地址,在这个Controller中,我们需要操作UserService来进行查询,那么,我们先将UserService注入进来:

复制代码
@Autowiredprivate UserService userService;

并定义一个方法,声明方法的路由地址:

复制代码
@GetMapping("/usernameIsExist")public IMOOCJSONResult usernameIsExist(@RequestParam String username) {    //判断用户名不能为空    if (StringUtils.isBlank(username)) {        return IMOOCJSONResult.errorMsg("用户名不能为空");    }
    //查找注册的用户名是否存在
    boolean isExist = userService.queryUsernameIsExist(username);    if (isExist) {        return IMOOCJSONResult.errorMsg("用户名已经存在");    }    // 请求成功,用户名没有重复    return IMOOCJSONResult.ok();}

在这个方法中,我们在判断username为空的时候使用了一个字符串的工具类,他是Apache提供的,我们需要首先引入他的依赖:

复制代码
<!-- apache 工具类 --><dependency>    <groupId>commons-codec</groupId>    <artifactId>commons-codec</artifactId>    <version>1.11</version></dependency><dependency>    <groupId>org.apache.commons</groupId>    <artifactId>commons-lang3</artifactId>    <version>3.4</version></dependency><dependency>    <groupId>commons-io</groupId>    <artifactId>commons-io</artifactId>    <version>1.3.2</version></dependency>

引入之后,我们便可以操作StringUtils,他提供了一个isBlank的方法,来判断字符串是否为空;

同时我们在方法中,可以看到一个IMOOCJSONResult对象,这是一个结果集对象,因为在代码中,我们需要返回的结果值类型很多,很不确定,并且,我们需要返回自定义的响应码来告诉前端请求成功与否,前端对约定好的各类返回值做统一处理,避免前端出现代码报错,影响用户体验,同时,自定义的结果响应能提高我们定位错误的速度,规范接口;因此我们先定义一个结果返回类:

复制代码
public class IMOOCJSONResult {
    // 定义jackson对象    private static final ObjectMapper MAPPER = new ObjectMapper();
    // 响应业务状态    private Integer status;
    // 响应消息    private String msg;
    // 响应中的数据    private Object data;
    ...}

完整的类代码请大家去看源码,不方便在这里粘贴全部代码,只提供类中的属性供大家参考,大家可以参考对应的实现思路去封装自己的结果集,我们使用这样统一的结果类便可以在返回数据的同时,返回请求状态码给前端,提高接口可读性和稳定性。

接口测试

当我们第一个接口编写完成后,我们使用PostMan进行接口测试,postman是一款开源免费的接口调试工具,可以模拟客户端发出请求,是后端开发必备工具:

我们可以看到,我们的请求是成功的,当然图示中,我请求的是我的生产服务器接口,大家在本机调试的时候URL应该是localhost开头的,我们可以看到请求结果的响应也是我们封装的结果集的响应,有响应码和数据体组成,大家可以认识更换请求数据,测试接口功能的完整性。

用户注册

完成了第一个判断用户名是否存在的接口后,我们着手开始进行用户注册的逻辑编写,首先我们在注册的时候,依然是操作UserMapper,所以数据层是已经准备好的,我们在UserService中,定义第二个创建用户的方法:

复制代码
/** * 创建用户 * @param userBO * @return */public Users createUser(UserBO userBO);

这个方法会接收一个表单数据,数据包中包含密码,用户名,甚至更多的信息,如果我们在后端一个一个的接收,显然是不合理的,当请求参数过多时,我们便将请求参数封装成一个请求的实体类,在这里我们封装一个UserBO:

复制代码
public class UserBO {    @ApiModelProperty(value = "用户名",name = "username",example ="张三",required =true)    private String username;    @ApiModelProperty(value = "密码",name = "password",example ="123456",required =true)    private String password;    @ApiModelProperty(value = "确认密码",name = "confirmPassword",example ="123456",required =false)    private String confirmPassword;    ...}

get/set方法大家自行生成,方法定义好后,我们去实现类中,实现他:

复制代码
@Transactional(propagation = Propagation.REQUIRED)@Overridepublic Users createUser(UserBO userBO) {    //使用工具类生成唯一id    String userId = sid.nextShort();    Users user = new Users();    user.setId(userId);    user.setUsername(userBO.getUsername());    try {        user.setPassword(MD5Utils.getMD5Str(userBO.getPassword()));    } catch (Exception e) {        e.printStackTrace();    }    //默认用户昵称同用户名    user.setNickname(userBO.getUsername());    //默认头像    user.setFace(USER_FACE);    //默认生日    user.setBirthday(DateUtil.stringToDate("1970-01-01"));    //设置性别(使用枚举操作)默认为:保密    user.setSex(Sex.secret.type);
    user.setCreatedTime(new Date());    user.setUpdatedTime(new Date());
    usersMapper.insert(user);    return user;}

我们在这里存储密码时,使用了MD5加密机制,防止数据库资源泄露,导致用户数据泄露,保证数据的安全性,MD5的工具类大家可以在源码中获取,我就不贴在这里了;同时使用的工具类还有日期格式化工具类,同样的,大家在源码中获取;我们在新建用户设置性别时,我们可以使用枚举的形式去定义用户的性别,增强代码的可读性:

复制代码
/** * @Desc:性别枚举 */public enum Sex {
    woman(0,"女"),    man(1,"男"),    secret(2,"保密");
    public final Integer type;    public final String value;
    Sex(Integer type, String value) {        this.type = type;        this.value = value;    }}

我们在生成唯一ID的时候,我们也会使用工具类,注入Sid对象,使用org.n3r.idworker中的方法为了使用其他包中的方法,我们需要SpringBoot在启动时,扫描到idworker,我们需要在Application中配置:

复制代码
//扫描所有包,以及相关组件包@ComponentScan(basePackages = {"com.imooc","org.n3r.idworker"})

接口实现

写完Service,我们回到PassportController,因为是一个保存数据的方法,我们使用Post的方式:

复制代码
@ApiOperation(value = "用户注册",notes ="用户注册",httpMethod = "POST")@PostMapping("/regist")public IMOOCJSONResult regist(@RequestBody UserBO userBO,                              HttpServletRequest request,                              HttpServletResponse response){
    String username = userBO.getUsername();    String password = userBO.getPassword();    String confirmPwd = userBO.getConfirmPassword();
    System.out.println(username);    System.out.println(password);    System.out.println(confirmPwd);
    // 判断用户名和密码必须不为空    if (StringUtils.isBlank(username) ||        StringUtils.isBlank(password) ||        StringUtils.isBlank(confirmPwd)) {        return IMOOCJSONResult.errorMsg("用户名及密码不能为空");    }    // 查询用户名是否存在    boolean isExist = userService.queryUsernameIsExist(username);    if (isExist) {        return IMOOCJSONResult.errorMsg("用户名已经存在");    }    // 密码长度不能小于6位    if (password.length() < 6) {        return IMOOCJSONResult.errorMsg("密码长度不能小于6位");    }    // 判断两次密码是否一致    if (!password.equals(confirmPwd)) {        return IMOOCJSONResult.errorMsg("两次密码输入不一致");    }    // 实现注册    Users userResult = userService.createUser(userBO);
    userResult = setNullProperty(userResult);
    CookieUtils.setCookie(request,response,"user",      //使用Cookie工具类获取Cookie            JsonUtils.objectToJson(userResult),true);      //使用Json工具类将对象转换为String    // TODO 生成用户token,存入redis会话    // TODO 同步购物车数据
    return IMOOCJSONResult.ok();}

Controller的实现的没有什么注重的点,我们接收到前端传来的数据包,解析数据包中的属性,进行校验,校验通过,调用Service层的方法,实现注册;同样的,对于这个方法,我们使用PostMan进行调试。

推荐

No.1

New Arrival

Swagger2

在上面的部分,我们实现了两个接口,实现了注册的业务功能,并使用PostMan进行了接口调试,这些都属于后端开发的部分,那么我们如何在前后端分离的模式下,与前端沟通联调呢,这时候我们需要使用接口文档:接口文档规范了接口路由、参数,入参、出参以及参数的类型。但是对于程序员来说,花费大量的时间去编写文档,是不科学不可取不合理的,那么Swagger2应运而生!首先,我们引入依赖:

复制代码
<!-- swagger2 配置 --><dependency>    <groupId>io.springfox</groupId>    <artifactId>springfox-swagger2</artifactId>    <version>2.4.0</version></dependency><dependency>    <groupId>io.springfox</groupId>    <artifactId>springfox-swagger-ui</artifactId>    <version>2.4.0</version></dependency><dependency>    <groupId>com.github.xiaoymin</groupId>    <artifactId>swagger-bootstrap-ui</artifactId>    <version>1.6</version></dependency>

引入依赖后,我们需要定义配置类,我们在api工程汇总创建Swagger2的配置类:

复制代码
@Configuration@EnableSwagger2public class Swagger2 {
//    http://localhost:8088/swagger-ui.html         原路径//    http://localhost:8088/doc.html
    //配置swagger2核心配置 docket    @Bean    public Docket createRestApi(){        return new Docket(DocumentationType.SWAGGER_2)//指定api类型为swagger2                .apiInfo(apiInfo())                   //用于定义api文档汇总信息                .select().apis(RequestHandlerSelectors                        .basePackage("com.imooc.controller"))   //指定controller包                .paths(PathSelectors.any())           //所有Controller                .build();    }
    private ApiInfo apiInfo() {        return new ApiInfoBuilder()                .title("天天吃货    电商平台接口api")     //文档页标题                .contact(new Contact("eumen",                        "https://www.imooc.com",                        "18829526908@163.com"))         //联系人信息                .description("api文档")                 //详细信息                .version("1.0.1")                       //文档版本号                .termsOfServiceUrl("106.54.54.200")//网站地址                .build();    }}

配置完成后,我们便可以使用API这个注解去添加方法的参数说明,和方法说明了,并且,启动项目,访问BASE_URL/Swagger-ui.html便可以看到生成的项目API文档:

对于Swagger2的使用,大家可以去查阅文档,这是一个非常好用的文档生成工具,提高生产力,让开发远离文档困扰!我们甚至可以在文档里做接口的调试:

相关推荐
chuanauc4 分钟前
Kubernets K8s 学习
java·学习·kubernetes
一头生产的驴20 分钟前
java整合itext pdf实现自定义PDF文件格式导出
java·spring boot·pdf·itextpdf
YuTaoShao27 分钟前
【LeetCode 热题 100】73. 矩阵置零——(解法二)空间复杂度 O(1)
java·算法·leetcode·矩阵
zzywxc78730 分钟前
AI 正在深度重构软件开发的底层逻辑和全生命周期,从技术演进、流程重构和未来趋势三个维度进行系统性分析
java·大数据·开发语言·人工智能·spring
3gying1 小时前
chromedriver
python
DES 仿真实践家2 小时前
【Day 11-N22】Python类(3)——Python的继承性、多继承、方法重写
开发语言·笔记·python
YuTaoShao3 小时前
【LeetCode 热题 100】56. 合并区间——排序+遍历
java·算法·leetcode·职场和发展
程序员张33 小时前
SpringBoot计时一次请求耗时
java·spring boot·后端
llwszx6 小时前
深入理解Java锁原理(一):偏向锁的设计原理与性能优化
java·spring··偏向锁
云泽野6 小时前
【Java|集合类】list遍历的6种方式
java·python·list