Files
yunzerwebsiteallinone/go/controllers/app_password_store.go
T
2026-11-03 11:13:59 +08:00

403 lines
13 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package controllers
import (
"encoding/json"
"strconv"
"strings"
"time"
"server/middleware"
"server/models"
"server/pkg/identity"
"server/pkg/jwtutil"
"server/services/gueststore"
"github.com/beego/beego/v2/client/orm"
"github.com/beego/beego/v2/core/logs"
beego "github.com/beego/beego/v2/server/web"
)
// ==================== 移动端 PasswordStore ====================
//
// 移动端「账号管理」(密码库)接口。与 Web 端 /backend/passwordStore 同一套数据
// (yz_backend_password_store,一个平台下挂多个账号),按 JWT 中的 tid + user_id
// 隔离,小程序「账号管理」页面调用。
type AppPasswordStoreController struct {
beego.Controller
}
// 放行移动端、租户端与游客令牌:游客 tid=0 合法,数据进 gueststore 而非业务表。
func (c *AppPasswordStoreController) appPwdClaims() (*jwtutil.Claims, error) {
claims, err := identity.Claims(c.Ctx.Request.Header.Get("Authorization"), identity.EndApp, identity.EndBackend, identity.EndGuest)
if err != nil {
return nil, err
}
if identity.IsGuest(claims) {
if claims.UserID <= 0 {
return nil, orm.ErrNoRows
}
return claims, nil
}
if claims.UserID <= 0 || claims.TenantId <= 0 {
return nil, orm.ErrNoRows
}
return claims, nil
}
// GetList GET /api/passwordStore/list?keyword=
func (c *AppPasswordStoreController) GetList() {
cl, e := c.appPwdClaims()
if e != nil {
passwordReply(&c.Controller, 401, "未登录或无权限", nil)
return
}
if identity.IsGuest(cl) {
c.guestList(cl, strings.TrimSpace(c.GetString("keyword")))
return
}
qs := models.Orm.QueryTable(new(models.BackendPasswordStore)).
Filter("is_deleted", 0).Filter("tid", cl.TenantId).Filter("user_id", cl.UserID)
if k := strings.TrimSpace(c.GetString("keyword")); 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))
}
var rows []models.BackendPasswordStore
if _, e = qs.OrderBy("-id").All(&rows); e != nil && e != orm.ErrNoRows {
logs.Error("[passwordStore] app list failed: %v", e)
passwordReply(&c.Controller, 500, "查询失败: "+e.Error(), nil)
return
}
res := make([]PasswordStoreItemDTO, 0, len(rows))
for _, row := range rows {
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": len(res)})
}
// GetDetail GET /api/passwordStore/detail/:id
func (c *AppPasswordStoreController) GetDetail() {
cl, e := c.appPwdClaims()
if e != nil {
passwordReply(&c.Controller, 401, "未登录或无权限", nil)
return
}
id, e := strconv.Atoi(c.Ctx.Input.Param(":id"))
if e != nil {
passwordReply(&c.Controller, 400, "参数错误", nil)
return
}
if identity.IsGuest(cl) {
c.guestDetail(cl, uint64(id))
return
}
var m models.BackendPasswordStore
e = models.Orm.QueryTable(new(models.BackendPasswordStore)).
Filter("id", id).Filter("is_deleted", 0).Filter("tid", cl.TenantId).Filter("user_id", cl.UserID).One(&m)
if e == orm.ErrNoRows {
passwordReply(&c.Controller, 404, "记录不存在", nil)
return
}
if e != nil {
logs.Error("[passwordStore] app detail failed: %v", e)
passwordReply(&c.Controller, 500, "查询失败: "+e.Error(), nil)
return
}
passwordReply(&c.Controller, 200, "success", PasswordStoreItemDTO{
ID: m.ID, Tid: m.Tid, Platform: m.Platform, URL: m.URL,
Accounts: accountsFromJSON(m.Accounts), Remark: m.Remark,
UserID: m.UserID, CreateTime: m.CreateTime, UpdateTime: m.UpdateTime,
})
}
// Create POST /api/passwordStore/create
func (c *AppPasswordStoreController) Create() {
cl, e := c.appPwdClaims()
if e != nil {
passwordReply(&c.Controller, 401, "未登录或无权限", nil)
return
}
p, e := parsePassword(&c.Controller)
if e != nil {
passwordReply(&c.Controller, 400, e.Error(), nil)
return
}
if !validPassword(p) {
passwordReply(&c.Controller, 400, "请输入平台名称", nil)
return
}
if e := validateAccountsUnique(p.Accounts); e != nil {
passwordReply(&c.Controller, 400, e.Error(), nil)
return
}
if identity.IsGuest(cl) {
c.guestCreate(cl, p)
return
}
qs := models.Orm.QueryTable(new(models.BackendPasswordStore)).
Filter("is_deleted", 0).Filter("tid", cl.TenantId).Filter("user_id", cl.UserID)
if existing, e := findSamePlatformRecord(qs, 0, p.Platform, p.URL); e != nil {
passwordReply(&c.Controller, 500, "查询失败: "+e.Error(), nil)
return
} else if existing > 0 {
passwordReply(&c.Controller, 400, "同平台同网址记录已存在,请勿重复添加", nil)
return
}
m := models.BackendPasswordStore{
Tid: cl.TenantId, UserID: uint64(cl.UserID),
Platform: p.Platform, URL: p.URL,
Accounts: accountsToJSON(p.Accounts), Remark: p.Remark,
}
if _, e = models.Orm.Insert(&m); e != nil {
logs.Error("[passwordStore] app create failed: %v", e)
passwordReply(&c.Controller, 500, "创建失败: "+e.Error(), nil)
return
}
passwordReply(&c.Controller, 200, "success", PasswordStoreItemDTO{
ID: m.ID, Tid: m.Tid, Platform: m.Platform, URL: m.URL,
Accounts: p.Accounts, Remark: m.Remark,
UserID: m.UserID, CreateTime: m.CreateTime, UpdateTime: m.UpdateTime,
})
}
// Update POST /api/passwordStore/update/:id
func (c *AppPasswordStoreController) Update() {
cl, e := c.appPwdClaims()
if e != nil {
passwordReply(&c.Controller, 401, "未登录或无权限", nil)
return
}
id, e := strconv.Atoi(c.Ctx.Input.Param(":id"))
if e != nil {
passwordReply(&c.Controller, 400, "参数错误", nil)
return
}
p, e := parsePassword(&c.Controller)
if e != nil {
passwordReply(&c.Controller, 400, e.Error(), nil)
return
}
if !validPassword(p) {
passwordReply(&c.Controller, 400, "请输入平台名称", nil)
return
}
if e := validateAccountsUnique(p.Accounts); e != nil {
passwordReply(&c.Controller, 400, e.Error(), nil)
return
}
if identity.IsGuest(cl) {
c.guestUpdate(cl, uint64(id), p)
return
}
var m models.BackendPasswordStore
e = models.Orm.QueryTable(new(models.BackendPasswordStore)).
Filter("id", id).Filter("is_deleted", 0).Filter("tid", cl.TenantId).Filter("user_id", cl.UserID).One(&m)
if e == orm.ErrNoRows {
passwordReply(&c.Controller, 404, "记录不存在", nil)
return
}
if e != nil {
logs.Error("[passwordStore] app update query failed: %v", e)
passwordReply(&c.Controller, 500, "查询失败: "+e.Error(), nil)
return
}
qs := models.Orm.QueryTable(new(models.BackendPasswordStore)).
Filter("is_deleted", 0).Filter("tid", cl.TenantId).Filter("user_id", cl.UserID)
if existing, e := findSamePlatformRecord(qs, m.ID, p.Platform, p.URL); e != nil {
passwordReply(&c.Controller, 500, "查询失败: "+e.Error(), nil)
return
} else if existing > 0 {
passwordReply(&c.Controller, 400, "同平台同网址记录已存在,请勿重复添加", nil)
return
}
m.Platform = p.Platform
m.URL = p.URL
m.Remark = p.Remark
m.Accounts = accountsToJSON(p.Accounts)
if _, e = models.Orm.Update(&m); e != nil {
logs.Error("[passwordStore] app update failed: %v", e)
passwordReply(&c.Controller, 500, "更新失败: "+e.Error(), nil)
return
}
passwordReply(&c.Controller, 200, "success", PasswordStoreItemDTO{
ID: m.ID, Tid: m.Tid, Platform: m.Platform, URL: m.URL,
Accounts: p.Accounts, Remark: m.Remark,
UserID: m.UserID, CreateTime: m.CreateTime, UpdateTime: m.UpdateTime,
})
}
// Delete DELETE /api/passwordStore/delete/:id
func (c *AppPasswordStoreController) Delete() {
cl, e := c.appPwdClaims()
if e != nil {
passwordReply(&c.Controller, 401, "未登录或无权限", nil)
return
}
id, e := strconv.Atoi(c.Ctx.Input.Param(":id"))
if e != nil {
passwordReply(&c.Controller, 400, "参数错误", nil)
return
}
if identity.IsGuest(cl) {
if err := gueststore.Delete(uint64(cl.UserID), guestModulePasswordStore, uint64(id)); err != nil {
if err == gueststore.ErrNotFound {
passwordReply(&c.Controller, 404, "记录不存在", nil)
return
}
passwordReply(&c.Controller, 500, "删除失败", nil)
return
}
passwordReply(&c.Controller, 200, "success", nil)
return
}
var m models.BackendPasswordStore
e = models.Orm.QueryTable(new(models.BackendPasswordStore)).
Filter("id", id).Filter("is_deleted", 0).Filter("tid", cl.TenantId).Filter("user_id", cl.UserID).One(&m)
if e == orm.ErrNoRows {
passwordReply(&c.Controller, 404, "记录不存在", nil)
return
}
if e != nil {
logs.Error("[passwordStore] app delete query failed: %v", e)
passwordReply(&c.Controller, 500, "查询失败: "+e.Error(), nil)
return
}
if _, e = models.Orm.Delete(&m); e != nil {
logs.Error("[passwordStore] app delete failed: %v", e)
passwordReply(&c.Controller, 500, "删除失败: "+e.Error(), nil)
return
}
middleware.WriteDeleteLog(uint64(cl.UserID), cl.TenantId, "passwordStore", m.Platform)
passwordReply(&c.Controller, 200, "success", nil)
}
// ---------- 游客分支(免登录态):数据存 yz_guest_data,响应结构与登录态一致 ----------
func guestPwdFull(r gueststore.Row) PasswordStoreItemDTO {
var it PasswordStoreItemDTO
if json.Unmarshal(r.Payload, &it) != nil {
it = PasswordStoreItemDTO{}
}
it.ID = r.ID
it.Tid = 0
it.UserID = 0
it.CreateTime = r.CreateTime
it.UpdateTime = r.UpdateTime
if it.Accounts == nil {
it.Accounts = []models.PasswordAccountItem{}
}
return it
}
// guestPwdSamePlatform 与登录态 findSamePlatformRecord 同语义:平台+网址重复。
func guestPwdSamePlatform(it PasswordStoreItemDTO, platform, url string, excludeID uint64) bool {
if it.ID == excludeID {
return false
}
return strings.EqualFold(strings.TrimSpace(it.Platform), strings.TrimSpace(platform)) &&
strings.TrimSpace(it.URL) == strings.TrimSpace(url)
}
func guestPwdKeywordHit(it PasswordStoreItemDTO, k string) bool {
k = strings.ToLower(k)
if strings.Contains(strings.ToLower(it.Platform), k) ||
strings.Contains(strings.ToLower(it.URL), k) ||
strings.Contains(strings.ToLower(it.Remark), k) {
return true
}
for _, a := range it.Accounts {
if strings.Contains(strings.ToLower(a.Username), k) {
return true
}
}
return false
}
func (c *AppPasswordStoreController) guestList(cl *jwtutil.Claims, keyword string) {
rows, err := gueststore.List(uint64(cl.UserID), guestModulePasswordStore)
if err != nil {
passwordReply(&c.Controller, 500, "查询失败", nil)
return
}
res := make([]PasswordStoreItemDTO, 0, len(rows))
for _, r := range rows {
it := guestPwdFull(r)
if keyword != "" && !guestPwdKeywordHit(it, keyword) {
continue
}
res = append(res, it)
}
passwordReply(&c.Controller, 200, "success", map[string]interface{}{"list": res, "total": len(res)})
}
func (c *AppPasswordStoreController) guestDetail(cl *jwtutil.Claims, id uint64) {
r, err := gueststore.Get(uint64(cl.UserID), guestModulePasswordStore, id)
if err != nil {
passwordReply(&c.Controller, 404, "记录不存在", nil)
return
}
passwordReply(&c.Controller, 200, "success", guestPwdFull(r))
}
func (c *AppPasswordStoreController) guestCreate(cl *jwtutil.Claims, p passwordPayload) {
rows, err := gueststore.List(uint64(cl.UserID), guestModulePasswordStore)
if err != nil {
passwordReply(&c.Controller, 500, "查询失败", nil)
return
}
for _, r := range rows {
if guestPwdSamePlatform(guestPwdFull(r), p.Platform, p.URL, 0) {
passwordReply(&c.Controller, 400, "同平台同网址记录已存在,请勿重复添加", nil)
return
}
}
it := PasswordStoreItemDTO{
Platform: p.Platform, URL: p.URL, Accounts: p.Accounts, Remark: p.Remark,
CreateTime: time.Now(),
}
newID, err := gueststore.Insert(uint64(cl.UserID), guestModulePasswordStore, it)
if err != nil {
passwordReply(&c.Controller, 500, "创建失败", nil)
return
}
it.ID = newID
passwordReply(&c.Controller, 200, "success", it)
}
func (c *AppPasswordStoreController) guestUpdate(cl *jwtutil.Claims, id uint64, p passwordPayload) {
r, err := gueststore.Get(uint64(cl.UserID), guestModulePasswordStore, id)
if err != nil {
passwordReply(&c.Controller, 404, "记录不存在", nil)
return
}
rows, err := gueststore.List(uint64(cl.UserID), guestModulePasswordStore)
if err != nil {
passwordReply(&c.Controller, 500, "查询失败", nil)
return
}
for _, rr := range rows {
if guestPwdSamePlatform(guestPwdFull(rr), p.Platform, p.URL, id) {
passwordReply(&c.Controller, 400, "同平台同网址记录已存在,请勿重复添加", nil)
return
}
}
it := guestPwdFull(r)
it.Platform = p.Platform
it.URL = p.URL
it.Accounts = p.Accounts
it.Remark = p.Remark
if err = gueststore.Update(uint64(cl.UserID), guestModulePasswordStore, id, it); err != nil {
passwordReply(&c.Controller, 500, "更新失败", nil)
return
}
passwordReply(&c.Controller, 200, "success", it)
}