完成租户登录
This commit is contained in:
+168
-58
@@ -16,19 +16,21 @@ type AuthController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
// Login 处理登录请求
|
||||
// Login 处理登录请求(支持租户模式,使用租户名称)
|
||||
func (c *AuthController) Login() {
|
||||
var username, password string
|
||||
var username, password, tenantName string
|
||||
|
||||
// 优先尝试从URL参数获取(Apifox测试方式)
|
||||
username = c.GetString("username")
|
||||
password = c.GetString("password")
|
||||
tenantName = c.GetString("tenant_name")
|
||||
|
||||
// 如果URL参数为空,尝试从JSON请求体获取(前端方式)
|
||||
if username == "" || password == "" {
|
||||
if username == "" || password == "" || tenantName == "" {
|
||||
var loginData struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
TenantName string `json:"tenant_name"`
|
||||
}
|
||||
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &loginData)
|
||||
@@ -44,31 +46,43 @@ func (c *AuthController) Login() {
|
||||
|
||||
username = loginData.Username
|
||||
password = loginData.Password
|
||||
tenantName = loginData.TenantName
|
||||
}
|
||||
|
||||
// 验证参数
|
||||
if tenantName == "" {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "租户名称不能为空",
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
// 添加日志调试
|
||||
fmt.Println("接收到的登录请求:")
|
||||
fmt.Println("用户名:", username)
|
||||
fmt.Println("密码:", password)
|
||||
fmt.Println("租户名称:", tenantName)
|
||||
|
||||
// 验证用户
|
||||
fmt.Println("开始验证用户:", username)
|
||||
user, err := models.ValidateUser(username, password)
|
||||
// 验证用户(先验证租户,再验证租户下的用户)
|
||||
fmt.Println("开始验证用户:", username, "租户:", tenantName)
|
||||
user, err := models.ValidateUser(username, password, tenantName)
|
||||
fmt.Println("验证结果:", err)
|
||||
if user != nil {
|
||||
fmt.Println("用户信息:ID=", user.Id, "Username=", user.Username, "Salt=", user.Salt)
|
||||
fmt.Println("用户信息:ID=", user.Id, "Username=", user.Username, "TenantId=", user.TenantId)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// 登录失败
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "用户名或密码错误",
|
||||
"message": err.Error(),
|
||||
"data": nil,
|
||||
}
|
||||
} else {
|
||||
// 使用models包中的GenerateToken函数生成token
|
||||
tokenString, err := models.GenerateToken(user.Id, user.Username)
|
||||
// 使用models包中的GenerateToken函数生成token(包含租户ID)
|
||||
tokenString, err := models.GenerateToken(user.Id, user.Username, user.TenantId)
|
||||
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
@@ -85,11 +99,12 @@ func (c *AuthController) Login() {
|
||||
"accessToken": tokenString,
|
||||
"token": tokenString, // 兼容性
|
||||
"user": map[string]interface{}{
|
||||
"id": user.Id,
|
||||
"username": user.Username,
|
||||
"email": user.Email,
|
||||
"avatar": user.Avatar,
|
||||
"nickname": user.Nickname,
|
||||
"id": user.Id,
|
||||
"username": user.Username,
|
||||
"email": user.Email,
|
||||
"avatar": user.Avatar,
|
||||
"nickname": user.Nickname,
|
||||
"tenant_id": user.TenantId,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -99,14 +114,35 @@ func (c *AuthController) Login() {
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// ResetPassword 重置用户密码
|
||||
// ResetPassword 重置用户密码(支持租户模式)
|
||||
func (c *AuthController) ResetPassword() {
|
||||
// 获取请求参数
|
||||
username := c.GetString("username")
|
||||
superPassword := c.GetString("superPassword")
|
||||
tenantId, _ := c.GetInt("tenant_id", 0)
|
||||
|
||||
// 如果URL参数中没有租户ID,尝试从JSON请求体获取
|
||||
if tenantId == 0 {
|
||||
var resetData struct {
|
||||
Username string `json:"username"`
|
||||
SuperPassword string `json:"superPassword"`
|
||||
TenantId int `json:"tenant_id"`
|
||||
}
|
||||
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &resetData); err == nil {
|
||||
username = resetData.Username
|
||||
superPassword = resetData.SuperPassword
|
||||
tenantId = resetData.TenantId
|
||||
}
|
||||
}
|
||||
|
||||
if tenantId <= 0 {
|
||||
c.Data["json"] = map[string]interface{}{"success": false, "message": "租户ID不能为空"}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
// 调用模型方法
|
||||
err := models.ResetPassword(username, superPassword)
|
||||
err := models.ResetPassword(username, superPassword, tenantId)
|
||||
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{"success": false, "message": err.Error()}
|
||||
@@ -117,15 +153,38 @@ func (c *AuthController) ResetPassword() {
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// ChangePassword 修改用户密码
|
||||
// ChangePassword 修改用户密码(支持租户模式)
|
||||
func (c *AuthController) ChangePassword() {
|
||||
// 获取请求参数
|
||||
username := c.GetString("username")
|
||||
oldPassword := c.GetString("oldPassword")
|
||||
newPassword := c.GetString("newPassword")
|
||||
tenantId, _ := c.GetInt("tenant_id", 0)
|
||||
|
||||
// 如果URL参数中没有租户ID,尝试从JSON请求体获取
|
||||
if tenantId == 0 {
|
||||
var changeData struct {
|
||||
Username string `json:"username"`
|
||||
OldPassword string `json:"oldPassword"`
|
||||
NewPassword string `json:"newPassword"`
|
||||
TenantId int `json:"tenant_id"`
|
||||
}
|
||||
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &changeData); err == nil {
|
||||
username = changeData.Username
|
||||
oldPassword = changeData.OldPassword
|
||||
newPassword = changeData.NewPassword
|
||||
tenantId = changeData.TenantId
|
||||
}
|
||||
}
|
||||
|
||||
if tenantId <= 0 {
|
||||
c.Data["json"] = map[string]interface{}{"success": false, "message": "租户ID不能为空"}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
// 调用模型方法
|
||||
err := models.ChangePassword(username, oldPassword, newPassword)
|
||||
err := models.ChangePassword(username, oldPassword, newPassword, tenantId)
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{"success": false, "message": err.Error()}
|
||||
} else {
|
||||
@@ -144,9 +203,11 @@ func (c *AuthController) Logout() {
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// FindAllUsers 获取所有用户
|
||||
// FindAllUsers 获取所有用户(支持按租户过滤)
|
||||
func (c *AuthController) FindAllUsers() {
|
||||
users := models.FindAllUsers()
|
||||
// 从查询参数获取租户ID(可选)
|
||||
tenantId, _ := c.GetInt("tenant_id", 0)
|
||||
users := models.FindAllUsers(tenantId)
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"success": true,
|
||||
"message": "获取用户列表成功",
|
||||
@@ -155,10 +216,12 @@ func (c *AuthController) FindAllUsers() {
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetUserByUsername 通过用户名查询用户信息
|
||||
// GetUserByUsername 通过用户名查询用户信息(支持租户模式)
|
||||
func (c *AuthController) GetUserByUsername() {
|
||||
// 获取请求参数中的用户名
|
||||
// 获取请求参数中的用户名和租户ID
|
||||
username := c.GetString("username")
|
||||
tenantId, _ := c.GetInt("tenant_id", 0)
|
||||
|
||||
if username == "" {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
@@ -169,8 +232,18 @@ func (c *AuthController) GetUserByUsername() {
|
||||
return
|
||||
}
|
||||
|
||||
if tenantId <= 0 {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "租户ID不能为空",
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
// 调用模型层方法查询用户
|
||||
user, err := models.GetUserByUsername(username) // 假设models层有这个方法
|
||||
user, err := models.GetUserByUsername(username, tenantId)
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
@@ -188,19 +261,19 @@ func (c *AuthController) GetUserByUsername() {
|
||||
"code": 0,
|
||||
"message": "查询成功",
|
||||
"data": map[string]interface{}{
|
||||
"id": user.Id,
|
||||
"username": user.Username,
|
||||
"email": user.Email,
|
||||
"avatar": user.Avatar,
|
||||
"nickname": user.Nickname,
|
||||
// 其他需要返回的用户字段
|
||||
"id": user.Id,
|
||||
"username": user.Username,
|
||||
"email": user.Email,
|
||||
"avatar": user.Avatar,
|
||||
"nickname": user.Nickname,
|
||||
"tenant_id": user.TenantId,
|
||||
},
|
||||
}
|
||||
}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddUser 添加新用户
|
||||
// AddUser 添加新用户(支持租户模式)
|
||||
func (c *AuthController) AddUser() {
|
||||
// 定义接收用户数据的结构体(与JSON请求体对应)
|
||||
var userData struct {
|
||||
@@ -209,6 +282,7 @@ func (c *AuthController) AddUser() {
|
||||
Email string `json:"email"`
|
||||
Nickname string `json:"nickname"`
|
||||
Avatar string `json:"avatar"`
|
||||
TenantId int `json:"tenant_id"`
|
||||
}
|
||||
|
||||
// 解析请求体JSON数据
|
||||
@@ -242,6 +316,15 @@ func (c *AuthController) AddUser() {
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
if userData.TenantId <= 0 {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "租户ID不能为空",
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
// 调用模型层方法添加用户(传递参数,接收新用户对象)
|
||||
newUser, err := models.AddUser(
|
||||
@@ -250,6 +333,7 @@ func (c *AuthController) AddUser() {
|
||||
userData.Email,
|
||||
userData.Nickname,
|
||||
userData.Avatar,
|
||||
userData.TenantId,
|
||||
)
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
@@ -262,26 +346,28 @@ func (c *AuthController) AddUser() {
|
||||
"code": 0,
|
||||
"message": "用户添加成功",
|
||||
"data": map[string]interface{}{
|
||||
"id": newUser.Id,
|
||||
"username": newUser.Username,
|
||||
"email": newUser.Email,
|
||||
"nickname": newUser.Nickname,
|
||||
"avatar": newUser.Avatar,
|
||||
"id": newUser.Id,
|
||||
"username": newUser.Username,
|
||||
"email": newUser.Email,
|
||||
"nickname": newUser.Nickname,
|
||||
"avatar": newUser.Avatar,
|
||||
"tenant_id": newUser.TenantId,
|
||||
},
|
||||
}
|
||||
}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// UpdateUser 更新用户信息
|
||||
// UpdateUser 更新用户信息(支持租户模式)
|
||||
func (c *AuthController) UpdateUser() {
|
||||
// 定义接收更新数据的结构体
|
||||
var updateData struct {
|
||||
Id int `json:"id"` // 必须包含用户ID,用于定位要更新的用户
|
||||
Username string `json:"username"` // 可选更新字段
|
||||
Email string `json:"email"` // 可选更新字段
|
||||
Nickname string `json:"nickname"` // 可选更新字段
|
||||
Avatar string `json:"avatar"` // 可选更新字段
|
||||
Id int `json:"id"` // 必须包含用户ID,用于定位要更新的用户
|
||||
Username string `json:"username"` // 可选更新字段
|
||||
Email string `json:"email"` // 可选更新字段
|
||||
Nickname string `json:"nickname"` // 可选更新字段
|
||||
Avatar string `json:"avatar"` // 可选更新字段
|
||||
TenantId int `json:"tenant_id"` // 必须包含租户ID,用于验证用户归属
|
||||
}
|
||||
|
||||
// 解析请求体JSON
|
||||
@@ -296,7 +382,7 @@ func (c *AuthController) UpdateUser() {
|
||||
return
|
||||
}
|
||||
|
||||
// 校验必要参数(用户ID不能为空)
|
||||
// 校验必要参数(用户ID和租户ID不能为空)
|
||||
if updateData.Id == 0 {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
@@ -306,6 +392,15 @@ func (c *AuthController) UpdateUser() {
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
if updateData.TenantId <= 0 {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "租户ID不能为空",
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
// 调用模型层方法更新用户
|
||||
updatedUser, err := models.UpdateUser(
|
||||
@@ -314,6 +409,7 @@ func (c *AuthController) UpdateUser() {
|
||||
updateData.Email,
|
||||
updateData.Nickname,
|
||||
updateData.Avatar,
|
||||
updateData.TenantId,
|
||||
)
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
@@ -326,39 +422,44 @@ func (c *AuthController) UpdateUser() {
|
||||
"code": 0,
|
||||
"message": "用户更新成功",
|
||||
"data": map[string]interface{}{
|
||||
"id": updatedUser.Id,
|
||||
"username": updatedUser.Username,
|
||||
"email": updatedUser.Email,
|
||||
"nickname": updatedUser.Nickname,
|
||||
"avatar": updatedUser.Avatar,
|
||||
"id": updatedUser.Id,
|
||||
"username": updatedUser.Username,
|
||||
"email": updatedUser.Email,
|
||||
"nickname": updatedUser.Nickname,
|
||||
"avatar": updatedUser.Avatar,
|
||||
"tenant_id": updatedUser.TenantId,
|
||||
},
|
||||
}
|
||||
}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeleteUser 删除用户
|
||||
// DeleteUser 删除用户(支持租户模式)
|
||||
func (c *AuthController) DeleteUser() {
|
||||
// 获取要删除的用户ID(从URL参数或请求体中获取)
|
||||
// 获取要删除的用户ID和租户ID(从URL参数或请求体中获取)
|
||||
userId, err := c.GetInt("id") // 从URL参数获取,如 /user?id=1
|
||||
if err != nil {
|
||||
tenantId, _ := c.GetInt("tenant_id", 0)
|
||||
|
||||
if err != nil || tenantId == 0 {
|
||||
// 若URL参数获取失败,尝试从JSON请求体获取
|
||||
var deleteData struct {
|
||||
Id int `json:"id"`
|
||||
Id int `json:"id"`
|
||||
TenantId int `json:"tenant_id"`
|
||||
}
|
||||
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &deleteData); err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "用户ID获取失败: " + err.Error(),
|
||||
"message": "用户ID或租户ID获取失败: " + err.Error(),
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
userId = deleteData.Id
|
||||
tenantId = deleteData.TenantId
|
||||
}
|
||||
|
||||
// 校验用户ID
|
||||
// 校验用户ID和租户ID
|
||||
if userId <= 0 {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
@@ -368,9 +469,18 @@ func (c *AuthController) DeleteUser() {
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
if tenantId <= 0 {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "租户ID不能为空",
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
// 调用模型层方法删除用户
|
||||
err = models.DeleteUser(userId)
|
||||
err = models.DeleteUser(userId, tenantId)
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"server/models"
|
||||
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
beego "github.com/beego/beego/v2/server/web"
|
||||
)
|
||||
|
||||
@@ -111,6 +112,77 @@ func (c *MenuController) UpdateMenu() {
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// UpdateMenuStatus 更新菜单状态
|
||||
func (c *MenuController) UpdateMenuStatus() {
|
||||
id, err := c.GetInt(":id")
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"success": false,
|
||||
"message": "参数错误",
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
// 解析请求体获取 status 字段
|
||||
var req struct {
|
||||
Status int8 `json:"status"`
|
||||
}
|
||||
|
||||
// 尝试从请求体获取数据
|
||||
requestBody := c.Ctx.Input.RequestBody
|
||||
if len(requestBody) > 0 {
|
||||
// 有请求体,从 JSON 解析
|
||||
if err := json.Unmarshal(requestBody, &req); err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"success": false,
|
||||
"message": "请求参数错误",
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// 如果请求体为空,尝试从表单参数或查询参数获取
|
||||
statusValue, err := c.GetInt("status", -1)
|
||||
if err != nil || statusValue == -1 {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"success": false,
|
||||
"message": "请求参数错误:缺少 status 参数",
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
req.Status = int8(statusValue)
|
||||
}
|
||||
|
||||
// 先查询该菜单,确保存在
|
||||
o := orm.NewOrm()
|
||||
menu := models.Menu{Id: id}
|
||||
if err = o.Read(&menu); err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"success": false,
|
||||
"message": "菜单不存在",
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
// 更新状态
|
||||
menu.Status = req.Status
|
||||
if _, err := o.Update(&menu, "Status"); err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"success": false,
|
||||
"message": "更新菜单状态失败",
|
||||
}
|
||||
} else {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"success": true,
|
||||
"message": "菜单状态更新成功",
|
||||
}
|
||||
}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeleteMenu 删除菜单
|
||||
func (c *MenuController) DeleteMenu() {
|
||||
id, err := c.GetInt(":id")
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
|
||||
"server/models"
|
||||
|
||||
"github.com/beego/beego/v2/server/web"
|
||||
)
|
||||
|
||||
type TenantController struct {
|
||||
web.Controller
|
||||
}
|
||||
|
||||
// GetAllTenants 获取所有租户
|
||||
func (c *TenantController) GetAllTenants() {
|
||||
tenants, err := models.GetTenantList()
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"success": false,
|
||||
"message": "获取租户列表失败: " + err.Error(),
|
||||
}
|
||||
} else {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"success": true,
|
||||
"message": "获取租户列表成功",
|
||||
"data": tenants,
|
||||
}
|
||||
}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// CreateTenant 新增租户
|
||||
func (c *TenantController) CreateTenant() {
|
||||
var tenant models.Tenant
|
||||
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &tenant); err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"success": false,
|
||||
"message": "解析租户数据失败: " + err.Error(),
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
err := models.CreateTenant(&tenant)
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"success": false,
|
||||
"message": "创建租户失败: " + err.Error(),
|
||||
}
|
||||
} else {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"success": true,
|
||||
"message": "租户创建成功",
|
||||
"data": tenant,
|
||||
}
|
||||
}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// UpdateTenant 编辑租户
|
||||
func (c *TenantController) UpdateTenant() {
|
||||
idStr := c.Ctx.Input.Param(":id")
|
||||
id, err := strconv.Atoi(idStr)
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"success": false,
|
||||
"message": "无效的租户ID",
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
var data map[string]interface{}
|
||||
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &data); err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"success": false,
|
||||
"message": "解析更新数据失败: " + err.Error(),
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
err = models.UpdateTenant(id, data)
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"success": false,
|
||||
"message": "编辑租户失败: " + err.Error(),
|
||||
}
|
||||
} else {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"success": true,
|
||||
"message": "租户编辑成功",
|
||||
}
|
||||
}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeleteTenant 删除租户
|
||||
func (c *TenantController) DeleteTenant() {
|
||||
idStr := c.Ctx.Input.Param(":id")
|
||||
id, err := strconv.Atoi(idStr)
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"success": false,
|
||||
"message": "无效的租户ID",
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
err = models.DeleteTenant(id)
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"success": false,
|
||||
"message": "删除租户失败: " + err.Error(),
|
||||
}
|
||||
} else {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"success": true,
|
||||
"message": "租户删除成功",
|
||||
}
|
||||
}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AuditTenant 审核租户
|
||||
func (c *TenantController) AuditTenant() {
|
||||
idStr := c.Ctx.Input.Param(":id")
|
||||
id, err := strconv.Atoi(idStr)
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"success": false,
|
||||
"message": "无效的租户ID",
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
// 解析 JSON 请求体
|
||||
var auditData struct {
|
||||
AuditStatus string `json:"audit_status"`
|
||||
AuditComment string `json:"audit_comment"`
|
||||
AuditBy string `json:"audit_by"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &auditData); err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"success": false,
|
||||
"message": "解析审核数据失败: " + err.Error(),
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
if auditData.AuditStatus == "" {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"success": false,
|
||||
"message": "审核状态不能为空",
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
err = models.AuditTenant(id, auditData.AuditStatus, auditData.AuditComment, auditData.AuditBy)
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"success": false,
|
||||
"message": "审核租户失败: " + err.Error(),
|
||||
}
|
||||
} else {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"success": true,
|
||||
"message": "审核成功",
|
||||
}
|
||||
}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetTenantDetail 查看租户详情
|
||||
func (c *TenantController) GetTenantDetail() {
|
||||
idStr := c.Ctx.Input.Param(":id")
|
||||
id, err := strconv.Atoi(idStr)
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"success": false,
|
||||
"message": "无效的租户ID",
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
tenant, err := models.GetTenantById(id)
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"success": false,
|
||||
"message": "获取租户详情失败: " + err.Error(),
|
||||
}
|
||||
} else if tenant == nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"success": false,
|
||||
"message": "租户不存在",
|
||||
}
|
||||
} else {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"success": true,
|
||||
"message": "获取租户详情成功",
|
||||
"data": tenant,
|
||||
}
|
||||
}
|
||||
c.ServeJSON()
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
# 租户表数据库创建说明
|
||||
|
||||
## 创建步骤
|
||||
|
||||
### 方法1:执行独立的 SQL 文件(推荐)
|
||||
```bash
|
||||
# 在 MySQL 中执行
|
||||
mysql -u root -p your_database < server/database/yz_tenants.sql
|
||||
```
|
||||
|
||||
### 方法2:手动执行 SQL
|
||||
```sql
|
||||
-- 1. 进入 MySQL
|
||||
mysql -u root -p your_database
|
||||
|
||||
-- 2. 执行 SQL 脚本
|
||||
SOURCE server/database/yz_tenants.sql;
|
||||
```
|
||||
|
||||
### 方法3:在 MySQL 客户端中复制粘贴
|
||||
直接打开 `server/database/yz_tenants.sql` 文件,复制所有内容,在 MySQL 客户端中执行。
|
||||
|
||||
## 创建的表
|
||||
|
||||
**yz_tenants** - 租户管理表
|
||||
|
||||
### 表结构说明
|
||||
|
||||
#### 基本信息字段
|
||||
- `id` - 租户ID(主键,自增)
|
||||
- `name` - 租户名称(必填)
|
||||
- `code` - 租户编码(必填,唯一)
|
||||
- `owner` - 负责人(必填)
|
||||
- `phone` - 联系电话(可选)
|
||||
- `email` - 邮箱地址(可选)
|
||||
|
||||
#### 状态字段
|
||||
- `status` - 状态:`enabled`(启用)或 `disabled`(禁用)
|
||||
- `audit_status` - 审核状态:
|
||||
- `pending` - 待审核
|
||||
- `approved` - 已通过
|
||||
- `rejected` - 已拒绝
|
||||
|
||||
#### 审核信息字段
|
||||
- `audit_comment` - 审核意见(可选)
|
||||
- `audit_by` - 审核人(可选)
|
||||
- `audit_time` - 审核时间(可选)
|
||||
|
||||
#### 其他字段
|
||||
- `remark` - 备注(可选)
|
||||
- `create_time` - 创建时间(自动)
|
||||
- `update_time` - 更新时间(自动)
|
||||
- `create_by` - 创建人(可选)
|
||||
- `update_by` - 更新人(可选)
|
||||
|
||||
### 索引说明
|
||||
- `uk_code` - 租户编码唯一索引
|
||||
- `idx_name` - 租户名称索引
|
||||
- `idx_owner` - 负责人索引
|
||||
- `idx_status` - 状态索引
|
||||
- `idx_audit_status` - 审核状态索引
|
||||
- `idx_create_time` - 创建时间索引
|
||||
|
||||
## 测试数据
|
||||
|
||||
SQL 文件中包含 10 条测试数据,涵盖了以下场景:
|
||||
1. **默认租户** - 已通过审核的系统默认租户
|
||||
2. **示例租户A** - 已通过审核的演示租户
|
||||
3. **示例租户B** - 待审核的租户
|
||||
4. **新申请租户C** - 待审核的新申请租户
|
||||
5. **已拒绝租户D** - 被拒绝的租户示例
|
||||
6. **企业租户E** - 已通过审核的企业级租户
|
||||
7. **测试租户F** - 已通过审核的测试环境租户
|
||||
8. **禁用租户G** - 已通过审核但被禁用的租户
|
||||
9. **小公司租户H** - 待审核的小型公司
|
||||
10. **个人开发者I** - 已通过审核的个人开发者账户
|
||||
|
||||
## 验证创建
|
||||
|
||||
```sql
|
||||
-- 查看租户表结构
|
||||
DESC yz_tenants;
|
||||
|
||||
-- 查看所有租户数据
|
||||
SELECT * FROM yz_tenants;
|
||||
|
||||
-- 查看特定状态的租户
|
||||
SELECT * FROM yz_tenants WHERE audit_status = 'pending';
|
||||
SELECT * FROM yz_tenants WHERE status = 'enabled';
|
||||
|
||||
-- 查看租户统计
|
||||
SELECT
|
||||
audit_status,
|
||||
COUNT(*) as count
|
||||
FROM yz_tenants
|
||||
GROUP BY audit_status;
|
||||
```
|
||||
|
||||
## 如果表已存在
|
||||
|
||||
如果想重新创建表(会清空现有数据):
|
||||
```sql
|
||||
DROP TABLE IF EXISTS yz_tenants;
|
||||
```
|
||||
然后再执行创建脚本。
|
||||
|
||||
## 与其他表的关系
|
||||
|
||||
租户表是系统中重要的基础表:
|
||||
- `yz_files` 表中的 `tenant_id` 字段引用租户编码(VARCHAR 类型,不是外键)
|
||||
- 未来可能会在 `yz_users` 表中添加 `tenant_id` 字段来关联租户
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **租户编码唯一性**:`code` 字段设置了唯一索引,确保每个租户编码都是唯一的
|
||||
2. **审核流程**:新创建的租户默认 `audit_status` 为 `pending`(待审核)
|
||||
3. **状态管理**:租户可以同时拥有 `status` 和 `audit_status` 两个状态字段,分别控制启用状态和审核状态
|
||||
4. **时间字段**:`create_time` 和 `update_time` 会自动管理,无需手动设置
|
||||
|
||||
@@ -99,6 +99,44 @@ CREATE TABLE IF NOT EXISTS yz_program_info (
|
||||
CONSTRAINT yz_fk_program_category FOREIGN KEY (category_id) REFERENCES yz_program_category (category_id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='程序信息表';
|
||||
|
||||
-- 检查并创建租户表(如果不存在)
|
||||
CREATE TABLE IF NOT EXISTS yz_tenants (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '租户ID',
|
||||
|
||||
-- 基本信息
|
||||
name VARCHAR(100) NOT NULL COMMENT '租户名称',
|
||||
code VARCHAR(50) NOT NULL COMMENT '租户编码(唯一)',
|
||||
owner VARCHAR(50) NOT NULL COMMENT '负责人',
|
||||
phone VARCHAR(20) DEFAULT NULL COMMENT '联系电话',
|
||||
email VARCHAR(100) DEFAULT NULL COMMENT '邮箱地址',
|
||||
|
||||
-- 状态信息
|
||||
status VARCHAR(20) DEFAULT 'enabled' COMMENT '状态:enabled-启用,disabled-禁用',
|
||||
audit_status VARCHAR(20) DEFAULT 'pending' COMMENT '审核状态:pending-待审核,approved-已通过,rejected-已拒绝',
|
||||
|
||||
-- 审核信息
|
||||
audit_comment TEXT DEFAULT NULL COMMENT '审核意见',
|
||||
audit_by VARCHAR(50) DEFAULT NULL COMMENT '审核人',
|
||||
audit_time DATETIME DEFAULT NULL COMMENT '审核时间',
|
||||
|
||||
-- 其他信息
|
||||
remark TEXT DEFAULT NULL COMMENT '备注',
|
||||
|
||||
-- 时间戳
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
create_by VARCHAR(50) DEFAULT NULL COMMENT '创建人',
|
||||
update_by VARCHAR(50) DEFAULT NULL COMMENT '更新人',
|
||||
|
||||
-- 索引
|
||||
UNIQUE KEY uk_code (code),
|
||||
INDEX idx_name (name),
|
||||
INDEX idx_owner (owner),
|
||||
INDEX idx_status (status),
|
||||
INDEX idx_audit_status (audit_status),
|
||||
INDEX idx_create_time (create_time)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='租户表';
|
||||
|
||||
-- 检查并创建文件表(如果不存在)
|
||||
CREATE TABLE IF NOT EXISTS yz_files (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '文件ID',
|
||||
|
||||
@@ -107,7 +107,49 @@ CREATE TABLE yz_program_info (
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='程序信息表';
|
||||
|
||||
-- =============================================
|
||||
-- 4. 文件管理相关表
|
||||
-- 4. 租户管理相关表
|
||||
-- =============================================
|
||||
|
||||
-- 创建租户表
|
||||
CREATE TABLE yz_tenants (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '租户ID',
|
||||
|
||||
-- 基本信息
|
||||
name VARCHAR(100) NOT NULL COMMENT '租户名称',
|
||||
code VARCHAR(50) NOT NULL COMMENT '租户编码(唯一)',
|
||||
owner VARCHAR(50) NOT NULL COMMENT '负责人',
|
||||
phone VARCHAR(20) DEFAULT NULL COMMENT '联系电话',
|
||||
email VARCHAR(100) DEFAULT NULL COMMENT '邮箱地址',
|
||||
|
||||
-- 状态信息
|
||||
status VARCHAR(20) DEFAULT 'enabled' COMMENT '状态:enabled-启用,disabled-禁用',
|
||||
audit_status VARCHAR(20) DEFAULT 'pending' COMMENT '审核状态:pending-待审核,approved-已通过,rejected-已拒绝',
|
||||
|
||||
-- 审核信息
|
||||
audit_comment TEXT DEFAULT NULL COMMENT '审核意见',
|
||||
audit_by VARCHAR(50) DEFAULT NULL COMMENT '审核人',
|
||||
audit_time DATETIME DEFAULT NULL COMMENT '审核时间',
|
||||
|
||||
-- 其他信息
|
||||
remark TEXT DEFAULT NULL COMMENT '备注',
|
||||
|
||||
-- 时间戳
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
create_by VARCHAR(50) DEFAULT NULL COMMENT '创建人',
|
||||
update_by VARCHAR(50) DEFAULT NULL COMMENT '更新人',
|
||||
|
||||
-- 索引
|
||||
UNIQUE KEY uk_code (code),
|
||||
INDEX idx_name (name),
|
||||
INDEX idx_owner (owner),
|
||||
INDEX idx_status (status),
|
||||
INDEX idx_audit_status (audit_status),
|
||||
INDEX idx_create_time (create_time)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='租户表';
|
||||
|
||||
-- =============================================
|
||||
-- 5. 文件管理相关表
|
||||
-- =============================================
|
||||
|
||||
-- 创建文件表
|
||||
@@ -146,9 +188,15 @@ CREATE TABLE yz_files (
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='文件表';
|
||||
|
||||
-- =============================================
|
||||
-- 5. 插入初始数据
|
||||
-- 6. 插入初始数据
|
||||
-- =============================================
|
||||
|
||||
-- 插入默认租户数据
|
||||
INSERT INTO yz_tenants (name, code, owner, phone, email, status, audit_status, audit_comment, audit_by, audit_time, remark, create_by) VALUES
|
||||
('默认租户', 'default', 'admin', '13800138000', 'admin@yunzer.com', 'enabled', 'approved', '系统默认租户,自动通过审核', 'system', NOW(), '系统默认租户,用于初始化数据', 'system'),
|
||||
('示例租户A', 'demo-a', '张三', '13900139000', 'zhangsan@demo.com', 'enabled', 'approved', '资料完整,审核通过', 'admin', DATE_SUB(NOW(), INTERVAL 30 DAY), '演示租户A,用于展示功能', 'admin'),
|
||||
('示例租户B', 'demo-b', '李四', '13700137000', 'lisi@demo.com', 'enabled', 'pending', NULL, NULL, NULL, '待审核租户,资料已提交', 'admin');
|
||||
|
||||
-- 插入默认管理员用户
|
||||
-- 注意:实际使用时需要生成真实的加密密码和盐值
|
||||
INSERT INTO yz_users (username, password, salt, email, nickname, role, status, create_by) VALUES
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
-- 创建租户表
|
||||
-- 创建时间: 2025
|
||||
-- 描述: 云泽系统租户管理表
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
-- 检查并创建租户表(如果不存在)
|
||||
CREATE TABLE IF NOT EXISTS yz_tenants (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '租户ID',
|
||||
|
||||
-- 基本信息
|
||||
name VARCHAR(100) NOT NULL COMMENT '租户名称',
|
||||
code VARCHAR(50) NOT NULL COMMENT '租户编码(唯一)',
|
||||
owner VARCHAR(50) NOT NULL COMMENT '负责人',
|
||||
phone VARCHAR(20) DEFAULT NULL COMMENT '联系电话',
|
||||
email VARCHAR(100) DEFAULT NULL COMMENT '邮箱地址',
|
||||
|
||||
-- 状态信息
|
||||
status VARCHAR(20) DEFAULT 'enabled' COMMENT '状态:enabled-启用,disabled-禁用',
|
||||
audit_status VARCHAR(20) DEFAULT 'pending' COMMENT '审核状态:pending-待审核,approved-已通过,rejected-已拒绝',
|
||||
|
||||
-- 审核信息
|
||||
audit_comment TEXT DEFAULT NULL COMMENT '审核意见',
|
||||
audit_by VARCHAR(50) DEFAULT NULL COMMENT '审核人',
|
||||
audit_time DATETIME DEFAULT NULL COMMENT '审核时间',
|
||||
|
||||
-- 其他信息
|
||||
remark TEXT DEFAULT NULL COMMENT '备注',
|
||||
|
||||
-- 时间戳
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
create_by VARCHAR(50) DEFAULT NULL COMMENT '创建人',
|
||||
update_by VARCHAR(50) DEFAULT NULL COMMENT '更新人',
|
||||
|
||||
-- 索引
|
||||
UNIQUE KEY uk_code (code),
|
||||
INDEX idx_name (name),
|
||||
INDEX idx_owner (owner),
|
||||
INDEX idx_status (status),
|
||||
INDEX idx_audit_status (audit_status),
|
||||
INDEX idx_create_time (create_time)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='租户表';
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
-- =============================================
|
||||
-- 插入测试数据
|
||||
-- =============================================
|
||||
|
||||
-- 清空现有测试数据(可选,注释掉以保留现有数据)
|
||||
-- DELETE FROM yz_tenants WHERE id > 0;
|
||||
|
||||
-- 插入测试租户数据
|
||||
INSERT INTO yz_tenants (
|
||||
name,
|
||||
code,
|
||||
owner,
|
||||
phone,
|
||||
email,
|
||||
status,
|
||||
audit_status,
|
||||
audit_comment,
|
||||
audit_by,
|
||||
audit_time,
|
||||
remark,
|
||||
create_by
|
||||
) VALUES
|
||||
-- 默认租户(已通过审核)
|
||||
('默认租户', 'default', 'admin', '13800138000', 'admin@yunzer.com', 'enabled', 'approved', '系统默认租户,自动通过审核', 'system', NOW(), '系统默认租户,用于初始化数据', 'system'),
|
||||
|
||||
-- 示例租户A(已通过审核)
|
||||
('示例租户A', 'demo-a', '张三', '13900139000', 'zhangsan@demo.com', 'enabled', 'approved', '资料完整,审核通过', 'admin', DATE_SUB(NOW(), INTERVAL 30 DAY), '演示租户A,用于展示功能', 'admin'),
|
||||
|
||||
-- 示例租户B(待审核)
|
||||
('示例租户B', 'demo-b', '李四', '13700137000', 'lisi@demo.com', 'enabled', 'pending', NULL, NULL, NULL, '待审核租户,资料已提交', 'admin'),
|
||||
|
||||
-- 新申请租户C(待审核)
|
||||
('新申请租户C', 'new-tenant-c', '王五', '13600136000', 'wangwu@new.com', 'enabled', 'pending', NULL, NULL, NULL, '新申请的租户,等待审核', 'admin'),
|
||||
|
||||
-- 已拒绝租户D
|
||||
('已拒绝租户D', 'rejected-tenant', '赵六', '13500135000', 'zhaoliu@reject.com', 'disabled', 'rejected', '申请资料不完整,缺少必要信息', 'admin', DATE_SUB(NOW(), INTERVAL 10 DAY), '申请被拒绝的租户示例', 'admin'),
|
||||
|
||||
-- 企业租户E(已通过)
|
||||
('企业租户E', 'enterprise-e', '陈七', '13400134000', 'chenqi@enterprise.com', 'enabled', 'approved', '企业级用户,认证通过', 'admin', DATE_SUB(NOW(), INTERVAL 15 DAY), '大型企业客户租户', 'admin'),
|
||||
|
||||
-- 测试租户F(已通过)
|
||||
('测试租户F', 'test-f', '刘八', '13300133000', 'liuba@test.com', 'enabled', 'approved', '测试环境使用,已通过', 'admin', DATE_SUB(NOW(), INTERVAL 5 DAY), '测试环境租户', 'admin'),
|
||||
|
||||
-- 禁用租户G
|
||||
('禁用租户G', 'disabled-g', '周九', '13200132000', 'zhoujiu@disabled.com', 'disabled', 'approved', '已通过审核但被禁用', 'admin', DATE_SUB(NOW(), INTERVAL 20 DAY), '已禁用的租户示例', 'admin'),
|
||||
|
||||
-- 小公司租户H(待审核)
|
||||
('小公司租户H', 'small-h', '吴十', '13100131000', 'wushi@small.com', 'enabled', 'pending', NULL, NULL, NULL, '小型公司申请,等待审核', 'admin'),
|
||||
|
||||
-- 个人开发者租户I(已通过)
|
||||
('个人开发者I', 'developer-i', '郑十一', '13000130000', 'zhengshiyi@dev.com', 'enabled', 'approved', '个人开发者账户,审核通过', 'admin', DATE_SUB(NOW(), INTERVAL 8 DAY), '个人开发者租户', 'admin');
|
||||
|
||||
-- 查询验证
|
||||
SELECT 'Tenants table created and test data inserted successfully!' as message;
|
||||
SELECT COUNT(*) as total_tenants FROM yz_tenants;
|
||||
SELECT
|
||||
id,
|
||||
name,
|
||||
code,
|
||||
status,
|
||||
audit_status,
|
||||
create_time
|
||||
FROM yz_tenants
|
||||
ORDER BY id;
|
||||
+3
-2
@@ -14,9 +14,10 @@ func main() {
|
||||
models.Init()
|
||||
|
||||
// CORS配置已移至router.go中统一管理
|
||||
// 确保请求体被正确读取
|
||||
// 确保请求体被正确读取(包括 POST、PUT、PATCH)
|
||||
beego.InsertFilter("*", beego.BeforeRouter, func(ctx *context.Context) {
|
||||
if ctx.Input.Method() == "PUT" || ctx.Input.Method() == "POST" {
|
||||
method := ctx.Input.Method()
|
||||
if method == "PUT" || method == "POST" || method == "PATCH" {
|
||||
ctx.Input.CopyBody(1024 * 1024) // 1MB 缓冲区
|
||||
}
|
||||
})
|
||||
|
||||
@@ -14,18 +14,20 @@ var jwtSecret = []byte("yunzer_jwt_secret_key") // 在实际应用中应从配
|
||||
type Claims struct {
|
||||
UserID int `json:"user_id"`
|
||||
Username string `json:"username"`
|
||||
TenantId int `json:"tenant_id"` // 租户ID
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
// GenerateToken 生成JWT token
|
||||
func GenerateToken(userID int, username string) (string, error) {
|
||||
func GenerateToken(userID int, username string, tenantId int) (string, error) {
|
||||
// 设置token过期时间
|
||||
expirationTime := time.Now().Add(24 * time.Hour) // 24小时后过期
|
||||
|
||||
// 创建claims
|
||||
claims := &Claims{
|
||||
UserID: userID,
|
||||
UserID: userID,
|
||||
Username: username,
|
||||
TenantId: tenantId,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
ExpiresAt: jwt.NewNumericDate(expirationTime),
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||
@@ -63,4 +65,4 @@ func ParseToken(tokenString string) (*Claims, error) {
|
||||
}
|
||||
|
||||
return claims, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +69,18 @@ func UpdateMenu(menu *Menu) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateMenuStatus 更新菜单状态
|
||||
func UpdateMenuStatus(id int, status int8) error {
|
||||
o := orm.NewOrm()
|
||||
menu := Menu{Id: id}
|
||||
if err := o.Read(&menu); err != nil {
|
||||
return err
|
||||
}
|
||||
menu.Status = status
|
||||
_, err := o.Update(&menu, "Status")
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteMenu 删除菜单
|
||||
func DeleteMenu(id int) error {
|
||||
o := orm.NewOrm()
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
)
|
||||
|
||||
type Tenant struct {
|
||||
Id int `orm:"pk;auto" json:"id"`
|
||||
Name string `orm:"size(100)" json:"name"`
|
||||
Code string `orm:"size(50);unique" json:"code"`
|
||||
Owner string `orm:"size(50)" json:"owner"`
|
||||
Phone string `orm:"size(20);null" json:"phone"`
|
||||
Email string `orm:"size(100);null" json:"email"`
|
||||
Status string `orm:"size(20);default(enabled)" json:"status"` // enabled, disabled
|
||||
AuditStatus string `orm:"size(20);default(pending)" json:"audit_status"` // pending, approved, rejected
|
||||
AuditComment string `orm:"type(text);null" json:"audit_comment"`
|
||||
AuditBy string `orm:"size(50);null" json:"audit_by"`
|
||||
AuditTime *time.Time `orm:"null;type(datetime)" json:"audit_time"`
|
||||
Remark string `orm:"type(text);null" json:"remark"`
|
||||
CreateTime time.Time `orm:"auto_now_add;type(datetime)" json:"create_time"`
|
||||
UpdateTime time.Time `orm:"auto_now;type(datetime)" json:"update_time"`
|
||||
DeleteTime *time.Time `orm:"null;type(datetime)" json:"delete_time"`
|
||||
CreateBy string `orm:"size(50);null" json:"create_by"`
|
||||
UpdateBy string `orm:"size(50);null" json:"update_by"`
|
||||
}
|
||||
|
||||
// TableName 设置表名
|
||||
func (t *Tenant) TableName() string {
|
||||
return "yz_tenants"
|
||||
}
|
||||
|
||||
func init() {
|
||||
orm.RegisterModel(new(Tenant))
|
||||
}
|
||||
|
||||
// GetTenantList 获取所有租户(软删除:只返回未删除的)
|
||||
func GetTenantList() ([]Tenant, error) {
|
||||
o := orm.NewOrm()
|
||||
var tenants []Tenant
|
||||
// 使用原生 SQL 查询 delete_time IS NULL 的记录(更可靠)
|
||||
_, err := o.Raw("SELECT * FROM yz_tenants WHERE delete_time IS NULL ORDER BY id DESC").QueryRows(&tenants)
|
||||
return tenants, err
|
||||
}
|
||||
|
||||
// CreateTenant 新建租户
|
||||
func CreateTenant(tenant *Tenant) error {
|
||||
o := orm.NewOrm()
|
||||
_, err := o.Insert(tenant)
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateTenant 编辑租户(只能编辑未删除的)
|
||||
func UpdateTenant(id int, data map[string]interface{}) error {
|
||||
o := orm.NewOrm()
|
||||
// 先检查租户是否存在且未删除
|
||||
tenant := Tenant{}
|
||||
err := o.Raw("SELECT * FROM yz_tenants WHERE id = ? AND delete_time IS NULL", id).QueryRow(&tenant)
|
||||
if err == orm.ErrNoRows {
|
||||
return errors.New("租户不存在或已被删除")
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 更新记录
|
||||
_, err = o.QueryTable(new(Tenant)).Filter("Id", id).Update(data)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteTenant 软删除租户(设置 delete_time)
|
||||
func DeleteTenant(id int) error {
|
||||
o := orm.NewOrm()
|
||||
deleteTime := time.Now()
|
||||
_, err := o.QueryTable(new(Tenant)).Filter("Id", id).Update(orm.Params{"delete_time": deleteTime})
|
||||
return err
|
||||
}
|
||||
|
||||
// AuditTenant 审核租户(只能审核未删除的)
|
||||
func AuditTenant(id int, auditStatus, auditComment, auditBy string) error {
|
||||
o := orm.NewOrm()
|
||||
tenant := Tenant{}
|
||||
// 先检查租户是否存在且未删除
|
||||
err := o.Raw("SELECT * FROM yz_tenants WHERE id = ? AND delete_time IS NULL", id).QueryRow(&tenant)
|
||||
if err == orm.ErrNoRows {
|
||||
return errors.New("租户不存在或已被删除")
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
now := time.Now()
|
||||
tenant.AuditStatus = auditStatus
|
||||
tenant.AuditComment = auditComment
|
||||
tenant.AuditBy = auditBy
|
||||
tenant.AuditTime = &now
|
||||
|
||||
_, err = o.Update(&tenant, "AuditStatus", "AuditComment", "AuditBy", "AuditTime")
|
||||
return err
|
||||
}
|
||||
|
||||
// GetTenantById 根据ID获取租户详情(只返回未删除的)
|
||||
func GetTenantById(id int) (*Tenant, error) {
|
||||
o := orm.NewOrm()
|
||||
tenant := Tenant{}
|
||||
// 使用原生 SQL 查询,只返回未删除的记录
|
||||
err := o.Raw("SELECT * FROM yz_tenants WHERE id = ? AND delete_time IS NULL", id).QueryRow(&tenant)
|
||||
if err == orm.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
return &tenant, err
|
||||
}
|
||||
|
||||
// GetTenantByName 根据名称获取租户详情(只返回未删除的)
|
||||
func GetTenantByName(name string) (*Tenant, error) {
|
||||
o := orm.NewOrm()
|
||||
tenant := Tenant{}
|
||||
// 使用原生 SQL 查询,只返回未删除的记录
|
||||
err := o.Raw("SELECT * FROM yz_tenants WHERE name = ? AND delete_time IS NULL", name).QueryRow(&tenant)
|
||||
if err == orm.ErrNoRows {
|
||||
return nil, errors.New("租户不存在")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &tenant, nil
|
||||
}
|
||||
+108
-42
@@ -16,7 +16,8 @@ import (
|
||||
// User 用户模型,增加Salt字段存储每个用户的唯一盐值
|
||||
type User struct {
|
||||
Id int `orm:"auto"`
|
||||
Username string `orm:"unique"`
|
||||
TenantId int `orm:"column(tenant_id);default(0)" json:"tenant_id"` // 租户ID
|
||||
Username string // 用户名不再全局唯一,而是在租户内唯一(tenant_id + username 的组合唯一)
|
||||
Password string // 存储加密后的密码
|
||||
Salt string // 存储该用户的唯一盐值
|
||||
Email string
|
||||
@@ -66,15 +67,14 @@ func verifyPassword(password, salt, storedHash string) bool {
|
||||
return hash == storedHash
|
||||
}
|
||||
|
||||
// ResetPassword 重置用户密码
|
||||
func ResetPassword(username, superPassword string) error {
|
||||
// ResetPassword 重置用户密码(支持租户模式)
|
||||
func ResetPassword(username, superPassword string, tenantId int) error {
|
||||
if superPassword != "Lzq920103" {
|
||||
return fmt.Errorf("超级密码错误")
|
||||
}
|
||||
|
||||
o := orm.NewOrm()
|
||||
user := &User{Username: username}
|
||||
err := o.Read(user, "Username")
|
||||
user, err := GetUserByUsername(username, tenantId)
|
||||
if err != nil {
|
||||
return fmt.Errorf("用户不存在: %v", err)
|
||||
}
|
||||
@@ -102,9 +102,9 @@ func ResetPassword(username, superPassword string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ChangePassword 修改用户密码
|
||||
func ChangePassword(username, oldPassword, newPassword string) error {
|
||||
user, err := GetUserByUsername(username)
|
||||
// ChangePassword 修改用户密码(支持租户模式)
|
||||
func ChangePassword(username, oldPassword, newPassword string, tenantId int) error {
|
||||
user, err := GetUserByUsername(username, tenantId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -124,22 +124,32 @@ func ChangePassword(username, oldPassword, newPassword string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// FindAllUsers 获取所有用户
|
||||
func FindAllUsers() []*User {
|
||||
// FindAllUsers 获取所有用户(支持按租户过滤)
|
||||
func FindAllUsers(tenantId int) []*User {
|
||||
o := orm.NewOrm()
|
||||
var users []*User
|
||||
_, err := o.QueryTable("yz_users").All(&users)
|
||||
if err != nil {
|
||||
return []*User{}
|
||||
if tenantId > 0 {
|
||||
// 按租户ID查询
|
||||
_, err := o.Raw("SELECT * FROM yz_users WHERE tenant_id = ?", tenantId).QueryRows(&users)
|
||||
if err != nil {
|
||||
return []*User{}
|
||||
}
|
||||
} else {
|
||||
// 查询所有用户
|
||||
_, err := o.QueryTable("yz_users").All(&users)
|
||||
if err != nil {
|
||||
return []*User{}
|
||||
}
|
||||
}
|
||||
return users
|
||||
}
|
||||
|
||||
// GetUserByUsername 根据用户名获取用户
|
||||
func GetUserByUsername(username string) (*User, error) {
|
||||
// GetUserByUsername 根据用户名获取用户(支持租户隔离)
|
||||
func GetUserByUsername(username string, tenantId int) (*User, error) {
|
||||
o := orm.NewOrm()
|
||||
user := &User{Username: username}
|
||||
err := o.Read(user, "Username")
|
||||
user := &User{}
|
||||
// 使用原生 SQL 查询,考虑租户ID
|
||||
err := o.Raw("SELECT * FROM yz_users WHERE username = ? AND tenant_id = ?", username, tenantId).QueryRow(user)
|
||||
if err == orm.ErrNoRows {
|
||||
return nil, errors.New("用户不存在")
|
||||
}
|
||||
@@ -149,24 +159,74 @@ func GetUserByUsername(username string) (*User, error) {
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// ValidateUser 验证用户登录信息
|
||||
func ValidateUser(username, password string) (*User, error) {
|
||||
user, err := GetUserByUsername(username)
|
||||
// ValidateUser 验证用户登录信息(支持租户模式,根据租户名称)
|
||||
// 先验证租户是否存在且有效,再验证租户下的用户
|
||||
func ValidateUser(username, password string, tenantName string) (*User, error) {
|
||||
o := orm.NewOrm()
|
||||
|
||||
// 1. 根据租户名称查询租户(只查询未删除的)
|
||||
var tenant struct {
|
||||
Id int
|
||||
Status string
|
||||
DeleteTime interface{} // 使用 interface{} 来处理 NULL 值
|
||||
}
|
||||
err := o.Raw("SELECT id, status, delete_time FROM yz_tenants WHERE name = ? AND delete_time IS NULL", tenantName).QueryRow(&tenant)
|
||||
if err == orm.ErrNoRows {
|
||||
// 租户不存在(数据库中根本没有这个名称)
|
||||
return nil, errors.New("租户不存在")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("查询租户失败: %v", err)
|
||||
}
|
||||
|
||||
// 检查租户是否被删除(软删除)
|
||||
if tenant.DeleteTime != nil {
|
||||
// delete_time 不为 NULL,说明已被删除
|
||||
return nil, errors.New("租户已被删除")
|
||||
}
|
||||
|
||||
// 检查租户状态
|
||||
if tenant.Status == "disabled" {
|
||||
return nil, errors.New("租户已被禁用")
|
||||
}
|
||||
|
||||
if tenant.Status != "enabled" {
|
||||
return nil, fmt.Errorf("租户状态异常: %s", tenant.Status)
|
||||
}
|
||||
|
||||
tenantId := tenant.Id
|
||||
|
||||
// 2. 获取租户下的用户
|
||||
user, err := GetUserByUsername(username, tenantId)
|
||||
if err != nil {
|
||||
// 用户不存在或查询失败
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 3. 验证密码
|
||||
if verifyPassword(password, user.Salt, user.Password) {
|
||||
return user, nil
|
||||
}
|
||||
return nil, errors.New("密码不正确")
|
||||
}
|
||||
|
||||
// AddUser 向数据库添加新用户(模型层核心方法)
|
||||
func AddUser(username, password, email, nickname, avatar string) (*User, error) {
|
||||
// 1. 检查用户是否已存在(避免用户名重复)
|
||||
existingUser, err := GetUserByUsername(username)
|
||||
// AddUser 向数据库添加新用户(模型层核心方法,支持租户模式)
|
||||
func AddUser(username, password, email, nickname, avatar string, tenantId int) (*User, error) {
|
||||
// 1. 验证租户是否存在且有效
|
||||
o := orm.NewOrm()
|
||||
var tenantExists bool
|
||||
err := o.Raw("SELECT EXISTS(SELECT 1 FROM yz_tenants WHERE id = ? AND delete_time IS NULL AND status = 'enabled')", tenantId).QueryRow(&tenantExists)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("验证租户失败: %v", err)
|
||||
}
|
||||
if !tenantExists {
|
||||
return nil, fmt.Errorf("租户不存在或已被禁用")
|
||||
}
|
||||
|
||||
// 2. 检查该租户下用户是否已存在(避免用户名重复,但不同租户可以有相同的用户名)
|
||||
existingUser, err := GetUserByUsername(username, tenantId)
|
||||
if err == nil && existingUser != nil {
|
||||
return nil, fmt.Errorf("用户名已存在")
|
||||
return nil, fmt.Errorf("该租户下用户名已存在")
|
||||
}
|
||||
if err != nil && err.Error() != "用户不存在" { // 排除"用户不存在"的正常错误
|
||||
return nil, fmt.Errorf("查询用户失败: %v", err)
|
||||
@@ -186,6 +246,7 @@ func AddUser(username, password, email, nickname, avatar string) (*User, error)
|
||||
|
||||
// 4. 构建用户对象
|
||||
user := &User{
|
||||
TenantId: tenantId,
|
||||
Username: username,
|
||||
Password: hashedPassword, // 存储加密后的密码
|
||||
Salt: salt, // 存储盐值(用于后续验证)
|
||||
@@ -194,8 +255,7 @@ func AddUser(username, password, email, nickname, avatar string) (*User, error)
|
||||
Avatar: avatar,
|
||||
}
|
||||
|
||||
// 5. 插入数据库
|
||||
o := orm.NewOrm()
|
||||
// 5. 插入数据库(使用之前定义的 o)
|
||||
_, err = o.Insert(user)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("数据库插入失败: %v", err)
|
||||
@@ -205,22 +265,25 @@ func AddUser(username, password, email, nickname, avatar string) (*User, error)
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// UpdateUser 更新用户信息(模型层方法)
|
||||
func UpdateUser(id int, username, email, nickname, avatar string) (*User, error) {
|
||||
// 1. 根据ID查询用户是否存在
|
||||
// UpdateUser 更新用户信息(模型层方法,支持租户模式)
|
||||
func UpdateUser(id int, username, email, nickname, avatar string, tenantId int) (*User, error) {
|
||||
// 1. 根据ID和租户ID查询用户是否存在(确保只能更新自己租户下的用户)
|
||||
o := orm.NewOrm()
|
||||
user := &User{Id: id}
|
||||
err := o.Read(user)
|
||||
user := &User{}
|
||||
err := o.Raw("SELECT * FROM yz_users WHERE id = ? AND tenant_id = ?", id, tenantId).QueryRow(user)
|
||||
if err == orm.ErrNoRows {
|
||||
return nil, fmt.Errorf("用户不存在或不属于该租户")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("用户不存在: %v", err)
|
||||
return nil, fmt.Errorf("查询用户失败: %v", err)
|
||||
}
|
||||
|
||||
// 2. 仅更新非空字段(避免覆盖原有值)
|
||||
if username != "" {
|
||||
// 若更新用户名,需检查新用户名是否已被占用
|
||||
existingUser, _ := GetUserByUsername(username)
|
||||
// 若更新用户名,需检查同一租户下新用户名是否已被占用
|
||||
existingUser, _ := GetUserByUsername(username, tenantId)
|
||||
if existingUser != nil && existingUser.Id != id {
|
||||
return nil, fmt.Errorf("用户名已被占用")
|
||||
return nil, fmt.Errorf("该租户下用户名已被占用")
|
||||
}
|
||||
user.Username = username
|
||||
}
|
||||
@@ -243,14 +306,17 @@ func UpdateUser(id int, username, email, nickname, avatar string) (*User, error)
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// DeleteUser 根据ID删除用户(模型层方法)
|
||||
func DeleteUser(id int) error {
|
||||
// DeleteUser 根据ID删除用户(模型层方法,支持租户模式)
|
||||
func DeleteUser(id int, tenantId int) error {
|
||||
o := orm.NewOrm()
|
||||
// 先查询用户是否存在
|
||||
user := &User{Id: id}
|
||||
err := o.Read(user)
|
||||
// 先查询用户是否存在且属于指定租户
|
||||
user := &User{}
|
||||
err := o.Raw("SELECT * FROM yz_users WHERE id = ? AND tenant_id = ?", id, tenantId).QueryRow(user)
|
||||
if err == orm.ErrNoRows {
|
||||
return fmt.Errorf("用户不存在或不属于该租户")
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("用户不存在: %v", err)
|
||||
return fmt.Errorf("查询用户失败: %v", err)
|
||||
}
|
||||
|
||||
// 执行删除操作
|
||||
|
||||
@@ -14,7 +14,7 @@ func init() {
|
||||
beego.InsertFilter("*", beego.BeforeRouter, func(ctx *context.Context) {
|
||||
// 设置CORS头
|
||||
ctx.Output.Header("Access-Control-Allow-Origin", "*")
|
||||
ctx.Output.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
||||
ctx.Output.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS")
|
||||
ctx.Output.Header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization")
|
||||
ctx.Output.Header("Access-Control-Allow-Credentials", "true")
|
||||
ctx.Output.Header("Access-Control-Max-Age", "86400")
|
||||
@@ -72,6 +72,7 @@ func init() {
|
||||
beego.Router("/api/menu", &controllers.MenuController{}, "post:CreateMenu")
|
||||
beego.Router("/api/menu/:id", &controllers.MenuController{}, "put:UpdateMenu")
|
||||
beego.Router("/api/menu/:id", &controllers.MenuController{}, "delete:DeleteMenu")
|
||||
beego.Router("/api/menu/status/:id", &controllers.MenuController{}, "patch:UpdateMenuStatus")
|
||||
|
||||
// 程序分类路由 - 自动映射到 /api/programcategory/*
|
||||
beego.AutoRouter(&controllers.ProgramCategoryController{})
|
||||
@@ -100,6 +101,14 @@ func init() {
|
||||
beego.Router("/api/knowledge/category/add", &controllers.KnowledgeController{}, "post:AddCategory")
|
||||
beego.Router("/api/knowledge/tag/add", &controllers.KnowledgeController{}, "post:AddTag")
|
||||
|
||||
//租户相关路由
|
||||
beego.Router("/api/tenant/list", &controllers.TenantController{}, "get:GetAllTenants")
|
||||
beego.Router("/api/tenant", &controllers.TenantController{}, "post:CreateTenant")
|
||||
beego.Router("/api/tenant/:id", &controllers.TenantController{}, "put:UpdateTenant")
|
||||
beego.Router("/api/tenant/:id", &controllers.TenantController{}, "delete:DeleteTenant")
|
||||
beego.Router("/api/tenant/:id/audit", &controllers.TenantController{}, "post:AuditTenant")
|
||||
beego.Router("/api/tenant/:id", &controllers.TenantController{}, "get:GetTenantDetail")
|
||||
|
||||
// 手动配置特殊路由(无法通过自动路由处理的)
|
||||
beego.Router("/api/allmenu", &controllers.MenuController{}, "get:GetAllMenus")
|
||||
beego.Router("/api/program-categories/public", &controllers.ProgramCategoryController{}, "get:GetProgramCategoriesPublic")
|
||||
|
||||
Reference in New Issue
Block a user