# go-web-demo
**Repository Path**: hoowaynew/go-web-demo
## Basic Information
- **Project Name**: go-web-demo
- **Description**: 使用go语言开发的demo级别web项目,项目技术栈包含MySQL,Redis,Gin框架以及简单的前端页面等
- **Primary Language**: Unknown
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 3
- **Forks**: 1
- **Created**: 2021-10-10
- **Last Updated**: 2024-05-15
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
#
**Entry Task技术文档**
[toc]
## 一、背景及目的
EntryTask项目,通过实现RPC通用框架,使用Go HTTP API,MySQL或Redis操作等能力完成一个用户管理系统,主要功能包含用户(管理员)登录及用户数据的增删查改功能。

1.用户登陆页面

2.用户登陆用户不存在

3.用户登陆密码不正确

4.用户登陆后信息展示

4.用户信息变更后页面
## 二、逻辑架构设计
整体分为API层(RPC consumer)、TCP Server层(RPC provider)两层,用户上传的头像图片存放于磁盘static/pic/路径下,项目的具体代码结构组织如下。

``` html
├── README.md
├── main.go // 服务端程序入口
├── conf
│ ├── config.yaml // 配置文件,如web和rpc的启动地址和MySQL和Redis的参数配置
├── pkg // 依赖框架代码包路径
│ ├── error // 自定义错误码代码包
│ │ ├── code.go // 错误码统一管理类
│ │ └── error.go // 自定义错误类型封装类
│ └── rpc // rpc框架包
│ ├── session.go // 网络回话调用实现类,用于从连接conn中读取和写入数据
│ ├── codec.go // 序列化编解码实现类
│ ├── server.go // rpc服务端框架实现类
│ ├── pooled_obj.go // rpc客户端线程池实现类(go-common-pool)
│ ├── pooled_client.go // rpc客户端池化实现类
│ └── cleint.go // rpc客户端框架实现类
├── rpc // rpc服务端业务实现核心包
│ ├── config
│ │ ├── config.go // 配置类,用于启动加载MySQL和Redis等配置
│ ├── dal
│ │ ├── model_dao.go // 原始crud的dao类
│ │ ├── user_manager.go // user操作数据库类
│ ├── manager
│ │ ├── rpc_register.go // rpc接口注册类
│ └── service
│ ├── user.go // 真正user服务类
│ └── cache.go // 缓存Redis操作类
├── facade
│ └── facade.go // rpc服务接口调用类
├── go.mod
├── go.sum
├── handler
│ ├── response_handler.go // 统一的web response处理器
│ └── user_service.go // api操作service类,如用户登陆,用户信息更新等
├── log // 日志记录文件包路径
├── model // 业务领域model包
│ └── user.go
├── router // api路由包
│ ├── middleware
│ │ ├── middleware.go // gin框架http请求头设置类
│ │ └── rpc_provider.go // rpc接口配置及线程池配置类
│ └── router.go // api程序router路由
├── static // 静态资源和图片路径
│ ├── pic // 头像图片上传路径
│ │ └── pic1.jpg
│ ├── index.html // 登陆态用户信息落地页
│ └── login.html // 用户登陆页面
├── test // 测试用例包路径
│ ├── rpc_test.go
│ └── session_test
│
└── util // 工具类路径
```
## **三、核心逻辑详细设计**
### 0、用例图

### 1、用户登录

### 2、登陆态用户信息回显

### 3、用户信息更新(nickname和头像)

