🎨 增加 Go 后端
@ -1,27 +0,0 @@
|
|||||||
import {_colors, _fontFamily} from "#tailwind-config/theme.mjs";
|
|
||||||
|
|
||||||
export default defineAppConfig({
|
|
||||||
naiveui: {
|
|
||||||
themeConfig: {
|
|
||||||
shared: {
|
|
||||||
common: {
|
|
||||||
fontFamily: _fontFamily.sans.join(", "),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
light: {
|
|
||||||
common: {
|
|
||||||
primaryColor: _colors.blue[600],
|
|
||||||
primaryColorHover: _colors.blue[500],
|
|
||||||
primaryColorPressed: _colors.blue[700],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
dark: {
|
|
||||||
common: {
|
|
||||||
primaryColor: _colors.blue[500],
|
|
||||||
primaryColorHover: _colors.blue[400],
|
|
||||||
primaryColorPressed: _colors.blue[600],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
3
backend/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/storage/
|
||||||
|
.idea
|
||||||
|
*.log
|
21
backend/LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 Nunu
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
184
backend/README.md
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
# nunu-layout-basic - Basic Layout
|
||||||
|
|
||||||
|
Nunu is an application scaffold based on Golang. Its name comes from a game character in League of Legends, a little boy riding on the shoulder of a yeti. Like Nunu, this project also stands on the shoulders of giants. It is a composition of various popular libraries from the Golang ecosystem, which can help you quickly build efficient and reliable applications.
|
||||||
|
|
||||||
|
[简体中文介绍](https://github.com/go-nunu/nunu-layout-basic/blob/main/README_zh.md)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
* [User Guide](https://github.com/go-nunu/nunu/blob/main/docs/en/guide.md)
|
||||||
|
* [Architecture](https://github.com/go-nunu/nunu/blob/main/docs/en/architecture.md)
|
||||||
|
* [Getting Started Tutorial](https://github.com/go-nunu/nunu/blob/main/docs/en/tutorial.md)
|
||||||
|
|
||||||
|
## Features
|
||||||
|
- **Gin**: https://github.com/gin-gonic/gin
|
||||||
|
- **Gorm**: https://github.com/go-gorm/gorm
|
||||||
|
- **Wire**: https://github.com/google/wire
|
||||||
|
- **Viper**: https://github.com/spf13/viper
|
||||||
|
- **Zap**: https://github.com/uber-go/zap
|
||||||
|
- **Golang-jwt**: https://github.com/golang-jwt/jwt
|
||||||
|
- **Go-redis**: https://github.com/go-redis/redis
|
||||||
|
- **Testify**: https://github.com/stretchr/testify
|
||||||
|
- **Sonyflake**: https://github.com/sony/sonyflake
|
||||||
|
- **gocron**: https://github.com/go-co-op/gocron
|
||||||
|
- More...
|
||||||
|
|
||||||
|
## Highlights
|
||||||
|
* **Low Learning Curve and Customization**: Nunu encapsulates popular libraries that Gophers are familiar with. You can easily customize the application to meet specific requirements.
|
||||||
|
* **High Performance and Scalability**: Nunu aims to be high-performance and scalable. It utilizes the latest technologies and best practices to ensure your application can handle high traffic and large data volumes.
|
||||||
|
* **Security and Reliability**: Nunu uses stable and reliable third-party libraries to ensure the security and reliability of your application.
|
||||||
|
* **Modularity and Extensibility**: Nunu is designed to be modular and extensible. You can easily add new features and functionalities by using third-party libraries or writing your own modules.
|
||||||
|
* **Comprehensive Documentation and Test Coverage**: Nunu has comprehensive documentation and test coverage. It provides detailed documentation and examples to help you get started quickly. It also includes a test suite to ensure your application works as expected.
|
||||||
|
|
||||||
|
## Nunu CLI
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Directory Structure
|
||||||
|
```
|
||||||
|
.
|
||||||
|
├── cmd
|
||||||
|
│ └── server
|
||||||
|
│ ├── main.go
|
||||||
|
│ ├── wire.go
|
||||||
|
│ └── wire_gen.go
|
||||||
|
├── config
|
||||||
|
│ ├── local.yml
|
||||||
|
│ └── prod.yml
|
||||||
|
├── internal
|
||||||
|
│ ├── handler
|
||||||
|
│ │ ├── handler.go
|
||||||
|
│ │ └── user.go
|
||||||
|
│ ├── middleware
|
||||||
|
│ │ └── cors.go
|
||||||
|
│ ├── model
|
||||||
|
│ │ └── user.go
|
||||||
|
│ ├── repository
|
||||||
|
│ │ ├── repository.go
|
||||||
|
│ │ └── user.go
|
||||||
|
│ ├── server
|
||||||
|
│ │ └── http.go
|
||||||
|
│ └── service
|
||||||
|
│ ├── service.go
|
||||||
|
│ └── user.go
|
||||||
|
├── pkg
|
||||||
|
├── LICENSE
|
||||||
|
├── README.md
|
||||||
|
├── README_zh.md
|
||||||
|
├── go.mod
|
||||||
|
└── go.sum
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
This is a classic directory structure for a Golang project, which includes the following directories:
|
||||||
|
|
||||||
|
- cmd: Contains the entry points of the application, including the main function and dependency injection code.
|
||||||
|
- server: The main entry point of the application, including the main function and dependency injection code.
|
||||||
|
- main.go: The main function used to start the application.
|
||||||
|
- wire.go: The dependency injection code generated using Wire.
|
||||||
|
- wire_gen.go: The dependency injection code generated using Wire.
|
||||||
|
|
||||||
|
- config: Contains the configuration files of the application.
|
||||||
|
- local.yml: The configuration file for the local environment.
|
||||||
|
- prod.yml: The configuration file for the production environment.
|
||||||
|
|
||||||
|
- internal: Contains the internal code of the application.
|
||||||
|
- handler: Contains the handlers for handling HTTP requests.
|
||||||
|
- handler.go: The common handler for handling HTTP requests.
|
||||||
|
- user.go: The handler for handling user-related HTTP requests.
|
||||||
|
- middleware: Contains the middleware code.
|
||||||
|
- cors.go: The CORS (Cross-Origin Resource Sharing) middleware.
|
||||||
|
- model: Contains the data model code.
|
||||||
|
- user.go: The user data model.
|
||||||
|
- repository: Contains the data access code.
|
||||||
|
- repository.go: The common interface for data access.
|
||||||
|
- user.go: The implementation of the user data access interface.
|
||||||
|
- server: Contains the server code.
|
||||||
|
- http.go: The implementation of the HTTP server.
|
||||||
|
- service: Contains the business logic code.
|
||||||
|
- service.go: The common interface for business logic.
|
||||||
|
- user.go: The implementation of the user business logic.
|
||||||
|
|
||||||
|
- pkg: Contains the public packages of the application.
|
||||||
|
- storage: Contains the storage files of the application.
|
||||||
|
- go.mod: The Go module file.
|
||||||
|
- go.sum: The dependency versions file for the Go module.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
To use Nunu, you need to have the following software installed on your system:
|
||||||
|
|
||||||
|
* Golang 1.16 or higher
|
||||||
|
* Git
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
You can install Nunu using the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go install github.com/go-nunu/nunu@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
### Creating a New Project
|
||||||
|
|
||||||
|
You can create a new Golang project using the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nunu new projectName
|
||||||
|
```
|
||||||
|
|
||||||
|
By default, it will pull from the GitHub repository, but you can also use a mirror repository for faster access:
|
||||||
|
|
||||||
|
```
|
||||||
|
// Use the basic template
|
||||||
|
nunu new projectName -r https://gitee.com/go-nunu/nunu-layout-basic.git
|
||||||
|
// Use the advanced template
|
||||||
|
nunu new projectName -r https://gitee.com/go-nunu/nunu-layout-advanced.git
|
||||||
|
```
|
||||||
|
|
||||||
|
This command will create a directory named `projectName` and generate an elegant Golang project structure within it.
|
||||||
|
|
||||||
|
### Creating Components
|
||||||
|
|
||||||
|
You can create handlers, services, repositories, and models for your project using the following commands:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nunu create handler user
|
||||||
|
nunu create service user
|
||||||
|
nunu create repository user
|
||||||
|
nunu create model user
|
||||||
|
```
|
||||||
|
or
|
||||||
|
```
|
||||||
|
nunu create all user
|
||||||
|
```
|
||||||
|
|
||||||
|
These commands will create components named `UserHandler`, `UserService`, `UserDao`, and `UserModel` respectively and place them in the correct directories.
|
||||||
|
|
||||||
|
### Starting the Project
|
||||||
|
|
||||||
|
You can quickly start your project using the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nunu run
|
||||||
|
```
|
||||||
|
|
||||||
|
This command will start your Golang project and support hot-reloading of files.
|
||||||
|
|
||||||
|
### Compiling wire.go
|
||||||
|
|
||||||
|
You can quickly compile `wire.go` using the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nunu wire
|
||||||
|
```
|
||||||
|
|
||||||
|
This command will compile your `wire.go` file and generate the required dependencies.
|
||||||
|
|
||||||
|
## Contribution
|
||||||
|
|
||||||
|
If you find any issues or have any improvement suggestions, please feel free to raise an issue or submit a pull request. Your contributions are highly appreciated!
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Nunu is released under the MIT License. See the [LICENSE](LICENSE) file for more information.
|
186
backend/README_zh.md
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
# nunu-layout-basic — 基础布局
|
||||||
|
|
||||||
|
|
||||||
|
Nunu是一个基于Golang的应用脚手架,它的名字来自于英雄联盟中的游戏角色,一个骑在雪怪肩膀上的小男孩。和努努一样,该项目也是站在巨人的肩膀上,它是由Golang生态中各种非常流行的库整合而成的,它们的组合可以帮助你快速构建一个高效、可靠的应用程序。
|
||||||
|
|
||||||
|
[英文介绍](https://github.com/go-nunu/nunu-layout-basic/blob/main/README.md)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
## 文档
|
||||||
|
* [使用指南](https://github.com/go-nunu/nunu/blob/main/docs/zh/guide.md)
|
||||||
|
* [分层架构](https://github.com/go-nunu/nunu/blob/main/docs/zh/architecture.md)
|
||||||
|
* [上手教程](https://github.com/go-nunu/nunu/blob/main/docs/zh/tutorial.md)
|
||||||
|
|
||||||
|
## 功能
|
||||||
|
- **Gin**: https://github.com/gin-gonic/gin
|
||||||
|
- **Gorm**: https://github.com/go-gorm/gorm
|
||||||
|
- **Wire**: https://github.com/google/wire
|
||||||
|
- **Viper**: https://github.com/spf13/viper
|
||||||
|
- **Zap**: https://github.com/uber-go/zap
|
||||||
|
- **Golang-jwt**: https://github.com/golang-jwt/jwt
|
||||||
|
- **Go-redis**: https://github.com/go-redis/redis
|
||||||
|
- **Testify**: https://github.com/stretchr/testify
|
||||||
|
- **Sonyflake**: https://github.com/sony/sonyflake
|
||||||
|
- **gocron**: https://github.com/go-co-op/gocron
|
||||||
|
- More...
|
||||||
|
## 特性
|
||||||
|
* **超低学习成本和定制**:Nunu封装了Gopher最熟悉的一些流行库。您可以轻松定制应用程序以满足特定需求。
|
||||||
|
* **高性能和可扩展性**:Nunu旨在具有高性能和可扩展性。它使用最新的技术和最佳实践,确保您的应用程序可以处理高流量和大量数据。
|
||||||
|
* **安全可靠**:Nunu使用了稳定可靠的第三方库,确保您的应用程序安全可靠。
|
||||||
|
* **模块化和可扩展**:Nunu旨在具有模块化和可扩展性。您可以通过使用第三方库或编写自己的模块轻松添加新功能和功能。
|
||||||
|
* **文档完善和测试完备**:Nunu文档完善,测试完备。它提供了全面的文档和示例,帮助您快速入门。它还包括一套测试套件,确保您的应用程序按预期工作。
|
||||||
|
|
||||||
|
## Nunu CLI
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
## 目录结构
|
||||||
|
```
|
||||||
|
.
|
||||||
|
├── cmd
|
||||||
|
│ └── server
|
||||||
|
│ ├── main.go
|
||||||
|
│ ├── wire.go
|
||||||
|
│ └── wire_gen.go
|
||||||
|
├── config
|
||||||
|
│ ├── local.yml
|
||||||
|
│ └── prod.yml
|
||||||
|
├── internal
|
||||||
|
│ ├── handler
|
||||||
|
│ │ ├── handler.go
|
||||||
|
│ │ └── user.go
|
||||||
|
│ ├── middleware
|
||||||
|
│ │ └── cors.go
|
||||||
|
│ ├── model
|
||||||
|
│ │ └── user.go
|
||||||
|
│ ├── repository
|
||||||
|
│ │ ├── repository.go
|
||||||
|
│ │ └── user.go
|
||||||
|
│ ├── server
|
||||||
|
│ │ └── http.go
|
||||||
|
│ └── service
|
||||||
|
│ ├── service.go
|
||||||
|
│ └── user.go
|
||||||
|
├── pkg
|
||||||
|
├── LICENSE
|
||||||
|
├── README.md
|
||||||
|
├── README_zh.md
|
||||||
|
├── go.mod
|
||||||
|
└── go.sum
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
这是一个经典的Golang 项目的目录结构,包含以下目录:
|
||||||
|
|
||||||
|
- cmd: 存放应用程序的入口点,包括主函数和依赖注入的代码。
|
||||||
|
- server: 应用程序的主要入口点,包含主函数和依赖注入的代码。
|
||||||
|
- main.go: 主函数,用于启动应用程序。
|
||||||
|
- wire.go: 使用Wire库生成的依赖注入代码。
|
||||||
|
- wire_gen.go: 使用Wire库生成的依赖注入代码。
|
||||||
|
|
||||||
|
- config: 存放应用程序的配置文件。
|
||||||
|
- local.yml: 本地环境的配置文件。
|
||||||
|
- prod.yml: 生产环境的配置文件。
|
||||||
|
|
||||||
|
- internal: 存放应用程序的内部代码。
|
||||||
|
- handler: 处理HTTP请求的处理程序。
|
||||||
|
- handler.go: 处理HTTP请求的通用处理程序。
|
||||||
|
- user.go: 处理用户相关的HTTP请求的处理程序。
|
||||||
|
- middleware: 存放中间件代码。
|
||||||
|
- cors.go: 跨域资源共享中间件。
|
||||||
|
- model: 存放数据模型代码。
|
||||||
|
- user.go: 用户数据模型。
|
||||||
|
- repository: 存放数据访问代码。
|
||||||
|
- repository.go: 数据访问的通用接口。
|
||||||
|
- user.go: 用户数据访问接口的实现。
|
||||||
|
- server: 存放服务器代码。
|
||||||
|
- http.go: HTTP服务器的实现。
|
||||||
|
- service: 存放业务逻辑代码。
|
||||||
|
- service.go: 业务逻辑的通用接口。
|
||||||
|
- user.go: 用户业务逻辑的实现。
|
||||||
|
|
||||||
|
- pkg: 存放应用程序的公共包。
|
||||||
|
- storage: 存放应用程序的存储文件。
|
||||||
|
- go.mod: Go模块文件。
|
||||||
|
- go.sum: Go模块的依赖版本文件。
|
||||||
|
|
||||||
|
## 要求
|
||||||
|
要使用Nunu,您需要在系统上安装以下软件:
|
||||||
|
|
||||||
|
* Golang 1.16或更高版本
|
||||||
|
* Git
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 安装
|
||||||
|
|
||||||
|
您可以通过以下命令安装Nunu:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go install github.com/go-nunu/nunu@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### 创建新项目
|
||||||
|
|
||||||
|
您可以使用以下命令创建一个新的Golang项目:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nunu new projectName
|
||||||
|
```
|
||||||
|
默认拉取github源,你也可以使用国内加速仓库
|
||||||
|
```
|
||||||
|
// 使用基础模板
|
||||||
|
nunu new projectName -r https://gitee.com/go-nunu/nunu-layout-basic.git
|
||||||
|
// 使用高级模板
|
||||||
|
nunu new projectName -r https://gitee.com/go-nunu/nunu-layout-advanced.git
|
||||||
|
```
|
||||||
|
|
||||||
|
此命令将创建一个名为`projectName`的目录,并在其中生成一个优雅的Golang项目结构。
|
||||||
|
|
||||||
|
### 创建组件
|
||||||
|
|
||||||
|
您可以使用以下命令为项目创建handler、service和dao等组件:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nunu create handler user
|
||||||
|
nunu create service user
|
||||||
|
nunu create repository user
|
||||||
|
nunu create model user
|
||||||
|
```
|
||||||
|
或
|
||||||
|
```
|
||||||
|
nunu create all user
|
||||||
|
```
|
||||||
|
这些命令将分别创建一个名为`UserHandler`、`UserService`、`UserDao`和`UserModel`的组件,并将它们放置在正确的目录中。
|
||||||
|
|
||||||
|
### 启动项目
|
||||||
|
|
||||||
|
您可以使用以下命令快速启动项目:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nunu run
|
||||||
|
```
|
||||||
|
|
||||||
|
此命令将启动您的Golang项目,并支持文件更新热重启。
|
||||||
|
|
||||||
|
### 编译wire.go
|
||||||
|
|
||||||
|
您可以使用以下命令快速编译`wire.go`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nunu wire
|
||||||
|
```
|
||||||
|
|
||||||
|
此命令将编译您的`wire.go`文件,并生成所需的依赖项。
|
||||||
|
|
||||||
|
## 贡献
|
||||||
|
|
||||||
|
如果您发现任何问题或有任何改进意见,请随时提出问题或提交拉取请求。我们非常欢迎您的贡献!
|
||||||
|
|
||||||
|
## 许可证
|
||||||
|
|
||||||
|
Nunu是根据MIT许可证发布的。有关更多信息,请参见[LICENSE](LICENSE)文件。
|
25
backend/api/v1/config.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package v1
|
||||||
|
|
||||||
|
// WhoisApiInterface is the interface for WhoisApi
|
||||||
|
type WhoisApiInterface struct {
|
||||||
|
Label string `json:"label"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Order int `json:"order"`
|
||||||
|
Show bool `json:"show"`
|
||||||
|
Disabled bool `json:"disabled"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DNSApiInterface is the interface for DNSApi
|
||||||
|
type DNSApiInterface struct {
|
||||||
|
Label string `json:"label"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Order int `json:"order"`
|
||||||
|
Show bool `json:"show"`
|
||||||
|
Disabled bool `json:"disabled"`
|
||||||
|
IName string `json:"iName"`
|
||||||
|
Flag string `json:"flag"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileConfig struct {
|
||||||
|
Config string `json:"config" binding:"required"`
|
||||||
|
}
|
62
backend/api/v1/respone.go
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Data interface{} `json:"data"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
ERROR = 500
|
||||||
|
SUCCESS = 200
|
||||||
|
)
|
||||||
|
|
||||||
|
func Result(code int, data interface{}, msg string, c *gin.Context) {
|
||||||
|
// 开始时间
|
||||||
|
c.JSON(http.StatusOK, Response{
|
||||||
|
code,
|
||||||
|
data,
|
||||||
|
msg,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Ok(c *gin.Context) {
|
||||||
|
Result(SUCCESS, map[string]interface{}{}, "操作成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func OkWithMessage(message string, c *gin.Context) {
|
||||||
|
Result(SUCCESS, map[string]interface{}{}, message, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func OkWithData(data interface{}, c *gin.Context) {
|
||||||
|
Result(SUCCESS, data, "查询成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func OkWithDetailed(data interface{}, message string, c *gin.Context) {
|
||||||
|
Result(SUCCESS, data, message, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Fail(c *gin.Context) {
|
||||||
|
Result(ERROR, map[string]interface{}{}, "操作失败", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func FailWithMessage(message string, c *gin.Context) {
|
||||||
|
Result(ERROR, map[string]interface{}{}, message, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NoAuth(message string, c *gin.Context) {
|
||||||
|
c.JSON(http.StatusUnauthorized, Response{
|
||||||
|
7,
|
||||||
|
nil,
|
||||||
|
message,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func FailWithDetailed(data interface{}, message string, c *gin.Context) {
|
||||||
|
Result(ERROR, data, message, c)
|
||||||
|
}
|
82
backend/api/v1/tianhu.go
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
package v1
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// TianHuWhoisResponse represents the top-level structure of the WHOIS response.
|
||||||
|
type TianHuWhoisResponse struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Data TianHuWhoisData `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TianHuWhoisData represents the detailed data part of the WHOIS response.
|
||||||
|
type TianHuWhoisData struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
Result string `json:"result"`
|
||||||
|
Status int `json:"status"`
|
||||||
|
Formatted TianHuFormattedWhois `json:"formatted"`
|
||||||
|
TLD string `json:"tld"`
|
||||||
|
Timezone TianHuWhoisTimezone `json:"timezone"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TianHuFormattedWhois represents the formatted WHOIS data.
|
||||||
|
type TianHuFormattedWhois struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
Domain TianHuDomainInfo `json:"domain"`
|
||||||
|
Registrar TianHuRegistrarInfo `json:"registrar"`
|
||||||
|
Registrant TianHuRegistrantInfo `json:"registrant"`
|
||||||
|
Administrative TianHuAdministrativeInfo `json:"administrative"`
|
||||||
|
Technical TianHuTechnicalInfo `json:"technical"`
|
||||||
|
Billing TianHuBillingInfo `json:"billing"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TianHuDomainInfo contains information about the domain.
|
||||||
|
type TianHuDomainInfo struct {
|
||||||
|
NameServers []string `json:"name_servers"`
|
||||||
|
Status []string `json:"status"`
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
WhoisServer string `json:"whois_server"`
|
||||||
|
UpdatedDate time.Time `json:"updated_date"`
|
||||||
|
CreatedDate time.Time `json:"created_date"`
|
||||||
|
ExpiredDate time.Time `json:"expired_date"`
|
||||||
|
DNSSEC bool `json:"dnssec"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TianHuRegistrarInfo contains information about the registrar.
|
||||||
|
type TianHuRegistrarInfo struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
ReferralURL string `json:"referral_url"`
|
||||||
|
RegistrarName string `json:"registrar_name"`
|
||||||
|
RegistrarIANAID string `json:"registrar_ianaid"`
|
||||||
|
RegistrarEmail string `json:"registrar_email"`
|
||||||
|
RegistrarPhone string `json:"registrar_phone"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TianHuRegistrantInfo, TianHuAdministrativeInfo, TianHuTechnicalInfo, and TianHuBillingInfo
|
||||||
|
// can be defined similarly, depending on the details you wish to include.
|
||||||
|
type TianHuRegistrantInfo struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
// Additional fields can be added here.
|
||||||
|
}
|
||||||
|
|
||||||
|
type TianHuAdministrativeInfo struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
// Additional fields can be added here.
|
||||||
|
}
|
||||||
|
|
||||||
|
type TianHuTechnicalInfo struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
// Additional fields can be added here.
|
||||||
|
}
|
||||||
|
|
||||||
|
type TianHuBillingInfo struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
// Additional fields can be added here.
|
||||||
|
}
|
||||||
|
|
||||||
|
// TianHuWhoisTimezone represents the timezone information of the WHOIS data.
|
||||||
|
type TianHuWhoisTimezone struct {
|
||||||
|
UTCOffset int `json:"utcoffset"`
|
||||||
|
Demo time.Time `json:"demo"`
|
||||||
|
}
|
14
backend/api/v1/whois.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package v1
|
||||||
|
|
||||||
|
type WhoisServer struct {
|
||||||
|
Name string `json:"name" binding:"required"`
|
||||||
|
Domain string `json:"domain" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WhocxRequestParams 请求参数结构体
|
||||||
|
type WhocxRequestParams struct {
|
||||||
|
Domain string `json:"domain"` // 域名 (必选)
|
||||||
|
Whois string `json:"whois"` // 域名的whois原始信息 (必选)
|
||||||
|
Lang string `json:"lang,omitempty"` // 语言代码 (可选)
|
||||||
|
TimeZone string `json:"time_zone,omitempty"` // 时区 (可选)
|
||||||
|
}
|
24
backend/cmd/server/main.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"whois-go/pkg/config"
|
||||||
|
"whois-go/pkg/http"
|
||||||
|
"whois-go/pkg/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
conf := config.NewConfig()
|
||||||
|
logger := log.NewLog(conf)
|
||||||
|
|
||||||
|
logger.Info("server start", zap.String("host", "http://127.0.0.1:"+conf.GetString("http.port")))
|
||||||
|
|
||||||
|
app, cleanup, err := newApp(conf, logger)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
http.Run(app, fmt.Sprintf(":%d", conf.GetInt("http.port")))
|
||||||
|
}
|
43
backend/cmd/server/wire.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
//go:build wireinject
|
||||||
|
// +build wireinject
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/google/wire"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"whois-go/internal/handler"
|
||||||
|
"whois-go/internal/repository"
|
||||||
|
"whois-go/internal/server"
|
||||||
|
"whois-go/internal/service"
|
||||||
|
"whois-go/pkg/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ServerSet = wire.NewSet(server.NewServerHTTP)
|
||||||
|
|
||||||
|
var RepositorySet = wire.NewSet(
|
||||||
|
repository.NewDb,
|
||||||
|
repository.NewRepository,
|
||||||
|
)
|
||||||
|
|
||||||
|
var ServiceSet = wire.NewSet(
|
||||||
|
service.NewService,
|
||||||
|
service.NewUserService,
|
||||||
|
service.NewSystemService,
|
||||||
|
)
|
||||||
|
|
||||||
|
var HandlerSet = wire.NewSet(
|
||||||
|
handler.NewHandler,
|
||||||
|
handler.NewUserHandler,
|
||||||
|
handler.NewSystemHandler,
|
||||||
|
)
|
||||||
|
|
||||||
|
func newApp(*viper.Viper, *log.Logger) (*gin.Engine, func(), error) {
|
||||||
|
panic(wire.Build(
|
||||||
|
ServerSet,
|
||||||
|
//RepositorySet,
|
||||||
|
ServiceSet,
|
||||||
|
HandlerSet,
|
||||||
|
))
|
||||||
|
}
|
40
backend/cmd/server/wire_gen.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// Code generated by Wire. DO NOT EDIT.
|
||||||
|
|
||||||
|
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
|
||||||
|
//go:build !wireinject
|
||||||
|
// +build !wireinject
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/google/wire"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"whois-go/internal/handler"
|
||||||
|
"whois-go/internal/repository"
|
||||||
|
"whois-go/internal/server"
|
||||||
|
"whois-go/internal/service"
|
||||||
|
"whois-go/pkg/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Injectors from wire.go:
|
||||||
|
|
||||||
|
func newApp(viperViper *viper.Viper, logger *log.Logger) (*gin.Engine, func(), error) {
|
||||||
|
handlerHandler := handler.NewHandler(logger)
|
||||||
|
serviceService := service.NewService(logger)
|
||||||
|
systemService := service.NewSystemService(serviceService, logger)
|
||||||
|
systemHandler := handler.NewSystemHandler(handlerHandler, systemService)
|
||||||
|
engine := server.NewServerHTTP(logger, systemHandler)
|
||||||
|
return engine, func() {
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// wire.go:
|
||||||
|
|
||||||
|
var ServerSet = wire.NewSet(server.NewServerHTTP)
|
||||||
|
|
||||||
|
var RepositorySet = wire.NewSet(repository.NewDb, repository.NewRepository)
|
||||||
|
|
||||||
|
var ServiceSet = wire.NewSet(service.NewService, service.NewUserService, service.NewSystemService)
|
||||||
|
|
||||||
|
var HandlerSet = wire.NewSet(handler.NewHandler, handler.NewUserHandler, handler.NewSystemHandler)
|
47
backend/config/json/dns.json
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"label": "本地接口",
|
||||||
|
"name": "nuxt",
|
||||||
|
"order": 0,
|
||||||
|
"show": true,
|
||||||
|
"disabled": false,
|
||||||
|
"iName": "本地 DNS",
|
||||||
|
"flag": "material-symbols:dns-outline"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Google",
|
||||||
|
"name": "google",
|
||||||
|
"order": 1,
|
||||||
|
"show": true,
|
||||||
|
"disabled": false,
|
||||||
|
"iName": "Google",
|
||||||
|
"flag": "flat-color-icons:google"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "AliYun",
|
||||||
|
"name": "aliyun",
|
||||||
|
"order": 2,
|
||||||
|
"show": true,
|
||||||
|
"disabled": false,
|
||||||
|
"iName": "AliYun",
|
||||||
|
"flag": "ant-design:aliyun-outlined"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Tencent",
|
||||||
|
"name": "tencent",
|
||||||
|
"order": 3,
|
||||||
|
"show": true,
|
||||||
|
"disabled": false,
|
||||||
|
"iName": "Tencent",
|
||||||
|
"flag": "emojione:cloud"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Cloudflare",
|
||||||
|
"name": "cloudflare",
|
||||||
|
"order": 4,
|
||||||
|
"show": true,
|
||||||
|
"disabled": false,
|
||||||
|
"iName": "CloudFlare",
|
||||||
|
"flag": "skill-icons:cloudflare-light"
|
||||||
|
}
|
||||||
|
]
|
18
backend/config/json/domain.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"label": "本地接口",
|
||||||
|
"name": "nuxt",
|
||||||
|
"order": 0,
|
||||||
|
"show": false,
|
||||||
|
"disabled": true,
|
||||||
|
"iName": "本地 DNS",
|
||||||
|
"flag": "material-symbols:dns-outline"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "TIAN.HU",
|
||||||
|
"name": "tianhu",
|
||||||
|
"order": 1,
|
||||||
|
"show": true,
|
||||||
|
"disabled": false
|
||||||
|
}
|
||||||
|
]
|
23
backend/config/json/whois.json
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"label": "本地接口",
|
||||||
|
"name": "nuxt",
|
||||||
|
"order": 0,
|
||||||
|
"show": true,
|
||||||
|
"disabled": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "WHO.CX",
|
||||||
|
"name": "whocx",
|
||||||
|
"order": 1,
|
||||||
|
"show": false,
|
||||||
|
"disabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "TIAN.HU",
|
||||||
|
"name": "tianhu",
|
||||||
|
"order": 2,
|
||||||
|
"show": true,
|
||||||
|
"disabled": false
|
||||||
|
}
|
||||||
|
]
|
34
backend/config/local.yml
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
env: local
|
||||||
|
http:
|
||||||
|
port: 8000
|
||||||
|
|
||||||
|
#网站配置
|
||||||
|
login:
|
||||||
|
username: admin
|
||||||
|
password: 123456
|
||||||
|
siteConfig:
|
||||||
|
logoLeftText: NuxtLeftLogo
|
||||||
|
logoRightText: NuxtRightLogo
|
||||||
|
defaultSelectOptions:
|
||||||
|
- label: Whois
|
||||||
|
value: whois
|
||||||
|
- label: Dns
|
||||||
|
value: dns
|
||||||
|
- label: Domain
|
||||||
|
value: domain
|
||||||
|
whoisSeverApi:
|
||||||
|
- whocx: https://who.cx/api/whois_extract
|
||||||
|
- tianhu: https://api.tian.hu/whois.php
|
||||||
|
dnsSeverApi:
|
||||||
|
- google: https://dns.google/resolve
|
||||||
|
- cloudflare: http://1.1.1.1/dns-query
|
||||||
|
- aliyun: https://223.5.5.5/resolve
|
||||||
|
- tencent: https://doh.pub/dns-query
|
||||||
|
log:
|
||||||
|
log_level: debug
|
||||||
|
encoding: console # json or console
|
||||||
|
log_file_name: "./storage/logs/server.log"
|
||||||
|
max_backups: 30 # 日志文件最多保存多少个备份
|
||||||
|
max_age: 7 # 文件最多保存多少天
|
||||||
|
max_size: 1024 # 每个日志文件保存的最大尺寸 单位:M
|
||||||
|
compress: true # 是否压缩
|
27
backend/config/prod.yml
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
env: local
|
||||||
|
http:
|
||||||
|
port: 8000
|
||||||
|
security:
|
||||||
|
api_sign:
|
||||||
|
app_key: 123456
|
||||||
|
app_security: 123456
|
||||||
|
jwt:
|
||||||
|
key: 1234
|
||||||
|
data:
|
||||||
|
mysql:
|
||||||
|
user: root:123456@tcp(127.0.0.1:3380)/user?charset=utf8mb4&parseTime=True&loc=Local
|
||||||
|
redis:
|
||||||
|
addr: 127.0.0.1:6350
|
||||||
|
password: ""
|
||||||
|
db: 0
|
||||||
|
read_timeout: 0.2s
|
||||||
|
write_timeout: 0.2s
|
||||||
|
|
||||||
|
log:
|
||||||
|
log_level: info
|
||||||
|
encoding: json # json or console
|
||||||
|
log_file_name: "./storage/logs/server.log"
|
||||||
|
max_backups: 30 # 日志文件最多保存多少个备份
|
||||||
|
max_age: 7 # 文件最多保存多少天
|
||||||
|
max_size: 1024 # 每个日志文件保存的最大尺寸 单位:M
|
||||||
|
compress: true # 是否压缩
|
28
backend/go.mod
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
module whois-go
|
||||||
|
|
||||||
|
go 1.16
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/bytedance/sonic v1.11.3 // indirect
|
||||||
|
github.com/chenzhuoyu/iasm v0.9.1 // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.7.0
|
||||||
|
github.com/gin-gonic/gin v1.9.1
|
||||||
|
github.com/go-playground/validator/v10 v10.19.0 // indirect
|
||||||
|
github.com/google/uuid v1.4.0
|
||||||
|
github.com/google/wire v0.6.0
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.0 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1
|
||||||
|
github.com/sony/sonyflake v1.1.0
|
||||||
|
github.com/spf13/viper v1.18.2
|
||||||
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
|
go.uber.org/zap v1.27.0
|
||||||
|
golang.org/x/arch v0.7.0 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect
|
||||||
|
golang.org/x/net v0.23.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.33.0 // indirect
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||||
|
gorm.io/gorm v1.25.9
|
||||||
|
)
|
2592
backend/go.sum
Normal file
6
backend/internal/config/config.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
SiteConfig SiteConfig `mapstructure:"siteConfig" json:"siteConfig" yaml:"siteConfig"`
|
||||||
|
Login LoginConfig `mapstructure:"login" json:"login" yaml:"login"`
|
||||||
|
}
|
6
backend/internal/config/login.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
type LoginConfig struct {
|
||||||
|
Username string `mapstructure:"username" json:"username" yaml:"username"`
|
||||||
|
Password string `mapstructure:"password" json:"password" yaml:"password"`
|
||||||
|
}
|
13
backend/internal/config/siteConfig.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
type SiteConfig struct {
|
||||||
|
LogoLeftText string `mapstructure:"logoLeftText" json:"logoLeftText" yaml:"logoLeftText"` // Logo左文字
|
||||||
|
LogoRightText string `mapstructure:"logoRightText" json:"logoRightText" yaml:"logoRightText"` // logo右文字
|
||||||
|
DefaultSelectOptions []SelectOptions `mapstructure:"defaultSelectOptions" json:"defaultSelectOptions" yaml:"defaultSelectOptions"` // 默认选择项
|
||||||
|
WhoisServerApi map[string]string `mapstructure:"whoisSeverApi" json:"whoisSeverApi" yaml:"whoisSeverApi"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SelectOptions struct {
|
||||||
|
Label string `json:"label"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
}
|
9
backend/internal/global/global.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package global
|
||||||
|
|
||||||
|
import "whois-go/internal/config"
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Version is the version of the application.
|
||||||
|
G_Version = "0.1"
|
||||||
|
G_CONFIG config.Server
|
||||||
|
)
|
15
backend/internal/handler/handler.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"whois-go/pkg/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Handler struct {
|
||||||
|
logger *log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHandler(logger *log.Logger) *Handler {
|
||||||
|
return &Handler{
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
78
backend/internal/handler/system.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"net/http"
|
||||||
|
v1 "whois-go/api/v1"
|
||||||
|
"whois-go/internal/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SystemHandler interface {
|
||||||
|
GetWebSiteConfig(ctx *gin.Context)
|
||||||
|
GetWhoisServer(c *gin.Context)
|
||||||
|
GetWhois(c *gin.Context)
|
||||||
|
}
|
||||||
|
|
||||||
|
type systemHandler struct {
|
||||||
|
*Handler
|
||||||
|
systemService service.SystemService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSystemHandler(handler *Handler, systemService service.SystemService) SystemHandler {
|
||||||
|
return &systemHandler{
|
||||||
|
Handler: handler,
|
||||||
|
systemService: systemService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *systemHandler) GetWebSiteConfig(c *gin.Context) {
|
||||||
|
config, err := h.systemService.GetSystemConfig()
|
||||||
|
if err != nil {
|
||||||
|
v1.FailWithMessage("获取失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
v1.OkWithDetailed(config, "获取成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *systemHandler) GetWhoisServer(c *gin.Context) {
|
||||||
|
var fileConfig v1.FileConfig
|
||||||
|
// 从请求体中绑定JSON数据到configs变量
|
||||||
|
if err := c.BindJSON(&fileConfig); err != nil {
|
||||||
|
// 如果解析出错,返回错误信息
|
||||||
|
v1.FailWithMessage("解析失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
config, err := h.systemService.GetWhoisJson(fileConfig.Config)
|
||||||
|
if err != nil {
|
||||||
|
v1.FailWithMessage("获取失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
v1.OkWithDetailed(config, "获取成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *systemHandler) GetWhois(c *gin.Context) {
|
||||||
|
var whoisServer v1.WhoisServer
|
||||||
|
if err := c.BindJSON(&whoisServer); err != nil {
|
||||||
|
// 如果解析出错,返回错误信息
|
||||||
|
v1.FailWithMessage("解析失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//判断whoisServer的值来进行不同的函数
|
||||||
|
switch whoisServer.Name {
|
||||||
|
case "whocx":
|
||||||
|
resp := h.systemService.WhoisServerIsWhocx(whoisServer.Domain)
|
||||||
|
v1.OkWithDetailed(resp, "获取成功", c)
|
||||||
|
return
|
||||||
|
case "tianhu":
|
||||||
|
resp, err := h.systemService.WhoisServerIsTianhu(whoisServer.Domain)
|
||||||
|
if err != nil {
|
||||||
|
v1.FailWithMessage("获取失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusOK, resp)
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
v1.FailWithMessage("未知的Whois服务器", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
20
backend/internal/handler/user.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"whois-go/internal/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserHandler interface {
|
||||||
|
}
|
||||||
|
|
||||||
|
type userHandler struct {
|
||||||
|
*Handler
|
||||||
|
userService service.UserService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserHandler(handler *Handler, userService service.UserService) UserHandler {
|
||||||
|
return &userHandler{
|
||||||
|
Handler: handler,
|
||||||
|
userService: userService,
|
||||||
|
}
|
||||||
|
}
|
23
backend/internal/middleware/cors.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CORSMiddleware() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
method := c.Request.Method
|
||||||
|
c.Header("Access-Control-Allow-Origin", c.GetHeader("Origin"))
|
||||||
|
c.Header("Access-Control-Allow-Credentials", "true")
|
||||||
|
|
||||||
|
if method == "OPTIONS" {
|
||||||
|
c.Header("Access-Control-Allow-Methods", c.GetHeader("Access-Control-Request-Method"))
|
||||||
|
c.Header("Access-Control-Allow-Headers", c.GetHeader("Access-Control-Request-Headers"))
|
||||||
|
c.Header("Access-Control-Max-Age", "7200")
|
||||||
|
c.AbortWithStatus(http.StatusNoContent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
29
backend/internal/repository/repository.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"whois-go/pkg/log"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Repository struct {
|
||||||
|
db *gorm.DB
|
||||||
|
//rdb *redis.Client
|
||||||
|
logger *log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRepository(logger *log.Logger, db *gorm.DB) *Repository {
|
||||||
|
return &Repository{
|
||||||
|
db: db,
|
||||||
|
//rdb: rdb,
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func NewDb() *gorm.DB {
|
||||||
|
// TODO: init db
|
||||||
|
//db, err := gorm.Open(mysql.Open(conf.GetString("data.mysql.user")), &gorm.Config{})
|
||||||
|
//if err != nil {
|
||||||
|
// panic(err)
|
||||||
|
//}
|
||||||
|
//return db
|
||||||
|
return &gorm.DB{}
|
||||||
|
}
|
37
backend/internal/server/http.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"whois-go/internal/handler"
|
||||||
|
"whois-go/internal/middleware"
|
||||||
|
"whois-go/pkg/helper/resp"
|
||||||
|
"whois-go/pkg/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewServerHTTP(
|
||||||
|
logger *log.Logger,
|
||||||
|
systemHandler handler.SystemHandler,
|
||||||
|
) *gin.Engine {
|
||||||
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
r := gin.Default()
|
||||||
|
r.Use(
|
||||||
|
middleware.CORSMiddleware(),
|
||||||
|
)
|
||||||
|
r.GET("/", func(ctx *gin.Context) {
|
||||||
|
resp.HandleSuccess(ctx, map[string]interface{}{
|
||||||
|
"say": "This is GoLang!",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取配置文件内容
|
||||||
|
api := r.Group("/api")
|
||||||
|
api.POST("/getWebSiteConfig", systemHandler.GetWebSiteConfig)
|
||||||
|
|
||||||
|
api.POST("/getWhoisServer", systemHandler.GetWhoisServer)
|
||||||
|
|
||||||
|
server := api.Group("/server")
|
||||||
|
server.POST("/getWhois", systemHandler.GetWhois)
|
||||||
|
server.POST("/getDns", systemHandler.GetWhois)
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
13
backend/internal/service/service.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import "whois-go/pkg/log"
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
logger *log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewService(logger *log.Logger) *Service {
|
||||||
|
return &Service{
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
94
backend/internal/service/system.go
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
v1 "whois-go/api/v1"
|
||||||
|
"whois-go/internal/config"
|
||||||
|
"whois-go/internal/global"
|
||||||
|
"whois-go/internal/utils"
|
||||||
|
"whois-go/pkg/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SystemService interface {
|
||||||
|
GetSystemConfig() (config.SiteConfig, error)
|
||||||
|
GetWhoisJson(fileConfig string) ([]v1.WhoisApiInterface, error)
|
||||||
|
WhoisServerIsWhocx(domain string) string
|
||||||
|
WhoisServerIsTianhu(domain string) (v1.TianHuWhoisResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSystemService(service *Service, logger *log.Logger) SystemService {
|
||||||
|
return &systemService{
|
||||||
|
Service: service,
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type systemService struct {
|
||||||
|
*Service
|
||||||
|
logger *log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSystemConfig 读取配置文件
|
||||||
|
func (s *systemService) GetSystemConfig() (config.SiteConfig, error) {
|
||||||
|
return global.G_CONFIG.SiteConfig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *systemService) GetWhoisJson(fileConfig string) ([]v1.WhoisApiInterface, error) {
|
||||||
|
file, err := os.Open(fmt.Sprintf("config/json/%s.json", fileConfig))
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Fatal(fmt.Sprintf("Error opening file: %s", err))
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
// 读取文件内容
|
||||||
|
bytes, err := ioutil.ReadAll(file)
|
||||||
|
|
||||||
|
// 反序列化JSON到结构体切片
|
||||||
|
var apis []v1.WhoisApiInterface
|
||||||
|
if err = json.Unmarshal(bytes, &apis); err != nil {
|
||||||
|
s.logger.Fatal(fmt.Sprintf("Error unmarshalling JSON: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return apis, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *systemService) WhoisServerIsWhocx(domain string) string {
|
||||||
|
whocxURL := global.G_CONFIG.SiteConfig.WhoisServerApi["whocx"]
|
||||||
|
params := url.Values{}
|
||||||
|
params.Set("domain", domain)
|
||||||
|
params.Set("whois", "Domain Name: ")
|
||||||
|
params.Set("lang", "zh")
|
||||||
|
params.Set("time_zone", "8")
|
||||||
|
request, err := utils.SendPostRequest(whocxURL, params)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return request
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *systemService) WhoisServerIsTianhu(domain string) (v1.TianHuWhoisResponse, error) {
|
||||||
|
tianhuURL := global.G_CONFIG.SiteConfig.WhoisServerApi["tianhu"]
|
||||||
|
params := url.Values{}
|
||||||
|
//domain=baidu.com&action=searchWhois
|
||||||
|
params.Set("domain", domain)
|
||||||
|
params.Set("action", "searchWhois")
|
||||||
|
var tianhuResp v1.TianHuWhoisResponse
|
||||||
|
// Append query parameters to the URL
|
||||||
|
fullURL := tianhuURL + "?" + params.Encode()
|
||||||
|
// Perform the GET request
|
||||||
|
response, err := http.Get(fullURL)
|
||||||
|
|
||||||
|
defer response.Body.Close()
|
||||||
|
|
||||||
|
// Read the response body
|
||||||
|
body, err := ioutil.ReadAll(response.Body)
|
||||||
|
|
||||||
|
err = json.Unmarshal(body, &tianhuResp)
|
||||||
|
|
||||||
|
return tianhuResp, err
|
||||||
|
}
|
18
backend/internal/service/user.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
type UserService interface {
|
||||||
|
}
|
||||||
|
|
||||||
|
type userService struct {
|
||||||
|
*Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserService(service *Service) UserService {
|
||||||
|
return &userService{
|
||||||
|
Service: service,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *userService) GetUserById(id int64) {
|
||||||
|
|
||||||
|
}
|
55
backend/internal/utils/request.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FetchData 从给定的URL获取数据
|
||||||
|
func FetchData(url string) ([]byte, error) {
|
||||||
|
// 发起GET请求
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("请求失败: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// 读取响应体
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("读取响应体失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return body, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendPostRequest 发送POST请求的函数
|
||||||
|
func SendPostRequest(requestURL string, params url.Values) (string, error) {
|
||||||
|
// 创建POST请求
|
||||||
|
req, err := http.NewRequest("POST", requestURL, strings.NewReader(params.Encode()))
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("创建请求失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置请求头,表明请求体是表单数据
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
|
||||||
|
// 发送请求
|
||||||
|
client := &http.Client{}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("发送请求失败: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// 读取响应体
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("读取响应体失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(body), nil
|
||||||
|
}
|
44
backend/pkg/config/config.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"os"
|
||||||
|
"whois-go/internal/global"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewConfig() *viper.Viper {
|
||||||
|
envConf := os.Getenv("APP_CONF")
|
||||||
|
if envConf == "" {
|
||||||
|
flag.StringVar(&envConf, "conf", "config/local.yml", "config path, eg: -conf config/local.yml")
|
||||||
|
flag.Parse()
|
||||||
|
}
|
||||||
|
if envConf == "" {
|
||||||
|
envConf = "config/local.yml"
|
||||||
|
}
|
||||||
|
fmt.Println("load conf file:", envConf)
|
||||||
|
return getConfig(envConf)
|
||||||
|
|
||||||
|
}
|
||||||
|
func getConfig(path string) *viper.Viper {
|
||||||
|
conf := viper.New()
|
||||||
|
conf.SetConfigFile(path)
|
||||||
|
err := conf.ReadInConfig()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
conf.WatchConfig()
|
||||||
|
|
||||||
|
conf.OnConfigChange(func(e fsnotify.Event) {
|
||||||
|
fmt.Println("config file changed:", e.Name)
|
||||||
|
if err = conf.Unmarshal(&global.G_CONFIG); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err = conf.Unmarshal(&global.G_CONFIG); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return conf
|
||||||
|
}
|
24
backend/pkg/helper/convert/convert.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package convert
|
||||||
|
|
||||||
|
const (
|
||||||
|
base62 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
)
|
||||||
|
|
||||||
|
func IntToBase62(n int) string {
|
||||||
|
if n == 0 {
|
||||||
|
return string(base62[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
var result []byte
|
||||||
|
for n > 0 {
|
||||||
|
result = append(result, base62[n%62])
|
||||||
|
n /= 62
|
||||||
|
}
|
||||||
|
|
||||||
|
// 反转字符串
|
||||||
|
for i, j := 0, len(result)-1; i < j; i, j = i+1, j-1 {
|
||||||
|
result[i], result[j] = result[j], result[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(result)
|
||||||
|
}
|
11
backend/pkg/helper/md5/md5.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package md5
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/hex"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Md5(str string) string {
|
||||||
|
hash := md5.Sum([]byte(str))
|
||||||
|
return hex.EncodeToString(hash[:])
|
||||||
|
}
|
28
backend/pkg/helper/resp/resp.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package resp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type response struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Data interface{} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleSuccess(ctx *gin.Context, data interface{}) {
|
||||||
|
if data == nil {
|
||||||
|
data = map[string]string{}
|
||||||
|
}
|
||||||
|
resp := response{Code: 0, Message: "success", Data: data}
|
||||||
|
ctx.JSON(http.StatusOK, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleError(ctx *gin.Context, httpCode, code int, message string, data interface{}) {
|
||||||
|
if data == nil {
|
||||||
|
data = map[string]string{}
|
||||||
|
}
|
||||||
|
resp := response{Code: code, Message: message, Data: data}
|
||||||
|
ctx.JSON(httpCode, resp)
|
||||||
|
}
|
32
backend/pkg/helper/sid/sid.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package sid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"whois-go/pkg/helper/convert"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sony/sonyflake"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Sid struct {
|
||||||
|
sf *sonyflake.Sonyflake
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSid() *Sid {
|
||||||
|
sf := sonyflake.NewSonyflake(sonyflake.Settings{})
|
||||||
|
if sf == nil {
|
||||||
|
panic("sonyflake not created")
|
||||||
|
}
|
||||||
|
return &Sid{sf}
|
||||||
|
}
|
||||||
|
func (s Sid) GenString() (string, error) {
|
||||||
|
// 生成分布式ID
|
||||||
|
id, err := s.sf.NextID()
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.Wrap(err, "failed to generate sonyflake ID")
|
||||||
|
}
|
||||||
|
// 将ID转换为字符串
|
||||||
|
return convert.IntToBase62(int(id)), nil
|
||||||
|
}
|
||||||
|
func (s Sid) GenUint64() (uint64, error) {
|
||||||
|
// 生成分布式ID
|
||||||
|
return s.sf.NextID()
|
||||||
|
}
|
7
backend/pkg/helper/uuid/uuid.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package uuid
|
||||||
|
|
||||||
|
import "github.com/google/uuid"
|
||||||
|
|
||||||
|
func GenUUID() string {
|
||||||
|
return uuid.NewString()
|
||||||
|
}
|
48
backend/pkg/http/http.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Run(r *gin.Engine, addr string) {
|
||||||
|
|
||||||
|
srv := &http.Server{
|
||||||
|
Addr: addr,
|
||||||
|
Handler: r,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initializing the server in a goroutine so that
|
||||||
|
// it won't block the graceful shutdown handling below
|
||||||
|
go func() {
|
||||||
|
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||||
|
log.Fatalf("listen: %s\n", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Wait for interrupt signal to gracefully shutdown the server with
|
||||||
|
// a timeout of 5 seconds.
|
||||||
|
quit := make(chan os.Signal, 1)
|
||||||
|
// kill (no param) default send syscall.SIGTERM
|
||||||
|
// kill -2 is syscall.SIGINT
|
||||||
|
// kill -9 is syscall.SIGKILL but can't be catch, so don't need add it
|
||||||
|
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
<-quit
|
||||||
|
log.Println("Shutting down server...")
|
||||||
|
|
||||||
|
// The context is used to inform the server it has 5 seconds to finish
|
||||||
|
// the request it is currently handling
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
if err := srv.Shutdown(ctx); err != nil {
|
||||||
|
log.Fatal("Server forced to shutdown: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Server exiting")
|
||||||
|
}
|
115
backend/pkg/log/log.go
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
"gopkg.in/natefinch/lumberjack.v2"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const LOGGER_KEY = "zapLogger"
|
||||||
|
|
||||||
|
type Logger struct {
|
||||||
|
*zap.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLog(conf *viper.Viper) *Logger {
|
||||||
|
return initZap(conf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func initZap(conf *viper.Viper) *Logger {
|
||||||
|
// 日志地址 "out.log" 自定义
|
||||||
|
lp := conf.GetString("log.log_file_name")
|
||||||
|
// 日志级别 DEBUG,ERROR, INFO
|
||||||
|
lv := conf.GetString("log.log_level")
|
||||||
|
var level zapcore.Level
|
||||||
|
//debug<info<warn<error<fatal<panic
|
||||||
|
switch lv {
|
||||||
|
case "debug":
|
||||||
|
level = zap.DebugLevel
|
||||||
|
case "info":
|
||||||
|
level = zap.InfoLevel
|
||||||
|
case "warn":
|
||||||
|
level = zap.WarnLevel
|
||||||
|
case "error":
|
||||||
|
level = zap.ErrorLevel
|
||||||
|
default:
|
||||||
|
level = zap.InfoLevel
|
||||||
|
}
|
||||||
|
hook := lumberjack.Logger{
|
||||||
|
Filename: lp, // 日志文件路径
|
||||||
|
MaxSize: conf.GetInt("log.max_size"), // 每个日志文件保存的最大尺寸 单位:M
|
||||||
|
MaxBackups: conf.GetInt("log.max_backups"), // 日志文件最多保存多少个备份
|
||||||
|
MaxAge: conf.GetInt("log.max_age"), // 文件最多保存多少天
|
||||||
|
Compress: conf.GetBool("log.compress"), // 是否压缩
|
||||||
|
}
|
||||||
|
|
||||||
|
var encoder zapcore.Encoder
|
||||||
|
if conf.GetString("log.encoding") == "console" {
|
||||||
|
encoder = zapcore.NewConsoleEncoder(zapcore.EncoderConfig{
|
||||||
|
TimeKey: "ts",
|
||||||
|
LevelKey: "level",
|
||||||
|
NameKey: "Logger",
|
||||||
|
CallerKey: "caller",
|
||||||
|
MessageKey: "msg",
|
||||||
|
StacktraceKey: "stacktrace",
|
||||||
|
LineEnding: zapcore.DefaultLineEnding,
|
||||||
|
EncodeLevel: zapcore.LowercaseColorLevelEncoder,
|
||||||
|
EncodeTime: timeEncoder,
|
||||||
|
EncodeDuration: zapcore.SecondsDurationEncoder,
|
||||||
|
EncodeCaller: zapcore.FullCallerEncoder,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
encoder = zapcore.NewJSONEncoder(zapcore.EncoderConfig{
|
||||||
|
TimeKey: "ts",
|
||||||
|
LevelKey: "level",
|
||||||
|
NameKey: "logger",
|
||||||
|
CallerKey: "caller",
|
||||||
|
FunctionKey: zapcore.OmitKey,
|
||||||
|
MessageKey: "msg",
|
||||||
|
StacktraceKey: "stacktrace",
|
||||||
|
LineEnding: zapcore.DefaultLineEnding,
|
||||||
|
EncodeLevel: zapcore.LowercaseLevelEncoder,
|
||||||
|
EncodeTime: zapcore.EpochTimeEncoder,
|
||||||
|
EncodeDuration: zapcore.SecondsDurationEncoder,
|
||||||
|
EncodeCaller: zapcore.ShortCallerEncoder,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
core := zapcore.NewCore(
|
||||||
|
encoder, // 编码器配置
|
||||||
|
zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(&hook)), // 打印到控制台和文件
|
||||||
|
level, // 日志级别
|
||||||
|
)
|
||||||
|
if conf.GetString("env") != "prod" {
|
||||||
|
return &Logger{zap.New(core, zap.Development(), zap.AddCaller(), zap.AddStacktrace(zap.ErrorLevel))}
|
||||||
|
}
|
||||||
|
return &Logger{zap.New(core, zap.AddCaller(), zap.AddStacktrace(zap.ErrorLevel))}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 自定义时间编码器
|
||||||
|
func timeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
|
||||||
|
//enc.AppendString(t.Format("2006-01-02 15:04:05"))
|
||||||
|
enc.AppendString(t.Format("2006-01-02 15:04:05.000000000"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewContext 给指定的context添加字段
|
||||||
|
func (l *Logger) NewContext(ctx *gin.Context, fields ...zapcore.Field) {
|
||||||
|
ctx.Set(LOGGER_KEY, l.WithContext(ctx).With(fields...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithContext 从指定的context返回一个zap实例
|
||||||
|
func (l *Logger) WithContext(ctx *gin.Context) *Logger {
|
||||||
|
if ctx == nil {
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
zl, _ := ctx.Get(LOGGER_KEY)
|
||||||
|
ctxLogger, ok := zl.(*zap.Logger)
|
||||||
|
if ok {
|
||||||
|
return &Logger{ctxLogger}
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
}
|
@ -1,84 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
|
|
||||||
import {useTimeStore} from "~/stores/time";
|
|
||||||
|
|
||||||
const availableTimeZones = ref([
|
|
||||||
{ id: 1, name: 'UTC-12', displayName: 'International Date Line West' },
|
|
||||||
{ id: 2, name: 'UTC-11', displayName: 'Coordinated Universal Time-11' },
|
|
||||||
{ id: 3, name: 'UTC-10', displayName: 'Hawaii' },
|
|
||||||
{ id: 4, name: 'UTC-9', displayName: 'Alaska' },
|
|
||||||
{ id: 5, name: 'UTC-8', displayName: 'Pacific Time (US & Canada)' },
|
|
||||||
{ id: 6, name: 'UTC-7', displayName: 'Mountain Time (US & Canada)' },
|
|
||||||
{ id: 7, name: 'UTC-6', displayName: 'Central Time (US & Canada), Mexico City' },
|
|
||||||
{ id: 8, name: 'UTC-5', displayName: 'Eastern Time (US & Canada), Bogota, Lima' },
|
|
||||||
{ id: 9, name: 'UTC-4', displayName: 'Atlantic Time (Canada), Caracas, La Paz' },
|
|
||||||
{ id: 10, name: 'UTC-3', displayName: 'Buenos Aires, Georgetown' },
|
|
||||||
{ id: 11, name: 'UTC-2', displayName: 'Coordinated Universal Time-02' },
|
|
||||||
{ id: 12, name: 'UTC-1', displayName: 'Azores' },
|
|
||||||
{ id: 13, name: 'UTC', displayName: 'Coordinated Universal Time' },
|
|
||||||
{ id: 14, name: 'UTC+1', displayName: 'Brussels, Copenhagen, Madrid, Paris' },
|
|
||||||
{ id: 15, name: 'UTC+2', displayName: 'Athens, Bucharest, Istanbul' },
|
|
||||||
{ id: 16, name: 'UTC+3', displayName: 'Moscow, St. Petersburg, Nairobi' },
|
|
||||||
{ id: 17, name: 'UTC+3:30', displayName: 'Tehran' },
|
|
||||||
{ id: 18, name: 'UTC+4', displayName: 'Abu Dhabi, Muscat' },
|
|
||||||
{ id: 19, name: 'UTC+4:30', displayName: 'Kabul' },
|
|
||||||
{ id: 20, name: 'UTC+5', displayName: 'Islamabad, Karachi, Tashkent' },
|
|
||||||
{ id: 21, name: 'UTC+5:30', displayName: 'Chennai, Kolkata, Mumbai, New Delhi' },
|
|
||||||
{ id: 22, name: 'UTC+5:45', displayName: 'Kathmandu' },
|
|
||||||
{ id: 23, name: 'UTC+6', displayName: 'Astana, Dhaka' },
|
|
||||||
{ id: 24, name: 'UTC+6:30', displayName: 'Yangon (Rangoon)' },
|
|
||||||
{ id: 25, name: 'UTC+7', displayName: 'Bangkok, Hanoi, Jakarta' },
|
|
||||||
{ id: 26, name: 'UTC+8', displayName: 'Beijing, Hong Kong, Singapore, Taipei' },
|
|
||||||
{ id: 27, name: 'UTC+9', displayName: 'Osaka, Sapporo, Tokyo' },
|
|
||||||
{ id: 28, name: 'UTC+9:30', displayName: 'Adelaide, Darwin' },
|
|
||||||
{ id: 29, name: 'UTC+10', displayName: 'Brisbane, Canberra, Melbourne, Sydney' },
|
|
||||||
{ id: 30, name: 'UTC+11', displayName: 'Solomon Is., New Caledonia' },
|
|
||||||
{ id: 31, name: 'UTC+12', displayName: 'Auckland, Wellington' },
|
|
||||||
{ id: 32, name: 'UTC+13', displayName: 'Nuku alofa' }
|
|
||||||
]);
|
|
||||||
|
|
||||||
const timeStore = useTimeStore()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<HeadlessListbox
|
|
||||||
v-model="timeStore.timeZones"
|
|
||||||
as="div"
|
|
||||||
class="relative flex items-center"
|
|
||||||
>
|
|
||||||
<HeadlessListboxLabel class="sr-only">
|
|
||||||
Theme
|
|
||||||
</HeadlessListboxLabel>
|
|
||||||
<HeadlessListboxButton type="button" title="Change Color">
|
|
||||||
<div
|
|
||||||
class="flex h-10 w-10 items-center justify-center rounded-lg bg-gray-100 dark:bg-gray-700"
|
|
||||||
>
|
|
||||||
<Icon name="ri:time-zone-line" class=" text-lg dark:text-white" />
|
|
||||||
</div>
|
|
||||||
</HeadlessListboxButton>
|
|
||||||
<HeadlessListboxOptions
|
|
||||||
class="absolute top-full right-0 z-[999] mt-2 w-40 max-h-60 overflow-y-auto rounded-lg bg-white text-sm font-semibold text-gray-700 shadow-lg shadow-gray-300 outline-none dark:bg-gray-800 dark:text-white dark:shadow-gray-500 dark:ring-0"
|
|
||||||
>
|
|
||||||
<HeadlessListboxOption
|
|
||||||
v-for="item in availableTimeZones"
|
|
||||||
:key="item.id"
|
|
||||||
:value="item.name"
|
|
||||||
class="flex w-full cursor-pointer items-center justify-between py-2 px-3 hover:bg-gray-100 dark:hover:bg-gray-600"
|
|
||||||
:class="{
|
|
||||||
'text-white-500 bg-gray-200 dark:bg-gray-500/50':
|
|
||||||
timeStore.timeZones === item.name,
|
|
||||||
'hover:bg-gray-200 dark:hover:bg-gray-700/30':
|
|
||||||
timeStore.timeZones !== item.name,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<span class="truncate">
|
|
||||||
{{ item.name }}
|
|
||||||
</span>
|
|
||||||
<span class="flex items-center justify-center text-sm">
|
|
||||||
</span>
|
|
||||||
</HeadlessListboxOption>
|
|
||||||
</HeadlessListboxOptions>
|
|
||||||
</HeadlessListbox>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
@ -1,43 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
defineProps({
|
|
||||||
data: {
|
|
||||||
type: Object,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="flex bg-gray-100 p-8">
|
|
||||||
<div class="w-full mx-auto">
|
|
||||||
<div class="bg-white shadow-lg rounded-lg p-6 mb-4" v-for="item in data.Answer">
|
|
||||||
<h2 class="text-xl font-bold">{{ item.name }}</h2>
|
|
||||||
<div class="grid grid-cols-2 gap-4">
|
|
||||||
<div>
|
|
||||||
<p class="font-semibold">Type</p>
|
|
||||||
<p>{{ item.type }}</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p class="font-semibold">TTL</p>
|
|
||||||
<p>{{ item.TTL }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-span-2">
|
|
||||||
<p class="font-semibold">Data</p>
|
|
||||||
<p>{{ item.data }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex space-x-2 mt-4">
|
|
||||||
<span class="bg-green-200 text-green-800 px-2 py-1 rounded">RD: {{ data.RD }}</span>
|
|
||||||
<span class="bg-green-200 text-green-800 px-2 py-1 rounded">RA: {{ data.RA }}</span>
|
|
||||||
<span class="bg-red-200 text-red-800 px-2 py-1 rounded">TC: {{ data.TC }}</span>
|
|
||||||
<span class="bg-red-200 text-red-800 px-2 py-1 rounded">AD: {{ data.AD }}</span>
|
|
||||||
<span class="bg-red-200 text-red-800 px-2 py-1 rounded">CD: {{ data.CD }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
@ -1,43 +0,0 @@
|
|||||||
export const trimDomain = (domain: string): string => {
|
|
||||||
return domain.trim().toLowerCase(); // 确保域名为小写
|
|
||||||
};
|
|
||||||
|
|
||||||
export const splitDomain = (domain: string): string[] => {
|
|
||||||
return domain.split('.');
|
|
||||||
};
|
|
||||||
//
|
|
||||||
// const SupportedTLDs = new Set(Object.keys(domainStore.SupportedTLDs));
|
|
||||||
//
|
|
||||||
// export const updateDomainForTLD = (parts: string[]): string => {
|
|
||||||
// const potentialTLD = parts.slice(-2).join('.').toLowerCase(); // 确保为小写
|
|
||||||
// let domainToKeep: string;
|
|
||||||
// if (SupportedTLDs.has(potentialTLD)) {
|
|
||||||
// domainToKeep = parts.length > 2 ? parts.slice(-3).join('.') : parts.join('.');
|
|
||||||
// } else {
|
|
||||||
// domainToKeep = parts.slice(-2).join('.');
|
|
||||||
// }
|
|
||||||
// return domainToKeep;
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// export const validateDomain = (parts: string[]): boolean => {
|
|
||||||
// if (parts.length < 2) {
|
|
||||||
// const message = useMessage()
|
|
||||||
// message.warning('域名格式不正确')
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
// return true;
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// const isTLDValid = (parts: string[]): boolean => {
|
|
||||||
// const lastPart = parts[parts.length - 1].toLowerCase(); // 获取最后一部分,并确保为小写
|
|
||||||
// const potentialTLD = parts.slice(-2).join('.').toLowerCase(); // 获取可能的多部分TLD,并确保为小写
|
|
||||||
//
|
|
||||||
// if (!SupportedTLDs.has(lastPart) && !SupportedTLDs.has(potentialTLD)) {
|
|
||||||
// const message = useMessage()
|
|
||||||
// message.warning('域名后缀不合法')
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
// return true;
|
|
||||||
// };
|
|
0
.gitignore → frontend/.gitignore
vendored
12
frontend/apis/admin/index.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
enum Api {
|
||||||
|
blogInfo = '/',
|
||||||
|
report = '/report'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取博客信息
|
||||||
|
* @returns 博客信息
|
||||||
|
*/
|
||||||
|
export function getBlogInfo(option: any) {
|
||||||
|
return ""
|
||||||
|
}
|
23
frontend/apis/front/home.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
enum Api {
|
||||||
|
webSiteConfig = '/getWebSiteConfig',
|
||||||
|
whoisServer = "/getWhoisServer"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取网站配置
|
||||||
|
export function GetWebSiteConfig() {
|
||||||
|
return useHttp.post<WebSiteConfig>("/getWebSiteConfig")
|
||||||
|
}
|
||||||
|
|
||||||
|
//获取whois服务器
|
||||||
|
export function GetWhoisServer(config: string) {
|
||||||
|
return useHttp.post<any>("/getWhoisServer", {
|
||||||
|
config: config,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GetWhois(domain: string, name: string) {
|
||||||
|
return useHttp.post<any>("/getWhois", {
|
||||||
|
domain: domain,
|
||||||
|
name: name,
|
||||||
|
})
|
||||||
|
}
|
5
frontend/apis/front/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
// api/front/index.ts
|
||||||
|
import * as home from './home';
|
||||||
|
|
||||||
|
// Re-export home
|
||||||
|
export {home};
|
2
frontend/apis/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * as admin from './admin/index';
|
||||||
|
export * as front from './front/index';
|
@ -11,7 +11,6 @@
|
|||||||
<NuxtLayout>
|
<NuxtLayout>
|
||||||
<NuxtLoadingIndicator/>
|
<NuxtLoadingIndicator/>
|
||||||
<NuxtPage/>
|
<NuxtPage/>
|
||||||
<!-- <CommonLayoutSetting v-if="isAdminRoute" class="fixed right-12 top-1/2 z-999" />-->
|
|
||||||
</NuxtLayout>
|
</NuxtLayout>
|
||||||
</n-message-provider>
|
</n-message-provider>
|
||||||
</n-modal-provider>
|
</n-modal-provider>
|
||||||
@ -19,21 +18,26 @@
|
|||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {darkTheme, lightTheme} from 'naive-ui'
|
import {darkTheme, lightTheme} from 'naive-ui'
|
||||||
import {naiveThemeOverrides} from "~/settings/settings";
|
|
||||||
|
|
||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
const themeOverrides = naiveThemeOverrides
|
|
||||||
|
|
||||||
const theme = computed(() => {
|
const theme = computed(() => {
|
||||||
return colorMode.value === 'system' ? (colorMode.value ? lightTheme : darkTheme) : colorMode.value === 'light' ? lightTheme : darkTheme
|
return colorMode.value === 'system' ? (colorMode.value ? lightTheme : darkTheme) : colorMode.value === 'light' ? lightTheme : darkTheme
|
||||||
})
|
})
|
||||||
|
|
||||||
const whoisStore = useWhoisStore()
|
const styleStore = useStyleStore()
|
||||||
const dnsStore = useDnsStore()
|
const {common} = storeToRefs(styleStore)
|
||||||
const domainStore = useDomainStore()
|
const themeOverrides = computed(() => {
|
||||||
whoisStore.newWhoisList()
|
return {
|
||||||
dnsStore.newDnsList()
|
common: common.value, // 注意这里要使用 common.value
|
||||||
domainStore.newDomainList()
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await callOnce(async () => {
|
||||||
|
await useSettingsStore().webSiteConfigInit()
|
||||||
|
await useConfigStore().configServerInit()
|
||||||
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
|
|
BIN
frontend/assets/images/isme.png
Normal file
After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 8.4 KiB |
BIN
frontend/assets/img_avatar.gif
Normal file
After Width: | Height: | Size: 133 KiB |
82
frontend/assets/styles/global.scss
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
/**********************************
|
||||||
|
* @Author: Ronnie Zhang
|
||||||
|
* @LastEditor: Ronnie Zhang
|
||||||
|
* @LastEditTime: 2023/12/05 21:26:28
|
||||||
|
* @Email: zclzone@outlook.com
|
||||||
|
* Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
|
||||||
|
**********************************/
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* transition fade-slide */
|
||||||
|
.fade-slide-leave-active,
|
||||||
|
.fade-slide-enter-active {
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-slide-enter-from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-30px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-slide-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(30px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 自定义滚动条样式 */
|
||||||
|
.cus-scroll {
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cus-scroll-x {
|
||||||
|
overflow-x: auto;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 0;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cus-scroll-y {
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cus-scroll,
|
||||||
|
.cus-scroll-x,
|
||||||
|
.cus-scroll-y {
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background-color: transparent;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background: #bfbfbf;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--primary-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
49
frontend/assets/styles/reset.css
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
/**********************************
|
||||||
|
* @Author: Ronnie Zhang
|
||||||
|
* @LastEditor: Ronnie Zhang
|
||||||
|
* @LastEditTime: 2023/12/05 21:26:38
|
||||||
|
* @Email: zclzone@outlook.com
|
||||||
|
* Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
|
||||||
|
**********************************/
|
||||||
|
|
||||||
|
html {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
::before,
|
||||||
|
::after {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover,
|
||||||
|
a:link,
|
||||||
|
a:visited,
|
||||||
|
a:active {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol,
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
textarea {
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
img,
|
||||||
|
video {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
@ -1,7 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
||||||
const domainListStore = useDomainListStore();
|
|
||||||
const SupportedTLDs = domainListStore.getSupportedTLDKeys
|
|
||||||
const isOpen = ref(false)
|
const isOpen = ref(false)
|
||||||
const {t} = useI18n()
|
const {t} = useI18n()
|
||||||
|
|
||||||
@ -28,13 +26,13 @@ const {t} = useI18n()
|
|||||||
closable
|
closable
|
||||||
>
|
>
|
||||||
<div class="flex flex-wrap mt-2 ">
|
<div class="flex flex-wrap mt-2 ">
|
||||||
<span
|
<!-- <span-->
|
||||||
v-for="item in SupportedTLDs"
|
<!-- v-for="item in SupportedTLDs"-->
|
||||||
:key="item"
|
<!-- :key="item"-->
|
||||||
class="m-1 px-2 py-1 text-sm font-semibold text-gray-800 bg-gray-200 rounded hover:bg-gray-300"
|
<!-- class="m-1 px-2 py-1 text-sm font-semibold text-gray-800 bg-gray-200 rounded hover:bg-gray-300"-->
|
||||||
>
|
<!-- >-->
|
||||||
{{ item }}
|
<!-- {{ item }}-->
|
||||||
</span>
|
<!-- </span>-->
|
||||||
</div>
|
</div>
|
||||||
</NDrawerContent>
|
</NDrawerContent>
|
||||||
</NDrawer>
|
</NDrawer>
|
@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
||||||
const isOpen = ref(false)
|
const isOpen = ref(false)
|
||||||
const styleStore = useStyleStore()
|
const settingsStore = useSettingsStore()
|
||||||
const {t} = useI18n()
|
const {t} = useI18n()
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
@ -33,12 +33,12 @@ const {t} = useI18n()
|
|||||||
<h1 class="text-2xl font-bold text-gray-800 mb-5 flex items-center justify-between">
|
<h1 class="text-2xl font-bold text-gray-800 mb-5 flex items-center justify-between">
|
||||||
<span
|
<span
|
||||||
class="text-sm text-gray-500 py-1 px-3 rounded-full dark:text-white dark:bg-[#000000FF]">
|
class="text-sm text-gray-500 py-1 px-3 rounded-full dark:text-white dark:bg-[#000000FF]">
|
||||||
{{ t('history.tips', {length: styleStore.getHistory.length}) }}
|
{{ t('history.tips', {length: settingsStore.getHistory.length}) }}
|
||||||
</span>
|
</span>
|
||||||
</h1>
|
</h1>
|
||||||
<div class=" shadow-md rounded-lg ">
|
<div class=" shadow-md rounded-lg ">
|
||||||
<!-- 条件渲染,如果有历史记录则显示表格,否则显示提示 -->
|
<!-- 条件渲染,如果有历史记录则显示表格,否则显示提示 -->
|
||||||
<div v-if="styleStore.getHistory.length">
|
<div v-if="settingsStore.getHistory.length">
|
||||||
<!-- 表格头部和内容 -->
|
<!-- 表格头部和内容 -->
|
||||||
<table class="min-w-full leading-normal ">
|
<table class="min-w-full leading-normal ">
|
||||||
<thead>
|
<thead>
|
||||||
@ -59,7 +59,7 @@ const {t} = useI18n()
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<!-- 这里将使用循环来动态展示查询历史 -->
|
<!-- 这里将使用循环来动态展示查询历史 -->
|
||||||
<tr v-for="item in styleStore.getHistory" :key="item.id"
|
<tr v-for="item in settingsStore.getHistory" :key="item.id"
|
||||||
class="border-b border-gray-200 dark:text-white dark:bg-[#000000FF]">
|
class="border-b border-gray-200 dark:text-white dark:bg-[#000000FF]">
|
||||||
<td class="px-5 py-5 text-sm ">
|
<td class="px-5 py-5 text-sm ">
|
||||||
<NuxtLink :to="item.path">{{ item.domain }}</NuxtLink>
|
<NuxtLink :to="item.path">{{ item.domain }}</NuxtLink>
|
||||||
@ -72,7 +72,7 @@ const {t} = useI18n()
|
|||||||
</td>
|
</td>
|
||||||
<td class="px-5 py-5 text-sm ">
|
<td class="px-5 py-5 text-sm ">
|
||||||
<NButton
|
<NButton
|
||||||
@click="styleStore.deleteHistory(item.id)"
|
@click="settingsStore.deleteHistory(item.id)"
|
||||||
|
|
||||||
>{{ t('common.actions.delete') }}
|
>{{ t('common.actions.delete') }}
|
||||||
</NButton>
|
</NButton>
|
82
frontend/components/common/TimeZonesChange.vue
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
|
||||||
|
const availableTimeZones = ref([
|
||||||
|
{id: 1, name: 'UTC-12', displayName: 'International Date Line West'},
|
||||||
|
{id: 2, name: 'UTC-11', displayName: 'Coordinated Universal Time-11'},
|
||||||
|
{id: 3, name: 'UTC-10', displayName: 'Hawaii'},
|
||||||
|
{id: 4, name: 'UTC-9', displayName: 'Alaska'},
|
||||||
|
{id: 5, name: 'UTC-8', displayName: 'Pacific Time (US & Canada)'},
|
||||||
|
{id: 6, name: 'UTC-7', displayName: 'Mountain Time (US & Canada)'},
|
||||||
|
{id: 7, name: 'UTC-6', displayName: 'Central Time (US & Canada), Mexico City'},
|
||||||
|
{id: 8, name: 'UTC-5', displayName: 'Eastern Time (US & Canada), Bogota, Lima'},
|
||||||
|
{id: 9, name: 'UTC-4', displayName: 'Atlantic Time (Canada), Caracas, La Paz'},
|
||||||
|
{id: 10, name: 'UTC-3', displayName: 'Buenos Aires, Georgetown'},
|
||||||
|
{id: 11, name: 'UTC-2', displayName: 'Coordinated Universal Time-02'},
|
||||||
|
{id: 12, name: 'UTC-1', displayName: 'Azores'},
|
||||||
|
{id: 13, name: 'UTC', displayName: 'Coordinated Universal Time'},
|
||||||
|
{id: 14, name: 'UTC+1', displayName: 'Brussels, Copenhagen, Madrid, Paris'},
|
||||||
|
{id: 15, name: 'UTC+2', displayName: 'Athens, Bucharest, Istanbul'},
|
||||||
|
{id: 16, name: 'UTC+3', displayName: 'Moscow, St. Petersburg, Nairobi'},
|
||||||
|
{id: 17, name: 'UTC+3:30', displayName: 'Tehran'},
|
||||||
|
{id: 18, name: 'UTC+4', displayName: 'Abu Dhabi, Muscat'},
|
||||||
|
{id: 19, name: 'UTC+4:30', displayName: 'Kabul'},
|
||||||
|
{id: 20, name: 'UTC+5', displayName: 'Islamabad, Karachi, Tashkent'},
|
||||||
|
{id: 21, name: 'UTC+5:30', displayName: 'Chennai, Kolkata, Mumbai, New Delhi'},
|
||||||
|
{id: 22, name: 'UTC+5:45', displayName: 'Kathmandu'},
|
||||||
|
{id: 23, name: 'UTC+6', displayName: 'Astana, Dhaka'},
|
||||||
|
{id: 24, name: 'UTC+6:30', displayName: 'Yangon (Rangoon)'},
|
||||||
|
{id: 25, name: 'UTC+7', displayName: 'Bangkok, Hanoi, Jakarta'},
|
||||||
|
{id: 26, name: 'UTC+8', displayName: 'Beijing, Hong Kong, Singapore, Taipei'},
|
||||||
|
{id: 27, name: 'UTC+9', displayName: 'Osaka, Sapporo, Tokyo'},
|
||||||
|
{id: 28, name: 'UTC+9:30', displayName: 'Adelaide, Darwin'},
|
||||||
|
{id: 29, name: 'UTC+10', displayName: 'Brisbane, Canberra, Melbourne, Sydney'},
|
||||||
|
{id: 30, name: 'UTC+11', displayName: 'Solomon Is., New Caledonia'},
|
||||||
|
{id: 31, name: 'UTC+12', displayName: 'Auckland, Wellington'},
|
||||||
|
{id: 32, name: 'UTC+13', displayName: 'Nuku alofa'}
|
||||||
|
]);
|
||||||
|
|
||||||
|
const settingsStore = useSettingsStore()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<HeadlessListbox
|
||||||
|
v-model="settingsStore.timeZones"
|
||||||
|
as="div"
|
||||||
|
class="relative flex items-center"
|
||||||
|
>
|
||||||
|
<HeadlessListboxLabel class="sr-only">
|
||||||
|
Theme
|
||||||
|
</HeadlessListboxLabel>
|
||||||
|
<HeadlessListboxButton type="button" title="Change Color">
|
||||||
|
<div
|
||||||
|
class="flex h-10 w-10 items-center justify-center rounded-lg bg-gray-100 dark:bg-gray-700"
|
||||||
|
>
|
||||||
|
<Icon name="ri:time-zone-line" class=" text-lg dark:text-white"/>
|
||||||
|
</div>
|
||||||
|
</HeadlessListboxButton>
|
||||||
|
<HeadlessListboxOptions
|
||||||
|
class="absolute top-full right-0 z-[999] mt-2 w-40 max-h-60 overflow-y-auto rounded-lg bg-white text-sm font-semibold text-gray-700 shadow-lg shadow-gray-300 outline-none dark:bg-gray-800 dark:text-white dark:shadow-gray-500 dark:ring-0"
|
||||||
|
>
|
||||||
|
<HeadlessListboxOption
|
||||||
|
v-for="item in availableTimeZones"
|
||||||
|
:key="item.id"
|
||||||
|
:value="item.name"
|
||||||
|
class="flex w-full cursor-pointer items-center justify-between py-2 px-3 hover:bg-gray-100 dark:hover:bg-gray-600"
|
||||||
|
:class="{
|
||||||
|
'text-white-500 bg-gray-200 dark:bg-gray-500/50':
|
||||||
|
settingsStore.timeZones === item.name,
|
||||||
|
'hover:bg-gray-200 dark:hover:bg-gray-700/30':
|
||||||
|
settingsStore.timeZones !== item.name,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<span class="truncate">
|
||||||
|
{{ item.name }}
|
||||||
|
</span>
|
||||||
|
<span class="flex items-center justify-center text-sm">
|
||||||
|
</span>
|
||||||
|
</HeadlessListboxOption>
|
||||||
|
</HeadlessListboxOptions>
|
||||||
|
</HeadlessListbox>
|
||||||
|
</div>
|
||||||
|
</template>
|
@ -1,14 +1,12 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
const switchLocalePath = useSwitchLocalePath()
|
const switchLocalePath = useSwitchLocalePath()
|
||||||
const timeStore = useTimeStore()
|
|
||||||
const settingsStore = useSettingsStore()
|
const settingsStore = useSettingsStore()
|
||||||
|
|
||||||
const dnsStore = useDnsStore()
|
|
||||||
|
|
||||||
const {newsDnsArr} = storeToRefs(dnsStore)
|
const configStore = useConfigStore()
|
||||||
|
const {configServer} = storeToRefs(configStore)
|
||||||
|
|
||||||
const handlePost = async (name: string) => {
|
const handlePost = async (name: string) => {
|
||||||
dnsStore.moveToTop(name)
|
|
||||||
//刷新数据
|
//刷新数据
|
||||||
await refreshNuxtData('dns')
|
await refreshNuxtData('dns')
|
||||||
}
|
}
|
||||||
@ -18,7 +16,7 @@ const handlePost = async (name: string) => {
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<HeadlessListbox
|
<HeadlessListbox
|
||||||
v-model="dnsStore.newsDnsArr"
|
v-model="configServer.dnsArr"
|
||||||
as="div"
|
as="div"
|
||||||
class="relative flex items-center"
|
class="relative flex items-center"
|
||||||
>
|
>
|
||||||
@ -37,7 +35,7 @@ const handlePost = async (name: string) => {
|
|||||||
class="absolute top-full right-0 z-[999] mt-2 w-40 overflow-hidden rounded-lg bg-white text-sm font-semibold text-gray-700 shadow-lg shadow-gray-300 outline-none dark:bg-gray-800 dark:text-white dark:shadow-gray-500 dark:ring-0"
|
class="absolute top-full right-0 z-[999] mt-2 w-40 overflow-hidden rounded-lg bg-white text-sm font-semibold text-gray-700 shadow-lg shadow-gray-300 outline-none dark:bg-gray-800 dark:text-white dark:shadow-gray-500 dark:ring-0"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-for="lang in newsDnsArr"
|
v-for="lang in configServer.dnsArr"
|
||||||
:key="lang.name"
|
:key="lang.name"
|
||||||
@click="handlePost(lang.name)"
|
@click="handlePost(lang.name)"
|
||||||
:class="{
|
:class="{
|
43
frontend/components/dns/InfoList.vue
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
defineProps({
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex p-8">
|
||||||
|
<div class="w-full mx-auto">
|
||||||
|
<div class=" shadow-lg rounded-lg p-6 mb-4" v-for="item in data.Answer">
|
||||||
|
<h2 class="text-xl font-bold">{{ item.name }}</h2>
|
||||||
|
<div class="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<p class="font-semibold">Type</p>
|
||||||
|
<p>{{ item.type }}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="font-semibold">TTL</p>
|
||||||
|
<p>{{ item.TTL }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-span-2">
|
||||||
|
<p class="font-semibold">Data</p>
|
||||||
|
<p>{{ item.data }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex space-x-2 mt-4">
|
||||||
|
<span class="bg-green-200 text-green-800 px-2 py-1 rounded">RD: {{ data.RD }}</span>
|
||||||
|
<span class="bg-green-200 text-green-800 px-2 py-1 rounded">RA: {{ data.RA }}</span>
|
||||||
|
<span class="bg-red-200 text-red-800 px-2 py-1 rounded">TC: {{ data.TC }}</span>
|
||||||
|
<span class="bg-red-200 text-red-800 px-2 py-1 rounded">AD: {{ data.AD }}</span>
|
||||||
|
<span class="bg-red-200 text-red-800 px-2 py-1 rounded">CD: {{ data.CD }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const timeStore = useTimeStore()
|
|
||||||
const settingsStore = useSettingsStore()
|
const settingsStore = useSettingsStore()
|
||||||
|
const {getIsDomainList, getIsHistory,} = storeToRefs(settingsStore)
|
||||||
const {t} = useI18n()
|
const {t} = useI18n()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -10,7 +10,7 @@ const {t} = useI18n()
|
|||||||
<div class="flex space-x-2">
|
<div class="flex space-x-2">
|
||||||
<!-- 左边的新元素 -->
|
<!-- 左边的新元素 -->
|
||||||
<n-tooltip
|
<n-tooltip
|
||||||
v-if="settingsStore.isDomainList"
|
v-if="getIsDomainList"
|
||||||
trigger="hover" placement="top">
|
trigger="hover" placement="top">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<CommonDomainList/>
|
<CommonDomainList/>
|
||||||
@ -19,7 +19,7 @@ const {t} = useI18n()
|
|||||||
</n-tooltip>
|
</n-tooltip>
|
||||||
|
|
||||||
<n-tooltip
|
<n-tooltip
|
||||||
v-if="settingsStore.getHistory"
|
v-if="getIsHistory"
|
||||||
trigger="hover" placement="top">
|
trigger="hover" placement="top">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<CommonHistory/>
|
<CommonHistory/>
|
||||||
@ -27,15 +27,6 @@ const {t} = useI18n()
|
|||||||
<span>{{ t('popper.history') }}</span>
|
<span>{{ t('popper.history') }}</span>
|
||||||
</n-tooltip>
|
</n-tooltip>
|
||||||
|
|
||||||
<!-- <n-tooltip-->
|
|
||||||
<!-- v-if="settingsStore.getHistory"-->
|
|
||||||
<!-- trigger="hover" placement="top" >-->
|
|
||||||
<!-- <template #trigger>-->
|
|
||||||
<!-- <CommonDnsList @action="handleActionFromDnsList" />-->
|
|
||||||
<!-- </template>-->
|
|
||||||
<!-- <span>{{t('popper.dns')}}</span>-->
|
|
||||||
<!-- </n-tooltip>-->
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex space-x-2">
|
<div class="flex space-x-2">
|
||||||
<!-- 右边的现有元素 -->
|
<!-- 右边的现有元素 -->
|
||||||
@ -63,7 +54,7 @@ const {t} = useI18n()
|
|||||||
<template #trigger>
|
<template #trigger>
|
||||||
<CommonTimeZonesChange/>
|
<CommonTimeZonesChange/>
|
||||||
</template>
|
</template>
|
||||||
<span>{{ timeStore.timeZones }}</span>
|
<span>{{ settingsStore.timeZones }}</span>
|
||||||
</n-tooltip>
|
</n-tooltip>
|
||||||
|
|
||||||
<n-tooltip
|
<n-tooltip
|
13
frontend/components/whois/Nuxt.vue
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const props = defineProps({
|
||||||
|
data: Object,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
{{ data }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
13
frontend/components/whois/TianHu.vue
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const props = defineProps({
|
||||||
|
data: Object,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
{{ data }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
12
frontend/composables/useHttp.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// 自动导出
|
||||||
|
export const useHttp = {
|
||||||
|
post<T = any>(url: string, config?: any): Promise<T> {
|
||||||
|
const runtimeConfig = useRuntimeConfig()
|
||||||
|
const baseUrl = runtimeConfig.public.baseUrl
|
||||||
|
return $fetch(url, {
|
||||||
|
baseURL: baseUrl,
|
||||||
|
method: 'POST',
|
||||||
|
body: config,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
Before Width: | Height: | Size: 134 KiB After Width: | Height: | Size: 134 KiB |
Before Width: | Height: | Size: 390 KiB After Width: | Height: | Size: 390 KiB |
112
frontend/layouts/admin/index.vue
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import {type MenuOption, NIcon} from "naive-ui";
|
||||||
|
import type {Component} from "vue";
|
||||||
|
import {Settings, Wrench, Pentagon, MessageCircleMore} from 'lucide-vue-next';
|
||||||
|
|
||||||
|
function renderIcon(icon: Component) {
|
||||||
|
return () => h(NIcon, null, {default: () => h(icon)})
|
||||||
|
}
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const menusOptions: MenuOption[] = [
|
||||||
|
{
|
||||||
|
label: '控制台',
|
||||||
|
key: '/admin/dashboard',
|
||||||
|
icon: renderIcon(Pentagon)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Whois管理',
|
||||||
|
key: 'whois',
|
||||||
|
icon: renderIcon(Settings),
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
label: '后缀管理',
|
||||||
|
key: '/admin/whois/website',
|
||||||
|
icon: renderIcon(Wrench),
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Dns管理',
|
||||||
|
key: 'dns',
|
||||||
|
icon: renderIcon(Settings),
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
label: '后缀管理',
|
||||||
|
key: '/admin/whois/website',
|
||||||
|
icon: renderIcon(Wrench),
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Domain管理',
|
||||||
|
key: 'domain',
|
||||||
|
icon: renderIcon(Settings),
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
label: '后缀管理',
|
||||||
|
key: '/admin/whois/website',
|
||||||
|
icon: renderIcon(Wrench),
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '系统管理',
|
||||||
|
key: 'settings',
|
||||||
|
icon: renderIcon(Settings),
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
label: '网站设置',
|
||||||
|
key: '/admin/settings/website',
|
||||||
|
icon: renderIcon(Wrench),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '公告管理',
|
||||||
|
key: '/admin/settings/bulletin',
|
||||||
|
icon: renderIcon(MessageCircleMore),
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const activeKey = computed(() => route.path)
|
||||||
|
console.log(route.path)
|
||||||
|
|
||||||
|
function handleMenuSelect(key: any, item: any) {
|
||||||
|
if (isExternal(item.key)) {
|
||||||
|
router.push(item.key)
|
||||||
|
} else {
|
||||||
|
router.push(item.key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex h-screen bg-gray-100">
|
||||||
|
<!-- Left Sidebar -->
|
||||||
|
<div class="w-64 text-black">
|
||||||
|
<n-menu
|
||||||
|
ref="menu"
|
||||||
|
class="side-menu"
|
||||||
|
accordion
|
||||||
|
:indent="18"
|
||||||
|
:collapsed-icon-size="22"
|
||||||
|
:collapsed-width="64"
|
||||||
|
:options="menusOptions"
|
||||||
|
:value="activeKey"
|
||||||
|
@update:value="handleMenuSelect"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Right Content -->
|
||||||
|
<div class="flex-1 p-10">
|
||||||
|
<slot/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
132
frontend/layouts/default.vue
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter();
|
||||||
|
const {t} = useI18n()
|
||||||
|
const localePath = useLocalePath()
|
||||||
|
const message = useMessage()
|
||||||
|
const settingsStore = useSettingsStore()
|
||||||
|
const {isObj, domainSearch, selectedOption, webSiteConfig} = storeToRefs(settingsStore)
|
||||||
|
|
||||||
|
//设置不同页面的样式
|
||||||
|
const stylePage = computed(() => {
|
||||||
|
return route.meta?.stylePage
|
||||||
|
})
|
||||||
|
|
||||||
|
// 提取主域名的函数,直接返回提取结果,不尝试猜测二级顶级域名
|
||||||
|
const handleAction = async (url) => {
|
||||||
|
const etDomainSearch = ExtractDomain(domainSearch.value);
|
||||||
|
switch (url) {
|
||||||
|
case 'whois':
|
||||||
|
// 使用正则表达式校验提取的主域名
|
||||||
|
if (DomainRegex.test(etDomainSearch)) {
|
||||||
|
// 将正确数据进行记录
|
||||||
|
domainSearch.value = etDomainSearch;
|
||||||
|
// 参数校验成功,进行跳转
|
||||||
|
const urlType = etDomainSearch.replace(/\./g, '_');
|
||||||
|
await router.push(localePath(`/whois/${urlType}.html`));
|
||||||
|
} else if (Ipv4Regex.test(domainSearch.value)) {
|
||||||
|
// 处理用户操作的异步函数
|
||||||
|
const urlType = domainSearch.value.replace(/\./g, '_');
|
||||||
|
await router.push(localePath(`/whois/${urlType}.html`));
|
||||||
|
} else {
|
||||||
|
message.error('请输入有效的域名或IP地址')
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'dns':
|
||||||
|
// 使用正则表达式校验提取的主域名
|
||||||
|
if (DomainRegex.test(etDomainSearch)) {
|
||||||
|
// 将正确数据进行记录
|
||||||
|
domainSearch.value = etDomainSearch;
|
||||||
|
// 参数校验成功,进行跳转
|
||||||
|
const urlType = etDomainSearch.replace(/\./g, '_');
|
||||||
|
await router.push(localePath(`/dns/${urlType}.html`));
|
||||||
|
} else if (Ipv4Regex.test(domainSearch.value)) {
|
||||||
|
// 处理用户操作的异步函数
|
||||||
|
const urlType = domainSearch.value.replace(/\./g, '_');
|
||||||
|
await router.push(localePath(`/dns/${urlType}.html`));
|
||||||
|
} else {
|
||||||
|
message.error('请输入有效的域名或IP地址')
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const handleSelectOptions = (value: any) => {
|
||||||
|
settingsStore.setSelectedOption(value);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="w-full text-xs bg-[#F1F3F4] dark:bg-transparent"
|
||||||
|
:class="{ 'h-[90vh]': !stylePage }"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class=" max-w-screen-lg mx-auto px-[1em] pb-[10vh] "
|
||||||
|
:class="{ 'pt-[25vh]': !stylePage, 'pt-[5vh]': stylePage }"
|
||||||
|
>
|
||||||
|
<nav
|
||||||
|
v-if="isObj.isLogo"
|
||||||
|
class=" w-full text-[#464747] h-5 ">
|
||||||
|
<NuxtLink class="mb-3 font-bold text-2xl inline-block text-current no-underline dark:text-white"
|
||||||
|
:to="localePath('/')"
|
||||||
|
>
|
||||||
|
<h1 class="inline-block text-current no-underline dark:text-white">{{ webSiteConfig.logoLeftText }}</h1>
|
||||||
|
<sup class="text-[#59a8d7] dark:text-[#ace4f8]">{{ webSiteConfig.logoRightText }}</sup>
|
||||||
|
</NuxtLink>
|
||||||
|
</nav>
|
||||||
|
<div class="mt-6">
|
||||||
|
<ClientOnly>
|
||||||
|
<div class="flex items-center space-x-2 mb-3 dark:text-white"
|
||||||
|
>
|
||||||
|
<!-- 容器div用于水平布局 -->
|
||||||
|
<div class="flex-grow">
|
||||||
|
<NInputGroup>
|
||||||
|
<n-select
|
||||||
|
class="w-1/4"
|
||||||
|
size="large"
|
||||||
|
v-model:value="selectedOption"
|
||||||
|
:options="webSiteConfig.defaultSelectOptions"
|
||||||
|
@update:value="handleSelectOptions"
|
||||||
|
/>
|
||||||
|
<NAutoComplete
|
||||||
|
v-model:value="domainSearch"
|
||||||
|
@keyup.enter="handleAction(settingsStore.selectedOption)"
|
||||||
|
:input-props="{ autocomplete: 'off' }"
|
||||||
|
type="text"
|
||||||
|
:placeholder="t('index.placeholder')"
|
||||||
|
size="large"
|
||||||
|
clearable
|
||||||
|
autofocus
|
||||||
|
class="w-full ">
|
||||||
|
</NAutoComplete>
|
||||||
|
</NInputGroup>
|
||||||
|
</div>
|
||||||
|
<!-- 使用v-if基于state.domain的值来控制按钮的显示 -->
|
||||||
|
<NButton type="primary"
|
||||||
|
size="large"
|
||||||
|
@click="handleAction(settingsStore.selectedOption)"
|
||||||
|
v-if="settingsStore.domainSearch">
|
||||||
|
{{ t('index.onSubmit') }}
|
||||||
|
</NButton>
|
||||||
|
</div>
|
||||||
|
</ClientOnly>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CommonBulletin
|
||||||
|
v-if="!stylePage && isObj.isBulletin"
|
||||||
|
:text="`➡️ ${t('index.tips') }`"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TabList @action="handleAction"/>
|
||||||
|
<slot/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<CommonFooter/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
@ -12,29 +12,39 @@ export default defineNuxtConfig({
|
|||||||
'nuxt-simple-robots',
|
'nuxt-simple-robots',
|
||||||
'nuxt-headlessui', // 组件库
|
'nuxt-headlessui', // 组件库
|
||||||
'@bg-dev/nuxt-naiveui', // 组件库
|
'@bg-dev/nuxt-naiveui', // 组件库
|
||||||
'@nuxtjs/tailwindcss', // 组件库
|
|
||||||
'nuxt-icon',
|
'nuxt-icon',
|
||||||
'@nuxtjs/color-mode',
|
'@nuxtjs/color-mode',
|
||||||
|
'@nuxtjs/tailwindcss',
|
||||||
],
|
],
|
||||||
|
css: ['~/assets/css/main.css'],
|
||||||
|
postcss: {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
experimental: {
|
||||||
|
appManifest: false,
|
||||||
|
},
|
||||||
features: {
|
features: {
|
||||||
inlineStyles: true,
|
inlineStyles: true,
|
||||||
},
|
},
|
||||||
runtimeConfig: {
|
runtimeConfig: {
|
||||||
public: {
|
public: {
|
||||||
Domain: process.env.WebSiteDomain || 'Nuxt Whois',
|
baseUrl: process.env.BASE_URL
|
||||||
DomainSuffix: process.env.WebSiteDomainSuffix || 'Dns',
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
app: {
|
app: {
|
||||||
head: {
|
head: {
|
||||||
title: process.env.WebSiteTitle || 'Nuxt Whois',
|
title: 'Nuxt Whois',
|
||||||
meta: [
|
meta: [
|
||||||
{charset: 'utf-8'},
|
{charset: 'utf-8'},
|
||||||
{name: 'viewport', content: 'width=device-width, initial-scale=1'},
|
{name: 'viewport', content: 'width=device-width, initial-scale=1'},
|
||||||
],
|
],
|
||||||
link: [
|
link: [
|
||||||
{rel: 'icon', type: 'image/x-icon', href: '/favicon.ico'}
|
{rel: 'icon', type: 'image/x-icon', href: '/favicon.ico'}
|
||||||
]
|
],
|
||||||
|
// script: [{src: "/darkModelVerify.js"}],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
i18n: {
|
i18n: {
|
||||||
@ -57,12 +67,4 @@ export default defineNuxtConfig({
|
|||||||
colorMode: {
|
colorMode: {
|
||||||
classSuffix: '',
|
classSuffix: '',
|
||||||
},
|
},
|
||||||
tailwindcss: {
|
|
||||||
cssPath: '~/assets/css/tailwind.css',
|
|
||||||
exposeConfig: {
|
|
||||||
write: true,
|
|
||||||
|
|
||||||
},
|
|
||||||
viewer: true,
|
|
||||||
}
|
|
||||||
})
|
})
|
@ -10,26 +10,28 @@
|
|||||||
"postinstall": "nuxt prepare"
|
"postinstall": "nuxt prepare"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxt/ui": "^2.14.2",
|
|
||||||
"@pinia/nuxt": "^0.5.1",
|
"@pinia/nuxt": "^0.5.1",
|
||||||
"lucide-vue-next": "^0.363.0",
|
"lucide-vue-next": "^0.365.0",
|
||||||
"nuxt": "^3.11.1",
|
"nuxt": "^3.11.2",
|
||||||
"socks": "^2.8.1",
|
"socks": "^2.8.1",
|
||||||
"vue": "^3.4.21",
|
"vue": "^3.4.21",
|
||||||
"vue-router": "^4.3.0",
|
"vue-router": "^4.3.0",
|
||||||
"vuedraggable": "^4.1.0"
|
"vuedraggable": "^4.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@bg-dev/nuxt-naiveui": "^1.10.4",
|
"@bg-dev/nuxt-naiveui": "^1.12.2",
|
||||||
"@nuxtjs/color-mode": "^3.3.3",
|
"@nuxtjs/color-mode": "^3.3.3",
|
||||||
"@nuxtjs/i18n": "^8.2.0",
|
"@nuxtjs/i18n": "^8.3.0",
|
||||||
"@nuxtjs/tailwindcss": "^6.11.4",
|
"@nuxtjs/tailwindcss": "^6.11.4",
|
||||||
"@pinia-plugin-persistedstate/nuxt": "^1.2.0",
|
"@pinia-plugin-persistedstate/nuxt": "^1.2.0",
|
||||||
|
"autoprefixer": "^10.4.19",
|
||||||
"less": "^4.2.0",
|
"less": "^4.2.0",
|
||||||
"nuxt-headlessui": "^1.1.5",
|
"nuxt-headlessui": "^1.2.0",
|
||||||
"nuxt-icon": "^0.6.10",
|
"nuxt-icon": "^0.6.10",
|
||||||
"nuxt-simple-robots": "4.0.0-rc.16",
|
"nuxt-simple-robots": "4.0.0-rc.16",
|
||||||
"sass": "^1.72.0",
|
"postcss": "^8.4.38",
|
||||||
"typescript": "5.4.3"
|
"sass": "^1.74.1",
|
||||||
|
"tailwindcss": "^3.4.3",
|
||||||
|
"typescript": "5.4.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
15
frontend/pages/admin/dashboard/index.vue
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
definePageMeta({
|
||||||
|
layout: "admin",
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
123
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|