GVA,gin,vue,admin项目架构分析

        一、处理登录

实现只有登录之后才能访问到里面的页面要用到sessions,因为每一个页面前都要进行判断是否登录。

go get github.com/gin-contrib/sessions
 

	rootRouter.GET("/login", func(c *gin.Context) {
		// session ---- 是一种所有请求之间的数据共享机制,为什么会出现session,是因为http请求是一种无状态。
		// 什么叫无状态:就是指,用户在浏览器输入方位地址的时候,地址请求到服务区,到响应服务,并不会存储任何数据在客户端或者服务端,
		// 也是就:一次request---response就意味着内存消亡,也就以为整个过程请求和响应过程结束。
		// 但是往往在开发中,我们可能要存存储一些信息,让各个请求之间进行共享。所有就出现了session会话机制
		// session会话机制其实是一种服务端存储技术,底层原理是一个map
		// 比如:我登录的时候,要把用户信息存储session中,然后给 map[key]any =
		// key = sdf365454klsdflsd --sessionid

		// 初始化session对象
		session := sessions.Default(c)
		// 存放用户信息到session
		session.Set("user", "feige") //map[sessionid] == map[user][feige]
		// 记住一定调用save方法,否则内存不会写入进去
		session.Save()
		c.JSON(http.StatusOK, "我是gin")
	})

相当于,登录页面设置成功登录之后,会拿到一个session,然后我们在其他页面设置有session的才给通过。但是这个session是存在服务端的会消耗我们的资源。

在每个页面加,例如

很明显,如果路由有很多,那么代码会很臃肿 。

所以要用到中间件。JWT。

把session放到上下文中,c.set。

将会话的上下文过渡到c的上下文里面来。

然后在路由组里面配

用.use

下一步,把路由封装,不要把写在main中 。

二、

结构体的方法在别的包使用前一定要先new

user:=User{},这是普通,生成的是一个值,如果要将他变为指针,则fmt.println(&user).

user4:=new(User),这种就是生成一个指针。

这种方法可以接受普通。

这种就只接受指针

指针类型经典

c.shouldbindjson。

三、配置化

用viper。

数据配置的设定 tab对齐,shift+tab 回退对齐。

监听配置文件

将从配置文件拿到的东西,设置为全局变量。

用这个

用结构体,

map里面的键都是小写。

我比较喜欢这种,如果数量比较少的话。将配置文件映射到对应的结构体中。

app:
  host: "localhost"
  port: 8080
  debug: true
package config

type AppConfig struct {
	Host string
	Port int
	Debug bool
}
package main

import (
	"fmt"
	"github.com/spf13/viper"
)

// AppConfig 是用来存放配置文件数据的结构体
type AppConfig struct {
	Host string
	Port int
	Debug bool
}

func main() {
	// 创建一个新的 viper 实例
	config := viper.New()

	// 设置配置文件的类型,例如 yaml, toml, json 等
	config.SetConfigType("yaml")

	// 添加配置文件的搜索路径,可以添加多个路径
	config.AddConfigPath(".")

	// 设置配置文件的名称,不包含扩展名
	config.SetConfigName("config")

	// 读取配置文件
	if err := config.ReadInConfig(); err != nil {
		panic(fmt.Errorf("Fatal error config file: %w", err))
	}

	// 将配置文件中的数据映射到结构体变量
	var appConfig AppConfig
	if err := config.Unmarshal(&appConfig); err != nil {
		panic(fmt.Errorf("unable to decode into struct, %w", err))
	}

	// 打印配置信息,查看是否成功读取
	fmt.Println("Application config: ", appConfig)

	// 接下来可以使用 appConfig 中的配置信息...
}

四、继承和整合gorm,实现登录

覆盖生成表

比如微信支付v2版本很完善了,但现在要升级到v3该怎么做。

不能改别人代码,先把接口用结构体规范起来,

比如旧的

新的代码

然后调用也是

 五、实现登录接口

定义路由

注册路由

获取参数

与数据库交互

创建一个用户表

nil 是go空值处理,必须是指针类型 。

加了unique,会自动创建索引.

状态码不要乱写,特别是需要重定向的时候,只有303才可以跳。

状态码,成功的只有一个,失败的有n个,具体原因具体分析。

有一种:只要能请求,都设置成http.statusOK,但是在业务层做返回。

