Skip to content

2024年11月

一步步实现微服务权限管理系统(3)

前言

  • 前端参考了很多框架,可谓百花齐放,但很多封装过剩,不利于学习和应用,最终我选择了 [vue-pure-admin](https://github.com/pure-admin/vue-pure-admin)
  • 后端我将使用 go-zero 来带领大家一步步实现一个权限管理系统
  • 本系列项目存放在 purezeroadmin 中,每一部分我都将打tag,并保证每个tag能正常运行。请多点赞和评论。
  • 后面示例均为 purezeroadmin 项目为例,你们可以根据需要自建工程来进行试验。均采用vscode进行试验。
  • go-zero 常用命令我将放入其对应的 makefile 文件中。

前端所有请求接口前缀加api

修改对应请求文件

  1. src/api/user.ts
/** 登录 */
export const getLogin = (data?: object) => {
  return http.request<UserResult>("post", "/api/login", { data });
};

/** 刷新`token` */
export const refreshTokenApi = (data?: object) => {
  return http.request<RefreshTokenResult>("post", "/api/refresh-token", { data });
};
  1. src/api/routes.ts
export const getAsyncRoutes = () => {
  return http.request<Result>("get", "/api/get-async-routes");
};

修改本地跨域代理

  • vite.config.ts
// 本地跨域代理 https://cn.vitejs.dev/config/server-options.html#server-proxy
proxy: {
'/api': {
    target: 'http://localhost:8888',
    changeOrigin: true,
}
},

后端接口前缀统一为/api

修改user-api/api/user.api相关信息

...
service user-api {
    @doc "用户登录"
    @handler userLogin
    post /api/login (UserLoginReq) returns (UserLoginResp)
}
...
service user-api {
    @doc "刷新token"
    @handler UserRefreshToken
    post /api/refresh-token (UserRefreshTokenReq) returns (UserRefreshTokenResp)
}
...
service user-api {
    @doc "获取路由"
    @handler userRouter
    get /api/get-async-routes (UserRouterReq) returns (UserRouterResp)
}

修改后重新生成代码

测试

完成以上可进行测试

tag版本

purezeroadmin 项目下

git checkout v1.2.0

接下来

现在有个问题,就是后端成功和失败返回的格式不统一,如用户登陆,成功返回 types.UserLoginResp, 而失败返回 error, 这是不规范的,下一节将解决相关问题

一步步实现微服务权限管理系统(2)

前言

  • 前端参考了很多框架,可谓百花齐放,但很多封装过剩,不利于学习和应用,最终我选择了 [vue-pure-admin](https://github.com/pure-admin/vue-pure-admin)
  • 后端我将使用 go-zero 来带领大家一步步实现一个权限管理系统
  • 本系列项目存放在 purezeroadmin 中,每一部分我都将打tag,并保证每个tag能正常运行。请多点赞和评论。
  • 后面示例均为 purezeroadmin 项目为例,你们可以根据需要自建工程来进行试验。均采用vscode进行试验。
  • go-zero 常用命令我将放入其对应的 makefile 文件中。

前端相关接口参考

前端模拟的接口在 mock 文件夹中,后端可参照修改

后端修改

登陆接口

  1. 公共api创建 前端返回接口均为 {success: true, data: xxx}, 所以创建公共api `
cat base-api/base.api

api:

type Base {
    Success bool `json:"success"`
}
  1. 依照前章示例,创建 user.api 文件
cat `user-api/api/user.api`

api:

syntax = "v1"

info (
    title: "q9090960bnb3"
    desc:  "haha"
    date:  "2024-10-31"
)

import "../../base-api/base.api"

type UserLoginData {
    Avatar       string   `json:"avatar"`
    Username     string   `json:"username"`
    Nickname     string   `json:"nickname"`
    Roles        []string `json:"roles"`
    Permissions  []string `json:"permissions"`
    AccessToken  string   `json:"accessToken"`
    RefreshToken string   `json:"refreshToken"`
    Expires      string   `json:"expires"`
}

type (
    UserLoginReq {
        UserName string `json:"username"`
        Password string `json:"password"`
    }
    UserLoginResp {
        Base
        Data UserLoginData `json:"data"`
    }
)

service user-api {
    @doc "用户登录"
    @handler userLogin
    post /login (UserLoginReq) returns (UserLoginResp)
}
  1. 生成代码
goctl api go --api user-api/api/user.api --dir user-api
  1. 参考前端mock数据,修改 user-api/internal/logic/userloginlogic.go 中的函数
func (l *UserLoginLogic) UserLogin(req *types.UserLoginReq) (resp *types.UserLoginResp, err error) {
    if req.UserName == "admin" {
        return &types.UserLoginResp{
            Base: types.Base{
                Success: true,
            },
            Data: types.UserLoginData{
                Avatar:       "https://avatars.githubusercontent.com/u/44761321",
                Username:     "admin",
                Nickname:     "小铭",
                Roles:        []string{"admin"},
                Permissions:  []string{"*:*:*"},
                AccessToken:  "eyJhbGciOiJIUzUxMiJ9.admin",
                RefreshToken: "eyJhbGciOiJIUzUxMiJ9.adminRefresh",
                Expires:      "2030/10/30 00:00:00",
            },
        }, nil
    }

    return &types.UserLoginResp{
        Base: types.Base{
            Success: true,
        },
        Data: types.UserLoginData{
            Avatar:       "https://avatars.githubusercontent.com/u/52823142",
            Username:     "common",
            Nickname:     "小林",
            Roles:        []string{"common"},
            Permissions:  []string{"permission:btn:add", "permission:btn:edit"},
            AccessToken:  "eyJhbGciOiJIUzUxMiJ9.common",
            RefreshToken: "eyJhbGciOiJIUzUxMiJ9.commonRefresh",
            Expires:      "2030/10/30 00:00:00",
        },
    }, nil
}

修改刷新token接口

  1. user.api 中添加
type UserRefreshTokenData {
    AccessToken  string `json:"accessToken"`
    RefreshToken string `json:"refreshToken"`
    Expires      string `json:"expires"`
}

type (
    UserRefreshTokenReq {
        RefreshToken string `json:"refreshToken"`
    }
    UserRefreshTokenResp {
        Base
        Data UserRefreshTokenData `json:"data"`
    }
)

service user-api {
    @doc "用户登录"
    @handler UserRefreshToken
    post /refresh-token (UserRefreshTokenReq) returns (UserRefreshTokenResp)
}
  1. 参考前端mock数据,修改 user-api/internal/logic/userrefreshtokenlogic.go 中的函数
func (l *UserRefreshTokenLogic) UserRefreshToken(req *types.UserRefreshTokenReq) (resp *types.UserRefreshTokenResp, err error) {
    if req.RefreshToken != "" {
        return &types.UserRefreshTokenResp{
            Base: types.Base{
                Success: true,
            },
            Data: types.UserRefreshTokenData{
                AccessToken:  "",
                RefreshToken: "",
                Expires:      "",
            },
        }, nil
    }
    return &types.UserRefreshTokenResp{}, nil
}

修改异步获取路由接口

  1. user.api 中添加
type RouterData {
    Path      string       `json:"path"`
    Name      string       `json:"name,omitempty"`
    Component string       `json:"component,omitemty"`
    Meta      Meta         `json:"meta"`
    Children  []RouterData `json:"children,omitempty"`
}

type Meta {
    Title string   `json:"title"`
    Icon  string   `json:"icon,omitempty"`
    Rank  int64    `json:"rank,omitempty"`
    Roles []string `json:"roles,omitempty"`
    Auths []string `json:"auths,omitempty"`
}

type (
    UserRouterReq  {}
    UserRouterResp {
        Base
        Data []RouterData `json:"data"`
    }
)

service user-api {
    @doc "用户登出"
    @handler userRouter
    get /get-async-routes (UserRouterReq) returns (UserRouterResp)
}
  1. 参考前端mock数据,修改 user-api/internal/logic/userrouterlogic.go 中的函数
func (l *UserRouterLogic) UserRouter(req *types.UserRouterReq) (resp *types.UserRouterResp, err error) {
    return &types.UserRouterResp{
        Base: types.Base{
            Success: true,
        },
        Data: []types.RouterData{
            {
                Path: "/permission",
                Meta: types.Meta{
                    Title: "权限管理",
                    Icon:  "ep:lollipop",
                    Rank:  10,
                },
                Children: []types.RouterData{
                    {
                        Path: "/permission/page/index",
                        Name: "PermissionPage",
                        Meta: types.Meta{
                            Title: "页面权限",
                            Roles: []string{"admin", "common"},
                        },
                    },
                    {
                        Path: "/permission/button",
                        Meta: types.Meta{
                            Title: "按钮权限",
                            Roles: []string{"admin", "common"},
                        },
                        Children: []types.RouterData{
                            {
                                Path:      "/permission/button/router",
                                Component: "permission/button/index",
                                Name:      "PermissionButtonRouter",
                                Meta: types.Meta{
                                    Title: "路由返回按钮权限",
                                    Auths: []string{
                                        "permission:btn:add",
                                        "permission:btn:edit",
                                        "permission:btn:delete",
                                    },
                                },
                            },
                            {
                                Path:      "/permission/button/login",
                                Component: "permission/button/perms",
                                Name:      "PermissionButtonLogin",
                                Meta: types.Meta{
                                    Title: "登录返回按钮权限",
                                },
                            },
                        },
                    },
                },
            },
        },
    }, nil
}

前端取消mock

参考 如何关闭mock

前端配置端口转发

vite.config.ts 中配置转发

// 本地跨域代理 https://cn.vitejs.dev/config/server-options.html#server-proxy
proxy: {
"^/(login|refresh-token|get-async-routes)": {
    target: "http://localhost:8888",
    changeOrigin: true,
},
},

启动测试

  1. 启动后端服务
# vue-pure-admin工程下执行 cd back-end/user-api
❯ go run user.go
Starting server at 0.0.0.0:8888...
  1. 启动前端服务
# vue-pure-admin工程下执行 cd front-end
❯ pnpm dev

tag版本

purezeroadmin 项目下

git checkout v1.1.0

接下来

目前,前端请求路由太自由了,最好采用统一前缀去访问后端系统

一步步实现微服务权限管理系统(1)

前言

  • 前端参考了很多框架,可谓百花齐放,但很多封装过剩,不利于学习和应用,最终我选择了 [vue-pure-admin](https://github.com/pure-admin/vue-pure-admin)
  • 后端我将使用 go-zero 来带领大家一步步实现一个权限管理系统
  • 本系列项目存放在 purezeroadmin 中,每一部分我都将打tag,并保证每个tag能正常运行。
  • 后面示例均为 purezeroadmin 项目为例,你们可以根据需要自建工程来进行试验。均采用vscode进行试验。
  • go-zero 常用命令我将放入其对应的 makefile 文件中。

搭建环境

前端环境

  1. 参考 vue-pure-admin 官网,下载对应工具包
 cd vue-pure-admin
❯ pure create front-end
? 请输入项目名称 front-end
? 选择一个代码托管平台下载模板 Gitee
? 请选择模板类型 thin
  1. 运行,打开浏览器观测成功
# vue-pure-admin工程下执行 cd front-end
# 删除git仓库,外部工程自己git管理 rm -rf .git  pnpm i
❯ pnpm dev

后端环境

  1. 参考 go-zero 官网,下载对应工具包

  2. 创建工程

# vue-pure-admin工程下执行 mkdir back-end
❯ cd back-end
❯ go mod init backend
  1. 创建测试api
# back-end工程下执行 mkdir -p hello-api/api

创建测试 api 文件 hello.api

syntax = "v1"

info (
    title: "q9090960bnb3"
    desc:  "hello"
    date:  "2024-11-01"
)

type (
    HelloReq {
        World string `form:"world"`
    }
    HelloResp {
        HelloWorld string `json:"hello_world"`
    }
)

service hello-api {
    @doc "你好,世界"
    @handler helloInfo
    get /hello (HelloReq) returns (HelloResp)
}
  1. 生成 api 项目代码
# back-end工程下执行 goctl api go --api hello-api/api/hello.api --dir hello-api
  1. 修改返回逻辑
# back-end工程下执行
cd hello-api/internal/logic

修改 helloinfologic.goHelloInfo 函数

func (l *HelloInfoLogic) HelloInfo(req *types.HelloReq) (resp *types.HelloResp, err error) {
    return &types.HelloResp{
        HelloWorld: "hello " + req.World,
    }, nil
}
  1. 运行查看效果
# back-end工程下执行 cd hello-api
❯ go run hello.go
Starting server at 0.0.0.0:8888...
{"@timestamp":"2024-11-01T11:17:36.574+08:00","caller":"stat/usage.go:61","content":"CPU: 77m, MEMORY: Alloc=1.7Mi, TotalAlloc=3.6Mi, Sys=12.5Mi, NumGC=1","level":"stat"}
...

curl 测试

 curl "http://localhost:8888/hello?world=purezeroadmin"
{"hello_world":"hello purezeroadmin"}#  

tag版本

purezeroadmin 项目下

git checkout v1.0.0

接下来

front-end 中的mock数据替换到 back-end 中了

Podman容器概述

Podman是一种用于管理和运行容器的工具,类似于Docker。它可以帮助你在Linux系统上创 建、管理和运行容器,让你的应用程序更加轻便和可移植。与Docker相比,Podman提供了更强的隔 离和安全性,支持在非特权用户下运行容器,并提供与Docker·CLI兼容的命令行接口,让Docker用户 可以很容易地使用Podman。Podman可以通过简单的命令行操作来启动、停止和检查容器状态,并支 持使用Dockerfile来构建容器镜像。总的来说,Podman是一个强大而灵活的工具,可以帮助你更方便 地管理和部署容器化应用程序。

podman 与 docker 区别

与Docker相比,Podman具有以下一些不同之处: 1、Podman不需要运行Docker守护程序,因此可以在不同的Linux主机上运行,从而提高了可 移植性。 2、Podman容器不需要特权用户或管理员权限即可运行,这使得非特权用户可以在Linux系统上 运行容器,从而提高了安全性。 3、Podman·CLI与Docker·CLI兼容,因此Docker用户可以很容易地转换到Podman容器环境 中。

podman 进程

使用Podman之后我们不需要管理和Docker守护进程一样的守护进程,Podman也同样支持 Docker命令,他们的镜像也是兼容的。 Podman·官网地址: https://podman.io/ Podman·项目地址:https://github.com/containers/libpod

podman的安装和使用

  • 安装
$ yum install  -y podman --allowerasing
  • 简单使用
$ podman search busybox
$ podman pull busybox
# 查看镜像
$ podman images
# 删除镜像
$ podman rmi -f af
# 加载镜像
$ podman load -i nginx.tar
# 运行容器
$ podman run -it busybox
# 运行并进入容器
$ podman run -it busybox sh
# 删除容器
$ podman rm -f d7
# 查看容器
$ podman ps 
  • exam
$ podman run --name nginx-v1 --restart=always -itd -p 9090:80 nginx

设置podman 容器实例开机自动运行

$ podman run --name web86 --restart=always -itd -p 86:80 nginx
# 进入做启动脚本目录
$ cd /usr/lib/systemd/system
# 
$ podman generate systemd --name web86 --files

DEPRECATED command:
It is recommended to use Quadlets for running containers and pods under systemd.

Please refer to podman-systemd.unit(5) for details.
WARN[0000] Container 2a874d250f134ffa1b7e4a53f65fceae687a496e303347e95983f7337125f494 has restart policy "always" which can lead to issues on shutdown: consider recreating the container without a restart policy and use systemd's restart mechanism instead 
/usr/lib/systemd/system/container-web86.service
# 重启测试
$ systemctl daemon-reload
$ systemctl enable container-web86.service
$ reboot -f

Containerd容器介绍

官方文档: https://containerd.io

概述

Containerd是一个工业级标准的容器运行时,它由Docker公司开发并贡献给了Cloud·Native Computing·Foundation(CNCF)。它提供了基本的容器管理功能,包括容器镜像的传输和存储、容器 的执行和管理、容器的网络管理等。与Docker相比,Containerd更加轻量化和模块化,可以更好地与 Kubernetes等容器编排系统集成。

containerd只能运行和拉取镜像,不能制作镜像

发展

  • 从Docker 1.11版本开始,Docker公司将Containerd从Docker中分离出来,并将其作为一个 独立的开源项目提交给CNCF进行维护。这是因为Docker作为一个完整的容器平台,包含了很多不必 要的功能,而且代码比较庞大,难以维护。Containerd则是一个更加专注于容器管理的运行时,可以更 好地满足云原生应用的需求。

  • K8s1.24版本开始,不在支持docker,为什么? 第一,Docker·的发展方向与·Kubernetes·并不完全一致,Docker·更注重整个生态系统,而 Kubernetes·更关注以容器为中心。 第二,containerd·是一个工业级标准的容器运行时,它可以更好地与·Kubernetes·集成,并提供 更好的性能和可用性,更轻量。( 第三,它不符合Kubernetes的CRl(容器运行时接口)规范,Docker不符合Kubernetes·CRI 规范的原因有几个: 1)过于庞大:Docker是一个庞大的软件包,其中包含了大量的功能,这些功能并不都是 Kubernetes需要的。这使得Docker的部署和管理变得复杂,也不利于轻量级容器的运行。 2)APl不稳定:Docker的API并不是为Kubernetes设计的,因此在Kubernetes环境中使用 Docker会出现API不稳定的情况,这会导致兼容性问题和不必要的麻烦。 3)安全性问题:Docker的安全模型并不是为Kubernetes设计的,这可能会导致在Kubernetes 环境中运行容器时出现不必要的安全风险。

containerd安装与配置

$ yum install containerd.io-1.6.22 --allowerasing -y
# 查看版本 
$ ctr version
  • 生成配置文件
containerd config default > /etc/containerd/config1.toml
  • 修改 containerd 配置文件 /etc/containerd/config.toml
...
SystemdCgroup = true
...
[plugins."io.containerd.grpc.v1.cri".registry.configs]
  [plugins."io.containerd.grpc.v1.cri".registry.configs."192.168.59.63".tls]
      insecure_skip_verify = true
  [plugins."io.containerd.grpc.v1.cri".registry.configs."192.168.59.63".auth]
      username = "admin"
      password = "Harbor12345"

  [plugins."io.containerd.grpc.v1.cri".registry.headers]

    # 改成自己的镜像源
  [plugins."io.containerd.grpc.v1.cri".registry.mirrors]
    [plugins."io.containerd.grpc.v1.cri".registry.mirrors."192.168.59.63"]
      endpoint = ["https://192.168.59.63:443"]
    [plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
      endpoint = ["crpi-cs92oq2e1ryfz2xe.cn-chengdu.personal.cr.aliyuncs.com"]
  • 重启
$ systemctl enable containerd.service 
$ systemctl restart containerd.service 
$ systemctl status containerd.service 

containerd 命令行工具 ctr

  • 拉取
# 拉取镜像,必须完整格式
$ ctr image pull docker.io/library/nginx:latest
# 查看镜像
$ ctr image ls
  • 导出
$ ctr image pull registry.aliyuncs.com/google_containers/pause:3.2
# 导出
$ ctr image export pause.tar.gz registry.aliyuncs.com/google_containers/pause:3.2
# 可供docker 导入
$ docker load -i pause.tar.gz
  • 导入
$ docker save -o nginx.tar nginx:latest
# 导入
$ ctr image import nginx.tar
  • 跟docker对比 alt text

  • 从 harbor 拉取镜像

$ ctr image pull 192.168.59.63/test/tomcat:v1 --skip-verify --user=admin:Harbor12345
# 查看镜像
$ ctr image ls

docker 4种网络模式

概况

host模式

使用--net=host指定。容器使用主机的IP地址和端口来进行通信,这样可以达到和主 机一样的网络性能和配置,但是也存在安全风险。使用Host网络模式时,容器不会创建自己的网络命名 空间,也不会为容器单独分配IP地址,而是直接使用主机的IP地址和端口,因此容器之间的网络隔离会 被打破,容器可以直接访问主机上所有的端口和服务,也可以被主机上的其他设备访问到。Host网络模 式一般用于需要直接访问主机资源的场景,例如主机上运行的服务需要直接访问容器中的服务。

none模式

使用--net=none指定。None网络模式就是不给容器分配任何网络资源,也就是说, 容器不会创建虚拟网卡、IP地址和路由等网络资源,容器之间无法直接通信,也无法与主机和外部网络 进行通信。使用None网络模式时,容器只能使用本地文件系统和进程空间等资源。None网络模式一 般用于一些特殊场景,例如需要在容器中进行一些本地操作,但又不需要使用网络资源的情况下,或者需 要创建一个完全隔离的容器时。

bridge模式

使用--net=bridge指定,这是·Docker·默认的网络模式。在Docker中,Bridge网络模式就像是一座桥, 它通过一个虚拟网桥来连接多个容器。这个虚拟网桥就像是一座桥梁,把连接到它上面的容器连接起来, 这样它们就可以相互通信了。与外部网络进行通信时,虚拟网桥通过物理网卡连接到主机上的网络。

container模式

Docker网络container模式是指,创建新容器的时候,通过--net container 参数,指定其和已经存在的某个容器共享一个·Network-Namespace。如下图所示,右方黄色新创建的 container,其网卡共享左边容器。因此就不会拥有自已独立的·IP,而是共享左边容器的·IP 172.17.0.2.端口范围等网络资源,两个容器的进程通过·10?网卡设备通信。

alt text

实验

## none 无ip 需要自己配
$ docker run -itd --name none --net=none --privileged=true rockylinux:8.8 bash
## host 与主机共享ip
$ docker run -itd --name host --net=host --privileged=true rockylinux:8.8 bash
## 跟 名称为 none 容器共享ip
$ docker run --name container --net=container:none -it --privileged=true rockylinux:8.8 bash
## 默认就是 -net=bridge 
$ docker run --name bridgee -it --privileged=true rockylinux:8.8 bash

拓展

-privileged=true·#允许开启特权功能,使用这个参数会让Docker容器拥有更高的权限,可以 访问宿主机的所有资源。类比于一个人,如果你是普通的游客,你只能在景区指定的区域内走动,但是如 果你是景区的管理员,那么你就可以随意进出景区的各个区域,进行各种操作。

在Docker中,运行特权容器的场景通常是需要执行一些底层操作,例如管理宿主机上的硬件设备、 加载内核模块、访问系统的底层资源等。因此,如果需要在容器中进行这些底层操作,就需要使用-- privileged=true参数。不过,需要注意的是,这种高权限操作也可能存在安全风险,因此需要仔细考虑 和控制风险。 特权模式通常用于需要访问宿主机底层设备或进行特殊操作的应用场景,例如: 1)需要在容器中执行内核模块加载或卸载操作; 2)需要访问系统中的设备节点,例如/dev和/sys目录下的文件; 3)需要访问系统的硬件资源,例如CPU、内存、网络和存储设备等。

python打包相关

setup.py

早期 python 的测试和打包方式, setup.py 如下

from setuptools import setup

setup(
    name='HelloDolly',
    version='1.0.1',
    packages=['hellodolly'],
    url='https://github.com/fmaida/hello-dolly-mkdocs-plugin',
    license='MIT',
    author='Francesco Maida',
    author_email='fran@cesco.it',
    description='Hello Dolly is a very simple mkdocs plugin.',
    install_requires=['mkdocs'],

    # The following rows are important to register your plugin.
    # The format is "(plugin name) = (plugin folder):(class name)"
    # Without them, mkdocs will not be able to recognize it.
    entry_points={
        'mkdocs.plugins': [
            'hello-dolly = hellodolly:HelloDolly',
        ]
    },
)

现如今对应命令被弃用

Deprecated Recommendation
python setup.py install python -m pip install .
python setup.py develop python -m pip install --editable . 或 python -m pip install -e .
python setup.py sdist python -m build
python setup.py bdist_wheel python -m build
  • python setup.py install:

作用:安装包到系统的 Python 环境中。 特点:会将包复制到 Python 的安装目录下的 site-packages 文件夹中。如果目标目录没有写权限,则可能需要使用 sudo 或者以管理员身份运行。

  • python setup.py develop:

作用:将当前目录作为包安装,即在不复制文件的情况下让 Python 解释器可以找到并使用这个包。 特点:适合开发过程中频繁修改代码的情况,因为不需要重新安装包即可看到修改后的效果。这通过创建一个指向源代码的链接来实现。

  • python setup.py sdist:

作用:生成源码发布包(通常为 .tar.gz 或 .zip 格式)。 特点:打包后可以在其他机器上使用 pip install 包名 来安装此包。

  • python setup.py bdist_wheel:

作用:生成 wheel 发布包(.whl 文件)。 特点:与系统相关的二进制包,安装速度比源码包快,可以直接被 Python 解析和使用,无需编译步骤。

  • python -m build

作用: 生成源码包和 wheel 包。

需要先安装 build 模块 :pip install build

注意 python setup.py develop 或 python -m pip install -e . 测试完后可以用 pip uninstall <> 来解除连接

pypi 发布包

  1. 创建账号
  2. 申请api token
  3. 配置 pypi
    poetry config pypi-token.pypi <token>
    
  4. 编译
python -m build
  1. 发布
    poetry publish
    

windows 端口转发

windows 端口转发

  • netsh interface portproxy add v4tov4 listenport=源端口 listenaddress=本地IP connectport=目标端口 connectaddress=目标IP protocol=tcp
# 添加转发
netsh interface portproxy add v4tov4 listenport=15006 connectaddress=127.0.0.1 connectport=5006
# 查看转发
netsh interface portproxy show all
# 删除转发
netsh interface portproxy delete v4tov4 listenport=15006