package com.jt.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;
@TableName("tb_cart")
@Data
@Accessors(chain=true)
public class Cart extends BasePojo{
@TableId(type=IdType.AUTO) //主键自增
private Long id; //主键信息
private Long userId; //用户Id
private Long itemId; //商品id信息
private String itemTitle; //商品标题
private String itemImage; //商品图片信息
private Long itemPrice; //商品价格
private Integer num; //购买数量
}
<parent>
<groupId>com.jt.huanan</groupId>
<artifactId>jt</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<!--2.添加依赖 jt-common -->
<dependencies>
<dependency>
<groupId>com.jt.huanan</groupId>
<artifactId>jt-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
<!--3.添加插件 -->
<!--build是负责项目打包部署 一般将项目开发完成之后,需要进行服务器部署(Linux) -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
@Service
public class DubboCartServiceImpl implements DubboCartService {
@Autowired
private CartMapper cartMapper;
}
server:
port: 8094
servlet:
context-path: /
spring:
datasource:
#引入druid数据源
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/jtdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
username: root
password: root
mvc:
view:
prefix: /WEB-INF/views/
suffix: .jsp
#mybatis-plush配置
mybatis-plus:
type-aliases-package: com.jt.pojo
mapper-locations: classpath:/mybatis/mappers/*.xml
configuration:
map-underscore-to-camel-case: true
logging:
level:
com.jt.mapper: debug
#关于Dubbo配置
dubbo:
scan:
basePackages: com.jt #指定dubbo的包路径
application: #应用名称
name: provider-cart #一个接口对应一个服务名称
registry:
address: zookeeper://192.168.126.129:2181 #?backup=192.168.126.129:2182,192.168.126.129:2183
protocol: #指定协议
name: dubbo #使用dubbo协议(tcp-ip) web-controller直接调用sso-Service
port: 20882 #每一个服务都有自己特定的端口 不能重复.
1.当用户点击购车车按钮时,应该跳转到购物车管理页面 cart.jsp
2.根据用户id查询 当前用户的购物车信息.
3.通过查询之后得到结果,应该在页面中进行展现. 页面中通过el表达式 获取购物车列表记录,之后展现 ${cartList}
1.url网址: http://www.jt.com/cart/show.html
2.参数: 无
3.返回值: cart.jsp页面
package com.jt.controller;
import java.util.List;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import com.alibaba.dubbo.config.annotation.Reference;
import com.jt.pojo.Cart;
import com.jt.service.DubboCartService;
@Controller
@RequestMapping("/cart")
public class CartController {
@Reference(check=false)
private DubboCartService cartService;
/**
* 业务功能:展现购物车列表记录
* url:http://www.jt.com/cart/show.html
* 返回值: cart.jsp页面
* 页面取值问题: ${cartList}
*/
@RequestMapping("/show")
public String findCartList(Model model) {
//.查询购物车信息
Long userId = 7L; //暂时写死,后期维护
List<Cart> cartList = cartService.findCartList(userId);
model.addAttribute("cartList", cartList);
return "cart";
}
}
@Service
public class DubboCartServiceImpl implements DubboCartService {
@Autowired
private CartMapper cartMapper;
//根据userId查询购物车记录
@Override
public List<Cart> findCartList(Long userId) {
QueryWrapper<Cart> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user_id", userId);
return cartMapper.selectList(queryWrapper);
}
}
说明:当用户点击购物车按钮时, 数量会发生变化/.通过ajax的远程调用的形式将数据进行更新.
$(".increment").click(function(){//+
var _thisInput = $(this).siblings("input");
_thisInput.val(eval(_thisInput.val()) + 1);
$.post("/cart/update/num/"+_thisInput.attr("itemId")+"/"+_thisInput.val(),function(data){
TTCart.refreshTotalPrice();
});
});
/**
* 完成购物车数量的更新
* 1.url:http://www.jt.com/cart/update/num/1474392189/3
2.参数: 通过url地址,拼接参数.
3.返回值: void
规则: //如果restFul中的参数名称与对象的属性一致.则可以使用对象接收
*/
@RequestMapping("/update/num/{itemId}/{num}")
@ResponseBody
public void updateCartNum(Cart cart) {
Long userId = 7L; //暂时写死,后期维护
cart.setUserId(userId);
cartService.updateCartNum(cart);
}
//修改购物车数量信息 set num,updated where item_id,user_id
//entity: 修改数据的实体
@Override
public void updateCartNum(Cart cart) {
Cart entity = new Cart();
entity.setNum(cart.getNum())
.setUpdated(new Date());
UpdateWrapper<Cart> updateWrapper = new UpdateWrapper<Cart>();
updateWrapper.eq("item_id", cart.getItemId());
updateWrapper.eq("user_id", cart.getUserId());
cartMapper.update(entity, updateWrapper);
}
说明:当用户点击删除按钮时,应该执行购物车的删除业务.
1.应该根据user_id和item_id 删除购物车记录.
2.删除数据成功之后,需要重定向到购物车列表页面.
/**
* 思考问题: 用户添加购物车时,商品库存量数量信息是否变化????
* 思路1:用户操作购物车时不考虑库存量的问题. 如果用户下订单时更新库存量
* 思路2: 用户只要进行加够操作,会与当前库存量进行对比.如果购买的数量超过库存量,则提示用户库存不足.
* @param cart
* @return
*/
@RequestMapping("/delete/{itemId}")
public String deleteCart(Cart cart) {
Long userId = 7L;
cart.setUserId(userId);
cartService.deleteCart(cart);
//重定向到购物车首页
return "redirect:/cart/show.html";
}
//1.itemId 2.userId
@Override
public void deleteCart(Cart cart) {
//根据对象中不为null的属性充当where条件
cartMapper.delete(new QueryWrapper<Cart>(cart));
}
说明:当用户点击加入购物车操作时,需要将商品信息一起进行提交,之后在jt-cart数据库中完成新增操作.
注意事项: 1.如果数据第一次加够,则新增购物车记录
2.如果数据不是第一次加够,则更新数据库数量信息.
3).页面数据提交分析
<form id="cartForm" method="post">
<input class="text" id="buy-num" name="num" value="1" onkeyup="setAmount.modify('#buy-num');"/>
<input type="hidden" class="text" name="itemTitle" value="${item.title }"/>
<input type="hidden" class="text" name="itemImage" value="${item.images[0]}"/>
<input type="hidden" class="text" name="itemPrice" value="${item.price}"/>
</form>
4.为了满足页面取值要求添加getImages方法,同时配置了注解,让以后的程序调用不出问题
package com.jt.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import lombok.experimental.Accessors;
@JsonIgnoreProperties(ignoreUnknown=true) //表示JSON转化时忽略未知属性
@TableName("tb_item")
@Data
@Accessors(chain=true)
public class Item extends BasePojo{
@TableId(type=IdType.AUTO) //主键自增
private Long id; //商品id
private String title; //商品标题
private String sellPoint; //商品卖点信息
private Long price; //商品价格 Long > Double 计算时有误差
//0.111111111...+0.899999999...=0.999999
//数据库中存储的数据都是扩大100被以后的效果. 但是页面中展现时缩小100倍.
private Integer num; //商品数量
private String barcode; //条形码
private String image; //商品图片信息 1.jpg,2.jpg,3.jpg
private Long cid; //表示商品的分类id
private Integer status; //1正常,2下架
//为了满足页面调用需求,添加get方法
public String[] getImages(){ //缺少set方法
return image.split(",");
}
}
/**
* 完成购物车新增操作
* 1.url地址:http://www.jt.com/cart/add/1474392220.html
* 2.参数:form表单提交
* 3.返回值结果: 重定向到购物车展现页面
*/
@RequestMapping("/add/{itemId}")
public String saveCart(Cart cart) {
Long userId = 7L;
cart.setUserId(userId);
cartService.saveCart(cart);
return "redirect:/cart/show.html";
}
/*
* 第一次加够 则新增记录
* 否则更新数量信息
* */
@Override
@Transactional
public void saveCart(Cart cart) {
//1.根据user_id 和item_id查询购物车信息
QueryWrapper<Cart> queryWrapper = new QueryWrapper<Cart>();
queryWrapper.eq("user_id", cart.getUserId())
.eq("item_id", cart.getItemId());
Cart cartDB = cartMapper.selectOne(queryWrapper);
//2.判断数据库中是否有记录.
if(cartDB == null) {
//第一次新增购物车
cart.setCreated(new Date())
.setUpdated(cart.getCreated());
cartMapper.insert(cart);
}else {
//不是第一次加够 ,数量的更新操作
int num = cartDB.getNum() + cart.getNum();
//根据主键,更新购物车记录
//cartDB.setNum(num).setUpdated(new Date());
//UPDATE tb_cart SET item_id=?, item_title=?, created=?, num=?, item_price=?, user_id=?, updated=?, item_image=? WHERE id=?
//cartMapper.updateById(cartDB);
Cart entity = new Cart(); //该对象封装了要修改的记录
entity.setNum(num).setUpdated(new Date());
UpdateWrapper<Cart> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("item_id", cart.getItemId());
updateWrapper.eq("user_id", cart.getUserId());
cartMapper.update(entity, updateWrapper);
}
}
说明: 当用户在没有登陆的条件下,不允许访问购物车/订单/支付等敏感系统.如果用户没有登陆访问敏感系统,则应该跳转到用户的登陆页面.
分析问题:
1.如何判断用户是否登陆???
2.如何判断用户访问的是敏感系统???
3.每个用户的每次请求都应该判断.
问题解决:
1.通过COOKIE信息判断用户是否登陆.
2.应该准备一个访问的策略表 在表中标识哪些系统需要权限.
3.使用拦截器实现该功能
拦截器(Interceptor),主要完成请求参数的解析、将页面表单参数赋给值栈中相应属性、执行功能检验、程序异常调试等工作.
Struts 2中将各个功能对应的拦截器分开定义, 每个拦截器完成单个功能, 如果需要对Action运用某个功能就引用对应的拦截器。在实际开发中, 经常需要在Action执行前同时执行多个拦截动作, 如:用户登录检查、登录日志记录以及权限检查等, 这时, 可以把多个拦截器组成一个拦截器栈。所谓的拦截器栈是指对应各个功能的拦截器按照一定的顺序排列形成的链, 在使用时, 可以将栈内的多个拦截器当成一个整体来引用。当拦截器栈被附加到一个Action上时, 在执行Action之前必须先执行拦截器栈中的每一个拦截器。通常情况下, 拦截器都是以代理方式调用的。 [3]
特点:拦截/放行
package com.jt.inter;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
//处理器的拦截器
import com.jt.pojo.User;
import com.jt.util.ObjectMapperUtil;
import redis.clients.jedis.JedisCluster;
@Component
public class UserInterceptor implements HandlerInterceptor{
@Autowired
private JedisCluster jedisCluster;
private static final String TICKET = "JT_TICKET";
private static final String JT_USER = "JT_USER";
//实现某一个接口,就应该实现该接口的方法.
/**
* 返回值说明
* boolean true 表示放行
* false 表示拦截
*
* 拦截器策略说明:
* 要求用户访问服务器时首先应该通过拦截器判断用户是否登陆.如果用户登陆则放行.
* 如果用户没有登陆则拦截,重定向到用户的登陆页面.
*
* 业务分析:
* 1.如何判断用户是否登陆
* 1.cookie是否有数据 2.根据JT_TICKET中的值 校验redis.
* 2.控制哪些请求需要使用拦截器
* 3.如果没有登陆则重定向到登陆页面
* @param request
* @return
*/
@Override
public boolean preHandle(HttpServletRequest request,HttpServletResponse response, Object handler) throws Exception {
//1.判断用户是否有指定的cookie
Cookie[] cookies = request.getCookies();
//2.判断cookie是否有记录
String ticket = null;
if(cookies !=null && cookies.length >0) {
for (Cookie cookie : cookies) {
if(TICKET.equals(cookie.getName())) {
ticket = cookie.getValue();
break;
}
}
}
//3.判断ticket数据是否有效
if(!StringUtils.isEmpty(ticket)) {
//4.根据ticket信息判断redis集群中时候有该记录.
if(jedisCluster.exists(ticket)) {
//5.根据ticket信息获取redis中的数据
String userJSON = jedisCluster.get(ticket);
//6.利用request对象传递用户信息
User user = ObjectMapperUtil.toObj(userJSON, User.class);
request.setAttribute(JT_USER, user);
return true; //表示放行
}
}
response.sendRedirect("/user/login.html");
return false; //表示拦截
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
// TODO Auto-generated method stub
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
// TODO Auto-generated method stub
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
@Configuration //web.xml配置文件
public class MvcConfigurer implements WebMvcConfigurer{
//开启匹配后缀型配置
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseSuffixPatternMatch(true);
}
@Autowired
private UserInterceptor userInterceptor;
//配置拦截器 registry拦截器的注册中心
// /cart/** 拦截购物下的所有的请求
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(userInterceptor)
.addPathPatterns("/cart/**","/order/**");
}
}
@Controller
@RequestMapping("/cart")
public class CartController {
@Reference(check=false)
private DubboCartService cartService;
//应该准备一个常量池
private static final String JT_USER = "JT_USER";
/**
* 业务功能:展现购物车列表记录
* url:http://www.jt.com/cart/show.html
* 返回值: cart.jsp页面
* 页面取值问题: ${cartList}
*/
@RequestMapping("/show")
public String findCartList(Model model,HttpServletRequest request) {
User user = (User) request.getAttribute(JT_USER);
//.查询购物车信息
Long userId = user.getId();
List<Cart> cartList = cartService.findCartList(userId);
model.addAttribute("cartList", cartList);
return "cart";
}
}
名称: 本地线程变量
作用: 在同一个线程之内,可以实现数据的共享.
说明:threadLocal的研究的对象是线程.并且可以实现每个线程的数据不共享.
说明:有时可能会遇到参数传递不变,则可以利用thread实现数据的传递.
package com.jt.inter;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
//处理器的拦截器
import com.jt.pojo.User;
import com.jt.thread.UserThreadLocal;
import com.jt.util.ObjectMapperUtil;
import redis.clients.jedis.JedisCluster;
@Component
public class UserInterceptor implements HandlerInterceptor{
@Autowired
private JedisCluster jedisCluster;
private static final String TICKET = "JT_TICKET";
private static final String JT_USER = "JT_USER";
//实现某一个接口,就应该实现该接口的方法.
/**
* 返回值说明
* boolean true 表示放行
* false 表示拦截
*
* 拦截器策略说明:
* 要求用户访问服务器时首先应该通过拦截器判断用户是否登陆.如果用户登陆则放行.
* 如果用户没有登陆则拦截,重定向到用户的登陆页面.
*
* 业务分析:
* 1.如何判断用户是否登陆
* 1.cookie是否有数据 2.根据JT_TICKET中的值 校验redis.
* 2.控制哪些请求需要使用拦截器
* 3.如果没有登陆则重定向到登陆页面
* @param request
* @return
*/
@Override
public boolean preHandle(HttpServletRequest request,HttpServletResponse response, Object handler) throws Exception {
//1.判断用户是否有指定的cookie
Cookie[] cookies = request.getCookies();
//2.判断cookie是否有记录
String ticket = null;
if(cookies !=null && cookies.length >0) {
for (Cookie cookie : cookies) {
if(TICKET.equals(cookie.getName())) {
ticket = cookie.getValue();
break;
}
}
}
//3.判断ticket数据是否有效
if(!StringUtils.isEmpty(ticket)) {
//4.根据ticket信息判断redis集群中时候有该记录.
if(jedisCluster.exists(ticket)) {
//5.根据ticket信息获取redis中的数据
String userJSON = jedisCluster.get(ticket);
//6.利用request对象传递用户信息
User user = ObjectMapperUtil.toObj(userJSON, User.class);
//方式1:利用request对象的传递数据
request.setAttribute(JT_USER, user);
//方式2:利用ThreadLocal传递数据
UserThreadLocal.set(user);
return true; //表示放行
}
}
response.sendRedirect("/user/login.html");
return false; //表示拦截
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
// TODO Auto-generated method stub
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
//防止内存泄漏,最好添加如下代码
UserThreadLocal.remove();
request.removeAttribute(JT_USER);
}
}