更新密码管理
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
const resource = '/backend/passwordStore'
|
||||
export const getPasswordStoreList = (params) => request({ url: `${resource}/list`, method: 'get', params })
|
||||
export const getPasswordStoreDetail = (id) => request({ url: `${resource}/detail/${id}`, method: 'get' })
|
||||
export const createPasswordStore = (data) => request({ url: `${resource}/create`, method: 'post', data })
|
||||
export const updatePasswordStore = (id, data) => request({ url: `${resource}/update/${id}`, method: 'post', data })
|
||||
export const deletePasswordStore = (id) => request({ url: `${resource}/delete/${id}`, method: 'delete' })
|
||||
@@ -76,6 +76,12 @@ const staticMainChildren = [
|
||||
component: () => import("@/views/apps/oa/organization/index.vue"),
|
||||
meta: { requiresAuth: true, title: "组织架构", modulePath: "/apps/oa" }
|
||||
},
|
||||
{
|
||||
path: "/tools/passwordStore",
|
||||
name: "BackendPasswordStore",
|
||||
component: () => import("@/views/tools/passwordStore/index.vue"),
|
||||
meta: { requiresAuth: true, title: "密码存储" }
|
||||
},
|
||||
// 兼容拼写错误的路径重定向
|
||||
{
|
||||
path: "/apps/erp/dashborad",
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
<div class="node-content">
|
||||
<span class="node-icon">
|
||||
<el-icon v-if="data.is_company">
|
||||
<Office />
|
||||
<OfficeBuilding />
|
||||
</el-icon>
|
||||
<el-icon v-else>
|
||||
<Folder />
|
||||
@@ -203,7 +203,7 @@ import {
|
||||
Plus,
|
||||
Refresh,
|
||||
Setting,
|
||||
Office,
|
||||
OfficeBuilding,
|
||||
Folder,
|
||||
Expand,
|
||||
Fold
|
||||
@@ -220,7 +220,7 @@ import {
|
||||
getEmployeeList,
|
||||
createEmployee,
|
||||
updateEmployee,
|
||||
deleteEmployee,
|
||||
deleteEmployee as apiDeleteEmployee,
|
||||
getOrgSettings
|
||||
} from "@/api/organization";
|
||||
|
||||
@@ -437,7 +437,7 @@ const deleteEmployee = async (data) => {
|
||||
await ElMessageBox.confirm('确认删除这个员工吗?删除后将无法恢复。', '删除确认', {
|
||||
type: 'warning',
|
||||
});
|
||||
await deleteEmployee(data.id);
|
||||
await apiDeleteEmployee(data.id);
|
||||
ElMessage.success('删除成功');
|
||||
await loadEmployeeList(selectedOrg.value.id);
|
||||
} catch (error) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,459 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"server/models"
|
||||
"server/pkg/jwtutil"
|
||||
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
"github.com/beego/beego/v2/core/logs"
|
||||
beego "github.com/beego/beego/v2/server/web"
|
||||
)
|
||||
|
||||
type passwordPayload struct {
|
||||
Platform string `json:"platform"`
|
||||
URL string `json:"url"`
|
||||
Accounts []models.PasswordAccountItem `json:"accounts"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
// PasswordStoreItemDTO 统一返回给前端的结构体,包含解析后的账号列表
|
||||
type PasswordStoreItemDTO struct {
|
||||
ID uint64 `json:"id"`
|
||||
Tid int `json:"tid,omitempty"`
|
||||
Platform string `json:"platform"`
|
||||
URL string `json:"url"`
|
||||
Accounts []models.PasswordAccountItem `json:"accounts"`
|
||||
Remark string `json:"remark"`
|
||||
UserID uint64 `json:"user_id"`
|
||||
CreateTime time.Time `json:"create_time"`
|
||||
UpdateTime *time.Time `json:"update_time"`
|
||||
}
|
||||
|
||||
func accountsToJSON(list []models.PasswordAccountItem) string {
|
||||
cleaned := make([]models.PasswordAccountItem, 0, len(list))
|
||||
for _, item := range list {
|
||||
u := strings.TrimSpace(item.Username)
|
||||
p := strings.TrimSpace(item.Password)
|
||||
reg := strings.TrimSpace(item.RegistrationInfo)
|
||||
rem := strings.TrimSpace(item.Remark)
|
||||
if u == "" && p == "" && reg == "" && rem == "" {
|
||||
continue
|
||||
}
|
||||
cleaned = append(cleaned, models.PasswordAccountItem{
|
||||
Username: u,
|
||||
Password: p,
|
||||
RegistrationInfo: reg,
|
||||
Remark: rem,
|
||||
})
|
||||
}
|
||||
b, err := json.Marshal(cleaned)
|
||||
if err != nil {
|
||||
return "[]"
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func accountsFromJSON(raw string) []models.PasswordAccountItem {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return []models.PasswordAccountItem{}
|
||||
}
|
||||
var list []models.PasswordAccountItem
|
||||
if err := json.Unmarshal([]byte(raw), &list); err == nil && list != nil {
|
||||
return list
|
||||
}
|
||||
return []models.PasswordAccountItem{}
|
||||
}
|
||||
|
||||
func passwordClaims(c *beego.Controller, kind string) (*jwtutil.Claims, error) {
|
||||
p := strings.SplitN(c.Ctx.Request.Header.Get("Authorization"), " ", 2)
|
||||
if len(p) != 2 || p[0] != "Bearer" {
|
||||
return nil, orm.ErrNoRows
|
||||
}
|
||||
cl, e := jwtutil.ParseToken(p[1])
|
||||
if e != nil || cl.UserType != kind || cl.UserID <= 0 {
|
||||
return nil, orm.ErrNoRows
|
||||
}
|
||||
return cl, nil
|
||||
}
|
||||
|
||||
func parsePassword(c *beego.Controller) (passwordPayload, error) {
|
||||
var p passwordPayload
|
||||
b, e := io.ReadAll(c.Ctx.Request.Body)
|
||||
if e == nil {
|
||||
e = json.Unmarshal(b, &p)
|
||||
}
|
||||
p.Platform = strings.TrimSpace(p.Platform)
|
||||
p.URL = strings.TrimSpace(p.URL)
|
||||
p.Remark = strings.TrimSpace(p.Remark)
|
||||
return p, e
|
||||
}
|
||||
|
||||
func passwordReply(c *beego.Controller, status int, msg string, data interface{}) {
|
||||
c.Ctx.Output.SetStatus(status)
|
||||
c.Data["json"] = map[string]interface{}{"code": status, "msg": msg, "data": data}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
func validPassword(p passwordPayload) bool {
|
||||
return p.Platform != ""
|
||||
}
|
||||
|
||||
// ==================== 平台端 Password Store ====================
|
||||
|
||||
type PlatformPasswordStoreController struct{ beego.Controller }
|
||||
|
||||
func (c *PlatformPasswordStoreController) List() {
|
||||
cl, e := passwordClaims(&c.Controller, "platform")
|
||||
if e != nil {
|
||||
passwordReply(&c.Controller, 401, "未登录或无权限", nil)
|
||||
return
|
||||
}
|
||||
qs := models.Orm.QueryTable(new(models.PlatformPasswordStore)).Filter("is_deleted", 0).Filter("user_id", cl.UserID)
|
||||
k := strings.TrimSpace(c.GetString("keyword"))
|
||||
if k != "" {
|
||||
cond := orm.NewCondition().
|
||||
Or("platform__icontains", k).
|
||||
Or("url__icontains", k).
|
||||
Or("accounts__icontains", k).
|
||||
Or("remark__icontains", k)
|
||||
qs = qs.SetCond(orm.NewCondition().And("is_deleted", 0).And("user_id", cl.UserID).AndCond(cond))
|
||||
}
|
||||
total, _ := qs.Count()
|
||||
var list []models.PlatformPasswordStore
|
||||
_, e = qs.OrderBy("-id").Limit(200).All(&list)
|
||||
if e != nil && e != orm.ErrNoRows {
|
||||
logs.Error("[passwordStore] platform list failed: %v", e)
|
||||
passwordReply(&c.Controller, 500, "查询失败: "+e.Error(), nil)
|
||||
return
|
||||
}
|
||||
res := make([]PasswordStoreItemDTO, 0, len(list))
|
||||
for _, row := range list {
|
||||
res = append(res, PasswordStoreItemDTO{
|
||||
ID: row.ID,
|
||||
Platform: row.Platform,
|
||||
URL: row.URL,
|
||||
Accounts: accountsFromJSON(row.Accounts),
|
||||
Remark: row.Remark,
|
||||
UserID: row.UserID,
|
||||
CreateTime: row.CreateTime,
|
||||
UpdateTime: row.UpdateTime,
|
||||
})
|
||||
}
|
||||
passwordReply(&c.Controller, 200, "success", map[string]interface{}{"list": res, "total": total})
|
||||
}
|
||||
|
||||
func (c *PlatformPasswordStoreController) Detail() {
|
||||
cl, e := passwordClaims(&c.Controller, "platform")
|
||||
if e != nil {
|
||||
passwordReply(&c.Controller, 401, "未登录或无权限", nil)
|
||||
return
|
||||
}
|
||||
id, _ := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
|
||||
var v models.PlatformPasswordStore
|
||||
e = models.Orm.QueryTable(new(models.PlatformPasswordStore)).Filter("id", id).Filter("is_deleted", 0).Filter("user_id", cl.UserID).One(&v)
|
||||
if e != nil {
|
||||
passwordReply(&c.Controller, 404, "记录不存在", nil)
|
||||
return
|
||||
}
|
||||
dto := PasswordStoreItemDTO{
|
||||
ID: v.ID,
|
||||
Platform: v.Platform,
|
||||
URL: v.URL,
|
||||
Accounts: accountsFromJSON(v.Accounts),
|
||||
Remark: v.Remark,
|
||||
UserID: v.UserID,
|
||||
CreateTime: v.CreateTime,
|
||||
UpdateTime: v.UpdateTime,
|
||||
}
|
||||
passwordReply(&c.Controller, 200, "success", dto)
|
||||
}
|
||||
|
||||
func (c *PlatformPasswordStoreController) Create() {
|
||||
cl, e := passwordClaims(&c.Controller, "platform")
|
||||
if e != nil {
|
||||
passwordReply(&c.Controller, 401, "未登录或无权限", nil)
|
||||
return
|
||||
}
|
||||
p, e := parsePassword(&c.Controller)
|
||||
if e != nil || !validPassword(p) {
|
||||
passwordReply(&c.Controller, 400, "平台名称不能为空", nil)
|
||||
return
|
||||
}
|
||||
accJSON := accountsToJSON(p.Accounts)
|
||||
v := &models.PlatformPasswordStore{
|
||||
Platform: p.Platform,
|
||||
URL: p.URL,
|
||||
Accounts: accJSON,
|
||||
Remark: p.Remark,
|
||||
UserID: uint64(cl.UserID),
|
||||
}
|
||||
id, e := models.Orm.Insert(v)
|
||||
if e != nil {
|
||||
logs.Error("[passwordStore] platform create failed: %v", e)
|
||||
passwordReply(&c.Controller, 500, "创建失败: "+e.Error(), nil)
|
||||
return
|
||||
}
|
||||
v.ID = uint64(id)
|
||||
dto := PasswordStoreItemDTO{
|
||||
ID: v.ID,
|
||||
Platform: v.Platform,
|
||||
URL: v.URL,
|
||||
Accounts: accountsFromJSON(v.Accounts),
|
||||
Remark: v.Remark,
|
||||
UserID: v.UserID,
|
||||
CreateTime: v.CreateTime,
|
||||
UpdateTime: v.UpdateTime,
|
||||
}
|
||||
passwordReply(&c.Controller, 200, "创建成功", dto)
|
||||
}
|
||||
|
||||
func (c *PlatformPasswordStoreController) Update() {
|
||||
cl, e := passwordClaims(&c.Controller, "platform")
|
||||
if e != nil {
|
||||
passwordReply(&c.Controller, 401, "未登录或无权限", nil)
|
||||
return
|
||||
}
|
||||
id, _ := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
|
||||
p, e := parsePassword(&c.Controller)
|
||||
if e != nil || !validPassword(p) {
|
||||
passwordReply(&c.Controller, 400, "平台名称不能为空", nil)
|
||||
return
|
||||
}
|
||||
var v models.PlatformPasswordStore
|
||||
e = models.Orm.QueryTable(new(models.PlatformPasswordStore)).Filter("id", id).Filter("is_deleted", 0).Filter("user_id", cl.UserID).One(&v)
|
||||
if e != nil {
|
||||
passwordReply(&c.Controller, 404, "记录不存在", nil)
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
accJSON := accountsToJSON(p.Accounts)
|
||||
_, e = models.Orm.QueryTable(new(models.PlatformPasswordStore)).Filter("id", id).Update(map[string]interface{}{
|
||||
"platform": p.Platform,
|
||||
"url": p.URL,
|
||||
"accounts": accJSON,
|
||||
"remark": p.Remark,
|
||||
"update_time": now,
|
||||
})
|
||||
if e != nil {
|
||||
logs.Error("[passwordStore] platform update failed: %v", e)
|
||||
passwordReply(&c.Controller, 500, "更新失败: "+e.Error(), nil)
|
||||
return
|
||||
}
|
||||
v.Platform = p.Platform
|
||||
v.URL = p.URL
|
||||
v.Accounts = accJSON
|
||||
v.Remark = p.Remark
|
||||
v.UpdateTime = &now
|
||||
dto := PasswordStoreItemDTO{
|
||||
ID: v.ID,
|
||||
Platform: v.Platform,
|
||||
URL: v.URL,
|
||||
Accounts: accountsFromJSON(v.Accounts),
|
||||
Remark: v.Remark,
|
||||
UserID: v.UserID,
|
||||
CreateTime: v.CreateTime,
|
||||
UpdateTime: v.UpdateTime,
|
||||
}
|
||||
passwordReply(&c.Controller, 200, "更新成功", dto)
|
||||
}
|
||||
|
||||
func (c *PlatformPasswordStoreController) Delete() {
|
||||
cl, e := passwordClaims(&c.Controller, "platform")
|
||||
if e != nil {
|
||||
passwordReply(&c.Controller, 401, "未登录或无权限", nil)
|
||||
return
|
||||
}
|
||||
id, _ := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
|
||||
n, e := models.Orm.QueryTable(new(models.PlatformPasswordStore)).Filter("id", id).Filter("user_id", cl.UserID).Update(map[string]interface{}{"is_deleted": 1, "delete_time": time.Now()})
|
||||
if e != nil || n == 0 {
|
||||
passwordReply(&c.Controller, 404, "记录不存在", nil)
|
||||
return
|
||||
}
|
||||
passwordReply(&c.Controller, 200, "删除成功", nil)
|
||||
}
|
||||
|
||||
// ==================== 租户端 Password Store ====================
|
||||
|
||||
type BackendPasswordStoreController struct{ beego.Controller }
|
||||
|
||||
func (c *BackendPasswordStoreController) List() {
|
||||
cl, e := passwordClaims(&c.Controller, "backend")
|
||||
if e != nil || cl.TenantId <= 0 {
|
||||
passwordReply(&c.Controller, 401, "未登录或无权限", nil)
|
||||
return
|
||||
}
|
||||
qs := models.Orm.QueryTable(new(models.BackendPasswordStore)).Filter("is_deleted", 0).Filter("tid", cl.TenantId).Filter("user_id", cl.UserID)
|
||||
k := strings.TrimSpace(c.GetString("keyword"))
|
||||
if k != "" {
|
||||
cond := orm.NewCondition().
|
||||
Or("platform__icontains", k).
|
||||
Or("url__icontains", k).
|
||||
Or("accounts__icontains", k).
|
||||
Or("remark__icontains", k)
|
||||
qs = qs.SetCond(orm.NewCondition().And("is_deleted", 0).And("tid", cl.TenantId).And("user_id", cl.UserID).AndCond(cond))
|
||||
}
|
||||
total, _ := qs.Count()
|
||||
var list []models.BackendPasswordStore
|
||||
_, e = qs.OrderBy("-id").Limit(200).All(&list)
|
||||
if e != nil && e != orm.ErrNoRows {
|
||||
logs.Error("[passwordStore] backend list failed: %v", e)
|
||||
passwordReply(&c.Controller, 500, "查询失败: "+e.Error(), nil)
|
||||
return
|
||||
}
|
||||
res := make([]PasswordStoreItemDTO, 0, len(list))
|
||||
for _, row := range list {
|
||||
res = append(res, PasswordStoreItemDTO{
|
||||
ID: row.ID,
|
||||
Tid: row.Tid,
|
||||
Platform: row.Platform,
|
||||
URL: row.URL,
|
||||
Accounts: accountsFromJSON(row.Accounts),
|
||||
Remark: row.Remark,
|
||||
UserID: row.UserID,
|
||||
CreateTime: row.CreateTime,
|
||||
UpdateTime: row.UpdateTime,
|
||||
})
|
||||
}
|
||||
passwordReply(&c.Controller, 200, "success", map[string]interface{}{"list": res, "total": total})
|
||||
}
|
||||
|
||||
func (c *BackendPasswordStoreController) Detail() {
|
||||
cl, e := passwordClaims(&c.Controller, "backend")
|
||||
if e != nil || cl.TenantId <= 0 {
|
||||
passwordReply(&c.Controller, 401, "未登录或无权限", nil)
|
||||
return
|
||||
}
|
||||
id, _ := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
|
||||
var v models.BackendPasswordStore
|
||||
e = models.Orm.QueryTable(new(models.BackendPasswordStore)).Filter("id", id).Filter("tid", cl.TenantId).Filter("is_deleted", 0).Filter("user_id", cl.UserID).One(&v)
|
||||
if e != nil {
|
||||
passwordReply(&c.Controller, 404, "记录不存在", nil)
|
||||
return
|
||||
}
|
||||
dto := PasswordStoreItemDTO{
|
||||
ID: v.ID,
|
||||
Tid: v.Tid,
|
||||
Platform: v.Platform,
|
||||
URL: v.URL,
|
||||
Accounts: accountsFromJSON(v.Accounts),
|
||||
Remark: v.Remark,
|
||||
UserID: v.UserID,
|
||||
CreateTime: v.CreateTime,
|
||||
UpdateTime: v.UpdateTime,
|
||||
}
|
||||
passwordReply(&c.Controller, 200, "success", dto)
|
||||
}
|
||||
|
||||
func (c *BackendPasswordStoreController) Create() {
|
||||
cl, e := passwordClaims(&c.Controller, "backend")
|
||||
if e != nil || cl.TenantId <= 0 {
|
||||
passwordReply(&c.Controller, 401, "未登录或无权限", nil)
|
||||
return
|
||||
}
|
||||
p, e := parsePassword(&c.Controller)
|
||||
if e != nil || !validPassword(p) {
|
||||
passwordReply(&c.Controller, 400, "平台名称不能为空", nil)
|
||||
return
|
||||
}
|
||||
accJSON := accountsToJSON(p.Accounts)
|
||||
v := &models.BackendPasswordStore{
|
||||
Tid: cl.TenantId,
|
||||
Platform: p.Platform,
|
||||
URL: p.URL,
|
||||
Accounts: accJSON,
|
||||
Remark: p.Remark,
|
||||
UserID: uint64(cl.UserID),
|
||||
}
|
||||
id, e := models.Orm.Insert(v)
|
||||
if e != nil {
|
||||
logs.Error("[passwordStore] backend create failed: %v", e)
|
||||
passwordReply(&c.Controller, 500, "创建失败: "+e.Error(), nil)
|
||||
return
|
||||
}
|
||||
v.ID = uint64(id)
|
||||
dto := PasswordStoreItemDTO{
|
||||
ID: v.ID,
|
||||
Tid: v.Tid,
|
||||
Platform: v.Platform,
|
||||
URL: v.URL,
|
||||
Accounts: accountsFromJSON(v.Accounts),
|
||||
Remark: v.Remark,
|
||||
UserID: v.UserID,
|
||||
CreateTime: v.CreateTime,
|
||||
UpdateTime: v.UpdateTime,
|
||||
}
|
||||
passwordReply(&c.Controller, 200, "创建成功", dto)
|
||||
}
|
||||
|
||||
func (c *BackendPasswordStoreController) Update() {
|
||||
cl, e := passwordClaims(&c.Controller, "backend")
|
||||
if e != nil || cl.TenantId <= 0 {
|
||||
passwordReply(&c.Controller, 401, "未登录或无权限", nil)
|
||||
return
|
||||
}
|
||||
id, _ := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
|
||||
p, e := parsePassword(&c.Controller)
|
||||
if e != nil || !validPassword(p) {
|
||||
passwordReply(&c.Controller, 400, "平台名称不能为空", nil)
|
||||
return
|
||||
}
|
||||
var v models.BackendPasswordStore
|
||||
e = models.Orm.QueryTable(new(models.BackendPasswordStore)).Filter("id", id).Filter("tid", cl.TenantId).Filter("is_deleted", 0).Filter("user_id", cl.UserID).One(&v)
|
||||
if e != nil {
|
||||
passwordReply(&c.Controller, 404, "记录不存在", nil)
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
accJSON := accountsToJSON(p.Accounts)
|
||||
_, e = models.Orm.QueryTable(new(models.BackendPasswordStore)).Filter("id", id).Update(map[string]interface{}{
|
||||
"platform": p.Platform,
|
||||
"url": p.URL,
|
||||
"accounts": accJSON,
|
||||
"remark": p.Remark,
|
||||
"update_time": now,
|
||||
})
|
||||
if e != nil {
|
||||
logs.Error("[passwordStore] backend update failed: %v", e)
|
||||
passwordReply(&c.Controller, 500, "更新失败: "+e.Error(), nil)
|
||||
return
|
||||
}
|
||||
v.Platform = p.Platform
|
||||
v.URL = p.URL
|
||||
v.Accounts = accJSON
|
||||
v.Remark = p.Remark
|
||||
v.UpdateTime = &now
|
||||
dto := PasswordStoreItemDTO{
|
||||
ID: v.ID,
|
||||
Tid: v.Tid,
|
||||
Platform: v.Platform,
|
||||
URL: v.URL,
|
||||
Accounts: accountsFromJSON(v.Accounts),
|
||||
Remark: v.Remark,
|
||||
UserID: v.UserID,
|
||||
CreateTime: v.CreateTime,
|
||||
UpdateTime: v.UpdateTime,
|
||||
}
|
||||
passwordReply(&c.Controller, 200, "更新成功", dto)
|
||||
}
|
||||
|
||||
func (c *BackendPasswordStoreController) Delete() {
|
||||
cl, e := passwordClaims(&c.Controller, "backend")
|
||||
if e != nil || cl.TenantId <= 0 {
|
||||
passwordReply(&c.Controller, 401, "未登录或无权限", nil)
|
||||
return
|
||||
}
|
||||
id, _ := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
|
||||
n, e := models.Orm.QueryTable(new(models.BackendPasswordStore)).Filter("id", id).Filter("tid", cl.TenantId).Filter("user_id", cl.UserID).Update(map[string]interface{}{"is_deleted": 1, "delete_time": time.Now()})
|
||||
if e != nil || n == 0 {
|
||||
passwordReply(&c.Controller, 404, "记录不存在", nil)
|
||||
return
|
||||
}
|
||||
passwordReply(&c.Controller, 200, "删除成功", nil)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
// BackendPasswordStore 租户端密码存储,支持一个平台下挂载多个账号,按租户隔离
|
||||
type BackendPasswordStore struct {
|
||||
ID uint64 `orm:"column(id);pk;auto" json:"id"`
|
||||
Tid int `orm:"column(tid)" json:"tid"`
|
||||
Platform string `orm:"column(platform);size(100)" json:"platform"`
|
||||
URL string `orm:"column(url);size(500)" json:"url"`
|
||||
Accounts string `orm:"column(accounts);type(text);null" json:"accounts"`
|
||||
Remark string `orm:"column(remark);type(text);null" json:"remark"`
|
||||
UserID uint64 `orm:"column(user_id)" json:"user_id"`
|
||||
IsDeleted int8 `orm:"column(is_deleted);default(0)" json:"is_deleted"`
|
||||
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
|
||||
UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"`
|
||||
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
|
||||
}
|
||||
|
||||
func (m *BackendPasswordStore) TableName() string { return "yz_backend_password_store" }
|
||||
@@ -68,6 +68,8 @@ func Init(_ string) {
|
||||
new(PlatformAccountPoolCodex),
|
||||
new(PlatformNotebook),
|
||||
new(PlatformAgentApi),
|
||||
new(PlatformPasswordStore),
|
||||
new(BackendPasswordStore),
|
||||
|
||||
new(CmsArticleCategory),
|
||||
new(CmsArticle),
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
// PasswordAccountItem 平台下挂载的单个账号信息
|
||||
type PasswordAccountItem struct {
|
||||
Username string `json:"username"` // 账号 / 用户名 / 邮箱 / 手机号
|
||||
Password string `json:"password"` // 密码 (明文存储和返回)
|
||||
RegistrationInfo string `json:"registration_info"` // 注册信息 (绑定手机、邮箱、注册时间、实名信息等)
|
||||
Remark string `json:"remark"` // 账号备注 (如主账号、备用账号、VIP等)
|
||||
}
|
||||
|
||||
// PlatformPasswordStore 平台端密码存储,支持一个平台下挂载多个账号
|
||||
type PlatformPasswordStore struct {
|
||||
ID uint64 `orm:"column(id);pk;auto" json:"id"`
|
||||
Platform string `orm:"column(platform);size(100)" json:"platform"`
|
||||
URL string `orm:"column(url);size(500)" json:"url"`
|
||||
Accounts string `orm:"column(accounts);type(text);null" json:"accounts"`
|
||||
Remark string `orm:"column(remark);type(text);null" json:"remark"`
|
||||
UserID uint64 `orm:"column(user_id)" json:"user_id"`
|
||||
IsDeleted int8 `orm:"column(is_deleted);default(0)" json:"is_deleted"`
|
||||
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
|
||||
UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"`
|
||||
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
|
||||
}
|
||||
|
||||
func (m *PlatformPasswordStore) TableName() string { return "yz_platform_password_store" }
|
||||
@@ -206,6 +206,13 @@ func RegisterAuthRoutes() {
|
||||
beego.Router("/backend/domain/tenant/toggleStatus", &controllers.BackendTenantDomainController{}, "post:ToggleStatus")
|
||||
beego.Router("/backend/domain/tenant/delete/:id", &controllers.BackendTenantDomainController{}, "delete:Delete")
|
||||
|
||||
// 密码存储管理
|
||||
beego.Router("/backend/passwordStore/list", &controllers.BackendPasswordStoreController{}, "get:List")
|
||||
beego.Router("/backend/passwordStore/detail/:id", &controllers.BackendPasswordStoreController{}, "get:Detail")
|
||||
beego.Router("/backend/passwordStore/create", &controllers.BackendPasswordStoreController{}, "post:Create")
|
||||
beego.Router("/backend/passwordStore/update/:id", &controllers.BackendPasswordStoreController{}, "post:Update")
|
||||
beego.Router("/backend/passwordStore/delete/:id", &controllers.BackendPasswordStoreController{}, "delete:Delete")
|
||||
|
||||
// 记事本管理
|
||||
beego.Router("/backend/notebook/list", &controllers.BackendNotebookController{}, "get:List")
|
||||
beego.Router("/backend/notebook/detail/:id", &controllers.BackendNotebookController{}, "get:Detail")
|
||||
|
||||
@@ -268,6 +268,13 @@ func Register() {
|
||||
beego.Router("/platform/notebook/update/:id", &controllers.PlatformNotebookController{}, "post:Update")
|
||||
beego.Router("/platform/notebook/delete/:id", &controllers.PlatformNotebookController{}, "delete:Delete")
|
||||
|
||||
// 密码存储管理
|
||||
beego.Router("/platform/passwordStore/list", &controllers.PlatformPasswordStoreController{}, "get:List")
|
||||
beego.Router("/platform/passwordStore/detail/:id", &controllers.PlatformPasswordStoreController{}, "get:Detail")
|
||||
beego.Router("/platform/passwordStore/create", &controllers.PlatformPasswordStoreController{}, "post:Create")
|
||||
beego.Router("/platform/passwordStore/update/:id", &controllers.PlatformPasswordStoreController{}, "post:Update")
|
||||
beego.Router("/platform/passwordStore/delete/:id", &controllers.PlatformPasswordStoreController{}, "delete:Delete")
|
||||
|
||||
// 智能体API管理(yz_platform_agent_api)
|
||||
// 注意:固定路径需先于 /:id 通配路径注册,避免 list/test/batchDelete 被当作 ID 解析
|
||||
beego.Router("/platform/agentApi/list", &controllers.PlatformAgentApiController{}, "get:List")
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
const resource = '/platform/passwordStore'
|
||||
export const getPasswordStoreList = (params) => request({ url: `${resource}/list`, method: 'get', params })
|
||||
export const getPasswordStoreDetail = (id) => request({ url: `${resource}/detail/${id}`, method: 'get' })
|
||||
export const createPasswordStore = (data) => request({ url: `${resource}/create`, method: 'post', data })
|
||||
export const updatePasswordStore = (id, data) => request({ url: `${resource}/update/${id}`, method: 'post', data })
|
||||
export const deletePasswordStore = (id) => request({ url: `${resource}/delete/${id}`, method: 'delete' })
|
||||
@@ -21,6 +21,12 @@ const staticMainChildren = [
|
||||
component: () => import("@/views/system/email/index.vue"),
|
||||
meta: { requiresAuth: true, title: "邮箱管理" }
|
||||
},
|
||||
{
|
||||
path: "/tools/passwordStore",
|
||||
name: "PlatformPasswordStore",
|
||||
component: () => import("@/views/tools/passwordStore/index.vue"),
|
||||
meta: { requiresAuth: true, title: "密码存储" }
|
||||
},
|
||||
{
|
||||
path: "/template/index",
|
||||
name: "PlatformTemplateManage",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,78 @@
|
||||
-- ============================================================
|
||||
-- 密码存储:单账号结构 -> 一个平台挂载多个账号(accounts JSON)
|
||||
--
|
||||
-- 使用说明:
|
||||
-- 1. 先备份:
|
||||
-- mysqldump -u用户 -p 库名 yz_platform_password_store yz_backend_password_store > pwd_backup.sql
|
||||
-- 2. 按顺序执行下面的「第 1 步」「第 2 步」「第 3 步」。
|
||||
-- 3. 如果某条 ALTER 报 "Duplicate column name 'accounts'",说明该列已存在,
|
||||
-- 忽略该条错误继续往下执行即可。
|
||||
-- 4. 「第 4 步」删除旧列不可逆,确认页面数据显示正常后再手动执行。
|
||||
-- ============================================================
|
||||
|
||||
|
||||
-- ---------- 第 1 步:新增 accounts 列 ----------
|
||||
-- 报 Duplicate column name 表示已加过,忽略即可。
|
||||
|
||||
ALTER TABLE `yz_platform_password_store`
|
||||
ADD COLUMN `accounts` longtext NULL
|
||||
COMMENT '账号列表JSON: [{"username":"","password":"","registration_info":"","remark":""}]'
|
||||
AFTER `url`;
|
||||
|
||||
ALTER TABLE `yz_backend_password_store`
|
||||
ADD COLUMN `accounts` longtext NULL
|
||||
COMMENT '账号列表JSON: [{"username":"","password":"","registration_info":"","remark":""}]'
|
||||
AFTER `url`;
|
||||
|
||||
-- 旧表可能没有 delete_time 列,模型里有该字段,一并补上。
|
||||
-- 报 Duplicate column name 同样忽略。
|
||||
ALTER TABLE `yz_platform_password_store` ADD COLUMN `delete_time` datetime NULL;
|
||||
ALTER TABLE `yz_backend_password_store` ADD COLUMN `delete_time` datetime NULL;
|
||||
|
||||
|
||||
-- ---------- 第 2 步:把旧的单账号数据搬进 accounts ----------
|
||||
-- 只处理 accounts 仍为空、且旧列确实有内容的行,包装成只含一条账号的 JSON 数组。
|
||||
-- 如果报 Unknown column 'username',说明旧列已经删掉了,无需迁移,跳过这两条。
|
||||
|
||||
UPDATE `yz_platform_password_store`
|
||||
SET `accounts` = JSON_ARRAY(JSON_OBJECT(
|
||||
'username', IFNULL(`username`, ''),
|
||||
'password', IFNULL(`password`, ''),
|
||||
'registration_info', IFNULL(`registration_info`, ''),
|
||||
'remark', ''
|
||||
))
|
||||
WHERE (`accounts` IS NULL OR `accounts` = '' OR `accounts` = '[]')
|
||||
AND (IFNULL(`username`, '') <> ''
|
||||
OR IFNULL(`password`, '') <> ''
|
||||
OR IFNULL(`registration_info`, '') <> '');
|
||||
|
||||
UPDATE `yz_backend_password_store`
|
||||
SET `accounts` = JSON_ARRAY(JSON_OBJECT(
|
||||
'username', IFNULL(`username`, ''),
|
||||
'password', IFNULL(`password`, ''),
|
||||
'registration_info', IFNULL(`registration_info`, ''),
|
||||
'remark', ''
|
||||
))
|
||||
WHERE (`accounts` IS NULL OR `accounts` = '' OR `accounts` = '[]')
|
||||
AND (IFNULL(`username`, '') <> ''
|
||||
OR IFNULL(`password`, '') <> ''
|
||||
OR IFNULL(`registration_info`, '') <> '');
|
||||
|
||||
|
||||
-- ---------- 第 3 步:accounts 为空的行统一补成空数组 ----------
|
||||
|
||||
UPDATE `yz_platform_password_store` SET `accounts` = '[]'
|
||||
WHERE `accounts` IS NULL OR `accounts` = '';
|
||||
|
||||
UPDATE `yz_backend_password_store` SET `accounts` = '[]'
|
||||
WHERE `accounts` IS NULL OR `accounts` = '';
|
||||
|
||||
|
||||
-- ---------- 第 4 步(可选,不可逆):删除旧的单账号列 ----------
|
||||
-- 先在页面上确认历史数据都能正常显示,再手动执行下面两条。
|
||||
-- 旧表这三列是 NOT NULL DEFAULT '',不删也不影响新代码运行。
|
||||
|
||||
-- ALTER TABLE `yz_platform_password_store`
|
||||
-- DROP COLUMN `username`, DROP COLUMN `password`, DROP COLUMN `registration_info`;
|
||||
-- ALTER TABLE `yz_backend_password_store`
|
||||
-- DROP COLUMN `username`, DROP COLUMN `password`, DROP COLUMN `registration_info`;
|
||||
@@ -0,0 +1,37 @@
|
||||
-- 平台端和租户后台密码存储表。支持一个平台下挂载多个账号(每个账号包含账号、密码、注册信息、备注)。
|
||||
CREATE TABLE IF NOT EXISTS `yz_platform_password_store` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`platform` varchar(100) NOT NULL DEFAULT '' COMMENT '平台名称',
|
||||
`url` varchar(500) NOT NULL DEFAULT '' COMMENT '平台网址/登录地址',
|
||||
`accounts` longtext COMMENT '账号列表JSON: [{"username":"","password":"","registration_info":"","remark":""}]',
|
||||
`remark` text COMMENT '平台备注',
|
||||
`user_id` bigint unsigned NOT NULL COMMENT '所属用户ID',
|
||||
`is_deleted` tinyint NOT NULL DEFAULT 0,
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` datetime DEFAULT NULL,
|
||||
`delete_time` datetime DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_platform_user` (`platform`,`user_id`),
|
||||
KEY `idx_user_deleted` (`user_id`,`is_deleted`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='平台密码存储';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `yz_backend_password_store` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`tid` bigint NOT NULL COMMENT '租户ID',
|
||||
`platform` varchar(100) NOT NULL DEFAULT '' COMMENT '平台名称',
|
||||
`url` varchar(500) NOT NULL DEFAULT '' COMMENT '平台网址/登录地址',
|
||||
`accounts` longtext COMMENT '账号列表JSON: [{"username":"","password":"","registration_info":"","remark":""}]',
|
||||
`remark` text COMMENT '平台备注',
|
||||
`user_id` bigint unsigned NOT NULL COMMENT '所属用户ID',
|
||||
`is_deleted` tinyint NOT NULL DEFAULT 0,
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` datetime DEFAULT NULL,
|
||||
`delete_time` datetime DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_tid_user` (`tid`,`user_id`),
|
||||
KEY `idx_tid_platform` (`tid`,`platform`),
|
||||
KEY `idx_tid_deleted` (`tid`,`is_deleted`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='租户密码存储';
|
||||
|
||||
-- 已经按旧的「单账号」结构建过表的库,请执行
|
||||
-- sql/alter_password_store_to_multi_accounts.sql 完成升级与数据迁移。
|
||||
Reference in New Issue
Block a user