首页 > 编程语言 > 详细

SpringBoot构建电商基础秒杀项目(一)

时间:2020-05-25 22:19:27      阅读:53      评论:0      收藏:0      [点我收藏+]

SpringBoot其实不是新框架,而是默认配置了很多框架的使用方式。就像maven整合了所有jar包,Springboot整合了所有框架,并通过一行简单的main方法启动应用

继承了spring的框架们:

技术分享图片

 

电商秒杀应用简介:

商品列表页获取秒杀商品列表

进入商品详情页获取秒杀商品详情

秒杀开始后进入下单确认页下单并支付成功

项目实战:

使用IDEA+Maven搭建SpringBoot开发环境

集成Mybatis操作数据库

实现秒杀项目

 

 第2章·基础项目搭建的基本流程:

构建Maven项目—>引入SpringBoot依赖—>接入Mybatis

2.1 使用IDEA创建maven项目

new->project->maven项目->选择maven-archetype-quickstart

1.导入springboot相关配置:

https://blog.csdn.net/m0_37657841/article/details/90524410

https://blog.csdn.net/midnight_time/article/details/90717676

跟着做完后输入8080出现了一下页面说明springboot工程在不需要任何外力下内嵌了tomcat容器

技术分享图片

2.SpringMVC+RESTful的使用:

???有点失败,网页没有出来,依旧是上面那样。没有报错也不知为啥。

***************************
APPLICATION FAILED TO START
***************************

Description:

Web server failed to start. Port 8080 was already in use.

----------------------------------------------------------------

看到报错了。。。8080已经被用了??之前tomcat不是都关了吗。。

注:8080 是默认端口,如果要使用其他端口,可以在res下面写 application.properties 修改,如:server.port=8090

然后我把idea停止运行再试就成功了。。。。所以是自己占了自己??晕

好了,还是挺神奇的,都没有在index.jsp上面写对应到方法的requestmapping?(不过简单的显示确实不需要页面。。。

技术分享图片

3.接入Mybatis(看安利视频的博主图文并茂的博客链接)

mybatis竟然有自动生成mapping.xml文件的插件。。。

创建数据库,填写mybatis-generator.xml,这个xml里面指定mybatis插件连接数据库和让插件生成什么文件生到哪里。

运行Mybatis插件,自动生成相关文件

就还挺神奇的,实体类(对应表),DAO接口(写方法)和mapping.xml文件(sql语句),这3个都不需要手动编写了。只需要把包写好就行。

技术分享图片

 运行run:mybatis_generator

然后application.properties中配置SpringBoot项目数据源

------------------------------------------------------------------------------------------------------------------

编写测试的时候@MapperScan注解报红??

技术分享图片

找半天后发现pom文件里mybatis的包引错了。。。

要用mybatis-springboot

<!--5、引入的第五个:Mybatis相关的jar包-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.1</version>
</dependency>

 ------------------------------------------------------------------------------------------------------

然后要运行app这个,换过来。千万运行之前那个generator的maven

网友总结了所有代码,但没有详细注解说明:

https://www.cnblogs.com/victorbu/p/10538615.html

输入8090端口可以看到了:

技术分享图片

 然后手动网数据库插入一条数据:

显示数据库中找到的用户的名字

技术分享图片

以上就是第二章基础搭建,springboot接入mybatis,编写generator.xml让插件自动生成实体类,dao接口和映射文件。

 

 第三章 用户模块开发

上一章,我们学会了:

构建Maven项目
引入SpringBoot
引入Mybatis
简单使用SpringMVC

https://blog.csdn.net/midnight_time/article/details/91048543

https://blog.csdn.net/m0_37657841/article/details/90545984

接下来就要详细的学习使用SpringMVC了,具体知识点有:

1.MVC分层架构

之前学的都是数据库查询到的一行实体对象数据直接返回到前端,但实际开发中不可以直接返回数据库的数据,需要service层有一个model掩盖一下。

password属性也加到model里面。 model层才是真正处理业务核心层,而dataobject实体类仅仅只是对数据库的映射

技术分享图片

但是model也不需要全都返回给前端,所以在cotroller层再增加一个viewobject层

技术分享图片

由cotroller层的@requestbody和@requestparm在页面展示了json数据

    1. DAO层 --- dataobject,与数据库一一映射

    2. Service层 --- model,整合因业务需求而分离的dataobject

    3. Controller层 --- viewobject,屏蔽如密码等敏感信息返回给前端

2.通用返回类型

Controller中的方法返回值类型会出现不同,比如返回viewobject,返回null,返回异常Exception信息等等。如果每个方法都有独自的返回值类型的话,那样的设计不太好,也不容易以统一的形式(比如统一的JSON格式)呈现给前端。好的解决方法就是独立出一层:response,创建一种通用的返回值类型CommonReturnType,它有两个属性status和data。

1.增加一个response包。创建CommonReturnType类给他统一返回类型。

技术分享图片
public class CommonReturnType {
    //表明对应请求的返回结果 success fail
    private String status;
    //若status=success,则data内返回前端需要的json数据
    //若status=fail,则data内使用通用的错误码格式
    private Object data;

//    public CommonReturnType(String status, Object data) {//这才是构造方法
//        this.status = status;
//        this.data = data;
//    }
//定义一个通用的创建方法 是自己写的方法 不是构造方法 那为什么不用构造方法呢?因为他这个可以返回值!
public static CommonReturnType create(Object result){//传入对象或基本数据类型都可以
    return create(result, "success");//调用下面这个重载的方法,传入的都依然返回 但是多设置了一个status
}

    public static CommonReturnType create(Object result, String status){

        CommonReturnType type = new CommonReturnType();
        type.setStatus(status);
        type.setData(result);

        return type;
    }



    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }
}
View Code

