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

news2024/11/24 22:32:02

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

图片

图片

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/766124.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数仓学习---7、数据仓库设计、数据仓库环境准备

这是本人的学习过程&#xff0c;看到的同道中人祝福你们心若有所向往&#xff0c;何惧道阻且长&#xff1b; 但愿每一个人都像星星一样安详而从容的&#xff0c;不断沿着既定的目标走完自己的路程&#xff1b; 最后想说一句君子不隐其短&#xff0c;不知则问&#xff0c;不能则…

Stable Diffusion - 高清局部重绘 (Inpaint) 调整脸部和手部细节

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/131775232 在 Stable Diffusion 中&#xff0c;局部重绘(Inpaint)功能是一种可以让你在图像上删除不想要的区域&#xff0c;并用周围的像素自动填…

JAVA中的Socket编程、通信协议、传输协议

JAVA中的Socket编程 一、Socket概述 Socket&#xff0c;建立起客户端和服务器之间的连接&#xff0c;实现数据的传输和交互&#xff0c;它既可以发送请求&#xff0c;也可以接受请求&#xff0c;一个Socket由一个IP地址和一个端口号唯一确定&#xff0c;利用Socket能比较方便的…

Springboot + Vue 上传Word、PDF文档并保留内部格式

因为业务需求&#xff0c;上传Word文件需要编辑&#xff0c;但如何使用Blob方式&#xff0c;在数据库里存文件&#xff0c;就会造成格式消失。所以修改思路&#xff1a;上传文件到服务器本地&#xff0c;保证数据存储的完整性。 前端 <el-upload class"upload-demo&quo…

centos7.8从卸载python2,安装python3

因为目前所有环境都是python2.7.5&#xff0c;但是项目上使用的是python3.7.5&#xff0c;迫切需要使用python3.7.5验证。安装遇到困难&#xff0c;记录一下。 首先卸载python2&#xff0c;如果不想卸载python2的可以跳过 这里卸载python2和其依赖的yum 先卸载python2.7.5 …

基于意外流行的自适应模因算法求解分布式柔性作业车间调度问题——付源代码和论文

实在是太忙了&#xff0c;终于闲下来更新一下CSDN来介绍自己的工作 《Surprisingly Popular-Based Adaptive Memetic Algorithm for Energy-Efficient Distributed Flexible Job Shop Scheduling》发表在IEEE Transactions on Cybernetics上。 原文链接-可下载 Matlab代码 IEEE…

pdf能转成ppt格式吗?这几个方法你试过了吗

作为办公人士&#xff0c;掌握不同文件格式之间的转换技能是必不可少的。每当毕业季来临&#xff0c;我都会收到许多刚刚步入职场的小伙伴们的求助&#xff0c;寻找PDF转PPT的方法。所以&#xff0c;我总结了以下三种方法&#xff0c;希望能帮助大家解决这个一直以来困扰着许多…

RabbitMQ死信交换机、TTL及延迟队列

一&#xff0c;死信交换机 1&#xff0c;什么是死信交换机 了解死信交换机之前我们先来了解一下什么是死信&#xff0c;当一个队列满足下面的三种情况的时候我们一般称为死信&#xff08;dead letter&#xff09;&#xff1a; 消费者使用basic.reject或 basic.nack声明消费失…

深入 Linux 进程

问题 进程参数 和 环境变量 对于进程意味着什么&#xff1f; 进程参数和环境变量的意义 一般情况下&#xff0c;子进程的创建是为了解决某个子问题 子进程解决问题需要父进程的 "数据输入" (进程参数 & 环境变量) 设计原则&#xff1a; 子进程启动时必然用到…

SpringCloud学习路线(1)—— 从头开始的微服务

一、服务架构的历史 现有的服务框架&#xff1a; 单体架构 概念&#xff1a; 将业务所有功能集中在一个项目中开发&#xff0c;打包部署优点&#xff1a; 架构简单&#xff0c;部署成本低缺陷&#xff1a; 耦合度高 分布式架构 概念&#xff1a; 根据业务功能对系统进行拆分&a…

