README.md 13.6 KB
Newer Older
Z
zhangZhiHao 已提交
1
# admin
张致豪 已提交
2 3 4 5 6 7 8 9 10 11 12 13 14 15
### 前言
自己一直想做个包括前后端的项目,了解和熟悉一个网站从构建到部署整个流程。正好以前做过一个react的[后台模板(纯前端)](https://blog.csdn.net/qq_37860930/article/details/81327320)。另外在慕课网找了一个node项目学习后,开始自己做一个前后台项目。

所有权限都在后台做,比如非管理员不能添加和删除作品,如果仅靠前台禁用按钮,用户可以直在浏览器修改dom属性绕过禁用功能,所以前台只是起对不同权限用户展示不同UI的功能,而不是通过前台做权限功能。

想测试多个账号在线,请用不同的浏览器登陆

**项目地址**[github地址](https://github.com/zhangZhiHao1996/admin)  
**预览地址**[预览地址](http://47.99.130.140/admin/)
<br/>



### 1.项目截图
张致豪 已提交
16 17 18 19
![在这里插入图片描述](https://github.com/zhangZhiHao1996/image-store/blob/master/admin/01.png?raw=true)
![在这里插入图片描述](https://github.com/zhangZhiHao1996/image-store/blob/master/admin/02.png?raw=true)
![在这里插入图片描述](https://github.com/zhangZhiHao1996/image-store/blob/master/admin/03.png?raw=true)
![在这里插入图片描述](https://github.com/zhangZhiHao1996/image-store/blob/master/admin/04.png?raw=true)
张致豪 已提交
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257

<br/>

### 2.项目功能
- [x] 用户注册功能
- [x] 用户登录功能
- [x] 修改账号信息上传头像等功能
- [x] 网站换肤、切换全屏功能
- [x] 网站留言板
- [x] 聊天室

[jwt实现注册与登陆系统](https://blog.csdn.net/qq_37860930/article/details/96145590)  
[react+koa实现留言板功能](https://blog.csdn.net/qq_37860930/article/details/96150575)  
[webSocket实现聊天室功能](https://blog.csdn.net/qq_37860930/article/details/96302728)  
[react编写打字组件](https://blog.csdn.net/qq_37860930/article/details/96285201)  
[antd实现换肤功能](https://www.jianshu.com/p/b635211658c8)  
[富文本编辑器braft-editor的使用](https://blog.csdn.net/qq_37860930/article/details/97115621)  

<br/>

### 3.前端
前端使用的技术栈:`react、react-router、redux、canvas、fetch、antd、websocket、es6`
<br/>

##### 3.1创建
使用`create-react-app`脚手架搭建了前端项目并扩展了webpack的配置(配置可以参考[这里](https://blog.csdn.net/qq_37860930/article/details/85162024))。
新增修饰器语法,这是ES7的一个新语法,作用就是修改组件的一些属性。比如路由的withRouter将组件包裹,可以在组件的props上传递history对象等等。

```javascript
//不使用修饰器语法
withRouter(Test)

//使用修饰器语法
@withRouter
class Test extends React.Component{
	...
}
```
当有一个组件需要多个高阶组件包裹时,这种写法就很有优势

```JavaScript
withRouter(Form.create()(Test))

@withRouter @Form.create() ...
class Test extends React.Component{
	...
}
```
当然包裹的顺序也很重要

<br/>



##### 3.2依赖

 - 全屏插件[screenfull](https://github.com/sindresorhus/screenfull.js)
 - antd皮肤在线更换[antd-theme-generator](https://github.com/mzohaibqc/antd-theme-generator)

自己根据需求封装了一个fetch来进行前后台通信,使用简单。

```javascript
const res = await get('/test')
console.log(res)
```

##### 3.3组件
`公用组件`应该完全隔离`业务逻辑`,我通过事件分发和props传递编写了以下组件,这些组件完全隔离了业务逻辑,所以在其它项目中也可以使用。

组件     | 组件的作用 | 组件接收的props
-------- | ----- | -----
AnimatedBooks  | 展示翻书动画效果的组件 |  content(内容) 、cover(封面)
Background | 登录粒子背景 | url(背景图)
ColorPicker | 拾色器 | color(当前颜色)、onChange(颜色改变的函数)
Loading | loading动画 | 
Typing | 打字插件 |  delay(打印延时)、frequency(打印频率)、done(打印完成的回调)

##### 3.4页面
页面可以拆分成不同的组件,然后将组件拼接成一个页面,使得页面维护和编写变得简单。但是会带来不同组件之间通信的问题,解决的方法有两个:

 - 状态提升
 - 状态管理

`状态提升`适用于组件关系简单的通信,比如兄弟组件之间的通信,就可以将状态提升到它们的父组件中,通过props和事件传递给子组件。这种方式不适合层级结构太深的组件通信,否则一层层传递太过麻烦。

在Index页面中,我将页面按照UI拆分成了`头部、侧边栏、中间部分、底部`组件,然后使用了状态提升进行组件之间的通信。

`状态管理`适用于需要全局使用的状态或者组件嵌套复杂的状态,



至于如何拆分页面,我认为可以按**UI拆分****功能拆分**,如果拆分的组件可以共用,我们可以进一步抽象成公共组件使用(公共组件就是将业务逻辑解耦出去)

<br/>

### 4.项目后端
后端采用的是`koa、mysql`使用了一些koa的中间件
<br/>

##### 4.1创建
后端通过`koa-generator`脚手架搭建后端项目
这是我第一次写后端项目,对于后端的的`mvc`也不算特别熟悉,参考网上的介绍后以自己的理解去写了,routes文件存储路由、controller文件件实现对应的路由逻辑,如果有问题欢迎指出。

**为什么要使用框架?**
原生的`node`只提供了最基本的功能,我们还需要编写一些额外的代码才能达到我们的要求,比如获取前台传过来的参数,原生node只提供了url属性,需要我们自己解析url来获取querying,而`koa2`框架已经封装好了,直接在ctx中提供了query对象;还有前台post的数据、cookie、session、路由等,在原生中都需要我们自己处理,而`koa2`框架直接封装好了,就算`koa2`没有提供也有很多其他人编写好了的`中间件`供我们使用。

我认为使用的框架的意义就是编写更好可维护的代码,**减少和业务逻辑无关的代码**,比如解析前台参数、数据库连接等等重复的代码。

之所以用`koa2`而不用`express`是相较于promise我更喜欢async/await。


**接口规范**

当然也有很多方式定义状态码,只有自己写好文档即可。
```javascript
{
	"status":0,     //0代表成功,1失败
	"httpCode":200,    
	"message":"请求成功",
	"data":{}
}
```
<br/>

### 5.项目部署
##### 5.1项目打包
我将前端项目打包到node服务器上,
我想访问网址根目录就能直接访问项目首页了

```javascript
//app.js
app.use(views(__dirname + '/public/build'))   //这里的路径是前端项目打包后放在后端的位置

//routes ->index.js
router.get('/', async (ctx, next) => {
	await ctx.render('index.html')
})
```
由于我们前端使用的是HTML5的history,页面一刷新就404了,所以后端要配置。我使用了[koa-connect-history-api-fallback](https://github.com/davezuko/koa-connect-history-api-fallback)中间件来支持。
中间件实现的功能是如果 当URL 匹配不到任何静态资源,返回指定的页面(中间件默认返回的是index.html,配置参考[文档](https://github.com/bripkens/connect-history-api-fallback)

```javascript
//app.js
const historyApiFallback = require('koa-history-api-fallback')
app.use(historyApiFallback());

```

这里要注意中间的顺序,historyApiFallback一定要放在所有接口路由后面,否则所有接口都是返回index.html了。
historyApiFallback一定要在静态资源前面,否则资源找不到

```javascript
//app.js
const historyApiFallback = require('koa-history-api-fallback')
// routes
app.use(index.routes(), index.allowedMethods())
app.use(user.routes(), user.allowedMethods())
app.use(works.routes(), works.allowedMethods())
app.use(message.routes(), message.allowedMethods())
app.use(score.routes(), score.allowedMethods())

app.use(historyApiFallback());

app.use(require('koa-static')(__dirname))
app.use(require('koa-static')(__dirname + '/public/build'))
app.use(require('koa-static')(__dirname + '/public/upload-files'))

```

另外项目用了jwt进行权限认证了,前端打包后的资源(css、js、图片等)这些资源不应该权限认证,否则401,前端获取不到这些js来生成页面了


上线时,创建数据报错`Unknown collation: 'utf8mb4_0900_ai_ci'`,我的数据库版本不支持这种格式。`SHOW COLLATION`,可查看数据库所有支持的collation,我将排序规则改为`utf8mb4_general_ci`
<hr/>





### 总结
期间遇见的问题我都是边找边学边用,磕磕绊绊完成了这个小项目。通过这个小项目,我熟悉了一个基本的网站从零到上线的整个流程,并从中发现问题、解决问题。这也是我写这个项目的初衷。我并没有过多深入每个细节,如数据库的字段设计,加密、防止mysql的注入、数据库优化等。




部署时,发现网页太慢了,查看network发现
用了nginx的gzip进行了压缩。
压缩前
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190715144426371.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3ODYwOTMw,size_16,color_FFFFFF,t_70)
压缩后
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190715144501968.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3ODYwOTMw,size_16,color_FFFFFF,t_70)
可以看到size明显变小了,但是请求的时间还是有点长
下面是我的nginx配置

```javascript
upstream admin{                                                                                                            
    server 47.99.130.140:8888;                                                                                             
}                                                                                                                          
upstream reactMusic{                                                                                                       
    server 47.99.130.140:8088;                                                                                             
}                                                                                                                          
                                                                                                                           
                                                                                                                           
                                                                                                                           
server{                                                                                                                    
     gzip on;                                                                                                              
     gzip_min_length 1k;                                                                                                   
     gzip_buffers 4 16k;                                                                                                   
     gzip_comp_level 6;                                                                                                    
     #gzip_types text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php;     
     gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/x
ml application/xml+rss text/javascript application/x-font-woff;                                                            
     gzip_http_version 1.1;                                                                                                
     gzip_vary on;                                                                                                         
     gzip_proxied off;                                                                                                     
                                                                                                                           
     listen 80;                                                                                                            
     server_name 47.99.130.140;                                                                                            
     location /admin/ {                                                                                                    
          proxy_redirect   off;                                                                                            
            proxy_set_header Host $host;                                                                                   
            proxy_set_header X-Forwarded-For $remote_addr;                                                                 
            proxy_set_header X-Real-IP $remote_addr;                                                                       
            proxy_pass http://admin/;                                                                                      
     }                                                                                                                     
     location /react-music/ {
                 proxy_set_header Host $host;                                                                              
            
            proxy_set_header X-Forwarded-For $remote_addr;
            proxy_set_header X-Real-IP $remote_addr;
                proxy_pass http://reactMusic/;   
     }

}                                                                                                        
                                                                           
                                                                           

```