feat: 添加商城相关操作

This commit is contained in:
LittleBoy 2022-11-26 10:56:59 +08:00
parent cada4dbe49
commit 5463c1e60c
19 changed files with 646 additions and 3 deletions

View File

@ -0,0 +1,28 @@
package me.xiaoyan.point.api.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import me.xiaoyan.point.api.pojo.Goods;
import me.xiaoyan.point.api.pojo.vo.PageParam;
import me.xiaoyan.point.api.service.GoodsService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@RequestMapping("shop/goods")
public class ShopGoodsController {
@Resource
private GoodsService goodsService;
@GetMapping("query")
public Page<Goods> query(
int category,
int page,
int pageSize
) {
return goodsService.queryByPage(category, Page.of(page, pageSize));
}
}

View File

@ -0,0 +1,76 @@
package me.xiaoyan.point.api.controller;
import cn.dev33.satoken.stp.StpUtil;
import lombok.extern.slf4j.Slf4j;
import me.xiaoyan.point.api.error.BizException;
import me.xiaoyan.point.api.pojo.OrderInfo;
import me.xiaoyan.point.api.pojo.vo.CreateOrderData;
import me.xiaoyan.point.api.service.GoodsService;
import me.xiaoyan.point.api.service.OrderInfoService;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.concurrent.ConcurrentHashMap;
/**
* 订单表(OrderInfo)表控制层
*
* @author makejava
* @since 2022-11-24 09:32:38
*/
@Slf4j
@RestController
@RequestMapping("shop/order")
public class ShopOrderInfoController {
@Resource
private OrderInfoService orderInfoService;
@Resource
private GoodsService goodsService;
@Resource
private StringRedisTemplate stringRedisTemplate;
// 是否已经没有库存
private ConcurrentHashMap<Integer, Boolean> stockOutMap = new ConcurrentHashMap<>();
private static final String CACHE_STOCK_KEY = "goods:stock:";
private String cacheKey(long gid) {
return CACHE_STOCK_KEY + gid;
}
@PostConstruct // 初始化执行一次
private void initGoodsStockCache() {
//TODO 应该定时更新缓存数据
goodsService.queryAllGoodsIdAndStock().forEach(g -> {
log.info("缓存 id:{} stock:{} ", g.getId(), g.getStock());
// 缓存库存
stringRedisTemplate.opsForValue().set(cacheKey(g.getId()), g.getStock().toString());
});
}
@PostMapping("create")
public OrderInfo create(@Validated @RequestBody CreateOrderData data) {
if (data.getGoodsId() <= 0 || data.getBuyCount() <= 0) {
throw BizException.create("订单参数不正确");
}
//1.内存判断
if (stockOutMap.get(data.getGoodsId())) {
throw BizException.create("库存不足");
}
//2.缓存redis判断
long count = stringRedisTemplate.opsForValue().decrement(cacheKey(data.getGoodsId()));
if (count < 0) {
// 此时库存没有了 , 保存到已买完的对象
stockOutMap.put(data.getGoodsId(),true);
log.info("stock count ===>" + count);
// 对缓存进行库存 + 1
stringRedisTemplate.opsForValue().increment(cacheKey(data.getGoodsId())); //
throw BizException.create("库存不足");
}
//3.数据库
return orderInfoService.create(StpUtil.getLoginIdAsInt(), data);
}
}

View File

@ -0,0 +1,30 @@
package me.xiaoyan.point.api.mapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import me.xiaoyan.point.api.pojo.Goods;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* @Entity me.xiaoyan.point.api.pojo.Goods
*/
@Mapper
public interface GoodsMapper extends BaseMapper<Goods> {
public Page<Goods> queryByCategory(@Param("category") int category,Page page);
/**
* 扣除商品库存
* @param id
* @param count
* @return
*/
public int deductCount(@Param("id") int id, @Param("count") int count);
public List<Goods> queryAllGoodsIdAndStock();
}

View File

@ -0,0 +1,17 @@
package me.xiaoyan.point.api.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import me.xiaoyan.point.api.pojo.OrderInfo;
import org.apache.ibatis.annotations.Mapper;
/**
* 订单表(OrderInfo)表数据库访问层
*
* @author makejava
* @since 2022-11-24 09:32:38
*/
@Mapper
public interface OrderInfoMapper extends BaseMapper<OrderInfo> {
}

