Authored by yoho
查询用户优惠券可用不可用列表接口业务逻辑
-----
### 1.gateway入口
接口地址:method=app.Shopping.listCoupon,需要参数:int uid,required = true.
Postman模拟URL示例:http://192.168.102.205:8080/gateway?method=app.Shopping.listCoupon&uid=8041834&debug=XYZ
类名:com.yoho.gateway.controller.order.shopping.ShoppingController,方法名:listCoupon
### 2.返回
通过serviceCaller调用order模块的order.listCoupon服务进行具体的业务操作。
返回两个优惠券list:usable_coupons和unusable_coupons
### 3.调用order.listCoupon服务的具体过程
#### 3.1order模块的服务入口
服务入口:com.yoho.yhorder.shopping.restapi.ShoppingCouponController的listCoupon方法。
postman模拟URL示例:http://192.168.102.205:8084/order/shopping/listCoupon,post请求,body:{"uid":8041834}
方法中调用ShoppingCouponService的listCoupon方法
返回ShoppingCouponListResponse对象
#### 3.2listCoupon的执行过程
#### 3.2.1调用chargeUsableCoupon方法,返回ChargeContext对象,结果放在ChargeTotal中
#### 3.2.1.1生成算费参数ChargeParam,
设置CartType=ORDINARY_CART_TYPE,
ChargeType=LISTCOUPONO_CHARGE_TYPE
是否需要计算运费:不需要
是否需要查询有货币:不需要
是否需要查询红包:不需要
是否需要校验货到付款:不需要
##### 3.2.1.2chargeContextFactory构建ChargeContext算费上下文(具体见算费上下文ChargeContext构建具体过程.md文档)
##### 3.2.1.3chargerService.charge进行购物车算费(具体见购物车算费.md文档的LISTCOUPONO_CHARGE_TYPE计算优惠券可用不可用部分)
##### 3.2.2将ChargeContext.ChargeTotal中的结果包装到ShoppingCouponListResponse中返回
... ...
>#product项目#
>
>文档中只涉及数据库的相关查询逻辑,并未包含缓存的相关内容,具体请查询业务代码。
>
>##CommentController 评论##
>###查询商品的最新评论
>1、**yh\_comment** <br>
> `select * from comments where element_id=? and comment_type=1 and status=1 order by create_time desc limit 1`
>
>###分页查询商品评论
>1、**yh\_comment** <br>
>`select * from comments where element_id=? comment_type=1 and status=1 order by create_time desc limit ?,?` <br>
>2、**yoho\_passport** 查询用户昵称<br>
>`select * from user_base where uid = #{uid} limit 1`
>
>
>##ConsultController 咨询
>###查询咨询总数
>1、**yh_passport**
>
>`select count(1) from consult where product_id =? and answer_user_id > 0`
>
>###查询商品最新咨询
>1、**yh_passport**<br>
>` select * from consult where product_id =? and answer_user_id > 0 order by id DESC limit 1`
>
>###分页查询咨询
>1、**yh_passport**<br>
>`select * from consult where product_id =? and answer_user_id > 0 order by id DESC limit ?,?`
>
>###查询是咨询列表否点赞喜欢、有用
>1、数据存储在redis中
><table>
> <tr>
> <td>"yh:like:id:" + consultId</td>
> <td>咨询被喜欢次数</td>
> </tr>
> <tr>
> <td>"yh:useful:id:" + consultId</td>
> <td>咨询被用户点有用次数</td>
> </tr>
> <tr>
> <td>"yh:like:id:" + consultId+":"+uid</td>
> <td>该用户是否喜该咨询</td>
> </tr>
> <tr>
> <td>"yh:useful:id:" + consultId+":"+uid</td>
> <td>该用户是否对该咨询点有用</td>
> </tr>
></table>
>
>###新增咨询
>1、**yh\_shops** 查询product是否存在根据商品id。<br>
>`select * from product where id=?`
>不存在直接抛异常,中断执行。
>
>2、**yh\_passport** 插入咨询表 <br>
> `insert into consult values()`
>
>###咨询点赞接口
>1、判断是否已经点赞 <br>
> 从redis中取:"yh:like:id:" + consultId+":"+uid 判断用户是否已经点赞,已经点赞直接返回。<br>
>2、增加redis中key为:"yh:like:id:" + consultId。 咨询的点赞的总数。<br>
>3、增加redis中key为:"yh:like:id:" + consultId+":"+uid的值为1,标示已点赞<br>
>4、**yh\_consult** 异步记录点赞表<br>
> a:`select count(1) from consult_praise_$(consultId%100) where where consult_id=? and uid=?` 判断是否已经点赞,已经点赞,直接返回 <br>
> b:`insert into consult_praise_$(consultId%100) (consult_id,uid,create_time) values(?,?,?)`
>
>
>###咨询有用接口
>1、判断用户是否已经点击有用<br>
> 从redis中取:"yh:useful:id:" + consultId+":"+uid 判断用户是否已经点击有用,已经点击则直接返回。<br>
>2、增加redis中key为:"yh:useful:id:" + consultId。咨询的有用总数。<br>
>3、增加redis中key为:"yh:useful:id:" + consultId+":"+uid;的值为1,标示该用户已经点击有用。<br>
>4、**yh\_consult** 异步记录点有用<br>
> a:`select count(1) from consult_useful_$(consultId%100) where consult_id=? and uid=?` 判断用户是否已经点击有用,已经点有用,直接返回<br>
> b:` insert into consult_useful_$(consultId%100) (consult_id,uid,create_time) values(?,?,?)` 插入用户点击有用表。
>
>##CouponsController
>###根据品牌Id查询品牌及优惠券相关信息
>1、缓存中查询品牌相关信息,品牌不存在直接返回。
>2、**yhb\_promotion** <br>
> `select coupons_id from brand_coupons where status=2 and brand_id =? order by update_time desc,create_time desc`
>3、**yhb\_promotion** <br>
> ` select * from coupons where id in ()`
>
>##KeywordsController
>###搜索关键词及平台记录接口
>1、异步插入表 `yh_shops.search_keywords` 表,平台编号
> <table>
> <tr><td>pc</td><td>1</td></tr>
> <tr><td>mobile</td><td>2</td></tr>
> <tr><td>h5</td><td>3</td></tr>
> <tr><td>ipad</td><td>4</td></tr>
> </table>
>
>
>##LimitProductController 限量商品Controller ##
>###查询已经发布的限量商品 ###
>1. 查询限量销售商品
>`select * from yh_shops.limit_product where 1=1 and status=1 and sale_time < UNIX_TIMESTAMP()order by sale_time DESC`
>2. 查询限量销售商品关联商品
>`select * from yh_shops.limit_product_attach where product_id in()`
>3. 封装关联关系并缓存
>4. 根据商品skn(erp_product_id)查询商品ID
> `select * from yh_shops.product where erp_product_id in()` <br>
>5. 根据商品id查询商品价格
> `select * from yh_shops.product_price where product_id in()`
>6. 封装并返回结果
>
>### 查询热门发售的限量商品 ###
>1. 查询限量热门商品
> ` select * from yh_shops.limit_product where 1=1 and hotFlag=1 and status=1 order by order_by DESC`
> (备注:区别于已经发布的是限量商品查询条件)
>2. 查询限量销售商品关联商品
> `select * from yh_shops.limit_product_attach where product_id in()`
>(备注:limit_product_attach表中的product_id实际是limit_product表中的id,而不是商品表中的Id)
>3. 封装限量销售商品和附件的关联关系并缓存。
>4. 根据商品skn(erp_product_id)查询商品ID
> `select * from yh_shops.product where erp_product_id in()` <br>
>5. 根据商品id查询商品价格
> `select * from yh_shops.product_price where product_id in()`
>
>###查询即将发售的限量商品###
>1. 查询即将发售的限量商品
> `select * from yh_shops.limit_product where 1=1 and status=1 and showFlag=1 and sale_time > UNIX_TIMESTAMP() order by order_by DESC,sale_time ASC;`
>2. 查询限量销售商品关联商品
> `select * from yh_shops.limit_product_attach where product_id in()`
>(备注:limit_product_attach表中的product_id实际是limit_product表中的id,而不是商品表中的Id)
>3. 封装限量销售商品和附件的关联关系并缓存。
>4. 根据商品skn(erp_product_id)查询商品ID
> `select * from yh_shops.product where erp_product_id in()` <br>
>5. 根据商品id查询商品价格
> `select * from yh_shops.product_price where product_id in()`
>
>###根据限量商品code获取限量商品详情###
>1. 根据商品code查询限量商品
> `select * from yh_shops.limit_product where code =? and status=1`
>
>2. 查询限量销售商品关联商品
> `select * from yh_shops.limit_product_attach where product_id in()`
>(备注:limit_product_attach表中的product_id实际是limit_product表中的id,而不是商品表中的Id)
>
>3. 封装限量销售商品和附件的关联关系并缓存。
>4. 根据商品skn(erp_product_id)查询商品ID
> `select * from yh_shops.product where erp_product_id in()` <br>
>5. 根据商品id查询商品价格
> `select * from yh_shops.product_price where product_id in()`
>
>###根据活动ID查询限量商品###
>1.根据活动ID查询限量商品
>`select * form yh_shops.limit_product where activityId=? and status=1`
>2. 查询限量销售商品关联商品
> `select * from yh_shops.limit_product_attach where product_id in()`
>(备注:limit_product_attach表中的product_id实际是limit_product表中的id,而不是商品表中的Id)
>
>
>###批量根据限量商品code获取限量商品详情###
>1. 根据商品code列表查询商品详情
>
> select * from yh_shops.limit_product where code in ()
>
>2. 查询限量商品关联的商品
> `select * from yh_shops.limit_product_attach where product_id in()`
>3. 封装限量销售商品和附件的关联关系并缓存。
>4. 根据商品skn(erp_product_id)查询商品ID
> `select * from yh_shops.product where erp_product_id in()` <br>
>5. 根据商品id查询商品价格
> `select * from yh_shops.product_price where product_id in()`
>###已经发售的商品总数###
>1. 查询已经发售的商品总数
>`select count(1) from yh_shops.limit_product where sale_time<UNIX_TIMESTAMP() and status=1`
>
>
>###热门发售商品的发售总数###
>1. 查询热门发售商品的发售总数
> `select count(1) from yh_shops.limit_product where hotFlag=1 and status=1`
>
>###即将发售的商品总数##
>1.查询即将发售的商品总数
>> `select count(1) from yh_shops.limit_product where sale_time>UNIX_TIMESTAMP() and status=1 and showFlag=1`
>
>###给后台提供的新增限量商品###
>
> 1. 校验限量商品是否存在
> `select * from yh_shops.limit_product where code=?`
> 2. 插入限量商品
> `insert into yh_shops.limit_product values();`
> 3.
>
#promotion模块
##CouponController
###发送优惠券 flag=1支持重复发送###
1. 检查用户是否已经领取过优惠券
> select count(1) from yhb_promotion.coupons_logs where uid=8038725 and coupon_id=11759;
2. 检查券是否存在
> select * from yhb_promotion.coupons where id= 11759;
3. 查询券类型是否存在
> select * from yhb_promotion.coupon_type ;
4. 判断yhb_promotion.coupons.custom_type 是否存在,存在需要校验用户的会员级别等信息
5. 查询一个可用的优惠券
> select * from yhb_promotion.coupons_sn where is_use="N" limit 1;
> select * from yhb_promotion.a_coupons_sn where is_use="N" limit 1;
> select * from yhb_promotion.b_coupons_sn where is_use="N" limit 1;
6. 增加券的领用记录(包括用户id,是否重复领用标记,券号等)
> insert into yhb_promotion.coupons_logs value();
7. 缓存中增加券的领用数量,并设置用户的领券状态
8. 记录用户领券同步表(用户id,券号等)
>insert into yhb_promotion.user_coupon_logs_sync value();
... ...
查询购物车接口业务逻辑
------
## 一、入口和返回
### 1.入口
method=app.Shopping.add,参数:uid、sale_channel、shopping_key,皆非必选参数,通常传uid或shopping_key
postman调用示例:
http://192.168.102.205:8080/gateway?method=app.Shopping.cart&uid=8041834&debug=XYZ
该接口由类com.yoho.gateway.controller.order.shopping的cart方法实现
@RequestMapping(params = "method=app.Shopping.cart")
@ResponseBody
public ApiResponse cart(HttpServletRequest httpServletRequest,\n
@RequestParam(value = "sale_channel", required = false) String saleChannel,
@RequestParam(value = "uid", required = false) Integer uid,\n
@RequestParam(value = "shopping_key", required = false) String shoppingKey)
### 2.返回
主要分两类数据,预售商品和普通商品,因这两类数据是分开结算的。每类数据包括:促销信息和结算费用,各商品详情等。
## 二、服务调用
该接口会调用order模块的cartShopping服务,该服务会实现具体的查询业务。而该服务具体执行过程中会调用商品和促销模块的相关服务
## 三、具体实现逻辑
1.进入gateway模块的com.yoho.gateway.controller.order.shopping.cart()方法
2.cart方法调用order.cartShopping服务
3.进入order模块的com.yoho.yhorder.shopping.restapi.ShoppingCartController.query()方法,该方法提供order.cartShopping服务
4.query方法调用com.yoho.yhorder.shopping.service.impl.ShoppingCartServiceImpl.query()方法。
5.再调用com.yoho.yhorder.shopping.service.impl.ShoppingCartQueryService.query()方法,
6.在ShoppingCartQueryService.query()方法中开始进行业务操作,先进行普通商品算费,再进行预售商品算费,然后将两类结果包装到ShoppingQueryResponse中返回。具体如下:
普通商品算费:
1)构建算费参数对象ChargeParam,设置CartType=ORDINARY_CART_TYPE、ChargeType=ORDINARY_CHARGE_TYPE。
2)生成算费上下文ChargeContext ordinaryChargeContext
3)调用ChargerService.charge()进行算费,算费结果在ChargeContext对象的ChargeTotal中
4)从ChargeContext中取出结果信息ShoppingChargeResult ordinaryChargeResult。
预售商品算费:
1)生成预售商品算费上下文ChargeContext preSaleChargeContext
2)构建预售商品算费对象(CartType=PRESALE_CART_TYPE、ChargeType=ADVANCE_CHARGE_TYPE)并设置到上下文preSaleChargeContext中,同时将商品列表(从ordinaryChargeContext获取)和用户信息保存上下文中。\n
3)调用ChargerService.charge()进行算费。
4)从preSaleChargeContext取出结果信息ShoppingChargeResult preSaleChargeResult
由ordinaryChargeResult和preSaleChargeResult构造结果ShoppingQueryResponse response,并返回。
### 在ShoppingCartQueryService.query()中涉及到两个公共的业务:构建算费上下文和算费。具体实现见后续文档
\ No newline at end of file
... ...
购物车算费过程
--------
### 算费结果在context对象的ChargeTotal中,调用ChargerService.charge(context).
### 1.docharge进行算费
#### 1.1对商品进行分类,
1.1.1 遍历ChargeContext中的chargeGoodsList,
首先判断是否下架,再根据是否是预售,结果存入preOffShelvesGoods或offShelvesGoods中。
根据库存信息,判断是否售罄,结果存入offShelvesGoods或soldOutGoods中。
根据chargeGoods信息,进行分类。(promotion id > 0, 说明是赠品或加价购商品,PromotionType=gift则是mainGoodsGift,否则是mainGoodsPriceGift。)
共分为:mainGoodsGift、mainGoodsPriceGift、advanceGoods、outletGoods、mainGoods。
如果购物车类型是预售,则mainGoods=advanceGoods(预售购物车只取mainGoods类别)
设置jit信息,goodlist中只要有一个是jit则,设置ChargeParam的IsJit为true。
#### 1.2进行初始化算费,调用InitCharge.charge方法,计算普通商品的总价和运费
遍历maingoods,
计算maingoods的购买数量goodsCount,
计算已选maingoods的购买数量selectedGoodsCount
计算已选且不是赠品和加价购商品的总价orderAmount
从ChargeParam中取得是否需要运费(默认不需要)以及是否加急等,计算运费(普通10元,加急加5元)。
将以上信息保存到ChargeTotal中并设置到chargeContext中
#### 1.3从ChargeParam中获取chargeType
#### 1.4根据chargeType执行对应的操作(分别是普通商品、预售产品、限购商品、计算优惠券可用不可用)
#### ORDINARY普通商品:
#### 调用doOrdinaryCharge(chargeContext)进行具体算费。
##### 1.4.1异步调用货到付款信息
##### 1.4.2mainGoodsVIP算费,调用vipCharge.charge方法。
如果用户每个月订单数量大于阈值,则不享受VIP的优惠,返回。
计算优惠信息,优惠后价格、优惠比例:
遍历mainGoodsList,特价商品不享受vip,获取商品的折扣信息,
1-正常折扣
根据用户等级获取折扣量进行计算。
2-统一9.5折
3-无折扣
4-统一vip价
5-vip自定价
根据用户等级进行计算
如果发生了优惠则设置good的Real_price、Real_vip_price、Real_vip_price、优惠比例、优惠金额Vip_discount_money
如果是选择的商品,将优惠总额加到total中VipCutdownAmount上。
##### 1.4.3计算outlets,调用outletsCharge.charge方法,
遍历OutletGoods,
取good的Real_price设置good的Real_price、Last_price
计算outletAmount总价。
如果总价outletAmount大于阈值,则享受9折优惠,遍历OutletGoods设置每个商品的新的价格,
并将优惠总额加到total中VipCutdownAmount上,同时在chargeTotal.PromotionFormulaList中加入outlet优惠信息。
##### 1.4.4计算促销,promotionCharge.charge方法,
获取所有促销信息,遍历促销信息,根据促销类型多态调用compute方法计算(具体计算比较复杂以后再详细描述)是否匹配优惠,如果匹配则设置good的相关信息。
##### 1.4.5计算优惠券
根据ChargeParam中的conponCode远程调用促销模块的信息取到优惠卷的信息,如果没有优惠码则不计算返回。
根据优惠券的信息与mainGoods进行匹配计算,如果匹配则设置good和chargeTotal的相关信息。
##### 1.4.6购物车计算
对普通商品、gift,priceGift,outlet分别计算费用,并累加存入total中。
##### 1.4.7根据优惠(优惠券、红包、运费等)计算最后价格
##### 1.4.8是否可货到付款判断
#### LISTCOUPONO_CHARGE_TYPE计算优惠券可用不可用:
#### 调用doListCouponCharge方法进行具体计算
##### 1.4.1异步调用促销模块的promotion.queryUserNoUsedCoupons服务获取用户当前可用的优惠券。
##### 1.4.2mainGoodsVIP算费,调用vipCharge.charge方法(具体同上)
##### 1.4.3计算outlets,调用outletsCharge.charge方法(具体同上)
##### 1.4.4计算促销,promotionCharge.charge方法(具体同上)
##### 1.4.5计算优惠券可用不可用usableCouponCharge.charge方法。
maingoods不为空才进行计算
获取用户可用优惠券列表,进行优惠券相对当前购物车计算可用不可用,具体过程如下。
遍历优惠券:
不在使用期限的优惠券不可用
遍历maingoods筛选出可用优惠券的商品
如果没有该优惠券的商品,该优惠券不可用,免邮券除外(免邮没有商品限制,只有金额和数量限制)
对符合该优惠券的商品进行使用条件的匹配计算,是否达到金额或购买数量,满足可用,不满足不可用
#### 预售产品、限购商品(未完待续)
\ No newline at end of file
... ...
算费上下文ChargeContext构建具体过程
----
### ChargeContext由ChargeContextFactory.build()构建,参数:Boolean selected、ChargeParam,
### ChargeContext需要构建的信息:
用户信息:vip、当月订单数、有货币、红包
购物车中商品的详情:价格,是否货到付款,是否下架,outlet、促销信息等
### 具体实现过程:
根据ChargeParam中的List<ShoppingItem>是否为空类进行不同的构建过程.
如果为空则调用buildByShoppingCart通过购物车构建。
如果不为空,则是立即购物,通过buildByShoppingItems进行构建。
### buildByShoppingCart构建过程如下:
1)通过ChargeParam中的uid和shoppingkey找到购物车对象ShoppingCart,为空则抛异常。
如果uid>0,通过uid到数据库表shoppingCart查询到物车对象ShoppingCart
如果uid<=0再通过shoppingkey,到数据库表shoppingCart查询到物车对象ShoppingCart。
2)根据ShoppingCart设置ChargeParam的属性(uid,shoppingkey,shoppingcarid)
因为uid或shoppingkey可能有一个为空,所以查询到购物车后统一更新这两个值
3)查询设置商品的详细信息List<ShoppingCartItems> shoppingCartGoodsList。
根据shoppingCartId到数据库表shopping_cart_items中
查询当前购物车中商品的信息List<ShoppingCartItems> goodsList。
从goodsList获取到skns、skus
根据skns到商品模块查询到商品的详细信息
然后将商品的各种信息(价格,是否货到付款,是否下架,outlet等保存到good的extmap中)
根据skns到促销模块查询商品的购买数量限制信息,并设置good的购买限制,
根据good的促销id查询促销信息(先到缓存,再到数据库中查询)
根据促销信息设置good的promotion_type和real_price、last_Price
promotion_type=Gift为赠品,real和last Price为0,
promotion_type=Needpaygift为加价购商品,real和last Price为add_cost,
4)根据ChargeParam和shoppingCartGoodsList调用doBuild()构建ChargeContext并返回
将shoppingCartGoodsList转换成List<ChargeGoods>(利用反射将map中的属性设置到ChargeGoods中)
并将List<ChargeGoods>保存到ChargeContext。
根据uid到用户模块和数据获取各种相关信息,并保持到UserInfo中。
调用users.getVipSimpleInfo服务,获取用户的vip信息。
到order表查询用户当月点单数。
调用users.getYohoCoin服务,获取用户的有货币信息,并转化成货币。
调用users.selectRedenvelopesCount服务,获取用户的红包,
将UserInfo保存到ChargeContext中。
### buildByShoppingItems构建过程:
1)首先将ChargeParam中的List<ShoppingItem>转换为List<ShoppingCartItems>
2)下面的处理和buildByShoppingCart中通过购物车ShoppingCartid到数据库查到
List<ShoppingCartItems> goodsList后的处理过程相同
\ No newline at end of file
... ...
# 前言
最近在做限定商品排队活动这块,把平台端的业务流稍微看了一下,整理如下,欢迎纠错
# 限定商品排队活动的平台端设置
* 首先在限购码管理中,新增一个限购码批次,审核通过以后会给指定的skn或者sku生成指定数量的限购码
* 在限定商品管理中,新建限定商品,页面必须填入刚才限购码管理新建的批次号(限购码批次号:限购码列表中可以查询)
* 在活动管理中,新建活动,页面必须填入同上的限购码批次号
# 限购码管理
## 限购码表(库 yh_promotion)
* limit_code 新增限购批次时候的情报
* 包含了,自动生成的批次号,限购批次的名称,需要生成的限购码的数量等
* limit_code_sku
* 本次新增表
* 和limit_code表中的limit_skn是一对多的关系
* 如果页面是正对skn生成限购码,那么这张表里无需插入记录
* 包含:xxx的sku,需要给该sku生成的限购码的数量
* limit_code_batchno
* 生成的限购码的情报,有没有使用,是给哪个sku生成的限购码
## 限购码的业务/数据流
1. 在平台上增加一个限购码批次 分为两种情况<br>
第一种:是给指定的`skn`生成限购码
在页面选择填入skn,以及指定的数量,点击保存,数据将会被保存到`limit_code`<br>
第二种:是给指定的`sku`生成限购码
在页面选择sku,首先skn此时会将该skn下的所有的sku都带出来,可以在各个sku后面填上数量,
点击保存,限购的基本情报将会被保存到`limit_code`中,其中页面上填入的skn和sku(sku的限购码数量)的一对多的关系,将会被保存到`limit_code_sku`
2. 在平台的限购码列表中,会看到刚才新建的限购批次<br>
此时还没有经过审核,选择刚才的限购码,点击`[通过]`
会根据上面两种情况分别生成限购码,插入到表limit_code_batchno
第一种:给`skn`生成限购码的场合,会根据页面填入的数量,生成指定数量的限购码,插入到表limit_code_batchno,此时sku的字段默认为空<br>
第二种:给`sku`生成限购码的场合,通过批次号可以查询limit_code_sku表得到xxx的sku数量a,yyy的sku数量b,生成指定数量的限购码a和b,插入到表limit_code_batchno,一共a+b条记录,其中a条记录的sku字段是xxx,b条记录的sku字段是yyy
# 限定商品管理
## 限定商品的表 (库 erp_product)
* limit_product 限定商品的表
* 活动id,限购码批次号,限定商品的名称,skn
* limit_product_attach 限定商品的附件情报表
## 限定商品的业务/数据流
1. 在平台端增加一个限定商品<br>
页面上需要填入限购码管理中生成的限购批次号,点击保存会插一条记录到limit_product中
* 其中表中字段productSkn是从限购码的表limit_code中根据限购码批次号同步过来的)
* 限定商品的附近信息保存在表limit_product_attach表中
* 同时会将该条限定商品情报同步到前台表中,包含以下:
* limit_product@yh_promotion中的数据同步到limit_product@yh_shops中
* limit_product_attach@yh_promotion中的数据同步到limit_product_attach@yh_shops中
根据skn,更新product@yh_shops表中的字段是否为限定商品更新成限定Y
# 抽奖活动管理
## 活动表 (库 yoho_activity)
* drawline_activity 活动的基本信息的表
* drawline_prize_setting 活动的奖项设置的表
* 通过平台页面设置的,白名单用户,真实的用户中奖数,马甲用户中奖数,奖品类型[比如限购码]
* drawline_activity_lucydraw 查询是否开奖
* drawline_user_queue 参加排队活动的表
* 当app上用户点了参加排队活动,相应的排队信息将被记录到这张表中
* 其中包含了用户的uid,用户类型[真实的用户还是马甲用户] 活动id, 排队号
* drawline_lucky_user 中奖用户
* 该活动的奖品发没发,站内信通知了吗?
* drawline_virtula_user 马甲用户信息的表
* 马甲用户由于不是真实的用户头像等信息就是从这张表中取得
# 抽奖活动的业务/数据流
1. 在平台上增加一个活动
这些数据将被插入到 下面的表
* [drawline_activity] : 设置活动的基本信息
* [drawline_prize_setting] : 页面需要填入限购码批次号,以及活动的奖项设置情报等等
2. 用户在app上通过[排队]按钮,将会将数据插入到 排队表 [drawline_user_queue]
3. 关于抽奖,根据表[drawline_prize_setting] 中设定的白名单,真实的用户中奖数,马甲用户中奖数
从表[drawline_user_queue]中,随机抽取指定数量的用户,将中奖用户信息插入到表[drawline_lucky_user]
对于中奖的用户,需要做两件事情:给用户发站内信,给用户发放奖品
关于给用户发放奖品的数据流如下:
* 根据活动id,查询limit_product@yh_shops(限定商品的表)和limit_produc@yh_shops(限定商品的附件情报表)
* 根据batchno,查询limit_code_batch@yhb_promotion,查询未使用的限购码
* 插入到表limit_code_recevice_record@yhb_promotion,其中插入的字段由uid,限购码,批次号,skn,限购商品code
更新limit_code_batch@yh_promotion中,限购码的状态
\ No newline at end of file
... ...
实现Repository, CrudRepository or PagingAndSortingRepository接口,
或在类上添加@RepositoryDefinition注解,
创建repository 实例:
1.XML配置
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/jpa
http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
<!-- repository-impl-postfix指定repository实现类的名称 == 接口名称 + impl,
repository-impl-postfix默认值就是impl
-->
<repositories base-package="com.acme.repositories" repository-impl-postfix="impl" />
</beans:beans>
2.自定义repository实现Repository,接口或其子接口,放在base-package包下
java代码中获取repository实例对象:(有点类似于ApplicationContext.getBean(...)感觉)
RepositoryFactorySupport factory = … // Instantiate factory here
UserRepository repository = factory.getRepository(UserRepository.class);
Spring Data 启用 Web支持:
1.采用Java Config方式:
@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
class WebConfiguration extends WebMvcConfigurationSupport { }
2.Spring XML配置方式:
<bean class="org.springframework.data.web.config.SpringDataWebConfiguration" />
Spring Data 默认web支持依赖的是SpringMVC:
congtroller中实现分页查询:
@Controller
@RequestMapping("/users")
public class UserController {
@Autowired UserRepository repository;
@RequestMapping
public String showUsers(Model model, Pageable pageable) {
model.addAttribute("users", repository.findAll(pageable));
return "users";
}
}
page:当前页码,从0开始
page.size:每页显示几条
page.sort:排序字段以及排序方式,默认升序asc
sort=age,desc
sort=age,asc
sort=age,desc&sort=id //多个排序字段
page.sort.dir
controller中分页:
注入Pageable参数
@RequestMapping
public String showUsers(Model model, Pageable pageable) {
Pageable对象默认的pageSize即每页显示条数为20,可以通过给Pageable添加@PageableDefaults(value="50")注解来修改
返回JSON数据:
HttpEntity<PagedResources<Person>> persons(Pageable pageable,
PagedResourcesAssembler assembler) {
Page<Person> persons = repository.findAll(pageable);
return new ResponseEntity<>(assembler.toResources(persons), HttpStatus.OK);
客户端可能会接受类似这样的JSON数据:
{ "links" : [ { "rel" : "next",
"href" : "http://localhost:8080/persons?page=1&size=20 }
],
"content" : [
… // 20 Person instances rendered here
],
"pageMetadata" : {
"size" : 20,
"totalElements" : 30,
"totalPages" : 2,
"number" : 0
}
}
原始对象到领域模型对象之间的转换器注册:
<mvc:annotation-driven conversion-service="conversionService" />
<bean class="org.springframework.data.repository.support.DomainClassConverter">
<constructor-arg ref="conversionService" />
</bean>
分页参数解析器注册:
1.Java Config方式:
@Configuration
public class WebConfig extends WebMvcConfigurationSupport {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new PageableHandlerArgumentResolver());
}
}
2.XML配置方式:
<bean class="….web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="customArgumentResolvers">
<list>
<bean class="org.springframework.data.web.PageableHandlerArgumentResolver" />
</list>
</property>
</bean>
spring-data的maven依赖:
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>1.2.4
</version>
</dependency>
Spring的官方Maven仓库:
<repositories>
<repository>
<id>spring-milestone</id>
<name>Spring Maven MILESTONE Repository</name>
<url>http://repo.springsource.org/libs-milestone</url>
</repository>
</repositories>
mongoDB的long4j配置:
log4j.category.org.springframework.data.document.mongodb=DEBUG
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %40.40c:%4L - %m%n
Spring-data操作MongoDB的简单示例:
MongoOperations mongoOps = new MongoTemplate(new Mongo(), "your-database-name");
mongoOps.insert(new Person("Joe", 34));
log.info(mongoOps.findOne(new Query(where("name").is("Joe")), Person.class));
mongoOps.dropCollection("person");
Mongo实例注册:
1.Java config方式:
public class AppConfig {
public @Bean Mongo mongo() throws UnknownHostException {
return new Mongo(ip, port);
}
}
2. Java config方式2:通过Spring的MongoFactoryBean实例获取
public class AppConfig {
public @Bean MongoFactoryBean mongo() {
MongoFactoryBean mongo = new MongoFactoryBean();
mongo.setHost("localhost");
mongo.setPort(27017);
return mongo;
}
}
然后通过工厂bean的getObject()获取Mongo对象
3.SPring XML配置方式:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mongo="http://www.springframework.org/schema/data/mongo"
xsi:schemaLocation=
"http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/data/mongo
http://www.springframework.org/schema/data/mongo/spring-mongo-1.0.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<!-- Default bean name is 'mongo' -->
<mongo:mongo host="localhost" port="27017"/>
</beans>
注意:必须引入上面的mongo命名空间
下面是有关mongo数据库相关参数详细配置示例:
<mongo:mongo host="localhost" port="27017">
<mongo:options connections-per-host="8"
threads-allowed-to-block-for-connection-multiplier="4"
connect-timeout="1000"
max-wait-time="1500}"
auto-connect-retry="true"
socket-keep-alive="true"
socket-timeout="1500"
slave-ok="true"
write-number="1"
write-timeout="0"
write-fsync="true"/>
</mongo:mongo/>
SimpleMongoDbFactory使用示例:
public class MongoApp {
private static final Log log = LogFactory.getLog(MongoApp.class);
public static void main(String[] args) throws Exception {
MongoOperations mongoOps = new MongoTemplate(new SimpleMongoDbFactory(new Mongo(), "database"));
mongoOps.insert(new Person("Joe", 34));
log.info(mongoOps.findOne(new Query(where("name").is("Joe")), Person.class));
mongoOps.dropCollection("person");
}
}
MongoDBFactory实例注册:
1. Java config方式:
@Configuration
public class MongoConfiguration {
public @Bean MongoDbFactory mongoDbFactory() throws Exception {
return new SimpleMongoDbFactory(new Mongo(), "your-database-name");
}
}
2.Spring-XML配置方式:
<context:property-placeholder location="classpath:/com/myapp/mongodb/config/mongo.properties"/>
<mongo:mongo host="${mongo.host}" port="${mongo.port}">
<mongo:options
connections-per-host="${mongo.connectionsPerHost}"
threads-allowed-to-block-for-connection-multiplier="${mongo.threadsAllowedToBlockForConnectionMultiplier}"
connect-timeout="${mongo.connectTimeout}"
max-wait-time="${mongo.maxWaitTime}"
auto-connect-retry="${mongo.autoConnectRetry}"
socket-keep-alive="${mongo.socketKeepAlive}"
socket-timeout="${mongo.socketTimeout}"
slave-ok="${mongo.slaveOk}"
write-number="1"
write-timeout="0"
write-fsync="true"/>
</mongo:mongo>
<mongo:db-factory dbname="database" mongo-ref="mongo"/>
<bean id="anotherMongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
<constructor-arg name="mongoDbFactory" ref="mongoDbFactory"/>
</bean>
spring-data的auditing配置:
1.Java Config方式:
@Configuration
@EnableMongoAuditing
class Config {
@Bean
public AuditorAware<AuditableUser> myAuditorProvider() {
return new AuditorAwareImpl();
}
}
2.Spring XML方式:
<mongo:auditing mapping-context-ref="customMappingContext" auditor-aware-ref="yourAuditorAwareImpl"/>
MongoTemplate模版类是线程安全的,MongoTemplate之于Mongo,类似于JDBCTemplate之于JDBC,都是Spring封装的工具类
MongoTemplate的所有方法抛出的异常都是unchecked异常,即不用强制用户try..catch,Spring做这样处理,是不想让代码被try-catch丑陋化
MongoTemplate实例注册:
1.Java Config方式:
@Configuration
public class AppConfig {
public @Bean Mongo mongo() throws Exception {
return new Mongo("localhost");
}
public @Bean MongoTemplate mongoTemplate() throws Exception {
return new MongoTemplate(mongo(), "mydatabase");
}
}
2.Spring-XML配置方式:
<bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
<constructor-arg ref="mongo"/>
<constructor-arg name="databaseName" value="your-database-name"/>
</bean>
WriteResultChecking Policy(MongoDB操作返回结果检测策略):
即当mongoDB数据库操作发生异常时,告诉Spring-data该如何处理异常,默认是不处理
WriteResultChecking Policy有如下枚举值:
LOG : 记录日志
EXCEPTION: 抛异常
NONE:什么也不做,默认值为NONE
mongoDB中若你insert一个object时未指定id,则默认生成一个_id Object,类似这样:
{"_id":{"objectId": "xxxxxxxxxxxxxxxxxxxxxxxxxx"}}
假定你有这样一个PO类
public class Person {
private int id;
private String name;
}
要实现id属性的映射,spring-data提供了@id注解,注意包路径是:org.springframework.data.annotation.Id
即:
@id
private int id;
默认只要你的PO类中有名称叫id属性就会自动映射到MongoDB的_id属性,如果不是叫id,那就必须用@id注解
如果你的id属性定义的是String类型,那么MappingMongoConverter类型转换器会首先尝试是否能够转换成
MongoDB中的ObjectId对象类型,若不能,则会直接使用String类型存储到MongoDB数据库
如果你的id属性定义的是Integer(或Long)类型,那么MappingMongoConverter类型转换器会首先尝试是否能够转换成MongoDB中的ObjectId对象类型,若不能转换,则MongoDB会自动添加_id属性,且未和PO类中任何属性映射
当你insert一个PO对象时,如果PO对象里有依赖另一个PO对象时,MongoDB会自动帮我们生成一个_class属性,标识出当前的JSON Object在Java中的class类型,用例子说明可能比较形象:
public class Sample {
Contact value;
}
public abstract class Contact { … }
public class Person extends Contact {
private int id;
private String name;
}
Sample sample = new Sample();
Person person = new Person();
person.setId(1);
person.setName("John");
sample.value = person;
mongoTemplate.save(sample);
{ "_class" : "com.acme.Sample",
"value" : { "_class" : "com.acme.Person","_id": {"objectId" : "1"},"name" : "john" }
}
默认MongoDB帮我们生成的_class属性值是java类的完整包路径,比如: com.xx.oo.Person
如果你想改变这种默认行为,你可以使用@TypeAlias注解
@TypeAlias("per")
class Person {
......
}
这样生成的_class属性可能是这样:
{"_class" : "per"}
Java对象里属性的数据类型和MongoDB里的数据类型之间的映射关系是通过MongoTypeMapper绑定的,
如果你自定义的Java对象类型不在 MongoTypeMapper 支持范围内,你可能需要实现此接口自己实现两种
类型之间的映射:
然后在Spring XML中注册你自己的类型映射器:
<mongo:mapping-converter type-mapper-ref="your-MongoTypeMapper-bean-name"/>
<bean name="your-MongoTypeMapper-bean-name" class="your-MongoTypeMapper-class"/>
MongoDB提供了insert和save两种保存java 对象到MongoDB数据库的方法:
Person p = new Person("Bob", 33);
mongoTemplate.insert(p);
insert和save的细微区别:
1. 当person对象没有指定id属性,则两者执行的操作没有区别,都是插入一条记录到MongDB的collection中,
那默认插到哪个Collection里呢,Spring-data默认插到你的Java类名首字母小写的collection中,
比如上面的mongoTemplate.insert(p);,那么实际默认就是插入到person中,我们知道MongoDB是会自动创建
collection的,当然spring-data的MongoDBTemplate类的insert也提供了insert(class,collectionName)的重载
第二个参数即满足你需要手工指定collection名称的需求,当然save也有同样的重载
2.当person对象指定了id属性,即person.setId(1);
那么insert会首先检查数据库是否存在相同id的数据,若存在则会抛异常
而save则会对id相同的数据执行更新操作
关于Spring-data提供的更新方法对MongoDB的 update的支持:
Spring-data的Update提供了如下方法来支持MongoDB的各种update selector(更新操作符)
对MongoDB的原生更新操作符不熟悉的,请参阅我写的MongoDB的常用命令.docx文档,里面有详细介绍,
spring-data只不过是用java对这些操作符进行封装下,其实最终还是要生成你在cmd命令窗口敲的那么命令,
在Java里我们当前希望是以面向对象的方式来操作MongoDB,
看到Update类里提供的那些方法的名字就一目了然是对应到MongoDB中哪个update selector:
updateFirst 更新符合匹配的第一个
updateMulti 更新符合匹配的多个
Update addToSet (String key, Object value)
Update inc (String key, Number inc)
Update pop (String key, Update.Position pos)
Update pull (String key, Object value)
Update pullAll (String key, Object[] values)
Update push (String key, Object value)
Update pushAll (String key, Object[] values)
太多了我就不一一贴了,什么意思干什么用的,只要你熟悉了Mon个DB的原生更新操作符就一目了然咯
介绍下MongoDBTemplate提供的findAndModify方法对MongoDB原生的findAndModify函数的支持:
findAndModify(Query query, Update update, FindAndModifyOptions options, Class<T> entityClass, String collectionName);
参数解释:
query: 过滤参数,类似于SQL里的where条件,这里可以用spring-date提供的Where对象以OO的方式来构建query表达式,可以直接使用JSON字符串的方式构建,如果你习惯了cmd下敲命令,那么这种以字符串方式构建查询参数可能比较适合你.示例如下:
BasicQuery query = new BasicQuery("{ age : { $lt : 50 }, accounts.balance : { $gt : 1000.00 }}");
List<Person> result = mongoTemplate.find(query, Person.class);
udate:即更新参数,如:
Update update = new Update().inc("age", 1); //表示age属性值加1
options:对findAndmodify行为参数设置,对应到MongoDB原生的findAndModify函数的new和remove,upsert等参数,如:
new FindAndModifyOptions().returnNew(true) //即表示new参数设置为true,new参数为true表示执行findAndModify返回更新后的新对象,默认为false返回的是更新前的旧对象
new FindAndModifyOptions().returnNew(true).upsert(true) //upsert参数设置为true表示当根据第一个query参数没有找到数据时,需要把第二个update参数对象插入到collection中,默认为false
new FindAndModifyOptions().remove(true) //即表示对根据第一个query参数匹配到的数据执行remove操作,即类似SQL里的delete from table where xxx,默认remove参数为false,
总结:findModify方法包含了更新,删除,插入等操作,到底执行哪个操作,由FindAndModifyOptions参数配置决定
entityClass:表示返回的DBObject应该映射到哪个Java Object
collectionName:即告诉Spring-data你要操作哪个collection
Spring data对MongoDB的map-reduce(类似于SQL里的存储过程)的支持:
mongoDB的mapReduce命令示例:
{ "_id" : ObjectId("4e5ff893c0277826074ec533"), "x" : [ "a", "b" ] }
{ "_id" : ObjectId("4e5ff893c0277826074ec534"), "x" : [ "b", "c" ] }
{ "_id" : ObjectId("4e5ff893c0277826074ec535"), "x" : [ "c", "d" ] }
db.orders.mapReduce(
function () {
for (var i = 0; i < this.x.length; i++) {
emit(this.x[i], 1);
}
},
function (key, values) {
var sum = 0;
for (var i = 0; i < values.length; i++)
sum += values[i];
return sum;
}
);
返回:
{ "_id" : "a", "value" : 1 }
{ "_id" : "b", "value" : 2 }
{ "_id" : "c", "value" : 2 }
{ "_id" : "d", "value" : 1 }
Spring-data API操作方式:
MapReduceResults<ValueObject> results = mongoTemplate.mapReduce("your-collection-name", "classpath:map.js", "classpath:reduce.js", ValueObject.class);
for (ValueObject valueObject : results) {
System.out.println(valueObject);
}
打印如下:
ValueObject [id=a, value=1.0]
ValueObject [id=b, value=2.0]
ValueObject [id=c, value=2.0]
ValueObject [id=d, value=1.0]
ValueObject是一个普通的JavaBean
public class ValueObject {
private String id;
private float value;
MongoTemplate的mapReduce方法参数说明:
第一个参数是你要操作的collection名称,
第二个参数是要执行的function,可以直接用字符串指定js的function,也可以通过classpath指定类路径下的一个js文件,第三个参数跟第二个参数类似,分别对应MongoDB中原生的mapReduce命令的两个function参数,
最后一个class类型表示数据库返回的DBObject类型自动转成java里的什么类型
MongoTemplated对MongoDB原生的group函数支持:
GroupByResults<XObject> results = mongoTemplate.group("your_collection-name",
GroupBy.key("分组属性").initialDocument("{ count: 0 }").reduceFunction("function(doc, prev) { prev.count += 1 }"), Your-XXOO-Object.class);
返回的JSON数据类似这样
{
"retval" : [ { "x" : 1.0 , "count" : 2.0} ,
{ "x" : 2.0 , "count" : 1.0} ,
{ "x" : 3.0 , "count" : 3.0} ] ,
"count" : 6.0 ,
"keys" : 3 ,
"ok" : 1.0
}
同理reduceFunction的参数也可以通过classpath引入一个外部的js文件,如:classpath:com/xx/oo/abc.js
即表示reduceFunction里的function参数从外部的js文件读取,当你的function内容很长,在程序里拼接显得不利于以后维护,这是外部引入就觉得尤为重要.
Spring-data对MongoDB的aggregate函数支持:
Aggregation aggregation = newAggregation(
pipelineOP1(),
pipelineOP2(),
pipelineOPn()
);
AggregationResults<OutputType> results = mongoTemplate.aggregate(aggregation,"your-collection-name", XXOO.class);
List<OutputType> mappedResult = results.getMappedResults();
Aggregation类的具体构建请参看API文档
Spring-data对$Projection 表达式的支持:
project("name", "netPrice") // {$project: {name: 1, netPrice: 1}}
project().and("foo").as("bar") // {$project: {bar: $foo}}
project("a","b").and("foo").as("bar") // {$project: {a: 1, b: 1, bar: $foo}}
下面是官方提供的6个有关Aggregate聚合统计方面的示例:
class TagCount {
String tag;
int n;
}
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
Aggregation agg = newAggregation(
project("tags"),
unwind("tags"),
group("tags").count().as("n"),
project("n").and("tag").previousOperation(),
sort(DESC, "n")
);
AggregationResults<TagCount> results = mongoTemplate.aggregate(agg, "tags", TagCount.class);
List<TagCount> tagCount = results.getMappedResults();
********************************华丽的分割线**************************************************
class ZipInfo {
String id;
String city;
String state;
@Field("pop") int population;
@Field("loc") double[] location;
}
class City {
String name;
int population;
}
class ZipInfoStats {
String id;
String state;
City biggestCity;
City smallestCity;
}
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
TypedAggregation<ZipInfo> aggregation = newAggregation(ZipInfo.class,
group("state", "city")
.sum("population").as("pop"),
sort(ASC, "pop", "state", "city"),
group("state")
.last("city").as("biggestCity")
.last("pop").as("biggestPop")
.first("city").as("smallestCity")
.first("pop").as("smallestPop"),
project()
.and("state").previousOperation()
.and("biggestCity")
.nested(bind("name", "biggestCity").and("population", "biggestPop"))
.and("smallestCity")
.nested(bind("name", "smallestCity").and("population", "smallestPop")),
sort(ASC, "state")
);
AggregationResults<ZipInfoStats> result = mongoTemplate.aggregate(aggregation, ZipInfoStats.class);
ZipInfoStats firstZipInfoStats = result.getMappedResults().get(0);
********************************华丽的分割线**************************************************
class StateStats {
@Id String id;
String state;
@Field("totalPop") int totalPopulation;
}
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
TypedAggregation<ZipInfo> agg = newAggregation(ZipInfo.class,
group("state").sum("population").as("totalPop"),
sort(ASC, previousOperation(), "totalPop"),
match(where("totalPop").gte(10 * 1000 * 1000))
);
AggregationResults<StateStats> result = mongoTemplate.aggregate(agg, StateStats.class);
List<StateStats> stateStatsList = result.getMappedResults();
********************************华丽的分割线**************************************************
class Product {
String id;
String name;
double netPrice;
int spaceUnits;
}
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
TypedAggregation<Product> agg = newAggregation(Product.class,
project("name", "netPrice")
.and("netPrice").plus(1).as("netPricePlus1")
.and("netPrice").minus(1).as("netPriceMinus1")
.and("netPrice").multiply(1.19).as("grossPrice")
.and("netPrice").divide(2).as("netPriceDiv2")
.and("spaceUnits").mod(2).as("spaceUnitsMod2")
);
AggregationResults<DBObject> result = mongoTemplate.aggregate(agg, DBObject.class);
List<DBObject> resultList = result.getMappedResults();
********************************华丽的分割线**************************************************
class Product {
String id;
String name;
double netPrice;
int spaceUnits;
}
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
TypedAggregation<Product> agg = newAggregation(Product.class,
project("name", "netPrice")
.andExpression("netPrice + 1").as("netPricePlus1")
.andExpression("netPrice - 1").as("netPriceMinus1")
.andExpression("netPrice / 2").as("netPriceDiv2")
.andExpression("netPrice * 1.19").as("grossPrice")
.andExpression("spaceUnits % 2").as("spaceUnitsMod2")
.andExpression("(netPrice * 0.8 + 1.2) * 1.19").as("grossPriceIncludingDiscountAndCharge")
);
AggregationResults<DBObject> result = mongoTemplate.aggregate(agg, DBObject.class);
List<DBObject> resultList = result.getMappedResults();
********************************华丽的分割线**************************************************
class Product {
String id;
String name;
double netPrice;
int spaceUnits;
}
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
double shippingCosts = 1.2;
TypedAggregation<Product> agg = newAggregation(Product.class,
project("name", "netPrice")
.andExpression("(netPrice * (1-discountRate) + [0]) * (1+taxRate)", shippingCosts).as("salesPrice")
);
AggregationResults<DBObject> result = mongoTemplate.aggregate(agg, DBObject.class);
List<DBObject> resultList = result.getMappedResults();
自定义DBObject与自定义PO之间的类型转换器:
public class PersonWriteConverter implements Converter<Person, DBObject> {
public DBObject convert(Person source) {
DBObject dbo = new BasicDBObject();
dbo.put("_id", source.getId());
dbo.put("name", source.getFirstName());
dbo.put("age", source.getAge());
return dbo;
}
}
自定义转换器注册:
<mongo:mapping-converter>
<mongo:custom-converters>
<mongo:converter ref="writeConverter"/>
</mongo:custom-converters>
</mongo:mapping-converter>
为collection创建索引:
mongoTemplate.indexOps(Person.class).ensureIndex(new Index().on("name",Order.ASCENDING));
查询索引:
template.indexOps(Person.class).ensureIndex(new Index().on("age", Order.DESCENDING).unique(Duplicates.DROP));
List<IndexInfo> indexInfoList = template.indexOps(Person.class).getIndexInfo();
Duplicates:表示重复索引的处理策略,drop表示重复的删除
MongoTemplate也封装了MongoDB-java驱动中Mongo.runCommand()方法:
CommandResult executeCommand (DBObject command)
CommandResult executeCommand (String jsonCommand)
Spring-data为Mongo的生命周期设计了事件监听器:AbstractMongoEventListener:
可以在mongo的每个生命周期开始或结束时刻,动态嵌入一段代码:
事件有:
onBeforeConvert java对象转成成MongoDB中的DBObject之前
onBeforeSave 在insert之前
onAfterSave insert之后
onAfterConvert java对象转成成MongoDB中的DBObject之后
Spring配置文件中注册MongoTemplate:
<?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:mongo="http://www.springframework.org/schema/data/mongo"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/data/mongo
http://www.springframework.org/schema/data/mongo/spring-mongo-1.0.xsd">
<mongo:mongo id="mongo" />
<bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
<constructor-arg ref="mongo" />
<constructor-arg value="databaseName" />
</bean>
<mongo:repositories base-package="com.acme.*.repositories" />
</beans>
注意:mongo命名空间必须要引入
用方法名语义来代替接口实现:
GreaterThan findByAgeGreaterThan(int age) {"age" : {"$gt" : age}}
LessThan findByAgeLessThan(int age) {"age" : {"$lt" : age}}
Between findByAgeBetween(int from, int to) {"age" : {"$gt" : from, "$lt" : to}}
IsNotNull, NotNull findByFirstnameNotNull() {"age" : {"$ne" : null}}
IsNull, Null findByFirstnameNull() {"age" : null}
Like findByFirstnameLike(String name) {"age" : age} ( age as regex)
Regex findByFirstnameRegex(String firstname) {"firstname" : {"$regex" : firstname }}
(No keyword) findByFirstname(String name) {"age" : name}
Not findByFirstnameNot(String name) {"age" : {"$ne" : name}}
Near findByLocationNear(Point point) {"location" : {"$near" : [x,y]}}
Within findByLocationWithin(Circle circle) {"location" : {"$within" : {"$center" : [ [x, y], distance]}}}
Within findByLocationWithin(Box box) {"location" : {"$within" : {"$box" : [ [x1, y1], x2, y2]}}}True
IsTrue, True findByActiveIsTrue() {"active" : true}
IsFalse, False findByActiveIsFalse() {"active" : false}
Exists findByLocationExists(boolean exists) {"location" : {"$exists" : exists }}
Spring-data对Log4J的支持:
配置样例:
log4j.rootCategory=INFO, stdout
log4j.appender.stdout=org.springframework.data.document.mongodb.log4j.MongoLog4jAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n
log4j.appender.stdout.host = localhost
log4j.appender.stdout.port = 27017
log4j.appender.stdout.database = logs
log4j.appender.stdout.collectionPattern = %X{year}%X{month}
log4j.appender.stdout.applicationId = my.application
log4j.appender.stdout.warnOrHigherWriteConcern = FSYNC_SAFE
log4j.category.org.apache.activemq=ERROR
log4j.category.org.springframework.batch=DEBUG
log4j.category.org.springframework.data.document.mongodb=DEBUG
log4j.category.org.springframework.transaction=INFO
... ...
#zookeeper 实践
Zookeeper,一种分布式应用的协作服务,是Google的Chubby一个开源的实现,是Hadoop的分布式协调服务,它包含一个简单的原语集,应用于
分布式应用的协作服务,使得分布式应用可以基于这些接口实现诸如同步、配置维护和分集群或者命名的服务。
zookeeper是一个由多个service组成的集群,一个leader,多个follower,每个server保存一份数据部分,全局数据一致,分布式读写,更新
请求转发由leader实施.更新请求顺序进行,来自同一个client的更新请求按其发送顺序依次执行,数据更新原子性,一次数据更新要么成功,
要么失败,全局唯一数据试图,client无论连接到哪个server,数据试图是一致的.
##为什么要用zookeeper
大部分分布式应用需要一个主控、协调器或控制器来管理物理分布的子进程(如资源、任务分配等),目前,大部分应用需要开发私有的协调程序,
缺乏一个通用的机制.协调程序的反复编写浪费,且难以形成通用、伸缩性好的协调器,ZooKeeper:提供通用的分布式锁服务,用以协调分布式应用
##zookeeper工作原理
zookeeper的核心是原子广播,这个机制保证了各个server之间的同步,实现这个机制的协议叫做Zab协议.Zab协议有两种模式,他们分别是恢复
模式和广播模式.
当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导着被选举出来,且大多数server都完成了和leader的状态同步后,恢复模式就结
束了.状态同步保证了leader和server具有相同的系统状态.
一旦leader已经和多数的follower进行了状态同步后,他就可以开始广播消息了,即进入广播状态.这时候当一个server加入zookeeper服务
中,它会在恢复模式下启动,发下leader,并和leader进行状态同步,待到同步结束,它也参与广播消息.
*注意*
广播模式需要保证proposal被按顺序处理,因此zk采用了递增的事务id号(zxid)来保证.所有的提议(proposal)都在被提出的时候加上zxid.
实现中zxid是一个64为的数字,它高32位是epoch用来标识leader关系是否改变,每次一个leader被选出来,它都会有一个新的epoch.低32位是
个递增计数.
##leader选举
每个Server启动以后都询问其它的Server它要投票给谁,对于其他server的询问,server每次根据自己的状态都回复自己推荐的leader的id和
上一次处理事务的zxid(系统启动时每个server都会推荐自己),收到所有Server回复以后,就计算出zxid最大的哪个Server,并将这Server
相关信息设置成下一次要投票的Server.计算这过程中获得票数最多的的sever为获胜者,如果获胜者的票数超过半数,则改server被选leader
.否则,继续这个过程,直到leader被选举出来.leader就会开始等待server连接,Follower连接leader,将最大的zxid发送给leader,Leader
根据follower的zxid确定同步点,完成同步后通知follower 已经成为uptodate状态,Follower收到uptodate消息后,又可以重新接client
的请求进行服务了.
##zookeeper的数据模型
每个节点在zookeeper中叫做znode,并且其有一个唯一的路径标识节点Znode可以包含数据和子节点,但是EPHEMERAL类型的节点不能有子节点
Znode中的数据可以有多个版本,比如某一个路径下存有多个数据版本,那么查询这个路径下的数据就需要带上版本客户端应用可以在节点上设置
监视器,节点不支持部分读写,而是一次性完整读写Zoopkeeper 提供了一套很好的分布式集群管理的机制,就是它这种基于层次型的目录树的数
据结构,并对树中的节点进行有效管理,从而可以设计出多种多样的分布式的数据管理模型.
##zookeeper的节点
znode有两种类型,短暂的(ephemeral)和持久的(persistent)
znode的类型在创建时确定并且之后不能再修改,短暂znode的客户端会话结时,zookeeper会将该短暂znode删除,短暂znode不可以有子节点,
持久znode不依赖于客户端会话,只有当客户端明确要删除该持久znode时才会被删除.
znode有四种形式的目录节点PERSISTENT、PERSISTENT_SEQUENTIAL、EPHEMERAL、EPHEMERAL_SEQUENTIAL.
znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个功能是zookeeper
对于应用最重要的特性,通过这个特性可以实现的功能包括配置的集中管理,集群管理,分布式锁等等.
##zookeeper的角色
领导者(leader),负责进行投票的发起和决议,更新系统状态.
学习者(learner),包括跟随者(follower)和观察者(observer).follower用于接受客户端请求并想客户端返回结果,在选主过程中参与
投票,observer可以接受客户端连接,将写请求转发给leader,但observer不参加投票过程,只同步leader的状态,observer的目的是为了扩展
系统,提高读取速度.
客户端(client),请求发起方.
watcher
watcher 在 zooKeeper 是一个核心功能,Watcher 可以监控目录节点的数据变化以及子目录的变化,一旦这些状态发生变化,服务器就会通知
所有设置在这个目录节点上的 Watcher,从而每个客户端都很快知道它所关注的目录节点的状态发生变化,而做出相应的反应可以设置观察的操
作:exists,getChildren,getData可以触发观察的操作:create,delete,setDataznode以某种方式发生变化时,“观察”(watch)机制可
以让客户端得到通知.可以针对zooKeeper服务的“操作”来设置观察,该服务的其他 操作可以触发观察.比如,客户端可以对某个客户端调exists
操作,同时在它上面设置一个观察,如果此时这个znode不存在,则exists返回 false,如果一段时间之后,这个znode被其他客户端创建,则这个
观察会被触发,之前的那个客户端就会得到通知.
\ No newline at end of file
... ...
... ... @@ -130,5 +130,5 @@ PS:不推荐很长时间一次性提交太多的代码,尽量将自己的代码以完整体量方式多次提交,
PS:logger.debug这种不打印日志,logger.info是打印日志的,这种日志在《debug-log.log》文件中,如果有异常错误在《warn.log》文件中。
## 灰度环境中验证接口(还未开始,带完成后继续完善)
## 灰度环境中验证接口(还未开始,带完成后继续完善 )
... ...
技术部--资源位模型
------------
## 资源位物理模型
资源位
resources
资源位预发布放在resources.publish_time当中,即一个资源位code可能对应多个资源位实体,多出来的就是预发布的数据。
资源位楼层
resources_content
资源位楼层数据
resources_content_data
模型图见
![](PIC/resource_phy_model.png)
## 资源位业务模型
资源位业务模型图见
![](PIC/resource_business_model.png)
... ...
# Nginx入门&配置
## windows安装
官网下载nginx-1.8.1.zip,解压
## 启动Nginx
默认配置在conf/nginx.conf
```
启动 nginx.exe
```
浏览器访问localhost:80/ 成功显示 Welcome to nginx!
### 常用命令
| 名称 | 命令 |
| :-- | :-- |
| 关闭 | nginx -s stop |
| 重启 | nginx -s reopen |
| 重新load | nginx -s reload |
## 配置测试APP 访问dev环境
```
1.配置Fiddler与手机网络代理,让手机通过自己机器间接访问服务
```
2.配置hosts 添加域名 127.0.0.1 testapi.yoho.cn
```
3.配置nginx(nginx.conf) 添加server节点:
```json
server
{
#testapi的默认访问端口
listen 28078;
server_name localhost;
location / {
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
#devapi访问路径
proxy_pass http://devapi.yoho.cn:58078;
}
#访问路径
access_log logs/dev_access.log;
}
```
4.启动nginx,手机app访问转到dev环境
\ No newline at end of file
... ...
个人理解的 git 流程:
![](pic/yoho_git.png)
... ...
# 购物车模块
## 查询处理流程
未完成
![](pic/shoppingCart_cart_version_1.0.png)
\ No newline at end of file
... ...