# 3 litemall小商城

技术：

* 小商城前端，即litemall-wx模块
  * 微信小程序
* 小商城后端，即litemall-wx-api模块
  * Spring Boot 1.5.10
  * Spring MVC
  * [weixin-java-tools](https://gitee.com/binary/weixin-java-tools)


目前发现存在的一些问题：
  
* `严重`账号登录和注册中验证码

  注意：
  > 这里之所以没有实现，是因为需要接入云平台的短信服务。
  > 后期可能接入腾讯的短信服务。
  
* `缺失`优惠券功能
* `缺失`商品评价中管理员回复功能，进一步地用户之间相互评价回复
* `缺失`后台服务返回的token存在有效期，小商场应该自动刷新
* `缺失`账号多次登录失败，应该小商城出现图片验证码限制，或者后台账号锁定
* `改善`商品搜索中采用更好的搜索机制
* `改善`商品搜索中，支持热门搜索"换一批"
* `改善`地址优化，目前每一次点击都会请求后台，应该缓存已有的数据
* `改善`商品数量和规格中，如果货品数量不足，则显示效果，通常是是两种效果
  * 某个规格选择以后，另外规格的某些规格是实线，而某些是虚线，
  * 商品的都规格选择以后，“立即购买”和“加入购物车显示”灰色
* `改善`商品好评计算与显示，例如90%好评
* `改善`商品的评论列表中显示评价的评论分数、商品规格
* `改善`商品的评论列表中的图片点击可放大，同时用户评价的多个图片可以选择左右滑动查看。
* `改善`当一些页面查询数据没有跟新时，底部显示相应的提醒，例如“没有更多数据”。
* `改善`个人页面中实现订单部件，显示用户订单数量，同时点击以后跳转到订单的相应页面。
* `改善`商品的订单中支持订单号搜索功能
* `改善`在一些内容比较多的页面中支持“顶部”功能
* `功能`目前已经有账号登录页面，可以再支持手机短信登录方式。
* `功能`个人页面支持联系客服
* `功能`个人页面支持帮助中心
* `功能`推荐功能，基于用户的一些信息，在合适的页面给出推荐商品

## 3.0 小商场环境

开发者小商场开发环境以后，启动后台服务，小程序端可以
显示数据和图片，但是微信登录会失败，因为appid不是
开发者自己的，这里进一步介绍开发者需要设置的小商场环境。

### 3.0.1 微信小程序信息

开发者在微信小程序官网申请以后，可以有app-id和app-secret信息。

1. 在litemall-wx-api模块的src/main/resources的资源文件中设置
```
wx.app-id=开发者申请的app-id
wx.app-secret=开发者申请的app-secret
```

2. 在litemall-wx模块的project.config.json文件中设置
```
"appid": "开发者申请的app-id",
```

3. 启动后台服务

4. 建议开发者关闭当前项目，重新打开（因为此时litemall-wx模块的appid可能未更新）。
   编译运行，尝试微信登录

### 3.0.2 微信商户支付信息

开发者在微信支付平台申请以后，可以有app-id和app-secret信息。

1. 在litemall-wx-api模块的src/main/resources的资源文件中设置

    ```
    wx.mch-id=开发者申请的mch-id
    wx.mch-key=开发者申请的mch-key
    wx.notify-url=开发者部署服务的微信支付成功回调地址
    ```

    注意
    > 1. notify-url是微信支付平台向小商场后台服务发送支付结果的地址。
    >    因此这就要求该地址是可访问的。
    > 2. 目前小商场后台服务的默认request mapping是`/wx/order/pay-notify`（见WxOrderController类的payNotify）,
    >    因此notify-url应该设置的地址类似于`http://www.example.com/wx/order/pay-notify`
    > 3. 当开发者真正上线后台服务时，强烈建议默认request mapping要重新命名，不能对外公开。

2. 启动后台服务

3. 部署后台服务到云主机

4. litemall-wx的api.js设置云主机的公网IP。
   编译运行，尝试微信支付。
   
## 3.1 litemall-wx-api

### 3.1.1 授权服务

#### 3.1.1.1 账号登录

#### 3.1.1.4 微信登录

#### 3.1.1.5 账号注册

目前账号注册只是简单的根据用户名和密码新建一个账号。

这里缺失一个重要的属性，即用户邮箱或者手机号，来限制用户随意注册。

#### 3.1.1.6 密码找回

### 3.1.2 首页服务

### 3.1.3 类目服务

### 3.1.4 商品服务

#### 3.1.4.1 商品新品

#### 3.1.4.2 商品热品

#### 3.1.4.3 商品列表

* 关键字搜索

用户的搜索采用和商品的关键字属性匹配来查找商品。
因此需要管理员添加商品时设置关键字值。

这里只是简单的搜索，更好地做法可能是进一步搜索商品的名字、简介。
或者采用更为专业的搜索算法。


#### 3.1.4.4 商品详情

#### 3.1.4.5 同类商品

#### 3.1.4.6 商品总数

#### 3.1.4.7 相关商品

### 3.1.5 品牌服务

#### 3.1.5.1 品牌列表

#### 3.1.5.2 品牌详情

### 3.1.6 专题服务

#### 3.1.6.1 专题列表

#### 3.1.6.2 专题详情

#### 3.1.6.3 相关专题

### 3.1.7 搜索服务

#### 3.1.7.1 搜索关键字

#### 3.1.7.2 搜索帮助

#### 3.1.7.3 搜索历史清除

#### 3.1.7.4 搜索结果

### 3.1.8 购物车服务

#### 3.1.8.1 购物车商品详情

#### 3.1.8.2 添加商品到购物车

#### 3.1.8.3 立即购物

#### 3.1.8.4 更新购物车商品

#### 3.1.8.5 删除购物车商品

#### 3.1.8.6 设置购物车商品选中状态

#### 3.1.8.7 购物车商品数量

#### 3.1.8.8 购物车下单前确认

### 3.1.9 订单服务

#### 3.1.9.1 提交订单

* 运费计算

订单费用小于88时，则需要运费8元；
否则运费0元。

目前运费8元是写在后台代码中，未来可能允许设置管理员设置其他值；
或者采用更加符合实际情况的运费计算方式。

#### 3.1.9.2 取消订单

* 用户手动取消

  用户下单以后但是未付款，

* 用户超时取消

  这里，订单超时未付款则系统会自动取消订单。
  
  在实现上，则是采用Spring定时功能查询数据库内订单的时间和状态，
  如果发现超时了，则自动取消订单，而取消订单的具体操作可以简单
  采用“用户手动取消”相同的操作。
  
  目前这里取消状态码是一样的，因此最终可能并不能分别订单取消是因为
  何种原因而取消的。
  
#### 3.1.9.3 取消订单并退款

用户付款以后再申请取消订单比较复杂，涉及微信退款操作，因此这里并没有
简单作为“取消订单”，而是一种独立的功能。

#### 3.1.9.4 删除订单
#### 3.1.9.5 订单确认发货
#### 3.1.9.6 订单超时确认收货

用户收到货物以后可能并不会积极地进行“确认收货”操作，因此有必要实现
一定时间以后订单自动确认收货。

“订单超时确认”的起始时间如何来设计

#### 3.1.9.6 可评价订单商品信息
#### 3.1.9.7 订单列表
#### 3.1.9.8 订单详情


### 3.1.10 评价服务

#### 3.1.10.1 评论列表

#### 3.1.10.2 评论总数

#### 3.1.10.3 提交评论

### 3.1.11 支付服务

准备采用weixin-java-tools工具简化微信支付代码的开发。
由于需要商户相关信息，目前没有开发。

### 3.1.12 收藏服务

#### 3.1.12.1 收藏列表

#### 3.1.12.2 收藏设置

### 3.1.13 足迹服务

#### 3.1.13.1 足迹列表

#### 3.1.13.2 删除足迹

### 3.1.14 收货地址服务

#### 3.1.14.1 收货地址列表

#### 3.1.14.2 收货地址详情

#### 3.1.14.3 收货地址添加

#### 3.1.14.4 收货地址删除

### 3.1.15 区域服务

### 3.1.16 安全

#### 3.1.161 Token

用户登录成功以后，后端会返回`token`，之后用户的请求都会携带token。

目前token的失效和跟新机制没有涉及。

#### 3.1.16.2 CROS

如果litemall-admin-api不配置CROS，则Spring Boot会失败。
但是，这里litemall-wx-api没有配置CROS，Spring Boot却不会报错，需要进一步研究。

#### 3.1.16.3 账号密码加盐

如果是微信登录，那么无需账号和密码。

而如果用户采用了账号和密码的形式登录，那么后端需要把用户密码加盐。

#### 3.1.16.4 限制登录

如果采用账号密码登录，那么登录失败一定次数，应该限制登录。

进一步地，如果项目启用了短信功能，应该短信提醒用户，防止他人登录。

目前这里没有实现，仅列出。

### 3.1.17 定时任务

目前有些业务需要定时任务：
* 订单未付款超时自动取消订单
* 订单未确认超时自动确认订单

定时任务技术上采用Spring的任务机制，即`@EnableScheduling`和`@Scheduled`注解。

目前需要讨论存在的限制或者问题：
1. 定时任务带来的延时问题

   定时任务是定时启动，而不是针对任务中具体的工作定时处理，因此会带来延时问题。
   
   例如，订单未付款工作检测是基于半小时超时时间，但是因为订单是相隔半个小时才启动，
   因此会导致实际最长一个小时才能检测订单超时。
  
   当然，这个问题可能并不严重。
   * 可以结合其他机制来减轻这个问题。例如订单未付款超时可以在用户查询自身订单时
     也启动，从而提前完成检测工作，给用户的感觉也是最长半小时的超时时间。
   * 这个延时是可以接收的，或者说定时任务中的工作是延时不敏感的。例如，订单未确认
     虽然希望是七天确认，但是延时带来的八天也是可以接受的。
   * 如果需要严格的时间管理，可能不应该采用定时任务机制。
     
2. 定时任务中数据库查询问题

   目前这里的两个定时任务都是简单的查询数据库以后处理所有符合情况的订单。
   理论上可能存在大量符合情况的订单，这样在定时任务处理大量工作可能不是很好。
   但是，本项目的场景是小微型企业，因此设想的订单业务量不会很大，因此这里简化。
   
3. 分布式环境下相同定时任务问题

   如果两台云主机都部署小商城后台服务，那么这里也会出现两个相同的定时任务。
   虽然相同定时任务导致的并发问题可以通过锁机制解决，对系统实际业务不会造成影响，
   但是相同定时任务同时存在仍然是不合理的，因此应该避免。可行做法是从小商场后台
   模块中剥离这些定时任务形成一个独立任务模块，然后单独部署，从而保证分布式环境
   下定时任务是唯一存在的。通常，这个任务模块是基于quartz技术。
   
   但是目前本项目设想场景是小商场后台仅部署一台主机，同时系统中定时任务不是很多，
   因此这里定时任务仍然是耦合在小商城后台服务模块中。因此开发者需要注意到这里
   存在的潜在问题。

### 3.1.18 并发控制

参考`2.2.8 乐观锁`

当乐观锁更新失败时采用多次尝试方案。

### 3.1.19 事务管理

### 3.1.20 开发技巧

当小商城后台服务开发中因为测试或者debug可能需要经常性重启应用，此时
一旦重启，将导致小商场的小程序段的token失效，因此要求用户再次登录。
这里，介绍一个小技巧:
开发时，


## 3.2 litemall-wx

这里的代码基于[nideshop-mini-program](https://gitee.com/tumobi/nideshop-mini-program)，但是做了一定的修改：

* 数据属性名称调整，原项目中数据属性名称是下划线法命名（例如goods_id），而这里采用骆驼式命名法（例如goodsId），因此代码中需要进行相应调整；
* 代码清理重构，删除了一些目前不必要的文件，梳理一些逻辑功能；
* BUG修补，修改了一些错误；
* 功能完善拓展，例如商品立即购买功能、商品评价功能；

具体变化可以采用工具进行对比。

注意
> 目前litemall-wx项目代码基于nideshop-mini-program的commit版本[acbf6276eb27abc6a48887cddd223d7261f0088e](https://github.com/tumobi/nideshop-mini-program/commit/acbf6276eb27abc6a48887cddd223d7261f0088e)。由于改动变化较大，因此之后litemall-wx将独立开发，nideshop-mini-program的跟新不一定会合并到litemall-wx中。

### 3.2.1 业务API设置

业务API存放在`config/api.js`。

但是可以发现这样的代码：

```
// 本机开发时使用
var WxApiRoot = 'http://localhost:8082/wx/';
// 局域网测试使用
// var WxApiRoot = 'http://192.168.0.101:8082/wx/';
// 云平台部署时使用
//  var WxApiRoot = 'http://122.152.206.172:8082/wx/';
```

也就是说这里存在三种类型的API服务地址，这里是考虑到开发存在三种情况：

1. 本机开发时，localhost是当前开发机的地址；
2. 手机预览时，192.168.0.101是开发机的IP地址；
3. 当后台部署在云主机中时，122.152.206.172是云主机的IP地址；
4. 此外，更最重要的是，如果小程序正式部署时，这里的地址必须是域名，
而不能是IP地址。

因此，开发阶段开发者可以按照具体情况切换1，2或3的选项。

### 3.2.2 页面

#### 3.2.2.1 首页

#### 3.2.2.

#### 3.2.2.

#### 3.2.2.

#### 3.2.2.

#### 3.2.2.

#### 3.2.2.

#### 3.2.2.
#### 3.2.2.

#### 3.2.2.
#### 3.2.2.
#### 3.2.2.
#### 3.2.2.
#### 3.2.2.
#### 3.2.2.


### 3.2.3 登录设计

按照官方文档，开发者采用`wx.login`方法即可实现登录操作；
然而，由于`wx.login`只能返回临时登录凭证`code`，从服务器也只能返回对应的sessionId,
因此虽然已经可以视作登录，但是在小程序中不能显示有意义的登录状态，
因此实际很多小程序是继续采用`wx.getUserInfo`来进一步请求用户信息。

因此本模块中，用户的登录状态也是由`wx.login`和`wx.getUserInfo`组成。

#### 3.2.2.1 登录检测

开发者可以采用`user.checkLogin`来检查是否`已登录`，而其检测逻辑是：

1. 可以从storage获取`userInfo`和`token`
2. 同时`wx.checkSession`也成功。

但是如果每次都使用`checkLogin`可能也不太好，因此目前机制是：
1. 应用启动时就检测一次，如果登录则设置app.globalData.hasLogin为已登录状态;
之后，其他页面只要查看这个状态即可知道目前是否已登录；
2. 此外，如果后台token过期返回401代码时，则及时清除过期的登录状态信息；
而用户登录失败时则app.globalData.hasLogin为未登录状态。

注意：
> 这里的逻辑可能有点乱。。。，但是目前实际效果看没有问题。

#### 3.2.2.2 登录时机

登录请求用户信息的时机存在两种设计：

1. 一种是小程序加载时，即申请用户信息，这种实现较简单；
2. 另外一种是小程序加载时不需要，但是小程序用户需要真正用户信息时才申请用户信息，
而这种实现较复杂。

目前采用第二张实现，可以分成两种情况：

* 用户主动登录

  用户主动登录，指的是`我的`页面中用户没有登录显示`点击登录`的效果。

* 用户被动登录

  用户被动登录，指的是用户想购买商品或者需要用户登录才能操作的行为，
  此时因为向服务器请求时token没有设置，因此服务器拒绝用户的请求，同时返回`401`业务代码。
 
  目前需要检测用户登录的页面有:
   
   * 购物车
   * 我的主页

讨论:
> 对于第二张情况，原nideshop-mini-program项目是采取一种自动登录的方式。
> 这里则采用跳转登录页面的方式。

#### 3.2.2.3 登录操作

如前面讨论，这里的登录操作实际包含两个操作`wx.login`和`wx.getUserInfo`。
开发者可以采用`user.loginByWeixin`来进行登录操作。

按照官网文档，用户登录前应该检测以下，来避免频繁无意义的登录操作，
因此较合适的做法如下所示:

```
    user.checkLogin().catch(() => {

      user.loginByWeixin().then(res => {
        this.setData({
          userInfo: res.data.userInfo,
        });
      }).catch((err) => {
        util.showErrorToast('登录失败');
      });

    });
```

#### 3.2.2.4 登录拒绝授权

还存在一个问题，当用户登录时，会出现"微信授权"的对话框。
如果用户选择”拒绝“，那么之后用户的登录操作总是失败的。

目前的做法是，用户拒绝授权后，如果再次登录，则：
1. 弹出对话框，请求用户授权
2. 如果用户仍然拒绝，则返回
3. 如果用户接受授权，则弹出系统权限配置页面，等待用户给与授权。

### 3.2.4 立即购买和放入购物车

### 3.2.5 storage使用

本模块中采用storage来存储一些数据，以及组件间进行通信。

#### 3.2.5.1 userInfo和token

#### 3.2.5.2 cartId

#### 3.2.5.3 addressId

## 3.3 开发新功能

本章节介绍如何开发新的微信小程序功能。

### 3.3.1 小商场页面开发

### 3.3.2 交互服务API设计

### 3.3.3 后台服务开发

### 3.3.4 数据库