View File

@ -0,0 +1,107 @@
package me.xiaoyan.point.api.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.Date;
/**
* 商品表
* @TableName goods
*/
@Data
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
@Builder
@TableName(value ="goods")
public class Goods implements Serializable {
/**
*
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 商品类别(1:普通 2:精选 3:秒杀 4:抽奖)
*/
private Integer category;
/**
* 商品类型(1:实物 2:虚拟)
*/
private Integer type;
/**
*
*/
private String title;
/**
* 原价
*/
private Integer originPrice;
/**
* 价格
*/
private Integer price;
/**
* 库存数量
*/
private Integer stock;
/**
* 购买最大数量(0表示不限制)
*/
private Integer limitCount;
/**
* 商品图
*/
private String cover;
/**
* 描述
*/
private String description;
/**
* 提示
*/
private String notice;
/**
* 上架时间
*/
private Date onlineTime;
/**
* 下架时间
*/
private Date offlineTime;
/**
*
*/
private Date createTime;
/**
*
*/
private Date updateTime;
/**
*
*/
private Integer status;
}

View File

@ -0,0 +1,50 @@
package me.xiaoyan.point.api.pojo;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.io.Serializable;
/**
* 订单表(OrderInfo)表实体类
*
* @author makejava
* @since 2022-11-24 09:32:42
*/
@Data
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
@Builder
@TableName(value ="order_info")
public class OrderInfo {
@TableId
//订单编号
private String id;
//商品编号
private Long gid;
//价格
private Integer price;
//购买数量
private Integer count;
//用户编号
private Integer uid;
//订单数据
private String data;
private Date createTime;
private Date updateTime;
//订单状态(0:已删除 1:已取消 2:待确认 3:已完成)
private Integer status;
}

View File

@ -0,0 +1,8 @@
package me.xiaoyan.point.api.pojo.dto;
public class OrderStatus {
public static final int DELETE = 0;
public static final int CANCEL = 1;
public static final int CONFIRM = 2;
public static final int DONE = 3;
}

View File

@ -0,0 +1,11 @@
package me.xiaoyan.point.api.pojo.vo;
import lombok.Data;
import java.io.Serializable;
@Data
public class CreateOrderData implements Serializable {
private int goodsId;
private int buyCount;
}

View File

@ -0,0 +1,26 @@
package me.xiaoyan.point.api.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import me.xiaoyan.point.api.pojo.Goods;
import com.baomidou.mybatisplus.extension.service.IService;
import me.xiaoyan.point.api.pojo.vo.PageParam;
import java.util.List;
/**
*
*/
public interface GoodsService extends IService<Goods> {
Page<Goods> queryByPage(int category, Page page);
/**
* 减库存
* @param id
* @param count
* @return
*/
boolean deductStock(int id,int count);
List<Goods> queryAllGoodsIdAndStock();
}

View File

@ -0,0 +1,17 @@
package me.xiaoyan.point.api.service;
import com.baomidou.mybatisplus.extension.service.IService;
import me.xiaoyan.point.api.pojo.OrderInfo;
import me.xiaoyan.point.api.pojo.vo.CreateOrderData;
/**
* 订单表(OrderInfo)表服务接口
*
* @author makejava
* @since 2022-11-24 09:32:44
*/
public interface OrderInfoService extends IService<OrderInfo> {
OrderInfo create(int uid, CreateOrderData data);
}

View File

@ -0,0 +1,37 @@
package me.xiaoyan.point.api.service.impl;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import me.xiaoyan.point.api.pojo.Goods;
import me.xiaoyan.point.api.service.GoodsService;
import me.xiaoyan.point.api.mapper.GoodsMapper;
import org.springframework.stereotype.Service;
import java.util.List;
/**
*
*/
@Service
public class GoodsServiceImpl extends ServiceImpl<GoodsMapper, Goods>
implements GoodsService {
@Override
public Page<Goods> queryByPage(int category, Page page) {
return this.getBaseMapper().queryByCategory(category, page);
}
@Override
public boolean deductStock(int id, int count) {
return getBaseMapper().deductCount(id,count) == 1;
}
@Override
public List<Goods> queryAllGoodsIdAndStock() {
return getBaseMapper().queryAllGoodsIdAndStock();
}
}