修改cotroller的返回值为这个,则可以看到浏览器返回多了个statusdata 统一的返回类型

技术分享图片

(学MVC时那个前端控制器和视图控制器可以自动对应success.jsp,fail.jsp也很好用。。这里springboot好像没有web.xml配置这些了??

 定义通用的返回对象——返回错误信息

    1.创建error包

    2.创建commonError接口

public interface CommonError {
    public int getErrCode();
    public String getErrMsg();
    public CommonError setErrMsg(String errMsg);

    3.创建接口的实现类枚举类(就可以直接写定义),因为异常有ErrorCode和ErrorMsg两个属性,因此我们将异常封装一下抽象到一个枚举类EmBusinessError

技术分享图片
//实现类是一个枚举类 这个类可以传给cotroller里面捕获异常相关的继承exception的新建一个BusinessException类
public enum EmBusinessError implements CommonError {

    //通用错误类型00001
    PARAMETER_VALIDATION_ERROR(00001, "参数不合法"),

    //10000开头为用户信息相关错误定义
    USER_NOT_EXIST(10001, "用户不存在")
    //下面还可以继续方便增加枚举的错误信息 代码修改可用性高
    ;

    private int errCode;
    private String errMsg;

    EmBusinessError(int errCode, String errMsg) {
        this.errCode = errCode;
        this.errMsg = errMsg;
    }

    @Override
    public int getErrCode() {
        return this.errCode;
    }

    @Override
    public String getErrMsg() {
        return this.errMsg;
    }

    @Override
    public CommonError setErrMsg(String errMsg) {
        this.errMsg = errMsg;
        return this;
    }
}
View Code

使用这个类就是,这个类可以传给cotroller里面捕获异常相关的继承exception的新建一个BusinessException类

技术分享图片
//包装器业务异常实现 和em类一样实现接口,且都可以通过set方法覆盖掉原来的接口对象
public class BusinessException extends Exception implements CommonError {
    private CommonError commonError;

    //直接接收EmBusinessError的传参用于构造业务异常(?怎么传啊?这个参数就是来自em实现类?还是里面的set方法返回值?
    // 应该是后者em的set方法返回值传进这里面初始化)
    public BusinessException(CommonError commonError) {
        super();//有一些Exception自身初始化的机制在里面
        this.commonError = commonError;
    }
    //接收自定义errMsg的方式构造业务异常
    public BusinessException(CommonError commonError, String errMsg) {
        super();
        this.commonError = commonError;
        this.commonError.setErrMsg(errMsg);
    }

    @Override
    public int getErrCode() {
        return this.commonError.getErrCode();
    }

    @Override
    public String getErrMsg() {
        return this.commonError.getErrMsg();
    }

    @Override
    public CommonError setErrMsg(String errMsg) {
        this.commonError.setErrMsg(errMsg);
        return this;
    }
}
View Code

然后就可以在controller里面抛出了,用法是这样的,参数直接传写好的枚举就可以!!省事儿!

        //若获取的对应用户信息不存在
        if (userModel == null) {
            throw new BusinessException(EmBusinessError.USER_NOT_EXIST);
        }

但不管是直接throw的业务异常或者JVM虚拟机抛出的运行时异常,仅仅是抛到了Tomcat的容器层,我们在Controller层并不能进行处理。所以接着来

     4.定义@exceptionHandler解决未被controller层吸收的exception

像处理异常这种工作,不只是UserController要用到,以后处理其他模块也有可能有异常,因此将代码提取到新建一个BaseController中,让UserController继承BaseController,这样以后扩展其他模块时,代码就可以复用了

技术分享图片
@Controller("user")
@RequestMapping("/user")
public class UserController extends BaseController{

    @Autowired
    private UserService userService;

    @RequestMapping("/get")
    @ResponseBody
    public CommonReturnType getUser(@RequestParam(name = "id") Integer id) throws BusinessException {
        //调用service服务获取对应id的用户对象并返回给前端
        UserModel userModel = userService.getUserById(id);

        //若获取的对应用户信息不存在
        if (userModel == null) {
//            userModel.setEncrptPassword("123");
           throw new BusinessException(EmBusinessError.USER_NOT_EXIST);
        }

        //将核心领域模型用户对象转化为可供UI使用的viewobject
        UserVO userVO = convertFromModel(userModel);
        //返回通用对象
        return CommonReturnType.create(userVO);
    }

    //把model转成VO 主要是为了调用service方法生成的是model,但我们需要VO类
    private UserVO convertFromModel(UserModel userModel){
        if(userModel == null){
            return null;
        }
        UserVO userVO = new UserVO();
        BeanUtils.copyProperties(userModel,userVO);

        return userVO;
    }

}
View Code

技术分享图片

以上统一了返回类型和错误信息

完成了基础能力建设,接下来进入模型能力管理。功能方面了

用户信息管理:

     otp短信获取

  otp注册用户

  用户手机登录

3.otp(一次性密码)验证码生成

这里是后台生成随机数发短信给用户,跟之前学的网页上生成随机数让用户填写不太一样

 //用户获取!!otp短信方法
    @RequestMapping("/getotp")
    @ResponseBody
    public CommonReturnType getOtp(@RequestParam(name="telphone")String telphone){
        //需要按照一定的规则生成OTP验证码 就是单纯的Random随机数
        Random random = new Random();
        int randomInt = random.nextInt(99999);//此时随机数取值[0,99999)
        randomInt+=10000;//此时取值[10000,109999)
        String otpCode = String.valueOf(randomInt);

        //将OTP验证码同对应用户的手机号关联 企业级是使用Redis来实现的,(键值对形式的数据库,反复点击可以反复覆盖)
        // 但是目前这个项目属于入门级别没讲到分布式,暂时用httpServletRequest对象获取session方式来绑定手机号与验证码。
        request.getSession().setAttribute(telphone,otpCode);//就这样做关联了

        //将OTP验证码通过短信通道发送给用户,省略(有兴趣可买第三方服务的短信通道)
        System.out.println("telphone = "+telphone+"&otpCode ="+otpCode);

        return CommonReturnType.create(null);
    }

技术分享图片

控制台打印了手机号和绑定的验证码:

技术分享图片

3.6 用户模型管理——Metronic模板简介

采用前后端分离的思想,建立一个html文件夹,引入static文件夹(下载资料里)

前端文件保存在本地的哪个盘下都可以,因为是通过ajax来异步获取接口

getotp.html:

技术分享图片
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>GetOtp</title>
    <script src = "static/assets/global/plugins/jquery-1.11.0.min.js" type="text/javascript"></script>
    <link href="static/assets/global/plugins/bootstrap/css/bootstrap.min.css" rel="stylesheet" type="text/css"/>
    <link href="static/assets/global/plugins/css/component.css" rel="stylesheet" type="text/css"/>
    <link href="static/assets/admin/pages/css/login.css" rel="stylesheet" type="text/css"/>

</head>
<body class="login">
<div class="content">
    <h3 class="form-title">获取otp信息</h3>
    <div class="form-group">
        <label class="control-label">手机号</label>
        <div>
            <input class="form-control" type="text" placeholder="手机号" name="telphone" id="telphone"/>
        </div>
    </div>
    <div class="form-actions">
        <button class="btn blue" id="getotp" type="submit">
            获取otp短信
        </button>
    </div>
</div>

</body>

<script>
    jQuery(document).ready(function () {

        //绑定otp的click事件用于向后端发送获取手机验证码的请求
        $("#getotp").on("click",function () {

            var telphone=$("#telphone").val();
            if (telphone==null || telphone=="") {
                alert("手机号不能为空");
                return false;
            }

            //映射到后端@RequestMapping(value = "/getotp", method = {RequestMethod.POST}, consumes = {CONTENT_TYPE_FORMED})
            $.ajax({
                type:"POST",
                contentType:"application/x-www-form-urlencoded",
                url:"http://localhost:8090/user/getotp",
                data:{
                    "telphone":$("#telphone").val(),
                },
                success:function (data) {
                    if (data.status=="success") {
                        alert("otp已经发送到了您的手机,请注意查收");
                    }else {
                        alert("otp发送失败,原因为" + data.data.errMsg);
                    }
                },
                error:function (data) {
                    alert("otp发送失败,原因为"+data.responseText);
                }
            });
        });
    });

</script>
</html>
View Code

这里的$#是取地址去id取元素 好像不是jsp的el表达式

技术分享图片

但是有跨域请求出错,因为html是本地文件的端口,服务器是主机端口。跨域请求错误,只需要在UserController类上加一个注解@CrossOrigin即可

技术分享图片

 控制台打印成功

技术分享图片

4.登录、注册功能

 

3.9 用户模型管理——用户注册功能实现

1.实现方法:conttoller中用户注册方法,也是通用返回类型给前端,就是数据加状态,不返回就create方法传进null。

//    用户注册方法
    @RequestMapping(value = "/register", method = {RequestMethod.POST}, consumes = {CONTENT_TYPE_FORMED})
    @ResponseBody //要从请求体拿的参数就都要写出
    public CommonReturnType register(@RequestParam(name = "telphone") String telphone,
                                     @RequestParam(name = "otpCode") String otpCode,
                                     @RequestParam(name = "name") String name,
                                     @RequestParam(name = "gender") String gender,
                                     @RequestParam(name = "age") String age,
                                     @RequestParam(name = "password") String password) throws BusinessException, UnsupportedEncodingException, NoSuchAlgorithmException {

        //验证手机号和对应的otpCode相符合
        String inSessionOtpCode = (String) this.request.getSession().getAttribute(telphone);//找到session里的otp(用户提交的手机号必须一致才能找到)
        if (!com.alibaba.druid.util.StringUtils.equals(otpCode, inSessionOtpCode)) {throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "短信验证码不符合");
        }//符合的话往下走 给数据库传入用户前端填写的数据 所以要去业务层写方法

        //用户的注册流程
        UserModel userModel = new UserModel();
        userModel.setName(name);
        userModel.setAge(Integer.valueOf(age));
        userModel.setGender(Byte.valueOf(gender));
        userModel.setTelphone(telphone);
        userModel.setRegisterMode("byphone");

        //密码加密才能传入数据库(?。。
        userModel.setEncrptPassword(this.EncodeByMd5(password));

        userService.register(userModel);
        return CommonReturnType.create(null);

   }

    //密码加密
    public String EncodeByMd5(String str) throws NoSuchAlgorithmException, UnsupportedEncodingException {
        //确定计算方法
        MessageDigest md5 = MessageDigest.getInstance("MD5");
        BASE64Encoder base64en = new BASE64Encoder();
        //加密字符串
        String newstr = base64en.encode(md5.digest(str.getBytes("utf-8")));
        return newstr;
    }

5.项目中的校验逻辑

要做校验在传入数据库之前判断各个属性的数据是否为空,在serviceimpl里面加注册register方法。

 @Override
    @Transactional
    public void register(UserModel userModel) throws BusinessException {
        if (userModel == null) {
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR);
        }
        if (StringUtils.isEmpty(userModel.getName()) //因为字符串类型不能用==null判断所以用StringUtils.isEmpty方法(依赖的?)
                || userModel.getGender() == null
                || userModel.getAge() == null
                || StringUtils.isEmpty(userModel.getTelphone())) {
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR);
        }


        //实现传入的model->dataobject方法 下面写一个转换方法 这些框架以后也有用
        UserDO userDO = convertFromModel(userModel);
        userDOMapper.insertSelective(userDO);//这个好像之前也学过我还测试来着(学框架的时候在哪里???找不着了。。。)
        // 就是如果传入了null是不会覆盖之前有数据的值。如果是insert就会覆盖了。
        UserPasswordDO userPasswordDO = convertPasswordFromModel(userModel);
        userPasswordDOMapper.insertSelective(userPasswordDO);

        return;
    }

    //model->dataobject的转换方法
    private UserDO convertFromModel(UserModel userModel) {
        if (userModel == null) {
            return null;
        }
        UserDO userDO = new UserDO();
        BeanUtils.copyProperties(userModel, userDO);
        return userDO;
    }
