# 3. 小商场

技术：

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

目前发现存在的一些问题：

* `缺失`后台服务返回的token存在有效期，小商场应该自动刷新
* `缺失`账号多次登录失败，应该小商城出现图片验证码限制，或者后台账号锁定
* `改善`商品搜索中采用更好的搜索机制
* `改善`商品搜索中，支持热门搜索"换一批"
* `改善`地址优化，目前每一次点击都会请求后台，应该缓存已有的数据
* `改善`商品数量和规格中，如果货品数量不足，则显示效果，通常是是两种效果
  * 某个规格选择以后，另外规格的某些规格是实线，而某些是虚线，
  * 商品的都规格选择以后，“立即购买”和“加入购物车显示”灰色
* `改善`商品好评计算与显示，例如90%好评
* `改善`商品的评论列表中显示评价的评论分数、商品规格
* `改善`商品的评论列表中的图片点击可放大，同时用户评价的多个图片可以选择左右滑动查看。
* `改善`商品的订单中支持订单号搜索功能
* `改善`在一些内容比较多的页面中支持“顶部”功能

## 3.0 小商场环境

按照项目README文档中的“快速启动”一节，开发者可以快速启动小商场项目。 但是小程序端只可以显示数据和图片，而微信登录会失败、微信支付也会失败， 因为appid不是开发者自己的，

这里进一步介绍开发者需要设置的小商场环境。

### 3.0.1 微信登录配置

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

1. 在litemall-core模块的src/main/resources的application-core.yml资源文件中设置

   ```
    litemall
        wx
            app-id: 开发者申请的app-id
            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-core-api模块的src/main/resources的application-core.yml资源文件中设置

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

   注意

   > * notify-url是微信商户平台向小商场后台服务发送支付结果的地址。
   >
   >   &#x20;因此这就要求该地址是可访问的。
   > * 目前小商场后台服务的默认request mapping是`/wx/order/pay-notify`（见WxOrderController类的payNotify）,
   >
   >   &#x20;因此notify-url应该设置的地址类似于`http://www.example.com/wx/order/pay-notify`
   > * 当开发者真正上线后台服务时，强烈建议默认request mapping要重新命名，不能对外公开。
2. 启动后台服务
3. 部署后台服务到云服务器
4. litemall-wx的api.js设置云服务器的域名。 编译运行，尝试微信支付。

### 3.0.3 微信退款配置

目前管理平台的退款功能需要进行微信商户退款配置

1. 从微信商户平台下载商户证书（或者叫做API证书），保存到合适位置，

   请阅读[文档](https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=4_3)
2. 在litemall-core-api模块的src/main/resources的application-core.yml资源文件中设置

   ```
    litemall
        wx
            key-path: 证书文件访问路径
   ```
3. 启动小程序前端和后端，进行下单、支付、申请退款操作
4. 启动管理后台前端和后端，进行订单退款操作，然后验证手机是否收到退款。

注意：

> 虽然这里管理后台退款接入了微信退款API，但是从安全角度考虑，**强烈建议** 开发者删除管理后台微信退款代码，然后分成两个步骤实现管理员退款操作：
>
> * 首先，管理员登录微信平台进行退款操作；
> * 然后，管理员登陆管理后台点击退款按钮，进行订单退款状态变更和商品库存回库。

## 3.1 litemall-wx-api

本节介绍小商场的后台服务模块。

### 3.1.1 授权服务

见WxAuthController类。

### 3.1.2 首页服务

见WxHomeController类。

### 3.1.3 类目服务

见WxCatelogController类。

### 3.1.4 商品服务

见WxGoodsController类。

### 3.1.5 品牌服务

见WxBrandController类。

### 3.1.6 专题服务

见WxTopicController类。

### 3.1.7 搜索服务

见WxSearchController类。

### 3.1.8 购物车服务

见WxCartController类。

### 3.1.9 订单服务

见WxOrderController类。

### 3.1.10 评价服务

见WxCommentController类。

注意：

> 订单商品评价功能见WxOrderController类的comment方法。

### 3.1.11 团购服务

见WxGrouponController类。

### 3.1.12 收藏服务