## **四、接口设计**
*给出对外接口描述,包括名称、地址或URL、类型(GRPC/HTTPS等)、协议内容(PB/JSON等)、各参数(类型、描述、限制)等;*
*对外接口需要给出鉴权机制和其他安全性考虑;*
### *1.创建新用户(http)*
#### Reuqest
- Method: **POST**
- URL: *http://localhost:8181/user/token/login*
- DESC:用户登陆接口(*用户登陆鉴权采用cookie-session机制,httpOnly设置为true防止前端js获取造成XSS攻击*)
- Headers: Content-Type:multipart/form-data
- Body:
```json
{
"username" : "hooker",
"password" : "123456"
}
```
#### Response
- Body
```json
{
"code": 0,
"message": "OK",
"data": {
"id": 0,
"username": "hooker",
"nickname": "hooker520",
"profile": "./static/pic/1634291433769046000_pic2.jpg"
}
}
```
### *2.用户态查询(http)*
#### Reuqest
- Method: **GET**
- URL: **http://localhost:8181/user/token/query**
- DESC:根据用户的token获取用户信息
- Headers: Content-Type:multipart/form-data;Cookie:token=E50E4F994DE9196A7CC3479621F96B82
- Body:
```json
null
```
#### Response
- Body
```json
{
"code": 0,
"message": "OK",
"data": {
"id": 0,
"username": "hooker",
"nickname": "hooker520",
"profile": "./static/pic/1634291433769046000_pic2.jpg"
}
}
```
### *3.用户信息更新(http)*
#### Reuqest
- Method: **POST**
- URL: **http://localhost:8181/user/token/update**
- DESC:更新用户的nickname和profile接口
- Headers: Content-Type:multipart/form-data;Cookie:token=E50E4F994DE9196A7CC3479621F96B82
- Body:
```json
{
"username" : "hooker",
"password" : "123456",
"profile": "./static/pic.png"
}
```
#### Response
- Body
```json
// 发起重定向跳回用户信息落地页面
context.Redirect(http.StatusMovedPermanently, "../static/index.html")
```
### *4.用户信息查询(http)*
#### Reuqest
- Method: **GET**
- URL: **http://localhost:8181/user/name/query**
- DESC:根据用户名查询用户信息接口
- Headers: Content-Type:application/json
- Body:
```json
null
```
#### Response
- Body
```json
// 发起重定向跳回用户信息落地页面
{
"code": 0,
"message": "OK",
"data": {
"id": 0,
"username": "hooker",
"nickname": "hooker520",
"profile": "./static/pic/1634291433769046000_pic2.jpg"
}
}
```
4. *http://localhost:8181/user/add:用户添加接口 -暂未使用*
### *1.用户登陆(rpc)*
#### Reuqest
```go
method:var UserLogin func(request *UserLoginRequest) (user *User, token string, err *errno.Errno)
// UserLoginRequest 用户登录时的请求对象
type UserLoginRequest struct {
Username string `json:"username"`
Password string `json:"password"`
}
```
#### Response
```go
// User 返回用户信息对象
type User struct {
ID int64 `json:"id"`
Username string `json:"username"`
Nickname string `json:"nickname"`
Profile string `json:"profile"`
}
// Errno user define error
type Errno struct {
Code int
Message string
}
```
### *2.根据姓名查询用户信息(rpc)*
#### Reuqest
```go
method:var UserQueryByName func(username string) (user *User, err *errno.Errno)
```
#### Response
```go
// User 返回用户信息对象
type User struct {
ID int64 `json:"id"`
Username string `json:"username"`
Nickname string `json:"nickname"`
Profile string `json:"profile"`
}
// Errno user define error
type Errno struct {
Code int
Message string
}
```
### *3.根据token查询用户信息(rpc)*
#### Reuqest
```go
method:var UserQueryByToken func(token string) (user *User, err *errno.Errno)
```
#### Response
```go
// User 返回用户信息对象
type User struct {
ID int64 `json:"id"`
Username string `json:"username"`
Nickname string `json:"nickname"`
Profile string `json:"profile"`
}
// Errno user define error
type Errno struct {
Code int
Message string
}
```
### *4.更新用户信息(rpc)*
#### Reuqest
```go
method:var UserUpdateInfo func(request *UserUpdateRequest) (user *User, err *errno.Errno)
// UserUpdateRequest 用户更新昵称和头像时的请求对象
type UserUpdateRequest struct {
Username string `json:"username"`
Nickname string `json:"nickname"`
ProfilePath string `json:"profile"`
}
```
#### Response
```go
// User 返回用户信息对象
type User struct {
ID int64 `json:"id"`
Username string `json:"username"`
Nickname string `json:"nickname"`
Profile string `json:"profile"`
}
// Errno user define error
type Errno struct {
Code int
Message string
}
```
## **五、存储设计**
*可包括:*
*1、用户信息t_user数据库表定义如下,username设置唯一索引;*
```sql
CREATE TABLE `t_user` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '自增ID',
`username` varchar(64) NOT NULL DEFAULT '' COMMENT '用户名',
`nickname` varchar(128) NOT NULL DEFAULT '' COMMENT '昵称',
`password` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9' COMMENT '登录密码,默认UPPER(SHA1(UNHEX(SHA1(''123456''))))',
`profile` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '/profile/default.jpg' COMMENT '用户头像图片地址',
`state` tinyint NOT NULL DEFAULT '1' COMMENT '逻辑状态(1正常,2冻结,0已删除)',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '最近更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `unique_idx_username` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=33 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户信息表'
```
*2、Redis缓存主要利用Hash结构缓存登陆用户信息以及部分string存取登陆的token;*