//model的password->dataobject的password转换方法
private UserPasswordDO convertPasswordFromModel(UserModel userModel) {
    if (userModel == null) {
        return null;
    }
    UserPasswordDO userPasswordDO = new UserPasswordDO();
    userPasswordDO.setEncrptPassword(userModel.getEncrptPassword());
    userPasswordDO.setUserId(userModel.getId());

    return userPasswordDO;
}

引入做输入校验的依赖(apache.lang包)

<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-lang3</artifactId>
  <version>3.7</version>
</dependency>

提交到数据库insert要记得加事务控制。@Transactional

首先在getotp界面添加注册成功的跳转界面,然后写前端注册界面:也用h5 复制getotp写一个register.html

 七、用户登录流程

还需要解决跨域的问题,因为

//跨域请求中,不能做到session共享,要设置

@CrossOrigin(allowCredentials = "true",allowedHeaders = "*")

然后前端也要加ajax受信 

//允许跨域请求
xhrFields:{withCredentials:true},

--------------------------------------------------------------------------------------------------

我加了register.html忘记加getotp了 debug半天也不太会,最后感觉sout比debug好使。。。

-------------------------------------------------------------------------------------------------

还是出错,刚刚是手机号和验证码不匹配的错误,现在直接未知错误。。。。

debug半天发现是serviceimpl的这里有问题 那么就是mapper的问题??这不是自动生成的嘛。。。。我晕 

