一个基于SpringBoot搭建的秒杀项目:rocket:,整体框架基于Data Access Object (DAO) -> Service -> Controller结构设计(本项目来源于慕课网免费课程)
- Spring Boot
- Maven
- MyBatis
- MySQL
- IDEA
- 为防止层与层之间的数据透传,每一层最好自己定义数据对象模型。
- 数据库表设计过程中,在建表过程中,可以将用户信息与其对应的密码信息分开建表,可以在后续业务运行过程中提升查询效率,减少不必要的信息的查询,同时也是为了安全。
- Controller层的统一异常处理,通过自定义一个异常处理类
BusinessException实现,内部涉及一个异常信息接口CommonError以及一个异常信息枚举类EmBusinessError,处于controller.error包下。 - Controller层的统一返回信息定义,通过自定义类
CommonReturnType实现,处于controller.response包下。
通过定义getOpt方法处理前端提交的请求,该方法需要处理的逻辑如下
public ReturnType getOpt(@RequestParam(name="telephone") String telephone){
//按照一定规则生成验证码
//将Opt验证码同用户手机号关联,一般采用Redis,此处先采用HttpSession实现
httpServeletRequest.getSession.setAttribute(telephone, optCode);
//将验证码通过短信通道发送给用户
}此处的HttpSession可以通过自动装载HttpServletRequest(单例)得到,此处之所以可以存储多个用户信息,是由于其内部采用Threalocal实现多线程绑定,使得每个用户的先短信验证信息隔离。
通过javascript响应事件,后端收到post请求时,调动getOpt方法进行处理。此处针对后端代码需要做如下处理:
- 完善RequestMapping注解的参数;
@RequestMapping(value = "/getOtp", method = {RequestMethod.POST}, consumes = {CONTENT_TYPE_FORMED})- 加入跨域请求注解
@CrossOrigin,加到UserController类上,加载到方法getOpt上会依然报错(后来被验证无效,在方法上加与不加一样),但是可以与后端产生交互;
//不设置参数的话,依然无法避免
@CrossOrigin(allowCredentials = "true", allowedHeaders = "*")public ReturnType register(//@RequestParam获取前端传入的数据){
// 1.验证用户的手机号与验证码符合
// 2.进入注册流程,分层设计
// 3.返回注册成功信息
}用户自增id不需要传入值,非自增字段,且没有设置默认值会导致mybatis报错。
为避免相同手机号多次注册,可以将telephone字段设为唯一索引。
主要涉及通过用户手机号获取用户信息,需要在UserDOMapper.xml中加入相关的数据库查询代码,如下:
<select id="selectByTelephone" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from user_info
where telephone = #{telephone, jdbcType=VARCHAR}
</select>然后,在UserDO.java中加入对应的方法:
UserDO selectByTelephone(String telephone);验证成功后,将登录凭证加入到用户登录成功的session内。
- 调用Maven仓库中的Hibernate Validator Engine中的模块;
- 通过@NotBlank、@NotNull、@Max、@Min注解约束参数的范围
优先设计领域模型(不能一上来就去数据库建表) 领域模型
public class ItemModel{
// id字段
private Integer id;
//商品名称
private String title;
//价格
private BigDecimal price;
//库存
private Integer stock;
//商品描述
private String description;
//商品销量
private Integer sales;
//商品图片url
private String url;
}根据领域模型合理分表,创建对应表结构,最后通过mybatis-generator生成DOMapper.xml、DOMapper的java接口、DO类。
此处分出了两张表,一张商品信息(item)表,一张库存(item_stock)表,修改DOMopper.xml使得item的id与item_stock中的item_id关联。(删除线中的内容对于这一关联操作,是通过java代码实现的,先取出item的id,然后把它赋值给item_stock表的item_id字段)。
(ItemDOMapper.xml以及ItemStockMapper.xml)
(找到insert操作以及insertSelective操作) userGenertedKeys = "true" keyProperty = "id"(不是true) (此处的原因与id自增有关,既然如此,为何myBatis在设计的时候为啥不默认为true)(后来发现,如果不写,service层通过DO取不出来id值)主要需要有创建商品、商品列表浏览、商品详情浏览等功能。
ItemModel createItem(ItemModel itemModel);
List<ItemModel> listItem();
ItemModel getItemById(Integer id);创建Item的方法设计流程:
@Transactional
public ItemModel createItem(ItemModel itemModel){
// 1.校验入参,通过先前定义的ValdationImpl实现
// 2.转化itemModel -> dataObject
// 3.写入数据库
// 4.返回创建完成的对象
// 注记:double在传到前端会有精度损失的问题,需要转化为BigDecmal
}public class OrderModel{
private String id;
private Integer userId;
private Integer itemId;
private BigDecimal itemPrice;
private Integer amount;
private BigDecimal orderPrice;
}根据领域模型定义数据库表,double类型长度为“x, y”。
OrderModel createOrder(用户, 商品, 数量){
// 1.校验下单状态,商品是否存在,用户是否合法,购买数量是否正确
// 2.落单减库存
// 3.订单入库
// 4.返回给Controller
}- 设计领域模型
- 根据领域模型建表(是否设计多张表)
- 使用mybatis-generator得到表对应的DOMapper.xml、DOMapper.java、以及DO数据模型
- 定义对应的service接口
- 实现service接口
- 实现对应Controller层的前端展示模型(VO)以及前端请求时需要的处理方法
- 绘制前端模板
期间对于一些新的数据库操作,可以去DOMapper.java以及DOMapper.xml中添加或者修改相应逻辑。
<update id="updateByPrimaryKey" parameterType="com.miaoshaproject.dataobject.SequenceDO">
<!--
WARNING - @mbg.generated
This element is automatically generated by MyBatis Generator, do not modify.
This element was generated on Sun Jul 26 14:13:49 CST 2020.
-->
update sequence_info
set step = #{step,jdbcType=INTEGER}
<!--此处仅根据name匹配进行更新>
</update> <update id="updateByPrimaryKeySelective" parameterType="com.miaoshaproject.dataobject.SequenceDO">
<!--
WARNING - @mbg.generated
This element is automatically generated by MyBatis Generator, do not modify.
This element was generated on Sun Jul 26 14:13:49 CST 2020.
-->
update sequence_info
<set>
<if test="currentValue != null">
current_value = #{currentValue,jdbcType=INTEGER},
</if>
<if test="step != null">
step = #{step,jdbcType=INTEGER},
</if>
</set>
where name = #{name,jdbcType=VARCHAR}
</update>
<update id="updateByPrimaryKey" parameterType="com.miaoshaproject.dataobject.SequenceDO">
<!--
WARNING - @mbg.generated
This element is automatically generated by MyBatis Generator, do not modify.
This element was generated on Sun Jul 26 14:13:49 CST 2020.
-->
update sequence_info
set current_value = #{currentValue,jdbcType=INTEGER},step = #{step,jdbcType=INTEGER}
where name = #{name,jdbcType=VARCHAR}
</update>Field : id; promoName; startDate; endDate; itemId;(秒杀活动适用商品) promoItemPrice; 时间模型使用Joda;
秒杀活动与秒杀商品做聚合:将PromoModel聚合(嵌套)在ItemMOdel中。
PromoModel promoModel = promoService.getPromoModel(itemModel.getId());
if(promoModel != null && promoModel.getStatus().intValue() != 3)
itemModel.setPromoModel(promoModel);将数据库中的double类型转化为BigDecimal类型。 修改订单模型,在其中加入秒杀活动判断(通过前端传入的秒杀活动id判断是秒杀,还是平价)。