六、验证码

 session服务存储的方案:

  - 在go的web服务生成一个图形验证码。比如生成:98477

  - 然后把98477存储到服务端(session)

  - 然后生成图形验证码,*使用img标签,进行加载,可以看到图像验证码

  - 然后用户输入图形验证码信息,然后发起登录请求

  - 然后在登录请求中,根据用户输入图像验证码和服务端存储的验证码进行比较

  - 如果相同代表就是合法,如果不同说明输入验证码有误,就提示用户。

- 在go的base64Captcha组件的原理

  - 会初始化一个组件对象,然后生成一个base64的图片地址,和一个id

  - 这个id会返回给客户端。

  - 然后用户根据base64的图片地址使用img标签进行渲染,根据显示图片信息

  - 然后输入验证码,然后发起请求登录,同时携带验证码的id

  - 根据id和用户输入验证码,进行校验,如果校验true.代表就合法验证码。就直接去登录

  - 否则提示验证码输入有误。

 02、Go整合验证码

- 官网:https://github.com/mojocn/base64Captcha

- 下载模块:

  ```

  go get github.com/mojocn/base64Captcha

  ```

- 验证码模块

前端的验证码就是一个 img标签和go生成的验证码图片地址。

验证码校验,后端

前端路由

前端页面。配置路由,如何对验证码,异步请求验证码 ,调用验证码的接口和登录接口。安装axios。

浏览器是8777端口,访问服务器8989端口,显示跨域问题 。要在服务端设置gin里设置跨域问题

校验验证码,如果输入错的验证码 返回一个状态码,根据这个状态码来判断 是否刷新验证码。

七、jwt

单体架构

说白了,就是把所有的数据的管理和处理都交由服务端来完成。

把路由的处理和资源访问都由服务端来定义和规范,用户只需要按照服务端定义的路由,可以找到服务端的资源(数据库数据,文件)。

接下来就就会出现一个问题,请求和请求之间的数据共享的问题。

发起一个请求和响应一个请求,整个过程是无状态(一个请求:go语言会开辟一个线程(协程)去执行请求)。如果request到response结束了。线程肯定要么回收,要么就是被销毁。在整个请求线程数据是无状态就不会任何的保留。比如登录:

用户输入账号和密码,点击登录按钮,请求服务端,把用户查询数据查询出来,显示到页面。那么这里查询出来的用户数据,是不可能维持状态,也就说这个用户信息只能在当前登录请求才会看的到。别人请求是获取不到该用户信息的。那么就出现一个问题。请求和请求的数据共享。你怎么实现?

 如果不解决会出现什么情况

- 小维—-thread01———-登录—request–response—用户信息是可以拿到—-也可以在浏览器显示出来。—–进入首页(显示当前用户信息)

- 小维—首页—-课程商品—购买———-thread02—–执行购买的服务方法——-这个时候我们必须要知道是谁在操作购买需要?–那么你必须要获取登录的用户信息。

如果不想办法解决这个,你肯定在登录后续的所有的请求中你肯定是拿不到用户信息。那么你必须:

- 每次请求都执行一次登录。那这样的话肯定是不显示

- 可以把用户信息返回浏览器,使用浏览器的存储技术(cookie)然后把登录用户信息放入cookie。然后在每次请求的时候把userId携带上。但是这个出现安全问题。 http://www.xxxx.com/course/buy/100?userId=100

加解密不行。速度慢,而且被人破解就麻烦了;用置换技术

这种安全问题不能再客户端(浏览器)存,安全问题太大了。只能存在服务端。

session其实就是一种服务端存储技术,主要就是用来解决,请求和请求之间数据共享的一种机制。所以上面问题很明显。userId=100 存在在浏览器(B客户端)肯定不现实。客户端自然不行。那就服务端来完成这个事情。而且服务端是把数据写到操作系统的内容中的。除非你能把服务器攻破。并且对go操作内存你才能攻破,所以存在在服务端是非常安全的。那么如何完成的呢?

session的底层原理就是: map[string]map[string]any{}

userId=100&id=1&name=zhangsan——map/struct—user{userid:100,id:1,name:zhangsan}—-

session机制:它必须可以存储一切, 所以map的值必须interface{} (any)

 因为是map的map,不是单map。

那信息已经存进去了,如何在付款的时候能找到对应的人呢?如何在map的map找到对应的人,那就是设置一个sessionId进行查找,登录的时候把sessionID返回给浏览器

