商城

用户名+邮箱都是唯一的,相同则默认为同一个客户

横向越权,纵向越权

MD5加解密

guava的LRU算法来刷新缓存

高可用的服务对象封装:ResverResponse

常量封装对象:利用枚举维护参数信息ResponseCode

常量块:接口字段,枚举字段

Vo视图的对象

时间与字符串互转模块

PageHelper.setStage和PageInfo进行分页

Jackson格式化对象,利用了 ObjectMapper来转换对象

FTPClient工具类

Schedule当时调度

RestFul服务

-----

注解信息

@Controller
@RequestMapping("/product/")
@Slf4j
@Autowired
@RequestMapping(value ="getDetail.do",method = RequestMethod.GET)
@ResponseBody
@RequestParam(value="pageNum",defaultValue ="1")
@RequestParam(value ="keyword",required =false)
RestFul应用
@RequestMapping(value ="/{productId}",method = RequestMethod.GET)
@ResponseBody
@PathVariable(value ="keyword")

 

初版本:

======================================用户模块=============================================

用户模块涉及到:注册+登录+退出+忘记密码+查询问题+查询答案

1--------注册模块:

流程:输入用户信息+进行密码的MD5散列+盐值加密+身份权限设置+校验唯一性(用户名+邮箱)+维护进数据库

先封装了一个公共方法,用于校验用户名+邮箱是否存在

将参数封装为一个用户角色(包括等级+加密的密码)

插入数据库,调用mapper(Dao层)

成功的话返回成功(无参数)
2---------登录模块:

先去查下用户名,失败则返回参数

成功则写入request.getSession().setAtrXXX中或者直接再方法前传递HttpSession参数回来方便直接调用

失败则返回

3--------退出模块:

在方法参数里面设置HttpSesson参数,request等参数,然后移除参数

4--------忘记密码:

校验用户名存不存在,存在着把问题查出来

5-------校验答案:

将用户名,答案,问题回传查询,如果查到结果

利用UUID生成一个唯一标志:forgetToken,将这个标志与用户名组合在一起存入guava缓存中 

返回一个forgetoken回去给他们,有效为12个小时

guava缓存中使用了LRU算法来刷新缓存,如果数据最近被访问过则越容易被访问

6-------重置密码

传入用户名,新密码,forgetToken作为参数

判断用户名存在,新密码存在,forgetToken不为空,然后组装成key标志去guavaCache中获取forgetToken

如果传进来的forgetToken跟我们获取到的token是一致的,允许修改密码

7-------修改密码:

在用户已经登录的情况下可以进行修改密码,在初始版本我们仍然是从session红获取用户信息,如果获取到用户则说明用户已经登录,输入新旧密码则可以修改

8-------用户信息:
从session中尝试获取对象,如果获取成功,则说明对象已经登录,允许获取,否则不允许获取

=====================================商品模块==============================================

1-----获取商品详情:
根据商品的productId获取到信息去数据库查询产品,查询到后判断是否已经下架,没有则将商品信息放入ProductVo中

ProductVo中将查询查询出来的ProductVo填充到ProductVo中,其中产品类型根据产品ID去产品类型表总查找,查找出来后将类型的ID给维护进来!

回传到前台的时间也会转成字符串

2-----获取产品列表:
传入关键字或者产品类型,以及分页信息

判断产品类型不为空,则进行查询返回产品类型ID

判断关键字不为空,则组成模糊字符串

判断排序字段存在,并且确定类型

查询产品list,并存入productListVo中,进行PageHelper分页,返回给前端

3----产品类型列表:

传入一个产品Id,那么跟我这个Id去查询产品类型,如果不为空则将给产品类型给存储进去Set列表中。

然后以此Id作为子节点去查询记录,形成一个记录类型列表,然后遍历类型类别,将Set列表和每个记录类型的id给传进去

类型表每条记录有自己的id也有自己的一个节点id,其中id是自动递增,但是子节点id是可认为维护的。每次都是以查出来的id为条件查找,如果查到不为空,则添加进Set列表中,进列表,查到为空则跳过每次提供一个categoryId节点,先出该节点的记录,有多少个,存储多少个进列表 然后根据该对象的categoryId作为递归节点去查询,查出多少个则获取多少个,直到以递归节点去查询再也找不到下级节点 原则:类型节点的子节点必须为0,代表着最大级别