技术分享图片

 

 还有我都给model设置id了它还是等于null

技术分享图片

 技术分享图片

 这都什么人间疾苦。。

包导错了改过来了,还是不成功.................................

技术分享图片

花费一下午也没找出问题,放弃了。总之这个包是得注释掉,但是注释掉这个包,我的数据库操作还是可能有问题。不再用这个了。

ps:github下载的要重新设置maven的仓库地址,setting地址和证书-Dmaven.wagon.http.ssl.insecure=true -Dmaven.wagon.http.ssl.allowall=true

然后clean-install-reimport操作。但首先要改成本地都有的版本比较好。

------------------------------------------------------------------------------------------------------------

 <insert id="insertSelective" parameterType="com.miaoshaproject.dataobject.UserDO" keyProperty="id" useGeneratedKeys="true">

 添加id自增

八、用户登录流程

用户注册流程,后端Controller层需要获取用户输入,然后封装成Model,传递给Service层,Service层再拆分成dataobject,调用DAO层,将DO插入数据库。

登录和注册流程相似,只不过在调用DAO层时,不是进行插入操作,而是查询操作。

查询密码的时候,需要修改两个文件(Mybatis自动生成的只有selectByPrimaryKey)

UserPasswordDOMapper.xml 添加 selectByUserId

