# 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'


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://gb.akanote.cn/cms/part5-qian-hou-duan-deng-lu-zhu-ce-shi-xian.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