//递归节点:利用HashSet是因为Set无序不重复
private Set<Category> findChildCategory(Set<Category> categorySet,Integer categoryId){
    Category category=categoryMapper.selectByPrimaryKey(categoryId);/*类型Id*/
    if (category!=null){
        categorySet.add(category);
    }
    List<Category>  categorylist=categoryMapper.selectCategoryChildrenByParentId(categoryId);
    for(Category categoryItem :categorylist){
        findChildCategory(categorySet,categoryItem.getId());
    }
    return categorySet;
}

======================================地址模块=============================================

1-----新增地址:查询session是否有该用户信息,有则维护

2-----修改地址

3-----删除地址

4-----查询地址(全部,地址)

====================================购物车模块==============================================

1-----查询购物车列表:从session中获取对象,并判断兑现是否存在,存在则根据用户ID去查询购物车列表(每种产品一条)

同属于一个用户ID的就是同一只购物车

2-----添加购物车商品:从session中判断用户信息

传入产品数量,产品ID,用户ID

先根据用户ID+产品ID查购物车中是否存在该商品,不存在新增一条记录,存在则在原来的基础上增加数量并更新记录

因为购物车商品需要回显在页面,因此需要传递给前端,我们在后面维护CartProductVoList 将查询到购物车转给你的数据给填充到集合中。在这个过程中需要查询用户的购物车又几种产品,每种产品的库存是多少,满足不满足当前维护的数量,如果满足则返回当前购物车里面数量给前端,不满足则将当前库存给赋值给该产品并添加到Volist中,计算购物车中被勾选数目的总价,最后

3-----更新购物车:同样调用

getCartVoLimit()查询到用户的信息后统一处理

该方法 根据插入的用户ID查找属于该用户的购物车,然后统计该用户的购物车每一句商品的库存是否满足,不满足则将最大库存赋值给购物车,然后统计每件商品的总价,以及购物车的总价,然后将每件商品给填充到购物车中购物车商品列表中返回给前端!

4----删除购物车商品

要求前端回传产品ID的字符串,因此在堆产品ID进行分割后,然后放入一个list中,,传给dao层开始删除商品删除成功后将当前购物车的信息重新统计然后返回给前端

5----全选或者反选

根据用户D,去更新购物车里面的商品

6----获取购物车产品的数量

直接查询

======================================订单模块=============================================

1-----查询订单商品:

用户校验+查询所有被选中的购物车列表(订单)+将查询出来的订单列表传入getCatrOrderItem中对订单的每个商品进行库存校验+上下架状态校验+总价计算+返回

判断返回的结果是成功的或者失败,失败则终止,成功则统计 应支付总价并且把订单商品列表给填充到视图模型的订单商品orderproductVo中的orderItem集合

2----订单详情:根据订单号和用户ID去查询详细信息,并填充订单orderVo

3----个人中心查看订单:用户校验+分页查询

4-----创建订单:用户校验+调用创建订单的service方法+查询出购物车所有的记录+getOrderTotalPrice()总价计算+插入一条订单进入数据库+判断订单的子项目orderItem是不是为空+如果不为空则批量插入orderItem表中,以订单号为关联标志

5-----取消订单:用户校验+查询订单+判断订单状态+更新订单状态

6-----支付订单:校验身份+获取本地upload文件夹路径+根据用户和订单号查询订单+封装支付宝(订单号+是否打折这些信息)+Bigdecmal计算每一类商品的价格+创建扫码支付对象+获取支付宝配置+向支付宝预下单(成功后支付宝会返回一个二维码串)+我们尝试创建一个文件夹路径,然后拼接在本地目录,之后调用ftpUtil将之上传

我们找到二维码使用支付宝沙箱扫码支付后,即可当代支付宝回调

7----等待支付宝发起回调:从request中获取支付宝的回调参数+获取其RSA秘钥进行进行判断

成功:进入下一层service查询支付宝返回的订单号是否存在,如果存在则修改其支付状态

失败:如果是已经支付的,则向cntroller返回支付宝重复回调+Controller统一转化为支付宝的回调成功/失败标志给支付宝

8----支付宝定时查询:

用户校验+通过订单号本地查询订单的状态

==========================================================================================

第一次升级:将缓存修改为Redis,利用cookie来进行存储需要修改流程如下,为了实现Httpsession共享,将项目由单机改成分布式!

