> For the complete documentation index, see [llms.txt](https://gb.akanote.cn/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://gb.akanote.cn/cms/part5-qian-hou-duan-deng-lu-zhu-ce-shi-xian.md).

# Part5-前后端登录注册实现

## 一、前端登录注册界面布局

借助Antd的 [表单组件（登录框）](https://ant.design/components/form-cn/#components-form-demo-normal-login) 可以快速实现。

## 二、注册接口开发

### 1、JWT简介

Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。

注册必然要产生token，这是用户的登录凭证。JWT的官网地址：<https://jwt.io/>。

一个JWT实际上就是一个字符串，它由三部分组成，`头部`、`载荷`与`签名`。

### 2、Koa中生成token

#### a. 安装

```shell
$ npm i jsonwebtoken -S
```

#### b. 生成token

```js
const jwt = require('jsonwebtoken');

// 根据username和password生成token
let token=jwt.sign(
    {username,password},    // 携带信息
    'zhaowenxian',          // 秘钥
    {expiresIn:'1h'}        // 有效期：1h一小时
)
```

> expiresIn：以秒表示或描述时间跨度zeit/ms的字符串。如60，"2 days"，"10h"，"7d"，Expiration time，过期时间
>
> 更多字段可参考：<https://blog.csdn.net/weixin_34314962/article/details/89057225>

### 3、鉴权

已经产生了token，前端调用接口时传过来的token，就需要后端进行比较，我们称之为 `鉴权` 。

```js
//鉴权
const auth=async (ctx,next)=>{
    try{
        // let result = jwt.verify(token,'zhaowenxian')//解构
        jwt.verify(token,'zhaowenxian')//验证
        
    }catch(err){
        //try中报错就会走catch，
        ctx.body={
            code:500,
            message: "token无效或登录已过期"
        }
        return;
    }
    await next();//放行函数，继续往下走
}
router.get('/api/edit',auth,async (ctx,next)=>{//在参数里执行
//需要权限才加auth，没有next（）放行函数，不会往下走
    ctx.body={
        code:200
    }
})
```

## 三、Koa获取请求体

Koa获取请求体的方式是使用第三方插件：

```shell
$ npm i koa-bodyparser
```

然后在 `app.js` 中直接引入并调用：

```js
const bodyParser = require('koa-bodyparser');

// 调用router中间件
app.use(bodyParser());	// bodyParser
app.use(router.routes(), router.allowedMethods());
```

## 四、Axios+TS的请求封装

前端React管理系统项目使用Axios进行请求，封装的过程结合ts使用。

在 `src` 下创建 `request/api.ts` 与 `request/request.ts`。

#### 1、封装request

```typescript
import axios from 'axios'

// 配置项接口
interface AxiosOption {
    baseURL: string;
    timeout: number;
}

// 配置项
const axiosOption: AxiosOption = {
    baseURL: 'http://localhost:9000/manage',
    timeout: 5000
}

// 创建一个单例
const instance = axios.create(axiosOption);

// 添加请求拦截器
instance.interceptors.request.use(function (config) {
  let token = localStorage.getItem("x-auth-token");
  if (token) {
    config.headers = {
      "x-auth-token": token
    }
  }
  return config;
}, function (error) {
  // 对请求错误做些什么
  return Promise.reject(error);
});

// 添加响应拦截器
instance.interceptors.response.use(function (response) {
  // 对响应数据做点什么
  return response.data;
}, function (error) {
  // 对响应错误做点什么
  return Promise.reject(error);
});

export default instance;
```

#### 2、导出api

在 `api.ts` 中：

```typescript
import request from './request'

interface IRegisterParams {
    username: string | number;
    password: string | number;
}

// 注册
export const RegisterApi = (params: IRegisterParams) => request.post('/register', params)
```

#### 3、调用api

```tsx
import { RegisterApi } from "request/api";
import { message } from 'antd';

interface IValues {
    username: string | number;
    password: string | number;
}

// 执行注册
const onFinish = (values: IValues) => {
    RegisterApi(values).then((res: any) => {
        if(res.errCode===0){
            message.success(res.message);
        }else{
            message.error(res.message);
        }
    });
};
```

## 五、接口文档

小幺鸡（docway）平台：<http://xiaoyaoji.cn/>

## 六、React Redux管理用户信息

首先，安装redux与react-redux：

```shell
$ npm i redux react-redux
```

### 1、reducer

在 src 下新建 `store/reducer.ts`：

```typescript
interface IState {
  username: string;
  player: string;
  avatar: string;
}

// 定义默认数据
const defaultState: IState = {
  username: "",
  player: "",
  avatar: "",
};

interface IAction {
  type: string;
  value?: unknown;
}

// eslint-disable-next-line
export default (state = defaultState, action: IAction) => {
  let newState = JSON.parse(JSON.stringify(state));
  switch (action.type) {
    case "ChangeUsername":	// 修改名称
      newState.username = action.value;
      break;
    case "ChangePlayer":	// 修改角色
      newState.player = action.value;
      break;
    case "ChangeAvatar": 	// 修改头像
      newState.avatar = action.value;
      break;
    default:
      break;
  }
  return newState;
};
```

### 2、store

在 src 下新建 `store/index.ts` ：

```typescript
import { createStore } from "redux";
import reducer from './reducer'

const store = createStore(reducer)
export default store;
```

### 3、调用

组件中使用connect进行调用：

```typescript
import { connect } from "react-redux";

const mapStateToProps = (state: IState) => {
  return {
    userName: state.username,
    avatar: state.avatar,
  };
};

const mapDispatchToProps = (dispatch: any) => {
  return {
    // 修改react-redux的用户名
    changeUsername<T>(value: T):void{
      dispatch({type: "ChangeUsername", value})
    },
    // 修改react-redux的密码
    changePlayer<T>(value: T):void{
      dispatch({type: "ChangePlayer", value})
    },
    // 修改react-redux的头像
    changeAvatar<T>(value: T):void{
      dispatch({type: "ChangeAvatar", value})
    },
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(组件名称);
```

## 七、React环境变量配置

React的环境变量配置可以通过.env文件进行配置，建议使用插件配合。

### 1、cross-env插件

```shell
$ npm install --save-dev cross-env
```

然后打开 `config/env.js`，搜索 `getClientEnvironment` ，在其中添加：

```js
function getClientEnvironment(publicUrl) {
  const raw = Object.keys(process.env)
    .filter(key => REACT_APP.test(key))
    .reduce(
      (env, key) => {
        env[key] = process.env[key];
        return env;
      },
      {
        ...
        SERVER_HOST_PORT: process.env.SERVER_HOST_PORT,  // 添加这一句
        SERVER_HOST: process.env.SERVER_HOST  // 添加这一句
      }
    );
  return { raw, stringified };
}
```

然后在项目根目录新建两个文件：

`.env` 开发环境：

```
NODE_ENV = development
SERVER_HOST_PORT = http://localhost:9000/manage
SERVER_HOST = http://localhost:9000
```

`.env.production` 生产环境：

```
NODE_ENV = production
SERVER_HOST_PORT = 线上真实接口地址
SERVER_HOST = 线上真实项目
```

### 2、项目运行、测试与打包

当你运行 npm start 时，它总是等于 'development' ，

当你运行 npm test 它总是等于 'test' ，

当你运行 npm run build 来生成一个生产 bundle(包) 时，它总是等于 'production'