View File

@ -0,0 +1,84 @@
package me.xiaoyan.point.api.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import me.xiaoyan.point.api.error.BizException;
import me.xiaoyan.point.api.mapper.OrderInfoMapper;
import me.xiaoyan.point.api.pojo.Goods;
import me.xiaoyan.point.api.pojo.OrderInfo;
import me.xiaoyan.point.api.pojo.UserInfo;
import me.xiaoyan.point.api.pojo.dto.OrderStatus;
import me.xiaoyan.point.api.pojo.vo.CreateOrderData;
import me.xiaoyan.point.api.service.GoodsService;
import me.xiaoyan.point.api.service.OrderInfoService;
import me.xiaoyan.point.api.service.UserInfoService;
import me.xiaoyan.point.api.util.OrderIdGenerator;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.Date;
/**
* 订单表(OrderInfo)表服务实现类
*
* @author makejava
* @since 2022-11-24 09:32:44
*/
@Service
public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo> implements OrderInfoService {
@Resource
private UserInfoService userInfoService;
@Resource
private GoodsService goodsService;
@Transactional
@Override
public OrderInfo create(int uid, CreateOrderData data) {
// 1.查询用户信息
final UserInfo user = userInfoService.getInfoById(uid);
// 判断用户信息
if (user == null) throw BizException.create("用户信息不存在");
if (user.getStatus() != 1) throw BizException.create("用户状态不正确");
// 2.查询商品信息
final Goods goods = goodsService.getById(data.getGoodsId());
if (goods == null) throw BizException.create("兑换的商品不存在");
long now = new Date().getTime();
if (goods.getOnlineTime().getTime() > now || goods.getOfflineTime().getTime() < now) {
throw BizException.create("商品未上架或已下架");
}
if (goods.getStock() < data.getBuyCount()) throw BizException.create("商品存库不足");
// 判断购买数量的限制
if(buyHistoryCount(uid,data.getGoodsId()) + data.getBuyCount() > goods.getLimitCount()){
throw BizException.create("最多兑换" + goods.getLimitCount() + "");
}
// 3.减库存
if (!goodsService.deductStock(data.getGoodsId(), data.getBuyCount())) {
throw BizException.create("商品库存不足");
}
// 4.创建订单
OrderInfo orderInfo = OrderInfo.builder()
.id(OrderIdGenerator.next())
.gid((long) data.getGoodsId())
.uid(uid)
.count(data.getBuyCount())
.price(goods.getPrice())
.status(OrderStatus.CONFIRM)
.build();
if (save(orderInfo)) {
return orderInfo;
}
throw BizException.create("创建订单失败");
}
public long buyHistoryCount(int uid, int gid) {
QueryWrapper q = new QueryWrapper();
q.eq("uid", gid);
q.eq("gid", gid);
q.ge("status", OrderStatus.CONFIRM); // 状态为2(待确认)和3(已完成)
return count(q);
}
}

View File

@ -0,0 +1,28 @@
package me.xiaoyan.point.api.util;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
public class OrderIdGenerator {
private static AtomicInteger atomic = new AtomicInteger();
private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyMMddHHmmss");
private static long prevTime = 0;
public static String next() {
long now = System.currentTimeMillis() / 1000;
if (now > prevTime) {
prevTime = now;
atomic.set(0);
}
return dateFormat.format(new Date()) + String.format("%05d", atomic.addAndGet(1));
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 1000; i++) {
System.out.println(next());
Thread.sleep(1);
}
}
}

View File