1------创建RedisPool工具类,对外置redis进行连接

2------创建CookieUtils从httpResponse响应容器中设置参数,将sessionId存入作为cookie的token随处理结果返回前端。

3------利用RedisPool,以返回给前端做token的sessionId做为key向redis写入查询出来的对象(对象已经通过json转化格式)

4------redisPool存入的是expire具有时间限制的方式

5------修改其他所有的需要“用户校验”的模块:

--------利用CookieUtils从前端返回的httprequest请求容器中获取cookie信息,也即是登录时候存放给前端作为cookie信息的sessionId

--------利用这个查询回来的sessionId作为key值,使用RedisPool查询对应的用户信息

==========================================================================================

第二次升级:在需要用户校验的时候,每次都需要调用工具类CookieUtils去读取cookie信息以及RedisPool去查询登录状态,对当前的业务仍然存在着侵入,能否不侵入当前业务!在这种前提下开始考虑springSessionReposityFilter零侵入式框架!

1-----自动创建httpsession,实现session共享

2-----采用外置redis,更方便!

3-----全面使用xml注入的方式配置

我们知道web.xml作为整个Tomcat项目的电源插座,那么我们的Filter是针对于整个项目的,因此也需要在web.xml中维护数据(添加依赖),因此需要进行如下改动:

<!--springSession零侵入式框架-->
<!--<filter>
  <filter-name>springSessionRepositoryFilter</filter-name>
  <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
  <filter-name>springSessionRepositoryFilter</filter-name>
  <url-pattern>*.do</url-pattern>
</filter-mapping>-->

在applicationContext.xml中追加一个配置文件<import resource="applicationContext-spring-session.xml"/>在文件中采取XML注入的方式:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
     http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
     http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
     http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
     http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--注入对象-->
    <bean id="redisHttpSessionConfiguration" class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
        <property name="maxInactiveIntervalInSeconds" value="1800"/>
    </bean>

    <!--设置jedisPool-->
    <!--class的路径就是在写工具类的时候转化的,调用的那些方法的类的路径-->
    <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <property name="maxTotal" value="20"/>
    </bean>

    <!--cookie注入-->
    <bean id="defaultCookieSerializer" class="org.springframework.session.web.http.DefaultCookieSerializer">
        <property name="cookieName" value="SESSION_NAME"/>
        <property name="domainName" value=".cyqxm.com"/>
        <property name="useHttpOnlyCookie" value="true"/>
        <property name="cookiePath" value="/"/>
        <property name="cookieMaxAge" value="31536000"/>
    </bean>

    <!--生成jedisPool连接的工厂-->
    <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" >
       <property name="hostName" value="127.0.0.1"/>
       <property name="port" value="6379"/>
       <property name="poolConfig" ref="jedisPoolConfig"/><!--模拟工具类里面配置信息-->
    </bean>
</beans>

 配置httpSessionConfiguration,配置jedisPoolConfig,配置defaultCookieSerializer,配置jedisPool的连接工厂!

所有的东西都被框架抽象为一个连接层,我们只需要配置value值!

----效果:当完成之后只需要在注册的时候注入session的属性值,在退出的时候移除session,其他时候在需要用户校验的时候会自动进行用户校验

==========================================================================================

全局异常处理:当项目系统出现异常的时候我们会报错,然后将信息给返回给前端,但是对于 客户来说这种错误并么有意义 ,他只想知道能怎么办?同样地,为了异常的时候可能的包结构或者SQL信息不被泄露,我们需要对异常进行一个控制,因此设置到过滤器,对异常的捕获!

我们知道springmnvc的流程是前端发送http请求过来到达前端控制器dispatcherServlet,接着处理映射器handlerMapper找到Handler,通过Handler找到对应的Contoller名称,然后给到dispatcherServlet进行分配,DispatcherServlet调用HandlerAduapt分配适配器找到Controller进行处理,当Controller处理完成后返回给ModelAndView视图模型,ModelAndView进行分析后给到ViewResolve(视图解析器),解析器处理完成后给到View,DIspatcherServlet进行渲染处理。那么在这个过程中几乎所有的Controller处理结果都会通过ModelAndView进行分析,,那么在我们的异常控制也在这个位置进行处理!

在Common模块中创建一个ExceptionrResolver继承HandlerExceptionResolver