UserPasswordDOMapper.java 添加 selectByUserId

UserPasswordDOMapper.java中的方法与UserPasswordDOMapper.xml 中数据库CURD语句一一映射。

其他流程可以参考注册流程,这里就不再赘述。可以看https://blog.csdn.net/m0_37657841/article/details/90545984

技术分享图片

3.10 优化校验规则

去仓库可以直接搜有用的轮子 ,老师直接搜了validator然后点击版本号找依赖就是。

好用?????

https://mvnrepository.com/

好用????

https://www.mvnjar.com/

好用????

http://maven.outofmemory.cn/hot/

2.对validator进行一个简单的封装

新建validator的目录

新建一个ValidationResult的类

新建一个ValidatiorImpl的类

给属性们加注解作为限制条件@NotNull @NotBlank

public class UserModel {

    private Integer id;

    @NotBlank(message = "姓名不能为空")
    private String name;

    @NotNull(message = "姓名不能为空")
    private Byte gender;

    @NotNull(message = "年龄不能为空")
    @Min(value = 0,message = "年龄不能小于0")
    @Max(value = 150,message = "年龄不能大于150")
    private Integer age;

    @NotBlank(message = "手机号不能为空")
    private String telphone;

    private String registerMode;

    private String thirdPartyId;

    @NotBlank(message = "密码不能为空")
    private String encrptPassword;

在UserServiceImpl中使用validator做校验

