美文网首页前端开发Flutter
【JAVA】电商业务逻辑

【JAVA】电商业务逻辑

作者: Y了个J | 来源:发表于2018-04-18 23:23 被阅读253次
    屏幕快照 2018-04-20 下午9.26.09.png 屏幕快照 2018-04-20 下午9.26.41.png 屏幕快照 2018-04-20 下午9.27.14.png 分布式就各模块拆分成独立的工程,集群是同一个工程部署到多台服务器,概念不同 表现层是不同的工程,服务层也做成独立的工程,服务层没有界面只有业务逻辑代码 屏幕快照 2018-04-20 下午9.54.03.png war包是web工程,pom做父节点,jar别的工程也可以依赖引用

    用户模块逻辑

    用户自动登录逻辑,用户登录后产生一个loginToken,我们把它存在前台的Cookie里,并把user的信息以json形式存到redis服务器里去,loginToken作为key,下次再访问后台的时候,我们就去Cookie里读loginToken,然后通过loginToken去redis服务器读出user信息了。user信息存redis的时候我们会设一个超时时间,我在拦截器里进行过滤,判断到user不为空,则调用expire命令去刷新延长redis里loginToken对应的信息的有效期。
    代码如下:

    public class SessionExpireFilter implements Filter {
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
    
        }
    
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
                throws IOException, ServletException {
    
            HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
            String loginToken = CookieUtil.readLoginToken(httpServletRequest);
            if (StringUtils.isNotEmpty(loginToken)) {
                //判断logintoken是否为空或者"";
                //如果不为空的话,符合条件,继续拿user信息
                String userJsonStr = RedisShardedPoolUtil.get(loginToken);
                User user = JsonUtil.stringToObj(userJsonStr, User.class);
                if (user != null) {
                    //如果user不为空,则调用expire命令
                    RedisShardedPoolUtil.expire(loginToken, Const.RedisCacheExtime.REDIS_SESSION_EXTIME);
                }
            }
            filterChain.doFilter(servletRequest, servletResponse);
        }
    
        @Override
        public void destroy() {
    
        }
    }
    
    

    用org.springframework.web.servlet.HandlerInterceptor去对一些接口进行拦截,判断有没有用户信息,没有的话就不会进入controller里的方法

    @Slf4j
    public class AuthorityInterceptor implements HandlerInterceptor {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            log.info("preHandle");
    
            //请求中Controller中的方法名
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            //解析HandlerMethod
            String methodName = handlerMethod.getMethod().getName();
            String className = handlerMethod.getBean().getClass().getSimpleName();//类名
    
            //解析参数,具体的参数key以及value是什么,我们打印日志
            StringBuffer requestParamBuffer = new StringBuffer();
            Map paramMap = request.getParameterMap();
            Iterator it = paramMap.entrySet().iterator();
            while (it.hasNext()) {
                Map.Entry entry = (Map.Entry) it.next();
                String mapKey = (String) entry.getKey();
                String mapValue = "";
                //request这个参数的map,里面的value返回的是一个String[]
                Object obj = entry.getValue();
                if (obj instanceof String[]) {
                    String[] strs = (String[]) obj;
                    mapValue = Arrays.toString(strs);
                }
                requestParamBuffer.append(mapKey).append("=").append(mapValue);
            }
    
            if (StringUtils.equals(className, "UserManageController") && StringUtils.equals(methodName, "login")) {
                log.info("权限拦截器拦截到请求,className:{},methodName:{}", className, methodName);
                //如果是拦截到登录请求,不打印参数,因为参数里面有密码,全部会打印到日志中,防止日志泄露
                return true;
            }
    
            log.info("权限拦截器拦截到请求,className:{},methodName:{},param:{}", className, methodName, requestParamBuffer.toString());
    
            User user = null;
    
            String loginToken = CookieUtil.readLoginToken(request);
            if (StringUtils.isNotEmpty(loginToken)) {
                String userJsonStr = RedisShardedPoolUtil.get(loginToken);
                user = JsonUtil.string2Obj(userJsonStr, User.class);
            }
    
            if (user == null || (user.getRole().intValue() != Const.Role.ROLE_ADMIN)) {
                //返回false.即不会调用controller里的方法
                response.reset();//note 这里要添加reset,否则报异常 getWriter() has already been called for this response.
                response.setCharacterEncoding("UTF-8");//note 这里要设置编码,否则会乱码
                response.setContentType("application/json;charset=UTF-8");//note 这里要设置返回值的类型,因为全部是json接口。
    
                PrintWriter out = response.getWriter();
                //上传由于富文本的控件要求,要特殊处理返回值,这里面区分是否登录以及是否有权限
                if (user == null) {
                    if (StringUtils.equals(className, "ProductManageController") &&
                            StringUtils.equals(methodName, "richtextImgUpload")) {
                        Map resultMap = Maps.newHashMap();
                        resultMap.put("success", false);
                        resultMap.put("msg", "请登录管理员");
                        out.print(JsonUtil.obj2String(resultMap));
                    } else {
                        out.print(JsonUtil.obj2String(ServerResponse.createByErrorMessage("拦截器拦截,用户未登录")));
                    }
                } else {
                    if (StringUtils.equals(className, "ProductManageController") &&
                            StringUtils.equals(methodName, "richtextImgUpload")) {
                        Map resultMap = Maps.newHashMap();
                        resultMap.put("success", false);
                        resultMap.put("msg", "无权限操作");
                        out.print(JsonUtil.obj2String(resultMap));
                    } else {
                        out.print(JsonUtil.obj2String(ServerResponse.createByErrorMessage("拦截器拦截,用户无权限操作")));
                    }
                }
                out.flush();
                out.close();//geelynote 这里要关闭
                return false;//不会再进入controller
            }
            return true;
        }
    
        @Override
        public void postHandle(HttpServletRequest request,
                               HttpServletResponse response,
                               Object handler,
                               ModelAndView modelAndView) throws Exception {
            log.info("postHandle");
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request,
                                    HttpServletResponse response,
                                    Object handler,
                                    Exception ex) throws Exception {
            log.info("afterCompletion");
        }
    }
    
    

    定时关闭订单

    Spring Schedule定时关单
    Cron表达式的格式:秒 分 时 日 月 周 年(可选)


    屏幕快照 2018-04-19 下午11.17.45.png 屏幕快照 2018-04-19 下午11.18.34.png 屏幕快照 2018-04-19 下午11.21.35.png
    屏幕快照 2018-04-19 下午11.23.17.png

    Spring Schedule Cron生成器,百度搜索下就有了
    Spring Schedule Cron配置
    在applicationContext.xml里加上 <task:annotation-driven/>
    写一个CloseOrderTask类加上@Component注解,在需要定时的方法上加上@Scheduled(...)即可

    MySQL行锁、表锁
    Row-Level Lock(明确的主键)
    Table-Level Lock (无明确的主键)

    select ... for update 是悲观锁

    屏幕快照 2018-04-19 下午11.35.21.png 屏幕快照 2018-04-19 下午11.36.10.png 无主键,表锁 主键不明确,表锁
    @Scheduled(cron = "0 */1 * * * ?")
        public void closeOrderTaskV4() {
            RLock lock = redissonManager.getRedisson().getLock(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);
            boolean getLock = false;
            try {
                if (getLock = lock.tryLock(0, 50, TimeUnit.SECONDS)) {
                    log.info("Redisson获取到分布式锁:{},ThreadName:{}", Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK,
                            Thread.currentThread().getName());
                    int hour = Integer.parseInt(PropertiesUtil.getProperty("close.order.task.time.hour", "2"));
                    iOrderService.closeOrder(hour);
                } else {
                    log.info("Redisson没有获取到分布式锁:{},ThreadName:{}", Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK,
                            Thread.currentThread().getName());
                }
            } catch (InterruptedException e) {
                log.error("Redisson分布式锁获取异常", e);
            } finally {
                if (!getLock) {
                    return;
                }
                lock.unlock();
                log.info("Redisson分布式锁释放锁");
            }
        }
    

    购物车

    今天来开始写一下关于购物车的东西, 这里首先抛出四个问题:
    1)用户没登陆用户名和密码,添加商品, 关闭浏览器再打开后 不登录用户名和密码 问:购物车商品还在吗?
    2)用户登陆了用户名密码,添加商品,关闭浏览器再打开后 不登录用户名和密码 问:购物车商品还在吗?
    3)用户登陆了用户名密码,添加商品, 关闭浏览器,然后再打开,登陆用户名和密码 问:购物车商品还在吗?
    4)用户登陆了用户名密码,添加商品, 关闭浏览器 外地老家打开浏览器 登陆用户名和密码 问:购物车商品还在吗?

    答案
    1)用户没有登录, 添加商品, 此时的商品是被添加到了浏览器的Cookie中, 所以当再次访问时(不登录),商品仍然在Cookie中, 所以购物车中的商品还是存在的.
    2)用户登录了,添加商品, 此时会将Cookie中和用户选择的商品都添加到购物车中, 然后删除Cookie中的商品. 所以当用户再次访问(不登录),此时Cookie中的购物车商品已经被删除了, 所以此时购物车中的商品不在了.
    3)用户登录, 添加商品,此时商品被添加到数据库做了持久化存储, 再次打开登录用户名和密码, 该用户选择的商品肯定还是存在的, 所以购物车中的商品还是存在的.
    4)理由3)

    这里再说下 没登录 保存商品到Cookie的优点以及保存到Session和数据库的对比:
    1:Cookie: 优点: 保存用户浏览器(不用浪费我们公司的服务器) 缺点:Cookie禁用,不提供保存
    2:Session:(Redis : 浪费大量服务器内存:实现、禁用Cookie) 速度很快
    3:数据库(Mysql、Redis、SOlr) 能持久化的就数据库 速度太慢

    那么我今天要讲的就是:
    用户没登陆:购物车添加到Cookie中
    用户登陆: 保存购物车到Redis中 (不用数据库)

    添加购物车需要买家id、商品数量、颜色尺码不同对应不同的sku
    先用买家id数据库查询BuyerCart,如果没有这个BuyerCart就创建个新的,有的话就去BuyerCart里判断是否包含同款,有的话就追加数量,没有再创建新的BuyerItem

     public class BuyerCart implements Serializable{
     2 
     3     /**
     4      * 购物车
     5      */
     6     private static final long serialVersionUID = 1L;
     7     
     8     //商品结果集
     9     private List<BuyerItem> items = new ArrayList<BuyerItem>();
    10     
    11     //添加购物项到购物车
    12     public void addItem(BuyerItem item){
    13         //判断是否包含同款
    14         if (items.contains(item)) {
    15             //追加数量
    16             for (BuyerItem buyerItem : items) {
    17                 if (buyerItem.equals(item)) {
    18                     buyerItem.setAmount(item.getAmount() + buyerItem.getAmount());
    19                 }
    20             }
    21         }else {
    22             items.add(item);
    23         }
    24         
    25     }
    26 
    27     public List<BuyerItem> getItems() {
    28         return items;
    29     }
    30 
    31     public void setItems(List<BuyerItem> items) {
    32         this.items = items;
    33     }
    34     
    35     
    36     //小计
    37     //商品数量
    38     @JsonIgnore
    39     public Integer getProductAmount(){
    40         Integer result = 0;
    41         //计算
    42         for (BuyerItem buyerItem : items) {
    43             result += buyerItem.getAmount();
    44         }
    45         return result;
    46     }
    47     
    48     //商品金额
    49     @JsonIgnore
    50     public Float getProductPrice(){
    51         Float result = 0f;
    52         //计算
    53         for (BuyerItem buyerItem : items) {
    54             result += buyerItem.getAmount()*buyerItem.getSku().getPrice();
    55         }
    56         return result;
    57     }
    58     
    59     //运费
    60     @JsonIgnore
    61     public Float getFee(){
    62         Float result = 0f;
    63         //计算
    64         if (getProductPrice() < 79) {
    65             result = 5f;
    66         }
    67         
    68         return result;
    69     }
    70     
    71     //总价
    72     @JsonIgnore
    73     public Float getTotalPrice(){
    74         return getProductPrice() + getFee();
    75     }
    76     
    77 }
    
     public class BuyerItem implements Serializable{
     2 
     3     private static final long serialVersionUID = 1L;
     4 
     5     //SKu对象
     6     private Sku sku;
     7     
     8     //是否有货
     9     private Boolean isHave = true;
    10     
    11     //购买的数量
    12     private Integer amount = 1;
    13 
    14     public Sku getSku() {
    15         return sku;
    16     }
    17 
    18     public void setSku(Sku sku) {
    19         this.sku = sku;
    20     }
    21 
    22     public Boolean getIsHave() {
    23         return isHave;
    24     }
    25 
    26     public void setIsHave(Boolean isHave) {
    27         this.isHave = isHave;
    28     }
    29 
    30     public Integer getAmount() {
    31         return amount;
    32     }
    33 
    34     public void setAmount(Integer amount) {
    35         this.amount = amount;
    36     }
    37 
    38     @Override
    39     public int hashCode() {
    40         final int prime = 31;
    41         int result = 1;
    42         result = prime * result + ((sku == null) ? 0 : sku.hashCode());
    43         return result;
    44     }
    45 
    46     @Override
    47     public boolean equals(Object obj) {
    48         if (this == obj) //比较地址
    49             return true;
    50         if (obj == null)
    51             return false;
    52         if (getClass() != obj.getClass())
    53             return false;
    54         BuyerItem other = (BuyerItem) obj;
    55         if (sku == null) {
    56             if (other.sku != null)
    57                 return false;
    58         } else if (!sku.getId().equals(other.sku.getId()))
    59             return false;
    60         return true;
    61     }
    62 }
    
    

    购物车中商品必须有库存 且购买大于库存数量时视为无货. 提示: 购物车原页面不动. 有货改为无货, 加红提醒.

    利用springmvc的过滤功能, 用户点击结算的时候必须要先登录, 如果没有登录的话就提示用户需要登录.

    Redis分布式锁

    定时关单

    setnx 具有原子性,set if not exist
    getset 原子性,设置新的值并且返回旧值
    expire
    del
    
    
    set if not exist,如果redis里有CLOSE_ORDER_TASK_LOCK对应的值了 setnx就不成功
    Long setnxResult = RedisShardedPoolUtil.setnx(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK,
     String.valueOf(System.currentTimeMillis() + lockTimeout));
        
            if (setnxResult != null && setnxResult.intValue() == 1) {
                closeOrder(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);//取得锁成功
            } else {
                //未获取到锁,说明redis里CLOSE_ORDER_TASK_LOCK对应的值肯定存在,继续判断,判断时间戳,看是否可以重置并获取到锁
                //lockValueStr  是旧值
                String lockValueStr = RedisShardedPoolUtil.get(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);
               
                if (lockValueStr != null && System.currentTimeMillis() > Long.parseLong(lockValueStr)) {
                    //锁过期,可以重新设置,设置新的值并且返回旧值
                   //下面判断这里返回的旧值和上面get的旧值相等不,防止其他线程进行了getset操作
                    String getSetResult = RedisShardedPoolUtil.getSet(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK, String.valueOf(System.currentTimeMillis() + lockTimeout));
                    //再次用当前时间戳getset,设置新的值并且返回旧值
                    //返回给定的key的旧值,->旧值判断,是否可以获取锁
                    //当key没有旧值时,即key不存在时,返回nil,说明锁没有了 ->获取锁
                    //这里我们set了一个新的value值,获取旧的值。
                    if (getSetResult == null || (getSetResult != null && StringUtils.equals(lockValueStr, getSetResult))) {
                        //真正获取到锁
                        closeOrder(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);
                    } else {
                        log.info("没有获取到分布式锁:{}", Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);
                    }
                } else {
                    log.info("没有获取到分布式锁:{}", Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);
                }
            }
    
    

    相关文章

      网友评论

        本文标题:【JAVA】电商业务逻辑

        本文链接:https://www.haomeiwen.com/subject/tzfikftx.html