1------@Component:标记为组件:所有非componment的需要springmvc加载的都需要标记为组件

2------重写resolveException:

3-----在web.xml中进行过滤器的配合和映射配置

<!--二期重置session的时间过滤器-->
<filter>
  <filter-name>SessionExpireFilter</filter-name>
  <filter-class>com.mmall.controller.common.SessionExpireFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>SessionExpireFilter</filter-name>
  <url-pattern>*.do</url-pattern>
</filter-mapping>

4-------重写ModelAndView方法

@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
    log.error("{} Exception ",httpServletRequest.getRequestURI(),e);
    //MappingJacksonJsonView是view的实现类,所以可以被使用
    ModelAndView modelAndView=new ModelAndView(new MappingJacksonJsonView());

    //我们需要模拟controller放回jackson给前端的,所以根据项目中jackson中的版本选择使用MappingJacksonJsonView,如果jackson2.x则使用MappingJackson2JsonView
    modelAndView.addObject("status",ResponseCode.ERROR.getCode());
    modelAndView.addObject("msg","网关通讯异常,详情请查看服务端日志的异常信息");
    modelAndView.addObject("data",e.toString());

    return modelAndView;
}

将异常捕获后,返回给前端一个统一的标志,告诉由后端人员处理!

==========================================================================================

拦截器:用于做一些特殊性质的操作,纵向开发;

获取分配器里面给到的controller名称(类名)和方法名称,然后进行匹配,查找对应的方法,判断后进行拦截,通过返回布尔值来选择是否跳过或者继续往下执行;

在Dispatcher-servlet.xml中配置拦截器:(假设我要拦截所有的后台登录都是管理员)

<!--=====拦截器=======-->
<mvc:interceptors><!--复数表示所有的都会被拦截-->
    <mvc:interceptor>
        <mvc:mapping path="/manage/**"/>
        <mvc:exclude-mapping path="/manage/user/managerLogin.do" /><!--排除-->
        <bean class="com.mmall.controller.common.Interceptor.AuthorityInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

然后在com.mmall.controller.common.Interceptor下创建AuthorityInterceptor类继承HandlerIntercptor重写三个XXXHandler方法!

-----preHandler:在请求到达Controller之前的区域拦截(主要处理)

-----postHandle:在请求到达controller后处理完某个方法之后拦截

-----afterCompletion:在整个模块处理完后拦截

拦截器的设置如下:

package com.mmall.controller.common.Interceptor;

import com.google.common.collect.Maps;
import com.mmall.common.Conts;
import com.mmall.common.ServerResponse;
import com.mmall.pojo.User;
import com.mmall.utils.CookieUtil;
import com.mmall.utils.JacksonUtil;
import com.mmall.utils.RedisShardedJedisPoolUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;

@Slf4j
public class AuthorityInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler) throws Exception {
        log.info("preHandle拦截-未登录和无权限的操作");
        log.info("===================获取Controller中的方法名==========================");
        HandlerMethod handlerMethod=(HandlerMethod)handler;
        log.info("获取");
        String methodName=handlerMethod.getMethod().getName();//方法名
        String className=handlerMethod.getBean().getClass().getSimpleName();//类名

        log.info("解析");
        StringBuffer stringBuffer=new StringBuffer();//线程安全(慢)
        Map paramsMap=httpServletRequest.getParameterMap();
        Iterator it=paramsMap.entrySet().iterator();//enrtySet可以就是key-value对
        while(it.hasNext()){
            Map.Entry entry=(Map.Entry)it.next();
            String mapKey= (String) entry.getKey();

            String mapValue= StringUtils.EMPTY;
            //request里面的map,返回的是一个String[]
            Object obj=entry.getValue();
            if (obj instanceof String[]){
                String[] strs= (String[]) obj;
                mapValue= Arrays.toString(strs);//java库中数组的方法
            }
            stringBuffer.append(mapKey).append("=").append(mapValue).append("; ");
        }

        /*用户登录拦截忽略*/
        if (StringUtils.equals(className,"UserManagerController")&&StringUtils.equals(methodName,"loginAdmin")){
            //如果拦截到登录,不执行拦截
            log.info("拦截器拦截到请求 Controller:{},Method:{},程序放行",className,methodName);
            return true;
        }


        log.info("日志信息:"+stringBuffer.toString());
        User user=null;
        //拦截器处理