*3、当前用户上传的头像图片存储服务器磁盘,暂未搭建文件服务器等优化手段;*
*1、数据库表定义、字段定义、索引、主/备库读写等;*
*2、缓存KV设计、加载/更新/失效逻辑等;*
*3、文件存储介质、目录组织形式、数据格式、索引、保存周期与清理机制等;*
*4、消息队列等中间件选型;*
## **六、外部依赖与限制**
*可包括:*
*1、对公司内部跨产品系统或者外部的依赖,如SPM、CoreServer、银行、供应商等。*
*2、受限于外部依赖的现有条件产生的限制,比如接口时延、TPS等;*
1、Mac下可食用Homebrew进行MySQL和Redis安装,会依赖于Homebrew软件*
```markdown
1.安装MySQL和Redis指令
- brew install mysql / brew install redis
2.启动MySQL和Redis指令
- brew services start|stop mysql / brew services start|stop redis
```
*2、MySQL和Redis也可使用Docker进行搭建(默认配置较低需更改配置),会依赖于Docker环境的配置及软件安装*
``` go
1. 首先安装Docker Desktop以及使用终端执行安装brew install docker(首先需要安装homebrew);
2. 然后可以使用docker命令安装MySQL和Redis等软件
// 安装和启动MySQL指令
- docker pull mysql:latest // 安装最新版本的MySQL,可指定版本安装
- docker images // 查看本地安装的docker软件镜像
- docker run -itd --name mysql-master -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql // 安装完成后,首次使用该命令来运行 MySQL 容器
- docker start/stop mysql-master // 后续使用该指令运行和关闭MySQL
- docker ps // 查看当前容器的安装和运行情况
// 安装和启动Redis指令
- docker pull redis:latest // 安装最新版本的 redis,可指定版本安装
- docker run -itd --name redis-master -p 6379:6379 redis // -p 6379:6379:映射容器服务的 6379 端口到宿主机的 6379 端口。外部可以直接通过宿主机ip:6379 访问到 redis 的服务
- docker start/stop redis-master // 后续使用该指令运行和关闭Redis
- docker exec -it redis-master /bin/bash // 然后可以通过 redis-cli 连接测试使用 redis 服务
// 其他软件安装类似,如 nginx
- docker pull nginx:latest // 这里拉取官方的最新版本的镜像
- docker run --name nginx-master -p 8080:80 -d nginx // -p 8080:80: 端口进行映射,将本地 8080 端口映射到容器内部的 80 端口
```
## **七、部署方案与环境要求**
*可包括:*
*1、配置初始化、更改、下发、推送等;*
*2、各种存储容量的预估、需要扩容的实例、备库、账号要求等;*
*3、接入层、逻辑层实例数;*
*4、VIP、域名、防火墙等特殊要求;*
*1、当前核心配置存放在/conf/config.yaml中,包含MySQL和Redis的相关配置,RPC和Web地址配置等*
2、当前图片路径为了便于处理在代码中指定在static/pic/路径下存放
## **八、项目SLA**
*目标:*
- 数据库必须有10,000,000条用户账号信息
- 必须确保返回结果是正确的
- 每个请求都要包含RPC调用以及Mysql或Redis访问
- 200并发(固定用户)情况下,HTTP API QPS大于3000
- - 200个client(200条TCP连接),每个client模拟一个用户(因此需要200个不同的固定用户账号)
- 200并发(随机用户)情况下,HTTP API QPS大于1000
- - 200个client(200条TCP连接),每个client每次随机从10,000,000条记录中选取一个用户,发起请求(如果涉及到鉴权,可以使用一个测试用的token)
- 2000并发(固定用户)情况下,HTTP API QPS大于1500
- 2000并发(随机用户)情况下,HTTP API QPS大于800
*1、可支持的存储容量;*
*2、关键接口可支撑的TPS、QPS、时延等;*
*3、系统可用性保证,如故障时间、恢复时间、Crash率等;*
### 1.并发固定200用户(清空redis缓存)
``` html
➜ test git:(master) ✗ go test -v -bench=Login -benchmem
goos: darwin
goarch: amd64
pkg: entrytask/test
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkLogin
benchmark login parallelism:200
benchmark login parallelism:200
benchmark login parallelism:200
benchmark login parallelism:200
BenchmarkLogin-12 5770 222157 ns/op 6740 B/op 92 allocs/op
PASS
ok entrytask/test 2.712s
```
### 2.并发固定200用户(存在redis)
```html
➜ test git:(master) ✗ go test -v -bench=Login -benchmem
goos: darwin
goarch: amd64
pkg: entrytask/test
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkLogin
benchmark login parallelism:200
benchmark login parallelism:200
benchmark login parallelism:200
benchmark login parallelism:200
BenchmarkLogin-12 5108 227180 ns/op 6822 B/op 93 allocs/op
PASS
ok entrytask/test 1.865s
```
### 3.并定固定2000用户(清空redis)
```html
test git:(master) ✗ go test -v -bench=Login -benchmem
goos: darwin
goarch: amd64
pkg: entrytask/test
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkLogin
benchmark login parallelism:2000
benchmark login parallelism:2000
benchmark login parallelism:2000
benchmark login parallelism:2000
BenchmarkLogin-12 3709 304117 ns/op 15223 B/op 140 allocs/op
PASS
ok entrytask/test 2.234s
```
### 4.并发固定2000用户(存在redis)
```html
➜ test git:(master) ✗ go test -v -bench=Login -benchmem
goos: darwin
goarch: amd64
pkg: entrytask/test
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkLogin
benchmark login parallelism:2000
benchmark login parallelism:2000
benchmark login parallelism:2000
BenchmarkLogin-12 4402 253539 ns/op 14200 B/op 133 allocs/op
PASS
ok entrytask/test 1.294s
```
### 5.并发随机200用户
```html
➜ test git:(master) ✗ go test -v -bench=QueryUser -benchmem -benchtime=5s
goos: darwin
goarch: amd64
pkg: entrytask/test
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkQueryUser
benchmark query user parallelism:200
benchmark query user parallelism:200
benchmark query user parallelism:200
benchmark query user parallelism:200
BenchmarkQueryUser-12 51646 120465 ns/op 4479 B/op 62 allocs/op
PASS
ok entrytask/test 8.105s
```
### 6.并发随机2000用户
```html
➜ test git:(master) ✗ go test -v -bench=QueryUser -benchmem -benchtime=5s
goos: darwin
goarch: amd64
pkg: entrytask/test
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkQueryUser
benchmark query user parallelism:2000
benchmark query user parallelism:2000
benchmark query user parallelism:2000
benchmark query user parallelism:2000
BenchmarkQueryUser-12 43384 135143 ns/op 5300 B/op 67 allocs/op
PASS
ok entrytask/test 7.854s
```
### 7.jmeter压测结果截图

