【JAVA】电商业务逻辑

屏幕快照 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
无主键,表锁
主键不明确,表锁

购物车

今天来开始写一下关于购物车的东西, 这里首先抛出四个问题:
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的过滤功能, 用户点击结算的时候必须要先登录, 如果没有登录的话就提示用户需要登录.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,200评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,526评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,321评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,601评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,446评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,345评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,753评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,405评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,712评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,743评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,529评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,369评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,770评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,026评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,301评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,732评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,927评论 2 336

推荐阅读更多精彩内容