        log.info("获取Token判断权限");
        String loginToken= CookieUtil.readLoginToken(httpServletRequest);
        if(StringUtils.isNotEmpty(loginToken)){
            String strLoginToken= RedisShardedJedisPoolUtil.getJedis(loginToken);//将用户登录信息存入redis中
            user= JacksonUtil.StrToObject(strLoginToken,User.class);
        }

        if (user==null || (user.getRole().intValue()!= Conts.Role.ROLE_ADMIN)){

            log.info("重置返回容器");
            //用户为空或者用户不为管理员,不允许调用controller方法
            httpServletResponse.reset();//此处需要进行重置,否则会报已经存在的异常
            httpServletResponse.setCharacterEncoding("UTF-8");//脱离了springMVC的监控,需要重新设置编码(正常在dispatcherServlet也有设置)
            httpServletResponse.setContentType("application/json;charset=UTF-8");//设置返回值类型

            //写
            PrintWriter printWriter=httpServletResponse.getWriter();
            if (user==null){

                //拦截富文本上传
                if (StringUtils.equals(className,"ProductManageController")&&StringUtils.equals(methodName,"richtext_img_upload")){
                    Map resultMap= Maps.newHashMap();
                    resultMap.put("success", false);
                    resultMap.put("msg", "请登录管理员");
                   printWriter.print(JacksonUtil.objToString(resultMap));
                }else{
                    printWriter.println(JacksonUtil.objToString(ServerResponse.createByErrorMessage("拦截器拦截:用户未登录")));
                }
            }else{
                if (StringUtils.equals(className,"ProductManageController")&&StringUtils.equals(methodName,"richtext_upload")){
                    Map resultMap= Maps.newHashMap();
                    resultMap.put("success", false);
                    resultMap.put("msg", "无权限操作");
                    printWriter.print(JacksonUtil.objToString(resultMap));
                }else {
                    printWriter.println(JacksonUtil.objToString(ServerResponse.createByErrorMessage("拦截器拦截:用户无权限")));
                }
            }
            printWriter.flush();//写入
            printWriter.close();//关闭

            return false;
        }

        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
        log.info("postHandle");
    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
        log.info("afterCompletion");
    }
}

==========================================================================================

定时调度:设置定时关闭订单

1-------在applicationContext中设置<!--定时调度--> <task:annotation-driven/>的配置

2-------需要在配置文件中追加关于定时调度的描述引用;

xmlns:task="http://www.springframework.org/schema/task"

3-------在src下建立task文件夹,尽力定时调度的类

4-------因为属于spring的配置,因此需要配置@Component组件化

5-------使用@Scheduled(cron="0/59 * * * * ? ")来作为 corn表达式来进行定时调度的时间配置,在方法里面进行定时调度的内容

---------此部分涉及到单实例共享以及分布式锁处理机制,以下分标记表达:

<1>使用单实例redis的时候的处理,如果单机部署多应用的但是使用的是同一个redis,那么需要考虑到两个项目调度的时候的锁问题;

创建redissonManager类(维护组件),通过springSessionResposityFilter框架来配置,获得redission对象

从redisson中获取锁,在定时调度的时候则先去判断是否嫩获取到锁,如果不能则停止本次调度

如果获取到锁(搜索规则是如果获取不到则马上停止,锁的维护时长只有5秒),那么则开始进行定时关单处理!

<2>redis实例处理-分布式redis处理:当redis也存在着多个的时候,那么针对redis的分布式,我们也需要进行对应的分布式锁;

尝试设置分布式锁:setNx---只有已经不存在的key才可以设置成功,维护当前时间+超时的时长作为value,如果设置失败则说明当前锁已经被占用了,与其他人在使用!如果成功则直接发起调度!

那么尝试单纯获取该分布式锁,判断是否存在和超时,如果存在并且已经超时了,那么说明该锁被放弃了,没人在使用,可以获取;那么便重置该锁,并发起调度!

如果该锁仍然有效,并且在时间内则返回获取分布式锁失败,停止当前调度

6-------维护异常项目时候的锁特殊处理

//非正常流程关闭项目,但tomcat进程仍然存活,可触发阻止
@PreDestroy
private void delLock(){
    RedisShardedJedisPoolUtil.delJedis(Conts.REDIS_LOCK.TASK_CLOSE_ORDER_LOCK);
}

==========================================================================================

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值