这样才知道拿到谁的数据。每次请求过程中都要带上cookie。

要将存在登录信息在1服务里面的数据共享给服务2。

做一个redis服务器。

这些都要花钱。

session为什么过时?

不适合前后端分离架构,session技术毕竟是一个服务端的存在技术,消耗go的内存,用户量小没有毛病;用户暴增,如果把用户信息放入服务端来存储肯定会出现内存问题。

session

如何解决——————使用token技术

token时限

什么是jwt

 go整合jwt

在Golang语言中,**[jwt-go](https://link.zhihu.com/?target=https%3A//github.com/dgrijalva/jwt-go)**库提供了一些jwt编码和验证的工具,因此我们很容易使用该库来实现token认证。

另外,我们也知道**[gin](https://link.zhihu.com/?target=https%3A//github.com/gin-gonic/gin)**框架中支持用户自定义middleware,我们可以很好的将jwt相关的逻辑封装在middleware中,然后对具体的接口进行认证。

到期了怎么办?写一个续期。


这里的时间.add要time.second*10,十秒。类似。为什么生效时间设置时要减秒数,因为要他快速生效。

业务中使用token

set了之后,可以让后续的路由可以直接通过c.get

 只要时接口,全部使用jwt拦截

 从头部传递token。

测试token的有效性

为用户创建一个token,用用户的id和名字作为载体。一定要写过期时间。

多久过期才好。

如何将token续期?

原理:只要在有效期内的一个时间点,进行续期接口,重新生成一个新的token,然后替换,旧的token要拉入黑名单。设置过期时间,换的时候要加锁

方案1:在指定的某个时间点

方案2:利用缓存时间86400秒

如果过期时间-当前时间<缓冲时间,则重新创造一个token替换旧的。利用redis,有个自动过期。

在中间件里面判断token要不要续,设置ep。

如果生成了新的token,那么旧的token怎么办。

数据库写一个token的黑名单,存进去。在jwt中间件,查一遍看看在不在,但是最好的是用redis。因为mysql要做一个定时清除表。

八、跳转到别的页面

vue2

vue3

router和route

登录

token放到一个公共的地方,就是状态管理,pinia。

如果b的id是100 ,d的id是100,现在改了头像或者其他信息,b的id变成了10,d还是100,这样就不行了。

pinia


在前端的路由跳转过程中,路由和路由直接如果要实现数据共享,除了通过参数的层层传递以外。也可以使用状态共享一种机制来完成。这个就所谓:路由与路由数据共享的机制,这个避免了传递问题,响应式的问题。

因为路由通过传递参数确实可以解决一些问题,但是参数 不具备响应式的。如果有数据更改要立即生效。

在早期vue生态其实:vuex  

第一步安装:

```js
yarn add pinia
# 或者使用 npm
npm install pinia
```

第二步:开始定义stores*目录新建一个index.js*

```js
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'

// vue3的版本
export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  const doubleCount = computed(() => count.value * 2)
  function increment() {
    count.value++
  }

  return { count, doubleCount, increment }
})

```

第三步:注册到vue

```js
import './assets/base.css'
import { createApp } from 'vue'
import App from './App.vue'
const pinia = createPinia();
app.use(pinia)
app.mount('#app')

```

第四步:新建一个user.js的模块

```js
import { defineStore } from 'pinia'

// 你可以对 `defineStore()` 的返回值进行任意命名,但最好使用 store 的名字,同时以 `use` 开头且以 `Store` 结尾。(比如 `useUserStore`,`useCartStore`,`useProductStore`)
// 第一个参数是你的应用中 Store 的唯一 ID。
export const useUserStore = defineStore('userStore', {
  // 其他配置...
})
```

第五步:使用

```js
import {useUserStore} from '@/stores/user.js'
const userStore = useUserStore();
```

接下来userStore对象,就可以调用方法所以得:state/getters/actions

```js
import { defineStore } from 'pinia'
import axios from 'axios'

//https://blog.csdn.net/weixin_62897746/article/details/129124364
//https://prazdevs.github.io/pinia-plugin-persistedstate/guide/
export const userStore = defineStore('user', {
  // 定义状态
  state: () => ({
    user: {},
    username: '',
    userId: '',
    token: ''
  }),

  // 定义动作
  actions: {
    toLogin(loginUser){
      this.user = {id:100}
      this.userId = 100
      this.username = "feige"
      this.token = 324234234
    }
  }
})

```

## Pinia状态管理

选项式的定义

```js
import { defineStore } from 'pinia'
import axios from 'axios'

//https://blog.csdn.net/weixin_62897746/article/details/129124364
//https://prazdevs.github.io/pinia-plugin-persistedstate/guide/
export const useUserStore = defineStore('user', {
  // 定义状态
  state: () => ({
    user: {},
    username: '',
    userId: '',
    token: '',
    male:1,
    roles:[],
    permissions:[]
  }),

  // 就是一种计算属性的机制,定义的是函数,使用的是属性就相当于computed
  getters:{

    malestr(state){
      if(state.male==1)return "男"
      if(state.male==0)return "女"
      if(state.male==1)return "保密"
    },

    roleName(state){
      return state.roles.map(r=>r.name).join(",")
    },

    permissionCode(state){
      return state.permissions.map(r=>r.code).join(",")
    }
  },

  // 定义动作
  actions: {
   async toLogin(loginUser){
      const resp = await axios.post("http://localhost:8989/login/toLogin", loginUser)
      if (resp.data.code === 20000) {
          // 这个会回退,回退登录页
          var { user ,token,roles,permissions } = resp.data.data
          // 把数据放入到状态管理中
          this.user = user
          this.userId = user.id
          this.username = user.name
          this.token = token
          this.roles = roles
          this.permissions = permissions
          return Promise.resolve(resp)
      } else {
        return Promise.reject(resp)
      }
    }
  },
  
  persist: {
    key: 'pinia-userstore',
    storage: localStorage,
  }
})
```

组合式方式

```js
import { defineStore } from 'pinia'
import axios from 'axios'
import { ref,computed } from 'vue'

//https://blog.csdn.net/weixin_62897746/article/details/129124364
//https://prazdevs.github.io/pinia-plugin-persistedstate/guide/
export const useUserStore = defineStore('user', ()=>{
   
   // 相当于  state
   const user = ref({})
   const username = ref('')
   const userId = ref('')
   const token = ref('')
   const roles = ref([])
   const permissions = ref([])

   
  // 使用计算属性处理状态数据
  const roleName = computed(()=>{
    return roles.value.map(r=>r.name).join(",")
  })

  const permissionCode = computed(()=>{
    return permissions.value.map(r=>r.code).join(",")
  })


   // 定义函数---actions
   const toLogin = async (loginUser) => {
      const resp = await axios.post("http://localhost:8989/login/toLogin", loginUser)
      if (resp.data.code === 20000) {
          // 这个会回退,回退登录页
          var  result = resp.data.data
          console.log("result",result)
          // 把数据放入到状态管理中
          user.value = result.user
          userId.value = result.user.id
          username.value = result.user.name
          token.value = result.token
          roles.value = result.roles
          permissions.value = result.permissions
          return Promise.resolve(resp)
      } else {
        return Promise.reject(resp)
      }
    }

    return {
      user,userId,username,token,toLogin,roles,permissions,roleName,permissionCode
    }
  })
```

使用

```js
import {useUserStore} from '@/stores/user.js'
const userStore = useUserStore();

// 获取状态属性
userStore.user = ""
// 获取getter属性
userStore.roleName
userStore.permissionCode
// 调用actions的方法
userStore.toLogin(xxxx)
```

关于mapGetter 、mapState、mapActions 的用法

```vue
<!-- <template>
    我是一个首页
   <div> {{ store.token }}</div>
    <div>{{ store.userId }}</div>
    <div>{{ store.username }}</div>
    <div><button @click="handleChangeStore">改变</button></div>
    <div><button @click="handleResetStore">重置</button></div>
</template>

<script setup>
    import { userStore } from '@/stores/user.js'
    const store = userStore()

    const  handleChangeStore = () => {
        store.username = 'feifei'
    }
    const  handleResetStore = () => {
        store.$reset()
    }
</script> -->

<!-- 
<template>
    我是一个首页
    <div>{{ userStore.userId }}</div>
    <div>{{ userStore.username }}</div>
    <div>{{ userStore.roles }}</div>
    <div>{{ userStore.roleName }}</div>
    <div>{{ userStore.permissions }}</div>
    <div>{{ userStore.permissionCode }}</div>
</template>

<script setup>
import {useUserStore} from '@/stores/user.js'
const userStore = useUserStore(); -->
<!-- </script> -->


<!-- <template>
    我是一个首页
    <div>{{ userStore.userId }}</div>
    <div>{{ userStore.roles }}</div>
    <div>{{ userStore.roleName }}</div>
    <div>{{ userStore.permissions }}</div>
    <div>{{ userStore.permissionCode }}</div>
    <hr>
    <div>{{ userStore.username }}</div>
    <div>{{ userStore.token }}</div>
    <hr>
    <div>{{ username }}</div>
    <div>{{ token }}</div>
</template>

<script setup>
import {useUserStore} from '@/stores/user.js'
import { computed } from 'vue';
const userStore = useUserStore();
const username =computed(()=>userStore.username)
const token =computed(()=>userStore.token)

</script> -->


<!-- <template>
    我是一个首页
    <div>{{ username }}</div>
    <div>{{ token }}</div>
</template>

<script>
import {useUserStore} from '@/stores/user.js'
const userStore = useUserStore();
//选项式
export default {
    computed: {
        username(){
            return userStore.username
        },
        token(){
            return userStore.token
        }
    }
}

</script> -->


<template>
    我是一个首页
    <div>{{ username }}</div>
    <div>{{ token }}</div>

    <button @click="toLogin">去登录</button>
</template>

<script>
import { mapState,mapActions } from 'pinia'
import {useUserStore} from '@/stores/user.js'
// mapState
// mapGetter 就是一种简化在计算属性中的一种状态管理获取状态属性或者getter属性的一种简化机制。

//选项式
export default {
    computed: {
        ...mapState(useUserStore,["username","token"]),
    },
    methods:{
        ...mapActions(useUserStore,["toLogin"]),
    }
}
</script>
```

 通过一个动作去修改状态。

Vue3 的状态管理库(Pinia)_vue3 pinia-CSDN博客 这个很好

 pinia持久化,用插件。

九、初始化页面布局

样式重置

导这个包。

饿了么组件导入

十、进场动画

如何实现

实现步骤:1

## 如何实现头部nprogress动画

1:安装NProgress组件

```sh
npm install nprogress
yarn add nprogress
pnpm install nprogress
```

2:使用nprogress

```js
import NProgress from 'nprogress'
// 显示右上角螺旋加载提示
NProgress.configure({ showSpinner: true })
//开启进度条
NProgress.start()
//完成进度条
NProgress.done()
```

3: 结合业务

**登录的时候使用:**

login.vue

```js
import NProgress from 'nprogress'
// 显示右上角螺旋加载提示
NProgress.configure({ showSpinner: true })
// 提交表单
const  handleSubmit = async () => {
    // axios.post ---application/json---gin-request.body
    if(!loginUser.code){
        alert("请输入验证码")
        return;
    }
    if(!loginUser.account){
        alert("请输入账号")
        return;
    }
    if(!loginUser.password){
        alert("请输入密码")
        return;
    }

    //开启进度条
    NProgress.start()
    // 把数据放入到状态管理中
    const resp = await userStore.toLogin(loginUser)
    if (resp.data.code === 20000) {
        router.push("/")
        //完成进度条
        NProgress.done()
    } else {
        //完成进度条
        NProgress.done()
        if (resp.data.code === 60002) {
            alert(e.data.msg);
            loginUser.code = "";
            handleGetCapatcha();//重新激活生成新的验证码
        }
    }
}
```

加载路由登录的场景

```js
import { createRouter, createWebHistory } from 'vue-router'
import NProgress from 'nprogress'
// 获取状态管理的token
import { useUserStore } from '@/stores/user.js'
// 显示右上角螺旋加载提示
NProgress.configure({ showSpinner: true })

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'index',
      meta: { title: "index" },
      component: () => import('@/views/index.vue')
    },
    {
      path: '/login',
      name: 'login',
      meta: { title: "login" },
      component: () => import('@/views/login.vue')
    },
    {
      path:'/:pathMatch(.*)*',
      name:'NotFound',
      component: () => import('@/views/NotFound.vue')
    }
  ]
})


router.beforeEach((to, from, next) => {
  //开启进度条
  NProgress.start()
  next()
})

router.afterEach(() => {
  //完成进度条
  NProgress.done()
})


export default router

```



## 如何实现数字动画

参考:https://blog.csdn.net/weixin_44439675/article/details/121117468

参考:http://www.manongjc.com/detail/42-ftlenknixxjlktb.html



**vue2**

1 安装插件

```sh
npm install vue-animate-number
```

2 在main.js中引入

```js
import Vue from 'vue'
import VueAnimateNumber from 'vue-animate-number'
Vue.use(VueAnimateNumber)
```

3: 使用

```vue
<template>
<div>

<animate-number
from="1"
to="10"
duration="1000"
easing="easeOutQuad"
:formatter="formatter"
></animate-number>

<!-- 最简单的案例,from是开始值,to是结束值 -->
<animate-number from="1" to="10"></animate-number>

<animate-number ref="myNum" from="0" to="10" mode="manual" :formatter="formatter"></animate-number><br>

<!-- 也可以通过按钮去触发-->
<button type="button" @click="startAnimate()"> animate! </button>
</div>
</template>
<script>
export default {
methods: {
formatter: function (num) {
return num.toFixed(2)
},

startAnimate: function () {
this.$refs.myNum.start()
}
}
}
</script>
```

 **vue3**

1.安装

```sh
npm install animated-number-vue3
yarn add animated-number-vue3
pnpm add animated-number-vue3
```

2.在main.js中引入

```js
import AnimatedNumber from 'animated-number-vue3'
app.use(AnimatedNumber)
```

3:使用

```vue
<animated-number from="0" :to="toNum" :key="toNum" duration="2000"></animated-number>
```

```vue
<template>
    <div>
        <animated-number from="0" :to="toNum1" :key="toNum"></animated-number>
        <animated-number from="0" :to="toNum2" :key="toNum"></animated-number>
        <animated-number from="0" :to="toNum3" :key="toNum"></animated-number>
    </div>
</template>
<script setup>
import { ref } from 'vue'
const toNum1 = ref(4654)
const toNum2 = ref(1152)
const toNum3 = ref(5486) 
</script>
```

十一 、环境隔离

在开发中,项目分为:生成阶段、开发阶段、测试阶段。如何无缝切换。

服务端-------nacos(统一配置)

客户端:.env环境配置

把要拦截的接口放在中间件后面,不用拦截的放在前面。

十二、

设置页面落脚点router-view 的位置,页面多的时候异步加载。

十三、用户

用户登出,token要放黑名单里。

十四、打包部署

打包go,linux和windows

十五、日志

logrus搭配lumberjack,实现滚动日志。

func InitLogrus() {
	log1 := logrus.New()
	// 创建一个文本格式化器并设置时间格式
	customFormatter := &logrus.TextFormatter{
		FullTimestamp:   true, // 显示完整时间戳
		TimestampFormat: "2006-01-02 15:04:05",
	}
	//使用lumberjack.Logger作为日志输出
	log1.SetOutput(&lumberjack.Logger{
		Filename:   "./logs/err-" + getCurrentDate() + ".log",
		MaxSize:    10,  //文件大小(MB)
		MaxBackups: 100, //最多备份文件数量
		MaxAge:     30,  //文件最长保存天数
		Compress:   false,
	})
	// 设置格式化器
	log1.Formatter = customFormatter
	log1.SetLevel(logrus.DebugLevel)
	Log = log1
}

func getCurrentDate() string {
	return time.Now().Format("2006-01-02")
}

十六、nginx

十七、集群docker

不要单节运行,使用集群的方式部署。

 jenkins 小公司(开发阶段)

单项目与微服务

不要乱搞微服务,业务小就应该用单项目。

grpc基于底层的tcp。

web集群就是把若干个项目运行对外提供。

要同一个ip段上。192.168

如果是云服务器就用上面写着的私有地址。

集群部署一定是使用奇数节点,不能使用偶数。偶数成本高。(3个集群基本开端,为什么不是两台,因为如果第一台崩了,然后全部的请求去了第二台,那么第二台肯定也是承受不住。)

单机部署多应用:

把一个项目打包三次,每次的打包的项目的端口需要改变。

集群 :在nginx里使用负载均衡upstream

会轮询8081->8082->8083->8081,可以做一个权重轮询。

nginx的负载均衡知识点:

有一个weight

Docker

宿主机,docker容器镜像

环境隔离

读取环境变量

golang会缓存一份环境变量,如果改了环境变量但go没读到,应该重启golang。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值