分布式dobbu
提高性能,透明化的RPC远程服务调用方案,以及SOA服务治理方案.是个远程服务调用的分布式框
架
节点角色说明:
Provider: 暴露服务的服务提供方。
Consumer: 调用远程服务的服务消费方。
Registry: 服务注册与发现的注册中心。
Monitor: 统计服务的调用次调和调用时间的监控中心。
Container: 服务运行容器。
调用关系说明:
0. 服务容器负责启动,加载,运行服务提供者。
1. 服务提供者在启动时,向注册中心注册自己提供的服务。
2. 服务消费者在启动时,向注册中心订阅自己所需的服务。
3. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推
送变更数据给消费者。
4. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,
如果调用失败,再选另一台调用。
5. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计
数据到监控中心。
其中注册中心不用zookeeper用redis缓存同样可以完成
项目中 Zookeeper 服务器挂了,服务调用可以进行吗
可以的,消费者在启动时,消费者会从 zk 拉取注册的生产者的地址接口等数据,缓存在本地。每次调用时,按照本地存储的地址进行调用
品牌
注意分页控件的使用
规格,规格选项
注意维护一对多的关系,在增加语句中设置自增语句
在删除规格时,注意删除规格选项
在修改规格时,删除原有的规格选项,将修改的直接添加进去
模板
两个作用 :关联品牌与规格
定义扩充属性
里面的属性都是JSON字符串,传到前台都要转成Json对象
商品分类
面包屑导航,分级操作,
根据id和父id来进行自查询
登录功能
用的安全框架Spring-Security集成CAS单点登录
导入相关坐标
在web.xml中编写安全框架过滤器
配置说明:
always-use-default-target:指定了是否在身份验证通过后总是跳转到default-target-url属性 指定的URL。
default-target-url:安全认证通过,访问成功后跳转的地址
authentication-failure-url:认证失败后跳转的地址
login-page:系统访问默认的登录的页面
在http节点 添加<logout/>配置,即为设置退出登录
在UserDetailsServiceImpl 类中获取角色,进行权限分级
在Controller中使用SecurityContextHolder....即可获取登录名
密码加密
商家申请入驻的密码要使用BCrypt算法加密
必须在认证 管理器对密码加密,加密后使用明码去匹配数据库密码
springSecurity框架使用流程:
1.springsecurity 导入两个坐标
2.web.xml做配置
3.spring-security.xml
springSecurity的验证原理是把用户信息放到session中;怎么验证?清理浏览器缓存,再访问就需要重新登录了
SpringSecurity作为安全认证框架,通过filter过滤器,在web.xml配置过滤器,拦截所有的访问,还有配置spring-security的配置文件位置,在spring-security配置文件中配置intercept-url拦截器,项目一启动,走web.xml去加载 spring 的容器,其中就有spring-security核心控制器,里面就有拦截规则,拦截规则要注意一点:小范围在上,大范围在下;在spring-security配置文件中除了要配置拦截规则(intercept-url),还要配置认证管理器(authentication-manager),springsecurity要认证的话就要走认证管理器,认证管理器会加载出用户所有的角色:
在intercept-url中可以规定角色或者走认证类: userDetailService
userDetailService认证是否用户,如果有做返回;
access写法规定:必须大写,必须ROLE_ 开头,可以有多个角色,用逗号隔开;
获取登录人信息:SecurityContextHolder.getContext().getAuthentication().getName();
退出登录,在配置文件中配置</logout>,然后在页面调用;登录需要在页面调用login
shiro 安全认证时如何做的
在 application Code 应用程序中调用 subject 的 login 方法。将页面收集的用户名和
密码传给安全管理器 securityManager,将用户名传给 realm 对象。Realm 对象可以
理解为是安全数据桥,realm 中认证方法基于用户名从数据库中查询用户信息。如果用
户存在,将数据库查询密码返回给安全管理器 securityManager,然后安全管理器判断
密码是否正确。
商家入驻
不太了解
经典权限5张表
注册后提交给运营商后台进行审核,在添加时,设置默认审核状态为0(未审核状态)
状态码: 0未审核 ,1已审核 , 2审核通过 3关闭
文件上传FastDFS
在前端页面,必须使用ost方法提交,原始表单<form enctype="multipart/form-data">要添加此属性
FastDfs包括Tracker server调度服务器,和Storage server服务器
Tracker 负责负载均衡和调度 Storage负责文件存储,分为多个组,没有主从概念
文件上传流程
客户端发送上传文件请求,tracker查询可用的storage,把可以用的存储服务器ip和端口号返回,
客户端上传文件(两个参数,文件内容和描述),storage保存文件,并生成filedId(路径信息和文件名),客户端只需要保存返回的文件信息
组名/虚拟磁盘路径/数据两级目录/文件名(根据特定信息生成,与上传时并不一致)
文件下载流程
客户端发送下载文件请求,tracker查询可用的storage,返回可用的storage信息(ip和端口号),
拿存储的索引fielId到Storage查找文件,返回fileContext
引入坐标fastDfs,添加配置文件设置tracker服务器的ip地址
商品的增删改查
添加:
根据3级分类确定模板,根据模板的id查到品牌列表,和specId下的值,以及扩展属性
在描述商品介绍时是用了富文本编辑器kindeditor
三级分类:用$watch属性观察变量来执行函数
图片上传:在Spring中使用FastDFS
使用FastDFS工具类
在springmvc添加多媒体解析器ComminMultipartResolver,可以设置文件大小
在Controller中 参数类型为MultipartFile
规格和规格选项:判断是否启用 将其传到前端,用两层for解析数据
删除:
逻辑删除,将删除的商品的isdelete字段设置为1,查询不显示
查询:
注意,商家设置sellerId只能查到自家的商品
运营商显示所有商品
修改:
回显,要注意数据转换
关于规格的更改,直接删掉原有的规格选项数据,将修改的添加即可
商品的状态码
添加商品后 设置默认状态 0 待审核
由商家提交商品给运营商审核 1 审核中
运营商审核后(如果审核成功后修改了商品,需要重新提交审核) 2 审核成功 --修改后1
审核不批准 (商家查看修改商品后状态修改为1) 3 驳回 --修改后1
拉黑,不可再提交 4 屏蔽
商家可以将审核成功的商品上架 5 上架 状态2 可以执行5
商家下架商品 6 下架 下架后状态改为2
运营商强制下架 7 强制下架 状态改为3
广告及同步问题解决
广告系统并发压力很大,用户频繁访问电商系统首页
解决首页门户系统压力: 门户系统做集群
门户系统调用广告服务,压力转给广告服务
解决广告服务压力:广告服务采用集群部署
广告服务频繁访问数据库,导致数据库压力过大,甚至雪崩
解决数据库压力:在数据库前加缓存
使用redis
缓存优势:减轻数据库压力,提高并发能力(内存版数据库)
业务查询流程
查询数据先从redis中查,频繁查询的数据都放入redis,
如果redis没有,去查数据库,数据同样存到redis
如果有,直接返回数据,不必查询数据库,减轻数据库压力
SpringDataRedis
广告管理业务逻辑:
一共两张表,广告分类和广告表,广告分类里有分类的ID和广告分类的名称,我们根据分类的ID在广告表里查询相应的广告,今日推荐,轮播图等;因为考虑到门户网站做访问每次查询数据库是一个瓶颈,所以我们用redis进行缓存,先查询redis,如果查询不出来,就查询数据库,并同步广告信息到redis;(绑定hash,大key是content,里面的小key是categoryId,value是contentList),考虑到用户可能修改广告分类,所以要在客户修改之前,将此分类的下存的redis广告信息删除,再进行修改;
同步问题
如果商家修改数据并存入数据库,而此时redis中存储的数据仍然是未修改的数据,则会产生数据不同步的问题
解决方案:
如果添加某分类的数据,则直接在商品添加方法中直接删除redis中此分类数据,在做查询时,此类数据为空,走数据库查询并存入redis
如果是删除某分类数据,需要先根据参数的主键从数据库查到要删除的对象,根据此对象的分类id删除redis中的此分类下所有数据,然后在删除数据库中的数据
Long categoryId = contentMapper.selectByPrimaryKey(id).getCategoryId();
//广告分类ID
redisTemplate.boundHashOps("content").delete(categoryId);
contentMapper.deleteByPrimaryKey(id);
修改略有不同,
如果只是修改一部分数据而删除整个分类效率太低,
而且如果修改了广告的分类,则需要将原分类和修改后分类的数据全部删掉
所以采取以下办法
因为修改的对象的主键id不会变,则通过主键查询到数据库尚未修改的对象,对比分类id
如果分类id不一致,则说明该对象的分类已被修改,需要将原分类以及修改后的分类下所有数据一起删除
如果分类id一致,则说明分类未被修改,只需要删除原分类的数据即可
站内搜索
项目的站内搜索使用的是SringDateSolr框架,底层对SolrJ进行的封装,提高搜索性能
solr是一个war包可以在tomcat中直接使用,lucene是全文检索底层原理
@Field注解:
在实体类属性使用此注解标识,如果属性与配置文件定义的域名称不同,则在注解中指定域名称
中文分词器IK Analyzer
会按照中国人用词习惯自动分词
基本可分为2种模式
smart:根据内在方法输出一个最合理的分词结果
张三 | 说的 | 确实 | 在理
非smart:将能够分出来的的词全部输出
张三 | 三 | 说的 | 的确 | 的 | 确实 | 实在 | 在理
涉及到了歧义判断,而项目中采用的是smart模式
共有3种域 业务域,复制域,动态域
业务域:相当于数据库的表字段:常用属性
name:指定域的名称
type:指定域的类型
indexed:是否索引
stored:是否存储
required:是否必须
multiValued:是否多值
复制域:复制域的作用在于将某一个Field中的数据复制到另一个域中
动态域:在项目中应用在规格上,因为规格的值是不固定的,需要扩充字段
在SpringDataSolr中,域的字段要和pojo类的属性一致,若不一致,则需要使用@Field直接,如果是动态域还要加@Dynamic注解
站内搜索做了几个功能
分页查询:query.setOffset()开始索引,setRows每页记录数
高亮查询:通过创建高亮对象,设置高亮域,添加高亮前缀,后缀,以及高亮选项
条件查询:设置条件Criteria,并将条件添加至query
过滤查询:通过过滤对象SimpleFilterQuery拼接条件
价格排序序:确定排序的域:价格,Sort.Direction.ASC据此判断确定升降序
Solr高亮不能显示问题:
前台使用angularJS加载搜索结果,但是发现高亮不能展示,
原因:
aJS底层使用ajax,一部加载高亮信息返回给页面,页面没有刷新,直接显示返回数据.
此时会把所有的数据作为普通文本进行加载,故而无高亮效果
解决方案:
使用AJS过滤器过滤文本数据,此时过滤器把html文本数据解析为浏览器能识别的html标签,高亮即可展示
(后台:在设置高亮时,需要将高亮部分取出并赋值,否则无高亮效果)
注意增删改索引库的操作都需要commit提交
solr同步问题
商家修改商品,而此时索引库的索引并未与数据库同步
使用消息中间件解决索引库同步问题,在商品上架时,更新到索引库
Solr怎么设置搜索结果排名靠前?
可以设置文档中域的boost值,boost值越高,计算出来的相关度得分就越高,排名也就越靠前。此方法可以把热点产品或者推广商品的排名提高。
商品详情页FreeMaker
商品繁多,商品又有大量信息,使用页面静态化技术可以减轻数据库的访问压力,有利于SEO,
而且如果是静态页面可以用Niginx服务器来部署,
网页静态化技术和缓存共同点是为了缓解数据库的访问压力,缓存比较适合小规模的数据,网页静态化比较适合大规模且相对变化不频繁的数据
freeMaker用于商品上架后,生成商品详情静态页面
1导入坐标
2配合config文件,两个关键属性 模板所在路径以及编码格式
3通过conf类获取模板,导出的是map类型数据,用得是输出字符流
3在路径下编写ftl文件(创建HTML文件,将后缀改成ftl即可)
然后使用nginx作为服务器,加载静态页面和作为反向代理的服务器。
4下架操作,删除页面即可
消息中间件ActiveMQ
引入ActiveMQ来解除运营商与搜索服务,商品详情页的耦合
提高项目并发能力,提高任务处理速度
ActiveMQ有自带的持久化配置,如果需要存到数据库,则需更改相关配置
Java消息服务JMS
5种数据类型
TextMessage--一个字符串对象(json字符串)
· MapMessage--一套名称-值对
· ObjectMessage--一个序列化的 Java 对象(传数组Long,包装类对象)
· BytesMessage--一个字节的数据流
· StreamMessage -- Java 原始值的数据流
产生的问题:信息过长会报错,所以尽量做逻辑截取
controller发送消息,如果service崩溃了,没有处理完消息
此消息会存在队列中,可以重新处理消息
2种消息传递类型
点对点:生产者对应消费者(项目应用 更新索引库)
queue队列消息 点对点 导入与删除solr库
订阅模式:由多个消费者进行接收(生成商品详情页)
topic群发消息 一对多 生成与删除静态页面
发送&接收消息的9个步骤:
1.创建连接工厂
2.获取连接
3.启动连接
4.获取session (为了获取session才会创建连接工厂,只要拿到session后面全都是session创建的)
5.创建队列对象
6.创建消息生产者对象或消费者
7.创建消息 //接收消息setMessageListenner
8.使用生产者发送消息 //使用消费者接收消息
9.关闭资源
为什么要用ActiveMQ
考虑到当时运营商后台调用的关系太多,比如商家商品服务,广告内容服务,搜索服务和商品页面生成服务等,耦合度比较高,后期维护起来困难;为了改善系统模板调用关系,减少模块之间的耦合,所以用到消息中间件.
我们通过引入消息中间件 activeMQ,使得运营商系统与搜索服务、页面生成服务,注册短信验证服务解除了耦合。
ActiveMq 消息被重复消费,丢失,或者不消费怎么办
重复消费:Queue 支持存在多个消费者,但是对一个消息而言,只会有一个消费者可以消费。
丢消息:用持久化消息,或者非持久化消息及时处理不要堆积,或者启动事务,启动事务后,commit()方法会负责任的等待服务器的返回,也就不会关闭连接导致消息丢失了。
不消费:去 ActiveMQ.DLQ 里找找
如果 activeMQ 的消息没有发送成功,怎样确保再次发送成功?
重新传递消息的情况
ActiveMQ 在接收消息的 Client 有以下几种操作的时候,需要重新传递消息:
1:Client 用了 transactions(事务),且在 session 中调用了 rollback()
2:Client 用了 transactions,且在调用 commit()之前关闭
3:Client 在 CLIENT_ACKNOWLEDGE 的传递模式下,在 session 中调用了 recover()
确保客户端有几种状态,检测状态,只要提交了那就说明客户端成功
用户注册,短信验证
c端的时候注册阿里短信
通过阿里的sdk,就可以发短信
用的是httpClient技术
HttpClient 提供的主要的功能
(1)实现了所有 HTTP 的方法(GET,POST,PUT,DELETE 等)
(2)支持自动转向
(3)支持 HTTPS 协议
(4)支持代理服务器等
httpclient是短链接技术,socket是长链接的技术;短链接请求,发送一次请求从服务器拿回数据,长链接请求是三次TCP/IP协议进行交互数据;长链接相对于短链接的区别最主要的是客户端能够主动向服务器端交互数据,而短链接只能让服务器被动等待浏览器做请求,而长链接正好相反;【手机推送消息:服务器端推送消息(友盟推送),所有装有友盟SDK的手机和友盟的服务器端做长链接,而我们的服务器端跟友盟的服务器端做短链接】;
验证码存到redis缓存中,在用户做注册时,作对比即可
为了发送短信搭建了短信服务
用httpClient实现发送短信sendSms(String phone,String code)
但是,如果发生阻塞(譬如service用了缓存存储数据,然而redis没有启动) controller 请求3次service 也就会发送3次验证码
用的是MQ,queue队列效果
user-web发送一条mq消息,sms-web开消息监听,收到消息后,调用阿里接口
HttpClient有restful风格 实现get.post,put,delete四种方法
restFul风格
符合REST约束风格和原则的应用程序或设计就是RESTful.
post:create get:read put:update delete
(在SpringMVC中,在用delete方法时,根据id删除会报405错,但是用ajax没问题)
用户注册逻辑:
首先要写一个add方法,这个方法是将用户信息存入数据库;
因为是面向服务的架构,为了发送注册短信我们要单起一个服务,这个服务一直开着监听,这里就涉及到消息中间键ActiveMQ;发短信需要导一个工具类SMSUtil,这个工具类需要发送四个参数,手机号,验证码,模板号,签名;然后模板号和签名写入properties配置文件里;所以只需要发送手机号和验证码,验证码是一个随机数,六位正整数;然后把这个验证码以key,value的形式存到redis里面,key是手机号。点注册,从redis中取出验证码信息,如果验证顺利,则放行,完成该用户的注册;
单点登录SSo
访问流程
1访问 用户在浏览器发送请求给后台
2重定向: 后台重定向用户请求到SSO服务器
3认证:用户认证
4票据:SSO服务器会发一个票给用户
5验证:用户把票给后台,后台去SSO服务器验证票据合法性,验证通过后允许用户访问
6传输用户信息:SSO票据验证通过后,把用户认证结果给后台
总体来说就是走两次循环,一个循环问有没有票,没票去验证登录,第二个循环拿着票做验证并登录;
SSO单点登录使用的CAS开源软件
SpringSecurity集成CAS
springSecurity配置文件中简单来说要配置单点登录,单点登出,还有认证类,认证管理器中将登录认证的权限叫给cas,配置的userDetailService的作用不再是验证用户信息,只是赋予权限;
怎么确保 session 共享?
在分布式项目中实现 session 共享必须做以下准备工作:
1) Cookie 中共享 ticket
2) Redis 存储 session
分布式系统共享用户身份信息 session,必须先获取 ticket 票据,然后再根据票据信息获取 redis 中用户身份信息。
实现以上 2 点即可实现 session 共享
购物车
实现有三种方式
1、当用户在未登录的情况下,将此购物车存入Cookie中, 在用户登陆的情况下,将购物车数据存入redis 。如果用户登陆时,Cookie中存在购物车数据,需要将Redis的购物车合并到redis中存储.
缺点:Cookie最大存放:4K;
2、当用户在未登录的情况下,将此购物车存入LocalStorage中, 在用户登陆的情况下,将购物车数据存入redis 。如果用户登陆时,LocalStorage中存在购物车数据,需要将Redis的购物车合并到redis中存储.
缺点:当用户清空浏览器缓存时,LocalStorage数据就没有了;
3、不管用户是否登录,都保存在Redis中;
(1)当用户未登录的情况下,获取SessionID,以SessionID作为Redis的Key保存;
(2)如果用户登录了,根据用户名来保存到Redis;
(3)如果用户登录时,把SessionID中的Redis数据和用户名获取的Redis数据合并;
将数据全部存入redis是最后敲定的方式
一个商家一个购物车生成一个商家订单,对用户来说就是一个购物车集合,一个支付订单
具体实现逻辑
创建购物车实体类,内中有三个属性,商家id,名称,订单详情(购物车明细)
生成一个uuId存入Cookie(可以设置一个生命周期)
开启SpringSecurity匿名登录功能 匿名登录名anonymousUser
获取当前登录名
在web层判断用户是否登录,
添加
如果登录
用登录名userId作为key获取存在redis中的购物车
把商品id和数量存到购物车中
根据userId把购物车重新存到redis
若未登录(anonymousUser)
根据uuid获取购物车
把商品id和数量存到购物车中
根据uuid把购物车重新存到redis
查询
如果登录
先获取已存在的购物车和未登录时的购物车
如果未登录的购物车不为空
合并购物车
删除未登录的购物车
将合并后的购物车保存到redis
如果未登录
根据uuid从redis查询
service层
添加商品到购物车集合(购物车集合,商品id,数量)
根据商品id判断是否有该商家的购物车
如果没有
就创建一个购物车将商品和数量存进去
添加到购物车集合
如果有
根据商品id判断是否已存在该商品
如果存在
把数量加进去,在保存
如果订单商品数量小于1了
把商品移除
如果该商家购物车商品集合小于1
把此购物车移除
如果没有
添加这个商品id和数量到购物车
返回这个购物车集合
跨域&订单
订单实现:
从购物车系统跳转到订单页面,选择默认收货地址
选择支付方式
购物清单展示
提交订单
订单业务处理:
一个商家一个订单,不同的仓库发送的货品也是属于不同的订单。因此会产出不同的订单号。
订单处理:根据支付的状态进行不同的处理
1) 在线支付
a) 支付未成功—从新发起支付
b) 支付超时---订单关闭
2) 货到付款
订单中的事物是如何保证一致性的
使用分布式事务来进行控制,保证数据最终结果的一致性
跨域问题:
首先明白什么是跨域。什么时候涉及跨域问题。当涉及前端异步请求的时候才涉及跨域。
那什么是跨域呢?当异步请求时,访问的请求地址的协议、ip 地址、端口号任意一个与当前站点不同时,就会涉及跨域访问。
解决方案:
1、jQuery 提供了 jsonp 实现 2、W3C 标准提供了 CORS(跨
域资源共享)解决方案。
{'withCredentials':true}在ajax请求需要跨域的url中添加
springMVC的版本在4.2或以上版本,可以使用注解实现跨域, 我们只需要在需要跨域的方法上添加注解@CrossOrigin即可
@CrossOrigin(origins="http://localhost:9105",allowCredentials="true")
不用SpringMVC注解可以用以下代码
response.setHeader("Access-Control-Allow-Origin", "http://localhost:9105");
response.setHeader("Access-Control-Allow-Credentials", "true");
微信支付
调用微信的支付接口,参考微信提供的 api使用了微信的统一下单接口和查询支付状态接口
每个接口需要的参数放入到 map 中使用微信提供的 sdk 转成 XML 字符串,httpClient
远程提交参数和接收结果
具体实现流程
1.二维码:前端使用qrious框架,导入js文件
2.共有10个接口,而我只负责两个
一个请求支付地址接口,另一个查询支付接口
3.
用httpClient技术,封装了一个工具类
1.封装请求内容
2通过微信的工具类 将封装参数的map转为xml的字符串
3设置https的请求
4发送post
5接受到返回的xml字符串,通过微信工具类转为map
支付流程
我们用的支付模块是用的微信,当用户提交订单后,我们会根据用户提交的订单,通过第三方的算法生成一个二维码,显示到前台,服务端时刻监控这个二维码, 如果用户到了二维码页面一直未支付,或是关掉了支付页面,我们的代码会一直循环调用微信接口,这样会对程序造成很大的压力。所以我们要加一个时间限制或是循环次数限制,当超过时间或次数时,跳出循环。如果用户成功付款,则api返回过来一个支付成功的状态,我们根据这个成功的状态,做出对该订单的一个后续状态的改变,如果支付失败,则向前台返回一个支付失败的页面,我们是怎么处理之后的日志的问题呢?
(1)在用户下订单时,判断如果为微信支付,就想支付日志表添加一条记录,信息包括支付总金额、订单ID(多个)、用户ID 、下单时间等信息,支付状态为0(未支付)
(2)生成的支付日志对象放入redis中,以用户ID作为key,这样在生成支付二维码时就可以从redis中提取支付日志对象中的金额和订单号。
(3)当用户支付成功后,修改支付日志的支付状态为1(已支付),并记录微信传递给我们的交易流水号。根据订单ID(多个)修改订单的状态为2(已付款)。
修改好相应的日志之后,就可以根据相应的记录做其他的操作了…..
秒杀方案
秒杀涉及的技术
SpringTask定时任务
cron表达式秒时分日(星期几?)月周年
redis用到了3种类型
hash:所有商品存储,id是小key(库存限制,超卖问题)
set:解决重复购买问题,用户购买后存到set集合
用商品id作为大key,用户id作为值存进去
list:秒杀生成一个订单,leftpush到一个集合中,createOrder线程类中rightPop出来(解决数据库 卡死主线程不操作数据库的问题)
3线程池
4乐观锁,悲观锁
乐观锁:svn
悲观锁:select*```for update 不能同步操作
具体实现
创建定时任务将符合要求的秒杀商品存到redis
库存限制,时间限制,审核限制
在web层需先判断是否登录,若未登录需先登录