kenaito-dns/controller/resolve_record.go

396 lines
13 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package controller
/*
* @Description Web控制层
* @Author www.odboy.cn
* @Date 20241108
*/
import (
"fmt"
"github.com/gin-gonic/gin"
"kenaito-dns/cache"
"kenaito-dns/constant"
"kenaito-dns/dao"
"kenaito-dns/domain"
"kenaito-dns/util"
"net/http"
"strings"
)
func InitRestFunc(r *gin.Engine) {
// 健康检查
r.GET("/health", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "success",
"data": "pong",
})
})
// 测试解析状态
r.POST("/test", func(c *gin.Context) {
var jsonObj domain.TestArgs
err := c.ShouldBindJSON(&jsonObj)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"message": fmt.Sprintf("校验失败, %v", err)})
return
}
name := jsonObj.Name
valid := util.IsValidDomain(name)
if !valid {
c.JSON(http.StatusBadRequest, gin.H{"message": "域名解析失败"})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "域名解析成功",
})
})
// 启停记录
r.POST("/switch", func(c *gin.Context) {
var jsonObj domain.SwitchArgs
err := c.ShouldBindJSON(&jsonObj)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"message": fmt.Sprintf("校验失败, %v", err)})
return
}
_, err = dao.SwitchResolveRecord(jsonObj.Id, jsonObj.Enabled)
if err != nil {
if jsonObj.Enabled == 1 {
c.JSON(http.StatusBadRequest, gin.H{"message": fmt.Sprintf("启用失败, %v", err)})
} else {
c.JSON(http.StatusBadRequest, gin.H{"message": fmt.Sprintf("停用失败, %v", err)})
}
return
}
cache.ReloadCache()
if jsonObj.Enabled == 1 {
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "启用成功",
})
} else {
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "停用成功",
})
}
})
// 创建RR记录
r.POST("/create", func(c *gin.Context) {
var jsonObj domain.CreateResolveRecord
err := c.ShouldBindJSON(&jsonObj)
newRecord, isErr := validRequestBody(c, err, jsonObj.Name, jsonObj.Type, jsonObj.Ttl, jsonObj.Value, false)
if isErr {
return
}
if dao.IsResolveRecordExist(newRecord) {
c.JSON(http.StatusBadRequest, gin.H{
"message": "记录 " + newRecord.Name + " " + newRecord.RecordType + " " + newRecord.Value + " 已存在",
})
return
}
newRecord.Ttl = jsonObj.Ttl
executeResult, err, oldVersion, newVersion := dao.BackupResolveRecord(newRecord)
if !executeResult {
c.JSON(http.StatusBadRequest, gin.H{"message": fmt.Sprintf("备份"+newRecord.RecordType+"记录失败, %v", err)})
return
}
executeResult, _ = dao.SaveResolveRecord(newRecord)
if !executeResult {
c.JSON(http.StatusBadRequest, gin.H{"message": fmt.Sprintf("添加"+newRecord.RecordType+"记录失败, %v", err)})
return
}
cache.ReloadCache()
body := make(map[string]interface{})
body["oldVersion"] = oldVersion
body["newVersion"] = newVersion
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "添加" + newRecord.RecordType + "记录成功",
"data": body,
})
return
})
// 删除RR记录
r.POST("/remove", func(c *gin.Context) {
var jsonObj domain.RemoveResolveRecord
err := c.ShouldBindJSON(&jsonObj)
newRecord, isErr := validRequestBody(c, err, jsonObj.Name, jsonObj.Type, 0, jsonObj.Value, true)
if isErr {
return
}
if !dao.IsResolveRecordExist(newRecord) {
c.JSON(http.StatusBadRequest, gin.H{"message": "记录 " + newRecord.Name + " " + newRecord.RecordType + " " + newRecord.Value + " 不存在"})
return
}
executeResult, err, oldVersion, newVersion := dao.BackupResolveRecord(newRecord)
if !executeResult {
c.JSON(http.StatusBadRequest, gin.H{"message": fmt.Sprintf("备份"+newRecord.RecordType+"记录失败, %v", err)})
return
}
executeResult, err = dao.RemoveResolveRecord(newRecord)
if !executeResult {
c.JSON(http.StatusBadRequest, gin.H{"message": fmt.Sprintf("删除"+newRecord.RecordType+"记录失败, %v", err)})
return
}
cache.ReloadCache()
body := make(map[string]interface{})
body["oldVersion"] = oldVersion
body["newVersion"] = newVersion
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "删除" + newRecord.RecordType + "记录成功",
"data": body,
})
return
})
// 修改RR记录
r.POST("/modify", func(c *gin.Context) {
var jsonObj domain.ModifyResolveRecord
err := c.ShouldBindJSON(&jsonObj)
newRecord, isErr := validModifyRequestBody(c, err, jsonObj.Name, jsonObj.Type, jsonObj.Ttl, jsonObj.Value)
if isErr {
return
}
if jsonObj.Id == 0 {
c.JSON(http.StatusBadRequest, gin.H{"message": "参数ID(id)必填"})
return
}
if !dao.IsResolveRecordExistById(jsonObj.Id) {
c.JSON(http.StatusBadRequest, gin.H{"message": fmt.Sprintf("记录 id=%d 不存在", jsonObj.Id)})
return
}
if dao.IsUpdResolveRecordExist(jsonObj.Id, newRecord) {
c.JSON(http.StatusBadRequest, gin.H{"message": "记录 " + jsonObj.Name + " " + jsonObj.Type + " " + jsonObj.Value + " 已存在"})
return
}
localOldRecord := dao.FindResolveRecordById(jsonObj.Id)
executeResult, err, oldVersion, newVersion := dao.BackupResolveRecord(newRecord)
if !executeResult {
c.JSON(http.StatusBadRequest, gin.H{"message": fmt.Sprintf("备份"+newRecord.RecordType+"记录失败, %v", err)})
return
}
localNewRecord := dao.FindOneResolveRecord(localOldRecord, newVersion)
if localNewRecord == nil || localNewRecord.Id == 0 {
c.JSON(http.StatusBadRequest, gin.H{"message": fmt.Sprintf("查询待更新" + newRecord.RecordType + "记录失败")})
return
}
if localNewRecord.Ttl == 0 {
localNewRecord.Ttl = 10
}
updRecord := new(dao.ResolveRecord)
updRecord.Name = newRecord.Name
updRecord.RecordType = newRecord.RecordType
updRecord.Ttl = newRecord.Ttl
updRecord.Value = newRecord.Value
updRecord.CreateTime = localNewRecord.CreateTime
executeResult, err = dao.ModifyResolveRecordById(localNewRecord.Id, updRecord)
if !executeResult {
c.JSON(http.StatusBadRequest, gin.H{"message": fmt.Sprintf("更新"+newRecord.RecordType+"记录失败, %v", err)})
return
}
cache.ReloadCache()
body := make(map[string]interface{})
body["oldVersion"] = oldVersion
body["newVersion"] = newVersion
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "更新" + newRecord.RecordType + "记录成功",
"data": body,
})
return
})
// 分页查询RR记录
r.POST("/queryPage", func(c *gin.Context) {
var jsonObj domain.QueryPageArgs
err := c.ShouldBindJSON(&jsonObj)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"message": fmt.Sprintf("校验失败, %v", err)})
return
}
records := dao.FindResolveRecordPage(jsonObj.Page, jsonObj.PageSize, &jsonObj)
count := dao.CountResolveRecordPage(jsonObj.Page, jsonObj.PageSize, &jsonObj)
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "分页查询RR记录成功",
"data": records,
"count": count,
})
return
})
// 根据id查询RR记录明细
r.POST("/queryById", func(c *gin.Context) {
var jsonObj domain.QueryByIdArgs
err := c.ShouldBindJSON(&jsonObj)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"message": fmt.Sprintf("校验失败, %v", err)})
return
}
records := dao.FindResolveRecordById(jsonObj.Id)
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "根据id查询RR记录明细成功",
"data": records,
})
return
})
// 分页查询变更历史记录
r.POST("/queryVersionPage", func(c *gin.Context) {
var jsonObj domain.QueryPageArgs
err := c.ShouldBindJSON(&jsonObj)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"message": fmt.Sprintf("校验失败, %v", err)})
return
}
records := dao.FindResolveVersionPage(jsonObj.Page, jsonObj.PageSize)
count := dao.CountResolveVersionPage(jsonObj.Page, jsonObj.PageSize)
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "分页查询变更历史记录成功",
"data": records,
"count": count,
})
return
})
// 回滚到某一版本
r.POST("/rollback", func(c *gin.Context) {
var jsonObj domain.RollbackVersionArgs
err := c.ShouldBindJSON(&jsonObj)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"message": fmt.Sprintf("校验失败, %v", err)})
return
}
resolveVersion := dao.FindResolveVersionByVersion(jsonObj.Version)
if resolveVersion == nil {
c.JSON(http.StatusBadRequest, gin.H{"message": fmt.Sprintf("版本号 %d 不存在, 回滚失败", jsonObj.Version)})
return
}
err = dao.ModifyResolveVersion(jsonObj.Version)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"message": fmt.Sprintf("回滚失败, %v", err)})
return
}
cache.ReloadCache()
body := make(map[string]interface{})
body["currentVersion"] = jsonObj.Version
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "回滚成功",
"data": body,
})
return
})
}
func validRequestBody(c *gin.Context, err error, name string, recordType string, ttl int, value string, isDelete bool) (*dao.ResolveRecord, bool) {
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"message": fmt.Sprintf("校验失败, %v", err)})
return nil, true
}
if util.IsBlank(name) {
c.JSON(http.StatusBadRequest, gin.H{"message": "参数主机记录(name)必填, 例如: www.odboy.cn 或 odboy.cn"})
return nil, true
}
if util.IsBlank(recordType) {
c.JSON(http.StatusBadRequest, gin.H{"message": "参数记录类型(type)必填, 目前支持 A、AAAA 记录"})
return nil, true
}
if util.IsBlank(value) {
c.JSON(http.StatusBadRequest, gin.H{"message": "参数记录值(value)必填"})
return nil, true
}
if !isDelete {
if ttl < 10 {
c.JSON(http.StatusBadRequest, gin.H{"message": "参数缓存有效时间(ttl)有误必须大于等于10"})
return nil, true
}
}
if !util.IsValidName(name) {
c.JSON(http.StatusBadRequest, gin.H{"message": "参数主机记录(name)有误,无效的主机记录"})
return nil, true
}
switch recordType {
case constant.R_A:
if !util.IsIPv4(value) {
c.JSON(http.StatusBadRequest, gin.H{"message": "参数记录值(value)有误无效的IPv4地址"})
return nil, true
}
case constant.R_AAAA:
if !util.IsIPv6(value) {
c.JSON(http.StatusBadRequest, gin.H{"message": "参数记录值(value)有误无效的IPv6地址"})
return nil, true
}
case constant.R_CNAME:
if !util.IsValidDomain(value) {
c.JSON(http.StatusBadRequest, gin.H{"message": "参数记录值(value)有误,无效的主机记录"})
return nil, true
}
case constant.R_MX:
if !util.IsValidDomain(value) {
c.JSON(http.StatusBadRequest, gin.H{"message": "参数记录值(value)有误,无效的主机记录"})
return nil, true
}
case constant.R_TXT:
if len(value) > 512 {
c.JSON(http.StatusBadRequest, gin.H{"message": "参数记录值(value)有误,长度必须 <= 512"})
return nil, true
}
default:
c.JSON(http.StatusBadRequest, gin.H{"message": "参数记录值(type)有误,不支持的记录类型: " + recordType})
return nil, true
}
newRecord := new(dao.ResolveRecord)
newRecord.Name = strings.TrimSpace(name)
newRecord.RecordType = strings.TrimSpace(recordType)
newRecord.Value = strings.TrimSpace(value)
return newRecord, false
}
func validModifyRequestBody(c *gin.Context, err error, name string, recordType string, ttl int, value string) (*dao.ResolveRecord, bool) {
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"message": fmt.Sprintf("校验失败, %v", err)})
return nil, true
}
if ttl != 0 && ttl < 10 {
c.JSON(http.StatusBadRequest, gin.H{"message": "参数缓存有效时间(ttl)有误必须大于等于10"})
return nil, true
}
if !util.IsValidName(name) {
c.JSON(http.StatusBadRequest, gin.H{"message": "参数主机记录(name)有误,无效的主机记录"})
return nil, true
}
switch recordType {
case constant.R_A:
if !util.IsIPv4(value) {
c.JSON(http.StatusBadRequest, gin.H{"message": "参数记录值(value)有误无效的IPv4地址"})
return nil, true
}
case constant.R_AAAA:
if !util.IsIPv6(value) {
c.JSON(http.StatusBadRequest, gin.H{"message": "参数记录值(value)有误无效的IPv6地址"})
return nil, true
}
case constant.R_CNAME:
if !util.IsValidDomain(value) {
c.JSON(http.StatusBadRequest, gin.H{"message": "参数记录值(value)有误,无效的主机记录"})
return nil, true
}
case constant.R_MX:
if !util.IsValidDomain(value) {
c.JSON(http.StatusBadRequest, gin.H{"message": "参数记录值(value)有误,无效的主机记录"})
return nil, true
}
case constant.R_TXT:
if len(value) > 512 {
c.JSON(http.StatusBadRequest, gin.H{"message": "参数记录值(value)有误,长度必须 <= 512"})
return nil, true
}
default:
c.JSON(http.StatusBadRequest, gin.H{"message": "参数记录值(type)有误,不支持的记录类型: " + recordType})
return nil, true
}
newRecord := new(dao.ResolveRecord)
newRecord.Name = strings.TrimSpace(name)
newRecord.RecordType = strings.TrimSpace(recordType)
newRecord.Value = strings.TrimSpace(value)
return newRecord, false
}