├── .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 | MixPHP 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 | [![使用 MixGo 快速开发 API 项目](https://openstr.com/cover/aa328ff33de085aa8fc87301056f3407.jpg?size=small&share=true)](https://openstr.com/watch/aa328ff33de085aa8fc87301056f3407) 16 | [![从 PHP 转 Go 的基础知识对比视频讲解](https://openstr.com/cover/41e9dc609cb8f9a4530fe8f7a37f1130.jpg?size=small&share=true)](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 |
6 | 10 | 14 | 15 |
16 | -------------------------------------------------------------------------------- /examples/web-skeleton/templates/user_add.tmpl: -------------------------------------------------------------------------------- 1 | 2 |

3 | {{ .title }} 4 |

5 |
6 | 10 | 11 |
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 | --------------------------------------------------------------------------------