1.固定200用户100轮循环访问

2.随机200用户100轮循环访问

1.固定2000用户10轮循环访问

1.随机2000用户10轮循环访问
### **8.jmeter压测结果分析:**
| 测试项 | 固定200用户并发 | 随机200用户并发 | 固定2000用户并发 | 随机2000用户并发 |
| :------: | :-------------: | :-------------: | :--------------: | :--------------: |
| 平均耗时 | 10ms | 24ms | 217ms | 499ms |
| 平均QPS | 6657.8(t/s) | 4318.7(t/s) | 5007.5(t/s) | 2830.5(t/s) |
| 目标QPS | 3000 | 1000 | 1500 | 800 |
## **九、遗留问题与风险预估**
*1、本次方案受限于时间、人力、外部因素等原因,未充分设计或者实现,代码可能存在潜在风险及Bug。*
*2、受限于外部依赖限制、硬件资源、网络等不可控条件以及暂未引入网关、未使用Https传输,存在运行风险。*
3、当前用户的密码在数据库虽然做了不可逆加密,但是前端仍然是明文传递,这里可以进一步做好加密保护操作。
4.当前静态文件及网页文件与项目混在一起,后续可引入nginx代理甚至文件服务器托管静态文件上传。
5.当前性能压测粒度还比较粗糙,可以更细粒度的压测,挖掘更多潜在问题和优化点(Gin和RPC线程池数,编解码序列化,网络传输TCP连接管理,数据库连接数和Buffer Size,操作系统文件句柄限制等)。
## **十、附录**
*可包括一些附带的内容,例如引用的文档链接、提供的操作手册附件等*
1. Go语言及框架
2. Go Web编程
3. Go RPC 开发指南
4. [七天用Go从零实现系列](https://geektutu.com/post/gee.html)
5. 菜鸟Docker教程
6. Go语言基础Web项目框架