前端两种实现轮播图方式

今天研究两种简单实现轮播图功能的方式。 目录 Layui实现轮播图 码云下载 提取静态文件 示例 注意 参数说明 改为轮播图 增加图片资源文件 轮播栏目修改 改为上下切换 切换事件 脚本中绑定改变事件 控制器查看 Swiper实现轮播图 下载swiper 下载到本地 加载sw…

【数据结构】_3.List接口实现类ArrayList与线性表

目录 1.List接口 1.1 List接口的集合关系网络 1.2 List的使用 2. ArrayList与顺序表 2.1 线性表 2.2 顺序表 2.3 ArrayList 2.3.1 ArrayList的集合关系网络 2.3.2 ArrayList的使用 2.3.2.1 ArrayList的构造方法 2.3.2.2 ArrayList的扩容机制逻辑示图如下&#xff1…

企业如何通过CRM提高客户转化?

现如今&#xff0c;企业想要给潜在客户留下深刻的印象&#xff0c;那么就需要一系列的措施和手段。通过CRM管理系统让潜在客户信任企业&#xff0c;更好地进行客户培育&#xff0c;从而提高潜在客户转化。下面来说说&#xff0c;企业提高潜在客户转化的7个做法。 1、永远不要让…

input模糊搜索

input模糊搜索 getList() {let dicthis.queryParameters()let val this.queryParams["userName"]if (null ! val && ! val) {dic["userName"]*val*;}listPamsArchiveSearch(dic).then(response > {this.nameinfo response.data;});},主要问…

Spring Batch之读数据库——JdbcCursorItemReader之使用框架提供的BeanPropertyRowMapper(三十六)

一、BeanPropertyRowMapper介绍 参考我的另一篇博客&#xff1a; Spring Batch之读数据库——JdbcCursorItemReader&#xff08;三十五&#xff09;_人……杰的博客-CSDN博客 二、项目实例 1.项目框架 2.代码实现 BatchMain.java: package com.xj.demo27;import org.spri…

Echarts环形饼状图设置内外边框

上班快一个月了&#xff0c;最近再写echarts的项目&#xff0c;亚历山大啊 记录一下饼状图设置内外边框的代码 series: [{name: 项目分数,type: pie,radius: [50%, 70%],avoidLabelOverlap: false,labelLine: {show: true},data: [{value: 335, name: 970分以上},{value: 310…

python用playwright自动化测试程序打包exe

playwright自动化测试代码写好后&#xff0c;打包为exe运行在目标PC上可能出现错误。 原因&#xff1a; 1、运行的PC没有响应的浏览器。 2、playwright没有打包到代码中。 所以本例用AutoPytoExe为例来制作exe程序解决问题。 1、安装&#xff1a; 2、安装完成之后&#xff0c;…

CUDA并行归约算法(二)

CUDA并行归约算法&#xff08;二&#xff09; 文章目录 CUDA并行归约算法&#xff08;二&#xff09;前情回顾线程束分化内存组织Reference>>>>> 欢迎关注公众号【三戒纪元】 <<<<< 前情回顾 首先看下上节设计的核函数&#xff0c;如何进行并行…

基于FPGA的视频接口之HDMI1.4(以下)编码

简介 为什么要特别说明HDMI的版本,是因为HDMI的版本众多,代表的HDMI速度同样不同,当前版本在HDMI2.1速度达到48Gbps,可以传输4K及以上图像,但我们当前还停留在1080P@60部分,且使用的芯片和硬件结构有很大差别,故将HDMI分为两个部分说明1080@60以下分辨率和4K以上分辨率(…

docker运行redis容器

参考文章 Redis从入门到精通&#xff08;4&#xff09;&#xff1a;docker运行redis容器详解 问题及总结 docker 命令 拉取镜像&#xff1a;docker pull redis 或者 docker pull redis:bullseye&#xff1b;查看镜像&#xff1a;docker image ls&#xff1b;直接运行 redis…