
# 2 litemall基础系统

目前litemall基础系统主要由litemall数据库、litemall-db模块和litemall-os-api模块组成。

目前存在的问题：

* `严重`数据库采用git，每次跟新都是5MB数据，影响项目下载速度
* `缺失`litemall-os-api返回的图片不能在浏览器直接显示
* `改善` litemall-db的一些CRUD操作可以基于开源库重构
* `功能`可以参考一些云存储服务的API加强一些功能

## 2.1 litemall.sql

litemall.sql数据库基于nideshop中的[nideshop.sql](https://github.com/tumobi/nideshop/blob/master/nideshop.sql)数据库，然后在实际开发过程中进行了调整和修改:

* 删除了一些目前不必要的表；
* 删除了表中一些目前不必要的字段；
* 行政区域数据litemall_region没有采用原nideshop中的数据，而是采用了[Administrative-divisions-of-China](https://github.com/modood/Administrative-divisions-of-China)；
* 表中的某些字段采用JSON；
* 表中的日期或时间字段采用DATE、DATETIME；
* 字段的数据类型粗粒度化，例如避免MEDIUMINT，而是INT；
* 表的数据做了清理、调整和补充（假数据）。

具体不同可以比较litemall-db模块下sql文件夹中nideshop.sql和litemall.sql。

以下讨论一些关键性设计

注意：
> 以下设计基于个人理解，很可能存在不合理或者与实际系统不符合的地方。

### 2.1.1 商品和货品设计

这里商品存在商品，商品属性，商品规格，货品四种表

商品表是一种商品的基本信息，主要包括商品介绍，商品图片，商品所属类目，商品品牌商等；

商品参数表其实也是商品的基本信息，但是由于是一对多关系，因此不能直接保存在商品表中（虽然采用JSON也可以但是不合理），
因此采用独立的商品参数表，通常是商品的一些公共基本商品参数；

商品规格表是商品进一步区分货品的标识，例如同样一款衣服，基本信息一致，基本属性一致，但是在尺寸这个属性上可以
把衣服区分成多个货品，而且造成对应的数量和价格不一致。商品规格可以看着是商品属性，但具有特殊特征。

商品规格和规格值存在以下几种关系：

* 单一规格和单一规格值，最常见的，即当前商品存在一种货品；
* 单一规格和多个规格值，较常见，即当前商品基于某个规格存在多种货品，通常价格都是相同的，当然也可能不相同；
* 多个规格和单一规格值，可以简化成第一种情况，或者采用第四种情况，通常实际情况下不常见；
* 多个规格和多个规格值，通常是两种规格或者三种规格较为常见，而且对应的价格不完全相同。

货品则是最终面向开发者购买的商品标识，存在多个规格值、数量和价格。

因此这里一个商品表项，存在（至少0个）多个商品属性表项目，存在（至少一个）多个商品规格表项，
存在（至少一个）多个货品表项。

举例如下：

* 一个商品“2018春季衣服商品编号1111111”，
* 存在两个商品参数，
  * 属性名称“面向人群”，属性值“男士”
  * 属性名称“面料”，属性值“100%棉”
* 存在两种规格共八个商品规格项，
  * 规格名称“尺寸”，规则值“S”
  * 规格名称“尺寸”，规则值“M”
  * 规格名称“尺寸”，规则值“L”
  * 规格名称“尺寸”，规则值“XL”
  * 规格名称“尺寸”，规则值“XXL”
  * 规格名称“颜色”，规格值“蓝色”
  * 规格名称“颜色”，规格值“灰色”
  * 规格名称“颜色”，规格值“黑色”
* 存在15个货品（尺寸*颜色=15个货品)
  * 货品“S蓝”，数量 100， 价格 100
  * 货品“M蓝”，数量 100， 价格 100
  * 货品“L蓝”，数量 100， 价格 100
  * 货品“XL蓝”，数量 100， 价格 100
  * 货品“XXL蓝”，数量 100， 价格 100
  * 货品“S灰”，数量 100， 价格 100
  * 货品“M灰”，数量 100， 价格 100
  * 货品“L灰”，数量 100， 价格 100
  * 货品“XL灰”，数量 100， 价格 100
  * 货品“XXL灰”，数量 100， 价格 100
  * 货品“S黑”，数量 100， 价格 100
  * 货品“M黑”，数量 100， 价格 100
  * 货品“L黑”，数量 100， 价格 100
  * 货品“XL黑”，数量 0， 价格 100
  * 货品“XXL黑”，数量 0， 价格 100
  
以下是一些细节的讨论：

* 商品表中可能存在数量和价格属性，而货品中也存在数量和价格属性，目前设计这样：
  * 商品表的价格应该和某个货品的价格一样，通常应该是所有货品价格的最小值，或者基本款式的价格；
  * 商品表中的数量和价格应该仅用于展示，而不能用于最终的订单价格计算；
  * 商品表的数量应该设置成所有货品数量的总和；
  * 在管理后台添加商品时，如果管理员不填写商品表的数量和价格属性，则自动填写合适的值；如果填写，则使用显示。
  * 当小商城中，用户查看商品详情时，初始显示商品表的价格，而如果用户选择具体规格后，则商品
    详情里面的价格需要自动切换到该规格的价格。
* 商品规格可以存在规格图片，效果是规格名称前放置规格图片
* 货品也可以存在货品图片，效果是所有规格选定以后对应的货品有货，则在货品价格前放置货品图片
* 如果商品是两种规格，分别是M个和N个规格值，那么通常应该是`M*N`个货品，但是有些货品可能天然不存在。
  那么，此时数据库如何来设计，是允许少于`M*N`个项，还是必须等于`M*N`个，而不存在货品的数量设置为0？
  * 

注意：

> 这里的设计可能与实际项目设计不一致，但是目前是可行的。
> 商品的中文用语“商品”和英语用语“goods”，货品的中文用语“货品”和英语用语“product”可能是不正确的。

### 2.1.2 用户和微信用户设计

目前准备支持用户普通账号登录和微信登录两种方式，两种登录方式仅仅采用一个litemall-user表可能不是很合适。此外，如果进一步支持其他多种第三方登录，那么这里需要重新设计。

### 2.1.3 行政区域设计
 
原nideship.sql中存在region数据，但是litemall.sql的region数据则来自
[Administrative-divisions-of-China](https://github.com/modood/Administrative-divisions-of-China)项目。从该项目中导入数据到litemall.sql的litemall-province、litemall-city、litemall-area和litemall-street四个表，然后重新生成一个新的litemall-region表。

### 2.1.4 订单设计

订单信息主要由基本信息、商品信息、地址信息、费用信息、快递信息、支付信息和其他信息组成。

* 基本信息
订单创建时的一些基本信息。

* 商品信息
由于订单可以存在多个商品，因此订单的商品信息是由独立的订单商品表记录（可能更应该称为货品）。

* 费用信息

* 快递信息
目前快递信息仅仅记录快递公司、快递单号、快递发出时间、快递接收时间。而如果快递过程中如果存在一些异常，例如物品丢失，则目前系统难以处理。关于快递费的计算，目前采取简单方式，即满88元则免费，否则10元。

* 支付信息


* 其他信息

#### 2.1.4.1 订单状态

![](pic2/2-1.png)

订单分成几种基本的状态：

* 下单
状态码101，此时订单生成，记录订单编号、收货地址信息、订单商品信息和订单相关费用信息；
* 付款
状态码201，此时用户微信支付付款，系统记录微信支付订单号、支付时间、支付状态；
* 发货
状态码301，此时商场已经发货，系统记录快递公司、快递单号、快递发送时间。
当快递公司反馈用户签收后，系统记录快递到达时间。
* 收货
状态码401，当用户收到货以后点击确认收货，系统记录确认时间。
此时，用户可以评价订单商品。

除了这几种正常状态以外，还存在一些非普通的状态：

* 订单取消
状态码102，用户在生产订单以后未付款之前，点击取消按钮，系统记录结束时间
* 订单取消并退款
状态码202，用户付款以后未发货前，点击取消按钮，系统记录结束时间和退款信息
* 系统自动确认收货
状态码402，快递反馈商场用户已签收，但是用户却不点击确认收货按钮，
此时系统在快递到达时间的一段时间后，自动确认收货。
用户不能再点击确认收货按钮，但是可以评价订单商品

当然，以上的基本状态和非普通状态，和实际项目相比仍然相对简单。

此外，当订单状态码是102、202、401、402时，订单可以设置删除状态，此时
用户查看自己订单信息时将看不到这些“已删除”的订单。


#### 2.1.4.2 状态码所支持的操作

不同的状态码下面，用户能够进行的操作是：

* 101
此时，用户可以“订单支付”、“订单取消”
* 102
此时，用户可以“订单删除”
* 201
此时，用户可以“订单取消”（并退款）
* 202
此时，用户可以“订单删除”
* 301
此时，用户可以“确认收货”
* 401
此时，用户可以“订单删除”、“评价”、“再次购买”
* 402
此时，用户可以“订单删除”、“评价”、“再次购买”

#### 2.1.4.3 售后处理

目前不支持售后或退货相关业务。

#### 2.1.4.4 黑名单

从一些资料看，如果用户订单多次取消，应该加入黑名单。
目前不支持。

#### 2.1.5 数据删除

所有表的数据在代码层面都不支持删除，除非数据库管理员连接到数据库删除数据。

在访问层，数据库的删除操作

## 2.2 litemall-db

技术：
* Spring Boot 1.5.10
* MySQL
* Druid
* Mybatis
* PageHelper
* Mybatis Generator
* Mybatis Generator非官方插件mybatis-generator-plugin
  
因为litemall-db是一个业务模块，并不对外直接服务，因此无需使用Spring MVC。

![](./pic2/2-2.png)

这里litemall-db模块可以分成以下几种代码：

* mybatis generator自动化代码
* 业务代码
* 安全代码
* JSON支持代码
* 配置代码

### 2.2.1 自动化代码

![](./pic2/2-3.png)

如上图所示，双击`mybatis-generator:generate`，则mybatis generator插件会：

1. 读取`mybatis-generator`文件夹下的`generatorConfig.xml`文件
2. 根据`jdbcConnection`访问数据库
3. 根据`table`, 自动生成三种代码:
   * src文件夹`org.linlinjava.litemall.db.domain` 包内的Java代码
   * src文件夹`org.linlinjava.litemall.db.domain` 包内的Java代码
   * resources文件夹`org.linlinjava.litemall.db.domain.dao` 内的XML文件

以上三种代码即可封装对数据库的操作，开发者无需直接操作sql代码，
而是直接操作Java代码来完成对数据库的访问处理。

关于如何基于mybatis的Example代码来访问数据库，请查阅相关资料，
或者参考本模块`org.linlinjava.litemall.db.dservice` 包内的Java代码。

当然，为了达到数据库访问效率，开发者也可以手动自定义mapper文件和对应的Java代码，但目前这里不采用或者不建议采用。
例如，当需要访问两个表的数据时，这里是在业务层通过Java代码遍历的形式来访问两个表。

这里，以`litemall_brand`表举例说明：

1. mybatis generator插件会根据数据库`table`标签

    ```
    <generatorConfiguration>
         <table tableName="litemall_brand">
             <generatedKey column="id" sqlStatement="MySql" identity="true" />
             <columnOverride javaType="java.time.LocalDateTime" column="add_time"/>
         </table>
    </generatorConfiguration>
    ```
    
2. 自动生产src文件夹下domain包内的LitemallBrand.java类、LitemallBrandExample.java类、
    dao包内的LitemallBrandMapper.java接口和resources文件夹下dao包内的LitemallBrandMapper.xml文件。

3. 手动在service包内创建LitemallBrandService.java来对外提供具体的服务。
   例如，为了得到Brand列表，那么创建list方法，基于前面创建的三个Java来来实现。
   
   ```java
    @Service
    public class LitemallBrandService {
       @Resource
       private LitemallBrandMapper brandMapper;

        public List<LitemallBrand> query(int offset, int limit) {
           LitemallBrandExample example = new LitemallBrandExample();
           example.or().andDeletedEqualTo(false);
           PageHelper.startPage(offset, limit);
           return brandMapper.selectByExample(example);    
        }
    }
   ```


如果基于一个新表创建新访问组件，请阅读下面章节2.2.6

### 2.2.2 业务代码

基于2.2.1的代码，业务代码处理一些具体业务相关的操作，对其他模块提供具体的服务。

### 2.2.3 安全代码

### 2.2.4 JSON支持代码

### 2.2.5 配置代码

采用Java注解的方式来完成一些特定的配置操作。

### 2.2.6 新服务组件

本节介绍如果基于一个表创建新的服务组件。

1. 在数据库里面创建一个表，例如`litemall_demo`:

    ```sql
    CREATE TABLE `litemall`.`litemall_demo` (
      `id` INT NOT NULL AUTO_INCREMENT,
      `name` VARCHAR(45) NULL,
      `address` VARCHAR(45) NULL,
      PRIMARY KEY (`id`));
      
    INSERT INTO `litemall`.`litemall_demo` (`id`, `name`, `address`) 
    VALUES ('1', 'hello', 'world');
    ```

2. 在generatorConfig.xml中增加一个新的table标签

    ```
    <generatorConfiguration>
         <table tableName="litemall_demo">
             <generatedKey column="id" sqlStatement="MySql" identity="true" />
         </table>
    </generatorConfiguration>
    ```
3. 双击mybatis generator插件，检查LitemallDemo.java类、LitemallDemoExample.java类、
   LitemallDemoMapper.java接口和LitemallDemoMapper.xml是否生产。
   
4. 在service里面新建LitemallDemoService.java类，

   ```java
    @Service
    public class LitemallDemoService {
       @Resource
       private LitemallDemoMapper demoMapper;

        public List<LitemallDemo> list() {
           LitemallDemoExample example = new LitemallDemoExample();
           return demoMapper.selectByExample(example);    
        }
    }
   ```

5. 可以在`src/test/java/org.linlinjava.litemall.db`包里面创建LitemallDemoTest.java类,
    使用Junit进行测试。

    ```java
    @WebAppConfiguration
    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringBootTest
    public class LitemallDemoTest {    
       @Autowired
       private LitemallDemoService demoService;
    
       @Test
       public void test() {    
        List<LitemallDemo> litemallDemoList = demoService.list();
        Assert.assertTrue(litemallDemoList.size() != 0);
       }
    
    }
    ```

6. 同样地，可以在Controller中使用LitemallDemoService来对外提供服务。
    
    ```java
    @RestController
    @RequestMapping("/demo")
    public class DemoController {
       @Autowired
       private LitemallDemoService demoService;
    
       @RequestMapping("/list")
       public Object list(){    
           List<LitemallDemo> demoList = demoService.list();   
           return demoList;
       }
    }
    ```

## 2.3 litemall-core
        
## 2.4 litemall-os-api

对象存储服务目前的目标是支持图片的上传下载。

作为后台模块之一，litemall-os-api并没有对应的前端模块，而只是在litemall-admin模块
的对象存储页面中允许管理员修改。

注意：
> 这个模块是可选的，或者说不建议最终部署时所使用。
> 最终部署时建议采用第三方云存储方案。

### 2.4.1 业务

支持服务：

* 列表
* 创建
* 修改
* 读取
* 删除
* 下载，即下载对象数据文件
* 访问，即直接访问对象数据

### 2.4.2 安全

警告
> 目前这里没有任何安全机制，这意味着任何人如果知道对象存储服务的地址，都可以直接存储访问对象数据。

这样简化的目的是对象存储服务建议最终采用云服务，因此这里仅仅实现一个简单的服务面向测试开发。

如果开发者需要局域网部署，那么这里需要加入一定的安全机制。

### 2.4.3 文件Key

每一个上传的文件都会采用一个随机值key，作为当前文件的网络访问链接的一部分。

以后可能需要进一步支持自定义Key，例如采用原文件名字作为key。


## 2.5 litemall-all

在章节1.5中讨论的部署方案中设计了一种单主机单服务方案，
也就是说三个后台服务和静态文件都部署在一个Spring Boot应用中。

注意：
> 这个模块也是可选的，或者说不是非常建议的，应该仅用在主机内存资源紧张的情况下。
> 最终部署，仍然建议部署多个服务更为安全和稳定。

查看litemall-all模块，代码仅仅只有一个Application类。

实际的原理是litemall-all模块内的pom.xml文件：

1. 声明打包方式是`war`，因此最后会打包war格式

2. 设置`spring-boot-starter-tomcat`包是`provided`，因此最终不会打包

3. 申明依赖`litemall-os-api`、`litemall-wx-api`和`litemall-admin-api`，
   因此最终会打包；
   
   在Application类里面通过`scanBasePackages`即可把三个后台服务模块
   的服务启动。此外在tomcat中启动，需要采用继承`SpringBootServletInitializer`类
   的Application。
   
4. 利用maven-resources-plugin插件，将litemall-admin模块下编译得到dist文件下的
   静态文件打包到static目录中。
   
   注意，这里只是简单的复制。因此开发者需要保证litemall-all打包前，litemall-admin
   模块内dist目录下静态文件已经生成。
   
最终打包以后则是一个war格式的项目包，包含了三个后台服务和静态文件。