 //校验入参
        ValidationResult result = validator.validate(userModel);
        if (result.isHasErrors()) {
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, result.getErrMsg());
        }

使用这个校验在业务层impl,以后做校验时只需要在model的属性上做注解即可。

 我们的报错系统和校验系统就很健壮了。

九、后端所有带异常的方法的返回值

我们这套代码是前后端分离,且前后端都有进行校验。有些校验只有后端才能完成,

比如说:

手机号是否重复

验证码是否获取成功

是否登录成功

是否注册成功

参数是否合法

用户登录手机号和密码是否匹配等等。

这些校验之后,如果异常则会抛出对应的异常, Service层throw Exception会抛到调用服务的Controller层,Controller中throw Exception最终都会在BaseController中进行return给前端。

列一下后端所有带异常的方法的返回值,有助于逻辑的理解。

 

   
  
  层
   
   
    

类名 方法名 返回正确信息 返回异常信息
Controller层 BaseController handlerException

 return CommonReturnType.create

(responseData,“fail”);

Controller层  UserController getUser

return CommonReturnType

.create(userVO);

 无,但会throw new BusinessException

(EmBusinessError.USER_NOT_EXIST);

通过BaseController进行return

Controller层 UserController getOtp

return CommonReturnType

.create

(otpCodeObj, “successGetOtpCode”);

  无,但会throw new BusinessException

(EmBusinessError.PARAMETER_VALIDATION_ERROR,

“手机号已重复注册”);

通过BaseController进行return

Controller层 UserController register

 return CommonReturnType.

create(null);

无,但会 throw new BusinessException

(EmBusinessError.PARAMETER_VALIDATION_ERROR,

“短信验证码错误”);

通过BaseController进行return

Controller层 UserController login

return CommonReturnType.

create(null);

无,但会throw new BusinessException

(EmBusinessError.PARAMETER_VALIDATION_ERROR);

通过BaseController进行return

Service层 UserServiceImpl register

throw new BusinessException

(EmBusinessError.PARAMETER_VALIDATION_ERROR);

通过BaseController进行return throw new BusinessException

(EmBusinessError.PARAMETER_VALIDATION_ERROR, result.getErrMsg());

通过BaseController进行return throw new BusinessException

(EmBusinessError.PARAMETER_VALIDATION_ERROR,

“手机号已重复注册”);

通过BaseController进行return

Service层  UserServiceImpl  validateLogin return userModel;

 throw new BusinessException

(EmBusinessError.USER_LOGIN_FAIL);

通过BaseController进行return throw new BusinessException

(EmBusinessError.PARAMETER_VALIDATION_ERROR);

通过BaseController进行return

 

SpringBoot构建电商基础秒杀项目(一)

原文:https://www.cnblogs.com/gezi1007/p/12944975.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!