乐优商城--服务(三) : 商品微服务(LyItemApplication)--后半部分
商品微服务--后半部分:
- 4.6 商品规格数据结构与商品表结构分析
- 4.7 实现商品规格参数管理
- 4.7.1 规格组查询
- 4.7.1.1 SpecGroup数据表
- 4.7.1.2 SpecGroup实体类
- 4.7.1.3 业务
- 4.7.1.3.1 web
- 4.7.1.3.1.1 页面分析
- 4.7.1.3.1.2 实现业务
注: 由于商品微服务内容太多,所以做了优化,拆分成两部分写。地址:商品微服务–前半部分
4.6 商品规格数据结构与商品表结构分析
内容偏多,重新写了一篇商品规格数据结构与商品表结构分析
4.7 实现商品规格参数管理
4.7.1 规格组查询
4.7.1.1 SpecGroup数据表
由于在商品规格数据结构与商品表结构分析中SpecGroup数据表已给出,这里就不再附一遍了。
4.7.1.2 SpecGroup实体类
@Data @Table(name = "tb_spec_group") public class SpecGroup { @Id @KeySql(useGeneratedKeys = true) private Long id; private Long cid; private String name; @Transient //数据库表中没有params这一字段,因此要加上@Transient注解 private List<SpecParam> params; }
- @Transient注解
serialization
会忽略掉
Java的serialization提供了一种持久化对象实例的机制。当持久化对象时,可能有一个特殊的对象数据成员,我们不想用serialization机制来保存它。
为了在一个特定对象的一个域上关闭serialization,可以在这个域前加上关键字transient -
不跟数据库表做映射 就是表中没有这个字段
@Transient
表示该属性并非一个到数据库表的字段的映射,ORM框架将忽略该属性.
4.7.1.3 业务
4.7.1.3.1 web
4.7.1.3.1.1 页面分析
- 请求方式:get
- 请求路径:/spec/groups/cid
- 请求参数:cid
- 返回结果:group集合
@RestController @RequestMapping("spec") public class SpecificationController { @Autowired private SpecificationService specificationService; // 根据分类id查询规格组 @GetMapping("groups/{cid}") public ResponseEntity<List<SpecGroup>> queryGroupByCid(@PathVariable("cid")Long cid){ return ResponseEntity.ok(specificationService.queryGroupByCid(cid)); }
4.7.1.3.2 service
@Service public class SpecificationService { @Autowired private SpecGroupMapper groupMapper; @Autowired private SpecParamMapper paramMapper; // 根据分类id查询规格组 public List<SpecGroup> queryGroupByCid(Long cid) { SpecGroup group = new SpecGroup(); group.setCid(cid); //根据非空字段进行查询 List<SpecGroup> list = groupMapper.select(group); if(CollectionUtils.isEmpty(list)){ throw new LyException(ExceptionEnum.SPEC_GROUP_NOT_FOUND); } return list; } }
4.7.1.3.3 mapper
public interface SpecGroupMapper extends Mapper<SpecGroup> { }
4.7.2 规格参数查询
当我们点击规格组,切换到规格参数显示
4.7.2.1 SpecParam数据表
表结构见商品规格数据结构与商品表结构分析
4.7.2.2 SpecParam实体类
@Data @Table(name = "tb_spec_param") public class SpecParam { @Id @KeySql(useGeneratedKeys = true) private Long id; private Long cid; private Long groupId; private String name; //通用mapper生成sql语句时,不要直接拼接numeric,而要拼接`numeric`,反引号是转义为字符串,numeric是一个关键字 @Column(name = "`numeric`") private Boolean numeric; private String unit; private Boolean generic; private Boolean searching; private String segments; }
4.7.2.3 业务
4.7.2.3.1 web
4.7.2.3.1.1 页面分析
- 请求方式:get
- 请求路径:/spec/params
- 请求参数:gid,分组id
- 返回结果:当前组下的所有规格参数
// 根据组id查询规格参数 @GetMapping("/params") public ResponseEntity<List<SpecParam>> querySpecParams(@RequestParam("gid")Long gid){ return ResponseEntity.ok(specificationService.querySpecParams(gid)); }
4.7.2.3.1 service
// 根据组id查询规格参数 public List<SpecParam> querySpecParams(Long gid) { SpecParam param = new SpecParam(); param.setGroupId(gid); List<SpecParam> params = paramMapper.select(param); if(CollectionUtils.isEmpty(params)){ throw new LyException(ExceptionEnum.SPEC_PARAM_NOT_FOUND); } return params; }
4.7.2.3.1 mapper
public interface SpecParamMapper extends Mapper<SpecParam> { }
4.8 商品查询
4.8.1 spu与spuDetail数据表
表结构见商品规格数据结构与商品表结构分析
4.8.1 spu与spuDetail实体类
@Data @Table(name = "tb_spu") public class Spu { @Id @KeySql(useGeneratedKeys=true) private Long id; private Long brandId; private Long cid1;// 1级类目 private Long cid2;// 2级类目 private Long cid3;// 3级类目 private String title;// 标题 private String subTitle;// 子标题 private Boolean saleable;// 是否上架 @JsonIgnore //在json序列化时将java bean中的一些属性忽略掉,序列化和反序列化都受影响。 private Boolean valid;// 是否有效,逻辑删除用 private Date createTime;// 创建时间 @JsonIgnore private Date lastUpdateTime;// 最后修改时间 @Transient private String cname; @Transient private String bname; @Transient private List<Sku> skus; @Transient private SpuDetail spuDetail; }
注:
- po:persisent object,持久对象;它的字段必须和数据库字段完全一致
- 由于数据库里没有商品分类和品牌的name字段,应该写成专门的vo对象(专门返回页面上的)
- 查出来的是spu po,应该转成vo。但将来从页面接收到vo转成spu po才能往数据库传,可能会有各种对象转换的错误
- 为了方便,我们把 商品分类和品牌的name字段 等字段添加到spu里去,由于这些字段数据库中没有,因此都应逐个添加
@Transient
注解(javax.persistence包下的) - 不想返回到界面的字段 可以加一个
@JsonIgnore
注解
@Data @Table(name="tb_spu_detail") public class SpuDetail { @Id // id没有加上自增主键,因为这张表id不是自增的,而是和spu表的id关联的 private Long spuId;// 对应的SPU的id private String description;// 商品描述 private String genericSpec;// 商品特殊规格的名称及可选值模板 private String specialSpec;// 商品的全局规格属性 private String packingList;// 包装清单 private String afterService;// 售后服务 }
4.8.1 业务
4.8.3.1 web
4.8.3.1.1 页面分析
效果预览:
注:这的id为spu_id,标题为spu的title字段。
多个sku有大量的共享数据,因此商品管理的单位应该为spu。
- 请求方式:GET
- 请求路径:/spu/page
- 请求参数: page:当前页
- rows:每页大小
- key:过滤条件
- saleable:上架或下架
4.8.3.1.1 实现业务
@RestController public class GoodsController { @Autowired private GoodsService goodsService; // 分页查询spu @GetMapping("/spu/page") public ResponseEntity<PageResult<Spu>> querySpuByPage( @RequestParam(value = "page",defaultValue = "1")Integer page, @RequestParam(value = "rows",defaultValue = "5")Integer rows, @RequestParam(value = "saleable",required = false)Boolean saleable, @RequestParam(value = "key",required = false)String key ){ return ResponseEntity.ok(goodsService.querySpuByPage(page,rows,saleable,key)); } }
4.8.3.1 service
@Service public class GoodsService { @Autowired private SpuMapper spuMapper; @Autowired private SpuDetailMapper spuDetailMapper; // 分页查询spu public PageResult<Spu> querySpuByPage(Integer page, Integer rows, Boolean saleable, String key) { //分页 PageHelper.startPage(page,rows); //过滤 Example example = new Example(Spu.class); Example.Criteria criteria = example.createCriteria(); //搜索字段过滤 if(StringUtils.isNotBlank(key)) criteria.andLike("title","%"+key+"%"); //上下架过滤 if(saleable!=null) criteria.andEqualTo("saleable",saleable); //默认排序 example.setOrderByClause("last_update_time DESC"); //查询 List<Spu> spus = spuMapper.selectByExample(example); if(CollectionUtils.isEmpty(spus)) throw new LyException(ExceptionEnum.GOODS_NOT_FOUND); //解析分类和品牌名称 for (Spu spu : spus) { //处理分类名称 List<String> names = categoryService.queryByIds(Arrays.asList(spu.getCid1(), spu.getCid2(), spu.getCid3())) .stream().map(Category::getName).collect(Collectors.toList()); spu.setCname(StringUtils.join(names,"/")); //处理品牌名称 spu.setBname(brandService.queryById(spu.getBrandId()).getName()); } //解析分页结果 PageInfo<Spu> pageInfo = new PageInfo<>(spus); return new PageResult<>(pageInfo.getTotal(),spus); } }
注:
- 这里用到了
categoryService.queryByIds(cids)
, // 根据商品分类cid列表查询分类集合,因此应在categoryService写出该方法 - 用户同时输入根据 名称(title)过滤 和 上下架(saleable)过滤,因此采用
and
连接 - 由于spu数据库表中 商品和品牌存的是id,因此需要解析分类和品牌名称
@Service public class CategoryService { @Autowired private CategoryMapper categoryMapper; // 根据商品分类cid列表查询分类集合 public List<Category> queryByIds(List<Long> cids){ List<Category> idList = categoryMapper.selectByIdList(cids); if(CollectionUtils.isEmpty(idList)){ throw new LyException(ExceptionEnum.CATEGORY_NOT_FOUND); } return idList; } }
public interface CategoryMapper extends Mapper<Category> , IdListMapper<Category,Long>{ }
注:
- 由于根据一堆id查询,因此需要在 CategoryMapper 中添加
IdListMapper
。
IdListMapper中继承了SelectByIdListMapper<T, PK>
和DeleteByIdListMapper<T, PK>
4.8.3.1 mapper
public interface SpuMapper extends Mapper<Spu>{ }
public interface SpuDetailMapper extends BaseMapper<SpuDetail> { }
4.9 商品新增
4.9.1 基本数据
当我们点击新增商品按钮时:
会出现一个弹框:
里面把商品的数据分为了4部分来填写:
- 基本信息:主要是一些简单的文本数据,包含了SPU和SpuDetail的部分数据,如 商品分类:是SPU中的cid1,cid2,cid3属性
- 品牌:是spu中的brandId属性
- 标题:是spu中的title属性
- 子标题:是spu中的subTitle属性
- 售后服务:是SpuDetail中的afterService属性
- 包装列表:是SpuDetail中的packingList属性
4.9.1.2 根据商品分类id查询品牌
商品分类信息查询我们之前已经做过,品牌也是一个下拉选框,不过其选项是不确定的,只有当用户选择了商品分类,才会把这个分类下的所有品牌展示出来。
(页面编写了watch函数,监控商品分类的变化,每当商品分类值有变化,就会发起请求,查询品牌列表)
因此我们只要编写后台接口,根据商品分类id查询对应品牌即可。
4.9.1.2.1 web
@RestController @RequestMapping("brand") public class BrandController { // 根据cid查询品牌 @GetMapping("cid/{cid}") public ResponseEntity<List<Brand>> queryBrandByCid(@PathVariable("cid")Long cid){ return ResponseEntity.ok(brandService.queryBrandByCid(cid)); } }
4.9.1.2.2 service
@Service public class BrandService { // 根据cid查到所有的品牌 public List<Brand> queryBrandByCid(Long cid) { List<Brand> brands = brandMapper.queryByCategoryId(cid); if(CollectionUtils.isEmpty(brands)){ throw new LyException(ExceptionEnum.BRAND_NOT_FOUND); } return brands; } }
- cid 和 tb_category,tb_category_brand表都有关系,要自己写sql语句
4.9.1.2.3 mapper
public interface BrandMapper extends BaseMapper<Brand> { @Select("SELECT b.* FROM tb_brand b LEFT JOIN tb_category_brand cb ON b.id = cb.brand_id WHERE cb.category_id = #{cid}") List<Brand> queryByCategoryId(@Param("cid")Long cid); }
4.9.2 商品描述
商品描述信息比较复杂,而且图文并茂,甚至包括视频。
这样的内容,一般都会使用富文本编辑器。
4.9.2.1 什么是富文本编辑器
百度百科:
通俗来说:富文本,就是比较丰富的文本编辑器。普通的框只能输入文字,而富文本还能给文字加颜色样式等。
富文本编辑器有很多,例如:KindEditor、Ueditor。但并不原生支持vue
但我们用的是一款支持Vue的富文本编辑器:vue-quill-editor
4.9.2.2 Vue-Quill-Editor
GitHub的主页:https://github.com/surmon-china/vue-quill-editor
Vue-Quill-Editor是一个基于Quill的富文本编辑器:Quill官网
4.9.3 商品规格参数
-
规格参数的查询我们之前也已经编写过接口,因为商品规格参数也是与商品分类绑定,所以需要在商品分类变化后去查询(通过watch监控来实现)
-
因此我们一样实现根据商品分类id查询规格参数。我们之前写过一个根据gid(分组id)来查询规格参数的接口,若如果再编写一个新的接口(根据分类id查询规格参数)由于和 根据gid来查询规格参数的接口 的路径一模一样,会发生冲突(路径都是/spec/params,参数不一样,但SpringMVC不会因为参数不同就会认定是两次请求)
-
最终:我们在 根据gid来查询规格参数的接口上 对其进行扩展
4.9.3.1 web
@RestController @RequestMapping("spec") public class SpecificationController { // 查询规格参数集合 @GetMapping("/params") public ResponseEntity<List<SpecParam>> querySpecParams(@RequestParam(value = "gid",required = false)Long gid, @RequestParam(value = "cid",required = false)Long cid, @RequestParam(value = "searching",required = false)Boolean searching){ return ResponseEntity.ok(specificationService.querySpecParams(gid,cid,searching)); } }
- 因为两个参数传一个就好,因此应加上
required = false
- 现在我们想要的功能实现了,但从长远角度来看,以后可能会有 根据搜索字段来查规格参数 需求(搜索微服务,做索引库新增),因此应再请求参数里再加一个参数
value = "searching"
4.9.3.2 service
@Service public class SpecificationService { // 查询规格参数集合 public List<SpecParam> querySpecParams(Long gid, Long cid, Boolean searching) { SpecParam param = new SpecParam(); param.setGroupId(gid); param.setCid(cid); param.setSearching(searching); List<SpecParam> params = paramMapper.select(param); if(CollectionUtils.isEmpty(params)){ throw new LyException(ExceptionEnum.SPEC_PARAM_NOT_FOUND); } return params; } }
4.9.4 SKU信息
Sku属性是SPU下的每个商品的不同特征,如图:
当我们填写一些属性后,会在页面下方生成一个sku表格(不同属性的Sku)
当我们选择了上图中的这些选项时:
- 颜色共2种:夜空黑,绚丽红
- 内存共2种:4GB,6GB
- 机身存储1种:64GB
此时会产生 2 * 2 * 1 = 4种,这其实就是在求笛卡尔积。
我们会在页面下方生成一个sku的表格:
4.9.5 提交
整体是一个json格式数据,包含Spu表所有数据:
- brandId:品牌id
- cid1、cid2、cid3:商品分类id
- subTitle:副标题
- title:标题
- spuDetail:是一个json对象,代表商品详情表数据 afterService:售后服务
- description:商品描述
- packingList:包装列表
- specialSpec:sku规格属性模板
- genericSpec:通用规格参数
-
title:标题
4.9.5.1 Sku和Stock表结构
Spu、SpuDetail和 Sku表结构 见商品规格数据结构与商品表结构分析
CREATE TABLE `tb_stock` ( `sku_id` bigint(20) NOT NULL COMMENT '库存对应的商品sku id', `seckill_stock` int(9) DEFAULT '0' COMMENT '可秒杀库存', `seckill_total` int(9) DEFAULT '0' COMMENT '秒杀总数量', `stock` int(9) NOT NULL COMMENT '库存数量', PRIMARY KEY (`sku_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='库存表,代表库存,秒杀库存等信息'
4.9.5.2 Sku和Stock实体类
@Data @Table(name = "tb_sku") public class Sku { @Id @KeySql(useGeneratedKeys = true) private Long id; private Long spuId; private String title; private String images; private Long price; private String ownSpec;// 商品特殊规格的键值对 private String indexes;// 商品特殊规格的下标 private Boolean enable;// 是否有效,逻辑删除用 private Date createTime;// 创建时间 private Date lastUpdateTime;// 最后修改时间 @Transient //和数据库无关 private Integer stock;// 库存 }
@Data @Table(name = "tb_stock") public class Stock { @Id private Long skuId; private Integer seckillStock;// 秒杀可用库存 private Integer seckillTotal;// 已秒杀数量 private Integer stock;// 正常库存 }
4.9.5.3 业务
4.9.5.3.1 web
4.9.5.3.1.1 页面分析
- 请求方式:PUT
- 请求路径:/
- 请求参数:Spu对象
- 返回结果:无
@RestController public class GoodsController { //商品新增 @PostMapping("goods") public ResponseEntity<Void> saveGoods(@RequestBody Spu spu){ //json结构,加上@RequestBody注解 goodsService.saveGoods(spu); return ResponseEntity.status(HttpStatus.CREATED).build();//没有返回值 } }
4.9.5.3.2 service
@Service public class GoodsService { //商品新增 @Transactional public void saveGoods(Spu spu) { //新增spu spu.setId(null); spu.setSaleable(true); spu.setValid(false); spu.setCreateTime(new Date()); spu.setLastUpdateTime(spu.getCreateTime()); int count = spuMapper.insert(spu); if (count != 1) { throw new LyException(ExceptionEnum.GOODS_SAVE_ERROR); } //新增detail SpuDetail spuDetail = spu.getSpuDetail(); spuDetail.setSpuId(spu.getId()); spuDetailMapper.insert(spuDetail); //新增sku和库存 saveSkuAndStock(spu); //发送mq消息(这一步后边用到,可忽略) //amqpTemplate.convertAndSend("item.insert",spu.getId()); } }
在这里,我们把 新增sku和库存 单独抽取成一个方法了
// 新增sku和库存 private void saveSkuAndStock(Spu spu) { int count;//新增sku List<Sku> skus = spu.getSkus(); List<Stock> stockList = new ArrayList<>(); for (Sku sku : skus) { sku.setCreateTime(new Date()); sku.setLastUpdateTime(sku.getCreateTime()); sku.setSpuId(spu.getId()); count = skuMapper.insert(sku); if(count!=1) throw new LyException(ExceptionEnum.GOODS_SAVE_ERROR); //新增库存 Stock stock = new Stock(); stock.setSkuId(sku.getId()); stock.setStock(sku.getStock()); stockList.add(stock); } //批量新增库存 count = stockMapper.insertList(stockList); if(count!=stockList.size()) throw new LyException(ExceptionEnum.GOODS_SAVE_ERROR); }
4.9.5.3.3 mapper
public interface SkuMapper extends BaseMapper<Sku>{ }
public interface StockMapper extends BaseMapper<Stock> { }
注:
- 为了以后Mapper实现的功能更加全面,简单起见,我们自己定义一个
BaseMapper<>
,泛型为要传递的 类 - BaseMapper继承
Mapper<T>,IdListMapper<T,Long>,InsertListMapper<T>
,可实现 从数据库中批量查询与添加数据 - 将BaseMapper放在ly-common 工具包中,供其他mapper继承
- 同时,InsertListMapper要导 tk.mybatis.mapper.additional.insert.InsertListMapper包下的;(另外一个包下的InsertListMapper接口限制实体中必须包含‘Id’属性且必须为自增列)
@RegisterMapper public interface BaseMapper<T> extends Mapper<T>,IdListMapper<T,Long>,InsertListMapper<T> { }
@RegisterMapper注解可以避免 mappers 参数配置,通用 Mapper 检测到该接口被继承时,会自动注册。
即使不增加该接口,如果只用到了通用 Mapper 提供的方法,也可以自动注册,通用 Mapper 会自动向上查找带有该注解的父接口。如果是自己开发的通用方法,建议加上该注解,否则还需要自己配置 mappers 参数
4.10. 商品修改
因为在商品列表页面,只有spu的基本信息:id、标题、品牌、商品分类等。比较复杂的商品详情(spuDetail)和sku信息都没有,编辑页面要回显数据,就需要查询这些内容。
因此,接下来我们就编写后台接口,提供查询服务接口。
4.10.1 查询SpuDetail接口
4.10.1.1 web
4.10.1.1.1 页面分析
- 请求方式:GET
- 请求路径:/spu/detail/{id}
- 请求参数:id,应该是spu的id
- 返回结果:SpuDetail对象
4.10.1.1.2 实现业务
@RestController public class GoodsController { //根据spu的id查询详情detail @GetMapping("/spu/detail/{id}") public ResponseEntity<SpuDetail> querySpuDetailById(@PathVariable("id")Long id){ return ResponseEntity.ok(goodsService.queryDetailById(id)); } }
4.10.1.2 service
@Service public class GoodsService { public SpuDetail queryDetailById(Long spuId) { SpuDetail spuDetail = spuDetailMapper.selectByPrimaryKey(spuId); if(spuDetail==null) throw new LyException(ExceptionEnum.GOODS_SAVE_ERROR); return spuDetail; } }
4.10.2 查询sku
4.10.2.1 web
4.10.2.1.1 页面分析
- 请求方式:Get
- 请求路径:/sku/list
- 请求参数:id,应该是spu的id
- 返回结果:sku的集合
4.10.2.1.2 实现业务
@RestController public class GoodsController { //根据spu查询下面所有的sku @GetMapping("/sku/list") public ResponseEntity<List<Sku>> querySkuBySpuId(@RequestParam("id") Long spuId){ return ResponseEntity.ok(goodsService.querySkuBySpuId(spuId)); } }
4.10.2.1 service
@Service public class GoodsService { //根据spu查询下面所有的sku public List<Sku> querySkuBySpuId(Long spuId) { //查询sku Sku sku = new Sku(); sku.setSpuId(spuId); List<Sku> skuList = skuMapper.select(sku); if(CollectionUtils.isEmpty(skuList)) throw new LyException(ExceptionEnum.GOODS_SKU_NOT_FOUND); List<Long> ids = skuList.stream().map(Sku::getId).collect(Collectors.toList()); //将库存放到相应的sku中 //查询库存 List<Stock> stockList = stockMapper.selectByIdList(ids); if(CollectionUtils.isEmpty(stockList)) throw new LyException(ExceptionEnum.GOODS_NOT_FOUND); //把stock变成一个map,其key:skuId,值:库存值 Map<Long, Integer> stockMap = stockList.stream().collect(Collectors.toMap(Stock::getSkuId, Stock::getStock)); skus.forEach(s ->s.setStock(stockMap.get(s.getId()))); return skuList; } }
4.10.3 提交业务
4.10.3.1 web
4.10.3.1.1 页面分析
- 请求方式:PUT
- 请求路径:/
- 请求参数:Spu对象
- 返回结果:无
4.10.3.1.1 实现业务
@RestController public class GoodsController { //商品修改 @PutMapping("goods") public ResponseEntity<Void> updateGoods(@RequestBody Spu spu){ goodsService.updateGoods(spu); return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); } }
4.10.3.1 service
spu数据可以修改,但是SKU数据无法修改,因为有可能之前存在的SKU现在已经不存在了,或者以前的sku属性都不存在了。比如以前内存有4G,现在没了。
因此这里直接删除以前的SKU,然后新增即可
@Service public class GoodsService { @Transactional public void updateGoods(Spu spu) { if(spu.getId() == null) throw new LyException(ExceptionEnum.GOODS_ID_CANNOT_BE_NULL); Sku sku = new Sku(); sku.setSpuId(spu.getId()); //查询sku List<Sku> skuList = skuMapper.select(sku); if(!CollectionUtils.isEmpty(skuList)){ //删除sku skuMapper.delete(sku); //删除库存 List<Long> ids = skuList.stream().map(Sku::getId).collect(Collectors.toList()); stockMapper.deleteByIdList(ids); } //修改spu spu.setValid(null); spu.setSaleable(null); spu.setCreateTime(null); spu.setLastUpdateTime(new Date()); int count = spuMapper.updateByPrimaryKeySelective(spu); if(count!=1) throw new LyException(ExceptionEnum.GOODS_UPDATE_ERROR); //修改detail spuDetailMapper.updateByPrimaryKeySelective(spu.getSpuDetail()); if(count!=1) throw new LyException(ExceptionEnum.GOODS_UPDATE_ERROR); //新增sku和库存stock(单独提取成一个方法了,上边已经写过了) saveSkuAndStock(spu); //发送mq消息(以后会用到) amqpTemplate.convertAndSend("item.update",spu.getId()); } }
以上,商品微服务可以先告一段落了!
- 乐优商城--服务(四) : 上传微服务(LyUploadApplication)
- 乐优商城--服务(九) :授权微服务(LyAuthApplication)--前半部分
- 乐优商城--服务(七) : 用户中心微服务(LyUserApplication)
- 乐优商城--服务--优化搜索微服务和页面微服务
- 乐优商城--服务(八) :短信微服务(LySmsApplication)
- 使用 Rational Application Developer 构建 HTTPS Web 服务,第 1 部分:Web 服务与 Web 服务客户机
- **从零开始完成微服务项目乐优商城**
- 乐优商城--关于微服务的安全问题
- 乐优商城学习笔记八-商品管理(修改商品)
- 【乐优商城】springcloud微服务-项目搭建
- 《WCF技术内幕》翻译13:第1部分_第2章_面向服务:为什么SO有意义和本章小结
- IBM WebSphere sMash 简介,第 1 部分: 为 Web 应用程序构建 RESTful 服务
- 面向服务的体系结构概述:第三部分(转)
- 商品服务
- 使用 WebSphere Business Services Fabric 创建面向服务的灵活业务解决方案,第 1 部分:概述
- 移动商城第八篇【添加商品之基本属性和大字段数据(FCK文本编辑器)】
- 使用策略设计模式,反射,解决商城系统中的商品折扣问题
- iOS之推送通知-本地-服务器3.服务器端实现: 如果要编写内容提供者的推送服务程序,需要进行SSL认证编程,以及构建APNS数据包,数据包分为3个主要部分:Command(命令)、deviceTo
- 窗口类Win32 application (2) 看局域网聊天开源软件IPMsg的Win32部分
- JQUERY手机商城各个分类商品条件筛选