├── .gitignore ├── LICENSE ├── README.en.md ├── README.md ├── docs ├── CHANGLOG.md └── todo.md ├── nav-site-server ├── .gitignore ├── .goreleaser.yaml ├── .version ├── README.md ├── api │ ├── auth.go │ ├── base.go │ ├── upload.go │ └── website.go ├── build.bat ├── config │ ├── code.go │ ├── config.go │ ├── config.yaml │ ├── config_test.go │ ├── json.go │ └── nav-site.service ├── extend │ └── util │ │ ├── common.go │ │ ├── file.go │ │ ├── jwt.go │ │ ├── jwt_test.go │ │ └── order.go ├── go.mod ├── go.sum ├── main.go ├── model │ ├── base.go │ ├── config │ │ ├── code.go │ │ ├── config.go │ │ └── json.go │ ├── file.go │ ├── file_test.go │ └── order2.go ├── script │ ├── install-npm.bat │ └── install-npm.sh └── server │ ├── app.go │ ├── html-handler.go │ ├── html-resource.go │ └── router.go └── nav-site-web ├── .env ├── .gitignore ├── .npmrc ├── README.md ├── babel.config.js ├── package.json ├── public ├── favicon.ico └── index.html ├── src ├── App.vue ├── api │ ├── auth.js │ ├── cookie.js │ ├── upload.js │ └── website.js ├── assets │ └── logo.png ├── components │ ├── Auth │ │ └── LoginForm.vue │ └── Web │ │ ├── WebFixedNav.vue │ │ ├── WebForm.vue │ │ ├── WebGroup.vue │ │ ├── WebItem.vue │ │ └── WebList.vue ├── main.js └── util │ └── request.js └── vue.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | 3 | dist/ 4 | dist.zip 5 | node_modules/ 6 | release/ 7 | **/dist/ 8 | test 9 | *-lock.* 10 | 11 | dist/ 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.en.md: -------------------------------------------------------------------------------- 1 | # gcwguide 2 | 3 | #### Description 4 | 公司内部的网址导航工具 5 | 6 | #### Software Architecture 7 | Software architecture description 8 | 9 | #### Installation 10 | 11 | 1. xxxx 12 | 2. xxxx 13 | 3. xxxx 14 | 15 | #### Instructions 16 | 17 | 1. xxxx 18 | 2. xxxx 19 | 3. xxxx 20 | 21 | #### Contribution 22 | 23 | 1. Fork the repository 24 | 2. Create Feat_xxx branch 25 | 3. Commit your code 26 | 4. Create Pull Request 27 | 28 | 29 | #### Gitee Feature 30 | 31 | 1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md 32 | 2. Gitee blog [blog.gitee.com](https://blog.gitee.com) 33 | 3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore) 34 | 4. The most valuable open source project [GVP](https://gitee.com/gvp) 35 | 5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help) 36 | 6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 迷你网址导航NavSite (mini navigate site) 2 | - 公司的各种资源太多, 记不住? 这个软件将帮你大忙, 公司所有人员只需要打开这一个网站就可以清楚知道公司内部所有资源 3 | - 一款简单易用的公司内部官网 4 | - 使用golang编写, 只运行一个文件 5 | - 没有第三方依赖, 数据保存为文件, 自己可任何备份 6 | - 前端:vue3 + element 7 | - 后端:golang + gin 8 | - 存储:文件存储,直接存储在服务器上,未使用任何数据库 9 | 10 | ## 介绍 11 | 使用与公司内部或个人的简易版网址导航工具 12 | 13 | ## 示例站点 14 | [demo](http://nav.cifaz.com:8083/) 15 | 16 | #### 操作账号 17 | - 其它帐号请参见配置文件 18 | - 用户名:add 19 | - 密码:123456 20 | - 权限:只能添加,不能删除和编辑 21 | 22 | ## 快速使用 23 | - 安装go语言环境, 请参与网上, 此处不列出, 安装好后, 请检测 24 | ``` 25 | go version 26 | 版本应该在1.16及以上 27 | ``` 28 | - 下载最新程序, 地址: [https://github.com/cifaz/nav-site/releases](https://github.com/cifaz/nav-site/releases) 29 | - 第一次运行时会自动创建默认配置目录和数据目录, 请根据需要修改 30 | - windows安装, 下载到你指定的目录解压后, 双击exe文件运行即可, 默认端口号:8083, 请使用http://ip:8083访问, 如需指定配置目录,请使用命令行运行, 具体参见linux参数 31 | - linux安装, 下载到指定目录后, 32 | ``` 33 | # 1. 安装golang环境包, 如有请跳过 34 | yum -y install golang wget 35 | # 2. 下载运行包 36 | cd /opt/ && wget https://github.com/cifaz/nav-site/releases/download/v0.0.18/nav-site_0.0.18_Linux_x86_64.tar.gz 37 | 38 | # 3. 解压安装包 39 | tar xzvf nav-site_0.0.18_Linux_x86_64.tar.gz --strip-components 1 -C nav-site 40 | mv nav-site_0.0.18_Linux_x86_64 nav-site 41 | 42 | # 4. 赋执行权限 43 | chmod u+x /opt/nav-site/nav-site 44 | 45 | # 5. 运行程序 46 | 普通运行 47 | /opt/nav-site/nav-site 48 | 49 | 指定配置目录运行 50 | /opt/nav-site/nav-site conf-dir=/opt/nav-site/ 51 | 52 | 后台运行 53 | nohup /opt/nav-site/nav-site > /opt/nav-site/nav-site.out 2>&1 & 54 | 55 | # 6. 登录创建数据即可 56 | 默认用户密码: admin 123456 57 | ``` 58 | - linux服务化 systemctl 59 | ``` 60 | 示例脚本仅适用于centos7/8, 其它系统请参考 61 | # 复制conf目录下的nav-site-server-centos7-8.service至并命名为/etc/systemd/system/nav-site.service 62 | cp /opt/nav-site/conf/nav-site.service /etc/systemd/system/nav-site.service 63 | 64 | # 重新载入配置 65 | systemctl daemon-reload 66 | # 启动程序 67 | systemctl start nav-site 68 | # 查看启动状态 69 | systemctl status nav-site.service 70 | # 查看端口是否被监听 71 | netstat -ntlp | grep :8083 72 | 73 | ``` 74 | - 程序配置介绍 75 | ``` 76 | conf目录, 为配置文件, 程序会自动生成conf/config.yaml, 请根据具体需要变更 77 | conf目录, 如果网站需要不同的favicon, 请将文件放在此目录, 程序会自动加载, 更多的自定义配置, 敬请期待... 78 | data目录为数据目录, 1.是JSON数据, 是存储网站导航和分组信息, 2.是网站上传的图片信息 79 | ``` 80 | - 当前版本不支持自动备份数据,请自行备份,备份时请同时备份conf和data目录 81 | 82 | ## 联系作者交流 83 | 作者邮箱: hanlin2531@163.com 84 | 85 | ## 感谢 86 | - 此版本原始设计为https://gitee.com/hyqc/gcwguide, 原作者不再维护, 为了更好的维护, 脱离原来的分支发展 87 | -------------------------------------------------------------------------------- /docs/CHANGLOG.md: -------------------------------------------------------------------------------- 1 | 2 | 2023-12-14 09:32:02 3 | 1. [文档] 修改linux安装过程,直接使用命令展示 4 | 2. [文档] 变更演示网站为nav.cifaz.com 5 | 3. [文档] config/nav-site-server-centos7-8.service变更为config/nav-site.service 6 | 4. [修改] 修改分组逻辑的新增, 编辑, 删除逻辑; -------------------------------------------------------------------------------- /docs/todo.md: -------------------------------------------------------------------------------- 1 | ### TODO list 2 | 3. 增加最近15个常用到本地, 可关闭显示 3 | 4. 全部使用日志打印 4 | 5. 增加备份功能 5 | 6 | ### 更新 2023年4月12日 7 | 1. [BUG] 恢复http限制, 后期再完善 8 | 9 | 10 | ### 更新 2023年4月1日 11 | 1. [修复] 保存新组时错误 12 | 2. [优化] 完善输入http时的限制, 自动变更为//开头的项目 13 | 3. [优化] 添加图标更换 14 | 4. [优化] 其它BUG修复 15 | 16 | ### 已完成 17 | 2. 启动时带参指定创建配置文件地址 18 | 1. 列表展示样式优化 19 | 4. 增加网站名称, LOGO, 底部等可配置项 20 | 5. 增加顶部标题 21 | 3. 默认组配置公共化, 如果本地没有配置, 自动生成默认配置 22 | 8. 增加启动时可指定配置位置, 存储位置, 上传根目录, 重新整理数据目录和配置目录 23 | 2. 增加主要环境发布流程及快速发布 24 | 11. 升级golang版本为16 25 | 12. 静态资源快速打包为一个包 26 | 13. 增加侧边浮动导航 27 | 14. 排序, 分组排序, 站点排序 28 | 15. 升级了vue最新版本 -------------------------------------------------------------------------------- /nav-site-server/.gitignore: -------------------------------------------------------------------------------- 1 | server/static 2 | dist/ 3 | *.exe 4 | conf/ 5 | data/ -------------------------------------------------------------------------------- /nav-site-server/.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | # Make sure to check the documentation at https://goreleaser.com 2 | project_name: nav-site 3 | before: 4 | hooks: 5 | - script\install-npm.bat 6 | # 请根据不同环境放开不同脚本windows为bat 7 | # - script\install-npm.sh 8 | builds: 9 | - env: 10 | - CGO_ENABLED=0 11 | # binary: nav-site/nav-site-server 12 | goos: 13 | - linux 14 | - darwin 15 | - windows 16 | goarch: 17 | - amd64 18 | - arm64 19 | ldflags: 20 | - -s -w -X main.build={{.Version}} 21 | archives: 22 | - replacements: 23 | darwin: macOS 24 | linux: Linux 25 | windows: Windows 26 | amd64: x86_64 27 | # meta: true 28 | wrap_in_directory: true 29 | format_overrides: 30 | - goos: windows 31 | format: zip 32 | # nav-site-server_0.0.2-next_Linux_arm64.tar 33 | # name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Runtime.Goos }}_{{ .Runtime.Goarch }}" 34 | checksum: 35 | name_template: 'checksums.txt' 36 | snapshot: 37 | name_template: "{{ incpatch .Version }}-next" 38 | changelog: 39 | sort: asc 40 | filters: 41 | exclude: 42 | - '^docs:' 43 | - '^test:' 44 | -------------------------------------------------------------------------------- /nav-site-server/.version: -------------------------------------------------------------------------------- 1 | 0.0.16 -------------------------------------------------------------------------------- /nav-site-server/README.md: -------------------------------------------------------------------------------- 1 | ## 后端调试 2 | - 安装golang >= 1.18 3 | - 配置go mod, 安装依赖 go mod tidy 4 | 5 | ### 前端调试 6 | ``` 7 | 直接调试 8 | 修改前端vue.config.js中配置的地址或端口 9 | 修改后端的启动端口 10 | (以上可不修改,如果本地不冲突) 11 | 单独运行前端代码和后端代码即可开始调试 12 | 13 | 将前端打包进go语言调试 14 | 将前端打包好的代码, 目录一般在nav-site-web/dist 15 | 复制到/nav-site-server/server/static下, 16 | 运行后端代码即可 17 | 18 | 快速部署前端代码到golang工程里, 进入script目录, 运行脚本, 19 | 如windows: PS E:\2project\golang\goland\001\NavSite\nav-site-server\script> .\install-npm.bat 20 | ``` 21 | 22 | ### 后台打包 23 | ``` 24 | 右键运行main.go即可 25 | ``` 26 | 27 | ### 后台打包 28 | ``` 29 | 打包环境说明 30 | GOOS: linux,windows,DARWIN,FREEBSD 31 | GOARCH: amd64,386,arm 32 | 33 | 编译linux 34 | go env -w CGO_ENABLED=0 35 | go env -w GOOS=LINUX 36 | go env -w GOARCH=amd64 37 | go build -ldflags '-s -w' 38 | 39 | 编译windows 40 | go env -w CGO_ENABLED=0 41 | go env -w GOOS=windows 42 | go env -w GOARCH=amd64 43 | go build -ldflags '-s -w' 44 | 45 | 1. Windows主机编译Linux,MAC客户端 46 | Windows主机编译Windows客户端 47 | go env -w CGO_ENABLED=0 48 | go env -w GOOS=windows 49 | go env -w GOARCH=amd64 50 | go build -ldflags '-s -w' 或 go build -o nav-site-server.exe main.go 51 | 52 | Windows主机编译LINUX客户端 53 | go env -w CGO_ENABLED=0 54 | go env -w GOOS=linux 55 | go env -w GOARCH=amd64 56 | go build -ldflags '-s -w' 或 go build -o nav-site-server main.go 57 | 58 | Windows主机编译MAC客户端 59 | go env -w CGO_ENABLED=0 60 | go env -w GOARCH=darwin 61 | go env -w GOARCH=amd64 62 | go build -ldflags '-s -w' 或 go build -o nav-site-server main.go 63 | 64 | 2 Linux主机编译Widows,MAC客户端 65 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o nav-site-server main.go 66 | CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o nav-site-server main.go 67 | CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o nav-site-server.exe main.go 68 | 69 | 3 MAC主机编译Widows,linux客户端 70 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o nav-site-server main.go 71 | CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o nav-site-server main.go 72 | CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o nav-site-server.exe main.go 73 | 74 | ``` 75 | 76 | #### 发布至github 77 | ``` 78 | 发布本地版本 79 | goreleaser release --auto-snapshot --rm-dist 80 | 81 | git tag -a v0.0.18 -m 'release v0.0.18' 82 | git push origin v0.0.18 83 | 84 | 发布到github上 85 | goreleaser release --rm-dist 86 | ``` 87 | 88 | ### 调试失效 89 | ```bash 90 | go get -u github.com/go-delve/delve/cmd/dlv@latest 91 | ``` 92 | -------------------------------------------------------------------------------- /nav-site-server/api/auth.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | conf "nav-site-server/config" 7 | "nav-site-server/extend/util" 8 | "strings" 9 | 10 | "github.com/gin-gonic/gin" 11 | ) 12 | 13 | type RequestLoginParams struct { 14 | Name string `json:"name"` 15 | Pwd string `json:"pwd"` 16 | } 17 | 18 | func Login(c *gin.Context) { 19 | app := conf.App 20 | output := conf.JsonOutput{} 21 | body, err := ioutil.ReadAll(c.Request.Body) 22 | if err != nil { 23 | output.Debug = err.Error() 24 | output.Code = conf.ParamsInvalid 25 | output.Data = nil 26 | output.Message = conf.ErrorMsg[conf.ParamsInvalid] 27 | response(c, output) 28 | return 29 | } 30 | data := RequestLoginParams{} 31 | if err := json.Unmarshal(body, &data); err != nil { 32 | output.Debug = err.Error() 33 | output.Code = conf.AuthLoginAccountInvalid 34 | output.Data = nil 35 | output.Message = conf.ErrorMsg[conf.AuthLoginAccountInvalid] 36 | response(c, output) 37 | return 38 | } 39 | 40 | if checkAccount(&data) == false { 41 | output.Debug = "account or password invalid" 42 | output.Code = conf.AuthLoginAccountInvalid 43 | output.Data = nil 44 | output.Message = conf.ErrorMsg[conf.AuthLoginAccountInvalid] 45 | response(c, output) 46 | return 47 | } 48 | j := util.JWT{} 49 | token, err := j.Make(data.Name, app.Account.Secret, app.Account.CookieExpireSeconds) 50 | if err != nil { 51 | output.Debug = err.Error() 52 | output.Code = conf.Error 53 | output.Data = nil 54 | output.Message = conf.ErrorMsg[conf.Error] 55 | response(c, output) 56 | return 57 | } 58 | res := make(map[string]interface{}) 59 | res["token"] = token 60 | res["expire"] = app.Account.CookieExpireSeconds 61 | output.Debug = "" 62 | output.Code = conf.Success 63 | output.Data = res 64 | output.Message = conf.ErrorMsg[conf.Success] 65 | response(c, output) 66 | return 67 | } 68 | 69 | func checkAccount(r *RequestLoginParams) bool { 70 | members := conf.App.Account.Members 71 | for _, user := range members { 72 | if user.Password == r.Pwd && user.Name == r.Name { 73 | return true 74 | } 75 | } 76 | return false 77 | } 78 | 79 | func checkAuth(name, methodType string) bool { 80 | members := conf.App.Account.Members 81 | admin := conf.App.Account.Admin 82 | for _, user := range members { 83 | if user.Name == admin { 84 | return true 85 | } 86 | if user.Name == name { 87 | auths := strings.Split(user.Rule, ",") 88 | for _, auth := range auths { 89 | return auth == methodType 90 | } 91 | } 92 | } 93 | return false 94 | } 95 | -------------------------------------------------------------------------------- /nav-site-server/api/base.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gin-gonic/gin" 6 | conf "nav-site-server/config" 7 | "net/http" 8 | "strings" 9 | ) 10 | 11 | func response(c *gin.Context, r conf.JsonOutput) { 12 | c.JSON(http.StatusOK, r) 13 | return 14 | } 15 | 16 | func getAuthorization(c *gin.Context) string { 17 | header := c.GetHeader("Authorization") 18 | fmt.Println("Authorization:", header) 19 | return strings.ReplaceAll(header, "Bearer ", "") 20 | } 21 | -------------------------------------------------------------------------------- /nav-site-server/api/upload.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/gin-gonic/gin" 7 | "log" 8 | conf "nav-site-server/config" 9 | "nav-site-server/extend/util" 10 | "path/filepath" 11 | "strings" 12 | ) 13 | 14 | // 上传单个图片 15 | func Image(c *gin.Context) { 16 | app := conf.App 17 | output := conf.JsonOutput{} 18 | _, headers, err := c.Request.FormFile("file") 19 | if err != nil { 20 | log.Println(err) 21 | output.Debug = err.Error() 22 | output.Code = conf.UploadFileError 23 | output.Data = nil 24 | output.Message = conf.ErrorMsg[conf.UploadFileError] 25 | response(c, output) 26 | return 27 | } 28 | //headers.Size 获取文件大小 29 | if headers.Size > app.Static.Upload.Maxsize { 30 | errMsg := fmt.Sprintf(conf.ErrorMsg[conf.UploadFileSizeOutRange], int(app.Static.Upload.Maxsize/1024/1024)) 31 | err := errors.New(errMsg) 32 | log.Println(err) 33 | output.Debug = err.Error() 34 | output.Code = conf.UploadFileSizeOutRange 35 | output.Data = nil 36 | output.Message = errMsg 37 | response(c, output) 38 | return 39 | } 40 | 41 | fileType := headers.Header.Get("Content-Type") 42 | if strings.HasPrefix(fileType, "image/") == false { 43 | errMsg := fmt.Sprintf(conf.ErrorMsg[conf.UploadFileTypeNotAllow], fileType, "image/*") 44 | err := errors.New(errMsg) 45 | log.Println(err) 46 | output.Debug = err.Error() 47 | output.Code = conf.UploadFileTypeNotAllow 48 | output.Data = nil 49 | output.Message = errMsg 50 | response(c, output) 51 | return 52 | } 53 | fileUtil := util.FileUtil{} 54 | if err := fileUtil.CreateFolderIfNotExist(getStaticUploadImagePath(), 0755); err != nil { 55 | log.Println(err) 56 | output.Debug = err.Error() 57 | output.Code = conf.UploadFileError 58 | output.Data = nil 59 | output.Message = err.Error() 60 | response(c, output) 61 | return 62 | } 63 | fileFullPath, fileNewName := makeNewFileName(headers.Filename) 64 | if err := c.SaveUploadedFile(headers, fileFullPath); err != nil { 65 | log.Println(err) 66 | output.Debug = err.Error() 67 | output.Code = conf.UploadFileError 68 | output.Data = nil 69 | output.Message = err.Error() 70 | response(c, output) 71 | return 72 | } 73 | data := make(map[string]interface{}) 74 | data["url"] = makeNewFileUrl(fileNewName) 75 | output.Debug = "" 76 | output.Code = conf.Success 77 | output.Data = data 78 | output.Message = conf.ErrorMsg[conf.Success] 79 | response(c, output) 80 | return 81 | } 82 | 83 | func makeNewFileName(filename string) (fileFullPath, fileNewName string) { 84 | fileNewName = util.CreateMD5(filename+util.CreateRandomUNID(12), true) + filepath.Ext(filename) 85 | fileFullPath = getStaticUploadImagePath() + fileNewName 86 | return fileFullPath, fileNewName 87 | } 88 | 89 | func makeNewFileUrl(filepath string) string { 90 | accessUrl := conf.App.Static.Upload.BaseUrl + "/data/" + conf.App.Static.Upload.Path + filepath 91 | if strings.HasSuffix(conf.App.Static.Upload.BaseUrl, "/") { 92 | accessUrl = conf.App.Static.Upload.BaseUrl + "data/" + conf.App.Static.Upload.Path + filepath 93 | } 94 | return accessUrl 95 | } 96 | 97 | func getStaticUploadImagePath() string { 98 | pathArr := []string{conf.App.Static.Root, "/data/", conf.App.Static.Upload.Path} 99 | uploadPath := strings.Join(pathArr, "") 100 | 101 | prefix := strings.HasPrefix(uploadPath, "/data") 102 | if prefix { 103 | uploadPath = uploadPath[1:] 104 | } 105 | 106 | return uploadPath 107 | } 108 | -------------------------------------------------------------------------------- /nav-site-server/api/website.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/gin-gonic/gin" 6 | log "github.com/sirupsen/logrus" 7 | "io/ioutil" 8 | conf "nav-site-server/config" 9 | "nav-site-server/extend/util" 10 | "nav-site-server/model" 11 | "strings" 12 | ) 13 | 14 | func WebSiteOrderUpdate(c *gin.Context) { 15 | // update order 16 | 17 | } 18 | 19 | func WetSiteInfo(c *gin.Context) { 20 | app := conf.App 21 | output := conf.JsonOutput{} 22 | site := app.Site 23 | site.HasLogo = conf.HasLogo 24 | 25 | output.Data = site 26 | output.Debug = "" 27 | output.Code = conf.Success 28 | output.Message = conf.ErrorMsg[conf.Success] 29 | 30 | response(c, output) 31 | return 32 | } 33 | 34 | func WebSiteList(c *gin.Context) { 35 | app := conf.App 36 | output := conf.JsonOutput{} 37 | data := make(map[string]interface{}) 38 | fileModel := model.WebsitesModel{} 39 | list, err := fileModel.List(app.Store.FileSync) 40 | if err != nil { 41 | output.Debug = err.Error() 42 | output.Code = conf.WebsiteListGetError 43 | data["rows"] = nil 44 | data["total"] = 0 45 | output.Data = data 46 | output.Message = conf.ErrorMsg[conf.WebsiteListGetError] 47 | response(c, output) 48 | return 49 | } 50 | if list == nil { 51 | data["rows"] = nil 52 | data["total"] = 0 53 | output.Data = data 54 | } else { 55 | mapList := fileModel.OrderWebSiteByOrder(list) 56 | 57 | //mapList := make(map[string][]model.WebsitesStoreItem) 58 | //for _, item := range list { 59 | // if item.Group == "" { 60 | // item.Group = model.WebsitesGroupDefault 61 | // } 62 | // if _, ok := mapList[item.Group]; ok == false { 63 | // mapList[item.Group] = make([]model.WebsitesStoreItem, 0) 64 | // } 65 | // mapList[item.Group] = append(mapList[item.Group], item) 66 | //} 67 | 68 | // TODO sorted 69 | //for key, val := range mapList { 70 | // util.SortBodyByField(val, "order") 71 | // mapList[key] = val 72 | //} 73 | 74 | data["rows"] = mapList 75 | data["total"] = len(list) 76 | output.Data = data 77 | } 78 | output.Debug = "" 79 | output.Code = conf.Success 80 | output.Message = conf.ErrorMsg[conf.Success] 81 | 82 | response(c, output) 83 | return 84 | } 85 | 86 | func WebSiteAdd(c *gin.Context) { 87 | app := conf.App 88 | output := conf.JsonOutput{} 89 | j := util.JWT{} 90 | name, err := j.Check(getAuthorization(c), app.Account.Secret) 91 | if err != nil { 92 | output.Debug = err.Error() 93 | output.Code = conf.AuthTokenAccountInvalid 94 | output.Data = nil 95 | output.Message = conf.ErrorMsg[conf.AuthTokenAccountInvalid] 96 | response(c, output) 97 | return 98 | } 99 | if checkAuth(name, conf.RuleAdd) == false { 100 | output.Debug = conf.ErrorMsg[conf.AuthTokenAccountNotAllowAdd] 101 | output.Code = conf.AuthTokenAccountNotAllowAdd 102 | output.Data = nil 103 | output.Message = conf.ErrorMsg[conf.AuthTokenAccountNotAllowAdd] 104 | response(c, output) 105 | return 106 | } 107 | body, err := ioutil.ReadAll(c.Request.Body) 108 | if err != nil { 109 | output.Debug = err.Error() 110 | output.Code = conf.ParamsInvalid 111 | output.Data = nil 112 | output.Message = conf.ErrorMsg[conf.ParamsInvalid] 113 | response(c, output) 114 | return 115 | } 116 | data := model.WebsitesStoreItem{} 117 | if err := json.Unmarshal(body, &data); err != nil { 118 | output.Debug = err.Error() 119 | output.Code = conf.WebsiteListAddError 120 | output.Data = nil 121 | output.Message = conf.ErrorMsg[conf.WebsiteListAddError] 122 | response(c, output) 123 | return 124 | } 125 | fileModel := model.WebsitesModel{} 126 | success := 0 127 | if success, err = fileModel.Add(app.Store.FileSync, data, app.Store.BackupsDir); err != nil { 128 | output.Debug = err.Error() 129 | output.Code = conf.WebsiteListAddError 130 | output.Data = nil 131 | output.Message = conf.ErrorMsg[conf.WebsiteListAddError] 132 | response(c, output) 133 | return 134 | } 135 | info := make(map[string]interface{}) 136 | info["success"] = success 137 | output.Debug = "" 138 | output.Code = conf.Success 139 | output.Data = info 140 | output.Message = conf.ErrorMsg[conf.Success] 141 | response(c, output) 142 | return 143 | } 144 | 145 | func WebSiteUpdate(c *gin.Context) { 146 | log.Info("WebSiteUpdate...") 147 | app := conf.App 148 | output := conf.JsonOutput{} 149 | j := util.JWT{} 150 | 151 | name, err := j.Check(getAuthorization(c), app.Account.Secret) 152 | if err != nil { 153 | output.Debug = err.Error() 154 | output.Code = conf.AuthTokenAccountInvalid 155 | output.Data = nil 156 | output.Message = conf.ErrorMsg[conf.AuthTokenAccountInvalid] 157 | response(c, output) 158 | return 159 | } 160 | if checkAuth(name, conf.RuleEdit) == false { 161 | output.Debug = conf.ErrorMsg[conf.AuthTokenAccountNotAllowEdit] 162 | output.Code = conf.AuthTokenAccountNotAllowEdit 163 | output.Data = nil 164 | output.Message = conf.ErrorMsg[conf.AuthTokenAccountNotAllowEdit] 165 | response(c, output) 166 | return 167 | } 168 | body, err := ioutil.ReadAll(c.Request.Body) 169 | if err != nil { 170 | output.Debug = err.Error() 171 | output.Code = conf.ParamsInvalid 172 | output.Data = nil 173 | output.Message = conf.ErrorMsg[conf.ParamsInvalid] 174 | response(c, output) 175 | return 176 | } 177 | data := model.WebsitesStoreItem{} 178 | if err := json.Unmarshal(body, &data); err != nil { 179 | output.Debug = err.Error() 180 | output.Code = conf.WebsiteListUpdateError 181 | output.Data = nil 182 | output.Message = conf.ErrorMsg[conf.WebsiteListUpdateError] 183 | response(c, output) 184 | return 185 | } 186 | fileModel := model.WebsitesModel{} 187 | success := 0 188 | if success, err = fileModel.Update(app.Store.FileSync, data, app.Store.BackupsDir); err != nil { 189 | output.Debug = err.Error() 190 | output.Code = conf.WebsiteListUpdateError 191 | output.Data = nil 192 | output.Message = conf.ErrorMsg[conf.WebsiteListUpdateError] 193 | response(c, output) 194 | return 195 | } 196 | info := make(map[string]interface{}) 197 | info["success"] = success 198 | output.Debug = "" 199 | output.Code = conf.Success 200 | output.Data = info 201 | output.Message = conf.ErrorMsg[conf.Success] 202 | response(c, output) 203 | return 204 | } 205 | 206 | func WebSiteDelete(c *gin.Context) { 207 | app := conf.App 208 | output := conf.JsonOutput{} 209 | j := util.JWT{} 210 | name, err := j.Check(getAuthorization(c), app.Account.Secret) 211 | if err != nil { 212 | output.Debug = err.Error() 213 | output.Code = conf.AuthTokenAccountInvalid 214 | output.Data = nil 215 | output.Message = conf.ErrorMsg[conf.AuthTokenAccountInvalid] 216 | response(c, output) 217 | return 218 | } 219 | if checkAuth(name, conf.RuleDelete) == false { 220 | output.Debug = conf.ErrorMsg[conf.AuthTokenAccountNotAllowDelete] 221 | output.Code = conf.AuthTokenAccountNotAllowDelete 222 | output.Data = nil 223 | output.Message = conf.ErrorMsg[conf.AuthTokenAccountNotAllowDelete] 224 | response(c, output) 225 | return 226 | } 227 | body, err := ioutil.ReadAll(c.Request.Body) 228 | if err != nil { 229 | output.Debug = err.Error() 230 | output.Code = conf.ParamsInvalid 231 | output.Data = nil 232 | output.Message = conf.ErrorMsg[conf.ParamsInvalid] 233 | response(c, output) 234 | return 235 | } 236 | data := model.RequestWebsitesDelete{} 237 | if err := json.Unmarshal(body, &data); err != nil { 238 | output.Debug = err.Error() 239 | output.Code = conf.WebsiteListDeleteError 240 | output.Data = nil 241 | output.Message = conf.ErrorMsg[conf.WebsiteListDeleteError] 242 | response(c, output) 243 | return 244 | } 245 | data.IDS = strings.TrimSpace(data.IDS) 246 | idsArr := strings.Split(data.IDS, ",") 247 | if len(idsArr) == 0 { 248 | output.Debug = "params ids is invalid" 249 | output.Code = conf.ParamsInvalid 250 | output.Data = nil 251 | output.Message = conf.ErrorMsg[conf.ParamsInvalid] 252 | response(c, output) 253 | return 254 | } 255 | 256 | fileModel := model.WebsitesModel{} 257 | success := 0 258 | if success, err = fileModel.Delete(app.Store.FileSync, idsArr, app.Store.BackupsDir); err != nil { 259 | output.Debug = err.Error() 260 | output.Code = conf.WebsiteListDeleteError 261 | output.Data = nil 262 | output.Message = conf.ErrorMsg[conf.WebsiteListDeleteError] 263 | response(c, output) 264 | return 265 | } 266 | info := make(map[string]interface{}) 267 | info["success"] = success 268 | output.Debug = "" 269 | output.Code = conf.Success 270 | output.Data = info 271 | output.Message = conf.ErrorMsg[conf.Success] 272 | response(c, output) 273 | return 274 | } 275 | 276 | func WebsiteGroups(c *gin.Context) { 277 | //app := conf.App 278 | output := conf.JsonOutput{} 279 | fileModel := model.WebsitesModel{} 280 | //list, err := fileModel.Groups(app.Store.FileSync) 281 | list, err := fileModel.GetGroupsOnly() 282 | if err != nil { 283 | output.Debug = err.Error() 284 | output.Code = conf.WebsiteListDeleteError 285 | output.Data = nil 286 | output.Message = conf.ErrorMsg[conf.WebsiteListDeleteError] 287 | response(c, output) 288 | return 289 | } 290 | output.Debug = "" 291 | output.Code = conf.Success 292 | output.Data = list 293 | output.Message = conf.ErrorMsg[conf.Success] 294 | response(c, output) 295 | return 296 | } 297 | 298 | // 组排序 299 | func WebSiteGroupOrder(c *gin.Context) { 300 | app := conf.App 301 | output := conf.JsonOutput{} 302 | j := util.JWT{} 303 | name, err := j.Check(getAuthorization(c), app.Account.Secret) 304 | if err != nil { 305 | output.Debug = err.Error() 306 | output.Code = conf.AuthTokenAccountInvalid 307 | output.Data = nil 308 | output.Message = conf.ErrorMsg[conf.AuthTokenAccountInvalid] 309 | response(c, output) 310 | return 311 | } 312 | if checkAuth(name, conf.RuleEdit) == false { 313 | output.Debug = conf.ErrorMsg[conf.AuthTokenAccountNotAllowEdit] 314 | output.Code = conf.AuthTokenAccountNotAllowEdit 315 | output.Data = nil 316 | output.Message = conf.ErrorMsg[conf.AuthTokenAccountNotAllowEdit] 317 | response(c, output) 318 | return 319 | } 320 | body, err := ioutil.ReadAll(c.Request.Body) 321 | if err != nil { 322 | output.Debug = err.Error() 323 | output.Code = conf.ParamsInvalid 324 | output.Data = nil 325 | output.Message = conf.ErrorMsg[conf.ParamsInvalid] 326 | response(c, output) 327 | return 328 | } 329 | 330 | var data []string 331 | if err := json.Unmarshal(body, &data); err != nil { 332 | output.Debug = err.Error() 333 | output.Code = conf.WebsiteListUpdateError 334 | output.Data = nil 335 | output.Message = conf.ErrorMsg[conf.WebsiteListUpdateError] 336 | response(c, output) 337 | return 338 | } 339 | 340 | fileModel := model.WebsitesModel{} 341 | success := 0 342 | if success, err = fileModel.AddGroupOrder(app.GroupStore.FileSync, data, app.Store.BackupsDir); err != nil { 343 | output.Debug = err.Error() 344 | output.Code = conf.WebsiteListUpdateError 345 | output.Data = nil 346 | output.Message = conf.ErrorMsg[conf.WebsiteListUpdateError] 347 | response(c, output) 348 | return 349 | } 350 | info := make(map[string]interface{}) 351 | info["success"] = success 352 | output.Debug = "" 353 | output.Code = conf.Success 354 | output.Data = info 355 | output.Message = conf.ErrorMsg[conf.Success] 356 | response(c, output) 357 | return 358 | } 359 | 360 | // 组-站点排序 361 | func WebSiteOrder(c *gin.Context) { 362 | app := conf.App 363 | output := conf.JsonOutput{} 364 | j := util.JWT{} 365 | name, err := j.Check(getAuthorization(c), app.Account.Secret) 366 | if err != nil { 367 | output.Debug = err.Error() 368 | output.Code = conf.AuthTokenAccountInvalid 369 | output.Data = nil 370 | output.Message = conf.ErrorMsg[conf.AuthTokenAccountInvalid] 371 | response(c, output) 372 | return 373 | } 374 | if checkAuth(name, conf.RuleEdit) == false { 375 | output.Debug = conf.ErrorMsg[conf.AuthTokenAccountNotAllowEdit] 376 | output.Code = conf.AuthTokenAccountNotAllowEdit 377 | output.Data = nil 378 | output.Message = conf.ErrorMsg[conf.AuthTokenAccountNotAllowEdit] 379 | response(c, output) 380 | return 381 | } 382 | body, err := ioutil.ReadAll(c.Request.Body) 383 | if err != nil { 384 | output.Debug = err.Error() 385 | output.Code = conf.ParamsInvalid 386 | output.Data = nil 387 | output.Message = conf.ErrorMsg[conf.ParamsInvalid] 388 | response(c, output) 389 | return 390 | } 391 | 392 | var data []model.WebsitesStoreItem 393 | if err := json.Unmarshal(body, &data); err != nil { 394 | output.Debug = err.Error() 395 | output.Code = conf.WebsiteListUpdateError 396 | output.Data = nil 397 | output.Message = conf.ErrorMsg[conf.WebsiteListUpdateError] 398 | response(c, output) 399 | return 400 | } 401 | 402 | fileModel := model.WebsitesModel{} 403 | success := 0 404 | if success, err = fileModel.UpdateWebSiteOrder(app.Store.FileSync, data, app.Store.BackupsDir); err != nil { 405 | output.Debug = err.Error() 406 | output.Code = conf.WebsiteListUpdateError 407 | output.Data = nil 408 | output.Message = conf.ErrorMsg[conf.WebsiteListUpdateError] 409 | response(c, output) 410 | return 411 | } 412 | info := make(map[string]interface{}) 413 | info["success"] = success 414 | output.Debug = "" 415 | output.Code = conf.Success 416 | output.Data = info 417 | output.Message = conf.ErrorMsg[conf.Success] 418 | response(c, output) 419 | return 420 | } 421 | -------------------------------------------------------------------------------- /nav-site-server/build.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | :loop 3 | @echo off&color 0A 4 | cls 5 | echo, 6 | echo 请选择要编译的系统环境: 7 | echo, 8 | echo 1. Windows_amd64 9 | echo 2. linux_amd64 10 | echo 3. linux_i386 11 | echo 4. All 12 | echo 0. quit 13 | echo, 14 | ::清空release目录... 15 | rmdir /s release /Q 16 | del app 17 | 18 | set/p action=请选择: 19 | if %action% == 1 goto build_Windows_amd64 20 | if %action% == 2 goto build_linux_amd64 21 | if %action% == 3 goto build_linux_i386 22 | if %action% == 4 goto all 23 | if %action% == 0 goto end 24 | cls & goto :loop 25 | 26 | :build_Windows_amd64 27 | echo 编译Windows版本64位 28 | SET CGO_ENABLED=0 29 | SET GOOS=windows 30 | SET GOARCH=amd64 31 | go build -v -a -o release/windows/amd64/app.exe 32 | echo 添加资源到工作目录并打包 33 | copy release\windows\amd64\app.exe . 34 | echo 打成zip包 35 | 7z.exe a app_Windows_amd64.zip conf/ docs/ static/ templates/ app.exe 36 | timeout /t 2 /nobreak 37 | del app.exe 38 | goto end 39 | 40 | :build_linux_amd64 41 | echo 编译Linux版本64位 42 | SET CGO_ENABLED=0 43 | SET GOOS=linux 44 | SET GOARCH=amd64 45 | go build -v -a -o release/linux/amd64/app 46 | echo 添加资源到工作目录并打包 47 | copy release\linux\amd64\app . 48 | echo 打成zip包 49 | 7z.exe a app_linux_amd64.zip conf/ docs/ static/ templates/ app 50 | timeout /t 2 /nobreak 51 | del app 52 | goto end 53 | 54 | :build_linux_i386 55 | echo 编译Linux版本32位 56 | SET CGO_ENABLED=0 57 | SET GOOS=linux 58 | SET GOARCH=386 59 | go build -v -a -o release/linux/i386/app 60 | echo 添加资源到工作目录并打包 61 | copy release\linux\i386\app . 62 | echo 打成zip包 63 | 7z.exe a app_linux_i386.zip conf/ docs/ static/ templates/ app 64 | timeout /t 2 /nobreak 65 | del app 66 | goto end 67 | 68 | :all 69 | echo 准备编译所有版本,请耐心等待... 70 | timeout /t 3 /nobreak 71 | ::删除之前的zip包 72 | del *.zip 73 | echo, 74 | 75 | echo 编译Windows版本64位 76 | SET CGO_ENABLED=0 77 | SET GOOS=windows 78 | SET GOARCH=amd64 79 | go build -v -a -o release/windows/amd64/app.exe 80 | echo 添加资源到工作目录并打包 81 | copy release\windows\amd64\app.exe . 82 | echo 打成zip包 83 | 7z.exe a app_Windows_amd64.zip conf/ docs/ static/ templates/ app.exe 84 | timeout /t 2 /nobreak 85 | del app.exe 86 | 87 | echo ===============我是分隔符===================== 88 | 89 | echo 编译Linux版本64位 90 | SET CGO_ENABLED=0 91 | SET GOOS=linux 92 | SET GOARCH=amd64 93 | go build -v -a -o release/linux/amd64/app 94 | echo 添加资源到工作目录并打包 95 | copy release\linux\amd64\app . 96 | echo 打成zip包 97 | 7z.exe a app_linux_amd64.zip conf/ docs/ static/ templates/ app 98 | timeout /t 2 /nobreak 99 | del app 100 | 101 | echo ===============我是分隔符===================== 102 | 103 | echo 编译Linux版本32位 104 | SET CGO_ENABLED=0 105 | SET GOOS=linux 106 | SET GOARCH=386 107 | go build -v -a -o release/linux/i386/app 108 | echo 添加资源到工作目录并打包 109 | copy release\linux\i386\app . 110 | echo 打成zip包 111 | 7z.exe a app_linux_i386.zip conf/ docs/ static/ templates/ app 112 | timeout /t 2 /nobreak 113 | del app 114 | 115 | :end 116 | @exit -------------------------------------------------------------------------------- /nav-site-server/config/code.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | var ErrorMsg = make(map[int]string) 4 | 5 | const ( 6 | Success = 0 7 | Error = 1000 8 | 9 | ParamsInvalid = 4001 10 | 11 | WebsiteListGetError = 5001 12 | WebsiteListAddError = 5002 13 | WebsiteListUpdateError = 5003 14 | WebsiteListDeleteError = 5004 15 | 16 | UploadFileError = 6000 17 | UploadFileSizeOutRange = 6001 18 | UploadFileTypeNotAllow = 6002 19 | 20 | AuthLoginAccountInvalid = 7000 21 | AuthTokenAccountInvalid = 7001 22 | AuthTokenAccountNotAllowAdd = 7002 23 | AuthTokenAccountNotAllowEdit = 7003 24 | AuthTokenAccountNotAllowDelete = 7004 25 | ) 26 | 27 | func init() { 28 | ErrorMsg[Success] = "请求成功" 29 | ErrorMsg[Error] = "请求失败" 30 | 31 | ErrorMsg[ParamsInvalid] = "无效请求参数" 32 | ErrorMsg[WebsiteListGetError] = "获取网站失败" 33 | ErrorMsg[WebsiteListAddError] = "添加网站失败" 34 | ErrorMsg[WebsiteListUpdateError] = "更新网站失败" 35 | ErrorMsg[WebsiteListDeleteError] = "删除网站失败" 36 | 37 | ErrorMsg[UploadFileError] = "上传文件失败" 38 | ErrorMsg[UploadFileSizeOutRange] = "上传文件的大小不能超出%dM" 39 | ErrorMsg[UploadFileTypeNotAllow] = "上传的文件类型错误%s,应为%s类型" 40 | 41 | ErrorMsg[AuthLoginAccountInvalid] = "账号或密码错误" 42 | ErrorMsg[AuthTokenAccountInvalid] = "登录已过期或未登录" 43 | ErrorMsg[AuthTokenAccountNotAllowAdd] = "您没有添加权限" 44 | ErrorMsg[AuthTokenAccountNotAllowEdit] = "您没有编辑权限" 45 | ErrorMsg[AuthTokenAccountNotAllowDelete] = "您没有删除权限" 46 | } 47 | -------------------------------------------------------------------------------- /nav-site-server/config/config.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "bufio" 5 | _ "embed" 6 | "errors" 7 | "fmt" 8 | "gopkg.in/yaml.v2" 9 | "nav-site-server/extend/util" 10 | "os" 11 | "path" 12 | "path/filepath" 13 | "strconv" 14 | "time" 15 | ) 16 | 17 | //go:embed config.yaml 18 | var DefaultConfigFile []byte 19 | 20 | //go:embed nav-site.service 21 | var NavSiteServerSystemCtl []byte 22 | 23 | var HasLogo bool = false 24 | 25 | type Program struct { 26 | // 程序所在目录 27 | ProgramDir string 28 | // 配置及数据目录 29 | ConfDir string 30 | } 31 | 32 | // Config server config 33 | type Config struct { 34 | Server Server `yaml:"server"` 35 | Site Site `yaml:"site"` 36 | Store Store `yaml:"store"` 37 | GroupStore Store `yaml:"GroupStore"` 38 | Static Static `yaml:"static"` 39 | Account Account `yaml:"account"` 40 | Program Program 41 | } 42 | 43 | // App application config 44 | type Server struct { 45 | Name string `yaml:"name"` 46 | Port string `yaml:"port"` 47 | } 48 | 49 | type Site struct { 50 | Title string `yaml:"title"` 51 | Logo string `yaml:"logo"` 52 | HasLogo bool 53 | Url string `yaml:"url"` 54 | Copyright string `yaml:"copyright"` 55 | } 56 | 57 | // Store store config 58 | type Store struct { 59 | Drive string `yaml:"drive"` 60 | Type string `yaml:"type"` 61 | Path string `yaml:"path"` 62 | Suffix string `yaml:"suffix"` 63 | BackupsDir string `yaml:"backupsDir"` 64 | BackupsMax int `yaml:"backupsMax"` 65 | FileSync *util.FileSync 66 | } 67 | 68 | type Static struct { 69 | Root string `yaml:"root"` 70 | Upload Upload `yaml:"upload"` 71 | } 72 | 73 | type Upload struct { 74 | Path string `yaml:"path"` 75 | Maxsize int64 `yaml:"maxsize"` 76 | BaseUrl string `yaml:"baseUrl"` 77 | } 78 | 79 | type Account struct { 80 | Secret string `yaml:"secret"` 81 | Admin string `yaml:"admin"` 82 | Members []User `yaml:"members"` 83 | CookieExpireSeconds time.Duration `yaml:"cookieExpireSeconds"` 84 | } 85 | 86 | type User struct { 87 | Name string `yaml:"name"` 88 | Rule string `yaml:"rule"` 89 | Password string `yaml:"password"` 90 | } 91 | 92 | var App Config 93 | 94 | const ( 95 | StoreDriveFile = "file" 96 | // RuleAdd 添加权限 97 | RuleAdd = "add" 98 | // RuleEdit 编辑权限 99 | RuleEdit = "edit" 100 | // RuleDelete 删除权限 101 | RuleDelete = "delete" 102 | ) 103 | 104 | func InitConfig(confDir string) (*Config, error) { 105 | release := util.IsRelease() 106 | var programPath = "" 107 | if !release { 108 | fmt.Println("非正式环境运行中..." + strconv.FormatBool(!release)) 109 | } else { 110 | path, _ := util.GetExecPath() 111 | fmt.Println("程序所在目录:" + path) 112 | programPath = path 113 | } 114 | 115 | var conf Config 116 | conf.Program.ConfDir = confDir 117 | conf.Program.ProgramDir = programPath 118 | confDataDir := conf.GetConfDataDir() 119 | fmt.Println("数据配置目录:" + confDataDir) 120 | 121 | confFileFull := path.Join(confDataDir, "conf/config.yaml") 122 | 123 | err := createConfAuto(confFileFull) 124 | if err != nil { 125 | return nil, err 126 | } 127 | config, err := util.ParseYaml(confFileFull) 128 | if err != nil { 129 | errMsg := errors.New("parse config.yaml file error : " + err.Error()) 130 | return nil, errMsg 131 | } 132 | 133 | if err := yaml.Unmarshal(config, &conf); err != nil { 134 | errMsg := errors.New("parse config []byte to struct error :" + err.Error()) 135 | return nil, errMsg 136 | } 137 | 138 | conf.initAccount() 139 | 140 | if err := conf.initStoreDrive(); err != nil { 141 | return &conf, nil 142 | } 143 | return &conf, nil 144 | } 145 | 146 | // 自动创建配置文件 147 | func createConfAuto(confFile string) error { 148 | confExists, _ := util.FileExists(confFile) 149 | 150 | if !confExists { 151 | baseDir := filepath.Dir(confFile) 152 | os.MkdirAll(baseDir, os.ModePerm) 153 | file, err := os.OpenFile(confFile, os.O_WRONLY|os.O_CREATE, 0644) 154 | defer file.Close() 155 | 156 | if err != nil { 157 | fmt.Println("create conf err") 158 | return err 159 | } 160 | 161 | //file.WriteAt(DefaultConfigFile, 0) 162 | writer := bufio.NewWriter(file) 163 | writer.Write(DefaultConfigFile) 164 | writer.Flush() 165 | 166 | } 167 | // 写出linux-centos8-9配置 168 | linuxServerConf := "conf/nav-site.service" 169 | linuxServerFile, _ := os.OpenFile(linuxServerConf, os.O_WRONLY|os.O_CREATE, 0644) 170 | defer linuxServerFile.Close() 171 | 172 | writer := bufio.NewWriter(linuxServerFile) 173 | writer.Write(NavSiteServerSystemCtl) 174 | writer.Flush() 175 | 176 | return nil 177 | } 178 | 179 | func (c *Config) GetConfDataDir() string { 180 | var confDir string 181 | if c.Program.ConfDir != "" { 182 | confDir = c.Program.ConfDir 183 | } else { 184 | confDir = c.Program.ProgramDir 185 | } 186 | return confDir 187 | } 188 | 189 | // initStoreDrive 初始化存储驱动 190 | func (c *Config) initStoreDrive() (err error) { 191 | switch c.Store.Drive { 192 | case StoreDriveFile: 193 | fallthrough 194 | default: 195 | confDataDir := c.GetConfDataDir() 196 | 197 | fileSync := util.FileSync{} 198 | webDataPath := path.Join(confDataDir, c.Store.Path) 199 | fileSync.FilePath = webDataPath 200 | if err = fileSync.InitStoreFile(webDataPath, 0755); err != nil { 201 | return err 202 | } 203 | c.Store.FileSync = &fileSync 204 | 205 | fileGroupSync := util.FileSync{} 206 | groupPath := path.Join(confDataDir, c.GroupStore.Path) 207 | fileGroupSync.FilePath = groupPath 208 | if err = fileGroupSync.InitStoreFile(groupPath, 0755); err != nil { 209 | return err 210 | } 211 | c.GroupStore.FileSync = &fileGroupSync 212 | 213 | if c.Store.BackupsMax <= 7 { 214 | c.Store.BackupsMax = 7 215 | } 216 | } 217 | return nil 218 | } 219 | 220 | func (c *Config) initAccount() { 221 | if c.Account.CookieExpireSeconds <= 7200 { 222 | c.Account.CookieExpireSeconds = 7200 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /nav-site-server/config/config.yaml: -------------------------------------------------------------------------------- 1 | server: 2 | name: nav-site-server 3 | port: 8083 4 | # 站点配置 5 | site: 6 | title: 迷你网址导航 7 | # logo, 放放置于conf文件夹下, 并命名为login.png 8 | # 标题点击的URL 9 | url: https://baidu.com 10 | # 格式: 可填写任意信息, 多行使用|分隔, 最后一个为版权信息, 格式为: 迷你网址导航 @ 2022-2022; 注:@后为固定值暂时不可变更 11 | copyright: 九十九次的理论不如一次的行动来得实际 | 迷你网址导航 12 | store: 13 | drive: file 14 | # 存储文件的类型 15 | type: json 16 | # 存储文件的目录 17 | path: data/json/webs.json 18 | # 备份文件的目录,暂不支持 19 | backupsDir: data/backups 20 | # 最大保留备最近份文件数量,暂不支持 21 | backupsMax: 30 22 | GroupStore: 23 | drive: file 24 | # 存储文件的类型 25 | type: json 26 | # 存储文件的目录 27 | path: data/json/groups.json 28 | # 备份文件的目录,暂不支持 29 | backupsDir: data/backups 30 | # 最大保留备最近份文件数量,暂不支持 31 | backupsMax: 30 32 | # 静态文件根目录 33 | static: 34 | # static: /data/nav-site-server/ 35 | # 默认为当前目录, 如果为空为当前目录, 可填写其它绝对路径 36 | root: "" 37 | # root: D:/tmp 38 | # 上传相关信息 39 | upload: 40 | # 请注意程序加自动加data开头, 不用重复加 41 | path: image/ 42 | maxsize: 2097152 43 | baseUrl: http://localhost:8083/ 44 | account: 45 | cookieExpireSeconds: 7200 46 | secert: nav-site-web 47 | admin: admin 48 | members: 49 | - name: admin 50 | rule: add,edit,delete 51 | password: 123456 52 | - name: save 53 | rule: add,edit 54 | password: 123456 55 | - name: add 56 | rule: add 57 | password: 123456 58 | - name: edit 59 | rule: edit 60 | password: 123456 61 | - name: delete 62 | rule: delete 63 | password: 123456 64 | 65 | -------------------------------------------------------------------------------- /nav-site-server/config/config_test.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "bufio" 5 | "os" 6 | "testing" 7 | ) 8 | 9 | func TestCreateConfAuto(t *testing.T) { 10 | confFile := "conf/config.yaml" 11 | os.MkdirAll("conf", os.ModePerm) 12 | file, _ := os.OpenFile(confFile, os.O_WRONLY|os.O_CREATE, 0644) 13 | defer file.Close() 14 | 15 | //file.WriteAt(DefaultConfigFile, 0) 16 | abc := "sdggggggggggggggggggggg" 17 | writer := bufio.NewWriter(file) 18 | //writer.Write(DefaultConfigFile) 19 | writer.Write([]byte(abc)) 20 | writer.Flush() 21 | } 22 | -------------------------------------------------------------------------------- /nav-site-server/config/json.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import "github.com/gin-gonic/gin" 4 | 5 | type JsonOutput struct { 6 | Code int `json:"code"` 7 | Message string `json:"message"` 8 | Data interface{} `json:"data"` 9 | Debug string `json:"debug"` 10 | } 11 | 12 | type JsonOutputList struct { 13 | Total int `json:"total"` 14 | Rows interface{} 15 | PageSize int 16 | PageNo int 17 | } 18 | 19 | func HandleNotFound(c *gin.Context) { 20 | handleErr := JsonOutput{} 21 | handleErr.Message = "not found : " + c.Request.Method + " " + c.Request.URL.String() 22 | c.JSON(handleErr.Code, handleErr) 23 | return 24 | } 25 | -------------------------------------------------------------------------------- /nav-site-server/config/nav-site.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Nav Site Api Service 3 | After=network.target 4 | 5 | [Service] 6 | Type=simple 7 | WorkingDirectory=/opt/nav-site/ 8 | ExecStart=/opt/nav-site/nav-site-server 9 | Restart=always 10 | 11 | [Install] 12 | WantedBy=multi-user.target 13 | -------------------------------------------------------------------------------- /nav-site-server/extend/util/common.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/hex" 6 | "fmt" 7 | "io/ioutil" 8 | "math/rand" 9 | "time" 10 | ) 11 | 12 | // ParseYaml 配置文件转[]byte 13 | func ParseYaml(filepath string) ([]byte, error) { 14 | data, err := ioutil.ReadFile(filepath) 15 | if err != nil { 16 | return nil, err 17 | } 18 | return data, nil 19 | } 20 | 21 | // CreateMD5 create a md5 string 22 | func CreateMD5(str string, long bool) string { 23 | h := md5.New() 24 | h.Write([]byte(str)) 25 | pwd := hex.EncodeToString(h.Sum(nil)) 26 | if long { 27 | return pwd 28 | } 29 | return pwd[8:24] 30 | } 31 | 32 | // CreateRandom @desc: CreateRandom crate a random string 33 | // @params l: length of the string 34 | // @paramss: is include special charters 35 | // @paramst:is include timestamp s 36 | // @return a random string 37 | func CreateRandom(l int, s, t bool) string { 38 | str := "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 39 | if s { 40 | str += "-+=@#$%^*!." 41 | } 42 | bytes := []byte(str) 43 | result := []byte{} 44 | r := rand.New(rand.NewSource(time.Now().UnixNano())) 45 | for i := 0; i < l; i++ { 46 | result = append(result, bytes[r.Intn(len(bytes))]) 47 | } 48 | if t { 49 | return string(result) + fmt.Sprintf("%d", time.Now().Unix()) 50 | } 51 | return string(result) 52 | } 53 | 54 | func CreateRandomUNID(l int) string { 55 | str := "123456789123456789123456789123456789" 56 | bytes := []byte(str) 57 | result := []byte{} 58 | r := rand.New(rand.NewSource(time.Now().UnixNano())) 59 | for i := 0; i < l; i++ { 60 | result = append(result, bytes[r.Intn(len(bytes))]) 61 | } 62 | return string(result) 63 | } 64 | 65 | // CreateUniqueID create a unique id 66 | func CreateUniqueID(prefix string) string { 67 | str := CreateMD5(prefix+CreateRandom(32, true, true), true) 68 | return CreateMD5(str, true) 69 | } 70 | 71 | // UintInArray 数组中是否存在该该值 72 | func UintInArray(id uint, arr []uint) bool { 73 | for _, val := range arr { 74 | if id == val { 75 | return true 76 | } 77 | } 78 | return false 79 | } 80 | 81 | // StringInArray 数组中是否存在该该值, 存在TRUE, 不存在FALSE 82 | func StringInArray(id string, arr []string) bool { 83 | for _, val := range arr { 84 | if id == val { 85 | return true 86 | } 87 | } 88 | return false 89 | } 90 | -------------------------------------------------------------------------------- /nav-site-server/extend/util/file.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "os" 7 | "os/exec" 8 | "path/filepath" 9 | "runtime" 10 | "strings" 11 | "sync" 12 | "time" 13 | ) 14 | 15 | type FileUtil struct { 16 | } 17 | 18 | // CreateFolderIfNotExist 文件夹不不存在则创建 19 | func (f *FileUtil) CreateFolderIfNotExist(dir string, mode os.FileMode) error { 20 | _, err := os.Stat(dir) 21 | if os.IsNotExist(err) { 22 | // 创建文件夹 23 | if err := os.MkdirAll(dir, mode); err != nil { 24 | return err 25 | } 26 | } 27 | return nil 28 | } 29 | 30 | // CreateFileIfNotExist 文件不存在则创建后打开 31 | func (f *FileUtil) CreateFileIfNotExist(path string, mode os.FileMode) (*os.File, error) { 32 | dir := filepath.Dir(path) 33 | if err := f.CreateFolderIfNotExist(dir, mode); err != nil { 34 | return nil, err 35 | } 36 | _, err := os.Stat(path) 37 | if err != nil { 38 | // 文件不存在 39 | file, err := os.Create(path) 40 | if err != nil { 41 | return nil, err 42 | } 43 | return file, nil 44 | } 45 | return os.OpenFile(path, os.O_RDWR|os.O_CREATE, mode) 46 | } 47 | 48 | // RemoveFileIfExist 文件存在则删除 49 | func (f *FileUtil) RemoveFileIfExist(path string) error { 50 | _, err := os.Stat(path) 51 | if os.IsExist(err) { 52 | return os.Remove(path) 53 | } 54 | return nil 55 | } 56 | 57 | type FileSync struct { 58 | File *os.File 59 | FilePath string 60 | FileFullPath string 61 | Type string 62 | FileInfo os.FileInfo 63 | rw sync.RWMutex //读写锁 64 | } 65 | 66 | // InitStoreFile 初始化文件存储 67 | func (f *FileSync) InitStoreFile(path string, mode os.FileMode) error { 68 | path, err := filepath.Abs(path) 69 | if err != nil { 70 | return err 71 | } 72 | f.FileFullPath = path 73 | fileUtil := FileUtil{} 74 | f.File, err = fileUtil.CreateFileIfNotExist(path, mode) 75 | if err != nil { 76 | return err 77 | } 78 | info, err := f.File.Stat() 79 | if err != nil { 80 | return err 81 | } 82 | f.FileInfo = info 83 | return nil 84 | } 85 | 86 | // CloseStoreFile 退出程序时关闭文件资源 87 | func (f *FileSync) CloseStoreFile() error { 88 | if f.File != nil { 89 | return f.File.Close() 90 | } 91 | return nil 92 | } 93 | 94 | // Read 文件安全读 95 | func (f *FileSync) ReadJSON() ([]byte, error) { 96 | //添加读写锁 97 | f.rw.Lock() 98 | defer func() { 99 | // 解锁 100 | f.rw.Unlock() 101 | }() 102 | if f.FileInfo != nil && f.FileInfo.Size() == 0 { 103 | return nil, nil 104 | } 105 | var content interface{} 106 | _, err := f.File.Seek(0, 0) 107 | if err != nil { 108 | return nil, err 109 | } 110 | err = json.NewDecoder(f.File).Decode(&content) 111 | if err != nil { 112 | return nil, err 113 | } 114 | return json.Marshal(content) 115 | } 116 | 117 | // Write 文件安全写 118 | func (f *FileSync) CoverJSON(content []byte) (err error) { 119 | //添加读写锁 120 | f.rw.Lock() 121 | defer func() { 122 | // 解锁 123 | f.rw.Unlock() 124 | }() 125 | err = f.File.Truncate(0) 126 | if err != nil { 127 | return err 128 | } 129 | f.File.Sync() 130 | _, err = f.File.WriteAt(content, 0) 131 | if err != nil { 132 | return err 133 | } 134 | info, err := f.File.Stat() 135 | if err != nil { 136 | return err 137 | } 138 | f.FileInfo = info 139 | return nil 140 | } 141 | 142 | // BackupsFileIfExist 文件备份 143 | func (f *FileSync) Backups(backupsDir string) (err error) { 144 | if f.File == nil { 145 | return errors.New("file target is nil") 146 | } 147 | // 备份文件名称 148 | newFileName := backupsDir + string(os.PathSeparator) + time.Now().Format("2006.01.02.15.04.05.999.") + f.Type 149 | newFileName, err = filepath.Abs(newFileName) 150 | if err != nil { 151 | return err 152 | } 153 | content, err := f.ReadJSON() 154 | if err != nil { 155 | return err 156 | } 157 | fileUtil := FileUtil{} 158 | if err = fileUtil.RemoveFileIfExist(newFileName); err != nil { 159 | return err 160 | } 161 | file, err := fileUtil.CreateFileIfNotExist(newFileName, 0755) 162 | if err != nil { 163 | return err 164 | } 165 | defer func() { 166 | _ = file.Close() 167 | }() 168 | fileModel := FileSync{} 169 | fileModel.File = file 170 | return fileModel.CoverJSON(content) 171 | } 172 | 173 | // FileExists 判断所给路径文件/文件夹是否存在(返回true是存在) 174 | func FileExists(path string) (bool, error) { 175 | _, err := os.Stat(path) 176 | if err == nil { 177 | return true, nil 178 | } 179 | 180 | if os.IsNotExist(err) { 181 | return false, err 182 | } 183 | 184 | return false, err 185 | } 186 | 187 | func IsRelease() bool { 188 | arg1 := strings.ToLower(os.Args[0]) 189 | return strings.Index(arg1, "go_build") < 0 && strings.Index(arg1, "go-build") < 0 190 | } 191 | 192 | func CurrentFile() string { 193 | _, file, _, ok := runtime.Caller(2) 194 | if !ok { 195 | panic(errors.New("Can not get current file info")) 196 | } 197 | return file 198 | } 199 | 200 | func GetExecPath() (string, error) { 201 | file, err := exec.LookPath(os.Args[0]) 202 | if err != nil { 203 | return "", err 204 | } 205 | path, err := filepath.Abs(file) 206 | if err != nil { 207 | return "", err 208 | } 209 | i := strings.LastIndex(path, "/") 210 | if i < 0 { 211 | i = strings.LastIndex(path, "\\") 212 | } 213 | if i < 0 { 214 | return "", errors.New(`error: Can't find "/" or "\".`) 215 | } 216 | return string(path[0 : i+1]), nil 217 | } 218 | -------------------------------------------------------------------------------- /nav-site-server/extend/util/jwt.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "github.com/dgrijalva/jwt-go" 6 | log "github.com/sirupsen/logrus" 7 | "time" 8 | ) 9 | 10 | type JWT struct { 11 | } 12 | 13 | func (j *JWT) Make(name, secret string, expire time.Duration) (string, error) { 14 | at := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ 15 | "name": name, 16 | "exp": time.Now().Add(expire * time.Second).Unix(), 17 | }) 18 | token, err := at.SignedString([]byte(secret)) 19 | if err != nil { 20 | return "", err 21 | } 22 | return token, nil 23 | } 24 | 25 | func (j *JWT) Check(token, secret string) (string, error) { 26 | //secretBytes, err := base64.URLEncoding.DecodeString(secret) 27 | log.Info("token:{}, secret: {}", token, secret) 28 | secretBytes := []byte(secret) 29 | claim, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { 30 | 31 | return secretBytes, nil 32 | }) 33 | if err != nil { 34 | fmt.Println("jwt error:", err) 35 | return "", err 36 | } 37 | return claim.Claims.(jwt.MapClaims)["name"].(string), nil 38 | } 39 | -------------------------------------------------------------------------------- /nav-site-server/extend/util/jwt_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestJWT_Make(t *testing.T) { 9 | jwt := JWT{} 10 | s, err := jwt.Make("admin", "nav-site-web", 7200) 11 | fmt.Println(s, err) 12 | } 13 | 14 | func TestJWT_Check(t *testing.T) { 15 | //app := conf.App 16 | 17 | jwt := JWT{} 18 | //token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NTc3MTY2NTIsIm5hbWUiOiJhZG1pbiJ9.10FOjxfYCkLP5FvQ78BZcBQ2g5Ync9-vTGwgVQT1wqY" 19 | //token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NTc3MTg2ODIsIm5hbWUiOiJhZG1pbiJ9.UbRmnvlpD8cUoZgUxzhudq3B3Aj7nWE9BAXYZITfips" 20 | //token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NTc3MTg3NjgsIm5hbWUiOiJhZG1pbiJ9.1VoxIJY7GgtGqQzZf7fOEAiJB6hsNqz9dCSoUodHyXw" 21 | token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NTc3MjcyMTksIm5hbWUiOiJhZG1pbiJ9.ujyGwZW0_pd3bd7fimfWFvKIUzPGtjvDkasx2OapKZE" 22 | check, err := jwt.Check(token, "nav-site-web") 23 | fmt.Println("check:", check, err) 24 | } 25 | -------------------------------------------------------------------------------- /nav-site-server/extend/util/order.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "reflect" 5 | "sort" 6 | ) 7 | 8 | //通用排序 9 | //结构体排序,必须重写数组Len() Swap() Less()函数 10 | type body_wrapper struct { 11 | Bodys []struct{} 12 | by func(p, q *struct{}) bool //内部Less()函数会用到 13 | } 14 | type SortBodyBy func(p, q *struct{}) bool //定义一个函数类型 15 | 16 | //数组长度Len() 17 | func (acw body_wrapper) Len() int { 18 | return len(acw.Bodys) 19 | } 20 | 21 | //元素交换 22 | func (acw body_wrapper) Swap(i, j int) { 23 | acw.Bodys[i], acw.Bodys[j] = acw.Bodys[j], acw.Bodys[i] 24 | } 25 | 26 | //比较函数,使用外部传入的by比较函数 27 | func (acw body_wrapper) Less(i, j int) bool { 28 | return acw.by(&acw.Bodys[i], &acw.Bodys[j]) 29 | } 30 | 31 | //自定义排序字段,参考SortBodyByCreateTime中的传入函数 32 | func SortBody(bodys []struct{}, by SortBodyBy) { 33 | sort.Sort(body_wrapper{bodys, by}) 34 | } 35 | 36 | //按照createtime排序,需要注意是否有createtime 37 | func SortBodyByField(bodys []struct{}, filed string) { 38 | sort.Sort(body_wrapper{bodys, func(p, q *struct{}) bool { 39 | v := reflect.ValueOf(*p) 40 | i := v.FieldByName(filed) 41 | v = reflect.ValueOf(*q) 42 | j := v.FieldByName(filed) 43 | return i.String() > j.String() 44 | }}) 45 | } 46 | -------------------------------------------------------------------------------- /nav-site-server/go.mod: -------------------------------------------------------------------------------- 1 | module nav-site-server 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/dgrijalva/jwt-go v3.2.0+incompatible 7 | github.com/gin-gonic/gin v1.7.1 8 | github.com/sirupsen/logrus v1.9.0 9 | gopkg.in/yaml.v2 v2.4.0 10 | ) 11 | 12 | require ( 13 | github.com/cilium/ebpf v0.10.0 // indirect 14 | github.com/derekparker/trie v0.0.0-20221221181808-1424fce0c981 // indirect 15 | github.com/go-delve/delve v1.20.1 // indirect 16 | github.com/google/go-cmp v0.5.9 // indirect 17 | github.com/google/go-dap v0.7.0 // indirect 18 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 19 | github.com/kr/pretty v0.3.1 // indirect 20 | github.com/mattn/go-colorable v0.1.13 // indirect 21 | github.com/mattn/go-isatty v0.0.18 // indirect 22 | github.com/mattn/go-runewidth v0.0.14 // indirect 23 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 24 | github.com/modern-go/reflect2 v1.0.1 // indirect 25 | github.com/rivo/uniseg v0.4.4 // indirect 26 | github.com/spf13/cobra v1.6.1 // indirect 27 | go.starlark.net v0.0.0-20230302034142-4b1e35fe2254 // indirect 28 | golang.org/x/arch v0.3.0 // indirect 29 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect 30 | google.golang.org/protobuf v1.25.0 // indirect 31 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect 32 | gopkg.in/yaml.v3 v3.0.1 // indirect 33 | ) 34 | -------------------------------------------------------------------------------- /nav-site-server/go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 8 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 9 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 10 | cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= 11 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 12 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 13 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 14 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 15 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 16 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 17 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 18 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 19 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= 20 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= 21 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 22 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 23 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 24 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 25 | github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= 26 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 27 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 28 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 29 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 30 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 31 | github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= 32 | github.com/cilium/ebpf v0.10.0 h1:nk5HPMeoBXtOzbkZBWym+ZWq1GIiHUsBFXxwewXAHLQ= 33 | github.com/cilium/ebpf v0.10.0/go.mod h1:DPiVdY/kT534dgc9ERmvP8mWA+9gvwgKfRvk4nNWnoE= 34 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 35 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 36 | github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 37 | github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 38 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 39 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 40 | github.com/cosiner/argv v0.1.0 h1:BVDiEL32lwHukgJKP87btEPenzrrHUjajs/8yzaqcXg= 41 | github.com/cosiner/argv v0.1.0/go.mod h1:EusR6TucWKX+zFgtdUsKT2Cvg45K5rtpCcWz4hK06d8= 42 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 43 | github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= 44 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 45 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 46 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 47 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 48 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 49 | github.com/derekparker/trie v0.0.0-20200317170641-1fdf38b7b0e9/go.mod h1:D6ICZm05D9VN1n/8iOtBxLpXtoGp6HDFUJ1RNVieOSE= 50 | github.com/derekparker/trie v0.0.0-20221221181808-1424fce0c981 h1:zPGs5GlTifgPVykTSvWB6/+J7EefOmYqH/JuUBI3kf0= 51 | github.com/derekparker/trie v0.0.0-20221221181808-1424fce0c981/go.mod h1:C7Es+DLenIpPc9J6IYw4jrK0h7S9bKj4DNl8+KxGEXU= 52 | github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= 53 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 54 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= 55 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 56 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 57 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 58 | github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= 59 | github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 60 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 61 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 62 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 63 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 64 | github.com/gin-gonic/gin v1.7.1 h1:qC89GU3p8TvKWMAVhEpmpB2CIb1hnqt2UdKZaP93mS8= 65 | github.com/gin-gonic/gin v1.7.1/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= 66 | github.com/go-delve/delve v1.20.1 h1:km9RA+oUw6vd/mYL4EGcT5DsqGE0w/To8zFq0ZRj4PQ= 67 | github.com/go-delve/delve v1.20.1/go.mod h1:oeVm2dZ1zgc9wWHGv6dUarkLBPyMvVEBi6RJiW8BORI= 68 | github.com/go-delve/liner v1.2.3-0.20220127212407-d32d89dd2a5d h1:pxjSLshkZJGLVm0wv20f/H0oTWiq/egkoJQ2ja6LEvo= 69 | github.com/go-delve/liner v1.2.3-0.20220127212407-d32d89dd2a5d/go.mod h1:biJCRbqp51wS+I92HMqn5H8/A0PAhxn2vyOT+JqhiGI= 70 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 71 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 72 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 73 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 74 | github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= 75 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 76 | github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= 77 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= 78 | github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= 79 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= 80 | github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= 81 | github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= 82 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 83 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 84 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 85 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 86 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 87 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 88 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 89 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 90 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 91 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 92 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 93 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 94 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 95 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 96 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 97 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 98 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 99 | github.com/golang/protobuf v1.4.1 h1:ZFgWrT+bLgsYPirOnRfKLYJLvssAegOj/hgyMFdJZe0= 100 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 101 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 102 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 103 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 104 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 105 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 106 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 107 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 108 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 109 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 110 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 111 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 112 | github.com/google/go-dap v0.6.0/go.mod h1:5q8aYQFnHOAZEMP+6vmq25HKYAEwE+LF5yh7JKrrhSQ= 113 | github.com/google/go-dap v0.7.0 h1:088PdKBUkxAxrXrnY8FREUJXpS6Y6jhAyZIuJv3OGOM= 114 | github.com/google/go-dap v0.7.0/go.mod h1:5q8aYQFnHOAZEMP+6vmq25HKYAEwE+LF5yh7JKrrhSQ= 115 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 116 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 117 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 118 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 119 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 120 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 121 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 122 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 123 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 124 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 125 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 126 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 127 | github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= 128 | github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= 129 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 130 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 131 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 132 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 133 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 134 | github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= 135 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= 136 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= 137 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 138 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 139 | github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= 140 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 141 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 142 | github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= 143 | github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 144 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 145 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= 146 | github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= 147 | github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= 148 | github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= 149 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 150 | github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 151 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 152 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 153 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 154 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 155 | github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= 156 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 157 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 158 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 159 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 160 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 161 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 162 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 163 | github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 164 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 165 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 166 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 167 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 168 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 169 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 170 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 171 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 172 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 173 | github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= 174 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= 175 | github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 176 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 177 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 178 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 179 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 180 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 181 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 182 | github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= 183 | github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 184 | github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 185 | github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 186 | github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= 187 | github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 188 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 189 | github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= 190 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= 191 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 192 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 193 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 194 | github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= 195 | github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= 196 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 197 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 198 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 199 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 200 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 201 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 202 | github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= 203 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 204 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 205 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 206 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 207 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 208 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 209 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 210 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 211 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 212 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 213 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 214 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 215 | github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= 216 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 217 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 218 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 219 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 220 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 221 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 222 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 223 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= 224 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 225 | github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= 226 | github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 227 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 228 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 229 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= 230 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 231 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 232 | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= 233 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 234 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 235 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= 236 | github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= 237 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 238 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 239 | github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= 240 | github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= 241 | github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 242 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 243 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 244 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 245 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 246 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 247 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 248 | github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= 249 | github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= 250 | github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= 251 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 252 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 253 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 254 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 255 | github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= 256 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 257 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 258 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 259 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 260 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 261 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 262 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 263 | github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= 264 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 265 | github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= 266 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= 267 | github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= 268 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= 269 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 270 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 271 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 272 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 273 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 274 | go.starlark.net v0.0.0-20220816155156-cfacd8902214/go.mod h1:VZcBMdr3cT3PnBoWunTabuSEXwVAH+ZJ5zxfs3AdASk= 275 | go.starlark.net v0.0.0-20230302034142-4b1e35fe2254 h1:Ss6D3hLXTM0KobyBYEAygXzFfGcjnmfEJOBgSbemCtg= 276 | go.starlark.net v0.0.0-20230302034142-4b1e35fe2254/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= 277 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 278 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 279 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 280 | golang.org/x/arch v0.0.0-20190927153633-4e8777c89be4/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4= 281 | golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= 282 | golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 283 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 284 | golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 285 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 286 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 287 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 288 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 289 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= 290 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 291 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 292 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 293 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 294 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 295 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 296 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 297 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 298 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 299 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 300 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 301 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 302 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 303 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 304 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 305 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 306 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 307 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 308 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 309 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 310 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 311 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 312 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 313 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 314 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 315 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 316 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 317 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 318 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 319 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 320 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 321 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 322 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 323 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 324 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 325 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 326 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 327 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 328 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 329 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 330 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 331 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 332 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 333 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 334 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 335 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 336 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 337 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 338 | golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 339 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 340 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 341 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 342 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 343 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 344 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 345 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 346 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 347 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 348 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 349 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 350 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 351 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 352 | golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 353 | golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 354 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 355 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 356 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 357 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 358 | golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 359 | golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 360 | golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= 361 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 362 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 363 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 364 | golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 365 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 366 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 367 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 368 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 369 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 370 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 371 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 372 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 373 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 374 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 375 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 376 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 377 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 378 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 379 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 380 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 381 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 382 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 383 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 384 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 385 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 386 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 387 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 388 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 389 | golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 390 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 391 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 392 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 393 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 394 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 395 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 396 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 397 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 398 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 399 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 400 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 401 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 402 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 403 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 404 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 405 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 406 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 407 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 408 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 409 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 410 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 411 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 412 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 413 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 414 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 415 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 416 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 417 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 418 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 419 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 420 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 421 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 422 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 423 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 424 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 425 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 426 | google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= 427 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 428 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 429 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 430 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 431 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 432 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 433 | gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 434 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 435 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 436 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 437 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 438 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 439 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 440 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 441 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 442 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 443 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 444 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 445 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 446 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 447 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 448 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 449 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 450 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 451 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 452 | -------------------------------------------------------------------------------- /nav-site-server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "nav-site-server/server" 5 | ) 6 | 7 | func main() { 8 | server.Run() 9 | } 10 | -------------------------------------------------------------------------------- /nav-site-server/model/base.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Model struct { 4 | } 5 | -------------------------------------------------------------------------------- /nav-site-server/model/config/code.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | var ErrorMsg = make(map[int]string) 4 | 5 | const ( 6 | Success = 0 7 | Error = 1000 8 | 9 | ParamsInvalid = 4001 10 | 11 | WebsiteListGetError = 5001 12 | WebsiteListAddError = 5002 13 | WebsiteListUpdateError = 5003 14 | WebsiteListDeleteError = 5004 15 | 16 | UploadFileError = 6000 17 | UploadFileSizeOutRange = 6001 18 | UploadFileTypeNotAllow = 6002 19 | 20 | AuthLoginAccountInvalid = 7000 21 | AuthTokenAccountInvalid = 7001 22 | AuthTokenAccountNotAllowAdd = 7002 23 | AuthTokenAccountNotAllowEdit = 7003 24 | AuthTokenAccountNotAllowDelete = 7004 25 | ) 26 | 27 | func init() { 28 | ErrorMsg[Success] = "请求成功" 29 | ErrorMsg[Error] = "请求失败" 30 | 31 | ErrorMsg[ParamsInvalid] = "无效请求参数" 32 | ErrorMsg[WebsiteListGetError] = "获取网站失败" 33 | ErrorMsg[WebsiteListAddError] = "添加网站失败" 34 | ErrorMsg[WebsiteListUpdateError] = "更新网站失败" 35 | ErrorMsg[WebsiteListDeleteError] = "删除网站失败" 36 | 37 | ErrorMsg[UploadFileError] = "上传文件失败" 38 | ErrorMsg[UploadFileSizeOutRange] = "上传文件的大小不能超出%dM" 39 | ErrorMsg[UploadFileTypeNotAllow] = "上传的文件类型错误%s,应为%s类型" 40 | 41 | ErrorMsg[AuthLoginAccountInvalid] = "账号或密码错误" 42 | ErrorMsg[AuthTokenAccountInvalid] = "登录已过期或未登录" 43 | ErrorMsg[AuthTokenAccountNotAllowAdd] = "您没有添加权限" 44 | ErrorMsg[AuthTokenAccountNotAllowEdit] = "您没有编辑权限" 45 | ErrorMsg[AuthTokenAccountNotAllowDelete] = "您没有删除权限" 46 | } 47 | -------------------------------------------------------------------------------- /nav-site-server/model/config/config.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "errors" 5 | "gopkg.in/yaml.v2" 6 | "nav-site-server/extend/util" 7 | "time" 8 | ) 9 | 10 | // Config server config 11 | type Config struct { 12 | Server Server `yaml:"server"` 13 | Store Store `yaml:"store"` 14 | Static Static `yaml:"static"` 15 | Account Account `yaml:"account"` 16 | } 17 | 18 | // App application config 19 | type Server struct { 20 | Name string `yaml:"name"` 21 | Port string `yaml:"port"` 22 | } 23 | 24 | // Store store config 25 | type Store struct { 26 | Drive string `yaml:"drive"` 27 | Type string `yaml:"type"` 28 | Path string `yaml:"path"` 29 | Suffix string `yaml:"suffix"` 30 | BackupsDir string `yaml:"backupsDir"` 31 | BackupsMax int `yaml:"backupsMax"` 32 | FileSync *util.FileSync 33 | } 34 | 35 | type Static struct { 36 | Static string `yaml:"static"` 37 | Upload Upload `yaml:"upload"` 38 | } 39 | 40 | type Upload struct { 41 | Path string `yaml:"path"` 42 | Maxsize int64 `yaml:"maxsize"` 43 | BaseUrl string `yaml:"baseUrl"` 44 | } 45 | 46 | type Account struct { 47 | Secret string `yaml:"secret"` 48 | Admin string `yaml:"admin"` 49 | Members []User `yaml:"members"` 50 | CookieExpireSeconds time.Duration `yaml:"cookieExpireSeconds"` 51 | } 52 | 53 | type User struct { 54 | Name string `yaml:"name"` 55 | Rule string `yaml:"rule"` 56 | Password string `yaml:"password"` 57 | } 58 | 59 | var App Config 60 | 61 | const ( 62 | StoreDriveFile = "file" 63 | // RuleAdd 添加权限 64 | RuleAdd = "add" 65 | // RuleEdit 编辑权限 66 | RuleEdit = "edit" 67 | // RuleDelete 删除权限 68 | RuleDelete = "delete" 69 | ) 70 | 71 | func InitConfig() (*Config, error) { 72 | config, err := util.ParseYaml("config/config.yaml") 73 | if err != nil { 74 | errMsg := errors.New("parse config.yaml file error : " + err.Error()) 75 | return nil, errMsg 76 | } 77 | var c Config 78 | if err := yaml.Unmarshal(config, &c); err != nil { 79 | errMsg := errors.New("parse config []byte to struct error :" + err.Error()) 80 | return nil, errMsg 81 | } 82 | 83 | c.initAccount() 84 | 85 | if err := c.initStoreDrive(); err != nil { 86 | return &c, nil 87 | } 88 | return &c, nil 89 | } 90 | 91 | // initStoreDrive 初始化存储驱动 92 | func (c *Config) initStoreDrive() (err error) { 93 | switch c.Store.Drive { 94 | case StoreDriveFile: 95 | fallthrough 96 | default: 97 | fileSync := util.FileSync{} 98 | fileSync.FilePath = c.Store.Path 99 | if err = fileSync.InitStoreFile(c.Store.Path, 0755); err != nil { 100 | return err 101 | } 102 | c.Store.FileSync = &fileSync 103 | if c.Store.BackupsMax <= 7 { 104 | c.Store.BackupsMax = 7 105 | } 106 | } 107 | return nil 108 | } 109 | 110 | func (c *Config) initAccount() { 111 | if c.Account.CookieExpireSeconds <= 7200 { 112 | c.Account.CookieExpireSeconds = 7200 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /nav-site-server/model/config/json.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import "github.com/gin-gonic/gin" 4 | 5 | type JsonOutput struct { 6 | Code int `json:"code"` 7 | Message string `json:"message"` 8 | Data interface{} `json:"data"` 9 | Debug string `json:"debug"` 10 | } 11 | 12 | type JsonOutputList struct { 13 | Total int `json:"total"` 14 | Rows interface{} 15 | PageSize int 16 | PageNo int 17 | } 18 | 19 | func HandleNotFound(c *gin.Context) { 20 | handleErr := JsonOutput{} 21 | handleErr.Message = "not found : " + c.Request.Method + " " + c.Request.URL.String() 22 | c.JSON(handleErr.Code, handleErr) 23 | return 24 | } 25 | -------------------------------------------------------------------------------- /nav-site-server/model/file.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | conf "nav-site-server/config" 7 | "nav-site-server/extend/util" 8 | "strconv" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | type WebsitesModel struct { 14 | Model 15 | } 16 | 17 | // WebsitesStoreItem 存储的站点信息 18 | type WebsitesStoreItem struct { 19 | ID string `json:"id"` // 网站ID 20 | Group string `json:"group"` // 网站分组 21 | Order int `json:"order"` // 网站分组 22 | Name string `json:"name"` // 网站名称 23 | Pic string `json:"pic"` // 网站图标 24 | Host string `json:"host"` // 网站地址 25 | Desc string `json:"desc"` // 网站描述 26 | Create string `json:"create"` // 添加时间 27 | Update string `json:"update"` // 更新时间 28 | } 29 | 30 | const ( 31 | // WebsitesGroupDefault 默认分组名称 32 | WebsitesGroupDefault = "default" 33 | ) 34 | 35 | // List 获取站点列表 36 | func (w *WebsitesModel) List(fileSync *util.FileSync) ([]WebsitesStoreItem, error) { 37 | content, err := fileSync.ReadJSON() 38 | if err != nil { 39 | return nil, err 40 | } 41 | if content == nil { 42 | return nil, nil 43 | } 44 | list := make([]WebsitesStoreItem, 0) 45 | if err = json.Unmarshal(content, &list); err != nil { 46 | return nil, err 47 | } 48 | return list, nil 49 | } 50 | 51 | // List 获取所有分组信息 52 | func (w *WebsitesModel) ListGroupOrder(fileSync *util.FileSync) ([]string, error) { 53 | content, err := fileSync.ReadJSON() 54 | if err != nil { 55 | return nil, err 56 | } 57 | if content == nil { 58 | return nil, nil 59 | } 60 | list := make([]string, 0) 61 | if err = json.Unmarshal(content, &list); err != nil { 62 | return nil, err 63 | } 64 | return list, nil 65 | } 66 | 67 | // Add 添加站点 68 | func (w *WebsitesModel) Add(fileSync *util.FileSync, data WebsitesStoreItem, backupsDir string) (int, error) { 69 | // 读取 70 | list := make([]WebsitesStoreItem, 0) 71 | list, err := w.List(fileSync) 72 | if err != nil { 73 | return 0, err 74 | } 75 | 76 | data.Create = time.Now().Format(time.RFC3339) 77 | data.Update = data.Create 78 | data.Group = strings.TrimSpace(data.Group) 79 | if data.Group == "" { 80 | data.Group = WebsitesGroupDefault 81 | } 82 | data.ID = util.CreateMD5(data.Group+data.Host, true) 83 | for _, item := range list { 84 | if item.ID == data.ID { 85 | return 0, nil 86 | } 87 | if item.Group == "" { 88 | item.Group = WebsitesGroupDefault 89 | } 90 | } 91 | 92 | // 如果有新组则增加组 93 | w.AddGroups(data.Group) 94 | 95 | // 获取当前组有多少个, 并返回合适的order 96 | lastOrder := w.GetLastOrderInGroup(data.Group, list) + 1 97 | data.Order = lastOrder 98 | fmt.Println("lastOrder", lastOrder) 99 | 100 | list = append(list, data) 101 | if err := w.save(list, fileSync); err != nil { 102 | return 0, err 103 | } 104 | return 1, nil 105 | } 106 | 107 | // AddGroups 添加站点 108 | func (w *WebsitesModel) AddGroups(group string) { 109 | groups, _ := w.GetGroupsOnly() 110 | 111 | isExist := false 112 | for _, group1 := range groups { 113 | if group1 == group { 114 | isExist = true 115 | } 116 | } 117 | if !isExist { 118 | groups = append(groups, group) 119 | 120 | _, err := w.AddGroupOrder(conf.App.GroupStore.FileSync, groups, "") 121 | if err != nil { 122 | return 123 | } 124 | } 125 | } 126 | 127 | // AddGroupOrder Add 添加站点分组 128 | func (w *WebsitesModel) AddGroupOrder(groupSync *util.FileSync, data []string, backupsDir string) (int, error) { 129 | if err := w.saveGroupsString(data, groupSync); err != nil { 130 | return 0, err 131 | } 132 | return 1, nil 133 | } 134 | 135 | // Update 更新站点 136 | func (w *WebsitesModel) Update(fileSync *util.FileSync, data WebsitesStoreItem, backupsDir string) (int, error) { 137 | list := make([]WebsitesStoreItem, 0) 138 | list, err := w.List(fileSync) 139 | if err != nil { 140 | return 0, err 141 | } 142 | // 遍历更新 143 | oldID := data.ID 144 | data.Update = time.Now().Format(time.RFC3339) 145 | data.ID = util.CreateMD5(data.Host, true) 146 | if data.Group == "" { 147 | data.Group = WebsitesGroupDefault 148 | } 149 | for index, item := range list { 150 | if item.ID == oldID { 151 | list[index] = data 152 | } 153 | } 154 | 155 | if err := w.save(list, fileSync); err != nil { 156 | return 0, err 157 | } 158 | 159 | w.AddGroups(data.Group) 160 | return 1, nil 161 | } 162 | 163 | // Update 更新顺序 164 | func (w *WebsitesModel) UpdateWebSiteOrder(fileSync *util.FileSync, data []WebsitesStoreItem, 165 | backupsDir string) (int, error) { 166 | list := make([]WebsitesStoreItem, 0) 167 | list, err := w.List(fileSync) 168 | if err != nil { 169 | return 0, err 170 | } 171 | 172 | for _, dataItem := range data { 173 | // 遍历更新 174 | oldID := dataItem.ID 175 | dataItem.Update = time.Now().Format(time.RFC3339) 176 | dataItem.ID = util.CreateMD5(dataItem.Host, true) 177 | if dataItem.Group == "" { 178 | dataItem.Group = WebsitesGroupDefault 179 | } 180 | for index, item := range list { 181 | if item.ID == oldID { 182 | list[index] = dataItem 183 | } 184 | } 185 | } 186 | 187 | if err := w.save(list, fileSync); err != nil { 188 | return 0, err 189 | } 190 | 191 | return 1, nil 192 | } 193 | 194 | // RequestWebsitesDelete 删除请求参数 195 | type RequestWebsitesDelete struct { 196 | IDS string `json:"ids"` 197 | } 198 | 199 | // Delete 删除站点 200 | func (w *WebsitesModel) Delete(fileSync *util.FileSync, ids []string, backupsDir string) (int, error) { 201 | // 读取 202 | list := make([]WebsitesStoreItem, 0) 203 | list, err := w.List(fileSync) 204 | if err != nil { 205 | return 0, err 206 | } 207 | // 新的站点列表 208 | newList := make([]WebsitesStoreItem, 0) 209 | success := 0 210 | var site = WebsitesStoreItem{} 211 | for _, item := range list { 212 | if util.StringInArray(item.ID, ids) == false { 213 | newList = append(newList, item) 214 | } else { 215 | success++ 216 | site = item 217 | } 218 | } 219 | 220 | // 检查分组是否还有数据, 没有则删除分组 221 | _ = w.DeleteGroupsOne(site.Group) 222 | 223 | if err := w.save(newList, fileSync); err != nil { 224 | return 0, err 225 | } 226 | return success, nil 227 | } 228 | 229 | func (w *WebsitesModel) DeleteGroupsOne(group string) error { 230 | // 从站点中获取所有站点数据 231 | list := make([]WebsitesStoreItem, 0) 232 | list, err := w.List(conf.App.Store.FileSync) 233 | if err != nil { 234 | return err 235 | } 236 | 237 | // 从站点列表中区分唯一的分组数据 238 | listMap := w.OrderWebSiteByOrder(list) 239 | 240 | // 判断是否存在数据, 存在1条即可, 因为现在删除的就是最后一条 241 | newGroupKeyExistsCount := 0 242 | for key, data := range listMap { 243 | if strings.HasSuffix(key, group) && len(data) == 1 { 244 | newGroupKeyExistsCount++ 245 | } 246 | } 247 | 248 | if newGroupKeyExistsCount == 1 { 249 | // 获取本地分组数据 250 | currGroupsInGroupFile, _ := w.GetGroupsOnly() 251 | newGroups := currGroupsInGroupFile 252 | 253 | // 从站点分组中删除某个组; 254 | tmpGroups := make([]string, 0) 255 | for _, groupName1 := range newGroups { 256 | if groupName1 != group { 257 | tmpGroups = append(tmpGroups, groupName1) 258 | } 259 | } 260 | 261 | newGroups = tmpGroups 262 | 263 | _, err = w.AddGroupOrder(conf.App.GroupStore.FileSync, newGroups, "") 264 | if err != nil { 265 | return err 266 | } 267 | } 268 | 269 | return nil 270 | } 271 | 272 | // GetGroupsOnly 获取分组数据, 从文件中 273 | func (w *WebsitesModel) GetGroupsOnly() ([]string, error) { 274 | app := conf.App 275 | currGroupsInGroupFile, err := w.ListGroupOrder(app.GroupStore.FileSync) 276 | return currGroupsInGroupFile, err 277 | } 278 | 279 | // Groups 获取站点分组列表 280 | // Deprecate 281 | func (w *WebsitesModel) Groups(fileSync *util.FileSync) ([]string, error) { 282 | // 从站点中获取所有站点数据 283 | list := make([]WebsitesStoreItem, 0) 284 | list, err := w.List(fileSync) 285 | if err != nil { 286 | return nil, err 287 | } 288 | 289 | allGroups := make([]string, 0) 290 | mapGroups := make(map[string]string) 291 | // 从站点数据中获取所有分组数据 292 | for _, item := range list { 293 | if item.Group == "" { 294 | continue 295 | } 296 | if _, ok := mapGroups[item.Group]; ok { 297 | continue 298 | } 299 | mapGroups[item.Group] = item.Group 300 | allGroups = append(allGroups, item.Group) 301 | } 302 | 303 | app := conf.App 304 | currGroupsInGroupFile, _ := w.ListGroupOrder(app.GroupStore.FileSync) 305 | newGroups := currGroupsInGroupFile 306 | if len(currGroupsInGroupFile) == len(allGroups) { 307 | newGroups = allGroups 308 | } else { 309 | // 分组有自定义排序, 需要比对写入 310 | // 移除不再判断, 直接写入 311 | isAppendNew := false 312 | 313 | if currGroupsInGroupFile == nil { 314 | newGroups = allGroups 315 | isAppendNew = true 316 | } else { 317 | for _, groupName := range allGroups { 318 | isContains := false 319 | for _, groupName2 := range currGroupsInGroupFile { 320 | if groupName == groupName2 { 321 | isContains = true 322 | } 323 | } 324 | 325 | if !isContains { 326 | newGroups = append(newGroups, groupName) 327 | isAppendNew = true 328 | } 329 | } 330 | 331 | // 判断是否有空组存在 332 | delArr := make([]string, 0) 333 | for _, groupName1 := range currGroupsInGroupFile { 334 | isNotEmpty := false 335 | for _, groupName2 := range allGroups { 336 | if groupName1 == groupName2 { 337 | isNotEmpty = true 338 | } 339 | } 340 | 341 | if !isNotEmpty { 342 | delArr = append(delArr, groupName1) 343 | } 344 | } 345 | 346 | // 从新组装的分组中删除空组; 347 | tmpGroups := make([]string, 0) 348 | for _, groupName1 := range newGroups { 349 | isTrue := false 350 | for _, groupName2 := range delArr { 351 | if groupName1 == groupName2 { 352 | isTrue = true 353 | } 354 | } 355 | 356 | if !isTrue { 357 | tmpGroups = append(tmpGroups, groupName1) 358 | } 359 | } 360 | 361 | newGroups = tmpGroups 362 | } 363 | 364 | if isAppendNew { 365 | _, err := w.AddGroupOrder(app.GroupStore.FileSync, newGroups, "") 366 | if err != nil { 367 | return nil, err 368 | } 369 | } 370 | } 371 | 372 | return newGroups, nil 373 | } 374 | 375 | func (w *WebsitesModel) save(list []WebsitesStoreItem, fileSync *util.FileSync) error { 376 | if len(list) == 0 { 377 | return nil 378 | } 379 | content, err := json.Marshal(list) 380 | if err != nil { 381 | return err 382 | } 383 | if err := fileSync.CoverJSON(content); err != nil { 384 | return err 385 | } 386 | return nil 387 | } 388 | 389 | func (w *WebsitesModel) saveGroupsString(data []string, groupSync *util.FileSync) error { 390 | if len(data) == 0 { 391 | return nil 392 | } 393 | content, err := json.Marshal(data) 394 | if err != nil { 395 | return err 396 | } 397 | if err := groupSync.CoverJSON(content); err != nil { 398 | return err 399 | } 400 | return nil 401 | } 402 | 403 | // InitOrder /** 初始化顺序, 如果没有的情况下, 如果有顺序将会按读取顺序添加 */ 404 | func (w *WebsitesModel) InitOrder(fileSync *util.FileSync) error { 405 | // 读取 406 | srcList := make([]WebsitesStoreItem, 0) 407 | srcList, _ = w.List(fileSync) 408 | 409 | // group list by group 410 | mapList := make(map[string][]WebsitesStoreItem) 411 | for _, item := range srcList { 412 | if item.Group == "" { 413 | item.Group = WebsitesGroupDefault 414 | } 415 | if _, ok := mapList[item.Group]; ok == false { 416 | mapList[item.Group] = make([]WebsitesStoreItem, 0) 417 | } 418 | mapList[item.Group] = append(mapList[item.Group], item) 419 | } 420 | 421 | // sort by group, add to orderList 422 | orderMapList := make(map[string][]WebsitesStoreItem) 423 | orderList := make([]WebsitesStoreItem, 0) 424 | for key, val := range mapList { 425 | for index, item := range val { 426 | item.Order = index + 1 427 | orderMapList[key] = append(orderMapList[key], item) 428 | orderList = append(orderList, item) 429 | } 430 | } 431 | 432 | // save 433 | w.save(orderList, fileSync) 434 | return nil 435 | } 436 | 437 | // GetLastOrderInGroup 获取当前组有多少个, 并返回合适的order 438 | func (w *WebsitesModel) GetLastOrderInGroup(groupKey string, webSiteList []WebsitesStoreItem) int { 439 | order := 1 440 | if len(webSiteList) > 0 { 441 | listMap := w.OrderWebSiteByOrder(webSiteList) 442 | 443 | var newGroupKey = groupKey 444 | newGroupKeyExistsCount := 0 445 | for key, _ := range listMap { 446 | if strings.HasSuffix(key, groupKey) { 447 | newGroupKey = key 448 | newGroupKeyExistsCount++ 449 | } 450 | } 451 | 452 | println(listMap) 453 | if newGroupKeyExistsCount > 0 { 454 | if len(listMap[newGroupKey]) > 0 { 455 | order = listMap[newGroupKey][len(listMap[newGroupKey])-1].Order 456 | } else { 457 | split2 := strings.Split(newGroupKey, "-") 458 | order, _ = strconv.Atoi(split2[0]) 459 | } 460 | 461 | } else { 462 | order = len(listMap) 463 | } 464 | 465 | } 466 | 467 | return order 468 | } 469 | 470 | func (w *WebsitesModel) List2listMap(webSiteList []WebsitesStoreItem) map[string][]WebsitesStoreItem { 471 | mapList := make(map[string][]WebsitesStoreItem) 472 | for _, item := range webSiteList { 473 | if item.Group == "" { 474 | item.Group = WebsitesGroupDefault 475 | } 476 | if _, ok := mapList[item.Group]; ok == false { 477 | mapList[item.Group] = make([]WebsitesStoreItem, 0) 478 | } 479 | mapList[item.Group] = append(mapList[item.Group], item) 480 | } 481 | return mapList 482 | } 483 | 484 | // 将website list 转为分组并排序的map 485 | func (w *WebsitesModel) OrderWebSiteByOrder(webSiteList []WebsitesStoreItem) map[string][]WebsitesStoreItem { 486 | mapList := w.List2listMap(webSiteList) 487 | 488 | // 排序 489 | orderMapList := make(map[string][]WebsitesStoreItem) 490 | //groupArr := make([]string, 0) 491 | for mapKey, mapValList := range mapList { 492 | sortList := Sort("order", mapValList) 493 | orderMapList[mapKey] = sortList 494 | //append(groupArr, mapKey) 495 | } 496 | 497 | // 分组排序 498 | //orderMapList 499 | app := conf.App 500 | 501 | groupArr, _ := w.ListGroupOrder(app.GroupStore.FileSync) 502 | 503 | // 编号-分组名称, 用于前端排序不规则 504 | newWebList := make(map[string][]WebsitesStoreItem) 505 | if len(groupArr) > 0 && len(orderMapList) > 0 { 506 | for index, group := range groupArr { 507 | newWebList[strconv.Itoa(index)+"-"+group] = orderMapList[group] 508 | } 509 | } else { 510 | newWebList = orderMapList 511 | } 512 | 513 | return newWebList 514 | } 515 | -------------------------------------------------------------------------------- /nav-site-server/model/file_test.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "fmt" 5 | conf "nav-site-server/config" 6 | "testing" 7 | ) 8 | 9 | func TestUpdateOrder(t *testing.T) { 10 | abc := new(WebsitesModel) 11 | 12 | s, _ := conf.InitConfig("") 13 | conf.App = *s 14 | app := conf.App 15 | _ = abc.InitOrder(app.Store.FileSync) 16 | fmt.Println("==========") 17 | } 18 | 19 | func TestFileOrder(t *testing.T) { 20 | webSite := new(WebsitesModel) 21 | 22 | conf, _ := conf.InitConfig("") 23 | 24 | list, _ := webSite.List(conf.Store.FileSync) 25 | //fmt.Println(list) 26 | 27 | mapList := make(map[string][]WebsitesStoreItem) 28 | for _, item := range list { 29 | if item.Group == "" { 30 | item.Group = WebsitesGroupDefault 31 | } 32 | if _, ok := mapList[item.Group]; ok == false { 33 | mapList[item.Group] = make([]WebsitesStoreItem, 0) 34 | } 35 | mapList[item.Group] = append(mapList[item.Group], item) 36 | } 37 | 38 | //fmt.Println(mapList) 39 | 40 | // 排序 41 | for mapKey, mapValList := range mapList { 42 | fmt.Println(mapKey) 43 | //fmt.Println(mapKey, mapValList) 44 | 45 | //for index, mapArr := range mapValList { 46 | // fmt.Println(index, mapArr) 47 | //} 48 | 49 | sortList := Sort("order", mapValList) 50 | //sortList := util.SortBodyByField(mapValList, "order") 51 | fmt.Println(sortList) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /nav-site-server/model/order2.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "sort" 5 | ) 6 | 7 | type MapsSort struct { 8 | Key string 9 | MapList []WebsitesStoreItem 10 | } 11 | 12 | func (m *MapsSort) Len() int { 13 | return len(m.MapList) 14 | } 15 | 16 | func (m *MapsSort) Less(i, j int) bool { 17 | return m.MapList[i].Order < m.MapList[j].Order 18 | } 19 | 20 | func (m *MapsSort) Swap(i, j int) { 21 | m.MapList[i], m.MapList[j] = m.MapList[j], m.MapList[i] 22 | } 23 | 24 | func Sort(key string, maps []WebsitesStoreItem) []WebsitesStoreItem { 25 | mapsSort := MapsSort{} 26 | mapsSort.Key = key 27 | mapsSort.MapList = maps 28 | sort.Sort(&mapsSort) 29 | 30 | return mapsSort.MapList 31 | } 32 | -------------------------------------------------------------------------------- /nav-site-server/script/install-npm.bat: -------------------------------------------------------------------------------- 1 | ::, please run the *.bat in the document 2 | 3 | @echo off 4 | cd ../nav-site-web 5 | echo "current path: %cd%" 6 | call npm run build 7 | echo "npm run build end..." 8 | 9 | cd ../nav-site-server/server 10 | echo "current path: %cd%" 11 | rmdir /S/Q static 12 | 13 | xcopy ..\..\nav-site-web\dist static\ /S /F 14 | echo "web build end" -------------------------------------------------------------------------------- /nav-site-server/script/install-npm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cd ../../nav-site-web 3 | echo "current path: %cd%" 4 | npm run build 5 | echo "npm run build end..." 6 | 7 | cd ../nav-site-server/server 8 | echo "current path: %cd%" 9 | rm -rf static/ 10 | 11 | \cp ../../nav-site-web/dist static/ 12 | echo "web build end" -------------------------------------------------------------------------------- /nav-site-server/server/app.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/gin-gonic/gin" 7 | log "github.com/sirupsen/logrus" 8 | conf "nav-site-server/config" 9 | "net/http" 10 | "os" 11 | "os/signal" 12 | "strings" 13 | "time" 14 | ) 15 | 16 | func initLog() { 17 | log.SetFormatter(&log.JSONFormatter{}) //设置日志的输出格式为json格式,还可以设置为text格式 18 | log.SetOutput(os.Stdout) //设置日志的输出为标准输出 19 | log.SetLevel(log.InfoLevel) //设置日志的显示级别,这一级别以及更高级别的日志信息将会输出 20 | } 21 | 22 | func init() { 23 | //以package级别方式使用日志 24 | initLog() 25 | 26 | // 读取参数 27 | var confDir string 28 | for _, item := range os.Args { 29 | itemArr := strings.Split(item, "=") 30 | if itemArr[0] == "-conf-dir" || itemArr[0] == "conf-dir" { 31 | confDir = itemArr[1] 32 | } 33 | } 34 | 35 | s, err := conf.InitConfig(confDir) 36 | if err != nil { 37 | log.Info("init server config failed, error: ", err) 38 | //log.Println("init server config failed, error: ", err) 39 | os.Exit(1) 40 | } 41 | conf.App = *s 42 | //log.Info("init config:") 43 | log.Info("init config:", conf.App) 44 | //log.Printf("init config:") 45 | //log.Printf("%+v", conf.App) 46 | } 47 | 48 | func Run() { 49 | engine := gin.Default() 50 | defer func() { 51 | if err := conf.App.Store.FileSync.CloseStoreFile(); err != nil { 52 | log.Info("close store file resource failed, error :", err) 53 | //log.Println("close store file resource failed, error :", err) 54 | os.Exit(1) 55 | } 56 | }() 57 | 58 | InitHtmlResource(engine) 59 | 60 | // 读取本地静态资源 61 | //InitLocalResource(engine) 62 | // embed方式读取静态资源 63 | InitEmbedResource(engine) 64 | 65 | Router(engine) 66 | engine.NoMethod(conf.HandleNotFound) 67 | engine.NoRoute(conf.HandleNotFound) 68 | 69 | port := fmt.Sprintf(":%s", conf.App.Server.Port) 70 | //log.Println("listen port", port) 71 | log.Info("listen port", port) 72 | 73 | srv := &http.Server{ 74 | Addr: port, 75 | Handler: engine, 76 | } 77 | 78 | go func() { 79 | // service connections 80 | if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { 81 | //log.Fatalf("listen: %s\n", err) 82 | log.Fatal("listen: %s\n", err) 83 | } 84 | }() 85 | 86 | // Wait for interrupt signal to gracefully shutdown the server with 87 | // a timeout of 5 seconds. 88 | quit := make(chan os.Signal) 89 | signal.Notify(quit, os.Interrupt) 90 | <-quit 91 | log.Info("Shutdown Server ...") 92 | //log.Println("Shutdown Server ...") 93 | 94 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 95 | defer cancel() 96 | if err := srv.Shutdown(ctx); err != nil { 97 | //log.Fatal("Server Shutdown:", err) 98 | log.Fatal("Server Shutdown:", err) 99 | } 100 | log.Info("Server exiting") 101 | //log.Println("Server exiting") 102 | 103 | } 104 | -------------------------------------------------------------------------------- /nav-site-server/server/html-handler.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | ) 7 | 8 | type HtmlHandler struct{} 9 | 10 | func NewHtmlHandler() *HtmlHandler { 11 | return &HtmlHandler{} 12 | } 13 | 14 | func (h *HtmlHandler) RedirectIndex(c *gin.Context) { 15 | c.Redirect(http.StatusFound, "/") 16 | return 17 | } 18 | 19 | func (h *HtmlHandler) Index(c *gin.Context) { 20 | c.Header("content-type", "text/html;charset=utf-8") 21 | c.String(http.StatusOK, string(HtmlIndex)) 22 | //c.SetCookie("test-domain", "abc", 60000, "/", "test.com", false, true) 23 | } 24 | 25 | func (h *HtmlHandler) Favicon(c *gin.Context) { 26 | file, _ := Static.ReadFile("static/favicon.ico") 27 | c.Data( 28 | http.StatusOK, 29 | "image/x-icon", 30 | file, 31 | ) 32 | 33 | } 34 | -------------------------------------------------------------------------------- /nav-site-server/server/html-resource.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "embed" 5 | "errors" 6 | "github.com/gin-gonic/gin" 7 | "io/fs" 8 | conf "nav-site-server/config" 9 | "nav-site-server/extend/util" 10 | "net/http" 11 | "path" 12 | "path/filepath" 13 | "strings" 14 | ) 15 | 16 | //go:embed static/index.html 17 | var HtmlIndex []byte 18 | 19 | //go:embed static/favicon.ico 20 | var Favicon []byte 21 | 22 | //go:embed static 23 | var Static embed.FS 24 | 25 | type HtmlResource struct { 26 | fs embed.FS 27 | path string 28 | } 29 | 30 | func NewHtmlResource() *HtmlResource { 31 | return &HtmlResource{ 32 | fs: Static, 33 | path: "static", 34 | } 35 | } 36 | 37 | func (h *HtmlResource) Open(name string) (fs.File, error) { 38 | if filepath.Separator != '/' && strings.ContainsRune(name, filepath.Separator) { 39 | return nil, errors.New("http: invalid character in file path") 40 | } 41 | fullName := filepath.Join(h.path, filepath.FromSlash(path.Clean("/static/"+name))) 42 | file, err := h.fs.Open(fullName) 43 | return file, err 44 | } 45 | 46 | func InitHtmlResource(engine *gin.Engine) *gin.Engine { 47 | engine.StaticFS("/static", http.FS(NewHtmlResource())) 48 | return engine 49 | } 50 | 51 | func InitEmbedResource(engine *gin.Engine) { 52 | js, _ := fs.Sub(Static, "static/js") 53 | css, _ := fs.Sub(Static, "static/css") 54 | engine.StaticFS("/js", http.FS(js)) 55 | engine.StaticFS("/css", http.FS(css)) 56 | 57 | if len(conf.App.Static.Root) > 0 { 58 | engine.Static("/data", conf.App.Static.Root+"/data") 59 | } else { 60 | engine.Static("/data", "./data") 61 | } 62 | 63 | htmlHandler := NewHtmlHandler() 64 | engine.GET("/", htmlHandler.Index) 65 | 66 | exists, _ := util.FileExists("./conf/favicon.ico") 67 | if exists { 68 | engine.StaticFile("/favicon.ico", "./conf/favicon.ico") 69 | } else { 70 | engine.GET("/favicon.ico", htmlHandler.Favicon) 71 | } 72 | 73 | logoExists, _ := util.FileExists("./conf/logo.png") 74 | if logoExists { 75 | conf.HasLogo = true 76 | engine.StaticFile("/logo.png", "./conf/logo.png") 77 | } 78 | } 79 | 80 | func InitLocalResource(engine *gin.Engine) { 81 | // 以下代码为读取本地文件, 目前使用embed方式打包, 如果代码量大请使用此种方式 82 | //engine.LoadHTMLGlob("static/*.html") 83 | //engine.Static("/static", "./static") 84 | //engine.Static("/css", "./static/css") 85 | //engine.Static("/js", "./static/js") 86 | //engine.StaticFile("/favicon.ico", "favicon.ico") 87 | //engine.StaticFile("/background.jpg", "./static/background.jpg") 88 | // 89 | //engine.GET("/", func(c *gin.Context) { 90 | // c.HTML(http.StatusOK, "index.html", gin.H{}) 91 | //}) 92 | } 93 | -------------------------------------------------------------------------------- /nav-site-server/server/router.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "nav-site-server/api" 6 | ) 7 | 8 | func Router(gin *gin.Engine) { 9 | website(gin) 10 | upload(gin) 11 | auth(gin) 12 | } 13 | 14 | func website(gin *gin.Engine) { 15 | website := gin.Group("/api/website") 16 | { 17 | website.GET("/info", api.WetSiteInfo) 18 | website.GET("/list", api.WebSiteList) 19 | website.POST("/add", api.WebSiteAdd) 20 | website.POST("/update", api.WebSiteUpdate) 21 | website.POST("/delete", api.WebSiteDelete) 22 | website.GET("/groups", api.WebsiteGroups) 23 | website.PUT("/order/list", api.WebSiteOrder) 24 | website.PUT("/order/group", api.WebSiteGroupOrder) 25 | } 26 | 27 | } 28 | 29 | func upload(r *gin.Engine) { 30 | upload := r.Group("/api/upload") 31 | { 32 | upload.POST("/image", api.Image) 33 | } 34 | } 35 | 36 | func auth(r *gin.Engine) { 37 | auth := r.Group("/api/auth") 38 | { 39 | auth.POST("/login", api.Login) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /nav-site-web/.env: -------------------------------------------------------------------------------- 1 | VUE_APP_API_BASE="/api" 2 | VUE_APP_TITLE="迷你网址导航NavSite" 3 | -------------------------------------------------------------------------------- /nav-site-web/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | 4 | # local env files 5 | .env.local 6 | .env.*.local 7 | 8 | dist/ 9 | dist.zip 10 | node_modules/ 11 | release/ 12 | 13 | # Log files 14 | npm-debug.log* 15 | yarn-debug.log* 16 | yarn-error.log* 17 | pnpm-debug.log* 18 | 19 | # Editor directories and files 20 | .idea 21 | .vscode 22 | *.suo 23 | *.ntvs* 24 | *.njsproj 25 | *.sln 26 | *.sw? 27 | -------------------------------------------------------------------------------- /nav-site-web/.npmrc: -------------------------------------------------------------------------------- 1 | node-linker=hoisted -------------------------------------------------------------------------------- /nav-site-web/README.md: -------------------------------------------------------------------------------- 1 | # nav-site-web 2 | 3 | ## 运行环境 4 | ``` 5 | node版本: 6 | vue版本: 7 | ``` 8 | 9 | ## 构建前端:(vue3+element) 10 | ``` 11 | 调试模式 12 | pnpm install 13 | pnpm run dev 14 | 默认端口为8080 15 | 16 | 集成到go语言调试 17 | 将生成的dist目录复制到nav-site-server/server/static下, 运行main.go即可 18 | 更多参见服务端说明 19 | ``` 20 | 21 | 22 | ## Project setup 23 | ``` 24 | pnpm install 25 | ``` 26 | 27 | ### Compiles and hot-reloads for development 28 | ``` 29 | pnpm dev 30 | ``` 31 | 32 | ### Compiles and minifies for production 33 | ``` 34 | pnpm run build 35 | ``` 36 | 37 | ### Lints and fixes files 38 | ``` 39 | pnpm run lint 40 | ``` 41 | 42 | ### Customize configuration 43 | See [Configuration Reference](https://cli.vuejs.org/config/). 44 | -------------------------------------------------------------------------------- /nav-site-web/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /nav-site-web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nav-site-web", 3 | "version": "0.0.13", 4 | "private": true, 5 | "scripts": { 6 | "dev": "set NODE_OPTIONS=--openssl-legacy-provider & vue-cli-service serve", 7 | "build": "set NODE_OPTIONS=--openssl-legacy-provider & vue-cli-service build", 8 | "lint": "set NODE_OPTIONS=--openssl-legacy-provider & vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "@element-plus/icons-vue": "^2.0.4", 12 | "axios": "^0.21.1", 13 | "core-js": "^3.6.5", 14 | "element-plus": "^2.2.4", 15 | "js-cookie": "^2.2.1", 16 | "less-loader": "^5.0.0", 17 | "vue": "^3.2.37" 18 | }, 19 | "devDependencies": { 20 | "@vue/cli-plugin-babel": "~4.5.0", 21 | "@vue/cli-plugin-eslint": "^4.5.12", 22 | "@vue/cli-service": "~4.5.0", 23 | "@vue/compiler-sfc": "^3.0.0", 24 | "babel-eslint": "^10.1.0", 25 | "eslint": "^6.7.2", 26 | "eslint-plugin-vue": "^7.0.0", 27 | "less": "^4.1.1", 28 | "sass-loader": "^11.0.1" 29 | }, 30 | "eslintConfig": { 31 | "root": true, 32 | "env": { 33 | "node": true 34 | }, 35 | "extends": [ 36 | "plugin:vue/vue3-essential", 37 | "eslint:recommended" 38 | ], 39 | "parserOptions": { 40 | "parser": "babel-eslint" 41 | }, 42 | "rules": {} 43 | }, 44 | "browserslist": [ 45 | "> 1%", 46 | "last 2 versions", 47 | "not dead" 48 | ] 49 | } 50 | -------------------------------------------------------------------------------- /nav-site-web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cifaz/nav-site/fb739f801b89b689b279276dc5409fc343607f31/nav-site-web/public/favicon.ico -------------------------------------------------------------------------------- /nav-site-web/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /nav-site-web/src/App.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 73 | 74 | 132 | -------------------------------------------------------------------------------- /nav-site-web/src/api/auth.js: -------------------------------------------------------------------------------- 1 | import axios from "@/util/request.js"; 2 | 3 | const auth = { 4 | login: "auth/login", 5 | }; 6 | 7 | export function AuthLogin(data) { 8 | return axios({ 9 | method: "post", 10 | data: data, 11 | url: auth.login, 12 | }); 13 | } 14 | 15 | -------------------------------------------------------------------------------- /nav-site-web/src/api/cookie.js: -------------------------------------------------------------------------------- 1 | import Cookies from "js-cookie"; 2 | 3 | const TokenName = "token" 4 | 5 | export function CookieSet(token, expire) { 6 | expire = expire / 86400 ; 7 | Cookies.set(TokenName, token || "", { expires: expire }); 8 | } 9 | 10 | export function CookieGetToken(){ 11 | let token = Cookies.get(TokenName) 12 | return token || "" 13 | } 14 | 15 | export function CookieRemoveToken(){ 16 | Cookies.remove(TokenName) 17 | } -------------------------------------------------------------------------------- /nav-site-web/src/api/upload.js: -------------------------------------------------------------------------------- 1 | const upload = { 2 | imageUpload: "upload/image", 3 | }; 4 | 5 | export const WebLogoUploadUrl = process.env.VUE_APP_API_BASE + '/' + upload.imageUpload 6 | -------------------------------------------------------------------------------- /nav-site-web/src/api/website.js: -------------------------------------------------------------------------------- 1 | import axios from "@/util/request.js"; 2 | 3 | const website = { 4 | add: "website/add", 5 | info: "website/info", 6 | list: "website/list", 7 | delete: "website/delete", 8 | update: "website/update", 9 | groups: "website/groups", 10 | imageUpload: "upload/image", 11 | orderWebSite: "/website/order/list", 12 | orderGroup: "/website/order/group", 13 | }; 14 | 15 | export function WebInfo(data) { 16 | return axios({ 17 | method: "get", 18 | data: data, 19 | url: website.info, 20 | }); 21 | } 22 | 23 | export function WebList(data) { 24 | return axios({ 25 | method: "get", 26 | data: data, 27 | url: website.list, 28 | }); 29 | } 30 | 31 | export function WebSiteOrder(data) { 32 | return axios({ 33 | method: "put", 34 | data: data, 35 | url: website.orderWebSite, 36 | }); 37 | } 38 | 39 | export function WebSiteGroupOrder(data) { 40 | return axios({ 41 | method: "put", 42 | data: data, 43 | url: website.orderGroup, 44 | }); 45 | } 46 | 47 | export function WebAdd(data) { 48 | return axios({ 49 | method: "post", 50 | data: data, 51 | url: website.add, 52 | }); 53 | } 54 | 55 | 56 | export function WebEdit(data) { 57 | return axios({ 58 | method: "post", 59 | data: data, 60 | url: website.update, 61 | }); 62 | } 63 | 64 | export function WebDelete(data) { 65 | return axios({ 66 | method: "post", 67 | data: data, 68 | url: website.delete, 69 | }); 70 | } 71 | 72 | export function WebGroups(data) { 73 | return axios({ 74 | method: "get", 75 | data: data, 76 | url: website.groups, 77 | }); 78 | } 79 | 80 | export const WebLogoUploadUrl = website.imageUpload 81 | -------------------------------------------------------------------------------- /nav-site-web/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cifaz/nav-site/fb739f801b89b689b279276dc5409fc343607f31/nav-site-web/src/assets/logo.png -------------------------------------------------------------------------------- /nav-site-web/src/components/Auth/LoginForm.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 122 | 123 | 131 | -------------------------------------------------------------------------------- /nav-site-web/src/components/Web/WebFixedNav.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 52 | 53 | -------------------------------------------------------------------------------- /nav-site-web/src/components/Web/WebForm.vue: -------------------------------------------------------------------------------- 1 | 100 | 101 | 259 | 260 | 302 | -------------------------------------------------------------------------------- /nav-site-web/src/components/Web/WebGroup.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 165 | 166 | 187 | -------------------------------------------------------------------------------- /nav-site-web/src/components/Web/WebItem.vue: -------------------------------------------------------------------------------- 1 | 70 | 71 | 178 | 179 | 272 | -------------------------------------------------------------------------------- /nav-site-web/src/components/Web/WebList.vue: -------------------------------------------------------------------------------- 1 | 82 | 83 | 291 | 292 | 365 | -------------------------------------------------------------------------------- /nav-site-web/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "vue"; 2 | import ElementPlus from "element-plus"; 3 | import "element-plus/dist/index.css"; 4 | import App from "./App.vue"; 5 | import axios from "axios"; 6 | import "dayjs/locale/zh-cn"; 7 | import locale from "element-plus/lib/locale/lang/zh-cn"; 8 | import * as ElementPlusIconsVue from '@element-plus/icons-vue' 9 | 10 | const app = createApp(App); 11 | 12 | 13 | for (const [key, component] of Object.entries(ElementPlusIconsVue)) { 14 | app.component(key, component) 15 | } 16 | 17 | document.title = process.env.VUE_APP_TITLE | "网址导航" 18 | 19 | app.config.globalProperties.$axios = axios; 20 | app.use(ElementPlus, { locale }); 21 | app.mount("#app"); 22 | -------------------------------------------------------------------------------- /nav-site-web/src/util/request.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import {CookieGetToken } from "@/api/cookie.js"; 3 | 4 | const instance = axios.create({ 5 | baseURL: process.env.VUE_APP_API_BASE, 6 | timeout: 6000, 7 | }); 8 | 9 | instance.interceptors.request.use( 10 | function(config) { 11 | // 在发送请求之前做些什么 12 | const token = CookieGetToken() 13 | config.headers["Authorization"] = "Bearer " + token; 14 | return config; 15 | }, 16 | function(error) { 17 | // 对请求错误做些什么 18 | return Promise.reject(error); 19 | } 20 | ); 21 | 22 | // 添加响应拦截器 23 | instance.interceptors.response.use(function (response) { 24 | // 对响应数据做点什么 25 | return response; 26 | }, function (error) { 27 | // 对响应错误做点什么 28 | return Promise.reject(error); 29 | }); 30 | 31 | export default instance; 32 | -------------------------------------------------------------------------------- /nav-site-web/vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | configureWebpack: { 3 | module: { 4 | rules: [ 5 | { 6 | include: /node_modules/, 7 | test: /\.mjs$/, 8 | type: 'javascript/auto' 9 | } 10 | ] 11 | } 12 | }, 13 | 14 | devServer: { 15 | proxy: { 16 | '/api/': { 17 | target: 'http://localhost:8083/', 18 | changeOrigin: true, 19 | ws: false, 20 | pathRewrite: { 21 | // '^/api/': '/' 22 | } 23 | } 24 | } 25 | } 26 | } --------------------------------------------------------------------------------