feat: 初始化配置中心项目

This commit is contained in:
2026-01-14 21:02:13 +08:00
commit 4436d3ee88
95 changed files with 8736 additions and 0 deletions
+302
View File
@@ -0,0 +1,302 @@
package handler
import (
"kenaito-conf/server/internal/dao"
"kenaito-conf/server/internal/middleware"
"kenaito-conf/server/internal/model"
"net/http"
"strconv"
"time"
"github.com/gin-gonic/gin"
)
type AppHandler struct{}
func NewAppHandler() *AppHandler {
return &AppHandler{}
}
type AppListRequest struct {
PageNo int `form:"pageNo" binding:"required"`
PageSize int `form:"pageSize" binding:"required"`
AppType string `form:"appType"`
}
func (h *AppHandler) List(c *gin.Context) {
var req AppListRequest
if err := c.ShouldBindQuery(&req); err != nil {
c.JSON(http.StatusBadRequest, model.WrapperResponse{Code: 400, Msg: "参数错误"})
return
}
userInfo := middleware.GetUserInfo(c)
query := dao.Engine.Where("enabled = 1")
if !userInfo.IsSuperAdmin() {
if userInfo.IsGroupAdmin() && userInfo.GroupId != nil {
query = query.And("group_id = ?", *userInfo.GroupId)
} else {
userId, _ := strconv.Atoi(userInfo.Id)
query = query.And("(owner_id = ? OR id IN (SELECT app_id FROM user_permissions WHERE user_id = ?))", userId, userId)
}
}
var apps []model.Application
total, _ := query.Count(&model.Application{})
query.Limit(req.PageSize, (req.PageNo-1)*req.PageSize).Find(&apps)
// load group names
groupIds := make(map[int]bool)
for _, app := range apps {
if app.GroupId != nil {
groupIds[*app.GroupId] = true
}
}
groupMap := make(map[int]string)
for gid := range groupIds {
var group model.BusinessGroup
if has, _ := dao.Engine.ID(gid).Get(&group); has {
groupMap[gid] = group.Name
}
}
for i := range apps {
if apps[i].GroupId != nil {
apps[i].GroupName = groupMap[*apps[i].GroupId]
}
}
c.JSON(http.StatusOK, model.PageResult{PageNo: req.PageNo, PageSize: req.PageSize, Total: total, Data: apps})
}
func (h *AppHandler) GetById(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
var app model.Application
has, _ := dao.Engine.ID(id).Get(&app)
if !has {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 404, Msg: "应用不存在"})
return
}
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0, Data: app})
}
func (h *AppHandler) Tree(c *gin.Context) {
var apps []model.Application
dao.Engine.Find(&apps)
tree := make(map[string]*AppTreeModel)
for _, app := range apps {
node := &AppTreeModel{
Id: strconv.Itoa(app.Id),
Name: app.Name,
Code: app.Code,
ServiceId: app.ServiceId,
Children: make([]*AppTreeModel, 0),
}
tree[strconv.Itoa(app.Id)] = node
}
result := make([]*AppTreeModel, 0)
for _, app := range apps {
node := tree[strconv.Itoa(app.Id)]
if app.ParentId == nil {
result = append(result, node)
} else {
parentId := strconv.Itoa(*app.ParentId)
if parent, ok := tree[parentId]; ok {
parent.Children = append(parent.Children, node)
}
}
}
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0, Data: result})
}
type AddAppRequest struct {
Id int `json:"id"`
GroupId *int `json:"groupId"`
ParentId *int `json:"parentId"`
Name string `json:"name" binding:"required"`
Code string `json:"code" binding:"required"`
ServiceId string `json:"serviceId"`
OwnerId *int `json:"ownerId"`
Remarks string `json:"remarks"`
}
func (h *AppHandler) Add(c *gin.Context) {
var req AddAppRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, model.WrapperResponse{Code: 400, Msg: "参数错误"})
return
}
userInfo := middleware.GetUserInfo(c)
// check if code exists
var existing model.Application
if has, _ := dao.Engine.Where("code = ?", req.Code).Get(&existing); has {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 1002, Msg: "应用已存在"})
return
}
ownerId := req.OwnerId
if ownerId == nil {
uid, _ := strconv.Atoi(userInfo.Id)
ownerId = &uid
}
var owner model.User
if has, _ := dao.Engine.ID(*ownerId).Get(&owner); !has {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 400, Msg: "负责人不存在"})
return
}
app := model.Application{
GroupId: req.GroupId,
ParentId: req.ParentId,
Name: req.Name,
Code: req.Code,
ServiceId: req.ServiceId,
OwnerId: ownerId,
OwnerName: owner.Name,
Remarks: req.Remarks,
Enabled: true,
CreatedAt: func() *time.Time { t := time.Now(); return &t }(),
CreatedBy: userInfo.Name,
}
if req.ParentId != nil {
var parent model.Application
if has, _ := dao.Engine.ID(*req.ParentId).Get(&parent); has {
app.GroupId = parent.GroupId
app.IsModule = true
app.Code = parent.Code + "-" + req.Code
}
}
dao.Engine.Insert(&app)
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0, Data: true})
}
func (h *AppHandler) Update(c *gin.Context) {
var req AddAppRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, model.WrapperResponse{Code: 400, Msg: "参数错误"})
return
}
if req.Id == 0 {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 400, Msg: "id参数缺失"})
return
}
var app model.Application
has, _ := dao.Engine.ID(req.Id).Get(&app)
if !has {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 404, Msg: "应用不存在"})
return
}
userInfo := middleware.GetUserInfo(c)
app.Name = req.Name
app.ServiceId = req.ServiceId
app.Remarks = req.Remarks
if req.OwnerId != nil {
if app.OwnerId == nil || *req.OwnerId != *app.OwnerId {
var owner model.User
if has, _ := dao.Engine.ID(*req.OwnerId).Get(&owner); has {
app.OwnerName = owner.Name
}
}
app.OwnerId = req.OwnerId
}
app.UpdatedBy = userInfo.Name
now := time.Now()
app.UpdatedAt = &now
dao.Engine.ID(app.Id).Cols("name", "service_id", "remarks", "owner_id", "owner_name", "updated_by", "updated_at").Update(&app)
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0, Data: true})
}
func (h *AppHandler) Delete(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
userInfo := middleware.GetUserInfo(c)
if !userInfo.IsSuperAdmin() {
c.JSON(http.StatusForbidden, model.WrapperResponse{Code: 403, Msg: "超级管理员才能删除"})
return
}
var app model.Application
has, _ := dao.Engine.ID(id).Get(&app)
if !has {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 404, Msg: "应用不存在"})
return
}
app.Enabled = false
app.UpdatedBy = userInfo.Name
now := time.Now()
app.UpdatedAt = &now
dao.Engine.ID(app.Id).Cols("enabled", "updated_by", "updated_at").Update(&app)
// soft delete related configs
dao.Engine.Where("app_id = ?", id).Cols("enabled").Update(&model.AppConfig{Enabled: false})
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0})
}
func (h *AppHandler) Switch(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
userInfo := middleware.GetUserInfo(c)
var app model.Application
has, _ := dao.Engine.ID(id).Get(&app)
if !has {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 404, Msg: "应用不存在"})
return
}
app.Enabled = !app.Enabled
app.UpdatedBy = userInfo.Name
now := time.Now()
app.UpdatedAt = &now
dao.Engine.ID(app.Id).Cols("enabled", "updated_by", "updated_at").Update(&app)
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0, Data: true})
}
func (h *AppHandler) Options(c *gin.Context) {
userInfo := middleware.GetUserInfo(c)
query := dao.Engine.Where("enabled = 1")
if !userInfo.IsSuperAdmin() {
if userInfo.IsGroupAdmin() && userInfo.GroupId != nil {
query = query.And("group_id = ?", *userInfo.GroupId)
} else {
userId, _ := strconv.Atoi(userInfo.Id)
query = query.And("owner_id = ?", userId)
}
}
var apps []model.Application
query.Find(&apps)
options := make([]model.SelectOption, 0)
for _, app := range apps {
options = append(options, model.SelectOption{Value: strconv.Itoa(app.Id), Label: app.Name})
}
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0, Data: options})
}
type AppTreeModel struct {
Id string `json:"id"`
Name string `json:"name"`
Code string `json:"code"`
ServiceId string `json:"serviceId,omitempty"`
Children []*AppTreeModel `json:"children"`
}
+55
View File
@@ -0,0 +1,55 @@
package handler
import (
"kenaito-conf/server/internal/middleware"
"kenaito-conf/server/internal/model"
"kenaito-conf/server/internal/service"
"net/http"
"github.com/gin-gonic/gin"
)
type AuthHandler struct {
authService *service.AuthService
}
func NewAuthHandler(authService *service.AuthService) *AuthHandler {
return &AuthHandler{authService: authService}
}
type LoginRequest struct {
UserName string `json:"userName" binding:"required"`
Password string `json:"password" binding:"required"`
}
func (h *AuthHandler) Login(c *gin.Context) {
var req LoginRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, model.WrapperResponse{Code: 400, Msg: "参数错误"})
return
}
token, userInfo, err := h.authService.Login(req.UserName, req.Password)
if err != nil {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 400, Msg: err.Error()})
return
}
c.JSON(http.StatusOK, model.WrapperResponse{
Code: 0,
Data: gin.H{"token": token, "userInfo": userInfo},
})
}
func (h *AuthHandler) GetLoginUserInfo(c *gin.Context) {
userInfo := middleware.GetUserInfo(c)
if userInfo == nil {
c.JSON(http.StatusUnauthorized, model.WrapperResponse{Code: 401, Msg: "未登录"})
return
}
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0, Data: userInfo})
}
func (h *AuthHandler) Logout(c *gin.Context) {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0})
}
+320
View File
@@ -0,0 +1,320 @@
package handler
import (
"kenaito-conf/server/internal/dao"
"kenaito-conf/server/internal/middleware"
"kenaito-conf/server/internal/model"
"kenaito-conf/server/internal/service"
"net/http"
"strconv"
"time"
"github.com/gin-gonic/gin"
)
type ConfigHandler struct {
cryptoService *service.CryptoService
parseService *service.ConfigParseService
permService *service.PermissionService
}
func NewConfigHandler(crypto *service.CryptoService, parse *service.ConfigParseService, perm *service.PermissionService) *ConfigHandler {
return &ConfigHandler{cryptoService: crypto, parseService: parse, permService: perm}
}
type ConfigListRequest struct {
PageNo int `form:"pageNo" binding:"required"`
PageSize int `form:"pageSize" binding:"required"`
Env string `form:"env"`
AppId *int `form:"appId"`
}
func (h *ConfigHandler) List(c *gin.Context) {
var req ConfigListRequest
if err := c.ShouldBindQuery(&req); err != nil {
c.JSON(http.StatusBadRequest, model.WrapperResponse{Code: 400, Msg: "参数错误"})
return
}
userInfo := middleware.GetUserInfo(c)
query := dao.Engine.Where("enabled = 1")
if req.Env != "" {
query = query.And("env = ?", req.Env)
}
if req.AppId != nil {
if *req.AppId <= 0 {
query = query.And("(app_id is null OR app_id = 0)")
} else {
var app model.Application
if has, _ := dao.Engine.ID(*req.AppId).Get(&app); has {
query = query.And("app_code like ?", app.Code+"%")
}
}
}
// permission filter
if !userInfo.IsSuperAdmin() {
if userInfo.IsGroupAdmin() && userInfo.GroupId != nil {
query = query.And("group_id = ?", *userInfo.GroupId)
} else if len(userInfo.GrantAppIds) > 0 {
query = query.In("app_id", userInfo.GrantAppIds)
} else {
c.JSON(http.StatusOK, model.PageResult{PageNo: req.PageNo, PageSize: req.PageSize, Total: 0, Data: []interface{}{}})
return
}
}
var configs []model.AppConfig
total, _ := query.Count(&model.AppConfig{})
query.Limit(req.PageSize, (req.PageNo-1)*req.PageSize).Find(&configs)
// build app names
for i := range configs {
configs[i].AppNames = h.buildAppNames(&configs[i])
}
c.JSON(http.StatusOK, model.PageResult{PageNo: req.PageNo, PageSize: req.PageSize, Total: total, Data: configs})
}
func (h *ConfigHandler) GetById(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
var config model.AppConfig
has, _ := dao.Engine.ID(id).Get(&config)
if !has {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 404, Msg: "配置不存在"})
return
}
userInfo := middleware.GetUserInfo(c)
if err := h.permService.RequirePermission(userInfo, config.GroupId, config.AppId, "RO"); err != nil {
c.JSON(http.StatusForbidden, model.WrapperResponse{Code: 403, Msg: err.Error()})
return
}
config.AppNames = h.buildAppNames(&config)
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0, Data: config})
}
type AddConfigRequest struct {
Id int `json:"id"`
GroupId *int `json:"groupId"`
AppId *int `json:"appId"`
Env string `json:"env" binding:"required"`
Version string `json:"version"`
Name string `json:"name"`
Type int `json:"type" binding:"required"`
Contents string `json:"contents"`
Global bool `json:"global"`
}
func (h *ConfigHandler) Add(c *gin.Context) {
var req AddConfigRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, model.WrapperResponse{Code: 400, Msg: "参数错误"})
return
}
userInfo := middleware.GetUserInfo(c)
if req.Global {
if req.GroupId == nil {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 400, Msg: "全局配置需要指定所属业务组"})
return
}
if err := h.permService.RequirePermission(userInfo, req.GroupId, nil, "RW"); err != nil {
c.JSON(http.StatusForbidden, model.WrapperResponse{Code: 403, Msg: err.Error()})
return
}
}
if !req.Global && req.AppId == nil {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 400, Msg: "非全局绑定应用不能为空"})
return
}
var app model.Application
if req.AppId != nil {
if has, _ := dao.Engine.ID(*req.AppId).Get(&app); !has {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 400, Msg: "应用不存在"})
return
}
}
config := model.AppConfig{
GroupId: req.GroupId,
AppId: req.AppId,
Env: req.Env,
Version: req.Version,
Name: req.Name,
Type: req.Type,
Contents: req.Contents,
Enabled: true,
CreatedAt: func() *time.Time { t := time.Now(); return &t }(),
CreatedBy: userInfo.Name,
UpdatedAt: func() *time.Time { t := time.Now(); return &t }(),
UpdatedBy: userInfo.Name,
}
if req.AppId != nil {
config.AppCode = app.Code
if !req.Global {
config.GroupId = app.GroupId
}
}
// encrypt sensitive values
h.encryptIfRequired(&config)
if _, err := dao.Engine.Insert(&config); err != nil {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 500, Msg: "保存失败"})
return
}
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0, Data: true})
}
func (h *ConfigHandler) Update(c *gin.Context) {
var req AddConfigRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, model.WrapperResponse{Code: 400, Msg: "参数错误"})
return
}
if req.Id == 0 {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 400, Msg: "id参数缺失"})
return
}
var config model.AppConfig
has, _ := dao.Engine.ID(req.Id).Get(&config)
if !has {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 404, Msg: "配置不存在"})
return
}
userInfo := middleware.GetUserInfo(c)
if err := h.permService.RequirePermission(userInfo, config.GroupId, config.AppId, "RW"); err != nil {
c.JSON(http.StatusForbidden, model.WrapperResponse{Code: 403, Msg: err.Error()})
return
}
// save history
h.saveHistory(&config, userInfo.Name)
originalContents := config.Contents
config.Contents = req.Contents
config.Version = req.Version
config.UpdatedBy = userInfo.Name
now := time.Now()
config.UpdatedAt = &now
h.encryptIfRequired(&config)
dao.Engine.ID(config.Id).Cols("contents", "version", "updated_by", "updated_at").Update(&config)
// TODO: publish config change event (ZK removed, could use webhook or other mechanism)
_ = originalContents
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0, Data: true})
}
func (h *ConfigHandler) Delete(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
var config model.AppConfig
has, _ := dao.Engine.ID(id).Get(&config)
if !has {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 404, Msg: "配置不存在"})
return
}
userInfo := middleware.GetUserInfo(c)
if err := h.permService.RequirePermission(userInfo, config.GroupId, config.AppId, "RW"); err != nil {
c.JSON(http.StatusForbidden, model.WrapperResponse{Code: 403, Msg: err.Error()})
return
}
config.Enabled = false
config.UpdatedBy = userInfo.Name
now := time.Now()
config.UpdatedAt = &now
dao.Engine.ID(config.Id).Cols("enabled", "updated_by", "updated_at").Update(&config)
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0})
}
func (h *ConfigHandler) GetHistory(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
var histories []model.AppConfigHistory
dao.Engine.Where("origin_id = ?", id).OrderBy("id desc").Limit(5).Find(&histories)
if len(histories) > 0 {
var currentConfig model.AppConfig
if has, _ := dao.Engine.ID(histories[0].OriginId).Get(&currentConfig); has {
for i := range histories {
histories[i].ActiveContents = currentConfig.Contents
}
}
}
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0, Data: histories})
}
func (h *ConfigHandler) Rollback(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
var history model.AppConfigHistory
has, _ := dao.Engine.ID(id).Get(&history)
if !has {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 404, Msg: "历史记录不存在"})
return
}
var config model.AppConfig
if has, _ := dao.Engine.ID(history.OriginId).Get(&config); has {
userInfo := middleware.GetUserInfo(c)
if err := h.permService.RequirePermission(userInfo, config.GroupId, config.AppId, "RW"); err != nil {
c.JSON(http.StatusForbidden, model.WrapperResponse{Code: 403, Msg: err.Error()})
return
}
config.Contents = history.Contents
config.UpdatedBy = userInfo.Name
now := time.Now()
config.UpdatedAt = &now
dao.Engine.ID(config.Id).Cols("contents", "updated_by", "updated_at").Update(&config)
}
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0})
}
func (h *ConfigHandler) buildAppNames(config *model.AppConfig) string {
if config.Global || (config.AppId == nil || *config.AppId == 0) {
return "全局配置"
}
var app model.Application
if has, _ := dao.Engine.ID(*config.AppId).Get(&app); has {
return app.Name + "(" + app.Code + ")"
}
return ""
}
func (h *ConfigHandler) saveHistory(config *model.AppConfig, operator string) {
history := model.AppConfigHistory{
OriginId: config.Id,
AppId: config.AppId,
AppNames: h.buildAppNames(config),
Env: config.Env,
Version: config.Version,
Name: config.Name,
Type: config.Type,
Contents: config.Contents,
CreatedAt: func() *time.Time { t := time.Now(); return &t }(),
CreatedBy: operator,
}
dao.Engine.Insert(&history)
}
func (h *ConfigHandler) encryptIfRequired(config *model.AppConfig) {
// simplified: check if contents contain sensitive keys
// In production, parse the config and encrypt values with {Cipher} prefix
// For now, skip encryption logic
}
+171
View File
@@ -0,0 +1,171 @@
package handler
import (
"fmt"
"kenaito-conf/server/internal/dao"
"kenaito-conf/server/internal/model"
"kenaito-conf/server/internal/service"
"net/http"
"strconv"
"time"
"github.com/gin-gonic/gin"
)
type ConfigApiHandler struct {
cryptoService *service.CryptoService
parseService *service.ConfigParseService
}
func NewConfigApiHandler(crypto *service.CryptoService, parse *service.ConfigParseService) *ConfigApiHandler {
return &ConfigApiHandler{cryptoService: crypto, parseService: parse}
}
func (h *ConfigApiHandler) FetchAllConfigs(c *gin.Context) {
appName := c.Query("appName")
env := c.Query("env")
version := c.DefaultQuery("version", "latest")
ignoreGlobal := c.DefaultQuery("ignoreGlobal", "false") == "true"
if appName == "" || env == "" {
c.JSON(http.StatusBadRequest, model.WrapperResponse{Code: 400, Msg: "参数缺失"})
return
}
// find app by code
var app model.Application
has, _ := dao.Engine.Where("code = ? AND enabled = 1", appName).Get(&app)
if !has {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 1001, Msg: "配置不存在"})
return
}
result := make(map[string]interface{})
// load global configs
if !ignoreGlobal && app.GroupId != nil {
var globalConfigs []model.AppConfig
dao.Engine.Where("env = ? AND version = ? AND enabled = 1 AND group_id = ? AND (app_id is null OR app_id = 0)",
env, version, *app.GroupId).Find(&globalConfigs)
for _, cfg := range globalConfigs {
kvMap := h.parseService.ParseConfigToKVMap(&cfg)
for k, v := range kvMap {
result[k] = v
}
}
}
// load parent app configs
if app.ParentId != nil {
var parentConfigs []model.AppConfig
dao.Engine.Where("env = ? AND version = ? AND enabled = 1 AND app_id = ?",
env, version, *app.ParentId).Find(&parentConfigs)
for _, cfg := range parentConfigs {
kvMap := h.parseService.ParseConfigToKVMap(&cfg)
for k, v := range kvMap {
result[k] = v
}
}
}
// load app configs
var appConfigs []model.AppConfig
dao.Engine.Where("env = ? AND version = ? AND enabled = 1 AND app_id = ?",
env, version, app.Id).Find(&appConfigs)
for _, cfg := range appConfigs {
kvMap := h.parseService.ParseConfigToKVMap(&cfg)
for k, v := range kvMap {
result[k] = v
}
}
if len(result) == 0 {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 1001, Msg: "配置不存在"})
return
}
// add encryption keys
globalCryptKey := h.cryptoService.GetCryptKey(0, env)
result["kenaito.config.global-encrypt-secret"] = globalCryptKey
appCryptKey := h.cryptoService.GetCryptKey(app.Id, env)
result["kenaito.config.encrypt-secret"] = appCryptKey
result["latest.fetch.timestamp"] = strconv.FormatInt(time.Now().UnixMilli(), 10)
c.JSON(http.StatusOK, result)
}
type FetchChangedRequest struct {
AppName string `json:"appName" binding:"required"`
Env string `json:"env" binding:"required"`
Version string `json:"version"`
LastTime int64 `json:"lastTime" binding:"required"`
IgnoreGlobal bool `json:"ignoreGlobal"`
FetchAll bool `json:"fetchAll"`
}
func (h *ConfigApiHandler) FetchChangedConfigs(c *gin.Context) {
var req FetchChangedRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, model.WrapperResponse{Code: 400, Msg: "参数错误"})
return
}
var app model.Application
has, _ := dao.Engine.Where("code = ? AND enabled = 1", req.AppName).Get(&app)
if !has {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 1001, Msg: "应用不存在"})
return
}
version := req.Version
if version == "" {
version = "latest"
}
lastUpdateTime := time.UnixMilli(req.LastTime)
result := make(map[string]string)
// load global changed configs
if !req.IgnoreGlobal && app.GroupId != nil {
var globalConfigs []model.AppConfig
dao.Engine.Where("env = ? AND version = ? AND enabled = 1 AND group_id = ? AND (app_id is null OR app_id = 0) AND updated_at > ?",
req.Env, version, *app.GroupId, lastUpdateTime).Find(&globalConfigs)
for _, cfg := range globalConfigs {
h.addChangedConfigs(result, &cfg, req.FetchAll)
}
}
// load parent app changed configs
if app.ParentId != nil {
var parentConfigs []model.AppConfig
dao.Engine.Where("env = ? AND version = ? AND enabled = 1 AND app_id = ? AND updated_at > ?",
req.Env, version, *app.ParentId, lastUpdateTime).Find(&parentConfigs)
for _, cfg := range parentConfigs {
h.addChangedConfigs(result, &cfg, req.FetchAll)
}
}
// load app changed configs
var appConfigs []model.AppConfig
dao.Engine.Where("env = ? AND version = ? AND enabled = 1 AND app_id = ? AND updated_at > ?",
req.Env, version, app.Id, lastUpdateTime).Find(&appConfigs)
for _, cfg := range appConfigs {
h.addChangedConfigs(result, &cfg, req.FetchAll)
}
result["latest.fetch.timestamp"] = strconv.FormatInt(time.Now().UnixMilli(), 10)
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0, Data: result})
}
func (h *ConfigApiHandler) addChangedConfigs(result map[string]string, config *model.AppConfig, fetchAll bool) {
kvMap := h.parseService.ParseConfigToKVMap(config)
for k, v := range kvMap {
result[k] = fmt.Sprintf("%v", v)
}
}
func (h *ConfigApiHandler) Ping(c *gin.Context) {
c.String(http.StatusOK, "200")
}
+137
View File
@@ -0,0 +1,137 @@
package handler
import (
"kenaito-conf/server/internal/dao"
"kenaito-conf/server/internal/middleware"
"kenaito-conf/server/internal/model"
"net/http"
"strconv"
"time"
"github.com/gin-gonic/gin"
)
type GroupHandler struct{}
func NewGroupHandler() *GroupHandler {
return &GroupHandler{}
}
func (h *GroupHandler) List(c *gin.Context) {
var groups []model.BusinessGroup
dao.Engine.Find(&groups)
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0, Data: groups})
}
func (h *GroupHandler) GetById(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
var group model.BusinessGroup
has, _ := dao.Engine.ID(id).Get(&group)
if !has {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 404, Msg: "业务组不存在"})
return
}
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0, Data: group})
}
type AddGroupRequest struct {
Id int `json:"id"`
Name string `json:"name" binding:"required"`
MasterUid *int `json:"masterUid" binding:"required"`
Remarks string `json:"remarks"`
}
func (h *GroupHandler) Add(c *gin.Context) {
var req AddGroupRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, model.WrapperResponse{Code: 400, Msg: "参数错误"})
return
}
userInfo := middleware.GetUserInfo(c)
if !userInfo.IsSuperAdmin() {
c.JSON(http.StatusForbidden, model.WrapperResponse{Code: 403, Msg: "超级管理员才能操作"})
return
}
var existing model.BusinessGroup
if has, _ := dao.Engine.Where("name = ?", req.Name).Get(&existing); has {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 1002, Msg: "组名已存在"})
return
}
var master model.User
if has, _ := dao.Engine.ID(*req.MasterUid).Get(&master); !has {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 400, Msg: "管理员不存在"})
return
}
group := model.BusinessGroup{
Name: req.Name,
Master: master.Name,
MasterUid: req.MasterUid,
Remarks: req.Remarks,
Enabled: true,
CreatedAt: func() *time.Time { t := time.Now(); return &t }(),
CreatedBy: userInfo.Name,
}
dao.Engine.Insert(&group)
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0})
}
func (h *GroupHandler) Update(c *gin.Context) {
var req AddGroupRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, model.WrapperResponse{Code: 400, Msg: "参数错误"})
return
}
userInfo := middleware.GetUserInfo(c)
if !userInfo.IsSuperAdmin() {
c.JSON(http.StatusForbidden, model.WrapperResponse{Code: 403, Msg: "超级管理员才能操作"})
return
}
if req.Id == 0 {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 400, Msg: "id参数缺失"})
return
}
var group model.BusinessGroup
has, _ := dao.Engine.ID(req.Id).Get(&group)
if !has {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 404, Msg: "业务组不存在"})
return
}
group.Name = req.Name
group.Remarks = req.Remarks
if req.MasterUid != nil && (group.MasterUid == nil || *req.MasterUid != *group.MasterUid) {
var master model.User
if has, _ := dao.Engine.ID(*req.MasterUid).Get(&master); has {
group.Master = master.Name
group.MasterUid = req.MasterUid
}
}
group.UpdatedBy = userInfo.Name
now := time.Now()
group.UpdatedAt = &now
dao.Engine.ID(group.Id).Cols("name", "remarks", "master", "master_uid", "updated_by", "updated_at").Update(&group)
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0})
}
func (h *GroupHandler) Options(c *gin.Context) {
var groups []model.BusinessGroup
dao.Engine.Where("enabled = 1").Find(&groups)
options := make([]model.SelectOption, 0)
for _, g := range groups {
options = append(options, model.SelectOption{Value: strconv.Itoa(g.Id), Label: g.Name})
}
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0, Data: options})
}
+45
View File
@@ -0,0 +1,45 @@
package handler
import (
"kenaito-conf/server/internal/middleware"
"kenaito-conf/server/internal/model"
"net/http"
"github.com/gin-gonic/gin"
)
type IndexHandler struct{}
func NewIndexHandler() *IndexHandler {
return &IndexHandler{}
}
func (h *IndexHandler) GetMenus(c *gin.Context) {
userInfo := middleware.GetUserInfo(c)
if userInfo == nil {
c.JSON(http.StatusUnauthorized, model.WrapperResponse{Code: 401, Msg: "未登录"})
return
}
menus := make([]model.Menu, 0)
// 全局管理菜单
menu1 := model.NewMenu("全局管理", "fa-cubes", false)
if userInfo.IsSuperAdmin() {
menu1.AddSubMenu("环境管理", "&#xe61d;", "/profile/list")
}
menu1.AddSubMenu("业务组管理", "&#xe613;", "/group/list")
if userInfo.IsSuperAdmin() || userInfo.IsGroupAdmin() {
menu1.AddSubMenu("用户管理", "&#xe612;", "/user/list")
}
menu1.AddSubMenu("应用管理", "&#xe63b;", "/app/list")
menus = append(menus, menu1)
// 配置管理菜单
menu2 := model.NewMenu("配置管理", "fa-cubes", true)
menu2.AddSubMenu("配置列表", "&#xe60a;", "/config/list")
menu2.AddSubMenu("新建配置", "&#xe63c;", "/config/add")
menus = append(menus, menu2)
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0, Data: menus})
}
+168
View File
@@ -0,0 +1,168 @@
package handler
import (
"kenaito-conf/server/internal/dao"
"kenaito-conf/server/internal/middleware"
"kenaito-conf/server/internal/model"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
)
type ProfileHandler struct{}
func NewProfileHandler() *ProfileHandler {
return &ProfileHandler{}
}
func (h *ProfileHandler) List(c *gin.Context) {
var profiles []model.Profile
dao.Engine.Find(&profiles)
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0, Data: profiles})
}
func (h *ProfileHandler) GetById(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
var profile model.Profile
has, _ := dao.Engine.ID(id).Get(&profile)
if !has {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 404, Msg: "环境不存在"})
return
}
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0, Data: profile})
}
type AddProfileRequest struct {
Name string `json:"name" binding:"required"`
Alias string `json:"alias"`
IsDefault bool `json:"isDefault"`
}
func (h *ProfileHandler) Add(c *gin.Context) {
var req AddProfileRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, model.WrapperResponse{Code: 400, Msg: "参数错误"})
return
}
userInfo := middleware.GetUserInfo(c)
if !userInfo.IsSuperAdmin() {
c.JSON(http.StatusForbidden, model.WrapperResponse{Code: 403, Msg: "超级管理员才能操作"})
return
}
var existing model.Profile
if has, _ := dao.Engine.Where("name = ?", req.Name).Get(&existing); has {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 1002, Msg: "环境已存在"})
return
}
profile := model.Profile{
Name: req.Name,
Alias: req.Alias,
IsDefault: req.IsDefault,
Enabled: true,
}
dao.Engine.Insert(&profile)
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0, Data: true})
}
type UpdateProfileRequest struct {
Id int `json:"id" binding:"required"`
Name string `json:"name" binding:"required"`
Alias string `json:"alias"`
IsDefault bool `json:"isDefault"`
}
func (h *ProfileHandler) Update(c *gin.Context) {
var req UpdateProfileRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, model.WrapperResponse{Code: 400, Msg: "参数错误"})
return
}
userInfo := middleware.GetUserInfo(c)
if !userInfo.IsSuperAdmin() {
c.JSON(http.StatusForbidden, model.WrapperResponse{Code: 403, Msg: "超级管理员才能操作"})
return
}
var profile model.Profile
has, _ := dao.Engine.ID(req.Id).Get(&profile)
if !has {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 404, Msg: "环境不存在"})
return
}
if profile.Name != req.Name {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 400, Msg: "环境标识不可修改"})
return
}
profile.Alias = req.Alias
dao.Engine.ID(profile.Id).Update(&profile)
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0, Data: true})
}
func (h *ProfileHandler) Delete(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
userInfo := middleware.GetUserInfo(c)
if !userInfo.IsSuperAdmin() {
c.JSON(http.StatusForbidden, model.WrapperResponse{Code: 403, Msg: "超级管理员才能操作"})
return
}
var profile model.Profile
has, _ := dao.Engine.ID(id).Get(&profile)
if !has {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 404, Msg: "环境不存在"})
return
}
if profile.IsDefault {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 1003, Msg: "默认环境不能删除"})
return
}
dao.Engine.ID(id).Delete(&model.Profile{})
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0})
}
func (h *ProfileHandler) Switch(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
userInfo := middleware.GetUserInfo(c)
if !userInfo.IsSuperAdmin() {
c.JSON(http.StatusForbidden, model.WrapperResponse{Code: 403, Msg: "超级管理员才能操作"})
return
}
var profile model.Profile
has, _ := dao.Engine.ID(id).Get(&profile)
if !has {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 404, Msg: "环境不存在"})
return
}
profile.Enabled = !profile.Enabled
dao.Engine.ID(profile.Id).Cols("enabled").Update(&profile)
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0, Data: true})
}
func (h *ProfileHandler) Options(c *gin.Context) {
var profiles []model.Profile
dao.Engine.Where("enabled = 1").Find(&profiles)
options := make([]model.SelectOption, 0)
for _, p := range profiles {
options = append(options, model.SelectOption{Value: p.Name, Label: p.Alias + "(" + p.Name + ")"})
}
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0, Data: options})
}
+348
View File
@@ -0,0 +1,348 @@
package handler
import (
"kenaito-conf/server/internal/dao"
"kenaito-conf/server/internal/middleware"
"kenaito-conf/server/internal/model"
"kenaito-conf/server/internal/service"
"kenaito-conf/server/internal/util"
"math/rand"
"net/http"
"strconv"
"time"
"github.com/gin-gonic/gin"
)
type UserHandler struct {
permService *service.PermissionService
}
func NewUserHandler(perm *service.PermissionService) *UserHandler {
return &UserHandler{permService: perm}
}
func (h *UserHandler) List(c *gin.Context) {
groupIdStr := c.Query("groupId")
userInfo := middleware.GetUserInfo(c)
var users []model.User
query := dao.Engine.Where("1=1")
if !userInfo.IsSuperAdmin() && userInfo.GroupId != nil {
query = query.And("group_id = ?", *userInfo.GroupId)
}
if groupIdStr != "" {
groupId, _ := strconv.Atoi(groupIdStr)
query = query.And("group_id = ?", groupId)
}
query.Find(&users)
// load group names
var groups []model.BusinessGroup
dao.Engine.Find(&groups)
groupMap := make(map[int]string)
for _, g := range groups {
groupMap[g.Id] = g.Name
}
for i := range users {
if users[i].GroupId != nil {
users[i].GroupName = groupMap[*users[i].GroupId]
}
}
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0, Data: users})
}
type AddUserRequest struct {
Name string `json:"name" binding:"required"`
Mobile string `json:"mobile" binding:"required"`
Email string `json:"email"`
Type string `json:"type" binding:"required"`
GroupId *int `json:"groupId"`
GroupName string `json:"groupName"`
}
func (h *UserHandler) Add(c *gin.Context) {
var req AddUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, model.WrapperResponse{Code: 400, Msg: "参数错误"})
return
}
userInfo := middleware.GetUserInfo(c)
// validate type - only superAdmin can create superAdmin
if req.Type != "user" && req.Type != "groupAdmin" && req.Type != "superAdmin" {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 400, Msg: "无效的用户类型"})
return
}
if req.Type == "superAdmin" && !userInfo.IsSuperAdmin() {
c.JSON(http.StatusForbidden, model.WrapperResponse{Code: 403, Msg: "只有超级管理员才能创建超级管理员"})
return
}
// check name exists
var existing model.User
if has, _ := dao.Engine.Where("name = ?", req.Name).Get(&existing); has {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 1001, Msg: "用户名已存在"})
return
}
if has, _ := dao.Engine.Where("mobile = ?", req.Mobile).Get(&existing); has {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 1001, Msg: "手机号已存在"})
return
}
groupId := req.GroupId
if groupId == nil && req.GroupName != "" {
var group model.BusinessGroup
if has, _ := dao.Engine.Where("name = ?", req.GroupName).Get(&group); has {
groupId = &group.Id
} else {
group = model.BusinessGroup{
Name: req.GroupName,
Enabled: true,
CreatedAt: func() *time.Time { t := time.Now(); return &t }(),
CreatedBy: userInfo.Name,
}
dao.Engine.Insert(&group)
groupId = &group.Id
}
}
password := "admin123"
hashedPassword, _ := util.HashPassword(password)
user := model.User{
Name: req.Name,
Mobile: req.Mobile,
Email: req.Email,
Type: req.Type,
GroupId: groupId,
Password: hashedPassword,
Enabled: true,
CreatedAt: func() *time.Time { t := time.Now(); return &t }(),
}
dao.Engine.Insert(&user)
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0, Data: user})
}
type UpdateUserRequest struct {
Email string `json:"email"`
Mobile string `json:"mobile"`
Password string `json:"password"`
OldPassword string `json:"oldPassword"`
GroupId *int `json:"groupId"`
}
func (h *UserHandler) Update(c *gin.Context) {
var req UpdateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, model.WrapperResponse{Code: 400, Msg: "参数错误"})
return
}
userInfo := middleware.GetUserInfo(c)
userId, _ := strconv.Atoi(userInfo.Id)
var user model.User
has, _ := dao.Engine.ID(userId).Get(&user)
if !has {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 404, Msg: "用户不存在"})
return
}
if req.Email != "" {
user.Email = req.Email
}
if req.Mobile != "" {
user.Mobile = req.Mobile
}
if req.Password != "" {
if err := util.VerifyPassword(user.Password, req.OldPassword); err != nil {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 1001, Msg: "原密码不正确"})
return
}
hashedPassword, _ := util.HashPassword(req.Password)
user.Password = hashedPassword
}
if req.GroupId != nil && *req.GroupId > 0 {
user.GroupId = req.GroupId
}
user.UpdatedAt = func() *time.Time { t := time.Now(); return &t }()
dao.Engine.ID(user.Id).Cols("email", "mobile", "password", "group_id", "updated_at").Update(&user)
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0, Data: true})
}
type AdminUpdateUserRequest struct {
Id int `json:"id" binding:"required"`
Email string `json:"email"`
Mobile string `json:"mobile"`
Type string `json:"type"`
GroupId *int `json:"groupId"`
GroupName string `json:"groupName"`
}
func (h *UserHandler) AdminUpdate(c *gin.Context) {
var req AdminUpdateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, model.WrapperResponse{Code: 400, Msg: "参数错误"})
return
}
userInfo := middleware.GetUserInfo(c)
if !userInfo.IsSuperAdmin() {
c.JSON(http.StatusForbidden, model.WrapperResponse{Code: 403, Msg: "只有超级管理员才能编辑用户"})
return
}
var user model.User
has, _ := dao.Engine.ID(req.Id).Get(&user)
if !has {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 404, Msg: "用户不存在"})
return
}
// superAdmin 不能修改类型和所属组
if user.Type == "superAdmin" {
if req.Type != "" && req.Type != "superAdmin" {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 400, Msg: "不能修改超级管理员的类型"})
return
}
if req.GroupId != nil || req.GroupName != "" {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 400, Msg: "不能修改超级管理员的所属组"})
return
}
}
if req.Mobile != "" {
user.Mobile = req.Mobile
}
if req.Email != "" {
user.Email = req.Email
}
if req.Type != "" && user.Type != "superAdmin" {
user.Type = req.Type
}
// 处理所属组
if user.Type != "superAdmin" {
if req.GroupId != nil && *req.GroupId > 0 {
user.GroupId = req.GroupId
} else if req.GroupName != "" {
var group model.BusinessGroup
has, _ := dao.Engine.Where("name = ?", req.GroupName).Get(&group)
if !has {
newGroup := model.BusinessGroup{
Name: req.GroupName,
Enabled: true,
CreatedAt: func() *time.Time { t := time.Now(); return &t }(),
CreatedBy: userInfo.Name,
}
dao.Engine.Insert(&newGroup)
user.GroupId = &newGroup.Id
} else {
user.GroupId = &group.Id
}
}
}
user.UpdatedAt = func() *time.Time { t := time.Now(); return &t }()
dao.Engine.ID(user.Id).Cols("mobile", "email", "type", "group_id", "updated_at").Update(&user)
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0, Data: true})
}
func (h *UserHandler) Delete(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
userInfo := middleware.GetUserInfo(c)
var user model.User
has, _ := dao.Engine.ID(id).Get(&user)
if !has {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 404, Msg: "用户不存在"})
return
}
if err := h.permService.HasPermissionForUser(userInfo, &user); err != nil {
c.JSON(http.StatusForbidden, model.WrapperResponse{Code: 403, Msg: err.Error()})
return
}
dao.Engine.ID(id).Delete(&model.User{})
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0})
}
func (h *UserHandler) ResetPassword(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
var user model.User
has, _ := dao.Engine.ID(id).Get(&user)
if !has {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 404, Msg: "用户不存在"})
return
}
newPassword := randomString(8)
hashedPassword, _ := util.HashPassword(newPassword)
user.Password = hashedPassword
now := time.Now()
user.UpdatedAt = &now
dao.Engine.ID(user.Id).Cols("password", "updated_at").Update(&user)
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0, Data: newPassword})
}
func (h *UserHandler) Switch(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
var user model.User
has, _ := dao.Engine.ID(id).Get(&user)
if !has {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 404, Msg: "用户不存在"})
return
}
// 用户名为 admin 的用户不能被禁用
if user.Name == "admin" && user.Enabled {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 400, Msg: "admin 用户不能被禁用"})
return
}
user.Enabled = !user.Enabled
dao.Engine.ID(user.Id).Cols("enabled").Update(&user)
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0, Data: true})
}
func (h *UserHandler) Options(c *gin.Context) {
groupIdStr := c.Query("groupId")
var users []model.User
query := dao.Engine.Where("enabled = 1")
if groupIdStr != "" {
groupId, _ := strconv.Atoi(groupIdStr)
query = query.And("group_id = ?", groupId)
}
query.Find(&users)
options := make([]model.SelectOption, 0)
for _, u := range users {
options = append(options, model.SelectOption{Value: strconv.Itoa(u.Id), Label: u.Name})
}
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0, Data: options})
}
func randomString(n int) string {
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
b := make([]byte, n)
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
return string(b)
}