# 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
select
from user_password
where user_id = #{userId,jdbcType=INTEGER}
```
```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
```
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
```
### 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
select
from user_info
where telphone = #{telphone,jdbcType=VARCHAR}
```
5.UserDOMapper中建立映射
```java
//根据电话号码取得用户对象
UserDO selectByTelphone(String telphone);
```
6.新建前端界面:login.html
```html