初始提交:OCI 面板后端(含 GenAI 网关一期)

This commit is contained in:
Wang Defa
2026-07-09 15:31:04 +08:00
commit b9a3e97e84
168 changed files with 31794 additions and 0 deletions
+263
View File
@@ -0,0 +1,263 @@
package api
import (
"net/http"
"github.com/gin-gonic/gin"
"oci-portal/internal/oci"
)
func (h *ociConfigHandler) availabilityDomains(c *gin.Context) {
id, ok := pathID(c)
if !ok {
return
}
ads, err := h.svc.AvailabilityDomains(c.Request.Context(), id, c.Query("region"))
if err != nil {
respondError(c, err)
return
}
c.JSON(http.StatusOK, ads)
}
// ---- 实例 ----
type createInstanceRequest struct {
Region string `json:"region"`
CompartmentID string `json:"compartmentId"` // 为空时建在租户根
AvailabilityDomain string `json:"availabilityDomain"` // 为空时默认使用第一个可用域
DisplayName string `json:"displayName" binding:"required"`
Shape string `json:"shape" binding:"required"`
Count int `json:"count"` // 批量创建数量,缺省 1,上限 10
Ocpus float32 `json:"ocpus"`
MemoryInGBs float32 `json:"memoryInGBs"`
ImageID string `json:"imageId"`
BootVolumeID string `json:"bootVolumeId"`
BootVolumeSizeGBs int64 `json:"bootVolumeSizeGBs"`
BootVolumeVpusPerGB int64 `json:"bootVolumeVpusPerGB"`
SubnetID string `json:"subnetId"` // 为空时自动创建 VCN 与子网
AssignPublicIP bool `json:"assignPublicIp"`
AssignIpv6 bool `json:"assignIpv6"`
SSHPublicKey string `json:"sshPublicKey"`
RootPassword string `json:"rootPassword"`
GenerateRootPassword bool `json:"generateRootPassword"` // 服务端生成随机 root 密码并写入 TAG RootPassword
UserData string `json:"userData"`
}
type updateInstanceRequest struct {
Region string `json:"region"`
DisplayName string `json:"displayName"`
Shape string `json:"shape"` // 更换 shape,运行中实例会自动重启
Ocpus float32 `json:"ocpus"`
MemoryInGBs float32 `json:"memoryInGBs"`
}
type instanceActionRequest struct {
Region string `json:"region"`
Action string `json:"action" binding:"required"`
}
func (h *ociConfigHandler) listInstances(c *gin.Context) {
id, ok := pathID(c)
if !ok {
return
}
instances, err := h.svc.Instances(c.Request.Context(), id, c.Query("region"), c.Query("compartmentId"))
if err != nil {
respondError(c, err)
return
}
c.JSON(http.StatusOK, instances)
}
func (h *ociConfigHandler) createInstance(c *gin.Context) {
id, ok := pathID(c)
if !ok {
return
}
var req createInstanceRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
count := req.Count
if count == 0 {
count = 1
}
instances, failures, err := h.svc.CreateInstances(c.Request.Context(), id, oci.CreateInstanceInput{
Region: req.Region,
CompartmentID: req.CompartmentID,
AvailabilityDomain: req.AvailabilityDomain,
DisplayName: req.DisplayName,
Shape: req.Shape,
Ocpus: req.Ocpus,
MemoryInGBs: req.MemoryInGBs,
ImageID: req.ImageID,
BootVolumeID: req.BootVolumeID,
BootVolumeSizeGBs: req.BootVolumeSizeGBs,
BootVolumeVpusPerGB: req.BootVolumeVpusPerGB,
SubnetID: req.SubnetID,
AssignPublicIP: req.AssignPublicIP,
AssignIpv6: req.AssignIpv6,
SSHPublicKey: req.SSHPublicKey,
RootPassword: req.RootPassword,
GenerateRootPassword: req.GenerateRootPassword,
UserData: req.UserData,
}, count)
if err != nil {
respondError(c, err)
return
}
// 全部失败视为执行失败,部分成功仍返回 201 并附带逐台失败信息
status := http.StatusCreated
if len(instances) == 0 && len(failures) > 0 {
status = http.StatusInternalServerError
}
// nil 切片会序列化为 null,显式空数组免去前端判空
if instances == nil {
instances = []oci.Instance{}
}
if failures == nil {
failures = []string{}
}
c.JSON(status, gin.H{"instances": instances, "errors": failures})
}
func (h *ociConfigHandler) getInstance(c *gin.Context) {
id, ok := pathID(c)
if !ok {
return
}
instance, err := h.svc.Instance(c.Request.Context(), id, c.Query("region"), c.Param("instanceId"))
if err != nil {
respondError(c, err)
return
}
c.JSON(http.StatusOK, instance)
}
func (h *ociConfigHandler) updateInstance(c *gin.Context) {
id, ok := pathID(c)
if !ok {
return
}
var req updateInstanceRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
instance, err := h.svc.UpdateInstance(c.Request.Context(), id, c.Param("instanceId"), oci.UpdateInstanceInput{
Region: req.Region,
DisplayName: req.DisplayName,
Shape: req.Shape,
Ocpus: req.Ocpus,
MemoryInGBs: req.MemoryInGBs,
})
if err != nil {
respondError(c, err)
return
}
c.JSON(http.StatusOK, instance)
}
func (h *ociConfigHandler) terminateInstance(c *gin.Context) {
id, ok := pathID(c)
if !ok {
return
}
preserve := c.Query("preserveBootVolume") == "true"
if err := h.svc.TerminateInstance(c.Request.Context(), id, c.Query("region"), c.Param("instanceId"), preserve); err != nil {
respondError(c, err)
return
}
c.Status(http.StatusNoContent)
}
func (h *ociConfigHandler) instanceAction(c *gin.Context) {
id, ok := pathID(c)
if !ok {
return
}
var req instanceActionRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
instance, err := h.svc.InstanceAction(c.Request.Context(), id, req.Region, c.Param("instanceId"), req.Action)
if err != nil {
respondError(c, err)
return
}
c.JSON(http.StatusOK, instance)
}
// ---- 引导卷 ----
type updateBootVolumeRequest struct {
Region string `json:"region"`
DisplayName string `json:"displayName"`
SizeInGBs int64 `json:"sizeInGBs"`
VpusPerGB int64 `json:"vpusPerGB"`
}
func (h *ociConfigHandler) listBootVolumes(c *gin.Context) {
id, ok := pathID(c)
if !ok {
return
}
volumes, err := h.svc.BootVolumes(c.Request.Context(), id, c.Query("region"), c.Query("availabilityDomain"), c.Query("compartmentId"))
if err != nil {
respondError(c, err)
return
}
c.JSON(http.StatusOK, volumes)
}
func (h *ociConfigHandler) getBootVolume(c *gin.Context) {
id, ok := pathID(c)
if !ok {
return
}
volume, err := h.svc.BootVolume(c.Request.Context(), id, c.Query("region"), c.Param("bootVolumeId"))
if err != nil {
respondError(c, err)
return
}
c.JSON(http.StatusOK, volume)
}
func (h *ociConfigHandler) updateBootVolume(c *gin.Context) {
id, ok := pathID(c)
if !ok {
return
}
var req updateBootVolumeRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
volume, err := h.svc.UpdateBootVolume(c.Request.Context(), id, c.Param("bootVolumeId"), oci.UpdateBootVolumeInput{
Region: req.Region,
DisplayName: req.DisplayName,
SizeInGBs: req.SizeInGBs,
VpusPerGB: req.VpusPerGB,
})
if err != nil {
respondError(c, err)
return
}
c.JSON(http.StatusOK, volume)
}
func (h *ociConfigHandler) deleteBootVolume(c *gin.Context) {
id, ok := pathID(c)
if !ok {
return
}
if err := h.svc.DeleteBootVolume(c.Request.Context(), id, c.Query("region"), c.Param("bootVolumeId")); err != nil {
respondError(c, err)
return
}
c.Status(http.StatusNoContent)
}