403 lines
13 KiB
Go
403 lines
13 KiB
Go
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)
|
||
}
|