# PromoProject **Repository Path**: yanbingzn/PromoProject ## Basic Information - **Project Name**: PromoProject - **Description**: SpringBoot构建电商基础秒杀项目 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 2 - **Forks**: 0 - **Created**: 2020-07-06 - **Last Updated**: 2025-02-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README #### 项目环境:IDEA,maven,MySQL5.x * 项目运行方式:从IDEA导入项目,更新maven依赖,然后在MySQL数据库中运行miaosha.sql文件生成数据库。 * 项目入口为:com.miaoshaproject.App,使用IDEA启动后,若端口被占用,修改application.properties中的端口配置。 * 项目采用前后端分离,直接在浏览器打开resources目录下的getotp.html即可。 我的博客地址https://blog.csdn.net/m0_37657841/article/details/90524410 ## 第一章 课程介绍 **电商秒杀应用简介** > * 商品列表页获取秒杀商品列表 > * 进入商品详情页获取秒杀商品详情 > * 秒杀开始后进入下单确认页下单并支付成功 ## 第二章 应用SpringBoot完成基础项目搭建 ### 2.1 使用IDEA创建maven项目 1.new->project->maven项目->选择maven-archetype-quickstart 以jar包方式对外输出 ​ 稍等一会,可能会有点慢 2.新建一个resources目录,作为资源文件目录,指定为Resource root ### 2.2 引入SpringBoot依赖包实现简单的Web项目 进入官方文档https://spring.io/guides/gs/rest-service/ **Building a RESTful Web Service** 1.引入父pom ```xml org.springframework.boot spring-boot-starter-parent 2.1.4.RELEASE ``` 2.引入依赖 ```xml org.springframework.boot spring-boot-starter-web ``` 3.maven Reimport刷新一下,会自动下载相应jar包(注:可以把idea设定为自动导入maven依赖) 4.SpringBoot的Web项目 ```java @EnableAutoConfiguration @RestController public class App { @RequestMapping("/") public String home() { return "hello World!"; } public static void main( String[] args ) { System.out.println("Hello World!"); SpringApplication.run(App.class,args); } } ``` 再次启动App,访问localhost:8080 ### 2.3 Mybatis接入SpringBoot项目 1.SpringBoot的默认配置 在resources目录下新建SpringBoot的默认配置文件application.properties 通过一行简单的属性就能更改tomcat的端口 ```xml server.port=8090 ``` 2.配置pom文件 ```xml mysql mysql-connector-java 5.1.47 com.alibaba druid 1.1.3 org.mybatis.spring.boot mybatis-spring-boot-starter 1.3.1 ``` 3.配置文件application.properties,设置 `mybatis.mapper-locations=classpath:mapping/*.xml` 然后在resources目录下新建mapping目录 4.自动生成工具,生成数据库文件的映射 引入插件 ```xml org.mybatis.generator mybatis-generator-maven-plugin 1.3.5 org.mybatis.generator mybatis-generator-core 1.3.5 mysql mysql-connector-java 5.1.41 mybatis generator package generate true true src/main/resources/mybatis-generator.xml ``` ### 2.4 Mybatis自动生成器的使用方式 1.新建文件src/main/resources/mybatis-generator.xml,从官网下载xml配置文件 http://www.mybatis.org/generator/configreference/xmlconfig.html 2.新建数据库 新建一个miaosha的数据库,并建立两张表,分别是user_info和user_password 3.修改配置文件 ```xml
``` 4.生成文件 在终端运行``` mvn mybatis-generator:generate```命令 5.接入mysql数据源 ```xml spring.datasource.name=miaosha spring.datasource.url=jdbc:mysql://localhost:3306/miaosha spring.datasource.username=root spring.datasource.password=123456 #使用druid数据源 spring.datasource.type=com.alibaba.druid.pool.DruidDataSource spring.datasource.driver-class-name=com.mysql.jdbc.Driver ``` 6.测试数据库 修改App类 ```java @SpringBootApplication(scanBasePackages = {"com.miaoshaproject"}) @RestController @MapperScan("com.miaoshaproject.dao") public class App { @Autowired private UserDOMapper userDOMapper; @RequestMapping("/") public String home() { UserDO userDO = userDOMapper.selectByPrimaryKey(1); if (userDO == null) { return "用户对象不存在"; } else { return userDO.getName(); } } } ``` 启动测试 ## 第三章 用户模块开发 ### 3.1 使用SpringMVC方式开发用户信息 1.增加controller层、dao层 创建UserController ```java @Controller("user") @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @RequestMapping("/get") @ResponseBody public UserModel getUser(@RequestParam(name = "id") Integer id) { //调用service服务获取对应id的用户对象并返回给前端 UserModel userModel = userService.getUserById(id); return userModel; } } ``` userController需要UserModel 2.在service层增加UserModel ```java package com.miaoshaproject.service.model; /** * @author KiroScarlet * @date 2019-05-15 -16:50 */ public class UserModel { private Integer id; private String name; private Byte gender; private Integer age; private String telphone; private String regisitMode; private Integer thirdPartyId; private String encrptPassword; } ``` UserModel需要增加 用户的密码,其通过userPasswordDOMapper从userPasswordDO得到 3.修改userPasswordDOMapper.xml和.java文件 增加方法 ```xml ``` ```java userPasswordDO selectByUserId(Integer UserId); ``` 4.编写UserService ```java @Service public class UserServiceImpl implements UserService { @Autowired private UserDOMapper userDOMapper; @Autowired private userPasswordDOMapper userPasswordDOMapper; @Override public UserModel getUserById(Integer id) { //调用UserDOMapper获取到对应的用户dataobject UserDO userDO = userDOMapper.selectByPrimaryKey(id); if (userDO == null) { return null; } //通过用户id获取对应的用户加密密码信息 userPasswordDO userPasswordDO = userPasswordDOMapper.selectByUserId(userDO.getId()); return convertFromDataObject(userDO, userPasswordDO); } private UserModel convertFromDataObject(UserDO userDO,userPasswordDO userPasswordDO) { if (userDO == null) { return null; } UserModel userModel = new UserModel(); BeanUtils.copyProperties(userDO, userModel); if (userPasswordDO != null) { userModel.setEncrptPassword(userPasswordDO.getEncrptPassword()); } return userModel; } } ``` 5.这种方式存在的问题 > 直接给前端用户返回了UserModel,使得攻击者可以直接看到密码 > > 需要在controller层增加一个viewobject模型对象 只需要这些信息: ```java public class UserVO { private Integer id; private String name; private Byte gender; private Integer age; private String telphone; } ``` 6.改造controller ```java public UserVO getUser(@RequestParam(name = "id") Integer id) { //调用service服务获取对应id的用户对象并返回给前端 UserModel userModel = userService.getUserById(id); //将核心领域模型用户对象转化为可供UI使用的viewobject return convertFromModel(userModel); } private UserVO convertFromModel(UserModel userModel) { if (userModel == null) { return null; } UserVO userVO = new UserVO(); BeanUtils.copyProperties(userModel, userVO); return userVO; } ``` ### 3.2 定义通用的返回对象——返回正确信息 之前的程序一旦出错,只会返回一个白页,并没有错误信息,需要返回一个有意义的错误信息。 1.增加一个response包。创建CommonReturnType类 ```java public class CommonReturnType { //表明对应请求的返回处理结果“success”或“fail” private String status; //若status=success,则data内返回前端需要的json数据 //若status=fail,则data内使用通用的错误码格式 private Object data; //定义一个通用的创建方法 public static CommonReturnType create(Object result) { return CommonReturnType.create(result, "success"); } public static CommonReturnType create(Object result,String status) { CommonReturnType type = new CommonReturnType(); type.setStatus(status); type.setData(result); return type; } } ``` 2.改造返回值 ```java public CommonReturnType getUser(@RequestParam(name = "id") Integer id) { //调用service服务获取对应id的用户对象并返回给前端 UserModel userModel = userService.getUserById(id); //将核心领域模型用户对象转化为可供UI使用的viewobject UserVO userVO = convertFromModel(userModel); //返回通用对象 return CommonReturnType.create(userVO); } ``` ### 3.3 定义通用的返回对象——返回错误信息 1.创建error包 2.创建commonError接口 ``` public interface CommonError { public int getErrCode(); public String getErrMsg(); public CommonError setErrMsg(String errMs); } ``` 3.创建实现类 ```java public enum EmBusinessError implements CommonError { //通用错误类型00001 PARAMETER_VALIDATION_ERROR(00001, "参数不合法"), //10000开头为用户信息相关错误定义 USER_NOT_EXIST(10001, "用户不存在") ; private EmBusinessError(int errCode, String errMsg) { this.errCode = errCode; this.errMsg = errMsg; } private int errCode; private String 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; } } ``` 4.包装器模式实现BusinessException类 ```java /包装器业务异常实现 public class BusinessException extends Exception implements CommonError { private CommonError commonError; //直接接受EmBusinessError的传参用于构造业务异常 public BusinessException(CommonError commonError) { super(); 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; } } ``` 5.抛出异常类 ```java public CommonReturnType getUser(@RequestParam(name = "id") Integer id) throws BusinessException { //调用service服务获取对应id的用户对象并返回给前端 UserModel userModel = userService.getUserById(id); //若获取的对应用户信息不存在 if (userModel == null) { throw new BusinessException(EmBusinessError.USER_NOT_EXIST); } //将核心领域模型用户对象转化为可供UI使用的viewobject UserVO userVO = convertFromModel(userModel); //返回通用对象 return CommonReturnType.create(userVO); } ``` ### 3.4 定义通用的返回对象——异常处理 1.定义exceptionHandler解决未被controller层吸收的exception ```java public class BaseController { //定义exceptionHandler解决未被controller层吸收的exception @ExceptionHandler(Exception.class) @ResponseStatus(HttpStatus.OK) @ResponseBody public Object handlerException(HttpServletRequest request, Exception ex) { Map responseData = new HashMap<>(); if (ex instanceof BusinessException) { BusinessException businessException = (BusinessException) ex; responseData.put("errCode", businessException.getErrCode()); responseData.put("errMsg", businessException.getErrMsg()); } else { responseData.put("errCode", EmBusinessError.UNKNOWN_ERROR.getErrCode()); responseData.put("errMsg", EmBusinessError.UNKNOWN_ERROR.getErrMsg()); } return CommonReturnType.create(responseData, "fail"); } } ``` ### 3.5 用户模型管理——otp验证码获取 ```java public class UserController extends BaseController{ @Autowired private UserService userService; @Autowired private HttpServletRequest httpServletRequest; //用户获取otp短信接口 @RequestMapping("/getotp") @ResponseBody public CommonReturnType getOtp(@RequestParam(name = "telphone") String telphone) { //需要按照一定的规则生成OTP验证码 Random random = new Random(); int randomInt = random.nextInt(99999); randomInt += 10000; String otpCode = String.valueOf(randomInt); //将OTP验证码同对应用户的手机号关联,使用httpsession的方式绑定手机号与OTPCDOE httpServletRequest.getSession().setAttribute(telphone, otpCode); //将OTP验证码通过短信通道发送给用户,省略 System.out.println("telphone=" + telphone + "&otpCode=" + otpCode); return CommonReturnType.create(null); } ``` 测试,在控制台打印数据 ### 3.6 用户模型管理——Metronic模板简介 采用前后端分离的思想,建立一个html文件夹,引入static文件夹 前端文件保存在本地的哪个盘下都可以,因为是通过ajax来异步获取接口 ### 3.7 用户模型管理——getotp页面实现 1.getotp.html: ```xml Title

