订单单点登录功能实现
导入shop-sso依赖
开启@enabledubbo
配置yml文件中的dubbo服务
提供应用信息和zookeeper地址
拦截器和配置拦截器类
因为订单这个系统是没有登录页面的,所以我们拦截器拦截这个订单系统的时候,如果他的ticket为空或者过期那么就跳回到前台系统的登录页面,因为这个是跨系统的,所以我们重定向的时候需要一个完整的路径,那么我们需要拿到前台系统的url,前台系统的url可以在yml文件中配置,通过@value("${shop.portal.url}")
去获取。
在拦截器中我们根据ticket拦截,如果没有登录则去登录,登录成功后重定向到已经登录过后才可以访问的页面。
代码:important!!!
单点登录失败,从订单系统重定向前台系统
response.sendredirect(portalurl+"login?redirecturl="+request.getrequesturl());
这是一个javaweb中的重定向代码。response.sendredirect()
是重定向到指定的url,参数portalurl是重定向的url基础地址,login是登录页面的url,redirecturl是登录成功后要重定向回来的页面的url。request.getrequesturl()
是获取当前请求的url,这里将其作为参数传递给了重定向url,登录成功后重回当前页面。其中redirecturl参数可以是任何需要登录后才能访问的页面,例如用户个人中心、订单页面等等。
代码
配置拦截器类:
/**
* mvc配置类
*
* @author zhoubin
* @since 1.0.0
*/
@configuration
@enablewebmvc
public class mvcconfig implements webmvcconfigurer {
@autowired
private orderlogininterceptor logininterceptor;
@autowired
private ordercommoninterceptor commoninterceptor;
/**
* addinterceptor:添加自定义拦截器
* addpathpatterns:添加拦截请求 /**表示拦截所有
* excludepathpatterns:不拦截的请求
* @param registry
*/
@override
public void addinterceptors(interceptorregistry registry) {
registry.addinterceptor(commoninterceptor)
.addpathpatterns("/**");
registry.addinterceptor(logininterceptor)
.addpathpatterns("/**")
.excludepathpatterns("/static/**")
.excludepathpatterns("/login/**")
.excludepathpatterns("/image/**")
.excludepathpatterns("/user/login/**")
.excludepathpatterns("/user/logout/**");
}
/**
* 放行静态资源
* @param registry
*/
@override
public void addresourcehandlers(resourcehandlerregistry registry) {
registry.addresourcehandler("/static/**").addresourcelocations("classpath:/static/");
}
}
拦截器三种方法:
@component
public class orderlogininterceptor implements handlerinterceptor {
@reference(interfaceclass = shopssoservice.class)
private shopssoservice ssoservice;
@autowired
private redistemplate<string,string> redistemplate;
@value("${user.ticket}")
private string userticket;
@value("${shop.portal.url}")
private string portalurl;
/**
* 请求处理的方法之前执行
* @param request
* @param response
* @param handler
* @return
* @throws exception
*/
@override
public boolean prehandle(httpservletrequest request, httpservletresponse response, object handler) throws exception {
//获取用户票据
string ticket = cookieutil.getcookievalue(request, "userticket");
if (!stringutils.isempty(ticket)){
//如果票据存在,进行验证
admin admin = ssoservice.validate(ticket);
//将用户信息存入session中,用于页面返显
request.getsession().setattribute("user",admin);
//重新设置失效时间
valueoperations<string, string> valueoperations = redistemplate.opsforvalue();
valueoperations.set(userticket+":"+ticket, jsonutil.object2jsonstr(admin),30, timeunit.minutes);
return true;
}
//票据不存在或者用户验证失败,重定向至登录页面
response.sendredirect(portalurl+"login?redirecturl="+request.getrequesturl());
return false;
}
/**
* 请求处理的方法之后执行
* @param request
* @param response
* @param handler
* @param modelandview
* @throws exception
*/
@override
public void posthandle(httpservletrequest request, httpservletresponse response, object handler, modelandview modelandview) throws exception {
}
/**
* 处理后执行清理工作
* @param request
* @param response
* @param handler
* @param ex
* @throws exception
*/
@override
public void aftercompletion(httpservletrequest request, httpservletresponse response, object handler,
exception ex) throws exception {
}
}
在portal前台页面中编写login方法
@requestmapping("login")
public string login(string redirecturl, model model){
model.addattribute("redirecturl",redirecturl);
return "login";
}
前台
!''
表示如果获取到的值为空,则将其转换为空字符串。
<input type="hidden" id="redirecturl" value="${redirecturl!''}"/>
<script type="text/javascript">
// 用户登录
function userlogin() {
$.ajax({
url: "${ctx}/user/login",
type: "post",
data: $("#formlogin").serialize(),
datatype: "json",
success: function (result) {
if (200 == result.code) {
// 如果存在重定向url则重定向至该url
if ($("#redirecturl").val()) {
location.href = $("#redirecturl").val();
}else {
location.href = "${ctx}/index";
}
} else {
layer.msg("用户名或密码错误,请重新输入!");
}
},
error: function () {
layer.alert("亲,系统正在升级中,请稍后再试!");
}
});
}
</script>
点击去结算跳转到预订单页面,说明为什么需要定义一个拦截器?@value?
去结算的那个页面是在前台系统的商品列表页面中,点击按钮之后跳转到订单系统的预订单页面。因此我们把订单系统的url放到前台系统的yml中,通过@value("${shop.order.url}")
获取订单系统的完整的url
注意这里的小坑
@value注解是spring容器中的,我们无法在controller层也就是springmvc容器中获取key中的value值。
解决方法:我们可以把我们的url放到我们的一个最大的作用域application
,通过拦截器去拿,因为拦截器会使用@component
注解,这样我们就可以将@value注解注册到spring容器中了。然后将url放到application域中。
但是我们这个拦截器会不拦截请求,但是每一个请求他都会将url存入到我们的application域中,会造成一个频繁的写操作,因此我们要进行判断,如果这个url已经存过了,那么就不需要再存了。
配置拦截器类
@autowired
private portalcommoninterceptor commoninterceptor;
/**
* addinterceptor:添加自定义拦截器
* addpathpatterns:添加拦截请求 /**表示拦截所有
* excludepathpatterns:不拦截的请求
* @param registry
*/
@override
public void addinterceptors(interceptorregistry registry) {
registry.addinterceptor(commoninterceptor)
.addpathpatterns("/**");
registry.addinterceptor(logininterceptor)
.addpathpatterns("/cart/**")
.excludepathpatterns("/static/**")
.excludepathpatterns("/login/**")
.excludepathpatterns("/user/login/**")
.excludepathpatterns("/user/logout/**");
}
实现hanlerinterceptor接口
@component
public class portalcommoninterceptor implements handlerinterceptor {
@value("${shop.order.url}")
private string shoporderurl;
@override
public boolean prehandle(httpservletrequest request, httpservletresponse response, object handler) throws exception {
//获取application对象
servletcontext servletcontext = request.getsession().getservletcontext();
string orderurl = (string) servletcontext.getattribute("orderurl");
if (stringutils.isempty(orderurl)){
servletcontext.setattribute("orderurl",shoporderurl);
}
return true;
}
}
接下来就是实现从前台系统跳转到订单系统了。
此时portal的全局域内有orderurl,我们点击去结算按钮走重定向的controller,到达订单系统之后我们走订单系统的controller,去预订单页面。
前台系统的controller
/**
* 跳转订单系统
* @return
*/
@requestmapping("/topreorder")
public string topreorder(httpservletrequest request){
//获取servletcontext上下文中的值
string orderurl = (string) request.getsession().getservletcontext().getattribute("orderurl");
//重定向
return "redirect:"+orderurl+"order/preorder";
}
订单系统的controller
/**
* 跳转到预订单页面
*
* @return
*/
@requestmapping("/preorder")
public string preorder(model model, httpservletrequest request) {
admin admin = (admin) request.getsession().getattribute("user");
model.addattribute("cartresult", cartservice.getcartlist(admin));
return "order/preorder";
}
到达预订单系统应当展示购物车列表和总金额
**怎么获取?**购物车列表和总金额可以调用rpc服务中的cartservice获取。要调用这个方法必须要获取用户信息,查看哪个用户的购物车信息。因为在拦截器里面登录的用户信息存放在session中,因此我们从session中获取用户信息。
@requestmapping("/preorder")
public string preorder(model model, httpservletrequest request) {
admin admin = (admin) request.getsession().getattribute("user");
model.addattribute("cartresult", cartservice.getcartlist(admin));
return "order/preorder";
}
公共的页面展示出购物车的数量,调用rpc的catservice
@controller
@requestmapping("/cart")
public class cartcontroller {
@reference(interfaceclass = cartservice.class)
private cartservice cartservice;
/**
* 获取购物车数量
* @return
*/
@requestmapping("/getcartnum")
@responsebody
public integer getcartnum(httpservletrequest request){
admin admin = (admin) request.getsession().getattribute("user");
return cartservice.getcartnum(admin);
}
}
提交订单
点击提交按钮,前端传过来的有cartresult(购物车列表,总金额),
点击提交订单之后,有三件事情需要完成
1、跳转到订单页面
2、删除购物车中的数据
3、将提交的数据变道数据库里面去,为了后续的查看全部支部订单。
返回的是否成功,因为需要渲染页面,把订单和总金额传入前台。
生成订单
要把订单存入到数据库中首先要先生成订单。
通过mybatis-plus进行生成。
有关订单的数据库有t_order,t_order_goods,生成pojo,mapper,xml文件。
new一个order,属性赋值,然后插入到数据库中,如果插入成功,则获取carresult中list的每一个元素,让他转换为ordergoods,设置订单商品中的订单号为订单编码,与order关联起来,然后将ordergoods放到一个list集合中,插入数据库中,如果插入成功,并未出现异常,则返回成功,否则返回失败,因为controller需要一个订单编号,所以我们可以将返回的成功的信息里面放入我们的订单编号,在controller就可以进行存入了。
在这里生成的订单的状态信息会用一个枚举类来进行定义。
注意这里的订单编号是唯一的,我们用的是redis的自增key
@override
public baseresult ordersave(admin admin, cartresult cartresult) {
//创建order对象
order order = new order();
//订单编号 shop_年月日时分秒_自增key
string ordersn = "shop_"+ datetimeformatter.ofpattern("yyyymmddhhmmss").format(localdatetime.now())+"_"+getincrement(redisorderincrement);
order.setordersn(ordersn);
//用户id
order.setuserid(admin.getadminid().intvalue());
//订单状态(未确认)
order.setorderstatus(orderstatus.no_confirm.getstatus());
//发货状态(未发货)
order.setshippingstatus(sendstatus.no_pay.getstatus());
//支付状态(未支付)
order.setpaystatus(paystatus.no_pay.getstatus());
//商品总价
order.setgoodsprice(cartresult.gettotalprice());
//应付金额
order.setorderamount(cartresult.gettotalprice());
//订单总价
order.settotalamount(cartresult.gettotalprice());
//订单时间
long addtime = localdatetime.now().toepochsecond(zoneoffset.of("+8"));
order.setaddtime(addtime.intvalue());
int result = ordermapper.insertselective(order);
//存储成功
if (result>0){
list<ordergoods> ordergoodslist = new arraylist<>();
for (cartvo cartvo : cartresult.getcartlist()) {
//创建ordergoods对象
ordergoods ordergoods = new ordergoods();
//订单id
ordergoods.setorderid(order.getorderid());
//商品id
ordergoods.setgoodsid(cartvo.getgoodsid());
//商品名称
ordergoods.setgoodsname(cartvo.getgoodsname());
//商品价格
ordergoods.setgoodsprice(cartvo.getmarketprice());
//商品数量
ordergoods.setgoodsnum(cartvo.getgoodsnum().shortvalue());
//订单方式
ordergoods.setpromtype(promtypestatus.normal.getstatus());
//发货状态
ordergoods.setissend(sendstatus.no_pay.getstatus());
//添加到订单商品对象列表
ordergoodslist.add(ordergoods);
}
//批量插入
result = ordergoodsmapper.insertordergoodsbatch(ordergoodslist);
if (result>0){
baseresult baseresult = baseresult.success();
baseresult.setmessage(ordersn);
return baseresult;
}
}
return baseresult.error();
}
/**
* redis自增key
* @param key
* @return
*/
private long getincrement(string key){
redisatomiclong entityidcounter = new redisatomiclong(key,redistemplate.getconnectionfactory());
return entityidcounter.getandincrement();
}
}
跳转到提交页面
1、生成订单
2、清空购物车
3、跳转页面
@requestmapping("/submitorder")
public string submitorder(model model, httpservletrequest request) {
admin admin = (admin) request.getsession().getattribute("user");
cartresult cartresult = cartservice.getcartlist(admin);
//1.存入订单信息
baseresult baseresult = orderservice.ordersave(admin, cartresult);
//2.清除购物车信息
cartservice.clearcart(admin);
//总价
model.addattribute("totalprice", cartresult.gettotalprice());
//订单编号
model.addattribute("ordersn", baseresult.getmessage());
//3.页面跳转
return "/order/submitorder";
}
订单系统和前台系统的交互
订单系统可以在搜索框中进行搜索,搜索跳转到前台系统。
搜索进入前台页面
@controller
@requestmapping("search")
public class searchcontroller {
/**
* 跳转搜索页面,传过去一个searchstr
* @param request
* @param searchstr
* @param model
* @return
*/
@requestmapping("index")
public string index(httpservletrequest request, string searchstr, model model){
try {
//对输入的内容进行编码,防止中文乱码
searchstr = urlencoder.encode(searchstr,"utf-8");
} catch (unsupportedencodingexception e) {
e.printstacktrace();
}
return "redirect:"+request.getsession().getservletcontext().getattribute("portalurl")+"search/index?searchstr="+searchstr;
}
}
在订单系统点击登出:
重定向,从应用域中获取数据request.getsession().getservletcontext().getattribute()
@requestmapping("/index")
public string index(httpservletrequest request, string searchstr, model model){
try {
//对输入的内容进行编码,防止中文乱码
searchstr = urlencoder.encode(searchstr,"utf-8");
} catch (unsupportedencodingexception e) {
e.printstacktrace();
}
return "redirect:"+request.getsession().getservletcontext().getattribute("portalurl")+"search/index?searchstr="+searchstr;
}
}
发表评论