java电商项目搭建-------购物车模块
努力好了,时间会给你答案。--------magic_guo购物车模块的设计方式有很多种:1、用户登录或者未登录① 登录将购物车放入redis,未登录将购物车放入Cookie中② 登录将购物车放入mysql,未登录将购物车存入redis2、强制用户登录除了首页和商品详情页,其他的模块访问都会做限制,以达到用户登录的效果;但无论如何,购物车信息只是一个暂存的信息,创建订单后,会将购物车的信息删除,而
努力好了,时间会给你答案。--------magic_guo
购物车模块的设计方式有很多种:
1、用户登录或者未登录
① 登录将购物车放入redis,未登录将购物车放入Cookie中
② 登录将购物车放入mysql,未登录将购物车存入redis
2、强制用户登录
除了首页和商品详情页,其他的模块访问都会做限制,以达到用户登录的效果;
但无论如何,购物车信息只是一个暂存的信息,创建订单后,会将购物车的信息删除,而且购物车访问的频率会很大,对于性能也有一定的要求,这是购物车数据库的设计的两个重要因素;
一般来说,购物车模块需要存储的重要信息分为两个:用户和商品,也就是用户id和商品id。如果放在redis中,则使用hash结构来存储比较方便;如果放在Cookie中,因为cookie是使用键值对存储的,其结构也类似于map或者hash;如果mysql中,则牵涉到数据库表的设计;
本文所采用的是mysql+redis的方案,如下图:
如上图所表示,用户登录则将购物车放入mysql中,未登录则放入redis中,那问题来了,怎么区分用户是否登录?
上文在sso单点登录模块,我们在登录时,会返回给浏览器一个token,下次用户登录在请求头中携带一个字段为Authorization的token,在路由网关验证token,则可以访问其他的模块;
那么在购物车模块,我们以AOP面向切面编程思想,做了一个@LoginUser的装饰器,来验证用户是否登录,如果获取到token,则会注入user,没有携带token则user的注入为null;凡是方法上添加了@LoginUser注解,则在方法执行前都会做一个登录的验证,返回一个user;
@Documented // 生成API文档
@Target({ElementType.METHOD}) // 这个注解可以添加到那些元素上
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginUser {
}
实现类:
在这里,我们使用环绕通知,来实现此功能,
@Around的作用
1、既可以在目标方法之前织入增强动作,也可以在执行目标方法之后织入增强动作;
2、可以决定目标方法在什么时候执行,如何执行,甚至可以完全阻止目标目标方法的执行;
3、可以改变执行目标方法的参数值,也可以改变执行目标方法之后的返回值; 当需要改变目标方法的返回值时,只能使用Around方法;
注意事项:虽然Around功能强大,但通常需要在线程安全的环境下使用。因此,如果使用普通的Before、AfterReturing增强方法就可以解决的事情,就没有必要使用Around增强处理了。
@Component
@Aspect // 标识 这是一个切面
public class LoginUserImpl {
@Autowired
private HttpServletRequest request;
@Around("@annotation(loginUser)")
public Object loginUser(ProceedingJoinPoint point, LoginUser loginUser) throws Exception {
// 封装的登录的对象
User user = null;
// 1.获取token
String token = request.getHeader("Authorization");
// 如果token在请求头中没有获取到,再看看地址栏中是否存在
if (StringUtils.isEmpty(token)) {
token = request.getParameter("token");
}
Object[] args = point.getArgs(); //因为方法有多个形参
if (!StringUtils.isEmpty(token)) {
// 解析token
DecodedJWT verify = JWTUtils.verify(token);
// 从token中获取uid
String id = verify.getClaim("id").asString();
// 把uid封装到User对象中
user = new User();
user.setId(Integer.parseInt(id));
// 2.遍历数组,找到user的参数
for (int i = 0; i < args.length; i++) {
if (args[i] != null && args[i].getClass() == User.class) {
args[i] = user;
break; // 替换完成就马上返回
}
}
}
try {
// 放行
return point.proceed(args);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return user;
}
}
maven依赖:
<dependencies>
<dependency>
<groupId>com.guo</groupId>
<artifactId>shop-common</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
配置:
spring:
cloud:
config:
uri: http://localhost:9999
profile: shop-car, eureka-client, log, datasource, redis
name: application
mybatis-plus:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.guo
各个层级的代码:
Controller层:
@RequestMapping("/car")
@RestController
@Slf4j
public class CarController {
@Autowired
private ICarService carService;
@Autowired
private RedisTemplate redisTemplate;
@PostConstruct
public void init() {
// 设置key的序列化方式为字符串
redisTemplate.setKeySerializer(new StringRedisSerializer());
}
@RequestMapping("/addCarMysql")
public ResultEntity addCarMysql(@RequestBody Car car) {
return ResultEntity.success(carService.insert(car));
}
@LoginUser // 代表这个接口上需要注入当前登录用户(如果登录就注入user, 没有登录就注入null)
@RequestMapping("/addCar")
@ResponseBody
public ResultEntity addCar(@CookieValue(name = ShopConstants.ANON_ID, required = false) String anonId, Car car, User user, HttpServletResponse response) {
// 判断用户是否登录
log.info("user:{}", user);
if (user.getId() != null) {
// 插入数据库,并判断数据库中是否存在此数据,如果存在则更新数量,如果不存在直接加入
car.setUid(user.getId());
carService.addCarMysql(car);
} else {
// 如果没有匿名用户的唯一id,生成一个匿名用户的唯一标识uuid
if (StringUtils.isEmpty(anonId)) {
anonId = UUID.randomUUID().toString();
// 创建一个cookie
Cookie cookie = new Cookie(ShopConstants.ANON_ID, anonId);
// 解决cookie跨域问题
cookie.setPath("/");
// 设置cookie的有效时间为7天
cookie.setMaxAge(60 * 60 * 24 * 7);
// 将cookie响应给浏览器
response.addCookie(cookie);
}
// 插入redis中,使用hash数据结构(相当于map)
if (redisTemplate.opsForHash().hasKey(anonId, car.getGid())) {
Object count = redisTemplate.opsForHash().get(anonId, car.getGid());
assert count != null;
car.setGcount(Integer.parseInt(count.toString()) + car.getGcount());
}
log.info("anonId:{}", anonId);
redisTemplate.opsForHash().put(anonId, car.getGid(), car.getGcount());
}
return ResultEntity.success();
}
@LoginUser
@RequestMapping("/getCarList")
public ResultEntity getCarList(@CookieValue(name = ShopConstants.ANON_ID, required = false) String anonId, User user) {
List<CarGoods> carArrayList = new ArrayList<>();
// 判断用户是否登录
if (user.getId() != null) {
// 查询数据库
carArrayList = carService.getCarList(user.getId());
} else {
// 查询redis
if (!StringUtils.isEmpty(anonId)) {
Set keys = redisTemplate.opsForHash().keys(anonId);
if (!keys.isEmpty()) {
for (Object gid : keys) {
Object count = redisTemplate.opsForHash().get(anonId, gid);
CarGoods carGoods = carService.getCarGoodsByGid(gid);
assert count != null;
carGoods.setCount(Integer.parseInt(count.toString()));
carArrayList.add(carGoods);
}
}
}
}
return ResultEntity.success(carArrayList);
}
@LoginUser
@RequestMapping("/updateCar")
public ResultEntity updateCar(User user, @CookieValue(name = ShopConstants.ANON_ID, required = false) String anonId, Integer gid, Integer count) {
if (user.getId() != null) {
// 更新数据库
Car car = new Car();
car.setGcount(count);
EntityWrapper<Car> carEntityWrapper = new EntityWrapper<>();
carEntityWrapper.eq("uid", user.getId());
carEntityWrapper.eq("gid", gid);
// 根据用户id和商品id来修改购物车的中商品的数量
carService.update(car, carEntityWrapper);
} else {
redisTemplate.opsForHash().put(anonId, gid, count);
}
return ResultEntity.success();
}
@LoginUser
@RequestMapping("/showCarNum")
public ResultEntity showCarNum(@CookieValue(name = ShopConstants.ANON_ID, required = false) String anonID, User user) {
int count = 0;
if (user.getId() != null) {
EntityWrapper<Car> carEntityWrapper = new EntityWrapper<>();
carEntityWrapper.eq("uid", user.getId());
count = carService.selectCount(carEntityWrapper);
}else {
count = redisTemplate.opsForHash().keys(anonID).size();
}
return ResultEntity.success(count);
}
@RequestMapping("/deleteCarItems")
@LoginUser
public ResultEntity deleteCarItems(User user, @CookieValue(name = ShopConstants.ANON_ID, required = false) String anonId, Integer gid) {
if (user.getId() != null) {
EntityWrapper<Car> carEntityWrapper = new EntityWrapper<>();
carEntityWrapper.eq("uid", user.getId());
carEntityWrapper.eq("gid", gid);
carService.delete(carEntityWrapper);
}else {
redisTemplate.opsForHash().delete(anonId, gid);
}
return ResultEntity.success();
}
@RequestMapping("/clearCar")
@LoginUser
public ResultEntity clearCar(User user, @CookieValue(name = ShopConstants.ANON_ID, required = false) String anonId) {
if (user.getId() != null) {
EntityWrapper<Car> carEntityWrapper = new EntityWrapper<>();
carEntityWrapper.eq("uid", user.getId());
// 删除此用户下的所有购物车商品
carService.delete(carEntityWrapper);
} else {
// 将此hash结构删除掉
Set keys = redisTemplate.opsForHash().keys(anonId);
for (Object key : keys) {
redisTemplate.opsForHash().delete(anonId, key);
}
}
return ResultEntity.success();
}
}
Service interface层:
public interface ICarService extends IService<Car> {
Integer addCarMysql(Car car);
ArrayList<CarGoods> getCarList(Integer id);
CarGoods getCarGoodsByGid(Object gid);
}
ServiceImpl层:
@Service
@Slf4j
public class CarServiceImpl extends ServiceImpl<CarMapper, Car> implements ICarService {
@Override
public Integer addCarMysql(Car car) {
Integer numberOfAffectedRows;
// 查询数据库中此用户是否有此类商品(防止用户一直在点添加购物车,而不是直接点数量)
EntityWrapper<Car> carEntityWrapper1 = new EntityWrapper<>();
carEntityWrapper1.eq("uid", car.getUid());
carEntityWrapper1.eq("gid", car.getGid());
List<Car> cars = baseMapper.selectList(carEntityWrapper1);
if (cars.size() > 0) {
Car dbCar = cars.get(0);
car.setGcount(car.getGcount() + dbCar.getGcount());
EntityWrapper<Car> carEntityWrapper = new EntityWrapper<>();
carEntityWrapper.eq("uid", car.getUid());
carEntityWrapper.eq("gid", car.getGid());
numberOfAffectedRows = baseMapper.update(car, carEntityWrapper);
} else {
numberOfAffectedRows = baseMapper.insert(car);
}
return numberOfAffectedRows;
}
@Override
public ArrayList<CarGoods> getCarList(Integer uid) {
return baseMapper.getCarList(uid);
}
@Override
public CarGoods getCarGoodsByGid(Object gid) {
return baseMapper.getCarGoodsByGid(gid);
}
}
mapper层:
public interface CarMapper extends BaseMapper<Car> {
ArrayList<CarGoods> getCarList(Integer uid);
CarGoods getCarGoodsByGid(Object gid);
}
mapper.xml层:
<?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="com.guo.mapper.CarMapper">
<resultMap id="baseResultMap" type="com.guo.domain.CarGoods">
<result column="gcount" property="count"/>
<result column="gname" property="gname"/>
<result column="gprice" property="gprice"/>
<result column="gid" property="gid"/>
<result column="gdesc" property="gdesc"/>
<!-- 对多-->
<collection property="goodsPicList" ofType="com.guo.entity.GoodsPng">
<id column="gpid" property="id"/>
<result column="gid" property="gid"/>
<result property="png" column="png"/>
</collection>
</resultMap>
<select id="getCarList" resultMap="baseResultMap">
select
c.*, g.*, gp.id as gpid
from
t_car as c
left join t_goods as g on (c.gid=g.id)
left join t_goods_pic as gp on (g.id=gp.gid)
where c.uid = #{uid}
</select>
<select id="getCarGoodsByGid" resultMap="baseResultMap">
select
g.*, gp.id as gpid, gp.gid, gp.png
from t_goods as g
left join t_goods_pic as gp on (g.id=gp.gid)
where g.id = #{gid}
</select>
</mapper>
启动类:
@SpringBootApplication(scanBasePackages = "com.guo")
@EnableEurekaClient
@MapperScan("com.guo.mapper")
public class ShopCarApplication {
public static void main(String[] args) {
SpringApplication.run(ShopCarApplication.class, args);
}
}
对于模块的设计,一定要理清思路和流程,这样的话,在编码的时候,就不会有疑问,至少大致流程是没问题的;以上接口都经过测试无误,有什么问题,请批评指正!
本文章教学视频来自:https://www.bilibili.com/video/BV1tb4y1Q74E?p=3&t=125
静下心,慢慢来,会很快!
更多推荐
所有评论(0)