@ -46,5 +46,33 @@ create table sign_record
create table goods
(
id bigint(15) primary key auto_increment,
category tinyint(2) null default 1 comment '商品类别(1:普通 2:精选 3:秒杀 4:抽奖)',
type tinyint(2) null default 1 comment '商品类型(1:实物 2:虚拟)',
title varchar(50) not null,
origin_price int(10) unsigned comment '原价' default 0,
price int(10) unsigned not null comment '价格',
stock int(10) unsigned not null comment '库存数量',
limit_count int(10) unsigned null default 1 comment '购买最大数量(0表示不限制)',
cover varchar(200) not null comment '商品图',
description text not null comment '描述',
notice varchar(500) null comment '提示',
online_time datetime not null comment '上架时间',
offline_time datetime not null comment '下架时间',
create_time datetime default current_timestamp,
update_time datetime null on update current_timestamp,
status tinyint(2) default 1,
index ix_title (title)
) engine = innodb comment '商品表';
create table order_info
(
id varchar(50) not null comment '订单编号',
gid bigint(15) not null comment '商品编号',
price int(10) not null comment '价格',
uid int(10) not null comment '用户编号',
data json null comment '订单数据',
create_time datetime default current_timestamp,
update_time datetime null on update current_timestamp,
status tinyint(2) default 1 comment '订单状态(0:已删除 1:待确认 2:已取消 3:已完成)'
) comment '订单表';

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="me.xiaoyan.point.api.mapper.GoodsMapper">
<resultMap id="BaseResultMap" type="me.xiaoyan.point.api.pojo.Goods">
<id property="id" column="id" jdbcType="BIGINT"/>
<result property="category" column="category" jdbcType="TINYINT"/>
<result property="type" column="type" jdbcType="TINYINT"/>
<result property="title" column="title" jdbcType="VARCHAR"/>
<result property="originPrice" column="origin_price" jdbcType="INTEGER"/>
<result property="price" column="price" jdbcType="INTEGER"/>
<result property="stock" column="stock" jdbcType="INTEGER"/>
<result property="limitCount" column="limit_count" jdbcType="INTEGER"/>
<result property="cover" column="cover" jdbcType="VARCHAR"/>
<result property="description" column="description" jdbcType="VARCHAR"/>
<result property="notice" column="notice" jdbcType="VARCHAR"/>
<result property="onlineTime" column="online_time" jdbcType="TIMESTAMP"/>
<result property="offlineTime" column="offline_time" jdbcType="TIMESTAMP"/>
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
<result property="updateTime" column="update_time" jdbcType="TIMESTAMP"/>
<result property="status" column="status" jdbcType="TINYINT"/>
</resultMap>
<sql id="Base_Column_List">
id,category,type,
title,origin_price,price,
stock,limit_count,cover,
description,notice,online_time,
offline_time,create_time,update_time,
status
</sql>
<update id="deductCount">
update points_sys.goods
set stock=stock - #{count}
where id = #{id}
and stock >= #{count}
</update>
<select id="queryByCategory" resultType="me.xiaoyan.point.api.pojo.Goods">
select *
from points_sys.goods
where online_time &lt; current_timestamp
and offline_time > current_timestamp
and stock > 0
and category = #{category}
and status != 0
</select>
<select id="queryAllGoodsIdAndStock" resultType="me.xiaoyan.point.api.pojo.Goods">
select id,stock
from points_sys.goods
where online_time &lt; current_timestamp
and offline_time > current_timestamp
and status != 0
</select>
</mapper>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="me.xiaoyan.point.api.mapper.OrderInfoMapper">
</mapper>

View File

@ -2,12 +2,42 @@ package me.xiaoyan.point.api;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.StringRedisTemplate;
import javax.annotation.Resource;
@SpringBootTest
class ApiApplicationTests {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Test
void contextLoads() {
}
void testBuy(long buyCount){
long count = stringRedisTemplate.opsForValue().decrement("a",buyCount);
System.out.println(count);
if(count<0){
System.out.println("库存不足");
stringRedisTemplate.opsForValue().increment("a",buyCount);
return;
}
if(count == 0){
System.out.println("下一次就没有存库了");
return;
}
System.out.println("购买成功");
}
@Test
void testRedis(){
stringRedisTemplate.opsForValue().set("a","1");
System.out.println("----------第1次----------");
testBuy(2);
System.out.println("----------第2次----------");
testBuy(1);
System.out.println("----------第3次----------");
testBuy(1);
}
}