1.关于单点登录策略说明
1.1 传统登录方式存在问题
说明:由于后端服务都是集群部署.如果用户登录操作采用Session的形式进行保存.则必然导致用户多次登录,才能保证用户的请求不受影响. 该种方式没有办法实现用户信息共享.并且效率低
1.2 SSO(单点登录)
单点登录(SingleSignOn,SSO),就是通过用户的一次性鉴别登录。当用户在身份认证服务器上登录一次以后,即可获得访问单点登录系统中其他关联系统和应用软件的权限,同时这种实现是不需要管理员对用户的登录状态或其他信息进行修改的,这意味着在多个应用系统中,**用户只需一次登录就可以访问所有相互信任的应用系统。**这种方式减少了由登录产生的时间消耗,辅助了用户管理,是目前比较流行的。
重要信息: 身份认证服务器 JT-SSO
核心思想: 1.用户信息共享 2.保证用户信息安全
1.3 SSO单点登录实现原理
实现步骤:
1.用户通过浏览器输入用户名和密码点击登录按钮开始执行单点登录操作.
2.JT-WEB服务器将用户请求传递给JT-SSO 单点登录服务器 进行数据校验.
3.JT-SSO接收用户名和密码之后,开始校验.如果用户名和密码正确则将user对象转化为JSON字符串.之后动态生成UUID.保存到Redis中. 注意超时时间(7天超时)
4.无论登录是否成功,都应该返回TICKET信息(可以为null)
5.用户通过浏览器提供的COOKIE保存返回的秘钥信息.方便下次访问.
6.当用户执行业务操作时,动态的获取TICKET信息,之后校验.校验通过则放行请求,否则拦截重定向到用户登录页面.
总结:步骤1-5其实是第一次工作的整体流程,步骤3是为了让我们的账户信息存在redis缓存中的key保证唯一,步骤6是当用户再次访问的时候拿着存在浏览器中的ticket凭证直接到redis校验是否正确,如果校验通过则放行请求。
1.4 用户登录具体实现
1.4.1 页面url分析
1.4.2 查看登录页面JS
1.5 编辑UserController(jt-web)
/**
* url地址: /user/doLogin
* 参数: {username:_username,password:_password}
* 返回值: SysResult对象
*
*
* 关于Cookie参数说明:
*
* Cookie特点: Cookie默认条件下,只能在自己的网址下进行展现, 京东的网站,看不到百度的cookie.
* Domain: 指定域名可以实现Cookie数据的共享 (jd.miaosha.com和jdqianggou.com正常来说两者之间的cookie数据是互相看不到的)
* Path:(权限设定) 只有在特定的路径下,才能获取Cookie
* 网址: www.jd.com/abc/findAll
* cookie.setPath("/aa"); 只允许/aa的路径获取该Cookie信息
* cookie.setPath("/"); 任意网址,都可以获取Cookie信息.
*/
@RequestMapping("/doLogin")
@ResponseBody
public SysResult doLogin(User user,HttpServletResponse response){
//1.通过user传递用户名和密码,交给业务层service进行校验,获取ticket信息(校验之后的回执)
String ticket = dubboUserService.doLogin(user);
if(StringUtils.isEmpty(ticket)) {
//证明用户名或密码错误.
return SysResult.fail();
}
//2.准备Cookie实现数据存储.
Cookie cookie = new Cookie("JT_TICKET", ticket);//这里必须写死,因为前端js使用这个JT_TICKET
cookie.setDomain("jt.com");
cookie.setPath("/");
cookie.setMaxAge(7*24*60*60); //7天超时
//将cookie保存到客户端中.
response.addCookie(cookie);
return SysResult.success();
}
1.6 编写jt-sso的DubboServiceImpl
@Override
public String doLogin(User user) {
String password = DigestUtils.md5DigestAsHex(user.getPassword().getBytes());
user.setPassword(password);//需要把我们输入的密码进行md5再次加密与我们创建用户的时候进行比对
//查询数据库检查是否正确 根据对象中不为null的属性充当where条件
QueryWrapper<User> queryWrapper = new QueryWrapper<>(user);
//queryWrapper.eq("password",password).eq("username",user.getUsername());可以简写
User userDB = userMapper.selectOne(queryWrapper);//同一个用户名和密码,要么没有数据,要么只有一个
//用户名和密码正确. 开始单点登录
String ticket = UUID.randomUUID().toString().replace("-", "");
//防止涉密信息泄露,则需要进行脱敏处理
//因为userDB是根据数据库查出来的,有可能会导致泄密,密码是敏感数据所以要优化
userDB.setPassword("你猜猜,看看能不能猜对!!!!");
String value = ObjectMapperUtil.toJson(userDB);
jedisCluster.setex(ticket, 7*24*3600, value);
return ticket;
}
1.7 用户信息回显
说明:当用户登录成功之后,需要回显用户信息,表明某某某用户登录成功了
当用户登录成功之后,F12检查是否发起跨域请求.
页面JS分析
1.8 编辑JT-SSO的UserController
/**
* url地址:http://sso.jt.com/user/query/f3ebc356884641d8abe5da509d6f21d9?callback=jsonp1597713923785&_=1597713923850
* 参数 :1.ticket(根据js分析得出) 2.callback(因为是Jsonp跨域访问,必定有这个参数)
* 返回值 JSONObject (必定是这个,因为是jsonp跨域访问)
*/
@RequestMapping("/query/{ticket}")
public JSONPObject findUserByTicket(String callback,@PathVariable String ticket){
if(jedisCluster.exists(ticket)) {
//用户之前登录过
String json = jedisCluster.get(ticket);
SysResult sysResult = SysResult.success(json);
return new JSONPObject(callback, sysResult);
}else {
//用户信息有误
SysResult sysResult = SysResult.fail();
return new JSONPObject(callback, sysResult);
}
}
1.9 页面效果展现
2.用户退出操作
2.1 业务说明
说明:当用户单击退出按钮之后, 页面应该重定向到系统首页,同时删除cookie和redis的登录信息
2.2 页面分析
2.3 编辑jt-web的UserController
/**
* url地址: http://www.jt.com/user/logout.html
* 参数 :无
* 返回值 String (根据业务我们得知,我们想重定向到jt.com首页,所以返回值是String)
*
* 业务实现思路:
* 0.先获取cookie中的数据 NAME=JT_TICKET
* 1.删除redis中的数据 key-value key=cookie中的value
* 2.删除cookie记录 根据cookie名称 设置存活时间即可.
*
* 注意事项: request对象中只能传递cookie的name和value.不能传递其他数据参数.
* 所以如果需要再次操作cookie则最好设定参数,否则可能导致操作失败
*/
@RequestMapping("/logout")
public String doLogOut(HttpServletRequest request,HttpServletResponse response){
Cookie[] cookies = request.getCookies();
if(cookies !=null && cookies.length >0) { //如果用户是个坏人,故意在控制台清除了cookie在来退出,程序会报空指针异常
for(Cookie cookie : cookies){
if("JT_TICKET".equalsIgnoreCase(cookie.getName())){
String ticket = cookie.getValue();
// 1.删除redis中的数据
jedisCluster.del(ticket);
//2.删除cookie 立即删除cookie 0 , 暂时不删,关闭浏览器时删除 -1
cookie.setDomain("jt.com");
cookie.setPath("/");
cookie.setMaxAge(0); //7天超时
response.addCookie(cookie);
break;
}
}
}
//重定向到系统首页
return "redirect:/";
//因为我们首页就写了www.jt.com 后面什么都没写,前面是经过nginx代理的,所以这样就能退出登录。
}
3 商品数据展现
3.1 业务描述
当用户看中某一个商品时,点击该商品,应该展现的是商品的详情信息.
3.2 商品页面取值描述item.jsp
3.3 将JT-MANAGE项目改造为Dubbo项目
编辑业务实现类
编写jt-manage(提供者)YML配置文件,添加Dubbo配置
3.4 编写JT-WEB中的ItemController
@Reference(check = false) //启动时暂时不校验提供者,为了避免我们启动顺序的前后问题
private DubboItemService dubboItemService;
/**
* 请求url地址:http://www.jt.com/items/562379.html
* 参数 商品id Item的主键
* ${item.title } 商品信息
* ${itemDesc.itemDesc } 商品详情信息
*
* 返回值 item.jsp页面 String 因为我们使用一个通用的页面只要在url地址上更换商品id就能呈现出不一样的商品
*
* 业务说明: 根据商品id查询执行的商品/商品详情信息,之后在页面中展现.
* @param itemId
* @return
*/
@RequestMapping("{itemId}")
public String findItemById(@PathVariable Long itemId,Model model){
Item item = dubboItemService.findItem(itemId);
ItemDesc itemDesc = dubboItemService.findItemDesc(itemId);
model.addAttribute("item", item); //key值固定因为前端js写死了,通过el表达式获取数据
model.addAttribute("itemDesc", itemDesc);
return "item";
}
3.5 编辑JT-MANAGE DubboItemServiceImpl
说明:直接根据商品id查询出商品信息和商品描述信息
public class DubboItemServiceImpl implements DubboItemService {
@Autowired
private ItemMapper itemMapper;
@Autowired
private ItemDescMapper itemDescMapper;
@Override
public Item findItem(Long itemId) {
return itemMapper.selectById(itemId);
}
@Override
public ItemDesc findItemDesc(Long itemId) {
return itemDescMapper.selectById(itemId);
}
}
3.6 页面效果展现(成功)
注意:此时jt-manage服务器必须开启,不然查不到数据
4 创建购物车项目 JT-Cart
4.1 购物车表设计
说明: 通过userID和商品ID标识唯一的购物行为.
总结:通过哪一个用户的id和哪一件商品的id来确定购物行为。
4.2 编辑Cart pojo对象
@TableName("tb_cart")
@Data
@Accessors(chain = true)
public class Cart extends BasePojo{
@TableId(type = IdType.AUTO)
private Long id;
private Long userId;
private Long itemId;
private String itemTitle;
private String itemImage;
private Long itemPrice;
private Integer num;
}
4.3 构建jt-cart 服务提供者
4.3.1 编辑POM.xml文件
说明: 添加继承/依赖/插件
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.jt.vip</groupId>
<artifactId>jt</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>jt-cart</artifactId>
<!--添加依赖 -->
<dependencies>
<dependency>
<groupId>com.jt.vip</groupId>
<artifactId>jt-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
<!--3.添加插件 -->
<!--负责项目打包 更新 maven操作相关的配置 必须添加 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
4.3.2 编辑YML配置文件
server:
port: 8094
servlet:
context-path: /
spring:
datasource:
#引入druid数据源
#type: com.alibaba.druid.pool.DruidDataSource
#driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/jtdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
username: root
password: root
mvc:
view:
prefix: /WEB-INF/views/
suffix: .jsp
#mybatis-plush配置
mybatis-plus:
type-aliases-package: com.jt.pojo
mapper-locations: classpath:/mybatis/mappers/*.xml
configuration:
map-underscore-to-camel-case: true
logging:
level:
com.jt.mapper: debug
#关于Dubbo配置
dubbo:
scan:
basePackages: com.jt #指定dubbo的包路径
application: #应用名称
name: provider-cart #一个接口对应一个服务名称
registry:
address: zookeeper://192.168.126.129:2181?backup=192.168.126.129:2182,192.168.126.129:2183
protocol: #指定协议
name: dubbo #使用dubbo协议(tcp-ip) web-controller直接调用sso-Service
port: 20882 #每一个服务都有自己特定的端口 不能重复.
4.3.3 定义购物车项目结构
4.4 购物车列表页面展现
.url分析: 当用户发起请求时应该跳转/cart/show.html页面 将来展现的页面名称为:cart.jsp
4.5 编辑jt-web CartController
@RequestMapping("/cart")
@Controller //因为返回的是一个页面
public class CartController {
@Reference(check = false)
private DubboCartService dubboCartService;
/**
* url地址:http://www.jt.com/cart/show.html (访问的时候需要返回一个购物车的页面)
* 参数
* 返回值是一个页面 所以是一个String cart.jsp
*
* 业务思路: 当用户点击购物车按钮时,应该根据userId查询购物车信息,之后在列表页面中展现.
* (因为是我这个用户的购物车)
*
* 页面数据展现: 利用${cartList}展现数据(因为这是前端页面使用EL表达式写死的值,我们必须叫这个名字)
*/
@RequestMapping("/show")
public String show(Model model){
//1.获取userId 利用单点登录方式动态获取userID 暂时定死
Long userId = 7L;
//2.根据userId查询购物车数据
List<Cart> cartList = dubboCartService.findCartListByUserId(userId);
//利用model对象将数据填充到域对象中request域
model.addAttribute("cartList", cartList);
return "cart";
}
}
4.6 编辑jt-cart的 DubboCartServiceImpl
@Service
public class DubboCartServiceImpl implements DubboCartService {
@Autowired
private CartMapper cartMapper;
@Override
public List<Cart> findCartListByUserId(Long userId) {
QueryWrapper<Cart> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user_id", userId);
return cartMapper.selectList(queryWrapper);
}
}
4.7 编辑jt-web 页面效果展现
5 购物车商品数量的修改
5.1 业务分析
1).页面url分析 /cart/update/num/商品ID/数量
2). 页面JS分析
$(".increment") 代表class选择器. 识别标签元素 class=“increment” 的元素
$(".increment").click(function(){//+
var _thisInput = $(this).siblings("input"); //找到input框
//_thisInput.val() 获取input框中的元素
//eval 进行算数运算
//_thisInput.val(14); 赋值操作
_thisInput.val(eval(_thisInput.val()) + 1);
$.post("/cart/update/num/"+_thisInput.attr("itemId")+"/"+_thisInput.val(),function(data){
TTCart.refreshTotalPrice();
});
});
5.2 编辑jt-web CartController
/**
* 业务需求: 完成购物车商品数量更新操作
*
* url地址: http://www.jt.com/cart/update/num/562379/5
* 参数: 1. 562379 itemId 2. 5 商品数量num
* 返回值 :无 看看前端js的需求,没有需要我们返回什么数据(写SysResult也可以)
*/
@RequestMapping("/update/num/{itemId}/{num}")
//参数如果和属性(pojo对象)名称一致,则可以直接赋值.
//public void updateCartNum(@PathVariable Long itemId,@PathVariable Integer num)
public void updateCartNum(Cart cart){
Long userId = 7L;
cart.setUserId(userId);
dubboCartService.doUpdateItemNum(cart);
}
5.3 编辑jt-cart DubboCartServiceImpl
//update tb_cart set num = #{num},updated = #{updated} where user_id=#{userId} and item_id =#{itemId}
//本质上是通过sql去更新商品数量的变化
@Override
@Transactional //事务控制
//本质上跟着sql来写我们的代码就可以了
public void doUpdateItemNum(Cart cart) {
Cart cartTemp = new Cart();
cartTemp.setNum(cart.getNum()).setUpdated(cart.getUpdated());
QueryWrapper<Cart> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user_id",cart.getUserId())
.eq("item_id", cart.getItemId());
cartMapper.update(cartTemp, queryWrapper);
}
6 购物车新增
6.1 业务说明
当查询商品之后,点击新增购物车时.应该跳转到购物车展现页面. 之后完成购物车新增操作.
强调:
1.当用户第一次新增购物车时,应该入库保存
2.当用户重复添加时,应该只更新商品的数量.
6.2 编辑jt-web CartController
/**
*
* url地址:http://www.jt.com/cart/add/562379.html
* 参数: 表单数据提交 cart
* 返回值: String 重定向到购物车展现页面
*/
@RequestMapping("/add/{itemId}")
public String saveCart(Cart cart) {
Long userId = 7L; //暂时写死,效果是每个用户看到的购物车的内容都是一致的现在
cart.setUserId(userId);
dubboCartService.saveCart(cart);
return "redirect:/cart/show.html"; //维护伪静态策略
}
6.3 编辑 jt-cart的 DubboCartServiceImpl
//1.第一次架构 入库
//2.第N次架构 更新数据库
@Override
@Transactional //事务控制
public void saveCart(Cart cart) {
//1.先查询数据库中是否有该记录 itemId和userId
QueryWrapper<Cart> queryWrapper = new QueryWrapper<Cart>();
queryWrapper.eq("user_id", cart.getUserId());
queryWrapper.eq("item_id", cart.getItemId());
Cart cartDB = cartMapper.selectOne(queryWrapper);
//cartDB几乎所有的数据都不为null 将来可能被当做条件使用.
if(cartDB == null) {
//说明第一次加购
cart.setCreated(new Date())
.setUpdated(cart.getCreated());
cartMapper.insert(cart);
}else {
//只更新商品数量(因为如果此商品购物车之前加载过的,我们要在原来的基础上增加)
Integer num = cart.getNum() + cartDB.getNum();
/**Cart cartTemp = new Cart();
cartTemp.setId(cartDB.getId())
.setNum(num)
.setUpdated(new Date());
cartMapper.updateById(cartTemp); //根据主键更新数据库.
**/
//自己手动操作Sql(当前商品id,数量,时间)
cartMapper.updateCartNum(cartDB.getId(),num,new Date());
}
}
6.4 编辑 jt-cart的 CartMapper
public interface CartMapper extends BaseMapper<Cart> {
@Update("update tb_cart set num=#{num},updated=#{date} where id=#{id}")
void updateCartNum(Long id, Integer num, Date date);
}
7 购物车商品的删除
7.1 页面url分析
7.2 编辑CartController
/**
* 业务:删除购物车商品信息
*
* url地址:http://www.jt.com/cart/delete/1474391988.html
* 参数: 商品id itemId
* 返回值: 重定向到购物车页面(分析:因为我们删除商品的时候不希望调到其他的页面去,就再原页面显示内容即可)
*/
@RequestMapping("/delete/{itemId}")
public String deleteItemInfo(@PathVariable Long itemId){
Long userId = 7l; //需要确定是哪一个用户的那一间商品
Cart cart = new Cart();
cart.setUserId(userId).setItemId(itemId);
dubboCartService.doDeleteItemInfo(cart);
return "redirect:/cart/show.html";//维护伪静态策略
}
7.3 编辑jt-cart 的 DubboCartServiceImpl
@Override
public void doDeleteItemInfo(Cart cart) {
//让对象中不为null的属性充当where条件 (直接把cart对象传进去条件构造器,也可以仅仅根据商品id修改)
QueryWrapper<Cart> wrapper = new QueryWrapper<>(cart);
//wrapper.eq("item_id", cart.getItemId());
cartMapper.delete(wrapper);
}
8 权限控制
业务需求说明
当用户未登录时,不允许访问敏感操作. 例如访问购物车/订单等系统.如何实现???
技术: 拦截器技术 或者 shiro
8.1 SpringMVC中拦截器定义
说明:拦截器一般只拦截web页面资源的请求.
拦截器处理的流程图:
8.2 编辑UserInterceptor自定义拦截器业务(初级版)
/**
* 目的: 如果用户不登录,则不允许访问权限相关业务.
* 返回值:
* true:表示放行
* false: 表示拦截 一般配置重定向使用.(因为我们把请求拦截了,是不是要做点处理呢?总不能什么都不做吧!)
* 注意事项:必须添加拦截器策略.
*
*/
//springmvc对外提供的拦截器接口HandlerInterceptor
@Component //将对象交给spring容器管理
public class UserInterceptor implements HandlerInterceptor {
@Autowired
private JedisCluster jedisCluster;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
//重定向到用户的登录页面
response.sendRedirect("/user/login.html");
return false;
}
}
8.3 拦截器配置
8.4 编辑UserInterceptor自定义拦截器业务(终极版)
/**
* 目的: 如果用户不登录,则不允许访问权限相关业务.
* 返回值:
* true:表示放行
* false: 表示拦截 一般配置重定向使用.(因为我们把请求拦截了,是不是要做点处理呢?总不能什么都不做吧!)
*
*
* 注意事项:必须添加拦截器策略.
* 业务说明:
* 用户如果已经登录,则放行,反则拦截
*
* 如何判断用户是否登录:
* 1.判断客户端是否有指定的Cookie true
* 2.应该获取cookie中的值 去redis中校验是否存在. true
* 如果上述条件都满足,则应该放行请求.
*
*/
//springmvc对外提供的拦截器接口HandlerInterceptor
@Component //将对象交给spring容器管理
public class UserInterceptor implements HandlerInterceptor {
@Autowired
private JedisCluster jedisCluster;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
Cookie[] cookies = request.getCookies();
if(cookies!=null && cookies.length>0){
for(Cookie cookie : cookies){
if("JT_TICKET".equalsIgnoreCase(cookie.getName())){
//如果equals则说明cookie是存在的.
String ticket = cookie.getValue();
//2.redis中是否有该记录 如果有记录则放行请求.
if(jedisCluster.exists(ticket)){
//说明数据以及存在.可以放行
return true;
}
}
}
}
//重定向到用户的登录页面
response.sendRedirect("/user/login.html");
return false;
}
}
8.5 如何动态获取userId(因为之前我们写死了userId等于7L)
说明:我们之前为了方便检验查询效果,把userId固定写死为7L,这样的效果就是不管哪一个用户登入到我们的前端页面,它的购物车内容都是一样的,这样的效果十分不合理,并且不正常。所以我们应该动态获取我们用户登录的userId才正确。
9订单确认页面跳转
订单业务说明
说明:http://www.jt.com/order/create.html 当用户点击提交订单时.应该跳转到订单确认页面.之后添加收件人信息.
订单确认页面名称:order-cart.jsp(返回值为String,我们定义好了一个页面来展现数据)
9.1 编辑jt-web的OrderController
@RequestMapping("/order")
@Controller
public class OrderController {
@Reference(check = false)
private DubboCartService dubboCartService;
/**
* 跳转到订单确认页面 http://www.jt.com/order/create.html
* 业务逻辑: 根据userId,之后查询购物车记录信息.之后在页面中展现购物车数据.
* 页面取值: ${carts} 前端通过这个名字使用el表达式遍历数据,所以这个key的名字必须叫carts
*/
@RequestMapping("/create")
public String create(HttpServletRequest request,Model model) {
User user = (User) request.getAttribute("JT_USER");
long userId = user.getId();
//这个方法之前写过的,我们直接调用即可(我们根据用户的id再次查询购物车的信息)
List<Cart> cartList = dubboCartService.findCartListByUserId(userId);
model.addAttribute("carts", cartList);//key值必须为carts
return "order-cart";
}
}
9.2 订单业务结构表设计
9.3 导入订单pojo对象
说明:从资料中导入pojo对象 导入到jt-common中
9.4 创建JT-ORDER项目
3).添加依赖/ 继承 /插件
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.jt.vip</groupId>
<artifactId>jt</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>jt-order</artifactId>
<!--添加依赖 -->
<dependencies>
<dependency>
<groupId>com.jt.vip</groupId>
<artifactId>jt-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
<!--3.添加插件 -->
<!--负责项目打包 更新 maven操作相关的配置 必须添加 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
4).赋值jt-order的src文件即可
9.5 编辑jt-order 的YML配置文件
server:
port: 8095
servlet:
context-path: /
spring:
datasource:
#driver-class-name: com.mysql.jdbc.Driver
#如果需要项目发布,则数据库地址必须配置远程数据库
url: jdbc:mysql://127.0.0.1:3306/jtdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
username: root
password: root
#配置视图解析器
mvc:
view:
prefix: /WEB-INF/views/
suffix: .jsp
#mybatis-plush配置
mybatis-plus:
type-aliases-package: com.jt.pojo
mapper-locations: classpath:/mybatis/mappers/*.xml
configuration:
map-underscore-to-camel-case: true
#日志记录 输出数据库的日志信息.
logging:
config: classpath:logging-config.xml
level:
com.jt.mapper: debug
dubbo:
scan:
basePackages: com.jt #指定dubbo的包路径
application:
name: provider-order #指定服务名称(必须指定)
registry:
address: zookeeper://192.168.126.129:2181?backup=192.168.126.129:2182,192.168.126.129:2183
protocol: #指定协议
name: dubbo #使用dubbo协议(tcp-ip) web-controller直接调用sso-Service
port: 20883 #每个服务都应该有自己特定的端口
9.6 关于jt-order服务器的POJO主键的说明
说明:这个pojo写在的地方是jt-common中的
10 SpringMVC的页面与后端数据传递方式.
10.1 简单模式
html页面信息:
<html>
<input id="name" name="name"/>
<input id="age" name="age"/>
</html>
UserController后端接收:
利用request对象的取值和赋值的方式实现操作
public xxxxx saveUser(String name,Integer age){ //这里的参数就是前端页面的name属性,必须一致
}
10.2 利用对象的方式封装
<html>
<input id="name" name="name"/>
<input id="age" name="age"/>
</html>
UserController后端接收:
1.利用request对象取赋值操作. 2.利用对象的set方法,为对象的属性赋值.
public xxxxx saveUser(User user){ //封装一个pojo的User对象,里面的属性就是和name属性是一致的
}
10.3 为对象的引入赋值
说明:如果按照下列的方式提交.,则会出现重名提交的问题.
解决方案: 可以利用为对象的引用赋值的操作.
<html>
<input id="name" name="name"/>
<input id="age" name="age"/>
<input id="name" name="dog.name" value="哮天犬"/>
<input id="age" name="dog.age" value="3"/>
</html>
//这里面人和狗的属性name一致怎么区分解决呢????
public class User{
private String name;
private Integer age;
private Dog dog; //对象的引用. (这一行是关键的解释,我们再User对象引入Dog对象,对象的引用)
}
public class Dog{
private String name;
private Integer age;
}
//利用;User对象实现了User的数据与Dog的数据的获取.
public xxxxx saveUser(User user){
}
10.4 京淘项目的应用案例
标识POJO 对象封装方式.
页面HTML标记
10.5 请求url页面分析
请求js分析
10.6 编辑jt-web的OrderController
/**
* 1.url地址:http://www.jt.com/order/submit
* 2.参数 form表单提交
* 3.返回值 SysResult对象 并且包含orderId数据(因为前端js需要用到,所以我们要返回json数据)
* @return
*/
@RequestMapping("/submit")
@ResponseBody
public SysResult saveOrder(Order order,HttpServletRequest request) {
User user = (User) request.getAttribute("JT_USER");
Long userId = user.getId();
order.setUserId(userId); //将userId进行赋值操作.
String orderId = orderService.saveOrder(order);
if(StringUtils.isEmpty(orderId)) {
//说明:后端服务器异常
return SysResult.fail();
}
return SysResult.success(orderId);
}
10.7 编辑jt-order里的orderServiceImpl
@Transactional //控制事务.
@Override
public String saveOrder(Order order) {
//orderId订单id号,我们自己重新设定好一个订好单号生成的规则,前面""为了不要做加法计算,做拼接
String orderId = ""+order.getUserId() + System.currentTimeMillis();
Date date = new Date();
//1.实现订单入库
order.setOrderId(orderId)
//刚进入的订单必定状态为1 未付款
.setStatus(1) //未付款 '状态:1、未付款2、已付款3、未发货4、已发货5、交易成功6、交易关闭',
.setCreated(date)
.setUpdated(date);
orderMapper.insert(order);
System.out.println("订单入库成功!!!");
//因为我们要一个操作三表入库,Order order的pojo中是我们的对象的引用,所以可以这么操作
//2.订单物流入库
OrderShipping orderShipping = order.getOrderShipping();
orderShipping.setOrderId(orderId)
.setCreated(date)
.setUpdated(date);
orderShippingMapper.insert(orderShipping);
System.out.println("订单物流入库成功!!!!");
//我应该知道我们Order里面的pojo对象的返回值就是List<OrderItem>,所以这里也一样
//3.订单商品入库
List<OrderItem> list = order.getOrderItems();
for (OrderItem orderItem : list) {
orderItem.setOrderId(orderId)
.setCreated(date)
.setUpdated(date);
orderItemMapper.insert(orderItem);
}
System.out.println("订单商品入库成功!!!!");
return orderId;
}
10.8 订单查询操作
业务说明
说明:当订单提交成功之后 需要展现订单成功页面
10.9 编辑jt-web的OrderController
/**
* 请求URL地质:http://www.jt.com/order/success.html?id=111597912501409
* 参数 id 根据id去查询出订单的信息
* 返回值 String 是一个页面
* 获取order对象信息 ${order.orderId}
*/
@RequestMapping("/success")
public String findOrderById(String id,Model model){ //这个id实则是订单的id
Order order = dubboOrderService.findOrderById(id); //根据id信息查询出订单的所有信息
model.addAttribute("order",order);
return "success"; //success.jsp页面
}
10.10 编辑jt-order的OrderServiceImpl
@Autowired
private OrderMapper orderMapper;
@Autowired
private OrderShippingMapper orderShippingMapper;
@Autowired
private OrderItemMapper orderItemMapper;
@Override
public Order findOrderById(String orderId) {
/**
* 首先这个操作我们明白几个关系
* 1.订单orderId是订单表和订单物流表的主键可以根据主键查询出所有信息
* 2.订单商品表的主键不是orderId,所以差查询出数据是需要通过条件构造器的
*/
Order order = orderMapper.selectById(orderId);
OrderShipping orderShipping = orderShippingMapper.selectById(orderId);
QueryWrapper<OrderItem> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("order_id",orderId );
List<OrderItem> orderItem = orderItemMapper.selectList(queryWrapper);
return order.setOrderShipping(orderShipping).setOrderItems(orderItem);
//因为返回的值Order对象,但是我们需要对三张表的数据都要查出来,所以要把数据set进去order对象
}
10.11 页面最终效果
11 定时任务实现
11 定时业务说明
如果用户下单之后,30分钟之后如果还没有完成支付功能.则应该将订单的state 由1改为6
如何实现:
思路1: 在订单成功页面添加时钟. 规定三十分钟之后订单超时,之后发起ajax请求 设定订单状态!!! (表象) 糊弄鬼的 除非引入消息队列机制.
思路2: 在数据库中添加一个超时事件 如果数据库发现时间已经到了超时时间,则触发sql语句. 更新!!!
思路3: 准备一个单独的线程 该线程每隔1分钟 查询一次数据库是否有超时记录.如果有则批量修改.
8.2 Quartz介绍
Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用。Quartz可以用来创建简单或为运行十个,百个,甚至是好几万个Jobs这样复杂的程序。Jobs可以做成标准的Java组件或 EJBs。Quartz的最新版本为Quartz 2.3.2。
理解: Quartz 可以开启线程周期性的执行某个任务.
组件3大部分
1.调度器 2.负责管理/运行任务的组件
2.触发器 3. 当调度器发现程序要执行时,则通过触发器去实现页面调用.
3.job/jobDetail 1.自定义任务,之后需要设定任务的执行时间,需要将job封装为jobDetail.主要的目的提供 API.
8.3 导入jar包
<!--添加Quartz的支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
下面的资料全是导入进来的
8.4编写jt-order配置类
@Configuration
public class OrderQuartzConfig {
//定义任务详情 JobDetail封装job任务.
@Bean
public JobDetail orderjobDetail() {
//指定job的名称和持久化保存任务
return JobBuilder
.newJob(OrderQuartz.class) //1.任务的类型
.withIdentity("orderQuartz") //2.任务名称
.storeDurably()
.build();
}
//springBoot会实现对象的自动装配 开箱即用的功能.
//定义触发器 告知将来执行的任务是谁?
@Bean
public Trigger orderTrigger() {
/*SimpleScheduleBuilder builder = SimpleScheduleBuilder.simpleSchedule()
.withIntervalInMinutes(1) //定义时间周期
.repeatForever();*/
//设定程序1分钟执行一次...
CronScheduleBuilder scheduleBuilder
= CronScheduleBuilder.cronSchedule("0 0/1 * * * ?");
return TriggerBuilder
.newTrigger()
.forJob(orderjobDetail()) //执行什么样的任务 *
.withIdentity("orderQuartz") //任务名称 *
.withSchedule(scheduleBuilder).build(); //什么时候执行
}
}
8.5 编辑定时任务处理类
//准备订单定时任务
@Component
public class OrderQuartz extends QuartzJobBean{
@Autowired
private OrderMapper orderMapper;
/**
* 删除30分钟之后没有支付的订单,将状态由1改为6
* 业务实现:
* 如何判断超时: create < now -30分钟
* 1.sql update tb_order set status=6,updated=now() where status = 1 and
*/
@Override
@Transactional
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
//1.计算超时时间
Calendar calendar = Calendar.getInstance(); //获取当前时间
calendar.add(Calendar.MINUTE, -30);
Date timeOut = calendar.getTime(); //获取时间
//2.实现数据库更新
orderMapper.updateStatus(timeOut);
System.out.println("定时任务执行成!!!!!!"); //1分钟执行一次
}
}