获取otp信息

``` 2.指定controller的method ```java @RequestMapping(value = "/getotp", method = {RequestMethod.POST}, consumes = {CONTENT_TYPE_FORMED}) ``` 3.提示发送失败,使用chrome调试,发现报错为 ``` getotp.html?_ijt=cqdae6hmhq9069c9s4muooakju:1 Access to XMLHttpRequest at 'http://localhost:8080/user/getotp' from origin 'http://localhost:63342' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. ``` 跨域请求错误,只需要在UserController类上加一个注解`@CrossOrigin`即可 ### 3.8 用户模型管理——getotp页面美化 1.引入样式表 ```html ``` 2.使用样式 ```html

获取otp信息

``` ### 3.9 用户模型管理——用户注册功能实现 1.实现方法:用户注册接口 ```java //用户注册接口 @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.httpServletRequest.getSession().getAttribute(telphone); 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.setRegisitMode("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; } ``` 2.引入做输入校验的依赖 ```xml org.apache.commons commons-lang3 3.7 ``` 3.UserServiceImpl的register方法 ```java @Override @Transactional//声明事务 public void register(UserModel userModel) throws BusinessException { //校验 if (userModel == null) { throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR); } if (StringUtils.isEmpty(userModel.getName()) || userModel.getGender() == null || userModel.getAge() == null || StringUtils.isEmpty(userModel.getTelphone())) { throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR); } //实现model->dataobject方法 UserDO userDO = convertFromModel(userModel); //insertSelective相对于insert方法,不会覆盖掉数据库的默认值 userDOMapper.insertSelective(userDO); userModel.setId(userDO.getId()); userPasswordDO userPasswordDO = convertPasswordFromModel(userModel); userPasswordDOMapper.insertSelective(userPasswordDO); return; } private userPasswordDO convertPasswordFromModel(UserModel userModel) { if (userModel == null) { return null; } userPasswordDO userPasswordDO = new userPasswordDO(); userPasswordDO.setEncrptPassword(userModel.getEncrptPassword()); userPasswordDO.setUserId(userModel.getId()); return userPasswordDO; } private UserDO convertFromModel(UserModel userModel) { if (userModel == null) { return null; } UserDO userDO = new UserDO(); BeanUtils.copyProperties(userModel, userDO); return userDO; } ``` 4.前端界面 首先在getotp界面添加注册成功的跳转界面 ```javascript success:function (data) { if (data.status=="success") { alert("otp已经发送到了您的手机,请注意查收"); window.location.href="register.html"; }else { alert("otp发送失败,原因为" + data.data.errMsg); } }, ``` 模仿之前写的界面,新建一个register.html ```html

