├── .github
└── workflows
│ └── split.yml
├── .gitignore
├── LICENSE
├── README.md
├── README_CN.md
├── bin
├── release.sh
├── setversion.sh
├── split-linux.sh
├── splitsh-lite-darwin
└── splitsh-lite-linux
├── docs
├── 1.1
│ ├── _navbar.md
│ ├── index.html
│ └── zh-cn
│ │ ├── README.md
│ │ ├── _navbar.md
│ │ ├── instructions.md
│ │ ├── mix-mixcli.md
│ │ ├── mix-varwatch.md
│ │ ├── mix-xcli.md
│ │ ├── mix-xdi.md
│ │ ├── mix-xsql.md
│ │ ├── mix-xutil.md
│ │ ├── mix-xwp.md
│ │ ├── online-chating.md
│ │ ├── summary.md
│ │ ├── write-api.md
│ │ ├── write-cli.md
│ │ ├── write-grpc.md
│ │ └── write-web.md
└── index.html
├── examples
├── api-skeleton
│ ├── .env
│ ├── .gitignore
│ ├── README.md
│ ├── bin
│ │ └── .gitignore
│ ├── commands
│ │ ├── api.go
│ │ ├── main.go
│ │ └── welcome.go
│ ├── conf
│ │ └── config.yml
│ ├── config
│ │ ├── configor
│ │ │ └── main.go
│ │ ├── dotenv
│ │ │ └── main.go
│ │ ├── main.go
│ │ └── viper
│ │ │ └── main.go
│ ├── controllers
│ │ ├── auth.go
│ │ ├── hello.go
│ │ └── user.go
│ ├── di
│ │ ├── goredis.go
│ │ ├── gorm.go
│ │ ├── logrus.go
│ │ ├── server.go
│ │ ├── session.go
│ │ ├── xorm.go
│ │ ├── xsql.go
│ │ └── zap.go
│ ├── go.mod
│ ├── go.sum
│ ├── main.go
│ ├── middleware
│ │ ├── auth.go
│ │ └── cors.go
│ ├── routes
│ │ └── main.go
│ ├── runtime
│ │ └── .gitignore
│ └── shell
│ │ └── server.sh
├── cli-skeleton
│ ├── .env
│ ├── .gitignore
│ ├── README.md
│ ├── bin
│ │ └── .gitignore
│ ├── commands
│ │ ├── hello.go
│ │ └── main.go
│ ├── conf
│ │ └── config.yml
│ ├── config
│ │ ├── configor
│ │ │ └── main.go
│ │ ├── dotenv
│ │ │ └── main.go
│ │ ├── main.go
│ │ └── viper
│ │ │ └── main.go
│ ├── di
│ │ ├── goredis.go
│ │ ├── gorm.go
│ │ ├── logrus.go
│ │ ├── xorm.go
│ │ ├── xsql.go
│ │ └── zap.go
│ ├── go.mod
│ ├── go.sum
│ ├── logs
│ │ └── .gitignore
│ └── main.go
├── grpc-skeleton
│ ├── .env
│ ├── .gitignore
│ ├── README.md
│ ├── bin
│ │ └── .gitignore
│ ├── commands
│ │ ├── client.go
│ │ ├── main.go
│ │ ├── server.go
│ │ └── welcome.go
│ ├── conf
│ │ └── config.yml
│ ├── config
│ │ ├── configor
│ │ │ └── main.go
│ │ ├── dotenv
│ │ │ └── main.go
│ │ ├── main.go
│ │ └── viper
│ │ │ └── main.go
│ ├── di
│ │ ├── goredis.go
│ │ ├── gorm.go
│ │ ├── logrus.go
│ │ ├── xorm.go
│ │ ├── xsql.go
│ │ └── zap.go
│ ├── go.mod
│ ├── go.sum
│ ├── logs
│ │ └── .gitignore
│ ├── main.go
│ ├── protos
│ │ ├── user.pb.go
│ │ └── user.proto
│ ├── services
│ │ └── user.go
│ └── shell
│ │ └── server.sh
└── web-skeleton
│ ├── .env
│ ├── .gitignore
│ ├── README.md
│ ├── bin
│ └── .gitignore
│ ├── commands
│ ├── main.go
│ ├── web.go
│ └── welcome.go
│ ├── conf
│ └── config.yml
│ ├── config
│ ├── configor
│ │ └── main.go
│ ├── dotenv
│ │ └── main.go
│ ├── main.go
│ └── viper
│ │ └── main.go
│ ├── controllers
│ ├── hello.go
│ ├── login.go
│ ├── user.go
│ └── ws.go
│ ├── di
│ ├── goredis.go
│ ├── gorm.go
│ ├── logrus.go
│ ├── server.go
│ ├── session.go
│ ├── xorm.go
│ ├── xsql.go
│ └── zap.go
│ ├── go.mod
│ ├── go.sum
│ ├── main.go
│ ├── middleware
│ └── session.go
│ ├── public
│ ├── favicon.ico
│ └── static
│ │ └── index.html
│ ├── routes
│ └── main.go
│ ├── runtime
│ └── .gitignore
│ ├── shell
│ └── server.sh
│ └── templates
│ ├── index.tmpl
│ ├── login.tmpl
│ └── user_add.tmpl
└── src
├── authenticator
├── README.md
├── authenticator.go
├── authenticator_test.go
├── go.mod
└── go.sum
├── mixcli
├── README.md
├── bin
│ └── .gitignore
├── commands
│ ├── cmds.go
│ ├── new.go
│ └── version.go
├── go.mod
├── go.sum
├── logic
│ ├── filesystem.go
│ └── replace.go
└── main.go
├── xcli
├── LICENSE
├── README.md
├── application.go
├── application_test.go
├── argv
│ ├── argv.go
│ └── parse.go
├── command.go
├── error.go
├── flag
│ ├── arguments.go
│ ├── flag.go
│ ├── flag_test.go
│ ├── options.go
│ └── parse.go
├── go.mod
├── go.sum
└── process
│ └── daemon.go
├── xdi
├── README.md
├── container.go
├── container_test.go
├── go.mod
├── go.sum
└── object.go
├── xhttp
├── README.md
├── build.go
├── client.go
├── client_test.go
├── go.mod
├── go.sum
├── log.go
├── middleware.go
├── options.go
├── retry.go
└── shutdownctrl.go
├── xrpc
├── README.md
├── api
│ ├── example.pb.go
│ ├── example.pb.gw.go
│ ├── example.proto
│ └── example_grpc.pb.go
├── certificates
│ └── .gitignore
├── generate-pb.sh
├── generate-rsa.cnf
├── generate-rsa.sh
├── go.mod
├── go.sum
├── rpcclient.go
├── rpcclient_test.go
├── rpcserver.go
├── rpcserver_test.go
└── tls.go
├── xsql
├── README.md
├── db.go
├── db_test.go
├── dbora_test.go
├── executor.go
├── fetcher.go
├── go.mod
├── go.sum
├── interface.go
├── log.go
├── options.go
├── query.go
├── testdata
│ └── types.pb.go
├── tx.go
├── util.go
├── xsql.sql
└── xsqlora.sql
├── xutil
├── README.md
├── go.mod
├── go.sum
├── xconv
│ └── conv.go
├── xcrypt
│ ├── aes.go
│ └── aes_test.go
├── xenv
│ ├── README.md
│ ├── conv.go
│ └── load.go
├── xfmt
│ ├── README.md
│ ├── print.go
│ └── print_test.go
├── xslices
│ └── slices.go
└── xstrings
│ └── strings.go
└── xwp
├── LICENSE
├── README.md
├── go.mod
├── go.sum
├── worker.go
├── workerpool.go
└── workerpool_test.go
/.github/workflows/split.yml:
--------------------------------------------------------------------------------
1 | name: Split
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | jobs:
7 | split:
8 | runs-on: ubuntu-latest
9 | env:
10 | TOKEN: ${{ secrets.ACCESS_TOKEN }}
11 | steps:
12 | - name: Checkout
13 | uses: actions/checkout@v2
14 | with:
15 | persist-credentials: false
16 | fetch-depth: 0
17 | - name: Run
18 | run: |
19 | git config pull.rebase true
20 | git config --global user.email "actions@github.com"
21 | git config --global user.name "actions-bot"
22 | bash ./bin/split-linux.sh
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # goland project files
2 | .idea
3 |
4 | # netbeans project files
5 | nbproject
6 |
7 | # zend studio for eclipse project files
8 | .buildpath
9 | .project
10 | .settings
11 |
12 | # windows thumbnail cache
13 | Thumbs.db
14 |
15 | # Mac DS_Store Files
16 | .DS_Store
17 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
Mix Go
2 |
3 | English | [中文](README_CN.md)
4 |
5 | MixGo is a Go rapid development standard toolkit; the internal modules are highly decoupled, and the overall code is built on multiple independent modules. Even if users do not use our `mixcli` scaffolding to quickly generate code, they can also use these independent modules. For example: you can use `xcli` alone to build your command-line interaction; use `xsql` to call the database; use `xwp` to handle MQ queue consumption; you can freely combine all modules like building blocks.
6 |
7 | ## Independent Modules
8 |
9 | All core modules can be used independently.
10 |
11 | - [mix-go/mixcli](src/mixcli) Scaffold to quickly create Go projects, similar to Vue CLI in the frontend field.
12 | - [mix-go/xcli](src/xcli) Command-line interaction and command management tool, also includes command-line parameter acquisition, middleware, program daemon, etc.
13 | - [mix-go/xsql](src/xsql) Lightweight database based on database/sql, fully functional and supports any database driver.
14 | - [mix-go/xrpc](src/xrpc) Assistant for gRPC and Gateway.
15 | - [mix-go/xdi](src/xdi) IoC, DI library for handling object dependencies, can implement unified dependency management, global object management, dynamic configuration refresh, etc.
16 | - [mix-go/xwp](src/xwp) A universal work pool, coroutine pool, can dynamically expand and shrink.
17 | - [mix-go/xhttp](src/xhttp) A highly efficient HTTP library.
18 | - [mix-go/xutil](src/xutil) A set of tools to keep Golang sweet.
19 |
20 | ## Development Documentation
21 |
22 | - `V1.1` 1.[Quick Start](#Quick-Start) 2.[Examples](#Examples) 3.[Independent Modules](#Independent-Modules)
23 | - `V1.0` https://www.kancloud.cn/onanying/mixgo1/content
24 |
25 | ## Quick Start
26 |
27 | Provides ready-to-use scaffolding tools to quickly create projects and produce immediate output.
28 |
29 | ```bash
30 | go install github.com/mix-go/mixcli@latest
31 | ```
32 |
33 | ```bash
34 | $ mixcli new hello
35 | Use the arrow keys to navigate: ↓ ↑ → ←
36 | ? Select project type:
37 | ▸ CLI
38 | API
39 | Web (contains the websocket)
40 | gRPC
41 | ```
42 |
43 | If there is an error during compilation, tidy up the dependencies.
44 |
45 | ~~~
46 | go mod tidy
47 | ~~~
48 |
49 | ### Goland
50 |
51 | - [How to use MixGo in IDE Goland](https://zhuanlan.zhihu.com/p/391857663)
52 |
53 | ### Examples
54 |
55 | - [Write a CLI program](examples/cli-skeleton#readme)
56 | - [Write a Worker Pool queue consumer](examples/cli-skeleton#%E7%BC%96%E5%86%99%E4%B8%80%E4%B8%AA-worker-pool-%E9%98%9F%E5%88%97%E6%B6%88%E8%B4%B9)
57 | - [Write an API service](examples/api-skeleton#readme)
58 | - [Write a Web service](examples/web-skeleton#readme)
59 | - [Write a WebSocket service](examples/web-skeleton#%E7%BC%96%E5%86%99%E4%B8%80%E4%B8%AA-WebSocket-%E6%9C%8D%E5%8A%A1)
60 | - [Write a gRPC service, client](examples/grpc-skeleton#readme)
61 |
62 | ## Technical Discussion
63 |
64 | Zhihu: https://www.zhihu.com/people/onanying
65 | Official QQ Group: [284806582](https://shang.qq.com/wpa/qunwpa?idkey=b3a8618d3977cda4fed2363a666b081a31d89e3d31ab164497f53b72cf49968a), [825122875](http://shang.qq.com/wpa/qunwpa?idkey=d2908b0c7095fc7ec63a2391fa4b39a8c5cb16952f6cfc3f2ce4c9726edeaf20) Secret Password: gopher
66 |
67 | ## PHP Framework
68 |
69 | OpenMix also has PHP ecosystem frameworks:
70 |
71 | - https://github.com/mix-php/mix
72 |
73 | ## License
74 |
75 | Apache License Version 2.0, http://www.apache.org/licenses/
76 |
--------------------------------------------------------------------------------
/README_CN.md:
--------------------------------------------------------------------------------
1 | Mix Go
2 |
3 | [English](README.md) | 中文
4 |
5 | MixGo 是一个 Go 快速开发标准工具包;内部模块高度解耦,整体代码基于多个独立的模块构建,即便用户不使用我们的 `mixcli` 脚手架快速生成代码,也可以使用这些独立模块。例如:你可以只使用 `xcli` 来构建你的命令行交互;可以使用 `xsql` 来调用数据库;可以使用 `xwp` 来处理 MQ 队列消费;所有的模块你可以像搭积木一样随意组合。
6 |
7 | ## 独立模块
8 |
9 | 核心模块全部可独立使用。
10 |
11 | - [mix-go/mixcli](src/mixcli) 快速创建 Go 项目的脚手架,类似前端界的 Vue CLI。
12 | - [mix-go/xcli](src/xcli) 命令行交互与指挥管理工具,同时它还包括命令行参数获取、中间件、程序守护等。
13 | - [mix-go/xsql](src/xsql) 基于 database/sql 的轻量数据库,功能完备且支持任何数据库驱动。
14 | - [mix-go/xrpc](src/xrpc) gRPC 和 Gateway 助手。
15 | - [mix-go/xdi](src/xdi) 处理对象依赖关系的 IoC、DI 库,可以实现统一管理依赖,全局对象管理,动态配置刷新等。
16 | - [mix-go/xwp](src/xwp) 一个通用工作池、协程池,可动态扩容缩容。
17 | - [mix-go/xhttp](src/xhttp) 一个高效的 HTTP 库。
18 | - [mix-go/xutil](src/xutil) 一套让 Golang 保持甜美的工具。
19 |
20 | ## 开发文档
21 |
22 | - `V1.1` 1.[快速开始](#快速开始) 2.[示例](#示例) 3.[独立模块](#独立模块)
23 | - `V1.0` https://www.kancloud.cn/onanying/mixgo1/content
24 |
25 | ## 快速开始
26 |
27 | 提供了现成的脚手架工具,快速创建项目,立即产出。
28 |
29 | ```bash
30 | go install github.com/mix-go/mixcli@latest
31 | ```
32 |
33 | ```bash
34 | $ mixcli new hello
35 | Use the arrow keys to navigate: ↓ ↑ → ←
36 | ? Select project type:
37 | ▸ CLI
38 | API
39 | Web (contains the websocket)
40 | gRPC
41 | ```
42 |
43 | 如果编译时报错,整理一下依赖
44 |
45 | ~~~
46 | go mod tidy
47 | ~~~
48 |
49 | ### Goland
50 |
51 | - [MixGo 在 IDE Goland 中的如何使用](https://zhuanlan.zhihu.com/p/391857663)
52 |
53 | ### 示例
54 |
55 | - [编写一个 CLI 程序](examples/cli-skeleton#readme)
56 | - [编写一个 Worker Pool 队列消费](examples/cli-skeleton#%E7%BC%96%E5%86%99%E4%B8%80%E4%B8%AA-worker-pool-%E9%98%9F%E5%88%97%E6%B6%88%E8%B4%B9)
57 | - [编写一个 API 服务](examples/api-skeleton#readme)
58 | - [编写一个 Web 服务](examples/web-skeleton#readme)
59 | - [编写一个 WebSocket 服务](examples/web-skeleton#%E7%BC%96%E5%86%99%E4%B8%80%E4%B8%AA-WebSocket-%E6%9C%8D%E5%8A%A1)
60 | - [编写一个 gRPC 服务、客户端](examples/grpc-skeleton#readme)
61 |
62 | ## 技术交流
63 |
64 | 知乎:https://www.zhihu.com/people/onanying
65 | 官方QQ群:[284806582](https://shang.qq.com/wpa/qunwpa?idkey=b3a8618d3977cda4fed2363a666b081a31d89e3d31ab164497f53b72cf49968a), [825122875](http://shang.qq.com/wpa/qunwpa?idkey=d2908b0c7095fc7ec63a2391fa4b39a8c5cb16952f6cfc3f2ce4c9726edeaf20) 敲门暗号:gopher
66 |
67 | ## PHP 框架
68 |
69 | OpenMix 同时还有 PHP 生态的框架
70 |
71 | - https://github.com/mix-php/mix
72 | - https://gitee.com/mix-php/mix
73 |
74 | ## License
75 |
76 | Apache License Version 2.0, http://www.apache.org/licenses/
77 |
--------------------------------------------------------------------------------
/bin/release.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -e
3 |
4 | if (("$#" != 1)); then
5 | echo "Tag has to be provided"
6 |
7 | exit 1
8 | fi
9 |
10 | NOW=$(date +%s)
11 | TOKEN=$(cat .git/config | grep url | head -n 1 | awk -F'@' '{split($1,a,"//"); print a[2]}')
12 | CURRENT_BRANCH="master"
13 | VERSION=$1
14 | BASEPATH=$(
15 | cd $(dirname $0)
16 | cd ../src/
17 | pwd
18 | )
19 |
20 | # Always prepend with "v"
21 | if [[ $VERSION != v* ]]; then
22 | VERSION="v$VERSION"
23 | fi
24 |
25 | repos=$(ls $BASEPATH)
26 | repos="$repos cli-skeleton web-skeleton api-skeleton grpc-skeleton mix"
27 |
28 | for REMOTE in $repos; do
29 | echo ""
30 | echo ""
31 | echo "Cloning $REMOTE"
32 | TMP_DIR="/tmp/mix-split"
33 | REMOTE_URL="https://$TOKEN@github.com/mix-go/$REMOTE.git"
34 |
35 | rm -rf $TMP_DIR
36 | mkdir $TMP_DIR
37 |
38 | (
39 | cd $TMP_DIR
40 |
41 | git clone $REMOTE_URL .
42 | git checkout "$CURRENT_BRANCH"
43 |
44 | if [[ $(git log --pretty="%d" -n 1 | grep tag --count) -eq 0 ]]; then
45 | echo "Releasing $REMOTE"
46 | git tag $VERSION || true
47 | git push origin --tags
48 | fi
49 | )
50 | done
51 |
52 | TIME=$(echo "$(date +%s) - $NOW" | bc)
53 | printf "Execution time: %f seconds\n" $TIME
54 |
--------------------------------------------------------------------------------
/bin/setversion.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -e
3 |
4 | if (("$#" != 1)); then
5 | echo "Tag has to be provided"
6 |
7 | exit 1
8 | fi
9 |
10 | NOW=$(date +%s)
11 | CURRENT_BRANCH="master"
12 | VERSION=$1
13 | EXAMPLESPATH=$(cd $(dirname $0); cd ../examples/; pwd)
14 | SRCPATH=$(cd $(dirname $0); cd ../src/; pwd)
15 |
16 | # Always prepend with "v"
17 | if [[ $VERSION != v* ]]; then
18 | VERSION="v$VERSION"
19 | fi
20 |
21 | repos=$(ls $EXAMPLESPATH)
22 |
23 | for SKELETON in $repos; do
24 | echo $SKELETON
25 | (
26 | cd $(dirname $0)
27 | cd ../examples/$SKELETON
28 | go get -u all && go mod tidy
29 | )
30 | done
31 |
32 | # Update devtool version
33 | sed -i "" "s/SkeletonVersion = \".*/SkeletonVersion = \"${VERSION##*v}\"/g" ${SRCPATH}/mixcli/commands/version.go
34 | sed -i "" "s/CLIVersion = \".*/CLIVersion = \"${VERSION##*v}\"/g" ${SRCPATH}/mixcli/commands/version.go
35 |
36 | TIME=$(echo "$(date +%s) - $NOW" | bc)
37 | printf "Execution time: %f seconds\n" $TIME
38 |
--------------------------------------------------------------------------------
/bin/split-linux.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 | set -x
5 |
6 | NOW=$(date +%s)
7 | CURRENT_BRANCH="master"
8 | REPOS=$@
9 |
10 | function split()
11 | {
12 | SHA1=`./bin/splitsh-lite-linux --prefix=$1`
13 | git push $2 "$SHA1:refs/heads/$CURRENT_BRANCH" -f
14 | }
15 |
16 | function remote()
17 | {
18 | git remote add $1 $2 || true
19 | }
20 |
21 | git pull origin $CURRENT_BRANCH
22 |
23 | IGNORE="NONE"
24 |
25 | WORKDIR="src"
26 | BASEPATH=$(cd `dirname $0`; cd ../$WORKDIR/; pwd)
27 |
28 | if [[ $# -eq 0 ]]; then
29 | REPOS=$(ls $BASEPATH | grep -v $IGNORE)
30 | fi
31 | for REPO in $REPOS ; do
32 | remote $REPO https://$TOKEN@github.com/mix-go/$REPO.git
33 | split "$WORKDIR/$REPO" $REPO
34 | done
35 |
36 | WORKDIR="examples"
37 | BASEPATH=$(cd `dirname $0`; cd ../$WORKDIR/; pwd)
38 | if [[ $# -eq 0 ]]; then
39 | REPOS=$(ls $BASEPATH | grep -v $IGNORE)
40 | fi
41 | for REPO in $REPOS ; do
42 | remote $REPO https://$TOKEN@github.com/mix-go/$REPO.git
43 | split "$WORKDIR/$REPO" $REPO
44 | done
45 |
46 | TIME=$(echo "$(date +%s) - $NOW" | bc)
47 | printf "Execution time: %f seconds\n" $TIME
48 |
--------------------------------------------------------------------------------
/bin/splitsh-lite-darwin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mix-go/mix/8559688b0ade0b1c8fb4c3450f403f673e5f59f7/bin/splitsh-lite-darwin
--------------------------------------------------------------------------------
/bin/splitsh-lite-linux:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mix-go/mix/8559688b0ade0b1c8fb4c3450f403f673e5f59f7/bin/splitsh-lite-linux
--------------------------------------------------------------------------------
/docs/1.1/_navbar.md:
--------------------------------------------------------------------------------
1 | * 多语言/Translations
2 | * [简体中文](zh-cn/)
--------------------------------------------------------------------------------
/docs/1.1/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | MixGo V1.1 开发文档
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
63 |
64 |
65 |
66 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/docs/1.1/zh-cn/README.md:
--------------------------------------------------------------------------------
1 | > OpenMix 出品:[https://openmix.org](https://openmix.org/mix-go)
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | Mix Go
12 |
13 | ## 简介
14 |
15 | MixGo 是一个 Go 快速开发标准工具包;内部模块高度解耦,整体代码基于多个独立的模块构建,即便用户不使用我们的 `mixcli` 脚手架快速生成代码,也可以使用这些独立模块。例如:你可以只使用 `xcli` 来构建你的命令行交互;可以使用 `xsql` 来调用数据库;可以使用 `xwp` 来处理 MQ 队列消费;所有的模块你可以像搭积木一样随意组合。
16 |
17 | ## 请帮忙 Star 一下
18 |
19 | - https://github.com/mix-go/mix
20 | - https://gitee.com/mix-go/mix
21 |
22 | ## 独立模块
23 |
24 | 核心模块全部可独立使用。
25 |
26 | - [mix-go/mixcli](zh-cn/mix-mixcli) 快速创建 Go 项目的脚手架,类似前端界的 Vue CLI。
27 | - [mix-go/xcli](zh-cn/mix-xcli) 命令行交互与指挥管理工具,同时它还包括命令行参数获取、中间件、程序守护等。
28 | - [mix-go/xsql](zh-cn/mix-xsql) 基于 database/sql 的轻量数据库,功能完备且支持任何数据库驱动。
29 | - [mix-go/xdi](zh-cn/mix-xdi) 处理对象依赖关系的 IoC、DI 库,可以实现统一管理依赖,全局对象管理,动态配置刷新等。
30 | - [mix-go/xwp](zh-cn/mix-xwp) 一个通用工作池、协程池,可动态扩容缩容。
31 | - [mix-go/xfmt](zh-cn/mix-xfmt) 可以打印结构体嵌套指针地址内部数据的格式化库。
32 | - [mix-go/dotenv](zh-cn/mix-dotenv) 具有类型转换功能的 DotEnv 环境配置库。
33 |
34 | ## PHP 框架
35 |
36 | OpenMix 同时还有 PHP 生态的框架
37 |
38 | - https://github.com/mix-php/mix
39 | - https://gitee.com/mix-php/mix
40 |
41 | ## 旧版文档
42 |
43 | - `V1.0` https://www.kancloud.cn/onanying/mixgo1/content
44 |
--------------------------------------------------------------------------------
/docs/1.1/zh-cn/_navbar.md:
--------------------------------------------------------------------------------
1 | * 多语言/Translations
2 | * [简体中文](zh-cn/)
--------------------------------------------------------------------------------
/docs/1.1/zh-cn/instructions.md:
--------------------------------------------------------------------------------
1 | # 编程须知
2 |
3 | ## 编译与执行
4 |
5 | - 不可直接 `go run main.go`
6 |
7 | 由于骨架代码有读取 `.env`、`conf/config.yml` 的配置文件,而配置文件的路径采用的是编译的二进制文件所在的 `bin` 目录的相对路径,由于 `go run main.go` 生成的二进制文件路径是由go编译器随机指定在 `/var/folders/` 目录底下一个随机临时目录,此时依据相对路径是无法成功读取配置文件的,因此会抛出 `panic` 异常提示找不到配置文件。
8 |
9 | ```
10 | // 切换到项目根目录
11 | cd project
12 |
13 | // 只能这样编译到 project/bin 目录
14 | go build -o bin/go_build_main_go main.go
15 |
16 | // 执行编译好的程序
17 | bin/go_build_main_go api
18 | ```
19 |
20 | 上面的命令可以合并执行
21 |
22 | ```
23 | go build -o bin/go_build_main_go main.go && bin/go_build_main_go api
24 | ```
25 |
26 | ## `Goland` 如何使用
27 |
28 | 使用的 `Goland` 请阅读以下文章,学习更加快速的编译方法
29 |
30 | - [MixGo 在 IDE Goland 中的如何使用](https://zhuanlan.zhihu.com/p/391857663)
31 |
32 |
--------------------------------------------------------------------------------
/docs/1.1/zh-cn/mix-mixcli.md:
--------------------------------------------------------------------------------
1 | ## Mix CLI
2 |
3 | 一个快速创建 Go 项目的脚手架
4 |
5 | ## Installation
6 |
7 | ```
8 | go install -u github.com/mix-go/mixcli
9 | ```
10 |
11 | ## New Project
12 |
13 | 创建项目
14 |
15 | - 可以生成 `cli`, `api`, `web`, `grpc` 多种项目代码,生成的代码开箱即用
16 | - 可选择是否需要 `.env` 环境配置
17 | - 可选择使用 `viper`, `configor` 加载 `.yml`, `.json`, `.toml` 等独立配置
18 | - 可选择使用 `gorm`, `xorm` 的数据库
19 | - 可选择使用 `zap`, `logrus` 的日志库
20 |
21 | ~~~
22 | $ mixcli new hello
23 | Use the arrow keys to navigate: ↓ ↑ → ←
24 | ? Select project type:
25 | ▸ CLI
26 | API
27 | Web (contains the websocket)
28 | gRPC
29 | ~~~
30 |
--------------------------------------------------------------------------------
/docs/1.1/zh-cn/mix-varwatch.md:
--------------------------------------------------------------------------------
1 | ## Mix VarWatch
2 |
3 | 监视配置结构体变量的数据变化并执行一些任务
4 |
5 | ## Installation
6 |
7 | ```
8 | go get github.com/mix-go/varwatch
9 | ```
10 | ## Usage
11 |
12 | 当采用 [spf13/viper](https://github.com/spf13/viper) [jinzhu/configor](https://github.com/jinzhu/configor) 这种绑定变量的配置库来动态更新配置信息
13 |
14 | > 任何采用 &Config 指针绑定数据的配置库都可以
15 |
16 | ~~~go
17 | var Config struct {
18 | Logger struct {
19 | Level int `json:"level"`
20 | } `json:"logger" varwatch:"logger"`
21 | Database struct {
22 | User string `json:"user"`
23 | Pwd string `json:"pwd"`
24 | Db string `json:"db"`
25 | MaxOpen int `json:"max_open"`
26 | MaxIdle int `json:"max_idle"`
27 | } `json:"database" varwatch:"database"`
28 | }
29 |
30 | err := viper.Unmarshal(&Config)
31 | ~~~
32 |
33 | 以动态修改日志级别举例:当 `Config.Logger.Level` 发生变化时我们需要执行一些代码修改日志的级别
34 |
35 | - 首先将 Logger 节点配置 `varwatch:"logger"` 标签信息
36 | - 然后采用以下代码执行监听逻辑
37 |
38 | ```go
39 | w := varwatch.NewWatcher(&Config, 10 * time.Second)
40 | w.Watch("logger", func() {
41 | // 获取变化后的值
42 | lv := Config.Logger.Level
43 | // 修改 logrus 的日志级别
44 | logrus.SetLevel(logrus.Level(uint32(lv)))
45 | })
46 | ```
47 |
48 | 需要动态修改连接池信息,或者数据库账号密码都可以通过上面的范例实现。
49 |
--------------------------------------------------------------------------------
/docs/1.1/zh-cn/mix-xdi.md:
--------------------------------------------------------------------------------
1 | ## Mix XDI
2 |
3 | DI、IoC 容器,创建对象以及处理对象依赖关系,该库可以实现统一管理依赖,全局对象管理,动态配置刷新等。
4 |
5 | ## Installation
6 |
7 | ```
8 | go get github.com/mix-go/xdi
9 | ```
10 |
11 | ## Quick start
12 |
13 | 通过依赖配置实例化一个单例
14 |
15 | ```go
16 | package main
17 |
18 | import (
19 | "github.com/mix-go/xdi"
20 | )
21 |
22 | type Foo struct {
23 | }
24 |
25 | func init() {
26 | obj := &xdi.Object{
27 | Name: "foo",
28 | New: func() (interface{}, error) {
29 | i := &Foo{}
30 | return i, nil
31 | },
32 | }
33 | if err := xdi.Provide(obj); err != nil {
34 | panic(err)
35 | }
36 | }
37 |
38 | func main() {
39 | var foo *Foo
40 | if err := xdi.Populate("foo", &foo); err != nil {
41 | panic(err)
42 | }
43 | // use foo
44 | }
45 | ```
46 |
47 | ## Reference
48 |
49 | 依赖配置中引用另一个依赖配置的实例
50 |
51 | ```go
52 | package main
53 |
54 | import (
55 | "github.com/mix-go/xdi"
56 | )
57 |
58 | type Foo struct {
59 | Bar *Bar
60 | }
61 |
62 | type Bar struct {
63 | }
64 |
65 | func init() {
66 | objs := []*xdi.Object{
67 | {
68 | Name: "foo",
69 | New: func() (interface{}, error) {
70 | // reference bar
71 | var bar *Bar
72 | if err := xdi.Populate("bar", &bar); err != nil {
73 | return nil, err
74 | }
75 |
76 | i := &Foo{
77 | Bar: bar,
78 | }
79 | return i, nil
80 | },
81 | },
82 | {
83 | Name: "bar",
84 | New: func() (interface{}, error) {
85 | i := &Bar{}
86 | return i, nil
87 | },
88 | NewEverytime: true,
89 | },
90 | }
91 | if err := xdi.Provide(objs...); err != nil {
92 | panic(err)
93 | }
94 | }
95 |
96 | func main() {
97 | var foo *Foo
98 | if err := xdi.Populate("foo", &foo); err != nil {
99 | panic(err)
100 | }
101 | // use foo
102 | }
103 | ```
104 |
105 | ## Refresh singleton
106 |
107 | 程序执行中配置信息发生变化时,`Refresh()` 可以刷新单例的实例来切换使用新的配置,通常在微服务配置中心中使用。
108 |
109 | ```go
110 | obj, err := xdi.Container().Object("foo")
111 | if err != nil {
112 | panic(err)
113 | }
114 | if err := obj.Refresh(); err != nil {
115 | panic(err)
116 | }
117 | ```
118 |
--------------------------------------------------------------------------------
/docs/1.1/zh-cn/mix-xutil.md:
--------------------------------------------------------------------------------
1 | > Produced by OpenMix: [https://openmix.org](https://openmix.org/mix-go)
2 |
3 | ## Mix XUtil
4 |
5 | A set of tools that keep Golang sweet.
6 |
7 | ## Installation
8 |
9 | ```
10 | go get github.com/mix-go/xutil
11 | ```
12 |
13 | ## xhttp
14 |
15 | | Function | Description |
16 | |--------------------------------------------------------------------------------------------|-------------------------|
17 | | xhttp.Request(method string, u string, body string, opts ...Options) ([]byte, error) | Execute a http request. |
18 | | xhttp.RequestString(method string, u string, body string, opts ...Options) (string, error) | Execute a http request. |
19 |
20 | ## xslices
21 |
22 | | Function | Description |
23 | |-------------------------------------------------------|------------------------------------------------------|
24 | | xslices.InArray[T comparable](item T, slice []T) bool | Searches if the specified value exists in the array. |
25 |
26 | ## xstrings
27 |
28 | | Function | Description |
29 | |-----------------------------------|----------------------------------------------------------------|
30 | | xstrings.IsNumeric(s string) bool | Used to check if the variable is a number or a numeric string. |
31 |
32 | ## xfmt
33 |
34 | A formatting library that ca print data inside nested pointer addresses of
35 | structures, [see more](https://github.com/mix-go/mix/blob/master/src/xutil/xfmt/README.md).
36 |
37 | The supported methods are identical to the `fmt` system library
38 |
39 | | Function | Description |
40 | |---------------------------------------------------------|-------------------|
41 | | xfmt.Sprintf(format string, args ...interface{}) string | |
42 | | xfmt.Sprint(args ...interface{}) string | |
43 | | xfmt.Sprintln(args ...interface{}) string | |
44 | | xfmt.Printf(format string, args ...interface{}) | |
45 | | xfmt.Print(args ...interface{}) | |
46 | | xfmt.Println(args ...interface{}) | |
47 | | xfmt.Disable() | Equivalent to fmt |
48 | | xfmt.Enable() | |
49 |
50 | ## xenv
51 |
52 | Environment configuration library with type
53 | conversion, [see more](https://github.com/mix-go/mix/blob/master/src/xutil/xenv/README.md).
54 |
55 | | Function | Description |
56 | |-------------------------------------------|-------------|
57 | | err := xenv.Load(".env") | |
58 | | err := xenv.Overload(".env") | |
59 | | i := xenv.Getenv("key").String("default") | |
60 | | i := xenv.Getenv("key").Bool(false) | |
61 | | i := xenv.Getenv("key").Int64(123) | |
62 | | i := xenv.Getenv("key").Float64(123.4) | |
63 |
64 | ## License
65 |
66 | Apache License Version 2.0, http://www.apache.org/licenses/
67 |
--------------------------------------------------------------------------------
/docs/1.1/zh-cn/mix-xwp.md:
--------------------------------------------------------------------------------
1 | ## Mix XWP
2 |
3 | 通用动态工作池、协程池
4 |
5 | ## 安装
6 |
7 | ```
8 | go get github.com/mix-go/xwp
9 | ```
10 |
11 | ## 单次调度
12 |
13 | > 适合处理数据计算、转换等场景
14 |
15 | 先创建一个结构体用来处理任务,使用类型断言转换任务数据类型,例如:`i := data.(int)`
16 |
17 | ~~~go
18 | type Foo struct {
19 | }
20 |
21 | func (t *Foo) Do(data interface{}) {
22 | // do something
23 | }
24 | ~~~
25 |
26 | 调度任务
27 |
28 | - 也可以使用 `RunF` 采用闭包来处理任务
29 | - 如果不想阻塞执行,可以使用 `p.Start()` 启动
30 |
31 | ~~~go
32 | jobQueue := make(chan interface{}, 200)
33 | p := &xwp.WorkerPool{
34 | JobQueue: jobQueue,
35 | MaxWorkers: 1000,
36 | InitWorkers: 100,
37 | MaxIdleWorkers: 100,
38 | RunI: &Foo{},
39 | }
40 |
41 | go func() {
42 | // 投放任务
43 | for i := 0; i < 10000; i++ {
44 | jobQueue <- i
45 | }
46 |
47 | // 投放完停止调度
48 | p.Stop()
49 | }()
50 |
51 | p.Run() // 阻塞等待
52 | ~~~
53 |
54 | ## 常驻调度
55 |
56 | > 适合处理 MQ 队列的异步消费
57 |
58 | 以 Redis 作为 MQ 为例:
59 |
60 | ~~~go
61 | jobQueue := make(chan interface{}, 200)
62 | p := &xwp.WorkerPool{
63 | JobQueue: jobQueue,
64 | MaxWorkers: 1000,
65 | InitWorkers: 100,
66 | MaxIdleWorkers: 100,
67 | RunI: &Foo{},
68 | }
69 |
70 | ch := make(chan os.Signal)
71 | signal.Notify(ch, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
72 | go func() {
73 | <-ch
74 | p.Stop()
75 | }()
76 |
77 | go func() {
78 | for {
79 | res, err := redis.BRPop(context.Background(), 3*time.Second, "foo").Result()
80 | if err != nil {
81 | if strings.Contains(err.Error(), "redis: nil") {
82 | continue
83 | }
84 | fmt.Println(fmt.Sprintf("Redis Error: %s", err))
85 | p.Stop()
86 | return
87 | }
88 | // brPop命令最后一个键才是值
89 | jobQueue <- res[1]
90 | }
91 | }()
92 |
93 | p.Run() // 阻塞等待
94 | ~~~
95 |
96 | ## 异常处理
97 |
98 | `Do` 方法中执行的代码,可能会出现 `panic` 异常,我们可以通过 `recover` 获取异常信息记录到日志或者执行其他处理
99 |
100 | ~~~go
101 | func (t *Foo) Do(data interface{}) {
102 | defer func() {
103 | if err := recover(); err != nil {
104 | // handle error
105 | }
106 | }()
107 | // do something
108 | }
109 | ~~~
110 |
111 | ## 执行状态
112 |
113 | `Stats()` 可以返回 `Workers` 实时执行状态,通常可以使用一个定时器,定时打印或者告警处理
114 |
115 | ```go
116 | go func() {
117 | ticker := time.NewTicker(1000 * time.Millisecond)
118 | for {
119 | <-ticker.C
120 | log.Printf("%+v", p.Stats()) // 2021/04/26 14:32:53 &{Active:5 Idle:95 Total:100}
121 | }
122 | }()
123 | ```
124 |
--------------------------------------------------------------------------------
/docs/1.1/zh-cn/online-chating.md:
--------------------------------------------------------------------------------
1 | ## QQ 交流群
2 |
3 | 敲门暗号:`gopher`
4 |
5 | - OpenMix 技术交流 A 群: [284806582](https://shang.qq.com/wpa/qunwpa?idkey=b3a8618d3977cda4fed2363a666b081a31d89e3d31ab164497f53b72cf49968a)
6 | - OpenMix 技术交流 B 群: [825122875](http://shang.qq.com/wpa/qunwpa?idkey=d2908b0c7095fc7ec63a2391fa4b39a8c5cb16952f6cfc3f2ce4c9726edeaf20)
7 |
8 | ## 知乎
9 |
10 | - 作者: https://www.zhihu.com/people/onanying
11 | - MixGo 专栏: https://www.zhihu.com/column/mix-go
12 |
13 | ## 视频教程
14 |
15 | [](https://openstr.com/watch/aa328ff33de085aa8fc87301056f3407)
16 | [](https://openstr.com/watch/41e9dc609cb8f9a4530fe8f7a37f1130)
17 |
--------------------------------------------------------------------------------
/docs/1.1/zh-cn/summary.md:
--------------------------------------------------------------------------------
1 | * 序言
2 |
3 | * [项目介绍](zh-cn/README.md)
4 | * [技术交流](zh-cn/online-chating.md)
5 |
6 | * 快速开始
7 |
8 | * [编程须知](zh-cn/instructions.md)
9 | * [编写 CLI 程序](zh-cn/write-cli.md)
10 | * [编写 API 接口](zh-cn/write-api.md)
11 | * [编写 Web 页面](zh-cn/write-web.md)
12 | * [编写 gRPC 接口](zh-cn/write-grpc.md)
13 |
14 | * 独立模块
15 |
16 | * [mix-go/mixcli](zh-cn/mix-mixcli.md)
17 | * [mix-go/xcli](zh-cn/mix-xcli.md)
18 | * [mix-go/xsql](zh-cn/mix-xsql.md)
19 | * [mix-go/xdi](zh-cn/mix-xdi.md)
20 | * [mix-go/xwp](zh-cn/mix-xwp.md)
21 | * [mix-go/xutil](zh-cn/mix-xutil.md)
22 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | MixGo 开发文档
6 |
25 |
26 |
27 | MixGo 开发文档
28 |
32 |
33 |
--------------------------------------------------------------------------------
/examples/api-skeleton/.env:
--------------------------------------------------------------------------------
1 | # APP
2 | APP_DEBUG=true
3 |
4 | # DATABASE
5 | DATABASE_DSN=root:123456@tcp(127.0.0.1:3306)/test?charset=utf8&loc=Asia%2FShanghai&parseTime=true&timeout=10s
6 |
7 | # REDIS
8 | REDIS_ADDR=127.0.0.1:6379
9 | REDIS_DATABASE=0
10 | REDIS_PASSWORD=
11 | REDIS_DIAL_TIMEOUT=10
12 |
13 | # GIN
14 | GIN_ADDR=:8080
15 | GIN_MODE=test
16 |
17 | # JWT
18 | HMAC_SECRET=my_secret_key
19 |
--------------------------------------------------------------------------------
/examples/api-skeleton/.gitignore:
--------------------------------------------------------------------------------
1 | # goland project files
2 | .idea
3 |
4 | # netbeans project files
5 | nbproject
6 |
7 | # zend studio for eclipse project files
8 | .buildpath
9 | .project
10 | .settings
11 |
12 | # windows thumbnail cache
13 | Thumbs.db
14 |
15 | # Mac DS_Store Files
16 | .DS_Store
17 |
--------------------------------------------------------------------------------
/examples/api-skeleton/bin/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
--------------------------------------------------------------------------------
/examples/api-skeleton/commands/api.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "github.com/gin-gonic/gin"
7 | "github.com/mix-go/api-skeleton/di"
8 | "github.com/mix-go/api-skeleton/routes"
9 | "github.com/mix-go/xcli/flag"
10 | "github.com/mix-go/xcli/process"
11 | "github.com/mix-go/xutil/xenv"
12 | "os"
13 | "os/signal"
14 | "strings"
15 | "syscall"
16 | "time"
17 | )
18 |
19 | type APICommand struct {
20 | }
21 |
22 | func (t *APICommand) Main() {
23 | if flag.Match("d", "daemon").Bool() {
24 | process.Daemon()
25 | }
26 |
27 | logger := di.Logrus()
28 | server := di.Server()
29 | addr := xenv.Getenv("GIN_ADDR").String(":8080")
30 | mode := xenv.Getenv("GIN_MODE").String(gin.ReleaseMode)
31 |
32 | // server
33 | gin.SetMode(mode)
34 | router := gin.New()
35 | if mode != gin.ReleaseMode {
36 | handlerFunc := gin.LoggerWithConfig(gin.LoggerConfig{
37 | Formatter: func(params gin.LogFormatterParams) string {
38 | return fmt.Sprintf("%s|%s|%d|%s\n",
39 | params.Method,
40 | params.Path,
41 | params.StatusCode,
42 | params.ClientIP,
43 | )
44 | },
45 | Output: logger.Writer(),
46 | })
47 | router.Use(handlerFunc)
48 | }
49 | routes.Load(router)
50 | server.Addr = flag.Match("a", "addr").String(addr)
51 | server.Handler = router
52 |
53 | // signal
54 | ch := make(chan os.Signal)
55 | signal.Notify(ch, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
56 | go func() {
57 | <-ch
58 | logger.Info("Server shutdown")
59 | ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
60 | if err := server.Shutdown(ctx); err != nil {
61 | logger.Errorf("Server shutdown error: %s", err)
62 | }
63 | }()
64 |
65 | // run
66 | welcome()
67 | logger.Infof("Server start at %s", server.Addr)
68 | if err := server.ListenAndServe(); err != nil && !strings.Contains(err.Error(), "http: Server closed") {
69 | panic(err)
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/examples/api-skeleton/commands/main.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "github.com/mix-go/xcli"
5 | )
6 |
7 | var Commands = []*xcli.Command{
8 | {
9 | Name: "api",
10 | Short: "\tStart the api server",
11 | Options: []*xcli.Option{
12 | {
13 | Names: []string{"a", "addr"},
14 | Usage: "\tListen to the specified address",
15 | },
16 | {
17 | Names: []string{"d", "daemon"},
18 | Usage: "\tRun in the background",
19 | },
20 | },
21 | RunI: &APICommand{},
22 | },
23 | }
24 |
--------------------------------------------------------------------------------
/examples/api-skeleton/commands/welcome.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "fmt"
5 | "github.com/mix-go/api-skeleton/di"
6 | "runtime"
7 | "strings"
8 | )
9 |
10 | const logo = ` ___
11 | ______ ___ _ /__ ___ _____ ______
12 | / __ *__ \/ /\ \/ /__ __ */ __ \
13 | / / / / / / / /\ \/ _ /_/ // /_/ /
14 | /_/ /_/ /_/_/ /_/\_\ \__, / \____/
15 | /____/
16 | `
17 |
18 | func welcome() {
19 | fmt.Println(strings.Replace(logo, "*", "`", -1))
20 | fmt.Println("")
21 | fmt.Println(fmt.Sprintf("Server Name: %s", "mix-api"))
22 | fmt.Println(fmt.Sprintf("System Name: %s", runtime.GOOS))
23 | fmt.Println(fmt.Sprintf("Go Version: %s", runtime.Version()[2:]))
24 | fmt.Println(fmt.Sprintf("Listen Addr: %s", di.Server().Addr))
25 | }
26 |
--------------------------------------------------------------------------------
/examples/api-skeleton/conf/config.yml:
--------------------------------------------------------------------------------
1 | foo: bar
2 |
--------------------------------------------------------------------------------
/examples/api-skeleton/config/configor/main.go:
--------------------------------------------------------------------------------
1 | package configor
2 |
3 | import (
4 | "fmt"
5 | "github.com/jinzhu/configor"
6 | "github.com/mix-go/api-skeleton/config"
7 | "github.com/mix-go/xcli/argv"
8 | )
9 |
10 | func init() {
11 | // Conf support YAML, JSON, TOML, Shell Environment
12 | if err := configor.Load(&config.Config, fmt.Sprintf("%s/../conf/config.yml", argv.Program().Dir)); err != nil {
13 | panic(err)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/examples/api-skeleton/config/dotenv/main.go:
--------------------------------------------------------------------------------
1 | package dotenv
2 |
3 | import (
4 | "fmt"
5 | "github.com/mix-go/xcli/argv"
6 | "github.com/mix-go/xutil/xenv"
7 | )
8 |
9 | func init() {
10 | // Env
11 | if err := xenv.Load(fmt.Sprintf("%s/../.env", argv.Program().Dir)); err != nil {
12 | panic(err)
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/examples/api-skeleton/config/main.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | var Config = struct {
4 | Foo string
5 | }{}
6 |
--------------------------------------------------------------------------------
/examples/api-skeleton/config/viper/main.go:
--------------------------------------------------------------------------------
1 | package viper
2 |
3 | import (
4 | "fmt"
5 | "github.com/mix-go/api-skeleton/config"
6 | "github.com/mix-go/xcli/argv"
7 | "github.com/spf13/viper"
8 | )
9 |
10 | func init() {
11 | // Conf support JSON, TOML, YAML, HCL, INI, envfile
12 | viper.SetConfigFile(fmt.Sprintf("%s/../conf/config.yml", argv.Program().Dir))
13 | if err := viper.ReadInConfig(); err != nil {
14 | panic(err)
15 | }
16 | if err := viper.Unmarshal(&config.Config); err != nil {
17 | panic(err)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/examples/api-skeleton/controllers/auth.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "github.com/golang-jwt/jwt/v4"
6 | "github.com/mix-go/xutil/xenv"
7 | "net/http"
8 | "time"
9 | )
10 |
11 | type AuthController struct {
12 | }
13 |
14 | func (t *AuthController) Index(c *gin.Context) {
15 | // 检查用户登录代码
16 | // ...
17 |
18 | // 创建 token
19 | now := time.Now().Unix()
20 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
21 | "iss": "http://example.org", // 签发人
22 | "iat": now, // 签发时间
23 | "exp": now + int64(7200), // 过期时间
24 | "nbf": time.Date(2015, 10, 10, 12, 0, 0, 0, time.UTC).Unix(), // 什么时间之前不可用
25 | "uid": 100008,
26 | })
27 | tokenString, err := token.SignedString([]byte(xenv.Getenv("HMAC_SECRET").String()))
28 | if err != nil {
29 | c.JSON(http.StatusInternalServerError, gin.H{
30 | "status": http.StatusInternalServerError,
31 | "message": "Creation of token fails",
32 | })
33 | }
34 |
35 | c.JSON(http.StatusOK, gin.H{
36 | "status": http.StatusOK,
37 | "message": "ok",
38 | "access_token": tokenString,
39 | "expire_in": 7200,
40 | })
41 | }
42 |
--------------------------------------------------------------------------------
/examples/api-skeleton/controllers/hello.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "net/http"
6 | )
7 |
8 | type HelloController struct {
9 | }
10 |
11 | func (t *HelloController) Index(c *gin.Context) {
12 | c.JSON(http.StatusOK, gin.H{
13 | "status": http.StatusOK,
14 | "message": "hello, world!",
15 | })
16 | }
17 |
--------------------------------------------------------------------------------
/examples/api-skeleton/controllers/user.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "net/http"
6 | )
7 |
8 | type UserController struct {
9 | }
10 |
11 | func (t *UserController) Add(c *gin.Context) {
12 | // 执行数据库操作
13 | // ...
14 |
15 | c.JSON(http.StatusOK, gin.H{
16 | "status": http.StatusOK,
17 | "message": "ok",
18 | })
19 | }
20 |
--------------------------------------------------------------------------------
/examples/api-skeleton/di/goredis.go:
--------------------------------------------------------------------------------
1 | package di
2 |
3 | import (
4 | "github.com/mix-go/xdi"
5 | "github.com/mix-go/xutil/xenv"
6 | "github.com/redis/go-redis/v9"
7 | "time"
8 | )
9 |
10 | func init() {
11 | obj := xdi.Object{
12 | Name: "goredis",
13 | New: func() (i interface{}, e error) {
14 | opt := redis.Options{
15 | Addr: xenv.Getenv("REDIS_ADDR").String(),
16 | Password: xenv.Getenv("REDIS_PASSWORD").String(),
17 | DB: int(xenv.Getenv("REDIS_DATABASE").Int64()),
18 | DialTimeout: time.Duration(xenv.Getenv("REDIS_DIAL_TIMEOUT").Int64(10)) * time.Second,
19 | }
20 | return redis.NewClient(&opt), nil
21 | },
22 | }
23 | if err := xdi.Provide(&obj); err != nil {
24 | panic(err)
25 | }
26 | }
27 |
28 | func GoRedis() (client *redis.Client) {
29 | if err := xdi.Populate("goredis", &client); err != nil {
30 | panic(err)
31 | }
32 | return
33 | }
34 |
--------------------------------------------------------------------------------
/examples/api-skeleton/di/gorm.go:
--------------------------------------------------------------------------------
1 | package di
2 |
3 | import (
4 | "github.com/mix-go/xdi"
5 | "github.com/mix-go/xutil/xenv"
6 | "gorm.io/driver/mysql"
7 | "gorm.io/gorm"
8 | )
9 |
10 | func init() {
11 | obj := xdi.Object{
12 | Name: "gorm",
13 | New: func() (i interface{}, e error) {
14 | return gorm.Open(mysql.Open(xenv.Getenv("DATABASE_DSN").String()))
15 | },
16 | }
17 | if err := xdi.Provide(&obj); err != nil {
18 | panic(err)
19 | }
20 | }
21 |
22 | func Gorm() (db *gorm.DB) {
23 | if err := xdi.Populate("gorm", &db); err != nil {
24 | panic(err)
25 | }
26 | return
27 | }
28 |
--------------------------------------------------------------------------------
/examples/api-skeleton/di/logrus.go:
--------------------------------------------------------------------------------
1 | package di
2 |
3 | import (
4 | "fmt"
5 | "github.com/mix-go/xcli"
6 | "github.com/mix-go/xdi"
7 | "github.com/sirupsen/logrus"
8 | "gopkg.in/natefinch/lumberjack.v2"
9 | "io"
10 | "os"
11 | "path/filepath"
12 | "runtime"
13 | )
14 |
15 | func init() {
16 | obj := xdi.Object{
17 | Name: "logrus",
18 | New: func() (i interface{}, e error) {
19 | logger := logrus.New()
20 | logger.ReportCaller = true // 显示调用信息
21 | formatter := new(logrus.TextFormatter)
22 | formatter.FullTimestamp = true
23 | formatter.TimestampFormat = "2006-01-02 15:04:05.000"
24 | formatter.DisableQuote = true // 不转义换行符,为了保存错误堆栈到日志文件
25 | formatter.CallerPrettyfier = func(frame *runtime.Frame) (function string, file string) {
26 | return "", fmt.Sprintf("%s:%d", filepath.Base(frame.File), frame.Line)
27 | }
28 | logger.Formatter = formatter
29 | filename := fmt.Sprintf("%s/../runtime/logs/cli.log", xcli.App().BasePath)
30 | fileRotate := &lumberjack.Logger{
31 | Filename: filename,
32 | MaxBackups: 7,
33 | }
34 | writer := io.MultiWriter(os.Stdout, fileRotate)
35 | logger.SetOutput(writer)
36 | if xcli.App().Debug {
37 | logger.SetLevel(logrus.DebugLevel)
38 | }
39 | return logger, nil
40 | },
41 | }
42 | if err := xdi.Provide(&obj); err != nil {
43 | panic(err)
44 | }
45 | }
46 |
47 | func Logrus() (logger *logrus.Logger) {
48 | if err := xdi.Populate("logrus", &logger); err != nil {
49 | panic(err)
50 | }
51 | return
52 | }
53 |
--------------------------------------------------------------------------------
/examples/api-skeleton/di/server.go:
--------------------------------------------------------------------------------
1 | package di
2 |
3 | import (
4 | "github.com/mix-go/xdi"
5 | "net/http"
6 | )
7 |
8 | func init() {
9 | obj := xdi.Object{
10 | Name: "server",
11 | New: func() (i interface{}, e error) {
12 | return &http.Server{}, nil
13 | },
14 | }
15 | if err := xdi.Provide(&obj); err != nil {
16 | panic(err)
17 | }
18 | }
19 |
20 | func Server() (s *http.Server) {
21 | if err := xdi.Populate("server", &s); err != nil {
22 | panic(err)
23 | }
24 | return
25 | }
26 |
--------------------------------------------------------------------------------
/examples/api-skeleton/di/session.go:
--------------------------------------------------------------------------------
1 | package di
2 |
3 | import (
4 | "github.com/go-session/redis"
5 | "github.com/go-session/session"
6 | "github.com/mix-go/xdi"
7 | "github.com/mix-go/xutil/xenv"
8 | "time"
9 | )
10 |
11 | func init() {
12 | obj := xdi.Object{
13 | Name: "session",
14 | New: func() (i interface{}, e error) {
15 | opts := redis.Options{
16 | Addr: xenv.Getenv("REDIS_ADDR").String(),
17 | Password: xenv.Getenv("REDIS_PASSWORD").String(),
18 | DB: int(xenv.Getenv("REDIS_DATABASE").Int64()),
19 | DialTimeout: time.Duration(xenv.Getenv("REDIS_DIAL_TIMEOUT").Int64(10)) * time.Second,
20 | }
21 | opt := redis.NewRedisStore(&opts)
22 | return session.NewManager(session.SetStore(opt)), nil
23 | },
24 | }
25 | if err := xdi.Provide(&obj); err != nil {
26 | panic(err)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/examples/api-skeleton/di/xorm.go:
--------------------------------------------------------------------------------
1 | package di
2 |
3 | import (
4 | _ "github.com/go-sql-driver/mysql"
5 | "github.com/mix-go/xdi"
6 | "github.com/mix-go/xutil/xenv"
7 | "xorm.io/xorm"
8 | )
9 |
10 | func init() {
11 | obj := xdi.Object{
12 | Name: "xorm",
13 | New: func() (i interface{}, e error) {
14 | return xorm.NewEngine("mysql", xenv.Getenv("DATABASE_DSN").String())
15 | },
16 | }
17 | if err := xdi.Provide(&obj); err != nil {
18 | panic(err)
19 | }
20 | }
21 |
22 | func Xorm() (db *xorm.Engine) {
23 | if err := xdi.Populate("xorm", &db); err != nil {
24 | panic(err)
25 | }
26 | return
27 | }
28 |
--------------------------------------------------------------------------------
/examples/api-skeleton/di/xsql.go:
--------------------------------------------------------------------------------
1 | package di
2 |
3 | import (
4 | "database/sql"
5 | _ "github.com/go-sql-driver/mysql"
6 | "github.com/mix-go/xdi"
7 | "github.com/mix-go/xsql"
8 | "github.com/mix-go/xutil/xenv"
9 | )
10 |
11 | func init() {
12 | obj := xdi.Object{
13 | Name: "xsql",
14 | New: func() (i interface{}, e error) {
15 | db, err := sql.Open("mysql", xenv.Getenv("DATABASE_DSN").String())
16 | if err != nil {
17 | return nil, err
18 | }
19 | return xsql.New(db), nil
20 | },
21 | }
22 | if err := xdi.Provide(&obj); err != nil {
23 | panic(err)
24 | }
25 | }
26 |
27 | func Xsql() (db *xsql.DB) {
28 | if err := xdi.Populate("xsql", &db); err != nil {
29 | panic(err)
30 | }
31 | return
32 | }
33 |
--------------------------------------------------------------------------------
/examples/api-skeleton/di/zap.go:
--------------------------------------------------------------------------------
1 | package di
2 |
3 | import (
4 | "fmt"
5 | "github.com/mix-go/xcli"
6 | "github.com/mix-go/xdi"
7 | "go.uber.org/zap"
8 | "go.uber.org/zap/zapcore"
9 | "gopkg.in/natefinch/lumberjack.v2"
10 | "os"
11 | "time"
12 | )
13 |
14 | func init() {
15 | obj := xdi.Object{
16 | Name: "zap",
17 | New: func() (i interface{}, e error) {
18 | filename := fmt.Sprintf("%s/../runtime/logs/mix.log", xcli.App().BasePath)
19 | fileRotate := &lumberjack.Logger{
20 | Filename: filename,
21 | MaxBackups: 7,
22 | }
23 | atomicLevel := zap.NewAtomicLevelAt(zap.InfoLevel)
24 | core := zapcore.NewCore(
25 | zapcore.NewConsoleEncoder(zapcore.EncoderConfig{
26 | TimeKey: "T",
27 | LevelKey: "L",
28 | NameKey: "N",
29 | CallerKey: "C",
30 | MessageKey: "M",
31 | StacktraceKey: "S",
32 | LineEnding: zapcore.DefaultLineEnding,
33 | EncodeLevel: zapcore.CapitalLevelEncoder,
34 | EncodeTime: func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
35 | enc.AppendString(t.Format("2006-01-02 15:04:05.000"))
36 | },
37 | EncodeDuration: zapcore.StringDurationEncoder,
38 | EncodeCaller: zapcore.ShortCallerEncoder,
39 | }),
40 | zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(fileRotate)),
41 | atomicLevel,
42 | )
43 | logger := zap.New(core, zap.AddCaller())
44 | if xcli.App().Debug {
45 | atomicLevel.SetLevel(zap.DebugLevel)
46 | }
47 | return logger.Sugar(), nil
48 | },
49 | }
50 | if err := xdi.Provide(&obj); err != nil {
51 | panic(err)
52 | }
53 | }
54 |
55 | func Zap() (logger *zap.SugaredLogger) {
56 | if err := xdi.Populate("zap", &logger); err != nil {
57 | panic(err)
58 | }
59 | return
60 | }
61 |
62 | type ZapOutput struct {
63 | Logger *zap.SugaredLogger
64 | }
65 |
66 | func (t *ZapOutput) Write(p []byte) (n int, err error) {
67 | t.Logger.Info(string(p))
68 | return len(p), nil
69 | }
70 |
--------------------------------------------------------------------------------
/examples/api-skeleton/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/mix-go/api-skeleton
2 |
3 | go 1.20
4 |
5 | require (
6 | github.com/gin-gonic/gin v1.9.1
7 | github.com/go-session/redis v3.0.1+incompatible
8 | github.com/go-session/session v3.1.2+incompatible
9 | github.com/go-sql-driver/mysql v1.7.1
10 | github.com/golang-jwt/jwt/v4 v4.5.0
11 | github.com/jinzhu/configor v1.2.1
12 | github.com/mix-go/xcli v1.1.21
13 | github.com/mix-go/xdi v1.1.17
14 | github.com/mix-go/xsql v1.1.11
15 | github.com/mix-go/xutil v1.1.6
16 | github.com/redis/go-redis/v9 v9.0.4
17 | github.com/sirupsen/logrus v1.9.0
18 | github.com/spf13/viper v1.15.0
19 | go.uber.org/zap v1.24.0
20 | gopkg.in/natefinch/lumberjack.v2 v2.2.1
21 | gorm.io/driver/mysql v1.5.0
22 | gorm.io/gorm v1.25.0
23 | xorm.io/xorm v1.3.2
24 | )
25 |
26 | require (
27 | github.com/BurntSushi/toml v1.2.1 // indirect
28 | github.com/bytedance/sonic v1.9.1 // indirect
29 | github.com/cespare/xxhash/v2 v2.2.0 // indirect
30 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
31 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
32 | github.com/fsnotify/fsnotify v1.6.0 // indirect
33 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect
34 | github.com/gin-contrib/sse v0.1.0 // indirect
35 | github.com/go-playground/locales v0.14.1 // indirect
36 | github.com/go-playground/universal-translator v0.18.1 // indirect
37 | github.com/go-playground/validator/v10 v10.14.0 // indirect
38 | github.com/go-redis/redis v6.15.9+incompatible // indirect
39 | github.com/goccy/go-json v0.10.2 // indirect
40 | github.com/golang/snappy v0.0.4 // indirect
41 | github.com/hashicorp/hcl v1.0.0 // indirect
42 | github.com/jinzhu/inflection v1.0.0 // indirect
43 | github.com/jinzhu/now v1.1.5 // indirect
44 | github.com/joho/godotenv v1.5.1 // indirect
45 | github.com/json-iterator/go v1.1.12 // indirect
46 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect
47 | github.com/leodido/go-urn v1.2.4 // indirect
48 | github.com/magiconair/properties v1.8.7 // indirect
49 | github.com/mattn/go-isatty v0.0.19 // indirect
50 | github.com/mitchellh/mapstructure v1.5.0 // indirect
51 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
52 | github.com/modern-go/reflect2 v1.0.2 // indirect
53 | github.com/onsi/ginkgo v1.16.5 // indirect
54 | github.com/onsi/gomega v1.18.1 // indirect
55 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect
56 | github.com/sijms/go-ora/v2 v2.7.3 // indirect
57 | github.com/spf13/afero v1.9.5 // indirect
58 | github.com/spf13/cast v1.5.0 // indirect
59 | github.com/spf13/jwalterweatherman v1.1.0 // indirect
60 | github.com/spf13/pflag v1.0.5 // indirect
61 | github.com/subosito/gotenv v1.4.2 // indirect
62 | github.com/syndtr/goleveldb v1.0.0 // indirect
63 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
64 | github.com/ugorji/go/codec v1.2.11 // indirect
65 | go.uber.org/atomic v1.11.0 // indirect
66 | go.uber.org/multierr v1.11.0 // indirect
67 | golang.org/x/arch v0.3.0 // indirect
68 | golang.org/x/crypto v0.21.0 // indirect
69 | golang.org/x/net v0.23.0 // indirect
70 | golang.org/x/sys v0.18.0 // indirect
71 | golang.org/x/text v0.14.0 // indirect
72 | google.golang.org/protobuf v1.33.0 // indirect
73 | gopkg.in/ini.v1 v1.67.0 // indirect
74 | gopkg.in/yaml.v2 v2.4.0 // indirect
75 | gopkg.in/yaml.v3 v3.0.1 // indirect
76 | xorm.io/builder v0.3.12 // indirect
77 | )
78 |
--------------------------------------------------------------------------------
/examples/api-skeleton/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/mix-go/api-skeleton/commands"
5 | _ "github.com/mix-go/api-skeleton/config/configor"
6 | _ "github.com/mix-go/api-skeleton/config/dotenv"
7 | _ "github.com/mix-go/api-skeleton/di"
8 | "github.com/mix-go/xcli"
9 | "github.com/mix-go/xutil/xenv"
10 | )
11 |
12 | func main() {
13 | xcli.SetName("app").
14 | SetVersion("0.0.0-alpha").
15 | SetDebug(xenv.Getenv("APP_DEBUG").Bool(false))
16 | xcli.AddCommand(commands.Commands...).Run()
17 | }
18 |
--------------------------------------------------------------------------------
/examples/api-skeleton/middleware/auth.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "fmt"
5 | "github.com/gin-gonic/gin"
6 | "github.com/golang-jwt/jwt/v4"
7 | "github.com/mix-go/xutil/xenv"
8 | "net/http"
9 | "strings"
10 | )
11 |
12 | func AuthMiddleware() gin.HandlerFunc {
13 | return func(c *gin.Context) {
14 | // 获取 token
15 | tokenString := c.GetHeader("Authorization")
16 | if strings.Index(tokenString, "Bearer ") != 0 {
17 | c.JSON(http.StatusInternalServerError, gin.H{
18 | "status": http.StatusInternalServerError,
19 | "message": "failed to extract token",
20 | })
21 | c.Abort()
22 | return
23 | }
24 |
25 | // 解码
26 | token, err := jwt.Parse(tokenString[7:], func(token *jwt.Token) (interface{}, error) {
27 | // Don't forget to validate the alg is what you expect:
28 | if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
29 | return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
30 | }
31 |
32 | // hmacSampleSecret is a []byte containing your secret, e.g. []byte("my_secret_key")
33 | return []byte(xenv.Getenv("HMAC_SECRET").String()), nil
34 | })
35 | if err != nil {
36 | c.JSON(http.StatusInternalServerError, gin.H{
37 | "status": http.StatusInternalServerError,
38 | "message": err.Error(),
39 | })
40 | c.Abort()
41 | return
42 | }
43 |
44 | // 保存信息
45 | if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
46 | c.Set("payload", claims)
47 | }
48 |
49 | c.Next()
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/examples/api-skeleton/middleware/cors.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "net/http"
6 | )
7 |
8 | func CorsMiddleware() gin.HandlerFunc {
9 | return func(c *gin.Context) {
10 | c.Header("Access-Control-Allow-Origin", "*")
11 | c.Header("Access-Control-Allow-Headers", "Origin, Accept, Keep-Alive, User-Agent, Cache-Control, Content-Type, X-Requested-With, Authorization")
12 | c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS")
13 | if c.Request.Method == "OPTIONS" {
14 | c.String(http.StatusOK, "")
15 | c.Abort()
16 | return
17 | }
18 |
19 | c.Next()
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/examples/api-skeleton/routes/main.go:
--------------------------------------------------------------------------------
1 | package routes
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "github.com/mix-go/api-skeleton/controllers"
6 | "github.com/mix-go/api-skeleton/middleware"
7 | )
8 |
9 | func Load(router *gin.Engine) {
10 | router.Use(gin.Recovery()) // error handle
11 |
12 | router.GET("hello",
13 | middleware.CorsMiddleware(),
14 | func(ctx *gin.Context) {
15 | hello := controllers.HelloController{}
16 | hello.Index(ctx)
17 | },
18 | )
19 |
20 | router.POST("users/add",
21 | middleware.AuthMiddleware(),
22 | func(ctx *gin.Context) {
23 | hello := controllers.UserController{}
24 | hello.Add(ctx)
25 | },
26 | )
27 |
28 | router.POST("auth", func(ctx *gin.Context) {
29 | auth := controllers.AuthController{}
30 | auth.Index(ctx)
31 | })
32 | }
33 |
--------------------------------------------------------------------------------
/examples/api-skeleton/runtime/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
--------------------------------------------------------------------------------
/examples/api-skeleton/shell/server.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | echo "============`date +%F' '%T`==========="
3 |
4 | file=/project/bin/program
5 | cmd=api
6 |
7 | getpid()
8 | {
9 | docmd=`ps aux | grep ${file} | grep ${cmd} | grep -v 'grep' | grep -v '\.sh' | awk '{print $2}' | xargs`
10 | echo $docmd
11 | }
12 |
13 | start()
14 | {
15 | pidstr=`getpid`
16 | if [ -n "$pidstr" ];then
17 | echo "running with pids $pidstr"
18 | else
19 | $file $cmd > /dev/null 2>&1 &
20 | sleep 1
21 | pidstr=`getpid`
22 | echo "start with pids $pidstr"
23 | fi
24 | }
25 |
26 | stop()
27 | {
28 | pidstr=`getpid`
29 | if [ ! -n "$pidstr" ];then
30 | echo "not executed!"
31 | return
32 | fi
33 | echo "kill $pidstr"
34 | kill $pidstr
35 | }
36 |
37 | restart()
38 | {
39 | stop
40 | sleep 1
41 | start
42 | }
43 |
44 | case "$1" in
45 | start)
46 | start
47 | ;;
48 | stop)
49 | stop
50 | ;;
51 | restart)
52 | restart
53 | ;;
54 | esac
55 |
--------------------------------------------------------------------------------
/examples/cli-skeleton/.env:
--------------------------------------------------------------------------------
1 | # APP
2 | APP_DEBUG=true
3 |
4 | # DATABASE
5 | DATABASE_DSN=root:123456@tcp(127.0.0.1:3306)/test?charset=utf8&loc=Asia%2FShanghai&parseTime=true&timeout=10s
6 |
7 | # REDIS
8 | REDIS_ADDR=127.0.0.1:6379
9 | REDIS_DATABASE=0
10 | REDIS_PASSWORD=
11 | REDIS_DIAL_TIMEOUT=10
12 |
--------------------------------------------------------------------------------
/examples/cli-skeleton/.gitignore:
--------------------------------------------------------------------------------
1 | # goland project files
2 | .idea
3 |
4 | # netbeans project files
5 | nbproject
6 |
7 | # zend studio for eclipse project files
8 | .buildpath
9 | .project
10 | .settings
11 |
12 | # windows thumbnail cache
13 | Thumbs.db
14 |
15 | # Mac DS_Store Files
16 | .DS_Store
17 |
--------------------------------------------------------------------------------
/examples/cli-skeleton/bin/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
--------------------------------------------------------------------------------
/examples/cli-skeleton/commands/hello.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "fmt"
5 | "github.com/mix-go/xcli/flag"
6 | )
7 |
8 | type HelloCommand struct {
9 | }
10 |
11 | func (t *HelloCommand) Main() {
12 | name := flag.Match("n", "name").String("OpenMix")
13 | say := flag.Match("say").String("Hello, World!")
14 | fmt.Printf("%s: %s\n", name, say)
15 | }
16 |
--------------------------------------------------------------------------------
/examples/cli-skeleton/commands/main.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "github.com/mix-go/xcli"
5 | )
6 |
7 | var Commands = []*xcli.Command{
8 | {
9 | Name: "hello",
10 | Short: "\tEcho demo",
11 | Options: []*xcli.Option{
12 | {
13 | Names: []string{"n", "name"},
14 | Usage: "Your name",
15 | },
16 | {
17 | Names: []string{"say"},
18 | Usage: "\tSay ...",
19 | },
20 | },
21 | RunI: &HelloCommand{},
22 | },
23 | }
24 |
--------------------------------------------------------------------------------
/examples/cli-skeleton/conf/config.yml:
--------------------------------------------------------------------------------
1 | foo: bar
2 |
--------------------------------------------------------------------------------
/examples/cli-skeleton/config/configor/main.go:
--------------------------------------------------------------------------------
1 | package configor
2 |
3 | import (
4 | "fmt"
5 | "github.com/jinzhu/configor"
6 | "github.com/mix-go/cli-skeleton/config"
7 | "github.com/mix-go/xcli/argv"
8 | )
9 |
10 | func init() {
11 | // Conf support YAML, JSON, TOML, Shell Environment
12 | if err := configor.Load(&config.Config, fmt.Sprintf("%s/../conf/config.yml", argv.Program().Dir)); err != nil {
13 | panic(err)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/examples/cli-skeleton/config/dotenv/main.go:
--------------------------------------------------------------------------------
1 | package dotenv
2 |
3 | import (
4 | "fmt"
5 | "github.com/mix-go/xcli/argv"
6 | "github.com/mix-go/xutil/xenv"
7 | )
8 |
9 | func init() {
10 | // Env
11 | if err := xenv.Load(fmt.Sprintf("%s/../.env", argv.Program().Dir)); err != nil {
12 | panic(err)
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/examples/cli-skeleton/config/main.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | var Config = struct {
4 | Foo string
5 | }{}
6 |
--------------------------------------------------------------------------------
/examples/cli-skeleton/config/viper/main.go:
--------------------------------------------------------------------------------
1 | package viper
2 |
3 | import (
4 | "fmt"
5 | "github.com/mix-go/cli-skeleton/config"
6 | "github.com/mix-go/xcli/argv"
7 | "github.com/spf13/viper"
8 | )
9 |
10 | func init() {
11 | // Conf support JSON, TOML, YAML, HCL, INI, envfile
12 | viper.SetConfigFile(fmt.Sprintf("%s/../conf/config.yml", argv.Program().Dir))
13 | if err := viper.ReadInConfig(); err != nil {
14 | panic(err)
15 | }
16 | if err := viper.Unmarshal(&config.Config); err != nil {
17 | panic(err)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/examples/cli-skeleton/di/goredis.go:
--------------------------------------------------------------------------------
1 | package di
2 |
3 | import (
4 | "github.com/mix-go/xdi"
5 | "github.com/mix-go/xutil/xenv"
6 | "github.com/redis/go-redis/v9"
7 | "time"
8 | )
9 |
10 | func init() {
11 | obj := xdi.Object{
12 | Name: "goredis",
13 | New: func() (i interface{}, e error) {
14 | opt := redis.Options{
15 | Addr: xenv.Getenv("REDIS_ADDR").String(),
16 | Password: xenv.Getenv("REDIS_PASSWORD").String(),
17 | DB: int(xenv.Getenv("REDIS_DATABASE").Int64()),
18 | DialTimeout: time.Duration(xenv.Getenv("REDIS_DIAL_TIMEOUT").Int64(10)) * time.Second,
19 | }
20 | return redis.NewClient(&opt), nil
21 | },
22 | }
23 | if err := xdi.Provide(&obj); err != nil {
24 | panic(err)
25 | }
26 | }
27 |
28 | func GoRedis() (client *redis.Client) {
29 | if err := xdi.Populate("goredis", &client); err != nil {
30 | panic(err)
31 | }
32 | return
33 | }
34 |
--------------------------------------------------------------------------------
/examples/cli-skeleton/di/gorm.go:
--------------------------------------------------------------------------------
1 | package di
2 |
3 | import (
4 | "github.com/mix-go/xdi"
5 | "github.com/mix-go/xutil/xenv"
6 | "gorm.io/driver/mysql"
7 | "gorm.io/gorm"
8 | )
9 |
10 | func init() {
11 | obj := xdi.Object{
12 | Name: "gorm",
13 | New: func() (i interface{}, e error) {
14 | return gorm.Open(mysql.Open(xenv.Getenv("DATABASE_DSN").String()))
15 | },
16 | }
17 | if err := xdi.Provide(&obj); err != nil {
18 | panic(err)
19 | }
20 | }
21 |
22 | func Gorm() (db *gorm.DB) {
23 | if err := xdi.Populate("gorm", &db); err != nil {
24 | panic(err)
25 | }
26 | return
27 | }
28 |
--------------------------------------------------------------------------------
/examples/cli-skeleton/di/logrus.go:
--------------------------------------------------------------------------------
1 | package di
2 |
3 | import (
4 | "fmt"
5 | "github.com/mix-go/xcli"
6 | "github.com/mix-go/xdi"
7 | "github.com/sirupsen/logrus"
8 | "gopkg.in/natefinch/lumberjack.v2"
9 | "io"
10 | "os"
11 | "path/filepath"
12 | "runtime"
13 | )
14 |
15 | func init() {
16 | obj := xdi.Object{
17 | Name: "logrus",
18 | New: func() (i interface{}, e error) {
19 | logger := logrus.New()
20 | logger.ReportCaller = true // 显示调用信息
21 | formatter := new(logrus.TextFormatter)
22 | formatter.FullTimestamp = true
23 | formatter.TimestampFormat = "2006-01-02 15:04:05.000"
24 | formatter.DisableQuote = true // 不转义换行符,为了保存错误堆栈到日志文件
25 | formatter.CallerPrettyfier = func(frame *runtime.Frame) (function string, file string) {
26 | return "", fmt.Sprintf("%s:%d", filepath.Base(frame.File), frame.Line)
27 | }
28 | logger.Formatter = formatter
29 | filename := fmt.Sprintf("%s/../logs/mix.log", xcli.App().BasePath)
30 | fileRotate := &lumberjack.Logger{
31 | Filename: filename,
32 | MaxBackups: 7,
33 | }
34 | writer := io.MultiWriter(os.Stdout, fileRotate)
35 | logger.SetOutput(writer)
36 | if xcli.App().Debug {
37 | logger.SetLevel(logrus.DebugLevel)
38 | }
39 | return logger, nil
40 | },
41 | }
42 | if err := xdi.Provide(&obj); err != nil {
43 | panic(err)
44 | }
45 | }
46 |
47 | func Logrus() (logger *logrus.Logger) {
48 | if err := xdi.Populate("logrus", &logger); err != nil {
49 | panic(err)
50 | }
51 | return
52 | }
53 |
--------------------------------------------------------------------------------
/examples/cli-skeleton/di/xorm.go:
--------------------------------------------------------------------------------
1 | package di
2 |
3 | import (
4 | _ "github.com/go-sql-driver/mysql"
5 | "github.com/mix-go/xdi"
6 | "github.com/mix-go/xutil/xenv"
7 | "xorm.io/xorm"
8 | )
9 |
10 | func init() {
11 | obj := xdi.Object{
12 | Name: "xorm",
13 | New: func() (i interface{}, e error) {
14 | return xorm.NewEngine("mysql", xenv.Getenv("DATABASE_DSN").String())
15 | },
16 | }
17 | if err := xdi.Provide(&obj); err != nil {
18 | panic(err)
19 | }
20 | }
21 |
22 | func Xorm() (db *xorm.Engine) {
23 | if err := xdi.Populate("xorm", &db); err != nil {
24 | panic(err)
25 | }
26 | return
27 | }
28 |
--------------------------------------------------------------------------------
/examples/cli-skeleton/di/xsql.go:
--------------------------------------------------------------------------------
1 | package di
2 |
3 | import (
4 | "database/sql"
5 | _ "github.com/go-sql-driver/mysql"
6 | "github.com/mix-go/xdi"
7 | "github.com/mix-go/xsql"
8 | "github.com/mix-go/xutil/xenv"
9 | )
10 |
11 | func init() {
12 | obj := xdi.Object{
13 | Name: "xsql",
14 | New: func() (i interface{}, e error) {
15 | db, err := sql.Open("mysql", xenv.Getenv("DATABASE_DSN").String())
16 | if err != nil {
17 | return nil, err
18 | }
19 | return xsql.New(db), nil
20 | },
21 | }
22 | if err := xdi.Provide(&obj); err != nil {
23 | panic(err)
24 | }
25 | }
26 |
27 | func Xsql() (db *xsql.DB) {
28 | if err := xdi.Populate("xsql", &db); err != nil {
29 | panic(err)
30 | }
31 | return
32 | }
33 |
--------------------------------------------------------------------------------
/examples/cli-skeleton/di/zap.go:
--------------------------------------------------------------------------------
1 | package di
2 |
3 | import (
4 | "fmt"
5 | "github.com/mix-go/xcli"
6 | "github.com/mix-go/xdi"
7 | "go.uber.org/zap"
8 | "go.uber.org/zap/zapcore"
9 | "gopkg.in/natefinch/lumberjack.v2"
10 | "os"
11 | "time"
12 | )
13 |
14 | func init() {
15 | obj := xdi.Object{
16 | Name: "zap",
17 | New: func() (i interface{}, e error) {
18 | filename := fmt.Sprintf("%s/../logs/mix.log", xcli.App().BasePath)
19 | fileRotate := &lumberjack.Logger{
20 | Filename: filename,
21 | MaxBackups: 7,
22 | }
23 | atomicLevel := zap.NewAtomicLevelAt(zap.InfoLevel)
24 | core := zapcore.NewCore(
25 | zapcore.NewConsoleEncoder(zapcore.EncoderConfig{
26 | TimeKey: "T",
27 | LevelKey: "L",
28 | NameKey: "N",
29 | CallerKey: "C",
30 | MessageKey: "M",
31 | StacktraceKey: "S",
32 | LineEnding: zapcore.DefaultLineEnding,
33 | EncodeLevel: zapcore.CapitalLevelEncoder,
34 | EncodeTime: func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
35 | enc.AppendString(t.Format("2006-01-02 15:04:05.000"))
36 | },
37 | EncodeDuration: zapcore.StringDurationEncoder,
38 | EncodeCaller: zapcore.ShortCallerEncoder,
39 | }),
40 | zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(fileRotate)),
41 | atomicLevel,
42 | )
43 | logger := zap.New(core, zap.AddCaller())
44 | if xcli.App().Debug {
45 | atomicLevel.SetLevel(zap.DebugLevel)
46 | }
47 | return logger.Sugar(), nil
48 | },
49 | }
50 | if err := xdi.Provide(&obj); err != nil {
51 | panic(err)
52 | }
53 | }
54 |
55 | func Zap() (logger *zap.SugaredLogger) {
56 | if err := xdi.Populate("zap", &logger); err != nil {
57 | panic(err)
58 | }
59 | return
60 | }
61 |
62 | type ZapOutput struct {
63 | Logger *zap.SugaredLogger
64 | }
65 |
66 | func (t *ZapOutput) Write(p []byte) (n int, err error) {
67 | t.Logger.Info(string(p))
68 | return len(p), nil
69 | }
70 |
--------------------------------------------------------------------------------
/examples/cli-skeleton/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/mix-go/cli-skeleton
2 |
3 | go 1.20
4 |
5 | require (
6 | github.com/go-sql-driver/mysql v1.7.1
7 | github.com/jinzhu/configor v1.2.1
8 | github.com/mix-go/xcli v1.1.21
9 | github.com/mix-go/xdi v1.1.17
10 | github.com/mix-go/xsql v1.1.11
11 | github.com/mix-go/xutil v1.1.6
12 | github.com/redis/go-redis/v9 v9.0.4
13 | github.com/sirupsen/logrus v1.9.0
14 | github.com/spf13/viper v1.15.0
15 | go.uber.org/zap v1.24.0
16 | gopkg.in/natefinch/lumberjack.v2 v2.2.1
17 | gorm.io/driver/mysql v1.5.0
18 | gorm.io/gorm v1.25.0
19 | xorm.io/xorm v1.3.2
20 | )
21 |
22 | require (
23 | github.com/BurntSushi/toml v1.2.1 // indirect
24 | github.com/cespare/xxhash/v2 v2.2.0 // indirect
25 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
26 | github.com/fsnotify/fsnotify v1.6.0 // indirect
27 | github.com/goccy/go-json v0.10.2 // indirect
28 | github.com/golang/snappy v0.0.4 // indirect
29 | github.com/hashicorp/hcl v1.0.0 // indirect
30 | github.com/jinzhu/inflection v1.0.0 // indirect
31 | github.com/jinzhu/now v1.1.5 // indirect
32 | github.com/joho/godotenv v1.5.1 // indirect
33 | github.com/json-iterator/go v1.1.12 // indirect
34 | github.com/magiconair/properties v1.8.7 // indirect
35 | github.com/mitchellh/mapstructure v1.5.0 // indirect
36 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
37 | github.com/modern-go/reflect2 v1.0.2 // indirect
38 | github.com/pelletier/go-toml/v2 v2.0.7 // indirect
39 | github.com/sijms/go-ora/v2 v2.7.3 // indirect
40 | github.com/spf13/afero v1.9.5 // indirect
41 | github.com/spf13/cast v1.5.0 // indirect
42 | github.com/spf13/jwalterweatherman v1.1.0 // indirect
43 | github.com/spf13/pflag v1.0.5 // indirect
44 | github.com/subosito/gotenv v1.4.2 // indirect
45 | github.com/syndtr/goleveldb v1.0.0 // indirect
46 | go.uber.org/atomic v1.11.0 // indirect
47 | go.uber.org/multierr v1.11.0 // indirect
48 | golang.org/x/sys v0.8.0 // indirect
49 | golang.org/x/text v0.9.0 // indirect
50 | gopkg.in/ini.v1 v1.67.0 // indirect
51 | gopkg.in/yaml.v2 v2.4.0 // indirect
52 | gopkg.in/yaml.v3 v3.0.1 // indirect
53 | xorm.io/builder v0.3.12 // indirect
54 | )
55 |
--------------------------------------------------------------------------------
/examples/cli-skeleton/logs/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
--------------------------------------------------------------------------------
/examples/cli-skeleton/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/mix-go/cli-skeleton/commands"
5 | _ "github.com/mix-go/cli-skeleton/config/configor"
6 | _ "github.com/mix-go/cli-skeleton/config/dotenv"
7 | _ "github.com/mix-go/cli-skeleton/di"
8 | "github.com/mix-go/xcli"
9 | "github.com/mix-go/xutil/xenv"
10 | )
11 |
12 | func main() {
13 | xcli.SetName("app").
14 | SetVersion("0.0.0-alpha").
15 | SetDebug(xenv.Getenv("APP_DEBUG").Bool(false))
16 | xcli.AddCommand(commands.Commands...).Run()
17 | }
18 |
--------------------------------------------------------------------------------
/examples/grpc-skeleton/.env:
--------------------------------------------------------------------------------
1 | # APP
2 | APP_DEBUG=true
3 |
4 | # DATABASE
5 | DATABASE_DSN=root:123456@tcp(127.0.0.1:3306)/test?charset=utf8&loc=Asia%2FShanghai&parseTime=true&timeout=10s
6 |
7 | # REDIS
8 | REDIS_ADDR=127.0.0.1:6379
9 | REDIS_DATABASE=0
10 | REDIS_PASSWORD=
11 | REDIS_DIAL_TIMEOUT=10
12 |
13 | # gRPC
14 | RPC_ADDR=:8080
15 |
--------------------------------------------------------------------------------
/examples/grpc-skeleton/.gitignore:
--------------------------------------------------------------------------------
1 | # goland project files
2 | .idea
3 |
4 | # netbeans project files
5 | nbproject
6 |
7 | # zend studio for eclipse project files
8 | .buildpath
9 | .project
10 | .settings
11 |
12 | # windows thumbnail cache
13 | Thumbs.db
14 |
15 | # Mac DS_Store Files
16 | .DS_Store
17 |
--------------------------------------------------------------------------------
/examples/grpc-skeleton/bin/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
--------------------------------------------------------------------------------
/examples/grpc-skeleton/commands/client.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | pb "github.com/mix-go/grpc-skeleton/protos"
7 | "github.com/mix-go/xutil/xenv"
8 | "google.golang.org/grpc"
9 | "time"
10 | )
11 |
12 | type GrpcClientCommand struct {
13 | }
14 |
15 | func (t *GrpcClientCommand) Main() {
16 | addr := xenv.Getenv("GIN_ADDR").String(":8080")
17 | ctx, _ := context.WithTimeout(context.Background(), time.Duration(5)*time.Second)
18 | conn, err := grpc.DialContext(ctx, addr, grpc.WithInsecure(), grpc.WithBlock())
19 | if err != nil {
20 | panic(err)
21 | }
22 | defer func() {
23 | _ = conn.Close()
24 | }()
25 | cli := pb.NewUserClient(conn)
26 | req := pb.AddRequest{
27 | Name: "xiaoliu",
28 | }
29 | resp, err := cli.Add(ctx, &req)
30 | if err != nil {
31 | panic(err)
32 | }
33 | fmt.Println(fmt.Sprintf("Add User: %d", resp.UserId))
34 | }
35 |
--------------------------------------------------------------------------------
/examples/grpc-skeleton/commands/main.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "github.com/mix-go/xcli"
5 | )
6 |
7 | var Commands = []*xcli.Command{
8 | {
9 | Name: "grpc:server",
10 | Short: "gRPC server demo",
11 | Options: []*xcli.Option{
12 | {
13 | Names: []string{"d", "daemon"},
14 | Usage: "Run in the background",
15 | },
16 | },
17 | RunI: &GrpcServerCommand{},
18 | },
19 | {
20 | Name: "grpc:client",
21 | Short: "gRPC client demo",
22 | RunI: &GrpcClientCommand{},
23 | },
24 | }
25 |
--------------------------------------------------------------------------------
/examples/grpc-skeleton/commands/server.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "github.com/mix-go/grpc-skeleton/di"
5 | pb "github.com/mix-go/grpc-skeleton/protos"
6 | "github.com/mix-go/grpc-skeleton/services"
7 | "github.com/mix-go/xcli/flag"
8 | "github.com/mix-go/xcli/process"
9 | "github.com/mix-go/xutil/xenv"
10 | "google.golang.org/grpc"
11 | "net"
12 | "os"
13 | "os/signal"
14 | "strings"
15 | "syscall"
16 | )
17 |
18 | var netListener net.Listener
19 |
20 | type GrpcServerCommand struct {
21 | }
22 |
23 | func (t *GrpcServerCommand) Main() {
24 | if flag.Match("d", "daemon").Bool() {
25 | process.Daemon()
26 | }
27 |
28 | addr := xenv.Getenv("RPC_ADDR").String(":8080")
29 | logger := di.Logrus()
30 |
31 | // listen
32 | listener, err := net.Listen("tcp", addr)
33 | if err != nil {
34 | panic(err)
35 | }
36 | netListener = listener
37 |
38 | // signal
39 | ch := make(chan os.Signal)
40 | signal.Notify(ch, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
41 | go func() {
42 | <-ch
43 | logger.Info("Server shutdown")
44 | if err := listener.Close(); err != nil {
45 | panic(err)
46 | }
47 | }()
48 |
49 | // server
50 | s := grpc.NewServer()
51 | pb.RegisterUserServer(s, &services.UserService{})
52 |
53 | // run
54 | welcome()
55 | logger.Infof("Server run %s", addr)
56 | if err := s.Serve(listener); err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
57 | panic(err)
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/examples/grpc-skeleton/commands/welcome.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "fmt"
5 | "runtime"
6 | "strings"
7 | )
8 |
9 | const logo = ` ___
10 | ______ ___ _ /__ ___ _____ ______
11 | / __ *__ \/ /\ \/ /__ __ */ __ \
12 | / / / / / / / /\ \/ _ /_/ // /_/ /
13 | /_/ /_/ /_/_/ /_/\_\ \__, / \____/
14 | /____/
15 | `
16 |
17 | func welcome() {
18 | fmt.Println(strings.Replace(logo, "*", "`", -1))
19 | fmt.Println("")
20 | fmt.Println(fmt.Sprintf("Server Name: %s", "mix-grpc"))
21 | fmt.Println(fmt.Sprintf("System Name: %s", runtime.GOOS))
22 | fmt.Println(fmt.Sprintf("Go Version: %s", runtime.Version()[2:]))
23 | fmt.Println(fmt.Sprintf("Listen Addr: %s", netListener.Addr()))
24 | }
25 |
--------------------------------------------------------------------------------
/examples/grpc-skeleton/conf/config.yml:
--------------------------------------------------------------------------------
1 | foo: bar
2 |
--------------------------------------------------------------------------------
/examples/grpc-skeleton/config/configor/main.go:
--------------------------------------------------------------------------------
1 | package configor
2 |
3 | import (
4 | "fmt"
5 | "github.com/jinzhu/configor"
6 | "github.com/mix-go/grpc-skeleton/config"
7 | "github.com/mix-go/xcli/argv"
8 | )
9 |
10 | func init() {
11 | // Conf support YAML, JSON, TOML, Shell Environment
12 | if err := configor.Load(&config.Config, fmt.Sprintf("%s/../conf/config.yml", argv.Program().Dir)); err != nil {
13 | panic(err)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/examples/grpc-skeleton/config/dotenv/main.go:
--------------------------------------------------------------------------------
1 | package dotenv
2 |
3 | import (
4 | "fmt"
5 | "github.com/mix-go/xcli/argv"
6 | "github.com/mix-go/xutil/xenv"
7 | )
8 |
9 | func init() {
10 | // Env
11 | if err := xenv.Load(fmt.Sprintf("%s/../.env", argv.Program().Dir)); err != nil {
12 | panic(err)
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/examples/grpc-skeleton/config/main.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | var Config = struct {
4 | Foo string
5 | }{}
6 |
--------------------------------------------------------------------------------
/examples/grpc-skeleton/config/viper/main.go:
--------------------------------------------------------------------------------
1 | package viper
2 |
3 | import (
4 | "fmt"
5 | "github.com/mix-go/grpc-skeleton/config"
6 | "github.com/mix-go/xcli/argv"
7 | "github.com/spf13/viper"
8 | )
9 |
10 | func init() {
11 | // Conf support JSON, TOML, YAML, HCL, INI, envfile
12 | viper.SetConfigFile(fmt.Sprintf("%s/../conf/config.yml", argv.Program().Dir))
13 | if err := viper.ReadInConfig(); err != nil {
14 | panic(err)
15 | }
16 | if err := viper.Unmarshal(&config.Config); err != nil {
17 | panic(err)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/examples/grpc-skeleton/di/goredis.go:
--------------------------------------------------------------------------------
1 | package di
2 |
3 | import (
4 | "github.com/mix-go/xdi"
5 | "github.com/mix-go/xutil/xenv"
6 | "github.com/redis/go-redis/v9"
7 | "time"
8 | )
9 |
10 | func init() {
11 | obj := xdi.Object{
12 | Name: "goredis",
13 | New: func() (i interface{}, e error) {
14 | opt := redis.Options{
15 | Addr: xenv.Getenv("REDIS_ADDR").String(),
16 | Password: xenv.Getenv("REDIS_PASSWORD").String(),
17 | DB: int(xenv.Getenv("REDIS_DATABASE").Int64()),
18 | DialTimeout: time.Duration(xenv.Getenv("REDIS_DIAL_TIMEOUT").Int64(10)) * time.Second,
19 | }
20 | return redis.NewClient(&opt), nil
21 | },
22 | }
23 | if err := xdi.Provide(&obj); err != nil {
24 | panic(err)
25 | }
26 | }
27 |
28 | func GoRedis() (client *redis.Client) {
29 | if err := xdi.Populate("goredis", &client); err != nil {
30 | panic(err)
31 | }
32 | return
33 | }
34 |
--------------------------------------------------------------------------------
/examples/grpc-skeleton/di/gorm.go:
--------------------------------------------------------------------------------
1 | package di
2 |
3 | import (
4 | "github.com/mix-go/xdi"
5 | "github.com/mix-go/xutil/xenv"
6 | "gorm.io/driver/mysql"
7 | "gorm.io/gorm"
8 | )
9 |
10 | func init() {
11 | obj := xdi.Object{
12 | Name: "gorm",
13 | New: func() (i interface{}, e error) {
14 | return gorm.Open(mysql.Open(xenv.Getenv("DATABASE_DSN").String()))
15 | },
16 | }
17 | if err := xdi.Provide(&obj); err != nil {
18 | panic(err)
19 | }
20 | }
21 |
22 | func Gorm() (db *gorm.DB) {
23 | if err := xdi.Populate("gorm", &db); err != nil {
24 | panic(err)
25 | }
26 | return
27 | }
28 |
--------------------------------------------------------------------------------
/examples/grpc-skeleton/di/logrus.go:
--------------------------------------------------------------------------------
1 | package di
2 |
3 | import (
4 | "fmt"
5 | "github.com/mix-go/xcli"
6 | "github.com/mix-go/xdi"
7 | "github.com/sirupsen/logrus"
8 | "gopkg.in/natefinch/lumberjack.v2"
9 | "io"
10 | "os"
11 | "path/filepath"
12 | "runtime"
13 | )
14 |
15 | func init() {
16 | obj := xdi.Object{
17 | Name: "logrus",
18 | New: func() (i interface{}, e error) {
19 | logger := logrus.New()
20 | logger.ReportCaller = true // 显示调用信息
21 | formatter := new(logrus.TextFormatter)
22 | formatter.FullTimestamp = true
23 | formatter.TimestampFormat = "2006-01-02 15:04:05.000"
24 | formatter.DisableQuote = true // 不转义换行符,为了保存错误堆栈到日志文件
25 | formatter.CallerPrettyfier = func(frame *runtime.Frame) (function string, file string) {
26 | return "", fmt.Sprintf("%s:%d", filepath.Base(frame.File), frame.Line)
27 | }
28 | logger.Formatter = formatter
29 | filename := fmt.Sprintf("%s/../logs/mix.log", xcli.App().BasePath)
30 | fileRotate := &lumberjack.Logger{
31 | Filename: filename,
32 | MaxBackups: 7,
33 | }
34 | writer := io.MultiWriter(os.Stdout, fileRotate)
35 | logger.SetOutput(writer)
36 | if xcli.App().Debug {
37 | logger.SetLevel(logrus.DebugLevel)
38 | }
39 | return logger, nil
40 | },
41 | }
42 | if err := xdi.Provide(&obj); err != nil {
43 | panic(err)
44 | }
45 | }
46 |
47 | func Logrus() (logger *logrus.Logger) {
48 | if err := xdi.Populate("logrus", &logger); err != nil {
49 | panic(err)
50 | }
51 | return
52 | }
53 |
--------------------------------------------------------------------------------
/examples/grpc-skeleton/di/xorm.go:
--------------------------------------------------------------------------------
1 | package di
2 |
3 | import (
4 | _ "github.com/go-sql-driver/mysql"
5 | "github.com/mix-go/xdi"
6 | "github.com/mix-go/xutil/xenv"
7 | "xorm.io/xorm"
8 | )
9 |
10 | func init() {
11 | obj := xdi.Object{
12 | Name: "xorm",
13 | New: func() (i interface{}, e error) {
14 | return xorm.NewEngine("mysql", xenv.Getenv("DATABASE_DSN").String())
15 | },
16 | }
17 | if err := xdi.Provide(&obj); err != nil {
18 | panic(err)
19 | }
20 | }
21 |
22 | func Xorm() (db *xorm.Engine) {
23 | if err := xdi.Populate("xorm", &db); err != nil {
24 | panic(err)
25 | }
26 | return
27 | }
28 |
--------------------------------------------------------------------------------
/examples/grpc-skeleton/di/xsql.go:
--------------------------------------------------------------------------------
1 | package di
2 |
3 | import (
4 | "database/sql"
5 | _ "github.com/go-sql-driver/mysql"
6 | "github.com/mix-go/xdi"
7 | "github.com/mix-go/xsql"
8 | "github.com/mix-go/xutil/xenv"
9 | )
10 |
11 | func init() {
12 | obj := xdi.Object{
13 | Name: "xsql",
14 | New: func() (i interface{}, e error) {
15 | db, err := sql.Open("mysql", xenv.Getenv("DATABASE_DSN").String())
16 | if err != nil {
17 | return nil, err
18 | }
19 | return xsql.New(db), nil
20 | },
21 | }
22 | if err := xdi.Provide(&obj); err != nil {
23 | panic(err)
24 | }
25 | }
26 |
27 | func Xsql() (db *xsql.DB) {
28 | if err := xdi.Populate("xsql", &db); err != nil {
29 | panic(err)
30 | }
31 | return
32 | }
33 |
--------------------------------------------------------------------------------
/examples/grpc-skeleton/di/zap.go:
--------------------------------------------------------------------------------
1 | package di
2 |
3 | import (
4 | "fmt"
5 | "github.com/mix-go/xcli"
6 | "github.com/mix-go/xdi"
7 | "go.uber.org/zap"
8 | "go.uber.org/zap/zapcore"
9 | "gopkg.in/natefinch/lumberjack.v2"
10 | "os"
11 | "time"
12 | )
13 |
14 | func init() {
15 | obj := xdi.Object{
16 | Name: "zap",
17 | New: func() (i interface{}, e error) {
18 | filename := fmt.Sprintf("%s/../logs/mix.log", xcli.App().BasePath)
19 | fileRotate := &lumberjack.Logger{
20 | Filename: filename,
21 | MaxBackups: 7,
22 | }
23 | atomicLevel := zap.NewAtomicLevelAt(zap.InfoLevel)
24 | core := zapcore.NewCore(
25 | zapcore.NewConsoleEncoder(zapcore.EncoderConfig{
26 | TimeKey: "T",
27 | LevelKey: "L",
28 | NameKey: "N",
29 | CallerKey: "C",
30 | MessageKey: "M",
31 | StacktraceKey: "S",
32 | LineEnding: zapcore.DefaultLineEnding,
33 | EncodeLevel: zapcore.CapitalLevelEncoder,
34 | EncodeTime: func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
35 | enc.AppendString(t.Format("2006-01-02 15:04:05.000"))
36 | },
37 | EncodeDuration: zapcore.StringDurationEncoder,
38 | EncodeCaller: zapcore.ShortCallerEncoder,
39 | }),
40 | zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(fileRotate)),
41 | atomicLevel,
42 | )
43 | logger := zap.New(core, zap.AddCaller())
44 | if xcli.App().Debug {
45 | atomicLevel.SetLevel(zap.DebugLevel)
46 | }
47 | return logger.Sugar(), nil
48 | },
49 | }
50 | if err := xdi.Provide(&obj); err != nil {
51 | panic(err)
52 | }
53 | }
54 |
55 | func Zap() (logger *zap.SugaredLogger) {
56 | if err := xdi.Populate("zap", &logger); err != nil {
57 | panic(err)
58 | }
59 | return
60 | }
61 |
62 | type ZapOutput struct {
63 | Logger *zap.SugaredLogger
64 | }
65 |
66 | func (t *ZapOutput) Write(p []byte) (n int, err error) {
67 | t.Logger.Info(string(p))
68 | return len(p), nil
69 | }
70 |
--------------------------------------------------------------------------------
/examples/grpc-skeleton/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/mix-go/grpc-skeleton
2 |
3 | go 1.20
4 |
5 | require (
6 | github.com/go-sql-driver/mysql v1.7.1
7 | github.com/jinzhu/configor v1.2.1
8 | github.com/mix-go/xcli v1.1.21
9 | github.com/mix-go/xdi v1.1.17
10 | github.com/mix-go/xsql v1.1.11
11 | github.com/mix-go/xutil v1.1.6
12 | github.com/redis/go-redis/v9 v9.0.4
13 | github.com/sirupsen/logrus v1.9.0
14 | github.com/spf13/viper v1.15.0
15 | go.uber.org/zap v1.24.0
16 | google.golang.org/grpc v1.56.3
17 | google.golang.org/protobuf v1.33.0
18 | gopkg.in/natefinch/lumberjack.v2 v2.2.1
19 | gorm.io/driver/mysql v1.5.0
20 | gorm.io/gorm v1.25.0
21 | xorm.io/xorm v1.3.2
22 | )
23 |
24 | require (
25 | github.com/BurntSushi/toml v1.2.1 // indirect
26 | github.com/cespare/xxhash/v2 v2.2.0 // indirect
27 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
28 | github.com/fsnotify/fsnotify v1.6.0 // indirect
29 | github.com/goccy/go-json v0.10.2 // indirect
30 | github.com/golang/protobuf v1.5.3 // indirect
31 | github.com/golang/snappy v0.0.4 // indirect
32 | github.com/hashicorp/hcl v1.0.0 // indirect
33 | github.com/jinzhu/inflection v1.0.0 // indirect
34 | github.com/jinzhu/now v1.1.5 // indirect
35 | github.com/joho/godotenv v1.5.1 // indirect
36 | github.com/json-iterator/go v1.1.12 // indirect
37 | github.com/magiconair/properties v1.8.7 // indirect
38 | github.com/mitchellh/mapstructure v1.5.0 // indirect
39 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
40 | github.com/modern-go/reflect2 v1.0.2 // indirect
41 | github.com/onsi/ginkgo v1.16.5 // indirect
42 | github.com/onsi/gomega v1.18.1 // indirect
43 | github.com/pelletier/go-toml/v2 v2.0.7 // indirect
44 | github.com/sijms/go-ora/v2 v2.7.3 // indirect
45 | github.com/spf13/afero v1.9.5 // indirect
46 | github.com/spf13/cast v1.5.0 // indirect
47 | github.com/spf13/jwalterweatherman v1.1.0 // indirect
48 | github.com/spf13/pflag v1.0.5 // indirect
49 | github.com/subosito/gotenv v1.4.2 // indirect
50 | github.com/syndtr/goleveldb v1.0.0 // indirect
51 | go.uber.org/atomic v1.11.0 // indirect
52 | go.uber.org/multierr v1.11.0 // indirect
53 | golang.org/x/net v0.23.0 // indirect
54 | golang.org/x/sys v0.18.0 // indirect
55 | golang.org/x/text v0.14.0 // indirect
56 | google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
57 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
58 | gopkg.in/ini.v1 v1.67.0 // indirect
59 | gopkg.in/yaml.v2 v2.4.0 // indirect
60 | gopkg.in/yaml.v3 v3.0.1 // indirect
61 | xorm.io/builder v0.3.12 // indirect
62 | )
63 |
--------------------------------------------------------------------------------
/examples/grpc-skeleton/logs/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
--------------------------------------------------------------------------------
/examples/grpc-skeleton/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/mix-go/grpc-skeleton/commands"
5 | _ "github.com/mix-go/grpc-skeleton/config/configor"
6 | _ "github.com/mix-go/grpc-skeleton/config/dotenv"
7 | _ "github.com/mix-go/grpc-skeleton/di"
8 | "github.com/mix-go/xcli"
9 | "github.com/mix-go/xutil/xenv"
10 | )
11 |
12 | func main() {
13 | xcli.SetName("app").
14 | SetVersion("0.0.0-alpha").
15 | SetDebug(xenv.Getenv("APP_DEBUG").Bool(false))
16 | xcli.AddCommand(commands.Commands...).Run()
17 | }
18 |
--------------------------------------------------------------------------------
/examples/grpc-skeleton/protos/user.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package micro.grpc.user;
4 | option php_metadata_namespace = "Micro/Grpc/User/GPBMetadata";
5 | option go_package = "/;protos";
6 |
7 | service User {
8 | rpc Add(AddRequest) returns (AddResponse) {}
9 | }
10 |
11 | message AddRequest {
12 | string Name = 1;
13 | }
14 |
15 | message AddResponse {
16 | int32 error_code = 1;
17 | string error_message = 2;
18 | int64 user_id = 3;
19 | }
--------------------------------------------------------------------------------
/examples/grpc-skeleton/services/user.go:
--------------------------------------------------------------------------------
1 | package services
2 |
3 | import (
4 | "context"
5 | pb "github.com/mix-go/grpc-skeleton/protos"
6 | )
7 |
8 | type UserService struct {
9 | }
10 |
11 | func (t *UserService) Add(ctx context.Context, in *pb.AddRequest) (*pb.AddResponse, error) {
12 | // 执行数据库操作
13 | // ...
14 |
15 | resp := pb.AddResponse{
16 | ErrorCode: 0,
17 | ErrorMessage: "",
18 | UserId: 10001,
19 | }
20 | return &resp, nil
21 | }
22 |
--------------------------------------------------------------------------------
/examples/grpc-skeleton/shell/server.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | echo "============`date +%F' '%T`==========="
3 |
4 | file=/project/bin/program
5 | cmd=grpc:server
6 |
7 | getpid()
8 | {
9 | docmd=`ps aux | grep ${file} | grep ${cmd} | grep -v 'grep' | grep -v '\.sh' | awk '{print $2}' | xargs`
10 | echo $docmd
11 | }
12 |
13 | start()
14 | {
15 | pidstr=`getpid`
16 | if [ -n "$pidstr" ];then
17 | echo "running with pids $pidstr"
18 | else
19 | $file $cmd > /dev/null 2>&1 &
20 | sleep 1
21 | pidstr=`getpid`
22 | echo "start with pids $pidstr"
23 | fi
24 | }
25 |
26 | stop()
27 | {
28 | pidstr=`getpid`
29 | if [ ! -n "$pidstr" ];then
30 | echo "not executed!"
31 | return
32 | fi
33 | echo "kill $pidstr"
34 | kill $pidstr
35 | }
36 |
37 | restart()
38 | {
39 | stop
40 | sleep 1
41 | start
42 | }
43 |
44 | case "$1" in
45 | start)
46 | start
47 | ;;
48 | stop)
49 | stop
50 | ;;
51 | restart)
52 | restart
53 | ;;
54 | esac
55 |
--------------------------------------------------------------------------------
/examples/web-skeleton/.env:
--------------------------------------------------------------------------------
1 | # APP
2 | APP_DEBUG=true
3 |
4 | # DATABASE
5 | DATABASE_DSN=root:123456@tcp(127.0.0.1:3306)/test?charset=utf8&loc=Asia%2FShanghai&parseTime=true&timeout=10s
6 |
7 | # REDIS
8 | REDIS_ADDR=127.0.0.1:6379
9 | REDIS_DATABASE=0
10 | REDIS_PASSWORD=
11 | REDIS_DIAL_TIMEOUT=10
12 |
13 | # GIN
14 | GIN_ADDR=:8080
15 | GIN_MODE=test
16 |
--------------------------------------------------------------------------------
/examples/web-skeleton/.gitignore:
--------------------------------------------------------------------------------
1 | # goland project files
2 | .idea
3 |
4 | # netbeans project files
5 | nbproject
6 |
7 | # zend studio for eclipse project files
8 | .buildpath
9 | .project
10 | .settings
11 |
12 | # windows thumbnail cache
13 | Thumbs.db
14 |
15 | # Mac DS_Store Files
16 | .DS_Store
17 |
--------------------------------------------------------------------------------
/examples/web-skeleton/bin/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
--------------------------------------------------------------------------------
/examples/web-skeleton/commands/main.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "github.com/mix-go/xcli"
5 | )
6 |
7 | var Commands = []*xcli.Command{
8 | {
9 | Name: "web",
10 | Short: "\tStart the web server",
11 | Options: []*xcli.Option{
12 | {
13 | Names: []string{"a", "addr"},
14 | Usage: "\tListen to the specified address",
15 | },
16 | {
17 | Names: []string{"d", "daemon"},
18 | Usage: "\tRun in the background",
19 | },
20 | },
21 | RunI: &WebCommand{},
22 | },
23 | }
24 |
--------------------------------------------------------------------------------
/examples/web-skeleton/commands/web.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "github.com/gin-gonic/gin"
7 | "github.com/mix-go/web-skeleton/di"
8 | "github.com/mix-go/web-skeleton/routes"
9 | "github.com/mix-go/xcli"
10 | "github.com/mix-go/xcli/flag"
11 | "github.com/mix-go/xcli/process"
12 | "github.com/mix-go/xutil/xenv"
13 | "os"
14 | "os/signal"
15 | "strings"
16 | "syscall"
17 | "time"
18 | )
19 |
20 | type WebCommand struct {
21 | }
22 |
23 | func (t *WebCommand) Main() {
24 | if flag.Match("d", "daemon").Bool() {
25 | process.Daemon()
26 | }
27 |
28 | logger := di.Logrus()
29 | server := di.Server()
30 | addr := xenv.Getenv("GIN_ADDR").String(":8080")
31 | mode := xenv.Getenv("GIN_MODE").String(gin.ReleaseMode)
32 |
33 | // server
34 | gin.SetMode(mode)
35 | router := gin.New()
36 | // logger
37 | if mode != gin.ReleaseMode {
38 | handlerFunc := gin.LoggerWithConfig(gin.LoggerConfig{
39 | Formatter: func(params gin.LogFormatterParams) string {
40 | return fmt.Sprintf("%s|%s|%d|%s",
41 | params.Method,
42 | params.Path,
43 | params.StatusCode,
44 | params.ClientIP,
45 | )
46 | },
47 | Output: logger.Writer(),
48 | })
49 | router.Use(handlerFunc)
50 | }
51 | routes.Load(router)
52 | server.Addr = flag.Match("a", "addr").String(addr)
53 | server.Handler = router
54 |
55 | // signal
56 | ch := make(chan os.Signal)
57 | signal.Notify(ch, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
58 | go func() {
59 | <-ch
60 | logger.Info("Server shutdown")
61 | ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
62 | if err := server.Shutdown(ctx); err != nil {
63 | logger.Errorf("Server shutdown error: %s", err)
64 | }
65 | }()
66 |
67 | // templates
68 | router.LoadHTMLGlob(fmt.Sprintf("%s/../templates/*", xcli.App().BasePath))
69 |
70 | // static file
71 | router.Static("/static", fmt.Sprintf("%s/../public/static", xcli.App().BasePath))
72 | router.StaticFile("/favicon.ico", fmt.Sprintf("%s/../public/favicon.ico", xcli.App().BasePath))
73 |
74 | // run
75 | welcome()
76 | logger.Infof("Server start at %s", server.Addr)
77 | if err := server.ListenAndServe(); err != nil && !strings.Contains(err.Error(), "http: Server closed") {
78 | panic(err)
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/examples/web-skeleton/commands/welcome.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "fmt"
5 | "github.com/mix-go/web-skeleton/di"
6 | "runtime"
7 | "strings"
8 | )
9 |
10 | const logo = ` ___
11 | ______ ___ _ /__ ___ _____ ______
12 | / __ *__ \/ /\ \/ /__ __ */ __ \
13 | / / / / / / / /\ \/ _ /_/ // /_/ /
14 | /_/ /_/ /_/_/ /_/\_\ \__, / \____/
15 | /____/
16 | `
17 |
18 | func welcome() {
19 | fmt.Println(strings.Replace(logo, "*", "`", -1))
20 | fmt.Println("")
21 | fmt.Println(fmt.Sprintf("Server Name: %s", "mix-web"))
22 | fmt.Println(fmt.Sprintf("System Name: %s", runtime.GOOS))
23 | fmt.Println(fmt.Sprintf("Go Version: %s", runtime.Version()[2:]))
24 | fmt.Println(fmt.Sprintf("Listen Addr: %s", di.Server().Addr))
25 | }
26 |
--------------------------------------------------------------------------------
/examples/web-skeleton/conf/config.yml:
--------------------------------------------------------------------------------
1 | foo: bar
2 |
--------------------------------------------------------------------------------
/examples/web-skeleton/config/configor/main.go:
--------------------------------------------------------------------------------
1 | package configor
2 |
3 | import (
4 | "fmt"
5 | "github.com/jinzhu/configor"
6 | "github.com/mix-go/web-skeleton/config"
7 | "github.com/mix-go/xcli/argv"
8 | )
9 |
10 | func init() {
11 | // Conf support YAML, JSON, TOML, Shell Environment
12 | if err := configor.Load(&config.Config, fmt.Sprintf("%s/../conf/config.yml", argv.Program().Dir)); err != nil {
13 | panic(err)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/examples/web-skeleton/config/dotenv/main.go:
--------------------------------------------------------------------------------
1 | package dotenv
2 |
3 | import (
4 | "fmt"
5 | "github.com/mix-go/xcli/argv"
6 | "github.com/mix-go/xutil/xenv"
7 | )
8 |
9 | func init() {
10 | // Env
11 | if err := xenv.Load(fmt.Sprintf("%s/../.env", argv.Program().Dir)); err != nil {
12 | panic(err)
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/examples/web-skeleton/config/main.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | var Config = struct {
4 | Foo string
5 | }{}
6 |
--------------------------------------------------------------------------------
/examples/web-skeleton/config/viper/main.go:
--------------------------------------------------------------------------------
1 | package viper
2 |
3 | import (
4 | "fmt"
5 | "github.com/mix-go/web-skeleton/config"
6 | "github.com/mix-go/xcli/argv"
7 | "github.com/spf13/viper"
8 | )
9 |
10 | func init() {
11 | // Conf support JSON, TOML, YAML, HCL, INI, envfile
12 | viper.SetConfigFile(fmt.Sprintf("%s/../conf/config.yml", argv.Program().Dir))
13 | if err := viper.ReadInConfig(); err != nil {
14 | panic(err)
15 | }
16 | if err := viper.Unmarshal(&config.Config); err != nil {
17 | panic(err)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/examples/web-skeleton/controllers/hello.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "net/http"
6 | )
7 |
8 | type HelloController struct {
9 | }
10 |
11 | func (t *HelloController) Index(c *gin.Context) {
12 | c.HTML(http.StatusOK, "index.tmpl", gin.H{
13 | "title": "Hello, World!",
14 | })
15 | }
16 |
--------------------------------------------------------------------------------
/examples/web-skeleton/controllers/login.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "context"
5 | "github.com/gin-gonic/gin"
6 | "github.com/mix-go/web-skeleton/di"
7 | "net/http"
8 | )
9 |
10 | type LoginController struct {
11 | }
12 |
13 | func (t *LoginController) Index(c *gin.Context) {
14 | // 网页
15 | if c.Request.Method == http.MethodGet {
16 | c.HTML(http.StatusOK, "login.tmpl", gin.H{
17 | "title": "Login",
18 | })
19 | c.Abort()
20 | return
21 | }
22 |
23 | // 检查用户登录代码
24 | // ...
25 |
26 | // session
27 | session := di.Session()
28 | store, err := session.Start(context.Background(), c.Writer, c.Request)
29 | if err != nil {
30 | panic(err)
31 | }
32 | store.Set("userinfo", gin.H{
33 | "user_id": 10008,
34 | })
35 | if err := store.Save(); err != nil {
36 | panic(err)
37 | }
38 |
39 | // 跳转到登录成功页
40 | c.Redirect(http.StatusMovedPermanently, "/users/add")
41 | }
42 |
--------------------------------------------------------------------------------
/examples/web-skeleton/controllers/user.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "net/http"
6 | )
7 |
8 | type UserController struct {
9 | }
10 |
11 | func (t *UserController) Add(c *gin.Context) {
12 | // 网页
13 | if c.Request.Method == http.MethodGet {
14 | c.HTML(http.StatusOK, "user_add.tmpl", gin.H{
15 | "title": "User add",
16 | })
17 | c.Abort()
18 | return
19 | }
20 |
21 | // 执行数据库操作
22 | // ...
23 |
24 | c.String(http.StatusInternalServerError, "%s ", "Add ok!")
25 | }
26 |
--------------------------------------------------------------------------------
/examples/web-skeleton/controllers/ws.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "github.com/gorilla/websocket"
6 | "github.com/mix-go/web-skeleton/di"
7 | "github.com/mix-go/xcli"
8 | "net/http"
9 | )
10 |
11 | var upgrader = websocket.Upgrader{
12 | ReadBufferSize: 1024,
13 | WriteBufferSize: 1024,
14 | }
15 |
16 | type WebSocketController struct {
17 | }
18 |
19 | func (t *WebSocketController) Index(c *gin.Context) {
20 | logger := di.Logrus()
21 | if xcli.App().Debug {
22 | upgrader.CheckOrigin = func(r *http.Request) bool {
23 | return true
24 | }
25 | }
26 | conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
27 | if err != nil {
28 | logger.Error(err)
29 | c.Status(http.StatusInternalServerError)
30 | c.Abort()
31 | return
32 | }
33 |
34 | session := WebSocketSession{
35 | Conn: conn,
36 | Header: c.Request.Header,
37 | Send: make(chan []byte, 100),
38 | }
39 | session.Start()
40 |
41 | server := di.Server()
42 | server.RegisterOnShutdown(func() {
43 | session.Stop()
44 | })
45 |
46 | logger.Infof("Upgrade: %s", c.Request.UserAgent())
47 | }
48 |
49 | type WebSocketSession struct {
50 | Conn *websocket.Conn
51 | Header http.Header
52 | Send chan []byte
53 | }
54 |
55 | func (t *WebSocketSession) Start() {
56 | go func() {
57 | logger := di.Logrus()
58 | for {
59 | msgType, msg, err := t.Conn.ReadMessage()
60 | if err != nil {
61 | if !websocket.IsCloseError(err, 1001, 1006) {
62 | logger.Error(err)
63 | }
64 | t.Stop()
65 | return
66 | }
67 | if msgType != websocket.TextMessage {
68 | continue
69 | }
70 |
71 | handler := WebSocketHandler{
72 | Session: t,
73 | }
74 | handler.Index(msg)
75 | }
76 | }()
77 | go func() {
78 | logger := di.Logrus()
79 | for {
80 | msg, ok := <-t.Send
81 | if !ok {
82 | return
83 | }
84 | if err := t.Conn.WriteMessage(websocket.TextMessage, msg); err != nil {
85 | logger.Error(err)
86 | t.Stop()
87 | return
88 | }
89 | }
90 | }()
91 | }
92 |
93 | func (t *WebSocketSession) Stop() {
94 | defer func() {
95 | if err := recover(); err != nil {
96 | logger := di.Logrus()
97 | logger.Error(err)
98 | }
99 | }()
100 | close(t.Send)
101 | _ = t.Conn.Close()
102 | }
103 |
104 | type WebSocketHandler struct {
105 | Session *WebSocketSession
106 | }
107 |
108 | func (t *WebSocketHandler) Index(msg []byte) {
109 | t.Session.Send <- []byte("hello, world!")
110 | }
111 |
--------------------------------------------------------------------------------
/examples/web-skeleton/di/goredis.go:
--------------------------------------------------------------------------------
1 | package di
2 |
3 | import (
4 | "github.com/mix-go/xdi"
5 | "github.com/mix-go/xutil/xenv"
6 | "github.com/redis/go-redis/v9"
7 | "time"
8 | )
9 |
10 | func init() {
11 | obj := xdi.Object{
12 | Name: "goredis",
13 | New: func() (i interface{}, e error) {
14 | opt := redis.Options{
15 | Addr: xenv.Getenv("REDIS_ADDR").String(),
16 | Password: xenv.Getenv("REDIS_PASSWORD").String(),
17 | DB: int(xenv.Getenv("REDIS_DATABASE").Int64()),
18 | DialTimeout: time.Duration(xenv.Getenv("REDIS_DIAL_TIMEOUT").Int64(10)) * time.Second,
19 | }
20 | return redis.NewClient(&opt), nil
21 | },
22 | }
23 | if err := xdi.Provide(&obj); err != nil {
24 | panic(err)
25 | }
26 | }
27 |
28 | func GoRedis() (client *redis.Client) {
29 | if err := xdi.Populate("goredis", &client); err != nil {
30 | panic(err)
31 | }
32 | return
33 | }
34 |
--------------------------------------------------------------------------------
/examples/web-skeleton/di/gorm.go:
--------------------------------------------------------------------------------
1 | package di
2 |
3 | import (
4 | "github.com/mix-go/xdi"
5 | "github.com/mix-go/xutil/xenv"
6 | "gorm.io/driver/mysql"
7 | "gorm.io/gorm"
8 | )
9 |
10 | func init() {
11 | obj := xdi.Object{
12 | Name: "gorm",
13 | New: func() (i interface{}, e error) {
14 | return gorm.Open(mysql.Open(xenv.Getenv("DATABASE_DSN").String()))
15 | },
16 | }
17 | if err := xdi.Provide(&obj); err != nil {
18 | panic(err)
19 | }
20 | }
21 |
22 | func Gorm() (db *gorm.DB) {
23 | if err := xdi.Populate("gorm", &db); err != nil {
24 | panic(err)
25 | }
26 | return
27 | }
28 |
--------------------------------------------------------------------------------
/examples/web-skeleton/di/logrus.go:
--------------------------------------------------------------------------------
1 | package di
2 |
3 | import (
4 | "fmt"
5 | "github.com/mix-go/xcli"
6 | "github.com/mix-go/xdi"
7 | "github.com/sirupsen/logrus"
8 | "gopkg.in/natefinch/lumberjack.v2"
9 | "io"
10 | "os"
11 | "path/filepath"
12 | "runtime"
13 | )
14 |
15 | func init() {
16 | obj := xdi.Object{
17 | Name: "logrus",
18 | New: func() (i interface{}, e error) {
19 | logger := logrus.New()
20 | logger.ReportCaller = true // 显示调用信息
21 | formatter := new(logrus.TextFormatter)
22 | formatter.FullTimestamp = true
23 | formatter.TimestampFormat = "2006-01-02 15:04:05.000"
24 | formatter.DisableQuote = true // 不转义换行符,为了保存错误堆栈到日志文件
25 | formatter.CallerPrettyfier = func(frame *runtime.Frame) (function string, file string) {
26 | return "", fmt.Sprintf("%s:%d", filepath.Base(frame.File), frame.Line)
27 | }
28 | logger.Formatter = formatter
29 | filename := fmt.Sprintf("%s/../runtime/logs/mix.log", xcli.App().BasePath)
30 | fileRotate := &lumberjack.Logger{
31 | Filename: filename,
32 | MaxBackups: 7,
33 | }
34 | writer := io.MultiWriter(os.Stdout, fileRotate)
35 | logger.SetOutput(writer)
36 | if xcli.App().Debug {
37 | logger.SetLevel(logrus.DebugLevel)
38 | }
39 | return logger, nil
40 | },
41 | }
42 | if err := xdi.Provide(&obj); err != nil {
43 | panic(err)
44 | }
45 | }
46 |
47 | func Logrus() (logger *logrus.Logger) {
48 | if err := xdi.Populate("logrus", &logger); err != nil {
49 | panic(err)
50 | }
51 | return
52 | }
53 |
--------------------------------------------------------------------------------
/examples/web-skeleton/di/server.go:
--------------------------------------------------------------------------------
1 | package di
2 |
3 | import (
4 | "github.com/mix-go/xdi"
5 | "net/http"
6 | )
7 |
8 | func init() {
9 | obj := xdi.Object{
10 | Name: "server",
11 | New: func() (i interface{}, e error) {
12 | return &http.Server{}, nil
13 | },
14 | }
15 | if err := xdi.Provide(&obj); err != nil {
16 | panic(err)
17 | }
18 | }
19 |
20 | func Server() (s *http.Server) {
21 | if err := xdi.Populate("server", &s); err != nil {
22 | panic(err)
23 | }
24 | return
25 | }
26 |
--------------------------------------------------------------------------------
/examples/web-skeleton/di/session.go:
--------------------------------------------------------------------------------
1 | package di
2 |
3 | import (
4 | "github.com/go-session/redis"
5 | "github.com/go-session/session"
6 | "github.com/mix-go/xdi"
7 | "github.com/mix-go/xutil/xenv"
8 | "time"
9 | )
10 |
11 | func init() {
12 | obj := xdi.Object{
13 | Name: "session",
14 | New: func() (i interface{}, e error) {
15 | opts := redis.Options{
16 | Addr: xenv.Getenv("REDIS_ADDR").String(),
17 | Password: xenv.Getenv("REDIS_PASSWORD").String(),
18 | DB: int(xenv.Getenv("REDIS_DATABASE").Int64()),
19 | DialTimeout: time.Duration(xenv.Getenv("REDIS_DIAL_TIMEOUT").Int64(10)) * time.Second,
20 | }
21 | opt := redis.NewRedisStore(&opts)
22 | return session.NewManager(session.SetStore(opt)), nil
23 | },
24 | }
25 | if err := xdi.Provide(&obj); err != nil {
26 | panic(err)
27 | }
28 | }
29 |
30 | func Session() (manager *session.Manager) {
31 | if err := xdi.Populate("session", &manager); err != nil {
32 | panic(err)
33 | }
34 | return
35 | }
36 |
--------------------------------------------------------------------------------
/examples/web-skeleton/di/xorm.go:
--------------------------------------------------------------------------------
1 | package di
2 |
3 | import (
4 | _ "github.com/go-sql-driver/mysql"
5 | "github.com/mix-go/xdi"
6 | "github.com/mix-go/xutil/xenv"
7 | "xorm.io/xorm"
8 | )
9 |
10 | func init() {
11 | obj := xdi.Object{
12 | Name: "xorm",
13 | New: func() (i interface{}, e error) {
14 | return xorm.NewEngine("mysql", xenv.Getenv("DATABASE_DSN").String())
15 | },
16 | }
17 | if err := xdi.Provide(&obj); err != nil {
18 | panic(err)
19 | }
20 | }
21 |
22 | func Xorm() (db *xorm.Engine) {
23 | if err := xdi.Populate("xorm", &db); err != nil {
24 | panic(err)
25 | }
26 | return
27 | }
28 |
--------------------------------------------------------------------------------
/examples/web-skeleton/di/xsql.go:
--------------------------------------------------------------------------------
1 | package di
2 |
3 | import (
4 | "database/sql"
5 | _ "github.com/go-sql-driver/mysql"
6 | "github.com/mix-go/xdi"
7 | "github.com/mix-go/xsql"
8 | "github.com/mix-go/xutil/xenv"
9 | )
10 |
11 | func init() {
12 | obj := xdi.Object{
13 | Name: "xsql",
14 | New: func() (i interface{}, e error) {
15 | db, err := sql.Open("mysql", xenv.Getenv("DATABASE_DSN").String())
16 | if err != nil {
17 | return nil, err
18 | }
19 | return xsql.New(db), nil
20 | },
21 | }
22 | if err := xdi.Provide(&obj); err != nil {
23 | panic(err)
24 | }
25 | }
26 |
27 | func Xsql() (db *xsql.DB) {
28 | if err := xdi.Populate("xsql", &db); err != nil {
29 | panic(err)
30 | }
31 | return
32 | }
33 |
--------------------------------------------------------------------------------
/examples/web-skeleton/di/zap.go:
--------------------------------------------------------------------------------
1 | package di
2 |
3 | import (
4 | "fmt"
5 | "github.com/mix-go/xcli"
6 | "github.com/mix-go/xdi"
7 | "go.uber.org/zap"
8 | "go.uber.org/zap/zapcore"
9 | "gopkg.in/natefinch/lumberjack.v2"
10 | "os"
11 | "time"
12 | )
13 |
14 | func init() {
15 | obj := xdi.Object{
16 | Name: "zap",
17 | New: func() (i interface{}, e error) {
18 | filename := fmt.Sprintf("%s/../runtime/logs/mix.log", xcli.App().BasePath)
19 | fileRotate := &lumberjack.Logger{
20 | Filename: filename,
21 | MaxBackups: 7,
22 | }
23 | atomicLevel := zap.NewAtomicLevelAt(zap.InfoLevel)
24 | core := zapcore.NewCore(
25 | zapcore.NewConsoleEncoder(zapcore.EncoderConfig{
26 | TimeKey: "T",
27 | LevelKey: "L",
28 | NameKey: "N",
29 | CallerKey: "C",
30 | MessageKey: "M",
31 | StacktraceKey: "S",
32 | LineEnding: zapcore.DefaultLineEnding,
33 | EncodeLevel: zapcore.CapitalLevelEncoder,
34 | EncodeTime: func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
35 | enc.AppendString(t.Format("2006-01-02 15:04:05.000"))
36 | },
37 | EncodeDuration: zapcore.StringDurationEncoder,
38 | EncodeCaller: zapcore.ShortCallerEncoder,
39 | }),
40 | zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(fileRotate)),
41 | atomicLevel,
42 | )
43 | logger := zap.New(core, zap.AddCaller())
44 | if xcli.App().Debug {
45 | atomicLevel.SetLevel(zap.DebugLevel)
46 | }
47 | return logger.Sugar(), nil
48 | },
49 | }
50 | if err := xdi.Provide(&obj); err != nil {
51 | panic(err)
52 | }
53 | }
54 |
55 | func Zap() (logger *zap.SugaredLogger) {
56 | if err := xdi.Populate("zap", &logger); err != nil {
57 | panic(err)
58 | }
59 | return
60 | }
61 |
62 | type ZapOutput struct {
63 | Logger *zap.SugaredLogger
64 | }
65 |
66 | func (t *ZapOutput) Write(p []byte) (n int, err error) {
67 | t.Logger.Info(string(p))
68 | return len(p), nil
69 | }
70 |
--------------------------------------------------------------------------------
/examples/web-skeleton/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/mix-go/web-skeleton
2 |
3 | go 1.20
4 |
5 | require (
6 | github.com/gin-gonic/gin v1.9.1
7 | github.com/go-session/redis v3.0.1+incompatible
8 | github.com/go-session/session v3.1.2+incompatible
9 | github.com/go-sql-driver/mysql v1.7.1
10 | github.com/gorilla/websocket v1.5.0
11 | github.com/jinzhu/configor v1.2.1
12 | github.com/mix-go/xcli v1.1.21
13 | github.com/mix-go/xdi v1.1.17
14 | github.com/mix-go/xsql v1.1.11
15 | github.com/mix-go/xutil v1.1.6
16 | github.com/redis/go-redis/v9 v9.0.4
17 | github.com/sirupsen/logrus v1.9.0
18 | github.com/spf13/viper v1.15.0
19 | go.uber.org/zap v1.24.0
20 | gopkg.in/natefinch/lumberjack.v2 v2.2.1
21 | gorm.io/driver/mysql v1.5.0
22 | gorm.io/gorm v1.25.0
23 | xorm.io/xorm v1.3.2
24 | )
25 |
26 | require (
27 | github.com/BurntSushi/toml v1.2.1 // indirect
28 | github.com/bytedance/sonic v1.9.1 // indirect
29 | github.com/cespare/xxhash/v2 v2.2.0 // indirect
30 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
31 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
32 | github.com/fsnotify/fsnotify v1.6.0 // indirect
33 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect
34 | github.com/gin-contrib/sse v0.1.0 // indirect
35 | github.com/go-playground/locales v0.14.1 // indirect
36 | github.com/go-playground/universal-translator v0.18.1 // indirect
37 | github.com/go-playground/validator/v10 v10.14.0 // indirect
38 | github.com/go-redis/redis v6.15.9+incompatible // indirect
39 | github.com/goccy/go-json v0.10.2 // indirect
40 | github.com/golang/snappy v0.0.4 // indirect
41 | github.com/hashicorp/hcl v1.0.0 // indirect
42 | github.com/jinzhu/inflection v1.0.0 // indirect
43 | github.com/jinzhu/now v1.1.5 // indirect
44 | github.com/joho/godotenv v1.5.1 // indirect
45 | github.com/json-iterator/go v1.1.12 // indirect
46 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect
47 | github.com/leodido/go-urn v1.2.4 // indirect
48 | github.com/magiconair/properties v1.8.7 // indirect
49 | github.com/mattn/go-isatty v0.0.19 // indirect
50 | github.com/mitchellh/mapstructure v1.5.0 // indirect
51 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
52 | github.com/modern-go/reflect2 v1.0.2 // indirect
53 | github.com/onsi/ginkgo v1.16.5 // indirect
54 | github.com/onsi/gomega v1.18.1 // indirect
55 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect
56 | github.com/sijms/go-ora/v2 v2.7.3 // indirect
57 | github.com/spf13/afero v1.9.5 // indirect
58 | github.com/spf13/cast v1.5.0 // indirect
59 | github.com/spf13/jwalterweatherman v1.1.0 // indirect
60 | github.com/spf13/pflag v1.0.5 // indirect
61 | github.com/subosito/gotenv v1.4.2 // indirect
62 | github.com/syndtr/goleveldb v1.0.0 // indirect
63 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
64 | github.com/ugorji/go/codec v1.2.11 // indirect
65 | go.uber.org/atomic v1.11.0 // indirect
66 | go.uber.org/multierr v1.11.0 // indirect
67 | golang.org/x/arch v0.3.0 // indirect
68 | golang.org/x/crypto v0.21.0 // indirect
69 | golang.org/x/net v0.23.0 // indirect
70 | golang.org/x/sys v0.18.0 // indirect
71 | golang.org/x/text v0.14.0 // indirect
72 | google.golang.org/protobuf v1.33.0 // indirect
73 | gopkg.in/ini.v1 v1.67.0 // indirect
74 | gopkg.in/yaml.v2 v2.4.0 // indirect
75 | gopkg.in/yaml.v3 v3.0.1 // indirect
76 | xorm.io/builder v0.3.12 // indirect
77 | )
78 |
--------------------------------------------------------------------------------
/examples/web-skeleton/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/mix-go/web-skeleton/commands"
5 | _ "github.com/mix-go/web-skeleton/config/configor"
6 | _ "github.com/mix-go/web-skeleton/config/dotenv"
7 | _ "github.com/mix-go/web-skeleton/di"
8 | "github.com/mix-go/xcli"
9 | "github.com/mix-go/xutil/xenv"
10 | )
11 |
12 | func main() {
13 | xcli.SetName("app").
14 | SetVersion("0.0.0-alpha").
15 | SetDebug(xenv.Getenv("APP_DEBUG").Bool(false))
16 | xcli.AddCommand(commands.Commands...).Run()
17 | }
18 |
--------------------------------------------------------------------------------
/examples/web-skeleton/middleware/session.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "context"
5 | "github.com/gin-gonic/gin"
6 | "github.com/mix-go/web-skeleton/di"
7 | )
8 |
9 | func SessionMiddleware() gin.HandlerFunc {
10 | return func(c *gin.Context) {
11 | session := di.Session()
12 | store, err := session.Start(context.Background(), c.Writer, c.Request)
13 | if err != nil {
14 | panic(err)
15 | }
16 | if _, ok := store.Get("userinfo"); !ok {
17 | c.Status(401)
18 | c.Abort()
19 | return
20 | }
21 |
22 | c.Next()
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/examples/web-skeleton/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mix-go/mix/8559688b0ade0b1c8fb4c3450f403f673e5f59f7/examples/web-skeleton/public/favicon.ico
--------------------------------------------------------------------------------
/examples/web-skeleton/public/static/index.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mix-go/mix/8559688b0ade0b1c8fb4c3450f403f673e5f59f7/examples/web-skeleton/public/static/index.html
--------------------------------------------------------------------------------
/examples/web-skeleton/routes/main.go:
--------------------------------------------------------------------------------
1 | package routes
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "github.com/mix-go/web-skeleton/controllers"
6 | "github.com/mix-go/web-skeleton/middleware"
7 | )
8 |
9 | func Load(router *gin.Engine) {
10 | router.Use(gin.Recovery()) // error handle
11 |
12 | router.GET("hello",
13 | func(ctx *gin.Context) {
14 | hello := controllers.HelloController{}
15 | hello.Index(ctx)
16 | },
17 | )
18 |
19 | router.Any("users/add",
20 | middleware.SessionMiddleware(),
21 | func(ctx *gin.Context) {
22 | user := controllers.UserController{}
23 | user.Add(ctx)
24 | },
25 | )
26 |
27 | router.Any("login", func(ctx *gin.Context) {
28 | login := controllers.LoginController{}
29 | login.Index(ctx)
30 | })
31 |
32 | router.GET("websocket",
33 | func(ctx *gin.Context) {
34 | ws := controllers.WebSocketController{}
35 | ws.Index(ctx)
36 | },
37 | )
38 | }
39 |
--------------------------------------------------------------------------------
/examples/web-skeleton/runtime/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
--------------------------------------------------------------------------------
/examples/web-skeleton/shell/server.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | echo "============`date +%F' '%T`==========="
3 |
4 | file=/project/bin/program
5 | cmd=web
6 |
7 | getpid()
8 | {
9 | docmd=`ps aux | grep ${file} | grep ${cmd} | grep -v 'grep' | grep -v '\.sh' | awk '{print $2}' | xargs`
10 | echo $docmd
11 | }
12 |
13 | start()
14 | {
15 | pidstr=`getpid`
16 | if [ -n "$pidstr" ];then
17 | echo "running with pids $pidstr"
18 | else
19 | $file $cmd > /dev/null 2>&1 &
20 | sleep 1
21 | pidstr=`getpid`
22 | echo "start with pids $pidstr"
23 | fi
24 | }
25 |
26 | stop()
27 | {
28 | pidstr=`getpid`
29 | if [ ! -n "$pidstr" ];then
30 | echo "not executed!"
31 | return
32 | fi
33 | echo "kill $pidstr"
34 | kill $pidstr
35 | }
36 |
37 | restart()
38 | {
39 | stop
40 | sleep 1
41 | start
42 | }
43 |
44 | case "$1" in
45 | start)
46 | start
47 | ;;
48 | stop)
49 | stop
50 | ;;
51 | restart)
52 | restart
53 | ;;
54 | esac
55 |
--------------------------------------------------------------------------------
/examples/web-skeleton/templates/index.tmpl:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ .title }}
4 |
5 |
--------------------------------------------------------------------------------
/examples/web-skeleton/templates/login.tmpl:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ .title }}
4 |
5 |
16 |
--------------------------------------------------------------------------------
/examples/web-skeleton/templates/user_add.tmpl:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ .title }}
4 |
5 |
12 |
--------------------------------------------------------------------------------
/src/authenticator/README.md:
--------------------------------------------------------------------------------
1 | > Developed by OpenMix: [https://openmix.org](https://openmix.org/mix-go)
2 |
3 | ## Mix Authenticator
4 |
5 | Install
6 |
7 | ```
8 | go get github.com/mix-go/authenticator@latest
9 | ```
10 |
11 | Generate Secret
12 |
13 | ```go
14 | secret := authenticator.GenerateSecret()
15 | ```
16 |
17 | Generate Code
18 |
19 | ```go
20 | code := authenticator.GenerateToken(secret)
21 | ```
22 |
23 | Verify Code
24 |
25 | ```go
26 | ok := authenticator.VerifyToken(secret, code)
27 | // or
28 | ok := authenticator.VerifyTokenCustom(secret, code, 60)
29 | ```
30 |
31 | Generate Url
32 |
33 | ```go
34 | uri := authenticator.GenerateTotpUri("Foo", "bar", secret)
35 | // or
36 | url := authenticator.GenerateQRCodeGoogleUrl("Foo", "bar", secret)
37 | ```
38 |
39 | ## License
40 |
41 | Apache License Version 2.0, http://www.apache.org/licenses/
42 |
--------------------------------------------------------------------------------
/src/authenticator/authenticator.go:
--------------------------------------------------------------------------------
1 | package authenticator
2 |
3 | import (
4 | "crypto/rand"
5 | "encoding/base32"
6 | "fmt"
7 | "net/url"
8 | "regexp"
9 | "strconv"
10 | "strings"
11 | "time"
12 |
13 | "github.com/pquerna/otp"
14 | "github.com/pquerna/otp/totp"
15 | )
16 |
17 | const NONE = "none"
18 |
19 | func GenerateSecret() string {
20 | formattedKey := encodeGoogleAuthKey(generateOtpKey())
21 | rx := regexp.MustCompile(`\W+`)
22 | secret := []byte(rx.ReplaceAllString(formattedKey, ""))
23 | return base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(secret)
24 | }
25 |
26 | // Generate a key
27 | func generateOtpKey() []byte {
28 | // 20 cryptographically random binary bytes (160-bit key)
29 | key := make([]byte, 20)
30 | _, _ = rand.Read(key)
31 | return key
32 | }
33 |
34 | // Text-encode the key as base32 (in the style of Google Authenticator - same as Facebook, Microsoft, etc)
35 | func encodeGoogleAuthKey(bin []byte) string {
36 | // 32 ascii characters without trailing '='s
37 | rx := regexp.MustCompile(`=`)
38 | base32 := rx.ReplaceAllString(base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(bin), "")
39 | base32 = strings.ToLower(base32)
40 |
41 | // lowercase with a space every 4 characters
42 | rx = regexp.MustCompile(`(\w{4})`)
43 | key := strings.TrimSpace(rx.ReplaceAllString(base32, "$1 "))
44 |
45 | return key
46 | }
47 |
48 | func VerifyToken(secret, passcode string) bool {
49 | return VerifyTokenCustom(secret, passcode, 30)
50 | }
51 |
52 | func VerifyTokenCustom(secret, passcode string, period int) bool {
53 | b, _ := base32.StdEncoding.WithPadding(base32.NoPadding).DecodeString(secret)
54 | key, _ := totp.Generate(totp.GenerateOpts{
55 | Issuer: NONE,
56 | AccountName: NONE,
57 | SecretSize: uint(len(b)),
58 | Secret: b,
59 | })
60 | rv, _ := totp.ValidateCustom(
61 | passcode,
62 | key.Secret(),
63 | time.Now().UTC(),
64 | totp.ValidateOpts{
65 | Period: uint(period),
66 | Skew: 1,
67 | Digits: otp.DigitsSix,
68 | Algorithm: otp.AlgorithmSHA1,
69 | },
70 | )
71 | return rv
72 | }
73 |
74 | func GenerateToken(secret string) (passcode string) {
75 | b, _ := base32.StdEncoding.WithPadding(base32.NoPadding).DecodeString(secret)
76 | key, _ := totp.Generate(totp.GenerateOpts{
77 | Issuer: NONE,
78 | AccountName: NONE,
79 | SecretSize: uint(len(b)),
80 | Secret: b,
81 | })
82 | passcode, _ = totp.GenerateCode(key.Secret(), time.Now().UTC())
83 | return
84 | }
85 |
86 | func GenerateTotpUri(issuer, accountName, secret string) string {
87 | // Full OTPAUTH URI spec as explained at
88 | // https://github.com/google/google-authenticator/wiki/Key-Uri-Format
89 | u := url.URL{}
90 | v := url.Values{}
91 | u.Scheme = "otpauth"
92 | u.Host = "totp"
93 | u.Path = fmt.Sprintf("%s:%s", issuer, accountName)
94 | v.Add("secret", secret)
95 | v.Add("issuer", issuer)
96 | v.Add("algorithm", "SHA1")
97 | v.Add("digits", strconv.Itoa(6))
98 | v.Add("period", strconv.Itoa(30))
99 | u.RawQuery = v.Encode()
100 | return u.String()
101 | }
102 |
103 | func GenerateQRCodeGoogleUrl(issuer, accountName, secret string) string {
104 | uri := GenerateTotpUri(issuer, accountName, secret)
105 | return fmt.Sprintf("https://www.google.com/chart?chs=200x200&chld=M|0&cht=qr&chl=%s", url.QueryEscape(uri))
106 | }
107 |
--------------------------------------------------------------------------------
/src/authenticator/authenticator_test.go:
--------------------------------------------------------------------------------
1 | package authenticator_test
2 |
3 | import (
4 | "fmt"
5 | "github.com/mix-go/authenticator"
6 | "github.com/stretchr/testify/assert"
7 | "testing"
8 | )
9 |
10 | func TestVerifyToken(t *testing.T) {
11 | a := assert.New(t)
12 |
13 | secret := authenticator.GenerateSecret()
14 | code := authenticator.GenerateToken(secret)
15 | ok := authenticator.VerifyToken(secret, code)
16 | uri := authenticator.GenerateTotpUri("foo", "bar", secret)
17 | url := authenticator.GenerateQRCodeGoogleUrl("foo", "bar", secret)
18 | fmt.Printf("%v\n%s\n%s\n", ok, uri, url)
19 |
20 | a.Equal(ok, true)
21 | }
22 |
23 | func TestPHPVerifyToken(t *testing.T) {
24 | a := assert.New(t)
25 |
26 | secret := "OQB6ZZGYHCPSX4AK"
27 | code := authenticator.GenerateToken(secret)
28 | ok := authenticator.VerifyToken(secret, code)
29 | uri := authenticator.GenerateTotpUri("foo", "bar", secret)
30 | url := authenticator.GenerateQRCodeGoogleUrl("foo", "bar", secret)
31 | fmt.Printf("%v\n%s\n%s\n", ok, uri, url)
32 |
33 | a.Equal(ok, true)
34 | }
35 |
--------------------------------------------------------------------------------
/src/authenticator/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/mix-go/authenticator
2 |
3 | go 1.20
4 |
5 | require (
6 | github.com/pquerna/otp v1.4.0
7 | github.com/stretchr/testify v1.3.0
8 | )
9 |
10 | require (
11 | github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
12 | github.com/davecgh/go-spew v1.1.0 // indirect
13 | github.com/pmezard/go-difflib v1.0.0 // indirect
14 | )
15 |
--------------------------------------------------------------------------------
/src/authenticator/go.sum:
--------------------------------------------------------------------------------
1 | github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
2 | github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
3 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
6 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
7 | github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
8 | github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
9 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
10 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
11 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
12 |
--------------------------------------------------------------------------------
/src/mixcli/README.md:
--------------------------------------------------------------------------------
1 | > OpenMix 出品:[https://openmix.org](https://openmix.org/mix-go)
2 |
3 | ## Mix CLI
4 |
5 | 一个快速创建 Go 项目的脚手架
6 |
7 | A scaffold to quickly create a go project
8 |
9 | ## Installation
10 |
11 | ```
12 | go get github.com/mix-go/mixcli
13 | ```
14 |
15 | ## New Project
16 |
17 | 创建项目
18 |
19 | - 可以生成 `cli`, `api`, `web`, `grpc` 多种项目代码,生成的代码开箱即用
20 | - 可选择是否需要 `.env` 环境配置
21 | - 可选择使用 `viper`, `configor` 加载 `.yml`, `.json`, `.toml` 等独立配置
22 | - 可选择使用 `gorm`, `xorm` 的数据库
23 | - 可选择使用 `zap`, `logrus` 的日志库
24 |
25 | ~~~
26 | $ mixcli new hello
27 | Use the arrow keys to navigate: ↓ ↑ → ←
28 | ? Select project type:
29 | ▸ CLI
30 | API
31 | Web (contains the websocket)
32 | gRPC
33 | ~~~
34 |
35 | ## License
36 |
37 | Apache License Version 2.0, http://www.apache.org/licenses/
38 |
--------------------------------------------------------------------------------
/src/mixcli/bin/.gitignore:
--------------------------------------------------------------------------------
1 | .gitignore
2 | !.gitignore
--------------------------------------------------------------------------------
/src/mixcli/commands/cmds.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import "github.com/mix-go/xcli"
4 |
5 | var Cmds = []*xcli.Command{
6 | {
7 | Name: "new",
8 | Short: "\tCreate a project",
9 | RunI: &NewCommand{},
10 | },
11 | }
12 |
--------------------------------------------------------------------------------
/src/mixcli/commands/version.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "strings"
5 | )
6 |
7 | var (
8 | SkeletonVersion = "1.1.25"
9 | CLIVersion = "1.1.25"
10 | )
11 |
12 | var (
13 | VersionBig = 1
14 | VersionSmall = 2
15 | VersionEqual = 0
16 | )
17 |
18 | func VersionCompare(verA, verB string) int {
19 | verStrArrA := splitStrByNet(verA)
20 | verStrArrB := splitStrByNet(verB)
21 | lenStrA := len(verStrArrA)
22 | lenStrB := len(verStrArrB)
23 | if lenStrA != lenStrB {
24 | panic("version length is inconsistent")
25 | }
26 | return compareArrStrVers(verStrArrA, verStrArrB)
27 | }
28 |
29 | func compareArrStrVers(verA, verB []string) int {
30 | for index, _ := range verA {
31 | littleResult := compareLittleVer(verA[index], verB[index])
32 | if littleResult != VersionEqual {
33 | return littleResult
34 | }
35 | }
36 | return VersionEqual
37 | }
38 |
39 | func compareLittleVer(verA, verB string) int {
40 | bytesA := []byte(verA)
41 | bytesB := []byte(verB)
42 | lenA := len(bytesA)
43 | lenB := len(bytesB)
44 | if lenA > lenB {
45 | return VersionBig
46 | }
47 | if lenA < lenB {
48 | return VersionSmall
49 | }
50 | return compareByBytes(bytesA, bytesB)
51 | }
52 |
53 | func compareByBytes(verA, verB []byte) int {
54 | for index, _ := range verA {
55 | if verA[index] > verB[index] {
56 | return VersionBig
57 | }
58 | if verA[index] < verB[index] {
59 | return VersionSmall
60 | }
61 |
62 | }
63 | return VersionEqual
64 | }
65 |
66 | func splitStrByNet(strV string) []string {
67 | return strings.Split(strV, ".")
68 | }
69 |
--------------------------------------------------------------------------------
/src/mixcli/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/mix-go/mixcli
2 |
3 | go 1.13
4 |
5 | require (
6 | github.com/cheggaaa/pb/v3 v3.0.8
7 | github.com/manifoldco/promptui v0.8.0
8 | github.com/mix-go/dotenv v1.1.15
9 | github.com/mix-go/xcli v1.1.20
10 | )
11 |
--------------------------------------------------------------------------------
/src/mixcli/logic/filesystem.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "github.com/mix-go/xcli"
7 | "io"
8 | "io/ioutil"
9 | "os"
10 | "path/filepath"
11 | "strings"
12 | )
13 |
14 | func CopyPath(src, dst string) bool {
15 | debug := xcli.App().Debug
16 | if debug {
17 | fmt.Println("")
18 | }
19 |
20 | src = strings.Replace(src, "\\", "/", -1)
21 | srcFileInfo := GetFileInfo(src)
22 | if srcFileInfo == nil || !srcFileInfo.IsDir() {
23 | return false
24 | }
25 |
26 | err := filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
27 | if err != nil {
28 | return err
29 | }
30 |
31 | path = strings.Replace(path, "\\", "/", -1)
32 | relationPath := strings.Replace(path, src, "", -1)
33 | dstPath := strings.TrimRight(strings.TrimRight(strings.Replace(dst, "\\", "/", -1), "/"), "\\") + relationPath
34 |
35 | if debug {
36 | fmt.Println(fmt.Sprintf(" Copy %s to %s", path, dstPath))
37 | }
38 |
39 | if !info.IsDir() {
40 | if CopyFile(path, dstPath) {
41 | return nil
42 | } else {
43 | return errors.New(path + " copy fail")
44 | }
45 | } else {
46 | if _, err := os.Stat(dstPath); err != nil {
47 | if os.IsNotExist(err) {
48 | if err := os.MkdirAll(dstPath, os.ModePerm); err != nil {
49 | return err
50 | } else {
51 | return nil
52 | }
53 | } else {
54 | return err
55 | }
56 | } else {
57 | return nil
58 | }
59 | }
60 | })
61 |
62 | if err != nil {
63 | return false
64 | }
65 | return true
66 | }
67 |
68 | func CopyFile(src, dst string) bool {
69 | if len(src) == 0 || len(dst) == 0 {
70 | return false
71 | }
72 | src = strings.Replace(src, "\\", "/", -1)
73 | srcFile, err := os.OpenFile(src, os.O_RDONLY, os.ModePerm)
74 | if err != nil {
75 | return false
76 | }
77 | defer srcFile.Close()
78 |
79 | dst = strings.Replace(dst, "\\", "/", -1)
80 | dstPathArr := strings.Split(dst, "/")
81 | dstPathArr = dstPathArr[0 : len(dstPathArr)-1]
82 | dstPath := strings.Join(dstPathArr, "/")
83 |
84 | dstFileInfo := GetFileInfo(dstPath)
85 | if dstFileInfo == nil {
86 | if err := os.MkdirAll(dstPath, os.ModePerm); err != nil {
87 | return false
88 | }
89 | }
90 |
91 | //这里要把O_TRUNC 加上,否则会出现新旧文件内容出现重叠现象
92 | dstFile, err := os.OpenFile(dst, os.O_CREATE|os.O_TRUNC|os.O_RDWR, os.ModePerm)
93 | if err != nil {
94 | return false
95 | }
96 | defer dstFile.Close()
97 |
98 | if _, err := io.Copy(dstFile, srcFile); err != nil {
99 | return false
100 | } else {
101 | return true
102 | }
103 | }
104 |
105 | func GetFileInfo(src string) os.FileInfo {
106 | if fileInfo, e := os.Stat(src); e != nil {
107 | if os.IsNotExist(e) {
108 | return nil
109 | }
110 | return nil
111 | } else {
112 | return fileInfo
113 | }
114 | }
115 |
116 | func ReadAll(filePth string) ([]byte, error) {
117 | f, err := os.Open(filePth)
118 | if err != nil {
119 | return nil, err
120 | }
121 | return ioutil.ReadAll(f)
122 | }
123 |
124 | // os.O_TRUNC 覆盖写入,不加则追加写入
125 | func WriteToFile(fileName string, content string) error {
126 | f, err := os.OpenFile(fileName, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644)
127 | if err != nil {
128 | return err
129 | } else {
130 | // offset
131 | //os.Truncate(filename, 0) //clear
132 | n, _ := f.Seek(0, io.SeekEnd)
133 | _, err = f.WriteAt([]byte(content), n)
134 | defer f.Close()
135 | }
136 | return err
137 | }
138 |
--------------------------------------------------------------------------------
/src/mixcli/logic/replace.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "path/filepath"
7 | "regexp"
8 | )
9 |
10 | func ReplaceAll(root, old, new string) error {
11 | err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
12 | if err != nil {
13 | return err
14 | }
15 |
16 | if !info.IsDir() {
17 | // 替换内容
18 | text, err := ReadAll(path)
19 | if err != nil {
20 | return err
21 | }
22 | str := string(text)
23 | reg := regexp.MustCompile(old)
24 | str = reg.ReplaceAllString(str, new)
25 | if err := WriteToFile(path, str); err != nil {
26 | return err
27 | }
28 | }
29 |
30 | return err
31 | })
32 | return err
33 | }
34 |
35 | func ReplaceMod(root string) error {
36 | path := fmt.Sprintf("%s/go.mod", root)
37 | text, err := ReadAll(path)
38 | if err != nil {
39 | return err
40 | }
41 | str := string(text)
42 | reg := regexp.MustCompile(`(replace \([\s\S]*?\))`)
43 | str = reg.ReplaceAllString(str, "")
44 | if err := WriteToFile(path, str); err != nil {
45 | return err
46 | }
47 | return nil
48 | }
49 |
50 | func ReplaceMain(root, old, new string) error {
51 | path := fmt.Sprintf("%s/main.go", root)
52 | text, err := ReadAll(path)
53 | if err != nil {
54 | return err
55 | }
56 | str := string(text)
57 | reg := regexp.MustCompile(old)
58 | str = reg.ReplaceAllString(str, new)
59 | if err := WriteToFile(path, str); err != nil {
60 | return err
61 | }
62 | return nil
63 | }
64 |
--------------------------------------------------------------------------------
/src/mixcli/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/mix-go/xcli"
5 | "github.com/mix-go/dotenv"
6 | "github.com/mix-go/mixcli/commands"
7 | )
8 |
9 | func main() {
10 | xcli.SetName("mixcli").
11 | SetVersion(commands.CLIVersion).
12 | SetDebug(dotenv.Getenv("APP_DEBUG").Bool(false))
13 | xcli.AddCommand(commands.Cmds...).Run()
14 | }
15 |
--------------------------------------------------------------------------------
/src/xcli/argv/argv.go:
--------------------------------------------------------------------------------
1 | package argv
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "path/filepath"
7 | "regexp"
8 | )
9 |
10 | // 命令行信息
11 | var (
12 | prog program
13 | cmd string
14 | )
15 |
16 | // Program 返回命令行程序信息
17 | func Program() *program {
18 | return &prog
19 | }
20 |
21 | // Command 返回当前命令信息
22 | func Command() string {
23 | return cmd
24 | }
25 |
26 | // 命令行程序信息
27 | type program struct {
28 | Path string `json:"path"`
29 | AbsPath string `json:"absPath"`
30 | Dir string `json:"dir"`
31 | ParentDir string `json:"parentDir"`
32 | File string `json:"file"`
33 | }
34 |
35 | // 创建命令行程序信息
36 | func newProgram() program {
37 | p := program{
38 | Path: os.Args[0],
39 | AbsPath: "",
40 | Dir: "",
41 | ParentDir: "",
42 | File: "",
43 | }
44 |
45 | absPath, err := filepath.Abs(os.Args[0])
46 | if err != nil {
47 | return p
48 | }
49 | p.AbsPath = absPath
50 |
51 | dirRaw, file := filepath.Split(absPath)
52 | dir := dirRaw[:len(dirRaw)-1]
53 | p.Dir = dir
54 | p.File = file
55 |
56 | parentDir, err := filepath.Abs(fmt.Sprintf("%s/../", dir))
57 | if err != nil {
58 | return p
59 | }
60 | p.ParentDir = parentDir
61 | return p
62 | }
63 |
64 | // 创建当前命令信息
65 | func newCommand(singleton bool) string {
66 | if len(os.Args) <= 1 || singleton {
67 | return ""
68 | }
69 | cmd := ""
70 | if ok, _ := regexp.MatchString(`^[a-zA-Z0-9_\-:]+$`, os.Args[1]); ok {
71 | cmd = os.Args[1]
72 | if cmd[:1] == "-" {
73 | cmd = ""
74 | }
75 | }
76 | return cmd
77 | }
78 |
--------------------------------------------------------------------------------
/src/xcli/argv/parse.go:
--------------------------------------------------------------------------------
1 | package argv
2 |
3 | // 初始化
4 | func init() {
5 | Parse()
6 | }
7 |
8 | // Parse 解析命令行参数
9 | func Parse(singleton ...bool) {
10 | var s bool
11 | switch len(singleton) {
12 | case 0:
13 | s = false
14 | default:
15 | s = singleton[0]
16 | }
17 |
18 | prog = newProgram()
19 | cmd = newCommand(s)
20 | }
21 |
--------------------------------------------------------------------------------
/src/xcli/command.go:
--------------------------------------------------------------------------------
1 | package xcli
2 |
3 | // Command
4 | type Command struct {
5 | // 命令名称
6 | Name string
7 | // 简短描述
8 | Short string
9 | // 详细描述
10 | Long string
11 | // 使用范例
12 | // 子命令:"Usage: %s %s [ARG...]"
13 | // 单命令:"Usage: %s [ARG...]"
14 | UsageFormat string
15 | // 选项
16 | Options []*Option
17 | // 执行
18 | RunF func()
19 | RunI RunI
20 | // 是否单命令
21 | Singleton bool
22 | // 是否为默认命令
23 | Default bool
24 |
25 | // handlers
26 | handlers []HandlerFunc
27 | }
28 |
29 | // AddOption
30 | func (t *Command) AddOption(options ...*Option) *Command {
31 | t.Options = append(t.Options, options...)
32 | return t
33 | }
34 |
35 | // Use
36 | func (t *Command) Use(h ...HandlerFunc) *Command {
37 | t.handlers = append(t.handlers, h...)
38 | return t
39 | }
40 |
41 | // RunI
42 | type RunI interface {
43 | Main()
44 | }
45 |
46 | // Option
47 | type Option struct {
48 | Names []string
49 | Usage string
50 | }
51 |
--------------------------------------------------------------------------------
/src/xcli/error.go:
--------------------------------------------------------------------------------
1 | package xcli
2 |
3 | // NotFoundError 未找到错误
4 | type NotFoundError struct {
5 | error
6 | }
7 |
8 | // NewNotFoundError 创建未找到错误
9 | func NewNotFoundError(err error) *NotFoundError {
10 | return &NotFoundError{err}
11 | }
12 |
13 | // UnsupportedError 不支持错误
14 | type UnsupportedError struct {
15 | error
16 | }
17 |
18 | // NewUnsupportedError 创建不支持错误
19 | func NewUnsupportedError(err error) *UnsupportedError {
20 | return &UnsupportedError{err}
21 | }
22 |
--------------------------------------------------------------------------------
/src/xcli/flag/arguments.go:
--------------------------------------------------------------------------------
1 | package flag
2 |
3 | var args arguments
4 |
5 | type arguments []string
6 |
7 | // Array 返回数组
8 | func (t *arguments) Array() []string {
9 | return *t
10 | }
11 |
12 | // Values 返回值
13 | func (t *arguments) Values() []*flagValue {
14 | args := *t
15 | var values []*flagValue
16 | for _, v := range args {
17 | values = append(values, &flagValue{v, true})
18 | }
19 | return values
20 | }
21 |
22 | // First 获取第一个参数
23 | func (t *arguments) First() *flagValue {
24 | args := *t
25 | if len(args) == 0 {
26 | return &flagValue{}
27 | }
28 | return &flagValue{args[0], true}
29 | }
30 |
31 | // Arguments 获取全部命令行参数
32 | func Arguments() *arguments {
33 | return &args
34 | }
35 |
--------------------------------------------------------------------------------
/src/xcli/flag/flag.go:
--------------------------------------------------------------------------------
1 | package flag
2 |
3 | import (
4 | "fmt"
5 | "strconv"
6 | )
7 |
8 | // Match 匹配参数名称
9 | func Match(names ...string) *flagValue {
10 | for _, name := range names {
11 | v, exist := find(name)
12 | if exist {
13 | return &flagValue{v, exist}
14 | }
15 | }
16 | return &flagValue{}
17 | }
18 |
19 | // 获取指定参数的值
20 | func find(name string) (string, bool) {
21 | key := ""
22 | if len(name) == 1 {
23 | key = fmt.Sprintf("-%s", name)
24 | } else {
25 | key = fmt.Sprintf("--%s", name)
26 | }
27 | if v, ok := Options().Map()[key]; ok {
28 | return v, true
29 | }
30 | return "", false
31 | }
32 |
33 | // 参数值
34 | type flagValue struct {
35 | v string
36 | exist bool
37 | }
38 |
39 | // String 转换为字符串
40 | func (t *flagValue) String(val ...string) string {
41 | d := ""
42 | if len(val) >= 1 {
43 | d = val[0]
44 | }
45 |
46 | if t.v == "" {
47 | return d
48 | }
49 |
50 | return t.v
51 | }
52 |
53 | // Bool 转换为布尔
54 | func (t *flagValue) Bool(val ...bool) bool {
55 | d := false
56 | if len(val) >= 1 {
57 | d = val[0]
58 | }
59 |
60 | if !t.exist {
61 | return d
62 | }
63 |
64 | switch t.v {
65 | case "false":
66 | return false
67 | default:
68 | return true
69 | }
70 | }
71 |
72 | // Int64 转换为整型
73 | func (t *flagValue) Int64(val ...int64) int64 {
74 | d := int64(0)
75 | if len(val) >= 1 {
76 | d = val[0]
77 | }
78 |
79 | if t.v == "" {
80 | return d
81 | }
82 |
83 | v, _ := strconv.ParseInt(t.v, 10, 64)
84 | return v
85 | }
86 |
87 | // Float64 转换为浮点
88 | func (t *flagValue) Float64(val ...float64) float64 {
89 | d := float64(0)
90 | if len(val) >= 1 {
91 | d = val[0]
92 | }
93 |
94 | if t.v == "" {
95 | return d
96 | }
97 |
98 | v, _ := strconv.ParseFloat(t.v, 64)
99 | return v
100 | }
101 |
--------------------------------------------------------------------------------
/src/xcli/flag/flag_test.go:
--------------------------------------------------------------------------------
1 | package flag
2 |
3 | import (
4 | "github.com/mix-go/xcli/argv"
5 | "github.com/stretchr/testify/assert"
6 | "os"
7 | "testing"
8 | )
9 |
10 | func TestSingle(t *testing.T) {
11 | a := assert.New(t)
12 |
13 | os.Args = []string{os.Args[0], "foo", "-a=a1", "-b", "--cd", "--ab=ab1", "--de", "de1", "-c", "c1", "--sw", "false"}
14 | argv.Parse()
15 | Parse()
16 |
17 | v1 := Match("a").String()
18 | a.Equal(v1, "a1")
19 |
20 | v2 := Match("b").Bool()
21 | a.Equal(v2, true)
22 |
23 | v3 := Match("cd").Bool()
24 | a.Equal(v3, true)
25 |
26 | v4 := Match("sw").Bool()
27 | a.Equal(v4, false)
28 |
29 | v5 := Match("ab", "").String()
30 | a.Equal(v5, "ab1")
31 |
32 | v6 := Match("de", "").String()
33 | a.Equal(v6, "de1")
34 |
35 | v7 := Match("c", "").String()
36 | a.Equal(v7, "c1")
37 | }
38 |
39 | func TestMatch(t *testing.T) {
40 | a := assert.New(t)
41 |
42 | os.Args = []string{os.Args[0], "foo", "-a=a1", "-b", "--bc", "--ab=ab1", "--de", "de1", "-c", "c1", "--sw", "false"}
43 | argv.Parse()
44 | Parse()
45 |
46 | v1 := Match("b", "bc").Bool()
47 | a.Equal(v1, true)
48 |
49 | v2 := Match("a", "ab").String()
50 | a.Equal(v2, "a1")
51 | }
52 |
53 | func TestNotFound(t *testing.T) {
54 | a := assert.New(t)
55 |
56 | os.Args = []string{os.Args[0]}
57 | argv.Parse()
58 | Parse()
59 |
60 | v1 := Match("cde").Bool()
61 | a.Equal(v1, false)
62 |
63 | v2 := Match("x").String()
64 | a.Equal(v2, "")
65 |
66 | v3 := Match("b", "bc").Bool()
67 | a.Equal(v3, false)
68 |
69 | v4 := Match("a", "ab").String()
70 | a.Equal(v4, "")
71 | }
72 |
73 | func TestOptions(t *testing.T) {
74 | a := assert.New(t)
75 |
76 | os.Args = []string{os.Args[0], "foo", "-a=a1", "-b", "-bc", "--cd", "--ab=ab1", "arg0", "--de", "de1", "-c", "c1", "--sw", "false", "arg1", "arg2"}
77 | argv.Parse()
78 | Parse()
79 |
80 | a.Equal(Options().Map(), map[string]string{"--ab": "ab1", "--cd": "", "--de": "de1", "--sw": "false", "-a": "a1", "-b": "", "-bc": "", "-c": "c1"})
81 | }
82 |
83 | func TestArguments(t *testing.T) {
84 | a := assert.New(t)
85 |
86 | os.Args = []string{os.Args[0], "foo", "-a=a1", "-b", "--cd", "--ab=ab1", "arg0", "--de", "de1", "-c", "c1", "--sw", "false", "arg1", "arg2"}
87 | argv.Parse()
88 | Parse()
89 |
90 | a.Equal(Arguments().Array(), []string{"arg0", "arg1", "arg2"})
91 |
92 | a.Equal(Arguments().First().String(), "arg0")
93 | a.Equal(Arguments().First().Bool(), true)
94 | }
95 |
--------------------------------------------------------------------------------
/src/xcli/flag/options.go:
--------------------------------------------------------------------------------
1 | package flag
2 |
3 | var opts options
4 |
5 | type options map[string]string
6 |
7 | // Map {"--foo": "bar"}
8 | func (t *options) Map() map[string]string {
9 | return *t
10 | }
11 |
12 | // Options 获取全部命令行选项
13 | func Options() *options {
14 | return &opts
15 | }
16 |
--------------------------------------------------------------------------------
/src/xcli/flag/parse.go:
--------------------------------------------------------------------------------
1 | package flag
2 |
3 | import (
4 | "github.com/mix-go/xcli/argv"
5 | "os"
6 | "regexp"
7 | "strings"
8 | )
9 |
10 | // 初始化
11 | func init() {
12 | Parse()
13 | }
14 |
15 | // Parse 解析参数
16 | func Parse() {
17 | var o = make(map[string]string, 0)
18 | var a []string
19 | s := 1
20 | if argv.Command() == "" {
21 | s = 0
22 | }
23 | ignore := ""
24 | for k, v := range os.Args {
25 | if k <= s {
26 | continue
27 | }
28 | name := v
29 | value := ""
30 | if strings.Contains(v, "=") {
31 | name = strings.Split(v, "=")[0]
32 | value = v[strings.Index(v, "=")+1:]
33 | }
34 | if (len(name) >= 1 && name[:1] == "-") || (len(name) >= 2 && name[:2] == "--") {
35 | if name[:1] == "-" && value == "" && len(os.Args)-1 >= k+1 && os.Args[k+1][:1] != "-" {
36 | next := os.Args[k+1]
37 | ok, _ := regexp.MatchString(`^[\S\s]+$`, next)
38 | if ok {
39 | value = next
40 | ignore = next
41 | }
42 | }
43 | } else {
44 | name = ""
45 | if v != ignore {
46 | a = append(a, v)
47 | }
48 | }
49 | if name != "" {
50 | o[name] = value
51 | }
52 | }
53 | opts = options(o)
54 | args = arguments(a)
55 | }
56 |
--------------------------------------------------------------------------------
/src/xcli/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/mix-go/xcli
2 |
3 | go 1.15
4 |
5 | require (
6 | github.com/davecgh/go-spew v1.1.1 // indirect
7 | github.com/stretchr/testify v1.7.0
8 | gopkg.in/yaml.v3 v3.0.0 // indirect
9 | )
10 |
--------------------------------------------------------------------------------
/src/xcli/go.sum:
--------------------------------------------------------------------------------
1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
5 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
6 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
7 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
8 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
10 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
11 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
12 | gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
13 | gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
14 |
--------------------------------------------------------------------------------
/src/xcli/process/daemon.go:
--------------------------------------------------------------------------------
1 | package process
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "github.com/mix-go/xcli"
7 | "os"
8 | "os/exec"
9 | "runtime"
10 | "strconv"
11 | )
12 |
13 | // Daemon 使当前进程蜕变为一个守护进程
14 | func Daemon() {
15 | var ok bool
16 | switch runtime.GOOS {
17 | case "darwin", "linux":
18 | ok = true
19 | case "windows":
20 | ok = false
21 | default:
22 | ok = true
23 | }
24 | if !ok {
25 | panic(xcli.NewUnsupportedError(fmt.Errorf("error: the current operating system does not support daemon execution")))
26 | }
27 |
28 | if getgid() != 1 {
29 | panic(fmt.Errorf("error: Daemon() can only be used in the main goroutine"))
30 | }
31 |
32 | // Getppid 父进程ID: 当父进程已经结束,在Unix中返回的ID是初始进程(1),在Windows中仍然是同一个进程ID,该进程ID有可能已经被其他进程占用
33 | if os.Getppid() != 1 {
34 | cmd := exec.Command(os.Args[0], os.Args[1:]...)
35 | if err := cmd.Start(); err != nil {
36 | panic(err)
37 | }
38 | os.Exit(0)
39 | }
40 | }
41 |
42 | // 获取协程id
43 | func getgid() uint64 {
44 | b := make([]byte, 64)
45 | runtime.Stack(b, false)
46 | b = bytes.TrimPrefix(b, []byte("goroutine "))
47 | b = b[:bytes.IndexByte(b, ' ')]
48 | n, _ := strconv.ParseUint(string(b), 10, 64)
49 | return n
50 | }
51 |
--------------------------------------------------------------------------------
/src/xdi/README.md:
--------------------------------------------------------------------------------
1 | > Produced by OpenMix: [https://openmix.org](https://openmix.org/mix-go)
2 |
3 | ## Mix XDI
4 |
5 | DI, IoC container
6 |
7 | ## Overview
8 |
9 | A library for creating objects and managing their dependencies. This library can be used for managing dependencies in a unified way, managing global objects, and refreshing dynamic configurations.
10 |
11 | ## Installation
12 |
13 | ```
14 | go get github.com/mix-go/xdi
15 | ```
16 |
17 | ## Quick start
18 |
19 | Create a singleton through dependency configuration
20 |
21 | ```go
22 | package main
23 |
24 | import (
25 | "github.com/mix-go/xdi"
26 | )
27 |
28 | type Foo struct {
29 | }
30 |
31 | func init() {
32 | obj := &xdi.Object{
33 | Name: "foo",
34 | New: func() (interface{}, error) {
35 | i := &Foo{}
36 | return i, nil
37 | },
38 | }
39 | if err := xdi.Provide(obj); err != nil {
40 | panic(err)
41 | }
42 | }
43 |
44 | func main() {
45 | var foo *Foo
46 | if err := xdi.Populate("foo", &foo); err != nil {
47 | panic(err)
48 | }
49 | // use foo
50 | }
51 | ```
52 |
53 | ## Reference
54 |
55 | Refer to another dependency configuration instance in the dependency configuration
56 |
57 | ```go
58 | package main
59 |
60 | import (
61 | "github.com/mix-go/xdi"
62 | )
63 |
64 | type Foo struct {
65 | Bar *Bar
66 | }
67 |
68 | type Bar struct {
69 | }
70 |
71 | func init() {
72 | objs := []*xdi.Object{
73 | {
74 | Name: "foo",
75 | New: func() (interface{}, error) {
76 | // reference bar
77 | var bar *Bar
78 | if err := xdi.Populate("bar", &bar); err != nil {
79 | return nil, err
80 | }
81 |
82 | i := &Foo{
83 | Bar: bar,
84 | }
85 | return i, nil
86 | },
87 | },
88 | {
89 | Name: "bar",
90 | New: func() (interface{}, error) {
91 | i := &Bar{}
92 | return i, nil
93 | },
94 | NewEverytime: true,
95 | },
96 | }
97 | if err := xdi.Provide(objs...); err != nil {
98 | panic(err)
99 | }
100 | }
101 |
102 | func main() {
103 | var foo *Foo
104 | if err := xdi.Populate("foo", &foo); err != nil {
105 | panic(err)
106 | }
107 | // use foo
108 | }
109 | ```
110 |
111 | ## Refresh singleton
112 |
113 | When the configuration information changes during program execution, `Refresh()` can refresh the singleton instance to switch to using the new configuration. It is commonly used in microservice configuration centers.
114 |
115 | ```go
116 | obj, err := xdi.DefaultContainer.Object("foo")
117 | if err != nil {
118 | panic(err)
119 | }
120 | if err := obj.Refresh(); err != nil {
121 | panic(err)
122 | }
123 | ```
124 |
125 | ## License
126 |
127 | Apache License Version 2.0, http://www.apache.org/licenses/
128 |
--------------------------------------------------------------------------------
/src/xdi/container.go:
--------------------------------------------------------------------------------
1 | package xdi
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "reflect"
7 | "sync"
8 | )
9 |
10 | var DefaultContainer *Container
11 |
12 | func init() {
13 | DefaultContainer = New()
14 | }
15 |
16 | func New() *Container {
17 | return &Container{}
18 | }
19 |
20 | func Provide(objects ...*Object) error {
21 | return DefaultContainer.Provide(objects...)
22 | }
23 |
24 | func Populate(name string, ptr interface{}) error {
25 | return DefaultContainer.Populate(name, ptr)
26 | }
27 |
28 | type Container struct {
29 | Objects []*Object
30 | tidyObjects sync.Map
31 | instances sync.Map
32 | }
33 |
34 | func (t *Container) Provide(objects ...*Object) error {
35 | for _, o := range objects {
36 | if _, ok := t.tidyObjects.Load(o.Name); ok {
37 | return fmt.Errorf("xdi: object '%s' existing", o.Name)
38 | }
39 | t.tidyObjects.Store(o.Name, o)
40 | }
41 | return nil
42 | }
43 |
44 | func (t *Container) Object(name string) (*Object, error) {
45 | v, ok := t.tidyObjects.Load(name)
46 | if !ok {
47 | return nil, fmt.Errorf("xdi: object '%s' not found", name)
48 | }
49 | obj := v.(*Object)
50 | return obj, nil
51 | }
52 |
53 | func (t *Container) Populate(name string, ptr interface{}) (err error) {
54 | defer func() {
55 | if e := recover(); e != nil {
56 | err = errors.New("xdi: " + fmt.Sprint(e))
57 | }
58 | }()
59 |
60 | obj, err := t.Object(name)
61 | if err != nil {
62 | return err
63 | }
64 |
65 | if reflect.ValueOf(ptr).Kind() != reflect.Ptr {
66 | return errors.New("xdi: argument can only be pointer type")
67 | }
68 |
69 | ptrCopy := func(ptr, newValue interface{}) {
70 | v := reflect.ValueOf(ptr)
71 | if v.Kind() == reflect.Ptr {
72 | v = v.Elem()
73 | }
74 | v.Set(reflect.ValueOf(newValue))
75 | }
76 |
77 | if !obj.NewEverytime {
78 | refresher := &obj.refresher
79 | if p, ok := t.instances.Load(name); ok && !refresher.status() {
80 | ptrCopy(ptr, p)
81 | return nil
82 | }
83 |
84 | // 处理并发穿透
85 | // 之前是采用 LoadOrStore 重复创建但只保存一个
86 | // 现在采用 Mutex 直接锁死只创建一次
87 | obj.mutex.Lock()
88 | defer obj.mutex.Unlock()
89 | if p, ok := t.instances.Load(name); ok && !refresher.status() {
90 | ptrCopy(ptr, p)
91 | return nil
92 | }
93 |
94 | v, err := obj.New()
95 | if err != nil {
96 | return err
97 | }
98 | t.instances.Store(name, v)
99 | refresher.off()
100 | ptrCopy(ptr, v)
101 | return nil
102 | } else {
103 | v, err := obj.New()
104 | if err != nil {
105 | return err
106 | }
107 | ptrCopy(ptr, v)
108 | return nil
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/xdi/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/mix-go/xdi
2 |
3 | go 1.13
4 |
5 | require github.com/stretchr/testify v1.6.1
6 |
--------------------------------------------------------------------------------
/src/xdi/go.sum:
--------------------------------------------------------------------------------
1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
5 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
6 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
7 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
8 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
10 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
11 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
12 |
--------------------------------------------------------------------------------
/src/xdi/object.go:
--------------------------------------------------------------------------------
1 | package xdi
2 |
3 | import (
4 | "fmt"
5 | "sync"
6 | )
7 |
8 | type Object struct {
9 | Name string
10 | // 创建对象的闭包
11 | New func() (interface{}, error)
12 | // 每次都创建新的对象
13 | NewEverytime bool
14 |
15 | refresher refresher
16 | mutex sync.Mutex
17 | }
18 |
19 | func (t *Object) Refresh() error {
20 | if t.NewEverytime {
21 | return fmt.Errorf("xdi: '%s' is NewEverytime, unable to refresh", t.Name)
22 | }
23 | t.refresher.on()
24 | return nil
25 | }
26 |
27 | type refresher struct {
28 | val bool
29 | }
30 |
31 | func (t *refresher) on() {
32 | t.val = true
33 | }
34 |
35 | func (t *refresher) off() {
36 | t.val = false
37 | }
38 |
39 | func (t *refresher) status() bool {
40 | return t.val
41 | }
42 |
--------------------------------------------------------------------------------
/src/xhttp/build.go:
--------------------------------------------------------------------------------
1 | package xhttp
2 |
3 | import (
4 | "encoding/json"
5 | "net/url"
6 | )
7 |
8 | func BuildJSON(v interface{}) Body {
9 | b, _ := json.Marshal(v)
10 | return Body(b)
11 | }
12 |
13 | func BuildQuery(m map[string]string) Body {
14 | values := &url.Values{}
15 | for k, v := range m {
16 | values.Add(k, v)
17 | }
18 | return Body(values.Encode())
19 | }
20 |
--------------------------------------------------------------------------------
/src/xhttp/client.go:
--------------------------------------------------------------------------------
1 | package xhttp
2 |
3 | import (
4 | "bytes"
5 | "errors"
6 | "github.com/mix-go/xutil/xconv"
7 | "io"
8 | "net/http"
9 | "net/url"
10 | "time"
11 | )
12 |
13 | var (
14 | ErrAbortRetry = errors.New("xhttp: abort further retries")
15 |
16 | ErrShutdown = errors.New("xhttp: service is currently being shutdown and will no longer accept new requests")
17 | )
18 |
19 | var DefaultClient = &Client{}
20 |
21 | func NewRequest(method string, u string, opts ...RequestOption) (*Response, error) {
22 | return DefaultClient.NewRequest(method, u, opts...)
23 | }
24 |
25 | type Request struct {
26 | *http.Request
27 |
28 | Body Body
29 |
30 | // Number of retries
31 | RetryAttempts int
32 | }
33 |
34 | type Response struct {
35 | *http.Response
36 |
37 | Body Body
38 | }
39 |
40 | type Body []byte
41 |
42 | func (t Body) String() string {
43 | return xconv.BytesToString(t)
44 | }
45 |
46 | type Client struct {
47 | http.Client
48 | }
49 |
50 | func newRequest(r *http.Request) *Request {
51 | if r == nil {
52 | return nil
53 | }
54 | req := &Request{
55 | Request: r,
56 | }
57 |
58 | if r.Body == nil {
59 | return req
60 | }
61 |
62 | defer r.Body.Close()
63 | b, err := io.ReadAll(r.Body)
64 | if err != nil {
65 | return req
66 | }
67 | req.Body = b
68 | r.Body = io.NopCloser(bytes.NewReader(b))
69 | return req
70 | }
71 |
72 | func newResponse(r *http.Response) *Response {
73 | if r == nil {
74 | return nil
75 | }
76 | resp := &Response{
77 | Response: r,
78 | }
79 |
80 | if r.Body == nil {
81 | return resp
82 | }
83 |
84 | defer r.Body.Close()
85 | b, err := io.ReadAll(r.Body)
86 | if err != nil {
87 | return resp
88 | }
89 | resp.Body = b
90 | return resp
91 | }
92 |
93 | func (t *Client) NewRequest(method string, u string, opts ...RequestOption) (*Response, error) {
94 | o := mergeOptions(opts)
95 | URL, err := url.Parse(u)
96 | if err != nil {
97 | return nil, err
98 | }
99 | req := &http.Request{
100 | Method: method,
101 | URL: URL,
102 | Header: o.Header,
103 | }
104 | if o.Body != nil {
105 | req.Body = io.NopCloser(bytes.NewReader(o.Body))
106 | }
107 |
108 | if o.RetryOptions != nil {
109 | xReq := newRequest(req)
110 | return doRetry(o, func() (*Response, error) {
111 | xReq.RetryAttempts++
112 | return t.doRequest(xReq, o)
113 | })
114 | }
115 | return t.doRequest(newRequest(req), o)
116 | }
117 |
118 | func (t *Client) SendRequest(req *http.Request, opts ...RequestOption) (*Response, error) {
119 | o := mergeOptions(opts)
120 |
121 | if o.RetryOptions != nil {
122 | xReq := newRequest(req)
123 | return doRetry(o, func() (*Response, error) {
124 | xReq.RetryAttempts++
125 | return t.doRequest(xReq, o)
126 | })
127 | }
128 | return t.doRequest(newRequest(req), o)
129 | }
130 |
131 | func (t *Client) doRequest(xReq *Request, opts *RequestOptions) (*Response, error) {
132 | var finalHandler HandlerFunc = func(xReq *Request, opts *RequestOptions) (*Response, error) {
133 | if !shutdownController.BeginRequest() {
134 | return nil, ErrShutdown
135 | }
136 | defer shutdownController.EndRequest()
137 |
138 | cli := t
139 | cli.Timeout = opts.Timeout
140 | startTime := time.Now()
141 | r, err := cli.Do(xReq.Request)
142 | if err != nil {
143 | t.doDebug(opts, time.Now().Sub(startTime), xReq, nil, err)
144 | return nil, err
145 | }
146 | xResp := newResponse(r)
147 | t.doDebug(opts, time.Now().Sub(startTime), xReq, xResp, nil)
148 | return xResp, nil
149 | }
150 |
151 | for i := len(opts.Middlewares) - 1; i >= 0; i-- {
152 | finalHandler = opts.Middlewares[i](finalHandler)
153 | }
154 |
155 | return finalHandler(xReq, opts)
156 | }
157 |
--------------------------------------------------------------------------------
/src/xhttp/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/mix-go/xhttp
2 |
3 | go 1.22.1
4 |
5 | require (
6 | github.com/avast/retry-go v3.0.0+incompatible
7 | github.com/mix-go/xutil v1.1.18
8 | github.com/stretchr/testify v1.9.0
9 | )
10 |
11 | require (
12 | github.com/davecgh/go-spew v1.1.1 // indirect
13 | github.com/pmezard/go-difflib v1.0.0 // indirect
14 | gopkg.in/yaml.v3 v3.0.1 // indirect
15 | )
16 |
--------------------------------------------------------------------------------
/src/xhttp/go.sum:
--------------------------------------------------------------------------------
1 | github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0=
2 | github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
3 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5 | github.com/mix-go/xutil v1.1.18 h1:ubl1duPTsS21gvVh3JQ9S7joxlDqSTdaD9iCiT/Gmnc=
6 | github.com/mix-go/xutil v1.1.18/go.mod h1:serVG1ZJiTDDN68qmMzULX1DvM5ZiZ+7F45lLKpRxU0=
7 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
8 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
9 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
10 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
11 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
12 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
13 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
14 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
15 |
--------------------------------------------------------------------------------
/src/xhttp/log.go:
--------------------------------------------------------------------------------
1 | package xhttp
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | type Log struct {
8 | Duration time.Duration `json:"duration"`
9 | Request *Request `json:"request"` // The Request.RetryAttempts field records the number of retry attempts
10 | Response *Response `json:"response"` // If request error this field is equal to nil
11 | Error error `json:"error"`
12 | }
13 |
14 | type DebugFunc func(l *Log)
15 |
16 | func (t *Client) doDebug(opts *RequestOptions, duration time.Duration, req *Request, resp *Response, err error) {
17 | if opts.DebugFunc == nil {
18 | return
19 | }
20 |
21 | l := &Log{
22 | Duration: duration,
23 | Request: req,
24 | Response: resp,
25 | Error: err,
26 | }
27 | opts.DebugFunc(l)
28 | }
29 |
--------------------------------------------------------------------------------
/src/xhttp/middleware.go:
--------------------------------------------------------------------------------
1 | package xhttp
2 |
3 | type Middleware func(next HandlerFunc) HandlerFunc
4 |
5 | type HandlerFunc func(*Request, *RequestOptions) (*Response, error)
6 |
--------------------------------------------------------------------------------
/src/xhttp/options.go:
--------------------------------------------------------------------------------
1 | package xhttp
2 |
3 | import (
4 | "github.com/avast/retry-go"
5 | "github.com/mix-go/xutil/xconv"
6 | "net/http"
7 | "time"
8 | )
9 |
10 | var DefaultOptions = newDefaultOptions()
11 |
12 | func newDefaultOptions() RequestOptions {
13 | return RequestOptions{
14 | Header: http.Header{},
15 | Timeout: time.Second * 5,
16 | }
17 | }
18 |
19 | type RequestOptions struct {
20 | Header http.Header
21 |
22 | Body Body
23 |
24 | // 默认: time.Second * 5
25 | Timeout time.Duration
26 |
27 | DebugFunc DebugFunc
28 |
29 | // Retry
30 | RetryIfFunc RetryIfFunc
31 | RetryOptions []retry.Option
32 |
33 | Middlewares []Middleware
34 | }
35 |
36 | func mergeOptions(opts []RequestOption) *RequestOptions {
37 | cp := DefaultOptions // copy
38 | for _, o := range opts {
39 | o.apply(&cp)
40 | }
41 | return &cp
42 | }
43 |
44 | type RequestOption interface {
45 | apply(*RequestOptions)
46 | }
47 |
48 | type funcRequestOption struct {
49 | f func(*RequestOptions)
50 | }
51 |
52 | func (fdo *funcRequestOption) apply(do *RequestOptions) {
53 | fdo.f(do)
54 | }
55 |
56 | func WithHeader(header http.Header) RequestOption {
57 | return &funcRequestOption{func(opts *RequestOptions) {
58 | opts.Header = header
59 | }}
60 | }
61 |
62 | func WithContentType(contentType string) RequestOption {
63 | return &funcRequestOption{func(opts *RequestOptions) {
64 | opts.Header.Set("Content-Type", contentType)
65 | }}
66 | }
67 |
68 | func WithTimeout(timeout time.Duration) RequestOption {
69 | return &funcRequestOption{func(opts *RequestOptions) {
70 | opts.Timeout = timeout
71 | }}
72 | }
73 |
74 | func WithBody(body Body) RequestOption {
75 | return &funcRequestOption{func(opts *RequestOptions) {
76 | opts.Body = body
77 | }}
78 | }
79 |
80 | func WithBodyBytes(body []byte) RequestOption {
81 | return &funcRequestOption{func(opts *RequestOptions) {
82 | opts.Body = body
83 | }}
84 | }
85 |
86 | func WithBodyString(body string) RequestOption {
87 | return &funcRequestOption{func(opts *RequestOptions) {
88 | opts.Body = xconv.StringToBytes(body)
89 | }}
90 | }
91 |
92 | func WithDebugFunc(f DebugFunc) RequestOption {
93 | return &funcRequestOption{func(opts *RequestOptions) {
94 | opts.DebugFunc = f
95 | }}
96 | }
97 |
98 | func WithRetry(f RetryIfFunc, opts ...retry.Option) RequestOption {
99 | return &funcRequestOption{func(o *RequestOptions) {
100 | o.RetryIfFunc = f
101 | o.RetryOptions = opts
102 | }}
103 | }
104 |
105 | func WithMiddleware(middlewares ...Middleware) RequestOption {
106 | return &funcRequestOption{func(o *RequestOptions) {
107 | o.Middlewares = append(o.Middlewares, middlewares...)
108 | }}
109 | }
110 |
--------------------------------------------------------------------------------
/src/xhttp/retry.go:
--------------------------------------------------------------------------------
1 | package xhttp
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "github.com/avast/retry-go"
7 | )
8 |
9 | type RetryIfFunc func(*Response, error) error
10 |
11 | type Error []error
12 |
13 | func (t Error) Error() string {
14 | var logWithNumber []error
15 | for i, err := range t {
16 | logWithNumber = append(logWithNumber, fmt.Errorf("#%d: %s", i+1, err))
17 | }
18 | return errors.Join(logWithNumber...).Error()
19 | }
20 |
21 | func (t Error) HasAbortRetry() bool {
22 | for _, err := range t {
23 | if errors.Is(err, ErrAbortRetry) {
24 | return true
25 | }
26 | }
27 | return false
28 | }
29 |
30 | func doRetry(opts *RequestOptions, f func() (*Response, error)) (*Response, error) {
31 | var resp *Response
32 | var err error
33 | var errorLog Error
34 | err = retry.Do(
35 | func() error {
36 | resp, err = f()
37 | if opts.RetryIfFunc != nil {
38 | err := opts.RetryIfFunc(resp, err)
39 | if err != nil {
40 | errorLog = append(errorLog, err)
41 | if errors.Is(err, ErrAbortRetry) {
42 | return nil
43 | }
44 | }
45 | return err
46 | }
47 | if err != nil {
48 | return err
49 | }
50 | return nil
51 | },
52 | opts.RetryOptions...,
53 | )
54 | if err != nil {
55 | return nil, err
56 | }
57 | if errorLog.HasAbortRetry() {
58 | return nil, &errorLog
59 | }
60 | return resp, nil
61 | }
62 |
--------------------------------------------------------------------------------
/src/xhttp/shutdownctrl.go:
--------------------------------------------------------------------------------
1 | package xhttp
2 |
3 | import (
4 | "context"
5 | "sync"
6 | "sync/atomic"
7 | )
8 |
9 | var shutdownController = NewShutdownController()
10 |
11 | type ShutdownController struct {
12 | shutdownFlag int32 // 原子标记,表示是否进入停机状态
13 | waitGroup sync.WaitGroup // 用于等待所有处理中的请求完成
14 | }
15 |
16 | func NewShutdownController() *ShutdownController {
17 | return &ShutdownController{}
18 | }
19 |
20 | func (sc *ShutdownController) BeginRequest() bool {
21 | if atomic.LoadInt32(&sc.shutdownFlag) == 1 {
22 | return false
23 | }
24 | sc.waitGroup.Add(1)
25 | return true
26 | }
27 |
28 | func (sc *ShutdownController) EndRequest() {
29 | sc.waitGroup.Done()
30 | }
31 |
32 | func (sc *ShutdownController) InitiateShutdown(ctx context.Context) {
33 | atomic.StoreInt32(&sc.shutdownFlag, 1)
34 |
35 | done := make(chan struct{})
36 | go func() {
37 | sc.waitGroup.Wait()
38 | close(done)
39 | }()
40 | select {
41 | case <-ctx.Done(): // Timeout or ctx canceled, stop waiting.
42 | return
43 | case <-done: // waitGroup has completed
44 | return
45 | }
46 | }
47 |
48 | func Shutdown(ctx context.Context) {
49 | shutdownController.InitiateShutdown(ctx)
50 | }
51 |
--------------------------------------------------------------------------------
/src/xrpc/api/example.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package api;
4 | option go_package = "./;api";
5 |
6 | import "google/api/annotations.proto";
7 |
8 | service AppMessages {
9 | rpc Send(SendRequest) returns (SendResponse) {
10 | option (google.api.http) = {
11 | post: "/v1/send_message"
12 | body: "*"
13 | };
14 | }
15 | }
16 |
17 | message SendRequest {
18 | string text = 1;
19 | string parse_mode = 2;
20 | }
21 |
22 | message SendResponse {
23 | int64 message_id = 1;
24 | }
25 |
--------------------------------------------------------------------------------
/src/xrpc/certificates/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
--------------------------------------------------------------------------------
/src/xrpc/generate-pb.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -ex
3 |
4 | BASEPATH=$(pwd)
5 | GOPATH=$(go env GOPATH)
6 | repos=$(find . -name "*.proto")
7 | for FILE in $repos; do
8 | protoc -I . -I $GOPATH/src --go_out=paths=source_relative:$BASEPATH --go-grpc_out=paths=source_relative:$BASEPATH --grpc-gateway_out=paths=source_relative:$BASEPATH $FILE
9 | done
10 |
--------------------------------------------------------------------------------
/src/xrpc/generate-rsa.cnf:
--------------------------------------------------------------------------------
1 | [ req ]
2 | #default_bits = 2048
3 | #default_md = sha256
4 | #default_keyfile = privkey.pem
5 | distinguished_name = req_distinguished_name
6 | attributes = req_attributes
7 |
8 | [ req_distinguished_name ]
9 | countryName = Country Name (2 letter code)
10 | countryName_min = 2
11 | countryName_max = 2
12 | stateOrProvinceName = State or Province Name (full name)
13 | localityName = Locality Name (eg, city)
14 | 0.organizationName = Organization Name (eg, company)
15 | organizationalUnitName = Organizational Unit Name (eg, section)
16 | commonName = Common Name (e.g. server FQDN or YOUR name)
17 | commonName_max = 64
18 | emailAddress = Email Address
19 | emailAddress_max = 64
20 |
21 | [ req_attributes ]
22 | challengePassword = A challenge password
23 | challengePassword_min = 4
24 | challengePassword_max = 20
25 |
26 | [ SAN ]
27 | subjectAltName = DNS:localhost,IP:127.0.0.1,DNS:*.openmix.org
28 |
--------------------------------------------------------------------------------
/src/xrpc/generate-rsa.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -ex
3 |
4 | openssl genrsa -out certificates/ca.key 2048
5 | openssl req -new -x509 -key certificates/ca.key -out certificates/ca.pem -days 3650 -subj "/CN=localhost"
6 |
7 | openssl ecparam -genkey -name secp384r1 -out certificates/server.key
8 | openssl req -new -key certificates/server.key -out certificates/server.csr -config generate-rsa.cnf -extensions SAN
9 | openssl x509 -req -sha256 -CA certificates/ca.pem -CAkey certificates/ca.key -CAcreateserial -days 3650 -in certificates/server.csr -out certificates/server.pem -extfile generate-rsa.cnf -extensions SAN
10 |
11 | openssl ecparam -genkey -name secp384r1 -out certificates/client.key
12 | openssl req -new -key certificates/client.key -out certificates/client.csr -config generate-rsa.cnf -extensions SAN
13 | openssl x509 -req -sha256 -CA certificates/ca.pem -CAkey certificates/ca.key -CAcreateserial -days 3650 -in certificates/client.csr -out certificates/client.pem -extfile generate-rsa.cnf -extensions SAN
14 |
15 | # Common Name (e.g. server FQDN or YOUR name) []:localhost
16 |
--------------------------------------------------------------------------------
/src/xrpc/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/mix-go/xrpc
2 |
3 | go 1.20
4 |
5 | require (
6 | github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.0-rc.5
7 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2
8 | google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1
9 | google.golang.org/grpc v1.56.3
10 | google.golang.org/protobuf v1.33.0
11 | )
12 |
13 | require (
14 | github.com/golang/protobuf v1.5.3 // indirect
15 | github.com/stretchr/testify v1.8.0 // indirect
16 | golang.org/x/net v0.23.0 // indirect
17 | golang.org/x/sys v0.18.0 // indirect
18 | golang.org/x/text v0.14.0 // indirect
19 | )
20 |
--------------------------------------------------------------------------------
/src/xrpc/go.sum:
--------------------------------------------------------------------------------
1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4 | github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE=
5 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
6 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
7 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
8 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
9 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
10 | github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.0-rc.5 h1:3IZOAnD058zZllQTZNBioTlrzrBG/IjpiZ133IEtusM=
11 | github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.0-rc.5/go.mod h1:xbKERva94Pw2cPen0s79J3uXmGzbbpDYFBFDlZ4mV/w=
12 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2 h1:gDLXvp5S9izjldquuoAhDzccbskOL6tDC5jMSyx3zxE=
13 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2/go.mod h1:7pdNwVWBBHGiCxa9lAszqCJMbfTISJ7oMftp8+UGV08=
14 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
15 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
16 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
17 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
18 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
19 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
20 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
21 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
22 | golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
23 | golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
24 | golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
25 | golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
26 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
27 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
28 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
29 | google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
30 | google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
31 | google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc=
32 | google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
33 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
34 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
35 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
36 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
37 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
38 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
39 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
40 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
41 |
--------------------------------------------------------------------------------
/src/xrpc/rpcclient.go:
--------------------------------------------------------------------------------
1 | package xrpc
2 |
3 | import (
4 | "context"
5 | "google.golang.org/grpc"
6 | "google.golang.org/grpc/credentials/insecure"
7 | "google.golang.org/grpc/keepalive"
8 | "time"
9 | )
10 |
11 | var (
12 | DialTimeout = 5 * time.Second
13 |
14 | CallTimeout = 5 * time.Second
15 | )
16 |
17 | func NewGrpcClient(addr string, opts ...grpc.DialOption) (*grpc.ClientConn, error) {
18 | ctx, cancel := context.WithTimeout(context.Background(), DialTimeout)
19 | defer cancel()
20 | dialOpts := []grpc.DialOption{
21 | grpc.WithBlock(),
22 | grpc.WithTransportCredentials(insecure.NewCredentials()),
23 | grpc.WithKeepaliveParams(keepalive.ClientParameters{
24 | Time: 10 * time.Second,
25 | Timeout: time.Second,
26 | PermitWithoutStream: true,
27 | }),
28 | }
29 | if len(opts) > 0 {
30 | dialOpts = append(dialOpts, opts...)
31 | }
32 | c, err := grpc.DialContext(ctx, addr, dialOpts...)
33 | if err != nil {
34 | return nil, err
35 | }
36 | return c, nil
37 | }
38 |
--------------------------------------------------------------------------------
/src/xrpc/rpcclient_test.go:
--------------------------------------------------------------------------------
1 | package xrpc
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | pb "github.com/mix-go/xrpc/api"
7 | "google.golang.org/grpc"
8 | "google.golang.org/grpc/credentials"
9 | "io"
10 | "log"
11 | "net/http"
12 | "os"
13 | "strings"
14 | "testing"
15 | )
16 |
17 | func TestNewGrpcClient(t *testing.T) {
18 | conn, err := NewGrpcClient("127.0.0.1:50000")
19 | if err != nil {
20 | log.Fatal(err)
21 | }
22 | defer conn.Close()
23 | client := pb.NewAppMessagesClient(conn)
24 | ctx, _ := context.WithTimeout(context.Background(), CallTimeout)
25 | resp, err := client.Send(ctx, &pb.SendRequest{
26 | Text: "123456789",
27 | })
28 | fmt.Println(resp, err)
29 | }
30 |
31 | func TestNewGrpcTLSClient(t *testing.T) {
32 | dir, _ := os.Getwd()
33 | tlsConf, err := LoadClientTLSConfig(dir+"/certificates/ca.pem", dir+"/certificates/client.pem", dir+"/certificates/client.key")
34 | if err != nil {
35 | log.Fatal(err)
36 | }
37 | conn, err := NewGrpcClient("127.0.0.1:50000", grpc.WithTransportCredentials(credentials.NewTLS(tlsConf)))
38 | if err != nil {
39 | log.Fatal(err)
40 | }
41 | defer conn.Close()
42 | client := pb.NewAppMessagesClient(conn)
43 | ctx, _ := context.WithTimeout(context.Background(), CallTimeout)
44 | resp, err := client.Send(ctx, &pb.SendRequest{
45 | Text: "123456789",
46 | })
47 | fmt.Println(resp, err)
48 | }
49 |
50 | func TestNewGatewayClient(t *testing.T) {
51 | client := &http.Client{}
52 | resp, err := client.Post("http://127.0.0.1:50001/v1/send_message", "application/json", strings.NewReader(`{"order_number":"123456789"}`))
53 | if err != nil {
54 | log.Fatal(err)
55 | }
56 | b, _ := io.ReadAll(resp.Body)
57 | fmt.Println(string(b), err)
58 | }
59 |
60 | func TestNewGatewayTLSClient(t *testing.T) {
61 | dir, _ := os.Getwd()
62 | tlsConf, err := LoadClientTLSConfig(dir+"/certificates/ca.pem", dir+"/certificates/client.pem", dir+"/certificates/client.key")
63 | if err != nil {
64 | log.Fatal(err)
65 | }
66 | client := &http.Client{
67 | Transport: &http.Transport{
68 | TLSClientConfig: tlsConf,
69 | },
70 | }
71 | defer client.CloseIdleConnections()
72 | resp, err := client.Post("https://127.0.0.1:50001/v1/send_message", "application/json", strings.NewReader(`{"order_number":"123456789"}`))
73 | b, _ := io.ReadAll(resp.Body)
74 | fmt.Println(string(b), err)
75 | }
76 |
--------------------------------------------------------------------------------
/src/xrpc/rpcserver_test.go:
--------------------------------------------------------------------------------
1 | package xrpc
2 |
3 | import (
4 | "context"
5 | "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
6 | pb "github.com/mix-go/xrpc/api"
7 | "google.golang.org/grpc"
8 | "log"
9 | "os"
10 | "testing"
11 | )
12 |
13 | type service struct {
14 | pb.UnimplementedAppMessagesServer
15 | }
16 |
17 | func (t *service) Send(ctx context.Context, in *pb.SendRequest) (*pb.SendResponse, error) {
18 | log.Printf("%+v", in)
19 | return &pb.SendResponse{
20 | MessageId: 1,
21 | }, nil
22 | }
23 |
24 | func TestRPCServer_Serve(t *testing.T) {
25 | s := &RpcServer{
26 | GrpcServer: &GrpcServer{
27 | Addr: "0.0.0.0:50000",
28 | Registrar: func(s *grpc.Server) {
29 | pb.RegisterAppMessagesServer(s, &service{})
30 | },
31 | },
32 | GatewayServer: &GatewayServer{ // Optional
33 | Addr: "0.0.0.0:50001",
34 | Registrar: func(mux *runtime.ServeMux, conn *grpc.ClientConn) {
35 | pb.RegisterAppMessagesHandler(context.Background(), mux, conn)
36 | },
37 | },
38 | Logger: nil,
39 | }
40 | s.Serve()
41 | // s.Shutdown()
42 | }
43 |
44 | func TestRpcServerTLS_Serve(t *testing.T) {
45 | dir, _ := os.Getwd()
46 | tlsConf, err := LoadServerTLSConfig(dir+"/certificates/ca.pem", dir+"/certificates/server.pem", dir+"/certificates/server.key")
47 | if err != nil {
48 | log.Fatal(err)
49 | }
50 | tlsClientConf, err := LoadClientTLSConfig(dir+"/certificates/ca.pem", dir+"/certificates/client.pem", dir+"/certificates/client.key")
51 | if err != nil {
52 | log.Fatal(err)
53 | }
54 | s := &RpcServer{
55 | GrpcServer: &GrpcServer{
56 | Addr: "0.0.0.0:50000",
57 | Registrar: func(s *grpc.Server) {
58 | pb.RegisterAppMessagesServer(s, &service{})
59 | },
60 | },
61 | GatewayServer: &GatewayServer{ // Optional
62 | Addr: "0.0.0.0:50001",
63 | Registrar: func(mux *runtime.ServeMux, conn *grpc.ClientConn) {
64 | pb.RegisterAppMessagesHandler(context.Background(), mux, conn)
65 | },
66 | },
67 | Logger: nil,
68 | TLSConfig: tlsConf,
69 | TLSClientConfig: tlsClientConf,
70 | }
71 | s.Serve()
72 | }
73 |
--------------------------------------------------------------------------------
/src/xrpc/tls.go:
--------------------------------------------------------------------------------
1 | package xrpc
2 |
3 | import (
4 | "crypto/tls"
5 | "crypto/x509"
6 | "fmt"
7 | "os"
8 | )
9 |
10 | // LoadServerTLSConfig This is self-signed TLS
11 | // Normal TLS use credentials.NewServerTLSFromFile
12 | func LoadServerTLSConfig(caFile, certFile, keyFile string) (*tls.Config, error) {
13 | caPEMBlock, err := os.ReadFile(caFile)
14 | if err != nil {
15 | return nil, err
16 | }
17 | certPEMBlock, err := os.ReadFile(certFile)
18 | if err != nil {
19 | return nil, err
20 | }
21 | keyPEMBlock, err := os.ReadFile(keyFile)
22 | if err != nil {
23 | return nil, err
24 | }
25 | return NewServerTLSConfig(caPEMBlock, certPEMBlock, keyPEMBlock)
26 | }
27 |
28 | // NewServerTLSConfig This is self-signed TLS
29 | func NewServerTLSConfig(ca, cert, key []byte) (*tls.Config, error) {
30 | certificate, err := tls.X509KeyPair(cert, key)
31 | if err != nil {
32 | return nil, err
33 | }
34 | caCertPool := x509.NewCertPool()
35 | if ok := caCertPool.AppendCertsFromPEM(ca); !ok {
36 | return nil, fmt.Errorf("x509: certPool.AppendCertsFromPEM failed")
37 | }
38 | return &tls.Config{
39 | Certificates: []tls.Certificate{certificate},
40 | ClientAuth: tls.RequireAndVerifyClientCert,
41 | ClientCAs: caCertPool,
42 | }, nil
43 | }
44 |
45 | // LoadClientTLSConfig This is self-signed TLS
46 | // Normal TLS use credentials.NewClientTLSFromFile
47 | func LoadClientTLSConfig(caFile, certFile, keyFile string) (*tls.Config, error) {
48 | caPEMBlock, err := os.ReadFile(caFile)
49 | if err != nil {
50 | return nil, err
51 | }
52 | certPEMBlock, err := os.ReadFile(certFile)
53 | if err != nil {
54 | return nil, err
55 | }
56 | keyPEMBlock, err := os.ReadFile(keyFile)
57 | if err != nil {
58 | return nil, err
59 | }
60 | return NewClientTLSConfig(caPEMBlock, certPEMBlock, keyPEMBlock)
61 | }
62 |
63 | // NewClientTLSConfig This is self-signed TLS
64 | func NewClientTLSConfig(ca, cert, key []byte) (*tls.Config, error) {
65 | certificate, err := tls.X509KeyPair(cert, key)
66 | if err != nil {
67 | return nil, err
68 | }
69 | caCertPool := x509.NewCertPool()
70 | if ok := caCertPool.AppendCertsFromPEM(ca); !ok {
71 | return nil, fmt.Errorf("x509: certPool.AppendCertsFromPEM failed")
72 | }
73 | return &tls.Config{
74 | Certificates: []tls.Certificate{certificate},
75 | ServerName: "",
76 | RootCAs: caCertPool,
77 | }, nil
78 | }
79 |
--------------------------------------------------------------------------------
/src/xsql/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/mix-go/xsql
2 |
3 | go 1.18
4 |
5 | require (
6 | github.com/go-sql-driver/mysql v1.6.0
7 | github.com/sijms/go-ora/v2 v2.5.2
8 | github.com/stretchr/testify v1.7.1
9 | google.golang.org/protobuf v1.34.1
10 | )
11 |
12 | require (
13 | github.com/davecgh/go-spew v1.1.0 // indirect
14 | github.com/pmezard/go-difflib v1.0.0 // indirect
15 | gopkg.in/yaml.v3 v3.0.0 // indirect
16 | )
17 |
--------------------------------------------------------------------------------
/src/xsql/go.sum:
--------------------------------------------------------------------------------
1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3 | github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
4 | github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
5 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
6 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
7 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
8 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
9 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
10 | github.com/sijms/go-ora/v2 v2.5.2 h1:8ACnYT4rOI7vjCIXQuGopiClXrXt4AnmSrv+nyMxELQ=
11 | github.com/sijms/go-ora/v2 v2.5.2/go.mod h1:EHxlY6x7y9HAsdfumurRfTd+v8NrEOTR3Xl4FWlH6xk=
12 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
13 | github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
14 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
15 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
16 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
17 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
18 | google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
19 | google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
20 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
21 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
22 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
23 | gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
24 | gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
25 |
--------------------------------------------------------------------------------
/src/xsql/interface.go:
--------------------------------------------------------------------------------
1 | package xsql
2 |
3 | import "database/sql"
4 |
5 | type Executor interface {
6 | Exec(query string, args ...interface{}) (sql.Result, error)
7 | }
8 |
9 | type Query interface {
10 | Query(query string, args ...interface{}) (*sql.Rows, error)
11 | }
12 |
13 | type Table interface {
14 | TableName() string
15 | }
16 |
--------------------------------------------------------------------------------
/src/xsql/log.go:
--------------------------------------------------------------------------------
1 | package xsql
2 |
3 | import "time"
4 |
5 | type Log struct {
6 | Duration time.Duration `json:"duration"`
7 | SQL string `json:"sql"`
8 | Bindings []interface{} `json:"bindings"`
9 | RowsAffected int64 `json:"rowsAffected"`
10 | Error error `json:"error"`
11 | }
12 |
13 | type DebugFunc func(l *Log)
14 |
--------------------------------------------------------------------------------
/src/xsql/options.go:
--------------------------------------------------------------------------------
1 | package xsql
2 |
3 | import (
4 | "fmt"
5 | "time"
6 | )
7 |
8 | var DefaultOptions = newDefaultOptions()
9 |
10 | func newDefaultOptions() sqlOptions {
11 | return sqlOptions{
12 | Tag: "xsql",
13 | InsertKey: "INSERT INTO",
14 | TableKey: "${TABLE}",
15 | Placeholder: "?",
16 | ColumnQuotes: "`",
17 | TimeLayout: "2006-01-02 15:04:05",
18 | TimeLocation: time.Local,
19 | TimeFunc: func(placeholder string) string {
20 | return placeholder
21 | },
22 | DebugFunc: nil,
23 | }
24 | }
25 |
26 | type sqlOptions struct {
27 | // Default: xsql
28 | Tag string
29 |
30 | // Default: INSERT INTO
31 | InsertKey string
32 |
33 | // Default: ${TABLE}
34 | TableKey string
35 |
36 | // Default: ?
37 | // For oracle, can be configured as :%d
38 | Placeholder string
39 |
40 | // Default: `
41 | // For oracle, can be configured as "
42 | ColumnQuotes string
43 |
44 | // Default: 2006-01-02 15:04:05
45 | TimeLayout string
46 |
47 | // Default: time.Local
48 | TimeLocation *time.Location
49 |
50 | // Default: func(placeholder string) string { return placeholder }
51 | // For oracle, this closure can be modified to add TO_TIMESTAMP
52 | TimeFunc TimeFunc
53 |
54 | // Global debug SQL
55 | DebugFunc DebugFunc
56 | }
57 |
58 | func mergeOptions(opts []SqlOption) *sqlOptions {
59 | cp := DefaultOptions // copy
60 | for _, o := range opts {
61 | o.apply(&cp)
62 | }
63 | return &cp
64 | }
65 |
66 | type SqlOption interface {
67 | apply(*sqlOptions)
68 | }
69 |
70 | type funcSqlOption struct {
71 | f func(*sqlOptions)
72 | }
73 |
74 | func (fdo *funcSqlOption) apply(do *sqlOptions) {
75 | fdo.f(do)
76 | }
77 |
78 | func WithTag(tag string) SqlOption {
79 | return &funcSqlOption{func(opts *sqlOptions) {
80 | opts.Tag = tag
81 | }}
82 | }
83 |
84 | func WithInsertKey(insertKey string) SqlOption {
85 | return &funcSqlOption{func(opts *sqlOptions) {
86 | opts.InsertKey = insertKey
87 | }}
88 | }
89 |
90 | func WithPlaceholder(placeholder string) SqlOption {
91 | return &funcSqlOption{func(opts *sqlOptions) {
92 | opts.Placeholder = placeholder
93 | }}
94 | }
95 |
96 | func WithColumnQuotes(columnQuotes string) SqlOption {
97 | return &funcSqlOption{func(opts *sqlOptions) {
98 | opts.ColumnQuotes = columnQuotes
99 | }}
100 | }
101 |
102 | func WithTimeLayout(timeLayout string) SqlOption {
103 | return &funcSqlOption{func(opts *sqlOptions) {
104 | opts.TimeLayout = timeLayout
105 | }}
106 | }
107 |
108 | func WithTimeLocation(timeLocation *time.Location) SqlOption {
109 | return &funcSqlOption{func(opts *sqlOptions) {
110 | opts.TimeLocation = timeLocation
111 | }}
112 | }
113 |
114 | func WithTimeFunc(f TimeFunc) SqlOption {
115 | return &funcSqlOption{func(opts *sqlOptions) {
116 | opts.TimeFunc = f
117 | }}
118 | }
119 |
120 | func WithDebugFunc(f DebugFunc) SqlOption {
121 | return &funcSqlOption{func(opts *sqlOptions) {
122 | opts.DebugFunc = f
123 | }}
124 | }
125 |
126 | func UseOracle() SqlOption {
127 | return &funcSqlOption{func(opts *sqlOptions) {
128 | opts.Placeholder = `:%d`
129 | opts.ColumnQuotes = `"`
130 | opts.TimeFunc = func(placeholder string) string {
131 | return fmt.Sprintf("TO_TIMESTAMP(%s, 'SYYYY-MM-DD HH24:MI:SS:FF6')", placeholder)
132 | }
133 | }}
134 | }
135 |
136 | func (t *sqlOptions) doDebug(l *Log) {
137 | if t.DebugFunc == nil {
138 | return
139 | }
140 | t.DebugFunc(l)
141 | }
142 |
--------------------------------------------------------------------------------
/src/xsql/query.go:
--------------------------------------------------------------------------------
1 | package xsql
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | type query struct {
8 | Query
9 | }
10 |
11 | func (t *query) Fetch(query string, args []interface{}, opts *sqlOptions) (*Fetcher, error) {
12 | startTime := time.Now()
13 | r, err := t.Query.Query(query, args...)
14 | l := &Log{
15 | Duration: time.Now().Sub(startTime),
16 | SQL: query,
17 | Bindings: args,
18 | RowsAffected: 0,
19 | Error: err,
20 | }
21 | if err != nil {
22 | opts.doDebug(l)
23 | return nil, err
24 | }
25 |
26 | f := &Fetcher{
27 | r: r,
28 | log: l,
29 | options: opts,
30 | }
31 | return f, err
32 | }
33 |
--------------------------------------------------------------------------------
/src/xsql/tx.go:
--------------------------------------------------------------------------------
1 | package xsql
2 |
3 | import "database/sql"
4 |
5 | type Tx struct {
6 | raw *sql.Tx
7 | *DB
8 | }
9 |
10 | func (t *Tx) Commit() error {
11 | return t.raw.Commit()
12 | }
13 |
14 | func (t *Tx) Rollback() error {
15 | return t.raw.Rollback()
16 | }
17 |
--------------------------------------------------------------------------------
/src/xsql/util.go:
--------------------------------------------------------------------------------
1 | package xsql
2 |
3 | import (
4 | "fmt"
5 | "reflect"
6 | )
7 |
8 | type TagValues []TagValue
9 |
10 | type TagValue struct {
11 | Key interface{}
12 | Value interface{}
13 | }
14 |
15 | // TagValuesMap takes a tag key, a pointer to a struct, and TagValues.
16 | // It constructs a map where each key is the struct field's tag value, paired with the corresponding value from TagValues.
17 | func TagValuesMap(tagKey string, ptr interface{}, values TagValues) (map[string]interface{}, error) {
18 | result := make(map[string]interface{})
19 | structValue := reflect.ValueOf(ptr).Elem()
20 |
21 | if structValue.Kind() != reflect.Struct {
22 | return nil, fmt.Errorf("xsql: ptr must be a pointer to a struct")
23 | }
24 |
25 | fieldsMap := make(map[string]reflect.Value)
26 | populateFieldsMap(tagKey, structValue, fieldsMap)
27 |
28 | for i, tagValue := range values {
29 | fieldPtr, fieldValue := tagValue.Key, reflect.ValueOf(tagValue.Key)
30 | if fieldValue.Kind() != reflect.Ptr || fieldValue.IsNil() {
31 | return nil, fmt.Errorf("xsql: error at item %d in values slice: key is not a non-nil pointer to a struct field", i)
32 | }
33 |
34 | foundFieldName := ""
35 | for tagName, field := range fieldsMap {
36 | if field.Addr().Interface() == fieldPtr {
37 | foundFieldName = tagName
38 | break
39 | }
40 | }
41 |
42 | if foundFieldName == "" {
43 | return nil, fmt.Errorf("xsql: no matching struct field found for item %d in values slice", i)
44 | }
45 |
46 | result[foundFieldName] = tagValue.Value
47 | }
48 |
49 | return result, nil
50 | }
51 |
52 | func populateFieldsMap(tagKey string, v reflect.Value, fieldsMap map[string]reflect.Value) {
53 | for i := 0; i < v.NumField(); i++ {
54 | field := v.Field(i)
55 | fieldType := v.Type().Field(i)
56 | tag := fieldType.Tag.Get(tagKey)
57 | if fieldType.Anonymous && field.Type().Kind() == reflect.Struct {
58 | populateFieldsMap(tagKey, field, fieldsMap)
59 | } else if tag != "" {
60 | fieldsMap[tag] = field
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/xsql/xsql.sql:
--------------------------------------------------------------------------------
1 | DROP TABLE IF EXISTS `xsql`;
2 | CREATE TABLE `xsql` (
3 | `id` int unsigned NOT NULL AUTO_INCREMENT,
4 | `foo` varchar(255) DEFAULT NULL,
5 | `bar` datetime DEFAULT NULL,
6 | `bool` int NOT NULL DEFAULT '0',
7 | `enum` int NOT NULL DEFAULT '0',
8 | `json` json DEFAULT NULL,
9 | PRIMARY KEY (`id`)
10 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
11 | INSERT INTO `xsql` (`id`, `foo`, `bar`, `bool`, `enum`, `json`) VALUES (1, 'v', '2022-04-12 23:50:00', 1, 1, '{"foo":"bar"}');
12 | INSERT INTO `xsql` (`id`, `foo`, `bar`, `bool`, `enum`, `json`) VALUES (2, 'v1', '2022-04-13 23:50:00', 1, 1, '[1,2]');
13 | INSERT INTO `xsql` (`id`, `foo`, `bar`, `bool`, `enum`, `json`) VALUES (3, 'v2', '2022-04-14 23:50:00', 1, 1, null);
14 |
15 | DROP TABLE IF EXISTS `devices`;
16 | CREATE TABLE `devices` (
17 | `id` bigint unsigned NOT NULL AUTO_INCREMENT,
18 | `uuid` varchar(255) NOT NULL,
19 | `user_id` bigint unsigned NOT NULL,
20 | `platform` tinyint unsigned NOT NULL,
21 | `info` varchar(255) NOT NULL,
22 | `app` json DEFAULT NULL,
23 | `language_code` varchar(255) NOT NULL,
24 | `status` tinyint unsigned NOT NULL,
25 | `synced_message_id` bigint unsigned NOT NULL DEFAULT '0',
26 | `firebase_token` varchar(255) NOT NULL DEFAULT '',
27 | `last_used_at` timestamp NOT NULL,
28 | `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
29 | `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
30 | PRIMARY KEY (`id`)
31 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
32 | INSERT INTO `devices` (`id`, `uuid`, `user_id`, `platform`, `info`, `app`, `language_code`, `status`, `synced_message_id`, `firebase_token`, `last_used_at`, `created_at`, `updated_at`) VALUES (1, '0c7f60e8-d7a3-48ae-9139-5128e336736e', 100000010, 1, 'postman', '{\"build\": 1, \"version\": \"v1.0.0\"}', '', 1, 0, '', '1970-01-01 00:00:01', '2025-04-07 07:41:23', '2025-04-07 07:41:23');
33 | INSERT INTO `devices` (`id`, `uuid`, `user_id`, `platform`, `info`, `app`, `language_code`, `status`, `synced_message_id`, `firebase_token`, `last_used_at`, `created_at`, `updated_at`) VALUES (2, '0c7f60e8-d7a3-48ae-9139-5128e336736e', 100000011, 1, 'postman', '{\"build\": 1, \"version\": \"v1.0.0\"}', '', 1, 0, '', '1970-01-01 00:00:01', '2025-04-07 07:41:25', '2025-04-07 07:41:25');
34 | INSERT INTO `devices` (`id`, `uuid`, `user_id`, `platform`, `info`, `app`, `language_code`, `status`, `synced_message_id`, `firebase_token`, `last_used_at`, `created_at`, `updated_at`) VALUES (3, '0c7f60e8-d7a3-48ae-9139-5128e336736e', 100000012, 1, 'postman', '{\"build\": 1, \"version\": \"v1.0.0\"}', '', 1, 0, '', '1970-01-01 00:00:01', '2025-04-07 07:41:26', '2025-04-07 07:41:26');
35 |
--------------------------------------------------------------------------------
/src/xsql/xsqlora.sql:
--------------------------------------------------------------------------------
1 | -- ----------------------------
2 | -- Table structure for XSQL
3 | -- ----------------------------
4 | DROP TABLE "TEST"."XSQL";
5 | CREATE TABLE "TEST"."XSQL" (
6 | "ID" NUMBER VISIBLE NOT NULL,
7 | "FOO" VARCHAR2(255 BYTE) VISIBLE,
8 | "BAR" TIMESTAMP(6) VISIBLE
9 | )
10 | LOGGING
11 | NOCOMPRESS
12 | PCTFREE 10
13 | INITRANS 1
14 | STORAGE (
15 | INITIAL 1048576
16 | NEXT 1048576
17 | MINEXTENTS 1
18 | MAXEXTENTS 2147483645
19 | BUFFER_POOL DEFAULT
20 | )
21 | PARALLEL 1
22 | NOCACHE
23 | DISABLE ROW MOVEMENT
24 | ;
25 |
26 | -- ----------------------------
27 | -- Records of XSQL
28 | -- ----------------------------
29 | INSERT INTO "TEST"."XSQL" ("ID", "FOO", "BAR") VALUES ('1', 'v', TO_TIMESTAMP('2022-04-14 23:49:48.000000', 'SYYYY-MM-DD HH24:MI:SS:FF6'));
30 | INSERT INTO "TEST"."XSQL" ("ID", "FOO", "BAR") VALUES ('2', 'v1', TO_TIMESTAMP('2022-04-14 23:50:00.000000', 'SYYYY-MM-DD HH24:MI:SS:FF6'));
31 | COMMIT;
32 | COMMIT;
33 |
34 | -- ----------------------------
35 | -- Primary Key structure for table XSQL
36 | -- ----------------------------
37 | ALTER TABLE "TEST"."XSQL" ADD CONSTRAINT "SYS_C00214001" PRIMARY KEY ("ID");
38 |
39 | -- ----------------------------
40 | -- Checks structure for table XSQL
41 | -- ----------------------------
42 | ALTER TABLE "TEST"."XSQL" ADD CONSTRAINT "SYS_C00214000" CHECK ("ID" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
43 |
--------------------------------------------------------------------------------
/src/xutil/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/mix-go/xutil
2 |
3 | go 1.20
4 |
5 | require (
6 | github.com/joho/godotenv v1.5.1
7 | github.com/stretchr/testify v1.8.2
8 | )
9 |
10 | require (
11 | github.com/davecgh/go-spew v1.1.1 // indirect
12 | github.com/pmezard/go-difflib v1.0.0 // indirect
13 | gopkg.in/yaml.v3 v3.0.1 // indirect
14 | )
15 |
--------------------------------------------------------------------------------
/src/xutil/go.sum:
--------------------------------------------------------------------------------
1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
5 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
6 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
7 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
8 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
9 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
10 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
11 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
12 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
13 | github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
14 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
15 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
16 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
17 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
18 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
19 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
20 |
--------------------------------------------------------------------------------
/src/xutil/xconv/conv.go:
--------------------------------------------------------------------------------
1 | package xconv
2 |
3 | import (
4 | "reflect"
5 | "strings"
6 | "unsafe"
7 | )
8 |
9 | // StringToBytes converts string to byte slice without a memory allocation.
10 | func StringToBytes(s string) []byte {
11 | return *(*[]byte)(unsafe.Pointer(
12 | &struct {
13 | string
14 | Cap int
15 | }{s, len(s)},
16 | ))
17 | }
18 |
19 | // BytesToString converts byte slice to string without a memory allocation.
20 | func BytesToString(b []byte) string {
21 | return *(*string)(unsafe.Pointer(&b))
22 | }
23 |
24 | // StructToMap Convert struct to map.
25 | // This function first tries to use the bson tag, and if the bson tag does not exist, it will use the json tag.
26 | // if both bson and json tags do not exist, then it will use the field name as the key. Additionally,
27 | // if the tag value is "-", this field will be ignored and not added to the map.
28 | func StructToMap(i interface{}) map[string]interface{} {
29 | result := make(map[string]interface{})
30 | inputType := reflect.TypeOf(i)
31 | inputVal := reflect.ValueOf(i)
32 |
33 | if inputType.Kind() == reflect.Ptr {
34 | inputType = inputType.Elem()
35 | inputVal = inputVal.Elem()
36 | }
37 |
38 | for i := 0; i < inputType.NumField(); i++ {
39 | field := inputType.Field(i)
40 | value := inputVal.Field(i).Interface()
41 |
42 | key := field.Tag.Get("bson")
43 | if key == "" {
44 | key = field.Tag.Get("json")
45 | }
46 |
47 | if key == "-" {
48 | continue
49 | }
50 |
51 | key = strings.Split(key, ",")[0]
52 | if key == "" {
53 | key = field.Name
54 | }
55 |
56 | result[key] = value
57 | }
58 |
59 | return result
60 | }
61 |
--------------------------------------------------------------------------------
/src/xutil/xcrypt/aes.go:
--------------------------------------------------------------------------------
1 | package xcrypt
2 |
3 | import (
4 | "bytes"
5 | "crypto/aes"
6 | "crypto/cipher"
7 | "encoding/base64"
8 | "errors"
9 | )
10 |
11 | const (
12 | ModeCBC = "CBC"
13 | ModeCFB = "CFB"
14 | ModeOFB = "OFB"
15 | )
16 |
17 | var ErrUnknownMode = errors.New("unknown mode")
18 |
19 | // AESEncrypt AES encryption, supports CBC,CFB,OFB mode, PKCS7Padding padding.
20 | // key128 := "abcdefghijklmnop" // 16 bytes = 128 bits
21 | // key192 := "abcdefghijklmnopqrstuvwx" // 24 bytes = 192 bits
22 | // key256 := "abcdefghijklmnopabcdefghijklmnop" // 32 bytes = 256 bits
23 | func AESEncrypt(plainText, mode, key, iv string) (string, error) {
24 | block, err := aes.NewCipher([]byte(key))
25 | if err != nil {
26 | return "", err
27 | }
28 |
29 | plainTextBytes := PKCS7Padding([]byte(plainText), block.BlockSize())
30 | cipherText := make([]byte, len(plainTextBytes))
31 |
32 | switch mode {
33 | case ModeCBC:
34 | cbc := cipher.NewCBCEncrypter(block, []byte(iv))
35 | cbc.CryptBlocks(cipherText, plainTextBytes)
36 | case ModeCFB:
37 | cfb := cipher.NewCFBEncrypter(block, []byte(iv))
38 | cfb.XORKeyStream(cipherText, plainTextBytes)
39 | case ModeOFB:
40 | ofb := cipher.NewOFB(block, []byte(iv))
41 | ofb.XORKeyStream(cipherText, plainTextBytes)
42 | default:
43 | return "", ErrUnknownMode
44 | }
45 |
46 | return base64.StdEncoding.EncodeToString(cipherText), nil
47 | }
48 |
49 | func AESDecrypt(cipherText, mode, key, iv string) (string, error) {
50 | cipherTextBytes, err := base64.StdEncoding.DecodeString(cipherText)
51 | if err != nil {
52 | return "", err
53 | }
54 |
55 | block, err := aes.NewCipher([]byte(key))
56 | if err != nil {
57 | return "", err
58 | }
59 |
60 | decryptedText := make([]byte, len(cipherTextBytes))
61 |
62 | switch mode {
63 | case ModeCBC:
64 | cbc := cipher.NewCBCDecrypter(block, []byte(iv))
65 | cbc.CryptBlocks(decryptedText, cipherTextBytes)
66 | case ModeCFB:
67 | cfb := cipher.NewCFBDecrypter(block, []byte(iv))
68 | cfb.XORKeyStream(decryptedText, cipherTextBytes)
69 | case ModeOFB:
70 | ofb := cipher.NewOFB(block, []byte(iv))
71 | ofb.XORKeyStream(decryptedText, cipherTextBytes)
72 | default:
73 | return "", ErrUnknownMode
74 | }
75 |
76 | decryptedText = PKCS7UnPadding(decryptedText)
77 |
78 | return string(decryptedText), nil
79 | }
80 |
81 | // PKCS7Padding adds padding to the input plainTextByte according to PKCS7 mode
82 | func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
83 | padding := blockSize - len(ciphertext)%blockSize
84 | padtext := bytes.Repeat([]byte{byte(padding)}, padding)
85 | return append(ciphertext, padtext...)
86 | }
87 |
88 | // PKCS7UnPadding removes padding from the input cipherTextBytes according to PKCS7 mode
89 | func PKCS7UnPadding(origData []byte) []byte {
90 | length := len(origData)
91 | // subtract the number of padding bytes
92 | unpadding := int(origData[length-1])
93 | return origData[:(length - unpadding)]
94 | }
95 |
--------------------------------------------------------------------------------
/src/xutil/xenv/README.md:
--------------------------------------------------------------------------------
1 | > OpenMix 出品:[https://openmix.org](https://openmix.org/mix-go)
2 |
3 | ## Mix XENV
4 |
5 | Based on GoDotEnv library, with type conversion function
6 |
7 | 基于 [GoDotEnv](https://github.com/joho/godotenv) 开发的具有**类型转换功能**的环境配置库
8 |
9 | ## Usage
10 |
11 | 载入 `.env` 到环境变量
12 |
13 | ~~~go
14 | _ = xenv.Load(".env")
15 | _ = xenv.Overload(".env")
16 | ~~~
17 |
18 | 获取环境变量
19 |
20 | ~~~go
21 | i := xenv.Getenv("key").String()
22 | i := xenv.Getenv("key").Bool()
23 | i := xenv.Getenv("key").Int64()
24 | i := xenv.Getenv("key").Float64()
25 | ~~~
26 |
27 | 设置默认值
28 |
29 | ~~~go
30 | i := xenv.Getenv("key").String("default")
31 | i := xenv.Getenv("key").Bool(false)
32 | i := xenv.Getenv("key").Int64(123)
33 | i := xenv.Getenv("key").Float64(123.4)
34 | ~~~
35 |
36 | ## License
37 |
38 | Apache License Version 2.0, http://www.apache.org/licenses/
39 |
--------------------------------------------------------------------------------
/src/xutil/xenv/conv.go:
--------------------------------------------------------------------------------
1 | package xenv
2 |
3 | import (
4 | "os"
5 | "strconv"
6 | )
7 |
8 | type envValue struct {
9 | v string
10 | }
11 |
12 | // String 转换为字符串
13 | func (t *envValue) String(val ...string) string {
14 | d := ""
15 | if len(val) >= 1 {
16 | d = val[0]
17 | }
18 |
19 | if t.v == "" {
20 | return d
21 | }
22 |
23 | return t.v
24 | }
25 |
26 | // Bool 转换为布尔
27 | func (t *envValue) Bool(val ...bool) bool {
28 | d := false
29 | if len(val) >= 1 {
30 | d = val[0]
31 | }
32 |
33 | switch t.v {
34 | case "":
35 | return d
36 | case "0", "false":
37 | return false
38 | default:
39 | return true
40 | }
41 | }
42 |
43 | // Int64 转换为整型
44 | func (t *envValue) Int64(val ...int64) int64 {
45 | d := int64(0)
46 | if len(val) >= 1 {
47 | d = val[0]
48 | }
49 |
50 | if t.v == "" {
51 | return d
52 | }
53 |
54 | v, _ := strconv.ParseInt(t.v, 10, 64)
55 | return v
56 | }
57 |
58 | // Float64 转换为浮点
59 | func (t *envValue) Float64(val ...float64) float64 {
60 |
61 | d := float64(0)
62 | if len(val) >= 1 {
63 | d = val[0]
64 | }
65 |
66 | if t.v == "" {
67 | return d
68 | }
69 |
70 | v, _ := strconv.ParseFloat(t.v, 64)
71 | return v
72 | }
73 |
74 | // Getenv 获取环境变量
75 | func Getenv(key string) *envValue {
76 | return &envValue{os.Getenv(key)}
77 | }
78 |
--------------------------------------------------------------------------------
/src/xutil/xenv/load.go:
--------------------------------------------------------------------------------
1 | package xenv
2 |
3 | import "github.com/joho/godotenv"
4 |
5 | // Load will read your env file(s) and load them into ENV for this process.
6 | func Load(filenames ...string) (err error) {
7 | return godotenv.Load(filenames...)
8 | }
9 |
10 | // Overload will read your env file(s) and load them into ENV for this process.
11 | func Overload(filenames ...string) (err error) {
12 | return godotenv.Overload(filenames...)
13 | }
14 |
--------------------------------------------------------------------------------
/src/xutil/xfmt/README.md:
--------------------------------------------------------------------------------
1 | > OpenMix 出品:[https://openmix.org](https://openmix.org/mix-go)
2 |
3 | ## Mix XFMT
4 |
5 | Formatting library that can print the internal data of the nested pointer address of the struct
6 |
7 | 可以打印结构体嵌套指针地址内部数据的格式化库
8 |
9 | ## Usage
10 |
11 | - 支持的方法,与 `fmt` 系统库完全一致
12 |
13 | - `Sprintf(format string, args ...interface{}) string`
14 | - `Sprint(args ...interface{}) string`
15 | - `Sprintln(args ...interface{}) string`
16 | - `Printf(format string, args ...interface{})`
17 | - `Print(args ...interface{})`
18 | - `Println(args ...interface{})`
19 |
20 | - 支持 `Tag` 忽略某个引用字段
21 |
22 | ```go
23 | type Foo struct {
24 | Bar *Bar `xfmt:"-"`
25 | }
26 | ```
27 |
28 | - 使用
29 |
30 | 包含指针的结构体
31 |
32 | ```go
33 | type Level3 struct {
34 | Name string
35 | }
36 |
37 | type Level2 struct {
38 | Level3 *Level3 `xfmt:"-"`
39 | Name string
40 | }
41 |
42 | type Level1 struct {
43 | Name string
44 | Level2 *Level2
45 | Level2_1 *Level2
46 | }
47 | ```
48 |
49 | 创建变量
50 |
51 | ```go
52 | l3 := Level3{Name: "Level3"}
53 | l2 := Level2{Name: "Level2", Level3: &l3}
54 | l1 := Level1{Name: "Level1", Level2: &l2, Level2_1: &l2}
55 | ```
56 |
57 | 打印对比
58 |
59 | - `fmt` 打印
60 |
61 | ```go
62 | fmt.Println(fmt.Sprintf("%+v", l1))
63 | ```
64 |
65 | ```
66 | {Name:Level1 Level2:0xc00009c500 Level2_1:0xc00009c500}
67 | ```
68 |
69 | - `xfmt` 打印:其中 Level3 被定义的 tag 忽略,Level2_1 由于和 Level2 是同一个指针因此后面的忽略处理
70 |
71 | ```go
72 | fmt.Println(xfmt.Sprintf("%+v", l1))
73 | ```
74 |
75 | ```
76 | {Name:Level1 Level2:0xc00009c500:&{Level3:0xc00007f030 Name:Level2} Level2_1:0xc00009c500}
77 | ```
78 |
79 | 动态停用和启用
80 |
81 | ```go
82 | xfmt.Disable() // 停用后xfmt等同于fmt
83 | xfmt.Enable()
84 | ```
85 |
86 | ## License
87 |
88 | Apache License Version 2.0, http://www.apache.org/licenses/
89 |
--------------------------------------------------------------------------------
/src/xutil/xslices/slices.go:
--------------------------------------------------------------------------------
1 | package xslices
2 |
3 | func InArray[T comparable](item T, slice []T) bool {
4 | for _, v := range slice {
5 | if v == item {
6 | return true
7 | }
8 | }
9 | return false
10 | }
11 |
--------------------------------------------------------------------------------
/src/xutil/xstrings/strings.go:
--------------------------------------------------------------------------------
1 | package xstrings
2 |
3 | import (
4 | "strconv"
5 | "strings"
6 | )
7 |
8 | func IsNumeric(s string) bool {
9 | _, err := strconv.ParseFloat(s, 64)
10 | return err == nil
11 | }
12 |
13 | func SubString(s string, start int, length int) string {
14 | runes := []rune(s)
15 | end := start + length
16 |
17 | if start < 0 {
18 | start = 0
19 | }
20 |
21 | if end > len(runes) {
22 | end = len(runes)
23 | }
24 |
25 | return string(runes[start:end])
26 | }
27 |
28 | func Capitalize(s string) string {
29 | if len(s) > 0 {
30 | first := strings.ToUpper(s[:1]) // 将首字母转化为大写
31 | return first + s[1:]
32 | }
33 | return s
34 | }
35 |
--------------------------------------------------------------------------------
/src/xwp/README.md:
--------------------------------------------------------------------------------
1 | > OpenMix 出品:[https://openmix.org](https://openmix.org/mix-go)
2 |
3 | ## Mix XWP
4 |
5 | 通用动态工作池、协程池
6 |
7 | A dynamic golang worker pool, coroutine pool
8 |
9 | ## Installation
10 |
11 | ```
12 | go get github.com/mix-go/xwp
13 | ```
14 |
15 | ## 单次调度
16 |
17 | > 适合处理数据计算、转换等场景
18 |
19 | 先创建一个结构体用来处理任务,使用类型断言转换任务数据类型,例如:`i := data.(int)`
20 |
21 | ~~~go
22 | type Foo struct {
23 | }
24 |
25 | func (t *Foo) Do(data interface{}) {
26 | // do something
27 | }
28 | ~~~
29 |
30 | 调度任务
31 |
32 | - 也可以使用 `RunF` 采用闭包来处理任务
33 | - 如果不想阻塞执行,可以使用 `p.Start()` 启动
34 |
35 | ~~~go
36 | jobQueue := make(chan interface{}, 200)
37 | p := &xwp.WorkerPool{
38 | JobQueue: jobQueue,
39 | MaxWorkers: 1000,
40 | InitWorkers: 100,
41 | MaxIdleWorkers: 100,
42 | RunI: &Foo{},
43 | }
44 |
45 | go func() {
46 | // 投放任务
47 | for i := 0; i < 10000; i++ {
48 | jobQueue <- i
49 | }
50 |
51 | // 投放完停止调度
52 | p.Stop()
53 | }()
54 |
55 | p.Run() // 阻塞等待
56 | ~~~
57 |
58 | ## 常驻调度
59 |
60 | > 适合处理 MQ 队列的异步消费
61 |
62 | 以 Redis 作为 MQ 为例:
63 |
64 | ~~~go
65 | jobQueue := make(chan interface{}, 200)
66 | p := &xwp.WorkerPool{
67 | JobQueue: jobQueue,
68 | MaxWorkers: 1000,
69 | InitWorkers: 100,
70 | MaxIdleWorkers: 100,
71 | RunI: &Foo{},
72 | }
73 |
74 | ch := make(chan os.Signal)
75 | signal.Notify(ch, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
76 | go func() {
77 | <-ch
78 | p.Stop()
79 | }()
80 |
81 | go func() {
82 | for {
83 | res, err := redis.BRPop(context.Background(), 3*time.Second, "foo").Result()
84 | if err != nil {
85 | if strings.Contains(err.Error(), "redis: nil") {
86 | continue
87 | }
88 | fmt.Println(fmt.Sprintf("Redis Error: %s", err))
89 | p.Stop()
90 | return
91 | }
92 | // brPop命令最后一个键才是值
93 | jobQueue <- res[1]
94 | }
95 | }()
96 |
97 | p.Run() // 阻塞等待
98 | ~~~
99 |
100 | ## 异常处理
101 |
102 | `Do` 方法中执行的代码,可能会出现 `panic` 异常,我们可以通过 `recover` 获取异常信息记录到日志或者执行其他处理
103 |
104 | ~~~go
105 | func (t *Foo) Do(data interface{}) {
106 | defer func() {
107 | if err := recover(); err != nil {
108 | // handle error
109 | }
110 | }()
111 | // do something
112 | }
113 | ~~~
114 |
115 | ## 执行状态
116 |
117 | `Stats()` 可以返回 `Workers` 实时执行状态,通常可以使用一个定时器,定时打印或者告警处理
118 |
119 | ```go
120 | go func() {
121 | ticker := time.NewTicker(1000 * time.Millisecond)
122 | for {
123 | <-ticker.C
124 | log.Printf("%+v", p.Stats()) // 2021/04/26 14:32:53 &{Active:5 Idle:95 Total:100}
125 | }
126 | }()
127 | ```
128 |
129 | ## License
130 |
131 | Apache License Version 2.0, http://www.apache.org/licenses/
132 |
--------------------------------------------------------------------------------
/src/xwp/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/mix-go/xwp
2 |
3 | go 1.13
4 |
5 | require github.com/stretchr/testify v1.6.1
6 |
--------------------------------------------------------------------------------
/src/xwp/go.sum:
--------------------------------------------------------------------------------
1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
3 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
4 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
5 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
6 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
7 |
--------------------------------------------------------------------------------
/src/xwp/worker.go:
--------------------------------------------------------------------------------
1 | package xwp
2 |
3 | import (
4 | "fmt"
5 | "sync/atomic"
6 | "time"
7 | )
8 |
9 | // Handler 处理器
10 | type Handler func(data interface{})
11 |
12 | // Worker 工作者
13 | type Worker struct {
14 | pool *WorkerPool
15 | handler Handler
16 | jobChan JobQueue
17 | quit chan bool
18 | }
19 |
20 | // NewWorker
21 | func NewWorker(p *WorkerPool) *Worker {
22 | return &Worker{
23 | pool: p,
24 | handler: func(data interface{}) {
25 | if p.RunF != nil {
26 | p.RunF(data)
27 | } else if p.RunI != nil {
28 | i := p.RunI
29 | i.Do(data)
30 | }
31 | },
32 | jobChan: make(chan interface{}),
33 | quit: make(chan bool),
34 | }
35 | }
36 |
37 | // Run 执行
38 | func (t *Worker) Run() {
39 | // 先注册
40 | select {
41 | case t.pool.workerQueuePool <- t.jobChan:
42 | atomic.AddInt64(&t.pool.workerCount, 1)
43 | t.pool.workers.Store(fmt.Sprintf("%p", t), t)
44 | default:
45 | return
46 | }
47 |
48 | t.pool.wg.Add(1)
49 | go func() {
50 | defer func() {
51 | atomic.AddInt64(&t.pool.workerCount, -1)
52 | t.pool.workers.Delete(fmt.Sprintf("%p", t))
53 | t.pool.wg.Done()
54 | }()
55 |
56 | for {
57 | select {
58 | case data := <-t.jobChan:
59 | if data == nil {
60 | return
61 | }
62 | t.handler(data)
63 | tr := time.NewTimer(5 * time.Second)
64 | select {
65 | case t.pool.workerQueuePool <- t.jobChan:
66 | case <-tr.C:
67 | return
68 | }
69 | case <-t.quit:
70 | close(t.jobChan)
71 | }
72 | }
73 | }()
74 | }
75 |
76 | // Stop 停止
77 | func (t *Worker) Stop() {
78 | go func() {
79 | t.quit <- true
80 | }()
81 | }
82 |
--------------------------------------------------------------------------------
/src/xwp/workerpool.go:
--------------------------------------------------------------------------------
1 | package xwp
2 |
3 | import (
4 | "fmt"
5 | "runtime"
6 | "sync"
7 | "sync/atomic"
8 | "time"
9 | )
10 |
11 | // JobQueue 任务队列
12 | type JobQueue chan interface{}
13 |
14 | // RunI
15 | type RunI interface {
16 | Do(data interface{})
17 | }
18 |
19 | // WorkerPool 调度器
20 | type WorkerPool struct {
21 | JobQueue JobQueue
22 |
23 | // default == runtime.NumCPU()
24 | MaxWorkers int
25 | // default == MaxWorkers
26 | InitWorkers int
27 | // default == InitWorkers
28 | MaxIdleWorkers int
29 |
30 | RunF func(data interface{})
31 | RunI RunI
32 |
33 | workers *sync.Map
34 | workerCount int64
35 | workerQueuePool chan JobQueue
36 | wg *sync.WaitGroup
37 | quit chan bool
38 | }
39 |
40 | // Run 执行
41 | func (t *WorkerPool) Run() {
42 | t.Start()
43 | t.Wait()
44 | }
45 |
46 | // init
47 | func (t *WorkerPool) init() {
48 | if t.MaxWorkers == 0 {
49 | t.MaxWorkers = runtime.NumCPU()
50 | }
51 | if t.InitWorkers == 0 {
52 | t.InitWorkers = t.MaxWorkers
53 | }
54 | if t.MaxIdleWorkers == 0 {
55 | t.MaxIdleWorkers = t.InitWorkers
56 | }
57 |
58 | t.workers = &sync.Map{}
59 | t.workerQueuePool = make(chan JobQueue, t.MaxIdleWorkers)
60 | t.wg = &sync.WaitGroup{}
61 | t.quit = make(chan bool)
62 |
63 | if t.RunF == nil && t.RunI == nil {
64 | panic(fmt.Errorf("xwp.WorkerPool RunF & RunI field is empty"))
65 | }
66 | }
67 |
68 | // Start 启动
69 | func (t *WorkerPool) Start() {
70 | t.init()
71 |
72 | for i := 0; i < t.InitWorkers; i++ {
73 | NewWorker(t).Run()
74 | }
75 |
76 | go func() {
77 | timer := time.NewTimer(time.Millisecond)
78 | timer.Stop()
79 | for {
80 | select {
81 | case data := <-t.JobQueue:
82 | if data == nil {
83 | t.workers.Range(func(key, value interface{}) bool {
84 | w := value.(*Worker)
85 | w.Stop()
86 | return true
87 | })
88 | return
89 | }
90 | func() {
91 | for {
92 | select {
93 | case ch := <-t.workerQueuePool:
94 | ch <- data
95 | default:
96 | if atomic.LoadInt64(&t.workerCount) < int64(t.MaxWorkers) {
97 | NewWorker(t).Run()
98 | continue
99 | } else {
100 | // 设定时间的监听
101 | timer.Reset(10 * time.Millisecond)
102 | select {
103 | case ch := <-t.workerQueuePool:
104 | timer.Stop()
105 | ch <- data
106 | case <-timer.C:
107 | continue
108 | }
109 | }
110 | }
111 | return
112 | }
113 | }()
114 | case <-t.quit:
115 | close(t.JobQueue)
116 | }
117 | }
118 | }()
119 | }
120 |
121 | // Stop 停止
122 | func (t *WorkerPool) Stop() {
123 | go func() {
124 | t.quit <- true
125 | }()
126 | }
127 |
128 | // Wait 等待执行完成
129 | func (t *WorkerPool) Wait() {
130 | t.wg.Wait()
131 | }
132 |
133 | type Statistic struct {
134 | Active int `json:"active"`
135 | Idle int `json:"idle"`
136 | Total int `json:"total"`
137 | }
138 |
139 | // Stats 统计
140 | func (t *WorkerPool) Stats() *Statistic {
141 | total := int(t.workerCount)
142 | idle := len(t.workerQueuePool)
143 | return &Statistic{
144 | Active: total - idle,
145 | Idle: idle,
146 | Total: total,
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/src/xwp/workerpool_test.go:
--------------------------------------------------------------------------------
1 | package xwp
2 |
3 | import (
4 | "github.com/stretchr/testify/assert"
5 | "log"
6 | "math/rand"
7 | "sync/atomic"
8 | "testing"
9 | "time"
10 | )
11 |
12 | var (
13 | count int64
14 | )
15 |
16 | type TestWorker struct {
17 | }
18 |
19 | func (t *TestWorker) Do(data interface{}) {
20 | atomic.AddInt64(&count, 1)
21 | time.Sleep(time.Millisecond * time.Duration(rand.Intn(10)))
22 | }
23 |
24 | func TestOnceRun(t *testing.T) {
25 | a := assert.New(t)
26 |
27 | jobQueue := make(chan interface{}, 200)
28 | num := 10000000
29 | p := &WorkerPool{
30 | JobQueue: jobQueue,
31 | MaxWorkers: 1000,
32 | InitWorkers: 100,
33 | MaxIdleWorkers: 100,
34 | RunI: &TestWorker{},
35 | }
36 |
37 | go func() {
38 | for i := 0; i < num; i++ {
39 | jobQueue <- i
40 | }
41 | p.Stop()
42 | }()
43 |
44 | go func() {
45 | ticker := time.NewTicker(100 * time.Millisecond)
46 | for {
47 | <-ticker.C
48 | log.Printf("%+v", p.Stats())
49 | }
50 | }()
51 |
52 | p.Run()
53 |
54 | a.Equal(count, int64(num))
55 | }
56 |
57 | func TestStop(t *testing.T) {
58 | a := assert.New(t)
59 |
60 | jobQueue := make(chan interface{}, 200)
61 | p := &WorkerPool{
62 | JobQueue: jobQueue,
63 | MaxWorkers: 10,
64 | RunI: &TestWorker{},
65 | }
66 |
67 | go func() {
68 | defer func() {
69 | err := recover()
70 | a.EqualError(err.(error), "send on closed channel")
71 | }()
72 | for {
73 | jobQueue <- struct {
74 | }{}
75 | }
76 | }()
77 |
78 | go func() {
79 | time.Sleep(100 * time.Millisecond)
80 | p.Stop()
81 | }()
82 |
83 | p.Run()
84 | }
85 |
--------------------------------------------------------------------------------