见WxCollectController类。

### 3.1.13 足迹服务

见WxFootprintController类。

### 3.1.14 收货地址服务

见WxAddressController类。

### 3.1.15 区域服务

见WxRegionController类。

### 3.1.16 安全

#### 3.1.16.1 Token

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

见WxWebMvcConfiguration类、LoginUser和LoginUserHandlerMethodArgumentResolver类。

小商城后端服务每一次请求都会检测是否存在HTTP头部域`X-Litemall-Token`。 如果存在，则内部查询转换成LoginUser，然后作为请求参数。 如果不存在，则作为null请求参数。

而具体的后端服务controller中，则可以利用LoginUser来检查。

例如用户地址服务中：

```
@RestController
@RequestMapping("/wx/address")
@Validated
public class WxAddressController {
    @GetMapping("list")
    public Object list(@LoginUser Integer userId) {
        if (userId == null) {
            return ResponseUtil.unlogin();
        }

        ...
    }
```

如果检测`userId`是null，则返回错误信息“用户未登录”。

## 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的更新。

### 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.51.199.160:8082/wx/';
```

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

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

   而不能是IP地址。

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

### 3.2.2 页面

* 首页
* 专题页
* 专题详情页
* 专题评论页
* 专题评论添加页
* 品牌页
* 品牌详情页
* 人气推荐页
* 新品首发页
* 分类页
* 分类详情页
* 查找页
* 商品详情页
* 商品评论页
* 购物车页
* 下单页
* 下单地址页
* 下单地址添加页
* 支付结果页
* 个人页
* 订单列表页
* 订单详情页
* 优惠券页
* 收藏页
* 足迹页
* 收货地址页
* 收货地址添加页
* 登录页
* 注册页
* 找回密码页

### 3.2.3 登录设计

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

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

#### 3.2.3.1 登录检测

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

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

但是如果每次都使用`checkLogin`可能也不太好，因此目前机制是：

1. 应用启动时检测一次，如果登录则设置app.globalData.hasLogin为已登录状态;

   之后，其他页面只要查看这个状态即可知道目前是否已登录；
2. 如果后端token过期返回501错误码时，则前端清理`userInfo`和`token`；
3. 用户执行退出操作，则清理`userInfo`和`token`，同时设置hasLogin未登录状态。

注意：

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

#### 3.2.3.2 登录时机

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

1. 一种是小程序加载时，即申请用户信息，这种实现较简单，但是用户体验可能不是很好；
2. 另外一种是小程序加载时不需要，但是小程序用户需要真正用户信息时才请求用户登录，

   而这种实现较复杂。

目前采用第二种方式实现，这里又可以进一步分成两种情况：

* 用户主动登录

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

  用户被动登录，指的是用户想购买商品或者需要用户登录才能操作的行为， 此时因为向服务器请求时token没有设置，因此服务器拒绝用户的请求，同时返回`501`业务代码。

  以上无论哪种情况，都会导致用户被重定向到`登录`页面来进行登录操作。

#### 3.2.3.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.3.4 登出操作

在`个人`页面，如果用户已经登录，则会出现`退出登录`按钮，支持用户退出当前登录状态。

退出逻辑如下所示：

```
        util.request(api.AuthLogout, {}, 'POST');
        app.globalData.hasLogin = false;
        wx.removeStorageSync('token');
        wx.removeStorageSync('userInfo');
        wx.reLaunch({
          url: '/pages/index/index'
        });
```

### 3.2.4 storage

litemall-wx模块采用storage来存储一些数据，以及支持组件间数据通信。

#### 3.2.4.1 userInfo和token

#### 3.2.4.2 cartId

#### 3.2.4.3 addressId

### 3.2.5 加入购物车和立即购买

### 3.2.6 团购

## 3.3 renard-wx

renard-wx是另外一个小程序前端，其后端API也是litemall-wx-api。

和litemall-wx的区别是： 1. 界面样式有所调整； 2. 功能进一步简化。

## 3.4 开发新组件

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

### 3.4.1 小商场前端页面

### 3.4.2 前后端交互服务API

### 3.4.3 小商场后端服务

### 3.4.4 数据库