用户注册

``` 5.调试 发现报错,获取不到验证码 跨域请求问题 在UserController上添加如下注解: ```java //跨域请求中,不能做到session共享 @CrossOrigin(allowCredentials = "true",allowedHeaders = "*") ``` 6.注册成功,但是查看数据库,发现password表中并没有user_id 在UserDOMapper的insertSelective方法中添加如下代码: ```xml ``` 通过这样的方式将自增id取出之后复制给对应的UserDO 7.修改UserServiceImpl ```java UserDO userDO = convertFromModel(userModel); //insertSelective相对于insert方法,不会覆盖掉数据库的默认值 userDOMapper.insertSelective(userDO); userModel.setId(userDO.getId()); userPasswordDO userPasswordDO = convertPasswordFromModel(userModel); userPasswordDOMapper.insertSelective(userPasswordDO); return; ``` 重新测试成功 8.上面并没有做手机号的唯一性验证 首先,在数据库中添加索引: 索引名称为:telphone_unique_index,索引字段选择telphone,索引类型为UNIQUE,索引方法为BTREE 然后修改以下代码: ```java try { userDOMapper.insertSelective(userDO); } catch (DuplicateKeyException ex) { throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "手机号已注册"); } ``` ### 3.9 用户模型管理——用户登录功能实现 1.UserController中的用户登录接口 ```java //用户登录接口 @RequestMapping(value = "/login", method = {RequestMethod.POST}, consumes = {CONTENT_TYPE_FORMED}) @ResponseBody public CommonReturnType login(@RequestParam(name = "telphone") String telphone, @RequestParam(name = "password") String password) throws BusinessException, UnsupportedEncodingException, NoSuchAlgorithmException { //入参校验 if (StringUtils.isEmpty(telphone) || StringUtils.isEmpty(password)) { throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR); } //用户登录服务,用来校验用户登录是否合法 //用户加密后的密码 UserModel userModel = userService.validateLogin(telphone, this.EncodeByMd5(password)); //将登陆凭证加入到用户登录成功的session内 this.httpServletRequest.getSession().setAttribute("IS_LOGIN", true); this.httpServletRequest.getSession().setAttribute("LOGIN_USER", userModel); return CommonReturnType.create(null); } ``` 2.UserService中的校验登录方法 ```java /* telphone:用户注册手机 encrptPassowrd:用户加密后的密码 */ UserModel validateLogin(String telphone, String encrptPassword) throws BusinessException; ``` 3.UserServiceImpl的登录方法实现 ```java @Override public UserModel validateLogin(String telphone, String encrptPassword) throws BusinessException { //通过用户手机获取用户信息 UserDO userDO = userDOMapper.selectByTelphone(telphone); if (userDO == null) { throw new BusinessException(EmBusinessError.USER_LOOGIN_FAIL); } userPasswordDO userPasswordDO = userPasswordDOMapper.selectByUserId(userDO.getId()); UserModel userModel = convertFromDataObject(userDO, userPasswordDO); //比对用户信息内加密的密码是否和传输进来的密码相匹配 if (StringUtils.equals(encrptPassword, userModel.getEncrptPassword())) { throw new BusinessException(EmBusinessError.USER_LOOGIN_FAIL); } return userModel; } ``` 4.UserDOMapper.xml中的新建方法 ```xml ``` 5.UserDOMapper中建立映射 ```java //根据电话号码取得用户对象 UserDO selectByTelphone(String telphone); ``` 6.新建前端界面:login.html ```html

用户登录