完成租户登录

This commit is contained in:
2025-10-29 23:07:53 +08:00
parent b9938b4c0d
commit ddf90424ba
43 changed files with 7761 additions and 474 deletions
+168 -58
View File
@@ -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,
+72
View File
@@ -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")
+208
View File
@@ -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()
}
+119
View File
@@ -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` 会自动管理,无需手动设置
+38
View File
@@ -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',
+50 -2
View File
@@ -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
+111
View File
@@ -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
View File
@@ -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 缓冲区
}
})
+5 -3
View File
@@ -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
}
}
+12
View File
@@ -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()
+127
View File
@@ -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
View File
@@ -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)
}
// 执行删除操作
+10 -1
View File
@@ -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")